MEDIUM: mux-h1/h1-htx: move HTX convertion of H1 messages in dedicated file

To avoid code duplication in the futur mux FCGI, functions parsing H1 messages
and converting them into HTX have been moved in the file h1_htx.c. Some
specific parts remain in the mux H1. But most of the parsing is now generic.
This commit is contained in:
Christopher Faulet 2019-08-10 11:17:44 +02:00
parent 341fac1eb2
commit 4f0f88a9d0
5 changed files with 743 additions and 513 deletions

View File

@ -787,7 +787,7 @@ OBJS = src/http_ana.o src/cfgparse-listen.o src/stream.o \
src/protocol.o src/arg.o src/hpack-huff.o src/base64.o src/ring.o \
src/hash.o src/mailers.o src/activity.o src/version.o src/trace.o \
src/mworker.o src/mworker-prog.o src/debug.o src/wdt.o src/dict.o \
src/xprt_handshake.o
src/xprt_handshake.o src/h1_htx.o
EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o $(EBTREE_DIR)/eb32sctree.o \
$(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \

View File

@ -94,6 +94,8 @@ enum h1m_state {
#define H1_MF_NO_PHDR 0x00000400 // don't add pseudo-headers in the header list
#define H1_MF_HDRS_ONLY 0x00000800 // parse headers only
#define H1_MF_CLEAN_CONN_HDR 0x00001000 // skip close/keep-alive values of connection headers during parsing
#define H1_MF_METH_CONNECT 0x00002000 // Set for a response to a CONNECT request
#define H1_MF_METH_HEAD 0x00004000 // Set for a response to a HEAD request
/* Note: for a connection to be persistent, we need this for the request :
* - one of CLEN or CHNK

45
include/proto/h1_htx.h Normal file
View File

@ -0,0 +1,45 @@
/*
* include/types/h1_htx.h
* This file defines function prototypes for H1 manipulation using the
* internal representation.
*
* Copyright (C) 2019 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, version 2.1
* exclusively.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _PROTO_H1_HTX_H
#define _PROTO_H1_HTX_H
#include <common/buf.h>
#include <common/ist.h>
#include <common/h1.h>
int h1_parse_msg_hdrs(struct h1m *h1m, union h1_sl *h1sl, struct htx *dsthtx,
struct buffer *srcbuf, size_t ofs, size_t max);
int h1_parse_msg_data(struct h1m *h1m, struct htx *dsthtx,
struct buffer *srcbuf, size_t ofs, size_t max,
struct buffer *htxbuf);
int h1_parse_msg_tlrs(struct h1m *h1m, struct htx *dsthtx,
struct buffer *srcbuf, size_t ofs, size_t max);
#endif /* _PROTO_H1_HTX_H */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/

595
src/h1_htx.c Normal file
View File

@ -0,0 +1,595 @@
/*
* 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 <common/config.h>
#include <common/debug.h>
#include <common/cfgparse.h>
#include <common/h1.h>
#include <common/http.h>
#include <common/htx.h>
#include <proto/h1_htx.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;
}
/* Switch the message to tunnel mode. On the request, it must only be called for
* a CONNECT method. On the response, this function must only be called on
* successfull replies to CONNECT requests or on protocol switching.
*/
static void h1_set_tunnel_mode(struct h1m *h1m)
{
h1m->flags &= ~(H1_MF_XFER_LEN|H1_MF_CLEN|H1_MF_CHNK);
h1m->state = H1_MSG_TUNNEL;
}
/* 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 (*(sl->rq.v.ptr + 4) != '/' ||
!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;
}
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;
size_t used;
/* <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;
h1m_init_res(h1m);
h1m->flags |= (H1_MF_NO_PHDR|H1_MF_CLEAN_CONN_HDR);
return 0;
}
/* By default, request have always a known length */
h1m->flags |= H1_MF_XFER_LEN;
if (h1sl->rq.meth == HTTP_METH_CONNECT) {
/* Switch CONNECT requests to tunnel mode */
h1_set_tunnel_mode(h1m);
}
used = htx_used_space(htx);
flags = h1m_htx_sl_flags(h1m);
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 explicit scheme and if it is
* "http" or "https". */
if (uri.len && uri.ptr[0] != '/') {
sl->flags |= 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);
}
/* Set bytes used in the HTX mesage for the headers now */
sl->hdrs_bytes = htx_used_space(htx) - used;
/* If body length cannot be determined, set htx->extra to
* ULLONG_MAX. This value is impossible in other cases.
*/
htx->extra = ((h1m->flags & H1_MF_XFER_LEN) ? h1m->curr_len : ULLONG_MAX);
end:
return 1;
error:
h1m->err_pos = h1m->next;
h1m->err_state = h1m->state;
htx->flags |= HTX_FL_PARSING_ERROR;
return 0;
}
/* 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;
size_t used;
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;
h1m_init_res(h1m);
h1m->flags |= (H1_MF_NO_PHDR|H1_MF_CLEAN_CONN_HDR);
return 0;
}
if (((h1m->flags & H1_MF_METH_CONNECT) && code == 200) || code == 101) {
/* Switch successfull replies to CONNECT requests and
* protocol switching to tunnel mode. */
h1_set_tunnel_mode(h1m);
}
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;
}
else {
/* Responses with an unknown body length */
h1m->state = H1_MSG_TUNNEL;
}
used = htx_used_space(htx);
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;
/* Set bytes used in the HTX mesage for the headers now */
sl->hdrs_bytes = htx_used_space(htx) - used;
/* If body length cannot be determined, set htx->extra to
* ULLONG_MAX. This value is impossible in other cases.
*/
htx->extra = ((h1m->flags & H1_MF_XFER_LEN) ? h1m->curr_len : ULLONG_MAX);
end:
return 1;
error:
h1m->err_pos = h1m->next;
h1m->err_state = h1m->state;
htx->flags |= HTX_FL_PARSING_ERROR;
return 0;
}
/* Parse HTTP/1 headers. 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. 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 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(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;
}
/* 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;
}
if (!h1_postparse_req_hdrs(h1m, h1sl, dsthtx, hdrs, max))
ret = 0;
}
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;
}
if (!h1_postparse_res_hdrs(h1m, h1sl, dsthtx, hdrs, max))
ret = 0;
}
end:
return ret;
error:
h1m->err_pos = h1m->next;
h1m->err_state = h1m->state;
vsn_error:
dsthtx->flags |= HTX_FL_PARSING_ERROR;
return 0;
}
/* 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>.
*/
int h1_parse_msg_data(struct h1m *h1m, struct htx *dsthtx,
struct buffer *srcbuf, size_t ofs, size_t max,
struct buffer *htxbuf)
{
size_t total = 0;
int32_t ret = 0;
if (h1m->flags & H1_MF_CLEN) {
/* content-length: read only h2m->body_len */
ret = htx_get_max_blksz(dsthtx, max);
if ((uint64_t)ret > h1m->curr_len)
ret = h1m->curr_len;
if (ret > b_contig_data(srcbuf, ofs))
ret = b_contig_data(srcbuf, ofs);
if (ret) {
/* very often with large files we'll face the following
* situation :
* - htx is empty and points to <htxbuf>
* - ret == buf->data
* - buf->head == sizeof(struct htx)
* => we can swap the buffers and place an htx header into
* the target buffer instead
*/
int32_t try = ret;
if (unlikely(htx_is_empty(dsthtx) && ret == 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;
dsthtx = (struct htx *)htxbuf->area;
dsthtx->size = htxbuf->size - sizeof(*dsthtx);
htx_reset(dsthtx);
b_set_data(htxbuf, b_size(htxbuf));
blk = htx_add_blk(dsthtx, HTX_BLK_DATA, ret);
blk->info += ret;
/* nothing else to do, the old buffer now contains an
* empty pre-initialized HTX header
*/
}
else
ret = htx_add_data(dsthtx, ist2(b_peek(srcbuf, ofs), try));
h1m->curr_len -= ret;
max -= sizeof(struct htx_blk) + ret;
ofs += ret;
total += ret;
if (ret < try)
goto end;
}
if (!h1m->curr_len) {
if (max < sizeof(struct htx_blk) + 1 || !htx_add_endof(dsthtx, HTX_BLK_EOM))
goto end;
h1m->state = H1_MSG_DONE;
}
}
else if (h1m->flags & H1_MF_CHNK) {
/* te:chunked : parse chunks */
new_chunk:
if (h1m->state == H1_MSG_CHUNK_CRLF) {
ret = h1_skip_chunk_crlf(srcbuf, ofs, b_data(srcbuf));
if (ret <= 0)
goto end;
h1m->state = H1_MSG_CHUNK_SIZE;
ofs += ret;
total += ret;
}
if (h1m->state == H1_MSG_CHUNK_SIZE) {
unsigned int chksz;
ret = h1_parse_chunk_size(srcbuf, ofs, b_data(srcbuf), &chksz);
if (ret <= 0)
goto end;
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)
goto end;
}
if (h1m->state == H1_MSG_DATA) {
ret = htx_get_max_blksz(dsthtx, max);
if ((uint64_t)ret > h1m->curr_len)
ret = h1m->curr_len;
if (ret > b_contig_data(srcbuf, ofs))
ret = b_contig_data(srcbuf, ofs);
if (ret) {
int32_t try = ret;
ret = htx_add_data(dsthtx, ist2(b_peek(srcbuf, ofs), try));
h1m->curr_len -= ret;
max -= sizeof(struct htx_blk) + ret;
ofs += ret;
total += ret;
if (ret < try)
goto end;
}
if (!h1m->curr_len) {
h1m->state = H1_MSG_CHUNK_CRLF;
goto new_chunk;
}
goto end;
}
}
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
*/
if (max < sizeof(struct htx_blk) + 1 || !htx_add_endof(dsthtx, HTX_BLK_EOM))
goto end;
h1m->state = H1_MSG_DONE;
}
else {
/* no content length, read till SHUTW */
ret = htx_get_max_blksz(dsthtx, max);
if (ret > b_contig_data(srcbuf, ofs))
ret = b_contig_data(srcbuf, ofs);
if (ret)
total = htx_add_data(dsthtx, ist2(b_peek(srcbuf, ofs), ret));
}
end:
if (ret < 0) {
dsthtx->flags |= HTX_FL_PARSING_ERROR;
h1m->err_state = h1m->state;
h1m->err_pos = ofs;
total = 0;
}
/* update htx->extra, only when the body length is known */
if (h1m->flags & H1_MF_XFER_LEN)
dsthtx->extra = h1m->curr_len;
return total;
}
/* Parse HTTP/1 trailers. 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>.
*/
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 (!max || !b_data(srcbuf))
goto end;
/* Realing input buffer if necessary */
if (b_peek(srcbuf, ofs) > b_tail(srcbuf))
b_slow_realign(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;
ret = 0;
goto end;
}
if (!htx_add_all_trailers(dsthtx, hdrs))
goto error;
end:
return ret;
error:
h1m->err_state = h1m->state;
h1m->err_pos = h1m->next;
dsthtx->flags |= HTX_FL_PARSING_ERROR;
return 0;
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/

