@@ -4,6 +4,11 @@ module UriUtils
44 SSH_REGEX = %r{ \A (?:ssh://)? git@ .+? : .+? \. git \z }x
55 GIT_REGEX = %r{ \A git:// .+? : .+? \. git \z }x
66 DOCKER_INDEX_SERVER = 'docker.io' . freeze
7+ DOCKER_PATH_REGEX = %r{\A [a-z0-9_\- \. \/ ]{2,255}\Z }
8+ DOCKER_TAG_REGEX = /[a-zA-Z0-9_\- \. ]{1,128}/
9+ DOCKER_DIGEST_REGEX = /sha256:[a-z0-9]{64}/
10+ DOCKER_TAG_DIGEST_REGEX = Regexp . new ( "\\ A(#{ DOCKER_TAG_REGEX . source } |
11+ (#{ DOCKER_TAG_REGEX . source } @#{ DOCKER_DIGEST_REGEX . source } ) | #{ DOCKER_DIGEST_REGEX . source } )\\ Z" , Regexp ::EXTENDED )
712
813 class InvalidDockerURI < StandardError ; end
914
@@ -62,13 +67,20 @@ def self.parse_docker_uri(docker_uri)
6267 end
6368
6469 path = 'library/' + path if ( official_docker_registry ( name_parts [ 0 ] ) || missing_registry ( name_parts ) ) && path . exclude? ( '/' )
70+ path , tag_digest = parse_docker_tag_digest_from_path ( path )
6571
66- path , tag = parse_docker_repository_tag ( path )
72+ raise InvalidDockerURI . new "Invalid image name [#{ path } ]" unless DOCKER_PATH_REGEX =~ path
73+ raise InvalidDockerURI . new "Invalid image tag [#{ tag_digest } ]" if tag_digest && DOCKER_TAG_DIGEST_REGEX !~ tag_digest
6774
68- raise InvalidDockerURI . new "Invalid image name [#{ path } ]" unless %r{\A [a-z0-9_\- \. \/ ]{2,255}\Z } =~ path
69- raise InvalidDockerURI . new "Invalid image tag [#{ tag } ]" if tag && !( /\A (([a-zA-Z0-9_\- \. ]{1,128})|(([a-zA-Z0-9_\- \. ]{0,128})(@sha256:[a-z0-9]{64})))\Z / =~ tag )
75+ # if only sha256 presented, we add hash value as fragment to the uri,
76+ # since the ruby uri parser confuses because of second ':' in uri's path part.
77+ if tag_digest && tag_digest . start_with? ( 'sha256:' )
78+ _ , hash_value = tag_digest . split ( ':' )
79+ path += '@sha256'
80+ tag_digest = hash_value
81+ end
7082
71- [ host , path , tag ]
83+ [ host , path , tag_digest ]
7284 end
7385
7486 private_class_method def self . official_docker_registry ( host )
@@ -81,11 +93,29 @@ def self.parse_docker_uri(docker_uri)
8193 ( host . exclude? ( '.' ) && host . exclude? ( ':' ) && host != 'localhost' )
8294 end
8395
84- private_class_method def self . parse_docker_repository_tag ( path )
85- path , tag = path . split ( /(?=@)|:/ , 2 )
96+ private_class_method def self . parse_docker_tag_digest_from_path ( path )
97+ # Split path into base path and digest if digest is present (after '@')
98+ base_path , digest = path . split ( '@' , 2 )
99+
100+ if digest
101+ # If digest is present and base_path contains a tag (':'), split it
102+ if base_path . include? ( ':' )
103+ base_path , tag = base_path . split ( ':' , 2 )
104+ # Return path and combined tag@digest
105+ return [ base_path , "#{ tag } @#{ digest } " ]
106+ end
107+
108+ # Return path and digest if no tag present
109+ return [ base_path , digest ]
110+ end
111+
112+ # No digest present, check for tag
113+ base_path , tag = base_path . split ( ':' , 2 )
86114
87- return [ path , tag ] unless tag && tag . include? ( '/' )
115+ # If tag is present but looks like a path segment (contains '/'), treat as no tag
116+ return [ base_path , 'latest' ] if tag &.include? ( '/' )
88117
89- [ path , 'latest' ]
118+ # Return path and tag (or nil if no tag)
119+ [ base_path , tag ]
90120 end
91121end
0 commit comments