📄 mimeTypes.js
/home/palash/git/site/node_modules/webpack/lib/util/mimeTypes.js
Language: 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 };