iodine 0.7.41 → 0.7.45
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/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG.md +24 -0
- data/README.md +2 -2
- data/SPEC-PubSub-Draft.md +89 -47
- data/SPEC-WebSocket-Draft.md +92 -55
- data/examples/async_task.ru +92 -0
- data/ext/iodine/extconf.rb +21 -16
- data/ext/iodine/fio.c +1108 -162
- data/ext/iodine/fio.h +49 -13
- data/ext/iodine/fio_cli.c +1 -1
- data/ext/iodine/fio_tls_missing.c +8 -0
- data/ext/iodine/fio_tls_openssl.c +8 -0
- data/ext/iodine/fio_tmpfile.h +13 -1
- data/ext/iodine/fiobj_data.c +6 -4
- data/ext/iodine/fiobj_data.h +2 -1
- data/ext/iodine/fiobj_hash.c +32 -6
- data/ext/iodine/fiobj_mustache.c +9 -0
- data/ext/iodine/fiobj_numbers.c +86 -8
- data/ext/iodine/fiobj_str.c +24 -11
- data/ext/iodine/fiobject.c +1 -1
- data/ext/iodine/fiobject.h +5 -3
- data/ext/iodine/http.c +66 -10
- data/ext/iodine/http1.c +2 -1
- data/ext/iodine/http1_parser.h +1065 -103
- data/ext/iodine/http_internal.c +1 -0
- data/ext/iodine/http_internal.h +4 -2
- data/ext/iodine/iodine.c +66 -1
- data/ext/iodine/iodine.h +3 -0
- data/ext/iodine/iodine_caller.c +48 -8
- data/ext/iodine/iodine_connection.c +24 -8
- data/ext/iodine/iodine_http.c +32 -8
- data/ext/iodine/iodine_mustache.c +2 -4
- data/ext/iodine/iodine_rack_io.c +21 -0
- data/ext/iodine/iodine_tcp.c +14 -0
- data/ext/iodine/iodine_tls.c +8 -0
- data/ext/iodine/mustache_parser.h +4 -0
- data/ext/iodine/redis_engine.c +14 -11
- data/ext/iodine/websockets.c +7 -3
- data/iodine.gemspec +5 -4
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +6 -0
- metadata +15 -13
data/ext/iodine/http1_parser.h
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
#ifndef H_HTTP1_PARSER_H
|
2
1
|
/*
|
3
2
|
Copyright: Boaz Segev, 2017-2020
|
4
3
|
License: MIT
|
@@ -9,8 +8,10 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
9
8
|
/**
|
10
9
|
This is a callback based parser. It parses the skeleton of the HTTP/1.x protocol
|
11
10
|
and leaves most of the work (validation, error checks, etc') to the callbacks.
|
11
|
+
|
12
|
+
This is an attempt to replace the existing HTTP/1.x parser with something easier
|
13
|
+
to maintain and that could be used for an HTTP/1.x client as well.
|
12
14
|
*/
|
13
|
-
#define H_HTTP1_PARSER_H
|
14
15
|
#include <stddef.h>
|
15
16
|
#include <stdint.h>
|
16
17
|
#include <stdio.h>
|
@@ -38,19 +39,19 @@ Parser Settings
|
|
38
39
|
#endif
|
39
40
|
|
40
41
|
#ifndef FIO_MEMCHAR
|
41
|
-
/** Prefer a custom memchr implementation.
|
42
|
+
/** Prefer a custom memchr implementation. Usually memchr is better. */
|
42
43
|
#define FIO_MEMCHAR 0
|
43
44
|
#endif
|
44
45
|
|
45
|
-
#ifndef
|
46
|
-
/**
|
47
|
-
#define
|
46
|
+
#ifndef HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED
|
47
|
+
/** Preforms some optimizations assuming unaligned memory access is okay. */
|
48
|
+
#define HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED 0
|
48
49
|
#endif
|
49
50
|
|
50
|
-
#ifndef
|
51
|
-
|
51
|
+
#ifndef HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER
|
52
|
+
/** The RFC doesn't allow this, but this parser can manage... probably... */
|
53
|
+
#define HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER 0
|
52
54
|
#endif
|
53
|
-
|
54
55
|
/* *****************************************************************************
|
55
56
|
Parser API
|
56
57
|
***************************************************************************** */
|
@@ -87,37 +88,40 @@ typedef struct http1_parser_s {
|
|
87
88
|
*/
|
88
89
|
static size_t http1_parse(http1_parser_s *parser, void *buffer, size_t length);
|
89
90
|
|
91
|
+
/** Returns true if the parsing stopped after a complete request / response. */
|
92
|
+
inline static int http1_complete(http1_parser_s *parser);
|
93
|
+
|
90
94
|
/* *****************************************************************************
|
91
95
|
Required Callbacks (MUST be implemented by including file)
|
92
96
|
***************************************************************************** */
|
97
|
+
// clang-format off
|
93
98
|
|
94
99
|
/** called when a request was received. */
|
95
100
|
static int http1_on_request(http1_parser_s *parser);
|
96
101
|
/** called when a response was received. */
|
97
102
|
static int http1_on_response(http1_parser_s *parser);
|
98
103
|
/** called when a request method is parsed. */
|
99
|
-
static int
|
100
|
-
|
104
|
+
static int
|
105
|
+
http1_on_method(http1_parser_s *parser, char *method, size_t method_len);
|
101
106
|
/** called when a response status is parsed. the status_str is the string
|
102
107
|
* without the prefixed numerical status indicator.*/
|
103
|
-
static int http1_on_status(http1_parser_s *parser, size_t status,
|
104
|
-
char *status_str, size_t len);
|
108
|
+
static int http1_on_status(http1_parser_s *parser, size_t status, char *status_str, size_t len);
|
105
109
|
/** called when a request path (excluding query) is parsed. */
|
106
110
|
static int http1_on_path(http1_parser_s *parser, char *path, size_t path_len);
|
107
111
|
/** called when a request path (excluding query) is parsed. */
|
108
|
-
static int
|
109
|
-
|
112
|
+
static int
|
113
|
+
http1_on_query(http1_parser_s *parser, char *query, size_t query_len);
|
110
114
|
/** called when a the HTTP/1.x version is parsed. */
|
111
115
|
static int http1_on_version(http1_parser_s *parser, char *version, size_t len);
|
112
116
|
/** called when a header is parsed. */
|
113
|
-
static int http1_on_header(http1_parser_s *parser, char *name, size_t name_len,
|
114
|
-
char *data, size_t data_len);
|
117
|
+
static int http1_on_header(http1_parser_s *parser, char *name, size_t name_len, char *data, size_t data_len);
|
115
118
|
/** called when a body chunk is parsed. */
|
116
|
-
static int
|
117
|
-
|
119
|
+
static int
|
120
|
+
http1_on_body_chunk(http1_parser_s *parser, char *data, size_t data_len);
|
118
121
|
/** called when a protocol error occurred. */
|
119
122
|
static int http1_on_error(http1_parser_s *parser);
|
120
123
|
|
124
|
+
// clang-format on
|
121
125
|
/* *****************************************************************************
|
122
126
|
|
123
127
|
|
@@ -164,15 +168,18 @@ static int http1_on_error(http1_parser_s *parser);
|
|
164
168
|
(!strncasecmp((var_name), (const_name), (len)))
|
165
169
|
#endif
|
166
170
|
|
167
|
-
#define HTTP1_P_FLAG_STATUS_LINE
|
171
|
+
#define HTTP1_P_FLAG_STATUS_LINE 1
|
168
172
|
#define HTTP1_P_FLAG_HEADER_COMPLETE 2
|
169
|
-
#define HTTP1_P_FLAG_COMPLETE
|
170
|
-
#define HTTP1_P_FLAG_CLENGTH
|
171
|
-
#define HTTP1_PARSER_BIT_16
|
172
|
-
#define HTTP1_PARSER_BIT_32
|
173
|
-
#define HTTP1_P_FLAG_CHUNKED
|
174
|
-
#define HTTP1_P_FLAG_RESPONSE
|
175
|
-
|
173
|
+
#define HTTP1_P_FLAG_COMPLETE 4
|
174
|
+
#define HTTP1_P_FLAG_CLENGTH 8
|
175
|
+
#define HTTP1_PARSER_BIT_16 16
|
176
|
+
#define HTTP1_PARSER_BIT_32 32
|
177
|
+
#define HTTP1_P_FLAG_CHUNKED 64
|
178
|
+
#define HTTP1_P_FLAG_RESPONSE 128
|
179
|
+
|
180
|
+
#ifdef __cplusplus
|
181
|
+
#define _Bool bool
|
182
|
+
#endif
|
176
183
|
/* *****************************************************************************
|
177
184
|
Seeking for characters in a string
|
178
185
|
***************************************************************************** */
|
@@ -184,7 +191,8 @@ Seeking for characters in a string
|
|
184
191
|
*
|
185
192
|
* On newer systems, `memchr` should be faster.
|
186
193
|
*/
|
187
|
-
static int seek2ch(uint8_t **buffer,
|
194
|
+
static int seek2ch(uint8_t **buffer,
|
195
|
+
register uint8_t *const limit,
|
188
196
|
const uint8_t c) {
|
189
197
|
if (*buffer >= limit)
|
190
198
|
return 0;
|
@@ -264,14 +272,8 @@ inline static uint8_t seek2eol(uint8_t **pos, uint8_t *const limit) {
|
|
264
272
|
if (!seek2ch(pos, limit, '\n'))
|
265
273
|
return 0;
|
266
274
|
if ((*pos)[-1] == '\r') {
|
267
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
268
|
-
(*pos)[-1] = (*pos)[0] = 0;
|
269
|
-
#endif
|
270
275
|
return 2;
|
271
276
|
}
|
272
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
273
|
-
(*pos)[0] = 0;
|
274
|
-
#endif
|
275
277
|
return 1;
|
276
278
|
}
|
277
279
|
|
@@ -280,7 +282,7 @@ Change a letter to lower case (latin only)
|
|
280
282
|
***************************************************************************** */
|
281
283
|
|
282
284
|
static uint8_t http_tolower(uint8_t c) {
|
283
|
-
if (c >= 'A'
|
285
|
+
if (((c >= 'A') & (c <= 'Z')))
|
284
286
|
c |= 32;
|
285
287
|
return c;
|
286
288
|
}
|
@@ -317,7 +319,8 @@ static long long http1_atol16(const uint8_t *buf, const uint8_t **end) {
|
|
317
319
|
register unsigned long long i = 0;
|
318
320
|
uint8_t inv = 0;
|
319
321
|
for (int limit_ = 0;
|
320
|
-
(*buf == ' ' || *buf == '\t' || *buf == '\f') && limit_ < 32;
|
322
|
+
(*buf == ' ' || *buf == '\t' || *buf == '\f') && limit_ < 32;
|
323
|
+
++limit_)
|
321
324
|
++buf;
|
322
325
|
for (int limit_ = 0; (*buf == '-' || *buf == '+') && limit_ < 32; ++limit_)
|
323
326
|
inv ^= (*(buf++) == '-');
|
@@ -350,7 +353,8 @@ HTTP/1.1 parsre stages
|
|
350
353
|
***************************************************************************** */
|
351
354
|
|
352
355
|
inline static int http1_consume_response_line(http1_parser_s *parser,
|
353
|
-
uint8_t *start,
|
356
|
+
uint8_t *start,
|
357
|
+
uint8_t *end) {
|
354
358
|
parser->state.reserved |= HTTP1_P_FLAG_RESPONSE;
|
355
359
|
uint8_t *tmp = start;
|
356
360
|
if (!seek2ch(&tmp, end, ' '))
|
@@ -360,14 +364,17 @@ inline static int http1_consume_response_line(http1_parser_s *parser,
|
|
360
364
|
tmp = start = tmp + 1;
|
361
365
|
if (!seek2ch(&tmp, end, ' '))
|
362
366
|
return -1;
|
363
|
-
if (http1_on_status(parser,
|
367
|
+
if (http1_on_status(parser,
|
368
|
+
http1_atol(start, NULL),
|
369
|
+
(char *)(tmp + 1),
|
364
370
|
end - tmp))
|
365
371
|
return -1;
|
366
372
|
return 0;
|
367
373
|
}
|
368
374
|
|
369
375
|
inline static int http1_consume_request_line(http1_parser_s *parser,
|
370
|
-
uint8_t *start,
|
376
|
+
uint8_t *start,
|
377
|
+
uint8_t *end) {
|
371
378
|
uint8_t *tmp = start;
|
372
379
|
uint8_t *host_start = NULL;
|
373
380
|
uint8_t *host_end = NULL;
|
@@ -422,19 +429,24 @@ start_version:
|
|
422
429
|
if (http1_on_version(parser, (char *)start, end - start))
|
423
430
|
return -1;
|
424
431
|
/* */
|
425
|
-
if (host_start && http1_on_header(parser,
|
426
|
-
(char *)
|
432
|
+
if (host_start && http1_on_header(parser,
|
433
|
+
(char *)"host",
|
434
|
+
4,
|
435
|
+
(char *)host_start,
|
436
|
+
host_end - host_start))
|
427
437
|
return -1;
|
428
438
|
return 0;
|
429
439
|
}
|
430
440
|
|
431
|
-
#
|
432
|
-
inline /* inline the function
|
441
|
+
#if !HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER
|
442
|
+
inline /* inline the function if it's short enough */
|
433
443
|
#endif
|
434
444
|
static int
|
435
445
|
http1_consume_header_transfer_encoding(http1_parser_s *parser,
|
436
|
-
uint8_t *start,
|
437
|
-
uint8_t *
|
446
|
+
uint8_t *start,
|
447
|
+
uint8_t *end_name,
|
448
|
+
uint8_t *start_value,
|
449
|
+
uint8_t *end) {
|
438
450
|
/* this removes the `chunked` marker and prepares to "unchunk" the data */
|
439
451
|
while (start_value < end && (end[-1] == ',' || end[-1] == ' '))
|
440
452
|
--end;
|
@@ -445,9 +457,9 @@ inline /* inline the function of it's short enough */
|
|
445
457
|
(((uint32_t *)(start_value + 3))[0] | 0x20202020) ==
|
446
458
|
((uint32_t *)"nked")[0]
|
447
459
|
#else
|
448
|
-
((start_value[0] | 32) == 'c'
|
449
|
-
(start_value[2] | 32) == 'u'
|
450
|
-
(start_value[4] | 32) == 'k'
|
460
|
+
((start_value[0] | 32) == 'c' & (start_value[1] | 32) == 'h' &
|
461
|
+
(start_value[2] | 32) == 'u' & (start_value[3] | 32) == 'n' &
|
462
|
+
(start_value[4] | 32) == 'k' & (start_value[5] | 32) == 'e' &
|
451
463
|
(start_value[6] | 32) == 'd')
|
452
464
|
#endif
|
453
465
|
) {
|
@@ -460,9 +472,9 @@ inline /* inline the function of it's short enough */
|
|
460
472
|
if (!(end - start_value))
|
461
473
|
return 0;
|
462
474
|
} else if ((end - start_value) > 7 &&
|
463
|
-
((end[(-7 + 0)] | 32) == 'c'
|
464
|
-
(end[(-7 + 2)] | 32) == 'u'
|
465
|
-
(end[(-7 + 4)] | 32) == 'k'
|
475
|
+
((end[(-7 + 0)] | 32) == 'c' & (end[(-7 + 1)] | 32) == 'h' &
|
476
|
+
(end[(-7 + 2)] | 32) == 'u' & (end[(-7 + 3)] | 32) == 'n' &
|
477
|
+
(end[(-7 + 4)] | 32) == 'k' & (end[(-7 + 5)] | 32) == 'e' &
|
466
478
|
(end[(-7 + 6)] | 32) == 'd')) {
|
467
479
|
/* simple case,`chunked` at the end of list (RFC required) */
|
468
480
|
parser->state.reserved |= HTTP1_P_FLAG_CHUNKED;
|
@@ -473,7 +485,7 @@ inline /* inline the function of it's short enough */
|
|
473
485
|
if (!(end - start_value))
|
474
486
|
return 0;
|
475
487
|
}
|
476
|
-
#
|
488
|
+
#if HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER /* RFC disallows this */
|
477
489
|
else if ((end - start_value) > 7 && (end - start_value) < 256) {
|
478
490
|
/* complex case, `the, chunked, marker, is in the middle of list */
|
479
491
|
uint8_t val[256];
|
@@ -524,25 +536,33 @@ inline /* inline the function of it's short enough */
|
|
524
536
|
val[val_len++] = *start_value;
|
525
537
|
++start_value;
|
526
538
|
}
|
527
|
-
|
539
|
+
if (val_len < 255)
|
540
|
+
val[val_len] = 0;
|
528
541
|
}
|
529
542
|
/* perform callback with `val` or indicate error */
|
530
|
-
if (val_len == 256 ||
|
531
|
-
|
532
|
-
|
543
|
+
if (val_len == 256 || (val_len && http1_on_header(parser,
|
544
|
+
(char *)start,
|
545
|
+
(end_name - start),
|
546
|
+
(char *)val,
|
547
|
+
val_len)))
|
533
548
|
return -1;
|
534
549
|
return 0;
|
535
550
|
}
|
536
551
|
#endif /* HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER */
|
537
552
|
/* perform callback */
|
538
|
-
if (http1_on_header(parser,
|
539
|
-
(char *)
|
553
|
+
if (http1_on_header(parser,
|
554
|
+
(char *)start,
|
555
|
+
(end_name - start),
|
556
|
+
(char *)start_value,
|
557
|
+
end - start_value))
|
540
558
|
return -1;
|
541
559
|
return 0;
|
542
560
|
}
|
543
561
|
inline static int http1_consume_header_top(http1_parser_s *parser,
|
544
|
-
uint8_t *start,
|
545
|
-
uint8_t *
|
562
|
+
uint8_t *start,
|
563
|
+
uint8_t *end_name,
|
564
|
+
uint8_t *start_value,
|
565
|
+
uint8_t *end) {
|
546
566
|
if ((end_name - start) == 14 &&
|
547
567
|
#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED && HTTP_HEADERS_LOWERCASE
|
548
568
|
*((uint64_t *)start) == *((uint64_t *)"content-") &&
|
@@ -565,19 +585,26 @@ inline static int http1_consume_header_top(http1_parser_s *parser,
|
|
565
585
|
} else if ((end_name - start) == 17 && (end - start_value) >= 7 &&
|
566
586
|
!parser->state.content_length &&
|
567
587
|
#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED && HTTP_HEADERS_LOWERCASE
|
568
|
-
*((uint64_t *)start) == *((uint64_t *)"transfer")
|
569
|
-
|
588
|
+
((*((uint64_t *)start) == *((uint64_t *)"transfer")) &
|
589
|
+
((*((uint64_t *)(start + 8)) == *((uint64_t *)"-encodin")) &
|
590
|
+
(start[16] == 'g')))
|
570
591
|
#else
|
571
592
|
HEADER_NAME_IS_EQ((char *)start, "transfer-encoding", 17)
|
572
593
|
#endif
|
573
594
|
) {
|
574
595
|
/* handle the special `transfer-encoding: chunked` header */
|
575
|
-
return http1_consume_header_transfer_encoding(parser,
|
576
|
-
|
596
|
+
return http1_consume_header_transfer_encoding(parser,
|
597
|
+
start,
|
598
|
+
end_name,
|
599
|
+
start_value,
|
600
|
+
end);
|
577
601
|
}
|
578
602
|
/* perform callback */
|
579
|
-
if (http1_on_header(parser,
|
580
|
-
(char *)
|
603
|
+
if (http1_on_header(parser,
|
604
|
+
(char *)start,
|
605
|
+
(end_name - start),
|
606
|
+
(char *)start_value,
|
607
|
+
end - start_value))
|
581
608
|
return -1;
|
582
609
|
return 0;
|
583
610
|
}
|
@@ -587,58 +614,82 @@ inline static int http1_consume_header_trailer(http1_parser_s *parser,
|
|
587
614
|
uint8_t *end_name,
|
588
615
|
uint8_t *start_value,
|
589
616
|
uint8_t *end) {
|
590
|
-
if ((end_name - start) > 1 && start[0] == 'x') {
|
591
|
-
/* X- headers are allowed */
|
592
|
-
goto white_listed;
|
593
|
-
}
|
594
|
-
|
595
617
|
/* white listed trailer names */
|
596
618
|
const struct {
|
597
619
|
char *name;
|
598
620
|
long len;
|
599
|
-
}
|
600
|
-
{"server-timing", 13}, /* specific for client data... */
|
601
|
-
{NULL, 0},
|
621
|
+
} http1_trailer_allowed_list[] = {
|
622
|
+
{(char *)"server-timing", 13}, /* specific for client data... */
|
623
|
+
{NULL, 0}, /* end of list marker */
|
602
624
|
};
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
625
|
+
if ((end_name - start) > 1 && start[0] == 'x') {
|
626
|
+
/* X- headers are allowed */
|
627
|
+
goto allowed_list;
|
628
|
+
}
|
629
|
+
for (size_t i = 0; http1_trailer_allowed_list[i].name; ++i) {
|
630
|
+
if ((long)(end_name - start) == http1_trailer_allowed_list[i].len &&
|
631
|
+
HEADER_NAME_IS_EQ((char *)start,
|
632
|
+
http1_trailer_allowed_list[i].name,
|
633
|
+
http1_trailer_allowed_list[i].len)) {
|
607
634
|
/* header disallowed here */
|
608
|
-
goto
|
635
|
+
goto allowed_list;
|
609
636
|
}
|
610
637
|
}
|
611
638
|
return 0;
|
612
|
-
|
639
|
+
allowed_list:
|
613
640
|
/* perform callback */
|
614
|
-
if (http1_on_header(parser,
|
615
|
-
(char *)
|
641
|
+
if (http1_on_header(parser,
|
642
|
+
(char *)start,
|
643
|
+
(end_name - start),
|
644
|
+
(char *)start_value,
|
645
|
+
end - start_value))
|
616
646
|
return -1;
|
617
647
|
return 0;
|
618
648
|
}
|
619
649
|
|
620
|
-
inline static int http1_consume_header(http1_parser_s *parser,
|
650
|
+
inline static int http1_consume_header(http1_parser_s *parser,
|
651
|
+
uint8_t *start,
|
621
652
|
uint8_t *end) {
|
653
|
+
static const _Bool forbidden_name_chars[256] = {
|
654
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
655
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1,
|
656
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
|
657
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
|
658
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
659
|
+
0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
660
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
661
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
662
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
663
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
664
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
622
665
|
uint8_t *end_name = start;
|
623
666
|
/* divide header name from data */
|
624
667
|
if (!seek2ch(&end_name, end, ':'))
|
625
668
|
return -1;
|
626
669
|
if (end_name[-1] == ' ' || end_name[-1] == '\t')
|
627
670
|
return -1;
|
671
|
+
if (forbidden_name_chars[start[0] & 0xFF])
|
672
|
+
return -1;
|
628
673
|
#if HTTP_HEADERS_LOWERCASE
|
629
|
-
for (uint8_t *t = start; t < end_name; t++)
|
674
|
+
for (uint8_t *t = start; t < end_name; t++)
|
630
675
|
*t = http_tolower(*t);
|
631
|
-
}
|
632
676
|
#endif
|
633
677
|
uint8_t *start_value = end_name + 1;
|
634
678
|
// clear away leading white space from value.
|
635
679
|
while (start_value < end &&
|
636
|
-
(start_value[0] == ' '
|
680
|
+
((start_value[0] == ' ') | (start_value[0] == '\t'))) {
|
637
681
|
start_value++;
|
638
682
|
};
|
683
|
+
// clear away added white space from value.
|
684
|
+
while (start_value < end && ((end[0] == ' ') | (end[0] == '\t'))) {
|
685
|
+
end++;
|
686
|
+
};
|
639
687
|
return (parser->state.read ? http1_consume_header_trailer
|
640
|
-
: http1_consume_header_top)(
|
641
|
-
|
688
|
+
: http1_consume_header_top)(parser,
|
689
|
+
start,
|
690
|
+
end_name,
|
691
|
+
start_value,
|
692
|
+
end);
|
642
693
|
}
|
643
694
|
|
644
695
|
/* *****************************************************************************
|
@@ -646,7 +697,8 @@ HTTP/1.1 Body handling
|
|
646
697
|
***************************************************************************** */
|
647
698
|
|
648
699
|
inline static int http1_consume_body_streamed(http1_parser_s *parser,
|
649
|
-
void *buffer,
|
700
|
+
void *buffer,
|
701
|
+
size_t length,
|
650
702
|
uint8_t **start) {
|
651
703
|
uint8_t *end = *start + parser->state.content_length - parser->state.read;
|
652
704
|
uint8_t *const stop = ((uint8_t *)buffer) + length;
|
@@ -663,7 +715,8 @@ inline static int http1_consume_body_streamed(http1_parser_s *parser,
|
|
663
715
|
}
|
664
716
|
|
665
717
|
inline static int http1_consume_body_chunked(http1_parser_s *parser,
|
666
|
-
void *buffer,
|
718
|
+
void *buffer,
|
719
|
+
size_t length,
|
667
720
|
uint8_t **start) {
|
668
721
|
uint8_t *const stop = ((uint8_t *)buffer) + length;
|
669
722
|
uint8_t *end = *start;
|
@@ -681,7 +734,7 @@ inline static int http1_consume_body_chunked(http1_parser_s *parser,
|
|
681
734
|
long long chunk_len = http1_atol16(end, (const uint8_t **)&end);
|
682
735
|
if (end + 2 > stop) /* overflowed? */
|
683
736
|
return 0;
|
684
|
-
if ((end[0] != '\r'
|
737
|
+
if ((end[0] != '\r') | (end[1] != '\n') | (chunk_len < 0))
|
685
738
|
return -1; /* required EOL after content length */
|
686
739
|
end += 2;
|
687
740
|
|
@@ -691,25 +744,32 @@ inline static int http1_consume_body_chunked(http1_parser_s *parser,
|
|
691
744
|
/* all chunked data was parsed */
|
692
745
|
/* update content-length */
|
693
746
|
parser->state.content_length = parser->state.read;
|
694
|
-
#
|
747
|
+
#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
695
748
|
{ /* add virtual header ... ? */
|
696
749
|
char buf[512];
|
697
750
|
size_t buf_len = 512;
|
698
751
|
size_t tmp_len = parser->state.read;
|
699
752
|
buf[--buf_len] = 0;
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
753
|
+
if (tmp_len) {
|
754
|
+
while (tmp_len) {
|
755
|
+
size_t mod = tmp_len / 10;
|
756
|
+
buf[--buf_len] = '0' + (tmp_len - (mod * 10));
|
757
|
+
tmp_len = mod;
|
758
|
+
}
|
759
|
+
} else {
|
760
|
+
buf[--buf_len] = '0';
|
704
761
|
}
|
705
762
|
if (!(parser->state.reserved & HTTP1_P_FLAG_CLENGTH) &&
|
706
|
-
http1_on_header(parser,
|
707
|
-
(char *)
|
763
|
+
http1_on_header(parser,
|
764
|
+
(char *)"content-length",
|
765
|
+
14,
|
766
|
+
(char *)buf + buf_len,
|
767
|
+
511 - buf_len)) {
|
708
768
|
return -1;
|
709
769
|
}
|
710
770
|
}
|
711
771
|
#endif
|
712
|
-
/*
|
772
|
+
/* consume trailing EOL */
|
713
773
|
if (*start + 2 <= stop && (start[0][0] == '\r' || start[0][0] == '\n'))
|
714
774
|
*start += 1 + (start[0][1] == '\r' || start[0][1] == '\n');
|
715
775
|
else {
|
@@ -737,8 +797,10 @@ inline static int http1_consume_body_chunked(http1_parser_s *parser,
|
|
737
797
|
return 0;
|
738
798
|
}
|
739
799
|
|
740
|
-
inline static int http1_consume_body(http1_parser_s *parser,
|
741
|
-
|
800
|
+
inline static int http1_consume_body(http1_parser_s *parser,
|
801
|
+
void *buffer,
|
802
|
+
size_t length,
|
803
|
+
uint8_t **start) {
|
742
804
|
if (parser->state.content_length > 0 &&
|
743
805
|
parser->state.content_length > parser->state.read) {
|
744
806
|
/* normal, streamed data */
|
@@ -823,7 +885,8 @@ re_eval:
|
|
823
885
|
do {
|
824
886
|
if (start >= stop)
|
825
887
|
return HTTP1_CONSUMED; /* buffer ended on header line */
|
826
|
-
if (*start == '\
|
888
|
+
if ((*start == '\n') |
|
889
|
+
(start + 1 < stop && ((start[0] == '\r') & (start[1] == '\n')))) {
|
827
890
|
goto finished_headers; /* empty line, end of headers */
|
828
891
|
}
|
829
892
|
end = start;
|
@@ -839,7 +902,7 @@ re_eval:
|
|
839
902
|
++start;
|
840
903
|
end = start;
|
841
904
|
parser->state.reserved |= HTTP1_P_FLAG_HEADER_COMPLETE;
|
842
|
-
/*
|
905
|
+
/* fall through */
|
843
906
|
case (HTTP1_P_FLAG_HEADER_COMPLETE | HTTP1_P_FLAG_STATUS_LINE):
|
844
907
|
/* request body */
|
845
908
|
{
|
@@ -870,4 +933,903 @@ error:
|
|
870
933
|
#undef HTTP1_CONSUMED
|
871
934
|
}
|
872
935
|
|
936
|
+
/** Returns true if the parsing stopped after a complete request / response. */
|
937
|
+
inline static int http1_complete(http1_parser_s *parser) {
|
938
|
+
return !parser->state.reserved;
|
939
|
+
}
|
940
|
+
|
941
|
+
/* *****************************************************************************
|
942
|
+
|
943
|
+
|
944
|
+
|
945
|
+
|
946
|
+
HTTP/1.1 TESTING
|
947
|
+
|
948
|
+
|
949
|
+
|
950
|
+
|
951
|
+
***************************************************************************** */
|
952
|
+
#ifdef HTTP1_TEST_PARSER
|
953
|
+
#include "signal.h"
|
954
|
+
|
955
|
+
#define HTTP1_TEST_ASSERT(cond, ...) \
|
956
|
+
if (!(cond)) { \
|
957
|
+
fprintf(stderr, __VA_ARGS__); \
|
958
|
+
fprintf(stderr, "\n"); \
|
959
|
+
kill(0, SIGINT); \
|
960
|
+
exit(-1); \
|
961
|
+
}
|
962
|
+
|
963
|
+
static size_t http1_test_pos;
|
964
|
+
static char http1_test_temp_buf[8092];
|
965
|
+
static size_t http1_test_temp_buf_pos;
|
966
|
+
static struct {
|
967
|
+
char *test_name;
|
968
|
+
char *request[16];
|
969
|
+
struct {
|
970
|
+
char body[1024];
|
971
|
+
size_t body_len;
|
972
|
+
const char *method;
|
973
|
+
ssize_t status;
|
974
|
+
const char *path;
|
975
|
+
const char *query;
|
976
|
+
const char *version;
|
977
|
+
struct http1_test_header_s {
|
978
|
+
const char *name;
|
979
|
+
size_t name_len;
|
980
|
+
const char *val;
|
981
|
+
size_t val_len;
|
982
|
+
} headers[12];
|
983
|
+
} result, expect;
|
984
|
+
} http1_test_data[] = {
|
985
|
+
{
|
986
|
+
.test_name = "simple empty request",
|
987
|
+
.request = {"GET / HTTP/1.1\r\nHost:localhost\r\n\r\n"},
|
988
|
+
.expect =
|
989
|
+
{
|
990
|
+
.body = "",
|
991
|
+
.body_len = 0,
|
992
|
+
.method = "GET",
|
993
|
+
.path = "/",
|
994
|
+
.query = NULL,
|
995
|
+
.version = "HTTP/1.1",
|
996
|
+
.headers =
|
997
|
+
{
|
998
|
+
{.name = "host",
|
999
|
+
.name_len = 4,
|
1000
|
+
.val = "localhost",
|
1001
|
+
.val_len = 9},
|
1002
|
+
},
|
1003
|
+
},
|
1004
|
+
},
|
1005
|
+
{
|
1006
|
+
.test_name = "space before header data",
|
1007
|
+
.request = {"POST /my/path HTTP/1.2\r\nHost: localhost\r\n\r\n"},
|
1008
|
+
.expect =
|
1009
|
+
{
|
1010
|
+
.body = "",
|
1011
|
+
.body_len = 0,
|
1012
|
+
.method = "POST",
|
1013
|
+
.path = "/my/path",
|
1014
|
+
.query = NULL,
|
1015
|
+
.version = "HTTP/1.2",
|
1016
|
+
.headers =
|
1017
|
+
{
|
1018
|
+
{.name = "host",
|
1019
|
+
.name_len = 4,
|
1020
|
+
.val = "localhost",
|
1021
|
+
.val_len = 9},
|
1022
|
+
},
|
1023
|
+
},
|
1024
|
+
},
|
1025
|
+
{
|
1026
|
+
.test_name = "simple request, fragmented header (in new line)",
|
1027
|
+
.request = {"GET / HTTP/1.1\r\n", "Host:localhost\r\n\r\n"},
|
1028
|
+
.expect =
|
1029
|
+
{
|
1030
|
+
.body = "",
|
1031
|
+
.body_len = 0,
|
1032
|
+
.method = "GET",
|
1033
|
+
.path = "/",
|
1034
|
+
.query = NULL,
|
1035
|
+
.version = "HTTP/1.1",
|
1036
|
+
.headers =
|
1037
|
+
{
|
1038
|
+
{.name = "host",
|
1039
|
+
.name_len = 4,
|
1040
|
+
.val = "localhost",
|
1041
|
+
.val_len = 9},
|
1042
|
+
},
|
1043
|
+
},
|
1044
|
+
},
|
1045
|
+
{
|
1046
|
+
.test_name = "request with query",
|
1047
|
+
.request = {"METHOD /path?q=query HTTP/1.3\r\nHost:localhost\r\n\r\n"},
|
1048
|
+
.expect =
|
1049
|
+
{
|
1050
|
+
.body = "",
|
1051
|
+
.body_len = 0,
|
1052
|
+
.method = "METHOD",
|
1053
|
+
.path = "/path",
|
1054
|
+
.query = "q=query",
|
1055
|
+
.version = "HTTP/1.3",
|
1056
|
+
.headers =
|
1057
|
+
{
|
1058
|
+
{.name = "host",
|
1059
|
+
.name_len = 4,
|
1060
|
+
.val = "localhost",
|
1061
|
+
.val_len = 9},
|
1062
|
+
},
|
1063
|
+
},
|
1064
|
+
},
|
1065
|
+
{
|
1066
|
+
.test_name = "mid-fragmented header",
|
1067
|
+
.request = {"GET / HTTP/1.1\r\nHost: loca", "lhost\r\n\r\n"},
|
1068
|
+
.expect =
|
1069
|
+
{
|
1070
|
+
.body = "",
|
1071
|
+
.body_len = 0,
|
1072
|
+
.method = "GET",
|
1073
|
+
.path = "/",
|
1074
|
+
.query = NULL,
|
1075
|
+
.version = "HTTP/1.1",
|
1076
|
+
.headers =
|
1077
|
+
{
|
1078
|
+
{.name = "host",
|
1079
|
+
.name_len = 4,
|
1080
|
+
.val = "localhost",
|
1081
|
+
.val_len = 9},
|
1082
|
+
},
|
1083
|
+
},
|
1084
|
+
},
|
1085
|
+
{
|
1086
|
+
.test_name = "simple with body",
|
1087
|
+
.request = {"GET / HTTP/1.1\r\nHost:with body\r\n"
|
1088
|
+
"Content-lEnGth: 5\r\n\r\nHello"},
|
1089
|
+
.expect =
|
1090
|
+
{
|
1091
|
+
.body = "Hello",
|
1092
|
+
.body_len = 5,
|
1093
|
+
.method = "GET",
|
1094
|
+
.path = "/",
|
1095
|
+
.query = NULL,
|
1096
|
+
.version = "HTTP/1.1",
|
1097
|
+
.headers =
|
1098
|
+
{
|
1099
|
+
{
|
1100
|
+
.name = "host",
|
1101
|
+
.name_len = 4,
|
1102
|
+
.val = "with body",
|
1103
|
+
.val_len = 9,
|
1104
|
+
},
|
1105
|
+
{
|
1106
|
+
.name = "content-length",
|
1107
|
+
.name_len = 14,
|
1108
|
+
.val = "5",
|
1109
|
+
.val_len = 1,
|
1110
|
+
},
|
1111
|
+
},
|
1112
|
+
},
|
1113
|
+
},
|
1114
|
+
{
|
1115
|
+
.test_name = "fragmented body",
|
1116
|
+
.request = {"GET / HTTP/1.1\r\nHost:with body\r\n",
|
1117
|
+
"Content-lEnGth: 5\r\n\r\nHe",
|
1118
|
+
"llo"},
|
1119
|
+
.expect =
|
1120
|
+
{
|
1121
|
+
.body = "Hello",
|
1122
|
+
.body_len = 5,
|
1123
|
+
.method = "GET",
|
1124
|
+
.path = "/",
|
1125
|
+
.query = NULL,
|
1126
|
+
.version = "HTTP/1.1",
|
1127
|
+
.headers =
|
1128
|
+
{
|
1129
|
+
{
|
1130
|
+
.name = "host",
|
1131
|
+
.name_len = 4,
|
1132
|
+
.val = "with body",
|
1133
|
+
.val_len = 9,
|
1134
|
+
},
|
1135
|
+
{
|
1136
|
+
.name = "content-length",
|
1137
|
+
.name_len = 14,
|
1138
|
+
.val = "5",
|
1139
|
+
.val_len = 1,
|
1140
|
+
},
|
1141
|
+
},
|
1142
|
+
},
|
1143
|
+
},
|
1144
|
+
{
|
1145
|
+
.test_name = "fragmented body 2 (cuts EOL)",
|
1146
|
+
.request = {"POST / HTTP/1.1\r\nHost:with body\r\n",
|
1147
|
+
"Content-lEnGth: 5\r\n",
|
1148
|
+
"\r\n",
|
1149
|
+
"He",
|
1150
|
+
"llo"},
|
1151
|
+
.expect =
|
1152
|
+
{
|
1153
|
+
.body = "Hello",
|
1154
|
+
.body_len = 5,
|
1155
|
+
.method = "POST",
|
1156
|
+
.path = "/",
|
1157
|
+
.query = NULL,
|
1158
|
+
.version = "HTTP/1.1",
|
1159
|
+
.headers =
|
1160
|
+
{
|
1161
|
+
{
|
1162
|
+
.name = "host",
|
1163
|
+
.name_len = 4,
|
1164
|
+
.val = "with body",
|
1165
|
+
.val_len = 9,
|
1166
|
+
},
|
1167
|
+
{
|
1168
|
+
.name = "content-length",
|
1169
|
+
.name_len = 14,
|
1170
|
+
.val = "5",
|
1171
|
+
.val_len = 1,
|
1172
|
+
},
|
1173
|
+
},
|
1174
|
+
},
|
1175
|
+
},
|
1176
|
+
{
|
1177
|
+
.test_name = "chunked body (simple)",
|
1178
|
+
.request = {"POST / HTTP/1.1\r\nHost:with body\r\n"
|
1179
|
+
"Transfer-Encoding: chunked\r\n"
|
1180
|
+
"\r\n"
|
1181
|
+
"5\r\n"
|
1182
|
+
"Hello"
|
1183
|
+
"\r\n0\r\n\r\n"},
|
1184
|
+
.expect =
|
1185
|
+
{
|
1186
|
+
.body = "Hello",
|
1187
|
+
.body_len = 5,
|
1188
|
+
.method = "POST",
|
1189
|
+
.path = "/",
|
1190
|
+
.query = NULL,
|
1191
|
+
.version = "HTTP/1.1",
|
1192
|
+
.headers =
|
1193
|
+
{
|
1194
|
+
{
|
1195
|
+
.name = "host",
|
1196
|
+
.name_len = 4,
|
1197
|
+
.val = "with body",
|
1198
|
+
.val_len = 9,
|
1199
|
+
},
|
1200
|
+
#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
1201
|
+
{
|
1202
|
+
.name = "content-length",
|
1203
|
+
.name_len = 14,
|
1204
|
+
.val = "5",
|
1205
|
+
.val_len = 1,
|
1206
|
+
},
|
1207
|
+
#endif
|
1208
|
+
},
|
1209
|
+
},
|
1210
|
+
},
|
1211
|
+
{
|
1212
|
+
.test_name = "chunked body (empty)",
|
1213
|
+
.request = {"POST / HTTP/1.1\r\nHost:with body\r\n"
|
1214
|
+
"Transfer-Encoding: chunked\r\n"
|
1215
|
+
"\r\n0\r\n\r\n"},
|
1216
|
+
.expect =
|
1217
|
+
{
|
1218
|
+
.body = "",
|
1219
|
+
.body_len = 0,
|
1220
|
+
.method = "POST",
|
1221
|
+
.path = "/",
|
1222
|
+
.query = NULL,
|
1223
|
+
.version = "HTTP/1.1",
|
1224
|
+
.headers =
|
1225
|
+
{
|
1226
|
+
{
|
1227
|
+
.name = "host",
|
1228
|
+
.name_len = 4,
|
1229
|
+
.val = "with body",
|
1230
|
+
.val_len = 9,
|
1231
|
+
},
|
1232
|
+
#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
1233
|
+
{
|
1234
|
+
.name = "content-length",
|
1235
|
+
.name_len = 14,
|
1236
|
+
.val = "0",
|
1237
|
+
.val_len = 1,
|
1238
|
+
},
|
1239
|
+
#endif
|
1240
|
+
},
|
1241
|
+
},
|
1242
|
+
},
|
1243
|
+
{
|
1244
|
+
.test_name = "chunked body (end of list)",
|
1245
|
+
.request = {"POST / HTTP/1.1\r\nHost:with body\r\n"
|
1246
|
+
"Transfer-Encoding: gzip, foo, chunked\r\n"
|
1247
|
+
"\r\n"
|
1248
|
+
"5\r\n"
|
1249
|
+
"Hello"
|
1250
|
+
"\r\n0\r\n\r\n"},
|
1251
|
+
.expect =
|
1252
|
+
{
|
1253
|
+
.body = "Hello",
|
1254
|
+
.body_len = 5,
|
1255
|
+
.method = "POST",
|
1256
|
+
.path = "/",
|
1257
|
+
.query = NULL,
|
1258
|
+
.version = "HTTP/1.1",
|
1259
|
+
.headers =
|
1260
|
+
{
|
1261
|
+
{
|
1262
|
+
.name = "host",
|
1263
|
+
.name_len = 4,
|
1264
|
+
.val = "with body",
|
1265
|
+
.val_len = 9,
|
1266
|
+
},
|
1267
|
+
{
|
1268
|
+
.name = "transfer-encoding",
|
1269
|
+
.name_len = 17,
|
1270
|
+
.val = "gzip, foo",
|
1271
|
+
.val_len = 9,
|
1272
|
+
},
|
1273
|
+
#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
1274
|
+
{
|
1275
|
+
.name = "content-length",
|
1276
|
+
.name_len = 14,
|
1277
|
+
.val = "5",
|
1278
|
+
.val_len = 1,
|
1279
|
+
},
|
1280
|
+
#endif
|
1281
|
+
},
|
1282
|
+
},
|
1283
|
+
},
|
1284
|
+
#if HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER
|
1285
|
+
{
|
1286
|
+
.test_name = "chunked body (middle of list - RFC violation)",
|
1287
|
+
.request = {"POST / HTTP/1.1\r\nHost:with body\r\n"
|
1288
|
+
"Transfer-Encoding: gzip, chunked, foo\r\n"
|
1289
|
+
"\r\n",
|
1290
|
+
"5\r\n"
|
1291
|
+
"Hello"
|
1292
|
+
"\r\n0\r\n\r\n"},
|
1293
|
+
.expect =
|
1294
|
+
{
|
1295
|
+
.body = "Hello",
|
1296
|
+
.body_len = 5,
|
1297
|
+
.method = "POST",
|
1298
|
+
.path = "/",
|
1299
|
+
.query = NULL,
|
1300
|
+
.version = "HTTP/1.1",
|
1301
|
+
.headers =
|
1302
|
+
{
|
1303
|
+
{
|
1304
|
+
.name = "host",
|
1305
|
+
.name_len = 4,
|
1306
|
+
.val = "with body",
|
1307
|
+
.val_len = 9,
|
1308
|
+
},
|
1309
|
+
{
|
1310
|
+
.name = "transfer-encoding",
|
1311
|
+
.name_len = 17,
|
1312
|
+
.val = "gzip,foo",
|
1313
|
+
.val_len = 8,
|
1314
|
+
},
|
1315
|
+
#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
1316
|
+
{
|
1317
|
+
.name = "content-length",
|
1318
|
+
.name_len = 14,
|
1319
|
+
.val = "5",
|
1320
|
+
.val_len = 1,
|
1321
|
+
},
|
1322
|
+
#endif
|
1323
|
+
},
|
1324
|
+
},
|
1325
|
+
},
|
1326
|
+
#endif /* HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER */
|
1327
|
+
{
|
1328
|
+
.test_name = "chunked body (fragmented)",
|
1329
|
+
.request =
|
1330
|
+
{
|
1331
|
+
"POST / HTTP/1.1\r\nHost:with body\r\n",
|
1332
|
+
"Transfer-Encoding: chunked\r\n",
|
1333
|
+
"\r\n"
|
1334
|
+
"5\r\n",
|
1335
|
+
"He",
|
1336
|
+
"llo",
|
1337
|
+
"\r\n0\r\n\r\n",
|
1338
|
+
},
|
1339
|
+
.expect =
|
1340
|
+
{
|
1341
|
+
.body = "Hello",
|
1342
|
+
.body_len = 5,
|
1343
|
+
.method = "POST",
|
1344
|
+
.path = "/",
|
1345
|
+
.query = NULL,
|
1346
|
+
.version = "HTTP/1.1",
|
1347
|
+
.headers =
|
1348
|
+
{
|
1349
|
+
{
|
1350
|
+
.name = "host",
|
1351
|
+
.name_len = 4,
|
1352
|
+
.val = "with body",
|
1353
|
+
.val_len = 9,
|
1354
|
+
},
|
1355
|
+
#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
1356
|
+
{
|
1357
|
+
.name = "content-length",
|
1358
|
+
.name_len = 14,
|
1359
|
+
.val = "5",
|
1360
|
+
.val_len = 1,
|
1361
|
+
},
|
1362
|
+
#endif
|
1363
|
+
},
|
1364
|
+
},
|
1365
|
+
},
|
1366
|
+
{
|
1367
|
+
.test_name = "chunked body (fragmented + multi-message)",
|
1368
|
+
.request =
|
1369
|
+
{
|
1370
|
+
"POST / HTTP/1.1\r\nHost:with body\r\n",
|
1371
|
+
"Transfer-Encoding: chunked\r\n",
|
1372
|
+
"\r\n"
|
1373
|
+
"2\r\n",
|
1374
|
+
"He",
|
1375
|
+
"3\r\nl",
|
1376
|
+
"lo",
|
1377
|
+
"\r\n0\r\n\r\n",
|
1378
|
+
},
|
1379
|
+
.expect =
|
1380
|
+
{
|
1381
|
+
.body = "Hello",
|
1382
|
+
.body_len = 5,
|
1383
|
+
.method = "POST",
|
1384
|
+
.path = "/",
|
1385
|
+
.query = NULL,
|
1386
|
+
.version = "HTTP/1.1",
|
1387
|
+
.headers =
|
1388
|
+
{
|
1389
|
+
{
|
1390
|
+
.name = "host",
|
1391
|
+
.name_len = 4,
|
1392
|
+
.val = "with body",
|
1393
|
+
.val_len = 9,
|
1394
|
+
},
|
1395
|
+
#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
1396
|
+
{
|
1397
|
+
.name = "content-length",
|
1398
|
+
.name_len = 14,
|
1399
|
+
.val = "5",
|
1400
|
+
.val_len = 1,
|
1401
|
+
},
|
1402
|
+
#endif
|
1403
|
+
},
|
1404
|
+
},
|
1405
|
+
},
|
1406
|
+
{
|
1407
|
+
.test_name = "chunked body (fragmented + broken-multi-message)",
|
1408
|
+
.request =
|
1409
|
+
{
|
1410
|
+
"POST / HTTP/1.1\r\nHost:with body\r\n",
|
1411
|
+
"Transfer-Encoding: chunked\r\n",
|
1412
|
+
"\r\n",
|
1413
|
+
"2\r\n",
|
1414
|
+
"H",
|
1415
|
+
"e",
|
1416
|
+
"3\r\nl",
|
1417
|
+
"l"
|
1418
|
+
"o",
|
1419
|
+
"\r\n0\r\n\r\n",
|
1420
|
+
},
|
1421
|
+
.expect =
|
1422
|
+
{
|
1423
|
+
.body = "Hello",
|
1424
|
+
.body_len = 5,
|
1425
|
+
.method = "POST",
|
1426
|
+
.path = "/",
|
1427
|
+
.query = NULL,
|
1428
|
+
.version = "HTTP/1.1",
|
1429
|
+
.headers =
|
1430
|
+
{
|
1431
|
+
{
|
1432
|
+
.name = "host",
|
1433
|
+
.name_len = 4,
|
1434
|
+
.val = "with body",
|
1435
|
+
.val_len = 9,
|
1436
|
+
},
|
1437
|
+
#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
1438
|
+
{
|
1439
|
+
.name = "content-length",
|
1440
|
+
.name_len = 14,
|
1441
|
+
.val = "5",
|
1442
|
+
.val_len = 1,
|
1443
|
+
},
|
1444
|
+
#endif
|
1445
|
+
},
|
1446
|
+
},
|
1447
|
+
},
|
1448
|
+
{
|
1449
|
+
.test_name = "chunked body (...longer + trailer + empty value...)",
|
1450
|
+
.request =
|
1451
|
+
{
|
1452
|
+
"POST / HTTP/1.1\r\nHost:with body\r\n",
|
1453
|
+
"Transfer-Encoding: chunked\r\n",
|
1454
|
+
"\r\n",
|
1455
|
+
"4\r\n",
|
1456
|
+
"Wiki\r\n",
|
1457
|
+
"5\r\n",
|
1458
|
+
"pedia\r\n",
|
1459
|
+
"E\r\n",
|
1460
|
+
" in\r\n",
|
1461
|
+
"\r\n",
|
1462
|
+
"chunks.\r\n",
|
1463
|
+
"0\r\n",
|
1464
|
+
"X-Foo: trailer\r\n",
|
1465
|
+
"sErvEr-tiMing: \r\n",
|
1466
|
+
"\r\n",
|
1467
|
+
},
|
1468
|
+
.expect =
|
1469
|
+
{
|
1470
|
+
.body = "Wikipedia in\r\n\r\nchunks.",
|
1471
|
+
.body_len = 23,
|
1472
|
+
.method = "POST",
|
1473
|
+
.path = "/",
|
1474
|
+
.query = NULL,
|
1475
|
+
.version = "HTTP/1.1",
|
1476
|
+
.headers =
|
1477
|
+
{
|
1478
|
+
{
|
1479
|
+
.name = "host",
|
1480
|
+
.name_len = 4,
|
1481
|
+
.val = "with body",
|
1482
|
+
.val_len = 9,
|
1483
|
+
},
|
1484
|
+
#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
1485
|
+
{
|
1486
|
+
.name = "content-length",
|
1487
|
+
.name_len = 14,
|
1488
|
+
.val = "23",
|
1489
|
+
.val_len = 2,
|
1490
|
+
},
|
1491
|
+
#endif
|
1492
|
+
{
|
1493
|
+
.name = "x-foo",
|
1494
|
+
.name_len = 5,
|
1495
|
+
.val = "trailer",
|
1496
|
+
.val_len = 7,
|
1497
|
+
},
|
1498
|
+
{
|
1499
|
+
.name = "server-timing",
|
1500
|
+
.name_len = 13,
|
1501
|
+
.val = "",
|
1502
|
+
.val_len = 0,
|
1503
|
+
},
|
1504
|
+
},
|
1505
|
+
},
|
1506
|
+
},
|
1507
|
+
{
|
1508
|
+
.test_name = "chunked body (fragmented + surprize trailer)",
|
1509
|
+
.request =
|
1510
|
+
{
|
1511
|
+
"POST / HTTP/1.1\r\nHost:with body\r\n",
|
1512
|
+
"Transfer-Encoding: chunked\r\n",
|
1513
|
+
"\r\n"
|
1514
|
+
"5\r\n",
|
1515
|
+
"He",
|
1516
|
+
"llo",
|
1517
|
+
"\r\n0\r\nX-Foo: trailer\r\n\r\n",
|
1518
|
+
},
|
1519
|
+
.expect =
|
1520
|
+
{
|
1521
|
+
.body = "Hello",
|
1522
|
+
.body_len = 5,
|
1523
|
+
.method = "POST",
|
1524
|
+
.path = "/",
|
1525
|
+
.query = NULL,
|
1526
|
+
.version = "HTTP/1.1",
|
1527
|
+
.headers =
|
1528
|
+
{
|
1529
|
+
{
|
1530
|
+
.name = "host",
|
1531
|
+
.name_len = 4,
|
1532
|
+
.val = "with body",
|
1533
|
+
.val_len = 9,
|
1534
|
+
},
|
1535
|
+
#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
1536
|
+
{
|
1537
|
+
.name = "content-length",
|
1538
|
+
.name_len = 14,
|
1539
|
+
.val = "5",
|
1540
|
+
.val_len = 1,
|
1541
|
+
},
|
1542
|
+
#endif
|
1543
|
+
{
|
1544
|
+
.name = "x-foo",
|
1545
|
+
.name_len = 5,
|
1546
|
+
.val = "trailer",
|
1547
|
+
.val_len = 7,
|
1548
|
+
},
|
1549
|
+
},
|
1550
|
+
},
|
1551
|
+
},
|
1552
|
+
/* stop marker */
|
1553
|
+
{
|
1554
|
+
.request = {NULL},
|
1555
|
+
},
|
1556
|
+
};
|
1557
|
+
|
1558
|
+
/** called when a request was received. */
|
1559
|
+
static int http1_on_request(http1_parser_s *parser) {
|
1560
|
+
(void)parser;
|
1561
|
+
return 0;
|
1562
|
+
}
|
1563
|
+
/** called when a response was received. */
|
1564
|
+
static int http1_on_response(http1_parser_s *parser) {
|
1565
|
+
(void)parser;
|
1566
|
+
return 0;
|
1567
|
+
}
|
1568
|
+
/** called when a request method is parsed. */
|
1569
|
+
static int http1_on_method(http1_parser_s *parser,
|
1570
|
+
char *method,
|
1571
|
+
size_t method_len) {
|
1572
|
+
(void)parser;
|
1573
|
+
http1_test_data[http1_test_pos].result.method = method;
|
1574
|
+
HTTP1_TEST_ASSERT(method_len ==
|
1575
|
+
strlen(http1_test_data[http1_test_pos].expect.method),
|
1576
|
+
"method_len test error for: %s",
|
1577
|
+
http1_test_data[http1_test_pos].test_name);
|
1578
|
+
return 0;
|
1579
|
+
}
|
1580
|
+
/** called when a response status is parsed. the status_str is the string
|
1581
|
+
* without the prefixed numerical status indicator.*/
|
1582
|
+
static int http1_on_status(http1_parser_s *parser,
|
1583
|
+
size_t status,
|
1584
|
+
char *status_str,
|
1585
|
+
size_t len) {
|
1586
|
+
(void)parser;
|
1587
|
+
http1_test_data[http1_test_pos].result.status = status;
|
1588
|
+
http1_test_data[http1_test_pos].result.method = status_str;
|
1589
|
+
HTTP1_TEST_ASSERT(len ==
|
1590
|
+
strlen(http1_test_data[http1_test_pos].expect.method),
|
1591
|
+
"status length test error for: %s",
|
1592
|
+
http1_test_data[http1_test_pos].test_name);
|
1593
|
+
return 0;
|
1594
|
+
}
|
1595
|
+
/** called when a request path (excluding query) is parsed. */
|
1596
|
+
static int http1_on_path(http1_parser_s *parser, char *path, size_t len) {
|
1597
|
+
(void)parser;
|
1598
|
+
http1_test_data[http1_test_pos].result.path = path;
|
1599
|
+
HTTP1_TEST_ASSERT(len == strlen(http1_test_data[http1_test_pos].expect.path),
|
1600
|
+
"path length test error for: %s",
|
1601
|
+
http1_test_data[http1_test_pos].test_name);
|
1602
|
+
return 0;
|
1603
|
+
}
|
1604
|
+
/** called when a request path (excluding query) is parsed. */
|
1605
|
+
static int http1_on_query(http1_parser_s *parser, char *query, size_t len) {
|
1606
|
+
(void)parser;
|
1607
|
+
http1_test_data[http1_test_pos].result.query = query;
|
1608
|
+
HTTP1_TEST_ASSERT(len == strlen(http1_test_data[http1_test_pos].expect.query),
|
1609
|
+
"query length test error for: %s",
|
1610
|
+
http1_test_data[http1_test_pos].test_name);
|
1611
|
+
return 0;
|
1612
|
+
}
|
1613
|
+
/** called when a the HTTP/1.x version is parsed. */
|
1614
|
+
static int http1_on_version(http1_parser_s *parser, char *version, size_t len) {
|
1615
|
+
(void)parser;
|
1616
|
+
http1_test_data[http1_test_pos].result.version = version;
|
1617
|
+
HTTP1_TEST_ASSERT(len ==
|
1618
|
+
strlen(http1_test_data[http1_test_pos].expect.version),
|
1619
|
+
"version length test error for: %s",
|
1620
|
+
http1_test_data[http1_test_pos].test_name);
|
1621
|
+
return 0;
|
1622
|
+
}
|
1623
|
+
/** called when a header is parsed. */
|
1624
|
+
static int http1_on_header(http1_parser_s *parser,
|
1625
|
+
char *name,
|
1626
|
+
size_t name_len,
|
1627
|
+
char *val,
|
1628
|
+
size_t val_len) {
|
1629
|
+
(void)parser;
|
1630
|
+
size_t pos = 0;
|
1631
|
+
while (pos < 12 && http1_test_data[http1_test_pos].result.headers[pos].name)
|
1632
|
+
++pos;
|
1633
|
+
HTTP1_TEST_ASSERT(pos < 12,
|
1634
|
+
"header result overflow for: %s",
|
1635
|
+
http1_test_data[http1_test_pos].test_name);
|
1636
|
+
memcpy(http1_test_temp_buf + http1_test_temp_buf_pos, name, name_len);
|
1637
|
+
name = http1_test_temp_buf + http1_test_temp_buf_pos;
|
1638
|
+
http1_test_temp_buf_pos += name_len;
|
1639
|
+
http1_test_temp_buf[http1_test_temp_buf_pos++] = 0;
|
1640
|
+
memcpy(http1_test_temp_buf + http1_test_temp_buf_pos, val, val_len);
|
1641
|
+
val = http1_test_temp_buf + http1_test_temp_buf_pos;
|
1642
|
+
http1_test_temp_buf_pos += val_len;
|
1643
|
+
http1_test_temp_buf[http1_test_temp_buf_pos++] = 0;
|
1644
|
+
http1_test_data[http1_test_pos].result.headers[pos].name = name;
|
1645
|
+
http1_test_data[http1_test_pos].result.headers[pos].name_len = name_len;
|
1646
|
+
http1_test_data[http1_test_pos].result.headers[pos].val = val;
|
1647
|
+
http1_test_data[http1_test_pos].result.headers[pos].val_len = val_len;
|
1648
|
+
return 0;
|
1649
|
+
}
|
1650
|
+
/** called when a body chunk is parsed. */
|
1651
|
+
static int http1_on_body_chunk(http1_parser_s *parser,
|
1652
|
+
char *data,
|
1653
|
+
size_t data_len) {
|
1654
|
+
(void)parser;
|
1655
|
+
http1_test_data[http1_test_pos]
|
1656
|
+
.result.body[http1_test_data[http1_test_pos].result.body_len] = 0;
|
1657
|
+
HTTP1_TEST_ASSERT(data_len +
|
1658
|
+
http1_test_data[http1_test_pos].result.body_len <=
|
1659
|
+
http1_test_data[http1_test_pos].expect.body_len,
|
1660
|
+
"body overflow for: %s"
|
1661
|
+
"\r\n Expect:\n%s\nGot:\n%s%s\n",
|
1662
|
+
http1_test_data[http1_test_pos].test_name,
|
1663
|
+
http1_test_data[http1_test_pos].expect.body,
|
1664
|
+
http1_test_data[http1_test_pos].result.body,
|
1665
|
+
data);
|
1666
|
+
memcpy(http1_test_data[http1_test_pos].result.body +
|
1667
|
+
http1_test_data[http1_test_pos].result.body_len,
|
1668
|
+
data,
|
1669
|
+
data_len);
|
1670
|
+
http1_test_data[http1_test_pos].result.body_len += data_len;
|
1671
|
+
http1_test_data[http1_test_pos]
|
1672
|
+
.result.body[http1_test_data[http1_test_pos].result.body_len] = 0;
|
1673
|
+
return 0;
|
1674
|
+
}
|
1675
|
+
|
1676
|
+
/** called when a protocol error occurred. */
|
1677
|
+
static int http1_on_error(http1_parser_s *parser) {
|
1678
|
+
(void)parser;
|
1679
|
+
http1_test_data[http1_test_pos].result.status = -1;
|
1680
|
+
return 0;
|
1681
|
+
}
|
1682
|
+
|
1683
|
+
#define HTTP1_TEST_STRING_FIELD(field, i) \
|
1684
|
+
HTTP1_TEST_ASSERT((!http1_test_data[i].expect.field && \
|
1685
|
+
!http1_test_data[i].result.field) || \
|
1686
|
+
http1_test_data[i].expect.field && \
|
1687
|
+
http1_test_data[i].result.field && \
|
1688
|
+
!memcmp(http1_test_data[i].expect.field, \
|
1689
|
+
http1_test_data[i].result.field, \
|
1690
|
+
strlen(http1_test_data[i].expect.field)), \
|
1691
|
+
"string field error for %s - " #field " \n%s\n%s", \
|
1692
|
+
http1_test_data[i].test_name, \
|
1693
|
+
http1_test_data[i].expect.field, \
|
1694
|
+
http1_test_data[i].result.field);
|
1695
|
+
static void http1_parser_test(void) {
|
1696
|
+
http1_test_pos = 0;
|
1697
|
+
struct {
|
1698
|
+
const char *str;
|
1699
|
+
long long num;
|
1700
|
+
long long (*fn)(const uint8_t *, const uint8_t **);
|
1701
|
+
} atol_test[] = {
|
1702
|
+
{
|
1703
|
+
.str = "0",
|
1704
|
+
.num = 0,
|
1705
|
+
.fn = http1_atol,
|
1706
|
+
},
|
1707
|
+
{
|
1708
|
+
.str = "-0",
|
1709
|
+
.num = 0,
|
1710
|
+
.fn = http1_atol,
|
1711
|
+
},
|
1712
|
+
{
|
1713
|
+
.str = "1",
|
1714
|
+
.num = 1,
|
1715
|
+
.fn = http1_atol,
|
1716
|
+
},
|
1717
|
+
{
|
1718
|
+
.str = "-1",
|
1719
|
+
.num = -1,
|
1720
|
+
.fn = http1_atol,
|
1721
|
+
},
|
1722
|
+
{
|
1723
|
+
.str = "123456789",
|
1724
|
+
.num = 123456789,
|
1725
|
+
.fn = http1_atol,
|
1726
|
+
},
|
1727
|
+
{
|
1728
|
+
.str = "-123456789",
|
1729
|
+
.num = -123456789,
|
1730
|
+
.fn = http1_atol,
|
1731
|
+
},
|
1732
|
+
{
|
1733
|
+
.str = "0x0",
|
1734
|
+
.num = 0,
|
1735
|
+
.fn = http1_atol16,
|
1736
|
+
},
|
1737
|
+
{
|
1738
|
+
.str = "-0x0",
|
1739
|
+
.num = 0,
|
1740
|
+
.fn = http1_atol16,
|
1741
|
+
},
|
1742
|
+
{
|
1743
|
+
.str = "-0x1",
|
1744
|
+
.num = -1,
|
1745
|
+
.fn = http1_atol16,
|
1746
|
+
},
|
1747
|
+
{
|
1748
|
+
.str = "-f",
|
1749
|
+
.num = -15,
|
1750
|
+
.fn = http1_atol16,
|
1751
|
+
},
|
1752
|
+
{
|
1753
|
+
.str = "-20",
|
1754
|
+
.num = -32,
|
1755
|
+
.fn = http1_atol16,
|
1756
|
+
},
|
1757
|
+
{
|
1758
|
+
.str = "0xf0EAf9ff",
|
1759
|
+
.num = 0xf0eaf9ff,
|
1760
|
+
.fn = http1_atol16,
|
1761
|
+
},
|
1762
|
+
/* stop marker */
|
1763
|
+
{
|
1764
|
+
.str = NULL,
|
1765
|
+
},
|
1766
|
+
};
|
1767
|
+
fprintf(stderr, "* testing string=>number conversion\n");
|
1768
|
+
for (size_t i = 0; atol_test[i].str; ++i) {
|
1769
|
+
const uint8_t *end;
|
1770
|
+
fprintf(stderr, " %s", atol_test[i].str);
|
1771
|
+
HTTP1_TEST_ASSERT(atol_test[i].fn((const uint8_t *)atol_test[i].str,
|
1772
|
+
&end) == atol_test[i].num,
|
1773
|
+
"\nhttp1_atol error: %s != %lld",
|
1774
|
+
atol_test[i].str,
|
1775
|
+
atol_test[i].num);
|
1776
|
+
HTTP1_TEST_ASSERT((char *)end ==
|
1777
|
+
(atol_test[i].str + strlen(atol_test[i].str)),
|
1778
|
+
"\nhttp1_atol error: didn't end after (%s): %s",
|
1779
|
+
atol_test[i].str,
|
1780
|
+
(char *)end)
|
1781
|
+
}
|
1782
|
+
fprintf(stderr, "\n");
|
1783
|
+
for (unsigned long long i = 1; i; i <<= 1) {
|
1784
|
+
char tmp[128];
|
1785
|
+
size_t tmp_len = sprintf(tmp, "%llx", i);
|
1786
|
+
uint8_t *pos = (uint8_t *)tmp;
|
1787
|
+
HTTP1_TEST_ASSERT(http1_atol16(pos, (const uint8_t **)&pos) ==
|
1788
|
+
(long long)i &&
|
1789
|
+
pos == (uint8_t *)(tmp + tmp_len),
|
1790
|
+
"http1_atol16 roundtrip error.");
|
1791
|
+
}
|
1792
|
+
|
1793
|
+
for (size_t i = 0; http1_test_data[i].request[0]; ++i) {
|
1794
|
+
fprintf(stderr, "* http1 parser test: %s\n", http1_test_data[i].test_name);
|
1795
|
+
/* parse each request / response */
|
1796
|
+
http1_parser_s parser = HTTP1_PARSER_INIT;
|
1797
|
+
char buf[4096];
|
1798
|
+
size_t r = 0;
|
1799
|
+
size_t w = 0;
|
1800
|
+
http1_test_temp_buf_pos = 0;
|
1801
|
+
for (int j = 0; http1_test_data[i].request[j]; ++j) {
|
1802
|
+
memcpy(buf + w,
|
1803
|
+
http1_test_data[i].request[j],
|
1804
|
+
strlen(http1_test_data[i].request[j]));
|
1805
|
+
w += strlen(http1_test_data[i].request[j]);
|
1806
|
+
size_t p = http1_parse(&parser, buf + r, w - r);
|
1807
|
+
r += p;
|
1808
|
+
HTTP1_TEST_ASSERT(r <= w, "parser consumed more than the buffer holds!");
|
1809
|
+
}
|
1810
|
+
/* test each request / response before overwriting the buffer */
|
1811
|
+
HTTP1_TEST_STRING_FIELD(body, i);
|
1812
|
+
HTTP1_TEST_STRING_FIELD(method, i);
|
1813
|
+
HTTP1_TEST_STRING_FIELD(path, i);
|
1814
|
+
HTTP1_TEST_STRING_FIELD(version, i);
|
1815
|
+
r = 0;
|
1816
|
+
while (http1_test_data[i].result.headers[r].name) {
|
1817
|
+
HTTP1_TEST_STRING_FIELD(headers[r].name, i);
|
1818
|
+
HTTP1_TEST_STRING_FIELD(headers[r].val, i);
|
1819
|
+
HTTP1_TEST_ASSERT(http1_test_data[i].expect.headers[r].val_len ==
|
1820
|
+
http1_test_data[i].result.headers[r].val_len &&
|
1821
|
+
http1_test_data[i].expect.headers[r].name_len ==
|
1822
|
+
http1_test_data[i].result.headers[r].name_len,
|
1823
|
+
"--- name / value length error");
|
1824
|
+
++r;
|
1825
|
+
}
|
1826
|
+
HTTP1_TEST_ASSERT(!http1_test_data[i].expect.headers[r].name,
|
1827
|
+
"Expected header missing:\n\t%s: %s",
|
1828
|
+
http1_test_data[i].expect.headers[r].name,
|
1829
|
+
http1_test_data[i].expect.headers[r].val);
|
1830
|
+
/* advance counter */
|
1831
|
+
++http1_test_pos;
|
1832
|
+
}
|
1833
|
+
}
|
1834
|
+
|
873
1835
|
#endif
|