picohttp 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +43 -0
- data/ext/picohttp/picohttp.c +59 -8
- data/lib/picohttp/version.rb +1 -1
- metadata +35 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 32830c406cc611f0808d7b360b22294b03a7a3a76cec684175e6bd058c2d2d6e
|
|
4
|
+
data.tar.gz: f929ecc68df200572fd38506cf00db14cc8b80baaa5ad01a1d51a5b5f11f2805
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f0cc7ecee0bcbb834c2a44ac85c8a33e66278e87efee338fa91c9d392b07337be3d1c7feb2f2b98b015f2bbae574022136702a98b266474b68c1263373520940
|
|
7
|
+
data.tar.gz: 2bbdee0a6887a7fb0d068f80d61e934edb3fd703e67e75f4dbf0fbb271b9f3ffa03a55f3b0995f28e5316daaf37a31f0eb16bc84d88b8149429fb7927d6781df
|
data/README.md
CHANGED
|
@@ -34,6 +34,49 @@ env = Picohttp.parse_request_env(request)
|
|
|
34
34
|
|
|
35
35
|
Returns `nil` for incomplete requests, raises `Picohttp::ParseError` for invalid ones.
|
|
36
36
|
|
|
37
|
+
### Environment fields
|
|
38
|
+
|
|
39
|
+
The produced hash uses CGI/Rack-style keys:
|
|
40
|
+
|
|
41
|
+
| Key | Value | Set when |
|
|
42
|
+
| ----------------- | --------------------------------------------------------------------------------------------------- | -------- |
|
|
43
|
+
| `REQUEST_METHOD` | The request method, e.g. `"GET"`, `"POST"`. | always |
|
|
44
|
+
| `SCRIPT_NAME` | Always `""`. | always |
|
|
45
|
+
| `PATH_INFO` | The request path with any query string removed, e.g. `"/api/users"`. | always |
|
|
46
|
+
| `QUERY_STRING` | The part of the target after `?`, or `""` when there is none. | always |
|
|
47
|
+
| `REQUEST_URI` | The full request target, e.g. `"/api/users?limit=10"`. | always |
|
|
48
|
+
| `SERVER_PROTOCOL` | `"HTTP/1.0"` or `"HTTP/1.1"`. | always |
|
|
49
|
+
| `SERVER_NAME` | The host portion of the `Host` header, with brackets kept for IPv6 literals (`"[::1]"`). | `Host` present |
|
|
50
|
+
| `SERVER_PORT` | The port from the `Host` header, e.g. `"8080"`. | `Host` has a port |
|
|
51
|
+
| `CONTENT_TYPE` | The `Content-Type` header (note: no `HTTP_` prefix). | header present |
|
|
52
|
+
| `CONTENT_LENGTH` | The `Content-Length` header (note: no `HTTP_` prefix). | header present |
|
|
53
|
+
|
|
54
|
+
Every other request header becomes an `HTTP_`-prefixed key: the name is
|
|
55
|
+
uppercased and `-` becomes `_` (e.g. `User-Agent` → `HTTP_USER_AGENT`). Repeated
|
|
56
|
+
headers are joined with `, `. A repeated `Host` header raises
|
|
57
|
+
`Picohttp::ParseError` (RFC 7230 §5.4).
|
|
58
|
+
|
|
59
|
+
### Starting from a template
|
|
60
|
+
|
|
61
|
+
Servers usually add the same constant keys to every env. `parse_request_env_with_template` starts from a copy of a template hash instead, which is faster than merging the constants in afterwards:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
RACK_ENV_CONST = {
|
|
65
|
+
"rack.url_scheme" => "http",
|
|
66
|
+
"SERVER_SOFTWARE" => "MyServer/1.0",
|
|
67
|
+
}.freeze
|
|
68
|
+
|
|
69
|
+
env = Picohttp.parse_request_env_with_template(request, RACK_ENV_CONST)
|
|
70
|
+
# => {
|
|
71
|
+
# "rack.url_scheme" => "http",
|
|
72
|
+
# "SERVER_SOFTWARE" => "MyServer/1.0",
|
|
73
|
+
# "REQUEST_METHOD" => "GET",
|
|
74
|
+
# ...
|
|
75
|
+
# }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The template is never modified and may be frozen. If a key is in both the template and the request, the request wins, but this isn't recommended.
|
|
79
|
+
|
|
37
80
|
## Development
|
|
38
81
|
|
|
39
82
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/ext/picohttp/picohttp.c
CHANGED
|
@@ -132,8 +132,8 @@ build_hash_with_combined_duplicates(VALUE *header_values, int count)
|
|
|
132
132
|
return env;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
static
|
|
136
|
-
|
|
135
|
+
static int
|
|
136
|
+
parse_request_env_pairs(VALUE str, VALUE *header_values)
|
|
137
137
|
{
|
|
138
138
|
Check_Type(str, T_STRING);
|
|
139
139
|
|
|
@@ -150,12 +150,11 @@ picohttp_parse_request_env(VALUE self, VALUE str)
|
|
|
150
150
|
|
|
151
151
|
if (result < 0) {
|
|
152
152
|
if (result == -2) {
|
|
153
|
-
return
|
|
153
|
+
return -1; // Incomplete request
|
|
154
154
|
}
|
|
155
155
|
rb_raise(rb_ePicohttpParseError, "Invalid HTTP request");
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
VALUE header_values[(MAX_HTTP_HEADERS + EXTRA_RACK_HEADERS) * 2];
|
|
159
158
|
int idx = 0;
|
|
160
159
|
|
|
161
160
|
// Standard CGI/Rack environment variables
|
|
@@ -193,6 +192,7 @@ picohttp_parse_request_env(VALUE self, VALUE str)
|
|
|
193
192
|
header_values[idx++] = rb_str_empty;
|
|
194
193
|
|
|
195
194
|
// Convert headers to HTTP_ prefixed environment variables
|
|
195
|
+
int host_seen = 0;
|
|
196
196
|
for (size_t i = 0; i < num_headers; i++) {
|
|
197
197
|
if (headers[i].name == NULL) {
|
|
198
198
|
rb_raise(rb_ePicohttpParseError, "HTTP line folding not supported");
|
|
@@ -201,21 +201,37 @@ picohttp_parse_request_env(VALUE self, VALUE str)
|
|
|
201
201
|
header_values[idx++] = header_name_to_env_key(headers[i].name, headers[i].name_len);
|
|
202
202
|
header_values[idx++] = rb_str_new(headers[i].value, headers[i].value_len);
|
|
203
203
|
|
|
204
|
-
// Extract SERVER_NAME/SERVER_PORT from Host header
|
|
205
204
|
if (headers[i].name_len == 4 &&
|
|
206
205
|
(headers[i].name[0] | 0x20) == 'h' &&
|
|
207
206
|
(headers[i].name[1] | 0x20) == 'o' &&
|
|
208
207
|
(headers[i].name[2] | 0x20) == 's' &&
|
|
209
208
|
(headers[i].name[3] | 0x20) == 't') {
|
|
209
|
+
// More than one Host is a 400 (RFC 7230 5.4). Rejecting also keeps
|
|
210
|
+
// idx bounded: the SERVER_* extras are written at most once.
|
|
211
|
+
if (host_seen) {
|
|
212
|
+
rb_raise(rb_ePicohttpParseError, "Duplicate Host header");
|
|
213
|
+
}
|
|
214
|
+
host_seen = 1;
|
|
215
|
+
|
|
210
216
|
const char *host = headers[i].value;
|
|
211
217
|
size_t host_len = headers[i].value_len;
|
|
212
|
-
const char *
|
|
218
|
+
const char *host_end = host + host_len;
|
|
219
|
+
|
|
220
|
+
// IPv6 authority is "[::1]" or "[::1]:port"; the colons inside the
|
|
221
|
+
// brackets aren't the port separator, so scan only after the ']'.
|
|
222
|
+
const char *colon;
|
|
223
|
+
if (host_len > 0 && host[0] == '[') {
|
|
224
|
+
const char *bracket = memchr(host, ']', host_len);
|
|
225
|
+
colon = bracket ? memchr(bracket, ':', host_end - bracket) : NULL;
|
|
226
|
+
} else {
|
|
227
|
+
colon = memchr(host, ':', host_len);
|
|
228
|
+
}
|
|
213
229
|
|
|
214
230
|
if (colon) {
|
|
215
231
|
header_values[idx++] = rb_str_server_name;
|
|
216
232
|
header_values[idx++] = rb_str_new(host, colon - host);
|
|
217
233
|
header_values[idx++] = rb_str_server_port;
|
|
218
|
-
header_values[idx++] = rb_str_new(colon + 1,
|
|
234
|
+
header_values[idx++] = rb_str_new(colon + 1, host_end - colon - 1);
|
|
219
235
|
} else {
|
|
220
236
|
header_values[idx++] = rb_str_server_name;
|
|
221
237
|
header_values[idx++] = rb_str_new(host, host_len);
|
|
@@ -223,8 +239,19 @@ picohttp_parse_request_env(VALUE self, VALUE str)
|
|
|
223
239
|
}
|
|
224
240
|
}
|
|
225
241
|
|
|
242
|
+
return idx;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
static VALUE
|
|
246
|
+
picohttp_parse_request_env(VALUE self, VALUE str)
|
|
247
|
+
{
|
|
248
|
+
VALUE header_values[(MAX_HTTP_HEADERS + EXTRA_RACK_HEADERS) * 2];
|
|
249
|
+
int idx = parse_request_env_pairs(str, header_values);
|
|
250
|
+
if (idx < 0) return Qnil;
|
|
251
|
+
|
|
226
252
|
#ifdef HAVE_RB_HASH_NEW_CAPA
|
|
227
|
-
|
|
253
|
+
// Extra slack so servers can add their own env keys without a rehash
|
|
254
|
+
VALUE env = rb_hash_new_capa(idx / 2 + 8);
|
|
228
255
|
#else
|
|
229
256
|
VALUE env = rb_hash_new();
|
|
230
257
|
#endif
|
|
@@ -239,6 +266,29 @@ picohttp_parse_request_env(VALUE self, VALUE str)
|
|
|
239
266
|
return env;
|
|
240
267
|
}
|
|
241
268
|
|
|
269
|
+
static VALUE
|
|
270
|
+
picohttp_parse_request_env_with_template(VALUE self, VALUE str, VALUE template)
|
|
271
|
+
{
|
|
272
|
+
Check_Type(template, T_HASH);
|
|
273
|
+
|
|
274
|
+
VALUE header_values[(MAX_HTTP_HEADERS + EXTRA_RACK_HEADERS) * 2];
|
|
275
|
+
int idx = parse_request_env_pairs(str, header_values);
|
|
276
|
+
if (idx < 0) return Qnil;
|
|
277
|
+
|
|
278
|
+
VALUE env = rb_hash_dup(template);
|
|
279
|
+
size_t expected_size = RHASH_SIZE(env) + idx / 2;
|
|
280
|
+
|
|
281
|
+
rb_hash_bulk_insert(idx, header_values, env);
|
|
282
|
+
|
|
283
|
+
// Handle duplicate headers per RFC 7230
|
|
284
|
+
if (RHASH_SIZE(env) != expected_size) {
|
|
285
|
+
env = rb_hash_dup(template);
|
|
286
|
+
rb_hash_update_by(env, build_hash_with_combined_duplicates(header_values, idx), NULL);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return env;
|
|
290
|
+
}
|
|
291
|
+
|
|
242
292
|
static VALUE
|
|
243
293
|
register_interned_string(const char *str)
|
|
244
294
|
{
|
|
@@ -258,6 +308,7 @@ Init_picohttp(void)
|
|
|
258
308
|
rb_ePicohttpParseError = rb_define_class_under(rb_mPicohttp, "ParseError", rb_eStandardError);
|
|
259
309
|
rb_define_module_function(rb_mPicohttp, "parse_request", picohttp_parse_request, 1);
|
|
260
310
|
rb_define_module_function(rb_mPicohttp, "parse_request_env", picohttp_parse_request_env, 1);
|
|
311
|
+
rb_define_module_function(rb_mPicohttp, "parse_request_env_with_template", picohttp_parse_request_env_with_template, 2);
|
|
261
312
|
|
|
262
313
|
// Initialize interned string constants
|
|
263
314
|
init_string_lookup();
|
data/lib/picohttp/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,60 +1,61 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: picohttp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
- John Hawthorn
|
|
7
|
+
- John Hawthorn
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
|
-
description: Wraps picohttpparser to parse HTTP request strings into Rack-compatible
|
|
13
|
-
hashes
|
|
12
|
+
description: Wraps picohttpparser to parse HTTP request strings into Rack-compatible hashes
|
|
14
13
|
email:
|
|
15
|
-
- john@hawthorn.email
|
|
14
|
+
- john@hawthorn.email
|
|
16
15
|
executables: []
|
|
17
16
|
extensions:
|
|
18
|
-
- ext/picohttp/extconf.rb
|
|
17
|
+
- ext/picohttp/extconf.rb
|
|
19
18
|
extra_rdoc_files: []
|
|
20
19
|
files:
|
|
21
|
-
- CODE_OF_CONDUCT.md
|
|
22
|
-
- LICENSE.txt
|
|
23
|
-
- README.md
|
|
24
|
-
- Rakefile
|
|
25
|
-
- ext/picohttp/extconf.rb
|
|
26
|
-
- ext/picohttp/picohttp.c
|
|
27
|
-
- ext/picohttp/picohttp.h
|
|
28
|
-
- ext/picohttp/picohttpparser.c
|
|
29
|
-
- ext/picohttp/picohttpparser.h
|
|
30
|
-
- ext/picohttp/string_lookup.inc
|
|
31
|
-
- lib/picohttp.rb
|
|
32
|
-
- lib/picohttp/version.rb
|
|
33
|
-
- tool/generate_lookup.rb
|
|
34
|
-
homepage: https://github.com/jhawthorn/picohttp
|
|
20
|
+
- CODE_OF_CONDUCT.md
|
|
21
|
+
- LICENSE.txt
|
|
22
|
+
- README.md
|
|
23
|
+
- Rakefile
|
|
24
|
+
- ext/picohttp/extconf.rb
|
|
25
|
+
- ext/picohttp/picohttp.c
|
|
26
|
+
- ext/picohttp/picohttp.h
|
|
27
|
+
- ext/picohttp/picohttpparser.c
|
|
28
|
+
- ext/picohttp/picohttpparser.h
|
|
29
|
+
- ext/picohttp/string_lookup.inc
|
|
30
|
+
- lib/picohttp.rb
|
|
31
|
+
- lib/picohttp/version.rb
|
|
32
|
+
- tool/generate_lookup.rb
|
|
33
|
+
homepage: "https://github.com/jhawthorn/picohttp"
|
|
35
34
|
licenses:
|
|
36
|
-
- MIT
|
|
35
|
+
- MIT
|
|
37
36
|
metadata:
|
|
38
|
-
homepage_uri: https://github.com/jhawthorn/picohttp
|
|
39
|
-
source_code_uri: https://github.com/jhawthorn/picohttp
|
|
40
|
-
changelog_uri: https://github.com/jhawthorn/picohttp/blob/main/CHANGELOG.md
|
|
41
|
-
bug_tracker_uri: https://github.com/jhawthorn/picohttp/issues
|
|
42
|
-
documentation_uri: https://github.com/jhawthorn/picohttp#readme
|
|
37
|
+
homepage_uri: "https://github.com/jhawthorn/picohttp"
|
|
38
|
+
source_code_uri: "https://github.com/jhawthorn/picohttp"
|
|
39
|
+
changelog_uri: "https://github.com/jhawthorn/picohttp/blob/main/CHANGELOG.md"
|
|
40
|
+
bug_tracker_uri: "https://github.com/jhawthorn/picohttp/issues"
|
|
41
|
+
documentation_uri: "https://github.com/jhawthorn/picohttp#readme"
|
|
43
42
|
rdoc_options: []
|
|
44
43
|
require_paths:
|
|
45
|
-
- lib
|
|
44
|
+
- lib
|
|
46
45
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
46
|
requirements:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
-
|
|
48
|
+
- ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: 3.2.0
|
|
51
51
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
52
52
|
requirements:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
-
|
|
54
|
+
- ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: "0"
|
|
56
57
|
requirements: []
|
|
57
|
-
rubygems_version: 4.0.
|
|
58
|
+
rubygems_version: 4.1.0.beta1
|
|
58
59
|
specification_version: 4
|
|
59
60
|
summary: Fast HTTP request parser using picohttpparser
|
|
60
61
|
test_files: []
|