How to serve file for URL with a given prefix?

Viewed 293

Context

I have been serving files via Vapor (v4) successfully with:

func configure(_ app: Application) throws {
   // ...
   app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
   // ...
}

Problem

  • This attempts to map any/every URL that would match a file within publicDirectory.
  • How would one limit the "reach" of the FileMiddleware to only requests whose URL have a specific prefix? (say /files/)

As in, if I have /Public/foobar.txt, one would request it via GET /files/foobar.txt while GET /foobar.txt would NOT match anything.

Considered approaches

  • My understanding is that Middleware is applied server-wide (affects every "route") and not a subset ... so this wouldn't work.
  • Moving /Public/foobar.txt to /Public/files/foobar.txt is NOT a solution as I'm trying to limit URLs that can potentially get mapped to the file system...
  • Redirecting GET /files/foobar.txt to GET /foobar.txt is also not acceptable. Again, I need to limit the potential URLs which are mapped to the file system.
2 Answers

It seems that FileMiddleware will only work globally and not if attached to groups of routes as is usually the case with instances of middleware.

If you have a folder called Private in the project folder (i.e. the same folder holding your Public folder), then accessing files contained in it is straightforward:

public func configure(_ app: Application) throws {
    app.get("files", "**") { req -> Response in
        let filepath = req.parameters.getCatchall()
        let workPath = DirectoryConfiguration.detect().workingDirectory
        var fileURL = URL(fileURLWithPath: workPath)
            .appendingPathComponent("Private", isDirectory: true)
            .appendingPathComponent(filepath.joined(separator: "/"), isDirectory: false)
        return req.fileio.streamFile(at: fileURL.path)
    }
}

Assuming you are running this minimal project on localhost:8080, it will serve files via the URL:

http://localhost:8080/files/path/to/myFile.txt

EDIT

OP indicates flat files only. In the light of comment, made it work with arbitrary paths under Private/. I'll leave you to add action if file/path does not exist.

Not sure of its validity, but I came up with the following after many more hours of searching for documentation:

app.get("files", "**") { req - > Response in
    // get rest of URL directly from `req.url.path`
    // ensure clean/secure URL and map it to file path using `DirectoryConfiguration`
    req.fileio.streamFile(at: "/path/to/files")
}

Aside some security concerns with making sure the URL is clean (i.e. not some "/../../" garbage), this should do I think.

Related