]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: htx: Add htx_xfer function to replace htx_xfer_blks
authorChristopher Faulet <cfaulet@haproxy.com>
Mon, 16 Mar 2026 07:50:42 +0000 (08:50 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 23 Mar 2026 13:02:42 +0000 (14:02 +0100)
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.

doc/internals/api/htx-api.txt
include/haproxy/htx.h
src/htx.c

index 93142018e9d1e0f1c27ef6dc3edaef677ca433be..32a3a8a72985f24c3cf12589caeb2d393742e0ec 100644 (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
index aad49c2dad95f86ce6ce93ddbc36331c62f86bf2..e22b2c0732656f935e36db0e815efc535a92de2a 100644 (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.
  */
index cb8fd3770871c962da2a21133b898ab425ac00ae..695a69aace15063b00710b3cf4257758c31321aa 100644 (file)
--- 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 <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)