Reverse-Proxy

 view release on metacpan or  search on metacpan

Proxy.xs  view on Meta::CPAN

                    (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) {

Proxy.xs  view on Meta::CPAN

        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 )