diff --git a/Zend/tests/bug70748.phpt b/Zend/tests/bug70748.phpt index d6703487cefe1..c1db494d96796 100644 --- a/Zend/tests/bug70748.phpt +++ b/Zend/tests/bug70748.phpt @@ -15,6 +15,6 @@ var_dump(parse_ini_file($ini_file)); unlink(__DIR__ . "/bug70748.ini"); ?> --EXPECTF-- -Warning: syntax error, unexpected end of file, expecting '}' in %sbug70748.ini on line %d +Warning: syntax error, unexpected end of file, expecting TC_FALLBACK or '}' in %s on line %d in %sbug70748.php on line %d bool(false) diff --git a/Zend/zend_ini_parser.y b/Zend/zend_ini_parser.y index 1699fddd6e753..071fdeb13fb75 100644 --- a/Zend/zend_ini_parser.y +++ b/Zend/zend_ini_parser.y @@ -170,7 +170,7 @@ static void zend_ini_get_constant(zval *result, zval *name) /* }}} */ /* {{{ zend_ini_get_var() */ -static void zend_ini_get_var(zval *result, zval *name) +static void zend_ini_get_var(zval *result, zval *name, zval *fallback) { zval *curval; char *envvar; @@ -182,9 +182,13 @@ static void zend_ini_get_var(zval *result, zval *name) } else if ((envvar = zend_getenv(Z_STRVAL_P(name), Z_STRLEN_P(name))) != NULL || (envvar = getenv(Z_STRVAL_P(name))) != NULL) { ZVAL_NEW_STR(result, zend_string_init(envvar, strlen(envvar), ZEND_SYSTEM_INI)); + /* ..or if not defined, try fallback value */ + } else if (fallback) { + ZVAL_NEW_STR(result, zend_string_init(Z_STRVAL_P(fallback), strlen(Z_STRVAL_P(fallback)), ZEND_SYSTEM_INI)); } else { zend_ini_init_string(result); } + } /* }}} */ @@ -338,6 +342,7 @@ static void normalize_value(zval *zv) %token TC_DOLLAR_CURLY %token TC_VARNAME %token TC_QUOTED_STRING +%token TC_FALLBACK %token BOOL_TRUE %token BOOL_FALSE %token NULL_NULL @@ -440,7 +445,14 @@ expr: ; cfg_var_ref: - TC_DOLLAR_CURLY TC_VARNAME '}' { zend_ini_get_var(&$$, &$2); zend_string_free(Z_STR($2)); } + TC_DOLLAR_CURLY TC_VARNAME '}' { zend_ini_get_var(&$$, &$2, NULL); zend_string_free(Z_STR($2)); } + | TC_DOLLAR_CURLY TC_VARNAME TC_FALLBACK fallback '}' { zend_ini_get_var(&$$, &$2, &$4); zend_string_free(Z_STR($2)); zend_string_free(Z_STR($4)); } +; + + +fallback: + var_string_list { $$ = $1; } + | %empty { zend_ini_init_string(&$$); } ; constant_literal: diff --git a/Zend/zend_ini_scanner.l b/Zend/zend_ini_scanner.l index 3c4a22ad35941..44159297a04e1 100644 --- a/Zend/zend_ini_scanner.l +++ b/Zend/zend_ini_scanner.l @@ -359,7 +359,8 @@ NEWLINE ("\r"|"\n"|"\r\n") TABS_AND_SPACES [ \t] WHITESPACE [ \t]+ CONSTANT [a-zA-Z_][a-zA-Z0-9_]* -LABEL [^=\n\r\t;&|^$~(){}!"\[]+ +LABEL_CHAR [^=\n\r\t;&|^$~(){}!"\[\]\x00] +LABEL ({LABEL_CHAR}+) TOKENS [:,.\[\]"'()&|^+-/*=%$!~<>?@{}] OPERATORS [&|^~()!] DOLLAR_CURLY "${" @@ -370,6 +371,7 @@ RAW_VALUE_CHARS [^\n\r;\000] LITERAL_DOLLAR ("$"([^{\000]|("\\"{ANY_CHAR}))) VALUE_CHARS ([^$= \t\n\r;&|^~()!"'\000]|{LITERAL_DOLLAR}) +FALLBACK_CHARS ([^$\n\r;"'}\\]|("\\"{ANY_CHAR})|{LITERAL_DOLLAR}) SECTION_VALUE_CHARS ([^$\n\r;"'\]\\]|("\\"{ANY_CHAR})|{LITERAL_DOLLAR}) := yyleng = YYCURSOR - SCNG(yy_text); @@ -417,12 +419,61 @@ SECTION_VALUE_CHARS ([^$\n\r;"'\]\\]|("\\"{ANY_CHAR})|{LITERAL_DOLLAR}) return ']'; } -{DOLLAR_CURLY} { /* Variable start */ +{DOLLAR_CURLY} { /* Variable start */ yy_push_state(ST_VARNAME); return TC_DOLLAR_CURLY; } -{LABEL} { /* Variable name */ +":-" { /* End Variable name, fallback start */ +fallback_lexing: + yy_pop_state(); + yy_push_state(ST_VAR_FALLBACK); + return TC_FALLBACK; +} + +{LABEL_CHAR} { /* Variable name */ + if (YYCURSOR[0] == ':' && YYCURSOR[1] == '-') { + YYCURSOR++; + goto fallback_lexing; + } + + while (YYCURSOR < YYLIMIT) { + switch (*YYCURSOR++) { + case '=': + case '\n': + case '\r': + case '\t': + case ';': + case '&': + case '|': + case '^': + case '$': + case '~': + case '(': + case ')': + case '{': + case '}': + case '!': + case '"': + case '[': + case ']': + break; + /* ':' is only allowed if it isn't followed by '-'. */ + case ':': + if (YYCURSOR[0] == '-') { + break; + } else { + continue; + } + default: + continue; + } + + YYCURSOR--; + yyleng = YYCURSOR - SCNG(yy_text); + break; + } + /* Eat leading whitespace */ EAT_LEADING_WHITESPACE(); @@ -432,7 +483,7 @@ SECTION_VALUE_CHARS ([^$\n\r;"'\]\\]|("\\"{ANY_CHAR})|{LITERAL_DOLLAR}) RETURN_TOKEN(TC_VARNAME, yytext, yyleng); } -"}" { /* Variable end */ +"}" { /* Variable/fallback end */ yy_pop_state(); return '}'; } @@ -522,11 +573,11 @@ end_raw_value_chars: return END_OF_LINE; } -{CONSTANT} { /* Get constant option value */ +{CONSTANT} { /* Get constant option value */ RETURN_TOKEN(TC_CONSTANT, yytext, yyleng); } -{NUMBER} { /* Get number option value as string */ +{NUMBER} { /* Get number option value as string */ RETURN_TOKEN(TC_NUMBER, yytext, yyleng); } @@ -548,11 +599,15 @@ end_raw_value_chars: RETURN_TOKEN(TC_STRING, yytext, yyleng); } +{FALLBACK_CHARS}+ { /* Same as below, but excluding '}' */ + RETURN_TOKEN(TC_STRING, yytext, yyleng); +} + {SECTION_VALUE_CHARS}+ { /* Get rest as section/offset value */ RETURN_TOKEN(TC_STRING, yytext, yyleng); } -{TABS_AND_SPACES}*["] { /* Double quoted '"' string start */ +{TABS_AND_SPACES}*["] { /* Double quoted '"' string start */ yy_push_state(ST_DOUBLE_QUOTES); return '"'; } @@ -603,7 +658,7 @@ end_raw_value_chars: return TC_QUOTED_STRING; } -{WHITESPACE} { +{WHITESPACE} { RETURN_TOKEN(TC_WHITESPACE, yytext, yyleng); } diff --git a/ext/standard/tests/general_functions/parse_ini_basic.data b/ext/standard/tests/general_functions/parse_ini_basic.data index 00860b24173f3..8ea086aa39d86 100644 --- a/ext/standard/tests/general_functions/parse_ini_basic.data +++ b/ext/standard/tests/general_functions/parse_ini_basic.data @@ -76,11 +76,13 @@ novalue_option4[]= ["Quoted strings and variables in sections"] [${basicval}] +[${undefinedval:-foo}] [${basicval}/foo] [foo/${basicval}] [foo/${basicval}/foo] ["${basicqval}"] +["${undefinedval:-foo}"] ["${basicqval}/foo"] ["foo/${basicqval}"] ["foo/${basicqval}/foo"] @@ -141,3 +143,24 @@ double_quoted_multiline = "Lorem \"ipsum\""" dolor" dollar_test = "\${test}" unescaped ="\n\r\t" + +[variable-fallback] +defined1 = "Hello, ${basicval:-world}!" +defined2 = ${basicval:-123} +falsy = ${falsyval:-hi} +undefined = "Hello, ${undefined_var:-world}!" +empty = ${emptyval:-foo} +spaces = ${undefined_var:-hi there} +exclamation = ${hi_is_this_undefined:-"no, this is fallback!"} +nested = ${undefined_var:-${also_undefined:-${undefined_too:-hello}}} +hostname = localhost:${undefined_var:-8080} +outer_quotes = "foo:${undefined_var:-bar}" +number_value = ${undefined_var:-12345} +true_value = ${undefined_var:-true} +false_value = ${undefined_var:-false} +null_value = ${undefined_var:-null} +empty1 = ${undefined_var:-} +empty2 = ${undefined} +constant = ${undefined:-TEST_CONSTANT} +INI:WITH:COLON = ${INI:WITH:COLON:-} +NONEXISTENT:INI:WITH:COLON = ${NONEXISTENT:INI:WITH:COLON:-fallback} diff --git a/ext/standard/tests/general_functions/parse_ini_basic.phpt b/ext/standard/tests/general_functions/parse_ini_basic.phpt index b2cae43745a8b..516bbd8bab456 100644 --- a/ext/standard/tests/general_functions/parse_ini_basic.phpt +++ b/ext/standard/tests/general_functions/parse_ini_basic.phpt @@ -3,6 +3,10 @@ parse_ini_file() tests --ENV-- basicval=FUBAR_VARIABLE basicqval=FUBAR_QUOTES_VARIABLE +falsyval=false +emptyval= +--INI-- +INI:WITH:COLON=ini_with_colon --FILE-- --EXPECT-- -array(27) { +array(29) { ["basic"]=> array(15) { ["basicval"]=> @@ -179,6 +183,9 @@ array(27) { ["FUBAR_VARIABLE"]=> array(0) { } + ["foo"]=> + array(0) { + } ["FUBAR_VARIABLE/foo"]=> array(0) { } @@ -295,5 +302,46 @@ array(27) { ["unescaped"]=> string(6) "\n\r\t" } + ["variable-fallback"]=> + array(19) { + ["defined1"]=> + string(22) "Hello, FUBAR_VARIABLE!" + ["defined2"]=> + string(14) "FUBAR_VARIABLE" + ["falsy"]=> + string(5) "false" + ["undefined"]=> + string(13) "Hello, world!" + ["empty"]=> + string(3) "foo" + ["spaces"]=> + string(8) "hi there" + ["exclamation"]=> + string(21) "no, this is fallback!" + ["nested"]=> + string(5) "hello" + ["hostname"]=> + string(14) "localhost:8080" + ["outer_quotes"]=> + string(7) "foo:bar" + ["number_value"]=> + string(5) "12345" + ["true_value"]=> + string(1) "1" + ["false_value"]=> + string(0) "" + ["null_value"]=> + string(0) "" + ["empty1"]=> + string(0) "" + ["empty2"]=> + string(0) "" + ["constant"]=> + string(21) "this_is_test_constant" + ["INI:WITH:COLON"]=> + string(14) "ini_with_colon" + ["NONEXISTENT:INI:WITH:COLON"]=> + string(8) "fallback" + } } Done. diff --git a/ext/standard/tests/general_functions/parse_ini_string_error.phpt b/ext/standard/tests/general_functions/parse_ini_string_error.phpt index afa291d31f378..0ff1be2530a98 100644 --- a/ext/standard/tests/general_functions/parse_ini_string_error.phpt +++ b/ext/standard/tests/general_functions/parse_ini_string_error.phpt @@ -3,8 +3,18 @@ Ini parsing errors should not result in memory leaks --FILE-- --EXPECTF-- Warning: syntax error, unexpected end of file, expecting TC_DOLLAR_CURLY or TC_QUOTED_STRING or '"' in Unknown on line 1 in %s on line %d bool(false) + +Warning: syntax error, unexpected end of file, expecting TC_FALLBACK or '}' in Unknown on line 1 + in %s on line %d +bool(false) + +Warning: syntax error, unexpected TC_FALLBACK, expecting TC_VARNAME in Unknown on line 1 + in %s on line %d +bool(false)