Moved code common with it and http-response-parser to http-message-parser.
http-parser.c \
http-header-parser.c \
http-transfer-chunked.c \
+ http-message-parser.c \
+ http-request-parser.c \
http-response-parser.c \
http-client-request.c \
http-client-connection.c \
http-parser.h \
http-header-parser.h \
http-transfer.h \
+ http-message-parser.h \
+ http-request-parser.h \
http-response.h \
http-response-parser.h \
http-client-private.h \
http-parser.lo \
http-header-parser.lo \
http-transfer-chunked.lo \
+ http-message-parser.lo \
http-response-parser.lo \
$(test_libs)
test_http_response_parser_DEPENDENCIES = $(test_deps)
--- /dev/null
+/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "http-parser.h"
+#include "http-header-parser.h"
+#include "http-date.h"
+#include "http-transfer.h"
+#include "http-message-parser.h"
+
+#include <ctype.h>
+
+void http_message_parser_init(struct http_message_parser *parser,
+ struct istream *input)
+{
+ memset(parser, 0, sizeof(*parser));
+ parser->input = input;
+}
+
+void http_message_parser_deinit(struct http_message_parser *parser)
+{
+ if (parser->header_parser != NULL)
+ http_header_parser_deinit(&parser->header_parser);
+ if (parser->msg_pool != NULL)
+ pool_unref(&parser->msg_pool);
+ if (parser->payload != NULL)
+ i_stream_unref(&parser->payload);
+}
+
+void http_message_parser_restart(struct http_message_parser *parser)
+{
+ i_assert(parser->payload == NULL);
+
+ if (parser->header_parser == NULL)
+ parser->header_parser = http_header_parser_init(parser->input);
+ else
+ http_header_parser_reset(parser->header_parser);
+
+ if (parser->msg_pool != NULL)
+ pool_unref(&parser->msg_pool);
+ parser->msg_pool = pool_alloconly_create("http_message", 4096);
+ memset(&parser->msg, 0, sizeof(parser->msg));
+ parser->msg.date = (time_t)-1;
+ p_array_init(&parser->msg.headers, parser->msg_pool, 32);
+}
+
+int http_message_parse_version(struct http_message_parser *parser)
+{
+ const unsigned char *p = parser->cur;
+ const size_t size = parser->end - parser->cur;
+
+ /* HTTP-version = HTTP-name "/" DIGIT "." DIGIT
+ HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive
+ */
+ if (size < 8)
+ return 0;
+ if (memcmp(p, "HTTP/", 5) != 0 ||
+ !i_isdigit(p[5]) || p[6] != '.' || !i_isdigit(p[7]))
+ return -1;
+ parser->msg.version_major = p[5] - '0';
+ parser->msg.version_minor = p[7] - '0';
+ parser->cur += 8;
+ return 1;
+}
+
+int http_message_parse_finish_payload(struct http_message_parser *parser,
+ const char **error_r)
+{
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ if (parser->payload == NULL)
+ return 1;
+
+ while ((ret = i_stream_read_data(parser->payload, &data, &size, 0)) > 0)
+ i_stream_skip(parser->payload, size);
+ if (ret == 0 || parser->payload->stream_errno != 0) {
+ if (ret < 0)
+ *error_r = "Stream error while skipping payload";
+ return ret;
+ }
+ i_stream_unref(&parser->payload);
+ return 1;
+}
+
+static int
+http_message_parse_header(struct http_message_parser *parser, const char *name,
+ const unsigned char *data, size_t size,
+ const char **error_r)
+{
+ struct http_response_header *hdr;
+ struct http_parser hparser;
+ void *value;
+
+ hdr = array_append_space(&parser->msg.headers);
+ hdr->key = p_strdup(parser->msg_pool, name);
+ hdr->value = value = p_malloc(parser->msg_pool, size+1);
+ memcpy(value, data, size);
+ hdr->size = size;
+
+ switch (name[0]) {
+ case 'C': case 'c':
+ if (strcasecmp(name, "Connection") == 0) {
+ const char *option;
+
+ /* Connection = 1#connection-option
+ connection-option = token
+ */
+ http_parser_init(&hparser, data, size);
+ for (;;) {
+ if (http_parse_token_list_next(&hparser, &option) <= 0)
+ break;
+ if (strcasecmp(option, "close") == 0) {
+ parser->msg.connection_close = TRUE;
+ break; // not interested in any other options
+ }
+ }
+ return 0;
+ }
+ if (strcasecmp(name, "Content-Length") == 0) {
+ /* Content-Length = 1*DIGIT */
+ if (str_to_uoff(hdr->value, &parser->msg.content_length) < 0) {
+ *error_r = "Invalid Content-Length header";
+ return -1;
+ }
+ return 0;
+ }
+ break;
+ case 'D': case 'd':
+ if (strcasecmp(name, "Date") == 0) {
+ /* Date = HTTP-date */
+ (void)http_date_parse(data, size, &parser->msg.date);
+ return 0;
+ }
+ break;
+ case 'L': case 'l':
+ if (strcasecmp(name, "Location") == 0) {
+ /* Location = URI-reference (not parsed here) */
+ parser->msg.location = hdr->value;
+ return 0;
+ }
+ break;
+ case 'T': case 't':
+ if (strcasecmp(name, "Transfer-Encoding") == 0) {
+ /* Transfer-Encoding = 1#transfer-coding */
+ parser->msg.transfer_encoding = hdr->value;
+ return 0;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+int http_message_parse_headers(struct http_message_parser *parser,
+ const char **error_r)
+{
+ const char *field_name, *error;
+ const unsigned char *field_data;
+ size_t field_size;
+ int ret;
+
+ /* *( header-field CRLF ) CRLF */
+ while ((ret=http_header_parse_next_field
+ (parser->header_parser, &field_name, &field_data, &field_size, &error)) > 0) {
+ if (field_name == NULL) {
+ /* EOH */
+ return 1;
+ }
+ if (http_message_parse_header(parser, field_name, field_data,
+ field_size, error_r) < 0)
+ return -1;
+ }
+
+ if (ret < 0) {
+ *error_r = t_strdup_printf(
+ "Failed to parse response header: %s", error);
+ }
+ return ret;
+}
+
+int http_message_parse_body(struct http_message_parser *parser,
+ const char **error_r)
+{
+ struct http_parser hparser;
+
+ if (parser->msg.content_length > 0) {
+ /* Got explicit message size from Content-Length: header */
+ parser->payload =
+ i_stream_create_limit(parser->input,
+ parser->msg.content_length);
+ } else if (parser->msg.transfer_encoding != NULL) {
+ const char *tenc;
+
+ /* Transfer-Encoding = 1#transfer-coding
+ transfer-coding = "chunked" / "compress" / "deflate" / "gzip"
+ / transfer-extension ; [FIXME]
+ transfer-extension = token *( OWS ";" OWS transfer-parameter )
+ */
+ http_parser_init(&hparser,
+ (const unsigned char *)parser->msg.transfer_encoding,
+ strlen(parser->msg.transfer_encoding));
+ for (;;) {
+ if (http_parse_token_list_next(&hparser, &tenc) <= 0)
+ break;
+ if (strcasecmp(tenc, "chunked") == 0) {
+ parser->payload =
+ http_transfer_chunked_istream_create(parser->input);
+ break; // FIXME
+ } else {
+ *error_r = t_strdup_printf(
+ "Unknown Transfer-Encoding `%s'", tenc);
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
--- /dev/null
+#ifndef HTTP_MESSAGE_PARSER_H
+#define HTTP_MESSAGE_PARSER_H
+
+#include "http-response.h"
+
+struct http_message {
+ unsigned int version_major;
+ unsigned int version_minor;
+
+ ARRAY_TYPE(http_response_header) headers;
+ time_t date;
+
+ uoff_t content_length;
+ const char *location;
+ const char *transfer_encoding;
+
+ unsigned int connection_close:1;
+};
+
+struct http_message_parser {
+ struct istream *input;
+
+ const unsigned char *cur, *end;
+
+ struct http_header_parser *header_parser;
+ struct istream *payload;
+
+ pool_t msg_pool;
+ struct http_message msg;
+};
+
+void http_message_parser_init(struct http_message_parser *parser,
+ struct istream *input);
+void http_message_parser_deinit(struct http_message_parser *parser);
+void http_message_parser_restart(struct http_message_parser *parser);
+
+int http_message_parse_finish_payload(struct http_message_parser *parser,
+ const char **error_r);
+int http_message_parse_version(struct http_message_parser *parser);
+int http_message_parse_headers(struct http_message_parser *parser,
+ const char **error_r);
+int http_message_parse_body(struct http_message_parser *parser,
+ const char **error_r);
+
+#endif
--- /dev/null
+/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "http-parser.h"
+#include "http-message-parser.h"
+#include "http-request-parser.h"
+
+enum http_request_parser_state {
+ HTTP_REQUEST_PARSE_STATE_INIT = 0,
+ HTTP_REQUEST_PARSE_STATE_METHOD,
+ HTTP_REQUEST_PARSE_STATE_SP1,
+ HTTP_REQUEST_PARSE_STATE_TARGET,
+ HTTP_REQUEST_PARSE_STATE_SP2,
+ HTTP_REQUEST_PARSE_STATE_VERSION,
+ HTTP_REQUEST_PARSE_STATE_CR,
+ HTTP_REQUEST_PARSE_STATE_LF,
+ HTTP_REQUEST_PARSE_STATE_HEADER
+};
+
+struct http_request_parser {
+ struct http_message_parser parser;
+ enum http_request_parser_state state;
+
+ struct http_request request;
+};
+
+struct http_request_parser *http_request_parser_init(struct istream *input)
+{
+ struct http_request_parser *parser;
+
+ parser = i_new(struct http_request_parser, 1);
+ http_message_parser_init(&parser->parser, input);
+ return parser;
+}
+
+void http_request_parser_deinit(struct http_request_parser **_parser)
+{
+ struct http_request_parser *parser = *_parser;
+
+ http_message_parser_deinit(&parser->parser);
+ i_free(parser);
+}
+
+static void
+http_request_parser_restart(struct http_request_parser *parser)
+{
+ http_message_parser_restart(&parser->parser);
+ memset(&parser->request, 0, sizeof(parser->request));
+}
+
+static int http_request_parse_method(struct http_request_parser *parser)
+{
+ const unsigned char *p = parser->parser.cur;
+
+ /* method = token
+ */
+ while (p < parser->parser.end && http_char_is_token(*p))
+ p++;
+
+ if (p == parser->parser.end)
+ return 0;
+ parser->request.method =
+ p_strdup_until(parser->parser.msg_pool, parser->parser.cur, p);
+ parser->parser.cur = p;
+ return 1;
+}
+
+static int http_request_parse_target(struct http_request_parser *parser)
+{
+ const unsigned char *p = parser->parser.cur;
+
+ /* We'll just parse anything up to the first SP or a control char.
+ We could also implement workarounds for buggy HTTP clients and
+ parse anything up to the HTTP-version and return 301 with the
+ target properly encoded. */
+ while (p < parser->parser.end && *p > ' ')
+ p++;
+
+ if (p == parser->parser.end)
+ return 0;
+ parser->request.target =
+ p_strdup_until(parser->parser.msg_pool, parser->parser.cur, p);
+ parser->parser.cur = p;
+ 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_request_parse(struct http_request_parser *parser,
+ const char **error_r)
+{
+ 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);
+ parser->state = HTTP_REQUEST_PARSE_STATE_VERSION;
+ if (_parser->cur == _parser->end)
+ return 0;
+ if (*_parser->cur == '\r' || *_parser->cur == '\n') {
+ /* HTTP/1.0 client sent a CRLF after body.
+ ignore it. */
+ parser->state = HTTP_REQUEST_PARSE_STATE_CR;
+ return http_request_parse(parser, error_r);
+ }
+ /* fall through */
+ case HTTP_REQUEST_PARSE_STATE_METHOD:
+ if ((ret=http_request_parse_method(parser)) <= 0) {
+ if (ret < 0)
+ *error_r = "Invalid HTTP method in request";
+ 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 != ' ') {
+ *error_r = t_strdup_printf
+ ("Expected ' ' after request method, but found %s",
+ _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) {
+ if (ret < 0)
+ *error_r = "Invalid HTTP target in request";
+ 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 != ' ') {
+ *error_r = t_strdup_printf
+ ("Expected ' ' after request target, but found %s",
+ _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)
+ *error_r = "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') {
+ *error_r = t_strdup_printf
+ ("Expected line end after request, but found %s",
+ _chr_sanitize(*_parser->cur));
+ return -1;
+ }
+ _parser->cur++;
+ parser->state = HTTP_REQUEST_PARSE_STATE_HEADER;
+ return 1;
+ case HTTP_REQUEST_PARSE_STATE_HEADER:
+ default:
+ i_unreached();
+ }
+ }
+
+ i_unreached();
+ return -1;
+}
+
+static int http_request_parse_request_line(struct http_request_parser *parser,
+ const char **error_r)
+{
+ struct http_message_parser *_parser = &parser->parser;
+ const unsigned char *begin;
+ size_t size, old_bytes = 0;
+ int ret;
+
+ while ((ret = i_stream_read_data(_parser->input, &begin, &size,
+ old_bytes)) > 0) {
+ _parser->cur = begin;
+ _parser->end = _parser->cur + size;
+
+ if ((ret = http_request_parse(parser, error_r)) < 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);
+ }
+
+ i_assert(ret != -2);
+ if (ret < 0) {
+ if (_parser->input->eof &&
+ parser->state == HTTP_REQUEST_PARSE_STATE_INIT)
+ return 0;
+ *error_r = "Stream error";
+ return -1;
+ }
+ return 0;
+}
+
+int http_request_parse_next(struct http_request_parser *parser,
+ struct http_request **request_r,
+ const char **error_r)
+{
+ int ret;
+
+ /* make sure we finished streaming payload from previous request
+ before we continue. */
+ if ((ret = http_message_parse_finish_payload(&parser->parser, error_r)) <= 0)
+ return ret;
+
+ /* HTTP-message = start-line
+ *( header-field CRLF )
+ CRLF
+ [ message-body ]
+ */
+ if (parser->state != HTTP_REQUEST_PARSE_STATE_HEADER) {
+ if ((ret = http_request_parse_request_line(parser, error_r)) <= 0)
+ return ret;
+ }
+ if ((ret = http_message_parse_headers(&parser->parser, error_r)) <= 0)
+ return ret;
+ if (http_message_parse_body(&parser->parser, error_r) < 0)
+ return -1;
+ parser->state = HTTP_REQUEST_PARSE_STATE_INIT;
+
+ parser->request.version_major = parser->parser.msg.version_major;
+ parser->request.version_minor = parser->parser.msg.version_minor;
+ parser->request.date = parser->parser.msg.date;
+ parser->request.payload = parser->parser.payload;
+ parser->request.headers = parser->parser.msg.headers;
+ parser->request.connection_close = parser->parser.msg.connection_close;
+
+ *request_r = &parser->request;
+ return 1;
+}
--- /dev/null
+#ifndef HTTP_REQUEST_PARSER_H
+#define HTTP_REQUEST_PARSER_H
+
+#include "http-response.h"
+
+struct http_request {
+ const char *method;
+ const char *target;
+
+ unsigned char version_major;
+ unsigned char version_minor;
+
+ time_t date;
+ struct istream *payload;
+
+ ARRAY_TYPE(http_response_header) headers;
+
+ unsigned int connection_close:1;
+};
+
+struct http_request_parser *
+http_request_parser_init(struct istream *input);
+void http_request_parser_deinit(struct http_request_parser **_parser);
+
+int http_request_parse_next(struct http_request_parser *parser,
+ struct http_request **request_r,
+ const char **error_r);
+
+#endif
/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
#include "lib.h"
-#include "array.h"
-#include "str.h"
-#include "strfuncs.h"
#include "istream.h"
#include "http-parser.h"
-#include "http-date.h"
-#include "http-header-parser.h"
-#include "http-transfer.h"
-
+#include "http-message-parser.h"
#include "http-response-parser.h"
#include <ctype.h>
};
struct http_response_parser {
- struct istream *input;
-
- const unsigned char *begin, *cur, *end;
- const char *error;
-
+ struct http_message_parser parser;
enum http_response_parser_state state;
- struct http_header_parser *header_parser;
-
- uoff_t content_length;
- const char *transfer_encoding;
- struct istream *payload;
- struct http_response *response;
- pool_t response_pool;
+ struct http_response response;
};
struct http_response_parser *http_response_parser_init(struct istream *input)
struct http_response_parser *parser;
parser = i_new(struct http_response_parser, 1);
- parser->input = input;
+ http_message_parser_init(&parser->parser, input);
return parser;
}
{
struct http_response_parser *parser = *_parser;
- if (parser->header_parser != NULL)
- http_header_parser_deinit(&parser->header_parser);
- if (parser->response_pool != NULL)
- pool_unref(&parser->response_pool);
- if (parser->payload != NULL)
- i_stream_unref(&parser->payload);
+ http_message_parser_deinit(&parser->parser);
i_free(parser);
}
static void
http_response_parser_restart(struct http_response_parser *parser)
{
- i_assert(parser->payload == NULL);
- parser->content_length = 0;
- parser->transfer_encoding = NULL;
- if (parser->response_pool != NULL)
- pool_unref(&parser->response_pool);
- parser->response_pool = pool_alloconly_create("http_response", 4096);
- parser->response = p_new(parser->response_pool, struct http_response, 1);
- parser->response->date = (time_t)-1;
- p_array_init(&parser->response->headers, parser->response_pool, 32);
-}
-
-static int http_response_parse_version(struct http_response_parser *parser)
-{
- const unsigned char *p = parser->cur;
- const size_t size = parser->end - parser->cur;
-
- /* HTTP-version = HTTP-name "/" DIGIT "." DIGIT
- HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive
- */
- if (size < 8)
- return 0;
- if (memcmp(p, "HTTP/", 5) != 0 ||
- !i_isdigit(p[5]) || p[6] != '.' || !i_isdigit(p[7]))
- return -1;
- parser->response->version_major = p[5] - '0';
- parser->response->version_minor = p[7] - '0';
- parser->cur += 8;
- return 1;
+ http_message_parser_restart(&parser->parser);
+ memset(&parser->response, 0, sizeof(parser->response));
}
static int http_response_parse_status(struct http_response_parser *parser)
{
- const unsigned char *p = parser->cur;
- const size_t size = parser->end - parser->cur;
+ const unsigned char *p = parser->parser.cur;
+ const size_t size = parser->parser.end - parser->parser.cur;
/* status-code = 3DIGIT
*/
return 0;
if (!i_isdigit(p[0]) || !i_isdigit(p[1]) || !i_isdigit(p[2]))
return -1;
- parser->response->status =
+ parser->response.status =
(p[0] - '0')*100 + (p[1] - '0')*10 + (p[2] - '0');
- parser->cur += 3;
+ parser->parser.cur += 3;
return 1;
}
static int http_response_parse_reason(struct http_response_parser *parser)
{
- const unsigned char *p = parser->cur;
+ const unsigned char *p = parser->parser.cur;
/* reason-phrase = *( HTAB / SP / VCHAR / obs-text )
*/
- while (p < parser->end && http_char_is_text(*p))
+ while (p < parser->parser.end && http_char_is_text(*p))
p++;
- if (p == parser->end)
+ if (p == parser->parser.end)
return 0;
- parser->response->reason =
- p_strdup_until(parser->response_pool, parser->cur, p);
- parser->cur = p;
+ parser->response.reason =
+ p_strdup_until(parser->parser.msg_pool, parser->parser.cur, p);
+ parser->parser.cur = p;
return 1;
}
return t_strdup_printf("0x%02x", c);
}
-static int http_response_parse(struct http_response_parser *parser)
+static int http_response_parse(struct http_response_parser *parser,
+ const char **error_r)
{
+ struct http_message_parser *_parser = &parser->parser;
int ret;
/* status-line = HTTP-version SP status-code SP reason-phrase CRLF
parser->state = HTTP_RESPONSE_PARSE_STATE_VERSION;
/* fall through */
case HTTP_RESPONSE_PARSE_STATE_VERSION:
- if ((ret=http_response_parse_version(parser)) <= 0) {
+ if ((ret=http_message_parse_version(_parser)) <= 0) {
if (ret < 0)
- parser->error = "Invalid HTTP version in response";
+ *error_r = "Invalid HTTP version in response";
return ret;
}
parser->state = HTTP_RESPONSE_PARSE_STATE_SP1;
- if (parser->cur == parser->end)
+ if (_parser->cur == _parser->end)
return 0;
/* fall through */
case HTTP_RESPONSE_PARSE_STATE_SP1:
- if (*parser->cur != ' ') {
- parser->error = t_strdup_printf
+ if (*_parser->cur != ' ') {
+ *error_r = t_strdup_printf
("Expected ' ' after response version, but found %s",
- _chr_sanitize(*parser->cur));
+ _chr_sanitize(*_parser->cur));
return -1;
}
- parser->cur++;
+ _parser->cur++;
parser->state = HTTP_RESPONSE_PARSE_STATE_STATUS;
- if (parser->cur >= parser->end)
+ 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";
+ *error_r = "Invalid HTTP status code in response";
return ret;
}
parser->state = HTTP_RESPONSE_PARSE_STATE_SP2;
- if (parser->cur == parser->end)
+ if (_parser->cur == _parser->end)
return 0;
/* fall through */
case HTTP_RESPONSE_PARSE_STATE_SP2:
- if (*parser->cur != ' ') {
- parser->error = t_strdup_printf
+ if (*_parser->cur != ' ') {
+ *error_r = t_strdup_printf
("Expected ' ' after response status code, but found %s",
- _chr_sanitize(*parser->cur));
+ _chr_sanitize(*_parser->cur));
return -1;
}
- parser->cur++;
+ _parser->cur++;
parser->state = HTTP_RESPONSE_PARSE_STATE_REASON;
- if (parser->cur >= parser->end)
+ if (_parser->cur >= _parser->end)
return 0;
/* fall through */
case HTTP_RESPONSE_PARSE_STATE_REASON:
- if ((ret=http_response_parse_reason(parser)) <= 0)
- return ret;
+ 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)
+ if (_parser->cur == _parser->end)
return 0;
/* fall through */
case HTTP_RESPONSE_PARSE_STATE_CR:
- if (*parser->cur == '\r')
- parser->cur++;
+ if (*_parser->cur == '\r')
+ _parser->cur++;
parser->state = HTTP_RESPONSE_PARSE_STATE_LF;
- if (parser->cur == parser->end)
+ if (_parser->cur == _parser->end)
return 0;
/* fall through */
case HTTP_RESPONSE_PARSE_STATE_LF:
- if (*parser->cur != '\n') {
- parser->error = t_strdup_printf
+ if (*_parser->cur != '\n') {
+ *error_r = t_strdup_printf
("Expected line end after response, but found %s",
- _chr_sanitize(*parser->cur));
+ _chr_sanitize(*_parser->cur));
return -1;
}
- parser->cur++;
+ _parser->cur++;
parser->state = HTTP_RESPONSE_PARSE_STATE_HEADER;
return 1;
case HTTP_RESPONSE_PARSE_STATE_HEADER:
return -1;
}
-static int http_response_parse_status_line(struct http_response_parser *parser)
+static int http_response_parse_status_line(struct http_response_parser *parser,
+ const char **error_r)
{
+ struct http_message_parser *_parser = &parser->parser;
+ const unsigned char *begin;
size_t size, old_bytes = 0;
int ret;
- while ((ret = i_stream_read_data(parser->input,
- &parser->begin, &size, old_bytes)) > 0) {
- parser->cur = parser->begin;
- parser->end = parser->cur + size;
+ while ((ret = i_stream_read_data(_parser->input, &begin, &size,
+ old_bytes)) > 0) {
+ _parser->cur = begin;
+ _parser->end = _parser->cur + size;
- if ((ret = http_response_parse(parser)) < 0)
+ if ((ret = http_response_parse(parser, error_r)) < 0)
return -1;
- i_stream_skip(parser->input, parser->cur - parser->begin);
+ i_stream_skip(_parser->input, _parser->cur - begin);
if (ret > 0)
return 1;
- old_bytes = i_stream_get_data_size(parser->input);
+ old_bytes = i_stream_get_data_size(_parser->input);
}
i_assert(ret != -2);
if (ret < 0) {
- if (parser->input->eof && parser->state == HTTP_RESPONSE_PARSE_STATE_INIT)
+ if (_parser->input->eof &&
+ parser->state == HTTP_RESPONSE_PARSE_STATE_INIT)
return 0;
- parser->error = "Stream error";
+ *error_r = "Stream error";
return -1;
}
return 0;
}
-static int
-http_response_parse_header(struct http_response_parser *parser,
- const char *name, const unsigned char *data, size_t size)
-{
- struct http_response_header *hdr;
- struct http_parser hparser;
- void *value;
-
- hdr = array_append_space(&parser->response->headers);
- hdr->key = p_strdup(parser->response_pool, name);
- hdr->value = value = p_malloc(parser->response_pool, size+1);
- memcpy(value, data, size);
- hdr->size = size;
-
- switch (name[0]) {
- case 'C': case 'c':
- if (strcasecmp(name, "Connection") == 0) {
- const char *option;
-
- /* Connection = 1#connection-option
- connection-option = token
- */
- http_parser_init(&hparser, data, size);
- for (;;) {
- if (http_parse_token_list_next(&hparser, &option) <= 0)
- break;
- if (strcasecmp(option, "close") == 0) {
- parser->response->connection_close = TRUE;
- break; // not interested in any other options
- }
- }
- return 1;
- }
- if (strcasecmp(name, "Content-Length") == 0) {
- /* Content-Length = 1*DIGIT */
- if (str_to_uoff(hdr->value, &parser->content_length) < 0) {
- parser->error = "Invalid Content-Length header";
- return -1;
- }
- return 1;
- }
- break;
- case 'D': case 'd':
- if (strcasecmp(name, "Date") == 0) {
- /* Date = HTTP-date */
- (void)http_date_parse(data, size, &parser->response->date);
- return 1;
- }
- break;
- case 'L': case 'l':
- if (strcasecmp(name, "Location") == 0) {
- /* Location = URI-reference (not parsed here) */
- parser->response->location =
- p_strndup(parser->response_pool, data, size);
- return 1;
- }
- break;
- case 'T': case 't':
- if (strcasecmp(name, "Transfer-Encoding") == 0) {
- /* Transfer-Encoding = 1#transfer-coding */
- parser->transfer_encoding = hdr->value;
- return 1;
- }
- break;
- default:
- break;
- }
- return 1;
-}
-
int http_response_parse_next(struct http_response_parser *parser,
bool no_payload, struct http_response **response_r,
const char **error_r)
{
- struct http_parser hparser;
- const char *field_name, *error;
- const unsigned char *field_data;
- size_t field_size;
int ret;
/* make sure we finished streaming payload from previous response
before we continue. */
- if (parser->payload != NULL) {
- struct istream *payload = parser->payload;
- const unsigned char *data;
- size_t size;
-
- i_assert(parser->state == HTTP_RESPONSE_PARSE_STATE_INIT);
-
- while ((ret = i_stream_read_data(payload, &data, &size, 0)) > 0)
- i_stream_skip(payload, size);
- if (ret == 0 || payload->stream_errno != 0) {
- if (ret < 0)
- *error_r = "Stream error while skipping payload";
- return ret;
- }
- i_stream_unref(&parser->payload);
- }
+ if ((ret = http_message_parse_finish_payload(&parser->parser, error_r)) <= 0)
+ return ret;
/* HTTP-message = start-line
*( header-field CRLF )
CRLF
[ message-body ]
*/
-
- /* start-line */
if (parser->state != HTTP_RESPONSE_PARSE_STATE_HEADER) {
- if ((ret=http_response_parse_status_line(parser)) <= 0) {
- *error_r = parser->error;
+ if ((ret = http_response_parse_status_line(parser, error_r)) <= 0)
return ret;
- }
- /* *( header-field CRLF ) CRLF */
- if (parser->header_parser == NULL)
- parser->header_parser = http_header_parser_init(parser->input);
- else
- http_header_parser_reset(parser->header_parser);
}
-
- while ((ret=http_header_parse_next_field
- (parser->header_parser, &field_name, &field_data, &field_size, &error)) > 0) {
- if (field_name == NULL) break;
- if ((ret=http_response_parse_header
- (parser, field_name, field_data, field_size)) < 0) {
- *error_r = parser->error;
- return -1;
- }
- }
-
- if (ret <= 0) {
- if (ret < 0)
- *error_r = t_strdup_printf("Failed to parse response header: %s", error);
+ if ((ret = http_message_parse_headers(&parser->parser, error_r)) <= 0)
return ret;
- }
/* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21
Section 3.3.2:
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->content_length > 0) {
+ 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->content_length);
+ "(length=%"PRIuUOFF_T")", parser->response.status,
+ parser->parser.msg.content_length);
return -1;
}
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
+ if (parser->response.status / 100 == 1 || parser->response.status == 204
+ || parser->response.status == 304) { // HEAD is handled in caller
no_payload = TRUE;
}
if (!no_payload) {
/* [ message-body ] */
- if (parser->content_length > 0) {
- /* Got explicit message size from Content-Length: header */
- parser->payload = parser->response->payload =
- i_stream_create_limit(parser->input, parser->content_length);
- } else if (parser->transfer_encoding != NULL) {
- const char *tenc;
-
- /* Transfer-Encoding = 1#transfer-coding
- transfer-coding = "chunked" / "compress" / "deflate" / "gzip"
- / transfer-extension ; [FIXME]
- transfer-extension = token *( OWS ";" OWS transfer-parameter )
- */
- http_parser_init(&hparser,
- (const unsigned char *)parser->transfer_encoding,
- strlen(parser->transfer_encoding));
- for (;;) {
- if (http_parse_token_list_next(&hparser, &tenc) <= 0)
- break;
- if (strcasecmp(tenc, "chunked") == 0) {
- parser->payload = parser->response->payload =
- http_transfer_chunked_istream_create(parser->input);
- break; // FIXME
- } else {
- *error_r = t_strdup_printf(
- "Unkown Transfer-Encoding `%s' for %u response",
- tenc, parser->response->status);
- return -1;
- }
- }
- }
+ if (http_message_parse_body(&parser->parser, error_r) < 0)
+ return -1;
}
-
parser->state = HTTP_RESPONSE_PARSE_STATE_INIT;
- *response_r = parser->response;
+
+ parser->response.version_major = parser->parser.msg.version_major;
+ parser->response.version_minor = parser->parser.msg.version_minor;
+ parser->response.location = parser->parser.msg.location;
+ parser->response.date = parser->parser.msg.date;
+ parser->response.payload = parser->parser.payload;
+ parser->response.headers = parser->parser.msg.headers;
+ parser->response.connection_close = parser->parser.msg.connection_close;
+
+ *response_r = &parser->response;
return 1;
}