addressable 2.5.2 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # encoding:utf-8
2
4
  #--
3
5
  # Copyright (C) Bob Aman
@@ -35,7 +37,7 @@ module Addressable
35
37
  Addressable::URI::CharacterClasses::DIGIT + '_'
36
38
 
37
39
  var_char =
38
- "(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
40
+ "(?>(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
39
41
  RESERVED =
40
42
  "(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
41
43
  UNRESERVED =
@@ -410,7 +412,7 @@ module Addressable
410
412
  # match.captures
411
413
  # #=> ["a", ["b", "c"]]
412
414
  def match(uri, processor=nil)
413
- uri = Addressable::URI.parse(uri)
415
+ uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
414
416
  mapping = {}
415
417
 
416
418
  # First, we need to process the pattern, and extract the values.
@@ -651,40 +653,6 @@ module Addressable
651
653
  self.to_regexp.named_captures
652
654
  end
653
655
 
654
- ##
655
- # Generates a route result for a given set of parameters.
656
- # Should only be used by rack-mount.
657
- #
658
- # @param params [Hash] The set of parameters used to expand the template.
659
- # @param recall [Hash] Default parameters used to expand the template.
660
- # @param options [Hash] Either a `:processor` or a `:parameterize` block.
661
- #
662
- # @api private
663
- def generate(params={}, recall={}, options={})
664
- merged = recall.merge(params)
665
- if options[:processor]
666
- processor = options[:processor]
667
- elsif options[:parameterize]
668
- # TODO: This is sending me into fits trying to shoe-horn this into
669
- # the existing API. I think I've got this backwards and processors
670
- # should be a set of 4 optional blocks named :validate, :transform,
671
- # :match, and :restore. Having to use a singleton here is a huge
672
- # code smell.
673
- processor = Object.new
674
- class <<processor
675
- attr_accessor :block
676
- def transform(name, value)
677
- block.call(name, value)
678
- end
679
- end
680
- processor.block = options[:parameterize]
681
- else
682
- processor = nil
683
- end
684
- result = self.expand(merged, processor)
685
- result.to_s if result
686
- end
687
-
688
656
  private
689
657
  def ordered_variable_defaults
690
658
  @ordered_variable_defaults ||= begin
@@ -728,54 +696,32 @@ module Addressable
728
696
  normalize_values = true)
729
697
  _, operator, varlist = *capture.match(EXPRESSION)
730
698
 
731
- vars = varlist.split(',')
699
+ vars = varlist.split(",")
732
700
 
733
- if '?' == operator
701
+ if operator == "?"
734
702
  # partial expansion of form style query variables sometimes requires a
735
703
  # slight reordering of the variables to produce a valid url.
736
704
  first_to_expand = vars.find { |varspec|
737
705
  _, name, _ = *varspec.match(VARSPEC)
738
- mapping.key? name
706
+ mapping.key?(name) && !mapping[name].nil?
739
707
  }
740
708
 
741
709
  vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand
742
710
  end
743
711
 
744
- vars
745
- .zip(operator_sequence(operator).take(vars.length))
746
- .reduce("".dup) do |acc, (varspec, op)|
712
+ vars.
713
+ inject("".dup) do |acc, varspec|
747
714
  _, name, _ = *varspec.match(VARSPEC)
748
-
749
- acc << if mapping.key? name
750
- transform_capture(mapping, "{#{op}#{varspec}}",
751
- processor, normalize_values)
752
- else
753
- "{#{op}#{varspec}}"
754
- end
755
- end
756
- end
757
-
758
- ##
759
- # Creates a lazy Enumerator of the operators that should be used to expand
760
- # variables in a varlist starting with `operator`. For example, an operator
761
- # `"?"` results in the sequence `"?","&","&"...`
762
- #
763
- # @param [String] operator from which to generate a sequence
764
- #
765
- # @return [Enumerator] sequence of operators
766
- def operator_sequence(operator)
767
- rest_operator = if "?" == operator
768
- "&"
769
- else
770
- operator
771
- end
772
- head_operator = operator
773
-
774
- Enumerator.new do |y|
775
- y << head_operator.to_s
776
- while true
777
- y << rest_operator.to_s
778
- end
715
+ next_val = if mapping.key? name
716
+ transform_capture(mapping, "{#{operator}#{varspec}}",
717
+ processor, normalize_values)
718
+ else
719
+ "{#{operator}#{varspec}}"
720
+ end
721
+ # If we've already expanded at least one '?' operator with non-empty
722
+ # value, change to '&'
723
+ operator = "&" if (operator == "?") && (next_val != "")
724
+ acc << next_val
779
725
  end
