about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config/config.nim50
-rw-r--r--src/loader/connecterror.nim2
-rw-r--r--src/loader/loader.nim109
-rw-r--r--src/local/client.nim1
-rw-r--r--src/local/container.nim6
-rw-r--r--src/local/pager.nim10
-rw-r--r--src/server/buffer.nim7
-rw-r--r--src/server/forkserver.nim23
-rw-r--r--src/types/urimethodmap.nim68
-rw-r--r--src/utils/twtstr.nim6
10 files changed, 196 insertions, 86 deletions
diff --git a/src/config/config.nim b/src/config/config.nim
index da897133..fb0218ce 100644
--- a/src/config/config.nim
+++ b/src/config/config.nim
@@ -11,13 +11,15 @@ import js/error
 import js/javascript
 import js/regex
 import loader/headers
+import loader/loader
 import types/cell
 import types/color
 import types/cookie
+import types/opt
 import types/referer
+import types/urimethodmap
 import types/url
 import utils/mimeguess
-import types/opt
 import utils/twtstr
 
 import chakasu/charset
@@ -88,6 +90,7 @@ type
     mailcap* {.jsgetset.}: seq[string]
     mime_types* {.jsgetset.}: seq[string]
     cgi_dir* {.jsgetset.}: seq[string]
+    urimethodmap* {.jsgetset.}: seq[string]
 
   InputConfig = object
     vi_numeric_prefix* {.jsgetset.}: bool
@@ -132,15 +135,12 @@ type
 
   BufferConfig* = object
     userstyle*: string
-    filter*: URLFilter
-    cookiejar*: CookieJar
-    headers*: Headers
     referer_from*: bool
     referrerpolicy*: ReferrerPolicy
     scripting*: bool
     charsets*: seq[Charset]
     images*: bool
-    proxy*: URL
+    loaderConfig*: LoaderConfig
     mimeTypes*: MimeTypes
     cgiDir*: seq[string]
 
@@ -197,12 +197,6 @@ proc bindLineKey(config: Config, key, action: string) {.jsfunc.} =
 proc hasprop(a: ptr ActionMap, s: string): bool {.jshasprop.} =
   return s in a[]
 
-func getForkServerConfig*(config: Config): ForkServerConfig =
-  return ForkServerConfig(
-    tmpdir: config.external.tmpdir,
-    ambiguous_double: config.display.double_width_ambiguous
-  )
-
 func getProxy*(config: Config): URL =
   if config.network.proxy.isSome:
     let s = config.network.proxy.get
@@ -218,25 +212,28 @@ func getDefaultHeaders*(config: Config): Headers =
 
 proc getBufferConfig*(config: Config, location: URL, cookiejar: CookieJar,
     headers: Headers, referer_from, scripting: bool, charsets: seq[Charset],
-    images: bool, userstyle: string, proxy: URL, mimeTypes: MimeTypes):
-    BufferConfig =
+    images: bool, userstyle: string, proxy: URL, mimeTypes: MimeTypes,
+    urimethodmap: URIMethodMap): BufferConfig =
   let filter = newURLFilter(
     scheme = some(location.scheme),
     allowschemes = @["data"],
     default = true
   )
