Projekt

Obecné

Profil

Stáhnout (26.5 KB) Statistiky
| Větev: | Revize:
1
/*!
2
 * express
3
 * Copyright(c) 2009-2013 TJ Holowaychuk
4
 * Copyright(c) 2014-2015 Douglas Christopher Wilson
5
 * MIT Licensed
6
 */
7

    
8
'use strict';
9

    
10
/**
11
 * Module dependencies.
12
 * @private
13
 */
14

    
15
var Buffer = require('safe-buffer').Buffer
16
var contentDisposition = require('content-disposition');
17
var deprecate = require('depd')('express');
18
var encodeUrl = require('encodeurl');
19
var escapeHtml = require('escape-html');
20
var http = require('http');
21
var isAbsolute = require('./utils').isAbsolute;
22
var onFinished = require('on-finished');
23
var path = require('path');
24
var statuses = require('statuses')
25
var merge = require('utils-merge');
26
var sign = require('cookie-signature').sign;
27
var normalizeType = require('./utils').normalizeType;
28
var normalizeTypes = require('./utils').normalizeTypes;
29
var setCharset = require('./utils').setCharset;
30
var cookie = require('cookie');
31
var send = require('send');
32
var extname = path.extname;
33
var mime = send.mime;
34
var resolve = path.resolve;
35
var vary = require('vary');
36

    
37
/**
38
 * Response prototype.
39
 * @public
40
 */
41

    
42
var res = Object.create(http.ServerResponse.prototype)
43

    
44
/**
45
 * Module exports.
46
 * @public
47
 */
48

    
49
module.exports = res
50

    
51
/**
52
 * Module variables.
53
 * @private
54
 */
55

    
56
var charsetRegExp = /;\s*charset\s*=/;
57

    
58
/**
59
 * Set status `code`.
60
 *
61
 * @param {Number} code
62
 * @return {ServerResponse}
63
 * @public
64
 */
65

    
66
res.status = function status(code) {
67
  this.statusCode = code;
68
  return this;
69
};
70

    
71
/**
72
 * Set Link header field with the given `links`.
73
 *
74
 * Examples:
75
 *
76
 *    res.links({
77
 *      next: 'http://api.example.com/users?page=2',
78
 *      last: 'http://api.example.com/users?page=5'
79
 *    });
80
 *
81
 * @param {Object} links
82
 * @return {ServerResponse}
83
 * @public
84
 */
85

    
86
res.links = function(links){
87
  var link = this.get('Link') || '';
88
  if (link) link += ', ';
89
  return this.set('Link', link + Object.keys(links).map(function(rel){
90
    return '<' + links[rel] + '>; rel="' + rel + '"';
91
  }).join(', '));
92
};
93

    
94
/**
95
 * Send a response.
96
 *
97
 * Examples:
98
 *
99
 *     res.send(Buffer.from('wahoo'));
100
 *     res.send({ some: 'json' });
101
 *     res.send('<p>some html</p>');
102
 *
103
 * @param {string|number|boolean|object|Buffer} body
104
 * @public
105
 */
106

    
107
res.send = function send(body) {
108
  var chunk = body;
109
  var encoding;
110
  var req = this.req;
111
  var type;
112

    
113
  // settings
114
  var app = this.app;
115

    
116
  // allow status / body
117
  if (arguments.length === 2) {
118
    // res.send(body, status) backwards compat
119
    if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
120
      deprecate('res.send(body, status): Use res.status(status).send(body) instead');
121
      this.statusCode = arguments[1];
122
    } else {
123
      deprecate('res.send(status, body): Use res.status(status).send(body) instead');
124
      this.statusCode = arguments[0];
125
      chunk = arguments[1];
126
    }
127
  }
128

    
129
  // disambiguate res.send(status) and res.send(status, num)
130
  if (typeof chunk === 'number' && arguments.length === 1) {
131
    // res.send(status) will set status message as text string
132
    if (!this.get('Content-Type')) {
133
      this.type('txt');
134
    }
135

    
136
    deprecate('res.send(status): Use res.sendStatus(status) instead');
137
    this.statusCode = chunk;
138
    chunk = statuses[chunk]
139
  }
