Repository: docker-java/docker-java
Pull request #298 (2015-08-18 20:22:31)

Fix issue #290


Patch:
diff --git a/src/main/java/com/github/dockerjava/core/NameParser.java b/src/main/java/com/github/dockerjava/core/NameParser.java
index 2bd99bf3..49a550b1 100644
--- a/src/main/java/com/github/dockerjava/core/NameParser.java
+++ b/src/main/java/com/github/dockerjava/core/NameParser.java
@@ -5,15 +5,24 @@ package com.github.dockerjava.core;
 
 import java.util.regex.Pattern;
 
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
 import com.github.dockerjava.api.model.AuthConfig;
 
 public class NameParser {
 
-    private static final Pattern VALID_HEX_PATTERN = Pattern.compile("^([a-f0-9]{64})$");
+    private static final int RepositoryNameTotalLengthMax = 255;
+
+    private static final Pattern RepositoryNameComponentRegexp = Pattern.compile("[a-z0-9]+(?:[._-][a-z0-9]+)*");
 
-    private static final Pattern VALID_NAMESPACE_PATTERN = Pattern.compile("^([a-z0-9_]{4,30})$");
+    private static final Pattern RepositoryNameComponentAnchoredRegexp = Pattern.compile("^"
+            + RepositoryNameComponentRegexp.pattern() + "$");
 
-    private static final Pattern VALID_REPO_PATTERN = Pattern.compile("^([a-z0-9-_.]+)$");
+    // private static final Pattern RepositoryNameRegexp = Pattern.compile("(?:" +
+    // RepositoryNameComponentRegexp.pattern()
+    // + "/)*" + RepositoryNameComponentRegexp.pattern());
 
     public static ReposTag parseRepositoryTag(String name) {
         int n = name.lastIndexOf(':');
@@ -36,30 +45,43 @@ public class NameParser {
             this.repos = repos;
             this.tag = tag;
         }
-    }
 
-    public static void validateRepositoryName(String repositoryName) {
-        String name;
-        String namespace;
-        String[] nameParts = repositoryName.split("/", 2);
-        if (nameParts.length < 2) {
-            namespace = "library";
-            name = nameParts[0];
-            if (VALID_HEX_PATTERN.matcher(name).matches()) {
-                throw new InvalidRepositoryNameException(String.format(
-                        "Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name));
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof ReposTag) {
+                ReposTag other = (ReposTag) obj;
+                return new EqualsBuilder().append(repos, other.repos).append(tag, other.tag).isEquals();
+            } else {
+                return false;
             }
-        } else {
-            namespace = nameParts[0];
-            name = nameParts[1];
         }
-        if (!VALID_NAMESPACE_PATTERN.matcher(namespace).matches()) {
-            throw new InvalidRepositoryNameException(String.format(
-                    "Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", namespace));
+
+        @Override
+        public String toString() {
+            return ToStringBuilder.reflectionToString(this, ToStringStyle.SIMPLE_STYLE);
+        }
+    }
+
+    /*
+     * see https://github.com/docker/distribution/blob/master/registry/api/v2/names.go
+     */
+    public static void validateRepoName(String name) {
+        if (name.isEmpty()) {
+            throw new InvalidRepositoryNameException(String.format("Invalid empty repository name \"%s\"", name));
         }
-        if (!VALID_REPO_PATTERN.matcher(name).matches()) {
-            throw new InvalidRepositoryNameException(String.format(
-                    "Invalid repository name (%s), only [a-z0-9-_.] are allowed", name));
+
+        if (name.length() > RepositoryNameTotalLengthMax) {
+            throw new InvalidRepositoryNameException(String.format("Repository name \"%s\" is longer than "
+                    + RepositoryNameTotalLengthMax, name));
+        }
+
+        String[] components = name.split("/");
+
+        for (String component : components) {
+            if (!RepositoryNameComponentAnchoredRegexp.matcher(component).matches()) {
+                throw new InvalidRepositoryNameException(String.format(
+                        "Repository name \"%s\" is invalid. Component: %s", name, component));
+            }
         }
     }
 
@@ -82,7 +104,7 @@ public class NameParser {
                     reposName));
         }
 
-        validateRepositoryName(reposName);
+        validateRepoName(reposName);
         return new HostnameReposName(hostname, reposName);
     }
 
@@ -96,5 +118,21 @@ public class NameParser {
             this.reposName = reposName;
         }
 
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof HostnameReposName) {
+                HostnameReposName other = (HostnameReposName) obj;
+                return new EqualsBuilder().append(hostname, other.hostname).append(reposName, other.reposName)
+                        .isEquals();
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return ToStringBuilder.reflectionToString(this, ToStringStyle.SIMPLE_STYLE);
+        }
+
     }
 }


