addressable 2.8.0 → 2.8.5

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,26 @@ 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
+ PCHAR = (UNRESERVED + SUB_DELIMS + "\\:\\@").freeze
54
+ SCHEME = (ALPHA + DIGIT + "\\-\\+\\.").freeze
55
+ HOST = (UNRESERVED + SUB_DELIMS + "\\[\\:\\]").freeze
56
+ AUTHORITY = (PCHAR + "\\[\\]").freeze
57
+ PATH = (PCHAR + "\\/").freeze
58
+ QUERY = (PCHAR + "\\/\\?").freeze
59
+ FRAGMENT = (PCHAR + "\\/\\?").freeze
55
60
  end
56
61
 
57
62
  module NormalizeCharacterClasses
@@ -112,7 +117,7 @@ module Addressable
112
117
  uri = uri.to_str
113
118
  rescue TypeError, NoMethodError
114
119
  raise TypeError, "Can't convert #{uri.class} into String."
115
- end if not uri.is_a? String
120
+ end unless uri.is_a?(String)
116
121
 
117
122
  # This Regexp supplied as an example in RFC 3986, and it works great.
118
123
  scan = uri.scan(URIREGEX)
@@ -133,15 +138,15 @@ module Addressable
133
138
  user = userinfo.strip[/^([^:]*):?/, 1]
134
139
  password = userinfo.strip[/:(.*)$/, 1]
135
140
  end
141
+
136
142
  host = authority.sub(
137
143
  /^([^\[\]]*)@/, EMPTY_STR
138
144
  ).sub(
139
145
  /:([^:@\[\]]*?)$/, EMPTY_STR
140
146
  )
147
+
141
148
  port = authority[/:([^:@\[\]]*?)$/, 1]
142
- end
143
- if port == EMPTY_STR
144
- port = nil
149
+ port = nil if port == EMPTY_STR
145
150
  end
146
151
 
147
152
  return new(
@@ -184,7 +189,7 @@ module Addressable
184
189
  uri = uri.to_s
185
190
  end
186
191
 
187
- if !uri.respond_to?(:to_str)
192
+ unless uri.respond_to?(:to_str)
188
193
  raise TypeError, "Can't convert #{uri.class} into String."
189
194
  end
190
195
  # Otherwise, convert to a String
@@ -276,7 +281,7 @@ module Addressable
276
281
  return nil unless path
277
282
  # If a URI object is passed, just return itself.
278
283
  return path if path.kind_of?(self)
279
- if !path.respond_to?(:to_str)
284
+ unless path.respond_to?(:to_str)
280
285
  raise TypeError, "Can't convert #{path.class} into String."
281
286
  end
282
287
  # Otherwise, convert to a String
@@ -324,13 +329,13 @@ module Addressable
324
329
  # #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
325
330
  def self.join(*uris)
326
331
  uri_objects = uris.collect do |uri|
327
- if !uri.respond_to?(:to_str)
332
+ unless uri.respond_to?(:to_str)
328
333
  raise TypeError, "Can't convert #{uri.class} into String."
329
334
  end
330
335
  uri.kind_of?(self) ? uri : self.parse(uri.to_str)
331
336
  end
332
337
  result = uri_objects.shift.dup
333
- for uri in uri_objects
338
+ uri_objects.each do |uri|
334
339
  result.join!(uri)
335
340
  end
336
341
  return result
@@ -339,17 +344,13 @@ module Addressable
339
344
  ##
340
345
  # Tables used to optimize encoding operations in `self.encode_component`
341
346
  # 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
347
+ SEQUENCE_ENCODING_TABLE = (0..255).map do |byte|
348
+ format("%02x", byte).freeze
349
+ end.freeze
347
350
 
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
351
+ SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE = (0..255).map do |byte|
352
+ format("%%%02X", byte).freeze
353
+ end.freeze
353
354
 
354
355
  ##
355
356
  # Percent encodes a URI component.
@@ -416,16 +417,17 @@ module Addressable
416
417
  component = component.dup
417
418
  component.force_encoding(Encoding::ASCII_8BIT)
418
419
  # 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]
420
+ component = component.gsub(character_class) do |char|
421
+ SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[char.ord]
421
422
  end
422
423
  if upcase_encoded.length > 0
423
- upcase_encoded_chars = upcase_encoded.chars.map do |char|
424
- SEQUENCE_ENCODING_TABLE[char]
424
+ upcase_encoded_chars = upcase_encoded.bytes.map do |byte|
425
+ SEQUENCE_ENCODING_TABLE[byte]
425
426
  end
426
427
  component = component.gsub(/%(#{upcase_encoded_chars.join('|')})/,
427
428
  &:upcase)
428
429
  end
430
+
429
431
  return component
430
432
  end
431
433
 
@@ -469,20 +471,14 @@ module Addressable
469
471
  "Expected Class (String or Addressable::URI), " +
470
472
  "got #{return_type.inspect}"
471
473
  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
474
 
480
- result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
475
+ result = uri.gsub(/%[0-9a-f]{2}/i) do |sequence|
481
476
  c = sequence[1..3].to_i(16).chr
482
- c.force_encoding("utf-8")
477
+ c.force_encoding(sequence.encoding)
483
478
  leave_encoded.include?(c) ? sequence : c
484
479
  end
485
- result.force_encoding("utf-8")
480
+
481
+ result.force_encoding(Encoding::UTF_8)
486
482
  if return_type == String
487
483
  return result
488
484
  elsif return_type == ::Addressable::URI
@@ -561,10 +557,9 @@ module Addressable
561
557
  leave_re = if leave_encoded.length > 0
562
558
  character_class = "#{character_class}%" unless character_class.include?('%')
563
559
 
564
- "|%(?!#{leave_encoded.chars.map do |char|
565
- seq = SEQUENCE_ENCODING_TABLE[char]
566
- [seq.upcase, seq.downcase]
567
- end.flatten.join('|')})"
560
+ bytes = leave_encoded.bytes
561
+ leave_encoded_pattern = bytes.map { |b| SEQUENCE_ENCODING_TABLE[b] }.join('|')
562
+ "|%(?!#{leave_encoded_pattern}|#{leave_encoded_pattern.upcase})"
568
563
  end
