pico_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,62 @@
1
+ /*
2
+ * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase
3
+ *
4
+ * The software is licensed under either the MIT License (below) or the Perl
5
+ * license.
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to
9
+ * deal in the Software without restriction, including without limitation the
10
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11
+ * sell copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23
+ * IN THE SOFTWARE.
24
+ */
25
+
26
+ #ifndef picohttpparser_h
27
+ #define picohttpparser_h
28
+
29
+ /* $Id$ */
30
+
31
+ #ifdef __cplusplus
32
+ extern "C" {
33
+ #endif
34
+
35
+ /* contains name and value of a header (name == NULL if is a continuing line
36
+ * of a multiline header */
37
+ struct phr_header {
38
+ const char* name;
39
+ size_t name_len;
40
+ const char* value;
41
+ size_t value_len;
42
+ };
43
+
44
+ /* returns number of bytes cosumed if successful, -2 if request is partial,
45
+ * -1 if failed */
46
+ int phr_parse_request(const char* buf, size_t len, const char** method,
47
+ size_t* method_len, const char** path,
48
+ size_t* path_len, int* minor_version,
49
+ struct phr_header* headers, size_t* num_headers,
50
+ size_t last_len);
51
+
52
+ /* ditto */
53
+ int phr_parse_response(const char* _buf, size_t len, int *minor_version,
54
+ int *status, const char **msg, size_t *msg_len,
55
+ struct phr_header* headers, size_t* num_headers,
56
+ size_t last_len);
57
+
58
+ #ifdef __cplusplus
59
+ }
60
+ #endif
61
+
62
+ #endif
@@ -0,0 +1,241 @@
1
+ /* use `make test` to run the test */
2
+ /*
3
+ * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase
4
+ *
5
+ * The software is licensed under either the MIT License (below) or the Perl
6
+ * license.
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to
10
+ * deal in the Software without restriction, including without limitation the
11
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
+ * sell copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in
16
+ * all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
+ * IN THE SOFTWARE.
25
+ */
26
+
27
+ #include <stdio.h>
28
+ #include <string.h>
29
+ #include "picotest/picotest.h"
30
+ #include "picohttpparser.h"
31
+
32
+ static int bufis(const char* s, size_t l, const char* t)
33
+ {
34
+ return strlen(t) == l && memcmp(s, t, l) == 0;
35
+ }
36
+
37
+ static void test_request(void)
38
+ {
39
+ const char* method;
40
+ size_t method_len;
41
+ const char* path;
42
+ size_t path_len;
43
+ int minor_version;
44
+ struct phr_header headers[4];
45
+ size_t num_headers;
46
+
47
+ #define PARSE(s, last_len, exp, comment) \
48
+ do { \
49
+ note(comment); \
50
+ num_headers = sizeof(headers) / sizeof(headers[0]); \
51
+ ok(phr_parse_request(s, sizeof(s) - 1, &method, &method_len, &path, \
52
+ &path_len, &minor_version, headers, \
53
+ &num_headers, last_len) \
54
+ == (exp == 0 ? strlen(s) : exp)); \
55
+ } while (0)
56
+
57
+ PARSE("GET / HTTP/1.0\r\n\r\n", 0, 0, "simple");
58
+ ok(num_headers == 0);
59
+ ok(bufis(method, method_len, "GET"));
60
+ ok(bufis(path, path_len, "/"));
61
+ ok(minor_version == 0);
62
+
63
+ PARSE("GET / HTTP/1.0\r\n\r", 0, -2, "partial");
64
+
65
+ PARSE("GET /hoge HTTP/1.1\r\nHost: example.com\r\nCookie: \r\n\r\n", 0, 0,
66
+ "parse headers");
67
+ ok(num_headers == 2);
68
+ ok(bufis(method, method_len, "GET"));
69
+ ok(bufis(path, path_len, "/hoge"));
70
+ ok(minor_version == 1);
71
+ ok(bufis(headers[0].name, headers[0].name_len, "Host"));
72
+ ok(bufis(headers[0].value, headers[0].value_len, "example.com"));
73
+ ok(bufis(headers[1].name, headers[1].name_len, "Cookie"));
74
+ ok(bufis(headers[1].value, headers[1].value_len, ""));
75
+
76
+ PARSE("GET /hoge HTTP/1.1\r\nHost: example.com\r\nUser-Agent: \343\201\262\343/1.0\r\n\r\n", 0, 0,
77
+ "multibyte included");
78
+ ok(num_headers == 2);
79
+ ok(bufis(method, method_len, "GET"));
80
+ ok(bufis(path, path_len, "/hoge"));
81
+ ok(minor_version == 1);
82
+ ok(bufis(headers[0].name, headers[0].name_len, "Host"));
83
+ ok(bufis(headers[0].value, headers[0].value_len, "example.com"));
84
+ ok(bufis(headers[1].name, headers[1].name_len, "User-Agent"));
85
+ ok(bufis(headers[1].value, headers[1].value_len, "\343\201\262\343/1.0"));
86
+
87
+ PARSE("GET / HTTP/1.0\r\nfoo: \r\nfoo: b\r\n \tc\r\n\r\n", 0, 0,
88
+ "parse multiline");
89
+ ok(num_headers == 3);
90
+ ok(bufis(method, method_len, "GET"));
91
+ ok(bufis(path, path_len, "/"));
92
+ ok(minor_version == 0);
93
+ ok(bufis(headers[0].name, headers[0].name_len, "foo"));
94
+ ok(bufis(headers[0].value, headers[0].value_len, ""));
95
+ ok(bufis(headers[1].name, headers[1].name_len, "foo"));
96
+ ok(bufis(headers[1].value, headers[1].value_len, "b"));
97
+ ok(headers[2].name == NULL);
98
+ ok(bufis(headers[2].value, headers[2].value_len, " \tc"));
99
+
100
+ PARSE("GET", 0, -2, "incomplete 1");
101
+ ok(method == NULL);
102
+ PARSE("GET ", 0, -2, "incomplete 2");
103
+ ok(bufis(method, method_len, "GET"));
104
+ PARSE("GET /", 0, -2, "incomplete 3");
105
+ ok(path == NULL);
106
+ PARSE("GET / ", 0, -2, "incomplete 4");
107
+ ok(bufis(path, path_len, "/"));
108
+ PARSE("GET / H", 0, -2, "incomplete 5");
109
+ PARSE("GET / HTTP/1.", 0, -2, "incomplete 6");
110
+ PARSE("GET / HTTP/1.0", 0, -2, "incomplete 7");
111
+ ok(minor_version == -1);
112
+ PARSE("GET / HTTP/1.0\r", 0, -2, "incomplete 8");
113
+ ok(minor_version == 0);
114
+
115
+ PARSE("GET /hoge HTTP/1.0\r\n\r", strlen("GET /hoge HTTP/1.0\r\n\r") - 1,
116
+ -2, "slowloris (incomplete)");
117
+ PARSE("GET /hoge HTTP/1.0\r\n\r\n", strlen("GET /hoge HTTP/1.0\r\n\r\n") - 1,
118
+ 0, "slowloris (complete)");
119
+
120
+ PARSE("GET / HTTP/1.0\r\n:a\r\n\r\n", 0, -1, "empty header name");
121
+ PARSE("GET / HTTP/1.0\r\n :a\r\n\r\n", 0, -1, "header name (space only)");
122
+
123
+ PARSE("G\0T / HTTP/1.0\r\n\r\n", 0, -1, "NUL in method");
124
+ PARSE("G\tT / HTTP/1.0\r\n\r\n", 0, -1, "tab in method");
125
+ PARSE("GET /\x7fhello HTTP/1.0\r\n\r\n", 0, -1, "DEL in uri-path");
126
+ PARSE("GET / HTTP/1.0\r\na\0b: c\r\n\r\n", 0, -1, "NUL in header name");
127
+ PARSE("GET / HTTP/1.0\r\nab: c\0d\r\n\r\n", 0, -1, "NUL in header value");
128
+ PARSE("GET /\xa0 HTTP/1.0\r\nh: c\xa2y\r\n\r\n", 0, 0, "accept MSB chars");
129
+ ok(num_headers == 1);
130
+ ok(bufis(method, method_len, "GET"));
131
+ ok(bufis(path, path_len, "/\xa0"));
132
+ ok(minor_version == 0);
133
+ ok(bufis(headers[0].name, headers[0].name_len, "h"));
134
+ ok(bufis(headers[0].value, headers[0].value_len, "c\xa2y"));
135
+
136
+ #undef PARSE
137
+ }
138
+
139
+ static void test_response(void)
140
+ {
141
+ int minor_version;
142
+ int status;
143
+ const char *msg;
144
+ size_t msg_len;
145
+ struct phr_header headers[4];
146
+ size_t num_headers;
147
+
148
+ #define PARSE(s, last_len, exp, comment) \
149
+ do { \
150
+ note(comment); \
151
+ num_headers = sizeof(headers) / sizeof(headers[0]); \
152
+ ok(phr_parse_response(s, strlen(s), &minor_version, &status, \
153
+ &msg, &msg_len, headers, \
154
+ &num_headers, last_len) \
155
+ == (exp == 0 ? strlen(s) : exp)); \
156
+ } while (0)
157
+
158
+ PARSE("HTTP/1.0 200 OK\r\n\r\n", 0, 0, "simple");
159
+ ok(num_headers == 0);
160
+ ok(status == 200);
161
+ ok(minor_version = 1);
162
+ ok(bufis(msg, msg_len, "OK"));
163
+
164
+ PARSE("HTTP/1.0 200 OK\r\n\r", 0, -2, "partial");
165
+
166
+ PARSE("HTTP/1.1 200 OK\r\nHost: example.com\r\nCookie: \r\n\r\n", 0, 0,
167
+ "parse headers");
168
+ ok(num_headers == 2);
169
+ ok(minor_version == 1);
170
+ ok(status == 200);
171
+ ok(bufis(msg, msg_len, "OK"));
172
+ ok(bufis(headers[0].name, headers[0].name_len, "Host"));
173
+ ok(bufis(headers[0].value, headers[0].value_len, "example.com"));
174
+ ok(bufis(headers[1].name, headers[1].name_len, "Cookie"));
175
+ ok(bufis(headers[1].value, headers[1].value_len, ""));
176
+
177
+ PARSE("HTTP/1.0 200 OK\r\nfoo: \r\nfoo: b\r\n \tc\r\n\r\n", 0, 0,
178
+ "parse multiline");
179
+ ok(num_headers == 3);
180
+ ok(minor_version == 0);
181
+ ok(status == 200);
182
+ ok(bufis(msg, msg_len, "OK"));
183
+ ok(bufis(headers[0].name, headers[0].name_len, "foo"));
184
+ ok(bufis(headers[0].value, headers[0].value_len, ""));
185
+ ok(bufis(headers[1].name, headers[1].name_len, "foo"));
186
+ ok(bufis(headers[1].value, headers[1].value_len, "b"));
187
+ ok(headers[2].name == NULL);
188
+ ok(bufis(headers[2].value, headers[2].value_len, " \tc"));
189
+
190
+ PARSE("HTTP/1.0 500 Internal Server Error\r\n\r\n", 0, 0,
191
+ "internal server error");
192
+ ok(num_headers == 0);
193
+ ok(minor_version == 0);
194
+ ok(status == 500);
195
+ ok(bufis(msg, msg_len, "Internal Server Error"));
196
+ ok(msg_len == sizeof("Internal Server Error")-1);
197
+
198
+ PARSE("H", 0, -2, "incomplete 1");
199
+ PARSE("HTTP/1.", 0, -2, "incomplete 2");
200
+ PARSE("HTTP/1.1", 0, -2, "incomplete 3");
201
+ ok(minor_version == -1);
202
+ PARSE("HTTP/1.1 ", 0, -2, "incomplete 4");
203
+ ok(minor_version == 1);
204
+ PARSE("HTTP/1.1 2", 0, -2, "incomplete 5");
205
+ PARSE("HTTP/1.1 200", 0, -2, "incomplete 6");
206
+ ok(status == 0);
207
+ PARSE("HTTP/1.1 200 ", 0, -2, "incomplete 7");
208
+ ok(status == 200);
209
+ PARSE("HTTP/1.1 200 O", 0, -2, "incomplete 8");
210
+ PARSE("HTTP/1.1 200 OK\r", 0, -2, "incomplete 9");
211
+ ok(msg == NULL);
212
+ PARSE("HTTP/1.1 200 OK\r\n", 0, -2, "incomplete 10");
213
+ ok(bufis(msg, msg_len, "OK"));
214
+ PARSE("HTTP/1.1 200 OK\n", 0, -2, "incomplete 11");
215
+ ok(bufis(msg, msg_len, "OK"));
216
+
217
+ PARSE("HTTP/1.1 200 OK\r\nA: 1\r", 0, -2, "incomplete 11");
218
+ ok(num_headers == 0);
219
+ PARSE("HTTP/1.1 200 OK\r\nA: 1\r\n", 0, -2, "incomplete 12");
220
+ ok(num_headers == 1);
221
+ ok(bufis(headers[0].name, headers[0].name_len, "A"));
222
+ ok(bufis(headers[0].value, headers[0].value_len, "1"));
223
+
224
+ PARSE("HTTP/1.0 200 OK\r\n\r", strlen("GET /hoge HTTP/1.0\r\n\r") - 1,
225
+ -2, "slowloris (incomplete)");
226
+ PARSE("HTTP/1.0 200 OK\r\n\r\n", strlen("HTTP/1.0 200 OK\r\n\r\n") - 1,
227
+ 0, "slowloris (complete)");
228
+
229
+ PARSE("HTTP/1. 200 OK\r\n\r\n", 0, -1, "invalid http version");
230
+ PARSE("HTTP/1.2z 200 OK\r\n\r\n", 0, -1, "invalid http version 2");
231
+ PARSE("HTTP/1.1 OK\r\n\r\n", 0, -1, "no status code");
232
+
233
+ #undef PARSE
234
+ }
235
+
236
+ int main(int argc, char **argv)
237
+ {
238
+ subtest("request", test_request);
239
+ subtest("response", test_response);
240
+ return done_testing();
241
+ }
@@ -0,0 +1,5 @@
1
+ require "pico_http_parser/version"
2
+ require "pico_http_parser/pico_http_parser"
3
+
4
+ class PicoHTTPParser
5
+ end
@@ -0,0 +1,3 @@
1
+ class PicoHTTPParser
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,50 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pico_http_parser/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pico_http_parser"
8
+ spec.version = PicoHTTPParser::VERSION
9
+ spec.authors = ["Masahiro Nagano"]
10
+ spec.email = ["kazeburo@gmail.com"]
11
+ spec.summary = %q{Fast HTTP parser using picohttparser}
12
+ spec.description = %q{Fast HTTP parser using picohttparser}
13
+ spec.homepage = "https://github.com/kazeburo/pico_http_parser"
14
+ spec.license = "Artistic"
15
+ spec.extensions = %w[ext/pico_http_parser/extconf.rb]
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec", "~> 3"
25
+
26
+ # get an array of submodule dirs by executing 'pwd' inside each submodule
27
+ `git submodule --quiet foreach pwd`.split($\).each do |submodule_path|
28
+ # for each submodule, change working directory to that submodule
29
+ Dir.chdir(submodule_path) do
30
+
31
+ # issue git ls-files in submodule's directory
32
+ submodule_files = `git ls-files`.split($\)
33
+
34
+ # prepend the submodule path to create absolute file paths
35
+ submodule_files_fullpaths = submodule_files.map do |filename|
36
+ "#{submodule_path}/#{filename}"
37
+ end
38
+
39
+ # remove leading path parts to get paths relative to the gem's root dir
40
+ # (this assumes, that the gemspec resides in the gem's root dir)
41
+ submodule_files_paths = submodule_files_fullpaths.map do |filename|
42
+ filename.gsub "#{File.dirname(__FILE__)}/", ""
43
+ end
44
+
45
+ # add relative paths to gem.files
46
+ spec.files += submodule_files_paths
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,170 @@
1
+ require 'spec_helper'
2
+
3
+ describe PicoHTTPParser do
4
+ it 'has a version number' do
5
+ expect(PicoHTTPParser::VERSION).not_to be nil
6
+ end
7
+
8
+ it 'result of GET /' do
9
+ env = {}
10
+ PicoHTTPParser.parse_http_request("GET /abc?x=%79 HTTP/1.0\r\n\r\n",env)
11
+ expect(env).to match({
12
+ 'PATH_INFO' => '/abc',
13
+ 'QUERY_STRING' => 'x=%79',
14
+ 'REQUEST_METHOD' => "GET",
15
+ 'REQUEST_URI' => '/abc?x=%79',
16
+ 'SCRIPT_NAME' => '',
17
+ 'SERVER_PROTOCOL' => 'HTTP/1.0',
18
+ })
19
+ end
20
+
21
+ it 'result of POST with headers' do
22
+ env = {}
23
+ req = <<"EOT";
24
+ POST /hoge HTTP/1.1\r
25
+ Content-Type: text/plain\r
26
+ Content-Length: 15\r
27
+ Host: example.com\r
28
+ User-Agent: hoge\r
29
+ \r
30
+ EOT
31
+ ret = PicoHTTPParser.parse_http_request(req,env)
32
+ expect(ret).to eq(req.length)
33
+ expect(env).to match({
34
+ 'CONTENT_LENGTH' => "15",
35
+ 'CONTENT_TYPE' => 'text/plain',
36
+ 'HTTP_HOST' => 'example.com',
37
+ 'HTTP_USER_AGENT' => 'hoge',
38
+ 'PATH_INFO' => '/hoge',
39
+ 'REQUEST_METHOD' => "POST",
40
+ 'REQUEST_URI' => '/hoge',
41
+ 'QUERY_STRING' => '',
42
+ 'SCRIPT_NAME' => '',
43
+ 'SERVER_PROTOCOL' => 'HTTP/1.1',
44
+ })
45
+ end
46
+
47
+ it 'multiline header' do
48
+ env = {}
49
+ req = <<"EOT";
50
+ GET / HTTP/1.0\r
51
+ Foo: \r
52
+ Foo: \r
53
+ abc\r
54
+ de\r
55
+ Foo: fgh\r
56
+ \r
57
+ EOT
58
+ ret = PicoHTTPParser.parse_http_request(req,env)
59
+ expect(ret).to eq(req.length)
60
+ expect(env).to match({
61
+ 'HTTP_FOO' => ', abc de, fgh',
62
+ 'PATH_INFO' => '/',
63
+ 'QUERY_STRING' => '',
64
+ 'REQUEST_METHOD' => 'GET',
65
+ 'REQUEST_URI' => '/',
66
+ 'SCRIPT_NAME' => '',
67
+ 'SERVER_PROTOCOL' => 'HTTP/1.0',
68
+ })
69
+ end
70
+
71
+
72
+ it 'url-encoded' do
73
+ env = {}
74
+ req = <<"EOT";
75
+ GET /a%20b HTTP/1.0\r
76
+ \r
77
+ EOT
78
+ ret = PicoHTTPParser.parse_http_request(req,env)
79
+ expect(ret).to eq(req.length)
80
+ expect(env).to match({
81
+ 'PATH_INFO' => '/a b',
82
+ 'REQUEST_METHOD' => 'GET',
83
+ 'REQUEST_URI' => '/a%20b',
84
+ 'QUERY_STRING' => '',
85
+ 'SCRIPT_NAME' => '',
86
+ 'SERVER_PROTOCOL' => 'HTTP/1.0',
87
+ })
88
+ end
89
+
90
+ it 'invalid char in url-encoded path' do
91
+ env = {}
92
+ req = <<"EOT";
93
+ GET /a%2zb HTTP/1.0\r
94
+ \r
95
+ EOT
96
+ ret = PicoHTTPParser.parse_http_request(req,env)
97
+ expect(ret).to eq(-1)
98
+ expect(env).to match({})
99
+ end
100
+
101
+ it 'particaly url-encoded path' do
102
+ env = {}
103
+ req = <<"EOT";
104
+ GET /a%2 HTTP/1.0\r
105
+ \r
106
+ EOT
107
+ ret = PicoHTTPParser.parse_http_request(req,env)
108
+ expect(ret).to eq(-1)
109
+ expect(env).to match({})
110
+ end
111
+
112
+ it 'uri fragment' do
113
+ env = {}
114
+ req = <<"EOT";
115
+ GET /a/b#c HTTP/1.0\r
116
+ \r
117
+ EOT
118
+ ret = PicoHTTPParser.parse_http_request(req,env)
119
+ expect(ret).to eq(req.length)
120
+ expect(env).to match({
121
+ 'SCRIPT_NAME' => '',
122
+ 'PATH_INFO' => '/a/b',
123
+ 'REQUEST_METHOD' => 'GET',
124
+ 'REQUEST_URI' => '/a/b#c',
125
+ 'QUERY_STRING' => '',
126
+ 'SCRIPT_NAME' => '',
127
+ 'SERVER_PROTOCOL' => 'HTTP/1.0',
128
+ })
129
+ end
130
+
131
+ it 'uri fragment %23 -> #' do
132
+ env = {}
133
+ req = <<"EOT";
134
+ GET /a/b%23c HTTP/1.0\r
135
+ \r
136
+ EOT
137
+ ret = PicoHTTPParser.parse_http_request(req,env)
138
+ expect(ret).to eq(req.length)
139
+ expect(env).to match({
140
+ 'SCRIPT_NAME' => '',
141
+ 'PATH_INFO' => '/a/b#c',
142
+ 'REQUEST_METHOD' => 'GET',
143
+ 'REQUEST_URI' => '/a/b%23c',
144
+ 'QUERY_STRING' => '',
145
+ 'SCRIPT_NAME' => '',
146
+ 'SERVER_PROTOCOL' => 'HTTP/1.0',
147
+ })
148
+ end
149
+
150
+
151
+ it 'URI fragment after query string' do
152
+ env = {}
153
+ req = <<"EOT";
154
+ GET /a/b?c=d#e HTTP/1.0\r
155
+ \r
156
+ EOT
157
+ ret = PicoHTTPParser.parse_http_request(req,env)
158
+ expect(ret).to eq(req.length)
159
+ expect(env).to match({
160
+ 'SCRIPT_NAME' => '',
161
+ 'PATH_INFO' => '/a/b',
162
+ 'REQUEST_METHOD' => 'GET',
163
+ 'REQUEST_URI' => '/a/b?c=d#e',
164
+ 'QUERY_STRING' => 'c=d',
165
+ 'SCRIPT_NAME' => '',
166
+ 'SERVER_PROTOCOL' => 'HTTP/1.0',
167
+ })
168
+ end
169
+
170
+ end