Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dotenv: fix parse error on files with UTF-8 BOM #301

Merged
merged 1 commit into from Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions dotenv/fixtures/utf8-bom.env
@@ -0,0 +1,9 @@
OPTION_A=1
OPTION_B=2
OPTION_C= 3
OPTION_D =4
OPTION_E = 5
456 = ABC
OPTION_F =
OPTION_G=
OPTION_H = my string # Inline comment
7 changes: 7 additions & 0 deletions dotenv/godotenv.go
Expand Up @@ -14,6 +14,7 @@
package dotenv

import (
"bytes"
"errors"
"fmt"
"io"
Expand All @@ -29,6 +30,8 @@ import (

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

var utf8BOM = []byte("\uFEFF")

// LookupFn represents a lookup function to resolve variables from
type LookupFn func(string) (string, bool)

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

// seek past the UTF-8 BOM if it exists (particularly on Windows, some
// editors tend to add it, and it'll cause parsing to fail)
data = bytes.TrimPrefix(data, utf8BOM)

return UnmarshalBytesWithLookup(data, lookupFn)
}

Expand Down
30 changes: 27 additions & 3 deletions dotenv/godotenv_test.go
Expand Up @@ -7,6 +7,8 @@ import (
"reflect"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

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

func TestOveroadDoesOverride(t *testing.T) {
func TestOverloadDoesOverride(t *testing.T) {
envFileName := "fixtures/plain.env"

// ensure NO overload
Expand Down Expand Up @@ -525,7 +527,7 @@ func TestRoundtrip(t *testing.T) {
}
}

func TestInheritedEnvVariablSameSize(t *testing.T) {
func TestInheritedEnvVariableSameSize(t *testing.T) {
const envKey = "VAR_TO_BE_LOADED_FROM_OS_ENV"
const envVal = "SOME_RANDOM_VALUE"
os.Setenv(envKey, envVal)
Expand All @@ -551,7 +553,7 @@ func TestInheritedEnvVariablSameSize(t *testing.T) {
}
}

func TestInheritedEnvVariablSingleVar(t *testing.T) {
func TestInheritedEnvVariableSingleVar(t *testing.T) {
const envKey = "VAR_TO_BE_LOADED_FROM_OS_ENV"
const envVal = "SOME_RANDOM_VALUE"
os.Setenv(envKey, envVal)
Expand Down Expand Up @@ -702,3 +704,25 @@ func TestSubstitutionsWithUnsetVarEnvFileDefaultValuePrecedence(t *testing.T) {
}
}
}

func TestUTF8BOM(t *testing.T) {
envFileName := "fixtures/utf8-bom.env"

// sanity check the fixture, since the UTF-8 BOM is invisible, it'd be
// easy for it to get removed by accident, which would invalidate this
// test
envFileData, err := os.ReadFile(envFileName)
require.NoError(t, err)
require.True(t, bytes.HasPrefix(envFileData, []byte("\uFEFF")),
"Test fixture file is missing UTF-8 BOM")

expectedValues := map[string]string{
"OPTION_A": "1",
"OPTION_B": "2",
"OPTION_C": "3",
"OPTION_D": "4",
"OPTION_E": "5",
}

loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
}