-  result = BufferConfig(
+  return BufferConfig(
     userstyle: userstyle,
-    filter: filter,
-    cookiejar: cookiejar,
-    headers: headers,
     referer_from: referer_from,
     scripting: scripting,
     charsets: charsets,
     images: images,
-    proxy: proxy,
     mimeTypes: mimeTypes,
-    cgiDir: config.external.cgi_dir
+    loaderConfig: LoaderConfig(
+      defaultHeaders: headers,
+      filter: filter,
+      cookiejar: cookiejar,
+      proxy: proxy,
+      cgiDir: config.external.cgi_dir,
+      urimethodmap: urimethodmap
+    )
   )
 
 proc getSiteConfig*(config: Config, jsctx: JSContext): seq[SiteConfig] =
@@ -369,6 +366,21 @@ proc getMimeTypes*(config: Config): MimeTypes =
     return DefaultGuess
   return mimeTypes
 
+proc getURIMethodMap*(config: Config): URIMethodMap =
+  let configDir = getConfigDir() / "chawan" #TODO store this in config?
+  var urimethodmap: URIMethodMap
+  for p in config.external.urimethodmap:
+    let f = openFileExpand(configDir, p)
+    if f != nil:
+      urimethodmap.parseURIMethodMap(f.readAll())
+  return urimethodmap
+
+proc getForkServerConfig*(config: Config): ForkServerConfig =
+  return ForkServerConfig(
+    tmpdir: config.external.tmpdir,
+    ambiguous_double: config.display.double_width_ambiguous
+  )
+
 proc parseConfig(config: Config, dir: string, stream: Stream, name = "<input>",
   laxnames = false)
 proc parseConfig*(config: Config, dir: string, s: string, name = "<input>",
diff --git a/src/loader/connecterror.nim b/src/loader/connecterror.nim
index f10285d2..8f2f95d2 100644
--- a/src/loader/connecterror.nim
+++ b/src/loader/connecterror.nim
@@ -1,6 +1,8 @@
 import bindings/curl
 
 type ConnectErrorCode* = enum
+  ERROR_TOO_MANY_REWRITES = (-14, "too many URI method map rewrites")
+  ERROR_INVALID_URI_METHOD_ENTRY = (-13, "invalid URI method entry")
   ERROR_CGI_FILE_NOT_FOUND = (-12, "CGI file not found")
   ERROR_INVALID_CGI_PATH = (-11, "invalid CGI path")
   ERROR_FAIL_SETUP_CGI = (-10, "failed to set up CGI script")
diff --git a/src/loader/loader.nim b/src/loader/loader.nim
index 809219fe..a148b77b 100644
--- a/src/loader/loader.nim
+++ b/src/loader/loader.nim
@@ -43,6 +43,7 @@ import loader/request
 import loader/response
 import types/cookie
 import types/referer
+import types/urimethodmap
 import types/url
 import utils/mimeguess
 import utils/twtstr
@@ -79,6 +80,7 @@ type
     RESUME
     ADDREF
     UNREF
+    SET_REFERRER_POLICY
 
   LoaderContext = ref object
     refcount: int
@@ -89,17 +91,18 @@ type
     extra_fds: seq[curl_waitfd]
     handleList: seq[CurlHandle]
     handleMap: Table[int, LoaderHandle]
+    referrerpolicy: ReferrerPolicy
 
   LoaderConfig* = object
     defaultheaders*: Headers
     filter*: URLFilter
     cookiejar*: CookieJar
-    referrerpolicy*: ReferrerPolicy
     proxy*: URL
     # When set to false, requests with a proxy URL are overridden by the
     # loader proxy.
     acceptProxy*: bool
     cgiDir*: seq[string]
+    uriMethodMap*: URIMethodMap
 
   FetchPromise* = Promise[JSResult[Response]]
 
@@ -109,34 +112,51 @@ proc addFd(ctx: LoaderContext, fd: int, flags: int) =
     events: cast[cshort](flags)
   ))
 
+const MaxRewrites = 2 # should be enough? TODO find out what w3m thinks
+
 proc loadResource(ctx: LoaderContext, request: Request, handle: LoaderHandle) =
-  case request.url.scheme
-  of "file":
-    handle.loadFilePath(request.url)
-    handle.close()
-  of "http", "https":
-    let handleData = handle.loadHttp(ctx.curlm, request)
-    if handleData != nil:
-      ctx.handleList.add(handleData)
-  of "about":
-    handle.loadAbout(request)
-    handle.close()
-  of "data":
-    handle.loadData(request)
-    handle.close()
-  of "ftp", "ftps", "sftp":
-    let handleData = handle.loadFtp(ctx.curlm, request)
-    if handleData != nil:
-      ctx.handleList.add(handleData)
-  of "gopher", "gophers":
-    let handleData = handle.loadGopher(ctx.curlm, request)
-    if handleData != nil:
-      ctx.handleList.add(handleData)
-  of "cgi-bin":
-    handle.loadCGI(request, ctx.config.cgiDir)
-    handle.close()
-  else:
-    discard handle.sendResult(ERROR_UNKNOWN_SCHEME)
+  var redo = true
+  var tries = 0
+  while redo and tries < MaxRewrites:
+    redo = false
+    case request.url.scheme
+    of "file":
+      handle.loadFilePath(request.url)
+      handle.close()
+    of "http", "https":
+      let handleData = handle.loadHttp(ctx.curlm, request)
+      if handleData != nil:
+        ctx.handleList.add(handleData)
+    of "about":
+      handle.loadAbout(request)
+      handle.close()
+    of "data":
+      handle.loadData(request)
+      handle.close()
+    of "ftp", "ftps", "sftp":
+      let handleData = handle.loadFtp(ctx.curlm, request)
+      if handleData != nil:
+        ctx.handleList.add(handleData)
+    of "gopher", "gophers":
+      let handleData = handle.loadGopher(ctx.curlm, request)
+      if handleData != nil:
+        ctx.handleList.add(handleData)
+    of "cgi-bin":
+      handle.loadCGI(request, ctx.config.cgiDir)
+      handle.close()
+    else:
+      case ctx.config.urimethodmap.findAndRewrite(request.url)
+      of URI_RESULT_SUCCESS:
+        inc tries
+        redo = true
+      of URI_RESULT_WRONG_URL:
+        discard handle.sendResult(ERROR_INVALID_URI_METHOD_ENTRY)
+        handle.close()
+      of URI_RESULT_NOT_FOUND:
+        discard handle.sendResult(ERROR_UNKNOWN_SCHEME)
+        handle.close()
+  if tries >= MaxRewrites:
+    discard handle.sendResult(ERROR_TOO_MANY_REWRITES)
     handle.close()
 
 proc onLoad(ctx: LoaderContext, stream: SocketStream) =
