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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +167 -33
- data/README.md +36 -20
- data/lib/addressable/idna/native.rb +19 -8
- data/lib/addressable/idna/pure.rb +4263 -225
- data/lib/addressable/idna.rb +3 -2
- data/lib/addressable/template.rb +90 -101
- data/lib/addressable/uri.rb +414 -236
- data/lib/addressable/version.rb +4 -3
- data/lib/addressable.rb +2 -0
- metadata +21 -35
- data/Gemfile +0 -27
- data/Rakefile +0 -32
- data/addressable.gemspec +0 -34
- data/data/unicode.data +0 -0
- data/spec/addressable/idna_spec.rb +0 -270
- data/spec/addressable/net_http_compat_spec.rb +0 -28
- data/spec/addressable/rack_mount_compat_spec.rb +0 -104
- data/spec/addressable/security_spec.rb +0 -57
- data/spec/addressable/template_spec.rb +0 -1386
- data/spec/addressable/uri_spec.rb +0 -6205
- data/spec/spec_helper.rb +0 -21
- data/tasks/clobber.rake +0 -2
- data/tasks/gem.rake +0 -97
- data/tasks/git.rake +0 -45
- data/tasks/metrics.rake +0 -22
- data/tasks/rspec.rake +0 -21
- data/tasks/yard.rake +0 -27
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
|
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
739
|
-
.reduce("") do |acc, (varspec, op)|
|
|
713
|
+
vars.
|
|
714
|
+
inject("".dup) do |acc, varspec|
|
|
740
715
|
_, 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
|
|
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
|
-
|
|
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
|
|
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.
|
|
946
|
-
value.map! { |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[
|
|
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 =
|
|
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
|
-
#
|
|
990
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = "
|
|
1035
|
+
regexp_string = "\\A#{regexp_string}\\z"
|
|
1047
1036
|
return expansions, Regexp.new(regexp_string)
|
|
1048
1037
|
end
|
|
1049
1038
|
|