joeblau/gitignore.io

View on GitHub
Sources/App/Middleware/FileMiddlewareWithBasePrefix.swift

Summary

Maintainability
A
35 mins
Test Coverage
/// Serves static files from a public directory.
///
///     middlewareConfig = MiddlewareConfig()
///     middlewareConfig.use(FileMiddlewareWithBasePrefix.self)
///     services.register(middlewareConfig)
///
/// `FileMiddlewareWithBasePrefix` will default to `DirectoryConfig`'s working directory with `"/Public"` appended.
import Vapor

public final class FileMiddlewareWithBasePrefix: Middleware, ServiceType {
    /// See `ServiceType`.
    public static func makeService(for container: Container) throws -> FileMiddlewareWithBasePrefix {
        return try .init(publicDirectory: container.make(DirectoryConfig.self).workDir + "Public/")
    }

    /// The public directory.
    /// - note: Must end with a slash.
    private let publicDirectory: String

    /// Creates a new `FileMiddlewareWithBasePrefix`.
    public init(publicDirectory: String) {
        self.publicDirectory = publicDirectory.hasSuffix("/") ? publicDirectory : publicDirectory + "/"
    }

    /// See `Middleware`.
    public func respond(to req: Request, chainingTo next: Responder) throws -> Future<Response> {
        // make a copy of the path
        var path = req.http.url.path

        if let basePrefix = Environment.get("BASE_PREFIX") {
            if path.hasPrefix(basePrefix) {
                path = String(path.dropFirst(basePrefix.count))
            }
        }

        // path must be relative.
        while path.hasPrefix("/") {
            path = String(path.dropFirst())
        }

        // protect against relative paths
        guard !path.contains("../") else {
            throw Abort(.forbidden)
        }

        // create absolute file path
        let filePath = publicDirectory + path

        // check if file exists and is not a directory
        var isDir: ObjCBool = false
        guard FileManager.default.fileExists(atPath: filePath, isDirectory: &isDir), !isDir.boolValue else {
            return try next.respond(to: req)
        }

        // stream the file
        return try req.streamFile(at: filePath)
    }
}