Browse code

Move some validators from opts to runconfig/opts.

These validators are only used by runconfig.Parse() or some other part of the
client, so move them into the client-side package.

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2015/12/30 01:28:21
Showing 10 changed files
... ...
@@ -57,7 +57,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
57 57
 	flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
58 58
 	flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
59 59
 	flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
60
-	flBuildArg := opts.NewListOpts(opts.ValidateEnv)
60
+	flBuildArg := opts.NewListOpts(runconfigopts.ValidateEnv)
61 61
 	cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
62 62
 	isolation := cmd.String([]string{"-isolation"}, "", "Container isolation level")
63 63
 
64 64
deleted file mode 100644
... ...
@@ -1,67 +0,0 @@
1
-package opts
2
-
3
-import (
4
-	"bufio"
5
-	"fmt"
6
-	"os"
7
-	"strings"
8
-)
9
-
10
-// ParseEnvFile reads a file with environment variables enumerated by lines
11
-//
12
-// ``Environment variable names used by the utilities in the Shell and
13
-// Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase
14
-// letters, digits, and the '_' (underscore) from the characters defined in
15
-// Portable Character Set and do not begin with a digit. *But*, other
16
-// characters may be permitted by an implementation; applications shall
17
-// tolerate the presence of such names.''
18
-// -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
19
-//
20
-// As of #16585, it's up to application inside docker to validate or not
21
-// environment variables, that's why we just strip leading whitespace and
22
-// nothing more.
23
-func ParseEnvFile(filename string) ([]string, error) {
24
-	fh, err := os.Open(filename)
25
-	if err != nil {
26
-		return []string{}, err
27
-	}
28
-	defer fh.Close()
29
-
30
-	lines := []string{}
31
-	scanner := bufio.NewScanner(fh)
32
-	for scanner.Scan() {
33
-		// trim the line from all leading whitespace first
34
-		line := strings.TrimLeft(scanner.Text(), whiteSpaces)
35
-		// line is not empty, and not starting with '#'
36
-		if len(line) > 0 && !strings.HasPrefix(line, "#") {
37
-			data := strings.SplitN(line, "=", 2)
38
-
39
-			// trim the front of a variable, but nothing else
40
-			variable := strings.TrimLeft(data[0], whiteSpaces)
41
-			if strings.ContainsAny(variable, whiteSpaces) {
42
-				return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' has white spaces", variable)}
43
-			}
44
-
45
-			if len(data) > 1 {
46
-
47
-				// pass the value through, no trimming
48
-				lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1]))
49
-			} else {
50
-				// if only a pass-through variable is given, clean it up.
51
-				lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), os.Getenv(line)))
52
-			}
53
-		}
54
-	}
55
-	return lines, scanner.Err()
56
-}
57
-
58
-var whiteSpaces = " \t"
59
-
60
-// ErrBadEnvVariable typed error for bad environment variable
61
-type ErrBadEnvVariable struct {
62
-	msg string
63
-}
64
-
65
-func (e ErrBadEnvVariable) Error() string {
66
-	return fmt.Sprintf("poorly formatted environment: %s", e.msg)
67
-}
68 1
deleted file mode 100644
... ...
@@ -1,142 +0,0 @@
1
-package opts
2
-
3
-import (
4
-	"bufio"
5
-	"fmt"
6
-	"io/ioutil"
7
-	"os"
8
-	"reflect"
9
-	"strings"
10
-	"testing"
11
-)
12
-
13
-func tmpFileWithContent(content string, t *testing.T) string {
14
-	tmpFile, err := ioutil.TempFile("", "envfile-test")
15
-	if err != nil {
16
-		t.Fatal(err)
17
-	}
18
-	defer tmpFile.Close()
19
-
20
-	tmpFile.WriteString(content)
21
-	return tmpFile.Name()
22
-}
23
-
24
-// Test ParseEnvFile for a file with a few well formatted lines
25
-func TestParseEnvFileGoodFile(t *testing.T) {
26
-	content := `foo=bar
27
-    baz=quux
28
-# comment
29
-
30
-_foobar=foobaz
31
-with.dots=working
32
-and_underscore=working too
33
-`
34
-	// Adding a newline + a line with pure whitespace.
35
-	// This is being done like this instead of the block above
36
-	// because it's common for editors to trim trailing whitespace
37
-	// from lines, which becomes annoying since that's the
38
-	// exact thing we need to test.
39
-	content += "\n    \t  "
40
-	tmpFile := tmpFileWithContent(content, t)
41
-	defer os.Remove(tmpFile)
42
-
43
-	lines, err := ParseEnvFile(tmpFile)
44
-	if err != nil {
45
-		t.Fatal(err)
46
-	}
47
-
48
-	expectedLines := []string{
49
-		"foo=bar",
50
-		"baz=quux",
51
-		"_foobar=foobaz",
52
-		"with.dots=working",
53
-		"and_underscore=working too",
54
-	}
55
-
56
-	if !reflect.DeepEqual(lines, expectedLines) {
57
-		t.Fatal("lines not equal to expected_lines")
58
-	}
59
-}
60
-
61
-// Test ParseEnvFile for an empty file
62
-func TestParseEnvFileEmptyFile(t *testing.T) {
63
-	tmpFile := tmpFileWithContent("", t)
64
-	defer os.Remove(tmpFile)
65
-
66
-	lines, err := ParseEnvFile(tmpFile)
67
-	if err != nil {
68
-		t.Fatal(err)
69
-	}
70
-
71
-	if len(lines) != 0 {
72
-		t.Fatal("lines not empty; expected empty")
73
-	}
74
-}
75
-
76
-// Test ParseEnvFile for a non existent file
77
-func TestParseEnvFileNonExistentFile(t *testing.T) {
78
-	_, err := ParseEnvFile("foo_bar_baz")
79
-	if err == nil {
80
-		t.Fatal("ParseEnvFile succeeded; expected failure")
81
-	}
82
-	if _, ok := err.(*os.PathError); !ok {
83
-		t.Fatalf("Expected a PathError, got [%v]", err)
84
-	}
85
-}
86
-
87
-// Test ParseEnvFile for a badly formatted file
88
-func TestParseEnvFileBadlyFormattedFile(t *testing.T) {
89
-	content := `foo=bar
90
-    f   =quux
91
-`
92
-
93
-	tmpFile := tmpFileWithContent(content, t)
94
-	defer os.Remove(tmpFile)
95
-
96
-	_, err := ParseEnvFile(tmpFile)
97
-	if err == nil {
98
-		t.Fatalf("Expected a ErrBadEnvVariable, got nothing")
99
-	}
100
-	if _, ok := err.(ErrBadEnvVariable); !ok {
101
-		t.Fatalf("Expected a ErrBadEnvVariable, got [%v]", err)
102
-	}
103
-	expectedMessage := "poorly formatted environment: variable 'f   ' has white spaces"
104
-	if err.Error() != expectedMessage {
105
-		t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
106
-	}
107
-}
108
-
109
-// Test ParseEnvFile for a file with a line exceeding bufio.MaxScanTokenSize
110
-func TestParseEnvFileLineTooLongFile(t *testing.T) {
111
-	content := strings.Repeat("a", bufio.MaxScanTokenSize+42)
112
-	content = fmt.Sprint("foo=", content)
113
-
114
-	tmpFile := tmpFileWithContent(content, t)
115
-	defer os.Remove(tmpFile)
116
-
117
-	_, err := ParseEnvFile(tmpFile)
118
-	if err == nil {
119
-		t.Fatal("ParseEnvFile succeeded; expected failure")
120
-	}
121
-}
122
-
123
-// ParseEnvFile with a random file, pass through
124
-func TestParseEnvFileRandomFile(t *testing.T) {
125
-	content := `first line
126
-another invalid line`
127
-	tmpFile := tmpFileWithContent(content, t)
128
-	defer os.Remove(tmpFile)
129
-
130
-	_, err := ParseEnvFile(tmpFile)
131
-
132
-	if err == nil {
133
-		t.Fatalf("Expected a ErrBadEnvVariable, got nothing")
134
-	}
135
-	if _, ok := err.(ErrBadEnvVariable); !ok {
136
-		t.Fatalf("Expected a ErrBadEnvvariable, got [%v]", err)
137
-	}
138
-	expectedMessage := "poorly formatted environment: variable 'first line' has white spaces"
139
-	if err.Error() != expectedMessage {
140
-		t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
141
-	}
142
-}
... ...
@@ -3,7 +3,6 @@ package opts
3 3
 import (
4 4
 	"fmt"
5 5
 	"net"
6
-	"os"
7 6
 	"regexp"
8 7
 	"strings"
9 8
 )
