rage-iodine 1.7.58

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. data/.github/workflows/ruby.yml +42 -0
  4. data/.gitignore +20 -0
  5. data/.rspec +2 -0
  6. data/.yardopts +8 -0
  7. data/CHANGELOG.md +1098 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/LIMITS.md +41 -0
  11. data/README.md +782 -0
  12. data/Rakefile +23 -0
  13. data/SPEC-PubSub-Draft.md +159 -0
  14. data/SPEC-WebSocket-Draft.md +239 -0
  15. data/bin/console +22 -0
  16. data/bin/info.md +353 -0
  17. data/bin/mustache_bench.rb +100 -0
  18. data/bin/poc/Gemfile.lock +23 -0
  19. data/bin/poc/README.md +37 -0
  20. data/bin/poc/config.ru +66 -0
  21. data/bin/poc/gemfile +1 -0
  22. data/bin/poc/www/index.html +57 -0
  23. data/examples/async_task.ru +92 -0
  24. data/examples/bates/README.md +3 -0
  25. data/examples/bates/config.ru +342 -0
  26. data/examples/bates/david+bold.pdf +0 -0
  27. data/examples/bates/public/drop-pdf.png +0 -0
  28. data/examples/bates/public/index.html +600 -0
  29. data/examples/config.ru +59 -0
  30. data/examples/echo.ru +59 -0
  31. data/examples/etag.ru +16 -0
  32. data/examples/hello.ru +29 -0
  33. data/examples/pubsub_engine.ru +81 -0
  34. data/examples/rack3.ru +12 -0
  35. data/examples/redis.ru +70 -0
  36. data/examples/shootout.ru +73 -0
  37. data/examples/sub-protocols.ru +90 -0
  38. data/examples/tcp_client.rb +66 -0
  39. data/examples/x-sendfile.ru +14 -0
  40. data/exe/iodine +280 -0
  41. data/ext/iodine/extconf.rb +110 -0
  42. data/ext/iodine/fio.c +12096 -0
  43. data/ext/iodine/fio.h +6390 -0
  44. data/ext/iodine/fio_cli.c +431 -0
  45. data/ext/iodine/fio_cli.h +189 -0
  46. data/ext/iodine/fio_json_parser.h +687 -0
  47. data/ext/iodine/fio_siphash.c +157 -0
  48. data/ext/iodine/fio_siphash.h +37 -0
  49. data/ext/iodine/fio_tls.h +129 -0
  50. data/ext/iodine/fio_tls_missing.c +649 -0
  51. data/ext/iodine/fio_tls_openssl.c +1056 -0
  52. data/ext/iodine/fio_tmpfile.h +50 -0
  53. data/ext/iodine/fiobj.h +44 -0
  54. data/ext/iodine/fiobj4fio.h +21 -0
  55. data/ext/iodine/fiobj_ary.c +333 -0
  56. data/ext/iodine/fiobj_ary.h +139 -0
  57. data/ext/iodine/fiobj_data.c +1185 -0
  58. data/ext/iodine/fiobj_data.h +167 -0
  59. data/ext/iodine/fiobj_hash.c +409 -0
  60. data/ext/iodine/fiobj_hash.h +176 -0
  61. data/ext/iodine/fiobj_json.c +622 -0
  62. data/ext/iodine/fiobj_json.h +68 -0
  63. data/ext/iodine/fiobj_mem.h +71 -0
  64. data/ext/iodine/fiobj_mustache.c +317 -0
  65. data/ext/iodine/fiobj_mustache.h +62 -0
  66. data/ext/iodine/fiobj_numbers.c +344 -0
  67. data/ext/iodine/fiobj_numbers.h +127 -0
  68. data/ext/iodine/fiobj_str.c +433 -0
  69. data/ext/iodine/fiobj_str.h +172 -0
  70. data/ext/iodine/fiobject.c +620 -0
  71. data/ext/iodine/fiobject.h +654 -0
  72. data/ext/iodine/hpack.h +1923 -0
  73. data/ext/iodine/http.c +2736 -0
  74. data/ext/iodine/http.h +1019 -0
  75. data/ext/iodine/http1.c +825 -0
  76. data/ext/iodine/http1.h +29 -0
  77. data/ext/iodine/http1_parser.h +1835 -0
  78. data/ext/iodine/http_internal.c +1279 -0
  79. data/ext/iodine/http_internal.h +248 -0
  80. data/ext/iodine/http_mime_parser.h +350 -0
  81. data/ext/iodine/iodine.c +1433 -0
  82. data/ext/iodine/iodine.h +64 -0
  83. data/ext/iodine/iodine_caller.c +218 -0
  84. data/ext/iodine/iodine_caller.h +27 -0
  85. data/ext/iodine/iodine_connection.c +941 -0
  86. data/ext/iodine/iodine_connection.h +55 -0
  87. data/ext/iodine/iodine_defer.c +420 -0
  88. data/ext/iodine/iodine_defer.h +6 -0
  89. data/ext/iodine/iodine_fiobj2rb.h +120 -0
  90. data/ext/iodine/iodine_helpers.c +282 -0
  91. data/ext/iodine/iodine_helpers.h +12 -0
  92. data/ext/iodine/iodine_http.c +1280 -0
  93. data/ext/iodine/iodine_http.h +23 -0
  94. data/ext/iodine/iodine_json.c +302 -0
  95. data/ext/iodine/iodine_json.h +6 -0
  96. data/ext/iodine/iodine_mustache.c +567 -0
  97. data/ext/iodine/iodine_mustache.h +6 -0
  98. data/ext/iodine/iodine_pubsub.c +580 -0
  99. data/ext/iodine/iodine_pubsub.h +26 -0
  100. data/ext/iodine/iodine_rack_io.c +273 -0
  101. data/ext/iodine/iodine_rack_io.h +20 -0
  102. data/ext/iodine/iodine_store.c +142 -0
  103. data/ext/iodine/iodine_store.h +20 -0
  104. data/ext/iodine/iodine_tcp.c +346 -0
  105. data/ext/iodine/iodine_tcp.h +13 -0
  106. data/ext/iodine/iodine_tls.c +261 -0
  107. data/ext/iodine/iodine_tls.h +13 -0
  108. data/ext/iodine/mustache_parser.h +1546 -0
  109. data/ext/iodine/redis_engine.c +957 -0
  110. data/ext/iodine/redis_engine.h +79 -0
  111. data/ext/iodine/resp_parser.h +317 -0
  112. data/ext/iodine/scheduler.c +173 -0
  113. data/ext/iodine/scheduler.h +6 -0
  114. data/ext/iodine/websocket_parser.h +506 -0
  115. data/ext/iodine/websockets.c +752 -0
  116. data/ext/iodine/websockets.h +185 -0
  117. data/iodine.gemspec +50 -0
  118. data/lib/iodine/connection.rb +61 -0
  119. data/lib/iodine/json.rb +42 -0
  120. data/lib/iodine/mustache.rb +113 -0
  121. data/lib/iodine/pubsub.rb +55 -0
  122. data/lib/iodine/rack_utils.rb +43 -0
  123. data/lib/iodine/tls.rb +16 -0
  124. data/lib/iodine/version.rb +3 -0
  125. data/lib/iodine.rb +274 -0
  126. data/lib/rack/handler/iodine.rb +33 -0
  127. data/logo.png +0 -0
  128. metadata +284 -0
