addressable 2.9.0 → 2.9.2
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 +97 -0
- data/lib/addressable/idna/native.rb +11 -1
- data/lib/addressable/idna/pure.rb +133 -84
- data/lib/addressable/template.rb +206 -10
- data/lib/addressable/uri.rb +84 -36
- data/lib/addressable/version.rb +1 -1
- 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: 322c647045515735a4adbd9c7681c7ab0c29b0cf1df4b9893ec5eb7a4c357bba
|
|
4
|
+
data.tar.gz: 1ddb9d2653a52dc98833f1ff28a849102619c16c752a92b8a6f3a6f1d67ab7c1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 360a539e633029a58bad0b5173fdaee569c40f2d399dc51254f450dc4cf09f0b1805b2cad7033fd56ade0bf698376dc3be1b737ce0fa19f54a7a11ed6c3cfb0c
|
|
7
|
+
data.tar.gz: 9ee58aee4c18fb18a1516ac3bb3c85c1bc75804a8720f1719fb1fc66895b0fbaf84bd689b83ec850a52693c3976532bd94a13bdbf7f97fc5c583b2f2cb0d847d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,102 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Addressable 2.9.2 <a name="v2.9.2">
|
|
4
|
+
- fixes `Addressable::URI` returning stale values after a component was
|
|
5
|
+
changed: e.g. after `host=`, `#site`, `#normalized_site`,
|
|
6
|
+
`#normalized_authority` and `#normalize` could still use the old host, and
|
|
7
|
+
after `scheme=`, `#normalized_port` could keep a port that's the new
|
|
8
|
+
scheme's default (#613)
|
|
9
|
+
- fixes `Addressable::URI#normalized_query` reusing the result of an earlier
|
|
10
|
+
call with different flags: e.g. after `normalized_query(:sorted)`,
|
|
11
|
+
`normalized_query` and `#normalize` gave a sorted query, and after
|
|
12
|
+
`normalized_query`, `normalized_query(:sorted)` gave an unsorted one (#608)
|
|
13
|
+
- fixes `Addressable::Template#expand` and `#partial_expand` changing the
|
|
14
|
+
arrays given as values: e.g. `[1, :a]` became `["1", "a"]`; a frozen array
|
|
15
|
+
raised `FrozenError`, and an object that responds to `to_ary`
|
|
16
|
+
raised `TypeError` (#605)
|
|
17
|
+
- fixes `Addressable::URI#clone` sharing component strings with the original:
|
|
18
|
+
e.g. appending to the clone's `#host` also changed the original's host, but
|
|
19
|
+
not its cached normalized values; `#dup` already made a separate copy (#617)
|
|
20
|
+
- fixes the pure-Ruby `Addressable::IDNA.to_unicode` raising `ZeroDivisionError`
|
|
21
|
+
on some malformed labels, e.g. `xn--rp3=`, which also made
|
|
22
|
+
`Addressable::URI#display_uri` raise, because the Punycode decoder
|
|
23
|
+
treated punctuation as digits; such labels are now returned unchanged (#614)
|
|
24
|
+
- speeds up the pure-Ruby `Addressable::IDNA.to_unicode`, which became up to
|
|
25
|
+
25 times slower in 2.9.1 on long labels with many non-ASCII characters;
|
|
26
|
+
Punycode encoding no longer takes quadratic time (#645)
|
|
27
|
+
- fixes the "character class has duplicated range" warnings that 2.9.1 gave
|
|
28
|
+
when `Addressable::IDNA` was loaded with warnings enabled
|
|
29
|
+
- fixes the "instance variable @validation_deferred not initialized" warning
|
|
30
|
+
that 2.9.1 gave for every new `Addressable::URI` on Ruby before 3.0 with
|
|
31
|
+
warnings enabled, and a "shadowing outer local variable" warning from
|
|
32
|
+
`Addressable::Template` on Ruby 2.4 and 2.5
|
|
33
|
+
|
|
34
|
+
## Addressable 2.9.1 <a name="v2.9.1">
|
|
35
|
+
- fixes denial of service in `Addressable::URI.normalize_path`, which took
|
|
36
|
+
quadratic time on paths with many dot segments; `URI#normalize`, `#join`,
|
|
37
|
+
`#==` and `#===` were affected (GHSA-qq38-m9mg-xjj5)
|
|
38
|
+
- fixes `Addressable::URI.normalize_path` keeping segments that a `..` should
|
|
39
|
+
have removed after a `..` above the root, e.g.
|
|
40
|
+
`/../../public/x/../../secret` normalized to `/public/x/secret` instead of
|
|
41
|
+
`/secret` (GHSA-8gmh-2hhj-2hv9)
|
|
42
|
+
- fixes `Addressable::URI.normalize_path` treating a raw line feed as a
|
|
43
|
+
segment boundary, e.g. joining `..\n/secret` onto `/public/` gave
|
|
44
|
+
`/\n/secret`
|
|
45
|
+
- deprecates `Addressable::URI::RULE_2A`, `RULE_2B_2C`, `RULE_2D` and
|
|
46
|
+
`RULE_PREFIXED_PARENT`, which are no longer used; they will be removed in
|
|
47
|
+
2.10.0
|
|
48
|
+
- fixes denial of service in `Addressable::Template#match` and `#extract`,
|
|
49
|
+
which still backtracked exponentially in 2.9.0 for exploded `/`, `.`, `;`,
|
|
50
|
+
`?` and `&` expressions (e.g. `{/path*}`) on regex engines without match
|
|
51
|
+
memoization, such as JRuby, TruffleRuby and MRI before 3.2; match results
|
|
52
|
+
are unchanged (GHSA-j82w-28jv-3pmj)
|
|
53
|
+
- fixes denial of service in `Addressable::Template#match` and `#extract`,
|
|
54
|
+
which backtracked polynomially or exponentially on the same regex engines
|
|
55
|
+
for expressions with several variables (e.g. `{/owner,repo,ref}`, or
|
|
56
|
+
`{+a,b,c}` with many variables), for `;` expressions, and for adjacent
|
|
57
|
+
expressions (e.g. `{+a}{+b}{+c}` or `{+a}{b}`); match results are unchanged
|
|
58
|
+
(GHSA-9f42-57jx-vpp3)
|
|
59
|
+
- fixes denial of service in `Addressable::Template#match` and `#extract`,
|
|
60
|
+
which backtracked polynomially for expressions with several exploded
|
|
61
|
+
variables (e.g. `{+a*,b*}`), including on MRI 3.2; match results are
|
|
62
|
+
unchanged (GHSA-4w5q-wg7v-w6gp)
|
|
63
|
+
- fixes `Addressable::IDNA.to_unicode` in the pure-Ruby backend decoding
|
|
64
|
+
Punycode labels that aren't valid A-labels: labels that decode to only
|
|
65
|
+
ASCII characters (e.g. `xn--google-` decoded to `google`) and labels that
|
|
66
|
+
don't convert back to themselves; such a label is now returned unchanged,
|
|
67
|
+
as with libidn, so `Addressable::URI#display_uri` no longer shows a host
|
|
68
|
+
that differs from `#normalized_host` (GHSA-9crm-g4mf-wc5j)
|
|
69
|
+
- fixes `Addressable::IDNA.to_unicode` decoding Punycode labels with
|
|
70
|
+
characters that change how a host displays without being visible
|
|
71
|
+
themselves, such as bidi overrides and isolates (e.g. U+202E and U+2066),
|
|
72
|
+
zero width characters and control characters; such a label is now
|
|
73
|
+
returned unchanged, so `Addressable::URI#display_uri` no longer shows
|
|
74
|
+
them. The pure-Ruby backend also no longer decodes labels with other
|
|
75
|
+
characters that nameprep prohibits, as with libidn (GHSA-539f-xcgw-2gjg)
|
|
76
|
+
- fixes `Addressable::URI#route_from` and `#route_to` raising
|
|
77
|
+
`InvalidURIError` when the route's first segment contains a colon
|
|
78
|
+
(e.g. `/a:b` from `/a`); the route is now `./a:b` ([#625])
|
|
79
|
+
- fixes `Addressable::IDNA.to_unicode` raising in the pure-Ruby backend for
|
|
80
|
+
Punycode labels that are too long, overflow the decoder, or decode past
|
|
81
|
+
U+10FFFF; the label is now returned unchanged, as with libidn, so
|
|
82
|
+
`Addressable::URI#display_uri` no longer raises for such hosts ([#620])
|
|
83
|
+
- fixes `Addressable::URI#display_uri` raising `InvalidURIError` for URIs
|
|
84
|
+
without a host, such as `abc` or `mailto:user@example.com`; relative URIs
|
|
85
|
+
are also no longer displayed with an empty authority (e.g. `/path/to` was
|
|
86
|
+
displayed as `///path/to`) ([#610])
|
|
87
|
+
- fixes `Addressable::Template#match` and `#extract` raising
|
|
88
|
+
`Addressable::URI::InvalidURIError` for a string that isn't a valid URI;
|
|
89
|
+
they now return `nil`, as for any other URI that doesn't match ([#621])
|
|
90
|
+
- fixes `Addressable::URI#origin=` raising `InvalidURIError` for an origin
|
|
91
|
+
with an empty port (e.g. `http://example.com:`), which is the same as no
|
|
92
|
+
port ([#627])
|
|
93
|
+
|
|
94
|
+
[#610]: https://github.com/sporkmonger/addressable/pull/610
|
|
95
|
+
[#620]: https://github.com/sporkmonger/addressable/pull/620
|
|
96
|
+
[#621]: https://github.com/sporkmonger/addressable/pull/621
|
|
97
|
+
[#625]: https://github.com/sporkmonger/addressable/pull/625
|
|
98
|
+
[#627]: https://github.com/sporkmonger/addressable/pull/627
|
|
99
|
+
|
|
3
100
|
## Addressable 2.9.0 <a name="v2.9.0">
|
|
4
101
|
- fixes ReDoS vulnerability in Addressable::Template#match (fixes incomplete
|
|
5
102
|
remediation in 2.8.10)
|
|
@@ -51,10 +51,20 @@ 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. Format characters that are also
|
|
59
|
+
# default ignorable are left out of the second set, since Ruby warns about
|
|
60
|
+
# a duplicated range in a character class.
|
|
61
|
+
DISPLAY_CHANGING = /[\p{Cc}\p{Cf}[\p{Default_Ignorable_Code_Point}&&\P{Cf}]]/
|
|
62
|
+
|
|
54
63
|
def self.to_unicode(value)
|
|
55
64
|
value.to_s.split('.', -1).map do |segment|
|
|
56
65
|
if segment.size > 0 && segment.size < 64
|
|
57
|
-
IDN::Idna.toUnicode(segment, IDN::Idna::ALLOW_UNASSIGNED)
|
|
66
|
+
decoded = IDN::Idna.toUnicode(segment, IDN::Idna::ALLOW_UNASSIGNED)
|
|
67
|
+
decoded =~ DISPLAY_CHANGING ? segment : decoded
|
|
58
68
|
elsif segment.size >= 64
|
|
59
69
|
segment
|
|
60
70
|
else
|
|
@@ -55,6 +55,45 @@ 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. Some code points are in more than one table; they're
|
|
61
|
+
# listed only under the first, since Ruby warns about a duplicated range
|
|
62
|
+
# in a character class.
|
|
63
|
+
NAMEPREP_PROHIBITED = Regexp.new("[" + [
|
|
64
|
+
# Table C.1.2, non-ASCII space characters
|
|
65
|
+
"\u00A0\u1680\u2000-\u200B\u202F\u205F\u3000",
|
|
66
|
+
# Table C.2.2, non-ASCII control characters
|
|
67
|
+
"\u0080-\u009F\u06DD\u070F\u180E\u200C\u200D\u2028\u2029" +
|
|
68
|
+
"\u2060-\u2063\u206A-\u206F\uFEFF\uFFF9-\uFFFC\u{1D173}-\u{1D17A}",
|
|
69
|
+
# Table C.3, private use
|
|
70
|
+
"\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}",
|
|
71
|
+
# Table C.4, non-character code points
|
|
72
|
+
"\uFDD0-\uFDEF" + (0..16).map do |plane|
|
|
73
|
+
[plane * 0x10000 + 0xFFFE, 0x2D, plane * 0x10000 + 0xFFFF].pack("U*")
|
|
74
|
+
end.join,
|
|
75
|
+
# Table C.6, inappropriate for plain text (U+FFF9 to U+FFFC are in C.2.2)
|
|
76
|
+
"\uFFFD",
|
|
77
|
+
# Table C.7, inappropriate for canonical representation
|
|
78
|
+
"\u2FF0-\u2FFB",
|
|
79
|
+
# Table C.8, change display properties or are deprecated (U+206A to
|
|
80
|
+
# U+206F are in C.2.2)
|
|
81
|
+
"\u0340\u0341\u200E\u200F\u202A-\u202E",
|
|
82
|
+
# Table C.9, tagging characters
|
|
83
|
+
"\u{E0001}\u{E0020}-\u{E007F}",
|
|
84
|
+
# Table B.1, commonly mapped to nothing (U+200B is in C.1.2, and U+200C,
|
|
85
|
+
# U+200D, U+2060 and U+FEFF are in C.2.2)
|
|
86
|
+
"\u00AD\u034F\u1806\u180B-\u180D\uFE00-\uFE0F"
|
|
87
|
+
].join + "]")
|
|
88
|
+
|
|
89
|
+
# Characters that change how a label displays without being visible
|
|
90
|
+
# themselves: controls, format characters such as bidi overrides and
|
|
91
|
+
# isolates, and default ignorable code points, including those added to
|
|
92
|
+
# Unicode after the version nameprep is based on. Format characters that
|
|
93
|
+
# are also default ignorable are left out of the second set, since Ruby
|
|
94
|
+
# warns about a duplicated range in a character class.
|
|
95
|
+
DISPLAY_CHANGING = /[\p{Cc}\p{Cf}[\p{Default_Ignorable_Code_Point}&&\P{Cf}]]/
|
|
96
|
+
|
|
58
97
|
# :startdoc:
|
|
59
98
|
|
|
60
99
|
# Converts from a Unicode internationalized domain name to an ASCII
|
|
@@ -91,8 +130,22 @@ module Addressable
|
|
|
91
130
|
parts.map! do |part|
|
|
92
131
|
if part =~ /^#{ACE_PREFIX}(.+)/
|
|
93
132
|
begin
|
|
94
|
-
punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1])
|
|
95
|
-
|
|
133
|
+
decoded = punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1])
|
|
134
|
+
# A label that doesn't convert back to itself isn't a valid
|
|
135
|
+
# A-label (Section 4.2 of RFC 3490, steps 6 and 7). That includes
|
|
136
|
+
# a label that decodes to only ASCII characters. A label with a
|
|
137
|
+
# character that would change how it displays is returned
|
|
138
|
+
# unchanged too.
|
|
139
|
+
if to_ascii(decoded).downcase != part.downcase ||
|
|
140
|
+
decoded =~ NAMEPREP_PROHIBITED || decoded =~ DISPLAY_CHANGING
|
|
141
|
+
part
|
|
142
|
+
else
|
|
143
|
+
decoded
|
|
144
|
+
end
|
|
145
|
+
rescue Addressable::IDNA::PunycodeBadInput,
|
|
146
|
+
Addressable::IDNA::PunycodeBigOutput,
|
|
147
|
+
Addressable::IDNA::PunycodeOverflow,
|
|
148
|
+
RangeError, ArgumentError
|
|
96
149
|
# toUnicode is explicitly defined as never-fails by the spec
|
|
97
150
|
part
|
|
98
151
|
end
|
|
@@ -4418,50 +4471,50 @@ module Addressable
|
|
|
4418
4471
|
def self.punycode_encode(unicode)
|
|
4419
4472
|
unicode = unicode.to_s unless unicode.is_a?(String)
|
|
4420
4473
|
input = unicode.unpack("U*")
|
|
4421
|
-
output = [
|
|
4422
|
-
|
|
4423
|
-
output_length = [ACE_MAX_LENGTH]
|
|
4474
|
+
output = []
|
|
4475
|
+
max_out = ACE_MAX_LENGTH
|
|
4424
4476
|
|
|
4425
|
-
#
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4477
|
+
# The positions of each non-basic code point, and a Fenwick tree of
|
|
4478
|
+
# the positions of the code points handled so far, so that counting
|
|
4479
|
+
# the handled code points before a position doesn't take a pass over
|
|
4480
|
+
# the input.
|
|
4481
|
+
positions = {}
|
|
4482
|
+
handled = Array.new(input.size + 1, 0)
|
|
4430
4483
|
|
|
4431
4484
|
# Handle the basic code points:
|
|
4432
|
-
|
|
4433
|
-
if punycode_basic?(
|
|
4434
|
-
if max_out -
|
|
4485
|
+
input.each_with_index do |codepoint, j|
|
|
4486
|
+
if punycode_basic?(codepoint)
|
|
4487
|
+
if max_out - output.size < 2
|
|
4435
4488
|
raise PunycodeBigOutput,
|
|
4436
4489
|
"Output would exceed the space provided."
|
|
4437
4490
|
end
|
|
4438
|
-
output
|
|
4439
|
-
|
|
4491
|
+
output << codepoint
|
|
4492
|
+
handled[j + 1] = 1
|
|
4493
|
+
else
|
|
4494
|
+
(positions[codepoint] ||= []) << j
|
|
4440
4495
|
end
|
|
4441
4496
|
end
|
|
4497
|
+
(1..input.size).each do |i|
|
|
4498
|
+
parent = i + (i & -i)
|
|
4499
|
+
handled[parent] += handled[i] if parent <= input.size
|
|
4500
|
+
end
|
|
4442
4501
|
|
|
4443
|
-
h = b =
|
|
4502
|
+
h = b = output.size
|
|
4444
4503
|
|
|
4445
4504
|
# h is the number of code points that have been handled, b is the
|
|
4446
|
-
# number of basic code points, and
|
|
4447
|
-
# that have been output.
|
|
4505
|
+
# number of basic code points, and output.size is the number of
|
|
4506
|
+
# characters that have been output.
|
|
4448
4507
|
|
|
4449
|
-
if b > 0
|
|
4450
|
-
output[out] = PUNYCODE_DELIMITER
|
|
4451
|
-
out += 1
|
|
4452
|
-
end
|
|
4508
|
+
output << PUNYCODE_DELIMITER if b > 0
|
|
4453
4509
|
|
|
4454
|
-
# Main encoding loop:
|
|
4510
|
+
# Main encoding loop: handle each non-basic code point in ascending
|
|
4511
|
+
# order.
|
|
4455
4512
|
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
m = PUNYCODE_MAXINT
|
|
4461
|
-
input_length.times do |j|
|
|
4462
|
-
m = input[j] if (n...m) === input[j]
|
|
4463
|
-
end
|
|
4513
|
+
n = PUNYCODE_INITIAL_N
|
|
4514
|
+
delta = 0
|
|
4515
|
+
bias = PUNYCODE_INITIAL_BIAS
|
|
4464
4516
|
|
|
4517
|
+
positions.keys.sort.each do |m|
|
|
4465
4518
|
# Increase delta enough to advance the decoder's
|
|
4466
4519
|
# <n,i> state to <m,0>, but guard against overflow:
|
|
4467
4520
|
|
|
@@ -4471,47 +4524,56 @@ module Addressable
|
|
|
4471
4524
|
delta += (m - n) * (h + 1)
|
|
4472
4525
|
n = m
|
|
4473
4526
|
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4527
|
+
# Each occurrence of n adds the handled code points since the last
|
|
4528
|
+
# one, which RFC 3492 counts as code points less than n.
|
|
4529
|
+
handled_before_pass = h
|
|
4530
|
+
counted = 0
|
|
4531
|
+
positions[n].each do |position|
|
|
4532
|
+
before = 0
|
|
4533
|
+
i = position
|
|
4534
|
+
while i > 0
|
|
4535
|
+
before += handled[i]
|
|
4536
|
+
i -= i & -i
|
|
4482
4537
|
end
|
|
4538
|
+
delta += before - counted
|
|
4539
|
+
counted = before
|
|
4483
4540
|
|
|
4484
|
-
|
|
4485
|
-
# Represent delta as a generalized variable-length integer:
|
|
4541
|
+
# Represent delta as a generalized variable-length integer:
|
|
4486
4542
|
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
end
|
|
4493
|
-
t = (
|
|
4494
|
-
if k <= bias
|
|
4495
|
-
PUNYCODE_TMIN
|
|
4496
|
-
elsif k >= bias + PUNYCODE_TMAX
|
|
4497
|
-
PUNYCODE_TMAX
|
|
4498
|
-
else
|
|
4499
|
-
k - bias
|
|
4500
|
-
end
|
|
4501
|
-
)
|
|
4502
|
-
break if q < t
|
|
4503
|
-
output[out] =
|
|
4504
|
-
punycode_encode_digit(t + (q - t) % (PUNYCODE_BASE - t))
|
|
4505
|
-
out += 1
|
|
4506
|
-
q = (q - t) / (PUNYCODE_BASE - t)
|
|
4507
|
-
k += PUNYCODE_BASE
|
|
4543
|
+
q = delta; k = PUNYCODE_BASE
|
|
4544
|
+
while true
|
|
4545
|
+
if output.size >= max_out
|
|
4546
|
+
raise PunycodeBigOutput,
|
|
4547
|
+
"Output would exceed the space provided."
|
|
4508
4548
|
end
|
|
4549
|
+
t = (
|
|
4550
|
+
if k <= bias
|
|
4551
|
+
PUNYCODE_TMIN
|
|
4552
|
+
elsif k >= bias + PUNYCODE_TMAX
|
|
4553
|
+
PUNYCODE_TMAX
|
|
4554
|
+
else
|
|
4555
|
+
k - bias
|
|
4556
|
+
end
|
|
4557
|
+
)
|
|
4558
|
+
break if q < t
|
|
4559
|
+
output <<
|
|
4560
|
+
punycode_encode_digit(t + (q - t) % (PUNYCODE_BASE - t))
|
|
4561
|
+
q = (q - t) / (PUNYCODE_BASE - t)
|
|
4562
|
+
k += PUNYCODE_BASE
|
|
4563
|
+
end
|
|
4564
|
+
|
|
4565
|
+
output << punycode_encode_digit(q)
|
|
4566
|
+
bias = punycode_adapt(delta, h + 1, h == b)
|
|
4567
|
+
delta = 0
|
|
4568
|
+
h += 1
|
|
4569
|
+
end
|
|
4570
|
+
delta += handled_before_pass - counted
|
|
4509
4571
|
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4572
|
+
positions[n].each do |position|
|
|
4573
|
+
i = position + 1
|
|
4574
|
+
while i <= input.size
|
|
4575
|
+
handled[i] += 1
|
|
4576
|
+
i += i & -i
|
|
4515
4577
|
end
|
|
4516
4578
|
end
|
|
4517
4579
|
|
|
@@ -4519,20 +4581,7 @@ module Addressable
|
|
|
4519
4581
|
n += 1
|
|
4520
4582
|
end
|
|
4521
4583
|
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
outlen = out
|
|
4525
|
-
outlen.times do |j|
|
|
4526
|
-
c = output[j]
|
|
4527
|
-
unless c >= 0 && c <= 127
|
|
4528
|
-
raise StandardError, "Invalid output char."
|
|
4529
|
-
end
|
|
4530
|
-
unless PUNYCODE_PRINT_ASCII[c]
|
|
4531
|
-
raise PunycodeBadInput, "Input is invalid."
|
|
4532
|
-
end
|
|
4533
|
-
end
|
|
4534
|
-
|
|
4535
|
-
output[0..outlen].map { |x| x.chr }.join("").sub(/\0+\z/, "")
|
|
4584
|
+
output.pack("C*").force_encoding(Encoding::US_ASCII)
|
|
4536
4585
|
end
|
|
4537
4586
|
private_class_method :punycode_encode
|
|
4538
4587
|
|
|
@@ -4677,11 +4726,11 @@ module Addressable
|
|
|
4677
4726
|
# (for use in representing integers) in the range 0 to
|
|
4678
4727
|
# base - 1, or PUNYCODE_BASE if codepoint does not represent a value.
|
|
4679
4728
|
def self.punycode_decode_digit(codepoint)
|
|
4680
|
-
if codepoint
|
|
4729
|
+
if codepoint >= 48 && codepoint <= 57
|
|
4681
4730
|
codepoint - 22
|
|
4682
|
-
elsif codepoint
|
|
4731
|
+
elsif codepoint >= 65 && codepoint <= 90
|
|
4683
4732
|
codepoint - 65
|
|
4684
|
-
elsif codepoint
|
|
4733
|
+
elsif codepoint >= 97 && codepoint <= 122
|
|
4685
4734
|
codepoint - 97
|
|
4686
4735
|
else
|
|
4687
4736
|
PUNYCODE_BASE
|
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)
|
|
@@ -900,7 +910,7 @@ module Addressable
|
|
|
900
910
|
def normalize_value(value)
|
|
901
911
|
# Handle unicode normalization
|
|
902
912
|
if value.respond_to?(:to_ary)
|
|
903
|
-
value.to_ary.map
|
|
913
|
+
value = value.to_ary.map { |val| normalize_value(val) }
|
|
904
914
|
elsif value.kind_of?(Hash)
|
|
905
915
|
value = value.inject({}) { |acc, (k, v)|
|
|
906
916
|
acc[normalize_value(k)] = normalize_value(v)
|
|
@@ -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,20 +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(',')
|
|
991
|
-
|
|
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, position|
|
|
992
1089
|
_, name, modifier = *varspec.match(VARSPEC)
|
|
993
1090
|
|
|
994
|
-
result =
|
|
1091
|
+
result = results[position]
|
|
995
1092
|
if result
|
|
996
1093
|
"(?<#{name}>#{ result })"
|
|
997
1094
|
else
|
|
@@ -1005,7 +1102,9 @@ module Addressable
|
|
|
1005
1102
|
when '.'
|
|
1006
1103
|
"#{ UNRESERVED.gsub('\.', '') }*?"
|
|
1007
1104
|
when ';'
|
|
1008
|
-
|
|
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}*)"
|
|
1009
1108
|
when '?'
|
|
1010
1109
|
"#{ UNRESERVED }*=#{ UNRESERVED }*?"
|
|
1011
1110
|
when '&'
|
|
@@ -1014,20 +1113,117 @@ module Addressable
|
|
|
1014
1113
|
"#{ UNRESERVED }*?"
|
|
1015
1114
|
end
|
|
1016
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.
|
|
1017
1119
|
seg = case operator
|
|
1018
1120
|
when '+', '#' then "#{RESERVED_NO_COMMA}*+"
|
|
1121
|
+
when ';' then "(?:#{UNRESERVED}|=)*"
|
|
1122
|
+
when '?', '&'
|
|
1123
|
+
"#{UNRESERVED}*=(?:#{UNRESERVED}*=)*#{UNRESERVED}*?"
|
|
1019
1124
|
else group
|
|
1020
1125
|
end
|
|
1021
|
-
|
|
1022
|
-
|
|
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 && position > 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[(position + 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 && position > 0 && explodes[position - 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 && position > 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})?"
|
|
1023
1172
|
elsif varspecs.size > 1 && (operator == '+' || operator == '#') &&
|
|
1024
1173
|
varspec != varspecs.last
|
|
1025
1174
|
"(?<#{name}>#{RESERVED_NO_COMMA}*+)?"
|
|
1175
|
+
elsif plain && position < 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}))??)"
|
|
1026
1182
|
else
|
|
1027
1183
|
"(?<#{name}>#{group})?"
|
|
1028
1184
|
end
|
|
1029
1185
|
end
|
|
1030
|
-
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
|
|
1031
1227
|
"(?:|#{leader}#{combined})"
|
|
1032
1228
|
end
|
|
1033
1229
|
|
data/lib/addressable/uri.rb
CHANGED
|
@@ -928,6 +928,11 @@ module Addressable
|
|
|
928
928
|
|
|
929
929
|
# Reset dependent values
|
|
930
930
|
@normalized_scheme = NONE
|
|
931
|
+
@normalized_user = NONE
|
|
932
|
+
@normalized_password = NONE
|
|
933
|
+
@normalized_userinfo = NONE
|
|
934
|
+
@normalized_port = NONE
|
|
935
|
+
@normalized_path = nil
|
|
931
936
|
remove_composite_values
|
|
932
937
|
|
|
933
938
|
# Ensure we haven't created an invalid URI
|
|
@@ -983,6 +988,7 @@ module Addressable
|
|
|
983
988
|
@normalized_userinfo = NONE
|
|
984
989
|
@authority = nil
|
|
985
990
|
@normalized_user = NONE
|
|
991
|
+
@normalized_password = NONE
|
|
986
992
|
remove_composite_values
|
|
987
993
|
|
|
988
994
|
# Ensure we haven't created an invalid URI
|
|
@@ -1038,6 +1044,7 @@ module Addressable
|
|
|
1038
1044
|
@normalized_userinfo = NONE
|
|
1039
1045
|
@authority = nil
|
|
1040
1046
|
@normalized_password = NONE
|
|
1047
|
+
@normalized_user = NONE
|
|
1041
1048
|
remove_composite_values
|
|
1042
1049
|
|
|
1043
1050
|
# Ensure we haven't created an invalid URI
|
|
@@ -1345,6 +1352,10 @@ module Addressable
|
|
|
1345
1352
|
raise InvalidURIError, 'An origin cannot omit the host.'
|
|
1346
1353
|
end
|
|
1347
1354
|
new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
|
|
1355
|
+
# An empty port is the same as no port (Section 3.2.3 of RFC 3986).
|
|
1356
|
+
if new_port == EMPTY_STR && new_origin =~ /\A[^:\/?#]+:\/\/[^\/?#:@\[\]]+:\z/
|
|
1357
|
+
new_port = nil
|
|
1358
|
+
end
|
|
1348
1359
|
end
|
|
1349
1360
|
|
|
1350
1361
|
self.scheme = new_scheme
|
|
@@ -1612,11 +1623,8 @@ module Addressable
|
|
|
1612
1623
|
# @return [String] The query component, normalized.
|
|
1613
1624
|
def normalized_query(*flags)
|
|
1614
1625
|
return nil unless self.query
|
|
1615
|
-
return @normalized_query
|
|
1616
|
-
|
|
1617
|
-
modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
|
|
1618
|
-
# Make sure possible key-value pair delimiters are escaped.
|
|
1619
|
-
modified_query_class.sub!("\\&", "").sub!("\\;", "")
|
|
1626
|
+
return @normalized_query if flags.empty? && @normalized_query != NONE
|
|
1627
|
+
normalized_query = begin
|
|
1620
1628
|
pairs = (query || "").split("&", -1)
|
|
1621
1629
|
pairs.delete_if(&:empty?).uniq! if flags.include?(:compacted)
|
|
1622
1630
|
pairs.sort! if flags.include?(:sorted)
|
|
@@ -1630,8 +1638,9 @@ module Addressable
|
|
|
1630
1638
|
component == "" ? nil : component
|
|
1631
1639
|
end
|
|
1632
1640
|
# All normalized values should be UTF-8
|
|
1633
|
-
force_utf8_encoding_if_needed(
|
|
1634
|
-
@normalized_query
|
|
1641
|
+
force_utf8_encoding_if_needed(normalized_query)
|
|
1642
|
+
@normalized_query = normalized_query if flags.empty?
|
|
1643
|
+
normalized_query
|
|
1635
1644
|
end
|
|
1636
1645
|
|
|
1637
1646
|
##
|
|
@@ -2126,6 +2135,11 @@ module Addressable
|
|
|
2126
2135
|
if components[:host] != nil
|
|
2127
2136
|
components[:scheme] = normalized_self.scheme
|
|
2128
2137
|
end
|
|
2138
|
+
# A first segment containing a colon would be read as a scheme.
|
|
2139
|
+
if components[:scheme].nil? && components[:path] &&
|
|
2140
|
+
components[:path][/\A[^\/]*/].include?(":")
|
|
2141
|
+
components[:path] = "./" + components[:path]
|
|
2142
|
+
end
|
|
2129
2143
|
return Addressable::URI.new(
|
|
2130
2144
|
:scheme => components[:scheme],
|
|
2131
2145
|
:user => components[:user],
|
|
@@ -2200,7 +2214,7 @@ module Addressable
|
|
|
2200
2214
|
# @return [Addressable::URI] A URI suitable for display purposes.
|
|
2201
2215
|
def display_uri
|
|
2202
2216
|
display_uri = self.normalize
|
|
2203
|
-
display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host)
|
|
2217
|
+
display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host) if display_uri.host
|
|
2204
2218
|
return display_uri
|
|
2205
2219
|
end
|
|
2206
2220
|
|
|
@@ -2394,13 +2408,15 @@ module Addressable
|
|
|
2394
2408
|
# @param [Proc] block
|
|
2395
2409
|
# A set of operations to perform on a given URI.
|
|
2396
2410
|
def defer_validation
|
|
2411
|
+
validation_was_deferred =
|
|
2412
|
+
!!(defined?(@validation_deferred) && @validation_deferred)
|
|
2397
2413
|
raise LocalJumpError, "No block given." unless block_given?
|
|
2398
2414
|
@validation_deferred = true
|
|
2399
2415
|
yield
|
|
2400
|
-
@validation_deferred =
|
|
2401
|
-
validate
|
|
2416
|
+
@validation_deferred = validation_was_deferred
|
|
2417
|
+
validate unless validation_was_deferred
|
|
2402
2418
|
ensure
|
|
2403
|
-
@validation_deferred =
|
|
2419
|
+
@validation_deferred = validation_was_deferred
|
|
2404
2420
|
end
|
|
2405
2421
|
|
|
2406
2422
|
def encode_with(coder)
|
|
@@ -2426,10 +2442,14 @@ module Addressable
|
|
|
2426
2442
|
SELF_REF = '.'
|
|
2427
2443
|
PARENT = '..'
|
|
2428
2444
|
|
|
2445
|
+
# Deprecated. No longer used by normalize_path; will be removed in 2.10.0.
|
|
2429
2446
|
RULE_2A = /\/\.\/|\/\.$/
|
|
2430
2447
|
RULE_2B_2C = /\/([^\/]*)\/\.\.\/|\/([^\/]*)\/\.\.$/
|
|
2431
2448
|
RULE_2D = /^\.\.?\/?/
|
|
2432
2449
|
RULE_PREFIXED_PARENT = /^\/\.\.?\/|^(\/\.\.?)+\/?$/
|
|
2450
|
+
if respond_to?(:deprecate_constant, true)
|
|
2451
|
+
deprecate_constant :RULE_2A, :RULE_2B_2C, :RULE_2D, :RULE_PREFIXED_PARENT
|
|
2452
|
+
end
|
|
2433
2453
|
|
|
2434
2454
|
##
|
|
2435
2455
|
# Resolves paths to their simplest form.
|
|
@@ -2438,37 +2458,52 @@ module Addressable
|
|
|
2438
2458
|
#
|
|
2439
2459
|
# @return [String] The normalized path.
|
|
2440
2460
|
def self.normalize_path(path)
|
|
2441
|
-
# Section 5.2.4 of RFC 3986
|
|
2461
|
+
# Section 5.2.4 of RFC 3986, as a single pass over the segments so that
|
|
2462
|
+
# the running time is linear in the length of the path.
|
|
2442
2463
|
|
|
2443
2464
|
return if path.nil?
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2465
|
+
segments = path.split(SLASH, -1)
|
|
2466
|
+
return path.dup if segments.empty?
|
|
2467
|
+
|
|
2468
|
+
# The first segment is never removed by a parent reference. For an
|
|
2469
|
+
# absolute path it is the empty segment before the leading slash.
|
|
2470
|
+
output = [segments.first]
|
|
2471
|
+
last = segments.size - 1
|
|
2472
|
+
(1..last).each do |index|
|
|
2473
|
+
segment = segments[index]
|
|
2474
|
+
if segment == SELF_REF
|
|
2475
|
+
output << EMPTY_STR if index == last
|
|
2476
|
+
elsif segment == PARENT
|
|
2477
|
+
if output.size > 1
|
|
2478
|
+
output.pop
|
|
2479
|
+
output << EMPTY_STR if index == last
|
|
2480
|
+
else
|
|
2481
|
+
output << PARENT
|
|
2482
|
+
end
|
|
2452
2483
|
else
|
|
2453
|
-
|
|
2454
|
-
current = nil
|
|
2484
|
+
output << segment
|
|
2455
2485
|
end
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2486
|
+
end
|
|
2487
|
+
normalized_path = output.join(SLASH)
|
|
2488
|
+
|
|
2489
|
+
# Non-standard, removes prefixed dotted segments from path. At this
|
|
2490
|
+
# point an absolute path can only start with a single "/..", and a
|
|
2491
|
+
# relative path keeps its leading dots.
|
|
2492
|
+
start = 0
|
|
2493
|
+
if normalized_path.start_with?(SLASH)
|
|
2494
|
+
if normalized_path == "/#{PARENT}"
|
|
2495
|
+
return normalized_path[0, 1]
|
|
2496
|
+
elsif normalized_path.start_with?("/#{PARENT}/")
|
|
2497
|
+
start = PARENT.size + 1
|
|
2498
|
+
end
|
|
2499
|
+
else
|
|
2500
|
+
while normalized_path[start] == SELF_REF
|
|
2501
|
+
start += 1
|
|
2502
|
+
start += 1 if normalized_path[start] == SELF_REF
|
|
2503
|
+
start += 1 if normalized_path[start] == SLASH
|
|
2463
2504
|
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
2505
|
end
|
|
2470
|
-
|
|
2471
|
-
normalized_path
|
|
2506
|
+
start.zero? ? normalized_path : normalized_path[start..-1]
|
|
2472
2507
|
end
|
|
2473
2508
|
|
|
2474
2509
|
##
|
|
@@ -2552,6 +2587,9 @@ module Addressable
|
|
|
2552
2587
|
def remove_composite_values
|
|
2553
2588
|
@uri_string = nil
|
|
2554
2589
|
@hash = nil
|
|
2590
|
+
@site = nil
|
|
2591
|
+
@normalized_site = nil
|
|
2592
|
+
@normalized_authority = nil
|
|
2555
2593
|
end
|
|
2556
2594
|
|
|
2557
2595
|
##
|
|
@@ -2566,11 +2604,21 @@ module Addressable
|
|
|
2566
2604
|
|
|
2567
2605
|
private
|
|
2568
2606
|
|
|
2607
|
+
def initialize_copy(other)
|
|
2608
|
+
super
|
|
2609
|
+
instance_variables.each do |ivar|
|
|
2610
|
+
value = instance_variable_get(ivar)
|
|
2611
|
+
instance_variable_set(ivar, value.dup) if value.kind_of?(String)
|
|
2612
|
+
end
|
|
2613
|
+
end
|
|
2614
|
+
|
|
2569
2615
|
##
|
|
2570
2616
|
# Resets instance variables
|
|
2571
2617
|
#
|
|
2572
2618
|
# @api private
|
|
2573
2619
|
def reset_ivs
|
|
2620
|
+
@site = nil
|
|
2621
|
+
@normalized_site = nil
|
|
2574
2622
|
@scheme = nil
|
|
2575
2623
|
@user = nil
|
|
2576
2624
|
@normalized_scheme = NONE
|
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.9.
|
|
4
|
+
version: 2.9.2
|
|
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.9.
|
|
56
|
+
changelog_uri: https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md#v2.9.2
|
|
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.20
|
|
74
74
|
specification_version: 4
|
|
75
75
|
summary: URI Implementation
|
|
76
76
|
test_files: []
|