mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-09-03 21:12:06 +02:00
Since the commit b75b5eaf ("MEDIUM: htx: 1xx messages are now part of the final reponses"), these messages are part of the response and should not contain EOM. This block is skipped during responses parsing, but analyzers still add it for "100-Continue" and "103-Eraly-Hints". It can also be added for error files with 1xx status code. Now, when HAProxy generate such transitional responses, it does not emit EOM blocks. And informational messages are now forbidden in error files. This patch must be backported to 2.0.
721 lines
20 KiB
C
721 lines
20 KiB
C
/*
|
|
* Functions to manipulate HTTP messages using the internal representation.
|
|
*
|
|
* Copyright (C) 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/config.h>
|
|
#include <common/debug.h>
|
|
#include <common/cfgparse.h>
|
|
#include <common/h1.h>
|
|
#include <common/http.h>
|
|
#include <common/htx.h>
|
|
|
|
#include <proto/http_htx.h>
|
|
|
|
struct buffer htx_err_chunks[HTTP_ERR_SIZE];
|
|
|
|
/* Returns the next unporocessed start line in the HTX message. It returns NULL
|
|
* if the start-line is undefined (first == -1). Otherwise, it returns the
|
|
* pointer on the htx_sl structure.
|
|
*/
|
|
struct htx_sl *http_get_stline(struct htx *htx)
|
|
{
|
|
struct htx_blk *blk;
|
|
|
|
BUG_ON(htx->first == -1);
|
|
blk = htx_get_first_blk(htx);
|
|
if (!blk)
|
|
return NULL;
|
|
BUG_ON(htx_get_blk_type(blk) != HTX_BLK_REQ_SL && htx_get_blk_type(blk) != HTX_BLK_RES_SL);
|
|
return htx_get_blk_ptr(htx, blk);
|
|
}
|
|
|
|
/* Finds the first or next occurrence of header <name> in the HTX message <htx>
|
|
* using the context <ctx>. This structure holds everything necessary to use the
|
|
* header and find next occurrence. If its <blk> 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 <full> 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;
|
|
|
|
if (blk) {
|
|
char *p;
|
|
|
|
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;
|
|
|
|
for (blk = htx_get_first_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
|
|
rescan_hdr:
|
|
type = htx_get_blk_type(blk);
|
|
if (type == HTX_BLK_EOH || type == HTX_BLK_EOM)
|
|
break;
|
|
if (type != HTX_BLK_HDR)
|
|
continue;
|
|
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)
|
|
continue;
|
|
ctx->blk = blk;
|
|
ctx->value = v;
|
|
return 1;
|
|
|
|
next_blk:
|
|
;
|
|
}
|
|
|
|
ctx->blk = NULL;
|
|
ctx->value = ist("");
|
|
ctx->lws_before = ctx->lws_after = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Adds a header block int the HTX message <htx>, 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;
|
|
|
|
/* <blk> 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 != htx->first; 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);
|
|
|
|
/* Stop when end-of-header is reached */
|
|
if (type == HTX_BLK_EOH)
|
|
break;
|
|
|
|
blk = pblk;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Replaces parts of the start-line of the HTX message <htx>. It returns 1 on
|
|
* success, otherwise it returns 0.
|
|
*/
|
|
int http_replace_stline(struct htx *htx, const struct ist p1, const struct ist p2, const struct ist p3)
|
|
{
|
|
struct htx_blk *blk;
|
|
|
|
blk = htx_get_first_blk(htx);
|
|
if (!blk || !htx_replace_stline(htx, blk, p1, p2, p3))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/* Replace the request method in the HTX message <htx> by <meth>. It returns 1
|
|
* on success, otherwise 0.
|
|
*/
|
|
int http_replace_req_meth(struct htx *htx, const struct ist meth)
|
|
{
|
|
struct buffer *temp = get_trash_chunk();
|
|
struct htx_sl *sl = http_get_stline(htx);
|
|
struct ist uri, vsn;
|
|
|
|
if (!sl)
|
|
return 0;
|
|
|
|
/* Start by copying old uri and version */
|
|
chunk_memcat(temp, HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl)); /* uri */
|
|
uri = ist2(temp->area, HTX_SL_REQ_ULEN(sl));
|
|
|
|
chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
|
|
vsn = ist2(temp->area + uri.len, HTX_SL_REQ_VLEN(sl));
|
|
|
|
/* create the new start line */
|
|
sl->info.req.meth = find_http_meth(meth.ptr, meth.len);
|
|
return http_replace_stline(htx, meth, uri, vsn);
|
|
}
|
|
|
|
/* Replace the request uri in the HTX message <htx> by <uri>. It returns 1 on
|
|
* success, otherwise 0.
|
|
*/
|
|
int http_replace_req_uri(struct htx *htx, const struct ist uri)
|
|
{
|
|
struct buffer *temp = get_trash_chunk();
|
|
struct htx_sl *sl = http_get_stline(htx);
|
|
struct ist meth, vsn;
|
|
|
|
if (!sl)
|
|
return 0;
|
|
|
|
/* Start by copying old method and version */
|
|
chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
|
|
meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
|
|
|
|
chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
|
|
vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
|
|
|
|
/* create the new start line */
|
|
return http_replace_stline(htx, meth, uri, vsn);
|
|
}
|
|
|
|
/* Replace the request path in the HTX message <htx> by <path>. The host part
|
|
* and the query string are preserved. It returns 1 on success, otherwise 0.
|
|
*/
|
|
int http_replace_req_path(struct htx *htx, const struct ist path)
|
|
{
|
|
struct buffer *temp = get_trash_chunk();
|
|
struct htx_sl *sl = http_get_stline(htx);
|
|
struct ist meth, uri, vsn, p;
|
|
size_t plen = 0;
|
|
|
|
if (!sl)
|
|
return 0;
|
|
|
|
uri = htx_sl_req_uri(sl);
|
|
p = http_get_path(uri);
|
|
if (!p.ptr)
|
|
p = uri;
|
|
while (plen < p.len && *(p.ptr + plen) != '?')
|
|
plen++;
|
|
|
|
/* Start by copying old method and version and create the new uri */
|
|
chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
|
|
meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
|
|
|
|
chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
|
|
vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
|
|
|
|
chunk_memcat(temp, uri.ptr, p.ptr - uri.ptr); /* uri: host part */
|
|
chunk_memcat(temp, path.ptr, path.len); /* uri: new path */
|
|
chunk_memcat(temp, p.ptr + plen, p.len - plen); /* uri: QS part */
|
|
uri = ist2(temp->area + meth.len + vsn.len, uri.len - plen + path.len);
|
|
|
|
/* create the new start line */
|
|
return http_replace_stline(htx, meth, uri, vsn);
|
|
}
|
|
|
|
/* Replace the request query-string in the HTX message <htx> by <query>. The
|
|
* host part and the path are preserved. It returns 1 on success, otherwise
|
|
* 0.
|
|
*/
|
|
int http_replace_req_query(struct htx *htx, const struct ist query)
|
|
{
|
|
struct buffer *temp = get_trash_chunk();
|
|
struct htx_sl *sl = http_get_stline(htx);
|
|
struct ist meth, uri, vsn, q;
|
|
int offset = 1;
|
|
|
|
if (!sl)
|
|
return 0;
|
|
|
|
uri = htx_sl_req_uri(sl);
|
|
q = uri;
|
|
while (q.len > 0 && *(q.ptr) != '?') {
|
|
q.ptr++;
|
|
q.len--;
|
|
}
|
|
|
|
/* skip the question mark or indicate that we must insert it
|
|
* (but only if the format string is not empty then).
|
|
*/
|
|
if (q.len) {
|
|
q.ptr++;
|
|
q.len--;
|
|
}
|
|
else if (query.len > 1)
|
|
offset = 0;
|
|
|
|
/* Start by copying old method and version and create the new uri */
|
|
chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
|
|
meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
|
|
|
|
chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
|
|
vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
|
|
|
|
chunk_memcat(temp, uri.ptr, q.ptr - uri.ptr); /* uri: host + path part */
|
|
chunk_memcat(temp, query.ptr + offset, query.len - offset); /* uri: new QS */
|
|
uri = ist2(temp->area + meth.len + vsn.len, uri.len - q.len + query.len - offset);
|
|
|
|
/* create the new start line */
|
|
return http_replace_stline(htx, meth, uri, vsn);
|
|
}
|
|
|
|
/* Replace the response status in the HTX message <htx> by <status>. It returns
|
|
* 1 on success, otherwise 0.
|
|
*/
|
|
int http_replace_res_status(struct htx *htx, const struct ist status)
|
|
{
|
|
struct buffer *temp = get_trash_chunk();
|
|
struct htx_sl *sl = http_get_stline(htx);
|
|
struct ist vsn, reason;
|
|
|
|
if (!sl)
|
|
return 0;
|
|
|
|
/* Start by copying old uri and version */
|
|
chunk_memcat(temp, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); /* vsn */
|
|
vsn = ist2(temp->area, HTX_SL_RES_VLEN(sl));
|
|
|
|
chunk_memcat(temp, HTX_SL_RES_RPTR(sl), HTX_SL_RES_RLEN(sl)); /* reason */
|
|
reason = ist2(temp->area + vsn.len, HTX_SL_RES_RLEN(sl));
|
|
|
|
/* create the new start line */
|
|
sl->info.res.status = strl2ui(status.ptr, status.len);
|
|
return http_replace_stline(htx, vsn, status, reason);
|
|
}
|
|
|
|
/* Replace the response reason in the HTX message <htx> by <reason>. It returns
|
|
* 1 on success, otherwise 0.
|
|
*/
|
|
int http_replace_res_reason(struct htx *htx, const struct ist reason)
|
|
{
|
|
struct buffer *temp = get_trash_chunk();
|
|
struct htx_sl *sl = http_get_stline(htx);
|
|
struct ist vsn, status;
|
|
|
|
if (!sl)
|
|
return 0;
|
|
|
|
/* Start by copying old uri and version */
|
|
chunk_memcat(temp, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); /* vsn */
|
|
vsn = ist2(temp->area, HTX_SL_RES_VLEN(sl));
|
|
|
|
chunk_memcat(temp, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl)); /* code */
|
|
status = ist2(temp->area + vsn.len, HTX_SL_RES_CLEN(sl));
|
|
|
|
/* create the new start line */
|
|
return http_replace_stline(htx, vsn, status, reason);
|
|
}
|
|
|
|
/* Replaces a part of a header value referenced in the context <ctx> by
|
|
* <data>. 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 <ctx> by the name <name>
|
|
* with the value <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 <ctx> 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
|
|
* <ctx> is always updated accordingly, as well as the HTX message <htx>. It
|
|
* returns 1 on success. Otherwise, it returns 0. The <ctx> 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_change_blk_value_len(htx, blk, v.len-len);
|
|
|
|
/* Finally update the ctx */
|
|
ctx->value.ptr = start;
|
|
ctx->value.len = 0;
|
|
ctx->lws_before = ctx->lws_after = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* Return in <vptr> and <vlen> the pointer and length of occurrence <occ> of
|
|
* header whose name is <hname> of length <hlen>. If <ctx> is null, lookup is
|
|
* performed over the whole headers. Otherwise it must contain a valid header
|
|
* context, initialised with ctx->blk=NULL for the first lookup in a series. If
|
|
* <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
|
|
* is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
|
|
* than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
|
|
* -1. The value fetch stops at commas, so this function is suited for use with
|
|
* list headers.
|
|
* The return value is 0 if nothing was found, or non-zero otherwise.
|
|
*/
|
|
unsigned int http_get_htx_hdr(const struct htx *htx, const struct ist hdr,
|
|
int occ, struct http_hdr_ctx *ctx, char **vptr, size_t *vlen)
|
|
{
|
|
struct http_hdr_ctx local_ctx;
|
|
struct ist val_hist[MAX_HDR_HISTORY];
|
|
unsigned int hist_idx;
|
|
int found;
|
|
|
|
if (!ctx) {
|
|
local_ctx.blk = NULL;
|
|
ctx = &local_ctx;
|
|
}
|
|
|
|
if (occ >= 0) {
|
|
/* search from the beginning */
|
|
while (http_find_header(htx, hdr, ctx, 0)) {
|
|
occ--;
|
|
if (occ <= 0) {
|
|
*vptr = ctx->value.ptr;
|
|
*vlen = ctx->value.len;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* negative occurrence, we scan all the list then walk back */
|
|
if (-occ > MAX_HDR_HISTORY)
|
|
return 0;
|
|
|
|
found = hist_idx = 0;
|
|
while (http_find_header(htx, hdr, ctx, 0)) {
|
|
val_hist[hist_idx] = ctx->value;
|
|
if (++hist_idx >= MAX_HDR_HISTORY)
|
|
hist_idx = 0;
|
|
found++;
|
|
}
|
|
if (-occ > found)
|
|
return 0;
|
|
|
|
/* OK now we have the last occurrence in [hist_idx-1], and we need to
|
|
* find occurrence -occ. 0 <= hist_idx < MAX_HDR_HISTORY, and we have
|
|
* -10 <= occ <= -1. So we have to check [hist_idx%MAX_HDR_HISTORY+occ]
|
|
* to remain in the 0..9 range.
|
|
*/
|
|
hist_idx += occ + MAX_HDR_HISTORY;
|
|
if (hist_idx >= MAX_HDR_HISTORY)
|
|
hist_idx -= MAX_HDR_HISTORY;
|
|
*vptr = val_hist[hist_idx].ptr;
|
|
*vlen = val_hist[hist_idx].len;
|
|
return 1;
|
|
}
|
|
|
|
/* Return in <vptr> and <vlen> the pointer and length of occurrence <occ> of
|
|
* header whose name is <hname> of length <hlen>. If <ctx> is null, lookup is
|
|
* performed over the whole headers. Otherwise it must contain a valid header
|
|
* context, initialised with ctx->blk=NULL for the first lookup in a series. If
|
|
* <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
|
|
* is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
|
|
* than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
|
|
* -1. This function differs from http_get_hdr() in that it only returns full
|
|
* line header values and does not stop at commas.
|
|
* The return value is 0 if nothing was found, or non-zero otherwise.
|
|
*/
|
|
unsigned int http_get_htx_fhdr(const struct htx *htx, const struct ist hdr,
|
|
int occ, struct http_hdr_ctx *ctx, char **vptr, size_t *vlen)
|
|
{
|
|
struct http_hdr_ctx local_ctx;
|
|
struct ist val_hist[MAX_HDR_HISTORY];
|
|
unsigned int hist_idx;
|
|
int found;
|
|
|
|
if (!ctx) {
|
|
local_ctx.blk = NULL;
|
|
ctx = &local_ctx;
|
|
}
|
|
|
|
if (occ >= 0) {
|
|
/* search from the beginning */
|
|
while (http_find_header(htx, hdr, ctx, 1)) {
|
|
occ--;
|
|
if (occ <= 0) {
|
|
*vptr = ctx->value.ptr;
|
|
*vlen = ctx->value.len;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* negative occurrence, we scan all the list then walk back */
|
|
if (-occ > MAX_HDR_HISTORY)
|
|
return 0;
|
|
|
|
found = hist_idx = 0;
|
|
while (http_find_header(htx, hdr, ctx, 1)) {
|
|
val_hist[hist_idx] = ctx->value;
|
|
if (++hist_idx >= MAX_HDR_HISTORY)
|
|
hist_idx = 0;
|
|
found++;
|
|
}
|
|
if (-occ > found)
|
|
return 0;
|
|
|
|
/* OK now we have the last occurrence in [hist_idx-1], and we need to
|
|
* find occurrence -occ. 0 <= hist_idx < MAX_HDR_HISTORY, and we have
|
|
* -10 <= occ <= -1. So we have to check [hist_idx%MAX_HDR_HISTORY+occ]
|
|
* to remain in the 0..9 range.
|
|
*/
|
|
hist_idx += occ + MAX_HDR_HISTORY;
|
|
if (hist_idx >= MAX_HDR_HISTORY)
|
|
hist_idx -= MAX_HDR_HISTORY;
|
|
*vptr = val_hist[hist_idx].ptr;
|
|
*vlen = val_hist[hist_idx].len;
|
|
return 1;
|
|
}
|
|
|
|
static struct htx *http_str_to_htx(struct buffer *buf, struct ist raw)
|
|
{
|
|
struct htx *htx;
|
|
struct htx_sl *sl;
|
|
struct h1m h1m;
|
|
struct http_hdr hdrs[global.tune.max_http_hdr];
|
|
union h1_sl h1sl;
|
|
unsigned int flags = HTX_SL_F_IS_RESP;
|
|
int ret = 0;
|
|
|
|
buf->size = global.tune.bufsize;
|
|
buf->area = (char *)malloc(buf->size);
|
|
if (!buf->area)
|
|
goto error;
|
|
b_reset(buf);
|
|
|
|
h1m_init_res(&h1m);
|
|
h1m.flags |= H1_MF_NO_PHDR;
|
|
ret = h1_headers_to_hdr_list(raw.ptr, raw.ptr + raw.len,
|
|
hdrs, sizeof(hdrs)/sizeof(hdrs[0]), &h1m, &h1sl);
|
|
if (ret <= 0)
|
|
goto error;
|
|
|
|
if (unlikely(h1sl.st.v.len != 8))
|
|
goto error;
|
|
if ((*(h1sl.st.v.ptr + 5) > '1') ||
|
|
((*(h1sl.st.v.ptr + 5) == '1') && (*(h1sl.st.v.ptr + 7) >= '1')))
|
|
h1m.flags |= H1_MF_VER_11;
|
|
|
|
if (h1sl.st.status < 200 && (h1sl.st.status == 100 || h1sl.st.status >= 102))
|
|
goto error;
|
|
|
|
if (h1m.flags & H1_MF_VER_11)
|
|
flags |= HTX_SL_F_VER_11;
|
|
if (h1m.flags & H1_MF_XFER_ENC)
|
|
flags |= HTX_SL_F_XFER_ENC;
|
|
if (h1m.flags & H1_MF_XFER_LEN) {
|
|
flags |= HTX_SL_F_XFER_LEN;
|
|
if (h1m.flags & H1_MF_CHNK)
|
|
goto error; /* Unsupported because there is no body parsing */
|
|
else if (h1m.flags & H1_MF_CLEN) {
|
|
flags |= HTX_SL_F_CLEN;
|
|
if (h1m.body_len == 0)
|
|
flags |= HTX_SL_F_BODYLESS;
|
|
}
|
|
}
|
|
|
|
htx = htx_from_buf(buf);
|
|
sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, h1sl.st.v, h1sl.st.c, h1sl.st.r);
|
|
if (!sl || !htx_add_all_headers(htx, hdrs))
|
|
goto error;
|
|
sl->info.res.status = h1sl.st.status;
|
|
|
|
while (raw.len > ret) {
|
|
int sent = htx_add_data(htx, ist2(raw.ptr + ret, raw.len - ret));
|
|
if (!sent)
|
|
goto error;
|
|
ret += sent;
|
|
}
|
|
|
|
if (!htx_add_endof(htx, HTX_BLK_EOM))
|
|
goto error;
|
|
|
|
return htx;
|
|
|
|
error:
|
|
if (buf->size)
|
|
free(buf->area);
|
|
return NULL;
|
|
}
|
|
|
|
static int http_htx_init(void)
|
|
{
|
|
struct proxy *px;
|
|
struct buffer chk;
|
|
struct ist raw;
|
|
int rc;
|
|
int err_code = 0;
|
|
|
|
for (px = proxies_list; px; px = px->next) {
|
|
if (px->mode != PR_MODE_HTTP || !(px->options2 & PR_O2_USE_HTX))
|
|
continue;
|
|
|
|
for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
|
|
if (!b_data(&px->errmsg[rc]))
|
|
continue;
|
|
|
|
raw = ist2(b_head(&px->errmsg[rc]), b_data(&px->errmsg[rc]));
|
|
if (!http_str_to_htx(&chk, raw)) {
|
|
ha_alert("config: %s '%s': Unable to convert message in HTX for HTTP return code %d.\n",
|
|
proxy_type_str(px), px->id, http_err_codes[rc]);
|
|
err_code |= ERR_ALERT | ERR_FATAL;
|
|
}
|
|
chunk_destroy(&px->errmsg[rc]);
|
|
px->errmsg[rc] = chk;
|
|
}
|
|
}
|
|
|
|
for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
|
|
if (!http_err_msgs[rc]) {
|
|
ha_alert("Internal error: no message defined for HTTP return code %d", rc);
|
|
err_code |= ERR_ALERT | ERR_FATAL;
|
|
continue;
|
|
}
|
|
|
|
raw = ist2(http_err_msgs[rc], strlen(http_err_msgs[rc]));
|
|
if (!http_str_to_htx(&chk, raw)) {
|
|
ha_alert("Internal error: Unable to convert message in HTX for HTTP return code %d.\n",
|
|
http_err_codes[rc]);
|
|
err_code |= ERR_ALERT | ERR_FATAL;
|
|
}
|
|
htx_err_chunks[rc] = chk;
|
|
}
|
|
end:
|
|
return err_code;
|
|
}
|
|
|
|
REGISTER_CONFIG_POSTPARSER("http_htx", http_htx_init);
|