mirror of https://github.com/python/cpython.git
gh-65697: Prevent configparser from writing keys it cannot properly read (#129270)
--------- Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
This commit is contained in:
parent
1e4a4344af
commit
25a7ddf2ef
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
||||
|
|
|
@ -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"})
|
||||
|
|
|
@ -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…
Reference in New Issue