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