Skip to content

Commit f4d0f7d

Browse files
committed
dotenv: fix parse error on files with UTF-8 BOM
Some Windows editors tend to add UTF-8 BOM markers to files, which breaks parsing of `.env` files. Now, when the file is read, if it starts with a UTF-8 BOM, we'll skip it. (`.env` files are always processed as UTF-8.) See docker/compose#9799. Signed-off-by: Milas Bowman <[email protected]>
1 parent 7aed131 commit f4d0f7d

File tree

3 files changed

+43
-3
lines changed

3 files changed

+43
-3
lines changed

dotenv/fixtures/utf8-bom.env

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
OPTION_A=1
2+
OPTION_B=2
3+
OPTION_C= 3
4+
OPTION_D =4
5+
OPTION_E = 5
6+
456 = ABC
7+
OPTION_F =
8+
OPTION_G=
9+
OPTION_H = my string # Inline comment

dotenv/godotenv.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package dotenv
1515

1616
import (
17+
"bytes"
1718
"errors"
1819
"fmt"
1920
"io"
@@ -29,6 +30,8 @@ import (
2930

3031
const doubleQuoteSpecialChars = "\\\n\r\"!$`"
3132

33+
var utf8BOM = []byte("\uFEFF")
34+
3235
// LookupFn represents a lookup function to resolve variables from
3336
type LookupFn func(string) (string, bool)
3437

@@ -48,6 +51,10 @@ func ParseWithLookup(r io.Reader, lookupFn LookupFn) (map[string]string, error)
4851
return nil, err
4952
}
5053

54+
// seek past the UTF-8 BOM if it exists (particularly on Windows, some
55+
// editors tend to add it, and it'll cause parsing to fail)
56+
data = bytes.TrimPrefix(data, utf8BOM)
57+
5158
return UnmarshalBytesWithLookup(data, lookupFn)
5259
}
5360

dotenv/godotenv_test.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"reflect"
88
"strings"
99
"testing"
10+
11+
"github.com/stretchr/testify/require"
1012
)
1113

1214
var noopPresets = make(map[string]string)
@@ -131,7 +133,7 @@ func TestLoadDoesNotOverride(t *testing.T) {
131133
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, presets)
132134
}
133135

134-
func TestOveroadDoesOverride(t *testing.T) {
136+
func TestOverloadDoesOverride(t *testing.T) {
135137
envFileName := "fixtures/plain.env"
136138

137139
// ensure NO overload
@@ -525,7 +527,7 @@ func TestRoundtrip(t *testing.T) {
525527
}
526528
}
527529

528-
func TestInheritedEnvVariablSameSize(t *testing.T) {
530+
func TestInheritedEnvVariableSameSize(t *testing.T) {
529531
const envKey = "VAR_TO_BE_LOADED_FROM_OS_ENV"
530532
const envVal = "SOME_RANDOM_VALUE"
531533
os.Setenv(envKey, envVal)
@@ -551,7 +553,7 @@ func TestInheritedEnvVariablSameSize(t *testing.T) {
551553
}
552554
}
553555

554-
func TestInheritedEnvVariablSingleVar(t *testing.T) {
556+
func TestInheritedEnvVariableSingleVar(t *testing.T) {
555557
const envKey = "VAR_TO_BE_LOADED_FROM_OS_ENV"
556558
const envVal = "SOME_RANDOM_VALUE"
557559
os.Setenv(envKey, envVal)
@@ -702,3 +704,25 @@ func TestSubstitutionsWithUnsetVarEnvFileDefaultValuePrecedence(t *testing.T) {
702704
}
703705
}
704706
}
707+
708+
func TestUTF8BOM(t *testing.T) {
709+
envFileName := "fixtures/utf8-bom.env"
710+
711+
// sanity check the fixture, since the UTF-8 BOM is invisible, it'd be
712+
// easy for it to get removed by accident, which would invalidate this
713+
// test
714+
envFileData, err := os.ReadFile(envFileName)
715+
require.NoError(t, err)
716+
require.True(t, bytes.HasPrefix(envFileData, []byte("\uFEFF")),
717+
"Test fixture file is missing UTF-8 BOM")
718+
719+
expectedValues := map[string]string{
720+
"OPTION_A": "1",
721+
"OPTION_B": "2",
722+
"OPTION_C": "3",
723+
"OPTION_D": "4",
724+
"OPTION_E": "5",
725+
}
726+
727+
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
728+
}

0 commit comments

Comments
 (0)