Skip to content

Improve handling of multi-line quoted values in the fallback INI parser - #30

Open
sgiehl wants to merge 3 commits into
masterfrom
improve-multiline-quoted-value-handling
Open

Improve handling of multi-line quoted values in the fallback INI parser#30
sgiehl wants to merge 3 commits into
masterfrom
improve-multiline-quoted-value-handling

Conversation

@sgiehl

@sgiehl sgiehl commented Aug 13, 2026

Copy link
Copy Markdown
Member

Summary

The fallback INI parser in IniReader (used when PHP's native parse_ini_string() is not available) parsed the input strictly line by line. It therefore did not recognise quoted values that span multiple lines: the lines after the opening quote were treated as separate sections or keys, while the native parser reads them as part of the value.

This aligns the fallback implementation with the native parser and makes values round-trip reliably through IniWriter/IniReader.

Changes

IniReader

  • The fallback implementation reads multi-line quoted values — both " and ' — up to the matching closing quote, so both implementations return the same result. The value is scanned once while it is read, not from its beginning again for every line.
  • A quote at the very end of a line ends a value that would otherwise be unterminated, so values written by an earlier version of IniWriter are still read as before.
  • The value is taken from the unmodified line, so tabs and inner whitespace are kept, and \r\n counts as one line break so it is preserved inside a value.
  • Anything after the closing quote of a value, such as an inline comment, is ignored.
  • Unterminated quoted values are rejected, consistent with the native parser.
  • readComments() skips multi-line values as well, so it no longer reports sections and keys that readString() does not return.

IniWriter

  • Backslashes are escaped in addition to quotes, so a value ending in a backslash cannot leave the closing quote ambiguous. IniReader reverses this when reading.
  • All quotes directly before a line break are removed, so none of them can end up as the last character of a line.
  • Option names are encoded like array keys and section names, keeping characters that are valid in a name such as . or :.

Tests

Added tests that run against both the native and the fallback implementation, covering multi-line double- and single-quoted values, values containing newlines, tabs, \r\n, backslashes (e.g. Windows paths) and INI-significant characters, option names, content after a section name or a closing quote, and unterminated quotes.

vendor/bin/phpunit: 140 tests, 226 assertions, all passing (verified on PHP 8.3; CI covers 7.2-8.4).

Reading Matomo's bundled global.ini.php and an instance config.ini.php produces byte-identical results before and after this change, on both implementations, and readComments() returns the same descriptions as before.

@sgiehl
sgiehl marked this pull request as ready for review August 13, 2026 16:50
@sgiehl
sgiehl requested a review from a team August 13, 2026 16:50
@sgiehl
sgiehl marked this pull request as draft August 13, 2026 20:09
@sgiehl
sgiehl marked this pull request as ready for review August 14, 2026 09:04
@sgiehl
sgiehl marked this pull request as draft August 14, 2026 09:19
sgiehl added 2 commits August 14, 2026 11:23
Backslashes are escaped in addition to quotes, so a value ending in a backslash
cannot leave the closing quote preceded by a lone backslash, and all quotes
directly before a line break are removed so none of them can end up as the last
character of a line.

Option names are encoded as well, keeping the characters that are valid in a
name such as "." or ":". An option name that would end up empty is rejected
instead of producing a file that cannot be read.
The fallback implementation read the file strictly line by line, so the lines
after the opening quote of a value were taken for sections or keys of their own
instead of being part of the value, unlike the native parser.

Quoted values are now read up to the matching closing quote, scanning each line
only once. A quote at the very end of a line ends a value that would otherwise
be unterminated, so values written by an earlier version of IniWriter are still
read as before. The value is taken from the unmodified line so tabs and inner
whitespace are kept, "\r\n" counts as one line break, and anything after the
closing quote is ignored.

Lines the native parser does not accept either are now rejected: an unterminated
quoted value, a quote or an assignment after a section name, and a double quote
that opens a string which is never closed.

readComments() skips such values as well, so it no longer reports sections and
keys that readString() does not return, and values are only decoded with raw
values of the same structure.
@sgiehl
sgiehl force-pushed the improve-multiline-quoted-value-handling branch from a83ceb3 to a76be7e Compare August 14, 2026 09:24
Runs against both the native and the fallback implementation, and pins the
cases the fallback implementation cannot read the same way.
@sgiehl
sgiehl force-pushed the improve-multiline-quoted-value-handling branch from a76be7e to 4690f41 Compare August 14, 2026 09:31
@sgiehl
sgiehl marked this pull request as ready for review August 14, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant