/* * Functions to manipulate H1 messages using the internal representation. * * Copyright (C) 2019 HAProxy Technologies, Christopher Faulet * * 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 #include #include #include #include #include #include #include #include /* 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 = 0; /* 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; } else if (h1sl->rq.meth == HTTP_METH_HEAD) flags |= HTX_SL_F_BODYLESS_RESP; 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 = 0; 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; flags |= HTX_SL_F_BODYLESS_RESP; } 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_XFER_LEN; h1m->curr_len = h1m->body_len = 0; flags |= HTX_SL_F_BODYLESS_RESP; } 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 and the start-line if not NULL. For the requests, * must always be provided. For responses, may be NULL and 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 into an DATA block in . 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 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 * - 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 * with as max. is updated accordingly * * * Second block is the remaining (count - block1) if 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 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 is the beginning of * the block, it means it is a new block. We can remove the block size * from . 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 (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, especially LF alone */ 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 */ if (unlikely(end[ridx] != '\r')) { h1m->state = H1_MSG_CHUNK_CRLF; goto parsing_error; } ++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 . */ 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 . */ 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); tlr_h1m.err_pos = h1m->err_pos; 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 to the chunk . 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 to the chunk . 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 with the value to the * chunk . 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 to the chunk . If * 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: */