iodine 0.4.7 → 0.4.8
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +1 -1
- data/ext/iodine/http.c +34 -4
- data/ext/iodine/http.h +8 -0
- data/ext/iodine/http1.c +269 -160
- data/ext/iodine/http1_parser.c +276 -0
- data/ext/iodine/http1_parser.h +111 -0
- data/ext/iodine/http1_response.c +8 -8
- data/ext/iodine/http_response.c +1 -16
- data/lib/iodine/version.rb +1 -1
- metadata +4 -4
- data/ext/iodine/http1_simple_parser.c +0 -496
- data/ext/iodine/http1_simple_parser.h +0 -68
@@ -0,0 +1,276 @@
|
|
1
|
+
/*
|
2
|
+
Copyright: Boaz segev, 2017
|
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
|
+
#include <ctype.h>
|
13
|
+
#include <stdio.h>
|
14
|
+
#include <string.h>
|
15
|
+
|
16
|
+
#define PREFER_MEMCHAR 0
|
17
|
+
|
18
|
+
/* *****************************************************************************
|
19
|
+
Seeking for characters in a string
|
20
|
+
***************************************************************************** */
|
21
|
+
|
22
|
+
#if PREFER_MEMCHAR
|
23
|
+
|
24
|
+
/* a helper that seeks any char, converts it to NUL and returns 1 if found. */
|
25
|
+
inline static uint8_t seek2ch(uint8_t **pos, uint8_t *const limit, uint8_t ch) {
|
26
|
+
/* This is library based alternative that is sometimes slower */
|
27
|
+
if (*pos >= limit || **pos == ch) {
|
28
|
+
return 0;
|
29
|
+
}
|
30
|
+
uint8_t *tmp = memchr(*pos, ch, limit - (*pos));
|
31
|
+
if (tmp) {
|
32
|
+
*pos = tmp;
|
33
|
+
*tmp = 0;
|
34
|
+
return 1;
|
35
|
+
}
|
36
|
+
*pos = limit;
|
37
|
+
return 0;
|
38
|
+
}
|
39
|
+
|
40
|
+
#else
|
41
|
+
|
42
|
+
/* a helper that seeks any char, converts it to NUL and returns 1 if found. */
|
43
|
+
static inline uint8_t seek2ch(uint8_t **buffer, const uint8_t *const limit,
|
44
|
+
const uint8_t c) {
|
45
|
+
/* this single char lookup is better when target is closer... */
|
46
|
+
if (**buffer == c) {
|
47
|
+
**buffer = 0;
|
48
|
+
return 1;
|
49
|
+
}
|
50
|
+
|
51
|
+
uint64_t wanted = 0x0101010101010101ULL * c;
|
52
|
+
uint64_t *lpos = (uint64_t *)*buffer;
|
53
|
+
uint64_t *llimit = ((uint64_t *)limit) - 1;
|
54
|
+
|
55
|
+
for (; lpos < llimit; lpos++) {
|
56
|
+
const uint64_t eq = ~((*lpos) ^ wanted);
|
57
|
+
const uint64_t t0 = (eq & 0x7f7f7f7f7f7f7f7fllu) + 0x0101010101010101llu;
|
58
|
+
const uint64_t t1 = (eq & 0x8080808080808080llu);
|
59
|
+
if ((t0 & t1)) {
|
60
|
+
break;
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
*buffer = (uint8_t *)lpos;
|
65
|
+
while (*buffer < limit) {
|
66
|
+
if (**buffer == c) {
|
67
|
+
**buffer = 0;
|
68
|
+
return 1;
|
69
|
+
}
|
70
|
+
(*buffer)++;
|
71
|
+
}
|
72
|
+
return 0;
|
73
|
+
}
|
74
|
+
|
75
|
+
#endif
|
76
|
+
|
77
|
+
/* a helper that seeks the EOL, converts it to NUL and returns it's length */
|
78
|
+
inline static uint8_t seek2eol(uint8_t **pos, uint8_t *const limit) {
|
79
|
+
/* single char lookup using memchr might be better when target is far... */
|
80
|
+
if (!seek2ch(pos, limit, '\n'))
|
81
|
+
return 0;
|
82
|
+
if ((*pos)[-1] == '\r') {
|
83
|
+
(*pos)[-1] = 0;
|
84
|
+
return 2;
|
85
|
+
}
|
86
|
+
return 1;
|
87
|
+
}
|
88
|
+
|
89
|
+
/* *****************************************************************************
|
90
|
+
HTTP/1.1 parsre stages
|
91
|
+
***************************************************************************** */
|
92
|
+
|
93
|
+
inline static int consume_response_line(struct http1_fio_parser_args_s *args,
|
94
|
+
uint8_t *start, uint8_t *end) {
|
95
|
+
args->parser->state.reserved |= 128;
|
96
|
+
uint8_t *tmp = start;
|
97
|
+
if (!seek2ch(&tmp, end, ' '))
|
98
|
+
return -1;
|
99
|
+
if (args->on_http_version(args->parser, (char *)start, tmp - start))
|
100
|
+
return -1;
|
101
|
+
tmp = start = tmp + 1;
|
102
|
+
if (!seek2ch(&tmp, end, ' '))
|
103
|
+
return -1;
|
104
|
+
if (args->on_status(args->parser, atol((char *)start), (char *)(tmp + 1),
|
105
|
+
end - tmp))
|
106
|
+
return -1;
|
107
|
+
return 0;
|
108
|
+
}
|
109
|
+
|
110
|
+
inline static int consume_request_line(struct http1_fio_parser_args_s *args,
|
111
|
+
uint8_t *start, uint8_t *end) {
|
112
|
+
uint8_t *tmp = start;
|
113
|
+
if (!seek2ch(&tmp, end, ' '))
|
114
|
+
return -1;
|
115
|
+
if (args->on_method(args->parser, (char *)start, tmp - start))
|
116
|
+
return -1;
|
117
|
+
tmp = start = tmp + 1;
|
118
|
+
if (seek2ch(&tmp, end, '?')) {
|
119
|
+
if (args->on_path(args->parser, (char *)start, tmp - start))
|
120
|
+
return -1;
|
121
|
+
tmp = start = tmp + 1;
|
122
|
+
if (!seek2ch(&tmp, end, ' '))
|
123
|
+
return -1;
|
124
|
+
if (tmp - start > 0 &&
|
125
|
+
args->on_query(args->parser, (char *)start, tmp - start))
|
126
|
+
return -1;
|
127
|
+
} else {
|
128
|
+
tmp = start;
|
129
|
+
if (!seek2ch(&tmp, end, ' '))
|
130
|
+
return -1;
|
131
|
+
if (args->on_path(args->parser, (char *)start, tmp - start))
|
132
|
+
return -1;
|
133
|
+
}
|
134
|
+
start = tmp + 1;
|
135
|
+
if (start + 7 >= end)
|
136
|
+
return -1;
|
137
|
+
if (args->on_http_version(args->parser, (char *)start, end - start))
|
138
|
+
return -1;
|
139
|
+
return 0;
|
140
|
+
}
|
141
|
+
|
142
|
+
inline static int consume_header(struct http1_fio_parser_args_s *args,
|
143
|
+
uint8_t *start, uint8_t *end) {
|
144
|
+
uint8_t t2 = 1;
|
145
|
+
uint8_t *tmp = start;
|
146
|
+
/* divide header name from data */
|
147
|
+
if (!seek2ch(&tmp, end, ':'))
|
148
|
+
return -1;
|
149
|
+
#if HTTP_HEADERS_LOWERCASE
|
150
|
+
for (uint8_t *t3 = start; t3 < tmp; t3++) {
|
151
|
+
*t3 = tolower(*t3);
|
152
|
+
}
|
153
|
+
#endif
|
154
|
+
|
155
|
+
tmp++;
|
156
|
+
if (tmp[0] == ' ') {
|
157
|
+
tmp++;
|
158
|
+
t2++;
|
159
|
+
};
|
160
|
+
#if HTTP_HEADERS_LOWERCASE
|
161
|
+
if ((tmp - start) - t2 == 14 &&
|
162
|
+
*((uint64_t *)start) == *((uint64_t *)"content-") &&
|
163
|
+
*((uint64_t *)(start + 6)) == *((uint64_t *)"t-length")) {
|
164
|
+
/* handle the special `content-length` header */
|
165
|
+
args->parser->state.content_length = atol((char *)tmp);
|
166
|
+
}
|
167
|
+
#else
|
168
|
+
if ((tmp - start) - t2 == 14 &&
|
169
|
+
HEADER_NAME_IS_EQ((char *)start, "content-length", 14)) {
|
170
|
+
/* handle the special `content-length` header */
|
171
|
+
args->parser->state.content_length = atol((char *)tmp);
|
172
|
+
}
|
173
|
+
#endif
|
174
|
+
/* perform callback */
|
175
|
+
if (args->on_header(args->parser, (char *)start, (tmp - start) - t2,
|
176
|
+
(char *)tmp, end - tmp))
|
177
|
+
return -1;
|
178
|
+
return 0;
|
179
|
+
}
|
180
|
+
|
181
|
+
/* *****************************************************************************
|
182
|
+
HTTP/1.1 parsre function
|
183
|
+
***************************************************************************** */
|
184
|
+
|
185
|
+
size_t http1_fio_parser_fn(struct http1_fio_parser_args_s *args) {
|
186
|
+
uint8_t *start = args->buffer;
|
187
|
+
uint8_t *end = start;
|
188
|
+
uint8_t *const stop = start + args->length;
|
189
|
+
uint8_t eol_len = 0;
|
190
|
+
#define CONSUMED ((size_t)((uintptr_t)start - (uintptr_t)args->buffer))
|
191
|
+
// fprintf(stderr, "** resuming with at %p with %.*s...(%lu)\n", args->buffer,
|
192
|
+
// 4,
|
193
|
+
// start, args->length);
|
194
|
+
switch ((args->parser->state.reserved & 31)) {
|
195
|
+
|
196
|
+
/* request / response line */
|
197
|
+
case 0:
|
198
|
+
/* clear out any leadinng white space */
|
199
|
+
while (*start == '\r' || *start == '\r' || *start == ' ' || *start == 0) {
|
200
|
+
start++;
|
201
|
+
}
|
202
|
+
end = start;
|
203
|
+
/* make sure the whole line is available*/
|
204
|
+
if (!(eol_len = seek2eol(&end, stop)))
|
205
|
+
return CONSUMED;
|
206
|
+
|
207
|
+
if (start[0] >= '1' && start[0] <= '9') {
|
208
|
+
/* HTTP response */
|
209
|
+
if (consume_response_line(args, start, end - eol_len + 1))
|
210
|
+
goto error;
|
211
|
+
} else {
|
212
|
+
/* HTTP request */
|
213
|
+
if (consume_request_line(args, start, end - eol_len + 1))
|
214
|
+
goto error;
|
215
|
+
}
|
216
|
+
end = start = end + 1;
|
217
|
+
args->parser->state.reserved |= 1;
|
218
|
+
|
219
|
+
/* fallthrough */
|
220
|
+
/* headers */
|
221
|
+
case 1:
|
222
|
+
do {
|
223
|
+
if (!(eol_len = seek2eol(&end, stop)))
|
224
|
+
return CONSUMED;
|
225
|
+
/* test for header ending */
|
226
|
+
if (*start == 0)
|
227
|
+
goto finished_headers; /* break the do..while loop, not the switch
|
228
|
+
statement */
|
229
|
+
if (consume_header(args, start, end - eol_len + 1))
|
230
|
+
goto error;
|
231
|
+
end = start = end + 1;
|
232
|
+
} while ((args->parser->state.reserved & 2) == 0);
|
233
|
+
finished_headers:
|
234
|
+
end = start = end + 1;
|
235
|
+
args->parser->state.reserved |= 2;
|
236
|
+
if (args->parser->state.content_length == 0)
|
237
|
+
goto finish;
|
238
|
+
if (end >= stop)
|
239
|
+
return args->length;
|
240
|
+
|
241
|
+
/* fallthrough */
|
242
|
+
/* request body */
|
243
|
+
case 3: /* 2 | 1 == 3 */
|
244
|
+
end = start + args->parser->state.content_length - args->parser->state.read;
|
245
|
+
if (end > stop)
|
246
|
+
end = stop;
|
247
|
+
if (end == start)
|
248
|
+
return CONSUMED;
|
249
|
+
// fprintf(stderr, "Consuming body at (%lu/%lu):%.*s\n", end - start,
|
250
|
+
// args->parser->state.content_length, (int)(end - start), start);
|
251
|
+
if (args->on_body_chunk(args->parser, (char *)start, end - start))
|
252
|
+
goto error;
|
253
|
+
args->parser->state.read += (end - start);
|
254
|
+
start = end;
|
255
|
+
if (args->parser->state.content_length <= args->parser->state.read)
|
256
|
+
goto finish;
|
257
|
+
return CONSUMED;
|
258
|
+
break;
|
259
|
+
}
|
260
|
+
|
261
|
+
error:
|
262
|
+
args->on_error(args->parser);
|
263
|
+
args->parser->state =
|
264
|
+
(struct http1_parser_protected_read_only_state_s){0, 0, 0};
|
265
|
+
return args->length;
|
266
|
+
|
267
|
+
finish:
|
268
|
+
if (((args->parser->state.reserved & 128) ? args->on_response
|
269
|
+
: args->on_request)(args->parser))
|
270
|
+
goto error;
|
271
|
+
args->parser->state =
|
272
|
+
(struct http1_parser_protected_read_only_state_s){0, 0, 0};
|
273
|
+
return CONSUMED;
|
274
|
+
}
|
275
|
+
|
276
|
+
#undef CONSUMED
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#ifndef H_HTTP1_PARSER_H
|
2
|
+
/*
|
3
|
+
Copyright: Boaz segev, 2017
|
4
|
+
License: MIT
|
5
|
+
|
6
|
+
Feel free to copy, use and enjoy according to the license provided.
|
7
|
+
*/
|
8
|
+
|
9
|
+
/**
|
10
|
+
This is a callback based parser. It parses the skeleton of the HTTP/1.x protocol
|
11
|
+
and leaves most of the work (validation, error checks, etc') to the callbacks.
|
12
|
+
|
13
|
+
This is an attempt to replace the existing HTTP/1.x parser with something easier
|
14
|
+
to maintain and that could be used for an HTTP/1.x client as well.
|
15
|
+
*/
|
16
|
+
#define H_HTTP1_PARSER_H
|
17
|
+
#include <stdint.h>
|
18
|
+
#include <stdlib.h>
|
19
|
+
|
20
|
+
#ifndef HTTP_HEADERS_LOWERCASE
|
21
|
+
/** when defined, HTTP headers will be converted to lowercase and header
|
22
|
+
* searches will be case sensitive. */
|
23
|
+
#define HTTP_HEADERS_LOWERCASE 1
|
24
|
+
#endif
|
25
|
+
|
26
|
+
#if HTTP_HEADERS_LOWERCASE
|
27
|
+
|
28
|
+
#define HEADER_NAME_IS_EQ(var_name, const_name, len) \
|
29
|
+
(!memcmp((var_name), (const_name), (len)))
|
30
|
+
#else
|
31
|
+
#define HEADER_NAME_IS_EQ(var_name, const_name, len) \
|
32
|
+
(!strncasecmp((var_name), (const_name), (len)))
|
33
|
+
#endif
|
34
|
+
|
35
|
+
/** this struct contains the state of the parser. */
|
36
|
+
typedef struct http1_parser_s {
|
37
|
+
void *udata;
|
38
|
+
struct http1_parser_protected_read_only_state_s {
|
39
|
+
size_t content_length;
|
40
|
+
size_t read;
|
41
|
+
uint8_t reserved;
|
42
|
+
} state;
|
43
|
+
} http1_parser_s;
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Available options for the parsing function.
|
47
|
+
*
|
48
|
+
* Callbacks should return 0 unless an error occured.
|
49
|
+
*/
|
50
|
+
struct http1_fio_parser_args_s {
|
51
|
+
/** REQUIRED: the parser object that manages the parser's state. */
|
52
|
+
http1_parser_s *parser;
|
53
|
+
/** REQUIRED: the data to be parsed. */
|
54
|
+
void *buffer;
|
55
|
+
/** REQUIRED: the length of the data to be parsed. */
|
56
|
+
size_t length;
|
57
|
+
/** called when a request was received. */
|
58
|
+
int (*const on_request)(http1_parser_s *parser);
|
59
|
+
/** called when a response was received. */
|
60
|
+
int (*const on_response)(http1_parser_s *parser);
|
61
|
+
/** called when a request method is parsed. */
|
62
|
+
int (*const on_method)(http1_parser_s *parser, char *method,
|
63
|
+
size_t method_len);
|
64
|
+
/** called when a response status is parsed. the status_str is the string
|
65
|
+
* without the prefixed numerical status indicator.*/
|
66
|
+
int (*const on_status)(http1_parser_s *parser, size_t status,
|
67
|
+
char *status_str, size_t len);
|
68
|
+
/** called when a request path (excluding query) is parsed. */
|
69
|
+
int (*const on_path)(http1_parser_s *parser, char *path, size_t path_len);
|
70
|
+
/** called when a request path (excluding query) is parsed. */
|
71
|
+
int (*const on_query)(http1_parser_s *parser, char *query, size_t query_len);
|
72
|
+
/** called when a the HTTP/1.x version is parsed. */
|
73
|
+
int (*const on_http_version)(http1_parser_s *parser, char *version,
|
74
|
+
size_t len);
|
75
|
+
/** called when a header is parsed. */
|
76
|
+
int (*const on_header)(http1_parser_s *parser, char *name, size_t name_len,
|
77
|
+
char *data, size_t data_len);
|
78
|
+
/** called when a body chunk is parsed. */
|
79
|
+
int (*const on_body_chunk)(http1_parser_s *parser, char *data,
|
80
|
+
size_t data_len);
|
81
|
+
/** called when a protocol error occured. */
|
82
|
+
int (*const on_error)(http1_parser_s *parser);
|
83
|
+
};
|
84
|
+
|
85
|
+
/**
|
86
|
+
* Returns the amount of data actually consumed by the parser.
|
87
|
+
*
|
88
|
+
* The value 0 indicates there wasn't enough data to be parsed and the same
|
89
|
+
* buffer (with more data) should be resubmitted.
|
90
|
+
*
|
91
|
+
* A value smaller than the buffer size indicates that EITHER a request /
|
92
|
+
* response was detected OR that the leftover could not be consumed because more
|
93
|
+
* data was required.
|
94
|
+
*
|
95
|
+
* Simply resubmit the reminder of the data to continue parsing.
|
96
|
+
*
|
97
|
+
* A request / response callback automatically stops the parsing process,
|
98
|
+
* allowing the user to adjust or refresh the state of the data.
|
99
|
+
*/
|
100
|
+
size_t http1_fio_parser_fn(struct http1_fio_parser_args_s *args);
|
101
|
+
|
102
|
+
static inline __attribute__((unused)) size_t
|
103
|
+
http1_fio_parser(struct http1_fio_parser_args_s args) {
|
104
|
+
return http1_fio_parser_fn(&args);
|
105
|
+
}
|
106
|
+
#if __STDC_VERSION__ >= 199901L
|
107
|
+
#define http1_fio_parser(...) \
|
108
|
+
http1_fio_parser((struct http1_fio_parser_args_s){__VA_ARGS__})
|
109
|
+
#endif
|
110
|
+
|
111
|
+
#endif
|
data/ext/iodine/http1_response.c
CHANGED
@@ -85,11 +85,11 @@ initialize:
|
|
85
85
|
return (http_response_s *)http1_response_pool.pool_mem;
|
86
86
|
}
|
87
87
|
|
88
|
-
static void
|
88
|
+
static void http1_response_deferred_destroy(void *rs_, void *ignr) {
|
89
89
|
(void)(ignr);
|
90
90
|
http1_response_s *rs = rs_;
|
91
91
|
if (spn_trylock(&rs->lock)) {
|
92
|
-
defer(
|
92
|
+
defer(http1_response_deferred_destroy, rs, NULL);
|
93
93
|
return;
|
94
94
|
}
|
95
95
|
rs->use_count -= 1;
|
@@ -118,7 +118,7 @@ use_free:
|
|
118
118
|
/** Destroys the response object. No data is sent.*/
|
119
119
|
void http1_response_destroy(http_response_s *rs) {
|
120
120
|
((http1_response_s *)rs)->dest_count++;
|
121
|
-
|
121
|
+
http1_response_deferred_destroy(rs, NULL);
|
122
122
|
}
|
123
123
|
|
124
124
|
/* *****************************************************************************
|
@@ -162,15 +162,15 @@ static void http1_response_finalize_headers(http1_response_s *rs) {
|
|
162
162
|
rs->response.date = rs->response.last_modified;
|
163
163
|
struct tm t;
|
164
164
|
/* date header */
|
165
|
-
http_gmtime(&rs->response.date, &t);
|
166
165
|
h1p_protected_copy(rs, "Date: ", 6);
|
167
|
-
rs->buffer_end +=
|
166
|
+
rs->buffer_end +=
|
167
|
+
http_time2str(rs->buffer + rs->buffer_end, rs->response.date);
|
168
168
|
rs->buffer[rs->buffer_end++] = '\r';
|
169
169
|
rs->buffer[rs->buffer_end++] = '\n';
|
170
170
|
/* last-modified header */
|
171
|
-
http_gmtime(&rs->response.last_modified, &t);
|
172
171
|
h1p_protected_copy(rs, "Last-Modified: ", 15);
|
173
|
-
rs->buffer_end +=
|
172
|
+
rs->buffer_end +=
|
173
|
+
http_time2str(rs->buffer + rs->buffer_end, rs->response.last_modified);
|
174
174
|
rs->buffer[rs->buffer_end++] = '\r';
|
175
175
|
rs->buffer[rs->buffer_end++] = '\n';
|
176
176
|
}
|
@@ -229,7 +229,7 @@ void http1_response_finish(http_response_s *rs) {
|
|
229
229
|
http1_response_send_headers((http1_response_s *)rs);
|
230
230
|
if (rs->should_close)
|
231
231
|
sock_close(rs->fd);
|
232
|
-
|
232
|
+
http1_response_deferred_destroy(rs, NULL);
|
233
233
|
}
|
234
234
|
|
235
235
|
/* *****************************************************************************
|
data/ext/iodine/http_response.c
CHANGED
@@ -485,20 +485,6 @@ static void http_response_log_finish(http_response_s *response) {
|
|
485
485
|
? ((get_clock_mili() - response->clock_start) / CLOCK_RESOLUTION)
|
486
486
|
: 0;
|
487
487
|
|
488
|
-
/* pre-print log message every 1 or 2 seconds or so. */
|
489
|
-
static __thread time_t cached_tick;
|
490
|
-
static __thread char cached_httpdate[48];
|
491
|
-
static __thread size_t chached_len;
|
492
|
-
time_t last_tick = facil_last_tick();
|
493
|
-
if (last_tick > cached_tick) {
|
494
|
-
struct tm tm;
|
495
|
-
cached_tick = last_tick | 1;
|
496
|
-
http_gmtime(&last_tick, &tm);
|
497
|
-
chached_len = http_date2str(cached_httpdate, &tm);
|
498
|
-
fprintf(stderr, "Updated date cache to (%lu) %s\n", chached_len,
|
499
|
-
cached_httpdate);
|
500
|
-
}
|
501
|
-
|
502
488
|
// TODO Guess IP address from headers (forwarded) where possible
|
503
489
|
sock_peer_addr_s addrinfo = sock_peer_addr(response->fd);
|
504
490
|
|
@@ -519,8 +505,7 @@ static void http_response_log_finish(http_response_s *response) {
|
|
519
505
|
}
|
520
506
|
memcpy(buffer + pos, " - - [", 6);
|
521
507
|
pos += 6;
|
522
|
-
|
523
|
-
pos += chached_len;
|
508
|
+
pos += http_time2str(buffer + pos, facil_last_tick());
|
524
509
|
buffer[pos++] = ']';
|
525
510
|
buffer[pos++] = ' ';
|
526
511
|
buffer[pos++] = '"';
|