569
564
 
570
565
  character_class = if leave_re
@@ -580,7 +575,7 @@ module Addressable
580
575
  unencoded = self.unencode_component(component, String, leave_encoded)
581
576
  begin
582
577
  encoded = self.encode_component(
583
- Addressable::IDNA.unicode_normalize_kc(unencoded),
578
+ unencoded.unicode_normalize(:nfc),
584
579
  character_class,
585
580
  leave_encoded
586
581
  )
@@ -688,8 +683,7 @@ module Addressable
688
683
  components.each do |key, value|
689
684
  if value != nil
690
685
  begin
691
- components[key] =
692
- Addressable::IDNA.unicode_normalize_kc(value.to_str)
686
+ components[key] = value.to_str.unicode_normalize(:nfc)
693
687
  rescue ArgumentError
694
688
  # Likely a malformed UTF-8 character, skip unicode normalization
695
689
  components[key] = value.to_str
@@ -837,7 +831,9 @@ module Addressable
837
831
  end
838
832
  end
839
833
 
840
- self.defer_validation do
834
+ reset_ivs
835
+
836
+ defer_validation do
841
837
  # Bunch of crazy logic required because of the composite components
842
838
  # like userinfo and authority.
843
839
  self.scheme = options[:scheme] if options[:scheme]
@@ -852,7 +848,8 @@ module Addressable
852
848
  self.query_values = options[:query_values] if options[:query_values]
853
849
  self.fragment = options[:fragment] if options[:fragment]
854
850
  end
855
- self.to_s
851
+
852
+ to_s # force path validation
856
853
  end
857
854
 
858
855
  ##
@@ -879,9 +876,7 @@ module Addressable
879
876
  # The scheme component for this URI.
880
877
  #
881
878
  # @return [String] The scheme component.
882
- def scheme
883
- return defined?(@scheme) ? @scheme : nil
884
- end
879
+ attr_reader :scheme
885
880
 
886
881
  ##
887
882
  # The scheme component for this URI, normalized.
@@ -889,8 +884,8 @@ module Addressable
889
884
  # @return [String] The scheme component, normalized.
890
885
  def normalized_scheme
891
886
  return nil unless self.scheme
