diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config/mailcap.nim | 15 | ||||
-rw-r--r-- | src/io/dynstream.nim | 28 | ||||
-rw-r--r-- | src/io/stdio.nim | 25 | ||||
-rw-r--r-- | src/loader/loader.nim | 1 | ||||
-rw-r--r-- | src/local/client.nim | 129 | ||||
-rw-r--r-- | src/local/pager.nim | 343 | ||||
-rw-r--r-- | src/server/forkserver.nim | 1 |
7 files changed, 262 insertions, 280 deletions
diff --git a/src/config/mailcap.nim b/src/config/mailcap.nim index d1c103b5..8da44955 100644 --- a/src/config/mailcap.nim +++ b/src/config/mailcap.nim @@ -306,16 +306,14 @@ proc unquoteCommand*(ecmd, contentType, outpath: string; url: URL): string = var canpipe: bool return unquoteCommand(ecmd, contentType, outpath, url, canpipe) -proc getMailcapEntry*(mailcap: Mailcap; contentType, outpath: string; url: URL): - ptr MailcapEntry = +proc findMailcapEntry*(mailcap: var Mailcap; contentType, outpath: string; + url: URL): int = let mt = contentType.until('/') - if mt.len + 1 >= contentType.len: - return nil let st = contentType.until(AsciiWhitespace + {';'}, mt.len + 1) - for entry in mailcap: - if not (entry.mt.len == 1 and entry.mt[0] == '*') and entry.mt != mt: + for i, entry in mailcap.mpairs: + if entry.mt != "*" and not entry.mt.equalsIgnoreCase(mt): continue - if not (entry.subt.len == 1 and entry.subt[0] == '*') and entry.subt != st: + if entry.subt != "*" and not entry.subt.equalsIgnoreCase(st): continue if entry.test != "": var canpipe = true @@ -324,4 +322,5 @@ proc getMailcapEntry*(mailcap: Mailcap; contentType, outpath: string; url: URL): continue if execCmd(cmd) != 0: continue - return unsafeAddr entry + return i + return -1 diff --git a/src/io/dynstream.nim b/src/io/dynstream.nim index 4138f153..56faf202 100644 --- a/src/io/dynstream.nim +++ b/src/io/dynstream.nim @@ -163,6 +163,34 @@ method sclose*(s: PosixStream) = discard close(s.fd) s.closed = true +proc closeHandle(fd, flags: cint) = + let devnull = open("/dev/null", flags) + doAssert devnull != -1 + if devnull != fd: + discard dup2(devnull, fd) + discard close(devnull) + +proc closeStdin*() = + closeHandle(0, O_RDONLY) + +proc closeStdout*() = + closeHandle(1, O_WRONLY) + +proc closeStderr*() = + closeHandle(2, O_WRONLY) + +# When closing, ensure that no standard input stream ends up without a +# handle to write to. +#TODO do we really need this? I'm pretty sure I dup2 to every stream on +# fork in all processes... +proc safeClose*(ps: PosixStream) = + if ps.fd == 0: + closeStdin() + elif ps.fd == 1 or ps.fd == 2: + closeHandle(ps.fd, O_WRONLY) + else: + ps.sclose() + proc newPosixStream*(fd: FileHandle): PosixStream = return PosixStream(fd: cint(fd), blocking: true) diff --git a/src/io/stdio.nim b/src/io/stdio.nim deleted file mode 100644 index 729b50f6..00000000 --- a/src/io/stdio.nim +++ /dev/null @@ -1,25 +0,0 @@ -import std/posix - -proc closeHandle(fd, flags: cint) = - let devnull = open("/dev/null", flags) - doAssert devnull != -1 - if devnull != fd: - discard dup2(devnull, fd) - discard close(devnull) - -proc closeStdin*() = - closeHandle(0, O_RDONLY) - -proc closeStdout*() = - closeHandle(1, O_WRONLY) - -proc closeStderr*() = - closeHandle(2, O_WRONLY) - -proc safeClose*(fd: cint) = - if fd == 0: - closeStdin() - elif fd == 1 or fd == 2: - closeHandle(fd, O_WRONLY) - else: - discard close(fd) diff --git a/src/loader/loader.nim b/src/loader/loader.nim index 54ecb77d..64fc6899 100644 --- a/src/loader/loader.nim +++ b/src/loader/loader.nim @@ -34,7 +34,6 @@ import io/bufwriter import io/dynstream import io/poll import io/serversocket -import io/stdio import io/tempfile import io/urlfilter import loader/connecterror diff --git a/src/local/client.nim b/src/local/client.nim index 01688f9e..e090f0f4 100644 --- a/src/local/client.nim +++ b/src/local/client.nim @@ -57,7 +57,6 @@ type Client* = ref object of Window alive: bool config {.jsget.}: Config - consoleWrapper: ConsoleWrapper feednext: bool pager {.jsget.}: Pager pressed: tuple[col, row: int] @@ -67,14 +66,6 @@ type ContainerData = ref object of MapData container: Container - ConsoleWrapper = object - console: Console - container: Container - prev: Container - -func console(client: Client): Console {.jsfget.} = - return client.consoleWrapper.console - template pollData(client: Client): PollData = client.pager.pollData @@ -84,6 +75,12 @@ template forkserver(client: Client): ForkServer = template readChar(client: Client): char = client.pager.term.readChar() +template consoleWrapper(client: Client): ConsoleWrapper = + client.pager.consoleWrapper + +func console(client: Client): Console {.jsfget.} = + return client.consoleWrapper.console + proc interruptHandler(rt: JSRuntime; opaque: pointer): cint {.cdecl.} = let client = cast[Client](opaque) if client.console == nil or client.pager.term.istream == nil: @@ -363,16 +360,6 @@ proc input(client: Client): EmptyPromise = p.resolve() return p -proc showConsole(client: Client) {.jsfunc.} = - let container = client.consoleWrapper.container - if client.pager.container != container: - client.consoleWrapper.prev = client.pager.container - client.pager.setContainer(container) - -proc hideConsole(client: Client) {.jsfunc.} = - if client.pager.container == client.consoleWrapper.container: - client.pager.setContainer(client.consoleWrapper.prev) - proc consoleBuffer(client: Client): Container {.jsfget.} = return client.consoleWrapper.container @@ -411,33 +398,34 @@ proc acceptBuffers(client: Client) = let key = pager.addLoaderClient(container.process, container.loaderConfig, container.clonedFrom) let loader = pager.loader - stream.withPacketWriter w: - w.swrite(key) - if item.fdin != -1: - let outputId = item.istreamOutputId - if container.cacheId == -1: - container.cacheId = loader.addCacheFile(outputId, loader.clientPid) - if container.request.url.scheme == "cache": - # loading from cache; now both the buffer and us hold a new reference - # to the cached item, but it's only shared with the buffer. add a - # pager ref too. - loader.shareCachedItem(container.cacheId, loader.clientPid) - var outCacheId = container.cacheId - let pid = container.process - if item.fdout == item.fdin: - loader.shareCachedItem(container.cacheId, pid) - loader.resume(item.istreamOutputId) - else: - outCacheId = loader.addCacheFile(item.ostreamOutputId, pid) - loader.resume([item.istreamOutputId, item.ostreamOutputId]) + if item.istreamOutputId != -1: # new buffer + if container.cacheId == -1: + container.cacheId = loader.addCacheFile(item.istreamOutputId, + loader.clientPid) + if container.request.url.scheme == "cache": + # loading from cache; now both the buffer and us hold a new reference + # to the cached item, but it's only shared with the buffer. add a + # pager ref too. + loader.shareCachedItem(container.cacheId, loader.clientPid) + let pid = container.process + var outCacheId = container.cacheId + if not item.redirected: + loader.shareCachedItem(container.cacheId, pid) + loader.resume(item.istreamOutputId) + else: + outCacheId = loader.addCacheFile(item.ostreamOutputId, pid) + loader.resume([item.istreamOutputId, item.ostreamOutputId]) + stream.withPacketWriter w: + w.swrite(key) w.swrite(outCacheId) - if item.fdin != -1: - # pass down fdout + # pass down ostream # must come after the previous block so the first packet is flushed - stream.sendFileHandle(item.fdout) - discard close(item.fdout) + stream.sendFileHandle(FileHandle(item.ostream.fd)) + item.ostream.sclose() container.setStream(stream, registerFun) - else: + else: # cloned buffer + stream.withPacketWriter w: + w.swrite(key) # buffer is cloned, just share the parent's cached source loader.shareCachedItem(container.cacheId, container.process) # also add a reference here; it will be removed when the container is @@ -516,11 +504,7 @@ proc handleWrite(client: Client; fd: int) = client.pollData.register(fd, POLLIN) proc flushConsole*(client: Client) {.jsfunc.} = - if client.console == nil: - # hack for when client crashes before console has been initialized - client.consoleWrapper = ConsoleWrapper( - console: newConsole(newDynFileStream(stderr)) - ) + client.pager.flushConsole() client.handleRead(client.forkserver.estream.fd) proc handleError(client: Client; fd: int) = @@ -544,14 +528,14 @@ proc handleError(client: Client; fd: int) = client.pollData.unregister(fd) client.loader.unset(fd) doAssert client.consoleWrapper.container != nil - client.showConsole() + client.pager.showConsole() else: discard client.loader.onError(fd) #TODO handle connection error? elif fd in client.loader.unregistered: discard # already unregistered... else: doAssert client.consoleWrapper.container != nil - client.showConsole() + client.pager.showConsole() let SIGWINCH {.importc, header: "<signal.h>", nodecl.}: cint @@ -684,40 +668,6 @@ proc readFile(client: Client; path: string): string {.jsfunc.} = proc writeFile(client: Client; path, content: string) {.jsfunc.} = writeFile(path, content) -const ConsoleTitle = "Browser Console" - -proc addConsole(pager: Pager; interactive: bool; clearFun, showFun, hideFun: - proc()): ConsoleWrapper = - if interactive and pager.config.start.console_buffer: - var pipefd: array[0..1, cint] - if pipe(pipefd) == -1: - raise newException(Defect, "Failed to open console pipe.") - let url = newURL("stream:console").get - let container = pager.readPipe0("text/plain", CHARSET_UNKNOWN, pipefd[0], - url, ConsoleTitle, {}) - let err = newPosixStream(pipefd[1]) - err.write("Type (M-c) console.hide() to return to buffer mode.\n") - let console = newConsole(err, clearFun, showFun, hideFun) - return ConsoleWrapper(console: console, container: container) - else: - let err = newPosixStream(stderr.getFileHandle()) - return ConsoleWrapper(console: newConsole(err)) - -proc clearConsole(client: Client) = - var pipefd: array[0..1, cint] - if pipe(pipefd) == -1: - raise newException(Defect, "Failed to open console pipe.") - let url = newURL("stream:console").get - let pager = client.pager - let replacement = pager.readPipe0("text/plain", CHARSET_UNKNOWN, pipefd[0], - url, ConsoleTitle, {}) - replacement.replace = client.consoleWrapper.container - pager.replace(client.consoleWrapper.container, replacement) - client.consoleWrapper.container = replacement - let console = client.consoleWrapper.console - console.err.sclose() - console.err = newPosixStream(pipefd[1]) - proc dumpBuffers(client: Client) = client.headlessLoop() for container in client.pager.containers: @@ -751,14 +701,6 @@ proc launchClient*(client: Client; pages: seq[string]; client.loader.unregisterFun = proc(fd: int) = pager.pollData.unregister(fd) pager.launchPager(istream) - let clearFun = proc() = - client.clearConsole() - let showFun = proc() = - client.showConsole() - let hideFun = proc() = - client.hideConsole() - client.consoleWrapper = pager.addConsole(interactive = istream != nil, - clearFun, showFun, hideFun) client.timeouts = newTimeoutState(client.jsctx, evalJSFree2, client) client.pager.timeouts = client.timeouts addExitProc((proc() = client.cleanup())) @@ -773,7 +715,8 @@ proc launchClient*(client: Client; pages: seq[string]; if not stdin.isatty(): # stdin may very well receive ANSI text let contentType = contentType.get("text/x-ansi") - client.pager.readPipe(contentType, cs, stdin.getFileHandle(), "*stdin*") + let ps = newPosixStream(STDIN_FILENO) + client.pager.readPipe(contentType, cs, ps, "*stdin*") for page in pages: client.pager.loadURL(page, ctype = contentType, cs = cs) client.pager.showAlerts() diff --git a/src/local/pager.nim b/src/local/pager.nim index c3e1a8d1..d4999d63 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -15,9 +15,9 @@ import io/bufreader import io/dynstream import io/poll import io/promise -import io/stdio import io/tempfile import io/urlfilter +import js/console import js/timeout import layout/renderdocument import loader/connecterror @@ -71,14 +71,12 @@ type lmBufferFile = "(Upload)Filename: " lmAlert = "Alert: " - # fdin is the original fd; fdout may be the same, or different if mailcap - # is used. ProcMapItem = object container*: Container - fdin*: FileHandle - fdout*: FileHandle + ostream*: PosixStream istreamOutputId*: int ostreamOutputId*: int + redirected*: bool PagerAlertState = enum pasNormal, pasAlertOn, pasLoadInfo @@ -115,6 +113,11 @@ type redraw: bool grid: FixedGrid + ConsoleWrapper* = object + console*: Console + container*: Container + prev*: Container + Pager* = ref object alertState: PagerAlertState alerts*: seq[string] @@ -124,6 +127,7 @@ type askprompt: string commandMode {.jsget.}: bool config*: Config + consoleWrapper*: ConsoleWrapper container*: Container cookiejars: Table[string, CookieJar] devRandom: PosixStream @@ -159,6 +163,7 @@ type jsDestructor(Pager) # Forward declarations +proc addConsole(pager: Pager; interactive: bool): ConsoleWrapper proc alert*(pager: Pager; msg: string) proc getLineHist(pager: Pager; mode: LineMode): LineHistory @@ -376,6 +381,7 @@ proc launchPager*(pager: Pager; istream: PosixStream) = pager.alert("Failed to query DA1, please set display.query-da1 = false") pager.clearDisplay() pager.clearStatus() + pager.consoleWrapper = pager.addConsole(interactive = istream != nil) proc buffer(pager: Pager): Container {.jsfget, inline.} = return pager.container @@ -868,8 +874,6 @@ proc dupeBuffer(pager: Pager; container: Container; url: URL) = pager.addContainer(container) pager.procmap.add(ProcMapItem( container: container, - fdin: -1, - fdout: -1, istreamOutputId: -1, ostreamOutputId: -1 )) @@ -1433,12 +1437,19 @@ proc loadURL*(pager: Pager; url: string; ctype = none(string); if container != nil: container.retry = urls +proc createPipe(pager: Pager): (PosixStream, PosixStream) = + var pipefds {.noinit.}: array[2, cint] + if pipe(pipefds) == -1: + pager.alert("Failed to create pipe") + return (nil, nil) + return (newPosixStream(pipefds[0]), newPosixStream(pipefds[1])) + proc readPipe0*(pager: Pager; contentType: string; cs: Charset; - fd: FileHandle; url: URL; title: string; flags: set[ContainerFlag]): + ps: PosixStream; url: URL; title: string; flags: set[ContainerFlag]): Container = var url = url - pager.loader.passFd(url.pathname, fd) - safeClose(fd) + pager.loader.passFd(url.pathname, FileHandle(ps.fd)) + ps.safeClose() var loaderConfig: LoaderClientConfig var ourl: URL let bufferConfig = pager.applySiteconf(url, cs, loaderConfig, ourl) @@ -1451,14 +1462,65 @@ proc readPipe0*(pager: Pager; contentType: string; cs: Charset; contentType = some(contentType) ) -proc readPipe*(pager: Pager; contentType: string; cs: Charset; fd: FileHandle; +proc readPipe*(pager: Pager; contentType: string; cs: Charset; ps: PosixStream; title: string) = let url = newURL("stream:-").get - let container = pager.readPipe0(contentType, cs, fd, url, title, + let container = pager.readPipe0(contentType, cs, ps, url, title, {cfCanReinterpret, cfUserRequested}) inc pager.numload pager.addContainer(container) +const ConsoleTitle = "Browser Console" + +proc showConsole*(pager: Pager) = + let container = pager.consoleWrapper.container + if pager.container != container: + pager.consoleWrapper.prev = pager.container + pager.setContainer(container) + +proc hideConsole(pager: Pager) = + if pager.container == pager.consoleWrapper.container: + pager.setContainer(pager.consoleWrapper.prev) + +proc clearConsole(pager: Pager) = + let (pins, pouts) = pager.createPipe() + if pins != nil: + let url = newURL("stream:console").get + let replacement = pager.readPipe0("text/plain", CHARSET_UNKNOWN, pins, + url, ConsoleTitle, {}) + replacement.replace = pager.consoleWrapper.container + pager.replace(pager.consoleWrapper.container, replacement) + pager.consoleWrapper.container = replacement + let console = pager.consoleWrapper.console + console.err.sclose() + console.err = pouts + +proc addConsole(pager: Pager; interactive: bool): ConsoleWrapper = + if interactive and pager.config.start.console_buffer: + let (pins, pouts) = pager.createPipe() + if pins != nil: + let clearFun = proc() = + pager.clearConsole() + let showFun = proc() = + pager.showConsole() + let hideFun = proc() = + pager.hideConsole() + let url = newURL("stream:console").get + let container = pager.readPipe0("text/plain", CHARSET_UNKNOWN, pins, + url, ConsoleTitle, {}) + pouts.write("Type (M-c) console.hide() to return to buffer mode.\n") + let console = newConsole(pouts, clearFun, showFun, hideFun) + return ConsoleWrapper(console: console, container: container) + let err = newPosixStream(STDERR_FILENO) + return ConsoleWrapper(console: newConsole(err)) + +proc flushConsole*(pager: Pager) = + if pager.consoleWrapper.console == nil: + # hack for when client crashes before console has been initialized + pager.consoleWrapper = ConsoleWrapper( + console: newConsole(newDynFileStream(stderr)) + ) + proc command(pager: Pager) {.jsfunc.} = pager.setLineEdit(lmCommand) @@ -1679,77 +1741,63 @@ proc externFilterSource(pager: Pager; cmd: string; c: Container = nil; let fallback = pager.container.contentType.get("text/plain") let contentType = contentType.get(fallback) let container = pager.newContainerFrom(fromc, contentType) - if contentType == "text/html": - container.flags.incl(cfIsHTML) - else: - container.flags.excl(cfIsHTML) pager.addContainer(container) container.filter = BufferFilter(cmd: cmd) type CheckMailcapResult = object - fdout: int + ostream: PosixStream ostreamOutputId: int connect: bool ishtml: bool found: bool + redirected: bool # whether or not ostream is the same as istream template myFork(): cint = stdout.flushFile() stderr.flushFile() fork() -# Pipe output of an x-ansioutput mailcap command to the text/x-ansi handler. -proc ansiDecode(pager: Pager; url: URL; ishtml: var bool; fdin: cint): cint = - let entry = pager.config.external.mailcap.getMailcapEntry("text/x-ansi", "", - url) - var canpipe = true - let cmd = unquoteCommand(entry.cmd, "text/x-ansi", "", url, canpipe) - if not canpipe: - pager.alert("Error: could not pipe to text/x-ansi, decoding as text/plain") - return -1 - var pipefdOutAnsi: array[2, cint] - if pipe(pipefdOutAnsi) == -1: - pager.alert("Error: failed to open pipe") - return - case myFork() +proc execPipe(pager: Pager; cmd: string; ps, os, closeme: PosixStream): int = + case (let pid = myFork(); pid) of -1: - pager.alert("Error: failed to fork ANSI decoder process") - discard close(pipefdOutAnsi[0]) - discard close(pipefdOutAnsi[1]) + pager.alert("Failed to fork for " & cmd) + os.sclose() return -1 - of 0: # child process - discard close(pipefdOutAnsi[0]) - discard dup2(fdin, stdin.getFileHandle()) - discard close(fdin) - discard dup2(pipefdOutAnsi[1], stdout.getFileHandle()) - discard close(pipefdOutAnsi[1]) + of 0: + discard dup2(ps.fd, STDIN_FILENO) + ps.sclose() + discard dup2(os.fd, STDOUT_FILENO) + os.sclose() closeStderr() + closeme.sclose() + for it in pager.loader.data: + if it.stream.fd > 2: + it.stream.sclose() myExec(cmd) else: - discard close(pipefdOutAnsi[1]) - discard close(fdin) - ishtml = mfHtmloutput in entry.flags - return pipefdOutAnsi[0] + os.sclose() + return pid -# Pipe input into the mailcap command, then read its output into a buffer. -# needsterminal is ignored. -proc runMailcapReadPipe(pager: Pager; stream: SocketStream; cmd: string; - pipefdOut: array[2, cint]): int = - let pid = myFork() +# Pipe output of an x-ansioutput mailcap command to the text/x-ansi handler. +proc ansiDecode(pager: Pager; url: URL; ishtml: var bool; istream: PosixStream): + PosixStream = + let i = pager.config.external.mailcap.findMailcapEntry("text/x-ansi", "", url) + if i == -1: + pager.alert("No text/x-ansi entry found") + return nil + var canpipe = true + let cmd = unquoteCommand(pager.config.external.mailcap[i].cmd, "text/x-ansi", + "", url, canpipe) + if not canpipe: + pager.alert("Error: could not pipe to text/x-ansi, decoding as text/plain") + return nil + let (pins, pouts) = pager.createPipe() + if pins == nil: + return nil + let pid = pager.execPipe(cmd, istream, pouts, pins) if pid == -1: - pager.alert("Error: failed to fork mailcap read process") - return -1 - elif pid == 0: - # child process - discard close(pipefdOut[0]) - discard dup2(stream.fd, stdin.getFileHandle()) - stream.sclose() - discard dup2(pipefdOut[1], stdout.getFileHandle()) - closeStderr() - discard close(pipefdOut[1]) - myExec(cmd) - # parent - pid + return nil + return pins # Pipe input into the mailcap command, and discard its output. # If needsterminal, leave stderr and stdout open and wait for the process. @@ -1780,7 +1828,7 @@ proc writeToFile(istream: SocketStream; outpath: string): bool = let ps = newPosixStream(outpath, O_WRONLY or O_CREAT, 0o600) if ps == nil: return false - var buffer: array[4096, uint8] + var buffer {.noinit.}: array[4096, uint8] while true: let n = istream.recvData(buffer) if n == 0: @@ -1793,23 +1841,27 @@ proc writeToFile(istream: SocketStream; outpath: string): bool = # new buffer. # needsterminal is ignored. proc runMailcapReadFile(pager: Pager; stream: SocketStream; - cmd, outpath: string; pipefdOut: array[2, cint]): int = - let pid = myFork() - if pid == 0: + cmd, outpath: string; pins, pouts: PosixStream): int = + case (let pid = myFork(); pid) + of -1: + pager.alert("Error: failed to fork mailcap read process") + pouts.sclose() + return pid + of 0: # child process - discard close(pipefdOut[0]) - discard dup2(pipefdOut[1], stdout.getFileHandle()) - discard close(pipefdOut[1]) + pins.sclose() + discard dup2(pouts.fd, stdout.getFileHandle()) + pouts.sclose() closeStderr() if not stream.writeToFile(outpath): - #TODO print error message quit(1) stream.sclose() let ret = execCmd(cmd) discard tryRemoveFile(outpath) quit(ret) - # parent - pid + else: # parent + pouts.sclose() + return pid # Save input in a file, run the command, and discard its output. # If needsterminal, leave stderr and stdout open and wait for the process. @@ -1833,7 +1885,6 @@ proc runMailcapWriteFile(pager: Pager; stream: SocketStream; closeStdout() closeStderr() if not stream.writeToFile(outpath): - #TODO print error message (maybe in parent?) quit(1) stream.sclose() let ret = execCmd(cmd) @@ -1842,39 +1893,26 @@ proc runMailcapWriteFile(pager: Pager; stream: SocketStream; # parent stream.sclose() -proc filterBuffer(pager: Pager; stream: SocketStream; cmd: string; +proc filterBuffer(pager: Pager; ps: SocketStream; cmd: string; ishtml: bool): CheckMailcapResult = pager.setEnvVars() - var pipefd_out: array[2, cint] - if pipe(pipefd_out) == -1: - pager.alert("Error: failed to open pipe") - return CheckMailcapResult(connect: false, fdout: -1) - let pid = myFork() + let (pins, pouts) = pager.createPipe() + if pins == nil: + return CheckMailcapResult(connect: false) + let pid = pager.execPipe(cmd, ps, pouts, pins) if pid == -1: - pager.alert("Error: failed to fork buffer filter process") - return CheckMailcapResult(connect: false, fdout: -1) - elif pid == 0: - # child - discard close(pipefd_out[0]) - discard dup2(stream.fd, stdin.getFileHandle()) - stream.sclose() - discard dup2(pipefd_out[1], stdout.getFileHandle()) - closeStderr() - discard close(pipefd_out[1]) - myExec(cmd) - # parent - discard close(pipefd_out[1]) - let fdout = pipefd_out[0] + return CheckMailcapResult(connect: false) let url = parseURL("stream:" & $pid).get - pager.loader.passFd(url.pathname, FileHandle(fdout)) - safeClose(fdout) + pager.loader.passFd(url.pathname, FileHandle(pins.fd)) + pins.safeClose() let response = pager.loader.doRequest(newRequest(url)) return CheckMailcapResult( connect: true, - fdout: response.body.fd, + ostream: response.body, ostreamOutputId: response.outputId, ishtml: ishtml, - found: true + found: true, + redirected: true ) # Search for a mailcap entry, and if found, execute the specified command @@ -1887,42 +1925,13 @@ proc filterBuffer(pager: Pager; stream: SocketStream; cmd: string; # If needsterminal is specified, and stdout is not being read, then the # pager is suspended until the command exits. #TODO add support for edit/compose, better error handling -proc checkMailcap(pager: Pager; container: Container; stream: SocketStream; - istreamOutputId: int; contentType: string): CheckMailcapResult = - if container.filter != nil: - return pager.filterBuffer( - stream, - container.filter.cmd, - cfIsHTML in container.flags - ) - # contentType must exist, because we set it in applyResponse - let shortContentType = container.contentType.get - if shortContentType == "text/html": - # We support text/html natively, so it would make little sense to execute - # mailcap filters for it. - return CheckMailcapResult( - connect: true, - fdout: stream.fd, - ishtml: true, - found: true - ) - if shortContentType == "text/plain": - # text/plain could potentially be useful. Unfortunately, many mailcaps - # include a text/plain entry with less by default, so it's probably better - # to ignore this. - return CheckMailcapResult(connect: true, fdout: stream.fd, found: true) - #TODO callback for outpath or something - let url = container.url - let entry = pager.config.external.mailcap.getMailcapEntry(contentType, "", - url) - if entry == nil: - return CheckMailcapResult(connect: true, fdout: stream.fd, found: false) +proc checkMailcap0(pager: Pager; url: URL; stream: SocketStream; + istreamOutputId: int; contentType: string; entry: MailcapEntry): + CheckMailcapResult = let ext = url.pathname.afterLast('.') - let tempfile = pager.getTempFile(ext) - let outpath = if entry.nametemplate != "": - unquoteCommand(entry.nametemplate, contentType, tempfile, url) - else: - tempfile + var outpath = pager.getTempFile(ext) + if entry.nametemplate != "": + outpath = unquoteCommand(entry.nametemplate, contentType, outpath, url) var canpipe = true let cmd = unquoteCommand(entry.cmd, contentType, outpath, url, canpipe) var ishtml = mfHtmloutput in entry.flags @@ -1938,34 +1947,67 @@ proc checkMailcap(pager: Pager; container: Container; stream: SocketStream; pager.runMailcapWriteFile(stream, needsterminal, cmd, outpath) # stream is already closed break needsConnect # never connect here, since there's no output - var pipefdOut: array[2, cint] - if pipe(pipefdOut) == -1: - pager.alert("Error: failed to open pipe") + var (pins, pouts) = pager.createPipe() + if pins == nil: stream.sclose() # connect: false implies that we consumed the stream break needsConnect let pid = if canpipe: - pager.runMailcapReadPipe(stream, cmd, pipefdOut) - else: - pager.runMailcapReadFile(stream, cmd, outpath, pipefdOut) - discard close(pipefdOut[1]) # close write - let fdout = if not ishtml and mfAnsioutput in entry.flags: - pager.ansiDecode(url, ishtml, pipefdOut[0]) + # Pipe input into the mailcap command, then read its output into a buffer. + # needsterminal is ignored. + pager.execPipe(cmd, stream, pouts, pins) else: - pipefdOut[0] + pager.runMailcapReadFile(stream, cmd, outpath, pins, pouts) + stream.sclose() + if pid == -1: + break needsConnect + if not ishtml and mfAnsioutput in entry.flags: + pins = pager.ansiDecode(url, ishtml, pins) delEnv("MAILCAP_URL") let url = parseURL("stream:" & $pid).get - pager.loader.passFd(url.pathname, FileHandle(fdout)) - safeClose(cint(fdout)) + pager.loader.passFd(url.pathname, FileHandle(pins.fd)) + pins.safeClose() let response = pager.loader.doRequest(newRequest(url)) return CheckMailcapResult( connect: true, - fdout: response.body.fd, + ostream: response.body, ostreamOutputId: response.outputId, ishtml: ishtml, - found: true + found: true, + redirected: true ) delEnv("MAILCAP_URL") - return CheckMailcapResult(connect: false, fdout: -1, found: true) + return CheckMailcapResult(connect: false, found: true) + +proc checkMailcap(pager: Pager; container: Container; stream: SocketStream; + istreamOutputId: int; contentType: string): CheckMailcapResult = + # contentType must exist, because we set it in applyResponse + let shortContentType = container.contentType.get + if container.filter != nil: + return pager.filterBuffer( + stream, + container.filter.cmd, + shortContentType.equalsIgnoreCase("text/html") + ) + if shortContentType.equalsIgnoreCase("text/html"): + # We support text/html natively, so it would make little sense to execute + # mailcap filters for it. + return CheckMailcapResult( + connect: true, + ostream: stream, + ishtml: true, + found: true + ) + if shortContentType.equalsIgnoreCase("text/plain"): + # text/plain could potentially be useful. Unfortunately, many mailcaps + # include a text/plain entry with less by default, so it's probably better + # to ignore this. + return CheckMailcapResult(connect: true, ostream: stream, found: true) + let url = container.url + let i = pager.config.external.mailcap.findMailcapEntry(contentType, "", url) + if i == -1: + return CheckMailcapResult(connect: true, ostream: stream, found: false) + return pager.checkMailcap0(url, stream, istreamOutputId, contentType, + pager.config.external.mailcap[i]) proc redirectTo(pager: Pager; container: Container; request: Request) = let replaceBackup = if container.replaceBackup != nil: @@ -2076,13 +2118,10 @@ proc connected(pager: Pager; container: Container; response: Response) = mailcapRes.ishtml, container.charsetStack ) - if mailcapRes.fdout != istream.fd: - # istream has been redirected into a filter - istream.sclose() pager.procmap.add(ProcMapItem( container: container, - fdout: FileHandle(mailcapRes.fdout), - fdin: FileHandle(istream.fd), + ostream: mailcapRes.ostream, + redirected: mailcapRes.redirected, ostreamOutputId: mailcapRes.ostreamOutputId, istreamOutputId: response.outputId )) diff --git a/src/server/forkserver.nim b/src/server/forkserver.nim index 06c930f6..0190a425 100644 --- a/src/server/forkserver.nim +++ b/src/server/forkserver.nim @@ -10,7 +10,6 @@ import io/bufreader import io/bufwriter import io/dynstream import io/serversocket -import io/stdio import loader/loader import loader/loaderiface import server/buffer |