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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ffaab8b78df30a2126058a425f168d76b26bc33e60849cabc1a6beabae24464d
4
- data.tar.gz: 841ab2bd18fbcf3ff746cb85ea1661e628e278e6c7bc5ad96cd480e36a54f067
3
+ metadata.gz: 322c647045515735a4adbd9c7681c7ab0c29b0cf1df4b9893ec5eb7a4c357bba
4
+ data.tar.gz: 1ddb9d2653a52dc98833f1ff28a849102619c16c752a92b8a6f3a6f1d67ab7c1
5
5
  SHA512:
6
- metadata.gz: f5884313cb2c68ea73d25e8f7f0a76200030bb3fb7abad8b31532180b1ae3c1df10f8db614a273dba115c87860682d227fdf046c743370e020a577cae667d026
7
- data.tar.gz: c97dd91446991ce20400e9b4c2586aefbc1dca9fd1d49efca9606fc505ff0d49807686ab1fdfd7d4dba447c9c4bf8fb6c0bbd5a1d3d69a4952207d035e912dc2
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
- rescue Addressable::IDNA::PunycodeBadInput
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 = [0] * (ACE_MAX_LENGTH + 1)
4422
- input_length = input.size
4423
- output_length = [ACE_MAX_LENGTH]
4474
+ output = []
4475
+ max_out = ACE_MAX_LENGTH
4424
4476
 
4425
- # Initialize the state
4426
- n = PUNYCODE_INITIAL_N
4427
- delta = out = 0
4428
- max_out = output_length[0]
4429
- bias = PUNYCODE_INITIAL_BIAS
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
- input_length.times do |j|
4433
- if punycode_basic?(input[j])
4434
- if max_out - out < 2
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[out] = input[j]
4439
- out += 1
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 = out
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 out is the number of characters
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
- while h < input_length
4457
- # All non-basic code points < n have been
4458
- # handled already. Find the next larger one:
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
- input_length.times do |j|
4475
- # Punycode does not need to check whether input[j] is basic:
4476
- if input[j] < n
4477
- delta += 1
4478
- if delta == 0
4479
- raise PunycodeOverflow,
4480
- "Input needs wider integers to process."
4481
- end
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
- if input[j] == n
4485
- # Represent delta as a generalized variable-length integer:
4541
+ # Represent delta as a generalized variable-length integer:
4486
4542
 
4487
- q = delta; k = PUNYCODE_BASE
4488
- while true
4489
- if out >= max_out
4490
- raise PunycodeBigOutput,
4491
- "Output would exceed the space provided."
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
- output[out] = punycode_encode_digit(q)
4511
- out += 1
4512
- bias = punycode_adapt(delta, h + 1, h == b)
4513
- delta = 0
4514
- h += 1
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
- output_length[0] = out
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 - 48 < 10
4729
+ if codepoint >= 48 && codepoint <= 57
4681
4730
  codepoint - 22
4682
- elsif codepoint - 65 < 26
4731
+ elsif codepoint >= 65 && codepoint <= 90
4683
4732
  codepoint - 65
4684
- elsif codepoint - 97 < 26
4733
+ elsif codepoint >= 97 && codepoint <= 122
4685
4734
  codepoint - 97
4686
4735
  else
4687
4736
  PUNYCODE_BASE
@@ -413,15 +413,25 @@ module Addressable
413
413
  # match.captures
414
414
  # #=> ["a", ["b", "c"]]
415
415
  def match(uri, processor=nil)
416
- uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
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
- return nil unless uri.to_str.match(expansion_regexp)
424
- unparsed_values = uri.to_str.scan(expansion_regexp).flatten
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! { |val| normalize_value(val) }
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
- combined = varspecs.map do |varspec|
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 = processor && processor.respond_to?(:match) ? processor.match(name) : nil
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
- "#{ UNRESERVED }*=?#{ UNRESERVED }*?"
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
- joiner_pattern = operator.nil? ? joiner : "#{joiner}?"
1022
- "(?<#{name}>#{seg}(?:#{joiner_pattern}#{seg})*)?"
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.join("#{joiner}?")
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
 
@@ -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 unless @normalized_query == NONE
1616
- @normalized_query = begin
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(@normalized_query)
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 = false
2401
- validate
2416
+ @validation_deferred = validation_was_deferred
2417
+ validate unless validation_was_deferred
2402
2418
  ensure
2403
- @validation_deferred = false
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
- normalized_path = path.dup
2445
- loop do
2446
- mod ||= normalized_path.gsub!(RULE_2A, SLASH)
2447
-
2448
- pair = normalized_path.match(RULE_2B_2C)
2449
- if pair
2450
- parent = pair[1]
2451
- current = pair[2]
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
- parent = nil
2454
- current = nil
2484
+ output << segment
2455
2485
  end
2456
-
2457
- regexp = "/#{Regexp.escape(parent.to_s)}/\\.\\./|"
2458
- regexp += "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
2459
-
2460
- if pair && ((parent != SELF_REF && parent != PARENT) ||
2461
- (current != SELF_REF && current != PARENT))
2462
- mod ||= normalized_path.gsub!(Regexp.new(regexp), SLASH)
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
@@ -23,7 +23,7 @@ if !defined?(Addressable::VERSION)
23
23
  module VERSION
24
24
  MAJOR = 2
25
25
  MINOR = 9
26
- TINY = 0
26
+ TINY = 2
27
27
 
28
28
  STRING = [MAJOR, MINOR, TINY].join('.')
29
29
  end
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.0
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.0
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.6
73
+ rubygems_version: 4.0.20
74
74
  specification_version: 4
75
75
  summary: URI Implementation
76
76
  test_files: []