protocol-url 0.11.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/absolute.rb +11 -11
- data/lib/protocol/url/path.rb +76 -3
- data/lib/protocol/url/reference.rb +2 -2
- data/lib/protocol/url/relative.rb +25 -13
- 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: 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
|
|
@@ -15,8 +15,8 @@ module Protocol
|
|
|
15
15
|
# @parameter scheme [String] The URL scheme (e.g., "https", "http").
|
|
16
16
|
# @parameter authority [String] The authority component (e.g., "example.com", "user@host:port").
|
|
17
17
|
# @parameter path [String | Path] The encoded path component (defaults to "/").
|
|
18
|
-
# @parameter query [String
|
|
19
|
-
# @parameter fragment [String
|
|
18
|
+
# @parameter query [String | Nil] The query string.
|
|
19
|
+
# @parameter fragment [String | Nil] The fragment identifier.
|
|
20
20
|
def initialize(scheme, authority, path = "/", query = nil, fragment = nil)
|
|
21
21
|
@scheme = scheme
|
|
22
22
|
@authority = authority
|
|
@@ -36,11 +36,11 @@ module Protocol
|
|
|
36
36
|
return super
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
# @attribute [String] The URL scheme.
|
|
40
|
-
|
|
39
|
+
# @attribute [String | Nil] The URL scheme.
|
|
40
|
+
attr_accessor :scheme
|
|
41
41
|
|
|
42
|
-
# @attribute [String] The authority component.
|
|
43
|
-
|
|
42
|
+
# @attribute [String | Nil] The authority component.
|
|
43
|
+
attr_accessor :authority
|
|
44
44
|
|
|
45
45
|
# Check if the URL has a non-empty scheme.
|
|
46
46
|
#
|
|
@@ -122,11 +122,11 @@ module Protocol
|
|
|
122
122
|
|
|
123
123
|
# Create a new Absolute URL with modified components.
|
|
124
124
|
#
|
|
125
|
-
# @parameter scheme [String
|
|
126
|
-
# @parameter authority [String
|
|
127
|
-
# @parameter path [String
|
|
128
|
-
# @parameter query [String
|
|
129
|
-
# @parameter fragment [String
|
|
125
|
+
# @parameter scheme [String | Nil] The scheme to use (nil to remove scheme).
|
|
126
|
+
# @parameter authority [String | Nil] The authority to use (nil to remove authority).
|
|
127
|
+
# @parameter path [String | Nil] The path to merge with the current path.
|
|
128
|
+
# @parameter query [String | Nil] The query string to use.
|
|
129
|
+
# @parameter fragment [String | Nil] The fragment to use.
|
|
130
130
|
# @parameter pop [Boolean] Whether to pop the last path component before merging.
|
|
131
131
|
# @returns [Absolute] A new Absolute URL with the modified components.
|
|
132
132
|
#
|
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.
|
|
@@ -102,8 +102,8 @@ module Protocol
|
|
|
102
102
|
@parameters = parameters
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
-
# @attribute [Hash] User supplied parameters that will be appended to the query part.
|
|
106
|
-
|
|
105
|
+
# @attribute [Hash | Nil] User supplied parameters that will be appended to the query part.
|
|
106
|
+
attr_accessor :parameters
|
|
107
107
|
|
|
108
108
|
# Freeze the reference.
|
|
109
109
|
#
|
|
@@ -15,8 +15,8 @@ module Protocol
|
|
|
15
15
|
# Initialize a new relative URL.
|
|
16
16
|
#
|
|
17
17
|
# @parameter path [String | Path] The encoded path component.
|
|
18
|
-
# @parameter query [String
|
|
19
|
-
# @parameter fragment [String
|
|
18
|
+
# @parameter query [String | Nil] The query string.
|
|
19
|
+
# @parameter fragment [String | Nil] The fragment identifier.
|
|
20
20
|
def initialize(path, query = nil, fragment = nil)
|
|
21
21
|
@path = Path[path]
|
|
22
22
|
@query = query
|
|
@@ -38,11 +38,18 @@ module Protocol
|
|
|
38
38
|
# @attribute [Path] The path component of the URL.
|
|
39
39
|
attr :path
|
|
40
40
|
|
|
41
|
-
#
|
|
42
|
-
|
|
41
|
+
# Replace the path component of this URL.
|
|
42
|
+
# @parameter path [String | Path] The encoded path component.
|
|
43
|
+
# @returns [Path] The assigned path component.
|
|
44
|
+
def path=(path)
|
|
45
|
+
@path = Path[path]
|
|
46
|
+
end
|
|
43
47
|
|
|
44
|
-
# @attribute [String
|
|
45
|
-
|
|
48
|
+
# @attribute [String | Nil] The query string component.
|
|
49
|
+
attr_accessor :query
|
|
50
|
+
|
|
51
|
+
# @attribute [String | Nil] The fragment identifier.
|
|
52
|
+
attr_accessor :fragment
|
|
46
53
|
|
|
47
54
|
# Resolve the URL path beneath a local filesystem root.
|
|
48
55
|
#
|
|
@@ -104,9 +111,9 @@ module Protocol
|
|
|
104
111
|
|
|
105
112
|
# Create a new Relative URL with modified components.
|
|
106
113
|
#
|
|
107
|
-
# @parameter path [String
|
|
108
|
-
# @parameter query [String
|
|
109
|
-
# @parameter fragment [String
|
|
114
|
+
# @parameter path [String | Nil] The path to merge with the current path.
|
|
115
|
+
# @parameter query [String | Nil] The query string to use.
|
|
116
|
+
# @parameter fragment [String | Nil] The fragment to use.
|
|
110
117
|
# @parameter pop [Boolean] Whether to pop the last path component before merging.
|
|
111
118
|
# @returns [Relative] A new Relative URL with the modified components.
|
|
112
119
|
#
|
|
@@ -125,12 +132,17 @@ module Protocol
|
|
|
125
132
|
self.class.new(path || @path, query, fragment)
|
|
126
133
|
end
|
|
127
134
|
|
|
128
|
-
# Normalize the path
|
|
135
|
+
# Normalize the encoded path and simplify its structure.
|
|
129
136
|
#
|
|
130
|
-
# 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
|
|
131
140
|
# - Removes "." segments (current directory)
|
|
132
141
|
# - Resolves ".." segments (parent directory)
|
|
133
|
-
# - 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.
|
|
134
146
|
#
|
|
135
147
|
# @returns [self] The normalized URL.
|
|
136
148
|
#
|
|
@@ -139,7 +151,7 @@ module Protocol
|
|
|
139
151
|
# url.normalize!
|
|
140
152
|
# url.path.to_s # => "/foo/bar/qux"
|
|
141
153
|
def normalize!
|
|
142
|
-
@path = @path.simplify
|
|
154
|
+
@path = @path.normalize.simplify
|
|
143
155
|
|
|
144
156
|
return self
|
|
145
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.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
|
+
|
|
86
|
+
### v0.12.0
|
|
87
|
+
|
|
88
|
+
- Allow unfrozen relative and absolute URLs to replace their components.
|
|
89
|
+
|
|
90
|
+
### v0.10.0
|
|
91
|
+
|
|
92
|
+
- Rename `Protocol::URL::FormData::Parser::CONTENT_TYPE` to `MEDIA_TYPE`.
|
|
93
|
+
|
|
94
|
+
### v0.9.0
|
|
95
|
+
|
|
96
|
+
- Add `Protocol::URL::LimitError` for configured processing limits.
|
|
97
|
+
|
|
98
|
+
### v0.8.0
|
|
99
|
+
|
|
100
|
+
- Use consistent limit naming for form data parser constraints.
|
|
101
|
+
|
|
102
|
+
### v0.7.0
|
|
103
|
+
|
|
104
|
+
- Allow `Protocol::URL::FormData::Parser#parse` to populate a supplied result object.
|
|
105
|
+
|
|
106
|
+
### v0.6.0
|
|
107
|
+
|
|
108
|
+
- Add `Protocol::URL::FormData::Parser` for incremental, limited parsing of `application/x-www-form-urlencoded` form data.
|
|
109
|
+
- Add `Protocol::URL::FormData::Nested` for consistently building nested form data while preserving absent and empty values.
|
|
110
|
+
|
|
111
|
+
### v0.5.0
|
|
112
|
+
|
|
113
|
+
- Add `Protocol::URL::Encoding.decode_www_form` for decoding HTML form data where `+` represents a space.
|
|
114
|
+
|
|
115
|
+
### v0.4.0
|
|
116
|
+
|
|
117
|
+
- Add comparison methods to `Protocol::URL::Relative` (and by inheritance to `Protocol::URL::Absolute`):
|
|
118
|
+
- `#==` for structural equality comparison (compares path, query, fragment components).
|
|
119
|
+
- `#===` for string equality comparison (enables case statement matching).
|
|
120
|
+
- `#<=>` for ordering and sorting.
|
|
121
|
+
- `#hash` for hash key support.
|
|
122
|
+
- `#equal?` for component-based equality checking.
|
|
123
|
+
- Add JSON serialization support to `Protocol::URL::Relative`:
|
|
124
|
+
- `#as_json` returns the string representation.
|
|
125
|
+
- `#to_json` returns a JSON-encoded string.
|
|
126
|
+
|
|
127
|
+
### v0.3.0
|
|
128
|
+
|
|
129
|
+
- Add `relative(target, from)` for computing relative paths between URLs.
|
|
130
|
+
|
|
131
|
+
### v0.2.0
|
|
132
|
+
|
|
133
|
+
- Move `Protocol::URL::PATTERN` to `protocol/url/pattern.rb` so it can be shared more easily.
|
|
134
|
+
|
|
37
135
|
### v0.10.0
|
|
38
136
|
|
|
39
137
|
- Rename `Protocol::URL::FormData::Parser::CONTENT_TYPE` to `MEDIA_TYPE`.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.13.0
|
|
4
|
+
|
|
5
|
+
- Add conservative normalization of encoded URL paths.
|
|
6
|
+
|
|
7
|
+
## v0.12.0
|
|
8
|
+
|
|
9
|
+
- Allow unfrozen relative and absolute URLs to replace their components.
|
|
10
|
+
|
|
3
11
|
## v0.10.0
|
|
4
12
|
|
|
5
13
|
- Rename `Protocol::URL::FormData::Parser::CONTENT_TYPE` to `MEDIA_TYPE`.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
|
Binary file
|