lighttpd 1.4.x https://www.lighttpd.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1736 lines
63 KiB

#include "first.h"
#include "base.h"
#include "log.h"
#include "buffer.h"
#include "http_header.h"
#include "request.h"
#include "sock_addr.h"
#include "plugin.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "sys-socket.h"
/**
* mod_extforward.c for lighttpd, by comman.kang <at> gmail <dot> com
* extended, modified by Lionel Elie Mamane (LEM), lionel <at> mamane <dot> lu
* support chained proxies by glen@delfi.ee, #1528
*
*
* Mostly rewritten
* Portions:
* Copyright(c) 2017 Glenn Strauss gstrauss()gluelogic.com All rights reserved
* License: BSD 3-clause (same as lighttpd)
*
* Config example:
*
* Trust proxy 10.0.0.232 and 10.0.0.232
* extforward.forwarder = ( "10.0.0.232" => "trust",
* "10.0.0.233" => "trust" )
*
* Trust all proxies (NOT RECOMMENDED!)
* extforward.forwarder = ( "all" => "trust")
*
* Note that "all" has precedence over specific entries,
* so "all except" setups will not work.
*
* In case you have chained proxies, you can add all their IP's to the
* config. However "all" has effect only on connecting IP, as the
* X-Forwarded-For header can not be trusted.
*
* Note: The effect of this module is variable on $HTTP["remotip"] directives and
* other module's remote ip dependent actions.
* Things done by modules before we change the remoteip or after we reset it will match on the proxy's IP.
* Things done in between these two moments will match on the real client's IP.
* The moment things are done by a module depends on in which hook it does things and within the same hook
* on whether they are before/after us in the module loading order
* (order in the server.modules directive in the config file).
*
* Tested behaviours:
*
* mod_access: Will match on the real client.
*
* mod_accesslog:
* In order to see the "real" ip address in access log ,
* you'll have to load mod_extforward after mod_accesslog.
* like this:
*
* server.modules = (
* .....
* mod_accesslog,
* mod_extforward
* )
*/
typedef enum {
PROXY_FORWARDED_NONE = 0x00,
PROXY_FORWARDED_FOR = 0x01,
PROXY_FORWARDED_PROTO = 0x02,
PROXY_FORWARDED_HOST = 0x04,
PROXY_FORWARDED_BY = 0x08,
PROXY_FORWARDED_REMOTE_USER = 0x10
} proxy_forwarded_t;
struct sock_addr_mask {
sock_addr addr;
int bits;
};
struct forwarder_cfg {
const array *forwarder;
int forward_all;
uint32_t addrs_used;
#if defined(__STDC_VERSION__) && __STDC_VERSION__-0 >= 199901L /* C99 */
struct sock_addr_mask addrs[];
#else
struct sock_addr_mask addrs[1];
#endif
};
typedef struct {
const array *forwarder;
int forward_all;
uint32_t forward_masks_used;
const struct sock_addr_mask *forward_masks;
const array *headers;
unsigned int opts;
char hap_PROXY;
char hap_PROXY_ssl_client_verify;
} plugin_config;
typedef struct {
PLUGIN_DATA;
plugin_config defaults;
plugin_config conf;
array *default_headers;
} plugin_data;
static plugin_data *mod_extforward_plugin_data_singleton;
static int extforward_check_proxy;
/* context , used for restore remote ip */
typedef struct {
/* per-request state */
sock_addr saved_remote_addr;
buffer *saved_remote_addr_buf;
/* hap-PROXY protocol prior to receiving first request */
int(*saved_network_read)(connection *, chunkqueue *, off_t);
/* connection-level state applied to requests in handle_request_env */
array *env;
int ssl_client_verify;
uint32_t request_count;
} handler_ctx;
static handler_ctx * handler_ctx_init(void) {
handler_ctx * hctx;
hctx = calloc(1, sizeof(*hctx));
force_assert(hctx);
return hctx;
}
static void handler_ctx_free(handler_ctx *hctx) {
free(hctx);
}
INIT_FUNC(mod_extforward_init) {
return calloc(1, sizeof(plugin_data));
}
FREE_FUNC(mod_extforward_free) {
plugin_data * const p = p_d;
array_free(p->default_headers);
if (NULL == p->cvlist) return;
/* (init i to 0 if global context; to 1 to skip empty global context) */
for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
for (; -1 != cpv->k_id; ++cpv) {
switch (cpv->k_id) {
case 0: /* extforward.forwarder */
if (cpv->vtype == T_CONFIG_LOCAL) free(cpv->v.v);
break;
default:
break;
}
}
}
}
static void mod_extforward_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
case 0: /* extforward.forwarder */
if (cpv->vtype == T_CONFIG_LOCAL) {
const struct forwarder_cfg * const fwd = cpv->v.v;
pconf->forwarder = fwd->forwarder;
pconf->forward_all = fwd->forward_all;
pconf->forward_masks_used = fwd->addrs_used;
pconf->forward_masks = fwd->addrs;
}
break;
case 1: /* extforward.headers */
pconf->headers = cpv->v.a;
break;
case 2: /* extforward.params */
if (cpv->vtype == T_CONFIG_LOCAL)
pconf->opts = cpv->v.u;
break;
case 3: /* extforward.hap-PROXY */
pconf->hap_PROXY = (char)cpv->v.u;
break;
case 4: /* extforward.hap-PROXY-ssl-client-verify */
pconf->hap_PROXY_ssl_client_verify = (char)cpv->v.u;
break;
default:/* should not happen */
return;
}
}
static void mod_extforward_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
do {
mod_extforward_merge_config_cpv(pconf, cpv);
} while ((++cpv)->k_id != -1);
}
static void mod_extforward_patch_config(request_st * const r, plugin_data * const p) {
memcpy(&p->conf, &p->defaults, sizeof(plugin_config));
for (int i = 1, used = p->nconfig; i < used; ++i) {
if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
mod_extforward_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
}
}
static void * mod_extforward_parse_forwarder(server *srv, const array *forwarder) {
const data_string * const allds = (const data_string *)
array_get_element_klen(forwarder, CONST_STR_LEN("all"));
const int forward_all = (NULL == allds)
? 0
: buffer_eq_icase_slen(&allds->value, CONST_STR_LEN("trust")) ? 1 : -1;
uint32_t nmasks = 0;
for (uint32_t j = 0; j < forwarder->used; ++j) {
data_string * const ds = (data_string *)forwarder->data[j];
char * const nm_slash = strchr(ds->key.ptr, '/');
if (NULL != nm_slash) ++nmasks;
if (!buffer_eq_icase_slen(&ds->value, CONST_STR_LEN("trust"))) {
if (!buffer_eq_icase_slen(&ds->value, CONST_STR_LEN("untrusted")))
log_error(srv->errh, __FILE__, __LINE__,
"ERROR: expect \"trust\", not \"%s\" => \"%s\"; "
"treating as untrusted", ds->key.ptr, ds->value.ptr);
if (NULL != nm_slash) {
/* future: consider adding member next to bits in sock_addr_mask
* with bool trusted/untrusted member */
--nmasks;
log_error(srv->errh, __FILE__, __LINE__,
"ERROR: untrusted CIDR masks are ignored (\"%s\" => \"%s\")",
ds->key.ptr, ds->value.ptr);
}
buffer_clear(&ds->value); /* empty is untrusted */
continue;
}
}
struct forwarder_cfg * const fwd =
malloc(sizeof(struct forwarder_cfg)+sizeof(struct sock_addr_mask)*nmasks);
force_assert(fwd);
memset(fwd, 0,
sizeof(struct forwarder_cfg) + sizeof(struct sock_addr_mask)*nmasks);
fwd->forwarder = forwarder;
fwd->forward_all = forward_all;
fwd->addrs_used = 0;
for (uint32_t j = 0; j < forwarder->used; ++j) {
data_string * const ds = (data_string *)forwarder->data[j];
char * const nm_slash = strchr(ds->key.ptr, '/');
if (NULL == nm_slash) continue;
if (buffer_string_is_empty(&ds->value)) continue; /* ignored */
char *err;
const int nm_bits = strtol(nm_slash + 1, &err, 10);
int rc;
if (*err || nm_bits <= 0 || !light_isdigit(nm_slash[1])) {
log_error(srv->errh, __FILE__, __LINE__,
"ERROR: invalid netmask: %s %s", ds->key.ptr, err);
free(fwd);
return NULL;
}
struct sock_addr_mask * const sm = fwd->addrs + fwd->addrs_used++;
sm->bits = nm_bits;
*nm_slash = '\0';
rc = sock_addr_from_str_numeric(&sm->addr, ds->key.ptr, srv->errh);
*nm_slash = '/';
if (1 != rc) {
free(fwd);
return NULL;
}
buffer_clear(&ds->value);
/* empty is untrusted,
* e.g. if subnet (incorrectly) appears in X-Forwarded-For */
}
return fwd;
}
static unsigned int mod_extforward_parse_opts(server *srv, const array *opts_params) {
unsigned int opts = 0;
for (uint32_t j = 0, used = opts_params->used; j < used; ++j) {
proxy_forwarded_t param;
data_unset *du = opts_params->data[j];
#if 0 /*("for" and "proto" historical behavior: always enabled)*/
if (buffer_eq_slen(&du->key, CONST_STR_LEN("by")))
param = PROXY_FORWARDED_BY;
else if (buffer_eq_slen(&du->key, CONST_STR_LEN("for")))
param = PROXY_FORWARDED_FOR;
else
#endif
if (buffer_eq_slen(&du->key, CONST_STR_LEN("host")))
param = PROXY_FORWARDED_HOST;
#if 0
else if (buffer_eq_slen(&du->key, CONST_STR_LEN("proto")))
param = PROXY_FORWARDED_PROTO;
#endif
else if (buffer_eq_slen(&du->key, CONST_STR_LEN("remote_user")))
param = PROXY_FORWARDED_REMOTE_USER;
else {
log_error(srv->errh, __FILE__, __LINE__,
"extforward.params keys must be one of: "
"host, remote_user, but not: %s", du->key.ptr);
return UINT_MAX;
}
int val = config_plugin_value_tobool(du, 2);
if (2 == val) {
log_error(srv->errh, __FILE__, __LINE__,
"extforward.params values must be one of: "
"0, 1, enable, disable; error for key: %s", du->key.ptr);
return UINT_MAX;
}
if (val)
opts |= param;
}
return opts;
}
SETDEFAULTS_FUNC(mod_extforward_set_defaults) {
static const config_plugin_keys_t cpk[] = {
{ CONST_STR_LEN("extforward.forwarder"),
T_CONFIG_ARRAY_KVSTRING,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("extforward.headers"),
T_CONFIG_ARRAY_VLIST,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("extforward.params"),
T_CONFIG_ARRAY_KVANY,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("extforward.hap-PROXY"),
T_CONFIG_BOOL,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("extforward.hap-PROXY-ssl-client-verify"),
T_CONFIG_BOOL,
T_CONFIG_SCOPE_CONNECTION }
,{ NULL, 0,
T_CONFIG_UNSET,
T_CONFIG_SCOPE_UNSET }
};
plugin_data * const p = p_d;
if (!config_plugin_values_init(srv, p, cpk, "mod_extforward"))
return HANDLER_ERROR;
int hap_PROXY = 0;
/* process and validate config directives
* (init i to 0 if global context; to 1 to skip empty global context) */
for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
for (; -1 != cpv->k_id; ++cpv) {
switch (cpv->k_id) {
case 0: /* extforward.forwarder */
cpv->v.v = mod_extforward_parse_forwarder(srv, cpv->v.a);
if (NULL == cpv->v.v) {
log_error(srv->errh, __FILE__, __LINE__,
"unexpected value for %s", cpk[cpv->k_id].k);
return HANDLER_ERROR;
}
cpv->vtype = T_CONFIG_LOCAL;
break;
case 1: /* extforward.headers */
if (cpv->v.a->used) {
array *a;
*(const array **)&a = cpv->v.a;
for (uint32_t j = 0; j < a->used; ++j) {
data_string * const ds = (data_string *)a->data[j];
ds->ext =
http_header_hkey_get(CONST_BUF_LEN(&ds->value));
}
}
break;
case 2: /* extforward.params */
cpv->v.u = mod_extforward_parse_opts(srv, cpv->v.a);
if (UINT_MAX == cpv->v.u)
return HANDLER_ERROR;
break;
case 3: /* extforward.hap-PROXY */
if (cpv->v.u) hap_PROXY = 1;
break;
case 4: /* extforward.hap-PROXY-ssl-client-verify */
break;
default:/* should not happen */
break;
}
}
}
mod_extforward_plugin_data_singleton = p;
p->defaults.opts = PROXY_FORWARDED_NONE;
/* initialize p->defaults from global config context */
if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
if (-1 != cpv->k_id)
mod_extforward_merge_config(&p->defaults, cpv);
}
/* default to "X-Forwarded-For" or "Forwarded-For" if extforward.headers
* is not specified or is empty (and not using hap_PROXY) */
if (!p->defaults.hap_PROXY
&& (NULL == p->defaults.headers || 0 == p->defaults.headers->used)) {
p->defaults.headers = p->default_headers = array_init(2);
array_insert_value(p->default_headers,CONST_STR_LEN("X-Forwarded-For"));
array_insert_value(p->default_headers,CONST_STR_LEN("Forwarded-For"));
for (uint32_t i = 0; i < p->default_headers->used; ++i) {
data_string * const ds = (data_string *)p->default_headers->data[i];
ds->ext = http_header_hkey_get(CONST_BUF_LEN(&ds->value));
}
}
/* attempt to warn if mod_extforward is not last module loaded to hook
* handle_connection_accept. (Nice to have, but remove this check if
* it reaches too far into internals and prevents other code changes.)
* While it would be nice to check handle_connection_accept plugin slot
* to make sure mod_extforward is last, that info is private to plugin.c
* so merely warn if mod_openssl is loaded after mod_extforward, though
* future modules which hook handle_connection_accept might be missed.*/
if (hap_PROXY) {
uint32_t i;
for (i = 0; i < srv->srvconf.modules->used; ++i) {
data_string *ds = (data_string *)srv->srvconf.modules->data[i];
if (buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_extforward")))
break;
}
for (; i < srv->srvconf.modules->used; ++i) {
data_string *ds = (data_string *)srv->srvconf.modules->data[i];
if (buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_openssl"))
|| buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_mbedtls"))
|| buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_wolfssl"))
|| buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_nss"))
|| buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_gnutls"))) {
log_error(srv->errh, __FILE__, __LINE__,
"mod_extforward must be loaded after %s in "
"server.modules when extforward.hap-PROXY = \"enable\"",
ds->value.ptr);
break;
}
}
}
for (uint32_t i = 0; i < srv->srvconf.modules->used; ++i) {
data_string *ds = (data_string *)srv->srvconf.modules->data[i];
if (buffer_is_equal_string(&ds->value, CONST_STR_LEN("mod_proxy"))) {
extforward_check_proxy = 1;
break;
}
}
return HANDLER_GO_ON;
}
/*
extract a forward array from the environment
*/
static array *extract_forward_array(const buffer *pbuffer)
{
array *result = array_init(8);
if (!buffer_string_is_empty(pbuffer)) {
const char *base, *curr;
/* state variable, 0 means not in string, 1 means in string */
int in_str = 0;
for (base = pbuffer->ptr, curr = pbuffer->ptr; *curr; curr++) {
int hex_or_colon = (light_isxdigit(*curr) || *curr == ':');
if (in_str) {
if (!hex_or_colon && *curr != '.') {
/* found an separator , insert value into result array */
array_insert_value(result, base, curr - base);
/* change state to not in string */
in_str = 0;
}
} else {
if (hex_or_colon) {
/* found leading char of an IP address, move base pointer and change state */
base = curr;
in_str = 1;
}
}
}
/* if breaking out while in str, we got to the end of string, so add it */
if (in_str) {
array_insert_value(result, base, curr - base);
}
}
return result;
}
/*
* check whether ip is trusted, return 1 for trusted , 0 for untrusted
*/
static int is_proxy_trusted(plugin_data *p, const char * const ip, size_t iplen)
{
const data_string *ds =
(const data_string *)array_get_element_klen(p->conf.forwarder, ip, iplen);
if (NULL != ds) return !buffer_string_is_empty(&ds->value);
if (p->conf.forward_masks_used) {
const struct sock_addr_mask * const addrs = p->conf.forward_masks;
const uint32_t aused = p->conf.forward_masks_used;
sock_addr addr;
/* C funcs inet_aton(), inet_pton() require '\0'-terminated IP str */
char addrstr[64]; /*(larger than INET_ADDRSTRLEN and INET6_ADDRSTRLEN)*/
if (0 == iplen || iplen >= sizeof(addrstr)) return 0;
memcpy(addrstr, ip, iplen);
addrstr[iplen] = '\0';
if (1 != sock_addr_inet_pton(&addr, addrstr, AF_INET, 0)
&& 1 != sock_addr_inet_pton(&addr, addrstr, AF_INET6, 0)) return 0;
for (uint32_t i = 0; i < aused; ++i) {
if (sock_addr_is_addr_eq_bits(&addr, &addrs[i].addr, addrs[i].bits))
return 1;
}
}
return 0;
}
static int is_connection_trusted(connection * const con, plugin_data *p)
{
if (p->conf.forward_all) return (1 == p->conf.forward_all);
return is_proxy_trusted(p, CONST_BUF_LEN(con->dst_addr_buf));
}
/*
* Return last address of proxy that is not trusted.
* Do not accept "all" keyword here.
*/
static const char *last_not_in_array(array *a, plugin_data *p)
{
int i;
for (i = a->used - 1; i >= 0; i--) {
data_string *ds = (data_string *)a->data[i];
if (!is_proxy_trusted(p, CONST_BUF_LEN(&ds->value))) {
return ds->value.ptr;
}
}
return NULL;
}
static int mod_extforward_set_addr(request_st * const r, plugin_data *p, const char *addr) {
connection * const con = r->con;
sock_addr sock;
handler_ctx *hctx = con->plugin_ctx[p->id];
/* Preserve changed addr for lifetime of h2 connection; upstream proxy
* should not reuse same h2 connection for requests from different clients*/
if (hctx && NULL != hctx->saved_remote_addr_buf
&& r->http_version > HTTP_VERSION_1_1) { /*(e.g. HTTP_VERSION_2)*/
if (extforward_check_proxy) /* save old address */
http_header_env_set(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_FOR"),
CONST_BUF_LEN(hctx->saved_remote_addr_buf));
return 1;
}
if (r->conf.log_request_handling) {
log_error(r->conf.errh, __FILE__, __LINE__, "using address: %s", addr);
}
sock.plain.sa_family = AF_UNSPEC;
if (1 != sock_addr_from_str_numeric(&sock, addr, r->conf.errh)) return 0;
if (sock.plain.sa_family == AF_UNSPEC) return 0;
/* we found the remote address, modify current connection and save the old address */
if (hctx) {
if (hctx->saved_remote_addr_buf) {
if (r->conf.log_request_handling) {
log_error(r->conf.errh, __FILE__, __LINE__,
"-- mod_extforward_uri_handler already patched this connection, resetting state");
}
con->dst_addr = hctx->saved_remote_addr;
buffer_free(con->dst_addr_buf);
con->dst_addr_buf = hctx->saved_remote_addr_buf;
hctx->saved_remote_addr_buf = NULL;
}
} else {
con->plugin_ctx[p->id] = hctx = handler_ctx_init();
}
/* save old address */
if (extforward_check_proxy) {
http_header_env_set(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_FOR"), CONST_BUF_LEN(con->dst_addr_buf));
}
hctx->request_count = con->request_count;
hctx->saved_remote_addr = con->dst_addr;
hctx->saved_remote_addr_buf = con->dst_addr_buf;
/* patch connection address */
con->dst_addr = sock;
con->dst_addr_buf = buffer_init_string(addr);
if (r->conf.log_request_handling) {
log_error(r->conf.errh, __FILE__, __LINE__,
"patching con->dst_addr_buf for the accesslog: %s", addr);
}
/* Now, clean the conf_cond cache, because we may have changed the results of tests */
config_cond_cache_reset_item(r, COMP_HTTP_REMOTE_IP);
return 1;
}
static void mod_extforward_set_proto(request_st * const r, const char * const proto, size_t protolen) {
if (0 != protolen && !buffer_is_equal_caseless_string(&r->uri.scheme, proto, protolen)) {
/* update scheme if X-Forwarded-Proto is set
* Limitations:
* - Only "http" or "https" are currently accepted since the request to lighttpd currently has to
* be HTTP/1.0 or HTTP/1.1 using http or https. If this is changed, then the scheme from this
* untrusted header must be checked to contain only alphanumeric characters, and to be a
* reasonable length, e.g. < 256 chars.
* - r->uri.scheme is not reset in mod_extforward_restore() but is currently not an issues since
* r->uri.scheme will be reset by next request. If a new module uses r->uri.scheme in the
* handle_request_done hook, then should evaluate if that module should use the forwarded value
* (probably) or the original value.
*/
if (extforward_check_proxy) {
http_header_env_set(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_PROTO"), CONST_BUF_LEN(&r->uri.scheme));
}
if (buffer_eq_icase_ss(proto, protolen, CONST_STR_LEN("https"))) {
r->con->proto_default_port = 443; /* "https" */
buffer_copy_string_len(&r->uri.scheme, CONST_STR_LEN("https"));
config_cond_cache_reset_item(r, COMP_HTTP_SCHEME);
} else if (buffer_eq_icase_ss(proto, protolen, CONST_STR_LEN("http"))) {
r->con->proto_default_port = 80; /* "http" */
buffer_copy_string_len(&r->uri.scheme, CONST_STR_LEN("http"));
config_cond_cache_reset_item(r, COMP_HTTP_SCHEME);
}
}
}
static handler_t mod_extforward_X_Forwarded_For(request_st * const r, plugin_data * const p, const buffer * const x_forwarded_for) {
/* build forward_array from forwarded data_string */
array *forward_array = extract_forward_array(x_forwarded_for);
const char *real_remote_addr = last_not_in_array(forward_array, p);
if (real_remote_addr != NULL) { /* parsed */
/* get scheme if X-Forwarded-Proto is set
* Limitations:
* - X-Forwarded-Proto may or may not be set by proxies, even if X-Forwarded-For is set
* - X-Forwarded-Proto may be a comma-separated list if there are multiple proxies,
* but the historical behavior of the code below only honored it if there was exactly one value
* (not done: walking backwards in X-Forwarded-Proto the same num of steps
* as in X-Forwarded-For to find proto set by last trusted proxy)
*/
const buffer *x_forwarded_proto = http_header_request_get(r, HTTP_HEADER_X_FORWARDED_PROTO, CONST_STR_LEN("X-Forwarded-Proto"));
if (mod_extforward_set_addr(r, p, real_remote_addr) && NULL != x_forwarded_proto) {
mod_extforward_set_proto(r, CONST_BUF_LEN(x_forwarded_proto));
}
}
array_free(forward_array);
return HANDLER_GO_ON;
}
static int find_end_quoted_string (const char * const s, int i) {
do {
++i;
} while (s[i] != '"' && s[i] != '\0' && (s[i] != '\\' || s[++i] != '\0'));
return i;
}
static int find_next_semicolon_or_comma_or_eq (const char * const s, int i) {
for (; s[i] != '=' && s[i] != ';' && s[i] != ',' && s[i] != '\0'; ++i) {
if (s[i] == '"') {
i = find_end_quoted_string(s, i);
if (s[i] == '\0') return -1;
}
}
return i;
}
static int find_next_semicolon_or_comma (const char * const s, int i) {
for (; s[i] != ';' && s[i] != ',' && s[i] != '\0'; ++i) {
if (s[i] == '"') {
i = find_end_quoted_string(s, i);
if (s[i] == '\0') return -1;
}
}
return i;
}
static int buffer_backslash_unescape (buffer * const b) {
/* (future: might move to buffer.c) */
size_t j = 0;
size_t len = buffer_string_length(b);
char *p = memchr(b->ptr, '\\', len);
if (NULL == p) return 1; /*(nothing to do)*/
len -= (size_t)(p - b->ptr);
for (size_t i = 0; i < len; ++i) {
if (p[i] == '\\') {
if (++i == len) return 0; /*(invalid trailing backslash)*/
}
p[j++] = p[i];
}
buffer_string_set_length(b, (size_t)(p+j - b->ptr));
return 1;
}