protocol-url 0.5.0 → 0.6.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: 10d18a95d6f20f5ca2457a52a2defa8d961ecf26eb372d45bea1f2d339f3a5c6
4
+ data.tar.gz: f15a84b24bd971e6262306b84e271196b5c20cde76983eba4625af28b0895ddd
5
5
  SHA512:
6
- metadata.gz: c49c2bc6639b2b0777ae93c3b8ee34bd7c496831deb32da8af725ebc4f873d2666a47b68bc6aa031d7b1dbee522488927011c1d5ceb9b81140e4ea3c4ae2e575
7
- data.tar.gz: 05c41426fdc297a5bae0a59d6ef4ca31e88a0cc592769038474c28b948b7615b5cecae17943d6263fe8bd02b079f178d427b2cdc7c4ea1393a5e28da8ccaa55f
6
+ metadata.gz: e1493486247a9e36b696bf62225a33888905d7e4a5a83b31357dc67d227b3a6f481844e2423befc5cc21f4c0a14aebc4ea507faa68a06a50490619e455396950
7
+ data.tar.gz: ee45f80e209b66b2a30d6b155188138a513e9a2c632bf1a4edc0322823e9bef209d2515babc955030c43c26c9da9ea687303a2aa91fa9704a2ac0594405d8cda
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,113 @@
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
+ # @yields {|name, value| ...} Each decoded form pair before assignment.
38
+ # @returns [Hash] The nested form data.
39
+ def parse(body)
40
+ nested = Nested.new(maximum_depth: @maximum_depth)
41
+
42
+ each(body) do |name, value|
43
+ value = yield(name, value) if block_given?
44
+ nested.add(name, value)
45
+ end
46
+
47
+ return nested.to_h
48
+ end
49
+
50
+ # Incrementally enumerate URL-encoded form data as ordered name/value pairs.
51
+ # @parameter body [Object] A readable body which yields chunks from `#read`.
52
+ # @yields {|name, value| ...} Each decoded form pair.
53
+ # @returns [Enumerator | Boolean] An enumerator without a block, or true when complete.
54
+ def each(body)
55
+ return to_enum(__method__, body) unless block_given?
56
+
57
+ buffer = String.new.b
58
+ total_size = 0
59
+ pair_count = 0
60
+
61
+ while chunk = body.read
62
+ break if chunk.empty?
63
+
64
+ total_size += chunk.bytesize
65
+ check_limit(:total_size, total_size, @maximum_total_size)
66
+ buffer << chunk
67
+
68
+ while separator = buffer.index("&")
69
+ assignment = buffer.slice!(0, separator + 1)
70
+ assignment.chop!
71
+
72
+ unless assignment.empty?
73
+ pair_count += 1
74
+ check_limit(:pair_count, pair_count, @maximum_pair_count)
75
+ yield_pair(assignment) {|name, value| yield name, value}
76
+ end
77
+ end
78
+ end
79
+
80
+ unless buffer.empty?
81
+ pair_count += 1
82
+ check_limit(:pair_count, pair_count, @maximum_pair_count)
83
+ yield_pair(buffer) {|name, value| yield name, value}
84
+ end
85
+
86
+ return true
87
+ end
88
+
89
+ private
90
+
91
+ def yield_pair(assignment)
92
+ name, value = assignment.split("=", 2)
93
+
94
+ if value
95
+ value = decode_component(value)
96
+ end
97
+
98
+ yield decode_component(name), value
99
+ end
100
+
101
+ def decode_component(component)
102
+ return Encoding.unescape(component.tr("+", " "))
103
+ end
104
+
105
+ def check_limit(name, value, maximum)
106
+ if maximum and value > maximum
107
+ raise RangeError, "Form data #{name} exceeded limit of #{maximum}!"
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -7,6 +7,6 @@
7
7
  module Protocol
8
8
  # @namespace
9
9
  module URL
10
- VERSION = "0.5.0"
10
+ VERSION = "0.6.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,11 @@ 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.6.0
38
+
39
+ - Add `Protocol::URL::FormData::Parser` for incremental, limited parsing of `application/x-www-form-urlencoded` form data.
40
+ - Add `Protocol::URL::FormData::Nested` for consistently building nested form data while preserving absent and empty values.
41
+
37
42
  ### v0.5.0
38
43
 
39
44
  - Add `Protocol::URL::Encoding.decode_www_form` for decoding HTML form data where `+` represents a space.
data/releases.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Releases
2
2
 
3
+ ## v0.6.0
4
+
5
+ - Add `Protocol::URL::FormData::Parser` for incremental, limited parsing of `application/x-www-form-urlencoded` form data.
6
+ - Add `Protocol::URL::FormData::Nested` for consistently building nested form data while preserving absent and empty values.
7
+
3
8
  ## v0.5.0
4
9
 
5
10
  - 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.6.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