Web File Server and Web File Manager

The Web File Server (WFS) exposes one Barracuda I/O interface through three complementary interfaces:

The server implementation is the Lua module wfs.lua. The browser client is an ES module in /rtl/wfm/wfm.js, with default styles in /rtl/wfm/wfm.css. The JSON service and WebDAV server operate on the same I/O object, so changes made through one interface are immediately visible through the others.

Security: A writable file server is a sensitive application surface. Install an authenticator, and normally an authorizer, before exposing it on a network. A session URL is a bearer credential and must be protected in the same way as a password.

Loading the module

local wfs = require"wfs"

Loading the module performs two actions:

  1. It installs the backward-compatible function ba.create.wfs.
  2. It returns a table containing wfs.create and wfs.wfm.

Construction modes

ConstructionCommandless directory GETTypical use
ba.create.wfs(...) Returns the standard full-page WFM. A ready-to-use standalone file manager, JSON service, and WebDAV server at one mount point.
wfs.create(...) Returns HTTP 400 JSON with {"err":"missingcmd"}. A JSON/WebDAV service used by a WFM embedded in another page.
wfs.create(..., wfs.wfm(title)) Returns the standard full-page WFM. An explicitly constructed standalone manager with a custom page title.
wfs.create(..., pagefunc) Calls the supplied function. A custom page shell that owns layout, styling, and plugin assembly.

All four forms expose the same JSON/HTTP and WebDAV operations. The only difference is how a directory GET without a cmd argument is handled.

Server constructors

ba.create.wfs([name] [,priority], io [,lockdir] [,maxuploads, maxlocks])

Creates a ready-to-use standalone WFS. Its public interface supplies a lazily created and shared standard WFM page callback. Use wfs.create when a different page callback is required.

wfs.create([name] [,priority], io [,lockdir] [,maxuploads, maxlocks] [,pagefunc])

Creates a WFS without adding the standard page callback. Supply wfs.wfm() or your own pagefunc when direct browser navigation to WFS directory URLs should produce a page.

Shared arguments

name
Optional VFS directory name, such as "fs" for a server mounted at /fs/.
priority
Optional VFS search priority. The default is 0.
io
The required I/O interface containing the files. Create it with ba.openio or ba.mkio. The I/O object must be writable to support uploads, renames, and deletion.
lockdir
Optional directory used by the underlying WebDAV lock manager. Create it before mounting the WFS. A dot-prefixed name such as .LOCK keeps it out of ordinary directory listings.
maxuploads
Maximum number of concurrent uploads. The default is 5.
maxlocks
Maximum number of WebDAV locks. The default is 20. When specifying limits, provide both numbers.

wfs.create page callback

The optional pagefunc argument belongs to wfs.create, not ba.create.wfs. It is called for a commandless directory GET and receives the request command environment and the path relative to the WFS: pagefunc(_ENV, relpath). WFS authentication and authorization run before this callback. The callback is not used for files, JSON commands, uploads, or WebDAV methods.

Standalone example

local wfs = require"wfs"
local io = ba.openio"home"
local lockdir = ".LOCK"

if not io:stat(lockdir) then
   assert(io:mkdir(lockdir))
end

-- ba.create.wfs includes the ready-to-use full-page WFM.
app.files = ba.create.wfs("fs", io, lockdir)
app.files:insert()

Opening /fs/ now starts the WFM. Opening a directory URL such as /fs/documents/ starts the manager in that directory. Directory URLs are canonicalized with a trailing slash.

Custom full-page callback

local wfs = require"wfs"
local io = ba.openio"home"

app.files = wfs.create(
   "fs", io, ".LOCK", wfs.wfm"Device Files"
)
app.files:insert()

wfs.wfm([title]) returns the standard full-page callback. The default title is WFM. A completely custom callback can be supplied instead when an application needs a different page shell.

WFS object

The returned object inherits the standard directory methods, including insert, unlink, baseuri, and header configuration. Keep the object referenced for as long as it is mounted.

wfs:setauth(authenticator [,authorizer])

Installs authentication and optional authorization on both the JSON/HTTP service and the WebDAV service. Authorization uses the corresponding HTTP or WebDAV operation, such as GET, PUT, DELETE, MKCOL, and PROPFIND.

app.files:setauth(authenticator, authorizer)
app.files:configure{tmo=15*60}

wfs:configure(options)

Configures optional WFS behavior. The recognized fields are:

tmo
Maximum idle time, in seconds, for Session URLs. A nonzero timeout and an installed authenticator or authorizer enable Copy Session URL in the WFM. Set tmo to zero or omit it to disable Session URLs.
filterservice
An advanced relative-path filter. The callback receives (_ENV, relpath [,session]) and must return the relative path that WFS should serve. The optional session value is supplied when a Session URL resolves to a session.
pageaccessdenied
An optional callback for presenting an authorization failure when a commandless browser GET would otherwise open the configured WFM page. The callback receives (_ENV, relpath, method) after WFS sets status 403 and must write the response. It is not used for JSON commands, file operations, uploads, or WebDAV methods, which retain their protocol-specific error responses.
app.files:configure {
   tmo = 15 * 60,
   pageaccessdenied = function(env, relpath, method)
      env.response:setcontenttype "text/html; charset=utf-8"
      env.response:write "<h1>File access unavailable</h1>"
   end
}

