about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bindings/curl.nim2
-rw-r--r--src/loader/dirlist.nim60
-rw-r--r--src/loader/file.nim47
-rw-r--r--src/loader/ftp.nim58
4 files changed, 131 insertions, 36 deletions
diff --git a/src/bindings/curl.nim b/src/bindings/curl.nim
index 825ab656..720b7126 100644
--- a/src/bindings/curl.nim
+++ b/src/bindings/curl.nim
@@ -267,7 +267,7 @@ type
     CURLE_UNRECOVERABLE_POLL,      # 99 - poll/select returned fatal error 
     CURL_LAST # never use! 
 
-  curl_ftpmethod* {.size: sizeof(cint).} = enum
+  curl_ftpmethod* {.size: sizeof(clong).} = enum
     CURLFTPMETHOD_DEFAULT, # let libcurl pick
     CURLFTPMETHOD_MULTICWD, # single CWD operation for each path part
     CURLFTPMETHOD_NOCWD, # no CWD at all
diff --git a/src/loader/dirlist.nim b/src/loader/dirlist.nim
new file mode 100644
index 00000000..5de79084
--- /dev/null
+++ b/src/loader/dirlist.nim
@@ -0,0 +1,60 @@
+import algorithm
+
+import utils/twtstr
+
+type DirlistItemType = enum
+  ITEM_FILE, ITEM_LINK, ITEM_DIR
+
+type DirlistItem* = object
+  name*: string
+  modified*: string
+  case t*: DirlistItemType
+  of ITEM_LINK:
+    linkto*: string
+  of ITEM_FILE:
+    nsize*: int
+  of ITEM_DIR:
+    discard
+
+type NameWidthTuple = tuple[name: string, width: int]
+
+func makeDirlist*(items: seq[DirlistItem]): string =
+  var names: seq[NameWidthTuple]
+  var maxw = 20
+  for item in items:
+    var name = item.name
+    if item.t == ITEM_LINK:
+      name &= '@'
+    elif item.t == ITEM_DIR:
+      name &= '/'
+    let w = name.width()
+    maxw = max(w, maxw)
+    names.add((name, w))
+  names.sort(proc(a, b: NameWidthTuple): int = cmp(a.name, b.name))
+  var outs = "<A HREF=\"../\">[Upper Directory]</A>\n"
+  for i in 0 ..< items.len:
+    let item = items[i]
+    var (name, width) = names[i]
+    var path = percentEncode(item.name, PathPercentEncodeSet)
+    if item.t == ITEM_LINK:
+      if item.linkto.len > 0 and item.linkto[^1] == '/':
+        # If the target is a directory, treat it as a directory. (For FTP.)
+        path &= '/'
+    elif item.t == ITEM_DIR:
+      path &= '/'
+    var line = "<A HREF=" & path & '>' & htmlEscape(name) & "</A>"
+    while width <= maxw:
+      if width mod 2 == 0:
+        line &= ' '
+      else:
+        line &= '.'
+      inc width
+    if line[^1] != ' ':
+      line &= ' '
+    line &= htmlEscape(item.modified)
+    if item.t == ITEM_FILE:
+      line &= ' ' & convert_size(item.nsize)
+    elif item.t == ITEM_LINK:
+      line &= " -> " & htmlEscape(item.linkto)
+    outs &= line & '\n'
+  return outs
diff --git a/src/loader/file.nim b/src/loader/file.nim
index b627e0c7..40a19981 100644
--- a/src/loader/file.nim
+++ b/src/loader/file.nim
@@ -2,8 +2,10 @@ import algorithm
 import os
 import streams
 import tables
+import times
 
 import loader/connecterror
+import loader/dirlist
 import loader/headers
 import loader/loaderhandle
 import types/url
@@ -29,30 +31,47 @@ proc loadDir(handle: LoaderHandle, url: URL, path: string) =
 </HEAD>
 <BODY>
 <H1>Directory list of """ & path & """</H1>
