1/*
2   +----------------------------------------------------------------------+
3   | Zend Engine                                                          |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1998-2014 Zend Technologies Ltd. (http://www.zend.com) |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 2.00 of the Zend 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.zend.com/license/2_00.txt.                                |
11   | If you did not receive a copy of the Zend license and are unable to  |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@zend.com so we can mail you a copy immediately.              |
14   +----------------------------------------------------------------------+
15   | Authors: Zeev Suraski <zeev@zend.com>                                |
16   |          Jani Taskinen <jani@php.net>                                |
17   |          Marcus Boerger <helly@php.net>                              |
18   |          Nuno Lopes <nlopess@php.net>                                |
19   |          Scott MacVicar <scottmac@php.net>                           |
20   +----------------------------------------------------------------------+
21*/
22
23/* $Id$ */
24
25#include <errno.h>
26#include "zend.h"
27#include "zend_globals.h"
28#include <zend_ini_parser.h>
29#include "zend_ini_scanner.h"
30
31#if 0
32# define YYDEBUG(s, c) printf("state: %d char: %c\n", s, c)
33#else
34# define YYDEBUG(s, c)
35#endif
36
37#include "zend_ini_scanner_defs.h"
38
39#define YYCTYPE   unsigned char
40/* allow the scanner to read one null byte after the end of the string (from ZEND_MMAP_AHEAD)
41 * so that if will be able to terminate to match the current token (e.g. non-enclosed string) */
42#define YYFILL(n) { if (YYCURSOR > YYLIMIT) return 0; }
43#define YYCURSOR  SCNG(yy_cursor)
44#define YYLIMIT   SCNG(yy_limit)
45#define YYMARKER  SCNG(yy_marker)
46
47#define YYGETCONDITION()  SCNG(yy_state)
48#define YYSETCONDITION(s) SCNG(yy_state) = s
49
50#define STATE(name)  yyc##name
51
52/* emulate flex constructs */
53#define BEGIN(state) YYSETCONDITION(STATE(state))
54#define YYSTATE      YYGETCONDITION()
55#define yytext       ((char*)SCNG(yy_text))
56#define yyleng       SCNG(yy_leng)
57#define yyless(x)    do {   YYCURSOR = (unsigned char*)yytext + x; \
58                            yyleng   = (unsigned int)x; } while(0)
59
60/* #define yymore()     goto yymore_restart */
61
62/* perform sanity check. If this message is triggered you should
63   increase the ZEND_MMAP_AHEAD value in the zend_streams.h file */
64/*!max:re2c */
65#if ZEND_MMAP_AHEAD < (YYMAXFILL + 1)
66# error ZEND_MMAP_AHEAD should be greater than YYMAXFILL
67#endif
68
69
70/* How it works (for the core ini directives):
71 * ===========================================
72 *
73 * 1. Scanner scans file for tokens and passes them to parser.
74 * 2. Parser parses the tokens and passes the name/value pairs to the callback
75 *    function which stores them in the configuration hash table.
76 * 3. Later REGISTER_INI_ENTRIES() is called which triggers the actual
77 *    registering of ini entries and uses zend_get_configuration_directive()
78 *    to fetch the previously stored name/value pair from configuration hash table
79 *    and registers the static ini entries which match the name to the value
80 *    into EG(ini_directives) hash table.
81 * 4. PATH section entries are used per-request from down to top, each overriding
82 *    previous if one exists. zend_alter_ini_entry() is called for each entry.
83 *    Settings in PATH section are ZEND_INI_SYSTEM accessible and thus mimics the
84 *    php_admin_* directives used within Apache httpd.conf when PHP is compiled as
85 *    module for Apache.
86 * 5. User defined ini files (like .htaccess for apache) are parsed for each request and
87 *    stored in separate hash defined by SAPI.
88 */
89
90/* TODO: (ordered by importance :-)
91 * ===============================================================================
92 *
93 *  - Separate constant lookup totally from plain strings (using CONSTANT pattern)
94 *  - Add #if .. #else .. #endif and ==, !=, <, > , <=, >= operators
95 *  - Add #include "some.ini"
96 *  - Allow variables to refer to options also when using parse_ini_file()
97 *
98 */
99
100/* Globals Macros */
101#define SCNG    INI_SCNG
102#ifdef ZTS
103ZEND_API ts_rsrc_id ini_scanner_globals_id;
104#else
105ZEND_API zend_ini_scanner_globals ini_scanner_globals;
106#endif
107
108/* Eat leading whitespace */
109#define EAT_LEADING_WHITESPACE()                     \
110    while (yytext[0]) {                              \
111        if (yytext[0] == ' ' || yytext[0] == '\t') { \
112            SCNG(yy_text)++;                         \
113            yyleng--;                                \
114        } else {                                     \
115            break;                                   \
116        }                                            \
117    }
118
119/* Eat trailing whitespace + extra char */
120#define EAT_TRAILING_WHITESPACE_EX(ch)              \
121    while (yyleng > 0 && (                          \
122        (ch != 'X' && yytext[yyleng - 1] ==  ch) || \
123        yytext[yyleng - 1] == '\n' ||               \
124        yytext[yyleng - 1] == '\r' ||               \
125        yytext[yyleng - 1] == '\t' ||               \
126        yytext[yyleng - 1] == ' ')                  \
127    ) {                                             \
128        yyleng--;                                   \
129    }
130
131/* Eat trailing whitespace */
132#define EAT_TRAILING_WHITESPACE()   EAT_TRAILING_WHITESPACE_EX('X')
133
134#define zend_ini_copy_value(retval, str, len)   \
135    ZVAL_NEW_STR(retval, zend_string_init(str, len, 1))
136
137
138#define RETURN_TOKEN(type, str, len) {           \
139    zend_ini_copy_value(ini_lval, str, len);     \
140    return type;                                 \
141}
142
143static void _yy_push_state(int new_state TSRMLS_DC)
144{
145    zend_stack_push(&SCNG(state_stack), (void *) &YYGETCONDITION());
146    YYSETCONDITION(new_state);
147}
148
149#define yy_push_state(state_and_tsrm) _yy_push_state(yyc##state_and_tsrm)
150
151static void yy_pop_state(TSRMLS_D)
152{
153    int *stack_state = zend_stack_top(&SCNG(state_stack));
154    YYSETCONDITION(*stack_state);
155    zend_stack_del_top(&SCNG(state_stack));
156}
157
158static void yy_scan_buffer(char *str, unsigned int len TSRMLS_DC)
159{
160    YYCURSOR = (YYCTYPE*)str;
161    SCNG(yy_start) = YYCURSOR;
162    YYLIMIT  = YYCURSOR + len;
163}
164
165#define ini_filename SCNG(filename)
166
167/* {{{ init_ini_scanner()
168*/
169static int init_ini_scanner(int scanner_mode, zend_file_handle *fh TSRMLS_DC)
170{
171    /* Sanity check */
172    if (scanner_mode != ZEND_INI_SCANNER_NORMAL && scanner_mode != ZEND_INI_SCANNER_RAW) {
173        zend_error(E_WARNING, "Invalid scanner mode");
174        return FAILURE;
175    }
176
177    SCNG(lineno) = 1;
178    SCNG(scanner_mode) = scanner_mode;
179    SCNG(yy_in) = fh;
180
181    if (fh != NULL) {
182        ini_filename = zend_strndup(fh->filename, strlen(fh->filename));
183    } else {
184        ini_filename = NULL;
185    }
186
187    zend_stack_init(&SCNG(state_stack), sizeof(int));
188    BEGIN(INITIAL);
189
190    return SUCCESS;
191}
192/* }}} */
193
194/* {{{ shutdown_ini_scanner()
195*/
196void shutdown_ini_scanner(TSRMLS_D)
197{
198    zend_stack_destroy(&SCNG(state_stack));
199    if (ini_filename) {
200        free(ini_filename);
201    }
202}
203/* }}} */
204
205/* {{{ zend_ini_scanner_get_lineno()
206*/
207int zend_ini_scanner_get_lineno(TSRMLS_D)
208{
209    return SCNG(lineno);
210}
211/* }}} */
212
213/* {{{ zend_ini_scanner_get_filename()
214*/
215char *zend_ini_scanner_get_filename(TSRMLS_D)
216{
217    return ini_filename ? ini_filename : "Unknown";
218}
219/* }}} */
220
221/* {{{ zend_ini_open_file_for_scanning()
222*/
223int zend_ini_open_file_for_scanning(zend_file_handle *fh, int scanner_mode TSRMLS_DC)
224{
225    char *buf;
226    size_t size;
227
228    if (zend_stream_fixup(fh, &buf, &size TSRMLS_CC) == FAILURE) {
229        return FAILURE;
230    }
231
232    if (init_ini_scanner(scanner_mode, fh TSRMLS_CC) == FAILURE) {
233        zend_file_handle_dtor(fh TSRMLS_CC);
234        return FAILURE;
235    }
236
237    yy_scan_buffer(buf, size TSRMLS_CC);
238
239    return SUCCESS;
240}
241/* }}} */
242
243/* {{{ zend_ini_prepare_string_for_scanning()
244*/
245int zend_ini_prepare_string_for_scanning(char *str, int scanner_mode TSRMLS_DC)
246{
247    int len = strlen(str);
248
249    if (init_ini_scanner(scanner_mode, NULL TSRMLS_CC) == FAILURE) {
250        return FAILURE;
251    }
252
253    yy_scan_buffer(str, len TSRMLS_CC);
254
255    return SUCCESS;
256}
257/* }}} */
258
259/* {{{ zend_ini_escape_string()
260 */
261static void zend_ini_escape_string(zval *lval, char *str, int len, char quote_type TSRMLS_DC)
262{
263    register char *s, *t;
264    char *end;
265
266    zend_ini_copy_value(lval, str, len);
267
268    /* convert escape sequences */
269    s = t = Z_STRVAL_P(lval);
270    end = s + Z_STRLEN_P(lval);
271
272    while (s < end) {
273        if (*s == '\\') {
274            s++;
275            if (s >= end) {
276                *t++ = '\\';
277                continue;
278            }
279            switch (*s) {
280                case '"':
281                    if (*s != quote_type) {
282                        *t++ = '\\';
283                        *t++ = *s;
284                        break;
285                    }
286                case '\\':
287                case '$':
288                    *t++ = *s;
289                    Z_STRLEN_P(lval)--;
290                    break;
291                default:
292                    *t++ = '\\';
293                    *t++ = *s;
294                    break;
295            }
296        } else {
297            *t++ = *s;
298        }
299        if (*s == '\n' || (*s == '\r' && (*(s+1) != '\n'))) {
300            SCNG(lineno)++;
301        }
302        s++;
303    }
304    *t = 0;
305}
306/* }}} */
307
308int ini_lex(zval *ini_lval TSRMLS_DC)
309{
310restart:
311    SCNG(yy_text) = YYCURSOR;
312
313/* yymore_restart: */
314    /* detect EOF */
315    if (YYCURSOR >= YYLIMIT) {
316        if (YYSTATE == STATE(ST_VALUE) || YYSTATE == STATE(ST_RAW)) {
317            BEGIN(INITIAL);
318            return END_OF_LINE;
319        }
320        return 0;
321    }
322
323    /* Eat any UTF-8 BOM we find in the first 3 bytes */
324    if (YYCURSOR == SCNG(yy_start) && YYCURSOR + 3 < YYLIMIT) {
325        if (memcmp(YYCURSOR, "\xef\xbb\xbf", 3) == 0) {
326            YYCURSOR += 3;
327            goto restart;
328        }
329    }
330/*!re2c
331re2c:yyfill:check = 0;
332LNUM [0-9]+
333DNUM ([0-9]*[\.][0-9]+)|([0-9]+[\.][0-9]*)
334NUMBER [-]?{LNUM}|{DNUM}
335ANY_CHAR (.|[\n\t])
336NEWLINE ("\r"|"\n"|"\r\n")
337TABS_AND_SPACES [ \t]
338WHITESPACE [ \t]+
339CONSTANT [a-zA-Z_][a-zA-Z0-9_]*
340LABEL [^=\n\r\t;&|^$~(){}!"\[]+
341TOKENS [:,.\[\]"'()&|^+-/*=%$!~<>?@{}]
342OPERATORS [&|^~()!]
343DOLLAR_CURLY "${"
344
345SECTION_RAW_CHARS [^\]\n\r]
346SINGLE_QUOTED_CHARS [^']
347RAW_VALUE_CHARS [^\n\r;\000]
348
349LITERAL_DOLLAR ("$"([^{\000]|("\\"{ANY_CHAR})))
350VALUE_CHARS         ([^$= \t\n\r;&|^~()!"'\000]|{LITERAL_DOLLAR})
351SECTION_VALUE_CHARS ([^$\n\r;"'\]\\]|("\\"{ANY_CHAR})|{LITERAL_DOLLAR})
352
353<!*> := yyleng = YYCURSOR - SCNG(yy_text);
354
355<INITIAL>"[" { /* Section start */
356    /* Enter section data lookup state */
357    if (SCNG(scanner_mode) == ZEND_INI_SCANNER_RAW) {
358        yy_push_state(ST_SECTION_RAW TSRMLS_CC);
359    } else {
360        yy_push_state(ST_SECTION_VALUE TSRMLS_CC);
361    }
362    return TC_SECTION;
363}
364
365<ST_VALUE,ST_SECTION_VALUE,ST_OFFSET>"'"{SINGLE_QUOTED_CHARS}+"'" { /* Raw string */
366    /* Eat leading and trailing single quotes */
367    if (yytext[0] == '\'' && yytext[yyleng - 1] == '\'') {
368        SCNG(yy_text)++;
369        yyleng = yyleng - 2;
370    }
371    RETURN_TOKEN(TC_RAW, yytext, yyleng);
372}
373
374<ST_SECTION_RAW,ST_SECTION_VALUE>"]"{TABS_AND_SPACES}*{NEWLINE}? { /* End of section */
375    BEGIN(INITIAL);
376    SCNG(lineno)++;
377    return ']';
378}
379
380<INITIAL>{LABEL}"["{TABS_AND_SPACES}* { /* Start of option with offset */
381    /* Eat leading whitespace */
382    EAT_LEADING_WHITESPACE();
383
384    /* Eat trailing whitespace and [ */
385    EAT_TRAILING_WHITESPACE_EX('[');
386
387    /* Enter offset lookup state */
388    yy_push_state(ST_OFFSET TSRMLS_CC);
389
390    RETURN_TOKEN(TC_OFFSET, yytext, yyleng);
391}
392
393<ST_OFFSET>{TABS_AND_SPACES}*"]" { /* End of section or an option offset */
394    BEGIN(INITIAL);
395    return ']';
396}
397
398<ST_DOUBLE_QUOTES,ST_SECTION_VALUE,ST_VALUE,ST_OFFSET>{DOLLAR_CURLY} { /* Variable start */
399    yy_push_state(ST_VARNAME TSRMLS_CC);
400    return TC_DOLLAR_CURLY;
401}
402
403<ST_VARNAME>{LABEL} { /* Variable name */
404    /* Eat leading whitespace */
405    EAT_LEADING_WHITESPACE();
406
407    /* Eat trailing whitespace */
408    EAT_TRAILING_WHITESPACE();
409
410    RETURN_TOKEN(TC_VARNAME, yytext, yyleng);
411}
412
413<ST_VARNAME>"}" { /* Variable end */
414    yy_pop_state(TSRMLS_C);
415    return '}';
416}
417
418<INITIAL,ST_VALUE>("true"|"on"|"yes"){TABS_AND_SPACES}* { /* TRUE value (when used outside option value/offset this causes parse error!) */
419    RETURN_TOKEN(BOOL_TRUE, "1", 1);
420}
421
422<INITIAL,ST_VALUE>("false"|"off"|"no"|"none"|"null"){TABS_AND_SPACES}* { /* FALSE value (when used outside option value/offset this causes parse error!)*/
423    RETURN_TOKEN(BOOL_FALSE, "", 0);
424}
425
426<INITIAL>{LABEL} { /* Get option name */
427    /* Eat leading whitespace */
428    EAT_LEADING_WHITESPACE();
429
430    /* Eat trailing whitespace */
431    EAT_TRAILING_WHITESPACE();
432
433    RETURN_TOKEN(TC_LABEL, yytext, yyleng);
434}
435
436<INITIAL>{TABS_AND_SPACES}*[=]{TABS_AND_SPACES}* { /* Start option value */
437    if (SCNG(scanner_mode) == ZEND_INI_SCANNER_RAW) {
438        yy_push_state(ST_RAW TSRMLS_CC);
439    } else {
440        yy_push_state(ST_VALUE TSRMLS_CC);
441    }
442    return '=';
443}
444
445<ST_RAW>{RAW_VALUE_CHARS} { /* Raw value, only used when SCNG(scanner_mode) == ZEND_INI_SCANNER_RAW. */
446    unsigned char *sc = NULL;
447    while (YYCURSOR < YYLIMIT) {
448        switch (*YYCURSOR) {
449            case '\n':
450            case '\r':
451                goto end_raw_value_chars;
452                break;
453            case ';':
454                if (sc == NULL) {
455                    sc = YYCURSOR;
456                }
457                /* no break */
458            default:
459                YYCURSOR++;
460                break;
461        }
462    }
463end_raw_value_chars:
464    yyleng = YYCURSOR - SCNG(yy_text);
465
466    /* Eat trailing semicolons */
467    while (yytext[yyleng - 1] == ';') {
468        yyleng--;
469    }
470
471    /* Eat leading and trailing double quotes */
472    if (yytext[0] == '"' && yytext[yyleng - 1] == '"') {
473        SCNG(yy_text)++;
474        yyleng = yyleng - 2;
475    } else if (sc) {
476        YYCURSOR = sc;
477        yyleng = YYCURSOR - SCNG(yy_text);
478    }
479    RETURN_TOKEN(TC_RAW, yytext, yyleng);
480}
481
482<ST_SECTION_RAW>{SECTION_RAW_CHARS}+ { /* Raw value, only used when SCNG(scanner_mode) == ZEND_INI_SCANNER_RAW. */
483    RETURN_TOKEN(TC_RAW, yytext, yyleng);
484}
485
486<ST_VALUE,ST_RAW>{TABS_AND_SPACES}*{NEWLINE} { /* End of option value */
487    BEGIN(INITIAL);
488    SCNG(lineno)++;
489    return END_OF_LINE;
490}
491
492<ST_SECTION_VALUE,ST_VALUE,ST_OFFSET>{CONSTANT} { /* Get constant option value */
493    RETURN_TOKEN(TC_CONSTANT, yytext, yyleng);
494}
495
496<ST_SECTION_VALUE,ST_VALUE,ST_OFFSET>{NUMBER} { /* Get number option value as string */
497    RETURN_TOKEN(TC_NUMBER, yytext, yyleng);
498}
499
500<INITIAL>{TOKENS} { /* Disallow these chars outside option values */
501    return yytext[0];
502}
503
504<ST_VALUE>{OPERATORS}{TABS_AND_SPACES}* { /* Boolean operators */
505    return yytext[0];
506}
507
508<ST_VALUE>[=] { /* Make = used in option value to trigger error */
509    yyless(0);
510    BEGIN(INITIAL);
511    return END_OF_LINE;
512}
513
514<ST_VALUE>{VALUE_CHARS}+ { /* Get everything else as option/offset value */
515    RETURN_TOKEN(TC_STRING, yytext, yyleng);
516}
517
518<ST_SECTION_VALUE,ST_OFFSET>{SECTION_VALUE_CHARS}+ { /* Get rest as section/offset value */
519    RETURN_TOKEN(TC_STRING, yytext, yyleng);
520}
521
522<ST_SECTION_VALUE,ST_VALUE,ST_OFFSET>{TABS_AND_SPACES}*["] { /* Double quoted '"' string start */
523    yy_push_state(ST_DOUBLE_QUOTES TSRMLS_CC);
524    return '"';
525}
526
527<ST_DOUBLE_QUOTES>["]{TABS_AND_SPACES}* { /* Double quoted '"' string ends */
528    yy_pop_state(TSRMLS_C);
529    return '"';
530}
531
532<ST_DOUBLE_QUOTES>[^] { /* Escape double quoted string contents */
533    if (YYCURSOR > YYLIMIT) {
534        return 0;
535    }
536
537    while (YYCURSOR < YYLIMIT) {
538        switch (*YYCURSOR++) {
539            case '"':
540                if (YYCURSOR < YYLIMIT && YYCURSOR[-2] == '\\' && *YYCURSOR != '\r' && *YYCURSOR != '\n') {
541                    continue;
542                }
543                break;
544            case '$':
545                if (*YYCURSOR == '{') {
546                    break;
547                }
548                continue;
549            case '\\':
550                if (YYCURSOR < YYLIMIT && *YYCURSOR != '"') {
551                    YYCURSOR++;
552                }
553                /* fall through */
554            default:
555                continue;
556        }
557
558        YYCURSOR--;
559        break;
560    }
561
562    yyleng = YYCURSOR - SCNG(yy_text);
563
564    zend_ini_escape_string(ini_lval, yytext, yyleng, '"' TSRMLS_CC);
565    return TC_QUOTED_STRING;
566}
567
568<ST_SECTION_VALUE,ST_VALUE,ST_OFFSET>{WHITESPACE} {
569    RETURN_TOKEN(TC_WHITESPACE, yytext, yyleng);
570}
571
572<INITIAL,ST_RAW>{TABS_AND_SPACES}+ {
573    /* eat whitespace */
574    goto restart;
575}
576
577<INITIAL>{TABS_AND_SPACES}*{NEWLINE} {
578    SCNG(lineno)++;
579    return END_OF_LINE;
580}
581
582<INITIAL,ST_VALUE,ST_RAW>{TABS_AND_SPACES}*[;][^\r\n]*{NEWLINE} { /* Comment */
583    BEGIN(INITIAL);
584    SCNG(lineno)++;
585    return END_OF_LINE;
586}
587
588<INITIAL>{TABS_AND_SPACES}*[#][^\r\n]*{NEWLINE} { /* #Comment */
589    zend_error(E_DEPRECATED, "Comments starting with '#' are deprecated in %s on line %d", zend_ini_scanner_get_filename(TSRMLS_C), SCNG(lineno));
590    BEGIN(INITIAL);
591    SCNG(lineno)++;
592    return END_OF_LINE;
593}
594
595<ST_VALUE,ST_RAW>[^] { /* End of option value (if EOF is reached before EOL */
596    BEGIN(INITIAL);
597    return END_OF_LINE;
598}
599
600<*>[^] {
601    return 0;
602}
603
604*/
605}
606