js • Lines: 176/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
const { extname } = require("path");
const db = require("mime-db");
/**
* RegExp to match type in RFC 6838
*
* type-name = restricted-name
* subtype-name = restricted-name
* restricted-name = restricted-name-first *126restricted-name-chars
* restricted-name-first = ALPHA / DIGIT
* restricted-name-chars = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "-" / "^" / "_"
* restricted-name-chars =/ "." ; Characters before first dot always specify a facet name
* restricted-name-chars =/ "+" ; Characters after last plus always specify a structured syntax suffix
* ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
* DIGIT = %x30-39 ; 0-9
*/
const TYPE_REGEXP =
/^ *(([A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126})\/([A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126})) *(?:;.*)?$/;
const extensions = Object.create(null);
const types = Object.create(null);
// Score RFC facets (see https://tools.ietf.org/html/rfc6838#section-3)
/** @type {Record<string, number>} */
const FACET_SCORES = {
"prs.": 100,
"x-": 200,
"x.": 300,
"vnd.": 400,
default: 900
};
/** @typedef {"nginx" | "apache" | "iana" | "default"} SourceScore */
// Score mime source (Logic originally from `jshttp/mime-types` module)
/** @type {Record<SourceScore, number>} */
const SOURCE_SCORES = {
nginx: 10,
apache: 20,
iana: 40,
default: 30
};
/** @type {Record<string, number>} */
const TYPE_SCORES = {
// prefer application/xml over text/xml
// prefer application/rtf over text/rtf
application: 1,
// prefer font/woff over application/font-woff
font: 2,
// prefer video/mp4 over audio/mp4 over application/mp4
// See https://www.rfc-editor.org/rfc/rfc4337.html#section-2
audio: 2,
video: 3,
default: 0
};
/**
* @param {string} mimeType mime type
* @param {SourceScore=} source source
* @returns {number} min score
*/
function mimeScore(mimeType, source = "default") {
if (mimeType === "application/octet-stream") {
return 0;
}
const [type, subtype] = mimeType.split("/");
const facet = subtype.replace(/(\.|x-).*/, "$1");
const facetScore = FACET_SCORES[facet] || FACET_SCORES.default;
const sourceScore = SOURCE_SCORES[source] || SOURCE_SCORES.default;
const typeScore = TYPE_SCORES[type] || TYPE_SCORES.default;
// All else being equal prefer shorter types
const lengthScore = 1 - mimeType.length / 100;
return facetScore + sourceScore + typeScore + lengthScore;
}
/**
* @param {string} ext extension
* @param {string} type0 the first type
* @param {string} type1 the second type
* @returns {string} preferred type
*/
const preferredType = (ext, type0, type1) => {
const score0 = type0 ? mimeScore(type0, db[type0].source) : 0;
const score1 = type1 ? mimeScore(type1, db[type1].source) : 0;
return score0 > score1 ? type0 : type1;
};
/**
* @param {Record<string, readonly string[]>} extensions extensions
* @param {Record<string, string>} types types
*/
const populate = (extensions, types) => {
for (const type of Object.keys(db)) {
const mime = db[type];
const foundExtensions = mime.extensions;
if (!foundExtensions || foundExtensions.length === 0) {
continue;
}
// mime -> extensions
extensions[type] = foundExtensions;
// extension -> mime
for (let i = 0; i < foundExtensions.length; i++) {
const extension = foundExtensions[i];
types[extension] = preferredType(extension, types[extension], type);
}
}
};
populate(extensions, types);
/**
* Get the default extension for a MIME type.
* @param {string} type type
* @returns {undefined | string} resolve extension
*/
const extension = (type) => {
if (!type) {
return;
}
const match = TYPE_REGEXP.exec(type);
if (!match) {
return;
}
const possibleExtensions = extensions[match[1].toLowerCase()];
if (!possibleExtensions || possibleExtensions.length === 0) {
return;
}
return possibleExtensions[0];
};
/**
* Lookup the MIME type for a file path/extension.
* @param {string} path path
* @returns {undefined | string} resolved MIME type
*/
const lookup = (path) => {
if (!path) {
return;
}
// get the extension ("ext" or ".ext" or full path)
const extension = extname(`x.${path}`).toLowerCase().slice(1);
if (!extension) {
return;
}
return types[extension];
};
module.exports = { extension, lookup };