780
726
  end
781
727
 
@@ -993,15 +939,35 @@ module Addressable
993
939
  end
994
940
  end
995
941
 
942
+ ##
943
+ # Generates the <tt>Regexp</tt> that parses a template pattern. Memoizes the
944
+ # value if template processor not set (processors may not be deterministic)
945
+ #
946
+ # @param [String] pattern The URI template pattern.
947
+ # @param [#match] processor The template processor to use.
948
+ #
949
+ # @return [Array, Regexp]
950
+ # An array of expansion variables nad a regular expression which may be
951
+ # used to parse a template pattern
952
+ def parse_template_pattern(pattern, processor = nil)
953
+ if processor.nil? && pattern == @pattern
954
+ @cached_template_parse ||=
955
+ parse_new_template_pattern(pattern, processor)
956
+ else
957
+ parse_new_template_pattern(pattern, processor)
958
+ end
959
+ end
960
+
996
961
  ##
997
962
  # Generates the <tt>Regexp</tt> that parses a template pattern.
998
963
  #
999
964
  # @param [String] pattern The URI template pattern.
1000
965
  # @param [#match] processor The template processor to use.
1001
966
  #
1002
- # @return [Regexp]
1003
- # A regular expression which may be used to parse a template pattern.
1004
- def parse_template_pattern(pattern, processor=nil)
967
+ # @return [Array, Regexp]
968
+ # An array of expansion variables nad a regular expression which may be
969
+ # used to parse a template pattern
970
+ def parse_new_template_pattern(pattern, processor = nil)
1005
971
  # Escape the pattern. The two gsubs restore the escaped curly braces
1006
972
  # back to their original form. Basically, escape everything that isn't
1007
973
  # within an expansion.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # encoding:utf-8
2
4
  #--
3
5
  # Copyright (C) Bob Aman
@@ -46,12 +48,21 @@ module Addressable
46
48
  PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
47
49
  SCHEME = ALPHA + DIGIT + "\\-\\+\\."
48
50
  HOST = UNRESERVED + SUB_DELIMS + "\\[\\:\\]"
49
- AUTHORITY = PCHAR
51
+ AUTHORITY = PCHAR + "\\[\\:\\]"
50
52
  PATH = PCHAR + "\\/"
51
53
  QUERY = PCHAR + "\\/\\?"
52
54
  FRAGMENT = PCHAR + "\\/\\?"
53
55
  end
54
56
 
