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
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
|
|
}
|
|
}
|
|
}
|
|
}
|