140

    
141
  switch (typeof chunk) {
142
    // string defaulting to html
143
    case 'string':
144
      if (!this.get('Content-Type')) {
145
        this.type('html');
146
      }
147
      break;
148
    case 'boolean':
149
    case 'number':
150
    case 'object':
151
      if (chunk === null) {
152
        chunk = '';
153
      } else if (Buffer.isBuffer(chunk)) {
154
        if (!this.get('Content-Type')) {
155
          this.type('bin');
156
        }
157
      } else {
158
        return this.json(chunk);
159
      }
160
      break;
161
  }
162

    
163
  // write strings in utf-8
164
  if (typeof chunk === 'string') {
165
    encoding = 'utf8';
166
    type = this.get('Content-Type');
167

    
168
    // reflect this in content-type
169
    if (typeof type === 'string') {
170
      this.set('Content-Type', setCharset(type, 'utf-8'));
171
    }
172
  }
173

    
174
  // determine if ETag should be generated
175
  var etagFn = app.get('etag fn')
176
  var generateETag = !this.get('ETag') && typeof etagFn === 'function'
177

    
178
  // populate Content-Length
179
  var len
180
  if (chunk !== undefined) {
181
    if (Buffer.isBuffer(chunk)) {
182
      // get length of Buffer
183
      len = chunk.length
184
    } else if (!generateETag && chunk.length < 1000) {
185
      // just calculate length when no ETag + small chunk
186
      len = Buffer.byteLength(chunk, encoding)
187
    } else {
188
      // convert chunk to Buffer and calculate
189
      chunk = Buffer.from(chunk, encoding)
190
      encoding = undefined;
191
      len = chunk.length
192
    }
193

    
194
    this.set('Content-Length', len);
195
  }
196

    
197
  // populate ETag
198
  var etag;
199
  if (generateETag && len !== undefined) {
200
    if ((etag = etagFn(chunk, encoding))) {
201
      this.set('ETag', etag);
202
    }
203
  }
204

    
205
  // freshness
206
  if (req.fresh) this.statusCode = 304;
207

    
208
  // strip irrelevant headers
209
  if (204 === this.statusCode || 304 === this.statusCode) {
210
    this.removeHeader('Content-Type');
211
    this.removeHeader('Content-Length');
212
    this.removeHeader('Transfer-Encoding');
213
    chunk = '';
214
  }
215

    
216
  if (req.method === 'HEAD') {
217
    // skip body for HEAD
218
    this.end();
219
  } else {
220
    // respond
221
    this.end(chunk, encoding);
222
  }
223

    
224
  return this;
225
};
226

    
227
/**
228
 * Send JSON response.
229
 *
230
 * Examples:
231
 *
232
 *     res.json(null);
233
 *     res.json({ user: 'tj' });
234
 *
235
 * @param {string|number|boolean|object} obj
236
 * @public
237
 */
238

    
239
res.json = function json(obj) {
240
  var val = obj;
241

    
242
  // allow status / body
243
  if (arguments.length === 2) {
244
    // res.json(body, status) backwards compat
245
    if (typeof arguments[1] === 'number') {
246
      deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
247
      this.statusCode = arguments[1];
248
    } else {
249
      deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
250
      this.statusCode = arguments[0];
251
      val = arguments[1];
252
    }
253
  }
254

    
255
  // settings
256
  var app = this.app;
257
  var escape = app.get('json escape')
258
  var replacer = app.get('json replacer');
259
  var spaces = app.get('json spaces');
260
  var body = stringify(val, replacer, spaces, escape)
261

    
262
  // content-type
263
  if (!this.get('Content-Type')) {
264
    this.set('Content-Type', 'application/json');
265
  }
266

    
267
  return this.send(body);
268
};
269

    
270
/**
271
 * Send JSON response with JSONP callback support.
272
 *
273
 * Examples:
274
 *
275
 *     res.jsonp(null);
276
 *     res.jsonp({ user: 'tj' });
277
 *
278
 * @param {string|number|boolean|object} obj
279
 * @public
280
 */
