httpd_parse.c
#define EX_UTILS_NO_USE_INIT 1
#define EX_UTILS_NO_USE_EXIT 1
#define EX_UTILS_NO_USE_LIMIT 1
#define EX_UTILS_NO_USE_BLOCK 1
#define EX_UTILS_NO_USE_GET 1
#define EX_UTILS_NO_USE_PUT 1
#define EX_UTILS_RET_FAIL 1
#include "ex_utils.h"
#include "mk.h"
#include "vlg.h"
#define HTTPD_HAVE_GLOBAL_OPTS 1
#include "httpd.h"
#include "httpd_policy.h"
#if ! COMPILE_DEBUG
# define HTTP_CONF_MMAP_LIMIT_MIN (16 * 1024)
# define HTTP_CONF_SAFE_PRINT_REQ TRUE
#else
# define HTTP_CONF_MMAP_LIMIT_MIN 8
# define HTTP_CONF_SAFE_PRINT_REQ FALSE
#endif
#define HTTP_CONF_MMAP_LIMIT_MAX (50 * 1024 * 1024)
#define HTTPD_CONF_ZIP_LIMIT_MIN 8
#define CLEN COMPILE_STRLEN
#define VPREFIX(vstr, p, l, cstr) \
(((l) >= CLEN(cstr)) && \
vstr_cmp_buf_eq(vstr, p, CLEN(cstr), cstr, CLEN(cstr)))
#define VSUFFIX(vstr, p, l, cstr) \
(((l) >= CLEN(cstr)) && \
vstr_cmp_eod_buf_eq(vstr, p, l, cstr, CLEN(cstr)))
#define VIPREFIX(vstr, p, l, cstr) \
(((l) >= CLEN(cstr)) && \
vstr_cmp_case_buf_eq(vstr, p, CLEN(cstr), cstr, CLEN(cstr)))
#define VEQ(vstr, p, l, cstr) vstr_cmp_cstr_eq(vstr, p, l, cstr)
#define VIEQ(vstr, p, l, cstr) vstr_cmp_case_cstr_eq(vstr, p, l, cstr)
#define HTTP__HDR_SET(req, h, p, l) do { \
(req)-> http_hdrs -> hdr_ ## h ->pos = (p); \
(req)-> http_hdrs -> hdr_ ## h ->len = (l); \
} while (FALSE)
#define HTTP__HDR_MULTI_SET(req, h, p, l) do { \
(req)-> http_hdrs -> multi -> hdr_ ## h ->pos = (p); \
(req)-> http_hdrs -> multi -> hdr_ ## h ->len = (l); \
} while (FALSE)
#define HTTP__XTRA_HDR_INIT(x) do { \
req-> x ## _vs1 = NULL; \
req-> x ## _pos = 0; \
req-> x ## _len = 0; \
} while (FALSE)
#include <syslog.h>
static Vlg *vlg = NULL;
void httpd_parse_init(Vlg *passed_vlg)
{
ASSERT(passed_vlg && !vlg);
vlg = passed_vlg;
}
void httpd_parse_exit(void)
{
ASSERT(vlg);
vlg = NULL;
}
void http_parse_skip_lws(const Vstr_base *s1, size_t *pos, size_t *len)
{
size_t lws__len = 0;
ASSERT(s1 && pos && len);
while (TRUE)
{
if (VPREFIX(s1, *pos, *len, HTTP_EOL))
{
*len -= CLEN(HTTP_EOL); *pos += CLEN(HTTP_EOL);
}
else if (lws__len)
break;
if (!(lws__len = vstr_spn_cstr_chrs_fwd(s1, *pos, *len, HTTP_LWS)))
break;
*len -= lws__len;
*pos += lws__len;
}
}
int http_parse_version(struct Con *con, struct Httpd_req_data *req)
{
Vstr_base *data = con->evnt->io_r;
size_t op_pos = VSTR_SECTS_NUM(req->sects, 3)->pos;
size_t op_len = VSTR_SECTS_NUM(req->sects, 3)->len;
unsigned int major = 0;
unsigned int minor = 0;
size_t num_len = 0;
unsigned int num_flags = 10 | (VSTR_FLAG_PARSE_NUM_NO_BEG_PM |
VSTR_FLAG_PARSE_NUM_OVERFLOW);
if (!VPREFIX(data, op_pos, op_len, "HTTP"))
HTTPD_ERR_MSG_RET(req, 400, "<PROTO>/Version", FALSE);
op_len -= CLEN("HTTP"); op_pos += CLEN("HTTP");
HTTP_SKIP_LWS(data, op_pos, op_len);
if (!VPREFIX(data, op_pos, op_len, "/"))
HTTPD_ERR_MSG_RET(req, 400, "HTTP/Version", FALSE);
op_len -= CLEN("/"); op_pos += CLEN("/");
HTTP_SKIP_LWS(data, op_pos, op_len);
major = vstr_parse_uint(data, op_pos, op_len, num_flags, &num_len, NULL);
op_len -= num_len; op_pos += num_len;
HTTP_SKIP_LWS(data, op_pos, op_len);
if (!num_len || !VPREFIX(data, op_pos, op_len, "."))
HTTPD_ERR_MSG_RET(req, 400, "HTTP/Version", FALSE);
op_len -= CLEN("."); op_pos += CLEN(".");
HTTP_SKIP_LWS(data, op_pos, op_len);
minor = vstr_parse_uint(data, op_pos, op_len, num_flags, &num_len, NULL);
op_len -= num_len; op_pos += num_len;
HTTP_SKIP_LWS(data, op_pos, op_len);
if (!num_len || op_len)
HTTPD_ERR_MSG_RET(req, 400, "HTTP/Version", FALSE);
if (0) { }
else if ((major == 1) && (minor > 1))
req->ver_1_x = TRUE;
else if ((major == 1) && (minor == 1))
req->ver_1_1 = TRUE;
else if ((major == 1) && (minor == 0))
{ }
else
HTTPD_ERR_MSG_RET(req, 505, "HTTP/Version", FALSE);
return (TRUE);
}
void http_parse_clear_hdrs(struct Httpd_req_data *req)
{
Vstr_base *tmp = req->http_hdrs->multi->combiner_store;
ASSERT(tmp);
HTTP__HDR_SET(req, ua, 0, 0);
HTTP__HDR_SET(req, referer, 0, 0);
HTTP__HDR_SET(req, authorization, 0, 0);
HTTP__HDR_SET(req, expect, 0, 0);
HTTP__HDR_SET(req, host, 0, 0);
req->http_host_port = 80;
HTTP__HDR_SET(req, if_modified_since, 0, 0);
HTTP__HDR_SET(req, if_range, 0, 0);
HTTP__HDR_SET(req, if_unmodified_since, 0, 0);
HTTP__HDR_SET(req, range, 0, 0);
HTTP__HDR_SET(req, x_moz, 0, 0);
vstr_del(tmp, 1, tmp->len);
HTTP__HDR_MULTI_SET(req, accept, 0, 0);
HTTP__HDR_MULTI_SET(req, accept_charset, 0, 0);
HTTP__HDR_MULTI_SET(req, accept_encoding, 0, 0);
HTTP__HDR_MULTI_SET(req, accept_language, 0, 0);
HTTP__HDR_MULTI_SET(req, connection, 0, 0);
HTTP__HDR_MULTI_SET(req, if_match, 0, 0);
HTTP__HDR_MULTI_SET(req, if_none_match, 0, 0);
if (req->xtra_content)
vstr_del(req->xtra_content, 1, req->xtra_content->len);
HTTP__XTRA_HDR_INIT(content_type);
HTTP__XTRA_HDR_INIT(content_disposition);
HTTP__XTRA_HDR_INIT(content_language);
HTTP__XTRA_HDR_INIT(content_location);
HTTP__XTRA_HDR_INIT(content_md5);
HTTP__XTRA_HDR_INIT(gzip_content_md5);
HTTP__XTRA_HDR_INIT(bzip2_content_md5);
HTTP__XTRA_HDR_INIT(cache_control);
HTTP__XTRA_HDR_INIT(etag);
HTTP__XTRA_HDR_INIT(expires);
HTTP__XTRA_HDR_INIT(link);
HTTP__XTRA_HDR_INIT(p3p);
HTTP__XTRA_HDR_INIT(ext_vary_a);
HTTP__XTRA_HDR_INIT(ext_vary_ac);
HTTP__XTRA_HDR_INIT(ext_vary_al);
}
static void http__multi_hdr_fixup(Vstr_sect_node *hdr_ignore,
Vstr_sect_node *hdr, size_t pos, size_t len)
{
if (hdr == hdr_ignore)
return;
if (hdr->pos <= pos)
return;
hdr->pos += len;
}
static int http__multi_hdr_cp(Vstr_base *comb,
Vstr_base *data, Vstr_sect_node *hdr)
{
size_t pos = comb->len + 1;
if (!hdr->len)
return (TRUE);
if (!vstr_add_vstr(comb, comb->len,
data, hdr->pos, hdr->len, VSTR_TYPE_ADD_BUF_PTR))
return (FALSE);
hdr->pos = pos;
return (TRUE);
}
static int http__app_multi_hdr(Vstr_base *data, struct Http_hdrs *hdrs,
Vstr_sect_node *hdr, size_t pos, size_t len)
{
Vstr_base *comb = hdrs->multi->comb;
ASSERT(comb);
ASSERT((hdr == hdrs->multi->hdr_accept) ||
(hdr == hdrs->multi->hdr_accept_charset) ||
(hdr == hdrs->multi->hdr_accept_encoding) ||
(hdr == hdrs->multi->hdr_accept_language) ||
(hdr == hdrs->multi->hdr_connection) ||
(hdr == hdrs->multi->hdr_if_match) ||
(hdr == hdrs->multi->hdr_if_none_match));
ASSERT((comb == data) || (comb == hdrs->multi->combiner_store));
if ((data == comb) && !hdr->pos)
{
hdr->pos = pos;
hdr->len = len;
return (TRUE);
}
if (data == comb)
{
comb = hdrs->multi->comb = hdrs->multi->combiner_store;
if (!http__multi_hdr_cp(comb, data, hdrs->multi->hdr_accept) ||
!http__multi_hdr_cp(comb, data, hdrs->multi->hdr_accept_charset) ||
!http__multi_hdr_cp(comb, data, hdrs->multi->hdr_accept_encoding) ||
!http__multi_hdr_cp(comb, data, hdrs->multi->hdr_accept_language) ||
!http__multi_hdr_cp(comb, data, hdrs->multi->hdr_connection) ||
!http__multi_hdr_cp(comb, data, hdrs->multi->hdr_if_match) ||
!http__multi_hdr_cp(comb, data, hdrs->multi->hdr_if_none_match) ||
FALSE)
return (FALSE);
}
if (!hdr->pos)
{
hdr->pos = comb->len + 1;
hdr->len = len;
return (vstr_add_vstr(comb, comb->len,
data, pos, len, VSTR_TYPE_ADD_BUF_PTR));
}
if (!vstr_add_cstr_ptr(comb, hdr->pos - 1, ","))
return (FALSE);
if (!vstr_add_vstr(comb, hdr->pos - 1,
data, pos, len, VSTR_TYPE_ADD_BUF_PTR))
return (FALSE);
hdr->len += ++len;
pos = hdr->pos - 1;
http__multi_hdr_fixup(hdr, hdrs->multi->hdr_accept, pos, len);
http__multi_hdr_fixup(hdr, hdrs->multi->hdr_accept_charset, pos, len);
http__multi_hdr_fixup(hdr, hdrs->multi->hdr_accept_encoding, pos, len);
http__multi_hdr_fixup(hdr, hdrs->multi->hdr_accept_language, pos, len);
http__multi_hdr_fixup(hdr, hdrs->multi->hdr_connection, pos, len);
http__multi_hdr_fixup(hdr, hdrs->multi->hdr_if_match, pos, len);
http__multi_hdr_fixup(hdr, hdrs->multi->hdr_if_none_match, pos, len);
return (TRUE);
}
static int http__hdr_eq(struct Con *con, size_t pos, size_t len,
const char *hdr, size_t hdr_len, size_t *hdr_val_pos)
{
Vstr_base *data = con->evnt->io_r;
ASSERT(CLEN(hdr) == hdr_len);
ASSERT(hdr[hdr_len - 1] == ':');
if (!con->policy->use_hdrs_non_spc)
--hdr_len;
if ((len < hdr_len) ||
!vstr_cmp_case_buf_eq(data, pos, hdr_len, hdr, hdr_len))
return (FALSE);
len -= hdr_len; pos += hdr_len;
if (!con->policy->use_hdrs_non_spc)
{
HTTP_SKIP_LWS(data, pos, len);
if (!len)
return (FALSE);
if (vstr_export_chr(data, pos) != ':')
return (FALSE);
--len; ++pos;
}
*hdr_val_pos = pos;
return (TRUE);
}
static void http__hdr_fixup(Vstr_base *data, size_t *pos, size_t *len,
size_t hdr_val_pos)
{
size_t tmp = 0;
*len -= hdr_val_pos - *pos; *pos += hdr_val_pos - *pos;
HTTP_SKIP_LWS(data, *pos, *len);
while ((tmp = vstr_spn_cstr_chrs_rev(data, *pos, *len, HTTP_LWS)))
{
*len -= tmp;
if (VSUFFIX(data, *pos, *len, HTTP_EOL))
*len -= CLEN(HTTP_EOL);
}
}
#define HDR__EQ(x) http__hdr_eq(con, pos, len, x ":", CLEN(x ":"), &hdr_val_pos)
#define HDR__EQ_SET(x, h) \
else if (HDR__EQ(x)) do \
{ \
if (req->policy->use_hdrs_no_x2 && http_hdrs-> hdr_ ## h ->pos) \
HTTPD_ERR_MSG_RET(req, 400, "Double header " x, FALSE); \
http__hdr_fixup(data, &pos, &len, hdr_val_pos); \
http_hdrs-> hdr_ ## h ->pos = pos; \
http_hdrs-> hdr_ ## h ->len = len; \
} while (FALSE)
#define HDR__EQ_MULTI_SET(x, h) \
else if (HDR__EQ(x)) do \
{ \
http__hdr_fixup(data, &pos, &len, hdr_val_pos); \
if (!http__app_multi_hdr(data, http_hdrs, \
http_hdrs->multi-> hdr_ ## h, pos, len)) \
{ \
req->malloc_bad = TRUE; \
HTTPD_ERR_MSG_RET(req, 500, "Memory failure", FALSE); \
} \
} while (FALSE)
#define HDR__EQ_IGNORE(x, h) \
else if (HDR__EQ(x)) do \
{ \
if (req->policy->use_hdrs_no_x2 && got_ ## h) \
HTTPD_ERR_MSG_RET(req, 400, "Double header " x, FALSE); \
got_ ## h = TRUE; \
} while (FALSE)
int http_parse_hdrs(struct Con *con, Httpd_req_data *req)
{
Vstr_base *data = con->evnt->io_r;
struct Http_hdrs *http_hdrs = req->http_hdrs;
unsigned int num = 3;
int got_content_length = FALSE;
int got_content_lang = FALSE;
int got_content_type = FALSE;
int got_transfer_encoding = FALSE;
while (++num <= req->sects->num)
{
size_t pos = VSTR_SECTS_NUM(req->sects, num)->pos;
size_t len = VSTR_SECTS_NUM(req->sects, num)->len;
size_t hdr_val_pos = 0;
if (0) { }
HDR__EQ_SET("User-Agent", ua);
HDR__EQ_SET("Referer", referer);
HDR__EQ_SET("Authorization", authorization);
HDR__EQ_SET("Expect", expect);
HDR__EQ_SET("Host", host);
HDR__EQ_SET("If-Modified-Since", if_modified_since);
HDR__EQ_SET("If-Range", if_range);
HDR__EQ_SET("If-Unmodified-Since", if_unmodified_since);
HDR__EQ_SET("Range", range);
HDR__EQ_SET("X-Moz", x_moz);
HDR__EQ_MULTI_SET("Accept", accept);
HDR__EQ_MULTI_SET("Accept-Charset", accept_charset);
HDR__EQ_MULTI_SET("Accept-Encoding", accept_encoding);
HDR__EQ_MULTI_SET("Accept-Language", accept_language);
HDR__EQ_MULTI_SET("Connection", connection);
HDR__EQ_MULTI_SET("If-Match", if_match);
HDR__EQ_MULTI_SET("If-None-Match", if_none_match);
else if (HDR__EQ("Content-Length"))
{
unsigned int num_flags = 10 | (VSTR_FLAG_PARSE_NUM_NO_BEG_PM |
VSTR_FLAG_PARSE_NUM_OVERFLOW);
unsigned int num_val = 0;
size_t num_len = 0;
if (req->policy->use_hdrs_no_x2 && got_content_length)
HTTPD_ERR_MSG_RET(req, 400, "Double header Content-Length", FALSE);
got_content_length = TRUE;
http__hdr_fixup(data, &pos, &len, hdr_val_pos);
num_val = vstr_parse_uint(data, pos, len, num_flags, &num_len, NULL);
if (num_len != len)
HTTPD_ERR_MSG_RET(req, 400, "Content-Length is not a number", FALSE);
if (num_val)
HTTPD_ERR_MSG_RET(req, 413, "Content-Length is not zero", FALSE);
}
HDR__EQ_IGNORE("Content-Type", content_type);
HDR__EQ_IGNORE("Content-Language", content_lang);
else if (HDR__EQ("Transfer-Encoding"))
{
if (req->policy->use_hdrs_no_x2 && got_transfer_encoding)
HTTPD_ERR_MSG_RET(req, 400, "Double header Transfer-Encoding", FALSE);
got_transfer_encoding = TRUE;
http__hdr_fixup(data, &pos, &len, hdr_val_pos);
if (!VEQ(data, pos, len, "identity"))
HTTPD_ERR_MSG_RET(req, 501, "Transfer-Encoding is not identity", FALSE);
}
else
{
size_t tmp = 0;
tmp = vstr_srch_chr_fwd(data, pos, len, ':');
if (!tmp)
HTTPD_ERR_MSG_RET(req, 400, "Header does not end with : marker", FALSE);
tmp = vstr_sc_posdiff(pos, tmp);
if (req->policy->use_hdrs_non_spc &&
(vstr_cspn_cstr_chrs_fwd(data, pos, tmp, HTTP_LWS) != tmp))
HTTPD_ERR_MSG_RET(req, 400, "Header has spaces before : marker", FALSE);
}
}
if (!req->policy->use_hdrs_err_411)
return (TRUE);
if (got_content_type && !got_content_length)
HTTPD_ERR_MSG_RET(req, 411, "Type but no Length", FALSE);
if (got_content_lang && !got_content_length)
HTTPD_ERR_MSG_RET(req, 411, "Language but no Length", FALSE);
if (got_transfer_encoding && !got_content_length)
HTTPD_ERR_MSG_RET(req, 411, "Transfer-Encoding but no Length", FALSE);
return (TRUE);
}
#undef HDR__EQ
#undef HDR__SET
#undef HDR__MUTLI_SET
#define HDR__CON_1_0_FIXUP(name, h) \
else if (VIEQ(data, pos, tmp, name)) \
do { \
req -> http_hdrs -> hdr_ ## h ->pos = 0; \
req -> http_hdrs -> hdr_ ## h ->len = 0; \
} while (FALSE)
#define HDR__CON_1_0_MULTI_FIXUP(name, h) \
else if (VIEQ(data, pos, tmp, name)) \
do { \
req -> http_hdrs -> multi -> hdr_ ## h ->pos = 0; \
req -> http_hdrs -> multi -> hdr_ ## h ->len = 0; \
} while (FALSE)
void http_parse_connection(struct Con *con, struct Httpd_req_data *req)
{
Vstr_base *data = req->http_hdrs->multi->comb;
size_t pos = 0;
size_t len = 0;
unsigned int num = 0;
pos = req->http_hdrs->multi->hdr_connection->pos;
len = req->http_hdrs->multi->hdr_connection->len;
if (HTTPD_VER_GE_1_1(req))
con->keep_alive = HTTP_1_1_KEEP_ALIVE;
if (!len)
return;
while (len)
{
size_t tmp = vstr_cspn_cstr_chrs_fwd(data, pos, len,
HTTP_EOL HTTP_LWS ",");
++num;
if (HTTPD_VER_GE_1_1(req))
{
if (VIEQ(data, pos, tmp, "close"))
con->keep_alive = HTTP_NON_KEEP_ALIVE;
}
else if (VIEQ(data, pos, tmp, "keep-alive"))
{
if (req->policy->use_keep_alive_1_0)
con->keep_alive = HTTP_1_0_KEEP_ALIVE;
}
HDR__CON_1_0_FIXUP("User-Agent", ua);
HDR__CON_1_0_FIXUP("Referer", referer);
HDR__CON_1_0_FIXUP("Authorization", authorization);
HDR__CON_1_0_FIXUP("Expect", expect);
HDR__CON_1_0_FIXUP("Host", host);
HDR__CON_1_0_FIXUP("If-Modified-Since", if_modified_since);
HDR__CON_1_0_FIXUP("If-Range", if_range);
HDR__CON_1_0_FIXUP("If-Unmodified-Since", if_unmodified_since);
HDR__CON_1_0_FIXUP("Range", range);
HDR__CON_1_0_FIXUP("X-Moz", x_moz);
HDR__CON_1_0_MULTI_FIXUP("Accept", accept);
HDR__CON_1_0_MULTI_FIXUP("Accept-Charset", accept_charset);
HDR__CON_1_0_MULTI_FIXUP("Accept-Encoding", accept_encoding);
HDR__CON_1_0_MULTI_FIXUP("Accept-Language", accept_language);
HDR__CON_1_0_MULTI_FIXUP("If-Match", if_match);
HDR__CON_1_0_MULTI_FIXUP("If-None-Match", if_none_match);
tmp = vstr_cspn_cstr_chrs_fwd(data, pos, len, ",");
len -= tmp; pos += tmp;
if (!len)
break;
assert(VPREFIX(data, pos, len, ","));
len -= 1; pos += 1;
HTTP_SKIP_LWS(data, pos, len);
if (req->policy->max_connection_nodes &&
(num >= req->policy->max_connection_nodes))
return;
}
}
#undef HDR__CON_1_0_FIXUP
#undef HDR__CON_1_0_MULTI_FIXUP
int http_parse_1_x(struct Con *con, struct Httpd_req_data *req)
{
ASSERT(!req->ver_0_9);
if (!http_parse_version(con, req))
return (FALSE);
if (!http_parse_hdrs(con, req))
return (FALSE);
if (req->policy->max_requests &&
(req->policy->max_requests <= con->evnt->acct.req_got) &&
HTTPD_VER_GE_1_1(req))
return (TRUE);
if (!req->policy->use_keep_alive && HTTPD_VER_GE_1_1(req))
return (TRUE);
http_parse_connection(con, req);
if (req->policy->max_requests &&
(req->policy->max_requests <= con->evnt->acct.req_got))
con->keep_alive = HTTP_NON_KEEP_ALIVE;
return (TRUE);
}
#define HTTP__PARSE_CHK_RET_OK() do { \
HTTP_SKIP_LWS(data, pos, len); \
\
if (!len || \
VPREFIX(data, pos, len, ",") || \
(allow_more && VPREFIX(data, pos, len, ";"))) \
{ \
*passed_pos = pos; \
*passed_len = len; \
\
ASSERT(*val <= 1000); \
\
return (TRUE); \
} \
} while (FALSE)
static int http_parse_quality(Vstr_base *data,
size_t *passed_pos, size_t *passed_len,
int allow_more, unsigned int *val)
{
size_t pos = *passed_pos;
size_t len = *passed_len;
int lead_zero = FALSE;
size_t num_len = 0;
unsigned int parse_flags = VSTR_FLAG02(PARSE_NUM, NO_BEG_PM, NO_NEGATIVE);
ASSERT(val);
*val = 1000;
HTTP_SKIP_LWS(data, pos, len);
*passed_pos = pos;
*passed_len = len;
if (!len || VPREFIX(data, pos, len, ","))
return (TRUE);
if (VPREFIX(data, pos, len, ";q=0."))
{
len -= strlen(";q=0."); pos += strlen(";q=0.");
lead_zero = TRUE;
*val = 0;
}
else if (VPREFIX(data, pos, len, ";"))
{
len -= 1; pos += 1;
HTTP_SKIP_LWS(data, pos, len);
if (!VPREFIX(data, pos, len, "q"))
return (!!allow_more);
len -= 1; pos += 1;
HTTP_SKIP_LWS(data, pos, len);
if (!VPREFIX(data, pos, len, "="))
return (!!allow_more);
len -= 1; pos += 1;
HTTP_SKIP_LWS(data, pos, len);
if (!(lead_zero = VPREFIX(data, pos, len, "0")) &&
!VPREFIX(data, pos, len, "1"))
return (FALSE);
*val = (!lead_zero) * 1000;
len -= 1; pos += 1;
HTTP__PARSE_CHK_RET_OK();
if (!VPREFIX(data, pos, len, "."))
return (FALSE);
len -= 1; pos += 1;
}
HTTP_SKIP_LWS(data, pos, len);
*val += vstr_parse_uint(data, pos, len, 10 | parse_flags, &num_len, NULL);
if (!num_len || (num_len > 3) || (*val > 1000))
return (FALSE);
if (!lead_zero)
ASSERT(*val == 1000);
else
{
if (num_len < 3) *val *= 10;
if (num_len < 2) *val *= 10;
}
len -= num_len; pos += num_len;
HTTP__PARSE_CHK_RET_OK();
return (FALSE);
}
#undef HTTP__PARSE_CHK_RET_OK
static size_t http__len_quoted_string(const Vstr_base *data,
size_t pos, size_t len)
{
size_t orig_pos = pos;
if (!VPREFIX(data, pos, len, "\""))
return (0);
len -= 1; pos += 1;
while (TRUE)
{
size_t tmp = vstr_cspn_cstr_chrs_fwd(data, pos, len, "\"\\");
len -= tmp; pos += tmp;
if (!len)
return (0);
if (vstr_export_chr(data, pos) == '"')
return (vstr_sc_posdiff(orig_pos, pos));
ASSERT(vstr_export_chr(data, pos) == '\\');
if (len < 3)
return (0);
len -= 2; pos += 2;
}
assert_ret(FALSE, 0);
}
static int http__skip_quoted_string(const Vstr_base *data,
size_t *pos, size_t *len)
{
size_t qlen = http__len_quoted_string(data, *pos, *len);
assert(VPREFIX(data, *pos, *len, "\""));
if (!qlen)
return (FALSE);
*len -= qlen; *pos += qlen;
HTTP_SKIP_LWS(data, *pos, *len);
return (TRUE);
}
int httpd_match_etags(struct Httpd_req_data *req,
const Vstr_base *hdr, size_t hpos, size_t hlen,
const Vstr_base *vs1, size_t epos, size_t elen,
int allow_weak)
{
int need_comma = FALSE;
ASSERT(hdr);
if (!vs1)
return (FALSE);
while (hlen)
{
int weak = FALSE;
size_t htlen = 0;
if (vstr_export_chr(hdr, hpos) == ',')
{
hlen -= 1; hpos += 1;
HTTP_SKIP_LWS(hdr, hpos, hlen);
need_comma = FALSE;
continue;
}
else if (need_comma)
return (FALSE);
if (VPREFIX(hdr, hpos, hlen, "W/"))
{
weak = TRUE;
hlen -= CLEN("W/"); hpos += CLEN("W/");
}
if (!(htlen = http__len_quoted_string(hdr, hpos, hlen)))
return (FALSE);
if (allow_weak || !weak)
{
size_t orig_epos = epos;
size_t orig_elen = elen;
unsigned int num = 0;
while (elen)
{
size_t etlen = 0;
if (vstr_export_chr(vs1, epos) == ',')
{
elen -= 1; epos += 1;
HTTP_SKIP_LWS(vs1, epos, elen);
need_comma = FALSE;
continue;
}
else if (need_comma)
return (FALSE);
++num;
if (!VPREFIX(vs1, epos, elen, "W/"))
weak = FALSE;
else
{
weak = TRUE;
elen -= CLEN("W/"); epos += CLEN("W/");
}
if (!(etlen = http__len_quoted_string(vs1, epos, elen)))
return (FALSE);
if ((allow_weak || !weak) &&
vstr_cmp_eq(hdr, hpos, htlen, vs1, epos, etlen))
return (TRUE);
elen -= etlen; epos += etlen;
HTTP_SKIP_LWS(vs1, epos, elen);
need_comma = TRUE;
if (req->policy->max_etag_nodes && (num >= req->policy->max_etag_nodes))
return (FALSE);
}
epos = orig_epos;
elen = orig_elen;
}
hlen -= htlen; hpos += htlen;
HTTP_SKIP_LWS(hdr, hpos, hlen);
need_comma = TRUE;
}
return (FALSE);
}
static int http__skip_parameters(Vstr_base *data, size_t *pos, size_t *len)
{
while (*len && (vstr_export_chr(data, *pos) != ','))
{
size_t tmp = 0;
if (vstr_export_chr(data, *pos) != ';')
return (FALSE);
*len -= 1; *pos += 1;
HTTP_SKIP_LWS(data, *pos, *len);
tmp = vstr_cspn_cstr_chrs_fwd(data, *pos, *len, ";,=");
*len -= tmp; *pos += tmp;
if (!*len)
break;
switch (vstr_export_chr(data, *pos))
{
case ';': break;
case ',': break;
case '=':
*len -= 1; *pos += 1;
HTTP_SKIP_LWS(data, *pos, *len);
if (!*len)
return (FALSE);
if (vstr_export_chr(data, *pos) == '"')
{
if (!http__skip_quoted_string(data, pos, len))
return (FALSE);
}
else
{
tmp = vstr_cspn_cstr_chrs_fwd(data, *pos, *len, ";,");
*len -= tmp; *pos += tmp;
}
break;
}
}
return (TRUE);
}
unsigned int http_parse_accept(Httpd_req_data *req,
const Vstr_base *ct_vs1,
size_t ct_pos, size_t ct_len)
{
Vstr_base *data = req->http_hdrs->multi->comb;
size_t pos = 0;
size_t len = 0;
unsigned int num = 0;
unsigned int quality = 1001;
unsigned int dummy = 1001;
int done_sub_type = FALSE;
size_t ct_sub_len = 0;
pos = req->http_hdrs->multi->hdr_accept->pos;
len = req->http_hdrs->multi->hdr_accept->len;
if (!len)
return (1000);
ASSERT(ct_vs1);
if (!(ct_sub_len = vstr_srch_chr_fwd(ct_vs1, ct_pos, ct_len, '/')))
{
if (ct_vs1 == req->content_type_vs1)
req->content_type_vs1 = NULL;
return (1);
}
ct_sub_len = vstr_sc_posdiff(ct_pos, ct_sub_len);
while (len)
{
size_t tmp = vstr_cspn_cstr_chrs_fwd(data, pos, len,
HTTP_EOL HTTP_LWS ";,");
++num;
if (0) { }
else if (vstr_cmp_eq(data, pos, tmp, ct_vs1, ct_pos, ct_len))
{
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, TRUE, &quality))
return (1);
return (quality);
}
else if ((tmp == (ct_sub_len + 1)) &&
vstr_cmp_eq(data, pos, ct_sub_len, ct_vs1, ct_pos, ct_sub_len) &&
(vstr_export_chr(data, vstr_sc_poslast(pos, tmp)) == '*'))
{
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, TRUE, &quality))
return (1);
done_sub_type = TRUE;
}
else if (!done_sub_type && VEQ(data, pos, tmp, "*/*"))
{
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, TRUE, &quality))
return (1);
}
else
{
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, TRUE, &dummy))
return (1);
}
if (!http__skip_parameters(data, &pos, &len))
return (1);
if (!len)
break;
assert(VPREFIX(data, pos, len, ","));
len -= 1; pos += 1;
HTTP_SKIP_LWS(data, pos, len);
if (req->policy->max_A_nodes && (num >= req->policy->max_A_nodes))
return (1);
}
ASSERT(quality <= 1001);
if (quality == 1001)
return (0);
return (quality);
}
static int http__cmp_lang_eq(const Vstr_base *s1, size_t p1, size_t l1,
const Vstr_base *s2, size_t p2, size_t l2)
{
if (l1 == l2)
return (FALSE);
if (l1 > l2)
return ((vstr_export_chr(s1, p1 + l2) == '-') &&
vstr_cmp_case_eq(s1, p1, l2, s2, p2, l2));
return ((vstr_export_chr(s2, p2 + l1) == '-') &&
vstr_cmp_case_eq(s1, p1, l1, s2, p2, l1));
}
unsigned int http_parse_accept_language(Httpd_req_data *req,
const Vstr_base *ct_vs1,
size_t ct_pos, size_t ct_len)
{
Vstr_base *data = req->http_hdrs->multi->comb;
size_t pos = 0;
size_t len = 0;
unsigned int num = 0;
unsigned int quality = 1001;
unsigned int dummy = 1001;
size_t done_sub_type = 0;
pos = req->http_hdrs->multi->hdr_accept_language->pos;
len = req->http_hdrs->multi->hdr_accept_language->len;
if (!len)
return (1000);
ASSERT(ct_vs1);
while (len)
{
size_t tmp = vstr_cspn_cstr_chrs_fwd(data, pos, len,
HTTP_EOL HTTP_LWS ";,");
++num;
if (0) { }
else if (vstr_cmp_case_eq(data, pos, tmp, ct_vs1, ct_pos, ct_len))
{
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, FALSE, &quality))
return (1);
return (quality);
}
else if ((tmp >= done_sub_type) &&
http__cmp_lang_eq(data, pos, tmp, ct_vs1, ct_pos, ct_len))
{
unsigned int sub_type_qual = 0;
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, FALSE, &sub_type_qual))
return (1);
ASSERT(sub_type_qual <= 1000);
if ((tmp > done_sub_type) || (sub_type_qual > quality))
quality = sub_type_qual;
done_sub_type = tmp;
}
else if (!done_sub_type && VEQ(data, pos, tmp, "*"))
{
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, FALSE, &quality))
return (1);
}
else
{
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, FALSE, &dummy))
return (1);
}
if (!len)
break;
assert(VPREFIX(data, pos, len, ","));
len -= 1; pos += 1;
HTTP_SKIP_LWS(data, pos, len);
if (req->policy->max_AL_nodes && (num >= req->policy->max_AL_nodes))
return (1);
}
ASSERT(quality <= 1001);
if (quality == 1001)
return (0);
return (quality);
}
int http_parse_accept_encoding(struct Httpd_req_data *req, int force)
{
Vstr_base *data = req->http_hdrs->multi->comb;
size_t pos = 0;
size_t len = 0;
unsigned int num = 0;
unsigned int star_val = 1001;
unsigned int dummy_val = 1001;
pos = req->http_hdrs->multi->hdr_accept_encoding->pos;
len = req->http_hdrs->multi->hdr_accept_encoding->len;
if (!force && !req->policy->use_err_406 && !req->allow_accept_encoding)
return (FALSE);
if (!force)
req->vary_ae = TRUE;
if (req->parsed_content_encoding)
{
if (!req->allow_accept_encoding)
return (FALSE);
return (!!req->content_enc_gzip || !!req->content_enc_bzip2);
}
req->parsed_content_encoding = TRUE;
if (!len)
goto parse_err;
req->content_encoding_xgzip = FALSE;
req->content_enc_identity = 1001;
req->content_enc_gzip = 1001;
req->content_enc_bzip2 = 1001;
while (len)
{
size_t tmp = vstr_cspn_cstr_chrs_fwd(data, pos, len,
HTTP_EOL HTTP_LWS ";,");
++num;
if (0) { }
else if (VEQ(data, pos, tmp, "identity"))
{
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, FALSE,
&req->content_enc_identity))
goto parse_err;
}
else if (VEQ(data, pos, tmp, "gzip"))
{
len -= tmp; pos += tmp;
req->content_encoding_xgzip = FALSE;
if (!http_parse_quality(data, &pos, &len, FALSE, &req->content_enc_gzip))
goto parse_err;
}
else if (VEQ(data, pos, tmp, "bzip2"))
{
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, FALSE, &req->content_enc_bzip2))
goto parse_err;
}
else if (VEQ(data, pos, tmp, "x-gzip"))
{
len -= tmp; pos += tmp;
req->content_encoding_xgzip = TRUE;
if (!http_parse_quality(data, &pos, &len, FALSE, &req->content_enc_gzip))
goto parse_err;
req->content_enc_gzip = 1000;
}
else if (VEQ(data, pos, tmp, "*"))
{
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, FALSE, &star_val))
goto parse_err;
}
else
{
len -= tmp; pos += tmp;
if (!http_parse_quality(data, &pos, &len, FALSE, &dummy_val))
goto parse_err;
}
if (!len)
break;
assert(VPREFIX(data, pos, len, ","));
len -= 1; pos += 1;
HTTP_SKIP_LWS(data, pos, len);
if (req->policy->max_AE_nodes && (num >= req->policy->max_AE_nodes))
goto parse_err;
}
if (req->content_enc_gzip == 1001) req->content_enc_gzip = star_val;
if (req->content_enc_bzip2 == 1001) req->content_enc_bzip2 = star_val;
if (req->content_enc_identity == 1001) req->content_enc_identity = star_val;
if (req->content_enc_gzip == 1001) req->content_enc_gzip = 0;
if (req->content_enc_bzip2 == 1001) req->content_enc_bzip2 = 0;
if (req->content_enc_identity == 1001) req->content_enc_identity = 1;
if (!req->allow_accept_encoding)
return (FALSE);
if ((req->content_enc_identity > req->content_enc_gzip) &&
(req->content_enc_identity > req->content_enc_bzip2))
goto parse_err;
req->content_encoding_identity = !!req->content_enc_identity;
req->content_encoding_gzip = !!req->content_enc_gzip;
req->content_encoding_bzip2 = !!req->content_enc_bzip2;
return (!!req->content_enc_gzip || !!req->content_enc_bzip2);
parse_err:
req->content_enc_identity = 1;
req->content_enc_gzip = 0;
req->content_enc_bzip2 = 0;
req->content_encoding_identity = !!req->content_enc_identity;
req->content_encoding_gzip = !!req->content_enc_gzip;
req->content_encoding_bzip2 = !!req->content_enc_bzip2;
req->content_encoding_xgzip = FALSE;
return (FALSE);
}
static int http__try_encoded_content(struct Con *con, Httpd_req_data *req,
const struct stat64 *req_f_stat,
off64_t *req_f_stat_st_size,
Vstr_base *fname,
const char *zip_ext, size_t zip_len)
{
const char *fname_cstr = NULL;
int fd = -1;
int ret = FALSE;
int open_flags = O_NONBLOCK;
ASSERT(con->fs && !con->fs_num && !con->fs_off);
ASSERT(con->fs->len == (uintmax_t)req_f_stat->st_size);
if (req_f_stat->st_size < HTTPD_CONF_ZIP_LIMIT_MIN)
return (ret);
if (req->policy->use_noatime)
open_flags |= O_NOATIME;
vstr_add_cstr_ptr(fname, fname->len, zip_ext);
fname_cstr = vstr_export_cstr_ptr(fname, 1, fname->len);
if (fname->conf->malloc_bad)
vlg_warn(vlg, "Failed to export cstr for '%s'\n", zip_ext);
else if ((fd = io__open(fname_cstr, open_flags)) == -1)
{ }
else
{
struct stat64 f_stat[1];
if (fstat64(fd, f_stat) == -1)
vlg_warn(vlg, "fstat: %m\n");
else if ((req->policy->use_public_only && !(f_stat->st_mode & S_IROTH)) ||
(S_ISDIR(f_stat->st_mode)) || (!S_ISREG(f_stat->st_mode)) ||
(req_f_stat->st_mtime > f_stat->st_mtime) ||
!f_stat->st_size ||
(*req_f_stat_st_size <= f_stat->st_size))
{ }
else
{
SWAP_TYPE(con->fs->fd, fd, int);
con->fs->len = *req_f_stat_st_size = f_stat->st_size;
req->encoded_mtime = f_stat->st_mtime;
ret = TRUE;
}
close(fd);
}
if (!ret)
vstr_sc_reduce(fname, 1, fname->len, zip_len);
return (ret);
}
void httpd_parse_sc_try_fd_encoding(struct Con *con, Httpd_req_data *req,
const struct stat64 *fs,
off64_t *req_f_stat_st_size,
Vstr_base *fname)
{
if (http_parse_accept_encoding(req, FALSE))
{
if (req->content_enc_bzip2 >= req->content_enc_gzip)
{
if (req->content_encoding_bzip2 &&
!http__try_encoded_content(con, req, fs, req_f_stat_st_size,
fname, ".bz2", CLEN(".bz2")))
req->content_encoding_bzip2 = FALSE;
if (req->content_encoding_bzip2) req->content_encoding_gzip = FALSE;
if (req->content_encoding_gzip &&
!http__try_encoded_content(con, req, fs, req_f_stat_st_size,
fname, ".gz", CLEN(".gz")))
req->content_encoding_gzip = FALSE;
}
else
{
if (req->content_encoding_gzip &&
!http__try_encoded_content(con, req, fs, req_f_stat_st_size,
fname, ".gz", CLEN(".gz")))
req->content_encoding_gzip = FALSE;
if (req->content_encoding_gzip) req->content_encoding_bzip2 = FALSE;
if (req->content_encoding_bzip2 &&
!http__try_encoded_content(con, req, fs, req_f_stat_st_size,
fname, ".bz2", CLEN(".bz2")))
req->content_encoding_bzip2 = FALSE;
}
ASSERT(!req->content_encoding_bzip2 || !req->content_encoding_gzip);
}
}
static int httpd__file_sect_add(struct Con *con, Httpd_req_data *req,
uintmax_t range_beg, uintmax_t range_end)
{
struct File_sect *fs = NULL;
ASSERT(con->fs && (con->fs_sz >= 1));
if (req->policy->max_range_nodes <= con->fs_num)
return (FALSE);
if (!con->fs_num)
{
ASSERT((con->fs == con->fs_store) || (con->fs_sz > 1));
ASSERT(!con->use_mpbr);
goto file_sect_add;
}
con->use_mpbr = TRUE;
if (con->fs == con->fs_store)
{
ASSERT(con->fs_num == 1);
if (!(con->mpbr_ct = vstr_make_base(con->evnt->io_w->conf)))
return (FALSE);
if (!(fs = MK(sizeof(struct File_sect) * 16)))
return (FALSE);
con->fs = fs;
con->fs_sz = 16;
con->fs->fd = con->fs_store->fd;
con->fs->off = con->fs_store->off;
con->fs->len = con->fs_store->len;
++fs;
}
else if (con->fs_num >= con->fs_sz)
{
unsigned int num = (con->fs_sz << 1) + 1;
ASSERT(con->fs_num == con->fs_sz);
if (!MV(con->fs, fs, sizeof(struct File_sect) * num))
return (FALSE);
con->fs_sz = num;
}
file_sect_add:
fs = con->fs + con->fs_num++;
fs->fd = con->fs->fd;
fs->off = range_beg;
fs->len = (range_end - range_beg) + 1;
if ((req->fs_len + fs->len) < req->fs_len)
return (FALSE);
req->fs_len += fs->len;
return (TRUE);
}
int http_parse_range(struct Con *con, Httpd_req_data *req)
{
Vstr_base *data = con->evnt->io_r;
Vstr_sect_node *h_r = req->http_hdrs->hdr_range;
size_t pos = h_r->pos;
size_t len = h_r->len;
uintmax_t fsize = req->f_stat_st_size;
unsigned int num_flags = 10 | (VSTR_FLAG_PARSE_NUM_NO_BEG_PM |
VSTR_FLAG_PARSE_NUM_OVERFLOW);
size_t num_len = 0;
if (!VPREFIX(data, pos, len, "bytes"))
return (0);
len -= CLEN("bytes"); pos += CLEN("bytes");
HTTP_SKIP_LWS(data, pos, len);
if (!VPREFIX(data, pos, len, "="))
return (0);
len -= CLEN("="); pos += CLEN("=");
http_parse_skip_blanks(data, &pos, &len);
while (len)
{
uintmax_t range_beg = 0;
uintmax_t range_end = 0;
if (VPREFIX(data, pos, len, "-"))
{
uintmax_t tmp = 0;
len -= CLEN("-"); pos += CLEN("-");
HTTP_SKIP_LWS(data, pos, len);
tmp = vstr_parse_uintmax(data, pos, len, num_flags, &num_len, NULL);
len -= num_len; pos += num_len;
if (!num_len)
return (0);
if (!tmp)
return (416);
if (tmp >= fsize)
return (0);
range_beg = fsize - tmp;
range_end = fsize - 1;
}
else
{
range_beg = vstr_parse_uintmax(data, pos, len, num_flags, &num_len, NULL);
len -= num_len; pos += num_len;
HTTP_SKIP_LWS(data, pos, len);
if (!VPREFIX(data, pos, len, "-"))
return (0);
len -= CLEN("-"); pos += CLEN("-");
HTTP_SKIP_LWS(data, pos, len);
if (!len || VPREFIX(data, pos, len, ","))
range_end = fsize - 1;
else
{
range_end = vstr_parse_uintmax(data, pos, len, num_flags, &num_len, 0);
len -= num_len; pos += num_len;
if (!num_len)
return (0);
if (range_end >= fsize)
range_end = fsize - 1;
}
if ((range_beg >= fsize) || (range_beg > range_end))
return (416);
if ((range_beg == 0) &&
(range_end == (fsize - 1)))
return (0);
}
http_parse_skip_blanks(data, &pos, &len);
if (!httpd__file_sect_add(con, req, range_beg, range_end))
return (0);
}
return (200);
}
static int http__chk_single_crlf(Vstr_base *data, size_t pos, size_t len)
{
if (vstr_srch_chr_fwd(data, pos, len, '\r'))
return ('\r');
if (vstr_srch_chr_fwd(data, pos, len, '\n'))
return ('\n');
return (0);
}
unsigned short httpd_parse_host_port(Vstr_base *s1, size_t pos, size_t len)
{
unsigned int num_flags = 10 | (VSTR_FLAG_PARSE_NUM_NO_BEG_PM |
VSTR_FLAG_PARSE_NUM_OVERFLOW);
size_t num_len = 0;
unsigned short port = 0;
ASSERT(vstr_export_chr(s1, pos) == ':');
len -= 1; pos += 1;
port = vstr_parse_ushort(s1, pos, len, num_flags, &num_len, NULL);
if (!port || (num_len != len))
return (0);
return (port);
}
int http_parse_host(struct Con *con, struct Httpd_req_data *req)
{
Vstr_base *data = con->evnt->io_r;
size_t op_pos = req->path_pos;
size_t op_len = req->path_len;
if (req->ver_1_1 && !req->http_hdrs->hdr_host->pos)
HTTPD_ERR_MSG_RET(req, 400, "Hostname is required in 1.1", FALSE);
if (VIPREFIX(data, op_pos, op_len, "http://"))
{
size_t tmp = CLEN("http://");
op_len -= tmp; op_pos += tmp;
tmp = vstr_srch_chr_fwd(data, op_pos, op_len, '/');
if (!tmp)
{
HTTP__HDR_SET(req, host, op_pos, op_len);
op_len = 1;
--op_pos;
}
else
{
size_t host_len = tmp - op_pos;
HTTP__HDR_SET(req, host, op_pos, host_len);
op_len -= host_len; op_pos += host_len;
}
assert(VPREFIX(data, op_pos, op_len, "/"));
}
if (req->ver_1_x && !req->http_hdrs->hdr_host->pos)
HTTPD_ERR_MSG_RET(req, 400,
"Hostname or Absolute URL is required in 1.x", FALSE);
if (req->http_hdrs->hdr_host->len)
{
size_t pos = req->http_hdrs->hdr_host->pos;
size_t len = req->http_hdrs->hdr_host->len;
size_t tmp = 0;
if (vstr_srch_chr_fwd(data, pos, len, '/'))
HTTPD_ERR_MSG_RET(req, 400, "Hostname contains /", FALSE);
switch (http__chk_single_crlf(data, pos, len))
{
case '\r':
HTTPD_ERR_MSG_RET(req, 400, "Hostname contains \\r", FALSE);
case '\n':
HTTPD_ERR_MSG_RET(req, 400, "Hostname contains \\n", FALSE);
case 0:
ASSERT_NO_SWITCH_DEF();
}
if ((tmp = vstr_srch_chr_fwd(data, pos, len, ':')))
{
len -= tmp - pos; pos = tmp;
if (!VEQ(data, pos, len, ":"))
if (!(req->http_host_port = httpd_parse_host_port(data, pos, len)))
HTTPD_ERR_MSG_RET(req, 400, "Port is not a valid number", FALSE);
req->http_hdrs->hdr_host->len -= len;
}
}
switch (http__chk_single_crlf(data, op_pos, op_len))
{
case '\r':
HTTPD_ERR_MSG_RET(req, 400, "Path contains \\r", FALSE);
case '\n':
HTTPD_ERR_MSG_RET(req, 400, "Path contains \\n", FALSE);
case 0:
ASSERT_NO_SWITCH_DEF();
}
if (req->policy->remove_url_frag)
op_len = vstr_cspn_cstr_chrs_fwd(data, op_pos, op_len, "#");
if (req->policy->remove_url_query)
op_len = vstr_cspn_cstr_chrs_fwd(data, op_pos, op_len, "?");
req->path_pos = op_pos;
req->path_len = op_len;
return (TRUE);
}
void http_parse_skip_blanks(Vstr_base *data,
size_t *passed_pos, size_t *passed_len)
{
size_t pos = *passed_pos;
size_t len = *passed_len;
HTTP_SKIP_LWS(data, pos, len);
while (VPREFIX(data, pos, len, ","))
{
len -= CLEN(","); pos += CLEN(",");
HTTP_SKIP_LWS(data, pos, len);
}
*passed_pos = pos;
*passed_len = len;
}
static int http_parse_wait_io_r(struct Con *con)
{
if (con->evnt->io_r_shutdown)
return (!!con->evnt->io_w->len);
httpd_policy_change_con(con, con->policy);
evnt_wait_cntl_add(con->evnt, POLLIN);
evnt_fd_set_cork(con->evnt, FALSE);
return (TRUE);
}
static int httpd_serv__parse_no_req(struct Con *con, struct Httpd_req_data *req)
{
if (req->policy->max_header_sz &&
(con->evnt->io_r->len > req->policy->max_header_sz))
{
evnt_got_pkt(con->evnt);
HTTPD_ERR_MSG_RET(req, 400, "Too big", http_fin_err_req(con, req));
}
http_req_free(req);
return (http_parse_wait_io_r(con));
}
static int http__parse_req_all(struct Con *con, struct Httpd_req_data *req,
const char *eol, int *ern)
{
Vstr_base *data = con->evnt->io_r;
size_t pos = 0;
ASSERT(eol && ern);
*ern = FALSE;
while (VPREFIX(data, 1, data->len, HTTP_EOL))
vstr_del(data, 1, CLEN(HTTP_EOL));
ASSERT(!req->len);
if (!(pos = vstr_srch_cstr_buf_fwd(data, 1, data->len, eol)))
goto no_req;
req->len = pos + CLEN(eol) - 1;
return (TRUE);
no_req:
*ern = httpd_serv__parse_no_req(con, req);
return (FALSE);
}
int http_parse_req(struct Con *con)
{
Vstr_base *data = con->evnt->io_r;
struct Httpd_req_data *req = NULL;
int ern_req_all = FALSE;
ASSERT(con->fs && !con->fs_num);
if (!data->len)
return (http_parse_wait_io_r(con));
if (!(req = http_req_make(con)))
return (FALSE);
if (!req->policy->allow_http_0_9)
con->parsed_method_ver_1_0 = TRUE;
if (con->parsed_method_ver_1_0)
{
if (!http__parse_req_all(con, req, HTTP_END_OF_REQUEST, &ern_req_all))
return (ern_req_all);
}
else
{
if (!http__parse_req_all(con, req, HTTP_EOL, &ern_req_all))
return (ern_req_all);
}
con->keep_alive = HTTP_NON_KEEP_ALIVE;
http_req_split_method(con, req);
if (req->sects->malloc_bad)
{
evnt_got_pkt(con->evnt);
VLG_WARNNOMEM_RET(http_fin_errmem_req(con, req), (vlg, "split: %m\n"));
}
else if ((req->sects->num < 2) ||
(!req->policy->allow_http_0_9 && (req->sects->num < 3)))
{
evnt_got_pkt(con->evnt);
HTTPD_ERR_MSG_RET(req, 400, "Bad request line", http_fin_err_req(con, req));
}
else
{
size_t op_pos = 0;
size_t op_len = 0;
if (req->ver_0_9)
vlg_dbg1(vlg, "Method(0.9):"
" $<http-esc.vstr.sect:%p%p%u> $<http-esc.vstr.sect:%p%p%u>\n",
con->evnt->io_r, req->sects, 1U,
con->evnt->io_r, req->sects, 2U);
else
{
if (!con->parsed_method_ver_1_0)
{
con->parsed_method_ver_1_0 = TRUE;
req->len = 0;
if (!http__parse_req_all(con, req, HTTP_END_OF_REQUEST, &ern_req_all))
return (ern_req_all);
}
vlg_dbg1(vlg, "Method(1.x):"
" $<http-esc.vstr.sect:%p%p%u> $<http-esc.vstr.sect:%p%p%u>"
" $<http-esc.vstr.sect:%p%p%u>\n", data, req->sects, 1U,
data, req->sects, 2U, data, req->sects, 3U);
op_pos = VSTR_SECTS_NUM(req->sects, 1)->pos;
op_len = VSTR_SECTS_NUM(req->sects, 1)->len;
if (VEQ(data, op_pos, op_len, "HEAD"))
req->head_op = TRUE;
http_req_split_hdrs(con, req);
}
evnt_got_pkt(con->evnt);
if (HTTP_CONF_SAFE_PRINT_REQ)
vlg_dbg3(vlg, "REQ:\n$<vstr.hexdump:%p%zu%zu>",
data, (size_t)1, req->len);
else
vlg_dbg3(vlg, "REQ:\n$<vstr:%p%zu%zu>", data, (size_t)1, req->len);
assert(((req->sects->num >= 3) && !req->ver_0_9) || (req->sects->num == 2));
op_pos = VSTR_SECTS_NUM(req->sects, 1)->pos;
op_len = VSTR_SECTS_NUM(req->sects, 1)->len;
req->path_pos = VSTR_SECTS_NUM(req->sects, 2)->pos;
req->path_len = VSTR_SECTS_NUM(req->sects, 2)->len;
if (!req->ver_0_9 && !http_parse_1_x(con, req))
{
if (req->error_code == 500)
return (http_fin_errmem_req(con, req));
return (http_fin_err_req(con, req));
}
if (!http_parse_host(con, req))
return (http_fin_err_req(con, req));
if (0) { }
else if (VEQ(data, op_pos, op_len, "GET"))
{
if (!VPREFIX(data, req->path_pos, req->path_len, "/"))
HTTPD_ERR_MSG_RET(req, 400, "Bad path", http_fin_err_req(con, req));
if (!http_req_make_path(con, req))
return (http_fin_err_req(con, req));
return (http_req_op_get(con, req));
}
else if (req->ver_0_9)
HTTPD_ERR_RET(req, 501, http_fin_err_req(con, req));
else if (VEQ(data, op_pos, op_len, "HEAD"))
{
ASSERT(req->head_op == TRUE);
req->head_op = TRUE;
if (!VPREFIX(data, req->path_pos, req->path_len, "/"))
HTTPD_ERR_MSG_RET(req, 400, "Bad path", http_fin_err_req(con, req));
if (!http_req_make_path(con, req))
return (http_fin_err_req(con, req));
return (http_req_op_get(con, req));
}
else if (VEQ(data, op_pos, op_len, "OPTIONS"))
{
if (!VPREFIX(data, req->path_pos, req->path_len, "/") &&
!VEQ(data, req->path_pos, req->path_len, "*"))
HTTPD_ERR_MSG_RET(req, 400, "Bad path", http_fin_err_req(con, req));
if (req->policy->use_vhosts_name &&
req->policy->use_host_chk && req->policy->use_host_err_400 &&
!VEQ(data, req->path_pos, req->path_len, "*") &&
!http_req_make_path(con, req))
return (http_fin_err_req(con, req));
return (http_req_op_opts(con, req));
}
else if (req->policy->allow_trace_op && VEQ(data, op_pos, op_len, "TRACE"))
return (http_req_op_trace(con, req));
else if (VEQ(data, op_pos, op_len, "TRACE") ||
VEQ(data, op_pos, op_len, "POST") ||
VEQ(data, op_pos, op_len, "PUT") ||
VEQ(data, op_pos, op_len, "DELETE") ||
VEQ(data, op_pos, op_len, "CONNECT") ||
FALSE)
HTTPD_ERR_RET(req, 405, http_fin_err_req(con, req));
else
HTTPD_ERR_RET(req, 501, http_fin_err_req(con, req));
}
ASSERT_NOT_REACHED();
}