From 1c7b5bab5b0ba53ff70ed12d572eaeae9cb3b1ca Mon Sep 17 00:00:00 2001 From: youdie006 Date: Thu, 13 Aug 2026 12:02:58 +0900 Subject: [PATCH] Strip all leading BOMs, not just the first (*parser).BOM() removed at most one leading byte-order mark, so a file that begins with two BOMs (e.g. a tool prepending a BOM to already-BOM'd content) left the second BOM on the next line. That line no longer began with [, # or ; and had no key-value delimiter, so the parser aborted with a misleading "key-value delimiter not found" error on otherwise valid content. Loop the BOM detection until no BOM prefix remains. Fixes #390 --- parser.go | 50 ++++++++++++++++++++++++++++---------------------- parser_test.go | 8 ++++++++ 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/parser.go b/parser.go index 513b19e..ff5e8fc 100644 --- a/parser.go +++ b/parser.go @@ -73,36 +73,42 @@ func newParser(r io.Reader, opts parserOptions) *parser { // BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format. // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding func (p *parser) BOM() error { - mask, err := p.buf.Peek(2) - if err != nil && err != io.EOF { - return err - } else if len(mask) < 2 { - return nil - } - - switch { - case mask[0] == 254 && mask[1] == 255: - fallthrough - case mask[0] == 255 && mask[1] == 254: - _, err = p.buf.Read(mask) - if err != nil { - return err - } - case mask[0] == 239 && mask[1] == 187: - mask, err := p.buf.Peek(3) + // Strip every leading BOM: a file may accidentally carry more than one + // (e.g. a tool prepending a BOM to already-BOM'd content). Stopping after + // the first one leaves the remaining BOM on the next token and makes the + // parser fail with a misleading "key-value delimiter not found" (#390). + for { + mask, err := p.buf.Peek(2) if err != nil && err != io.EOF { return err - } else if len(mask) < 3 { + } else if len(mask) < 2 { return nil } - if mask[2] == 191 { - _, err = p.buf.Read(mask) - if err != nil { + + switch { + case mask[0] == 254 && mask[1] == 255: + fallthrough + case mask[0] == 255 && mask[1] == 254: + if _, err = p.buf.Read(mask); err != nil { return err } + case mask[0] == 239 && mask[1] == 187: + mask, err := p.buf.Peek(3) + if err != nil && err != io.EOF { + return err + } else if len(mask) < 3 { + return nil + } + if mask[2] != 191 { + return nil + } + if _, err = p.buf.Read(mask); err != nil { + return err + } + default: + return nil } } - return nil } func (p *parser) readUntil(delim byte) ([]byte, error) { diff --git a/parser_test.go b/parser_test.go index 7016d67..4555e0c 100644 --- a/parser_test.go +++ b/parser_test.go @@ -39,6 +39,14 @@ func TestBOM(t *testing.T) { t.Run("UTF-16-BE-BOM", func(t *testing.T) { }) + + t.Run("multiple UTF-8-BOM", func(t *testing.T) { + f, err := Load([]byte("\xef\xbb\xbf\xef\xbb\xbf[author]\nE-MAIL = example@email.com")) + require.NoError(t, err) + require.NotNil(t, f) + + assert.Equal(t, "example@email.com", f.Section("author").Key("E-MAIL").String()) + }) }) }