protocol-url 0.5.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb5e51237375afb74dda311e130ad550240453d53c42c5c0c05ddc532231261f
4
- data.tar.gz: fbcf705936fedf9e967af1eb66ef2d901f68448bfe9e47d3ffd21156486207f0
3
+ metadata.gz: 38bcf53ee312963527020be6c4a4c18697587ad4ecd75277552b1dbd744e0ec9
4
+ data.tar.gz: 85f2886788dfec25b6799f3c92d79f8bb31fbb5f48f3b017fca94993a21b507b
5
5
  SHA512:
6
- metadata.gz: c49c2bc6639b2b0777ae93c3b8ee34bd7c496831deb32da8af725ebc4f873d2666a47b68bc6aa031d7b1dbee522488927011c1d5ceb9b81140e4ea3c4ae2e575
7
- data.tar.gz: 05c41426fdc297a5bae0a59d6ef4ca31e88a0cc592769038474c28b948b7615b5cecae17943d6263fe8bd02b079f178d427b2cdc7c4ea1393a5e28da8ccaa55f
6
+ metadata.gz: c5870caa5923cc4918e909470522231f132946f3235a8b396dff155adcab311ab0a1743da9ae1de96c92d2427c6e61cb758760dc2a3133005c4b0929225a4af5
7
+ data.tar.gz: 820c6b061491cd83b553f38b5d53573cc641d76ad63be859e2835698e0b7242ae908763758b1730dd3b56a3f5a82dfe11d2f2de0cfafe12bd87ef66d4e405c17
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "../encoding"
7
+
8
+ module Protocol
9
+ module URL
10
+ module FormData
11
+ # Builds nested form data from names and values.
12
+ class Nested
13
+ # The default maximum depth of a bracketed form name.
14
+ MAXIMUM_DEPTH = 8
15
+
16
+ # Initialize the nested form data.
17
+ # @parameter maximum_depth [Integer | Nil] The maximum depth of a bracketed form name.
18
+ def initialize(maximum_depth: MAXIMUM_DEPTH)
19
+ @maximum_depth = maximum_depth
20
+ @root = {}
21
+ end
22
+
23
+ # Add a form data value using its bracketed name.
24
+ # @parameter name [String] The form data name.
25
+ # @parameter value [Object] The form data value.
26
+ # @returns [Nested] The receiver.
27
+ def add(name, value)
28
+ keys = Encoding.split(name)
29
+
30
+ if keys.empty?
31
+ raise ArgumentError, "Invalid form data name: #{name.inspect}!"
32
+ end
33
+
34
+ if @maximum_depth and keys.size > @maximum_depth
35
+ raise RangeError, "Form data depth exceeded limit of #{@maximum_depth}!"
36
+ end
37
+
38
+ Encoding.assign(keys, value, @root)
39
+
40
+ return self
41
+ end
42
+
43
+ # Convert the arguments to a nested hash.
44
+ # @returns [Hash] The nested arguments.
45
+ def to_h
46
+ return @root
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "nested"
7
+
8
+ module Protocol
9
+ module URL
10
+ # @namespace
11
+ module FormData
12
+ # Incrementally parses `application/x-www-form-urlencoded` form data.
13
+ class Parser
14
+ CONTENT_TYPE = "application/x-www-form-urlencoded"
15
+
16
+ # The default maximum encoded body size.
17
+ MAXIMUM_TOTAL_SIZE = 2 * 1024 * 1024
18
+
19
+ # The default maximum number of form pairs.
20
+ MAXIMUM_PAIR_COUNT = 1024
21
+
22
+ # Initialize the form data parser.
23
+ # @parameter maximum_total_size [Integer | Nil] The maximum encoded body size.
24
+ # @parameter maximum_pair_count [Integer | Nil] The maximum number of form pairs.
25
+ # @parameter maximum_depth [Integer | Nil] The maximum depth of a bracketed form name.
26
+ def initialize(maximum_total_size: MAXIMUM_TOTAL_SIZE, maximum_pair_count: MAXIMUM_PAIR_COUNT, maximum_depth: Nested::MAXIMUM_DEPTH)
27
+ @maximum_total_size = maximum_total_size
28
+ @maximum_pair_count = maximum_pair_count
29
+ @maximum_depth = maximum_depth
30
+ end
31
+
32
+ # Parse URL-encoded form data into a nested hash.
33
+ #
34
+ # When a block is given, each decoded value is passed through the block before assignment. The value returned by the block is assigned to the result.
35
+ #
36
+ # @parameter body [Object] A readable body which yields chunks from `#read`.
37
+ # @parameter result [Object] The result to populate. It must support `#add` and `#to_h`.
38
+ # @yields {|name, value| ...} Each decoded form pair before assignment.
39
+ # @returns [Hash] The nested form data.
40
+ def parse(body, result = make_result)
41
+ each(body) do |name, value|
42
+ value = yield(name, value) if block_given?
43
+ result.add(name, value)
44
+ end
45
+
46
+ return result.to_h
47
+ end
48
+
49
+ # Incrementally enumerate URL-encoded form data as ordered name/value pairs.
50
+ # @parameter body [Object] A readable body which yields chunks from `#read`.
51
+ # @yields {|name, value| ...} Each decoded form pair.
52
+ # @returns [Enumerator | Boolean] An enumerator without a block, or true when complete.
53
+ def each(body)
54
+ return to_enum(__method__, body) unless block_given?
55
+
56
+ buffer = String.new.b
57
+ total_size = 0
58
+ pair_count = 0
59
+
60
+ while chunk = body.read
61
+ break if chunk.empty?
62
+
63
+ total_size += chunk.bytesize
64
+ check_limit(:total_size, total_size, @maximum_total_size)
65
+ buffer << chunk
66
+
67
+ while separator = buffer.index("&")
68
+ assignment = buffer.slice!(0, separator + 1)
69
+ assignment.chop!
70
+
71
+ unless assignment.empty?
72
+ pair_count += 1
73
+ check_limit(:pair_count, pair_count, @maximum_pair_count)
74
+ yield_pair(assignment) {|name, value| yield name, value}
75
+ end
76
+ end
77
+ end
78
+
79
+ unless buffer.empty?
80
+ pair_count += 1
81
+ check_limit(:pair_count, pair_count, @maximum_pair_count)
82
+ yield_pair(buffer) {|name, value| yield name, value}
83
+ end
84
+
85
+ return true
86
+ end
87
+
88
+ private
89
+
90
+ def make_result
91
+ return Nested.new(maximum_depth: @maximum_depth)
92
+ end
93
+
94
+ def yield_pair(assignment)
95
+ name, value = assignment.split("=", 2)
96
+
97
+ if value
98
+ value = decode_component(value)
99
+ end
100
+
101
+ yield decode_component(name), value
102
+ end
103
+
104
+ def decode_component(component)
105
+ return Encoding.unescape(component.tr("+", " "))
106
+ end
107
+
108
+ def check_limit(name, value, maximum)
109
+ if maximum and value > maximum
110
+ raise RangeError, "Form data #{name} exceeded limit of #{maximum}!"
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -7,6 +7,6 @@
7
7
  module Protocol