@@ -155,8 +175,8 @@ proc onLoad(ctx: LoaderContext, stream: SocketStream) =
         let cookie = ctx.config.cookiejar.serialize(request.url)
         if cookie != "":
           request.headers["Cookie"] = cookie
-    if request.referer != nil and "Referer" notin request.headers.table:
-      let r = getReferer(request.referer, request.url, ctx.config.referrerpolicy)
+    if request.referer != nil and "Referer" notin request.headers:
+      let r = getReferer(request.referer, request.url, ctx.referrerpolicy)
       if r != "":
         request.headers["Referer"] = r
     if request.proxy == nil or not ctx.config.acceptProxy:
@@ -185,15 +205,6 @@ proc acceptConnection(ctx: LoaderContext) =
         let handle = ctx.handleMap[fd]
         handle.addOutputStream(stream)
         stream.swrite(true)
-    of ADDREF:
-      inc ctx.refcount
-    of UNREF:
-      dec ctx.refcount
-      if ctx.refcount == 0:
-        ctx.alive = false
-        stream.close()
-      else:
-        assert ctx.refcount > 0
     of SUSPEND:
       var fds: seq[int]
       stream.sread(fds)
@@ -206,6 +217,18 @@ proc acceptConnection(ctx: LoaderContext) =
       for fd in fds:
         ctx.handleMap.withValue(fd, handlep):
           handlep[].resume()
+    of ADDREF:
+      inc ctx.refcount
+    of UNREF:
+      dec ctx.refcount
+      if ctx.refcount == 0:
+        ctx.alive = false
+        stream.close()
+      else:
+        assert ctx.refcount > 0
+    of SET_REFERRER_POLICY:
+      stream.sread(ctx.referrerpolicy)
+      stream.close()
   except IOError:
     # End-of-file, broken pipe, or something else. For now we just
     # ignore it and pray nothing breaks.
@@ -492,8 +515,16 @@ proc addref*(loader: FileLoader) =
   let stream = connectSocketStream(loader.process)
   if stream != nil:
     stream.swrite(ADDREF)
+  stream.close()
 
 proc unref*(loader: FileLoader) =
   let stream = connectSocketStream(loader.process)
   if stream != nil:
     stream.swrite(UNREF)
+
+proc setReferrerPolicy*(loader: FileLoader, referrerpolicy: ReferrerPolicy) =
+  let stream = connectSocketStream(loader.process)
+  if stream != nil:
+    stream.swrite(SET_REFERRER_POLICY)
+    stream.swrite(referrerpolicy)
+  stream.close()
diff --git a/src/local/client.nim b/src/local/client.nim
index 5a02d2a8..8351c4b1 100644
--- a/src/local/client.nim
+++ b/src/local/client.nim
@@ -630,6 +630,7 @@ proc newClient*(config: Config, forkserver: ForkServer, mainproc: Pid): Client =
     loader: forkserver.newFileLoader(
       defaultHeaders = config.getDefaultHeaders(),
       proxy = config.getProxy(),
+      urimethodmap = config.getURIMethodMap(),
       acceptProxy = true
     ),
     jsrt: jsrt,
diff --git a/src/local/container.nim b/src/local/container.nim
index ce049c08..d3d0b879 100644
--- a/src/local/container.nim
+++ b/src/local/container.nim
@@ -822,8 +822,10 @@ proc load(container: Container) =
       if res.code == 0:
         container.triggerEvent(SUCCESS)
         # accept cookies
