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.
@@ -1,6 +1,7 @@
1
- # encoding:utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  #--
3
- # Copyright (C) 2006-2011 Bob Aman
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
- "(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
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
- value = unparsed_values[index].split(JOINERS[operator])
339
- value = value.inject({}) do |acc, v|
340
- key, val = v.split('=')
341
- val = "" if val.nil?
342
- acc[key] = val
343
- acc
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[name] == nil || mapping[name] == value
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, expansion_regexp = parse_template_pattern(pattern)
517
- expansions.map do |capture|
518
- _, operator, varlist = *capture.match(EXPRESSION)
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
- name = varspec[VARSPEC, 1]
662
+ varspec[VARSPEC, 1]
521
663
  end
522
- end.flatten
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
- is_first = true
553
- varlist.split(',').inject('') do |acc, varspec|
554
- _, name, modifier = *varspec.match(VARSPEC)
555
- value = mapping[name]
556
- if value
557
- operator = '&' if !is_first && operator == '?'
558
- acc << transform_capture(mapping, "{#{operator}#{varspec}}", processor)
559
- else
560
- operator = '&' if !is_first && operator == '?'
561
- acc << "{#{operator}#{varspec}}"
562
- end
563
- is_first = false
564
- acc
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
- value = value.to_s if Numeric === value || Symbol === value
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
- transformed_value = normalize_value(transformed_value)
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{|v| "#{k}=#{v}"}.join(joiner)
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{|v| "#{k}=#{v}"}.join(";")
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 IDNA#unicode_normalize_kc
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.kind_of?(Array)
736
- value.map! { |val| Addressable::IDNA.unicode_normalize_kc(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[Addressable::IDNA.unicode_normalize_kc(k)] =
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 = Addressable::IDNA.unicode_normalize_kc(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
- # A regular expression which may be used to parse a template pattern.
780
- def parse_template_pattern(pattern, processor=nil)
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
- leader + varlist.split(',').map do |varspec|
988
+ combined = varlist.split(',').map do |varspec|
801
989
  _, name, modifier = *varspec.match(VARSPEC)
802
- if processor != nil && processor.respond_to?(:match)
803
- "(#{ processor.match(name) })"
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
- "(#{group}(?:#{joiner}?#{group})*)?"
1014
+ "(?<#{name}>#{group}(?:#{joiner}?#{group})*)?"
825
1015
  else
826
- "(#{group})?"
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 = "^#{regexp_string}$"
1024
+ regexp_string = "\\A#{regexp_string}\\z"
834
1025
  return expansions, Regexp.new(regexp_string)
835
1026
  end
836
1027