57
+ module NormalizeCharacterClasses
58
+ HOST = /[^#{CharacterClasses::HOST}]/
59
+ UNRESERVED = /[^#{CharacterClasses::UNRESERVED}]/
60
+ PCHAR = /[^#{CharacterClasses::PCHAR}]/
61
+ SCHEME = /[^#{CharacterClasses::SCHEME}]/
62
+ FRAGMENT = /[^#{CharacterClasses::FRAGMENT}]/
63
+ QUERY = %r{[^a-zA-Z0-9\-\.\_\~\!\$\'\(\)\*\+\,\=\:\@\/\?%]|%(?!2B|2b)}
64
+ end
65
+
55
66
  SLASH = '/'
56
67
  EMPTY_STR = ''
57
68
 
@@ -71,7 +82,7 @@ module Addressable
71
82
  "wais" => 210,
72
83
  "ldap" => 389,
73
84
  "prospero" => 1525
74
- }
85
+ }.freeze
75
86
 
76
87
  ##
77
88
  # Returns a URI object based on the parsed string.
@@ -122,9 +133,9 @@ module Addressable
122
133
  user = userinfo.strip[/^([^:]*):?/, 1]
123
134
  password = userinfo.strip[/:(.*)$/, 1]
124
135
  end
125
- host = authority.gsub(
136
+ host = authority.sub(
126
137
  /^([^\[\]]*)@/, EMPTY_STR
127
- ).gsub(
138
+ ).sub(
128
139
  /:([^:@\[\]]*?)$/, EMPTY_STR
129
140
  )
130
141
  port = authority[/:([^:@\[\]]*?)$/, 1]
@@ -182,26 +193,33 @@ module Addressable
182
193
  :scheme => "http"
183
194
  }.merge(hints)
184
195
  case uri
185
- when /^http:\/+/
186
- uri.gsub!(/^http:\/+/, "http://")
187
- when /^https:\/+/
188
- uri.gsub!(/^https:\/+/, "https://")
189
- when /^feed:\/+http:\/+/
190
- uri.gsub!(/^feed:\/+http:\/+/, "feed:http://")
191
- when /^feed:\/+/
192
- uri.gsub!(/^feed:\/+/, "feed://")
193
- when /^file:\/+/
194
- uri.gsub!(/^file:\/+/, "file:///")
196
+ when /^http:\//i
197
+ uri.sub!(/^http:\/+/i, "http://")
198
+ when /^https:\//i
199
+ uri.sub!(/^https:\/+/i, "https://")
200
+ when /^feed:\/+http:\//i
201
+ uri.sub!(/^feed:\/+http:\/+/i, "feed:http://")
202
+ when /^feed:\//i
203
+ uri.sub!(/^feed:\/+/i, "feed://")
204
+ when %r[^file:/{4}]i
205
+ uri.sub!(%r[^file:/+]i, "file:////")
206
+ when %r[^file://localhost/]i
207
+ uri.sub!(%r[^file://localhost/+]i, "file:///")
208
+ when %r[^file:/+]i
209
+ uri.sub!(%r[^file:/+]i, "file:///")
195
210
  when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
196
- uri.gsub!(/^/, hints[:scheme] + "://")
211
+ uri.sub!(/^/, hints[:scheme] + "://")
212
+ when /\A\d+\..*:\d+\z/
213
+ uri = "#{hints[:scheme]}://#{uri}"
197
214
  end
198
215
  match = uri.match(URIREGEX)
199
216
  fragments = match.captures
200
217
  authority = fragments[3]
201
218
  if authority && authority.length > 0
202
- new_authority = authority.gsub(/\\/, '/').gsub(/ /, '%20')
219
+ new_authority = authority.tr("\\", "/").gsub(" ", "%20")
203
220
  # NOTE: We want offset 4, not 3!
204
221
  offset = match.offset(4)
222
+ uri = uri.dup
205
223
  uri[offset[0]...offset[1]] = new_authority
206
224
  end
207
225
  parsed = self.parse(uri)
@@ -209,10 +227,11 @@ module Addressable
209
227
  parsed = self.parse(hints[:scheme] + "://" + uri)
210
228
  end
211
229
  if parsed.path.include?(".")
212
- new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
213
- if new_host
230
+ if parsed.path[/\b@\b/]
231
+ parsed.scheme = "mailto" unless parsed.scheme
232
+ elsif new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
214
233
  parsed.defer_validation do
215
- new_path = parsed.path.gsub(
234
+ new_path = parsed.path.sub(
216
235
  Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
217
236
  parsed.host = new_host
218
237
  parsed.path = new_path
@@ -263,24 +282,24 @@ module Addressable
263
282
  # Otherwise, convert to a String
264
283
  path = path.to_str.strip
265
284
 
266
- path.gsub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
285
+ path.sub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
267
286
  path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/
268
287
  uri = self.parse(path)
269
288
 
270
289
  if uri.scheme == nil
271
290
  # Adjust windows-style uris
272
- uri.path.gsub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
291
+ uri.path.sub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
273
292
  "/#{$1.downcase}:/"
274
293
  end
275
- uri.path.gsub!(/\\/, SLASH)
294
+ uri.path.tr!("\\", SLASH)
276
295
  if File.exist?(uri.path) &&
277
296
  File.stat(uri.path).directory?
278
- uri.path.gsub!(/\/$/, EMPTY_STR)
297
+ uri.path.chomp!(SLASH)
279
298
  uri.path = uri.path + '/'
280
299
  end
281
300
 
282
301
  # If the path is absolute, set the scheme and host.
283
- if uri.path =~ /^\//
302
+ if uri.path.start_with?(SLASH)
284
303
  uri.scheme = "file"
285
304
  uri.host = EMPTY_STR
286
305
  end
@@ -317,6 +336,21 @@ module Addressable
317
336
  return result
318
337
  end
319
338
 
339
+ ##
340
+ # Tables used to optimize encoding operations in `self.encode_component`
341
+ # 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
+
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
353
+
320
354
  ##
321
355
  # Percent encodes a URI component.
322
356
  #
@@ -383,18 +417,20 @@ module Addressable
383
417
  component.force_encoding(Encoding::ASCII_8BIT)
384
418
  # Avoiding gsub! because there are edge cases with frozen strings
385
419
  component = component.gsub(character_class) do |sequence|
386
- (sequence.unpack('C*').map { |c| "%" + ("%02x" % c).upcase }).join
420
+ SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[sequence]
387
421
  end
388
422
  if upcase_encoded.length > 0
389
- component = component.gsub(/%(#{upcase_encoded.chars.map do |char|
390
- char.unpack('C*').map { |c| '%02x' % c }.join
391
- end.join('|')})/i) { |s| s.upcase }
423
+ upcase_encoded_chars = upcase_encoded.chars.map do |char|
424
+ SEQUENCE_ENCODING_TABLE[char]
425
+ end
426
+ component = component.gsub(/%(#{upcase_encoded_chars.join('|')})/,
427
+ &:upcase)
392
428
  end
393
429
  return component
394
430
  end
395
431
 
396
432
  class << self
397
- alias_method :encode_component, :encode_component
433
+ alias_method :escape_component, :encode_component
398
434
  end
399
435
 
400
436
  ##
@@ -436,7 +472,11 @@ module Addressable
436
472
  uri = uri.dup
437
473
  # Seriously, only use UTF-8. I'm really not kidding!
438
474
  uri.force_encoding("utf-8")
439
- leave_encoded = leave_encoded.dup.force_encoding("utf-8")
475
+
476
+ unless leave_encoded.empty?
477
+ leave_encoded = leave_encoded.dup.force_encoding("utf-8")
478
+ end
479
+
440
480
  result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
441
481
  c = sequence[1..3].to_i(16).chr
442
482
  c.force_encoding("utf-8")
@@ -522,12 +562,16 @@ module Addressable
522
562
  character_class = "#{character_class}%" unless character_class.include?('%')
523
563
 
524
564
  "|%(?!#{leave_encoded.chars.map do |char|
525
- seq = char.unpack('C*').map { |c| '%02x' % c }.join
565
+ seq = SEQUENCE_ENCODING_TABLE[char]
526
566
  [seq.upcase, seq.downcase]
527
567
  end.flatten.join('|')})"
528
568
  end
529
569
 
530
- character_class = /[^#{character_class}]#{leave_re}/
570
+ character_class = if leave_re
571
+ /[^#{character_class}]#{leave_re}/
572
+ else
573
+ /[^#{character_class}]/
574
+ end
531
575
  end
532
576
  # We can't perform regexps on invalid UTF sequences, but
533
577
  # here we need to, so switch to ASCII.
@@ -851,7 +895,7 @@ module Addressable
851
895
  else
852
896
  Addressable::URI.normalize_component(
853
897
  self.scheme.strip.downcase,
854
- Addressable::URI::CharacterClasses::SCHEME
898
+ Addressable::URI::NormalizeCharacterClasses::SCHEME
855
899
  )
856
900
  end
857
901
  end
@@ -871,7 +915,7 @@ module Addressable
871
915
  new_scheme = new_scheme.to_str
872
916
  end
873
917
  if new_scheme && new_scheme !~ /\A[a-z][a-z0-9\.\+\-]*\z/i
874
- raise InvalidURIError, "Invalid scheme format: #{new_scheme}"
918
+ raise InvalidURIError, "Invalid scheme format: '#{new_scheme}'"
875
919
  end
876
920
  @scheme = new_scheme
877
921
  @scheme = nil if @scheme.to_s.strip.empty?
@@ -906,7 +950,7 @@ module Addressable
906
950
  else
907
951
  Addressable::URI.normalize_component(
908
952
  self.user.strip,
909
- Addressable::URI::CharacterClasses::UNRESERVED
953
+ Addressable::URI::NormalizeCharacterClasses::UNRESERVED
910
954
  )
911
955
  end
912
956
  end
@@ -963,7 +1007,7 @@ module Addressable
963
1007
  else
964
1008
  Addressable::URI.normalize_component(
965
1009
  self.password.strip,
966
- Addressable::URI::CharacterClasses::UNRESERVED
1010
+ Addressable::URI::NormalizeCharacterClasses::UNRESERVED
967
1011
  )
968
1012
  end
969
1013
  end
@@ -1087,6 +1131,7 @@ module Addressable
1087
1131
  # @return [String] The host component, normalized.
1088
1132
  def normalized_host
1089
1133
  return nil unless self.host
1134
+
1090
1135
  @normalized_host ||= begin
1091
1136
  if !self.host.strip.empty?
1092
1137
  result = ::Addressable::IDNA.to_ascii(
@@ -1098,14 +1143,17 @@ module Addressable
1098
1143
  end
1099
1144
  result = Addressable::URI.normalize_component(
1100
1145
  result,
1101
- CharacterClasses::HOST)
1146
+ NormalizeCharacterClasses::HOST
1147
+ )
1102
1148
  result
1103
1149
  else
1104
1150
  EMPTY_STR.dup
1105
1151
  end
1106
1152
  end
1107
1153
  # All normalized values should be UTF-8
1108
- @normalized_host.force_encoding(Encoding::UTF_8) if @normalized_host
1154
+ if @normalized_host && !@normalized_host.empty?
1155
+ @normalized_host.force_encoding(Encoding::UTF_8)
1156
+ end
1109
1157
  @normalized_host
1110
1158
  end
1111
1159
 
@@ -1163,16 +1211,25 @@ module Addressable
1163
1211
  # Returns the top-level domain for this host.
1164
1212
  #
1165
1213
  # @example
1166
- # Addressable::URI.parse("www.example.co.uk").tld # => "co.uk"
1214
+ # Addressable::URI.parse("http://www.example.co.uk").tld # => "co.uk"
1167
1215
  def tld
1168
1216
  PublicSuffix.parse(self.host, ignore_private: true).tld
1169
1217
  end
1170
1218
 
1219
+ ##
1220
+ # Sets the top-level domain for this URI.
1221
+ #
1222
+ # @param [String, #to_str] new_tld The new top-level domain.
1223
+ def tld=(new_tld)
1224
+ replaced_tld = host.sub(/#{tld}\z/, new_tld)
1225
+ self.host = PublicSuffix::Domain.new(replaced_tld).to_s
1226
+ end
1227
+
1171
1228
  ##
1172
1229
  # Returns the public suffix domain for this host.
1173
1230
  #
1174
1231
  # @example
1175
- # Addressable::URI.parse("www.example.co.uk").domain # => "example.co.uk"
1232
+ # Addressable::URI.parse("http://www.example.co.uk").domain # => "example.co.uk"
1176
1233
  def domain
1177
1234
  PublicSuffix.domain(self.host, ignore_private: true)
1178
1235
  end
@@ -1235,9 +1292,9 @@ module Addressable
1235
1292
  new_user = new_userinfo.strip[/^([^:]*):?/, 1]
1236
1293
  new_password = new_userinfo.strip[/:(.*)$/, 1]
1237
1294
  end
1238
- new_host = new_authority.gsub(
1295
+ new_host = new_authority.sub(
1239
1296
  /^([^\[\]]*)@/, EMPTY_STR
1240
- ).gsub(
1297
+ ).sub(
1241
1298
  /:([^:@\[\]]*?)$/, EMPTY_STR
1242
1299
  )
1243
1300
  new_port =
@@ -1501,7 +1558,7 @@ module Addressable
1501
1558
  result = path.strip.split(SLASH, -1).map do |segment|
1502
1559
  Addressable::URI.normalize_component(
1503
1560
  segment,
1504
- Addressable::URI::CharacterClasses::PCHAR
1561
+ Addressable::URI::NormalizeCharacterClasses::PCHAR
1505
1562
  )
1506
1563
  end.join(SLASH)
1507
1564
 
@@ -1544,7 +1601,7 @@ module Addressable
1544
1601
  # @return [String] The path's basename.
1545
1602
  def basename
1546
1603
  # Path cannot be nil
1547
- return File.basename(self.path).gsub(/;[^\/]*$/, EMPTY_STR)
1604
+ return File.basename(self.path).sub(/;[^\/]*$/, EMPTY_STR)
1548
1605
  end
1549
1606
 
1550
1607
  ##
@@ -1576,10 +1633,15 @@ module Addressable
1576
1633
  modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
1577
1634
  # Make sure possible key-value pair delimiters are escaped.
1578
1635
  modified_query_class.sub!("\\&", "").sub!("\\;", "")
1579
- pairs = (self.query || "").split("&", -1)
1636
+ pairs = (query || "").split("&", -1)
1637
+ pairs.delete_if(&:empty?).uniq! if flags.include?(:compacted)
1580
1638
  pairs.sort! if flags.include?(:sorted)
1581
1639
  component = pairs.map do |pair|
1582
- Addressable::URI.normalize_component(pair, modified_query_class, "+")
1640
+ Addressable::URI.normalize_component(
1641
+ pair,
1642
+ Addressable::URI::NormalizeCharacterClasses::QUERY,
1643
+ "+"
1644
+ )
1583
1645
  end.join("&")
1584
1646
  component == "" ? nil : component
1585
1647
  end
@@ -1638,11 +1700,13 @@ module Addressable
1638
1700
  # so it's best to make all changes in-place.
1639
1701
  pair[0] = URI.unencode_component(pair[0])
1640
1702
  if pair[1].respond_to?(:to_str)
1703
+ value = pair[1].to_str
1641
1704
  # I loathe the fact that I have to do this. Stupid HTML 4.01.
1642
1705
  # Treating '+' as a space was just an unbelievably bad idea.
1643
1706
  # There was nothing wrong with '%20'!
1644
1707
  # If it ain't broke, don't fix it!
1645
- pair[1] = URI.unencode_component(pair[1].to_str.gsub(/\+/, " "))
1708
+ value = value.tr("+", " ") if ["http", "https", nil].include?(scheme)
1709
+ pair[1] = URI.unencode_component(value)
1646
1710
  end
1647
1711
  if return_type == Hash
1648
1712
  accu[pair[0]] = pair[1]
@@ -1744,7 +1808,7 @@ module Addressable
1744
1808
  "Cannot set an HTTP request URI for a non-HTTP URI."
1745
1809
  end
1746
1810
  new_request_uri = new_request_uri.to_str
1747
- path_component = new_request_uri[/^([^\?]*)\?(?:.*)$/, 1]
1811
+ path_component = new_request_uri[/^([^\?]*)\??(?:.*)$/, 1]
1748
1812
  query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1]
1749
1813
  path_component = path_component.to_s
1750
1814
  path_component = (!path_component.empty? ? path_component : SLASH)
@@ -1773,7 +1837,7 @@ module Addressable
1773
1837
  @normalized_fragment ||= begin
1774
1838
  component = Addressable::URI.normalize_component(
1775
1839
  self.fragment,
1776
- Addressable::URI::CharacterClasses::FRAGMENT
1840
+ Addressable::URI::NormalizeCharacterClasses::FRAGMENT
1777
1841
  )
1778
1842
  component == "" ? nil : component
1779
1843
  end
@@ -1899,8 +1963,8 @@ module Addressable
1899
1963
  # Section 5.2.3 of RFC 3986
1900
1964
  #
1901
1965
  # Removes the right-most path segment from the base path.
1902
- if base_path =~ /\//
1903
- base_path.gsub!(/\/[^\/]+$/, SLASH)
1966
+ if base_path.include?(SLASH)
1967
+ base_path.sub!(/\/[^\/]+$/, SLASH)
1904
1968
  else
1905
1969
  base_path = EMPTY_STR
1906
1970
  end
@@ -2349,10 +2413,10 @@ module Addressable
2349
2413
  #
2350
2414
  # @param [Proc] block
2351
2415
  # A set of operations to perform on a given URI.
2352
- def defer_validation(&block)
2353
- raise LocalJumpError, "No block given." unless block
2416
+ def defer_validation
2417
+ raise LocalJumpError, "No block given." unless block_given?
2354
2418
  @validation_deferred = true
2355
- block.call()
2419
+ yield
2356
2420
  @validation_deferred = false
2357
2421
  validate
2358
2422
  return nil