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