proxy: the line for a parent put back in the buffer, req only logged

A request to a parent HTTP proxy went as two writes, the line as the
client sent it from req and then the rest of the headers from buf,
held together with TCP_CORK, which does not help over TLS nor with
headers bigger than a segment. The line now goes back to absolute form
where it is: a request's header block is read HDRFRONT (300) bytes
into clibuf, room for http://, a host name of 256 characters and a
port; the method moves down into it and the scheme and authority go
in after it, and line and headers are sent in one piece. Where they
do not fit, or the headers are in a buffer of their own by then, both
are put together in one. TCP_CORK is gone.

The scheme and authority of a request are kept for that (auth), from
its line or, for a request with a path only, its Host header; the last
one is what the next request on the connection is compared with. A
CONNECT line is kept whole in buf. The request filters are given the
line in absolute form in a buffer of their own, made only when there
are some, as req was. req is the copy logged and nothing else: at most
REQMAX (6 KB) of the line, "..." after what is cut, never a reason to
refuse the request.

The headers this proxy adds are built in a buffer on the stack, of
bounded parts, and put after the header block once there is room for
them, in a standalone proxy too, which never asked for it. HDRRESERVE
is 1 KB.

Tests: through a parent a POST arrives with its line in absolute form,
its headers and body whole; user and password in the URL; headers in a
buffer of their own (a data filter) put together with the line; a 7 KB
request line served whole and logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-19 00:14:35 +03:00
parent aee69bf679
commit 96adc6d9cd
2 changed files with 291 additions and 97 deletions

View File

