Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gh-65697: Prevent configparser from writing keys it cannot properly read #129270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Feb 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
454f026
Added check for delimiters in cfgparser keys to _validate_value_types
lincolnj1 Jan 16, 2025
20b24b1
Deleted some trailing whitespace
lincolnj1 Jan 22, 2025
5b730b6
Merge branch 'python:main' into fix-issue-128843
lincolnj1 Jan 22, 2025
9cc3506
Added check for section pattern in key and moved
lincolnj1 Jan 24, 2025
3638e67
Clarified _validate_key_contents() doc comment
lincolnj1 Jan 24, 2025
b161cea
Merge branch 'python:main' into fix-issue-128843
lincolnj1 Jan 24, 2025
f73d17a
Merge branch 'python:main' into fix-issue-128843
lincolnj1 Jan 24, 2025
5a47f44
Clarified new error name/doc comment and improved
lincolnj1 Jan 24, 2025
e12b696
Clarified InvalidWriteError doc comment
lincolnj1 Jan 24, 2025
f5f1cbb
Merge branch 'fix-issue-128843' of https://github.com/lincolnj1/cpyth…
lincolnj1 Jan 24, 2025
3d84347
Merge branch 'main' into validate-config-writes
lincolnj1 Jan 24, 2025
97c8e93
Remove trailing whitespace
lincolnj1 Jan 27, 2025
ea137d0
Adding documentation for InvalidWriteError
lincolnj1 Feb 15, 2025
7ca33c7
Remove trailing whitespace
lincolnj1 Feb 15, 2025
353eaf1
Cleaned up documentation on InvalidWriteError
lincolnj1 Feb 21, 2025
ccf715e
Merge branch 'main' into validate-config-writes
lincolnj1 Feb 21, 2025
a3e8848
Delint of test-configparser.py
lincolnj1 Feb 21, 2025
0e278fd
Removing trailing whitespace on line 2184 of test_configparser
lincolnj1 Feb 21, 2025
3da65ae
📜🤖 Added by blurb_it.
blurb-it[bot] Feb 21, 2025
aa9c92a
Changed formatting of configparser.py __all__
lincolnj1 Feb 21, 2025
b001df6
Merge branch 'validate-config-writes' of github.com:lincolnj1/cpython…
lincolnj1 Feb 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Doc/library/configparser.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,10 @@ ConfigParser Objects
*space_around_delimiters* is true, delimiters between
keys and values are surrounded by spaces.

.. versionchanged:: 3.14
Raises InvalidWriteError if this would write a representation which cannot
be accurately parsed by a future :meth:`read` call from this parser.

.. note::

Comments in the original configuration file are not preserved when
Expand Down Expand Up @@ -1459,6 +1463,17 @@ Exceptions

.. versionadded:: 3.14

.. exception:: InvalidWriteError

Exception raised when an attempted :meth:`ConfigParser.write` would not be parsed
accurately with a future :meth:`ConfigParser.read` call.

Ex: Writing a key beginning with the :attr:`ConfigParser.SECTCRE` pattern
would parse as a section header when read. Attempting to write this will raise
this exception.

.. versionadded:: 3.14

.. rubric:: Footnotes

.. [1] Config parsers allow for heavy customization. If you are interested in
Expand Down
19 changes: 18 additions & 1 deletion Lib/configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
"InterpolationMissingOptionError", "InterpolationSyntaxError",
"ParsingError", "MissingSectionHeaderError",
"MultilineContinuationError", "UnnamedSectionDisabledError",
"ConfigParser", "RawConfigParser",
"InvalidWriteError", "ConfigParser", "RawConfigParser",
"Interpolation", "BasicInterpolation", "ExtendedInterpolation",
"SectionProxy", "ConverterMapping",
"DEFAULTSECT", "MAX_INTERPOLATION_DEPTH", "UNNAMED_SECTION")
Expand Down Expand Up @@ -375,6 +375,14 @@ class _UnnamedSection:
def __repr__(self):
return "<UNNAMED_SECTION>"

class InvalidWriteError(Error):
"""Raised when attempting to write data that the parser would read back differently.
ex: writing a key which begins with the section header pattern would read back as a
new section """

def __init__(self, msg=''):
Error.__init__(self, msg)


UNNAMED_SECTION = _UnnamedSection()

Expand Down Expand Up @@ -973,6 +981,7 @@ def _write_section(self, fp, section_name, section_items, delimiter, unnamed=Fal
if not unnamed:
fp.write("[{}]\n".format(section_name))
for key, value in section_items:
self._validate_key_contents(key)
value = self._interpolation.before_write(self, section_name, key,
value)
if value is not None or not self._allow_no_value:
Expand Down Expand Up @@ -1210,6 +1219,14 @@ def _convert_to_boolean(self, value):
raise ValueError('Not a boolean: %s' % value)
return self.BOOLEAN_STATES[value.lower()]

def _validate_key_contents(self, key):
"""Raises an InvalidWriteError for any keys containing
delimiters or that match the section header pattern"""
if re.match(self.SECTCRE, key):
raise InvalidWriteError("Cannot write keys matching section pattern")
if any(delim in key for delim in self._delimiters):
raise InvalidWriteError("Cannot write key that contains delimiters")

def _validate_value_types(self, *, section="", option="", value=""):
"""Raises a TypeError for illegal non-string values.

Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,30 @@ def test_multiple_configs(self):
self.assertEqual('2', cfg[configparser.UNNAMED_SECTION]['b'])


class InvalidInputTestCase(unittest.TestCase):
"""Tests for issue #65697, where configparser will write configs
it parses back differently. Ex: keys containing delimiters or
matching the section pattern"""

def test_delimiter_in_key(self):
cfg = configparser.ConfigParser(delimiters=('='))
cfg.add_section('section1')
cfg.set('section1', 'a=b', 'c')
output = io.StringIO()
with self.assertRaises(configparser.InvalidWriteError):
cfg.write(output)
output.close()

def test_section_bracket_in_key(self):
cfg = configparser.ConfigParser()
cfg.add_section('section1')
cfg.set('section1', '[this parses back as a section]', 'foo')
output = io.StringIO()
with self.assertRaises(configparser.InvalidWriteError):
cfg.write(output)
output.close()


class MiscTestCase(unittest.TestCase):
def test__all__(self):
support.check__all__(self, configparser, not_exported={"Error"})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stdlib configparser will now attempt to validate that keys it writes will not result in file corruption (creating a file unable to be accurately parsed by a future read() call from the same parser). Attempting a corrupting write() will raise an InvalidWriteError.
Loading