diff options
author | bptato <nincsnevem662@gmail.com> | 2023-08-13 17:42:34 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-08-13 17:54:05 +0200 |
commit | d526deb99e44f2a8d1a9c3eea60676703dd64302 (patch) | |
tree | f63689ff7654d14ad9bca182a837b3155b2471a0 /src/io | |
parent | f92e30232252deb194596e7c298cc7fcf56517cb (diff) | |
download | chawan-d526deb99e44f2a8d1a9c3eea60676703dd64302.tar.gz |
Add mailcap, mime.types & misc refactorings
* add mailcap: works with copiousoutput, needsterminal, etc. * add mime.types (only works with mailcap) * refactor pipeBuffer * remove "dispatcher" * fix bug in directory display where baseurl would not be used
Diffstat (limited to 'src/io')
-rw-r--r-- | src/io/about.nim | 29 | ||||
-rw-r--r-- | src/io/connecterror.nim | 17 | ||||
-rw-r--r-- | src/io/file.nim | 94 | ||||
-rw-r--r-- | src/io/http.nim | 75 | ||||
-rw-r--r-- | src/io/loader.nim | 146 | ||||
-rw-r--r-- | src/io/loaderhandle.nim | 73 | ||||
-rw-r--r-- | src/io/posixstream.nim | 5 | ||||
-rw-r--r-- | src/io/request.nim | 7 | ||||
-rw-r--r-- | src/io/response.nim | 2 | ||||
-rw-r--r-- | src/io/tempfile.nim | 18 |
10 files changed, 302 insertions, 164 deletions
diff --git a/src/io/about.nim b/src/io/about.nim index 97e01133..737a291b 100644 --- a/src/io/about.nim +++ b/src/io/about.nim @@ -1,9 +1,9 @@ -import streams import tables +import io/connecterror import io/headers +import io/loaderhandle import io/request -import ips/serialize import types/url const chawan = staticRead"res/chawan.html" @@ -11,19 +11,18 @@ const HeaderTable = { "Content-Type": "text/html" }.toTable() -proc loadAbout*(request: Request, ostream: Stream) = +proc loadAbout*(handle: LoaderHandle, request: Request) = + template t(body: untyped) = + if not body: + return if request.url.pathname == "blank": - ostream.swrite(0) - ostream.swrite(200) # ok - let headers = newHeaders(HeaderTable) - ostream.swrite(headers) + t handle.sendResult(0) + t handle.sendStatus(200) # ok + t handle.sendHeaders(newHeaders(HeaderTable)) elif request.url.pathname == "chawan": - ostream.swrite(0) - ostream.swrite(200) # ok - let headers = newHeaders(HeaderTable) - ostream.swrite(headers) - ostream.write(chawan) + t handle.sendResult(0) + t handle.sendStatus(200) # ok + t handle.sendHeaders(newHeaders(HeaderTable)) + t handle.sendData(chawan) else: - ostream.swrite(-1) - ostream.flush() - + t handle.sendResult(ERROR_ABOUT_PAGE_NOT_FOUND) diff --git a/src/io/connecterror.nim b/src/io/connecterror.nim new file mode 100644 index 00000000..563a4291 --- /dev/null +++ b/src/io/connecterror.nim @@ -0,0 +1,17 @@ +import bindings/curl + +type ConnectErrorCode* = enum + ERROR_ABOUT_PAGE_NOT_FOUND = (-6, "about page not found") + ERROR_FILE_NOT_FOUND = (-5, "file not found") + ERROR_SOURCE_NOT_FOUND = (-4, "clone source could not be found"), + ERROR_LOADER_KILLED = (-3, "loader killed during transfer"), + ERROR_DISALLOWED_URL = (-2, "url not allowed by filter"), + ERROR_UNKNOWN_SCHEME = (-1, "unknown scheme") + +converter toInt*(code: ConnectErrorCode): int = + return int(code) + +func getLoaderErrorMessage*(code: int): string = + if code < 0: + return $ConnectErrorCode(code) + return $curl_easy_strerror(CURLcode(cint(code))) diff --git a/src/io/file.nim b/src/io/file.nim index b9fe0ce2..fe732d6c 100644 --- a/src/io/file.nim +++ b/src/io/file.nim @@ -3,18 +3,28 @@ import os import streams import tables +import io/connecterror import io/headers -import ips/serialize +import io/loaderhandle import types/url -proc loadDir(url: URL, path: string, ostream: Stream) = - ostream.swrite(0) - ostream.swrite(200) # ok - ostream.swrite(newHeaders({"Content-Type": "text/html"}.toTable())) - ostream.write(""" +proc loadDir(handle: LoaderHandle, url: URL, path: string) = + template t(body: untyped) = + if not body: + return + var path = path + if path[^1] != '/': #TODO dos/windows + path &= '/' + var base = $url + if base[^1] != '/': #TODO dos/windows + base &= '/' + t handle.sendResult(0) + t handle.sendStatus(200) # ok + t handle.sendHeaders(newHeaders({"Content-Type": "text/html"}.toTable())) + t handle.sendData(""" <HTML> <HEAD> -<BASE HREF="""" & $url & """"> +<BASE HREF="""" & base & """"> <TITLE>Directory list of """ & path & """</TITLE> </HEAD> <BODY> @@ -28,29 +38,31 @@ proc loadDir(url: URL, path: string, ostream: Stream) = for (pc, file) in fs: case pc of pcDir: - ostream.write("[DIR] ") + t handle.sendData("[DIR] ") of pcFile: - ostream.write("[FILE] ") + t handle.sendData("[FILE] ") of pcLinkToDir, pcLinkToFile: - ostream.write("[LINK] ") + t handle.sendData("[LINK] ") var fn = file if pc == pcDir: fn &= '/' - ostream.write("<A HREF=\"" & fn & "\">" & fn & "</A>") + t handle.sendData("<A HREF=\"" & fn & "\">" & fn & "</A>") if pc in {pcLinkToDir, pcLinkToFile}: - ostream.write(" -> " & expandSymlink(path / file)) - ostream.write("<br>") - ostream.write(""" + discard handle.sendData(" -> " & expandSymlink(path / file)) + t handle.sendData("<br>") + t handle.sendData(""" </BODY> </HTML>""") - ostream.flush() -proc loadSymlink(path: string, ostream: Stream) = - ostream.swrite(0) - ostream.swrite(200) # ok - ostream.swrite(newHeaders({"Content-Type": "text/html"}.toTable())) +proc loadSymlink(handle: LoaderHandle, path: string) = + template t(body: untyped) = + if not body: + return + t handle.sendResult(0) + t handle.sendStatus(200) # ok + t handle.sendHeaders(newHeaders({"Content-Type": "text/html"}.toTable())) let sl = expandSymlink(path) - ostream.write(""" + t handle.sendData(""" <HTML> <HEAD> <TITLE>Symlink view<TITLE> @@ -59,10 +71,26 @@ proc loadSymlink(path: string, ostream: Stream) = Symbolic link to <A HREF="""" & sl & """">""" & sl & """</A></br> </BODY> </HTML>""") - ostream.flush() +proc loadFile(handle: LoaderHandle, istream: Stream) = + template t(body: untyped) = + if not body: + return + t handle.sendResult(0) + t handle.sendStatus(200) # ok + t handle.sendHeaders(newHeaders()) + while not istream.atEnd: + const bufferSize = 4096 + var buffer {.noinit.}: array[bufferSize, char] + while true: + let n = readData(istream, addr buffer[0], bufferSize) + if n == 0: + break + t handle.sendData(addr buffer[0], n) + if n < bufferSize: + break -proc loadFile*(url: URL, ostream: Stream) = +proc loadFilePath*(handle: LoaderHandle, url: URL) = when defined(windows) or defined(OS2) or defined(DOS): let path = url.path.serialize_unicode_dos() else: @@ -70,24 +98,10 @@ proc loadFile*(url: URL, ostream: Stream) = let istream = newFileStream(path, fmRead) if istream == nil: if dirExists(path): - loadDir(url, path, ostream) + handle.loadDir(url, path) elif symlinkExists(path): - loadSymlink(path, ostream) + handle.loadSymlink(path) else: - ostream.swrite(-1) # error - ostream.flush() + discard handle.sendResult(ERROR_FILE_NOT_FOUND) else: - ostream.swrite(0) - ostream.swrite(200) # ok - ostream.swrite(newHeaders()) - while not istream.atEnd: - const bufferSize = 4096 - var buffer {.noinit.}: array[bufferSize, char] - while true: - let n = readData(istream, addr buffer[0], bufferSize) - if n == 0: - break - ostream.writeData(addr buffer[0], n) - ostream.flush() - if n < bufferSize: - break + handle.loadFile(istream) diff --git a/src/io/http.nim b/src/io/http.nim index 1ebcaf72..0a5a6d79 100644 --- a/src/io/http.nim +++ b/src/io/http.nim @@ -1,11 +1,10 @@ import options -import streams import strutils import bindings/curl import io/headers +import io/loaderhandle import io/request -import ips/serialize import types/blob import types/formdata import types/url @@ -13,26 +12,27 @@ import utils/opt import utils/twtstr type - HandleData* = ref HandleDataObj - HandleDataObj = object + CurlHandle* = ref CurlHandleObj + CurlHandleObj = object curl*: CURL statusline: bool headers: Headers request: Request - ostream*: Stream + handle*: LoaderHandle mime: curl_mime slist: curl_slist -func newHandleData(curl: CURL, request: Request, ostream: Stream): HandleData = - let handleData = HandleData( +func newCurlHandle(curl: CURL, request: Request, handle: LoaderHandle): + CurlHandle = + return CurlHandle( headers: newHeaders(), curl: curl, - ostream: ostream, + handle: handle, request: request ) - return handleData -proc cleanup*(handleData: HandleData) = +proc cleanup*(handleData: CurlHandle) = + handleData.handle.close() if handleData.mime != nil: curl_mime_free(handleData.mime) if handleData.slist != nil: @@ -48,58 +48,51 @@ template setopt(curl: CURL, opt: CURLoption, arg: string) = template getinfo(curl: CURL, info: CURLINFO, arg: typed) = discard curl_easy_getinfo(curl, info, arg) -proc curlWriteHeader(p: cstring, size: csize_t, nitems: csize_t, userdata: pointer): csize_t {.cdecl.} = +proc curlWriteHeader(p: cstring, size: csize_t, nitems: csize_t, + userdata: pointer): csize_t {.cdecl.} = var line = newString(nitems) for i in 0..<nitems: line[i] = p[i] - let op = cast[HandleData](userdata) + let op = cast[CurlHandle](userdata) if not op.statusline: op.statusline = true - try: - op.ostream.swrite(int(CURLE_OK)) - except IOError: # Broken pipe + if not op.handle.sendResult(int(CURLE_OK)): return 0 var status: clong op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status) - op.ostream.swrite(cast[int](status)) + if not op.handle.sendStatus(cast[int](status)): + return 0 return nitems let k = line.until(':') if k.len == line.len: # empty line (last, before body) or invalid (=> error) - op.ostream.swrite(op.headers) + if not op.handle.sendHeaders(op.headers): + return 0 return nitems let v = line.substr(k.len + 1).strip() op.headers.add(k, v) return nitems -proc curlWriteBody(p: cstring, size: csize_t, nmemb: csize_t, userdata: pointer): csize_t {.cdecl.} = - let handleData = cast[HandleData](userdata) +# From the documentation: size is always 1. +proc curlWriteBody(p: cstring, size: csize_t, nmemb: csize_t, + userdata: pointer): csize_t {.cdecl.} = + let handleData = cast[CurlHandle](userdata) if nmemb > 0: - try: - handleData.ostream.writeData(p, int(nmemb)) - except IOError: # Broken pipe + if not handleData.handle.sendData(p, int(nmemb)): return 0 return nmemb -proc applyPostBody(curl: CURL, request: Request, handleData: HandleData) = +proc applyPostBody(curl: CURL, request: Request, handleData: CurlHandle) = if request.multipart.isOk: handleData.mime = curl_mime_init(curl) - if handleData.mime == nil: - # fail (TODO: raise?) - handleData.ostream.swrite(-1) - handleData.ostream.flush() - return + doAssert handleData.mime != nil for entry in request.multipart.get: let part = curl_mime_addpart(handleData.mime) - if part == nil: - # fail (TODO: raise?) - handleData.ostream.swrite(-1) - handleData.ostream.flush() - return + doAssert part != nil curl_mime_name(part, cstring(entry.name)) if entry.isstr: curl_mime_data(part, cstring(entry.svalue), csize_t(entry.svalue.len)) @@ -116,15 +109,13 @@ proc applyPostBody(curl: CURL, request: Request, handleData: HandleData) = curl.setopt(CURLOPT_POSTFIELDS, cstring(request.body.get)) curl.setopt(CURLOPT_POSTFIELDSIZE, request.body.get.len) -proc loadHttp*(curlm: CURLM, request: Request, ostream: Stream): HandleData = +proc loadHttp*(handle: LoaderHandle, curlm: CURLM, + request: Request): CurlHandle = let curl = curl_easy_init() - if curl == nil: - ostream.swrite(-1) - ostream.flush() - return # fail + doAssert curl != nil let surl = request.url.serialize() curl.setopt(CURLOPT_URL, surl) - let handleData = curl.newHandleData(request, ostream) + let handleData = curl.newCurlHandle(request, handle) curl.setopt(CURLOPT_WRITEDATA, handleData) curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody) curl.setopt(CURLOPT_HEADERDATA, handleData) @@ -146,8 +137,6 @@ proc loadHttp*(curlm: CURLM, request: Request, ostream: Stream): HandleData = curl.setopt(CURLOPT_HTTPHEADER, handleData.slist) let res = curl_multi_add_handle(curlm, curl) if res != CURLM_OK: - ostream.swrite(int(res)) - ostream.flush() - #TODO: raise here? - return + discard handle.sendResult(int(res)) + return nil return handleData diff --git a/src/io/loader.nim b/src/io/loader.nim index 46f694e6..8e125b31 100644 --- a/src/io/loader.nim +++ b/src/io/loader.nim @@ -12,18 +12,21 @@ # The body is passed to the stream as-is, so effectively nothing can follow it. import nativesockets +import net import options +import posix import streams +import strutils import tables -import net -when defined(posix): - import posix import bindings/curl +import data/charset import io/about +import io/connecterror import io/file import io/headers import io/http +import io/loaderhandle import io/posixstream import io/promise import io/request @@ -34,9 +37,9 @@ import ips/serversocket import ips/socketstream import js/javascript import types/cookie -import types/mime import types/referer import types/url +import utils/mimeguess import utils/twtstr export request @@ -62,14 +65,9 @@ type response: Response bodyRead: Promise[string] - ConnectErrorCode* = enum - ERROR_SOURCE_NOT_FOUND = (-4, "clone source could not be found"), - ERROR_LOADER_KILLED = (-3, "loader killed during transfer"), - ERROR_DISALLOWED_URL = (-2, "url not allowed by filter"), - ERROR_UNKNOWN_SCHEME = (-1, "unknown scheme") - LoaderCommand = enum - LOAD, QUIT + LOAD + QUIT LoaderContext = ref object ssock: ServerSocket @@ -77,7 +75,7 @@ type curlm: CURLM config: LoaderConfig extra_fds: seq[curl_waitfd] - handleList: seq[HandleData] + handleList: seq[CurlHandle] LoaderConfig* = object defaultheaders*: Headers @@ -91,39 +89,36 @@ type FetchPromise* = Promise[Result[Response, JSError]] -converter toInt*(code: ConnectErrorCode): int = - return int(code) - proc addFd(ctx: LoaderContext, fd: int, flags: int) = ctx.extra_fds.add(curl_waitfd( fd: cast[cint](fd), events: cast[cshort](flags) )) -proc loadResource(ctx: LoaderContext, request: Request, ostream: Stream) = +proc loadResource(ctx: LoaderContext, request: Request, handle: LoaderHandle) = case request.url.scheme of "file": - loadFile(request.url, ostream) - ostream.close() + handle.loadFilePath(request.url) + handle.close() of "http", "https": - let handleData = loadHttp(ctx.curlm, request, ostream) + let handleData = handle.loadHttp(ctx.curlm, request) if handleData != nil: ctx.handleList.add(handleData) of "about": - loadAbout(request, ostream) - ostream.close() + handle.loadAbout(request) + handle.close() else: - ostream.swrite(ERROR_UNKNOWN_SCHEME) # error - ostream.close() + discard handle.sendResult(ERROR_UNKNOWN_SCHEME) + handle.close() proc onLoad(ctx: LoaderContext, stream: Stream) = var request: Request stream.sread(request) if not ctx.config.filter.match(request.url): - stream.swrite(ERROR_DISALLOWED_URL) # error - stream.flush() + stream.swrite(ERROR_DISALLOWED_URL) stream.close() else: + let handle = newLoaderHandle(stream, request.canredir) for k, v in ctx.config.defaultHeaders.table: if k notin request.headers.table: request.headers.table[k] = v @@ -138,7 +133,7 @@ proc onLoad(ctx: LoaderContext, stream: Stream) = request.headers["Referer"] = r if request.proxy == nil or not ctx.config.acceptProxy: request.proxy = ctx.config.proxy - ctx.loadResource(request, stream) + ctx.loadResource(request, handle) proc acceptConnection(ctx: LoaderContext) = #TODO TODO TODO acceptSocketStream should be non-blocking here, @@ -160,15 +155,10 @@ proc acceptConnection(ctx: LoaderContext) = # (TODO: this is probably not a very good idea.) stream.close() -proc finishCurlTransfer(ctx: LoaderContext, handleData: HandleData, res: int) = +proc finishCurlTransfer(ctx: LoaderContext, handleData: CurlHandle, res: int) = if res != int(CURLE_OK): - try: - handleData.ostream.swrite(int(res)) - handleData.ostream.flush() - except IOError: # Broken pipe - discard + discard handleData.handle.sendResult(int(res)) discard curl_multi_remove_handle(ctx.curlm, handleData.curl) - handleData.ostream.close() handleData.cleanup() proc exitLoader(ctx: LoaderContext) = @@ -192,7 +182,9 @@ proc initLoaderContext(fd: cint, config: LoaderConfig): LoaderContext = config: config ) gctx = ctx - ctx.ssock = initServerSocket() + #TODO ideally, buffered would be true. Unfortunately this conflicts with + # sendFileHandle/recvFileHandle. + ctx.ssock = initServerSocket(buffered = false) # The server has been initialized, so the main process can resume execution. var writef: File if not open(writef, FileHandle(fd), fmWrite): @@ -235,11 +227,39 @@ proc runFileLoader*(fd: cint, config: LoaderConfig) = ctx.handleList.del(idx) ctx.exitLoader() -proc applyHeaders(request: Request, response: Response) = +proc getAttribute(contentType, attrname: string): string = + let kvs = contentType.after(';') + var i = kvs.find(attrname) + var s = "" + if i != -1 and kvs.len > i + attrname.len and + kvs[i + attrname.len] == '=': + i += attrname.len + 1 + while i < kvs.len and kvs[i] in AsciiWhitespace: + inc i + var q = false + for j in i ..< kvs.len: + if q: + s &= kvs[j] + else: + if kvs[j] == '\\': + q = true + elif kvs[j] == ';' or kvs[j] in AsciiWhitespace: + break + else: + s &= kvs[j] + return s + +proc applyHeaders(loader: FileLoader, request: Request, response: Response) = if "Content-Type" in response.headers.table: - response.contenttype = response.headers.table["Content-Type"][0].until(';') + #TODO this is inefficient and broken on several levels. (In particular, + # it breaks mailcap named attributes other than charset.) + # Ideally, contentType would be a separate object type. + let header = response.headers.table["Content-Type"][0].toLowerAscii() + response.contenttype = header.until(';').strip().toLowerAscii() + response.charset = getCharset(header.getAttribute("charset")) else: - response.contenttype = guessContentType($response.url.path) + response.contenttype = guessContentType($response.url.path, + "application/octet-stream", DefaultGuess) if "Location" in response.headers.table: if response.status in 301u16..303u16 or response.status in 307u16..308u16: let location = response.headers.table["Location"][0] @@ -276,6 +296,18 @@ proc fetch*(loader: FileLoader, input: Request): FetchPromise = const BufferSize = 4096 +proc handleHeaders(loader: FileLoader, request: Request, response: Response, + stream: Stream): bool = + var status: int + stream.sread(status) + response.status = cast[uint16](status) + response.headers = newHeaders() + stream.sread(response.headers) + loader.applyHeaders(request, response) + # Only a stream of the response body may arrive after this point. + response.body = stream + return true # success + proc onConnected*(loader: FileLoader, fd: int) = let connectData = loader.connecting[fd] let stream = connectData.stream @@ -283,8 +315,8 @@ proc onConnected*(loader: FileLoader, fd: int) = let request = connectData.request var res: int stream.sread(res) - if res == 0: - let response = newResponse(res, request, fd, stream) + let response = newResponse(res, request, fd, stream) + if res == 0 and loader.handleHeaders(request, response, stream): assert loader.unregisterFun != nil let realCloseImpl = stream.closeImpl stream.closeImpl = nil @@ -293,12 +325,6 @@ proc onConnected*(loader: FileLoader, fd: int) = loader.unregistered.add(fd) loader.unregisterFun(fd) realCloseImpl(stream) - var status: int - stream.sread(status) - response.status = cast[uint16](status) - stream.sread(response.headers) - applyHeaders(request, response) - response.body = stream loader.ongoing[fd] = OngoingData( response: response, readbufsize: BufferSize, @@ -339,31 +365,23 @@ proc onRead*(loader: FileLoader, fd: int) = proc onError*(loader: FileLoader, fd: int) = loader.onRead(fd) -proc doRequest*(loader: FileLoader, request: Request, blocking = true): Response = - new(result) - result.url = request.url +proc doRequest*(loader: FileLoader, request: Request, blocking = true, + canredir = false): Response = + let response = Response(url: request.url) let stream = connectSocketStream(loader.process, false, blocking = true) + if canredir: + request.canredir = true #TODO set this somewhere else? stream.swrite(LOAD) stream.swrite(request) stream.flush() - stream.sread(result.res) - if result.res == 0: - var status: int - stream.sread(status) - result.status = cast[uint16](status) - stream.sread(result.headers) - applyHeaders(request, result) - # Only a stream of the response body may arrive after this point. - result.body = stream - if not blocking: - stream.source.getFd().setBlocking(blocking) + stream.sread(response.res) + if response.res == 0: + if loader.handleHeaders(request, response, stream): + if not blocking: + stream.source.getFd().setBlocking(blocking) + return response proc quit*(loader: FileLoader) = let stream = connectSocketStream(loader.process) if stream != nil: stream.swrite(QUIT) - -func getLoaderErrorMessage*(code: int): string = - if code < 0: - return $ConnectErrorCode(code) - return $curl_easy_strerror(CURLcode(cint(code))) diff --git a/src/io/loaderhandle.nim b/src/io/loaderhandle.nim new file mode 100644 index 00000000..077b1a2a --- /dev/null +++ b/src/io/loaderhandle.nim @@ -0,0 +1,73 @@ +import net +import streams + +import io/posixstream +import io/headers +import ips/serialize +import ips/socketstream + +type LoaderHandle* = ref object + ostream: Stream + # Only the first handle can be redirected, because a) mailcap can only + # redirect the first handle and b) async redirects would result in race + # conditions that would be difficult to untangle. + canredir: bool + sostream: Stream # saved ostream when redirected + +# Create a new loader handle, with the output stream ostream. +proc newLoaderHandle*(ostream: Stream, canredir: bool): LoaderHandle = + return LoaderHandle(ostream: ostream, canredir: canredir) + +proc getFd*(handle: LoaderHandle): int = + return int(SocketStream(handle.ostream).source.getFd()) + +proc sendResult*(handle: LoaderHandle, res: int): bool = + try: + handle.ostream.swrite(res) + return true + except IOError: # broken pipe + return false + +proc sendStatus*(handle: LoaderHandle, status: int): bool = + try: + handle.ostream.swrite(status) + return true + except IOError: # broken pipe + return false + +proc sendHeaders*(handle: LoaderHandle, headers: Headers): bool = + try: + handle.ostream.swrite(headers) + if handle.canredir: + var redir: bool + handle.ostream.sread(redir) + if redir: + let fd = SocketStream(handle.ostream).recvFileHandle() + handle.sostream = handle.ostream + let stream = newPosixStream(fd) + handle.ostream = stream + return true + except IOError: # broken pipe + return false + +proc sendData*(handle: LoaderHandle, p: pointer, nmemb: int): bool = + try: + handle.ostream.writeData(p, nmemb) + return true + except IOError: # broken pipe + return false + +proc sendData*(handle: LoaderHandle, s: string): bool = + if s.len > 0: + return handle.sendData(unsafeAddr s[0], s.len) + return true + +proc close*(handle: LoaderHandle) = + if handle.sostream != nil: + try: + handle.sostream.swrite(true) + except IOError: + # ignore error, that just means the buffer has already closed the stream + discard + handle.sostream.close() + handle.ostream.close() diff --git a/src/io/posixstream.nim b/src/io/posixstream.nim index 10fd2237..e24facde 100644 --- a/src/io/posixstream.nim +++ b/src/io/posixstream.nim @@ -30,6 +30,10 @@ proc raisePosixIOError*() = else: raise newException(IOError, $strerror(errno)) +proc psClose(s: Stream) = + let s = cast[PosixStream](s) + discard close(s.fd) + proc psReadData(s: Stream, buffer: pointer, len: int): int = assert len != 0 let s = cast[PosixStream](s) @@ -63,6 +67,7 @@ proc psAtEnd(s: Stream): bool = proc newPosixStream*(fd: FileHandle): PosixStream = return PosixStream( fd: fd, + closeImpl: psClose, readDataImpl: psReadData, writeDataImpl: psWriteData, atEndImpl: psAtEnd diff --git a/src/io/request.nim b/src/io/request.nim index 4ddd5d6d..f609360b 100644 --- a/src/io/request.nim +++ b/src/io/request.nim @@ -73,6 +73,7 @@ type destination* {.jsget.}: RequestDestination credentialsMode* {.jsget.}: CredentialsMode proxy*: URL #TODO do something with this + canredir*: bool ReadableStream* = ref object of Stream isource*: Stream @@ -154,7 +155,8 @@ proc newReadableStream*(isource: Stream): ReadableStream = func newRequest*(url: URL, httpmethod = HTTP_GET, headers = newHeaders(), body = opt(string), multipart = opt(FormData), mode = RequestMode.NO_CORS, credentialsMode = CredentialsMode.SAME_ORIGIN, - destination = RequestDestination.NO_DESTINATION, proxy: URL = nil): Request = + destination = RequestDestination.NO_DESTINATION, proxy: URL = nil, + canredir = false): Request = return Request( url: url, httpmethod: httpmethod, @@ -169,7 +171,8 @@ func newRequest*(url: URL, httpmethod = HTTP_GET, headers = newHeaders(), func newRequest*(url: URL, httpmethod = HTTP_GET, headers: seq[(string, string)] = @[], body = opt(string), - multipart = opt(FormData), mode = RequestMode.NO_CORS, proxy: URL = nil): + multipart = opt(FormData), mode = RequestMode.NO_CORS, proxy: URL = nil, + canredir = false): Request = let hl = newHeaders() for pair in headers: diff --git a/src/io/response.nim b/src/io/response.nim index b64f1504..dedddbcd 100644 --- a/src/io/response.nim +++ b/src/io/response.nim @@ -1,6 +1,7 @@ import streams import bindings/quickjs +import data/charset import io/headers import io/promise import io/request @@ -20,6 +21,7 @@ type url*: URL #TODO should be urllist? unregisterFun*: proc() bodyRead*: Promise[string] + charset*: Charset jsDestructor(Response) diff --git a/src/io/tempfile.nim b/src/io/tempfile.nim new file mode 100644 index 00000000..d99ea4dc --- /dev/null +++ b/src/io/tempfile.nim @@ -0,0 +1,18 @@ +import os + +var tmpf_seq: int +proc getTempFile*(tmpdir: string, ext = ""): string = + if not dirExists(tmpdir): + createDir(tmpdir) + var tmpf = tmpdir / "chatmp" & $tmpf_seq + if ext != "": + tmpf &= "." + tmpf &= ext + while fileExists(tmpf): + inc tmpf_seq + tmpf = tmpdir / "chatmp" & $tmpf_seq + if ext != "": + tmpf &= "." + tmpf &= ext + inc tmpf_seq + return tmpf |