@ -137,7 +137,19 @@ char * proxy_stringtable[] = {
/* what the headers this proxy adds of its own can come to: a Forwarded or
Via with a host name in it, a Connection, a Proxy-support and a
Proxy-Authorization carrying an encoded user and password */
#define HDRRESERVE 2048
/* what of a request line is logged, into a line of CLIENTTMPBUFSIZE (8 KB)
with the rest of the log line; a longer one is cut there */
#define REQMAX 6144
/* the authority of a request, user:password@host:port, at most */
#define AUTHMAX (USERNAMELEN * 2 + HOSTNAMELEN + 16)
#define HDRRESERVE 1024
/* what a request keeps free before its header block, for its line to be
put back into the absolute form a parent proxy is sent: http://, a host
name of up to 256 characters and :port */
#define HDRFRONT 300
/* what the headers this proxy adds come to at most, built before they are
put after the header block */
#define HDRADD 2048
#define BUFSIZE (LINESIZE*2)
#define FTPBUFSIZE 1536
@ -223,10 +235,13 @@ static int hdrgrow(struct clientparam *param, DIRECTION which, unsigned want){
never reach the next one. After it HDRRESERVE is left free, for the
headers added, before whatever came after it, a body or another
request, from where the side is read on. *room is what may be written
from *buf. Returns the length of the block, 0 if nothing came, -1 if
the block is longer than maxsize, -2 for want of memory. If the end of
the stream comes first, what came is the block. */
static int hdrread(struct clientparam *param, DIRECTION which, unsigned maxsize, int firstto, int to, unsigned char **buf, int *room){
from *buf. Before it front bytes are kept free, *front of them, fewer
than asked for only if what was read before the block has to stay: the
request line may be made longer there. Returns the length of the
block, 0 if nothing came, -1 if the block is longer than maxsize, -2 for
want of memory. If the end of the stream comes first, what came is the
block. */
static int hdrread(struct clientparam *param, DIRECTION which, unsigned front, unsigned maxsize, int firstto, int to, unsigned char **buf, int *room, int *freefront){
unsigned char **bufp = which? &param->srvbuf : &param->clibuf;
unsigned *in = which? &param->srvinbuf : &param->cliinbuf;
unsigned *off = which? &param->srvoffset : &param->clioffset;
@ -244,12 +259,13 @@ static int hdrread(struct clientparam *param, DIRECTION which, unsigned maxsize,
mark = (int)(*off - (unsigned)param->climark);
lo = (unsigned)param->climark;
}
if(*in - lo + 1 > *size && hdrgrow(param, which, *in - lo + 1 + LINESIZE)) return -2;
if(*in - lo + front + 1 > *size && hdrgrow(param, which, *in - lo + front + 1 + LINESIZE)) return -2;
b = *bufp;
memmove(b + 1, b + lo, *in - lo);
*in = *in - lo + 1;
start = 1 + (mark >= 0? (unsigned)mark : 0);
if(mark >= 0) param->climark = 1;
/* the regions overlap: memmove */
memmove(b + front + 1, b + lo, *in - lo);
*in = *in - lo + front + 1;
start = front + 1 + (mark >= 0? (unsigned)mark : 0);
if(mark >= 0) param->climark = (int)front + 1;
*off = start;
pos = start;
for(;;){
@ -299,6 +315,9 @@ found:
*off = hend + HDRRESERVE;
*buf = b + start - 1;
*room = (int)(hend + HDRRESERVE - (start - 1));
/* all before *buf is free but for a request kept from its mark, which
lies right before it */
*freefront = (mark >= 0)? 0 : (int)(start - 1);
return (int)(hend - start);
}
@ -328,6 +347,37 @@ static uint64_t fillwhole(struct clientparam *param, DIRECTION which, uint64_t l
}
#endif
/* A copy of a request line for the log: REQMAX of it at most, "..." after
what is cut off. */
static unsigned char *logline(const unsigned char *line, size_t len){
unsigned char *p;
int cut = len > REQMAX;
if(cut) len = REQMAX;
if(!(p = malloc(len + 4))) return NULL;
memcpy(p, line, len);
if(cut){
memcpy(p + len, "...", 3);
len += 3;
}
p[len] = 0;
return p;
}
/* The request line in absolute form into dst: of line, which has a path
only, its method and the space after it (sp bytes), then scheme and
authority, then the rest of it (len bytes in all). dst holds len +
strlen(scheme) + authlen bytes and does not overlap line. */
static int absline(unsigned char *dst, const unsigned char *line, int len, int sp, const char *scheme, const unsigned char *auth, int authlen){
int sl = (int)strlen(scheme);
memcpy(dst, line, sp);
memcpy(dst + sp, scheme, sl);
memcpy(dst + sp + sl, auth, authlen);
memcpy(dst + sp + sl + authlen, line + sp, len - sp);
return len + sl + authlen;
}
/* Whether a request line names the host the previous one did, whatever its
method: the connection to the server is kept for the same host only, and
a POST after a GET to it is no reason to open another one. */
@ -463,7 +513,13 @@ void * proxychild(struct clientparam* param) {
int clhdrofs_req = -1;
int isconnect = 0;
int redirect = 0;
int prefix = 0, hostofs = 0, ckeepalive=0;
int ckeepalive=0;
/* the request's scheme and authority, as its line had them (or the Host
header, for a request with a path only): put back for a parent proxy;
the authority of the last one is what the next one is compared with */
unsigned char auth[AUTHMAX];
int authlen = 0;
const char *scheme = "http://";
int ftp = 0;
int ssoff = 0;
#ifndef ANONYMOUS
@ -472,7 +528,7 @@ void * proxychild(struct clientparam* param) {
int sleeptime = 0;
#ifndef WITHMAIN
int reqsize, reqbufsize;
unsigned char *origreq = NULL;
unsigned char *origreq = NULL, *fline = NULL;
#endif
int authenticate;
struct pollfd fds[2];
@ -483,6 +539,11 @@ void * proxychild(struct clientparam* param) {
/* the request's header block, read whole: where its next line is, its end */
unsigned char *hsrc, *hend;
int hlen0, hdrsdone = 0;
/* free before buf in the side's buffer, while buf is in it */
int hfront = 0;
/* the headers this proxy adds, built before they go after the block */
char add[HDRADD];
int alen;
/* buf is the headers' own buffer, not the side's they were read into */
int bufown = 0;
#ifndef WITHMAIN
@ -530,7 +591,7 @@ for(;;){
}
}
hlen0 = hdrread(param, CLIENT, LINESIZE * 16, param->srv->timeouts[STRING_L], param->srv->timeouts[STRING_S], &buf, &bufsize);
hlen0 = hdrread(param, CLIENT, HDRFRONT, LINESIZE * 16, param->srv->timeouts[STRING_L], param->srv->timeouts[STRING_S], &buf, &bufsize, &hfront);
if(hlen0 == -1) {RETURN(516);}
if(hlen0 == -2) {RETURN(21);}
if(hlen0 <= 0) {RETURN((keepalive)?555:508);}
@ -544,7 +605,7 @@ for(;;){
buf[i] = 0;
if(req) {
if(!param->transparent && !param->srv->transparent && !samehost(buf, i, req + hostofs, prefix - hostofs)){
if(!param->transparent && !param->srv->transparent && !samehost(buf, i, auth, authlen)){
ckeepalive = 0;
if(param->remsock != INVALID_SOCKET){
param->srv->so._shutdown(param->sostate, param->remsock, SHUT_RDWR);
@ -559,8 +620,11 @@ for(;;){
}
free(req);
}
req = (unsigned char *)strdup((char *)buf);
/* for the log only */
req = logline(buf, (size_t)i);
if(!req){RETURN(510);}
authlen = 0;
scheme = "http://";
if(i<10) {
RETURN(511);
}
@ -579,6 +643,7 @@ for(;;){
else if (!strncasecmp((char *)sb, "ftp://", 6)) {
if(!param->srv->allowftp) RETURN(513);
ftp = 1;
scheme = "ftp://";
sb += 6;
}
#endif
@ -601,8 +666,10 @@ for(;;){
if(se > sg) se=sg;
*se = 0;
}
prefix = (int)(se - buf);
hostofs = (int)(sb - buf);
/* the authority, before the line is made one with a path only */
if(se - sb >= AUTHMAX) RETURN(100);
authlen = (int)(se - sb);
memcpy(auth, sb, authlen);
su = (unsigned char*)strrchr((char *)sb, '@');
if(su) {
su = (unsigned char *)strdup((char *)sb);
@ -619,6 +686,8 @@ for(;;){
*se = '/';
memmove(ss, se, i - (se - buf) + 1);
}
/* a CONNECT line stays as it came, for a parent proxy */
else *se = ' ';
}
reqlen = i = (int)strlen((char *)buf);
{
@ -704,15 +773,26 @@ for(;;){
if(!*param->hostname){
if(parsehostname((char *)sb, param, 80)) RETURN(100);
}
newbuf = malloc(strlen((char *)req) + strlen((char *)(buf+inbuf)) + 8);
sp = (unsigned char *)strchr((char *)req+1, '/');
if(newbuf && sp){
memcpy(newbuf, req, (sp - req));
sprintf((char*)newbuf + (sp - req), "http://%s%s",sb,sp);
free(req);
req = newbuf;
/* the authority a parent proxy is given, and the line logged, in
absolute form: method, http://, the host, the rest of the line */
if(strlen((char *)sb) >= AUTHMAX) RETURN(100);
authlen = (int)strlen((char *)sb);
memcpy(auth, sb, authlen);
{
size_t ll = (size_t)reqlen + 7 + (size_t)authlen;
unsigned char *l = malloc(ll + 1);
if(l){
memcpy(l, buf, ssoff);
memcpy(l + ssoff, "http://", 7);
memcpy(l + ssoff + 7, auth, authlen);
memcpy(l + ssoff + 7 + authlen, buf + ssoff, reqlen - ssoff);
l[ll] = 0;
freeptr(&req);
req = logline(l, ll);
free(l);
}
}
else if(newbuf) free(newbuf);
if(se)*se = c;
}
if(ftp && i > 13 && (!strncasecmp((char *)(buf+inbuf), "authorization", 13))){
@ -769,10 +849,6 @@ for(;;){
buf[inbuf] = 0;
#ifndef WITHMAIN
reqsize = (int)strlen((char *)req);
reqbufsize = reqsize + 1;
#endif
if(param->srv->needuser > 1 && !*param->username) {RETURN(4);}
@ -790,36 +866,56 @@ for(;;){
#ifndef WITHMAIN
/* Only worth keeping a copy when something can rewrite it. */
if(param->nreqfilters) origreq = (unsigned char *)strdup((char *)req);
action = handlereqfilters(param, &req, &reqbufsize, 0, &reqsize);
if(action == HANDLED){
freeptr(&origreq);
RETURN(0);
}
if(action != PASS){
freeptr(&origreq);
RETURN(517);
/* The request filters are given the line as the client sent it, in
absolute form, in a buffer of its own: only when there are some. */
if(param->nreqfilters){
int abs = (!isconnect && authlen > 0);
reqbufsize = (int)reqlen + (abs? (int)strlen(scheme) + authlen : 0) + 1;
if(!(fline = malloc(reqbufsize))) RETURN(21);
if(abs) reqsize = absline(fline, buf, (int)reqlen, ssoff, scheme, auth, authlen);
else {
memcpy(fline, buf, reqlen);
reqsize = (int)reqlen;
}
fline[reqsize] = 0;
if(!(origreq = (unsigned char *)strdup((char *)fline))) { freeptr(&fline); RETURN(21); }
action = handlereqfilters(param, &fline, &reqbufsize, 0, &reqsize);
if(action == HANDLED){
freeptr(&origreq);
freeptr(&fline);
RETURN(0);
}
if(action != PASS){
freeptr(&origreq);
freeptr(&fline);
RETURN(517);
}
/* the rewritten line is the one logged */
if(strcmp((char *)fline, (char *)origreq)){
freeptr(&req);
req = logline(fline, strlen((char *)fline));
}
}
/* Only the copy in req was rewritten. On a direct connection the server is
sent the request line held in buf, which was parsed and reduced to its
path before the filters ran, so put the new path there as well.
/* Only the filters' copy was rewritten. The server is sent the request
line held in buf, which was parsed and reduced to its path before the
filters ran, so put the new path there as well.
The destination was chosen, and the access rules applied to it, before
the rewrite happened. A rewrite that changes the method or the authority
is therefore left alone: acting on it would send the request somewhere
the rules never saw. */
if(origreq && !isconnect && !ftp && strcmp((char *)req, (char *)origreq)){
if(origreq && fline && !isconnect && !ftp && strcmp((char *)fline, (char *)origreq)){
unsigned char *oldhost, *newhost, *oldpath, *newpath;
int oldhostlen, newhostlen, methodlen;
methodlen = (int)(strchr((char *)origreq, ' ') - (char *)origreq);
oldpath = reqpath(origreq, &oldhost, &oldhostlen);
newpath = reqpath(req, &newhost, &newhostlen);
newpath = reqpath(fline, &newhost, &newhostlen);
if(oldpath && newpath
&& methodlen > 0 && !strncmp((char *)req, (char *)origreq, methodlen)
&& req[methodlen] == ' '
&& methodlen > 0 && !strncmp((char *)fline, (char *)origreq, methodlen)
&& fline[methodlen] == ' '
&& oldhostlen == newhostlen
&& (!oldhostlen || !strncasecmp((char *)oldhost, (char *)newhost, oldhostlen))){
int newlen = (int)strlen((char *)newpath);
@ -835,6 +931,7 @@ for(;;){
}
}
freeptr(&origreq);
freeptr(&fline);
newbuf = buf;
action = handlehdrfilterscli(param, &buf, &bufsize, 0, &inbuf);
if(buf != newbuf) bufown = 1;
@ -1156,75 +1253,91 @@ for(;;){
RETURN(param->res);
}
if(!req || param->redirtype != R_HTTP) {
reqlen = 0;
}
else {
#ifdef TCP_CORK
int opt = 1;
param->srv->so._setsockopt(param->sostate, param->remsock, IPPROTO_TCP, TCP_CORK, (unsigned char *)&opt, sizeof(int));
#endif
redirect = 1;
res = (int)strlen((char *)req);
if(socksend(param, param->remsock, req , res, param->srv->timeouts[STRING_L]) != res) {
RETURN(518);
}
param->statscli64 += res;
param->nwrites++;
}
/* to a parent proxy the request line goes in absolute form, as the
client sent it, the scheme and authority put back */
redirect = (param->redirtype == R_HTTP);
inbuf = 0;
{
int hlen = (int)strlen((char *)buf);
unsigned char *out, *own = NULL;
int outlen, al;
alen = 0;
#ifndef ANONYMOUS
if(!anonymous){
hlen += sprintf((char*)buf + hlen, "Forwarded: for=");
if(*SAFAMILY(&param->sincr) == AF_INET6) hlen += sprintf((char*)buf + hlen, "\"[");
hlen += myinet_ntop(*SAFAMILY(&param->sincr), SAADDR(&param->sincr), (char *)buf + hlen, 128);
if(*SAFAMILY(&param->sincr) == AF_INET6) hlen += sprintf((char*)buf + hlen, "]:%d\";by=", (int)ntohs(*SAPORT(&param->sincr)));
else hlen += sprintf((char*)buf + hlen, ":%d;by=", (int)ntohs(*SAPORT(&param->sincr)));
gethostname((char *)(buf + hlen), 256);
hlen += (int)strlen((char *)(buf + hlen));
hlen += sprintf((char*)buf + hlen, ":%d\r\n", (int)ntohs(*SAPORT(&param->sincl)));
alen += sprintf(add + alen, "Forwarded: for=");
if(*SAFAMILY(&param->sincr) == AF_INET6) alen += sprintf(add + alen, "\"[");
alen += myinet_ntop(*SAFAMILY(&param->sincr), SAADDR(&param->sincr), add + alen, 128);
if(*SAFAMILY(&param->sincr) == AF_INET6) alen += sprintf(add + alen, "]:%d\";by=", (int)ntohs(*SAPORT(&param->sincr)));
else alen += sprintf(add + alen, ":%d;by=", (int)ntohs(*SAPORT(&param->sincr)));
gethostname(add + alen, 256);
add[alen + 255] = 0;
alen += (int)strlen(add + alen);
alen += sprintf(add + alen, ":%d\r\n", (int)ntohs(*SAPORT(&param->sincl)));
}
else if(anonymous>1){
hlen += sprintf((char*)buf + hlen, "Via: 1.1 ");
gethostname((char *)(buf + hlen), 256);
hlen += (int)strlen((char *)(buf + hlen));
hlen += sprintf((char*)buf + hlen, ":%d (%s %s)\r\nX-Forwarded-For: ", (int)ntohs(*SAPORT(&param->srv->intsa)), runconf->stringtable?runconf->stringtable[2]:(unsigned char *)"", runconf->stringtable?runconf->stringtable[3]:(unsigned char *)"");
if(anonymous != 2)hlen += myinet_ntop(*SAFAMILY(&param->sincr), SAADDR(&param->sincr), (char *)buf + hlen, 128);
alen += sprintf(add + alen, "Via: 1.1 ");
gethostname(add + alen, 256);
add[alen + 255] = 0;
alen += (int)strlen(add + alen);
alen += snprintf(add + alen, 400, ":%d (%.100s %.100s)\r\nX-Forwarded-For: ", (int)ntohs(*SAPORT(&param->srv->intsa)), runconf->stringtable?(char *)runconf->stringtable[2]:"", runconf->stringtable?(char *)runconf->stringtable[3]:"");
if(anonymous != 2)alen += myinet_ntop(*SAFAMILY(&param->sincr), SAADDR(&param->sincr), add + alen, 128);
else {
uint32_t tmp = myrand();
hlen += myinet_ntop(AF_INET, &tmp, (char *)buf + hlen, 64);
alen += myinet_ntop(AF_INET, &tmp, add + alen, 64);
}
hlen += sprintf((char*)buf + hlen, "\r\n");
alen += sprintf(add + alen, "\r\n");
}
#endif
if(keepalive <= 1) {
hlen += sprintf((char*)buf + hlen, "Connection: %s\r\n", keepalive? "keep-alive":"close");
alen += sprintf(add + alen, "Connection: %s\r\n", keepalive? "keep-alive":"close");
}
if(*param->extusername){
hlen += sprintf((char*)buf + hlen, "%s: Basic ", (redirect)?"Proxy-Authorization":"Authorization");
alen += sprintf(add + alen, "%s: Basic ", (redirect)?"Proxy-Authorization":"Authorization");
sprintf((char*)username, "%.128s:%.128s", param->extusername, param->extpassword);
if(!(sb = en64(username, buf + hlen, (int)strlen((char *)username), bufsize - hlen))) {RETURN(21);}
hlen = (int)(sb - buf);
hlen += sprintf((char*)buf + hlen, "\r\n");
/* room kept for the line end, the empty line and the 0 after it */
if(!(sb = en64(username, (unsigned char *)add + alen, (int)strlen((char *)username), HDRADD - 5 - alen))) {RETURN(21);}
alen = (int)((char *)sb - add);
alen += sprintf(add + alen, "\r\n");
}
hlen += sprintf((char*)buf + hlen, "\r\n");
if ((res = socksend(param, param->remsock, buf+reqlen, hlen - reqlen, param->srv->timeouts[STRING_S])) != hlen - reqlen) {
alen += sprintf(add + alen, "\r\n");
/* after the block, in the room left there, or in a buffer of its own */
if(hdrroom(&buf, &bufown, &bufsize, hlen + alen + 1)) RETURN(21);
memcpy(buf + hlen, add, alen);
hlen += alen;
buf[hlen] = 0;
out = buf;
outlen = hlen;
if(redirect && !isconnect && authlen > 0){
/* scheme and authority put back after the method: in the room before
the headers in the side's buffer, the method moved down into it,
else in one buffer of their own; sent in one piece either way */
al = (int)strlen(scheme) + authlen;
if(!bufown && al <= hfront){
out = buf - al;
/* method and space move down by al: the regions may overlap */
memmove(out, buf, ssoff);
memcpy(out + ssoff, scheme, strlen(scheme));
memcpy(out + ssoff + strlen(scheme), auth, authlen);
}
else {
if(!(own = malloc(hlen + al))) RETURN(21);
absline(own, buf, hlen, ssoff, scheme, auth, authlen);
out = own;
}
outlen = hlen + al;
}
res = socksend(param, param->remsock, out, outlen, param->srv->timeouts[STRING_S]);
free(own);
if(res != outlen) {
RETURN(518);
}
}
#ifdef TCP_CORK
{
int opt = 0;
param->srv->so._setsockopt(param->sostate, param->remsock, IPPROTO_TCP, TCP_CORK, (unsigned char *)&opt, sizeof(int));
}
#endif
param->statscli64 += res;
param->nwrites++;
if(param->bandlimfunc) {
sleeptime = param->bandlimfunc(param, 0, (int)strlen((char *)buf));
/* what was sent, request line and headers */
sleeptime = param->bandlimfunc(param, 0, res);
}
if(chunkedcli || contentlength64 > 0){
if(chunkedcli){
@ -1280,7 +1393,7 @@ for(;;){
/* the answer's headers, read whole into srvbuf: the request's are done */
hdrfree(&buf, &bufown);
hlen0 = hdrread(param, SERVER, BUFSIZE, param->srv->timeouts[STRING_L], param->srv->timeouts[STRING_S], &buf, &bufsize);
hlen0 = hdrread(param, SERVER, 0, BUFSIZE, param->srv->timeouts[STRING_L], param->srv->timeouts[STRING_S], &buf, &bufsize, &hfront);
if(hlen0 == -1) {RETURN(516);}
if(hlen0 == -2) {RETURN(21);}
if(hlen0 <= 0) {RETURN(522);}
@ -1405,15 +1518,22 @@ for(;;){
#endif
if(!isconnect || param->operation){
int hlen = (int)strlen((char *)buf);
if(authenticate && !param->transparent) hlen += sprintf((char*)buf + hlen,
alen = 0;
if(authenticate && !param->transparent) alen += sprintf(add + alen,
"Proxy-support: Session-Based-Authentication\r\n"
"Connection: Proxy-support\r\n"
);
if(!param->srv->transparent && res>=200){
if(ckeepalive <= 1) hlen += sprintf((char*)buf + hlen, "Connection: %s\r\n",
if(ckeepalive <= 1) alen += sprintf(add + alen, "Connection: %s\r\n",
(hascontent && ckeepalive)?"keep-alive":"close");
}
hlen += sprintf((char*)buf + hlen, "\r\n");
alen += sprintf(add + alen, "\r\n");
/* after the block, in the room left there, or in a buffer of its own */
if(hdrroom(&buf, &bufown, &bufsize, hlen + alen + 1)) RETURN(21);
memcpy(buf + hlen, add, alen);
hlen += alen;
buf[hlen] = 0;
if(socksend(param, param->clisock, buf, hlen, param->srv->timeouts[STRING_S]) != hlen) {
RETURN(521);
}

View File

@ -87,7 +87,7 @@ def run(t):
ok = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: keep-alive\r\n\r\nok"
origin = Origin(ok)
proxy = t.free_port()
t.start("proxy_headers", f"""
server = t.start("proxy_headers", f"""
log
auth iponly
allow *
@ -145,6 +145,80 @@ def run(t):
t.ok("and its body after it") if got.endswith(b"\r\n\r\nok") else \
t.fail("and its body after it", "ok at the end", got[-40:])
# --- through a parent proxy: the line goes back to absolute form, put
# in front of the headers in the buffer, one piece with them -----------
origin.answer = ok
viaparent = t.free_port()
t.start("proxy_headers_parent", f"""
log
auth iponly
allow *
parent 1000 http 127.0.0.1 {origin.port}
proxy -p{viaparent}
""", ports=[viaparent])
origin.requests.clear()
body = b"abcdefghij" * 30
got = session(viaparent, request(b"/viaparent", b"", body, b"POST"), 1)
t.contains(got.decode("latin-1"), "200 OK", "a request through an HTTP parent is answered")
head, rbody = origin.requests[0] if origin.requests else (b"", b"")
want = b"POST " + base.encode() + b"/viaparent HTTP/1.1\r\n"
t.ok("the parent gets the request line in absolute form") if head.startswith(want) else \
t.fail("the parent gets the request line in absolute form", want, head[:80])
t.ok("and the headers after it, with Forwarded added") if b"\r\nHost: 127.0.0.1" in head and b"\r\nForwarded: for=" in head else \
t.fail("and the headers after it, with Forwarded added", "Host, Forwarded", head[:300])
t.eq(body, rbody, "and the body whole")
# user and password in the URL: the line put back is the longer for them
origin.requests.clear()
user, password = "u" * 120, "p" * 120
long = (b"GET http://" + user.encode() + b":" + password.encode() + b"@127.0.0.1:" +
str(origin.port).encode() + b"/longline HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
got = session(viaparent, long, 1)
t.contains(got.decode("latin-1"), "200 OK", "a request with user and password in its URL is answered")
head = origin.requests[0][0] if origin.requests else b""
t.ok("its line arrives in absolute form") if head.startswith(b"GET http://") and b"/longline HTTP/1.1\r\n" in head else \
t.fail("its line arrives in absolute form", "GET http://...", head[:80])
t.ok("with the credentials of the URL as Proxy-Authorization for the parent") if b"\r\nProxy-Authorization: Basic " in head else \
t.fail("with the credentials of the URL as Proxy-Authorization for the parent", "Proxy-Authorization", head[-200:])
# headers which went to a buffer of their own, here for a data filter
# to read the body into the connection's: line and headers are put
# together there, still in one piece
filtered = t.free_port()
t.start("proxy_headers_filtered", f"""
log
auth iponly
allow *
pcre clidata dunno *
parent 1000 http 127.0.0.1 {origin.port}
proxy -p{filtered}
""", ports=[filtered])
origin.requests.clear()
body = b"klmnopqrst" * 20
got = session(filtered, request(b"/filtered", b"", body, b"POST"), 1)
t.contains(got.decode("latin-1"), "200 OK", "a request with a data filter, through a parent, is answered")
head, rbody = origin.requests[0] if origin.requests else (b"", b"")
want = b"POST " + base.encode() + b"/filtered HTTP/1.1\r\n"
t.ok("its line is in absolute form, put together with the headers") if head.startswith(want) and b"\r\nHost: 127.0.0.1" in head else \
t.fail("its line is in absolute form, put together with the headers", want, head[:120])
t.eq(body, rbody, "and its body whole")
# --- a long request line: served whole, logged up to REQMAX (6 KB) ----
origin.answer = ok
origin.requests.clear()
got = session(proxy, request(b"/" + b"q" * 7000), 1)
t.contains(got.decode("latin-1"), "200 OK", "a request line of 7 KB is answered")
head = origin.requests[0][0] if origin.requests else b""
t.ok("and reaches the server whole") if (b"/" + b"q" * 7000 + b" HTTP/1.1") in head else \
t.fail("and reaches the server whole", "7000 q", len(head))
deadline = time.time() + 3
while time.time() < deadline and "q" * 3000 not in server.output():
time.sleep(0.1)
# the log line holds 4000 characters (log.c): cut there before REQMAX
logged = [l for l in server.output().splitlines() if "qqqq" in l]
t.ok("and it is logged, cut") if logged and "q" * 7000 not in logged[-1] else \
t.fail("and it is logged, cut", "a log line", logged[-1][-60:] if logged else None)
# --- a request too big for any buffer is refused, and the proxy stays --
huge = b"".join(b"X-Huge-%d: %s\r\n" % (i, b"H" * 30000) for i in range(20))
got = session(proxy, request(b"/huge", huge), 1, timeout=3)