/*! * express * Copyright(c) 2009-2013 TJ Holowaychuk * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. * @api private */ var contentType = require('content-type'); var etag = require('etag'); var mime = require('connect').mime; var proxyaddr = require('proxy-addr'); /** * toString ref. */ var toString = {}.toString; /** * Return strong ETag for `body`. * * @param {String|Buffer} body * @param {String} [encoding] * @return {String} * @api private */ exports.etag = function (body, encoding) { var buf = !Buffer.isBuffer(body) ? new Buffer(body, encoding) : body; return etag(buf, {weak: false}); }; /** * Return weak ETag for `body`. * * @param {String|Buffer} body * @param {String} [encoding] * @return {String} * @api private */ exports.wetag = function wetag(body, encoding){ var buf = !Buffer.isBuffer(body) ? new Buffer(body, encoding) : body; return etag(buf, {weak: true}); }; /** * Make `locals()` bound to the given `obj`. * * This is used for `app.locals` and `res.locals`. * * @param {Object} obj * @return {Function} * @api private */ exports.locals = function(){ function locals(obj){ for (var key in obj) locals[key] = obj[key]; return obj; }; return locals; }; /** * Check if `path` looks absolute. * * @param {String} path * @return {Boolean} * @api private */ exports.isAbsolute = function(path){ if ('/' == path[0]) return true; if (':' == path[1] && '\\' == path[2]) return true; if ('\\\\' == path.substring(0, 2)) return true; // Microsoft Azure absolute path }; /** * Flatten the given `arr`. * * @param {Array} arr * @return {Array} * @api private */ exports.flatten = function(arr, ret){ var ret = ret || [] , len = arr.length; for (var i = 0; i < len; ++i) { if (Array.isArray(arr[i])) { exports.flatten(arr[i], ret); } else { ret.push(arr[i]); } } return ret; }; /** * Normalize the given `type`, for example "html" becomes "text/html". * * @param {String} type * @return {Object} * @api private */ exports.normalizeType = function(type){ return ~type.indexOf('/') ? acceptParams(type) : { value: mime.lookup(type), params: {} }; }; /** * Normalize `types`, for example "html" becomes "text/html". * * @param {Array} types * @return {Array} * @api private */ exports.normalizeTypes = function(types){ var ret = []; for (var i = 0; i < types.length; ++i) { ret.push(exports.normalizeType(types[i])); } return ret; }; /** * Return the acceptable type in `types`, if any. * * @param {Array} types * @param {String} str * @return {String} * @api private */ exports.acceptsArray = function(types, str){ // accept anything when Accept is not present if (!str) return types[0]; // parse var accepted = exports.parseAccept(str) , normalized = exports.normalizeTypes(types) , len = accepted.length; for (var i = 0; i < len; ++i) { for (var j = 0, jlen = types.length; j < jlen; ++j) { if (exports.accept(normalized[j], accepted[i])) { return types[j]; } } } }; /** * Check if `type(s)` are acceptable based on * the given `str`. * * @param {String|Array} type(s) * @param {String} str * @return {Boolean|String} * @api private */ exports.accepts = function(type, str){ if ('string' == typeof type) type = type.split(/ *, */); return exports.acceptsArray(type, str); }; /** * Check if `type` array is acceptable for `other`. * * @param {Object} type * @param {Object} other * @return {Boolean} * @api private */ exports.accept = function(type, other){ var t = type.value.split('/'); return (t[0] == other.type || '*' == other.type) && (t[1] == other.subtype || '*' == other.subtype) && paramsEqual(type.params, other.params); }; /** * Check if accept params are equal. * * @param {Object} a * @param {Object} b * @return {Boolean} * @api private */ function paramsEqual(a, b){ return !Object.keys(a).some(function(k) { return a[k] != b[k]; }); } /** * Parse accept `str`, returning * an array objects containing * `.type` and `.subtype` along * with the values provided by * `parseQuality()`. * * @param {Type} name * @return {Type} * @api private */ exports.parseAccept = function(str){ return exports .parseParams(str) .map(function(obj){ var parts = obj.value.split('/'); obj.type = parts[0]; obj.subtype = parts[1]; return obj; }); }; /** * Parse quality `str`, returning an * array of objects with `.value`, * `.quality` and optional `.params` * * @param {String} str * @return {Array} * @api private */ exports.parseParams = function(str){ return str .split(/ *, */) .map(acceptParams) .filter(function(obj){ return obj.quality; }) .sort(function(a, b){ if (a.quality === b.quality) { return a.originalIndex - b.originalIndex; } else { return b.quality - a.quality; } }); }; /** * Parse accept params `str` returning an * object with `.value`, `.quality` and `.params`. * also includes `.originalIndex` for stable sorting * * @param {String} str * @return {Object} * @api private */ function acceptParams(str, index) { var parts = str.split(/ *; */); var ret = { value: parts[0], quality: 1, params: {}, originalIndex: index }; for (var i = 1; i < parts.length; ++i) { var pms = parts[i].split(/ *= */); if ('q' == pms[0]) { ret.quality = parseFloat(pms[1]); } else { ret.params[pms[0]] = pms[1]; } } return ret; } /** * Normalize the given path string, * returning a regular expression. * * An empty array should be passed, * which will contain the placeholder * key names. For example "/user/:id" will * then contain ["id"]. * * @param {String|RegExp|Array} path * @param {Array} keys * @param {Boolean} sensitive * @param {Boolean} strict * @return {RegExp} * @api private */ exports.pathRegexp = function(path, keys, sensitive, strict) { if (toString.call(path) == '[object RegExp]') return path; if (Array.isArray(path)) path = '(' + path.join('|') + ')'; path = path .concat(strict ? '' : '/?') .replace(/\/\(/g, '(?:/') .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){ keys.push({ name: key, optional: !! optional }); slash = slash || ''; return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + (optional || '') + (star ? '(/*)?' : ''); }) .replace(/([\/.])/g, '\\$1') .replace(/\*/g, '(.*)'); return new RegExp('^' + path + '$', sensitive ? '' : 'i'); } /** * Compile "etag" value to function. * * @param {Boolean|String|Function} val * @return {Function} * @api private */ exports.compileETag = function(val) { var fn; if (typeof val === 'function') { return val; } switch (val) { case true: fn = exports.wetag; break; case false: break; case 'strong': fn = exports.etag; break; case 'weak': fn = exports.wetag; break; default: throw new TypeError('unknown value for etag function: ' + val); } return fn; } /** * Compile "proxy trust" value to function. * * @param {Boolean|String|Number|Array|Function} val * @return {Function} * @api private */ exports.compileTrust = function(val) { if (typeof val === 'function') return val; if (val === true) { // Support plain true/false return function(){ return true }; } if (typeof val === 'number') { // Support trusting hop count return function(a, i){ return i < val }; } if (typeof val === 'string') { // Support comma-separated values val = val.split(/ *, */); } return proxyaddr.compile(val || []); } /** * Set the charset in a given Content-Type string. * * @param {String} type * @param {String} charset * @return {String} * @api private */ exports.setCharset = function setCharset(type, charset) { if (!type || !charset) { return type; } // parse type var parsed = contentType.parse(type); // set charset parsed.parameters.charset = charset; // format type return contentType.format(parsed); };