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/test/payload.js
ADDED
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Fs = require('fs');
|
|
4
|
+
const Http = require('http');
|
|
5
|
+
const Path = require('path');
|
|
6
|
+
const Zlib = require('zlib');
|
|
7
|
+
|
|
8
|
+
const Code = require('@hapi/code');
|
|
9
|
+
const Hapi = require('..');
|
|
10
|
+
const Hoek = require('@hapi/hoek');
|
|
11
|
+
const Lab = require('@hapi/lab');
|
|
12
|
+
const Wreck = require('@hapi/wreck');
|
|
13
|
+
|
|
14
|
+
const Common = require('./common');
|
|
15
|
+
|
|
16
|
+
const internals = {};
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const { describe, it, before } = exports.lab = Lab.script();
|
|
20
|
+
const expect = Code.expect;
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
describe('Payload', () => {
|
|
24
|
+
|
|
25
|
+
before(Common.setDefaultDnsOrder);
|
|
26
|
+
|
|
27
|
+
it('sets payload', async () => {
|
|
28
|
+
|
|
29
|
+
const payload = '{"x":"1","y":"2","z":"3"}';
|
|
30
|
+
|
|
31
|
+
const handler = (request) => {
|
|
32
|
+
|
|
33
|
+
expect(request.payload).to.exist();
|
|
34
|
+
expect(request.payload.z).to.equal('3');
|
|
35
|
+
expect(request.mime).to.equal('application/json');
|
|
36
|
+
return request.payload;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const server = Hapi.server();
|
|
40
|
+
server.route({ method: 'POST', path: '/', options: { handler } });
|
|
41
|
+
|
|
42
|
+
const res = await server.inject({ method: 'POST', url: '/', payload });
|
|
43
|
+
expect(res.result).to.exist();
|
|
44
|
+
expect(res.result.x).to.equal('1');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('handles request socket error', async () => {
|
|
48
|
+
|
|
49
|
+
let called = false;
|
|
50
|
+
const handler = function () {
|
|
51
|
+
|
|
52
|
+
called = true;
|
|
53
|
+
return null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const server = Hapi.server();
|
|
57
|
+
server.route({ method: 'POST', path: '/', options: { handler } });
|
|
58
|
+
|
|
59
|
+
const res = await server.inject({ method: 'POST', url: '/', payload: 'test', simulate: { error: true, end: false } });
|
|
60
|
+
expect(res.result).to.exist();
|
|
61
|
+
expect(res.result.statusCode).to.equal(500);
|
|
62
|
+
expect(called).to.be.false();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('handles request socket close', async () => {
|
|
66
|
+
|
|
67
|
+
const handler = function () {
|
|
68
|
+
|
|
69
|
+
throw new Error('never called');
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const server = Hapi.server();
|
|
73
|
+
server.route({ method: 'POST', path: '/', options: { handler } });
|
|
74
|
+
|
|
75
|
+
const responded = server.ext('onPostResponse');
|
|
76
|
+
|
|
77
|
+
server.inject({ method: 'POST', url: '/', payload: 'test', simulate: { close: true, end: false } });
|
|
78
|
+
const request = await responded;
|
|
79
|
+
expect(request._isReplied).to.equal(true);
|
|
80
|
+
expect(request.response.output.statusCode).to.equal(500);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('handles aborted request mid-lifecycle step', async (flags) => {
|
|
84
|
+
|
|
85
|
+
let req = null;
|
|
86
|
+
const server = Hapi.server();
|
|
87
|
+
|
|
88
|
+
server.route({
|
|
89
|
+
method: 'GET',
|
|
90
|
+
path: '/',
|
|
91
|
+
handler: async (request) => {
|
|
92
|
+
|
|
93
|
+
req.destroy();
|
|
94
|
+
|
|
95
|
+
await request.events.once('disconnect');
|
|
96
|
+
|
|
97
|
+
return 'ok';
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Register post handler that should not be called
|
|
102
|
+
|
|
103
|
+
let post = 0;
|
|
104
|
+
server.ext('onPostHandler', () => {
|
|
105
|
+
|
|
106
|
+
++post;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
flags.onCleanup = () => server.stop();
|
|
110
|
+
await server.start();
|
|
111
|
+
|
|
112
|
+
req = Http.request({
|
|
113
|
+
hostname: 'localhost',
|
|
114
|
+
port: server.info.port,
|
|
115
|
+
method: 'get'
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
req.on('error', Hoek.ignore);
|
|
119
|
+
req.end();
|
|
120
|
+
|
|
121
|
+
const [request] = await server.events.once('response');
|
|
122
|
+
|
|
123
|
+
expect(request.response.isBoom).to.be.true();
|
|
124
|
+
expect(request.response.output.statusCode).to.equal(499);
|
|
125
|
+
expect(request.info.completed).to.be.above(0);
|
|
126
|
+
expect(request.info.responded).to.equal(0);
|
|
127
|
+
|
|
128
|
+
expect(post).to.equal(0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('handles aborted request', { retry: true }, async () => {
|
|
132
|
+
|
|
133
|
+
const server = Hapi.server();
|
|
134
|
+
server.route({ method: 'POST', path: '/', options: { handler: () => 'Success', payload: { parse: false } } });
|
|
135
|
+
|
|
136
|
+
const log = server.events.once('log');
|
|
137
|
+
|
|
138
|
+
await server.start();
|
|
139
|
+
|
|
140
|
+
const options = {
|
|
141
|
+
hostname: 'localhost',
|
|
142
|
+
port: server.info.port,
|
|
143
|
+
path: '/',
|
|
144
|
+
method: 'POST',
|
|
145
|
+
headers: {
|
|
146
|
+
'Content-Length': '10'
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const req = Http.request(options, (res) => { });
|
|
151
|
+
req.on('error', Hoek.ignore);
|
|
152
|
+
req.write('Hello\n');
|
|
153
|
+
setTimeout(() => req.abort(), 50);
|
|
154
|
+
|
|
155
|
+
const [event] = await log;
|
|
156
|
+
expect(event.error.message).to.equal('Parse Error');
|
|
157
|
+
await server.stop({ timeout: 10 });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('errors when payload too big', async () => {
|
|
161
|
+
|
|
162
|
+
const payload = '{"x":"1","y":"2","z":"3"}';
|
|
163
|
+
|
|
164
|
+
const server = Hapi.server();
|
|
165
|
+
server.route({ method: 'POST', path: '/', options: { handler: () => null, payload: { maxBytes: 10 } } });
|
|
166
|
+
|
|
167
|
+
const res = await server.inject({ method: 'POST', url: '/', payload, headers: { 'content-length': payload.length } });
|
|
168
|
+
expect(res.statusCode).to.equal(413);
|
|
169
|
+
expect(res.result).to.exist();
|
|
170
|
+
expect(res.result.message).to.equal('Payload content length greater than maximum allowed: 10');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('errors when payload too big (implicit length)', async () => {
|
|
174
|
+
|
|
175
|
+
const payload = '{"x":"1","y":"2","z":"3"}';
|
|
176
|
+
|
|
177
|
+
const server = Hapi.server();
|
|
178
|
+
server.route({ method: 'POST', path: '/', options: { handler: () => null, payload: { maxBytes: 10 } } });
|
|
179
|
+
|
|
180
|
+
const res = await server.inject({ method: 'POST', url: '/', payload });
|
|
181
|
+
expect(res.statusCode).to.equal(413);
|
|
182
|
+
expect(res.result).to.exist();
|
|
183
|
+
expect(res.result.message).to.equal('Payload content length greater than maximum allowed: 10');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('errors when payload too big (file)', async () => {
|
|
187
|
+
|
|
188
|
+
const payload = '{"x":"1","y":"2","z":"3"}';
|
|
189
|
+
|
|
190
|
+
const server = Hapi.server();
|
|
191
|
+
server.route({ method: 'POST', path: '/', options: { handler: () => null, payload: { output: 'file', maxBytes: 10 } } });
|
|
192
|
+
|
|
193
|
+
const res = await server.inject({ method: 'POST', url: '/', payload, headers: { 'content-length': payload.length } });
|
|
194
|
+
expect(res.statusCode).to.equal(413);
|
|
195
|
+
expect(res.result).to.exist();
|
|
196
|
+
expect(res.result.message).to.equal('Payload content length greater than maximum allowed: 10');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('errors when payload too big (file implicit length)', async () => {
|
|
200
|
+
|
|
201
|
+
const payload = '{"x":"1","y":"2","z":"3"}';
|
|
202
|
+
|
|
203
|
+
const server = Hapi.server();
|
|
204
|
+
server.route({ method: 'POST', path: '/', options: { handler: () => null, payload: { output: 'file', maxBytes: 10 } } });
|
|
205
|
+
|
|
206
|
+
const res = await server.inject({ method: 'POST', url: '/', payload });
|
|
207
|
+
expect(res.statusCode).to.equal(413);
|
|
208
|
+
expect(res.result).to.exist();
|
|
209
|
+
expect(res.result.message).to.equal('Payload content length greater than maximum allowed: 10');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('errors when payload contains prototype poisoning', async () => {
|
|
213
|
+
|
|
214
|
+
const server = Hapi.server();
|
|
215
|
+
server.route({ method: 'POST', path: '/', handler: (request) => request.payload.x });
|
|
216
|
+
|
|
217
|
+
const payload = '{"x":"1","y":"2","z":"3","__proto__":{"x":"4"}}';
|
|
218
|
+
const res = await server.inject({ method: 'POST', url: '/', payload });
|
|
219
|
+
expect(res.statusCode).to.equal(400);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('ignores when payload contains prototype poisoning', async () => {
|
|
223
|
+
|
|
224
|
+
const server = Hapi.server();
|
|
225
|
+
server.route({
|
|
226
|
+
method: 'POST',
|
|
227
|
+
path: '/',
|
|
228
|
+
options: {
|
|
229
|
+
payload: {
|
|
230
|
+
protoAction: 'ignore'
|
|
231
|
+
},
|
|
232
|
+
handler: (request) => request.payload.__proto__
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const payload = '{"x":"1","y":"2","z":"3","__proto__":{"x":"4"}}';
|
|
237
|
+
const res = await server.inject({ method: 'POST', url: '/', payload });
|
|
238
|
+
expect(res.statusCode).to.equal(200);
|
|
239
|
+
expect(res.result).to.equal({ x: '4' });
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('sanitizes when payload contains prototype poisoning', async () => {
|
|
243
|
+
|
|
244
|
+
const server = Hapi.server();
|
|
245
|
+
server.route({
|
|
246
|
+
method: 'POST',
|
|
247
|
+
path: '/',
|
|
248
|
+
options: {
|
|
249
|
+
payload: {
|
|
250
|
+
protoAction: 'remove'
|
|
251
|
+
},
|
|
252
|
+
handler: (request) => request.payload.__proto__
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const payload = '{"x":"1","y":"2","z":"3","__proto__":{"x":"4"}}';
|
|
257
|
+
const res = await server.inject({ method: 'POST', url: '/', payload });
|
|
258
|
+
expect(res.statusCode).to.equal(200);
|
|
259
|
+
expect(res.result).to.equal({});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('returns 413 with response when payload is not consumed', async () => {
|
|
263
|
+
|
|
264
|
+
const payload = Buffer.alloc(10 * 1024 * 1024).toString();
|
|
265
|
+
|
|
266
|
+
const server = Hapi.server();
|
|
267
|
+
server.route({ method: 'POST', path: '/', options: { handler: () => null, payload: { maxBytes: 1024 * 1024 } } });
|
|
268
|
+
|
|
269
|
+
await server.start();
|
|
270
|
+
|
|
271
|
+
const uri = 'http://localhost:' + server.info.port;
|
|
272
|
+
const err = await expect(Wreck.post(uri, { payload })).to.reject();
|
|
273
|
+
expect(err.data.res.statusCode).to.equal(413);
|
|
274
|
+
expect(err.data.payload.toString()).to.equal('{"statusCode":413,"error":"Request Entity Too Large","message":"Payload content length greater than maximum allowed: 1048576"}');
|
|
275
|
+
|
|
276
|
+
await server.stop();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('handles expect 100-continue', async () => {
|
|
280
|
+
|
|
281
|
+
const server = Hapi.server();
|
|
282
|
+
server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
|
|
283
|
+
|
|
284
|
+
await server.start();
|
|
285
|
+
|
|
286
|
+
const uri = 'http://localhost:' + server.info.port;
|
|
287
|
+
const { res, payload } = await Wreck.post(uri, { payload: { hello: true }, headers: { expect: '100-continue' } });
|
|
288
|
+
expect(res.statusCode).to.equal(200);
|
|
289
|
+
expect(payload.toString()).to.equal('{"hello":true}');
|
|
290
|
+
|
|
291
|
+
await server.stop();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('peeks at unparsed data', async () => {
|
|
295
|
+
|
|
296
|
+
let data = null;
|
|
297
|
+
const ext = (request, h) => {
|
|
298
|
+
|
|
299
|
+
const chunks = [];
|
|
300
|
+
request.events.on('peek', (chunk, encoding) => {
|
|
301
|
+
|
|
302
|
+
chunks.push(chunk);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
request.events.once('finish', () => {
|
|
306
|
+
|
|
307
|
+
data = Buffer.concat(chunks);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return h.continue;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const server = Hapi.server();
|
|
314
|
+
server.ext('onRequest', ext);
|
|
315
|
+
server.route({ method: 'POST', path: '/', options: { handler: () => data, payload: { parse: false } } });
|
|
316
|
+
|
|
317
|
+
const payload = '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789';
|
|
318
|
+
const res = await server.inject({ method: 'POST', url: '/', payload });
|
|
319
|
+
expect(res.result).to.equal(payload);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('peeks at unparsed data (finish only)', async () => {
|
|
323
|
+
|
|
324
|
+
let peeked = false;
|
|
325
|
+
const ext = (request, h) => {
|
|
326
|
+
|
|
327
|
+
request.events.once('finish', () => {
|
|
328
|
+
|
|
329
|
+
peeked = true;
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
return h.continue;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const server = Hapi.server();
|
|
336
|
+
server.ext('onRequest', ext);
|
|
337
|
+
server.route({ method: 'POST', path: '/', options: { handler: () => null, payload: { parse: false } } });
|
|
338
|
+
|
|
339
|
+
const payload = '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789';
|
|
340
|
+
await server.inject({ method: 'POST', url: '/', payload });
|
|
341
|
+
expect(peeked).to.be.true();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('handles gzipped payload', async () => {
|
|
345
|
+
|
|
346
|
+
const message = { 'msg': 'This message is going to be gzipped.' };
|
|
347
|
+
const server = Hapi.server();
|
|
348
|
+
server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
|
|
349
|
+
|
|
350
|
+
const compressed = await new Promise((resolve) => Zlib.gzip(JSON.stringify(message), (ignore, result) => resolve(result)));
|
|
351
|
+
|
|
352
|
+
const request = {
|
|
353
|
+
method: 'POST',
|
|
354
|
+
url: '/',
|
|
355
|
+
headers: {
|
|
356
|
+
'content-type': 'application/json',
|
|
357
|
+
'content-encoding': 'gzip',
|
|
358
|
+
'content-length': compressed.length
|
|
359
|
+
},
|
|
360
|
+
payload: compressed
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const res = await server.inject(request);
|
|
364
|
+
expect(res.result).to.exist();
|
|
365
|
+
expect(res.result).to.equal(message);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('handles deflated payload', async () => {
|
|
369
|
+
|
|
370
|
+
const message = { 'msg': 'This message is going to be gzipped.' };
|
|
371
|
+
const server = Hapi.server();
|
|
372
|
+
server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
|
|
373
|
+
|
|
374
|
+
const compressed = await new Promise((resolve) => Zlib.deflate(JSON.stringify(message), (ignore, result) => resolve(result)));
|
|
375
|
+
|
|
376
|
+
const request = {
|
|
377
|
+
method: 'POST',
|
|
378
|
+
url: '/',
|
|
379
|
+
headers: {
|
|
380
|
+
'content-type': 'application/json',
|
|
381
|
+
'content-encoding': 'deflate',
|
|
382
|
+
'content-length': compressed.length
|
|
383
|
+
},
|
|
384
|
+
payload: compressed
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const res = await server.inject(request);
|
|
388
|
+
expect(res.result).to.exist();
|
|
389
|
+
expect(res.result).to.equal(message);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('handles custom compression', async () => {
|
|
393
|
+
|
|
394
|
+
const message = { 'msg': 'This message is going to be gzipped.' };
|
|
395
|
+
const server = Hapi.server({ routes: { payload: { compression: { test: { some: 'options' } } } } });
|
|
396
|
+
|
|
397
|
+
const decoder = (options) => {
|
|
398
|
+
|
|
399
|
+
expect(options).to.equal({ some: 'options' });
|
|
400
|
+
return Zlib.createGunzip();
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
server.decoder('test', decoder);
|
|
404
|
+
server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
|
|
405
|
+
|
|
406
|
+
const compressed = await new Promise((resolve) => Zlib.gzip(JSON.stringify(message), (ignore, result) => resolve(result)));
|
|
407
|
+
|
|
408
|
+
const request = {
|
|
409
|
+
method: 'POST',
|
|
410
|
+
url: '/',
|
|
411
|
+
headers: {
|
|
412
|
+
'content-type': 'application/json',
|
|
413
|
+
'content-encoding': 'test',
|
|
414
|
+
'content-length': compressed.length
|
|
415
|
+
},
|
|
416
|
+
payload: compressed
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const res = await server.inject(request);
|
|
420
|
+
expect(res.result).to.exist();
|
|
421
|
+
expect(res.result).to.equal(message);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('saves a file after content decoding', async () => {
|
|
425
|
+
|
|
426
|
+
const path = Path.join(__dirname, './file/image.jpg');
|
|
427
|
+
const sourceContents = Fs.readFileSync(path);
|
|
428
|
+
const stats = Fs.statSync(path);
|
|
429
|
+
|
|
430
|
+
const handler = (request) => {
|
|
431
|
+
|
|
432
|
+
const receivedContents = Fs.readFileSync(request.payload.path);
|
|
433
|
+
Fs.unlinkSync(request.payload.path);
|
|
434
|
+
expect(receivedContents).to.equal(sourceContents);
|
|
435
|
+
return request.payload.bytes;
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const compressed = await new Promise((resolve) => Zlib.gzip(sourceContents, (ignore, result) => resolve(result)));
|
|
439
|
+
const server = Hapi.server();
|
|
440
|
+
server.route({ method: 'POST', path: '/file', options: { handler, payload: { output: 'file' } } });
|
|
441
|
+
const res = await server.inject({ method: 'POST', url: '/file', payload: compressed, headers: { 'content-encoding': 'gzip' } });
|
|
442
|
+
expect(res.result).to.equal(stats.size);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('errors saving a file without parse', async () => {
|
|
446
|
+
|
|
447
|
+
const server = Hapi.server();
|
|
448
|
+
server.route({ method: 'POST', path: '/file', options: { handler: Hoek.block, payload: { output: 'file', parse: false, uploads: '/a/b/c/d/not' } } });
|
|
449
|
+
const res = await server.inject({ method: 'POST', url: '/file', payload: 'abcde' });
|
|
450
|
+
expect(res.statusCode).to.equal(500);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('sets parse mode when route method is * and request is POST', async () => {
|
|
454
|
+
|
|
455
|
+
const server = Hapi.server();
|
|
456
|
+
server.route({ method: '*', path: '/any', handler: (request) => request.payload.key });
|
|
457
|
+
|
|
458
|
+
const res = await server.inject({ url: '/any', method: 'POST', payload: { key: '09876' } });
|
|
459
|
+
expect(res.statusCode).to.equal(200);
|
|
460
|
+
expect(res.result).to.equal('09876');
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('returns an error on unsupported mime type', async () => {
|
|
464
|
+
|
|
465
|
+
const server = Hapi.server();
|
|
466
|
+
server.route({ method: 'POST', path: '/', handler: (request) => request.payload.key });
|
|
467
|
+
await server.start();
|
|
468
|
+
|
|
469
|
+
const options = {
|
|
470
|
+
headers: {
|
|
471
|
+
'Content-Type': 'application/unknown',
|
|
472
|
+
'Content-Length': '18'
|
|
473
|
+
},
|
|
474
|
+
payload: '{ "key": "value" }'
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const err = await expect(Wreck.post(`http://localhost:${server.info.port}/?x=4`, options)).to.reject();
|
|
478
|
+
expect(err.output.statusCode).to.equal(415);
|
|
479
|
+
await server.stop({ timeout: 1 });
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('ignores unsupported mime type', async () => {
|
|
483
|
+
|
|
484
|
+
const server = Hapi.server();
|
|
485
|
+
server.route({ method: 'POST', path: '/', options: { handler: (request) => request.payload, payload: { failAction: 'ignore' } } });
|
|
486
|
+
|
|
487
|
+
const res = await server.inject({ method: 'POST', url: '/', payload: 'testing123', headers: { 'content-type': 'application/unknown' } });
|
|
488
|
+
expect(res.statusCode).to.equal(204);
|
|
489
|
+
expect(res.result).to.equal(null);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('returns 200 on octet mime type', async () => {
|
|
493
|
+
|
|
494
|
+
const server = Hapi.server();
|
|
495
|
+
server.route({ method: 'POST', path: '/', handler: () => 'ok' });
|
|
496
|
+
|
|
497
|
+
const res = await server.inject({ method: 'POST', url: '/', payload: 'testing123', headers: { 'content-type': 'application/octet-stream' } });
|
|
498
|
+
expect(res.statusCode).to.equal(200);
|
|
499
|
+
expect(res.result).to.equal('ok');
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('returns 200 on text mime type', async () => {
|
|
503
|
+
|
|
504
|
+
const handler = (request) => {
|
|
505
|
+
|
|
506
|
+
return request.payload + '+456';
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const server = Hapi.server();
|
|
510
|
+
server.route({ method: 'POST', path: '/text', handler });
|
|
511
|
+
|
|
512
|
+
const res = await server.inject({ method: 'POST', url: '/text', payload: 'testing123', headers: { 'content-type': 'text/plain' } });
|
|
513
|
+
expect(res.statusCode).to.equal(200);
|
|
514
|
+
expect(res.result).to.equal('testing123+456');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('returns 200 on override mime type', async () => {
|
|
518
|
+
|
|
519
|
+
const server = Hapi.server();
|
|
520
|
+
server.route({ method: 'POST', path: '/override', options: { handler: (request) => request.payload.key, payload: { override: 'application/json' } } });
|
|
521
|
+
|
|
522
|
+
const res = await server.inject({ method: 'POST', url: '/override', payload: '{"key":"cool"}', headers: { 'content-type': 'text/plain' } });
|
|
523
|
+
expect(res.statusCode).to.equal(200);
|
|
524
|
+
expect(res.result).to.equal('cool');
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('returns 200 on text mime type when allowed', async () => {
|
|
528
|
+
|
|
529
|
+
const handler = (request) => {
|
|
530
|
+
|
|
531
|
+
return request.payload + '+456';
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
const server = Hapi.server();
|
|
535
|
+
server.route({ method: 'POST', path: '/textOnly', options: { handler, payload: { allow: 'text/plain' } } });
|
|
536
|
+
|
|
537
|
+
const res = await server.inject({ method: 'POST', url: '/textOnly', payload: 'testing123', headers: { 'content-type': 'text/plain' } });
|
|
538
|
+
expect(res.statusCode).to.equal(200);
|
|
539
|
+
expect(res.result).to.equal('testing123+456');
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('returns 415 on non text mime type when disallowed', async () => {
|
|
543
|
+
|
|
544
|
+
const handler = (request) => {
|
|
545
|
+
|
|
546
|
+
return request.payload + '+456';
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const server = Hapi.server();
|
|
550
|
+
server.route({ method: 'POST', path: '/textOnly', options: { handler, payload: { allow: 'text/plain' } } });
|
|
551
|
+
|
|
552
|
+
const res = await server.inject({ method: 'POST', url: '/textOnly', payload: 'testing123', headers: { 'content-type': 'application/octet-stream' } });
|
|
553
|
+
expect(res.statusCode).to.equal(415);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it('returns 200 on text mime type when allowed (array)', async () => {
|
|
557
|
+
|
|
558
|
+
const handler = (request) => {
|
|
559
|
+
|
|
560
|
+
return request.payload + '+456';
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
const server = Hapi.server();
|
|
564
|
+
server.route({ method: 'POST', path: '/textOnlyArray', options: { handler, payload: { allow: ['text/plain'] } } });
|
|
565
|
+
|
|
566
|
+
const res = await server.inject({ method: 'POST', url: '/textOnlyArray', payload: 'testing123', headers: { 'content-type': 'text/plain' } });
|
|
567
|
+
expect(res.statusCode).to.equal(200);
|
|
568
|
+
expect(res.result).to.equal('testing123+456');
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('returns 415 on non text mime type when disallowed (array)', async () => {
|
|
572
|
+
|
|
573
|
+
const handler = (request) => {
|
|
574
|
+
|
|
575
|
+
return request.payload + '+456';
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const server = Hapi.server();
|
|
579
|
+
server.route({ method: 'POST', path: '/textOnlyArray', options: { handler, payload: { allow: ['text/plain'] } } });
|
|
580
|
+
|
|
581
|
+
const res = await server.inject({ method: 'POST', url: '/textOnlyArray', payload: 'testing123', headers: { 'content-type': 'application/octet-stream' } });
|
|
582
|
+
expect(res.statusCode).to.equal(415);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it('returns parsed multipart data (route)', async () => {
|
|
586
|
+
|
|
587
|
+
const multipartPayload =
|
|
588
|
+
'--AaB03x\r\n' +
|
|
589
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
590
|
+
'\r\n' +
|
|
591
|
+
'First\r\n' +
|
|
592
|
+
'--AaB03x\r\n' +
|
|
593
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
594
|
+
'\r\n' +
|
|
595
|
+
'Second\r\n' +
|
|
596
|
+
'--AaB03x\r\n' +
|
|
597
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
598
|
+
'\r\n' +
|
|
599
|
+
'Third\r\n' +
|
|
600
|
+
'--AaB03x\r\n' +
|
|
601
|
+
'content-disposition: form-data; name="field1"\r\n' +
|
|
602
|
+
'\r\n' +
|
|
603
|
+
'Joe Blow\r\nalmost tricked you!\r\n' +
|
|
604
|
+
'--AaB03x\r\n' +
|
|
605
|
+
'content-disposition: form-data; name="field1"\r\n' +
|
|
606
|
+
'\r\n' +
|
|
607
|
+
'Repeated name segment\r\n' +
|
|
608
|
+
'--AaB03x\r\n' +
|
|
609
|
+
'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' +
|
|
610
|
+
'Content-Type: text/plain\r\n' +
|
|
611
|
+
'\r\n' +
|
|
612
|
+
'... contents of file1.txt ...\r\r\n' +
|
|
613
|
+
'--AaB03x--\r\n';
|
|
614
|
+
|
|
615
|
+
const handler = (request) => {
|
|
616
|
+
|
|
617
|
+
const result = {};
|
|
618
|
+
const keys = Object.keys(request.payload);
|
|
619
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
620
|
+
const key = keys[i];
|
|
621
|
+
const value = request.payload[key];
|
|
622
|
+
result[key] = value._readableState ? true : value;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return result;
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const server = Hapi.server();
|
|
629
|
+
server.route({ method: 'POST', path: '/echo', handler, options: { payload: { multipart: true } } });
|
|
630
|
+
|
|
631
|
+
const res = await server.inject({ method: 'POST', url: '/echo', payload: multipartPayload, headers: { 'content-type': 'multipart/form-data; boundary=AaB03x' } });
|
|
632
|
+
expect(Object.keys(res.result).length).to.equal(3);
|
|
633
|
+
expect(res.result.field1).to.exist();
|
|
634
|
+
expect(res.result.field1.length).to.equal(2);
|
|
635
|
+
expect(res.result.field1[1]).to.equal('Repeated name segment');
|
|
636
|
+
expect(res.result.pics).to.exist();
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('returns parsed multipart data (server)', async () => {
|
|
640
|
+
|
|
641
|
+
const multipartPayload =
|
|
642
|
+
'--AaB03x\r\n' +
|
|
643
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
644
|
+
'\r\n' +
|
|
645
|
+
'First\r\n' +
|
|
646
|
+
'--AaB03x\r\n' +
|
|
647
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
648
|
+
'\r\n' +
|
|
649
|
+
'Second\r\n' +
|
|
650
|
+
'--AaB03x\r\n' +
|
|
651
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
652
|
+
'\r\n' +
|
|
653
|
+
'Third\r\n' +
|
|
654
|
+
'--AaB03x\r\n' +
|
|
655
|
+
'content-disposition: form-data; name="field1"\r\n' +
|
|
656
|
+
'\r\n' +
|
|
657
|
+
'Joe Blow\r\nalmost tricked you!\r\n' +
|
|
658
|
+
'--AaB03x\r\n' +
|
|
659
|
+
'content-disposition: form-data; name="field1"\r\n' +
|
|
660
|
+
'\r\n' +
|
|
661
|
+
'Repeated name segment\r\n' +
|
|
662
|
+
'--AaB03x\r\n' +
|
|
663
|
+
'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' +
|
|
664
|
+
'Content-Type: text/plain\r\n' +
|
|
665
|
+
'\r\n' +
|
|
666
|
+
'... contents of file1.txt ...\r\r\n' +
|
|
667
|
+
'--AaB03x--\r\n';
|
|
668
|
+
|
|
669
|
+
const handler = (request) => {
|
|
670
|
+
|
|
671
|
+
const result = {};
|
|
672
|
+
const keys = Object.keys(request.payload);
|
|
673
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
674
|
+
const key = keys[i];
|
|
675
|
+
const value = request.payload[key];
|
|
676
|
+
result[key] = value._readableState ? true : value;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return result;
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
const server = Hapi.server({ routes: { payload: { multipart: true } } });
|
|
683
|
+
server.route({ method: 'POST', path: '/echo', handler });
|
|
684
|
+
|
|
685
|
+
const res = await server.inject({ method: 'POST', url: '/echo', payload: multipartPayload, headers: { 'content-type': 'multipart/form-data; boundary=AaB03x' } });
|
|
686
|
+
expect(Object.keys(res.result).length).to.equal(3);
|
|
687
|
+
expect(res.result.field1).to.exist();
|
|
688
|
+
expect(res.result.field1.length).to.equal(2);
|
|
689
|
+
expect(res.result.field1[1]).to.equal('Repeated name segment');
|
|
690
|
+
expect(res.result.pics).to.exist();
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it('signals connection close when payload is unconsumed', async () => {
|
|
694
|
+
|
|
695
|
+
const payload = Buffer.alloc(1024);
|
|
696
|
+
const server = Hapi.server();
|
|
697
|
+
server.route({ method: 'POST', path: '/', options: { handler: () => 'ok', payload: { maxBytes: 1024, output: 'stream', parse: false } } });
|
|
698
|
+
|
|
699
|
+
const res = await server.inject({ method: 'POST', url: '/', payload, headers: { 'content-type': 'application/octet-stream' } });
|
|
700
|
+
expect(res.statusCode).to.equal(200);
|
|
701
|
+
expect(res.headers).to.include({ connection: 'close' });
|
|
702
|
+
expect(res.result).to.equal('ok');
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it('times out when client request taking too long', async () => {
|
|
706
|
+
|
|
707
|
+
const server = Hapi.server({ routes: { payload: { timeout: 50 } } });
|
|
708
|
+
server.route({ method: 'POST', path: '/', handler: () => null });
|
|
709
|
+
await server.start();
|
|
710
|
+
|
|
711
|
+
const request = () => {
|
|
712
|
+
|
|
713
|
+
const options = {
|
|
714
|
+
hostname: '127.0.0.1',
|
|
715
|
+
port: server.info.port,
|
|
716
|
+
path: '/',
|
|
717
|
+
method: 'POST'
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
const req = Http.request(options);
|
|
721
|
+
req.on('error', Hoek.ignore);
|
|
722
|
+
req.write('{}\n');
|
|
723
|
+
setTimeout(() => req.end(), 100);
|
|
724
|
+
return new Promise((resolve) => req.once('response', resolve));
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const timer = new Hoek.Bench();
|
|
728
|
+
const res = await request();
|
|
729
|
+
expect(res.statusCode).to.equal(408);
|
|
730
|
+
expect(timer.elapsed()).to.be.at.least(50);
|
|
731
|
+
|
|
732
|
+
await server.stop({ timeout: 1 });
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it('times out when client request taking too long (route override)', async () => {
|
|
736
|
+
|
|
737
|
+
const server = Hapi.server({ routes: { payload: { timeout: false } } });
|
|
738
|
+
server.route({ method: 'POST', path: '/', options: { payload: { timeout: 50 }, handler: () => null } });
|
|
739
|
+
await server.start();
|
|
740
|
+
|
|
741
|
+
const request = () => {
|
|
742
|
+
|
|
743
|
+
const options = {
|
|
744
|
+
hostname: '127.0.0.1',
|
|
745
|
+
port: server.info.port,
|
|
746
|
+
path: '/',
|
|
747
|
+
method: 'POST'
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
const req = Http.request(options);
|
|
751
|
+
req.on('error', Hoek.ignore);
|
|
752
|
+
req.write('{}\n');
|
|
753
|
+
setTimeout(() => req.end(), 100);
|
|
754
|
+
return new Promise((resolve) => req.once('response', resolve));
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
const timer = new Hoek.Bench();
|
|
758
|
+
const res = await request();
|
|
759
|
+
expect(res.statusCode).to.equal(408);
|
|
760
|
+
expect(timer.elapsed()).to.be.at.least(50);
|
|
761
|
+
|
|
762
|
+
await server.stop({ timeout: 1 });
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it('returns payload when timeout is not triggered', async () => {
|
|
766
|
+
|
|
767
|
+
const server = Hapi.server({ routes: { payload: { timeout: 50 } } });
|
|
768
|
+
server.route({ method: 'POST', path: '/', handler: () => 'fast' });
|
|
769
|
+
await server.start();
|
|
770
|
+
const { res } = await Wreck.post(`http://localhost:${server.info.port}/`);
|
|
771
|
+
expect(res.statusCode).to.equal(200);
|
|
772
|
+
await server.stop({ timeout: 1 });
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it('errors if multipart payload exceeds byte limit', async () => {
|
|
776
|
+
|
|
777
|
+
const multipartPayload =
|
|
778
|
+
'--AaB03x\r\n' +
|
|
779
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
780
|
+
'\r\n' +
|
|
781
|
+
'First\r\n' +
|
|
782
|
+
'--AaB03x\r\n' +
|
|
783
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
784
|
+
'\r\n' +
|
|
785
|
+
'Second\r\n' +
|
|
786
|
+
'--AaB03x\r\n' +
|
|
787
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
788
|
+
'\r\n' +
|
|
789
|
+
'Third\r\n' +
|
|
790
|
+
'--AaB03x\r\n' +
|
|
791
|
+
'content-disposition: form-data; name="field1"\r\n' +
|
|
792
|
+
'\r\n' +
|
|
793
|
+
'Joe Blow\r\nalmost tricked you!\r\n' +
|
|
794
|
+
'--AaB03x\r\n' +
|
|
795
|
+
'content-disposition: form-data; name="field1"\r\n' +
|
|
796
|
+
'\r\n' +
|
|
797
|
+
'Repeated name segment\r\n' +
|
|
798
|
+
'--AaB03x\r\n' +
|
|
799
|
+
'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' +
|
|
800
|
+
'Content-Type: text/plain\r\n' +
|
|
801
|
+
'\r\n' +
|
|
802
|
+
'... contents of file1.txt ...\r\r\n' +
|
|
803
|
+
'--AaB03x--\r\n';
|
|
804
|
+
|
|
805
|
+
const server = Hapi.server();
|
|
806
|
+
server.route({ method: 'POST', path: '/echo', options: { handler: () => 'result', payload: { output: 'data', parse: true, maxBytes: 5, multipart: true } } });
|
|
807
|
+
|
|
808
|
+
const res = await server.inject({ method: 'POST', url: '/echo', payload: multipartPayload, simulate: { split: true }, headers: { 'content-length': null, 'content-type': 'multipart/form-data; boundary=AaB03x' } });
|
|
809
|
+
expect(res.statusCode).to.equal(400);
|
|
810
|
+
expect(res.payload.toString()).to.equal('{"statusCode":400,"error":"Bad Request","message":"Invalid multipart payload format"}');
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
it('errors if multipart disabled (default)', async () => {
|
|
814
|
+
|
|
815
|
+
const multipartPayload =
|
|
816
|
+
'--AaB03x\r\n' +
|
|
817
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
818
|
+
'\r\n' +
|
|
819
|
+
'First\r\n' +
|
|
820
|
+
'--AaB03x\r\n' +
|
|
821
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
822
|
+
'\r\n' +
|
|
823
|
+
'Second\r\n' +
|
|
824
|
+
'--AaB03x\r\n' +
|
|
825
|
+
'content-disposition: form-data; name="x"\r\n' +
|
|
826
|
+
'\r\n' +
|
|
827
|
+
'Third\r\n' +
|
|
828
|
+
'--AaB03x\r\n' +
|
|
829
|
+
'content-disposition: form-data; name="field1"\r\n' +
|
|
830
|
+
'\r\n' +
|
|
831
|
+
'Joe Blow\r\nalmost tricked you!\r\n' +
|
|
832
|
+
'--AaB03x\r\n' +
|
|
833
|
+
'content-disposition: form-data; name="field1"\r\n' +
|
|
834
|
+
'\r\n' +
|
|
835
|
+
'Repeated name segment\r\n' +
|
|
836
|
+
'--AaB03x\r\n' +
|
|
837
|
+
'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' +
|
|
838
|
+
'Content-Type: text/plain\r\n' +
|
|
839
|
+
'\r\n' +
|
|
840
|
+
'... contents of file1.txt ...\r\r\n' +
|
|
841
|
+
'--AaB03x--\r\n';
|
|
842
|
+
|
|
843
|
+
const server = Hapi.server();
|
|
844
|
+
server.route({ method: 'POST', path: '/echo', options: { handler: () => 'result', payload: { output: 'data', parse: true, maxBytes: 5 } } });
|
|
845
|
+
|
|
846
|
+
const res = await server.inject({ method: 'POST', url: '/echo', payload: multipartPayload, simulate: { split: true }, headers: { 'content-length': null, 'content-type': 'multipart/form-data; boundary=AaB03x' } });
|
|
847
|
+
expect(res.statusCode).to.equal(415);
|
|
848
|
+
});
|
|
849
|
+
});
|