8
8
  # @namespace
9
9
  module URL
10
- VERSION = "0.5.0"
10
+ VERSION = "0.7.0"
11
11
  end
12
12
  end
data/lib/protocol/url.rb CHANGED
@@ -6,6 +6,8 @@
6
6
  require_relative "url/version"
7
7
  require_relative "url/pattern"
8
8
  require_relative "url/encoding"
9
+ require_relative "url/form_data/nested"
10
+ require_relative "url/form_data/parser"
9
11
  require_relative "url/reference"
10
12
  require_relative "url/relative"
11
13
  require_relative "url/absolute"
data/readme.md CHANGED
@@ -34,6 +34,15 @@ This project is best served by a collaborative and respectful environment. Treat
34
34
 
35
35
  Please see the [project releases](https://socketry.github.io/protocol-url/releases/index) for all releases.
36
36
 
37
+ ### v0.7.0
38
+
39
+ - Allow `Protocol::URL::FormData::Parser#parse` to populate a supplied result object.
40
+
41
+ ### v0.6.0
42
+
43
+ - Add `Protocol::URL::FormData::Parser` for incremental, limited parsing of `application/x-www-form-urlencoded` form data.
44
+ - Add `Protocol::URL::FormData::Nested` for consistently building nested form data while preserving absent and empty values.
45
+
37
46
  ### v0.5.0
38
47
 
39
48
  - Add `Protocol::URL::Encoding.decode_www_form` for decoding HTML form data where `+` represents a space.
data/releases.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Releases
2
2
 
3
+ ## v0.7.0
4
+
5
+ - Allow `Protocol::URL::FormData::Parser#parse` to populate a supplied result object.
6
+
7
+ ## v0.6.0
8
+
9
+ - Add `Protocol::URL::FormData::Parser` for incremental, limited parsing of `application/x-www-form-urlencoded` form data.
10
+ - Add `Protocol::URL::FormData::Nested` for consistently building nested form data while preserving absent and empty values.
11
+
3
12
  ## v0.5.0
4
13
 
5
14
  - Add `Protocol::URL::Encoding.decode_www_form` for decoding HTML form data where `+` represents a space.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-url
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -45,6 +45,8 @@ files:
45
45
  - lib/protocol/url.rb
46
46
  - lib/protocol/url/absolute.rb
47
47
  - lib/protocol/url/encoding.rb
48
+ - lib/protocol/url/form_data/nested.rb
49
+ - lib/protocol/url/form_data/parser.rb
48
50
  - lib/protocol/url/path.rb
49
51
  - lib/protocol/url/pattern.rb
50
52
  - lib/protocol/url/reference.rb
metadata.gz.sig CHANGED
Binary file