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.
This commit is contained in:
Christopher Faulet 2026-03-16 08:50:42 +01:00
parent 41c89e4fb6
commit 5ead611cc2
3 changed files with 166 additions and 4 deletions

View File

@ -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

View File

@ -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.
*/

144
src/htx.c
View File

@ -719,10 +719,154 @@ struct htx_blk *htx_replace_blk_value(struct htx *htx, struct htx_blk *blk,
return blk;
}
/* Transfer HTX blocks from <src> to <dst>, stopping if <count> bytes were
* transferred (including payload and meta-data). It returns the number of bytes
* copied. By default, copied blocks are removed from <src> and only full
* headers and trailers part can be moved. <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
*/
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, <blk> point on the first not fully copied block in
* <src>. And <last_dstblk>, if defined, is the last not fully copied
* block in <dst>. So have:
* - <blk> == NULL: everything was copied. <last_dstblk> must be NULL
* - <blk> != NULL && <last_dstblk> == NULL: partial copy but the last block was fully copied
* - <blk> != NULL && <last_dstblk> != 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) {
/* <src > cannot have partial headers or trailers part */
BUG_ON(blk == NULL);
/* Remove partial headers/trailers from <dst> and rollback on <str> 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 <dst> 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 <src> */
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 <src> to <dst>, stopping once the first block of the
* type <mark> is transferred (typically EOH or EOT) or when <count> bytes were
* moved (including payload and meta-data). It returns the number of bytes moved
* and the last HTX block inserted in <dst>.
*
* DEPRECATED
*/
struct htx_ret htx_xfer_blks(struct htx *dst, struct htx *src, uint32_t count,
enum htx_blk_type mark)