-        if res.cookies.len > 0 and container.config.cookiejar != nil:
-          container.config.cookiejar.add(res.cookies)
+        let cookiejar = container.config.loaderConfig.cookiejar
+        if res.cookies.len > 0 and cookiejar != nil:
+          cookiejar.add(res.cookies)
+        # set referrer policy, if any
         if res.referrerpolicy.isSome and container.config.referer_from:
           container.config.referrerpolicy = res.referrerpolicy.get
         container.setLoadInfo("Connected to " & $container.source.location & ". Downloading...")
diff --git a/src/local/pager.nim b/src/local/pager.nim
index b07d300e..1333e32f 100644
--- a/src/local/pager.nim
+++ b/src/local/pager.nim
@@ -35,8 +35,9 @@ import types/buffersource
 import types/cell
 import types/color
 import types/cookie
-import types/url
 import types/opt
+import types/urimethodmap
+import types/url
 import utils/twtstr
 
 import chakasu/charset
@@ -82,6 +83,7 @@ type
     term*: Terminal
     tty: File
     unreg*: seq[(Pid, SocketStream)]
+    urimethodmap: URIMethodMap
     username: string
 
 jsDestructor(Pager)
@@ -211,7 +213,8 @@ proc newPager*(config: Config, attrs: WindowAttributes,
     statusgrid: newFixedGrid(attrs.width),
     term: newTerminal(stdout, config, attrs),
     mimeTypes: config.getMimeTypes(),
-    mailcap: mailcap
+    mailcap: mailcap,
+    urimethodmap: config.getURIMethodMap()
   )
   for err in errs:
     pager.alert("Error reading mailcap: " & err)
@@ -607,6 +610,7 @@ proc applySiteconf(pager: Pager, url: var URL): BufferConfig =
   var userstyle = pager.config.css.stylesheet
   var proxy = pager.proxy
   let mimeTypes = pager.mimeTypes
+  let urimethodmap = pager.urimethodmap
   for sc in pager.siteconf:
     if sc.url.isSome and not sc.url.get.match($url):
       continue
@@ -640,7 +644,7 @@ proc applySiteconf(pager: Pager, url: var URL): BufferConfig =
     if sc.proxy.isSome:
       proxy = sc.proxy.get
   return pager.config.getBufferConfig(url, cookiejar, headers, referer_from,
-    scripting, charsets, images, userstyle, proxy, mimeTypes)
+    scripting, charsets, images, userstyle, proxy, mimeTypes, urimethodmap)
 
 # Load request in a new buffer.
 proc gotoURL(pager: Pager, request: Request, prevurl = none(URL),
diff --git a/src/server/buffer.nim b/src/server/buffer.nim
index 22dd8a8a..1e97a4bb 100644
--- a/src/server/buffer.nim
+++ b/src/server/buffer.nim
@@ -37,6 +37,7 @@ import js/regex
 import js/timeout
 import layout/box
 import loader/connecterror
+import loader/headers
 import loader/loader
 import render/renderdocument
 import render/rendertext
@@ -741,8 +742,10 @@ proc connect*(buffer: Buffer): ConnectResult {.proxy.} =
         let cookie = newCookie(s, response.url)
         if cookie.isOk:
           cookies.add(cookie.get)
-    if "Referrer-Policy" in response.headers.table:
-      referrerpolicy = getReferrerPolicy(response.headers.table["Referrer-Policy"][0])
+    if "Referrer-Policy" in response.headers:
+      referrerpolicy = getReferrerPolicy(response.headers["Referrer-Policy"])
+      if referrerpolicy.isSome:
+        buffer.loader.setReferrerPolicy(referrerpolicy.get)
   buffer.connected = true
   let contentType = buffer.source.contentType.get("")
   buffer.ishtml = contentType == "text/html"
diff --git a/src/server/forkserver.nim b/src/server/forkserver.nim
index 8ece45e6..5f89d6ca 100644
--- a/src/server/forkserver.nim
+++ b/src/server/forkserver.nim
@@ -16,6 +16,7 @@ import loader/loader
 import server/buffer
 import types/buffersource
 import types/cookie
+import types/urimethodmap
 import types/url
 import utils/twtstr
 
@@ -35,16 +36,14 @@ type
     children: seq[(Pid, Pid)]
 
 proc newFileLoader*(forkserver: ForkServer, defaultHeaders: Headers,
-    filter = newURLFilter(default = true), cookiejar: CookieJar = nil,
-    proxy: URL = nil, acceptProxy = false): FileLoader =
+    proxy: URL, urimethodmap: URIMethodMap, acceptProxy: bool): FileLoader =
   forkserver.ostream.swrite(FORK_LOADER)
