text: /*!
   * 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 '<li>' + escapeHtmlBlock(v) + '</li>' })
          .join('')
        var body = TEMPLATE
          .replace('{style}', STYLESHEET)
          .replace('{stack}', stackHtml)
          .replace('{title}', escapeHtml(exports.title))
          .replace('{statusCode}', res.statusCode)
          .replace(/\{error\}/g, errorHtml)
        res.setHeader('Content-Type', 'text/html; charset=utf-8')
        res.end(body)
      // json
      } else if (type === 'json') {
        var error = { message: err.message, stack: err.stack }
        for (var prop in err) error[prop] = err[prop]
        var json = JSON.stringify({ error: error }, null, 2)
        res.setHeader('Content-Type', 'application/json; charset=utf-8')
        res.end(json)
      // plain text
      } else {
        res.setHeader('Content-Type', 'text/plain; charset=utf-8')
        res.end(str)
      }
    }
  }
  
  /**
   * Template title, framework authors may override this value.
   */
  
  exports.title = 'Connect'
  
  /**
   * Escape a block of HTML, preserving whitespace.
   * @api private
   */
  
  function escapeHtmlBlock (str) {
    return escapeHtml(str)
      .replace(DOUBLE_SPACE_REGEXP, ' &nbsp;')
      .replace(NEW_LINE_REGEXP, '<br>')
  }
  
  /**
   * Stringify a value.
   * @api private
   */
  
  function stringify (val) {
    var stack = val.stack
  
    if (stack) {
      return String(stack)
    }
  
    var str = String(val)
  
    return str === toString.call(val)
      ? inspect(val)
      : str
  }
  
  /**
   * Log error to console.
   * @api private
   */
  
  function logerror (err, str) {
    console.error(str || err.stack)
  }
  