iodine 0.7.38 → 0.7.39
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +5 -12
- data/CHANGELOG.md +8 -0
- data/README.md +20 -10
- data/Rakefile +3 -1
- data/ext/iodine/extconf.rb +0 -34
- data/ext/iodine/http.c +76 -93
- data/ext/iodine/http1.c +2 -13
- data/ext/iodine/http1_parser.h +806 -63
- data/ext/iodine/iodine_defer.c +1 -0
- data/lib/iodine/connection.rb +12 -0
- data/lib/iodine/version.rb +1 -1
- metadata +4 -5
- data/ext/iodine/http1_parser.c +0 -616
data/ext/iodine/iodine_defer.c
CHANGED
data/lib/iodine/connection.rb
CHANGED
@@ -17,28 +17,40 @@ module Iodine
|
|
17
17
|
# Instances of this class are passed to the callback objects. i.e.:
|
18
18
|
#
|
19
19
|
# module MyConnectionCallbacks
|
20
|
+
#
|
20
21
|
# # called when the callback object is linked with a new client
|
21
22
|
# def on_open client
|
22
23
|
# client.is_a?(Iodine::Connection) # => true
|
23
24
|
# end
|
25
|
+
#
|
24
26
|
# # called when data is available
|
25
27
|
# def on_message client, data
|
26
28
|
# client.is_a?(Iodine::Connection) # => true
|
27
29
|
# end
|
30
|
+
#
|
28
31
|
# # called when the server is shutting down, before closing the client
|
29
32
|
# # (it's still possible to send messages to the client)
|
30
33
|
# def on_shutdown client
|
31
34
|
# client.is_a?(Iodine::Connection) # => true
|
32
35
|
# end
|
36
|
+
#
|
33
37
|
# # called when the client is closed (no longer available)
|
34
38
|
# def on_close client
|
35
39
|
# client.is_a?(Iodine::Connection) # => true
|
36
40
|
# end
|
41
|
+
#
|
37
42
|
# # called when all the previous calls to `client.write` have completed
|
38
43
|
# # (the local buffer was drained and is now empty)
|
39
44
|
# def on_drained client
|
40
45
|
# client.is_a?(Iodine::Connection) # => true
|
41
46
|
# end
|
47
|
+
#
|
48
|
+
# # called when timeout was reached, llowing a `ping` to be sent
|
49
|
+
# def ping client
|
50
|
+
# client.is_a?(Iodine::Connection) # => true
|
51
|
+
# clint.close() # close connection on timeout is the default
|
52
|
+
# end
|
53
|
+
#
|
42
54
|
# # Allows the module to be used as a static callback object (avoiding object allocation)
|
43
55
|
# extend self
|
44
56
|
# end
|
data/lib/iodine/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iodine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.39
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -188,7 +188,6 @@ files:
|
|
188
188
|
- ext/iodine/http.h
|
189
189
|
- ext/iodine/http1.c
|
190
190
|
- ext/iodine/http1.h
|
191
|
-
- ext/iodine/http1_parser.c
|
192
191
|
- ext/iodine/http1_parser.h
|
193
192
|
- ext/iodine/http_internal.c
|
194
193
|
- ext/iodine/http_internal.h
|
@@ -244,7 +243,7 @@ licenses:
|
|
244
243
|
metadata:
|
245
244
|
allowed_push_host: https://rubygems.org
|
246
245
|
post_install_message: |-
|
247
|
-
Thank you for installing Iodine 0.7.
|
246
|
+
Thank you for installing Iodine 0.7.39.
|
248
247
|
Remember: if iodine supports your business, it's only fair to give value back (code contributions / donations).
|
249
248
|
rdoc_options: []
|
250
249
|
require_paths:
|
@@ -266,7 +265,7 @@ requirements:
|
|
266
265
|
- Ruby >= 2.3.8 (Ruby EOL).
|
267
266
|
- Ruby >= 2.5.0 recommended.
|
268
267
|
- TLS requires OpenSSL >= 1.1.0
|
269
|
-
rubygems_version: 3.
|
268
|
+
rubygems_version: 3.1.2
|
270
269
|
signing_key:
|
271
270
|
specification_version: 4
|
272
271
|
summary: iodine - a fast HTTP / Websocket Server with Pub/Sub support, optimized for
|
data/ext/iodine/http1_parser.c
DELETED
@@ -1,616 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
Copyright: Boaz Segev, 2017-2019
|
3
|
-
License: MIT
|
4
|
-
|
5
|
-
Feel free to copy, use and enjoy according to the license provided.
|
6
|
-
*/
|
7
|
-
#ifndef __GNU_SOURCE
|
8
|
-
#define __GNU_SOURCE
|
9
|
-
#endif
|
10
|
-
|
11
|
-
#include <http1_parser.h>
|
12
|
-
|
13
|
-
#include <ctype.h>
|
14
|
-
#include <stdio.h>
|
15
|
-
#include <string.h>
|
16
|
-
|
17
|
-
/* *****************************************************************************
|
18
|
-
Seeking for characters in a string
|
19
|
-
***************************************************************************** */
|
20
|
-
|
21
|
-
#ifndef ALLOW_UNALIGNED_MEMORY_ACCESS
|
22
|
-
#define ALLOW_UNALIGNED_MEMORY_ACCESS 0
|
23
|
-
#endif
|
24
|
-
|
25
|
-
#ifndef HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
26
|
-
#define HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING 1
|
27
|
-
#endif
|
28
|
-
|
29
|
-
#if FIO_MEMCHAR
|
30
|
-
|
31
|
-
/**
|
32
|
-
* This seems to be faster on some systems, especially for smaller distances.
|
33
|
-
*
|
34
|
-
* On newer systems, `memchr` should be faster.
|
35
|
-
*/
|
36
|
-
static int seek2ch(uint8_t **buffer, register uint8_t *const limit,
|
37
|
-
const uint8_t c) {
|
38
|
-
if (*buffer >= limit)
|
39
|
-
return 0;
|
40
|
-
|
41
|
-
if (**buffer == c) {
|
42
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
43
|
-
**buffer = 0;
|
44
|
-
#endif
|
45
|
-
return 1;
|
46
|
-
}
|
47
|
-
|
48
|
-
#if !ALLOW_UNALIGNED_MEMORY_ACCESS || !defined(__x86_64__)
|
49
|
-
/* too short for this mess */
|
50
|
-
if ((uintptr_t)limit <= 16 + ((uintptr_t)*buffer & (~(uintptr_t)7)))
|
51
|
-
goto finish;
|
52
|
-
|
53
|
-
/* align memory */
|
54
|
-
{
|
55
|
-
const uint8_t *alignment =
|
56
|
-
(uint8_t *)(((uintptr_t)(*buffer) & (~(uintptr_t)7)) + 8);
|
57
|
-
if (limit >= alignment) {
|
58
|
-
while (*buffer < alignment) {
|
59
|
-
if (**buffer == c) {
|
60
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
61
|
-
**buffer = 0;
|
62
|
-
#endif
|
63
|
-
return 1;
|
64
|
-
}
|
65
|
-
*buffer += 1;
|
66
|
-
}
|
67
|
-
}
|
68
|
-
}
|
69
|
-
const uint8_t *limit64 = (uint8_t *)((uintptr_t)limit & (~(uintptr_t)7));
|
70
|
-
#else
|
71
|
-
const uint8_t *limit64 = (uint8_t *)limit - 7;
|
72
|
-
#endif
|
73
|
-
uint64_t wanted1 = 0x0101010101010101ULL * c;
|
74
|
-
for (; *buffer < limit64; *buffer += 8) {
|
75
|
-
const uint64_t eq1 = ~((*((uint64_t *)*buffer)) ^ wanted1);
|
76
|
-
const uint64_t t0 = (eq1 & 0x7f7f7f7f7f7f7f7fllu) + 0x0101010101010101llu;
|
77
|
-
const uint64_t t1 = (eq1 & 0x8080808080808080llu);
|
78
|
-
if ((t0 & t1)) {
|
79
|
-
break;
|
80
|
-
}
|
81
|
-
}
|
82
|
-
#if !ALLOW_UNALIGNED_MEMORY_ACCESS || !defined(__x86_64__)
|
83
|
-
finish:
|
84
|
-
#endif
|
85
|
-
while (*buffer < limit) {
|
86
|
-
if (**buffer == c) {
|
87
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
88
|
-
**buffer = 0;
|
89
|
-
#endif
|
90
|
-
return 1;
|
91
|
-
}
|
92
|
-
(*buffer)++;
|
93
|
-
}
|
94
|
-
return 0;
|
95
|
-
}
|
96
|
-
|
97
|
-
#else
|
98
|
-
|
99
|
-
/* a helper that seeks any char, converts it to NUL and returns 1 if found. */
|
100
|
-
inline static uint8_t seek2ch(uint8_t **pos, uint8_t *const limit, uint8_t ch) {
|
101
|
-
/* This is library based alternative that is sometimes slower */
|
102
|
-
if (*pos >= limit)
|
103
|
-
return 0;
|
104
|
-
if (**pos == ch) {
|
105
|
-
return 1;
|
106
|
-
}
|
107
|
-
uint8_t *tmp = memchr(*pos, ch, limit - (*pos));
|
108
|
-
if (tmp) {
|
109
|
-
*pos = tmp;
|
110
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
111
|
-
*tmp = 0;
|
112
|
-
#endif
|
113
|
-
return 1;
|
114
|
-
}
|
115
|
-
*pos = limit;
|
116
|
-
return 0;
|
117
|
-
}
|
118
|
-
|
119
|
-
#endif
|
120
|
-
|
121
|
-
/* a helper that seeks the EOL, converts it to NUL and returns it's length */
|
122
|
-
inline static uint8_t seek2eol(uint8_t **pos, uint8_t *const limit) {
|
123
|
-
/* single char lookup using memchr might be better when target is far... */
|
124
|
-
if (!seek2ch(pos, limit, '\n'))
|
125
|
-
return 0;
|
126
|
-
if ((*pos)[-1] == '\r') {
|
127
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
128
|
-
(*pos)[-1] = 0;
|
129
|
-
#endif
|
130
|
-
return 2;
|
131
|
-
}
|
132
|
-
return 1;
|
133
|
-
}
|
134
|
-
|
135
|
-
/* *****************************************************************************
|
136
|
-
String to Number
|
137
|
-
***************************************************************************** */
|
138
|
-
|
139
|
-
/** Converts a String to a number using base 10 */
|
140
|
-
static long long http1_atol(const uint8_t *buf, const uint8_t **end) {
|
141
|
-
register unsigned long long i = 0;
|
142
|
-
uint8_t inv = 0;
|
143
|
-
while (*buf == ' ' || *buf == '\t' || *buf == '\f')
|
144
|
-
++buf;
|
145
|
-
while (*buf == '-' || *buf == '+')
|
146
|
-
inv ^= (*(buf++) == '-');
|
147
|
-
while (i <= ((((~0ULL) >> 1) / 10)) && *buf >= '0' && *buf <= '9') {
|
148
|
-
i = i * 10;
|
149
|
-
i += *buf - '0';
|
150
|
-
++buf;
|
151
|
-
}
|
152
|
-
/* test for overflow */
|
153
|
-
if (i >= (~((~0ULL) >> 1)) || (*buf >= '0' && *buf <= '9'))
|
154
|
-
i = (~0ULL >> 1);
|
155
|
-
if (inv)
|
156
|
-
i = 0ULL - i;
|
157
|
-
if (end)
|
158
|
-
*end = buf;
|
159
|
-
return i;
|
160
|
-
}
|
161
|
-
|
162
|
-
/** Converts a String to a number using base 16, overflow limited to 113bytes */
|
163
|
-
static long long http1_atol16(const uint8_t *buf, const uint8_t **end) {
|
164
|
-
register unsigned long long i = 0;
|
165
|
-
uint8_t inv = 0;
|
166
|
-
for (int limit_ = 0;
|
167
|
-
(*buf == ' ' || *buf == '\t' || *buf == '\f') && limit_ < 32; ++limit_)
|
168
|
-
++buf;
|
169
|
-
for (int limit_ = 0; (*buf == '-' || *buf == '+') && limit_ < 32; ++limit_)
|
170
|
-
inv ^= (*(buf++) == '-');
|
171
|
-
if (*buf == '0')
|
172
|
-
++buf;
|
173
|
-
if ((*buf | 32) == 'x')
|
174
|
-
++buf;
|
175
|
-
for (int limit_ = 0; (*buf == '0') && limit_ < 32; ++limit_)
|
176
|
-
++buf;
|
177
|
-
while (!(i & (~((~(0ULL)) >> 4)))) {
|
178
|
-
if (*buf >= '0' && *buf <= '9') {
|
179
|
-
i <<= 4;
|
180
|
-
i |= *buf - '0';
|
181
|
-
} else if ((*buf | 32) >= 'a' && (*buf | 32) <= 'f') {
|
182
|
-
i <<= 4;
|
183
|
-
i |= (*buf | 32) - ('a' - 10);
|
184
|
-
} else
|
185
|
-
break;
|
186
|
-
++buf;
|
187
|
-
}
|
188
|
-
if (inv)
|
189
|
-
i = 0ULL - i;
|
190
|
-
if (end)
|
191
|
-
*end = buf;
|
192
|
-
return i;
|
193
|
-
}
|
194
|
-
|
195
|
-
/* *****************************************************************************
|
196
|
-
HTTP/1.1 parsre stages
|
197
|
-
***************************************************************************** */
|
198
|
-
|
199
|
-
inline static int consume_response_line(struct http1_fio_parser_args_s *args,
|
200
|
-
uint8_t *start, uint8_t *end) {
|
201
|
-
args->parser->state.reserved |= 128;
|
202
|
-
uint8_t *tmp = start;
|
203
|
-
if (!seek2ch(&tmp, end, ' '))
|
204
|
-
return -1;
|
205
|
-
if (args->on_http_version(args->parser, (char *)start, tmp - start))
|
206
|
-
return -1;
|
207
|
-
tmp = start = tmp + 1;
|
208
|
-
if (!seek2ch(&tmp, end, ' '))
|
209
|
-
return -1;
|
210
|
-
if (args->on_status(args->parser, atol((char *)start), (char *)(tmp + 1),
|
211
|
-
end - tmp))
|
212
|
-
return -1;
|
213
|
-
return 0;
|
214
|
-
}
|
215
|
-
|
216
|
-
inline static int consume_request_line(struct http1_fio_parser_args_s *args,
|
217
|
-
uint8_t *start, uint8_t *end) {
|
218
|
-
uint8_t *tmp = start;
|
219
|
-
uint8_t *host_start = NULL;
|
220
|
-
uint8_t *host_end = NULL;
|
221
|
-
if (!seek2ch(&tmp, end, ' '))
|
222
|
-
return -1;
|
223
|
-
if (args->on_method(args->parser, (char *)start, tmp - start))
|
224
|
-
return -1;
|
225
|
-
tmp = start = tmp + 1;
|
226
|
-
if (start[0] == 'h' && start[1] == 't' && start[2] == 't' &&
|
227
|
-
start[3] == 'p') {
|
228
|
-
if (start[4] == ':' && start[5] == '/' && start[6] == '/') {
|
229
|
-
/* Request URI is in long form... emulate Host header instead. */
|
230
|
-
tmp = host_end = host_start = (start += 7);
|
231
|
-
} else if (start[4] == 's' && start[5] == ':' && start[6] == '/' &&
|
232
|
-
start[7] == '/') {
|
233
|
-
/* Secure request is in long form... emulate Host header instead. */
|
234
|
-
tmp = host_end = host_start = (start += 8);
|
235
|
-
} else
|
236
|
-
goto review_path;
|
237
|
-
if (!seek2ch(&tmp, end, ' '))
|
238
|
-
return -1;
|
239
|
-
*tmp = ' ';
|
240
|
-
if (!seek2ch(&host_end, tmp, '/')) {
|
241
|
-
if (args->on_path(args->parser, (char *)"/", 1))
|
242
|
-
return -1;
|
243
|
-
goto start_version;
|
244
|
-
}
|
245
|
-
host_end[0] = '/';
|
246
|
-
start = host_end;
|
247
|
-
}
|
248
|
-
review_path:
|
249
|
-
tmp = start;
|
250
|
-
if (seek2ch(&tmp, end, '?')) {
|
251
|
-
if (args->on_path(args->parser, (char *)start, tmp - start))
|
252
|
-
return -1;
|
253
|
-
tmp = start = tmp + 1;
|
254
|
-
if (!seek2ch(&tmp, end, ' '))
|
255
|
-
return -1;
|
256
|
-
if (tmp - start > 0 &&
|
257
|
-
args->on_query(args->parser, (char *)start, tmp - start))
|
258
|
-
return -1;
|
259
|
-
} else {
|
260
|
-
tmp = start;
|
261
|
-
if (!seek2ch(&tmp, end, ' '))
|
262
|
-
return -1;
|
263
|
-
if (args->on_path(args->parser, (char *)start, tmp - start))
|
264
|
-
return -1;
|
265
|
-
}
|
266
|
-
start_version:
|
267
|
-
start = tmp + 1;
|
268
|
-
if (start + 5 >= end) /* require "HTTP/" */
|
269
|
-
return -1;
|
270
|
-
if (args->on_http_version(args->parser, (char *)start, end - start))
|
271
|
-
return -1;
|
272
|
-
/* */
|
273
|
-
if (host_start && args->on_header(args->parser, (char *)"host", 4,
|
274
|
-
(char *)host_start, host_end - host_start))
|
275
|
-
return -1;
|
276
|
-
return 0;
|
277
|
-
}
|
278
|
-
|
279
|
-
inline static int consume_header(struct http1_fio_parser_args_s *args,
|
280
|
-
uint8_t *start, uint8_t *end) {
|
281
|
-
uint8_t *end_name = start;
|
282
|
-
/* divide header name from data */
|
283
|
-
if (!seek2ch(&end_name, end, ':'))
|
284
|
-
return -1;
|
285
|
-
#if HTTP_HEADERS_LOWERCASE
|
286
|
-
for (uint8_t *t = start; t < end_name; t++) {
|
287
|
-
*t = tolower(*t);
|
288
|
-
}
|
289
|
-
#endif
|
290
|
-
uint8_t *start_value = end_name + 1;
|
291
|
-
if (start_value[0] == ' ') {
|
292
|
-
start_value++;
|
293
|
-
};
|
294
|
-
|
295
|
-
if ((end_name - start) == 14 &&
|
296
|
-
#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED && HTTP_HEADERS_LOWERCASE
|
297
|
-
*((uint64_t *)start) == *((uint64_t *)"content-") &&
|
298
|
-
*((uint64_t *)(start + 6)) == *((uint64_t *)"t-length")
|
299
|
-
#else
|
300
|
-
HEADER_NAME_IS_EQ((char *)start, "content-length", 14)
|
301
|
-
#endif
|
302
|
-
) {
|
303
|
-
/* handle the special `content-length` header */
|
304
|
-
args->parser->state.content_length = http1_atol(start_value, NULL);
|
305
|
-
} else if ((end_name - start) == 17 && (end - start_value) >= 7 &&
|
306
|
-
#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED && HTTP_HEADERS_LOWERCASE
|
307
|
-
*((uint64_t *)start) == *((uint64_t *)"transfer") &&
|
308
|
-
*((uint64_t *)(start + 8)) == *((uint64_t *)"-encodin")
|
309
|
-
#else
|
310
|
-
HEADER_NAME_IS_EQ((char *)start, "transfer-encoding", 17)
|
311
|
-
#endif
|
312
|
-
) {
|
313
|
-
/* handle the special `transfer-encoding: chunked` header? */
|
314
|
-
/* this removes the `chunked` marker and "unchunks" the data */
|
315
|
-
if (
|
316
|
-
#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED
|
317
|
-
(((uint32_t *)(start_value))[0] | 0x20202020) ==
|
318
|
-
((uint32_t *)"chun")[0] &&
|
319
|
-
(((uint32_t *)(start_value + 3))[0] | 0x20202020) ==
|
320
|
-
((uint32_t *)"nked")[0]
|
321
|
-
#else
|
322
|
-
((start_value[0] | 32) == 'c' && (start_value[1] | 32) == 'h' &&
|
323
|
-
(start_value[2] | 32) == 'u' && (start_value[3] | 32) == 'n' &&
|
324
|
-
(start_value[4] | 32) == 'k' && (start_value[5] | 32) == 'e' &&
|
325
|
-
(start_value[6] | 32) == 'd')
|
326
|
-
#endif
|
327
|
-
) {
|
328
|
-
/* simple case,`chunked` at the beginning */
|
329
|
-
args->parser->state.reserved |= 64;
|
330
|
-
start_value += 7;
|
331
|
-
while (start_value < end && (*start_value == ',' || *start_value == ' '))
|
332
|
-
++start_value;
|
333
|
-
if (!(end - start_value))
|
334
|
-
return 0;
|
335
|
-
} else if ((end - start_value) > 7 &&
|
336
|
-
((end[(-7 + 0)] | 32) == 'c' && (end[(-7 + 1)] | 32) == 'h' &&
|
337
|
-
(end[(-7 + 2)] | 32) == 'u' && (end[(-7 + 3)] | 32) == 'n' &&
|
338
|
-
(end[(-7 + 4)] | 32) == 'k' && (end[(-7 + 5)] | 32) == 'e' &&
|
339
|
-
(end[(-7 + 6)] | 32) == 'd')) {
|
340
|
-
/* simple case,`chunked` at the end of list */
|
341
|
-
args->parser->state.reserved |= 64;
|
342
|
-
end -= 7;
|
343
|
-
while (start_value < end && (end[-1] == ',' || end[-1] == ' '))
|
344
|
-
--end;
|
345
|
-
if (!(end - start_value))
|
346
|
-
return 0;
|
347
|
-
} else if ((end - start_value) > 7 && (end - start_value) < 256) {
|
348
|
-
/* complex case, `the, chunked, marker, is in the middle of list */
|
349
|
-
uint8_t val[256];
|
350
|
-
size_t val_len = 0;
|
351
|
-
while (start_value < end) {
|
352
|
-
if (
|
353
|
-
#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED
|
354
|
-
(((uint32_t *)(start_value))[0] | 0x20202020) ==
|
355
|
-
((uint32_t *)"chun")[0] &&
|
356
|
-
(((uint32_t *)(start_value + 3))[0] | 0x20202020) ==
|
357
|
-
((uint32_t *)"nked")[0]
|
358
|
-
#else
|
359
|
-
((start_value[0] | 32) == 'c' && (start_value[1] | 32) == 'h' &&
|
360
|
-
(start_value[2] | 32) == 'u' && (start_value[3] | 32) == 'n' &&
|
361
|
-
(start_value[4] | 32) == 'k' && (start_value[5] | 32) == 'e' &&
|
362
|
-
(start_value[6] | 32) == 'd')
|
363
|
-
#endif
|
364
|
-
|
365
|
-
) {
|
366
|
-
args->parser->state.reserved |= 64;
|
367
|
-
start_value += 7;
|
368
|
-
/* skip comma / white space */
|
369
|
-
while (start_value < end &&
|
370
|
-
(*start_value == ',' || *start_value == ' '))
|
371
|
-
++start_value;
|
372
|
-
break;
|
373
|
-
}
|
374
|
-
val[val_len++] = *start_value;
|
375
|
-
++start_value;
|
376
|
-
}
|
377
|
-
while (start_value < end) {
|
378
|
-
val[val_len++] = *start_value;
|
379
|
-
++start_value;
|
380
|
-
}
|
381
|
-
/* perform callback with `val` */
|
382
|
-
val[val_len] = 0;
|
383
|
-
if (val_len && args->on_header(args->parser, (char *)start,
|
384
|
-
(end_name - start), (char *)val, val_len))
|
385
|
-
return -1;
|
386
|
-
return 0;
|
387
|
-
}
|
388
|
-
} else if ((end_name - start) == 7 &&
|
389
|
-
#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED && HTTP_HEADERS_LOWERCASE
|
390
|
-
*((uint64_t *)start) == *((uint64_t *)"trailer")
|
391
|
-
#else
|
392
|
-
HEADER_NAME_IS_EQ((char *)start, "trailer", 7)
|
393
|
-
#endif
|
394
|
-
) {
|
395
|
-
/* chunked data with trailer... */
|
396
|
-
args->parser->state.reserved |= 64;
|
397
|
-
args->parser->state.reserved |= 32;
|
398
|
-
// return 0; /* hide Trailer header, since we process the headers? */
|
399
|
-
}
|
400
|
-
|
401
|
-
/* perform callback */
|
402
|
-
if (args->on_header(args->parser, (char *)start, (end_name - start),
|
403
|
-
(char *)start_value, end - start_value))
|
404
|
-
return -1;
|
405
|
-
return 0;
|
406
|
-
}
|
407
|
-
|
408
|
-
/* *****************************************************************************
|
409
|
-
HTTP/1.1 Body handling
|
410
|
-
***************************************************************************** */
|
411
|
-
|
412
|
-
inline static int consume_body_streamed(struct http1_fio_parser_args_s *args,
|
413
|
-
uint8_t **start) {
|
414
|
-
uint8_t *end =
|
415
|
-
*start + args->parser->state.content_length - args->parser->state.read;
|
416
|
-
uint8_t *const stop = ((uint8_t *)args->buffer) + args->length;
|
417
|
-
if (end > stop)
|
418
|
-
end = stop;
|
419
|
-
if (end > *start &&
|
420
|
-
args->on_body_chunk(args->parser, (char *)(*start), end - *start))
|
421
|
-
return -1;
|
422
|
-
args->parser->state.read += (end - *start);
|
423
|
-
*start = end;
|
424
|
-
if (args->parser->state.content_length <= args->parser->state.read)
|
425
|
-
args->parser->state.reserved |= 4;
|
426
|
-
return 0;
|
427
|
-
}
|
428
|
-
|
429
|
-
inline static int consume_body_chunked(struct http1_fio_parser_args_s *args,
|
430
|
-
uint8_t **start) {
|
431
|
-
uint8_t *const stop = ((uint8_t *)args->buffer) + args->length;
|
432
|
-
uint8_t *end = *start;
|
433
|
-
while (*start < stop) {
|
434
|
-
if (args->parser->state.content_length == 0) {
|
435
|
-
if (end + 2 >= stop)
|
436
|
-
return 0;
|
437
|
-
if ((end[0] == '\r' && end[1] == '\n')) {
|
438
|
-
/* remove tailing EOL that wasn't processed and retest */
|
439
|
-
end += 2;
|
440
|
-
*start = end;
|
441
|
-
if (end + 2 >= stop)
|
442
|
-
return 0;
|
443
|
-
}
|
444
|
-
long long chunk_len = http1_atol16(end, (const uint8_t **)&end);
|
445
|
-
if (end + 2 > stop) /* overflowed? */
|
446
|
-
return 0;
|
447
|
-
if ((end[0] != '\r' || end[1] != '\n'))
|
448
|
-
return -1; /* required EOL after content length */
|
449
|
-
end += 2;
|
450
|
-
|
451
|
-
args->parser->state.content_length = 0 - chunk_len;
|
452
|
-
*start = end;
|
453
|
-
if (args->parser->state.content_length == 0) {
|
454
|
-
/* all chunked data was parsed - update content-length */
|
455
|
-
args->parser->state.content_length = args->parser->state.read;
|
456
|
-
/* consume trailing EOL */
|
457
|
-
if (*start + 2 <= stop)
|
458
|
-
*start += 2;
|
459
|
-
#ifdef HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
460
|
-
{ /* add virtual header ... ? */
|
461
|
-
char buf[512];
|
462
|
-
size_t buf_len = 512;
|
463
|
-
size_t tmp_len = args->parser->state.read;
|
464
|
-
buf[--buf_len] = 0;
|
465
|
-
while (tmp_len) {
|
466
|
-
size_t mod = tmp_len / 10;
|
467
|
-
buf[--buf_len] = '0' + (tmp_len - (mod * 10));
|
468
|
-
tmp_len = mod;
|
469
|
-
}
|
470
|
-
if (args->on_header(args->parser, "content-length", 14,
|
471
|
-
(char *)buf + buf_len, 511 - buf_len))
|
472
|
-
return -1;
|
473
|
-
}
|
474
|
-
#endif
|
475
|
-
if (args->parser->state.reserved & 32) {
|
476
|
-
/* remove the "headers complete" and "trailer" flags */
|
477
|
-
args->parser->state.reserved &= 0xDD; /* 0xDD == ~2 & ~32 & 0xFF */
|
478
|
-
return -2;
|
479
|
-
}
|
480
|
-
/* the parsing complete flag */
|
481
|
-
args->parser->state.reserved |= 4;
|
482
|
-
return 0;
|
483
|
-
}
|
484
|
-
}
|
485
|
-
end = *start + (0 - args->parser->state.content_length);
|
486
|
-
if (end > stop)
|
487
|
-
end = stop;
|
488
|
-
if (end > *start &&
|
489
|
-
args->on_body_chunk(args->parser, (char *)(*start), end - *start)) {
|
490
|
-
return -1;
|
491
|
-
}
|
492
|
-
args->parser->state.read += (end - *start);
|
493
|
-
args->parser->state.content_length += (end - *start);
|
494
|
-
*start = end;
|
495
|
-
}
|
496
|
-
return 0;
|
497
|
-
}
|
498
|
-
|
499
|
-
inline static int consume_body(struct http1_fio_parser_args_s *args,
|
500
|
-
uint8_t **start) {
|
501
|
-
if (args->parser->state.content_length > 0 &&
|
502
|
-
args->parser->state.content_length > args->parser->state.read) {
|
503
|
-
/* normal, streamed data */
|
504
|
-
return consume_body_streamed(args, start);
|
505
|
-
} else if (args->parser->state.content_length <= 0 &&
|
506
|
-
(args->parser->state.reserved & 64)) {
|
507
|
-
/* chuncked encoding */
|
508
|
-
return consume_body_chunked(args, start);
|
509
|
-
} else {
|
510
|
-
/* nothing to do - parsing complete */
|
511
|
-
args->parser->state.reserved |= 4;
|
512
|
-
}
|
513
|
-
return 0;
|
514
|
-
}
|
515
|
-
|
516
|
-
/* *****************************************************************************
|
517
|
-
HTTP/1.1 parsre function
|
518
|
-
***************************************************************************** */
|
519
|
-
#if DEBUG
|
520
|
-
#include <assert.h>
|
521
|
-
#else
|
522
|
-
#define DEBUG 0
|
523
|
-
#define assert(...)
|
524
|
-
#endif
|
525
|
-
|
526
|
-
size_t http1_fio_parser_fn(struct http1_fio_parser_args_s *args) {
|
527
|
-
assert(args->parser && args->buffer);
|
528
|
-
args->parser->state.next = NULL;
|
529
|
-
uint8_t *start = args->buffer;
|
530
|
-
uint8_t *end = start;
|
531
|
-
uint8_t *const stop = start + args->length;
|
532
|
-
uint8_t eol_len = 0;
|
533
|
-
#define CONSUMED ((size_t)((uintptr_t)start - (uintptr_t)args->buffer))
|
534
|
-
// fprintf(stderr, "** resuming with at %p with %.*s...(%lu)\n", args->buffer,
|
535
|
-
// 4,
|
536
|
-
// start, args->length);
|
537
|
-
re_eval:
|
538
|
-
switch ((args->parser->state.reserved & 15)) {
|
539
|
-
|
540
|
-
/* request / response line */
|
541
|
-
case 0:
|
542
|
-
/* clear out any leadinng white space */
|
543
|
-
while ((start < stop) &&
|
544
|
-
(*start == '\r' || *start == '\n' || *start == ' ' || *start == 0)) {
|
545
|
-
++start;
|
546
|
-
}
|
547
|
-
end = start;
|
548
|
-
/* make sure the whole line is available*/
|
549
|
-
if (!(eol_len = seek2eol(&end, stop)))
|
550
|
-
return CONSUMED;
|
551
|
-
|
552
|
-
if (start[0] == 'H' && start[1] == 'T' && start[2] == 'T' &&
|
553
|
-
start[3] == 'P') {
|
554
|
-
/* HTTP response */
|
555
|
-
if (consume_response_line(args, start, end - eol_len + 1))
|
556
|
-
goto error;
|
557
|
-
} else if (tolower(start[0]) >= 'a' && tolower(start[0]) <= 'z') {
|
558
|
-
/* HTTP request */
|
559
|
-
if (consume_request_line(args, start, end - eol_len + 1))
|
560
|
-
goto error;
|
561
|
-
}
|
562
|
-
end = start = end + 1;
|
563
|
-
args->parser->state.reserved |= 1;
|
564
|
-
|
565
|
-
/* fallthrough */
|
566
|
-
/* headers */
|
567
|
-
case 1:
|
568
|
-
do {
|
569
|
-
if (start >= stop)
|
570
|
-
return CONSUMED; /* buffer ended on header line */
|
571
|
-
if (*start == '\r' || *start == '\n') {
|
572
|
-
goto finished_headers; /* empty line, end of headers */
|
573
|
-
}
|
574
|
-
if (!(eol_len = seek2eol(&end, stop)))
|
575
|
-
return CONSUMED;
|
576
|
-
if (consume_header(args, start, end - eol_len + 1))
|
577
|
-
goto error;
|
578
|
-
end = start = end + 1;
|
579
|
-
} while ((args->parser->state.reserved & 2) == 0);
|
580
|
-
finished_headers:
|
581
|
-
++start;
|
582
|
-
if (*start == '\n')
|
583
|
-
++start;
|
584
|
-
end = start;
|
585
|
-
args->parser->state.reserved |= 2;
|
586
|
-
/* fallthrough */
|
587
|
-
/* request body */
|
588
|
-
case 3: { /* 2 | 1 == 3 */
|
589
|
-
int t3 = consume_body(args, &start);
|
590
|
-
switch (t3) {
|
591
|
-
case -1:
|
592
|
-
goto error;
|
593
|
-
case -2:
|
594
|
-
goto re_eval;
|
595
|
-
}
|
596
|
-
break;
|
597
|
-
}
|
598
|
-
}
|
599
|
-
/* are we done ? */
|
600
|
-
if (args->parser->state.reserved & 4) {
|
601
|
-
args->parser->state.next = start;
|
602
|
-
if (((args->parser->state.reserved & 128) ? args->on_response
|
603
|
-
: args->on_request)(args->parser))
|
604
|
-
goto error;
|
605
|
-
args->parser->state =
|
606
|
-
(struct http1_parser_protected_read_only_state_s){0, 0, 0};
|
607
|
-
}
|
608
|
-
return CONSUMED;
|
609
|
-
error:
|
610
|
-
args->on_error(args->parser);
|
611
|
-
args->parser->state =
|
612
|
-
(struct http1_parser_protected_read_only_state_s){0, 0, 0};
|
613
|
-
return args->length;
|
614
|
-
}
|
615
|
-
|
616
|
-
#undef CONSUMED
|