892
- @normalized_scheme ||= begin
893
- if self.scheme =~ /^\s*ssh\+svn\s*$/i
887
+ if @normalized_scheme == NONE
888
+ @normalized_scheme = if self.scheme =~ /^\s*ssh\+svn\s*$/i
894
889
  "svn+ssh".dup
895
890
  else
896
891
  Addressable::URI.normalize_component(
@@ -900,7 +895,7 @@ module Addressable
900
895
  end
901
896
  end
902
897
  # All normalized values should be UTF-8
903
- @normalized_scheme.force_encoding(Encoding::UTF_8) if @normalized_scheme
898
+ force_utf8_encoding_if_needed(@normalized_scheme)
904
899
  @normalized_scheme
905
900
  end
906
901
 
@@ -921,7 +916,7 @@ module Addressable
921
916
  @scheme = nil if @scheme.to_s.strip.empty?
922
917
 
923
918
  # Reset dependent values
924
- remove_instance_variable(:@normalized_scheme) if defined?(@normalized_scheme)
919
+ @normalized_scheme = NONE
925
920
  remove_composite_values
926
921
 
927
922
  # Ensure we haven't created an invalid URI
@@ -932,9 +927,7 @@ module Addressable
932
927
  # The user component for this URI.
933
928
  #
934
929
  # @return [String] The user component.
935
- def user
936
- return defined?(@user) ? @user : nil
937
- end
930
+ attr_reader :user
938
931
 
939
932
  ##
940
933
  # The user component for this URI, normalized.
@@ -942,8 +935,8 @@ module Addressable
942
935
  # @return [String] The user component, normalized.
943
936
  def normalized_user
944
937
  return nil unless self.user
945
- return @normalized_user if defined?(@normalized_user)
946
- @normalized_user ||= begin
938
+ return @normalized_user unless @normalized_user == NONE
939
+ @normalized_user = begin
947
940
  if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
948
941
  (!self.password || self.password.strip.empty?)
949
942
  nil
@@ -955,7 +948,7 @@ module Addressable
955
948
  end
956
949
  end
957
950
  # All normalized values should be UTF-8
958
- @normalized_user.force_encoding(Encoding::UTF_8) if @normalized_user
951
+ force_utf8_encoding_if_needed(@normalized_user)
959
952
  @normalized_user
960
953
  end
961
954
 
@@ -971,14 +964,14 @@ module Addressable
971
964
 
972
965
  # You can't have a nil user with a non-nil password
973
966
  if password != nil
974
- @user = EMPTY_STR if @user.nil?
967
+ @user = EMPTY_STR unless user
975
968
  end
976
969
 
977
970
  # 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)
971
+ @userinfo = nil
972
+ @normalized_userinfo = NONE
973
+ @authority = nil
974
+ @normalized_user = NONE
982
975
  remove_composite_values
983
976
 
984
977
  # Ensure we haven't created an invalid URI
@@ -989,9 +982,7 @@ module Addressable
989
982
  # The password component for this URI.
990
983
  #
991
984
  # @return [String] The password component.
992
- def password
993
- return defined?(@password) ? @password : nil
994
- end
985
+ attr_reader :password
995
986
 
996
987
  ##
997
988
  # The password component for this URI, normalized.
@@ -999,8 +990,8 @@ module Addressable
999
990
  # @return [String] The password component, normalized.
1000
991
  def normalized_password
1001
992
  return nil unless self.password
1002
- return @normalized_password if defined?(@normalized_password)
1003
- @normalized_password ||= begin
993
+ return @normalized_password unless @normalized_password == NONE
994
+ @normalized_password = begin
1004
995
  if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
1005
996
  (!self.user || self.user.strip.empty?)
1006
997
  nil
@@ -1012,9 +1003,7 @@ module Addressable
1012
1003
  end
1013
1004
  end
1014
1005
  # All normalized values should be UTF-8
1015
- if @normalized_password
1016
- @normalized_password.force_encoding(Encoding::UTF_8)
1017
- end
1006
+ force_utf8_encoding_if_needed(@normalized_password)
1018
1007
  @normalized_password
1019
1008
  end
1020
1009
 
@@ -1029,17 +1018,15 @@ module Addressable
1029
1018
  @password = new_password ? new_password.to_str : nil
1030
1019
 
1031
1020
  # You can't have a nil user with a non-nil password
1032
- @password ||= nil
1033
- @user ||= nil
1034
1021
  if @password != nil
1035
- @user = EMPTY_STR if @user.nil?
1022
+ self.user = EMPTY_STR if user.nil?
1036
1023
  end
1037
1024
 
1038
1025
  # 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)
1026
+ @userinfo = nil
1027
+ @normalized_userinfo = NONE
1028
+ @authority = nil
1029
+ @normalized_password = NONE
1043
1030
  remove_composite_values
1044
1031
 
1045
1032
  # Ensure we haven't created an invalid URI
@@ -1069,8 +1056,8 @@ module Addressable
1069
1056
  # @return [String] The userinfo component, normalized.
1070
1057
  def normalized_userinfo
1071
1058
  return nil unless self.userinfo
1072
- return @normalized_userinfo if defined?(@normalized_userinfo)
1073
- @normalized_userinfo ||= begin
1059
+ return @normalized_userinfo unless @normalized_userinfo == NONE
1060
+ @normalized_userinfo = begin
1074
1061
  current_user = self.normalized_user
1075
1062
  current_password = self.normalized_password
1076
1063
  if !current_user && !current_password
@@ -1082,9 +1069,7 @@ module Addressable
1082
1069
  end
1083
1070
  end
1084
1071
  # All normalized values should be UTF-8
1085
- if @normalized_userinfo
1086
- @normalized_userinfo.force_encoding(Encoding::UTF_8)
1087
- end
1072
+ force_utf8_encoding_if_needed(@normalized_userinfo)
1088
1073
  @normalized_userinfo
1089
1074
  end
1090
1075
 
@@ -1110,7 +1095,7 @@ module Addressable
1110
1095
  self.user = new_user
1111
1096
 
1112
1097
  # Reset dependent values
1113
- remove_instance_variable(:@authority) if defined?(@authority)
1098
+ @authority = nil
1114
1099
  remove_composite_values
1115
1100
 
1116
1101
  # Ensure we haven't created an invalid URI
@@ -1121,9 +1106,7 @@ module Addressable
1121
1106
  # The host component for this URI.
1122
1107
  #
1123
1108
  # @return [String] The host component.
1124
- def host
1125
- return defined?(@host) ? @host : nil
1126
- end
1109
+ attr_reader :host
1127
1110
 
1128
1111
  ##
1129
1112
  # The host component for this URI, normalized.
@@ -1151,9 +1134,7 @@ module Addressable
1151
1134
  end
1152
1135
  end
1153
1136
  # All normalized values should be UTF-8
1154
- if @normalized_host && !@normalized_host.empty?
1155
- @normalized_host.force_encoding(Encoding::UTF_8)
1156
- end
1137
+ force_utf8_encoding_if_needed(@normalized_host)
1157
1138
  @normalized_host
1158
1139
  end
1159
1140
 
@@ -1168,8 +1149,8 @@ module Addressable
1168
1149
  @host = new_host ? new_host.to_str : nil
1169
1150
 
1170
1151
  # Reset dependent values
1171
- remove_instance_variable(:@authority) if defined?(@authority)
1172
- remove_instance_variable(:@normalized_host) if defined?(@normalized_host)
1152
+ @authority = nil
1153
+ @normalized_host = nil
1173
1154
  remove_composite_values
1174
1155
 
1175
1156
  # Ensure we haven't created an invalid URI
@@ -1271,9 +1252,7 @@ module Addressable
1271
1252
  authority
1272
1253
  end
1273
1254
  # All normalized values should be UTF-8
1274
- if @normalized_authority
1275
- @normalized_authority.force_encoding(Encoding::UTF_8)
1276
- end
1255
+ force_utf8_encoding_if_needed(@normalized_authority)
1277
1256
  @normalized_authority
1278
1257
  end
1279
1258
 
@@ -1302,14 +1281,14 @@ module Addressable
1302
1281
  end
1303
1282
 
1304
1283
  # 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
1284
+ self.password = new_password
1285
+ self.user = new_user
1286
+ self.host = new_host
1287
+ self.port = new_port
1309
1288
 
1310
1289
  # Reset dependent values
1311
- remove_instance_variable(:@userinfo) if defined?(@userinfo)
1312
- remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
1290
+ @userinfo = nil
1291
+ @normalized_userinfo = NONE
1313
1292
  remove_composite_values
1314
1293
 
1315
1294
  # Ensure we haven't created an invalid URI
@@ -1357,16 +1336,16 @@ module Addressable
1357
1336
  new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
1358
1337
  end
1359
1338
 
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
1339
+ self.scheme = new_scheme
1340
+ self.host = new_host
1341
+ self.port = new_port
1363
1342
  self.userinfo = nil
1364
1343
 
1365
1344
  # 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)
1345
+ @userinfo = nil
1346
+ @normalized_userinfo = NONE
1347
+ @authority = nil
1348
+ @normalized_authority = nil
1370
1349
  remove_composite_values
1371
1350
 
1372
1351
  # Ensure we haven't created an invalid URI
@@ -1393,9 +1372,7 @@ module Addressable
1393
1372
  # infer port numbers from default values.
1394
1373
  #
1395
1374
  # @return [Integer] The port component.
1396
- def port
1397
- return defined?(@port) ? @port : nil
1398
- end
1375
+ attr_reader :port
1399
1376
 
1400
1377
  ##
1401
1378
  # The port component for this URI, normalized.
@@ -1403,8 +1380,8 @@ module Addressable
1403
1380
  # @return [Integer] The port component, normalized.
1404
1381
  def normalized_port
1405
1382
  return nil unless self.port
1406
- return @normalized_port if defined?(@normalized_port)
1407
- @normalized_port ||= begin
1383
+ return @normalized_port unless @normalized_port == NONE
1384
+ @normalized_port = begin
1408
1385
  if URI.port_mapping[self.normalized_scheme] == self.port
1409
1386
  nil
1410
1387
  else
@@ -1435,8 +1412,8 @@ module Addressable
1435
1412
  @port = nil if @port == 0
1436
1413
 
1437
1414
  # Reset dependent values
1438
- remove_instance_variable(:@authority) if defined?(@authority)
1439
- remove_instance_variable(:@normalized_port) if defined?(@normalized_port)
1415
+ @authority = nil
1416
+ @normalized_port = NONE
1440
1417
  remove_composite_values
1441
1418
 
1442
1419
  # Ensure we haven't created an invalid URI
@@ -1507,7 +1484,7 @@ module Addressable
1507
1484
  site_string
1508
1485
  end
1509
1486
  # All normalized values should be UTF-8
1510
- @normalized_site.force_encoding(Encoding::UTF_8) if @normalized_site
1487
+ force_utf8_encoding_if_needed(@normalized_site)
1511
1488
  @normalized_site
1512
1489
  end
1513
1490
 
@@ -1537,9 +1514,7 @@ module Addressable
1537
1514
  # The path component for this URI.
1538
1515
  #
1539
1516
  # @return [String] The path component.
1540
- def path
1541
- return defined?(@path) ? @path : EMPTY_STR
1542
- end
1517
+ attr_reader :path
1543
1518
 
1544
1519
  NORMPATH = /^(?!\/)[^\/:]*:.*$/
1545
1520
  ##
@@ -1570,7 +1545,7 @@ module Addressable
1570
1545
  result
1571
1546
  end
1572
1547
  # All normalized values should be UTF-8
1573
- @normalized_path.force_encoding(Encoding::UTF_8) if @normalized_path
1548
+ force_utf8_encoding_if_needed(@normalized_path)
1574
1549
  @normalized_path
1575
1550
  end
1576
1551
 
@@ -1588,7 +1563,7 @@ module Addressable
1588
1563
  end
1589
1564
 
1590
1565
  # Reset dependent values
1591
- remove_instance_variable(:@normalized_path) if defined?(@normalized_path)
1566
+ @normalized_path = nil
1592
1567
  remove_composite_values
1593
1568
 
1594
1569
  # Ensure we haven't created an invalid URI
@@ -1618,9 +1593,7 @@ module Addressable
1618
1593
  # The query component for this URI.
1619
1594
  #
1620
1595
  # @return [String] The query component.
1621
- def query
1622
- return defined?(@query) ? @query : nil
1623
- end
1596
+ attr_reader :query
1624
1597
 
1625
1598
  ##
1626
1599
  # The query component for this URI, normalized.
@@ -1628,8 +1601,8 @@ module Addressable
1628
1601
  # @return [String] The query component, normalized.
1629
1602
  def normalized_query(*flags)
1630
1603
  return nil unless self.query
1631
- return @normalized_query if defined?(@normalized_query)
1632
- @normalized_query ||= begin
1604
+ return @normalized_query unless @normalized_query == NONE
1605
+ @normalized_query = begin
1633
1606
  modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
1634
1607
  # Make sure possible key-value pair delimiters are escaped.
1635
1608
  modified_query_class.sub!("\\&", "").sub!("\\;", "")
@@ -1646,7 +1619,7 @@ module Addressable
1646
1619
  component == "" ? nil : component
1647
1620
  end
1648
1621
  # All normalized values should be UTF-8
1649
- @normalized_query.force_encoding(Encoding::UTF_8) if @normalized_query
1622
+ force_utf8_encoding_if_needed(@normalized_query)
1650
1623
  @normalized_query
1651
1624
  end
1652
1625
 
@@ -1661,7 +1634,7 @@ module Addressable
1661
1634
  @query = new_query ? new_query.to_str : nil
1662
1635
 
1663
1636
  # Reset dependent values
1664
- remove_instance_variable(:@normalized_query) if defined?(@normalized_query)
1637
+ @normalized_query = NONE
1665
1638
  remove_composite_values
1666
1639
  end
1667
1640
 
@@ -1823,9 +1796,7 @@ module Addressable
1823
1796
  # The fragment component for this URI.
1824
1797
  #
1825
1798
  # @return [String] The fragment component.
1826
- def fragment
1827
- return defined?(@fragment) ? @fragment : nil
1828
- end
1799
+ attr_reader :fragment
1829
1800
 
1830
1801
  ##
1831
1802
  # The fragment component for this URI, normalized.
@@ -1833,8 +1804,8 @@ module Addressable
1833
1804
  # @return [String] The fragment component, normalized.
1834
1805
  def normalized_fragment
1835
1806
  return nil unless self.fragment
1836
- return @normalized_fragment if defined?(@normalized_fragment)
1837
- @normalized_fragment ||= begin
1807
+ return @normalized_fragment unless @normalized_fragment == NONE
1808
+ @normalized_fragment = begin
1838
1809
  component = Addressable::URI.normalize_component(
1839
1810
  self.fragment,
1840
1811
  Addressable::URI::NormalizeCharacterClasses::FRAGMENT
@@ -1842,9 +1813,7 @@ module Addressable
1842
1813
  component == "" ? nil : component
1843
1814
  end
1844
1815
  # All normalized values should be UTF-8
1845
- if @normalized_fragment
1846
- @normalized_fragment.force_encoding(Encoding::UTF_8)
1847
- end
1816
+ force_utf8_encoding_if_needed(@normalized_fragment)
1848
1817
  @normalized_fragment
1849
1818
  end
1850
1819
 
@@ -1859,7 +1828,7 @@ module Addressable
1859
1828
  @fragment = new_fragment ? new_fragment.to_str : nil
1860
1829
 
1861
1830
  # Reset dependent values
1862
- remove_instance_variable(:@normalized_fragment) if defined?(@normalized_fragment)
1831
+ @normalized_fragment = NONE
1863
1832
  remove_composite_values
1864
1833
 
1865
1834
  # Ensure we haven't created an invalid URI
@@ -2025,7 +1994,7 @@ module Addressable
2025
1994
  #
2026
1995
  # @see Hash#merge
2027
1996
  def merge(hash)
2028
- if !hash.respond_to?(:to_hash)
1997
+ unless hash.respond_to?(:to_hash)
2029
1998
  raise TypeError, "Can't convert #{hash.class} into Hash."
2030
1999
  end
2031
2000
  hash = hash.to_hash
@@ -2419,7 +2388,27 @@ module Addressable
2419
2388
  yield
2420
2389
  @validation_deferred = false
2421
2390
  validate
2422
- return nil
2391
+ ensure
2392
+ @validation_deferred = false
2393
+ end
2394
+
2395
+ def encode_with(coder)
2396
+ instance_variables.each do |ivar|
2397
+ value = instance_variable_get(ivar)
2398
+ if value != NONE
2399
+ key = ivar.to_s.slice(1..-1)
2400
+ coder[key] = value
2401
+ end
2402
+ end
2403
+ nil
2404
+ end
2405
+
2406
+ def init_with(coder)
2407
+ reset_ivs
2408
+ coder.map.each do |key, value|
2409
+ instance_variable_set("@#{key}", value)
2410
+ end
2411
+ nil
2423
2412
  end
2424
2413
 
2425
2414
  protected
@@ -2440,30 +2429,35 @@ module Addressable
2440
2429
  def self.normalize_path(path)
2441
2430
  # Section 5.2.4 of RFC 3986
2442
2431
 
2443
- return nil if path.nil?
2432
+ return if path.nil?
2444
2433
  normalized_path = path.dup
2445
- begin
2446
- mod = nil
2434
+ loop do
2447
2435
  mod ||= normalized_path.gsub!(RULE_2A, SLASH)
2448
2436
 
2449
2437
  pair = normalized_path.match(RULE_2B_2C)
2450
- parent, current = pair[1], pair[2] if pair
2438
+ if pair
2439
+ parent = pair[1]
2440
+ current = pair[2]
2441
+ else
2442
+ parent = nil
2443
+ current = nil
2444
+ end
2445
+
2446
+ regexp = "/#{Regexp.escape(parent.to_s)}/\\.\\./|"
2447
+ regexp += "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
2448
+
2451
2449
  if pair && ((parent != SELF_REF && parent != PARENT) ||
2452
2450
  (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
- )
2451
+ mod ||= normalized_path.gsub!(Regexp.new(regexp), SLASH)
2459
2452
  end
2460
2453
 
2461
2454
  mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
2462
2455
  # Non-standard, removes prefixed dotted segments from path.
2463
2456
  mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
2464
- end until mod.nil?
2457
+ break if mod.nil?
2458
+ end
2465
2459
 
2466
- return normalized_path
2460
+ normalized_path
2467
2461
  end
2468
2462
 
2469
2463
  ##
@@ -2513,11 +2507,7 @@ module Addressable
2513
2507
  # @return [Addressable::URI] <code>self</code>.
2514
2508
  def replace_self(uri)
2515
2509
  # 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
2510
+ reset_ivs
2521
2511
 
2522
2512
  @scheme = uri.scheme
2523
2513
  @user = uri.user
@@ -2549,8 +2539,53 @@ module Addressable
2549
2539
  #
2550
2540
  # @api private
2551
2541
  def remove_composite_values
2552
- remove_instance_variable(:@uri_string) if defined?(@uri_string)
2553
- remove_instance_variable(:@hash) if defined?(@hash)
2542
+ @uri_string = nil
2543
+ @hash = nil
2544
+ end
2545
+
2546
+ ##
2547
+ # Converts the string to be UTF-8 if it is not already UTF-8
2548
+ #
2549
+ # @api private
2550
+ def force_utf8_encoding_if_needed(str)
2551
+ if str && str.encoding != Encoding::UTF_8
2552
+ str.force_encoding(Encoding::UTF_8)
2553
+ end
2554
2554
  end
2555
+
2556
+ private
2557
+
2558
+ ##
2559
+ # Resets instance variables
2560
+ #
2561
+ # @api private
2562
+ def reset_ivs
2563
+ @scheme = nil
2564
+ @user = nil
2565
+ @normalized_scheme = NONE
2566
+ @normalized_user = NONE
2567
+ @uri_string = nil
2568
+ @hash = nil
2569
+ @userinfo = nil
2570
+ @normalized_userinfo = NONE
2571
+ @authority = nil
2572
+ @password = nil
2573
+ @normalized_authority = nil
2574
+ @port = nil
2575
+ @normalized_password = NONE
2576
+ @host = nil
2577
+ @normalized_host = nil
2578
+ @normalized_port = NONE
2579
+ @path = EMPTY_STR
2580
+ @normalized_path = nil
2581
+ @normalized_query = NONE
2582
+ @fragment = nil
2583
+ @normalized_fragment = NONE
2584
+ @query = nil
2585
+ end
2586
+
2587
+ NONE = Module.new.freeze
2588
+
2589
+ private_constant :NONE
2555
2590
  end
2556
2591
  end