... ...
@@ -152,34 +151,6 @@ type ValidatorFctType func(val string) (string, error)
152 152
 // ValidatorFctListType defines a validator function that returns a validated list of string and/or an error
153 153
 type ValidatorFctListType func(val string) ([]string, error)
154 154
 
155
-// ValidateAttach validates that the specified string is a valid attach option.
156
-func ValidateAttach(val string) (string, error) {
157
-	s := strings.ToLower(val)
158
-	for _, str := range []string{"stdin", "stdout", "stderr"} {
159
-		if s == str {
160
-			return s, nil
161
-		}
162
-	}
163
-	return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR")
164
-}
165
-
166
-// ValidateEnv validates an environment variable and returns it.
167
-// If no value is specified, it returns the current value using os.Getenv.
168
-//
169
-// As on ParseEnvFile and related to #16585, environment variable names
170
-// are not validate what so ever, it's up to application inside docker
171
-// to validate them or not.
172
-func ValidateEnv(val string) (string, error) {
173
-	arr := strings.Split(val, "=")
174
-	if len(arr) > 1 {
175
-		return val, nil
176
-	}
177
-	if !doesEnvExist(val) {
178
-		return val, nil
179
-	}
180
-	return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
181
-}
182
-
183 155
 // ValidateIPAddress validates an Ip address.
