• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

expressjs / express / 21073547779

16 Jan 2026 03:29PM UTC coverage: 99.612% (-0.4%) from 100.0%
21073547779

push

github

web-flow
Polish HTML structure of the response in the res.redirect() function (#5167)

* structure the DOM body

* structure the DOM body

* test: add html title to redirect test

* fix: update HTML structure for include body and head tags

* docs: improve HTML structure in res.redirect() responses for better browser compatibility

---------

Co-authored-by: Sebastian Beltran <bjohansebas@gmail.com>

1 of 1 new or added line in 1 file covered. (100.0%)

3 existing lines in 1 file now uncovered.

770 of 773 relevant lines covered (99.61%)

581.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.08
/lib/response.js
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 contentDisposition = require('content-disposition');
2✔
16
var createError = require('http-errors')
2✔
17
var deprecate = require('depd')('express');
2✔
18
var encodeUrl = require('encodeurl');
2✔
19
var escapeHtml = require('escape-html');
2✔
20
var http = require('node:http');
2✔
21
var onFinished = require('on-finished');
2✔
22
var mime = require('mime-types')
2✔
23
var path = require('node:path');
2✔
24
var pathIsAbsolute = require('node:path').isAbsolute;
2✔
25
var statuses = require('statuses')
2✔
26
var sign = require('cookie-signature').sign;
2✔
27
var normalizeType = require('./utils').normalizeType;
2✔
28
var normalizeTypes = require('./utils').normalizeTypes;
2✔
29
var setCharset = require('./utils').setCharset;
2✔
30
var cookie = require('cookie');
2✔
31
var send = require('send');
2✔
32
var extname = path.extname;
2✔
33
var resolve = path.resolve;
2✔
34
var vary = require('vary');
2✔
35
const { Buffer } = require('node:buffer');
2✔
36

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

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

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

49
module.exports = res
2✔
50

51
/**
52
 * Set the HTTP status code for the response.
53
 *
54
 * Expects an integer value between 100 and 999 inclusive.
55
 * Throws an error if the provided status code is not an integer or if it's outside the allowable range.
56
 *
57
 * @param {number} code - The HTTP status code to set.
58
 * @return {ServerResponse} - Returns itself for chaining methods.
59
 * @throws {TypeError} If `code` is not an integer.
60
 * @throws {RangeError} If `code` is outside the range 100 to 999.
61
 * @public
62
 */
63

64
res.status = function status(code) {
2✔
65
  // Check if the status code is not an integer
66
  if (!Number.isInteger(code)) {
384✔
67
    throw new TypeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`);
12✔
68
  }
69
  // Check if the status code is outside of Node's valid range
70
  if (code < 100 || code > 999) {
372✔
71
    throw new RangeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`);
4✔
72
  }
73

74
  this.statusCode = code;
368✔
75
  return this;
368✔
76
};
77

78
/**
79
 * Set Link header field with the given `links`.
80
 *
81
 * Examples:
82
 *
83
 *    res.links({
84
 *      next: 'http://api.example.com/users?page=2',
85
 *      last: 'http://api.example.com/users?page=5',
86
 *      pages: [
87
 *        'http://api.example.com/users?page=1',
88
 *        'http://api.example.com/users?page=2'
89
 *      ]
90
 *    });
91
 *
92
 * @param {Object} links
93
 * @return {ServerResponse}
94
 * @public
95
 */
96

97
res.links = function(links) {
2✔
98
  var link = this.get('Link') || '';
8✔
99
  if (link) link += ', ';
8✔
100
  return this.set('Link', link + Object.keys(links).map(function(rel) {
8✔
101
    // Allow multiple links if links[rel] is an array
102
    if (Array.isArray(links[rel])) {
14✔
103
      return links[rel].map(function (singleLink) {
2✔
104
        return `<${singleLink}>; rel="${rel}"`;
4✔
105
      }).join(', ');
106
    } else {
107
      return `<${links[rel]}>; rel="${rel}"`;
12✔
108
    }
109
  }).join(', '));
110
};
111

