addressable 2.3.2 → 2.9.0

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