184 156
 func ValidateIPAddress(val string) (string, error) {
185 157
 	var ip = net.ParseIP(strings.TrimSpace(val))
... ...
@@ -189,15 +160,6 @@ func ValidateIPAddress(val string) (string, error) {
189 189
 	return "", fmt.Errorf("%s is not an ip address", val)
190 190
 }
191 191
 
192
-// ValidateMACAddress validates a MAC address.
193
-func ValidateMACAddress(val string) (string, error) {
194
-	_, err := net.ParseMAC(strings.TrimSpace(val))
195
-	if err != nil {
196
-		return "", err
197
-	}
198
-	return val, nil
199
-}
200
-
201 192
 // ValidateDNSSearch validates domain for resolvconf search configuration.
202 193
 // A zero length domain is represented by a dot (.).
203 194
 func ValidateDNSSearch(val string) (string, error) {
... ...
@@ -218,20 +180,6 @@ func validateDomain(val string) (string, error) {
218 218
 	return "", fmt.Errorf("%s is not a valid domain", val)
219 219
 }
220 220
 
221
-// ValidateExtraHost validates that the specified string is a valid extrahost and returns it.
222
-// ExtraHost are in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6).
223
-func ValidateExtraHost(val string) (string, error) {
224
-	// allow for IPv6 addresses in extra hosts by only splitting on first ":"
225
-	arr := strings.SplitN(val, ":", 2)
226
-	if len(arr) != 2 || len(arr[0]) == 0 {
227
-		return "", fmt.Errorf("bad format for add-host: %q", val)
228
-	}
229
-	if _, err := ValidateIPAddress(arr[1]); err != nil {
230
-		return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1])
231
-	}
232
-	return val, nil
233
-}
234
-
235 221
 // ValidateLabel validates that the specified string is a valid label, and returns it.
236 222
 // Labels are in the form on key=value.
237 223
 func ValidateLabel(val string) (string, error) {
... ...
@@ -240,13 +188,3 @@ func ValidateLabel(val string) (string, error) {
240 240
 	}
241 241
 	return val, nil
242 242
 }
243
-
244
-func doesEnvExist(name string) bool {
245
-	for _, entry := range os.Environ() {
246
-		parts := strings.SplitN(entry, "=", 2)
247
-		if parts[0] == name {
248
-			return true
249
-		}
250
-	}
251
-	return false
252
-}
... ...
@@ -2,7 +2,6 @@ package opts
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"os"
6 5
 	"strings"
7 6
 	"testing"
