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