1/*
2   +----------------------------------------------------------------------+
3   | Zend Engine                                                          |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1998-2016 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 (yyleng) {                                 \
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 && (                              \
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*/
258ZEND_COLD int zend_ini_scanner_get_lineno(void)
259{
260	return SCNG(lineno);
261}
262/* }}} */
263
264/* {{{ zend_ini_scanner_get_filename()
265*/
266ZEND_COLD char *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