index.js 4.9 KB

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