]> git.cworth.org Git - obsolete/notmuch-web/blob - node_modules/express/lib/response.js
Install the "express" node module via npm
[obsolete/notmuch-web] / node_modules / express / lib / response.js
1
2 /*!
3  * Express - response
4  * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
5  * MIT Licensed
6  */
7
8 /**
9  * Module dependencies.
10  */
11
12 var fs = require('fs')
13   , http = require('http')
14   , path = require('path')
15   , connect = require('connect')
16   , utils = connect.utils
17   , parseRange = require('./utils').parseRange
18   , res = http.ServerResponse.prototype
19   , send = connect.static.send
20   , join = require('path').join
21   , mime = require('mime');
22
23 /**
24  * Send a response with the given `body` and optional `headers` and `status` code.
25  *
26  * Examples:
27  *
28  *     res.send();
29  *     res.send(new Buffer('wahoo'));
30  *     res.send({ some: 'json' });
31  *     res.send('<p>some html</p>');
32  *     res.send('Sorry, cant find that', 404);
33  *     res.send('text', { 'Content-Type': 'text/plain' }, 201);
34  *     res.send(404);
35  *
36  * @param {String|Object|Number|Buffer} body or status
37  * @param {Object|Number} headers or status
38  * @param {Number} status
39  * @return {ServerResponse}
40  * @api public
41  */
42
43 res.send = function(body, headers, status){
44   // allow status as second arg
45   if ('number' == typeof headers) {
46     status = headers,
47     headers = null;
48   }
49
50   // default status
51   status = status || this.statusCode;
52
53   // allow 0 args as 204
54   if (!arguments.length || undefined === body) body = status = 204;
55
56   // determine content type
57   switch (typeof body) {
58     case 'number':
59       if (!this.header('Content-Type')) {
60         this.contentType('.txt');
61       }
62       body = http.STATUS_CODES[status = body];
63       break;
64     case 'string':
65       if (!this.header('Content-Type')) {
66         this.charset = this.charset || 'utf-8';
67         this.contentType('.html');
68       }
69       break;
70     case 'boolean':
71     case 'object':
72       if (Buffer.isBuffer(body)) {
73         if (!this.header('Content-Type')) {
74           this.contentType('.bin');
75         }
76       } else {
77         if (!this.header('Content-Type')) {
78           this.charset = this.charset || 'utf-8';
79           this.contentType('.json');
80         }
81         body = JSON.stringify(body);
82         if (this.req.query.callback && this.app.set('jsonp callback')) {
83           this.charset = this.charset || 'utf-8';
84           this.header('Content-Type', 'text/javascript');
85           body = this.req.query.callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
86         }
87       }
88       break;
89   }
90
91   // populate Content-Length
92   if (!this.header('Content-Length')) {
93     this.header('Content-Length', Buffer.isBuffer(body)
94       ? body.length
95       : Buffer.byteLength(body));
96   }
97
98   // merge headers passed
99   if (headers) {
100     var fields = Object.keys(headers);
101     for (var i = 0, len = fields.length; i < len; ++i) {
102       var field = fields[i];
103       this.header(field, headers[field]);
104     }
105   }
106
107   // strip irrelevant headers
108   if (204 === status) {
109     this.removeHeader('Content-Type');
110     this.removeHeader('Content-Length');
111   }
112
113   // respond
114   this.statusCode = status;
115   this.end('HEAD' == this.req.method ? undefined : body);
116 };
117
118 /**
119  * Transfer the file at the given `path`. Automatically sets 
120  * the _Content-Type_ response header field. `next()` is called
121  * when `path` is a directory, or when an error occurs.
122  *
123  * Options:
124  *
125  *   - `maxAge` defaulting to 0
126  *   - `root`   root directory for relative filenames
127  *
128  * @param {String} path
129  * @param {Object|Function} options or fn
130  * @param {Function} fn
131  * @api public
132  */
133
134 res.sendfile = function(path, options, fn){
135   var next = this.req.next;
136   options = options || {};
137
138   // support function as second arg
139   if ('function' == typeof options) {
140     fn = options;
141     options = {};
142   }
143
144   options.path = path;
145   options.callback = fn;
146   send(this.req, this, next, options);
147 };
148
149 /**
150  * Set _Content-Type_ response header passed through `mime.lookup()`.
151  *
152  * Examples:
153  *
154  *     var filename = 'path/to/image.png';
155  *     res.contentType(filename);
156  *     // res.headers['Content-Type'] is now "image/png"
157  *
158  *     res.contentType('.html');
159  *     res.contentType('html');
160  *     res.contentType('json');
161  *     res.contentType('png');
162  *
163  * @param {String} type
164  * @return {String} the resolved mime type
165  * @api public
166  */
167
168 res.contentType = function(type){
169   return this.header('Content-Type', mime.lookup(type));
170 };
171
172 /**
173  * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
174  *
175  * @param {String} filename
176  * @return {ServerResponse}
177  * @api public
178  */
179
180 res.attachment = function(filename){
181   if (filename) this.contentType(filename);
182   this.header('Content-Disposition', filename
183     ? 'attachment; filename="' + path.basename(filename) + '"'
184     : 'attachment');
185   return this;
186 };
187
188 /**
189  * Transfer the file at the given `path`, with optional 
190  * `filename` as an attachment and optional callback `fn(err)`,
191  * and optional `fn2(err)` which is invoked when an error has
192  * occurred after headers have been sent.
193  *
194  * @param {String} path
195  * @param {String|Function} filename or fn
196  * @param {Function} fn
197  * @param {Function} fn2
198  * @api public
199  */
200
201 res.download = function(path, filename, fn, fn2){
202   var self = this;
203
204   // support callback as second arg
205   if ('function' == typeof filename) {
206     fn2 = fn;
207     fn = filename;
208     filename = null;
209   }
210
211   // transfer the file
212   this.attachment(filename || path).sendfile(path, function(err){
213     var sentHeader = self._header;
214     if (err) {
215       if (!sentHeader) self.removeHeader('Content-Disposition');
216       if (sentHeader) {
217         fn2 && fn2(err);
218       } else if (fn) {
219         fn(err);
220       } else {
221         self.req.next(err);
222       }
223     } else if (fn) {
224       fn();
225     }
226   });
227 };
228
229 /**
230  * Set or get response header `name` with optional `val`.
231  *
232  * @param {String} name
233  * @param {String} val
234  * @return {String}
235  * @api public
236  */
237
238 res.header = function(name, val){
239   if (val === undefined) {
240     return this.getHeader(name);
241   } else {
242     this.setHeader(name, val);
243     return val;
244   }
245 };
246
247 /**
248  * Clear cookie `name`.
249  *
250  * @param {String} name
251  * @param {Object} options
252  * @api public
253  */
254
255 res.clearCookie = function(name, options){
256   var opts = { expires: new Date(1) };
257   this.cookie(name, '', options
258     ? utils.merge(options, opts)
259     : opts);
260 };
261
262 /**
263  * Set cookie `name` to `val`, with the given `options`.
264  *
265  * Options:
266  *
267  *    - `maxAge`   max-age in milliseconds, converted to `expires`
268  *
269  * Examples:
270  *
271  *    // "Remember Me" for 15 minutes
272  *    res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
273  *
274  *    // save as above
275  *    res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
276  *
277  * @param {String} name
278  * @param {String} val
279  * @param {Options} options
280  * @api public
281  */
282
283 res.cookie = function(name, val, options){
284   options = options || {};
285   if ('maxAge' in options) options.expires = new Date(Date.now() + options.maxAge);
286   var cookie = utils.serializeCookie(name, val, options);
287   this.header('Set-Cookie', cookie);
288 };
289
290 /**
291  * Redirect to the given `url` with optional response `status`
292  * defauling to 302.
293  *
294  * The given `url` can also be the name of a mapped url, for
295  * example by default express supports "back" which redirects
296  * to the _Referrer_ or _Referer_ headers or the application's
297  * "home" setting. Express also supports "home" out of the box,
298  * which can be set via `app.set('home', '/blog');`, and defaults
299  * to '/'.
300  *
301  * Redirect Mapping:
302  * 
303  *  To extend the redirect mapping capabilities that Express provides,
304  *  we may use the `app.redirect()` method:
305  * 
306  *     app.redirect('google', 'http://google.com');
307  * 
308  *  Now in a route we may call:
309  *
310  *     res.redirect('google');
311  *
312  *  We may also map dynamic redirects:
313  *
314  *      app.redirect('comments', function(req, res){
315  *          return '/post/' + req.params.id + '/comments';
316  *      });
317  *
318  *  So now we may do the following, and the redirect will dynamically adjust to
319  *  the context of the request. If we called this route with _GET /post/12_ our
320  *  redirect _Location_ would be _/post/12/comments_.
321  *
322  *      app.get('/post/:id', function(req, res){
323  *        res.redirect('comments');
324  *      });
325  *
326  *  Unless an absolute `url` is given, the app's mount-point
327  *  will be respected. For example if we redirect to `/posts`,
328  *  and our app is mounted at `/blog` we will redirect to `/blog/posts`.
329  *
330  * @param {String} url
331  * @param {Number} code
332  * @api public
333  */
334
335 res.redirect = function(url, status){
336   var app = this.app
337     , req = this.req
338     , base = app.set('home') || '/'
339     , status = status || 302
340     , body;
341
342   // Setup redirect map
343   var map = {
344       back: req.header('Referrer', base)
345     , home: base
346   };
347
348   // Support custom redirect map
349   map.__proto__ = app.redirects;
350
351   // Attempt mapped redirect
352   var mapped = 'function' == typeof map[url]
353     ? map[url](req, this)
354     : map[url];
355
356   // Perform redirect
357   url = mapped || url;
358
359   // Relative
360   if (!~url.indexOf('://')) {
361     // Respect mount-point
362     if (app.route) {
363       url = join(app.route, url);
364     }
365
366     // Absolute
367     var host = req.headers.host
368       , tls = req.connection.encrypted;
369     url = 'http' + (tls ? 's' : '') + '://' + host + url;
370   }
371   
372
373   // Support text/{plain,html} by default
374   if (req.accepts('html')) {
375     body = '<p>' + http.STATUS_CODES[status] + '. Redirecting to <a href="' + url + '">' + url + '</a></p>';
376     this.header('Content-Type', 'text/html');
377   } else {
378     body = http.STATUS_CODES[status] + '. Redirecting to ' + url;
379     this.header('Content-Type', 'text/plain');
380   }
381
382   // Respond
383   this.statusCode = status;
384   this.header('Location', url);
385   this.end(body);
386 };
387
388 /**
389  * Assign the view local variable `name` to `val` or return the
390  * local previously assigned to `name`.
391  *
392  * @param {String} name
393  * @param {Mixed} val
394  * @return {Mixed} val
395  * @api public
396  */
397
398 res.local = function(name, val){
399   this._locals = this._locals || {};
400   return undefined === val
401     ? this._locals[name]
402     : this._locals[name] = val;
403 };
404
405 /**
406  * Assign several locals with the given `obj`,
407  * or return the locals.
408  *
409  * @param {Object} obj
410  * @return {Object|Undefined}
411  * @api public
412  */
413
414 res.locals =
415 res.helpers = function(obj){
416   if (obj) {
417     for (var key in obj) {
418       this.local(key, obj[key]);
419     }
420   } else {
421     return this._locals;
422   }
423 };