addressable 2.8.0 → 2.8.7

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,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # encoding:utf-8
4
3
  #--
5
4
  # Copyright (C) Bob Aman
6
5
  #
@@ -38,20 +37,27 @@ module Addressable
38
37
  ##
39
38
  # Container for the character classes specified in
40
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
41
46
  module CharacterClasses
42
47
  ALPHA = "a-zA-Z"
43
48
  DIGIT = "0-9"
44
49
  GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
45
50
  SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
46
- RESERVED = GEN_DELIMS + SUB_DELIMS
47
- UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
48
- PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
49
- SCHEME = ALPHA + DIGIT + "\\-\\+\\."
50
- HOST = UNRESERVED + SUB_DELIMS + "\\[\\:\\]"
51
- AUTHORITY = PCHAR + "\\[\\:\\]"
52
- PATH = PCHAR + "\\/"
53
- QUERY = PCHAR + "\\/\\?"
54
- 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
55
61
  end
56
62
 
57
63
  module NormalizeCharacterClasses
@@ -63,6 +69,18 @@ module Addressable
63
69
  QUERY = %r{[^a-zA-Z0-9\-\.\_\~\!\$\'\(\)\*\+\,\=\:\@\/\?%]|%(?!2B|2b)}
64
70
  end
65
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}]/
82
+ end
83
+
66
84
  SLASH = '/'
67
85
  EMPTY_STR = ''
68
86
 
@@ -112,7 +130,7 @@ module Addressable
112
130
  uri = uri.to_str
113
131
  rescue TypeError, NoMethodError
114
132
  raise TypeError, "Can't convert #{uri.class} into String."
115
- end if not uri.is_a? String
133
+ end unless uri.is_a?(String)
116
134
 
117
135
  # This Regexp supplied as an example in RFC 3986, and it works great.
118
136
  scan = uri.scan(URIREGEX)
@@ -133,15 +151,15 @@ module Addressable
133
151
  user = userinfo.strip[/^([^:]*):?/, 1]
134
152
  password = userinfo.strip[/:(.*)$/, 1]
135
153
  end
154
+
136
155
  host = authority.sub(
137
156
  /^([^\[\]]*)@/, EMPTY_STR
138
157
  ).sub(
139
158
  /:([^:@\[\]]*?)$/, EMPTY_STR
140
159
  )
160
+
141
161
  port = authority[/:([^:@\[\]]*?)$/, 1]
142
- end
143
- if port == EMPTY_STR
144
- port = nil
162
+ port = nil if port == EMPTY_STR
145
163
  end
146
164
 
