Skip to content

file: escape written values the default reader would alter - #389

Open
gaoflow wants to merge 1 commit into
go-ini:mainfrom
gaoflow:fix-writer-surrounded-quote-roundtrip
Open

file: escape written values the default reader would alter#389
gaoflow wants to merge 1 commit into
go-ini:mainfrom
gaoflow:fix-writer-surrounded-quote-roundtrip

Conversation

@gaoflow

@gaoflow gaoflow commented Jul 28, 2026

Copy link
Copy Markdown

Describe the pull request

WriteTo can emit a value that its own default reader parses back as a different value, so Load(WriteTo(v)) != v.

writeKeyValue wraps a value only when it contains `/\n (→ """…"""), #/; (→ backtick), or has surrounding whitespace (→ "…"). The comment above it says it covers ", but there is no branch for a value that is itself surrounded by matching quotes. The default reader strips surrounding quotes (hasSurroundedQuote in parser.go), so such a value comes back unquoted:

cfg := ini.Empty()
cfg.Section("").Key("k").SetValue(`"quoted"`)
var buf bytes.Buffer
cfg.WriteTo(&buf)            // k = "quoted"
rc, _ := ini.Load(buf.Bytes())
rc.Section("").Key("k").String() // "quoted"  ->  quoted   (corrupted)

This is one root cause — the writer produces a form the default reader re-interprets — with a few faces:

  • values surrounded by " or ' ("quoted", '123', "", "a b", …) are unquoted on read;
  • a value that wraps an inner quote in whitespace (_"x"_) breaks the "…" branch, because the reader can't strip a "…" pair that contains another ";
  • a value opening with """ is read as a multi-line wrapper.

Fix

Route these through the raw-string (backtick) wrapper the reader already inverts verbatim, so a serialized value is read back byte-for-byte. Plain values, and values that merely contain an interior quote (e.g. "one", "two"), are untouched.

I checked this with a small round-trip census (SetValueWriteToLoad, default options) over the value domain — plain, empty, numeric, leading/trailing space, interior/surrounding quotes, #/;/=, `/\n, and """. On main 11 of 42 cases were corrupted; after the change 0 of 42 are, and no previously-correct case changed. The regression test covers the corrupt set plus the no-regression boundary (plain values and interior-quote values must stay unwrapped).

Checklist

  • I agree to follow the Code of Conduct by submitting this pull request.
  • I have read and acknowledge the Contributing guide.
  • I have added test cases to cover the new code.

WriteTo emitted a value surrounded by matching quotes ("…" or '…')
verbatim, but the default reader strips surrounding quotes, so
Load(WriteTo(v)) != v. The same round-trip broke for values wrapping an
inner quote in whitespace and for values opening with """.

Route these to the raw-string wrapper the reader already inverts, so a
serialized value is read back byte-for-byte. Plain values are untouched.
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