-  var defaultHeaders = defaultHeaders
   let config = LoaderConfig(
     defaultHeaders: defaultHeaders,
-    filter: filter,
-    cookiejar: cookiejar,
+    filter: newURLFilter(default = true),
     proxy: proxy,
-    acceptProxy: acceptProxy
+    acceptProxy: acceptProxy,
+    urimethodmap: urimethodmap
   )
   forkserver.ostream.swrite(config)
   forkserver.ostream.flush()
@@ -111,17 +110,7 @@ proc forkBuffer(ctx: var ForkServerContext): Pid =
   ctx.istream.sread(config)
   ctx.istream.sread(attrs)
   ctx.istream.sread(mainproc)
-  let loaderPid = ctx.forkLoader(
-    LoaderConfig(
-      defaultHeaders: config.headers,
-      filter: config.filter,
-      cookiejar: config.cookiejar,
-      referrerpolicy: config.referrerpolicy,
-      #TODO these should be in a separate config I think
-      proxy: config.proxy,
-      cgiDir: config.cgiDir
-    )
-  )
+  let loaderPid = ctx.forkLoader(config.loaderConfig)
   var pipefd: array[2, cint]
   if pipe(pipefd) == -1:
     raise newException(Defect, "Failed to open pipe.")
diff --git a/src/types/urimethodmap.nim b/src/types/urimethodmap.nim
new file mode 100644
index 00000000..6d57230b
--- /dev/null
+++ b/src/types/urimethodmap.nim
@@ -0,0 +1,68 @@
+# w3m's URI method map format.
+
+import strutils
+import tables
+
+import types/opt
+import types/url
+import utils/twtstr
+
+type URIMethodMap* = object
+  map: Table[string, string]
+
+func rewriteURL(pattern, surl: string): string =
+  result = ""
+  var was_perc = false
+  for c in pattern:
+    if was_perc:
+      if c == '%':
+        result &= '%'
+      elif c == 's':
+        result.percentEncode(surl, ComponentPercentEncodeSet)
+      else:
+        result &= '%'
+        result &= c
+      was_perc = false
+    elif c != '%':
+      result &= c
+    else:
+      was_perc = true
+  if was_perc:
+    result &= '%'
+
+proc `[]=`*(this: var URIMethodMap, k, v: string) =
+  this.map[k] = v
+
+type URIMethodMapResult* = enum
+  URI_RESULT_NOT_FOUND, URI_RESULT_SUCCESS, URI_RESULT_WRONG_URL
+
+proc findAndRewrite*(this: URIMethodMap, url: var URL): URIMethodMapResult =
+  let protocol = url.protocol
+  if protocol in this.map:
+    let surl = this.map[protocol].rewriteURL($url)
+    let x = newURL(surl)
+    if x.isNone:
+      return URI_RESULT_WRONG_URL
+    url = x.get
+    return URI_RESULT_SUCCESS
+  return URI_RESULT_NOT_FOUND
+
+proc parseURIMethodMap*(this: var URIMethodMap, s: string) =
+  for line in s.split('\n'):
+    if line.len == 0 or line[0] == '#':
+      continue # comments
+    var k = ""
+    var i = 0
+    while i < line.len and line[i] != ':':
+      k &= line[i].toLowerAscii()
+      inc i
+    if i >= line.len:
+      continue # invalid
+    while i < line.len and line[i] in AsciiWhitespace:
+      inc i
+    var v = line.until(AsciiWhitespace, i)
+    if v.startsWith("file:/cgi-bin/"):
+      v = "cgi-bin:" & v.substr("file:/cgi-bin/".len)
+    elif v.startsWith("/cgi-bin/"):
+      v = "cgi-bin:" & v.substr("/cgi-bin/".len)
+    this[k] = v
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index ca79fa76..d8ce9ae8 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -206,13 +206,11 @@ func skipBlanks*(buf: string, at: int): int =
   while result < buf.len and buf[result].isWhitespace():
     inc result
 
-func until*(s: string, c: set[char]): string =
-  var i = 0
-  while i < s.len:
+func until*(s: string, c: set[char], starti = 0): string =
+  for i in starti ..< s.len:
     if s[i] in c:
       break
     result.add(s[i])
-    inc i
 
 func until*(s: string, c: char): string = s.until({c})