This commit is contained in:
Dominik Picheta 2016-09-25 13:02:10 +02:00
commit 8a6343b5b6
4 changed files with 52 additions and 10 deletions

View file

@ -1646,7 +1646,7 @@ proc send*(socket: AsyncFD, data: string,
# -- Await Macro # -- Await Macro
include asyncmacro include asyncmacro
proc recvLine*(socket: AsyncFD): Future[string] {.async.} = proc recvLine*(socket: AsyncFD): Future[string] {.async, deprecated.} =
## Reads a line of data from ``socket``. Returned future will complete once ## Reads a line of data from ``socket``. Returned future will complete once
## a full line is read or an error occurs. ## a full line is read or an error occurs.
## ##
@ -1664,6 +1664,8 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
## ##
## **Note**: This procedure is mostly used for testing. You likely want to ## **Note**: This procedure is mostly used for testing. You likely want to
## use ``asyncnet.recvLine`` instead. ## use ``asyncnet.recvLine`` instead.
##
## **Deprecated since version 0.15.0**: Use ``asyncnet.recvLine()`` instead.
template addNLIfEmpty(): stmt = template addNLIfEmpty(): stmt =
if result.len == 0: if result.len == 0:

View file

@ -9,6 +9,12 @@
## This module implements a high performance asynchronous HTTP server. ## This module implements a high performance asynchronous HTTP server.
## ##
## This HTTP server has not been designed to be used in production, but
## for testing applications locally. Because of this, when deploying your
## application you should use a reverse proxy (for example nginx) instead of
## allowing users to connect directly to this server.
##
##
## Examples ## Examples
## -------- ## --------
## ##

View file

@ -388,7 +388,7 @@ proc accept*(socket: AsyncSocket,
return retFut return retFut
proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string], proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
flags = {SocketFlag.SafeDisconn}) {.async.} = flags = {SocketFlag.SafeDisconn}, maxLength = MaxLineLength) {.async.} =
## Reads a line of data from ``socket`` into ``resString``. ## Reads a line of data from ``socket`` into ``resString``.
## ##
## If a full line is read ``\r\L`` is not ## If a full line is read ``\r\L`` is not
@ -401,13 +401,14 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
## is read) then line will be set to ``""``. ## is read) then line will be set to ``""``.
## The partial line **will be lost**. ## The partial line **will be lost**.
## ##
## The ``maxLength`` parameter determines the maximum amount of characters
## that can be read before a ``ValueError`` is raised. This prevents Denial
## of Service (DOS) attacks.
##
## **Warning**: The ``Peek`` flag is not yet implemented. ## **Warning**: The ``Peek`` flag is not yet implemented.
## ##
## **Warning**: ``recvLineInto`` on unbuffered sockets assumes that the ## **Warning**: ``recvLineInto`` on unbuffered sockets assumes that the
## protocol uses ``\r\L`` to delimit a new line. ## protocol uses ``\r\L`` to delimit a new line.
##
## **Warning**: ``recvLineInto`` currently uses a raw pointer to a string for
## performance reasons. This will likely change soon to use FutureVars.
assert SocketFlag.Peek notin flags ## TODO: assert SocketFlag.Peek notin flags ## TODO:
assert(not resString.mget.isNil(), assert(not resString.mget.isNil(),
"String inside resString future needs to be initialised") "String inside resString future needs to be initialised")
@ -454,6 +455,12 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
else: else:
resString.mget.add socket.buffer[socket.currPos] resString.mget.add socket.buffer[socket.currPos]
socket.currPos.inc() socket.currPos.inc()
# Verify that this isn't a DOS attack: #3847.
if resString.mget.len > maxLength:
let msg = "recvLine received more than the specified `maxLength` " &
"allowed."
raise newException(ValueError, msg)
else: else:
var c = "" var c = ""
while true: while true:
@ -475,10 +482,17 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
resString.complete() resString.complete()
return return
resString.mget.add c resString.mget.add c
# Verify that this isn't a DOS attack: #3847.
if resString.mget.len > maxLength:
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,
flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} = flags = {SocketFlag.SafeDisconn},
maxLength = MaxLineLength): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once ## Reads a line of data from ``socket``. Returned future will complete once
## a full line is read or an error occurs. ## a full line is read or an error occurs.
## ##
@ -492,6 +506,10 @@ proc recvLine*(socket: AsyncSocket,
## is read) then line will be set to ``""``. ## is read) then line will be set to ``""``.
## The partial line **will be lost**. ## The partial line **will be lost**.
## ##
## The ``maxLength`` parameter determines the maximum amount of characters
## that can be read before a ``ValueError`` is raised. This prevents Denial
## of Service (DOS) attacks.
##
## **Warning**: The ``Peek`` flag is not yet implemented. ## **Warning**: The ``Peek`` flag is not yet implemented.
## ##
## **Warning**: ``recvLine`` on unbuffered sockets assumes that the protocol ## **Warning**: ``recvLine`` on unbuffered sockets assumes that the protocol
@ -501,7 +519,7 @@ proc recvLine*(socket: AsyncSocket,
# TODO: Optimise this # TODO: Optimise this
var resString = newFutureVar[string]("asyncnet.recvLine") var resString = newFutureVar[string]("asyncnet.recvLine")
resString.mget() = "" resString.mget() = ""
await socket.recvLineInto(resString, flags) await socket.recvLineInto(resString, flags, maxLength)
result = resString.mget() result = resString.mget()
proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} = proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =

View file

@ -112,6 +112,7 @@ else:
const const
BufferSize*: int = 4000 ## size of a buffered socket's buffer BufferSize*: int = 4000 ## size of a buffered socket's buffer
MaxLineLength* = 1_000_000
type type
SocketImpl* = object ## socket type SocketImpl* = object ## socket type
@ -1006,7 +1007,7 @@ proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
result = recv(socket.fd, addr(c), 1, MSG_PEEK) result = recv(socket.fd, addr(c), 1, MSG_PEEK)
proc readLine*(socket: Socket, line: var TaintedString, timeout = -1, proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
flags = {SocketFlag.SafeDisconn}) {. flags = {SocketFlag.SafeDisconn}, maxLength = MaxLineLength) {.
tags: [ReadIOEffect, TimeEffect].} = tags: [ReadIOEffect, TimeEffect].} =
## Reads a line of data from ``socket``. ## Reads a line of data from ``socket``.
## ##
@ -1021,6 +1022,10 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
## A timeout can be specified in milliseconds, if data is not received within ## A timeout can be specified in milliseconds, if data is not received within
## 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
## that can be read before a ``ValueError`` is raised. This prevents Denial
## of Service (DOS) attacks.
##
## **Warning**: Only the ``SafeDisconn`` flag is currently supported. ## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
template addNLIfEmpty() = template addNLIfEmpty() =
@ -1054,8 +1059,15 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
return return
add(line.string, c) add(line.string, c)
# Verify that this isn't a DOS attack: #3847.
if line.string.len > maxLength:
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}): TaintedString = flags = {SocketFlag.SafeDisconn},
maxLength = MaxLineLength): TaintedString =
## Reads a line of data from ``socket``. ## Reads a line of data from ``socket``.
## ##
## If a full line is read ``\r\L`` is not ## If a full line is read ``\r\L`` is not
@ -1069,9 +1081,13 @@ proc recvLine*(socket: Socket, timeout = -1,
## A timeout can be specified in milliseconds, if data is not received within ## A timeout can be specified in milliseconds, if data is not received within
## 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
## that can be read before a ``ValueError`` is raised. This prevents Denial
## of Service (DOS) attacks.
##
## **Warning**: Only the ``SafeDisconn`` flag is currently supported. ## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
result = "" result = ""
readLine(socket, result, timeout, flags) readLine(socket, result, timeout, flags, maxLength)
proc recvFrom*(socket: Socket, data: var string, length: int, proc recvFrom*(socket: Socket, data: var string, length: int,
address: var string, port: var Port, flags = 0'i32): int {. address: var string, port: var Port, flags = 0'i32): int {.