optimize CL/TE check;

replace the heavyhanded connection:close added in b4fddbc3d
with a comparison of content-length to num bytes consumed

this approach also covers incorrectly configured servers
where the reverseproxy was not detected

also adds explicit TE/CL handling, even though most
(all?) reverseproxies already prevent such issues

also adds explicit sanchk of up2k chunk-receiver,
in case any bugs are ever added there
This commit is contained in:
ed
2026-01-30 20:06:02 +00:00
parent 73d06eaf84
commit ed6a8d5a73
3 changed files with 43 additions and 3 deletions

View File

@@ -374,6 +374,7 @@ class HttpCli(object):
return False
self.sr.nb = 0
self.conn.hsrv.nreq += 1
self.ua = self.headers.get("user-agent", "")
@@ -383,6 +384,19 @@ class HttpCli(object):
self.keepalive = "close" not in zs and (
self.http_ver != "HTTP/1.0" or zs == "keep-alive"
)
if (
"transfer-encoding" in self.headers
and self.headers["transfer-encoding"].lower() != "identity"
):
self.sr.te = 1
if "content-length" in self.headers:
# rfc9112:6.2: ignore CL if TE
self.keepalive = False
self.headers.pop("content-length")
t = "suspicious request (has both TE and CL); ignoring CL and disabling keepalive"
self.log(t, 3)
self.host = self.headers.get("host") or ""
if not self.host:
if self.s.family == socket.AF_UNIX:
@@ -396,7 +410,6 @@ class HttpCli(object):
if n:
zso = self.headers.get(self.args.xff_hdr)
if zso:
self.keepalive = False
if n > 0:
n -= 1
@@ -881,7 +894,11 @@ class HttpCli(object):
self.terse_reply(b"", 500)
return False
post = self.mode in ("POST", "PUT") or "content-length" in self.headers
post = (
self.mode in ("POST", "PUT")
or "content-length" in self.headers
or self.sr.te
)
if pex.code >= (300 if post else 400):
self.keepalive = False
@@ -3200,7 +3217,7 @@ class HttpCli(object):
t = "your chunk got corrupted somehow (received {} bytes); expected vs received hash:\n{}\n{}"
raise Pebkac(400, t.format(post_sz, chash, sha_b64))
remains -= chunksize
remains -= post_sz
if len(cstart) > 1 and path != os.devnull:
t = " & ".join(unicode(x) for x in cstart[1:])
@@ -3278,6 +3295,12 @@ class HttpCli(object):
spd = self._spd(postsize)
self.log("%70s thank %r" % (spd, cinf))
if remains:
t = "incorrect content-length from client"
self.log("%s; header=%d, remains=%d" % (t, postsize, remains), 3)
raise Pebkac(400, t)
self.reply(b"thank")
return True

View File

@@ -222,6 +222,21 @@ class HttpConn(object):
if not self.cli.run():
return
if self.sr.te == 1:
self.log("closing socket (leftover TE)", "90")
return
if (
"content-length" in self.cli.headers
and int(self.cli.headers["content-length"]) != self.sr.nb
):
self.log("closing socket (CL mismatch)", "90")
return
# note: proxies reject PUT sans Content-Length; illegal for HTTP/1.1
self.sr.nb = self.sr.te = 0
if self.u2idx:
self.hsrv.put_u2idx(str(self.addr), self.u2idx)
self.u2idx = None

View File

@@ -971,6 +971,7 @@ class _Unrecv(object):
self.log = log
self.buf: bytes = b""
self.nb = 0
self.te = 0
def recv(self, nbytes: int, spins: int = 1) -> bytes:
if self.buf:
@@ -3008,6 +3009,7 @@ def read_socket_chunked(
if chunklen == 0:
x = sr.recv_ex(2, False)
if x == b"\r\n":
sr.te = 2
return
t = "protocol error after final chunk: want b'\\r\\n', got {!r}"