1/* 2 +----------------------------------------------------------------------+ 3 | PHP Version 5 | 4 +----------------------------------------------------------------------+ 5 | Copyright (c) 1997-2013 The PHP Group | 6 +----------------------------------------------------------------------+ 7 | This source file is subject to version 3.01 of the PHP license, | 8 | that is bundled with this package in the file LICENSE, and is | 9 | available through the world-wide-web at the following url: | 10 | http://www.php.net/license/3_01.txt | 11 | If you did not receive a copy of the PHP license and are unable to | 12 | obtain it through the world-wide-web, please send a note to | 13 | license@php.net so we can mail you a copy immediately. | 14 +----------------------------------------------------------------------+ 15 | Author: Sascha Schumann <sascha@schumann.cx> | 16 +----------------------------------------------------------------------+ 17*/ 18 19/* $Id$ */ 20 21#include "php.h" 22 23#ifdef HAVE_UNISTD_H 24#include <unistd.h> 25#endif 26#ifdef HAVE_LIMITS_H 27#include <limits.h> 28#endif 29 30#include <stdio.h> 31#include <stdlib.h> 32#include <string.h> 33 34#include "php_ini.h" 35#include "php_globals.h" 36#define STATE_TAG SOME_OTHER_STATE_TAG 37#include "basic_functions.h" 38#include "url.h" 39#undef STATE_TAG 40 41#define url_scanner url_scanner_ex 42 43#include "php_smart_str.h" 44 45static PHP_INI_MH(OnUpdateTags) 46{ 47 url_adapt_state_ex_t *ctx; 48 char *key; 49 char *lasts; 50 char *tmp; 51 52 ctx = &BG(url_adapt_state_ex); 53 54 tmp = estrndup(new_value, new_value_length); 55 56 if (ctx->tags) 57 zend_hash_destroy(ctx->tags); 58 else { 59 ctx->tags = malloc(sizeof(HashTable)); 60 if (!ctx->tags) { 61 return FAILURE; 62 } 63 } 64 65 zend_hash_init(ctx->tags, 0, NULL, NULL, 1); 66 67 for (key = php_strtok_r(tmp, ",", &lasts); 68 key; 69 key = php_strtok_r(NULL, ",", &lasts)) { 70 char *val; 71 72 val = strchr(key, '='); 73 if (val) { 74 char *q; 75 int keylen; 76 77 *val++ = '\0'; 78 for (q = key; *q; q++) 79 *q = tolower(*q); 80 keylen = q - key; 81 /* key is stored withOUT NUL 82 val is stored WITH NUL */ 83 zend_hash_add(ctx->tags, key, keylen, val, strlen(val)+1, NULL); 84 } 85 } 86 87 efree(tmp); 88 89 return SUCCESS; 90} 91 92PHP_INI_BEGIN() 93 STD_PHP_INI_ENTRY("url_rewriter.tags", "a=href,area=href,frame=src,form=,fieldset=", PHP_INI_ALL, OnUpdateTags, url_adapt_state_ex, php_basic_globals, basic_globals) 94PHP_INI_END() 95 96/*!re2c 97any = [\000-\377]; 98N = (any\[<]); 99alpha = [a-zA-Z]; 100alphanamespace = [a-zA-Z:]; 101alphadash = ([a-zA-Z] | "-"); 102*/ 103 104#define YYFILL(n) goto done 105#define YYCTYPE unsigned char 106#define YYCURSOR p 107#define YYLIMIT q 108#define YYMARKER r 109 110static inline void append_modified_url(smart_str *url, smart_str *dest, smart_str *url_app, const char *separator) 111{ 112 register const char *p, *q; 113 const char *bash = NULL; 114 const char *sep = "?"; 115 116 q = (p = url->c) + url->len; 117 118scan: 119/*!re2c 120 ":" { smart_str_append(dest, url); return; } 121 "?" { sep = separator; goto scan; } 122 "#" { bash = p - 1; goto done; } 123 (any\[:?#])+ { goto scan; } 124*/ 125done: 126 127 /* Don't modify URLs of the format "#mark" */ 128 if (bash && bash - url->c == 0) { 129 smart_str_append(dest, url); 130 return; 131 } 132 133 if (bash) 134 smart_str_appendl(dest, url->c, bash - url->c); 135 else 136 smart_str_append(dest, url); 137 138 smart_str_appends(dest, sep); 139 smart_str_append(dest, url_app); 140 141 if (bash) 142 smart_str_appendl(dest, bash, q - bash); 143} 144 145 146#undef YYFILL 147#undef YYCTYPE 148#undef YYCURSOR 149#undef YYLIMIT 150#undef YYMARKER 151 152static inline void tag_arg(url_adapt_state_ex_t *ctx, char quotes, char type TSRMLS_DC) 153{ 154 char f = 0; 155 156 if (strncasecmp(ctx->arg.c, ctx->lookup_data, ctx->arg.len) == 0) 157 f = 1; 158 159 if (quotes) 160 smart_str_appendc(&ctx->result, type); 161 if (f) { 162 append_modified_url(&ctx->val, &ctx->result, &ctx->url_app, PG(arg_separator).output); 163 } else { 164 smart_str_append(&ctx->result, &ctx->val); 165 } 166 if (quotes) 167 smart_str_appendc(&ctx->result, type); 168} 169 170enum { 171 STATE_PLAIN = 0, 172 STATE_TAG, 173 STATE_NEXT_ARG, 174 STATE_ARG, 175 STATE_BEFORE_VAL, 176 STATE_VAL 177}; 178 179#define YYFILL(n) goto stop 180#define YYCTYPE unsigned char 181#define YYCURSOR xp 182#define YYLIMIT end 183#define YYMARKER q 184#define STATE ctx->state 185 186#define STD_PARA url_adapt_state_ex_t *ctx, char *start, char *YYCURSOR TSRMLS_DC 187#define STD_ARGS ctx, start, xp TSRMLS_CC 188 189#if SCANNER_DEBUG 190#define scdebug(x) printf x 191#else 192#define scdebug(x) 193#endif 194 195static inline void passthru(STD_PARA) 196{ 197 scdebug(("appending %d chars, starting with %c\n", YYCURSOR-start, *start)); 198 smart_str_appendl(&ctx->result, start, YYCURSOR - start); 199} 200 201/* 202 * This function appends a hidden input field after a <form> or 203 * <fieldset>. The latter is important for XHTML. 204 */ 205 206static void handle_form(STD_PARA) 207{ 208 int doit = 0; 209 210 if (ctx->form_app.len > 0) { 211 switch (ctx->tag.len) { 212 case sizeof("form") - 1: 213 if (!strncasecmp(ctx->tag.c, "form", sizeof("form") - 1)) { 214 doit = 1; 215 } 216 if (doit && ctx->val.c && ctx->lookup_data && *ctx->lookup_data) { 217 char *e, *p = zend_memnstr(ctx->val.c, "://", sizeof("://") - 1, ctx->val.c + ctx->val.len); 218 if (p) { 219 e = memchr(p, '/', (ctx->val.c + ctx->val.len) - p); 220 if (!e) { 221 e = ctx->val.c + ctx->val.len; 222 } 223 if ((e - p) && strncasecmp(p, ctx->lookup_data, (e - p))) { 224 doit = 0; 225 } 226 } 227 } 228 break; 229 230 case sizeof("fieldset") - 1: 231 if (!strncasecmp(ctx->tag.c, "fieldset", sizeof("fieldset") - 1)) { 232 doit = 1; 233 } 234 break; 235 } 236 237 if (doit) 238 smart_str_append(&ctx->result, &ctx->form_app); 239 } 240} 241 242/* 243 * HANDLE_TAG copies the HTML Tag and checks whether we 244 * have that tag in our table. If we might modify it, 245 * we continue to scan the tag, otherwise we simply copy the complete 246 * HTML stuff to the result buffer. 247 */ 248 249static inline void handle_tag(STD_PARA) 250{ 251 int ok = 0; 252 int i; 253 254 ctx->tag.len = 0; 255 smart_str_appendl(&ctx->tag, start, YYCURSOR - start); 256 for (i = 0; i < ctx->tag.len; i++) 257 ctx->tag.c[i] = tolower((int)(unsigned char)ctx->tag.c[i]); 258 if (zend_hash_find(ctx->tags, ctx->tag.c, ctx->tag.len, (void **) &ctx->lookup_data) == SUCCESS) 259 ok = 1; 260 STATE = ok ? STATE_NEXT_ARG : STATE_PLAIN; 261} 262 263static inline void handle_arg(STD_PARA) 264{ 265 ctx->arg.len = 0; 266 smart_str_appendl(&ctx->arg, start, YYCURSOR - start); 267} 268 269static inline void handle_val(STD_PARA, char quotes, char type) 270{ 271 smart_str_setl(&ctx->val, start + quotes, YYCURSOR - start - quotes * 2); 272 tag_arg(ctx, quotes, type TSRMLS_CC); 273} 274 275static inline void xx_mainloop(url_adapt_state_ex_t *ctx, const char *newdata, size_t newlen TSRMLS_DC) 276{ 277 char *end, *q; 278 char *xp; 279 char *start; 280 int rest; 281 282 smart_str_appendl(&ctx->buf, newdata, newlen); 283 284 YYCURSOR = ctx->buf.c; 285 YYLIMIT = ctx->buf.c + ctx->buf.len; 286 287 switch (STATE) { 288 case STATE_PLAIN: goto state_plain; 289 case STATE_TAG: goto state_tag; 290 case STATE_NEXT_ARG: goto state_next_arg; 291 case STATE_ARG: goto state_arg; 292 case STATE_BEFORE_VAL: goto state_before_val; 293 case STATE_VAL: goto state_val; 294 } 295 296 297state_plain_begin: 298 STATE = STATE_PLAIN; 299 300state_plain: 301 start = YYCURSOR; 302/*!re2c 303 "<" { passthru(STD_ARGS); STATE = STATE_TAG; goto state_tag; } 304 N+ { passthru(STD_ARGS); goto state_plain; } 305*/ 306 307state_tag: 308 start = YYCURSOR; 309/*!re2c 310 alphanamespace+ { handle_tag(STD_ARGS); /* Sets STATE */; passthru(STD_ARGS); if (STATE == STATE_PLAIN) goto state_plain; else goto state_next_arg; } 311 any { passthru(STD_ARGS); goto state_plain_begin; } 312*/ 313 314state_next_arg_begin: 315 STATE = STATE_NEXT_ARG; 316 317state_next_arg: 318 start = YYCURSOR; 319/*!re2c 320 ">" { passthru(STD_ARGS); handle_form(STD_ARGS); goto state_plain_begin; } 321 [ \v\r\t\n]+ { passthru(STD_ARGS); goto state_next_arg; } 322 alpha { --YYCURSOR; STATE = STATE_ARG; goto state_arg; } 323 any { passthru(STD_ARGS); goto state_plain_begin; } 324*/ 325 326state_arg: 327 start = YYCURSOR; 328/*!re2c 329 alpha alphadash* { passthru(STD_ARGS); handle_arg(STD_ARGS); STATE = STATE_BEFORE_VAL; goto state_before_val; } 330 any { passthru(STD_ARGS); STATE = STATE_NEXT_ARG; goto state_next_arg; } 331*/ 332 333state_before_val: 334 start = YYCURSOR; 335/*!re2c 336 [ ]* "=" [ ]* { passthru(STD_ARGS); STATE = STATE_VAL; goto state_val; } 337 any { --YYCURSOR; goto state_next_arg_begin; } 338*/ 339 340 341state_val: 342 start = YYCURSOR; 343/*!re2c 344 ["] (any\[">])* ["] { handle_val(STD_ARGS, 1, '"'); goto state_next_arg_begin; } 345 ['] (any\['>])* ['] { handle_val(STD_ARGS, 1, '\''); goto state_next_arg_begin; } 346 (any\[ \r\t\n>])+ { handle_val(STD_ARGS, 0, ' '); goto state_next_arg_begin; } 347 any { passthru(STD_ARGS); goto state_next_arg_begin; } 348*/ 349 350stop: 351 rest = YYLIMIT - start; 352 scdebug(("stopped in state %d at pos %d (%d:%c) %d\n", STATE, YYCURSOR - ctx->buf.c, *YYCURSOR, *YYCURSOR, rest)); 353 /* XXX: Crash avoidance. Need to work with reporter to figure out what goes wrong */ 354 if (rest < 0) rest = 0; 355 356 if (rest) memmove(ctx->buf.c, start, rest); 357 ctx->buf.len = rest; 358} 359 360char *php_url_scanner_adapt_single_url(const char *url, size_t urllen, const char *name, const char *value, size_t *newlen TSRMLS_DC) 361{ 362 smart_str surl = {0}; 363 smart_str buf = {0}; 364 smart_str url_app = {0}; 365 366 smart_str_setl(&surl, url, urllen); 367 368 smart_str_appends(&url_app, name); 369 smart_str_appendc(&url_app, '='); 370 smart_str_appends(&url_app, value); 371 372 append_modified_url(&surl, &buf, &url_app, PG(arg_separator).output); 373 374 smart_str_0(&buf); 375 if (newlen) *newlen = buf.len; 376 377 smart_str_free(&url_app); 378 379 return buf.c; 380} 381 382 383static char *url_adapt_ext(const char *src, size_t srclen, size_t *newlen, zend_bool do_flush TSRMLS_DC) 384{ 385 url_adapt_state_ex_t *ctx; 386 char *retval; 387 388 ctx = &BG(url_adapt_state_ex); 389 390 xx_mainloop(ctx, src, srclen TSRMLS_CC); 391 392 *newlen = ctx->result.len; 393 if (!ctx->result.c) { 394 smart_str_appendl(&ctx->result, "", 0); 395 } 396 smart_str_0(&ctx->result); 397 if (do_flush) { 398 smart_str_appendl(&ctx->result, ctx->buf.c, ctx->buf.len); 399 *newlen += ctx->buf.len; 400 smart_str_free(&ctx->buf); 401 } 402 retval = ctx->result.c; 403 ctx->result.c = NULL; 404 ctx->result.len = 0; 405 return retval; 406} 407 408static int php_url_scanner_ex_activate(TSRMLS_D) 409{ 410 url_adapt_state_ex_t *ctx; 411 412 ctx = &BG(url_adapt_state_ex); 413 414 memset(ctx, 0, ((size_t) &((url_adapt_state_ex_t *)0)->tags)); 415 416 return SUCCESS; 417} 418 419static int php_url_scanner_ex_deactivate(TSRMLS_D) 420{ 421 url_adapt_state_ex_t *ctx; 422 423 ctx = &BG(url_adapt_state_ex); 424 425 smart_str_free(&ctx->result); 426 smart_str_free(&ctx->buf); 427 smart_str_free(&ctx->tag); 428 smart_str_free(&ctx->arg); 429 430 return SUCCESS; 431} 432 433static void php_url_scanner_output_handler(char *output, uint output_len, char **handled_output, uint *handled_output_len, int mode TSRMLS_DC) 434{ 435 size_t len; 436 437 if (BG(url_adapt_state_ex).url_app.len != 0) { 438 *handled_output = url_adapt_ext(output, output_len, &len, (zend_bool) (mode & (PHP_OUTPUT_HANDLER_END | PHP_OUTPUT_HANDLER_CONT) ? 1 : 0) TSRMLS_CC); 439 if (sizeof(uint) < sizeof(size_t)) { 440 if (len > UINT_MAX) 441 len = UINT_MAX; 442 } 443 *handled_output_len = len; 444 } else if (BG(url_adapt_state_ex).url_app.len == 0) { 445 url_adapt_state_ex_t *ctx = &BG(url_adapt_state_ex); 446 if (ctx->buf.len) { 447 smart_str_appendl(&ctx->result, ctx->buf.c, ctx->buf.len); 448 smart_str_appendl(&ctx->result, output, output_len); 449 450 *handled_output = ctx->result.c; 451 *handled_output_len = ctx->buf.len + output_len; 452 453 ctx->result.c = NULL; 454 ctx->result.len = 0; 455 smart_str_free(&ctx->buf); 456 } else { 457 *handled_output = NULL; 458 } 459 } else { 460 *handled_output = NULL; 461 } 462} 463 464PHPAPI int php_url_scanner_add_var(char *name, int name_len, char *value, int value_len, int urlencode TSRMLS_DC) 465{ 466 char *encoded; 467 int encoded_len; 468 smart_str val; 469 470 if (! BG(url_adapt_state_ex).active) { 471 php_url_scanner_ex_activate(TSRMLS_C); 472 php_ob_set_internal_handler(php_url_scanner_output_handler, 0, "URL-Rewriter", 1 TSRMLS_CC); 473 BG(url_adapt_state_ex).active = 1; 474 } 475 476 477 if (BG(url_adapt_state_ex).url_app.len != 0) { 478 smart_str_appends(&BG(url_adapt_state_ex).url_app, PG(arg_separator).output); 479 } 480 481 if (urlencode) { 482 encoded = php_url_encode(value, value_len, &encoded_len); 483 smart_str_setl(&val, encoded, encoded_len); 484 } else { 485 smart_str_setl(&val, value, value_len); 486 } 487 488 smart_str_appendl(&BG(url_adapt_state_ex).url_app, name, name_len); 489 smart_str_appendc(&BG(url_adapt_state_ex).url_app, '='); 490 smart_str_append(&BG(url_adapt_state_ex).url_app, &val); 491 492 smart_str_appends(&BG(url_adapt_state_ex).form_app, "<input type=\"hidden\" name=\""); 493 smart_str_appendl(&BG(url_adapt_state_ex).form_app, name, name_len); 494 smart_str_appends(&BG(url_adapt_state_ex).form_app, "\" value=\""); 495 smart_str_append(&BG(url_adapt_state_ex).form_app, &val); 496 smart_str_appends(&BG(url_adapt_state_ex).form_app, "\" />"); 497 498 if (urlencode) 499 efree(encoded); 500 501 return SUCCESS; 502} 503 504PHPAPI int php_url_scanner_reset_vars(TSRMLS_D) 505{ 506 BG(url_adapt_state_ex).form_app.len = 0; 507 BG(url_adapt_state_ex).url_app.len = 0; 508 509 return SUCCESS; 510} 511 512PHP_MINIT_FUNCTION(url_scanner) 513{ 514 BG(url_adapt_state_ex).tags = NULL; 515 516 BG(url_adapt_state_ex).form_app.c = BG(url_adapt_state_ex).url_app.c = 0; 517 BG(url_adapt_state_ex).form_app.len = BG(url_adapt_state_ex).url_app.len = 0; 518 519 REGISTER_INI_ENTRIES(); 520 return SUCCESS; 521} 522 523PHP_MSHUTDOWN_FUNCTION(url_scanner) 524{ 525 UNREGISTER_INI_ENTRIES(); 526 527 return SUCCESS; 528} 529 530PHP_RINIT_FUNCTION(url_scanner) 531{ 532 BG(url_adapt_state_ex).active = 0; 533 534 return SUCCESS; 535} 536 537PHP_RSHUTDOWN_FUNCTION(url_scanner) 538{ 539 if (BG(url_adapt_state_ex).active) { 540 php_url_scanner_ex_deactivate(TSRMLS_C); 541 BG(url_adapt_state_ex).active = 0; 542 } 543 544 smart_str_free(&BG(url_adapt_state_ex).form_app); 545 smart_str_free(&BG(url_adapt_state_ex).url_app); 546 547 return SUCCESS; 548} 549