@@ -0,0 +1,1835 @@
1
+ /*
2
+ Copyright: Boaz Segev, 2017-2020
3
+ License: MIT
4
+
5
+ Feel free to copy, use and enjoy according to the license provided.
6
+ */
7
+
8
+ /**
9
+ This is a callback based parser. It parses the skeleton of the HTTP/1.x protocol
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.
14
+ */
15
+ #include <stddef.h>
16
+ #include <stdint.h>
17
+ #include <stdio.h>
18
+ #include <stdlib.h>
19
+ #include <string.h>
20
+ #include <sys/types.h>
21
+
22
+ /* *****************************************************************************
23
+ Parser Settings
24
+ ***************************************************************************** */
25
+
26
+ #ifndef HTTP_HEADERS_LOWERCASE
27
+ /**
28
+ * When defined, HTTP headers will be converted to lowercase and header
29
+ * searches will be case sensitive.
30
+ *
31
+ * This is highly recommended, required by facil.io and helps with HTTP/2
32
+ * compatibility.
33
+ */
34
+ #define HTTP_HEADERS_LOWERCASE 1
35
+ #endif
36
+
37
+ #ifndef HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
38
+ #define HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING 1
39
+ #endif
40
+
41
+ #ifndef FIO_MEMCHAR
42
+ /** Prefer a custom memchr implementation. Usually memchr is better. */
43
+ #define FIO_MEMCHAR 0
44
+ #endif
45
+
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
49
+ #endif
50
+
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
54
+ #endif
55
+ /* *****************************************************************************
56
+ Parser API
57
+ ***************************************************************************** */
58
+
59
+ /** this struct contains the state of the parser. */
60
+ typedef struct http1_parser_s {
61
+ struct http1_parser_protected_read_only_state_s {
62
+ long long content_length; /* negative values indicate chuncked data state */
63
+ ssize_t read; /* total number of bytes read so far (body only) */
64
+ uint8_t *next; /* the known position for the end of request/response */
65
+ uint8_t reserved; /* for internal use */
66
+ } state;
67
+ } http1_parser_s;
68
+
69
+ #define HTTP1_PARSER_INIT \
70
+ { \
71
+ { 0 } \
72
+ }
73
+
74
+ /**
75
+ * Returns the amount of data actually consumed by the parser.
76
+ *
77
+ * The value 0 indicates there wasn't enough data to be parsed and the same
78
+ * buffer (with more data) should be resubmitted.
79
+ *
80
+ * A value smaller than the buffer size indicates that EITHER a request /
81
+ * response was detected OR that the leftover could not be consumed because more
82
+ * data was required.
83
+ *
84
+ * Simply resubmit the reminder of the data to continue parsing.
85
+ *
86
+ * A request / response callback automatically stops the parsing process,
87
+ * allowing the user to adjust or refresh the state of the data.
88
+ */
89
+ static size_t http1_parse(http1_parser_s *parser, void *buffer, size_t length);
90
+
91
+ /** Returns true if the parsing stopped after a complete request / response. */
92
+ inline static int http1_complete(http1_parser_s *parser);
93
+
94
+ /* *****************************************************************************
95
+ Required Callbacks (MUST be implemented by including file)
96
+ ***************************************************************************** */
97
+ // clang-format off
98
+
99
+ /** called when a request was received. */
100
+ static int http1_on_request(http1_parser_s *parser);
101
+ /** called when a response was received. */
102
+ static int http1_on_response(http1_parser_s *parser);
103
+ /** called when a request method is parsed. */
104
+ static int
105
+ http1_on_method(http1_parser_s *parser, char *method, size_t method_len);
106
+ /** called when a response status is parsed. the status_str is the string
107
+ * without the prefixed numerical status indicator.*/
108
+ static int http1_on_status(http1_parser_s *parser, size_t status, char *status_str, size_t len);
109
+ /** called when a request path (excluding query) is parsed. */
110
+ static int http1_on_path(http1_parser_s *parser, char *path, size_t path_len);
111
+ /** called when a request path (excluding query) is parsed. */
112
+ static int
113
+ http1_on_query(http1_parser_s *parser, char *query, size_t query_len);
114
+ /** called when a the HTTP/1.x version is parsed. */
115
+ static int http1_on_version(http1_parser_s *parser, char *version, size_t len);
116
+ /** called when a header is parsed. */
117
+ static int http1_on_header(http1_parser_s *parser, char *name, size_t name_len, char *data, size_t data_len);
118
+ /** called when a body chunk is parsed. */
119
+ static int
120
+ http1_on_body_chunk(http1_parser_s *parser, char *data, size_t data_len);
121
+ /** called when a protocol error occurred. */
122
+ static int http1_on_error(http1_parser_s *parser);
123
+
124
+ // clang-format on
125
+ /* *****************************************************************************
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+ Implementation Details
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+ ***************************************************************************** */
162
+
163
+ #if HTTP_HEADERS_LOWERCASE
164
+ #define HEADER_NAME_IS_EQ(var_name, const_name, len) \
165
+ (!memcmp((var_name), (const_name), (len)))
166
+ #else
167
+ #define HEADER_NAME_IS_EQ(var_name, const_name, len) \
168
+ (!strncasecmp((var_name), (const_name), (len)))
169
+ #endif
170
+
171
+ #define HTTP1_P_FLAG_STATUS_LINE 1
172
+ #define HTTP1_P_FLAG_HEADER_COMPLETE 2
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
183
+ /* *****************************************************************************
184
+ Seeking for characters in a string
185
+ ***************************************************************************** */
186
+
187
+ #if FIO_MEMCHAR
188
+
189
+ /**
190
+ * This seems to be faster on some systems, especially for smaller distances.
191
+ *
192
+ * On newer systems, `memchr` should be faster.
193
+ */
194
+ static int seek2ch(uint8_t **buffer,
195
+ register uint8_t *const limit,
196
+ const uint8_t c) {
197
+ if (*buffer >= limit)
198
+ return 0;
199
+ if (**buffer == c) {
200
+ return 1;
201
+ }
202
+
203
+ #if !HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED
204
+ /* too short for this mess */
205
+ if ((uintptr_t)limit <= 16 + ((uintptr_t)*buffer & (~(uintptr_t)7)))
206
+ goto finish;
207
+
208
+ /* align memory */
209
+ {
210
+ const uint8_t *alignment =
211
+ (uint8_t *)(((uintptr_t)(*buffer) & (~(uintptr_t)7)) + 8);
212
+ if (*buffer < alignment)
213
+ *buffer += 1; /* we already tested this char */
214
+ if (limit >= alignment) {
215
+ while (*buffer < alignment) {
216
+ if (**buffer == c) {
217
+ return 1;
218
+ }
219
+ *buffer += 1;
220
+ }
221
+ }
222
+ }
223
+ const uint8_t *limit64 = (uint8_t *)((uintptr_t)limit & (~(uintptr_t)7));
224
+ #else
225
+ const uint8_t *limit64 = (uint8_t *)limit - 7;
226
+ #endif
227
+ uint64_t wanted1 = 0x0101010101010101ULL * c;
228
+ for (; *buffer < limit64; *buffer += 8) {
229
+ const uint64_t eq1 = ~((*((uint64_t *)*buffer)) ^ wanted1);
230
+ const uint64_t t0 = (eq1 & 0x7f7f7f7f7f7f7f7fllu) + 0x0101010101010101llu;
231
+ const uint64_t t1 = (eq1 & 0x8080808080808080llu);
232
+ if ((t0 & t1)) {
233
+ break;
234
+ }
235
+ }
236
+ #if !HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED
237
+ finish:
238
+ #endif
239
+ while (*buffer < limit) {
240
+ if (**buffer == c) {
241
+ return 1;
242
+ }
243
+ (*buffer)++;
244
+ }
245
+ return 0;
246
+ }
247
+
248
+ #else
249
+
250
+ /* a helper that seeks any char, converts it to NUL and returns 1 if found. */
251
+ inline static uint8_t seek2ch(uint8_t **pos, uint8_t *const limit, uint8_t ch) {
252
+ /* This is library based alternative that is sometimes slower */
253
+ if (*pos >= limit)
254
+ return 0;
255
+ if (**pos == ch) {
256
+ return 1;
257
+ }
258
+ uint8_t *tmp = memchr(*pos, ch, limit - (*pos));
259
+ if (tmp) {
260
+ *pos = tmp;
261
+ return 1;
262
+ }
263
+ *pos = limit;
264
+ return 0;
265
+ }
266
+
267
+ #endif
268
+
269
+ /* a helper that seeks the EOL, converts it to NUL and returns it's length */
270
+ inline static uint8_t seek2eol(uint8_t **pos, uint8_t *const limit) {
271
+ /* single char lookup using memchr might be better when target is far... */
272
+ if (!seek2ch(pos, limit, '\n'))
273
+ return 0;
274
+ if ((*pos)[-1] == '\r') {
275
+ return 2;
276
+ }
277
+ return 1;
278
+ }
279
+
280
+ /* *****************************************************************************
281
+ Change a letter to lower case (latin only)
282
+ ***************************************************************************** */
283
+
284
+ static uint8_t http_tolower(uint8_t c) {
285
+ if (((c >= 'A') & (c <= 'Z')))
286
+ c |= 32;
287
+ return c;
288
+ }
289
+
290
+ /* *****************************************************************************
291
+ String to Number
292
+ ***************************************************************************** */
293
+
294
+ /** Converts a String to a number using base 10 */
295
+ static long long http1_atol(const uint8_t *buf, const uint8_t **end) {
296
+ register unsigned long long i = 0;
297
+ uint8_t inv = 0;
298
+ while (*buf == ' ' || *buf == '\t' || *buf == '\f')
299
+ ++buf;
300
+ while (*buf == '-' || *buf == '+')
301
+ inv ^= (*(buf++) == '-');
302
+ while (i <= ((((~0ULL) >> 1) / 10)) && *buf >= '0' && *buf <= '9') {
303
+ i = i * 10;
304
+ i += *buf - '0';
305
+ ++buf;
306
+ }
307
+ /* test for overflow */
308
+ if (i >= (~((~0ULL) >> 1)) || (*buf >= '0' && *buf <= '9'))
309
+ i = (~0ULL >> 1);
310
+ if (inv)
311
+ i = 0ULL - i;
312
+ if (end)
313
+ *end = buf;
314
+ return i;
315
+ }
316
+
317
+ /** Converts a String to a number using base 16, overflow limited to 113bytes */
318
+ static long long http1_atol16(const uint8_t *buf, const uint8_t **end) {
319
+ register unsigned long long i = 0;
320
+ uint8_t inv = 0;
321
+ for (int limit_ = 0;
322
+ (*buf == ' ' || *buf == '\t' || *buf == '\f') && limit_ < 32;
323
+ ++limit_)
324
+ ++buf;
325
+ for (int limit_ = 0; (*buf == '-' || *buf == '+') && limit_ < 32; ++limit_)
326
+ inv ^= (*(buf++) == '-');
327
+ if (*buf == '0')
328
+ ++buf;
329
+ if ((*buf | 32) == 'x')
330
+ ++buf;
331
+ for (int limit_ = 0; (*buf == '0') && limit_ < 32; ++limit_)
332
+ ++buf;
333
+ while (!(i & (~((~(0ULL)) >> 4)))) {
334
+ if (*buf >= '0' && *buf <= '9') {
335
+ i <<= 4;
336
+ i |= *buf - '0';
337
+ } else if ((*buf | 32) >= 'a' && (*buf | 32) <= 'f') {
338
+ i <<= 4;
339
+ i |= (*buf | 32) - ('a' - 10);
340
+ } else
341
+ break;
342
+ ++buf;
343
+ }
344
+ if (inv)
345
+ i = 0ULL - i;
346
+ if (end)
347
+ *end = buf;
348
+ return i;
349
+ }
350
+
351
+ /* *****************************************************************************
352
+ HTTP/1.1 parsre stages
353
+ ***************************************************************************** */
354
+
355
+ inline static int http1_consume_response_line(http1_parser_s *parser,
356
+ uint8_t *start,
357
+ uint8_t *end) {
358
+ parser->state.reserved |= HTTP1_P_FLAG_RESPONSE;
359
+ uint8_t *tmp = start;
360
+ if (!seek2ch(&tmp, end, ' '))
361
+ return -1;
362
+ if (http1_on_version(parser, (char *)start, tmp - start))
363
+ return -1;
364
+ tmp = start = tmp + 1;
365
+ if (!seek2ch(&tmp, end, ' '))
366
+ return -1;
367
+ if (http1_on_status(parser,
368
+ http1_atol(start, NULL),
369
+ (char *)(tmp + 1),
370
+ end - tmp))
371
+ return -1;
372
+ return 0;
373
+ }
374
+
375
+ inline static int http1_consume_request_line(http1_parser_s *parser,
376
+ uint8_t *start,
377
+ uint8_t *end) {
378
+ uint8_t *tmp = start;
379
+ uint8_t *host_start = NULL;
380
+ uint8_t *host_end = NULL;
381
+ if (!seek2ch(&tmp, end, ' '))
382
+ return -1;
383
+ if (http1_on_method(parser, (char *)start, tmp - start))
384
+ return -1;
385
+ tmp = start = tmp + 1;
386
+ if (start[0] == 'h' && start[1] == 't' && start[2] == 't' &&
387
+ start[3] == 'p') {
388
+ if (start[4] == ':' && start[5] == '/' && start[6] == '/') {
389
+ /* Request URI is in long form... emulate Host header instead. */
390
+ tmp = host_end = host_start = (start += 7);
391
+ } else if (start[4] == 's' && start[5] == ':' && start[6] == '/' &&
392
+ start[7] == '/') {
393
+ /* Secure request is in long form... emulate Host header instead. */
394
+ tmp = host_end = host_start = (start += 8);
395
+ } else
396
+ goto review_path;
397
+ if (!seek2ch(&tmp, end, ' '))
398
+ return -1;
399
+ *tmp = ' ';
400
+ if (!seek2ch(&host_end, tmp, '/')) {
401
+ if (http1_on_path(parser, (char *)"/", 1))
402
+ return -1;
403
+ goto start_version;
404
+ }
405
+ host_end[0] = '/';
406
+ start = host_end;
407
+ }
408
+ review_path:
409
+ tmp = start;
410
+ if (seek2ch(&tmp, end, '?')) {
411
+ if (http1_on_path(parser, (char *)start, tmp - start))
412
+ return -1;
413
+ tmp = start = tmp + 1;
414
+ if (!seek2ch(&tmp, end, ' '))
415
+ return -1;
416
+ if (tmp - start > 0 && http1_on_query(parser, (char *)start, tmp - start))
417
+ return -1;
418
+ } else {
419
+ tmp = start;
420
+ if (!seek2ch(&tmp, end, ' '))
421
+ return -1;
422
+ if (http1_on_path(parser, (char *)start, tmp - start))
423
+ return -1;
424
+ }
425
+ start_version:
426
+ start = tmp + 1;
427
+ if (start + 5 >= end) /* require "HTTP/" */
428
+ return -1;
429
+ if (http1_on_version(parser, (char *)start, end - start))
430
+ return -1;
431
+ /* */
432
+ if (host_start && http1_on_header(parser,
433
+ (char *)"host",
434
+ 4,
435
+ (char *)host_start,
436
+ host_end - host_start))
437
+ return -1;
438
+ return 0;
439
+ }
440
+
441
+ #if !HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER
442
+ inline /* inline the function if it's short enough */
443
+ #endif
444
+ static int
445
+ http1_consume_header_transfer_encoding(http1_parser_s *parser,
446
+ uint8_t *start,
447
+ uint8_t *end_name,
448
+ uint8_t *start_value,
449
+ uint8_t *end) {
450
+ /* this removes the `chunked` marker and prepares to "unchunk" the data */
451
+ while (start_value < end && (end[-1] == ',' || end[-1] == ' '))
452
+ --end;
453
+ if ((end - start_value) == 7 &&
454
+ #if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED
455
+ (((uint32_t *)(start_value))[0] | 0x20202020) ==
456
+ ((uint32_t *)"chun")[0] &&
457
+ (((uint32_t *)(start_value + 3))[0] | 0x20202020) ==
458
+ ((uint32_t *)"nked")[0]
459
+ #else
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' &
463
+ (start_value[6] | 32) == 'd')
464
+ #endif
465
+ ) {
466
+ /* simple case,only `chunked` as a value */
467
+ parser->state.reserved |= HTTP1_P_FLAG_CHUNKED;
468
+ parser->state.content_length = 0;
469
+ start_value += 7;
470
+ while (start_value < end && (*start_value == ',' || *start_value == ' '))
471
+ ++start_value;
472
+ if (!(end - start_value))
473
+ return 0;
474
+ } else if ((end - start_value) > 7 &&
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' &
478
+ (end[(-7 + 6)] | 32) == 'd')) {
479
+ /* simple case,`chunked` at the end of list (RFC required) */
480
+ parser->state.reserved |= HTTP1_P_FLAG_CHUNKED;
481
+ parser->state.content_length = 0;
482
+ end -= 7;
483
+ while (start_value < end && (end[-1] == ',' || end[-1] == ' '))
484
+ --end;
485
+ if (!(end - start_value))
486
+ return 0;
487
+ }
488
+ #if HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER /* RFC disallows this */
489
+ else if ((end - start_value) > 7 && (end - start_value) < 256) {
490
+ /* complex case, `the, chunked, marker, is in the middle of list */
491
+ uint8_t val[256];
492
+ size_t val_len = 0;
493
+ while (start_value < end && val_len < 256) {
494
+ if ((end - start_value) >= 7) {
495
+ if (
496
+ #if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED
497
+ (((uint32_t *)(start_value))[0] | 0x20202020) ==
498
+ ((uint32_t *)"chun")[0] &&
499
+ (((uint32_t *)(start_value + 3))[0] | 0x20202020) ==
500
+ ((uint32_t *)"nked")[0]
501
+ #else
502
+ ((start_value[0] | 32) == 'c' && (start_value[1] | 32) == 'h' &&
503
+ (start_value[2] | 32) == 'u' && (start_value[3] | 32) == 'n' &&
504
+ (start_value[4] | 32) == 'k' && (start_value[5] | 32) == 'e' &&
505
+ (start_value[6] | 32) == 'd')
506
+ #endif
507
+
508
+ ) {
509
+ parser->state.reserved |= HTTP1_P_FLAG_CHUNKED;
510
+ parser->state.content_length = 0;
511
+ start_value += 7;
512
+ /* skip comma / white space */
513
+ while (start_value < end &&
514
+ (*start_value == ',' || *start_value == ' '))
515
+ ++start_value;
516
+ continue;
517
+ }
518
+ }
519
+ /* copy value */
520
+ while (start_value < end && val_len < 256 && start_value[0] != ',') {
521
+ val[val_len++] = *start_value;
522
+ ++start_value;
523
+ }
524
+ /* copy comma */
525
+ if (start_value[0] == ',' && val_len < 256) {
526
+ val[val_len++] = *start_value;
527
+ ++start_value;
528
+ }
529
+ /* skip spaces */
530
+ while (start_value < end && start_value[0] == ' ') {
531
+ ++start_value;
532
+ }
533
+ }
534
+ if (val_len < 256) {
535
+ while (start_value < end && val_len < 256) {
536
+ val[val_len++] = *start_value;
537
+ ++start_value;
538
+ }
539
+ if (val_len < 255)
540
+ val[val_len] = 0;
541
+ }
542
+ /* perform callback with `val` or indicate error */
543
+ if (val_len == 256 || (val_len && http1_on_header(parser,
544
+ (char *)start,
545
+ (end_name - start),
546
+ (char *)val,
547
+ val_len)))
548
+ return -1;
549
+ return 0;
550
+ }
551
+ #endif /* HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER */
552
+ /* perform callback */
553
+ if (http1_on_header(parser,
554
+ (char *)start,
555
+ (end_name - start),
556
+ (char *)start_value,
557
+ end - start_value))
558
+ return -1;
559
+ return 0;
560
+ }
561
+ inline static int http1_consume_header_top(http1_parser_s *parser,
562
+ uint8_t *start,
563
+ uint8_t *end_name,
564
+ uint8_t *start_value,
565
+ uint8_t *end) {
566
+ if ((end_name - start) == 14 &&
567
+ #if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED && HTTP_HEADERS_LOWERCASE
568
+ *((uint64_t *)start) == *((uint64_t *)"content-") &&
569
+ *((uint64_t *)(start + 6)) == *((uint64_t *)"t-length")
570
+ #else
571
+ HEADER_NAME_IS_EQ((char *)start, "content-length", 14)
572
+ #endif
573
+ ) {
574
+ /* handle the special `content-length` header */
575
+ if ((parser->state.reserved & HTTP1_P_FLAG_CHUNKED))
576
+ return 0; /* ignore if `chunked` */
577
+ long long old_clen = parser->state.content_length;
578
+ parser->state.content_length = http1_atol(start_value, NULL);
579
+ if ((parser->state.reserved & HTTP1_P_FLAG_CLENGTH) &&
580
+ old_clen != parser->state.content_length) {
581
+ /* content-length header repeated with conflict */
582
+ return -1;
583
+ }
584
+ parser->state.reserved |= HTTP1_P_FLAG_CLENGTH;
585
+ } else if ((end_name - start) == 17 && (end - start_value) >= 7 &&
586
+ !parser->state.content_length &&
587
+ #if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED && HTTP_HEADERS_LOWERCASE
588
+ ((*((uint64_t *)start) == *((uint64_t *)"transfer")) &
589
+ ((*((uint64_t *)(start + 8)) == *((uint64_t *)"-encodin")) &
590
+ (start[16] == 'g')))
591
+ #else
592
+ HEADER_NAME_IS_EQ((char *)start, "transfer-encoding", 17)
593
+ #endif
594
+ ) {
595
+ /* handle the special `transfer-encoding: chunked` header */
596
+ return http1_consume_header_transfer_encoding(parser,
597
+ start,
598
+ end_name,
599
+ start_value,
600
+ end);
601
+ }
602
+ /* perform callback */
603
+ if (http1_on_header(parser,
604
+ (char *)start,
605
+ (end_name - start),
606
+ (char *)start_value,
607
+ end - start_value))
608
+ return -1;
609
+ return 0;
610
+ }
611
+
612
+ inline static int http1_consume_header_trailer(http1_parser_s *parser,
613
+ uint8_t *start,
614
+ uint8_t *end_name,
615
+ uint8_t *start_value,
616
+ uint8_t *end) {
617
+ /* white listed trailer names */
618
+ const struct {
619
+ char *name;
620
+ long len;
621
+ } http1_trailer_allowed_list[] = {
622
+ {(char *)"server-timing", 13}, /* specific for client data... */
623
+ {NULL, 0}, /* end of list marker */
624
+ };
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)) {
634
+ /* header disallowed here */
635
+ goto allowed_list;
636
+ }
637
+ }
638
+ return 0;
639
+ allowed_list:
640
+ /* perform callback */
641
+ if (http1_on_header(parser,
642
+ (char *)start,
643
+ (end_name - start),
644
+ (char *)start_value,
645
+ end - start_value))
646
+ return -1;
647
+ return 0;
648
+ }
649
+
650
+ inline static int http1_consume_header(http1_parser_s *parser,
651
+ uint8_t *start,
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};
665
+ uint8_t *end_name = start;
666
+ /* divide header name from data */
667
+ if (!seek2ch(&end_name, end, ':'))
668
+ return -1;
669
+ if (end_name[-1] == ' ' || end_name[-1] == '\t')
670
+ return -1;
671
+ if (forbidden_name_chars[start[0] & 0xFF])
672
+ return -1;
673
+ #if HTTP_HEADERS_LOWERCASE
674
+ for (uint8_t *t = start; t < end_name; t++)
675
+ *t = http_tolower(*t);
676
+ #endif
677
+ uint8_t *start_value = end_name + 1;
678
+ // clear away leading white space from value.
679
+ while (start_value < end &&
680
+ ((start_value[0] == ' ') | (start_value[0] == '\t'))) {
681
+ start_value++;
682
+ };
683
+ // clear away added white space from value.
684
+ while (start_value < end && ((end[0] == ' ') | (end[0] == '\t'))) {
685
+ end++;
686
+ };
687
+ return (parser->state.read ? http1_consume_header_trailer
688
+ : http1_consume_header_top)(parser,
689
+ start,
690
+ end_name,
691
+ start_value,
692
+ end);
693
+ }
694
+
695
+ /* *****************************************************************************
696
+ HTTP/1.1 Body handling
697
+ ***************************************************************************** */
698
+
699
+ inline static int http1_consume_body_streamed(http1_parser_s *parser,
700
+ void *buffer,
701
+ size_t length,
702
+ uint8_t **start) {
703
+ uint8_t *end = *start + parser->state.content_length - parser->state.read;
704
+ uint8_t *const stop = ((uint8_t *)buffer) + length;
705
+ if (end > stop)
706
+ end = stop;
707
+ if (end > *start &&
708
+ http1_on_body_chunk(parser, (char *)(*start), end - *start))
709
+ return -1;
710
+ parser->state.read += (end - *start);
711
+ *start = end;
712
+ if (parser->state.content_length <= parser->state.read)
713
+ parser->state.reserved |= HTTP1_P_FLAG_COMPLETE;
714
+ return 0;
715
+ }
716
+
717
+ inline static int http1_consume_body_chunked(http1_parser_s *parser,
718
+ void *buffer,
719
+ size_t length,
720
+ uint8_t **start) {
721
+ uint8_t *const stop = ((uint8_t *)buffer) + length;
722
+ uint8_t *end = *start;
723
+ while (*start < stop) {
724
+ if (parser->state.content_length == 0) {
725
+ if (end + 2 >= stop)
726
+ return 0;
727
+ if ((end[0] == '\r' && end[1] == '\n')) {
728
+ /* remove tailing EOL that wasn't processed and retest */
729
+ end += 2;
730
+ *start = end;
731
+ if (end + 2 >= stop)
732
+ return 0;
733
+ }
734
+ long long chunk_len = http1_atol16(end, (const uint8_t **)&end);
735
+ if (end + 2 > stop) /* overflowed? */
736
+ return 0;
737
+ if ((end[0] != '\r') | (end[1] != '\n') | (chunk_len < 0))
738
+ return -1; /* required EOL after content length */
739
+ end += 2;
740
+
741
+ parser->state.content_length = 0 - chunk_len;
742
+ *start = end;
743
+ if (parser->state.content_length == 0) {
744
+ /* all chunked data was parsed */
745
+ /* update content-length */
746
+ parser->state.content_length = parser->state.read;
747
+ #if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
748
+ { /* add virtual header ... ? */
749
+ char buf[512];
750
+ size_t buf_len = 512;
751
+ size_t tmp_len = parser->state.read;
752
+ buf[--buf_len] = 0;
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';
761
+ }
762
+ if (!(parser->state.reserved & HTTP1_P_FLAG_CLENGTH) &&
763
+ http1_on_header(parser,
764
+ (char *)"content-length",
765
+ 14,
766
+ (char *)buf + buf_len,
767
+ 511 - buf_len)) {
768
+ return -1;
769
+ }
770
+ }
771
+ #endif
772
+ /* consume trailing EOL */
773
+ if (*start + 2 <= stop && (start[0][0] == '\r' || start[0][0] == '\n'))
774
+ *start += 1 + (start[0][1] == '\r' || start[0][1] == '\n');
775
+ else {
776
+ /* remove the "headers complete" and "trailer" flags */
777
+ parser->state.reserved =
778
+ HTTP1_P_FLAG_STATUS_LINE | HTTP1_P_FLAG_CLENGTH;
779
+ return -2;
780
+ }
781
+ /* the parsing complete flag */
782
+ parser->state.reserved |= HTTP1_P_FLAG_COMPLETE;
783
+ return 0;
784
+ }
785
+ }
786
+ end = *start + (0 - parser->state.content_length);
787
+ if (end > stop)
788
+ end = stop;
789
+ if (end > *start &&
790
+ http1_on_body_chunk(parser, (char *)(*start), end - *start)) {
791
+ return -1;
792
+ }
793
+ parser->state.read += (end - *start);
794
+ parser->state.content_length += (end - *start);
795
+ *start = end;
796
+ }
797
+ return 0;
798
+ }
799
+
800
+ inline static int http1_consume_body(http1_parser_s *parser,
801
+ void *buffer,
802
+ size_t length,
803
+ uint8_t **start) {
804
+ if (parser->state.content_length > 0 &&
805
+ parser->state.content_length > parser->state.read) {
806
+ /* normal, streamed data */
807
+ return http1_consume_body_streamed(parser, buffer, length, start);
808
+ } else if (parser->state.content_length <= 0 &&
809
+ (parser->state.reserved & HTTP1_P_FLAG_CHUNKED)) {
810
+ /* chuncked encoding */
811
+ return http1_consume_body_chunked(parser, buffer, length, start);
812
+ } else {
813
+ /* nothing to do - parsing complete */
814
+ parser->state.reserved |= HTTP1_P_FLAG_COMPLETE;
815
+ }
816
+ return 0;
817
+ }
818
+
819
+ /* *****************************************************************************
820
+ HTTP/1.1 parsre function
821
+ ***************************************************************************** */
822
+ #if DEBUG
823
+ #include <assert.h>
824
+ #define HTTP1_ASSERT assert
825
+ #else
826
+ #define HTTP1_ASSERT(...)
827
+ #endif
828
+
829
+ /**
830
+ * Returns the amount of data actually consumed by the parser.
831
+ *
832
+ * The value 0 indicates there wasn't enough data to be parsed and the same
833
+ * buffer (with more data) should be resubmitted.
834
+ *
835
+ * A value smaller than the buffer size indicates that EITHER a request /
836
+ * response was detected OR that the leftover could not be consumed because more
837
+ * data was required.
838
+ *
839
+ * Simply resubmit the reminder of the data to continue parsing.
840
+ *
841
+ * A request / response callback automatically stops the parsing process,
842
+ * allowing the user to adjust or refresh the state of the data.
843
+ */
844
+ static size_t http1_parse(http1_parser_s *parser, void *buffer, size_t length) {
845
+ if (!length)
846
+ return 0;
847
+ HTTP1_ASSERT(parser && buffer);
848
+ parser->state.next = NULL;
849
+ uint8_t *start = (uint8_t *)buffer;
850
+ uint8_t *end = start;
851
+ uint8_t *const stop = start + length;
852
+ uint8_t eol_len = 0;
853
+ #define HTTP1_CONSUMED ((size_t)((uintptr_t)start - (uintptr_t)buffer))
854
+
855
+ re_eval:
856
+ switch ((parser->state.reserved & 7)) {
857
+
858
+ case 0: /* request / response line */
859
+ /* clear out any leading white space */
860
+ while ((start < stop) &&
861
+ (*start == '\r' || *start == '\n' || *start == ' ' || *start == 0)) {
862
+ ++start;
863
+ }
864
+ end = start;
865
+ /* make sure the whole line is available*/
866
+ if (!(eol_len = seek2eol(&end, stop)))
867
+ return HTTP1_CONSUMED;
868
+
869
+ if (start[0] == 'H' && start[1] == 'T' && start[2] == 'T' &&
870
+ start[3] == 'P') {
871
+ /* HTTP response */
872
+ if (http1_consume_response_line(parser, start, end - eol_len + 1))
873
+ goto error;
874
+ } else if (http_tolower(start[0]) >= 'a' && http_tolower(start[0]) <= 'z') {
875
+ /* HTTP request */
876
+ if (http1_consume_request_line(parser, start, end - eol_len + 1))
877
+ goto error;
878
+ } else
879
+ goto error;
880
+ end = start = end + 1;
881
+ parser->state.reserved |= HTTP1_P_FLAG_STATUS_LINE;
882
+
883
+ /* fallthrough */
884
+ case 1: /* headers */
885
+ do {
886
+ if (start >= stop)
887
+ return HTTP1_CONSUMED; /* buffer ended on header line */
888
+ if ((*start == '\n') |
889
+ (start + 1 < stop && ((start[0] == '\r') & (start[1] == '\n')))) {
890
+ goto finished_headers; /* empty line, end of headers */
891
+ }
892
+ end = start;
893
+ if (!(eol_len = seek2eol(&end, stop)))
894
+ return HTTP1_CONSUMED;
895
+ if (http1_consume_header(parser, start, end - eol_len + 1))
896
+ goto error;
897
+ end = start = end + 1;
898
+ } while ((parser->state.reserved & HTTP1_P_FLAG_HEADER_COMPLETE) == 0);
899
+ finished_headers:
900
+ ++start;
901
+ if (*start == '\n')
902
+ ++start;
903
+ end = start;
904
+ parser->state.reserved |= HTTP1_P_FLAG_HEADER_COMPLETE;
905
+ /* fall through */
906
+ case (HTTP1_P_FLAG_HEADER_COMPLETE | HTTP1_P_FLAG_STATUS_LINE):
907
+ /* request body */
908
+ {
909
+ int t3 = http1_consume_body(parser, buffer, length, &start);
910
+ switch (t3) {
911
+ case -1:
912
+ goto error;
913
+ case -2:
914
+ goto re_eval;
915
+ }
916
+ break;
917
+ }
918
+ }
919
+ /* are we done ? */
920
+ if (parser->state.reserved & HTTP1_P_FLAG_COMPLETE) {
921
+ parser->state.next = start;
922
+ if (((parser->state.reserved & HTTP1_P_FLAG_RESPONSE)
923
+ ? http1_on_response
924
+ : http1_on_request)(parser))
925
+ goto error;
926
+ parser->state = (struct http1_parser_protected_read_only_state_s){0};
927
+ }
928
+ return HTTP1_CONSUMED;
929
+ error:
930
+ http1_on_error(parser);
931
+ parser->state = (struct http1_parser_protected_read_only_state_s){0};
932
+ return length;
933
+ #undef HTTP1_CONSUMED
934
+ }
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
+
1835
+ #endif