diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bindings/curl.nim | 2 | ||||
-rw-r--r-- | src/loader/dirlist.nim | 60 | ||||
-rw-r--r-- | src/loader/file.nim | 47 | ||||
-rw-r--r-- | src/loader/ftp.nim | 58 |
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] <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] ") + 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 |