]> git.cworth.org Git - obsolete/notmuch-web/blobdiff - node_modules/express/lib/response.js
Install the "express" node module via npm
[obsolete/notmuch-web] / node_modules / express / lib / response.js
diff --git a/node_modules/express/lib/response.js b/node_modules/express/lib/response.js
new file mode 100644 (file)
index 0000000..2c3ac64
--- /dev/null
@@ -0,0 +1,423 @@
+
+/*!
+ * Express - response
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var fs = require('fs')
+  , http = require('http')
+  , path = require('path')
+  , connect = require('connect')
+  , utils = connect.utils
+  , parseRange = require('./utils').parseRange
+  , res = http.ServerResponse.prototype
+  , send = connect.static.send
+  , join = require('path').join
+  , mime = require('mime');
+
+/**
+ * Send a response with the given `body` and optional `headers` and `status` code.
+ *
+ * Examples:
+ *
+ *     res.send();
+ *     res.send(new Buffer('wahoo'));
+ *     res.send({ some: 'json' });
+ *     res.send('<p>some html</p>');
+ *     res.send('Sorry, cant find that', 404);
+ *     res.send('text', { 'Content-Type': 'text/plain' }, 201);
+ *     res.send(404);
+ *
+ * @param {String|Object|Number|Buffer} body or status
+ * @param {Object|Number} headers or status
+ * @param {Number} status
+ * @return {ServerResponse}
+ * @api public
+ */
+
+res.send = function(body, headers, status){
+  // allow status as second arg
+  if ('number' == typeof headers) {
+    status = headers,
+    headers = null;
+  }
+
+  // default status
+  status = status || this.statusCode;
+
+  // allow 0 args as 204
+  if (!arguments.length || undefined === body) body = status = 204;
+
+  // determine content type
+  switch (typeof body) {
+    case 'number':
+      if (!this.header('Content-Type')) {
+        this.contentType('.txt');
+      }
+      body = http.STATUS_CODES[status = body];
+      break;
+    case 'string':
+      if (!this.header('Content-Type')) {
+        this.charset = this.charset || 'utf-8';
+        this.contentType('.html');
+      }
+      break;
+    case 'boolean':
+    case 'object':
+      if (Buffer.isBuffer(body)) {
+        if (!this.header('Content-Type')) {
+          this.contentType('.bin');
+        }
+      } else {
+        if (!this.header('Content-Type')) {
+          this.charset = this.charset || 'utf-8';
+          this.contentType('.json');
+        }
+        body = JSON.stringify(body);
+        if (this.req.query.callback && this.app.set('jsonp callback')) {
+          this.charset = this.charset || 'utf-8';
+          this.header('Content-Type', 'text/javascript');
+          body = this.req.query.callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
+        }
+      }
+      break;
+  }
+
+  // populate Content-Length
+  if (!this.header('Content-Length')) {
+    this.header('Content-Length', Buffer.isBuffer(body)
+      ? body.length
+      : Buffer.byteLength(body));
+  }
+
+  // merge headers passed
+  if (headers) {
+    var fields = Object.keys(headers);
+    for (var i = 0, len = fields.length; i < len; ++i) {
+      var field = fields[i];
+      this.header(field, headers[field]);
+    }
+  }
+
+  // strip irrelevant headers
+  if (204 === status) {
+    this.removeHeader('Content-Type');
+    this.removeHeader('Content-Length');
+  }
+
+  // respond
+  this.statusCode = status;
+  this.end('HEAD' == this.req.method ? undefined : body);
+};
+
+/**
+ * Transfer the file at the given `path`. Automatically sets 
+ * the _Content-Type_ response header field. `next()` is called
+ * when `path` is a directory, or when an error occurs.
+ *
+ * Options:
+ *
+ *   - `maxAge` defaulting to 0
+ *   - `root`   root directory for relative filenames
+ *
+ * @param {String} path
+ * @param {Object|Function} options or fn
+ * @param {Function} fn
+ * @api public
+ */
+
+res.sendfile = function(path, options, fn){
+  var next = this.req.next;
+  options = options || {};
+
+  // support function as second arg
+  if ('function' == typeof options) {
+    fn = options;
+    options = {};
+  }
+
+  options.path = path;
+  options.callback = fn;
+  send(this.req, this, next, options);
+};
+
+/**
+ * Set _Content-Type_ response header passed through `mime.lookup()`.
+ *
+ * Examples:
+ *
+ *     var filename = 'path/to/image.png';
+ *     res.contentType(filename);
+ *     // res.headers['Content-Type'] is now "image/png"
+ *
+ *     res.contentType('.html');
+ *     res.contentType('html');
+ *     res.contentType('json');
+ *     res.contentType('png');
+ *
+ * @param {String} type
+ * @return {String} the resolved mime type
+ * @api public
+ */
+
+res.contentType = function(type){
+  return this.header('Content-Type', mime.lookup(type));
+};
+
+/**
+ * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
+ *
+ * @param {String} filename
+ * @return {ServerResponse}
+ * @api public
+ */
+
+res.attachment = function(filename){
+  if (filename) this.contentType(filename);
+  this.header('Content-Disposition', filename
+    ? 'attachment; filename="' + path.basename(filename) + '"'
+    : 'attachment');
+  return this;
+};
+
+/**
+ * Transfer the file at the given `path`, with optional 
+ * `filename` as an attachment and optional callback `fn(err)`,
+ * and optional `fn2(err)` which is invoked when an error has
+ * occurred after headers have been sent.
+ *
+ * @param {String} path
+ * @param {String|Function} filename or fn
+ * @param {Function} fn
+ * @param {Function} fn2
+ * @api public
+ */
+
+res.download = function(path, filename, fn, fn2){
+  var self = this;
+
+  // support callback as second arg
+  if ('function' == typeof filename) {
+    fn2 = fn;
+    fn = filename;
+    filename = null;
+  }
+
+  // transfer the file
+  this.attachment(filename || path).sendfile(path, function(err){
+    var sentHeader = self._header;
+    if (err) {
+      if (!sentHeader) self.removeHeader('Content-Disposition');
+      if (sentHeader) {
+        fn2 && fn2(err);
+      } else if (fn) {
+        fn(err);
+      } else {
+        self.req.next(err);
+      }
+    } else if (fn) {
+      fn();
+    }
+  });
+};
+
+/**
+ * Set or get response header `name` with optional `val`.
+ *
+ * @param {String} name
+ * @param {String} val
+ * @return {String}
+ * @api public
+ */
+
+res.header = function(name, val){
+  if (val === undefined) {
+    return this.getHeader(name);
+  } else {
+    this.setHeader(name, val);
+    return val;
+  }
+};
+
+/**
+ * Clear cookie `name`.
+ *
+ * @param {String} name
+ * @param {Object} options
+ * @api public
+ */
+
+res.clearCookie = function(name, options){
+  var opts = { expires: new Date(1) };
+  this.cookie(name, '', options
+    ? utils.merge(options, opts)
+    : opts);
+};
+
+/**
+ * Set cookie `name` to `val`, with the given `options`.
+ *
+ * Options:
+ *
+ *    - `maxAge`   max-age in milliseconds, converted to `expires`
+ *
+ * Examples:
+ *
+ *    // "Remember Me" for 15 minutes
+ *    res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
+ *
+ *    // save as above
+ *    res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
+ *
+ * @param {String} name
+ * @param {String} val
+ * @param {Options} options
+ * @api public
+ */
+
+res.cookie = function(name, val, options){
+  options = options || {};
+  if ('maxAge' in options) options.expires = new Date(Date.now() + options.maxAge);
+  var cookie = utils.serializeCookie(name, val, options);
+  this.header('Set-Cookie', cookie);
+};
+
+/**
+ * Redirect to the given `url` with optional response `status`
+ * defauling to 302.
+ *
+ * The given `url` can also be the name of a mapped url, for
+ * example by default express supports "back" which redirects
+ * to the _Referrer_ or _Referer_ headers or the application's
+ * "home" setting. Express also supports "home" out of the box,
+ * which can be set via `app.set('home', '/blog');`, and defaults
+ * to '/'.
+ *
+ * Redirect Mapping:
+ * 
+ *  To extend the redirect mapping capabilities that Express provides,
+ *  we may use the `app.redirect()` method:
+ * 
+ *     app.redirect('google', 'http://google.com');
+ * 
+ *  Now in a route we may call:
+ *
+ *     res.redirect('google');
+ *
+ *  We may also map dynamic redirects:
+ *
+ *      app.redirect('comments', function(req, res){
+ *          return '/post/' + req.params.id + '/comments';
+ *      });
+ *
+ *  So now we may do the following, and the redirect will dynamically adjust to
+ *  the context of the request. If we called this route with _GET /post/12_ our
+ *  redirect _Location_ would be _/post/12/comments_.
+ *
+ *      app.get('/post/:id', function(req, res){
+ *        res.redirect('comments');
+ *      });
+ *
+ *  Unless an absolute `url` is given, the app's mount-point
+ *  will be respected. For example if we redirect to `/posts`,
+ *  and our app is mounted at `/blog` we will redirect to `/blog/posts`.
+ *
+ * @param {String} url
+ * @param {Number} code
+ * @api public
+ */
+
+res.redirect = function(url, status){
+  var app = this.app
+    , req = this.req
+    , base = app.set('home') || '/'
+    , status = status || 302
+    , body;
+
+  // Setup redirect map
+  var map = {
+      back: req.header('Referrer', base)
+    , home: base
+  };
+
+  // Support custom redirect map
+  map.__proto__ = app.redirects;
+
+  // Attempt mapped redirect
+  var mapped = 'function' == typeof map[url]
+    ? map[url](req, this)
+    : map[url];
+
+  // Perform redirect
+  url = mapped || url;
+
+  // Relative
+  if (!~url.indexOf('://')) {
+    // Respect mount-point
+    if (app.route) {
+      url = join(app.route, url);
+    }
+
+    // Absolute
+    var host = req.headers.host
+      , tls = req.connection.encrypted;
+    url = 'http' + (tls ? 's' : '') + '://' + host + url;
+  }
+  
+
+  // Support text/{plain,html} by default
+  if (req.accepts('html')) {
+    body = '<p>' + http.STATUS_CODES[status] + '. Redirecting to <a href="' + url + '">' + url + '</a></p>';
+    this.header('Content-Type', 'text/html');
+  } else {
+    body = http.STATUS_CODES[status] + '. Redirecting to ' + url;
+    this.header('Content-Type', 'text/plain');
+  }
+
+  // Respond
+  this.statusCode = status;
+  this.header('Location', url);
+  this.end(body);
+};
+
+/**
+ * Assign the view local variable `name` to `val` or return the
+ * local previously assigned to `name`.
+ *
+ * @param {String} name
+ * @param {Mixed} val
+ * @return {Mixed} val
+ * @api public
+ */
+
+res.local = function(name, val){
+  this._locals = this._locals || {};
+  return undefined === val
+    ? this._locals[name]
+    : this._locals[name] = val;
+};
+
+/**
+ * Assign several locals with the given `obj`,
+ * or return the locals.
+ *
+ * @param {Object} obj
+ * @return {Object|Undefined}
+ * @api public
+ */
+
+res.locals =
+res.helpers = function(obj){
+  if (obj) {
+    for (var key in obj) {
+      this.local(key, obj[key]);
+    }
+  } else {
+    return this._locals;
+  }
+};