addressable 2.3.6 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +76 -0
- data/Gemfile +18 -18
- data/README.md +37 -18
- data/Rakefile +8 -11
- data/addressable.gemspec +37 -0
- data/lib/addressable/idna/native.rb +24 -6
- data/lib/addressable/idna/pure.rb +67 -58
- data/lib/addressable/idna.rb +3 -1
- data/lib/addressable/template.rb +126 -33
- data/lib/addressable/uri.rb +416 -211
- data/lib/addressable/version.rb +5 -3
- data/lib/addressable.rb +4 -0
- data/spec/addressable/idna_spec.rb +110 -46
- data/spec/addressable/net_http_compat_spec.rb +4 -2
- data/spec/addressable/security_spec.rb +59 -0
- data/spec/addressable/template_spec.rb +297 -165
- data/spec/addressable/uri_spec.rb +2054 -1329
- data/spec/spec_helper.rb +28 -1
- data/tasks/clobber.rake +2 -0
- data/tasks/gem.rake +22 -16
- data/tasks/git.rake +3 -1
- data/tasks/metrics.rake +2 -0
- data/tasks/profile.rake +72 -0
- data/tasks/rspec.rake +10 -45
- data/tasks/yard.rake +2 -0
- metadata +44 -45
- data/tasks/rubyforge.rake +0 -73
- data/website/index.html +0 -110
data/lib/addressable/template.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# encoding:utf-8
|
2
4
|
#--
|
3
|
-
# Copyright (C)
|
5
|
+
# Copyright (C) Bob Aman
|
4
6
|
#
|
5
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
8
|
# you may not use this file except in compliance with the License.
|
@@ -35,7 +37,7 @@ module Addressable
|
|
35
37
|
Addressable::URI::CharacterClasses::DIGIT + '_'
|
36
38
|
|
37
39
|
var_char =
|
38
|
-
"(
|
40
|
+
"(?>(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
|
39
41
|
RESERVED =
|
40
42
|
"(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
|
41
43
|
UNRESERVED =
|
@@ -234,7 +236,18 @@ module Addressable
|
|
234
236
|
if !pattern.respond_to?(:to_str)
|
235
237
|
raise TypeError, "Can't convert #{pattern.class} into String."
|
236
238
|
end
|
237
|
-
@pattern = pattern.to_str.freeze
|
239
|
+
@pattern = pattern.to_str.dup.freeze
|
240
|
+
end
|
241
|
+
|
242
|
+
##
|
243
|
+
# Freeze URI, initializing instance variables.
|
244
|
+
#
|
245
|
+
# @return [Addressable::URI] The frozen URI object.
|
246
|
+
def freeze
|
247
|
+
self.variables
|
248
|
+
self.variable_defaults
|
249
|
+
self.named_captures
|
250
|
+
super
|
238
251
|
end
|
239
252
|
|
240
253
|
##
|
@@ -399,7 +412,7 @@ module Addressable
|
|
399
412
|
# match.captures
|
400
413
|
# #=> ["a", ["b", "c"]]
|
401
414
|
def match(uri, processor=nil)
|
402
|
-
uri = Addressable::URI.parse(uri)
|
415
|
+
uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
|
403
416
|
mapping = {}
|
404
417
|
|
405
418
|
# First, we need to process the pattern, and extract the values.
|
@@ -477,6 +490,8 @@ module Addressable
|
|
477
490
|
# @param [Hash] mapping The mapping that corresponds to the pattern.
|
478
491
|
# @param [#validate, #transform] processor
|
479
492
|
# An optional processor object may be supplied.
|
493
|
+
# @param [Boolean] normalize_values
|
494
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
480
495
|
#
|
481
496
|
# The object should respond to either the <tt>validate</tt> or
|
482
497
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
@@ -507,11 +522,11 @@ module Addressable
|
|
507
522
|
# "http://example.com/{?one,two,three}/"
|
508
523
|
# ).partial_expand({"one" => "1", "three" => 3}).pattern
|
509
524
|
# #=> "http://example.com/?one=1{&two}&three=3"
|
510
|
-
def partial_expand(mapping, processor=nil)
|
525
|
+
def partial_expand(mapping, processor=nil, normalize_values=true)
|
511
526
|
result = self.pattern.dup
|
512
527
|
mapping = normalize_keys(mapping)
|
513
528
|
result.gsub!( EXPRESSION ) do |capture|
|
514
|
-
transform_partial_capture(mapping, capture, processor)
|
529
|
+
transform_partial_capture(mapping, capture, processor, normalize_values)
|
515
530
|
end
|
516
531
|
return Addressable::Template.new(result)
|
517
532
|
end
|
@@ -522,6 +537,8 @@ module Addressable
|
|
522
537
|
# @param [Hash] mapping The mapping that corresponds to the pattern.
|
523
538
|
# @param [#validate, #transform] processor
|
524
539
|
# An optional processor object may be supplied.
|
540
|
+
# @param [Boolean] normalize_values
|
541
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
525
542
|
#
|
526
543
|
# The object should respond to either the <tt>validate</tt> or
|
527
544
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
@@ -572,11 +589,11 @@ module Addressable
|
|
572
589
|
# ExampleProcessor
|
573
590
|
# ).to_str
|
574
591
|
# #=> Addressable::Template::InvalidTemplateValueError
|
575
|
-
def expand(mapping, processor=nil)
|
592
|
+
def expand(mapping, processor=nil, normalize_values=true)
|
576
593
|
result = self.pattern.dup
|
577
594
|
mapping = normalize_keys(mapping)
|
578
595
|
result.gsub!( EXPRESSION ) do |capture|
|
579
|
-
transform_capture(mapping, capture, processor)
|
596
|
+
transform_capture(mapping, capture, processor, normalize_values)
|
580
597
|
end
|
581
598
|
return Addressable::URI.parse(result)
|
582
599
|
end
|
@@ -592,6 +609,7 @@ module Addressable
|
|
592
609
|
@variables ||= ordered_variable_defaults.map { |var, val| var }.uniq
|
593
610
|
end
|
594
611
|
alias_method :keys, :variables
|
612
|
+
alias_method :names, :variables
|
595
613
|
|
596
614
|
##
|
597
615
|
# Returns a mapping of variables to their default values specified
|
@@ -603,9 +621,41 @@ module Addressable
|
|
603
621
|
Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten]
|
604
622
|
end
|
605
623
|
|
624
|
+
##
|
625
|
+
# Coerces a template into a `Regexp` object. This regular expression will
|
626
|
+
# behave very similarly to the actual template, and should match the same
|
627
|
+
# URI values, but it cannot fully handle, for example, values that would
|
628
|
+
# extract to an `Array`.
|
629
|
+
#
|
630
|
+
# @return [Regexp] A regular expression which should match the template.
|
631
|
+
def to_regexp
|
632
|
+
_, source = parse_template_pattern(pattern)
|
633
|
+
Regexp.new(source)
|
634
|
+
end
|
635
|
+
|
636
|
+
##
|
637
|
+
# Returns the source of the coerced `Regexp`.
|
638
|
+
#
|
639
|
+
# @return [String] The source of the `Regexp` given by {#to_regexp}.
|
640
|
+
#
|
641
|
+
# @api private
|
642
|
+
def source
|
643
|
+
self.to_regexp.source
|
644
|
+
end
|
645
|
+
|
646
|
+
##
|
647
|
+
# Returns the named captures of the coerced `Regexp`.
|
648
|
+
#
|
649
|
+
# @return [Hash] The named captures of the `Regexp` given by {#to_regexp}.
|
650
|
+
#
|
651
|
+
# @api private
|
652
|
+
def named_captures
|
653
|
+
self.to_regexp.named_captures
|
654
|
+
end
|
655
|
+
|
606
656
|
private
|
607
657
|
def ordered_variable_defaults
|
608
|
-
@ordered_variable_defaults ||=
|
658
|
+
@ordered_variable_defaults ||= begin
|
609
659
|
expansions, _ = parse_template_pattern(pattern)
|
610
660
|
expansions.map do |capture|
|
611
661
|
_, _, varlist = *capture.match(EXPRESSION)
|
@@ -613,7 +663,7 @@ module Addressable
|
|
613
663
|
varspec[VARSPEC, 1]
|
614
664
|
end
|
615
665
|
end.flatten
|
616
|
-
|
666
|
+
end
|
617
667
|
end
|
618
668
|
|
619
669
|
|
@@ -626,6 +676,8 @@ module Addressable
|
|
626
676
|
# The expression to expand
|
627
677
|
# @param [#validate, #transform] processor
|
628
678
|
# An optional processor object may be supplied.
|
679
|
+
# @param [Boolean] normalize_values
|
680
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
629
681
|
#
|
630
682
|
# The object should respond to either the <tt>validate</tt> or
|
631
683
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
@@ -640,21 +692,36 @@ module Addressable
|
|
640
692
|
# after sending the value to the transform method.
|
641
693
|
#
|
642
694
|
# @return [String] The expanded expression
|
643
|
-
def transform_partial_capture(mapping, capture, processor = nil
|
695
|
+
def transform_partial_capture(mapping, capture, processor = nil,
|
696
|
+
normalize_values = true)
|
644
697
|
_, operator, varlist = *capture.match(EXPRESSION)
|
645
|
-
|
646
|
-
varlist.split(
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
698
|
+
|
699
|
+
vars = varlist.split(",")
|
700
|
+
|
701
|
+
if operator == "?"
|
702
|
+
# partial expansion of form style query variables sometimes requires a
|
703
|
+
# slight reordering of the variables to produce a valid url.
|
704
|
+
first_to_expand = vars.find { |varspec|
|
705
|
+
_, name, _ = *varspec.match(VARSPEC)
|
706
|
+
mapping.key?(name) && !mapping[name].nil?
|
707
|
+
}
|
708
|
+
|
709
|
+
vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand
|
710
|
+
end
|
711
|
+
|
712
|
+
vars.
|
713
|
+
inject("".dup) do |acc, varspec|
|
714
|
+
_, name, _ = *varspec.match(VARSPEC)
|
715
|
+
next_val = if mapping.key? name
|
716
|
+
transform_capture(mapping, "{#{operator}#{varspec}}",
|
717
|
+
processor, normalize_values)
|
718
|
+
else
|
719
|
+
"{#{operator}#{varspec}}"
|
720
|
+
end
|
721
|
+
# If we've already expanded at least one '?' operator with non-empty
|
722
|
+
# value, change to '&'
|
723
|
+
operator = "&" if (operator == "?") && (next_val != "")
|
724
|
+
acc << next_val
|
658
725
|
end
|
659
726
|
end
|
660
727
|
|
@@ -667,6 +734,9 @@ module Addressable
|
|
667
734
|
# The expression to replace
|
668
735
|
# @param [#validate, #transform] processor
|
669
736
|
# An optional processor object may be supplied.
|
737
|
+
# @param [Boolean] normalize_values
|
738
|
+
# Optional flag to enable/disable unicode normalization. Default: true
|
739
|
+
#
|
670
740
|
#
|
671
741
|
# The object should respond to either the <tt>validate</tt> or
|
672
742
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
@@ -681,7 +751,8 @@ module Addressable
|
|
681
751
|
# after sending the value to the transform method.
|
682
752
|
#
|
683
753
|
# @return [String] The expanded expression
|
684
|
-
def transform_capture(mapping, capture, processor=nil
|
754
|
+
def transform_capture(mapping, capture, processor=nil,
|
755
|
+
normalize_values=true)
|
685
756
|
_, operator, varlist = *capture.match(EXPRESSION)
|
686
757
|
return_value = varlist.split(',').inject([]) do |acc, varspec|
|
687
758
|
_, name, modifier = *varspec.match(VARSPEC)
|
@@ -701,7 +772,7 @@ module Addressable
|
|
701
772
|
"Can't convert #{value.class} into String or Array."
|
702
773
|
end
|
703
774
|
|
704
|
-
value = normalize_value(value)
|
775
|
+
value = normalize_value(value) if normalize_values
|
705
776
|
|
706
777
|
if processor == nil || !processor.respond_to?(:transform)
|
707
778
|
# Handle percent escaping
|
@@ -764,7 +835,9 @@ module Addressable
|
|
764
835
|
end
|
765
836
|
if processor.respond_to?(:transform)
|
766
837
|
transformed_value = processor.transform(name, value)
|
767
|
-
|
838
|
+
if normalize_values
|
839
|
+
transformed_value = normalize_value(transformed_value)
|
840
|
+
end
|
768
841
|
end
|
769
842
|
end
|
770
843
|
acc << [name, transformed_value]
|
@@ -866,15 +939,35 @@ module Addressable
|
|
866
939
|
end
|
867
940
|
end
|
868
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
|
+
|
869
961
|
##
|
870
962
|
# Generates the <tt>Regexp</tt> that parses a template pattern.
|
871
963
|
#
|
872
964
|
# @param [String] pattern The URI template pattern.
|
873
965
|
# @param [#match] processor The template processor to use.
|
874
966
|
#
|
875
|
-
# @return [Regexp]
|
876
|
-
#
|
877
|
-
|
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)
|
878
971
|
# Escape the pattern. The two gsubs restore the escaped curly braces
|
879
972
|
# back to their original form. Basically, escape everything that isn't
|
880
973
|
# within an expansion.
|
@@ -899,7 +992,7 @@ module Addressable
|
|
899
992
|
|
900
993
|
result = processor && processor.respond_to?(:match) ? processor.match(name) : nil
|
901
994
|
if result
|
902
|
-
"(
|
995
|
+
"(?<#{name}>#{ result })"
|
903
996
|
else
|
904
997
|
group = case operator
|
905
998
|
when '+'
|
@@ -920,9 +1013,9 @@ module Addressable
|
|
920
1013
|
"#{ UNRESERVED }*?"
|
921
1014
|
end
|
922
1015
|
if modifier == '*'
|
923
|
-
"(
|
1016
|
+
"(?<#{name}>#{group}(?:#{joiner}?#{group})*)?"
|
924
1017
|
else
|
925
|
-
"(
|
1018
|
+
"(?<#{name}>#{group})?"
|
926
1019
|
end
|
927
1020
|
end
|
928
1021
|
end.join("#{joiner}?")
|