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