addressable 2.5.0 → 2.8.8

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