addressable 2.5.0 → 2.8.8
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 +5 -5
- data/CHANGELOG.md +138 -34
- data/Gemfile +15 -16
- data/README.md +20 -21
- data/Rakefile +16 -11
- data/addressable.gemspec +28 -0
- data/lib/addressable/idna/native.rb +12 -5
- data/lib/addressable/idna/pure.rb +4257 -214
- data/lib/addressable/idna.rb +2 -1
- data/lib/addressable/template.rb +76 -98
- data/lib/addressable/uri.rb +338 -228
- data/lib/addressable/version.rb +4 -3
- data/lib/addressable.rb +2 -0
- data/spec/addressable/idna_spec.rb +29 -13
- data/spec/addressable/net_http_compat_spec.rb +2 -1
- data/spec/addressable/security_spec.rb +2 -1
- data/spec/addressable/template_spec.rb +144 -267
- data/spec/addressable/uri_spec.rb +598 -216
- data/spec/spec_helper.rb +12 -0
- data/tasks/clobber.rake +2 -0
- data/tasks/gem.rake +18 -9
- data/tasks/git.rake +2 -0
- data/tasks/metrics.rake +2 -0
- data/tasks/profile.rake +72 -0
- data/tasks/rspec.rake +3 -1
- data/tasks/yard.rake +2 -0
- metadata +25 -22
- data/data/unicode.data +0 -0
- data/spec/addressable/rack_mount_compat_spec.rb +0 -104
data/lib/addressable/uri.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
#--
|
|
3
4
|
# Copyright (C) Bob Aman
|
|
4
5
|
#
|
|
@@ -36,20 +37,48 @@ module Addressable
|
|
|
36
37
|
##
|
|
37
38
|
# Container for the character classes specified in
|
|
38
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
|
|
39
46
|
module CharacterClasses
|
|
40
47
|
ALPHA = "a-zA-Z"
|
|
41
48
|
DIGIT = "0-9"
|
|
42
49
|
GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
|
|
43
50
|
SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
|
|
44
|
-
RESERVED = GEN_DELIMS + SUB_DELIMS
|
|
45
|
-
UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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}]/
|
|
53
82
|
end
|
|
54
83
|
|
|
55
84
|
SLASH = '/'
|
|
@@ -71,7 +100,7 @@ module Addressable
|
|
|
71
100
|
"wais" => 210,
|
|
72
101
|
"ldap" => 389,
|
|
73
102
|
"prospero" => 1525
|
|
74
|
-
}
|
|
103
|
+
}.freeze
|
|
75
104
|
|
|
76
105
|
##
|
|
77
106
|
# Returns a URI object based on the parsed string.
|
|
@@ -101,7 +130,7 @@ module Addressable
|
|
|
101
130
|
uri = uri.to_str
|
|
102
131
|
rescue TypeError, NoMethodError
|
|
103
132
|
raise TypeError, "Can't convert #{uri.class} into String."
|
|
104
|
-
end
|
|
133
|
+
end unless uri.is_a?(String)
|
|
105
134
|
|
|
106
135
|
# This Regexp supplied as an example in RFC 3986, and it works great.
|
|
107
136
|
scan = uri.scan(URIREGEX)
|
|
@@ -122,15 +151,15 @@ module Addressable
|
|
|
122
151
|
user = userinfo.strip[/^([^:]*):?/, 1]
|
|
123
152
|
password = userinfo.strip[/:(.*)$/, 1]
|
|
124
153
|
end
|
|
125
|
-
|
|
154
|
+
|
|
155
|
+
host = authority.sub(
|
|
126
156
|
/^([^\[\]]*)@/, EMPTY_STR
|
|
127
|
-
).
|
|
157
|
+
).sub(
|
|
128
158
|
/:([^:@\[\]]*?)$/, EMPTY_STR
|
|
129
159
|
)
|
|
160
|
+
|
|
130
161
|
port = authority[/:([^:@\[\]]*?)$/, 1]
|
|
131
|
-
|
|
132
|
-
if port == EMPTY_STR
|
|
133
|
-
port = nil
|
|
162
|
+
port = nil if port == EMPTY_STR
|
|
134
163
|
end
|
|
135
164
|
|
|
136
165
|
return new(
|
|
@@ -173,7 +202,7 @@ module Addressable
|
|
|
173
202
|
uri = uri.to_s
|
|
174
203
|
end
|
|
175
204
|
|
|
176
|
-
|
|
205
|
+
unless uri.respond_to?(:to_str)
|
|
177
206
|
raise TypeError, "Can't convert #{uri.class} into String."
|
|
178
207
|
end
|
|
179
208
|
# Otherwise, convert to a String
|
|
@@ -182,26 +211,33 @@ module Addressable
|
|
|
182
211
|
:scheme => "http"
|
|
183
212
|
}.merge(hints)
|
|
184
213
|
case uri
|
|
185
|
-
when /^http
|
|
186
|
-
uri.
|
|
187
|
-
when /^https
|
|
188
|
-
uri.
|
|
189
|
-
when /^feed:\/+http
|
|
190
|
-
uri.
|
|
191
|
-
when /^feed
|
|
192
|
-
uri.
|
|
193
|
-
when
|
|
194
|
-
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:///")
|
|
195
228
|
when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
|
196
|
-
uri.
|
|
229
|
+
uri.sub!(/^/, hints[:scheme] + "://")
|
|
230
|
+
when /\A\d+\..*:\d+\z/
|
|
231
|
+
uri = "#{hints[:scheme]}://#{uri}"
|
|
197
232
|
end
|
|
198
233
|
match = uri.match(URIREGEX)
|
|
199
234
|
fragments = match.captures
|
|
200
235
|
authority = fragments[3]
|
|
201
236
|
if authority && authority.length > 0
|
|
202
|
-
new_authority = authority.
|
|
237
|
+
new_authority = authority.tr("\\", "/").gsub(" ", "%20")
|
|
203
238
|
# NOTE: We want offset 4, not 3!
|
|
204
239
|
offset = match.offset(4)
|
|
240
|
+
uri = uri.dup
|
|
205
241
|
uri[offset[0]...offset[1]] = new_authority
|
|
206
242
|
end
|
|
207
243
|
parsed = self.parse(uri)
|
|
@@ -209,10 +245,11 @@ module Addressable
|
|
|
209
245
|
parsed = self.parse(hints[:scheme] + "://" + uri)
|
|
210
246
|
end
|
|
211
247
|
if parsed.path.include?(".")
|
|
212
|
-
|
|
213
|
-
|
|
248
|
+
if parsed.path[/\b@\b/]
|
|
249
|
+
parsed.scheme = "mailto" unless parsed.scheme
|
|
250
|
+
elsif new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
|
|
214
251
|
parsed.defer_validation do
|
|
215
|
-
new_path = parsed.path.
|
|
252
|
+
new_path = parsed.path.sub(
|
|
216
253
|
Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
|
|
217
254
|
parsed.host = new_host
|
|
218
255
|
parsed.path = new_path
|
|
@@ -257,30 +294,30 @@ module Addressable
|
|
|
257
294
|
return nil unless path
|
|
258
295
|
# If a URI object is passed, just return itself.
|
|
259
296
|
return path if path.kind_of?(self)
|
|
260
|
-
|
|
297
|
+
unless path.respond_to?(:to_str)
|
|
261
298
|
raise TypeError, "Can't convert #{path.class} into String."
|
|
262
299
|
end
|
|
263
300
|
# Otherwise, convert to a String
|
|
264
301
|
path = path.to_str.strip
|
|
265
302
|
|
|
266
|
-
path.
|
|
303
|
+
path.sub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
|
|
267
304
|
path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/
|
|
268
305
|
uri = self.parse(path)
|
|
269
306
|
|
|
270
307
|
if uri.scheme == nil
|
|
271
308
|
# Adjust windows-style uris
|
|
272
|
-
uri.path.
|
|
309
|
+
uri.path.sub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
|
|
273
310
|
"/#{$1.downcase}:/"
|
|
274
311
|
end
|
|
275
|
-
uri.path.
|
|
312
|
+
uri.path.tr!("\\", SLASH)
|
|
276
313
|
if File.exist?(uri.path) &&
|
|
277
314
|
File.stat(uri.path).directory?
|
|
278
|
-
uri.path.
|
|
315
|
+
uri.path.chomp!(SLASH)
|
|
279
316
|
uri.path = uri.path + '/'
|
|
280
317
|
end
|
|
281
318
|
|
|
282
319
|
# If the path is absolute, set the scheme and host.
|
|
283
|
-
if uri.path
|
|
320
|
+
if uri.path.start_with?(SLASH)
|
|
284
321
|
uri.scheme = "file"
|
|
285
322
|
uri.host = EMPTY_STR
|
|
286
323
|
end
|
|
@@ -305,18 +342,29 @@ module Addressable
|
|
|
305
342
|
# #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
|
|
306
343
|
def self.join(*uris)
|
|
307
344
|
uri_objects = uris.collect do |uri|
|
|
308
|
-
|
|
345
|
+
unless uri.respond_to?(:to_str)
|
|
309
346
|
raise TypeError, "Can't convert #{uri.class} into String."
|
|
310
347
|
end
|
|
311
348
|
uri.kind_of?(self) ? uri : self.parse(uri.to_str)
|
|
312
349
|
end
|
|
313
350
|
result = uri_objects.shift.dup
|
|
314
|
-
|
|
351
|
+
uri_objects.each do |uri|
|
|
315
352
|
result.join!(uri)
|
|
316
353
|
end
|
|
317
354
|
return result
|
|
318
355
|
end
|
|
319
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
|
+
|
|
320
368
|
##
|
|
321
369
|
# Percent encodes a URI component.
|
|
322
370
|
#
|
|
@@ -352,9 +400,7 @@ module Addressable
|
|
|
352
400
|
# "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
|
|
353
401
|
# )
|
|
354
402
|
# => "simple%2Fexample"
|
|
355
|
-
def self.encode_component(component, character_class=
|
|
356
|
-
CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
|
|
357
|
-
upcase_encoded='')
|
|
403
|
+
def self.encode_component(component, character_class=CharacterClassesRegexps::RESERVED_AND_UNRESERVED, upcase_encoded='')
|
|
358
404
|
return nil if component.nil?
|
|
359
405
|
|
|
360
406
|
begin
|
|
@@ -382,19 +428,22 @@ module Addressable
|
|
|
382
428
|
component = component.dup
|
|
383
429
|
component.force_encoding(Encoding::ASCII_8BIT)
|
|
384
430
|
# Avoiding gsub! because there are edge cases with frozen strings
|
|
385
|
-
component = component.gsub(character_class) do |
|
|
386
|
-
|
|
431
|
+
component = component.gsub(character_class) do |char|
|
|
432
|
+
SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[char.ord]
|
|
387
433
|
end
|
|
388
434
|
if upcase_encoded.length > 0
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
end
|
|
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)
|
|
392
440
|
end
|
|
441
|
+
|
|
393
442
|
return component
|
|
394
443
|
end
|
|
395
444
|
|
|
396
445
|
class << self
|
|
397
|
-
alias_method :
|
|
446
|
+
alias_method :escape_component, :encode_component
|
|
398
447
|
end
|
|
399
448
|
|
|
400
449
|
##
|
|
@@ -433,16 +482,14 @@ module Addressable
|
|
|
433
482
|
"Expected Class (String or Addressable::URI), " +
|
|
434
483
|
"got #{return_type.inspect}"
|
|
435
484
|
end
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
uri.force_encoding("utf-8")
|
|
439
|
-
leave_encoded.force_encoding("utf-8")
|
|
440
|
-
result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
|
|
485
|
+
|
|
486
|
+
result = uri.gsub(/%[0-9a-f]{2}/i) do |sequence|
|
|
441
487
|
c = sequence[1..3].to_i(16).chr
|
|
442
|
-
c.force_encoding(
|
|
488
|
+
c.force_encoding(sequence.encoding)
|
|
443
489
|
leave_encoded.include?(c) ? sequence : c
|
|
444
490
|
end
|
|
445
|
-
|
|
491
|
+
|
|
492
|
+
result.force_encoding(Encoding::UTF_8)
|
|
446
493
|
if return_type == String
|
|
447
494
|
return result
|
|
448
495
|
elsif return_type == ::Addressable::URI
|
|
@@ -503,7 +550,7 @@ module Addressable
|
|
|
503
550
|
# )
|
|
504
551
|
# => "one two%2Fthree&four"
|
|
505
552
|
def self.normalize_component(component, character_class=
|
|
506
|
-
|
|
553
|
+
CharacterClassesRegexps::RESERVED_AND_UNRESERVED,
|
|
507
554
|
leave_encoded='')
|
|
508
555
|
return nil if component.nil?
|
|
509
556
|
|
|
@@ -521,13 +568,16 @@ module Addressable
|
|
|
521
568
|
leave_re = if leave_encoded.length > 0
|
|
522
569
|
character_class = "#{character_class}%" unless character_class.include?('%')
|
|
523
570
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
end.flatten.join('|')})"
|
|
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})"
|
|
528
574
|
end
|
|
529
575
|
|
|
530
|
-
character_class =
|
|
576
|
+
character_class = if leave_re
|
|
577
|
+
/[^#{character_class}]#{leave_re}/
|
|
578
|
+
else
|
|
579
|
+
/[^#{character_class}]/
|
|
580
|
+
end
|
|
531
581
|
end
|
|
532
582
|
# We can't perform regexps on invalid UTF sequences, but
|
|
533
583
|
# here we need to, so switch to ASCII.
|
|
@@ -536,7 +586,7 @@ module Addressable
|
|
|
536
586
|
unencoded = self.unencode_component(component, String, leave_encoded)
|
|
537
587
|
begin
|
|
538
588
|
encoded = self.encode_component(
|
|
539
|
-
|
|
589
|
+
unencoded.unicode_normalize(:nfc),
|
|
540
590
|
character_class,
|
|
541
591
|
leave_encoded
|
|
542
592
|
)
|
|
@@ -580,15 +630,15 @@ module Addressable
|
|
|
580
630
|
uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
|
|
581
631
|
encoded_uri = Addressable::URI.new(
|
|
582
632
|
:scheme => self.encode_component(uri_object.scheme,
|
|
583
|
-
Addressable::URI::
|
|
633
|
+
Addressable::URI::CharacterClassesRegexps::SCHEME),
|
|
584
634
|
:authority => self.encode_component(uri_object.authority,
|
|
585
|
-
Addressable::URI::
|
|
635
|
+
Addressable::URI::CharacterClassesRegexps::AUTHORITY),
|
|
586
636
|
:path => self.encode_component(uri_object.path,
|
|
587
|
-
Addressable::URI::
|
|
637
|
+
Addressable::URI::CharacterClassesRegexps::PATH),
|
|
588
638
|
:query => self.encode_component(uri_object.query,
|
|
589
|
-
Addressable::URI::
|
|
639
|
+
Addressable::URI::CharacterClassesRegexps::QUERY),
|
|
590
640
|
:fragment => self.encode_component(uri_object.fragment,
|
|
591
|
-
Addressable::URI::
|
|
641
|
+
Addressable::URI::CharacterClassesRegexps::FRAGMENT)
|
|
592
642
|
)
|
|
593
643
|
if return_type == String
|
|
594
644
|
return encoded_uri.to_s
|
|
@@ -644,8 +694,7 @@ module Addressable
|
|
|
644
694
|
components.each do |key, value|
|
|
645
695
|
if value != nil
|
|
646
696
|
begin
|
|
647
|
-
components[key] =
|
|
648
|
-
Addressable::IDNA.unicode_normalize_kc(value.to_str)
|
|
697
|
+
components[key] = value.to_str.unicode_normalize(:nfc)
|
|
649
698
|
rescue ArgumentError
|
|
650
699
|
# Likely a malformed UTF-8 character, skip unicode normalization
|
|
651
700
|
components[key] = value.to_str
|
|
@@ -654,19 +703,19 @@ module Addressable
|
|
|
654
703
|
end
|
|
655
704
|
encoded_uri = Addressable::URI.new(
|
|
656
705
|
:scheme => self.encode_component(components[:scheme],
|
|
657
|
-
Addressable::URI::
|
|
706
|
+
Addressable::URI::CharacterClassesRegexps::SCHEME),
|
|
658
707
|
:user => self.encode_component(components[:user],
|
|
659
|
-
Addressable::URI::
|
|
708
|
+
Addressable::URI::CharacterClassesRegexps::UNRESERVED),
|
|
660
709
|
:password => self.encode_component(components[:password],
|
|
661
|
-
Addressable::URI::
|
|
710
|
+
Addressable::URI::CharacterClassesRegexps::UNRESERVED),
|
|
662
711
|
:host => components[:host],
|
|
663
712
|
:port => components[:port],
|
|
664
713
|
:path => self.encode_component(components[:path],
|
|
665
|
-
Addressable::URI::
|
|
714
|
+
Addressable::URI::CharacterClassesRegexps::PATH),
|
|
666
715
|
:query => self.encode_component(components[:query],
|
|
667
|
-
Addressable::URI::
|
|
716
|
+
Addressable::URI::CharacterClassesRegexps::QUERY),
|
|
668
717
|
:fragment => self.encode_component(components[:fragment],
|
|
669
|
-
Addressable::URI::
|
|
718
|
+
Addressable::URI::CharacterClassesRegexps::FRAGMENT)
|
|
670
719
|
)
|
|
671
720
|
if return_type == String
|
|
672
721
|
return encoded_uri.to_s
|
|
@@ -717,11 +766,11 @@ module Addressable
|
|
|
717
766
|
[
|
|
718
767
|
self.encode_component(
|
|
719
768
|
key.gsub(/(\r\n|\n|\r)/, "\r\n"),
|
|
720
|
-
|
|
769
|
+
CharacterClassesRegexps::UNRESERVED
|
|
721
770
|
).gsub("%20", "+"),
|
|
722
771
|
self.encode_component(
|
|
723
772
|
value.gsub(/(\r\n|\n|\r)/, "\r\n"),
|
|
724
|
-
|
|
773
|
+
CharacterClassesRegexps::UNRESERVED
|
|
725
774
|
).gsub("%20", "+")
|
|
726
775
|
]
|
|
727
776
|
end
|
|
@@ -793,7 +842,9 @@ module Addressable
|
|
|
793
842
|
end
|
|
794
843
|
end
|
|
795
844
|
|
|
796
|
-
|
|
845
|
+
reset_ivs
|
|
846
|
+
|
|
847
|
+
defer_validation do
|
|
797
848
|
# Bunch of crazy logic required because of the composite components
|
|
798
849
|
# like userinfo and authority.
|
|
799
850
|
self.scheme = options[:scheme] if options[:scheme]
|
|
@@ -808,7 +859,8 @@ module Addressable
|
|
|
808
859
|
self.query_values = options[:query_values] if options[:query_values]
|
|
809
860
|
self.fragment = options[:fragment] if options[:fragment]
|
|
810
861
|
end
|
|
811
|
-
|
|
862
|
+
|
|
863
|
+
to_s # force path validation
|
|
812
864
|
end
|
|
813
865
|
|
|
814
866
|
##
|
|
@@ -835,9 +887,7 @@ module Addressable
|
|
|
835
887
|
# The scheme component for this URI.
|
|
836
888
|
#
|
|
837
889
|
# @return [String] The scheme component.
|
|
838
|
-
|
|
839
|
-
return defined?(@scheme) ? @scheme : nil
|
|
840
|
-
end
|
|
890
|
+
attr_reader :scheme
|
|
841
891
|
|
|
842
892
|
##
|
|
843
893
|
# The scheme component for this URI, normalized.
|
|
@@ -845,18 +895,18 @@ module Addressable
|
|
|
845
895
|
# @return [String] The scheme component, normalized.
|
|
846
896
|
def normalized_scheme
|
|
847
897
|
return nil unless self.scheme
|
|
848
|
-
@normalized_scheme
|
|
849
|
-
if self.scheme =~ /^\s*ssh\+svn\s*$/i
|
|
850
|
-
"svn+ssh"
|
|
898
|
+
if @normalized_scheme == NONE
|
|
899
|
+
@normalized_scheme = if self.scheme =~ /^\s*ssh\+svn\s*$/i
|
|
900
|
+
"svn+ssh".dup
|
|
851
901
|
else
|
|
852
902
|
Addressable::URI.normalize_component(
|
|
853
903
|
self.scheme.strip.downcase,
|
|
854
|
-
Addressable::URI::
|
|
904
|
+
Addressable::URI::NormalizeCharacterClasses::SCHEME
|
|
855
905
|
)
|
|
856
906
|
end
|
|
857
907
|
end
|
|
858
908
|
# All normalized values should be UTF-8
|
|
859
|
-
@normalized_scheme
|
|
909
|
+
force_utf8_encoding_if_needed(@normalized_scheme)
|
|
860
910
|
@normalized_scheme
|
|
861
911
|
end
|
|
862
912
|
|
|
@@ -871,13 +921,13 @@ module Addressable
|
|
|
871
921
|
new_scheme = new_scheme.to_str
|
|
872
922
|
end
|
|
873
923
|
if new_scheme && new_scheme !~ /\A[a-z][a-z0-9\.\+\-]*\z/i
|
|
874
|
-
raise InvalidURIError, "Invalid scheme format: #{new_scheme}"
|
|
924
|
+
raise InvalidURIError, "Invalid scheme format: '#{new_scheme}'"
|
|
875
925
|
end
|
|
876
926
|
@scheme = new_scheme
|
|
877
927
|
@scheme = nil if @scheme.to_s.strip.empty?
|
|
878
928
|
|
|
879
929
|
# Reset dependent values
|
|
880
|
-
|
|
930
|
+
@normalized_scheme = NONE
|
|
881
931
|
remove_composite_values
|
|
882
932
|
|
|
883
933
|
# Ensure we haven't created an invalid URI
|
|
@@ -888,9 +938,7 @@ module Addressable
|
|
|
888
938
|
# The user component for this URI.
|
|
889
939
|
#
|
|
890
940
|
# @return [String] The user component.
|
|
891
|
-
|
|
892
|
-
return defined?(@user) ? @user : nil
|
|
893
|
-
end
|
|
941
|
+
attr_reader :user
|
|
894
942
|
|
|
895
943
|
##
|
|
896
944
|
# The user component for this URI, normalized.
|
|
@@ -898,20 +946,20 @@ module Addressable
|
|
|
898
946
|
# @return [String] The user component, normalized.
|
|
899
947
|
def normalized_user
|
|
900
948
|
return nil unless self.user
|
|
901
|
-
return @normalized_user
|
|
902
|
-
@normalized_user
|
|
949
|
+
return @normalized_user unless @normalized_user == NONE
|
|
950
|
+
@normalized_user = begin
|
|
903
951
|
if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
|
|
904
952
|
(!self.password || self.password.strip.empty?)
|
|
905
953
|
nil
|
|
906
954
|
else
|
|
907
955
|
Addressable::URI.normalize_component(
|
|
908
956
|
self.user.strip,
|
|
909
|
-
Addressable::URI::
|
|
957
|
+
Addressable::URI::NormalizeCharacterClasses::UNRESERVED
|
|
910
958
|
)
|
|
911
959
|
end
|
|
912
960
|
end
|
|
913
961
|
# All normalized values should be UTF-8
|
|
914
|
-
@normalized_user
|
|
962
|
+
force_utf8_encoding_if_needed(@normalized_user)
|
|
915
963
|
@normalized_user
|
|
916
964
|
end
|
|
917
965
|
|
|
@@ -927,14 +975,14 @@ module Addressable
|
|
|
927
975
|
|
|
928
976
|
# You can't have a nil user with a non-nil password
|
|
929
977
|
if password != nil
|
|
930
|
-
@user = EMPTY_STR
|
|
978
|
+
@user = EMPTY_STR unless user
|
|
931
979
|
end
|
|
932
980
|
|
|
933
981
|
# Reset dependent values
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
982
|
+
@userinfo = nil
|
|
983
|
+
@normalized_userinfo = NONE
|
|
984
|
+
@authority = nil
|
|
985
|
+
@normalized_user = NONE
|
|
938
986
|
remove_composite_values
|
|
939
987
|
|
|
940
988
|
# Ensure we haven't created an invalid URI
|
|
@@ -945,9 +993,7 @@ module Addressable
|
|
|
945
993
|
# The password component for this URI.
|
|
946
994
|
#
|
|
947
995
|
# @return [String] The password component.
|
|
948
|
-
|
|
949
|
-
return defined?(@password) ? @password : nil
|
|
950
|
-
end
|
|
996
|
+
attr_reader :password
|
|
951
997
|
|
|
952
998
|
##
|
|
953
999
|
# The password component for this URI, normalized.
|
|
@@ -955,22 +1001,20 @@ module Addressable
|
|
|
955
1001
|
# @return [String] The password component, normalized.
|
|
956
1002
|
def normalized_password
|
|
957
1003
|
return nil unless self.password
|
|
958
|
-
return @normalized_password
|
|
959
|
-
@normalized_password
|
|
1004
|
+
return @normalized_password unless @normalized_password == NONE
|
|
1005
|
+
@normalized_password = begin
|
|
960
1006
|
if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
|
|
961
1007
|
(!self.user || self.user.strip.empty?)
|
|
962
1008
|
nil
|
|
963
1009
|
else
|
|
964
1010
|
Addressable::URI.normalize_component(
|
|
965
1011
|
self.password.strip,
|
|
966
|
-
Addressable::URI::
|
|
1012
|
+
Addressable::URI::NormalizeCharacterClasses::UNRESERVED
|
|
967
1013
|
)
|
|
968
1014
|
end
|
|
969
1015
|
end
|
|
970
1016
|
# All normalized values should be UTF-8
|
|
971
|
-
|
|
972
|
-
@normalized_password.force_encoding(Encoding::UTF_8)
|
|
973
|
-
end
|
|
1017
|
+
force_utf8_encoding_if_needed(@normalized_password)
|
|
974
1018
|
@normalized_password
|
|
975
1019
|
end
|
|
976
1020
|
|
|
@@ -985,17 +1029,15 @@ module Addressable
|
|
|
985
1029
|
@password = new_password ? new_password.to_str : nil
|
|
986
1030
|
|
|
987
1031
|
# You can't have a nil user with a non-nil password
|
|
988
|
-
@password ||= nil
|
|
989
|
-
@user ||= nil
|
|
990
1032
|
if @password != nil
|
|
991
|
-
|
|
1033
|
+
self.user = EMPTY_STR if user.nil?
|
|
992
1034
|
end
|
|
993
1035
|
|
|
994
1036
|
# Reset dependent values
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1037
|
+
@userinfo = nil
|
|
1038
|
+
@normalized_userinfo = NONE
|
|
1039
|
+
@authority = nil
|
|
1040
|
+
@normalized_password = NONE
|
|
999
1041
|
remove_composite_values
|
|
1000
1042
|
|
|
1001
1043
|
# Ensure we haven't created an invalid URI
|
|
@@ -1025,22 +1067,20 @@ module Addressable
|
|
|
1025
1067
|
# @return [String] The userinfo component, normalized.
|
|
1026
1068
|
def normalized_userinfo
|
|
1027
1069
|
return nil unless self.userinfo
|
|
1028
|
-
return @normalized_userinfo
|
|
1029
|
-
@normalized_userinfo
|
|
1070
|
+
return @normalized_userinfo unless @normalized_userinfo == NONE
|
|
1071
|
+
@normalized_userinfo = begin
|
|
1030
1072
|
current_user = self.normalized_user
|
|
1031
1073
|
current_password = self.normalized_password
|
|
1032
1074
|
if !current_user && !current_password
|
|
1033
1075
|
nil
|
|
1034
1076
|
elsif current_user && current_password
|
|
1035
|
-
"#{current_user}:#{current_password}"
|
|
1077
|
+
"#{current_user}:#{current_password}".dup
|
|
1036
1078
|
elsif current_user && !current_password
|
|
1037
|
-
"#{current_user}"
|
|
1079
|
+
"#{current_user}".dup
|
|
1038
1080
|
end
|
|
1039
1081
|
end
|
|
1040
1082
|
# All normalized values should be UTF-8
|
|
1041
|
-
|
|
1042
|
-
@normalized_userinfo.force_encoding(Encoding::UTF_8)
|
|
1043
|
-
end
|
|
1083
|
+
force_utf8_encoding_if_needed(@normalized_userinfo)
|
|
1044
1084
|
@normalized_userinfo
|
|
1045
1085
|
end
|
|
1046
1086
|
|
|
@@ -1066,7 +1106,7 @@ module Addressable
|
|
|
1066
1106
|
self.user = new_user
|
|
1067
1107
|
|
|
1068
1108
|
# Reset dependent values
|
|
1069
|
-
|
|
1109
|
+
@authority = nil
|
|
1070
1110
|
remove_composite_values
|
|
1071
1111
|
|
|
1072
1112
|
# Ensure we haven't created an invalid URI
|
|
@@ -1077,9 +1117,7 @@ module Addressable
|
|
|
1077
1117
|
# The host component for this URI.
|
|
1078
1118
|
#
|
|
1079
1119
|
# @return [String] The host component.
|
|
1080
|
-
|
|
1081
|
-
return defined?(@host) ? @host : nil
|
|
1082
|
-
end
|
|
1120
|
+
attr_reader :host
|
|
1083
1121
|
|
|
1084
1122
|
##
|
|
1085
1123
|
# The host component for this URI, normalized.
|
|
@@ -1087,6 +1125,7 @@ module Addressable
|
|
|
1087
1125
|
# @return [String] The host component, normalized.
|
|
1088
1126
|
def normalized_host
|
|
1089
1127
|
return nil unless self.host
|
|
1128
|
+
|
|
1090
1129
|
@normalized_host ||= begin
|
|
1091
1130
|
if !self.host.strip.empty?
|
|
1092
1131
|
result = ::Addressable::IDNA.to_ascii(
|
|
@@ -1098,14 +1137,15 @@ module Addressable
|
|
|
1098
1137
|
end
|
|
1099
1138
|
result = Addressable::URI.normalize_component(
|
|
1100
1139
|
result,
|
|
1101
|
-
|
|
1140
|
+
NormalizeCharacterClasses::HOST
|
|
1141
|
+
)
|
|
1102
1142
|
result
|
|
1103
1143
|
else
|
|
1104
|
-
EMPTY_STR
|
|
1144
|
+
EMPTY_STR.dup
|
|
1105
1145
|
end
|
|
1106
1146
|
end
|
|
1107
1147
|
# All normalized values should be UTF-8
|
|
1108
|
-
@normalized_host
|
|
1148
|
+
force_utf8_encoding_if_needed(@normalized_host)
|
|
1109
1149
|
@normalized_host
|
|
1110
1150
|
end
|
|
1111
1151
|
|
|
@@ -1120,8 +1160,8 @@ module Addressable
|
|
|
1120
1160
|
@host = new_host ? new_host.to_str : nil
|
|
1121
1161
|
|
|
1122
1162
|
# Reset dependent values
|
|
1123
|
-
|
|
1124
|
-
|
|
1163
|
+
@authority = nil
|
|
1164
|
+
@normalized_host = nil
|
|
1125
1165
|
remove_composite_values
|
|
1126
1166
|
|
|
1127
1167
|
# Ensure we haven't created an invalid URI
|
|
@@ -1163,16 +1203,25 @@ module Addressable
|
|
|
1163
1203
|
# Returns the top-level domain for this host.
|
|
1164
1204
|
#
|
|
1165
1205
|
# @example
|
|
1166
|
-
# Addressable::URI.parse("www.example.co.uk").tld # => "co.uk"
|
|
1206
|
+
# Addressable::URI.parse("http://www.example.co.uk").tld # => "co.uk"
|
|
1167
1207
|
def tld
|
|
1168
1208
|
PublicSuffix.parse(self.host, ignore_private: true).tld
|
|
1169
1209
|
end
|
|
1170
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
|
+
|
|
1171
1220
|
##
|
|
1172
1221
|
# Returns the public suffix domain for this host.
|
|
1173
1222
|
#
|
|
1174
1223
|
# @example
|
|
1175
|
-
# Addressable::URI.parse("www.example.co.uk").domain # => "example.co.uk"
|
|
1224
|
+
# Addressable::URI.parse("http://www.example.co.uk").domain # => "example.co.uk"
|
|
1176
1225
|
def domain
|
|
1177
1226
|
PublicSuffix.domain(self.host, ignore_private: true)
|
|
1178
1227
|
end
|
|
@@ -1214,9 +1263,7 @@ module Addressable
|
|
|
1214
1263
|
authority
|
|
1215
1264
|
end
|
|
1216
1265
|
# All normalized values should be UTF-8
|
|
1217
|
-
|
|
1218
|
-
@normalized_authority.force_encoding(Encoding::UTF_8)
|
|
1219
|
-
end
|
|
1266
|
+
force_utf8_encoding_if_needed(@normalized_authority)
|
|
1220
1267
|
@normalized_authority
|
|
1221
1268
|
end
|
|
1222
1269
|
|
|
@@ -1235,9 +1282,9 @@ module Addressable
|
|
|
1235
1282
|
new_user = new_userinfo.strip[/^([^:]*):?/, 1]
|
|
1236
1283
|
new_password = new_userinfo.strip[/:(.*)$/, 1]
|
|
1237
1284
|
end
|
|
1238
|
-
new_host = new_authority.
|
|
1285
|
+
new_host = new_authority.sub(
|
|
1239
1286
|
/^([^\[\]]*)@/, EMPTY_STR
|
|
1240
|
-
).
|
|
1287
|
+
).sub(
|
|
1241
1288
|
/:([^:@\[\]]*?)$/, EMPTY_STR
|
|
1242
1289
|
)
|
|
1243
1290
|
new_port =
|
|
@@ -1245,14 +1292,14 @@ module Addressable
|
|
|
1245
1292
|
end
|
|
1246
1293
|
|
|
1247
1294
|
# Password assigned first to ensure validity in case of nil
|
|
1248
|
-
self.password =
|
|
1249
|
-
self.user =
|
|
1250
|
-
self.host =
|
|
1251
|
-
self.port =
|
|
1295
|
+
self.password = new_password
|
|
1296
|
+
self.user = new_user
|
|
1297
|
+
self.host = new_host
|
|
1298
|
+
self.port = new_port
|
|
1252
1299
|
|
|
1253
1300
|
# Reset dependent values
|
|
1254
|
-
|
|
1255
|
-
|
|
1301
|
+
@userinfo = nil
|
|
1302
|
+
@normalized_userinfo = NONE
|
|
1256
1303
|
remove_composite_values
|
|
1257
1304
|
|
|
1258
1305
|
# Ensure we haven't created an invalid URI
|
|
@@ -1300,16 +1347,16 @@ module Addressable
|
|
|
1300
1347
|
new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
|
|
1301
1348
|
end
|
|
1302
1349
|
|
|
1303
|
-
self.scheme =
|
|
1304
|
-
self.host =
|
|
1305
|
-
self.port =
|
|
1350
|
+
self.scheme = new_scheme
|
|
1351
|
+
self.host = new_host
|
|
1352
|
+
self.port = new_port
|
|
1306
1353
|
self.userinfo = nil
|
|
1307
1354
|
|
|
1308
1355
|
# Reset dependent values
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1356
|
+
@userinfo = nil
|
|
1357
|
+
@normalized_userinfo = NONE
|
|
1358
|
+
@authority = nil
|
|
1359
|
+
@normalized_authority = nil
|
|
1313
1360
|
remove_composite_values
|
|
1314
1361
|
|
|
1315
1362
|
# Ensure we haven't created an invalid URI
|
|
@@ -1336,9 +1383,7 @@ module Addressable
|
|
|
1336
1383
|
# infer port numbers from default values.
|
|
1337
1384
|
#
|
|
1338
1385
|
# @return [Integer] The port component.
|
|
1339
|
-
|
|
1340
|
-
return defined?(@port) ? @port : nil
|
|
1341
|
-
end
|
|
1386
|
+
attr_reader :port
|
|
1342
1387
|
|
|
1343
1388
|
##
|
|
1344
1389
|
# The port component for this URI, normalized.
|
|
@@ -1346,8 +1391,8 @@ module Addressable
|
|
|
1346
1391
|
# @return [Integer] The port component, normalized.
|
|
1347
1392
|
def normalized_port
|
|
1348
1393
|
return nil unless self.port
|
|
1349
|
-
return @normalized_port
|
|
1350
|
-
@normalized_port
|
|
1394
|
+
return @normalized_port unless @normalized_port == NONE
|
|
1395
|
+
@normalized_port = begin
|
|
1351
1396
|
if URI.port_mapping[self.normalized_scheme] == self.port
|
|
1352
1397
|
nil
|
|
1353
1398
|
else
|
|
@@ -1378,8 +1423,8 @@ module Addressable
|
|
|
1378
1423
|
@port = nil if @port == 0
|
|
1379
1424
|
|
|
1380
1425
|
# Reset dependent values
|
|
1381
|
-
|
|
1382
|
-
|
|
1426
|
+
@authority = nil
|
|
1427
|
+
@normalized_port = NONE
|
|
1383
1428
|
remove_composite_values
|
|
1384
1429
|
|
|
1385
1430
|
# Ensure we haven't created an invalid URI
|
|
@@ -1421,7 +1466,7 @@ module Addressable
|
|
|
1421
1466
|
# @return [String] The components that identify a site.
|
|
1422
1467
|
def site
|
|
1423
1468
|
(self.scheme || self.authority) && @site ||= begin
|
|
1424
|
-
site_string = ""
|
|
1469
|
+
site_string = "".dup
|
|
1425
1470
|
site_string << "#{self.scheme}:" if self.scheme != nil
|
|
1426
1471
|
site_string << "//#{self.authority}" if self.authority != nil
|
|
1427
1472
|
site_string
|
|
@@ -1440,7 +1485,7 @@ module Addressable
|
|
|
1440
1485
|
def normalized_site
|
|
1441
1486
|
return nil unless self.site
|
|
1442
1487
|
@normalized_site ||= begin
|
|
1443
|
-
site_string = ""
|
|
1488
|
+
site_string = "".dup
|
|
1444
1489
|
if self.normalized_scheme != nil
|
|
1445
1490
|
site_string << "#{self.normalized_scheme}:"
|
|
1446
1491
|
end
|
|
@@ -1450,7 +1495,7 @@ module Addressable
|
|
|
1450
1495
|
site_string
|
|
1451
1496
|
end
|
|
1452
1497
|
# All normalized values should be UTF-8
|
|
1453
|
-
@normalized_site
|
|
1498
|
+
force_utf8_encoding_if_needed(@normalized_site)
|
|
1454
1499
|
@normalized_site
|
|
1455
1500
|
end
|
|
1456
1501
|
|
|
@@ -1480,9 +1525,7 @@ module Addressable
|
|
|
1480
1525
|
# The path component for this URI.
|
|
1481
1526
|
#
|
|
1482
1527
|
# @return [String] The path component.
|
|
1483
|
-
|
|
1484
|
-
return defined?(@path) ? @path : EMPTY_STR
|
|
1485
|
-
end
|
|
1528
|
+
attr_reader :path
|
|
1486
1529
|
|
|
1487
1530
|
NORMPATH = /^(?!\/)[^\/:]*:.*$/
|
|
1488
1531
|
##
|
|
@@ -1496,24 +1539,24 @@ module Addressable
|
|
|
1496
1539
|
# Relative paths with colons in the first segment are ambiguous.
|
|
1497
1540
|
path = path.sub(":", "%2F")
|
|
1498
1541
|
end
|
|
1499
|
-
# String#split(
|
|
1542
|
+
# String#split(delimiter, -1) uses the more strict splitting behavior
|
|
1500
1543
|
# found by default in Python.
|
|
1501
1544
|
result = path.strip.split(SLASH, -1).map do |segment|
|
|
1502
1545
|
Addressable::URI.normalize_component(
|
|
1503
1546
|
segment,
|
|
1504
|
-
Addressable::URI::
|
|
1547
|
+
Addressable::URI::NormalizeCharacterClasses::PCHAR
|
|
1505
1548
|
)
|
|
1506
1549
|
end.join(SLASH)
|
|
1507
1550
|
|
|
1508
1551
|
result = URI.normalize_path(result)
|
|
1509
1552
|
if result.empty? &&
|
|
1510
1553
|
["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
|
|
1511
|
-
result = SLASH
|
|
1554
|
+
result = SLASH.dup
|
|
1512
1555
|
end
|
|
1513
1556
|
result
|
|
1514
1557
|
end
|
|
1515
1558
|
# All normalized values should be UTF-8
|
|
1516
|
-
@normalized_path
|
|
1559
|
+
force_utf8_encoding_if_needed(@normalized_path)
|
|
1517
1560
|
@normalized_path
|
|
1518
1561
|
end
|
|
1519
1562
|
|
|
@@ -1531,7 +1574,7 @@ module Addressable
|
|
|
1531
1574
|
end
|
|
1532
1575
|
|
|
1533
1576
|
# Reset dependent values
|
|
1534
|
-
|
|
1577
|
+
@normalized_path = nil
|
|
1535
1578
|
remove_composite_values
|
|
1536
1579
|
|
|
1537
1580
|
# Ensure we haven't created an invalid URI
|
|
@@ -1544,7 +1587,7 @@ module Addressable
|
|
|
1544
1587
|
# @return [String] The path's basename.
|
|
1545
1588
|
def basename
|
|
1546
1589
|
# Path cannot be nil
|
|
1547
|
-
return File.basename(self.path).
|
|
1590
|
+
return File.basename(self.path).sub(/;[^\/]*$/, EMPTY_STR)
|
|
1548
1591
|
end
|
|
1549
1592
|
|
|
1550
1593
|
##
|
|
@@ -1561,9 +1604,7 @@ module Addressable
|
|
|
1561
1604
|
# The query component for this URI.
|
|
1562
1605
|
#
|
|
1563
1606
|
# @return [String] The query component.
|
|
1564
|
-
|
|
1565
|
-
return defined?(@query) ? @query : nil
|
|
1566
|
-
end
|
|
1607
|
+
attr_reader :query
|
|
1567
1608
|
|
|
1568
1609
|
##
|
|
1569
1610
|
# The query component for this URI, normalized.
|
|
@@ -1571,20 +1612,25 @@ module Addressable
|
|
|
1571
1612
|
# @return [String] The query component, normalized.
|
|
1572
1613
|
def normalized_query(*flags)
|
|
1573
1614
|
return nil unless self.query
|
|
1574
|
-
return @normalized_query
|
|
1575
|
-
@normalized_query
|
|
1615
|
+
return @normalized_query unless @normalized_query == NONE
|
|
1616
|
+
@normalized_query = begin
|
|
1576
1617
|
modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
|
|
1577
1618
|
# Make sure possible key-value pair delimiters are escaped.
|
|
1578
1619
|
modified_query_class.sub!("\\&", "").sub!("\\;", "")
|
|
1579
|
-
pairs = (
|
|
1620
|
+
pairs = (query || "").split("&", -1)
|
|
1621
|
+
pairs.delete_if(&:empty?).uniq! if flags.include?(:compacted)
|
|
1580
1622
|
pairs.sort! if flags.include?(:sorted)
|
|
1581
1623
|
component = pairs.map do |pair|
|
|
1582
|
-
Addressable::URI.normalize_component(
|
|
1624
|
+
Addressable::URI.normalize_component(
|
|
1625
|
+
pair,
|
|
1626
|
+
Addressable::URI::NormalizeCharacterClasses::QUERY,
|
|
1627
|
+
"+"
|
|
1628
|
+
)
|
|
1583
1629
|
end.join("&")
|
|
1584
1630
|
component == "" ? nil : component
|
|
1585
1631
|
end
|
|
1586
1632
|
# All normalized values should be UTF-8
|
|
1587
|
-
@normalized_query
|
|
1633
|
+
force_utf8_encoding_if_needed(@normalized_query)
|
|
1588
1634
|
@normalized_query
|
|
1589
1635
|
end
|
|
1590
1636
|
|
|
@@ -1599,7 +1645,7 @@ module Addressable
|
|
|
1599
1645
|
@query = new_query ? new_query.to_str : nil
|
|
1600
1646
|
|
|
1601
1647
|
# Reset dependent values
|
|
1602
|
-
|
|
1648
|
+
@normalized_query = NONE
|
|
1603
1649
|
remove_composite_values
|
|
1604
1650
|
end
|
|
1605
1651
|
|
|
@@ -1638,11 +1684,13 @@ module Addressable
|
|
|
1638
1684
|
# so it's best to make all changes in-place.
|
|
1639
1685
|
pair[0] = URI.unencode_component(pair[0])
|
|
1640
1686
|
if pair[1].respond_to?(:to_str)
|
|
1687
|
+
value = pair[1].to_str
|
|
1641
1688
|
# I loathe the fact that I have to do this. Stupid HTML 4.01.
|
|
1642
1689
|
# Treating '+' as a space was just an unbelievably bad idea.
|
|
1643
1690
|
# There was nothing wrong with '%20'!
|
|
1644
1691
|
# If it ain't broke, don't fix it!
|
|
1645
|
-
|
|
1692
|
+
value = value.tr("+", " ") if ["http", "https", nil].include?(scheme)
|
|
1693
|
+
pair[1] = URI.unencode_component(value)
|
|
1646
1694
|
end
|
|
1647
1695
|
if return_type == Hash
|
|
1648
1696
|
accu[pair[0]] = pair[1]
|
|
@@ -1694,23 +1742,23 @@ module Addressable
|
|
|
1694
1742
|
end
|
|
1695
1743
|
|
|
1696
1744
|
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
|
|
1697
|
-
buffer = ""
|
|
1745
|
+
buffer = "".dup
|
|
1698
1746
|
new_query_values.each do |key, value|
|
|
1699
1747
|
encoded_key = URI.encode_component(
|
|
1700
|
-
key,
|
|
1748
|
+
key, CharacterClassesRegexps::UNRESERVED
|
|
1701
1749
|
)
|
|
1702
1750
|
if value == nil
|
|
1703
1751
|
buffer << "#{encoded_key}&"
|
|
1704
1752
|
elsif value.kind_of?(Array)
|
|
1705
1753
|
value.each do |sub_value|
|
|
1706
1754
|
encoded_value = URI.encode_component(
|
|
1707
|
-
sub_value,
|
|
1755
|
+
sub_value, CharacterClassesRegexps::UNRESERVED
|
|
1708
1756
|
)
|
|
1709
1757
|
buffer << "#{encoded_key}=#{encoded_value}&"
|
|
1710
1758
|
end
|
|
1711
1759
|
else
|
|
1712
1760
|
encoded_value = URI.encode_component(
|
|
1713
|
-
value,
|
|
1761
|
+
value, CharacterClassesRegexps::UNRESERVED
|
|
1714
1762
|
)
|
|
1715
1763
|
buffer << "#{encoded_key}=#{encoded_value}&"
|
|
1716
1764
|
end
|
|
@@ -1724,7 +1772,7 @@ module Addressable
|
|
|
1724
1772
|
#
|
|
1725
1773
|
# @return [String] The request URI required for an HTTP request.
|
|
1726
1774
|
def request_uri
|
|
1727
|
-
return nil if self.absolute? && self.scheme !~ /^https?$/
|
|
1775
|
+
return nil if self.absolute? && self.scheme !~ /^https?$/i
|
|
1728
1776
|
return (
|
|
1729
1777
|
(!self.path.empty? ? self.path : SLASH) +
|
|
1730
1778
|
(self.query ? "?#{self.query}" : EMPTY_STR)
|
|
@@ -1739,12 +1787,12 @@ module Addressable
|
|
|
1739
1787
|
if !new_request_uri.respond_to?(:to_str)
|
|
1740
1788
|
raise TypeError, "Can't convert #{new_request_uri.class} into String."
|
|
1741
1789
|
end
|
|
1742
|
-
if self.absolute? && self.scheme !~ /^https?$/
|
|
1790
|
+
if self.absolute? && self.scheme !~ /^https?$/i
|
|
1743
1791
|
raise InvalidURIError,
|
|
1744
1792
|
"Cannot set an HTTP request URI for a non-HTTP URI."
|
|
1745
1793
|
end
|
|
1746
1794
|
new_request_uri = new_request_uri.to_str
|
|
1747
|
-
path_component = new_request_uri[/^([^\?]*)
|
|
1795
|
+
path_component = new_request_uri[/^([^\?]*)\??(?:.*)$/, 1]
|
|
1748
1796
|
query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1]
|
|
1749
1797
|
path_component = path_component.to_s
|
|
1750
1798
|
path_component = (!path_component.empty? ? path_component : SLASH)
|
|
@@ -1759,9 +1807,7 @@ module Addressable
|
|
|
1759
1807
|
# The fragment component for this URI.
|
|
1760
1808
|
#
|
|
1761
1809
|
# @return [String] The fragment component.
|
|
1762
|
-
|
|
1763
|
-
return defined?(@fragment) ? @fragment : nil
|
|
1764
|
-
end
|
|
1810
|
+
attr_reader :fragment
|
|
1765
1811
|
|
|
1766
1812
|
##
|
|
1767
1813
|
# The fragment component for this URI, normalized.
|
|
@@ -1769,18 +1815,16 @@ module Addressable
|
|
|
1769
1815
|
# @return [String] The fragment component, normalized.
|
|
1770
1816
|
def normalized_fragment
|
|
1771
1817
|
return nil unless self.fragment
|
|
1772
|
-
return @normalized_fragment
|
|
1773
|
-
@normalized_fragment
|
|
1818
|
+
return @normalized_fragment unless @normalized_fragment == NONE
|
|
1819
|
+
@normalized_fragment = begin
|
|
1774
1820
|
component = Addressable::URI.normalize_component(
|
|
1775
1821
|
self.fragment,
|
|
1776
|
-
Addressable::URI::
|
|
1822
|
+
Addressable::URI::NormalizeCharacterClasses::FRAGMENT
|
|
1777
1823
|
)
|
|
1778
1824
|
component == "" ? nil : component
|
|
1779
1825
|
end
|
|
1780
1826
|
# All normalized values should be UTF-8
|
|
1781
|
-
|
|
1782
|
-
@normalized_fragment.force_encoding(Encoding::UTF_8)
|
|
1783
|
-
end
|
|
1827
|
+
force_utf8_encoding_if_needed(@normalized_fragment)
|
|
1784
1828
|
@normalized_fragment
|
|
1785
1829
|
end
|
|
1786
1830
|
|
|
@@ -1795,7 +1839,7 @@ module Addressable
|
|
|
1795
1839
|
@fragment = new_fragment ? new_fragment.to_str : nil
|
|
1796
1840
|
|
|
1797
1841
|
# Reset dependent values
|
|
1798
|
-
|
|
1842
|
+
@normalized_fragment = NONE
|
|
1799
1843
|
remove_composite_values
|
|
1800
1844
|
|
|
1801
1845
|
# Ensure we haven't created an invalid URI
|
|
@@ -1899,8 +1943,8 @@ module Addressable
|
|
|
1899
1943
|
# Section 5.2.3 of RFC 3986
|
|
1900
1944
|
#
|
|
1901
1945
|
# Removes the right-most path segment from the base path.
|
|
1902
|
-
if base_path
|
|
1903
|
-
base_path.
|
|
1946
|
+
if base_path.include?(SLASH)
|
|
1947
|
+
base_path.sub!(/\/[^\/]+$/, SLASH)
|
|
1904
1948
|
else
|
|
1905
1949
|
base_path = EMPTY_STR
|
|
1906
1950
|
end
|
|
@@ -1961,7 +2005,7 @@ module Addressable
|
|
|
1961
2005
|
#
|
|
1962
2006
|
# @see Hash#merge
|
|
1963
2007
|
def merge(hash)
|
|
1964
|
-
|
|
2008
|
+
unless hash.respond_to?(:to_hash)
|
|
1965
2009
|
raise TypeError, "Can't convert #{hash.class} into Hash."
|
|
1966
2010
|
end
|
|
1967
2011
|
hash = hash.to_hash
|
|
@@ -2338,7 +2382,7 @@ module Addressable
|
|
|
2338
2382
|
#
|
|
2339
2383
|
# @return [String] The URI object's state, as a <code>String</code>.
|
|
2340
2384
|
def inspect
|
|
2341
|
-
sprintf("#<%s:%#0x URI:%s>",
|
|
2385
|
+
sprintf("#<%s:%#0x URI:%s>", self.class.to_s, self.object_id, self.to_s)
|
|
2342
2386
|
end
|
|
2343
2387
|
|
|
2344
2388
|
##
|
|
@@ -2349,13 +2393,33 @@ module Addressable
|
|
|
2349
2393
|
#
|
|
2350
2394
|
# @param [Proc] block
|
|
2351
2395
|
# A set of operations to perform on a given URI.
|
|
2352
|
-
def defer_validation
|
|
2353
|
-
raise LocalJumpError, "No block given." unless
|
|
2396
|
+
def defer_validation
|
|
2397
|
+
raise LocalJumpError, "No block given." unless block_given?
|
|
2354
2398
|
@validation_deferred = true
|
|
2355
|
-
|
|
2399
|
+
yield
|
|
2356
2400
|
@validation_deferred = false
|
|
2357
2401
|
validate
|
|
2358
|
-
|
|
2402
|
+
ensure
|
|
2403
|
+
@validation_deferred = false
|
|
2404
|
+
end
|
|
2405
|
+
|
|
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
|
|
2359
2423
|
end
|
|
2360
2424
|
|
|
2361
2425
|
protected
|
|
@@ -2376,30 +2440,35 @@ module Addressable
|
|
|
2376
2440
|
def self.normalize_path(path)
|
|
2377
2441
|
# Section 5.2.4 of RFC 3986
|
|
2378
2442
|
|
|
2379
|
-
return
|
|
2443
|
+
return if path.nil?
|
|
2380
2444
|
normalized_path = path.dup
|
|
2381
|
-
|
|
2382
|
-
mod = nil
|
|
2445
|
+
loop do
|
|
2383
2446
|
mod ||= normalized_path.gsub!(RULE_2A, SLASH)
|
|
2384
2447
|
|
|
2385
2448
|
pair = normalized_path.match(RULE_2B_2C)
|
|
2386
|
-
|
|
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
|
+
|
|
2387
2460
|
if pair && ((parent != SELF_REF && parent != PARENT) ||
|
|
2388
2461
|
(current != SELF_REF && current != PARENT))
|
|
2389
|
-
mod ||= normalized_path.gsub!(
|
|
2390
|
-
Regexp.new(
|
|
2391
|
-
"/#{Regexp.escape(parent.to_s)}/\\.\\./|" +
|
|
2392
|
-
"(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
|
|
2393
|
-
), SLASH
|
|
2394
|
-
)
|
|
2462
|
+
mod ||= normalized_path.gsub!(Regexp.new(regexp), SLASH)
|
|
2395
2463
|
end
|
|
2396
2464
|
|
|
2397
2465
|
mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
|
|
2398
2466
|
# Non-standard, removes prefixed dotted segments from path.
|
|
2399
2467
|
mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
|
|
2400
|
-
|
|
2468
|
+
break if mod.nil?
|
|
2469
|
+
end
|
|
2401
2470
|
|
|
2402
|
-
|
|
2471
|
+
normalized_path
|
|
2403
2472
|
end
|
|
2404
2473
|
|
|
2405
2474
|
##
|
|
@@ -2449,11 +2518,7 @@ module Addressable
|
|
|
2449
2518
|
# @return [Addressable::URI] <code>self</code>.
|
|
2450
2519
|
def replace_self(uri)
|
|
2451
2520
|
# Reset dependent values
|
|
2452
|
-
|
|
2453
|
-
if instance_variable_defined?(var) && var != :@validation_deferred
|
|
2454
|
-
remove_instance_variable(var)
|
|
2455
|
-
end
|
|
2456
|
-
end
|
|
2521
|
+
reset_ivs
|
|
2457
2522
|
|
|
2458
2523
|
@scheme = uri.scheme
|
|
2459
2524
|
@user = uri.user
|
|
@@ -2485,8 +2550,53 @@ module Addressable
|
|
|
2485
2550
|
#
|
|
2486
2551
|
# @api private
|
|
2487
2552
|
def remove_composite_values
|
|
2488
|
-
|
|
2489
|
-
|
|
2553
|
+
@uri_string = nil
|
|
2554
|
+
@hash = nil
|
|
2490
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
|
|
2491
2601
|
end
|
|
2492
2602
|
end
|