utils.js
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 { METHODS } = require('node:http'); 16 var contentType = require('content-type'); 17 var etag = require('etag'); 18 var mime = require('mime-types') 19 var proxyaddr = require('proxy-addr'); 20 var qs = require('qs'); 21 var querystring = require('querystring'); 22 23 /** 24 * A list of lowercased HTTP methods that are supported by Node.js. 25 * @api private 26 */ 27 exports.methods = METHODS.map((method) => method.toLowerCase()); 28 29 /** 30 * Return strong ETag for `body`. 31 * 32 * @param {String|Buffer} body 33 * @param {String} [encoding] 34 * @return {String} 35 * @api private 36 */ 37 38 exports.etag = createETagGenerator({ weak: false }) 39 40 /** 41 * Return weak ETag for `body`. 42 * 43 * @param {String|Buffer} body 44 * @param {String} [encoding] 45 * @return {String} 46 * @api private 47 */ 48 49 exports.wetag = createETagGenerator({ weak: true }) 50 51 /** 52 * Normalize the given `type`, for example "html" becomes "text/html". 53 * 54 * @param {String} type 55 * @return {Object} 56 * @api private 57 */ 58 59 exports.normalizeType = function(type){ 60 return ~type.indexOf('/') 61 ? acceptParams(type) 62 : { value: (mime.lookup(type) || 'application/octet-stream'), params: {} } 63 }; 64 65 /** 66 * Normalize `types`, for example "html" becomes "text/html". 67 * 68 * @param {Array} types 69 * @return {Array} 70 * @api private 71 */ 72 73 exports.normalizeTypes = function(types) { 74 return types.map(exports.normalizeType); 75 }; 76 77 78 /** 79 * Parse accept params `str` returning an 80 * object with `.value`, `.quality` and `.params`. 81 * 82 * @param {String} str 83 * @return {Object} 84 * @api private 85 */ 86 87 function acceptParams (str) { 88 var length = str.length; 89 var colonIndex = str.indexOf(';'); 90 var index = colonIndex === -1 ? length : colonIndex; 91 var ret = { value: str.slice(0, index).trim(), quality: 1, params: {} }; 92 93 while (index < length) { 94 var splitIndex = str.indexOf('=', index); 95 if (splitIndex === -1) break; 96 97 var colonIndex = str.indexOf(';', index); 98 var endIndex = colonIndex === -1 ? length : colonIndex; 99 100 if (splitIndex > endIndex) { 101 index = str.lastIndexOf(';', splitIndex - 1) + 1; 102 continue; 103 } 104 105 var key = str.slice(index, splitIndex).trim(); 106 var value = str.slice(splitIndex + 1, endIndex).trim(); 107 108 if (key === 'q') { 109 ret.quality = parseFloat(value); 110 } else { 111 ret.params[key] = value; 112 } 113 114 index = endIndex + 1; 115 } 116 117 return ret; 118 } 119 120 /** 121 * Compile "etag" value to function. 122 * 123 * @param {Boolean|String|Function} val 124 * @return {Function} 125 * @api private 126 */ 127 128 exports.compileETag = function(val) { 129 var fn; 130 131 if (typeof val === 'function') { 132 return val; 133 } 134 135 switch (val) { 136 case true: 137 case 'weak': 138 fn = exports.wetag; 139 break; 140 case false: 141 break; 142 case 'strong': 143 fn = exports.etag; 144 break; 145 default: 146 throw new TypeError('unknown value for etag function: ' + val); 147 } 148 149 return fn; 150 } 151 152 /** 153 * Compile "query parser" value to function. 154 * 155 * @param {String|Function} val 156 * @return {Function} 157 * @api private 158 */ 159 160 exports.compileQueryParser = function compileQueryParser(val) { 161 var fn; 162 163 if (typeof val === 'function') { 164 return val; 165 } 166 167 switch (val) { 168 case true: 169 case 'simple': 170 fn = querystring.parse; 171 break; 172 case false: 173 break; 174 case 'extended': 175 fn = parseExtendedQueryString; 176 break; 177 default: 178 throw new TypeError('unknown value for query parser function: ' + val); 179 } 180 181 return fn; 182 } 183 184 /** 185 * Compile "proxy trust" value to function. 186 * 187 * @param {Boolean|String|Number|Array|Function} val 188 * @return {Function} 189 * @api private 190 */ 191 192 exports.compileTrust = function(val) { 193 if (typeof val === 'function') return val; 194 195 if (val === true) { 196 // Support plain true/false 197 return function(){ return true }; 198 } 199 200 if (typeof val === 'number') { 201 // Support trusting hop count 202 return function(a, i){ return i < val }; 203 } 204 205 if (typeof val === 'string') { 206 // Support comma-separated values 207 val = val.split(',') 208 .map(function (v) { return v.trim() }) 209 } 210 211 return proxyaddr.compile(val || []); 212 } 213 214 /** 215 * Set the charset in a given Content-Type string. 216 * 217 * @param {String} type 218 * @param {String} charset 219 * @return {String} 220 * @api private 221 */ 222 223 exports.setCharset = function setCharset(type, charset) { 224 if (!type || !charset) { 225 return type; 226 } 227 228 // parse type 229 var parsed = contentType.parse(type); 230 231 // set charset 232 parsed.parameters.charset = charset; 233 234 // format type 235 return contentType.format(parsed); 236 }; 237 238 /** 239 * Create an ETag generator function, generating ETags with 240 * the given options. 241 * 242 * @param {object} options 243 * @return {function} 244 * @private 245 */ 246 247 function createETagGenerator (options) { 248 return function generateETag (body, encoding) { 249 var buf = !Buffer.isBuffer(body) 250 ? Buffer.from(body, encoding) 251 : body 252 253 return etag(buf, options) 254 } 255 } 256 257 /** 258 * Parse an extended query string with qs. 259 * 260 * @param {String} str 261 * @return {Object} 262 * @private 263 */ 264 265 function parseExtendedQueryString(str) { 266 return qs.parse(str, { 267 allowPrototypes: true 268 }); 269 }