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