View File

@ -23,6 +23,7 @@
#include <types/session.h>
#include <proto/connection.h>
#include <proto/h1_htx.h>
#include <proto/http_htx.h>
#include <proto/log.h>
#include <proto/session.h>
@ -570,82 +571,6 @@ static void h1_parse_res_vsn(struct h1m *h1m, const struct htx_sl *sl)
h1m->flags |= H1_MF_VER_11;
}
/*
* 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 h1s *h1s, struct h1m *h1m, union h1_sl sl)
{
struct h1c *h1c = h1s->h1c;
/* 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 (!(h1c->px->options2 & PR_O2_REQBUG_OK)) {
if (sl.rq.v.len != 8)
return 0;
if (*(sl.rq.v.ptr + 4) != '/' ||
!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 h1s *h1s, struct h1m *h1m, union h1_sl sl)
{
struct h1c *h1c = h1s->h1c;
/* 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 (!(h1c->px->options2 & PR_O2_RSPBUG_OK)) {
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;
}
/* Deduce the connection mode of the client connection, depending on the
* configuration and the H1 message flags. This function is called twice, the
* first time when the request is parsed and the second time when the response
@ -955,38 +880,115 @@ static void h1_set_res_tunnel_mode(struct h1s *h1s)
}
}
/* Estimate the size of the HTX headers after the parsing, including the EOH. */
static size_t h1_eval_htx_hdrs_size(struct http_hdr *hdrs)
/*
* Parse HTTP/1 headers. It returns the number of bytes parsed if > 0, or 0 if
* it couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR
* flag. If relies on the function http_parse_msg_hdrs() to do the parsing.
*/
static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
struct buffer *buf, size_t *ofs, size_t max)
{
size_t sz = 0;
int i;
union h1_sl h1sl;
int ret = 0;
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;
if (!(h1m->flags & H1_MF_RESP)) {
/* Try to match H2 preface before parsing the request headers. */
ret = b_isteq(buf, 0, b_data(buf), ist(H2_CONN_PREFACE));
if (ret > 0)
goto h2c_upgrade;
}
else {
if (h1s->meth == HTTP_METH_CONNECT)
h1m->flags |= H1_MF_METH_CONNECT;
if (h1s->meth == HTTP_METH_HEAD)
h1m->flags |= H1_MF_METH_HEAD;
}
ret = h1_parse_msg_hdrs(h1m, &h1sl, htx, buf, *ofs, max);
if (!ret) {
if (htx->flags & HTX_FL_PARSING_ERROR) {
h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR);
h1s->cs->flags |= CS_FL_EOI;
h1_capture_bad_message(h1s->h1c, h1s, h1m, buf);
}
goto end;
}
if (!(h1m->flags & H1_MF_RESP)) {
h1s->meth = h1sl.rq.meth;
if (h1m->state == H1_MSG_TUNNEL)
h1_set_req_tunnel_mode(h1s);
}
else {
h1s->status = h1sl.st.status;
if (h1m->state == H1_MSG_TUNNEL)
h1_set_res_tunnel_mode(h1s);
}
h1_process_input_conn_mode(h1s, h1m, htx);
*ofs += ret;
end:
return ret;
h2c_upgrade:
h1s->h1c->flags |= H1C_F_UPG_H2C;
h1s->cs->flags |= CS_FL_EOI;
htx->flags |= HTX_FL_UPGRADE;
ret = 0;
goto end;
}
/* Estimate the size of the HTX request after the parsing. */
static size_t h1_eval_htx_req_size(struct h1m *h1m, union h1_sl *h1sl, struct http_hdr *hdrs)
/*
* 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 H1S_F_*_ERROR flag.
* If relies on the function http_parse_msg_data() to do the parsing.
*/
static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
struct buffer *buf, size_t *ofs, size_t max,
struct buffer *htxbuf)
{
size_t sz;
int ret;
/* size of the HTX start-line */
sz = sizeof(struct htx_blk) + sizeof(struct htx_sl) + h1sl->rq.m.len + h1sl->rq.u.len + h1sl->rq.v.len;
sz += h1_eval_htx_hdrs_size(hdrs);
return sz;
ret = h1_parse_msg_data(h1m, htx, buf, *ofs, max, htxbuf);
if (ret <= 0) {
if (ret < 0) {
h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR);
h1s->cs->flags |= CS_FL_EOI;
h1_capture_bad_message(h1s->h1c, h1s, h1m, buf);
}
return 0;
}
if (h1m->state == H1_MSG_DONE)
h1s->cs->flags |= CS_FL_EOI;
*ofs += ret;
return ret;
}
/* Estimate the size of the HTX response after the parsing. */
static size_t h1_eval_htx_res_size(struct h1m *h1m, union h1_sl *h1sl, struct http_hdr *hdrs)
/*
* Parse HTTP/1 trailers. It returns the number of bytes parsed if > 0, or 0 if
* it couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR
* flag and filling h1s->err_pos and h1s->err_state fields. This functions is
* responsible to update the parser state <h1m>.
*/
static size_t h1_process_trailers(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
struct buffer *buf, size_t *ofs, size_t max)
{
size_t sz;
int ret;
/* size of the HTX start-line */
sz = sizeof(struct htx_blk) + sizeof(struct htx_sl) + h1sl->st.v.len + h1sl->st.c.len + h1sl->st.r.len;
sz += h1_eval_htx_hdrs_size(hdrs);
return sz;
ret = h1_parse_msg_tlrs(h1m, htx, buf, *ofs, max);
if (ret <= 0) {
if (ret < 0) {
h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR);
h1s->cs->flags |= CS_FL_EOI;
h1_capture_bad_message(h1s->h1c, h1s, h1m, buf);
}
return 0;
}
*ofs += ret;
h1s->flags |= H1S_F_HAVE_I_TLR;
return ret;
}
/*
@ -1008,420 +1010,6 @@ static size_t h1_process_eom(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
return (sizeof(struct htx_blk) + 1);
}
/*
* Parse HTTP/1 headers. It returns the number of bytes parsed if > 0, or 0 if
* it couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR
* flag and filling h1s->err_pos and h1s->err_state fields. This functions is
* responsible to update the parser state <h1m>.
*/
static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
struct buffer *buf, size_t *ofs, size_t max)
{
struct htx_sl *sl;
struct http_hdr hdrs[global.tune.max_http_hdr];
union h1_sl h1sl;
unsigned int flags = HTX_SL_F_NONE;
size_t used;
int ret = 0;
if (!max || !b_data(buf))
goto end;
/* Realing input buffer if necessary */
if (b_head(buf) + b_data(buf) > b_wrap(buf))
b_slow_realign(buf, trash.area, 0);
if (!(h1m->flags & H1_MF_RESP)) {
/* Try to match H2 preface before parsing the request headers. */
ret = b_isteq(buf, 0, b_data(buf), ist(H2_CONN_PREFACE));
if (ret > 0)
goto h2c_upgrade;
}
ret = h1_headers_to_hdr_list(b_peek(buf, *ofs), b_tail(buf),
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(buf)))
goto error;
goto end;
}
/* messages headers fully parsed, do some checks to prepare the body
* parsing.
*/
/* Save the request's method or the response's status, check if the body
* length is known and check the VSN validity */
if (!(h1m->flags & H1_MF_RESP)) {
h1s->meth = h1sl.rq.meth;
/* By default, request have always a known length */
h1m->flags |= H1_MF_XFER_LEN;
if (h1s->meth == HTTP_METH_CONNECT) {
/* Switch CONNECT requests to tunnel mode */
h1_set_req_tunnel_mode(h1s);
}
if (!h1_process_req_vsn(h1s, h1m, h1sl)) {
h1m->err_pos = h1sl.rq.v.ptr - b_head(buf);
h1m->err_state = h1m->state;
goto vsn_error;
}
}
else {
h1s->status = h1sl.st.status;
if ((h1s->meth == HTTP_METH_CONNECT && h1s->status == 200) ||
h1s->status == 101) {
/* Switch successfull replies to CONNECT requests and
* protocol switching to tunnel mode. */
h1_set_res_tunnel_mode(h1s);
}
else if ((h1s->meth == HTTP_METH_HEAD) ||
(h1s->status >= 100 && h1s->status < 200) ||
(h1s->status == 204) || (h1s->status == 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;
}
else {
/* Responses with an unknown body length */
h1m->state = H1_MSG_TUNNEL;
}
if (!h1_process_res_vsn(h1s, h1m, h1sl)) {
h1m->err_pos = h1sl.st.v.ptr - b_head(buf);
h1m->err_state = h1m->state;
goto vsn_error;
}
}
/* Set HTX start-line flags */
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;
}
used = htx_used_space(htx);
if (!(h1m->flags & H1_MF_RESP)) {
if (h1_eval_htx_req_size(h1m, &h1sl, hdrs) > max) {
if (htx_is_empty(htx))
goto error;
h1m_init_req(h1m);
h1m->flags |= (H1_MF_NO_PHDR|H1_MF_CLEAN_CONN_HDR);
ret = 0;
goto end;
}
sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, h1sl.rq.m, h1sl.rq.u, h1sl.rq.v);
if (!sl || !htx_add_all_headers(htx, hdrs))
goto error;
sl->info.req.meth = h1s->meth;
/* Check if the uri contains an explicit scheme and if it is
* "http" or "https". */
if (h1sl.rq.u.len && h1sl.rq.u.ptr[0] != '/') {
sl->flags |= HTX_SL_F_HAS_SCHM;
if (h1sl.rq.u.len > 4 && (h1sl.rq.u.ptr[0] | 0x20) == 'h')
sl->flags |= ((h1sl.rq.u.ptr[4] == ':') ? HTX_SL_F_SCHM_HTTP : HTX_SL_F_SCHM_HTTPS);
}
}
else {
if (h1_eval_htx_res_size(h1m, &h1sl, hdrs) > max) {
if (htx_is_empty(htx))
goto error;
h1m_init_res(h1m);
h1m->flags |= (H1_MF_NO_PHDR|H1_MF_CLEAN_CONN_HDR);
ret = 0;
goto end;
}
flags |= HTX_SL_F_IS_RESP;
sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, h1sl.st.v, h1sl.st.c, h1sl.st.r);
if (!sl || !htx_add_all_headers(htx, hdrs))
goto error;
sl->info.res.status = h1s->status;
}
/* Set bytes used in the HTX mesage for the headers now */
sl->hdrs_bytes = htx_used_space(htx) - used;
h1_process_input_conn_mode(h1s, h1m, htx);
/* If body length cannot be determined, set htx->extra to
* ULLONG_MAX. This value is impossible in other cases.
*/
htx->extra = ((h1m->flags & H1_MF_XFER_LEN) ? h1m->curr_len : ULLONG_MAX);
*ofs += ret;
end:
return ret;
error:
h1m->err_state = h1m->state;
h1m->err_pos = h1m->next;
vsn_error:
h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR);
h1s->cs->flags |= CS_FL_EOI;
htx->flags |= HTX_FL_PARSING_ERROR;
h1_capture_bad_message(h1s->h1c, h1s, h1m, buf);
ret = 0;
goto end;
h2c_upgrade:
h1s->h1c->flags |= H1C_F_UPG_H2C;
h1s->cs->flags |= CS_FL_EOI;
htx->flags |= HTX_FL_UPGRADE;
ret = 0;
goto end;
}
/*
* 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 H1S_F_*_ERROR flag
* and filling h1s->err_pos and h1s->err_state fields. This functions is
* responsible to update the parser state <h1m>.
*/
static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
struct buffer *buf, size_t *ofs, size_t max,
struct buffer *htxbuf)
{
size_t total = 0;
int32_t ret = 0;
if (h1m->flags & H1_MF_XFER_LEN) {
if (h1m->flags & H1_MF_CLEN) {
/* content-length: read only h2m->body_len */
ret = htx_get_max_blksz(htx, max);
if ((uint64_t)ret > h1m->curr_len)
ret = h1m->curr_len;
if (ret > b_contig_data(buf, *ofs))
ret = b_contig_data(buf, *ofs);
if (ret) {
/* very often with large files we'll face the following
* situation :
* - htx is empty and points to <htxbuf>
* - ret == buf->data
* - buf->head == sizeof(struct htx)
* => we can swap the buffers and place an htx header into
* the target buffer instead
*/
int32_t try = ret;
if (unlikely(htx_is_empty(htx) && ret == b_data(buf) &&
!*ofs && b_head_ofs(buf) == sizeof(struct htx))) {
void *raw_area = buf->area;
void *htx_area = htxbuf->area;
struct htx_blk *blk;
buf->area = htx_area;
htxbuf->area = raw_area;
htx = (struct htx *)htxbuf->area;
htx->size = htxbuf->size - sizeof(*htx);
htx_reset(htx);
b_set_data(htxbuf, b_size(htxbuf));
blk = htx_add_blk(htx, HTX_BLK_DATA, ret);
blk->info += ret;
/* nothing else to do, the old buffer now contains an
* empty pre-initialized HTX header
*/
}
else {
ret = htx_add_data(htx, ist2(b_peek(buf, *ofs), try));
}
h1m->curr_len -= ret;
max -= sizeof(struct htx_blk) + ret;
*ofs += ret;
total += ret;
if (ret < try)
goto end;
}
if (!h1m->curr_len) {
if (!h1_process_eom(h1s, h1m, htx, max))
goto end;
}
}
else if (h1m->flags & H1_MF_CHNK) {
new_chunk:
/* te:chunked : parse chunks */
if (h1m->state == H1_MSG_CHUNK_CRLF) {
ret = h1_skip_chunk_crlf(buf, *ofs, b_data(buf));
if (ret <= 0)
goto end;
h1m->state = H1_MSG_CHUNK_SIZE;
*ofs += ret;
total += ret;
}
if (h1m->state == H1_MSG_CHUNK_SIZE) {
unsigned int chksz;
ret = h1_parse_chunk_size(buf, *ofs, b_data(buf), &chksz);
if (ret <= 0)
goto end;
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)
goto end;
}
if (h1m->state == H1_MSG_DATA) {
ret = htx_get_max_blksz(htx, max);
if ((uint64_t)ret > h1m->curr_len)
ret = h1m->curr_len;
if (ret > b_contig_data(buf, *ofs))
ret = b_contig_data(buf, *ofs);
if (ret) {
int32_t try = ret;
ret = htx_add_data(htx, ist2(b_peek(buf, *ofs), try));
h1m->curr_len -= ret;
max -= sizeof(struct htx_blk) + ret;
*ofs += ret;
total += ret;
if (ret < try)
goto end;
}
if (!h1m->curr_len) {
h1m->state = H1_MSG_CHUNK_CRLF;
goto new_chunk;
}
goto end;
}
}
else {
/* XFER_LEN is set but not CLEN nor CHNK, it means there
* is no body. Switch the message in DONE state
*/
if (!h1_process_eom(h1s, h1m, htx, max))
goto end;
}
}
else {
/* no content length, read till SHUTW */
ret = htx_get_max_blksz(htx, max);
if (ret > b_contig_data(buf, *ofs))
ret = b_contig_data(buf, *ofs);
if (ret) {
int32_t try = ret;
ret = htx_add_data(htx, ist2(b_peek(buf, *ofs), try));
*ofs += ret;
total = ret;
if (ret < try)
goto end;
}
}
end:
if (ret < 0) {
h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR);
h1s->cs->flags |= CS_FL_EOI;
htx->flags |= HTX_FL_PARSING_ERROR;
h1m->err_state = h1m->state;
h1m->err_pos = *ofs + max + ret;
h1_capture_bad_message(h1s->h1c, h1s, h1m, buf);
return 0;
}
/* update htx->extra, only when the body length is known */
if (h1m->flags & H1_MF_XFER_LEN)
htx->extra = h1m->curr_len;
return total;
}
/*
* Parse HTTP/1 trailers. It returns the number of bytes parsed if > 0, or 0 if
* it couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR
* flag and filling h1s->err_pos and h1s->err_state fields. This functions is
* responsible to update the parser state <h1m>.
*/
static size_t h1_process_trailers(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
struct buffer *buf, size_t *ofs, size_t max)
{
struct http_hdr hdrs[global.tune.max_http_hdr];
struct h1m tlr_h1m;
int ret = 0;
if (!max || !b_data(buf))
goto end;
/* Realing input buffer if necessary */
if (b_peek(buf, *ofs) > b_tail(buf))
b_slow_realign(buf, trash.area, 0);
tlr_h1m.flags = (H1_MF_NO_PHDR|H1_MF_HDRS_ONLY);
ret = h1_headers_to_hdr_list(b_peek(buf, *ofs), b_tail(buf),
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(buf)))
goto error;
goto end;
}
/* messages trailers fully parsed. */
if (h1_eval_htx_hdrs_size(hdrs) > max) {
if (htx_is_empty(htx))
goto error;
ret = 0;
goto end;
}
if (!htx_add_all_trailers(htx, hdrs))
goto error;
*ofs += ret;
h1s->flags |= H1S_F_HAVE_I_TLR;
end:
return ret;
error:
h1m->err_state = h1m->state;
h1m->err_pos = h1m->next;
h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR);
h1s->cs->flags |= CS_FL_EOI;
htx->flags |= HTX_FL_PARSING_ERROR;
h1_capture_bad_message(h1s->h1c, h1s, h1m, buf);
ret = 0;
goto end;
}
/*
* Process incoming data. It parses data and transfer them from h1c->ibuf into
* <buf>. It returns the number of bytes parsed and transferred if > 0, or 0 if