@@ -11,111 +11,114 @@ module UriUtils
1111(#{ DOCKER_TAG_REGEX . source } @#{ DOCKER_DIGEST_REGEX . source } ) | #{ DOCKER_DIGEST_REGEX . source } )\\ Z" , Regexp ::EXTENDED )
1212
1313 class InvalidDockerURI < StandardError ; end
14+ class << self
15+ def is_uri? ( candidate )
16+ !!( candidate . is_a? ( String ) && /\A #{ URI ::DEFAULT_PARSER . make_regexp } \Z / =~ candidate && URI ( candidate ) )
17+ rescue StandardError
18+ false
19+ end
1420
15- def self . is_uri? ( candidate )
16- !!( candidate . is_a? ( String ) && /\A #{ URI ::DEFAULT_PARSER . make_regexp } \Z / =~ candidate && URI ( candidate ) )
17- rescue StandardError
18- false
19- end
21+ def is_buildpack_uri? ( candidate )
22+ return false unless candidate . is_a? ( String )
23+ return true if is_uri? ( candidate )
2024
21- def self . is_buildpack_uri? ( candidate )
22- return false unless candidate . is_a? ( String )
23- return true if is_uri? ( candidate )
25+ !!( SSH_REGEX . match ( candidate ) || GIT_REGEX . match ( candidate ) )
26+ end
2427
25- !!( SSH_REGEX . match ( candidate ) || GIT_REGEX . match ( candidate ) )
26- end
28+ def is_cnb_buildpack_uri? ( candidate )
29+ return false unless candidate . is_a? ( String )
30+ return is_uri? ( candidate ) if candidate . start_with? ( %r{\A http(s)?://}x )
31+ return !!parse_docker_uri ( candidate . split ( '://' ) . last ) if candidate . start_with? ( 'docker://' )
2732
28- def self . is_cnb_buildpack_uri? ( candidate )
29- return false unless candidate . is_a? ( String )
30- return is_uri? ( candidate ) if candidate . start_with? ( %r{ \A http(s)?://}x )
31- return !! parse_docker_uri ( candidate . split ( '://' ) . last ) if candidate . start_with? ( 'docker://' )
33+ false
34+ rescue StandardError
35+ false
36+ end
3237
33- false
34- rescue StandardError
35- false
36- end
38+ def is_uri_path? ( candidate )
39+ !!( candidate . is_a? ( String ) && candidate =~ %r{^(?:/|/([^\s /]\S *)?)$} )
40+ end
3741
38- def self . is_uri_path? ( candidate )
39- !!( candidate . is_a? ( String ) && candidate =~ %r{^(?:/|/([^\s /]\S *)?)$} )
40- end
42+ # This escapes only the values in queries.
43+ def uri_escape ( uri )
44+ parts = uri . split ( '?' , 2 )
45+ return uri if parts . size == 1
46+
47+ query = parts [ 1 ] . split ( '&' ) . map do |subquery |
48+ subparts = subquery . split ( '=' , 2 )
49+ if subparts . size == 1
50+ CGI . escape ( subparts [ 0 ] )
51+ else
52+ [ subparts [ 0 ] , CGI . escape ( subparts [ 1 ] ) ] . join ( '=' )
53+ end
54+ end . join ( '&' )
55+ [ parts [ 0 ] . tr ( ' ' , '+' ) , query ] . join ( '?' )
56+ end
4157
42- # This escapes only the values in queries.
43- def self . uri_escape ( uri )
44- parts = uri . split ( '?' , 2 )
45- return uri if parts . size == 1
46-
47- query = parts [ 1 ] . split ( '&' ) . map do |subquery |
48- subparts = subquery . split ( '=' , 2 )
49- if subparts . size == 1
50- CGI . escape ( subparts [ 0 ] )
51- else
52- [ subparts [ 0 ] , CGI . escape ( subparts [ 1 ] ) ] . join ( '=' )
53- end
54- end . join ( '&' )
55- [ parts [ 0 ] . tr ( ' ' , '+' ) , query ] . join ( '?' )
56- end
58+ def parse_docker_uri ( docker_uri )
59+ name_parts = docker_uri . split ( '/' , 2 )
5760
58- def self . parse_docker_uri ( docker_uri )
59- name_parts = docker_uri . split ( '/' , 2 )
61+ host = name_parts [ 0 ]
62+ path = name_parts [ 1 ]
6063
61- host = name_parts [ 0 ]
62- path = name_parts [ 1 ]
64+ if missing_registry ( name_parts )
65+ host = ''
66+ path = docker_uri
67+ end
6368
64- if missing_registry ( name_parts )
65- host = ''
66- path = docker_uri
67- end
69+ 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 )
6871
69- 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 )
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
7174
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
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
7482
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
83+ [ host , path , tag_digest ]
8184 end
8285
83- [ host , path , tag_digest ]
84- end
86+ private
8587
86- private_class_method def self . official_docker_registry ( host )
87- host == DOCKER_INDEX_SERVER
88- end
88+ def official_docker_registry ( host )
89+ host == DOCKER_INDEX_SERVER
90+ end
8991
90- private_class_method def self . missing_registry ( name_parts )
91- host = name_parts [ 0 ]
92- name_parts . length == 1 ||
93- ( host . exclude? ( '.' ) && host . exclude? ( ':' ) && host != 'localhost' )
94- end
92+ def missing_registry ( name_parts )
93+ host = name_parts [ 0 ]
94+ name_parts . length == 1 ||
95+ ( host . exclude? ( '.' ) && host . exclude? ( ':' ) && host != 'localhost' )
96+ end
9597
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 )
98+ def parse_docker_tag_digest_from_path ( path )
99+ # Split path into base path and digest if digest is present (after '@')
100+ base_path , digest = path . split ( '@' , 2 )
99101
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
102+ if digest
103+ # If digest is present and base_path contains a tag (':'), split it
104+ if base_path . include? ( ':' )
105+ base_path , tag = base_path . split ( ':' , 2 )
106+ # Return path and combined tag@digest
107+ return [ base_path , "#{ tag } @#{ digest } " ]
108+ end
107109
108- # Return path and digest if no tag present
109- return [ base_path , digest ]
110- end
110+ # Return path and digest if no tag present
111+ return [ base_path , digest ]
112+ end
111113
112- # No digest present, check for tag
113- base_path , tag = base_path . split ( ':' , 2 )
114+ # No digest present, check for tag
115+ base_path , tag = base_path . split ( ':' , 2 )
114116
115- # If tag is present but looks like a path segment (contains '/'), treat as no tag
116- return [ base_path , 'latest' ] if tag &.include? ( '/' )
117+ # If tag is present but looks like a path segment (contains '/'), treat as no tag
118+ return [ base_path , 'latest' ] if tag &.include? ( '/' )
117119
118- # Return path and tag (or nil if no tag)
119- [ base_path , tag ]
120+ # Return path and tag (or nil if no tag)
121+ [ base_path , tag ]
122+ end
120123 end
121124end
0 commit comments