Test patch:
diff --git a/src/test/java/com/github/dockerjava/core/NameParserTest.java b/src/test/java/com/github/dockerjava/core/NameParserTest.java
new file mode 100644
index 00000000..6b694753
--- /dev/null
+++ b/src/test/java/com/github/dockerjava/core/NameParserTest.java
@@ -0,0 +1,126 @@
+/*
+ * Created on 17.08.2015
+ */
+package com.github.dockerjava.core;
+
+import org.apache.commons.lang.StringUtils;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+import com.github.dockerjava.api.model.AuthConfig;
+import com.github.dockerjava.core.NameParser.HostnameReposName;
+import com.github.dockerjava.core.NameParser.ReposTag;
+
+/**
+ *
+ *
+ * @author marcus
+ *
+ */
+public class NameParserTest {
+
+    @Test
+    public void testValidateRepoName() throws Exception {
+        NameParser.validateRepoName("repository");
+        NameParser.validateRepoName("namespace/repository");
+        NameParser.validateRepoName("namespace-with-dashes/repository");
+        NameParser.validateRepoName("namespace/repository-with-dashes");
+        NameParser.validateRepoName("namespace.with.dots/repository");
+        NameParser.validateRepoName("namespace/repository.with.dots");
+        NameParser.validateRepoName("namespace_with_underscores/repository");
+        NameParser.validateRepoName("namespace/repository_with_underscores");
+    }
+
+    @Test(expectedExceptions = InvalidRepositoryNameException.class)
+    public void testValidateRepoNameEmpty() throws Exception {
+        NameParser.validateRepoName("");
+    }
+
+    @Test(expectedExceptions = InvalidRepositoryNameException.class)
+    public void testValidateRepoNameExceedsMaxLength() throws Exception {
+        NameParser.validateRepoName(StringUtils.repeat("repository", 255));
+    }
+
+    @Test(expectedExceptions = InvalidRepositoryNameException.class)
+    public void testValidateRepoNameEndWithDash() throws Exception {
+        NameParser.validateRepoName("repository-");
+    }
+
+    @Test(expectedExceptions = InvalidRepositoryNameException.class)
+    public void testValidateRepoNameStartWithDash() throws Exception {
+        NameParser.validateRepoName("-repository");
+    }
+
+    @Test(expectedExceptions = InvalidRepositoryNameException.class)
+    public void testValidateRepoNameEndWithDot() throws Exception {
+        NameParser.validateRepoName("repository.");
+    }
+
+    @Test(expectedExceptions = InvalidRepositoryNameException.class)
+    public void testValidateRepoNameStartWithDot() throws Exception {
+        NameParser.validateRepoName(".repository");
+    }
+
+    @Test(expectedExceptions = InvalidRepositoryNameException.class)
+    public void testValidateRepoNameEndWithUnderscore() throws Exception {
+        NameParser.validateRepoName("repository_");
+    }
+
+    @Test(expectedExceptions = InvalidRepositoryNameException.class)
+    public void testValidateRepoNameStartWithUnderscore() throws Exception {
+        NameParser.validateRepoName("_repository");
+    }
+
+    @Test(expectedExceptions = InvalidRepositoryNameException.class)
+    public void testValidateRepoNameWithColon() throws Exception {
+        NameParser.validateRepoName("repository:with:colon");
+    }
+
+    @Test
+    public void testResolveSimpleRepositoryName() throws Exception {
+        HostnameReposName resolved = NameParser.resolveRepositoryName("repository");
+        assertEquals(resolved, new HostnameReposName(AuthConfig.DEFAULT_SERVER_ADDRESS, "repository"));
+    }
+
+    @Test
+    public void testResolveRepositoryNameWithNamespace() throws Exception {
+        HostnameReposName resolved = NameParser.resolveRepositoryName("namespace/repository");
+        assertEquals(resolved, new HostnameReposName(AuthConfig.DEFAULT_SERVER_ADDRESS, "namespace/repository"));
+    }
+
+    @Test
+    public void testResolveRepositoryNameWithNamespaceAndHostname() throws Exception {
+        HostnameReposName resolved = NameParser.resolveRepositoryName("localhost:5000/namespace/repository");
+        assertEquals(resolved, new HostnameReposName("localhost:5000", "namespace/repository"));
+    }
+
+    @Test(expectedExceptions = InvalidRepositoryNameException.class)
+    public void testResolveRepositoryNameWithIndex() throws Exception {
+        NameParser.resolveRepositoryName("index.docker.io/repository");
+    }
+
+    @Test
+    public void testResolveReposTagWithoutTagSimple() throws Exception {
+        ReposTag resolved = NameParser.parseRepositoryTag("repository");
+        assertEquals(resolved, new ReposTag("repository", ""));
+
+        resolved = NameParser.parseRepositoryTag("namespace/repository");
+        assertEquals(resolved, new ReposTag("namespace/repository", ""));
+
+        resolved = NameParser.parseRepositoryTag("localhost:5000/namespace/repository");
+        assertEquals(resolved, new ReposTag("localhost:5000/namespace/repository", ""));
+    }
+
+    @Test
+    public void testResolveReposTagWithTag() throws Exception {
+        ReposTag resolved = NameParser.parseRepositoryTag("repository:tag");
+        assertEquals(resolved, new ReposTag("repository", "tag"));
+
+        resolved = NameParser.parseRepositoryTag("namespace/repository:tag");
+        assertEquals(resolved, new ReposTag("namespace/repository", "tag"));
+
+        resolved = NameParser.parseRepositoryTag("localhost:5000/namespace/repository:tag");
+        assertEquals(resolved, new ReposTag("localhost:5000/namespace/repository", "tag"));
+    }
+}