about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--doc/config.md23
-rw-r--r--doc/localcgi.md19
-rw-r--r--res/config.toml1
-rw-r--r--src/config/config.nim4
-rw-r--r--src/loader/cgi.nim6
-rw-r--r--src/loader/file.nim6
-rw-r--r--src/loader/loader.nim24
-rw-r--r--src/local/client.nim3
-rw-r--r--src/server/forkserver.nim6
-rw-r--r--src/types/urimethodmap.nim4
-rw-r--r--src/types/url.nim43
11 files changed, 101 insertions, 38 deletions
diff --git a/doc/config.md b/doc/config.md
index 7af5d5db..a82712c1 100644
--- a/doc/config.md
+++ b/doc/config.md
@@ -220,6 +220,21 @@ MANOFF -->
 </td>
 </tr>
 
+<tr>
+<td>w3m-cgi-compat</td>
+<td>boolean</td>
+<td>Enable local CGI compatibility with w3m. In short, it redirects
+`file:///cgi-bin/*` and `file:///$LIB/cgi-bin/*` to `cgi-bin:*`. For further
+details, see
+<!-- MANOFF -->
+[localcgi.md](localcgi.md).
+<!-- MANON -->
+<!-- MANON
+**cha-localcgi**(5).
+MANOFF -->
+</td>
+</tr>
+
 </table>
 
 ## Input
@@ -1123,10 +1138,10 @@ with a caret (^) sign or end with an unescaped dollar ($) sign.
 In other words, the following transformations occur:
 
 ```
-^abcd -> ^abcd
-efgh$ -> efgh$
-^ijkl$ -> ^ijkl$
-mnop -> ^mnop$
+^abcd -> ^abcd (no change)
+efgh$ -> efgh$ (no change)
+^ijkl$ -> ^ijkl$ (no change)
+mnop -> ^mnop$ (changed to exact match)
 ```
 <!-- MANON
 
diff --git a/doc/localcgi.md b/doc/localcgi.md
index ba7d5232..b9b3e5f5 100644
--- a/doc/localcgi.md
+++ b/doc/localcgi.md
@@ -19,14 +19,23 @@ Further notes on processing CGI paths:
 
 * The URL must be opaque, so you must not add a double slash after the scheme.
   e.g. `cgi-bin://script-name` will NOT work, only `cgi-bin:script-name`.
+* Paths beginning with `/cgi-bin/` or `/$LIB/` are stripped of this segment
+  automatically. So e.g. `file:///cgi-bin/script-name` becomes
+  `cgi-bin:script-name`.
+* If `extern.w3m-cgi-compat` is true, file: URLs are converted to cgi-bin: URLs
+  if the path name starts with `/cgi-bin/`, `/$LIB/`, or the path of a local
+  CGI script.
 * Absolute paths are accepted as e.g. `cgi-bin:/path/to/cgi/dir/script-name`.
   Note however, that this only works if `/path/to/cgi/dir` has already been
   specified as a CGI directory in `external.cgi-dir`.
 
 Note that this is different from w3m's cgi-bin functionality, in that we
 use a custom scheme for local CGI instead of interpreting all requests to
-a designated path as a CGI request. Also, for now Chawan has no equivalent
-to the W3m-control headers (but this may change in the future).
+a designated path as a CGI request. (This incompatibility is bridged over when
+`external.cgi-dir` is true.)
+
+Also, for now Chawan has no equivalent to the W3m-control headers (but this
+may change in the future).
 
 ## Environment variables
 
@@ -109,3 +118,9 @@ script's executable bit is set, i.e. run `chmod +x /path/to/cgi/script`.
 
 This means that either `pipe` or `fork` failed. Something strange is going on
 with your system; we recommend exorcism. (Maybe you are running out of memory?)
+
+<!-- MANON
+## See also
+
+**cha**(1)
+MANOFF -->
diff --git a/res/config.toml b/res/config.toml
index 0b3536a8..a1678361 100644
--- a/res/config.toml
+++ b/res/config.toml
@@ -32,6 +32,7 @@ urimethodmap = [
 ]
 tmpdir = "/tmp/cha"
 editor = "vi %s +%d"
+w3m-cgi-compat = true
 
 [network]
 max-redirect = 10
diff --git a/src/config/config.nim b/src/config/config.nim
index fb0218ce..0df6285a 100644
--- a/src/config/config.nim
+++ b/src/config/config.nim
@@ -91,6 +91,7 @@ type
     mime_types* {.jsgetset.}: seq[string]
     cgi_dir* {.jsgetset.}: seq[string]
     urimethodmap* {.jsgetset.}: seq[string]
+    w3m_cgi_compat* {.jsgetset.}: bool
 
   InputConfig = object
     vi_numeric_prefix* {.jsgetset.}: bool
