}
/* start protocol I/O */
- conn->http_parser = http_response_parser_init(conn->conn.input);
+ conn->http_parser = http_response_parser_init
+ (conn->conn.input, &conn->client->set.response_hdr_limits);
o_stream_set_flush_callback(conn->conn.output,
http_client_connection_output, conn);
}
(set->max_pipelined_requests > 0 ? set->max_pipelined_requests : 1);
client->set.max_attempts = set->max_attempts;
client->set.max_redirects = set->max_redirects;
+ client->set.response_hdr_limits = set->response_hdr_limits;
client->set.request_timeout_msecs = set->request_timeout_msecs;
client->set.connect_timeout_msecs = set->connect_timeout_msecs;
client->set.soft_connect_timeout_msecs = set->soft_connect_timeout_msecs;
/* maximum number of attempts for a request */
unsigned int max_attempts;
+ /* response header limits */
+ struct http_header_limits response_hdr_limits;
+
/* max time to wait for HTTP request to finish before retrying
(default = unlimited) */
unsigned int request_timeout_msecs;
#include "str.h"
#include "str-sanitize.h"
#include "http-parser.h"
+#include "http-header.h"
#include "http-header-parser.h"
struct http_header_parser {
struct istream *input;
+ struct http_header_limits limits;
+ uoff_t size, field_size;
+ unsigned int field_count;
+
const unsigned char *begin, *cur, *end;
const char *error;
enum http_header_parse_state state;
};
-// FIXME(Stephan): Add support for limiting maximum header size.
-
-struct http_header_parser *http_header_parser_init(struct istream *input)
+struct http_header_parser *
+http_header_parser_init(struct istream *input,
+ const struct http_header_limits *limits)
{
struct http_header_parser *parser;
parser->name = str_new(default_pool, 128);
parser->value_buf = buffer_create_dynamic(default_pool, 4096);
+ if (limits != NULL)
+ parser->limits = *limits;
+
+ if (parser->limits.max_size == 0)
+ parser->limits.max_size = (uoff_t)-1;
+ if (parser->limits.max_field_size == 0)
+ parser->limits.max_field_size = (uoff_t)-1;
+ if (parser->limits.max_fields == 0)
+ parser->limits.max_fields = (unsigned int)-1;
+
return 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)
if (http_char_is_token(*parser->cur)) {
if ((ret=http_header_parse_name(parser)) <= 0)
return ret;
- } else if (str_len(parser->name) == 0) {
+ } else if (*parser->cur != ':' && str_len(parser->name) == 0) {
parser->state = HTTP_HEADER_PARSE_STATE_LAST_LINE;
break;
}
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:
parser->state = HTTP_HEADER_PARSE_STATE_OWS;
break;
}
- parser->state = HTTP_HEADER_PARSE_STATE_NAME;
+ parser->state = HTTP_HEADER_PARSE_STATE_INIT;
return 1;
case HTTP_HEADER_PARSE_STATE_LAST_LINE:
if (*parser->cur == '\r') {
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;
+ uoff_t size;
int ret;
+ *error_r = NULL;
+
while ((ret=i_stream_read_data
(parser->input, &parser->begin, &size, 0)) > 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;
}
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);
#ifndef HTTP_HEADER_PARSER_H
#define HTTP_HEADER_PARSER_H
+struct http_header_limits;
struct http_header_parser;
-struct http_header_parser *http_header_parser_init(struct istream *input);
+struct http_header_parser *
+http_header_parser_init(struct istream *input,
+ const struct http_header_limits *limits);
void http_header_parser_deinit(struct http_header_parser **_parser);
void http_header_parser_reset(struct http_header_parser *parser);
struct http_header;
+struct http_header_limits {
+ uoff_t max_size;
+ uoff_t max_field_size;
+ unsigned int max_fields;
+};
+
struct http_header_field {
const char *key; /* FIXME: rename to 'name' for v2.3 */
const char *value;
#include <ctype.h>
void http_message_parser_init(struct http_message_parser *parser,
- struct istream *input)
+ struct istream *input, const struct http_header_limits *hdr_limits)
{
memset(parser, 0, sizeof(*parser));
parser->input = input;
+ if (hdr_limits != NULL)
+ parser->header_limits = *hdr_limits;
}
void http_message_parser_deinit(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
+ if (parser->header_parser == NULL) {
+ parser->header_parser =
+ http_header_parser_init(parser->input, &parser->header_limits);
+ } else {
http_header_parser_reset(parser->header_parser);
+ }
if (parser->msg.pool != NULL)
pool_unref(&parser->msg.pool);
#include "http-response.h"
#include "http-transfer.h"
-struct http_header;
+#include "http-header.h"
struct http_message {
pool_t pool;
struct http_message_parser {
struct istream *input;
+ struct http_header_limits header_limits;
const unsigned char *cur, *end;
};
void http_message_parser_init(struct http_message_parser *parser,
- struct istream *input);
+ struct istream *input, const struct http_header_limits *hdr_limits)
+ ATTR_NULL(3);
void http_message_parser_deinit(struct http_message_parser *parser);
void http_message_parser_restart(struct http_message_parser *parser,
pool_t pool);
unsigned int skipping_line:1;
};
-struct http_request_parser *http_request_parser_init(struct istream *input)
+struct http_request_parser *
+http_request_parser_init(struct istream *input,
+ const struct http_header_limits *hdr_limits)
{
struct http_request_parser *parser;
parser = i_new(struct http_request_parser, 1);
- http_message_parser_init(&parser->parser, input);
+ http_message_parser_init(&parser->parser, input, hdr_limits);
return parser;
}
#include "http-request.h"
struct http_request_parser *
-http_request_parser_init(struct istream *input);
+http_request_parser_init(struct istream *input,
+ const struct http_header_limits *hdr_limits) ATTR_NULL(2);
void http_request_parser_deinit(struct http_request_parser **_parser);
int http_request_parse_next(struct http_request_parser *parser,
const char *response_reason;
};
-struct http_response_parser *http_response_parser_init(struct istream *input)
+struct http_response_parser *
+http_response_parser_init(struct istream *input,
+ const struct http_header_limits *hdr_limits)
{
struct http_response_parser *parser;
+ /* FIXME: implement status line limit */
parser = i_new(struct http_response_parser, 1);
- http_message_parser_init(&parser->parser, input);
+ http_message_parser_init(&parser->parser, input, hdr_limits);
return parser;
}
#include "http-response.h"
+struct http_header_limits;
struct http_response_parser;
struct http_response_parser *
-http_response_parser_init(struct istream *input);
+http_response_parser_init(struct istream *input,
+ const struct http_header_limits *hdr_limits) ATTR_NULL(2);
void http_response_parser_deinit(struct http_response_parser **_parser);
int http_response_parse_next(struct http_response_parser *parser,
int ret;
if (tcstream->header_parser == NULL) {
- tcstream->header_parser = http_header_parser_init(tcstream->istream.parent);
+ /* FIXME: limit trailer size */
+ tcstream->header_parser =
+ http_header_parser_init(tcstream->istream.parent, 0);
}
while ((ret=http_header_parse_next_field(tcstream->header_parser,
#include "test-lib.h"
#include "istream.h"
#include "test-common.h"
+#include "http-response.h"
#include "http-header-parser.h"
#include <time.h>
struct http_header_parse_test {
const char *header;
+ struct http_header_limits limits;
const struct http_header_parse_result *fields;
};
"Content-Type: text/html; charset=utf-8\r\n"
"Content-Encoding: gzip\r\n"
"\r\n",
- .fields = valid_header_parse_result4
+ .fields = valid_header_parse_result4,
+ .limits = {
+ .max_size = 340,
+ .max_field_size = 46,
+ .max_fields = 12
+ }
},{
.header =
"\r\n",
for (i = 0; i < valid_header_parse_test_count; i++) T_BEGIN {
struct istream *input;
struct http_header_parser *parser;
- const char *header, *field_name, *error;
+ const struct http_header_limits *limits;
+ const char *header, *field_name, *error = NULL;
const unsigned char *field_data;
size_t field_size;
int ret;
header = valid_header_parse_tests[i].header;
header_len = strlen(header);
+ limits = &valid_header_parse_tests[i].limits;
input = test_istream_create_data(header, header_len);
- parser = http_header_parser_init(input);
+ parser = http_header_parser_init(input, limits);
test_begin(t_strdup_printf("http header valid [%d]", i));
j++;
}
- test_out("parse success", ret > 0);
+ test_out_reason("parse success", ret > 0, error);
test_end();
http_header_parser_deinit(&parser);
} T_END;
}
-static const char *invalid_header_parse_tests[] = {
- "Date: Sat, 06 Oct 2012 16:01:44 GMT\r\n"
- "Server : Apache/2.2.16 (Debian)\r\n"
- "Last-Modified: Mon, 30 Jul 2012 11:09:28 GMT\r\n"
- "\r\n",
- "Date: Sat, 06 Oct 2012 17:18:22 GMT\r\n"
- "Server: Apache/2.2.3 (CentOS)\r\n"
- "X Powered By: PHP/5.3.6\r\n"
- "\r\n",
- "Host: www.example.com\n\r"
- "Accept: image/png,image/*;q=0.8,*/*;q=0.5\n\r"
- "Accept-Language: en-us,en;q=0.5\n\r"
- "Accept-Encoding: gzip, deflate\n\r"
- "\n\r",
- "Host: p5-lrqzb4yavu4l7nagydw-428649-i2-v6exp3-ds.metric.example.com\n"
- "User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0)\n"
- "Accept:\t\timage/png,image/*;q=0.8,*/\177;q=0.5\n"
- "\n",
- "Date: Sat, 06 Oct 2012 17:12:37 GMT\r\n"
- "Server: Apache/2.2.16 (Debian) PHP/5.3.3-7+squeeze14 with\r\n"
- "Suhosin-Patch proxy_html/3.0.1 mod_python/3.3.1 Python/2.6.6\r\n"
- "mod_ssl/2.2.16 OpenSSL/0.9.8o mod_perl/2.0.4 Perl/v5.10.1\r\n"
- "\r\n",
+static const struct http_header_parse_test invalid_header_parse_tests[] = {
+ {
+ .header =
+ "Date: Sat, 06 Oct 2012 16:01:44 GMT\r\n"
+ "Server : Apache/2.2.16 (Debian)\r\n"
+ "Last-Modified: Mon, 30 Jul 2012 11:09:28 GMT\r\n"
+ "\r\n"
+ },{
+ .header =
+ "Date: Sat, 06 Oct 2012 17:18:22 GMT\r\n"
+ "Server: Apache/2.2.3 (CentOS)\r\n"
+ "X Powered By: PHP/5.3.6\r\n"
+ "\r\n"
+ },{
+ .header =
+ "Host: www.example.com\n\r"
+ "Accept: image/png,image/*;q=0.8,*/*;q=0.5\n\r"
+ "Accept-Language: en-us,en;q=0.5\n\r"
+ "Accept-Encoding: gzip, deflate\n\r"
+ "\n\r"
+ },{
+ .header =
+ "Host: p5-lrqzb4yavu4l7nagydw-428649-i2-v6exp3-ds.metric.example.com\n"
+ "User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0)\n"
+ "Accept:\t\timage/png,image/*;q=0.8,*/\177;q=0.5\n"
+ "\n"
+ },{
+ .header =
+ "Date: Sat, 06 Oct 2012 17:12:37 GMT\r\n"
+ "Server: Apache/2.2.16 (Debian) PHP/5.3.3-7+squeeze14 with\r\n"
+ "Suhosin-Patch proxy_html/3.0.1 mod_python/3.3.1 Python/2.6.6\r\n"
+ "mod_ssl/2.2.16 OpenSSL/0.9.8o mod_perl/2.0.4 Perl/v5.10.1\r\n"
+ "\r\n"
+ },{
+ .header =
+ "Age: 58 \r\n"
+ "Date: Sun, 04 Aug 2013 09:33:09 GMT\r\n"
+ "Expires: Sun, 04 Aug 2013 09:34:08 GMT\r\n"
+ "Cache-Control: max-age=60 \r\n"
+ "Content-Length: 17336 \r\n"
+ "Connection: Keep-Alive\r\n"
+ "Via: NS-CACHE-9.3\r\n"
+ "Server: Apache\r\n"
+ "Vary: Host\r\n"
+ "Last-Modified: Sun, 04 Aug 2013 09:33:07 GMT\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n"
+ "Content-Encoding: gzip\r\n"
+ "\r\n",
+ .limits = { .max_size = 339 }
+ },{
+ .header =
+ "Age: 58 \r\n"
+ "Date: Sun, 04 Aug 2013 09:33:09 GMT\r\n"
+ "Expires: Sun, 04 Aug 2013 09:34:08 GMT\r\n"
+ "Cache-Control: max-age=60 \r\n"
+ "Content-Length: 17336 \r\n"
+ "Connection: Keep-Alive\r\n"
+ "Via: NS-CACHE-9.3\r\n"
+ "Server: Apache\r\n"
+ "Vary: Host\r\n"
+ "Last-Modified: Sun, 04 Aug 2013 09:33:07 GMT\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n"
+ "Content-Encoding: gzip\r\n"
+ "\r\n",
+ .fields = valid_header_parse_result4,
+ .limits = { .max_field_size = 45 }
+ },{
+ .header =
+ "Age: 58 \r\n"
+ "Date: Sun, 04 Aug 2013 09:33:09 GMT\r\n"
+ "Expires: Sun, 04 Aug 2013 09:34:08 GMT\r\n"
+ "Cache-Control: max-age=60 \r\n"
+ "Content-Length: 17336 \r\n"
+ "Connection: Keep-Alive\r\n"
+ "Via: NS-CACHE-9.3\r\n"
+ "Server: Apache\r\n"
+ "Vary: Host\r\n"
+ "Last-Modified: Sun, 04 Aug 2013 09:33:07 GMT\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n"
+ "Content-Encoding: gzip\r\n"
+ "\r\n",
+ .fields = valid_header_parse_result4,
+ .limits = { .max_fields = 11 }
+ }
};
unsigned int invalid_header_parse_test_count = N_ELEMENTS(invalid_header_parse_tests);
for (i = 0; i < invalid_header_parse_test_count; i++) T_BEGIN {
struct istream *input;
struct http_header_parser *parser;
- const char *header, *field_name, *error;
+ const struct http_header_limits *limits;
+ const char *header, *field_name, *error = NULL;
const unsigned char *field_data;
size_t field_size;
int ret;
- header = invalid_header_parse_tests[i];
+ header = invalid_header_parse_tests[i].header;
+ limits = &invalid_header_parse_tests[i].limits;
input = i_stream_create_from_data(header, strlen(header));
- parser = http_header_parser_init(input);
+ parser = http_header_parser_init(input, limits);
test_begin(t_strdup_printf("http header invalid [%d]", i));
if (field_name == NULL) break;
}
- test_out("parse failure", ret < 0);
+ test_out_reason("parse failure", ret < 0, error);
test_end();
http_header_parser_deinit(&parser);
} T_END;
response_text = test->response;
response_text_len = strlen(response_text);
input = test_istream_create_data(response_text, response_text_len);
- parser = http_response_parser_init(input);
+ parser = http_response_parser_init(input, NULL);
test_begin(t_strdup_printf("http response valid [%d]", i));
test = invalid_response_parse_tests[i];
response_text = test;
input = i_stream_create_from_data(response_text, strlen(response_text));
- parser = http_response_parser_init(input);
+ parser = http_response_parser_init(input, NULL);
test_begin(t_strdup_printf("http response invalid [%d]", i));
test_begin("http response with NULs");
input = i_stream_create_from_data(invalid_response_with_nuls,
sizeof(invalid_response_with_nuls)-1);
- parser = http_response_parser_init(input);
+ parser = http_response_parser_init(input, 0);
while ((ret=http_response_parse_next(parser, FALSE, &response, &error)) > 0);
test_assert(ret < 0);
test_end();
client = i_new(struct client, 1);
connection_init_server(clients, &client->conn,
"(http client)", fd, fd);
- client->parser = http_request_parser_init(client->conn.input);
+ client->parser = http_request_parser_init(client->conn.input, 0);
}
static void client_accept(void *context ATTR_UNUSED)