index.js 5.1 KB

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