ruby_http_parser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,141 @@
1
+ /* Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
2
+ *
3
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ * of this software and associated documentation files (the "Software"), to
5
+ * deal in the Software without restriction, including without limitation the
6
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ * sell copies of the Software, and to permit persons to whom the Software is
8
+ * furnished to do so, subject to the following conditions:
9
+ *
10
+ * The above copyright notice and this permission notice shall be included in
11
+ * all copies or substantial portions of the Software.
12
+ *
13
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19
+ * IN THE SOFTWARE.
20
+ */
21
+ #ifndef http_parser_h
22
+ #define http_parser_h
23
+ #ifdef __cplusplus
24
+ extern "C" {
25
+ #endif
26
+
27
+ #ifdef _MSC_VER
28
+ # include <stddef.h>
29
+ #endif
30
+ #include <sys/types.h>
31
+
32
+ /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
33
+ * faster
34
+ */
35
+ #ifndef HTTP_PARSER_STRICT
36
+ # define HTTP_PARSER_STRICT 1
37
+ #else
38
+ # define HTTP_PARSER_STRICT 0
39
+ #endif
40
+
41
+ typedef struct http_parser http_parser;
42
+
43
+ /* Callbacks should return non-zero to indicate an error. The parse will
44
+ * then halt execution.
45
+ *
46
+ * http_data_cb does not return data chunks. It will be call arbitrarally
47
+ * many times for each string. E.G. you might get 10 callbacks for "on_path"
48
+ * each providing just a few characters more data.
49
+ */
50
+ typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
51
+ typedef int (*http_cb) (http_parser*);
52
+
53
+ /* Should be at least one longer than the longest request method */
54
+ #define HTTP_PARSER_MAX_METHOD_LEN 10
55
+
56
+ /* Request Methods */
57
+ enum http_method
58
+ { HTTP_DELETE = 0x0001
59
+ , HTTP_GET = 0x0002
60
+ , HTTP_HEAD = 0x0004
61
+ , HTTP_POST = 0x0008
62
+ , HTTP_PUT = 0x0010
63
+ /* pathological */
64
+ , HTTP_CONNECT = 0x0020
65
+ , HTTP_OPTIONS = 0x0040
66
+ , HTTP_TRACE = 0x0080
67
+ /* webdav */
68
+ , HTTP_COPY = 0x0100
69
+ , HTTP_LOCK = 0x0200
70
+ , HTTP_MKCOL = 0x0400
71
+ , HTTP_MOVE = 0x0800
72
+ , HTTP_PROPFIND = 0x1000
73
+ , HTTP_PROPPATCH = 0x2000
74
+ , HTTP_UNLOCK = 0x4000
75
+ };
76
+
77
+ struct http_parser {
78
+ /** PRIVATE **/
79
+ unsigned short state;
80
+ unsigned short header_state;
81
+ size_t index;
82
+
83
+ char flags;
84
+
85
+ ssize_t body_read;
86
+ ssize_t content_length;
87
+
88
+ const char *header_field_mark;
89
+ size_t header_field_size;
90
+ const char *header_value_mark;
91
+ size_t header_value_size;
92
+ const char *query_string_mark;
93
+ size_t query_string_size;
94
+ const char *path_mark;
95
+ size_t path_size;
96
+ const char *url_mark;
97
+ size_t url_size;
98
+ const char *fragment_mark;
99
+ size_t fragment_size;
100
+
101
+ /** READ-ONLY **/
102
+ unsigned short status_code; /* responses only */
103
+ enum http_method method; /* requests only */
104
+ unsigned short http_major;
105
+ unsigned short http_minor;
106
+ char buffer[HTTP_PARSER_MAX_METHOD_LEN];
107
+
108
+ /** PUBLIC **/
109
+ void *data; /* A pointer to get hook to the "connection" or "socket" object */
110
+
111
+ /* an ordered list of callbacks */
112
+
113
+ http_cb on_message_begin;
114
+
115
+ /* requests only */
116
+ http_data_cb on_path;
117
+ http_data_cb on_query_string;
118
+ http_data_cb on_url;
119
+ http_data_cb on_fragment;
120
+
121
+ http_data_cb on_header_field;
122
+ http_data_cb on_header_value;
123
+ http_cb on_headers_complete;
124
+ http_data_cb on_body;
125
+ http_cb on_message_complete;
126
+ };
127
+
128
+ void http_parser_init(http_parser *parser);
129
+ size_t http_parse_requests(http_parser *parser, const char *data, size_t len);
130
+ size_t http_parse_responses(http_parser *parser, const char *data, size_t len);
131
+ /* Call this in the on_headers_complete or on_message_complete callback to
132
+ * determine if this will be the last message on the connection.
133
+ * If you are the server, respond with the "Connection: close" header
134
+ * if you are the client, close the connection.
135
+ */
136
+ int http_should_keep_alive(http_parser *parser);
137
+
138
+ #ifdef __cplusplus
139
+ }
140
+ #endif
141
+ #endif
@@ -0,0 +1,1176 @@
1
+ /* Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
2
+ *
3
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ * of this software and associated documentation files (the "Software"), to
5
+ * deal in the Software without restriction, including without limitation the
6
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ * sell copies of the Software, and to permit persons to whom the Software is
8
+ * furnished to do so, subject to the following conditions:
9
+ *
10
+ * The above copyright notice and this permission notice shall be included in
11
+ * all copies or substantial portions of the Software.
12
+ *
13
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19
+ * IN THE SOFTWARE.
20
+ */
21
+ #include "http_parser.h"
22
+ #include <stdlib.h>
23
+ #include <assert.h>
24
+ #include <stdio.h>
25
+ #include <stdlib.h> /* rand */
26
+ #include <string.h>
27
+ #include <stdarg.h>
28
+
29
+ #undef TRUE
30
+ #define TRUE 1
31
+ #undef FALSE
32
+ #define FALSE 0
33
+
34
+ #define MAX_HEADERS 10
35
+ #define MAX_ELEMENT_SIZE 500
36
+
37
+ enum message_type { REQUEST, RESPONSE };
38
+
39
+ static http_parser *parser;
40
+
41
+ struct message {
42
+ const char *name; // for debugging purposes
43
+ const char *raw;
44
+ enum message_type type;
45
+ enum http_method method;
46
+ int status_code;
47
+ char request_path[MAX_ELEMENT_SIZE];
48
+ char request_url[MAX_ELEMENT_SIZE];
49
+ char fragment[MAX_ELEMENT_SIZE];
50
+ char query_string[MAX_ELEMENT_SIZE];
51
+ char body[MAX_ELEMENT_SIZE];
52
+ int num_headers;
53
+ enum { NONE=0, FIELD, VALUE } last_header_element;
54
+ char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE];
55
+ int should_keep_alive;
56
+
57
+ unsigned short http_major;
58
+ unsigned short http_minor;
59
+
60
+ int message_begin_cb_called;
61
+ int headers_complete_cb_called;
62
+ int message_complete_cb_called;
63
+ int message_complete_on_eof;
64
+ };
65
+
66
+ static int currently_parsing_eof;
67
+
68
+ inline size_t parse (enum message_type t, const char *buf, size_t len)
69
+ {
70
+ size_t nparsed;
71
+ currently_parsing_eof = (len == 0);
72
+ nparsed = (t == REQUEST ? http_parse_requests(parser, buf, len)
73
+ : http_parse_responses(parser, buf, len));
74
+ return nparsed;
75
+ }
76
+
77
+ static struct message messages[5];
78
+ static int num_messages;
79
+
80
+ /* * R E Q U E S T S * */
81
+ const struct message requests[] =
82
+ #define CURL_GET 0
83
+ { {.name= "curl get"
84
+ ,.type= REQUEST
85
+ ,.raw= "GET /test HTTP/1.1\r\n"
86
+ "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n"
87
+ "Host: 0.0.0.0=5000\r\n"
88
+ "Accept: */*\r\n"
89
+ "\r\n"
90
+ ,.should_keep_alive= TRUE
91
+ ,.message_complete_on_eof= FALSE
92
+ ,.http_major= 1
93
+ ,.http_minor= 1
94
+ ,.method= HTTP_GET
95
+ ,.query_string= ""
96
+ ,.fragment= ""
97
+ ,.request_path= "/test"
98
+ ,.request_url= "/test"
99
+ ,.num_headers= 3
100
+ ,.headers=
101
+ { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" }
102
+ , { "Host", "0.0.0.0=5000" }
103
+ , { "Accept", "*/*" }
104
+ }
105
+ ,.body= ""
106
+ }
107
+
108
+ #define FIREFOX_GET 1
109
+ , {.name= "firefox get"
110
+ ,.type= REQUEST
111
+ ,.raw= "GET /favicon.ico HTTP/1.1\r\n"
112
+ "Host: 0.0.0.0=5000\r\n"
113
+ "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n"
114
+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
115
+ "Accept-Language: en-us,en;q=0.5\r\n"
116
+ "Accept-Encoding: gzip,deflate\r\n"
117
+ "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
118
+ "Keep-Alive: 300\r\n"
119
+ "Connection: keep-alive\r\n"
120
+ "\r\n"
121
+ ,.should_keep_alive= TRUE
122
+ ,.message_complete_on_eof= FALSE
123
+ ,.http_major= 1
124
+ ,.http_minor= 1
125
+ ,.method= HTTP_GET
126
+ ,.query_string= ""
127
+ ,.fragment= ""
128
+ ,.request_path= "/favicon.ico"
129
+ ,.request_url= "/favicon.ico"
130
+ ,.num_headers= 8
131
+ ,.headers=
132
+ { { "Host", "0.0.0.0=5000" }
133
+ , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" }
134
+ , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }
135
+ , { "Accept-Language", "en-us,en;q=0.5" }
136
+ , { "Accept-Encoding", "gzip,deflate" }
137
+ , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" }
138
+ , { "Keep-Alive", "300" }
139
+ , { "Connection", "keep-alive" }
140
+ }
141
+ ,.body= ""
142
+ }
143
+
144
+ #define DUMBFUCK 2
145
+ , {.name= "dumbfuck"
146
+ ,.type= REQUEST
147
+ ,.raw= "GET /dumbfuck HTTP/1.1\r\n"
148
+ "aaaaaaaaaaaaa:++++++++++\r\n"
149
+ "\r\n"
150
+ ,.should_keep_alive= TRUE
151
+ ,.message_complete_on_eof= FALSE
152
+ ,.http_major= 1
153
+ ,.http_minor= 1
154
+ ,.method= HTTP_GET
155
+ ,.query_string= ""
156
+ ,.fragment= ""
157
+ ,.request_path= "/dumbfuck"
158
+ ,.request_url= "/dumbfuck"
159
+ ,.num_headers= 1
160
+ ,.headers=
161
+ { { "aaaaaaaaaaaaa", "++++++++++" }
162
+ }
163
+ ,.body= ""
164
+ }
165
+
166
+ #define FRAGMENT_IN_URI 3
167
+ , {.name= "fragment in url"
168
+ ,.type= REQUEST
169
+ ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n"
170
+ "\r\n"
171
+ ,.should_keep_alive= TRUE
172
+ ,.message_complete_on_eof= FALSE
173
+ ,.http_major= 1
174
+ ,.http_minor= 1
175
+ ,.method= HTTP_GET
176
+ ,.query_string= "page=1"
177
+ ,.fragment= "posts-17408"
178
+ ,.request_path= "/forums/1/topics/2375"
179
+ /* XXX request url does include fragment? */
180
+ ,.request_url= "/forums/1/topics/2375?page=1#posts-17408"
181
+ ,.num_headers= 0
182
+ ,.body= ""
183
+ }
184
+
185
+ #define GET_NO_HEADERS_NO_BODY 4
186
+ , {.name= "get no headers no body"
187
+ ,.type= REQUEST
188
+ ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n"
189
+ "\r\n"
190
+ ,.should_keep_alive= TRUE
191
+ ,.message_complete_on_eof= FALSE /* would need Connection: close */
192
+ ,.http_major= 1
193
+ ,.http_minor= 1
194
+ ,.method= HTTP_GET
195
+ ,.query_string= ""
196
+ ,.fragment= ""
197
+ ,.request_path= "/get_no_headers_no_body/world"
198
+ ,.request_url= "/get_no_headers_no_body/world"
199
+ ,.num_headers= 0
200
+ ,.body= ""
201
+ }
202
+
203
+ #define GET_ONE_HEADER_NO_BODY 5
204
+ , {.name= "get one header no body"
205
+ ,.type= REQUEST
206
+ ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n"
207
+ "Accept: */*\r\n"
208
+ "\r\n"
209
+ ,.should_keep_alive= TRUE
210
+ ,.message_complete_on_eof= FALSE /* would need Connection: close */
211
+ ,.http_major= 1
212
+ ,.http_minor= 1
213
+ ,.method= HTTP_GET
214
+ ,.query_string= ""
215
+ ,.fragment= ""
216
+ ,.request_path= "/get_one_header_no_body"
217
+ ,.request_url= "/get_one_header_no_body"
218
+ ,.num_headers= 1
219
+ ,.headers=
220
+ { { "Accept" , "*/*" }
221
+ }
222
+ ,.body= ""
223
+ }
224
+
225
+ #define GET_FUNKY_CONTENT_LENGTH 6
226
+ , {.name= "get funky content length body hello"
227
+ ,.type= REQUEST
228
+ ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n"
229
+ "conTENT-Length: 5\r\n"
230
+ "\r\n"
231
+ "HELLO"
232
+ ,.should_keep_alive= FALSE
233
+ ,.message_complete_on_eof= FALSE
234
+ ,.http_major= 1
235
+ ,.http_minor= 0
236
+ ,.method= HTTP_GET
237
+ ,.query_string= ""
238
+ ,.fragment= ""
239
+ ,.request_path= "/get_funky_content_length_body_hello"
240
+ ,.request_url= "/get_funky_content_length_body_hello"
241
+ ,.num_headers= 1
242
+ ,.headers=
243
+ { { "conTENT-Length" , "5" }
244
+ }
245
+ ,.body= "HELLO"
246
+ }
247
+
248
+ #define POST_IDENTITY_BODY_WORLD 7
249
+ , {.name= "post identity body world"
250
+ ,.type= REQUEST
251
+ ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n"
252
+ "Accept: */*\r\n"
253
+ "Transfer-Encoding: identity\r\n"
254
+ "Content-Length: 5\r\n"
255
+ "\r\n"
256
+ "World"
257
+ ,.should_keep_alive= TRUE
258
+ ,.message_complete_on_eof= FALSE
259
+ ,.http_major= 1
260
+ ,.http_minor= 1
261
+ ,.method= HTTP_POST
262
+ ,.query_string= "q=search"
263
+ ,.fragment= "hey"
264
+ ,.request_path= "/post_identity_body_world"
265
+ ,.request_url= "/post_identity_body_world?q=search#hey"
266
+ ,.num_headers= 3
267
+ ,.headers=
268
+ { { "Accept", "*/*" }
269
+ , { "Transfer-Encoding", "identity" }
270
+ , { "Content-Length", "5" }
271
+ }
272
+ ,.body= "World"
273
+ }
274
+
275
+ #define POST_CHUNKED_ALL_YOUR_BASE 8
276
+ , {.name= "post - chunked body: all your base are belong to us"
277
+ ,.type= REQUEST
278
+ ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n"
279
+ "Transfer-Encoding: chunked\r\n"
280
+ "\r\n"
281
+ "1e\r\nall your base are belong to us\r\n"
282
+ "0\r\n"
283
+ "\r\n"
284
+ ,.should_keep_alive= TRUE
285
+ ,.message_complete_on_eof= FALSE
286
+ ,.http_major= 1
287
+ ,.http_minor= 1
288
+ ,.method= HTTP_POST
289
+ ,.query_string= ""
290
+ ,.fragment= ""
291
+ ,.request_path= "/post_chunked_all_your_base"
292
+ ,.request_url= "/post_chunked_all_your_base"
293
+ ,.num_headers= 1
294
+ ,.headers=
295
+ { { "Transfer-Encoding" , "chunked" }
296
+ }
297
+ ,.body= "all your base are belong to us"
298
+ }
299
+
300
+ #define TWO_CHUNKS_MULT_ZERO_END 9
301
+ , {.name= "two chunks ; triple zero ending"
302
+ ,.type= REQUEST
303
+ ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n"
304
+ "Transfer-Encoding: chunked\r\n"
305
+ "\r\n"
306
+ "5\r\nhello\r\n"
307
+ "6\r\n world\r\n"
308
+ "000\r\n"
309
+ "\r\n"
310
+ ,.should_keep_alive= TRUE
311
+ ,.message_complete_on_eof= FALSE
312
+ ,.http_major= 1
313
+ ,.http_minor= 1
314
+ ,.method= HTTP_POST
315
+ ,.query_string= ""
316
+ ,.fragment= ""
317
+ ,.request_path= "/two_chunks_mult_zero_end"
318
+ ,.request_url= "/two_chunks_mult_zero_end"
319
+ ,.num_headers= 1
320
+ ,.headers=
321
+ { { "Transfer-Encoding", "chunked" }
322
+ }
323
+ ,.body= "hello world"
324
+ }
325
+
326
+ #define CHUNKED_W_TRAILING_HEADERS 10
327
+ , {.name= "chunked with trailing headers. blech."
328
+ ,.type= REQUEST
329
+ ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n"
330
+ "Transfer-Encoding: chunked\r\n"
331
+ "\r\n"
332
+ "5\r\nhello\r\n"
333
+ "6\r\n world\r\n"
334
+ "0\r\n"
335
+ "Vary: *\r\n"
336
+ "Content-Type: text/plain\r\n"
337
+ "\r\n"
338
+ ,.should_keep_alive= TRUE
339
+ ,.message_complete_on_eof= FALSE
340
+ ,.http_major= 1
341
+ ,.http_minor= 1
342
+ ,.method= HTTP_POST
343
+ ,.query_string= ""
344
+ ,.fragment= ""
345
+ ,.request_path= "/chunked_w_trailing_headers"
346
+ ,.request_url= "/chunked_w_trailing_headers"
347
+ ,.num_headers= 3
348
+ ,.headers=
349
+ { { "Transfer-Encoding", "chunked" }
350
+ , { "Vary", "*" }
351
+ , { "Content-Type", "text/plain" }
352
+ }
353
+ ,.body= "hello world"
354
+ }
355
+
356
+ #define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
357
+ , {.name= "with bullshit after the length"
358
+ ,.type= REQUEST
359
+ ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n"
360
+ "Transfer-Encoding: chunked\r\n"
361
+ "\r\n"
362
+ "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n"
363
+ "6; blahblah; blah\r\n world\r\n"
364
+ "0\r\n"
365
+ "\r\n"
366
+ ,.should_keep_alive= TRUE
367
+ ,.message_complete_on_eof= FALSE
368
+ ,.http_major= 1
369
+ ,.http_minor= 1
370
+ ,.method= HTTP_POST
371
+ ,.query_string= ""
372
+ ,.fragment= ""
373
+ ,.request_path= "/chunked_w_bullshit_after_length"
374
+ ,.request_url= "/chunked_w_bullshit_after_length"
375
+ ,.num_headers= 1
376
+ ,.headers=
377
+ { { "Transfer-Encoding", "chunked" }
378
+ }
379
+ ,.body= "hello world"
380
+ }
381
+
382
+ #define WITH_QUOTES 12
383
+ , {.name= "with quotes"
384
+ ,.type= REQUEST
385
+ ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n"
386
+ ,.should_keep_alive= TRUE
387
+ ,.message_complete_on_eof= FALSE
388
+ ,.http_major= 1
389
+ ,.http_minor= 1
390
+ ,.method= HTTP_GET
391
+ ,.query_string= "foo=\"bar\""
392
+ ,.fragment= ""
393
+ ,.request_path= "/with_\"stupid\"_quotes"
394
+ ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\""
395
+ ,.num_headers= 0
396
+ ,.headers= { }
397
+ ,.body= ""
398
+ }
399
+
400
+ #define APACHEBENCH_GET 13
401
+ /* The server receiving this request SHOULD NOT wait for EOF
402
+ * to know that content-length == 0.
403
+ * How to represent this in a unit test? message_complete_on_eof
404
+ * Compare with NO_CONTENT_LENGTH_RESPONSE.
405
+ */
406
+ , {.name = "apachebench get"
407
+ ,.type= REQUEST
408
+ ,.raw= "GET /test HTTP/1.0\r\n"
409
+ "Host: 0.0.0.0:5000\r\n"
410
+ "User-Agent: ApacheBench/2.3\r\n"
411
+ "Accept: */*\r\n\r\n"
412
+ ,.should_keep_alive= FALSE
413
+ ,.message_complete_on_eof= FALSE
414
+ ,.http_major= 1
415
+ ,.http_minor= 0
416
+ ,.method= HTTP_GET
417
+ ,.query_string= ""
418
+ ,.fragment= ""
419
+ ,.request_path= "/test"
420
+ ,.request_url= "/test"
421
+ ,.num_headers= 3
422
+ ,.headers= { { "Host", "0.0.0.0:5000" }
423
+ , { "User-Agent", "ApacheBench/2.3" }
424
+ , { "Accept", "*/*" }
425
+ }
426
+ ,.body= ""
427
+ }
428
+
429
+ , {.name= NULL } /* sentinel */
430
+ };
431
+
432
+ /* * R E S P O N S E S * */
433
+ const struct message responses[] =
434
+ #define GOOGLE_301 0
435
+ { {.name= "google 301"
436
+ ,.type= RESPONSE
437
+ ,.raw= "HTTP/1.1 301 Moved Permanently\r\n"
438
+ "Location: http://www.google.com/\r\n"
439
+ "Content-Type: text/html; charset=UTF-8\r\n"
440
+ "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n"
441
+ "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n"
442
+ "Cache-Control: public, max-age=2592000\r\n"
443
+ "Server: gws\r\n"
444
+ "Content-Length: 219\r\n"
445
+ "\r\n"
446
+ "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
447
+ "<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
448
+ "<H1>301 Moved</H1>\n"
449
+ "The document has moved\n"
450
+ "<A HREF=\"http://www.google.com/\">here</A>.\r\n"
451
+ "</BODY></HTML>\r\n"
452
+ ,.should_keep_alive= TRUE
453
+ ,.message_complete_on_eof= FALSE
454
+ ,.http_major= 1
455
+ ,.http_minor= 1
456
+ ,.status_code= 301
457
+ ,.num_headers= 7
458
+ ,.headers=
459
+ { { "Location", "http://www.google.com/" }
460
+ , { "Content-Type", "text/html; charset=UTF-8" }
461
+ , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" }
462
+ , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" }
463
+ , { "Cache-Control", "public, max-age=2592000" }
464
+ , { "Server", "gws" }
465
+ , { "Content-Length", "219" }
466
+ }
467
+ ,.body= "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
468
+ "<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
469
+ "<H1>301 Moved</H1>\n"
470
+ "The document has moved\n"
471
+ "<A HREF=\"http://www.google.com/\">here</A>.\r\n"
472
+ "</BODY></HTML>\r\n"
473
+ }
474
+
475
+ #define NO_CONTENT_LENGTH_RESPONSE 1
476
+ /* The client should wait for the server's EOF. That is, when content-length
477
+ * is not specified, and "Connection: close", the end of body is specified
478
+ * by the EOF.
479
+ * Compare with APACHEBENCH_GET
480
+ */
481
+ , {.name= "no content-length response"
482
+ ,.type= RESPONSE
483
+ ,.raw= "HTTP/1.1 200 OK\r\n"
484
+ "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n"
485
+ "Server: Apache\r\n"
486
+ "X-Powered-By: Servlet/2.5 JSP/2.1\r\n"
487
+ "Content-Type: text/xml; charset=utf-8\r\n"
488
+ "Connection: close\r\n"
489
+ "\r\n"
490
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
491
+ "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
492
+ " <SOAP-ENV:Body>\n"
493
+ " <SOAP-ENV:Fault>\n"
494
+ " <faultcode>SOAP-ENV:Client</faultcode>\n"
495
+ " <faultstring>Client Error</faultstring>\n"
496
+ " </SOAP-ENV:Fault>\n"
497
+ " </SOAP-ENV:Body>\n"
498
+ "</SOAP-ENV:Envelope>"
499
+ ,.should_keep_alive= FALSE
500
+ ,.message_complete_on_eof= TRUE
501
+ ,.http_major= 1
502
+ ,.http_minor= 1
503
+ ,.status_code= 200
504
+ ,.num_headers= 5
505
+ ,.headers=
506
+ { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" }
507
+ , { "Server", "Apache" }
508
+ , { "X-Powered-By", "Servlet/2.5 JSP/2.1" }
509
+ , { "Content-Type", "text/xml; charset=utf-8" }
510
+ , { "Connection", "close" }
511
+ }
512
+ ,.body= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
513
+ "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
514
+ " <SOAP-ENV:Body>\n"
515
+ " <SOAP-ENV:Fault>\n"
516
+ " <faultcode>SOAP-ENV:Client</faultcode>\n"
517
+ " <faultstring>Client Error</faultstring>\n"
518
+ " </SOAP-ENV:Fault>\n"
519
+ " </SOAP-ENV:Body>\n"
520
+ "</SOAP-ENV:Envelope>"
521
+ }
522
+
523
+ #define NO_HEADERS_NO_BODY_404 2
524
+ , {.name= "404 no headers no body"
525
+ ,.type= RESPONSE
526
+ ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n"
527
+ ,.should_keep_alive= TRUE
528
+ ,.message_complete_on_eof= FALSE
529
+ ,.http_major= 1
530
+ ,.http_minor= 1
531
+ ,.status_code= 404
532
+ ,.num_headers= 0
533
+ ,.headers= {}
534
+ ,.body= ""
535
+ }
536
+
537
+ #define NO_REASON_PHRASE 3
538
+ , {.name= "301 no response phrase"
539
+ ,.type= RESPONSE
540
+ ,.raw= "HTTP/1.1 301\r\n\r\n"
541
+ ,.should_keep_alive = TRUE
542
+ ,.message_complete_on_eof= FALSE
543
+ ,.http_major= 1
544
+ ,.http_minor= 1
545
+ ,.status_code= 301
546
+ ,.num_headers= 0
547
+ ,.headers= {}
548
+ ,.body= ""
549
+ }
550
+
551
+ #define TRAILING_SPACE_ON_CHUNKED_BODY 4
552
+ , {.name="200 trailing space on chunked body"
553
+ ,.type= RESPONSE
554
+ ,.raw= "HTTP/1.1 200 OK\r\n"
555
+ "Content-Type: text/plain\r\n"
556
+ "Transfer-Encoding: chunked\r\n"
557
+ "\r\n"
558
+ "25 \r\n"
559
+ "This is the data in the first chunk\r\n"
560
+ "\r\n"
561
+ "1C\r\n"
562
+ "and this is the second one\r\n"
563
+ "\r\n"
564
+ "0 \r\n"
565
+ "\r\n"
566
+ ,.should_keep_alive= TRUE
567
+ ,.message_complete_on_eof= FALSE
568
+ ,.http_major= 1
569
+ ,.http_minor= 1
570
+ ,.status_code= 200
571
+ ,.num_headers= 2
572
+ ,.headers=
573
+ { {"Content-Type", "text/plain" }
574
+ , {"Transfer-Encoding", "chunked" }
575
+ }
576
+ ,.body =
577
+ "This is the data in the first chunk\r\n"
578
+ "and this is the second one\r\n"
579
+
580
+ }
581
+
582
+ , {.name= NULL } /* sentinel */
583
+ };
584
+
585
+ int
586
+ request_path_cb (http_parser *p, const char *buf, size_t len)
587
+ {
588
+ assert(p == parser);
589
+ strncat(messages[num_messages].request_path, buf, len);
590
+ return 0;
591
+ }
592
+
593
+ int
594
+ request_url_cb (http_parser *p, const char *buf, size_t len)
595
+ {
596
+ assert(p == parser);
597
+ strncat(messages[num_messages].request_url, buf, len);
598
+ return 0;
599
+ }
600
+
601
+ int
602
+ query_string_cb (http_parser *p, const char *buf, size_t len)
603
+ {
604
+ assert(p == parser);
605
+ strncat(messages[num_messages].query_string, buf, len);
606
+ return 0;
607
+ }
608
+
609
+ int
610
+ fragment_cb (http_parser *p, const char *buf, size_t len)
611
+ {
612
+ assert(p == parser);
613
+ strncat(messages[num_messages].fragment, buf, len);
614
+ return 0;
615
+ }
616
+
617
+ int
618
+ header_field_cb (http_parser *p, const char *buf, size_t len)
619
+ {
620
+ assert(p == parser);
621
+ struct message *m = &messages[num_messages];
622
+
623
+ if (m->last_header_element != FIELD)
624
+ m->num_headers++;
625
+
626
+ strncat(m->headers[m->num_headers-1][0], buf, len);
627
+
628
+ m->last_header_element = FIELD;
629
+
630
+ return 0;
631
+ }
632
+
633
+ int
634
+ header_value_cb (http_parser *p, const char *buf, size_t len)
635
+ {
636
+ assert(p == parser);
637
+ struct message *m = &messages[num_messages];
638
+
639
+ strncat(m->headers[m->num_headers-1][1], buf, len);
640
+
641
+ m->last_header_element = VALUE;
642
+
643
+ return 0;
644
+ }
645
+
646
+ int
647
+ body_cb (http_parser *p, const char *buf, size_t len)
648
+ {
649
+ assert(p == parser);
650
+ strncat(messages[num_messages].body, buf, len);
651
+ // printf("body_cb: '%s'\n", requests[num_messages].body);
652
+ return 0;
653
+ }
654
+
655
+ int
656
+ message_begin_cb (http_parser *p)
657
+ {
658
+ assert(p == parser);
659
+ messages[num_messages].message_begin_cb_called = TRUE;
660
+ return 0;
661
+ }
662
+
663
+ int
664
+ headers_complete_cb (http_parser *p)
665
+ {
666
+ assert(p == parser);
667
+ messages[num_messages].method = parser->method;
668
+ messages[num_messages].status_code = parser->status_code;
669
+ messages[num_messages].http_major = parser->http_major;
670
+ messages[num_messages].http_minor = parser->http_minor;
671
+ messages[num_messages].headers_complete_cb_called = TRUE;
672
+ messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
673
+ return 0;
674
+ }
675
+
676
+ int
677
+ message_complete_cb (http_parser *p)
678
+ {
679
+ assert(p == parser);
680
+ if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser))
681
+ {
682
+ fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same "
683
+ "value in both on_message_complete and on_headers_complete "
684
+ "but it doesn't! ***\n\n");
685
+ assert(0);
686
+ exit(1);
687
+ }
688
+ messages[num_messages].message_complete_cb_called = TRUE;
689
+
690
+ messages[num_messages].message_complete_on_eof = currently_parsing_eof;
691
+
692
+ num_messages++;
693
+ return 0;
694
+ }
695
+
696
+ void
697
+ parser_init ()
698
+ {
699
+ num_messages = 0;
700
+
701
+ assert(parser == NULL);
702
+
703
+ parser = malloc(sizeof(http_parser));
704
+
705
+ http_parser_init(parser);
706
+
707
+ memset(&messages, 0, sizeof messages);
708
+
709
+ parser->on_message_begin = message_begin_cb;
710
+ parser->on_header_field = header_field_cb;
711
+ parser->on_header_value = header_value_cb;
712
+ parser->on_path = request_path_cb;
713
+ parser->on_url = request_url_cb;
714
+ parser->on_fragment = fragment_cb;
715
+ parser->on_query_string = query_string_cb;
716
+ parser->on_body = body_cb;
717
+ parser->on_headers_complete = headers_complete_cb;
718
+ parser->on_message_complete = message_complete_cb;
719
+ }
720
+
721
+ void
722
+ parser_free ()
723
+ {
724
+ assert(parser);
725
+ free(parser);
726
+ parser = NULL;
727
+ }
728
+
729
+ static inline int
730
+ check_str_eq (const struct message *m,
731
+ const char *prop,
732
+ const char *expected,
733
+ const char *found) {
734
+ if (0 != strcmp(expected, found)) {
735
+ printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
736
+ printf("expected '%s'\n", expected);
737
+ printf(" found '%s'\n", found);
738
+ return 0;
739
+ }
740
+ return 1;
741
+ }
742
+
743
+ static inline int
744
+ check_num_eq (const struct message *m,
745
+ const char *prop,
746
+ int expected,
747
+ int found) {
748
+ if (expected != found) {
749
+ printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
750
+ printf("expected %d\n", expected);
751
+ printf(" found %d\n", found);
752
+ return 0;
753
+ }
754
+ return 1;
755
+ }
756
+
757
+ #define MESSAGE_CHECK_STR_EQ(expected, found, prop) \
758
+ if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0
759
+
760
+ #define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \
761
+ if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0
762
+
763
+
764
+ int
765
+ message_eq (int index, const struct message *expected)
766
+ {
767
+ int i;
768
+ struct message *m = &messages[index];
769
+
770
+ MESSAGE_CHECK_NUM_EQ(expected, m, http_major);
771
+ MESSAGE_CHECK_NUM_EQ(expected, m, http_minor);
772
+
773
+ if (expected->type == REQUEST) {
774
+ MESSAGE_CHECK_NUM_EQ(expected, m, method);
775
+ } else {
776
+ MESSAGE_CHECK_NUM_EQ(expected, m, status_code);
777
+ }
778
+
779
+ MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive);
780
+ MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof);
781
+
782
+ assert(m->message_begin_cb_called);
783
+ assert(m->headers_complete_cb_called);
784
+ assert(m->message_complete_cb_called);
785
+
786
+
787
+ MESSAGE_CHECK_STR_EQ(expected, m, request_path);
788
+ MESSAGE_CHECK_STR_EQ(expected, m, query_string);
789
+ MESSAGE_CHECK_STR_EQ(expected, m, fragment);
790
+ MESSAGE_CHECK_STR_EQ(expected, m, request_url);
791
+ MESSAGE_CHECK_STR_EQ(expected, m, body);
792
+
793
+ MESSAGE_CHECK_NUM_EQ(expected, m, num_headers);
794
+
795
+ int r;
796
+ for (i = 0; i < m->num_headers; i++) {
797
+ r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]);
798
+ if (!r) return 0;
799
+ r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]);
800
+ if (!r) return 0;
801
+ }
802
+
803
+ return 1;
804
+ }
805
+
806
+ static void
807
+ print_error (const char *raw, size_t error_location)
808
+ {
809
+ fprintf(stderr, "\n*** parse error ***\n\n");
810
+
811
+ int this_line = 0, char_len = 0;
812
+ size_t i, j, len = strlen(raw), error_location_line = 0;
813
+ for (i = 0; i < len; i++) {
814
+ if (i == error_location) this_line = 1;
815
+ switch (raw[i]) {
816
+ case '\r':
817
+ char_len = 2;
818
+ fprintf(stderr, "\\r");
819
+ break;
820
+
821
+ case '\n':
822
+ char_len = 2;
823
+ fprintf(stderr, "\\n\n");
824
+
825
+ if (this_line) goto print;
826
+
827
+ error_location_line = 0;
828
+ continue;
829
+
830
+ default:
831
+ char_len = 1;
832
+ fputc(raw[i], stderr);
833
+ break;
834
+ }
835
+ if (!this_line) error_location_line += char_len;
836
+ }
837
+
838
+ fprintf(stderr, "[eof]\n");
839
+
840
+ print:
841
+ for (j = 0; j < error_location_line; j++) {
842
+ fputc(' ', stderr);
843
+ }
844
+ fprintf(stderr, "^\n\nerror location: %d\n", error_location);
845
+ }
846
+
847
+
848
+ void
849
+ test_message (const struct message *message)
850
+ {
851
+ parser_init();
852
+
853
+ size_t read;
854
+
855
+ read = parse(message->type, message->raw, strlen(message->raw));
856
+ if (read != strlen(message->raw)) {
857
+ print_error(message->raw, read);
858
+ exit(1);
859
+ }
860
+
861
+ read = parse(message->type, NULL, 0);
862
+ if (read != 0) {
863
+ print_error(message->raw, read);
864
+ exit(1);
865
+ }
866
+
867
+ if (num_messages != 1) {
868
+ printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
869
+ exit(1);
870
+ }
871
+
872
+ if(!message_eq(0, message)) exit(1);
873
+
874
+ parser_free();
875
+ }
876
+
877
+ void
878
+ test_error (const char *buf)
879
+ {
880
+ parser_init();
881
+
882
+ size_t parsed;
883
+
884
+ parsed = parse(REQUEST, buf, strlen(buf));
885
+ if (parsed != strlen(buf)) goto out;
886
+ parsed = parse(REQUEST, NULL, 0);
887
+ if (parsed != 0) goto out;
888
+
889
+ fprintf(stderr, "\n*** Error expected but none found ***\n\n%s", buf);
890
+ exit(1);
891
+ return;
892
+
893
+ out:
894
+ parser_free();
895
+ }
896
+
897
+ void
898
+ test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3)
899
+ {
900
+ char total[ strlen(r1->raw)
901
+ + strlen(r2->raw)
902
+ + strlen(r3->raw)
903
+ + 1
904
+ ];
905
+ total[0] = '\0';
906
+
907
+ strcat(total, r1->raw);
908
+ strcat(total, r2->raw);
909
+ strcat(total, r3->raw);
910
+
911
+ parser_init();
912
+
913
+ size_t read;
914
+
915
+ read = parse(r1->type, total, strlen(total));
916
+ if (read != strlen(total)) {
917
+ print_error(total, read);
918
+ exit(1);
919
+ }
920
+
921
+ read = parse(REQUEST, NULL, 0);
922
+ if (read != 0) {
923
+ print_error(total, read);
924
+ exit(1);
925
+ }
926
+
927
+ if (3 != num_messages) {
928
+ fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages);
929
+ exit(1);
930
+ }
931
+
932
+ if (!message_eq(0, r1)) exit(1);
933
+ if (!message_eq(1, r2)) exit(1);
934
+ if (!message_eq(2, r3)) exit(1);
935
+
936
+ parser_free();
937
+ }
938
+
939
+ /* SCAN through every possible breaking to make sure the
940
+ * parser can handle getting the content in any chunks that
941
+ * might come from the socket
942
+ */
943
+ void
944
+ test_scan (const struct message *r1, const struct message *r2, const struct message *r3)
945
+ {
946
+ char total[80*1024] = "\0";
947
+ char buf1[80*1024] = "\0";
948
+ char buf2[80*1024] = "\0";
949
+ char buf3[80*1024] = "\0";
950
+
951
+ strcat(total, r1->raw);
952
+ strcat(total, r2->raw);
953
+ strcat(total, r3->raw);
954
+
955
+ size_t read;
956
+
957
+ int total_len = strlen(total);
958
+
959
+ int total_ops = (total_len - 1) * (total_len - 2) / 2;
960
+ int ops = 0 ;
961
+
962
+ size_t buf1_len, buf2_len, buf3_len;
963
+
964
+ int i,j;
965
+ for (j = 2; j < total_len; j ++ ) {
966
+ for (i = 1; i < j; i ++ ) {
967
+
968
+ if (ops % 1000 == 0) {
969
+ printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops);
970
+ fflush(stdout);
971
+ }
972
+ ops += 1;
973
+
974
+ parser_init();
975
+
976
+ buf1_len = i;
977
+ strncpy(buf1, total, buf1_len);
978
+ buf1[buf1_len] = 0;
979
+
980
+ buf2_len = j - i;
981
+ strncpy(buf2, total+i, buf2_len);
982
+ buf2[buf2_len] = 0;
983
+
984
+ buf3_len = total_len - j;
985
+ strncpy(buf3, total+j, buf3_len);
986
+ buf3[buf3_len] = 0;
987
+
988
+ read = parse(r1->type, buf1, buf1_len);
989
+ if (read != buf1_len) {
990
+ print_error(buf1, read);
991
+ goto error;
992
+ }
993
+
994
+ read = parse(r1->type, buf2, buf2_len);
995
+ if (read != buf2_len) {
996
+ print_error(buf2, read);
997
+ goto error;
998
+ }
999
+
1000
+ read = parse(r1->type, buf3, buf3_len);
1001
+ if (read != buf3_len) {
1002
+ print_error(buf3, read);
1003
+ goto error;
1004
+ }
1005
+
1006
+ parse(r1->type, NULL, 0);
1007
+
1008
+ if (3 != num_messages) {
1009
+ fprintf(stderr, "\n\nParser didn't see 3 messages only %d\n", num_messages);
1010
+ goto error;
1011
+ }
1012
+
1013
+ if (!message_eq(0, r1)) {
1014
+ fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n");
1015
+ goto error;
1016
+ }
1017
+
1018
+ if (!message_eq(1, r2)) {
1019
+ fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n");
1020
+ goto error;
1021
+ }
1022
+
1023
+ if (!message_eq(2, r3)) {
1024
+ fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n");
1025
+ goto error;
1026
+ }
1027
+
1028
+ parser_free();
1029
+ }
1030
+ }
1031
+ puts("\b\b\b\b100%");
1032
+ return;
1033
+
1034
+ error:
1035
+ fprintf(stderr, "i=%d j=%d\n", i, j);
1036
+ fprintf(stderr, "buf1 (%d) %s\n\n", buf1_len, buf1);
1037
+ fprintf(stderr, "buf2 (%d) %s\n\n", buf2_len , buf2);
1038
+ fprintf(stderr, "buf3 (%d) %s\n", buf3_len, buf3);
1039
+ exit(1);
1040
+ }
1041
+
1042
+ int
1043
+ main (void)
1044
+ {
1045
+ parser = NULL;
1046
+ int i, j, k;
1047
+ int request_count;
1048
+ int response_count;
1049
+
1050
+ printf("sizeof(http_parser) = %d\n", sizeof(http_parser));
1051
+
1052
+ for (request_count = 0; requests[request_count].name; request_count++);
1053
+ for (response_count = 0; responses[response_count].name; response_count++);
1054
+
1055
+ //// RESPONSES
1056
+
1057
+ for (i = 0; i < response_count; i++) {
1058
+ test_message(&responses[i]);
1059
+ }
1060
+
1061
+ for (i = 0; i < response_count; i++) {
1062
+ if (!responses[i].should_keep_alive) continue;
1063
+ for (j = 0; j < response_count; j++) {
1064
+ if (!responses[j].should_keep_alive) continue;
1065
+ for (k = 0; k < response_count; k++) {
1066
+ test_multiple3(&responses[i], &responses[j], &responses[k]);
1067
+ }
1068
+ }
1069
+ }
1070
+
1071
+ printf("response scan 1/1 ");
1072
+ test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY]
1073
+ , &responses[NO_HEADERS_NO_BODY_404]
1074
+ , &responses[NO_REASON_PHRASE]
1075
+ );
1076
+
1077
+ puts("responses okay");
1078
+
1079
+
1080
+ /// REQUESTS
1081
+
1082
+
1083
+ test_error("hello world");
1084
+ test_error("GET / HTP/1.1\r\n\r\n");
1085
+
1086
+ const char *dumbfuck2 =
1087
+ "GET / HTTP/1.1\r\n"
1088
+ "X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n"
1089
+ "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n"
1090
+ "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n"
1091
+ "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n"
1092
+ "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n"
1093
+ "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n"
1094
+ "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n"
1095
+ "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n"
1096
+ "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n"
1097
+ "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n"
1098
+ "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n"
1099
+ "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n"
1100
+ "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n"
1101
+ "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n"
1102
+ "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n"
1103
+ "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n"
1104
+ "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n"
1105
+ "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n"
1106
+ "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n"
1107
+ "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n"
1108
+ "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n"
1109
+ "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n"
1110
+ "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n"
1111
+ "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n"
1112
+ "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n"
1113
+ "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n"
1114
+ "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n"
1115
+ "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n"
1116
+ "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n"
1117
+ "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n"
1118
+ "\tRA==\r\n"
1119
+ "\t-----END CERTIFICATE-----\r\n"
1120
+ "\r\n";
1121
+ test_error(dumbfuck2);
1122
+
1123
+ #if 0
1124
+ // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body
1125
+ // until EOF.
1126
+ //
1127
+ // no content-length
1128
+ // error if there is a body without content length
1129
+ const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n"
1130
+ "Accept: */*\r\n"
1131
+ "\r\n"
1132
+ "HELLO";
1133
+ test_error(bad_get_no_headers_no_body);
1134
+ #endif
1135
+ /* TODO sending junk and large headers gets rejected */
1136
+
1137
+
1138
+ /* check to make sure our predefined requests are okay */
1139
+ for (i = 0; requests[i].name; i++) {
1140
+ test_message(&requests[i]);
1141
+ }
1142
+
1143
+
1144
+
1145
+ for (i = 0; i < request_count; i++) {
1146
+ if (!requests[i].should_keep_alive) continue;
1147
+ for (j = 0; j < request_count; j++) {
1148
+ if (!requests[j].should_keep_alive) continue;
1149
+ for (k = 0; k < request_count; k++) {
1150
+ test_multiple3(&requests[i], &requests[j], &requests[k]);
1151
+ }
1152
+ }
1153
+ }
1154
+
1155
+ printf("request scan 1/3 ");
1156
+ test_scan( &requests[GET_NO_HEADERS_NO_BODY]
1157
+ , &requests[GET_ONE_HEADER_NO_BODY]
1158
+ , &requests[GET_NO_HEADERS_NO_BODY]
1159
+ );
1160
+
1161
+ printf("request scan 2/3 ");
1162
+ test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE]
1163
+ , &requests[POST_IDENTITY_BODY_WORLD]
1164
+ , &requests[GET_FUNKY_CONTENT_LENGTH]
1165
+ );
1166
+
1167
+ printf("request scan 3/3 ");
1168
+ test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END]
1169
+ , &requests[CHUNKED_W_TRAILING_HEADERS]
1170
+ , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]
1171
+ );
1172
+
1173
+ puts("requests okay");
1174
+
1175
+ return 0;
1176
+ }