addressable 2.2.3 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of addressable might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +235 -0
- data/Gemfile +32 -0
- data/LICENSE.txt +202 -0
- data/README.md +121 -0
- data/Rakefile +9 -17
- data/data/unicode.data +0 -0
- data/lib/addressable.rb +4 -0
- data/lib/addressable/idna.rb +26 -4870
- data/lib/addressable/idna/native.rb +61 -0
- data/lib/addressable/idna/pure.rb +676 -0
- data/lib/addressable/template.rb +581 -585
- data/lib/addressable/uri.rb +870 -609
- data/lib/addressable/version.rb +16 -20
- data/spec/addressable/idna_spec.rb +170 -64
- data/spec/addressable/net_http_compat_spec.rb +30 -0
- data/spec/addressable/rack_mount_compat_spec.rb +106 -0
- data/spec/addressable/security_spec.rb +59 -0
- data/spec/addressable/template_spec.rb +1346 -2047
- data/spec/addressable/uri_spec.rb +3472 -1198
- data/spec/spec_helper.rb +24 -0
- data/tasks/clobber.rake +2 -0
- data/tasks/gem.rake +28 -19
- data/tasks/git.rake +9 -2
- data/tasks/metrics.rake +2 -0
- data/tasks/rspec.rake +23 -0
- data/tasks/yard.rake +7 -4
- metadata +78 -117
- data/CHANGELOG +0 -101
- data/LICENSE +0 -20
- data/README +0 -60
- data/tasks/rdoc.rake +0 -26
- data/tasks/rubyforge.rake +0 -89
- data/tasks/spec.rake +0 -47
- data/website/index.html +0 -110
data/lib/addressable/uri.rb
CHANGED
@@ -1,30 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# encoding:utf-8
|
2
4
|
#--
|
3
|
-
#
|
5
|
+
# Copyright (C) Bob Aman
|
4
6
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
# the following conditions:
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
12
10
|
#
|
13
|
-
#
|
14
|
-
# included in all copies or substantial portions of the Software.
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
15
12
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
23
18
|
#++
|
24
19
|
|
20
|
+
|
25
21
|
require "addressable/version"
|
26
22
|
require "addressable/idna"
|
23
|
+
require "public_suffix"
|
27
24
|
|
25
|
+
##
|
26
|
+
# Addressable is a library for processing links and URIs.
|
28
27
|
module Addressable
|
29
28
|
##
|
30
29
|
# This is an implementation of a URI parser based on
|
@@ -48,12 +47,34 @@ module Addressable
|
|
48
47
|
UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
|
49
48
|
PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
|
50
49
|
SCHEME = ALPHA + DIGIT + "\\-\\+\\."
|
50
|
+
HOST = UNRESERVED + SUB_DELIMS + "\\[\\:\\]"
|
51
51
|
AUTHORITY = PCHAR
|
52
52
|
PATH = PCHAR + "\\/"
|
53
53
|
QUERY = PCHAR + "\\/\\?"
|
54
54
|
FRAGMENT = PCHAR + "\\/\\?"
|
55
55
|
end
|
56
56
|
|
57
|
+
SLASH = '/'
|
58
|
+
EMPTY_STR = ''
|
59
|
+
|
60
|
+
URIREGEX = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/
|
61
|
+
|
62
|
+
PORT_MAPPING = {
|
63
|
+
"http" => 80,
|
64
|
+
"https" => 443,
|
65
|
+
"ftp" => 21,
|
66
|
+
"tftp" => 69,
|
67
|
+
"sftp" => 22,
|
68
|
+
"ssh" => 22,
|
69
|
+
"svn+ssh" => 22,
|
70
|
+
"telnet" => 23,
|
71
|
+
"nntp" => 119,
|
72
|
+
"gopher" => 70,
|
73
|
+
"wais" => 210,
|
74
|
+
"ldap" => 389,
|
75
|
+
"prospero" => 1525
|
76
|
+
}
|
77
|
+
|
57
78
|
##
|
58
79
|
# Returns a URI object based on the parsed string.
|
59
80
|
#
|
@@ -67,7 +88,7 @@ module Addressable
|
|
67
88
|
# If we were given nil, return nil.
|
68
89
|
return nil unless uri
|
69
90
|
# If a URI object is passed, just return itself.
|
70
|
-
return uri if uri.kind_of?(self)
|
91
|
+
return uri.dup if uri.kind_of?(self)
|
71
92
|
|
72
93
|
# If a URI object of the Ruby standard library variety is passed,
|
73
94
|
# convert it to a string, then parse the string.
|
@@ -77,16 +98,15 @@ module Addressable
|
|
77
98
|
uri = uri.to_s
|
78
99
|
end
|
79
100
|
|
80
|
-
if !uri.respond_to?(:to_str)
|
81
|
-
raise TypeError, "Can't convert #{uri.class} into String."
|
82
|
-
end
|
83
101
|
# Otherwise, convert to a String
|
84
|
-
|
102
|
+
begin
|
103
|
+
uri = uri.to_str
|
104
|
+
rescue TypeError, NoMethodError
|
105
|
+
raise TypeError, "Can't convert #{uri.class} into String."
|
106
|
+
end if not uri.is_a? String
|
85
107
|
|
86
108
|
# This Regexp supplied as an example in RFC 3986, and it works great.
|
87
|
-
|
88
|
-
/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/
|
89
|
-
scan = uri.scan(uri_regex)
|
109
|
+
scan = uri.scan(URIREGEX)
|
90
110
|
fragments = scan[0]
|
91
111
|
scheme = fragments[1]
|
92
112
|
authority = fragments[3]
|
@@ -104,14 +124,18 @@ module Addressable
|
|
104
124
|
user = userinfo.strip[/^([^:]*):?/, 1]
|
105
125
|
password = userinfo.strip[/:(.*)$/, 1]
|
106
126
|
end
|
107
|
-
host = authority.
|
127
|
+
host = authority.sub(
|
128
|
+
/^([^\[\]]*)@/, EMPTY_STR
|
129
|
+
).sub(
|
130
|
+
/:([^:@\[\]]*?)$/, EMPTY_STR
|
131
|
+
)
|
108
132
|
port = authority[/:([^:@\[\]]*?)$/, 1]
|
109
133
|
end
|
110
|
-
if port ==
|
134
|
+
if port == EMPTY_STR
|
111
135
|
port = nil
|
112
136
|
end
|
113
137
|
|
114
|
-
return
|
138
|
+
return new(
|
115
139
|
:scheme => scheme,
|
116
140
|
:user => user,
|
117
141
|
:password => password,
|
@@ -141,35 +165,65 @@ module Addressable
|
|
141
165
|
# If we were given nil, return nil.
|
142
166
|
return nil unless uri
|
143
167
|
# If a URI object is passed, just return itself.
|
144
|
-
return uri if uri.kind_of?(self)
|
168
|
+
return uri.dup if uri.kind_of?(self)
|
169
|
+
|
170
|
+
# If a URI object of the Ruby standard library variety is passed,
|
171
|
+
# convert it to a string, then parse the string.
|
172
|
+
# We do the check this way because we don't want to accidentally
|
173
|
+
# cause a missing constant exception to be thrown.
|
174
|
+
if uri.class.name =~ /^URI\b/
|
175
|
+
uri = uri.to_s
|
176
|
+
end
|
177
|
+
|
145
178
|
if !uri.respond_to?(:to_str)
|
146
179
|
raise TypeError, "Can't convert #{uri.class} into String."
|
147
180
|
end
|
148
181
|
# Otherwise, convert to a String
|
149
|
-
uri = uri.to_str.dup
|
182
|
+
uri = uri.to_str.dup.strip
|
150
183
|
hints = {
|
151
184
|
:scheme => "http"
|
152
185
|
}.merge(hints)
|
153
186
|
case uri
|
154
|
-
when /^http
|
155
|
-
uri.
|
156
|
-
when /^
|
157
|
-
uri.
|
158
|
-
when /^feed
|
159
|
-
uri.
|
160
|
-
when /^
|
161
|
-
uri.
|
187
|
+
when /^http:\//i
|
188
|
+
uri.sub!(/^http:\/+/i, "http://")
|
189
|
+
when /^https:\//i
|
190
|
+
uri.sub!(/^https:\/+/i, "https://")
|
191
|
+
when /^feed:\/+http:\//i
|
192
|
+
uri.sub!(/^feed:\/+http:\/+/i, "feed:http://")
|
193
|
+
when /^feed:\//i
|
194
|
+
uri.sub!(/^feed:\/+/i, "feed://")
|
195
|
+
when %r[^file:/{4}]i
|
196
|
+
uri.sub!(%r[^file:/+]i, "file:////")
|
197
|
+
when %r[^file://localhost/]i
|
198
|
+
uri.sub!(%r[^file://localhost/+]i, "file:///")
|
199
|
+
when %r[^file:/+]i
|
200
|
+
uri.sub!(%r[^file:/+]i, "file:///")
|
201
|
+
when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
202
|
+
uri.sub!(/^/, hints[:scheme] + "://")
|
203
|
+
when /\A\d+\..*:\d+\z/
|
204
|
+
uri = "#{hints[:scheme]}://#{uri}"
|
205
|
+
end
|
206
|
+
match = uri.match(URIREGEX)
|
207
|
+
fragments = match.captures
|
208
|
+
authority = fragments[3]
|
209
|
+
if authority && authority.length > 0
|
210
|
+
new_authority = authority.tr("\\", "/").gsub(" ", "%20")
|
211
|
+
# NOTE: We want offset 4, not 3!
|
212
|
+
offset = match.offset(4)
|
213
|
+
uri = uri.dup
|
214
|
+
uri[offset[0]...offset[1]] = new_authority
|
162
215
|
end
|
163
216
|
parsed = self.parse(uri)
|
164
217
|
if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
|
165
218
|
parsed = self.parse(hints[:scheme] + "://" + uri)
|
166
219
|
end
|
167
220
|
if parsed.path.include?(".")
|
168
|
-
|
169
|
-
|
221
|
+
if parsed.path[/\b@\b/]
|
222
|
+
parsed.scheme = "mailto" unless parsed.scheme
|
223
|
+
elsif new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
|
170
224
|
parsed.defer_validation do
|
171
|
-
new_path = parsed.path.
|
172
|
-
Regexp.new("^" + Regexp.escape(new_host)),
|
225
|
+
new_path = parsed.path.sub(
|
226
|
+
Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
|
173
227
|
parsed.host = new_host
|
174
228
|
parsed.path = new_path
|
175
229
|
parsed.scheme = hints[:scheme] unless parsed.scheme
|
@@ -219,26 +273,26 @@ module Addressable
|
|
219
273
|
# Otherwise, convert to a String
|
220
274
|
path = path.to_str.strip
|
221
275
|
|
222
|
-
path.
|
223
|
-
path =
|
276
|
+
path.sub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
|
277
|
+
path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/
|
224
278
|
uri = self.parse(path)
|
225
279
|
|
226
280
|
if uri.scheme == nil
|
227
281
|
# Adjust windows-style uris
|
228
|
-
uri.path.
|
282
|
+
uri.path.sub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
|
229
283
|
"/#{$1.downcase}:/"
|
230
284
|
end
|
231
|
-
uri.path.
|
232
|
-
if File.
|
285
|
+
uri.path.tr!("\\", SLASH)
|
286
|
+
if File.exist?(uri.path) &&
|
233
287
|
File.stat(uri.path).directory?
|
234
|
-
uri.path.
|
288
|
+
uri.path.chomp!(SLASH)
|
235
289
|
uri.path = uri.path + '/'
|
236
290
|
end
|
237
291
|
|
238
292
|
# If the path is absolute, set the scheme and host.
|
239
|
-
if uri.path
|
293
|
+
if uri.path.start_with?(SLASH)
|
240
294
|
uri.scheme = "file"
|
241
|
-
uri.host =
|
295
|
+
uri.host = EMPTY_STR
|
242
296
|
end
|
243
297
|
uri.normalize!
|
244
298
|
end
|
@@ -273,6 +327,21 @@ module Addressable
|
|
273
327
|
return result
|
274
328
|
end
|
275
329
|
|
330
|
+
##
|
331
|
+
# Tables used to optimize encoding operations in `self.encode_component`
|
332
|
+
# and `self.normalize_component`
|
333
|
+
SEQUENCE_ENCODING_TABLE = Hash.new do |hash, sequence|
|
334
|
+
hash[sequence] = sequence.unpack("C*").map do |c|
|
335
|
+
format("%02x", c)
|
336
|
+
end.join
|
337
|
+
end
|
338
|
+
|
339
|
+
SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE = Hash.new do |hash, sequence|
|
340
|
+
hash[sequence] = sequence.unpack("C*").map do |c|
|
341
|
+
format("%%%02X", c)
|
342
|
+
end.join
|
343
|
+
end
|
344
|
+
|
276
345
|
##
|
277
346
|
# Percent encodes a URI component.
|
278
347
|
#
|
@@ -291,6 +360,12 @@ module Addressable
|
|
291
360
|
# value is the reserved plus unreserved character classes specified in
|
292
361
|
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
|
293
362
|
#
|
363
|
+
# @param [Regexp] upcase_encoded
|
364
|
+
# A string of characters that may already be percent encoded, and whose
|
365
|
+
# encodings should be upcased. This allows normalization of percent
|
366
|
+
# encodings for characters not included in the
|
367
|
+
# <code>character_class</code>.
|
368
|
+
#
|
294
369
|
# @return [String] The encoded component.
|
295
370
|
#
|
296
371
|
# @example
|
@@ -303,12 +378,23 @@ module Addressable
|
|
303
378
|
# )
|
304
379
|
# => "simple%2Fexample"
|
305
380
|
def self.encode_component(component, character_class=
|
306
|
-
CharacterClasses::RESERVED + CharacterClasses::UNRESERVED
|
381
|
+
CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
|
382
|
+
upcase_encoded='')
|
307
383
|
return nil if component.nil?
|
308
|
-
|
384
|
+
|
385
|
+
begin
|
386
|
+
if component.kind_of?(Symbol) ||
|
387
|
+
component.kind_of?(Numeric) ||
|
388
|
+
component.kind_of?(TrueClass) ||
|
389
|
+
component.kind_of?(FalseClass)
|
390
|
+
component = component.to_s
|
391
|
+
else
|
392
|
+
component = component.to_str
|
393
|
+
end
|
394
|
+
rescue TypeError, NoMethodError
|
309
395
|
raise TypeError, "Can't convert #{component.class} into String."
|
310
|
-
end
|
311
|
-
|
396
|
+
end if !component.is_a? String
|
397
|
+
|
312
398
|
if ![String, Regexp].include?(character_class.class)
|
313
399
|
raise TypeError,
|
314
400
|
"Expected String or Regexp, got #{character_class.inspect}"
|
@@ -316,15 +402,22 @@ module Addressable
|
|
316
402
|
if character_class.kind_of?(String)
|
317
403
|
character_class = /[^#{character_class}]/
|
318
404
|
end
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
405
|
+
# We can't perform regexps on invalid UTF sequences, but
|
406
|
+
# here we need to, so switch to ASCII.
|
407
|
+
component = component.dup
|
408
|
+
component.force_encoding(Encoding::ASCII_8BIT)
|
409
|
+
# Avoiding gsub! because there are edge cases with frozen strings
|
410
|
+
component = component.gsub(character_class) do |sequence|
|
411
|
+
SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[sequence]
|
412
|
+
end
|
413
|
+
if upcase_encoded.length > 0
|
414
|
+
upcase_encoded_chars = upcase_encoded.chars.map do |char|
|
415
|
+
SEQUENCE_ENCODING_TABLE[char]
|
416
|
+
end
|
417
|
+
component = component.gsub(/%(#{upcase_encoded_chars.join('|')})/,
|
418
|
+
&:upcase)
|
327
419
|
end
|
420
|
+
return component
|
328
421
|
end
|
329
422
|
|
330
423
|
class << self
|
@@ -340,32 +433,46 @@ module Addressable
|
|
340
433
|
# @param [String, Addressable::URI, #to_str] uri
|
341
434
|
# The URI or component to unencode.
|
342
435
|
#
|
343
|
-
# @param [Class]
|
436
|
+
# @param [Class] return_type
|
344
437
|
# The type of object to return.
|
345
438
|
# This value may only be set to <code>String</code> or
|
346
439
|
# <code>Addressable::URI</code>. All other values are invalid. Defaults
|
347
440
|
# to <code>String</code>.
|
348
441
|
#
|
442
|
+
# @param [String] leave_encoded
|
443
|
+
# A string of characters to leave encoded. If a percent encoded character
|
444
|
+
# in this list is encountered then it will remain percent encoded.
|
445
|
+
#
|
349
446
|
# @return [String, Addressable::URI]
|
350
447
|
# The unencoded component or URI.
|
351
|
-
# The return type is determined by the <code>
|
352
|
-
|
448
|
+
# The return type is determined by the <code>return_type</code>
|
449
|
+
# parameter.
|
450
|
+
def self.unencode(uri, return_type=String, leave_encoded='')
|
353
451
|
return nil if uri.nil?
|
354
|
-
|
452
|
+
|
453
|
+
begin
|
454
|
+
uri = uri.to_str
|
455
|
+
rescue NoMethodError, TypeError
|
355
456
|
raise TypeError, "Can't convert #{uri.class} into String."
|
356
|
-
end
|
357
|
-
if ![String, ::Addressable::URI].include?(
|
457
|
+
end if !uri.is_a? String
|
458
|
+
if ![String, ::Addressable::URI].include?(return_type)
|
358
459
|
raise TypeError,
|
359
460
|
"Expected Class (String or Addressable::URI), " +
|
360
|
-
"got #{
|
361
|
-
end
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
461
|
+
"got #{return_type.inspect}"
|
462
|
+
end
|
463
|
+
uri = uri.dup
|
464
|
+
# Seriously, only use UTF-8. I'm really not kidding!
|
465
|
+
uri.force_encoding("utf-8")
|
466
|
+
leave_encoded = leave_encoded.dup.force_encoding("utf-8")
|
467
|
+
result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
|
468
|
+
c = sequence[1..3].to_i(16).chr
|
469
|
+
c.force_encoding("utf-8")
|
470
|
+
leave_encoded.include?(c) ? sequence : c
|
471
|
+
end
|
472
|
+
result.force_encoding("utf-8")
|
473
|
+
if return_type == String
|
367
474
|
return result
|
368
|
-
elsif
|
475
|
+
elsif return_type == ::Addressable::URI
|
369
476
|
return ::Addressable::URI.parse(result)
|
370
477
|
end
|
371
478
|
end
|
@@ -387,14 +494,21 @@ module Addressable
|
|
387
494
|
# is passed, the <code>String</code> must be formatted as a regular
|
388
495
|
# expression character class. (Do not include the surrounding square
|
389
496
|
# brackets.) For example, <code>"b-zB-Z0-9"</code> would cause
|
390
|
-
# everything but the letters 'b' through 'z' and the numbers '0'
|
391
|
-
#
|
392
|
-
# value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A
|
393
|
-
# useful <code>String</code> values may be found in the
|
497
|
+
# everything but the letters 'b' through 'z' and the numbers '0'
|
498
|
+
# through '9' to be percent encoded. If a <code>Regexp</code> is passed,
|
499
|
+
# the value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A
|
500
|
+
# set of useful <code>String</code> values may be found in the
|
394
501
|
# <code>Addressable::URI::CharacterClasses</code> module. The default
|
395
502
|
# value is the reserved plus unreserved character classes specified in
|
396
503
|
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
|
397
504
|
#
|
505
|
+
# @param [String] leave_encoded
|
506
|
+
# When <code>character_class</code> is a <code>String</code> then
|
507
|
+
# <code>leave_encoded</code> is a string of characters that should remain
|
508
|
+
# percent encoded while normalizing the component; if they appear percent
|
509
|
+
# encoded in the original component, then they will be upcased ("%2f"
|
510
|
+
# normalized to "%2F") but otherwise left alone.
|
511
|
+
#
|
398
512
|
# @return [String] The normalized component.
|
399
513
|
#
|
400
514
|
# @example
|
@@ -409,35 +523,54 @@ module Addressable
|
|
409
523
|
# Addressable::URI::CharacterClasses::UNRESERVED
|
410
524
|
# )
|
411
525
|
# => "simple%2Fexample"
|
526
|
+
# Addressable::URI.normalize_component(
|
527
|
+
# "one%20two%2fthree%26four",
|
528
|
+
# "0-9a-zA-Z &/",
|
529
|
+
# "/"
|
530
|
+
# )
|
531
|
+
# => "one two%2Fthree&four"
|
412
532
|
def self.normalize_component(component, character_class=
|
413
|
-
CharacterClasses::RESERVED + CharacterClasses::UNRESERVED
|
533
|
+
CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
|
534
|
+
leave_encoded='')
|
414
535
|
return nil if component.nil?
|
415
|
-
|
536
|
+
|
537
|
+
begin
|
538
|
+
component = component.to_str
|
539
|
+
rescue NoMethodError, TypeError
|
416
540
|
raise TypeError, "Can't convert #{component.class} into String."
|
417
|
-
end
|
418
|
-
|
541
|
+
end if !component.is_a? String
|
542
|
+
|
419
543
|
if ![String, Regexp].include?(character_class.class)
|
420
544
|
raise TypeError,
|
421
545
|
"Expected String or Regexp, got #{character_class.inspect}"
|
422
546
|
end
|
423
547
|
if character_class.kind_of?(String)
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
548
|
+
leave_re = if leave_encoded.length > 0
|
549
|
+
character_class = "#{character_class}%" unless character_class.include?('%')
|
550
|
+
|
551
|
+
"|%(?!#{leave_encoded.chars.map do |char|
|
552
|
+
seq = SEQUENCE_ENCODING_TABLE[char]
|
553
|
+
[seq.upcase, seq.downcase]
|
554
|
+
end.flatten.join('|')})"
|
555
|
+
end
|
556
|
+
|
557
|
+
character_class = /[^#{character_class}]#{leave_re}/
|
431
558
|
end
|
432
|
-
|
559
|
+
# We can't perform regexps on invalid UTF sequences, but
|
560
|
+
# here we need to, so switch to ASCII.
|
561
|
+
component = component.dup
|
562
|
+
component.force_encoding(Encoding::ASCII_8BIT)
|
563
|
+
unencoded = self.unencode_component(component, String, leave_encoded)
|
433
564
|
begin
|
434
565
|
encoded = self.encode_component(
|
435
566
|
Addressable::IDNA.unicode_normalize_kc(unencoded),
|
436
|
-
character_class
|
567
|
+
character_class,
|
568
|
+
leave_encoded
|
437
569
|
)
|
438
570
|
rescue ArgumentError
|
439
571
|
encoded = self.encode_component(unencoded)
|
440
572
|
end
|
573
|
+
encoded.force_encoding(Encoding::UTF_8)
|
441
574
|
return encoded
|
442
575
|
end
|
443
576
|
|
@@ -447,7 +580,7 @@ module Addressable
|
|
447
580
|
# @param [String, Addressable::URI, #to_str] uri
|
448
581
|
# The URI to encode.
|
449
582
|
#
|
450
|
-
# @param [Class]
|
583
|
+
# @param [Class] return_type
|
451
584
|
# The type of object to return.
|
452
585
|
# This value may only be set to <code>String</code> or
|
453
586
|
# <code>Addressable::URI</code>. All other values are invalid. Defaults
|
@@ -455,18 +588,23 @@ module Addressable
|
|
455
588
|
#
|
456
589
|
# @return [String, Addressable::URI]
|
457
590
|
# The encoded URI.
|
458
|
-
# The return type is determined by the <code>
|
459
|
-
|
591
|
+
# The return type is determined by the <code>return_type</code>
|
592
|
+
# parameter.
|
593
|
+
def self.encode(uri, return_type=String)
|
460
594
|
return nil if uri.nil?
|
461
|
-
|
595
|
+
|
596
|
+
begin
|
597
|
+
uri = uri.to_str
|
598
|
+
rescue NoMethodError, TypeError
|
462
599
|
raise TypeError, "Can't convert #{uri.class} into String."
|
463
|
-
end
|
464
|
-
|
600
|
+
end if !uri.is_a? String
|
601
|
+
|
602
|
+
if ![String, ::Addressable::URI].include?(return_type)
|
465
603
|
raise TypeError,
|
466
604
|
"Expected Class (String or Addressable::URI), " +
|
467
|
-
"got #{
|
605
|
+
"got #{return_type.inspect}"
|
468
606
|
end
|
469
|
-
uri_object = uri.kind_of?(self) ? uri : self.parse(uri
|
607
|
+
uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
|
470
608
|
encoded_uri = Addressable::URI.new(
|
471
609
|
:scheme => self.encode_component(uri_object.scheme,
|
472
610
|
Addressable::URI::CharacterClasses::SCHEME),
|
@@ -479,9 +617,9 @@ module Addressable
|
|
479
617
|
:fragment => self.encode_component(uri_object.fragment,
|
480
618
|
Addressable::URI::CharacterClasses::FRAGMENT)
|
481
619
|
)
|
482
|
-
if
|
620
|
+
if return_type == String
|
483
621
|
return encoded_uri.to_s
|
484
|
-
elsif
|
622
|
+
elsif return_type == ::Addressable::URI
|
485
623
|
return encoded_uri
|
486
624
|
end
|
487
625
|
end
|
@@ -497,7 +635,7 @@ module Addressable
|
|
497
635
|
# @param [String, Addressable::URI, #to_str] uri
|
498
636
|
# The URI to encode.
|
499
637
|
#
|
500
|
-
# @param [Class]
|
638
|
+
# @param [Class] return_type
|
501
639
|
# The type of object to return.
|
502
640
|
# This value may only be set to <code>String</code> or
|
503
641
|
# <code>Addressable::URI</code>. All other values are invalid. Defaults
|
@@ -505,23 +643,27 @@ module Addressable
|
|
505
643
|
#
|
506
644
|
# @return [String, Addressable::URI]
|
507
645
|
# The encoded URI.
|
508
|
-
# The return type is determined by the <code>
|
509
|
-
|
510
|
-
|
646
|
+
# The return type is determined by the <code>return_type</code>
|
647
|
+
# parameter.
|
648
|
+
def self.normalized_encode(uri, return_type=String)
|
649
|
+
begin
|
650
|
+
uri = uri.to_str
|
651
|
+
rescue NoMethodError, TypeError
|
511
652
|
raise TypeError, "Can't convert #{uri.class} into String."
|
512
|
-
end
|
513
|
-
|
653
|
+
end if !uri.is_a? String
|
654
|
+
|
655
|
+
if ![String, ::Addressable::URI].include?(return_type)
|
514
656
|
raise TypeError,
|
515
657
|
"Expected Class (String or Addressable::URI), " +
|
516
|
-
"got #{
|
658
|
+
"got #{return_type.inspect}"
|
517
659
|
end
|
518
|
-
uri_object = uri.kind_of?(self) ? uri : self.parse(uri
|
660
|
+
uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
|
519
661
|
components = {
|
520
662
|
:scheme => self.unencode_component(uri_object.scheme),
|
521
663
|
:user => self.unencode_component(uri_object.user),
|
522
664
|
:password => self.unencode_component(uri_object.password),
|
523
665
|
:host => self.unencode_component(uri_object.host),
|
524
|
-
:port => uri_object.port,
|
666
|
+
:port => (uri_object.port.nil? ? nil : uri_object.port.to_s),
|
525
667
|
:path => self.unencode_component(uri_object.path),
|
526
668
|
:query => self.unencode_component(uri_object.query),
|
527
669
|
:fragment => self.unencode_component(uri_object.fragment)
|
@@ -553,9 +695,9 @@ module Addressable
|
|
553
695
|
:fragment => self.encode_component(components[:fragment],
|
554
696
|
Addressable::URI::CharacterClasses::FRAGMENT)
|
555
697
|
)
|
556
|
-
if
|
698
|
+
if return_type == String
|
557
699
|
return encoded_uri.to_s
|
558
|
-
elsif
|
700
|
+
elsif return_type == ::Addressable::URI
|
559
701
|
return encoded_uri
|
560
702
|
end
|
561
703
|
end
|
@@ -581,9 +723,18 @@ module Addressable
|
|
581
723
|
else
|
582
724
|
raise TypeError, "Can't convert #{form_values.class} into Array."
|
583
725
|
end
|
584
|
-
|
585
|
-
|
726
|
+
|
727
|
+
form_values = form_values.inject([]) do |accu, (key, value)|
|
728
|
+
if value.kind_of?(Array)
|
729
|
+
value.each do |v|
|
730
|
+
accu << [key.to_s, v.to_s]
|
731
|
+
end
|
732
|
+
else
|
733
|
+
accu << [key.to_s, value.to_s]
|
734
|
+
end
|
735
|
+
accu
|
586
736
|
end
|
737
|
+
|
587
738
|
if sort
|
588
739
|
# Useful for OAuth and optimizing caching systems
|
589
740
|
form_values = form_values.sort
|
@@ -601,9 +752,9 @@ module Addressable
|
|
601
752
|
).gsub("%20", "+")
|
602
753
|
]
|
603
754
|
end
|
604
|
-
return
|
755
|
+
return escaped_form_values.map do |(key, value)|
|
605
756
|
"#{key}=#{value}"
|
606
|
-
end
|
757
|
+
end.join("&")
|
607
758
|
end
|
608
759
|
|
609
760
|
##
|
@@ -681,8 +832,30 @@ module Addressable
|
|
681
832
|
self.authority = options[:authority] if options[:authority]
|
682
833
|
self.path = options[:path] if options[:path]
|
683
834
|
self.query = options[:query] if options[:query]
|
835
|
+
self.query_values = options[:query_values] if options[:query_values]
|
684
836
|
self.fragment = options[:fragment] if options[:fragment]
|
685
837
|
end
|
838
|
+
self.to_s
|
839
|
+
end
|
840
|
+
|
841
|
+
##
|
842
|
+
# Freeze URI, initializing instance variables.
|
843
|
+
#
|
844
|
+
# @return [Addressable::URI] The frozen URI object.
|
845
|
+
def freeze
|
846
|
+
self.normalized_scheme
|
847
|
+
self.normalized_user
|
848
|
+
self.normalized_password
|
849
|
+
self.normalized_userinfo
|
850
|
+
self.normalized_host
|
851
|
+
self.normalized_port
|
852
|
+
self.normalized_authority
|
853
|
+
self.normalized_site
|
854
|
+
self.normalized_path
|
855
|
+
self.normalized_query
|
856
|
+
self.normalized_fragment
|
857
|
+
self.hash
|
858
|
+
super
|
686
859
|
end
|
687
860
|
|
688
861
|
##
|
@@ -690,7 +863,7 @@ module Addressable
|
|
690
863
|
#
|
691
864
|
# @return [String] The scheme component.
|
692
865
|
def scheme
|
693
|
-
return @scheme
|
866
|
+
return defined?(@scheme) ? @scheme : nil
|
694
867
|
end
|
695
868
|
|
696
869
|
##
|
@@ -698,20 +871,20 @@ module Addressable
|
|
698
871
|
#
|
699
872
|
# @return [String] The scheme component, normalized.
|
700
873
|
def normalized_scheme
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
else
|
706
|
-
Addressable::URI.normalize_component(
|
707
|
-
self.scheme.strip.downcase,
|
708
|
-
Addressable::URI::CharacterClasses::SCHEME
|
709
|
-
)
|
710
|
-
end
|
874
|
+
return nil unless self.scheme
|
875
|
+
@normalized_scheme ||= begin
|
876
|
+
if self.scheme =~ /^\s*ssh\+svn\s*$/i
|
877
|
+
"svn+ssh".dup
|
711
878
|
else
|
712
|
-
|
879
|
+
Addressable::URI.normalize_component(
|
880
|
+
self.scheme.strip.downcase,
|
881
|
+
Addressable::URI::CharacterClasses::SCHEME
|
882
|
+
)
|
713
883
|
end
|
714
|
-
end
|
884
|
+
end
|
885
|
+
# All normalized values should be UTF-8
|
886
|
+
@normalized_scheme.force_encoding(Encoding::UTF_8) if @normalized_scheme
|
887
|
+
@normalized_scheme
|
715
888
|
end
|
716
889
|
|
717
890
|
##
|
@@ -719,24 +892,20 @@ module Addressable
|
|
719
892
|
#
|
720
893
|
# @param [String, #to_str] new_scheme The new scheme component.
|
721
894
|
def scheme=(new_scheme)
|
722
|
-
# Check for frozenness
|
723
|
-
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
724
|
-
|
725
895
|
if new_scheme && !new_scheme.respond_to?(:to_str)
|
726
896
|
raise TypeError, "Can't convert #{new_scheme.class} into String."
|
727
897
|
elsif new_scheme
|
728
898
|
new_scheme = new_scheme.to_str
|
729
899
|
end
|
730
|
-
if new_scheme && new_scheme !~
|
731
|
-
raise InvalidURIError, "Invalid scheme format
|
900
|
+
if new_scheme && new_scheme !~ /\A[a-z][a-z0-9\.\+\-]*\z/i
|
901
|
+
raise InvalidURIError, "Invalid scheme format: #{new_scheme}"
|
732
902
|
end
|
733
903
|
@scheme = new_scheme
|
734
|
-
@scheme = nil if @scheme.to_s.strip
|
904
|
+
@scheme = nil if @scheme.to_s.strip.empty?
|
735
905
|
|
736
|
-
# Reset
|
737
|
-
|
738
|
-
|
739
|
-
@hash = nil
|
906
|
+
# Reset dependent values
|
907
|
+
remove_instance_variable(:@normalized_scheme) if defined?(@normalized_scheme)
|
908
|
+
remove_composite_values
|
740
909
|
|
741
910
|
# Ensure we haven't created an invalid URI
|
742
911
|
validate()
|
@@ -747,7 +916,7 @@ module Addressable
|
|
747
916
|
#
|
748
917
|
# @return [String] The user component.
|
749
918
|
def user
|
750
|
-
return @user
|
919
|
+
return defined?(@user) ? @user : nil
|
751
920
|
end
|
752
921
|
|
753
922
|
##
|
@@ -755,21 +924,22 @@ module Addressable
|
|
755
924
|
#
|
756
925
|
# @return [String] The user component, normalized.
|
757
926
|
def normalized_user
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
else
|
764
|
-
Addressable::URI.normalize_component(
|
765
|
-
self.user.strip,
|
766
|
-
Addressable::URI::CharacterClasses::UNRESERVED
|
767
|
-
)
|
768
|
-
end
|
769
|
-
else
|
927
|
+
return nil unless self.user
|
928
|
+
return @normalized_user if defined?(@normalized_user)
|
929
|
+
@normalized_user ||= begin
|
930
|
+
if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
|
931
|
+
(!self.password || self.password.strip.empty?)
|
770
932
|
nil
|
933
|
+
else
|
934
|
+
Addressable::URI.normalize_component(
|
935
|
+
self.user.strip,
|
936
|
+
Addressable::URI::CharacterClasses::UNRESERVED
|
937
|
+
)
|
771
938
|
end
|
772
|
-
end
|
939
|
+
end
|
940
|
+
# All normalized values should be UTF-8
|
941
|
+
@normalized_user.force_encoding(Encoding::UTF_8) if @normalized_user
|
942
|
+
@normalized_user
|
773
943
|
end
|
774
944
|
|
775
945
|
##
|
@@ -777,27 +947,22 @@ module Addressable
|
|
777
947
|
#
|
778
948
|
# @param [String, #to_str] new_user The new user component.
|
779
949
|
def user=(new_user)
|
780
|
-
# Check for frozenness
|
781
|
-
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
782
|
-
|
783
950
|
if new_user && !new_user.respond_to?(:to_str)
|
784
951
|
raise TypeError, "Can't convert #{new_user.class} into String."
|
785
952
|
end
|
786
953
|
@user = new_user ? new_user.to_str : nil
|
787
954
|
|
788
955
|
# You can't have a nil user with a non-nil password
|
789
|
-
|
790
|
-
|
791
|
-
@user = "" if @user.nil?
|
956
|
+
if password != nil
|
957
|
+
@user = EMPTY_STR if @user.nil?
|
792
958
|
end
|
793
959
|
|
794
|
-
# Reset
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
@hash = nil
|
960
|
+
# Reset dependent values
|
961
|
+
remove_instance_variable(:@userinfo) if defined?(@userinfo)
|
962
|
+
remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
|
963
|
+
remove_instance_variable(:@authority) if defined?(@authority)
|
964
|
+
remove_instance_variable(:@normalized_user) if defined?(@normalized_user)
|
965
|
+
remove_composite_values
|
801
966
|
|
802
967
|
# Ensure we haven't created an invalid URI
|
803
968
|
validate()
|
@@ -808,7 +973,7 @@ module Addressable
|
|
808
973
|
#
|
809
974
|
# @return [String] The password component.
|
810
975
|
def password
|
811
|
-
return @password
|
976
|
+
return defined?(@password) ? @password : nil
|
812
977
|
end
|
813
978
|
|
814
979
|
##
|
@@ -816,21 +981,24 @@ module Addressable
|
|
816
981
|
#
|
817
982
|
# @return [String] The password component, normalized.
|
818
983
|
def normalized_password
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
else
|
825
|
-
Addressable::URI.normalize_component(
|
826
|
-
self.password.strip,
|
827
|
-
Addressable::URI::CharacterClasses::UNRESERVED
|
828
|
-
)
|
829
|
-
end
|
830
|
-
else
|
984
|
+
return nil unless self.password
|
985
|
+
return @normalized_password if defined?(@normalized_password)
|
986
|
+
@normalized_password ||= begin
|
987
|
+
if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
|
988
|
+
(!self.user || self.user.strip.empty?)
|
831
989
|
nil
|
990
|
+
else
|
991
|
+
Addressable::URI.normalize_component(
|
992
|
+
self.password.strip,
|
993
|
+
Addressable::URI::CharacterClasses::UNRESERVED
|
994
|
+
)
|
832
995
|
end
|
833
|
-
end
|
996
|
+
end
|
997
|
+
# All normalized values should be UTF-8
|
998
|
+
if @normalized_password
|
999
|
+
@normalized_password.force_encoding(Encoding::UTF_8)
|
1000
|
+
end
|
1001
|
+
@normalized_password
|
834
1002
|
end
|
835
1003
|
|
836
1004
|
##
|
@@ -838,9 +1006,6 @@ module Addressable
|
|
838
1006
|
#
|
839
1007
|
# @param [String, #to_str] new_password The new password component.
|
840
1008
|
def password=(new_password)
|
841
|
-
# Check for frozenness
|
842
|
-
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
843
|
-
|
844
1009
|
if new_password && !new_password.respond_to?(:to_str)
|
845
1010
|
raise TypeError, "Can't convert #{new_password.class} into String."
|
846
1011
|
end
|
@@ -850,16 +1015,15 @@ module Addressable
|
|
850
1015
|
@password ||= nil
|
851
1016
|
@user ||= nil
|
852
1017
|
if @password != nil
|
853
|
-
@user =
|
1018
|
+
@user = EMPTY_STR if @user.nil?
|
854
1019
|
end
|
855
1020
|
|
856
|
-
# Reset
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
@hash = nil
|
1021
|
+
# Reset dependent values
|
1022
|
+
remove_instance_variable(:@userinfo) if defined?(@userinfo)
|
1023
|
+
remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
|
1024
|
+
remove_instance_variable(:@authority) if defined?(@authority)
|
1025
|
+
remove_instance_variable(:@normalized_password) if defined?(@normalized_password)
|
1026
|
+
remove_composite_values
|
863
1027
|
|
864
1028
|
# Ensure we haven't created an invalid URI
|
865
1029
|
validate()
|
@@ -871,17 +1035,15 @@ module Addressable
|
|
871
1035
|
#
|
872
1036
|
# @return [String] The userinfo component.
|
873
1037
|
def userinfo
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
if
|
878
|
-
nil
|
879
|
-
elsif current_user && current_password
|
1038
|
+
current_user = self.user
|
1039
|
+
current_password = self.password
|
1040
|
+
(current_user || current_password) && @userinfo ||= begin
|
1041
|
+
if current_user && current_password
|
880
1042
|
"#{current_user}:#{current_password}"
|
881
1043
|
elsif current_user && !current_password
|
882
1044
|
"#{current_user}"
|
883
1045
|
end
|
884
|
-
end
|
1046
|
+
end
|
885
1047
|
end
|
886
1048
|
|
887
1049
|
##
|
@@ -889,17 +1051,24 @@ module Addressable
|
|
889
1051
|
#
|
890
1052
|
# @return [String] The userinfo component, normalized.
|
891
1053
|
def normalized_userinfo
|
892
|
-
|
1054
|
+
return nil unless self.userinfo
|
1055
|
+
return @normalized_userinfo if defined?(@normalized_userinfo)
|
1056
|
+
@normalized_userinfo ||= begin
|
893
1057
|
current_user = self.normalized_user
|
894
1058
|
current_password = self.normalized_password
|
895
1059
|
if !current_user && !current_password
|
896
1060
|
nil
|
897
1061
|
elsif current_user && current_password
|
898
|
-
"#{current_user}:#{current_password}"
|
1062
|
+
"#{current_user}:#{current_password}".dup
|
899
1063
|
elsif current_user && !current_password
|
900
|
-
"#{current_user}"
|
1064
|
+
"#{current_user}".dup
|
901
1065
|
end
|
902
|
-
end
|
1066
|
+
end
|
1067
|
+
# All normalized values should be UTF-8
|
1068
|
+
if @normalized_userinfo
|
1069
|
+
@normalized_userinfo.force_encoding(Encoding::UTF_8)
|
1070
|
+
end
|
1071
|
+
@normalized_userinfo
|
903
1072
|
end
|
904
1073
|
|
905
1074
|
##
|
@@ -907,9 +1076,6 @@ module Addressable
|
|
907
1076
|
#
|
908
1077
|
# @param [String, #to_str] new_userinfo The new userinfo component.
|
909
1078
|
def userinfo=(new_userinfo)
|
910
|
-
# Check for frozenness
|
911
|
-
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
912
|
-
|
913
1079
|
if new_userinfo && !new_userinfo.respond_to?(:to_str)
|
914
1080
|
raise TypeError, "Can't convert #{new_userinfo.class} into String."
|
915
1081
|
end
|
@@ -926,10 +1092,9 @@ module Addressable
|
|
926
1092
|
self.password = new_password
|
927
1093
|
self.user = new_user
|
928
1094
|
|
929
|
-
# Reset
|
930
|
-
|
931
|
-
|
932
|
-
@hash = nil
|
1095
|
+
# Reset dependent values
|
1096
|
+
remove_instance_variable(:@authority) if defined?(@authority)
|
1097
|
+
remove_composite_values
|
933
1098
|
|
934
1099
|
# Ensure we haven't created an invalid URI
|
935
1100
|
validate()
|
@@ -940,7 +1105,7 @@ module Addressable
|
|
940
1105
|
#
|
941
1106
|
# @return [String] The host component.
|
942
1107
|
def host
|
943
|
-
return @host
|
1108
|
+
return defined?(@host) ? @host : nil
|
944
1109
|
end
|
945
1110
|
|
946
1111
|
##
|
@@ -948,24 +1113,27 @@ module Addressable
|
|
948
1113
|
#
|
949
1114
|
# @return [String] The host component, normalized.
|
950
1115
|
def normalized_host
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
end
|
961
|
-
result
|
962
|
-
else
|
963
|
-
""
|
1116
|
+
return nil unless self.host
|
1117
|
+
@normalized_host ||= begin
|
1118
|
+
if !self.host.strip.empty?
|
1119
|
+
result = ::Addressable::IDNA.to_ascii(
|
1120
|
+
URI.unencode_component(self.host.strip.downcase)
|
1121
|
+
)
|
1122
|
+
if result =~ /[^\.]\.$/
|
1123
|
+
# Single trailing dots are unnecessary.
|
1124
|
+
result = result[0...-1]
|
964
1125
|
end
|
1126
|
+
result = Addressable::URI.normalize_component(
|
1127
|
+
result,
|
1128
|
+
CharacterClasses::HOST)
|
1129
|
+
result
|
965
1130
|
else
|
966
|
-
|
1131
|
+
EMPTY_STR.dup
|
967
1132
|
end
|
968
|
-
end
|
1133
|
+
end
|
1134
|
+
# All normalized values should be UTF-8
|
1135
|
+
@normalized_host.force_encoding(Encoding::UTF_8) if @normalized_host
|
1136
|
+
@normalized_host
|
969
1137
|
end
|
970
1138
|
|
971
1139
|
##
|
@@ -973,45 +1141,95 @@ module Addressable
|
|
973
1141
|
#
|
974
1142
|
# @param [String, #to_str] new_host The new host component.
|
975
1143
|
def host=(new_host)
|
976
|
-
# Check for frozenness
|
977
|
-
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
978
|
-
|
979
1144
|
if new_host && !new_host.respond_to?(:to_str)
|
980
1145
|
raise TypeError, "Can't convert #{new_host.class} into String."
|
981
1146
|
end
|
982
1147
|
@host = new_host ? new_host.to_str : nil
|
983
1148
|
|
984
|
-
# Reset
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
@hash = nil
|
1149
|
+
# Reset dependent values
|
1150
|
+
remove_instance_variable(:@authority) if defined?(@authority)
|
1151
|
+
remove_instance_variable(:@normalized_host) if defined?(@normalized_host)
|
1152
|
+
remove_composite_values
|
989
1153
|
|
990
1154
|
# Ensure we haven't created an invalid URI
|
991
1155
|
validate()
|
992
1156
|
end
|
993
1157
|
|
1158
|
+
##
|
1159
|
+
# This method is same as URI::Generic#host except
|
1160
|
+
# brackets for IPv6 (and 'IPvFuture') addresses are removed.
|
1161
|
+
#
|
1162
|
+
# @see Addressable::URI#host
|
1163
|
+
#
|
1164
|
+
# @return [String] The hostname for this URI.
|
1165
|
+
def hostname
|
1166
|
+
v = self.host
|
1167
|
+
/\A\[(.*)\]\z/ =~ v ? $1 : v
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
##
|
1171
|
+
# This method is same as URI::Generic#host= except
|
1172
|
+
# the argument can be a bare IPv6 address (or 'IPvFuture').
|
1173
|
+
#
|
1174
|
+
# @see Addressable::URI#host=
|
1175
|
+
#
|
1176
|
+
# @param [String, #to_str] new_hostname The new hostname for this URI.
|
1177
|
+
def hostname=(new_hostname)
|
1178
|
+
if new_hostname &&
|
1179
|
+
(new_hostname.respond_to?(:ipv4?) || new_hostname.respond_to?(:ipv6?))
|
1180
|
+
new_hostname = new_hostname.to_s
|
1181
|
+
elsif new_hostname && !new_hostname.respond_to?(:to_str)
|
1182
|
+
raise TypeError, "Can't convert #{new_hostname.class} into String."
|
1183
|
+
end
|
1184
|
+
v = new_hostname ? new_hostname.to_str : nil
|
1185
|
+
v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v
|
1186
|
+
self.host = v
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
##
|
1190
|
+
# Returns the top-level domain for this host.
|
1191
|
+
#
|
1192
|
+
# @example
|
1193
|
+
# Addressable::URI.parse("http://www.example.co.uk").tld # => "co.uk"
|
1194
|
+
def tld
|
1195
|
+
PublicSuffix.parse(self.host, ignore_private: true).tld
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
##
|
1199
|
+
# Sets the top-level domain for this URI.
|
1200
|
+
#
|
1201
|
+
# @param [String, #to_str] new_tld The new top-level domain.
|
1202
|
+
def tld=(new_tld)
|
1203
|
+
replaced_tld = host.sub(/#{tld}\z/, new_tld)
|
1204
|
+
self.host = PublicSuffix::Domain.new(replaced_tld).to_s
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
##
|
1208
|
+
# Returns the public suffix domain for this host.
|
1209
|
+
#
|
1210
|
+
# @example
|
1211
|
+
# Addressable::URI.parse("http://www.example.co.uk").domain # => "example.co.uk"
|
1212
|
+
def domain
|
1213
|
+
PublicSuffix.domain(self.host, ignore_private: true)
|
1214
|
+
end
|
1215
|
+
|
994
1216
|
##
|
995
1217
|
# The authority component for this URI.
|
996
1218
|
# Combines the user, password, host, and port components.
|
997
1219
|
#
|
998
1220
|
# @return [String] The authority component.
|
999
1221
|
def authority
|
1000
|
-
@authority ||=
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
authority << self.host
|
1009
|
-
if self.port != nil
|
1010
|
-
authority << ":#{self.port}"
|
1011
|
-
end
|
1012
|
-
authority
|
1222
|
+
self.host && @authority ||= begin
|
1223
|
+
authority = String.new
|
1224
|
+
if self.userinfo != nil
|
1225
|
+
authority << "#{self.userinfo}@"
|
1226
|
+
end
|
1227
|
+
authority << self.host
|
1228
|
+
if self.port != nil
|
1229
|
+
authority << ":#{self.port}"
|
1013
1230
|
end
|
1014
|
-
|
1231
|
+
authority
|
1232
|
+
end
|
1015
1233
|
end
|
1016
1234
|
|
1017
1235
|
##
|
@@ -1019,21 +1237,23 @@ module Addressable
|
|
1019
1237
|
#
|
1020
1238
|
# @return [String] The authority component, normalized.
|
1021
1239
|
def normalized_authority
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
authority
|
1027
|
-
if self.normalized_userinfo != nil
|
1028
|
-
authority << "#{self.normalized_userinfo}@"
|
1029
|
-
end
|
1030
|
-
authority << self.normalized_host
|
1031
|
-
if self.normalized_port != nil
|
1032
|
-
authority << ":#{self.normalized_port}"
|
1033
|
-
end
|
1034
|
-
authority
|
1240
|
+
return nil unless self.authority
|
1241
|
+
@normalized_authority ||= begin
|
1242
|
+
authority = String.new
|
1243
|
+
if self.normalized_userinfo != nil
|
1244
|
+
authority << "#{self.normalized_userinfo}@"
|
1035
1245
|
end
|
1036
|
-
|
1246
|
+
authority << self.normalized_host
|
1247
|
+
if self.normalized_port != nil
|
1248
|
+
authority << ":#{self.normalized_port}"
|
1249
|
+
end
|
1250
|
+
authority
|
1251
|
+
end
|
1252
|
+
# All normalized values should be UTF-8
|
1253
|
+
if @normalized_authority
|
1254
|
+
@normalized_authority.force_encoding(Encoding::UTF_8)
|
1255
|
+
end
|
1256
|
+
@normalized_authority
|
1037
1257
|
end
|
1038
1258
|
|
1039
1259
|
##
|
@@ -1041,9 +1261,6 @@ module Addressable
|
|
1041
1261
|
#
|
1042
1262
|
# @param [String, #to_str] new_authority The new authority component.
|
1043
1263
|
def authority=(new_authority)
|
1044
|
-
# Check for frozenness
|
1045
|
-
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
1046
|
-
|
1047
1264
|
if new_authority
|
1048
1265
|
if !new_authority.respond_to?(:to_str)
|
1049
1266
|
raise TypeError, "Can't convert #{new_authority.class} into String."
|
@@ -1054,8 +1271,11 @@ module Addressable
|
|
1054
1271
|
new_user = new_userinfo.strip[/^([^:]*):?/, 1]
|
1055
1272
|
new_password = new_userinfo.strip[/:(.*)$/, 1]
|
1056
1273
|
end
|
1057
|
-
new_host =
|
1058
|
-
|
1274
|
+
new_host = new_authority.sub(
|
1275
|
+
/^([^\[\]]*)@/, EMPTY_STR
|
1276
|
+
).sub(
|
1277
|
+
/:([^:@\[\]]*?)$/, EMPTY_STR
|
1278
|
+
)
|
1059
1279
|
new_port =
|
1060
1280
|
new_authority[/:([^:@\[\]]*?)$/, 1]
|
1061
1281
|
end
|
@@ -1066,12 +1286,67 @@ module Addressable
|
|
1066
1286
|
self.host = defined?(new_host) ? new_host : nil
|
1067
1287
|
self.port = defined?(new_port) ? new_port : nil
|
1068
1288
|
|
1069
|
-
# Reset
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1289
|
+
# Reset dependent values
|
1290
|
+
remove_instance_variable(:@userinfo) if defined?(@userinfo)
|
1291
|
+
remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
|
1292
|
+
remove_composite_values
|
1293
|
+
|
1294
|
+
# Ensure we haven't created an invalid URI
|
1295
|
+
validate()
|
1296
|
+
end
|
1297
|
+
|
1298
|
+
##
|
1299
|
+
# The origin for this URI, serialized to ASCII, as per
|
1300
|
+
# RFC 6454, section 6.2.
|
1301
|
+
#
|
1302
|
+
# @return [String] The serialized origin.
|
1303
|
+
def origin
|
1304
|
+
if self.scheme && self.authority
|
1305
|
+
if self.normalized_port
|
1306
|
+
"#{self.normalized_scheme}://#{self.normalized_host}" +
|
1307
|
+
":#{self.normalized_port}"
|
1308
|
+
else
|
1309
|
+
"#{self.normalized_scheme}://#{self.normalized_host}"
|
1310
|
+
end
|
1311
|
+
else
|
1312
|
+
"null"
|
1313
|
+
end
|
1314
|
+
end
|
1315
|
+
|
1316
|
+
##
|
1317
|
+
# Sets the origin for this URI, serialized to ASCII, as per
|
1318
|
+
# RFC 6454, section 6.2. This assignment will reset the `userinfo`
|
1319
|
+
# component.
|
1320
|
+
#
|
1321
|
+
# @param [String, #to_str] new_origin The new origin component.
|
1322
|
+
def origin=(new_origin)
|
1323
|
+
if new_origin
|
1324
|
+
if !new_origin.respond_to?(:to_str)
|
1325
|
+
raise TypeError, "Can't convert #{new_origin.class} into String."
|
1326
|
+
end
|
1327
|
+
new_origin = new_origin.to_str
|
1328
|
+
new_scheme = new_origin[/^([^:\/?#]+):\/\//, 1]
|
1329
|
+
unless new_scheme
|
1330
|
+
raise InvalidURIError, 'An origin cannot omit the scheme.'
|
1331
|
+
end
|
1332
|
+
new_host = new_origin[/:\/\/([^\/?#:]+)/, 1]
|
1333
|
+
unless new_host
|
1334
|
+
raise InvalidURIError, 'An origin cannot omit the host.'
|
1335
|
+
end
|
1336
|
+
new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
|
1337
|
+
end
|
1338
|
+
|
1339
|
+
self.scheme = defined?(new_scheme) ? new_scheme : nil
|
1340
|
+
self.host = defined?(new_host) ? new_host : nil
|
1341
|
+
self.port = defined?(new_port) ? new_port : nil
|
1342
|
+
self.userinfo = nil
|
1343
|
+
|
1344
|
+
# Reset dependent values
|
1345
|
+
remove_instance_variable(:@userinfo) if defined?(@userinfo)
|
1346
|
+
remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
|
1347
|
+
remove_instance_variable(:@authority) if defined?(@authority)
|
1348
|
+
remove_instance_variable(:@normalized_authority) if defined?(@normalized_authority)
|
1349
|
+
remove_composite_values
|
1075
1350
|
|
1076
1351
|
# Ensure we haven't created an invalid URI
|
1077
1352
|
validate()
|
@@ -1088,21 +1363,7 @@ module Addressable
|
|
1088
1363
|
# numbers. Adding new schemes to this hash, as necessary, will allow
|
1089
1364
|
# for better URI normalization.
|
1090
1365
|
def self.port_mapping
|
1091
|
-
|
1092
|
-
"http" => 80,
|
1093
|
-
"https" => 443,
|
1094
|
-
"ftp" => 21,
|
1095
|
-
"tftp" => 69,
|
1096
|
-
"sftp" => 22,
|
1097
|
-
"ssh" => 22,
|
1098
|
-
"svn+ssh" => 22,
|
1099
|
-
"telnet" => 23,
|
1100
|
-
"nntp" => 119,
|
1101
|
-
"gopher" => 70,
|
1102
|
-
"wais" => 210,
|
1103
|
-
"ldap" => 389,
|
1104
|
-
"prospero" => 1525
|
1105
|
-
}
|
1366
|
+
PORT_MAPPING
|
1106
1367
|
end
|
1107
1368
|
|
1108
1369
|
##
|
@@ -1112,7 +1373,7 @@ module Addressable
|
|
1112
1373
|
#
|
1113
1374
|
# @return [Integer] The port component.
|
1114
1375
|
def port
|
1115
|
-
return @port
|
1376
|
+
return defined?(@port) ? @port : nil
|
1116
1377
|
end
|
1117
1378
|
|
1118
1379
|
##
|
@@ -1120,13 +1381,15 @@ module Addressable
|
|
1120
1381
|
#
|
1121
1382
|
# @return [Integer] The port component, normalized.
|
1122
1383
|
def normalized_port
|
1123
|
-
|
1124
|
-
|
1384
|
+
return nil unless self.port
|
1385
|
+
return @normalized_port if defined?(@normalized_port)
|
1386
|
+
@normalized_port ||= begin
|
1387
|
+
if URI.port_mapping[self.normalized_scheme] == self.port
|
1125
1388
|
nil
|
1126
1389
|
else
|
1127
1390
|
self.port
|
1128
1391
|
end
|
1129
|
-
end
|
1392
|
+
end
|
1130
1393
|
end
|
1131
1394
|
|
1132
1395
|
##
|
@@ -1134,12 +1397,14 @@ module Addressable
|
|
1134
1397
|
#
|
1135
1398
|
# @param [String, Integer, #to_s] new_port The new port component.
|
1136
1399
|
def port=(new_port)
|
1137
|
-
# Check for frozenness
|
1138
|
-
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
1139
|
-
|
1140
1400
|
if new_port != nil && new_port.respond_to?(:to_str)
|
1141
1401
|
new_port = Addressable::URI.unencode_component(new_port.to_str)
|
1142
1402
|
end
|
1403
|
+
|
1404
|
+
if new_port.respond_to?(:valid_encoding?) && !new_port.valid_encoding?
|
1405
|
+
raise InvalidURIError, "Invalid encoding in port"
|
1406
|
+
end
|
1407
|
+
|
1143
1408
|
if new_port != nil && !(new_port.to_s =~ /^\d+$/)
|
1144
1409
|
raise InvalidURIError,
|
1145
1410
|
"Invalid port number: #{new_port.inspect}"
|
@@ -1148,12 +1413,10 @@ module Addressable
|
|
1148
1413
|
@port = new_port.to_s.to_i
|
1149
1414
|
@port = nil if @port == 0
|
1150
1415
|
|
1151
|
-
# Reset
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
@uri_string = nil
|
1156
|
-
@hash = nil
|
1416
|
+
# Reset dependent values
|
1417
|
+
remove_instance_variable(:@authority) if defined?(@authority)
|
1418
|
+
remove_instance_variable(:@normalized_port) if defined?(@normalized_port)
|
1419
|
+
remove_composite_values
|
1157
1420
|
|
1158
1421
|
# Ensure we haven't created an invalid URI
|
1159
1422
|
validate()
|
@@ -1166,17 +1429,21 @@ module Addressable
|
|
1166
1429
|
#
|
1167
1430
|
# @return [Integer] The inferred port component.
|
1168
1431
|
def inferred_port
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1432
|
+
if self.port.to_i == 0
|
1433
|
+
self.default_port
|
1434
|
+
else
|
1435
|
+
self.port.to_i
|
1436
|
+
end
|
1437
|
+
end
|
1438
|
+
|
1439
|
+
##
|
1440
|
+
# The default port for this URI's scheme.
|
1441
|
+
# This method will always returns the default port for the URI's scheme
|
1442
|
+
# regardless of the presence of an explicit port in the URI.
|
1443
|
+
#
|
1444
|
+
# @return [Integer] The default port.
|
1445
|
+
def default_port
|
1446
|
+
URI.port_mapping[self.scheme.strip.downcase] if self.scheme
|
1180
1447
|
end
|
1181
1448
|
|
1182
1449
|
##
|
@@ -1189,16 +1456,12 @@ module Addressable
|
|
1189
1456
|
#
|
1190
1457
|
# @return [String] The components that identify a site.
|
1191
1458
|
def site
|
1192
|
-
@site ||=
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
else
|
1199
|
-
nil
|
1200
|
-
end
|
1201
|
-
end)
|
1459
|
+
(self.scheme || self.authority) && @site ||= begin
|
1460
|
+
site_string = "".dup
|
1461
|
+
site_string << "#{self.scheme}:" if self.scheme != nil
|
1462
|
+
site_string << "//#{self.authority}" if self.authority != nil
|
1463
|
+
site_string
|
1464
|
+
end
|
1202
1465
|
end
|
1203
1466
|
|
1204
1467
|
##
|
@@ -1211,20 +1474,20 @@ module Addressable
|
|
1211
1474
|
#
|
1212
1475
|
# @return [String] The normalized components that identify a site.
|
1213
1476
|
def normalized_site
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
end
|
1220
|
-
if self.normalized_authority != nil
|
1221
|
-
site_string << "//#{self.normalized_authority}"
|
1222
|
-
end
|
1223
|
-
site_string
|
1224
|
-
else
|
1225
|
-
nil
|
1477
|
+
return nil unless self.site
|
1478
|
+
@normalized_site ||= begin
|
1479
|
+
site_string = "".dup
|
1480
|
+
if self.normalized_scheme != nil
|
1481
|
+
site_string << "#{self.normalized_scheme}:"
|
1226
1482
|
end
|
1227
|
-
|
1483
|
+
if self.normalized_authority != nil
|
1484
|
+
site_string << "//#{self.normalized_authority}"
|
1485
|
+
end
|
1486
|
+
site_string
|
1487
|
+
end
|
1488
|
+
# All normalized values should be UTF-8
|
1489
|
+
@normalized_site.force_encoding(Encoding::UTF_8) if @normalized_site
|
1490
|
+
@normalized_site
|
1228
1491
|
end
|
1229
1492
|
|
1230
1493
|
##
|
@@ -1254,36 +1517,40 @@ module Addressable
|
|
1254
1517
|
#
|
1255
1518
|
# @return [String] The path component.
|
1256
1519
|
def path
|
1257
|
-
@path
|
1258
|
-
return @path
|
1520
|
+
return defined?(@path) ? @path : EMPTY_STR
|
1259
1521
|
end
|
1260
1522
|
|
1523
|
+
NORMPATH = /^(?!\/)[^\/:]*:.*$/
|
1261
1524
|
##
|
1262
1525
|
# The path component for this URI, normalized.
|
1263
1526
|
#
|
1264
1527
|
# @return [String] The path component, normalized.
|
1265
1528
|
def normalized_path
|
1266
|
-
@normalized_path ||=
|
1267
|
-
|
1268
|
-
|
1529
|
+
@normalized_path ||= begin
|
1530
|
+
path = self.path.to_s
|
1531
|
+
if self.scheme == nil && path =~ NORMPATH
|
1269
1532
|
# Relative paths with colons in the first segment are ambiguous.
|
1270
|
-
|
1533
|
+
path = path.sub(":", "%2F")
|
1271
1534
|
end
|
1272
1535
|
# String#split(delimeter, -1) uses the more strict splitting behavior
|
1273
1536
|
# found by default in Python.
|
1274
|
-
result =
|
1537
|
+
result = path.strip.split(SLASH, -1).map do |segment|
|
1275
1538
|
Addressable::URI.normalize_component(
|
1276
1539
|
segment,
|
1277
1540
|
Addressable::URI::CharacterClasses::PCHAR
|
1278
1541
|
)
|
1279
|
-
end
|
1280
|
-
|
1281
|
-
|
1542
|
+
end.join(SLASH)
|
1543
|
+
|
1544
|
+
result = URI.normalize_path(result)
|
1545
|
+
if result.empty? &&
|
1282
1546
|
["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
|
1283
|
-
result =
|
1547
|
+
result = SLASH.dup
|
1284
1548
|
end
|
1285
1549
|
result
|
1286
|
-
end
|
1550
|
+
end
|
1551
|
+
# All normalized values should be UTF-8
|
1552
|
+
@normalized_path.force_encoding(Encoding::UTF_8) if @normalized_path
|
1553
|
+
@normalized_path
|
1287
1554
|
end
|
1288
1555
|
|
1289
1556
|
##
|
@@ -1291,21 +1558,20 @@ module Addressable
|
|
1291
1558
|
#
|
1292
1559
|
# @param [String, #to_str] new_path The new path component.
|
1293
1560
|
def path=(new_path)
|
1294
|
-
# Check for frozenness
|
1295
|
-
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
1296
|
-
|
1297
1561
|
if new_path && !new_path.respond_to?(:to_str)
|
1298
1562
|
raise TypeError, "Can't convert #{new_path.class} into String."
|
1299
1563
|
end
|
1300
|
-
@path = (new_path ||
|
1301
|
-
if
|
1564
|
+
@path = (new_path || EMPTY_STR).to_str
|
1565
|
+
if !@path.empty? && @path[0..0] != SLASH && host != nil
|
1302
1566
|
@path = "/#{@path}"
|
1303
1567
|
end
|
1304
1568
|
|
1305
|
-
# Reset
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1569
|
+
# Reset dependent values
|
1570
|
+
remove_instance_variable(:@normalized_path) if defined?(@normalized_path)
|
1571
|
+
remove_composite_values
|
1572
|
+
|
1573
|
+
# Ensure we haven't created an invalid URI
|
1574
|
+
validate()
|
1309
1575
|
end
|
1310
1576
|
|
1311
1577
|
##
|
@@ -1314,7 +1580,7 @@ module Addressable
|
|
1314
1580
|
# @return [String] The path's basename.
|
1315
1581
|
def basename
|
1316
1582
|
# Path cannot be nil
|
1317
|
-
return File.basename(self.path).
|
1583
|
+
return File.basename(self.path).sub(/;[^\/]*$/, EMPTY_STR)
|
1318
1584
|
end
|
1319
1585
|
|
1320
1586
|
##
|
@@ -1332,24 +1598,31 @@ module Addressable
|
|
1332
1598
|
#
|
1333
1599
|
# @return [String] The query component.
|
1334
1600
|
def query
|
1335
|
-
return @query
|
1601
|
+
return defined?(@query) ? @query : nil
|
1336
1602
|
end
|
1337
1603
|
|
1338
1604
|
##
|
1339
1605
|
# The query component for this URI, normalized.
|
1340
1606
|
#
|
1341
1607
|
# @return [String] The query component, normalized.
|
1342
|
-
def normalized_query
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1608
|
+
def normalized_query(*flags)
|
1609
|
+
return nil unless self.query
|
1610
|
+
return @normalized_query if defined?(@normalized_query)
|
1611
|
+
@normalized_query ||= begin
|
1612
|
+
modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
|
1613
|
+
# Make sure possible key-value pair delimiters are escaped.
|
1614
|
+
modified_query_class.sub!("\\&", "").sub!("\\;", "")
|
1615
|
+
pairs = (self.query || "").split("&", -1)
|
1616
|
+
pairs.delete_if(&:empty?) if flags.include?(:compacted)
|
1617
|
+
pairs.sort! if flags.include?(:sorted)
|
1618
|
+
component = pairs.map do |pair|
|
1619
|
+
Addressable::URI.normalize_component(pair, modified_query_class, "+")
|
1620
|
+
end.join("&")
|
1621
|
+
component == "" ? nil : component
|
1622
|
+
end
|
1623
|
+
# All normalized values should be UTF-8
|
1624
|
+
@normalized_query.force_encoding(Encoding::UTF_8) if @normalized_query
|
1625
|
+
@normalized_query
|
1353
1626
|
end
|
1354
1627
|
|
1355
1628
|
##
|
@@ -1357,134 +1630,86 @@ module Addressable
|
|
1357
1630
|
#
|
1358
1631
|
# @param [String, #to_str] new_query The new query component.
|
1359
1632
|
def query=(new_query)
|
1360
|
-
# Check for frozenness
|
1361
|
-
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
1362
|
-
|
1363
1633
|
if new_query && !new_query.respond_to?(:to_str)
|
1364
1634
|
raise TypeError, "Can't convert #{new_query.class} into String."
|
1365
1635
|
end
|
1366
1636
|
@query = new_query ? new_query.to_str : nil
|
1367
1637
|
|
1368
|
-
# Reset
|
1369
|
-
|
1370
|
-
|
1371
|
-
@hash = nil
|
1638
|
+
# Reset dependent values
|
1639
|
+
remove_instance_variable(:@normalized_query) if defined?(@normalized_query)
|
1640
|
+
remove_composite_values
|
1372
1641
|
end
|
1373
1642
|
|
1374
1643
|
##
|
1375
1644
|
# Converts the query component to a Hash value.
|
1376
1645
|
#
|
1377
|
-
# @
|
1378
|
-
#
|
1379
|
-
# <code>:subscript</code>. The <code>:dot</code> notation is not
|
1380
|
-
# supported for assignment. Default value is <code>:subscript</code>.
|
1646
|
+
# @param [Class] return_type The return type desired. Value must be either
|
1647
|
+
# `Hash` or `Array`.
|
1381
1648
|
#
|
1382
|
-
# @return [Hash, Array] The query string parsed as a Hash or Array
|
1649
|
+
# @return [Hash, Array, nil] The query string parsed as a Hash or Array
|
1650
|
+
# or nil if the query string is blank.
|
1383
1651
|
#
|
1384
1652
|
# @example
|
1385
1653
|
# Addressable::URI.parse("?one=1&two=2&three=3").query_values
|
1386
1654
|
# #=> {"one" => "1", "two" => "2", "three" => "3"}
|
1387
|
-
# Addressable::URI.parse("?one
|
1388
|
-
# #=>
|
1389
|
-
# Addressable::URI.parse("?one
|
1390
|
-
#
|
1391
|
-
# )
|
1392
|
-
# #=> {
|
1393
|
-
# Addressable::URI.parse("
|
1394
|
-
#
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1399
|
-
# )
|
1400
|
-
# #=> {"one.two.three" => "four"}
|
1401
|
-
# Addressable::URI.parse(
|
1402
|
-
# "?one[two][three][]=four&one[two][three][]=five"
|
1403
|
-
# ).query_values
|
1404
|
-
# #=> {"one" => {"two" => {"three" => ["four", "five"]}}}
|
1405
|
-
# Addressable::URI.parse(
|
1406
|
-
# "?one=two&one=three").query_values(:notation => :flat_array)
|
1407
|
-
# #=> [['one', 'two'], ['one', 'three']]
|
1408
|
-
def query_values(options={})
|
1409
|
-
defaults = {:notation => :subscript}
|
1410
|
-
options = defaults.merge(options)
|
1411
|
-
if ![:flat, :dot, :subscript, :flat_array].include?(options[:notation])
|
1412
|
-
raise ArgumentError,
|
1413
|
-
"Invalid notation. Must be one of: " +
|
1414
|
-
"[:flat, :dot, :subscript, :flat_array]."
|
1415
|
-
end
|
1416
|
-
dehash = lambda do |hash|
|
1417
|
-
hash.each do |(key, value)|
|
1418
|
-
if value.kind_of?(Hash)
|
1419
|
-
hash[key] = dehash.call(value)
|
1420
|
-
end
|
1421
|
-
end
|
1422
|
-
if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
|
1423
|
-
hash.sort.inject([]) do |accu, (key, value)|
|
1424
|
-
accu << value; accu
|
1425
|
-
end
|
1426
|
-
else
|
1427
|
-
hash
|
1428
|
-
end
|
1655
|
+
# Addressable::URI.parse("?one=two&one=three").query_values(Array)
|
1656
|
+
# #=> [["one", "two"], ["one", "three"]]
|
1657
|
+
# Addressable::URI.parse("?one=two&one=three").query_values(Hash)
|
1658
|
+
# #=> {"one" => "three"}
|
1659
|
+
# Addressable::URI.parse("?").query_values
|
1660
|
+
# #=> {}
|
1661
|
+
# Addressable::URI.parse("").query_values
|
1662
|
+
# #=> nil
|
1663
|
+
def query_values(return_type=Hash)
|
1664
|
+
empty_accumulator = Array == return_type ? [] : {}
|
1665
|
+
if return_type != Hash && return_type != Array
|
1666
|
+
raise ArgumentError, "Invalid return type. Must be Hash or Array."
|
1429
1667
|
end
|
1430
1668
|
return nil if self.query == nil
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
value
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1669
|
+
split_query = self.query.split("&").map do |pair|
|
1670
|
+
pair.split("=", 2) if pair && !pair.empty?
|
1671
|
+
end.compact
|
1672
|
+
return split_query.inject(empty_accumulator.dup) do |accu, pair|
|
1673
|
+
# I'd rather use key/value identifiers instead of array lookups,
|
1674
|
+
# but in this case I really want to maintain the exact pair structure,
|
1675
|
+
# so it's best to make all changes in-place.
|
1676
|
+
pair[0] = URI.unencode_component(pair[0])
|
1677
|
+
if pair[1].respond_to?(:to_str)
|
1678
|
+
# I loathe the fact that I have to do this. Stupid HTML 4.01.
|
1679
|
+
# Treating '+' as a space was just an unbelievably bad idea.
|
1680
|
+
# There was nothing wrong with '%20'!
|
1681
|
+
# If it ain't broke, don't fix it!
|
1682
|
+
pair[1] = URI.unencode_component(pair[1].to_str.tr("+", " "))
|
1439
1683
|
end
|
1440
|
-
if
|
1441
|
-
|
1442
|
-
raise ArgumentError, "Key was repeated: #{key.inspect}"
|
1443
|
-
end
|
1444
|
-
accumulator[key] = value
|
1445
|
-
elsif options[:notation] == :flat_array
|
1446
|
-
accumulator << [key, value]
|
1684
|
+
if return_type == Hash
|
1685
|
+
accu[pair[0]] = pair[1]
|
1447
1686
|
else
|
1448
|
-
|
1449
|
-
array_value = false
|
1450
|
-
subkeys = key.split(".")
|
1451
|
-
elsif options[:notation] == :subscript
|
1452
|
-
array_value = !!(key =~ /\[\]$/)
|
1453
|
-
subkeys = key.split(/[\[\]]+/)
|
1454
|
-
end
|
1455
|
-
current_hash = accumulator
|
1456
|
-
for i in 0...(subkeys.size - 1)
|
1457
|
-
subkey = subkeys[i]
|
1458
|
-
current_hash[subkey] = {} unless current_hash[subkey]
|
1459
|
-
current_hash = current_hash[subkey]
|
1460
|
-
end
|
1461
|
-
if array_value
|
1462
|
-
current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
|
1463
|
-
current_hash[subkeys.last] << value
|
1464
|
-
else
|
1465
|
-
current_hash[subkeys.last] = value
|
1466
|
-
end
|
1687
|
+
accu << pair
|
1467
1688
|
end
|
1468
|
-
|
1469
|
-
end).inject(empty_accumulator.dup) do |accumulator, (key, value)|
|
1470
|
-
if options[:notation] == :flat_array
|
1471
|
-
accumulator << [key, value]
|
1472
|
-
else
|
1473
|
-
accumulator[key] = value.kind_of?(Hash) ? dehash.call(value) : value
|
1474
|
-
end
|
1475
|
-
accumulator
|
1689
|
+
accu
|
1476
1690
|
end
|
1477
1691
|
end
|
1478
1692
|
|
1479
1693
|
##
|
1480
1694
|
# Sets the query component for this URI from a Hash object.
|
1481
|
-
#
|
1482
|
-
# An empty Hash will result in a nil query.
|
1695
|
+
# An empty Hash or Array will result in an empty query string.
|
1483
1696
|
#
|
1484
1697
|
# @param [Hash, #to_hash, Array] new_query_values The new query values.
|
1698
|
+
#
|
1699
|
+
# @example
|
1700
|
+
# uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
|
1701
|
+
# uri.query
|
1702
|
+
# # => "a=a&b=c&b=d&b=e"
|
1703
|
+
# uri.query_values = [['a', 'a'], ['b', 'c'], ['b', 'd'], ['b', 'e']]
|
1704
|
+
# uri.query
|
1705
|
+
# # => "a=a&b=c&b=d&b=e"
|
1706
|
+
# uri.query_values = [['a', 'a'], ['b', ['c', 'd', 'e']]]
|
1707
|
+
# uri.query
|
1708
|
+
# # => "a=a&b=c&b=d&b=e"
|
1709
|
+
# uri.query_values = [['flag'], ['key', 'value']]
|
1710
|
+
# uri.query
|
1711
|
+
# # => "flag&key=value"
|
1485
1712
|
def query_values=(new_query_values)
|
1486
|
-
# Check for frozenness
|
1487
|
-
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
1488
1713
|
if new_query_values == nil
|
1489
1714
|
self.query = nil
|
1490
1715
|
return nil
|
@@ -1504,38 +1729,27 @@ module Addressable
|
|
1504
1729
|
# Only to be used for non-Array inputs. Arrays should preserve order.
|
1505
1730
|
new_query_values.sort!
|
1506
1731
|
end
|
1507
|
-
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
|
1508
1732
|
|
1509
|
-
#
|
1510
|
-
buffer = ""
|
1511
|
-
stack = []
|
1512
|
-
e = lambda do |component|
|
1513
|
-
component = component.to_s if component.kind_of?(Symbol)
|
1514
|
-
self.class.encode_component(component, CharacterClasses::UNRESERVED)
|
1515
|
-
end
|
1733
|
+
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
|
1734
|
+
buffer = "".dup
|
1516
1735
|
new_query_values.each do |key, value|
|
1517
|
-
|
1518
|
-
|
1736
|
+
encoded_key = URI.encode_component(
|
1737
|
+
key, CharacterClasses::UNRESERVED
|
1738
|
+
)
|
1739
|
+
if value == nil
|
1740
|
+
buffer << "#{encoded_key}&"
|
1519
1741
|
elsif value.kind_of?(Array)
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
buffer << "#{e.call(key)}&"
|
1526
|
-
else
|
1527
|
-
buffer << "#{e.call(key)}=#{e.call(value)}&"
|
1528
|
-
end
|
1529
|
-
end
|
1530
|
-
stack.each do |(parent, hash)|
|
1531
|
-
(hash.sort_by { |key| key.to_s }).each do |(key, value)|
|
1532
|
-
if value.kind_of?(Hash)
|
1533
|
-
stack << ["#{parent}[#{key}]", value]
|
1534
|
-
elsif value == true
|
1535
|
-
buffer << "#{parent}[#{e.call(key)}]&"
|
1536
|
-
else
|
1537
|
-
buffer << "#{parent}[#{e.call(key)}]=#{e.call(value)}&"
|
1742
|
+
value.each do |sub_value|
|
1743
|
+
encoded_value = URI.encode_component(
|
1744
|
+
sub_value, CharacterClasses::UNRESERVED
|
1745
|
+
)
|
1746
|
+
buffer << "#{encoded_key}=#{encoded_value}&"
|
1538
1747
|
end
|
1748
|
+
else
|
1749
|
+
encoded_value = URI.encode_component(
|
1750
|
+
value, CharacterClasses::UNRESERVED
|
1751
|
+
)
|
1752
|
+
buffer << "#{encoded_key}=#{encoded_value}&"
|
1539
1753
|
end
|
1540
1754
|
end
|
1541
1755
|
self.query = buffer.chop
|
@@ -1547,10 +1761,10 @@ module Addressable
|
|
1547
1761
|
#
|
1548
1762
|
# @return [String] The request URI required for an HTTP request.
|
1549
1763
|
def request_uri
|
1550
|
-
return nil if self.absolute? && self.scheme !~ /^https?$/
|
1764
|
+
return nil if self.absolute? && self.scheme !~ /^https?$/i
|
1551
1765
|
return (
|
1552
|
-
(self.path
|
1553
|
-
(self.query ? "?#{self.query}" :
|
1766
|
+
(!self.path.empty? ? self.path : SLASH) +
|
1767
|
+
(self.query ? "?#{self.query}" : EMPTY_STR)
|
1554
1768
|
)
|
1555
1769
|
end
|
1556
1770
|
|
@@ -1562,21 +1776,20 @@ module Addressable
|
|
1562
1776
|
if !new_request_uri.respond_to?(:to_str)
|
1563
1777
|
raise TypeError, "Can't convert #{new_request_uri.class} into String."
|
1564
1778
|
end
|
1565
|
-
if self.absolute? && self.scheme !~ /^https?$/
|
1779
|
+
if self.absolute? && self.scheme !~ /^https?$/i
|
1566
1780
|
raise InvalidURIError,
|
1567
1781
|
"Cannot set an HTTP request URI for a non-HTTP URI."
|
1568
1782
|
end
|
1569
1783
|
new_request_uri = new_request_uri.to_str
|
1570
|
-
path_component = new_request_uri[/^([^\?]*)
|
1784
|
+
path_component = new_request_uri[/^([^\?]*)\??(?:.*)$/, 1]
|
1571
1785
|
query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1]
|
1572
1786
|
path_component = path_component.to_s
|
1573
|
-
path_component = (path_component
|
1787
|
+
path_component = (!path_component.empty? ? path_component : SLASH)
|
1574
1788
|
self.path = path_component
|
1575
1789
|
self.query = query_component
|
1576
1790
|
|
1577
|
-
# Reset
|
1578
|
-
|
1579
|
-
@hash = nil
|
1791
|
+
# Reset dependent values
|
1792
|
+
remove_composite_values
|
1580
1793
|
end
|
1581
1794
|
|
1582
1795
|
##
|
@@ -1584,7 +1797,7 @@ module Addressable
|
|
1584
1797
|
#
|
1585
1798
|
# @return [String] The fragment component.
|
1586
1799
|
def fragment
|
1587
|
-
return @fragment
|
1800
|
+
return defined?(@fragment) ? @fragment : nil
|
1588
1801
|
end
|
1589
1802
|
|
1590
1803
|
##
|
@@ -1592,16 +1805,20 @@ module Addressable
|
|
1592
1805
|
#
|
1593
1806
|
# @return [String] The fragment component, normalized.
|
1594
1807
|
def normalized_fragment
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1808
|
+
return nil unless self.fragment
|
1809
|
+
return @normalized_fragment if defined?(@normalized_fragment)
|
1810
|
+
@normalized_fragment ||= begin
|
1811
|
+
component = Addressable::URI.normalize_component(
|
1812
|
+
self.fragment,
|
1813
|
+
Addressable::URI::CharacterClasses::FRAGMENT
|
1814
|
+
)
|
1815
|
+
component == "" ? nil : component
|
1816
|
+
end
|
1817
|
+
# All normalized values should be UTF-8
|
1818
|
+
if @normalized_fragment
|
1819
|
+
@normalized_fragment.force_encoding(Encoding::UTF_8)
|
1820
|
+
end
|
1821
|
+
@normalized_fragment
|
1605
1822
|
end
|
1606
1823
|
|
1607
1824
|
##
|
@@ -1609,18 +1826,14 @@ module Addressable
|
|
1609
1826
|
#
|
1610
1827
|
# @param [String, #to_str] new_fragment The new fragment component.
|
1611
1828
|
def fragment=(new_fragment)
|
1612
|
-
# Check for frozenness
|
1613
|
-
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
1614
|
-
|
1615
1829
|
if new_fragment && !new_fragment.respond_to?(:to_str)
|
1616
1830
|
raise TypeError, "Can't convert #{new_fragment.class} into String."
|
1617
1831
|
end
|
1618
1832
|
@fragment = new_fragment ? new_fragment.to_str : nil
|
1619
1833
|
|
1620
|
-
# Reset
|
1621
|
-
|
1622
|
-
|
1623
|
-
@hash = nil
|
1834
|
+
# Reset dependent values
|
1835
|
+
remove_instance_variable(:@normalized_fragment) if defined?(@normalized_fragment)
|
1836
|
+
remove_composite_values
|
1624
1837
|
|
1625
1838
|
# Ensure we haven't created an invalid URI
|
1626
1839
|
validate()
|
@@ -1634,7 +1847,7 @@ module Addressable
|
|
1634
1847
|
# <code>false</code> otherwise.
|
1635
1848
|
def ip_based?
|
1636
1849
|
if self.scheme
|
1637
|
-
return
|
1850
|
+
return URI.ip_based_schemes.include?(
|
1638
1851
|
self.scheme.strip.downcase)
|
1639
1852
|
end
|
1640
1853
|
return false
|
@@ -1670,11 +1883,11 @@ module Addressable
|
|
1670
1883
|
if !uri.respond_to?(:to_str)
|
1671
1884
|
raise TypeError, "Can't convert #{uri.class} into String."
|
1672
1885
|
end
|
1673
|
-
if !uri.kind_of?(
|
1886
|
+
if !uri.kind_of?(URI)
|
1674
1887
|
# Otherwise, convert to a String, then parse.
|
1675
|
-
uri =
|
1888
|
+
uri = URI.parse(uri.to_str)
|
1676
1889
|
end
|
1677
|
-
if uri.to_s
|
1890
|
+
if uri.to_s.empty?
|
1678
1891
|
return self.dup
|
1679
1892
|
end
|
1680
1893
|
|
@@ -1694,7 +1907,7 @@ module Addressable
|
|
1694
1907
|
joined_password = uri.password
|
1695
1908
|
joined_host = uri.host
|
1696
1909
|
joined_port = uri.port
|
1697
|
-
joined_path =
|
1910
|
+
joined_path = URI.normalize_path(uri.path)
|
1698
1911
|
joined_query = uri.query
|
1699
1912
|
else
|
1700
1913
|
if uri.authority != nil
|
@@ -1702,10 +1915,10 @@ module Addressable
|
|
1702
1915
|
joined_password = uri.password
|
1703
1916
|
joined_host = uri.host
|
1704
1917
|
joined_port = uri.port
|
1705
|
-
joined_path =
|
1918
|
+
joined_path = URI.normalize_path(uri.path)
|
1706
1919
|
joined_query = uri.query
|
1707
1920
|
else
|
1708
|
-
if uri.path == nil || uri.path
|
1921
|
+
if uri.path == nil || uri.path.empty?
|
1709
1922
|
joined_path = self.path
|
1710
1923
|
if uri.query != nil
|
1711
1924
|
joined_query = uri.query
|
@@ -1713,29 +1926,29 @@ module Addressable
|
|
1713
1926
|
joined_query = self.query
|
1714
1927
|
end
|
1715
1928
|
else
|
1716
|
-
if uri.path[0..0] ==
|
1717
|
-
joined_path =
|
1929
|
+
if uri.path[0..0] == SLASH
|
1930
|
+
joined_path = URI.normalize_path(uri.path)
|
1718
1931
|
else
|
1719
1932
|
base_path = self.path.dup
|
1720
|
-
base_path =
|
1721
|
-
base_path =
|
1933
|
+
base_path = EMPTY_STR if base_path == nil
|
1934
|
+
base_path = URI.normalize_path(base_path)
|
1722
1935
|
|
1723
1936
|
# Section 5.2.3 of RFC 3986
|
1724
1937
|
#
|
1725
1938
|
# Removes the right-most path segment from the base path.
|
1726
|
-
if base_path
|
1727
|
-
base_path.
|
1939
|
+
if base_path.include?(SLASH)
|
1940
|
+
base_path.sub!(/\/[^\/]+$/, SLASH)
|
1728
1941
|
else
|
1729
|
-
base_path =
|
1942
|
+
base_path = EMPTY_STR
|
1730
1943
|
end
|
1731
1944
|
|
1732
1945
|
# If the base path is empty and an authority segment has been
|
1733
|
-
# defined, use a base path of
|
1734
|
-
if base_path
|
1735
|
-
base_path =
|
1946
|
+
# defined, use a base path of SLASH
|
1947
|
+
if base_path.empty? && self.authority != nil
|
1948
|
+
base_path = SLASH
|
1736
1949
|
end
|
1737
1950
|
|
1738
|
-
joined_path =
|
1951
|
+
joined_path = URI.normalize_path(base_path + uri.path)
|
1739
1952
|
end
|
1740
1953
|
joined_query = uri.query
|
1741
1954
|
end
|
@@ -1748,7 +1961,7 @@ module Addressable
|
|
1748
1961
|
end
|
1749
1962
|
joined_fragment = uri.fragment
|
1750
1963
|
|
1751
|
-
return
|
1964
|
+
return self.class.new(
|
1752
1965
|
:scheme => joined_scheme,
|
1753
1966
|
:user => joined_user,
|
1754
1967
|
:password => joined_password,
|
@@ -1804,7 +2017,7 @@ module Addressable
|
|
1804
2017
|
end
|
1805
2018
|
end
|
1806
2019
|
|
1807
|
-
uri =
|
2020
|
+
uri = self.class.new
|
1808
2021
|
uri.defer_validation do
|
1809
2022
|
# Bunch of crazy logic required because of the composite components
|
1810
2023
|
# like userinfo and authority.
|
@@ -1863,7 +2076,7 @@ module Addressable
|
|
1863
2076
|
# @return [Addressable::URI]
|
1864
2077
|
# The normalized relative URI that is equivalent to the original URI.
|
1865
2078
|
def route_from(uri)
|
1866
|
-
uri =
|
2079
|
+
uri = URI.parse(uri).normalize
|
1867
2080
|
normalized_self = self.normalize
|
1868
2081
|
if normalized_self.relative?
|
1869
2082
|
raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
|
@@ -1888,9 +2101,16 @@ module Addressable
|
|
1888
2101
|
components[:query] = nil
|
1889
2102
|
end
|
1890
2103
|
else
|
1891
|
-
if uri.path !=
|
1892
|
-
components[:path]
|
1893
|
-
|
2104
|
+
if uri.path != SLASH and components[:path]
|
2105
|
+
self_splitted_path = split_path(components[:path])
|
2106
|
+
uri_splitted_path = split_path(uri.path)
|
2107
|
+
self_dir = self_splitted_path.shift
|
2108
|
+
uri_dir = uri_splitted_path.shift
|
2109
|
+
while !self_splitted_path.empty? && !uri_splitted_path.empty? and self_dir == uri_dir
|
2110
|
+
self_dir = self_splitted_path.shift
|
2111
|
+
uri_dir = uri_splitted_path.shift
|
2112
|
+
end
|
2113
|
+
components[:path] = (uri_splitted_path.fill('..') + [self_dir] + self_splitted_path).join(SLASH)
|
1894
2114
|
end
|
1895
2115
|
end
|
1896
2116
|
end
|
@@ -1921,7 +2141,7 @@ module Addressable
|
|
1921
2141
|
# @return [Addressable::URI]
|
1922
2142
|
# The normalized relative URI that is equivalent to the supplied URI.
|
1923
2143
|
def route_to(uri)
|
1924
|
-
return
|
2144
|
+
return URI.parse(uri).route_from(self)
|
1925
2145
|
end
|
1926
2146
|
|
1927
2147
|
##
|
@@ -1939,13 +2159,13 @@ module Addressable
|
|
1939
2159
|
# URI scheme.
|
1940
2160
|
if normalized_scheme == "feed"
|
1941
2161
|
if self.to_s =~ /^feed:\/*http:\/*/
|
1942
|
-
return
|
2162
|
+
return URI.parse(
|
1943
2163
|
self.to_s[/^feed:\/*(http:\/*.*)/, 1]
|
1944
2164
|
).normalize
|
1945
2165
|
end
|
1946
2166
|
end
|
1947
2167
|
|
1948
|
-
return
|
2168
|
+
return self.class.new(
|
1949
2169
|
:scheme => normalized_scheme,
|
1950
2170
|
:authority => normalized_authority,
|
1951
2171
|
:path => normalized_path,
|
@@ -2010,7 +2230,7 @@ module Addressable
|
|
2010
2230
|
# <code>true</code> if the URIs are equivalent, <code>false</code>
|
2011
2231
|
# otherwise.
|
2012
2232
|
def ==(uri)
|
2013
|
-
return false unless uri.kind_of?(
|
2233
|
+
return false unless uri.kind_of?(URI)
|
2014
2234
|
return self.normalize.to_s == uri.normalize.to_s
|
2015
2235
|
end
|
2016
2236
|
|
@@ -2024,7 +2244,7 @@ module Addressable
|
|
2024
2244
|
# <code>true</code> if the URIs are equivalent, <code>false</code>
|
2025
2245
|
# otherwise.
|
2026
2246
|
def eql?(uri)
|
2027
|
-
return false unless uri.kind_of?(
|
2247
|
+
return false unless uri.kind_of?(URI)
|
2028
2248
|
return self.to_s == uri.to_s
|
2029
2249
|
end
|
2030
2250
|
|
@@ -2034,7 +2254,7 @@ module Addressable
|
|
2034
2254
|
#
|
2035
2255
|
# @return [Integer] A hash of the URI.
|
2036
2256
|
def hash
|
2037
|
-
|
2257
|
+
@hash ||= self.to_s.hash * -1
|
2038
2258
|
end
|
2039
2259
|
|
2040
2260
|
##
|
@@ -2042,7 +2262,7 @@ module Addressable
|
|
2042
2262
|
#
|
2043
2263
|
# @return [Addressable::URI] The cloned URI.
|
2044
2264
|
def dup
|
2045
|
-
duplicated_uri =
|
2265
|
+
duplicated_uri = self.class.new(
|
2046
2266
|
:scheme => self.scheme ? self.scheme.dup : nil,
|
2047
2267
|
:user => self.user ? self.user.dup : nil,
|
2048
2268
|
:password => self.password ? self.password.dup : nil,
|
@@ -2055,28 +2275,6 @@ module Addressable
|
|
2055
2275
|
return duplicated_uri
|
2056
2276
|
end
|
2057
2277
|
|
2058
|
-
##
|
2059
|
-
# Freezes the URI object.
|
2060
|
-
#
|
2061
|
-
# @return [Addressable::URI] The frozen URI.
|
2062
|
-
def freeze
|
2063
|
-
# Unfortunately, because of the memoized implementation of many of the
|
2064
|
-
# URI methods, the default freeze method will cause unexpected errors.
|
2065
|
-
# As an alternative, we freeze the string representation of the URI
|
2066
|
-
# instead. This should generally produce the desired effect.
|
2067
|
-
self.to_s.freeze
|
2068
|
-
return self
|
2069
|
-
end
|
2070
|
-
|
2071
|
-
##
|
2072
|
-
# Determines if the URI is frozen.
|
2073
|
-
#
|
2074
|
-
# @return [TrueClass, FalseClass]
|
2075
|
-
# <code>true</code> if the URI is frozen, <code>false</code> otherwise.
|
2076
|
-
def frozen?
|
2077
|
-
self.to_s.frozen?
|
2078
|
-
end
|
2079
|
-
|
2080
2278
|
##
|
2081
2279
|
# Omits components from a URI.
|
2082
2280
|
#
|
@@ -2120,23 +2318,35 @@ module Addressable
|
|
2120
2318
|
replace_self(self.omit(*components))
|
2121
2319
|
end
|
2122
2320
|
|
2321
|
+
##
|
2322
|
+
# Determines if the URI is an empty string.
|
2323
|
+
#
|
2324
|
+
# @return [TrueClass, FalseClass]
|
2325
|
+
# Returns <code>true</code> if empty, <code>false</code> otherwise.
|
2326
|
+
def empty?
|
2327
|
+
return self.to_s.empty?
|
2328
|
+
end
|
2329
|
+
|
2123
2330
|
##
|
2124
2331
|
# Converts the URI to a <code>String</code>.
|
2125
2332
|
#
|
2126
2333
|
# @return [String] The URI's <code>String</code> representation.
|
2127
2334
|
def to_s
|
2128
|
-
|
2129
|
-
|
2335
|
+
if self.scheme == nil && self.path != nil && !self.path.empty? &&
|
2336
|
+
self.path =~ NORMPATH
|
2337
|
+
raise InvalidURIError,
|
2338
|
+
"Cannot assemble URI string with ambiguous path: '#{self.path}'"
|
2339
|
+
end
|
2340
|
+
@uri_string ||= begin
|
2341
|
+
uri_string = String.new
|
2130
2342
|
uri_string << "#{self.scheme}:" if self.scheme != nil
|
2131
2343
|
uri_string << "//#{self.authority}" if self.authority != nil
|
2132
2344
|
uri_string << self.path.to_s
|
2133
2345
|
uri_string << "?#{self.query}" if self.query != nil
|
2134
2346
|
uri_string << "##{self.fragment}" if self.fragment != nil
|
2135
|
-
|
2136
|
-
uri_string.force_encoding(Encoding::UTF_8)
|
2137
|
-
end
|
2347
|
+
uri_string.force_encoding(Encoding::UTF_8)
|
2138
2348
|
uri_string
|
2139
|
-
end
|
2349
|
+
end
|
2140
2350
|
end
|
2141
2351
|
|
2142
2352
|
##
|
@@ -2165,7 +2375,7 @@ module Addressable
|
|
2165
2375
|
#
|
2166
2376
|
# @return [String] The URI object's state, as a <code>String</code>.
|
2167
2377
|
def inspect
|
2168
|
-
sprintf("#<%s:%#0x URI:%s>",
|
2378
|
+
sprintf("#<%s:%#0x URI:%s>", URI.to_s, self.object_id, self.to_s)
|
2169
2379
|
end
|
2170
2380
|
|
2171
2381
|
##
|
@@ -2176,16 +2386,24 @@ module Addressable
|
|
2176
2386
|
#
|
2177
2387
|
# @param [Proc] block
|
2178
2388
|
# A set of operations to perform on a given URI.
|
2179
|
-
def defer_validation
|
2180
|
-
raise LocalJumpError, "No block given." unless
|
2389
|
+
def defer_validation
|
2390
|
+
raise LocalJumpError, "No block given." unless block_given?
|
2181
2391
|
@validation_deferred = true
|
2182
|
-
|
2392
|
+
yield
|
2183
2393
|
@validation_deferred = false
|
2184
2394
|
validate
|
2185
2395
|
return nil
|
2186
2396
|
end
|
2187
2397
|
|
2188
|
-
|
2398
|
+
protected
|
2399
|
+
SELF_REF = '.'
|
2400
|
+
PARENT = '..'
|
2401
|
+
|
2402
|
+
RULE_2A = /\/\.\/|\/\.$/
|
2403
|
+
RULE_2B_2C = /\/([^\/]*)\/\.\.\/|\/([^\/]*)\/\.\.$/
|
2404
|
+
RULE_2D = /^\.\.?\/?/
|
2405
|
+
RULE_PREFIXED_PARENT = /^\/\.\.?\/|^(\/\.\.?)+\/?$/
|
2406
|
+
|
2189
2407
|
##
|
2190
2408
|
# Resolves paths to their simplest form.
|
2191
2409
|
#
|
@@ -2197,22 +2415,27 @@ module Addressable
|
|
2197
2415
|
|
2198
2416
|
return nil if path.nil?
|
2199
2417
|
normalized_path = path.dup
|
2200
|
-
previous_state = normalized_path.dup
|
2201
2418
|
begin
|
2202
|
-
|
2203
|
-
normalized_path.gsub!(
|
2204
|
-
|
2205
|
-
|
2206
|
-
|
2207
|
-
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2419
|
+
mod = nil
|
2420
|
+
mod ||= normalized_path.gsub!(RULE_2A, SLASH)
|
2421
|
+
|
2422
|
+
pair = normalized_path.match(RULE_2B_2C)
|
2423
|
+
parent, current = pair[1], pair[2] if pair
|
2424
|
+
if pair && ((parent != SELF_REF && parent != PARENT) ||
|
2425
|
+
(current != SELF_REF && current != PARENT))
|
2426
|
+
mod ||= normalized_path.gsub!(
|
2427
|
+
Regexp.new(
|
2428
|
+
"/#{Regexp.escape(parent.to_s)}/\\.\\./|" +
|
2429
|
+
"(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
|
2430
|
+
), SLASH
|
2431
|
+
)
|
2212
2432
|
end
|
2213
|
-
|
2214
|
-
normalized_path.gsub!(
|
2215
|
-
|
2433
|
+
|
2434
|
+
mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
|
2435
|
+
# Non-standard, removes prefixed dotted segments from path.
|
2436
|
+
mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
|
2437
|
+
end until mod.nil?
|
2438
|
+
|
2216
2439
|
return normalized_path
|
2217
2440
|
end
|
2218
2441
|
|
@@ -2220,9 +2443,9 @@ module Addressable
|
|
2220
2443
|
# Ensures that the URI is valid.
|
2221
2444
|
def validate
|
2222
2445
|
return if !!@validation_deferred
|
2223
|
-
if self.scheme != nil &&
|
2224
|
-
(self.host == nil || self.host
|
2225
|
-
(self.path == nil || self.path
|
2446
|
+
if self.scheme != nil && self.ip_based? &&
|
2447
|
+
(self.host == nil || self.host.empty?) &&
|
2448
|
+
(self.path == nil || self.path.empty?)
|
2226
2449
|
raise InvalidURIError,
|
2227
2450
|
"Absolute URI missing hierarchical segment: '#{self.to_s}'"
|
2228
2451
|
end
|
@@ -2233,11 +2456,24 @@ module Addressable
|
|
2233
2456
|
raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'"
|
2234
2457
|
end
|
2235
2458
|
end
|
2236
|
-
if self.path != nil && self.path
|
2459
|
+
if self.path != nil && !self.path.empty? && self.path[0..0] != SLASH &&
|
2237
2460
|
self.authority != nil
|
2238
2461
|
raise InvalidURIError,
|
2239
2462
|
"Cannot have a relative path with an authority set: '#{self.to_s}'"
|
2240
2463
|
end
|
2464
|
+
if self.path != nil && !self.path.empty? &&
|
2465
|
+
self.path[0..1] == SLASH + SLASH && self.authority == nil
|
2466
|
+
raise InvalidURIError,
|
2467
|
+
"Cannot have a path with two leading slashes " +
|
2468
|
+
"without an authority set: '#{self.to_s}'"
|
2469
|
+
end
|
2470
|
+
unreserved = CharacterClasses::UNRESERVED
|
2471
|
+
sub_delims = CharacterClasses::SUB_DELIMS
|
2472
|
+
if !self.host.nil? && (self.host =~ /[<>{}\/\\\?\#\@"[[:space:]]]/ ||
|
2473
|
+
(self.host[/^\[(.*)\]$/, 1] != nil && self.host[/^\[(.*)\]$/, 1] !~
|
2474
|
+
Regexp.new("^[#{unreserved}#{sub_delims}:]*$")))
|
2475
|
+
raise InvalidURIError, "Invalid character in host: '#{self.host.to_s}'"
|
2476
|
+
end
|
2241
2477
|
return nil
|
2242
2478
|
end
|
2243
2479
|
|
@@ -2249,9 +2485,11 @@ module Addressable
|
|
2249
2485
|
#
|
2250
2486
|
# @return [Addressable::URI] <code>self</code>.
|
2251
2487
|
def replace_self(uri)
|
2252
|
-
# Reset
|
2488
|
+
# Reset dependent values
|
2253
2489
|
instance_variables.each do |var|
|
2254
|
-
|
2490
|
+
if instance_variable_defined?(var) && var != :@validation_deferred
|
2491
|
+
remove_instance_variable(var)
|
2492
|
+
end
|
2255
2493
|
end
|
2256
2494
|
|
2257
2495
|
@scheme = uri.scheme
|
@@ -2264,5 +2502,28 @@ module Addressable
|
|
2264
2502
|
@fragment = uri.fragment
|
2265
2503
|
return self
|
2266
2504
|
end
|
2505
|
+
|
2506
|
+
##
|
2507
|
+
# Splits path string with "/" (slash).
|
2508
|
+
# It is considered that there is empty string after last slash when
|
2509
|
+
# path ends with slash.
|
2510
|
+
#
|
2511
|
+
# @param [String] path The path to split.
|
2512
|
+
#
|
2513
|
+
# @return [Array<String>] An array of parts of path.
|
2514
|
+
def split_path(path)
|
2515
|
+
splitted = path.split(SLASH)
|
2516
|
+
splitted << EMPTY_STR if path.end_with? SLASH
|
2517
|
+
splitted
|
2518
|
+
end
|
2519
|
+
|
2520
|
+
##
|
2521
|
+
# Resets composite values for the entire URI
|
2522
|
+
#
|
2523
|
+
# @api private
|
2524
|
+
def remove_composite_values
|
2525
|
+
remove_instance_variable(:@uri_string) if defined?(@uri_string)
|
2526
|
+
remove_instance_variable(:@hash) if defined?(@hash)
|
2527
|
+
end
|
2267
2528
|
end
|
2268
2529
|
end
|