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