123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- "use strict";
- /** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
- /** @typedef {import("../index.js").ServerResponse} ServerResponse */
- /**
- * @typedef {Object} ExpectedRequest
- * @property {(name: string) => string | undefined} get
- */
- /**
- * @typedef {Object} ExpectedResponse
- * @property {(name: string) => string | string[] | undefined} get
- * @property {(name: string, value: number | string | string[]) => void} set
- * @property {(status: number) => void} status
- * @property {(data: any) => void} send
- */
- /**
- * @template {ServerResponse} Response
- * @param {Response} res
- * @returns {string[]}
- */
- function getHeaderNames(res) {
- if (typeof res.getHeaderNames !== "function") {
- // @ts-ignore
- // eslint-disable-next-line no-underscore-dangle
- return Object.keys(res._headers || {});
- }
- return res.getHeaderNames();
- }
- /**
- * @template {IncomingMessage} Request
- * @param {Request} req
- * @param {string} name
- * @returns {string | undefined}
- */
- function getHeaderFromRequest(req, name) {
- // Express API
- if (typeof
- /** @type {Request & ExpectedRequest} */
- req.get === "function") {
- return (
- /** @type {Request & ExpectedRequest} */
- req.get(name)
- );
- } // Node.js API
- // @ts-ignore
- return req.headers[name];
- }
- /**
- * @template {ServerResponse} Response
- * @param {Response} res
- * @param {string} name
- * @returns {number | string | string[] | undefined}
- */
- function getHeaderFromResponse(res, name) {
- // Express API
- if (typeof
- /** @type {Response & ExpectedResponse} */
- res.get === "function") {
- return (
- /** @type {Response & ExpectedResponse} */
- res.get(name)
- );
- } // Node.js API
- return res.getHeader(name);
- }
- /**
- * @template {ServerResponse} Response
- * @param {Response} res
- * @param {string} name
- * @param {number | string | string[]} value
- * @returns {void}
- */
- function setHeaderForResponse(res, name, value) {
- // Express API
- if (typeof
- /** @type {Response & ExpectedResponse} */
- res.set === "function") {
- /** @type {Response & ExpectedResponse} */
- res.set(name, typeof value === "number" ? String(value) : value);
- return;
- } // Node.js API
- res.setHeader(name, value);
- }
- /**
- * @template {ServerResponse} Response
- * @param {Response} res
- * @param {number} code
- */
- function setStatusCode(res, code) {
- if (typeof
- /** @type {Response & ExpectedResponse} */
- res.status === "function") {
- /** @type {Response & ExpectedResponse} */
- res.status(code);
- return;
- } // eslint-disable-next-line no-param-reassign
- res.statusCode = code;
- }
- /**
- * @template {IncomingMessage} Request
- * @template {ServerResponse} Response
- * @param {Request} req
- * @param {Response} res
- * @param {string | Buffer | import("fs").ReadStream} bufferOtStream
- * @param {number} byteLength
- */
- function send(req, res, bufferOtStream, byteLength) {
- if (typeof
- /** @type {import("fs").ReadStream} */
- bufferOtStream.pipe === "function") {
- setHeaderForResponse(res, "Content-Length", byteLength);
- if (req.method === "HEAD") {
- res.end();
- return;
- }
- /** @type {import("fs").ReadStream} */
- bufferOtStream.pipe(res);
- return;
- }
- if (typeof
- /** @type {Response & ExpectedResponse} */
- res.send === "function") {
- /** @type {Response & ExpectedResponse} */
- res.send(bufferOtStream);
- return;
- } // Only Node.js API used
- res.setHeader("Content-Length", byteLength);
- if (req.method === "HEAD") {
- res.end();
- } else {
- res.end(bufferOtStream);
- }
- }
- /**
- * @template {ServerResponse} Response
- * @param {Response} res
- */
- function clearHeadersForResponse(res) {
- const headers = getHeaderNames(res);
- for (let i = 0; i < headers.length; i++) {
- res.removeHeader(headers[i]);
- }
- }
- const matchHtmlRegExp = /["'&<>]/;
- /**
- * @param {string} string raw HTML
- * @returns {string} escaped HTML
- */
- function escapeHtml(string) {
- const str = `${string}`;
- const match = matchHtmlRegExp.exec(str);
- if (!match) {
- return str;
- }
- let escape;
- let html = "";
- let index = 0;
- let lastIndex = 0;
- for (({
- index
- } = match); index < str.length; index++) {
- switch (str.charCodeAt(index)) {
- // "
- case 34:
- escape = """;
- break;
- // &
- case 38:
- escape = "&";
- break;
- // '
- case 39:
- escape = "'";
- break;
- // <
- case 60:
- escape = "<";
- break;
- // >
- case 62:
- escape = ">";
- break;
- default:
- // eslint-disable-next-line no-continue
- continue;
- }
- if (lastIndex !== index) {
- html += str.substring(lastIndex, index);
- }
- lastIndex = index + 1;
- html += escape;
- }
- return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
- }
- /** @type {Record<number, string>} */
- const statuses = {
- 400: "Bad Request",
- 403: "Forbidden",
- 404: "Not Found",
- 416: "Range Not Satisfiable",
- 500: "Internal Server Error"
- };
- /**
- * @template {IncomingMessage} Request
- * @template {ServerResponse} Response
- * @param {Request} req response
- * @param {Response} res response
- * @param {number} status status
- * @returns {void}
- */
- function sendError(req, res, status) {
- const content = statuses[status] || String(status);
- const document = `<!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <title>Error</title>
- </head>
- <body>
- <pre>${escapeHtml(content)}</pre>
- </body>
- </html>`; // Clear existing headers
- clearHeadersForResponse(res); // Send basic response
- setStatusCode(res, status);
- setHeaderForResponse(res, "Content-Type", "text/html; charset=utf-8");
- setHeaderForResponse(res, "Content-Security-Policy", "default-src 'none'");
- setHeaderForResponse(res, "X-Content-Type-Options", "nosniff");
- const byteLength = Buffer.byteLength(document);
- setHeaderForResponse(res, "Content-Length", byteLength);
- res.end(document);
- }
- module.exports = {
- getHeaderNames,
- getHeaderFromRequest,
- getHeaderFromResponse,
- setHeaderForResponse,
- setStatusCode,
- send,
- sendError
- };
|