Skip to content

Quote keys containing '=' when writing (fixes #273)#274

Open
gaoflow wants to merge 1 commit into
DiffSK:releasefrom
gaoflow:fix-quote-key-containing-equals
Open

Quote keys containing '=' when writing (fixes #273)#274
gaoflow wants to merge 1 commit into
DiffSK:releasefrom
gaoflow:fix-quote-key-containing-equals

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 20, 2026

Copy link
Copy Markdown

Summary

Fixes #273. A config key containing = is written unquoted, so on the next parse the first = is read as the key/value divider — silently splitting the key and corrupting the value:

from configobj import ConfigObj
import io

co = ConfigObj()
co['a = b'] = 'val'
buf = io.BytesIO(); co.write(buf)          # writes:  a = b = val
buf.seek(0)
dict(ConfigObj(buf))                        # -> {'a': 'b = val'}   (corrupted)

The parser already accepts the correct quoted form ("a = b" = val) — only the writer fails to emit the quotes. The issue #273 example "x <a='b'>" is worse: it produces a file that raises ParseError on the next read.

Cause

_quote() is shared by keys, values, list elements and section names. Its "no quoting needed" branch checks for leading/trailing whitespace-plus chars and ,, but not =. That is correct for values, list elements and section names — = is legal unquoted there — but not for a key, where the first = on the line is the divider. _write_line quoted the key with no key-context flag.

Fix

Add an is_key flag to _quote(). When it is set and the key contains =, force a quoted form via _get_single_quote() (which already picks "/' correctly, even when the key itself contains a quote char). _write_line passes is_key=True for the keyword; values, list elements and section markers are unchanged (is_key=False).

The guard sits ahead of both no-quote paths, so it also covers list_values=False (where the same corruption occurred).

Verification

  • New regression test test_key_with_equals_is_quoted asserts the written form ("a = b" = val) and that it round-trips back to {'a = b': 'val'}. It fails before the fix and passes after.
  • Confirmed by hand: the minimal repro, the list_values=False path, issue removed quotation marks if writing keys containing equal sign and "less than" sign #273's "x <a='b'>" example, and nested keys all round-trip; section names containing = stay unquoted ([sec = x]) and round-trip; normal keys and values are written exactly as before.
  • Full suite: 80 passed (was 79). flake8 --max-line-length=132 introduces zero new warnings.

This pull request was prepared with the assistance of AI, under my direction and review.

A config key containing '=' was written unquoted, so on the next parse
the first '=' was read as the key/value divider, splitting the key and
corrupting the value: ConfigObj({'a = b': 'val'}) round-tripped to
{'a': 'b = val'}, and keys like "x <a='b'>" produced an unparseable
file (issue DiffSK#273).

_quote() is shared by keys, values, list elements and section names;
'=' is only special for a key (the first '=' on a line is the divider).
Add an is_key flag and, when set and the key contains '=', force a
quoted form via _get_single_quote(). _write_line passes is_key=True for
the keyword; values, list elements and section markers are unchanged.

Fixes DiffSK#273
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.

removed quotation marks if writing keys containing equal sign and "less than" sign

1 participant