From: Christopher Faulet Date: Mon, 16 Mar 2026 07:50:42 +0000 (+0100) Subject: MEDIUM: htx: Add htx_xfer function to replace htx_xfer_blks X-Git-Tag: v3.4-dev8~132 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5ead611cc2bf9cc851dd0264c346c261b1af7d06;p=thirdparty%2Fhaproxy.git MEDIUM: htx: Add htx_xfer function to replace htx_xfer_blks htx_xfer() function should replace htx_xfer_blks(). It will be a bit easier to maintain and to use. The behavior of htx_xfer() can be changed by calling it with specific flags: * HTX_XFER_KEEP_SRC_BLKS: Blocks from the source message are just copied * HTX_XFER_PARTIAL_HDRS_COPY: It is allowed to partially xfer headers or trailers * HTX_XFER_HDRS_ONLY: only headers are xferred By default (HTX_XFER_DEFAULT or 0), all blocks from the source message are moved into to the destination mesage. So copied in the destination messageand removed from the source message. The caller must still define the maximum amount of data (including meta-data) that can be xferred. It is no longer necessary to specify a block type to stop the copy. Most of time, with htx_xfer_blks(), this parameter was set to HTX_BLK_UNUSED. And otherwise it was only specified to transfer headers. It is important to not that the caller is responsible to verify the original HTX message is well-formated. Especially, it must be sure headers part and trailers part are complete (finished by EOH/EOT block). For now, htx_xfer_blks() is not removed for compatiblity reason. But it is deprecated. --- 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)