import Middleware from '../Middleware.ts' import {Request} from '../Request.ts' import {Injectable} from '../../../../di/src/decorator/Injection.ts' import {file, http} from '../response/helpers.ts' import {HTTPStatus} from '../../const/http.ts' import {Logging} from '../../service/logging/Logging.ts' import {UniversalPath} from "../../support/UniversalPath.ts"; /** * Daton-provided middleware that serves files from a static directory. * @extends Middleware */ @Injectable() export default class StaticServer extends Middleware { constructor( protected readonly logger: Logging, ) { super() } /** * Handle an incoming request. Get the path to the file from the route params, and * resolve it using the asset_dir argument. If the file exists, serve it. * @param {Request} request * @param {string} asset_dir */ public async handleRequest(request: Request, asset_dir?: string) { if ( !asset_dir ) { throw new Error(`This static server is mis-configured. You must provide, as an argument in the route definition, the relative path to the base directory of the static files to be served.`) } const params = request.route.params const rel_file = params.$1 if ( !rel_file || !rel_file.trim() || rel_file.trim().startsWith('/') || rel_file.trim().startsWith('.') ) { this.logger.info(`Blocked attempt to access invalid static file path: "${rel_file}"`) return http(HTTPStatus.NOT_FOUND) } const abs_file = this.resolve(asset_dir, rel_file) if ( !(await this.fileExists(abs_file)) ) { this.logger.debug(`File does not exist: ${abs_file}`) return http(HTTPStatus.NOT_FOUND) } return file(abs_file) } /** * Given the asset base dir and a relative path, resolve the fully-qualified * path to the file. * @param {string} asset_dir * @param {string} rel_file * @return string */ protected resolve(asset_dir: string, rel_file: string): UniversalPath { return this.app.app_path(asset_dir, rel_file) } /** * Resolves true if the given path exists, and is a file. * @param {string} path * @return Promise */ protected async fileExists(path: UniversalPath): Promise { try { const stat = await Deno.lstat(path.to_local) return stat && stat.isFile } catch (e) { if ( e && e instanceof Deno.errors.NotFound ) { return false } else { throw e } } } }