/* * 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 = htxbuf(chunk); struct htx_blk *newblk, *oldblk; uint32_t new, old, blkpos; uint32_t addr, blksz; int32_t sl_off = -1; if (!htx->used) return NULL; blkpos = -1; 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); /* update the start-line offset */ if (htx->sl_off == oldblk->addr) sl_off = addr; /* if is defined, set its new position */ if (blk != NULL && blk == oldblk) blkpos = new; memcpy((void *)tmp->blocks + addr, htx_get_blk_ptr(htx, oldblk), blksz); new++; addr += blksz; } while (new < htx->used); htx->sl_off = sl_off; htx->wrap = htx->used; htx->front = htx->tail = new - 1; memcpy((void *)htx->blocks, (void *)tmp->blocks, htx->size); return ((blkpos == -1) ? NULL : htx_get_blk(htx, blkpos)); } /* 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) { enum htx_blk_type type = htx_get_blk_type(blk); uint32_t next, head, pos; if (type != 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); if (htx->sl_off == blk->addr) htx->sl_off = -1; } /* 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); if (htx->sl_off == -1) { /* Try to update the start-line offset, if possible */ type = htx_get_blk_type(blk); if (type == HTX_BLK_REQ_SL || type == HTX_BLK_RES_SL) htx->sl_off = blk->addr; } end: if (pos == htx->front) htx->front = htx_find_front(htx); return blk; } /* Truncate all blocks after the one containing the offset . This last * one is truncated too. */ void htx_truncate(struct htx *htx, uint32_t offset) { struct htx_blk *blk; struct htx_ret htxret; htxret = htx_find_blk(htx, offset); blk = htxret.blk; if (blk && htxret.ret) { uint32_t sz = htx_get_blksz(blk); htx_set_blk_value_len(blk, sz - htxret.ret); blk = htx_get_next_blk(htx, blk); } while (blk) blk = htx_remove_blk(htx, blk); } /* Drain bytes from the HTX message . DATA blocks will be cut if * necessary. Others blocks will be removed at once if is large * enough. The function returns an htx_ret with the first block remaing in the * messsage and the amount of data drained. If everything is removed, * htx_ret.blk is set to NULL. */ struct htx_ret htx_drain(struct htx *htx, uint32_t count) { struct htx_blk *blk; struct htx_ret htxret = { .blk = NULL, .ret = 0 }; blk = htx_get_head_blk(htx); while (count && blk) { uint32_t sz = htx_get_blksz(blk); enum htx_blk_type type = htx_get_blk_type(blk); /* Ingore unused block */ if (type == HTX_BLK_UNUSED) goto next; if (sz > count) { if (type == HTX_BLK_DATA) { htx_cut_data_blk(htx, blk, count); htxret.ret += count; } break; } count -= sz; htxret.ret += sz; next: blk = htx_remove_blk(htx, blk); } htxret.blk = blk; return htxret; } /* Tries to append data to the last inserted block, if the type matches and if * there is enough non-wrapping space. Only DATA 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, *tailblk, *headblk, *frtblk; struct ist v; int32_t room; 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 */ if (type != HTX_BLK_DATA) goto add_new_block; /* get the tail and head block */ tailblk = htx_get_tail_blk(htx); headblk = htx_get_head_blk(htx); if (tailblk == NULL || headblk == NULL) goto add_new_block; /* Don't try to append data if the last inserted block is not of the * same type */ if (type != htx_get_blk_type(tailblk)) goto add_new_block; /* * Same type and enough space: append data */ frtblk = htx_get_blk(htx, htx->front); if (tailblk->addr >= headblk->addr) { if (htx->tail != htx->front) goto add_new_block; room = sizeof(htx->blocks[0]) * htx_pos_to_idx(htx, htx->tail) - (frtblk->addr + htx_get_blksz(frtblk)); } else room = headblk->addr - (tailblk->addr + htx_get_blksz(tailblk)); if (room < (int32_t)data.len) tailblk = htx_defrag(htx, tailblk); append_data: /* get the value of the tail block */ /* FIXME: check v.len + data.len < 256MB */ v = htx_get_blk_value(htx, tailblk); /* Append data and update the block itself */ memcpy(v.ptr + v.len, data.ptr, data.len); htx_set_blk_value_len(tailblk, v.len + data.len); /* Update HTTP message */ htx->data += data.len; return tailblk; 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 buffer *tmp; struct ist n, v; int32_t delta; n = htx_get_blk_name(htx, blk); v = htx_get_blk_value(htx, blk); delta = new.len - old.len; /* easy case, new data are smaller, so replace it in-place */ if (delta <= 0) { 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 + delta); htx->data += delta; return blk; } /* we need to allocate more space to store the new header value */ if (delta > 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)); /* Expand the block size. But data are not copied yet. Then defragment * the HTX message. */ htx_set_blk_value_len(blk, v.len + delta); htx->data += delta; blk = htx_defrag(htx, blk); /* Finaly, copy data. */ memcpy(htx_get_blk_ptr(htx, blk), tmp->area, tmp->data); 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 - ret) max = count - ret; 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; } if (dst->sl_off == -1 && src->sl_off == blk->addr) dst->sl_off = dstblk->addr; next: blk = htx_remove_blk(src, blk); if (type == mark) break; } return (struct htx_ret){.ret = ret, .blk = dstblk}; } /* 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. * The header name is always lower cased. */ 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; int32_t delta; type = htx_get_blk_type(blk); if (type != HTX_BLK_HDR) return NULL; delta = name.len + value.len - htx_get_blksz(blk); /* easy case, new value is smaller, so replace it in-place */ if (delta <= 0) { blk->info = (type << 28) + (value.len << 8) + name.len; htx->data += delta; goto copy; } /* we need to allocate more space to store the new value */ if (delta > htx_free_space(htx)) return NULL; /* not enough space */ /* Expand the block size. But data are not copied yet. Then defragment * the HTX message. */ blk->info = (type << 28) + (value.len << 8) + name.len; htx->data += delta; blk = htx_defrag(htx, blk); copy: /* Finaly, copy data. */ ist2bin_lc(htx_get_blk_ptr(htx, blk), name); memcpy(htx_get_blk_ptr(htx, blk) + name.len, value.ptr, value.len); return blk; } /* Replaces the parts of the start-line. It returns the new start-line on * success, otherwise it returns NULL. It is the caller responsibility to update * sl->info, if necessary. */ struct htx_sl *htx_replace_stline(struct htx *htx, struct htx_blk *blk, const struct ist p1, const struct ist p2, const struct ist p3) { struct htx_sl *sl; struct htx_sl tmp; /* used to save sl->info and sl->flags */ enum htx_blk_type type; uint32_t size; int32_t delta; type = htx_get_blk_type(blk); if (type != HTX_BLK_REQ_SL && type != HTX_BLK_RES_SL) return NULL; /* Save start-line info and flags */ sl = htx_get_blk_ptr(htx, blk); tmp.info = sl->info; tmp.flags = sl->flags; if (htx->sl_off == blk->addr) htx->sl_off = -1; size = sizeof(*sl) + p1.len + p2.len + p3.len; delta = size - htx_get_blksz(blk); /* easy case, new data are smaller, so replace it in-place */ if (delta <= 0) { htx_set_blk_value_len(blk, size); htx->data += delta; goto copy; } /* we need to allocate more space to store the new value */ if (delta > htx_free_space(htx)) return NULL; /* not enough space */ /* Expand the block size. But data are not copied yet. Then defragment * the HTX message. */ htx_set_blk_value_len(blk, size); htx->data += delta; blk = htx_defrag(htx, blk); copy: /* Restore start-line info and flags and copy parts of the start-line */ sl = htx_get_blk_ptr(htx, blk); sl->info = tmp.info; sl->flags = tmp.flags; if (htx->sl_off == -1) htx->sl_off = blk->addr; HTX_SL_P1_LEN(sl) = p1.len; HTX_SL_P2_LEN(sl) = p2.len; HTX_SL_P3_LEN(sl) = p3.len; memcpy(HTX_SL_P1_PTR(sl), p1.ptr, p1.len); memcpy(HTX_SL_P2_PTR(sl), p2.ptr, p2.len); memcpy(HTX_SL_P3_PTR(sl), p3.ptr, p3.len); return sl; } /* Add a new start-line. It returns it on success, otherwise it returns NULL. It * is the caller responsibility to set sl->info, if necessary. */ struct htx_sl *htx_add_stline(struct htx *htx, enum htx_blk_type type, unsigned int flags, const struct ist p1, const struct ist p2, const struct ist p3) { struct htx_blk *blk; struct htx_sl *sl; uint32_t size; if (type != HTX_BLK_REQ_SL && type != HTX_BLK_RES_SL) return NULL; size = sizeof(*sl) + p1.len + p2.len + p3.len; /* FIXME: check size (< 256MB) */ blk = htx_add_blk(htx, type, size); if (!blk) return NULL; blk->info += size; sl = htx_get_blk_ptr(htx, blk); if (htx->sl_off == -1) htx->sl_off = blk->addr; sl->flags = flags; HTX_SL_P1_LEN(sl) = p1.len; HTX_SL_P2_LEN(sl) = p2.len; HTX_SL_P3_LEN(sl) = p3.len; memcpy(HTX_SL_P1_PTR(sl), p1.ptr, p1.len); memcpy(HTX_SL_P2_PTR(sl), p2.ptr, p2.len); memcpy(HTX_SL_P3_PTR(sl), p3.ptr, p3.len); return sl; } /* Adds an HTX block of type HDR in . It returns the new block on * success. Otherwise, it returns NULL. The header name is always lower cased. */ 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; ist2bin_lc(htx_get_blk_ptr(htx, blk), name); memcpy(htx_get_blk_ptr(htx, blk) + name.len, value.ptr, value.len); return blk; } /* Adds an HTX block of type in , of size . It returns the * new block on success. Otherwise, it returns NULL. The caller is responsible * for filling the block itself. */ struct htx_blk *htx_add_blk_type_size(struct htx *htx, enum htx_blk_type type, uint32_t blksz) { struct htx_blk *blk; blk = htx_add_blk(htx, type, blksz); if (!blk) return NULL; blk->info += blksz; 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 returns the new block on * success. Otherwise, it returns NULL. */ struct htx_blk *htx_add_trailer(struct htx *htx, const struct ist tlr) { struct htx_blk *blk; /* FIXME: check tlr.len (< 256MB) */ blk = htx_add_blk(htx, HTX_BLK_TLR, tlr.len); if (!blk) return NULL; blk->info += tlr.len; memcpy(htx_get_blk_ptr(htx, blk), tlr.ptr, tlr.len); return blk; } struct htx_blk *htx_add_data_before(struct htx *htx, const struct htx_blk *ref, const struct ist data) { struct htx_blk *blk; int32_t prev; /* FIXME: check data.len (< 256MB) */ blk = htx_add_blk(htx, HTX_BLK_DATA, data.len); if (!blk) return NULL; blk->info += data.len; memcpy(htx_get_blk_ptr(htx, blk), data.ptr, data.len); for (prev = htx_get_prev(htx, htx->tail); prev != -1; prev = htx_get_prev(htx, prev)) { struct htx_blk *pblk = htx_get_blk(htx, prev); /* Swap .addr and .info fields */ blk->addr ^= pblk->addr; pblk->addr ^= blk->addr; blk->addr ^= pblk->addr; blk->info ^= pblk->info; pblk->info ^= blk->info; blk->info ^= pblk->info; if (blk->addr == pblk->addr) blk->addr += htx_get_blksz(pblk); htx->front = prev; if (pblk == ref) break; blk = pblk; } if (htx_get_blk_pos(htx, blk) != htx->front) htx_defrag(htx, NULL); return blk; } /* Appends the H1 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_h1(const struct htx_sl *sl, struct buffer *chk) { if (HTX_SL_LEN(sl) + 4 > b_room(chk)) return 0; chunk_memcat(chk, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); chunk_memcat(chk, " ", 1); chunk_memcat(chk, HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl)); chunk_memcat(chk, " ", 1); if (sl->flags & HTX_SL_F_VER_11) chunk_memcat(chk, "HTTP/1.1", 8); else chunk_memcat(chk, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); chunk_memcat(chk, "\r\n", 2); return 1; } /* Appends the H1 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_h1(const struct htx_sl *sl, struct buffer *chk) { if (HTX_SL_LEN(sl) + 4 > b_size(chk)) return 0; if (sl->flags & HTX_SL_F_VER_11) chunk_memcat(chk, "HTTP/1.1", 8); else chunk_memcat(chk, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); chunk_memcat(chk, " ", 1); chunk_memcat(chk, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl)); chunk_memcat(chk, " ", 1); chunk_memcat(chk, HTX_SL_RES_RPTR(sl), HTX_SL_RES_RLEN(sl)); chunk_memcat(chk, "\r\n", 2); return 1; } /* Appends the H1 representation of the header block to the chunk * . It returns 1 if data are successfully appended, otherwise it returns * 0. */ int htx_hdr_to_h1(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 H1 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_h1(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 h1 representation of the trailer block to the chunk * . It returns 1 if data are successfully appended, otherwise it returns * 0. */ int htx_trailer_to_h1(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; }