2424import org .openqa .selenium .docker .DockerException ;
2525import org .openqa .selenium .internal .Require ;
2626
27- import java .util .Map ;
2827import java .util .Objects ;
29- import java .util .function .Function ;
30- import java .util .regex .Matcher ;
31- import java .util .regex .Pattern ;
3228
3329@ Beta
3430public class Reference {
35-
3631 private static final String DEFAULT_DOMAIN = "docker.io" ;
32+ private static final String LEGACY_DEFAULT_DOMAIN = "index.docker.io" ;
3733 private static final String DEFAULT_REPO = "library" ;
3834 private static final String DEFAULT_TAG = "latest" ;
3935
40- // Capturing groups used in patterns below
41- private static final String DOMAIN = "([\\ w\\ d-_.]+?(:(\\ d+))?)" ;
42- private static final String REPO = "([\\ w\\ d-_.]+?)" ;
43- private static final String NAME = "([\\ w\\ d-_.]+?)" ;
44- private static final String TAG = "([\\ w\\ d-_.]+?)" ;
45- private static final String DIGEST = "(sha256:[A-Fa-f0-9]{64})" ;
46-
47- // name -> {domain: "docker.io", repository: "repository", name: name, tag: "latest", digest: null}
48- // name:tag -> {domain: "docker.io", repository: "repository", name: name, tag: tag, digest: null}
49- // name@digest -> {domain: "docker.io", repository: "repository", name: name, tag: null, digest: digest}
50- // repository/name -> {domain: "docker.io", repository: repository, name: name, tag: "latest", digest: null}
51- // repository/name:tag -> {domain: "docker.io", repository: repository, name: name, tag: tag, digest: null}
52- // repository/name@digest -> {domain: "docker.io", repository: repository, name: name, tag: null, digest: digest}
53- // domain/repository/name:tag -> {domain: "domain", repository: repository, name: name, tag: tag, digest: null}
54- // domain:port/repository/name@digest -> {domain: "domain:port", repository: repository, name: name, tag: tag, digest: null}
55- private static final Map <Pattern , Function <Matcher , Reference >> PATTERNS = ImmutableMap .<Pattern , Function <Matcher , Reference >>builder ()
56- .put (Pattern .compile (TAG ), m -> new Reference (DEFAULT_DOMAIN , DEFAULT_REPO , m .group (1 ), DEFAULT_TAG , null ))
57- .put (Pattern .compile (String .format ("%s:%s" , NAME , TAG )), m -> new Reference (DEFAULT_DOMAIN , DEFAULT_REPO , m .group (1 ), m .group (2 ), null ))
58- .put (Pattern .compile (String .format ("%s/%s" , REPO , NAME )), m -> new Reference (DEFAULT_DOMAIN , m .group (1 ), m .group (2 ), DEFAULT_TAG , null ))
59- .put (Pattern .compile (String .format ("%s@%s" , NAME , DIGEST )), m -> new Reference (DEFAULT_DOMAIN , DEFAULT_REPO , m .group (1 ), null , m .group (2 )))
60- .put (Pattern .compile (String .format ("%s/%s:%s" , REPO , NAME , TAG )), m -> new Reference (DEFAULT_DOMAIN , m .group (1 ), m .group (2 ), m .group (3 ), null ))
61- .put (Pattern .compile (String .format ("%s/%s@%s" , REPO , NAME , DIGEST )), m -> new Reference (DEFAULT_DOMAIN , m .group (1 ), m .group (2 ), null , m .group (3 )))
62- .put (Pattern .compile (String .format ("%s/%s/%s" , DOMAIN , REPO , NAME )), m -> new Reference (m .group (1 ), m .group (4 ), m .group (5 ), DEFAULT_TAG , null ))
63- .put (Pattern .compile (String .format ("%s/%s/%s:%s" , DOMAIN , REPO , NAME , TAG )), m -> new Reference (m .group (1 ), m .group (4 ), m .group (5 ), m .group (6 ), null ))
64- .build ();
65-
6636 private final String domain ;
67- private final String repository ;
6837 private final String name ;
6938 private final String tag ;
7039 private final String digest ;
7140
7241 @ VisibleForTesting
73- Reference (String domain , String repository , String name , String tag , String digest ) {
42+ Reference (String domain , String name , String tag , String digest ) {
7443 this .domain = Require .nonNull ("Domain" , domain );
75- this .repository = Require .nonNull ("Repository" , repository );
7644 this .name = Require .nonNull ("Name" , name );
7745 this .tag = tag ;
7846 this .digest = digest ;
7947 }
8048
81- public String getDomain () {
82- return domain ;
49+ // Logic taken from https://github.com/distribution/distribution/blob/main/reference/normalize.go
50+ public static Reference parse (String input ) {
51+ Require .nonNull ("Reference to parse" , input );
52+
53+ ImmutableMap <String , String > splitDockerDomain = splitDockerDomain (input );
54+ String domain = splitDockerDomain .get ("domain" );
55+ String remainder = splitDockerDomain .get ("remainder" );
56+
57+ String name ;
58+ String digest = null ;
59+ String tag = DEFAULT_TAG ;
60+
61+ int digestSep = remainder .indexOf ("@" );
62+ int tagSep = remainder .indexOf (":" );
63+ if (digestSep > -1 && tagSep > -1 ) {
64+ digest = remainder .substring (digestSep + 1 );
65+ name = remainder .substring (0 , digestSep );
66+ tag = null ;
67+ } else if (tagSep > -1 ) {
68+ tag = remainder .substring (tagSep + 1 );
69+ name = remainder .substring (0 , tagSep );
70+ } else {
71+ name = remainder ;
72+ }
73+
74+ if (!name .toLowerCase ().equals (name )) {
75+ throw new DockerException (String .format (
76+ "Invalid reference format: repository name (%s) must be lowercase" , name ));
77+ }
78+
79+ return new Reference (domain , name , tag , digest );
8380 }
8481
85- public String getRepository () {
86- return repository ;
82+ private static ImmutableMap <String , String > splitDockerDomain (String name ) {
83+ String domain ;
84+ String remainder ;
85+ int domSep = name .indexOf ("/" );
86+ String possibleDomain = domSep == -1 ? "" : name .substring (0 , domSep );
87+ if (domSep == -1 || (!possibleDomain .contains ("." ) && !possibleDomain .contains (":" )
88+ && !"localhost" .equalsIgnoreCase (possibleDomain )
89+ && possibleDomain .toLowerCase ().equals (possibleDomain ))) {
90+ remainder = name ;
91+ domain = DEFAULT_DOMAIN ;
92+ } else {
93+ domain = possibleDomain ;
94+ remainder = name .substring (domSep + 1 );
95+ }
96+ if (LEGACY_DEFAULT_DOMAIN .equals (domain )) {
97+ domain = DEFAULT_DOMAIN ;
98+ }
99+ if (DEFAULT_DOMAIN .equals (domain ) && !remainder .contains ("/" )) {
100+ remainder = String .format ("%s/%s" , DEFAULT_REPO , remainder );
101+ }
102+ return ImmutableMap .of ("domain" , domain , "remainder" , remainder );
103+ }
104+
105+ public String getDomain () {
106+ return domain ;
87107 }
88108
89109 public String getName () {
@@ -105,12 +125,12 @@ public String getFamiliarName() {
105125 familiar .append (domain ).append ("/" );
106126 }
107127
108- if (!DEFAULT_REPO .equals (repository )) {
109- familiar .append (repository ).append ("/" );
128+ if (name .contains (DEFAULT_REPO ) && DEFAULT_DOMAIN .equals (domain )) {
129+ familiar .append (name .replace (DEFAULT_REPO + "/" , "" ));
130+ } else {
131+ familiar .append (name );
110132 }
111133
112- familiar .append (name );
113-
114134 if (digest != null ) {
115135 familiar .append ("@" ).append (digest );
116136 } else if (tag != null ) {
@@ -122,24 +142,10 @@ public String getFamiliarName() {
122142 return familiar .toString ();
123143 }
124144
125- public static Reference parse (String input ) {
126- Require .nonNull ("Reference to parse" , input );
127-
128- for (Map .Entry <Pattern , Function <Matcher , Reference >> entry : PATTERNS .entrySet ()) {
129- Matcher matcher = entry .getKey ().matcher (input );
130- if (matcher .matches ()) {
131- return entry .getValue ().apply (matcher );
132- }
133- }
134-
135- throw new DockerException ("Unable to parse: " + input );
136- }
137-
138145 @ Override
139146 public String toString () {
140147 return "Reference{" +
141148 "domain='" + domain + '\'' +
142- ", repository='" + repository + '\'' +
143149 ", name='" + name + '\'' +
144150 ", tag='" + tag + '\'' +
145151 ", digest='" + digest + '\'' +
@@ -154,14 +160,13 @@ public boolean equals(Object o) {
154160
155161 Reference that = (Reference ) o ;
156162 return this .domain .equals (that .domain ) &&
157- this .repository .equals (that .repository ) &&
158163 this .name .equals (that .name ) &&
159164 Objects .equals (tag , that .tag ) &&
160165 Objects .equals (digest , that .digest );
161166 }
162167
163168 @ Override
164169 public int hashCode () {
165- return Objects .hash (domain , repository , name , tag , digest );
170+ return Objects .hash (domain , name , tag , digest );
166171 }
167172}
0 commit comments