From 49f392ff6b528d571eac538f5365bd51c4c83e5c Mon Sep 17 00:00:00 2001
From: John Howard <jhoward@microsoft.com>
Date: Tue, 22 Nov 2016 11:26:02 -0800
Subject: [PATCH] Windows: Builder case insensitive env

Signed-off-by: John Howard <jhoward@microsoft.com>
---
 builder/dockerfile/dispatchers.go        |  10 +-
 builder/dockerfile/envVarTest            | 228 ++++++++++++-----------
 builder/dockerfile/shell_parser.go       |  14 +-
 builder/dockerfile/shell_parser_test.go  |  26 ++-
 daemon/commit.go                         |   5 +
 integration-cli/docker_cli_build_test.go |  16 ++
 6 files changed, 177 insertions(+), 122 deletions(-)

diff --git a/builder/dockerfile/dispatchers.go b/builder/dockerfile/dispatchers.go
index 93598882f6..b57f59b473 100644
--- a/builder/dockerfile/dispatchers.go
+++ b/builder/dockerfile/dispatchers.go
@@ -71,14 +71,20 @@ func env(b *Builder, args []string, attributes map[string]bool, original string)
 		if len(args[j]) == 0 {
 			return errBlankCommandNames("ENV")
 		}
-
 		newVar := args[j] + "=" + args[j+1] + ""
 		commitStr += " " + newVar
 
 		gotOne := false
 		for i, envVar := range b.runConfig.Env {
 			envParts := strings.SplitN(envVar, "=", 2)
-			if envParts[0] == args[j] {
+			compareFrom := envParts[0]
+			compareTo := args[j]
+			if runtime.GOOS == "windows" {
+				// Case insensitive environment variables on Windows
+				compareFrom = strings.ToUpper(compareFrom)
+				compareTo = strings.ToUpper(compareTo)
+			}
+			if compareFrom == compareTo {
 				b.runConfig.Env[i] = newVar
 				gotOne = true
 				break
diff --git a/builder/dockerfile/envVarTest b/builder/dockerfile/envVarTest
index 1a7fe975a7..067dca9a54 100644
--- a/builder/dockerfile/envVarTest
+++ b/builder/dockerfile/envVarTest
@@ -1,112 +1,116 @@
-hello                    |     hello
-he'll'o                  |     hello
-he'llo                   |     hello
-he\'llo                  |     he'llo
-he\\'llo                 |     he\llo
-abc\tdef                 |     abctdef
-"abc\tdef"               |     abc\tdef
-'abc\tdef'               |     abc\tdef
-hello\                   |     hello
-hello\\                  |     hello\
-"hello                   |     hello
-"hello\"                 |     hello"
-"hel'lo"                 |     hel'lo
-'hello                   |     hello
-'hello\'                 |     hello\
-"''"                     |     ''
-$.                       |     $.
-$1                       |
-he$1x                    |     hex
-he$.x                    |     he$.x
-he$pwd.                  |     he.
-he$PWD                   |     he/home
-he\$PWD                  |     he$PWD
-he\\$PWD                 |     he\/home
-he\${}                   |     he${}
-he\${}xx                 |     he${}xx
-he${}                    |     he
-he${}xx                  |     hexx
-he${hi}                  |     he
-he${hi}xx                |     hexx
-he${PWD}                 |     he/home
-he${.}                   |     error
-he${XXX:-000}xx          |     he000xx
-he${PWD:-000}xx          |     he/homexx
-he${XXX:-$PWD}xx         |     he/homexx
-he${XXX:-${PWD:-yyy}}xx  |     he/homexx
-he${XXX:-${YYY:-yyy}}xx  |     heyyyxx
-he${XXX:YYY}             |     error
-he${XXX:+${PWD}}xx       |     hexx
-he${PWD:+${XXX}}xx       |     hexx
-he${PWD:+${SHELL}}xx     |     hebashxx
-he${XXX:+000}xx          |     hexx
-he${PWD:+000}xx          |     he000xx
-'he${XX}'                |     he${XX}
-"he${PWD}"               |     he/home
-"he'$PWD'"               |     he'/home'
-"$PWD"                   |     /home
-'$PWD'                   |     $PWD
-'\$PWD'                  |     \$PWD
-'"hello"'                |     "hello"
-he\$PWD                  |     he$PWD
-"he\$PWD"                |     he$PWD
-'he\$PWD'                |     he\$PWD
-he${PWD                  |     error
-he${PWD:=000}xx          |     error
-he${PWD:+${PWD}:}xx      |     he/home:xx
-he${XXX:-\$PWD:}xx       |     he$PWD:xx
-he${XXX:-\${PWD}z}xx     |     he${PWDz}xx
-안녕하세요                 |     안녕하세요
-안'녕'하세요               |     안녕하세요
-안'녕하세요                |     안녕하세요
-안녕\'하세요               |     안녕'하세요
-안\\'녕하세요              |     안\녕하세요
-안녕\t하세요               |     안녕t하세요
-"안녕\t하세요"             |     안녕\t하세요
-'안녕\t하세요              |     안녕\t하세요
-안녕하세요\                |     안녕하세요
-안녕하세요\\               |     안녕하세요\
-"안녕하세요                |     안녕하세요
-"안녕하세요\"              |     안녕하세요"
-"안녕'하세요"              |     안녕'하세요
-'안녕하세요                |     안녕하세요
-'안녕하세요\'              |     안녕하세요\
-안녕$1x                    |     안녕x
-안녕$.x                    |     안녕$.x
-안녕$pwd.                  |     안녕.
-안녕$PWD                   |     안녕/home
-안녕\$PWD                  |     안녕$PWD
-안녕\\$PWD                 |     안녕\/home
-안녕\${}                   |     안녕${}
-안녕\${}xx                 |     안녕${}xx
-안녕${}                    |     안녕
-안녕${}xx                  |     안녕xx
-안녕${hi}                  |     안녕
-안녕${hi}xx                |     안녕xx
-안녕${PWD}                 |     안녕/home
-안녕${.}                   |     error
-안녕${XXX:-000}xx          |     안녕000xx
-안녕${PWD:-000}xx          |     안녕/homexx
-안녕${XXX:-$PWD}xx         |     안녕/homexx
-안녕${XXX:-${PWD:-yyy}}xx  |     안녕/homexx
-안녕${XXX:-${YYY:-yyy}}xx  |     안녕yyyxx
-안녕${XXX:YYY}             |     error
-안녕${XXX:+${PWD}}xx       |     안녕xx
-안녕${PWD:+${XXX}}xx       |     안녕xx
-안녕${PWD:+${SHELL}}xx     |     안녕bashxx
-안녕${XXX:+000}xx          |     안녕xx
-안녕${PWD:+000}xx          |     안녕000xx
-'안녕${XX}'                |     안녕${XX}
-"안녕${PWD}"               |     안녕/home
-"안녕'$PWD'"               |     안녕'/home'
-'"안녕"'                   |     "안녕"
-안녕\$PWD                  |     안녕$PWD
-"안녕\$PWD"                |     안녕$PWD
-'안녕\$PWD'                |     안녕\$PWD
-안녕${PWD                  |     error
-안녕${PWD:=000}xx          |     error
-안녕${PWD:+${PWD}:}xx      |     안녕/home:xx
-안녕${XXX:-\$PWD:}xx       |     안녕$PWD:xx
-안녕${XXX:-\${PWD}z}xx     |     안녕${PWDz}xx
-$KOREAN                    |     한국어
-안녕$KOREAN                |     안녕한국어
+A|hello                    |     hello
+A|he'll'o                  |     hello
+A|he'llo                   |     hello
+A|he\'llo                  |     he'llo
+A|he\\'llo                 |     he\llo
+A|abc\tdef                 |     abctdef
+A|"abc\tdef"               |     abc\tdef
+A|'abc\tdef'               |     abc\tdef
+A|hello\                   |     hello
+A|hello\\                  |     hello\
+A|"hello                   |     hello
+A|"hello\"                 |     hello"
+A|"hel'lo"                 |     hel'lo
+A|'hello                   |     hello
+A|'hello\'                 |     hello\
+A|"''"                     |     ''
+A|$.                       |     $.
+A|$1                       |
+A|he$1x                    |     hex
+A|he$.x                    |     he$.x
+# Next one is different on Windows as $pwd==$PWD
+U|he$pwd.                  |     he.
+W|he$pwd.                  |     he/home.
+A|he$PWD                   |     he/home
+A|he\$PWD                  |     he$PWD
+A|he\\$PWD                 |     he\/home
+A|he\${}                   |     he${}
+A|he\${}xx                 |     he${}xx
+A|he${}                    |     he
+A|he${}xx                  |     hexx
+A|he${hi}                  |     he
+A|he${hi}xx                |     hexx
+A|he${PWD}                 |     he/home
+A|he${.}                   |     error
+A|he${XXX:-000}xx          |     he000xx
+A|he${PWD:-000}xx          |     he/homexx
+A|he${XXX:-$PWD}xx         |     he/homexx
+A|he${XXX:-${PWD:-yyy}}xx  |     he/homexx
+A|he${XXX:-${YYY:-yyy}}xx  |     heyyyxx
+A|he${XXX:YYY}             |     error
+A|he${XXX:+${PWD}}xx       |     hexx
+A|he${PWD:+${XXX}}xx       |     hexx
+A|he${PWD:+${SHELL}}xx     |     hebashxx
+A|he${XXX:+000}xx          |     hexx
+A|he${PWD:+000}xx          |     he000xx
+A|'he${XX}'                |     he${XX}
+A|"he${PWD}"               |     he/home
+A|"he'$PWD'"               |     he'/home'
+A|"$PWD"                   |     /home
+A|'$PWD'                   |     $PWD
+A|'\$PWD'                  |     \$PWD
+A|'"hello"'                |     "hello"
+A|he\$PWD                  |     he$PWD
+A|"he\$PWD"                |     he$PWD
+A|'he\$PWD'                |     he\$PWD
+A|he${PWD                  |     error
+A|he${PWD:=000}xx          |     error
+A|he${PWD:+${PWD}:}xx      |     he/home:xx
+A|he${XXX:-\$PWD:}xx       |     he$PWD:xx
+A|he${XXX:-\${PWD}z}xx     |     he${PWDz}xx
+A|안녕하세요                 |     안녕하세요
+A|안'녕'하세요               |     안녕하세요
+A|안'녕하세요                |     안녕하세요
+A|안녕\'하세요               |     안녕'하세요
+A|안\\'녕하세요              |     안\녕하세요
+A|안녕\t하세요               |     안녕t하세요
+A|"안녕\t하세요"             |     안녕\t하세요
+A|'안녕\t하세요              |     안녕\t하세요
+A|안녕하세요\                |     안녕하세요
+A|안녕하세요\\               |     안녕하세요\
+A|"안녕하세요                |     안녕하세요
+A|"안녕하세요\"              |     안녕하세요"
+A|"안녕'하세요"              |     안녕'하세요
+A|'안녕하세요                |     안녕하세요
+A|'안녕하세요\'              |     안녕하세요\
+A|안녕$1x                    |     안녕x
+A|안녕$.x                    |     안녕$.x
+# Next one is different on Windows as $pwd==$PWD
+U|안녕$pwd.                  |     안녕.
+W|안녕$pwd.                  |     안녕/home.
+A|안녕$PWD                   |     안녕/home
+A|안녕\$PWD                  |     안녕$PWD
+A|안녕\\$PWD                 |     안녕\/home
+A|안녕\${}                   |     안녕${}
+A|안녕\${}xx                 |     안녕${}xx
+A|안녕${}                    |     안녕
+A|안녕${}xx                  |     안녕xx
+A|안녕${hi}                  |     안녕
+A|안녕${hi}xx                |     안녕xx
+A|안녕${PWD}                 |     안녕/home
+A|안녕${.}                   |     error
+A|안녕${XXX:-000}xx          |     안녕000xx
+A|안녕${PWD:-000}xx          |     안녕/homexx
+A|안녕${XXX:-$PWD}xx         |     안녕/homexx
+A|안녕${XXX:-${PWD:-yyy}}xx  |     안녕/homexx
+A|안녕${XXX:-${YYY:-yyy}}xx  |     안녕yyyxx
+A|안녕${XXX:YYY}             |     error
+A|안녕${XXX:+${PWD}}xx       |     안녕xx
+A|안녕${PWD:+${XXX}}xx       |     안녕xx
+A|안녕${PWD:+${SHELL}}xx     |     안녕bashxx
+A|안녕${XXX:+000}xx          |     안녕xx
+A|안녕${PWD:+000}xx          |     안녕000xx
+A|'안녕${XX}'                |     안녕${XX}
+A|"안녕${PWD}"               |     안녕/home
+A|"안녕'$PWD'"               |     안녕'/home'
+A|'"안녕"'                   |     "안녕"
+A|안녕\$PWD                  |     안녕$PWD
+A|"안녕\$PWD"                |     안녕$PWD
+A|'안녕\$PWD'                |     안녕\$PWD
+A|안녕${PWD                  |     error
+A|안녕${PWD:=000}xx          |     error
+A|안녕${PWD:+${PWD}:}xx      |     안녕/home:xx
+A|안녕${XXX:-\$PWD:}xx       |     안녕$PWD:xx
+A|안녕${XXX:-\${PWD}z}xx     |     안녕${PWDz}xx
+A|$KOREAN                    |     한국어
+A|안녕$KOREAN                |     안녕한국어
diff --git a/builder/dockerfile/shell_parser.go b/builder/dockerfile/shell_parser.go
index 3a095299a8..189afd1fdb 100644
--- a/builder/dockerfile/shell_parser.go
+++ b/builder/dockerfile/shell_parser.go
@@ -8,6 +8,7 @@ package dockerfile
 
 import (
 	"fmt"
+	"runtime"
 	"strings"
 	"text/scanner"
 	"unicode"
@@ -298,9 +299,16 @@ func (sw *shellWord) processName() string {
 }
 
 func (sw *shellWord) getEnv(name string) string {
+	if runtime.GOOS == "windows" {
+		// Case-insensitive environment variables on Windows
+		name = strings.ToUpper(name)
+	}
 	for _, env := range sw.envs {
 		i := strings.Index(env, "=")
 		if i < 0 {
+			if runtime.GOOS == "windows" {
+				env = strings.ToUpper(env)
+			}
 			if name == env {
 				// Should probably never get here, but just in case treat
 				// it like "var" and "var=" are the same
@@ -308,7 +316,11 @@ func (sw *shellWord) getEnv(name string) string {
 			}
 			continue
 		}
-		if name != env[:i] {
+		compareName := env[:i]
+		if runtime.GOOS == "windows" {
+			compareName = strings.ToUpper(compareName)
+		}
+		if name != compareName {
 			continue
 		}
 		return env[i+1:]
diff --git a/builder/dockerfile/shell_parser_test.go b/builder/dockerfile/shell_parser_test.go
index 35dea63fb1..6cf691c077 100644
--- a/builder/dockerfile/shell_parser_test.go
+++ b/builder/dockerfile/shell_parser_test.go
@@ -3,12 +3,14 @@ package dockerfile
 import (
 	"bufio"
 	"os"
+	"runtime"
 	"strings"
 	"testing"
 )
 
 func TestShellParser4EnvVars(t *testing.T) {
 	fn := "envVarTest"
+	lineCount := 0
 
 	file, err := os.Open(fn)
 	if err != nil {
@@ -20,6 +22,7 @@ func TestShellParser4EnvVars(t *testing.T) {
 	envs := []string{"PWD=/home", "SHELL=bash", "KOREAN=한국어"}
 	for scanner.Scan() {
 		line := scanner.Text()
+		lineCount++
 
 		// Trim comments and blank lines
 		i := strings.Index(line, "#")
@@ -33,21 +36,30 @@ func TestShellParser4EnvVars(t *testing.T) {
 		}
 
 		words := strings.Split(line, "|")
-		if len(words) != 2 {
+		if len(words) != 3 {
 			t.Fatalf("Error in '%s' - should be exactly one | in:%q", fn, line)
 		}
 
 		words[0] = strings.TrimSpace(words[0])
 		words[1] = strings.TrimSpace(words[1])
+		words[2] = strings.TrimSpace(words[2])
 
-		newWord, err := ProcessWord(words[0], envs, '\\')
-
-		if err != nil {
-			newWord = "error"
+		// Key W=Windows; A=All; U=Unix
+		if (words[0] != "W") && (words[0] != "A") && (words[0] != "U") {
+			t.Fatalf("Invalid tag %s at line %d of %s. Must be W, A or U", words[0], lineCount, fn)
 		}
 
-		if newWord != words[1] {
-			t.Fatalf("Error. Src: %s  Calc: %s  Expected: %s", words[0], newWord, words[1])
+		if ((words[0] == "W" || words[0] == "A") && runtime.GOOS == "windows") ||
+			((words[0] == "U" || words[0] == "A") && runtime.GOOS != "windows") {
+			newWord, err := ProcessWord(words[1], envs, '\\')
+
+			if err != nil {
+				newWord = "error"
+			}
+
+			if newWord != words[2] {
+				t.Fatalf("Error. Src: %s  Calc: %s  Expected: %s at line %d", words[1], newWord, words[2], lineCount)
+			}
 		}
 	}
 }
diff --git a/daemon/commit.go b/daemon/commit.go
index ae6f51be3c..333f7f2f93 100644
--- a/daemon/commit.go
+++ b/daemon/commit.go
@@ -46,6 +46,11 @@ func merge(userConf, imageConf *containertypes.Config) error {
 			imageEnvKey := strings.Split(imageEnv, "=")[0]
 			for _, userEnv := range userConf.Env {
 				userEnvKey := strings.Split(userEnv, "=")[0]
+				if runtime.GOOS == "windows" {
+					// Case insensitive environment variables on Windows
+					imageEnvKey = strings.ToUpper(imageEnvKey)
+					userEnvKey = strings.ToUpper(userEnvKey)
+				}
 				if imageEnvKey == userEnvKey {
 					found = true
 					break
diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go
index eea4712fe7..e11f22d2f1 100644
--- a/integration-cli/docker_cli_build_test.go
+++ b/integration-cli/docker_cli_build_test.go
@@ -7311,3 +7311,19 @@ RUN ["cat", "/foo/file"]
 		c.Fatal(err)
 	}
 }
+
+// Case-insensitive environment variables on Windows
+func (s *DockerSuite) TestBuildWindowsEnvCaseInsensitive(c *check.C) {
+	testRequires(c, DaemonIsWindows)
+	name := "testbuildwindowsenvcaseinsensitive"
+	if _, err := buildImage(name, `
+		FROM `+WindowsBaseImage+`
+		ENV FOO=bar foo=bar
+  `, true); err != nil {
+		c.Fatal(err)
+	}
+	res := inspectFieldJSON(c, name, "Config.Env")
+	if res != `["foo=bar"]` { // Should not have FOO=bar in it - takes the last one processed. And only one entry as deduped.
+		c.Fatalf("Case insensitive environment variables on Windows failed. Got %s", res)
+	}
+}