Add .gitignore to exclude all node packages and lock files
This commit is contained in:
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
export declare const serveHandler: (request: IncomingMessage, response: ServerResponse<IncomingMessage>, config: {
|
||||
public: string;
|
||||
}) => Promise<void>;
|
||||
Generated
Vendored
+208
@@ -0,0 +1,208 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.serveHandler = void 0;
|
||||
const node_fs_1 = require("node:fs");
|
||||
const node_path_1 = __importDefault(require("node:path"));
|
||||
const mime_types_1 = require("../mime-types");
|
||||
// Packages
|
||||
const is_path_inside_1 = require("./is-path-inside");
|
||||
const range_parser_1 = require("./range-parser");
|
||||
const getHeaders = (absolutePath, stats) => {
|
||||
const { base } = node_path_1.default.parse(absolutePath);
|
||||
let defaultHeaders = {};
|
||||
if (stats) {
|
||||
defaultHeaders = {
|
||||
'Content-Length': String(stats.size),
|
||||
'Accept-Ranges': 'bytes',
|
||||
};
|
||||
defaultHeaders['Last-Modified'] = stats.mtime.toUTCString();
|
||||
const _contentType = (0, mime_types_1.mimeContentType)(base);
|
||||
if (_contentType) {
|
||||
defaultHeaders['Content-Type'] = _contentType;
|
||||
}
|
||||
}
|
||||
return defaultHeaders;
|
||||
};
|
||||
const getPossiblePaths = (relativePath, extension) => [
|
||||
node_path_1.default.join(relativePath, `index${extension}`),
|
||||
relativePath.endsWith('/')
|
||||
? relativePath.replace(/\/$/g, extension)
|
||||
: relativePath + extension,
|
||||
].filter((item) => node_path_1.default.basename(item) !== extension);
|
||||
const findRelated = async (current, relativePath) => {
|
||||
const possible = getPossiblePaths(relativePath, '.html');
|
||||
let stats = null;
|
||||
for (let index = 0; index < possible.length; index++) {
|
||||
const related = possible[index];
|
||||
const absolutePath = node_path_1.default.join(current, related);
|
||||
try {
|
||||
stats = await node_fs_1.promises.lstat(absolutePath);
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code !== 'ENOENT' &&
|
||||
err.code !== 'ENOTDIR') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
if (stats) {
|
||||
return {
|
||||
stats,
|
||||
absolutePath,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const sendError = (absolutePath, response, spec) => {
|
||||
const { message, statusCode } = spec;
|
||||
const headers = getHeaders(absolutePath, null);
|
||||
response.writeHead(statusCode, {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
response.end(JSON.stringify({ statusCode, message }));
|
||||
};
|
||||
const internalError = (absolutePath, response) => {
|
||||
return sendError(absolutePath, response, {
|
||||
statusCode: 500,
|
||||
code: 'internal_server_error',
|
||||
message: 'A server error has occurred',
|
||||
});
|
||||
};
|
||||
const serveHandler = async (request, response, config) => {
|
||||
const cwd = process.cwd();
|
||||
const current = node_path_1.default.resolve(cwd, config.public);
|
||||
let relativePath = null;
|
||||
try {
|
||||
const parsedUrl = new URL(request.url, `http://${request.headers.host}`);
|
||||
relativePath = decodeURIComponent(parsedUrl.pathname);
|
||||
}
|
||||
catch (_a) {
|
||||
return sendError('/', response, {
|
||||
statusCode: 400,
|
||||
code: 'bad_request',
|
||||
message: 'Bad Request',
|
||||
});
|
||||
}
|
||||
let absolutePath = node_path_1.default.join(current, relativePath);
|
||||
// Prevent path traversal vulnerabilities. We could do this
|
||||
// by ourselves, but using the package covers all the edge cases.
|
||||
if (!(0, is_path_inside_1.isPathInside)(absolutePath, current)) {
|
||||
return sendError(absolutePath, response, {
|
||||
statusCode: 400,
|
||||
code: 'bad_request',
|
||||
message: 'Bad Request',
|
||||
});
|
||||
}
|
||||
let stats = null;
|
||||
// It's extremely important that we're doing multiple stat calls. This one
|
||||
// right here could technically be removed, but then the program
|
||||
// would be slower. Because for directories, we always want to see if a related file
|
||||
// exists and then (after that), fetch the directory itself if no
|
||||
// related file was found. However (for files, of which most have extensions), we should
|
||||
// always stat right away.
|
||||
//
|
||||
// When simulating a file system without directory indexes, calculating whether a
|
||||
// directory exists requires loading all the file paths and then checking if
|
||||
// one of them includes the path of the directory. As that's a very
|
||||
// performance-expensive thing to do, we need to ensure it's not happening if not really necessary.
|
||||
if (node_path_1.default.extname(relativePath) !== '') {
|
||||
try {
|
||||
stats = await node_fs_1.promises.lstat(absolutePath);
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code !== 'ENOENT' &&
|
||||
err.code !== 'ENOTDIR') {
|
||||
return internalError(absolutePath, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stats) {
|
||||
try {
|
||||
const related = await findRelated(current, relativePath);
|
||||
if (related) {
|
||||
stats = related.stats;
|
||||
absolutePath = related.absolutePath;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code !== 'ENOENT' &&
|
||||
err.code !== 'ENOTDIR') {
|
||||
return internalError(absolutePath, response);
|
||||
}
|
||||
}
|
||||
try {
|
||||
stats = await node_fs_1.promises.lstat(absolutePath);
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code !== 'ENOENT' &&
|
||||
err.code !== 'ENOTDIR') {
|
||||
return internalError(absolutePath, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stats === null || stats === void 0 ? void 0 : stats.isDirectory()) {
|
||||
const directory = null;
|
||||
const singleFile = null;
|
||||
if (directory) {
|
||||
const _contentType = 'text/html; charset=utf-8';
|
||||
response.statusCode = 200;
|
||||
response.setHeader('Content-Type', _contentType);
|
||||
response.end('Is a directory');
|
||||
return;
|
||||
}
|
||||
if (!singleFile) {
|
||||
// The directory listing is disabled, so we want to
|
||||
// render a 404 error.
|
||||
stats = null;
|
||||
}
|
||||
}
|
||||
const isSymLink = stats === null || stats === void 0 ? void 0 : stats.isSymbolicLink();
|
||||
// There are two scenarios in which we want to reply with
|
||||
// a 404 error: Either the path does not exist, or it is a
|
||||
// symlink while the `symlinks` option is disabled (which it is by default).
|
||||
if (!stats || isSymLink) {
|
||||
// allow for custom 404 handling
|
||||
return sendError(absolutePath, response, {
|
||||
statusCode: 404,
|
||||
code: 'not_found',
|
||||
message: 'The requested path (' + absolutePath + ') could not be found',
|
||||
});
|
||||
}
|
||||
let streamOpts = null;
|
||||
if (request.headers.range && stats.size) {
|
||||
const range = (0, range_parser_1.rangeParser)(stats.size, request.headers.range);
|
||||
if (typeof range === 'object' && range.type === 'bytes') {
|
||||
const { start, end } = range.ranges[0];
|
||||
streamOpts = {
|
||||
start,
|
||||
end,
|
||||
};
|
||||
response.statusCode = 206;
|
||||
}
|
||||
else {
|
||||
response.statusCode = 416;
|
||||
response.setHeader('Content-Range', `bytes */${stats.size}`);
|
||||
}
|
||||
}
|
||||
let stream = null;
|
||||
try {
|
||||
stream = (0, node_fs_1.createReadStream)(absolutePath, streamOpts !== null && streamOpts !== void 0 ? streamOpts : {});
|
||||
}
|
||||
catch (_b) {
|
||||
return internalError(absolutePath, response);
|
||||
}
|
||||
const headers = getHeaders(absolutePath, stats);
|
||||
if (streamOpts !== null) {
|
||||
headers['Content-Range'] =
|
||||
`bytes ${streamOpts.start}-${streamOpts.end}/${stats.size}`;
|
||||
headers['Content-Length'] = String(streamOpts.end - streamOpts.start + 1);
|
||||
}
|
||||
headers['Cache-Control'] = 'no-cache, no-store, must-revalidate';
|
||||
response.writeHead(response.statusCode || 200, headers);
|
||||
stream.pipe(response);
|
||||
};
|
||||
exports.serveHandler = serveHandler;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const isPathInside: (thePath: string, potentialParent: string) => boolean;
|
||||
Generated
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isPathInside = void 0;
|
||||
const node_path_1 = __importDefault(require("node:path"));
|
||||
const isPathInside = function (thePath, potentialParent) {
|
||||
// For inside-directory checking, we want to allow trailing slashes, so normalize.
|
||||
thePath = stripTrailingSep(thePath);
|
||||
potentialParent = stripTrailingSep(potentialParent);
|
||||
// Node treats only Windows as case-insensitive in its path module; we follow those conventions.
|
||||
if (process.platform === 'win32') {
|
||||
thePath = thePath.toLowerCase();
|
||||
potentialParent = potentialParent.toLowerCase();
|
||||
}
|
||||
return (thePath.lastIndexOf(potentialParent, 0) === 0 &&
|
||||
(thePath[potentialParent.length] === node_path_1.default.sep ||
|
||||
thePath[potentialParent.length] === undefined));
|
||||
};
|
||||
exports.isPathInside = isPathInside;
|
||||
function stripTrailingSep(thePath) {
|
||||
if (thePath[thePath.length - 1] === node_path_1.default.sep) {
|
||||
return thePath.slice(0, -1);
|
||||
}
|
||||
return thePath;
|
||||
}
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
/*!
|
||||
* range-parser
|
||||
* Copyright(c) 2012-2014 TJ Holowaychuk
|
||||
* Copyright(c) 2015-2016 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
export declare const rangeParser: (size: number, str: string) => -2 | -1 | {
|
||||
type: string;
|
||||
ranges: {
|
||||
start: number;
|
||||
end: number;
|
||||
}[];
|
||||
};
|
||||
Generated
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
"use strict";
|
||||
/*!
|
||||
* range-parser
|
||||
* Copyright(c) 2012-2014 TJ Holowaychuk
|
||||
* Copyright(c) 2015-2016 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.rangeParser = void 0;
|
||||
const rangeParser = (size, str) => {
|
||||
if (typeof str !== 'string') {
|
||||
throw new TypeError('argument str must be a string');
|
||||
}
|
||||
const index = str.indexOf('=');
|
||||
if (index === -1) {
|
||||
return -2;
|
||||
}
|
||||
// split the range string
|
||||
const arr = str.slice(index + 1).split(',');
|
||||
const ranges = [];
|
||||
// add ranges type
|
||||
const type = str.slice(0, index);
|
||||
// parse all ranges
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const range = arr[i].split('-');
|
||||
let start = parseInt(range[0], 10);
|
||||
let end = parseInt(range[1], 10);
|
||||
// -nnn
|
||||
if (isNaN(start)) {
|
||||
start = size - end;
|
||||
end = size - 1;
|
||||
// nnn-
|
||||
}
|
||||
else if (isNaN(end)) {
|
||||
end = size - 1;
|
||||
}
|
||||
// limit last-byte-pos to current length
|
||||
if (end > size - 1) {
|
||||
end = size - 1;
|
||||
}
|
||||
// invalid or unsatisifiable
|
||||
if (isNaN(start) || isNaN(end) || start > end || start < 0) {
|
||||
continue;
|
||||
}
|
||||
// add range
|
||||
ranges.push({
|
||||
start,
|
||||
end,
|
||||
});
|
||||
}
|
||||
if (ranges.length < 1) {
|
||||
// unsatisifiable
|
||||
return -1;
|
||||
}
|
||||
return { ranges, type };
|
||||
};
|
||||
exports.rangeParser = rangeParser;
|
||||
Reference in New Issue
Block a user