From 475c03ff7b72d2a6f21d02a9449f5d356cb59123 Mon Sep 17 00:00:00 2001 From: cheatfate Date: Sat, 30 Jan 2021 20:16:44 +0200 Subject: [PATCH] Fix MultiPart handling mechanisms. --- chronos/apps/http/multipart.nim | 152 +++++++++++++++++--------------- 1 file changed, 83 insertions(+), 69 deletions(-) diff --git a/chronos/apps/http/multipart.nim b/chronos/apps/http/multipart.nim index 894bb4b..ac6b50d 100644 --- a/chronos/apps/http/multipart.nim +++ b/chronos/apps/http/multipart.nim @@ -28,6 +28,7 @@ type buffer: seq[byte] offset: int boundary: seq[byte] + counter: int MultiPartReaderRef* = ref MultiPartReader @@ -39,9 +40,12 @@ type discard buffer: seq[byte] headers: HttpTable + counter: int + name*: string + filename*: string MultipartError* = object of HttpCriticalError - MultipartEOMError* = object of MultipartError + MultipartEoM* = object of MultipartError MultipartIncorrectError* = object of MultipartError MultipartIncompleteError* = object of MultipartError MultipartReadError* = object of MultipartError @@ -89,10 +93,10 @@ proc init*[A: BChar, B: BChar](mpt: typedesc[MultiPartReader], MultiPartReader(kind: MultiPartSource.Buffer, buffer: buf, offset: 0, boundary: fboundary) -proc init*[B: BChar](mpt: typedesc[MultiPartReader], +proc new*[B: BChar](mpt: typedesc[MultiPartReaderRef], stream: AsyncStreamReader, boundary: openarray[B], - partHeadersMaxSize = 4096): MultiPartReader = + partHeadersMaxSize = 4096): MultiPartReaderRef = # Boundary should not be empty. doAssert(len(boundary) > 0) # Our internal boundary has format `<-><->`, so we can reuse @@ -103,9 +107,31 @@ proc init*[B: BChar](mpt: typedesc[MultiPartReader], fboundary[2] = byte('-') fboundary[3] = byte('-') copyMem(addr fboundary[4], unsafeAddr boundary[0], len(boundary)) - MultiPartReader(kind: MultiPartSource.Stream, - stream: stream, offset: 0, boundary: fboundary, - buffer: newSeq[byte](partHeadersMaxSize)) + MultiPartReaderRef(kind: MultiPartSource.Stream, firstTime: true, + stream: stream, offset: 0, boundary: fboundary, + buffer: newSeq[byte](partHeadersMaxSize)) + +func setPartNames*(part: var MultiPart): HttpResult[void] = + if part.headers.count("content-disposition") != 1: + return err("Content-Disposition header is incorrect") + var header = part.headers.getString("content-disposition") + let disp = parseDisposition(header, false) + if disp.failed(): + return err("Content-Disposition header value is incorrect") + let dtype = disp.dispositionType(header.toOpenArrayByte(0, len(header) - 1)) + if dtype.toLowerAscii() != "form-data": + return err("Content-Disposition type is incorrect") + for k, v in disp.fields(header.toOpenArrayByte(0, len(header) - 1)): + case k.toLowerAscii() + of "name": + part.name = v + of "filename": + part.filename = v + else: + discard + if len(part.name) == 0: + part.name = $part.counter + ok() proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} = doAssert(mpr.kind == MultiPartSource.Stream) @@ -114,15 +140,8 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} = # Read and verify initial <-><-> await mpr.stream.readExactly(addr mpr.buffer[0], len(mpr.boundary) - 2) mpr.firstTime = false - if startsWith(mpr.buffer.toOpenArray(0, len(mpr.boundary) - 5), - mpr.boundary.toOpenArray(2, len(mpr.boundary) - 1)): - if mpr.buffer[0] == byte('-') and mpr.buffer[1] == byte('-'): - raise newException(MultiPartEOMError, - "Unexpected EOM encountered") - if mpr.buffer[0] != 0x0D'u8 or mpr.buffer[1] != 0x0A'u8: - raise newException(MultiPartIncorrectError, - "Unexpected boundary suffix") - else: + 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") except CancelledError as exc: @@ -134,22 +153,33 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} = # Reading part's headers try: + await mpr.stream.readExactly(addr mpr.buffer[0], 2) + if mpr.buffer[0] == byte('-') and mpr.buffer[1] == byte('-'): + raise newException(MultiPartEoM, + "End of multipart message") + if mpr.buffer[0] != 0x0D'u8 or mpr.buffer[1] != 0x0A'u8: + raise newException(MultiPartIncorrectError, + "Unexpected boundary suffix") let res = await mpr.stream.readUntil(addr mpr.buffer[0], len(mpr.buffer), - HeadersMark) + HeadersMark) var headersList = parseHeaders(mpr.buffer.toOpenArray(0, res - 1), false) if headersList.failed(): raise newException(MultiPartIncorrectError, "Incorrect part headers found") - var part = MultiPart( kind: MultiPartSource.Stream, headers: HttpTable.init(), - stream: newBoundedStreamReader(mpr.stream, -1, mpr.boundary) + stream: newBoundedStreamReader(mpr.stream, -1, mpr.boundary), + counter: mpr.counter ) + inc(mpr.counter) for k, v in headersList.headers(mpr.buffer.toOpenArray(0, res - 1)): part.headers.add(k, v) + let sres = part.setPartNames() + if sres.isErr(): + raise newException(MultiPartIncorrectError, sres.error) return part except CancelledError as exc: @@ -273,69 +303,53 @@ proc getPart*(mpr: var MultiPartReader): Result[MultiPart, string] = var part = MultiPart( kind: MultiPartSource.Buffer, headers: HttpTable.init(), - buffer: @(mpr.buffer.toOpenArray(start, start + pos2 - 1)) + buffer: @(mpr.buffer.toOpenArray(start, start + pos2 - 1)), + counter: mpr.counter ) + inc(mpr.counter) + for k, v in headersList.headers(mpr.buffer.toOpenArray(hstart, hfinish)): part.headers.add(k, v) + + ? part.setPartNames() + ok(part) else: err("Incorrect multipart form") else: err("Incorrect multipart form") -template `-`(x: uint32): uint32 = - (0xFFFF_FFFF'u32 - x) + 1'u32 - -template LT(x, y: uint32): uint32 = - let z = x - y - (z xor ((y xor x) and (y xor z))) shr 31 - -proc boundaryValue(c: char): bool = - let a0 = uint32(c) - 0x27'u32 - let a1 = uint32(c) - 0x2B'u32 - let a2 = uint32(c) - 0x3A'u32 - let a3 = uint32(c) - 0x3D'u32 - let a4 = uint32(c) - 0x3F'u32 - let a5 = uint32(c) - 0x41'u32 - let a6 = uint32(c) - 0x5F - let a7 = uint32(c) - 0x61'u32 - let r = ((a0 + 1'u32) and -LT(a0, 3)) or - ((a1 + 1'u32) and -LT(a1, 15)) or - ((a2 + 1'u32) and -LT(a2, 1)) or - ((a3 + 1'u32) and -LT(a3, 1)) or - ((a4 + 1'u32) and -LT(a4, 1)) or - ((a5 + 1'u32) and -LT(a5, 26)) or - ((a6 + 1'u32) and -LT(a6, 1)) or - ((a7 + 1'u32) and -LT(a7, 26)) - (int(r) - 1) > 0 - -proc boundaryValue2(c: char): bool = - c in {'a'..'z', 'A' .. 'Z', '0' .. '9', - '\'' .. ')', '+' .. '/', ':', '=', '?', '_'} - -func getMultipartBoundary*(contentType: string): HttpResult[string] = - let mparts = contentType.split(";") - if strip(mparts[0]).toLowerAscii() != "multipart/form-data": - return err("Content-Type is not multipart") - if len(mparts) < 2: - return err("Content-Type missing boundary value") - let stripped = strip(mparts[1]) - if not(stripped.toLowerAscii().startsWith("boundary")): - return err("Incorrect Content-Type boundary format") - let bparts = stripped.split("=") - if len(bparts) < 2: - err("Missing Content-Type boundary") +func getMultipartBoundary*(ch: openarray[string]): HttpResult[string] = + if len(ch) > 1: + err("Multiple Content-Type headers found") else: - ok(strip(bparts[1])) - -func getContentType*(contentHeader: seq[string]): HttpResult[string] = - if len(contentHeader) > 1: - return err("Multiple Content-Header values found") - let mparts = contentHeader[0].split(";") - ok(strip(mparts[0]).toLowerAscii()) + if len(ch) == 0: + err("Content-Type header is missing") + else: + let mparts = ch[0].split(";") + if strip(mparts[0]).toLowerAscii() != "multipart/form-data": + return err("Content-Type is not multipart") + if len(mparts) < 2: + return err("Content-Type missing boundary value") + let stripped = strip(mparts[1]) + if not(stripped.toLowerAscii().startsWith("boundary")): + return err("Incorrect Content-Type boundary format") + let bparts = stripped.split("=") + if len(bparts) < 2: + err("Missing Content-Type boundary") + else: + let candidate = strip(bparts[1]) + if len(candidate) > 70: + err("Content-Type boundary must be less then 70 characters") + else: + for ch in candidate: + if ch notin {'a'..'z', 'A' .. 'Z', '0' .. '9', + '\'' .. ')', '+' .. '/', ':', '=', '?', '_'}: + return err("Content-Type boundary alphabat incorrect") + ok(candidate) when isMainModule: - var buf = "--------------------------5e7d0dd0ed6eb849\r\nContent-Disposition: form-data; name=\"key1\"\r\n\r\nvalue1\r\n--------------------------5e7d0dd0ed6eb849\r\nContent-Disposition: form-data; name=\"key2\"\r\n\r\nvalue2\r\n--------------------------5e7d0dd0ed6eb849--" + var buf = "--------------------------5e7d0dd0ed6eb849\r\nContent-Disposition: form-data; =\"key1\"\r\n\r\nvalue1\r\n--------------------------5e7d0dd0ed6eb849\r\nContent-Disposition: form-data; name=\"key2\"\r\n\r\nvalue2\r\n--------------------------5e7d0dd0ed6eb849--" var reader = MultiPartReader.init(buf, "------------------------5e7d0dd0ed6eb849") echo getPart(reader) echo "===="