Reverse-Proxy
view release on metacpan or search on metacpan
(void)hv_store(conn_hop, lc, (I32)tn, &PL_sv_yes, 0);
if (lc != buf) Safefree(lc);
}
}
s = t + 1;
}
}
/* forward each HTTP_* header (skip Host, hop-by-hop, X-Forwarded-*) */
hv_iterinit(env);
while ((he = hv_iternext(env))) {
I32 kl;
const char *k = hv_iterkey(he, &kl);
char namebuf[128], lcbuf[128];
char *name = namebuf, *lc = lcbuf;
STRLEN nl;
SV *val;
STRLEN i;
if (kl < 6 || memNE(k, "HTTP_", 5)) continue;
nl = (STRLEN)(kl - 5);
if (nl > sizeof(namebuf)) { Newx(name, nl, char); Newx(lc, nl, char); }
for (i = 0; i < nl; i++) {
char c = k[5 + i];
if (c == '_') c = '-';
name[i] = c;
lc[i] = (char)toLOWER((U8)c);
}
do {
if (nl == 4 && memEQ(lc, "host", 4)) break; /* handled below */
if (rp_is_hop(lc, nl) || rp_is_xfwd(lc, nl)) break;
if (hv_exists(conn_hop, lc, (I32)nl)) break;
/* titlecase name in place: Upper after start/'-', lower else */
{
int at_start = 1; STRLEN j;
for (j = 0; j < nl; j++) {
if (name[j] == '-') { at_start = 1; }
else { name[j] = at_start ? (char)toUPPER((U8)name[j])
: (char)toLOWER((U8)name[j]);
at_start = 0; }
}
}
val = hv_iterval(env, he);
{
STRLEN vl; const char *vp = SvPV_const(val, vl);
rp_push(aTHX_ hav, name, nl, vp, vl);
}
} while (0);
if (name != namebuf) { Safefree(name); Safefree(lc); }
}
/* Content-Type (Content-Length is added by Fetch from the body) */
p = rp_env(aTHX_ env, "CONTENT_TYPE", 12, &l);
if (p) rp_push(aTHX_ hav, "Content-Type", 12, p, l);
/* Host, only when preserving it */
if (preserve_host) {
p = rp_env(aTHX_ env, "HTTP_HOST", 9, &l);
if (p) rp_push(aTHX_ hav, "Host", 4, p, l);
}
/* X-Forwarded-For: chain REMOTE_ADDR onto any existing value */
{
STRLEN cl; const char *client = rp_env(aTHX_ env, "REMOTE_ADDR", 11, &cl);
if (client) {
STRLEN xl; const char *xff = rp_env(aTHX_ env, "HTTP_X_FORWARDED_FOR", 20, &xl);
SV *v = newSVpvs("");
if (xff) { sv_catpvn(v, xff, xl); sv_catpvs(v, ", "); }
sv_catpvn(v, client, cl);
av_push(hav, newSVpvs("X-Forwarded-For"));
av_push(hav, v);
}
}
/* X-Forwarded-Proto: existing, else psgi.url_scheme, else http */
{
STRLEN pl; const char *proto = rp_env(aTHX_ env, "HTTP_X_FORWARDED_PROTO", 22, &pl);
if (!proto) proto = rp_env(aTHX_ env, "psgi.url_scheme", 15, &pl);
av_push(hav, newSVpvs("X-Forwarded-Proto"));
av_push(hav, proto ? newSVpvn(proto, pl) : newSVpvs("http"));
}
/* X-Forwarded-Host: existing, else client Host */
{
STRLEN hl; const char *xfh = rp_env(aTHX_ env, "HTTP_X_FORWARDED_HOST", 21, &hl);
if (!xfh) xfh = rp_env(aTHX_ env, "HTTP_HOST", 9, &hl);
if (xfh) rp_push(aTHX_ hav, "X-Forwarded-Host", 16, xfh, hl);
}
/* Via */
if (via && SvOK(via)) {
STRLEN vl; const char *vp = SvPV_const(via, vl);
if (vl) rp_push(aTHX_ hav, "Via", 3, vp, vl);
}
return hav;
}
/* ---- $self field helpers ------------------------------------------------ */
static SV *rp_self(pTHX_ HV *self, const char *k, STRLEN kl) {
SV **e = hv_fetch(self, k, (I32)kl, 0);
return (e && *e) ? *e : NULL;
}
static int rp_self_bool(pTHX_ HV *self, const char *k, STRLEN kl) {
SV *v = rp_self(aTHX_ self, k, kl);
return (v && SvTRUE(v)) ? 1 : 0;
}
/* case-insensitive: does the comma/space token list contain `tok`? */
static int rp_ci_token(const char *hay, STRLEN hl, const char *tok, STRLEN tl) {
STRLEN i = 0;
while (i < hl) {
STRLEN s, e, j;
while (i < hl && (hay[i] == ',' || isSPACE((U8)hay[i]))) i++;
s = i;
while (i < hl && hay[i] != ',') i++;
e = i;
while (e > s && isSPACE((U8)hay[e - 1])) e--;
if (e - s == tl) {
for (j = 0; j < tl; j++)
if (toLOWER((U8)hay[s + j]) != (U8)tok[j]) break;
if (j == tl) return 1;
}
}
return 0;
}
/* this request asks for an HTTP Upgrade (WebSocket et al) */
static int rp_is_upgrade(pTHX_ HV *env) {
ssize_t w = write(fd, b + off, n - off);
if (w < 0) { if (errno == EINTR) continue; return -1; }
if (w == 0) return -1;
off += (size_t)w;
}
return 0;
}
/* Serialize the raw Upgrade request: client headers verbatim (Upgrade/
* Connection/Sec-WebSocket-* survive), Host adjusted, X-Forwarded-* added. */
static SV *rp_raw_request(pTHX_ HV *env, SV *path, const char *host, int port,
int preserve, SV *via) {
SV *req = newSVpvs("");
SV **mp = hv_fetchs(env, "REQUEST_METHOD", 0);
STRLEN ml; HE *he;
char pb[16];
if (mp && *mp && SvOK(*mp)) { const char *m = SvPV_const(*mp, ml); sv_catpvn(req, m, ml); }
else sv_catpvs(req, "GET");
sv_catpvs(req, " ");
rp_cat_target(aTHX_ req, path, env); /* encoded: see the note */
sv_catpvs(req, " HTTP/1.1\r\nHost: ");
{
STRLEN hl; const char *hh = preserve ? rp_env(aTHX_ env, "HTTP_HOST", 9, &hl) : NULL;
/* the client's own Host, reflected into a request line we serialize:
* stop at the first control byte so a bare LF cannot smuggle either */
if (hh) { STRLEN i = 0;
while (i < hl && (unsigned char)hh[i] > ' '
&& (unsigned char)hh[i] != 0x7f) i++;
sv_catpvn(req, hh, i); }
else {
sv_catpv(req, host);
if (port != 80 && port != 443)
{ sv_catpvs(req, ":"); sv_catpvn(req, pb, (STRLEN)my_snprintf(pb, sizeof pb, "%d", port)); }
}
}
sv_catpvs(req, "\r\n");
hv_iterinit(env);
while ((he = hv_iternext(env))) {
I32 kl; const char *k = hv_iterkey(he, &kl);
char nb[128], lb[128]; char *name = nb, *lc = lb; STRLEN nl, i;
if (kl < 6 || memNE(k, "HTTP_", 5)) continue;
nl = (STRLEN)(kl - 5);
if (nl > sizeof(nb)) { Newx(name, nl, char); Newx(lc, nl, char); }
for (i = 0; i < nl; i++) { char c = k[5 + i]; if (c == '_') c = '-';
name[i] = c; lc[i] = (char)toLOWER((U8)c); }
if (!((nl == 4 && memEQ(lc, "host", 4)) || rp_is_xfwd(lc, nl))) {
int at = 1; STRLEN j; SV *val;
for (j = 0; j < nl; j++) { if (name[j] == '-') at = 1;
else { name[j] = at ? (char)toUPPER((U8)name[j]) : (char)toLOWER((U8)name[j]); at = 0; } }
val = hv_iterval(env, he);
{ STRLEN vl; const char *vp = SvPV_const(val, vl);
sv_catpvn(req, name, nl); sv_catpvs(req, ": "); sv_catpvn(req, vp, vl);
sv_catpvs(req, "\r\n"); }
}
if (name != nb) { Safefree(name); Safefree(lc); }
}
{
STRLEN cl; const char *client = rp_env(aTHX_ env, "REMOTE_ADDR", 11, &cl);
if (client) {
STRLEN xl; const char *xff = rp_env(aTHX_ env, "HTTP_X_FORWARDED_FOR", 20, &xl);
sv_catpvs(req, "X-Forwarded-For: ");
if (xff) { sv_catpvn(req, xff, xl); sv_catpvs(req, ", "); }
sv_catpvn(req, client, cl); sv_catpvs(req, "\r\n");
}
}
{
STRLEN pl2; const char *proto = rp_env(aTHX_ env, "HTTP_X_FORWARDED_PROTO", 22, &pl2);
if (!proto) proto = rp_env(aTHX_ env, "psgi.url_scheme", 15, &pl2);
sv_catpvs(req, "X-Forwarded-Proto: ");
if (proto) sv_catpvn(req, proto, pl2); else sv_catpvs(req, "http");
sv_catpvs(req, "\r\n");
}
if (via && SvOK(via)) { STRLEN vl; const char *vp = SvPV_const(via, vl);
if (vl) { sv_catpvs(req, "Via: "); sv_catpvn(req, vp, vl); sv_catpvs(req, "\r\n"); } }
sv_catpvs(req, "\r\n");
return req;
}
#endif /* !WIN32 */
/* Hijack psgix.io, connect to the upstream, relay the Upgrade handshake, then
* splice bytes both ways until either side closes. Returns a PSGI triplet
* (200 after a handled hijack; 501/502 on error). */
static SV *rp_tunnel(pTHX_ HV *self, HV *env, SV *base, SV *path) {
#ifdef WIN32
PERL_UNUSED_ARG(self); PERL_UNUSED_ARG(env);
PERL_UNUSED_ARG(base); PERL_UNUSED_ARG(path);
return rp_plain(aTHX_ 501, "Upgrade tunnel unsupported on this platform", 43);
#else
SV **iop = hv_fetchs(env, "psgix.io", 0);
SV *io = (iop && *iop && SvOK(*iop)) ? *iop : NULL;
SV *via = rp_self(aTHX_ self, "via", 3);
int preserve = rp_self_bool(aTHX_ self, "preserve_host", 13);
int verify = rp_self_bool(aTHX_ self, "tls_verify", 10);
int cfd = -1, ufd, port, tls, seen = 0, fl;
char host[256], buf[65536];
STRLEN bl; const char *bp;
void *uc; /* Fetch-owned upstream connection (plain or TLS) */
SV *req, *resp; ssize_t n;
if (!io) return rp_plain(aTHX_ 501, "Upgrade requires a server that provides psgix.io", 48);
{ IO *iio = sv_2io(io); if (iio && IoIFP(iio)) cfd = PerlIO_fileno(IoIFP(iio)); }
if (cfd < 0) return rp_plain(aTHX_ 501, "psgix.io has no file descriptor", 31);
bp = SvPV_const(base, bl);
if (!rp_parse_base(bp, bl, host, sizeof host, &port, &tls))
return rp_plain(aTHX_ 502, "cannot parse upstream base", 26);
/* connect through Fetch's ABI: it does the TCP connect and, for a
* wss/https upstream, the TLS handshake with Fetch's own OpenSSL. */
uc = FETCH->tunnel_connect(host, port, tls, verify);
if (!uc) return rp_plain(aTHX_ 502, "cannot connect to upstream", 26);
ufd = FETCH->tunnel_fd(uc);
/* client socket may be non-blocking (Hyperman); make it blocking to splice */
fl = fcntl(cfd, F_GETFL, 0);
if (fl != -1) fcntl(cfd, F_SETFL, fl & ~O_NONBLOCK);
req = sv_2mortal(rp_raw_request(aTHX_ env, path, host, port, preserve, via));
{ STRLEN rl; const char *rb = SvPV_const(req, rl);
if (FETCH->tunnel_write_all(uc, rb, (IV)rl) < 0) { FETCH->tunnel_close(uc);
return rp_plain(aTHX_ 502, "write to upstream failed", 24); } }
( run in 0.988 second using v1.01-cache-2.11-cpan-54e63673c56 )