xref: /lighttpd1.4/src/mod_proxy.c (revision f2e4e83a)
1 #include "first.h"
2 
3 #include <string.h>
4 #include <stdlib.h>
5 
6 #include "gw_backend.h"
7 #include "base.h"
8 #include "array.h"
9 #include "buffer.h"
10 #include "fdevent.h"
11 #include "http_kv.h"
12 #include "http_header.h"
13 #include "log.h"
14 #include "sock_addr.h"
15 #include "status_counter.h"
16 
17 /**
18  *
19  * HTTP reverse proxy
20  *
21  * TODO:      - HTTP/1.1
22  *            - HTTP/1.1 persistent connection with upstream servers
23  */
24 
25 /* (future: might split struct and move part to http-header-glue.c) */
26 typedef struct http_header_remap_opts {
27     const array *urlpaths;
28     const array *hosts_request;
29     const array *hosts_response;
30     int https_remap;
31     int upgrade;
32     int connect_method;
33     /*(not used in plugin_config, but used in handler_ctx)*/
34     const buffer *http_host;
35     const buffer *forwarded_host;
36     const data_string *forwarded_urlpath;
37 } http_header_remap_opts;
38 
39 typedef enum {
40 	PROXY_FORWARDED_NONE         = 0x00,
41 	PROXY_FORWARDED_FOR          = 0x01,
42 	PROXY_FORWARDED_PROTO        = 0x02,
43 	PROXY_FORWARDED_HOST         = 0x04,
44 	PROXY_FORWARDED_BY           = 0x08,
45 	PROXY_FORWARDED_REMOTE_USER  = 0x10
46 } proxy_forwarded_t;
47 
48 typedef struct {
49     gw_plugin_config gw; /* start must match layout of gw_plugin_config */
50     unsigned int replace_http_host;
51     unsigned int forwarded;
52     http_header_remap_opts header;
53 } plugin_config;
54 
55 typedef struct {
56     PLUGIN_DATA;
57     pid_t srv_pid; /* must match layout of gw_plugin_data through conf member */
58     plugin_config conf;
59     plugin_config defaults;
60 } plugin_data;
61 
62 static int proxy_check_extforward;
63 static int proxy_force_http10;
64 
65 typedef struct {
66 	gw_handler_ctx gw;
67 	http_response_opts opts;
68 	plugin_config conf;
69 } handler_ctx;
70 
71 
72 INIT_FUNC(mod_proxy_init) {
73     return calloc(1, sizeof(plugin_data));
74 }
75 
76 
77 static void mod_proxy_free_config(plugin_data * const p)
78 {
79     if (NULL == p->cvlist) return;
80     /* (init i to 0 if global context; to 1 to skip empty global context) */
81     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
82         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
83         for (; -1 != cpv->k_id; ++cpv) {
84             switch (cpv->k_id) {
85               case 5: /* proxy.header */
86                 if (cpv->vtype == T_CONFIG_LOCAL) free(cpv->v.v);
87                 break;
88               default:
89                 break;
90             }
91         }
92     }
93 }
94 
95 
96 FREE_FUNC(mod_proxy_free) {
97     plugin_data * const p = p_d;
98     mod_proxy_free_config(p);
99     gw_free(p);
100 }
101 
102 static void mod_proxy_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv)
103 {
104     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
105       case 0: /* proxy.server */
106         if (cpv->vtype == T_CONFIG_LOCAL) {
107             gw_plugin_config * const gw = cpv->v.v;
108             pconf->gw.exts      = gw->exts;
109             pconf->gw.exts_auth = gw->exts_auth;
110             pconf->gw.exts_resp = gw->exts_resp;
111         }
112         break;
113       case 1: /* proxy.balance */
114         /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/
115             pconf->gw.balance = (int)cpv->v.u;
116         break;
117       case 2: /* proxy.debug */
118         pconf->gw.debug = (int)cpv->v.u;
119         break;
120       case 3: /* proxy.map-extensions */
121         pconf->gw.ext_mapping = cpv->v.a;
122         break;
123       case 4: /* proxy.forwarded */
124         /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/
125             pconf->forwarded = cpv->v.u;
126         break;
127       case 5: /* proxy.header */
128         /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/
129         pconf->header = *(http_header_remap_opts *)cpv->v.v; /*(copies struct)*/
130         break;
131       case 6: /* proxy.replace-http-host */
132         pconf->replace_http_host = cpv->v.u;
133         break;
134       default:/* should not happen */
135         return;
136     }
137 }
138 
139 
140 static void mod_proxy_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv)
141 {
142     do {
143         mod_proxy_merge_config_cpv(pconf, cpv);
144     } while ((++cpv)->k_id != -1);
145 }
146 
147 
148 static void mod_proxy_patch_config(request_st * const r, plugin_data * const p)
149 {
150     memcpy(&p->conf, &p->defaults, sizeof(plugin_config));
151     for (int i = 1, used = p->nconfig; i < used; ++i) {
152         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
153             mod_proxy_merge_config(&p->conf, p->cvlist+p->cvlist[i].v.u2[0]);
154     }
155 }
156 
157 
158 static unsigned int mod_proxy_parse_forwarded(server *srv, const array *a)
159 {
160     unsigned int forwarded = 0;
161     for (uint32_t j = 0, used = a->used; j < used; ++j) {
162         proxy_forwarded_t param;
163         data_unset *du = a->data[j];
164         if (buffer_eq_slen(&du->key, CONST_STR_LEN("by")))
165             param = PROXY_FORWARDED_BY;
166         else if (buffer_eq_slen(&du->key, CONST_STR_LEN("for")))
167             param = PROXY_FORWARDED_FOR;
168         else if (buffer_eq_slen(&du->key, CONST_STR_LEN("host")))
169             param = PROXY_FORWARDED_HOST;
170         else if (buffer_eq_slen(&du->key, CONST_STR_LEN("proto")))
171             param = PROXY_FORWARDED_PROTO;
172         else if (buffer_eq_slen(&du->key, CONST_STR_LEN("remote_user")))
173             param = PROXY_FORWARDED_REMOTE_USER;
174         else {
175             log_error(srv->errh, __FILE__, __LINE__,
176               "proxy.forwarded keys must be one of: "
177               "by, for, host, proto, remote_user, but not: %s", du->key.ptr);
178             return UINT_MAX;
179         }
180         if (du->type == TYPE_STRING) {
181             data_string *ds = (data_string *)du;
182             if (buffer_eq_slen(&ds->value, CONST_STR_LEN("enable")))
183                 forwarded |= param;
184             else if (!buffer_eq_slen(&ds->value, CONST_STR_LEN("disable"))) {
185                 log_error(srv->errh, __FILE__, __LINE__,
186                   "proxy.forwarded values must be one of: "
187                   "0, 1, enable, disable; error for key: %s", du->key.ptr);
188                 return UINT_MAX;
189             }
190         }
191         else if (du->type == TYPE_INTEGER) {
192             data_integer *di = (data_integer *)du;
193             if (di->value) forwarded |= param;
194         }
195         else {
196             log_error(srv->errh, __FILE__, __LINE__,
197               "proxy.forwarded values must be one of: "
198               "0, 1, enable, disable; error for key: %s", du->key.ptr);
199             return UINT_MAX;
200         }
201     }
202     return forwarded;
203 }
204 
205 
206 static http_header_remap_opts * mod_proxy_parse_header_opts(server *srv, const array *a)
207 {
208     http_header_remap_opts header;
209     memset(&header, 0, sizeof(header));
210     for (uint32_t j = 0, used = a->used; j < used; ++j) {
211         data_array *da = (data_array *)a->data[j];
212         if (buffer_eq_slen(&da->key, CONST_STR_LEN("https-remap"))) {
213             data_string *ds = (data_string *)da;
214             if (ds->type != TYPE_STRING) {
215                 log_error(srv->errh, __FILE__, __LINE__,
216                   "unexpected value for proxy.header; "
217                   "expected \"enable\" or \"disable\" for https-remap");
218                 return NULL;
219             }
220             header.https_remap =
221               !buffer_eq_slen(&ds->value, CONST_STR_LEN("disable"))
222               && !buffer_eq_slen(&ds->value, CONST_STR_LEN("0"));
223             continue;
224         }
225         else if (buffer_eq_slen(&da->key, CONST_STR_LEN("upgrade"))) {
226             data_string *ds = (data_string *)da;
227             if (ds->type != TYPE_STRING) {
228                 log_error(srv->errh, __FILE__, __LINE__,
229                   "unexpected value for proxy.header; "
230                   "expected \"upgrade\" => \"enable\" or \"disable\"");
231                 return NULL;
232             }
233             header.upgrade =
234               !buffer_eq_slen(&ds->value, CONST_STR_LEN("disable"))
235               && !buffer_eq_slen(&ds->value, CONST_STR_LEN("0"));
236             continue;
237         }
238         else if (buffer_eq_slen(&da->key, CONST_STR_LEN("connect"))) {
239             data_string *ds = (data_string *)da;
240             if (ds->type != TYPE_STRING) {
241                 log_error(srv->errh, __FILE__, __LINE__,
242                   "unexpected value for proxy.header; "
243                   "expected \"connect\" => \"enable\" or \"disable\"");
244                 return NULL;
245             }
246             header.connect_method =
247               !buffer_eq_slen(&ds->value, CONST_STR_LEN("disable"))
248               && !buffer_eq_slen(&ds->value, CONST_STR_LEN("0"));
249             continue;
250         }
251         if (da->type != TYPE_ARRAY || !array_is_kvstring(&da->value)) {
252             log_error(srv->errh, __FILE__, __LINE__,
253               "unexpected value for proxy.header; "
254               "expected ( \"param\" => ( \"key\" => \"value\" ) ) near key %s",
255               da->key.ptr);
256             return NULL;
257         }
258         if (buffer_eq_slen(&da->key, CONST_STR_LEN("map-urlpath"))) {
259             header.urlpaths = &da->value;
260         }
261         else if (buffer_eq_slen(&da->key, CONST_STR_LEN("map-host-request"))) {
262             header.hosts_request = &da->value;
263         }
264         else if (buffer_eq_slen(&da->key, CONST_STR_LEN("map-host-response"))) {
265             header.hosts_response = &da->value;
266         }
267         else {
268             log_error(srv->errh, __FILE__, __LINE__,
269               "unexpected key for proxy.header; "
270               "expected ( \"param\" => ( \"key\" => \"value\" ) ) near key %s",
271               da->key.ptr);
272             return NULL;
273         }
274     }
275 
276     http_header_remap_opts *opts = malloc(sizeof(header));
277     force_assert(opts);
278     memcpy(opts, &header, sizeof(header));
279     return opts;
280 }
281 
282 
283 SETDEFAULTS_FUNC(mod_proxy_set_defaults)
284 {
285     static const config_plugin_keys_t cpk[] = {
286       { CONST_STR_LEN("proxy.server"),
287         T_CONFIG_ARRAY_KVARRAY,
288         T_CONFIG_SCOPE_CONNECTION }
289      ,{ CONST_STR_LEN("proxy.balance"),
290         T_CONFIG_STRING,
291         T_CONFIG_SCOPE_CONNECTION }
292      ,{ CONST_STR_LEN("proxy.debug"),
293         T_CONFIG_INT,
294         T_CONFIG_SCOPE_CONNECTION }
295      ,{ CONST_STR_LEN("proxy.map-extensions"),
296         T_CONFIG_ARRAY_KVSTRING,
297         T_CONFIG_SCOPE_CONNECTION }
298      ,{ CONST_STR_LEN("proxy.forwarded"),
299         T_CONFIG_ARRAY_KVANY,
300         T_CONFIG_SCOPE_CONNECTION }
301      ,{ CONST_STR_LEN("proxy.header"),
302         T_CONFIG_ARRAY_KVANY,
303         T_CONFIG_SCOPE_CONNECTION }
304      ,{ CONST_STR_LEN("proxy.replace-http-host"),
305         T_CONFIG_BOOL,
306         T_CONFIG_SCOPE_CONNECTION }
307      ,{ NULL, 0,
308         T_CONFIG_UNSET,
309         T_CONFIG_SCOPE_UNSET }
310     };
311 
312     plugin_data * const p = p_d;
313     if (!config_plugin_values_init(srv, p, cpk, "mod_proxy"))
314         return HANDLER_ERROR;
315 
316     /* process and validate config directives
317      * (init i to 0 if global context; to 1 to skip empty global context) */
318     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
319         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
320         gw_plugin_config *gw = NULL;
321         for (; -1 != cpv->k_id; ++cpv) {
322             switch (cpv->k_id) {
323               case 0: /* proxy.server */
324                 gw = calloc(1, sizeof(gw_plugin_config));
325                 force_assert(gw);
326                 if (!gw_set_defaults_backend(srv, (gw_plugin_data *)p, cpv->v.a,
327                                              gw, 0, cpk[cpv->k_id].k)) {
328                     gw_plugin_config_free(gw);
329                     return HANDLER_ERROR;
330                 }
331                 /* error if "mode" = "authorizer";
332                  * proxy can not act as authorizer */
333                 /*(check after gw_set_defaults_backend())*/
334                 if (gw->exts_auth && gw->exts_auth->used) {
335                     log_error(srv->errh, __FILE__, __LINE__,
336                       "%s must not define any hosts with "
337                       "attribute \"mode\" = \"authorizer\"", cpk[cpv->k_id].k);
338                     gw_plugin_config_free(gw);
339                     return HANDLER_ERROR;
340                 }
341                 cpv->v.v = gw;
342                 cpv->vtype = T_CONFIG_LOCAL;
343                 break;
344               case 1: /* proxy.balance */
345                 cpv->v.u = (unsigned int)gw_get_defaults_balance(srv, cpv->v.b);
346                 break;
347               case 2: /* proxy.debug */
348               case 3: /* proxy.map-extensions */
349                 break;
350               case 4: /* proxy.forwarded */
351                 cpv->v.u = mod_proxy_parse_forwarded(srv, cpv->v.a);
352                 if (UINT_MAX == cpv->v.u) return HANDLER_ERROR;
353                 cpv->vtype = T_CONFIG_LOCAL;
354                 break;
355               case 5: /* proxy.header */
356                 cpv->v.v = mod_proxy_parse_header_opts(srv, cpv->v.a);
357                 if (NULL == cpv->v.v) return HANDLER_ERROR;
358                 cpv->vtype = T_CONFIG_LOCAL;
359                 break;
360               case 6: /* proxy.replace-http-host */
361                 break;
362               default:/* should not happen */
363                 break;
364             }
365         }
366 
367         /* disable check-local for all exts (default enabled) */
368         if (gw && gw->exts) { /*(check after gw_set_defaults_backend())*/
369             gw_exts_clear_check_local(gw->exts);
370         }
371     }
372 
373     /* default is 0 */
374     /*p->defaults.balance = (unsigned int)gw_get_defaults_balance(srv, NULL);*/
375 
376     /* initialize p->defaults from global config context */
377     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
378         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
379         if (-1 != cpv->k_id)
380             mod_proxy_merge_config(&p->defaults, cpv);
381     }
382 
383     /* special-case behavior if mod_extforward is loaded */
384     for (uint32_t i = 0; i < srv->srvconf.modules->used; ++i) {
385         buffer *m = &((data_string *)srv->srvconf.modules->data[i])->value;
386         if (buffer_eq_slen(m, CONST_STR_LEN("mod_extforward"))) {
387             proxy_check_extforward = 1;
388             break;
389         }
390     }
391 
392     proxy_force_http10 =
393       srv->srvconf.feature_flags
394       && config_plugin_value_tobool(
395            array_get_element_klen(srv->srvconf.feature_flags,
396                                   CONST_STR_LEN("proxy.force-http10")), 0);
397 
398     return HANDLER_GO_ON;
399 }
400 
401 
402 /* (future: might move to http-header-glue.c) */
403 static const buffer * http_header_remap_host_match (buffer *b, size_t off, http_header_remap_opts *remap_hdrs, int is_req, size_t alen)
404 {
405     const array *hosts = is_req
406       ? remap_hdrs->hosts_request
407       : remap_hdrs->hosts_response;
408     if (hosts) {
409         const char * const s = b->ptr+off;
410         for (size_t i = 0, used = hosts->used; i < used; ++i) {
411             const data_string * const ds = (data_string *)hosts->data[i];
412             const buffer *k = &ds->key;
413             size_t mlen = buffer_string_length(k);
414             if (1 == mlen && k->ptr[0] == '-') {
415                 /* match with authority provided in Host (if is_req)
416                  * (If no Host in client request, then matching against empty
417                  *  string will probably not match, and no remap will be
418                  *  performed) */
419                 k = is_req
420                   ? remap_hdrs->http_host
421                   : remap_hdrs->forwarded_host;
422                 if (NULL == k) continue;
423                 mlen = buffer_string_length(k);
424             }
425             if (buffer_eq_icase_ss(s, alen, k->ptr, mlen)) {
426                 if (buffer_is_equal_string(&ds->value, CONST_STR_LEN("-"))) {
427                     return remap_hdrs->http_host;
428                 }
429                 else if (!buffer_string_is_empty(&ds->value)) {
430                     /*(save first matched request host for response match)*/
431                     if (is_req && NULL == remap_hdrs->forwarded_host)
432                         remap_hdrs->forwarded_host = &ds->value;
433                     return &ds->value;
434                 } /*(else leave authority as-is and stop matching)*/
435                 break;
436             }
437         }
438     }
439     return NULL;
440 }
441 
442 
443 /* (future: might move to http-header-glue.c) */
444 static size_t http_header_remap_host (buffer *b, size_t off, http_header_remap_opts *remap_hdrs, int is_req, size_t alen)
445 {
446     const buffer * const m =
447       http_header_remap_host_match(b, off, remap_hdrs, is_req, alen);
448     if (NULL == m) return alen; /*(no match; return original authority length)*/
449 
450     buffer_substr_replace(b, off, alen, m);
451     return buffer_string_length(m); /*(length of replacement authority)*/
452 }
453 
454 
455 /* (future: might move to http-header-glue.c) */
456 static size_t http_header_remap_urlpath (buffer *b, size_t off, http_header_remap_opts *remap_hdrs, int is_req)
457 {
458     const array *urlpaths = remap_hdrs->urlpaths;
459     if (urlpaths) {
460         const char * const s = b->ptr+off;
461         const size_t plen = buffer_string_length(b) - off; /*(urlpath len)*/
462         if (is_req) { /* request */
463             for (size_t i = 0, used = urlpaths->used; i < used; ++i) {
464                 const data_string * const ds = (data_string *)urlpaths->data[i];
465                 const size_t mlen = buffer_string_length(&ds->key);
466                 if (mlen <= plen && 0 == memcmp(s, ds->key.ptr, mlen)) {
467                     if (NULL == remap_hdrs->forwarded_urlpath)
468                         remap_hdrs->forwarded_urlpath = ds;
469                     buffer_substr_replace(b, off, mlen, &ds->value);
470                     return buffer_string_length(&ds->value);/*(replacement len)*/
471                 }
472             }
473         }
474         else {        /* response; perform reverse map */
475             if (NULL != remap_hdrs->forwarded_urlpath) {
476                 const data_string * const ds = remap_hdrs->forwarded_urlpath;
477                 const size_t mlen = buffer_string_length(&ds->value);
478                 if (mlen <= plen && 0 == memcmp(s, ds->value.ptr, mlen)) {
479                     buffer_substr_replace(b, off, mlen, &ds->key);
480                     return buffer_string_length(&ds->key); /*(replacement len)*/
481                 }
482             }
483             for (size_t i = 0, used = urlpaths->used; i < used; ++i) {
484                 const data_string * const ds = (data_string *)urlpaths->data[i];
485                 const size_t mlen = buffer_string_length(&ds->value);
486                 if (mlen <= plen && 0 == memcmp(s, ds->value.ptr, mlen)) {
487                     buffer_substr_replace(b, off, mlen, &ds->key);
488                     return buffer_string_length(&ds->key); /*(replacement len)*/
489                 }
490             }
491         }
492     }
493     return 0;
494 }
495 
496 
497 /* (future: might move to http-header-glue.c) */
498 static void http_header_remap_uri (buffer *b, size_t off, http_header_remap_opts *remap_hdrs, int is_req)
499 {
500     /* find beginning of URL-path (might be preceded by scheme://authority
501      * (caller should make sure any leading whitespace is prior to offset) */
502     if (b->ptr[off] != '/') {
503         char *s = b->ptr+off;
504         size_t alen; /*(authority len (host len))*/
505         size_t slen; /*(scheme len)*/
506         const buffer *m;
507         /* skip over scheme and authority of URI to find beginning of URL-path
508          * (value might conceivably be relative URL-path instead of URI) */
509         if (NULL == (s = strchr(s, ':')) || s[1] != '/' || s[2] != '/') return;
510         slen = s - (b->ptr+off);
511         s += 3;
512         off = (size_t)(s - b->ptr);
513         if (NULL != (s = strchr(s, '/'))) {
514             alen = (size_t)(s - b->ptr) - off;
515             if (0 == alen) return; /*(empty authority, e.g. "http:///")*/
516         }
517         else {
518             alen = buffer_string_length(b) - off;
519             if (0 == alen) return; /*(empty authority, e.g. "http:///")*/
520             buffer_append_string_len(b, CONST_STR_LEN("/"));
521         }
522 
523         /* remap authority (if configured) and set offset to url-path */
524         m = http_header_remap_host_match(b, off, remap_hdrs, is_req, alen);
525         if (NULL != m) {
526             if (remap_hdrs->https_remap
527                 && (is_req ? 5==slen && 0==memcmp(b->ptr+off-slen-3,"https",5)
528                            : 4==slen && 0==memcmp(b->ptr+off-slen-3,"http",4))){
529                 if (is_req) {
530                     memcpy(b->ptr+off-slen-3+4,"://",3);  /*("https"=>"http")*/
531                     --off;
532                     ++alen;
533                 }
534                 else {/*(!is_req)*/
535                     memcpy(b->ptr+off-slen-3+4,"s://",4); /*("http" =>"https")*/
536                     ++off;
537                     --alen;
538                 }
539             }
540             buffer_substr_replace(b, off, alen, m);
541             alen = buffer_string_length(m);/*(length of replacement authority)*/
542         }
543         off += alen;
544     }
545 
546     /* remap URLs (if configured) */
547     http_header_remap_urlpath(b, off, remap_hdrs, is_req);
548 }
549 
550 
551 /* (future: might move to http-header-glue.c) */
552 static void http_header_remap_setcookie (buffer *b, size_t off, http_header_remap_opts *remap_hdrs)
553 {
554     /* Given the special-case of Set-Cookie and the (too) loosely restricted
555      * characters allowed, for best results, the Set-Cookie value should be the
556      * entire string in b from offset to end of string.  In response headers,
557      * lighttpd may concatenate multiple Set-Cookie headers into single entry
558      * in r->resp_headers, separated by "\r\nSet-Cookie: " */
559     for (char *s = b->ptr+off, *e; *s; s = e) {
560         size_t len;
561         {
562             while (*s != ';' && *s != '\n' && *s != '\0') ++s;
563             if (*s == '\n') {
564                 /*(include +1 for '\n', but leave ' ' for ++s below)*/
565                 s += sizeof("Set-Cookie:");
566             }
567             if ('\0' == *s) return;
568             do { ++s; } while (*s == ' ' || *s == '\t');
569             if ('\0' == *s) return;
570             e = s+1;
571             if ('=' == *s) continue;
572             /*(interested only in Domain and Path attributes)*/
573             while (*e != '=' && *e != '\0') ++e;
574             if ('\0' == *e) return;
575             ++e;
576             switch ((int)(e - s - 1)) {
577               case 4:
578                 if (buffer_eq_icase_ssn(s, "path", 4)) {
579                     if (*e == '"') ++e;
580                     if (*e != '/') continue;
581                     off = (size_t)(e - b->ptr);
582                     len = http_header_remap_urlpath(b, off, remap_hdrs, 0);
583                     e = b->ptr+off+len; /*(b may have been reallocated)*/
584                     continue;
585                 }
586                 break;
587               case 6:
588                 if (buffer_eq_icase_ssn(s, "domain", 6)) {
589                     size_t alen = 0;
590                     if (*e == '"') ++e;
591                     if (*e == '.') ++e;
592                     if (*e == ';') continue;
593                     off = (size_t)(e - b->ptr);
594                     for (char c; (c = e[alen]) != ';' && c != ' ' && c != '\t'
595                                           && c != '\r' && c != '\0'; ++alen);
596                     len = http_header_remap_host(b, off, remap_hdrs, 0, alen);
597                     e = b->ptr+off+len; /*(b may have been reallocated)*/
598                     continue;
599                 }
600                 break;
601               default:
602                 break;
603             }
604         }
605     }
606 }
607 
608 
609 static void buffer_append_string_backslash_escaped(buffer *b, const char *s, size_t len) {
610     /* (future: might move to buffer.c) */
611     size_t j = 0;
612     char *p;
613 
614     buffer_string_prepare_append(b, len*2 + 4);
615     p = b->ptr + buffer_string_length(b);
616 
617     for (size_t i = 0; i < len; ++i) {
618         int c = s[i];
619         if (c == '"' || c == '\\' || c == 0x7F || (c < 0x20 && c != '\t'))
620             p[j++] = '\\';
621         p[j++] = c;
622     }
623 
624     buffer_commit(b, j);
625 }
626 
627 static void proxy_set_Forwarded(connection * const con, request_st * const r, const unsigned int flags) {
628     buffer *b = NULL, *efor = NULL, *eproto = NULL, *ehost = NULL;
629     int semicolon = 0;
630 
631     if (proxy_check_extforward) {
632         efor   =
633           http_header_env_get(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_FOR"));
634         eproto =
635           http_header_env_get(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_PROTO"));
636         ehost  =
637           http_header_env_get(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_HOST"));
638     }
639 
640     /* note: set "Forwarded" prior to updating X-Forwarded-For (below) */
641 
642     if (flags)
643         b = http_header_request_get(r, HTTP_HEADER_FORWARDED, CONST_STR_LEN("Forwarded"));
644 
645     if (flags && NULL == b) {
646         const buffer *xff =
647           http_header_request_get(r, HTTP_HEADER_X_FORWARDED_FOR, CONST_STR_LEN("X-Forwarded-For"));
648         http_header_request_set(r, HTTP_HEADER_FORWARDED,
649                                 CONST_STR_LEN("Forwarded"),
650                                 CONST_STR_LEN("x")); /*(must not be blank for _get below)*/
651       #ifdef __COVERITY__
652         force_assert(NULL != b); /*(not NULL because created directly above)*/
653       #endif
654         b = http_header_request_get(r, HTTP_HEADER_FORWARDED, CONST_STR_LEN("Forwarded"));
655         buffer_clear(b);
656         if (NULL != xff) {
657             /* use X-Forwarded-For contents to seed Forwarded */
658             char *s = xff->ptr;
659             size_t used = buffer_string_length(xff);
660             for (size_t i=0, j, ipv6; i < used; ++i) {
661                 while (s[i] == ' ' || s[i] == '\t' || s[i] == ',') ++i;
662                 if (s[i] == '\0') break;
663                 j = i;
664                 do {
665                     ++i;
666                 } while (s[i]!=' ' && s[i]!='\t' && s[i]!=',' && s[i]!='\0');
667                 buffer_append_string_len(b, CONST_STR_LEN("for="));
668                 /* over-simplified test expecting only IPv4 or IPv6 addresses,
669                  * (not expecting :port, so treat existence of colon as IPv6,
670                  *  and not expecting unix paths, especially not containing ':')
671                  * quote all strings, backslash-escape since IPs not validated*/
672                 ipv6 = (NULL != memchr(s+j, ':', i-j)); /*(over-simplified) */
673                 buffer_append_string_len(b, CONST_STR_LEN("\""));
674                 if (ipv6)
675                     buffer_append_string_len(b, CONST_STR_LEN("["));
676                 buffer_append_string_backslash_escaped(b, s+j, i-j);
677                 if (ipv6)
678                     buffer_append_string_len(b, CONST_STR_LEN("]"));
679                 buffer_append_string_len(b, CONST_STR_LEN("\""));
680                 buffer_append_string_len(b, CONST_STR_LEN(", "));
681             }
682         }
683     } else if (flags) { /*(NULL != b)*/
684         buffer_append_string_len(b, CONST_STR_LEN(", "));
685     }
686 
687     if (flags & PROXY_FORWARDED_FOR) {
688         int family = sock_addr_get_family(&con->dst_addr);
689         buffer_append_string_len(b, CONST_STR_LEN("for="));
690         if (NULL != efor) {
691             /* over-simplified test expecting only IPv4 or IPv6 addresses,
692              * (not expecting :port, so treat existence of colon as IPv6,
693              *  and not expecting unix paths, especially not containing ':')
694              * quote all strings and backslash-escape since IPs not validated
695              * (should be IP from original con->dst_addr_buf,
696              *  so trustable and without :port) */
697             int ipv6 = (NULL != strchr(efor->ptr, ':'));
698             buffer_append_string_len(b, CONST_STR_LEN("\""));
699             if (ipv6) buffer_append_string_len(b, CONST_STR_LEN("["));
700             buffer_append_string_backslash_escaped(
701               b, CONST_BUF_LEN(efor));
702             if (ipv6) buffer_append_string_len(b, CONST_STR_LEN("]"));
703             buffer_append_string_len(b, CONST_STR_LEN("\""));
704         } else if (family == AF_INET) {
705             /*(Note: if :port is added, then must be quoted-string:
706              * e.g. for="...:port")*/
707             buffer_append_string_buffer(b, con->dst_addr_buf);
708         } else if (family == AF_INET6) {
709             buffer_append_string_len(b, CONST_STR_LEN("\"["));
710             buffer_append_string_buffer(b, con->dst_addr_buf);
711             buffer_append_string_len(b, CONST_STR_LEN("]\""));
712         } else {
713             buffer_append_string_len(b, CONST_STR_LEN("\""));
714             buffer_append_string_backslash_escaped(
715               b, CONST_BUF_LEN(con->dst_addr_buf));
716             buffer_append_string_len(b, CONST_STR_LEN("\""));
717         }
718         semicolon = 1;
719     }
720 
721     if (flags & PROXY_FORWARDED_BY) {
722         int family = sock_addr_get_family(&con->srv_socket->addr);
723         /* Note: getsockname() and inet_ntop() are expensive operations.
724          * (recommendation: do not to enable by=... unless required)
725          * future: might use con->srv_socket->srv_token if addr is not
726          *   INADDR_ANY or in6addr_any, but must omit optional :port
727          *   from con->srv_socket->srv_token for consistency */
728 
729         if (semicolon) buffer_append_string_len(b, CONST_STR_LEN(";"));
730         buffer_append_string_len(b, CONST_STR_LEN("by="));
731         buffer_append_string_len(b, CONST_STR_LEN("\""));
732       #ifdef HAVE_SYS_UN_H
733         /* special-case: might need to encode unix domain socket path */
734         if (family == AF_UNIX) {
735             buffer_append_string_backslash_escaped(
736               b, CONST_BUF_LEN(con->srv_socket->srv_token));
737         }
738         else
739       #endif
740         {
741             sock_addr addr;
742             socklen_t addrlen = sizeof(addr);
743             if (0 == getsockname(con->fd,(struct sockaddr *)&addr, &addrlen)) {
744                 sock_addr_stringify_append_buffer(b, &addr);
745             }
746         }
747         buffer_append_string_len(b, CONST_STR_LEN("\""));
748         semicolon = 1;
749     }
750 
751     if (flags & PROXY_FORWARDED_PROTO) {
752         /* expecting "http" or "https"
753          * (not checking if quoted-string and encoding needed) */
754         if (semicolon) buffer_append_string_len(b, CONST_STR_LEN(";"));
755         buffer_append_string_len(b, CONST_STR_LEN("proto="));
756         if (NULL != eproto) {
757             buffer_append_string_buffer(b, eproto);
758         } else if (con->srv_socket->is_ssl) {
759             buffer_append_string_len(b, CONST_STR_LEN("https"));
760         } else {
761             buffer_append_string_len(b, CONST_STR_LEN("http"));
762         }
763         semicolon = 1;
764     }
765 
766     if (flags & PROXY_FORWARDED_HOST) {
767         if (NULL != ehost) {
768             if (semicolon)
769                 buffer_append_string_len(b, CONST_STR_LEN(";"));
770             buffer_append_string_len(b, CONST_STR_LEN("host=\""));
771             buffer_append_string_backslash_escaped(
772               b, CONST_BUF_LEN(ehost));
773             buffer_append_string_len(b, CONST_STR_LEN("\""));
774             semicolon = 1;
775         } else if (!buffer_string_is_empty(r->http_host)) {
776             if (semicolon)
777                 buffer_append_string_len(b, CONST_STR_LEN(";"));
778             buffer_append_string_len(b, CONST_STR_LEN("host=\""));
779             buffer_append_string_backslash_escaped(
780               b, CONST_BUF_LEN(r->http_host));
781             buffer_append_string_len(b, CONST_STR_LEN("\""));
782             semicolon = 1;
783         }
784     }
785 
786     if (flags & PROXY_FORWARDED_REMOTE_USER) {
787         const buffer *remote_user =
788           http_header_env_get(r, CONST_STR_LEN("REMOTE_USER"));
789         if (NULL != remote_user) {
790             if (semicolon)
791                 buffer_append_string_len(b, CONST_STR_LEN(";"));
792             buffer_append_string_len(b, CONST_STR_LEN("remote_user=\""));
793             buffer_append_string_backslash_escaped(
794               b, CONST_BUF_LEN(remote_user));
795             buffer_append_string_len(b, CONST_STR_LEN("\""));
796             /*semicolon = 1;*/
797         }
798     }
799 
800     /* legacy X-* headers, including X-Forwarded-For */
801 
802     b = (NULL != efor) ? efor : con->dst_addr_buf;
803     http_header_request_set(r, HTTP_HEADER_X_FORWARDED_FOR,
804                             CONST_STR_LEN("X-Forwarded-For"),
805                             CONST_BUF_LEN(b));
806 
807     b = (NULL != ehost) ? ehost : r->http_host;
808     if (!buffer_string_is_empty(b)) {
809         http_header_request_set(r, HTTP_HEADER_OTHER,
810                                 CONST_STR_LEN("X-Host"),
811                                 CONST_BUF_LEN(b));
812         http_header_request_set(r, HTTP_HEADER_OTHER,
813                                 CONST_STR_LEN("X-Forwarded-Host"),
814                                 CONST_BUF_LEN(b));
815     }
816 
817     b = (NULL != eproto) ? eproto : &r->uri.scheme;
818     http_header_request_set(r, HTTP_HEADER_X_FORWARDED_PROTO,
819                             CONST_STR_LEN("X-Forwarded-Proto"),
820                             CONST_BUF_LEN(b));
821 }
822 
823 
824 static handler_t proxy_stdin_append(gw_handler_ctx *hctx) {
825     /*handler_ctx *hctx = (handler_ctx *)gwhctx;*/
826     chunkqueue * const req_cq = hctx->r->reqbody_queue;
827     const off_t req_cqlen = req_cq->bytes_in - req_cq->bytes_out;
828     if (req_cqlen) {
829         /* XXX: future: use http_chunk_len_append() */
830         buffer * const tb = hctx->r->tmp_buf;
831         buffer_clear(tb);
832         buffer_append_uint_hex_lc(tb, (uintmax_t)req_cqlen);
833         buffer_append_string_len(tb, CONST_STR_LEN("\r\n"));
834 
835         const off_t len = (off_t)buffer_string_length(tb)
836                         + 2 /*(+2 end chunk "\r\n")*/
837                         + req_cqlen;
838         if (-1 != hctx->wb_reqlen)
839             hctx->wb_reqlen += (hctx->wb_reqlen >= 0) ? len : -len;
840 
841         (chunkqueue_is_empty(hctx->wb) || hctx->wb->first->type == MEM_CHUNK)
842                                           /* else FILE_CHUNK for temp file */
843           ? chunkqueue_append_mem(hctx->wb, CONST_BUF_LEN(tb))
844           : chunkqueue_append_mem_min(hctx->wb, CONST_BUF_LEN(tb));
845         chunkqueue_steal(hctx->wb, req_cq, req_cqlen);
846 
847         chunkqueue_append_mem_min(hctx->wb, CONST_STR_LEN("\r\n"));
848     }
849 
850     if (hctx->wb->bytes_in == hctx->wb_reqlen) {/*hctx->r->reqbody_length >= 0*/
851         /* terminate STDIN */
852         chunkqueue_append_mem(hctx->wb, CONST_STR_LEN("0\r\n\r\n"));
853         hctx->wb_reqlen += (int)sizeof("0\r\n\r\n");
854     }
855 
856     return HANDLER_GO_ON;
857 }
858 
859 
860 static handler_t proxy_create_env(gw_handler_ctx *gwhctx) {
861 	handler_ctx *hctx = (handler_ctx *)gwhctx;
862 	request_st * const r = hctx->gw.r;
863 	const int remap_headers = (NULL != hctx->conf.header.urlpaths
864 				   || NULL != hctx->conf.header.hosts_request);
865 	size_t rsz = (size_t)(r->read_queue->bytes_out - hctx->gw.wb->bytes_in);
866 	buffer * const b = chunkqueue_prepend_buffer_open_sz(hctx->gw.wb, rsz < 65536 ? rsz : r->rqst_header_len);
867 
868 	/* build header */
869 
870 	/* request line */
871 	http_method_append(b, r->http_method);
872 	buffer_append_string_len(b, CONST_STR_LEN(" "));
873 	buffer_append_string_buffer(b, &r->target);
874 	if (remap_headers)
875 		http_header_remap_uri(b, buffer_string_length(b) - buffer_string_length(&r->target), &hctx->conf.header, 1);
876 
877 	if (!proxy_force_http10)
878 		buffer_append_string_len(b, CONST_STR_LEN(" HTTP/1.1\r\n"));
879 	else
880 		buffer_append_string_len(b, CONST_STR_LEN(" HTTP/1.0\r\n"));
881 
882 	if (hctx->conf.replace_http_host && !buffer_string_is_empty(hctx->gw.host->id)) {
883 		if (hctx->gw.conf.debug > 1) {
884 			log_error(r->conf.errh, __FILE__, __LINE__,
885 			  "proxy - using \"%s\" as HTTP Host", hctx->gw.host->id->ptr);
886 		}
887 		buffer_append_string_len(b, CONST_STR_LEN("Host: "));
888 		buffer_append_string_buffer(b, hctx->gw.host->id);
889 		buffer_append_string_len(b, CONST_STR_LEN("\r\n"));
890 	} else if (!buffer_string_is_empty(r->http_host)) {
891 		buffer_append_string_len(b, CONST_STR_LEN("Host: "));
892 		buffer_append_string_buffer(b, r->http_host);
893 		if (remap_headers) {
894 			size_t alen = buffer_string_length(r->http_host);
895 			http_header_remap_host(b, buffer_string_length(b) - alen, &hctx->conf.header, 1, alen);
896 		}
897 		buffer_append_string_len(b, CONST_STR_LEN("\r\n"));
898 	}
899 
900 	/* "Forwarded" and legacy X- headers */
901 	proxy_set_Forwarded(r->con, r, hctx->conf.forwarded);
902 
903 	if (r->reqbody_length > 0
904 	    || (0 == r->reqbody_length
905 		&& !http_method_get_or_head(r->http_method))) {
906 		/* set Content-Length if client sent Transfer-Encoding: chunked
907 		 * and not streaming to backend (request body has been fully received) */
908 		const buffer *vb = http_header_request_get(r, HTTP_HEADER_CONTENT_LENGTH, CONST_STR_LEN("Content-Length"));
909 		if (NULL == vb) {
910 			char buf[LI_ITOSTRING_LENGTH];
911 			http_header_request_set(r, HTTP_HEADER_CONTENT_LENGTH, CONST_STR_LEN("Content-Length"),
912 			                        buf, li_itostrn(buf, sizeof(buf), r->reqbody_length));
913 		}
914 	}
915 	else if (!proxy_force_http10
916 	         && -1 == r->reqbody_length
917 	         && (r->conf.stream_request_body
918 	             & (FDEVENT_STREAM_REQUEST | FDEVENT_STREAM_REQUEST_BUFMIN))) {
919 		hctx->gw.stdin_append = proxy_stdin_append; /* stream chunked body */
920 		buffer_append_string_len(b, CONST_STR_LEN("Transfer-Encoding: chunked\r\n"));
921 	}
922 
923 	/* request header */
924 	const buffer *connhdr = NULL;
925 	buffer *te = NULL;
926 	buffer *upgrade = NULL;
927 	for (size_t i = 0, used = r->rqst_headers.used; i < used; ++i) {
928 		data_string *ds = (data_string *)r->rqst_headers.data[i];
929 		const size_t klen = buffer_string_length(&ds->key);
930 		size_t vlen;
931 		switch (klen) {
932 		default:
933 			break;
934 		case 2:
935 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("TE"))) {
936 				if (proxy_force_http10 || r->http_version == HTTP_VERSION_1_0) continue;
937 				/* ignore if not exactly "trailers" */
938 				if (!buffer_eq_icase_slen(&ds->value, CONST_STR_LEN("trailers"))) continue;
939 				te = &ds->value;
940 			}
941 			break;
942 		case 4:
943 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("Host"))) continue; /*(handled further above)*/
944 			break;
945 		case 7:
946 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("Upgrade"))) {
947 				if (proxy_force_http10 || r->http_version == HTTP_VERSION_1_0) continue;
948 				if (!hctx->conf.header.upgrade) continue;
949 				upgrade = &ds->value;
950 			}
951 			break;
952 		case 10:
953 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("Connection"))) { connhdr = &ds->value; continue; }
954 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("Set-Cookie"))) continue; /*(response header only; avoid accidental reflection)*/
955 			break;
956 		case 16:
957 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("Proxy-Connection"))) continue;
958 			break;
959 		case 5:
960 			/* Do not emit HTTP_PROXY in environment.
961 			 * Some executables use HTTP_PROXY to configure
962 			 * outgoing proxy.  See also https://httpoxy.org/ */
963 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("Proxy"))) continue;
964 			break;
965 		case 6:
966 			/* Do not forward Expect: 100-continue
967 			 * since we do not handle "HTTP/1.1 100 Continue" response */
968 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("Expect"))) continue;
969 			break;
970 		case 0:
971 			continue;
972 		}
973 
974 		vlen = buffer_string_length(&ds->value);
975 		if (0 == vlen) continue;
976 
977 		if (buffer_string_space(b) < klen + vlen + 4) {
978 			size_t extend = b->size * 2 - buffer_string_length(b);
979 			extend = extend > klen + vlen + 4 ? extend : klen + vlen + 4 + 4095;
980 			buffer_string_prepare_append(b, extend);
981 		}
982 
983 		buffer_append_string_len(b, ds->key.ptr, klen);
984 		buffer_append_string_len(b, CONST_STR_LEN(": "));
985 		buffer_append_string_len(b, ds->value.ptr, vlen);
986 		buffer_append_string_len(b, CONST_STR_LEN("\r\n"));
987 
988 		if (!remap_headers) continue;
989 
990 		/* check for hdrs for which to remap URIs in-place after append to b */
991 
992 		switch (klen) {
993 		default:
994 			continue;
995 	      #if 0 /* "URI" is HTTP response header (non-standard; historical in Apache) */
996 		case 3:
997 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("URI"))) break;
998 			continue;
999 	      #endif
1000 	      #if 0 /* "Location" is HTTP response header */
1001 		case 8:
1002 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("Location"))) break;
1003 			continue;
1004 	      #endif
1005 		case 11: /* "Destination" is WebDAV request header */
1006 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("Destination"))) break;
1007 			continue;
1008 		case 16: /* "Content-Location" may be HTTP request or response header */
1009 			if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("Content-Location"))) break;
1010 			continue;
1011 		}
1012 
1013 		http_header_remap_uri(b, buffer_string_length(b) - vlen - 2, &hctx->conf.header, 1);
1014 	}
1015 
1016 	if (connhdr && !proxy_force_http10 && r->http_version >= HTTP_VERSION_1_1
1017 	    && !buffer_eq_icase_slen(connhdr, CONST_STR_LEN("close"))) {
1018 		/* mod_proxy always sends Connection: close to backend */
1019 		buffer_append_string_len(b, CONST_STR_LEN("Connection: close"));
1020 		/* (future: might be pedantic and also check Connection header for each
1021 		 * token using http_header_str_contains_token() */
1022 		if (!buffer_string_is_empty(te))
1023 			buffer_append_string_len(b, CONST_STR_LEN(", te"));
1024 		if (!buffer_string_is_empty(upgrade))
1025 			buffer_append_string_len(b, CONST_STR_LEN(", upgrade"));
1026 		buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n"));
1027 	}
1028 	else    /* mod_proxy always sends Connection: close to backend */
1029 		buffer_append_string_len(b, CONST_STR_LEN("Connection: close\r\n\r\n"));
1030 
1031 	hctx->gw.wb_reqlen = buffer_string_length(b);
1032 	chunkqueue_prepend_buffer_commit(hctx->gw.wb);
1033 
1034 	if (r->reqbody_length) {
1035 		chunkqueue_append_chunkqueue(hctx->gw.wb, r->reqbody_queue);
1036 		if (r->reqbody_length > 0)
1037 			hctx->gw.wb_reqlen += r->reqbody_length; /* total req size */
1038 		else /* as-yet-unknown total request size (Transfer-Encoding: chunked)*/
1039 			hctx->gw.wb_reqlen = -hctx->gw.wb_reqlen;
1040 	}
1041 
1042 	status_counter_inc(CONST_STR_LEN("proxy.requests"));
1043 	return HANDLER_GO_ON;
1044 }
1045 
1046 
1047 static handler_t proxy_create_env_connect(gw_handler_ctx *gwhctx) {
1048 	handler_ctx *hctx = (handler_ctx *)gwhctx;
1049 	request_st * const r = hctx->gw.r;
1050 	r->http_status = 200; /* OK */
1051 	r->resp_body_started = 1;
1052 	gw_set_transparent(&hctx->gw);
1053 	http_response_upgrade_read_body_unknown(r);
1054 
1055 	status_counter_inc(CONST_STR_LEN("proxy.requests"));
1056 	return HANDLER_GO_ON;
1057 }
1058 
1059 
1060 static handler_t proxy_response_headers(request_st * const r, struct http_response_opts_t *opts) {
1061     /* response headers just completed */
1062     handler_ctx *hctx = (handler_ctx *)opts->pdata;
1063 
1064     if (r->resp_htags & HTTP_HEADER_UPGRADE) {
1065         if (hctx->conf.header.upgrade && r->http_status == 101) {
1066             /* 101 Switching Protocols; transition to transparent proxy */
1067             gw_set_transparent(&hctx->gw);
1068             http_response_upgrade_read_body_unknown(r);
1069         }
1070         else {
1071             r->resp_htags &= ~HTTP_HEADER_UPGRADE;
1072           #if 0
1073             /* preserve prior questionable behavior; likely broken behavior
1074              * anyway if backend thinks connection is being upgraded but client
1075              * does not receive Connection: upgrade */
1076             http_header_response_unset(r, HTTP_HEADER_UPGRADE,
1077                                        CONST_STR_LEN("Upgrade"))
1078           #endif
1079         }
1080     }
1081 
1082     /* rewrite paths, if needed */
1083 
1084     if (NULL == hctx->conf.header.urlpaths
1085         && NULL == hctx->conf.header.hosts_response)
1086         return HANDLER_GO_ON;
1087 
1088     if (r->resp_htags & HTTP_HEADER_LOCATION) {
1089         buffer *vb = http_header_response_get(r, HTTP_HEADER_LOCATION, CONST_STR_LEN("Location"));
1090         if (vb) http_header_remap_uri(vb, 0, &hctx->conf.header, 0);
1091     }
1092     if (r->resp_htags & HTTP_HEADER_CONTENT_LOCATION) {
1093         buffer *vb = http_header_response_get(r, HTTP_HEADER_CONTENT_LOCATION, CONST_STR_LEN("Content-Location"));
1094         if (vb) http_header_remap_uri(vb, 0, &hctx->conf.header, 0);
1095     }
1096     if (r->resp_htags & HTTP_HEADER_SET_COOKIE) {
1097         buffer *vb = http_header_response_get(r, HTTP_HEADER_SET_COOKIE, CONST_STR_LEN("Set-Cookie"));
1098         if (vb) http_header_remap_setcookie(vb, 0, &hctx->conf.header);
1099     }
1100 
1101     return HANDLER_GO_ON;
1102 }
1103 
1104 static handler_t mod_proxy_check_extension(request_st * const r, void *p_d) {
1105 	plugin_data *p = p_d;
1106 	handler_t rc;
1107 
1108 	if (NULL != r->handler_module) return HANDLER_GO_ON;
1109 
1110 	mod_proxy_patch_config(r, p);
1111 	if (NULL == p->conf.gw.exts) return HANDLER_GO_ON;
1112 
1113 	rc = gw_check_extension(r, (gw_plugin_data *)p, 1, sizeof(handler_ctx));
1114 	if (HANDLER_GO_ON != rc) return rc;
1115 
1116 	if (r->handler_module == p->self) {
1117 		handler_ctx *hctx = r->plugin_ctx[p->id];
1118 		hctx->gw.create_env = proxy_create_env;
1119 		hctx->gw.response = chunk_buffer_acquire();
1120 		hctx->gw.opts.backend = BACKEND_PROXY;
1121 		hctx->gw.opts.pdata = hctx;
1122 		hctx->gw.opts.headers = proxy_response_headers;
1123 
1124 		hctx->conf = p->conf; /*(copies struct)*/
1125 		hctx->conf.header.http_host = r->http_host;
1126 		hctx->conf.header.upgrade  &= (r->http_version == HTTP_VERSION_1_1);
1127 		/* mod_proxy currently sends all backend requests as http.
1128 		 * https-remap is a flag since it might not be needed if backend
1129 		 * honors Forwarded or X-Forwarded-Proto headers, e.g. by using
1130 		 * lighttpd mod_extforward or similar functionality in backend*/
1131 		if (hctx->conf.header.https_remap) {
1132 			hctx->conf.header.https_remap =
1133 			  buffer_is_equal_string(&r->uri.scheme, CONST_STR_LEN("https"));
1134 		}
1135 
1136 		if (r->http_method == HTTP_METHOD_CONNECT) {
1137 			/*(note: not requiring HTTP/1.1 due to too many non-compliant
1138 			 * clients such as 'openssl s_client')*/
1139 			if (hctx->conf.header.connect_method) {
1140 				hctx->gw.create_env = proxy_create_env_connect;
1141 			}
1142 			else {
1143 				r->http_status = 405; /* Method Not Allowed */
1144 				r->handler_module = NULL;
1145 				return HANDLER_FINISHED;
1146 			}
1147 		}
1148 	}
1149 
1150 	return HANDLER_GO_ON;
1151 }
1152 
1153 
1154 int mod_proxy_plugin_init(plugin *p);
1155 int mod_proxy_plugin_init(plugin *p) {
1156 	p->version      = LIGHTTPD_VERSION_ID;
1157 	p->name         = "proxy";
1158 
1159 	p->init         = mod_proxy_init;
1160 	p->cleanup      = mod_proxy_free;
1161 	p->set_defaults = mod_proxy_set_defaults;
1162 	p->handle_request_reset    = gw_handle_request_reset;
1163 	p->handle_uri_clean        = mod_proxy_check_extension;
1164 	p->handle_subrequest       = gw_handle_subrequest;
1165 	p->handle_trigger          = gw_handle_trigger;
1166 	p->handle_waitpid          = gw_handle_waitpid_cb;
1167 
1168 	return 0;
1169 }
1170