You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

77 lines
2.6 KiB

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<boolean>
*/
protected async fileExists(path: UniversalPath): Promise<boolean> {
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
}
}
}
}