index.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /*!
  2. * type-is
  3. * Copyright(c) 2014 Jonathan Ong
  4. * Copyright(c) 2014-2015 Douglas Christopher Wilson
  5. * MIT Licensed
  6. */
  7. 'use strict'
  8. /**
  9. * Module dependencies.
  10. * @private
  11. */
  12. var typer = require('media-typer')
  13. var mime = require('mime-types')
  14. /**
  15. * Module exports.
  16. * @public
  17. */
  18. module.exports = typeofrequest
  19. module.exports.is = typeis
  20. module.exports.hasBody = hasbody
  21. module.exports.normalize = normalize
  22. module.exports.match = mimeMatch
  23. /**
  24. * Compare a `value` content-type with `types`.
  25. * Each `type` can be an extension like `html`,
  26. * a special shortcut like `multipart` or `urlencoded`,
  27. * or a mime type.
  28. *
  29. * If no types match, `false` is returned.
  30. * Otherwise, the first `type` that matches is returned.
  31. *
  32. * @param {String} value
  33. * @param {Array} types
  34. * @public
  35. */
  36. function typeis (value, types_) {
  37. var i
  38. var types = types_
  39. // remove parameters and normalize
  40. var val = tryNormalizeType(value)
  41. // no type or invalid
  42. if (!val) {
  43. return false
  44. }
  45. // support flattened arguments
  46. if (types && !Array.isArray(types)) {
  47. types = new Array(arguments.length - 1)
  48. for (i = 0; i < types.length; i++) {
  49. types[i] = arguments[i + 1]
  50. }
  51. }
  52. // no types, return the content type
  53. if (!types || !types.length) {
  54. return val
  55. }
  56. var type
  57. for (i = 0; i < types.length; i++) {
  58. if (mimeMatch(normalize(type = types[i]), val)) {
  59. return type[0] === '+' || type.indexOf('*') !== -1
  60. ? val
  61. : type
  62. }
  63. }
  64. // no matches
  65. return false
  66. }
  67. /**
  68. * Check if a request has a request body.
  69. * A request with a body __must__ either have `transfer-encoding`
  70. * or `content-length` headers set.
  71. * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
  72. *
  73. * @param {Object} request
  74. * @return {Boolean}
  75. * @public
  76. */
  77. function hasbody (req) {
  78. return req.headers['transfer-encoding'] !== undefined ||
  79. !isNaN(req.headers['content-length'])
  80. }
  81. /**
  82. * Check if the incoming request contains the "Content-Type"
  83. * header field, and it contains any of the give mime `type`s.
  84. * If there is no request body, `null` is returned.
  85. * If there is no content type, `false` is returned.
  86. * Otherwise, it returns the first `type` that matches.
  87. *
  88. * Examples:
  89. *
  90. * // With Content-Type: text/html; charset=utf-8
  91. * this.is('html'); // => 'html'
  92. * this.is('text/html'); // => 'text/html'
  93. * this.is('text/*', 'application/json'); // => 'text/html'
  94. *
  95. * // When Content-Type is application/json
  96. * this.is('json', 'urlencoded'); // => 'json'
  97. * this.is('application/json'); // => 'application/json'
  98. * this.is('html', 'application/*'); // => 'application/json'
  99. *
  100. * this.is('html'); // => false
  101. *
  102. * @param {String|Array} types...
  103. * @return {String|false|null}
  104. * @public
  105. */
  106. function typeofrequest (req, types_) {
  107. var types = types_
  108. // no body
  109. if (!hasbody(req)) {
  110. return null
  111. }
  112. // support flattened arguments
  113. if (arguments.length > 2) {
  114. types = new Array(arguments.length - 1)
  115. for (var i = 0; i < types.length; i++) {
  116. types[i] = arguments[i + 1]
  117. }
  118. }
  119. // request content type
  120. var value = req.headers['content-type']
  121. return typeis(value, types)
  122. }
  123. /**
  124. * Normalize a mime type.
  125. * If it's a shorthand, expand it to a valid mime type.
  126. *
  127. * In general, you probably want:
  128. *
  129. * var type = is(req, ['urlencoded', 'json', 'multipart']);
  130. *
  131. * Then use the appropriate body parsers.
  132. * These three are the most common request body types
  133. * and are thus ensured to work.
  134. *
  135. * @param {String} type
  136. * @private
  137. */
  138. function normalize (type) {
  139. if (typeof type !== 'string') {
  140. // invalid type
  141. return false
  142. }
  143. switch (type) {
  144. case 'urlencoded':
  145. return 'application/x-www-form-urlencoded'
  146. case 'multipart':
  147. return 'multipart/*'
  148. }
  149. if (type[0] === '+') {
  150. // "+json" -> "*/*+json" expando
  151. return '*/*' + type
  152. }
  153. return type.indexOf('/') === -1
  154. ? mime.lookup(type)
  155. : type
  156. }
  157. /**
  158. * Check if `expected` mime type
  159. * matches `actual` mime type with
  160. * wildcard and +suffix support.
  161. *
  162. * @param {String} expected
  163. * @param {String} actual
  164. * @return {Boolean}
  165. * @private
  166. */
  167. function mimeMatch (expected, actual) {
  168. // invalid type
  169. if (expected === false) {
  170. return false
  171. }
  172. // split types
  173. var actualParts = actual.split('/')
  174. var expectedParts = expected.split('/')
  175. // invalid format
  176. if (actualParts.length !== 2 || expectedParts.length !== 2) {
  177. return false
  178. }
  179. // validate type
  180. if (expectedParts[0] !== '*' && expectedParts[0] !== actualParts[0]) {
  181. return false
  182. }
  183. // validate suffix wildcard
  184. if (expectedParts[1].substr(0, 2) === '*+') {
  185. return expectedParts[1].length <= actualParts[1].length + 1 &&
  186. expectedParts[1].substr(1) === actualParts[1].substr(1 - expectedParts[1].length)
  187. }
  188. // validate subtype
  189. if (expectedParts[1] !== '*' && expectedParts[1] !== actualParts[1]) {
  190. return false
  191. }
  192. return true
  193. }
  194. /**
  195. * Normalize a type and remove parameters.
  196. *
  197. * @param {string} value
  198. * @return {string}
  199. * @private
  200. */
  201. function normalizeType (value) {
  202. // parse the type
  203. var type = typer.parse(value)
  204. // remove the parameters
  205. type.parameters = undefined
  206. // reformat it
  207. return typer.format(type)
  208. }
  209. /**
  210. * Try to normalize a type and remove parameters.
  211. *
  212. * @param {string} value
  213. * @return {string}
  214. * @private
  215. */
  216. function tryNormalizeType (value) {
  217. try {
  218. return normalizeType(value)
  219. } catch (err) {
  220. return null
  221. }
  222. }