http-response-parser.c revision c177bc7c153dd4d00dfa719f8a93c81129c4235e
e59faf65ce864fe95dc00f5d52b8323cdbd0608aTimo Sirainen/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen#include "lib.h"
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen#include "str.h"
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen#include "istream.h"
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen#include "http-parser.h"
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen#include "http-date.h"
c4b376dd6e0c423006d7ac83a39253bcaf8e7c47Timo Sirainen#include "http-message-parser.h"
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen#include "http-response-parser.h"
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen#include <ctype.h>
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainenenum http_response_parser_state {
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen HTTP_RESPONSE_PARSE_STATE_INIT = 0,
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen HTTP_RESPONSE_PARSE_STATE_VERSION,
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen HTTP_RESPONSE_PARSE_STATE_SP1,
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen HTTP_RESPONSE_PARSE_STATE_STATUS,
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen HTTP_RESPONSE_PARSE_STATE_SP2,
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen HTTP_RESPONSE_PARSE_STATE_REASON,
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen HTTP_RESPONSE_PARSE_STATE_CR,
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen HTTP_RESPONSE_PARSE_STATE_LF,
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen HTTP_RESPONSE_PARSE_STATE_HEADER
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen};
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainenstruct http_response_parser {
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen struct http_message_parser parser;
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen enum http_response_parser_state state;
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen unsigned int response_status;
9261dbf0675204898c6557591c7aa376e23a52b2Timo Sirainen const char *response_reason;
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen};
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainenstruct http_response_parser *
9261dbf0675204898c6557591c7aa376e23a52b2Timo Sirainenhttp_response_parser_init(struct istream *input,
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen const struct http_header_limits *hdr_limits)
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen{
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen struct http_response_parser *parser;
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen /* FIXME: implement status line limit */
9261dbf0675204898c6557591c7aa376e23a52b2Timo Sirainen parser = i_new(struct http_response_parser, 1);
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen http_message_parser_init(&parser->parser, input, hdr_limits, 0, TRUE);
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen return parser;
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen}
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainenvoid http_response_parser_deinit(struct http_response_parser **_parser)
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen{
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen struct http_response_parser *parser = *_parser;
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen *_parser = NULL;
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen http_message_parser_deinit(&parser->parser);
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen i_free(parser);
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen}
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
ebfbf5d78dcf95e8b176429f4b5b0694eb4e17d5Timo Sirainenstatic void
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainenhttp_response_parser_restart(struct http_response_parser *parser)
ac383c437b1ccb9420cae6b4c4b03af3c8019e02Timo Sirainen{
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen http_message_parser_restart(&parser->parser, NULL);
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen parser->response_status = 0;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen parser->response_reason = NULL;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen}
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainenstatic int http_response_parse_status(struct http_response_parser *parser)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen{
e5acc283bf030b0b5c79ca4e52d315c516a299faPascal Volk const unsigned char *p = parser->parser.cur;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen const size_t size = parser->parser.end - parser->parser.cur;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen /* status-code = 3DIGIT
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen */
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen if (size < 3)
ac383c437b1ccb9420cae6b4c4b03af3c8019e02Timo Sirainen return 0;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if (!i_isdigit(p[0]) || !i_isdigit(p[1]) || !i_isdigit(p[2]))
ac383c437b1ccb9420cae6b4c4b03af3c8019e02Timo Sirainen return -1;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen parser->response_status =
ac383c437b1ccb9420cae6b4c4b03af3c8019e02Timo Sirainen (p[0] - '0')*100 + (p[1] - '0')*10 + (p[2] - '0');
ebfbf5d78dcf95e8b176429f4b5b0694eb4e17d5Timo Sirainen if (parser->response_status < 100 ||
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen parser->response_status >= 600)
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen return -1;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen parser->parser.cur += 3;
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen return 1;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen}
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
45114a7f9600d04c294f8f1051c0289c092febceTimo Sirainenstatic int http_response_parse_reason(struct http_response_parser *parser)
45114a7f9600d04c294f8f1051c0289c092febceTimo Sirainen{
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen const unsigned char *p = parser->parser.cur;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen pool_t pool;
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen
4ae81f8f7aad06aad2f570535cad6e40aaec2b28Timo Sirainen /* reason-phrase = *( HTAB / SP / VCHAR / obs-text )
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen */
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen // FIXME: limit length
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen while (p < parser->parser.end && http_char_is_text(*p))
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen p++;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen if (p == parser->parser.end)
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen return 0;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen pool = http_message_parser_get_pool(&parser->parser);
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen parser->response_reason =
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen p_strdup_until(pool, parser->parser.cur, p);
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen parser->parser.cur = p;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen return 1;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen}
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainenstatic const char *_reply_sanitize(struct http_message_parser *parser)
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen{
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen string_t *str = t_str_new(32);
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen const unsigned char *p;
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen unsigned int i;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen bool quote_open = FALSE;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen i_assert(parser->cur < parser->end);
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen for (p = parser->cur, i = 0; p < parser->end && i < 20; p++, i++) {
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen if (*p >= 0x20 && *p < 0x7F) {
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen if (!quote_open) {
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen str_append_c(str, '`');
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen quote_open = TRUE;
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen }
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen str_append_c(str, *p);
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen } else {
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen if (quote_open) {
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen str_append_c(str, '\'');
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen quote_open = FALSE;
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen }
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen if (*p == 0x0a)
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen str_append(str, "<LF>");
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen else if (*p == 0x0d)
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen str_append(str, "<CR>");
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen else
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen str_printfa(str, "<0x%02x>", *p);
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen }
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen }
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen if (quote_open)
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen str_append_c(str, '\'');
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen return str_c(str);
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen}
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainenstatic int http_response_parse(struct http_response_parser *parser)
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen{
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen struct http_message_parser *_parser = &parser->parser;
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen int ret;
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen /* RFC 7230, Section 3.1.2: Status Line
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen status-line = HTTP-version SP status-code SP reason-phrase CRLF
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen status-code = 3DIGIT
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen reason-phrase = *( HTAB / SP / VCHAR / obs-text )
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen */
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen switch (parser->state) {
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_INIT:
c4b376dd6e0c423006d7ac83a39253bcaf8e7c47Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_VERSION;
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen /* fall through */
f3bb2fbe87425dc89a839908985af496f7f65702Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_VERSION:
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen if ((ret=http_message_parse_version(_parser)) <= 0) {
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen if (ret < 0)
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen _parser->error = t_strdup_printf(
c4b376dd6e0c423006d7ac83a39253bcaf8e7c47Timo Sirainen "Invalid HTTP version in response: %s",
c4b376dd6e0c423006d7ac83a39253bcaf8e7c47Timo Sirainen _reply_sanitize(_parser));
c4b376dd6e0c423006d7ac83a39253bcaf8e7c47Timo Sirainen return ret;
c4b376dd6e0c423006d7ac83a39253bcaf8e7c47Timo Sirainen }
c4b376dd6e0c423006d7ac83a39253bcaf8e7c47Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_SP1;
c4b376dd6e0c423006d7ac83a39253bcaf8e7c47Timo Sirainen if (_parser->cur == _parser->end)
f3bb2fbe87425dc89a839908985af496f7f65702Timo Sirainen return 0;
fcde781c3ceb470c8dff34a68df19c69f93bcec9Timo Sirainen /* fall through */
fcde781c3ceb470c8dff34a68df19c69f93bcec9Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_SP1:
fcde781c3ceb470c8dff34a68df19c69f93bcec9Timo Sirainen if (*_parser->cur != ' ') {
fcde781c3ceb470c8dff34a68df19c69f93bcec9Timo Sirainen _parser->error = t_strdup_printf
f3bb2fbe87425dc89a839908985af496f7f65702Timo Sirainen ("Expected ' ' after response version, but found %s",
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen _reply_sanitize(_parser));
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen return -1;
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen }
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen _parser->cur++;
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_STATUS;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen if (_parser->cur >= _parser->end)
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen return 0;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen /* fall through */
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen case HTTP_RESPONSE_PARSE_STATE_STATUS:
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen if ((ret=http_response_parse_status(parser)) <= 0) {
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen if (ret < 0)
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen _parser->error = "Invalid HTTP status code in response";
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen return ret;
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen }
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_SP2;
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen if (_parser->cur == _parser->end)
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen return 0;
fcde781c3ceb470c8dff34a68df19c69f93bcec9Timo Sirainen /* fall through */
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_SP2:
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen if (*_parser->cur != ' ') {
1caf757864e7734345660e7d190f84e42668a6f8Timo Sirainen _parser->error = t_strdup_printf
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen ("Expected ' ' after response status code, but found %s",
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen _reply_sanitize(_parser));
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen return -1;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen }
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen _parser->cur++;
43d32cbe60fdaef2699d99f1ca259053e9350411Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_REASON;
43d32cbe60fdaef2699d99f1ca259053e9350411Timo Sirainen if (_parser->cur >= _parser->end)
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen return 0;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen /* fall through */
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_REASON:
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen if ((ret=http_response_parse_reason(parser)) <= 0) {
340dac1753155dcac3a4fe8ac447f3ebfe7c2087Timo Sirainen i_assert(ret == 0);
340dac1753155dcac3a4fe8ac447f3ebfe7c2087Timo Sirainen return 0;
340dac1753155dcac3a4fe8ac447f3ebfe7c2087Timo Sirainen }
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_CR;
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen if (_parser->cur == _parser->end)
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen return 0;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen /* fall through */
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen case HTTP_RESPONSE_PARSE_STATE_CR:
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen if (*_parser->cur == '\r')
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen _parser->cur++;
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_LF;
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen if (_parser->cur == _parser->end)
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen return 0;
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen /* fall through */
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen case HTTP_RESPONSE_PARSE_STATE_LF:
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen if (*_parser->cur != '\n') {
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen _parser->error = t_strdup_printf
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen ("Expected line end after response, but found %s",
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen _reply_sanitize(_parser));
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen return -1;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen }
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen _parser->cur++;
916221f976af0ed8b397f06f4f381c0ac0be3b86Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_HEADER;
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen return 1;
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_HEADER:
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen default:
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen break;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen }
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen i_unreached();
916221f976af0ed8b397f06f4f381c0ac0be3b86Timo Sirainen return -1;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen}
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainenstatic int
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainenhttp_response_parse_status_line(struct http_response_parser *parser)
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen{
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen struct http_message_parser *_parser = &parser->parser;
916221f976af0ed8b397f06f4f381c0ac0be3b86Timo Sirainen const unsigned char *begin;
0fd1e7da973b92e0544ac5783eac2bac6567add1Timo Sirainen size_t size, old_bytes = 0;
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen int ret;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen while ((ret = i_stream_read_bytes(_parser->input, &begin, &size,
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen old_bytes + 1)) > 0) {
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen _parser->cur = begin;
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen _parser->end = _parser->cur + size;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen if ((ret = http_response_parse(parser)) < 0)
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen return -1;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen i_stream_skip(_parser->input, _parser->cur - begin);
3852872e6954b7132e637294132005e86b8ebd4aTimo Sirainen if (ret > 0)
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen return 1;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen old_bytes = i_stream_get_data_size(_parser->input);
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen }
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen if (ret == -2) {
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen _parser->error = "HTTP status line is too long";
e62f6437a4ff01d692a5a61369fe4168d69191edTimo Sirainen return -1;
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen }
809923ba26218971792182ae8894936f9ac99364Timo Sirainen if (ret < 0) {
1b0cfbf3cc77a670b92fff5c30f7b1eb17a63ab1Timo Sirainen if (_parser->input->eof &&
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen parser->state == HTTP_RESPONSE_PARSE_STATE_INIT)
74ab5ea66c0c4b388f1c774ae6a47ab94f1b4f18Timo Sirainen return 0;
_parser->error = "Stream error";
return -1;
}
return 0;
}
static int
http_response_parse_retry_after(const char *hdrval, time_t resp_time,
time_t *retry_after_r)
{
time_t delta;
/* RFC 7231, Section 7.1.3: Retry-After
The value of this field can be either an HTTP-date or a number of
seconds to delay after the response is received.
Retry-After = HTTP-date / delta-seconds
A delay-seconds value is a non-negative decimal integer, representing
time in seconds.
delta-seconds = 1*DIGIT
*/
if (str_to_time(hdrval, &delta) >= 0) {
if (resp_time == (time_t)-1) {
return -1;
}
*retry_after_r = resp_time + delta;
return 0;
}
return (http_date_parse
((unsigned char *)hdrval, strlen(hdrval), retry_after_r) ? 0 : -1);
}
int http_response_parse_next(struct http_response_parser *parser,
enum http_response_payload_type payload_type,
struct http_response *response, const char **error_r)
{
const char *hdrval;
time_t retry_after = (time_t)-1;
int ret;
i_zero(response);
/* make sure we finished streaming payload from previous response
before we continue. */
if ((ret = http_message_parse_finish_payload(&parser->parser)) <= 0) {
*error_r = parser->parser.error;
return ret;
}
if (parser->state == HTTP_RESPONSE_PARSE_STATE_INIT)
http_response_parser_restart(parser);
/* RFC 7230, Section 3:
HTTP-message = start-line
*( header-field CRLF )
CRLF
[ message-body ]
*/
if (parser->state != HTTP_RESPONSE_PARSE_STATE_HEADER) {
if ((ret = http_response_parse_status_line(parser)) <= 0) {
*error_r = parser->parser.error;
return ret;
}
}
if ((ret = http_message_parse_headers(&parser->parser)) <= 0) {
*error_r = parser->parser.error;
return ret;
}
/* RFC 7230, Section 3.3.2: Content-Length
A server MUST NOT send a Content-Length header field in any response
with a status code of 1xx (Informational) or 204 (No Content).
*/
if ((parser->response_status / 100 == 1 || parser->response_status == 204) &&
parser->parser.msg.content_length > 0) {
*error_r = t_strdup_printf(
"Unexpected Content-Length header field for %u response "
"(length=%"PRIuUOFF_T")", parser->response_status,
parser->parser.msg.content_length);
return -1;
}
/* RFC 7230, Section 3.3.3: Message Body Length
1. Any response to a HEAD request and any response with a 1xx
(Informational), 204 (No Content), or 304 (Not Modified) status
code is always terminated by the first empty line after the
header fields, regardless of the header fields present in the
message, and thus cannot contain a message body.
*/
if (parser->response_status / 100 == 1 || parser->response_status == 204
|| parser->response_status == 304) { // HEAD is handled in caller
payload_type = HTTP_RESPONSE_PAYLOAD_TYPE_NOT_PRESENT;
}
if ((payload_type == HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED) ||
(payload_type == HTTP_RESPONSE_PAYLOAD_TYPE_ONLY_UNSUCCESSFUL &&
parser->response_status / 100 != 2)) {
/* [ message-body ] */
if (http_message_parse_body(&parser->parser, FALSE) < 0) {
*error_r = parser->parser.error;
return -1;
}
}
/* RFC 7231, Section 7.1.3: Retry-After
Servers send the "Retry-After" header field to indicate how long the
user agent ought to wait before making a follow-up request. When
sent with a 503 (Service Unavailable) response, Retry-After indicates
how long the service is expected to be unavailable to the client.
When sent with any 3xx (Redirection) response, Retry-After indicates
the minimum time that the user agent is asked to wait before issuing
the redirected request.
*/
if (parser->response_status == 503 || (parser->response_status / 100) == 3) {
hdrval = http_header_field_get(parser->parser.msg.header, "Retry-After");
if (hdrval != NULL) {
(void)http_response_parse_retry_after
(hdrval, parser->parser.msg.date, &retry_after);
/* broken Retry-After header is ignored */
}
}
parser->state = HTTP_RESPONSE_PARSE_STATE_INIT;
response->status = parser->response_status;
response->reason = parser->response_reason;
response->version_major = parser->parser.msg.version_major;
response->version_minor = parser->parser.msg.version_minor;
response->location = parser->parser.msg.location;
response->date = parser->parser.msg.date;
response->retry_after = retry_after;
response->payload = parser->parser.payload;
response->header = parser->parser.msg.header;
response->connection_options = parser->parser.msg.connection_options;
response->connection_close = parser->parser.msg.connection_close;
return 1;
}