112
/**
113
 * Send a response.
114
 *
115
 * Examples:
116
 *
117
 *     res.send(Buffer.from('wahoo'));
118
 *     res.send({ some: 'json' });
119
 *     res.send('<p>some html</p>');
120
 *
121
 * @param {string|number|boolean|object|Buffer} body
122
 * @public
123
 */
124

125
res.send = function send(body) {
2✔
126
  var chunk = body;
1,496✔
127
  var encoding;
128
  var req = this.req;
1,496✔
129
  var type;
130

131
  // settings
132
  var app = this.app;
1,496✔
133

134
  switch (typeof chunk) {
1,496✔
135
    // string defaulting to html
136
    case 'string':
137
      if (!this.get('Content-Type')) {
1,284✔
138
        this.type('html');
676✔
139
      }
140
      break;
1,284✔
141
    case 'boolean':
142
    case 'number':
143
    case 'object':
144
      if (chunk === null) {
180✔
145
        chunk = '';
2✔
146
      } else if (ArrayBuffer.isView(chunk)) {
178✔
147
        if (!this.get('Content-Type')) {
14✔
148
          this.type('bin');
4✔
149
        }
150
      } else {
151
        return this.json(chunk);
164✔
152
      }
153
      break;
16✔
154
  }
155

156
  // write strings in utf-8
157
  if (typeof chunk === 'string') {
1,332✔
158
    encoding = 'utf8';
1,286✔
159
    type = this.get('Content-Type');
1,286✔
160

161
    // reflect this in content-type
162
    if (typeof type === 'string') {
1,286✔
163
      this.set('Content-Type', setCharset(type, 'utf-8'));
1,284✔
164
    }
165
  }
166

167
  // determine if ETag should be generated
168
  var etagFn = app.get('etag fn')
1,332✔
169
  var generateETag = !this.get('ETag') && typeof etagFn === 'function'
1,332✔
170

171
  // populate Content-Length
172
  var len
173
  if (chunk !== undefined) {
1,332✔
174
    if (Buffer.isBuffer(chunk)) {
1,300✔
175
      // get length of Buffer
176
      len = chunk.length
12✔
177
    } else if (!generateETag && chunk.length < 1000) {
1,288✔
178
      // just calculate length when no ETag + small chunk
179
      len = Buffer.byteLength(chunk, encoding)
22✔
180
    } else {
181
      // convert chunk to Buffer and calculate
182
      chunk = Buffer.from(chunk, encoding)
1,266✔
183
      encoding = undefined;
1,266✔
184
      len = chunk.length
1,266✔
185
    }
186

187
    this.set('Content-Length', len);
1,300✔
188
  }
189

190
  // populate ETag
191
  var etag;
192
  if (generateETag && len !== undefined) {
1,332✔
193
    if ((etag = etagFn(chunk, encoding))) {
1,276✔
194
      this.set('ETag', etag);
1,274✔
195
    }
196
  }
197

198
  // freshness
199
  if (req.fresh) this.status(304);
1,332✔
200

201
  // strip irrelevant headers
202
  if (204 === this.statusCode || 304 === this.statusCode) {
1,332✔
203
    this.removeHeader('Content-Type');
16✔
204
    this.removeHeader('Content-Length');
16✔
205
    this.removeHeader('Transfer-Encoding');
16✔
206
    chunk = '';
16✔
207
  }
208

209
  // alter headers for 205
210
  if (this.statusCode === 205) {
1,332✔
211
    this.set('Content-Length', '0')
2✔
212
    this.removeHeader('Transfer-Encoding')
2✔
213
    chunk = ''
2✔
214
  }
215

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

224
  return this;
1,332✔
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) {
2✔
240
  // settings
241
  var app = this.app;
476✔
242
  var escape = app.get('json escape')
476✔
243
  var replacer = app.get('json replacer');
476✔
244
  var spaces = app.get('json spaces');
476✔
245
  var body = stringify(obj, replacer, spaces, escape)
476✔
246

247
  // content-type
248
  if (!this.get('Content-Type')) {
476✔
249
    this.set('Content-Type', 'application/json');
438✔
250
  }
251

252
  return this.send(body);
476✔
253
};
254

