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