addressable 2.3.2 → 2.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +189 -28
- data/Gemfile +22 -8
- data/README.md +95 -61
- data/Rakefile +16 -16
- data/addressable.gemspec +28 -0
- data/lib/addressable/idna/native.rb +31 -8
- data/lib/addressable/idna/pure.rb +49 -202
- data/lib/addressable/idna.rb +3 -2
- data/lib/addressable/template.rb +255 -64
- data/lib/addressable/uri.rb +663 -306
- data/lib/addressable/version.rb +5 -4
- data/lib/addressable.rb +4 -0
- data/spec/addressable/idna_spec.rb +116 -45
- data/spec/addressable/net_http_compat_spec.rb +6 -3
- data/spec/addressable/security_spec.rb +58 -0
- data/spec/addressable/template_spec.rb +645 -382
- data/spec/addressable/uri_spec.rb +3014 -1238
- data/spec/spec_helper.rb +33 -0
- data/tasks/clobber.rake +2 -0
- data/tasks/gem.rake +27 -17
- data/tasks/git.rake +3 -1
- data/tasks/metrics.rake +2 -0
- data/tasks/profile.rake +72 -0
- data/tasks/rspec.rake +10 -45
- data/tasks/yard.rake +2 -0
- metadata +75 -73
- data/tasks/rubyforge.rake +0 -89
- data/website/index.html +0 -110
data/lib/addressable/template.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
#--
|
|
3
|
-
# Copyright (C)
|
|
4
|
+
# Copyright (C) Bob Aman
|
|
4
5
|
#
|
|
5
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -35,7 +36,7 @@ module Addressable
|
|
|
35
36
|
Addressable::URI::CharacterClasses::DIGIT + '_'
|
|
36
37
|
|
|
37
38
|
var_char =
|
|
38
|
-
"(
|
|
39
|
+
"(?>(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
|
|
39
40
|
RESERVED =
|
|
40
41
|
"(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
|
|
41
42
|
UNRESERVED =
|
|
@@ -132,6 +133,7 @@ module Addressable
|
|
|
132
133
|
self.template.variables
|
|
133
134
|
end
|
|
134
135
|
alias_method :keys, :variables
|
|
136
|
+
alias_method :names, :variables
|
|
135
137
|
|
|
136
138
|
##
|
|
137
139
|
# @return [Array]
|
|
@@ -146,6 +148,64 @@ module Addressable
|
|
|
146
148
|
end
|
|
147
149
|
alias_method :captures, :values
|
|
148
150
|
|
|
151
|
+
##
|
|
152
|
+
# Accesses captured values by name or by index.
|
|
153
|
+
#
|
|
154
|
+
# @param [String, Symbol, Fixnum] key
|
|
155
|
+
# Capture index or name. Note that when accessing by with index
|
|
156
|
+
# of 0, the full URI will be returned. The intention is to mimic
|
|
157
|
+
# the ::MatchData#[] behavior.
|
|
158
|
+
#
|
|
159
|
+
# @param [#to_int, nil] len
|
|
160
|
+
# If provided, an array of values will be returned with the given
|
|
161
|
+
# parameter used as length.
|
|
162
|
+
#
|
|
163
|
+
# @return [Array, String, nil]
|
|
164
|
+
# The captured value corresponding to the index or name. If the
|
|
165
|
+
# value was not provided or the key is unknown, nil will be
|
|
166
|
+
# returned.
|
|
167
|
+
#
|
|
168
|
+
# If the second parameter is provided, an array of that length will
|
|
169
|
+
# be returned instead.
|
|
170
|
+
def [](key, len = nil)
|
|
171
|
+
if len
|
|
172
|
+
to_a[key, len]
|
|
173
|
+
elsif String === key or Symbol === key
|
|
174
|
+
mapping[key.to_s]
|
|
175
|
+
else
|
|
176
|
+
to_a[key]
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
##
|
|
181
|
+
# @return [Array]
|
|
182
|
+
# Array with the matched URI as first element followed by the captured
|
|
183
|
+
# values.
|
|
184
|
+
def to_a
|
|
185
|
+
[to_s, *values]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
##
|
|
189
|
+
# @return [String]
|
|
190
|
+
# The matched URI as String.
|
|
191
|
+
def to_s
|
|
192
|
+
uri.to_s
|
|
193
|
+
end
|
|
194
|
+
alias_method :string, :to_s
|
|
195
|
+
|
|
196
|
+
# Returns multiple captured values at once.
|
|
197
|
+
#
|
|
198
|
+
# @param [String, Symbol, Fixnum] *indexes
|
|
199
|
+
# Indices of the captures to be returned
|
|
200
|
+
#
|
|
201
|
+
# @return [Array]
|
|
202
|
+
# Values corresponding to given indices.
|
|
203
|
+
#
|
|
204
|
+
# @see Addressable::Template::MatchData#[]
|
|
205
|
+
def values_at(*indexes)
|
|
206
|
+
indexes.map { |i| self[i] }
|
|
207
|
+
end
|
|
208
|
+
|
|
149
209
|
##
|
|
150
210
|
# Returns a <tt>String</tt> representation of the MatchData's state.
|
|
151
211
|
#
|
|
@@ -154,6 +214,15 @@ module Addressable
|
|
|
154
214
|
sprintf("#<%s:%#0x RESULT:%s>",
|
|
155
215
|
self.class.to_s, self.object_id, self.mapping.inspect)
|
|
156
216
|
end
|
|
217
|
+
|
|
218
|
+
##
|
|
219
|
+
# Dummy method for code expecting a ::MatchData instance
|
|
220
|
+
#
|
|
221
|
+
# @return [String] An empty string.
|
|
222
|
+
def pre_match
|
|
223
|
+
""
|
|
224
|
+
end
|
|
225
|
+
alias_method :post_match, :pre_match
|
|
157
226
|
end
|
|
158
227
|
|
|
159
228
|
##
|
|
@@ -166,7 +235,18 @@ module Addressable
|
|
|
166
235
|
if !pattern.respond_to?(:to_str)
|
|
167
236
|
raise TypeError, "Can't convert #{pattern.class} into String."
|
|
168
237
|
end
|
|
169
|
-
@pattern = pattern.to_str.freeze
|
|
238
|
+
@pattern = pattern.to_str.dup.freeze
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
##
|
|
242
|
+
# Freeze URI, initializing instance variables.
|
|
243
|
+
#
|
|
244
|
+
# @return [Addressable::URI] The frozen URI object.
|
|
245
|
+
def freeze
|
|
246
|
+
self.variables
|
|
247
|
+
self.variable_defaults
|
|
248
|
+
self.named_captures
|
|
249
|
+
super
|
|
170
250
|
end
|
|
171
251
|
|
|
172
252
|
##
|
|
@@ -182,6 +262,26 @@ module Addressable
|
|
|
182
262
|
self.class.to_s, self.object_id, self.pattern)
|
|
183
263
|
end
|
|
184
264
|
|
|
265
|
+
##
|
|
266
|
+
# Returns <code>true</code> if the Template objects are equal. This method
|
|
267
|
+
# does NOT normalize either Template before doing the comparison.
|
|
268
|
+
#
|
|
269
|
+
# @param [Object] template The Template to compare.
|
|
270
|
+
#
|
|
271
|
+
# @return [TrueClass, FalseClass]
|
|
272
|
+
# <code>true</code> if the Templates are equivalent, <code>false</code>
|
|
273
|
+
# otherwise.
|
|
274
|
+
def ==(template)
|
|
275
|
+
return false unless template.kind_of?(Template)
|
|
276
|
+
return self.pattern == template.pattern
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
##
|
|
280
|
+
# Addressable::Template makes no distinction between `==` and `eql?`.
|
|
281
|
+
#
|
|
282
|
+
# @see #==
|
|
283
|
+
alias_method :eql?, :==
|
|
284
|
+
|
|
185
285
|
##
|
|
186
286
|
# Extracts a mapping from the URI using a URI Template pattern.
|
|
187
287
|
#
|
|
@@ -311,12 +411,14 @@ module Addressable
|
|
|
311
411
|
# match.captures
|
|
312
412
|
# #=> ["a", ["b", "c"]]
|
|
313
413
|
def match(uri, processor=nil)
|
|
314
|
-
uri = Addressable::URI.parse(uri)
|
|
414
|
+
uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
|
|
315
415
|
mapping = {}
|
|
316
416
|
|
|
317
417
|
# First, we need to process the pattern, and extract the values.
|
|
318
418
|
expansions, expansion_regexp =
|
|
319
419
|
parse_template_pattern(pattern, processor)
|
|
420
|
+
|
|
421
|
+
return nil unless uri.to_str.match(expansion_regexp)
|
|
320
422
|
unparsed_values = uri.to_str.scan(expansion_regexp).flatten
|
|
321
423
|
|
|
322
424
|
if uri.to_str == pattern
|
|
@@ -327,6 +429,7 @@ module Addressable
|
|
|
327
429
|
_, operator, varlist = *expansion.match(EXPRESSION)
|
|
328
430
|
varlist.split(',').each do |varspec|
|
|
329
431
|
_, name, modifier = *varspec.match(VARSPEC)
|
|
432
|
+
mapping[name] ||= nil
|
|
330
433
|
case operator
|
|
331
434
|
when nil, '+', '#', '/', '.'
|
|
332
435
|
unparsed_value = unparsed_values[index]
|
|
@@ -335,12 +438,14 @@ module Addressable
|
|
|
335
438
|
value = value.split(JOINERS[operator]) if value && modifier == '*'
|
|
336
439
|
when ';', '?', '&'
|
|
337
440
|
if modifier == '*'
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
441
|
+
if unparsed_values[index]
|
|
442
|
+
value = unparsed_values[index].split(JOINERS[operator])
|
|
443
|
+
value = value.inject({}) do |acc, v|
|
|
444
|
+
key, val = v.split('=')
|
|
445
|
+
val = "" if val.nil?
|
|
446
|
+
acc[key] = val
|
|
447
|
+
acc
|
|
448
|
+
end
|
|
344
449
|
end
|
|
345
450
|
else
|
|
346
451
|
if (unparsed_values[index])
|
|
@@ -365,10 +470,9 @@ module Addressable
|
|
|
365
470
|
value = Addressable::URI.unencode_component(value)
|
|
366
471
|
end
|
|
367
472
|
end
|
|
368
|
-
if mapping
|
|
473
|
+
if !mapping.has_key?(name) || mapping[name].nil?
|
|
474
|
+
# Doesn't exist, set to value (even if value is nil)
|
|
369
475
|
mapping[name] = value
|
|
370
|
-
else
|
|
371
|
-
return nil
|
|
372
476
|
end
|
|
373
477
|
index = index + 1
|
|
374
478
|
end
|
|
@@ -385,6 +489,8 @@ module Addressable
|
|
|
385
489
|
# @param [Hash] mapping The mapping that corresponds to the pattern.
|
|
386
490
|
# @param [#validate, #transform] processor
|
|
387
491
|
# An optional processor object may be supplied.
|
|
492
|
+
# @param [Boolean] normalize_values
|
|
493
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
|
388
494
|
#
|
|
389
495
|
# The object should respond to either the <tt>validate</tt> or
|
|
390
496
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
|
@@ -415,10 +521,11 @@ module Addressable
|
|
|
415
521
|
# "http://example.com/{?one,two,three}/"
|
|
416
522
|
# ).partial_expand({"one" => "1", "three" => 3}).pattern
|
|
417
523
|
# #=> "http://example.com/?one=1{&two}&three=3"
|
|
418
|
-
def partial_expand(mapping, processor=nil)
|
|
524
|
+
def partial_expand(mapping, processor=nil, normalize_values=true)
|
|
419
525
|
result = self.pattern.dup
|
|
526
|
+
mapping = normalize_keys(mapping)
|
|
420
527
|
result.gsub!( EXPRESSION ) do |capture|
|
|
421
|
-
transform_partial_capture(mapping, capture, processor)
|
|
528
|
+
transform_partial_capture(mapping, capture, processor, normalize_values)
|
|
422
529
|
end
|
|
423
530
|
return Addressable::Template.new(result)
|
|
424
531
|
end
|
|
@@ -429,6 +536,8 @@ module Addressable
|
|
|
429
536
|
# @param [Hash] mapping The mapping that corresponds to the pattern.
|
|
430
537
|
# @param [#validate, #transform] processor
|
|
431
538
|
# An optional processor object may be supplied.
|
|
539
|
+
# @param [Boolean] normalize_values
|
|
540
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
|
432
541
|
#
|
|
433
542
|
# The object should respond to either the <tt>validate</tt> or
|
|
434
543
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
|
@@ -479,11 +588,11 @@ module Addressable
|
|
|
479
588
|
# ExampleProcessor
|
|
480
589
|
# ).to_str
|
|
481
590
|
# #=> Addressable::Template::InvalidTemplateValueError
|
|
482
|
-
def expand(mapping, processor=nil)
|
|
591
|
+
def expand(mapping, processor=nil, normalize_values=true)
|
|
483
592
|
result = self.pattern.dup
|
|
484
593
|
mapping = normalize_keys(mapping)
|
|
485
594
|
result.gsub!( EXPRESSION ) do |capture|
|
|
486
|
-
transform_capture(mapping, capture, processor)
|
|
595
|
+
transform_capture(mapping, capture, processor, normalize_values)
|
|
487
596
|
end
|
|
488
597
|
return Addressable::URI.parse(result)
|
|
489
598
|
end
|
|
@@ -499,6 +608,7 @@ module Addressable
|
|
|
499
608
|
@variables ||= ordered_variable_defaults.map { |var, val| var }.uniq
|
|
500
609
|
end
|
|
501
610
|
alias_method :keys, :variables
|
|
611
|
+
alias_method :names, :variables
|
|
502
612
|
|
|
503
613
|
##
|
|
504
614
|
# Returns a mapping of variables to their default values specified
|
|
@@ -510,17 +620,49 @@ module Addressable
|
|
|
510
620
|
Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten]
|
|
511
621
|
end
|
|
512
622
|
|
|
623
|
+
##
|
|
624
|
+
# Coerces a template into a `Regexp` object. This regular expression will
|
|
625
|
+
# behave very similarly to the actual template, and should match the same
|
|
626
|
+
# URI values, but it cannot fully handle, for example, values that would
|
|
627
|
+
# extract to an `Array`.
|
|
628
|
+
#
|
|
629
|
+
# @return [Regexp] A regular expression which should match the template.
|
|
630
|
+
def to_regexp
|
|
631
|
+
_, source = parse_template_pattern(pattern)
|
|
632
|
+
Regexp.new(source)
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
##
|
|
636
|
+
# Returns the source of the coerced `Regexp`.
|
|
637
|
+
#
|
|
638
|
+
# @return [String] The source of the `Regexp` given by {#to_regexp}.
|
|
639
|
+
#
|
|
640
|
+
# @api private
|
|
641
|
+
def source
|
|
642
|
+
self.to_regexp.source
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
##
|
|
646
|
+
# Returns the named captures of the coerced `Regexp`.
|
|
647
|
+
#
|
|
648
|
+
# @return [Hash] The named captures of the `Regexp` given by {#to_regexp}.
|
|
649
|
+
#
|
|
650
|
+
# @api private
|
|
651
|
+
def named_captures
|
|
652
|
+
self.to_regexp.named_captures
|
|
653
|
+
end
|
|
654
|
+
|
|
513
655
|
private
|
|
514
656
|
def ordered_variable_defaults
|
|
515
|
-
@ordered_variable_defaults ||=
|
|
516
|
-
expansions,
|
|
517
|
-
expansions.
|
|
518
|
-
_,
|
|
657
|
+
@ordered_variable_defaults ||= begin
|
|
658
|
+
expansions, _ = parse_template_pattern(pattern)
|
|
659
|
+
expansions.flat_map do |capture|
|
|
660
|
+
_, _, varlist = *capture.match(EXPRESSION)
|
|
519
661
|
varlist.split(',').map do |varspec|
|
|
520
|
-
|
|
662
|
+
varspec[VARSPEC, 1]
|
|
521
663
|
end
|
|
522
|
-
end
|
|
523
|
-
|
|
664
|
+
end
|
|
665
|
+
end
|
|
524
666
|
end
|
|
525
667
|
|
|
526
668
|
|
|
@@ -533,6 +675,8 @@ module Addressable
|
|
|
533
675
|
# The expression to expand
|
|
534
676
|
# @param [#validate, #transform] processor
|
|
535
677
|
# An optional processor object may be supplied.
|
|
678
|
+
# @param [Boolean] normalize_values
|
|
679
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
|
536
680
|
#
|
|
537
681
|
# The object should respond to either the <tt>validate</tt> or
|
|
538
682
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
|
@@ -547,21 +691,36 @@ module Addressable
|
|
|
547
691
|
# after sending the value to the transform method.
|
|
548
692
|
#
|
|
549
693
|
# @return [String] The expanded expression
|
|
550
|
-
def transform_partial_capture(mapping, capture, processor = nil
|
|
694
|
+
def transform_partial_capture(mapping, capture, processor = nil,
|
|
695
|
+
normalize_values = true)
|
|
551
696
|
_, operator, varlist = *capture.match(EXPRESSION)
|
|
552
|
-
|
|
553
|
-
varlist.split(
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
697
|
+
|
|
698
|
+
vars = varlist.split(",")
|
|
699
|
+
|
|
700
|
+
if operator == "?"
|
|
701
|
+
# partial expansion of form style query variables sometimes requires a
|
|
702
|
+
# slight reordering of the variables to produce a valid url.
|
|
703
|
+
first_to_expand = vars.find { |varspec|
|
|
704
|
+
_, name, _ = *varspec.match(VARSPEC)
|
|
705
|
+
mapping.key?(name) && !mapping[name].nil?
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
vars.
|
|
712
|
+
inject("".dup) do |acc, varspec|
|
|
713
|
+
_, name, _ = *varspec.match(VARSPEC)
|
|
714
|
+
next_val = if mapping.key? name
|
|
715
|
+
transform_capture(mapping, "{#{operator}#{varspec}}",
|
|
716
|
+
processor, normalize_values)
|
|
717
|
+
else
|
|
718
|
+
"{#{operator}#{varspec}}"
|
|
719
|
+
end
|
|
720
|
+
# If we've already expanded at least one '?' operator with non-empty
|
|
721
|
+
# value, change to '&'
|
|
722
|
+
operator = "&" if (operator == "?") && (next_val != "")
|
|
723
|
+
acc << next_val
|
|
565
724
|
end
|
|
566
725
|
end
|
|
567
726
|
|
|
@@ -574,6 +733,9 @@ module Addressable
|
|
|
574
733
|
# The expression to replace
|
|
575
734
|
# @param [#validate, #transform] processor
|
|
576
735
|
# An optional processor object may be supplied.
|
|
736
|
+
# @param [Boolean] normalize_values
|
|
737
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
|
738
|
+
#
|
|
577
739
|
#
|
|
578
740
|
# The object should respond to either the <tt>validate</tt> or
|
|
579
741
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
|
@@ -588,14 +750,19 @@ module Addressable
|
|
|
588
750
|
# after sending the value to the transform method.
|
|
589
751
|
#
|
|
590
752
|
# @return [String] The expanded expression
|
|
591
|
-
def transform_capture(mapping, capture, processor=nil
|
|
753
|
+
def transform_capture(mapping, capture, processor=nil,
|
|
754
|
+
normalize_values=true)
|
|
592
755
|
_, operator, varlist = *capture.match(EXPRESSION)
|
|
593
756
|
return_value = varlist.split(',').inject([]) do |acc, varspec|
|
|
594
757
|
_, name, modifier = *varspec.match(VARSPEC)
|
|
595
758
|
value = mapping[name]
|
|
596
759
|
unless value == nil || value == {}
|
|
597
760
|
allow_reserved = %w(+ #).include?(operator)
|
|
598
|
-
|
|
761
|
+
# Common primitives where the .to_s output is well-defined
|
|
762
|
+
if Numeric === value || Symbol === value ||
|
|
763
|
+
value == true || value == false
|
|
764
|
+
value = value.to_s
|
|
765
|
+
end
|
|
599
766
|
length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/
|
|
600
767
|
|
|
601
768
|
unless (Hash === value) ||
|
|
@@ -604,7 +771,7 @@ module Addressable
|
|
|
604
771
|
"Can't convert #{value.class} into String or Array."
|
|
605
772
|
end
|
|
606
773
|
|
|
607
|
-
value = normalize_value(value)
|
|
774
|
+
value = normalize_value(value) if normalize_values
|
|
608
775
|
|
|
609
776
|
if processor == nil || !processor.respond_to?(:transform)
|
|
610
777
|
# Handle percent escaping
|
|
@@ -667,7 +834,9 @@ module Addressable
|
|
|
667
834
|
end
|
|
668
835
|
if processor.respond_to?(:transform)
|
|
669
836
|
transformed_value = processor.transform(name, value)
|
|
670
|
-
|
|
837
|
+
if normalize_values
|
|
838
|
+
transformed_value = normalize_value(transformed_value)
|
|
839
|
+
end
|
|
671
840
|
end
|
|
672
841
|
end
|
|
673
842
|
acc << [name, transformed_value]
|
|
@@ -698,7 +867,7 @@ module Addressable
|
|
|
698
867
|
if v.is_a?(Array) && v.first =~ /=/
|
|
699
868
|
v.join(joiner)
|
|
700
869
|
elsif v.is_a?(Array)
|
|
701
|
-
v.map{|
|
|
870
|
+
v.map{|inner_value| "#{k}=#{inner_value}"}.join(joiner)
|
|
702
871
|
else
|
|
703
872
|
"#{k}=#{v}"
|
|
704
873
|
end
|
|
@@ -708,7 +877,7 @@ module Addressable
|
|
|
708
877
|
if v.is_a?(Array) && v.first =~ /=/
|
|
709
878
|
';' + v.join(";")
|
|
710
879
|
elsif v.is_a?(Array)
|
|
711
|
-
';' + v.map{|
|
|
880
|
+
';' + v.map{|inner_value| "#{k}=#{inner_value}"}.join(";")
|
|
712
881
|
else
|
|
713
882
|
v && v != '' ? ";#{k}=#{v}" : ";#{k}"
|
|
714
883
|
end
|
|
@@ -723,25 +892,24 @@ module Addressable
|
|
|
723
892
|
# operator.
|
|
724
893
|
#
|
|
725
894
|
# @param [Hash, Array, String] value
|
|
726
|
-
# Normalizes keys and values with
|
|
895
|
+
# Normalizes unicode keys and values with String#unicode_normalize (NFC)
|
|
727
896
|
#
|
|
728
897
|
# @return [Hash, Array, String] The normalized values
|
|
729
898
|
def normalize_value(value)
|
|
730
|
-
unless value.is_a?(Hash)
|
|
731
|
-
value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
|
|
732
|
-
end
|
|
733
|
-
|
|
734
899
|
# Handle unicode normalization
|
|
735
|
-
if value.
|
|
736
|
-
value.map! { |val|
|
|
900
|
+
if value.respond_to?(:to_ary)
|
|
901
|
+
value.to_ary.map! { |val| normalize_value(val) }
|
|
737
902
|
elsif value.kind_of?(Hash)
|
|
738
903
|
value = value.inject({}) { |acc, (k, v)|
|
|
739
|
-
acc[
|
|
740
|
-
Addressable::IDNA.unicode_normalize_kc(v)
|
|
904
|
+
acc[normalize_value(k)] = normalize_value(v)
|
|
741
905
|
acc
|
|
742
906
|
}
|
|
743
907
|
else
|
|
744
|
-
value =
|
|
908
|
+
value = value.to_s if !value.kind_of?(String)
|
|
909
|
+
if value.encoding != Encoding::UTF_8
|
|
910
|
+
value = value.dup.force_encoding(Encoding::UTF_8)
|
|
911
|
+
end
|
|
912
|
+
value = value.unicode_normalize(:nfc)
|
|
745
913
|
end
|
|
746
914
|
value
|
|
747
915
|
end
|
|
@@ -769,15 +937,35 @@ module Addressable
|
|
|
769
937
|
end
|
|
770
938
|
end
|
|
771
939
|
|
|
940
|
+
##
|
|
941
|
+
# Generates the <tt>Regexp</tt> that parses a template pattern. Memoizes the
|
|
942
|
+
# value if template processor not set (processors may not be deterministic)
|
|
943
|
+
#
|
|
944
|
+
# @param [String] pattern The URI template pattern.
|
|
945
|
+
# @param [#match] processor The template processor to use.
|
|
946
|
+
#
|
|
947
|
+
# @return [Array, Regexp]
|
|
948
|
+
# An array of expansion variables nad a regular expression which may be
|
|
949
|
+
# used to parse a template pattern
|
|
950
|
+
def parse_template_pattern(pattern, processor = nil)
|
|
951
|
+
if processor.nil? && pattern == @pattern
|
|
952
|
+
@cached_template_parse ||=
|
|
953
|
+
parse_new_template_pattern(pattern, processor)
|
|
954
|
+
else
|
|
955
|
+
parse_new_template_pattern(pattern, processor)
|
|
956
|
+
end
|
|
957
|
+
end
|
|
958
|
+
|
|
772
959
|
##
|
|
773
960
|
# Generates the <tt>Regexp</tt> that parses a template pattern.
|
|
774
961
|
#
|
|
775
962
|
# @param [String] pattern The URI template pattern.
|
|
776
963
|
# @param [#match] processor The template processor to use.
|
|
777
964
|
#
|
|
778
|
-
# @return [Regexp]
|
|
779
|
-
#
|
|
780
|
-
|
|
965
|
+
# @return [Array, Regexp]
|
|
966
|
+
# An array of expansion variables nad a regular expression which may be
|
|
967
|
+
# used to parse a template pattern
|
|
968
|
+
def parse_new_template_pattern(pattern, processor = nil)
|
|
781
969
|
# Escape the pattern. The two gsubs restore the escaped curly braces
|
|
782
970
|
# back to their original form. Basically, escape everything that isn't
|
|
783
971
|
# within an expansion.
|
|
@@ -797,10 +985,12 @@ module Addressable
|
|
|
797
985
|
_, operator, varlist = *expansion.match(EXPRESSION)
|
|
798
986
|
leader = Regexp.escape(LEADERS.fetch(operator, ''))
|
|
799
987
|
joiner = Regexp.escape(JOINERS.fetch(operator, ','))
|
|
800
|
-
|
|
988
|
+
combined = varlist.split(',').map do |varspec|
|
|
801
989
|
_, name, modifier = *varspec.match(VARSPEC)
|
|
802
|
-
|
|
803
|
-
|
|
990
|
+
|
|
991
|
+
result = processor && processor.respond_to?(:match) ? processor.match(name) : nil
|
|
992
|
+
if result
|
|
993
|
+
"(?<#{name}>#{ result })"
|
|
804
994
|
else
|
|
805
995
|
group = case operator
|
|
806
996
|
when '+'
|
|
@@ -821,16 +1011,17 @@ module Addressable
|
|
|
821
1011
|
"#{ UNRESERVED }*?"
|
|
822
1012
|
end
|
|
823
1013
|
if modifier == '*'
|
|
824
|
-
"(
|
|
1014
|
+
"(?<#{name}>#{group}(?:#{joiner}?#{group})*)?"
|
|
825
1015
|
else
|
|
826
|
-
"(
|
|
1016
|
+
"(?<#{name}>#{group})?"
|
|
827
1017
|
end
|
|
828
1018
|
end
|
|
829
1019
|
end.join("#{joiner}?")
|
|
1020
|
+
"(?:|#{leader}#{combined})"
|
|
830
1021
|
end
|
|
831
1022
|
|
|
832
1023
|
# Ensure that the regular expression matches the whole URI.
|
|
833
|
-
regexp_string = "
|
|
1024
|
+
regexp_string = "\\A#{regexp_string}\\z"
|
|
834
1025
|
return expansions, Regexp.new(regexp_string)
|
|
835
1026
|
end
|
|
836
1027
|
|