From 47596d378706e72db644e1a3dbafa3e24a0f6caf Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Mon, 22 Oct 2018 09:17:28 +0200 Subject: [PATCH] MINOR: http_htx: Add functions to manipulate HTX messages in http_htx.c This file will host all functions to manipulate HTTP messages using the HTX representation. Functions in this file will be able to be called from anywhere and are mainly related to the HTTP semantics. --- Makefile | 2 +- include/proto/http_htx.h | 40 +++++ include/types/http_htx.h | 37 ++++ src/http_htx.c | 363 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 441 insertions(+), 1 deletion(-) create mode 100644 include/proto/http_htx.h create mode 100644 include/types/http_htx.h create mode 100644 src/http_htx.c diff --git a/Makefile b/Makefile index 33f70e96d..b4125e1c8 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/htx.o + src/mux_h1.o src/htx.o src/http_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/http_htx.h b/include/proto/http_htx.h new file mode 100644 index 000000000..1ca7902f9 --- /dev/null +++ b/include/proto/http_htx.h @@ -0,0 +1,40 @@ +/* + * include/types/http_htx.h + * This file defines function prototypes for HTTP manipulation using the + * internal representation. + * + * 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_HTTP_HTX_H +#define _PROTO_HTTP_HTX_H + +#include + +#include +#include + +union h1_sl http_find_stline(const struct htx *htx); +int http_find_header(const struct htx *htx, const struct ist name, struct http_hdr_ctx *ctx, int full); +int http_add_header(struct htx *htx, const struct ist n, const struct ist v); +int http_replace_reqline(struct htx *htx, const union h1_sl sl); +int http_replace_resline(struct htx *htx, const union h1_sl sl); +int http_replace_header_value(struct htx *htx, struct http_hdr_ctx *ctx, const struct ist data); +int http_replace_header(struct htx *htx, struct http_hdr_ctx *ctx, const struct ist name, const struct ist value); +int http_remove_header(struct htx *htx, struct http_hdr_ctx *ctx); + +#endif /* _PROTO_HTTP_HTX_H */ diff --git a/include/types/http_htx.h b/include/types/http_htx.h new file mode 100644 index 000000000..c8f4197d0 --- /dev/null +++ b/include/types/http_htx.h @@ -0,0 +1,37 @@ +/* + * include/types/http_htx.h + * This file defines everything related to HTTP manipulation using the internal + * representation. + * + * 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_HTTP_HTX_H +#define _TYPES_HTTP_HTX_H + +#include +#include + +/* Context used to find/remove an HTTP header. */ +struct http_hdr_ctx { + struct htx_blk *blk; + struct ist value; + uint16_t lws_before; + uint16_t lws_after; +}; + +#endif /* _TYPES_HTTP_HTX_H */ diff --git a/src/http_htx.c b/src/http_htx.c new file mode 100644 index 000000000..47e36c856 --- /dev/null +++ b/src/http_htx.c @@ -0,0 +1,363 @@ +/* + * Functions to manipulate HTTP messages using the internal representation. + * + * Copyright (C) 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 + +#include +#include + +/* Finds the start line in the HTX message stopping at the first + * end-of-message. It returns an empty start line when not found, otherwise, it + * returns the corresponding . + */ +union h1_sl http_find_stline(const struct htx *htx) +{ + union htx_sl *htx_sl; + union h1_sl sl; + int32_t pos; + + 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_REQ_SL) { + htx_sl = htx_get_blk_ptr(htx, blk); + sl.rq.meth = htx_sl->rq.meth; + sl.rq.m = ist2(htx_sl->rq.l, htx_sl->rq.m_len); + sl.rq.u = ist2(htx_sl->rq.l + htx_sl->rq.m_len, htx_sl->rq.u_len); + sl.rq.v = ist2(htx_sl->rq.l + htx_sl->rq.m_len + htx_sl->rq.u_len, htx_sl->rq.v_len); + return sl; + } + + if (type == HTX_BLK_RES_SL) { + htx_sl = htx_get_blk_ptr(htx, blk); + sl.st.status = htx_sl->st.status; + sl.st.v = ist2(htx_sl->st.l, htx_sl->st.v_len); + sl.st.c = ist2(htx_sl->st.l + htx_sl->st.v_len, htx_sl->st.c_len); + sl.st.r = ist2(htx_sl->st.l + htx_sl->st.v_len + htx_sl->st.c_len, htx_sl->st.r_len); + return sl; + } + if (type == HTX_BLK_EOM) + break; + } + + sl.rq.m = ist(""); + sl.rq.u = ist(""); + sl.rq.v = ist(""); + return sl; +} + +/* Finds the first or next occurrence of header in the HTX message + * using the context . This structure holds everything necessary to use the + * header and find next occurrence. If its member is NULL, the header is + * searched from the beginning. Otherwise, the next occurrence is returned. The + * function returns 1 when it finds a value, and 0 when there is no more. It is + * designed to work with headers defined as comma-separated lists. If is + * set, it works on full-line headers in whose comma is not a delimiter but is + * part of the syntax. A special case, if ctx->value is NULL when searching for + * a new values of a header, the current header is rescanned. This allows + * rescanning after a header deletion. + */ +int http_find_header(const struct htx *htx, const struct ist name, + struct http_hdr_ctx *ctx, int full) +{ + struct htx_blk *blk = ctx->blk; + struct ist n, v; + enum htx_blk_type type; + uint32_t pos; + + if (blk) { + char *p; + + pos = htx_get_blk_pos(htx, blk); + if (!ctx->value.ptr) + goto rescan_hdr; + if (full) + goto next_blk; + v = htx_get_blk_value(htx, blk); + p = ctx->value.ptr + ctx->value.len + ctx->lws_after; + v.len -= (p - v.ptr); + v.ptr = p; + if (!v.len) + goto next_blk; + /* Skip comma */ + if (*(v.ptr) == ',') { + v.ptr++; + v.len--; + } + + goto return_hdr; + } + + if (!htx->used) + return 0; + + pos = htx_get_head(htx); + while (1) { + rescan_hdr: + blk = htx_get_blk(htx, pos); + type = htx_get_blk_type(blk); + if (type != HTX_BLK_HDR) + goto next_blk; + if (name.len) { + /* If no name was passed, we want any header. So skip the comparison */ + n = htx_get_blk_name(htx, blk); + if (!isteqi(n, name)) + goto next_blk; + } + v = htx_get_blk_value(htx, blk); + + return_hdr: + ctx->lws_before = 0; + ctx->lws_after = 0; + while (v.len && HTTP_IS_LWS(*v.ptr)) { + v.ptr++; + v.len--; + ctx->lws_before++; + } + if (!full) + v.len = http_find_hdr_value_end(v.ptr, v.ptr + v.len) - v.ptr; + while (v.len && HTTP_IS_LWS(*(v.ptr + v.len - 1))) { + v.len--; + ctx->lws_after++; + } + if (!v.len) + goto next_blk; + ctx->blk = blk; + ctx->value = v; + return 1; + + next_blk: + if (pos == htx->tail) + break; + pos++; + if (pos >= htx->wrap) + pos = 0; + } + + ctx->blk = NULL; + ctx->value = ist(""); + ctx->lws_before = ctx->lws_after = 0; + return 0; +} + +/* Adds a header block int the HTX message , just before the EOH block. It + * returns 1 on success, otherwise it returns 0. + */ +int http_add_header(struct htx *htx, const struct ist n, const struct ist v) +{ + struct htx_blk *blk; + enum htx_blk_type type = htx_get_tail_type(htx); + int32_t prev; + + blk = htx_add_header(htx, n, v); + if (!blk) + return 0; + + if (unlikely(type < HTX_BLK_EOH)) + return 1; + + /* is the head, swap it iteratively with its predecessor to place + * it just before the end-of-header block. So blocks remains ordered. */ + 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); + enum htx_blk_type type = htx_get_blk_type(pblk); + + /* 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; + + /* Stop when end-of-header is reached */ + if (type == HTX_BLK_EOH) + break; + + blk = pblk; + } + return 1; +} + +/* Replaces the request start line of the HTX message by . It returns + * 1 on success, otherwise it returns 0. The start line must be found in the + * message. + */ +int http_replace_reqline(struct htx *htx, const union h1_sl sl) +{ + int32_t pos; + + 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_REQ_SL) { + blk = htx_replace_reqline(htx, blk, sl); + if (!blk) + return 0; + return 1; + } + if (type == HTX_BLK_EOM) + break; + } + + return 0; +} + + +/* Replaces the response start line of the HTX message by . It returns + * 1 on success, otherwise it returns 0. The start line must be found in the + * message. + */ +int http_replace_resline(struct htx *htx, const union h1_sl sl) +{ + int32_t pos; + + 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_RES_SL) { + blk = htx_replace_resline(htx, blk, sl); + if (!blk) + return 0; + return 1; + } + if (type == HTX_BLK_EOM) + break; + } + + return 0; +} + +/* Replaces a part of a header value referenced in the context by + * . It returns 1 on success, otherwise it returns 0. The context is + * updated if necessary. + */ +int http_replace_header_value(struct htx *htx, struct http_hdr_ctx *ctx, const struct ist data) +{ + struct htx_blk *blk = ctx->blk; + char *start; + struct ist v; + uint32_t len, off; + + if (!blk) + return 0; + + v = htx_get_blk_value(htx, blk); + start = ctx->value.ptr - ctx->lws_before; + len = ctx->lws_before + ctx->value.len + ctx->lws_after; + off = start - v.ptr; + + blk = htx_replace_blk_value(htx, blk, ist2(start, len), data); + if (!blk) + return 0; + + v = htx_get_blk_value(htx, blk); + ctx->blk = blk; + ctx->value.ptr = v.ptr + off; + ctx->value.len = data.len; + ctx->lws_before = ctx->lws_after = 0; + + return 1; +} + +/* Fully replaces a header referenced in the context by the name + * with the value . It returns 1 on success, otherwise it returns 0. The + * context is updated if necessary. + */ +int http_replace_header(struct htx *htx, struct http_hdr_ctx *ctx, + const struct ist name, const struct ist value) +{ + struct htx_blk *blk = ctx->blk; + + if (!blk) + return 0; + + blk = htx_replace_header(htx, blk, name, value); + if (!blk) + return 0; + + ctx->blk = blk; + ctx->value = ist(NULL); + ctx->lws_before = ctx->lws_after = 0; + + return 1; +} + +/* Remove one value of a header. This only works on a returned by + * http_find_header function. The value is removed, as well as surrounding commas + * if any. If the removed value was alone, the whole header is removed. The + * is always updated accordingly, as well as the HTX message . It + * returns 1 on success. Otherwise, it returns 0. The is always left in a + * form that can be handled by http_find_header() to find next occurrence. + */ +int http_remove_header(struct htx *htx, struct http_hdr_ctx *ctx) +{ + struct htx_blk *blk = ctx->blk; + char *start; + struct ist v; + uint32_t len; + + if (!blk) + return 0; + + start = ctx->value.ptr - ctx->lws_before; + len = ctx->lws_before + ctx->value.len + ctx->lws_after; + + v = htx_get_blk_value(htx, blk); + if (len == v.len) { + blk = htx_remove_blk(htx, blk); + if (blk || !htx->used) { + ctx->blk = blk; + ctx->value = ist2(NULL, 0); + ctx->lws_before = ctx->lws_after = 0; + } + else { + ctx->blk = htx_get_blk(htx, htx->tail); + ctx->value = htx_get_blk_value(htx, ctx->blk); + ctx->lws_before = ctx->lws_after = 0; + } + return 1; + } + + /* This was not the only value of this header. We have to remove the + * part pointed by ctx->value. If it is the last entry of the list, we + * remove the last separator. + */ + if (start == v.ptr) { + /* It's the first header part but not the only one. So remove + * the comma after it. */ + len++; + } + else { + /* There is at least one header part before the removed one. So + * remove the comma between them. */ + start--; + len++; + } + /* Update the block content and its len */ + memmove(start, start+len, v.len-len); + htx_set_blk_value_len(blk, v.len-len); + + /* Update HTX msg */ + htx->data -= len; + + /* Finally update the ctx */ + ctx->value.ptr = start; + ctx->value.len = 0; + ctx->lws_before = ctx->lws_after = 0; + + return 1; +}