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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +24 -0
  5. data/README.md +2 -2
  6. data/SPEC-PubSub-Draft.md +89 -47
  7. data/SPEC-WebSocket-Draft.md +92 -55
  8. data/examples/async_task.ru +92 -0
  9. data/ext/iodine/extconf.rb +21 -16
  10. data/ext/iodine/fio.c +1108 -162
  11. data/ext/iodine/fio.h +49 -13
  12. data/ext/iodine/fio_cli.c +1 -1
  13. data/ext/iodine/fio_tls_missing.c +8 -0
  14. data/ext/iodine/fio_tls_openssl.c +8 -0
  15. data/ext/iodine/fio_tmpfile.h +13 -1
  16. data/ext/iodine/fiobj_data.c +6 -4
  17. data/ext/iodine/fiobj_data.h +2 -1
  18. data/ext/iodine/fiobj_hash.c +32 -6
  19. data/ext/iodine/fiobj_mustache.c +9 -0
  20. data/ext/iodine/fiobj_numbers.c +86 -8
  21. data/ext/iodine/fiobj_str.c +24 -11
  22. data/ext/iodine/fiobject.c +1 -1
  23. data/ext/iodine/fiobject.h +5 -3
  24. data/ext/iodine/http.c +66 -10
  25. data/ext/iodine/http1.c +2 -1
  26. data/ext/iodine/http1_parser.h +1065 -103
  27. data/ext/iodine/http_internal.c +1 -0
  28. data/ext/iodine/http_internal.h +4 -2
  29. data/ext/iodine/iodine.c +66 -1
  30. data/ext/iodine/iodine.h +3 -0
  31. data/ext/iodine/iodine_caller.c +48 -8
  32. data/ext/iodine/iodine_connection.c +24 -8
  33. data/ext/iodine/iodine_http.c +32 -8
  34. data/ext/iodine/iodine_mustache.c +2 -4
  35. data/ext/iodine/iodine_rack_io.c +21 -0
  36. data/ext/iodine/iodine_tcp.c +14 -0
  37. data/ext/iodine/iodine_tls.c +8 -0
  38. data/ext/iodine/mustache_parser.h +4 -0
  39. data/ext/iodine/redis_engine.c +14 -11
  40. data/ext/iodine/websockets.c +7 -3
  41. data/iodine.gemspec +5 -4
  42. data/lib/iodine/version.rb +1 -1
  43. data/lib/rack/handler/iodine.rb +6 -0
  44. metadata +15 -13
@@ -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. Usualy memchr is better. */
42
+ /** Prefer a custom memchr implementation. Usually memchr is better. */
42
43
  #define FIO_MEMCHAR 0
43
44
  #endif
44
45
 
45
- #ifndef ALLOW_UNALIGNED_MEMORY_ACCESS
46
- /** Peforms some optimizations assuming unaligned memory access is okay. */
47
- #define ALLOW_UNALIGNED_MEMORY_ACCESS 0
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 HTTP1_PARSER_CONVERT_EOL2NUL
51
- #define HTTP1_PARSER_CONVERT_EOL2NUL 0
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 http1_on_method(http1_parser_s *parser, char *method,
100
- size_t method_len);
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 http1_on_query(http1_parser_s *parser, char *query,
109
- size_t query_len);
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 http1_on_body_chunk(http1_parser_s *parser, char *data,
117
- size_t data_len);
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 1
171
+ #define HTTP1_P_FLAG_STATUS_LINE 1
168
172
  #define HTTP1_P_FLAG_HEADER_COMPLETE 2
169
- #define HTTP1_P_FLAG_COMPLETE 4
170
- #define HTTP1_P_FLAG_CLENGTH 8
171
- #define HTTP1_PARSER_BIT_16 16
172
- #define HTTP1_PARSER_BIT_32 32
173
- #define HTTP1_P_FLAG_CHUNKED 64
174
- #define HTTP1_P_FLAG_RESPONSE 128
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, register uint8_t *const limit,
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' && c <= 'Z')
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; ++limit_)
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, uint8_t *end) {
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, http1_atol(start, NULL), (char *)(tmp + 1),
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, uint8_t *end) {
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, (char *)"host", 4,
426
- (char *)host_start, host_end - host_start))
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
- #ifndef HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER
432
- inline /* inline the function of it's short enough */
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, uint8_t *end_name,
437
- uint8_t *start_value, uint8_t *end) {
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' && (start_value[1] | 32) == 'h' &&
449
- (start_value[2] | 32) == 'u' && (start_value[3] | 32) == 'n' &&
450
- (start_value[4] | 32) == 'k' && (start_value[5] | 32) == 'e' &&
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' && (end[(-7 + 1)] | 32) == 'h' &&
464
- (end[(-7 + 2)] | 32) == 'u' && (end[(-7 + 3)] | 32) == 'n' &&
465
- (end[(-7 + 4)] | 32) == 'k' && (end[(-7 + 5)] | 32) == 'e' &&
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
- #ifdef HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER /* RFC diisallows this */
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
- val[val_len] = 0;
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
- (val_len && http1_on_header(parser, (char *)start, (end_name - start),
532
- (char *)val, val_len)))
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, (char *)start, (end_name - start),
539
- (char *)start_value, end - start_value))
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, uint8_t *end_name,
545
- uint8_t *start_value, uint8_t *end) {
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
- *((uint64_t *)(start + 8)) == *((uint64_t *)"-encodin")
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, start, end_name,
576
- start_value, end);
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, (char *)start, (end_name - start),
580
- (char *)start_value, end - start_value))
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
- } http1_trailer_white_list[] = {
600
- {"server-timing", 13}, /* specific for client data... */
601
- {NULL, 0}, /* end of list marker */
621
+ } http1_trailer_allowed_list[] = {
622
+ {(char *)"server-timing", 13}, /* specific for client data... */
623
+ {NULL, 0}, /* end of list marker */
602
624
  };
603
- for (size_t i = 0; http1_trailer_white_list[i].name; ++i) {
604
- if ((long)(end_name - start) == http1_trailer_white_list[i].len &&
605
- HEADER_NAME_IS_EQ((char *)start, http1_trailer_white_list[i].name,
606
- http1_trailer_white_list[i].len)) {
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 white_listed;
635
+ goto allowed_list;
609
636
  }
610
637
  }
611
638
  return 0;
612
- white_listed:
639
+ allowed_list:
613
640
  /* perform callback */
614
- if (http1_on_header(parser, (char *)start, (end_name - start),
615
- (char *)start_value, end - start_value))
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, uint8_t *start,
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] == ' ' || start_value[0] == '\t')) {
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
- parser, start, end_name, start_value, end);
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, size_t length,
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, size_t length,
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' || end[1] != '\n'))
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
- #ifdef HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
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
- while (tmp_len) {
701
- size_t mod = tmp_len / 10;
702
- buf[--buf_len] = '0' + (tmp_len - (mod * 10));
703
- tmp_len = mod;
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, "content-length", 14,
707
- (char *)buf + buf_len, 511 - buf_len)) {
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
- /* FIXME: consume trailing EOL */
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, void *buffer,
741
- size_t length, uint8_t **start) {
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 == '\r' || *start == '\n') {
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
- /* fallthrough */
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