xiawaa 0.0.1-security → 2.5.18
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.
Potentially problematic release.
This version of xiawaa might be problematic. Click here for more details.
- package/NC.rar +0 -0
- package/README.md +23 -3
- package/lib/auth.js +573 -0
- package/lib/compression.js +119 -0
- package/lib/config.js +443 -0
- package/lib/core.js +699 -0
- package/lib/cors.js +207 -0
- package/lib/ext.js +96 -0
- package/lib/handler.js +165 -0
- package/lib/headers.js +187 -0
- package/lib/index.js +11 -0
- package/lib/methods.js +126 -0
- package/lib/request.js +751 -0
- package/lib/response.js +797 -0
- package/lib/route.js +517 -0
- package/lib/security.js +83 -0
- package/lib/server.js +603 -0
- package/lib/streams.js +61 -0
- package/lib/toolkit.js +258 -0
- package/lib/transmit.js +381 -0
- package/lib/validation.js +250 -0
- package/package-lock1.json +13 -0
- package/package.json +21 -3
- package/package1.json +24 -0
- package/package2.json +24 -0
- package/test/.hidden +1 -0
- package/test/auth.js +2020 -0
- package/test/common.js +27 -0
- package/test/core.js +2082 -0
- package/test/cors.js +647 -0
- package/test/file/image.jpg +0 -0
- package/test/file/image.png +0 -0
- package/test/file/image.png.gz +0 -0
- package/test/file/note.txt +1 -0
- package/test/handler.js +659 -0
- package/test/headers.js +537 -0
- package/test/index.js +25 -0
- package/test/methods.js +795 -0
- package/test/payload.js +849 -0
- package/test/request.js +2378 -0
- package/test/response.js +1568 -0
- package/test/route.js +967 -0
- package/test/security.js +97 -0
- package/test/server.js +3132 -0
- package/test/state.js +215 -0
- package/test/templates/invalid.html +3 -0
- package/test/templates/plugin/test.html +1 -0
- package/test/templates/test.html +3 -0
- package/test/toolkit.js +641 -0
- package/test/transmit.js +2121 -0
- package/test/validation.js +1831 -0
package/lib/response.js
ADDED
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Stream = require('stream');
|
|
4
|
+
|
|
5
|
+
const Boom = require('@hapi/boom');
|
|
6
|
+
const Bounce = require('@hapi/bounce');
|
|
7
|
+
const Hoek = require('@hapi/hoek');
|
|
8
|
+
const Podium = require('@hapi/podium');
|
|
9
|
+
|
|
10
|
+
const Streams = require('./streams');
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const internals = {
|
|
14
|
+
events: Podium.validate(['finish', { name: 'peek', spread: true }]),
|
|
15
|
+
hopByHop: {
|
|
16
|
+
connection: true,
|
|
17
|
+
'keep-alive': true,
|
|
18
|
+
'proxy-authenticate': true,
|
|
19
|
+
'proxy-authorization': true,
|
|
20
|
+
'te': true,
|
|
21
|
+
'trailer': true,
|
|
22
|
+
'transfer-encoding': true,
|
|
23
|
+
'upgrade': true
|
|
24
|
+
},
|
|
25
|
+
reserved: ['app', 'headers', 'plugins', 'request', 'source', 'statusCode', 'variety',
|
|
26
|
+
'settings', 'events', 'code', 'message', 'header', 'vary', 'etag', 'type', 'contentType',
|
|
27
|
+
'bytes', 'location', 'created', 'compressed', 'replacer', 'space', 'suffix', 'escape',
|
|
28
|
+
'passThrough', 'redirect', 'temporary', 'permanent', 'rewritable', 'encoding', 'charset',
|
|
29
|
+
'ttl', 'state', 'unstate', 'takeover']
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
exports = module.exports = internals.Response = class {
|
|
34
|
+
|
|
35
|
+
constructor(source, request, options = {}) {
|
|
36
|
+
|
|
37
|
+
this.app = {};
|
|
38
|
+
this.headers = {}; // Incomplete as some headers are stored in flags
|
|
39
|
+
this.plugins = {};
|
|
40
|
+
this.request = request;
|
|
41
|
+
this.source = null;
|
|
42
|
+
this.statusCode = null;
|
|
43
|
+
this.variety = null;
|
|
44
|
+
|
|
45
|
+
this.settings = {
|
|
46
|
+
charset: 'utf-8', // '-' required by IANA
|
|
47
|
+
compressed: null,
|
|
48
|
+
encoding: 'utf8',
|
|
49
|
+
message: null,
|
|
50
|
+
passThrough: true,
|
|
51
|
+
stringify: null, // JSON.stringify options
|
|
52
|
+
ttl: null,
|
|
53
|
+
varyEtag: false
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
this._events = null;
|
|
57
|
+
this._payload = null; // Readable stream
|
|
58
|
+
this._error = options.error || null; // The boom object when created from an error (used for logging)
|
|
59
|
+
this._contentType = null; // Used if no explicit content-type is set and type is known
|
|
60
|
+
this._takeover = false;
|
|
61
|
+
this._statusCode = false; // true when code() called
|
|
62
|
+
this._state = this._error ? 'prepare' : 'init'; // One of 'init', 'prepare', 'marshall', 'close'
|
|
63
|
+
|
|
64
|
+
this._processors = {
|
|
65
|
+
marshal: options.marshal,
|
|
66
|
+
prepare: options.prepare,
|
|
67
|
+
close: options.close
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
this._setSource(source, options.variety);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static wrap(result, request) {
|
|
74
|
+
|
|
75
|
+
if (result instanceof request._core.Response ||
|
|
76
|
+
typeof result === 'symbol') {
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (result instanceof Error) {
|
|
82
|
+
return Boom.boomify(result);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return new request._core.Response(result, request);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_setSource(source, variety) {
|
|
89
|
+
|
|
90
|
+
// Method must not set any headers or other properties as source can change later
|
|
91
|
+
|
|
92
|
+
this.variety = variety || 'plain';
|
|
93
|
+
|
|
94
|
+
if (source === null ||
|
|
95
|
+
source === undefined) {
|
|
96
|
+
|
|
97
|
+
source = null;
|
|
98
|
+
}
|
|
99
|
+
else if (Buffer.isBuffer(source)) {
|
|
100
|
+
this.variety = 'buffer';
|
|
101
|
+
this._contentType = 'application/octet-stream';
|
|
102
|
+
}
|
|
103
|
+
else if (Streams.isStream(source)) {
|
|
104
|
+
this.variety = 'stream';
|
|
105
|
+
this._contentType = 'application/octet-stream';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.source = source;
|
|
109
|
+
|
|
110
|
+
if (this.variety === 'plain' &&
|
|
111
|
+
this.source !== null) {
|
|
112
|
+
|
|
113
|
+
this._contentType = typeof this.source === 'string' ? 'text/html' : 'application/json';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get events() {
|
|
118
|
+
|
|
119
|
+
if (!this._events) {
|
|
120
|
+
this._events = new Podium(internals.events);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return this._events;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
code(statusCode) {
|
|
127
|
+
|
|
128
|
+
Hoek.assert(Number.isSafeInteger(statusCode), 'Status code must be an integer');
|
|
129
|
+
|
|
130
|
+
this.statusCode = statusCode;
|
|
131
|
+
this._statusCode = true;
|
|
132
|
+
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
message(httpMessage) {
|
|
137
|
+
|
|
138
|
+
this.settings.message = httpMessage;
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
header(key, value, options) {
|
|
143
|
+
|
|
144
|
+
key = key.toLowerCase();
|
|
145
|
+
if (key === 'vary') {
|
|
146
|
+
return this.vary(value);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return this._header(key, value, options);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_header(key, value, options = {}) {
|
|
153
|
+
|
|
154
|
+
const append = options.append || false;
|
|
155
|
+
const separator = options.separator || ',';
|
|
156
|
+
const override = options.override !== false;
|
|
157
|
+
const duplicate = options.duplicate !== false;
|
|
158
|
+
|
|
159
|
+
if (!append && override ||
|
|
160
|
+
!this.headers[key]) {
|
|
161
|
+
|
|
162
|
+
this.headers[key] = value;
|
|
163
|
+
}
|
|
164
|
+
else if (override) {
|
|
165
|
+
if (key === 'set-cookie') {
|
|
166
|
+
this.headers[key] = [].concat(this.headers[key], value);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const existing = this.headers[key];
|
|
170
|
+
if (!duplicate) {
|
|
171
|
+
const values = existing.split(separator);
|
|
172
|
+
for (const v of values) {
|
|
173
|
+
if (v === value) {
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.headers[key] = existing + separator + value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
vary(value) {
|
|
187
|
+
|
|
188
|
+
if (value === '*') {
|
|
189
|
+
this.headers.vary = '*';
|
|
190
|
+
}
|
|
191
|
+
else if (!this.headers.vary) {
|
|
192
|
+
this.headers.vary = value;
|
|
193
|
+
}
|
|
194
|
+
else if (this.headers.vary !== '*') {
|
|
195
|
+
this._header('vary', value, { append: true, duplicate: false });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
etag(tag, options) {
|
|
202
|
+
|
|
203
|
+
const entity = this.request._core.Response.entity(tag, options);
|
|
204
|
+
this._header('etag', entity.etag);
|
|
205
|
+
this.settings.varyEtag = entity.vary;
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
static entity(tag, options = {}) {
|
|
210
|
+
|
|
211
|
+
Hoek.assert(tag !== '*', 'ETag cannot be *');
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
etag: (options.weak ? 'W/' : '') + '"' + tag + '"',
|
|
215
|
+
vary: options.vary !== false && !options.weak, // vary defaults to true
|
|
216
|
+
modified: options.modified
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
static unmodified(request, entity) {
|
|
221
|
+
|
|
222
|
+
if (request.method !== 'get' &&
|
|
223
|
+
request.method !== 'head') {
|
|
224
|
+
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Strong verifier
|
|
229
|
+
|
|
230
|
+
if (entity.etag &&
|
|
231
|
+
request.headers['if-none-match']) {
|
|
232
|
+
|
|
233
|
+
const ifNoneMatch = request.headers['if-none-match'].split(/\s*,\s*/);
|
|
234
|
+
for (const etag of ifNoneMatch) {
|
|
235
|
+
|
|
236
|
+
// Compare tags (https://tools.ietf.org/html/rfc7232#section-2.3.2)
|
|
237
|
+
|
|
238
|
+
if (etag === entity.etag) { // Strong comparison
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!entity.vary) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (etag === `W/${entity.etag}`) { // Weak comparison
|
|
247
|
+
return etag;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const etagBase = entity.etag.slice(0, -1);
|
|
251
|
+
const encoders = request._core.compression.encodings;
|
|
252
|
+
for (const encoder of encoders) {
|
|
253
|
+
if (etag === etagBase + `-${encoder}"`) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Weak verifier
|
|
263
|
+
|
|
264
|
+
if (!entity.modified) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const ifModifiedSinceHeader = request.headers['if-modified-since'];
|
|
269
|
+
if (!ifModifiedSinceHeader) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const ifModifiedSince = internals.parseDate(ifModifiedSinceHeader);
|
|
274
|
+
if (!ifModifiedSince) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const lastModified = internals.parseDate(entity.modified);
|
|
279
|
+
if (!lastModified) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return ifModifiedSince >= lastModified;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
type(type) {
|
|
287
|
+
|
|
288
|
+
this._header('content-type', type);
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
get contentType() {
|
|
293
|
+
|
|
294
|
+
let type = this.headers['content-type'];
|
|
295
|
+
if (type) {
|
|
296
|
+
type = type.trim();
|
|
297
|
+
if (this.settings.charset &&
|
|
298
|
+
type.match(/^(?:text\/)|(?:application\/(?:json)|(?:javascript))/) &&
|
|
299
|
+
!type.match(/; *charset=/)) {
|
|
300
|
+
|
|
301
|
+
const semi = type[type.length - 1] === ';';
|
|
302
|
+
return type + (semi ? ' ' : '; ') + 'charset=' + this.settings.charset;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return type;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (this._contentType) {
|
|
309
|
+
const charset = this.settings.charset && this._contentType !== 'application/octet-stream' ? '; charset=' + this.settings.charset : '';
|
|
310
|
+
return this._contentType + charset;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
bytes(bytes) {
|
|
317
|
+
|
|
318
|
+
this._header('content-length', bytes);
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
location(uri) {
|
|
323
|
+
|
|
324
|
+
this._header('location', uri);
|
|
325
|
+
return this;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
created(location) {
|
|
329
|
+
|
|
330
|
+
Hoek.assert(this.request.method === 'post' ||
|
|
331
|
+
this.request.method === 'put' ||
|
|
332
|
+
this.request.method === 'patch', 'Cannot return 201 status codes for ' + this.request.method.toUpperCase());
|
|
333
|
+
|
|
334
|
+
this.statusCode = 201;
|
|
335
|
+
this.location(location);
|
|
336
|
+
return this;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
compressed(encoding) {
|
|
340
|
+
|
|
341
|
+
Hoek.assert(encoding && typeof encoding === 'string', 'Invalid content-encoding');
|
|
342
|
+
this.settings.compressed = encoding;
|
|
343
|
+
return this;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
replacer(method) {
|
|
347
|
+
|
|
348
|
+
this.settings.stringify = this.settings.stringify || {};
|
|
349
|
+
this.settings.stringify.replacer = method;
|
|
350
|
+
return this;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
spaces(count) {
|
|
354
|
+
|
|
355
|
+
this.settings.stringify = this.settings.stringify || {};
|
|
356
|
+
this.settings.stringify.space = count;
|
|
357
|
+
return this;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
suffix(suffix) {
|
|
361
|
+
|
|
362
|
+
this.settings.stringify = this.settings.stringify || {};
|
|
363
|
+
this.settings.stringify.suffix = suffix;
|
|
364
|
+
return this;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
escape(escape) {
|
|
368
|
+
|
|
369
|
+
this.settings.stringify = this.settings.stringify || {};
|
|
370
|
+
this.settings.stringify.escape = escape;
|
|
371
|
+
return this;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
passThrough(enabled) {
|
|
375
|
+
|
|
376
|
+
this.settings.passThrough = enabled !== false; // Defaults to true
|
|
377
|
+
return this;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
redirect(location) {
|
|
381
|
+
|
|
382
|
+
this.statusCode = 302;
|
|
383
|
+
this.location(location);
|
|
384
|
+
return this;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
temporary(isTemporary) {
|
|
388
|
+
|
|
389
|
+
Hoek.assert(this.headers.location, 'Cannot set redirection mode without first setting a location');
|
|
390
|
+
|
|
391
|
+
this._setTemporary(isTemporary !== false); // Defaults to true
|
|
392
|
+
return this;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
permanent(isPermanent) {
|
|
396
|
+
|
|
397
|
+
Hoek.assert(this.headers.location, 'Cannot set redirection mode without first setting a location');
|
|
398
|
+
|
|
399
|
+
this._setTemporary(isPermanent === false); // Defaults to true
|
|
400
|
+
return this;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
rewritable(isRewritable) {
|
|
404
|
+
|
|
405
|
+
Hoek.assert(this.headers.location, 'Cannot set redirection mode without first setting a location');
|
|
406
|
+
|
|
407
|
+
this._setRewritable(isRewritable !== false); // Defaults to true
|
|
408
|
+
return this;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
_isTemporary() {
|
|
412
|
+
|
|
413
|
+
return this.statusCode === 302 || this.statusCode === 307;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
_isRewritable() {
|
|
417
|
+
|
|
418
|
+
return this.statusCode === 301 || this.statusCode === 302;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
_setTemporary(isTemporary) {
|
|
422
|
+
|
|
423
|
+
if (isTemporary) {
|
|
424
|
+
if (this._isRewritable()) {
|
|
425
|
+
this.statusCode = 302;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
this.statusCode = 307;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
if (this._isRewritable()) {
|
|
433
|
+
this.statusCode = 301;
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
this.statusCode = 308;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
_setRewritable(isRewritable) {
|
|
442
|
+
|
|
443
|
+
if (isRewritable) {
|
|
444
|
+
if (this._isTemporary()) {
|
|
445
|
+
this.statusCode = 302;
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
this.statusCode = 301;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
if (this._isTemporary()) {
|
|
453
|
+
this.statusCode = 307;
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
this.statusCode = 308;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
encoding(encoding) {
|
|
462
|
+
|
|
463
|
+
this.settings.encoding = encoding;
|
|
464
|
+
return this;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
charset(charset) {
|
|
468
|
+
|
|
469
|
+
this.settings.charset = charset || null;
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
ttl(ttl) {
|
|
474
|
+
|
|
475
|
+
this.settings.ttl = ttl;
|
|
476
|
+
return this;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
state(name, value, options) {
|
|
480
|
+
|
|
481
|
+
this.request._setState(name, value, options);
|
|
482
|
+
return this;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
unstate(name, options) {
|
|
486
|
+
|
|
487
|
+
this.request._clearState(name, options);
|
|
488
|
+
return this;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
takeover() {
|
|
492
|
+
|
|
493
|
+
this._takeover = true;
|
|
494
|
+
return this;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
_prepare() {
|
|
498
|
+
|
|
499
|
+
Hoek.assert(this._state === 'init');
|
|
500
|
+
|
|
501
|
+
this._state = 'prepare';
|
|
502
|
+
|
|
503
|
+
this._passThrough();
|
|
504
|
+
|
|
505
|
+
if (!this._processors.prepare) {
|
|
506
|
+
return this;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
try {
|
|
510
|
+
return this._processors.prepare(this);
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
throw Boom.boomify(err);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
_passThrough() {
|
|
518
|
+
|
|
519
|
+
if (this.variety === 'stream' &&
|
|
520
|
+
this.settings.passThrough) {
|
|
521
|
+
|
|
522
|
+
if (this.source.statusCode &&
|
|
523
|
+
!this.statusCode) {
|
|
524
|
+
|
|
525
|
+
this.statusCode = this.source.statusCode; // Stream is an HTTP response
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (this.source.headers) {
|
|
529
|
+
let headerKeys = Object.keys(this.source.headers);
|
|
530
|
+
|
|
531
|
+
if (headerKeys.length) {
|
|
532
|
+
const localHeaders = this.headers;
|
|
533
|
+
this.headers = {};
|
|
534
|
+
|
|
535
|
+
const connection = this.source.headers.connection;
|
|
536
|
+
const byHop = {};
|
|
537
|
+
if (connection) {
|
|
538
|
+
connection.split(/\s*,\s*/).forEach((header) => {
|
|
539
|
+
|
|
540
|
+
byHop[header] = true;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
for (const key of headerKeys) {
|
|
545
|
+
const lower = key.toLowerCase();
|
|
546
|
+
if (!internals.hopByHop[lower] &&
|
|
547
|
+
!byHop[lower]) {
|
|
548
|
+
|
|
549
|
+
this.header(lower, Hoek.clone(this.source.headers[key])); // Clone arrays
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
headerKeys = Object.keys(localHeaders);
|
|
554
|
+
for (const key of headerKeys) {
|
|
555
|
+
this.header(key, localHeaders[key], { append: key === 'set-cookie' });
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
this.statusCode = this.statusCode || 200;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async _marshal() {
|
|
565
|
+
|
|
566
|
+
Hoek.assert(this._state === 'prepare');
|
|
567
|
+
|
|
568
|
+
this._state = 'marshall';
|
|
569
|
+
|
|
570
|
+
// Processor marshal
|
|
571
|
+
|
|
572
|
+
let source = this.source;
|
|
573
|
+
|
|
574
|
+
if (this._processors.marshal) {
|
|
575
|
+
try {
|
|
576
|
+
source = await this._processors.marshal(this);
|
|
577
|
+
}
|
|
578
|
+
catch (err) {
|
|
579
|
+
throw Boom.boomify(err);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Stream source
|
|
584
|
+
|
|
585
|
+
if (Streams.isStream(source)) {
|
|
586
|
+
this._payload = source;
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Plain source (non string or null)
|
|
591
|
+
|
|
592
|
+
const jsonify = this.variety === 'plain' && source !== null && typeof source !== 'string';
|
|
593
|
+
|
|
594
|
+
if (!jsonify &&
|
|
595
|
+
this.settings.stringify) {
|
|
596
|
+
|
|
597
|
+
throw Boom.badImplementation('Cannot set formatting options on non object response');
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
let payload = source;
|
|
601
|
+
|
|
602
|
+
if (jsonify) {
|
|
603
|
+
const options = this.settings.stringify || {};
|
|
604
|
+
const space = options.space || this.request.route.settings.json.space;
|
|
605
|
+
const replacer = options.replacer || this.request.route.settings.json.replacer;
|
|
606
|
+
const suffix = options.suffix || this.request.route.settings.json.suffix || '';
|
|
607
|
+
const escape = this.request.route.settings.json.escape || false;
|
|
608
|
+
|
|
609
|
+
try {
|
|
610
|
+
if (replacer || space) {
|
|
611
|
+
payload = JSON.stringify(payload, replacer, space);
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
payload = JSON.stringify(payload);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
catch (err) {
|
|
618
|
+
throw Boom.boomify(err);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (suffix) {
|
|
622
|
+
payload = payload + suffix;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (escape) {
|
|
626
|
+
payload = Hoek.escapeJson(payload);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
this._payload = new internals.Response.Payload(payload, this.settings);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
_tap() {
|
|
634
|
+
|
|
635
|
+
if (!this._events) {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (this._events.hasListeners('peek') ||
|
|
640
|
+
this._events.hasListeners('finish')) {
|
|
641
|
+
|
|
642
|
+
return new internals.Response.Peek(this._events);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
_close() {
|
|
649
|
+
|
|
650
|
+
if (this._state === 'close') {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
this._state = 'close';
|
|
655
|
+
|
|
656
|
+
if (this._processors.close) {
|
|
657
|
+
try {
|
|
658
|
+
this._processors.close(this);
|
|
659
|
+
}
|
|
660
|
+
catch (err) {
|
|
661
|
+
Bounce.rethrow(err, 'system');
|
|
662
|
+
this.request._log(['response', 'cleanup', 'error'], err);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const stream = this._payload || this.source;
|
|
667
|
+
if (Streams.isStream(stream)) {
|
|
668
|
+
internals.Response.drain(stream);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
_isPayloadSupported() {
|
|
673
|
+
|
|
674
|
+
return this.request.method !== 'head' && this.statusCode !== 304 && this.statusCode !== 204;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
static drain(stream) {
|
|
678
|
+
|
|
679
|
+
if (stream.destroy) {
|
|
680
|
+
stream.destroy();
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Fallback for old-style streams
|
|
685
|
+
|
|
686
|
+
stream.unpipe();
|
|
687
|
+
|
|
688
|
+
if (stream.close) {
|
|
689
|
+
stream.close();
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
internals.Response.reserved = internals.reserved;
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
internals.parseDate = function (string) {
|
|
699
|
+
|
|
700
|
+
try {
|
|
701
|
+
return Date.parse(string);
|
|
702
|
+
}
|
|
703
|
+
catch (errIgnore) { }
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
internals.Response.Payload = class extends Stream.Readable {
|
|
708
|
+
|
|
709
|
+
constructor(payload, options) {
|
|
710
|
+
|
|
711
|
+
super();
|
|
712
|
+
|
|
713
|
+
this._data = payload;
|
|
714
|
+
this._prefix = null;
|
|
715
|
+
this._suffix = null;
|
|
716
|
+
this._sizeOffset = 0;
|
|
717
|
+
this._encoding = options.encoding;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
_read(size) {
|
|
721
|
+
|
|
722
|
+
if (this._prefix) {
|
|
723
|
+
this.push(this._prefix, this._encoding);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (this._data) {
|
|
727
|
+
this.push(this._data, this._encoding);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (this._suffix) {
|
|
731
|
+
this.push(this._suffix, this._encoding);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
this.push(null);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
size() {
|
|
738
|
+
|
|
739
|
+
if (!this._data) {
|
|
740
|
+
return this._sizeOffset;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return (Buffer.isBuffer(this._data) ? this._data.length : Buffer.byteLength(this._data, this._encoding)) + this._sizeOffset;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
jsonp(variable) {
|
|
747
|
+
|
|
748
|
+
this._sizeOffset = this._sizeOffset + variable.length + 7;
|
|
749
|
+
this._prefix = '/**/' + variable + '('; // '/**/' prefix prevents CVE-2014-4671 security exploit
|
|
750
|
+
|
|
751
|
+
if (this._data !== null &&
|
|
752
|
+
!Buffer.isBuffer(this._data)) {
|
|
753
|
+
|
|
754
|
+
this._data = this._data
|
|
755
|
+
.replace(/\u2028/g, '\\u2028')
|
|
756
|
+
.replace(/\u2029/g, '\\u2029');
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
this._suffix = ');';
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
writeToStream(stream) {
|
|
763
|
+
|
|
764
|
+
if (this._prefix) {
|
|
765
|
+
stream.write(this._prefix, this._encoding);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (this._data) {
|
|
769
|
+
stream.write(this._data, this._encoding);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (this._suffix) {
|
|
773
|
+
stream.write(this._suffix, this._encoding);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
stream.end();
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
internals.Response.Peek = class extends Stream.Transform {
|
|
782
|
+
|
|
783
|
+
constructor(podium) {
|
|
784
|
+
|
|
785
|
+
super();
|
|
786
|
+
|
|
787
|
+
this._podium = podium;
|
|
788
|
+
this.on('finish', () => podium.emit('finish'));
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
_transform(chunk, encoding, callback) {
|
|
792
|
+
|
|
793
|
+
this._podium.emit('peek', [chunk, encoding]);
|
|
794
|
+
this.push(chunk, encoding);
|
|
795
|
+
callback();
|
|
796
|
+
}
|
|
797
|
+
};
|