--- /dev/null
+/*
+ * include/proto/hx.h
+ * This file defines everything related to the internal HTTP messages.
+ *
+ * Copyright (C) 2018 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _PROTO_HTX_H
+#define _PROTO_HTX_H
+
+#include <common/buf.h>
+#include <common/config.h>
+#include <common/standard.h>
+#include <common/http-hdr.h>
+
+#include <types/h1.h>
+#include <types/htx.h>
+
+extern struct htx htx_empty;
+
+struct htx_blk *htx_defrag(struct htx *htx, struct htx_blk *blk);
+struct htx_blk *htx_add_blk(struct htx *htx, enum htx_blk_type type, uint32_t blksz);
+struct htx_blk *htx_remove_blk(struct htx *htx, struct htx_blk *blk);
+
+struct htx_blk *htx_replace_blk_value(struct htx *htx, struct htx_blk *blk,
+ const struct ist old, const struct ist new);
+struct htx_ret htx_xfer_blks(struct htx *dst, struct htx *src, uint32_t count,
+ enum htx_blk_type mark);
+
+struct htx_blk *htx_replace_reqline(struct htx *htx, struct htx_blk *blk,
+ const union h1_sl sl);
+struct htx_blk *htx_replace_resline(struct htx *htx, struct htx_blk *blk,
+ const union h1_sl sl);
+struct htx_blk *htx_replace_header(struct htx *htx, struct htx_blk *blk,
+ const struct ist name, const struct ist value);
+
+struct htx_blk *htx_add_reqline(struct htx *htx, const union h1_sl sl);
+struct htx_blk *htx_add_resline(struct htx *htx, const union h1_sl sl);
+struct htx_blk *htx_add_header(struct htx *htx, const struct ist name, const struct ist value);
+struct htx_blk *htx_add_all_headers(struct htx *htx, const struct http_hdr *hdrs);
+struct htx_blk *htx_add_pseudo_header(struct htx *htx, enum htx_phdr_type phdr, const struct ist value);
+struct htx_blk *htx_add_endof(struct htx *htx, enum htx_blk_type type);
+struct htx_blk *htx_add_data(struct htx *htx, const struct ist data);
+struct htx_blk *htx_add_trailer(struct htx *htx, const struct ist tlr);
+struct htx_blk *htx_add_oob(struct htx *htx, const struct ist oob);
+
+int htx_reqline_to_str(const union htx_sl *sl, struct buffer *chk);
+int htx_stline_to_str(const union htx_sl *sl, struct buffer *chk);
+int htx_hdr_to_str(const struct ist n, const struct ist v, struct buffer *chk);
+int htx_data_to_str(const struct ist data, struct buffer *chk, int chunked);
+int htx_trailer_to_str(const struct ist tlr, struct buffer *chk);
+
+
+/* Returns the array index of a block given its position <pos> */
+static inline uint32_t htx_pos_to_idx(const struct htx *htx, uint32_t pos)
+{
+ return ((htx->size / sizeof(htx->blocks[0])) - pos - 1);
+}
+
+/* Returns the position of the block <blk> */
+static inline uint32_t htx_get_blk_pos(const struct htx *htx, const struct htx_blk *blk)
+{
+ return (htx->blocks + (htx->size / sizeof(htx->blocks[0])) - blk - 1);
+}
+
+/* Returns the block at the position <pos> */
+static inline struct htx_blk *htx_get_blk(const struct htx *htx, uint32_t pos)
+{
+ return ((struct htx_blk *)(htx->blocks) + htx_pos_to_idx(htx, pos));
+}
+
+/* Returns the type of the block <blk> */
+static inline enum htx_blk_type htx_get_blk_type(const struct htx_blk *blk)
+{
+ return (blk->info >> 28);
+}
+
+/* Returns the pseudo-header type of the block <blk>. If it's not a
+ * pseudo-header, HTX_PHDR_UNKNOWN is returned.
+ */
+static inline enum htx_phdr_type htx_get_blk_phdr(const struct htx_blk *blk)
+{
+ enum htx_blk_type type = htx_get_blk_type(blk);
+ enum htx_phdr_type phdr;
+
+ switch (type) {
+ case HTX_BLK_PHDR:
+ phdr = (blk->info & 0xff);
+ return phdr;
+
+ default:
+ /* Not a pseudo-header */
+ return HTX_PHDR_UNKNOWN;
+ }
+}
+
+/* Returns the size of the block <blk>, depending of its type */
+static inline uint32_t htx_get_blksz(const struct htx_blk *blk)
+{
+ enum htx_blk_type type = htx_get_blk_type(blk);
+
+ switch (type) {
+ case HTX_BLK_HDR:
+ /* name.length + value.length */
+ return ((blk->info & 0xff) + ((blk->info >> 8) & 0xfffff));
+ case HTX_BLK_PHDR:
+ /* value.length */
+ return ((blk->info >> 8) & 0xfffff);
+ default:
+ /* value.length */
+ return (blk->info & 0xfffffff);
+ }
+}
+
+/* Returns the position of the oldest entry (head).
+ *
+ * An signed 32-bits integer is returned to handle -1 case. Blocks position are
+ * store on unsigned 32-bits integer, but it is impossible to have so much
+ * blocks to overflow a 32-bits signed integer !
+ */
+static inline int32_t htx_get_head(const struct htx *htx)
+{
+ if (!htx->used)
+ return -1;
+
+ return (((htx->tail + 1U < htx->used) ? htx->wrap : 0) + htx->tail + 1U - htx->used);
+}
+
+/* Returns the oldest HTX block (head) if the HTX message is not
+ * empty. Otherwise it returns NULL.
+*/
+static inline struct htx_blk *htx_get_head_blk(const struct htx *htx)
+{
+ int32_t head = htx_get_head(htx);
+
+ return ((head == -1) ? NULL : htx_get_blk(htx, head));
+}
+
+/* Returns the type of the oldest HTX block (head) if the HTX message is not
+ * empty. Otherwise it returns HTX_BLK_UNUSED.
+ */
+static inline enum htx_blk_type htx_get_head_type(const struct htx *htx)
+{
+ struct htx_blk *blk = htx_get_head_blk(htx);
+
+ return (blk ? htx_get_blk_type(blk) : HTX_BLK_UNUSED);
+}
+
+/* Returns the position of the newest entry (tail).
+ *
+ * An signed 32-bits integer is returned to handle -1 case. Blocks position are
+ * store on unsigned 32-bits integer, but it is impossible to have so much
+ * blocks to overflow a 32-bits signed integer !
+ */
+static inline int32_t htx_get_tail(const struct htx *htx)
+{
+ return (htx->used ? htx->tail : -1);
+}
+
+/* Returns the newest HTX block (tail) if the HTX message is not
+ * empty. Otherwise it returns NULL.
+*/
+static inline struct htx_blk *htx_get_tail_blk(const struct htx *htx)
+{
+ int32_t tail = htx_get_tail(htx);
+
+ return ((tail == -1) ? NULL : htx_get_blk(htx, tail));
+}
+
+/* Returns the type of the newest HTX block (tail) if the HTX message is not
+ * empty. Otherwise it returns HTX_BLK_UNUSED.
+ */
+static inline enum htx_blk_type htx_get_tail_type(const struct htx *htx)
+{
+ struct htx_blk *blk = htx_get_tail_blk(htx);
+
+ return (blk ? htx_get_blk_type(blk) : HTX_BLK_UNUSED);
+}
+
+/* Returns the position of block immediatly before the one pointed by <pos>. If
+ * the message is empty or if <pos> is the position of the head, -1 returned.
+ *
+ * An signed 32-bits integer is returned to handle -1 case. Blocks position are
+ * store on unsigned 32-bits integer, but it is impossible to have so much
+ * blocks to overflow a 32-bits signed integer !
+ */
+static inline int32_t htx_get_prev(const struct htx *htx, uint32_t pos)
+{
+ int32_t head;
+
+ head = htx_get_head(htx);
+ if (head == -1 || pos == head)
+ return -1;
+ if (!pos)
+ return (htx->wrap - 1);
+ return (pos - 1);
+}
+
+/* Returns the position of block immediatly after the one pointed by <pos>. If
+ * the message is empty or if <pos> is the position of the tail, -1 returned.
+ *
+ * An signed 32-bits integer is returned to handle -1 case. Blocks position are
+ * store on unsigned 32-bits integer, but it is impossible to have so much
+ * blocks to overflow a 32-bits signed integer !
+ */
+static inline int32_t htx_get_next(const struct htx *htx, uint32_t pos)
+{
+ if (!htx->used)
+ return -1;
+
+ if (pos == htx->tail)
+ return -1;
+ pos++;
+ if (pos >= htx->wrap)
+ pos = 0;
+ return pos;
+
+}
+static inline int32_t htx_find_front(const struct htx *htx)
+{
+ int32_t front, pos;
+ uint32_t addr = 0;
+
+ if (!htx->used)
+ return -1;
+
+ front = -1;
+ for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
+ struct htx_blk *blk = htx_get_blk(htx, pos);
+ enum htx_blk_type type = htx_get_blk_type(blk);
+
+ if (type != HTX_BLK_UNUSED && blk->addr >= addr) {
+ front = pos;
+ addr = blk->addr;
+ }
+ }
+
+ return front;
+}
+
+/* Changes the size of the value. It is the caller responsibility to change the
+ * value itself, make sure there is enough space and update allocated value.
+ */
+static inline void htx_set_blk_value_len(struct htx_blk *blk, uint32_t vlen)
+{
+ enum htx_blk_type type = htx_get_blk_type(blk);
+
+ switch (type) {
+ case HTX_BLK_HDR:
+ case HTX_BLK_PHDR:
+ blk->info = (type << 28) + (vlen << 8) + (blk->info & 0xff);
+ break;
+ case HTX_BLK_REQ_SL:
+ case HTX_BLK_RES_SL:
+ case HTX_BLK_DATA:
+ case HTX_BLK_TLR:
+ case HTX_BLK_OOB:
+ blk->info = (type << 28) + vlen;
+ break;
+ default:
+ /* Unexpected case */
+ break;
+ }
+}
+
+/* Returns the data pointer of the block <blk> */
+static inline void *htx_get_blk_ptr(const struct htx *htx, const struct htx_blk *blk)
+{
+ return ((void *)htx->blocks + blk->addr);
+}
+
+/* Returns the name of the block <blk>, only if it is a header. Otherwise it
+ * returns an empty name.
+ */
+static inline struct ist htx_get_blk_name(const struct htx *htx, const struct htx_blk *blk)
+{
+ enum htx_blk_type type = htx_get_blk_type(blk);
+ struct ist ret;
+
+ switch (type) {
+ case HTX_BLK_HDR:
+ ret.ptr = htx_get_blk_ptr(htx, blk);
+ ret.len = blk->info & 0xff;
+ break;
+
+ default:
+ return ist("");
+ }
+ return ret;
+}
+
+/* Returns the value of the block <blk>, depending on its type. If there is no
+ * value, an empty one is retruned.
+ */
+static inline struct ist htx_get_blk_value(const struct htx *htx, const struct htx_blk *blk)
+{
+ enum htx_blk_type type = htx_get_blk_type(blk);
+ struct ist ret;
+
+ switch (type) {
+ case HTX_BLK_HDR:
+ ret.ptr = htx_get_blk_ptr(htx, blk) + (blk->info & 0xff);
+ ret.len = (blk->info >> 8) & 0xfffff;
+ break;
+
+ case HTX_BLK_PHDR:
+ ret.ptr = htx_get_blk_ptr(htx, blk);
+ ret.len = (blk->info >> 8) & 0xfffff;
+ break;
+
+ case HTX_BLK_REQ_SL:
+ case HTX_BLK_RES_SL:
+ case HTX_BLK_DATA:
+ case HTX_BLK_TLR:
+ case HTX_BLK_OOB:
+ ret.ptr = htx_get_blk_ptr(htx, blk);
+ ret.len = blk->info & 0xfffffff;
+ break;
+
+ default:
+ return ist("");
+ }
+ return ret;
+}
+
+
+/* Returns the space used by metadata in <htx>. */
+static inline uint32_t htx_meta_space(const struct htx *htx)
+{
+ return (htx->used * sizeof(htx->blocks[0]));
+}
+
+/* Returns the space used (data + metadata) in <htx> */
+static inline uint32_t htx_used_space(const struct htx *htx)
+{
+ return (htx->data + htx_meta_space(htx));
+}
+
+/* Returns the free space in <htx> */
+static inline uint32_t htx_free_space(const struct htx *htx)
+{
+ return (htx->size - htx_used_space(htx));
+}
+
+/* Returns the maximum size available to store some data in <htx> if a new block
+ * is reserved.
+ */
+static inline uint32_t htx_free_data_space(const struct htx *htx)
+{
+ uint32_t free = htx_free_space(htx);
+
+ if (free < sizeof(htx->blocks[0]))
+ return 0;
+ return (free - sizeof(htx->blocks[0]));
+}
+
+/* Returns 1 if the message has less than 1/4 of its capacity free, otherwise 0 */
+static inline int htx_almost_full(const struct htx *htx)
+{
+ if (!htx->size || htx_free_space(htx) < htx->size / 4)
+ return 1;
+ return 0;
+}
+
+static inline void htx_reset(struct htx *htx)
+{
+ htx->data = htx->used = htx->tail = htx->wrap = htx->front = 0;
+ htx->extra = 0;
+ htx->flags = HTX_FL_NONE;
+}
+
+/* Returns an HTX message using the buffer <buf>. */
+static inline struct htx *htx_from_buf(struct buffer *buf)
+{
+ struct htx *htx;
+
+ if (b_is_null(buf))
+ return &htx_empty;
+ htx = (struct htx *)(buf->area);
+ htx->size = buf->size - sizeof(*htx);
+ if (!b_data(buf))
+ htx_reset(htx);
+ return htx;
+}
+
+/* Returns 1 if the message is empty, otherwise it returns 0. */
+static inline int htx_is_empty(const struct htx *htx)
+{
+ return (!htx || !htx->used);
+}
+
+/* Returns 1 if the message is not empty, otherwise it returns 0. */
+static inline int htx_is_not_empty(const struct htx *htx)
+{
+ return (htx && htx->used);
+}
+
+/* For debugging purpose */
+static inline const char *htx_blk_type_str(enum htx_blk_type type)
+{
+ switch (type) {
+ case HTX_BLK_REQ_SL: return "HTX_BLK_REQ_SL";
+ case HTX_BLK_RES_SL: return "HTX_BLK_RES_SL";
+ case HTX_BLK_HDR: return "HTX_BLK_HDR";
+ case HTX_BLK_PHDR: return "HTX_BLK_PHDR";
+ case HTX_BLK_EOH: return "HTX_BLK_EOH";
+ case HTX_BLK_DATA: return "HTX_BLK_DATA";
+ case HTX_BLK_EOD: return "HTX_BLK_EOD";
+ case HTX_BLK_TLR: return "HTX_BLK_TLR";
+ case HTX_BLK_EOM: return "HTX_BLK_EOM";
+ case HTX_BLK_OOB: return "HTX_BLK_OOB";
+ case HTX_BLK_UNUSED: return "HTX_BLK_UNUSED";
+ default: return "HTX_BLK_???";
+ };
+}
+
+static inline const char *htx_blk_phdr_str(enum htx_phdr_type phdr)
+{
+ switch (phdr) {
+ case HTX_PHDR_UNKNOWN: return "HTX_PHDR_UNKNOWN";
+ default: return "HTX_PHDR_???";
+ }
+}
+
+static inline void htx_dump(struct htx *htx)
+{
+ int32_t pos;
+
+ fprintf(stderr, "htx:%p [ size=%u - data=%u - used=%u - wrap=%s - extra=%lu]\n",
+ htx, htx->size, htx->data, htx->used,
+ (!htx->used || htx->tail+1 == htx->wrap) ? "NO" : "YES",
+ htx->extra);
+ fprintf(stderr, "\thead=%d - tail=%u - front=%u - wrap=%u\n",
+ htx_get_head(htx), htx->tail, htx->front, htx->wrap);
+
+ for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
+ union htx_sl *sl;
+ struct htx_blk *blk = htx_get_blk(htx, pos);
+ enum htx_blk_type type = htx_get_blk_type(blk);
+ enum htx_phdr_type phdr = htx_get_blk_phdr(blk);
+ uint32_t sz = htx_get_blksz(blk);
+ struct ist n, v;
+
+ n = htx_get_blk_name(htx, blk);
+ v = htx_get_blk_value(htx, blk);
+
+ if (type == HTX_BLK_REQ_SL) {
+ sl = htx_get_blk_ptr(htx, blk);
+ fprintf(stderr, "\t\t[%u] type=%-17s - size=%-6u - addr=%-6u\t%.*s %.*s %.*s\n",
+ pos, htx_blk_type_str(type), sz, blk->addr,
+ (int)sl->rq.m_len, sl->rq.l,
+ (int)sl->rq.u_len, sl->rq.l + sl->rq.m_len,
+ (int)sl->rq.v_len, sl->rq.l + sl->rq.m_len + sl->rq.u_len);
+ }
+ else if (type == HTX_BLK_RES_SL) {
+ sl = htx_get_blk_ptr(htx, blk);
+ fprintf(stderr, "\t\t[%u] type=%-17s - size=%-6u - addr=%-6u\t%.*s %.*s %.*s\n",
+ pos, htx_blk_type_str(type), sz, blk->addr,
+ (int)sl->st.v_len, sl->st.l,
+ (int)sl->st.c_len, sl->st.l + sl->st.v_len,
+ (int)sl->st.r_len, sl->st.l + sl->rq.v_len + sl->st.c_len);
+ }
+ else if (type == HTX_BLK_HDR)
+ fprintf(stderr, "\t\t[%u] type=%-17s - size=%-6u - addr=%-6u\t%.*s: %.*s\n",
+ pos, htx_blk_type_str(type), sz, blk->addr,
+ (int)n.len, n.ptr,
+ (int)v.len, v.ptr);
+
+ else if (type == HTX_BLK_PHDR)
+ fprintf(stderr, "\t\t[%u] type=%-17s - size=%-6u - addr=%-6u\t%.*s\n",
+ pos, htx_blk_phdr_str(phdr), sz, blk->addr,
+ (int)v.len, v.ptr);
+ else
+ fprintf(stderr, "\t\t[%u] type=%-17s - size=%-6u - addr=%-6u%s\n",
+ pos, htx_blk_type_str(type), sz, blk->addr,
+ (!v.len ? "\t<empty>" : ""));
+ }
+ fprintf(stderr, "\n");
+}
+
+#endif /* _PROTO_HTX_H */
+
+/*
+ * Local variables:
+ * c-indent-level: 8
+ * c-basic-offset: 8
+ * End:
+ */
--- /dev/null
+/*
+ * internal HTTP message
+ *
+ * Copyright 2018 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
+ *
+ * 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 <common/chunk.h>
+#include <proto/htx.h>
+
+struct htx htx_empty = { .size = 0, .data = 0, .used = 0 };
+
+/* Defragments an HTTP message, removing unused blocks and unwrapping blocks and
+ * their contents. A temporary message is used to do so. This function never
+ * fails. if <blk> is not NULL, we replace it by the new block address, after
+ * the defragmentation. The new <blk> is returned.
+ */
+/* TODO: merge data blocks into one */
+struct htx_blk *htx_defrag(struct htx *htx, struct htx_blk *blk)
+{
+ struct buffer *chunk = get_trash_chunk();
+ struct htx *tmp = htx_from_buf(chunk);
+ struct htx_blk *newblk, *oldblk;
+ uint32_t new, old;
+ uint32_t addr, blksz;
+
+ if (!htx->used)
+ return NULL;
+
+ new = 0;
+ addr = 0;
+ tmp->size = htx->size;
+
+ /* start from the head */
+ for (old = htx_get_head(htx); old != -1; old = htx_get_next(htx, old)) {
+ oldblk = htx_get_blk(htx, old);
+ if (htx_get_blk_type(oldblk) == HTX_BLK_UNUSED) {
+ htx->used--;
+ continue;
+ }
+
+ newblk = htx_get_blk(tmp, new);
+ newblk->addr = addr;
+ newblk->info = oldblk->info;
+ blksz = htx_get_blksz(oldblk);
+
+ memcpy((void *)tmp->blocks + addr, htx_get_blk_ptr(htx, oldblk), blksz);
+ new++;
+ addr += blksz;
+
+ /* if <blk> is defined, set its new location */
+ if (blk != NULL && blk == oldblk)
+ blk = newblk;
+ } while (new < htx->used);
+
+ htx->wrap = htx->used;
+ htx->front = htx->tail = new - 1;
+ memcpy((void *)htx->blocks, (void *)tmp->blocks, htx->size);
+
+ return blk;
+}
+
+/* Reserves a new block in the HTTP message <htx> with a content of <blksz>
+ * bytes. If there is not enough space, NULL is returned. Otherwise the reserved
+ * block is returned and the HTTP message is updated. Space for this new block
+ * is reserved in the HTTP message. But it is the caller responsibility to set
+ * right info in the block to reflect the stored data.
+ */
+static struct htx_blk *htx_reserve_nxblk(struct htx *htx, uint32_t blksz)
+{
+ struct htx_blk *blk, *prevblk, *headblk, *frtblk;
+ uint32_t used;
+ uint32_t tail;
+ uint32_t prev;
+ uint32_t wrap;
+ uint32_t head;
+ int32_t headroom, tailroom;
+
+ if (blksz > htx_free_data_space(htx))
+ return NULL; /* full */
+
+ if (!htx->used) {
+ /* Empty message */
+ htx->front = htx->tail = 0;
+ htx->wrap = htx->used = 1;
+ blk = htx_get_blk(htx, htx->tail);
+ blk->addr = 0;
+ htx->data = blksz;
+ return blk;
+ }
+
+ used = htx->used + 1;
+ tail = htx->tail + 1;
+ prev = htx->tail;
+ wrap = htx->wrap;
+ head = htx_get_head(htx);
+
+ if (tail == wrap) {
+ frtblk = htx_get_blk(htx, htx->front);
+
+ /* Blocks don't wrap for now. We either need to push the new one
+ * on top of others or to defragement the table. */
+ if (sizeof(htx->blocks[0]) * htx_pos_to_idx(htx, wrap+1) >= frtblk->addr + htx_get_blksz(frtblk))
+ wrap++;
+ else if (tail >= used) /* There is hole at the beginning */
+ tail = 0;
+ else {
+ /* No more room, tail hits data. We have to realign the
+ * whole message. */
+ goto defrag;
+ }
+ }
+ else if (used >= wrap) {
+ /* We have hit the tail, we need to reorganize the blocks. */
+ goto defrag;
+ }
+
+ /* Now we have updated tail, used and wrap, we know that there is some
+ * available room at least from the protocol's perspective. This space
+ * is split in two areas :
+ *
+ * 1: the space between the beginning of the blocks table and the
+ * front data's address. This space will only be used if data don't
+ * wrap yet.
+
+ * 2: If the previous tail was the front block, the space between the
+ * beginning of the message and the head data's address.
+ * Otherwise, the space between the tail data's address and the
+ * tail's one.
+ */
+ prevblk = htx_get_blk(htx, prev);
+ headblk = htx_get_blk(htx, head);
+ if (prevblk->addr >= headblk->addr) {
+ /* the area was contiguous */
+ frtblk = htx_get_blk(htx, htx->front);
+ tailroom = sizeof(htx->blocks[0]) * htx_pos_to_idx(htx, wrap) - (frtblk->addr + htx_get_blksz(frtblk));
+ headroom = headblk->addr;
+
+ if (tailroom >= (int32_t)blksz) {
+ /* install upfront and update ->front */
+ blk = htx_get_blk(htx, tail);
+ blk->addr = frtblk->addr + htx_get_blksz(frtblk);
+ htx->front = tail;
+ }
+ else if (headroom >= (int32_t)blksz) {
+ blk = htx_get_blk(htx, tail);
+ blk->addr = 0;
+ }
+ else {
+ /* need to defragment the table before inserting upfront */
+ goto defrag;
+ }
+ }
+ else {
+ /* it's already wrapped so we can't store anything in the tailroom */
+ headroom = headblk->addr - (prevblk->addr + htx_get_blksz(prevblk));
+
+ if (headroom >= (int32_t)blksz) {
+ blk = htx_get_blk(htx, tail);
+ blk->addr = prevblk->addr + htx_get_blksz(prevblk);
+ }
+ else {
+ defrag:
+ /* need to defragment the table before inserting upfront */
+ htx_defrag(htx, NULL);
+ frtblk = htx_get_blk(htx, htx->front);
+ wrap = htx->wrap + 1;
+ tail = htx->tail + 1;
+ used = htx->used + 1;
+ blk = htx_get_blk(htx, tail);
+ blk->addr = frtblk->addr + htx_get_blksz(frtblk);
+ htx->front = tail;
+ }
+ }
+
+ htx->wrap = wrap;
+ htx->tail = tail;
+ htx->used = used;
+ htx->data += blksz;
+ return blk;
+}
+
+/* Adds a new block of type <type> in the HTTP message <htx>. Its content size
+ * is passed but it is the caller responsibility to do the copy.
+ */
+struct htx_blk *htx_add_blk(struct htx *htx, enum htx_blk_type type, uint32_t blksz)
+{
+ struct htx_blk *blk;
+
+ blk = htx_reserve_nxblk(htx, blksz);
+ if (!blk)
+ return NULL;
+
+ blk->info = (type << 28);
+ return blk;
+}
+
+/* Removes the block <blk> from the HTTP message <htx>. The function returns the
+ * block following <blk> or NULL if <blk> is the last block or the last
+ * inserted one.
+ */
+struct htx_blk *htx_remove_blk(struct htx *htx, struct htx_blk *blk)
+{
+ uint32_t next, head, pos;
+
+ if (htx_get_blk_type(blk) != HTX_BLK_UNUSED) {
+ /* Mark the block as unused, decrement allocated size */
+ htx->data -= htx_get_blksz(blk);
+ blk->info = ((uint32_t)HTX_BLK_UNUSED << 28);
+ }
+
+ /* This is the last block in use */
+ if (htx->used == 1/* || !htx->data */) {
+ htx->front = htx->tail = 0;
+ htx->wrap = htx->used = 0;
+ htx->data = 0;
+ return NULL;
+ }
+
+ /* There is at least 2 blocks, so tail is always >= 0 */
+ pos = htx_get_blk_pos(htx, blk);
+ head = htx_get_head(htx);
+ blk = NULL;
+ next = pos + 1; /* By default retrun the next block */
+ if (htx->tail + 1 == htx->wrap) {
+ /* The HTTP message doesn't wrap */
+ if (pos == head) {
+ /* remove the head, so just return the new head */
+ htx->used--;
+ next = htx_get_head(htx);
+ }
+ else if (pos == htx->tail) {
+ /* remove the tail. this was the last inserted block so
+ * return NULL. */
+ htx->wrap--;
+ htx->tail--;
+ htx->used--;
+ goto end;
+ }
+ }
+ else {
+ /* The HTTP message wraps */
+ if (pos == htx->tail) {
+ /* remove the tail. try to unwrap the message (pos == 0)
+ * and return NULL. */
+ htx->tail = ((pos == 0) ? htx->wrap-1 : htx->tail-1);
+ htx->used--;
+ goto end;
+ }
+ else if (pos == head) {
+ /* remove the head, try to unwrap the message (pos+1 ==
+ * wrap) and return the new head */
+ htx->used--;
+ if (pos + 1 == htx->wrap)
+ htx->wrap = htx->tail + 1;
+ next = htx_get_head(htx);
+ }
+ }
+
+ blk = htx_get_blk(htx, next);
+ end:
+ if (pos == htx->front)
+ htx->front = htx_find_front(htx);
+ return blk;
+}
+
+/* Tries to append data to the last inserted block, if the type matches and if
+ * there is enough non-wrapping space. Only DATA and TRAILERS content can be
+ * appended. If the append fails, a new block is inserted. If an error occurred,
+ * NULL is returned. Otherwise, on success, the updated block (or the new one)
+ * is returned.
+*/
+static struct htx_blk *htx_append_blk_value(struct htx *htx, enum htx_blk_type type,
+ const struct ist data)
+{
+ struct htx_blk *blk;
+ struct ist v;
+
+ if (!htx->used)
+ goto add_new_block;
+
+ /* Not enough space to store data */
+ if (data.len > htx_free_data_space(htx))
+ return NULL;
+
+ /* Append only DATA et TRAILERS data */
+ if (type != HTX_BLK_DATA && type != HTX_BLK_TLR)
+ goto add_new_block;
+
+ /* get the tail block */
+ blk = htx_get_blk(htx, htx->tail);
+
+ /* Don't try to append data if the last inserted block is not of the
+ * same type */
+ if (type != htx_get_blk_type(blk))
+ goto add_new_block;
+
+ /*
+ * Same type and enough space: append data
+ */
+ if (htx->tail + 1 == htx->wrap) {
+ struct htx_blk *frtblk = htx_get_blk(htx, htx->front);
+ int32_t tailroom = sizeof(htx->blocks[0]) * htx_pos_to_idx(htx, htx->tail) - (frtblk->addr + htx_get_blksz(frtblk));
+ if (tailroom >= (int32_t)data.len)
+ goto append_data;
+ htx_defrag(htx, NULL);
+ blk = htx_get_blk(htx, htx->tail);
+ }
+ else {
+ struct htx_blk *headblk = htx_get_blk(htx, htx_get_head(htx));
+ int32_t headroom = headblk->addr - (blk->addr + htx_get_blksz(blk));
+ if (headroom >= (int32_t)data.len)
+ goto append_data;
+ htx_defrag(htx, NULL);
+ blk = htx_get_blk(htx, htx->tail);
+ }
+
+ append_data:
+ /* get the value of the tail block */
+ /* FIXME: check v.len + data.len < 256MB */
+ v = htx_get_blk_value(htx, blk);
+
+ /* Append data and update the block itself */
+ memcpy(v.ptr + v.len, data.ptr, data.len);
+ htx_set_blk_value_len(blk, v.len + data.len);
+
+ /* Update HTTP message */
+ htx->data += data.len;
+
+ return blk;
+
+ add_new_block:
+ /* FIXME: check tlr.len (< 256MB) */
+ blk = htx_add_blk(htx, type, data.len);
+ if (!blk)
+ return NULL;
+
+ blk->info += data.len;
+ memcpy(htx_get_blk_ptr(htx, blk), data.ptr, data.len);
+ return blk;
+}
+
+/* Replaces a value part of a block by a new one. The new part can be smaller or
+ * larger than the old one. This function works for any kind of block with
+ * attached data. It returns the new block on success, otherwise it returns
+ * NULL.
+ */
+struct htx_blk *htx_replace_blk_value(struct htx *htx, struct htx_blk *blk,
+ const struct ist old, const struct ist new)
+{
+ struct htx_blk *frtblk;
+ struct buffer *tmp;
+ struct ist n, v;
+ uint32_t info, room;
+
+ n = htx_get_blk_name(htx, blk);
+ v = htx_get_blk_value(htx, blk);
+
+ /* easy case, new data are smaller, so replace it in-place */
+ if (new.len <= old.len) {
+ memcpy(old.ptr, new.ptr, new.len);
+ if (old.len != v.len)
+ memmove(old.ptr + new.len, old.ptr + old.len, (v.ptr + v.len) - (old.ptr + old.len));
+ htx_set_blk_value_len(blk, v.len - old.len + new.len);
+ htx->data -= (old.len - new.len);
+ return blk;
+ }
+
+ /* we need to allocate more space to store the new header value */
+ if ((new.len - old.len) > htx_free_space(htx))
+ return NULL; /* not enough space */
+
+ /*
+ * Copy the new header in a temp buffer
+ */
+ tmp = get_trash_chunk();
+
+ /* 1. copy the header name */
+ chunk_memcat(tmp, n.ptr, n.len);
+
+ /* 2. copy value before old part, if any */
+ if (old.ptr != v.ptr)
+ chunk_memcat(tmp, v.ptr, old.ptr - v.ptr);
+
+ /* 3. copy new value */
+ chunk_memcat(tmp, new.ptr, new.len);
+
+ /* 4. copy value after old part if any */
+ if (old.len != v.len)
+ chunk_memcat(tmp, old.ptr + old.len, (v.ptr + v.len) - (old.ptr + old.len));
+
+ /*
+ * temporarely remove space reserved for the header
+ */
+ info = blk->info;
+ blk->info &= 0xf0000000;
+ htx->data -= (n.len + v.len);
+
+ /*
+ * Try to find right addr to copy all the data
+ */
+ if (htx->tail + 1 == htx->wrap) {
+ frtblk = htx_get_blk(htx, htx->front);
+ room = sizeof(htx->blocks[0]) * htx_pos_to_idx(htx, htx->tail) - (frtblk->addr + htx_get_blksz(frtblk));
+ if (room >= htx->data) {
+ blk->addr = frtblk->addr + htx_get_blksz(frtblk);
+ goto replace_value;
+ }
+ }
+
+ /* HTX message need to be defragmented first */
+ blk = htx_defrag(htx, blk);
+ frtblk = htx_get_blk(htx, htx->front);
+ blk->addr = frtblk->addr + htx_get_blksz(frtblk);
+
+ replace_value:
+ blk->info = info;
+ htx_set_blk_value_len(blk, v.len - old.len + new.len);
+ memcpy(htx_get_blk_ptr(htx, blk), tmp->area, tmp->data);
+ htx->data += tmp->data;
+ htx->front = htx_get_blk_pos(htx, blk);
+
+ return blk;
+}
+
+/* Transfer HTX blocks from <src> to <dst>, stopping on the first block of the
+ * type <mark> (typically EOH, EOD or EOM) or when <count> bytes of data were
+ * moved. It returns the number of bytes of data moved and the last HTX block
+ * inserted in <dst>.
+ */
+struct htx_ret htx_xfer_blks(struct htx *dst, struct htx *src, uint32_t count,
+ enum htx_blk_type mark)
+{
+ struct htx_blk *blk, *dstblk;
+ enum htx_blk_type type;
+ uint32_t info, max, sz, ret;
+
+ ret = 0;
+ blk = htx_get_blk(src, htx_get_head(src));
+ dstblk = NULL;
+ while (blk && ret <= count) {
+ type = htx_get_blk_type(blk);
+
+ /* Ingore unused block */
+ if (type == HTX_BLK_UNUSED)
+ goto next;
+
+ sz = htx_get_blksz(blk);
+ if (!sz) {
+ dstblk = htx_reserve_nxblk(dst, 0);
+ if (!dstblk)
+ break;
+ dstblk->info = blk->info;
+ goto next;
+ }
+
+ info = blk->info;
+ max = htx_free_data_space(dst);
+ if (max > count)
+ max = count;
+ if (sz > max) {
+ sz = max;
+ info = (type << 28) + sz;
+ /* Headers and pseudo headers must be fully copied */
+ if (type < HTX_BLK_DATA || !sz)
+ break;
+ }
+
+ dstblk = htx_reserve_nxblk(dst, sz);
+ if (!dstblk)
+ break;
+ dstblk->info = info;
+ memcpy(htx_get_blk_ptr(dst, dstblk), htx_get_blk_ptr(src, blk), sz);
+
+ ret += sz;
+ if (blk->info != info) {
+ /* Partial move: don't remove <blk> from <src> but
+ * resize its content */
+ blk->addr += sz;
+ htx_set_blk_value_len(blk, htx_get_blksz(blk) - sz);
+ src->data -= sz;
+ break;
+ }
+
+ next:
+ blk = htx_remove_blk(src, blk);
+ if (type == mark)
+ break;
+
+ }
+
+ return (struct htx_ret){.ret = ret, .blk = dstblk};
+}
+
+static struct htx_blk *htx_new_blk_value(struct htx *htx, struct htx_blk *blk,
+ uint32_t newsz)
+{
+ struct htx_blk *frtblk;
+ uint32_t sz, room;
+ int32_t delta;
+
+ sz = htx_get_blksz(blk);
+ delta = newsz - sz;
+
+ /* easy case, new value is smaller, so replace it in-place */
+ if (delta <= 0) {
+ /* Reset value size. It is the caller responsibility to set the new one */
+ blk->info &= 0xf0000000;
+ htx->data += delta;
+ return blk;
+ }
+
+ /* we need to allocate more space to store the new value */
+ if (delta > htx_free_space(htx))
+ return NULL; /* not enough space */
+
+ /*
+ * temporarely remove space reserved for the old value
+ */
+ /* Reset value size. It is the caller responsibility to set the new one */
+ blk->info &= 0xf0000000;
+ htx->data -= sz;
+
+ /*
+ * Try to find right addr to copy all the data
+ */
+ if (htx->tail + 1 == htx->wrap) {
+ frtblk = htx_get_blk(htx, htx->front);
+ room = sizeof(htx->blocks[0]) * htx_pos_to_idx(htx, htx->tail) - (frtblk->addr + htx_get_blksz(frtblk));
+ if (room >= newsz)
+ goto replace_value;
+ }
+
+ /* HTX message need to be defragmented first */
+ blk = htx_defrag(htx, blk);
+ frtblk = htx_get_blk(htx, htx->front);
+
+ replace_value:
+ blk->addr = frtblk->addr + htx_get_blksz(frtblk);
+ htx->data += newsz;
+ htx->front = htx_get_blk_pos(htx, blk);
+
+ return blk;
+}
+
+/* Replaces an header by a new one. The new header can be smaller or larger than
+ * the old one. It returns the new block on success, otherwise it returns NULL.
+ */
+struct htx_blk *htx_replace_header(struct htx *htx, struct htx_blk *blk,
+ const struct ist name, const struct ist value)
+{
+ enum htx_blk_type type;
+
+ type = htx_get_blk_type(blk);
+ if (type != HTX_BLK_HDR)
+ return NULL;
+
+ blk = htx_new_blk_value(htx, blk, (name.len + value.len));
+ if (!blk)
+ return NULL;
+
+ blk->info = (type << 28) + (value.len << 8) + name.len;
+ memcpy(htx_get_blk_ptr(htx, blk), name.ptr, name.len);
+ memcpy(htx_get_blk_ptr(htx, blk) + name.len, value.ptr, value.len);
+
+ return blk;
+}
+
+static void htx_set_blk_reqline(struct htx *htx, struct htx_blk *blk, const union h1_sl sl)
+{
+ union htx_sl *htx_sl;
+
+ htx_sl = htx_get_blk_ptr(htx, blk);
+ htx_sl->rq.meth = sl.rq.meth;
+
+ htx_sl->rq.m_len = sl.rq.m.len;
+ htx_sl->rq.u_len = sl.rq.u.len;
+ htx_sl->rq.v_len = sl.rq.v.len;
+
+ memcpy(htx_sl->rq.l, sl.rq.m.ptr, sl.rq.m.len);
+ memcpy(htx_sl->rq.l + sl.rq.m.len, sl.rq.u.ptr, sl.rq.u.len);
+ memcpy(htx_sl->rq.l + sl.rq.m.len + sl.rq.u.len, sl.rq.v.ptr, sl.rq.v.len);
+}
+
+
+static void htx_set_blk_resline(struct htx *htx, struct htx_blk *blk, const union h1_sl sl)
+{
+ union htx_sl *htx_sl;
+
+ htx_sl = htx_get_blk_ptr(htx, blk);
+ htx_sl->st.status = sl.st.status;
+
+ htx_sl->st.v_len = sl.st.v.len;
+ htx_sl->st.c_len = sl.st.c.len;
+ htx_sl->st.r_len = sl.st.r.len;
+
+ memcpy(htx_sl->st.l, sl.st.v.ptr, sl.st.v.len);
+ memcpy(htx_sl->st.l + sl.st.v.len, sl.st.c.ptr, sl.st.c.len);
+ memcpy(htx_sl->st.l + sl.st.v.len + sl.st.c.len, sl.st.r.ptr, sl.st.r.len);
+}
+
+/* Replaces the request start line a new one. It returns the new block on
+ * success, otherwise it returns NULL.
+ */
+struct htx_blk *htx_replace_reqline(struct htx *htx, struct htx_blk *blk,
+ const union h1_sl sl)
+{
+ enum htx_blk_type type;
+ uint32_t size;
+
+ type = htx_get_blk_type(blk);
+ if (type != HTX_BLK_REQ_SL)
+ return NULL;
+
+ size = sizeof(sl) + sl.rq.m.len + sl.rq.u.len + sl.rq.v.len;
+ blk = htx_new_blk_value(htx, blk, size);
+ if (!blk)
+ return NULL;
+
+ blk->info = (type << 28) + size;
+ htx_set_blk_reqline(htx, blk, sl);
+ return blk;
+}
+
+/* Replaces the response start line a new one. It returns the new block on
+ * success, otherwise it returns NULL.
+ */
+struct htx_blk *htx_replace_resline(struct htx *htx, struct htx_blk *blk,
+ const union h1_sl sl)
+{
+ enum htx_blk_type type;
+ uint32_t size;
+
+ type = htx_get_blk_type(blk);
+ if (type != HTX_BLK_RES_SL)
+ return NULL;
+
+ size = sizeof(sl) + sl.rq.m.len + sl.rq.u.len + sl.rq.v.len;
+ blk = htx_new_blk_value(htx, blk, size);
+ if (!blk)
+ return NULL;
+
+ blk->info = (type << 28) + size;
+ htx_set_blk_resline(htx, blk, sl);
+ return blk;
+}
+
+
+/* Adds an HTX block of type SL in <htx>. It returns the new block on
+ * success. Otherwise, it returns NULL.
+ */
+struct htx_blk *htx_add_reqline(struct htx *htx, const union h1_sl sl)
+{
+ struct htx_blk *blk;
+ uint32_t size;
+
+ size = sizeof(sl) + sl.rq.m.len + sl.rq.u.len + sl.rq.v.len;
+
+ /* FIXME: check size (< 256MB) */
+ blk = htx_add_blk(htx, HTX_BLK_REQ_SL, size);
+ if (!blk)
+ return NULL;
+
+ blk->info += size;
+ htx_set_blk_reqline(htx, blk, sl);
+ return blk;
+}
+
+/* Adds an HTX block of type SL in <htx>. It returns the new block on
+ * success. Otherwise, it returns NULL.
+ */
+struct htx_blk *htx_add_resline(struct htx *htx, const union h1_sl sl)
+{
+ struct htx_blk *blk;
+ uint32_t size;
+
+ size = sizeof(sl) + sl.st.v.len + sl.st.c.len + sl.st.r.len;
+
+ /* FIXME: check size (< 256MB) */
+ blk = htx_add_blk(htx, HTX_BLK_RES_SL, size);
+ if (!blk)
+ return NULL;
+
+ blk->info += size;
+ htx_set_blk_resline(htx, blk, sl);
+ return blk;
+}
+
+/* Adds an HTX block of type HDR in <htx>. It returns the new block on
+ * success. Otherwise, it returns NULL.
+ */
+struct htx_blk *htx_add_header(struct htx *htx, const struct ist name,
+ const struct ist value)
+{
+ struct htx_blk *blk;
+
+ /* FIXME: check name.len (< 256B) and value.len (< 1MB) */
+ blk = htx_add_blk(htx, HTX_BLK_HDR, name.len + value.len);
+ if (!blk)
+ return NULL;
+
+ blk->info += (value.len << 8) + name.len;
+ memcpy(htx_get_blk_ptr(htx, blk), name.ptr, name.len);
+ memcpy(htx_get_blk_ptr(htx, blk) + name.len, value.ptr, value.len);
+ return blk;
+}
+
+struct htx_blk *htx_add_all_headers(struct htx *htx, const struct http_hdr *hdrs)
+{
+ int i;
+
+ for (i = 0; hdrs[i].n.len; i++) {
+ if (!htx_add_header(htx, hdrs[i].n, hdrs[i].v))
+ return NULL;
+ }
+ return htx_add_endof(htx, HTX_BLK_EOH);
+}
+/* Adds an HTX block of type PHDR in <htx>. It returns the new block on
+ * success. Otherwise, it returns NULL.
+ */
+struct htx_blk *htx_add_pseudo_header(struct htx *htx, enum htx_phdr_type phdr,
+ const struct ist value)
+{
+ struct htx_blk *blk;
+
+ /* FIXME: check value.len ( < 1MB) */
+ blk = htx_add_blk(htx, HTX_BLK_PHDR, value.len);
+ if (!blk)
+ return NULL;
+
+ blk->info += (value.len << 8) + phdr;
+ memcpy(htx_get_blk_ptr(htx, blk), value.ptr, value.len);
+ return blk;
+}
+
+/* Adds an HTX block of type EOH,EOD or EOM in <htx>. It returns the new block
+ * on success. Otherwise, it returns NULL.
+ */
+struct htx_blk *htx_add_endof(struct htx *htx, enum htx_blk_type type)
+{
+ struct htx_blk *blk;
+
+ blk = htx_add_blk(htx, type, 1);
+ if (!blk)
+ return NULL;
+
+ blk->info += 1;
+ return blk;
+}
+
+
+/* Adds an HTX block of type DATA in <htx>. It first tries to append data if
+ * possible. It returns the new block on success. Otherwise, it returns NULL.
+ */
+struct htx_blk *htx_add_data(struct htx *htx, const struct ist data)
+{
+ return htx_append_blk_value(htx, HTX_BLK_DATA, data);
+}
+
+/* Adds an HTX block of type TLR in <htx>. It first tries to append trailers
+ * data if possible. It returns the new block on success. Otherwise, it returns
+ * NULL.
+ */
+struct htx_blk *htx_add_trailer(struct htx *htx, const struct ist tlr)
+{
+ return htx_append_blk_value(htx, HTX_BLK_TLR, tlr);
+}
+
+/* Adds an HTX block of type OOB in <htx>. It returns the new block on
+ * success. Otherwise, it returns NULL.
+ */
+struct htx_blk *htx_add_oob(struct htx *htx, const struct ist oob)
+{
+ struct htx_blk *blk;
+
+ /* FIXME: check oob.len (< 256MB) */
+ blk = htx_add_blk(htx, HTX_BLK_OOB, oob.len);
+ if (!blk)
+ return NULL;
+
+ blk->info += oob.len;
+ memcpy(htx_get_blk_ptr(htx, blk), oob.ptr, oob.len);
+ return blk;
+}
+
+
+/* Appends the string representation of the request line block <blk> to the
+ * chunk <chk>. It returns 1 if data are successfully appended, otherwise it
+ * returns 0.
+ */
+int htx_reqline_to_str(const union htx_sl *sl, struct buffer *chk)
+{
+ if (sl->rq.m_len + sl->rq.u_len + sl->rq.v_len + 4 > b_room(chk))
+ return 0;
+
+ chunk_memcat(chk, sl->rq.l, sl->rq.m_len);
+ chunk_memcat(chk, " ", 1);
+ chunk_memcat(chk, sl->rq.l + sl->rq.m_len, sl->rq.u_len);
+ chunk_memcat(chk, " ", 1);
+ chunk_memcat(chk, sl->rq.l + sl->rq.m_len + sl->rq.u_len, sl->rq.v_len);
+ chunk_memcat(chk, "\r\n", 2);
+
+ return 1;
+}
+
+/* Appends the string representation of the status line block <blk> to the chunk
+ * <chk>. It returns 1 if data are successfully appended, otherwise it
+ * returns 0.
+ */
+int htx_stline_to_str(const union htx_sl *sl, struct buffer *chk)
+{
+ if (sl->st.v_len + sl->st.c_len + sl->st.r_len + 4 > b_size(chk))
+ return 0;
+
+ chunk_memcat(chk, sl->st.l, sl->st.v_len);
+ chunk_memcat(chk, " ", 1);
+ chunk_memcat(chk, sl->st.l + sl->st.v_len, sl->st.c_len);
+ chunk_memcat(chk, " ", 1);
+ chunk_memcat(chk, sl->st.l + sl->st.v_len + sl->st.c_len, sl->st.r_len);
+ chunk_memcat(chk, "\r\n", 2);
+
+ return 1;
+}
+
+/* Appends the string representation of the header block <blk> to the chunk
+ * <chk>. It returns 1 if data are successfully appended, otherwise it returns
+ * 0.
+ */
+int htx_hdr_to_str(const struct ist n, const struct ist v, struct buffer *chk)
+{
+ if (n.len + v.len + 4 > b_room(chk))
+ return 0;
+
+ chunk_memcat(chk, n.ptr, n.len);
+ chunk_memcat(chk, ": ", 2);
+ chunk_memcat(chk, v.ptr, v.len);
+ chunk_memcat(chk, "\r\n", 2);
+
+ return 1;
+}
+
+/* Appends the string representation of the data block <blk> to the chunk
+ * <chk>. If <chunked> is non-zero, it emits HTTP/1 chunk-encoded data. It
+ * returns 1 if data are successfully appended, otherwise it returns 0.
+ */
+int htx_data_to_str(const struct ist data, struct buffer *chk, int chunked)
+{
+ 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 (data.len + (end - beg) + 2 > b_room(chk))
+ return 0;
+ chunk_memcat(chk, beg, end - beg);
+ chunk_memcat(chk, data.ptr, data.len);
+ chunk_memcat(chk, "\r\n", 2);
+ }
+ else {
+ if (!chunk_memcat(chk, data.ptr, data.len))
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Appends the string representation of the trailer block <blk> to the chunk
+ * <chk>. It returns 1 if data are successfully appended, otherwise it returns
+ * 0.
+ */
+int htx_trailer_to_str(const struct ist tlr, struct buffer *chk)
+{
+ /* FIXME: be sure the CRLF is here or remove it when inserted */
+ if (!chunk_memcat(chk, tlr.ptr, tlr.len))
+ return 0;
+ return 1;
+}