isomorfeus-iodine 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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