diff options
author | bptato <nincsnevem662@gmail.com> | 2024-05-11 19:56:25 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-05-11 20:06:10 +0200 |
commit | b8345d19efdecb27139e011e92f89efbb7618c08 (patch) | |
tree | 5d0f3de089ef40f1f653ee386771e326f9847cd6 /src | |
parent | 2c1d1899e424c5e055214d3647979f7f0ba4dcfe (diff) | |
download | chawan-b8345d19efdecb27139e011e92f89efbb7618c08.tar.gz |
buffer: fix multipart forms
* fix enctype not getting picked up * fix form data constructor requiring open() syscall (which gets blocked by our seccomp filter) * add closing boundary to multipart end * pass fds instead of path names through WebFile/Blob and send those through bufwriter/bufreader
Diffstat (limited to 'src')
-rw-r--r-- | src/html/dom.nim | 57 | ||||
-rw-r--r-- | src/html/enums.nim | 12 | ||||
-rw-r--r-- | src/html/formdata.nim | 22 | ||||
-rw-r--r-- | src/io/bufreader.nim | 74 | ||||
-rw-r--r-- | src/io/bufstream.nim | 4 | ||||
-rw-r--r-- | src/io/bufwriter.nim | 83 | ||||
-rw-r--r-- | src/loader/cgi.nim | 1 | ||||
-rw-r--r-- | src/local/client.nim | 11 | ||||
-rw-r--r-- | src/local/container.nim | 40 | ||||
-rw-r--r-- | src/local/pager.nim | 18 | ||||
-rw-r--r-- | src/server/buffer.nim | 68 | ||||
-rw-r--r-- | src/server/forkserver.nim | 11 | ||||
-rw-r--r-- | src/types/blob.nim | 44 | ||||
-rw-r--r-- | src/types/formdata.nim | 32 |
14 files changed, 244 insertions, 233 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim index 5daba32e..3e93de79 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -51,7 +51,9 @@ import chame/tags type FormMethod* = enum - fmGet, fmPost, fmDialog + fmGet = "get" + fmPost = "post" + fmDialog = "dialog" FormEncodingType* = enum fetUrlencoded = "application/x-www-form-urlencoded", @@ -245,7 +247,7 @@ type checked* {.jsget.}: bool xcoord*: int ycoord*: int - file*: Option[URL] + file*: WebFile HTMLAnchorElement* = ref object of HTMLElement relList {.jsget.}: DOMTokenList @@ -281,7 +283,6 @@ type fetchStarted: bool HTMLFormElement* = ref object of HTMLElement - enctype*: string constructingEntryList*: bool controls*: seq[FormAssociatedElement] relList {.jsget.}: DOMTokenList @@ -2231,19 +2232,18 @@ func inputString*(input: HTMLInputElement): string = of itPassword: '*'.repeat(input.value.len).padToWidth(int(input.attrulgz(satSize).get(20))) of itReset: - if input.value != "": input.value - else: "RESET" + if input.value != "": + input.value + else: + "RESET" of itSubmit, itButton: if input.value != "": input.value else: "SUBMIT" of itFile: - if input.file.isNone: - "".padToWidth(int(input.attrulgz(satSize).get(20))) - else: - input.file.get.path.serialize_unicode() - .padToWidth(int(input.attrulgz(satSize).get(20))) + let s = if input.file != nil: input.file.name else: "" + s.padToWidth(int(input.attrulgz(satSize).get(20))) else: input.value func textAreaString*(textarea: HTMLTextAreaElement): string = @@ -2281,30 +2281,25 @@ func action*(element: Element): string = return "" func enctype*(element: Element): FormEncodingType = + if element of HTMLFormElement: + # Note: see below, this is not in the standard. + if element.attrb(satEnctype): + let s = element.attr(satEnctype) + return parseEnumNoCase[FormEncodingType](s).get(fetUrlencoded) if element.isSubmitButton(): if element.attrb(satFormenctype): - return case element.attr(satFormenctype).toLowerAscii() - of "application/x-www-form-urlencoded": fetUrlencoded - of "multipart/form-data": fetMultipart - of "text/plain": fetTextPlain - else: fetUrlencoded - if element of HTMLInputElement: - let element = HTMLInputElement(element) - if element.form != nil: - if element.form.attrb(satEnctype): - return case element.attr(satEnctype).toLowerAscii() - of "application/x-www-form-urlencoded": fetUrlencoded - of "multipart/form-data": fetMultipart - of "text/plain": fetTextPlain - else: fetUrlencoded + let s = element.attr(satFormenctype) + return parseEnumNoCase[FormEncodingType](s).get(fetUrlencoded) + if element of FormAssociatedElement: + let element = FormAssociatedElement(element) + if (let form = element.form; form != nil): + if form.attrb(satEnctype): + let s = form.attr(satEnctype) + return parseEnumNoCase[FormEncodingType](s).get(fetUrlencoded) return fetUrlencoded func parseFormMethod(s: string): FormMethod = - return case s.toLowerAscii() - of "get": fmGet - of "post": fmPost - of "dialog": fmDialog - else: fmGet + return parseEnumNoCase[FormMethod](s).get(fmGet) func formmethod*(element: Element): FormMethod = if element of HTMLFormElement: @@ -2951,7 +2946,7 @@ proc reflectAttrs(element: Element; name: CAtom; value: string) = input.reflect_str satValue, value input.reflect_bool satChecked, checked if name == satType: - input.inputType = inputType(value) + input.inputType = parseEnumNoCase[InputType](value).get(itText) of TAG_OPTION: let option = HTMLOptionElement(element) option.reflect_bool satSelected, selected @@ -3223,7 +3218,7 @@ proc resetElement*(element: Element) = of itCheckbox, itRadio: input.checked = input.attrb(satChecked) of itFile: - input.file = none(URL) + input.file = nil else: input.value = input.attr(satValue) input.invalid = true diff --git a/src/html/enums.nim b/src/html/enums.nim index 925e3b68..119a9a9b 100644 --- a/src/html/enums.nim +++ b/src/html/enums.nim @@ -1,6 +1,3 @@ -import std/strutils -import std/tables - import chame/tags type @@ -71,15 +68,6 @@ const ResettableElements* = { TAG_INPUT, TAG_OUTPUT, TAG_SELECT, TAG_TEXTAREA } -func getInputTypeMap(): Table[string, InputType] = - for i in InputType: - result[$InputType(i)] = InputType(i) - -const inputTypeMap = getInputTypeMap() - -func inputType*(s: string): InputType = - return inputTypeMap.getOrDefault(s.toLowerAscii()) - const AutoDirInput* = { itHidden, itText, itSearch, itTel, itURL, itEmail, itPassword, itSubmit, itReset, itButton diff --git a/src/html/formdata.nim b/src/html/formdata.nim index 335e36b6..7f099acd 100644 --- a/src/html/formdata.nim +++ b/src/html/formdata.nim @@ -1,8 +1,8 @@ -import std/streams - import html/catom import html/dom import html/enums +import io/dynstream +import io/posixstream import js/base64 import js/domexception import js/javascript @@ -16,11 +16,12 @@ import chame/tags proc constructEntryList*(form: HTMLFormElement; submitter: Element = nil; encoding = "UTF-8"): seq[FormDataEntry] +var urandom* {.global.}: PosixStream + proc generateBoundary(): string = - let urandom = newFileStream("/dev/urandom") - let s = urandom.readStr(32) - urandom.close() - # 32 * 4 / 3 (padded) = 44 + prefix string is 22 bytes = 66 bytes + var s: array[33, uint8] + urandom.recvDataLoop(s) + # 33 * 4 / 3 = 44 + prefix string is 22 bytes = 66 bytes return "----WebKitFormBoundary" & btoa(s) proc newFormData0*(): FormData = @@ -147,8 +148,13 @@ proc constructEntryList*(form: HTMLFormElement; submitter: Element = nil; "on" entrylist.add((name, value)) of itFile: - #TODO file - discard + if field.file != nil: + entrylist.add(FormDataEntry( + name: name, + filename: field.file.name, + isstr: false, + value: field.file + )) of itHidden: if name.equalsIgnoreCase("_charset_"): entrylist.add((name, encoding)) diff --git a/src/io/bufreader.nim b/src/io/bufreader.nim index f25309e2..90940424 100644 --- a/src/io/bufreader.nim +++ b/src/io/bufreader.nim @@ -1,10 +1,9 @@ -# Write data to streams. - import std/options import std/sets import std/tables import io/dynstream +import io/socketstream import types/blob import types/color import types/formdata @@ -14,29 +13,7 @@ import types/url type BufferedReader* = object buffer: seq[uint8] bufIdx: int - -proc initReader*(stream: DynStream; len: int): BufferedReader = - assert len != 0 - var reader = BufferedReader( - buffer: newSeqUninitialized[uint8](len), - bufIdx: 0 - ) - var n = 0 - while true: - n += stream.recvData(addr reader.buffer[n], len - n) - if n == len: - break - return reader - -proc initPacketReader*(stream: DynStream): BufferedReader = - var len: int - stream.recvDataLoop(addr len, sizeof(len)) - return stream.initReader(len) - -template withPacketReader*(stream: DynStream; r, body: untyped) = - block: - var r = stream.initPacketReader() - body + recvAux: seq[FileHandle] #TODO assert on unused ones proc sread*(reader: var BufferedReader; n: var SomeNumber) proc sread*[T](reader: var BufferedReader; s: var set[T]) @@ -56,6 +33,27 @@ proc sread*[T](reader: var BufferedReader; o: var Option[T]) proc sread*[T, E](reader: var BufferedReader; o: var Result[T, E]) proc sread*(reader: var BufferedReader; c: var ARGBColor) {.inline.} +proc initReader*(stream: DynStream; len, auxLen: int): BufferedReader = + assert len != 0 + var reader = BufferedReader( + buffer: newSeqUninitialized[uint8](len), + bufIdx: 0 + ) + stream.recvDataLoop(reader.buffer) + for i in 0 ..< auxLen: + reader.recvAux.add(SocketStream(stream).recvFileHandle()) + return reader + +proc initPacketReader*(stream: DynStream): BufferedReader = + var len: array[2, int] + stream.recvDataLoop(addr len[0], sizeof(len)) + return stream.initReader(len[0], len[1]) + +template withPacketReader*(stream: DynStream; r, body: untyped) = + block: + var r = stream.initPacketReader() + body + proc readData(reader: var BufferedReader; buffer: pointer; len: int) = assert reader.bufIdx + len <= reader.buffer.len copyMem(buffer, addr reader.buffer[reader.bufIdx], len) @@ -156,22 +154,22 @@ proc sread*(reader: var BufferedReader; part: var FormDataEntry) = reader.sread(part.value) proc sread*(reader: var BufferedReader; blob: var Blob) = - var isfile: bool - reader.sread(isfile) - if isfile: - var file = new WebFile - file.isfile = true - reader.sread(file.path) - blob = file - else: - blob = Blob() - reader.sread(blob.ctype) - reader.sread(blob.size) + var isWebFile: bool + reader.sread(isWebFile) + blob = if isWebFile: WebFile() else: Blob() + if isWebFile: + reader.sread(WebFile(blob).name) + var hasFd: bool + reader.sread(hasFd) + if hasFd: + blob.fd = some(reader.recvAux.pop()) + reader.sread(blob.ctype) + reader.sread(blob.size) + if blob.size > 0: let buffer = alloc(blob.size) + reader.readData(blob.buffer, int(blob.size)) blob.buffer = buffer blob.deallocFun = proc() = dealloc(buffer) - if blob.size > 0: - reader.readData(blob.buffer, int(blob.size)) proc sread*[T](reader: var BufferedReader; o: var Option[T]) = var x: bool diff --git a/src/io/bufstream.nim b/src/io/bufstream.nim index 62a0df3e..3dba86f1 100644 --- a/src/io/bufstream.nim +++ b/src/io/bufstream.nim @@ -45,5 +45,9 @@ proc flushWrite*(s: BufStream): bool = s.writeBuffer = s.writeBuffer.substr(n) return false +proc reallyFlush*(s: BufStream) = + if s.writeBuffer.len > 0: + s.source.sendDataLoop(s.writeBuffer) + proc newBufStream*(ps: PosixStream; registerFun: proc(fd: int)): BufStream = return BufStream(source: ps, blocking: ps.blocking, registerFun: registerFun) diff --git a/src/io/bufwriter.nim b/src/io/bufwriter.nim index 20ef1f4c..fd3c12a8 100644 --- a/src/io/bufwriter.nim +++ b/src/io/bufwriter.nim @@ -6,6 +6,7 @@ import std/sets import std/tables import io/dynstream +import io/socketstream import types/blob import types/color import types/formdata @@ -17,7 +18,7 @@ type BufferedWriter* = object buffer: ptr UncheckedArray[uint8] bufSize: int bufLen: int - writeLen: bool + sendAux: seq[FileHandle] {.warning[Deprecated]: off.}: proc `=destroy`(writer: var BufferedWriter) = @@ -25,26 +26,42 @@ type BufferedWriter* = object dealloc(writer.buffer) writer.buffer = nil -proc initWriter*(stream: DynStream; sizeInit = 64; writeLen = false): +proc swrite*(writer: var BufferedWriter; n: SomeNumber) +proc swrite*[T](writer: var BufferedWriter; s: set[T]) +proc swrite*[T: enum](writer: var BufferedWriter; x: T) +proc swrite*(writer: var BufferedWriter; s: string) +proc swrite*(writer: var BufferedWriter; b: bool) +proc swrite*(writer: var BufferedWriter; url: URL) +proc swrite*(writer: var BufferedWriter; tup: tuple) +proc swrite*[I, T](writer: var BufferedWriter; a: array[I, T]) +proc swrite*(writer: var BufferedWriter; s: seq) +proc swrite*[U, V](writer: var BufferedWriter; t: Table[U, V]) +proc swrite*(writer: var BufferedWriter; obj: object) +proc swrite*(writer: var BufferedWriter; obj: ref object) +proc swrite*(writer: var BufferedWriter; part: FormDataEntry) +proc swrite*(writer: var BufferedWriter; blob: Blob) +proc swrite*[T](writer: var BufferedWriter; o: Option[T]) +proc swrite*[T, E](writer: var BufferedWriter; o: Result[T, E]) +proc swrite*(writer: var BufferedWriter; c: ARGBColor) {.inline.} + +const InitLen = sizeof(int) * 2 +const SizeInit = max(64, InitLen) +proc initWriter*(stream: DynStream): BufferedWriter = - var w = BufferedWriter( + return BufferedWriter( stream: stream, - buffer: cast[ptr UncheckedArray[uint8]](alloc(sizeInit)), - bufSize: sizeInit, - bufLen: 0, - writeLen: writeLen + buffer: cast[ptr UncheckedArray[uint8]](alloc(SizeInit)), + bufSize: SizeInit, + bufLen: InitLen ) - if writeLen: # add space for `len' - w.bufLen += sizeof(w.bufLen) - assert w.bufLen < sizeInit - return w proc flush*(writer: var BufferedWriter) = - if writer.writeLen: - # subtract the length field's size - var realLen = writer.bufLen - sizeof(writer.bufLen) - copyMem(writer.buffer, addr realLen, sizeof(writer.bufLen)) + # subtract the length field's size + let len = [writer.bufLen - InitLen, writer.sendAux.len] + copyMem(writer.buffer, unsafeAddr len[0], sizeof(len)) writer.stream.sendDataLoop(writer.buffer, writer.bufLen) + for i in countdown(writer.sendAux.high, 0): + SocketStream(writer.stream).sendFileHandle(writer.sendAux[i]) writer.bufLen = 0 writer.stream.sflush() @@ -53,32 +70,15 @@ proc deinit*(writer: var BufferedWriter) = writer.buffer = nil writer.bufSize = 0 writer.bufLen = 0 + writer.sendAux.setLen(0) template withPacketWriter*(stream: DynStream; w, body: untyped) = block: - var w = stream.initWriter(writeLen = true) + var w = stream.initWriter() body w.flush() w.deinit() -proc swrite*(writer: var BufferedWriter; n: SomeNumber) -proc swrite*[T](writer: var BufferedWriter; s: set[T]) -proc swrite*[T: enum](writer: var BufferedWriter; x: T) -proc swrite*(writer: var BufferedWriter; s: string) -proc swrite*(writer: var BufferedWriter; b: bool) -proc swrite*(writer: var BufferedWriter; url: URL) -proc swrite*(writer: var BufferedWriter; tup: tuple) -proc swrite*[I, T](writer: var BufferedWriter; a: array[I, T]) -proc swrite*(writer: var BufferedWriter; s: seq) -proc swrite*[U, V](writer: var BufferedWriter; t: Table[U, V]) -proc swrite*(writer: var BufferedWriter; obj: object) -proc swrite*(writer: var BufferedWriter; obj: ref object) -proc swrite*(writer: var BufferedWriter; part: FormDataEntry) -proc swrite*(writer: var BufferedWriter; blob: Blob) -proc swrite*[T](writer: var BufferedWriter; o: Option[T]) -proc swrite*[T, E](writer: var BufferedWriter; o: Result[T, E]) -proc swrite*(writer: var BufferedWriter; c: ARGBColor) {.inline.} - proc writeData(writer: var BufferedWriter; buffer: pointer; len: int) = let targetLen = writer.bufLen + len let missing = targetLen - writer.bufSize @@ -161,12 +161,15 @@ proc swrite*(writer: var BufferedWriter; part: FormDataEntry) = #TODO clean up this mess proc swrite*(writer: var BufferedWriter; blob: Blob) = - writer.swrite(blob.isfile) - if blob.isfile: - writer.swrite(WebFile(blob).path) - else: - writer.swrite(blob.ctype) - writer.swrite(blob.size) + if blob.fd.isSome: + writer.sendAux.add(blob.fd.get) + writer.swrite(blob of WebFile) + if blob of WebFile: + writer.swrite(WebFile(blob).name) + writer.swrite(blob.fd.isSome) + writer.swrite(blob.ctype) + writer.swrite(blob.size) + if blob.size > 0: writer.writeData(blob.buffer, int(blob.size)) proc swrite*[T](writer: var BufferedWriter; o: Option[T]) = diff --git a/src/loader/cgi.nim b/src/loader/cgi.nim index 2c395e95..347855ac 100644 --- a/src/loader/cgi.nim +++ b/src/loader/cgi.nim @@ -229,6 +229,7 @@ proc loadCGI*(handle: LoaderHandle; request: Request; cgiDir: seq[string]; let multipart = request.multipart.get for entry in multipart.entries: ps.writeEntry(entry, multipart.boundary) + ps.writeEnd(multipart.boundary) ps.sclose() handle.parser = HeaderParser(headers: newHeaders()) handle.istream = newPosixStream(pipefd[0]) diff --git a/src/local/client.nim b/src/local/client.nim index af5e89a0..dc79438e 100644 --- a/src/local/client.nim +++ b/src/local/client.nim @@ -658,11 +658,12 @@ proc clientLoadJSModule(ctx: JSContext; module_name: cstringConst; JS_ThrowTypeError(ctx, "Failed to open file %s", module_name) return nil -proc readBlob(client: Client; path: string): Option[WebFile] {.jsfunc.} = - try: - return some(newWebFile(path)) - except IOError: - discard +proc readBlob(client: Client; path: string): WebFile {.jsfunc.} = + let ps = newPosixStream(path, O_RDONLY, 0) + if ps == nil: + return nil + let name = path.afterLast('/') + return newWebFile(name, ps.fd) #TODO this is dumb proc readFile(client: Client; path: string): string {.jsfunc.} = diff --git a/src/local/container.nim b/src/local/container.nim index 502b1de1..5b593f2a 100644 --- a/src/local/container.nim +++ b/src/local/container.nim @@ -7,6 +7,7 @@ import std/unicode import config/config import config/mimetypes +import io/bufstream import io/dynstream import io/promise import io/serversocket @@ -46,8 +47,8 @@ type setxsave: bool ContainerEventType* = enum - cetAnchor, cetNoAnchor, cetUpdate, cetReadLine, cetReadArea, cetOpen, - cetSetLoadInfo, cetStatus, cetAlert, cetLoaded, cetTitle, cetCancel + cetAnchor, cetNoAnchor, cetUpdate, cetReadLine, cetReadArea, cetReadFile, + cetOpen, cetSetLoadInfo, cetStatus, cetAlert, cetLoaded, cetTitle, cetCancel ContainerEvent* = object case t*: ContainerEventType @@ -1483,8 +1484,12 @@ proc readCanceled*(container: Container) = if repaint: container.needslines = true) -proc readSuccess*(container: Container; s: string) = - container.iface.readSuccess(s).then(proc(res: ReadSuccessResult) = +proc readSuccess*(container: Container; s: string; fd = -1) = + let p = container.iface.readSuccess(s, fd != -1) + if fd != -1: + container.iface.stream.reallyFlush() + SocketStream(container.iface.stream.source).sendFileHandle(FileHandle(fd)) + p.then(proc(res: ReadSuccessResult) = if res.repaint: container.needslines = true if res.open.isSome: @@ -1521,19 +1526,21 @@ proc onclick(container: Container; res: ClickResult; save: bool) = container.displaySelect(res.select.get) if res.readline.isSome: let rl = res.readline.get - let event = if rl.area: - ContainerEvent( - t: cetReadArea, - tvalue: rl.value - ) - else: - ContainerEvent( + case rl.t + of rltText: + container.triggerEvent(ContainerEvent( t: cetReadLine, prompt: rl.prompt, value: rl.value, password: rl.hide - ) - container.triggerEvent(event) + )) + of rltArea: + container.triggerEvent(ContainerEvent( + t: cetReadArea, + tvalue: rl.value + )) + of rltFile: + container.triggerEvent(ContainerEvent(t: cetReadFile)) proc click*(container: Container) {.jsfunc.} = if container.select.open: @@ -1601,10 +1608,9 @@ func hoverImage(container: Container): string {.jsfget.} = return container.hoverText[htImage] proc handleCommand(container: Container) = - var packetid, len: int - container.iface.stream.recvDataLoop(addr len, sizeof(len)) - container.iface.stream.recvDataLoop(addr packetid, sizeof(packetid)) - container.iface.resolve(packetid, len - sizeof(packetid)) + var packet: array[3, int] # 0 len, 1 auxLen, 2 packetid + container.iface.stream.recvDataLoop(addr packet[0], sizeof(packet)) + container.iface.resolve(packet[2], packet[0] - sizeof(packet[2]), packet[1]) proc startLoad(container: Container) = container.iface.load().then(proc(res: int) = diff --git a/src/local/pager.nim b/src/local/pager.nim index 93bf562c..c1bd18c0 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -65,6 +65,7 @@ type lmISearchB = "?" lmGotoLine = "Goto line: " lmDownload = "(Download)Save file to: " + lmBufferFile = "(Upload)Filename: " # fdin is the original fd; fdout may be the same, or different if mailcap # is used. @@ -1173,6 +1174,19 @@ proc updateReadLine*(pager: Pager) = if pager.commandMode: pager.command() of lmBuffer: pager.container.readSuccess(lineedit.news) + of lmBufferFile: + let ps = newPosixStream(lineedit.news, O_RDONLY, 0) + if ps == nil: + pager.alert("File not found") + pager.container.readCanceled() + else: + var stats: Stat + if fstat(ps.fd, stats) < 0 or S_ISDIR(stats.st_mode): + pager.alert("Not a file: " & lineedit.news) + else: + let name = lineedit.news.afterLast('/') + pager.container.readSuccess(name, ps.fd) + ps.sclose() of lmSearchF, lmSearchB: if lineedit.news != "": let regex = pager.compileSearchRegex(lineedit.news) @@ -1655,7 +1669,6 @@ proc connected(pager: Pager; container: Container; response: Response) = container.process = pager.forkserver.forkBuffer( container.config, container.url, - container.request, attrs, mailcapRes.ishtml, container.charsetStack @@ -1768,6 +1781,9 @@ proc handleEvent0(pager: Pager; container: Container; event: ContainerEvent): else: pager.container.readCanceled() pager.redraw = true + of cetReadFile: + if container == pager.container: + pager.setLineEdit(lmBufferFile, "") of cetOpen: let url = event.request.url if not event.save and (pager.container != container or diff --git a/src/server/buffer.nim b/src/server/buffer.nim index b9ef513f..4e4d2c0e 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -45,6 +45,7 @@ import js/tojs import layout/renderdocument import loader/headers import loader/loader +import types/blob import types/cell import types/color import types/cookie @@ -94,7 +95,6 @@ type firstBufferRead: bool lines: FlexibleGrid images: seq[PosBitmap] - request: Request # source request attrs: WindowAttributes window: Window document: Document @@ -130,6 +130,7 @@ type InterfaceOpaque = ref object stream: SocketStream len: int + auxLen: int BufferInterface* = ref object map: PromiseMap @@ -149,7 +150,7 @@ type proc getFromOpaque[T](opaque: pointer; res: var T) = let opaque = cast[InterfaceOpaque](opaque) if opaque.len != 0: - var r = opaque.stream.initReader(opaque.len) + var r = opaque.stream.initReader(opaque.len, opaque.auxLen) r.sread(res) opaque.len = 0 @@ -178,8 +179,9 @@ proc cloneInterface*(stream: SocketStream; registerFun: proc(fd: int)): r.sread(pid) return iface -proc resolve*(iface: BufferInterface; packetid, len: int) = +proc resolve*(iface: BufferInterface; packetid, len, auxLen: int) = iface.opaque.len = len + iface.opaque.auxLen = auxLen iface.map.resolve(packetid) # Protection against accidentally not exhausting data available to read, # by setting opaque len to 0 in getFromOpaque. @@ -226,7 +228,7 @@ proc buildInterfaceProc(fun: NimNode; funid: string): let id2 = newIdentDefs(ident(param[i].strVal), param[^2]) params2.add(id2) body.add(quote do: - var writer {.inject.} = `thisval`.stream.initWriter(writeLen = true) + var writer {.inject.} = `thisval`.stream.initWriter() writer.swrite(BufferCommand.`nup`) writer.swrite(`thisval`.packetid) ) @@ -1435,21 +1437,22 @@ proc implicitSubmit(input: HTMLInputElement): Option[Request] = else: return submitForm(form, form) -proc readSuccess*(buffer: Buffer; s: string): ReadSuccessResult {.proxy.} = +proc readSuccess*(buffer: Buffer; s: string; hasFd: bool): ReadSuccessResult + {.proxy.} = + var fd: FileHandle = -1 + if hasFd: + fd = buffer.pstream.recvFileHandle() if buffer.document.focus != nil: case buffer.document.focus.tagType of TAG_INPUT: let input = HTMLInputElement(buffer.document.focus) case input.inputType of itFile: - let cdir = parseURL("file://" & getCurrentDir() & DirSep) - let path = parseURL(s, cdir) - if path.isSome: - input.file = path - input.invalid = true - buffer.do_reshape() - result.repaint = true - result.open = implicitSubmit(input) + input.file = newWebFile(s, fd) + input.invalid = true + buffer.do_reshape() + result.repaint = true + result.open = implicitSubmit(input) else: input.value = s input.invalid = true @@ -1467,11 +1470,15 @@ proc readSuccess*(buffer: Buffer; s: string): ReadSuccessResult {.proxy.} = if not result.repaint: result.repaint = r -type ReadLineResult* = object - prompt*: string - value*: string - hide*: bool - area*: bool +type + ReadLineType* = enum + rltText, rltArea, rltFile + + ReadLineResult* = object + t*: ReadLineType + prompt*: string + value*: string + hide*: bool type SelectResult* = object @@ -1580,8 +1587,8 @@ proc click(buffer: Buffer; button: HTMLButtonElement): ClickResult = proc click(buffer: Buffer; textarea: HTMLTextAreaElement): ClickResult = let repaint = buffer.setFocus(textarea) let readline = ReadLineResult( - value: textarea.value, - area: true, + t: rltArea, + value: textarea.value ) return ClickResult( readline: some(readline), @@ -1596,7 +1603,7 @@ const InputTypePrompt = [ itDate: "Date", itDatetimeLocal: "Local date/time", itEmail: "E-Mail", - itFile: "Filename", + itFile: "", itHidden: "", itImage: "Image", itMonth: "Month", @@ -1617,16 +1624,10 @@ proc click(buffer: Buffer; input: HTMLInputElement): ClickResult = let repaint = buffer.restoreFocus() case input.inputType of itFile: - var path = if input.file.isSome: - input.file.get.path.serialize_unicode() - else: - "" + #TODO we should somehow extract the path name from the current file return ClickResult( repaint: buffer.setFocus(input) or repaint, - readline: some(ReadLineResult( - prompt: InputTypePrompt[itFile] & ": ", - value: path - )) + readline: some(ReadLineResult(t: rltFile)) ) of itCheckbox: input.checked = not input.checked @@ -1952,16 +1953,16 @@ proc runBuffer(buffer: Buffer) = proc cleanup(buffer: Buffer) = buffer.pstream.sclose() + urandom.sclose() # no unlink access on Linux when defined(linux): buffer.ssock.close(unlink = false) else: buffer.ssock.close() -proc launchBuffer*(config: BufferConfig; url: URL; request: Request; - attrs: WindowAttributes; ishtml: bool; charsetStack: seq[Charset]; - loader: FileLoader; ssock: ServerSocket; pstream: SocketStream; - selector: Selector[int]) = +proc launchBuffer*(config: BufferConfig; url: URL; attrs: WindowAttributes; + ishtml: bool; charsetStack: seq[Charset]; loader: FileLoader; + ssock: ServerSocket; pstream: SocketStream; selector: Selector[int]) = let emptySel = Selector[int]() emptySel[] = selector[] let buffer = Buffer( @@ -1972,7 +1973,6 @@ proc launchBuffer*(config: BufferConfig; url: URL; request: Request; loader: loader, needsBOMSniff: config.charsetOverride == CHARSET_UNKNOWN, pstream: pstream, - request: request, rfd: pstream.fd, selector: selector, ssock: ssock, diff --git a/src/server/forkserver.nim b/src/server/forkserver.nim index 7502a481..23204629 100644 --- a/src/server/forkserver.nim +++ b/src/server/forkserver.nim @@ -5,6 +5,7 @@ import std/selectors import std/tables import config/config +import html/formdata import io/bufreader import io/bufwriter import io/dynstream @@ -60,13 +61,12 @@ proc removeChild*(forkserver: ForkServer; pid: int) = w.swrite(pid) proc forkBuffer*(forkserver: ForkServer; config: BufferConfig; url: URL; - request: Request; attrs: WindowAttributes; ishtml: bool; - charsetStack: seq[Charset]): int = + attrs: WindowAttributes; ishtml: bool; charsetStack: seq[Charset]): + int = forkserver.ostream.withPacketWriter w: w.swrite(fcForkBuffer) w.swrite(config) w.swrite(url) - w.swrite(request) w.swrite(attrs) w.swrite(ishtml) w.swrite(charsetStack) @@ -121,13 +121,11 @@ proc forkLoader(ctx: var ForkServerContext; config: LoaderConfig): int = proc forkBuffer(ctx: var ForkServerContext; r: var BufferedReader): int = var config: BufferConfig var url: URL - var request: Request var attrs: WindowAttributes var ishtml: bool var charsetStack: seq[Charset] r.sread(config) r.sread(url) - r.sread(request) r.sread(attrs) r.sread(ishtml) r.sread(charsetStack) @@ -161,6 +159,7 @@ proc forkBuffer(ctx: var ForkServerContext; r: var BufferedReader): int = let ps = newPosixStream(pipefd[1]) ps.write(char(0)) ps.sclose() + urandom = newPosixStream("/dev/urandom", O_RDONLY, 0) let pstream = ssock.acceptSocketStream() gssock = ssock gpstream = pstream @@ -180,7 +179,7 @@ proc forkBuffer(ctx: var ForkServerContext; r: var BufferedReader): int = sockDirFd: sockDirFd ) try: - launchBuffer(config, url, request, attrs, ishtml, charsetStack, loader, + launchBuffer(config, url, attrs, ishtml, charsetStack, loader, ssock, pstream, selector) except CatchableError: let e = getCurrentException() diff --git a/src/types/blob.nim b/src/types/blob.nim index ebf5aedf..dd812c02 100644 --- a/src/types/blob.nim +++ b/src/types/blob.nim @@ -1,27 +1,25 @@ import std/options -import std/os +import std/posix import std/strutils import js/fromjs import js/javascript import js/jstypes import utils/mimeguess -import utils/twtstr type DeallocFun = proc() {.closure, raises: [].} Blob* = ref object of RootObj - isfile*: bool size* {.jsget.}: uint64 ctype* {.jsget: "type".}: string buffer*: pointer deallocFun*: DeallocFun + fd*: Option[FileHandle] WebFile* = ref object of Blob webkitRelativePath {.jsget.}: string - path*: string - file: File #TODO maybe use fd? + name* {.jsget.}: string jsDestructor(Blob) jsDestructor(WebFile) @@ -36,25 +34,20 @@ proc newBlob*(buffer: pointer; size: int; ctype: string; ) proc finalize(blob: Blob) {.jsfin.} = + if blob.fd.isSome: + discard close(blob.fd.get) if blob.deallocFun != nil and blob.buffer != nil: blob.deallocFun() blob.buffer = nil proc finalize(file: WebFile) {.jsfin.} = - if file.deallocFun != nil and file.buffer != nil: - file.deallocFun() - file.buffer = nil - -proc newWebFile*(path: string; webkitRelativePath = ""): WebFile = - var file: File - if not open(file, path, fmRead): - raise newException(IOError, "Failed to open file") + Blob(file).finalize() + +proc newWebFile*(name: string; fd: FileHandle): WebFile = return WebFile( - isfile: true, - path: path, - file: file, - ctype: DefaultGuess.guessContentType(path), - webkitRelativePath: webkitRelativePath + name: name, + fd: some(fd), + ctype: DefaultGuess.guessContentType(name) ) type @@ -68,8 +61,7 @@ type proc newWebFile(ctx: JSContext; fileBits: seq[string]; fileName: string; options = FilePropertyBag()): WebFile {.jsctor.} = let file = WebFile( - isfile: false, - path: fileName, + name: fileName ) var len = 0 for blobPart in fileBits: @@ -94,18 +86,16 @@ proc newWebFile(ctx: JSContext; fileBits: seq[string]; fileName: string; #TODO File, Blob constructors proc getSize*(this: Blob): uint64 = - if this.isfile: - return uint64(WebFile(this).path.getFileSize()) + if this.fd.isSome: + var statbuf: Stat + if fstat(this.fd.get, statbuf) < 0: + return 0 + return uint64(statbuf.st_size) return this.size proc size*(this: WebFile): uint64 {.jsfget.} = return this.getSize() -func name*(this: WebFile): string {.jsfget.} = - if this.path.len > 0 and this.path[^1] != '/': - return this.path.afterLast('/') - return this.path.afterLast('/', 2) - #TODO lastModified proc addBlobModule*(ctx: JSContext) = diff --git a/src/types/formdata.nim b/src/types/formdata.nim index b353b814..ec3d8bbf 100644 --- a/src/types/formdata.nim +++ b/src/types/formdata.nim @@ -1,7 +1,7 @@ import std/strutils import io/dynstream -import io/filestream +import io/posixstream import js/javascript import types/blob import utils/twtstr @@ -47,12 +47,11 @@ proc calcLength*(this: FormData): int = # content type result += "Content-Type: \r\n".len result += entry.value.ctype.len - if entry.value.isfile: - result += int(WebFile(entry.value).getSize()) - else: - result += int(entry.value.size) + result += int(entry.value.getSize()) result += "\r\n".len # header is always followed by \r\n result += "\r\n".len # value is always followed by \r\n + result += "--".len + this.boundary.len + "--\r\n".len + result += "\r\n".len proc getContentType*(this: FormData): string = return "multipart/form-data; boundary=" & this.boundary @@ -61,25 +60,27 @@ proc writeEntry*(stream: DynStream; entry: FormDataEntry; boundary: string) = stream.write("--" & boundary & "\r\n") let name = percentEncode(entry.name, {'"', '\r', '\n'}) if entry.isstr: - stream.write("Content-Disposition: form-data; name=\"" & name & "\"\r\n") - stream.write("\r\n") + stream.write("Content-Disposition: form-data; name=\"" & name & + "\"\r\n\r\n") stream.write(entry.svalue) + stream.write("\r\n") else: - stream.write("Content-Disposition: form-data; name=\"" & name & "\";") + var buf = "Content-Disposition: form-data; name=\"" & name & "\";" let filename = percentEncode(entry.filename, {'"', '\r', '\n'}) - stream.write(" filename=\"" & filename & "\"\r\n") + buf &= " filename=\"" & filename & "\"\r\n" let blob = entry.value let ctype = if blob.ctype == "": "application/octet-stream" else: blob.ctype - stream.write("Content-Type: " & ctype & "\r\n") - if blob.isfile: - let fs = newDynFileStream(WebFile(blob).path) - if fs != nil: + buf &= "Content-Type: " & ctype & "\r\n\r\n" + stream.write(buf) + if blob.fd.isSome: + let ps = newPosixStream(blob.fd.get) + if ps != nil: var buf {.noinit.}: array[4096, uint8] while true: - let n = fs.recvData(addr buf[0], 4096) + let n = ps.recvData(addr buf[0], 4096) if n == 0: break stream.sendDataLoop(addr buf[0], n) @@ -88,4 +89,7 @@ proc writeEntry*(stream: DynStream; entry: FormDataEntry; boundary: string) = else: stream.sendDataLoop(blob.buffer, int(blob.size)) stream.write("\r\n") + +proc writeEnd*(stream: DynStream; boundary: string) = + stream.write("--" & boundary & "--\r\n") stream.write("\r\n") |