/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "istream.h" #include "str.h" #include "str-sanitize.h" #include "http-parser.h" #include "http-header.h" #include "http-header-parser.h" enum http_header_parse_state { HTTP_HEADER_PARSE_STATE_INIT = 0, HTTP_HEADER_PARSE_STATE_NAME, HTTP_HEADER_PARSE_STATE_COLON, HTTP_HEADER_PARSE_STATE_OWS, HTTP_HEADER_PARSE_STATE_CONTENT, HTTP_HEADER_PARSE_STATE_CR, HTTP_HEADER_PARSE_STATE_LF, HTTP_HEADER_PARSE_STATE_NEW_LINE, HTTP_HEADER_PARSE_STATE_EOH }; struct http_header_parser { struct istream *input; struct http_header_limits limits; enum http_header_parse_flags flags; uoff_t size, field_size; unsigned int field_count; const unsigned char *begin, *cur, *end; const char *error; string_t *name; buffer_t *value_buf; enum http_header_parse_state state; }; struct http_header_parser * http_header_parser_init(struct istream *input, const struct http_header_limits *limits, enum http_header_parse_flags flags) { struct http_header_parser *parser; parser = i_new(struct http_header_parser, 1); parser->input = input; if (limits != NULL) parser->limits = *limits; if (parser->limits.max_size == 0) parser->limits.max_size = UOFF_T_MAX; if (parser->limits.max_field_size == 0) parser->limits.max_field_size = UOFF_T_MAX; if (parser->limits.max_fields == 0) parser->limits.max_fields = (unsigned int)-1; parser->flags = flags; parser->name = str_new(default_pool, 128); parser->value_buf = buffer_create_dynamic(default_pool, 4096); return parser; } void http_header_parser_deinit(struct http_header_parser **_parser) { struct http_header_parser *parser = *_parser; *_parser = NULL; //i_stream_skip(ctx->input, ctx->skip); buffer_free(&parser->value_buf); str_free(&parser->name); i_free(parser); } void http_header_parser_reset(struct http_header_parser *parser) { parser->state = HTTP_HEADER_PARSE_STATE_INIT; parser->size = 0; parser->field_size = 0; parser->field_count = 0; } static int http_header_parse_name(struct http_header_parser *parser) { const unsigned char *first = parser->cur; /* field-name = token token = 1*tchar */ while (parser->cur < parser->end && http_char_is_token(*parser->cur)) parser->cur++; str_append_data(parser->name, first, parser->cur-first); if (parser->cur == parser->end) return 0; if (str_len(parser->name) == 0) { parser->error = "Empty header field name"; return -1; } return 1; } static int http_header_parse_ows(struct http_header_parser *parser) { /* OWS = *( SP / HTAB ) ; "optional" whitespace */ while (parser->cur < parser->end && (*parser->cur == ' ' || *parser->cur == '\t')) parser->cur++; return (parser->cur == parser->end ? 0 : 1); } static int http_header_parse_content(struct http_header_parser *parser) { const unsigned char *first; /* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] field-vchar = VCHAR / obs-text */ do { first = parser->cur; while (parser->cur < parser->end && http_char_is_text(*parser->cur)) { parser->cur++; } buffer_append(parser->value_buf, first, parser->cur-first); if ((parser->flags & HTTP_HEADER_PARSE_FLAG_STRICT) != 0) break; /* We'll be lenient here to accommodate for some bad servers. We just drop offending characters */ while (parser->cur < parser->end && !http_char_is_text(*parser->cur) && (*parser->cur != '\r' && *parser->cur != '\n')) parser->cur++; } while (parser->cur < parser->end && (*parser->cur != '\r' && *parser->cur != '\n')); if (parser->cur == parser->end) return 0; return 1; } static inline const char *_chr_sanitize(unsigned char c) { if (c >= 0x20 && c < 0x7F) return t_strdup_printf("'%c'", c); return t_strdup_printf("0x%02x", c); } 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; } int http_header_parse_next_field(struct http_header_parser *parser, const char **name_r, const unsigned char **data_r, size_t *size_r, const char **error_r) { const uoff_t max_size = parser->limits.max_size; const uoff_t max_field_size = parser->limits.max_field_size; const unsigned char *data; size_t size; int ret; *error_r = NULL; while ((ret=i_stream_read_more(parser->input, &parser->begin, &size)) > 0) { /* check header size limits */ if (parser->size >= max_size) { *error_r = "Excessive header size"; return -1; } if (parser->field_size > max_field_size) { *error_r = "Excessive header field size"; return -1; } /* don't parse beyond header size limits */ if (size > (max_size - parser->size)) size = max_size - parser->size; if (size > (max_field_size - parser->field_size)) { size = max_field_size - parser->field_size; size = (size == 0 ? 1 : size); /* need to parse one more byte */ } parser->cur = parser->begin; parser->end = parser->cur + size; if ((ret=http_header_parse(parser)) < 0) { *error_r = parser->error; return -1; } i_stream_skip(parser->input, parser->cur - parser->begin); parser->size += parser->cur - parser->begin; parser->field_size += parser->cur - parser->begin; if (ret == 1) { parser->field_size = 0; if (parser->state != HTTP_HEADER_PARSE_STATE_EOH) { data = buffer_get_data(parser->value_buf, &size); /* trim trailing OWS */ while (size > 0 && (data[size-1] == ' ' || data[size-1] == '\t')) size--; *name_r = str_c(parser->name); *data_r = data; *size_r = size; parser->state = HTTP_HEADER_PARSE_STATE_INIT; } else { *name_r = NULL; *data_r = NULL; } return 1; } } i_assert(ret != -2); if (ret < 0) { i_assert(parser->input->eof); if (parser->input->stream_errno == 0) *error_r = "Premature end of input"; else *error_r = t_strdup_printf("Stream error: %s", i_stream_get_error(parser->input)); } return ret; }