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