http-response-parser.c revision 1ec26e0b70ac7f8a4e3dfbc59aa77f572651d5ae
02c335c23bf5fa225a467c19f2c063fb0dc7b8c3Timo Sirainen/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "lib.h"
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen#include "str.h"
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen#include "istream.h"
0536ccb51d41e3078c3a9fa33e509fb4b2420f95Timo Sirainen#include "http-parser.h"
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen#include "http-date.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "http-message-parser.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "http-response-parser.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include <ctype.h>
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenenum http_response_parser_state {
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen HTTP_RESPONSE_PARSE_STATE_INIT = 0,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen HTTP_RESPONSE_PARSE_STATE_VERSION,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen HTTP_RESPONSE_PARSE_STATE_SP1,
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen HTTP_RESPONSE_PARSE_STATE_STATUS,
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen HTTP_RESPONSE_PARSE_STATE_SP2,
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen HTTP_RESPONSE_PARSE_STATE_REASON,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen HTTP_RESPONSE_PARSE_STATE_CR,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen HTTP_RESPONSE_PARSE_STATE_LF,
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainen HTTP_RESPONSE_PARSE_STATE_HEADER
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen};
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstruct http_response_parser {
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen struct http_message_parser parser;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen enum http_response_parser_state state;
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainen
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainen unsigned int response_status;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen const char *response_reason;
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainen};
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainenstruct http_response_parser *
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainenhttp_response_parser_init(struct istream *input,
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen const struct http_header_limits *hdr_limits)
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen{
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen struct http_response_parser *parser;
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen /* FIXME: implement status line limit */
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen parser = i_new(struct http_response_parser, 1);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen http_message_parser_init(&parser->parser, input, hdr_limits, 0, TRUE);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return parser;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen}
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainenvoid http_response_parser_deinit(struct http_response_parser **_parser)
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen{
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen struct http_response_parser *parser = *_parser;
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen
4b8459c6c24b79d4ed5974ab6e3289a3f2b701c0Timo Sirainen *_parser = NULL;
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen http_message_parser_deinit(&parser->parser);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen i_free(parser);
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen}
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainenstatic void
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainenhttp_response_parser_restart(struct http_response_parser *parser)
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen{
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen http_message_parser_restart(&parser->parser, NULL);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen parser->response_status = 0;
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen parser->response_reason = NULL;
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen}
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstatic int http_response_parse_status(struct http_response_parser *parser)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen{
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen const unsigned char *p = parser->parser.cur;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen const size_t size = parser->parser.end - parser->parser.cur;
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen /* status-code = 3DIGIT
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (size < 3)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return 0;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (!i_isdigit(p[0]) || !i_isdigit(p[1]) || !i_isdigit(p[2]))
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return -1;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen parser->response_status =
49b6e2d72cfaa5c244c798ddbae5b61489b0f728Timo Sirainen (p[0] - '0')*100 + (p[1] - '0')*10 + (p[2] - '0');
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (parser->response_status < 100 ||
49b6e2d72cfaa5c244c798ddbae5b61489b0f728Timo Sirainen parser->response_status >= 600)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return -1;
49b6e2d72cfaa5c244c798ddbae5b61489b0f728Timo Sirainen parser->parser.cur += 3;
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen return 1;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen}
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstatic int http_response_parse_reason(struct http_response_parser *parser)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen{
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen const unsigned char *p = parser->parser.cur;
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen pool_t pool;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen /* reason-phrase = *( HTAB / SP / VCHAR / obs-text )
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen // FIXME: limit length
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen while (p < parser->parser.end && http_char_is_text(*p))
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen p++;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen if (p == parser->parser.end)
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen return 0;
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen pool = http_message_parser_get_pool(&parser->parser);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen parser->response_reason =
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen p_strdup_until(pool, parser->parser.cur, p);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen parser->parser.cur = p;
965e13eea8dc7f1da3769ab0c4667e36d0f33192Timo Sirainen return 1;
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen}
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstatic const char *_reply_sanitize(struct http_message_parser *parser)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen{
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen string_t *str = t_str_new(32);
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen const unsigned char *p;
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen unsigned int i;
7420207c4eae66bd7edc2bdebaee7d2cb0b6c341Timo Sirainen bool quote_open = FALSE;
4d4cd9cde9e01d4ad9354e6e30ac2f90d13042b2Timo Sirainen
4d4cd9cde9e01d4ad9354e6e30ac2f90d13042b2Timo Sirainen i_assert(parser->cur < parser->end);
4d4cd9cde9e01d4ad9354e6e30ac2f90d13042b2Timo Sirainen for (p = parser->cur, i = 0; p < parser->end && i < 20; p++, i++) {
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen if (*p >= 0x20 && *p < 0x7F) {
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen if (!quote_open) {
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen str_append_c(str, '`');
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen quote_open = TRUE;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen }
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen str_append_c(str, *p);
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen } else {
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen if (quote_open) {
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen str_append_c(str, '\'');
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen quote_open = FALSE;
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen }
31a574fda352ef4f71dbff9c30e15e4744e132c0Timo Sirainen if (*p == 0x0a)
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen str_append(str, "<LF>");
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen else if (*p == 0x0d)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen str_append(str, "<CR>");
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen else
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen str_printfa(str, "<0x%02x>", *p);
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen }
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (quote_open)
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen str_append_c(str, '\'');
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen return str_c(str);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen}
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainenstatic int http_response_parse(struct http_response_parser *parser)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen{
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen struct http_message_parser *_parser = &parser->parser;
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen int ret;
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen /* RFC 7230, Section 3.1.2: Status Line
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen status-line = HTTP-version SP status-code SP reason-phrase CRLF
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen status-code = 3DIGIT
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen reason-phrase = *( HTAB / SP / VCHAR / obs-text )
f2767c736d72e6aa9a2aae5d0a9b89abae9e29e9Timo Sirainen */
f2767c736d72e6aa9a2aae5d0a9b89abae9e29e9Timo Sirainen switch (parser->state) {
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_INIT:
f2767c736d72e6aa9a2aae5d0a9b89abae9e29e9Timo Sirainen http_response_parser_restart(parser);
f2767c736d72e6aa9a2aae5d0a9b89abae9e29e9Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_VERSION;
f2767c736d72e6aa9a2aae5d0a9b89abae9e29e9Timo Sirainen /* fall through */
f2767c736d72e6aa9a2aae5d0a9b89abae9e29e9Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_VERSION:
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen if ((ret=http_message_parse_version(_parser)) <= 0) {
f05b9dd37f830576ca7d32ec7071bf87906df3d2Timo Sirainen if (ret < 0)
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen _parser->error = t_strdup_printf(
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen "Invalid HTTP version in response: %s",
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen _reply_sanitize(_parser));
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen return ret;
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen }
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_SP1;
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen if (_parser->cur == _parser->end)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return 0;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen /* fall through */
f922ecaf766c60c10f642f3ac2d5f7748ff642b0Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_SP1:
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (*_parser->cur != ' ') {
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen _parser->error = t_strdup_printf
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen ("Expected ' ' after response version, but found %s",
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen _reply_sanitize(_parser));
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return -1;
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen _parser->cur++;
b9a13c136b7c5803a8271878fcbbf5328f6e7f2aTimo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_STATUS;
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen if (_parser->cur >= _parser->end)
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen return 0;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen /* fall through */
80cfaba9e253545cbcd08f84939b27cdbb4a50d0Aki Tuomi case HTTP_RESPONSE_PARSE_STATE_STATUS:
80cfaba9e253545cbcd08f84939b27cdbb4a50d0Aki Tuomi if ((ret=http_response_parse_status(parser)) <= 0) {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (ret < 0)
6795f542ed816a3c977085d4f74df1d62a37b690Timo Sirainen _parser->error = "Invalid HTTP status code in response";
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return ret;
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen }
b116c06e4d3609f07f1d9582a932ad3ea9ce7e15Stephan Bosch parser->state = HTTP_RESPONSE_PARSE_STATE_SP2;
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen if (_parser->cur == _parser->end)
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen return 0;
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen /* fall through */
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_SP2:
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen if (*_parser->cur != ' ') {
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen _parser->error = t_strdup_printf
c8920d5f3df9663668ccd6412218eb28008f4e9aTimo Sirainen ("Expected ' ' after response status code, but found %s",
c8920d5f3df9663668ccd6412218eb28008f4e9aTimo Sirainen _reply_sanitize(_parser));
f0a386b29f2c9163e8fff6a8e26077b59708c980Timo Sirainen return -1;
637f9883a385abb03fd1211e79cc68df696cc387Timo Sirainen }
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen _parser->cur++;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_REASON;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (_parser->cur >= _parser->end)
20a3870db4f78717574ee94bca1512994391b2abTimo Sirainen return 0;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen /* fall through */
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_REASON:
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if ((ret=http_response_parse_reason(parser)) <= 0) {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen i_assert(ret == 0);
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen return 0;
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen }
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_CR;
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen if (_parser->cur == _parser->end)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return 0;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen /* fall through */
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_CR:
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen if (*_parser->cur == '\r')
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen _parser->cur++;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_LF;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (_parser->cur == _parser->end)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return 0;
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen /* fall through */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_LF:
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen if (*_parser->cur != '\n') {
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen _parser->error = t_strdup_printf
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen ("Expected line end after response, but found %s",
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen _reply_sanitize(_parser));
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen return -1;
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen _parser->cur++;
b9a13c136b7c5803a8271878fcbbf5328f6e7f2aTimo Sirainen parser->state = HTTP_RESPONSE_PARSE_STATE_HEADER;
b9a13c136b7c5803a8271878fcbbf5328f6e7f2aTimo Sirainen return 1;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen case HTTP_RESPONSE_PARSE_STATE_HEADER:
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen default:
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen break;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
i_unreached();
return -1;
}
static int
http_response_parse_status_line(struct http_response_parser *parser)
{
struct http_message_parser *_parser = &parser->parser;
const unsigned char *begin;
size_t size, old_bytes = 0;
int ret;
while ((ret = i_stream_read_bytes(_parser->input, &begin, &size,
old_bytes + 1)) > 0) {
_parser->cur = begin;
_parser->end = _parser->cur + size;
if ((ret = http_response_parse(parser)) < 0)
return -1;
i_stream_skip(_parser->input, _parser->cur - begin);
if (ret > 0)
return 1;
old_bytes = i_stream_get_data_size(_parser->input);
}
if (ret == -2) {
_parser->error = "HTTP status line is too long";
return -1;
}
if (ret < 0) {
if (_parser->input->eof &&
parser->state == HTTP_RESPONSE_PARSE_STATE_INIT)
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;
/* 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;
}
/* 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;
i_zero(response);
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;
}