index.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /*!
  2. * mime-types
  3. * Copyright(c) 2014 Jonathan Ong
  4. * Copyright(c) 2015 Douglas Christopher Wilson
  5. * MIT Licensed
  6. */
  7. 'use strict'
  8. /**
  9. * Module dependencies.
  10. * @private
  11. */
  12. var db = require('mime-db')
  13. var extname = require('path').extname
  14. var mimeScore = require('./mimeScore')
  15. /**
  16. * Module variables.
  17. * @private
  18. */
  19. var EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/
  20. var TEXT_TYPE_REGEXP = /^text\//i
  21. /**
  22. * Module exports.
  23. * @public
  24. */
  25. exports.charset = charset
  26. exports.charsets = { lookup: charset }
  27. exports.contentType = contentType
  28. exports.extension = extension
  29. exports.extensions = Object.create(null)
  30. exports.lookup = lookup
  31. exports.types = Object.create(null)
  32. exports._extensionConflicts = []
  33. // Populate the extensions/types maps
  34. populateMaps(exports.extensions, exports.types)
  35. /**
  36. * Get the default charset for a MIME type.
  37. *
  38. * @param {string} type
  39. * @return {boolean|string}
  40. */
  41. function charset (type) {
  42. if (!type || typeof type !== 'string') {
  43. return false
  44. }
  45. // TODO: use media-typer
  46. var match = EXTRACT_TYPE_REGEXP.exec(type)
  47. var mime = match && db[match[1].toLowerCase()]
  48. if (mime && mime.charset) {
  49. return mime.charset
  50. }
  51. // default text/* to utf-8
  52. if (match && TEXT_TYPE_REGEXP.test(match[1])) {
  53. return 'UTF-8'
  54. }
  55. return false
  56. }
  57. /**
  58. * Create a full Content-Type header given a MIME type or extension.
  59. *
  60. * @param {string} str
  61. * @return {boolean|string}
  62. */
  63. function contentType (str) {
  64. // TODO: should this even be in this module?
  65. if (!str || typeof str !== 'string') {
  66. return false
  67. }
  68. var mime = str.indexOf('/') === -1 ? exports.lookup(str) : str
  69. if (!mime) {
  70. return false
  71. }
  72. // TODO: use content-type or other module
  73. if (mime.indexOf('charset') === -1) {
  74. var charset = exports.charset(mime)
  75. if (charset) mime += '; charset=' + charset.toLowerCase()
  76. }
  77. return mime
  78. }
  79. /**
  80. * Get the default extension for a MIME type.
  81. *
  82. * @param {string} type
  83. * @return {boolean|string}
  84. */
  85. function extension (type) {
  86. if (!type || typeof type !== 'string') {
  87. return false
  88. }
  89. // TODO: use media-typer
  90. var match = EXTRACT_TYPE_REGEXP.exec(type)
  91. // get extensions
  92. var exts = match && exports.extensions[match[1].toLowerCase()]
  93. if (!exts || !exts.length) {
  94. return false
  95. }
  96. return exts[0]
  97. }
  98. /**
  99. * Lookup the MIME type for a file path/extension.
  100. *
  101. * @param {string} path
  102. * @return {boolean|string}
  103. */
  104. function lookup (path) {
  105. if (!path || typeof path !== 'string') {
  106. return false
  107. }
  108. // get the extension ("ext" or ".ext" or full path)
  109. var extension = extname('x.' + path)
  110. .toLowerCase()
  111. .slice(1)
  112. if (!extension) {
  113. return false
  114. }
  115. return exports.types[extension] || false
  116. }
  117. /**
  118. * Populate the extensions and types maps.
  119. * @private
  120. */
  121. function populateMaps (extensions, types) {
  122. Object.keys(db).forEach(function forEachMimeType (type) {
  123. var mime = db[type]
  124. var exts = mime.extensions
  125. if (!exts || !exts.length) {
  126. return
  127. }
  128. // mime -> extensions
  129. extensions[type] = exts
  130. // extension -> mime
  131. for (var i = 0; i < exts.length; i++) {
  132. var extension = exts[i]
  133. types[extension] = _preferredType(extension, types[extension], type)
  134. // DELETE (eventually): Capture extension->type maps that change as a
  135. // result of switching to mime-score. This is just to help make reviewing
  136. // PR #119 easier, and can be removed once that PR is approved.
  137. const legacyType = _preferredTypeLegacy(
  138. extension,
  139. types[extension],
  140. type
  141. )
  142. if (legacyType !== types[extension]) {
  143. exports._extensionConflicts.push([extension, legacyType, types[extension]])
  144. }
  145. }
  146. })
  147. }
  148. // Resolve type conflict using mime-score
  149. function _preferredType (ext, type0, type1) {
  150. var score0 = type0 ? mimeScore(type0, db[type0].source) : 0
  151. var score1 = type1 ? mimeScore(type1, db[type1].source) : 0
  152. return score0 > score1 ? type0 : type1
  153. }
  154. // Resolve type conflict using pre-mime-score logic
  155. function _preferredTypeLegacy (ext, type0, type1) {
  156. var SOURCE_RANK = ['nginx', 'apache', undefined, 'iana']
  157. var score0 = type0 ? SOURCE_RANK.indexOf(db[type0].source) : 0
  158. var score1 = type1 ? SOURCE_RANK.indexOf(db[type1].source) : 0
  159. if (
  160. exports.types[extension] !== 'application/octet-stream' &&
  161. (score0 > score1 ||
  162. (score0 === score1 &&
  163. exports.types[extension]?.slice(0, 12) === 'application/'))
  164. ) {
  165. return type0
  166. }
  167. return score0 > score1 ? type0 : type1
  168. }