utils.js (5972B)
1 /*! 2 * express 3 * Copyright(c) 2009-2013 TJ Holowaychuk 4 * Copyright(c) 2014-2015 Douglas Christopher Wilson 5 * MIT Licensed 6 */ 7 8 'use strict'; 9 10 /** 11 * Module dependencies. 12 * @api private 13 */ 14 15 var Buffer = require('safe-buffer').Buffer 16 var contentDisposition = require('content-disposition'); 17 var contentType = require('content-type'); 18 var deprecate = require('depd')('express'); 19 var flatten = require('array-flatten'); 20 var mime = require('send').mime; 21 var etag = require('etag'); 22 var proxyaddr = require('proxy-addr'); 23 var qs = require('qs'); 24 var querystring = require('querystring'); 25 26 /** 27 * Return strong ETag for `body`. 28 * 29 * @param {String|Buffer} body 30 * @param {String} [encoding] 31 * @return {String} 32 * @api private 33 */ 34 35 exports.etag = createETagGenerator({ weak: false }) 36 37 /** 38 * Return weak ETag for `body`. 39 * 40 * @param {String|Buffer} body 41 * @param {String} [encoding] 42 * @return {String} 43 * @api private 44 */ 45 46 exports.wetag = createETagGenerator({ weak: true }) 47 48 /** 49 * Check if `path` looks absolute. 50 * 51 * @param {String} path 52 * @return {Boolean} 53 * @api private 54 */ 55 56 exports.isAbsolute = function(path){ 57 if ('/' === path[0]) return true; 58 if (':' === path[1] && ('\\' === path[2] || '/' === path[2])) return true; // Windows device path 59 if ('\\\\' === path.substring(0, 2)) return true; // Microsoft Azure absolute path 60 }; 61 62 /** 63 * Flatten the given `arr`. 64 * 65 * @param {Array} arr 66 * @return {Array} 67 * @api private 68 */ 69 70 exports.flatten = deprecate.function(flatten, 71 'utils.flatten: use array-flatten npm module instead'); 72 73 /** 74 * Normalize the given `type`, for example "html" becomes "text/html". 75 * 76 * @param {String} type 77 * @return {Object} 78 * @api private 79 */ 80 81 exports.normalizeType = function(type){ 82 return ~type.indexOf('/') 83 ? acceptParams(type) 84 : { value: mime.lookup(type), params: {} }; 85 }; 86 87 /** 88 * Normalize `types`, for example "html" becomes "text/html". 89 * 90 * @param {Array} types 91 * @return {Array} 92 * @api private 93 */ 94 95 exports.normalizeTypes = function(types){ 96 var ret = []; 97 98 for (var i = 0; i < types.length; ++i) { 99 ret.push(exports.normalizeType(types[i])); 100 } 101 102 return ret; 103 }; 104 105 /** 106 * Generate Content-Disposition header appropriate for the filename. 107 * non-ascii filenames are urlencoded and a filename* parameter is added 108 * 109 * @param {String} filename 110 * @return {String} 111 * @api private 112 */ 113 114 exports.contentDisposition = deprecate.function(contentDisposition, 115 'utils.contentDisposition: use content-disposition npm module instead'); 116 117 /** 118 * Parse accept params `str` returning an 119 * object with `.value`, `.quality` and `.params`. 120 * also includes `.originalIndex` for stable sorting 121 * 122 * @param {String} str 123 * @return {Object} 124 * @api private 125 */ 126 127 function acceptParams(str, index) { 128 var parts = str.split(/ *; */); 129 var ret = { value: parts[0], quality: 1, params: {}, originalIndex: index }; 130 131 for (var i = 1; i < parts.length; ++i) { 132 var pms = parts[i].split(/ *= */); 133 if ('q' === pms[0]) { 134 ret.quality = parseFloat(pms[1]); 135 } else { 136 ret.params[pms[0]] = pms[1]; 137 } 138 } 139 140 return ret; 141 } 142 143 /** 144 * Compile "etag" value to function. 145 * 146 * @param {Boolean|String|Function} val 147 * @return {Function} 148 * @api private 149 */ 150 151 exports.compileETag = function(val) { 152 var fn; 153 154 if (typeof val === 'function') { 155 return val; 156 } 157 158 switch (val) { 159 case true: 160 fn = exports.wetag; 161 break; 162 case false: 163 break; 164 case 'strong': 165 fn = exports.etag; 166 break; 167 case 'weak': 168 fn = exports.wetag; 169 break; 170 default: 171 throw new TypeError('unknown value for etag function: ' + val); 172 } 173 174 return fn; 175 } 176 177 /** 178 * Compile "query parser" value to function. 179 * 180 * @param {String|Function} val 181 * @return {Function} 182 * @api private 183 */ 184 185 exports.compileQueryParser = function compileQueryParser(val) { 186 var fn; 187 188 if (typeof val === 'function') { 189 return val; 190 } 191 192 switch (val) { 193 case true: 194 fn = querystring.parse; 195 break; 196 case false: 197 fn = newObject; 198 break; 199 case 'extended': 200 fn = parseExtendedQueryString; 201 break; 202 case 'simple': 203 fn = querystring.parse; 204 break; 205 default: 206 throw new TypeError('unknown value for query parser function: ' + val); 207 } 208 209 return fn; 210 } 211 212 /** 213 * Compile "proxy trust" value to function. 214 * 215 * @param {Boolean|String|Number|Array|Function} val 216 * @return {Function} 217 * @api private 218 */ 219 220 exports.compileTrust = function(val) { 221 if (typeof val === 'function') return val; 222 223 if (val === true) { 224 // Support plain true/false 225 return function(){ return true }; 226 } 227 228 if (typeof val === 'number') { 229 // Support trusting hop count 230 return function(a, i){ return i < val }; 231 } 232 233 if (typeof val === 'string') { 234 // Support comma-separated values 235 val = val.split(/ *, */); 236 } 237 238 return proxyaddr.compile(val || []); 239 } 240 241 /** 242 * Set the charset in a given Content-Type string. 243 * 244 * @param {String} type 245 * @param {String} charset 246 * @return {String} 247 * @api private 248 */ 249 250 exports.setCharset = function setCharset(type, charset) { 251 if (!type || !charset) { 252 return type; 253 } 254 255 // parse type 256 var parsed = contentType.parse(type); 257 258 // set charset 259 parsed.parameters.charset = charset; 260 261 // format type 262 return contentType.format(parsed); 263 }; 264 265 /** 266 * Create an ETag generator function, generating ETags with 267 * the given options. 268 * 269 * @param {object} options 270 * @return {function} 271 * @private 272 */ 273 274 function createETagGenerator (options) { 275 return function generateETag (body, encoding) { 276 var buf = !Buffer.isBuffer(body) 277 ? Buffer.from(body, encoding) 278 : body 279 280 return etag(buf, options) 281 } 282 } 283 284 /** 285 * Parse an extended query string with qs. 286 * 287 * @return {Object} 288 * @private 289 */ 290 291 function parseExtendedQueryString(str) { 292 return qs.parse(str, { 293 allowPrototypes: true 294 }); 295 } 296 297 /** 298 * Return new empty object. 299 * 300 * @return {Object} 301 * @api private 302 */ 303 304 function newObject() { 305 return {}; 306 }