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