From 3e9ffae407377b631abff7ad5e9e35406b2f747e Mon Sep 17 00:00:00 2001 From: cheatfate Date: Wed, 10 Feb 2021 10:43:05 +0200 Subject: [PATCH] Properly fix case when request body size exceeds maximum allowed size. --- chronos/apps/http/httpcommon.nim | 31 ++++++ chronos/apps/http/httpserver.nim | 94 +++++++++------- chronos/apps/http/multipart.nim | 74 +++++++------ chronos/streams/boundstream.nim | 2 +- tests/testasyncstream.nim | 16 +++ tests/testhttpserver.nim | 180 ++++++++++++++++++++++++++++--- 6 files changed, 310 insertions(+), 87 deletions(-) diff --git a/chronos/apps/http/httpcommon.nim b/chronos/apps/http/httpcommon.nim index 5d2a6fb..ebac36f 100644 --- a/chronos/apps/http/httpcommon.nim +++ b/chronos/apps/http/httpcommon.nim @@ -7,6 +7,8 @@ # Apache License, version 2.0, (LICENSE-APACHEv2) # MIT license (LICENSE-MIT) import stew/results, httputils, strutils, uri +import ../../asyncloop, ../../asyncsync +import ../../streams/[asyncstream, boundstream] export results, httputils, strutils const @@ -30,6 +32,35 @@ type ContentEncodingFlags* {.pure.} = enum Identity, Br, Compress, Deflate, Gzip + HttpBodyReader* = ref object of AsyncStreamReader + streams*: seq[AsyncStreamReader] + +proc newHttpBodyReader*(streams: varargs[AsyncStreamReader]): HttpBodyReader = + ## HttpBodyReader is AsyncStreamReader which holds references to all the + ## ``streams``. Also on close it will close all the ``streams``. + ## + ## First stream in sequence will be used as a source. + doAssert(len(streams) > 0, "At least one stream must be added") + var res = HttpBodyReader(streams: @streams) + res.init(streams[0]) + res + +proc closeWait*(bstream: HttpBodyReader) {.async.} = + ## Close and free resource allocated by body reader. + if len(bstream.streams) > 0: + var res = newSeq[Future[void]]() + for item in bstream.streams.items(): + res.add(item.closeWait()) + await allFutures(res) + await procCall(AsyncStreamReader(bstream).closeWait()) + +proc atBound*(bstream: HttpBodyReader): bool = + ## Returns ``true`` if lowest stream is at EOF. + let lreader = bstream.streams[^1] + doAssert(lreader of BoundedStreamReader) + let breader = cast[BoundedStreamReader](lreader) + breader.atEof() and (breader.bytesLeft() == 0) + proc newHttpDefect*(msg: string): ref HttpDefect = newException(HttpDefect, msg) diff --git a/chronos/apps/http/httpserver.nim b/chronos/apps/http/httpserver.nim index 6cdb354..3dad6ce 100644 --- a/chronos/apps/http/httpserver.nim +++ b/chronos/apps/http/httpserver.nim @@ -316,7 +316,7 @@ proc prepareRequest(conn: HttpConnectionRef, ok(request) -proc getBodyStream*(request: HttpRequestRef): HttpResult[AsyncStreamReader] = +proc getBodyReader*(request: HttpRequestRef): HttpResult[HttpBodyReader] = ## Returns stream's reader instance which can be used to read request's body. ## ## Please be sure to handle ``Expect`` header properly. @@ -324,10 +324,15 @@ proc getBodyStream*(request: HttpRequestRef): HttpResult[AsyncStreamReader] = ## Streams which was obtained using this procedure must be closed to avoid ## leaks. if HttpRequestFlags.BoundBody in request.requestFlags: - ok(newBoundedStreamReader(request.connection.reader, - request.contentLength)) + let bstream = newBoundedStreamReader(request.connection.reader, + request.contentLength) + ok(newHttpBodyReader(bstream)) elif HttpRequestFlags.UnboundBody in request.requestFlags: - ok(newChunkedStreamReader(request.connection.reader)) + let maxBodySize = request.connection.server.maxRequestBodySize + let bstream = newBoundedStreamReader(request.connection.reader, maxBodySize, + comparison = BoundCmp.LessOrEqual) + let cstream = newChunkedStreamReader(bstream) + ok(newHttpBodyReader(cstream, bstream)) else: err("Request do not have body available") @@ -347,21 +352,25 @@ proc handleExpect*(request: HttpRequestRef) {.async.} = proc getBody*(request: HttpRequestRef): Future[seq[byte]] {.async.} = ## Obtain request's body as sequence of bytes. - let res = request.getBodyStream() + let res = request.getBodyReader() if res.isErr(): return @[] else: + let reader = res.get() try: await request.handleExpect() - return await read(res.get()) + return await reader.read() except AsyncStreamError: - raise newHttpCriticalError("Unable to read request's body") + if reader.atBound(): + raise newHttpCriticalError("Maximum size of body reached", Http413) + else: + raise newHttpCriticalError("Unable to read request's body") finally: await closeWait(res.get()) proc consumeBody*(request: HttpRequestRef): Future[void] {.async.} = ## Consume/discard request's body. - let res = request.getBodyStream() + let res = request.getBodyReader() if res.isErr(): return else: @@ -370,7 +379,10 @@ proc consumeBody*(request: HttpRequestRef): Future[void] {.async.} = await request.handleExpect() discard await reader.consume() except AsyncStreamError: - raise newHttpCriticalError("Unable to consume request's body") + if reader.atBound(): + raise newHttpCriticalError("Maximum size of body reached", Http413) + else: + raise newHttpCriticalError("Unable to read request's body") finally: await closeWait(res.get()) @@ -413,7 +425,7 @@ proc getRequest(conn: HttpConnectionRef): Future[HttpRequestRef] {.async.} = else: let res = prepareRequest(conn, header) if res.isErr(): - raise newHttpCriticalError("Invalid request received") + raise newHttpCriticalError("Invalid request received", res.error) else: return res.get() except AsyncStreamIncompleteError: @@ -460,7 +472,7 @@ proc new(ht: typedesc[HttpConnectionRef], server: HttpServerRef, writer: writer ) -proc close(conn: HttpConnectionRef) {.async.} = +proc closeWait(conn: HttpConnectionRef) {.async.} = if HttpServerFlags.Secure in conn.server.flags: # First we will close TLS streams. await allFutures(conn.reader.closeWait(), conn.writer.closeWait()) @@ -469,7 +481,7 @@ proc close(conn: HttpConnectionRef) {.async.} = await allFutures(conn.mainReader.closeWait(), conn.mainWriter.closeWait(), conn.transp.closeWait()) -proc close(req: HttpRequestRef) {.async.} = +proc closeWait(req: HttpRequestRef) {.async.} = if req.response.isSome(): let resp = req.response.get() if (HttpResponseFlags.Chunked in resp.flags) and @@ -488,10 +500,10 @@ proc createConnection(server: HttpServerRef, await handshake(conn.tlsStream) return conn except CancelledError as exc: - await conn.close() + await conn.closeWait() raise exc except TLSStreamError: - await conn.close() + await conn.closeWait() raise newHttpCriticalError("Unable to establish secure connection") proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} = @@ -557,14 +569,18 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} = break breakLoop = false - var lastError: ref CatchableError + var lastErrorCode: Option[HttpCode] try: resp = await conn.server.processCallback(arg) except CancelledError: breakLoop = true - except CatchableError as exc: - lastError = exc + except HttpCriticalError as exc: + lastErrorCode = some(exc.code) + except HttpRecoverableError as exc: + lastErrorCode = some(exc.code) + except CatchableError: + lastErrorCode = some(Http503) if breakLoop: break @@ -584,7 +600,7 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} = else: let request = arg.get() var keepConn = if request.version == HttpVersion11: true else: false - if isNil(lastError): + if lastErrorCode.isNone(): if isNil(resp): # Response was `nil`. discard await conn.sendErrorResponse(HttpVersion11, Http404, @@ -603,17 +619,17 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} = # some data was already sent to the client. discard else: - discard await conn.sendErrorResponse(HttpVersion11, Http503, true) - + discard await conn.sendErrorResponse(HttpVersion11, lastErrorCode.get(), + false) # Closing and releasing all the request resources. - await request.close() + await request.closeWait() if not(keepConn): break # Connection could be `nil` only when secure handshake is failed. if not(isNil(conn)): - await conn.close() + await conn.closeWait() server.connections.del(transp.getId()) # if server.maxConnections > 0: @@ -674,7 +690,7 @@ proc drop*(server: HttpServerRef) {.async.} = if server.state in {ServerStopped, ServerRunning}: discard -proc close*(server: HttpServerRef) {.async.} = +proc closeWait*(server: HttpServerRef) {.async.} = ## Stop HTTP server and drop all the pending connections. if server.state != ServerClosed: await server.stop() @@ -713,7 +729,7 @@ proc getMultipartReader*(req: HttpRequestRef): HttpResult[MultiPartReaderRef] = let boundary = ? getMultipartBoundary( req.headers.getList("content-type") ) - var stream = ? req.getBodyStream() + var stream = ? req.getBodyReader() ok(MultiPartReaderRef.new(stream, boundary)) else: err("Request's data is not multipart encoded") @@ -732,7 +748,8 @@ proc post*(req: HttpRequestRef): Future[HttpTable] {.async.} = var table = HttpTable.init() # getBody() will handle `Expect`. var body = await req.getBody() - ## TODO (cheatfate) double copy here. + # TODO (cheatfate) double copy here, because of `byte` to `char` + # conversion. var strbody = newString(len(body)) if len(body) > 0: copyMem(addr strbody[0], addr body[0], len(body)) @@ -751,10 +768,10 @@ proc post*(req: HttpRequestRef): Future[HttpTable] {.async.} = try: await req.handleExpect() except CancelledError as exc: - await mpreader.close() + await mpreader.closeWait() raise exc except HttpCriticalError as exc: - await mpreader.close() + await mpreader.closeWait() raise exc # Reading multipart/form-data parts. @@ -764,25 +781,26 @@ proc post*(req: HttpRequestRef): Future[HttpTable] {.async.} = try: part = await mpreader.readPart() var value = await part.getBody() - ## TODO (cheatfate) double copy here. + # TODO (cheatfate) double copy here, because of `byte` to `char` + # conversion. var strvalue = newString(len(value)) if len(value) > 0: copyMem(addr strvalue[0], addr value[0], len(value)) table.add(part.name, strvalue) - await part.close() - except MultiPartEoM: + await part.closeWait() + except MultipartEOMError: runLoop = false + except HttpCriticalError as exc: + if not(part.isEmpty()): + await part.closeWait() + await mpreader.closeWait() + raise exc except CancelledError as exc: if not(part.isEmpty()): - await part.close() - await mpreader.close() + await part.closeWait() + await mpreader.closeWait() raise exc - except MultipartError as exc: - if not(part.isEmpty()): - await part.close() - await mpreader.close() - raise exc - await mpreader.close() + await mpreader.closeWait() req.postTable = some(table) return table else: diff --git a/chronos/apps/http/multipart.nim b/chronos/apps/http/multipart.nim index 33172b5..347ae2a 100644 --- a/chronos/apps/http/multipart.nim +++ b/chronos/apps/http/multipart.nim @@ -21,7 +21,7 @@ type MultiPartReader* = object case kind: MultiPartSource of MultiPartSource.Stream: - stream*: AsyncStreamReader + stream*: HttpBodyReader of MultiPartSource.Buffer: discard firstTime: bool @@ -35,6 +35,7 @@ type MultiPart* = object case kind: MultiPartSource of MultiPartSource.Stream: + breader: HttpBodyReader stream: BoundedStreamReader of MultiPartSource.Buffer: discard @@ -45,16 +46,10 @@ type filename*: string MultipartError* = object of HttpCriticalError - MultipartEoM* = object of MultipartError - MultipartIncorrectError* = object of MultipartError - MultipartIncompleteError* = object of MultipartError - MultipartReadError* = object of MultipartError + MultipartEOMError* = object of MultipartError BChar* = byte | char -proc newMultipartReadError(msg: string): ref MultipartReadError = - newException(MultipartReadError, msg) - proc startsWith*(s, prefix: openarray[byte]): bool = var i = 0 while true: @@ -97,7 +92,7 @@ proc init*[A: BChar, B: BChar](mpt: typedesc[MultiPartReader], buffer: buf, offset: 0, boundary: fboundary) proc new*[B: BChar](mpt: typedesc[MultiPartReaderRef], - stream: AsyncStreamReader, + stream: HttpBodyReader, boundary: openarray[B], partHeadersMaxSize = 4096): MultiPartReaderRef = ## Create new MultiPartReader instance with `stream` interface. @@ -149,14 +144,14 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} = mpr.firstTime = false if not(startsWith(mpr.buffer.toOpenArray(0, len(mpr.boundary) - 3), mpr.boundary.toOpenArray(2, len(mpr.boundary) - 1))): - raise newException(MultiPartIncorrectError, - "Unexpected boundary encountered") + raise newHttpCriticalError("Unexpected boundary encountered") except CancelledError as exc: raise exc - except AsyncStreamIncompleteError: - raise newMultipartReadError("Error reading multipart message") - except AsyncStreamReadError: - raise newMultipartReadError("Error reading multipart message") + except AsyncStreamError: + if mpr.stream.atBound(): + raise newHttpCriticalError("Maximum size of body reached", Http413) + else: + raise newHttpCriticalError("Unable to read multipart body") # Reading part's headers try: @@ -167,26 +162,26 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} = await mpr.stream.readExactly(addr mpr.buffer[0], 2) if mpr.buffer[0] == 0x0D'u8 and mpr.buffer[1] == 0x0A'u8: # If 3rd and 4th bytes are CRLF we are exactly at the end of message. - raise newException(MultiPartEoM, + raise newException(MultipartEOMError, "End of multipart message") else: - raise newException(MultiPartIncorrectError, - "Incorrect part headers found") + raise newHttpCriticalError("Incorrect multipart header found") if mpr.buffer[0] != 0x0D'u8 or mpr.buffer[1] != 0x0A'u8: - raise newException(MultiPartIncorrectError, - "Unexpected boundary suffix") + raise newHttpCriticalError("Incorrect multipart boundary found") + # If two bytes are CRLF we are at the part beginning. # Reading part's headers let res = await mpr.stream.readUntil(addr mpr.buffer[0], len(mpr.buffer), HeadersMark) var headersList = parseHeaders(mpr.buffer.toOpenArray(0, res - 1), false) if headersList.failed(): - raise newException(MultiPartIncorrectError, - "Incorrect part headers found") + raise newHttpCriticalError("Incorrect multipart's headers found") inc(mpr.counter) + var part = MultiPart( kind: MultiPartSource.Stream, headers: HttpTable.init(), + breader: mpr.stream, stream: newBoundedStreamReader(mpr.stream, -1, mpr.boundary), counter: mpr.counter ) @@ -196,17 +191,20 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} = let sres = part.setPartNames() if sres.isErr(): - raise newException(MultiPartIncorrectError, sres.error) + raise newHttpCriticalError(sres.error) return part except CancelledError as exc: raise exc - except AsyncStreamIncompleteError: - raise newMultipartReadError("Error reading multipart message") - except AsyncStreamLimitError: - raise newMultipartReadError("Multipart message headers size too big") - except AsyncStreamReadError: - raise newMultipartReadError("Error reading multipart message") + except AsyncStreamError: + if mpr.stream.atBound(): + raise newHttpCriticalError("Maximum size of body reached", Http413) + else: + raise newHttpCriticalError("Unable to read multipart body") + +proc atBound*(mp: MultiPart): bool = + ## Returns ``true`` if MultiPart's stream reached request body maximum size. + mp.breader.atBound() proc getBody*(mp: MultiPart): Future[seq[byte]] {.async.} = ## Get multipart's ``mp`` value as sequence of bytes. @@ -216,7 +214,10 @@ proc getBody*(mp: MultiPart): Future[seq[byte]] {.async.} = let res = await mp.stream.read() return res except AsyncStreamError: - raise newException(MultipartReadError, "Could not read multipart body") + if mp.breader.atBound(): + raise newHttpCriticalError("Maximum size of body reached", Http413) + else: + raise newHttpCriticalError("Unable to read multipart body") of MultiPartSource.Buffer: return mp.buffer @@ -225,9 +226,12 @@ proc consumeBody*(mp: MultiPart) {.async.} = case mp.kind of MultiPartSource.Stream: try: - await mp.stream.consume() + discard await mp.stream.consume() except AsyncStreamError: - raise newException(MultipartReadError, "Could not consume multipart body") + if mp.breader.atBound(): + raise newHttpCriticalError("Maximum size of body reached", Http413) + else: + raise newHttpCriticalError("Unable to consume multipart body") of MultiPartSource.Buffer: discard @@ -240,7 +244,7 @@ proc getBodyStream*(mp: MultiPart): HttpResult[AsyncStreamReader] = else: err("Could not obtain stream from buffer-like part") -proc close*(mp: MultiPart) {.async.} = +proc closeWait*(mp: MultiPart) {.async.} = ## Close and release MultiPart's ``mp`` stream and resources. case mp.kind of MultiPartSource.Stream: @@ -248,7 +252,7 @@ proc close*(mp: MultiPart) {.async.} = else: discard -proc close*(mpr: MultiPartReaderRef) {.async.} = +proc closeWait*(mpr: MultiPartReaderRef) {.async.} = ## Close and release MultiPartReader's ``mpr`` stream and resources. case mpr.kind of MultiPartSource.Stream: @@ -412,6 +416,8 @@ func getMultipartBoundary*(ch: openarray[string]): HttpResult[string] = if len(ch) == 0: err("Content-Type header is missing") else: + if len(ch[0]) == 0: + return err("Content-Type header has empty value") let mparts = ch[0].split(";") if strip(mparts[0]).toLowerAscii() != "multipart/form-data": return err("Content-Type is not multipart") diff --git a/chronos/streams/boundstream.nim b/chronos/streams/boundstream.nim index d5a3f7d..aa11d06 100644 --- a/chronos/streams/boundstream.nim +++ b/chronos/streams/boundstream.nim @@ -219,7 +219,7 @@ proc boundedWriteLoop(stream: AsyncStreamWriter) {.async.} = proc bytesLeft*(stream: BoundedStreamRW): uint64 = ## Returns number of bytes left in stream. - stream.boundSize - stream.bytesCount + uint64(stream.boundSize) - stream.bytesCount proc init*[T](child: BoundedStreamReader, rsource: AsyncStreamReader, bufferSize = BoundedBufferSize, udata: ref T) = diff --git a/tests/testasyncstream.nim b/tests/testasyncstream.nim index 3cedfe7..157ed05 100644 --- a/tests/testasyncstream.nim +++ b/tests/testasyncstream.nim @@ -491,6 +491,22 @@ suite "ChunkedStream test suite": "Wikipedia in\r\n\r\nchunks."], ["4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n0\r\n\r\n", "Wikipedia in\r\n\r\nchunks."], + ["3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key1\"" & + "\r\n\r\nA\r\n\r\n" & + "3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key2\"" & + "\r\n\r\nB\r\n\r\n" & + "3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key3\"" & + "\r\n\r\nC\r\n\r\n" & + "b\r\n--f98f0--\r\n\r\n" & + "0\r\n\r\n", + "--f98f0\r\nContent-Disposition: form-data; name=\"key1\"" & + "\r\n\r\nA\r\n" & + "--f98f0\r\nContent-Disposition: form-data; name=\"key2\"" & + "\r\n\r\nB\r\n" & + "--f98f0\r\nContent-Disposition: form-data; name=\"key3\"" & + "\r\n\r\nC\r\n" & + "--f98f0--\r\n" + ] ] proc checkVector(address: TransportAddress, inputstr: string): Future[string] {.async.} = diff --git a/tests/testhttpserver.nim b/tests/testhttpserver.nim index 9fc324d..0fb0b60 100644 --- a/tests/testhttpserver.nim +++ b/tests/testhttpserver.nim @@ -69,7 +69,6 @@ N8r5CwGcIX/XPC3lKazzbZ8baA== -----END CERTIFICATE----- """ - suite "HTTP server testing suite": proc httpClient(address: TransportAddress, data: string): Future[string] {.async.} = @@ -120,6 +119,69 @@ suite "HTTP server testing suite": await allFutures(reader.closeWait(), writer.closeWait(), transp.closeWait()) + proc testTooBigBodyChunked(address: TransportAddress, + operation: int): Future[bool] {.async.} = + var serverRes = false + proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. + async.} = + if r.isOk(): + let request = r.get() + try: + if operation == 0: + let body {.used.} = await request.getBody() + elif operation == 1: + await request.consumeBody() + elif operation == 2: + let ptable {.used.} = await request.post() + elif operation == 3: + let ptable {.used.} = await request.post() + except HttpCriticalError as exc: + if exc.code == Http413: + serverRes = true + # Reraising exception, because processor should properly handle it. + raise exc + else: + return dumbResponse() + + let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} + let res = HttpServerRef.new(address, process, + maxRequestBodySize = 10, + socketFlags = socketFlags) + if res.isErr(): + return false + + let server = res.get() + server.start() + + let request = + if operation in [0, 1, 2]: + "POST / HTTP/1.0\r\n" & + "Content-Type: application/x-www-form-urlencoded\r\n" & + "Transfer-Encoding: chunked\r\n" & + "Cookie: 2\r\n\r\n" & + "5\r\na=a&b\r\n5\r\n=b&c=\r\n4\r\nc&d=\r\n4\r\n%D0%\r\n" & + "2\r\n9F\r\n0\r\n\r\n" + elif operation in [3]: + "POST / HTTP/1.0\r\n" & + "Host: 127.0.0.1:30080\r\n" & + "Transfer-Encoding: chunked\r\n" & + "Content-Type: multipart/form-data; boundary=f98f0\r\n\r\n" & + "3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key1\"" & + "\r\n\r\nA\r\n\r\n" & + "3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key2\"" & + "\r\n\r\nB\r\n\r\n" & + "3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key3\"" & + "\r\n\r\nC\r\n\r\n" & + "b\r\n--f98f0--\r\n\r\n" & + "0\r\n\r\n" + else: + "" + + let data = await httpClient(address, request) + await server.stop() + await server.closeWait() + return serverRes and (data.startsWith("HTTP/1.1 413")) + test "Request headers timeout test": proc testTimeout(address: TransportAddress): Future[bool] {.async.} = var serverRes = false @@ -144,7 +206,7 @@ suite "HTTP server testing suite": let data = await httpClient(address, "") await server.stop() - await server.close() + await server.closeWait() return serverRes and (data.startsWith("HTTP/1.1 408")) check waitFor(testTimeout(initTAddress("127.0.0.1:30080"))) == true @@ -172,7 +234,7 @@ suite "HTTP server testing suite": let data = await httpClient(address, "\r\n\r\n") await server.stop() - await server.close() + await server.closeWait() return serverRes and (data.startsWith("HTTP/1.1 400")) check waitFor(testEmpty(initTAddress("127.0.0.1:30080"))) == true @@ -202,11 +264,57 @@ suite "HTTP server testing suite": let data = await httpClient(address, "GET / HTTP/1.1\r\n\r\n") await server.stop() - await server.close() + await server.closeWait() return serverRes and (data.startsWith("HTTP/1.1 413")) check waitFor(testTooBig(initTAddress("127.0.0.1:30080"))) == true + test "Too big request body test (content-length)": + proc testTooBigBody(address: TransportAddress): Future[bool] {.async.} = + var serverRes = false + proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. + async.} = + if r.isOk(): + discard + else: + if r.error().error == HTTPServerError.CriticalError: + serverRes = true + return dumbResponse() + + let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} + let res = HttpServerRef.new(address, process, + maxRequestBodySize = 10, + socketFlags = socketFlags) + if res.isErr(): + return false + + let server = res.get() + server.start() + + let request = "GET / HTTP/1.1\r\nContent-Length: 20\r\n\r\n" + let data = await httpClient(address, request) + await server.stop() + await server.closeWait() + return serverRes and (data.startsWith("HTTP/1.1 413")) + + check waitFor(testTooBigBody(initTAddress("127.0.0.1:30080"))) == true + + test "Too big request body test (getBody()/chunked encoding)": + check: + waitFor(testTooBigBodyChunked(initTAddress("127.0.0.1:30080"), 0)) == true + + test "Too big request body test (consumeBody()/chunked encoding)": + check: + waitFor(testTooBigBodyChunked(initTAddress("127.0.0.1:30080"), 1)) == true + + test "Too big request body test (post()/urlencoded/chunked encoding)": + check: + waitFor(testTooBigBodyChunked(initTAddress("127.0.0.1:30080"), 2)) == true + + test "Too big request body test (post()/multipart/chunked encoding)": + check: + waitFor(testTooBigBodyChunked(initTAddress("127.0.0.1:30080"), 3)) == true + test "Query arguments test": proc testQuery(address: TransportAddress): Future[bool] {.async.} = var serverRes = false @@ -239,7 +347,7 @@ suite "HTTP server testing suite": let data2 = await httpClient(address, "GET /?a=%D0%9F&%D0%A4=%D0%91&b=%D0%A6&c=%D0%AE HTTP/1.0\r\n\r\n") await server.stop() - await server.close() + await server.closeWait() let r = serverRes and (data1.find("TEST_OK:a:1:a:2:b:3:c:4") >= 0) and (data2.find("TEST_OK:a:П:b:Ц:c:Ю:Ф:Б") >= 0) @@ -285,12 +393,12 @@ suite "HTTP server testing suite": ":expect:100-continue:host:www.google.com" let data = await httpClient(address, message) await server.stop() - await server.close() + await server.closeWait() return serverRes and (data.find(expect) >= 0) check waitFor(testHeaders(initTAddress("127.0.0.1:30080"))) == true - test "POST arguments (application/x-www-form-urlencoded) test": + test "POST arguments (urlencoded/content-length) test": proc testPostUrl(address: TransportAddress): Future[bool] {.async.} = var serverRes = false proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. @@ -328,12 +436,56 @@ suite "HTTP server testing suite": let data = await httpClient(address, message) let expect = "TEST_OK:a:a:b:b:c:c:d:П" await server.stop() - await server.close() + await server.closeWait() return serverRes and (data.find(expect) >= 0) check waitFor(testPostUrl(initTAddress("127.0.0.1:30080"))) == true - test "POST arguments (multipart/form-data) test": + test "POST arguments (urlencoded/chunked encoding) test": + proc testPostUrl2(address: TransportAddress): Future[bool] {.async.} = + var serverRes = false + proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. + async.} = + if r.isOk(): + var kres = newSeq[string]() + let request = r.get() + if request.meth in PostMethods: + let post = await request.post() + for k, v in post.stringItems(): + kres.add(k & ":" & v) + sort(kres) + serverRes = true + return await request.respond(Http200, "TEST_OK:" & kres.join(":"), + HttpTable.init()) + else: + serverRes = false + return dumbResponse() + + let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} + let res = HttpServerRef.new(address, process, + socketFlags = socketFlags) + if res.isErr(): + return false + + let server = res.get() + server.start() + + let message = + "POST / HTTP/1.0\r\n" & + "Content-Type: application/x-www-form-urlencoded\r\n" & + "Transfer-Encoding: chunked\r\n" & + "Cookie: 2\r\n\r\n" & + "5\r\na=a&b\r\n5\r\n=b&c=\r\n4\r\nc&d=\r\n4\r\n%D0%\r\n" & + "2\r\n9F\r\n0\r\n\r\n" + let data = await httpClient(address, message) + let expect = "TEST_OK:a:a:b:b:c:c:d:П" + await server.stop() + await server.closeWait() + return serverRes and (data.find(expect) >= 0) + + check waitFor(testPostUrl2(initTAddress("127.0.0.1:30080"))) == true + + test "POST arguments (multipart/content-length) test": proc testPostMultipart(address: TransportAddress): Future[bool] {.async.} = var serverRes = false proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. @@ -383,12 +535,12 @@ suite "HTTP server testing suite": let data = await httpClient(address, message) let expect = "TEST_OK:key1:value1:key2:value2:key2:value4" await server.stop() - await server.close() + await server.closeWait() return serverRes and (data.find(expect) >= 0) check waitFor(testPostMultipart(initTAddress("127.0.0.1:30080"))) == true - test "POST arguments (multipart/form-data + chunked encoding) test": + test "POST arguments (multipart/chunked encoding) test": proc testPostMultipart2(address: TransportAddress): Future[bool] {.async.} = var serverRes = false proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. @@ -447,7 +599,7 @@ suite "HTTP server testing suite": "BBB:key2:CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" & "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" await server.stop() - await server.close() + await server.closeWait() return serverRes and (data.find(expect) >= 0) check waitFor(testPostMultipart2(initTAddress("127.0.0.1:30080"))) == true @@ -484,7 +636,7 @@ suite "HTTP server testing suite": let data = await httpsClient(address, message) await server.stop() - await server.close() + await server.closeWait() return serverRes and (data.find("TEST_OK:GET") >= 0) check waitFor(testHTTPS(initTAddress("127.0.0.1:30080"))) == true @@ -523,7 +675,7 @@ suite "HTTP server testing suite": let data = await httpsClient(address, message, {NoVerifyServerName}) await testFut await server.stop() - await server.close() + await server.closeWait() return serverRes and data == "EXCEPTION" check waitFor(testHTTPS2(initTAddress("127.0.0.1:30080"))) == true