protocol-multipart 0.3.0 → 0.4.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/multipart/form_data/parser.rb +16 -16
- data/lib/protocol/multipart/form_data.rb +14 -14
- data/lib/protocol/multipart/parser.rb +30 -30
- data/lib/protocol/multipart/version.rb +1 -1
- data/readme.md +4 -0
- data/releases.md +4 -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: 32d7a5d9ab53cbe80b179ea8ff2ac318415484eea858c4fc8f170c2bdcdb998a
|
|
4
|
+
data.tar.gz: 8540f4f11737134c960c1bcdd6303a2418c17ca6c754864e9b83204bc93410fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce50fbd0c0bb2afc3396eba792684b2db2cfd839808bede5b0e38196966481d5924a1548dabbb6f42621b7afe0039ba8342e27c22ee815de142cc1e266ac7ee1
|
|
7
|
+
data.tar.gz: 534c8bbc606b71e2d532d861a7d1633c53e28188f7542c1d5aa5000b758715a2eecad9ab1ed047a0f69d6e9def50ba019fe0f0e3965cf979e3c99aa4b1d5affe
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -15,20 +15,20 @@ module Protocol
|
|
|
15
15
|
CONTENT_TYPE = "multipart/form-data"
|
|
16
16
|
|
|
17
17
|
# Initialize the form data parser.
|
|
18
|
-
# @parameter
|
|
19
|
-
# @parameter
|
|
20
|
-
# @parameter
|
|
21
|
-
# @parameter
|
|
18
|
+
# @parameter field_size_limit [Integer | Nil] The buffered field size limit.
|
|
19
|
+
# @parameter upload_size_limit [Integer | Nil] The file upload size limit.
|
|
20
|
+
# @parameter total_size_limit [Integer | Nil] The combined size limit for all fields and uploads.
|
|
21
|
+
# @parameter depth_limit [Integer | Nil] The bracketed form name depth limit.
|
|
22
22
|
# @parameter options [Hash] Limits passed to the underlying multipart parser.
|
|
23
|
-
def initialize(
|
|
24
|
-
if
|
|
23
|
+
def initialize(field_size_limit: FIELD_SIZE_LIMIT, upload_size_limit: UPLOAD_SIZE_LIMIT, total_size_limit: TOTAL_SIZE_LIMIT, depth_limit: Protocol::URL::FormData::Nested::DEPTH_LIMIT, **options)
|
|
24
|
+
if depth_limit and depth_limit < 0
|
|
25
25
|
raise ArgumentError, "Form data limits must be non-negative!"
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
@
|
|
29
|
-
@
|
|
30
|
-
@
|
|
31
|
-
@
|
|
28
|
+
@field_size_limit = field_size_limit
|
|
29
|
+
@upload_size_limit = upload_size_limit
|
|
30
|
+
@total_size_limit = total_size_limit
|
|
31
|
+
@depth_limit = depth_limit
|
|
32
32
|
@options = options
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -66,7 +66,7 @@ module Protocol
|
|
|
66
66
|
def each(readable, boundary:)
|
|
67
67
|
return to_enum(__method__, readable, boundary:) unless block_given?
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
total_size_limit = ByteLimit.new(@total_size_limit, name: :total_size)
|
|
70
70
|
parser = Multipart::Parser.new(readable, boundary, **@options)
|
|
71
71
|
|
|
72
72
|
parser.each do |part|
|
|
@@ -77,16 +77,16 @@ module Protocol
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
if filename = disposition["filename"]
|
|
80
|
-
upload = Upload.new(part, filename, @
|
|
80
|
+
upload = Upload.new(part, filename, @upload_size_limit, total_size_limit)
|
|
81
81
|
yield name, upload
|
|
82
82
|
upload.discard
|
|
83
83
|
else
|
|
84
|
-
|
|
84
|
+
field_size_limit = ByteLimit.new(@field_size_limit, name: :field_size)
|
|
85
85
|
value = String.new.b
|
|
86
86
|
|
|
87
87
|
part.each do |chunk|
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
field_size_limit.consume(chunk.bytesize)
|
|
89
|
+
total_size_limit.consume(chunk.bytesize)
|
|
90
90
|
value << chunk
|
|
91
91
|
end
|
|
92
92
|
|
|
@@ -98,7 +98,7 @@ module Protocol
|
|
|
98
98
|
private
|
|
99
99
|
|
|
100
100
|
def make_result
|
|
101
|
-
return Protocol::URL::FormData::Nested.new(
|
|
101
|
+
return Protocol::URL::FormData::Nested.new(depth_limit: @depth_limit)
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
end
|
|
@@ -16,27 +16,27 @@ module Protocol
|
|
|
16
16
|
class FormData < Mixed
|
|
17
17
|
include Escape
|
|
18
18
|
|
|
19
|
-
# The
|
|
20
|
-
|
|
19
|
+
# The buffered form field size limit.
|
|
20
|
+
FIELD_SIZE_LIMIT = 2 * 1024 * 1024
|
|
21
21
|
|
|
22
|
-
# The
|
|
23
|
-
|
|
22
|
+
# The streamed file upload size limit.
|
|
23
|
+
UPLOAD_SIZE_LIMIT = 128 * 1024 * 1024
|
|
24
24
|
|
|
25
|
-
# The
|
|
26
|
-
|
|
25
|
+
# The combined size limit for all form fields and file uploads.
|
|
26
|
+
TOTAL_SIZE_LIMIT = 256 * 1024 * 1024
|
|
27
27
|
|
|
28
28
|
# A file upload yielded while parsing form data.
|
|
29
29
|
class Upload
|
|
30
30
|
# Initialize a streamed file upload.
|
|
31
31
|
# @parameter part [Parser::Part] The underlying multipart part.
|
|
32
32
|
# @parameter filename [String] The submitted filename.
|
|
33
|
-
# @parameter
|
|
34
|
-
# @parameter
|
|
35
|
-
def initialize(part, filename,
|
|
33
|
+
# @parameter size_limit [Integer | Nil] The upload size limit.
|
|
34
|
+
# @parameter total_size_limit [ByteLimit] The shared form-data size limit.
|
|
35
|
+
def initialize(part, filename, size_limit, total_size_limit)
|
|
36
36
|
@part = part
|
|
37
37
|
@filename = filename
|
|
38
|
-
@
|
|
39
|
-
@
|
|
38
|
+
@size_limit = ByteLimit.new(size_limit, name: :upload_size)
|
|
39
|
+
@total_size_limit = total_size_limit
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# The submitted filename.
|
|
@@ -49,7 +49,7 @@ module Protocol
|
|
|
49
49
|
|
|
50
50
|
# The number of upload bytes consumed so far.
|
|
51
51
|
def size
|
|
52
|
-
@
|
|
52
|
+
@size_limit.size
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
# Whether the complete upload has been consumed.
|
|
@@ -63,8 +63,8 @@ module Protocol
|
|
|
63
63
|
return to_enum(:each, chunk_size) unless block_given?
|
|
64
64
|
|
|
65
65
|
@part.each(chunk_size) do |chunk|
|
|
66
|
-
@
|
|
67
|
-
@
|
|
66
|
+
@size_limit.consume(chunk.bytesize)
|
|
67
|
+
@total_size_limit.consume(chunk.bytesize)
|
|
68
68
|
yield chunk
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -14,17 +14,17 @@ module Protocol
|
|
|
14
14
|
HEADER_PATTERN = /\A([!-9;-~]+):[ \t]*([^\x00-\x08\x0a-\x1f\x7f]*)\z/.freeze
|
|
15
15
|
private_constant :HEADER_PATTERN
|
|
16
16
|
|
|
17
|
-
# The
|
|
18
|
-
|
|
17
|
+
# The preamble size limit.
|
|
18
|
+
PREAMBLE_SIZE_LIMIT = 64 * 1024
|
|
19
19
|
|
|
20
|
-
# The
|
|
21
|
-
|
|
20
|
+
# The header size limit for each part.
|
|
21
|
+
HEADER_SIZE_LIMIT = 64 * 1024
|
|
22
22
|
|
|
23
|
-
# The
|
|
24
|
-
|
|
23
|
+
# The header count limit for each part.
|
|
24
|
+
HEADER_COUNT_LIMIT = 64
|
|
25
25
|
|
|
26
|
-
# The
|
|
27
|
-
|
|
26
|
+
# The part count limit.
|
|
27
|
+
PART_COUNT_LIMIT = 128
|
|
28
28
|
|
|
29
29
|
# Represents a single part within a multipart message.
|
|
30
30
|
class Part
|
|
@@ -163,12 +163,12 @@ module Protocol
|
|
|
163
163
|
#
|
|
164
164
|
# @parameter readable [IO, IO::Stream] The readable stream containing multipart data.
|
|
165
165
|
# @parameter boundary [String] The boundary string that separates the parts.
|
|
166
|
-
# @parameter
|
|
167
|
-
# @parameter
|
|
168
|
-
# @parameter
|
|
169
|
-
# @parameter
|
|
170
|
-
def initialize(readable, boundary,
|
|
171
|
-
limits = [
|
|
166
|
+
# @parameter preamble_size_limit [Integer | Nil] The preamble size limit, or nil for no limit.
|
|
167
|
+
# @parameter header_size_limit [Integer | Nil] The header size limit per part, or nil for no limit.
|
|
168
|
+
# @parameter header_count_limit [Integer | Nil] The header count limit per part, or nil for no limit.
|
|
169
|
+
# @parameter part_count_limit [Integer | Nil] The part count limit, or nil for no limit.
|
|
170
|
+
def initialize(readable, boundary, preamble_size_limit: PREAMBLE_SIZE_LIMIT, header_size_limit: HEADER_SIZE_LIMIT, header_count_limit: HEADER_COUNT_LIMIT, part_count_limit: PART_COUNT_LIMIT)
|
|
171
|
+
limits = [preamble_size_limit, header_size_limit, header_count_limit, part_count_limit]
|
|
172
172
|
|
|
173
173
|
if limits.any?{|limit| limit and limit < 0}
|
|
174
174
|
raise ArgumentError, "Multipart limits must be non-negative!"
|
|
@@ -176,10 +176,10 @@ module Protocol
|
|
|
176
176
|
|
|
177
177
|
@readable = IO::Stream(readable)
|
|
178
178
|
@boundary = boundary
|
|
179
|
-
@
|
|
180
|
-
@
|
|
181
|
-
@
|
|
182
|
-
@
|
|
179
|
+
@preamble_size_limit = preamble_size_limit
|
|
180
|
+
@header_size_limit = header_size_limit
|
|
181
|
+
@header_count_limit = header_count_limit
|
|
182
|
+
@part_count_limit = part_count_limit
|
|
183
183
|
|
|
184
184
|
@boundary_marker = "--#{@boundary}\r\n".freeze
|
|
185
185
|
end
|
|
@@ -195,12 +195,12 @@ module Protocol
|
|
|
195
195
|
|
|
196
196
|
# Read lines until we find the first boundary:
|
|
197
197
|
while true
|
|
198
|
-
if line = read_line(preamble_size, @
|
|
198
|
+
if line = read_line(preamble_size, @preamble_size_limit, allowance: @boundary_marker.bytesize, chomp: false)
|
|
199
199
|
if line == @boundary_marker
|
|
200
200
|
break
|
|
201
201
|
else
|
|
202
202
|
preamble_size += line.bytesize
|
|
203
|
-
check_limit(:preamble_size, preamble_size, @
|
|
203
|
+
check_limit(:preamble_size, preamble_size, @preamble_size_limit)
|
|
204
204
|
end
|
|
205
205
|
else
|
|
206
206
|
# End of stream reached without finding boundary:
|
|
@@ -212,7 +212,7 @@ module Protocol
|
|
|
212
212
|
|
|
213
213
|
while true
|
|
214
214
|
part_count += 1
|
|
215
|
-
check_limit(:part_count, part_count, @
|
|
215
|
+
check_limit(:part_count, part_count, @part_count_limit)
|
|
216
216
|
|
|
217
217
|
part = read_part
|
|
218
218
|
break unless part
|
|
@@ -234,18 +234,18 @@ module Protocol
|
|
|
234
234
|
|
|
235
235
|
private
|
|
236
236
|
|
|
237
|
-
def read_line(size,
|
|
238
|
-
if
|
|
239
|
-
limit =
|
|
237
|
+
def read_line(size, limit, allowance: 0, chomp:)
|
|
238
|
+
if limit
|
|
239
|
+
limit = limit - size + allowance + 1
|
|
240
240
|
return @readable.gets("\r\n", limit, chomp: chomp)
|
|
241
241
|
else
|
|
242
242
|
return @readable.gets("\r\n", chomp: chomp)
|
|
243
243
|
end
|
|
244
244
|
end
|
|
245
245
|
|
|
246
|
-
def check_limit(name, value,
|
|
247
|
-
if
|
|
248
|
-
raise RangeError, "Multipart #{name} exceeded limit of #{
|
|
246
|
+
def check_limit(name, value, limit)
|
|
247
|
+
if limit and value > limit
|
|
248
|
+
raise RangeError, "Multipart #{name} exceeded limit of #{limit}!"
|
|
249
249
|
end
|
|
250
250
|
end
|
|
251
251
|
|
|
@@ -255,18 +255,18 @@ module Protocol
|
|
|
255
255
|
header_count = 0
|
|
256
256
|
|
|
257
257
|
# Read headers until empty line
|
|
258
|
-
while line = read_line(header_size, @
|
|
258
|
+
while line = read_line(header_size, @header_size_limit, allowance: 2, chomp: true)
|
|
259
259
|
if line.empty?
|
|
260
260
|
break # End of headers
|
|
261
261
|
end
|
|
262
262
|
|
|
263
263
|
header_size += line.bytesize + 2
|
|
264
|
-
check_limit(:header_size, header_size, @
|
|
264
|
+
check_limit(:header_size, header_size, @header_size_limit)
|
|
265
265
|
|
|
266
266
|
if match = line.match(HEADER_PATTERN)
|
|
267
267
|
# Parse header line (name: value)
|
|
268
268
|
header_count += 1
|
|
269
|
-
check_limit(:header_count, header_count, @
|
|
269
|
+
check_limit(:header_count, header_count, @header_count_limit)
|
|
270
270
|
|
|
271
271
|
fields << [match[1], match[2].strip]
|
|
272
272
|
else
|
data/readme.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
Please see the [project releases](https://socketry.github.io/protocol-multipart/releases/index) for all releases.
|
|
8
8
|
|
|
9
|
+
### v0.4.0
|
|
10
|
+
|
|
11
|
+
- Use consistent limit naming for multipart parser constraints.
|
|
12
|
+
|
|
9
13
|
### v0.3.0
|
|
10
14
|
|
|
11
15
|
- Add a configurable `Protocol::Multipart::FormData::Parser` which parses a streaming body and explicit boundary into nested arguments.
|
data/releases.md
CHANGED
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
|
Binary file
|