/** * Module dependencies. */ var mime = require('connect').mime , crc32 = require('buffer-crc32'); /** * toString ref. */ var toString = {}.toString; /** * Return ETag for `body`. * * @param {String|Buffer} body * @return {String} * @api private */ exports.etag = function(body){ return '"' + crc32.signed(body) + '"'; }; /** * 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; } /** * Escape special characters in the given string of html. * * @param {String} html * @return {String} * @api private */ exports.escape = function(html) { return String(html) .replace(/&/g, '&') .replace(/"/g, '"') .replace(//g, '>'); }; /** * 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'); }