This commit is contained in:
Andreas Rumpf 2017-03-27 09:39:46 +02:00 • committed by GitHub
commit 159643824b
4 changed files with 36 additions and 29 deletions

View file

@ -36,6 +36,9 @@ import httpcore
export httpcore except parseHeader export httpcore except parseHeader
const
maxLine = 8*1024
# TODO: If it turns out that the decisions that asynchttpserver makes # TODO: If it turns out that the decisions that asynchttpserver makes
# explicitly, about whether to close the client sockets or upgrade them are # explicitly, about whether to close the client sockets or upgrade them are
# wrong, then add a return value which determines what to do for the callback. # wrong, then add a return value which determines what to do for the callback.
@ -97,6 +100,18 @@ proc respond*(req: Request, code: HttpCode, content: string,
if headers != nil: if headers != nil:
msg.addHeaders(headers) msg.addHeaders(headers)
msg.add("Content-Length: ")
# this particular way saves allocations:
msg.add content.len
msg.add "\c\L\c\L"
msg.add(content)
result = req.client.send(msg)
proc respondError(req: Request, code: HttpCode): Future[void] =
## Responds to the request with the specified ``HttpCode``.
let content = $code
var msg = "HTTP/1.1 " & content & "\c\L"
msg.add("Content-Length: " & $content.len & "\c\L\c\L") msg.add("Content-Length: " & $content.len & "\c\L\c\L")
msg.add(content) msg.add(content)
result = req.client.send(msg) result = req.client.send(msg)
@ -139,12 +154,16 @@ proc processClient(client: AsyncSocket, address: string,
for i in 0..1: for i in 0..1:
lineFut.mget().setLen(0) lineFut.mget().setLen(0)
lineFut.clean() lineFut.clean()
await client.recvLineInto(lineFut) # TODO: Timeouts. await client.recvLineInto(lineFut, maxLength=maxLine) # TODO: Timeouts.
if lineFut.mget == "": if lineFut.mget == "":
client.close() client.close()
return return
if lineFut.mget.len > maxLine:
await request.respondError(Http413)
client.close()
return
if lineFut.mget != "\c\L": if lineFut.mget != "\c\L":
break break
@ -157,19 +176,17 @@ proc processClient(client: AsyncSocket, address: string,
# TODO: this is likely slow. # TODO: this is likely slow.
request.reqMethod = parseEnum[HttpMethod]("http" & linePart) request.reqMethod = parseEnum[HttpMethod]("http" & linePart)
except ValueError: except ValueError:
asyncCheck request.respond(Http400, "Invalid request method. Got: " & asyncCheck request.respondError(Http400)
linePart)
continue continue
of 1: parseUri(linePart, request.url) of 1: parseUri(linePart, request.url)
of 2: of 2:
try: try:
request.protocol = parseProtocol(linePart) request.protocol = parseProtocol(linePart)
except ValueError: except ValueError:
asyncCheck request.respond(Http400, asyncCheck request.respondError(Http400)
"Invalid request protocol. Got: " & linePart)
continue continue
else: else:
await request.respond(Http400, "Invalid request. Got: " & lineFut.mget) await request.respondError(Http400)
continue continue
inc i inc i
@ -178,10 +195,13 @@ proc processClient(client: AsyncSocket, address: string,
i = 0 i = 0
lineFut.mget.setLen(0) lineFut.mget.setLen(0)
lineFut.clean() lineFut.clean()
await client.recvLineInto(lineFut) await client.recvLineInto(lineFut, maxLength=maxLine)
if lineFut.mget == "": if lineFut.mget == "":
client.close(); return client.close(); return
if lineFut.mget.len > maxLine:
await request.respondError(Http413)
client.close(); return
if lineFut.mget == "\c\L": break if lineFut.mget == "\c\L": break
let (key, value) = parseHeader(lineFut.mget) let (key, value) = parseHeader(lineFut.mget)
request.headers[key] = value request.headers[key] = value

View file

@ -464,8 +464,7 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
## The partial line **will be lost**. ## The partial line **will be lost**.
## ##
## The ``maxLength`` parameter determines the maximum amount of characters ## The ``maxLength`` parameter determines the maximum amount of characters
## that can be read before a ``ValueError`` is raised. This prevents Denial ## that can be read. ``resString`` will be truncated after that.
## of Service (DOS) attacks.
## ##
## **Warning**: The ``Peek`` flag is not yet implemented. ## **Warning**: The ``Peek`` flag is not yet implemented.
## ##
@ -519,10 +518,7 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
socket.currPos.inc() socket.currPos.inc()
# Verify that this isn't a DOS attack: #3847. # Verify that this isn't a DOS attack: #3847.
if resString.mget.len > maxLength: if resString.mget.len > maxLength: break
let msg = "recvLine received more than the specified `maxLength` " &
"allowed."
raise newException(ValueError, msg)
else: else:
var c = "" var c = ""
while true: while true:
@ -546,10 +542,7 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
resString.mget.add c resString.mget.add c
# Verify that this isn't a DOS attack: #3847. # Verify that this isn't a DOS attack: #3847.
if resString.mget.len > maxLength: if resString.mget.len > maxLength: break
let msg = "recvLine received more than the specified `maxLength` " &
"allowed."
raise newException(ValueError, msg)
resString.complete() resString.complete()
proc recvLine*(socket: AsyncSocket, proc recvLine*(socket: AsyncSocket,
@ -569,8 +562,7 @@ proc recvLine*(socket: AsyncSocket,
## The partial line **will be lost**. ## The partial line **will be lost**.
## ##
## The ``maxLength`` parameter determines the maximum amount of characters ## The ``maxLength`` parameter determines the maximum amount of characters
## that can be read before a ``ValueError`` is raised. This prevents Denial ## that can be read. The result is truncated after that.
## of Service (DOS) attacks.
## ##
## **Warning**: The ``Peek`` flag is not yet implemented. ## **Warning**: The ``Peek`` flag is not yet implemented.
## ##

View file

@ -285,7 +285,7 @@ proc `$`*(code: HttpCode): string =
proc `==`*(a, b: HttpCode): bool {.borrow.} proc `==`*(a, b: HttpCode): bool {.borrow.}
proc `==`*(rawCode: string, code: HttpCode): bool = proc `==`*(rawCode: string, code: HttpCode): bool =
return rawCode.toLower() == ($code).toLower() return cmpIgnoreCase(rawCode, $code) == 0
proc is2xx*(code: HttpCode): bool = proc is2xx*(code: HttpCode): bool =
## Determines whether ``code`` is a 2xx HTTP status code. ## Determines whether ``code`` is a 2xx HTTP status code.
@ -304,7 +304,7 @@ proc is5xx*(code: HttpCode): bool =
return code.int in {500 .. 599} return code.int in {500 .. 599}
proc `$`*(httpMethod: HttpMethod): string = proc `$`*(httpMethod: HttpMethod): string =
return (system.`$`(httpMethod))[4 .. ^1].toUpper() return (system.`$`(httpMethod))[4 .. ^1].toUpperAscii()
when isMainModule: when isMainModule:
var test = newHttpHeaders() var test = newHttpHeaders()

View file

@ -1010,8 +1010,7 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
## the specified time an ETimeout exception will be raised. ## the specified time an ETimeout exception will be raised.
## ##
## The ``maxLength`` parameter determines the maximum amount of characters ## The ``maxLength`` parameter determines the maximum amount of characters
## that can be read before a ``ValueError`` is raised. This prevents Denial ## that can be read. The result is truncated after that.
## of Service (DOS) attacks.
## ##
## **Warning**: Only the ``SafeDisconn`` flag is currently supported. ## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
@ -1047,10 +1046,7 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
add(line.string, c) add(line.string, c)
# Verify that this isn't a DOS attack: #3847. # Verify that this isn't a DOS attack: #3847.
if line.string.len > maxLength: if line.string.len > maxLength: break
let msg = "recvLine received more than the specified `maxLength` " &
"allowed."
raise newException(ValueError, msg)
proc recvLine*(socket: Socket, timeout = -1, proc recvLine*(socket: Socket, timeout = -1,
flags = {SocketFlag.SafeDisconn}, flags = {SocketFlag.SafeDisconn},
@ -1069,8 +1065,7 @@ proc recvLine*(socket: Socket, timeout = -1,
## the specified time an ETimeout exception will be raised. ## the specified time an ETimeout exception will be raised.
## ##
## The ``maxLength`` parameter determines the maximum amount of characters ## The ``maxLength`` parameter determines the maximum amount of characters
## that can be read before a ``ValueError`` is raised. This prevents Denial ## that can be read. The result is truncated after that.
## of Service (DOS) attacks.
## ##
## **Warning**: Only the ``SafeDisconn`` flag is currently supported. ## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
result = "" result = ""