static int http_transfer_chunked_parse_size (struct http_transfer_chunked_istream *tcstream) { uoff_t size = 0, prev; /* chunk-size = 1*HEXDIG */ while (tcstream->cur < tcstream->end) { prev = tcstream->chunk_size; if (*tcstream->cur >= '0' && *tcstream->cur <= '9') size = *tcstream->cur-'0'; else if (*tcstream->cur >= 'A' && *tcstream->cur <= 'F') size = *tcstream->cur-'A' + 10; else if (*tcstream->cur >= 'a' && *tcstream->cur <= 'f') size = *tcstream->cur-'a' + 10; else { if (tcstream->parsed_chars == 0) { tcstream->error = t_strdup_printf( "Expected chunk size digit, but found %s", _chr_sanitize(*tcstream->cur)); return -1; } tcstream->parsed_chars = 0; return 1; } tcstream->chunk_size <<= 4; tcstream->chunk_size += size; if (tcstream->chunk_size < prev) { tcstream->error = "Chunk size exceeds integer limit"; return -1; } tcstream->parsed_chars++; tcstream->cur++; } return 0; }
static int http_header_parse(struct http_header_parser *parser) { int ret; /* RFC 7230, Section 3.2: Header Fields 'header' = *( header-field CRLF ) CRLF ; Actually part of HTTP-message syntax header-field = field-name ":" OWS field-value OWS field-name = token field-value = *( field-content / obs-fold ) field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] field-vchar = VCHAR / obs-text obs-fold = CRLF 1*( SP / HTAB ) ; obsolete line folding ; see Section 3.2.4 */ for (;;) { switch (parser->state) { case HTTP_HEADER_PARSE_STATE_INIT: buffer_set_used_size(parser->value_buf, 0); str_truncate(parser->name, 0); if (*parser->cur == '\r') { /* last CRLF */ parser->cur++; parser->state = HTTP_HEADER_PARSE_STATE_EOH; if (parser->cur == parser->end) return 0; break; } else if (*parser->cur == '\n') { /* last LF */ parser->state = HTTP_HEADER_PARSE_STATE_EOH; break; } /* next line */ parser->state = HTTP_HEADER_PARSE_STATE_NAME; /* fall through */ case HTTP_HEADER_PARSE_STATE_NAME: if ((ret=http_header_parse_name(parser)) <= 0) return ret; parser->state = HTTP_HEADER_PARSE_STATE_COLON; /* fall through */ case HTTP_HEADER_PARSE_STATE_COLON: if (*parser->cur != ':') { parser->error = t_strdup_printf ("Expected ':' after header field name '%s', but found %s", str_sanitize(str_c(parser->name),64), _chr_sanitize(*parser->cur)); return -1; } parser->cur++; if (str_len(parser->name) == 0) { parser->error = "Empty header field name"; return -1; } if (++parser->field_count > parser->limits.max_fields) { parser->error = "Excessive number of header fields"; return -1; } parser->state = HTTP_HEADER_PARSE_STATE_OWS; /* fall through */ case HTTP_HEADER_PARSE_STATE_OWS: if ((ret=http_header_parse_ows(parser)) <= 0) return ret; parser->state = HTTP_HEADER_PARSE_STATE_CONTENT; /* fall through */ case HTTP_HEADER_PARSE_STATE_CONTENT: if ((ret=http_header_parse_content(parser)) <= 0) return ret; parser->state = HTTP_HEADER_PARSE_STATE_CR; /* fall through */ case HTTP_HEADER_PARSE_STATE_CR: if (*parser->cur == '\r') { parser->cur++; } else if (*parser->cur != '\n') { parser->error = t_strdup_printf ("Invalid character %s in content of header field '%s'", _chr_sanitize(*parser->cur), str_sanitize(str_c(parser->name),64)); return -1; } parser->state = HTTP_HEADER_PARSE_STATE_LF; if (parser->cur == parser->end) return 0; /* fall through */ case HTTP_HEADER_PARSE_STATE_LF: if (*parser->cur != '\n') { parser->error = t_strdup_printf ("Expected LF after CR at end of header field '%s', but found %s", str_sanitize(str_c(parser->name),64), _chr_sanitize(*parser->cur)); return -1; } parser->cur++; parser->state = HTTP_HEADER_PARSE_STATE_NEW_LINE; if (parser->cur == parser->end) return 0; /* fall through */ case HTTP_HEADER_PARSE_STATE_NEW_LINE: if (*parser->cur == ' ' || *parser->cur == '\t') { /* obs-fold */ buffer_append_c(parser->value_buf, ' '); parser->state = HTTP_HEADER_PARSE_STATE_OWS; break; } /* next header line */ parser->state = HTTP_HEADER_PARSE_STATE_INIT; return 1; case HTTP_HEADER_PARSE_STATE_EOH: if (*parser->cur != '\n') { parser->error = t_strdup_printf ("Encountered stray CR at beginning of header line, followed by %s", _chr_sanitize(*parser->cur)); return -1; } /* header fully parsed */ parser->cur++; return 1; default: i_unreached(); } } i_unreached(); return -1; }
static int http_request_parse(struct http_request_parser *parser, pool_t pool) { struct http_message_parser *_parser = &parser->parser; int ret; /* request-line = method SP request-target SP HTTP-version CRLF */ for (;;) { switch (parser->state) { case HTTP_REQUEST_PARSE_STATE_INIT: http_request_parser_restart(parser, pool); parser->state = HTTP_REQUEST_PARSE_STATE_SKIP_LINE; if (_parser->cur == _parser->end) return 0; case HTTP_REQUEST_PARSE_STATE_SKIP_LINE: if (*_parser->cur == '\r' || *_parser->cur == '\n') { if (parser->skipping_line) { /* second extra CRLF; not allowed */ parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST; _parser->error = "Empty request line"; return -1; } /* HTTP/1.0 client sent one extra CRLF after body. ignore it. */ parser->skipping_line = TRUE; parser->state = HTTP_REQUEST_PARSE_STATE_CR; break; } parser->state = HTTP_REQUEST_PARSE_STATE_METHOD; parser->skipping_line = FALSE; /* fall through */ case HTTP_REQUEST_PARSE_STATE_METHOD: if ((ret=http_request_parse_method(parser)) <= 0) return ret; parser->state = HTTP_REQUEST_PARSE_STATE_SP1; if (_parser->cur == _parser->end) return 0; /* fall through */ case HTTP_REQUEST_PARSE_STATE_SP1: if (*_parser->cur != ' ') { parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST; _parser->error = t_strdup_printf ("Unexpected character %s in request method", _chr_sanitize(*_parser->cur)); return -1; } _parser->cur++; parser->state = HTTP_REQUEST_PARSE_STATE_TARGET; if (_parser->cur >= _parser->end) return 0; /* fall through */ case HTTP_REQUEST_PARSE_STATE_TARGET: if ((ret=http_request_parse_target(parser)) <= 0) return ret; parser->state = HTTP_REQUEST_PARSE_STATE_SP2; if (_parser->cur == _parser->end) return 0; /* fall through */ case HTTP_REQUEST_PARSE_STATE_SP2: if (*_parser->cur != ' ') { parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST; _parser->error = t_strdup_printf ("Unexpected character %s in request target", _chr_sanitize(*_parser->cur)); return -1; } _parser->cur++; parser->state = HTTP_REQUEST_PARSE_STATE_VERSION; if (_parser->cur >= _parser->end) return 0; /* fall through */ case HTTP_REQUEST_PARSE_STATE_VERSION: if ((ret=http_message_parse_version(&parser->parser)) <= 0) { if (ret < 0) { parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST; _parser->error = "Invalid HTTP version in request"; } return ret; } parser->state = HTTP_REQUEST_PARSE_STATE_CR; if (_parser->cur == _parser->end) return 0; /* fall through */ case HTTP_REQUEST_PARSE_STATE_CR: if (*_parser->cur == '\r') _parser->cur++; parser->state = HTTP_REQUEST_PARSE_STATE_LF; if (_parser->cur == _parser->end) return 0; /* fall through */ case HTTP_REQUEST_PARSE_STATE_LF: if (*_parser->cur != '\n') { parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST; _parser->error = t_strdup_printf ("Unexpected character %s at end of request line", _chr_sanitize(*_parser->cur)); return -1; } _parser->cur++; if (!parser->skipping_line) { parser->state = HTTP_REQUEST_PARSE_STATE_HEADER; return 1; } parser->state = HTTP_REQUEST_PARSE_STATE_INIT; break; case HTTP_REQUEST_PARSE_STATE_HEADER: default: i_unreached(); } } i_unreached(); return -1; }
static int http_transfer_chunked_parse(struct http_transfer_chunked_istream *tcstream) { int ret; /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21; Section 4.1: chunked-body = *chunk last-chunk trailer-part CRLF chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF chunk-size = 1*HEXDIG last-chunk = 1*("0") [ chunk-ext ] CRLF chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) chunk-ext-name = token chunk-ext-val = token / quoted-str-nf chunk-data = 1*OCTET ; a sequence of chunk-size octets trailer-part = *( header-field CRLF ) quoted-str-nf = DQUOTE *( qdtext-nf / quoted-pair ) DQUOTE ; like quoted-string, but disallowing line folding qdtext-nf = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) */ for (;;) { switch (tcstream->state) { case HTTP_CHUNKED_PARSE_STATE_INIT: tcstream->chunk_size = 0; tcstream->chunk_pos = 0; tcstream->parsed_chars = 0; tcstream->state = HTTP_CHUNKED_PARSE_STATE_SIZE; /* fall through */ case HTTP_CHUNKED_PARSE_STATE_SIZE: if ((ret=http_transfer_chunked_parse_size(tcstream)) <= 0) return ret; tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT; /* fall through */ case HTTP_CHUNKED_PARSE_STATE_EXT: if (*tcstream->cur != ';') { tcstream->state = HTTP_CHUNKED_PARSE_STATE_CR; break; } /* chunk-ext */ tcstream->cur++; tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_NAME; if (tcstream->cur >= tcstream->end) return 0; /* fall through */ case HTTP_CHUNKED_PARSE_STATE_EXT_NAME: /* chunk-ext-name = token */ if ((ret=http_transfer_chunked_skip_token(tcstream)) <= 0) { if (ret < 0) tcstream->error = "Invalid chunked extension name"; return ret; } tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_EQ; /* fall through */ case HTTP_CHUNKED_PARSE_STATE_EXT_EQ: if (*tcstream->cur != '=') { tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT; break; } tcstream->cur++; tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE; if (tcstream->cur >= tcstream->end) return 0; /* fall through */ case HTTP_CHUNKED_PARSE_STATE_EXT_VALUE: /* chunk-ext-val = token / quoted-str-nf */ if (*tcstream->cur != '"') { tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_TOKEN; break; } tcstream->cur++; tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_STRING; if (tcstream->cur >= tcstream->end) return 0; /* fall through */ case HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_STRING: for (;;) { if (*tcstream->cur == '"') { tcstream->cur++; tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT; if (tcstream->cur >= tcstream->end) return 0; break; } else if ((ret=http_transfer_chunked_skip_qdtext(tcstream)) <= 0) { if (ret < 0) tcstream->error = "Invalid chunked extension value"; return ret; } else if (*tcstream->cur == '\\') { tcstream->cur++; tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_ESCAPE; if (tcstream->cur >= tcstream->end) return 0; break; } else { tcstream->error = t_strdup_printf( "Invalid character %s in chunked extension value string", _chr_sanitize(*tcstream->cur)); return -1; } } break; case HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_ESCAPE: /* ( HTAB / SP / VCHAR / obs-text ) */ if (!http_char_is_text(*tcstream->cur)) { tcstream->error = t_strdup_printf( "Escaped invalid character %s in chunked extension value string", _chr_sanitize(*tcstream->cur)); return -1; } tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_STRING; if (tcstream->cur >= tcstream->end) return 0; break; case HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_TOKEN: if ((ret=http_transfer_chunked_skip_token(tcstream)) <= 0) { if (ret < 0) tcstream->error = "Invalid chunked extension value"; return ret; } tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT; break; case HTTP_CHUNKED_PARSE_STATE_CR: tcstream->state = HTTP_CHUNKED_PARSE_STATE_LF; if (*tcstream->cur == '\r') { tcstream->cur++; if (tcstream->cur >= tcstream->end) return 0; } /* fall through */ case HTTP_CHUNKED_PARSE_STATE_LF: if (*tcstream->cur != '\n') { tcstream->error = t_strdup_printf( "Expected new line after chunk size, but found %s", _chr_sanitize(*tcstream->cur)); return -1; } tcstream->cur++; if (tcstream->chunk_size > 0) tcstream->state = HTTP_CHUNKED_PARSE_STATE_DATA; else tcstream->state = HTTP_CHUNKED_PARSE_STATE_TRAILER; return 1; case HTTP_CHUNKED_PARSE_STATE_DATA_READY: /* fall through */ case HTTP_CHUNKED_PARSE_STATE_DATA_CR: tcstream->state = HTTP_CHUNKED_PARSE_STATE_DATA_LF; if (*tcstream->cur == '\r') { tcstream->cur++; if (tcstream->cur >= tcstream->end) return 0; } /* fall through */ case HTTP_CHUNKED_PARSE_STATE_DATA_LF: if (*tcstream->cur != '\n') { tcstream->error = t_strdup_printf( "Expected new line after chunk data, but found %s", _chr_sanitize(*tcstream->cur)); return -1; } tcstream->cur++; tcstream->state = HTTP_CHUNKED_PARSE_STATE_INIT; break; default: i_unreached(); } } i_unreached(); return -1; }