147
165
  return new(
@@ -184,7 +202,7 @@ module Addressable
184
202
  uri = uri.to_s
185
203
  end
186
204
 
187
- if !uri.respond_to?(:to_str)
205
+ unless uri.respond_to?(:to_str)
188
206
  raise TypeError, "Can't convert #{uri.class} into String."
189
207
  end
190
208
  # Otherwise, convert to a String
@@ -276,7 +294,7 @@ module Addressable
276
294
  return nil unless path
277
295
  # If a URI object is passed, just return itself.
278
296
  return path if path.kind_of?(self)
279
- if !path.respond_to?(:to_str)
297
+ unless path.respond_to?(:to_str)
280
298
  raise TypeError, "Can't convert #{path.class} into String."
281
299
  end
282
300
  # Otherwise, convert to a String
@@ -324,13 +342,13 @@ module Addressable
324
342
  # #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
325
343
  def self.join(*uris)
326
344
  uri_objects = uris.collect do |uri|
327
- if !uri.respond_to?(:to_str)
345
+ unless uri.respond_to?(:to_str)
328
346
  raise TypeError, "Can't convert #{uri.class} into String."
329
347
  end
330
348
  uri.kind_of?(self) ? uri : self.parse(uri.to_str)
331
349
  end
332
350
  result = uri_objects.shift.dup
333
- for uri in uri_objects
351
+ uri_objects.each do |uri|
334
352
  result.join!(uri)
335
353
  end
336
354
  return result
@@ -339,17 +357,13 @@ module Addressable
339
357
  ##
340
358
  # Tables used to optimize encoding operations in `self.encode_component`
341
359
  # and `self.normalize_component`
342
- SEQUENCE_ENCODING_TABLE = Hash.new do |hash, sequence|
343
- hash[sequence] = sequence.unpack("C*").map do |c|
344
- format("%02x", c)
345
- end.join
346
- end
360
+ SEQUENCE_ENCODING_TABLE = (0..255).map do |byte|
361
+ format("%02x", byte).freeze
362
+ end.freeze
347
363
 
348
- SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE = Hash.new do |hash, sequence|
349
- hash[sequence] = sequence.unpack("C*").map do |c|
350
- format("%%%02X", c)
351
- end.join
352
- end
364
+ SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE = (0..255).map do |byte|
365
+ format("%%%02X", byte).freeze
366
+ end.freeze
353
367
 
354
368
  ##
355
369
  # Percent encodes a URI component.
@@ -386,9 +400,7 @@ module Addressable
386
400
  # "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
387
401
  # )
388
402
  # => "simple%2Fexample"
389
- def self.encode_component(component, character_class=
390
- CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
391
- upcase_encoded='')
403
+ def self.encode_component(component, character_class=CharacterClassesRegexps::RESERVED_AND_UNRESERVED, upcase_encoded='')
392
404
  return nil if component.nil?
393
405
 
394
406
  begin
@@ -416,16 +428,17 @@ module Addressable
416
428
  component = component.dup
417
429
  component.force_encoding(Encoding::ASCII_8BIT)
418
430
  # Avoiding gsub! because there are edge cases with frozen strings
419
- component = component.gsub(character_class) do |sequence|
420
- SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[sequence]
431
+ component = component.gsub(character_class) do |char|
432
+ SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[char.ord]
421
433
  end
422
434
  if upcase_encoded.length > 0
423
- upcase_encoded_chars = upcase_encoded.chars.map do |char|
424
- SEQUENCE_ENCODING_TABLE[char]
435
+ upcase_encoded_chars = upcase_encoded.bytes.map do |byte|
436
+ SEQUENCE_ENCODING_TABLE[byte]
425
437
  end
426
438
  component = component.gsub(/%(#{upcase_encoded_chars.join('|')})/,
427
439
  &:upcase)
428
440
  end
441
+
429
442
  return component
430
443
  end
431
444
 
@@ -469,20 +482,14 @@ module Addressable
469
482
  "Expected Class (String or Addressable::URI), " +
470
483
  "got #{return_type.inspect}"
471
484
  end
472
- uri = uri.dup
473
- # Seriously, only use UTF-8. I'm really not kidding!
474
- uri.force_encoding("utf-8")
475
-
476
- unless leave_encoded.empty?
477
- leave_encoded = leave_encoded.dup.force_encoding("utf-8")
478
- end
479
485
 
480
- result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
486
+ result = uri.gsub(/%[0-9a-f]{2}/i) do |sequence|
481
487
  c = sequence[1..3].to_i(16).chr
482
- c.force_encoding("utf-8")
488
+ c.force_encoding(sequence.encoding)
483
489
  leave_encoded.include?(c) ? sequence : c
484
490
  end
485
- result.force_encoding("utf-8")
491
+
492
+ result.force_encoding(Encoding::UTF_8)
486
493
  if return_type == String
487
494
  return result
488
495
  elsif return_type == ::Addressable::URI
@@ -543,7 +550,7 @@ module Addressable
543
550
  # )
544
551
  # => "one two%2Fthree&four"
545
552
  def self.normalize_component(component, character_class=
546
- CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
553
+ CharacterClassesRegexps::RESERVED_AND_UNRESERVED,
547
554
  leave_encoded='')
548
555
  return nil if component.nil?
549
556
 
@@ -561,10 +568,9 @@ module Addressable
561
568
  leave_re = if leave_encoded.length > 0
562
569
  character_class = "#{character_class}%" unless character_class.include?('%')
563
570
 
564
- "|%(?!#{leave_encoded.chars.map do |char|
565
- seq = SEQUENCE_ENCODING_TABLE[char]
566
- [seq.upcase, seq.downcase]
567
- 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})"
568
574
  end
569
575
 
570
576
  character_class = if leave_re
@@ -580,7 +586,7 @@ module Addressable
580
586
  unencoded = self.unencode_component(component, String, leave_encoded)
581
587
  begin
582
588
  encoded = self.encode_component(
583
- Addressable::IDNA.unicode_normalize_kc(unencoded),
589
+ unencoded.unicode_normalize(:nfc),
584
590
  character_class,
585
591
  leave_encoded
586
592
  )
@@ -624,15 +630,15 @@ module Addressable
624
630
  uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