8 7
 )
... ...
@@ -55,20 +54,6 @@ func TestMapOpts(t *testing.T) {
55 55
 	}
56 56
 }
57 57
 
58
-func TestValidateMACAddress(t *testing.T) {
59
-	if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil {
60
-		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err)
61
-	}
62
-
63
-	if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil {
64
-		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC")
65
-	}
66
-
67
-	if _, err := ValidateMACAddress(`random invalid string`); err == nil {
68
-		t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC")
69
-	}
70
-}
71
-
72 58
 func TestListOptsWithoutValidator(t *testing.T) {
73 59
 	o := NewListOpts(nil)
74 60
 	o.Set("foo")
... ...
@@ -188,92 +173,6 @@ func TestValidateDNSSearch(t *testing.T) {
188 188
 	}
189 189
 }
190 190
 
191
-func TestValidateExtraHosts(t *testing.T) {
192
-	valid := []string{
193
-		`myhost:192.168.0.1`,
194
-		`thathost:10.0.2.1`,
195
-		`anipv6host:2003:ab34:e::1`,
196
-		`ipv6local:::1`,
197
-	}
198
-
199
-	invalid := map[string]string{
200
-		`myhost:192.notanipaddress.1`:  `invalid IP`,
201
-		`thathost-nosemicolon10.0.0.1`: `bad format`,
202
-		`anipv6host:::::1`:             `invalid IP`,
203
-		`ipv6local:::0::`:              `invalid IP`,
204
-	}
205
-
206
-	for _, extrahost := range valid {
207
-		if _, err := ValidateExtraHost(extrahost); err != nil {
208
-			t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err)
209
-		}
210
-	}
211
-
212
-	for extraHost, expectedError := range invalid {
213
-		if _, err := ValidateExtraHost(extraHost); err == nil {
214
-			t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost)
215
-		} else {
216
-			if !strings.Contains(err.Error(), expectedError) {
217
-				t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError)
218
-			}
219
-		}
220
-	}
221
-}
222
-
223
-func TestValidateAttach(t *testing.T) {
224
-	valid := []string{
225
-		"stdin",
226
-		"stdout",
227
-		"stderr",
228
-		"STDIN",
229
-		"STDOUT",
230
-		"STDERR",
231
-	}
232
-	if _, err := ValidateAttach("invalid"); err == nil {
233
-		t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
234
-	}
235
-
236
-	for _, attach := range valid {
237
-		value, err := ValidateAttach(attach)
238
-		if err != nil {
239
-			t.Fatal(err)
240
-		}
241
-		if value != strings.ToLower(attach) {
242
-			t.Fatalf("Expected [%v], got [%v]", attach, value)
243
-		}
244
-	}
245
-}
246
-
247
-func TestValidateEnv(t *testing.T) {
248
-	valids := map[string]string{
249
-		"a":                   "a",
250
-		"something":           "something",
251
-		"_=a":                 "_=a",
252
-		"env1=value1":         "env1=value1",
253
-		"_env1=value1":        "_env1=value1",
254
-		"env2=value2=value3":  "env2=value2=value3",
255
-		"env3=abc!qwe":        "env3=abc!qwe",
256
-		"env_4=value 4":       "env_4=value 4",
257
-		"PATH":                fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
258
-		"PATH=something":      "PATH=something",
259
-		"asd!qwe":             "asd!qwe",
260
-		"1asd":                "1asd",
261
-		"123":                 "123",
262
-		"some space":          "some space",
263
-		"  some space before": "  some space before",
264
-		"some space after  ":  "some space after  ",
265
-	}
266
-	for value, expected := range valids {
267
-		actual, err := ValidateEnv(value)
268
-		if err != nil {
269
-			t.Fatal(err)
270
-		}
271
-		if actual != expected {
272
-			t.Fatalf("Expected [%v], got [%v]", expected, actual)
273
-		}
274
-	}
275
-}
276
-
277 191
 func TestValidateLabel(t *testing.T) {
278 192
 	if _, err := ValidateLabel("label"); err == nil || err.Error() != "bad attribute format: label" {
279 193
 		t.Fatalf("Expected an error [bad attribute format: label], go %v", err)
280 194
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+package opts
1
+
2
+import (
3
+	"bufio"
4
+	"fmt"
5
+	"os"
6
+	"strings"
7
+)
8
+
9
+// ParseEnvFile reads a file with environment variables enumerated by lines
10
+//
11
+// ``Environment variable names used by the utilities in the Shell and
12
+// Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase
13
+// letters, digits, and the '_' (underscore) from the characters defined in
14
+// Portable Character Set and do not begin with a digit. *But*, other
15
+// characters may be permitted by an implementation; applications shall
16
+// tolerate the presence of such names.''
17
+// -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
18
+//
19
+// As of #16585, it's up to application inside docker to validate or not
20
+// environment variables, that's why we just strip leading whitespace and
21
+// nothing more.
22
+func ParseEnvFile(filename string) ([]string, error) {
23
+	fh, err := os.Open(filename)
24
+	if err != nil {
25
+		return []string{}, err
26
+	}
27
+	defer fh.Close()
28
+
29
+	lines := []string{}
30
+	scanner := bufio.NewScanner(fh)
31
+	for scanner.Scan() {
32
+		// trim the line from all leading whitespace first
33
+		line := strings.TrimLeft(scanner.Text(), whiteSpaces)
34
+		// line is not empty, and not starting with '#'
35
+		if len(line) > 0 && !strings.HasPrefix(line, "#") {
36
+			data := strings.SplitN(line, "=", 2)
37
+
38
+			// trim the front of a variable, but nothing else
39
+			variable := strings.TrimLeft(data[0], whiteSpaces)
40
+			if strings.ContainsAny(variable, whiteSpaces) {
41
+				return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' has white spaces", variable)}
42
+			}
43
+
44
+			if len(data) > 1 {
45
+
46
+				// pass the value through, no trimming
47
+				lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1]))
48
+			} else {
49
+				// if only a pass-through variable is given, clean it up.
50
+				lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), os.Getenv(line)))
51
+			}
52
+		}
53
+	}
54
+	return lines, scanner.Err()
55
+}
56
+
57
+var whiteSpaces = " \t"
58
+
59
+// ErrBadEnvVariable typed error for bad environment variable
60
+type ErrBadEnvVariable struct {
61
+	msg string
62
+}
63
+
64
+func (e ErrBadEnvVariable) Error() string {
65
+	return fmt.Sprintf("poorly formatted environment: %s", e.msg)
66
+}
0 67
new file mode 100644
... ...
@@ -0,0 +1,142 @@
0
+package opts
1
+
2
+import (
3
+	"bufio"
4
+	"fmt"
5
+	"io/ioutil"
6
+	"os"
7
+	"reflect"
8
+	"strings"
9
+	"testing"
10
+)
11
+
12
+func tmpFileWithContent(content string, t *testing.T) string {
13
+	tmpFile, err := ioutil.TempFile("", "envfile-test")
14
+	if err != nil {
15
+		t.Fatal(err)
16
+	}
17
+	defer tmpFile.Close()
18
+
19
+	tmpFile.WriteString(content)
20
+	return tmpFile.Name()
21
+}
22
+
23
+// Test ParseEnvFile for a file with a few well formatted lines
24
+func TestParseEnvFileGoodFile(t *testing.T) {
25
+	content := `foo=bar
26
+    baz=quux
27
+# comment
28
+
29
+_foobar=foobaz
30
+with.dots=working
31
+and_underscore=working too
32
+`
33
+	// Adding a newline + a line with pure whitespace.
34
+	// This is being done like this instead of the block above
35
+	// because it's common for editors to trim trailing whitespace
36
+	// from lines, which becomes annoying since that's the
37
+	// exact thing we need to test.
38
+	content += "\n    \t  "
39
+	tmpFile := tmpFileWithContent(content, t)
40
+	defer os.Remove(tmpFile)
41
+
42
+	lines, err := ParseEnvFile(tmpFile)
43
+	if err != nil {
44
+		t.Fatal(err)
45
+	}
46
+
47
+	expectedLines := []string{
48
+		"foo=bar",
49
+		"baz=quux",
50
+		"_foobar=foobaz",
51
+		"with.dots=working",
52
+		"and_underscore=working too",
53
+	}
54
+
55
+	if !reflect.DeepEqual(lines, expectedLines) {
56
+		t.Fatal("lines not equal to expected_lines")
57
+	}
58
+}
59
+
60
+// Test ParseEnvFile for an empty file
61
+func TestParseEnvFileEmptyFile(t *testing.T) {
62
+	tmpFile := tmpFileWithContent("", t)
63
+	defer os.Remove(tmpFile)
64
+
65
+	lines, err := ParseEnvFile(tmpFile)
66
+	if err != nil {
67
+		t.Fatal(err)
68
+	}
69
+
70
+	if len(lines) != 0 {
71
+		t.Fatal("lines not empty; expected empty")
72
+	}
73
+}
74
+
75
+// Test ParseEnvFile for a non existent file
76
+func TestParseEnvFileNonExistentFile(t *testing.T) {
77
+	_, err := ParseEnvFile("foo_bar_baz")
78
+	if err == nil {
79
+		t.Fatal("ParseEnvFile succeeded; expected failure")
80
+	}
81
+	if _, ok := err.(*os.PathError); !ok {
82
+		t.Fatalf("Expected a PathError, got [%v]", err)
83
+	}
84
+}
85
+
86
+// Test ParseEnvFile for a badly formatted file
87
+func TestParseEnvFileBadlyFormattedFile(t *testing.T) {
88
+	content := `foo=bar
89
+    f   =quux
90
+`
91
+
92
+	tmpFile := tmpFileWithContent(content, t)
93
+	defer os.Remove(tmpFile)
94
+
95
+	_, err := ParseEnvFile(tmpFile)
96
+	if err == nil {
97
+		t.Fatalf("Expected a ErrBadEnvVariable, got nothing")
98
+	}
99
+	if _, ok := err.(ErrBadEnvVariable); !ok {
100
+		t.Fatalf("Expected a ErrBadEnvVariable, got [%v]", err)
101
+	}
102
+	expectedMessage := "poorly formatted environment: variable 'f   ' has white spaces"
103
+	if err.Error() != expectedMessage {
104
+		t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
105
+	}
106
+}
107
+
108
+// Test ParseEnvFile for a file with a line exceeding bufio.MaxScanTokenSize
109
+func TestParseEnvFileLineTooLongFile(t *testing.T) {
110
+	content := strings.Repeat("a", bufio.MaxScanTokenSize+42)
111
+	content = fmt.Sprint("foo=", content)
112
+
113
+	tmpFile := tmpFileWithContent(content, t)
114
+	defer os.Remove(tmpFile)
115
+
116
+	_, err := ParseEnvFile(tmpFile)
117
+	if err == nil {
118
+		t.Fatal("ParseEnvFile succeeded; expected failure")
119
+	}
120
+}
121
+
122
+// ParseEnvFile with a random file, pass through
123
+func TestParseEnvFileRandomFile(t *testing.T) {
124
+	content := `first line
125
+another invalid line`
126
+	tmpFile := tmpFileWithContent(content, t)
127
+	defer os.Remove(tmpFile)
128
+
129
+	_, err := ParseEnvFile(tmpFile)
130
+
131
+	if err == nil {
132
+		t.Fatalf("Expected a ErrBadEnvVariable, got nothing")
133
+	}
134
+	if _, ok := err.(ErrBadEnvVariable); !ok {
135
+		t.Fatalf("Expected a ErrBadEnvvariable, got [%v]", err)
136
+	}
137
+	expectedMessage := "poorly formatted environment: variable 'first line' has white spaces"
138
+	if err.Error() != expectedMessage {
139
+		t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
140
+	}
141
+}
0 142
new file mode 100644
... ...
@@ -0,0 +1,70 @@
0
+package opts
1
+
2
+import (
3
+	"fmt"
4
+	fopts "github.com/docker/docker/opts"
5
+	"net"
6
+	"os"
7
+	"strings"
8
+)
9
+
10
+// ValidateAttach validates that the specified string is a valid attach option.
11
+func ValidateAttach(val string) (string, error) {
12
+	s := strings.ToLower(val)
13
+	for _, str := range []string{"stdin", "stdout", "stderr"} {
14
+		if s == str {
15
+			return s, nil
16
+		}
17
+	}
18
+	return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR")
19
+}
20
+
21
+// ValidateEnv validates an environment variable and returns it.
22
+// If no value is specified, it returns the current value using os.Getenv.
23
+//
24
+// As on ParseEnvFile and related to #16585, environment variable names
25
+// are not validate what so ever, it's up to application inside docker
26
+// to validate them or not.
27
+func ValidateEnv(val string) (string, error) {
28
+	arr := strings.Split(val, "=")
29
+	if len(arr) > 1 {
30
+		return val, nil
31
+	}
32
+	if !doesEnvExist(val) {
33
+		return val, nil
34
+	}
35
+	return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
36
+}
37
+
38
+func doesEnvExist(name string) bool {
39
+	for _, entry := range os.Environ() {
40
+		parts := strings.SplitN(entry, "=", 2)
41
+		if parts[0] == name {
42
+			return true
43
+		}
44
+	}
45
+	return false
46
+}
47
+
48
+// ValidateExtraHost validates that the specified string is a valid extrahost and returns it.
49
+// ExtraHost are in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6).
50
+func ValidateExtraHost(val string) (string, error) {
51
+	// allow for IPv6 addresses in extra hosts by only splitting on first ":"
52
+	arr := strings.SplitN(val, ":", 2)
53
+	if len(arr) != 2 || len(arr[0]) == 0 {
54
+		return "", fmt.Errorf("bad format for add-host: %q", val)
55
+	}
56
+	if _, err := fopts.ValidateIPAddress(arr[1]); err != nil {
57
+		return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1])
58
+	}
59
+	return val, nil
60
+}
61
+
62
+// ValidateMACAddress validates a MAC address.
63
+func ValidateMACAddress(val string) (string, error) {
64
+	_, err := net.ParseMAC(strings.TrimSpace(val))
65
+	if err != nil {
66
+		return "", err
67
+	}
68
+	return val, nil
69
+}
0 70
new file mode 100644
... ...
@@ -0,0 +1,108 @@
0
+package opts
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+	"strings"
6
+	"testing"
7
+)
8
+
9
+func TestValidateAttach(t *testing.T) {
10
+	valid := []string{
11
+		"stdin",
12
+		"stdout",
13
+		"stderr",
14
+		"STDIN",
15
+		"STDOUT",
16
+		"STDERR",
17
+	}
18
+	if _, err := ValidateAttach("invalid"); err == nil {
19
+		t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
20
+	}
21
+
22
+	for _, attach := range valid {
23
+		value, err := ValidateAttach(attach)
24
+		if err != nil {
25
+			t.Fatal(err)
26
+		}
27
+		if value != strings.ToLower(attach) {
28
+			t.Fatalf("Expected [%v], got [%v]", attach, value)
29
+		}
30
+	}
31
+}
32
+
33
+func TestValidateEnv(t *testing.T) {
34
+	valids := map[string]string{
35
+		"a":                   "a",
36
+		"something":           "something",
37
+		"_=a":                 "_=a",
38
+		"env1=value1":         "env1=value1",
39
+		"_env1=value1":        "_env1=value1",
40
+		"env2=value2=value3":  "env2=value2=value3",
41
+		"env3=abc!qwe":        "env3=abc!qwe",
42
+		"env_4=value 4":       "env_4=value 4",
43
+		"PATH":                fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
44
+		"PATH=something":      "PATH=something",
45
+		"asd!qwe":             "asd!qwe",
46
+		"1asd":                "1asd",
47
+		"123":                 "123",
48
+		"some space":          "some space",
49
+		"  some space before": "  some space before",
50
+		"some space after  ":  "some space after  ",
51
+	}
52
+	for value, expected := range valids {
53
+		actual, err := ValidateEnv(value)
54
+		if err != nil {
55
+			t.Fatal(err)
56
+		}
57
+		if actual != expected {
58
+			t.Fatalf("Expected [%v], got [%v]", expected, actual)
59
+		}
60
+	}
61
+}
62
+
63
+func TestValidateExtraHosts(t *testing.T) {
64
+	valid := []string{
65
+		`myhost:192.168.0.1`,
66
+		`thathost:10.0.2.1`,
67
+		`anipv6host:2003:ab34:e::1`,
68
+		`ipv6local:::1`,
69
+	}
70
+
71
+	invalid := map[string]string{
72
+		`myhost:192.notanipaddress.1`:  `invalid IP`,
73
+		`thathost-nosemicolon10.0.0.1`: `bad format`,
74
+		`anipv6host:::::1`:             `invalid IP`,
75
+		`ipv6local:::0::`:              `invalid IP`,
76
+	}
77
+
78
+	for _, extrahost := range valid {
79
+		if _, err := ValidateExtraHost(extrahost); err != nil {
80
+			t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err)
81
+		}
82
+	}
83
+
84
+	for extraHost, expectedError := range invalid {
85
+		if _, err := ValidateExtraHost(extraHost); err == nil {
86
+			t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost)
87
+		} else {
88
+			if !strings.Contains(err.Error(), expectedError) {
89
+				t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError)
90
+			}
91
+		}
92
+	}
93
+}
94
+
95
+func TestValidateMACAddress(t *testing.T) {
96
+	if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil {
97
+		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err)
98
+	}
99
+
100
+	if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil {
101
+		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC")
102
+	}
103
+
104
+	if _, err := ValidateMACAddress(`random invalid string`); err == nil {
105
+		t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC")
106
+	}
107
+}
... ...
@@ -22,7 +22,7 @@ import (
22 22
 func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
23 23
 	var (
24 24
 		// FIXME: use utils.ListOpts for attach and volumes?
25
-		flAttach            = opts.NewListOpts(opts.ValidateAttach)
25
+		flAttach            = opts.NewListOpts(ValidateAttach)
26 26
 		flVolumes           = opts.NewListOpts(nil)
27 27
 		flTmpfs             = opts.NewListOpts(nil)
28 28
 		flBlkioWeightDevice = NewWeightdeviceOpt(ValidateWeightDevice)
... ...
@@ -31,8 +31,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
31 31
 		flLinks             = opts.NewListOpts(ValidateLink)
32 32
 		flDeviceReadIOps    = NewThrottledeviceOpt(ValidateThrottleIOpsDevice)
33 33
 		flDeviceWriteIOps   = NewThrottledeviceOpt(ValidateThrottleIOpsDevice)
34
-		flEnv               = opts.NewListOpts(opts.ValidateEnv)
35
-		flLabels            = opts.NewListOpts(opts.ValidateEnv)
34
+		flEnv               = opts.NewListOpts(ValidateEnv)
35
+		flLabels            = opts.NewListOpts(ValidateEnv)
36 36
 		flDevices           = opts.NewListOpts(ValidateDevice)
37 37
 
38 38
 		flUlimits = NewUlimitOpt(nil)
... ...
@@ -42,7 +42,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
42 42
 		flDNS               = opts.NewListOpts(opts.ValidateIPAddress)
43 43
 		flDNSSearch         = opts.NewListOpts(opts.ValidateDNSSearch)
44 44
 		flDNSOptions        = opts.NewListOpts(nil)
45
-		flExtraHosts        = opts.NewListOpts(opts.ValidateExtraHost)
45
+		flExtraHosts        = opts.NewListOpts(ValidateExtraHost)
46 46
 		flVolumesFrom       = opts.NewListOpts(nil)
47 47
 		flEnvFile           = opts.NewListOpts(nil)
48 48
 		flCapAdd            = opts.NewListOpts(nil)
... ...
@@ -130,7 +130,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
130 130
 
131 131
 	// Validate the input mac address
132 132
 	if *flMacAddress != "" {
133
-		if _, err := opts.ValidateMACAddress(*flMacAddress); err != nil {
133
+		if _, err := ValidateMACAddress(*flMacAddress); err != nil {
134 134
 			return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
135 135
 		}
136 136
 	}
... ...
@@ -413,7 +413,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
413 413
 func readKVStrings(files []string, override []string) ([]string, error) {
414 414
 	envVariables := []string{}
415 415
 	for _, ef := range files {
416
-		parsedVars, err := opts.ParseEnvFile(ef)
416
+		parsedVars, err := ParseEnvFile(ef)
417 417
 		if err != nil {
418 418
 			return nil, err
419 419
 		}