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