From 497715a0433c7df5a41653143994943923dcef97 Mon Sep 17 00:00:00 2001 From: Robert Kieffer Date: Mon, 11 Dec 2023 17:25:39 -0800 Subject: [PATCH] feat!: resolve extension conflicts with mime-score, close #116 --- index.js | 7 ++++--- mimeScore.js | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ test/test.js | 19 +++++++++++++++++-- 3 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 mimeScore.js diff --git a/index.js b/index.js index 9d09a11..88ccbab 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,7 @@ var db = require('mime-db') var extname = require('path').extname +var mimeScore = require('./mimeScore') /** * Module variables. @@ -171,11 +172,11 @@ function populateMaps (extensions, types) { var extension = exts[i] if (types[extension]) { - var from = preference.indexOf(db[types[extension]].source) - var to = preference.indexOf(mime.source) + var from = mimeScore(types[extension]) + var to = mimeScore(type) if (types[extension] !== 'application/octet-stream' && - (from > to || (from === to && types[extension].slice(0, 12) === 'application/'))) { + from > to) { // skip the remapping continue } diff --git a/mimeScore.js b/mimeScore.js new file mode 100644 index 0000000..7a67b7d --- /dev/null +++ b/mimeScore.js @@ -0,0 +1,48 @@ +// 'mime-score' back-ported to CommonJS + +// Score RFC facets (see https://tools.ietf.org/html/rfc6838#section-3) +var FACET_SCORES = { + 'prs.': 100, + 'x-': 200, + 'x.': 300, + 'vnd.': 400, + default: 900, +} + +// Score mime source (Logic originally from `jshttp/mime-types` module) +var SOURCE_SCORES = { + nginx: 10, + apache: 20, + iana: 40, + default: 30, // definitions added by `jshttp/mime-db` project? +} + +var 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, + + default: 0, +} + +/** + * Get each component of the score for a mime type. The sum of these is the + * total score. The higher the score, the more "official" the type. + */ +module.exports = function mimeScore(mimeType, source = 'default') { + let [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 +} diff --git a/test/test.js b/test/test.js index 0423b8f..9b482ba 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,3 @@ - var assert = require('assert') var mimeTypes = require('..') @@ -220,7 +219,23 @@ describe('mimeTypes', function () { }) it('should return mime type when there is extension, but no path', function () { - assert.strictEqual(mimeTypes.lookup('.config.json'), 'application/json') + assert.strictEqual( + mimeTypes.lookup('.config.json'), + 'application/json' + ) + }) + }) + + describe('extension conflicts', function () { + it('should use mime-score', function () { + // Test extension conflicts where the lookup has changed as a result of the switch to mime-score + assert.strictEqual(mimeTypes.lookup('exe'), 'application/x-msdownload') // was application/x-msdos-program + assert.strictEqual(mimeTypes.lookup('prc'), 'application/x-pilot') // was application/x-mobipocket-ebook + assert.strictEqual(mimeTypes.lookup('mp3'), 'audio/mp3') // was audio/mpeg + assert.strictEqual(mimeTypes.lookup('wav'), 'audio/wav') // was audio/wave + assert.strictEqual(mimeTypes.lookup('ra'), 'audio/x-realaudio') // was audio/x-pn-realaudio + assert.strictEqual(mimeTypes.lookup('x3db'), 'model/x3d+binary') // was model/x3d+fastinfoset + assert.strictEqual(mimeTypes.lookup('jpm'), 'video/jpm') // was image/jpm }) }) })