hercules/3rdparty/multipart-parser/multipartparser.c
2023-03-13 16:46:36 +03:00

338 lines
11 KiB
C

#include "multipartparser.h"
#include <string.h>
// gcc version (if any) - borrowed from Mana Plus
#ifdef __GNUC__
#define GCC_VERSION (__GNUC__ * 10000 \
+ __GNUC_MINOR__ * 100 \
+ __GNUC_PATCHLEVEL__)
#else
#define GCC_VERSION 0
#endif
// fallthrough attribute only enabled on gcc >= 7.0
#if defined(__GNUC__) && (GCC_VERSION >= 70000)
#define FALLTHROUGH __attribute__ ((fallthrough));
#else // ! defined(__GNUC__) && (GCC_VERSION >= 70000)
#define FALLTHROUGH
#endif // ! defined(__GNUC__) && (GCC_VERSION >= 70000)
#define CR '\r'
#define LF '\n'
#define SP ' '
#define HT '\t'
#define HYPHEN '-'
#define CALLBACK_NOTIFY(NAME) \
if (callbacks->on_##NAME != NULL) { \
if (callbacks->on_##NAME(parser) != 0) \
goto error; \
}
#define CALLBACK_DATA(NAME, P, S) \
if (callbacks->on_##NAME != NULL) { \
if (callbacks->on_##NAME(parser, P, S) != 0) \
goto error; \
}
enum state {
s_preamble,
s_preamble_hy_hy,
s_first_boundary,
s_header_field_start,
s_header_field,
s_header_value_start,
s_header_value,
s_header_value_cr,
s_headers_done,
s_data,
s_data_cr,
s_data_cr_lf,
s_data_cr_lf_hy,
s_data_boundary_start,
s_data_boundary,
s_data_boundary_done,
s_data_boundary_done_cr_lf,
s_data_boundary_done_hy_hy,
s_epilogue,
};
/* Header field name as defined by rfc 2616. Also lowercases them.
* field-name = token
* token = 1*<any CHAR except CTLs or tspecials>
* CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
* tspecials = "(" | ")" | "<" | ">" | "@"
* | "," | ";" | ":" | "\" | DQUOTE
* | "/" | "[" | "]" | "?" | "="
* | "{" | "}" | SP | HT
* DQUOTE = <US-ASCII double-quote mark (34)>
* SP = <US-ASCII SP, space (32)>
* HT = <US-ASCII HT, horizontal-tab (9)>
*/
static const char header_field_chars[256] = {
/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
0, 0, 0, 0, 0, 0, 0, 0,
/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
0, 0, 0, 0, 0, 0, 0, 0,
/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
0, 0, 0, 0, 0, 0, 0, 0,
/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
0, 0, 0, 0, 0, 0, 0, 0,
/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
0, '!', 0, '#', '$', '%', '&', '\'',
/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
0, 0, '*', '+', 0, '-', '.', 0,
/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
'0', '1', '2', '3', '4', '5', '6', '7',
/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
'8', '9', 0, 0, 0, 0, 0, 0,
/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
0, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
'X', 'Y', 'Z', 0, 0, 0, '^', '_',
/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
'x', 'y', 'z', 0, '|', 0, '~', 0
};
void multipartparser_init(multipartparser* parser, const char* boundary)
{
memset(parser, 0, sizeof(*parser));
strncpy(parser->boundary, boundary, MAX_BOUNDARY_SIZE);
parser->boundary[MAX_BOUNDARY_SIZE] = '\x0';
parser->boundary_length = strlen(parser->boundary);
parser->state = s_preamble;
}
void multipartparser_callbacks_init(multipartparser_callbacks* callbacks)
{
memset(callbacks, 0, sizeof(*callbacks));
}
size_t multipartparser_execute(multipartparser* parser,
multipartparser_callbacks* callbacks,
const char* data,
size_t size)
{
const char* mark;
const char* p;
unsigned char c;
for (p = data; p < data + size; ++p) {
c = *p;
reexecute:
switch (parser->state) {
case s_preamble:
if (c == HYPHEN)
parser->state = s_preamble_hy_hy;
// else ignore everything before first boundary
break;
case s_preamble_hy_hy:
if (c == HYPHEN)
parser->state = s_first_boundary;
else
parser->state = s_preamble;
break;
case s_first_boundary:
if (parser->index == parser->boundary_length) {
if (c != CR)
goto error;
parser->index++;
break;
}
if (parser->index == parser->boundary_length + 1) {
if (c != LF)
goto error;
CALLBACK_NOTIFY(body_begin);
CALLBACK_NOTIFY(part_begin);
parser->index = 0;
parser->state = s_header_field_start;
break;
}
if (c == parser->boundary[parser->index]) {
parser->index++;
break;
}
goto error;
case s_header_field_start:
if (c == CR) {
parser->state = s_headers_done;
break;
}
parser->state = s_header_field;
FALLTHROUGH
case s_header_field:
mark = p;
while (p != data + size) {
c = *p;
if (header_field_chars[c] == 0)
break;
++p;
}
if (p > mark) {
CALLBACK_DATA(header_field, mark, p - mark);
}
if (p == data + size) {
break;
}
if (c == ':') {
parser->state = s_header_value_start;
break;
}
goto error;
case s_header_value_start:
if (c == SP || c == HT) {
break;
}
parser->state = s_header_value;
FALLTHROUGH
case s_header_value:
mark = p;
while (p != data + size) {
c = *p;
if (c == CR) {
parser->state = s_header_value_cr;
break;
}
++p;
}
if (p > mark) {
CALLBACK_DATA(header_value, mark, p - mark);
}
break;
case s_header_value_cr:
if (c == LF) {
parser->state = s_header_field_start;
break;
}
goto error;
case s_headers_done:
if (c == LF) {
CALLBACK_NOTIFY(headers_complete);
parser->state = s_data;
break;
}
goto error;
case s_data:
mark = p;
while (p != data + size) {
c = *p;
if (c == CR) {
parser->state = s_data_cr;
break;
}
++p;
}
if (p > mark) {
CALLBACK_DATA(data, mark, p - mark);
}
break;
case s_data_cr:
if (c == LF) {
parser->state = s_data_cr_lf;
break;
}
CALLBACK_DATA(data, "\r", 1);
parser->state = s_data;
goto reexecute;
case s_data_cr_lf:
if (c == HYPHEN) {
parser->state = s_data_cr_lf_hy;
break;
}
CALLBACK_DATA(data, "\r\n", 2);
parser->state = s_data;
goto reexecute;
case s_data_cr_lf_hy:
if (c == HYPHEN) {
parser->state = s_data_boundary_start;
break;
}
CALLBACK_DATA(data, "\r\n-", 3);
parser->state = s_data;
goto reexecute;
case s_data_boundary_start:
parser->index = 0;
parser->state = s_data_boundary;
FALLTHROUGH
case s_data_boundary:
if (parser->index == parser->boundary_length) {
parser->index = 0;
parser->state = s_data_boundary_done;
goto reexecute;
}
if (c == parser->boundary[parser->index]) {
parser->index++;
break;
}
CALLBACK_DATA(data, parser->boundary, parser->index);
parser->state = s_data;
goto reexecute;
case s_data_boundary_done:
if (c == CR) {
parser->state = s_data_boundary_done_cr_lf;
break;
}
if (c == HYPHEN) {
parser->state = s_data_boundary_done_hy_hy;
break;
}
goto error;
case s_data_boundary_done_cr_lf:
if (c == LF) {
CALLBACK_NOTIFY(part_end);
CALLBACK_NOTIFY(part_begin);
parser->state = s_header_field_start;
break;
}
goto error;
case s_data_boundary_done_hy_hy:
if (c == HYPHEN) {
CALLBACK_NOTIFY(part_end);
CALLBACK_NOTIFY(body_end);
parser->state = s_epilogue;
break;
}
goto error;
case s_epilogue:
// Must be ignored according to rfc 1341.
break;
}
}
return size;
error:
return p - data;
}