The method returns a table containing the WFS io object and its authorization function for advanced integrations.

wfs:service(request, relpath)

Delegates a request directly to the WFS resource reader. Normal applications mount the WFS in the virtual file system and do not call this method directly.

Session URLs

Session URLs are intended for simple WebDAV or HTTP clients that cannot perform normal authentication. When enabled, cmd=sesuri returns a URL containing a public session reference. WFS resolves that reference with ba.session and applies the configured idle timeout.

If Session URLs are unavailable, the WFM disables Copy Session URL. For an unauthenticated WFS, a Session URL would be identical to the ordinary resource URL and is therefore not generated.

Anyone possessing an unexpired Session URL can use the associated access. Avoid logging it, placing it in public pages, or sending it over an unencrypted connection.

JSON/HTTP service

The WFS browser client and NetIo use a small REST-style HTTP service. Applications normally use the supplied clients rather than calling these operations directly. Directory paths should end with /.

Directory listing contract

GET directory/?cmd=lj returns application/json containing an array. Every entry has the following stable fields:

[
  {"n":"documents", "s":-1,  "t":1785580746},
  {"n":"notes.txt", "s":319, "t":1785580800}
]
n
File or directory name relative to the requested directory.
s
File size in bytes, or -1 for a directory.
t
Last-modified time as Unix epoch seconds.

The meanings and types of n, s, and t are part of the NetIo compatibility contract and must not be changed. Future versions may add fields that older clients can ignore. Internal directories such as .LOCK and .DAV are excluded.

Directory commands

CommandArgumentsPurpose
ljNoneList a directory using the stable JSON format above.
mkdirtdirCreate a directory.
mvfrom, toRename or move a resource within the WFS.
rmtfileLegacy command for deleting a named resource. New clients use HTTP DELETE.
getlocknameReturn lock owner and expiration information for one resource.
getlocksRepeated nReturn lock state for multiple files.
locktime, repeated nLock files until the supplied Unix epoch time.
unlockRepeated nUnlock files.
sesuriNoneReturn the resource's ordinary URL or an enabled Session URL.

Commands may be supplied in the query string or as form data. Successful mutation responses use {"ok":true}. Errors use {"err":"code","emsg":"description"} with an appropriate HTTP status. An unknown command returns badcmd; a commandless directory request on a JSON-only WFS returns missingcmd.

HTTP methods

Other applicable methods are offered to the embedded WebDAV server, including PROPFIND, MKCOL, COPY, MOVE, LOCK, and UNLOCK.

NetIo compatibility

NetIo presents a remote WFS as an I/O interface. It uses:

A NetIo base URL must identify a WFS directory. NetIo probes and normalizes directory URLs with a trailing slash, and the standard WFM similarly canonicalizes direct directory URLs. NetIo can authenticate normally, or it can use an enabled Session URL as its base URL.

Web File Manager ES module

The WFM is a reusable client-side ES module for modern browsers and does not generate its interface on the server. The host page supplies one HTML element, imports the module, and calls mount. The WFS URL must have the same origin as the embedding page.

Minimal embedded manager

<div id="files"></div>
<link rel="stylesheet" href="/rtl/wfm/wfm.css">

<script type="module">
  import {mount} from "/rtl/wfm/wfm.js";

  const files = mount(document.getElementById("files"), {
    url: "/fs/"
  });

  files.ready.catch(console.error);
</script>

This example mounts the core manager only. The host page retains ownership of the surrounding layout and may override the WFM CSS custom properties.

mount(host, options)

host
The HTML element whose contents are replaced by the WFM.
options.url
Base URL of the WFS. The default is /fs/. The URL must be same-origin.
options.path
Initial directory relative to the WFS root, for example /documents/. It defaults to the page's wfm query argument and then to /.
options.history
When true, directory navigation updates the browser path with the History API, and Back/Forward navigation reopens the corresponding directory. Use this for a page served directly from WFS, not for a host page whose URL belongs to another application route.
options.plugins
Array of plugin functions activated after the core manager is created.

Manager object

manager.ready
Promise for the initial directory load.
manager.open(path)
Open a directory and return a promise.
manager.refresh()
Reload the current directory.
manager.selection()
Return the currently selected entries.
manager.destroy()
Abort pending work, run plugin cleanup, remove listeners, close the dialog, and empty the host element.

Client plugins

A plugin is a JavaScript function receiving the small WFM API. It may register commands or previews and may return a cleanup function. Plugins are assembled entirely on the client; WFS does not load a plugin manifest or execute plugin-specific server operations.

The standard module exports two opt-in plugins:

<div id="files"></div>
<link rel="stylesheet" href="/rtl/wfm/wfm.css">

<script type="module">
  import {mount, search, text} from "/rtl/wfm/wfm.js";

  const files = mount(document.getElementById("files"), {
    url: "/fs/",
    plugins: [search, text]
  });

  files.ready.catch(console.error);
</script>

The plugin API provides add("command", spec), add("preview", spec), list(path), open(path), refresh(), url(entry, options), selection(), directory(), and the shared ui.open()/ui.close() dialog surface. A function returned by add unregisters the extension.

Styling

The default stylesheet is scoped below .wfm. An application may replace it or override these supported custom properties:

The standard page produced by wfs.wfm() occupies the complete viewport. Embedded managers retain the normal border, corner radius, and configurable height so they fit into a larger application.