From a6de97b7716a3be08ba2a747a238454392d6b891 Mon Sep 17 00:00:00 2001 From: Team Humaki Date: Thu, 17 Sep 2026 02:59:17 -0700 Subject: [PATCH] struct: escape delimiter in string slices on ReflectFrom Fixes #348 --- key.go | 10 ++++++++++ struct.go | 2 +- struct_test.go | 43 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/key.go b/key.go index 800d04a..441da97 100644 --- a/key.go +++ b/key.go @@ -488,6 +488,16 @@ func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time { return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max) } +// escapeDelimited prepares a slice element for joining with delim. +// Backslashes are escaped first, then occurrences of delim, matching Strings(). +func escapeDelimited(val, delim string) string { + if delim == "" { + return val + } + val = strings.ReplaceAll(val, `\`, `\\`) + return strings.ReplaceAll(val, delim, `\`+delim) +} + // Strings returns list of string divided by given delimiter. func (k *Key) Strings(delim string) []string { str := k.String() diff --git a/struct.go b/struct.go index 819a6e5..f0cdc81 100644 --- a/struct.go +++ b/struct.go @@ -498,7 +498,7 @@ func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, all for i := 0; i < field.Len(); i++ { switch sliceOf { case reflect.String: - buf.WriteString(slice.Index(i).String()) + buf.WriteString(escapeDelimited(slice.Index(i).String(), delim)) case reflect.Int, reflect.Int64: fmt.Fprint(&buf, slice.Index(i).Int()) case reflect.Uint, reflect.Uint64: diff --git a/struct_test.go b/struct_test.go index ad6ad04..76b0c84 100644 --- a/struct_test.go +++ b/struct_test.go @@ -639,12 +639,12 @@ func Test_ReflectFromStructNonUniqueSections(t *testing.T) { { PublicKey: "pubClientKey", PresharedKey: "psKey", - AllowedIPs: []string{"10.2.0.2/32,fd00:2::2/128"}, + AllowedIPs: []string{"10.2.0.2/32", "fd00:2::2/128"}, }, { PublicKey: "pubClientKey2", PresharedKey: "psKey2", - AllowedIPs: []string{"10.2.0.3/32,fd00:2::3/128"}, + AllowedIPs: []string{"10.2.0.3/32", "fd00:2::3/128"}, }, }, } @@ -665,12 +665,12 @@ func Test_ReflectFromStructNonUniqueSections(t *testing.T) { { PublicKey: "pubClientKey3", PresharedKey: "psKey3", - AllowedIPs: []string{"10.2.0.4/32,fd00:2::4/128"}, + AllowedIPs: []string{"10.2.0.4/32", "fd00:2::4/128"}, }, { PublicKey: "pubClientKey4", PresharedKey: "psKey4", - AllowedIPs: []string{"10.2.0.5/32,fd00:2::5/128"}, + AllowedIPs: []string{"10.2.0.5/32", "fd00:2::5/128"}, }, }) @@ -701,7 +701,7 @@ AllowedIPs = 10.2.0.5/32,fd00:2::5/128 err = cfg.Section("Peer").ReflectFrom(&testPeer{ PublicKey: "pubClientKey5", PresharedKey: "psKey5", - AllowedIPs: []string{"10.2.0.6/32,fd00:2::6/128"}, + AllowedIPs: []string{"10.2.0.6/32", "fd00:2::6/128"}, }) require.NoError(t, err) @@ -857,6 +857,39 @@ func Test_Duration(t *testing.T) { }) } +// https://github.com/go-ini/ini/issues/348 +func Test_ReflectFromStringSliceWithComma(t *testing.T) { + type C struct { + Scale float32 + History []string + } + in := C{ + Scale: 1.5, + History: []string{ + "Test One", + "Test Two", + "Test with comma, Three", + "Test Four", + }, + } + + f := Empty() + require.NoError(t, f.ReflectFrom(&in)) + + var buf bytes.Buffer + _, err := f.WriteTo(&buf) + require.NoError(t, err) + assert.Contains(t, buf.String(), `Test with comma\, Three`) + + got := f.Section("").Key("History").Strings(",") + assert.Equal(t, in.History, got) + + var out C + require.NoError(t, MapTo(&out, buf.Bytes())) + assert.Equal(t, in.History, out.History) + assert.Equal(t, float32(1.5), out.Scale) +} + type Employer struct { Name string Title string