addressable 2.8.0 → 2.8.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  ##
@@ -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