protocol-url 0.12.0 → 0.14.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 +83 -6
- data/lib/protocol/url/relative.rb +9 -4
- data/lib/protocol/url/version.rb +1 -1
- data/readme.md +98 -0
- data/releases.md +8 -0
- data.tar.gz.sig +0 -0
- 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: 9ff041406f4bc298081aaa3f7150a374bf937f87e0f2783458082c4635d51488
|
|
4
|
+
data.tar.gz: 90aa117ed86f9df29b3278420b8c3b450a8f6fc7b30cf6473e5fbf68bb939d40
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 371901582d694902f2610377663c7aca91835c3e04fde1a06854a3b2a67456bff313d71a347686e3d7e80b1dc35cd0103379fe0abf28c6050967534f0d3ae888
|
|
7
|
+
data.tar.gz: 8446857f07d8aa19317a1ce85e067b623df958e2a56b4df9b9110d52de1cc4eb70115d1af6ed068c5cbb9e2d78ebefd02d406ab551e36258e8acfb99276c9694
|
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
|
#
|
|
@@ -207,14 +208,18 @@ module Protocol
|
|
|
207
208
|
end
|
|
208
209
|
|
|
209
210
|
# @parameter other [Object] The value to compare with this path.
|
|
210
|
-
# @returns [Boolean] Whether both
|
|
211
|
+
# @returns [Boolean] Whether both values are the same.
|
|
211
212
|
def ==(other)
|
|
212
|
-
|
|
213
|
+
if other.is_a?(String)
|
|
214
|
+
return encoded == other
|
|
215
|
+
else
|
|
216
|
+
return eql?(other)
|
|
217
|
+
end
|
|
213
218
|
end
|
|
214
219
|
|
|
215
220
|
# Compare this path with another path using exact encoded string identity.
|
|
216
221
|
# @parameter other [Object] The value to compare with this path.
|
|
217
|
-
# @returns [Boolean] Whether both paths
|
|
222
|
+
# @returns [Boolean] Whether both paths are the same.
|
|
218
223
|
def eql?(other)
|
|
219
224
|
other.is_a?(Path) && encoded.eql?(other.encoded)
|
|
220
225
|
end
|
|
@@ -252,6 +257,42 @@ module Protocol
|
|
|
252
257
|
alias to_s encoded
|
|
253
258
|
alias to_str encoded
|
|
254
259
|
|
|
260
|
+
# Normalize the encoded spelling of this path.
|
|
261
|
+
#
|
|
262
|
+
# Percent-encoded unreserved characters are decoded, retained percent escapes
|
|
263
|
+
# use uppercase hexadecimal digits, and literal characters outside the path
|
|
264
|
+
# segment grammar are percent encoded. Reserved characters retain their
|
|
265
|
+
# encoded or literal form because those forms are not generally equivalent.
|
|
266
|
+
#
|
|
267
|
+
# This operation preserves the path structure. Use {simplify} separately when
|
|
268
|
+
# application semantics permit resolving dot segments or collapsing repeated separators.
|
|
269
|
+
#
|
|
270
|
+
# @returns [Path] The normalized path, or this path if already normalized.
|
|
271
|
+
# @raises [ArgumentError] If the path contains malformed percent encoding, NUL, or invalid string encoding.
|
|
272
|
+
def normalize
|
|
273
|
+
encoded = self.encoded
|
|
274
|
+
unless encoded.valid_encoding? && encoded.encoding.ascii_compatible?
|
|
275
|
+
raise ArgumentError, "Path segment has invalid encoding!"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
segments = self.segments
|
|
279
|
+
normalized_segments = nil
|
|
280
|
+
|
|
281
|
+
segments.each_with_index do |segment, index|
|
|
282
|
+
next unless NORMALIZATION_PATTERN.match?(segment)
|
|
283
|
+
|
|
284
|
+
normalized = normalize_segment(segment)
|
|
285
|
+
next if normalized == segment
|
|
286
|
+
|
|
287
|
+
normalized_segments ||= segments.dup
|
|
288
|
+
normalized_segments[index] = normalized
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
return self unless normalized_segments
|
|
292
|
+
|
|
293
|
+
return self.class.new(nil, normalized_segments)
|
|
294
|
+
end
|
|
295
|
+
|
|
255
296
|
# Simplify this path in place by resolving literal or percent-encoded dot segments and repeated separators.
|
|
256
297
|
#
|
|
257
298
|
# @returns [Path | Nil] This path when changed, otherwise `nil`.
|
|
@@ -342,6 +383,42 @@ module Protocol
|
|
|
342
383
|
|
|
343
384
|
private
|
|
344
385
|
|
|
386
|
+
# Normalize one encoded path segment:
|
|
387
|
+
def normalize_segment(segment)
|
|
388
|
+
return segment.gsub(NORMALIZATION_PATTERN) do |character|
|
|
389
|
+
byte = character.getbyte(0)
|
|
390
|
+
|
|
391
|
+
if byte == 0
|
|
392
|
+
raise ArgumentError, "Path segment contains NUL!"
|
|
393
|
+
elsif byte == 0x25
|
|
394
|
+
if character.bytesize == 1
|
|
395
|
+
raise ArgumentError, "String contains malformed percent encoding!"
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
byte = character.byteslice(1, 2).to_i(16)
|
|
399
|
+
if byte == 0
|
|
400
|
+
raise ArgumentError, "Path segment contains NUL!"
|
|
401
|
+
elsif unreserved_byte?(byte)
|
|
402
|
+
byte.chr
|
|
403
|
+
else
|
|
404
|
+
character.upcase
|
|
405
|
+
end
|
|
406
|
+
else
|
|
407
|
+
Encoding.escape(character)
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Whether the byte represents an unreserved URI character:
|
|
413
|
+
def unreserved_byte?(byte)
|
|
414
|
+
case byte
|
|
415
|
+
when 0x30..0x39, 0x41..0x5A, 0x61..0x7A, 0x2D, 0x2E, 0x5F, 0x7E
|
|
416
|
+
return true
|
|
417
|
+
else
|
|
418
|
+
return false
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
345
422
|
# Identify dot segments, including percent-encoded spellings. RFC 3986 treats
|
|
346
423
|
# percent-encoded unreserved characters as equivalent to their literal forms;
|
|
347
424
|
# the WHATWG URL Standard explicitly recognizes `%2e`, `.%2e`, `%2e.`, and
|
|
@@ -415,9 +492,9 @@ module Protocol
|
|
|
415
492
|
offset += 1
|
|
416
493
|
end
|
|
417
494
|
elsif segment == "" && index != last_index
|
|
418
|
-
# Collapse repeated separators
|
|
495
|
+
# Collapse repeated separators:
|
|
419
496
|
elsif dot == ".." && offset > 0 && dot_segment(segments[offset - 1]) != ".."
|
|
420
|
-
# Pop a component, but never pop the absolute-path root
|
|
497
|
+
# Pop a component, but never pop the absolute-path root:
|
|
421
498
|
offset -= 1 if segments[offset - 1] != ""
|
|
422
499
|
|
|
423
500
|
# 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,104 @@ 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.14.0
|
|
38
|
+
|
|
39
|
+
- Allow paths to compare with their encoded string representation.
|
|
40
|
+
|
|
41
|
+
### v0.13.0
|
|
42
|
+
|
|
43
|
+
- Add conservative normalization of encoded URL paths.
|
|
44
|
+
|
|
45
|
+
### v0.12.0
|
|
46
|
+
|
|
47
|
+
- Allow unfrozen relative and absolute URLs to replace their components.
|
|
48
|
+
|
|
49
|
+
### v0.10.0
|
|
50
|
+
|
|
51
|
+
- Rename `Protocol::URL::FormData::Parser::CONTENT_TYPE` to `MEDIA_TYPE`.
|
|
52
|
+
|
|
53
|
+
### v0.9.0
|
|
54
|
+
|
|
55
|
+
- Add `Protocol::URL::LimitError` for configured processing limits.
|
|
56
|
+
|
|
57
|
+
### v0.8.0
|
|
58
|
+
|
|
59
|
+
- Use consistent limit naming for form data parser constraints.
|
|
60
|
+
|
|
61
|
+
### v0.7.0
|
|
62
|
+
|
|
63
|
+
- Allow `Protocol::URL::FormData::Parser#parse` to populate a supplied result object.
|
|
64
|
+
|
|
65
|
+
### v0.6.0
|
|
66
|
+
|
|
67
|
+
- Add `Protocol::URL::FormData::Parser` for incremental, limited parsing of `application/x-www-form-urlencoded` form data.
|
|
68
|
+
- Add `Protocol::URL::FormData::Nested` for consistently building nested form data while preserving absent and empty values.
|
|
69
|
+
|
|
70
|
+
### v0.5.0
|
|
71
|
+
|
|
72
|
+
- Add `Protocol::URL::Encoding.decode_www_form` for decoding HTML form data where `+` represents a space.
|
|
73
|
+
|
|
74
|
+
### v0.4.0
|
|
75
|
+
|
|
76
|
+
- Add comparison methods to `Protocol::URL::Relative` (and by inheritance to `Protocol::URL::Absolute`):
|
|
77
|
+
- `#==` for structural equality comparison (compares path, query, fragment components).
|
|
78
|
+
- `#===` for string equality comparison (enables case statement matching).
|
|
79
|
+
- `#<=>` for ordering and sorting.
|
|
80
|
+
- `#hash` for hash key support.
|
|
81
|
+
- `#equal?` for component-based equality checking.
|
|
82
|
+
- Add JSON serialization support to `Protocol::URL::Relative`:
|
|
83
|
+
- `#as_json` returns the string representation.
|
|
84
|
+
- `#to_json` returns a JSON-encoded string.
|
|
85
|
+
|
|
86
|
+
### v0.13.0
|
|
87
|
+
|
|
88
|
+
- Add conservative normalization of encoded URL paths.
|
|
89
|
+
|
|
90
|
+
### v0.12.0
|
|
91
|
+
|
|
92
|
+
- Allow unfrozen relative and absolute URLs to replace their components.
|
|
93
|
+
|
|
94
|
+
### v0.10.0
|
|
95
|
+
|
|
96
|
+
- Rename `Protocol::URL::FormData::Parser::CONTENT_TYPE` to `MEDIA_TYPE`.
|
|
97
|
+
|
|
98
|
+
### v0.9.0
|
|
99
|
+
|
|
100
|
+
- Add `Protocol::URL::LimitError` for configured processing limits.
|
|
101
|
+
|
|
102
|
+
### v0.8.0
|
|
103
|
+
|
|
104
|
+
- Use consistent limit naming for form data parser constraints.
|
|
105
|
+
|
|
106
|
+
### v0.7.0
|
|
107
|
+
|
|
108
|
+
- Allow `Protocol::URL::FormData::Parser#parse` to populate a supplied result object.
|
|
109
|
+
|
|
110
|
+
### v0.6.0
|
|
111
|
+
|
|
112
|
+
- Add `Protocol::URL::FormData::Parser` for incremental, limited parsing of `application/x-www-form-urlencoded` form data.
|
|
113
|
+
- Add `Protocol::URL::FormData::Nested` for consistently building nested form data while preserving absent and empty values.
|
|
114
|
+
|
|
115
|
+
### v0.5.0
|
|
116
|
+
|
|
117
|
+
- Add `Protocol::URL::Encoding.decode_www_form` for decoding HTML form data where `+` represents a space.
|
|
118
|
+
|
|
119
|
+
### v0.4.0
|
|
120
|
+
|
|
121
|
+
- Add comparison methods to `Protocol::URL::Relative` (and by inheritance to `Protocol::URL::Absolute`):
|
|
122
|
+
- `#==` for structural equality comparison (compares path, query, fragment components).
|
|
123
|
+
- `#===` for string equality comparison (enables case statement matching).
|
|
124
|
+
- `#<=>` for ordering and sorting.
|
|
125
|
+
- `#hash` for hash key support.
|
|
126
|
+
- `#equal?` for component-based equality checking.
|
|
127
|
+
- Add JSON serialization support to `Protocol::URL::Relative`:
|
|
128
|
+
- `#as_json` returns the string representation.
|
|
129
|
+
- `#to_json` returns a JSON-encoded string.
|
|
130
|
+
|
|
131
|
+
### v0.3.0
|
|
132
|
+
|
|
133
|
+
- Add `relative(target, from)` for computing relative paths between URLs.
|
|
134
|
+
|
|
37
135
|
### v0.12.0
|
|
38
136
|
|
|
39
137
|
- Allow unfrozen relative and absolute URLs to replace their components.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.14.0
|
|
4
|
+
|
|
5
|
+
- Allow paths to compare with their encoded string representation.
|
|
6
|
+
|
|
7
|
+
## v0.13.0
|
|
8
|
+
|
|
9
|
+
- Add conservative normalization of encoded URL paths.
|
|
10
|
+
|
|
3
11
|
## v0.12.0
|
|
4
12
|
|
|
5
13
|
- Allow unfrozen relative and absolute URLs to replace their components.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
|
Binary file
|