addressable 2.4.0 → 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.
@@ -1,6 +1,7 @@
1
- # encoding:utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  #--
3
- # Copyright (C) 2006-2015 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.
@@ -1,6 +1,7 @@
1
- # encoding:utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  #--
3
- # Copyright (C) 2006-2015 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,9 +36,11 @@ 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])"
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
@@ -156,7 +159,7 @@ module Addressable
156
159
  # the ::MatchData#[] behavior.
157
160
  #
158
161
  # @param [#to_int, nil] len
159
- # If provided, an array of values will be returend with the given
162
+ # If provided, an array of values will be returned with the given
160
163
  # parameter used as length.
161
164
  #
162
165
  # @return [Array, String, nil]
@@ -410,7 +413,7 @@ module Addressable
410
413
  # match.captures
411
414
  # #=> ["a", ["b", "c"]]
412
415
  def match(uri, processor=nil)
413
- uri = Addressable::URI.parse(uri)
416
+ uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
414
417
  mapping = {}
415
418
 
416
419
  # First, we need to process the pattern, and extract the values.
@@ -488,6 +491,8 @@ module Addressable
488
491
  # @param [Hash] mapping The mapping that corresponds to the pattern.
489
492
  # @param [#validate, #transform] processor
490
493
  # An optional processor object may be supplied.
494
+ # @param [Boolean] normalize_values
495
+ # Optional flag to enable/disable unicode normalization. Default: true
491
496
  #
492
497
  # The object should respond to either the <tt>validate</tt> or
493
498
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -518,11 +523,11 @@ module Addressable
518
523
  # "http://example.com/{?one,two,three}/"
519
524
  # ).partial_expand({"one" => "1", "three" => 3}).pattern
520
525
  # #=> "http://example.com/?one=1{&two}&three=3"
521
- def partial_expand(mapping, processor=nil)
526
+ def partial_expand(mapping, processor=nil, normalize_values=true)
522
527
  result = self.pattern.dup
523
528
  mapping = normalize_keys(mapping)
524
529
  result.gsub!( EXPRESSION ) do |capture|
525
- transform_partial_capture(mapping, capture, processor)
530
+ transform_partial_capture(mapping, capture, processor, normalize_values)
526
531
  end
527
532
  return Addressable::Template.new(result)
528
533
  end
@@ -533,6 +538,8 @@ module Addressable
533
538
  # @param [Hash] mapping The mapping that corresponds to the pattern.
534
539
  # @param [#validate, #transform] processor
535
540
  # An optional processor object may be supplied.
541
+ # @param [Boolean] normalize_values
542
+ # Optional flag to enable/disable unicode normalization. Default: true
536
543
  #
537
544
  # The object should respond to either the <tt>validate</tt> or
538
545
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -583,11 +590,11 @@ module Addressable
583
590
  # ExampleProcessor
584
591
  # ).to_str
585
592
  # #=> Addressable::Template::InvalidTemplateValueError
586
- def expand(mapping, processor=nil)
593
+ def expand(mapping, processor=nil, normalize_values=true)
587
594
  result = self.pattern.dup
588
595
  mapping = normalize_keys(mapping)
589
596
  result.gsub!( EXPRESSION ) do |capture|
590
- transform_capture(mapping, capture, processor)
597
+ transform_capture(mapping, capture, processor, normalize_values)
591
598
  end
592
599
  return Addressable::URI.parse(result)
593
600
  end
@@ -647,50 +654,16 @@ module Addressable
647
654
  self.to_regexp.named_captures
648
655
  end
649
656
 
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
657
  private
685
658
  def ordered_variable_defaults
686
659
  @ordered_variable_defaults ||= begin
687
660
  expansions, _ = parse_template_pattern(pattern)
688
- expansions.map do |capture|
661
+ expansions.flat_map do |capture|
689
662
  _, _, varlist = *capture.match(EXPRESSION)
690
663
  varlist.split(',').map do |varspec|
691
664
  varspec[VARSPEC, 1]
692
665
  end
693
- end.flatten
666
+ end
694
667
  end
695
668
  end
696
669
 
@@ -704,6 +677,8 @@ module Addressable
704
677
  # The expression to expand
705
678
  # @param [#validate, #transform] processor
706
679
  # An optional processor object may be supplied.
680
+ # @param [Boolean] normalize_values
681
+ # Optional flag to enable/disable unicode normalization. Default: true
707
682
  #
708
683
  # The object should respond to either the <tt>validate</tt> or
709
684
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -718,56 +693,36 @@ module Addressable
718
693
  # after sending the value to the transform method.
719
694
  #
720
695
  # @return [String] The expanded expression
721
- def transform_partial_capture(mapping, capture, processor = nil)
696
+ def transform_partial_capture(mapping, capture, processor = nil,
697
+ normalize_values = true)
722
698
  _, operator, varlist = *capture.match(EXPRESSION)
723
699
 
724
- vars = varlist.split(',')
700
+ vars = varlist.split(",")
725
701
 
726
- if '?' == operator
702
+ if operator == "?"
727
703
  # partial expansion of form style query variables sometimes requires a
728
704
  # slight reordering of the variables to produce a valid url.
729
705
  first_to_expand = vars.find { |varspec|
730
706
  _, name, _ = *varspec.match(VARSPEC)
731
- mapping.key? name
707
+ mapping.key?(name) && !mapping[name].nil?
732
708
  }
