diff --git a/Makefile b/Makefile index 3bec31ef4..33f70e96d 100644 --- a/Makefile +++ b/Makefile @@ -924,7 +924,7 @@ OBJS = src/proto_http.o src/cfgparse.o src/server.o src/stream.o \ src/mailers.o src/h2.o src/base64.o src/hash.o src/http.o \ src/http_acl.o src/http_fetch.o src/http_conv.o src/http_act.o \ src/http_rules.o src/proto_sockpair.o src/proto_htx.o \ - src/mux_h1.o + src/mux_h1.o src/htx.o EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o $(EBTREE_DIR)/eb32sctree.o \ $(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \ diff --git a/include/proto/htx.h b/include/proto/htx.h new file mode 100644 index 000000000..91a2086a3 --- /dev/null +++ b/include/proto/htx.h @@ -0,0 +1,502 @@ +/* + * include/proto/hx.h + * This file defines everything related to the internal HTTP messages. + * + * Copyright (C) 2018 HAProxy Technologies, Christopher Faulet + * + * 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 +#include +#include +#include + +#include +#include + +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 */ +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 */ +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 */ +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 */ +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 . 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 , 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 . If + * the message is empty or if 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 . If + * the message is empty or if 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 */ +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 , 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 , 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 . */ +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 */ +static inline uint32_t htx_used_space(const struct htx *htx) +{ + return (htx->data + htx_meta_space(htx)); +} + +/* Returns the free space in */ +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 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 . */ +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" : "")); + } + fprintf(stderr, "\n"); +} + +#endif /* _PROTO_HTX_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/htx.h b/include/types/htx.h new file mode 100644 index 000000000..8f3280fa0 --- /dev/null +++ b/include/types/htx.h @@ -0,0 +1,156 @@ +/* + * include/types/htx.h + * This file contains the internal HTTP definitions. + * + * Copyright (C) 2018 HAProxy Technologies, Christopher Faulet + * + * 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 _TYPES_HTX_H +#define _TYPES_HTX_H + +#include +#include + +/* + * The internal representation of an HTTP message is a contiguous array + * containing both the blocks (htx_blk) and their contents. Blocks are stored + * starting from the end of the array while their contents are stored at the + * beginning. + * + * As data are sent to the peer, blocks and contents are released at the + * edges. This free space is reused when no more space left. So blocks and + * contents may wrap, not necessarily the same time. + * + * An HTTP block is as well a header as a body part or a trailer part. For all + * these types of block, a content is attached to the block. It can also be a + * mark, like the end-of-headers or end-of-message. For these blocks, there is + * no content but it count for a byte. It is important to not skip it when data + * are forwarded. An HTTP block is composed of 2 fields: + * + * - .info : It a 32 bits field containing the block's type on 4 bits + * followed by content' length. See below for details. + * + * - .addr : The content's address, if any, relatively to the beginning the + * array used to store the HTTP message itself. + * + * htx_blk.info representation: + * + * 0b 0000 0000 0000 0000 0000 0000 0000 0000 + * ---- ------------------------ --------- + * type value (1 MB max) name length (header) + * ---------------------------------- + * data length (256 MB max) + * (body, method, path, version, status, reason, trailers) + * + * types: + * - 0000 = request start-line + * - 0001 = response start-line + * - 0010 = header + * - 0011 = pseudo-header ou "special" header + * - 0100 = end-of-headers + * - 0101 = data + * - 0110 = end-of-data + * - 0111 = trailer + * - 1000 = end-of-message + * ... + * - 1101 = out-of-band + * - 1110 = error + * - 1111 = unused + * + */ + +/* HTX flags */ +#define HTX_FL_NONE 0x00000000 +#define HTX_FL_PARSING_ERROR 0x00000001 + + +/* Pseudo header types (max 255). */ +enum htx_phdr_type { + HTX_PHDR_UNKNOWN = 0, + HTX_PHDR_SIZE, +}; + +/* HTTP block's type (max 15). */ +enum htx_blk_type { + HTX_BLK_REQ_SL = 0, /* Request start-line */ + HTX_BLK_RES_SL = 1, /* Response start-line */ + HTX_BLK_HDR = 2, /* header name/value block */ + HTX_BLK_PHDR = 3, /* pseudo header block */ + HTX_BLK_EOH = 4, /* end-of-headers block */ + HTX_BLK_DATA = 5, /* data block */ + HTX_BLK_EOD = 6, /* end-of-data block */ + HTX_BLK_TLR = 7, /* trailer name/value block */ + HTX_BLK_EOM = 8, /* end-of-message block */ + /* 9 .. 13 unused */ + HTX_BLK_OOB = 14, /* Out of band block, don't alter the parser */ + HTX_BLK_UNUSED = 15, /* unused/removed block */ +}; + +/* One HTTP block descriptor */ +struct htx_blk { + uint32_t addr; /* relative storage address of a data block */ + uint32_t info; /* information about data stored */ +}; + +struct htx_ret { + int32_t ret; + struct htx_blk *blk; +}; + +union htx_sl { + struct { + enum http_meth_t meth; /* method */ + int m_len; /* METHOD length */ + int u_len; /* URI length */ + int v_len; /* VERSION length */ + char l[0]; + } rq; /* request line : field, length, data */ + struct { + uint16_t status; /* status code */ + int v_len; /* VERSION length */ + int c_len; /* CODE length */ + int r_len; /* REASON length */ + char l[0]; + } st; /* status line : field, length, data */ +}; + +/* Internal representation of an HTTP message */ +struct htx { + uint32_t size; /* the array size, in bytes, used to store the HTTP message itself */ + uint32_t data; /* the data size, in bytes. To known to total size used by all allocated + * blocks (blocks and their contents), you need to add size used by blocks, + * i.e. [ used * sizeof(struct htx_blk *) ] */ + + uint32_t used; /* number of blocks in use */ + uint32_t tail; /* last inserted block */ + uint32_t front; /* block's position of the first content before the blocks table */ + uint32_t wrap; /* the position were the blocks table wraps, if any */ + + uint64_t extra; /* known bytes amount remaining to receive */ + uint32_t flags; /* HTX_FL_* */ + + struct htx_blk blocks[0]; /* Blocks representing the HTTP message itself */ +}; + +#endif /* _TYPES_HTX_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/htx.c b/src/htx.c new file mode 100644 index 000000000..23ac61133 --- /dev/null +++ b/src/htx.c @@ -0,0 +1,891 @@ +/* + * internal HTTP message + * + * Copyright 2018 HAProxy Technologies, Christopher Faulet + * + * 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 +#include + +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 is not NULL, we replace it by the new block address, after + * the defragmentation. The new 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 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 with a content of + * 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 in the HTTP message . 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 from the HTTP message . The function returns the + * block following or NULL if 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 to , stopping on the first block of the + * type (typically EOH, EOD or EOM) or when bytes of data were + * moved. It returns the number of bytes of data moved and the last HTX block + * inserted in . + */ +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 from 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 . 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 . 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 . 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 . 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 . 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 . 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 . 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 . 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 to the + * chunk . 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 to the chunk + * . 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 to the chunk + * . 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 to the chunk + * . If 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 to the chunk + * . 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; +}