255
/**
256
 * Send JSON response with JSONP callback support.
257
 *
258
 * Examples:
259
 *
260
 *     res.jsonp(null);
261
 *     res.jsonp({ user: 'tj' });
262
 *
263
 * @param {string|number|boolean|object} obj
264
 * @public
265
 */
266

267
res.jsonp = function jsonp(obj) {
2✔
268
  // settings
269
  var app = this.app;
44✔
270
  var escape = app.get('json escape')
44✔
271
  var replacer = app.get('json replacer');
44✔
272
  var spaces = app.get('json spaces');
44✔
273
  var body = stringify(obj, replacer, spaces, escape)
44✔
274
  var callback = this.req.query[app.get('jsonp callback name')];
44✔
275

276
  // content-type
277
  if (!this.get('Content-Type')) {
44✔
278
    this.set('X-Content-Type-Options', 'nosniff');
38✔
279
    this.set('Content-Type', 'application/json');
38✔
280
  }
281

282
  // fixup callback
283
  if (Array.isArray(callback)) {
44✔
284
    callback = callback[0];
2✔
285
  }
286

287
  // jsonp
288
  if (typeof callback === 'string' && callback.length !== 0) {
44✔
289
    this.set('X-Content-Type-Options', 'nosniff');
32✔
290
    this.set('Content-Type', 'text/javascript');
32✔
291

292
    // restrict callback charset
293
    callback = callback.replace(/[^\[\]\w$.]/g, '');
32✔
294

295
    if (body === undefined) {
32✔
296
      // empty argument
297
      body = ''
4✔
298
    } else if (typeof body === 'string') {
28✔
299
      // replace chars not allowed in JavaScript that are in JSON
300
      body = body
28✔
301
        .replace(/\u2028/g, '\\u2028')
302
        .replace(/\u2029/g, '\\u2029')
303
    }
304

305
    // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
306
    // the typeof check is just to reduce client error noise
307
    body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
32✔
308
  }
309

310
  return this.send(body);
44✔
311
};
312

313
/**
314
 * Send given HTTP status code.
315
 *
316
 * Sets the response status to `statusCode` and the body of the
317
 * response to the standard description from node's http.STATUS_CODES
318
 * or the statusCode number if no description.
319
 *
320
 * Examples:
321
 *
322
 *     res.sendStatus(200);
323
 *
324
 * @param {number} statusCode
325
 * @public
326
 */
327

328
res.sendStatus = function sendStatus(statusCode) {
2✔
329
  var body = statuses.message[statusCode] || String(statusCode)
42✔
330

331
  this.status(statusCode);
42✔
332
  this.type('txt');
40✔
333

334
  return this.send(body);
40✔
335
};
336

337
/**
338
 * Transfer the file at the given `path`.
339
 *
340
 * Automatically sets the _Content-Type_ response header field.
341
 * The callback `callback(err)` is invoked when the transfer is complete
342
 * or when an error occurs. Be sure to check `res.headersSent`
343
 * if you wish to attempt responding, as the header and some data
344
 * may have already been transferred.
345
 *
346
 * Options:
347
 *
348
 *   - `maxAge`   defaulting to 0 (can be string converted by `ms`)
349
 *   - `root`     root directory for relative filenames
350
 *   - `headers`  object of headers to serve with file
351
 *   - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
352
 *
353
 * Other options are passed along to `send`.
354
 *
355
 * Examples:
356
 *
357
 *  The following example illustrates how `res.sendFile()` may
358
 *  be used as an alternative for the `static()` middleware for
359
 *  dynamic situations. The code backing `res.sendFile()` is actually
360
 *  the same code, so HTTP cache support etc is identical.
361
 *
362
 *     app.get('/user/:uid/photos/:file', function(req, res){
363
 *       var uid = req.params.uid
364
 *         , file = req.params.file;
365
 *
366
 *       req.user.mayViewFilesFrom(uid, function(yes){
367
 *         if (yes) {
368
 *           res.sendFile('/uploads/' + uid + '/' + file);
369
 *         } else {
370
 *           res.send(403, 'Sorry! you cant see that.');
371
 *         }
372
 *       });
373
 *     });
374
 *
375
 * @public
376
 */
377