625
631
  encoded_uri = Addressable::URI.new(
626
632
  :scheme => self.encode_component(uri_object.scheme,
627
- Addressable::URI::CharacterClasses::SCHEME),
633
+ Addressable::URI::CharacterClassesRegexps::SCHEME),
628
634
  :authority => self.encode_component(uri_object.authority,
629
- Addressable::URI::CharacterClasses::AUTHORITY),
635
+ Addressable::URI::CharacterClassesRegexps::AUTHORITY),
630
636
  :path => self.encode_component(uri_object.path,
631
- Addressable::URI::CharacterClasses::PATH),
637
+ Addressable::URI::CharacterClassesRegexps::PATH),
632
638
  :query => self.encode_component(uri_object.query,
633
- Addressable::URI::CharacterClasses::QUERY),
639
+ Addressable::URI::CharacterClassesRegexps::QUERY),
634
640
  :fragment => self.encode_component(uri_object.fragment,
635
- Addressable::URI::CharacterClasses::FRAGMENT)
641
+ Addressable::URI::CharacterClassesRegexps::FRAGMENT)
636
642
  )
637
643
  if return_type == String
638
644
  return encoded_uri.to_s
@@ -688,8 +694,7 @@ module Addressable
688
694
  components.each do |key, value|
689
695
  if value != nil
690
696
  begin
691
- components[key] =
692
- Addressable::IDNA.unicode_normalize_kc(value.to_str)
697
+ components[key] = value.to_str.unicode_normalize(:nfc)
693
698
  rescue ArgumentError
694
699
  # Likely a malformed UTF-8 character, skip unicode normalization
695
700
  components[key] = value.to_str
@@ -698,19 +703,19 @@ module Addressable
698
703
  end
699
704
  encoded_uri = Addressable::URI.new(
700
705
  :scheme => self.encode_component(components[:scheme],
701
- Addressable::URI::CharacterClasses::SCHEME),
706
+ Addressable::URI::CharacterClassesRegexps::SCHEME),
702
707
  :user => self.encode_component(components[:user],
703
- Addressable::URI::CharacterClasses::UNRESERVED),
708
+ Addressable::URI::CharacterClassesRegexps::UNRESERVED),
704
709
  :password => self.encode_component(components[:password],
705
- Addressable::URI::CharacterClasses::UNRESERVED),
710
+ Addressable::URI::CharacterClassesRegexps::UNRESERVED),
706
711
  :host => components[:host],
707
712
  :port => components[:port],
708
713
  :path => self.encode_component(components[:path],
709
- Addressable::URI::CharacterClasses::PATH),
714
+ Addressable::URI::CharacterClassesRegexps::PATH),
710
715
  :query => self.encode_component(components[:query],
711
- Addressable::URI::CharacterClasses::QUERY),
716
+ Addressable::URI::CharacterClassesRegexps::QUERY),
712
717
  :fragment => self.encode_component(components[:fragment],
713
- Addressable::URI::CharacterClasses::FRAGMENT)
718
+ Addressable::URI::CharacterClassesRegexps::FRAGMENT)
714
719
  )
715
720
  if return_type == String
716
721
  return encoded_uri.to_s
@@ -761,11 +766,11 @@ module Addressable
761
766
  [
762
767
  self.encode_component(
763
768
  key.gsub(/(\r\n|\n|\r)/, "\r\n"),
764
- CharacterClasses::UNRESERVED
769
+ CharacterClassesRegexps::UNRESERVED
765
770
  ).gsub("%20", "+"),
766
771
  self.encode_component(
767
772
  value.gsub(/(\r\n|\n|\r)/, "\r\n"),
768
- CharacterClasses::UNRESERVED
773
+ CharacterClassesRegexps::UNRESERVED
769
774
  ).gsub("%20", "+")
770
775
  ]
771
776
  end
@@ -837,7 +842,9 @@ module Addressable
837
842
  end
838
843
  end
839
844
 
840
- self.defer_validation do
845
+ reset_ivs
846
+
847
+ defer_validation do
841
848
  # Bunch of crazy logic required because of the composite components
842
849
  # like userinfo and authority.
843
850
  self.scheme = options[:scheme] if options[:scheme]
@@ -852,7 +859,8 @@ module Addressable
852
859
  self.query_values = options[:query_values] if options[:query_values]
853
860
  self.fragment = options[:fragment] if options[:fragment]
854
861
  end
855
- self.to_s
862
+
863
+ to_s # force path validation
856
864
  end
857
865
 
858
866
  ##
@@ -879,9 +887,7 @@ module Addressable
879
887
  # The scheme component for this URI.
880
888
  #
881
889
  # @return [String] The scheme component.