@@ -232,7 +233,8 @@ proc getBufferConfig*(config: Config, location: URL, cookiejar: CookieJar,
       cookiejar: cookiejar,
       proxy: proxy,
       cgiDir: config.external.cgi_dir,
-      urimethodmap: urimethodmap
+      urimethodmap: urimethodmap,
+      w3mCGICompat: config.external.w3m_cgi_compat
     )
   )
 
diff --git a/src/loader/cgi.nim b/src/loader/cgi.nim
index d94a2243..b0341a59 100644
--- a/src/loader/cgi.nim
+++ b/src/loader/cgi.nim
@@ -54,7 +54,11 @@ proc loadCGI*(handle: LoaderHandle, request: Request, cgiDir: seq[string]) =
   if cgiDir.len == 0:
     discard handle.sendResult(ERROR_NO_CGI_DIR)
     return
-  let path = percentDecode(request.url.pathname)
+  var path = percentDecode(request.url.pathname)
+  if path.startsWith("/cgi-bin/"):
+    path.delete(0 .. "/cgi-bin/".high)
+  elif path.startsWith("/$LIB/"):
+    path.delete(0 .. "/$LIB/".high)
   if path == "" or request.url.hostname != "":
     discard handle.sendResult(ERROR_INVALID_CGI_PATH)
     return
diff --git a/src/loader/file.nim b/src/loader/file.nim
index 5d552e40..cdb7afc2 100644
--- a/src/loader/file.nim
+++ b/src/loader/file.nim
@@ -108,11 +108,7 @@ proc loadFile(handle: LoaderHandle, istream: Stream) =
       if n < bufferSize:
         break
 
-proc loadFilePath*(handle: LoaderHandle, url: URL) =
-  when defined(windows) or defined(OS2) or defined(DOS):
-    let path = url.path.serialize_unicode_dos()
-  else:
-    let path = url.path.serialize_unicode()
+proc loadFilePath*(handle: LoaderHandle, url: URL, path: string) =
   let istream = newFileStream(path, fmRead)
   if istream == nil:
     if dirExists(path):
diff --git a/src/loader/loader.nim b/src/loader/loader.nim
index a148b77b..1f313a64 100644
--- a/src/loader/loader.nim
+++ b/src/loader/loader.nim
@@ -103,6 +103,7 @@ type
     acceptProxy*: bool
     cgiDir*: seq[string]
     uriMethodMap*: URIMethodMap
+    w3mCGICompat*: bool
 
   FetchPromise* = Promise[JSResult[Response]]
 
@@ -114,6 +115,16 @@ proc addFd(ctx: LoaderContext, fd: int, flags: int) =
 
 const MaxRewrites = 2 # should be enough? TODO find out what w3m thinks
 
+func canRewriteForCGICompat(ctx: LoaderContext, path: string): bool =
+  if not ctx.config.w3mCGICompat:
+    return false
+  if path.startsWith("/cgi-bin/") or path.startsWith("/$LIB/"):
+    return true
+  for dir in ctx.config.cgiDir:
+    if path.startsWith(dir):
+      return true
+  return false
+
 proc loadResource(ctx: LoaderContext, request: Request, handle: LoaderHandle) =
   var redo = true
   var tries = 0
@@ -121,7 +132,15 @@ proc loadResource(ctx: LoaderContext, request: Request, handle: LoaderHandle) =
     redo = false
     case request.url.scheme
     of "file":
-      handle.loadFilePath(request.url)
+      let path = request.url.path.serialize_unicode()
+      if ctx.canRewriteForCGICompat(path):
+        let newURL = newURL("cgi-bin:" & path & request.url.search)
+        if newURL.isSome:
+          request.url = newURL.get
+          inc tries
+          redo = true
+          continue
+      handle.loadFilePath(request.url, path)
       handle.close()
     of "http", "https":
       let handleData = handle.loadHttp(ctx.curlm, request)
@@ -280,6 +299,9 @@ proc initLoaderContext(fd: cint, config: LoaderConfig): LoaderContext =
     discard sig
     gctx.exitLoader()
   ctx.addFd(int(ctx.ssock.sock.getFd()), CURL_WAIT_POLLIN)
+  for dir in ctx.config.cgiDir.mitems:
+    if dir.len > 0 and dir[^1] != '/':
+      dir &= '/'
   return ctx
 
 proc runFileLoader*(fd: cint, config: LoaderConfig) =
diff --git a/src/local/client.nim b/src/local/client.nim
index 8351c4b1..937b4a8b 100644
--- a/src/local/client.nim
+++ b/src/local/client.nim
@@ -631,7 +631,8 @@ proc newClient*(config: Config, forkserver: ForkServer, mainproc: Pid): Client =
       defaultHeaders = config.getDefaultHeaders(),
       proxy = config.getProxy(),
       urimethodmap = config.getURIMethodMap(),
