addressable 2.4.0 → 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 +5 -5
- data/CHANGELOG.md +167 -33
- data/README.md +36 -20
- data/lib/addressable/idna/native.rb +19 -8
- data/lib/addressable/idna/pure.rb +4263 -225
- data/lib/addressable/idna.rb +3 -2
- data/lib/addressable/template.rb +90 -101
- data/lib/addressable/uri.rb +414 -236
- data/lib/addressable/version.rb +4 -3
- data/lib/addressable.rb +2 -0
- metadata +21 -35
- data/Gemfile +0 -27
- data/Rakefile +0 -32
- data/addressable.gemspec +0 -34
- data/data/unicode.data +0 -0
- data/spec/addressable/idna_spec.rb +0 -270
- data/spec/addressable/net_http_compat_spec.rb +0 -28
- data/spec/addressable/rack_mount_compat_spec.rb +0 -104
- data/spec/addressable/security_spec.rb +0 -57
- data/spec/addressable/template_spec.rb +0 -1386
- data/spec/addressable/uri_spec.rb +0 -6205
- data/spec/spec_helper.rb +0 -21
- data/tasks/clobber.rake +0 -2
- data/tasks/gem.rake +0 -97
- data/tasks/git.rake +0 -45
- data/tasks/metrics.rake +0 -22
- data/tasks/rspec.rake +0 -21
- data/tasks/yard.rake +0 -27
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,20 +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
|
-
|
|
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}]/
|
|
52
82
|
end
|
|
53
83
|
|
|
54
84
|
SLASH = '/'
|
|
@@ -70,7 +100,7 @@ module Addressable
|
|
|
70
100
|
"wais" => 210,
|
|
71
101
|
"ldap" => 389,
|
|
72
102
|
"prospero" => 1525
|
|
73
|
-
}
|
|
103
|
+
}.freeze
|
|
74
104
|
|
|
75
105
|
##
|
|
76
106
|
# Returns a URI object based on the parsed string.
|
|
@@ -100,7 +130,7 @@ module Addressable
|
|
|
100
130
|
uri = uri.to_str
|
|
101
131
|
rescue TypeError, NoMethodError
|
|
102
132
|
raise TypeError, "Can't convert #{uri.class} into String."
|
|
103
|
-
end
|
|
133
|
+
end unless uri.is_a?(String)
|
|
104
134
|
|
|
105
135
|
# This Regexp supplied as an example in RFC 3986, and it works great.
|
|
106
136
|
scan = uri.scan(URIREGEX)
|
|
@@ -121,15 +151,15 @@ module Addressable
|
|
|
121
151
|
user = userinfo.strip[/^([^:]*):?/, 1]
|
|
122
152
|
password = userinfo.strip[/:(.*)$/, 1]
|
|
123
153
|
end
|
|
124
|
-
|
|
154
|
+
|
|
155
|
+
host = authority.sub(
|
|
125
156
|
/^([^\[\]]*)@/, EMPTY_STR
|
|
126
|
-
).
|
|
157
|
+
).sub(
|
|
127
158
|
/:([^:@\[\]]*?)$/, EMPTY_STR
|
|
128
159
|
)
|
|
160
|
+
|
|
129
161
|
port = authority[/:([^:@\[\]]*?)$/, 1]
|
|
130
|
-
|
|
131
|
-
if port == EMPTY_STR
|
|
132
|
-
port = nil
|
|
162
|
+
port = nil if port == EMPTY_STR
|
|
133
163
|
end
|
|
134
164
|
|
|
135
165
|
return new(
|
|
@@ -172,37 +202,54 @@ module Addressable
|
|
|
172
202
|
uri = uri.to_s
|
|
173
203
|
end
|
|
174
204
|
|
|
175
|
-
|
|
205
|
+
unless uri.respond_to?(:to_str)
|
|
176
206
|
raise TypeError, "Can't convert #{uri.class} into String."
|
|
177
207
|
end
|
|
178
208
|
# Otherwise, convert to a String
|
|
179
|
-
uri = uri.to_str.dup
|
|
209
|
+
uri = uri.to_str.dup.strip
|
|
180
210
|
hints = {
|
|
181
211
|
:scheme => "http"
|
|
182
212
|
}.merge(hints)
|
|
183
213
|
case uri
|
|
184
|
-
when /^http
|
|
185
|
-
uri.
|
|
186
|
-
when /^https
|
|
187
|
-
uri.
|
|
188
|
-
when /^feed:\/+http
|
|
189
|
-
uri.
|
|
190
|
-
when /^feed
|
|
191
|
-
uri.
|
|
192
|
-
when
|
|
193
|
-
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:///")
|
|
194
228
|
when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
|
195
|
-
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
|
|
196
242
|
end
|
|
197
243
|
parsed = self.parse(uri)
|
|
198
244
|
if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
|
|
199
245
|
parsed = self.parse(hints[:scheme] + "://" + uri)
|
|
200
246
|
end
|
|
201
247
|
if parsed.path.include?(".")
|
|
202
|
-
|
|
203
|
-
|
|
248
|
+
if parsed.path[/\b@\b/]
|
|
249
|
+
parsed.scheme = "mailto" unless parsed.scheme
|
|
250
|
+
elsif new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
|
|
204
251
|
parsed.defer_validation do
|
|
205
|
-
new_path = parsed.path.
|
|
252
|
+
new_path = parsed.path.sub(
|
|
206
253
|
Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
|
|
207
254
|
parsed.host = new_host
|
|
208
255
|
parsed.path = new_path
|
|
@@ -247,30 +294,30 @@ module Addressable
|
|
|
247
294
|
return nil unless path
|
|
248
295
|
# If a URI object is passed, just return itself.
|
|
249
296
|
return path if path.kind_of?(self)
|
|
250
|
-
|
|
297
|
+
unless path.respond_to?(:to_str)
|
|
251
298
|
raise TypeError, "Can't convert #{path.class} into String."
|
|
252
299
|
end
|
|
253
300
|
# Otherwise, convert to a String
|
|
254
301
|
path = path.to_str.strip
|
|
255
302
|
|
|
256
|
-
path.
|
|
303
|
+
path.sub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
|
|
257
304
|
path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/
|
|
258
305
|
uri = self.parse(path)
|
|
259
306
|
|
|
260
307
|
if uri.scheme == nil
|
|
261
308
|
# Adjust windows-style uris
|
|
262
|
-
uri.path.
|
|
309
|
+
uri.path.sub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
|
|
263
310
|
"/#{$1.downcase}:/"
|
|
264
311
|
end
|
|
265
|
-
uri.path.
|
|
312
|
+
uri.path.tr!("\\", SLASH)
|
|
266
313
|
if File.exist?(uri.path) &&
|
|
267
314
|
File.stat(uri.path).directory?
|
|
268
|
-
uri.path.
|
|
315
|
+
uri.path.chomp!(SLASH)
|
|
269
316
|
uri.path = uri.path + '/'
|
|
270
317
|
end
|
|
271
318
|
|
|
272
319
|
# If the path is absolute, set the scheme and host.
|
|
273
|
-
if uri.path
|
|
320
|
+
if uri.path.start_with?(SLASH)
|
|
274
321
|
uri.scheme = "file"
|
|
275
322
|
uri.host = EMPTY_STR
|
|
276
323
|
end
|
|
@@ -295,18 +342,29 @@ module Addressable
|
|
|
295
342
|
# #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
|
|
296
343
|
def self.join(*uris)
|
|
297
344
|
uri_objects = uris.collect do |uri|
|
|
298
|
-
|
|
345
|
+
unless uri.respond_to?(:to_str)
|
|
299
346
|
raise TypeError, "Can't convert #{uri.class} into String."
|
|
300
347
|
end
|
|
301
348
|
uri.kind_of?(self) ? uri : self.parse(uri.to_str)
|
|
302
349
|
end
|
|
303
350
|
result = uri_objects.shift.dup
|
|
304
|
-
|
|
351
|
+
uri_objects.each do |uri|
|
|
305
352
|
result.join!(uri)
|
|
306
353
|
end
|
|
307
354
|
return result
|
|
308
355
|
end
|
|
309
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
|
+
|
|
310
368
|
##
|
|
311
369
|
# Percent encodes a URI component.
|
|
312
370
|
#
|
|
@@ -342,9 +400,7 @@ module Addressable
|
|
|
342
400
|
# "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
|
|
343
401
|
# )
|
|
344
402
|
# => "simple%2Fexample"
|
|
345
|
-
def self.encode_component(component, character_class=
|
|
346
|
-
CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
|
|
347
|
-
upcase_encoded='')
|
|
403
|
+
def self.encode_component(component, character_class=CharacterClassesRegexps::RESERVED_AND_UNRESERVED, upcase_encoded='')
|
|
348
404
|
return nil if component.nil?
|
|
349
405
|
|
|
350
406
|
begin
|
|
@@ -367,26 +423,27 @@ module Addressable
|
|
|
367
423
|
if character_class.kind_of?(String)
|
|
368
424
|
character_class = /[^#{character_class}]/
|
|
369
425
|
end
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
component.force_encoding(Encoding::ASCII_8BIT)
|
|
375
|
-
end
|
|
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)
|
|
376
430
|
# Avoiding gsub! because there are edge cases with frozen strings
|
|
377
|
-
component = component.gsub(character_class) do |
|
|
378
|
-
|
|
431
|
+
component = component.gsub(character_class) do |char|
|
|
432
|
+
SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[char.ord]
|
|
379
433
|
end
|
|
380
434
|
if upcase_encoded.length > 0
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
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)
|
|
384
440
|
end
|
|
441
|
+
|
|
385
442
|
return component
|
|
386
443
|
end
|
|
387
444
|
|
|
388
445
|
class << self
|
|
389
|
-
alias_method :
|
|
446
|
+
alias_method :escape_component, :encode_component
|
|
390
447
|
end
|
|
391
448
|
|
|
392
449
|
##
|
|
@@ -425,16 +482,14 @@ module Addressable
|
|
|
425
482
|
"Expected Class (String or Addressable::URI), " +
|
|
426
483
|
"got #{return_type.inspect}"
|
|
427
484
|
end
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
uri.force_encoding("utf-8") if uri.respond_to?(:force_encoding)
|
|
431
|
-
leave_encoded.force_encoding("utf-8") if leave_encoded.respond_to?(:force_encoding)
|
|
432
|
-
result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
|
|
485
|
+
|
|
486
|
+
result = uri.gsub(/%[0-9a-f]{2}/i) do |sequence|
|
|
433
487
|
c = sequence[1..3].to_i(16).chr
|
|
434
|
-
c.force_encoding(
|
|
488
|
+
c.force_encoding(sequence.encoding)
|
|
435
489
|
leave_encoded.include?(c) ? sequence : c
|
|
436
490
|
end
|
|
437
|
-
|
|
491
|
+
|
|
492
|
+
result.force_encoding(Encoding::UTF_8)
|
|
438
493
|
if return_type == String
|
|
439
494
|
return result
|
|
440
495
|
elsif return_type == ::Addressable::URI
|
|
@@ -495,7 +550,7 @@ module Addressable
|
|
|
495
550
|
# )
|
|
496
551
|
# => "one two%2Fthree&four"
|
|
497
552
|
def self.normalize_component(component, character_class=
|
|
498
|
-
|
|
553
|
+
CharacterClassesRegexps::RESERVED_AND_UNRESERVED,
|
|
499
554
|
leave_encoded='')
|
|
500
555
|
return nil if component.nil?
|
|
501
556
|
|
|
@@ -513,33 +568,32 @@ module Addressable
|
|
|
513
568
|
leave_re = if leave_encoded.length > 0
|
|
514
569
|
character_class = "#{character_class}%" unless character_class.include?('%')
|
|
515
570
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
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})"
|
|
520
574
|
end
|
|
521
575
|
|
|
522
|
-
character_class =
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
component = component.dup
|
|
528
|
-
component.force_encoding(Encoding::ASCII_8BIT)
|
|
576
|
+
character_class = if leave_re
|
|
577
|
+
/[^#{character_class}]#{leave_re}/
|
|
578
|
+
else
|
|
579
|
+
/[^#{character_class}]/
|
|
580
|
+
end
|
|
529
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)
|
|
530
586
|
unencoded = self.unencode_component(component, String, leave_encoded)
|
|
531
587
|
begin
|
|
532
588
|
encoded = self.encode_component(
|
|
533
|
-
|
|
589
|
+
unencoded.unicode_normalize(:nfc),
|
|
534
590
|
character_class,
|
|
535
591
|
leave_encoded
|
|
536
592
|
)
|
|
537
593
|
rescue ArgumentError
|
|
538
594
|
encoded = self.encode_component(unencoded)
|
|
539
595
|
end
|
|
540
|
-
|
|
541
|
-
encoded.force_encoding(Encoding::UTF_8)
|
|
542
|
-
end
|
|
596
|
+
encoded.force_encoding(Encoding::UTF_8)
|
|
543
597
|
return encoded
|
|
544
598
|
end
|
|
545
599
|
|
|
@@ -576,15 +630,15 @@ module Addressable
|
|
|
576
630
|
uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
|
|
577
631
|
encoded_uri = Addressable::URI.new(
|
|
578
632
|
:scheme => self.encode_component(uri_object.scheme,
|
|
579
|
-
Addressable::URI::
|
|
633
|
+
Addressable::URI::CharacterClassesRegexps::SCHEME),
|
|
580
634
|
:authority => self.encode_component(uri_object.authority,
|
|
581
|
-
Addressable::URI::
|
|
635
|
+
Addressable::URI::CharacterClassesRegexps::AUTHORITY),
|
|
582
636
|
:path => self.encode_component(uri_object.path,
|
|
583
|
-
Addressable::URI::
|
|
637
|
+
Addressable::URI::CharacterClassesRegexps::PATH),
|
|
584
638
|
:query => self.encode_component(uri_object.query,
|
|
585
|
-
Addressable::URI::
|
|
639
|
+
Addressable::URI::CharacterClassesRegexps::QUERY),
|
|
586
640
|
:fragment => self.encode_component(uri_object.fragment,
|
|
587
|
-
Addressable::URI::
|
|
641
|
+
Addressable::URI::CharacterClassesRegexps::FRAGMENT)
|
|
588
642
|
)
|
|
589
643
|
if return_type == String
|
|
590
644
|
return encoded_uri.to_s
|
|
@@ -640,8 +694,7 @@ module Addressable
|
|
|
640
694
|
components.each do |key, value|
|
|
641
695
|
if value != nil
|
|
642
696
|
begin
|
|
643
|
-
components[key] =
|
|
644
|
-
Addressable::IDNA.unicode_normalize_kc(value.to_str)
|
|
697
|
+
components[key] = value.to_str.unicode_normalize(:nfc)
|
|
645
698
|
rescue ArgumentError
|
|
646
699
|
# Likely a malformed UTF-8 character, skip unicode normalization
|
|
647
700
|
components[key] = value.to_str
|
|
@@ -650,19 +703,19 @@ module Addressable
|
|
|
650
703
|
end
|
|
651
704
|
encoded_uri = Addressable::URI.new(
|
|
652
705
|
:scheme => self.encode_component(components[:scheme],
|
|
653
|
-
Addressable::URI::
|
|
706
|
+
Addressable::URI::CharacterClassesRegexps::SCHEME),
|
|
654
707
|
:user => self.encode_component(components[:user],
|
|
655
|
-
Addressable::URI::
|
|
708
|
+
Addressable::URI::CharacterClassesRegexps::UNRESERVED),
|
|
656
709
|
:password => self.encode_component(components[:password],
|
|
657
|
-
Addressable::URI::
|
|
710
|
+
Addressable::URI::CharacterClassesRegexps::UNRESERVED),
|
|
658
711
|
:host => components[:host],
|
|
659
712
|
:port => components[:port],
|
|
660
713
|
:path => self.encode_component(components[:path],
|
|
661
|
-
Addressable::URI::
|
|
714
|
+
Addressable::URI::CharacterClassesRegexps::PATH),
|
|
662
715
|
:query => self.encode_component(components[:query],
|
|
663
|
-
Addressable::URI::
|
|
716
|
+
Addressable::URI::CharacterClassesRegexps::QUERY),
|
|
664
717
|
:fragment => self.encode_component(components[:fragment],
|
|
665
|
-
Addressable::URI::
|
|
718
|
+
Addressable::URI::CharacterClassesRegexps::FRAGMENT)
|
|
666
719
|
)
|
|
667
720
|
if return_type == String
|
|
668
721
|
return encoded_uri.to_s
|
|
@@ -713,11 +766,11 @@ module Addressable
|
|
|
713
766
|
[
|
|
714
767
|
self.encode_component(
|
|
715
768
|
key.gsub(/(\r\n|\n|\r)/, "\r\n"),
|
|
716
|
-
|
|
769
|
+
CharacterClassesRegexps::UNRESERVED
|
|
717
770
|
).gsub("%20", "+"),
|
|
718
771
|
self.encode_component(
|
|
719
772
|
value.gsub(/(\r\n|\n|\r)/, "\r\n"),
|
|
720
|
-
|
|
773
|
+
CharacterClassesRegexps::UNRESERVED
|
|
721
774
|
).gsub("%20", "+")
|
|
722
775
|
]
|
|
723
776
|
end
|
|
@@ -789,7 +842,9 @@ module Addressable
|
|
|
789
842
|
end
|
|
790
843
|
end
|
|
791
844
|
|
|
792
|
-
|
|
845
|
+
reset_ivs
|
|
846
|
+
|
|
847
|
+
defer_validation do
|
|
793
848
|
# Bunch of crazy logic required because of the composite components
|
|
794
849
|
# like userinfo and authority.
|
|
795
850
|
self.scheme = options[:scheme] if options[:scheme]
|
|
@@ -804,7 +859,8 @@ module Addressable
|
|
|
804
859
|
self.query_values = options[:query_values] if options[:query_values]
|
|
805
860
|
self.fragment = options[:fragment] if options[:fragment]
|
|
806
861
|
end
|
|
807
|
-
|
|
862
|
+
|
|
863
|
+
to_s # force path validation
|
|
808
864
|
end
|
|
809
865
|
|
|
810
866
|
##
|
|
@@ -831,9 +887,7 @@ module Addressable
|
|
|
831
887
|
# The scheme component for this URI.
|
|
832
888
|
#
|
|
833
889
|
# @return [String] The scheme component.
|
|
834
|
-
|
|
835
|
-
return defined?(@scheme) ? @scheme : nil
|
|
836
|
-
end
|
|
890
|
+
attr_reader :scheme
|
|
837
891
|
|
|
838
892
|
##
|
|
839
893
|
# The scheme component for this URI, normalized.
|
|
@@ -841,16 +895,19 @@ module Addressable
|
|
|
841
895
|
# @return [String] The scheme component, normalized.
|
|
842
896
|
def normalized_scheme
|
|
843
897
|
return nil unless self.scheme
|
|
844
|
-
@normalized_scheme
|
|
845
|
-
if self.scheme =~ /^\s*ssh\+svn\s*$/i
|
|
846
|
-
"svn+ssh"
|
|
898
|
+
if @normalized_scheme == NONE
|
|
899
|
+
@normalized_scheme = if self.scheme =~ /^\s*ssh\+svn\s*$/i
|
|
900
|
+
"svn+ssh".dup
|
|
847
901
|
else
|
|
848
902
|
Addressable::URI.normalize_component(
|
|
849
903
|
self.scheme.strip.downcase,
|
|
850
|
-
Addressable::URI::
|
|
904
|
+
Addressable::URI::NormalizeCharacterClasses::SCHEME
|
|
851
905
|
)
|
|
852
906
|
end
|
|
853
907
|
end
|
|
908
|
+
# All normalized values should be UTF-8
|
|
909
|
+
force_utf8_encoding_if_needed(@normalized_scheme)
|
|
910
|
+
@normalized_scheme
|
|
854
911
|
end
|
|
855
912
|
|
|
856
913
|
##
|
|
@@ -864,13 +921,13 @@ module Addressable
|
|
|
864
921
|
new_scheme = new_scheme.to_str
|
|
865
922
|
end
|
|
866
923
|
if new_scheme && new_scheme !~ /\A[a-z][a-z0-9\.\+\-]*\z/i
|
|
867
|
-
raise InvalidURIError, "Invalid scheme format: #{new_scheme}"
|
|
924
|
+
raise InvalidURIError, "Invalid scheme format: '#{new_scheme}'"
|
|
868
925
|
end
|
|
869
926
|
@scheme = new_scheme
|
|
870
927
|
@scheme = nil if @scheme.to_s.strip.empty?
|
|
871
928
|
|
|
872
929
|
# Reset dependent values
|
|
873
|
-
|
|
930
|
+
@normalized_scheme = NONE
|
|
874
931
|
remove_composite_values
|
|
875
932
|
|
|
876
933
|
# Ensure we haven't created an invalid URI
|
|
@@ -881,9 +938,7 @@ module Addressable
|
|
|
881
938
|
# The user component for this URI.
|
|
882
939
|
#
|
|
883
940
|
# @return [String] The user component.
|
|
884
|
-
|
|
885
|
-
return defined?(@user) ? @user : nil
|
|
886
|
-
end
|
|
941
|
+
attr_reader :user
|
|
887
942
|
|
|
888
943
|
##
|
|
889
944
|
# The user component for this URI, normalized.
|
|
@@ -891,18 +946,21 @@ module Addressable
|
|
|
891
946
|
# @return [String] The user component, normalized.
|
|
892
947
|
def normalized_user
|
|
893
948
|
return nil unless self.user
|
|
894
|
-
return @normalized_user
|
|
895
|
-
@normalized_user
|
|
949
|
+
return @normalized_user unless @normalized_user == NONE
|
|
950
|
+
@normalized_user = begin
|
|
896
951
|
if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
|
|
897
952
|
(!self.password || self.password.strip.empty?)
|
|
898
953
|
nil
|
|
899
954
|
else
|
|
900
955
|
Addressable::URI.normalize_component(
|
|
901
956
|
self.user.strip,
|
|
902
|
-
Addressable::URI::
|
|
957
|
+
Addressable::URI::NormalizeCharacterClasses::UNRESERVED
|
|
903
958
|
)
|
|
904
959
|
end
|
|
905
960
|
end
|
|
961
|
+
# All normalized values should be UTF-8
|
|
962
|
+
force_utf8_encoding_if_needed(@normalized_user)
|
|
963
|
+
@normalized_user
|
|
906
964
|
end
|
|
907
965
|
|
|
908
966
|
##
|
|
@@ -917,14 +975,14 @@ module Addressable
|
|
|
917
975
|
|
|
918
976
|
# You can't have a nil user with a non-nil password
|
|
919
977
|
if password != nil
|
|
920
|
-
@user = EMPTY_STR
|
|
978
|
+
@user = EMPTY_STR unless user
|
|
921
979
|
end
|
|
922
980
|
|
|
923
981
|
# Reset dependent values
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
982
|
+
@userinfo = nil
|
|
983
|
+
@normalized_userinfo = NONE
|
|
984
|
+
@authority = nil
|
|
985
|
+
@normalized_user = NONE
|
|
928
986
|
remove_composite_values
|
|
929
987
|
|
|
930
988
|
# Ensure we haven't created an invalid URI
|
|
@@ -935,9 +993,7 @@ module Addressable
|
|
|
935
993
|
# The password component for this URI.
|
|
936
994
|
#
|
|
937
995
|
# @return [String] The password component.
|
|
938
|
-
|
|
939
|
-
return defined?(@password) ? @password : nil
|
|
940
|
-
end
|
|
996
|
+
attr_reader :password
|
|
941
997
|
|
|
942
998
|
##
|
|
943
999
|
# The password component for this URI, normalized.
|
|
@@ -945,18 +1001,21 @@ module Addressable
|
|
|
945
1001
|
# @return [String] The password component, normalized.
|
|
946
1002
|
def normalized_password
|
|
947
1003
|
return nil unless self.password
|
|
948
|
-
return @normalized_password
|
|
949
|
-
@normalized_password
|
|
1004
|
+
return @normalized_password unless @normalized_password == NONE
|
|
1005
|
+
@normalized_password = begin
|
|
950
1006
|
if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
|
|
951
1007
|
(!self.user || self.user.strip.empty?)
|
|
952
1008
|
nil
|
|
953
1009
|
else
|
|
954
1010
|
Addressable::URI.normalize_component(
|
|
955
1011
|
self.password.strip,
|
|
956
|
-
Addressable::URI::
|
|
1012
|
+
Addressable::URI::NormalizeCharacterClasses::UNRESERVED
|
|
957
1013
|
)
|
|
958
1014
|
end
|
|
959
1015
|
end
|
|
1016
|
+
# All normalized values should be UTF-8
|
|
1017
|
+
force_utf8_encoding_if_needed(@normalized_password)
|
|
1018
|
+
@normalized_password
|
|
960
1019
|
end
|
|
961
1020
|
|
|
962
1021
|
##
|
|
@@ -970,17 +1029,15 @@ module Addressable
|
|
|
970
1029
|
@password = new_password ? new_password.to_str : nil
|
|
971
1030
|
|
|
972
1031
|
# You can't have a nil user with a non-nil password
|
|
973
|
-
@password ||= nil
|
|
974
|
-
@user ||= nil
|
|
975
1032
|
if @password != nil
|
|
976
|
-
|
|
1033
|
+
self.user = EMPTY_STR if user.nil?
|
|
977
1034
|
end
|
|
978
1035
|
|
|
979
1036
|
# Reset dependent values
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1037
|
+
@userinfo = nil
|
|
1038
|
+
@normalized_userinfo = NONE
|
|
1039
|
+
@authority = nil
|
|
1040
|
+
@normalized_password = NONE
|
|
984
1041
|
remove_composite_values
|
|
985
1042
|
|
|
986
1043
|
# Ensure we haven't created an invalid URI
|
|
@@ -1010,18 +1067,21 @@ module Addressable
|
|
|
1010
1067
|
# @return [String] The userinfo component, normalized.
|
|
1011
1068
|
def normalized_userinfo
|
|
1012
1069
|
return nil unless self.userinfo
|
|
1013
|
-
return @normalized_userinfo
|
|
1014
|
-
@normalized_userinfo
|
|
1070
|
+
return @normalized_userinfo unless @normalized_userinfo == NONE
|
|
1071
|
+
@normalized_userinfo = begin
|
|
1015
1072
|
current_user = self.normalized_user
|
|
1016
1073
|
current_password = self.normalized_password
|
|
1017
1074
|
if !current_user && !current_password
|
|
1018
1075
|
nil
|
|
1019
1076
|
elsif current_user && current_password
|
|
1020
|
-
"#{current_user}:#{current_password}"
|
|
1077
|
+
"#{current_user}:#{current_password}".dup
|
|
1021
1078
|
elsif current_user && !current_password
|
|
1022
|
-
"#{current_user}"
|
|
1079
|
+
"#{current_user}".dup
|
|
1023
1080
|
end
|
|
1024
1081
|
end
|
|
1082
|
+
# All normalized values should be UTF-8
|
|
1083
|
+
force_utf8_encoding_if_needed(@normalized_userinfo)
|
|
1084
|
+
@normalized_userinfo
|
|
1025
1085
|
end
|
|
1026
1086
|
|
|
1027
1087
|
##
|
|
@@ -1046,7 +1106,7 @@ module Addressable
|
|
|
1046
1106
|
self.user = new_user
|
|
1047
1107
|
|
|
1048
1108
|
# Reset dependent values
|
|
1049
|
-
|
|
1109
|
+
@authority = nil
|
|
1050
1110
|
remove_composite_values
|
|
1051
1111
|
|
|
1052
1112
|
# Ensure we haven't created an invalid URI
|
|
@@ -1057,9 +1117,7 @@ module Addressable
|
|
|
1057
1117
|
# The host component for this URI.
|
|
1058
1118
|
#
|
|
1059
1119
|
# @return [String] The host component.
|
|
1060
|
-
|
|
1061
|
-
return defined?(@host) ? @host : nil
|
|
1062
|
-
end
|
|
1120
|
+
attr_reader :host
|
|
1063
1121
|
|
|
1064
1122
|
##
|
|
1065
1123
|
# The host component for this URI, normalized.
|
|
@@ -1067,6 +1125,7 @@ module Addressable
|
|
|
1067
1125
|
# @return [String] The host component, normalized.
|
|
1068
1126
|
def normalized_host
|
|
1069
1127
|
return nil unless self.host
|
|
1128
|
+
|
|
1070
1129
|
@normalized_host ||= begin
|
|
1071
1130
|
if !self.host.strip.empty?
|
|
1072
1131
|
result = ::Addressable::IDNA.to_ascii(
|
|
@@ -1078,12 +1137,16 @@ module Addressable
|
|
|
1078
1137
|
end
|
|
1079
1138
|
result = Addressable::URI.normalize_component(
|
|
1080
1139
|
result,
|
|
1081
|
-
|
|
1140
|
+
NormalizeCharacterClasses::HOST
|
|
1141
|
+
)
|
|
1082
1142
|
result
|
|
1083
1143
|
else
|
|
1084
|
-
EMPTY_STR
|
|
1144
|
+
EMPTY_STR.dup
|
|
1085
1145
|
end
|
|
1086
1146
|
end
|
|
1147
|
+
# All normalized values should be UTF-8
|
|
1148
|
+
force_utf8_encoding_if_needed(@normalized_host)
|
|
1149
|
+
@normalized_host
|
|
1087
1150
|
end
|
|
1088
1151
|
|
|
1089
1152
|
##
|
|
@@ -1096,17 +1159,9 @@ module Addressable
|
|
|
1096
1159
|
end
|
|
1097
1160
|
@host = new_host ? new_host.to_str : nil
|
|
1098
1161
|
|
|
1099
|
-
unreserved = CharacterClasses::UNRESERVED
|
|
1100
|
-
sub_delims = CharacterClasses::SUB_DELIMS
|
|
1101
|
-
if !@host.nil? && (@host =~ /[<>{}\/\?\#\@"[[:space:]]]/ ||
|
|
1102
|
-
(@host[/^\[(.*)\]$/, 1] != nil && @host[/^\[(.*)\]$/, 1] !~
|
|
1103
|
-
Regexp.new("^[#{unreserved}#{sub_delims}:]*$")))
|
|
1104
|
-
raise InvalidURIError, "Invalid character in host: '#{@host.to_s}'"
|
|
1105
|
-
end
|
|
1106
|
-
|
|
1107
1162
|
# Reset dependent values
|
|
1108
|
-
|
|
1109
|
-
|
|
1163
|
+
@authority = nil
|
|
1164
|
+
@normalized_host = nil
|
|
1110
1165
|
remove_composite_values
|
|
1111
1166
|
|
|
1112
1167
|
# Ensure we haven't created an invalid URI
|
|
@@ -1144,6 +1199,33 @@ module Addressable
|
|
|
1144
1199
|
self.host = v
|
|
1145
1200
|
end
|
|
1146
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
|
|
1228
|
+
|
|
1147
1229
|
##
|
|
1148
1230
|
# The authority component for this URI.
|
|
1149
1231
|
# Combines the user, password, host, and port components.
|
|
@@ -1151,7 +1233,7 @@ module Addressable
|
|
|
1151
1233
|
# @return [String] The authority component.
|
|
1152
1234
|
def authority
|
|
1153
1235
|
self.host && @authority ||= begin
|
|
1154
|
-
authority =
|
|
1236
|
+
authority = String.new
|
|
1155
1237
|
if self.userinfo != nil
|
|
1156
1238
|
authority << "#{self.userinfo}@"
|
|
1157
1239
|
end
|
|
@@ -1170,7 +1252,7 @@ module Addressable
|
|
|
1170
1252
|
def normalized_authority
|
|
1171
1253
|
return nil unless self.authority
|
|
1172
1254
|
@normalized_authority ||= begin
|
|
1173
|
-
authority =
|
|
1255
|
+
authority = String.new
|
|
1174
1256
|
if self.normalized_userinfo != nil
|
|
1175
1257
|
authority << "#{self.normalized_userinfo}@"
|
|
1176
1258
|
end
|
|
@@ -1180,6 +1262,9 @@ module Addressable
|
|
|
1180
1262
|
end
|
|
1181
1263
|
authority
|
|
1182
1264
|
end
|
|
1265
|
+
# All normalized values should be UTF-8
|
|
1266
|
+
force_utf8_encoding_if_needed(@normalized_authority)
|
|
1267
|
+
@normalized_authority
|
|
1183
1268
|
end
|
|
1184
1269
|
|
|
1185
1270
|
##
|
|
@@ -1197,9 +1282,9 @@ module Addressable
|
|
|
1197
1282
|
new_user = new_userinfo.strip[/^([^:]*):?/, 1]
|
|
1198
1283
|
new_password = new_userinfo.strip[/:(.*)$/, 1]
|
|
1199
1284
|
end
|
|
1200
|
-
new_host = new_authority.
|
|
1285
|
+
new_host = new_authority.sub(
|
|
1201
1286
|
/^([^\[\]]*)@/, EMPTY_STR
|
|
1202
|
-
).
|
|
1287
|
+
).sub(
|
|
1203
1288
|
/:([^:@\[\]]*?)$/, EMPTY_STR
|
|
1204
1289
|
)
|
|
1205
1290
|
new_port =
|
|
@@ -1207,14 +1292,14 @@ module Addressable
|
|
|
1207
1292
|
end
|
|
1208
1293
|
|
|
1209
1294
|
# Password assigned first to ensure validity in case of nil
|
|
1210
|
-
self.password =
|
|
1211
|
-
self.user =
|
|
1212
|
-
self.host =
|
|
1213
|
-
self.port =
|
|
1295
|
+
self.password = new_password
|
|
1296
|
+
self.user = new_user
|
|
1297
|
+
self.host = new_host
|
|
1298
|
+
self.port = new_port
|
|
1214
1299
|
|
|
1215
1300
|
# Reset dependent values
|
|
1216
|
-
|
|
1217
|
-
|
|
1301
|
+
@userinfo = nil
|
|
1302
|
+
@normalized_userinfo = NONE
|
|
1218
1303
|
remove_composite_values
|
|
1219
1304
|
|
|
1220
1305
|
# Ensure we haven't created an invalid URI
|
|
@@ -1262,16 +1347,16 @@ module Addressable
|
|
|
1262
1347
|
new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
|
|
1263
1348
|
end
|
|
1264
1349
|
|
|
1265
|
-
self.scheme =
|
|
1266
|
-
self.host =
|
|
1267
|
-
self.port =
|
|
1350
|
+
self.scheme = new_scheme
|
|
1351
|
+
self.host = new_host
|
|
1352
|
+
self.port = new_port
|
|
1268
1353
|
self.userinfo = nil
|
|
1269
1354
|
|
|
1270
1355
|
# Reset dependent values
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1356
|
+
@userinfo = nil
|
|
1357
|
+
@normalized_userinfo = NONE
|
|
1358
|
+
@authority = nil
|
|
1359
|
+
@normalized_authority = nil
|
|
1275
1360
|
remove_composite_values
|
|
1276
1361
|
|
|
1277
1362
|
# Ensure we haven't created an invalid URI
|
|
@@ -1298,9 +1383,7 @@ module Addressable
|
|
|
1298
1383
|
# infer port numbers from default values.
|
|
1299
1384
|
#
|
|
1300
1385
|
# @return [Integer] The port component.
|
|
1301
|
-
|
|
1302
|
-
return defined?(@port) ? @port : nil
|
|
1303
|
-
end
|
|
1386
|
+
attr_reader :port
|
|
1304
1387
|
|
|
1305
1388
|
##
|
|
1306
1389
|
# The port component for this URI, normalized.
|
|
@@ -1308,8 +1391,8 @@ module Addressable
|
|
|
1308
1391
|
# @return [Integer] The port component, normalized.
|
|
1309
1392
|
def normalized_port
|
|
1310
1393
|
return nil unless self.port
|
|
1311
|
-
return @normalized_port
|
|
1312
|
-
@normalized_port
|
|
1394
|
+
return @normalized_port unless @normalized_port == NONE
|
|
1395
|
+
@normalized_port = begin
|
|
1313
1396
|
if URI.port_mapping[self.normalized_scheme] == self.port
|
|
1314
1397
|
nil
|
|
1315
1398
|
else
|
|
@@ -1340,8 +1423,8 @@ module Addressable
|
|
|
1340
1423
|
@port = nil if @port == 0
|
|
1341
1424
|
|
|
1342
1425
|
# Reset dependent values
|
|
1343
|
-
|
|
1344
|
-
|
|
1426
|
+
@authority = nil
|
|
1427
|
+
@normalized_port = NONE
|
|
1345
1428
|
remove_composite_values
|
|
1346
1429
|
|
|
1347
1430
|
# Ensure we haven't created an invalid URI
|
|
@@ -1383,7 +1466,7 @@ module Addressable
|
|
|
1383
1466
|
# @return [String] The components that identify a site.
|
|
1384
1467
|
def site
|
|
1385
1468
|
(self.scheme || self.authority) && @site ||= begin
|
|
1386
|
-
site_string = ""
|
|
1469
|
+
site_string = "".dup
|
|
1387
1470
|
site_string << "#{self.scheme}:" if self.scheme != nil
|
|
1388
1471
|
site_string << "//#{self.authority}" if self.authority != nil
|
|
1389
1472
|
site_string
|
|
@@ -1402,7 +1485,7 @@ module Addressable
|
|
|
1402
1485
|
def normalized_site
|
|
1403
1486
|
return nil unless self.site
|
|
1404
1487
|
@normalized_site ||= begin
|
|
1405
|
-
site_string = ""
|
|
1488
|
+
site_string = "".dup
|
|
1406
1489
|
if self.normalized_scheme != nil
|
|
1407
1490
|
site_string << "#{self.normalized_scheme}:"
|
|
1408
1491
|
end
|
|
@@ -1411,6 +1494,9 @@ module Addressable
|
|
|
1411
1494
|
end
|
|
1412
1495
|
site_string
|
|
1413
1496
|
end
|
|
1497
|
+
# All normalized values should be UTF-8
|
|
1498
|
+
force_utf8_encoding_if_needed(@normalized_site)
|
|
1499
|
+
@normalized_site
|
|
1414
1500
|
end
|
|
1415
1501
|
|
|
1416
1502
|
##
|
|
@@ -1439,9 +1525,7 @@ module Addressable
|
|
|
1439
1525
|
# The path component for this URI.
|
|
1440
1526
|
#
|
|
1441
1527
|
# @return [String] The path component.
|
|
1442
|
-
|
|
1443
|
-
return defined?(@path) ? @path : EMPTY_STR
|
|
1444
|
-
end
|
|
1528
|
+
attr_reader :path
|
|
1445
1529
|
|
|
1446
1530
|
NORMPATH = /^(?!\/)[^\/:]*:.*$/
|
|
1447
1531
|
##
|
|
@@ -1455,22 +1539,25 @@ module Addressable
|
|
|
1455
1539
|
# Relative paths with colons in the first segment are ambiguous.
|
|
1456
1540
|
path = path.sub(":", "%2F")
|
|
1457
1541
|
end
|
|
1458
|
-
# String#split(
|
|
1542
|
+
# String#split(delimiter, -1) uses the more strict splitting behavior
|
|
1459
1543
|
# found by default in Python.
|
|
1460
1544
|
result = path.strip.split(SLASH, -1).map do |segment|
|
|
1461
1545
|
Addressable::URI.normalize_component(
|
|
1462
1546
|
segment,
|
|
1463
|
-
Addressable::URI::
|
|
1547
|
+
Addressable::URI::NormalizeCharacterClasses::PCHAR
|
|
1464
1548
|
)
|
|
1465
1549
|
end.join(SLASH)
|
|
1466
1550
|
|
|
1467
1551
|
result = URI.normalize_path(result)
|
|
1468
1552
|
if result.empty? &&
|
|
1469
1553
|
["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
|
|
1470
|
-
result = SLASH
|
|
1554
|
+
result = SLASH.dup
|
|
1471
1555
|
end
|
|
1472
1556
|
result
|
|
1473
1557
|
end
|
|
1558
|
+
# All normalized values should be UTF-8
|
|
1559
|
+
force_utf8_encoding_if_needed(@normalized_path)
|
|
1560
|
+
@normalized_path
|
|
1474
1561
|
end
|
|
1475
1562
|
|
|
1476
1563
|
##
|
|
@@ -1487,8 +1574,11 @@ module Addressable
|
|
|
1487
1574
|
end
|
|
1488
1575
|
|
|
1489
1576
|
# Reset dependent values
|
|
1490
|
-
|
|
1577
|
+
@normalized_path = nil
|
|
1491
1578
|
remove_composite_values
|
|
1579
|
+
|
|
1580
|
+
# Ensure we haven't created an invalid URI
|
|
1581
|
+
validate()
|
|
1492
1582
|
end
|
|
1493
1583
|
|
|
1494
1584
|
##
|
|
@@ -1497,7 +1587,7 @@ module Addressable
|
|
|
1497
1587
|
# @return [String] The path's basename.
|
|
1498
1588
|
def basename
|
|
1499
1589
|
# Path cannot be nil
|
|
1500
|
-
return File.basename(self.path).
|
|
1590
|
+
return File.basename(self.path).sub(/;[^\/]*$/, EMPTY_STR)
|
|
1501
1591
|
end
|
|
1502
1592
|
|
|
1503
1593
|
##
|
|
@@ -1514,9 +1604,7 @@ module Addressable
|
|
|
1514
1604
|
# The query component for this URI.
|
|
1515
1605
|
#
|
|
1516
1606
|
# @return [String] The query component.
|
|
1517
|
-
|
|
1518
|
-
return defined?(@query) ? @query : nil
|
|
1519
|
-
end
|
|
1607
|
+
attr_reader :query
|
|
1520
1608
|
|
|
1521
1609
|
##
|
|
1522
1610
|
# The query component for this URI, normalized.
|
|
@@ -1524,18 +1612,26 @@ module Addressable
|
|
|
1524
1612
|
# @return [String] The query component, normalized.
|
|
1525
1613
|
def normalized_query(*flags)
|
|
1526
1614
|
return nil unless self.query
|
|
1527
|
-
return @normalized_query
|
|
1528
|
-
@normalized_query
|
|
1615
|
+
return @normalized_query unless @normalized_query == NONE
|
|
1616
|
+
@normalized_query = begin
|
|
1529
1617
|
modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
|
|
1530
1618
|
# Make sure possible key-value pair delimiters are escaped.
|
|
1531
1619
|
modified_query_class.sub!("\\&", "").sub!("\\;", "")
|
|
1532
|
-
pairs = (
|
|
1620
|
+
pairs = (query || "").split("&", -1)
|
|
1621
|
+
pairs.delete_if(&:empty?).uniq! if flags.include?(:compacted)
|
|
1533
1622
|
pairs.sort! if flags.include?(:sorted)
|
|
1534
1623
|
component = pairs.map do |pair|
|
|
1535
|
-
Addressable::URI.normalize_component(
|
|
1624
|
+
Addressable::URI.normalize_component(
|
|
1625
|
+
pair,
|
|
1626
|
+
Addressable::URI::NormalizeCharacterClasses::QUERY,
|
|
1627
|
+
"+"
|
|
1628
|
+
)
|
|
1536
1629
|
end.join("&")
|
|
1537
1630
|
component == "" ? nil : component
|
|
1538
1631
|
end
|
|
1632
|
+
# All normalized values should be UTF-8
|
|
1633
|
+
force_utf8_encoding_if_needed(@normalized_query)
|
|
1634
|
+
@normalized_query
|
|
1539
1635
|
end
|
|
1540
1636
|
|
|
1541
1637
|
##
|
|
@@ -1549,7 +1645,7 @@ module Addressable
|
|
|
1549
1645
|
@query = new_query ? new_query.to_str : nil
|
|
1550
1646
|
|
|
1551
1647
|
# Reset dependent values
|
|
1552
|
-
|
|
1648
|
+
@normalized_query = NONE
|
|
1553
1649
|
remove_composite_values
|
|
1554
1650
|
end
|
|
1555
1651
|
|
|
@@ -1588,11 +1684,13 @@ module Addressable
|
|
|
1588
1684
|
# so it's best to make all changes in-place.
|
|
1589
1685
|
pair[0] = URI.unencode_component(pair[0])
|
|
1590
1686
|
if pair[1].respond_to?(:to_str)
|
|
1687
|
+
value = pair[1].to_str
|
|
1591
1688
|
# I loathe the fact that I have to do this. Stupid HTML 4.01.
|
|
1592
1689
|
# Treating '+' as a space was just an unbelievably bad idea.
|
|
1593
1690
|
# There was nothing wrong with '%20'!
|
|
1594
1691
|
# If it ain't broke, don't fix it!
|
|
1595
|
-
|
|
1692
|
+
value = value.tr("+", " ") if ["http", "https", nil].include?(scheme)
|
|
1693
|
+
pair[1] = URI.unencode_component(value)
|
|
1596
1694
|
end
|
|
1597
1695
|
if return_type == Hash
|
|
1598
1696
|
accu[pair[0]] = pair[1]
|
|
@@ -1644,23 +1742,23 @@ module Addressable
|
|
|
1644
1742
|
end
|
|
1645
1743
|
|
|
1646
1744
|
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
|
|
1647
|
-
buffer = ""
|
|
1745
|
+
buffer = "".dup
|
|
1648
1746
|
new_query_values.each do |key, value|
|
|
1649
1747
|
encoded_key = URI.encode_component(
|
|
1650
|
-
key,
|
|
1748
|
+
key, CharacterClassesRegexps::UNRESERVED
|
|
1651
1749
|
)
|
|
1652
1750
|
if value == nil
|
|
1653
1751
|
buffer << "#{encoded_key}&"
|
|
1654
1752
|
elsif value.kind_of?(Array)
|
|
1655
1753
|
value.each do |sub_value|
|
|
1656
1754
|
encoded_value = URI.encode_component(
|
|
1657
|
-
sub_value,
|
|
1755
|
+
sub_value, CharacterClassesRegexps::UNRESERVED
|
|
1658
1756
|
)
|
|
1659
1757
|
buffer << "#{encoded_key}=#{encoded_value}&"
|
|
1660
1758
|
end
|
|
1661
1759
|
else
|
|
1662
1760
|
encoded_value = URI.encode_component(
|
|
1663
|
-
value,
|
|
1761
|
+
value, CharacterClassesRegexps::UNRESERVED
|
|
1664
1762
|
)
|
|
1665
1763
|
buffer << "#{encoded_key}=#{encoded_value}&"
|
|
1666
1764
|
end
|
|
@@ -1674,7 +1772,7 @@ module Addressable
|
|
|
1674
1772
|
#
|
|
1675
1773
|
# @return [String] The request URI required for an HTTP request.
|
|
1676
1774
|
def request_uri
|
|
1677
|
-
return nil if self.absolute? && self.scheme !~ /^https?$/
|
|
1775
|
+
return nil if self.absolute? && self.scheme !~ /^https?$/i
|
|
1678
1776
|
return (
|
|
1679
1777
|
(!self.path.empty? ? self.path : SLASH) +
|
|
1680
1778
|
(self.query ? "?#{self.query}" : EMPTY_STR)
|
|
@@ -1689,12 +1787,12 @@ module Addressable
|
|
|
1689
1787
|
if !new_request_uri.respond_to?(:to_str)
|
|
1690
1788
|
raise TypeError, "Can't convert #{new_request_uri.class} into String."
|
|
1691
1789
|
end
|
|
1692
|
-
if self.absolute? && self.scheme !~ /^https?$/
|
|
1790
|
+
if self.absolute? && self.scheme !~ /^https?$/i
|
|
1693
1791
|
raise InvalidURIError,
|
|
1694
1792
|
"Cannot set an HTTP request URI for a non-HTTP URI."
|
|
1695
1793
|
end
|
|
1696
1794
|
new_request_uri = new_request_uri.to_str
|
|
1697
|
-
path_component = new_request_uri[/^([^\?]*)
|
|
1795
|
+
path_component = new_request_uri[/^([^\?]*)\??(?:.*)$/, 1]
|
|
1698
1796
|
query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1]
|
|
1699
1797
|
path_component = path_component.to_s
|
|
1700
1798
|
path_component = (!path_component.empty? ? path_component : SLASH)
|
|
@@ -1709,9 +1807,7 @@ module Addressable
|
|
|
1709
1807
|
# The fragment component for this URI.
|
|
1710
1808
|
#
|
|
1711
1809
|
# @return [String] The fragment component.
|
|
1712
|
-
|
|
1713
|
-
return defined?(@fragment) ? @fragment : nil
|
|
1714
|
-
end
|
|
1810
|
+
attr_reader :fragment
|
|
1715
1811
|
|
|
1716
1812
|
##
|
|
1717
1813
|
# The fragment component for this URI, normalized.
|
|
@@ -1719,14 +1815,17 @@ module Addressable
|
|
|
1719
1815
|
# @return [String] The fragment component, normalized.
|
|
1720
1816
|
def normalized_fragment
|
|
1721
1817
|
return nil unless self.fragment
|
|
1722
|
-
return @normalized_fragment
|
|
1723
|
-
@normalized_fragment
|
|
1818
|
+
return @normalized_fragment unless @normalized_fragment == NONE
|
|
1819
|
+
@normalized_fragment = begin
|
|
1724
1820
|
component = Addressable::URI.normalize_component(
|
|
1725
1821
|
self.fragment,
|
|
1726
|
-
Addressable::URI::
|
|
1822
|
+
Addressable::URI::NormalizeCharacterClasses::FRAGMENT
|
|
1727
1823
|
)
|
|
1728
1824
|
component == "" ? nil : component
|
|
1729
1825
|
end
|
|
1826
|
+
# All normalized values should be UTF-8
|
|
1827
|
+
force_utf8_encoding_if_needed(@normalized_fragment)
|
|
1828
|
+
@normalized_fragment
|
|
1730
1829
|
end
|
|
1731
1830
|
|
|
1732
1831
|
##
|
|
@@ -1740,7 +1839,7 @@ module Addressable
|
|
|
1740
1839
|
@fragment = new_fragment ? new_fragment.to_str : nil
|
|
1741
1840
|
|
|
1742
1841
|
# Reset dependent values
|
|
1743
|
-
|
|
1842
|
+
@normalized_fragment = NONE
|
|
1744
1843
|
remove_composite_values
|
|
1745
1844
|
|
|
1746
1845
|
# Ensure we haven't created an invalid URI
|
|
@@ -1844,8 +1943,8 @@ module Addressable
|
|
|
1844
1943
|
# Section 5.2.3 of RFC 3986
|
|
1845
1944
|
#
|
|
1846
1945
|
# Removes the right-most path segment from the base path.
|
|
1847
|
-
if base_path
|
|
1848
|
-
base_path.
|
|
1946
|
+
if base_path.include?(SLASH)
|
|
1947
|
+
base_path.sub!(/\/[^\/]+$/, SLASH)
|
|
1849
1948
|
else
|
|
1850
1949
|
base_path = EMPTY_STR
|
|
1851
1950
|
end
|
|
@@ -1906,7 +2005,7 @@ module Addressable
|
|
|
1906
2005
|
#
|
|
1907
2006
|
# @see Hash#merge
|
|
1908
2007
|
def merge(hash)
|
|
1909
|
-
|
|
2008
|
+
unless hash.respond_to?(:to_hash)
|
|
1910
2009
|
raise TypeError, "Can't convert #{hash.class} into Hash."
|
|
1911
2010
|
end
|
|
1912
2011
|
hash = hash.to_hash
|
|
@@ -2246,15 +2345,13 @@ module Addressable
|
|
|
2246
2345
|
"Cannot assemble URI string with ambiguous path: '#{self.path}'"
|
|
2247
2346
|
end
|
|
2248
2347
|
@uri_string ||= begin
|
|
2249
|
-
uri_string =
|
|
2348
|
+
uri_string = String.new
|
|
2250
2349
|
uri_string << "#{self.scheme}:" if self.scheme != nil
|
|
2251
2350
|
uri_string << "//#{self.authority}" if self.authority != nil
|
|
2252
2351
|
uri_string << self.path.to_s
|
|
2253
2352
|
uri_string << "?#{self.query}" if self.query != nil
|
|
2254
2353
|
uri_string << "##{self.fragment}" if self.fragment != nil
|
|
2255
|
-
|
|
2256
|
-
uri_string.force_encoding(Encoding::UTF_8)
|
|
2257
|
-
end
|
|
2354
|
+
uri_string.force_encoding(Encoding::UTF_8)
|
|
2258
2355
|
uri_string
|
|
2259
2356
|
end
|
|
2260
2357
|
end
|
|
@@ -2285,7 +2382,7 @@ module Addressable
|
|
|
2285
2382
|
#
|
|
2286
2383
|
# @return [String] The URI object's state, as a <code>String</code>.
|
|
2287
2384
|
def inspect
|
|
2288
|
-
sprintf("#<%s:%#0x URI:%s>",
|
|
2385
|
+
sprintf("#<%s:%#0x URI:%s>", self.class.to_s, self.object_id, self.to_s)
|
|
2289
2386
|
end
|
|
2290
2387
|
|
|
2291
2388
|
##
|
|
@@ -2296,13 +2393,33 @@ module Addressable
|
|
|
2296
2393
|
#
|
|
2297
2394
|
# @param [Proc] block
|
|
2298
2395
|
# A set of operations to perform on a given URI.
|
|
2299
|
-
def defer_validation
|
|
2300
|
-
raise LocalJumpError, "No block given." unless
|
|
2396
|
+
def defer_validation
|
|
2397
|
+
raise LocalJumpError, "No block given." unless block_given?
|
|
2301
2398
|
@validation_deferred = true
|
|
2302
|
-
|
|
2399
|
+
yield
|
|
2303
2400
|
@validation_deferred = false
|
|
2304
2401
|
validate
|
|
2305
|
-
|
|
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
|
|
2306
2423
|
end
|
|
2307
2424
|
|
|
2308
2425
|
protected
|
|
@@ -2323,30 +2440,35 @@ module Addressable
|
|
|
2323
2440
|
def self.normalize_path(path)
|
|
2324
2441
|
# Section 5.2.4 of RFC 3986
|
|
2325
2442
|
|
|
2326
|
-
return
|
|
2443
|
+
return if path.nil?
|
|
2327
2444
|
normalized_path = path.dup
|
|
2328
|
-
|
|
2329
|
-
mod = nil
|
|
2445
|
+
loop do
|
|
2330
2446
|
mod ||= normalized_path.gsub!(RULE_2A, SLASH)
|
|
2331
2447
|
|
|
2332
2448
|
pair = normalized_path.match(RULE_2B_2C)
|
|
2333
|
-
|
|
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
|
+
|
|
2334
2460
|
if pair && ((parent != SELF_REF && parent != PARENT) ||
|
|
2335
2461
|
(current != SELF_REF && current != PARENT))
|
|
2336
|
-
mod ||= normalized_path.gsub!(
|
|
2337
|
-
Regexp.new(
|
|
2338
|
-
"/#{Regexp.escape(parent.to_s)}/\\.\\./|" +
|
|
2339
|
-
"(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
|
|
2340
|
-
), SLASH
|
|
2341
|
-
)
|
|
2462
|
+
mod ||= normalized_path.gsub!(Regexp.new(regexp), SLASH)
|
|
2342
2463
|
end
|
|
2343
2464
|
|
|
2344
2465
|
mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
|
|
2345
2466
|
# Non-standard, removes prefixed dotted segments from path.
|
|
2346
2467
|
mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
|
|
2347
|
-
|
|
2468
|
+
break if mod.nil?
|
|
2469
|
+
end
|
|
2348
2470
|
|
|
2349
|
-
|
|
2471
|
+
normalized_path
|
|
2350
2472
|
end
|
|
2351
2473
|
|
|
2352
2474
|
##
|
|
@@ -2371,6 +2493,19 @@ module Addressable
|
|
|
2371
2493
|
raise InvalidURIError,
|
|
2372
2494
|
"Cannot have a relative path with an authority set: '#{self.to_s}'"
|
|
2373
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
|
|
2374
2509
|
return nil
|
|
2375
2510
|
end
|
|
2376
2511
|
|
|
@@ -2383,9 +2518,7 @@ module Addressable
|
|
|
2383
2518
|
# @return [Addressable::URI] <code>self</code>.
|
|
2384
2519
|
def replace_self(uri)
|
|
2385
2520
|
# Reset dependent values
|
|
2386
|
-
|
|
2387
|
-
remove_instance_variable(var) if instance_variable_defined?(var)
|
|
2388
|
-
end
|
|
2521
|
+
reset_ivs
|
|
2389
2522
|
|
|
2390
2523
|
@scheme = uri.scheme
|
|
2391
2524
|
@user = uri.user
|
|
@@ -2417,8 +2550,53 @@ module Addressable
|
|
|
2417
2550
|
#
|
|
2418
2551
|
# @api private
|
|
2419
2552
|
def remove_composite_values
|
|
2420
|
-
|
|
2421
|
-
|
|
2553
|
+
@uri_string = nil
|
|
2554
|
+
@hash = nil
|
|
2422
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
|
|
2423
2601
|
end
|
|
2424
2602
|
end
|