733
709
 
734
710
  vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand
735
711
  end
736
712
 
737
- vars
738
- .zip(operator_sequence(operator).take(vars.length))
739
- .reduce("") do |acc, (varspec, op)|
713
+ vars.
714
+ inject("".dup) do |acc, varspec|
740
715
  _, name, _ = *varspec.match(VARSPEC)
741
-
742
- acc << if mapping.key? name
743
- transform_capture(mapping, "{#{op}#{varspec}}", processor)
744
- else
745
- "{#{op}#{varspec}}"
746
- end
747
- end
748
- end
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
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
771
726
  end
772
727
  end
773
728
 
@@ -780,6 +735,9 @@ module Addressable
780
735
  # The expression to replace
781
736
  # @param [#validate, #transform] processor
782
737
  # An optional processor object may be supplied.
738
+ # @param [Boolean] normalize_values
739
+ # Optional flag to enable/disable unicode normalization. Default: true
740
+ #
783
741
  #
784
742
  # The object should respond to either the <tt>validate</tt> or
785
743
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -794,7 +752,8 @@ module Addressable
794
752
  # after sending the value to the transform method.
795
753
  #
796
754
  # @return [String] The expanded expression
797
- def transform_capture(mapping, capture, processor=nil)
755
+ def transform_capture(mapping, capture, processor=nil,
756
+ normalize_values=true)
798
757
  _, operator, varlist = *capture.match(EXPRESSION)
799
758
  return_value = varlist.split(',').inject([]) do |acc, varspec|
800
759
  _, name, modifier = *varspec.match(VARSPEC)
@@ -814,7 +773,7 @@ module Addressable
814
773
  "Can't convert #{value.class} into String or Array."
815
774
  end
816
775
 
817
- value = normalize_value(value)
776
+ value = normalize_value(value) if normalize_values
818
777
 
819
778
  if processor == nil || !processor.respond_to?(:transform)
820
779
  # Handle percent escaping
@@ -877,7 +836,9 @@ module Addressable
877
836
  end
878
837
  if processor.respond_to?(:transform)
879
838
  transformed_value = processor.transform(name, value)
880
- transformed_value = normalize_value(transformed_value)
839
+ if normalize_values
840
+ transformed_value = normalize_value(transformed_value)
841
+ end
881
842
  end
882
843
  end
883
844
  acc << [name, transformed_value]
@@ -933,25 +894,24 @@ module Addressable
933
894
  # operator.
934
895
  #
935
896
  # @param [Hash, Array, String] value
936
- # Normalizes keys and values with IDNA#unicode_normalize_kc
897
+ # Normalizes unicode keys and values with String#unicode_normalize (NFC)
937
898
  #
938
899
  # @return [Hash, Array, String] The normalized values
939
900
  def normalize_value(value)
940
- unless value.is_a?(Hash)
941
- value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
942
- end
943
-
944
901
  # Handle unicode normalization
945
- if value.kind_of?(Array)
946
- value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
902
+ if value.respond_to?(:to_ary)
903
+ value.to_ary.map! { |val| normalize_value(val) }
947
904
  elsif value.kind_of?(Hash)
948
905
  value = value.inject({}) { |acc, (k, v)|
949
- acc[Addressable::IDNA.unicode_normalize_kc(k)] =
950
- Addressable::IDNA.unicode_normalize_kc(v)
906
+ acc[normalize_value(k)] = normalize_value(v)
951
907
  acc
952
908
  }
953
909
  else
954
- value = Addressable::IDNA.unicode_normalize_kc(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)
955
915
  end
956
916
  value
957
917
  end
@@ -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
- # A regular expression which may be used to parse a template pattern.
990
- def parse_template_pattern(pattern, processor=nil)
967
+ # @return [Array, Regexp]
968
+ # An array of expansion variables nad a regular expression which may be
969
+ # used to parse a template pattern
970
+ def parse_new_template_pattern(pattern, processor = nil)
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.
@@ -1007,7 +987,8 @@ module Addressable
1007
987
  _, operator, varlist = *expansion.match(EXPRESSION)
1008
988
  leader = Regexp.escape(LEADERS.fetch(operator, ''))
1009
989
  joiner = Regexp.escape(JOINERS.fetch(operator, ','))
1010
- combined = varlist.split(',').map do |varspec|
990
+ varspecs = varlist.split(',')
991
+ combined = varspecs.map do |varspec|
1011
992
  _, name, modifier = *varspec.match(VARSPEC)
1012
993
 
1013
994
  result = processor && processor.respond_to?(:match) ? processor.match(name) : nil
@@ -1033,7 +1014,15 @@ module Addressable
1033
1014
  "#{ UNRESERVED }*?"
1034
1015
  end
1035
1016
  if modifier == '*'
1036
- "(?<#{name}>#{group}(?:#{joiner}?#{group})*)?"
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}*+)?"
1037
1026
  else
1038
1027
  "(?<#{name}>#{group})?"
1039
1028
  end
@@ -1043,7 +1032,7 @@ module Addressable
1043
1032
  end
1044
1033
 
1045
1034
  # Ensure that the regular expression matches the whole URI.
1046
- regexp_string = "^#{regexp_string}$"
1035
+ regexp_string = "\\A#{regexp_string}\\z"
1047
1036
  return expansions, Regexp.new(regexp_string)
1048
1037
  end
1049
1038