281

    
282
res.jsonp = function jsonp(obj) {
283
  var val = obj;
284

    
285
  // allow status / body
286
  if (arguments.length === 2) {
287
    // res.json(body, status) backwards compat
288
    if (typeof arguments[1] === 'number') {
289
      deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead');
290
      this.statusCode = arguments[1];
291
    } else {
292
      deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');
293
      this.statusCode = arguments[0];
294
      val = arguments[1];
295
    }
296
  }
297

    
298
  // settings
299
  var app = this.app;
300
  var escape = app.get('json escape')
301
  var replacer = app.get('json replacer');
302
  var spaces = app.get('json spaces');
303
  var body = stringify(val, replacer, spaces, escape)
304
  var callback = this.req.query[app.get('jsonp callback name')];
305

    
306
  // content-type
307
  if (!this.get('Content-Type')) {
308
    this.set('X-Content-Type-Options', 'nosniff');
309
    this.set('Content-Type', 'application/json');
310
  }
311

    
312
  // fixup callback
313
  if (Array.isArray(callback)) {
314
    callback = callback[0];
315
  }
316

    
317
  // jsonp
318
  if (typeof callback === 'string' && callback.length !== 0) {
319
    this.set('X-Content-Type-Options', 'nosniff');
320
    this.set('Content-Type', 'text/javascript');
321

    
322
    // restrict callback charset
323
    callback = callback.replace(/[^\[\]\w$.]/g, '');
324

    
325
    // replace chars not allowed in JavaScript that are in JSON
326
    body = body
327
      .replace(/\u2028/g, '\\u2028')
328
      .replace(/\u2029/g, '\\u2029');
329

    
330
    // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
331
    // the typeof check is just to reduce client error noise
332
    body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
333
  }
334

    
335
  return this.send(body);
336
};
337

    
338
/**
339
 * Send given HTTP status code.
340
 *
341
 * Sets the response status to `statusCode` and the body of the
342
 * response to the standard description from node's http.STATUS_CODES
343
 * or the statusCode number if no description.
344
 *
345
 * Examples:
346
 *
347
 *     res.sendStatus(200);
348
 *
349
 * @param {number} statusCode
350
 * @public
351
 */
352

    
353
res.sendStatus = function sendStatus(statusCode) {
354
  var body = statuses[statusCode] || String(statusCode)
355

    
356
  this.statusCode = statusCode;
357
  this.type('txt');
358

    
359
  return this.send(body);
360
};
361

    
362
/**
363
 * Transfer the file at the given `path`.
364
 *
365
 * Automatically sets the _Content-Type_ response header field.
366
 * The callback `callback(err)` is invoked when the transfer is complete
367
 * or when an error occurs. Be sure to check `res.sentHeader`
368
 * if you wish to attempt responding, as the header and some data
369
 * may have already been transferred.
370
 *
371
 * Options:
372
 *
373
 *   - `maxAge`   defaulting to 0 (can be string converted by `ms`)
374
 *   - `root`     root directory for relative filenames
375
 *   - `headers`  object of headers to serve with file
376
 *   - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
377
 *
378
 * Other options are passed along to `send`.
379
 *
380
 * Examples:
381
 *
382
 *  The following example illustrates how `res.sendFile()` may
383
 *  be used as an alternative for the `static()` middleware for
384
 *  dynamic situations. The code backing `res.sendFile()` is actually
385
 *  the same code, so HTTP cache support etc is identical.
386
 *
387
 *     app.get('/user/:uid/photos/:file', function(req, res){
388
 *       var uid = req.params.uid
389
 *         , file = req.params.file;
390
 *
391
 *       req.user.mayViewFilesFrom(uid, function(yes){
392
 *         if (yes) {
393
 *           res.sendFile('/uploads/' + uid + '/' + file);
394
 *         } else {
395
 *           res.send(403, 'Sorry! you cant see that.');
396
 *         }
397
 *       });
398
 *     });
399
 *
400
 * @public
401
 */
