addressable 2.2.3 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of addressable might be problematic. Click here for more details.

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