http_parser.rb 0.5.1 → 0.5.3
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.
- data/.gitmodules +1 -1
- data/Gemfile.lock +6 -6
- data/LICENSE-MIT +20 -0
- data/README.md +1 -1
- data/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java +68 -11
- data/ext/ruby_http_parser/ruby_http_parser.c +80 -8
- data/ext/ruby_http_parser/vendor/http-parser/LICENSE-MIT +1 -1
- data/ext/ruby_http_parser/vendor/http-parser/http_parser.c +52 -10
- data/ext/ruby_http_parser/vendor/http-parser/http_parser.h +3 -1
- data/ext/ruby_http_parser/vendor/http-parser/test.c +89 -3
- data/ext/ruby_http_parser/vendor/http-parser-java/LICENSE-MIT +26 -1
- data/ext/ruby_http_parser/vendor/http-parser-java/README.md +23 -143
- data/ext/ruby_http_parser/vendor/http-parser-java/TODO +3 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/build.xml +74 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/ext/primitives.jar +0 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/http_parser.c +115 -61
- data/ext/ruby_http_parser/vendor/http-parser-java/http_parser.h +19 -3
- data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPCallback.java +8 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPDataCallback.java +34 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPErrorCallback.java +12 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPException.java +4 -2
- data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPMethod.java +64 -52
- data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParser.java +5 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserSettings.java +323 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/{lolevel/Util.java → Util.java} +27 -28
- data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPParser.java +259 -85
- data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/ParserSettings.java +1 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Message.java +324 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Requests.java +69 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Responses.java +51 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Test.java +15 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestHeaderOverflowError.java +47 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestLoaderNG.java +183 -447
- data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestNoOverflowLongBody.java +61 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/UnitTest.java +2 -1
- data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Upgrade.java +26 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Util.java +165 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/WrongContentLength.java +58 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/test.c +232 -29
- data/ext/ruby_http_parser/vendor/http-parser-java/test_permutations +1 -1
- data/ext/ruby_http_parser/vendor/http-parser-java/test_unit +1 -1
- data/ext/ruby_http_parser/vendor/http-parser-java/test_utf8 +1 -0
- data/ext/ruby_http_parser/vendor/http-parser-java/tests.dumped +154 -7
- data/ext/ruby_http_parser/vendor/http-parser-java/tests.utf8 +17 -0
- data/http_parser.rb.gemspec +8 -2
- data/lib/http_parser.rb +17 -0
- data/spec/parser_spec.rb +116 -6
- data/tasks/compile.rake +3 -1
- data/tasks/fixtures.rake +1 -1
- metadata +169 -125
- data/ext/ruby_http_parser/vendor/http-parser-java/CONTRIBUTIONS +0 -4
data/.gitmodules
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[submodule "ext/ruby_http_parser/vendor/http-parser"]
|
|
2
2
|
path = ext/ruby_http_parser/vendor/http-parser
|
|
3
|
-
url = git://github.com/
|
|
3
|
+
url = git://github.com/joyent/http-parser.git
|
|
4
4
|
[submodule "ext/ruby_http_parser/vendor/http-parser-java"]
|
|
5
5
|
path = ext/ruby_http_parser/vendor/http-parser-java
|
|
6
6
|
url = git://github.com/a2800276/http-parser.java.git
|
data/Gemfile.lock
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
http_parser.rb (0.5.
|
|
4
|
+
http_parser.rb (0.5.2)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: http://rubygems.org/
|
|
8
8
|
specs:
|
|
9
9
|
diff-lcs (1.1.2)
|
|
10
10
|
json (1.5.1)
|
|
11
|
-
|
|
12
|
-
rake (0.
|
|
13
|
-
rake-compiler (0.7.5)
|
|
11
|
+
rake (0.9.2)
|
|
12
|
+
rake-compiler (0.7.9)
|
|
14
13
|
rake
|
|
15
14
|
rspec (2.4.0)
|
|
16
15
|
rspec-core (~> 2.4.0)
|
|
@@ -20,13 +19,14 @@ GEM
|
|
|
20
19
|
rspec-expectations (2.4.0)
|
|
21
20
|
diff-lcs (~> 1.1.2)
|
|
22
21
|
rspec-mocks (2.4.0)
|
|
22
|
+
yajl-ruby (0.8.2)
|
|
23
23
|
|
|
24
24
|
PLATFORMS
|
|
25
|
-
java
|
|
26
25
|
ruby
|
|
27
26
|
|
|
28
27
|
DEPENDENCIES
|
|
29
28
|
http_parser.rb!
|
|
30
29
|
json (>= 1.4.6)
|
|
31
|
-
rake-compiler (>= 0.7.
|
|
30
|
+
rake-compiler (>= 0.7.9)
|
|
32
31
|
rspec (>= 2.0.1)
|
|
32
|
+
yajl-ruby (>= 0.8.1)
|
data/LICENSE-MIT
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2009,2010 Marc-André Cournoyer <macournoyer@gmail.com>
|
|
2
|
+
Copyright 2010,2011 Aman Gupta <aman@tmm1.net>
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to
|
|
6
|
+
deal in the Software without restriction, including without limitation the
|
|
7
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
8
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
|
12
|
+
all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
19
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
20
|
+
IN THE SOFTWARE.
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A simple callback-based HTTP request/response parser for writing http
|
|
4
4
|
servers, clients and proxies.
|
|
5
5
|
|
|
6
|
-
This gem is built on top of [
|
|
6
|
+
This gem is built on top of [joyent/http-parser](http://github.com/joyent/http-parser) and its java port [a2800276/http-parser.java](http://github.com/a2800276/http-parser.java).
|
|
7
7
|
|
|
8
8
|
## Supported Platforms
|
|
9
9
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package org.ruby_http_parser;
|
|
2
2
|
|
|
3
3
|
import org.jruby.Ruby;
|
|
4
|
+
import org.jruby.RubyArray;
|
|
4
5
|
import org.jruby.RubyClass;
|
|
5
6
|
import org.jruby.RubyHash;
|
|
6
7
|
import org.jruby.RubyModule;
|
|
@@ -59,6 +60,9 @@ public class RubyHttpParser extends RubyObject {
|
|
|
59
60
|
private IRubyObject queryString;
|
|
60
61
|
private IRubyObject fragment;
|
|
61
62
|
|
|
63
|
+
private IRubyObject header_value_type;
|
|
64
|
+
private IRubyObject upgradeData;
|
|
65
|
+
|
|
62
66
|
private IRubyObject callback_object;
|
|
63
67
|
|
|
64
68
|
private String _current_header;
|
|
@@ -77,6 +81,8 @@ public class RubyHttpParser extends RubyObject {
|
|
|
77
81
|
|
|
78
82
|
this.callback_object = null;
|
|
79
83
|
|
|
84
|
+
this.header_value_type = runtime.getModule("HTTP").getClass("Parser").getInstanceVariable("@default_header_value_type");
|
|
85
|
+
|
|
80
86
|
initSettings();
|
|
81
87
|
init();
|
|
82
88
|
}
|
|
@@ -130,25 +136,45 @@ public class RubyHttpParser extends RubyObject {
|
|
|
130
136
|
byte[] data = fetchBytes(buf, pos, len);
|
|
131
137
|
ThreadContext context = headers.getRuntime().getCurrentContext();
|
|
132
138
|
IRubyObject key, val;
|
|
139
|
+
int new_field = 0;
|
|
133
140
|
|
|
134
141
|
if (_current_header != null) {
|
|
142
|
+
new_field = 1;
|
|
135
143
|
_last_header = _current_header;
|
|
136
144
|
_current_header = null;
|
|
145
|
+
}
|
|
137
146
|
|
|
138
|
-
|
|
147
|
+
key = (IRubyObject)runtime.newString(_last_header);
|
|
148
|
+
val = headers.op_aref(context, key);
|
|
149
|
+
|
|
150
|
+
if (new_field == 1) {
|
|
151
|
+
if (val.isNil()) {
|
|
152
|
+
if (header_value_type == runtime.newSymbol("arrays")) {
|
|
153
|
+
headers.op_aset(context, key, RubyArray.newArrayLight(runtime, runtime.newString("")));
|
|
154
|
+
} else {
|
|
155
|
+
headers.op_aset(context, key, runtime.newString(""));
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
if (header_value_type == runtime.newSymbol("mixed")) {
|
|
159
|
+
if (val instanceof RubyString) {
|
|
160
|
+
headers.op_aset(context, key, RubyArray.newArrayLight(runtime, val, runtime.newString("")));
|
|
161
|
+
} else {
|
|
162
|
+
((RubyArray)val).add(runtime.newString(""));
|
|
163
|
+
}
|
|
164
|
+
} else if (header_value_type == runtime.newSymbol("arrays")) {
|
|
165
|
+
((RubyArray)val).add(runtime.newString(""));
|
|
166
|
+
} else {
|
|
167
|
+
((RubyString)val).cat(", ".getBytes());
|
|
168
|
+
}
|
|
169
|
+
}
|
|
139
170
|
val = headers.op_aref(context, key);
|
|
171
|
+
}
|
|
140
172
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
} else {
|
|
144
|
-
key = (IRubyObject)runtime.newString(_last_header);
|
|
145
|
-
val = headers.op_aref(context, key);
|
|
173
|
+
if (val instanceof RubyArray) {
|
|
174
|
+
val = ((RubyArray)val).entry(-1);
|
|
146
175
|
}
|
|
147
176
|
|
|
148
|
-
|
|
149
|
-
headers.op_aset(context, key, runtime.newString(new String(data)));
|
|
150
|
-
else
|
|
151
|
-
((RubyString)val).cat(data);
|
|
177
|
+
((RubyString)val).cat(data);
|
|
152
178
|
|
|
153
179
|
return 0;
|
|
154
180
|
}
|
|
@@ -163,6 +189,8 @@ public class RubyHttpParser extends RubyObject {
|
|
|
163
189
|
queryString = runtime.newString("");
|
|
164
190
|
fragment = runtime.newString("");
|
|
165
191
|
|
|
192
|
+
upgradeData = runtime.newString("");
|
|
193
|
+
|
|
166
194
|
IRubyObject ret = runtime.getNil();
|
|
167
195
|
|
|
168
196
|
if (callback_object != null) {
|
|
@@ -256,6 +284,8 @@ public class RubyHttpParser extends RubyObject {
|
|
|
256
284
|
this.requestPath = runtime.getNil();
|
|
257
285
|
this.queryString = runtime.getNil();
|
|
258
286
|
this.fragment = runtime.getNil();
|
|
287
|
+
|
|
288
|
+
this.upgradeData = runtime.getNil();
|
|
259
289
|
}
|
|
260
290
|
|
|
261
291
|
@JRubyMethod(name = "initialize")
|
|
@@ -269,6 +299,12 @@ public class RubyHttpParser extends RubyObject {
|
|
|
269
299
|
return initialize();
|
|
270
300
|
}
|
|
271
301
|
|
|
302
|
+
@JRubyMethod(name = "initialize")
|
|
303
|
+
public IRubyObject initialize(IRubyObject arg, IRubyObject arg2) {
|
|
304
|
+
header_value_type = arg2;
|
|
305
|
+
return initialize(arg);
|
|
306
|
+
}
|
|
307
|
+
|
|
272
308
|
@JRubyMethod(name = "on_message_begin=")
|
|
273
309
|
public IRubyObject set_on_message_begin(IRubyObject cb) {
|
|
274
310
|
on_message_begin = cb;
|
|
@@ -308,7 +344,9 @@ public class RubyHttpParser extends RubyObject {
|
|
|
308
344
|
}
|
|
309
345
|
|
|
310
346
|
if (parser.getUpgrade()) {
|
|
311
|
-
|
|
347
|
+
byte[] upData = fetchBytes(buf, buf.position(), buf.limit() - buf.position());
|
|
348
|
+
((RubyString)upgradeData).concat(runtime.newString(new String(upData)));
|
|
349
|
+
|
|
312
350
|
} else if (buf.hasRemaining()) {
|
|
313
351
|
if (!stopped)
|
|
314
352
|
throw new RaiseException(runtime, eParserError, "Could not parse data entirely", true);
|
|
@@ -394,6 +432,25 @@ public class RubyHttpParser extends RubyObject {
|
|
|
394
432
|
return fragment == null ? runtime.getNil() : fragment;
|
|
395
433
|
}
|
|
396
434
|
|
|
435
|
+
@JRubyMethod(name = "header_value_type")
|
|
436
|
+
public IRubyObject getHeaderValueType() {
|
|
437
|
+
return header_value_type == null ? runtime.getNil() : header_value_type;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
@JRubyMethod(name = "header_value_type=")
|
|
441
|
+
public IRubyObject set_header_value_type(IRubyObject val) {
|
|
442
|
+
if (val != runtime.newSymbol("mixed") && val != runtime.newSymbol("arrays") && val != runtime.newSymbol("strings")) {
|
|
443
|
+
throw runtime.newArgumentError("Invalid header value type");
|
|
444
|
+
}
|
|
445
|
+
header_value_type = val;
|
|
446
|
+
return val;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
@JRubyMethod(name = "upgrade_data")
|
|
450
|
+
public IRubyObject upgradeData() {
|
|
451
|
+
return upgradeData == null ? runtime.getNil() : upgradeData;
|
|
452
|
+
}
|
|
453
|
+
|
|
397
454
|
@JRubyMethod(name = "reset!")
|
|
398
455
|
public IRubyObject reset() {
|
|
399
456
|
init();
|
|
@@ -23,6 +23,8 @@ typedef struct ParserWrapper {
|
|
|
23
23
|
|
|
24
24
|
VALUE headers;
|
|
25
25
|
|
|
26
|
+
VALUE upgrade_data;
|
|
27
|
+
|
|
26
28
|
VALUE on_message_begin;
|
|
27
29
|
VALUE on_headers_complete;
|
|
28
30
|
VALUE on_body;
|
|
@@ -32,6 +34,8 @@ typedef struct ParserWrapper {
|
|
|
32
34
|
VALUE stopped;
|
|
33
35
|
VALUE completed;
|
|
34
36
|
|
|
37
|
+
VALUE header_value_type;
|
|
38
|
+
|
|
35
39
|
VALUE last_field_name;
|
|
36
40
|
VALUE curr_field_name;
|
|
37
41
|
|
|
@@ -49,6 +53,8 @@ void ParserWrapper_init(ParserWrapper *wrapper) {
|
|
|
49
53
|
wrapper->query_string = Qnil;
|
|
50
54
|
wrapper->fragment = Qnil;
|
|
51
55
|
|
|
56
|
+
wrapper->upgrade_data = Qnil;
|
|
57
|
+
|
|
52
58
|
wrapper->headers = Qnil;
|
|
53
59
|
wrapper->completed = Qfalse;
|
|
54
60
|
|
|
@@ -63,6 +69,7 @@ void ParserWrapper_mark(void *data) {
|
|
|
63
69
|
rb_gc_mark_maybe(wrapper->request_path);
|
|
64
70
|
rb_gc_mark_maybe(wrapper->query_string);
|
|
65
71
|
rb_gc_mark_maybe(wrapper->fragment);
|
|
72
|
+
rb_gc_mark_maybe(wrapper->upgrade_data);
|
|
66
73
|
rb_gc_mark_maybe(wrapper->headers);
|
|
67
74
|
rb_gc_mark_maybe(wrapper->on_message_begin);
|
|
68
75
|
rb_gc_mark_maybe(wrapper->on_headers_complete);
|
|
@@ -93,6 +100,10 @@ static ID Ion_body;
|
|
|
93
100
|
static ID Ion_message_complete;
|
|
94
101
|
|
|
95
102
|
static VALUE Sstop;
|
|
103
|
+
static VALUE Sreset;
|
|
104
|
+
static VALUE Sarrays;
|
|
105
|
+
static VALUE Sstrings;
|
|
106
|
+
static VALUE Smixed;
|
|
96
107
|
|
|
97
108
|
/** Callbacks **/
|
|
98
109
|
|
|
@@ -104,6 +115,7 @@ int on_message_begin(ryah_http_parser *parser) {
|
|
|
104
115
|
wrapper->query_string = rb_str_new2("");
|
|
105
116
|
wrapper->fragment = rb_str_new2("");
|
|
106
117
|
wrapper->headers = rb_hash_new();
|
|
118
|
+
wrapper->upgrade_data = rb_str_new2("");
|
|
107
119
|
|
|
108
120
|
VALUE ret = Qnil;
|
|
109
121
|
|
|
@@ -161,17 +173,45 @@ int on_header_field(ryah_http_parser *parser, const char *at, size_t length) {
|
|
|
161
173
|
int on_header_value(ryah_http_parser *parser, const char *at, size_t length) {
|
|
162
174
|
GET_WRAPPER(wrapper, parser);
|
|
163
175
|
|
|
176
|
+
int new_field = 0;
|
|
177
|
+
VALUE current_value;
|
|
178
|
+
|
|
164
179
|
if (wrapper->last_field_name == Qnil) {
|
|
180
|
+
new_field = 1;
|
|
165
181
|
wrapper->last_field_name = wrapper->curr_field_name;
|
|
166
182
|
wrapper->curr_field_name = Qnil;
|
|
183
|
+
}
|
|
167
184
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
185
|
+
current_value = rb_hash_aref(wrapper->headers, wrapper->last_field_name);
|
|
186
|
+
|
|
187
|
+
if (new_field == 1) {
|
|
188
|
+
if (current_value == Qnil) {
|
|
189
|
+
if (wrapper->header_value_type == Sarrays) {
|
|
190
|
+
rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_ary_new3(1, rb_str_new2("")));
|
|
191
|
+
} else {
|
|
192
|
+
rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_str_new2(""));
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
if (wrapper->header_value_type == Smixed) {
|
|
196
|
+
if (TYPE(current_value) == T_STRING) {
|
|
197
|
+
rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_ary_new3(2, current_value, rb_str_new2("")));
|
|
198
|
+
} else {
|
|
199
|
+
rb_ary_push(current_value, rb_str_new2(""));
|
|
200
|
+
}
|
|
201
|
+
} else if (wrapper->header_value_type == Sarrays) {
|
|
202
|
+
rb_ary_push(current_value, rb_str_new2(""));
|
|
203
|
+
} else {
|
|
204
|
+
rb_str_cat(current_value, ", ", 2);
|
|
205
|
+
}
|
|
171
206
|
}
|
|
207
|
+
current_value = rb_hash_aref(wrapper->headers, wrapper->last_field_name);
|
|
172
208
|
}
|
|
173
209
|
|
|
174
|
-
|
|
210
|
+
if (TYPE(current_value) == T_ARRAY) {
|
|
211
|
+
current_value = rb_ary_entry(current_value, -1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
rb_str_cat(current_value, at, length);
|
|
175
215
|
|
|
176
216
|
return 0;
|
|
177
217
|
}
|
|
@@ -190,6 +230,8 @@ int on_headers_complete(ryah_http_parser *parser) {
|
|
|
190
230
|
if (ret == Sstop) {
|
|
191
231
|
wrapper->stopped = Qtrue;
|
|
192
232
|
return -1;
|
|
233
|
+
} else if (ret == Sreset){
|
|
234
|
+
return 1;
|
|
193
235
|
} else {
|
|
194
236
|
return 0;
|
|
195
237
|
}
|
|
@@ -280,8 +322,16 @@ VALUE Parser_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
280
322
|
ParserWrapper *wrapper = NULL;
|
|
281
323
|
DATA_GET(self, ParserWrapper, wrapper);
|
|
282
324
|
|
|
283
|
-
|
|
325
|
+
wrapper->header_value_type = rb_iv_get(CLASS_OF(self), "@default_header_value_type");
|
|
326
|
+
|
|
327
|
+
if (argc == 1) {
|
|
328
|
+
wrapper->callback_object = argv[0];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (argc == 2) {
|
|
284
332
|
wrapper->callback_object = argv[0];
|
|
333
|
+
wrapper->header_value_type = argv[1];
|
|
334
|
+
}
|
|
285
335
|
|
|
286
336
|
return self;
|
|
287
337
|
}
|
|
@@ -299,8 +349,9 @@ VALUE Parser_execute(VALUE self, VALUE data) {
|
|
|
299
349
|
size_t nparsed = ryah_http_parser_execute(&wrapper->parser, &settings, ptr, len);
|
|
300
350
|
|
|
301
351
|
if (wrapper->parser.upgrade) {
|
|
302
|
-
|
|
303
|
-
|
|
352
|
+
rb_str_cat(wrapper->upgrade_data, ptr + nparsed + 1, len - nparsed - 1);
|
|
353
|
+
|
|
354
|
+
} else if (nparsed != (size_t)len) {
|
|
304
355
|
if (!RTEST(wrapper->stopped) && !RTEST(wrapper->completed))
|
|
305
356
|
rb_raise(eParserError, "Could not parse data entirely");
|
|
306
357
|
else
|
|
@@ -353,7 +404,7 @@ VALUE Parser_upgrade_p(VALUE self) {
|
|
|
353
404
|
ParserWrapper *wrapper = NULL;
|
|
354
405
|
DATA_GET(self, ParserWrapper, wrapper);
|
|
355
406
|
|
|
356
|
-
return wrapper->parser.upgrade
|
|
407
|
+
return wrapper->parser.upgrade ? Qtrue : Qfalse;
|
|
357
408
|
}
|
|
358
409
|
|
|
359
410
|
VALUE Parser_http_version(VALUE self) {
|
|
@@ -418,6 +469,19 @@ DEFINE_GETTER(request_path);
|
|
|
418
469
|
DEFINE_GETTER(query_string);
|
|
419
470
|
DEFINE_GETTER(fragment);
|
|
420
471
|
DEFINE_GETTER(headers);
|
|
472
|
+
DEFINE_GETTER(upgrade_data);
|
|
473
|
+
DEFINE_GETTER(header_value_type);
|
|
474
|
+
|
|
475
|
+
VALUE Parser_set_header_value_type(VALUE self, VALUE val) {
|
|
476
|
+
if (val != Sarrays && val != Sstrings && val != Smixed) {
|
|
477
|
+
rb_raise(rb_eArgError, "Invalid header value type");
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
ParserWrapper *wrapper = NULL;
|
|
481
|
+
DATA_GET(self, ParserWrapper, wrapper);
|
|
482
|
+
wrapper->header_value_type = val;
|
|
483
|
+
return wrapper->header_value_type;
|
|
484
|
+
}
|
|
421
485
|
|
|
422
486
|
VALUE Parser_reset(VALUE self) {
|
|
423
487
|
ParserWrapper *wrapper = NULL;
|
|
@@ -441,6 +505,11 @@ void Init_ruby_http_parser() {
|
|
|
441
505
|
Ion_body = rb_intern("on_body");
|
|
442
506
|
Ion_message_complete = rb_intern("on_message_complete");
|
|
443
507
|
Sstop = ID2SYM(rb_intern("stop"));
|
|
508
|
+
Sreset = ID2SYM(rb_intern("reset"));
|
|
509
|
+
|
|
510
|
+
Sarrays = ID2SYM(rb_intern("arrays"));
|
|
511
|
+
Sstrings = ID2SYM(rb_intern("strings"));
|
|
512
|
+
Smixed = ID2SYM(rb_intern("mixed"));
|
|
444
513
|
|
|
445
514
|
rb_define_alloc_func(cParser, Parser_alloc);
|
|
446
515
|
rb_define_alloc_func(cRequestParser, RequestParser_alloc);
|
|
@@ -469,6 +538,9 @@ void Init_ruby_http_parser() {
|
|
|
469
538
|
rb_define_method(cParser, "query_string", Parser_query_string, 0);
|
|
470
539
|
rb_define_method(cParser, "fragment", Parser_fragment, 0);
|
|
471
540
|
rb_define_method(cParser, "headers", Parser_headers, 0);
|
|
541
|
+
rb_define_method(cParser, "upgrade_data", Parser_upgrade_data, 0);
|
|
542
|
+
rb_define_method(cParser, "header_value_type", Parser_header_value_type, 0);
|
|
543
|
+
rb_define_method(cParser, "header_value_type=", Parser_set_header_value_type, 1);
|
|
472
544
|
|
|
473
545
|
rb_define_method(cParser, "reset!", Parser_reset, 0);
|
|
474
546
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright
|
|
1
|
+
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
|
2
2
|
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
4
|
of this software and associated documentation files (the "Software"), to
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Copyright
|
|
1
|
+
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
|
2
2
|
*
|
|
3
3
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
4
|
* of this software and associated documentation files (the "Software"), to
|
|
@@ -186,7 +186,28 @@ static const uint8_t normal_url_char[256] = {
|
|
|
186
186
|
/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
|
|
187
187
|
1, 1, 1, 1, 1, 1, 1, 1,
|
|
188
188
|
/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
|
|
189
|
-
1, 1, 1, 1, 1, 1, 1, 0
|
|
189
|
+
1, 1, 1, 1, 1, 1, 1, 0,
|
|
190
|
+
|
|
191
|
+
/* Remainder of non-ASCII range are accepted as-is to support implicitly UTF-8
|
|
192
|
+
encoded paths. This is out of spec, but clients generate this and most other
|
|
193
|
+
HTTP servers support it. We should, too. */
|
|
194
|
+
|
|
195
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
196
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
197
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
198
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
199
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
200
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
201
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
202
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
203
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
204
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
205
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
206
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
207
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
208
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
209
|
+
1, 1, 1, 1, 1, 1, 1, 1,
|
|
210
|
+
1, 1, 1, 1, 1, 1, 1, 1 };
|
|
190
211
|
|
|
191
212
|
|
|
192
213
|
enum state
|
|
@@ -240,15 +261,17 @@ enum state
|
|
|
240
261
|
|
|
241
262
|
, s_header_almost_done
|
|
242
263
|
|
|
264
|
+
, s_chunk_size_start
|
|
265
|
+
, s_chunk_size
|
|
266
|
+
, s_chunk_parameters
|
|
267
|
+
, s_chunk_size_almost_done
|
|
268
|
+
|
|
243
269
|
, s_headers_almost_done
|
|
244
270
|
/* Important: 's_headers_almost_done' must be the last 'header' state. All
|
|
245
271
|
* states beyond this must be 'body' states. It is used for overflow
|
|
246
272
|
* checking. See the PARSING_HEADER() macro.
|
|
247
273
|
*/
|
|
248
|
-
|
|
249
|
-
, s_chunk_size
|
|
250
|
-
, s_chunk_size_almost_done
|
|
251
|
-
, s_chunk_parameters
|
|
274
|
+
|
|
252
275
|
, s_chunk_data
|
|
253
276
|
, s_chunk_data_almost_done
|
|
254
277
|
, s_chunk_data_done
|
|
@@ -258,7 +281,7 @@ enum state
|
|
|
258
281
|
};
|
|
259
282
|
|
|
260
283
|
|
|
261
|
-
#define PARSING_HEADER(state) (state <= s_headers_almost_done
|
|
284
|
+
#define PARSING_HEADER(state) (state <= s_headers_almost_done)
|
|
262
285
|
|
|
263
286
|
|
|
264
287
|
enum header_states
|
|
@@ -331,10 +354,20 @@ size_t http_parser_execute (http_parser *parser,
|
|
|
331
354
|
uint64_t nread = parser->nread;
|
|
332
355
|
|
|
333
356
|
if (len == 0) {
|
|
334
|
-
|
|
335
|
-
|
|
357
|
+
switch (state) {
|
|
358
|
+
case s_body_identity_eof:
|
|
359
|
+
CALLBACK2(message_complete);
|
|
360
|
+
return 0;
|
|
361
|
+
|
|
362
|
+
case s_dead:
|
|
363
|
+
case s_start_req_or_res:
|
|
364
|
+
case s_start_res:
|
|
365
|
+
case s_start_req:
|
|
366
|
+
return 0;
|
|
367
|
+
|
|
368
|
+
default:
|
|
369
|
+
return 1; // error
|
|
336
370
|
}
|
|
337
|
-
return 0;
|
|
338
371
|
}
|
|
339
372
|
|
|
340
373
|
/* technically we could combine all of these (except for url_mark) into one
|
|
@@ -707,6 +740,9 @@ size_t http_parser_execute (http_parser *parser,
|
|
|
707
740
|
CALLBACK(url);
|
|
708
741
|
state = s_req_http_start;
|
|
709
742
|
break;
|
|
743
|
+
case '?':
|
|
744
|
+
state = s_req_query_string_start;
|
|
745
|
+
break;
|
|
710
746
|
default:
|
|
711
747
|
goto error;
|
|
712
748
|
}
|
|
@@ -729,6 +765,9 @@ size_t http_parser_execute (http_parser *parser,
|
|
|
729
765
|
CALLBACK(url);
|
|
730
766
|
state = s_req_http_start;
|
|
731
767
|
break;
|
|
768
|
+
case '?':
|
|
769
|
+
state = s_req_query_string_start;
|
|
770
|
+
break;
|
|
732
771
|
default:
|
|
733
772
|
goto error;
|
|
734
773
|
}
|
|
@@ -1448,6 +1487,7 @@ size_t http_parser_execute (http_parser *parser,
|
|
|
1448
1487
|
|
|
1449
1488
|
case s_chunk_size_start:
|
|
1450
1489
|
{
|
|
1490
|
+
assert(nread == 1);
|
|
1451
1491
|
assert(parser->flags & F_CHUNKED);
|
|
1452
1492
|
|
|
1453
1493
|
c = unhex[(unsigned char)ch];
|
|
@@ -1497,6 +1537,8 @@ size_t http_parser_execute (http_parser *parser,
|
|
|
1497
1537
|
assert(parser->flags & F_CHUNKED);
|
|
1498
1538
|
STRICT_CHECK(ch != LF);
|
|
1499
1539
|
|
|
1540
|
+
nread = 0;
|
|
1541
|
+
|
|
1500
1542
|
if (parser->content_length == 0) {
|
|
1501
1543
|
parser->flags |= F_TRAILING;
|
|
1502
1544
|
state = s_header_field_start;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Copyright
|
|
1
|
+
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
|
2
2
|
*
|
|
3
3
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
4
|
* of this software and associated documentation files (the "Software"), to
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
extern "C" {
|
|
25
25
|
#endif
|
|
26
26
|
|
|
27
|
+
#define HTTP_PARSER_VERSION_MAJOR 1
|
|
28
|
+
#define HTTP_PARSER_VERSION_MINOR 0
|
|
27
29
|
|
|
28
30
|
#include <sys/types.h>
|
|
29
31
|
#if defined(_WIN32) && !defined(__MINGW32__)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Copyright
|
|
1
|
+
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
|
2
2
|
*
|
|
3
3
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
4
|
* of this software and associated documentation files (the "Software"), to
|
|
@@ -557,7 +557,7 @@ const struct message requests[] =
|
|
|
557
557
|
,.body= ""
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
-
#define MSEARCH_REQ
|
|
560
|
+
#define MSEARCH_REQ 20
|
|
561
561
|
, {.name= "m-search request"
|
|
562
562
|
,.type= HTTP_REQUEST
|
|
563
563
|
,.raw= "M-SEARCH * HTTP/1.1\r\n"
|
|
@@ -582,6 +582,84 @@ const struct message requests[] =
|
|
|
582
582
|
,.body= ""
|
|
583
583
|
}
|
|
584
584
|
|
|
585
|
+
#define UTF8_PATH_REQ 21
|
|
586
|
+
, {.name= "utf-8 path request"
|
|
587
|
+
,.type= HTTP_REQUEST
|
|
588
|
+
,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n"
|
|
589
|
+
"Host: github.com\r\n"
|
|
590
|
+
"\r\n"
|
|
591
|
+
,.should_keep_alive= TRUE
|
|
592
|
+
,.message_complete_on_eof= FALSE
|
|
593
|
+
,.http_major= 1
|
|
594
|
+
,.http_minor= 1
|
|
595
|
+
,.method= HTTP_GET
|
|
596
|
+
,.query_string= "q=1"
|
|
597
|
+
,.fragment= "narf"
|
|
598
|
+
,.request_path= "/δ¶/δt/pope"
|
|
599
|
+
,.request_url= "/δ¶/δt/pope?q=1#narf"
|
|
600
|
+
,.num_headers= 1
|
|
601
|
+
,.headers= { {"Host", "github.com" }
|
|
602
|
+
}
|
|
603
|
+
,.body= ""
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
#define QUERY_TERMINATED_HOST 22
|
|
607
|
+
, {.name= "host terminated by a query string"
|
|
608
|
+
,.type= HTTP_REQUEST
|
|
609
|
+
,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n"
|
|
610
|
+
"\r\n"
|
|
611
|
+
,.should_keep_alive= TRUE
|
|
612
|
+
,.message_complete_on_eof= FALSE
|
|
613
|
+
,.http_major= 1
|
|
614
|
+
,.http_minor= 1
|
|
615
|
+
,.method= HTTP_GET
|
|
616
|
+
,.query_string= "hail=all"
|
|
617
|
+
,.fragment= ""
|
|
618
|
+
,.request_path= ""
|
|
619
|
+
,.request_url= "http://hypnotoad.org?hail=all"
|
|
620
|
+
,.num_headers= 0
|
|
621
|
+
,.headers= { }
|
|
622
|
+
,.body= ""
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
#define QUERY_TERMINATED_HOSTPORT 23
|
|
626
|
+
, {.name= "host:port terminated by a query string"
|
|
627
|
+
,.type= HTTP_REQUEST
|
|
628
|
+
,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n"
|
|
629
|
+
"\r\n"
|
|
630
|
+
,.should_keep_alive= TRUE
|
|
631
|
+
,.message_complete_on_eof= FALSE
|
|
632
|
+
,.http_major= 1
|
|
633
|
+
,.http_minor= 1
|
|
634
|
+
,.method= HTTP_GET
|
|
635
|
+
,.query_string= "hail=all"
|
|
636
|
+
,.fragment= ""
|
|
637
|
+
,.request_path= ""
|
|
638
|
+
,.request_url= "http://hypnotoad.org:1234?hail=all"
|
|
639
|
+
,.num_headers= 0
|
|
640
|
+
,.headers= { }
|
|
641
|
+
,.body= ""
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
#define SPACE_TERMINATED_HOSTPORT 24
|
|
645
|
+
, {.name= "host:port terminated by a space"
|
|
646
|
+
,.type= HTTP_REQUEST
|
|
647
|
+
,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n"
|
|
648
|
+
"\r\n"
|
|
649
|
+
,.should_keep_alive= TRUE
|
|
650
|
+
,.message_complete_on_eof= FALSE
|
|
651
|
+
,.http_major= 1
|
|
652
|
+
,.http_minor= 1
|
|
653
|
+
,.method= HTTP_GET
|
|
654
|
+
,.query_string= ""
|
|
655
|
+
,.fragment= ""
|
|
656
|
+
,.request_path= ""
|
|
657
|
+
,.request_url= "http://hypnotoad.org:1234"
|
|
658
|
+
,.num_headers= 0
|
|
659
|
+
,.headers= { }
|
|
660
|
+
,.body= ""
|
|
661
|
+
}
|
|
662
|
+
|
|
585
663
|
, {.name= NULL } /* sentinel */
|
|
586
664
|
};
|
|
587
665
|
|
|
@@ -1735,14 +1813,22 @@ main (void)
|
|
|
1735
1813
|
|
|
1736
1814
|
/// REQUESTS
|
|
1737
1815
|
|
|
1738
|
-
|
|
1739
1816
|
test_simple("hello world", 0);
|
|
1740
1817
|
test_simple("GET / HTP/1.1\r\n\r\n", 0);
|
|
1741
1818
|
|
|
1819
|
+
|
|
1742
1820
|
test_simple("ASDF / HTTP/1.1\r\n\r\n", 0);
|
|
1743
1821
|
test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", 0);
|
|
1744
1822
|
test_simple("GETA / HTTP/1.1\r\n\r\n", 0);
|
|
1745
1823
|
|
|
1824
|
+
// Well-formed but incomplete
|
|
1825
|
+
test_simple("GET / HTTP/1.1\r\n"
|
|
1826
|
+
"Content-Type: text/plain\r\n"
|
|
1827
|
+
"Content-Length: 6\r\n"
|
|
1828
|
+
"\r\n"
|
|
1829
|
+
"fooba",
|
|
1830
|
+
0);
|
|
1831
|
+
|
|
1746
1832
|
static const char *all_methods[] = {
|
|
1747
1833
|
"DELETE",
|
|
1748
1834
|
"GET",
|