402

    
403
res.sendFile = function sendFile(path, options, callback) {
404
  var done = callback;
405
  var req = this.req;
406
  var res = this;
407
  var next = req.next;
408
  var opts = options || {};
409

    
410
  if (!path) {
411
    throw new TypeError('path argument is required to res.sendFile');
412
  }
413

    
414
  if (typeof path !== 'string') {
415
    throw new TypeError('path must be a string to res.sendFile')
416
  }
417

    
418
  // support function as second arg
419
  if (typeof options === 'function') {
420
    done = options;
421
    opts = {};
422
  }
423

    
424
  if (!opts.root && !isAbsolute(path)) {
425
    throw new TypeError('path must be absolute or specify root to res.sendFile');
426
  }
427

    
428
  // create file stream
429
  var pathname = encodeURI(path);
430
  var file = send(req, pathname, opts);
431

    
432
  // transfer
433
  sendfile(res, file, opts, function (err) {
434
    if (done) return done(err);
435
    if (err && err.code === 'EISDIR') return next();
436

    
437
    // next() all but write errors
438
    if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
439
      next(err);
440
    }
441
  });
442
};
443

    
444
/**
445
 * Transfer the file at the given `path`.
446
 *
447
 * Automatically sets the _Content-Type_ response header field.
448
 * The callback `callback(err)` is invoked when the transfer is complete
449
 * or when an error occurs. Be sure to check `res.sentHeader`
450
 * if you wish to attempt responding, as the header and some data
451
 * may have already been transferred.
452
 *
453
 * Options:
454
 *
455
 *   - `maxAge`   defaulting to 0 (can be string converted by `ms`)
456
 *   - `root`     root directory for relative filenames
457
 *   - `headers`  object of headers to serve with file
458
 *   - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
459
 *
460
 * Other options are passed along to `send`.
461
 *
462
 * Examples:
463
 *
464
 *  The following example illustrates how `res.sendfile()` may
465
 *  be used as an alternative for the `static()` middleware for
466
 *  dynamic situations. The code backing `res.sendfile()` is actually
467
 *  the same code, so HTTP cache support etc is identical.
468
 *
469
 *     app.get('/user/:uid/photos/:file', function(req, res){
470
 *       var uid = req.params.uid
471
 *         , file = req.params.file;
472
 *
473
 *       req.user.mayViewFilesFrom(uid, function(yes){
474
 *         if (yes) {
475
 *           res.sendfile('/uploads/' + uid + '/' + file);
476
 *         } else {
477
 *           res.send(403, 'Sorry! you cant see that.');
478
 *         }
479
 *       });
480
 *     });
481
 *
482
 * @public
483
 */
484

    
485
res.sendfile = function (path, options, callback) {
486
  var done = callback;
487
  var req = this.req;
488
  var res = this;
489
  var next = req.next;
490
  var opts = options || {};
491

    
492
  // support function as second arg
493
  if (typeof options === 'function') {
494
    done = options;
495
    opts = {};
496
  }
497

    
498
  // create file stream
499
  var file = send(req, path, opts);
500

    
501
  // transfer
502
  sendfile(res, file, opts, function (err) {
503
    if (done) return done(err);
504
    if (err && err.code === 'EISDIR') return next();
505

    
506
    // next() all but write errors
507
    if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
508
      next(err);
509
    }
510
  });
511
};
512

    
513
res.sendfile = deprecate.function(res.sendfile,
514
  'res.sendfile: Use res.sendFile instead');
515

    
516
/**
517
 * Transfer the file at the given `path` as an attachment.
518
 *
519
 * Optionally providing an alternate attachment `filename`,
520
 * and optional callback `callback(err)`. The callback is invoked
521
 * when the data transfer is complete, or when an error has
522
 * ocurred. Be sure to check `res.headersSent` if you plan to respond.
523
 *
524
 * Optionally providing an `options` object to use with `res.sendFile()`.
525
 * This function will set the `Content-Disposition` header, overriding
526
 * any `Content-Disposition` header passed as header options in order
527
 * to set the attachment and filename.
528
 *
529
 * This method uses `res.sendFile()`.
530
 *
531
 * @public
532
 */