378
res.sendFile = function sendFile(path, options, callback) {
2✔
379
  var done = callback;
172✔
380
  var req = this.req;
172✔
381
  var res = this;
172✔
382
  var next = req.next;
172✔
383
  var opts = options || {};
172✔
384

385
  if (!path) {
172✔
386
    throw new TypeError('path argument is required to res.sendFile');
2✔
387
  }
388

389
  if (typeof path !== 'string') {
170✔
390
    throw new TypeError('path must be a string to res.sendFile')
2✔
391
  }
392

393
  // support function as second arg
394
  if (typeof options === 'function') {
168✔
395
    done = options;
18✔
396
    opts = {};
18✔
397
  }
398

399
  if (!opts.root && !pathIsAbsolute(path)) {
168✔
400
    throw new TypeError('path must be absolute or specify root to res.sendFile');
2✔
401
  }
402

403
  // create file stream
404
  var pathname = encodeURI(path);
166✔
405

406
  // wire application etag option to send
407
  opts.etag = this.app.enabled('etag');
166✔
408
  var file = send(req, pathname, opts);
166✔
409

410
  // transfer
411
  sendfile(res, file, opts, function (err) {
166✔
412
    if (done) return done(err);
166✔
413
    if (err && err.code === 'EISDIR') return next();
126✔
414

415
    // next() all but write errors
416
    if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
124✔
417
      next(err);
20✔
418
    }
419
  });
420
};
421

422
/**
423
 * Transfer the file at the given `path` as an attachment.
424
 *
425
 * Optionally providing an alternate attachment `filename`,
426
 * and optional callback `callback(err)`. The callback is invoked
427
 * when the data transfer is complete, or when an error has
428
 * occurred. Be sure to check `res.headersSent` if you plan to respond.
429
 *
430
 * Optionally providing an `options` object to use with `res.sendFile()`.
431
 * This function will set the `Content-Disposition` header, overriding
432
 * any `Content-Disposition` header passed as header options in order
433
 * to set the attachment and filename.
434
 *
435
 * This method uses `res.sendFile()`.
436
 *
437
 * @public
438
 */
439

440
res.download = function download (path, filename, options, callback) {
2✔
441
  var done = callback;
58✔
442
  var name = filename;
58✔
443
  var opts = options || null
58✔
444

445
  // support function as second or third arg
446
  if (typeof filename === 'function') {
58✔
447
    done = filename;
10✔
448
    name = null;
10✔
449
    opts = null
10✔
450
  } else if (typeof options === 'function') {
48✔
451
    done = options
10✔
452
    opts = null
10✔
453
  }
454

455
  // support optional filename, where options may be in it's place
456
  if (typeof filename === 'object' &&
58✔
457
    (typeof options === 'function' || options === undefined)) {
458
    name = null
30✔
459
    opts = filename
30✔
460
  }
461

462
  // set Content-Disposition when file is sent
463
  var headers = {
58✔
464
    'Content-Disposition': contentDisposition(name || path)
465
  };
466

467
  // merge user-provided headers
468
  if (opts && opts.headers) {
58✔
469
    var keys = Object.keys(opts.headers)
16✔
470
    for (var i = 0; i < keys.length; i++) {
16✔
471
      var key = keys[i]
24✔
472
      if (key.toLowerCase() !== 'content-disposition') {
24✔
473
        headers[key] = opts.headers[key]
16✔
474
      }
475
    }
476
  }
477

478
  // merge user-provided options
479
  opts = Object.create(opts)
58✔
480
  opts.headers = headers
58✔
481

482
  // Resolve the full path for sendFile
483
  var fullPath = !opts.root
58✔
484
    ? resolve(path)
485
    : path
486

487
  // send file
488
  return this.sendFile(fullPath, opts, done)
58✔
489
};
490

491
/**
492
 * Set _Content-Type_ response header with `type` through `mime.contentType()`
493
 * when it does not contain "/", or set the Content-Type to `type` otherwise.
494
 * When no mapping is found though `mime.contentType()`, the type is set to
495
 * "application/octet-stream".
496
 *
497
 * Examples:
498
 *
499
 *     res.type('.html');
500
 *     res.type('html');
501
 *     res.type('json');
502
 *     res.type('application/json');
503
 *     res.type('png');
504
 *
505
 * @param {String} type
506
 * @return {ServerResponse} for chaining
507
 * @public
508
 */
