From d3a6054d4441a6d431e6ca7aa63de62cee5585c2 Mon Sep 17 00:00:00 2001 From: Denis Ryabov Date: Sat, 28 Aug 2021 16:30:36 +0300 Subject: [PATCH] Fix/improve handling of escaping in ini parser Quoting from UPGRADING: - A leading dollar in a quoted string can now be escaped: "\${" will now be interpreted as a string with contents `${`. - Backslashes in double quoted strings are now more consistently treated as escape characters. Previously, "foo\\" followed by something other than a newline was not considered as a teminated string. It is now interpreted as a string with contents `foo\`. However, as an exception, the string "foo\" followed by a newline will continue to be treated as a valid string with contents `foo\` rather than an unterminated string. This exception exists to support naive uses of Windows file pahts as "C:\foo\". Closes GH-7420. --- Zend/zend_ini_scanner.l | 22 +++++++++++-------- .../general_functions/parse_ini_basic.data | 11 ++++++++++ .../general_functions/parse_ini_basic.phpt | 18 ++++++++++++++- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/Zend/zend_ini_scanner.l b/Zend/zend_ini_scanner.l index 73853f6ec11..474580ba3af 100644 --- a/Zend/zend_ini_scanner.l +++ b/Zend/zend_ini_scanner.l @@ -591,31 +591,35 @@ end_raw_value_chars: return 0; } - while (YYCURSOR < YYLIMIT) { - switch (*YYCURSOR++) { + unsigned char *s = SCNG(yy_text); + + while (s < YYLIMIT) { + switch (*s++) { case '"': - if (YYCURSOR < YYLIMIT && YYCURSOR[-2] == '\\' && *YYCURSOR != '\r' && *YYCURSOR != '\n') { - continue; - } break; case '$': - if (*YYCURSOR == '{') { + if (s < YYLIMIT && *s == '{') { break; } continue; case '\\': - if (YYCURSOR < YYLIMIT && *YYCURSOR != '"') { - YYCURSOR++; + if (s < YYLIMIT) { + unsigned char escaped = *s++; + /* A special case for Windows paths, e.g. key="C:\path\" */ + if (escaped == '"' && (s >= YYLIMIT || *s == '\n' || *s == '\r')) { + break; + } } ZEND_FALLTHROUGH; default: continue; } - YYCURSOR--; + s--; break; } + YYCURSOR = s; yyleng = YYCURSOR - SCNG(yy_text); zend_ini_escape_string(ini_lval, 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 cafbb157424..00860b24173 100644 --- a/ext/standard/tests/general_functions/parse_ini_basic.data +++ b/ext/standard/tests/general_functions/parse_ini_basic.data @@ -130,3 +130,14 @@ ini-with.hyphen = hyphen and dot [windows paths] winpath1="c:\some windows\path\test\new\r\quote \" here\single ' quote\some more" winpath2="special case\" + +[characters escaping] +; Note: single-quoted strings don't support characters escaping, and the line below +; is single-quoted string, followed by unquoted text, followed by single-quoted '.' +single_quoted = 'She said \'Exactly my point\'.' +double_quoted = "She said \"Exactly my point\"." +double_quoted_2 = "Use \\\" to escape double quote" +double_quoted_multiline = "Lorem \"ipsum\""" + dolor" +dollar_test = "\${test}" +unescaped ="\n\r\t" diff --git a/ext/standard/tests/general_functions/parse_ini_basic.phpt b/ext/standard/tests/general_functions/parse_ini_basic.phpt index 481f24045f7..b2cae43745a 100644 --- a/ext/standard/tests/general_functions/parse_ini_basic.phpt +++ b/ext/standard/tests/general_functions/parse_ini_basic.phpt @@ -15,7 +15,7 @@ var_dump(parse_ini_file($ini_file, 1)); echo "Done.\n"; ?> --EXPECT-- -array(26) { +array(27) { ["basic"]=> array(15) { ["basicval"]=> @@ -279,5 +279,21 @@ array(26) { ["winpath2"]=> string(13) "special case\" } + ["characters escaping"]=> + array(6) { + ["single_quoted"]=> + string(28) "She said \Exactly my point\." + ["double_quoted"]=> + string(28) "She said "Exactly my point"." + ["double_quoted_2"]=> + string(29) "Use \" to escape double quote" + ["double_quoted_multiline"]=> + string(20) "Lorem "ipsum" + dolor" + ["dollar_test"]=> + string(7) "${test}" + ["unescaped"]=> + string(6) "\n\r\t" + } } Done.