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