text: /*!
   * send
   * Copyright(c) 2012 TJ Holowaychuk
   * Copyright(c) 2014-2015 Douglas Christopher Wilson
   * MIT Licensed
   */
  
  'use strict'
  
  /**
   * Module dependencies.
   * @private
   */
  
  var createError = require('http-errors')
  var debug = require('debug')('send')
  var deprecate = require('depd')('send')
  var destroy = require('destroy')
  var escapeHtml = require('escape-html')
    , parseRange = require('range-parser')
    , Stream = require('stream')
    , mime = require('mime')
    , fresh = require('fresh')
    , path = require('path')
    , fs = require('fs')
    , normalize = path.normalize
    , join = path.join
  var etag = require('etag')
  var EventEmitter = require('events').EventEmitter;
  var ms = require('ms');
  var onFinished = require('on-finished')
  var statuses = require('statuses')
  
  /**
   * Variables.
   */
  var extname = path.extname
  var maxMaxAge = 60 * 60 * 24 * 365 * 1000; // 1 year
  var resolve = path.resolve
  var sep = path.sep
  var toString = Object.prototype.toString
  var upPathRegexp = /(?:^|[\\\/])\.\.(?:[\\\/]|$)/
  
  /**
   * Module exports.
   * @public
   */
  
  module.exports = send
  module.exports.mime = mime
  
  /**
   * Shim EventEmitter.listenerCount for node.js < 0.10
   */
  
  /* istanbul ignore next */
  var listenerCount = EventEmitter.listenerCount
    || function(emitter, type){ return emitter.listeners(type).length; };
  
  /**
   * Return a `SendStream` for `req` and `path`.
   *
   * @param {object} req
   * @param {string} path
   * @param {object} [options]
   * @return {SendStream}
   * @public
   */
  
  function send(req, path, options) {
    return new SendStream(req, path, options);
  }
  
  /**
   * Initialize a `SendStream` with the given `path`.
   *
   * @param {Request} req
   * @param {String} path
   * @param {object} [options]
   * @private
   */
  
  function SendStream(req, path, options) {
    var opts = options || {}
  
    this.options = opts
    this.path = path
    this.req = req
  
    this._etag = opts.etag !== undefined
      ? Boolean(opts.etag)
      : true
  
    this._dotfiles = opts.dotfiles !== undefined
      ? opts.dotfiles
      : 'ignore'
  
    if (this._dotfiles !== 'ignore' && this._dotfiles !== 'allow' && this._dotfiles !== 'deny') {
      throw new TypeError('dotfiles option must be "allow", "deny", or "ignore"')
    }
  
    this._hidden = Boolean(opts.hidden)
  
    if (opts.hidden !== undefined) {
      deprecate('hidden: use dotfiles: \'' + (this._hidden ? 'allow' : 'ignore') + '\' instead')
    }
  
    // legacy support
    if (opts.dotfiles === undefined) {
      this._dotfiles = undefined
    }
  
    this._extensions = opts.extensions !== undefined
      ? normalizeList(opts.extensions, 'extensions option')
      : []
  
    this._index = opts.index !== undefined
      ? normalizeList(opts.index, 'index option')
      : ['index.html']
  
    this._lastModified = opts.lastModified !== undefined
      ? Boolean(opts.lastModified)
      : true
  
    this._maxage = opts.maxAge || opts.maxage
    this._maxage = typeof this._maxage === 'string'
      ? ms(this._maxage)
      : Number(this._maxage)
    this._maxage = !isNaN(this._maxage)
      ? Math.min(Math.max(0, this._maxage), maxMaxAge)
      : 0
  
    this._root = opts.root
      ? resolve(opts.root)
      : null
  
    if (!this._root && opts.from) {
      this.from(opts.from)
    }
  }
  
  /**
   * Inherits from `Stream.prototype`.
   */
  
  SendStream.prototype.__proto__ = Stream.prototype;
  
  /**
   * Enable or disable etag generation.
   *
   * @param {Boolean} val
   * @return {SendStream}
   * @api public
   */
  
  SendStream.prototype.etag = deprecate.function(function etag(val) {
    val = Boolean(val);
    debug('etag %s', val);
    this._etag = val;
    return this;
  }, 'send.etag: pass etag as option');
  
  /**
   * Enable or disable "hidden" (dot) files.
   *
   * @param {Boolean} path
   * @return {SendStream}
   * @api public
   */
  
  SendStream.prototype.hidden = deprecate.function(function hidden(val) {
    val = Boolean(val);
    debug('hidden %s', val);
    this._hidden = val;
    this._dotfiles = undefined
    return this;
  }, 'send.hidden: use dotfiles option');
  
  /**
   * Set index `paths`, set to a falsy
   * value to disable index support.
   *
   * @param {String|Boolean|Array} paths
   * @return {SendStream}
   * @api public
   */
  
  SendStream.prototype.index = deprecate.function(function index(paths) {
    var index = !paths ? [] : normalizeList(paths, 'paths argument');
    debug('index %o', paths);
    this._index = index;
    return this;
  }, 'send.index: pass index as option');
  
  /**
   * Set root `path`.
   *
   * @param {String} path
   * @return {SendStream}
   * @api public
   */
  
  SendStream.prototype.root = function(path){
    path = String(path);
    this._root = resolve(path)
    return this;
  };
  
  SendStream.prototype.from = deprecate.function(SendStream.prototype.root,
    'send.from: pass root as option');
  
  SendStream.prototype.root = deprecate.function(SendStream.prototype.root,
    'send.root: pass root as option');
  
  /**
   * Set max-age to `maxAge`.
   *
   * @param {Number} maxAge
   * @return {SendStream}
   * @api public
   */
  
  SendStream.prototype.maxage = deprecate.function(function maxage(maxAge) {
    maxAge = typeof maxAge === 'string'
      ? ms(maxAge)
      : Number(maxAge);
    if (isNaN(maxAge)) maxAge = 0;
    if (Infinity == maxAge) maxAge = 60 * 60 * 24 * 365 * 1000;
    debug('max-age %d', maxAge);
    this._maxage = maxAge;
    return this;
  }, 'send.maxage: pass maxAge as option');
  
  /**
   * Emit error with `status`.
   *
   * @param {number} status
   * @param {Error} [error]
   * @private
   */
  
  SendStream.prototype.error = function error(status, error) {
    // emit if listeners instead of responding
    if (listenerCount(this, 'error') !== 0) {
      return this.emit('error', createError(error, status, {
        expose: false
      }))
    }
  
    var res = this.res
    var msg = statuses[status]
  
    // wipe all existing headers
    res._headers = null
  
    // send basic response
    res.statusCode = status
    res.setHeader('Content-Type', 'text/plain; charset=UTF-8')
    res.setHeader('Content-Length', Buffer.byteLength(msg))
    res.setHeader('X-Content-Type-Options', 'nosniff')
    res.end(msg)
  }
  
  /**
   * Check if the pathname ends with "/".
   *
   * @return {Boolean}
   * @api private
   */
  
  SendStream.prototype.hasTrailingSlash = function(){
    return '/' == this.path[this.path.length - 1];
  };
  
  /**
   * Check if this is a conditional GET request.
   *
   * @return {Boolean}
   * @api private
   */
  
  SendStream.prototype.isConditionalGET = function(){
    return this.req.headers['if-none-match']
      || this.req.headers['if-modified-since'];
  };
  
  /**
   * Strip content-* header fields.
   *
   * @private
   */
  
  SendStream.prototype.removeContentHeaderFields = function removeContentHeaderFields() {
    var res = this.res
    var headers = Object.keys(res._headers || {})
  
    for (var i = 0; i < headers.length; i++) {
      var header = headers[i]
      if (header.substr(0, 8) === 'content-' && header !== 'content-location') {
        res.removeHeader(header)
      }
    }
  }
  
  /**
   * Respond with 304 not modified.
   *
   * @api private
   */
  
  SendStream.prototype.notModified = function(){
    var res = this.res;
    debug('not modified');
    this.removeContentHeaderFields();
    res.statusCode = 304;
    res.end();
  };
  
  /**
   * Raise error that headers already sent.
   *
   * @api private
   */
  
  SendStream.prototype.headersAlreadySent = function headersAlreadySent(){
    var err = new Error('Can\'t set headers after they are sent.');
    debug('headers already sent');
    this.error(500, err);
  };
  
  /**
   * Check if the request is cacheable, aka
   * responded with 2xx or 304 (see RFC 2616 section 14.2{5,6}).
   *
   * @return {Boolean}
   * @api private
   */
  
  SendStream.prototype.isCachable = function(){
    var res = this.res;
    return (res.statusCode >= 200 && res.statusCode < 300) || 304 == res.statusCode;
  };
  
  /**
   * Handle stat() error.
   *
   * @param {Error} error
   * @private
   */
  
  SendStream.prototype.onStatError = function onStatError(error) {
    switch (error.code) {
      case 'ENAMETOOLONG':
      case 'ENOENT':
      case 'ENOTDIR':
        this.error(404, error)
        break
      default:
        this.error(500, error)
        break
    }
  }
  
  /**
   * Check if the cache is fresh.
   *
   * @return {Boolean}
   * @api private
   */
  
  SendStream.prototype.isFresh = function(){
    return fresh(this.req.headers, this.res._headers);
  };
  
  /**
   * Check if the range is fresh.
   *
   * @return {Boolean}
   * @api private
   */
  
  SendStream.prototype.isRangeFresh = function isRangeFresh(){
    var ifRange = this.req.headers['if-range'];
  
    if (!ifRange) return true;
  
    return ~ifRange.indexOf('"')
      ? ~ifRange.indexOf(this.res._headers['etag'])
      : Date.parse(this.res._headers['last-modified']) <= Date.parse(ifRange);
  };
  
  /**
   * Redirect to path.
   *
   * @param {string} path
   * @private
   */
  
  SendStream.prototype.redirect = function redirect(path) {
    if (listenerCount(this, 'directory') !== 0) {
      this.emit('directory')
      return
    }
  
    if (this.hasTrailingSlash()) {
      this.error(403)
      return
    }
  
    var loc = path + '/'
    var msg = 'Redirecting to <a href="' + escapeHtml(loc) + '">' + escapeHtml(loc) + '</a>\n'
    var res = this.res
  
    // redirect
    res.statusCode = 301
    res.setHeader('Content-Type', 'text/html; charset=UTF-8')
    res.setHeader('Content-Length', Buffer.byteLength(msg))
    res.setHeader('X-Content-Type-Options', 'nosniff')
    res.setHeader('Location', loc)
    res.end(msg)
  }
  
  /**
   * Pipe to `res.
   *
   * @param {Stream} res
   * @return {Stream} res
   * @api public
   */
  
  SendStream.prototype.pipe = function(res){
    var self = this
      , args = arguments
      , root = this._root;
  
    // references
    this.res = res;
  
    // decode the path
    var path = decode(this.path)
    if (path === -1) return this.error(400)
  
    // null byte(s)
    if (~path.indexOf('\0')) return this.error(400);
  
    var parts
    if (root !== null) {
      // malicious path
      if (upPathRegexp.test(normalize('.' + sep + path))) {
        debug('malicious path "%s"', path)
        return this.error(403)
      }
  
      // join / normalize from optional root dir
      path = normalize(join(root, path))
      root = normalize(root + sep)
  
      // explode path parts
      parts = path.substr(root.length).split(sep)
    } else {
      // ".." is malicious without "root"
      if (upPathRegexp.test(path)) {
        debug('malicious path "%s"', path)
        return this.error(403)
      }
  
      // explode path parts
      parts = normalize(path).split(sep)
  
      // resolve the path
      path = resolve(path)
    }
  
    // dotfile handling
    if (containsDotFile(parts)) {
      var access = this._dotfiles
  
      // legacy support
      if (access === undefined) {
        access = parts[parts.length - 1][0] === '.'
          ? (this._hidden ? 'allow' : 'ignore')
          : 'allow'
      }
  
      debug('%s dotfile "%s"', access, path)
      switch (access) {
        case 'allow':
          break
        case 'deny':
          return this.error(403)
        case 'ignore':
        default:
          return this.error(404)
      }
    }
  
    // index file support
    if (this._index.length && this.path[this.path.length - 1] === '/') {
      this.sendIndex(path);
      return res;
    }
  
    this.sendFile(path);
    return res;
  };
  
  /**
   * Transfer `path`.
   *
   * @param {String} path
   * @api public
   */
  
  SendStream.prototype.send = function(path, stat){
    var len = stat.size;
    var options = this.options
    var opts = {}
    var res = this.res;
    var req = this.req;
    var ranges = req.headers.range;
    var offset = options.start || 0;
  
    if (res._header) {
      // impossible to send now
      return this.headersAlreadySent();
    }
  
    debug('pipe "%s"', path)
  
    // set header fields
    this.setHeader(path, stat);
  
    // set content-type
    this.type(path);
  
    // conditional GET support
    if (this.isConditionalGET()
      && this.isCachable()
      && this.isFresh()) {
      return this.notModified();
    }
  
    // adjust len to start/end options
    len = Math.max(0, len - offset);
    if (options.end !== undefined) {
      var bytes = options.end - offset + 1;
      if (len > bytes) len = bytes;
    }
  
    // Range support
    if (ranges) {
      ranges = parseRange(len, ranges);
  
      // If-Range support
      if (!this.isRangeFresh()) {
        debug('range stale');
        ranges = -2;
      }
  
      // unsatisfiable
      if (-1 == ranges) {
        debug('range unsatisfiable');
        res.setHeader('Content-Range', 'bytes */' + stat.size);
        return this.error(416);
      }
  
      // valid (syntactically invalid/multiple ranges are treated as a regular response)
      if (-2 != ranges && ranges.length === 1) {
        debug('range %j', ranges);
  
        // Content-Range
        res.statusCode = 206;
        res.setHeader('Content-Range', 'bytes '
          + ranges[0].start
          + '-'
          + ranges[0].end
          + '/'
          + len);
  
        offset += ranges[0].start;
        len = ranges[0].end - ranges[0].start + 1;
      }
    }
  
    // clone options
    for (var prop in options) {
      opts[prop] = options[prop]
    }
  
    // set read options
    opts.start = offset
    opts.end = Math.max(offset, offset + len - 1)
  
    // content-length
    res.setHeader('Content-Length', len);
  
    // HEAD support
    if ('HEAD' == req.method) return res.end();
  
    this.stream(path, opts)
  };
  
  /**
   * Transfer file for `path`.
   *
   * @param {String} path
   * @api private
   */
  SendStream.prototype.sendFile = function sendFile(path) {
    var i = 0
    var self = this
  
    debug('stat "%s"', path);
    fs.stat(path, function onstat(err, stat) {
      if (err && err.code === 'ENOENT'
        && !extname(path)
        && path[path.length - 1] !== sep) {
        // not found, check extensions
        return next(err)
      }
      if (err) return self.onStatError(err)
      if (stat.isDirectory()) return self.redirect(self.path)
      self.emit('file', path, stat)
      self.send(path, stat)
    })
  
    function next(err) {
      if (self._extensions.length <= i) {
        return err
          ? self.onStatError(err)
          : self.error(404)
      }
  
      var p = path + '.' + self._extensions[i++]
  
      debug('stat "%s"', p)
      fs.stat(p, function (err, stat) {
        if (err) return next(err)
        if (stat.isDirectory()) return next()
        self.emit('file', p, stat)
        self.send(p, stat)
      })
    }
  }
  
  /**
   * Transfer index for `path`.
   *
   * @param {String} path
   * @api private
   */
  SendStream.prototype.sendIndex = function sendIndex(path){
    var i = -1;
    var self = this;
  
    function next(err){
      if (++i >= self._index.length) {
        if (err) return self.onStatError(err);
        return self.error(404);
      }
  
      var p = join(path, self._index[i]);
  
      debug('stat "%s"', p);
      fs.stat(p, function(err, stat){
        if (err) return next(err);
        if (stat.isDirectory()) return next();
        self.emit('file', p, stat);
        self.send(p, stat);
      });
    }
  
    next();
  };
  
  /**
   * Stream `path` to the response.
   *
   * @param {String} path
   * @param {Object} options
   * @api private
   */
  
  SendStream.prototype.stream = function(path, options){
    // TODO: this is all lame, refactor meeee
    var finished = false;
    var self = this;
    var res = this.res;
    var req = this.req;
  
    // pipe
    var stream = fs.createReadStream(path, options);
    this.emit('stream', stream);
    stream.pipe(res);
  
    // response finished, done with the fd
    onFinished(res, function onfinished(){
      finished = true;
      destroy(stream);
    });
  
    // error handling code-smell
    stream.on('error', function onerror(err){
      // request already finished
      if (finished) return;
  
      // clean up stream
      finished = true;
      destroy(stream);
  
      // error
      self.onStatError(err);
    });
  
    // end
    stream.on('end', function onend(){
      self.emit('end');
    });
  };
  
  /**
   * Set content-type based on `path`
   * if it hasn't been explicitly set.
   *
   * @param {String} path
   * @api private
   */
  
  SendStream.prototype.type = function(path){
    var res = this.res;
    if (res.getHeader('Content-Type')) return;
    var type = mime.lookup(path);
    var charset = mime.charsets.lookup(type);
    debug('content-type %s', type);
    res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
  };
  
  /**
   * Set response header fields, most
   * fields may be pre-defined.
   *
   * @param {String} path
   * @param {Object} stat
   * @api private
   */
  
  SendStream.prototype.setHeader = function setHeader(path, stat){
    var res = this.res;
  
    this.emit('headers', res, path, stat);
  
    if (!res.getHeader('Accept-Ranges')) res.setHeader('Accept-Ranges', 'bytes');
    if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + Math.floor(this._maxage / 1000));
  
    if (this._lastModified && !res.getHeader('Last-Modified')) {
      var modified = stat.mtime.toUTCString()
      debug('modified %s', modified)
      res.setHeader('Last-Modified', modified)
    }
  
    if (this._etag && !res.getHeader('ETag')) {
      var val = etag(stat)
      debug('etag %s', val)
      res.setHeader('ETag', val)
    }
  };
  
  /**
   * Determine if path parts contain a dotfile.
   *
   * @api private
   */
  
  function containsDotFile(parts) {
    for (var i = 0; i < parts.length; i++) {
      if (parts[i][0] === '.') {
        return true
      }
    }
  
    return false
  }
  
  /**
   * decodeURIComponent.
   *
   * Allows V8 to only deoptimize this fn instead of all
   * of send().
   *
   * @param {String} path
   * @api private
   */
  
  function decode(path) {
    try {
      return decodeURIComponent(path)
    } catch (err) {
      return -1
    }
  }
  
  /**
   * Normalize the index option into an array.
   *
   * @param {boolean|string|array} val
   * @param {string} name
   * @private
   */
  
  function normalizeList(val, name) {
    var list = [].concat(val || [])
  
    for (var i = 0; i < list.length; i++) {
      if (typeof list[i] !== 'string') {
        throw new TypeError(name + ' must be array of strings or false')
      }
    }
  
    return list
  }
  