/*! * Connect - utils * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. */ var bytes = require('bytes'); var contentType = require('content-type'); var cookieParser = require('cookie-parser'); var createError = require('http-errors'); var deprecate = require('depd')('connect'); var http = require('http') , crypto = require('crypto') , parseurl = require('parseurl') , sep = require('path').sep , signature = require('cookie-signature') , typeis = require('type-is') , nodeVersion = process.versions.node.split('.'); var merge = require('utils-merge'); /** * pause is broken in node < 0.10 */ exports.brokenPause = parseInt(nodeVersion[0], 10) === 0 && parseInt(nodeVersion[1], 10) < 10; /** * Return `true` if the request has a body, otherwise return `false`. * * @param {IncomingMessage} req * @return {Boolean} * @api private */ exports.hasBody = deprecate.function(typeis.hasBody, 'utils.hasBody: use type-is npm module instead'); /** * Extract the mime type from the given request's * _Content-Type_ header. * * @param {IncomingMessage} req * @return {String} * @api private */ exports.mime = function(req) { var str = req.headers['content-type'] || '' , i = str.indexOf(';'); return ~i ? str.slice(0, i) : str; }; exports.mime = deprecate.function(exports.mime, 'utils.mime: use type-is npm module instead for mime comparisons'); /** * Generate an `Error` from the given status `code` * and optional `msg`. * * @param {Number} code * @param {String} msg * @return {Error} * @api private */ exports.error = function(code, msg){ var err = new Error(msg || http.STATUS_CODES[code]); err.status = code; return err; }; exports.error = deprecate.function(exports.error, 'utils.error: use http-errors npm module instead'); /** * Return md5 hash of the given string and optional encoding, * defaulting to hex. * * utils.md5('wahoo'); * // => "e493298061761236c96b02ea6aa8a2ad" * * @param {String} str * @param {String} encoding * @return {String} * @api private */ exports.md5 = function(str, encoding){ return crypto .createHash('md5') .update(str, 'utf8') .digest(encoding || 'hex'); }; exports.md5 = deprecate.function(exports.md5, 'utils.md5: use crypto npm module instead for hashing'); /** * Merge object b with object a. * * var a = { foo: 'bar' } * , b = { bar: 'baz' }; * * utils.merge(a, b); * // => { foo: 'bar', bar: 'baz' } * * @param {Object} a * @param {Object} b * @return {Object} * @api private */ exports.merge = deprecate.function(merge, 'utils.merge: use utils-merge npm module instead'); /** * Escape the given string of `html`. * * @param {String} html * @return {String} * @api private */ exports.escape = function(html){ return String(html) .replace(/&(?!\w+;)/g, '&') .replace(//g, '>') .replace(/"/g, '"'); }; exports.escape = deprecate.function(exports.escape, 'utils.escape: use escape-html npm module instead'); /** * Sign the given `val` with `secret`. * * @param {String} val * @param {String} secret * @return {String} * @api private */ exports.sign = deprecate.function(signature.sign, 'utils.sign: use cookie-signature npm module instead'); /** * Unsign and decode the given `val` with `secret`, * returning `false` if the signature is invalid. * * @param {String} val * @param {String} secret * @return {String|Boolean} * @api private */ exports.unsign = deprecate.function(signature.unsign, 'utils.unsign: use cookie-signature npm module instead'); /** * Parse signed cookies, returning an object * containing the decoded key/value pairs, * while removing the signed key from `obj`. * * @param {Object} obj * @return {Object} * @api private */ exports.parseSignedCookies = deprecate.function(cookieParser.signedCookies, 'utils.parseSignedCookies: use cookie-parser npm module instead'); /** * Parse a signed cookie string, return the decoded value * * @param {String} str signed cookie string * @param {String} secret * @return {String} decoded value * @api private */ exports.parseSignedCookie = deprecate.function(cookieParser.signedCookie, 'utils.parseSignedCookie: use cookie-parser npm module instead'); /** * Parse JSON cookies. * * @param {Object} obj * @return {Object} * @api private */ exports.parseJSONCookies = deprecate.function(cookieParser.JSONCookies, 'utils.parseJSONCookies: use cookie-parser npm module instead'); /** * Parse JSON cookie string * * @param {String} str * @return {Object} Parsed object or null if not json cookie * @api private */ exports.parseJSONCookie = deprecate.function(cookieParser.JSONCookie, 'utils.parseJSONCookie: use cookie-parser npm module instead'); /** * Pause `data` and `end` events on the given `obj`. * Middleware performing async tasks _should_ utilize * this utility (or similar), to re-emit data once * the async operation has completed, otherwise these * events may be lost. Pause is only required for * node versions less than 10, and is replaced with * noop's otherwise. * * var pause = utils.pause(req); * fs.readFile(path, function(){ * next(); * pause.resume(); * }); * * @param {Object} obj * @return {Object} * @api private */ exports.pause = exports.brokenPause ? require('pause') : function () { return { end: noop, resume: noop } } /** * Strip `Content-*` headers from `res`. * * @param {ServerResponse} res * @api private */ exports.removeContentHeaders = function(res){ if (!res._headers) return; Object.keys(res._headers).forEach(function(field){ if (0 == field.indexOf('content')) { res.removeHeader(field); } }); }; exports.removeContentHeaders = deprecate.function(exports.removeContentHeaders, 'utils.removeContentHeaders: this private api moved with serve-static'); /** * Check if `req` is a conditional GET request. * * @param {IncomingMessage} req * @return {Boolean} * @api private */ exports.conditionalGET = function(req) { return req.headers['if-modified-since'] || req.headers['if-none-match']; }; exports.conditionalGET = deprecate.function(exports.conditionalGET, 'utils.conditionalGET: use fresh npm module instead'); /** * Respond with 401 "Unauthorized". * * @param {ServerResponse} res * @param {String} realm * @api private */ exports.unauthorized = function(res, realm) { res.statusCode = 401; res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"'); res.end('Unauthorized'); }; exports.unauthorized = deprecate.function(exports.unauthorized, 'utils.unauthorized: this private api moved with basic-auth-connect'); /** * Respond with 304 "Not Modified". * * @param {ServerResponse} res * @param {Object} headers * @api private */ exports.notModified = function(res) { exports.removeContentHeaders(res); res.statusCode = 304; res.end(); }; exports.notModified = deprecate.function(exports.notModified, 'utils.notModified: this private api moved with serve-static'); /** * Return an ETag in the form of `"-"` * from the given `stat`. * * @param {Object} stat * @return {String} * @api private */ exports.etag = function(stat) { return '"' + stat.size + '-' + Number(stat.mtime) + '"'; }; exports.etag = deprecate.function(exports.etag, 'utils.etag: this private api moved with serve-static'); /** * Parse the given Cache-Control `str`. * * @param {String} str * @return {Object} * @api private */ exports.parseCacheControl = function(str){ var directives = str.split(',') , obj = {}; for(var i = 0, len = directives.length; i < len; i++) { var parts = directives[i].split('=') , key = parts.shift().trim() , val = parseInt(parts.shift(), 10); obj[key] = isNaN(val) ? true : val; } return obj; }; /** * Parse the `req` url with memoization. * * @param {ServerRequest} req * @return {Object} * @api private */ exports.parseUrl = deprecate.function(parseurl, 'utils.parseUrl: use parseurl npm module instead'); /** * Parse byte `size` string. * * @param {String} size * @return {Number} * @api private */ exports.parseBytes = deprecate.function(bytes, 'utils.parseBytes: use bytes npm module instead'); /** * Normalizes the path separator from system separator * to URL separator, aka `/`. * * @param {String} path * @return {String} * @api private */ exports.normalizeSlashes = function normalizeSlashes(path) { return path.split(sep).join('/'); }; exports.normalizeSlashes = deprecate.function(exports.normalizeSlashes, 'utils.normalizeSlashes: this private api moved with serve-index'); /** * Set the charset in a given Content-Type string if none exists. * * @param {String} type * @param {String} charset * @return {String} * @api private */ exports.setCharset = function setCharset(type, charset) { if (!type || !charset) return type; var parsed = contentType.parse(type); var exists = parsed.parameters.charset; // keep existing charset if (exists) { return type; } // set charset parsed.parameters.charset = charset; return contentType.format(parsed); }; function noop() {}