Fix issue #10726 - HTTP response without Content-Length is not accessible (#11904)

* Add patch by @xenogenesi

* Async test for HTTP/1.1 without Content-Length

* Apply suggestions from code review

Co-Authored-By: Dominik Picheta <dominikpicheta@googlemail.com>
This commit is contained in:
konradmb 2019-08-08 08:41:56 +02:00 • committed by Andreas Rumpf
commit addd7b5e20
2 changed files with 48 additions and 30 deletions

View file

@ -340,7 +340,10 @@ proc parseBody(s: Socket, headers: HttpHeaders, httpVersion: string, timeout: in
# -REGION- Connection: Close # -REGION- Connection: Close
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5 # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0": let implicitConnectionClose =
httpVersion == "1.0" or
httpVersion == "1.1" # This doesn't match the HTTP spec, but it fixes issues for non-conforming servers.
if headers.getOrDefault"Connection" == "close" or implicitConnectionClose:
var buf = "" var buf = ""
while true: while true:
buf = newString(4000) buf = newString(4000)
@ -811,7 +814,10 @@ proc parseBody(client: HttpClient | AsyncHttpClient,
# -REGION- Connection: Close # -REGION- Connection: Close
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5 # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0": let implicitConnectionClose =
httpVersion == "1.0" or
httpVersion == "1.1" # This doesn't match the HTTP spec, but it fixes issues for non-conforming servers.
if headers.getOrDefault"Connection" == "close" or implicitConnectionClose:
while true: while true:
let recvLen = await client.recvFull(4000, client.timeout, true) let recvLen = await client.recvFull(4000, client.timeout, true)
if recvLen != 4000: if recvLen != 4000:

View file

@ -13,6 +13,31 @@ import nativesockets, os, httpclient, asyncdispatch
const manualTests = false const manualTests = false
proc makeIPv6HttpServer(hostname: string, port: Port,
message: string): AsyncFD =
let fd = newNativeSocket(AF_INET6)
setSockOptInt(fd, SOL_SOCKET, SO_REUSEADDR, 1)
var aiList = getAddrInfo(hostname, port, AF_INET6)
if bindAddr(fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
freeAddrInfo(aiList)
raiseOSError(osLastError())
freeAddrInfo(aiList)
if listen(fd) != 0:
raiseOSError(osLastError())
setBlocking(fd, false)
var serverFd = fd.AsyncFD
register(serverFd)
result = serverFd
proc onAccept(fut: Future[AsyncFD]) {.gcsafe.} =
if not fut.failed:
let clientFd = fut.read()
clientFd.send(message).callback = proc() =
clientFd.closeSocket()
serverFd.accept().callback = onAccept
serverFd.accept().callback = onAccept
proc asyncTest() {.async.} = proc asyncTest() {.async.} =
var client = newAsyncHttpClient() var client = newAsyncHttpClient()
var resp = await client.request("http://example.com/") var resp = await client.request("http://example.com/")
@ -58,6 +83,16 @@ proc asyncTest() {.async.} =
await client.downloadFile("http://speedtest-ams2.digitalocean.com/100mb.test", await client.downloadFile("http://speedtest-ams2.digitalocean.com/100mb.test",
"100mb.test") "100mb.test")
# HTTP/1.1 without Content-Length - issue #10726
var serverFd = makeIPv6HttpServer("::1", Port(18473),
"HTTP/1.1 200 \c\L" &
"\c\L" &
"Here comes reply")
resp = await client.request("http://[::1]:18473/")
body = await resp.body
doAssert(body == "Here comes reply")
serverFd.closeSocket()
client.close() client.close()
# Proxy test # Proxy test
@ -122,40 +157,17 @@ proc syncTest() =
except: except:
doAssert false, "TimeoutError should have been raised." doAssert false, "TimeoutError should have been raised."
proc makeIPv6HttpServer(hostname: string, port: Port): AsyncFD =
let fd = newNativeSocket(AF_INET6)
setSockOptInt(fd, SOL_SOCKET, SO_REUSEADDR, 1)
var aiList = getAddrInfo(hostname, port, AF_INET6)
if bindAddr(fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
freeAddrInfo(aiList)
raiseOSError(osLastError())
freeAddrInfo(aiList)
if listen(fd) != 0:
raiseOSError(osLastError())
setBlocking(fd, false)
var serverFd = fd.AsyncFD
register(serverFd)
result = serverFd
proc onAccept(fut: Future[AsyncFD]) {.gcsafe.} =
if not fut.failed:
let clientFd = fut.read()
clientFd.send("HTTP/1.1 200 OK\r\LContent-Length: 0\r\LConnection: Closed\r\L\r\L").callback = proc() =
clientFd.closeSocket()
serverFd.accept().callback = onAccept
serverFd.accept().callback = onAccept
proc ipv6Test() = proc ipv6Test() =
var client = newAsyncHttpClient() var client = newAsyncHttpClient()
let serverFd = makeIPv6HttpServer("::1", Port(18473)) let serverFd = makeIPv6HttpServer("::1", Port(18473),
"HTTP/1.1 200 OK\r\LContent-Length: 0\r\LConnection: Closed\r\L\r\L")
var resp = waitFor client.request("http://[::1]:18473/") var resp = waitFor client.request("http://[::1]:18473/")
doAssert(resp.status == "200 OK") doAssert(resp.status == "200 OK")
serverFd.closeSocket() serverFd.closeSocket()
client.close() client.close()
ipv6Test()
syncTest() syncTest()
waitFor(asyncTest()) waitFor(asyncTest())
ipv6Test()
echo "OK" echo "OK"