-[DIR]&nbsp; <A HREF="../">../</A></br>
+<PRE>
 """)
   var fs: seq[(PathComponent, string)]
   for pc, file in walkDir(path, relative = true):
     fs.add((pc, file))
   fs.sort(cmp = proc(a, b: (PathComponent, string)): int = cmp(a[1], b[1]))
+  var items: seq[DirlistItem]
   for (pc, file) in fs:
+    let fullpath = path / file
+    var info: FileInfo
+    try:
+      info = getFileInfo(fullpath, followSymlink = false)
+    except OSError:
+      continue
+    let modified = $info.lastWriteTime.local().format("MMM/dd/yyyy HH:MM")
     case pc
     of pcDir:
-      t handle.sendData("[DIR]&nbsp; ")
+      items.add(DirlistItem(
+        t: ITEM_DIR,
+        name: file,
+        modified: modified
+      ))
     of pcFile:
-      t handle.sendData("[FILE] ")
+      items.add(DirlistItem(
+        t: ITEM_FILE,
+        name: file,
+        modified: modified,
+        nsize: info.size
+      ))
     of pcLinkToDir, pcLinkToFile:
-      t handle.sendData("[LINK] ")
-    var fn = file
-    if pc == pcDir:
-      fn &= '/'
-    t handle.sendData("<A HREF=\"" & fn & "\">" & fn & "</A>")
-    if pc in {pcLinkToDir, pcLinkToFile}:
-      discard handle.sendData(" -> " & expandSymlink(path / file))
-    t handle.sendData("<br>")
-  t handle.sendData("""
-</BODY>
-</HTML>""")
+      var target = expandSymlink(fullpath)
+      if pc == pcLinkToDir:
+        target &= '/'
+      items.add(DirlistItem(
+        t: ITEM_LINK,
+        name: file,
+        modified: modified,
+        linkto: target
+      ))
+  t handle.sendData(makeDirlist(items))
+  t handle.sendData("\n</PRE>\n</BODY>\n</HTML>\n")
 
 proc loadSymlink(handle: LoaderHandle, path: string) =
   template t(body: untyped) =
diff --git a/src/loader/ftp.nim b/src/loader/ftp.nim
index 84693cb3..f380e5e9 100644
--- a/src/loader/ftp.nim
+++ b/src/loader/ftp.nim
@@ -4,6 +4,7 @@ import bindings/curl
 import loader/connecterror
 import loader/curlhandle
 import loader/curlwrap
+import loader/dirlist
 import loader/headers
 import loader/loaderhandle
 import loader/request
@@ -58,8 +59,7 @@ proc curlWriteHeader(p: cstring, size: csize_t, nitems: csize_t,
 <BODY>
 <H1>Index of """ & htmlEscape(op.path) & """</H1>
 <PRE>
-<A HREF="..">
-[Upper Directory]</A>"""):
+"""):
           return 0
       return nitems
     elif line.startsWith("530"): # login incorrect
@@ -76,7 +76,9 @@ proc curlWriteHeader(p: cstring, size: csize_t, nitems: csize_t,
 <HEAD>
 <TITLE>Unauthorized</TITLE>
 </HEAD>
-<BODY><PRE>""" & htmlEscape(line))
+<BODY>
+<PRE>
+""" & htmlEscape(line))
       return 0
   return nitems
 
@@ -97,6 +99,7 @@ proc curlWriteBody(p: cstring, size: csize_t, nmemb: csize_t,
 
 proc finish(op: CurlHandle) =
   let op = cast[FtpHandle](op)
+  var items: seq[DirlistItem]
   for line in op.buffer.split('\n'):
     if line.len == 0: continue
     var i = 10 # permission
@@ -123,52 +126,65 @@ proc finish(op: CurlHandle) =
     let nsize = parseInt64(sizes).get(-1)
     # date
     i = line.skipBlanks(i)
+    let datestarti = i
     skip_till_space # m
     i = line.skipBlanks(i)
     skip_till_space # d
     i = line.skipBlanks(i)
     skip_till_space # y
+    let dates = line.substr(datestarti, i)
     inc i
     let name = line.substr(i)
+    if name == "." or name == "..": continue
     case line[0]
     of 'l': # link
       let x = " -> "
       let linki = name.find(x)
       let linkfrom = name.substr(0, linki - 1)
       let linkto = name.substr(linki + 4) # you?
-      let path = percentEncode(linkfrom, PathPercentEncodeSet)
-      discard op.handle.sendData("""
-<A HREF="""" & path & """"">
-""" & htmlEscape(linkfrom) & """@ (-> """ & htmlEscape(linkto) & """)</A>""")
+      items.add(DirlistItem(
+        t: ITEM_LINK,
+        name: linkfrom,
+        modified: dates,
+        linkto: linkto
+      ))
     of 'd': # directory
-      let path = percentEncode(name, PathPercentEncodeSet)
-      discard op.handle.sendData("""
-<A HREF="""" & path & """/">
-""" & htmlEscape(name) & """/</A>""")
+      items.add(DirlistItem(
+        t: ITEM_DIR,
+        name: name,
+        modified: dates
+      ))
     else: # file
-      let path = percentEncode(name, PathPercentEncodeSet)
-      discard op.handle.sendData("""
-<A HREF="""" & path & """">
-""" & htmlEscape(name) & """ (""" & $nsize & """)</A>""")
-  discard op.handle.sendData("""
-</PRE>
-</BODY>
-</HTML>
-""")
+      items.add(DirlistItem(
+        t: ITEM_FILE,
+        name: name,
+        modified: dates,
+        nsize: nsize
+      ))
+  discard op.handle.sendData(makeDirlist(items))
+  discard op.handle.sendData("\n</PRE>\n</BODY>\n</HTML>\n")
 
 proc loadFtp*(handle: LoaderHandle, curlm: CURLM,
     request: Request): CurlHandle =
   let curl = curl_easy_init()
   doAssert curl != nil
   let surl = request.url.serialize()
-  curl.setopt(CURLOPT_URL, surl)
   let path = request.url.path.serialize_unicode()
+  # By default, cURL CWD's into relative paths, and an extra slash is
+  # necessary to specify absolute paths.
+  # This is incredibly confusing, and probably not what the user wanted.
+  # So we work around it by adding the extra slash ourselves.
+  let hackurl = newURL(request.url)
+  hackurl.setPathname('/' & request.url.pathname)
+  let csurl = hackurl.serialize()
+  curl.setopt(CURLOPT_URL, csurl)
   let dirmode = path.len > 0 and path[^1] == '/'
   let handleData = curl.newFtpHandle(request, handle, dirmode)
   curl.setopt(CURLOPT_HEADERDATA, handleData)
   curl.setopt(CURLOPT_HEADERFUNCTION, curlWriteHeader)
   curl.setopt(CURLOPT_WRITEDATA, handleData)
   curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody)
+  curl.setopt(CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD)
   if dirmode:
     handleData.finish = finish
     handleData.base = surl