addressable 2.5.0 → 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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +40 -0
- data/Gemfile +13 -17
- data/README.md +14 -15
- data/Rakefile +5 -3
- data/addressable.gemspec +37 -0
- data/lib/addressable.rb +2 -0
- data/lib/addressable/idna.rb +2 -0
- data/lib/addressable/idna/native.rb +4 -2
- data/lib/addressable/idna/pure.rb +54 -53
- data/lib/addressable/template.rb +63 -83
- data/lib/addressable/uri.rb +128 -64
- data/lib/addressable/version.rb +3 -1
- data/spec/addressable/idna_spec.rb +23 -7
- data/spec/addressable/net_http_compat_spec.rb +2 -0
- data/spec/addressable/security_spec.rb +2 -0
- data/spec/addressable/template_spec.rb +75 -2
- data/spec/addressable/uri_spec.rb +408 -215
- data/spec/spec_helper.rb +12 -0
- data/tasks/clobber.rake +2 -0
- data/tasks/gem.rake +8 -7
- data/tasks/git.rake +2 -0
- data/tasks/metrics.rake +2 -0
- data/tasks/profile.rake +72 -0
- data/tasks/rspec.rake +3 -1
- data/tasks/yard.rake +2 -0
- metadata +22 -16
- data/spec/addressable/rack_mount_compat_spec.rb +0 -104
data/lib/addressable/template.rb
CHANGED
@@ -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
|
-
"(
|
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.
|
@@ -488,6 +490,8 @@ module Addressable
|
|
488
490
|
# @param [Hash] mapping The mapping that corresponds to the pattern.
|
489
491
|
# @param [#validate, #transform] processor
|
490
492
|
# An optional processor object may be supplied.
|
493
|
+
# @param [Boolean] normalize_values
|
494
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
491
495
|
#
|
492
496
|
# The object should respond to either the <tt>validate</tt> or
|
493
497
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
@@ -518,11 +522,11 @@ module Addressable
|
|
518
522
|
# "http://example.com/{?one,two,three}/"
|
519
523
|
# ).partial_expand({"one" => "1", "three" => 3}).pattern
|
520
524
|
# #=> "http://example.com/?one=1{&two}&three=3"
|
521
|
-
def partial_expand(mapping, processor=nil)
|
525
|
+
def partial_expand(mapping, processor=nil, normalize_values=true)
|
522
526
|
result = self.pattern.dup
|
523
527
|
mapping = normalize_keys(mapping)
|
524
528
|
result.gsub!( EXPRESSION ) do |capture|
|
525
|
-
transform_partial_capture(mapping, capture, processor)
|
529
|
+
transform_partial_capture(mapping, capture, processor, normalize_values)
|
526
530
|
end
|
527
531
|
return Addressable::Template.new(result)
|
528
532
|
end
|
@@ -533,6 +537,8 @@ module Addressable
|
|
533
537
|
# @param [Hash] mapping The mapping that corresponds to the pattern.
|
534
538
|
# @param [#validate, #transform] processor
|
535
539
|
# An optional processor object may be supplied.
|
540
|
+
# @param [Boolean] normalize_values
|
541
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
536
542
|
#
|
537
543
|
# The object should respond to either the <tt>validate</tt> or
|
538
544
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
@@ -583,11 +589,11 @@ module Addressable
|
|
583
589
|
# ExampleProcessor
|
584
590
|
# ).to_str
|
585
591
|
# #=> Addressable::Template::InvalidTemplateValueError
|
586
|
-
def expand(mapping, processor=nil)
|
592
|
+
def expand(mapping, processor=nil, normalize_values=true)
|
587
593
|
result = self.pattern.dup
|
588
594
|
mapping = normalize_keys(mapping)
|
589
595
|
result.gsub!( EXPRESSION ) do |capture|
|
590
|
-
transform_capture(mapping, capture, processor)
|
596
|
+
transform_capture(mapping, capture, processor, normalize_values)
|
591
597
|
end
|
592
598
|
return Addressable::URI.parse(result)
|
593
599
|
end
|
@@ -647,40 +653,6 @@ module Addressable
|
|
647
653
|
self.to_regexp.named_captures
|
648
654
|
end
|
649
655
|
|
650
|
-
##
|
651
|
-
# Generates a route result for a given set of parameters.
|
652
|
-
# Should only be used by rack-mount.
|
653
|
-
#
|
654
|
-
# @param params [Hash] The set of parameters used to expand the template.
|
655
|
-
# @param recall [Hash] Default parameters used to expand the template.
|
656
|
-
# @param options [Hash] Either a `:processor` or a `:parameterize` block.
|
657
|
-
#
|
658
|
-
# @api private
|
659
|
-
def generate(params={}, recall={}, options={})
|
660
|
-
merged = recall.merge(params)
|
661
|
-
if options[:processor]
|
662
|
-
processor = options[:processor]
|
663
|
-
elsif options[:parameterize]
|
664
|
-
# TODO: This is sending me into fits trying to shoe-horn this into
|
665
|
-
# the existing API. I think I've got this backwards and processors
|
666
|
-
# should be a set of 4 optional blocks named :validate, :transform,
|
667
|
-
# :match, and :restore. Having to use a singleton here is a huge
|
668
|
-
# code smell.
|
669
|
-
processor = Object.new
|
670
|
-
class <<processor
|
671
|
-
attr_accessor :block
|
672
|
-
def transform(name, value)
|
673
|
-
block.call(name, value)
|
674
|
-
end
|
675
|
-
end
|
676
|
-
processor.block = options[:parameterize]
|
677
|
-
else
|
678
|
-
processor = nil
|
679
|
-
end
|
680
|
-
result = self.expand(merged, processor)
|
681
|
-
result.to_s if result
|
682
|
-
end
|
683
|
-
|
684
656
|
private
|
685
657
|
def ordered_variable_defaults
|
686
658
|
@ordered_variable_defaults ||= begin
|
@@ -704,6 +676,8 @@ module Addressable
|
|
704
676
|
# The expression to expand
|
705
677
|
# @param [#validate, #transform] processor
|
706
678
|
# An optional processor object may be supplied.
|
679
|
+
# @param [Boolean] normalize_values
|
680
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
707
681
|
#
|
708
682
|
# The object should respond to either the <tt>validate</tt> or
|
709
683
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
@@ -718,56 +692,36 @@ module Addressable
|
|
718
692
|
# after sending the value to the transform method.
|
719
693
|
#
|
720
694
|
# @return [String] The expanded expression
|
721
|
-
def transform_partial_capture(mapping, capture, processor = nil
|
695
|
+
def transform_partial_capture(mapping, capture, processor = nil,
|
696
|
+
normalize_values = true)
|
722
697
|
_, operator, varlist = *capture.match(EXPRESSION)
|
723
698
|
|
724
|
-
vars = varlist.split(
|
699
|
+
vars = varlist.split(",")
|
725
700
|
|
726
|
-
if
|
701
|
+
if operator == "?"
|
727
702
|
# partial expansion of form style query variables sometimes requires a
|
728
703
|
# slight reordering of the variables to produce a valid url.
|
729
704
|
first_to_expand = vars.find { |varspec|
|
730
705
|
_, name, _ = *varspec.match(VARSPEC)
|
731
|
-
mapping.key? name
|
706
|
+
mapping.key?(name) && !mapping[name].nil?
|
732
707
|
}
|
733
708
|
|
734
709
|
vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand
|
735
710
|
end
|
736
711
|
|
737
|
-
vars
|
738
|
-
|
739
|
-
.reduce("") do |acc, (varspec, op)|
|
712
|
+
vars.
|
713
|
+
inject("".dup) do |acc, varspec|
|
740
714
|
_, name, _ = *varspec.match(VARSPEC)
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
# Creates a lazy Enumerator of the operators that should be used to expand
|
752
|
-
# variables in a varlist starting with `operator`. For example, an operator
|
753
|
-
# `"?"` results in the sequence `"?","&","&"...`
|
754
|
-
#
|
755
|
-
# @param [String] operator from which to generate a sequence
|
756
|
-
#
|
757
|
-
# @return [Enumerator] sequence of operators
|
758
|
-
def operator_sequence(operator)
|
759
|
-
rest_operator = if "?" == operator
|
760
|
-
"&"
|
761
|
-
else
|
762
|
-
operator
|
763
|
-
end
|
764
|
-
head_operator = operator
|
765
|
-
|
766
|
-
Enumerator.new do |y|
|
767
|
-
y << head_operator.to_s
|
768
|
-
while true
|
769
|
-
y << rest_operator.to_s
|
770
|
-
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
|
771
725
|
end
|
772
726
|
end
|
773
727
|
|
@@ -780,6 +734,9 @@ module Addressable
|
|
780
734
|
# The expression to replace
|
781
735
|
# @param [#validate, #transform] processor
|
782
736
|
# An optional processor object may be supplied.
|
737
|
+
# @param [Boolean] normalize_values
|
738
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
739
|
+
#
|
783
740
|
#
|
784
741
|
# The object should respond to either the <tt>validate</tt> or
|
785
742
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
@@ -794,7 +751,8 @@ module Addressable
|
|
794
751
|
# after sending the value to the transform method.
|
795
752
|
#
|
796
753
|
# @return [String] The expanded expression
|
797
|
-
def transform_capture(mapping, capture, processor=nil
|
754
|
+
def transform_capture(mapping, capture, processor=nil,
|
755
|
+
normalize_values=true)
|
798
756
|
_, operator, varlist = *capture.match(EXPRESSION)
|
799
757
|
return_value = varlist.split(',').inject([]) do |acc, varspec|
|
800
758
|
_, name, modifier = *varspec.match(VARSPEC)
|
@@ -814,7 +772,7 @@ module Addressable
|
|
814
772
|
"Can't convert #{value.class} into String or Array."
|
815
773
|
end
|
816
774
|
|
817
|
-
value = normalize_value(value)
|
775
|
+
value = normalize_value(value) if normalize_values
|
818
776
|
|
819
777
|
if processor == nil || !processor.respond_to?(:transform)
|
820
778
|
# Handle percent escaping
|
@@ -877,7 +835,9 @@ module Addressable
|
|
877
835
|
end
|
878
836
|
if processor.respond_to?(:transform)
|
879
837
|
transformed_value = processor.transform(name, value)
|
880
|
-
|
838
|
+
if normalize_values
|
839
|
+
transformed_value = normalize_value(transformed_value)
|
840
|
+
end
|
881
841
|
end
|
882
842
|
end
|
883
843
|
acc << [name, transformed_value]
|
@@ -979,15 +939,35 @@ module Addressable
|
|
979
939
|
end
|
980
940
|
end
|
981
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
|
+
|
982
961
|
##
|
983
962
|
# Generates the <tt>Regexp</tt> that parses a template pattern.
|
984
963
|
#
|
985
964
|
# @param [String] pattern The URI template pattern.
|
986
965
|
# @param [#match] processor The template processor to use.
|
987
966
|
#
|
988
|
-
# @return [Regexp]
|
989
|
-
#
|
990
|
-
|
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)
|
991
971
|
# Escape the pattern. The two gsubs restore the escaped curly braces
|
992
972
|
# back to their original form. Basically, escape everything that isn't
|
993
973
|
# within an expansion.
|
data/lib/addressable/uri.rb
CHANGED
@@ -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.
|
136
|
+
host = authority.sub(
|
126
137
|
/^([^\[\]]*)@/, EMPTY_STR
|
127
|
-
).
|
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.
|
187
|
-
when /^https
|
188
|
-
uri.
|
189
|
-
when /^feed:\/+http
|
190
|
-
uri.
|
191
|
-
when /^feed
|
192
|
-
uri.
|
193
|
-
when
|
194
|
-
uri.
|
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.
|
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.
|
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
|
-
|
213
|
-
|
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.
|
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.
|
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.
|
291
|
+
uri.path.sub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
|
273
292
|
"/#{$1.downcase}:/"
|
274
293
|
end
|
275
|
-
uri.path.
|
294
|
+
uri.path.tr!("\\", SLASH)
|
276
295
|
if File.exist?(uri.path) &&
|
277
296
|
File.stat(uri.path).directory?
|
278
|
-
uri.path.
|
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
|
-
|
420
|
+
SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[sequence]
|
387
421
|
end
|
388
422
|
if upcase_encoded.length > 0
|
389
|
-
|
390
|
-
char
|
391
|
-
end
|
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 :
|
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
|
-
|
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
|
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 =
|
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.
|
@@ -847,11 +891,11 @@ module Addressable
|
|
847
891
|
return nil unless self.scheme
|
848
892
|
@normalized_scheme ||= begin
|
849
893
|
if self.scheme =~ /^\s*ssh\+svn\s*$/i
|
850
|
-
"svn+ssh"
|
894
|
+
"svn+ssh".dup
|
851
895
|
else
|
852
896
|
Addressable::URI.normalize_component(
|
853
897
|
self.scheme.strip.downcase,
|
854
|
-
Addressable::URI::
|
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::
|
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::
|
1010
|
+
Addressable::URI::NormalizeCharacterClasses::UNRESERVED
|
967
1011
|
)
|
968
1012
|
end
|
969
1013
|
end
|
@@ -1032,9 +1076,9 @@ module Addressable
|
|
1032
1076
|
if !current_user && !current_password
|
1033
1077
|
nil
|
1034
1078
|
elsif current_user && current_password
|
1035
|
-
"#{current_user}:#{current_password}"
|
1079
|
+
"#{current_user}:#{current_password}".dup
|
1036
1080
|
elsif current_user && !current_password
|
1037
|
-
"#{current_user}"
|
1081
|
+
"#{current_user}".dup
|
1038
1082
|
end
|
1039
1083
|
end
|
1040
1084
|
# All normalized values should be UTF-8
|
@@ -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
|
-
|
1146
|
+
NormalizeCharacterClasses::HOST
|
1147
|
+
)
|
1102
1148
|
result
|
1103
1149
|
else
|
1104
|
-
EMPTY_STR
|
1150
|
+
EMPTY_STR.dup
|
1105
1151
|
end
|
1106
1152
|
end
|
1107
1153
|
# All normalized values should be UTF-8
|
1108
|
-
@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.
|
1295
|
+
new_host = new_authority.sub(
|
1239
1296
|
/^([^\[\]]*)@/, EMPTY_STR
|
1240
|
-
).
|
1297
|
+
).sub(
|
1241
1298
|
/:([^:@\[\]]*?)$/, EMPTY_STR
|
1242
1299
|
)
|
1243
1300
|
new_port =
|
@@ -1421,7 +1478,7 @@ module Addressable
|
|
1421
1478
|
# @return [String] The components that identify a site.
|
1422
1479
|
def site
|
1423
1480
|
(self.scheme || self.authority) && @site ||= begin
|
1424
|
-
site_string = ""
|
1481
|
+
site_string = "".dup
|
1425
1482
|
site_string << "#{self.scheme}:" if self.scheme != nil
|
1426
1483
|
site_string << "//#{self.authority}" if self.authority != nil
|
1427
1484
|
site_string
|
@@ -1440,7 +1497,7 @@ module Addressable
|
|
1440
1497
|
def normalized_site
|
1441
1498
|
return nil unless self.site
|
1442
1499
|
@normalized_site ||= begin
|
1443
|
-
site_string = ""
|
1500
|
+
site_string = "".dup
|
1444
1501
|
if self.normalized_scheme != nil
|
1445
1502
|
site_string << "#{self.normalized_scheme}:"
|
1446
1503
|
end
|
@@ -1501,14 +1558,14 @@ 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::
|
1561
|
+
Addressable::URI::NormalizeCharacterClasses::PCHAR
|
1505
1562
|
)
|
1506
1563
|
end.join(SLASH)
|
1507
1564
|
|
1508
1565
|
result = URI.normalize_path(result)
|
1509
1566
|
if result.empty? &&
|
1510
1567
|
["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
|
1511
|
-
result = SLASH
|
1568
|
+
result = SLASH.dup
|
1512
1569
|
end
|
1513
1570
|
result
|
1514
1571
|
end
|
@@ -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).
|
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 = (
|
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(
|
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
|
-
|
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]
|
@@ -1694,7 +1758,7 @@ module Addressable
|
|
1694
1758
|
end
|
1695
1759
|
|
1696
1760
|
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
|
1697
|
-
buffer = ""
|
1761
|
+
buffer = "".dup
|
1698
1762
|
new_query_values.each do |key, value|
|
1699
1763
|
encoded_key = URI.encode_component(
|
1700
1764
|
key, CharacterClasses::UNRESERVED
|
@@ -1724,7 +1788,7 @@ module Addressable
|
|
1724
1788
|
#
|
1725
1789
|
# @return [String] The request URI required for an HTTP request.
|
1726
1790
|
def request_uri
|
1727
|
-
return nil if self.absolute? && self.scheme !~ /^https?$/
|
1791
|
+
return nil if self.absolute? && self.scheme !~ /^https?$/i
|
1728
1792
|
return (
|
1729
1793
|
(!self.path.empty? ? self.path : SLASH) +
|
1730
1794
|
(self.query ? "?#{self.query}" : EMPTY_STR)
|
@@ -1739,12 +1803,12 @@ module Addressable
|
|
1739
1803
|
if !new_request_uri.respond_to?(:to_str)
|
1740
1804
|
raise TypeError, "Can't convert #{new_request_uri.class} into String."
|
1741
1805
|
end
|
1742
|
-
if self.absolute? && self.scheme !~ /^https?$/
|
1806
|
+
if self.absolute? && self.scheme !~ /^https?$/i
|
1743
1807
|
raise InvalidURIError,
|
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[/^([^\?]*)
|
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::
|
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.
|
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
|
2353
|
-
raise LocalJumpError, "No block given." unless
|
2416
|
+
def defer_validation
|
2417
|
+
raise LocalJumpError, "No block given." unless block_given?
|
2354
2418
|
@validation_deferred = true
|
2355
|
-
|
2419
|
+
yield
|
2356
2420
|
@validation_deferred = false
|
2357
2421
|
validate
|
2358
2422
|
return nil
|