2023-03-30 13:15:48 +00:00
|
|
|
import * as plugins from '../typedserver.plugins.js';
|
|
|
|
import * as interfaces from '../interfaces/index.js';
|
|
|
|
|
|
|
|
import { Handler } from './classes.handler.js';
|
2024-01-09 09:14:06 +00:00
|
|
|
import { Compressor, type TCompressionMethod, type ICompressionResult } from './classes.compressor.js';
|
2023-03-30 13:15:48 +00:00
|
|
|
|
|
|
|
export class HandlerStatic extends Handler {
|
2024-01-07 13:50:14 +00:00
|
|
|
public compressor = new Compressor();
|
2023-03-30 13:15:48 +00:00
|
|
|
constructor(
|
|
|
|
pathArg: string,
|
|
|
|
optionsArg?: {
|
|
|
|
requestModifier?: interfaces.TRequestModifier;
|
|
|
|
responseModifier?: interfaces.TResponseModifier;
|
|
|
|
headers?: { [key: string]: string };
|
|
|
|
serveIndexHtmlDefault?: boolean;
|
2024-01-09 09:14:06 +00:00
|
|
|
enableCompression?: boolean;
|
2024-01-09 09:25:03 +00:00
|
|
|
preferredCompressionMethod?: TCompressionMethod;
|
2023-03-30 13:15:48 +00:00
|
|
|
}
|
|
|
|
) {
|
|
|
|
super('GET', async (req, res) => {
|
|
|
|
let requestPath = req.path;
|
|
|
|
let requestHeaders = req.headers;
|
|
|
|
let requestBody = req.body;
|
|
|
|
let travelData: unknown;
|
|
|
|
if (optionsArg && optionsArg.requestModifier) {
|
|
|
|
const modifiedRequest = await optionsArg.requestModifier({
|
|
|
|
headers: requestHeaders,
|
|
|
|
path: requestPath,
|
|
|
|
body: requestBody,
|
|
|
|
});
|
|
|
|
|
|
|
|
requestHeaders = modifiedRequest.headers;
|
|
|
|
requestPath = modifiedRequest.path;
|
|
|
|
requestBody = modifiedRequest.body;
|
|
|
|
travelData = modifiedRequest.travelData;
|
|
|
|
}
|
|
|
|
|
|
|
|
// lets compute some paths
|
|
|
|
let filePath: string = requestPath.slice(req.route.path.length - 1); // lets slice of the root
|
|
|
|
if (requestPath === '') {
|
|
|
|
console.log('replaced root with index.html');
|
|
|
|
filePath = 'index.html';
|
|
|
|
}
|
|
|
|
console.log(filePath);
|
|
|
|
const joinedPath = plugins.path.join(pathArg, filePath);
|
|
|
|
const defaultPath = plugins.path.join(pathArg, 'index.html');
|
|
|
|
let parsedPath = plugins.path.parse(joinedPath);
|
|
|
|
let usedPath: string;
|
|
|
|
|
|
|
|
// important security checks
|
|
|
|
if (
|
|
|
|
requestPath.includes('..') || // don't allow going up the filePath
|
|
|
|
requestPath.includes('~') || // don't allow referencing of home directory
|
|
|
|
!joinedPath.startsWith(pathArg) // make sure the joined path is within the directory
|
|
|
|
) {
|
|
|
|
res.writeHead(500);
|
|
|
|
res.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set additional headers
|
|
|
|
if (optionsArg && optionsArg.headers) {
|
|
|
|
for (const key of Object.keys(optionsArg.headers)) {
|
|
|
|
res.set(key, optionsArg.headers[key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// lets actually care about serving, if security checks pass
|
2024-01-07 13:50:14 +00:00
|
|
|
let fileBuffer: Buffer;
|
2023-03-30 13:15:48 +00:00
|
|
|
try {
|
2024-01-07 13:50:14 +00:00
|
|
|
fileBuffer = plugins.smartfile.fs.toBufferSync(joinedPath);
|
2023-03-30 13:15:48 +00:00
|
|
|
usedPath = joinedPath;
|
|
|
|
} catch (err) {
|
|
|
|
// try serving index.html instead
|
|
|
|
console.log(`could not resolve ${joinedPath}`);
|
|
|
|
if (optionsArg && optionsArg.serveIndexHtmlDefault) {
|
|
|
|
console.log(`serving default path ${defaultPath} instead of ${joinedPath}`);
|
|
|
|
try {
|
|
|
|
parsedPath = plugins.path.parse(defaultPath);
|
2024-01-07 13:50:14 +00:00
|
|
|
fileBuffer = plugins.smartfile.fs.toBufferSync(defaultPath);
|
2023-03-30 13:15:48 +00:00
|
|
|
usedPath = defaultPath;
|
|
|
|
} catch (err) {
|
|
|
|
res.writeHead(500);
|
|
|
|
res.end('File not found!');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
res.writeHead(500);
|
|
|
|
res.end('File not found!');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
res.type(parsedPath.ext);
|
|
|
|
|
|
|
|
const headers = res.getHeaders();
|
|
|
|
|
|
|
|
// lets modify the response at last
|
|
|
|
if (optionsArg && optionsArg.responseModifier) {
|
|
|
|
const modifiedResponse = await optionsArg.responseModifier({
|
|
|
|
headers: res.getHeaders(),
|
|
|
|
path: usedPath,
|
2024-01-07 13:50:14 +00:00
|
|
|
responseContent: fileBuffer,
|
2023-03-30 13:15:48 +00:00
|
|
|
travelData,
|
|
|
|
});
|
|
|
|
|
|
|
|
// headers
|
|
|
|
for (const key of Object.keys(res.getHeaders())) {
|
|
|
|
if (!modifiedResponse.headers[key]) {
|
|
|
|
res.removeHeader(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const key of Object.keys(modifiedResponse.headers)) {
|
|
|
|
res.setHeader(key, modifiedResponse.headers[key]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// responseContent
|
2024-01-07 13:50:14 +00:00
|
|
|
fileBuffer = modifiedResponse.responseContent;
|
2023-03-30 13:15:48 +00:00
|
|
|
}
|
|
|
|
|
2024-01-07 13:50:14 +00:00
|
|
|
// lets finally deal with compression
|
2024-01-09 09:14:06 +00:00
|
|
|
let compressionResult: ICompressionResult;
|
|
|
|
|
|
|
|
if (optionsArg && optionsArg.enableCompression) {
|
|
|
|
compressionResult = await this.compressor.maybeCompress(requestHeaders, fileBuffer);
|
|
|
|
}
|
2024-01-07 13:50:14 +00:00
|
|
|
|
2023-03-30 13:15:48 +00:00
|
|
|
res.status(200);
|
2024-01-09 09:14:06 +00:00
|
|
|
if (compressionResult?.compressionMethod) {
|
|
|
|
res.header('Content-Encoding', compressionResult.compressionMethod);
|
|
|
|
}
|
2024-01-07 13:50:14 +00:00
|
|
|
res.write(compressionResult.result);
|
2023-03-30 13:15:48 +00:00
|
|
|
res.end();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|