509

510
res.contentType =
2✔
511
res.type = function contentType(type) {
512
  var ct = type.indexOf('/') === -1
748✔
513
    ? (mime.contentType(type) || 'application/octet-stream')
514
    : type;
515

516
  return this.set('Content-Type', ct);
748✔
517
};
518

519
/**
520
 * Respond to the Acceptable formats using an `obj`
521
 * of mime-type callbacks.
522
 *
523
 * This method uses `req.accepted`, an array of
524
 * acceptable types ordered by their quality values.
525
 * When "Accept" is not present the _first_ callback
526
 * is invoked, otherwise the first match is used. When
527
 * no match is performed the server responds with
528
 * 406 "Not Acceptable".
529
 *
530
 * Content-Type is set for you, however if you choose
531
 * you may alter this within the callback using `res.type()`
532
 * or `res.set('Content-Type', ...)`.
533
 *
534
 *    res.format({
535
 *      'text/plain': function(){
536
 *        res.send('hey');
537
 *      },
538
 *
539
 *      'text/html': function(){
540
 *        res.send('<p>hey</p>');
541
 *      },
542
 *
543
 *      'application/json': function () {
544
 *        res.send({ message: 'hey' });
545
 *      }
546
 *    });
547
 *
548
 * In addition to canonicalized MIME types you may
549
 * also use extnames mapped to these types:
550
 *
551
 *    res.format({
552
 *      text: function(){
553
 *        res.send('hey');
554
 *      },
555
 *
556
 *      html: function(){
557
 *        res.send('<p>hey</p>');
558
 *      },
559
 *
560
 *      json: function(){
561
 *        res.send({ message: 'hey' });
562
 *      }
563
 *    });
564
 *
565
 * By default Express passes an `Error`
566
 * with a `.status` of 406 to `next(err)`
567
 * if a match is not made. If you provide
568
 * a `.default` callback it will be invoked
569
 * instead.
570
 *
571
 * @param {Object} obj
572
 * @return {ServerResponse} for chaining
573
 * @public
574
 */
575

576
res.format = function(obj){
2✔
577
  var req = this.req;
184✔
578
  var next = req.next;
184✔
579

580
  var keys = Object.keys(obj)
184✔
581
    .filter(function (v) { return v !== 'default' })
544✔
582

583
  var key = keys.length > 0
184✔
584
    ? req.accepts(keys)
585
    : false;
586

587
  this.vary("Accept");
184✔
588

589
  if (key) {
184✔
590
    this.set('Content-Type', normalizeType(key).value);
164✔
591
    obj[key](req, this, next);
164✔
592
  } else if (obj.default) {
20✔
593
    obj.default(req, this, next)
10✔
594
  } else {
595
    next(createError(406, {
10✔
596
      types: normalizeTypes(keys).map(function (o) { return o.value })
30✔
597
    }))
598
  }
599

600
  return this;
184✔
601
};
602

603
/**
604
 * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
605
 *
606
 * @param {String} filename
607
 * @return {ServerResponse}
608
 * @public
609
 */
610

611
res.attachment = function attachment(filename) {
2✔
612
  if (filename) {
10✔
613
    this.type(extname(filename));
8✔
614
  }
615

616
  this.set('Content-Disposition', contentDisposition(filename));
10✔
617

618
  return this;
10✔
619
};
620

621
/**
622
 * Append additional header `field` with value `val`.
623
 *
624
 * Example:
625
 *
626
 *    res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
627
 *    res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
628
 *    res.append('Warning', '199 Miscellaneous warning');
629
 *
630
 * @param {String} field
631
 * @param {String|Array} val
632
 * @return {ServerResponse} for chaining
633
 * @public
634
 */
635