533

    
534
res.download = function download (path, filename, options, callback) {
535
  var done = callback;
536
  var name = filename;
537
  var opts = options || null
538

    
539
  // support function as second or third arg
540
  if (typeof filename === 'function') {
541
    done = filename;
542
    name = null;
543
    opts = null
544
  } else if (typeof options === 'function') {
545
    done = options
546
    opts = null
547
  }
548

    
549
  // set Content-Disposition when file is sent
550
  var headers = {
551
    'Content-Disposition': contentDisposition(name || path)
552
  };
553

    
554
  // merge user-provided headers
555
  if (opts && opts.headers) {
556
    var keys = Object.keys(opts.headers)
557
    for (var i = 0; i < keys.length; i++) {
558
      var key = keys[i]
559
      if (key.toLowerCase() !== 'content-disposition') {
560
        headers[key] = opts.headers[key]
561
      }
562
    }
563
  }
564

    
565
  // merge user-provided options
566
  opts = Object.create(opts)
567
  opts.headers = headers
568

    
569
  // Resolve the full path for sendFile
570
  var fullPath = resolve(path);
571

    
572
  // send file
573
  return this.sendFile(fullPath, opts, done)
574
};
575

    
576
/**
577
 * Set _Content-Type_ response header with `type` through `mime.lookup()`
578
 * when it does not contain "/", or set the Content-Type to `type` otherwise.
579
 *
580
 * Examples:
581
 *
582
 *     res.type('.html');
583
 *     res.type('html');
584
 *     res.type('json');
585
 *     res.type('application/json');
586
 *     res.type('png');
587
 *
588
 * @param {String} type
589
 * @return {ServerResponse} for chaining
590
 * @public
591
 */
592

    
593
res.contentType =
594
res.type = function contentType(type) {
595
  var ct = type.indexOf('/') === -1
596
    ? mime.lookup(type)
597
    : type;
598

    
599
  return this.set('Content-Type', ct);
600
};
601

    
602
/**
603
 * Respond to the Acceptable formats using an `obj`
604
 * of mime-type callbacks.
605
 *
606
 * This method uses `req.accepted`, an array of
607
 * acceptable types ordered by their quality values.
608
 * When "Accept" is not present the _first_ callback
609
 * is invoked, otherwise the first match is used. When
610
 * no match is performed the server responds with
611
 * 406 "Not Acceptable".
612
 *
613
 * Content-Type is set for you, however if you choose
614
 * you may alter this within the callback using `res.type()`
615
 * or `res.set('Content-Type', ...)`.
616
 *
617
 *    res.format({
618
 *      'text/plain': function(){
619
 *        res.send('hey');
620
 *      },
621
 *
622
 *      'text/html': function(){
623
 *        res.send('<p>hey</p>');
624
 *      },
625
 *
626
 *      'appliation/json': function(){
627
 *        res.send({ message: 'hey' });
628
 *      }
629
 *    });
630
 *
631
 * In addition to canonicalized MIME types you may
632
 * also use extnames mapped to these types:
633
 *
634
 *    res.format({
635
 *      text: function(){
636
 *        res.send('hey');
637
 *      },
638
 *
639
 *      html: function(){
640
 *        res.send('<p>hey</p>');
641
 *      },
642
 *
643
 *      json: function(){
644
 *        res.send({ message: 'hey' });
645
 *      }
646
 *    });
647
 *
648
 * By default Express passes an `Error`
649
 * with a `.status` of 406 to `next(err)`
650
 * if a match is not made. If you provide
651
 * a `.default` callback it will be invoked
652
 * instead.
653
 *
654
 * @param {Object} obj
655
 * @return {ServerResponse} for chaining
656
 * @public
657
 */
658

    
659
res.format = function(obj){
660
  var req = this.req;
661
  var next = req.next;
662

    
663
  var fn = obj.default;
664
  if (fn) delete obj.default;
665
  var keys = Object.keys(obj);
666

    
667
  var key = keys.length > 0
668
    ? req.accepts(keys)
669
    : false;
670

    
671
  this.vary("Accept");
672

    
673
  if (key) {
674
    this.set('Content-Type', normalizeType(key).value);
675
    obj[key](req, this, next);
676
  } else if (fn) {
677
    fn();
678
  } else {
679
    var err = new Error('Not Acceptable');
680
    err.status = err.statusCode = 406;
681
    err.types = normalizeTypes(keys).map(function(o){ return o.value });
682
    next(err);
683
  }
684

    
685
  return this;
686
};
687

    
688
/**
689
 * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
690
 *
691
 * @param {String} filename
692
 * @return {ServerResponse}
693
 * @public
694
 */
