UNPKG

mime-type

Version:

the custom more powerful mime-type utility can work with mime-db.

348 lines (321 loc) 10.1 kB
import picomatch from 'picomatch/posix' import { defineProperty, isArray, isString, isFunction } from 'util-ex' import path from 'path.js' function isMatch(str, pattern, options) { return picomatch(pattern, options)(str) } const extractTypeRegExp = /^\s*([^;\s]*)(?:;|\s|$)/ const textTypeRegExp = /^text\//i const indexOf = Array.prototype.indexOf const refSources = ['nginx', 'apache', undefined, 'iana'] const shouldDebug = typeof process === 'object' && process && process.env && process.env.DEBUG_MIME MimeType.dupDefault = MimeType.prototype.dupDefault = 0; MimeType.dupSkip = MimeType.prototype.dupSkip = 1; MimeType.dupOverwrite = MimeType.prototype.dupOverwrite = 2; MimeType.dupAppend = MimeType.prototype.dupAppend = 3; /** * Constructs a MimeType object to manage MIME types and their associated extensions. * @param {Object} db - An initial database containing MIME type mappings. * @param {number} [duplicationProcessWay] - A flag specifying how to handle duplicate MIME type entries. * If provided, it should be one of: * - `dupDefault: 0`: Default strategy that resolves duplicates based on source priority. * See the description for details on this strategy. * - `dupSkip: 1`: Skip adding the new MIME type if a duplicate entry already exists. * - `dupOverwrite: 2`: Replace the existing MIME type with the new one if a duplicate is found. * - `dupAppend: 3`: Add the new MIME type to the end of the existing ones if a duplicate is encountered. * If not specified, it defaults to the `dupDefault` strategy, * The `dupDefault` strategy works as follows: * - If the existing MIME type is 'application/octet-stream', it's not overwritten. * - If sources are equal and the existing type starts with 'application/', it's preserved. * - Otherwise, if the new source is considered more authoritative (earlier in the `refSources` array), * the new type overwrites the old one. The `refSources` array may include server configurations * and standards bodies, such as ['nginx', 'apache', undefined, 'iana'], with `undefined` representing * an unspecified or less authoritative source. */ export function MimeType(db, duplicationProcessWay) { if (!(this instanceof MimeType)) { return new MimeType(db, duplicationProcessWay); } defineProperty(this, 'types', {}); defineProperty(this, 'dup', this.dupDefault); defineProperty(this, 'extensions', undefined, { get: function() { var j, k, len, mime, ref, result; result = {}; ref = Object.keys(this); for (j = 0, len = ref.length; j < len; j++) { k = ref[j]; mime = this[k]; result[k] = mime.extensions; } return result; } }); if (duplicationProcessWay && indexOf.call([0, 1, 2, 3], duplicationProcessWay) >= 0) { this.dup = duplicationProcessWay; } if (db) { this.load(db); } } /* * Get the default charset for a MIME type. * * @param {string} type * @return {boolean|string} */ MimeType.prototype.charset = function(type) { let result; if (type && isString(type)) { try { const match = extractTypeRegExp.exec(type) const mime = match && this[match[1].toLowerCase()] if (mime && mime.charset) { result = mime.charset } // default text/* to utf-8 if (!result && match && textTypeRegExp.test(match[1])) { result = 'utf-8' } } catch (error) {} } return result; }; /* * Create a full Content-Type header given a MIME type or extension. * * @param {string} str * @return {boolean|string} */ MimeType.prototype.contentType = function(str) { var charset; var charset, mime; if (str && isString(str)) { mime = str.indexOf('/') === -1 ? this.lookup(str) : str; if (mime) { if (mime.indexOf('charset') === -1) { charset = this.charset(mime); if (charset) { mime += '; charset=' + charset.toLowerCase(); } } } } return mime; }; /* * Get the default extension for a MIME type. * * @param {string} type * @return {boolean|string} */ MimeType.prototype.extension = function(type) { var result; if (type && isString(type)) { result = extractTypeRegExp.exec(type); result = result && this[result[1].toLowerCase()]; if (result) { result = result.defaultExtension || result.extensions[0]; } } return result; }; /* * Lookup the MIME types for a file path/extension. * * @param {string} path * @return {undefined|string|array} */ MimeType.prototype.lookup = function(aPath) { var extension, result; if (aPath && isString(aPath)) { // get the extension ("ext" or ".ext" or full path) extension = path.extname('x.' + aPath).toLowerCase().slice(1); if (extension) { if (/[*?!+|{]/.test(extension)) { result = Object.keys(this.types).filter((name) => isMatch(name, extension)) result = result.length ? (result.map((ext) => this.types[ext])) : undefined } else { result = this.types[extension]; } } } return result; }; /* * Return all MIME types which matching a pattern * [spec](http://tools.ietf.org/html/rfc2616#section-14.1) * @param {string} pattern the mime type pattern, For example "video/*", "audio/*", .. * @return {array} */ MimeType.prototype.glob = function(pattern) { var result; if (pattern === '*/*') { return ['application/octet-stream']; } result = Object.keys(this).filter(function(name) { return isMatch(name, pattern); }); return result; }; /* * Whether the mime type is exist. * @param {string} type the mime type * @return {boolean} */ MimeType.prototype.exist = Object.prototype.hasOwnProperty; /* * Add a custom mime/extension mapping and handles potential conflicts. * @param (string) type: mime type * @param (object) mime: mime object * * "source": "iana", * * "charset": "UTF-8", * * "compressible": true, * * "extensions": ["js"] * @param {number} [dup=this.dup] - The conflict resolution strategy. Can be one of: * - `this.dupSkip`: Skip the existing mapping. * - `this.dupAppend`: Append the new type to the existing mapping. * - `this.dupOverwrite`: Overwrite the existing mapping. * - `this.dupDefault`: Uses a default strategy where: * - If the existing MIME type is 'application/octet-stream', it is not overwritten. * - If sources are equal and the existing type starts with 'application/', it is retained. * - Otherwise, if the new source is considered more authoritative (appears earlier in `refSources`), * the new type will overwrite the old one. The `refSources` array includes server configurations * and standards bodies, e.g., ['nginx', 'apache', undefined, 'iana'], with `undefined` marking * an unspecified or less authoritative source. * @return {array<string>}: the added extensions */ MimeType.prototype.define = function(type, mime, dup) { var extension, exts, from, j, len, ref, t, to; if (!(type && mime && mime.extensions && !this.hasOwnProperty(type))) { return; } if (dup == null) { dup = this.dup; } exts = mime.extensions; if (!isArray(exts)) { mime.extensions = [exts]; } exts = []; if (mime.charset) { mime.charset = mime.charset.toLowerCase(); } ref = mime.extensions; for (j = 0, len = ref.length; j < len; j++) { extension = ref[j]; t = this.types[extension]; if (t) { switch (dup) { case this.dupSkip: continue; case this.dupAppend: if (isString(t)) { t = [t]; } if (indexOf.call(t, type) < 0) { t.push(type); } break; case this.dupOverwrite: t = type; break; case this.dupDefault: if (isArray(t)) { t = t[0]; } from = refSources.indexOf(this[t].source); to = refSources.indexOf(mime.source); if (t !== 'application/octet-stream' && from > to || from === to && t.substr(0, 12) === 'application/') { if (shouldDebug) { console.warn("defineMime(" + type + "): the " + extension + " extension is exists on\n" + t + " skipped it."); } continue; } else { t = type; } } } else { t = type; } this.types[extension] = t; exts.push(extension); } if (exts.length) { mime.extensions = exts; this[type] = mime; } return exts; }; /* * load mime-types from db. */ MimeType.prototype.load = function(mimes, duplicationProcessWay) { var result; result = 0; Object.keys(mimes).forEach((function(_this) { return function(type) { var t; t = _this.define(type, mimes[type], duplicationProcessWay); if (t && t.length) { return result++; } }; })(this)); return result; }; /* * remove the specified mime-type. */ MimeType.prototype.delete = function(type) { var i, k, ref, result, v; result = this.exist(type); if (result) { ref = this.types; for (k in ref) { v = ref[k]; if (isArray(v)) { i = v.indexOf(type); if (i !== -1) { v.splice(i, 1); if (v.length === 1) { this.types[k] = v[0]; } } } else if (type === v) { delete this.types[k]; } } delete this[type]; } return result; }; /* * clear the mime-types. */ MimeType.prototype.clear = function(filter) { var k, ref, result, v; result = 0; ref = this; for (k in ref) { v = ref[k]; if (this.hasOwnProperty(k)) { if (isFunction(filter)) { if (filter(k, v)) { this["delete"](k); result++; } } else if (isString(filter)) { if (isMatch(k, filter)) { this["delete"](k); result++; } } else { this["delete"](k); result++; } } } return result; }; export default MimeType;