feat: replace "mime-types" library with custom MIME type lookup function that uses "mime-db" library

This commit is contained in:
Dorian Niemiec 2024-11-16 17:15:34 +01:00
parent b925ff04f6
commit 070c282910
6 changed files with 162 additions and 22 deletions

18
package-lock.json generated
View file

@ -9,7 +9,7 @@
"version": "0.0.0",
"dependencies": {
"formidable": "^2.1.2",
"mime-types": "^2.1.35",
"mime-db": "^1.53.0",
"ocsp": "^1.2.0",
"tar": "^6.2.1"
},
@ -8233,9 +8233,9 @@
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"version": "1.53.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
"integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
"engines": {
"node": ">= 0.6"
}
@ -8244,6 +8244,7 @@
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"dependencies": {
"mime-db": "1.52.0"
},
@ -8251,6 +8252,15 @@
"node": ">= 0.6"
}
},
"node_modules/mime-types/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",

View file

@ -46,7 +46,7 @@
},
"dependencies": {
"formidable": "^2.1.2",
"mime-types": "^2.1.35",
"mime-db": "^1.53.0",
"ocsp": "^1.2.0",
"tar": "^6.2.1"
},

View file

@ -2,7 +2,7 @@ const fs = require("fs");
const os = require("os");
const path = require("path");
const zlib = require("zlib");
const mime = require("mime-types");
const { getMimeType, checkIfCompressible } = require("../utils/mimeTypes.js");
const defaultPageCSS = require("../res/defaultPageCSS.js");
const matchHostname = require("../utils/matchHostname.js");
const ipMatch = require("../utils/ipMatch.js");
@ -200,7 +200,7 @@ module.exports = (req, res, logFacilities, config, next) => {
"bytes " + begin + "-" + end + "/" + filelen;
rhd["Content-Length"] = end - begin + 1;
delete rhd["Content-Type"];
const mtype = mime.contentType(ext);
const mtype = getMimeType(ext);
if (mtype && ext != "") rhd["Content-Type"] = mtype;
if (fileETag) rhd["ETag"] = fileETag;
@ -338,7 +338,7 @@ module.exports = (req, res, logFacilities, config, next) => {
let useGzip =
ext != "gz" && filelen > 256 && acceptEncoding.match(/\bgzip\b/);
let isCompressable = true;
let isCompressible = checkIfCompressible(ext);
try {
// Check for files not to compressed and compression enabling setting. Also check for browser quirks and adjust compression accordingly
if (
@ -346,7 +346,7 @@ module.exports = (req, res, logFacilities, config, next) => {
config.enableCompression !== true ||
!canCompress(href, config.dontCompress)
) {
isCompressable = false; // Compression is disabled
isCompressible = false; // Compression is disabled
} else if (
ext != "html" &&
ext != "htm" &&
@ -362,9 +362,9 @@ module.exports = (req, res, logFacilities, config, next) => {
req.headers["user-agent"]
)
) {
isCompressable = false; // Netscape 4.x doesn't handle compressed data properly outside of HTML documents.
isCompressible = false; // Netscape 4.x doesn't handle compressed data properly outside of HTML documents.
} else if (/^w3m\/[^ ]*$/.test(req.headers["user-agent"])) {
isCompressable = false; // w3m doesn't handle compressed data properly outside of HTML documents.
isCompressible = false; // w3m doesn't handle compressed data properly outside of HTML documents.
}
} else {
if (
@ -375,7 +375,7 @@ module.exports = (req, res, logFacilities, config, next) => {
req.headers["user-agent"]
)
) {
isCompressable = false; // Netscape 4.06-4.08 doesn't handle compressed data properly.
isCompressible = false; // Netscape 4.06-4.08 doesn't handle compressed data properly.
}
}
} catch (err) {
@ -384,7 +384,7 @@ module.exports = (req, res, logFacilities, config, next) => {
}
// Bun 1.1 has definition for zlib.createBrotliCompress, but throws an error while invoking the function.
if (process.isBun && useBrotli && isCompressable) {
if (process.isBun && useBrotli && isCompressible) {
try {
zlib.createBrotliCompress();
// eslint-disable-next-line no-unused-vars
@ -395,11 +395,11 @@ module.exports = (req, res, logFacilities, config, next) => {
try {
let hdhds = {};
if (useBrotli && isCompressable) {
if (useBrotli && isCompressible) {
hdhds["Content-Encoding"] = "br";
} else if (useDeflate && isCompressable) {
} else if (useDeflate && isCompressible) {
hdhds["Content-Encoding"] = "deflate";
} else if (useGzip && isCompressable) {
} else if (useGzip && isCompressible) {
hdhds["Content-Encoding"] = "gzip";
} else {
if (ext == "html") {
@ -411,7 +411,7 @@ module.exports = (req, res, logFacilities, config, next) => {
}
hdhds["Accept-Ranges"] = "bytes";
delete hdhds["Content-Type"];
const mtype = mime.contentType(ext);
const mtype = getMimeType(ext);
if (mtype && ext != "") hdhds["Content-Type"] = mtype;
if (fileETag) hdhds["ETag"] = fileETag;
@ -442,13 +442,13 @@ module.exports = (req, res, logFacilities, config, next) => {
.on("open", () => {
try {
let resStream = {};
if (useBrotli && isCompressable) {
if (useBrotli && isCompressible) {
resStream = zlib.createBrotliCompress();
resStream.pipe(res);
} else if (useDeflate && isCompressable) {
} else if (useDeflate && isCompressible) {
resStream = zlib.createDeflateRaw();
resStream.pipe(res);
} else if (useGzip && isCompressable) {
} else if (useGzip && isCompressible) {
resStream = zlib.createGzip();
resStream.pipe(res);
} else {
@ -754,7 +754,7 @@ module.exports = (req, res, logFacilities, config, next) => {
const ename = filelist[i].name;
let eext = ename.match(/\.([^.]+)$/);
eext = eext ? eext[1] : "";
const emime = eext ? mime.contentType(eext) : false;
const emime = eext ? getMimeType(eext) : false;
if (filelist[i].errored) {
directoryListingRows.push(
`<tr><td style="width: 24px;"><img src="/.dirimages/bad.png" alt="[BAD]" width="24px" height="24px" /></td><td style="word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;"><a href="${(

67
src/utils/mimeTypes.js Normal file
View file

@ -0,0 +1,67 @@
const mimeDb = require("mime-db");
let optimizedMimeDb = {};
// Initialize the optimized MIME type database, similarly to what "mime-types" library does
Object.keys(mimeDb).forEach((mimeType) => {
const sourcePreference = ["nginx", "apache", undefined, "iana"];
if (
mimeType != "application/octet-stream" &&
mimeDb[mimeType].extensions &&
mimeDb[mimeType].extensions.length > 0
) {
mimeDb[mimeType].extensions.forEach((extension) => {
if (optimizedMimeDb[extension]) {
const from = sourcePreference.indexOf(
optimizedMimeDb[extension].source
);
const to = sourcePreference.indexOf(mimeDb[mimeType].source);
if (
from != -1 &&
to != -1 &&
(from > to ||
(from === to && mimeType.substring(0, 12) == "application/"))
)
return;
}
optimizedMimeDb[extension] = {
type: mimeType,
charset: mimeDb[mimeType].charset,
source: mimeDb[mimeType].source,
compressible: mimeDb[mimeType].compressible
};
});
}
});
// Function to get the MIME type from the extension
function getMimeType(extension) {
if (!extension || typeof extension !== "string") return false;
const extensionMatch = extension.match(/\.([^.]+)$/);
const normalizedExtension = extensionMatch ? extensionMatch[1] : extension;
if (optimizedMimeDb[normalizedExtension])
return (
optimizedMimeDb[normalizedExtension].type +
(optimizedMimeDb[normalizedExtension].charset
? "; charset=" +
optimizedMimeDb[normalizedExtension].charset.toLowerCase()
: "")
);
return false;
}
// Function to check if the file is compressible from the extension
function checkIfCompressible(extension) {
if (!extension || typeof extension !== "string") return true;
const extensionMatch = extension.match(/\.([^.]+)$/);
const normalizedExtension = extensionMatch ? extensionMatch[1] : extension;
if (optimizedMimeDb[normalizedExtension])
return optimizedMimeDb[normalizedExtension].compressible === undefined
? true
: optimizedMimeDb[normalizedExtension].compressible;
return true;
}
module.exports = {
getMimeType: getMimeType,
checkIfCompressible: checkIfCompressible
};

View file

@ -1,6 +1,6 @@
{
"name": "SVR.JS Core",
"externalPackages": ["mime-types"],
"externalPackages": ["mime-db"],
"packageJSON": {
"name": "svrjs-core",
"description": "A library for static file serving, built from SVR.JS source code.",

View file

@ -0,0 +1,63 @@
const {
getMimeType,
checkIfCompressible
} = require("../../src/utils/mimeTypes.js");
describe("MIME type utilities", () => {
describe("getMimeType", () => {
test("should return the correct MIME type for a given extension", () => {
const extension = "html";
const expectedMimeType = "text/html";
expect(getMimeType(extension)).toBe(expectedMimeType);
});
test("should return false for an unknown extension", () => {
const extension = "unknown";
expect(getMimeType(extension)).toBe(false);
});
test("should return false for an invalid extension", () => {
const extension = 123;
expect(getMimeType(extension)).toBe(false);
});
test("should return the correct MIME type for an extension with a dot", () => {
const extension = ".html";
const expectedMimeType = "text/html";
expect(getMimeType(extension)).toBe(expectedMimeType);
});
test("should return the correct MIME type for an extension with a charset", () => {
const extension = "css";
const expectedMimeType = "text/css; charset=utf-8";
expect(getMimeType(extension)).toBe(expectedMimeType);
});
});
describe("checkIfCompressible", () => {
test("should return true for a compressible extension", () => {
const extension = "html";
expect(checkIfCompressible(extension)).toBe(true);
});
test("should return false for a non-compressible extension", () => {
const extension = "jpg";
expect(checkIfCompressible(extension)).toBe(false);
});
test("should return true for an unknown extension", () => {
const extension = "unknown";
expect(checkIfCompressible(extension)).toBe(true);
});
test("should return true for an invalid extension", () => {
const extension = 123;
expect(checkIfCompressible(extension)).toBe(true);
});
test("should return true for an extension with a dot", () => {
const extension = ".html";
expect(checkIfCompressible(extension)).toBe(true);
});
});
});