695

    
696
res.attachment = function attachment(filename) {
697
  if (filename) {
698
    this.type(extname(filename));
699
  }
700

    
701
  this.set('Content-Disposition', contentDisposition(filename));
702

    
703
  return this;
704
};
705

    
706
/**
707
 * Append additional header `field` with value `val`.
708
 *
709
 * Example:
710
 *
711
 *    res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
712
 *    res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
713
 *    res.append('Warning', '199 Miscellaneous warning');
714
 *
715
 * @param {String} field
716
 * @param {String|Array} val
717
 * @return {ServerResponse} for chaining
718
 * @public
719
 */
720

    
721
res.append = function append(field, val) {
722
  var prev = this.get(field);
723
  var value = val;
724

    
725
  if (prev) {
726
    // concat the new and prev vals
727
    value = Array.isArray(prev) ? prev.concat(val)
728
      : Array.isArray(val) ? [prev].concat(val)
729
      : [prev, val];
730
  }
731

    
732
  return this.set(field, value);
733
};
734

    
735
/**
736
 * Set header `field` to `val`, or pass
737
 * an object of header fields.
738
 *
739
 * Examples:
740
 *
741
 *    res.set('Foo', ['bar', 'baz']);
742
 *    res.set('Accept', 'application/json');
743
 *    res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
744
 *
745
 * Aliased as `res.header()`.
746
 *
747
 * @param {String|Object} field
748
 * @param {String|Array} val
749
 * @return {ServerResponse} for chaining
750
 * @public
751
 */
752

    
753
res.set =
754
res.header = function header(field, val) {
755
  if (arguments.length === 2) {
756
    var value = Array.isArray(val)
757
      ? val.map(String)
758
      : String(val);
759

    
760
    // add charset to content-type
761
    if (field.toLowerCase() === 'content-type') {
762
      if (Array.isArray(value)) {
763
        throw new TypeError('Content-Type cannot be set to an Array');
764
      }
765
      if (!charsetRegExp.test(value)) {
766
        var charset = mime.charsets.lookup(value.split(';')[0]);
767
        if (charset) value += '; charset=' + charset.toLowerCase();
768
      }
769
    }
770

    
771
    this.setHeader(field, value);
772
  } else {
773
    for (var key in field) {
774
      this.set(key, field[key]);
775
    }
776
  }
777
  return this;
778
};
779

    
780
/**
781
 * Get value for header `field`.
782
 *
783
 * @param {String} field
784
 * @return {String}
785
 * @public
786
 */
787

    
788
res.get = function(field){
789
  return this.getHeader(field);
790
};
791

    
792
/**
793
 * Clear cookie `name`.
794
 *
795
 * @param {String} name
796
 * @param {Object} [options]
797
 * @return {ServerResponse} for chaining
798
 * @public
799
 */
800

    
801
res.clearCookie = function clearCookie(name, options) {
802
  var opts = merge({ expires: new Date(1), path: '/' }, options);
803

    
804
  return this.cookie(name, '', opts);
805
};
806

    
807
/**
808
 * Set cookie `name` to `value`, with the given `options`.
809
 *
810
 * Options:
811
 *
812
 *    - `maxAge`   max-age in milliseconds, converted to `expires`
813
 *    - `signed`   sign the cookie
814
 *    - `path`     defaults to "/"
815
 *
816
 * Examples:
817
 *
818
 *    // "Remember Me" for 15 minutes
819
 *    res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
820
 *
821
 *    // same as above
822
 *    res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
823
 *
824
 * @param {String} name
825
 * @param {String|Object} value
826
 * @param {Object} [options]
827
 * @return {ServerResponse} for chaining
828
 * @public
829
 */
