1 | 'use strict';
|
2 |
|
3 | const Hoek = require('@hapi/hoek');
|
4 |
|
5 |
|
6 | const internals = {
|
7 | codes: new Map([
|
8 | [100, 'Continue'],
|
9 | [101, 'Switching Protocols'],
|
10 | [102, 'Processing'],
|
11 | [200, 'OK'],
|
12 | [201, 'Created'],
|
13 | [202, 'Accepted'],
|
14 | [203, 'Non-Authoritative Information'],
|
15 | [204, 'No Content'],
|
16 | [205, 'Reset Content'],
|
17 | [206, 'Partial Content'],
|
18 | [207, 'Multi-Status'],
|
19 | [300, 'Multiple Choices'],
|
20 | [301, 'Moved Permanently'],
|
21 | [302, 'Moved Temporarily'],
|
22 | [303, 'See Other'],
|
23 | [304, 'Not Modified'],
|
24 | [305, 'Use Proxy'],
|
25 | [307, 'Temporary Redirect'],
|
26 | [400, 'Bad Request'],
|
27 | [401, 'Unauthorized'],
|
28 | [402, 'Payment Required'],
|
29 | [403, 'Forbidden'],
|
30 | [404, 'Not Found'],
|
31 | [405, 'Method Not Allowed'],
|
32 | [406, 'Not Acceptable'],
|
33 | [407, 'Proxy Authentication Required'],
|
34 | [408, 'Request Time-out'],
|
35 | [409, 'Conflict'],
|
36 | [410, 'Gone'],
|
37 | [411, 'Length Required'],
|
38 | [412, 'Precondition Failed'],
|
39 | [413, 'Request Entity Too Large'],
|
40 | [414, 'Request-URI Too Large'],
|
41 | [415, 'Unsupported Media Type'],
|
42 | [416, 'Requested Range Not Satisfiable'],
|
43 | [417, 'Expectation Failed'],
|
44 | [418, 'I\'m a teapot'],
|
45 | [422, 'Unprocessable Entity'],
|
46 | [423, 'Locked'],
|
47 | [424, 'Failed Dependency'],
|
48 | [425, 'Too Early'],
|
49 | [426, 'Upgrade Required'],
|
50 | [428, 'Precondition Required'],
|
51 | [429, 'Too Many Requests'],
|
52 | [431, 'Request Header Fields Too Large'],
|
53 | [451, 'Unavailable For Legal Reasons'],
|
54 | [500, 'Internal Server Error'],
|
55 | [501, 'Not Implemented'],
|
56 | [502, 'Bad Gateway'],
|
57 | [503, 'Service Unavailable'],
|
58 | [504, 'Gateway Time-out'],
|
59 | [505, 'HTTP Version Not Supported'],
|
60 | [506, 'Variant Also Negotiates'],
|
61 | [507, 'Insufficient Storage'],
|
62 | [509, 'Bandwidth Limit Exceeded'],
|
63 | [510, 'Not Extended'],
|
64 | [511, 'Network Authentication Required']
|
65 | ])
|
66 | };
|
67 |
|
68 |
|
69 | exports.Boom = class extends Error {
|
70 |
|
71 | constructor(message, options = {}) {
|
72 |
|
73 | if (message instanceof Error) {
|
74 | return exports.boomify(Hoek.clone(message), options);
|
75 | }
|
76 |
|
77 | const { statusCode = 500, data = null, ctor = exports.Boom } = options;
|
78 | const error = new Error(message ? message : undefined);
|
79 | Error.captureStackTrace(error, ctor);
|
80 | error.data = data;
|
81 | const boom = internals.initialize(error, statusCode);
|
82 |
|
83 | Object.defineProperty(boom, 'typeof', { value: ctor });
|
84 |
|
85 | if (options.decorate) {
|
86 | Object.assign(boom, options.decorate);
|
87 | }
|
88 |
|
89 | return boom;
|
90 | }
|
91 |
|
92 | static [Symbol.hasInstance](instance) {
|
93 |
|
94 | return exports.isBoom(instance);
|
95 | }
|
96 | };
|
97 |
|
98 |
|
99 | exports.isBoom = function (err, statusCode) {
|
100 |
|
101 | return err instanceof Error && !!err.isBoom && (!statusCode || err.output.statusCode === statusCode);
|
102 | };
|
103 |
|
104 |
|
105 | exports.boomify = function (err, options) {
|
106 |
|
107 | Hoek.assert(err instanceof Error, 'Cannot wrap non-Error object');
|
108 |
|
109 | options = options || {};
|
110 |
|
111 | if (options.data !== undefined) {
|
112 | err.data = options.data;
|
113 | }
|
114 |
|
115 | if (options.decorate) {
|
116 | Object.assign(err, options.decorate);
|
117 | }
|
118 |
|
119 | if (!err.isBoom) {
|
120 | return internals.initialize(err, options.statusCode || 500, options.message);
|
121 | }
|
122 |
|
123 | if (options.override === false ||
|
124 | !options.statusCode && !options.message) {
|
125 |
|
126 | return err;
|
127 | }
|
128 |
|
129 | return internals.initialize(err, options.statusCode || err.output.statusCode, options.message);
|
130 | };
|
131 |
|
132 |
|
133 |
|
134 |
|
135 | exports.badRequest = function (message, data) {
|
136 |
|
137 | return new exports.Boom(message, { statusCode: 400, data, ctor: exports.badRequest });
|
138 | };
|
139 |
|
140 |
|
141 | exports.unauthorized = function (message, scheme, attributes) {
|
142 |
|
143 | const err = new exports.Boom(message, { statusCode: 401, ctor: exports.unauthorized });
|
144 |
|
145 |
|
146 |
|
147 | if (!scheme) {
|
148 | return err;
|
149 | }
|
150 |
|
151 |
|
152 |
|
153 | if (typeof scheme !== 'string') {
|
154 | err.output.headers['WWW-Authenticate'] = scheme.join(', ');
|
155 | return err;
|
156 | }
|
157 |
|
158 |
|
159 |
|
160 | let wwwAuthenticate = `${scheme}`;
|
161 |
|
162 | if (attributes ||
|
163 | message) {
|
164 |
|
165 | err.output.payload.attributes = {};
|
166 | }
|
167 |
|
168 | if (attributes) {
|
169 | if (typeof attributes === 'string') {
|
170 | wwwAuthenticate += ' ' + Hoek.escapeHeaderAttribute(attributes);
|
171 | err.output.payload.attributes = attributes;
|
172 | }
|
173 | else {
|
174 | wwwAuthenticate += ' ' + Object.keys(attributes).map((name) => {
|
175 |
|
176 | let value = attributes[name];
|
177 | if (value === null ||
|
178 | value === undefined) {
|
179 |
|
180 | value = '';
|
181 | }
|
182 |
|
183 | err.output.payload.attributes[name] = value;
|
184 | return `${name}="${Hoek.escapeHeaderAttribute(value.toString())}"`;
|
185 | })
|
186 | .join(', ');
|
187 | }
|
188 | }
|
189 |
|
190 | if (message) {
|
191 | if (attributes) {
|
192 | wwwAuthenticate += ',';
|
193 | }
|
194 |
|
195 | wwwAuthenticate += ` error="${Hoek.escapeHeaderAttribute(message)}"`;
|
196 | err.output.payload.attributes.error = message;
|
197 | }
|
198 | else {
|
199 | err.isMissing = true;
|
200 | }
|
201 |
|
202 | err.output.headers['WWW-Authenticate'] = wwwAuthenticate;
|
203 | return err;
|
204 | };
|
205 |
|
206 |
|
207 | exports.paymentRequired = function (message, data) {
|
208 |
|
209 | return new exports.Boom(message, { statusCode: 402, data, ctor: exports.paymentRequired });
|
210 | };
|
211 |
|
212 |
|
213 | exports.forbidden = function (message, data) {
|
214 |
|
215 | return new exports.Boom(message, { statusCode: 403, data, ctor: exports.forbidden });
|
216 | };
|
217 |
|
218 |
|
219 | exports.notFound = function (message, data) {
|
220 |
|
221 | return new exports.Boom(message, { statusCode: 404, data, ctor: exports.notFound });
|
222 | };
|
223 |
|
224 |
|
225 | exports.methodNotAllowed = function (message, data, allow) {
|
226 |
|
227 | const err = new exports.Boom(message, { statusCode: 405, data, ctor: exports.methodNotAllowed });
|
228 |
|
229 | if (typeof allow === 'string') {
|
230 | allow = [allow];
|
231 | }
|
232 |
|
233 | if (Array.isArray(allow)) {
|
234 | err.output.headers.Allow = allow.join(', ');
|
235 | }
|
236 |
|
237 | return err;
|
238 | };
|
239 |
|
240 |
|
241 | exports.notAcceptable = function (message, data) {
|
242 |
|
243 | return new exports.Boom(message, { statusCode: 406, data, ctor: exports.notAcceptable });
|
244 | };
|
245 |
|
246 |
|
247 | exports.proxyAuthRequired = function (message, data) {
|
248 |
|
249 | return new exports.Boom(message, { statusCode: 407, data, ctor: exports.proxyAuthRequired });
|
250 | };
|
251 |
|
252 |
|
253 | exports.clientTimeout = function (message, data) {
|
254 |
|
255 | return new exports.Boom(message, { statusCode: 408, data, ctor: exports.clientTimeout });
|
256 | };
|
257 |
|
258 |
|
259 | exports.conflict = function (message, data) {
|
260 |
|
261 | return new exports.Boom(message, { statusCode: 409, data, ctor: exports.conflict });
|
262 | };
|
263 |
|
264 |
|
265 | exports.resourceGone = function (message, data) {
|
266 |
|
267 | return new exports.Boom(message, { statusCode: 410, data, ctor: exports.resourceGone });
|
268 | };
|
269 |
|
270 |
|
271 | exports.lengthRequired = function (message, data) {
|
272 |
|
273 | return new exports.Boom(message, { statusCode: 411, data, ctor: exports.lengthRequired });
|
274 | };
|
275 |
|
276 |
|
277 | exports.preconditionFailed = function (message, data) {
|
278 |
|
279 | return new exports.Boom(message, { statusCode: 412, data, ctor: exports.preconditionFailed });
|
280 | };
|
281 |
|
282 |
|
283 | exports.entityTooLarge = function (message, data) {
|
284 |
|
285 | return new exports.Boom(message, { statusCode: 413, data, ctor: exports.entityTooLarge });
|
286 | };
|
287 |
|
288 |
|
289 | exports.uriTooLong = function (message, data) {
|
290 |
|
291 | return new exports.Boom(message, { statusCode: 414, data, ctor: exports.uriTooLong });
|
292 | };
|
293 |
|
294 |
|
295 | exports.unsupportedMediaType = function (message, data) {
|
296 |
|
297 | return new exports.Boom(message, { statusCode: 415, data, ctor: exports.unsupportedMediaType });
|
298 | };
|
299 |
|
300 |
|
301 | exports.rangeNotSatisfiable = function (message, data) {
|
302 |
|
303 | return new exports.Boom(message, { statusCode: 416, data, ctor: exports.rangeNotSatisfiable });
|
304 | };
|
305 |
|
306 |
|
307 | exports.expectationFailed = function (message, data) {
|
308 |
|
309 | return new exports.Boom(message, { statusCode: 417, data, ctor: exports.expectationFailed });
|
310 | };
|
311 |
|
312 |
|
313 | exports.teapot = function (message, data) {
|
314 |
|
315 | return new exports.Boom(message, { statusCode: 418, data, ctor: exports.teapot });
|
316 | };
|
317 |
|
318 |
|
319 | exports.badData = function (message, data) {
|
320 |
|
321 | return new exports.Boom(message, { statusCode: 422, data, ctor: exports.badData });
|
322 | };
|
323 |
|
324 |
|
325 | exports.locked = function (message, data) {
|
326 |
|
327 | return new exports.Boom(message, { statusCode: 423, data, ctor: exports.locked });
|
328 | };
|
329 |
|
330 |
|
331 | exports.failedDependency = function (message, data) {
|
332 |
|
333 | return new exports.Boom(message, { statusCode: 424, data, ctor: exports.failedDependency });
|
334 | };
|
335 |
|
336 | exports.tooEarly = function (message, data) {
|
337 |
|
338 | return new exports.Boom(message, { statusCode: 425, data, ctor: exports.tooEarly });
|
339 | };
|
340 |
|
341 |
|
342 | exports.preconditionRequired = function (message, data) {
|
343 |
|
344 | return new exports.Boom(message, { statusCode: 428, data, ctor: exports.preconditionRequired });
|
345 | };
|
346 |
|
347 |
|
348 | exports.tooManyRequests = function (message, data) {
|
349 |
|
350 | return new exports.Boom(message, { statusCode: 429, data, ctor: exports.tooManyRequests });
|
351 | };
|
352 |
|
353 |
|
354 | exports.illegal = function (message, data) {
|
355 |
|
356 | return new exports.Boom(message, { statusCode: 451, data, ctor: exports.illegal });
|
357 | };
|
358 |
|
359 |
|
360 |
|
361 |
|
362 | exports.internal = function (message, data, statusCode = 500) {
|
363 |
|
364 | return internals.serverError(message, data, statusCode, exports.internal);
|
365 | };
|
366 |
|
367 |
|
368 | exports.notImplemented = function (message, data) {
|
369 |
|
370 | return internals.serverError(message, data, 501, exports.notImplemented);
|
371 | };
|
372 |
|
373 |
|
374 | exports.badGateway = function (message, data) {
|
375 |
|
376 | return internals.serverError(message, data, 502, exports.badGateway);
|
377 | };
|
378 |
|
379 |
|
380 | exports.serverUnavailable = function (message, data) {
|
381 |
|
382 | return internals.serverError(message, data, 503, exports.serverUnavailable);
|
383 | };
|
384 |
|
385 |
|
386 | exports.gatewayTimeout = function (message, data) {
|
387 |
|
388 | return internals.serverError(message, data, 504, exports.gatewayTimeout);
|
389 | };
|
390 |
|
391 |
|
392 | exports.badImplementation = function (message, data) {
|
393 |
|
394 | const err = internals.serverError(message, data, 500, exports.badImplementation);
|
395 | err.isDeveloperError = true;
|
396 | return err;
|
397 | };
|
398 |
|
399 |
|
400 | internals.initialize = function (err, statusCode, message) {
|
401 |
|
402 | const numberCode = parseInt(statusCode, 10);
|
403 | Hoek.assert(!isNaN(numberCode) && numberCode >= 400, 'First argument must be a number (400+):', statusCode);
|
404 |
|
405 | err.isBoom = true;
|
406 | err.isServer = numberCode >= 500;
|
407 |
|
408 | if (!err.hasOwnProperty('data')) {
|
409 | err.data = null;
|
410 | }
|
411 |
|
412 | err.output = {
|
413 | statusCode: numberCode,
|
414 | payload: {},
|
415 | headers: {}
|
416 | };
|
417 |
|
418 | Object.defineProperty(err, 'reformat', { value: internals.reformat });
|
419 |
|
420 | if (!message &&
|
421 | !err.message) {
|
422 |
|
423 | err.reformat();
|
424 | message = err.output.payload.error;
|
425 | }
|
426 |
|
427 | if (message) {
|
428 | const props = Object.getOwnPropertyDescriptor(err, 'message') || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(err), 'message');
|
429 | Hoek.assert(!props || props.configurable && !props.get, 'The error is not compatible with boom');
|
430 |
|
431 | err.message = message + (err.message ? ': ' + err.message : '');
|
432 | err.output.payload.message = err.message;
|
433 | }
|
434 |
|
435 | err.reformat();
|
436 | return err;
|
437 | };
|
438 |
|
439 |
|
440 | internals.reformat = function (debug = false) {
|
441 |
|
442 | this.output.payload.statusCode = this.output.statusCode;
|
443 | this.output.payload.error = internals.codes.get(this.output.statusCode) || 'Unknown';
|
444 |
|
445 | if (this.output.statusCode === 500 && debug !== true) {
|
446 | this.output.payload.message = 'An internal server error occurred';
|
447 | }
|
448 | else if (this.message) {
|
449 | this.output.payload.message = this.message;
|
450 | }
|
451 | };
|
452 |
|
453 |
|
454 | internals.serverError = function (message, data, statusCode, ctor) {
|
455 |
|
456 | if (data instanceof Error &&
|
457 | !data.isBoom) {
|
458 |
|
459 | return exports.boomify(data, { statusCode, message });
|
460 | }
|
461 |
|
462 | return new exports.Boom(message, { statusCode, data, ctor });
|
463 | };
|