skfb-viewer-protocol 0.0.1-security → 2.1202.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of skfb-viewer-protocol might be problematic. Click here for more details.

@@ -0,0 +1,1142 @@
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
+ }