diff --git a/runconfig/opts/envfile.go b/runconfig/opts/envfile.go index ba8b4f2016..f723799215 100644 --- a/runconfig/opts/envfile.go +++ b/runconfig/opts/envfile.go @@ -2,9 +2,12 @@ package opts import ( "bufio" + "bytes" "fmt" "os" "strings" + "unicode" + "unicode/utf8" ) // ParseEnvFile reads a file with environment variables enumerated by lines @@ -29,9 +32,20 @@ func ParseEnvFile(filename string) ([]string, error) { lines := []string{} scanner := bufio.NewScanner(fh) + currentLine := 0 + utf8bom := []byte{0xEF, 0xBB, 0xBF} for scanner.Scan() { + scannedBytes := scanner.Bytes() + if !utf8.Valid(scannedBytes) { + return []string{}, fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v", filename, currentLine+1, scannedBytes) + } + // We trim UTF8 BOM + if currentLine == 0 { + scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom) + } // trim the line from all leading whitespace first - line := strings.TrimLeft(scanner.Text(), whiteSpaces) + line := strings.TrimLeftFunc(string(scannedBytes), unicode.IsSpace) + currentLine++ // line is not empty, and not starting with '#' if len(line) > 0 && !strings.HasPrefix(line, "#") { data := strings.SplitN(line, "=", 2) diff --git a/runconfig/opts/fixtures/utf16.env b/runconfig/opts/fixtures/utf16.env new file mode 100755 index 0000000000..3a73358fff Binary files /dev/null and b/runconfig/opts/fixtures/utf16.env differ diff --git a/runconfig/opts/fixtures/utf16be.env b/runconfig/opts/fixtures/utf16be.env new file mode 100755 index 0000000000..e523da7af4 Binary files /dev/null and b/runconfig/opts/fixtures/utf16be.env differ diff --git a/runconfig/opts/fixtures/utf8.env b/runconfig/opts/fixtures/utf8.env new file mode 100755 index 0000000000..1ce45055b7 --- /dev/null +++ b/runconfig/opts/fixtures/utf8.env @@ -0,0 +1,3 @@ +FOO=BAR +HELLO=您好 +BAR=FOO \ No newline at end of file diff --git a/runconfig/opts/parse_test.go b/runconfig/opts/parse_test.go index 67f4c40e37..a1be379ae8 100644 --- a/runconfig/opts/parse_test.go +++ b/runconfig/opts/parse_test.go @@ -666,6 +666,33 @@ func TestParseEnvfileVariables(t *testing.T) { } } +func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) { + // UTF8 with BOM + config, _, _, err := parseRun([]string{"--env-file=fixtures/utf8.env", "img", "cmd"}) + if err != nil { + t.Fatal(err) + } + env := []string{"FOO=BAR", "HELLO=" + string([]byte{0xe6, 0x82, 0xa8, 0xe5, 0xa5, 0xbd}), "BAR=FOO"} + if len(config.Env) != len(env) { + t.Fatalf("Expected a config with %d env variables, got %v: %v", len(env), len(config.Env), config.Env) + } + for i, v := range env { + if config.Env[i] != v { + t.Fatalf("Expected a config with [%s], got %v", v, []byte(config.Env[i])) + } + } + + // UTF16 with BOM + e := "contains invalid utf8 bytes at line" + if _, _, _, err := parseRun([]string{"--env-file=fixtures/utf16.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) { + t.Fatalf("Expected an error with message '%s', got %v", e, err) + } + // UTF16BE with BOM + if _, _, _, err := parseRun([]string{"--env-file=fixtures/utf16be.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) { + t.Fatalf("Expected an error with message '%s', got %v", e, err) + } +} + func TestParseLabelfileVariables(t *testing.T) { e := "open nonexistent: no such file or directory" if runtime.GOOS == "windows" {