mirror of
https://github.com/status-im/nim-chronos.git
synced 2025-01-23 09:48:54 +00:00
Fix MultiPart handling mechanisms.
This commit is contained in:
parent
c0472bd349
commit
475c03ff7b
@ -28,6 +28,7 @@ type
|
|||||||
buffer: seq[byte]
|
buffer: seq[byte]
|
||||||
offset: int
|
offset: int
|
||||||
boundary: seq[byte]
|
boundary: seq[byte]
|
||||||
|
counter: int
|
||||||
|
|
||||||
MultiPartReaderRef* = ref MultiPartReader
|
MultiPartReaderRef* = ref MultiPartReader
|
||||||
|
|
||||||
@ -39,9 +40,12 @@ type
|
|||||||
discard
|
discard
|
||||||
buffer: seq[byte]
|
buffer: seq[byte]
|
||||||
headers: HttpTable
|
headers: HttpTable
|
||||||
|
counter: int
|
||||||
|
name*: string
|
||||||
|
filename*: string
|
||||||
|
|
||||||
MultipartError* = object of HttpCriticalError
|
MultipartError* = object of HttpCriticalError
|
||||||
MultipartEOMError* = object of MultipartError
|
MultipartEoM* = object of MultipartError
|
||||||
MultipartIncorrectError* = object of MultipartError
|
MultipartIncorrectError* = object of MultipartError
|
||||||
MultipartIncompleteError* = object of MultipartError
|
MultipartIncompleteError* = object of MultipartError
|
||||||
MultipartReadError* = object of MultipartError
|
MultipartReadError* = object of MultipartError
|
||||||
@ -89,10 +93,10 @@ proc init*[A: BChar, B: BChar](mpt: typedesc[MultiPartReader],
|
|||||||
MultiPartReader(kind: MultiPartSource.Buffer,
|
MultiPartReader(kind: MultiPartSource.Buffer,
|
||||||
buffer: buf, offset: 0, boundary: fboundary)
|
buffer: buf, offset: 0, boundary: fboundary)
|
||||||
|
|
||||||
proc init*[B: BChar](mpt: typedesc[MultiPartReader],
|
proc new*[B: BChar](mpt: typedesc[MultiPartReaderRef],
|
||||||
stream: AsyncStreamReader,
|
stream: AsyncStreamReader,
|
||||||
boundary: openarray[B],
|
boundary: openarray[B],
|
||||||
partHeadersMaxSize = 4096): MultiPartReader =
|
partHeadersMaxSize = 4096): MultiPartReaderRef =
|
||||||
# Boundary should not be empty.
|
# Boundary should not be empty.
|
||||||
doAssert(len(boundary) > 0)
|
doAssert(len(boundary) > 0)
|
||||||
# Our internal boundary has format `<CR><LF><-><-><boundary>`, so we can reuse
|
# Our internal boundary has format `<CR><LF><-><-><boundary>`, so we can reuse
|
||||||
@ -103,9 +107,31 @@ proc init*[B: BChar](mpt: typedesc[MultiPartReader],
|
|||||||
fboundary[2] = byte('-')
|
fboundary[2] = byte('-')
|
||||||
fboundary[3] = byte('-')
|
fboundary[3] = byte('-')
|
||||||
copyMem(addr fboundary[4], unsafeAddr boundary[0], len(boundary))
|
copyMem(addr fboundary[4], unsafeAddr boundary[0], len(boundary))
|
||||||
MultiPartReader(kind: MultiPartSource.Stream,
|
MultiPartReaderRef(kind: MultiPartSource.Stream, firstTime: true,
|
||||||
stream: stream, offset: 0, boundary: fboundary,
|
stream: stream, offset: 0, boundary: fboundary,
|
||||||
buffer: newSeq[byte](partHeadersMaxSize))
|
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.} =
|
proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
|
||||||
doAssert(mpr.kind == MultiPartSource.Stream)
|
doAssert(mpr.kind == MultiPartSource.Stream)
|
||||||
@ -114,15 +140,8 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
|
|||||||
# Read and verify initial <-><-><boundary><CR><LF>
|
# Read and verify initial <-><-><boundary><CR><LF>
|
||||||
await mpr.stream.readExactly(addr mpr.buffer[0], len(mpr.boundary) - 2)
|
await mpr.stream.readExactly(addr mpr.buffer[0], len(mpr.boundary) - 2)
|
||||||
mpr.firstTime = false
|
mpr.firstTime = false
|
||||||
if startsWith(mpr.buffer.toOpenArray(0, len(mpr.boundary) - 5),
|
if not(startsWith(mpr.buffer.toOpenArray(0, len(mpr.boundary) - 3),
|
||||||
mpr.boundary.toOpenArray(2, len(mpr.boundary) - 1)):
|
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:
|
|
||||||
raise newException(MultiPartIncorrectError,
|
raise newException(MultiPartIncorrectError,
|
||||||
"Unexpected boundary encountered")
|
"Unexpected boundary encountered")
|
||||||
except CancelledError as exc:
|
except CancelledError as exc:
|
||||||
@ -134,22 +153,33 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
|
|||||||
|
|
||||||
# Reading part's headers
|
# Reading part's headers
|
||||||
try:
|
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),
|
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)
|
var headersList = parseHeaders(mpr.buffer.toOpenArray(0, res - 1), false)
|
||||||
if headersList.failed():
|
if headersList.failed():
|
||||||
raise newException(MultiPartIncorrectError,
|
raise newException(MultiPartIncorrectError,
|
||||||
"Incorrect part headers found")
|
"Incorrect part headers found")
|
||||||
|
|
||||||
var part = MultiPart(
|
var part = MultiPart(
|
||||||
kind: MultiPartSource.Stream,
|
kind: MultiPartSource.Stream,
|
||||||
headers: HttpTable.init(),
|
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)):
|
for k, v in headersList.headers(mpr.buffer.toOpenArray(0, res - 1)):
|
||||||
part.headers.add(k, v)
|
part.headers.add(k, v)
|
||||||
|
|
||||||
|
let sres = part.setPartNames()
|
||||||
|
if sres.isErr():
|
||||||
|
raise newException(MultiPartIncorrectError, sres.error)
|
||||||
return part
|
return part
|
||||||
|
|
||||||
except CancelledError as exc:
|
except CancelledError as exc:
|
||||||
@ -273,69 +303,53 @@ proc getPart*(mpr: var MultiPartReader): Result[MultiPart, string] =
|
|||||||
var part = MultiPart(
|
var part = MultiPart(
|
||||||
kind: MultiPartSource.Buffer,
|
kind: MultiPartSource.Buffer,
|
||||||
headers: HttpTable.init(),
|
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)):
|
for k, v in headersList.headers(mpr.buffer.toOpenArray(hstart, hfinish)):
|
||||||
part.headers.add(k, v)
|
part.headers.add(k, v)
|
||||||
|
|
||||||
|
? part.setPartNames()
|
||||||
|
|
||||||
ok(part)
|
ok(part)
|
||||||
else:
|
else:
|
||||||
err("Incorrect multipart form")
|
err("Incorrect multipart form")
|
||||||
else:
|
else:
|
||||||
err("Incorrect multipart form")
|
err("Incorrect multipart form")
|
||||||
|
|
||||||
template `-`(x: uint32): uint32 =
|
func getMultipartBoundary*(ch: openarray[string]): HttpResult[string] =
|
||||||
(0xFFFF_FFFF'u32 - x) + 1'u32
|
if len(ch) > 1:
|
||||||
|
err("Multiple Content-Type headers found")
|
||||||
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")
|
|
||||||
else:
|
else:
|
||||||
ok(strip(bparts[1]))
|
if len(ch) == 0:
|
||||||
|
err("Content-Type header is missing")
|
||||||
func getContentType*(contentHeader: seq[string]): HttpResult[string] =
|
else:
|
||||||
if len(contentHeader) > 1:
|
let mparts = ch[0].split(";")
|
||||||
return err("Multiple Content-Header values found")
|
if strip(mparts[0]).toLowerAscii() != "multipart/form-data":
|
||||||
let mparts = contentHeader[0].split(";")
|
return err("Content-Type is not multipart")
|
||||||
ok(strip(mparts[0]).toLowerAscii())
|
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:
|
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")
|
var reader = MultiPartReader.init(buf, "------------------------5e7d0dd0ed6eb849")
|
||||||
echo getPart(reader)
|
echo getPart(reader)
|
||||||
echo "===="
|
echo "===="
|
||||||
|
Loading…
x
Reference in New Issue
Block a user