index.js 5.4 KB

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