636
res.append = function append(field, val) {
2✔
637
  var prev = this.get(field);
68✔
638
  var value = val;
68✔
639

640
  if (prev) {
68✔
641
    // concat the new and prev vals
642
    value = Array.isArray(prev) ? prev.concat(val)
12✔
643
      : Array.isArray(val) ? [prev].concat(val)
644
        : [prev, val]
645
  }
646

647
  return this.set(field, value);
68✔
648
};
649

650
/**
651
 * Set header `field` to `val`, or pass
652
 * an object of header fields.
653
 *
654
 * Examples:
655
 *
656
 *    res.set('Foo', ['bar', 'baz']);
657
 *    res.set('Accept', 'application/json');
658
 *    res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
659
 *
660
 * Aliased as `res.header()`.
661
 *
662
 * When the set header is "Content-Type", the type is expanded to include
663
 * the charset if not present using `mime.contentType()`.
664
 *
665
 * @param {String|Object} field
666
 * @param {String|Array} val
667
 * @return {ServerResponse} for chaining
668
 * @public
669
 */
670

671
res.set =
2✔
672
res.header = function header(field, val) {
673
  if (arguments.length === 2) {
5,700✔
674
    var value = Array.isArray(val)
5,696✔
675
      ? val.map(String)
676
      : String(val);
677

678
    // add charset to content-type
679
    if (field.toLowerCase() === 'content-type') {
5,696✔
680
      if (Array.isArray(value)) {
2,720✔
681
        throw new TypeError('Content-Type cannot be set to an Array');
2✔
682
      }
683
      value = mime.contentType(value)
2,718✔
684
    }
685

686
    this.setHeader(field, value);
5,694✔
687
  } else {
688
    for (var key in field) {
4✔
689
      this.set(key, field[key]);
6✔
690
    }
691
  }
692
  return this;
5,698✔
693
};
694

695
/**
696
 * Get value for header `field`.
697
 *
698
 * @param {String} field
699
 * @return {String}
700
 * @public
701
 */
702

703
res.get = function(field){
2✔
704
  return this.getHeader(field);
5,944✔
705
};
706

707
/**
708
 * Clear cookie `name`.
709
 *
710
 * @param {String} name
711
 * @param {Object} [options]
712
 * @return {ServerResponse} for chaining
713
 * @public
714
 */
715

716
res.clearCookie = function clearCookie(name, options) {
2✔
717
  // Force cookie expiration by setting expires to the past
718
  const opts = { path: '/', ...options, expires: new Date(1)};
10✔
719
  // ensure maxAge is not passed
720
  delete opts.maxAge
10✔
721

722
  return this.cookie(name, '', opts);
10✔
723
};
724

725
/**
726
 * Set cookie `name` to `value`, with the given `options`.
727
 *
728
 * Options:
729
 *
730
 *    - `maxAge`   max-age in milliseconds, converted to `expires`
731
 *    - `signed`   sign the cookie
732
 *    - `path`     defaults to "/"
733
 *
734
 * Examples:
735
 *
736
 *    // "Remember Me" for 15 minutes
737
 *    res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
738
 *
739
 *    // same as above
740
 *    res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
741
 *
742
 * @param {String} name
743
 * @param {String|Object} value
744
 * @param {Object} [options]
745
 * @return {ServerResponse} for chaining
746
 * @public
747
 */
748

749
res.cookie = function (name, value, options) {
2✔
750
  var opts = { ...options };
62✔
751
  var secret = this.req.secret;
62✔
752
  var signed = opts.signed;
62✔
753

754
  if (signed && !secret) {
62✔
755
    throw new Error('cookieParser("secret") required for signed cookies');
2✔
756
  }
757

758
  var val = typeof value === 'object'
60✔
759
    ? 'j:' + JSON.stringify(value)
760
    : String(value);
761

762
  if (signed) {
60✔
763
    val = 's:' + sign(val, secret);
6✔
764
  }
765

766
  if (opts.maxAge != null) {
60✔
767
    var maxAge = opts.maxAge - 0
14✔
768

769
    if (!isNaN(maxAge)) {
14✔
770
      opts.expires = new Date(Date.now() + maxAge)
12✔
771
      opts.maxAge = Math.floor(maxAge / 1000)
12✔
772
    }
773
  }
774

775
  if (opts.path == null) {
60✔
776
    opts.path = '/';
50✔
777
  }
778

779
  this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
60✔
780

781
  return this;
54✔
782
};
783

