summaryrefslogtreecommitdiffstats
path: root/src/lib-http/http-response-parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-http/http-response-parser.c')
-rw-r--r--src/lib-http/http-response-parser.c422
1 files changed, 422 insertions, 0 deletions
diff --git a/src/lib-http/http-response-parser.c b/src/lib-http/http-response-parser.c
new file mode 100644
index 0000000..e381403
--- /dev/null
+++ b/src/lib-http/http-response-parser.c
@@ -0,0 +1,422 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "http-parser.h"
+#include "http-date.h"
+#include "http-message-parser.h"
+#include "http-response-parser.h"
+
+#include <ctype.h>
+
+enum http_response_parser_state {
+ HTTP_RESPONSE_PARSE_STATE_INIT = 0,
+ HTTP_RESPONSE_PARSE_STATE_VERSION,
+ HTTP_RESPONSE_PARSE_STATE_SP1,
+ HTTP_RESPONSE_PARSE_STATE_STATUS,
+ HTTP_RESPONSE_PARSE_STATE_SP2,
+ HTTP_RESPONSE_PARSE_STATE_REASON,
+ HTTP_RESPONSE_PARSE_STATE_CR,
+ HTTP_RESPONSE_PARSE_STATE_LF,
+ HTTP_RESPONSE_PARSE_STATE_HEADER
+};
+
+struct http_response_parser {
+ struct http_message_parser parser;
+ enum http_response_parser_state state;
+
+ unsigned int response_status;
+ const char *response_reason;
+
+ uoff_t response_offset;
+};
+
+struct http_response_parser *
+http_response_parser_init(struct istream *input,
+ const struct http_header_limits *hdr_limits,
+ enum http_response_parse_flags flags)
+{
+ struct http_response_parser *parser;
+ enum http_message_parse_flags msg_flags = 0;
+
+ /* FIXME: implement status line limit */
+ if ((flags & HTTP_RESPONSE_PARSE_FLAG_STRICT) != 0)
+ msg_flags |= HTTP_MESSAGE_PARSE_FLAG_STRICT;
+ parser = i_new(struct http_response_parser, 1);
+ http_message_parser_init(&parser->parser,
+ input, hdr_limits, 0, msg_flags);
+ return parser;
+}
+
+void http_response_parser_deinit(struct http_response_parser **_parser)
+{
+ struct http_response_parser *parser = *_parser;
+
+ *_parser = NULL;
+
+ http_message_parser_deinit(&parser->parser);
+ i_free(parser);
+}
+
+static void
+http_response_parser_restart(struct http_response_parser *parser)
+{
+ http_message_parser_restart(&parser->parser, NULL);
+ parser->response_status = 0;
+ parser->response_reason = NULL;
+ parser->response_offset = UOFF_T_MAX;
+}
+
+static int http_response_parse_status(struct http_response_parser *parser)
+{
+ const unsigned char *p = parser->parser.cur;
+ const size_t size = parser->parser.end - parser->parser.cur;
+
+ /* status-code = 3DIGIT
+ */
+ if (size < 3)
+ return 0;
+ if (!i_isdigit(p[0]) || !i_isdigit(p[1]) || !i_isdigit(p[2]))
+ return -1;
+ parser->response_status =
+ (p[0] - '0')*100 + (p[1] - '0')*10 + (p[2] - '0');
+ if (parser->response_status < 100 ||
+ parser->response_status >= 600)
+ return -1;
+ parser->parser.cur += 3;
+ return 1;
+}
+
+static int http_response_parse_reason(struct http_response_parser *parser)
+{
+ const unsigned char *p = parser->parser.cur;
+ pool_t pool;
+
+ /* reason-phrase = *( HTAB / SP / VCHAR / obs-text )
+ */
+ // FIXME: limit length
+ while (p < parser->parser.end && http_char_is_text(*p))
+ p++;
+
+ if (p == parser->parser.end)
+ return 0;
+ pool = http_message_parser_get_pool(&parser->parser);
+ parser->response_reason =
+ p_strdup_until(pool, parser->parser.cur, p);
+ parser->parser.cur = p;
+ return 1;
+}
+
+static const char *_reply_sanitize(struct http_message_parser *parser)
+{
+ string_t *str = t_str_new(32);
+ const unsigned char *p;
+ unsigned int i;
+ bool quote_open = FALSE;
+
+ i_assert(parser->cur < parser->end);
+ for (p = parser->cur, i = 0; p < parser->end && i < 20; p++, i++) {
+ if (*p >= 0x20 && *p < 0x7F) {
+ if (!quote_open) {
+ str_append_c(str, '`');
+ quote_open = TRUE;
+ }
+ str_append_c(str, *p);
+ } else {
+ if (quote_open) {
+ str_append_c(str, '\'');
+ quote_open = FALSE;
+ }
+ if (*p == 0x0a)
+ str_append(str, "<LF>");
+ else if (*p == 0x0d)
+ str_append(str, "<CR>");
+ else
+ str_printfa(str, "<0x%02x>", *p);
+ }
+ }
+ if (quote_open)
+ str_append_c(str, '\'');
+ return str_c(str);
+}
+
+static int http_response_parse(struct http_response_parser *parser)
+{
+ struct http_message_parser *_parser = &parser->parser;
+ int ret;
+
+ /* RFC 7230, Section 3.1.2: Status Line
+
+ status-line = HTTP-version SP status-code SP reason-phrase CRLF
+ status-code = 3DIGIT
+ reason-phrase = *( HTAB / SP / VCHAR / obs-text )
+ */
+ switch (parser->state) {
+ case HTTP_RESPONSE_PARSE_STATE_INIT:
+ parser->state = HTTP_RESPONSE_PARSE_STATE_VERSION;
+ parser->response_offset = _parser->input->v_offset +
+ (_parser->cur - _parser->begin);
+ /* fall through */
+ case HTTP_RESPONSE_PARSE_STATE_VERSION:
+ if ((ret=http_message_parse_version(_parser)) <= 0) {
+ if (ret < 0)
+ _parser->error = t_strdup_printf(
+ "Invalid HTTP version in response: %s",
+ _reply_sanitize(_parser));
+ return ret;
+ }
+ parser->state = HTTP_RESPONSE_PARSE_STATE_SP1;
+ if (_parser->cur == _parser->end)
+ return 0;
+ /* fall through */
+ case HTTP_RESPONSE_PARSE_STATE_SP1:
+ if (*_parser->cur != ' ') {
+ _parser->error = t_strdup_printf
+ ("Expected ' ' after response version, but found %s",
+ _reply_sanitize(_parser));
+ return -1;
+ }
+ _parser->cur++;
+ parser->state = HTTP_RESPONSE_PARSE_STATE_STATUS;
+ if (_parser->cur >= _parser->end)
+ return 0;
+ /* fall through */
+ case HTTP_RESPONSE_PARSE_STATE_STATUS:
+ if ((ret=http_response_parse_status(parser)) <= 0) {
+ if (ret < 0)
+ _parser->error = "Invalid HTTP status code in response";
+ return ret;
+ }
+ parser->state = HTTP_RESPONSE_PARSE_STATE_SP2;
+ if (_parser->cur == _parser->end)
+ return 0;
+ /* fall through */
+ case HTTP_RESPONSE_PARSE_STATE_SP2:
+ if (*_parser->cur != ' ') {
+ _parser->error = t_strdup_printf
+ ("Expected ' ' after response status code, but found %s",
+ _reply_sanitize(_parser));
+ return -1;
+ }
+ _parser->cur++;
+ parser->state = HTTP_RESPONSE_PARSE_STATE_REASON;
+ if (_parser->cur >= _parser->end)
+ return 0;
+ /* fall through */
+ case HTTP_RESPONSE_PARSE_STATE_REASON:
+ if ((ret=http_response_parse_reason(parser)) <= 0) {
+ i_assert(ret == 0);
+ return 0;
+ }
+ parser->state = HTTP_RESPONSE_PARSE_STATE_CR;
+ if (_parser->cur == _parser->end)
+ return 0;
+ /* fall through */
+ case HTTP_RESPONSE_PARSE_STATE_CR:
+ if (*_parser->cur == '\r')
+ _parser->cur++;
+ parser->state = HTTP_RESPONSE_PARSE_STATE_LF;
+ if (_parser->cur == _parser->end)
+ return 0;
+ /* fall through */
+ case HTTP_RESPONSE_PARSE_STATE_LF:
+ if (*_parser->cur != '\n') {
+ _parser->error = t_strdup_printf
+ ("Expected line end after response, but found %s",
+ _reply_sanitize(_parser));
+ return -1;
+ }
+ _parser->cur++;
+ parser->state = HTTP_RESPONSE_PARSE_STATE_HEADER;
+ return 1;
+ case HTTP_RESPONSE_PARSE_STATE_HEADER:
+ default:
+ break;
+ }
+
+ 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->begin = _parser->cur = begin;
+ _parser->end = _parser->begin + 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 = t_strdup_printf("Stream error: %s",
+ i_stream_get_error(_parser->input));
+ 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
+ ((const unsigned char *)hdrval, strlen(hdrval), retry_after_r) ? 0 : -1);
+}
+
+uoff_t http_response_parser_get_last_offset(struct http_response_parser *parser)
+{
+ return parser->response_offset;
+}
+
+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;
+}