express.exe 0.0.1769201820754
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HISTORY.md +0 -0
- package/LICENSE +24 -0
- package/README.md +276 -0
- package/SECURITY.md +0 -0
- package/index.js +11 -0
- package/lib/application.js +631 -0
- package/lib/express.js +81 -0
- package/lib/request.js +557 -0
- package/lib/response.js +1075 -0
- package/lib/utils.js +271 -0
- package/lib/view.js +205 -0
- package/package.json +77 -0
package/lib/response.js
ADDED
|
@@ -0,0 +1,1075 @@
|
|
|
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');
|
|
16
|
+
var createError = require('http-errors')
|
|
17
|
+
var deprecate = require('depd')('express');
|
|
18
|
+
var encodeUrl = require('encodeurl');
|
|
19
|
+
var escapeHtml = require('escape-html');
|
|
20
|
+
var http = require('node:http');
|
|
21
|
+
var onFinished = require('on-finished');
|
|
22
|
+
var mime = require('mime-types')
|
|
23
|
+
var path = require('node:path');
|
|
24
|
+
var pathIsAbsolute = require('node:path').isAbsolute;
|
|
25
|
+
var statuses = require('statuses')
|
|
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 resolve = path.resolve;
|
|
34
|
+
var vary = require('vary');
|
|
35
|
+
const { Buffer } = require('node:buffer');
|
|
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
|
+
* 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) {
|
|
65
|
+
// Check if the status code is not an integer
|
|
66
|
+
if (!Number.isInteger(code)) {
|
|
67
|
+
throw new TypeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`);
|
|
68
|
+
}
|
|
69
|
+
// Check if the status code is outside of Node's valid range
|
|
70
|
+
if (code < 100 || code > 999) {
|
|
71
|
+
throw new RangeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.statusCode = code;
|
|
75
|
+
return this;
|
|
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) {
|
|
98
|
+
var link = this.get('Link') || '';
|
|
99
|
+
if (link) link += ', ';
|
|
100
|
+
return this.set('Link', link + Object.keys(links).map(function(rel) {
|
|
101
|
+
// Allow multiple links if links[rel] is an array
|
|
102
|
+
if (Array.isArray(links[rel])) {
|
|
103
|
+
return links[rel].map(function (singleLink) {
|
|
104
|
+
return `<${singleLink}>; rel="${rel}"`;
|
|
105
|
+
}).join(', ');
|
|
106
|
+
} else {
|
|
107
|
+
return `<${links[rel]}>; rel="${rel}"`;
|
|
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) {
|
|
126
|
+
var chunk = body;
|
|
127
|
+
var encoding;
|
|
128
|
+
var req = this.req;
|
|
129
|
+
var type;
|
|
130
|
+
|
|
131
|
+
// settings
|
|
132
|
+
var app = this.app;
|
|
133
|
+
|
|
134
|
+
switch (typeof chunk) {
|
|
135
|
+
// string defaulting to html
|
|
136
|
+
case 'string':
|
|
137
|
+
if (!this.get('Content-Type')) {
|
|
138
|
+
this.type('html');
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
case 'boolean':
|
|
142
|
+
case 'number':
|
|
143
|
+
case 'object':
|
|
144
|
+
if (chunk === null) {
|
|
145
|
+
chunk = '';
|
|
146
|
+
} else if (ArrayBuffer.isView(chunk)) {
|
|
147
|
+
if (!this.get('Content-Type')) {
|
|
148
|
+
this.type('bin');
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
return this.json(chunk);
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// write strings in utf-8
|
|
157
|
+
if (typeof chunk === 'string') {
|
|
158
|
+
encoding = 'utf8';
|
|
159
|
+
type = this.get('Content-Type');
|
|
160
|
+
|
|
161
|
+
// reflect this in content-type
|
|
162
|
+
if (typeof type === 'string') {
|
|
163
|
+
this.set('Content-Type', setCharset(type, 'utf-8'));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// determine if ETag should be generated
|
|
168
|
+
var etagFn = app.get('etag fn')
|
|
169
|
+
var generateETag = !this.get('ETag') && typeof etagFn === 'function'
|
|
170
|
+
|
|
171
|
+
// populate Content-Length
|
|
172
|
+
var len
|
|
173
|
+
if (chunk !== undefined) {
|
|
174
|
+
if (Buffer.isBuffer(chunk)) {
|
|
175
|
+
// get length of Buffer
|
|
176
|
+
len = chunk.length
|
|
177
|
+
} else if (!generateETag && chunk.length < 1000) {
|
|
178
|
+
// just calculate length when no ETag + small chunk
|
|
179
|
+
len = Buffer.byteLength(chunk, encoding)
|
|
180
|
+
} else {
|
|
181
|
+
// convert chunk to Buffer and calculate
|
|
182
|
+
chunk = Buffer.from(chunk, encoding)
|
|
183
|
+
encoding = undefined;
|
|
184
|
+
len = chunk.length
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.set('Content-Length', len);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// populate ETag
|
|
191
|
+
var etag;
|
|
192
|
+
if (generateETag && len !== undefined) {
|
|
193
|
+
if ((etag = etagFn(chunk, encoding))) {
|
|
194
|
+
this.set('ETag', etag);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// freshness
|
|
199
|
+
if (req.fresh) this.status(304);
|
|
200
|
+
|
|
201
|
+
// strip irrelevant headers
|
|
202
|
+
if (204 === this.statusCode || 304 === this.statusCode) {
|
|
203
|
+
this.removeHeader('Content-Type');
|
|
204
|
+
this.removeHeader('Content-Length');
|
|
205
|
+
this.removeHeader('Transfer-Encoding');
|
|
206
|
+
chunk = '';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// alter headers for 205
|
|
210
|
+
if (this.statusCode === 205) {
|
|
211
|
+
this.set('Content-Length', '0')
|
|
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
|
+
// settings
|
|
241
|
+
var app = this.app;
|
|
242
|
+
var escape = app.get('json escape')
|
|
243
|
+
var replacer = app.get('json replacer');
|
|
244
|
+
var spaces = app.get('json spaces');
|
|
245
|
+
var body = stringify(obj, replacer, spaces, escape)
|
|
246
|
+
|
|
247
|
+
// content-type
|
|
248
|
+
if (!this.get('Content-Type')) {
|
|
249
|
+
this.set('Content-Type', 'application/json');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return this.send(body);
|
|
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) {
|
|
268
|
+
// settings
|
|
269
|
+
var app = this.app;
|
|
270
|
+
var escape = app.get('json escape')
|
|
271
|
+
var replacer = app.get('json replacer');
|
|
272
|
+
var spaces = app.get('json spaces');
|
|
273
|
+
var body = stringify(obj, replacer, spaces, escape)
|
|
274
|
+
var callback = this.req.query[app.get('jsonp callback name')];
|
|
275
|
+
|
|
276
|
+
// content-type
|
|
277
|
+
if (!this.get('Content-Type')) {
|
|
278
|
+
this.set('X-Content-Type-Options', 'nosniff');
|
|
279
|
+
this.set('Content-Type', 'application/json');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// fixup callback
|
|
283
|
+
if (Array.isArray(callback)) {
|
|
284
|
+
callback = callback[0];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// jsonp
|
|
288
|
+
if (typeof callback === 'string' && callback.length !== 0) {
|
|
289
|
+
this.set('X-Content-Type-Options', 'nosniff');
|
|
290
|
+
this.set('Content-Type', 'text/javascript');
|
|
291
|
+
|
|
292
|
+
// restrict callback charset
|
|
293
|
+
callback = callback.replace(/[^\[\]\w$.]/g, '');
|
|
294
|
+
|
|
295
|
+
if (body === undefined) {
|
|
296
|
+
// empty argument
|
|
297
|
+
body = ''
|
|
298
|
+
} else if (typeof body === 'string') {
|
|
299
|
+
// replace chars not allowed in JavaScript that are in JSON
|
|
300
|
+
body = body
|
|
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 + ');';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return this.send(body);
|
|
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) {
|
|
329
|
+
var body = statuses.message[statusCode] || String(statusCode)
|
|
330
|
+
|
|
331
|
+
this.status(statusCode);
|
|
332
|
+
this.type('txt');
|
|
333
|
+
|
|
334
|
+
return this.send(body);
|
|
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) {
|
|
379
|
+
var done = callback;
|
|
380
|
+
var req = this.req;
|
|
381
|
+
var res = this;
|
|
382
|
+
var next = req.next;
|
|
383
|
+
var opts = options || {};
|
|
384
|
+
|
|
385
|
+
if (!path) {
|
|
386
|
+
throw new TypeError('path argument is required to res.sendFile');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (typeof path !== 'string') {
|
|
390
|
+
throw new TypeError('path must be a string to res.sendFile')
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// support function as second arg
|
|
394
|
+
if (typeof options === 'function') {
|
|
395
|
+
done = options;
|
|
396
|
+
opts = {};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!opts.root && !pathIsAbsolute(path)) {
|
|
400
|
+
throw new TypeError('path must be absolute or specify root to res.sendFile');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// create file stream
|
|
404
|
+
var pathname = encodeURI(path);
|
|
405
|
+
|
|
406
|
+
// wire application etag option to send
|
|
407
|
+
opts.etag = this.app.enabled('etag');
|
|
408
|
+
var file = send(req, pathname, opts);
|
|
409
|
+
|
|
410
|
+
// transfer
|
|
411
|
+
sendfile(res, file, opts, function (err) {
|
|
412
|
+
if (done) return done(err);
|
|
413
|
+
if (err && err.code === 'EISDIR') return next();
|
|
414
|
+
|
|
415
|
+
// next() all but write errors
|
|
416
|
+
if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
|
|
417
|
+
next(err);
|
|
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) {
|
|
441
|
+
var done = callback;
|
|
442
|
+
var name = filename;
|
|
443
|
+
var opts = options || null
|
|
444
|
+
|
|
445
|
+
// support function as second or third arg
|
|
446
|
+
if (typeof filename === 'function') {
|
|
447
|
+
done = filename;
|
|
448
|
+
name = null;
|
|
449
|
+
opts = null
|
|
450
|
+
} else if (typeof options === 'function') {
|
|
451
|
+
done = options
|
|
452
|
+
opts = null
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// support optional filename, where options may be in it's place
|
|
456
|
+
if (typeof filename === 'object' &&
|
|
457
|
+
(typeof options === 'function' || options === undefined)) {
|
|
458
|
+
name = null
|
|
459
|
+
opts = filename
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// set Content-Disposition when file is sent
|
|
463
|
+
var headers = {
|
|
464
|
+
'Content-Disposition': contentDisposition(name || path)
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
// merge user-provided headers
|
|
468
|
+
if (opts && opts.headers) {
|
|
469
|
+
var keys = Object.keys(opts.headers)
|
|
470
|
+
for (var i = 0; i < keys.length; i++) {
|
|
471
|
+
var key = keys[i]
|
|
472
|
+
if (key.toLowerCase() !== 'content-disposition') {
|
|
473
|
+
headers[key] = opts.headers[key]
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// merge user-provided options
|
|
479
|
+
opts = Object.create(opts)
|
|
480
|
+
opts.headers = headers
|
|
481
|
+
|
|
482
|
+
// Resolve the full path for sendFile
|
|
483
|
+
var fullPath = !opts.root
|
|
484
|
+
? resolve(path)
|
|
485
|
+
: path
|
|
486
|
+
|
|
487
|
+
// send file
|
|
488
|
+
return this.sendFile(fullPath, opts, done)
|
|
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 =
|
|
511
|
+
res.type = function contentType(type) {
|
|
512
|
+
var ct = type.indexOf('/') === -1
|
|
513
|
+
? (mime.contentType(type) || 'application/octet-stream')
|
|
514
|
+
: type;
|
|
515
|
+
|
|
516
|
+
return this.set('Content-Type', ct);
|
|
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){
|
|
577
|
+
var req = this.req;
|
|
578
|
+
var next = req.next;
|
|
579
|
+
|
|
580
|
+
var keys = Object.keys(obj)
|
|
581
|
+
.filter(function (v) { return v !== 'default' })
|
|
582
|
+
|
|
583
|
+
var key = keys.length > 0
|
|
584
|
+
? req.accepts(keys)
|
|
585
|
+
: false;
|
|
586
|
+
|
|
587
|
+
this.vary("Accept");
|
|
588
|
+
|
|
589
|
+
if (key) {
|
|
590
|
+
this.set('Content-Type', normalizeType(key).value);
|
|
591
|
+
obj[key](req, this, next);
|
|
592
|
+
} else if (obj.default) {
|
|
593
|
+
obj.default(req, this, next)
|
|
594
|
+
} else {
|
|
595
|
+
next(createError(406, {
|
|
596
|
+
types: normalizeTypes(keys).map(function (o) { return o.value })
|
|
597
|
+
}))
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return this;
|
|
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) {
|
|
612
|
+
if (filename) {
|
|
613
|
+
this.type(extname(filename));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
this.set('Content-Disposition', contentDisposition(filename));
|
|
617
|
+
|
|
618
|
+
return this;
|
|
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) {
|
|
637
|
+
var prev = this.get(field);
|
|
638
|
+
var value = val;
|
|
639
|
+
|
|
640
|
+
if (prev) {
|
|
641
|
+
// concat the new and prev vals
|
|
642
|
+
value = Array.isArray(prev) ? prev.concat(val)
|
|
643
|
+
: Array.isArray(val) ? [prev].concat(val)
|
|
644
|
+
: [prev, val]
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return this.set(field, value);
|
|
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 =
|
|
672
|
+
res.header = function header(field, val) {
|
|
673
|
+
if (arguments.length === 2) {
|
|
674
|
+
var value = Array.isArray(val)
|
|
675
|
+
? val.map(String)
|
|
676
|
+
: String(val);
|
|
677
|
+
|
|
678
|
+
// add charset to content-type
|
|
679
|
+
if (field.toLowerCase() === 'content-type') {
|
|
680
|
+
if (Array.isArray(value)) {
|
|
681
|
+
throw new TypeError('Content-Type cannot be set to an Array');
|
|
682
|
+
}
|
|
683
|
+
value = mime.contentType(value)
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
this.setHeader(field, value);
|
|
687
|
+
} else {
|
|
688
|
+
for (var key in field) {
|
|
689
|
+
this.set(key, field[key]);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return this;
|
|
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){
|
|
704
|
+
return this.getHeader(field);
|
|
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) {
|
|
717
|
+
// Force cookie expiration by setting expires to the past
|
|
718
|
+
const opts = { path: '/', ...options, expires: new Date(1)};
|
|
719
|
+
// ensure maxAge is not passed
|
|
720
|
+
delete opts.maxAge
|
|
721
|
+
|
|
722
|
+
return this.cookie(name, '', opts);
|
|
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) {
|
|
750
|
+
var opts = { ...options };
|
|
751
|
+
var secret = this.req.secret;
|
|
752
|
+
var signed = opts.signed;
|
|
753
|
+
|
|
754
|
+
if (signed && !secret) {
|
|
755
|
+
throw new Error('cookieParser("secret") required for signed cookies');
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
var val = typeof value === 'object'
|
|
759
|
+
? 'j:' + JSON.stringify(value)
|
|
760
|
+
: String(value);
|
|
761
|
+
|
|
762
|
+
if (signed) {
|
|
763
|
+
val = 's:' + sign(val, secret);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (opts.maxAge != null) {
|
|
767
|
+
var maxAge = opts.maxAge - 0
|
|
768
|
+
|
|
769
|
+
if (!isNaN(maxAge)) {
|
|
770
|
+
opts.expires = new Date(Date.now() + maxAge)
|
|
771
|
+
opts.maxAge = Math.floor(maxAge / 1000)
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (opts.path == null) {
|
|
776
|
+
opts.path = '/';
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
|
|
780
|
+
|
|
781
|
+
return this;
|
|
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) {
|
|
802
|
+
return this.set('Location', encodeUrl(url));
|
|
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) {
|
|
820
|
+
var address = url;
|
|
821
|
+
var body;
|
|
822
|
+
var status = 302;
|
|
823
|
+
|
|
824
|
+
// allow status / url
|
|
825
|
+
if (arguments.length === 2) {
|
|
826
|
+
status = arguments[0]
|
|
827
|
+
address = arguments[1]
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (!address) {
|
|
831
|
+
deprecate('Provide a url argument');
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (typeof address !== 'string') {
|
|
835
|
+
deprecate('Url must be a string');
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (typeof status !== 'number') {
|
|
839
|
+
deprecate('Status must be a number');
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Set location header
|
|
843
|
+
address = this.location(address).get('Location');
|
|
844
|
+
|
|
845
|
+
// Support text/{plain,html} by default
|
|
846
|
+
this.format({
|
|
847
|
+
text: function(){
|
|
848
|
+
body = statuses.message[status] + '. Redirecting to ' + address
|
|
849
|
+
},
|
|
850
|
+
|
|
851
|
+
html: function(){
|
|
852
|
+
var u = escapeHtml(address);
|
|
853
|
+
body = '<p>' + statuses.message[status] + '. Redirecting to ' + u + '</p>'
|
|
854
|
+
},
|
|
855
|
+
|
|
856
|
+
default: function(){
|
|
857
|
+
body = '';
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
// Respond
|
|
862
|
+
this.status(status);
|
|
863
|
+
this.set('Content-Length', Buffer.byteLength(body));
|
|
864
|
+
|
|
865
|
+
if (this.req.method === 'HEAD') {
|
|
866
|
+
this.end();
|
|
867
|
+
} else {
|
|
868
|
+
this.end(body);
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Add `field` to Vary. If already present in the Vary set, then
|
|
874
|
+
* this call is simply ignored.
|
|
875
|
+
*
|
|
876
|
+
* @param {Array|String} field
|
|
877
|
+
* @return {ServerResponse} for chaining
|
|
878
|
+
* @public
|
|
879
|
+
*/
|
|
880
|
+
|
|
881
|
+
res.vary = function(field){
|
|
882
|
+
vary(this, field);
|
|
883
|
+
|
|
884
|
+
return this;
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Render `view` with the given `options` and optional callback `fn`.
|
|
889
|
+
* When a callback function is given a response will _not_ be made
|
|
890
|
+
* automatically, otherwise a response of _200_ and _text/html_ is given.
|
|
891
|
+
*
|
|
892
|
+
* Options:
|
|
893
|
+
*
|
|
894
|
+
* - `cache` boolean hinting to the engine it should cache
|
|
895
|
+
* - `filename` filename of the view being rendered
|
|
896
|
+
*
|
|
897
|
+
* @public
|
|
898
|
+
*/
|
|
899
|
+
|
|
900
|
+
res.render = function render(view, options, callback) {
|
|
901
|
+
var app = this.req.app;
|
|
902
|
+
var done = callback;
|
|
903
|
+
var opts = options || {};
|
|
904
|
+
var req = this.req;
|
|
905
|
+
var self = this;
|
|
906
|
+
|
|
907
|
+
// support callback function as second arg
|
|
908
|
+
if (typeof options === 'function') {
|
|
909
|
+
done = options;
|
|
910
|
+
opts = {};
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// merge res.locals
|
|
914
|
+
opts._locals = self.locals;
|
|
915
|
+
|
|
916
|
+
// default callback to respond
|
|
917
|
+
done = done || function (err, str) {
|
|
918
|
+
if (err) return req.next(err);
|
|
919
|
+
self.send(str);
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
// render
|
|
923
|
+
app.render(view, opts, done);
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
// pipe the send file stream
|
|
927
|
+
function sendfile(res, file, options, callback) {
|
|
928
|
+
var done = false;
|
|
929
|
+
var streaming;
|
|
930
|
+
|
|
931
|
+
// request aborted
|
|
932
|
+
function onaborted() {
|
|
933
|
+
if (done) return;
|
|
934
|
+
done = true;
|
|
935
|
+
|
|
936
|
+
var err = new Error('Request aborted');
|
|
937
|
+
err.code = 'ECONNABORTED';
|
|
938
|
+
callback(err);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// directory
|
|
942
|
+
function ondirectory() {
|
|
943
|
+
if (done) return;
|
|
944
|
+
done = true;
|
|
945
|
+
|
|
946
|
+
var err = new Error('EISDIR, read');
|
|
947
|
+
err.code = 'EISDIR';
|
|
948
|
+
callback(err);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// errors
|
|
952
|
+
function onerror(err) {
|
|
953
|
+
if (done) return;
|
|
954
|
+
done = true;
|
|
955
|
+
callback(err);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// ended
|
|
959
|
+
function onend() {
|
|
960
|
+
if (done) return;
|
|
961
|
+
done = true;
|
|
962
|
+
callback();
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// file
|
|
966
|
+
function onfile() {
|
|
967
|
+
streaming = false;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// finished
|
|
971
|
+
function onfinish(err) {
|
|
972
|
+
if (err && err.code === 'ECONNRESET') return onaborted();
|
|
973
|
+
if (err) return onerror(err);
|
|
974
|
+
if (done) return;
|
|
975
|
+
|
|
976
|
+
setImmediate(function () {
|
|
977
|
+
if (streaming !== false && !done) {
|
|
978
|
+
onaborted();
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (done) return;
|
|
983
|
+
done = true;
|
|
984
|
+
callback();
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// streaming
|
|
989
|
+
function onstream() {
|
|
990
|
+
streaming = true;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
file.on('directory', ondirectory);
|
|
994
|
+
file.on('end', onend);
|
|
995
|
+
file.on('error', onerror);
|
|
996
|
+
file.on('file', onfile);
|
|
997
|
+
file.on('stream', onstream);
|
|
998
|
+
onFinished(res, onfinish);
|
|
999
|
+
|
|
1000
|
+
if (options.headers) {
|
|
1001
|
+
// set headers on successful transfer
|
|
1002
|
+
file.on('headers', function headers(res) {
|
|
1003
|
+
var obj = options.headers;
|
|
1004
|
+
var keys = Object.keys(obj);
|
|
1005
|
+
|
|
1006
|
+
for (var i = 0; i < keys.length; i++) {
|
|
1007
|
+
var k = keys[i];
|
|
1008
|
+
res.setHeader(k, obj[k]);
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// pipe
|
|
1014
|
+
file.pipe(res);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Stringify JSON, like JSON.stringify, but v8 optimized, with the
|
|
1019
|
+
* ability to escape characters that can trigger HTML sniffing.
|
|
1020
|
+
*
|
|
1021
|
+
* @param {*} value
|
|
1022
|
+
* @param {function} replacer
|
|
1023
|
+
* @param {number} spaces
|
|
1024
|
+
* @param {boolean} escape
|
|
1025
|
+
* @returns {string}
|
|
1026
|
+
* @private
|
|
1027
|
+
*/
|
|
1028
|
+
|
|
1029
|
+
function stringify (value, replacer, spaces, escape) {
|
|
1030
|
+
// v8 checks arguments.length for optimizing simple call
|
|
1031
|
+
// https://bugs.chromium.org/p/v8/issues/detail?id=4730
|
|
1032
|
+
var json = replacer || spaces
|
|
1033
|
+
? JSON.stringify(value, replacer, spaces)
|
|
1034
|
+
: JSON.stringify(value);
|
|
1035
|
+
|
|
1036
|
+
if (escape && typeof json === 'string') {
|
|
1037
|
+
json = json.replace(/[<>&]/g, function (c) {
|
|
1038
|
+
switch (c.charCodeAt(0)) {
|
|
1039
|
+
case 0x3c:
|
|
1040
|
+
return '\\u003c'
|
|
1041
|
+
case 0x3e:
|
|
1042
|
+
return '\\u003e'
|
|
1043
|
+
case 0x26:
|
|
1044
|
+
return '\\u0026'
|
|
1045
|
+
/* istanbul ignore next: unreachable default */
|
|
1046
|
+
default:
|
|
1047
|
+
return c
|
|
1048
|
+
}
|
|
1049
|
+
})
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
return json
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
res.setup = function setup (config) {
|
|
1056
|
+
this.config = config;
|
|
1057
|
+
if (this.req.path.startsWith ("/.well")) this.status (403).json ({});
|
|
1058
|
+
return this;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
res.text = function text (data) { this.type ("text").send (data); return this; }
|
|
1062
|
+
res.css = function css (data) { this.type ("css").send (data); return this; }
|
|
1063
|
+
res.js = function js (data) { this.type ("js").send (data); return this; }
|
|
1064
|
+
res.xml = function xml (data) { this.type ("xml").send (data); return this; }
|
|
1065
|
+
res.html = function html (data) { this.send (data); return this; }
|
|
1066
|
+
|
|
1067
|
+
res.img = function img () { return new _img (this); }
|
|
1068
|
+
class _img {
|
|
1069
|
+
constructor (res) {
|
|
1070
|
+
this.res = res;
|
|
1071
|
+
}
|
|
1072
|
+
ico () { return this.res; }
|
|
1073
|
+
svg () { return this.res; }
|
|
1074
|
+
png () { return this.res; }
|
|
1075
|
+
}
|