784
/**
785
 * Set the location header to `url`.
786
 *
787
 * The given `url` can also be "back", which redirects
788
 * to the _Referrer_ or _Referer_ headers or "/".
789
 *
790
 * Examples:
791
 *
792
 *    res.location('/foo/bar').;
793
 *    res.location('http://example.com');
794
 *    res.location('../login');
795
 *
796
 * @param {String} url
797
 * @return {ServerResponse} for chaining
798
 * @public
799
 */
800

801
res.location = function location(url) {
2✔
802
  return this.set('Location', encodeUrl(url));
114✔
803
};
804

805
/**
806
 * Redirect to the given `url` with optional response `status`
807
 * defaulting to 302.
808
 *
809
 * Examples:
810
 *
811
 *    res.redirect('/foo/bar');
812
 *    res.redirect('http://example.com');
813
 *    res.redirect(301, 'http://example.com');
814
 *    res.redirect('../login'); // /blog/post/1 -> /blog/login
815
 *
816
 * @public
817
 */
818

819
res.redirect = function redirect(url) {
2✔
820
  var address = url;
70✔
821
  var body;
822
  var status = 302;
70✔
823

824
  // allow status / url
825
  if (arguments.length === 2) {
70✔
826
    status = arguments[0]
6✔
827
    address = arguments[1]
6✔
828
  }
829

830
  if (!address) {
70✔
UNCOV
831
    deprecate('Provide a url argument');
×
832
  }
833

834
  if (typeof address !== 'string') {
70✔
UNCOV
835
    deprecate('Url must be a string');
×
836
  }
837

838
  if (typeof status !== 'number') {
70✔
UNCOV
839
    deprecate('Status must be a number');
×
840
  }
841

842
  // Set location header
843
  address = this.location(address).get('Location');
70✔
844

845
  // Support text/{plain,html} by default
846
  this.format({
70✔
847
    text: function(){
848
      body = statuses.message[status] + '. Redirecting to ' + address
60✔
849
    },
850

851
    html: function(){
852
      var u = escapeHtml(address);
8✔
853
      body = '<!DOCTYPE html><head><title>' + statuses.message[status] + '</title></head>'
8✔
854
       + '<body><p>' + statuses.message[status] + '. Redirecting to ' + u + '</p></body>'
855
    },
856

857
    default: function(){
858
      body = '';
2✔
859
    }
860
  });
861

862
  // Respond
863
  this.status(status);
70✔
864
  this.set('Content-Length', Buffer.byteLength(body));
70✔
865

866
  if (this.req.method === 'HEAD') {
70✔
867
    this.end();
2✔
868
  } else {
869
    this.end(body);
68✔
870
  }
871
};
872

873
/**
874
 * Add `field` to Vary. If already present in the Vary set, then
875
 * this call is simply ignored.
876
 *
877
 * @param {Array|String} field
878
 * @return {ServerResponse} for chaining
879
 * @public
880
 */
881

882
res.vary = function(field){
2✔
883
  vary(this, field);
202✔
884

885
  return this;
200✔
886
};
887

888
/**
889
 * Render `view` with the given `options` and optional callback `fn`.
890
 * When a callback function is given a response will _not_ be made
891
 * automatically, otherwise a response of _200_ and _text/html_ is given.
892
 *
893
 * Options:
894
 *
895
 *  - `cache`     boolean hinting to the engine it should cache
896
 *  - `filename`  filename of the view being rendered
897
 *
898
 * @public
899
 */
900

901
res.render = function render(view, options, callback) {
2✔
902
  var app = this.req.app;
108✔
903
  var done = callback;
108✔
904
  var opts = options || {};
108✔
905
  var req = this.req;
108✔
906
  var self = this;
108✔
907

908
  // support callback function as second arg
909
  if (typeof options === 'function') {
108✔
910
    done = options;
4✔
911
    opts = {};
4✔
912
  }
913

914
  // merge res.locals
915
  opts._locals = self.locals;
108✔
916

917
  // default callback to respond
918
  done = done || function (err, str) {
108✔
919
    if (err) return req.next(err);
98✔
920
    self.send(str);
94✔
921
  };
922

923
  // render
924
  app.render(view, opts, done);
108✔
925
};
926