830

    
831
res.cookie = function (name, value, options) {
832
  var opts = merge({}, options);
833
  var secret = this.req.secret;
834
  var signed = opts.signed;
835

    
836
  if (signed && !secret) {
837
    throw new Error('cookieParser("secret") required for signed cookies');
838
  }
839

    
840
  var val = typeof value === 'object'
841
    ? 'j:' + JSON.stringify(value)
842
    : String(value);
843

    
844
  if (signed) {
845
    val = 's:' + sign(val, secret);
846
  }
847

    
848
  if ('maxAge' in opts) {
849
    opts.expires = new Date(Date.now() + opts.maxAge);
850
    opts.maxAge /= 1000;
851
  }
852

    
853
  if (opts.path == null) {
854
    opts.path = '/';
855
  }
856

    
857
  this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
858

    
859
  return this;
860
};
861

    
862
/**
863
 * Set the location header to `url`.
864
 *
865
 * The given `url` can also be "back", which redirects
866
 * to the _Referrer_ or _Referer_ headers or "/".
867
 *
868
 * Examples:
869
 *
870
 *    res.location('/foo/bar').;
871
 *    res.location('http://example.com');
872
 *    res.location('../login');
873
 *
874
 * @param {String} url
875
 * @return {ServerResponse} for chaining
876
 * @public
877
 */
878

    
879
res.location = function location(url) {
880
  var loc = url;
881

    
882
  // "back" is an alias for the referrer
883
  if (url === 'back') {
884
    loc = this.req.get('Referrer') || '/';
885
  }
886

    
887
  // set location
888
  return this.set('Location', encodeUrl(loc));
889
};
890

    
891
/**
892
 * Redirect to the given `url` with optional response `status`
893
 * defaulting to 302.
894
 *
895
 * The resulting `url` is determined by `res.location()`, so
896
 * it will play nicely with mounted apps, relative paths,
897
 * `"back"` etc.
898
 *
899
 * Examples:
900
 *
901
 *    res.redirect('/foo/bar');
902
 *    res.redirect('http://example.com');
903
 *    res.redirect(301, 'http://example.com');
904
 *    res.redirect('../login'); // /blog/post/1 -> /blog/login
905
 *
906
 * @public
907
 */
