rc@73: /** rc@73: * Module dependencies. rc@73: */ rc@73: rc@73: var mime = require('send').mime; rc@73: var crc32 = require('buffer-crc32'); rc@73: var crypto = require('crypto'); rc@73: var basename = require('path').basename; rc@73: var deprecate = require('util').deprecate; rc@73: var proxyaddr = require('proxy-addr'); rc@73: rc@73: /** rc@73: * Simple detection of charset parameter in content-type rc@73: */ rc@73: var charsetRegExp = /;\s*charset\s*=/; rc@73: rc@73: /** rc@73: * Deprecate function, like core `util.deprecate`, rc@73: * but with NODE_ENV and color support. rc@73: * rc@73: * @param {Function} fn rc@73: * @param {String} msg rc@73: * @return {Function} rc@73: * @api private rc@73: */ rc@73: rc@73: exports.deprecate = function(fn, msg){ rc@73: if (process.env.NODE_ENV === 'test') return fn; rc@73: rc@73: // prepend module name rc@73: msg = 'express: ' + msg; rc@73: rc@73: if (process.stderr.isTTY) { rc@73: // colorize rc@73: msg = '\x1b[31;1m' + msg + '\x1b[0m'; rc@73: } rc@73: rc@73: return deprecate(fn, msg); rc@73: }; rc@73: rc@73: /** rc@73: * Return strong ETag for `body`. rc@73: * rc@73: * @param {String|Buffer} body rc@73: * @param {String} [encoding] rc@73: * @return {String} rc@73: * @api private rc@73: */ rc@73: rc@73: exports.etag = function etag(body, encoding){ rc@73: if (body.length === 0) { rc@73: // fast-path empty body rc@73: return '"1B2M2Y8AsgTpgAmY7PhCfg=="' rc@73: } rc@73: rc@73: var hash = crypto rc@73: .createHash('md5') rc@73: .update(body, encoding) rc@73: .digest('base64') rc@73: return '"' + hash + '"' rc@73: }; rc@73: rc@73: /** rc@73: * Return weak ETag for `body`. rc@73: * rc@73: * @param {String|Buffer} body rc@73: * @param {String} [encoding] rc@73: * @return {String} rc@73: * @api private rc@73: */ rc@73: rc@73: exports.wetag = function wetag(body, encoding){ rc@73: if (body.length === 0) { rc@73: // fast-path empty body rc@73: return 'W/"0-0"' rc@73: } rc@73: rc@73: var buf = Buffer.isBuffer(body) rc@73: ? body rc@73: : new Buffer(body, encoding) rc@73: var len = buf.length rc@73: return 'W/"' + len.toString(16) + '-' + crc32.unsigned(buf) + '"' rc@73: }; rc@73: rc@73: /** rc@73: * Check if `path` looks absolute. rc@73: * rc@73: * @param {String} path rc@73: * @return {Boolean} rc@73: * @api private rc@73: */ rc@73: rc@73: exports.isAbsolute = function(path){ rc@73: if ('/' == path[0]) return true; rc@73: if (':' == path[1] && '\\' == path[2]) return true; rc@73: if ('\\\\' == path.substring(0, 2)) return true; // Microsoft Azure absolute path rc@73: }; rc@73: rc@73: /** rc@73: * Flatten the given `arr`. rc@73: * rc@73: * @param {Array} arr rc@73: * @return {Array} rc@73: * @api private rc@73: */ rc@73: rc@73: exports.flatten = function(arr, ret){ rc@73: ret = ret || []; rc@73: var len = arr.length; rc@73: for (var i = 0; i < len; ++i) { rc@73: if (Array.isArray(arr[i])) { rc@73: exports.flatten(arr[i], ret); rc@73: } else { rc@73: ret.push(arr[i]); rc@73: } rc@73: } rc@73: return ret; rc@73: }; rc@73: rc@73: /** rc@73: * Normalize the given `type`, for example "html" becomes "text/html". rc@73: * rc@73: * @param {String} type rc@73: * @return {Object} rc@73: * @api private rc@73: */ rc@73: rc@73: exports.normalizeType = function(type){ rc@73: return ~type.indexOf('/') rc@73: ? acceptParams(type) rc@73: : { value: mime.lookup(type), params: {} }; rc@73: }; rc@73: rc@73: /** rc@73: * Normalize `types`, for example "html" becomes "text/html". rc@73: * rc@73: * @param {Array} types rc@73: * @return {Array} rc@73: * @api private rc@73: */ rc@73: rc@73: exports.normalizeTypes = function(types){ rc@73: var ret = []; rc@73: rc@73: for (var i = 0; i < types.length; ++i) { rc@73: ret.push(exports.normalizeType(types[i])); rc@73: } rc@73: rc@73: return ret; rc@73: }; rc@73: rc@73: /** rc@73: * Generate Content-Disposition header appropriate for the filename. rc@73: * non-ascii filenames are urlencoded and a filename* parameter is added rc@73: * rc@73: * @param {String} filename rc@73: * @return {String} rc@73: * @api private rc@73: */ rc@73: rc@73: exports.contentDisposition = function(filename){ rc@73: var ret = 'attachment'; rc@73: if (filename) { rc@73: filename = basename(filename); rc@73: // if filename contains non-ascii characters, add a utf-8 version ala RFC 5987 rc@73: ret = /[^\040-\176]/.test(filename) rc@73: ? 'attachment; filename="' + encodeURI(filename) + '"; filename*=UTF-8\'\'' + encodeURI(filename) rc@73: : 'attachment; filename="' + filename + '"'; rc@73: } rc@73: rc@73: return ret; rc@73: }; rc@73: rc@73: /** rc@73: * Parse accept params `str` returning an rc@73: * object with `.value`, `.quality` and `.params`. rc@73: * also includes `.originalIndex` for stable sorting rc@73: * rc@73: * @param {String} str rc@73: * @return {Object} rc@73: * @api private rc@73: */ rc@73: rc@73: function acceptParams(str, index) { rc@73: var parts = str.split(/ *; */); rc@73: var ret = { value: parts[0], quality: 1, params: {}, originalIndex: index }; rc@73: rc@73: for (var i = 1; i < parts.length; ++i) { rc@73: var pms = parts[i].split(/ *= */); rc@73: if ('q' == pms[0]) { rc@73: ret.quality = parseFloat(pms[1]); rc@73: } else { rc@73: ret.params[pms[0]] = pms[1]; rc@73: } rc@73: } rc@73: rc@73: return ret; rc@73: } rc@73: rc@73: /** rc@73: * Compile "etag" value to function. rc@73: * rc@73: * @param {Boolean|String|Function} val rc@73: * @return {Function} rc@73: * @api private rc@73: */ rc@73: rc@73: exports.compileETag = function(val) { rc@73: var fn; rc@73: rc@73: if (typeof val === 'function') { rc@73: return val; rc@73: } rc@73: rc@73: switch (val) { rc@73: case true: rc@73: fn = exports.wetag; rc@73: break; rc@73: case false: rc@73: break; rc@73: case 'strong': rc@73: fn = exports.etag; rc@73: break; rc@73: case 'weak': rc@73: fn = exports.wetag; rc@73: break; rc@73: default: rc@73: throw new TypeError('unknown value for etag function: ' + val); rc@73: } rc@73: rc@73: return fn; rc@73: } rc@73: rc@73: /** rc@73: * Compile "proxy trust" value to function. rc@73: * rc@73: * @param {Boolean|String|Number|Array|Function} val rc@73: * @return {Function} rc@73: * @api private rc@73: */ rc@73: rc@73: exports.compileTrust = function(val) { rc@73: if (typeof val === 'function') return val; rc@73: rc@73: if (val === true) { rc@73: // Support plain true/false rc@73: return function(){ return true }; rc@73: } rc@73: rc@73: if (typeof val === 'number') { rc@73: // Support trusting hop count rc@73: return function(a, i){ return i < val }; rc@73: } rc@73: rc@73: if (typeof val === 'string') { rc@73: // Support comma-separated values rc@73: val = val.split(/ *, */); rc@73: } rc@73: rc@73: return proxyaddr.compile(val || []); rc@73: } rc@73: rc@73: /** rc@73: * Set the charset in a given Content-Type string. rc@73: * rc@73: * @param {String} type rc@73: * @param {String} charset rc@73: * @return {String} rc@73: * @api private rc@73: */ rc@73: rc@73: exports.setCharset = function(type, charset){ rc@73: if (!type || !charset) return type; rc@73: rc@73: var exists = charsetRegExp.test(type); rc@73: rc@73: // removing existing charset rc@73: if (exists) { rc@73: var parts = type.split(';'); rc@73: rc@73: for (var i = 1; i < parts.length; i++) { rc@73: if (charsetRegExp.test(';' + parts[i])) { rc@73: parts.splice(i, 1); rc@73: break; rc@73: } rc@73: } rc@73: rc@73: type = parts.join(';'); rc@73: } rc@73: rc@73: return type + '; charset=' + charset; rc@73: };