882
- def scheme
883
- return defined?(@scheme) ? @scheme : nil
884
- end
890
+ attr_reader :scheme
885
891
 
886
892
  ##
887
893
  # The scheme component for this URI, normalized.
@@ -889,8 +895,8 @@ module Addressable
889
895
  # @return [String] The scheme component, normalized.
890
896
  def normalized_scheme
891
897
  return nil unless self.scheme
892
- @normalized_scheme ||= begin
893
- if self.scheme =~ /^\s*ssh\+svn\s*$/i
898
+ if @normalized_scheme == NONE
899
+ @normalized_scheme = if self.scheme =~ /^\s*ssh\+svn\s*$/i
894
900
  "svn+ssh".dup
895
901
  else
896
902
  Addressable::URI.normalize_component(
@@ -900,7 +906,7 @@ module Addressable
900
906
  end
901
907
  end
902
908
  # All normalized values should be UTF-8
903
- @normalized_scheme.force_encoding(Encoding::UTF_8) if @normalized_scheme
909
+ force_utf8_encoding_if_needed(@normalized_scheme)
904
910
  @normalized_scheme
905
911
  end
906
912
 
@@ -921,7 +927,7 @@ module Addressable
921
927
  @scheme = nil if @scheme.to_s.strip.empty?
922
928
 
923
929
  # Reset dependent values
924
- remove_instance_variable(:@normalized_scheme) if defined?(@normalized_scheme)
930
+ @normalized_scheme = NONE
925
931
  remove_composite_values
926
932
 
927
933
  # Ensure we haven't created an invalid URI
@@ -932,9 +938,7 @@ module Addressable
932
938
  # The user component for this URI.
933
939
  #
934
940
  # @return [String] The user component.
935
- def user
936
- return defined?(@user) ? @user : nil
937
- end
941
+ attr_reader :user
938
942
 
939
943
  ##
940
944
  # The user component for this URI, normalized.
@@ -942,8 +946,8 @@ module Addressable
942
946
  # @return [String] The user component, normalized.
943
947
  def normalized_user
944
948
  return nil unless self.user
945
- return @normalized_user if defined?(@normalized_user)
946
- @normalized_user ||= begin
949
+ return @normalized_user unless @normalized_user == NONE
950
+ @normalized_user = begin
947
951
  if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
948
952
  (!self.password || self.password.strip.empty?)
949
953
  nil
@@ -955,7 +959,7 @@ module Addressable
955
959
  end
956
960
  end
957
961
  # All normalized values should be UTF-8
958
- @normalized_user.force_encoding(Encoding::UTF_8) if @normalized_user
962
+ force_utf8_encoding_if_needed(@normalized_user)
959
963
  @normalized_user
960
964
  end
961
965
 
@@ -971,14 +975,14 @@ module Addressable
971
975
 
972
976
  # You can't have a nil user with a non-nil password
973
977
  if password != nil
974
- @user = EMPTY_STR if @user.nil?
978
+ @user = EMPTY_STR unless user
975
979
  end
976
980
 
977
981
  # Reset dependent values
978
- remove_instance_variable(:@userinfo) if defined?(@userinfo)
979
- remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
980
- remove_instance_variable(:@authority) if defined?(@authority)
981
- remove_instance_variable(:@normalized_user) if defined?(@normalized_user)
982
+ @userinfo = nil
983
+ @normalized_userinfo = NONE
984
+ @authority = nil
985
+ @normalized_user = NONE
982
986
  remove_composite_values
983
987
 
984
988
  # Ensure we haven't created an invalid URI
@@ -989,9 +993,7 @@ module Addressable
989
993
  # The password component for this URI.
990
994
  #
991
995
  # @return [String] The password component.
992
- def password
993
- return defined?(@password) ? @password : nil
994
- end
996
+ attr_reader :password
995
997
 
996
998
  ##
997
999
  # The password component for this URI, normalized.
@@ -999,8 +1001,8 @@ module Addressable
999
1001
  # @return [String] The password component, normalized.
1000
1002
  def normalized_password
1001
1003
  return nil unless self.password
1002
- return @normalized_password if defined?(@normalized_password)
1003
- @normalized_password ||= begin
1004
+ return @normalized_password unless @normalized_password == NONE
1005
+ @normalized_password = begin
1004
1006
  if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
1005
1007
  (!self.user || self.user.strip.empty?)
1006
1008
  nil
@@ -1012,9 +1014,7 @@ module Addressable
1012
1014
  end
1013
1015
  end
1014
1016
  # All normalized values should be UTF-8
1015
- if @normalized_password
1016
- @normalized_password.force_encoding(Encoding::UTF_8)
1017
- end
1017
+ force_utf8_encoding_if_needed(@normalized_password)
1018
1018
  @normalized_password
1019
1019
  end
1020
1020
 
@@ -1029,17 +1029,15 @@ module Addressable
1029
1029
  @password = new_password ? new_password.to_str : nil
1030
1030
 
1031
1031
  # You can't have a nil user with a non-nil password
1032
- @password ||= nil
1033
- @user ||= nil
1034
1032
  if @password != nil
1035
- @user = EMPTY_STR if @user.nil?
1033
+ self.user = EMPTY_STR if user.nil?
1036
1034
  end
1037
1035
 
1038
1036
  # Reset dependent values
1039
- remove_instance_variable(:@userinfo) if defined?(@userinfo)
1040
- remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
1041
- remove_instance_variable(:@authority) if defined?(@authority)
1042
- remove_instance_variable(:@normalized_password) if defined?(@normalized_password)
1037
+ @userinfo = nil
1038
+ @normalized_userinfo = NONE
1039
+ @authority = nil
1040
+ @normalized_password = NONE
1043
1041
  remove_composite_values
1044
1042
 
1045
1043
  # Ensure we haven't created an invalid URI
@@ -1069,8 +1067,8 @@ module Addressable
1069
1067
  # @return [String] The userinfo component, normalized.
1070
1068
  def normalized_userinfo
1071
1069
  return nil unless self.userinfo
1072
- return @normalized_userinfo if defined?(@normalized_userinfo)
1073
- @normalized_userinfo ||= begin
1070
+ return @normalized_userinfo unless @normalized_userinfo == NONE
1071
+ @normalized_userinfo = begin
1074
1072
  current_user = self.normalized_user
1075
1073
  current_password = self.normalized_password
1076
1074
  if !current_user && !current_password
@@ -1082,9 +1080,7 @@ module Addressable
1082
1080
  end
1083
1081
  end
1084
1082
  # All normalized values should be UTF-8
1085
- if @normalized_userinfo
1086
- @normalized_userinfo.force_encoding(Encoding::UTF_8)
1087
- end
1083
+ force_utf8_encoding_if_needed(@normalized_userinfo)
1088
1084
  @normalized_userinfo
1089
1085
  end
1090
1086
 
@@ -1110,7 +1106,7 @@ module Addressable
1110
1106
  self.user = new_user
1111
1107
 
1112
1108
  # Reset dependent values
1113
- remove_instance_variable(:@authority) if defined?(@authority)
1109
+ @authority = nil
1114
1110
  remove_composite_values
1115
1111
 
1116
1112
  # Ensure we haven't created an invalid URI
@@ -1121,9 +1117,7 @@ module Addressable
1121
1117
  # The host component for this URI.
1122
1118
  #
1123
1119
  # @return [String] The host component.
1124
- def host
1125
- return defined?(@host) ? @host : nil
1126
- end
1120
+ attr_reader :host
1127
1121
 
1128
1122
  ##
1129
1123
  # The host component for this URI, normalized.
@@ -1151,9 +1145,7 @@ module Addressable
1151
1145
  end
1152
1146
  end
1153
1147
  # All normalized values should be UTF-8
1154
- if @normalized_host && !@normalized_host.empty?
1155
- @normalized_host.force_encoding(Encoding::UTF_8)
1156
- end
1148
+ force_utf8_encoding_if_needed(@normalized_host)
1157
1149
  @normalized_host
1158
1150
  end
1159
1151
 
@@ -1168,8 +1160,8 @@ module Addressable
1168
1160
  @host = new_host ? new_host.to_str : nil
1169
1161
 
1170
1162
  # Reset dependent values
1171
- remove_instance_variable(:@authority) if defined?(@authority)
1172
- remove_instance_variable(:@normalized_host) if defined?(@normalized_host)
1163
+ @authority = nil
1164
+ @normalized_host = nil
1173
1165
  remove_composite_values
1174
1166
 
1175
1167
  # Ensure we haven't created an invalid URI
@@ -1271,9 +1263,7 @@ module Addressable
1271
1263
  authority
1272
1264
  end
1273
1265
  # All normalized values should be UTF-8
1274
- if @normalized_authority
1275
- @normalized_authority.force_encoding(Encoding::UTF_8)
1276
- end
1266
+ force_utf8_encoding_if_needed(@normalized_authority)
1277
1267
  @normalized_authority
1278
1268
  end
1279
1269
 
@@ -1302,14 +1292,14 @@ module Addressable
1302
1292
  end
1303
1293
 
1304
1294
  # Password assigned first to ensure validity in case of nil
1305
- self.password = defined?(new_password) ? new_password : nil
1306
- self.user = defined?(new_user) ? new_user : nil
1307
- self.host = defined?(new_host) ? new_host : nil
1308
- 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
1309
1299
 
1310
1300
  # Reset dependent values
1311
- remove_instance_variable(:@userinfo) if defined?(@userinfo)
1312
- remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
1301
+ @userinfo = nil
1302
+ @normalized_userinfo = NONE
1313
1303
  remove_composite_values
1314
1304
 
1315
1305
  # Ensure we haven't created an invalid URI
@@ -1357,16 +1347,16 @@ module Addressable
1357
1347
  new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
1358
1348
  end
1359
1349
 
1360
- self.scheme = defined?(new_scheme) ? new_scheme : nil
1361
- self.host = defined?(new_host) ? new_host : nil
1362
- self.port = defined?(new_port) ? new_port : nil
1350
+ self.scheme = new_scheme
1351
+ self.host = new_host
1352
+ self.port = new_port
1363
1353
  self.userinfo = nil
1364
1354
 
1365
1355
  # Reset dependent values
1366
- remove_instance_variable(:@userinfo) if defined?(@userinfo)
1367
- remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
1368
- remove_instance_variable(:@authority) if defined?(@authority)
1369
- remove_instance_variable(:@normalized_authority) if defined?(@normalized_authority)
1356
+ @userinfo = nil
1357
+ @normalized_userinfo = NONE
1358
+ @authority = nil
1359
+ @normalized_authority = nil
1370
1360
  remove_composite_values
1371
1361
 
1372
1362
  # Ensure we haven't created an invalid URI
@@ -1393,9 +1383,7 @@ module Addressable
1393
1383
  # infer port numbers from default values.
1394
1384
  #
1395
1385
  # @return [Integer] The port component.
1396
- def port
1397
- return defined?(@port) ? @port : nil
1398
- end
1386
+ attr_reader :port
1399
1387
 
1400
1388
  ##
1401
1389
  # The port component for this URI, normalized.
@@ -1403,8 +1391,8 @@ module Addressable
1403
1391
  # @return [Integer] The port component, normalized.
1404
1392
  def normalized_port
1405
1393
  return nil unless self.port
1406
- return @normalized_port if defined?(@normalized_port)
1407
- @normalized_port ||= begin
1394
+ return @normalized_port unless @normalized_port == NONE
1395
+ @normalized_port = begin
1408
1396
  if URI.port_mapping[self.normalized_scheme] == self.port
1409
1397
  nil
1410
1398
  else
@@ -1435,8 +1423,8 @@ module Addressable
1435
1423
  @port = nil if @port == 0
1436
1424
 
1437
1425
  # Reset dependent values
1438
- remove_instance_variable(:@authority) if defined?(@authority)
1439
- remove_instance_variable(:@normalized_port) if defined?(@normalized_port)
1426
+ @authority = nil
1427
+ @normalized_port = NONE
1440
1428
  remove_composite_values
1441
1429
 
1442
1430
  # Ensure we haven't created an invalid URI
@@ -1507,7 +1495,7 @@ module Addressable
1507
1495
  site_string
1508
1496
  end
1509
1497
  # All normalized values should be UTF-8
1510
- @normalized_site.force_encoding(Encoding::UTF_8) if @normalized_site
1498
+ force_utf8_encoding_if_needed(@normalized_site)
1511
1499
  @normalized_site
1512
1500
  end
1513
1501
 
@@ -1537,9 +1525,7 @@ module Addressable
1537
1525
  # The path component for this URI.
1538
1526
  #
1539
1527
  # @return [String] The path component.
1540
- def path
1541
- return defined?(@path) ? @path : EMPTY_STR
1542
- end
1528
+ attr_reader :path
1543
1529
 
1544
1530
  NORMPATH = /^(?!\/)[^\/:]*:.*$/
1545
1531
  ##
@@ -1553,7 +1539,7 @@ module Addressable
1553
1539
  # Relative paths with colons in the first segment are ambiguous.
1554
1540
  path = path.sub(":", "%2F")
1555
1541
  end
1556
- # String#split(delimeter, -1) uses the more strict splitting behavior
1542
+ # String#split(delimiter, -1) uses the more strict splitting behavior
1557
1543
  # found by default in Python.
1558
1544
  result = path.strip.split(SLASH, -1).map do |segment|
1559
1545
  Addressable::URI.normalize_component(
@@ -1570,7 +1556,7 @@ module Addressable
1570
1556
  result
1571
1557
  end
1572
1558
  # All normalized values should be UTF-8
1573
- @normalized_path.force_encoding(Encoding::UTF_8) if @normalized_path
1559
+ force_utf8_encoding_if_needed(@normalized_path)
1574
1560
  @normalized_path
1575
1561
  end
1576
1562
 
@@ -1588,7 +1574,7 @@ module Addressable
1588
1574
  end
1589
1575
 
1590
1576
  # Reset dependent values
1591
- remove_instance_variable(:@normalized_path) if defined?(@normalized_path)
1577
+ @normalized_path = nil
1592
1578
  remove_composite_values
1593
1579
 
1594
1580
  # Ensure we haven't created an invalid URI
@@ -1618,9 +1604,7 @@ module Addressable
1618
1604
  # The query component for this URI.
1619
1605
  #
1620
1606
  # @return [String] The query component.
1621
- def query
1622
- return defined?(@query) ? @query : nil
1623
- end
1607
+ attr_reader :query
1624
1608
 
1625
1609
  ##
1626
1610
  # The query component for this URI, normalized.
@@ -1628,8 +1612,8 @@ module Addressable
1628
1612
  # @return [String] The query component, normalized.
1629
1613
  def normalized_query(*flags)
1630
1614
  return nil unless self.query
1631
- return @normalized_query if defined?(@normalized_query)
1632
- @normalized_query ||= begin
1615
+ return @normalized_query unless @normalized_query == NONE
1616
+ @normalized_query = begin
1633
1617
  modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
1634
1618
  # Make sure possible key-value pair delimiters are escaped.
1635
1619
  modified_query_class.sub!("\\&", "").sub!("\\;", "")
@@ -1646,7 +1630,7 @@ module Addressable
1646
1630
  component == "" ? nil : component
1647
1631
  end
1648
1632
  # All normalized values should be UTF-8
1649
- @normalized_query.force_encoding(Encoding::UTF_8) if @normalized_query
1633
+ force_utf8_encoding_if_needed(@normalized_query)
1650
1634
  @normalized_query
1651
1635
  end
1652
1636
 
@@ -1661,7 +1645,7 @@ module Addressable
1661
1645
  @query = new_query ? new_query.to_str : nil
1662
1646
 
1663
1647
  # Reset dependent values
1664
- remove_instance_variable(:@normalized_query) if defined?(@normalized_query)
1648
+ @normalized_query = NONE
1665
1649
  remove_composite_values
1666
1650
  end
1667
1651
 
@@ -1761,20 +1745,20 @@ module Addressable
1761
1745
  buffer = "".dup
1762
1746
  new_query_values.each do |key, value|
1763
1747
  encoded_key = URI.encode_component(
1764
- key, CharacterClasses::UNRESERVED
1748
+ key, CharacterClassesRegexps::UNRESERVED
1765
1749
  )
1766
1750
  if value == nil
1767
1751
  buffer << "#{encoded_key}&"
1768
1752
  elsif value.kind_of?(Array)
1769
1753
  value.each do |sub_value|
1770
1754
  encoded_value = URI.encode_component(
1771
- sub_value, CharacterClasses::UNRESERVED
1755
+ sub_value, CharacterClassesRegexps::UNRESERVED
1772
1756
  )
1773
1757
  buffer << "#{encoded_key}=#{encoded_value}&"
1774
1758
  end
1775
1759
  else
1776
1760
  encoded_value = URI.encode_component(
1777
- value, CharacterClasses::UNRESERVED
1761
+ value, CharacterClassesRegexps::UNRESERVED
1778
1762
  )
1779
1763
  buffer << "#{encoded_key}=#{encoded_value}&"
1780
1764
  end
@@ -1823,9 +1807,7 @@ module Addressable
1823
1807
  # The fragment component for this URI.
1824
1808
  #
1825
1809
  # @return [String] The fragment component.
1826
- def fragment
1827
- return defined?(@fragment) ? @fragment : nil
1828
- end
1810
+ attr_reader :fragment
1829
1811
 
1830
1812
  ##
1831
1813
  # The fragment component for this URI, normalized.
@@ -1833,8 +1815,8 @@ module Addressable
1833
1815
  # @return [String] The fragment component, normalized.
1834
1816
  def normalized_fragment
1835
1817
  return nil unless self.fragment
1836
- return @normalized_fragment if defined?(@normalized_fragment)
1837
- @normalized_fragment ||= begin
1818
+ return @normalized_fragment unless @normalized_fragment == NONE
1819
+ @normalized_fragment = begin
1838
1820
  component = Addressable::URI.normalize_component(
1839
1821
  self.fragment,
1840
1822
  Addressable::URI::NormalizeCharacterClasses::FRAGMENT
@@ -1842,9 +1824,7 @@ module Addressable
1842
1824
  component == "" ? nil : component
1843
1825
  end
1844
1826
  # All normalized values should be UTF-8
1845
- if @normalized_fragment
1846
- @normalized_fragment.force_encoding(Encoding::UTF_8)
1847
- end
1827
+ force_utf8_encoding_if_needed(@normalized_fragment)
1848
1828
  @normalized_fragment
1849
1829
  end
1850
1830
 
@@ -1859,7 +1839,7 @@ module Addressable
1859
1839
  @fragment = new_fragment ? new_fragment.to_str : nil
1860
1840
 
1861
1841
  # Reset dependent values
1862
- remove_instance_variable(:@normalized_fragment) if defined?(@normalized_fragment)
1842
+ @normalized_fragment = NONE
1863
1843
  remove_composite_values
1864
1844
 
1865
1845
  # Ensure we haven't created an invalid URI
@@ -2025,7 +2005,7 @@ module Addressable
2025
2005
  #
2026
2006
  # @see Hash#merge
2027
2007
  def merge(hash)
2028
- if !hash.respond_to?(:to_hash)
2008
+ unless hash.respond_to?(:to_hash)
2029
2009
  raise TypeError, "Can't convert #{hash.class} into Hash."
2030
2010
  end
2031
2011
  hash = hash.to_hash
@@ -2419,7 +2399,27 @@ module Addressable
2419
2399
  yield
2420
2400
  @validation_deferred = false
2421
2401
  validate
2422
- 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
2423
2423
  end
2424
2424
 
2425
2425
  protected
@@ -2440,30 +2440,35 @@ module Addressable
2440
2440
  def self.normalize_path(path)
2441
2441
  # Section 5.2.4 of RFC 3986
2442
2442
 
2443
- return nil if path.nil?
2443
+ return if path.nil?
2444
2444
  normalized_path = path.dup
2445
- begin
2446
- mod = nil
2445
+ loop do
2447
2446
  mod ||= normalized_path.gsub!(RULE_2A, SLASH)
2448
2447
 
2449
2448
  pair = normalized_path.match(RULE_2B_2C)
2450
- 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
+
2451
2460
  if pair && ((parent != SELF_REF && parent != PARENT) ||
2452
2461
  (current != SELF_REF && current != PARENT))
2453
- mod ||= normalized_path.gsub!(
2454
- Regexp.new(
2455
- "/#{Regexp.escape(parent.to_s)}/\\.\\./|" +
2456
- "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
2457
- ), SLASH
2458
- )
2462
+ mod ||= normalized_path.gsub!(Regexp.new(regexp), SLASH)
2459
2463
  end
2460
2464
 
2461
2465
  mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
2462
2466
  # Non-standard, removes prefixed dotted segments from path.
2463
2467
  mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
2464
- end until mod.nil?
2468
+ break if mod.nil?
2469
+ end
2465
2470
 
2466
- return normalized_path
2471
+ normalized_path
2467
2472
  end
2468
2473
 
2469
2474
  ##
@@ -2513,11 +2518,7 @@ module Addressable
2513
2518
  # @return [Addressable::URI] <code>self</code>.
2514
2519
  def replace_self(uri)
2515
2520
  # Reset dependent values
2516
- instance_variables.each do |var|
2517
- if instance_variable_defined?(var) && var != :@validation_deferred
2518
- remove_instance_variable(var)
2519
- end
2520
- end
2521
+ reset_ivs
2521
2522
 
2522
2523
  @scheme = uri.scheme
2523
2524
  @user = uri.user
@@ -2549,8 +2550,53 @@ module Addressable
2549
2550
  #
2550
2551
  # @api private
2551
2552
  def remove_composite_values
2552
- remove_instance_variable(:@uri_string) if defined?(@uri_string)
2553
- remove_instance_variable(:@hash) if defined?(@hash)
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
2554
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
2555
2601
  end
2556
2602
  end