multipart_parser 0.0.1

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.
@@ -0,0 +1,302 @@
1
+ /* Based on node-formidable by Felix Geisendörfer
2
+ * Igor Afonov - afonov@gmail.com - 2012
3
+ * MIT License - http://www.opensource.org/licenses/mit-license.php
4
+ * Modified Benjamin Bryant - https://github.com/bhbryant/multipart_parser - 2015
5
+ */
6
+
7
+ #include "multipart_parser_c.h"
8
+
9
+ #include <stdio.h>
10
+ #include <stdarg.h>
11
+ #include <string.h>
12
+
13
+ static void multipart_log(const char * format, ...)
14
+ {
15
+ #ifdef DEBUG_MULTIPART
16
+ va_list args;
17
+ va_start(args, format);
18
+
19
+ fprintf(stderr, "[HTTP_MULTIPART_PARSER] %s:%d: ", __FILE__, __LINE__);
20
+ vfprintf(stderr, format, args);
21
+ fprintf(stderr, "\n");
22
+ #endif
23
+ }
24
+
25
+ #define NOTIFY_CB(FOR) \
26
+ do { \
27
+ if (settings->on_##FOR) { \
28
+ if (settings->on_##FOR(p) != 0) { \
29
+ return i; \
30
+ } \
31
+ } \
32
+ } while (0)
33
+
34
+ #define EMIT_DATA_CB(FOR, ptr, len) \
35
+ do { \
36
+ if (settings->on_##FOR) { \
37
+ if (settings->on_##FOR(p, ptr, len) != 0) { \
38
+ return i; \
39
+ } \
40
+ } \
41
+ } while (0)
42
+
43
+
44
+ #define LF 10
45
+ #define CR 13
46
+
47
+
48
+
49
+ enum state {
50
+ s_uninitialized = 1,
51
+ s_start,
52
+ s_start_boundary,
53
+ s_header_field_start,
54
+ s_header_field,
55
+ s_headers_almost_done,
56
+ s_header_value_start,
57
+ s_header_value,
58
+ s_header_value_almost_done,
59
+ s_part_data_start,
60
+ s_part_data,
61
+ s_part_data_almost_boundary,
62
+ s_part_data_boundary,
63
+ s_part_data_almost_end,
64
+ s_part_complete,
65
+ s_part_data_final_hyphen,
66
+ s_end
67
+ };
68
+
69
+ multipart_parser_c* multipart_parser_c_init
70
+ (const char *boundary, size_t boundary_length) {
71
+
72
+
73
+ multipart_parser_c* p = malloc(sizeof(multipart_parser_c) +
74
+ (2 * sizeof(char)) + boundary_length +
75
+ (2 * sizeof(char)) + boundary_length + 9 );
76
+
77
+
78
+ p->multipart_boundary[0] = '-';
79
+ p->multipart_boundary[1] = '-';
80
+ strcpy(&(p->multipart_boundary[2]), boundary);
81
+
82
+
83
+ p->boundary_length = (2 * sizeof(char)) + boundary_length;
84
+
85
+ p->lookbehind = (p->multipart_boundary + p->boundary_length + 1);
86
+
87
+ p->index = 0;
88
+ p->state = s_start;
89
+
90
+
91
+ return p;
92
+ }
93
+
94
+ void multipart_parser_c_free(multipart_parser_c* p) {
95
+ free(p);
96
+ }
97
+
98
+ void multipart_parser_c_set_data(multipart_parser_c *p, void *data) {
99
+ p->data = data;
100
+ }
101
+
102
+ void *multipart_parser_c_get_data(multipart_parser_c *p) {
103
+ return p->data;
104
+ }
105
+
106
+ size_t multipart_parser_c_execute(multipart_parser_c* p, const multipart_parser_c_settings* settings, const char *buf, size_t len) {
107
+ size_t i = 0;
108
+ size_t mark = 0;
109
+ char c, cl;
110
+ int is_last = 0;
111
+
112
+ while(i < len) {
113
+ c = buf[i];
114
+ is_last = (i == (len - 1));
115
+ switch (p->state) {
116
+ case s_start:
117
+ multipart_log("s_start");
118
+ p->index = 0;
119
+ NOTIFY_CB(message_begin);
120
+ p->state = s_start_boundary;
121
+
122
+ /* fallthrough */
123
+ case s_start_boundary:
124
+ multipart_log("s_start_boundary");
125
+ if (p->index == p->boundary_length) {
126
+ if (c != CR) {
127
+ return i;
128
+ }
129
+ p->index++;
130
+ break;
131
+ } else if (p->index == (p->boundary_length + 1)) {
132
+ if (c != LF) {
133
+ return i;
134
+ }
135
+ p->index = 0;
136
+ NOTIFY_CB(part_begin);
137
+ p->state = s_header_field_start;
138
+ break;
139
+ }
140
+ if (c != p->multipart_boundary[p->index]) {
141
+ return i;
142
+ }
143
+ p->index++;
144
+ break;
145
+
146
+ case s_header_field_start:
147
+ multipart_log("s_header_field_start");
148
+ mark = i;
149
+ p->state = s_header_field;
150
+
151
+ /* fallthrough */
152
+ case s_header_field:
153
+ multipart_log("s_header_field");
154
+ if (c == CR) {
155
+ p->state = s_headers_almost_done;
156
+ break;
157
+ }
158
+
159
+ if (c == ':') {
160
+ EMIT_DATA_CB(header_field, buf + mark, i - mark);
161
+ p->state = s_header_value_start;
162
+ break;
163
+ }
164
+
165
+ cl = tolower(c);
166
+ if ((c != '-') && (cl < 'a' || cl > 'z')) {
167
+ multipart_log("invalid character in header name");
168
+ return i;
169
+ }
170
+ if (is_last)
171
+ EMIT_DATA_CB(header_field, buf + mark, (i - mark) + 1);
172
+ break;
173
+
174
+ case s_headers_almost_done:
175
+ multipart_log("s_headers_almost_done");
176
+ if (c != LF) {
177
+ return i;
178
+ }
179
+
180
+ p->state = s_part_data_start;
181
+ break;
182
+
183
+ case s_header_value_start:
184
+ multipart_log("s_header_value_start");
185
+ if (c == ' ') {
186
+ break;
187
+ }
188
+
189
+ mark = i;
190
+ p->state = s_header_value;
191
+
192
+ /* fallthrough */
193
+ case s_header_value:
194
+ multipart_log("s_header_value");
195
+ if (c == CR) {
196
+ EMIT_DATA_CB(header_value, buf + mark, i - mark);
197
+ p->state = s_header_value_almost_done;
198
+ break;
199
+ }
200
+ if (is_last)
201
+ EMIT_DATA_CB(header_value, buf + mark, (i - mark) + 1);
202
+ break;
203
+
204
+ case s_header_value_almost_done:
205
+ multipart_log("s_header_value_almost_done");
206
+ if (c != LF) {
207
+ return i;
208
+ }
209
+ p->state = s_header_field_start;
210
+ break;
211
+
212
+ case s_part_data_start:
213
+ multipart_log("s_part_data_start");
214
+ NOTIFY_CB(headers_complete);
215
+ mark = i;
216
+ p->state = s_part_data;
217
+
218
+ /* fallthrough */
219
+ case s_part_data:
220
+ multipart_log("s_part_data");
221
+ if (c == CR) {
222
+ EMIT_DATA_CB(part_data, buf + mark, i - mark);
223
+ mark = i;
224
+ p->state = s_part_data_almost_boundary;
225
+ p->lookbehind[0] = CR;
226
+ break;
227
+ }
228
+ if (is_last)
229
+ EMIT_DATA_CB(part_data, buf + mark, (i - mark) + 1);
230
+ break;
231
+
232
+ case s_part_data_almost_boundary:
233
+ multipart_log("s_part_data_almost_boundary");
234
+ if (c == LF) {
235
+ p->state = s_part_data_boundary;
236
+ p->lookbehind[1] = LF;
237
+ p->index = 0;
238
+ break;
239
+ }
240
+ EMIT_DATA_CB(part_data, p->lookbehind, 1);
241
+ p->state = s_part_data;
242
+ mark = i --;
243
+ break;
244
+
245
+ case s_part_data_boundary:
246
+ multipart_log("s_part_data_boundary");
247
+ if (p->multipart_boundary[p->index] != c) {
248
+ EMIT_DATA_CB(part_data, p->lookbehind, 2 + p->index);
249
+ p->state = s_part_data;
250
+ mark = i --;
251
+ break;
252
+ }
253
+ p->lookbehind[2 + p->index] = c;
254
+ if ((++ p->index) == p->boundary_length) {
255
+ NOTIFY_CB(part_complete);
256
+ p->state = s_part_data_almost_end;
257
+ }
258
+ break;
259
+
260
+ case s_part_data_almost_end:
261
+ multipart_log("s_part_data_almost_end");
262
+ if (c == '-') {
263
+ p->state = s_part_data_final_hyphen;
264
+ break;
265
+ }
266
+ if (c == CR) {
267
+ p->state = s_part_complete;
268
+ break;
269
+ }
270
+ return i;
271
+
272
+ case s_part_data_final_hyphen:
273
+ multipart_log("s_part_data_final_hyphen");
274
+ if (c == '-') {
275
+ NOTIFY_CB(message_complete);
276
+ p->state = s_end;
277
+ break;
278
+ }
279
+ return i;
280
+
281
+ case s_part_complete:
282
+ multipart_log("s_part_complete");
283
+ if (c == LF) {
284
+ p->state = s_header_field_start;
285
+ NOTIFY_CB(part_begin);
286
+ break;
287
+ }
288
+ return i;
289
+
290
+ case s_end:
291
+ multipart_log("s_end: %02X", (int) c);
292
+ break;
293
+
294
+ default:
295
+ multipart_log("Multipart parser unrecoverable error");
296
+ return 0;
297
+ }
298
+ ++ i;
299
+ }
300
+
301
+ return len;
302
+ }
@@ -0,0 +1,67 @@
1
+ /* Based on node-formidable by Felix Geisendörfer
2
+ * Igor Afonov - afonov@gmail.com - 2012
3
+ * MIT License - http://www.opensource.org/licenses/mit-license.php
4
+ * Modified Benjamin Bryant - https://github.com/bhbryant/multipart_parser - 2015
5
+ */
6
+ #ifndef _multipart_parser_c_h
7
+ #define _multipart_parser_c_h
8
+
9
+ #ifdef __cplusplus
10
+ extern "C"
11
+ {
12
+ #endif
13
+
14
+ #include <stdlib.h>
15
+ #include <ctype.h>
16
+
17
+ typedef struct multipart_parser_c multipart_parser_c;
18
+ typedef struct multipart_parser_c_settings multipart_parser_c_settings;
19
+ typedef struct multipart_parser_c_state multipart_parser_c_state;
20
+
21
+ typedef int (*multipart_data_cb) (multipart_parser_c*, const char *at, size_t length);
22
+ typedef int (*multipart_notify_cb) (multipart_parser_c*);
23
+
24
+ struct multipart_parser_c_settings {
25
+ multipart_data_cb on_header_field;
26
+ multipart_data_cb on_header_value;
27
+ multipart_data_cb on_part_data;
28
+
29
+ multipart_notify_cb on_message_begin;
30
+ multipart_notify_cb on_part_begin;
31
+ multipart_notify_cb on_headers_complete;
32
+ multipart_notify_cb on_part_complete;
33
+ multipart_notify_cb on_message_complete;
34
+ };
35
+
36
+ struct multipart_parser_c {
37
+ void * data;
38
+
39
+ size_t index;
40
+ size_t boundary_length;
41
+
42
+ unsigned char state;
43
+
44
+
45
+ void * context; /* modified from original code to allow pointer wrapper to be passed to callbacks */
46
+
47
+ char* lookbehind;
48
+ char multipart_boundary[1];
49
+
50
+ };
51
+
52
+ /*modified from original code to move settings as an agument to execute fuction */
53
+ multipart_parser_c* multipart_parser_c_init(const char *boundary, size_t boundary_length);
54
+
55
+ void multipart_parser_c_free(multipart_parser_c* p);
56
+
57
+ /* modified from original code to allow take settings as argument */
58
+ size_t multipart_parser_c_execute(multipart_parser_c* p, const multipart_parser_c_settings* settings, const char *buf, size_t len);
59
+
60
+ void multipart_parser_c_set_data(multipart_parser_c* p, void* data);
61
+ void * multipart_parser_c_get_data(multipart_parser_c* p);
62
+
63
+ #ifdef __cplusplus
64
+ } /* extern "C" */
65
+ #endif
66
+
67
+ #endif
@@ -0,0 +1,3 @@
1
+ class MultipartParser
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,21 @@
1
+ require "multipart_parser/version"
2
+ require "multipart_parser/multipart_parser"
3
+
4
+ class MultipartParser
5
+
6
+ class << self
7
+
8
+ #Multiple headers are listed as arrays
9
+ attr_reader :default_header_value_type
10
+
11
+ def default_header_value_type=(val)
12
+ if (val != :mixed && val != :strings && val != :arrays)
13
+ raise ArgumentError, "Invalid header value type"
14
+ end
15
+ @default_header_value_type = val
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ MultipartParser.default_header_value_type = :mixed
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'multipart_parser/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "multipart_parser"
8
+ spec.version = MultipartParser::VERSION
9
+ spec.authors = ["Benjamin Bryant"]
10
+ spec.extensions = ["ext/multipart_parser/extconf.rb"]
11
+ spec.summary = %q{multipart/form-data parser}
12
+ spec.description = %q{Ruby bindings for https://github.com/iafonov/multipart-parser-c}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rake-compiler"
24
+ spec.add_development_dependency "rspec"
25
+ end
@@ -0,0 +1,157 @@
1
+ require 'spec_helper'
2
+
3
+ describe MultipartParser do
4
+
5
+ let(:handler) { proc {} }
6
+
7
+ def execute(parser)
8
+ parser << "--Boundary+17376C87E2579930\r\nContent-Disposition: form-data; name=ID; paramName=TEST_PARAM_1\r\nContent-Transfer-Encoding: binary\r\nContent-Type: text/plain\r\n\r\nfirst frame\r\n";
9
+ parser << "--Boundary+17376C87E2579930\r\nContent-Disposition: form-data; name=ID; paramName=TEST_PARAM_2\r\nContent-Transfer-Encoding: binary\r\nContent-Type: text/plain\r\n\r\nsecond frame\r\n";
10
+ parser << "--Boundary+17376C87E2579930--\r\n"
11
+ end
12
+
13
+
14
+
15
+ context "with assigned callbacks" do
16
+ let(:parser) { MultipartParser.new "Boundary+17376C87E2579930" }
17
+
18
+ it "emits message_begin" do
19
+ parser.on_message_begin = handler
20
+
21
+ expect(handler).to receive(:call).once
22
+
23
+ execute(parser)
24
+ end
25
+
26
+ it "emits part_begin" do
27
+ parser.on_part_begin = handler
28
+
29
+ expect(handler).to receive(:call).twice
30
+
31
+ execute(parser)
32
+ end
33
+
34
+ it "parse multipart headers" do
35
+ parser.on_headers_complete = handler
36
+
37
+
38
+ expect(handler).to receive(:call).with({"Content-Disposition"=>"form-data; name=ID; paramName=TEST_PARAM_1", "Content-Transfer-Encoding"=>"binary", "Content-Type"=>"text/plain"})
39
+ expect(handler).to receive(:call).with({"Content-Disposition"=>"form-data; name=ID; paramName=TEST_PARAM_2", "Content-Transfer-Encoding"=>"binary", "Content-Type"=>"text/plain"})
40
+
41
+ execute(parser)
42
+ end
43
+
44
+ it "parses multipart data" do
45
+
46
+ parser.on_data = handler
47
+
48
+ expect(handler).to receive(:call).with("first frame")
49
+ expect(handler).to receive(:call).with("second frame")
50
+
51
+ execute(parser)
52
+ end
53
+
54
+ it "emits part_complete" do
55
+ parser.on_part_complete = handler
56
+
57
+ expect(handler).to receive(:call).twice
58
+
59
+ execute(parser)
60
+ end
61
+
62
+ it "emits message_complete" do
63
+ parser.on_message_complete = handler
64
+
65
+ expect(handler).to receive(:call).once
66
+
67
+ execute(parser)
68
+ end
69
+
70
+
71
+ end
72
+
73
+ context "with callback object" do
74
+ let(:callback_obj) { double("Callback")}
75
+ let(:parser) { MultipartParser.new "Boundary+17376C87E2579930", callback_obj }
76
+
77
+
78
+ it "emits message_begin" do
79
+ expect(callback_obj).to receive(:on_message_begin).once
80
+
81
+ execute(parser)
82
+ end
83
+
84
+ it "emits part_begin" do
85
+ expect(callback_obj).to receive(:on_part_begin).twice
86
+
87
+ execute(parser)
88
+ end
89
+
90
+
91
+ it "parse multipart headers" do
92
+ expect(callback_obj).to receive(:on_headers_complete).with({"Content-Disposition"=>"form-data; name=ID; paramName=TEST_PARAM_1", "Content-Transfer-Encoding"=>"binary", "Content-Type"=>"text/plain"})
93
+ expect(callback_obj).to receive(:on_headers_complete).with({"Content-Disposition"=>"form-data; name=ID; paramName=TEST_PARAM_2", "Content-Transfer-Encoding"=>"binary", "Content-Type"=>"text/plain"})
94
+
95
+ execute(parser)
96
+ end
97
+
98
+
99
+ it "parses multipart data" do
100
+
101
+ expect(callback_obj).to receive(:on_data).with("first frame")
102
+ expect(callback_obj).to receive(:on_data).with("second frame")
103
+
104
+ execute(parser)
105
+ end
106
+
107
+ it "emits part_complete" do
108
+ expect(callback_obj).to receive(:on_part_complete).twice
109
+
110
+ execute(parser)
111
+ end
112
+
113
+ it "emits message_complete" do
114
+ expect(callback_obj).to receive(:on_message_complete).once
115
+
116
+ execute(parser)
117
+ end
118
+
119
+ end
120
+
121
+ it "allows the type of the header values to be configured" do
122
+
123
+ parser = MultipartParser.new("Boundary+17376C87E2579930", nil, :arrays)
124
+ parser.on_headers_complete = handler
125
+
126
+ expect(handler).to receive(:call).with({"Content-Disposition"=>["form-data; name=ID_A", "form-data; name=ID_B"], "Content-Type"=>["text/plain"]})
127
+
128
+ parser << "--Boundary+17376C87E2579930\r\nContent-Disposition: form-data; name=ID_A\r\nContent-Disposition: form-data; name=ID_B\r\nContent-Type: text/plain\r\n\r\ntext\r\n"
129
+
130
+ end
131
+
132
+ it "allows the default header value type to be set" do
133
+ value_type = MultipartParser.default_header_value_type
134
+
135
+ MultipartParser.default_header_value_type = :arrays
136
+
137
+ parser = MultipartParser.new("Boundary+17376C87E2579930")
138
+
139
+
140
+ parser.on_headers_complete = handler
141
+
142
+ expect(handler).to receive(:call).with({"Content-Disposition"=>["form-data; name=ID_A", "form-data; name=ID_B"], "Content-Type"=>["text/plain"]})
143
+
144
+ parser << "--Boundary+17376C87E2579930\r\nContent-Disposition: form-data; name=ID_A\r\nContent-Disposition: form-data; name=ID_B\r\nContent-Type: text/plain\r\n\r\ntext\r\n"
145
+
146
+ ## reset
147
+ MultipartParser.default_header_value_type = value_type
148
+ end
149
+
150
+ it 'has a version number' do
151
+ expect(MultipartParser::VERSION).not_to be nil
152
+ end
153
+
154
+ specify { expect { MultipartParser.new }.to raise_error(ArgumentError) }
155
+
156
+ end
157
+
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'multipart_parser'
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multipart_parser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Benjamin Bryant
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-compiler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Ruby bindings for https://github.com/iafonov/multipart-parser-c
70
+ email:
71
+ executables: []
72
+ extensions:
73
+ - ext/multipart_parser/extconf.rb
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - ".travis.yml"
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - ext/multipart_parser/ext_help.h
84
+ - ext/multipart_parser/extconf.rb
85
+ - ext/multipart_parser/multipart_parser.c
86
+ - ext/multipart_parser/multipart_parser_c.c
87
+ - ext/multipart_parser/multipart_parser_c.h
88
+ - lib/multipart_parser.rb
89
+ - lib/multipart_parser/version.rb
90
+ - multipart_parser.gemspec
91
+ - spec/multipart_parser_spec.rb
92
+ - spec/spec_helper.rb
93
+ homepage: ''
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.4.3
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: multipart/form-data parser
117
+ test_files:
118
+ - spec/multipart_parser_spec.rb
119
+ - spec/spec_helper.rb