addressable 2.5.0 → 2.8.8
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 +138 -34
- data/Gemfile +15 -16
- data/README.md +20 -21
- data/Rakefile +16 -11
- data/addressable.gemspec +28 -0
- data/lib/addressable/idna/native.rb +12 -5
- data/lib/addressable/idna/pure.rb +4257 -214
- data/lib/addressable/idna.rb +2 -1
- data/lib/addressable/template.rb +76 -98
- data/lib/addressable/uri.rb +338 -228
- data/lib/addressable/version.rb +4 -3
- data/lib/addressable.rb +2 -0
- data/spec/addressable/idna_spec.rb +29 -13
- data/spec/addressable/net_http_compat_spec.rb +2 -1
- data/spec/addressable/security_spec.rb +2 -1
- data/spec/addressable/template_spec.rb +144 -267
- data/spec/addressable/uri_spec.rb +598 -216
- data/spec/spec_helper.rb +12 -0
- data/tasks/clobber.rake +2 -0
- data/tasks/gem.rake +18 -9
- 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 +25 -22
- data/data/unicode.data +0 -0
- data/spec/addressable/rack_mount_compat_spec.rb +0 -104
data/lib/addressable/idna.rb
CHANGED
data/lib/addressable/template.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
#--
|
|
3
4
|
# Copyright (C) Bob Aman
|
|
4
5
|
#
|
|
@@ -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 =
|
|
@@ -156,7 +157,7 @@ module Addressable
|
|
|
156
157
|
# the ::MatchData#[] behavior.
|
|
157
158
|
#
|
|
158
159
|
# @param [#to_int, nil] len
|
|
159
|
-
# If provided, an array of values will be
|
|
160
|
+
# If provided, an array of values will be returned with the given
|
|
160
161
|
# parameter used as length.
|
|
161
162
|
#
|
|
162
163
|
# @return [Array, String, nil]
|
|
@@ -410,7 +411,7 @@ module Addressable
|
|
|
410
411
|
# match.captures
|
|
411
412
|
# #=> ["a", ["b", "c"]]
|
|
412
413
|
def match(uri, processor=nil)
|
|
413
|
-
uri = Addressable::URI.parse(uri)
|
|
414
|
+
uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
|
|
414
415
|
mapping = {}
|
|
415
416
|
|
|
416
417
|
# First, we need to process the pattern, and extract the values.
|
|
@@ -488,6 +489,8 @@ module Addressable
|
|
|
488
489
|
# @param [Hash] mapping The mapping that corresponds to the pattern.
|
|
489
490
|
# @param [#validate, #transform] processor
|
|
490
491
|
# An optional processor object may be supplied.
|
|
492
|
+
# @param [Boolean] normalize_values
|
|
493
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
|
491
494
|
#
|
|
492
495
|
# The object should respond to either the <tt>validate</tt> or
|
|
493
496
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
|
@@ -518,11 +521,11 @@ module Addressable
|
|
|
518
521
|
# "http://example.com/{?one,two,three}/"
|
|
519
522
|
# ).partial_expand({"one" => "1", "three" => 3}).pattern
|
|
520
523
|
# #=> "http://example.com/?one=1{&two}&three=3"
|
|
521
|
-
def partial_expand(mapping, processor=nil)
|
|
524
|
+
def partial_expand(mapping, processor=nil, normalize_values=true)
|
|
522
525
|
result = self.pattern.dup
|
|
523
526
|
mapping = normalize_keys(mapping)
|
|
524
527
|
result.gsub!( EXPRESSION ) do |capture|
|
|
525
|
-
transform_partial_capture(mapping, capture, processor)
|
|
528
|
+
transform_partial_capture(mapping, capture, processor, normalize_values)
|
|
526
529
|
end
|
|
527
530
|
return Addressable::Template.new(result)
|
|
528
531
|
end
|
|
@@ -533,6 +536,8 @@ module Addressable
|
|
|
533
536
|
# @param [Hash] mapping The mapping that corresponds to the pattern.
|
|
534
537
|
# @param [#validate, #transform] processor
|
|
535
538
|
# An optional processor object may be supplied.
|
|
539
|
+
# @param [Boolean] normalize_values
|
|
540
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
|
536
541
|
#
|
|
537
542
|
# The object should respond to either the <tt>validate</tt> or
|
|
538
543
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
|
@@ -583,11 +588,11 @@ module Addressable
|
|
|
583
588
|
# ExampleProcessor
|
|
584
589
|
# ).to_str
|
|
585
590
|
# #=> Addressable::Template::InvalidTemplateValueError
|
|
586
|
-
def expand(mapping, processor=nil)
|
|
591
|
+
def expand(mapping, processor=nil, normalize_values=true)
|
|
587
592
|
result = self.pattern.dup
|
|
588
593
|
mapping = normalize_keys(mapping)
|
|
589
594
|
result.gsub!( EXPRESSION ) do |capture|
|
|
590
|
-
transform_capture(mapping, capture, processor)
|
|
595
|
+
transform_capture(mapping, capture, processor, normalize_values)
|
|
591
596
|
end
|
|
592
597
|
return Addressable::URI.parse(result)
|
|
593
598
|
end
|
|
@@ -647,50 +652,16 @@ module Addressable
|
|
|
647
652
|
self.to_regexp.named_captures
|
|
648
653
|
end
|
|
649
654
|
|
|
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
655
|
private
|
|
685
656
|
def ordered_variable_defaults
|
|
686
657
|
@ordered_variable_defaults ||= begin
|
|
687
658
|
expansions, _ = parse_template_pattern(pattern)
|
|
688
|
-
expansions.
|
|
659
|
+
expansions.flat_map do |capture|
|
|
689
660
|
_, _, varlist = *capture.match(EXPRESSION)
|
|
690
661
|
varlist.split(',').map do |varspec|
|
|
691
662
|
varspec[VARSPEC, 1]
|
|
692
663
|
end
|
|
693
|
-
end
|
|
664
|
+
end
|
|
694
665
|
end
|
|
695
666
|
end
|
|
696
667
|
|
|
@@ -704,6 +675,8 @@ module Addressable
|
|
|
704
675
|
# The expression to expand
|
|
705
676
|
# @param [#validate, #transform] processor
|
|
706
677
|
# An optional processor object may be supplied.
|
|
678
|
+
# @param [Boolean] normalize_values
|
|
679
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
|
707
680
|
#
|
|
708
681
|
# The object should respond to either the <tt>validate</tt> or
|
|
709
682
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
|
@@ -718,56 +691,36 @@ module Addressable
|
|
|
718
691
|
# after sending the value to the transform method.
|
|
719
692
|
#
|
|
720
693
|
# @return [String] The expanded expression
|
|
721
|
-
def transform_partial_capture(mapping, capture, processor = nil
|
|
694
|
+
def transform_partial_capture(mapping, capture, processor = nil,
|
|
695
|
+
normalize_values = true)
|
|
722
696
|
_, operator, varlist = *capture.match(EXPRESSION)
|
|
723
697
|
|
|
724
|
-
vars = varlist.split(
|
|
698
|
+
vars = varlist.split(",")
|
|
725
699
|
|
|
726
|
-
if
|
|
700
|
+
if operator == "?"
|
|
727
701
|
# partial expansion of form style query variables sometimes requires a
|
|
728
702
|
# slight reordering of the variables to produce a valid url.
|
|
729
703
|
first_to_expand = vars.find { |varspec|
|
|
730
704
|
_, name, _ = *varspec.match(VARSPEC)
|
|
731
|
-
mapping.key? name
|
|
705
|
+
mapping.key?(name) && !mapping[name].nil?
|
|
732
706
|
}
|
|
733
707
|
|
|
734
708
|
vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand
|
|
735
709
|
end
|
|
736
710
|
|
|
737
|
-
vars
|
|
738
|
-
|
|
739
|
-
.reduce("") do |acc, (varspec, op)|
|
|
711
|
+
vars.
|
|
712
|
+
inject("".dup) do |acc, varspec|
|
|
740
713
|
_, 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
|
|
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
|
|
771
724
|
end
|
|
772
725
|
end
|
|
773
726
|
|
|
@@ -780,6 +733,9 @@ module Addressable
|
|
|
780
733
|
# The expression to replace
|
|
781
734
|
# @param [#validate, #transform] processor
|
|
782
735
|
# An optional processor object may be supplied.
|
|
736
|
+
# @param [Boolean] normalize_values
|
|
737
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
|
738
|
+
#
|
|
783
739
|
#
|
|
784
740
|
# The object should respond to either the <tt>validate</tt> or
|
|
785
741
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
|
@@ -794,7 +750,8 @@ module Addressable
|
|
|
794
750
|
# after sending the value to the transform method.
|
|
795
751
|
#
|
|
796
752
|
# @return [String] The expanded expression
|
|
797
|
-
def transform_capture(mapping, capture, processor=nil
|
|
753
|
+
def transform_capture(mapping, capture, processor=nil,
|
|
754
|
+
normalize_values=true)
|
|
798
755
|
_, operator, varlist = *capture.match(EXPRESSION)
|
|
799
756
|
return_value = varlist.split(',').inject([]) do |acc, varspec|
|
|
800
757
|
_, name, modifier = *varspec.match(VARSPEC)
|
|
@@ -814,7 +771,7 @@ module Addressable
|
|
|
814
771
|
"Can't convert #{value.class} into String or Array."
|
|
815
772
|
end
|
|
816
773
|
|
|
817
|
-
value = normalize_value(value)
|
|
774
|
+
value = normalize_value(value) if normalize_values
|
|
818
775
|
|
|
819
776
|
if processor == nil || !processor.respond_to?(:transform)
|
|
820
777
|
# Handle percent escaping
|
|
@@ -877,7 +834,9 @@ module Addressable
|
|
|
877
834
|
end
|
|
878
835
|
if processor.respond_to?(:transform)
|
|
879
836
|
transformed_value = processor.transform(name, value)
|
|
880
|
-
|
|
837
|
+
if normalize_values
|
|
838
|
+
transformed_value = normalize_value(transformed_value)
|
|
839
|
+
end
|
|
881
840
|
end
|
|
882
841
|
end
|
|
883
842
|
acc << [name, transformed_value]
|
|
@@ -933,25 +892,24 @@ module Addressable
|
|
|
933
892
|
# operator.
|
|
934
893
|
#
|
|
935
894
|
# @param [Hash, Array, String] value
|
|
936
|
-
# Normalizes keys and values with
|
|
895
|
+
# Normalizes unicode keys and values with String#unicode_normalize (NFC)
|
|
937
896
|
#
|
|
938
897
|
# @return [Hash, Array, String] The normalized values
|
|
939
898
|
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
899
|
# Handle unicode normalization
|
|
945
|
-
if value.
|
|
946
|
-
value.map! { |val|
|
|
900
|
+
if value.respond_to?(:to_ary)
|
|
901
|
+
value.to_ary.map! { |val| normalize_value(val) }
|
|
947
902
|
elsif value.kind_of?(Hash)
|
|
948
903
|
value = value.inject({}) { |acc, (k, v)|
|
|
949
|
-
acc[
|
|
950
|
-
Addressable::IDNA.unicode_normalize_kc(v)
|
|
904
|
+
acc[normalize_value(k)] = normalize_value(v)
|
|
951
905
|
acc
|
|
952
906
|
}
|
|
953
907
|
else
|
|
954
|
-
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)
|
|
955
913
|
end
|
|
956
914
|
value
|
|
957
915
|
end
|
|
@@ -979,15 +937,35 @@ module Addressable
|
|
|
979
937
|
end
|
|
980
938
|
end
|
|
981
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
|
+
|
|
982
959
|
##
|
|
983
960
|
# Generates the <tt>Regexp</tt> that parses a template pattern.
|
|
984
961
|
#
|
|
985
962
|
# @param [String] pattern The URI template pattern.
|
|
986
963
|
# @param [#match] processor The template processor to use.
|
|
987
964
|
#
|
|
988
|
-
# @return [Regexp]
|
|
989
|
-
#
|
|
990
|
-
|
|
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)
|
|
991
969
|
# Escape the pattern. The two gsubs restore the escaped curly braces
|
|
992
970
|
# back to their original form. Basically, escape everything that isn't
|
|
993
971
|
# within an expansion.
|
|
@@ -1043,7 +1021,7 @@ module Addressable
|
|
|
1043
1021
|
end
|
|
1044
1022
|
|
|
1045
1023
|
# Ensure that the regular expression matches the whole URI.
|
|
1046
|
-
regexp_string = "
|
|
1024
|
+
regexp_string = "\\A#{regexp_string}\\z"
|
|
1047
1025
|
return expansions, Regexp.new(regexp_string)
|
|
1048
1026
|
end
|
|
1049
1027
|
|