addressable 2.3.2 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +214 -28
- data/README.md +103 -69
- data/lib/addressable/idna/native.rb +31 -8
- data/lib/addressable/idna/pure.rb +4269 -217
- data/lib/addressable/idna.rb +3 -2
- data/lib/addressable/template.rb +266 -64
- data/lib/addressable/uri.rb +664 -307
- data/lib/addressable/version.rb +5 -4
- data/lib/addressable.rb +4 -0
- metadata +51 -89
- data/Gemfile +0 -17
- data/Rakefile +0 -37
- data/data/unicode.data +0 -0
- data/spec/addressable/idna_spec.rb +0 -231
- data/spec/addressable/net_http_compat_spec.rb +0 -26
- data/spec/addressable/template_spec.rb +0 -1001
- data/spec/addressable/uri_spec.rb +0 -5064
- data/tasks/clobber.rake +0 -2
- data/tasks/gem.rake +0 -85
- data/tasks/git.rake +0 -45
- data/tasks/metrics.rake +0 -22
- data/tasks/rspec.rake +0 -58
- data/tasks/rubyforge.rake +0 -89
- data/tasks/yard.rake +0 -27
- data/website/index.html +0 -110
data/lib/addressable/uri.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
#--
|
|
3
|
-
# Copyright (C)
|
|
4
|
+
# Copyright (C) Bob Aman
|
|
4
5
|
#
|
|
5
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
|
|
19
20
|
require "addressable/version"
|
|
20
21
|
require "addressable/idna"
|
|
22
|
+
require "public_suffix"
|
|
21
23
|
|
|
22
24
|
##
|
|
23
25
|
# Addressable is a library for processing links and URIs.
|
|
@@ -35,19 +37,48 @@ module Addressable
|
|
|
35
37
|
##
|
|
36
38
|
# Container for the character classes specified in
|
|
37
39
|
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
|
|
40
|
+
#
|
|
41
|
+
# Note: Concatenated and interpolated `String`s are not affected by the
|
|
42
|
+
# `frozen_string_literal` directive and must be frozen explicitly.
|
|
43
|
+
#
|
|
44
|
+
# Interpolated `String`s *were* frozen this way before Ruby 3.0:
|
|
45
|
+
# https://bugs.ruby-lang.org/issues/17104
|
|
38
46
|
module CharacterClasses
|
|
39
47
|
ALPHA = "a-zA-Z"
|
|
40
48
|
DIGIT = "0-9"
|
|
41
49
|
GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
|
|
42
50
|
SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
|
|
43
|
-
RESERVED = GEN_DELIMS + SUB_DELIMS
|
|
44
|
-
UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
RESERVED = (GEN_DELIMS + SUB_DELIMS).freeze
|
|
52
|
+
UNRESERVED = (ALPHA + DIGIT + "\\-\\.\\_\\~").freeze
|
|
53
|
+
RESERVED_AND_UNRESERVED = RESERVED + UNRESERVED
|
|
54
|
+
PCHAR = (UNRESERVED + SUB_DELIMS + "\\:\\@").freeze
|
|
55
|
+
SCHEME = (ALPHA + DIGIT + "\\-\\+\\.").freeze
|
|
56
|
+
HOST = (UNRESERVED + SUB_DELIMS + "\\[\\:\\]").freeze
|
|
57
|
+
AUTHORITY = (PCHAR + "\\[\\]").freeze
|
|
58
|
+
PATH = (PCHAR + "\\/").freeze
|
|
59
|
+
QUERY = (PCHAR + "\\/\\?").freeze
|
|
60
|
+
FRAGMENT = (PCHAR + "\\/\\?").freeze
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
module NormalizeCharacterClasses
|
|
64
|
+
HOST = /[^#{CharacterClasses::HOST}]/
|
|
65
|
+
UNRESERVED = /[^#{CharacterClasses::UNRESERVED}]/
|
|
66
|
+
PCHAR = /[^#{CharacterClasses::PCHAR}]/
|
|
67
|
+
SCHEME = /[^#{CharacterClasses::SCHEME}]/
|
|
68
|
+
FRAGMENT = /[^#{CharacterClasses::FRAGMENT}]/
|
|
69
|
+
QUERY = %r{[^a-zA-Z0-9\-\.\_\~\!\$\'\(\)\*\+\,\=\:\@\/\?%]|%(?!2B|2b)}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
module CharacterClassesRegexps
|
|
73
|
+
AUTHORITY = /[^#{CharacterClasses::AUTHORITY}]/
|
|
74
|
+
FRAGMENT = /[^#{CharacterClasses::FRAGMENT}]/
|
|
75
|
+
HOST = /[^#{CharacterClasses::HOST}]/
|
|
76
|
+
PATH = /[^#{CharacterClasses::PATH}]/
|
|
77
|
+
QUERY = /[^#{CharacterClasses::QUERY}]/
|
|
78
|
+
RESERVED = /[^#{CharacterClasses::RESERVED}]/
|
|
79
|
+
RESERVED_AND_UNRESERVED = /[^#{CharacterClasses::RESERVED_AND_UNRESERVED}]/
|
|
80
|
+
SCHEME = /[^#{CharacterClasses::SCHEME}]/
|
|
81
|
+
UNRESERVED = /[^#{CharacterClasses::UNRESERVED}]/
|
|
51
82
|
end
|
|
52
83
|
|
|
53
84
|
SLASH = '/'
|
|
@@ -69,7 +100,7 @@ module Addressable
|
|
|
69
100
|
"wais" => 210,
|
|
70
101
|
"ldap" => 389,
|
|
71
102
|
"prospero" => 1525
|
|
72
|
-
}
|
|
103
|
+
}.freeze
|
|
73
104
|
|
|
74
105
|
##
|
|
75
106
|
# Returns a URI object based on the parsed string.
|
|
@@ -99,7 +130,7 @@ module Addressable
|
|
|
99
130
|
uri = uri.to_str
|
|
100
131
|
rescue TypeError, NoMethodError
|
|
101
132
|
raise TypeError, "Can't convert #{uri.class} into String."
|
|
102
|
-
end
|
|
133
|
+
end unless uri.is_a?(String)
|
|
103
134
|
|
|
104
135
|
# This Regexp supplied as an example in RFC 3986, and it works great.
|
|
105
136
|
scan = uri.scan(URIREGEX)
|
|
@@ -120,15 +151,15 @@ module Addressable
|
|
|
120
151
|
user = userinfo.strip[/^([^:]*):?/, 1]
|
|
121
152
|
password = userinfo.strip[/:(.*)$/, 1]
|
|
122
153
|
end
|
|
123
|
-
|
|
154
|
+
|
|
155
|
+
host = authority.sub(
|
|
124
156
|
/^([^\[\]]*)@/, EMPTY_STR
|
|
125
|
-
).
|
|
157
|
+
).sub(
|
|
126
158
|
/:([^:@\[\]]*?)$/, EMPTY_STR
|
|
127
159
|
)
|
|
160
|
+
|
|
128
161
|
port = authority[/:([^:@\[\]]*?)$/, 1]
|
|
129
|
-
|
|
130
|
-
if port == EMPTY_STR
|
|
131
|
-
port = nil
|
|
162
|
+
port = nil if port == EMPTY_STR
|
|
132
163
|
end
|
|
133
164
|
|
|
134
165
|
return new(
|
|
@@ -162,37 +193,63 @@ module Addressable
|
|
|
162
193
|
return nil unless uri
|
|
163
194
|
# If a URI object is passed, just return itself.
|
|
164
195
|
return uri.dup if uri.kind_of?(self)
|
|
165
|
-
|
|
196
|
+
|
|
197
|
+
# If a URI object of the Ruby standard library variety is passed,
|
|
198
|
+
# convert it to a string, then parse the string.
|
|
199
|
+
# We do the check this way because we don't want to accidentally
|
|
200
|
+
# cause a missing constant exception to be thrown.
|
|
201
|
+
if uri.class.name =~ /^URI\b/
|
|
202
|
+
uri = uri.to_s
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
unless uri.respond_to?(:to_str)
|
|
166
206
|
raise TypeError, "Can't convert #{uri.class} into String."
|
|
167
207
|
end
|
|
168
208
|
# Otherwise, convert to a String
|
|
169
|
-
uri = uri.to_str.dup
|
|
209
|
+
uri = uri.to_str.dup.strip
|
|
170
210
|
hints = {
|
|
171
211
|
:scheme => "http"
|
|
172
212
|
}.merge(hints)
|
|
173
213
|
case uri
|
|
174
|
-
when /^http
|
|
175
|
-
uri.
|
|
176
|
-
when /^https
|
|
177
|
-
uri.
|
|
178
|
-
when /^feed:\/+http
|
|
179
|
-
uri.
|
|
180
|
-
when /^feed
|
|
181
|
-
uri.
|
|
182
|
-
when
|
|
183
|
-
uri.
|
|
214
|
+
when /^http:\//i
|
|
215
|
+
uri.sub!(/^http:\/+/i, "http://")
|
|
216
|
+
when /^https:\//i
|
|
217
|
+
uri.sub!(/^https:\/+/i, "https://")
|
|
218
|
+
when /^feed:\/+http:\//i
|
|
219
|
+
uri.sub!(/^feed:\/+http:\/+/i, "feed:http://")
|
|
220
|
+
when /^feed:\//i
|
|
221
|
+
uri.sub!(/^feed:\/+/i, "feed://")
|
|
222
|
+
when %r[^file:/{4}]i
|
|
223
|
+
uri.sub!(%r[^file:/+]i, "file:////")
|
|
224
|
+
when %r[^file://localhost/]i
|
|
225
|
+
uri.sub!(%r[^file://localhost/+]i, "file:///")
|
|
226
|
+
when %r[^file:/+]i
|
|
227
|
+
uri.sub!(%r[^file:/+]i, "file:///")
|
|
184
228
|
when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
|
185
|
-
uri.
|
|
229
|
+
uri.sub!(/^/, hints[:scheme] + "://")
|
|
230
|
+
when /\A\d+\..*:\d+\z/
|
|
231
|
+
uri = "#{hints[:scheme]}://#{uri}"
|
|
232
|
+
end
|
|
233
|
+
match = uri.match(URIREGEX)
|
|
234
|
+
fragments = match.captures
|
|
235
|
+
authority = fragments[3]
|
|
236
|
+
if authority && authority.length > 0
|
|
237
|
+
new_authority = authority.tr("\\", "/").gsub(" ", "%20")
|
|
238
|
+
# NOTE: We want offset 4, not 3!
|
|
239
|
+
offset = match.offset(4)
|
|
240
|
+
uri = uri.dup
|
|
241
|
+
uri[offset[0]...offset[1]] = new_authority
|
|
186
242
|
end
|
|
187
243
|
parsed = self.parse(uri)
|
|
188
244
|
if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
|
|
189
245
|
parsed = self.parse(hints[:scheme] + "://" + uri)
|
|
190
246
|
end
|
|
191
247
|
if parsed.path.include?(".")
|
|
192
|
-
|
|
193
|
-
|
|
248
|
+
if parsed.path[/\b@\b/]
|
|
249
|
+
parsed.scheme = "mailto" unless parsed.scheme
|
|
250
|
+
elsif new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
|
|
194
251
|
parsed.defer_validation do
|
|
195
|
-
new_path = parsed.path.
|
|
252
|
+
new_path = parsed.path.sub(
|
|
196
253
|
Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
|
|
197
254
|
parsed.host = new_host
|
|
198
255
|
parsed.path = new_path
|
|
@@ -237,30 +294,30 @@ module Addressable
|
|
|
237
294
|
return nil unless path
|
|
238
295
|
# If a URI object is passed, just return itself.
|
|
239
296
|
return path if path.kind_of?(self)
|
|
240
|
-
|
|
297
|
+
unless path.respond_to?(:to_str)
|
|
241
298
|
raise TypeError, "Can't convert #{path.class} into String."
|
|
242
299
|
end
|
|
243
300
|
# Otherwise, convert to a String
|
|
244
301
|
path = path.to_str.strip
|
|
245
302
|
|
|
246
|
-
path.
|
|
303
|
+
path.sub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
|
|
247
304
|
path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/
|
|
248
305
|
uri = self.parse(path)
|
|
249
306
|
|
|
250
307
|
if uri.scheme == nil
|
|
251
308
|
# Adjust windows-style uris
|
|
252
|
-
uri.path.
|
|
309
|
+
uri.path.sub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
|
|
253
310
|
"/#{$1.downcase}:/"
|
|
254
311
|
end
|
|
255
|
-
uri.path.
|
|
256
|
-
if File.
|
|
312
|
+
uri.path.tr!("\\", SLASH)
|
|
313
|
+
if File.exist?(uri.path) &&
|
|
257
314
|
File.stat(uri.path).directory?
|
|
258
|
-
uri.path.
|
|
315
|
+
uri.path.chomp!(SLASH)
|
|
259
316
|
uri.path = uri.path + '/'
|
|
260
317
|
end
|
|
261
318
|
|
|
262
319
|
# If the path is absolute, set the scheme and host.
|
|
263
|
-
if uri.path
|
|
320
|
+
if uri.path.start_with?(SLASH)
|
|
264
321
|
uri.scheme = "file"
|
|
265
322
|
uri.host = EMPTY_STR
|
|
266
323
|
end
|
|
@@ -285,18 +342,29 @@ module Addressable
|
|
|
285
342
|
# #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
|
|
286
343
|
def self.join(*uris)
|
|
287
344
|
uri_objects = uris.collect do |uri|
|
|
288
|
-
|
|
345
|
+
unless uri.respond_to?(:to_str)
|
|
289
346
|
raise TypeError, "Can't convert #{uri.class} into String."
|
|
290
347
|
end
|
|
291
348
|
uri.kind_of?(self) ? uri : self.parse(uri.to_str)
|
|
292
349
|
end
|
|
293
350
|
result = uri_objects.shift.dup
|
|
294
|
-
|
|
351
|
+
uri_objects.each do |uri|
|
|
295
352
|
result.join!(uri)
|
|
296
353
|
end
|
|
297
354
|
return result
|
|
298
355
|
end
|
|
299
356
|
|
|
357
|
+
##
|
|
358
|
+
# Tables used to optimize encoding operations in `self.encode_component`
|
|
359
|
+
# and `self.normalize_component`
|
|
360
|
+
SEQUENCE_ENCODING_TABLE = (0..255).map do |byte|
|
|
361
|
+
format("%02x", byte).freeze
|
|
362
|
+
end.freeze
|
|
363
|
+
|
|
364
|
+
SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE = (0..255).map do |byte|
|
|
365
|
+
format("%%%02X", byte).freeze
|
|
366
|
+
end.freeze
|
|
367
|
+
|
|
300
368
|
##
|
|
301
369
|
# Percent encodes a URI component.
|
|
302
370
|
#
|
|
@@ -315,6 +383,12 @@ module Addressable
|
|
|
315
383
|
# value is the reserved plus unreserved character classes specified in
|
|
316
384
|
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
|
|
317
385
|
#
|
|
386
|
+
# @param [Regexp] upcase_encoded
|
|
387
|
+
# A string of characters that may already be percent encoded, and whose
|
|
388
|
+
# encodings should be upcased. This allows normalization of percent
|
|
389
|
+
# encodings for characters not included in the
|
|
390
|
+
# <code>character_class</code>.
|
|
391
|
+
#
|
|
318
392
|
# @return [String] The encoded component.
|
|
319
393
|
#
|
|
320
394
|
# @example
|
|
@@ -326,12 +400,14 @@ module Addressable
|
|
|
326
400
|
# "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
|
|
327
401
|
# )
|
|
328
402
|
# => "simple%2Fexample"
|
|
329
|
-
def self.encode_component(component, character_class=
|
|
330
|
-
CharacterClasses::RESERVED + CharacterClasses::UNRESERVED)
|
|
403
|
+
def self.encode_component(component, character_class=CharacterClassesRegexps::RESERVED_AND_UNRESERVED, upcase_encoded='')
|
|
331
404
|
return nil if component.nil?
|
|
332
405
|
|
|
333
406
|
begin
|
|
334
|
-
if component.kind_of?(Symbol) ||
|
|
407
|
+
if component.kind_of?(Symbol) ||
|
|
408
|
+
component.kind_of?(Numeric) ||
|
|
409
|
+
component.kind_of?(TrueClass) ||
|
|
410
|
+
component.kind_of?(FalseClass)
|
|
335
411
|
component = component.to_s
|
|
336
412
|
else
|
|
337
413
|
component = component.to_str
|
|
@@ -347,19 +423,27 @@ module Addressable
|
|
|
347
423
|
if character_class.kind_of?(String)
|
|
348
424
|
character_class = /[^#{character_class}]/
|
|
349
425
|
end
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
426
|
+
# We can't perform regexps on invalid UTF sequences, but
|
|
427
|
+
# here we need to, so switch to ASCII.
|
|
428
|
+
component = component.dup
|
|
429
|
+
component.force_encoding(Encoding::ASCII_8BIT)
|
|
430
|
+
# Avoiding gsub! because there are edge cases with frozen strings
|
|
431
|
+
component = component.gsub(character_class) do |char|
|
|
432
|
+
SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[char.ord]
|
|
433
|
+
end
|
|
434
|
+
if upcase_encoded.length > 0
|
|
435
|
+
upcase_encoded_chars = upcase_encoded.bytes.map do |byte|
|
|
436
|
+
SEQUENCE_ENCODING_TABLE[byte]
|
|
437
|
+
end
|
|
438
|
+
component = component.gsub(/%(#{upcase_encoded_chars.join('|')})/,
|
|
439
|
+
&:upcase)
|
|
358
440
|
end
|
|
441
|
+
|
|
442
|
+
return component
|
|
359
443
|
end
|
|
360
444
|
|
|
361
445
|
class << self
|
|
362
|
-
alias_method :
|
|
446
|
+
alias_method :escape_component, :encode_component
|
|
363
447
|
end
|
|
364
448
|
|
|
365
449
|
##
|
|
@@ -377,11 +461,15 @@ module Addressable
|
|
|
377
461
|
# <code>Addressable::URI</code>. All other values are invalid. Defaults
|
|
378
462
|
# to <code>String</code>.
|
|
379
463
|
#
|
|
464
|
+
# @param [String] leave_encoded
|
|
465
|
+
# A string of characters to leave encoded. If a percent encoded character
|
|
466
|
+
# in this list is encountered then it will remain percent encoded.
|
|
467
|
+
#
|
|
380
468
|
# @return [String, Addressable::URI]
|
|
381
469
|
# The unencoded component or URI.
|
|
382
470
|
# The return type is determined by the <code>return_type</code>
|
|
383
471
|
# parameter.
|
|
384
|
-
def self.unencode(uri, return_type=String)
|
|
472
|
+
def self.unencode(uri, return_type=String, leave_encoded='')
|
|
385
473
|
return nil if uri.nil?
|
|
386
474
|
|
|
387
475
|
begin
|
|
@@ -394,10 +482,14 @@ module Addressable
|
|
|
394
482
|
"Expected Class (String or Addressable::URI), " +
|
|
395
483
|
"got #{return_type.inspect}"
|
|
396
484
|
end
|
|
485
|
+
|
|
397
486
|
result = uri.gsub(/%[0-9a-f]{2}/i) do |sequence|
|
|
398
|
-
sequence[1..3].to_i(16).chr
|
|
487
|
+
c = sequence[1..3].to_i(16).chr
|
|
488
|
+
c.force_encoding(sequence.encoding)
|
|
489
|
+
leave_encoded.include?(c) ? sequence : c
|
|
399
490
|
end
|
|
400
|
-
|
|
491
|
+
|
|
492
|
+
result.force_encoding(Encoding::UTF_8)
|
|
401
493
|
if return_type == String
|
|
402
494
|
return result
|
|
403
495
|
elsif return_type == ::Addressable::URI
|
|
@@ -430,6 +522,13 @@ module Addressable
|
|
|
430
522
|
# value is the reserved plus unreserved character classes specified in
|
|
431
523
|
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
|
|
432
524
|
#
|
|
525
|
+
# @param [String] leave_encoded
|
|
526
|
+
# When <code>character_class</code> is a <code>String</code> then
|
|
527
|
+
# <code>leave_encoded</code> is a string of characters that should remain
|
|
528
|
+
# percent encoded while normalizing the component; if they appear percent
|
|
529
|
+
# encoded in the original component, then they will be upcased ("%2f"
|
|
530
|
+
# normalized to "%2F") but otherwise left alone.
|
|
531
|
+
#
|
|
433
532
|
# @return [String] The normalized component.
|
|
434
533
|
#
|
|
435
534
|
# @example
|
|
@@ -444,8 +543,15 @@ module Addressable
|
|
|
444
543
|
# Addressable::URI::CharacterClasses::UNRESERVED
|
|
445
544
|
# )
|
|
446
545
|
# => "simple%2Fexample"
|
|
546
|
+
# Addressable::URI.normalize_component(
|
|
547
|
+
# "one%20two%2fthree%26four",
|
|
548
|
+
# "0-9a-zA-Z &/",
|
|
549
|
+
# "/"
|
|
550
|
+
# )
|
|
551
|
+
# => "one two%2Fthree&four"
|
|
447
552
|
def self.normalize_component(component, character_class=
|
|
448
|
-
|
|
553
|
+
CharacterClassesRegexps::RESERVED_AND_UNRESERVED,
|
|
554
|
+
leave_encoded='')
|
|
449
555
|
return nil if component.nil?
|
|
450
556
|
|
|
451
557
|
begin
|
|
@@ -459,23 +565,35 @@ module Addressable
|
|
|
459
565
|
"Expected String or Regexp, got #{character_class.inspect}"
|
|
460
566
|
end
|
|
461
567
|
if character_class.kind_of?(String)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
568
|
+
leave_re = if leave_encoded.length > 0
|
|
569
|
+
character_class = "#{character_class}%" unless character_class.include?('%')
|
|
570
|
+
|
|
571
|
+
bytes = leave_encoded.bytes
|
|
572
|
+
leave_encoded_pattern = bytes.map { |b| SEQUENCE_ENCODING_TABLE[b] }.join('|')
|
|
573
|
+
"|%(?!#{leave_encoded_pattern}|#{leave_encoded_pattern.upcase})"
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
character_class = if leave_re
|
|
577
|
+
/[^#{character_class}]#{leave_re}/
|
|
578
|
+
else
|
|
579
|
+
/[^#{character_class}]/
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
# We can't perform regexps on invalid UTF sequences, but
|
|
583
|
+
# here we need to, so switch to ASCII.
|
|
584
|
+
component = component.dup
|
|
585
|
+
component.force_encoding(Encoding::ASCII_8BIT)
|
|
586
|
+
unencoded = self.unencode_component(component, String, leave_encoded)
|
|
471
587
|
begin
|
|
472
588
|
encoded = self.encode_component(
|
|
473
|
-
|
|
474
|
-
character_class
|
|
589
|
+
unencoded.unicode_normalize(:nfc),
|
|
590
|
+
character_class,
|
|
591
|
+
leave_encoded
|
|
475
592
|
)
|
|
476
593
|
rescue ArgumentError
|
|
477
594
|
encoded = self.encode_component(unencoded)
|
|
478
595
|
end
|
|
596
|
+
encoded.force_encoding(Encoding::UTF_8)
|
|
479
597
|
return encoded
|
|
480
598
|
end
|
|
481
599
|
|
|
@@ -512,15 +630,15 @@ module Addressable
|
|
|
512
630
|
uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
|
|
513
631
|
encoded_uri = Addressable::URI.new(
|
|
514
632
|
:scheme => self.encode_component(uri_object.scheme,
|
|
515
|
-
Addressable::URI::
|
|
633
|
+
Addressable::URI::CharacterClassesRegexps::SCHEME),
|
|
516
634
|
:authority => self.encode_component(uri_object.authority,
|
|
517
|
-
Addressable::URI::
|
|
635
|
+
Addressable::URI::CharacterClassesRegexps::AUTHORITY),
|
|
518
636
|
:path => self.encode_component(uri_object.path,
|
|
519
|
-
Addressable::URI::
|
|
637
|
+
Addressable::URI::CharacterClassesRegexps::PATH),
|
|
520
638
|
:query => self.encode_component(uri_object.query,
|
|
521
|
-
Addressable::URI::
|
|
639
|
+
Addressable::URI::CharacterClassesRegexps::QUERY),
|
|
522
640
|
:fragment => self.encode_component(uri_object.fragment,
|
|
523
|
-
Addressable::URI::
|
|
641
|
+
Addressable::URI::CharacterClassesRegexps::FRAGMENT)
|
|
524
642
|
)
|
|
525
643
|
if return_type == String
|
|
526
644
|
return encoded_uri.to_s
|
|
@@ -568,7 +686,7 @@ module Addressable
|
|
|
568
686
|
:user => self.unencode_component(uri_object.user),
|
|
569
687
|
:password => self.unencode_component(uri_object.password),
|
|
570
688
|
:host => self.unencode_component(uri_object.host),
|
|
571
|
-
:port => uri_object.port,
|
|
689
|
+
:port => (uri_object.port.nil? ? nil : uri_object.port.to_s),
|
|
572
690
|
:path => self.unencode_component(uri_object.path),
|
|
573
691
|
:query => self.unencode_component(uri_object.query),
|
|
574
692
|
:fragment => self.unencode_component(uri_object.fragment)
|
|
@@ -576,8 +694,7 @@ module Addressable
|
|
|
576
694
|
components.each do |key, value|
|
|
577
695
|
if value != nil
|
|
578
696
|
begin
|
|
579
|
-
components[key] =
|
|
580
|
-
Addressable::IDNA.unicode_normalize_kc(value.to_str)
|
|
697
|
+
components[key] = value.to_str.unicode_normalize(:nfc)
|
|
581
698
|
rescue ArgumentError
|
|
582
699
|
# Likely a malformed UTF-8 character, skip unicode normalization
|
|
583
700
|
components[key] = value.to_str
|
|
@@ -586,19 +703,19 @@ module Addressable
|
|
|
586
703
|
end
|
|
587
704
|
encoded_uri = Addressable::URI.new(
|
|
588
705
|
:scheme => self.encode_component(components[:scheme],
|
|
589
|
-
Addressable::URI::
|
|
706
|
+
Addressable::URI::CharacterClassesRegexps::SCHEME),
|
|
590
707
|
:user => self.encode_component(components[:user],
|
|
591
|
-
Addressable::URI::
|
|
708
|
+
Addressable::URI::CharacterClassesRegexps::UNRESERVED),
|
|
592
709
|
:password => self.encode_component(components[:password],
|
|
593
|
-
Addressable::URI::
|
|
710
|
+
Addressable::URI::CharacterClassesRegexps::UNRESERVED),
|
|
594
711
|
:host => components[:host],
|
|
595
712
|
:port => components[:port],
|
|
596
713
|
:path => self.encode_component(components[:path],
|
|
597
|
-
Addressable::URI::
|
|
714
|
+
Addressable::URI::CharacterClassesRegexps::PATH),
|
|
598
715
|
:query => self.encode_component(components[:query],
|
|
599
|
-
Addressable::URI::
|
|
716
|
+
Addressable::URI::CharacterClassesRegexps::QUERY),
|
|
600
717
|
:fragment => self.encode_component(components[:fragment],
|
|
601
|
-
Addressable::URI::
|
|
718
|
+
Addressable::URI::CharacterClassesRegexps::FRAGMENT)
|
|
602
719
|
)
|
|
603
720
|
if return_type == String
|
|
604
721
|
return encoded_uri.to_s
|
|
@@ -649,17 +766,17 @@ module Addressable
|
|
|
649
766
|
[
|
|
650
767
|
self.encode_component(
|
|
651
768
|
key.gsub(/(\r\n|\n|\r)/, "\r\n"),
|
|
652
|
-
|
|
769
|
+
CharacterClassesRegexps::UNRESERVED
|
|
653
770
|
).gsub("%20", "+"),
|
|
654
771
|
self.encode_component(
|
|
655
772
|
value.gsub(/(\r\n|\n|\r)/, "\r\n"),
|
|
656
|
-
|
|
773
|
+
CharacterClassesRegexps::UNRESERVED
|
|
657
774
|
).gsub("%20", "+")
|
|
658
775
|
]
|
|
659
776
|
end
|
|
660
|
-
return
|
|
777
|
+
return escaped_form_values.map do |(key, value)|
|
|
661
778
|
"#{key}=#{value}"
|
|
662
|
-
end
|
|
779
|
+
end.join("&")
|
|
663
780
|
end
|
|
664
781
|
|
|
665
782
|
##
|
|
@@ -725,7 +842,9 @@ module Addressable
|
|
|
725
842
|
end
|
|
726
843
|
end
|
|
727
844
|
|
|
728
|
-
|
|
845
|
+
reset_ivs
|
|
846
|
+
|
|
847
|
+
defer_validation do
|
|
729
848
|
# Bunch of crazy logic required because of the composite components
|
|
730
849
|
# like userinfo and authority.
|
|
731
850
|
self.scheme = options[:scheme] if options[:scheme]
|
|
@@ -740,6 +859,8 @@ module Addressable
|
|
|
740
859
|
self.query_values = options[:query_values] if options[:query_values]
|
|
741
860
|
self.fragment = options[:fragment] if options[:fragment]
|
|
742
861
|
end
|
|
862
|
+
|
|
863
|
+
to_s # force path validation
|
|
743
864
|
end
|
|
744
865
|
|
|
745
866
|
##
|
|
@@ -766,25 +887,27 @@ module Addressable
|
|
|
766
887
|
# The scheme component for this URI.
|
|
767
888
|
#
|
|
768
889
|
# @return [String] The scheme component.
|
|
769
|
-
|
|
770
|
-
return instance_variable_defined?(:@scheme) ? @scheme : nil
|
|
771
|
-
end
|
|
890
|
+
attr_reader :scheme
|
|
772
891
|
|
|
773
892
|
##
|
|
774
893
|
# The scheme component for this URI, normalized.
|
|
775
894
|
#
|
|
776
895
|
# @return [String] The scheme component, normalized.
|
|
777
896
|
def normalized_scheme
|
|
778
|
-
self.scheme
|
|
779
|
-
|
|
780
|
-
|
|
897
|
+
return nil unless self.scheme
|
|
898
|
+
if @normalized_scheme == NONE
|
|
899
|
+
@normalized_scheme = if self.scheme =~ /^\s*ssh\+svn\s*$/i
|
|
900
|
+
"svn+ssh".dup
|
|
781
901
|
else
|
|
782
902
|
Addressable::URI.normalize_component(
|
|
783
903
|
self.scheme.strip.downcase,
|
|
784
|
-
Addressable::URI::
|
|
904
|
+
Addressable::URI::NormalizeCharacterClasses::SCHEME
|
|
785
905
|
)
|
|
786
906
|
end
|
|
787
|
-
end
|
|
907
|
+
end
|
|
908
|
+
# All normalized values should be UTF-8
|
|
909
|
+
force_utf8_encoding_if_needed(@normalized_scheme)
|
|
910
|
+
@normalized_scheme
|
|
788
911
|
end
|
|
789
912
|
|
|
790
913
|
##
|
|
@@ -797,16 +920,15 @@ module Addressable
|
|
|
797
920
|
elsif new_scheme
|
|
798
921
|
new_scheme = new_scheme.to_str
|
|
799
922
|
end
|
|
800
|
-
if new_scheme && new_scheme !~
|
|
801
|
-
raise InvalidURIError, "Invalid scheme format
|
|
923
|
+
if new_scheme && new_scheme !~ /\A[a-z][a-z0-9\.\+\-]*\z/i
|
|
924
|
+
raise InvalidURIError, "Invalid scheme format: '#{new_scheme}'"
|
|
802
925
|
end
|
|
803
926
|
@scheme = new_scheme
|
|
804
927
|
@scheme = nil if @scheme.to_s.strip.empty?
|
|
805
928
|
|
|
806
|
-
# Reset
|
|
807
|
-
@normalized_scheme =
|
|
808
|
-
|
|
809
|
-
@hash = nil
|
|
929
|
+
# Reset dependent values
|
|
930
|
+
@normalized_scheme = NONE
|
|
931
|
+
remove_composite_values
|
|
810
932
|
|
|
811
933
|
# Ensure we haven't created an invalid URI
|
|
812
934
|
validate()
|
|
@@ -816,26 +938,29 @@ module Addressable
|
|
|
816
938
|
# The user component for this URI.
|
|
817
939
|
#
|
|
818
940
|
# @return [String] The user component.
|
|
819
|
-
|
|
820
|
-
return instance_variable_defined?(:@user) ? @user : nil
|
|
821
|
-
end
|
|
941
|
+
attr_reader :user
|
|
822
942
|
|
|
823
943
|
##
|
|
824
944
|
# The user component for this URI, normalized.
|
|
825
945
|
#
|
|
826
946
|
# @return [String] The user component, normalized.
|
|
827
947
|
def normalized_user
|
|
828
|
-
self.user
|
|
948
|
+
return nil unless self.user
|
|
949
|
+
return @normalized_user unless @normalized_user == NONE
|
|
950
|
+
@normalized_user = begin
|
|
829
951
|
if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
|
|
830
952
|
(!self.password || self.password.strip.empty?)
|
|
831
953
|
nil
|
|
832
954
|
else
|
|
833
955
|
Addressable::URI.normalize_component(
|
|
834
956
|
self.user.strip,
|
|
835
|
-
Addressable::URI::
|
|
957
|
+
Addressable::URI::NormalizeCharacterClasses::UNRESERVED
|
|
836
958
|
)
|
|
837
959
|
end
|
|
838
|
-
end
|
|
960
|
+
end
|
|
961
|
+
# All normalized values should be UTF-8
|
|
962
|
+
force_utf8_encoding_if_needed(@normalized_user)
|
|
963
|
+
@normalized_user
|
|
839
964
|
end
|
|
840
965
|
|
|
841
966
|
##
|
|
@@ -850,16 +975,15 @@ module Addressable
|
|
|
850
975
|
|
|
851
976
|
# You can't have a nil user with a non-nil password
|
|
852
977
|
if password != nil
|
|
853
|
-
@user = EMPTY_STR
|
|
978
|
+
@user = EMPTY_STR unless user
|
|
854
979
|
end
|
|
855
980
|
|
|
856
|
-
# Reset
|
|
981
|
+
# Reset dependent values
|
|
857
982
|
@userinfo = nil
|
|
858
|
-
@normalized_userinfo =
|
|
983
|
+
@normalized_userinfo = NONE
|
|
859
984
|
@authority = nil
|
|
860
|
-
@normalized_user =
|
|
861
|
-
|
|
862
|
-
@hash = nil
|
|
985
|
+
@normalized_user = NONE
|
|
986
|
+
remove_composite_values
|
|
863
987
|
|
|
864
988
|
# Ensure we haven't created an invalid URI
|
|
865
989
|
validate()
|
|
@@ -869,26 +993,29 @@ module Addressable
|
|
|
869
993
|
# The password component for this URI.
|
|
870
994
|
#
|
|
871
995
|
# @return [String] The password component.
|
|
872
|
-
|
|
873
|
-
return instance_variable_defined?(:@password) ? @password : nil
|
|
874
|
-
end
|
|
996
|
+
attr_reader :password
|
|
875
997
|
|
|
876
998
|
##
|
|
877
999
|
# The password component for this URI, normalized.
|
|
878
1000
|
#
|
|
879
1001
|
# @return [String] The password component, normalized.
|
|
880
1002
|
def normalized_password
|
|
881
|
-
self.password
|
|
1003
|
+
return nil unless self.password
|
|
1004
|
+
return @normalized_password unless @normalized_password == NONE
|
|
1005
|
+
@normalized_password = begin
|
|
882
1006
|
if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
|
|
883
1007
|
(!self.user || self.user.strip.empty?)
|
|
884
1008
|
nil
|
|
885
1009
|
else
|
|
886
1010
|
Addressable::URI.normalize_component(
|
|
887
1011
|
self.password.strip,
|
|
888
|
-
Addressable::URI::
|
|
1012
|
+
Addressable::URI::NormalizeCharacterClasses::UNRESERVED
|
|
889
1013
|
)
|
|
890
1014
|
end
|
|
891
|
-
end
|
|
1015
|
+
end
|
|
1016
|
+
# All normalized values should be UTF-8
|
|
1017
|
+
force_utf8_encoding_if_needed(@normalized_password)
|
|
1018
|
+
@normalized_password
|
|
892
1019
|
end
|
|
893
1020
|
|
|
894
1021
|
##
|
|
@@ -902,19 +1029,16 @@ module Addressable
|
|
|
902
1029
|
@password = new_password ? new_password.to_str : nil
|
|
903
1030
|
|
|
904
1031
|
# You can't have a nil user with a non-nil password
|
|
905
|
-
@password ||= nil
|
|
906
|
-
@user ||= nil
|
|
907
1032
|
if @password != nil
|
|
908
|
-
|
|
1033
|
+
self.user = EMPTY_STR if user.nil?
|
|
909
1034
|
end
|
|
910
1035
|
|
|
911
|
-
# Reset
|
|
1036
|
+
# Reset dependent values
|
|
912
1037
|
@userinfo = nil
|
|
913
|
-
@normalized_userinfo =
|
|
1038
|
+
@normalized_userinfo = NONE
|
|
914
1039
|
@authority = nil
|
|
915
|
-
@normalized_password =
|
|
916
|
-
|
|
917
|
-
@hash = nil
|
|
1040
|
+
@normalized_password = NONE
|
|
1041
|
+
remove_composite_values
|
|
918
1042
|
|
|
919
1043
|
# Ensure we haven't created an invalid URI
|
|
920
1044
|
validate()
|
|
@@ -928,13 +1052,13 @@ module Addressable
|
|
|
928
1052
|
def userinfo
|
|
929
1053
|
current_user = self.user
|
|
930
1054
|
current_password = self.password
|
|
931
|
-
(current_user || current_password) && @userinfo ||=
|
|
1055
|
+
(current_user || current_password) && @userinfo ||= begin
|
|
932
1056
|
if current_user && current_password
|
|
933
1057
|
"#{current_user}:#{current_password}"
|
|
934
1058
|
elsif current_user && !current_password
|
|
935
1059
|
"#{current_user}"
|
|
936
1060
|
end
|
|
937
|
-
end
|
|
1061
|
+
end
|
|
938
1062
|
end
|
|
939
1063
|
|
|
940
1064
|
##
|
|
@@ -942,17 +1066,22 @@ module Addressable
|
|
|
942
1066
|
#
|
|
943
1067
|
# @return [String] The userinfo component, normalized.
|
|
944
1068
|
def normalized_userinfo
|
|
945
|
-
self.userinfo
|
|
1069
|
+
return nil unless self.userinfo
|
|
1070
|
+
return @normalized_userinfo unless @normalized_userinfo == NONE
|
|
1071
|
+
@normalized_userinfo = begin
|
|
946
1072
|
current_user = self.normalized_user
|
|
947
1073
|
current_password = self.normalized_password
|
|
948
1074
|
if !current_user && !current_password
|
|
949
1075
|
nil
|
|
950
1076
|
elsif current_user && current_password
|
|
951
|
-
"#{current_user}:#{current_password}"
|
|
1077
|
+
"#{current_user}:#{current_password}".dup
|
|
952
1078
|
elsif current_user && !current_password
|
|
953
|
-
"#{current_user}"
|
|
1079
|
+
"#{current_user}".dup
|
|
954
1080
|
end
|
|
955
|
-
end
|
|
1081
|
+
end
|
|
1082
|
+
# All normalized values should be UTF-8
|
|
1083
|
+
force_utf8_encoding_if_needed(@normalized_userinfo)
|
|
1084
|
+
@normalized_userinfo
|
|
956
1085
|
end
|
|
957
1086
|
|
|
958
1087
|
##
|
|
@@ -976,10 +1105,9 @@ module Addressable
|
|
|
976
1105
|
self.password = new_password
|
|
977
1106
|
self.user = new_user
|
|
978
1107
|
|
|
979
|
-
# Reset
|
|
1108
|
+
# Reset dependent values
|
|
980
1109
|
@authority = nil
|
|
981
|
-
|
|
982
|
-
@hash = nil
|
|
1110
|
+
remove_composite_values
|
|
983
1111
|
|
|
984
1112
|
# Ensure we haven't created an invalid URI
|
|
985
1113
|
validate()
|
|
@@ -989,33 +1117,36 @@ module Addressable
|
|
|
989
1117
|
# The host component for this URI.
|
|
990
1118
|
#
|
|
991
1119
|
# @return [String] The host component.
|
|
992
|
-
|
|
993
|
-
return instance_variable_defined?(:@host) ? @host : nil
|
|
994
|
-
end
|
|
1120
|
+
attr_reader :host
|
|
995
1121
|
|
|
996
1122
|
##
|
|
997
1123
|
# The host component for this URI, normalized.
|
|
998
1124
|
#
|
|
999
1125
|
# @return [String] The host component, normalized.
|
|
1000
1126
|
def normalized_host
|
|
1001
|
-
self.host
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
)
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
result
|
|
1012
|
-
else
|
|
1013
|
-
EMPTY_STR
|
|
1127
|
+
return nil unless self.host
|
|
1128
|
+
|
|
1129
|
+
@normalized_host ||= begin
|
|
1130
|
+
if !self.host.strip.empty?
|
|
1131
|
+
result = ::Addressable::IDNA.to_ascii(
|
|
1132
|
+
URI.unencode_component(self.host.strip.downcase)
|
|
1133
|
+
)
|
|
1134
|
+
if result =~ /[^\.]\.$/
|
|
1135
|
+
# Single trailing dots are unnecessary.
|
|
1136
|
+
result = result[0...-1]
|
|
1014
1137
|
end
|
|
1138
|
+
result = Addressable::URI.normalize_component(
|
|
1139
|
+
result,
|
|
1140
|
+
NormalizeCharacterClasses::HOST
|
|
1141
|
+
)
|
|
1142
|
+
result
|
|
1015
1143
|
else
|
|
1016
|
-
|
|
1144
|
+
EMPTY_STR.dup
|
|
1017
1145
|
end
|
|
1018
|
-
end
|
|
1146
|
+
end
|
|
1147
|
+
# All normalized values should be UTF-8
|
|
1148
|
+
force_utf8_encoding_if_needed(@normalized_host)
|
|
1149
|
+
@normalized_host
|
|
1019
1150
|
end
|
|
1020
1151
|
|
|
1021
1152
|
##
|
|
@@ -1028,31 +1159,72 @@ module Addressable
|
|
|
1028
1159
|
end
|
|
1029
1160
|
@host = new_host ? new_host.to_str : nil
|
|
1030
1161
|
|
|
1031
|
-
|
|
1032
|
-
sub_delims = CharacterClasses::SUB_DELIMS
|
|
1033
|
-
if @host != nil && (@host =~ /[<>{}\/\?\#\@]/ ||
|
|
1034
|
-
(@host[/^\[(.*)\]$/, 1] != nil && @host[/^\[(.*)\]$/, 1] !~
|
|
1035
|
-
Regexp.new("^[#{unreserved}#{sub_delims}:]*$")))
|
|
1036
|
-
raise InvalidURIError, "Invalid character in host: '#{@host.to_s}'"
|
|
1037
|
-
end
|
|
1038
|
-
|
|
1039
|
-
# Reset dependant values
|
|
1162
|
+
# Reset dependent values
|
|
1040
1163
|
@authority = nil
|
|
1041
1164
|
@normalized_host = nil
|
|
1042
|
-
|
|
1043
|
-
@hash = nil
|
|
1165
|
+
remove_composite_values
|
|
1044
1166
|
|
|
1045
1167
|
# Ensure we haven't created an invalid URI
|
|
1046
1168
|
validate()
|
|
1047
1169
|
end
|
|
1048
1170
|
|
|
1049
1171
|
##
|
|
1172
|
+
# This method is same as URI::Generic#host except
|
|
1173
|
+
# brackets for IPv6 (and 'IPvFuture') addresses are removed.
|
|
1174
|
+
#
|
|
1050
1175
|
# @see Addressable::URI#host
|
|
1051
|
-
|
|
1176
|
+
#
|
|
1177
|
+
# @return [String] The hostname for this URI.
|
|
1178
|
+
def hostname
|
|
1179
|
+
v = self.host
|
|
1180
|
+
/\A\[(.*)\]\z/ =~ v ? $1 : v
|
|
1181
|
+
end
|
|
1052
1182
|
|
|
1053
1183
|
##
|
|
1184
|
+
# This method is same as URI::Generic#host= except
|
|
1185
|
+
# the argument can be a bare IPv6 address (or 'IPvFuture').
|
|
1186
|
+
#
|
|
1054
1187
|
# @see Addressable::URI#host=
|
|
1055
|
-
|
|
1188
|
+
#
|
|
1189
|
+
# @param [String, #to_str] new_hostname The new hostname for this URI.
|
|
1190
|
+
def hostname=(new_hostname)
|
|
1191
|
+
if new_hostname &&
|
|
1192
|
+
(new_hostname.respond_to?(:ipv4?) || new_hostname.respond_to?(:ipv6?))
|
|
1193
|
+
new_hostname = new_hostname.to_s
|
|
1194
|
+
elsif new_hostname && !new_hostname.respond_to?(:to_str)
|
|
1195
|
+
raise TypeError, "Can't convert #{new_hostname.class} into String."
|
|
1196
|
+
end
|
|
1197
|
+
v = new_hostname ? new_hostname.to_str : nil
|
|
1198
|
+
v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v
|
|
1199
|
+
self.host = v
|
|
1200
|
+
end
|
|
1201
|
+
|
|
1202
|
+
##
|
|
1203
|
+
# Returns the top-level domain for this host.
|
|
1204
|
+
#
|
|
1205
|
+
# @example
|
|
1206
|
+
# Addressable::URI.parse("http://www.example.co.uk").tld # => "co.uk"
|
|
1207
|
+
def tld
|
|
1208
|
+
PublicSuffix.parse(self.host, ignore_private: true).tld
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
##
|
|
1212
|
+
# Sets the top-level domain for this URI.
|
|
1213
|
+
#
|
|
1214
|
+
# @param [String, #to_str] new_tld The new top-level domain.
|
|
1215
|
+
def tld=(new_tld)
|
|
1216
|
+
replaced_tld = host.sub(/#{tld}\z/, new_tld)
|
|
1217
|
+
self.host = PublicSuffix::Domain.new(replaced_tld).to_s
|
|
1218
|
+
end
|
|
1219
|
+
|
|
1220
|
+
##
|
|
1221
|
+
# Returns the public suffix domain for this host.
|
|
1222
|
+
#
|
|
1223
|
+
# @example
|
|
1224
|
+
# Addressable::URI.parse("http://www.example.co.uk").domain # => "example.co.uk"
|
|
1225
|
+
def domain
|
|
1226
|
+
PublicSuffix.domain(self.host, ignore_private: true)
|
|
1227
|
+
end
|
|
1056
1228
|
|
|
1057
1229
|
##
|
|
1058
1230
|
# The authority component for this URI.
|
|
@@ -1060,8 +1232,8 @@ module Addressable
|
|
|
1060
1232
|
#
|
|
1061
1233
|
# @return [String] The authority component.
|
|
1062
1234
|
def authority
|
|
1063
|
-
self.host && @authority ||=
|
|
1064
|
-
authority =
|
|
1235
|
+
self.host && @authority ||= begin
|
|
1236
|
+
authority = String.new
|
|
1065
1237
|
if self.userinfo != nil
|
|
1066
1238
|
authority << "#{self.userinfo}@"
|
|
1067
1239
|
end
|
|
@@ -1070,7 +1242,7 @@ module Addressable
|
|
|
1070
1242
|
authority << ":#{self.port}"
|
|
1071
1243
|
end
|
|
1072
1244
|
authority
|
|
1073
|
-
end
|
|
1245
|
+
end
|
|
1074
1246
|
end
|
|
1075
1247
|
|
|
1076
1248
|
##
|
|
@@ -1078,8 +1250,9 @@ module Addressable
|
|
|
1078
1250
|
#
|
|
1079
1251
|
# @return [String] The authority component, normalized.
|
|
1080
1252
|
def normalized_authority
|
|
1081
|
-
self.authority
|
|
1082
|
-
|
|
1253
|
+
return nil unless self.authority
|
|
1254
|
+
@normalized_authority ||= begin
|
|
1255
|
+
authority = String.new
|
|
1083
1256
|
if self.normalized_userinfo != nil
|
|
1084
1257
|
authority << "#{self.normalized_userinfo}@"
|
|
1085
1258
|
end
|
|
@@ -1088,7 +1261,10 @@ module Addressable
|
|
|
1088
1261
|
authority << ":#{self.normalized_port}"
|
|
1089
1262
|
end
|
|
1090
1263
|
authority
|
|
1091
|
-
end
|
|
1264
|
+
end
|
|
1265
|
+
# All normalized values should be UTF-8
|
|
1266
|
+
force_utf8_encoding_if_needed(@normalized_authority)
|
|
1267
|
+
@normalized_authority
|
|
1092
1268
|
end
|
|
1093
1269
|
|
|
1094
1270
|
##
|
|
@@ -1106,9 +1282,9 @@ module Addressable
|
|
|
1106
1282
|
new_user = new_userinfo.strip[/^([^:]*):?/, 1]
|
|
1107
1283
|
new_password = new_userinfo.strip[/:(.*)$/, 1]
|
|
1108
1284
|
end
|
|
1109
|
-
new_host = new_authority.
|
|
1285
|
+
new_host = new_authority.sub(
|
|
1110
1286
|
/^([^\[\]]*)@/, EMPTY_STR
|
|
1111
|
-
).
|
|
1287
|
+
).sub(
|
|
1112
1288
|
/:([^:@\[\]]*?)$/, EMPTY_STR
|
|
1113
1289
|
)
|
|
1114
1290
|
new_port =
|
|
@@ -1116,16 +1292,15 @@ module Addressable
|
|
|
1116
1292
|
end
|
|
1117
1293
|
|
|
1118
1294
|
# Password assigned first to ensure validity in case of nil
|
|
1119
|
-
self.password =
|
|
1120
|
-
self.user =
|
|
1121
|
-
self.host =
|
|
1122
|
-
self.port =
|
|
1295
|
+
self.password = new_password
|
|
1296
|
+
self.user = new_user
|
|
1297
|
+
self.host = new_host
|
|
1298
|
+
self.port = new_port
|
|
1123
1299
|
|
|
1124
|
-
# Reset
|
|
1300
|
+
# Reset dependent values
|
|
1125
1301
|
@userinfo = nil
|
|
1126
|
-
@normalized_userinfo =
|
|
1127
|
-
|
|
1128
|
-
@hash = nil
|
|
1302
|
+
@normalized_userinfo = NONE
|
|
1303
|
+
remove_composite_values
|
|
1129
1304
|
|
|
1130
1305
|
# Ensure we haven't created an invalid URI
|
|
1131
1306
|
validate()
|
|
@@ -1133,22 +1308,59 @@ module Addressable
|
|
|
1133
1308
|
|
|
1134
1309
|
##
|
|
1135
1310
|
# The origin for this URI, serialized to ASCII, as per
|
|
1136
|
-
#
|
|
1311
|
+
# RFC 6454, section 6.2.
|
|
1137
1312
|
#
|
|
1138
1313
|
# @return [String] The serialized origin.
|
|
1139
1314
|
def origin
|
|
1140
|
-
|
|
1315
|
+
if self.scheme && self.authority
|
|
1141
1316
|
if self.normalized_port
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
":#{self.normalized_port}"
|
|
1145
|
-
)
|
|
1317
|
+
"#{self.normalized_scheme}://#{self.normalized_host}" +
|
|
1318
|
+
":#{self.normalized_port}"
|
|
1146
1319
|
else
|
|
1147
1320
|
"#{self.normalized_scheme}://#{self.normalized_host}"
|
|
1148
1321
|
end
|
|
1149
1322
|
else
|
|
1150
1323
|
"null"
|
|
1151
|
-
end
|
|
1324
|
+
end
|
|
1325
|
+
end
|
|
1326
|
+
|
|
1327
|
+
##
|
|
1328
|
+
# Sets the origin for this URI, serialized to ASCII, as per
|
|
1329
|
+
# RFC 6454, section 6.2. This assignment will reset the `userinfo`
|
|
1330
|
+
# component.
|
|
1331
|
+
#
|
|
1332
|
+
# @param [String, #to_str] new_origin The new origin component.
|
|
1333
|
+
def origin=(new_origin)
|
|
1334
|
+
if new_origin
|
|
1335
|
+
if !new_origin.respond_to?(:to_str)
|
|
1336
|
+
raise TypeError, "Can't convert #{new_origin.class} into String."
|
|
1337
|
+
end
|
|
1338
|
+
new_origin = new_origin.to_str
|
|
1339
|
+
new_scheme = new_origin[/^([^:\/?#]+):\/\//, 1]
|
|
1340
|
+
unless new_scheme
|
|
1341
|
+
raise InvalidURIError, 'An origin cannot omit the scheme.'
|
|
1342
|
+
end
|
|
1343
|
+
new_host = new_origin[/:\/\/([^\/?#:]+)/, 1]
|
|
1344
|
+
unless new_host
|
|
1345
|
+
raise InvalidURIError, 'An origin cannot omit the host.'
|
|
1346
|
+
end
|
|
1347
|
+
new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
|
|
1348
|
+
end
|
|
1349
|
+
|
|
1350
|
+
self.scheme = new_scheme
|
|
1351
|
+
self.host = new_host
|
|
1352
|
+
self.port = new_port
|
|
1353
|
+
self.userinfo = nil
|
|
1354
|
+
|
|
1355
|
+
# Reset dependent values
|
|
1356
|
+
@userinfo = nil
|
|
1357
|
+
@normalized_userinfo = NONE
|
|
1358
|
+
@authority = nil
|
|
1359
|
+
@normalized_authority = nil
|
|
1360
|
+
remove_composite_values
|
|
1361
|
+
|
|
1362
|
+
# Ensure we haven't created an invalid URI
|
|
1363
|
+
validate()
|
|
1152
1364
|
end
|
|
1153
1365
|
|
|
1154
1366
|
# Returns an array of known ip-based schemes. These schemes typically
|
|
@@ -1171,19 +1383,21 @@ module Addressable
|
|
|
1171
1383
|
# infer port numbers from default values.
|
|
1172
1384
|
#
|
|
1173
1385
|
# @return [Integer] The port component.
|
|
1174
|
-
|
|
1175
|
-
return instance_variable_defined?(:@port) ? @port : nil
|
|
1176
|
-
end
|
|
1386
|
+
attr_reader :port
|
|
1177
1387
|
|
|
1178
1388
|
##
|
|
1179
1389
|
# The port component for this URI, normalized.
|
|
1180
1390
|
#
|
|
1181
1391
|
# @return [Integer] The port component, normalized.
|
|
1182
1392
|
def normalized_port
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
self.port
|
|
1393
|
+
return nil unless self.port
|
|
1394
|
+
return @normalized_port unless @normalized_port == NONE
|
|
1395
|
+
@normalized_port = begin
|
|
1396
|
+
if URI.port_mapping[self.normalized_scheme] == self.port
|
|
1397
|
+
nil
|
|
1398
|
+
else
|
|
1399
|
+
self.port
|
|
1400
|
+
end
|
|
1187
1401
|
end
|
|
1188
1402
|
end
|
|
1189
1403
|
|
|
@@ -1195,6 +1409,11 @@ module Addressable
|
|
|
1195
1409
|
if new_port != nil && new_port.respond_to?(:to_str)
|
|
1196
1410
|
new_port = Addressable::URI.unencode_component(new_port.to_str)
|
|
1197
1411
|
end
|
|
1412
|
+
|
|
1413
|
+
if new_port.respond_to?(:valid_encoding?) && !new_port.valid_encoding?
|
|
1414
|
+
raise InvalidURIError, "Invalid encoding in port"
|
|
1415
|
+
end
|
|
1416
|
+
|
|
1198
1417
|
if new_port != nil && !(new_port.to_s =~ /^\d+$/)
|
|
1199
1418
|
raise InvalidURIError,
|
|
1200
1419
|
"Invalid port number: #{new_port.inspect}"
|
|
@@ -1203,11 +1422,10 @@ module Addressable
|
|
|
1203
1422
|
@port = new_port.to_s.to_i
|
|
1204
1423
|
@port = nil if @port == 0
|
|
1205
1424
|
|
|
1206
|
-
# Reset
|
|
1425
|
+
# Reset dependent values
|
|
1207
1426
|
@authority = nil
|
|
1208
|
-
@normalized_port =
|
|
1209
|
-
|
|
1210
|
-
@hash = nil
|
|
1427
|
+
@normalized_port = NONE
|
|
1428
|
+
remove_composite_values
|
|
1211
1429
|
|
|
1212
1430
|
# Ensure we haven't created an invalid URI
|
|
1213
1431
|
validate()
|
|
@@ -1247,12 +1465,12 @@ module Addressable
|
|
|
1247
1465
|
#
|
|
1248
1466
|
# @return [String] The components that identify a site.
|
|
1249
1467
|
def site
|
|
1250
|
-
(self.scheme || self.authority) && @site ||=
|
|
1251
|
-
site_string = ""
|
|
1468
|
+
(self.scheme || self.authority) && @site ||= begin
|
|
1469
|
+
site_string = "".dup
|
|
1252
1470
|
site_string << "#{self.scheme}:" if self.scheme != nil
|
|
1253
1471
|
site_string << "//#{self.authority}" if self.authority != nil
|
|
1254
1472
|
site_string
|
|
1255
|
-
end
|
|
1473
|
+
end
|
|
1256
1474
|
end
|
|
1257
1475
|
|
|
1258
1476
|
##
|
|
@@ -1265,8 +1483,9 @@ module Addressable
|
|
|
1265
1483
|
#
|
|
1266
1484
|
# @return [String] The normalized components that identify a site.
|
|
1267
1485
|
def normalized_site
|
|
1268
|
-
self.site
|
|
1269
|
-
|
|
1486
|
+
return nil unless self.site
|
|
1487
|
+
@normalized_site ||= begin
|
|
1488
|
+
site_string = "".dup
|
|
1270
1489
|
if self.normalized_scheme != nil
|
|
1271
1490
|
site_string << "#{self.normalized_scheme}:"
|
|
1272
1491
|
end
|
|
@@ -1274,7 +1493,10 @@ module Addressable
|
|
|
1274
1493
|
site_string << "//#{self.normalized_authority}"
|
|
1275
1494
|
end
|
|
1276
1495
|
site_string
|
|
1277
|
-
end
|
|
1496
|
+
end
|
|
1497
|
+
# All normalized values should be UTF-8
|
|
1498
|
+
force_utf8_encoding_if_needed(@normalized_site)
|
|
1499
|
+
@normalized_site
|
|
1278
1500
|
end
|
|
1279
1501
|
|
|
1280
1502
|
##
|
|
@@ -1303,9 +1525,7 @@ module Addressable
|
|
|
1303
1525
|
# The path component for this URI.
|
|
1304
1526
|
#
|
|
1305
1527
|
# @return [String] The path component.
|
|
1306
|
-
|
|
1307
|
-
return instance_variable_defined?(:@path) ? @path : EMPTY_STR
|
|
1308
|
-
end
|
|
1528
|
+
attr_reader :path
|
|
1309
1529
|
|
|
1310
1530
|
NORMPATH = /^(?!\/)[^\/:]*:.*$/
|
|
1311
1531
|
##
|
|
@@ -1313,28 +1533,31 @@ module Addressable
|
|
|
1313
1533
|
#
|
|
1314
1534
|
# @return [String] The path component, normalized.
|
|
1315
1535
|
def normalized_path
|
|
1316
|
-
@normalized_path ||=
|
|
1536
|
+
@normalized_path ||= begin
|
|
1317
1537
|
path = self.path.to_s
|
|
1318
1538
|
if self.scheme == nil && path =~ NORMPATH
|
|
1319
1539
|
# Relative paths with colons in the first segment are ambiguous.
|
|
1320
1540
|
path = path.sub(":", "%2F")
|
|
1321
1541
|
end
|
|
1322
|
-
# String#split(
|
|
1542
|
+
# String#split(delimiter, -1) uses the more strict splitting behavior
|
|
1323
1543
|
# found by default in Python.
|
|
1324
|
-
result =
|
|
1544
|
+
result = path.strip.split(SLASH, -1).map do |segment|
|
|
1325
1545
|
Addressable::URI.normalize_component(
|
|
1326
1546
|
segment,
|
|
1327
|
-
Addressable::URI::
|
|
1547
|
+
Addressable::URI::NormalizeCharacterClasses::PCHAR
|
|
1328
1548
|
)
|
|
1329
|
-
end
|
|
1549
|
+
end.join(SLASH)
|
|
1330
1550
|
|
|
1331
1551
|
result = URI.normalize_path(result)
|
|
1332
1552
|
if result.empty? &&
|
|
1333
1553
|
["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
|
|
1334
|
-
result = SLASH
|
|
1554
|
+
result = SLASH.dup
|
|
1335
1555
|
end
|
|
1336
1556
|
result
|
|
1337
|
-
end
|
|
1557
|
+
end
|
|
1558
|
+
# All normalized values should be UTF-8
|
|
1559
|
+
force_utf8_encoding_if_needed(@normalized_path)
|
|
1560
|
+
@normalized_path
|
|
1338
1561
|
end
|
|
1339
1562
|
|
|
1340
1563
|
##
|
|
@@ -1350,10 +1573,12 @@ module Addressable
|
|
|
1350
1573
|
@path = "/#{@path}"
|
|
1351
1574
|
end
|
|
1352
1575
|
|
|
1353
|
-
# Reset
|
|
1576
|
+
# Reset dependent values
|
|
1354
1577
|
@normalized_path = nil
|
|
1355
|
-
|
|
1356
|
-
|
|
1578
|
+
remove_composite_values
|
|
1579
|
+
|
|
1580
|
+
# Ensure we haven't created an invalid URI
|
|
1581
|
+
validate()
|
|
1357
1582
|
end
|
|
1358
1583
|
|
|
1359
1584
|
##
|
|
@@ -1362,7 +1587,7 @@ module Addressable
|
|
|
1362
1587
|
# @return [String] The path's basename.
|
|
1363
1588
|
def basename
|
|
1364
1589
|
# Path cannot be nil
|
|
1365
|
-
return File.basename(self.path).
|
|
1590
|
+
return File.basename(self.path).sub(/;[^\/]*$/, EMPTY_STR)
|
|
1366
1591
|
end
|
|
1367
1592
|
|
|
1368
1593
|
##
|
|
@@ -1379,23 +1604,34 @@ module Addressable
|
|
|
1379
1604
|
# The query component for this URI.
|
|
1380
1605
|
#
|
|
1381
1606
|
# @return [String] The query component.
|
|
1382
|
-
|
|
1383
|
-
return instance_variable_defined?(:@query) ? @query : nil
|
|
1384
|
-
end
|
|
1607
|
+
attr_reader :query
|
|
1385
1608
|
|
|
1386
1609
|
##
|
|
1387
1610
|
# The query component for this URI, normalized.
|
|
1388
1611
|
#
|
|
1389
1612
|
# @return [String] The query component, normalized.
|
|
1390
|
-
def normalized_query
|
|
1391
|
-
self.query
|
|
1392
|
-
|
|
1613
|
+
def normalized_query(*flags)
|
|
1614
|
+
return nil unless self.query
|
|
1615
|
+
return @normalized_query unless @normalized_query == NONE
|
|
1616
|
+
@normalized_query = begin
|
|
1617
|
+
modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
|
|
1618
|
+
# Make sure possible key-value pair delimiters are escaped.
|
|
1619
|
+
modified_query_class.sub!("\\&", "").sub!("\\;", "")
|
|
1620
|
+
pairs = (query || "").split("&", -1)
|
|
1621
|
+
pairs.delete_if(&:empty?).uniq! if flags.include?(:compacted)
|
|
1622
|
+
pairs.sort! if flags.include?(:sorted)
|
|
1623
|
+
component = pairs.map do |pair|
|
|
1393
1624
|
Addressable::URI.normalize_component(
|
|
1394
1625
|
pair,
|
|
1395
|
-
Addressable::URI::
|
|
1626
|
+
Addressable::URI::NormalizeCharacterClasses::QUERY,
|
|
1627
|
+
"+"
|
|
1396
1628
|
)
|
|
1397
|
-
end
|
|
1398
|
-
|
|
1629
|
+
end.join("&")
|
|
1630
|
+
component == "" ? nil : component
|
|
1631
|
+
end
|
|
1632
|
+
# All normalized values should be UTF-8
|
|
1633
|
+
force_utf8_encoding_if_needed(@normalized_query)
|
|
1634
|
+
@normalized_query
|
|
1399
1635
|
end
|
|
1400
1636
|
|
|
1401
1637
|
##
|
|
@@ -1408,10 +1644,9 @@ module Addressable
|
|
|
1408
1644
|
end
|
|
1409
1645
|
@query = new_query ? new_query.to_str : nil
|
|
1410
1646
|
|
|
1411
|
-
# Reset
|
|
1412
|
-
@normalized_query =
|
|
1413
|
-
|
|
1414
|
-
@hash = nil
|
|
1647
|
+
# Reset dependent values
|
|
1648
|
+
@normalized_query = NONE
|
|
1649
|
+
remove_composite_values
|
|
1415
1650
|
end
|
|
1416
1651
|
|
|
1417
1652
|
##
|
|
@@ -1420,7 +1655,8 @@ module Addressable
|
|
|
1420
1655
|
# @param [Class] return_type The return type desired. Value must be either
|
|
1421
1656
|
# `Hash` or `Array`.
|
|
1422
1657
|
#
|
|
1423
|
-
# @return [Hash, Array] The query string parsed as a Hash or Array
|
|
1658
|
+
# @return [Hash, Array, nil] The query string parsed as a Hash or Array
|
|
1659
|
+
# or nil if the query string is blank.
|
|
1424
1660
|
#
|
|
1425
1661
|
# @example
|
|
1426
1662
|
# Addressable::URI.parse("?one=1&two=2&three=3").query_values
|
|
@@ -1429,31 +1665,32 @@ module Addressable
|
|
|
1429
1665
|
# #=> [["one", "two"], ["one", "three"]]
|
|
1430
1666
|
# Addressable::URI.parse("?one=two&one=three").query_values(Hash)
|
|
1431
1667
|
# #=> {"one" => "three"}
|
|
1668
|
+
# Addressable::URI.parse("?").query_values
|
|
1669
|
+
# #=> {}
|
|
1670
|
+
# Addressable::URI.parse("").query_values
|
|
1671
|
+
# #=> nil
|
|
1432
1672
|
def query_values(return_type=Hash)
|
|
1433
1673
|
empty_accumulator = Array == return_type ? [] : {}
|
|
1434
1674
|
if return_type != Hash && return_type != Array
|
|
1435
1675
|
raise ArgumentError, "Invalid return type. Must be Hash or Array."
|
|
1436
1676
|
end
|
|
1437
1677
|
return nil if self.query == nil
|
|
1438
|
-
split_query =
|
|
1678
|
+
split_query = self.query.split("&").map do |pair|
|
|
1439
1679
|
pair.split("=", 2) if pair && !pair.empty?
|
|
1440
|
-
end
|
|
1680
|
+
end.compact
|
|
1441
1681
|
return split_query.inject(empty_accumulator.dup) do |accu, pair|
|
|
1442
1682
|
# I'd rather use key/value identifiers instead of array lookups,
|
|
1443
1683
|
# but in this case I really want to maintain the exact pair structure,
|
|
1444
1684
|
# so it's best to make all changes in-place.
|
|
1445
1685
|
pair[0] = URI.unencode_component(pair[0])
|
|
1446
|
-
# This looks weird, but it's correct. Handles query values like:
|
|
1447
|
-
# ?data=1&flag&color=blue
|
|
1448
|
-
# In this case, `flag` would evaluate to `true`, which is what you
|
|
1449
|
-
# want. Its absence implies that `flag` resolves to `false`.
|
|
1450
|
-
# value = true if value.nil?
|
|
1451
1686
|
if pair[1].respond_to?(:to_str)
|
|
1687
|
+
value = pair[1].to_str
|
|
1452
1688
|
# I loathe the fact that I have to do this. Stupid HTML 4.01.
|
|
1453
1689
|
# Treating '+' as a space was just an unbelievably bad idea.
|
|
1454
1690
|
# There was nothing wrong with '%20'!
|
|
1455
1691
|
# If it ain't broke, don't fix it!
|
|
1456
|
-
|
|
1692
|
+
value = value.tr("+", " ") if ["http", "https", nil].include?(scheme)
|
|
1693
|
+
pair[1] = URI.unencode_component(value)
|
|
1457
1694
|
end
|
|
1458
1695
|
if return_type == Hash
|
|
1459
1696
|
accu[pair[0]] = pair[1]
|
|
@@ -1505,23 +1742,23 @@ module Addressable
|
|
|
1505
1742
|
end
|
|
1506
1743
|
|
|
1507
1744
|
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
|
|
1508
|
-
buffer = ""
|
|
1745
|
+
buffer = "".dup
|
|
1509
1746
|
new_query_values.each do |key, value|
|
|
1510
1747
|
encoded_key = URI.encode_component(
|
|
1511
|
-
key,
|
|
1748
|
+
key, CharacterClassesRegexps::UNRESERVED
|
|
1512
1749
|
)
|
|
1513
|
-
if value == nil
|
|
1750
|
+
if value == nil
|
|
1514
1751
|
buffer << "#{encoded_key}&"
|
|
1515
1752
|
elsif value.kind_of?(Array)
|
|
1516
1753
|
value.each do |sub_value|
|
|
1517
1754
|
encoded_value = URI.encode_component(
|
|
1518
|
-
sub_value,
|
|
1755
|
+
sub_value, CharacterClassesRegexps::UNRESERVED
|
|
1519
1756
|
)
|
|
1520
1757
|
buffer << "#{encoded_key}=#{encoded_value}&"
|
|
1521
1758
|
end
|
|
1522
1759
|
else
|
|
1523
1760
|
encoded_value = URI.encode_component(
|
|
1524
|
-
value,
|
|
1761
|
+
value, CharacterClassesRegexps::UNRESERVED
|
|
1525
1762
|
)
|
|
1526
1763
|
buffer << "#{encoded_key}=#{encoded_value}&"
|
|
1527
1764
|
end
|
|
@@ -1535,7 +1772,7 @@ module Addressable
|
|
|
1535
1772
|
#
|
|
1536
1773
|
# @return [String] The request URI required for an HTTP request.
|
|
1537
1774
|
def request_uri
|
|
1538
|
-
return nil if self.absolute? && self.scheme !~ /^https?$/
|
|
1775
|
+
return nil if self.absolute? && self.scheme !~ /^https?$/i
|
|
1539
1776
|
return (
|
|
1540
1777
|
(!self.path.empty? ? self.path : SLASH) +
|
|
1541
1778
|
(self.query ? "?#{self.query}" : EMPTY_STR)
|
|
@@ -1550,42 +1787,45 @@ module Addressable
|
|
|
1550
1787
|
if !new_request_uri.respond_to?(:to_str)
|
|
1551
1788
|
raise TypeError, "Can't convert #{new_request_uri.class} into String."
|
|
1552
1789
|
end
|
|
1553
|
-
if self.absolute? && self.scheme !~ /^https?$/
|
|
1790
|
+
if self.absolute? && self.scheme !~ /^https?$/i
|
|
1554
1791
|
raise InvalidURIError,
|
|
1555
1792
|
"Cannot set an HTTP request URI for a non-HTTP URI."
|
|
1556
1793
|
end
|
|
1557
1794
|
new_request_uri = new_request_uri.to_str
|
|
1558
|
-
path_component = new_request_uri[/^([^\?]*)
|
|
1795
|
+
path_component = new_request_uri[/^([^\?]*)\??(?:.*)$/, 1]
|
|
1559
1796
|
query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1]
|
|
1560
1797
|
path_component = path_component.to_s
|
|
1561
1798
|
path_component = (!path_component.empty? ? path_component : SLASH)
|
|
1562
1799
|
self.path = path_component
|
|
1563
1800
|
self.query = query_component
|
|
1564
1801
|
|
|
1565
|
-
# Reset
|
|
1566
|
-
|
|
1567
|
-
@hash = nil
|
|
1802
|
+
# Reset dependent values
|
|
1803
|
+
remove_composite_values
|
|
1568
1804
|
end
|
|
1569
1805
|
|
|
1570
1806
|
##
|
|
1571
1807
|
# The fragment component for this URI.
|
|
1572
1808
|
#
|
|
1573
1809
|
# @return [String] The fragment component.
|
|
1574
|
-
|
|
1575
|
-
return instance_variable_defined?(:@fragment) ? @fragment : nil
|
|
1576
|
-
end
|
|
1810
|
+
attr_reader :fragment
|
|
1577
1811
|
|
|
1578
1812
|
##
|
|
1579
1813
|
# The fragment component for this URI, normalized.
|
|
1580
1814
|
#
|
|
1581
1815
|
# @return [String] The fragment component, normalized.
|
|
1582
1816
|
def normalized_fragment
|
|
1583
|
-
self.fragment
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1817
|
+
return nil unless self.fragment
|
|
1818
|
+
return @normalized_fragment unless @normalized_fragment == NONE
|
|
1819
|
+
@normalized_fragment = begin
|
|
1820
|
+
component = Addressable::URI.normalize_component(
|
|
1821
|
+
self.fragment,
|
|
1822
|
+
Addressable::URI::NormalizeCharacterClasses::FRAGMENT
|
|
1587
1823
|
)
|
|
1588
|
-
|
|
1824
|
+
component == "" ? nil : component
|
|
1825
|
+
end
|
|
1826
|
+
# All normalized values should be UTF-8
|
|
1827
|
+
force_utf8_encoding_if_needed(@normalized_fragment)
|
|
1828
|
+
@normalized_fragment
|
|
1589
1829
|
end
|
|
1590
1830
|
|
|
1591
1831
|
##
|
|
@@ -1598,10 +1838,9 @@ module Addressable
|
|
|
1598
1838
|
end
|
|
1599
1839
|
@fragment = new_fragment ? new_fragment.to_str : nil
|
|
1600
1840
|
|
|
1601
|
-
# Reset
|
|
1602
|
-
@normalized_fragment =
|
|
1603
|
-
|
|
1604
|
-
@hash = nil
|
|
1841
|
+
# Reset dependent values
|
|
1842
|
+
@normalized_fragment = NONE
|
|
1843
|
+
remove_composite_values
|
|
1605
1844
|
|
|
1606
1845
|
# Ensure we haven't created an invalid URI
|
|
1607
1846
|
validate()
|
|
@@ -1704,8 +1943,8 @@ module Addressable
|
|
|
1704
1943
|
# Section 5.2.3 of RFC 3986
|
|
1705
1944
|
#
|
|
1706
1945
|
# Removes the right-most path segment from the base path.
|
|
1707
|
-
if base_path
|
|
1708
|
-
base_path.
|
|
1946
|
+
if base_path.include?(SLASH)
|
|
1947
|
+
base_path.sub!(/\/[^\/]+$/, SLASH)
|
|
1709
1948
|
else
|
|
1710
1949
|
base_path = EMPTY_STR
|
|
1711
1950
|
end
|
|
@@ -1729,7 +1968,7 @@ module Addressable
|
|
|
1729
1968
|
end
|
|
1730
1969
|
joined_fragment = uri.fragment
|
|
1731
1970
|
|
|
1732
|
-
return
|
|
1971
|
+
return self.class.new(
|
|
1733
1972
|
:scheme => joined_scheme,
|
|
1734
1973
|
:user => joined_user,
|
|
1735
1974
|
:password => joined_password,
|
|
@@ -1766,7 +2005,7 @@ module Addressable
|
|
|
1766
2005
|
#
|
|
1767
2006
|
# @see Hash#merge
|
|
1768
2007
|
def merge(hash)
|
|
1769
|
-
|
|
2008
|
+
unless hash.respond_to?(:to_hash)
|
|
1770
2009
|
raise TypeError, "Can't convert #{hash.class} into Hash."
|
|
1771
2010
|
end
|
|
1772
2011
|
hash = hash.to_hash
|
|
@@ -1785,7 +2024,7 @@ module Addressable
|
|
|
1785
2024
|
end
|
|
1786
2025
|
end
|
|
1787
2026
|
|
|
1788
|
-
uri =
|
|
2027
|
+
uri = self.class.new
|
|
1789
2028
|
uri.defer_validation do
|
|
1790
2029
|
# Bunch of crazy logic required because of the composite components
|
|
1791
2030
|
# like userinfo and authority.
|
|
@@ -1869,9 +2108,16 @@ module Addressable
|
|
|
1869
2108
|
components[:query] = nil
|
|
1870
2109
|
end
|
|
1871
2110
|
else
|
|
1872
|
-
if uri.path != SLASH
|
|
1873
|
-
components[:path]
|
|
1874
|
-
|
|
2111
|
+
if uri.path != SLASH and components[:path]
|
|
2112
|
+
self_splitted_path = split_path(components[:path])
|
|
2113
|
+
uri_splitted_path = split_path(uri.path)
|
|
2114
|
+
self_dir = self_splitted_path.shift
|
|
2115
|
+
uri_dir = uri_splitted_path.shift
|
|
2116
|
+
while !self_splitted_path.empty? && !uri_splitted_path.empty? and self_dir == uri_dir
|
|
2117
|
+
self_dir = self_splitted_path.shift
|
|
2118
|
+
uri_dir = uri_splitted_path.shift
|
|
2119
|
+
end
|
|
2120
|
+
components[:path] = (uri_splitted_path.fill('..') + [self_dir] + self_splitted_path).join(SLASH)
|
|
1875
2121
|
end
|
|
1876
2122
|
end
|
|
1877
2123
|
end
|
|
@@ -1926,7 +2172,7 @@ module Addressable
|
|
|
1926
2172
|
end
|
|
1927
2173
|
end
|
|
1928
2174
|
|
|
1929
|
-
return
|
|
2175
|
+
return self.class.new(
|
|
1930
2176
|
:scheme => normalized_scheme,
|
|
1931
2177
|
:authority => normalized_authority,
|
|
1932
2178
|
:path => normalized_path,
|
|
@@ -2015,7 +2261,7 @@ module Addressable
|
|
|
2015
2261
|
#
|
|
2016
2262
|
# @return [Integer] A hash of the URI.
|
|
2017
2263
|
def hash
|
|
2018
|
-
|
|
2264
|
+
@hash ||= self.to_s.hash * -1
|
|
2019
2265
|
end
|
|
2020
2266
|
|
|
2021
2267
|
##
|
|
@@ -2023,7 +2269,7 @@ module Addressable
|
|
|
2023
2269
|
#
|
|
2024
2270
|
# @return [Addressable::URI] The cloned URI.
|
|
2025
2271
|
def dup
|
|
2026
|
-
duplicated_uri =
|
|
2272
|
+
duplicated_uri = self.class.new(
|
|
2027
2273
|
:scheme => self.scheme ? self.scheme.dup : nil,
|
|
2028
2274
|
:user => self.user ? self.user.dup : nil,
|
|
2029
2275
|
:password => self.password ? self.password.dup : nil,
|
|
@@ -2079,6 +2325,15 @@ module Addressable
|
|
|
2079
2325
|
replace_self(self.omit(*components))
|
|
2080
2326
|
end
|
|
2081
2327
|
|
|
2328
|
+
##
|
|
2329
|
+
# Determines if the URI is an empty string.
|
|
2330
|
+
#
|
|
2331
|
+
# @return [TrueClass, FalseClass]
|
|
2332
|
+
# Returns <code>true</code> if empty, <code>false</code> otherwise.
|
|
2333
|
+
def empty?
|
|
2334
|
+
return self.to_s.empty?
|
|
2335
|
+
end
|
|
2336
|
+
|
|
2082
2337
|
##
|
|
2083
2338
|
# Converts the URI to a <code>String</code>.
|
|
2084
2339
|
#
|
|
@@ -2089,18 +2344,16 @@ module Addressable
|
|
|
2089
2344
|
raise InvalidURIError,
|
|
2090
2345
|
"Cannot assemble URI string with ambiguous path: '#{self.path}'"
|
|
2091
2346
|
end
|
|
2092
|
-
@uri_string ||=
|
|
2093
|
-
uri_string =
|
|
2347
|
+
@uri_string ||= begin
|
|
2348
|
+
uri_string = String.new
|
|
2094
2349
|
uri_string << "#{self.scheme}:" if self.scheme != nil
|
|
2095
2350
|
uri_string << "//#{self.authority}" if self.authority != nil
|
|
2096
2351
|
uri_string << self.path.to_s
|
|
2097
2352
|
uri_string << "?#{self.query}" if self.query != nil
|
|
2098
2353
|
uri_string << "##{self.fragment}" if self.fragment != nil
|
|
2099
|
-
|
|
2100
|
-
uri_string.force_encoding(Encoding::UTF_8)
|
|
2101
|
-
end
|
|
2354
|
+
uri_string.force_encoding(Encoding::UTF_8)
|
|
2102
2355
|
uri_string
|
|
2103
|
-
end
|
|
2356
|
+
end
|
|
2104
2357
|
end
|
|
2105
2358
|
|
|
2106
2359
|
##
|
|
@@ -2129,7 +2382,7 @@ module Addressable
|
|
|
2129
2382
|
#
|
|
2130
2383
|
# @return [String] The URI object's state, as a <code>String</code>.
|
|
2131
2384
|
def inspect
|
|
2132
|
-
sprintf("#<%s:%#0x URI:%s>",
|
|
2385
|
+
sprintf("#<%s:%#0x URI:%s>", self.class.to_s, self.object_id, self.to_s)
|
|
2133
2386
|
end
|
|
2134
2387
|
|
|
2135
2388
|
##
|
|
@@ -2140,16 +2393,36 @@ module Addressable
|
|
|
2140
2393
|
#
|
|
2141
2394
|
# @param [Proc] block
|
|
2142
2395
|
# A set of operations to perform on a given URI.
|
|
2143
|
-
def defer_validation
|
|
2144
|
-
raise LocalJumpError, "No block given." unless
|
|
2396
|
+
def defer_validation
|
|
2397
|
+
raise LocalJumpError, "No block given." unless block_given?
|
|
2145
2398
|
@validation_deferred = true
|
|
2146
|
-
|
|
2399
|
+
yield
|
|
2147
2400
|
@validation_deferred = false
|
|
2148
2401
|
validate
|
|
2149
|
-
|
|
2402
|
+
ensure
|
|
2403
|
+
@validation_deferred = false
|
|
2150
2404
|
end
|
|
2151
2405
|
|
|
2152
|
-
|
|
2406
|
+
def encode_with(coder)
|
|
2407
|
+
instance_variables.each do |ivar|
|
|
2408
|
+
value = instance_variable_get(ivar)
|
|
2409
|
+
if value != NONE
|
|
2410
|
+
key = ivar.to_s.slice(1..-1)
|
|
2411
|
+
coder[key] = value
|
|
2412
|
+
end
|
|
2413
|
+
end
|
|
2414
|
+
nil
|
|
2415
|
+
end
|
|
2416
|
+
|
|
2417
|
+
def init_with(coder)
|
|
2418
|
+
reset_ivs
|
|
2419
|
+
coder.map.each do |key, value|
|
|
2420
|
+
instance_variable_set("@#{key}", value)
|
|
2421
|
+
end
|
|
2422
|
+
nil
|
|
2423
|
+
end
|
|
2424
|
+
|
|
2425
|
+
protected
|
|
2153
2426
|
SELF_REF = '.'
|
|
2154
2427
|
PARENT = '..'
|
|
2155
2428
|
|
|
@@ -2167,37 +2440,42 @@ module Addressable
|
|
|
2167
2440
|
def self.normalize_path(path)
|
|
2168
2441
|
# Section 5.2.4 of RFC 3986
|
|
2169
2442
|
|
|
2170
|
-
return
|
|
2443
|
+
return if path.nil?
|
|
2171
2444
|
normalized_path = path.dup
|
|
2172
|
-
|
|
2173
|
-
mod = nil
|
|
2445
|
+
loop do
|
|
2174
2446
|
mod ||= normalized_path.gsub!(RULE_2A, SLASH)
|
|
2175
2447
|
|
|
2176
2448
|
pair = normalized_path.match(RULE_2B_2C)
|
|
2177
|
-
|
|
2449
|
+
if pair
|
|
2450
|
+
parent = pair[1]
|
|
2451
|
+
current = pair[2]
|
|
2452
|
+
else
|
|
2453
|
+
parent = nil
|
|
2454
|
+
current = nil
|
|
2455
|
+
end
|
|
2456
|
+
|
|
2457
|
+
regexp = "/#{Regexp.escape(parent.to_s)}/\\.\\./|"
|
|
2458
|
+
regexp += "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
|
|
2459
|
+
|
|
2178
2460
|
if pair && ((parent != SELF_REF && parent != PARENT) ||
|
|
2179
2461
|
(current != SELF_REF && current != PARENT))
|
|
2180
|
-
mod ||= normalized_path.gsub!(
|
|
2181
|
-
Regexp.new(
|
|
2182
|
-
"/#{Regexp.escape(parent.to_s)}/\\.\\./|" +
|
|
2183
|
-
"(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
|
|
2184
|
-
), SLASH
|
|
2185
|
-
)
|
|
2462
|
+
mod ||= normalized_path.gsub!(Regexp.new(regexp), SLASH)
|
|
2186
2463
|
end
|
|
2187
2464
|
|
|
2188
2465
|
mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
|
|
2189
2466
|
# Non-standard, removes prefixed dotted segments from path.
|
|
2190
2467
|
mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
|
|
2191
|
-
|
|
2468
|
+
break if mod.nil?
|
|
2469
|
+
end
|
|
2192
2470
|
|
|
2193
|
-
|
|
2471
|
+
normalized_path
|
|
2194
2472
|
end
|
|
2195
2473
|
|
|
2196
2474
|
##
|
|
2197
2475
|
# Ensures that the URI is valid.
|
|
2198
2476
|
def validate
|
|
2199
2477
|
return if !!@validation_deferred
|
|
2200
|
-
if self.scheme != nil &&
|
|
2478
|
+
if self.scheme != nil && self.ip_based? &&
|
|
2201
2479
|
(self.host == nil || self.host.empty?) &&
|
|
2202
2480
|
(self.path == nil || self.path.empty?)
|
|
2203
2481
|
raise InvalidURIError,
|
|
@@ -2215,6 +2493,19 @@ module Addressable
|
|
|
2215
2493
|
raise InvalidURIError,
|
|
2216
2494
|
"Cannot have a relative path with an authority set: '#{self.to_s}'"
|
|
2217
2495
|
end
|
|
2496
|
+
if self.path != nil && !self.path.empty? &&
|
|
2497
|
+
self.path[0..1] == SLASH + SLASH && self.authority == nil
|
|
2498
|
+
raise InvalidURIError,
|
|
2499
|
+
"Cannot have a path with two leading slashes " +
|
|
2500
|
+
"without an authority set: '#{self.to_s}'"
|
|
2501
|
+
end
|
|
2502
|
+
unreserved = CharacterClasses::UNRESERVED
|
|
2503
|
+
sub_delims = CharacterClasses::SUB_DELIMS
|
|
2504
|
+
if !self.host.nil? && (self.host =~ /[<>{}\/\\\?\#\@"[[:space:]]]/ ||
|
|
2505
|
+
(self.host[/^\[(.*)\]$/, 1] != nil && self.host[/^\[(.*)\]$/, 1] !~
|
|
2506
|
+
Regexp.new("^[#{unreserved}#{sub_delims}:]*$")))
|
|
2507
|
+
raise InvalidURIError, "Invalid character in host: '#{self.host.to_s}'"
|
|
2508
|
+
end
|
|
2218
2509
|
return nil
|
|
2219
2510
|
end
|
|
2220
2511
|
|
|
@@ -2226,10 +2517,8 @@ module Addressable
|
|
|
2226
2517
|
#
|
|
2227
2518
|
# @return [Addressable::URI] <code>self</code>.
|
|
2228
2519
|
def replace_self(uri)
|
|
2229
|
-
# Reset
|
|
2230
|
-
|
|
2231
|
-
instance_variable_set(var, nil)
|
|
2232
|
-
end
|
|
2520
|
+
# Reset dependent values
|
|
2521
|
+
reset_ivs
|
|
2233
2522
|
|
|
2234
2523
|
@scheme = uri.scheme
|
|
2235
2524
|
@user = uri.user
|
|
@@ -2241,5 +2530,73 @@ module Addressable
|
|
|
2241
2530
|
@fragment = uri.fragment
|
|
2242
2531
|
return self
|
|
2243
2532
|
end
|
|
2533
|
+
|
|
2534
|
+
##
|
|
2535
|
+
# Splits path string with "/" (slash).
|
|
2536
|
+
# It is considered that there is empty string after last slash when
|
|
2537
|
+
# path ends with slash.
|
|
2538
|
+
#
|
|
2539
|
+
# @param [String] path The path to split.
|
|
2540
|
+
#
|
|
2541
|
+
# @return [Array<String>] An array of parts of path.
|
|
2542
|
+
def split_path(path)
|
|
2543
|
+
splitted = path.split(SLASH)
|
|
2544
|
+
splitted << EMPTY_STR if path.end_with? SLASH
|
|
2545
|
+
splitted
|
|
2546
|
+
end
|
|
2547
|
+
|
|
2548
|
+
##
|
|
2549
|
+
# Resets composite values for the entire URI
|
|
2550
|
+
#
|
|
2551
|
+
# @api private
|
|
2552
|
+
def remove_composite_values
|
|
2553
|
+
@uri_string = nil
|
|
2554
|
+
@hash = nil
|
|
2555
|
+
end
|
|
2556
|
+
|
|
2557
|
+
##
|
|
2558
|
+
# Converts the string to be UTF-8 if it is not already UTF-8
|
|
2559
|
+
#
|
|
2560
|
+
# @api private
|
|
2561
|
+
def force_utf8_encoding_if_needed(str)
|
|
2562
|
+
if str && str.encoding != Encoding::UTF_8
|
|
2563
|
+
str.force_encoding(Encoding::UTF_8)
|
|
2564
|
+
end
|
|
2565
|
+
end
|
|
2566
|
+
|
|
2567
|
+
private
|
|
2568
|
+
|
|
2569
|
+
##
|
|
2570
|
+
# Resets instance variables
|
|
2571
|
+
#
|
|
2572
|
+
# @api private
|
|
2573
|
+
def reset_ivs
|
|
2574
|
+
@scheme = nil
|
|
2575
|
+
@user = nil
|
|
2576
|
+
@normalized_scheme = NONE
|
|
2577
|
+
@normalized_user = NONE
|
|
2578
|
+
@uri_string = nil
|
|
2579
|
+
@hash = nil
|
|
2580
|
+
@userinfo = nil
|
|
2581
|
+
@normalized_userinfo = NONE
|
|
2582
|
+
@authority = nil
|
|
2583
|
+
@password = nil
|
|
2584
|
+
@normalized_authority = nil
|
|
2585
|
+
@port = nil
|
|
2586
|
+
@normalized_password = NONE
|
|
2587
|
+
@host = nil
|
|
2588
|
+
@normalized_host = nil
|
|
2589
|
+
@normalized_port = NONE
|
|
2590
|
+
@path = EMPTY_STR
|
|
2591
|
+
@normalized_path = nil
|
|
2592
|
+
@normalized_query = NONE
|
|
2593
|
+
@fragment = nil
|
|
2594
|
+
@normalized_fragment = NONE
|
|
2595
|
+
@query = nil
|
|
2596
|
+
end
|
|
2597
|
+
|
|
2598
|
+
NONE = Module.new.freeze
|
|
2599
|
+
|
|
2600
|
+
private_constant :NONE
|
|
2244
2601
|
end
|
|
2245
2602
|
end
|