-      acceptProxy = true
+      acceptProxy = true,
+      w3mCGICompat = config.external.w3m_cgi_compat
     ),
     jsrt: jsrt,
     jsctx: jsctx,
diff --git a/src/server/forkserver.nim b/src/server/forkserver.nim
index 5f89d6ca..80cc0338 100644
--- a/src/server/forkserver.nim
+++ b/src/server/forkserver.nim
@@ -36,14 +36,16 @@ type
     children: seq[(Pid, Pid)]
 
 proc newFileLoader*(forkserver: ForkServer, defaultHeaders: Headers,
-    proxy: URL, urimethodmap: URIMethodMap, acceptProxy: bool): FileLoader =
+    proxy: URL, urimethodmap: URIMethodMap,
+    acceptProxy, w3mCGICompat: bool): FileLoader =
   forkserver.ostream.swrite(FORK_LOADER)
   let config = LoaderConfig(
     defaultHeaders: defaultHeaders,
     filter: newURLFilter(default = true),
     proxy: proxy,
     acceptProxy: acceptProxy,
-    urimethodmap: urimethodmap
+    urimethodmap: urimethodmap,
+    w3mCGICompat: w3mCGICompat
   )
   forkserver.ostream.swrite(config)
   forkserver.ostream.flush()
diff --git a/src/types/urimethodmap.nim b/src/types/urimethodmap.nim
index 6d57230b..f49162dd 100644
--- a/src/types/urimethodmap.nim
+++ b/src/types/urimethodmap.nim
@@ -61,8 +61,12 @@ proc parseURIMethodMap*(this: var URIMethodMap, s: string) =
     while i < line.len and line[i] in AsciiWhitespace:
       inc i
     var v = line.until(AsciiWhitespace, i)
+    # Basic w3m compatibility.
+    # If needed, w3m-cgi-compat covers more cases.
     if v.startsWith("file:/cgi-bin/"):
       v = "cgi-bin:" & v.substr("file:/cgi-bin/".len)
+    elif 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/types/url.nim b/src/types/url.nim
index ab8c9e8b..562ec31b 100644
--- a/src/types/url.nim
+++ b/src/types/url.nim
@@ -351,8 +351,6 @@ proc basicParseURL*(input: string, base = none(URL), url: URL = URL(),
     i + 2 <= endi and input[i] in AsciiAlpha and input[i + 1] in {':', '|'}
   template is_normalized_windows_drive_letter(s: string): bool =
     s.len == 2 and s[0] in AsciiAlpha and s[1] == ':'
-  template is_windows_drive_letter(s: string): bool =
-    s.len == 2 and s[0] in AsciiAlpha and (s[1] == ':' or s[1] == '|')
   template is_double_dot_path_segment(s: string): bool =
     s == ".." or s.equalsIgnoreCase(".%2e") or s.equalsIgnoreCase("%2e.") or
       s.equalsIgnoreCase("%2e%2e")
@@ -779,26 +777,29 @@ func serialize*(path: URLPath): string {.inline.} =
     result &= '/'
     result &= s
 
-func serialize_unicode*(path: URLPath): string {.inline.} =
-  if path.opaque:
-    return percentDecode(path.s)
-  for s in path.ss:
-    result &= '/'
-    result &= percentDecode(s)
-
-func serialize_unicode_dos*(path: URLPath): string {.inline.} =
-  if path.opaque:
-    return percentDecode(path.s)
-  var i = 0
-  if i < path.ss.len:
-    if path.ss[i].is_windows_drive_letter:
-      result &= path.ss[i]
+when defined(windows) or defined(OS2) or defined(DOS):
+  func serialize_unicode_dos(path: URLPath): string =
+    if path.opaque:
+      return percentDecode(path.s)
+    var i = 0
+    if i < path.ss.len:
+      if path.ss[i].is_windows_drive_letter:
+        result &= path.ss[i]
+        inc i
+    while i < path.ss.len:
+      let s = path.ss[i]
+      result &= '\\'
+      result &= percentDecode(s)
       inc i
-  while i < path.ss.len:
-    let s = path.ss[i]
-    result &= '\\'
-    result &= percentDecode(s)
-    inc i
+  func serialize_unicode*(path: URLPath): string =
+    return path.serialize_unicode_dos()
+else:
+  func serialize_unicode*(path: URLPath): string =
+    if path.opaque:
+      return percentDecode(path.s)
+    for s in path.ss:
+      result &= '/'
+      result &= percentDecode(s)
 
 func serialize*(url: URL, excludefragment = false, excludepassword = false):
     string =