Skip to content

Commit

Permalink
feat!: resolve extension conflicts with mime-score, close #116 (#119)
Browse files Browse the repository at this point in the history
* feat!: resolve extension conflicts with mime-score, close #116

* feat!: resolve extension conflicts with mime-score, close #116

* chore: fix lint issues

* wip: lint, logic, and test fixup

* test: tweak log output

* fix: update history.md

---------

Co-authored-by: Wes Todd <[email protected]>
  • Loading branch information
broofa and wesleytodd authored Aug 31, 2024
1 parent 540d9f3 commit 541f9c5
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 33 deletions.
25 changes: 13 additions & 12 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
2.1.35 / 2022-03-12
unreleased
===================

* deps: [email protected]
- Add extensions from IANA for more `image/*` types
- Add extension `.asc` to `application/pgp-keys`
- Add extensions to various XML types
- Add new upstream MIME types

2.1.34 / 2021-11-08
===================

* deps: [email protected]
- Add new upstream MIME types
* resolve extension conflicts with mime-score (#119)
* asc -> application/pgp-signature is now application/pgp-keys
* mpp -> application/vnd.ms-project is now application/dash-patch+xml
* ac -> application/vnd.nokia.n-gage.ac+xml is now application/pkix-attr-cert
* bdoc -> application/x-bdoc is now application/bdoc
* wmz -> application/x-msmetafile is now application/x-ms-wmz
* xsl -> application/xslt+xml is now application/xml
* wav -> audio/wave is now audio/wav
* rtf -> text/rtf is now application/rtf
* xml -> text/xml is now application/xml
* mp4 -> video/mp4 is now application/mp4
* mpg4 -> video/mp4 is now application/mp4

2.1.33 / 2021-10-01
===================
Expand Down
61 changes: 42 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

var db = require('mime-db')
var extname = require('path').extname
var mimeScore = require('./mimeScore')

/**
* Module variables.
Expand All @@ -35,6 +36,7 @@ exports.extension = extension
exports.extensions = Object.create(null)
exports.lookup = lookup
exports.types = Object.create(null)
exports._extensionConflicts = []

// Populate the extensions/types maps
populateMaps(exports.extensions, exports.types)
Expand Down Expand Up @@ -80,9 +82,7 @@ function contentType (str) {
return false
}

var mime = str.indexOf('/') === -1
? exports.lookup(str)
: str
var mime = str.indexOf('/') === -1 ? exports.lookup(str) : str

if (!mime) {
return false
Expand Down Expand Up @@ -152,9 +152,6 @@ function lookup (path) {
*/

function populateMaps (extensions, types) {
// source preference (least -> most)
var preference = ['nginx', 'apache', undefined, 'iana']

Object.keys(db).forEach(function forEachMimeType (type) {
var mime = db[type]
var exts = mime.extensions
Expand All @@ -169,20 +166,46 @@ function populateMaps (extensions, types) {
// extension -> mime
for (var i = 0; i < exts.length; i++) {
var extension = exts[i]

if (types[extension]) {
var from = preference.indexOf(db[types[extension]].source)
var to = preference.indexOf(mime.source)

if (types[extension] !== 'application/octet-stream' &&
(from > to || (from === to && types[extension].slice(0, 12) === 'application/'))) {
// skip the remapping
continue
}
types[extension] = _preferredType(extension, types[extension], type)

// DELETE (eventually): Capture extension->type maps that change as a
// result of switching to mime-score. This is just to help make reviewing
// PR #119 easier, and can be removed once that PR is approved.
const legacyType = _preferredTypeLegacy(
extension,
types[extension],
type
)
if (legacyType !== types[extension]) {
exports._extensionConflicts.push([extension, legacyType, types[extension]])
}

// set the extension -> mime
types[extension] = type
}
})
}

// Resolve type conflict using mime-score
function _preferredType (ext, type0, type1) {
var score0 = type0 ? mimeScore(type0, db[type0].source) : 0
var score1 = type1 ? mimeScore(type1, db[type1].source) : 0

return score0 > score1 ? type0 : type1
}

// Resolve type conflict using pre-mime-score logic
function _preferredTypeLegacy (ext, type0, type1) {
var SOURCE_RANK = ['nginx', 'apache', undefined, 'iana']

var score0 = type0 ? SOURCE_RANK.indexOf(db[type0].source) : 0
var score1 = type1 ? SOURCE_RANK.indexOf(db[type1].source) : 0

if (
exports.types[extension] !== 'application/octet-stream' &&
(score0 > score1 ||
(score0 === score1 &&
exports.types[extension]?.slice(0, 12) === 'application/'))
) {
return type0
}

return score0 > score1 ? type0 : type1
}
52 changes: 52 additions & 0 deletions mimeScore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// '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') {
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
}
14 changes: 12 additions & 2 deletions test/test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

var assert = require('assert')
var mimeTypes = require('..')

Expand Down Expand Up @@ -220,8 +219,19 @@ 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'
)
})
})
})

// Note the changes in extension->type mapping that result from using mime-score
describe('extension conflicts', function () {
console.warn('Mime-score logic changes extension->type mappings for the following:')
for (var [extension, legacy, current] of mimeTypes._extensionConflicts) {
console.warn(`* ${extension} -> ${legacy} is now ${current}`)
}
})
})

0 comments on commit 541f9c5

Please sign in to comment.