diff --git a/doc/internals/api/htx-api.txt b/doc/internals/api/htx-api.txt index 93142018e..32a3a8a72 100644 --- a/doc/internals/api/htx-api.txt +++ b/doc/internals/api/htx-api.txt @@ -539,10 +539,22 @@ message. These functions are used by HTX analyzers or by multiplexers. with the first block not removed, or NULL if everything was removed, and the amount of data drained. - - htx_xfer_blks() transfers HTX blocks from an HTX message to another, - stopping after the first block of a specified type is transferred or when - a specific amount of bytes, including meta-data, was moved. If the tail - block is a DATA block, it may be partially moved. All other block are + - htx_xfer() transfers HTX blocks from an HTX message to another, stopping + when a specific amount of bytes, including meta-data, was copied. If the + tail block is a DATA block, it may be partially copied. All other block + are transferred at once. By default, copied blocks are removed from the + original HTX message and headers and trailers parts cannot be partially + copied. But flags can be set to change the default behavior: + + - HTX_XFER_KEEP_SRC_BLKS: source blocks are not removed + - HTX_XFER_PARTIAL_HDRS_COPY: partial headers and trailers + part can be xferred + - HTX_XFER_HDRS_ONLY: Only the headers part is xferred + + - htx_xfer_blks() [DEPRECATED] transfers HTX blocks from an HTX message to + another, stopping after the first block of a specified type is transferred + or when a specific amount of bytes, including meta-data, was moved. If the + tail block is a DATA block, it may be partially moved. All other block are transferred at once or kept. This function returns a mixed value, with the last block moved, or NULL if nothing was moved, and the amount of data transferred. When HEADERS or TRAILERS blocks must be transferred, this diff --git a/include/haproxy/htx.h b/include/haproxy/htx.h index aad49c2da..e22b2c073 100644 --- a/include/haproxy/htx.h +++ b/include/haproxy/htx.h @@ -58,6 +58,12 @@ struct htx_blk *htx_add_last_data(struct htx *htx, struct ist data); void htx_move_blk_before(struct htx *htx, struct htx_blk **blk, struct htx_blk **ref); int htx_append_msg(struct htx *dst, const struct htx *src); +#define HTX_XFER_DEFAULT 0x00000000 /* Default XFER: no partial xfer / remove blocks from source */ +#define HTX_XFER_KEEP_SRC_BLKS 0x00000001 /* Don't remove xfer blocks from source messages during xfer */ +#define HTX_XFER_PARTIAL_HDRS_COPY 0x00000002 /* Allow partial copy of headers and trailers part */ +#define HTX_XFER_HDRS_ONLY 0x00000003 /* Only Transfert header blocks (start-line, header and EOH) */ +size_t htx_xfer(struct htx *dst, struct htx *src, size_t count, unsigned int flags); + /* Functions and macros to get parts of the start-line or length of these * parts. Request and response start-lines are both composed of 3 parts. */ diff --git a/src/htx.c b/src/htx.c index cb8fd3770..695a69aac 100644 --- a/src/htx.c +++ b/src/htx.c @@ -719,10 +719,154 @@ struct htx_blk *htx_replace_blk_value(struct htx *htx, struct htx_blk *blk, return blk; } +/* Transfer HTX blocks from to , stopping if bytes were + * transferred (including payload and meta-data). It returns the number of bytes + * copied. By default, copied blocks are removed from and only full + * headers and trailers part can be moved. can be set to change the + * default behavior: + * - HTX_XFER_KEEP_SRC_BLKS: source blocks are not removed + * - HTX_XFER_PARTIAL_HDRS_COPY: partial headers and trailers part can be xferred + * - HTX_XFER_HDRS_ONLY: Only the headers part is xferred + */ +size_t htx_xfer(struct htx *dst, struct htx *src, size_t count, unsigned int flags) +{ + struct htx_blk *blk, *last_dstblk; + size_t ret = 0; + int dst_full = 0; + + last_dstblk = NULL; + for (blk = htx_get_head_blk(src); blk && count; blk = htx_get_next_blk(src, blk)) { + struct ist v; + enum htx_blk_type type; + uint32_t sz; + + /* Ignore unused block */ + type = htx_get_blk_type(blk); + if (type == HTX_BLK_UNUSED) + continue; + + if ((flags & HTX_XFER_HDRS_ONLY) && + type != HTX_BLK_REQ_SL && type != HTX_BLK_RES_SL && + type != HTX_BLK_HDR && type != HTX_BLK_EOH) + break; + + sz = htx_get_blksz(blk); + switch (type) { + case HTX_BLK_DATA: + v = htx_get_blk_value(src, blk); + if (v.len > count) + v.len = count; + v.len = htx_add_data(dst, v); + if (!v.len) { + dst_full = 1; + goto stop; + } + last_dstblk = htx_get_tail_blk(dst); + count -= sizeof(*blk) + v.len; + ret += sizeof(*blk) + v.len; + if (v.len != sz) { + dst_full = 1; + goto stop; + } + break; + + default: + if (sz > count) { + dst_full = 1; + goto stop; + } + + last_dstblk = htx_add_blk(dst, type, sz); + if (!last_dstblk) { + dst_full = 1; + goto stop; + } + last_dstblk->info = blk->info; + htx_memcpy(htx_get_blk_ptr(dst, last_dstblk), htx_get_blk_ptr(src, blk), sz); + count -= sizeof(*blk) + sz; + ret += sizeof(*blk) + sz; + break; + } + + last_dstblk = NULL; /* Reset last_dstblk because it was fully copied */ + } + stop: + /* Here, if not NULL, point on the first not fully copied block in + * . And , if defined, is the last not fully copied + * block in . So have: + * - == NULL: everything was copied. must be NULL + * - != NULL && == NULL: partial copy but the last block was fully copied + * - != NULL && != NULL: partial copy and the last block was patially copied (DATA block only) + */ + if (!(flags & HTX_XFER_PARTIAL_HDRS_COPY)) { + /* Partial headers/trailers copy is not supported */ + struct htx_blk *dstblk; + enum htx_blk_type type = HTX_BLK_UNUSED; + + dstblk = htx_get_tail_blk(dst); + if (dstblk) + type = htx_get_blk_type(dstblk); + + /* the last copied block is a start-line, a header or a trailer */ + if (type == HTX_BLK_REQ_SL || type == HTX_BLK_RES_SL || type == HTX_BLK_HDR || type == HTX_BLK_TLR) { + /* cannot have partial headers or trailers part */ + BUG_ON(blk == NULL); + + /* Remove partial headers/trailers from and rollback on to not remove them later */ + while (type == HTX_BLK_REQ_SL || type == HTX_BLK_RES_SL || type == HTX_BLK_HDR || type == HTX_BLK_TLR) { + BUG_ON(type != htx_get_blk_type(blk)); + ret -= sizeof(*blk) + htx_get_blksz(blk); + htx_remove_blk(dst, dstblk); + dstblk = htx_get_tail_blk(dst); + blk = htx_get_prev_blk(src, blk); + if (!dstblk) + break; + type = htx_get_blk_type(dstblk); + } + + /* Report if the xfer was interrupted because was + * full but is was originally empty + */ + if (dst_full && htx_is_empty(dst)) + src->flags |= HTX_FL_PARSING_ERROR; + } + } + + if (!(flags & HTX_XFER_KEEP_SRC_BLKS)) { + /* True xfer performed, remove copied block from */ + struct htx_blk *blk2; + + /* Remove all fully copied blocks */ + if (!blk) + htx_drain(src, src->data); + else { + for (blk2 = htx_get_head_blk(src); blk2 && blk2 != blk; blk2 = htx_remove_blk(src, blk2)); + + /* If copy was stopped on a DATA block and the last destination + * block is not NULL, it means a partial copy was performed. So + * cut the source block accordingly + */ + if (last_dstblk && blk2 && htx_get_blk_type(blk2) == HTX_BLK_DATA) { + htx_cut_data_blk(src, blk2, htx_get_blksz(last_dstblk)); + } + } + } + + /* Everything was copied, transfert terminal HTX flags too */ + if (!blk) { + dst->flags |= (src->flags & (HTX_FL_EOM|HTX_FL_PARSING_ERROR|HTX_FL_PROCESSING_ERROR)); + src->flags = 0; + } + + return ret; +} + /* Transfer HTX blocks from to , stopping once the first block of the * type is transferred (typically EOH or EOT) or when bytes were * moved (including payload and meta-data). It returns the number of bytes moved * and the last HTX block inserted in . + * + * DEPRECATED */ struct htx_ret htx_xfer_blks(struct htx *dst, struct htx *src, uint32_t count, enum htx_blk_type mark)