mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-02-18 19:56:59 +00:00
A bug during H1 data parsing may lead to copy more data than the maximum allowed. The bug is an overflow on this max threshold when it is lower than the size of an htx_blk structure. At first glance, it means it is possible to not respsect the buffer's reserve. So it may lead to rewrite errors but it may also block any progress on the stream if the compression is enabled. In this case, the channel buffer appears as full and the compression must wait for space to proceed. Outside of any bug, it is only possible when there are outgoing data to forward, so the compression filter just waits. Because of this bug, there is nothing to forward. The buffer is just full of input data. Thus nothing move and the stream is infinitly blocked. To fix the bug, we must be sure to be able to create an HTX block of 1 byte without exceeding the maximum allowed. This patch should fix the issue #2053. It must be backported as far as 2.5.
1073 lines
29 KiB
C
1073 lines
29 KiB
C
/*
|
|
* Functions to manipulate H1 messages using the internal representation.
|
|
*
|
|
* Copyright (C) 2019 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <haproxy/api.h>
|
|
#include <haproxy/cfgparse.h>
|
|
#include <haproxy/global.h>
|
|
#include <haproxy/h1.h>
|
|
#include <haproxy/h1_htx.h>
|
|
#include <haproxy/http.h>
|
|
#include <haproxy/http_htx.h>
|
|
#include <haproxy/htx.h>
|
|
#include <haproxy/tools.h>
|
|
|
|
/* Estimate the size of the HTX headers after the parsing, including the EOH. */
|
|
static size_t h1_eval_htx_hdrs_size(const struct http_hdr *hdrs)
|
|
{
|
|
size_t sz = 0;
|
|
int i;
|
|
|
|
for (i = 0; hdrs[i].n.len; i++)
|
|
sz += sizeof(struct htx_blk) + hdrs[i].n.len + hdrs[i].v.len;
|
|
sz += sizeof(struct htx_blk) + 1;
|
|
return sz;
|
|
}
|
|
|
|
/* Estimate the size of the HTX request after the parsing. */
|
|
static size_t h1_eval_htx_size(const struct ist p1, const struct ist p2, const struct ist p3,
|
|
const struct http_hdr *hdrs)
|
|
{
|
|
size_t sz;
|
|
|
|
/* size of the HTX start-line */
|
|
sz = sizeof(struct htx_blk) + sizeof(struct htx_sl) + p1.len + p2.len + p3.len;
|
|
sz += h1_eval_htx_hdrs_size(hdrs);
|
|
return sz;
|
|
}
|
|
|
|
/* Check the validity of the request version. If the version is valid, it
|
|
* returns 1. Otherwise, it returns 0.
|
|
*/
|
|
static int h1_process_req_vsn(struct h1m *h1m, union h1_sl *sl)
|
|
{
|
|
/* RFC7230#2.6 has enforced the format of the HTTP version string to be
|
|
* exactly one digit "." one digit. This check may be disabled using
|
|
* option accept-invalid-http-request.
|
|
*/
|
|
if (h1m->err_pos == -2) { /* PR_O2_REQBUG_OK not set */
|
|
if (sl->rq.v.len != 8)
|
|
return 0;
|
|
|
|
if (!istnmatch(sl->rq.v, ist("HTTP/"), 5) ||
|
|
!isdigit((unsigned char)*(sl->rq.v.ptr + 5)) ||
|
|
*(sl->rq.v.ptr + 6) != '.' ||
|
|
!isdigit((unsigned char)*(sl->rq.v.ptr + 7)))
|
|
return 0;
|
|
}
|
|
else if (!sl->rq.v.len) {
|
|
/* try to convert HTTP/0.9 requests to HTTP/1.0 */
|
|
|
|
/* RFC 1945 allows only GET for HTTP/0.9 requests */
|
|
if (sl->rq.meth != HTTP_METH_GET)
|
|
return 0;
|
|
|
|
/* HTTP/0.9 requests *must* have a request URI, per RFC 1945 */
|
|
if (!sl->rq.u.len)
|
|
return 0;
|
|
|
|
/* Add HTTP version */
|
|
sl->rq.v = ist("HTTP/1.0");
|
|
return 1;
|
|
}
|
|
|
|
if ((sl->rq.v.len == 8) &&
|
|
((*(sl->rq.v.ptr + 5) > '1') ||
|
|
((*(sl->rq.v.ptr + 5) == '1') && (*(sl->rq.v.ptr + 7) >= '1'))))
|
|
h1m->flags |= H1_MF_VER_11;
|
|
return 1;
|
|
}
|
|
|
|
/* Check the validity of the response version. If the version is valid, it
|
|
* returns 1. Otherwise, it returns 0.
|
|
*/
|
|
static int h1_process_res_vsn(struct h1m *h1m, union h1_sl *sl)
|
|
{
|
|
/* RFC7230#2.6 has enforced the format of the HTTP version string to be
|
|
* exactly one digit "." one digit. This check may be disabled using
|
|
* option accept-invalid-http-request.
|
|
*/
|
|
if (h1m->err_pos == -2) { /* PR_O2_REQBUG_OK not set */
|
|
if (sl->st.v.len != 8)
|
|
return 0;
|
|
|
|
if (*(sl->st.v.ptr + 4) != '/' ||
|
|
!isdigit((unsigned char)*(sl->st.v.ptr + 5)) ||
|
|
*(sl->st.v.ptr + 6) != '.' ||
|
|
!isdigit((unsigned char)*(sl->st.v.ptr + 7)))
|
|
return 0;
|
|
}
|
|
|
|
if ((sl->st.v.len == 8) &&
|
|
((*(sl->st.v.ptr + 5) > '1') ||
|
|
((*(sl->st.v.ptr + 5) == '1') && (*(sl->st.v.ptr + 7) >= '1'))))
|
|
h1m->flags |= H1_MF_VER_11;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Convert H1M flags to HTX start-line flags. */
|
|
static unsigned int h1m_htx_sl_flags(struct h1m *h1m)
|
|
{
|
|
unsigned int flags = HTX_SL_F_NONE;
|
|
|
|
if (h1m->flags & H1_MF_RESP)
|
|
flags |= HTX_SL_F_IS_RESP;
|
|
if (h1m->flags & H1_MF_VER_11)
|
|
flags |= HTX_SL_F_VER_11;
|
|
if (h1m->flags & H1_MF_XFER_ENC)
|
|
flags |= HTX_SL_F_XFER_ENC;
|
|
if (h1m->flags & H1_MF_XFER_LEN) {
|
|
flags |= HTX_SL_F_XFER_LEN;
|
|
if (h1m->flags & H1_MF_CHNK)
|
|
flags |= HTX_SL_F_CHNK;
|
|
else if (h1m->flags & H1_MF_CLEN) {
|
|
flags |= HTX_SL_F_CLEN;
|
|
if (h1m->body_len == 0)
|
|
flags |= HTX_SL_F_BODYLESS;
|
|
}
|
|
else
|
|
flags |= HTX_SL_F_BODYLESS;
|
|
}
|
|
if (h1m->flags & H1_MF_CONN_UPG)
|
|
flags |= HTX_SL_F_CONN_UPG;
|
|
return flags;
|
|
}
|
|
|
|
/* Postprocess the parsed headers for a request and convert them into an htx
|
|
* message. It returns the number of bytes parsed if > 0, or 0 if it couldn't
|
|
* proceed. Parsing errors are reported by setting the htx flag
|
|
* HTX_FL_PARSING_ERROR and filling h1m->err_pos and h1m->err_state fields.
|
|
*/
|
|
static int h1_postparse_req_hdrs(struct h1m *h1m, union h1_sl *h1sl, struct htx *htx,
|
|
struct http_hdr *hdrs, size_t max)
|
|
{
|
|
struct htx_sl *sl;
|
|
struct ist meth, uri, vsn;
|
|
unsigned int flags;
|
|
|
|
/* <h1sl> is always defined for a request */
|
|
meth = h1sl->rq.m;
|
|
uri = h1sl->rq.u;
|
|
vsn = h1sl->rq.v;
|
|
|
|
/* Be sure the message, once converted into HTX, will not exceed the max
|
|
* size allowed.
|
|
*/
|
|
if (h1_eval_htx_size(meth, uri, vsn, hdrs) > max) {
|
|
if (htx_is_empty(htx))
|
|
goto error;
|
|
goto output_full;
|
|
}
|
|
|
|
/* By default, request have always a known length */
|
|
h1m->flags |= H1_MF_XFER_LEN;
|
|
|
|
if (h1sl->rq.meth == HTTP_METH_CONNECT) {
|
|
h1m->flags &= ~(H1_MF_CLEN|H1_MF_CHNK);
|
|
h1m->curr_len = h1m->body_len = 0;
|
|
}
|
|
|
|
|
|
flags = h1m_htx_sl_flags(h1m);
|
|
if ((flags & (HTX_SL_F_CONN_UPG|HTX_SL_F_BODYLESS)) == HTX_SL_F_CONN_UPG) {
|
|
int i;
|
|
|
|
for (i = 0; hdrs[i].n.len; i++) {
|
|
if (isteqi(hdrs[i].n, ist("upgrade")))
|
|
hdrs[i].v = IST_NULL;
|
|
}
|
|
h1m->flags &=~ H1_MF_CONN_UPG;
|
|
flags &= ~HTX_SL_F_CONN_UPG;
|
|
}
|
|
sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth, uri, vsn);
|
|
if (!sl || !htx_add_all_headers(htx, hdrs))
|
|
goto error;
|
|
sl->info.req.meth = h1sl->rq.meth;
|
|
|
|
/* Check if the uri contains an authority. Also check if it contains an
|
|
* explicit scheme and if it is "http" or "https". */
|
|
if (h1sl->rq.meth == HTTP_METH_CONNECT)
|
|
sl->flags |= HTX_SL_F_HAS_AUTHORITY;
|
|
else if (uri.len && uri.ptr[0] != '/' && uri.ptr[0] != '*') {
|
|
sl->flags |= (HTX_SL_F_HAS_AUTHORITY|HTX_SL_F_HAS_SCHM);
|
|
if (uri.len > 4 && (uri.ptr[0] | 0x20) == 'h')
|
|
sl->flags |= ((uri.ptr[4] == ':') ? HTX_SL_F_SCHM_HTTP : HTX_SL_F_SCHM_HTTPS);
|
|
|
|
/* absolute-form target URI present, proceed to scheme-based
|
|
* normalization */
|
|
http_scheme_based_normalize(htx);
|
|
}
|
|
|
|
/* If body length cannot be determined, set htx->extra to
|
|
* HTX_UNKOWN_PAYLOAD_LENGTH. This value is impossible in other cases.
|
|
*/
|
|
htx->extra = ((h1m->flags & H1_MF_XFER_LEN) ? h1m->curr_len : HTX_UNKOWN_PAYLOAD_LENGTH);
|
|
|
|
end:
|
|
return 1;
|
|
output_full:
|
|
h1m_init_req(h1m);
|
|
h1m->flags |= (H1_MF_NO_PHDR|H1_MF_CLEAN_CONN_HDR);
|
|
return -2;
|
|
error:
|
|
h1m->err_pos = h1m->next;
|
|
h1m->err_state = h1m->state;
|
|
htx->flags |= HTX_FL_PARSING_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
/* Postprocess the parsed headers for a response and convert them into an htx
|
|
* message. It returns the number of bytes parsed if > 0, or 0 if it couldn't
|
|
* proceed. Parsing errors are reported by setting the htx flag
|
|
* HTX_FL_PARSING_ERROR and filling h1m->err_pos and h1m->err_state fields.
|
|
*/
|
|
static int h1_postparse_res_hdrs(struct h1m *h1m, union h1_sl *h1sl, struct htx *htx,
|
|
struct http_hdr *hdrs, size_t max)
|
|
{
|
|
struct htx_sl *sl;
|
|
struct ist vsn, status, reason;
|
|
unsigned int flags;
|
|
uint16_t code = 0;
|
|
|
|
if (h1sl) {
|
|
/* For HTTP responses, the start-line was parsed */
|
|
code = h1sl->st.status;
|
|
vsn = h1sl->st.v;
|
|
status = h1sl->st.c;
|
|
reason = h1sl->st.r;
|
|
}
|
|
else {
|
|
/* For FCGI responses, there is no start(-line but the "Status"
|
|
* header must be parsed, if found.
|
|
*/
|
|
int hdr;
|
|
|
|
vsn = ((h1m->flags & H1_MF_VER_11) ? ist("HTTP/1.1") : ist("HTTP/1.0"));
|
|
for (hdr = 0; hdrs[hdr].n.len; hdr++) {
|
|
if (isteqi(hdrs[hdr].n, ist("status"))) {
|
|
code = http_parse_status_val(hdrs[hdr].v, &status, &reason);
|
|
}
|
|
else if (isteqi(hdrs[hdr].n, ist("location"))) {
|
|
code = 302;
|
|
status = ist("302");
|
|
reason = ist("Moved Temporarily");
|
|
}
|
|
}
|
|
if (!code) {
|
|
code = 200;
|
|
status = ist("200");
|
|
reason = ist("OK");
|
|
}
|
|
/* FIXME: Check the codes 1xx ? */
|
|
}
|
|
|
|
/* Be sure the message, once converted into HTX, will not exceed the max
|
|
* size allowed.
|
|
*/
|
|
if (h1_eval_htx_size(vsn, status, reason, hdrs) > max) {
|
|
if (htx_is_empty(htx))
|
|
goto error;
|
|
goto output_full;
|
|
}
|
|
|
|
if ((h1m->flags & (H1_MF_CONN_UPG|H1_MF_UPG_WEBSOCKET)) && code != 101)
|
|
h1m->flags &= ~(H1_MF_CONN_UPG|H1_MF_UPG_WEBSOCKET);
|
|
|
|
if (((h1m->flags & H1_MF_METH_CONNECT) && code >= 200 && code < 300) || code == 101) {
|
|
h1m->flags &= ~(H1_MF_CLEN|H1_MF_CHNK);
|
|
h1m->flags |= H1_MF_XFER_LEN;
|
|
h1m->curr_len = h1m->body_len = 0;
|
|
}
|
|
else if ((h1m->flags & H1_MF_METH_HEAD) || (code >= 100 && code < 200) ||
|
|
(code == 204) || (code == 304)) {
|
|
/* Responses known to have no body. */
|
|
h1m->flags &= ~(H1_MF_CLEN|H1_MF_CHNK);
|
|
h1m->flags |= H1_MF_XFER_LEN;
|
|
h1m->curr_len = h1m->body_len = 0;
|
|
}
|
|
else if (h1m->flags & (H1_MF_CLEN|H1_MF_CHNK)) {
|
|
/* Responses with a known body length. */
|
|
h1m->flags |= H1_MF_XFER_LEN;
|
|
}
|
|
|
|
flags = h1m_htx_sl_flags(h1m);
|
|
sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, vsn, status, reason);
|
|
if (!sl || !htx_add_all_headers(htx, hdrs))
|
|
goto error;
|
|
sl->info.res.status = code;
|
|
|
|
/* If body length cannot be determined, set htx->extra to
|
|
* HTX_UNKOWN_PAYLOAD_LENGTH. This value is impossible in other cases.
|
|
*/
|
|
htx->extra = ((h1m->flags & H1_MF_XFER_LEN) ? h1m->curr_len : HTX_UNKOWN_PAYLOAD_LENGTH);
|
|
|
|
end:
|
|
return 1;
|
|
output_full:
|
|
h1m_init_res(h1m);
|
|
h1m->flags |= (H1_MF_NO_PHDR|H1_MF_CLEAN_CONN_HDR);
|
|
return -2;
|
|
error:
|
|
h1m->err_pos = h1m->next;
|
|
h1m->err_state = h1m->state;
|
|
htx->flags |= HTX_FL_PARSING_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
/* Parse HTTP/1 headers. It returns the number of bytes parsed on success, 0 if
|
|
* headers are incomplete, -1 if an error occurred or -2 if it needs more space
|
|
* to proceed while the output buffer is not empty. Parsing errors are reported
|
|
* by setting the htx flag HTX_FL_PARSING_ERROR and filling h1m->err_pos and
|
|
* h1m->err_state fields. This functions is responsible to update the parser
|
|
* state <h1m> and the start-line <h1sl> if not NULL. For the requests, <h1sl>
|
|
* must always be provided. For responses, <h1sl> may be NULL and <h1m> flags
|
|
* HTTP_METH_CONNECT of HTTP_METH_HEAD may be set.
|
|
*/
|
|
int h1_parse_msg_hdrs(struct h1m *h1m, union h1_sl *h1sl, struct htx *dsthtx,
|
|
struct buffer *srcbuf, size_t ofs, size_t max)
|
|
{
|
|
struct http_hdr hdrs[global.tune.max_http_hdr];
|
|
int total = 0, ret = 0;
|
|
|
|
if (!max || !b_data(srcbuf))
|
|
goto end;
|
|
|
|
/* Realing input buffer if necessary */
|
|
if (b_head(srcbuf) + b_data(srcbuf) > b_wrap(srcbuf))
|
|
b_slow_realign_ofs(srcbuf, trash.area, 0);
|
|
|
|
if (!h1sl) {
|
|
/* If there no start-line, be sure to only parse the headers */
|
|
h1m->flags |= H1_MF_HDRS_ONLY;
|
|
}
|
|
ret = h1_headers_to_hdr_list(b_peek(srcbuf, ofs), b_tail(srcbuf),
|
|
hdrs, sizeof(hdrs)/sizeof(hdrs[0]), h1m, h1sl);
|
|
if (ret <= 0) {
|
|
/* Incomplete or invalid message. If the input buffer only
|
|
* contains headers and is full, which is detected by it being
|
|
* full and the offset to be zero, it's an error because
|
|
* headers are too large to be handled by the parser. */
|
|
if (ret < 0 || (!ret && !ofs && !buf_room_for_htx_data(srcbuf)))
|
|
goto error;
|
|
goto end;
|
|
}
|
|
total = ret;
|
|
|
|
/* messages headers fully parsed, do some checks to prepare the body
|
|
* parsing.
|
|
*/
|
|
|
|
if (!(h1m->flags & H1_MF_RESP)) {
|
|
if (!h1_process_req_vsn(h1m, h1sl)) {
|
|
h1m->err_pos = h1sl->rq.v.ptr - b_head(srcbuf);
|
|
h1m->err_state = h1m->state;
|
|
goto vsn_error;
|
|
}
|
|
ret = h1_postparse_req_hdrs(h1m, h1sl, dsthtx, hdrs, max);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
else {
|
|
if (h1sl && !h1_process_res_vsn(h1m, h1sl)) {
|
|
h1m->err_pos = h1sl->st.v.ptr - b_head(srcbuf);
|
|
h1m->err_state = h1m->state;
|
|
goto vsn_error;
|
|
}
|
|
ret = h1_postparse_res_hdrs(h1m, h1sl, dsthtx, hdrs, max);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
/* Switch messages without any payload to DONE state */
|
|
if (((h1m->flags & H1_MF_CLEN) && h1m->body_len == 0) ||
|
|
((h1m->flags & (H1_MF_XFER_LEN|H1_MF_CLEN|H1_MF_CHNK)) == H1_MF_XFER_LEN)) {
|
|
h1m->state = H1_MSG_DONE;
|
|
dsthtx->flags |= HTX_FL_EOM;
|
|
}
|
|
|
|
end:
|
|
return total;
|
|
error:
|
|
h1m->err_pos = h1m->next;
|
|
h1m->err_state = h1m->state;
|
|
vsn_error:
|
|
dsthtx->flags |= HTX_FL_PARSING_ERROR;
|
|
return -1;
|
|
|
|
}
|
|
|
|
/* Copy data from <srbuf> into an DATA block in <dsthtx>. If possible, a
|
|
* zero-copy is performed. It returns the number of bytes copied.
|
|
*/
|
|
static size_t h1_copy_msg_data(struct htx **dsthtx, struct buffer *srcbuf, size_t ofs,
|
|
size_t count, size_t max, struct buffer *htxbuf)
|
|
{
|
|
struct htx *tmp_htx = *dsthtx;
|
|
size_t block1, block2, ret = 0;
|
|
|
|
/* Be prepared to create at least one HTX block by reserving its size
|
|
* and adjust <count> accordingly.
|
|
*/
|
|
if (max <= sizeof(struct htx_blk))
|
|
goto end;
|
|
max -= sizeof(struct htx_blk);
|
|
if (count > max)
|
|
count = max;
|
|
|
|
/* very often with large files we'll face the following
|
|
* situation :
|
|
* - htx is empty and points to <htxbuf>
|
|
* - count == srcbuf->data
|
|
* - srcbuf->head == sizeof(struct htx)
|
|
* => we can swap the buffers and place an htx header into
|
|
* the target buffer instead
|
|
*/
|
|
if (unlikely(htx_is_empty(tmp_htx) && count == b_data(srcbuf) &&
|
|
!ofs && b_head_ofs(srcbuf) == sizeof(struct htx))) {
|
|
void *raw_area = srcbuf->area;
|
|
void *htx_area = htxbuf->area;
|
|
struct htx_blk *blk;
|
|
|
|
srcbuf->area = htx_area;
|
|
htxbuf->area = raw_area;
|
|
tmp_htx = (struct htx *)htxbuf->area;
|
|
tmp_htx->size = htxbuf->size - sizeof(*tmp_htx);
|
|
htx_reset(tmp_htx);
|
|
b_set_data(htxbuf, b_size(htxbuf));
|
|
|
|
blk = htx_add_blk(tmp_htx, HTX_BLK_DATA, count);
|
|
blk->info += count;
|
|
|
|
*dsthtx = tmp_htx;
|
|
/* nothing else to do, the old buffer now contains an
|
|
* empty pre-initialized HTX header
|
|
*/
|
|
return count;
|
|
}
|
|
|
|
/* * First block is the copy of contiguous data starting at offset <ofs>
|
|
* with <count> as max. <max> is updated accordingly
|
|
*
|
|
* * Second block is the remaining (count - block1) if <max> is large
|
|
* enough. Another HTX block is reserved.
|
|
*/
|
|
block1 = b_contig_data(srcbuf, ofs);
|
|
block2 = 0;
|
|
if (block1 > count)
|
|
block1 = count;
|
|
max -= block1;
|
|
|
|
if (max > sizeof(struct htx_blk)) {
|
|
block2 = count - block1;
|
|
max -= sizeof(struct htx_blk);
|
|
if (block2 > max)
|
|
block2 = max;
|
|
}
|
|
|
|
ret = htx_add_data(tmp_htx, ist2(b_peek(srcbuf, ofs), block1));
|
|
if (ret == block1 && block2)
|
|
ret += htx_add_data(tmp_htx, ist2(b_orig(srcbuf), block2));
|
|
end:
|
|
return ret;
|
|
}
|
|
|
|
static const char hextable[] = {
|
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
|
|
-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
|
};
|
|
|
|
/* Generic function to parse the current HTTP chunk. It may be used to parsed
|
|
* any kind of chunks, including incomplete HTTP chunks or split chunks
|
|
* because the buffer wraps. This version tries to performed zero-copy on large
|
|
* chunks if possible.
|
|
*/
|
|
static size_t h1_parse_chunk(struct h1m *h1m, struct htx **dsthtx,
|
|
struct buffer *srcbuf, size_t ofs, size_t *max,
|
|
struct buffer *htxbuf)
|
|
{
|
|
uint64_t chksz;
|
|
size_t sz, used, lmax, total = 0;
|
|
int ret = 0;
|
|
|
|
lmax = *max;
|
|
switch (h1m->state) {
|
|
case H1_MSG_DATA:
|
|
new_chunk:
|
|
used = htx_used_space(*dsthtx);
|
|
if (b_data(srcbuf) == ofs || lmax <= sizeof(struct htx_blk))
|
|
break;
|
|
|
|
sz = b_data(srcbuf) - ofs;
|
|
if (unlikely(sz > h1m->curr_len))
|
|
sz = h1m->curr_len;
|
|
sz = h1_copy_msg_data(dsthtx, srcbuf, ofs, sz, lmax, htxbuf);
|
|
lmax -= htx_used_space(*dsthtx) - used;
|
|
ofs += sz;
|
|
total += sz;
|
|
h1m->curr_len -= sz;
|
|
if (h1m->curr_len)
|
|
break;
|
|
|
|
h1m->state = H1_MSG_CHUNK_CRLF;
|
|
__fallthrough;
|
|
|
|
case H1_MSG_CHUNK_CRLF:
|
|
ret = h1_skip_chunk_crlf(srcbuf, ofs, b_data(srcbuf));
|
|
if (ret <= 0)
|
|
break;
|
|
ofs += ret;
|
|
total += ret;
|
|
|
|
/* Don't parse next chunk to try to handle contiguous chunks if possible */
|
|
h1m->state = H1_MSG_CHUNK_SIZE;
|
|
break;
|
|
|
|
case H1_MSG_CHUNK_SIZE:
|
|
ret = h1_parse_chunk_size(srcbuf, ofs, b_data(srcbuf), &chksz);
|
|
if (ret <= 0)
|
|
break;
|
|
h1m->state = ((!chksz) ? H1_MSG_TRAILERS : H1_MSG_DATA);
|
|
h1m->curr_len = chksz;
|
|
h1m->body_len += chksz;
|
|
ofs += ret;
|
|
total += ret;
|
|
|
|
if (h1m->curr_len) {
|
|
h1m->state = H1_MSG_DATA;
|
|
goto new_chunk;
|
|
}
|
|
h1m->state = H1_MSG_TRAILERS;
|
|
break;
|
|
|
|
default:
|
|
/* unexpected */
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
(*dsthtx)->flags |= HTX_FL_PARSING_ERROR;
|
|
h1m->err_state = h1m->state;
|
|
h1m->err_pos = ofs;
|
|
total = 0;
|
|
}
|
|
|
|
/* Don't forget to update htx->extra */
|
|
(*dsthtx)->extra = h1m->curr_len;
|
|
*max = lmax;
|
|
return total;
|
|
}
|
|
|
|
/* Parses full contiguous HTTP chunks. This version is optimized for small
|
|
* chunks and does not performed zero-copy. It must be called in
|
|
* H1_MSG_CHUNK_SIZE state. Be careful if you change something in this
|
|
* function. It is really sensitive, any change may have an impact on
|
|
* performance.
|
|
*/
|
|
static size_t h1_parse_full_contig_chunks(struct h1m *h1m, struct htx **dsthtx,
|
|
struct buffer *srcbuf, size_t ofs, size_t *max,
|
|
struct buffer *htxbuf)
|
|
{
|
|
char *start, *end, *dptr;
|
|
ssize_t dpos, ridx, save;
|
|
size_t lmax, total = 0;
|
|
uint64_t chksz;
|
|
struct htx_ret htxret;
|
|
|
|
lmax = *max;
|
|
if (lmax <= sizeof(struct htx_blk))
|
|
goto out;
|
|
|
|
/* source info :
|
|
* start : pointer at <ofs> position
|
|
* end : pointer marking the end of data to parse
|
|
* ridx : the reverse index (negative) marking the parser position (end[ridx])
|
|
*/
|
|
ridx = -b_contig_data(srcbuf, ofs);
|
|
if (!ridx)
|
|
goto out;
|
|
start = b_peek(srcbuf, ofs);
|
|
end = start - ridx;
|
|
|
|
/* Reserve the maximum possible size for the data */
|
|
htxret = htx_reserve_max_data(*dsthtx);
|
|
if (!htxret.blk)
|
|
goto out;
|
|
|
|
/* destination info :
|
|
* dptr : pointer on the beginning of the data
|
|
* dpos : current position where to copy data
|
|
*/
|
|
dptr = htx_get_blk_ptr(*dsthtx, htxret.blk);
|
|
dpos = htxret.ret;
|
|
|
|
/* Empty DATA block is not possible, thus if <dpos> is the beginning of
|
|
* the block, it means it is a new block. We can remove the block size
|
|
* from <max>. Then we must adjust it if it exceeds the free size in the
|
|
* block.
|
|
*/
|
|
if (!dpos)
|
|
lmax -= sizeof(struct htx_blk);
|
|
if (lmax > htx_get_blksz(htxret.blk) - dpos)
|
|
lmax = htx_get_blksz(htxret.blk) - dpos;
|
|
|
|
while (1) {
|
|
/* The chunk size is in the following form, though we are only
|
|
* interested in the size and CRLF :
|
|
* 1*HEXDIGIT *WSP *[ ';' extensions ] CRLF
|
|
*/
|
|
chksz = 0;
|
|
save = ridx; /* Save the parser position to rewind if necessary */
|
|
while (1) {
|
|
int c;
|
|
|
|
if (!ridx)
|
|
goto end_parsing;
|
|
|
|
/* Convert current character */
|
|
c = hextable[(unsigned char)end[ridx]];
|
|
|
|
/* not a hex digit anymore */
|
|
if (c & 0xF0)
|
|
break;
|
|
|
|
/* Update current chunk size */
|
|
chksz = (chksz << 4) + c;
|
|
|
|
if (unlikely(chksz & 0xF0000000000000ULL)) {
|
|
/* Don't get more than 13 hexa-digit (2^52 - 1)
|
|
* to never fed possibly bogus values from
|
|
* languages that use floats for their integers
|
|
*/
|
|
goto parsing_error;
|
|
}
|
|
++ridx;
|
|
}
|
|
|
|
if (unlikely(chksz > lmax))
|
|
goto end_parsing;
|
|
|
|
if (unlikely(ridx == save)) {
|
|
/* empty size not allowed */
|
|
goto parsing_error;
|
|
}
|
|
|
|
/* Skip spaces */
|
|
while (HTTP_IS_SPHT(end[ridx])) {
|
|
if (!++ridx)
|
|
goto end_parsing;
|
|
}
|
|
|
|
/* Up to there, we know that at least one byte is present. Check
|
|
* for the end of chunk size.
|
|
*/
|
|
while (1) {
|
|
if (likely(end[ridx] == '\r')) {
|
|
/* Parse CRLF */
|
|
if (!++ridx)
|
|
goto end_parsing;
|
|
if (unlikely(end[ridx] != '\n')) {
|
|
/* CR must be followed by LF */
|
|
goto parsing_error;
|
|
}
|
|
|
|
/* done */
|
|
++ridx;
|
|
break;
|
|
}
|
|
else if (end[ridx] == '\n') {
|
|
/* Parse LF only, nothing more to do */
|
|
++ridx;
|
|
break;
|
|
}
|
|
else if (likely(end[ridx] == ';')) {
|
|
/* chunk extension, ends at next CRLF */
|
|
if (!++ridx)
|
|
goto end_parsing;
|
|
while (!HTTP_IS_CRLF(end[ridx])) {
|
|
if (!++ridx)
|
|
goto end_parsing;
|
|
}
|
|
/* we have a CRLF now, loop above */
|
|
continue;
|
|
}
|
|
else {
|
|
/* all other characters are unexpected */
|
|
goto parsing_error;
|
|
}
|
|
}
|
|
|
|
/* Exit if it is the last chunk */
|
|
if (unlikely(!chksz)) {
|
|
h1m->state = H1_MSG_TRAILERS;
|
|
save = ridx;
|
|
goto end_parsing;
|
|
}
|
|
|
|
/* Now check if the whole chunk is here (including the CRLF at
|
|
* the end), otherwise we switch in H1_MSG_DATA state.
|
|
*/
|
|
if (chksz + 2 > -ridx) {
|
|
h1m->curr_len = chksz;
|
|
h1m->body_len += chksz;
|
|
h1m->state = H1_MSG_DATA;
|
|
(*dsthtx)->extra = h1m->curr_len;
|
|
save = ridx;
|
|
goto end_parsing;
|
|
}
|
|
|
|
memcpy(dptr + dpos, end + ridx, chksz);
|
|
h1m->body_len += chksz;
|
|
lmax -= chksz;
|
|
dpos += chksz;
|
|
ridx += chksz;
|
|
|
|
/* Parse CRLF or LF (always present) */
|
|
if (likely(end[ridx] == '\r'))
|
|
++ridx;
|
|
if (end[ridx] != '\n') {
|
|
h1m->state = H1_MSG_CHUNK_CRLF;
|
|
goto parsing_error;
|
|
}
|
|
++ridx;
|
|
}
|
|
|
|
end_parsing:
|
|
ridx = save;
|
|
|
|
/* Adjust the HTX block size or remove the block if nothing was copied
|
|
* (Empty HTX data block are not supported).
|
|
*/
|
|
if (!dpos)
|
|
htx_remove_blk(*dsthtx, htxret.blk);
|
|
else
|
|
htx_change_blk_value_len(*dsthtx, htxret.blk, dpos);
|
|
total = end + ridx - start;
|
|
*max = lmax;
|
|
|
|
out:
|
|
return total;
|
|
|
|
parsing_error:
|
|
(*dsthtx)->flags |= HTX_FL_PARSING_ERROR;
|
|
h1m->err_state = h1m->state;
|
|
h1m->err_pos = ofs + end + ridx - start;
|
|
return 0;
|
|
}
|
|
|
|
/* Parse HTTP chunks. This function relies on an optimized function to parse
|
|
* contiguous chunks if possible. Otherwise, when a chunk is incomplete or when
|
|
* the underlying buffer is wrapping, a generic function is used.
|
|
*/
|
|
static size_t h1_parse_msg_chunks(struct h1m *h1m, struct htx **dsthtx,
|
|
struct buffer *srcbuf, size_t ofs, size_t max,
|
|
struct buffer *htxbuf)
|
|
{
|
|
size_t ret, total = 0;
|
|
|
|
while (ofs < b_data(srcbuf)) {
|
|
ret = 0;
|
|
|
|
/* First parse full contiguous chunks. It is only possible if we
|
|
* are waiting for the next chunk size.
|
|
*/
|
|
if (h1m->state == H1_MSG_CHUNK_SIZE) {
|
|
ret = h1_parse_full_contig_chunks(h1m, dsthtx, srcbuf, ofs, &max, htxbuf);
|
|
/* exit on error */
|
|
if (!ret && (*dsthtx)->flags & HTX_FL_PARSING_ERROR) {
|
|
total = 0;
|
|
break;
|
|
}
|
|
/* or let a chance to parse remaining data */
|
|
total += ret;
|
|
ofs += ret;
|
|
ret = 0;
|
|
}
|
|
|
|
/* If some data remains, try to parse it using the generic
|
|
* function handling incomplete chunks and split chunks
|
|
* because of a wrapping buffer.
|
|
*/
|
|
if (h1m->state < H1_MSG_TRAILERS && ofs < b_data(srcbuf)) {
|
|
ret = h1_parse_chunk(h1m, dsthtx, srcbuf, ofs, &max, htxbuf);
|
|
total += ret;
|
|
ofs += ret;
|
|
}
|
|
|
|
/* nothing more was parsed or parsing was stopped on incomplete
|
|
* chunk, we can exit, handling parsing error if necessary.
|
|
*/
|
|
if (!ret || h1m->state != H1_MSG_CHUNK_SIZE) {
|
|
if ((*dsthtx)->flags & HTX_FL_PARSING_ERROR)
|
|
total = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
/* Parse HTTP/1 body. It returns the number of bytes parsed if > 0, or 0 if it
|
|
* couldn't proceed. Parsing errors are reported by setting the htx flags
|
|
* HTX_FL_PARSING_ERROR and filling h1m->err_pos and h1m->err_state fields. This
|
|
* functions is responsible to update the parser state <h1m>.
|
|
*/
|
|
size_t h1_parse_msg_data(struct h1m *h1m, struct htx **dsthtx,
|
|
struct buffer *srcbuf, size_t ofs, size_t max,
|
|
struct buffer *htxbuf)
|
|
{
|
|
size_t sz, total = 0;
|
|
|
|
if (b_data(srcbuf) == ofs || max <= sizeof(struct htx_blk))
|
|
return 0;
|
|
|
|
if (h1m->flags & H1_MF_CLEN) {
|
|
/* content-length: read only h2m->body_len */
|
|
sz = b_data(srcbuf) - ofs;
|
|
if (unlikely(sz > h1m->curr_len))
|
|
sz = h1m->curr_len;
|
|
sz = h1_copy_msg_data(dsthtx, srcbuf, ofs, sz, max, htxbuf);
|
|
h1m->curr_len -= sz;
|
|
(*dsthtx)->extra = h1m->curr_len;
|
|
total += sz;
|
|
if (!h1m->curr_len) {
|
|
h1m->state = H1_MSG_DONE;
|
|
(*dsthtx)->flags |= HTX_FL_EOM;
|
|
}
|
|
}
|
|
else if (h1m->flags & H1_MF_CHNK) {
|
|
/* te:chunked : parse chunks */
|
|
total += h1_parse_msg_chunks(h1m, dsthtx, srcbuf, ofs, max, htxbuf);
|
|
}
|
|
else if (h1m->flags & H1_MF_XFER_LEN) {
|
|
/* XFER_LEN is set but not CLEN nor CHNK, it means there is no
|
|
* body. Switch the message in DONE state
|
|
*/
|
|
h1m->state = H1_MSG_DONE;
|
|
(*dsthtx)->flags |= HTX_FL_EOM;
|
|
}
|
|
else {
|
|
/* no content length, read till SHUTW */
|
|
sz = b_data(srcbuf) - ofs;
|
|
sz = h1_copy_msg_data(dsthtx, srcbuf, ofs, sz, max, htxbuf);
|
|
total += sz;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
/* Parse HTTP/1 trailers. It returns the number of bytes parsed on success, 0 if
|
|
* trailers are incomplete, -1 if an error occurred or -2 if it needs more space
|
|
* to proceed while the output buffer is not empty. Parsing errors are reported
|
|
* by setting the htx flags HTX_FL_PARSING_ERROR and filling h1m->err_pos and
|
|
* h1m->err_state fields. This functions is responsible to update the parser
|
|
* state <h1m>.
|
|
*/
|
|
int h1_parse_msg_tlrs(struct h1m *h1m, struct htx *dsthtx,
|
|
struct buffer *srcbuf, size_t ofs, size_t max)
|
|
{
|
|
struct http_hdr hdrs[global.tune.max_http_hdr];
|
|
struct h1m tlr_h1m;
|
|
int ret = 0;
|
|
|
|
if (b_data(srcbuf) == ofs) {
|
|
/* Nothing to parse */
|
|
goto end;
|
|
}
|
|
if (!max) {
|
|
/* No more room */
|
|
goto output_full;
|
|
}
|
|
|
|
/* Realing input buffer if necessary */
|
|
if (b_peek(srcbuf, ofs) > b_tail(srcbuf))
|
|
b_slow_realign_ofs(srcbuf, trash.area, 0);
|
|
|
|
tlr_h1m.flags = (H1_MF_NO_PHDR|H1_MF_HDRS_ONLY);
|
|
ret = h1_headers_to_hdr_list(b_peek(srcbuf, ofs), b_tail(srcbuf),
|
|
hdrs, sizeof(hdrs)/sizeof(hdrs[0]), &tlr_h1m, NULL);
|
|
if (ret <= 0) {
|
|
/* Incomplete or invalid trailers. If the input buffer only
|
|
* contains trailers and is full, which is detected by it being
|
|
* full and the offset to be zero, it's an error because
|
|
* trailers are too large to be handled by the parser. */
|
|
if (ret < 0 || (!ret && !ofs && !buf_room_for_htx_data(srcbuf)))
|
|
goto error;
|
|
goto end;
|
|
}
|
|
|
|
/* messages trailers fully parsed. */
|
|
if (h1_eval_htx_hdrs_size(hdrs) > max) {
|
|
if (htx_is_empty(dsthtx))
|
|
goto error;
|
|
goto output_full;
|
|
}
|
|
|
|
if (!htx_add_all_trailers(dsthtx, hdrs))
|
|
goto error;
|
|
|
|
h1m->state = H1_MSG_DONE;
|
|
dsthtx->flags |= HTX_FL_EOM;
|
|
|
|
end:
|
|
return ret;
|
|
output_full:
|
|
return -2;
|
|
error:
|
|
h1m->err_state = h1m->state;
|
|
h1m->err_pos = h1m->next;
|
|
dsthtx->flags |= HTX_FL_PARSING_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
/* Appends the H1 representation of the request line <sl> to the chunk <chk>. It
|
|
* returns 1 if data are successfully appended, otherwise it returns 0.
|
|
*/
|
|
int h1_format_htx_reqline(const struct htx_sl *sl, struct buffer *chk)
|
|
{
|
|
struct ist uri;
|
|
size_t sz = chk->data;
|
|
|
|
uri = h1_get_uri(sl);
|
|
if (!chunk_memcat(chk, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)) ||
|
|
!chunk_memcat(chk, " ", 1) ||
|
|
!chunk_memcat(chk, uri.ptr, uri.len) ||
|
|
!chunk_memcat(chk, " ", 1))
|
|
goto full;
|
|
|
|
if (sl->flags & HTX_SL_F_VER_11) {
|
|
if (!chunk_memcat(chk, "HTTP/1.1", 8))
|
|
goto full;
|
|
}
|
|
else {
|
|
if (!chunk_memcat(chk, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)))
|
|
goto full;
|
|
}
|
|
|
|
if (!chunk_memcat(chk, "\r\n", 2))
|
|
goto full;
|
|
|
|
return 1;
|
|
|
|
full:
|
|
chk->data = sz;
|
|
return 0;
|
|
}
|
|
|
|
/* Appends the H1 representation of the status line <sl> to the chunk <chk>. It
|
|
* returns 1 if data are successfully appended, otherwise it returns 0.
|
|
*/
|
|
int h1_format_htx_stline(const struct htx_sl *sl, struct buffer *chk)
|
|
{
|
|
size_t sz = chk->data;
|
|
|
|
if (HTX_SL_LEN(sl) + 4 > b_room(chk))
|
|
return 0;
|
|
|
|
if (sl->flags & HTX_SL_F_VER_11) {
|
|
if (!chunk_memcat(chk, "HTTP/1.1", 8))
|
|
goto full;
|
|
}
|
|
else {
|
|
if (!chunk_memcat(chk, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)))
|
|
goto full;
|
|
}
|
|
if (!chunk_memcat(chk, " ", 1) ||
|
|
!chunk_memcat(chk, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl)) ||
|
|
!chunk_memcat(chk, " ", 1) ||
|
|
!chunk_memcat(chk, HTX_SL_RES_RPTR(sl), HTX_SL_RES_RLEN(sl)) ||
|
|
!chunk_memcat(chk, "\r\n", 2))
|
|
goto full;
|
|
|
|
return 1;
|
|
|
|
full:
|
|
chk->data = sz;
|
|
return 0;
|
|
}
|
|
|
|
/* Appends the H1 representation of the header <n> with the value <v> to the
|
|
* chunk <chk>. It returns 1 if data are successfully appended, otherwise it
|
|
* returns 0.
|
|
*/
|
|
int h1_format_htx_hdr(const struct ist n, const struct ist v, struct buffer *chk)
|
|
{
|
|
size_t sz = chk->data;
|
|
|
|
if (n.len + v.len + 4 > b_room(chk))
|
|
return 0;
|
|
|
|
if (!chunk_memcat(chk, n.ptr, n.len) ||
|
|
!chunk_memcat(chk, ": ", 2) ||
|
|
!chunk_memcat(chk, v.ptr, v.len) ||
|
|
!chunk_memcat(chk, "\r\n", 2))
|
|
goto full;
|
|
|
|
return 1;
|
|
|
|
full:
|
|
chk->data = sz;
|
|
return 0;
|
|
}
|
|
|
|
/* Appends the H1 representation of the data <data> to the chunk <chk>. If
|
|
* <chunked> is non-zero, it emits HTTP/1 chunk-encoded data. It returns 1 if
|
|
* data are successfully appended, otherwise it returns 0.
|
|
*/
|
|
int h1_format_htx_data(const struct ist data, struct buffer *chk, int chunked)
|
|
{
|
|
size_t sz = chk->data;
|
|
|
|
if (chunked) {
|
|
uint32_t chksz;
|
|
char tmp[10];
|
|
char *beg, *end;
|
|
|
|
chksz = data.len;
|
|
|
|
beg = end = tmp+10;
|
|
*--beg = '\n';
|
|
*--beg = '\r';
|
|
do {
|
|
*--beg = hextab[chksz & 0xF];
|
|
} while (chksz >>= 4);
|
|
|
|
if (!chunk_memcat(chk, beg, end - beg) ||
|
|
!chunk_memcat(chk, data.ptr, data.len) ||
|
|
!chunk_memcat(chk, "\r\n", 2))
|
|
goto full;
|
|
}
|
|
else {
|
|
if (!chunk_memcat(chk, data.ptr, data.len))
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
|
|
full:
|
|
chk->data = sz;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-indent-level: 8
|
|
* c-basic-offset: 8
|
|
* End:
|
|
*/
|