/*! * errorhandler * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * Copyright(c) 2014 Jonathan Ong * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ 'use strict' /** * Module dependencies. * @private */ var accepts = require('accepts') var escapeHtml = require('escape-html') var fs = require('fs') var path = require('path') var util = require('util') /** * Module variables. * @private */ var DOUBLE_SPACE_REGEXP = /\x20{2}/g var NEW_LINE_REGEXP = /\n/g var STYLESHEET = fs.readFileSync(path.join(__dirname, '/public/style.css'), 'utf8') var TEMPLATE = fs.readFileSync(path.join(__dirname, '/public/error.html'), 'utf8') var inspect = util.inspect var toString = Object.prototype.toString /* istanbul ignore next */ var defer = typeof setImmediate === 'function' ? setImmediate : function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) } /** * Error handler: * * Development error handler, providing stack traces * and error message responses for requests accepting text, html, * or json. * * Text: * * By default, and when _text/plain_ is accepted a simple stack trace * or error message will be returned. * * JSON: * * When _application/json_ is accepted, connect will respond with * an object in the form of `{ "error": error }`. * * HTML: * * When accepted connect will output a nice html stack trace. * * @return {Function} * @api public */ exports = module.exports = function errorHandler (options) { // get environment var env = process.env.NODE_ENV || 'development' // get options var opts = options || {} // get log option var log = opts.log === undefined ? env !== 'test' : opts.log if (typeof log !== 'function' && typeof log !== 'boolean') { throw new TypeError('option log must be function or boolean') } // default logging using console.error if (log === true) { log = logerror } return function errorHandler (err, req, res, next) { // respect err.statusCode if (err.statusCode) { res.statusCode = err.statusCode } // respect err.status if (err.status) { res.statusCode = err.status } // default status code to 500 if (res.statusCode < 400) { res.statusCode = 500 } // log the error var str = stringify(err) if (log) { defer(log, err, str, req, res) } // cannot actually respond if (res._header) { return req.socket.destroy() } // negotiate var accept = accepts(req) var type = accept.type('html', 'json', 'text') // Security header for content sniffing res.setHeader('X-Content-Type-Options', 'nosniff') // html if (type === 'html') { var isInspect = !err.stack && String(err) === toString.call(err) var errorHtml = !isInspect ? escapeHtmlBlock(str.split('\n', 1)[0] || 'Error') : 'Error' var stack = !isInspect ? String(str).split('\n').slice(1) : [str] var stackHtml = stack .map(function (v) { return '