927
// pipe the send file stream
928
function sendfile(res, file, options, callback) {
929
  var done = false;
166✔
930
  var streaming;
931

932
  // request aborted
933
  function onaborted() {
934
    if (done) return;
6✔
935
    done = true;
6✔
936

937
    var err = new Error('Request aborted');
6✔
938
    err.code = 'ECONNABORTED';
6✔
939
    callback(err);
6✔
940
  }
941

942
  // directory
943
  function ondirectory() {
944
    if (done) return;
2✔
945
    done = true;
2✔
946

947
    var err = new Error('EISDIR, read');
2✔
948
    err.code = 'EISDIR';
2✔
949
    callback(err);
2✔
950
  }
951

952
  // errors
953
  function onerror(err) {
954
    if (done) return;
34✔
955
    done = true;
34✔
956
    callback(err);
34✔
957
  }
958

959
  // ended
960
  function onend() {
961
    if (done) return;
116✔
962
    done = true;
116✔
963
    callback();
116✔
964
  }
965

966
  // file
967
  function onfile() {
968
    streaming = false;
130✔
969
  }
970

971
  // finished
972
  function onfinish(err) {
973
    if (err && err.code === 'ECONNRESET') return onaborted();
166✔
974
    if (err) return onerror(err);
166✔
975
    if (done) return;
166✔
976

977
    setImmediate(function () {
14✔
978
      if (streaming !== false && !done) {
14✔
979
        onaborted();
6✔
980
        return;
6✔
981
      }
982

983
      if (done) return;
8✔
984
      done = true;
8✔
985
      callback();
8✔
986
    });
987
  }
988

989
  // streaming
990
  function onstream() {
991
    streaming = true;
122✔
992
  }
993

994
  file.on('directory', ondirectory);
166✔
995
  file.on('end', onend);
166✔
996
  file.on('error', onerror);
166✔
997
  file.on('file', onfile);
166✔
998
  file.on('stream', onstream);
166✔
999
  onFinished(res, onfinish);
166✔
1000

1001
  if (options.headers) {
166✔
1002
    // set headers on successful transfer
1003
    file.on('headers', function headers(res) {
66✔
1004
      var obj = options.headers;
48✔
1005
      var keys = Object.keys(obj);
48✔
1006

1007
      for (var i = 0; i < keys.length; i++) {
48✔
1008
        var k = keys[i];
66✔
1009
        res.setHeader(k, obj[k]);
66✔
1010
      }
1011
    });
1012
  }
1013

1014
  // pipe
1015
  file.pipe(res);
166✔
1016
}
1017

1018
/**
1019
 * Stringify JSON, like JSON.stringify, but v8 optimized, with the
1020
 * ability to escape characters that can trigger HTML sniffing.
1021
 *
1022
 * @param {*} value
1023
 * @param {function} replacer
1024
 * @param {number} spaces
1025
 * @param {boolean} escape
1026
 * @returns {string}
1027
 * @private
1028
 */
1029

1030
function stringify (value, replacer, spaces, escape) {
1031
  // v8 checks arguments.length for optimizing simple call
1032
  // https://bugs.chromium.org/p/v8/issues/detail?id=4730
1033
  var json = replacer || spaces
520✔
1034
    ? JSON.stringify(value, replacer, spaces)
1035
    : JSON.stringify(value);
1036

1037
  if (escape && typeof json === 'string') {
520✔
1038
    json = json.replace(/[<>&]/g, function (c) {
4✔
1039
      switch (c.charCodeAt(0)) {
12✔
1040
        case 0x3c:
1041
          return '\\u003c'
4✔
1042
        case 0x3e:
1043
          return '\\u003e'
4✔
1044
        case 0x26:
1045
          return '\\u0026'
4✔
1046
        /* istanbul ignore next: unreachable default */
1047
        default:
1048
          return c
1049
      }
1050
    })
1051
  }
1052

1053
  return json
520✔
1054
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc