addressable 2.8.10 → 2.9.1
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 +4 -4
- data/CHANGELOG.md +70 -0
- data/lib/addressable/idna/native.rb +9 -1
- data/lib/addressable/idna/pure.rb +49 -2
- data/lib/addressable/template.rb +209 -8
- data/lib/addressable/uri.rb +58 -29
- data/lib/addressable/version.rb +2 -2
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6d91298f649477b2a758f6205bd3598d071f8c6ee6c063e6a906833d7b1c22d
|
|
4
|
+
data.tar.gz: '0911936040e27dda609f926393afbd2b5791ec217e25abd02cb744d512e6cf32'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82bc575aff52883ab8834cd6e588db9e086f9721ef86dc93a43120ac4ae7b3ea88da1d74c05ca1f96ad3890854152f363acf02064c2a754dcd02101f5b509b90
|
|
7
|
+
data.tar.gz: f5da7b05d4d49ed517a5e3773adbb3146069492e69ee6666285fc9f298925b2805227003489abb5ee353270f23197c165e1b4c6d5b5588d03f887a5a44f4822b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Addressable 2.9.1 <a name="v2.9.1">
|
|
4
|
+
- fixes denial of service in `Addressable::URI.normalize_path`, which took
|
|
5
|
+
quadratic time on paths with many dot segments; `URI#normalize`, `#join`,
|
|
6
|
+
`#==` and `#===` were affected (GHSA-qq38-m9mg-xjj5)
|
|
7
|
+
- fixes `Addressable::URI.normalize_path` keeping segments that a `..` should
|
|
8
|
+
have removed after a `..` above the root, e.g.
|
|
9
|
+
`/../../public/x/../../secret` normalized to `/public/x/secret` instead of
|
|
10
|
+
`/secret` (GHSA-8gmh-2hhj-2hv9)
|
|
11
|
+
- fixes `Addressable::URI.normalize_path` treating a raw line feed as a
|
|
12
|
+
segment boundary, e.g. joining `..\n/secret` onto `/public/` gave
|
|
13
|
+
`/\n/secret`
|
|
14
|
+
- deprecates `Addressable::URI::RULE_2A`, `RULE_2B_2C`, `RULE_2D` and
|
|
15
|
+
`RULE_PREFIXED_PARENT`, which are no longer used; they will be removed in
|
|
16
|
+
2.10.0
|
|
17
|
+
- fixes denial of service in `Addressable::Template#match` and `#extract`,
|
|
18
|
+
which still backtracked exponentially in 2.9.0 for exploded `/`, `.`, `;`,
|
|
19
|
+
`?` and `&` expressions (e.g. `{/path*}`) on regex engines without match
|
|
20
|
+
memoization, such as JRuby, TruffleRuby and MRI before 3.2; match results
|
|
21
|
+
are unchanged (GHSA-j82w-28jv-3pmj)
|
|
22
|
+
- fixes denial of service in `Addressable::Template#match` and `#extract`,
|
|
23
|
+
which backtracked polynomially or exponentially on the same regex engines
|
|
24
|
+
for expressions with several variables (e.g. `{/owner,repo,ref}`, or
|
|
25
|
+
`{+a,b,c}` with many variables), for `;` expressions, and for adjacent
|
|
26
|
+
expressions (e.g. `{+a}{+b}{+c}` or `{+a}{b}`); match results are unchanged
|
|
27
|
+
(GHSA-9f42-57jx-vpp3)
|
|
28
|
+
- fixes denial of service in `Addressable::Template#match` and `#extract`,
|
|
29
|
+
which backtracked polynomially for expressions with several exploded
|
|
30
|
+
variables (e.g. `{+a*,b*}`), including on MRI 3.2; match results are
|
|
31
|
+
unchanged (GHSA-4w5q-wg7v-w6gp)
|
|
32
|
+
- fixes `Addressable::IDNA.to_unicode` in the pure-Ruby backend decoding
|
|
33
|
+
Punycode labels that aren't valid A-labels: labels that decode to only
|
|
34
|
+
ASCII characters (e.g. `xn--google-` decoded to `google`) and labels that
|
|
35
|
+
don't convert back to themselves; such a label is now returned unchanged,
|
|
36
|
+
as with libidn, so `Addressable::URI#display_uri` no longer shows a host
|
|
37
|
+
that differs from `#normalized_host` (GHSA-9crm-g4mf-wc5j)
|
|
38
|
+
- fixes `Addressable::IDNA.to_unicode` decoding Punycode labels with
|
|
39
|
+
characters that change how a host displays without being visible
|
|
40
|
+
themselves, such as bidi overrides and isolates (e.g. U+202E and U+2066),
|
|
41
|
+
zero width characters and control characters; such a label is now
|
|
42
|
+
returned unchanged, so `Addressable::URI#display_uri` no longer shows
|
|
43
|
+
them. The pure-Ruby backend also no longer decodes labels with other
|
|
44
|
+
characters that nameprep prohibits, as with libidn (GHSA-539f-xcgw-2gjg)
|
|
45
|
+
- fixes `Addressable::URI#route_from` and `#route_to` raising
|
|
46
|
+
`InvalidURIError` when the route's first segment contains a colon
|
|
47
|
+
(e.g. `/a:b` from `/a`); the route is now `./a:b` ([#625])
|
|
48
|
+
- fixes `Addressable::IDNA.to_unicode` raising in the pure-Ruby backend for
|
|
49
|
+
Punycode labels that are too long, overflow the decoder, or decode past
|
|
50
|
+
U+10FFFF; the label is now returned unchanged, as with libidn, so
|
|
51
|
+
`Addressable::URI#display_uri` no longer raises for such hosts ([#620])
|
|
52
|
+
- fixes `Addressable::URI#display_uri` raising `InvalidURIError` for URIs
|
|
53
|
+
without a host, such as `abc` or `mailto:user@example.com`; relative URIs
|
|
54
|
+
are also no longer displayed with an empty authority (e.g. `/path/to` was
|
|
55
|
+
displayed as `///path/to`) ([#610])
|
|
56
|
+
- fixes `Addressable::Template#match` and `#extract` raising
|
|
57
|
+
`Addressable::URI::InvalidURIError` for a string that isn't a valid URI;
|
|
58
|
+
they now return `nil`, as for any other URI that doesn't match ([#621])
|
|
59
|
+
- fixes `Addressable::URI#origin=` raising `InvalidURIError` for an origin
|
|
60
|
+
with an empty port (e.g. `http://example.com:`), which is the same as no
|
|
61
|
+
port ([#627])
|
|
62
|
+
|
|
63
|
+
[#610]: https://github.com/sporkmonger/addressable/pull/610
|
|
64
|
+
[#620]: https://github.com/sporkmonger/addressable/pull/620
|
|
65
|
+
[#621]: https://github.com/sporkmonger/addressable/pull/621
|
|
66
|
+
[#625]: https://github.com/sporkmonger/addressable/pull/625
|
|
67
|
+
[#627]: https://github.com/sporkmonger/addressable/pull/627
|
|
68
|
+
|
|
69
|
+
## Addressable 2.9.0 <a name="v2.9.0">
|
|
70
|
+
- fixes ReDoS vulnerability in Addressable::Template#match (fixes incomplete
|
|
71
|
+
remediation in 2.8.10)
|
|
72
|
+
|
|
3
73
|
## Addressable 2.8.10 <a name="v2.8.10">
|
|
4
74
|
- fixes ReDoS vulnerability in Addressable::Template#match
|
|
5
75
|
|
|
@@ -51,10 +51,18 @@ module Addressable
|
|
|
51
51
|
end.join('.')
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
# Characters that change how a label displays without being visible
|
|
55
|
+
# themselves: controls, format characters such as bidi overrides and
|
|
56
|
+
# isolates, and default ignorable code points. libidn rejects most of
|
|
57
|
+
# them, but not those added to Unicode after the version it checks
|
|
58
|
+
# against, such as U+2066 to U+2069.
|
|
59
|
+
DISPLAY_CHANGING = /[\p{Cc}\p{Cf}\p{Default_Ignorable_Code_Point}]/
|
|
60
|
+
|
|
54
61
|
def self.to_unicode(value)
|
|
55
62
|
value.to_s.split('.', -1).map do |segment|
|
|
56
63
|
if segment.size > 0 && segment.size < 64
|
|
57
|
-
IDN::Idna.toUnicode(segment, IDN::Idna::ALLOW_UNASSIGNED)
|
|
64
|
+
decoded = IDN::Idna.toUnicode(segment, IDN::Idna::ALLOW_UNASSIGNED)
|
|
65
|
+
decoded =~ DISPLAY_CHANGING ? segment : decoded
|
|
58
66
|
elsif segment.size >= 64
|
|
59
67
|
segment
|
|
60
68
|
else
|
|
@@ -55,6 +55,39 @@ module Addressable
|
|
|
55
55
|
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
|
|
56
56
|
)/mnx
|
|
57
57
|
|
|
58
|
+
# Characters that nameprep doesn't allow in a label (Section 5 of
|
|
59
|
+
# RFC 3491), or that it maps to nothing, so they can't be in a label that
|
|
60
|
+
# toASCII produces.
|
|
61
|
+
NAMEPREP_PROHIBITED = Regexp.new("[" + [
|
|
62
|
+
# Table C.1.2, non-ASCII space characters
|
|
63
|
+
"\u00A0\u1680\u2000-\u200B\u202F\u205F\u3000",
|
|
64
|
+
# Table C.2.2, non-ASCII control characters
|
|
65
|
+
"\u0080-\u009F\u06DD\u070F\u180E\u200C\u200D\u2028\u2029" +
|
|
66
|
+
"\u2060-\u2063\u206A-\u206F\uFEFF\uFFF9-\uFFFC\u{1D173}-\u{1D17A}",
|
|
67
|
+
# Table C.3, private use
|
|
68
|
+
"\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}",
|
|
69
|
+
# Table C.4, non-character code points
|
|
70
|
+
"\uFDD0-\uFDEF" + (0..16).map do |plane|
|
|
71
|
+
[plane * 0x10000 + 0xFFFE, 0x2D, plane * 0x10000 + 0xFFFF].pack("U*")
|
|
72
|
+
end.join,
|
|
73
|
+
# Table C.6, inappropriate for plain text
|
|
74
|
+
"\uFFF9-\uFFFD",
|
|
75
|
+
# Table C.7, inappropriate for canonical representation
|
|
76
|
+
"\u2FF0-\u2FFB",
|
|
77
|
+
# Table C.8, change display properties or are deprecated
|
|
78
|
+
"\u0340\u0341\u200E\u200F\u202A-\u202E\u206A-\u206F",
|
|
79
|
+
# Table C.9, tagging characters
|
|
80
|
+
"\u{E0001}\u{E0020}-\u{E007F}",
|
|
81
|
+
# Table B.1, commonly mapped to nothing
|
|
82
|
+
"\u00AD\u034F\u1806\u180B-\u180D\u200B-\u200D\u2060\uFE00-\uFE0F\uFEFF"
|
|
83
|
+
].join + "]")
|
|
84
|
+
|
|
85
|
+
# Characters that change how a label displays without being visible
|
|
86
|
+
# themselves: controls, format characters such as bidi overrides and
|
|
87
|
+
# isolates, and default ignorable code points, including those added to
|
|
88
|
+
# Unicode after the version nameprep is based on.
|
|
89
|
+
DISPLAY_CHANGING = /[\p{Cc}\p{Cf}\p{Default_Ignorable_Code_Point}]/
|
|
90
|
+
|
|
58
91
|
# :startdoc:
|
|
59
92
|
|
|
60
93
|
# Converts from a Unicode internationalized domain name to an ASCII
|
|
@@ -91,8 +124,22 @@ module Addressable
|
|
|
91
124
|
parts.map! do |part|
|
|
92
125
|
if part =~ /^#{ACE_PREFIX}(.+)/
|
|
93
126
|
begin
|
|
94
|
-
punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1])
|
|
95
|
-
|
|
127
|
+
decoded = punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1])
|
|
128
|
+
# A label that doesn't convert back to itself isn't a valid
|
|
129
|
+
# A-label (Section 4.2 of RFC 3490, steps 6 and 7). That includes
|
|
130
|
+
# a label that decodes to only ASCII characters. A label with a
|
|
131
|
+
# character that would change how it displays is returned
|
|
132
|
+
# unchanged too.
|
|
133
|
+
if to_ascii(decoded).downcase != part.downcase ||
|
|
134
|
+
decoded =~ NAMEPREP_PROHIBITED || decoded =~ DISPLAY_CHANGING
|
|
135
|
+
part
|
|
136
|
+
else
|
|
137
|
+
decoded
|
|
138
|
+
end
|
|
139
|
+
rescue Addressable::IDNA::PunycodeBadInput,
|
|
140
|
+
Addressable::IDNA::PunycodeBigOutput,
|
|
141
|
+
Addressable::IDNA::PunycodeOverflow,
|
|
142
|
+
RangeError, ArgumentError
|
|
96
143
|
# toUnicode is explicitly defined as never-fails by the spec
|
|
97
144
|
part
|
|
98
145
|
end
|
data/lib/addressable/template.rb
CHANGED
|
@@ -413,15 +413,25 @@ module Addressable
|
|
|
413
413
|
# match.captures
|
|
414
414
|
# #=> ["a", ["b", "c"]]
|
|
415
415
|
def match(uri, processor=nil)
|
|
416
|
-
|
|
416
|
+
unless uri.is_a?(Addressable::URI)
|
|
417
|
+
begin
|
|
418
|
+
uri = Addressable::URI.parse(uri)
|
|
419
|
+
rescue Addressable::URI::InvalidURIError
|
|
420
|
+
# Per the documented contract, match returns nil for any input
|
|
421
|
+
# that doesn't match the template - including a string that
|
|
422
|
+
# isn't even a valid URI.
|
|
423
|
+
return nil
|
|
424
|
+
end
|
|
425
|
+
end
|
|
417
426
|
mapping = {}
|
|
418
427
|
|
|
419
428
|
# First, we need to process the pattern, and extract the values.
|
|
420
429
|
expansions, expansion_regexp =
|
|
421
430
|
parse_template_pattern(pattern, processor)
|
|
422
431
|
|
|
423
|
-
|
|
424
|
-
|
|
432
|
+
matched = uri.to_str.match(expansion_regexp)
|
|
433
|
+
return nil unless matched
|
|
434
|
+
unparsed_values = matched.captures
|
|
425
435
|
|
|
426
436
|
if uri.to_str == pattern
|
|
427
437
|
return Addressable::Template::MatchData.new(uri, self, mapping)
|
|
@@ -958,6 +968,77 @@ module Addressable
|
|
|
958
968
|
end
|
|
959
969
|
end
|
|
960
970
|
|
|
971
|
+
##
|
|
972
|
+
# Describes what follows each expression in a template pattern.
|
|
973
|
+
#
|
|
974
|
+
# @param [String] escaped_pattern The escaped URI template pattern.
|
|
975
|
+
#
|
|
976
|
+
# @return [Array<Array>] For each expression, the <tt>MatchData</tt> of
|
|
977
|
+
# the expression directly after it (or <tt>nil</tt>), and whether the
|
|
978
|
+
# text after it could start with a comma.
|
|
979
|
+
def expression_followers(escaped_pattern)
|
|
980
|
+
expressions = escaped_pattern.to_enum(:scan, EXPRESSION).map do
|
|
981
|
+
Regexp.last_match
|
|
982
|
+
end
|
|
983
|
+
expressions.each_with_index.map do |current, index|
|
|
984
|
+
following = expressions[index + 1]
|
|
985
|
+
following = nil unless following && following.begin(0) == current.end(0)
|
|
986
|
+
# Expressions can match nothing, so look past any that can't start
|
|
987
|
+
# with a comma.
|
|
988
|
+
position = current.end(0)
|
|
989
|
+
comma = expressions[(index + 1)..-1].any? do |later|
|
|
990
|
+
break false unless later.begin(0) == position
|
|
991
|
+
position = later.end(0)
|
|
992
|
+
may_start_with_comma?(later[1], later[2])
|
|
993
|
+
end
|
|
994
|
+
[following, comma || escaped_pattern[position] == ',']
|
|
995
|
+
end
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
##
|
|
999
|
+
# Whether an expression could match a string that starts with a comma.
|
|
1000
|
+
#
|
|
1001
|
+
# @param [String, nil] operator The expression's operator.
|
|
1002
|
+
# @param [String] varlist The expression's variable list.
|
|
1003
|
+
#
|
|
1004
|
+
# @return [TrueClass, FalseClass]
|
|
1005
|
+
def may_start_with_comma?(operator, varlist)
|
|
1006
|
+
return false unless LEADERS.fetch(operator, '').empty?
|
|
1007
|
+
operator == '+' || !(varlist =~ VARSPEC && $2 != '*')
|
|
1008
|
+
end
|
|
1009
|
+
|
|
1010
|
+
##
|
|
1011
|
+
# Whether an expression, followed directly by another one, could only
|
|
1012
|
+
# match characters that the other one could also match, in any amount.
|
|
1013
|
+
#
|
|
1014
|
+
# @param [MatchData] following The expression that follows.
|
|
1015
|
+
# @param [String, nil] operator The operator of the first expression.
|
|
1016
|
+
# @param [Array<String>] varspecs The varspecs of the first expression.
|
|
1017
|
+
#
|
|
1018
|
+
# @return [TrueClass, FalseClass]
|
|
1019
|
+
def absorbed_by?(following, operator, varspecs)
|
|
1020
|
+
last = following[2].split(',').last
|
|
1021
|
+
case following[1]
|
|
1022
|
+
when '+'
|
|
1023
|
+
# An unexploded last variable matches any characters that an
|
|
1024
|
+
# expression can, and can end anywhere.
|
|
1025
|
+
last[VARSPEC, 2] != '*'
|
|
1026
|
+
when nil
|
|
1027
|
+
if last[VARSPEC, 2] == '*'
|
|
1028
|
+
# Matches any unreserved characters and commas.
|
|
1029
|
+
operator.nil? || operator == '.'
|
|
1030
|
+
elsif following[2] == last
|
|
1031
|
+
# Matches any unreserved characters.
|
|
1032
|
+
operator == '.' || (operator.nil? && varspecs.size == 1 &&
|
|
1033
|
+
varspecs.first[VARSPEC, 2] != '*')
|
|
1034
|
+
else
|
|
1035
|
+
false
|
|
1036
|
+
end
|
|
1037
|
+
else
|
|
1038
|
+
false
|
|
1039
|
+
end
|
|
1040
|
+
end
|
|
1041
|
+
|
|
961
1042
|
##
|
|
962
1043
|
# Generates the <tt>Regexp</tt> that parses a template pattern.
|
|
963
1044
|
#
|
|
@@ -978,19 +1059,36 @@ module Addressable
|
|
|
978
1059
|
end
|
|
979
1060
|
|
|
980
1061
|
expansions = []
|
|
1062
|
+
# Processor patterns can match anything, so they get 2.9.0's choices.
|
|
1063
|
+
plain_processor = !(processor && processor.respond_to?(:match))
|
|
1064
|
+
followers = expression_followers(escaped_pattern)
|
|
981
1065
|
|
|
982
1066
|
# Create a regular expression that captures the values of the
|
|
983
1067
|
# variables in the URI.
|
|
984
1068
|
regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion|
|
|
985
1069
|
|
|
1070
|
+
index = expansions.size
|
|
986
1071
|
expansions << expansion
|
|
987
1072
|
_, operator, varlist = *expansion.match(EXPRESSION)
|
|
988
1073
|
leader = Regexp.escape(LEADERS.fetch(operator, ''))
|
|
989
1074
|
joiner = Regexp.escape(JOINERS.fetch(operator, ','))
|
|
990
|
-
|
|
1075
|
+
varspecs = varlist.split(',')
|
|
1076
|
+
results = varspecs.map do |varspec|
|
|
1077
|
+
name = varspec[VARSPEC, 1]
|
|
1078
|
+
processor && processor.respond_to?(:match) ? processor.match(name) : nil
|
|
1079
|
+
end
|
|
1080
|
+
# Without processor patterns, any variable in the expression can take
|
|
1081
|
+
# the characters that an earlier variable leaves, which lets the
|
|
1082
|
+
# variables after the first one have fewer choices below.
|
|
1083
|
+
plain = results.compact.empty?
|
|
1084
|
+
explodes = varspecs.map { |varspec| varspec[VARSPEC, 2] == '*' }
|
|
1085
|
+
first_explode = explodes.index(true)
|
|
1086
|
+
not_after_value =
|
|
1087
|
+
"(?<![#{Addressable::URI::CharacterClasses::UNRESERVED}])"
|
|
1088
|
+
combined = varspecs.each_with_index.map do |varspec, index|
|
|
991
1089
|
_, name, modifier = *varspec.match(VARSPEC)
|
|
992
1090
|
|
|
993
|
-
result =
|
|
1091
|
+
result = results[index]
|
|
994
1092
|
if result
|
|
995
1093
|
"(?<#{name}>#{ result })"
|
|
996
1094
|
else
|
|
@@ -1004,7 +1102,9 @@ module Addressable
|
|
|
1004
1102
|
when '.'
|
|
1005
1103
|
"#{ UNRESERVED.gsub('\.', '') }*?"
|
|
1006
1104
|
when ';'
|
|
1007
|
-
|
|
1105
|
+
# The same strings, tried in the same order, as 2.9.0's
|
|
1106
|
+
# "U*=?U*?", but a name without "=" can only match one way.
|
|
1107
|
+
"(?:#{UNRESERVED}*=#{UNRESERVED}*?|#{UNRESERVED}*)"
|
|
1008
1108
|
when '?'
|
|
1009
1109
|
"#{ UNRESERVED }*=#{ UNRESERVED }*?"
|
|
1010
1110
|
when '&'
|
|
@@ -1013,16 +1113,117 @@ module Addressable
|
|
|
1013
1113
|
"#{ UNRESERVED }*?"
|
|
1014
1114
|
end
|
|
1015
1115
|
if modifier == '*'
|
|
1116
|
+
# The ; ? and & members match the same strings, in the same
|
|
1117
|
+
# order of preference, as 2.9.0's members joined by an optional
|
|
1118
|
+
# joiner, but each character can only belong to one member.
|
|
1016
1119
|
seg = case operator
|
|
1017
1120
|
when '+', '#' then "#{RESERVED_NO_COMMA}*+"
|
|
1121
|
+
when ';' then "(?:#{UNRESERVED}|=)*"
|
|
1122
|
+
when '?', '&'
|
|
1123
|
+
"#{UNRESERVED}*=(?:#{UNRESERVED}*=)*#{UNRESERVED}*?"
|
|
1018
1124
|
else group
|
|
1019
1125
|
end
|
|
1020
|
-
|
|
1126
|
+
# The joiner between exploded members must be mandatory. With
|
|
1127
|
+
# an optional joiner, a run of members can be partitioned in
|
|
1128
|
+
# exponentially many ways, which backtracks catastrophically on
|
|
1129
|
+
# engines without match memoization (MRI < 3.2, JRuby,
|
|
1130
|
+
# TruffleRuby).
|
|
1131
|
+
value = "#{seg}(?:#{joiner}#{seg})*"
|
|
1132
|
+
end
|
|
1133
|
+
if plain && first_explode && index > first_explode &&
|
|
1134
|
+
(operator == '+' || operator == '#' || operator == ';')
|
|
1135
|
+
# Exploded values were tried longest first, so whatever this
|
|
1136
|
+
# variable could match after the first exploded one, that one
|
|
1137
|
+
# already tried matching itself. Only the last unexploded + or
|
|
1138
|
+
# # variable can stop inside a member, where an exploded
|
|
1139
|
+
# variable can't. As in 2.9.0, a variable is the last one if
|
|
1140
|
+
# its varspec is the same as the last varspec.
|
|
1141
|
+
if operator == ';'
|
|
1142
|
+
"(?<#{name}>)"
|
|
1143
|
+
elsif modifier != '*' && varspec == varspecs.last
|
|
1144
|
+
"(?<#{name}>#{RESERVED_NO_COMMA}*?)?"
|
|
1145
|
+
else
|
|
1146
|
+
# 2.9.0 skipped the variable when it couldn't be empty.
|
|
1147
|
+
"(?:(?<#{name}>)(?!#{RESERVED_NO_COMMA}))?"
|
|
1148
|
+
end
|
|
1149
|
+
elsif plain && modifier == '*' && explodes[(index + 1)..-1].any? &&
|
|
1150
|
+
(operator.nil? || operator == '/' || operator == '.')
|
|
1151
|
+
# Exploded values were tried shortest first, and a later
|
|
1152
|
+
# exploded variable can match whatever this one leaves, so once
|
|
1153
|
+
# a split fails, every split where this variable is longer
|
|
1154
|
+
# fails too. Of the values tried, only the leading joiners are
|
|
1155
|
+
# shorter than every value tried before them.
|
|
1156
|
+
"(?<#{name}>(?:#{joiner})*)"
|
|
1157
|
+
elsif plain && index > 0 && explodes[index - 1] &&
|
|
1158
|
+
(operator == '?' || operator == '&')
|
|
1159
|
+
# Whatever this variable could match after an exploded one,
|
|
1160
|
+
# that one already tried matching itself, unless it couldn't:
|
|
1161
|
+
# an exploded member can't be empty, so this variable may only
|
|
1162
|
+
# start right after an empty one.
|
|
1163
|
+
"(?<#{name}>(?<=#{leader}#{joiner}|#{joiner}#{joiner})" \
|
|
1164
|
+
"#{value || group})?"
|
|
1165
|
+
elsif operator == ';' && plain && index > 0
|
|
1166
|
+
# Values were tried longest first, and splitting an earlier
|
|
1167
|
+
# variable's run of characters with this one never gave a
|
|
1168
|
+
# different result, so this one may only start after that run.
|
|
1169
|
+
"(?<#{name}>(?:#{not_after_value}#{value || group})?)"
|
|
1170
|
+
elsif modifier == '*'
|
|
1171
|
+
"(?<#{name}>#{value})?"
|
|
1172
|
+
elsif varspecs.size > 1 && (operator == '+' || operator == '#') &&
|
|
1173
|
+
varspec != varspecs.last
|
|
1174
|
+
"(?<#{name}>#{RESERVED_NO_COMMA}*+)?"
|
|
1175
|
+
elsif plain && index < varspecs.size - 1 &&
|
|
1176
|
+
(operator.nil? || operator == '/' || operator == '.')
|
|
1177
|
+
# Values were tried shortest first, so a run of characters
|
|
1178
|
+
# without a joiner went to a later variable. The only other
|
|
1179
|
+
# value that could change the result is the whole run up to the
|
|
1180
|
+
# next joiner.
|
|
1181
|
+
"(?<#{name}>(?:#{group}(?=#{joiner}))??)"
|
|
1021
1182
|
else
|
|
1022
1183
|
"(?<#{name}>#{group})?"
|
|
1023
1184
|
end
|
|
1024
1185
|
end
|
|
1025
|
-
end
|
|
1186
|
+
end
|
|
1187
|
+
following, comma_follows = followers[index]
|
|
1188
|
+
single = varspecs.size == 1 && varspecs.first[VARSPEC, 2] != '*'
|
|
1189
|
+
if plain_processor && following &&
|
|
1190
|
+
absorbed_by?(following, operator, varspecs)
|
|
1191
|
+
# Each expression tried its empty alternative first. When the next
|
|
1192
|
+
# one follows it directly and can match anything it could, followed
|
|
1193
|
+
# by anything the next one could match on its own, every other
|
|
1194
|
+
# choice ends where the empty one can also end, so the empty
|
|
1195
|
+
# alternative always gave the first match.
|
|
1196
|
+
leader = "(?!)#{leader}"
|
|
1197
|
+
combined = combined.join("#{joiner}?")
|
|
1198
|
+
elsif plain_processor && single && following &&
|
|
1199
|
+
following[1].nil? && following[2] =~ VARSPEC && $2 != '*' &&
|
|
1200
|
+
(operator == '+' || operator == '#')
|
|
1201
|
+
# Values were tried shortest first, and the next expression can
|
|
1202
|
+
# match any unreserved characters, so the value only needs to end
|
|
1203
|
+
# after a character that the next expression couldn't match.
|
|
1204
|
+
combined = "(?<#{varspecs.first[VARSPEC, 1]}>" \
|
|
1205
|
+
"#{RESERVED}*?#{not_after_value})?"
|
|
1206
|
+
elsif plain && varspecs.size > 1 &&
|
|
1207
|
+
(operator == '+' || operator == '#') &&
|
|
1208
|
+
!comma_follows &&
|
|
1209
|
+
varspecs.none? { |varspec| varspec[VARSPEC, 2] == '*' } &&
|
|
1210
|
+
!varspecs[0...-1].include?(varspecs.last)
|
|
1211
|
+
# 2.9.0 tried each value with the joiner after it first, then the
|
|
1212
|
+
# value on its own, then skipping the variable, and every other
|
|
1213
|
+
# choice gave a value to a later variable that an earlier one had
|
|
1214
|
+
# already been tried with. A variable after a skipped one is
|
|
1215
|
+
# skipped too. Stopping before a joiner is left out, so this is
|
|
1216
|
+
# only used when no joiner can follow the expression. As in 2.9.0,
|
|
1217
|
+
# a variable is the last one if its varspec is the same as the
|
|
1218
|
+
# last varspec.
|
|
1219
|
+
nested = varspecs[0...-1].reverse.inject("") do |inner, varspec|
|
|
1220
|
+
"(?:(?<#{varspec[VARSPEC, 1]}>#{RESERVED_NO_COMMA}*+)" \
|
|
1221
|
+
"#{joiner}?+#{inner}|(?=#{RESERVED_NO_COMMA}))"
|
|
1222
|
+
end
|
|
1223
|
+
combined = nested + combined.last
|
|
1224
|
+
else
|
|
1225
|
+
combined = combined.join("#{joiner}?")
|
|
1226
|
+
end
|
|
1026
1227
|
"(?:|#{leader}#{combined})"
|
|
1027
1228
|
end
|
|
1028
1229
|
|
data/lib/addressable/uri.rb
CHANGED
|
@@ -1345,6 +1345,10 @@ module Addressable
|
|
|
1345
1345
|
raise InvalidURIError, 'An origin cannot omit the host.'
|
|
1346
1346
|
end
|
|
1347
1347
|
new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
|
|
1348
|
+
# An empty port is the same as no port (Section 3.2.3 of RFC 3986).
|
|
1349
|
+
if new_port == EMPTY_STR && new_origin =~ /\A[^:\/?#]+:\/\/[^\/?#:@\[\]]+:\z/
|
|
1350
|
+
new_port = nil
|
|
1351
|
+
end
|
|
1348
1352
|
end
|
|
1349
1353
|
|
|
1350
1354
|
self.scheme = new_scheme
|
|
@@ -2126,6 +2130,11 @@ module Addressable
|
|
|
2126
2130
|
if components[:host] != nil
|
|
2127
2131
|
components[:scheme] = normalized_self.scheme
|
|
2128
2132
|
end
|
|
2133
|
+
# A first segment containing a colon would be read as a scheme.
|
|
2134
|
+
if components[:scheme].nil? && components[:path] &&
|
|
2135
|
+
components[:path][/\A[^\/]*/].include?(":")
|
|
2136
|
+
components[:path] = "./" + components[:path]
|
|
2137
|
+
end
|
|
2129
2138
|
return Addressable::URI.new(
|
|
2130
2139
|
:scheme => components[:scheme],
|
|
2131
2140
|
:user => components[:user],
|
|
@@ -2200,7 +2209,7 @@ module Addressable
|
|
|
2200
2209
|
# @return [Addressable::URI] A URI suitable for display purposes.
|
|
2201
2210
|
def display_uri
|
|
2202
2211
|
display_uri = self.normalize
|
|
2203
|
-
display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host)
|
|
2212
|
+
display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host) if display_uri.host
|
|
2204
2213
|
return display_uri
|
|
2205
2214
|
end
|
|
2206
2215
|
|
|
@@ -2394,13 +2403,14 @@ module Addressable
|
|
|
2394
2403
|
# @param [Proc] block
|
|
2395
2404
|
# A set of operations to perform on a given URI.
|
|
2396
2405
|
def defer_validation
|
|
2406
|
+
validation_was_deferred = !!@validation_deferred
|
|
2397
2407
|
raise LocalJumpError, "No block given." unless block_given?
|
|
2398
2408
|
@validation_deferred = true
|
|
2399
2409
|
yield
|
|
2400
|
-
@validation_deferred =
|
|
2401
|
-
validate
|
|
2410
|
+
@validation_deferred = validation_was_deferred
|
|
2411
|
+
validate unless validation_was_deferred
|
|
2402
2412
|
ensure
|
|
2403
|
-
@validation_deferred =
|
|
2413
|
+
@validation_deferred = validation_was_deferred
|
|
2404
2414
|
end
|
|
2405
2415
|
|
|
2406
2416
|
def encode_with(coder)
|
|
@@ -2426,10 +2436,14 @@ module Addressable
|
|
|
2426
2436
|
SELF_REF = '.'
|
|
2427
2437
|
PARENT = '..'
|
|
2428
2438
|
|
|
2439
|
+
# Deprecated. No longer used by normalize_path; will be removed in 2.10.0.
|
|
2429
2440
|
RULE_2A = /\/\.\/|\/\.$/
|
|
2430
2441
|
RULE_2B_2C = /\/([^\/]*)\/\.\.\/|\/([^\/]*)\/\.\.$/
|
|
2431
2442
|
RULE_2D = /^\.\.?\/?/
|
|
2432
2443
|
RULE_PREFIXED_PARENT = /^\/\.\.?\/|^(\/\.\.?)+\/?$/
|
|
2444
|
+
if respond_to?(:deprecate_constant, true)
|
|
2445
|
+
deprecate_constant :RULE_2A, :RULE_2B_2C, :RULE_2D, :RULE_PREFIXED_PARENT
|
|
2446
|
+
end
|
|
2433
2447
|
|
|
2434
2448
|
##
|
|
2435
2449
|
# Resolves paths to their simplest form.
|
|
@@ -2438,37 +2452,52 @@ module Addressable
|
|
|
2438
2452
|
#
|
|
2439
2453
|
# @return [String] The normalized path.
|
|
2440
2454
|
def self.normalize_path(path)
|
|
2441
|
-
# Section 5.2.4 of RFC 3986
|
|
2455
|
+
# Section 5.2.4 of RFC 3986, as a single pass over the segments so that
|
|
2456
|
+
# the running time is linear in the length of the path.
|
|
2442
2457
|
|
|
2443
2458
|
return if path.nil?
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2459
|
+
segments = path.split(SLASH, -1)
|
|
2460
|
+
return path.dup if segments.empty?
|
|
2461
|
+
|
|
2462
|
+
# The first segment is never removed by a parent reference. For an
|
|
2463
|
+
# absolute path it is the empty segment before the leading slash.
|
|
2464
|
+
output = [segments.first]
|
|
2465
|
+
last = segments.size - 1
|
|
2466
|
+
(1..last).each do |index|
|
|
2467
|
+
segment = segments[index]
|
|
2468
|
+
if segment == SELF_REF
|
|
2469
|
+
output << EMPTY_STR if index == last
|
|
2470
|
+
elsif segment == PARENT
|
|
2471
|
+
if output.size > 1
|
|
2472
|
+
output.pop
|
|
2473
|
+
output << EMPTY_STR if index == last
|
|
2474
|
+
else
|
|
2475
|
+
output << PARENT
|
|
2476
|
+
end
|
|
2452
2477
|
else
|
|
2453
|
-
|
|
2454
|
-
current = nil
|
|
2478
|
+
output << segment
|
|
2455
2479
|
end
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2480
|
+
end
|
|
2481
|
+
normalized_path = output.join(SLASH)
|
|
2482
|
+
|
|
2483
|
+
# Non-standard, removes prefixed dotted segments from path. At this
|
|
2484
|
+
# point an absolute path can only start with a single "/..", and a
|
|
2485
|
+
# relative path keeps its leading dots.
|
|
2486
|
+
start = 0
|
|
2487
|
+
if normalized_path.start_with?(SLASH)
|
|
2488
|
+
if normalized_path == "/#{PARENT}"
|
|
2489
|
+
return normalized_path[0, 1]
|
|
2490
|
+
elsif normalized_path.start_with?("/#{PARENT}/")
|
|
2491
|
+
start = PARENT.size + 1
|
|
2492
|
+
end
|
|
2493
|
+
else
|
|
2494
|
+
while normalized_path[start] == SELF_REF
|
|
2495
|
+
start += 1
|
|
2496
|
+
start += 1 if normalized_path[start] == SELF_REF
|
|
2497
|
+
start += 1 if normalized_path[start] == SLASH
|
|
2463
2498
|
end
|
|
2464
|
-
|
|
2465
|
-
mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
|
|
2466
|
-
# Non-standard, removes prefixed dotted segments from path.
|
|
2467
|
-
mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
|
|
2468
|
-
break if mod.nil?
|
|
2469
2499
|
end
|
|
2470
|
-
|
|
2471
|
-
normalized_path
|
|
2500
|
+
start.zero? ? normalized_path : normalized_path[start..-1]
|
|
2472
2501
|
end
|
|
2473
2502
|
|
|
2474
2503
|
##
|
data/lib/addressable/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: addressable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bob Aman
|
|
@@ -53,7 +53,7 @@ homepage: https://github.com/sporkmonger/addressable
|
|
|
53
53
|
licenses:
|
|
54
54
|
- Apache-2.0
|
|
55
55
|
metadata:
|
|
56
|
-
changelog_uri: https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md#v2.
|
|
56
|
+
changelog_uri: https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md#v2.9.1
|
|
57
57
|
rdoc_options:
|
|
58
58
|
- "--main"
|
|
59
59
|
- README.md
|
|
@@ -70,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
70
70
|
- !ruby/object:Gem::Version
|
|
71
71
|
version: '0'
|
|
72
72
|
requirements: []
|
|
73
|
-
rubygems_version: 4.0.
|
|
73
|
+
rubygems_version: 4.0.10
|
|
74
74
|
specification_version: 4
|
|
75
75
|
summary: URI Implementation
|
|
76
76
|
test_files: []
|