protocol-url 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/url/path.rb +76 -3
- data/lib/protocol/url/relative.rb +9 -4
- data/lib/protocol/url/version.rb +1 -1
- data/readme.md +49 -0
- data/releases.md +4 -0
- data.tar.gz.sig +1 -3
- metadata +1 -1
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a8be4027ad2b93f7707396fe9a53a47846b53e78cde0269be4fdd3dbdb448e3
|
|
4
|
+
data.tar.gz: 89ae574fc48ca43bcd0b224bdbe91ab0da1354869f6c8dbc87bdfbe9e6cd45b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb21e1c877b41f75975183cacab2a9dbea71e09c5fa4e11f66767f713e50fe0c27b63f07b1daa83feb00309a41132703544620541c794c598ce1efc9c402a830
|
|
7
|
+
data.tar.gz: 7c0703c48866182719b0a12edee9b45447f2ce3233519058af33c171ddf729fa9f8615cb76ae0aaf8355383703e21937fa912e94eff05428c6007d62c4e763cd
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/lib/protocol/url/path.rb
CHANGED
|
@@ -20,7 +20,8 @@ module Protocol
|
|
|
20
20
|
|
|
21
21
|
EMPTY_SEGMENTS = [].freeze
|
|
22
22
|
ROOT_SEGMENTS = ["", ""].freeze
|
|
23
|
-
|
|
23
|
+
NORMALIZATION_PATTERN = /%[0-9A-Fa-f]{2}|%|[^a-zA-Z0-9_.~!$&'()*+,;=:@-]/
|
|
24
|
+
private_constant :EMPTY_SEGMENTS, :ROOT_SEGMENTS, :NORMALIZATION_PATTERN
|
|
24
25
|
|
|
25
26
|
# Coerce an encoded string or encoded segment array into a path.
|
|
26
27
|
#
|
|
@@ -252,6 +253,42 @@ module Protocol
|
|
|
252
253
|
alias to_s encoded
|
|
253
254
|
alias to_str encoded
|
|
254
255
|
|
|
256
|
+
# Normalize the encoded spelling of this path.
|
|
257
|
+
#
|
|
258
|
+
# Percent-encoded unreserved characters are decoded, retained percent escapes
|
|
259
|
+
# use uppercase hexadecimal digits, and literal characters outside the path
|
|
260
|
+
# segment grammar are percent encoded. Reserved characters retain their
|
|
261
|
+
# encoded or literal form because those forms are not generally equivalent.
|
|
262
|
+
#
|
|
263
|
+
# This operation preserves the path structure. Use {simplify} separately when
|
|
264
|
+
# application semantics permit resolving dot segments or collapsing repeated separators.
|
|
265
|
+
#
|
|
266
|
+
# @returns [Path] The normalized path, or this path if already normalized.
|
|
267
|
+
# @raises [ArgumentError] If the path contains malformed percent encoding, NUL, or invalid string encoding.
|
|
268
|
+
def normalize
|
|
269
|
+
encoded = self.encoded
|
|
270
|
+
unless encoded.valid_encoding? && encoded.encoding.ascii_compatible?
|
|
271
|
+
raise ArgumentError, "Path segment has invalid encoding!"
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
segments = self.segments
|
|
275
|
+
normalized_segments = nil
|
|
276
|
+
|
|
277
|
+
segments.each_with_index do |segment, index|
|
|
278
|
+
next unless NORMALIZATION_PATTERN.match?(segment)
|
|
279
|
+
|
|
280
|
+
normalized = normalize_segment(segment)
|
|
281
|
+
next if normalized == segment
|
|
282
|
+
|
|
283
|
+
normalized_segments ||= segments.dup
|
|
284
|
+
normalized_segments[index] = normalized
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
return self unless normalized_segments
|
|
288
|
+
|
|
289
|
+
return self.class.new(nil, normalized_segments)
|
|
290
|
+
end
|
|
291
|
+
|
|
255
292
|
# Simplify this path in place by resolving literal or percent-encoded dot segments and repeated separators.
|
|
256
293
|
#
|
|
257
294
|
# @returns [Path | Nil] This path when changed, otherwise `nil`.
|
|
@@ -342,6 +379,42 @@ module Protocol
|
|
|
342
379
|
|
|
343
380
|
private
|
|
344
381
|
|
|
382
|
+
# Normalize one encoded path segment:
|
|
383
|
+
def normalize_segment(segment)
|
|
384
|
+
return segment.gsub(NORMALIZATION_PATTERN) do |character|
|
|
385
|
+
byte = character.getbyte(0)
|
|
386
|
+
|
|
387
|
+
if byte == 0
|
|
388
|
+
raise ArgumentError, "Path segment contains NUL!"
|
|
389
|
+
elsif byte == 0x25
|
|
390
|
+
if character.bytesize == 1
|
|
391
|
+
raise ArgumentError, "String contains malformed percent encoding!"
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
byte = character.byteslice(1, 2).to_i(16)
|
|
395
|
+
if byte == 0
|
|
396
|
+
raise ArgumentError, "Path segment contains NUL!"
|
|
397
|
+
elsif unreserved_byte?(byte)
|
|
398
|
+
byte.chr
|
|
399
|
+
else
|
|
400
|
+
character.upcase
|
|
401
|
+
end
|
|
402
|
+
else
|
|
403
|
+
Encoding.escape(character)
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Whether the byte represents an unreserved URI character:
|
|
409
|
+
def unreserved_byte?(byte)
|
|
410
|
+
case byte
|
|
411
|
+
when 0x30..0x39, 0x41..0x5A, 0x61..0x7A, 0x2D, 0x2E, 0x5F, 0x7E
|
|
412
|
+
return true
|
|
413
|
+
else
|
|
414
|
+
return false
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
345
418
|
# Identify dot segments, including percent-encoded spellings. RFC 3986 treats
|
|
346
419
|
# percent-encoded unreserved characters as equivalent to their literal forms;
|
|
347
420
|
# the WHATWG URL Standard explicitly recognizes `%2e`, `.%2e`, `%2e.`, and
|
|
@@ -415,9 +488,9 @@ module Protocol
|
|
|
415
488
|
offset += 1
|
|
416
489
|
end
|
|
417
490
|
elsif segment == "" && index != last_index
|
|
418
|
-
# Collapse repeated separators
|
|
491
|
+
# Collapse repeated separators:
|
|
419
492
|
elsif dot == ".." && offset > 0 && dot_segment(segments[offset - 1]) != ".."
|
|
420
|
-
# Pop a component, but never pop the absolute-path root
|
|
493
|
+
# Pop a component, but never pop the absolute-path root:
|
|
421
494
|
offset -= 1 if segments[offset - 1] != ""
|
|
422
495
|
|
|
423
496
|
# A trailing parent reference also denotes a directory.
|
|
@@ -132,12 +132,17 @@ module Protocol
|
|
|
132
132
|
self.class.new(path || @path, query, fragment)
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
-
# Normalize the path
|
|
135
|
+
# Normalize the encoded path and simplify its structure.
|
|
136
136
|
#
|
|
137
|
-
# This modifies the URL in-place by simplifying the path component:
|
|
137
|
+
# This modifies the URL in-place by normalizing and simplifying the path component:
|
|
138
|
+
# - Decodes percent-encoded unreserved characters
|
|
139
|
+
# - Uses uppercase hexadecimal digits for retained percent escapes
|
|
138
140
|
# - Removes "." segments (current directory)
|
|
139
141
|
# - Resolves ".." segments (parent directory)
|
|
140
|
-
# - Collapses
|
|
142
|
+
# - Collapses empty path segments represented by consecutive slashes
|
|
143
|
+
#
|
|
144
|
+
# Normalization is intentionally lossy. Callers that need to preserve the
|
|
145
|
+
# original path structure should retain the parsed URL and avoid this method.
|
|
141
146
|
#
|
|
142
147
|
# @returns [self] The normalized URL.
|
|
143
148
|
#
|
|
@@ -146,7 +151,7 @@ module Protocol
|
|
|
146
151
|
# url.normalize!
|
|
147
152
|
# url.path.to_s # => "/foo/bar/qux"
|
|
148
153
|
def normalize!
|
|
149
|
-
@path = @path.simplify
|
|
154
|
+
@path = @path.normalize.simplify
|
|
150
155
|
|
|
151
156
|
return self
|
|
152
157
|
end
|
data/lib/protocol/url/version.rb
CHANGED
data/readme.md
CHANGED
|
@@ -34,6 +34,55 @@ bundle exec sus
|
|
|
34
34
|
|
|
35
35
|
Please see the [project releases](https://socketry.github.io/protocol-url/releases/index) for all releases.
|
|
36
36
|
|
|
37
|
+
### v0.13.0
|
|
38
|
+
|
|
39
|
+
- Add conservative normalization of encoded URL paths.
|
|
40
|
+
|
|
41
|
+
### v0.12.0
|
|
42
|
+
|
|
43
|
+
- Allow unfrozen relative and absolute URLs to replace their components.
|
|
44
|
+
|
|
45
|
+
### v0.10.0
|
|
46
|
+
|
|
47
|
+
- Rename `Protocol::URL::FormData::Parser::CONTENT_TYPE` to `MEDIA_TYPE`.
|
|
48
|
+
|
|
49
|
+
### v0.9.0
|
|
50
|
+
|
|
51
|
+
- Add `Protocol::URL::LimitError` for configured processing limits.
|
|
52
|
+
|
|
53
|
+
### v0.8.0
|
|
54
|
+
|
|
55
|
+
- Use consistent limit naming for form data parser constraints.
|
|
56
|
+
|
|
57
|
+
### v0.7.0
|
|
58
|
+
|
|
59
|
+
- Allow `Protocol::URL::FormData::Parser#parse` to populate a supplied result object.
|
|
60
|
+
|
|
61
|
+
### v0.6.0
|
|
62
|
+
|
|
63
|
+
- Add `Protocol::URL::FormData::Parser` for incremental, limited parsing of `application/x-www-form-urlencoded` form data.
|
|
64
|
+
- Add `Protocol::URL::FormData::Nested` for consistently building nested form data while preserving absent and empty values.
|
|
65
|
+
|
|
66
|
+
### v0.5.0
|
|
67
|
+
|
|
68
|
+
- Add `Protocol::URL::Encoding.decode_www_form` for decoding HTML form data where `+` represents a space.
|
|
69
|
+
|
|
70
|
+
### v0.4.0
|
|
71
|
+
|
|
72
|
+
- Add comparison methods to `Protocol::URL::Relative` (and by inheritance to `Protocol::URL::Absolute`):
|
|
73
|
+
- `#==` for structural equality comparison (compares path, query, fragment components).
|
|
74
|
+
- `#===` for string equality comparison (enables case statement matching).
|
|
75
|
+
- `#<=>` for ordering and sorting.
|
|
76
|
+
- `#hash` for hash key support.
|
|
77
|
+
- `#equal?` for component-based equality checking.
|
|
78
|
+
- Add JSON serialization support to `Protocol::URL::Relative`:
|
|
79
|
+
- `#as_json` returns the string representation.
|
|
80
|
+
- `#to_json` returns a JSON-encoded string.
|
|
81
|
+
|
|
82
|
+
### v0.3.0
|
|
83
|
+
|
|
84
|
+
- Add `relative(target, from)` for computing relative paths between URLs.
|
|
85
|
+
|
|
37
86
|
### v0.12.0
|
|
38
87
|
|
|
39
88
|
- Allow unfrozen relative and absolute URLs to replace their components.
|
data/releases.md
CHANGED
data.tar.gz.sig
CHANGED
|
@@ -1,3 +1 @@
|
|
|
1
|
-
|
|
2
|
-
X�{2�� �D�8���pj�(���h�_����j����E��= �����H�y�q�3�E1�?�c�.{-�R�ͻ�/�8�9�ͣ��j*�~o���3��,�t����à���)F���C�[j��GD��B����<r��ோ�\xGyM�)`?>1�����~W��1B���Dvօ��Q��)����
|
|
3
|
-
�'�M���$Hl�*d������ר��ly���h�.��4�\f�Ŭ�@X�<�9Y��H55Cv�����FU'���Hb~M�hd�u��QJ�G���������,D�Q�~Lٜ�TX�;HI�_�]��
|
|
1
|
+
�ɑ��2v�~�lK
|
metadata
CHANGED
metadata.gz.sig
CHANGED
|
Binary file
|