diff --git a/registry/registry.go b/registry/registry.go index d503a63d62..a122918977 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -23,7 +23,7 @@ var ( ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") ErrDoesNotExist = errors.New("Image does not exist") errLoginRequired = errors.New("Authentication is required.") - validNamespace = regexp.MustCompile(`^([a-z0-9_]{4,30})$`) + validNamespaceChars = regexp.MustCompile(`^([a-z0-9-_]*)$`) validRepo = regexp.MustCompile(`^([a-z0-9-_.]+)$`) ) @@ -178,8 +178,17 @@ func validateRepositoryName(repositoryName string) error { namespace = nameParts[0] name = nameParts[1] } - if !validNamespace.MatchString(namespace) { - return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", namespace) + if !validNamespaceChars.MatchString(namespace) { + return fmt.Errorf("Invalid namespace name (%s). Only [a-z0-9-_] are allowed.", namespace) + } + if len(namespace) < 4 || len(namespace) > 30 { + return fmt.Errorf("Invalid namespace name (%s). Cannot be fewer than 4 or more than 30 characters.", namespace) + } + if strings.HasPrefix(namespace, "-") || strings.HasSuffix(namespace, "-") { + return fmt.Errorf("Invalid namespace name (%s). Cannot begin or end with a hyphen.", namespace) + } + if strings.Contains(namespace, "--") { + return fmt.Errorf("Invalid namespace name (%s). Cannot contain consecutive hyphens.", namespace) } if !validRepo.MatchString(name) { return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name) diff --git a/registry/registry_test.go b/registry/registry_test.go index 52b8b32c53..c1bb97d657 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -233,24 +233,53 @@ func TestSearchRepositories(t *testing.T) { } func TestValidRepositoryName(t *testing.T) { - if err := validateRepositoryName("docker/docker"); err != nil { - t.Fatal(err) + validRepositoryNames := []string{ + // Sanity check. + "docker/docker", + + // Allow 64-character non-hexadecimal names (hexadecimal names are forbidden). + "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", + + // Allow embedded hyphens. + "docker-rules/docker", + + // Allow underscores everywhere (as opposed to hyphens). + "____/____", } - // Support 64-byte non-hexadecimal names (hexadecimal names are forbidden) - if err := validateRepositoryName("thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev"); err != nil { - t.Fatal(err) + for _, repositoryName := range validRepositoryNames { + if err := validateRepositoryName(repositoryName); err != nil { + t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err) + } } - if err := validateRepositoryName("docker/Docker"); err == nil { - t.Log("Repository name should be invalid") - t.Fail() + + invalidRepositoryNames := []string{ + // Disallow capital letters. + "docker/Docker", + + // Only allow one slash. + "docker///docker", + + // Disallow 64-character hexadecimal. + "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", + + // Disallow leading and trailing hyphens in namespace. + "-docker/docker", + "docker-/docker", + "-docker-/docker", + + // Disallow consecutive hyphens. + "dock--er/docker", + + // Namespace too short. + "doc/docker", + + // No repository. + "docker/", } - if err := validateRepositoryName("docker///docker"); err == nil { - t.Log("Repository name should be invalid") - t.Fail() - } - if err := validateRepositoryName("1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a"); err == nil { - t.Log("Repository name should be invalid, 64-byte hexadecimal names forbidden") - t.Fail() + for _, repositoryName := range invalidRepositoryNames { + if err := validateRepositoryName(repositoryName); err == nil { + t.Errorf("Repository name should be invalid: %v", repositoryName) + } } }