Browse code

Allow uppercase characters in image reference hostname

This PR makes restores the pre-Docker 1.10 behavior of allowing
uppercase characters in registry hostnames.

Note that this only applies to hostnames, not remote image names.
Previous versions also prohibited uppercase letters after the hostname,
but Docker 1.10 extended this to the hostname itself.

- Vendor updated docker/distribution.

- Add a check to "normalize" that rejects remote names with uppercase
letters.

- Add test cases to TestTagValidPrefixedRepo and
TestTagInvalidUnprefixedRepo

Fixes: #20056

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>

Aaron Lehmann authored on 2016/02/09 04:07:57
Showing 6 changed files
... ...
@@ -48,7 +48,7 @@ clone git github.com/boltdb/bolt v1.1.0
48 48
 clone git github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7
49 49
 
50 50
 # get graph and distribution packages
51
-clone git github.com/docker/distribution ab9b433fcaf7c8319562a8b80f2720f5faca712f
51
+clone git github.com/docker/distribution 77534e734063a203981df7024fe8ca9228b86930
52 52
 clone git github.com/vbatts/tar-split v0.9.11
53 53
 
54 54
 # get desired notary commit, might also need to be updated in Dockerfile
... ...
@@ -31,7 +31,7 @@ func (s *DockerSuite) TestTagUnprefixedRepoByID(c *check.C) {
31 31
 
32 32
 // ensure we don't allow the use of invalid repository names; these tag operations should fail
33 33
 func (s *DockerSuite) TestTagInvalidUnprefixedRepo(c *check.C) {
34
-	invalidRepos := []string{"fo$z$", "Foo@3cc", "Foo$3", "Foo*3", "Fo^3", "Foo!3", "F)xcz(", "fo%asd"}
34
+	invalidRepos := []string{"fo$z$", "Foo@3cc", "Foo$3", "Foo*3", "Fo^3", "Foo!3", "F)xcz(", "fo%asd", "FOO/bar"}
35 35
 
36 36
 	for _, repo := range invalidRepos {
37 37
 		out, _, err := dockerCmdWithError("tag", "busybox", repo)
... ...
@@ -61,7 +61,7 @@ func (s *DockerSuite) TestTagValidPrefixedRepo(c *check.C) {
61 61
 		}
62 62
 	}
63 63
 
64
-	validRepos := []string{"fooo/bar", "fooaa/test", "foooo:t"}
64
+	validRepos := []string{"fooo/bar", "fooaa/test", "foooo:t", "HOSTNAME.DOMAIN.COM:443/foo/bar"}
65 65
 
66 66
 	for _, repo := range validRepos {
67 67
 		_, _, err := dockerCmdWithError("tag", "busybox:latest", repo)
... ...
@@ -1,6 +1,7 @@
1 1
 package reference
2 2
 
3 3
 import (
4
+	"errors"
4 5
 	"fmt"
5 6
 	"strings"
6 7
 
... ...
@@ -72,7 +73,10 @@ func ParseNamed(s string) (Named, error) {
72 72
 // WithName returns a named object representing the given string. If the input
73 73
 // is invalid ErrReferenceInvalidFormat will be returned.
74 74
 func WithName(name string) (Named, error) {
75
-	name = normalize(name)
75
+	name, err := normalize(name)
76
+	if err != nil {
77
+		return nil, err
78
+	}
76 79
 	if err := validateName(name); err != nil {
77 80
 		return nil, err
78 81
 	}
... ...
@@ -172,15 +176,18 @@ func splitHostname(name string) (hostname, remoteName string) {
172 172
 
173 173
 // normalize returns a repository name in its normalized form, meaning it
174 174
 // will not contain default hostname nor library/ prefix for official images.
175
-func normalize(name string) string {
175
+func normalize(name string) (string, error) {
176 176
 	host, remoteName := splitHostname(name)
177
+	if strings.ToLower(remoteName) != remoteName {
178
+		return "", errors.New("invalid reference format: repository name must be lowercase")
179
+	}
177 180
 	if host == DefaultHostname {
178 181
 		if strings.HasPrefix(remoteName, DefaultRepoPrefix) {
179
-			return strings.TrimPrefix(remoteName, DefaultRepoPrefix)
182
+			return strings.TrimPrefix(remoteName, DefaultRepoPrefix), nil
180 183
 		}
181
-		return remoteName
184
+		return remoteName, nil
182 185
 	}
183
-	return name
186
+	return name, nil
184 187
 }
185 188
 
186 189
 func validateName(name string) error {
... ...
@@ -6,7 +6,7 @@
6 6
 // 	reference                       := repository [ ":" tag ] [ "@" digest ]
7 7
 //	name                            := [hostname '/'] component ['/' component]*
8 8
 //	hostname                        := hostcomponent ['.' hostcomponent]* [':' port-number]
9
-//	hostcomponent                   := /([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])/
9
+//	hostcomponent                   := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
10 10
 //	port-number                     := /[0-9]+/
11 11
 //	component                       := alpha-numeric [separator alpha-numeric]*
12 12
 // 	alpha-numeric                   := /[a-z0-9]+/
... ...
@@ -22,7 +22,7 @@ var (
22 22
 	// hostnameComponentRegexp restricts the registry hostname component of a
23 23
 	// repository name to start with a component as defined by hostnameRegexp
24 24
 	// and followed by an optional port.
25
-	hostnameComponentRegexp = match(`(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])`)
25
+	hostnameComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
26 26
 
27 27
 	// hostnameRegexp defines the structure of potential hostname components
28 28
 	// that may be part of image names. This is purposely a subset of what is
... ...
@@ -36,8 +36,21 @@ func checkHTTPRedirect(req *http.Request, via []*http.Request) error {
36 36
 
37 37
 	if len(via) > 0 {
38 38
 		for headerName, headerVals := range via[0].Header {
39
-			if headerName == "Accept" || headerName == "Range" {
40
-				for _, val := range headerVals {
39
+			if headerName != "Accept" && headerName != "Range" {
40
+				continue
41
+			}
42
+			for _, val := range headerVals {
43
+				// Don't add to redirected request if redirected
44
+				// request already has a header with the same
45
+				// name and value.
46
+				hasValue := false
47
+				for _, existingVal := range req.Header[headerName] {
48
+					if existingVal == val {
49
+						hasValue = true
50
+						break
51
+					}
52
+				}
53
+				if !hasValue {
41 54
 					req.Header.Add(headerName, val)
42 55
 				}
43 56
 			}