| /* |
| * 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("Found"); |
| } |
| } |
| 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) |
| 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: |
| */ |