908

    
909
res.redirect = function redirect(url) {
910
  var address = url;
911
  var body;
912
  var status = 302;
913

    
914
  // allow status / url
915
  if (arguments.length === 2) {
916
    if (typeof arguments[0] === 'number') {
917
      status = arguments[0];
918
      address = arguments[1];
919
    } else {
920
      deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');
921
      status = arguments[1];
922
    }
923
  }
924

    
925
  // Set location header
926
  address = this.location(address).get('Location');
927

    
928
  // Support text/{plain,html} by default
929
  this.format({
930
    text: function(){
931
      body = statuses[status] + '. Redirecting to ' + address
932
    },
933

    
934
    html: function(){
935
      var u = escapeHtml(address);
936
      body = '<p>' + statuses[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>'
937
    },
938

    
939
    default: function(){
940
      body = '';
941
    }
942
  });
943

    
944
  // Respond
945
  this.statusCode = status;
946
  this.set('Content-Length', Buffer.byteLength(body));
947

    
948
  if (this.req.method === 'HEAD') {
949
    this.end();
950
  } else {
951
    this.end(body);
952
  }
953
};
954

    
955
/**
956
 * Add `field` to Vary. If already present in the Vary set, then
957
 * this call is simply ignored.
958
 *
959
 * @param {Array|String} field
960
 * @return {ServerResponse} for chaining
961
 * @public
962
 */
963

    
964
res.vary = function(field){
965
  // checks for back-compat
966
  if (!field || (Array.isArray(field) && !field.length)) {
967
    deprecate('res.vary(): Provide a field name');
968
    return this;
969
  }
970

    
971
  vary(this, field);
972

    
973
  return this;
974
};
975

    
976
/**
977
 * Render `view` with the given `options` and optional callback `fn`.
978
 * When a callback function is given a response will _not_ be made
979
 * automatically, otherwise a response of _200_ and _text/html_ is given.
980
 *
981
 * Options:
982
 *
983
 *  - `cache`     boolean hinting to the engine it should cache
984
 *  - `filename`  filename of the view being rendered
985
 *
986
 * @public
987
 */
988

    
989
res.render = function render(view, options, callback) {
990
  var app = this.req.app;
991
  var done = callback;
992
  var opts = options || {};
993
  var req = this.req;
994
  var self = this;
995

    
996
  // support callback function as second arg
997
  if (typeof options === 'function') {
998
    done = options;
999
    opts = {};
1000
  }
1001

    
1002
  // merge res.locals
1003
  opts._locals = self.locals;
1004

    
1005
  // default callback to respond
1006
  done = done || function (err, str) {
1007
    if (err) return req.next(err);
1008
    self.send(str);
1009
  };
1010

    
1011
  // render
1012
  app.render(view, opts, done);
1013
};
1014

    
1015
// pipe the send file stream
1016
function sendfile(res, file, options, callback) {
1017
  var done = false;
1018
  var streaming;
1019

    
1020
  // request aborted
1021
  function onaborted() {
1022
    if (done) return;
1023
    done = true;
1024

    
1025
    var err = new Error('Request aborted');
1026
    err.code = 'ECONNABORTED';
1027
    callback(err);
1028
  }
1029

    
1030
  // directory
1031
  function ondirectory() {
1032
    if (done) return;
1033
    done = true;
1034

    
1035
    var err = new Error('EISDIR, read');
1036
    err.code = 'EISDIR';
1037
    callback(err);
1038
  }
1039

    
1040
  // errors
1041
  function onerror(err) {
1042
    if (done) return;
1043
    done = true;
1044
    callback(err);
1045
  }
1046

    
1047
  // ended
1048
  function onend() {
1049
    if (done) return;
1050
    done = true;
1051
    callback();
1052
  }
1053

    
1054
  // file
1055
  function onfile() {
1056
    streaming = false;
1057
  }
1058

    
1059
  // finished
1060
  function onfinish(err) {
1061
    if (err && err.code === 'ECONNRESET') return onaborted();
1062
    if (err) return onerror(err);
1063
    if (done) return;
1064

    
1065
    setImmediate(function () {
1066
      if (streaming !== false && !done) {
1067
        onaborted();
1068
        return;
1069
      }
1070

    
1071
      if (done) return;
1072
      done = true;
1073
      callback();
1074
    });
1075
  }
1076

    
1077
  // streaming
1078
  function onstream() {
1079
    streaming = true;
1080
  }
1081

    
1082
  file.on('directory', ondirectory);
1083
  file.on('end', onend);
1084
  file.on('error', onerror);
1085
  file.on('file', onfile);
1086
  file.on('stream', onstream);
1087
  onFinished(res, onfinish);
1088

    
1089
  if (options.headers) {
1090
    // set headers on successful transfer
1091
    file.on('headers', function headers(res) {
1092
      var obj = options.headers;
1093
      var keys = Object.keys(obj);
1094

    
1095
      for (var i = 0; i < keys.length; i++) {
1096
        var k = keys[i];
1097
        res.setHeader(k, obj[k]);
1098
      }
1099
    });
1100
  }
1101

    
1102
  // pipe
1103
  file.pipe(res);
1104
}
1105

    
1106
/**
1107
 * Stringify JSON, like JSON.stringify, but v8 optimized, with the
1108
 * ability to escape characters that can trigger HTML sniffing.
1109
 *
1110
 * @param {*} value
1111
 * @param {function} replaces
1112
 * @param {number} spaces
1113
 * @param {boolean} escape
1114
 * @returns {string}
1115
 * @private
1116
 */
1117

    
1118
function stringify (value, replacer, spaces, escape) {
1119
  // v8 checks arguments.length for optimizing simple call
1120
  // https://bugs.chromium.org/p/v8/issues/detail?id=4730
1121
  var json = replacer || spaces
1122
    ? JSON.stringify(value, replacer, spaces)
1123
    : JSON.stringify(value);
1124

    
1125
  if (escape) {
1126
    json = json.replace(/[<>&]/g, function (c) {
1127
      switch (c.charCodeAt(0)) {
1128
        case 0x3c:
1129
          return '\\u003c'
1130
        case 0x3e:
1131
          return '\\u003e'
1132
        case 0x26:
1133
          return '\\u0026'
1134
        /* istanbul ignore next: unreachable default */
1135
        default:
1136
          return c
1137
      }
1138
    })
1139
  }
1140

    
1141
  return json
1142
}
(4-4/6)