@depup/hapi 18.1.0-depup.0

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.
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const Zlib = require('zlib');
4
+
5
+ const Accept = require('accept');
6
+ const Bounce = require('bounce');
7
+ const Hoek = require('hoek');
8
+
9
+
10
+ const internals = {
11
+ common: ['gzip, deflate', 'deflate, gzip', 'gzip', 'deflate', 'gzip, deflate, br']
12
+ };
13
+
14
+
15
+ exports = module.exports = internals.Compression = class {
16
+
17
+ constructor() {
18
+
19
+ this.encodings = ['identity', 'gzip', 'deflate'];
20
+ this._encoders = {
21
+ identity: null,
22
+ gzip: (options) => Zlib.createGzip(options),
23
+ deflate: (options) => Zlib.createDeflate(options)
24
+ };
25
+
26
+ this._decoders = {
27
+ gzip: (options) => Zlib.createGunzip(options),
28
+ deflate: (options) => Zlib.createInflate(options)
29
+ };
30
+
31
+ this._updateCommons();
32
+ }
33
+
34
+ _updateCommons() {
35
+
36
+ this._common = new Map();
37
+ internals.common.forEach((header) => {
38
+
39
+ this._common.set(header, Accept.encoding(header, this.encodings));
40
+ });
41
+ }
42
+
43
+ addEncoder(encoding, encoder) {
44
+
45
+ Hoek.assert(this._encoders[encoding] === undefined, `Cannot override existing encoder for ${encoding}`);
46
+ Hoek.assert(typeof encoder === 'function', `Invalid encoder function for ${encoding}`);
47
+ this._encoders[encoding] = encoder;
48
+ this.encodings.unshift(encoding);
49
+ this._updateCommons();
50
+ }
51
+
52
+ addDecoder(encoding, decoder) {
53
+
54
+ Hoek.assert(this._decoders[encoding] === undefined, `Cannot override existing decoder for ${encoding}`);
55
+ Hoek.assert(typeof decoder === 'function', `Invalid decoder function for ${encoding}`);
56
+ this._decoders[encoding] = decoder;
57
+ }
58
+
59
+ accept(request) {
60
+
61
+ const header = request.headers['accept-encoding'];
62
+ const common = this._common.get(header);
63
+ if (common) {
64
+ return common;
65
+ }
66
+
67
+ try {
68
+ return Accept.encoding(header, this.encodings);
69
+ }
70
+ catch (err) {
71
+ Bounce.rethrow(err, 'system');
72
+ err.header = header;
73
+ request._log(['accept-encoding', 'error'], err);
74
+ return 'identity';
75
+ }
76
+ }
77
+
78
+ encoding(response, length) {
79
+
80
+ const request = response.request;
81
+ if (!request._core.settings.compression ||
82
+ (length !== null && length < request._core.settings.compression.minBytes)) {
83
+
84
+ return null;
85
+ }
86
+
87
+ const mime = request._core.mime.type(response.headers['content-type'] || 'application/octet-stream');
88
+ if (!mime.compressible) {
89
+ return null;
90
+ }
91
+
92
+ response.vary('accept-encoding');
93
+
94
+ if (response.headers['content-encoding']) {
95
+ return null;
96
+ }
97
+
98
+ return (request.info.acceptEncoding === 'identity' ? null : request.info.acceptEncoding);
99
+ }
100
+
101
+ encoder(request, encoding) {
102
+
103
+ const encoder = this._encoders[encoding];
104
+ Hoek.assert(encoder !== undefined, `Unknown encoding ${encoding}`);
105
+ return encoder(request.route.settings.compression[encoding]);
106
+ }
107
+ };
package/lib/config.js ADDED
@@ -0,0 +1,436 @@
1
+ 'use strict';
2
+
3
+ const Os = require('os');
4
+
5
+ const Joi = require('joi');
6
+
7
+
8
+ const internals = {};
9
+
10
+
11
+ exports.symbol = Symbol('hapi-response');
12
+
13
+
14
+ exports.apply = function (type, options, ...message) {
15
+
16
+ const result = Joi.validate(options, internals[type]);
17
+
18
+ if (result.error) {
19
+ throw new Error(`Invalid ${type} options ${message.length ? '(' + message.join(' ') + ')' : ''} ${result.error.annotate()}`);
20
+ }
21
+
22
+ return result.value;
23
+ };
24
+
25
+
26
+ exports.enable = function (options) {
27
+
28
+ const settings = (options ? Object.assign({}, options) : {}); // Shallow cloned
29
+
30
+ if (settings.security === true) {
31
+ settings.security = {};
32
+ }
33
+
34
+ if (settings.cors === true) {
35
+ settings.cors = {};
36
+ }
37
+
38
+ return settings;
39
+ };
40
+
41
+
42
+ internals.access = Joi.object({
43
+ entity: Joi.valid('user', 'app', 'any'),
44
+ scope: [false, Joi.array().items(Joi.string()).single().min(1)]
45
+ });
46
+
47
+
48
+ internals.auth = Joi.alternatives([
49
+ Joi.string(),
50
+ internals.access.keys({
51
+ mode: Joi.valid('required', 'optional', 'try'),
52
+ strategy: Joi.string(),
53
+ strategies: Joi.array().items(Joi.string()).min(1),
54
+ access: Joi.array().items(internals.access.min(1)).single().min(1),
55
+ payload: [
56
+ Joi.valid('required', 'optional'),
57
+ Joi.boolean()
58
+ ]
59
+ })
60
+ .without('strategy', 'strategies')
61
+ .without('access', ['scope', 'entity'])
62
+ ]);
63
+
64
+
65
+ internals.event = Joi.object({
66
+ method: Joi.array().items(Joi.func()).single(),
67
+ options: Joi.object({
68
+ before: Joi.array().items(Joi.string()).single(),
69
+ after: Joi.array().items(Joi.string()).single(),
70
+ bind: Joi.any(),
71
+ sandbox: Joi.valid('server', 'plugin')
72
+ })
73
+ .default({})
74
+ });
75
+
76
+
77
+ internals.exts = Joi.array().items(internals.event.keys({ type: Joi.string().required() })).single();
78
+
79
+
80
+ internals.failAction = Joi.alternatives([
81
+ Joi.valid('error', 'log', 'ignore'),
82
+ Joi.func()
83
+ ])
84
+ .default('error');
85
+
86
+
87
+ internals.routeBase = Joi.object({
88
+ app: Joi.object().allow(null),
89
+ auth: internals.auth.allow(false),
90
+ bind: Joi.object().allow(null),
91
+ cache: Joi.object({
92
+ expiresIn: Joi.number(),
93
+ expiresAt: Joi.string(),
94
+ privacy: Joi.valid('default', 'public', 'private'),
95
+ statuses: Joi.array().items(Joi.number().integer().min(200)).min(1).single().default([200, 204]),
96
+ otherwise: Joi.string().default('no-cache')
97
+ })
98
+ .allow(false)
99
+ .default(),
100
+ compression: Joi.object()
101
+ .pattern(/.+/, Joi.object())
102
+ .default(),
103
+ cors: Joi.object({
104
+ origin: Joi.array().min(1).allow('ignore').default(['*']),
105
+ maxAge: Joi.number().default(86400),
106
+ headers: Joi.array().items(Joi.string()).default(['Accept', 'Authorization', 'Content-Type', 'If-None-Match']),
107
+ additionalHeaders: Joi.array().items(Joi.string()).default([]),
108
+ exposedHeaders: Joi.array().items(Joi.string()).default(['WWW-Authenticate', 'Server-Authorization']),
109
+ additionalExposedHeaders: Joi.array().items(Joi.string()).default([]),
110
+ credentials: Joi.boolean().when('origin', { is: 'ignore', then: false }).default(false)
111
+ })
112
+ .allow(false, true)
113
+ .default(false),
114
+ ext: Joi.object({
115
+ onPreAuth: Joi.array().items(internals.event).single(),
116
+ onCredentials: Joi.array().items(internals.event).single(),
117
+ onPostAuth: Joi.array().items(internals.event).single(),
118
+ onPreHandler: Joi.array().items(internals.event).single(),
119
+ onPostHandler: Joi.array().items(internals.event).single(),
120
+ onPreResponse: Joi.array().items(internals.event).single()
121
+ })
122
+ .default({}),
123
+ files: Joi.object({
124
+ relativeTo: Joi.string().regex(/^([\/\.])|([A-Za-z]:\\)|(\\\\)/).default('.')
125
+ })
126
+ .default(),
127
+ json: Joi.object({
128
+ replacer: Joi.alternatives(Joi.func(), Joi.array()).allow(null).default(null),
129
+ space: Joi.number().allow(null).default(null),
130
+ suffix: Joi.string().allow(null).default(null),
131
+ escape: Joi.boolean().default(false)
132
+ })
133
+ .default(),
134
+ jsonp: Joi.string(),
135
+ log: Joi.object({
136
+ collect: Joi.boolean().default(false)
137
+ })
138
+ .default(),
139
+ payload: Joi.object({
140
+ output: Joi.valid('data', 'stream', 'file').default('data'),
141
+ parse: Joi.boolean().allow('gunzip').default(true),
142
+ multipart: Joi.object({
143
+ output: Joi.valid('data', 'stream', 'file', 'annotated').required()
144
+ })
145
+ .allow(false),
146
+ allow: Joi.array().items(Joi.string()).single(),
147
+ override: Joi.string(),
148
+ protoAction: Joi.valid('error', 'remove', 'ignore').default('error'),
149
+ maxBytes: Joi.number().integer().positive().default(1024 * 1024),
150
+ uploads: Joi.string().default(Os.tmpdir()),
151
+ failAction: internals.failAction,
152
+ timeout: Joi.number().integer().positive().allow(false).default(10 * 1000),
153
+ defaultContentType: Joi.string().default('application/json'),
154
+ compression: Joi.object()
155
+ .pattern(/.+/, Joi.object())
156
+ .default()
157
+ })
158
+ .default(),
159
+ plugins: Joi.object(),
160
+ response: Joi.object({
161
+ disconnectStatusCode: Joi.number().integer().min(400).default(499),
162
+ emptyStatusCode: Joi.valid(200, 204).default(200),
163
+ failAction: internals.failAction,
164
+ modify: Joi.boolean(),
165
+ options: Joi.object().default(),
166
+ ranges: Joi.boolean().default(true),
167
+ sample: Joi.number().min(0).max(100).when('modify', { is: true, then: Joi.forbidden() }),
168
+ schema: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(true, false),
169
+ status: Joi.object().pattern(/\d\d\d/, Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(true, false))
170
+ })
171
+ .default()
172
+ .assert('options.stripUnknown', Joi.when('modify', { is: true, otherwise: false }), 'meet requirement of having peer modify set to true'),
173
+ security: Joi.object({
174
+ hsts: Joi.alternatives([
175
+ Joi.object({
176
+ maxAge: Joi.number(),
177
+ includeSubdomains: Joi.boolean(),
178
+ includeSubDomains: Joi.boolean(),
179
+ preload: Joi.boolean()
180
+ }),
181
+ Joi.boolean(),
182
+ Joi.number()
183
+ ])
184
+ .default(15768000),
185
+ xframe: Joi.alternatives([
186
+ Joi.boolean(),
187
+ Joi.valid('sameorigin', 'deny'),
188
+ Joi.object({
189
+ rule: Joi.valid('sameorigin', 'deny', 'allow-from'),
190
+ source: Joi.string()
191
+ })
192
+ ])
193
+ .default('deny'),
194
+ xss: Joi.boolean().default(true),
195
+ noOpen: Joi.boolean().default(true),
196
+ noSniff: Joi.boolean().default(true),
197
+ referrer: Joi.alternatives([
198
+ Joi.boolean().valid(false),
199
+ Joi.valid('', 'no-referrer', 'no-referrer-when-downgrade',
200
+ 'unsafe-url', 'same-origin', 'origin', 'strict-origin',
201
+ 'origin-when-cross-origin', 'strict-origin-when-cross-origin')
202
+ ])
203
+ .default(false)
204
+ })
205
+ .allow(null, false, true)
206
+ .default(false),
207
+ state: Joi.object({
208
+ parse: Joi.boolean().default(true),
209
+ failAction: internals.failAction
210
+ })
211
+ .default(),
212
+ timeout: Joi.object({
213
+ socket: Joi.number().integer().positive().allow(false),
214
+ server: Joi.number().integer().positive().allow(false).default(false)
215
+ })
216
+ .default(),
217
+ validate: Joi.object({
218
+ headers: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(null, true),
219
+ params: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(null, true),
220
+ query: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(null, false, true),
221
+ payload: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(null, false, true),
222
+ state: Joi.alternatives(Joi.object(), Joi.array(), Joi.func()).allow(null, false, true),
223
+ failAction: internals.failAction,
224
+ errorFields: Joi.object(),
225
+ options: Joi.object().default()
226
+ })
227
+ .default()
228
+ });
229
+
230
+
231
+ internals.server = Joi.object({
232
+ address: Joi.string().hostname(),
233
+ app: Joi.object().allow(null),
234
+ autoListen: Joi.boolean(),
235
+ cache: Joi.allow(null), // Validated elsewhere
236
+ compression: Joi.object({
237
+ minBytes: Joi.number().min(1).integer().default(1024)
238
+ })
239
+ .allow(false)
240
+ .default(),
241
+ debug: Joi.object({
242
+ request: Joi.array().items(Joi.string()).single().allow(false).default(['implementation']),
243
+ log: Joi.array().items(Joi.string()).single().allow(false)
244
+ })
245
+ .allow(false)
246
+ .default(),
247
+ host: Joi.string().hostname().allow(null),
248
+ listener: Joi.any(),
249
+ load: Joi.object({
250
+ sampleInterval: Joi.number().integer().min(0).default(0),
251
+ concurrent: Joi.number().integer().min(0).default(0)
252
+ })
253
+ .unknown()
254
+ .default(),
255
+ mime: Joi.object().allow(null).default(null),
256
+ operations: Joi.object({
257
+ cleanStop: Joi.boolean().default(true)
258
+ })
259
+ .default(),
260
+ plugins: Joi.object(),
261
+ port: Joi.alternatives([
262
+ Joi.number().integer().min(0), // TCP port
263
+ Joi.string().regex(/\//), // Unix domain socket
264
+ Joi.string().regex(/^\\\\\.\\pipe\\/) // Windows named pipe
265
+ ])
266
+ .allow(null),
267
+ query: Joi.object({
268
+ parser: Joi.func()
269
+ })
270
+ .default(),
271
+ router: Joi.object({
272
+ isCaseSensitive: Joi.boolean().default(true),
273
+ stripTrailingSlash: Joi.boolean().default(false)
274
+ })
275
+ .default(),
276
+ routes: internals.routeBase.default(),
277
+ state: Joi.object(), // Cookie defaults
278
+ tls: Joi.alternatives([
279
+ Joi.object().allow(null),
280
+ Joi.boolean()
281
+ ]),
282
+ uri: Joi.string().regex(/[^/]$/)
283
+ });
284
+
285
+
286
+ internals.vhost = Joi.alternatives([
287
+ Joi.string().hostname(),
288
+ Joi.array().items(Joi.string().hostname()).min(1)
289
+ ]);
290
+
291
+
292
+ internals.handler = Joi.alternatives([
293
+ Joi.func(),
294
+ Joi.object().length(1)
295
+ ]);
296
+
297
+
298
+ internals.route = Joi.object({
299
+ method: Joi.string().regex(/^[a-zA-Z0-9!#\$%&'\*\+\-\.^_`\|~]+$/).required(),
300
+ path: Joi.string().required(),
301
+ rules: Joi.object(),
302
+ vhost: internals.vhost,
303
+
304
+ // Validated in route construction
305
+
306
+ handler: Joi.any(),
307
+ options: Joi.any(),
308
+ config: Joi.any() // Backwards compatibility
309
+ })
310
+ .without('config', 'options');
311
+
312
+
313
+ internals.pre = [
314
+ Joi.func(),
315
+ Joi.object({
316
+ method: Joi.alternatives(Joi.string(), Joi.func()).required(),
317
+ assign: Joi.string(),
318
+ mode: Joi.valid('serial', 'parallel'),
319
+ failAction: internals.failAction
320
+ })
321
+ ];
322
+
323
+
324
+ internals.routeConfig = internals.routeBase.keys({
325
+ description: Joi.string(),
326
+ id: Joi.string(),
327
+ isInternal: Joi.boolean(),
328
+ notes: [
329
+ Joi.string(),
330
+ Joi.array().items(Joi.string())
331
+ ],
332
+ pre: Joi.array().items(internals.pre.concat(Joi.array().items(internals.pre).min(1))),
333
+ tags: [
334
+ Joi.string(),
335
+ Joi.array().items(Joi.string())
336
+ ]
337
+ });
338
+
339
+
340
+ internals.cacheConfig = Joi.alternatives([
341
+ Joi.func(),
342
+ Joi.object({
343
+ name: Joi.string().invalid('_default'),
344
+ shared: Joi.boolean(),
345
+ provider: [
346
+ Joi.func(),
347
+ {
348
+ constructor: Joi.func().required(),
349
+ options: Joi.object({
350
+ partition: Joi.string().default('hapi-cache')
351
+ })
352
+ .unknown() // Catbox client validates other keys
353
+ .default({})
354
+ }
355
+ ],
356
+ engine: Joi.object()
357
+ })
358
+ .xor('provider', 'engine')
359
+ ]);
360
+
361
+
362
+ internals.cache = Joi.array().items(internals.cacheConfig).min(1).single();
363
+
364
+
365
+ internals.cachePolicy = Joi.object({
366
+ cache: Joi.string().allow(null).allow(''),
367
+ segment: Joi.string(),
368
+ shared: Joi.boolean()
369
+ })
370
+ .unknown(); // Catbox policy validates other keys
371
+
372
+
373
+ internals.method = Joi.object({
374
+ bind: Joi.object().allow(null),
375
+ generateKey: Joi.func(),
376
+ cache: internals.cachePolicy
377
+ });
378
+
379
+
380
+ internals.methodObject = Joi.object({
381
+ name: Joi.string().required(),
382
+ method: Joi.func().required(),
383
+ options: Joi.object()
384
+ });
385
+
386
+
387
+ internals.register = Joi.object({
388
+ once: true,
389
+ routes: Joi.object({
390
+ prefix: Joi.string().regex(/^\/.+/),
391
+ vhost: internals.vhost
392
+ })
393
+ .default({})
394
+ });
395
+
396
+
397
+ internals.semver = Joi.string();
398
+
399
+
400
+ internals.plugin = internals.register.keys({
401
+ options: Joi.any(),
402
+ plugin: Joi.object({
403
+ register: Joi.func().required(),
404
+ name: Joi.string().when('pkg.name', { is: Joi.exist(), otherwise: Joi.required() }),
405
+ version: Joi.string(),
406
+ multiple: Joi.boolean().default(false),
407
+ dependencies: [
408
+ Joi.array().items(Joi.string()).single(),
409
+ Joi.object().pattern(/.+/, internals.semver)
410
+ ],
411
+ once: true,
412
+ requirements: Joi.object({
413
+ hapi: Joi.string(),
414
+ node: Joi.string()
415
+ })
416
+ .default(),
417
+ pkg: Joi.object({
418
+ name: Joi.string(),
419
+ version: Joi.string().default('0.0.0')
420
+ })
421
+ .unknown()
422
+ .default({})
423
+ })
424
+ .unknown()
425
+ })
426
+ .without('once', 'options')
427
+ .unknown();
428
+
429
+
430
+ internals.rules = Joi.object({
431
+ validate: Joi.object({
432
+ schema: Joi.alternatives(Joi.object(), Joi.array()).required(),
433
+ options: Joi.object()
434
+ .default({ allowUnknown: true })
435
+ })
436
+ });