| |
| /** |
| * 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(obj){ |
| 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; |
| }; |
| |
| /** |
| * 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, '<') |
| .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'); |
| } |