I_Jemin

Validate Input

Showing 49 changed files with 10068 additions and 11 deletions
1 +const Joi = require('joi');
1 const express = require('express'); 2 const express = require('express');
2 const app = express(); 3 const app = express();
3 4
4 app.use(express.json()); 5 app.use(express.json());
5 6
6 const courses = [ 7 const courses = [
7 - { id: 1, name: 'course1'}, 8 + { id: 1, name: 'course1' },
8 - { id: 2, name: 'course2'}, 9 + { id: 2, name: 'course2' },
9 - { id: 3, name: 'course3'}, 10 + { id: 3, name: 'course3' },
10 ]; 11 ];
11 12
12 // Corespond to HTTP 13 // Corespond to HTTP
...@@ -15,21 +16,34 @@ const courses = [ ...@@ -15,21 +16,34 @@ const courses = [
15 // app.put(); 16 // app.put();
16 // app.delete() 17 // app.delete()
17 18
18 -app.get('/',(req,res)=> { 19 +app.get('/', (req, res) => {
19 res.send('Hello World!!!'); 20 res.send('Hello World!!!');
20 }); 21 });
21 22
22 -app.get('/api/courses',(req,res) => { 23 +app.get('/api/courses', (req, res) => {
23 res.send(courses); 24 res.send(courses);
24 }); 25 });
25 26
26 -app.get('/api/courses/:id',(req,res) => { 27 +app.get('/api/courses/:id', (req, res) => {
27 - const course = courses.find(c=> c.id === parseInt(req.params.id)); 28 + const course = courses.find(c => c.id === parseInt(req.params.id));
28 - if(!course) res.status(404).send('The course with the given ID was not found'); 29 + if (!course) res.status(404).send('The course with the given ID was not found');
29 res.send(course); 30 res.send(course);
30 }); 31 });
31 32
32 -app.post('/api/courses',(req,res)=> { 33 +app.post('/api/courses', (req, res) => {
34 + // Define Schema
35 + const schema = {
36 + name: Joi.string().min(3).required()
37 + };
38 +
39 + const result = Joi.validate(req.body, schema);
40 +
41 + if (result.error) {
42 + // 400 Bad Request
43 + res.status(400).send(result.error.details[0].message);
44 + return;
45 + }
46 +
33 const course = { 47 const course = {
34 id: courses.length + 1, 48 id: courses.length + 1,
35 name: req.body.name 49 name: req.body.name
...@@ -42,4 +56,4 @@ app.post('/api/courses',(req,res)=> { ...@@ -42,4 +56,4 @@ app.post('/api/courses',(req,res)=> {
42 // PORT 56 // PORT
43 const port = process.env.PORT || 3000; 57 const port = process.env.PORT || 3000;
44 58
45 -app.listen(port,()=> console.log(`Listening on port ${port}...`)); 59 +app.listen(port, () => console.log(`Listening on port ${port}...`));
......
1 +Copyright (c) 2011-2017, Project contributors
2 +Copyright (c) 2011-2014, Walmart
3 +Copyright (c) 2011, Yahoo Inc.
4 +All rights reserved.
5 +
6 +Redistribution and use in source and binary forms, with or without
7 +modification, are permitted provided that the following conditions are met:
8 + * Redistributions of source code must retain the above copyright
9 + notice, this list of conditions and the following disclaimer.
10 + * Redistributions in binary form must reproduce the above copyright
11 + notice, this list of conditions and the following disclaimer in the
12 + documentation and/or other materials provided with the distribution.
13 + * The names of any contributors may not be used to endorse or promote
14 + products derived from this software without specific prior written
15 + permission.
16 +
17 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
21 +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 +
28 + * * *
29 +
30 +The complete list of contributors can be found at: https://github.com/hapijs/hapi/graphs/contributors
31 +Portions of this project were initially based on the Yahoo! Inc. Postmile project,
32 +published at https://github.com/yahoo/postmile.
1 +![hoek Logo](https://raw.github.com/hapijs/hoek/master/images/hoek.png)
2 +
3 +Utility methods for the hapi ecosystem. This module is not intended to solve every problem for everyone, but rather as a central place to store hapi-specific methods. If you're looking for a general purpose utility module, check out [lodash](https://github.com/lodash/lodash) or [underscore](https://github.com/jashkenas/underscore).
4 +
5 +[![Build Status](https://secure.travis-ci.org/hapijs/hoek.svg)](http://travis-ci.org/hapijs/hoek)
6 +
7 +<a href="https://andyet.com"><img src="https://s3.amazonaws.com/static.andyet.com/images/%26yet-logo.svg" align="right" /></a>
8 +
9 +Lead Maintainer: [Nathan LaFreniere](https://github.com/nlf)
10 +
11 +**hoek** is sponsored by [&yet](https://andyet.com)
12 +
13 +## Usage
14 +
15 +The *Hoek* library contains some common functions used within the hapi ecosystem. It comes with useful methods for Arrays (clone, merge, applyToDefaults), Objects (removeKeys, copy), Asserting and more.
16 +
17 +For example, to use Hoek to set configuration with default options:
18 +```javascript
19 +const Hoek = require('hoek');
20 +
21 +const default = {url : "www.github.com", port : "8000", debug : true};
22 +
23 +const config = Hoek.applyToDefaults(default, {port : "3000", admin : true});
24 +
25 +// In this case, config would be { url: 'www.github.com', port: '3000', debug: true, admin: true }
26 +```
27 +
28 +## Documentation
29 +
30 +[**API Reference**](API.md)
1 +'use strict';
2 +
3 +// Declare internals
4 +
5 +const internals = {};
6 +
7 +
8 +exports.escapeJavaScript = function (input) {
9 +
10 + if (!input) {
11 + return '';
12 + }
13 +
14 + let escaped = '';
15 +
16 + for (let i = 0; i < input.length; ++i) {
17 +
18 + const charCode = input.charCodeAt(i);
19 +
20 + if (internals.isSafe(charCode)) {
21 + escaped += input[i];
22 + }
23 + else {
24 + escaped += internals.escapeJavaScriptChar(charCode);
25 + }
26 + }
27 +
28 + return escaped;
29 +};
30 +
31 +
32 +exports.escapeHtml = function (input) {
33 +
34 + if (!input) {
35 + return '';
36 + }
37 +
38 + let escaped = '';
39 +
40 + for (let i = 0; i < input.length; ++i) {
41 +
42 + const charCode = input.charCodeAt(i);
43 +
44 + if (internals.isSafe(charCode)) {
45 + escaped += input[i];
46 + }
47 + else {
48 + escaped += internals.escapeHtmlChar(charCode);
49 + }
50 + }
51 +
52 + return escaped;
53 +};
54 +
55 +
56 +exports.escapeJson = function (input) {
57 +
58 + if (!input) {
59 + return '';
60 + }
61 +
62 + const lessThan = 0x3C;
63 + const greaterThan = 0x3E;
64 + const andSymbol = 0x26;
65 + const lineSeperator = 0x2028;
66 +
67 + // replace method
68 + let charCode;
69 + return input.replace(/[<>&\u2028\u2029]/g, (match) => {
70 +
71 + charCode = match.charCodeAt(0);
72 +
73 + if (charCode === lessThan) {
74 + return '\\u003c';
75 + }
76 + else if (charCode === greaterThan) {
77 + return '\\u003e';
78 + }
79 + else if (charCode === andSymbol) {
80 + return '\\u0026';
81 + }
82 + else if (charCode === lineSeperator) {
83 + return '\\u2028';
84 + }
85 + return '\\u2029';
86 + });
87 +};
88 +
89 +
90 +internals.escapeJavaScriptChar = function (charCode) {
91 +
92 + if (charCode >= 256) {
93 + return '\\u' + internals.padLeft('' + charCode, 4);
94 + }
95 +
96 + const hexValue = Buffer.from(String.fromCharCode(charCode), 'ascii').toString('hex');
97 + return '\\x' + internals.padLeft(hexValue, 2);
98 +};
99 +
100 +
101 +internals.escapeHtmlChar = function (charCode) {
102 +
103 + const namedEscape = internals.namedHtml[charCode];
104 + if (typeof namedEscape !== 'undefined') {
105 + return namedEscape;
106 + }
107 +
108 + if (charCode >= 256) {
109 + return '&#' + charCode + ';';
110 + }
111 +
112 + const hexValue = Buffer.from(String.fromCharCode(charCode), 'ascii').toString('hex');
113 + return '&#x' + internals.padLeft(hexValue, 2) + ';';
114 +};
115 +
116 +
117 +internals.padLeft = function (str, len) {
118 +
119 + while (str.length < len) {
120 + str = '0' + str;
121 + }
122 +
123 + return str;
124 +};
125 +
126 +
127 +internals.isSafe = function (charCode) {
128 +
129 + return (typeof internals.safeCharCodes[charCode] !== 'undefined');
130 +};
131 +
132 +
133 +internals.namedHtml = {
134 + '38': '&amp;',
135 + '60': '&lt;',
136 + '62': '&gt;',
137 + '34': '&quot;',
138 + '160': '&nbsp;',
139 + '162': '&cent;',
140 + '163': '&pound;',
141 + '164': '&curren;',
142 + '169': '&copy;',
143 + '174': '&reg;'
144 +};
145 +
146 +
147 +internals.safeCharCodes = (function () {
148 +
149 + const safe = {};
150 +
151 + for (let i = 32; i < 123; ++i) {
152 +
153 + if ((i >= 97) || // a-z
154 + (i >= 65 && i <= 90) || // A-Z
155 + (i >= 48 && i <= 57) || // 0-9
156 + i === 32 || // space
157 + i === 46 || // .
158 + i === 44 || // ,
159 + i === 45 || // -
160 + i === 58 || // :
161 + i === 95) { // _
162 +
163 + safe[i] = null;
164 + }
165 + }
166 +
167 + return safe;
168 +}());
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Assert = require('assert');
6 +const Crypto = require('crypto');
7 +const Path = require('path');
8 +const Util = require('util');
9 +
10 +const Escape = require('./escape');
11 +
12 +
13 +// Declare internals
14 +
15 +const internals = {};
16 +
17 +
18 +// Clone object or array
19 +
20 +exports.clone = function (obj, seen) {
21 +
22 + if (typeof obj !== 'object' ||
23 + obj === null) {
24 +
25 + return obj;
26 + }
27 +
28 + seen = seen || new Map();
29 +
30 + const lookup = seen.get(obj);
31 + if (lookup) {
32 + return lookup;
33 + }
34 +
35 + let newObj;
36 + let cloneDeep = false;
37 +
38 + if (!Array.isArray(obj)) {
39 + if (Buffer.isBuffer(obj)) {
40 + newObj = Buffer.from(obj);
41 + }
42 + else if (obj instanceof Date) {
43 + newObj = new Date(obj.getTime());
44 + }
45 + else if (obj instanceof RegExp) {
46 + newObj = new RegExp(obj);
47 + }
48 + else {
49 + const proto = Object.getPrototypeOf(obj);
50 + if (proto &&
51 + proto.isImmutable) {
52 +
53 + newObj = obj;
54 + }
55 + else {
56 + newObj = Object.create(proto);
57 + cloneDeep = true;
58 + }
59 + }
60 + }
61 + else {
62 + newObj = [];
63 + cloneDeep = true;
64 + }
65 +
66 + seen.set(obj, newObj);
67 +
68 + if (cloneDeep) {
69 + const keys = Object.getOwnPropertyNames(obj);
70 + for (let i = 0; i < keys.length; ++i) {
71 + const key = keys[i];
72 + const descriptor = Object.getOwnPropertyDescriptor(obj, key);
73 + if (descriptor &&
74 + (descriptor.get ||
75 + descriptor.set)) {
76 +
77 + Object.defineProperty(newObj, key, descriptor);
78 + }
79 + else {
80 + newObj[key] = exports.clone(obj[key], seen);
81 + }
82 + }
83 + }
84 +
85 + return newObj;
86 +};
87 +
88 +
89 +// Merge all the properties of source into target, source wins in conflict, and by default null and undefined from source are applied
90 +
91 +/*eslint-disable */
92 +exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) {
93 + /*eslint-enable */
94 +
95 + exports.assert(target && typeof target === 'object', 'Invalid target value: must be an object');
96 + exports.assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object');
97 +
98 + if (!source) {
99 + return target;
100 + }
101 +
102 + if (Array.isArray(source)) {
103 + exports.assert(Array.isArray(target), 'Cannot merge array onto an object');
104 + if (isMergeArrays === false) { // isMergeArrays defaults to true
105 + target.length = 0; // Must not change target assignment
106 + }
107 +
108 + for (let i = 0; i < source.length; ++i) {
109 + target.push(exports.clone(source[i]));
110 + }
111 +
112 + return target;
113 + }
114 +
115 + const keys = Object.keys(source);
116 + for (let i = 0; i < keys.length; ++i) {
117 + const key = keys[i];
118 + if (key === '__proto__') {
119 + continue;
120 + }
121 +
122 + const value = source[key];
123 + if (value &&
124 + typeof value === 'object') {
125 +
126 + if (!target[key] ||
127 + typeof target[key] !== 'object' ||
128 + (Array.isArray(target[key]) !== Array.isArray(value)) ||
129 + value instanceof Date ||
130 + Buffer.isBuffer(value) ||
131 + value instanceof RegExp) {
132 +
133 + target[key] = exports.clone(value);
134 + }
135 + else {
136 + exports.merge(target[key], value, isNullOverride, isMergeArrays);
137 + }
138 + }
139 + else {
140 + if (value !== null &&
141 + value !== undefined) { // Explicit to preserve empty strings
142 +
143 + target[key] = value;
144 + }
145 + else if (isNullOverride !== false) { // Defaults to true
146 + target[key] = value;
147 + }
148 + }
149 + }
150 +
151 + return target;
152 +};
153 +
154 +
155 +// Apply options to a copy of the defaults
156 +
157 +exports.applyToDefaults = function (defaults, options, isNullOverride) {
158 +
159 + exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object');
160 + exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object');
161 +
162 + if (!options) { // If no options, return null
163 + return null;
164 + }
165 +
166 + const copy = exports.clone(defaults);
167 +
168 + if (options === true) { // If options is set to true, use defaults
169 + return copy;
170 + }
171 +
172 + return exports.merge(copy, options, isNullOverride === true, false);
173 +};
174 +
175 +
176 +// Clone an object except for the listed keys which are shallow copied
177 +
178 +exports.cloneWithShallow = function (source, keys) {
179 +
180 + if (!source ||
181 + typeof source !== 'object') {
182 +
183 + return source;
184 + }
185 +
186 + const storage = internals.store(source, keys); // Move shallow copy items to storage
187 + const copy = exports.clone(source); // Deep copy the rest
188 + internals.restore(copy, source, storage); // Shallow copy the stored items and restore
189 + return copy;
190 +};
191 +
192 +
193 +internals.store = function (source, keys) {
194 +
195 + const storage = {};
196 + for (let i = 0; i < keys.length; ++i) {
197 + const key = keys[i];
198 + const value = exports.reach(source, key);
199 + if (value !== undefined) {
200 + storage[key] = value;
201 + internals.reachSet(source, key, undefined);
202 + }
203 + }
204 +
205 + return storage;
206 +};
207 +
208 +
209 +internals.restore = function (copy, source, storage) {
210 +
211 + const keys = Object.keys(storage);
212 + for (let i = 0; i < keys.length; ++i) {
213 + const key = keys[i];
214 + internals.reachSet(copy, key, storage[key]);
215 + internals.reachSet(source, key, storage[key]);
216 + }
217 +};
218 +
219 +
220 +internals.reachSet = function (obj, key, value) {
221 +
222 + const path = key.split('.');
223 + let ref = obj;
224 + for (let i = 0; i < path.length; ++i) {
225 + const segment = path[i];
226 + if (i + 1 === path.length) {
227 + ref[segment] = value;
228 + }
229 +
230 + ref = ref[segment];
231 + }
232 +};
233 +
234 +
235 +// Apply options to defaults except for the listed keys which are shallow copied from option without merging
236 +
237 +exports.applyToDefaultsWithShallow = function (defaults, options, keys) {
238 +
239 + exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object');
240 + exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object');
241 + exports.assert(keys && Array.isArray(keys), 'Invalid keys');
242 +
243 + if (!options) { // If no options, return null
244 + return null;
245 + }
246 +
247 + const copy = exports.cloneWithShallow(defaults, keys);
248 +
249 + if (options === true) { // If options is set to true, use defaults
250 + return copy;
251 + }
252 +
253 + const storage = internals.store(options, keys); // Move shallow copy items to storage
254 + exports.merge(copy, options, false, false); // Deep copy the rest
255 + internals.restore(copy, options, storage); // Shallow copy the stored items and restore
256 + return copy;
257 +};
258 +
259 +
260 +// Deep object or array comparison
261 +
262 +exports.deepEqual = function (obj, ref, options, seen) {
263 +
264 + options = options || { prototype: true };
265 +
266 + const type = typeof obj;
267 +
268 + if (type !== typeof ref) {
269 + return false;
270 + }
271 +
272 + if (type !== 'object' ||
273 + obj === null ||
274 + ref === null) {
275 +
276 + if (obj === ref) { // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql
277 + return obj !== 0 || 1 / obj === 1 / ref; // -0 / +0
278 + }
279 +
280 + return obj !== obj && ref !== ref; // NaN
281 + }
282 +
283 + seen = seen || [];
284 + if (seen.indexOf(obj) !== -1) {
285 + return true; // If previous comparison failed, it would have stopped execution
286 + }
287 +
288 + seen.push(obj);
289 +
290 + if (Array.isArray(obj)) {
291 + if (!Array.isArray(ref)) {
292 + return false;
293 + }
294 +
295 + if (!options.part && obj.length !== ref.length) {
296 + return false;
297 + }
298 +
299 + for (let i = 0; i < obj.length; ++i) {
300 + if (options.part) {
301 + let found = false;
302 + for (let j = 0; j < ref.length; ++j) {
303 + if (exports.deepEqual(obj[i], ref[j], options)) {
304 + found = true;
305 + break;
306 + }
307 + }
308 +
309 + return found;
310 + }
311 +
312 + if (!exports.deepEqual(obj[i], ref[i], options)) {
313 + return false;
314 + }
315 + }
316 +
317 + return true;
318 + }
319 +
320 + if (Buffer.isBuffer(obj)) {
321 + if (!Buffer.isBuffer(ref)) {
322 + return false;
323 + }
324 +
325 + if (obj.length !== ref.length) {
326 + return false;
327 + }
328 +
329 + for (let i = 0; i < obj.length; ++i) {
330 + if (obj[i] !== ref[i]) {
331 + return false;
332 + }
333 + }
334 +
335 + return true;
336 + }
337 +
338 + if (obj instanceof Date) {
339 + return (ref instanceof Date && obj.getTime() === ref.getTime());
340 + }
341 +
342 + if (obj instanceof RegExp) {
343 + return (ref instanceof RegExp && obj.toString() === ref.toString());
344 + }
345 +
346 + if (options.prototype) {
347 + if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) {
348 + return false;
349 + }
350 + }
351 +
352 + const keys = Object.getOwnPropertyNames(obj);
353 +
354 + if (!options.part && keys.length !== Object.getOwnPropertyNames(ref).length) {
355 + return false;
356 + }
357 +
358 + for (let i = 0; i < keys.length; ++i) {
359 + const key = keys[i];
360 + const descriptor = Object.getOwnPropertyDescriptor(obj, key);
361 + if (descriptor.get) {
362 + if (!exports.deepEqual(descriptor, Object.getOwnPropertyDescriptor(ref, key), options, seen)) {
363 + return false;
364 + }
365 + }
366 + else if (!exports.deepEqual(obj[key], ref[key], options, seen)) {
367 + return false;
368 + }
369 + }
370 +
371 + return true;
372 +};
373 +
374 +
375 +// Remove duplicate items from array
376 +
377 +exports.unique = (array, key) => {
378 +
379 + let result;
380 + if (key) {
381 + result = [];
382 + const index = new Set();
383 + array.forEach((item) => {
384 +
385 + const identifier = item[key];
386 + if (!index.has(identifier)) {
387 + index.add(identifier);
388 + result.push(item);
389 + }
390 + });
391 + }
392 + else {
393 + result = Array.from(new Set(array));
394 + }
395 +
396 + return result;
397 +};
398 +
399 +
400 +// Convert array into object
401 +
402 +exports.mapToObject = function (array, key) {
403 +
404 + if (!array) {
405 + return null;
406 + }
407 +
408 + const obj = {};
409 + for (let i = 0; i < array.length; ++i) {
410 + if (key) {
411 + if (array[i][key]) {
412 + obj[array[i][key]] = true;
413 + }
414 + }
415 + else {
416 + obj[array[i]] = true;
417 + }
418 + }
419 +
420 + return obj;
421 +};
422 +
423 +
424 +// Find the common unique items in two arrays
425 +
426 +exports.intersect = function (array1, array2, justFirst) {
427 +
428 + if (!array1 || !array2) {
429 + return [];
430 + }
431 +
432 + const common = [];
433 + const hash = (Array.isArray(array1) ? exports.mapToObject(array1) : array1);
434 + const found = {};
435 + for (let i = 0; i < array2.length; ++i) {
436 + if (hash[array2[i]] && !found[array2[i]]) {
437 + if (justFirst) {
438 + return array2[i];
439 + }
440 +
441 + common.push(array2[i]);
442 + found[array2[i]] = true;
443 + }
444 + }
445 +
446 + return (justFirst ? null : common);
447 +};
448 +
449 +
450 +// Test if the reference contains the values
451 +
452 +exports.contain = function (ref, values, options) {
453 +
454 + /*
455 + string -> string(s)
456 + array -> item(s)
457 + object -> key(s)
458 + object -> object (key:value)
459 + */
460 +
461 + let valuePairs = null;
462 + if (typeof ref === 'object' &&
463 + typeof values === 'object' &&
464 + !Array.isArray(ref) &&
465 + !Array.isArray(values)) {
466 +
467 + valuePairs = values;
468 + values = Object.keys(values);
469 + }
470 + else {
471 + values = [].concat(values);
472 + }
473 +
474 + options = options || {}; // deep, once, only, part
475 +
476 + exports.assert(typeof ref === 'string' || typeof ref === 'object', 'Reference must be string or an object');
477 + exports.assert(values.length, 'Values array cannot be empty');
478 +
479 + let compare;
480 + let compareFlags;
481 + if (options.deep) {
482 + compare = exports.deepEqual;
483 +
484 + const hasOnly = options.hasOwnProperty('only');
485 + const hasPart = options.hasOwnProperty('part');
486 +
487 + compareFlags = {
488 + prototype: hasOnly ? options.only : hasPart ? !options.part : false,
489 + part: hasOnly ? !options.only : hasPart ? options.part : true
490 + };
491 + }
492 + else {
493 + compare = (a, b) => a === b;
494 + }
495 +
496 + let misses = false;
497 + const matches = new Array(values.length);
498 + for (let i = 0; i < matches.length; ++i) {
499 + matches[i] = 0;
500 + }
501 +
502 + if (typeof ref === 'string') {
503 + let pattern = '(';
504 + for (let i = 0; i < values.length; ++i) {
505 + const value = values[i];
506 + exports.assert(typeof value === 'string', 'Cannot compare string reference to non-string value');
507 + pattern += (i ? '|' : '') + exports.escapeRegex(value);
508 + }
509 +
510 + const regex = new RegExp(pattern + ')', 'g');
511 + const leftovers = ref.replace(regex, ($0, $1) => {
512 +
513 + const index = values.indexOf($1);
514 + ++matches[index];
515 + return ''; // Remove from string
516 + });
517 +
518 + misses = !!leftovers;
519 + }
520 + else if (Array.isArray(ref)) {
521 + for (let i = 0; i < ref.length; ++i) {
522 + let matched = false;
523 + for (let j = 0; j < values.length && matched === false; ++j) {
524 + matched = compare(values[j], ref[i], compareFlags) && j;
525 + }
526 +
527 + if (matched !== false) {
528 + ++matches[matched];
529 + }
530 + else {
531 + misses = true;
532 + }
533 + }
534 + }
535 + else {
536 + const keys = Object.getOwnPropertyNames(ref);
537 + for (let i = 0; i < keys.length; ++i) {
538 + const key = keys[i];
539 + const pos = values.indexOf(key);
540 + if (pos !== -1) {
541 + if (valuePairs &&
542 + !compare(valuePairs[key], ref[key], compareFlags)) {
543 +
544 + return false;
545 + }
546 +
547 + ++matches[pos];
548 + }
549 + else {
550 + misses = true;
551 + }
552 + }
553 + }
554 +
555 + let result = false;
556 + for (let i = 0; i < matches.length; ++i) {
557 + result = result || !!matches[i];
558 + if ((options.once && matches[i] > 1) ||
559 + (!options.part && !matches[i])) {
560 +
561 + return false;
562 + }
563 + }
564 +
565 + if (options.only &&
566 + misses) {
567 +
568 + return false;
569 + }
570 +
571 + return result;
572 +};
573 +
574 +
575 +// Flatten array
576 +
577 +exports.flatten = function (array, target) {
578 +
579 + const result = target || [];
580 +
581 + for (let i = 0; i < array.length; ++i) {
582 + if (Array.isArray(array[i])) {
583 + exports.flatten(array[i], result);
584 + }
585 + else {
586 + result.push(array[i]);
587 + }
588 + }
589 +
590 + return result;
591 +};
592 +
593 +
594 +// Convert an object key chain string ('a.b.c') to reference (object[a][b][c])
595 +
596 +exports.reach = function (obj, chain, options) {
597 +
598 + if (chain === false ||
599 + chain === null ||
600 + typeof chain === 'undefined') {
601 +
602 + return obj;
603 + }
604 +
605 + options = options || {};
606 + if (typeof options === 'string') {
607 + options = { separator: options };
608 + }
609 +
610 + const path = chain.split(options.separator || '.');
611 + let ref = obj;
612 + for (let i = 0; i < path.length; ++i) {
613 + let key = path[i];
614 + if (key[0] === '-' && Array.isArray(ref)) {
615 + key = key.slice(1, key.length);
616 + key = ref.length - key;
617 + }
618 +
619 + if (!ref ||
620 + !((typeof ref === 'object' || typeof ref === 'function') && key in ref) ||
621 + (typeof ref !== 'object' && options.functions === false)) { // Only object and function can have properties
622 +
623 + exports.assert(!options.strict || i + 1 === path.length, 'Missing segment', key, 'in reach path ', chain);
624 + exports.assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', key, 'in reach path ', chain);
625 + ref = options.default;
626 + break;
627 + }
628 +
629 + ref = ref[key];
630 + }
631 +
632 + return ref;
633 +};
634 +
635 +
636 +exports.reachTemplate = function (obj, template, options) {
637 +
638 + return template.replace(/{([^}]+)}/g, ($0, chain) => {
639 +
640 + const value = exports.reach(obj, chain, options);
641 + return (value === undefined || value === null ? '' : value);
642 + });
643 +};
644 +
645 +
646 +exports.formatStack = function (stack) {
647 +
648 + const trace = [];
649 + for (let i = 0; i < stack.length; ++i) {
650 + const item = stack[i];
651 + trace.push([item.getFileName(), item.getLineNumber(), item.getColumnNumber(), item.getFunctionName(), item.isConstructor()]);
652 + }
653 +
654 + return trace;
655 +};
656 +
657 +
658 +exports.formatTrace = function (trace) {
659 +
660 + const display = [];
661 +
662 + for (let i = 0; i < trace.length; ++i) {
663 + const row = trace[i];
664 + display.push((row[4] ? 'new ' : '') + row[3] + ' (' + row[0] + ':' + row[1] + ':' + row[2] + ')');
665 + }
666 +
667 + return display;
668 +};
669 +
670 +
671 +exports.callStack = function (slice) {
672 +
673 + // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
674 +
675 + const v8 = Error.prepareStackTrace;
676 + Error.prepareStackTrace = function (_, stack) {
677 +
678 + return stack;
679 + };
680 +
681 + const capture = {};
682 + Error.captureStackTrace(capture, this);
683 + const stack = capture.stack;
684 +
685 + Error.prepareStackTrace = v8;
686 +
687 + const trace = exports.formatStack(stack);
688 +
689 + return trace.slice(1 + slice);
690 +};
691 +
692 +
693 +exports.displayStack = function (slice) {
694 +
695 + const trace = exports.callStack(slice === undefined ? 1 : slice + 1);
696 +
697 + return exports.formatTrace(trace);
698 +};
699 +
700 +
701 +exports.abortThrow = false;
702 +
703 +
704 +exports.abort = function (message, hideStack) {
705 +
706 + if (process.env.NODE_ENV === 'test' || exports.abortThrow === true) {
707 + throw new Error(message || 'Unknown error');
708 + }
709 +
710 + let stack = '';
711 + if (!hideStack) {
712 + stack = exports.displayStack(1).join('\n\t');
713 + }
714 + console.log('ABORT: ' + message + '\n\t' + stack);
715 + process.exit(1);
716 +};
717 +
718 +
719 +exports.assert = function (condition, ...args) {
720 +
721 + if (condition) {
722 + return;
723 + }
724 +
725 + if (args.length === 1 && args[0] instanceof Error) {
726 + throw args[0];
727 + }
728 +
729 + const msgs = args
730 + .filter((arg) => arg !== '')
731 + .map((arg) => {
732 +
733 + return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : exports.stringify(arg);
734 + });
735 +
736 + throw new Assert.AssertionError({
737 + message: msgs.join(' ') || 'Unknown error',
738 + actual: false,
739 + expected: true,
740 + operator: '==',
741 + stackStartFunction: exports.assert
742 + });
743 +};
744 +
745 +
746 +exports.Bench = function () {
747 +
748 + this.ts = 0;
749 + this.reset();
750 +};
751 +
752 +
753 +exports.Bench.prototype.reset = function () {
754 +
755 + this.ts = exports.Bench.now();
756 +};
757 +
758 +
759 +exports.Bench.prototype.elapsed = function () {
760 +
761 + return exports.Bench.now() - this.ts;
762 +};
763 +
764 +
765 +exports.Bench.now = function () {
766 +
767 + const ts = process.hrtime();
768 + return (ts[0] * 1e3) + (ts[1] / 1e6);
769 +};
770 +
771 +
772 +// Escape string for Regex construction
773 +
774 +exports.escapeRegex = function (string) {
775 +
776 + // Escape ^$.*+-?=!:|\/()[]{},
777 + return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&');
778 +};
779 +
780 +
781 +// Base64url (RFC 4648) encode
782 +
783 +exports.base64urlEncode = function (value, encoding) {
784 +
785 + exports.assert(typeof value === 'string' || Buffer.isBuffer(value), 'value must be string or buffer');
786 + const buf = (Buffer.isBuffer(value) ? value : Buffer.from(value, encoding || 'binary'));
787 + return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
788 +};
789 +
790 +
791 +// Base64url (RFC 4648) decode
792 +
793 +exports.base64urlDecode = function (value, encoding) {
794 +
795 + if (typeof value !== 'string') {
796 +
797 + throw new Error('Value not a string');
798 + }
799 +
800 + if (!/^[\w\-]*$/.test(value)) {
801 +
802 + throw new Error('Invalid character');
803 + }
804 +
805 + const buf = Buffer.from(value, 'base64');
806 + return (encoding === 'buffer' ? buf : buf.toString(encoding || 'binary'));
807 +};
808 +
809 +
810 +// Escape attribute value for use in HTTP header
811 +
812 +exports.escapeHeaderAttribute = function (attribute) {
813 +
814 + // Allowed value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
815 +
816 + exports.assert(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/.test(attribute), 'Bad attribute value (' + attribute + ')');
817 +
818 + return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"'); // Escape quotes and slash
819 +};
820 +
821 +
822 +exports.escapeHtml = function (string) {
823 +
824 + return Escape.escapeHtml(string);
825 +};
826 +
827 +
828 +exports.escapeJavaScript = function (string) {
829 +
830 + return Escape.escapeJavaScript(string);
831 +};
832 +
833 +
834 +exports.escapeJson = function (string) {
835 +
836 + return Escape.escapeJson(string);
837 +};
838 +
839 +
840 +exports.once = function (method) {
841 +
842 + if (method._hoekOnce) {
843 + return method;
844 + }
845 +
846 + let once = false;
847 + const wrapped = function (...args) {
848 +
849 + if (!once) {
850 + once = true;
851 + method.apply(null, args);
852 + }
853 + };
854 +
855 + wrapped._hoekOnce = true;
856 + return wrapped;
857 +};
858 +
859 +
860 +exports.isInteger = Number.isSafeInteger;
861 +
862 +
863 +exports.ignore = function () { };
864 +
865 +
866 +exports.inherits = Util.inherits;
867 +
868 +
869 +exports.format = Util.format;
870 +
871 +
872 +exports.transform = function (source, transform, options) {
873 +
874 + exports.assert(source === null || source === undefined || typeof source === 'object' || Array.isArray(source), 'Invalid source object: must be null, undefined, an object, or an array');
875 + const separator = (typeof options === 'object' && options !== null) ? (options.separator || '.') : '.';
876 +
877 + if (Array.isArray(source)) {
878 + const results = [];
879 + for (let i = 0; i < source.length; ++i) {
880 + results.push(exports.transform(source[i], transform, options));
881 + }
882 + return results;
883 + }
884 +
885 + const result = {};
886 + const keys = Object.keys(transform);
887 +
888 + for (let i = 0; i < keys.length; ++i) {
889 + const key = keys[i];
890 + const path = key.split(separator);
891 + const sourcePath = transform[key];
892 +
893 + exports.assert(typeof sourcePath === 'string', 'All mappings must be "." delineated strings');
894 +
895 + let segment;
896 + let res = result;
897 +
898 + while (path.length > 1) {
899 + segment = path.shift();
900 + if (!res[segment]) {
901 + res[segment] = {};
902 + }
903 + res = res[segment];
904 + }
905 + segment = path.shift();
906 + res[segment] = exports.reach(source, sourcePath, options);
907 + }
908 +
909 + return result;
910 +};
911 +
912 +
913 +exports.uniqueFilename = function (path, extension) {
914 +
915 + if (extension) {
916 + extension = extension[0] !== '.' ? '.' + extension : extension;
917 + }
918 + else {
919 + extension = '';
920 + }
921 +
922 + path = Path.resolve(path);
923 + const name = [Date.now(), process.pid, Crypto.randomBytes(8).toString('hex')].join('-') + extension;
924 + return Path.join(path, name);
925 +};
926 +
927 +
928 +exports.stringify = function (...args) {
929 +
930 + try {
931 + return JSON.stringify.apply(null, args);
932 + }
933 + catch (err) {
934 + return '[Cannot display object: ' + err.message + ']';
935 + }
936 +};
937 +
938 +
939 +exports.shallow = function (source) {
940 +
941 + const target = {};
942 + const keys = Object.keys(source);
943 + for (let i = 0; i < keys.length; ++i) {
944 + const key = keys[i];
945 + target[key] = source[key];
946 + }
947 +
948 + return target;
949 +};
950 +
951 +
952 +exports.wait = function (timeout) {
953 +
954 + return new Promise((resolve) => setTimeout(resolve, timeout));
955 +};
956 +
957 +
958 +exports.block = function () {
959 +
960 + return new Promise(exports.ignore);
961 +};
1 +{
2 + "_from": "hoek@5.x.x",
3 + "_id": "hoek@5.0.3",
4 + "_inBundle": false,
5 + "_integrity": "sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw==",
6 + "_location": "/hoek",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "hoek@5.x.x",
12 + "name": "hoek",
13 + "escapedName": "hoek",
14 + "rawSpec": "5.x.x",
15 + "saveSpec": null,
16 + "fetchSpec": "5.x.x"
17 + },
18 + "_requiredBy": [
19 + "/joi",
20 + "/topo"
21 + ],
22 + "_resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.3.tgz",
23 + "_shasum": "b71d40d943d0a95da01956b547f83c4a5b4a34ac",
24 + "_spec": "hoek@5.x.x",
25 + "_where": "/Users/jeminlee/git-projects/node-practice/express-demo/node_modules/joi",
26 + "bugs": {
27 + "url": "https://github.com/hapijs/hoek/issues"
28 + },
29 + "bundleDependencies": false,
30 + "dependencies": {},
31 + "deprecated": false,
32 + "description": "General purpose node utilities",
33 + "devDependencies": {
34 + "code": "5.x.x",
35 + "lab": "15.x.x"
36 + },
37 + "engines": {
38 + "node": ">=8.9.0"
39 + },
40 + "homepage": "https://github.com/hapijs/hoek#readme",
41 + "keywords": [
42 + "utilities"
43 + ],
44 + "license": "BSD-3-Clause",
45 + "main": "lib/index.js",
46 + "name": "hoek",
47 + "repository": {
48 + "type": "git",
49 + "url": "git://github.com/hapijs/hoek.git"
50 + },
51 + "scripts": {
52 + "test": "lab -a code -t 100 -L",
53 + "test-cov-html": "lab -a code -t 100 -L -r html -o coverage.html"
54 + },
55 + "version": "5.0.3"
56 +}
1 +Copyright (c) 2014-2015, Eli Skeggs and Project contributors
2 +Copyright (c) 2013-2014, GlobeSherpa
3 +Copyright (c) 2008-2011, Dominic Sayers
4 +All rights reserved.
5 +
6 +Redistribution and use in source and binary forms, with or without
7 +modification, are permitted provided that the following conditions are met:
8 + * Redistributions of source code must retain the above copyright
9 + notice, this list of conditions and the following disclaimer.
10 + * Redistributions in binary form must reproduce the above copyright
11 + notice, this list of conditions and the following disclaimer in the
12 + documentation and/or other materials provided with the distribution.
13 + * The names of any contributors may not be used to endorse or promote
14 + products derived from this software without specific prior written
15 + permission.
16 +
17 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
21 +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 +
28 + * * *
29 +
30 +The complete list of contributors can be found at: https://github.com/hapijs/isemail/graphs/contributors
31 +Previously published under the 2-Clause-BSD license published here: https://github.com/hapijs/isemail/blob/v1.2.0/LICENSE
32 +
1 +# isemail
2 +
3 +Node email address validation library
4 +
5 +[![Build Status](https://travis-ci.org/hapijs/isemail.svg?branch=master)](https://travis-ci.org/hapijs/isemail)<a href="#footnote-1"><sup>&#91;1&#93;</sup></a>
6 +
7 +Lead Maintainer: [Eli Skeggs][skeggse]
8 +
9 +This library is a port of the PHP `is_email` function by Dominic Sayers.
10 +
11 +Install
12 +=======
13 +
14 +```sh
15 +$ npm install isemail
16 +```
17 +
18 +Test
19 +====
20 +
21 +The tests were pulled from `is_email`'s extensive [test suite][tests] on October 15, 2013. Many thanks to the contributors! Additional tests have been added to increase code coverage and verify edge-cases.
22 +
23 +Run any of the following.
24 +
25 +```sh
26 +$ lab
27 +$ npm test
28 +$ make test
29 +```
30 +
31 +_remember to_ `npm install` to get the development dependencies!
32 +
33 +API
34 +===
35 +
36 +validate(email, [options])
37 +--------------------------
38 +
39 +Determines whether the `email` is valid or not, for various definitions thereof. Optionally accepts an `options` object. Options may include `errorLevel`.
40 +
41 +Use `errorLevel` to specify the type of result for `validate()`. Passing a `false` literal will result in a true or false boolean indicating whether the email address is sufficiently defined for use in sending an email. Passing a `true` literal will result in a more granular numeric status, with zero being a perfectly valid email address. Passing a number will return `0` if the numeric status is below the `errorLevel` and the numeric status otherwise.
42 +
43 +The `tldBlacklist` option can be either an object lookup table or an array of invalid top-level domains. If the email address has a top-level domain that is in the whitelist, the email will be marked as invalid.
44 +
45 +The `tldWhitelist` option can be either an object lookup table or an array of valid top-level domains. If the email address has a top-level domain that is not in the whitelist, the email will be marked as invalid.
46 +
47 +The `allowUnicode` option governs whether non-ASCII characters are allowed. Defaults to `true` per RFC 6530.
48 +
49 +Only one of `tldBlacklist` and `tldWhitelist` will be consulted for TLD validity.
50 +
51 +The `minDomainAtoms` option is an optional positive integer that specifies the minimum number of domain atoms that must be included for the email address to be considered valid. Be careful with the option, as some top-level domains, like `io`, directly support email addresses.
52 +
53 +As of `3.1.1`, the `callback` parameter is deprecated, and will be removed in `4.0.0`.
54 +
55 +### Examples
56 +
57 +```js
58 +$ node
59 +> var Isemail = require('isemail');
60 +undefined
61 +> Isemail.validate('test@iana.org');
62 +true
63 +> Isemail.validate('test@iana.org', {errorLevel: true});
64 +0
65 +> Isemail.validate('test@e.com', {errorLevel: true});
66 +6
67 +> Isemail.validate('test@e.com', {errorLevel: 7});
68 +0
69 +> Isemail.validate('test@e.com', {errorLevel: 6});
70 +6
71 +```
72 +
73 +<sup name="footnote-1">&#91;1&#93;</sup>: if this badge indicates the build is passing, then isemail has 100% code coverage.
74 +
75 +[skeggse]: https://github.com/skeggse "Eli Skeggs"
76 +[tests]: http://isemail.info/_system/is_email/test/?all‎ "is_email test suite"
1 +// Code shows a string is valid,
2 +// but docs require string array or object.
3 +type TLDList = string | string[] | { [topLevelDomain: string]: any };
4 +
5 +type BaseOptions = {
6 + tldWhitelist?: TLDList;
7 + tldBlacklist?: TLDList;
8 + minDomainAtoms?: number;
9 + allowUnicode?: boolean;
10 +};
11 +
12 +type OptionsWithBool = BaseOptions & {
13 + errorLevel?: false;
14 +};
15 +
16 +type OptionsWithNumThreshold = BaseOptions & {
17 + errorLevel?: true | number;
18 +};
19 +
20 +interface Validator {
21 + /**
22 + * Check that an email address conforms to RFCs 5321, 5322, 6530 and others.
23 + *
24 + * The callback function will always be called
25 + * with the result of the operation.
26 + *
27 + * ```
28 + * import * as IsEmail from "isemail";
29 + *
30 + * const log = result => console.log(`Result: ${result}`);
31 + * IsEmail.validate("test@e.com");
32 + * // => true
33 + * ```
34 + */
35 + validate(email: string): boolean;
36 +
37 + /**
38 + * Check that an email address conforms to RFCs 5321, 5322, 6530 and others.
39 + *
40 + * The callback function will always be called
41 + * with the result of the operation.
42 + *
43 + * ```
44 + * import * as IsEmail from "isemail";
45 + *
46 + * IsEmail.validate("test@iana.org", { errorLevel: false });
47 + * // => true
48 + * ```
49 + */
50 + validate(email: string, options: OptionsWithBool): boolean;
51 +
52 + /**
53 + * Check that an email address conforms to RFCs 5321, 5322, 6530 and others.
54 + *
55 + * The callback function will always be called
56 + * with the result of the operation.
57 + *
58 + * ```
59 + * import * as IsEmail from "isemail";
60 + *
61 + * IsEmail.validate("test@iana.org", { errorLevel: true });
62 + * // => 0
63 + * IsEmail.validate("test @e.com", { errorLevel: 50 });
64 + * // => 0
65 + * IsEmail.validate('test @e.com', { errorLevel: true })
66 + * // => 49
67 + * ```
68 + */
69 + validate(email: string, options: OptionsWithNumThreshold): number;
70 +}
71 +
72 +declare const IsEmail: Validator;
73 +
74 +export = IsEmail;
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Punycode = require('punycode');
6 +
7 +// Declare internals
8 +
9 +const internals = {
10 + hasOwn: Object.prototype.hasOwnProperty,
11 + indexOf: Array.prototype.indexOf,
12 + defaultThreshold: 16,
13 + maxIPv6Groups: 8,
14 +
15 + categories: {
16 + valid: 1,
17 + dnsWarn: 7,
18 + rfc5321: 15,
19 + cfws: 31,
20 + deprecated: 63,
21 + rfc5322: 127,
22 + error: 255
23 + },
24 +
25 + diagnoses: {
26 +
27 + // Address is valid
28 +
29 + valid: 0,
30 +
31 + // Address is valid for SMTP but has unusual elements
32 +
33 + rfc5321TLD: 9,
34 + rfc5321TLDNumeric: 10,
35 + rfc5321QuotedString: 11,
36 + rfc5321AddressLiteral: 12,
37 +
38 + // Address is valid for message, but must be modified for envelope
39 +
40 + cfwsComment: 17,
41 + cfwsFWS: 18,
42 +
43 + // Address contains non-ASCII when the allowUnicode option is false
44 + // Has to be > internals.defaultThreshold so that it's rejected
45 + // without an explicit errorLevel:
46 + undesiredNonAscii: 25,
47 +
48 + // Address contains deprecated elements, but may still be valid in some contexts
49 +
50 + deprecatedLocalPart: 33,
51 + deprecatedFWS: 34,
52 + deprecatedQTEXT: 35,
53 + deprecatedQP: 36,
54 + deprecatedComment: 37,
55 + deprecatedCTEXT: 38,
56 + deprecatedIPv6: 39,
57 + deprecatedCFWSNearAt: 49,
58 +
59 + // Address is only valid according to broad definition in RFC 5322, but is otherwise invalid
60 +
61 + rfc5322Domain: 65,
62 + rfc5322TooLong: 66,
63 + rfc5322LocalTooLong: 67,
64 + rfc5322DomainTooLong: 68,
65 + rfc5322LabelTooLong: 69,
66 + rfc5322DomainLiteral: 70,
67 + rfc5322DomainLiteralOBSDText: 71,
68 + rfc5322IPv6GroupCount: 72,
69 + rfc5322IPv62x2xColon: 73,
70 + rfc5322IPv6BadCharacter: 74,
71 + rfc5322IPv6MaxGroups: 75,
72 + rfc5322IPv6ColonStart: 76,
73 + rfc5322IPv6ColonEnd: 77,
74 +
75 + // Address is invalid for any purpose
76 +
77 + errExpectingDTEXT: 129,
78 + errNoLocalPart: 130,
79 + errNoDomain: 131,
80 + errConsecutiveDots: 132,
81 + errATEXTAfterCFWS: 133,
82 + errATEXTAfterQS: 134,
83 + errATEXTAfterDomainLiteral: 135,
84 + errExpectingQPair: 136,
85 + errExpectingATEXT: 137,
86 + errExpectingQTEXT: 138,
87 + errExpectingCTEXT: 139,
88 + errBackslashEnd: 140,
89 + errDotStart: 141,
90 + errDotEnd: 142,
91 + errDomainHyphenStart: 143,
92 + errDomainHyphenEnd: 144,
93 + errUnclosedQuotedString: 145,
94 + errUnclosedComment: 146,
95 + errUnclosedDomainLiteral: 147,
96 + errFWSCRLFx2: 148,
97 + errFWSCRLFEnd: 149,
98 + errCRNoLF: 150,
99 + errUnknownTLD: 160,
100 + errDomainTooShort: 161
101 + },
102 +
103 + components: {
104 + localpart: 0,
105 + domain: 1,
106 + literal: 2,
107 + contextComment: 3,
108 + contextFWS: 4,
109 + contextQuotedString: 5,
110 + contextQuotedPair: 6
111 + }
112 +};
113 +
114 +
115 +internals.specials = function () {
116 +
117 + const specials = '()<>[]:;@\\,."'; // US-ASCII visible characters not valid for atext (http://tools.ietf.org/html/rfc5322#section-3.2.3)
118 + const lookup = new Array(0x100);
119 + lookup.fill(false);
120 +
121 + for (let i = 0; i < specials.length; ++i) {
122 + lookup[specials.codePointAt(i)] = true;
123 + }
124 +
125 + return function (code) {
126 +
127 + return lookup[code];
128 + };
129 +}();
130 +
131 +internals.c0Controls = function () {
132 +
133 + const lookup = new Array(0x100);
134 + lookup.fill(false);
135 +
136 + // add C0 control characters
137 +
138 + for (let i = 0; i < 33; ++i) {
139 + lookup[i] = true;
140 + }
141 +
142 + return function (code) {
143 +
144 + return lookup[code];
145 + };
146 +}();
147 +
148 +internals.c1Controls = function () {
149 +
150 + const lookup = new Array(0x100);
151 + lookup.fill(false);
152 +
153 + // add C1 control characters
154 +
155 + for (let i = 127; i < 160; ++i) {
156 + lookup[i] = true;
157 + }
158 +
159 + return function (code) {
160 +
161 + return lookup[code];
162 + };
163 +}();
164 +
165 +internals.regex = {
166 + ipV4: /\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,
167 + ipV6: /^[a-fA-F\d]{0,4}$/
168 +};
169 +
170 +// $lab:coverage:off$
171 +internals.nulNormalize = function (email) {
172 +
173 + let emailPieces = email.split('\u0000');
174 + emailPieces = emailPieces.map((string) => {
175 +
176 + return string.normalize('NFC');
177 + });
178 +
179 + return emailPieces.join('\u0000');
180 +};
181 +// $lab:coverage:on$
182 +
183 +
184 +internals.checkIpV6 = function (items) {
185 +
186 + return items.every((value) => internals.regex.ipV6.test(value));
187 +};
188 +
189 +
190 +internals.validDomain = function (tldAtom, options) {
191 +
192 + if (options.tldBlacklist) {
193 + if (Array.isArray(options.tldBlacklist)) {
194 + return internals.indexOf.call(options.tldBlacklist, tldAtom) === -1;
195 + }
196 +
197 + return !internals.hasOwn.call(options.tldBlacklist, tldAtom);
198 + }
199 +
200 + if (Array.isArray(options.tldWhitelist)) {
201 + return internals.indexOf.call(options.tldWhitelist, tldAtom) !== -1;
202 + }
203 +
204 + return internals.hasOwn.call(options.tldWhitelist, tldAtom);
205 +};
206 +
207 +
208 +/**
209 + * Check that an email address conforms to RFCs 5321, 5322, 6530 and others
210 + *
211 + * We distinguish clearly between a Mailbox as defined by RFC 5321 and an
212 + * addr-spec as defined by RFC 5322. Depending on the context, either can be
213 + * regarded as a valid email address. The RFC 5321 Mailbox specification is
214 + * more restrictive (comments, white space and obsolete forms are not allowed).
215 + *
216 + * @param {string} email The email address to check. See README for specifics.
217 + * @param {Object} options The (optional) options:
218 + * {*} errorLevel Determines the boundary between valid and invalid
219 + * addresses.
220 + * {*} tldBlacklist The set of domains to consider invalid.
221 + * {*} tldWhitelist The set of domains to consider valid.
222 + * {*} allowUnicode Whether to allow non-ASCII characters, defaults to true.
223 + * {*} minDomainAtoms The minimum number of domain atoms which must be present
224 + * for the address to be valid.
225 + * @param {function(number|boolean)} callback The (optional) callback handler.
226 + * @return {*}
227 + */
228 +
229 +exports.validate = internals.validate = function (email, options, callback) {
230 +
231 + options = options || {};
232 + email = internals.normalize(email);
233 +
234 + if (typeof options === 'function') {
235 + callback = options;
236 + options = {};
237 + }
238 +
239 + if (typeof callback !== 'function') {
240 + callback = null;
241 + }
242 +
243 + let diagnose;
244 + let threshold;
245 +
246 + if (typeof options.errorLevel === 'number') {
247 + diagnose = true;
248 + threshold = options.errorLevel;
249 + }
250 + else {
251 + diagnose = !!options.errorLevel;
252 + threshold = internals.diagnoses.valid;
253 + }
254 +
255 + if (options.tldWhitelist) {
256 + if (typeof options.tldWhitelist === 'string') {
257 + options.tldWhitelist = [options.tldWhitelist];
258 + }
259 + else if (typeof options.tldWhitelist !== 'object') {
260 + throw new TypeError('expected array or object tldWhitelist');
261 + }
262 + }
263 +
264 + if (options.tldBlacklist) {
265 + if (typeof options.tldBlacklist === 'string') {
266 + options.tldBlacklist = [options.tldBlacklist];
267 + }
268 + else if (typeof options.tldBlacklist !== 'object') {
269 + throw new TypeError('expected array or object tldBlacklist');
270 + }
271 + }
272 +
273 + if (options.minDomainAtoms && (options.minDomainAtoms !== ((+options.minDomainAtoms) | 0) || options.minDomainAtoms < 0)) {
274 + throw new TypeError('expected positive integer minDomainAtoms');
275 + }
276 +
277 + let maxResult = internals.diagnoses.valid;
278 + const updateResult = (value) => {
279 +
280 + if (value > maxResult) {
281 + maxResult = value;
282 + }
283 + };
284 +
285 + const allowUnicode = options.allowUnicode === undefined || !!options.allowUnicode;
286 + if (!allowUnicode && /[^\x00-\x7f]/.test(email)) {
287 + updateResult(internals.diagnoses.undesiredNonAscii);
288 + }
289 +
290 + const context = {
291 + now: internals.components.localpart,
292 + prev: internals.components.localpart,
293 + stack: [internals.components.localpart]
294 + };
295 +
296 + let prevToken = '';
297 +
298 + const parseData = {
299 + local: '',
300 + domain: ''
301 + };
302 + const atomData = {
303 + locals: [''],
304 + domains: ['']
305 + };
306 +
307 + let elementCount = 0;
308 + let elementLength = 0;
309 + let crlfCount = 0;
310 + let charCode;
311 +
312 + let hyphenFlag = false;
313 + let assertEnd = false;
314 +
315 + const emailLength = email.length;
316 +
317 + let token; // Token is used outside the loop, must declare similarly
318 + for (let i = 0; i < emailLength; i += token.length) {
319 + // Utilize codepoints to account for Unicode surrogate pairs
320 + token = String.fromCodePoint(email.codePointAt(i));
321 +
322 + switch (context.now) {
323 + // Local-part
324 + case internals.components.localpart:
325 + // http://tools.ietf.org/html/rfc5322#section-3.4.1
326 + // local-part = dot-atom / quoted-string / obs-local-part
327 + //
328 + // dot-atom = [CFWS] dot-atom-text [CFWS]
329 + //
330 + // dot-atom-text = 1*atext *("." 1*atext)
331 + //
332 + // quoted-string = [CFWS]
333 + // DQUOTE *([FWS] qcontent) [FWS] DQUOTE
334 + // [CFWS]
335 + //
336 + // obs-local-part = word *("." word)
337 + //
338 + // word = atom / quoted-string
339 + //
340 + // atom = [CFWS] 1*atext [CFWS]
341 + switch (token) {
342 + // Comment
343 + case '(':
344 + if (elementLength === 0) {
345 + // Comments are OK at the beginning of an element
346 + updateResult(elementCount === 0 ? internals.diagnoses.cfwsComment : internals.diagnoses.deprecatedComment);
347 + }
348 + else {
349 + updateResult(internals.diagnoses.cfwsComment);
350 + // Cannot start a comment in an element, should be end
351 + assertEnd = true;
352 + }
353 +
354 + context.stack.push(context.now);
355 + context.now = internals.components.contextComment;
356 + break;
357 +
358 + // Next dot-atom element
359 + case '.':
360 + if (elementLength === 0) {
361 + // Another dot, already?
362 + updateResult(elementCount === 0 ? internals.diagnoses.errDotStart : internals.diagnoses.errConsecutiveDots);
363 + }
364 + else {
365 + // The entire local-part can be a quoted string for RFC 5321; if one atom is quoted it's an RFC 5322 obsolete form
366 + if (assertEnd) {
367 + updateResult(internals.diagnoses.deprecatedLocalPart);
368 + }
369 +
370 + // CFWS & quoted strings are OK again now we're at the beginning of an element (although they are obsolete forms)
371 + assertEnd = false;
372 + elementLength = 0;
373 + ++elementCount;
374 + parseData.local += token;
375 + atomData.locals[elementCount] = '';
376 + }
377 +
378 + break;
379 +
380 + // Quoted string
381 + case '"':
382 + if (elementLength === 0) {
383 + // The entire local-part can be a quoted string for RFC 5321; if one atom is quoted it's an RFC 5322 obsolete form
384 + updateResult(elementCount === 0 ? internals.diagnoses.rfc5321QuotedString : internals.diagnoses.deprecatedLocalPart);
385 +
386 + parseData.local += token;
387 + atomData.locals[elementCount] += token;
388 + elementLength += Buffer.byteLength(token, 'utf8');
389 +
390 + // Quoted string must be the entire element
391 + assertEnd = true;
392 + context.stack.push(context.now);
393 + context.now = internals.components.contextQuotedString;
394 + }
395 + else {
396 + updateResult(internals.diagnoses.errExpectingATEXT);
397 + }
398 +
399 + break;
400 +
401 + // Folding white space
402 + case '\r':
403 + if (emailLength === ++i || email[i] !== '\n') {
404 + // Fatal error
405 + updateResult(internals.diagnoses.errCRNoLF);
406 + break;
407 + }
408 +
409 + // Fallthrough
410 +
411 + case ' ':
412 + case '\t':
413 + if (elementLength === 0) {
414 + updateResult(elementCount === 0 ? internals.diagnoses.cfwsFWS : internals.diagnoses.deprecatedFWS);
415 + }
416 + else {
417 + // We can't start FWS in the middle of an element, better be end
418 + assertEnd = true;
419 + }
420 +
421 + context.stack.push(context.now);
422 + context.now = internals.components.contextFWS;
423 + prevToken = token;
424 + break;
425 +
426 + case '@':
427 + // At this point we should have a valid local-part
428 + // $lab:coverage:off$
429 + if (context.stack.length !== 1) {
430 + throw new Error('unexpected item on context stack');
431 + }
432 + // $lab:coverage:on$
433 +
434 + if (parseData.local.length === 0) {
435 + // Fatal error
436 + updateResult(internals.diagnoses.errNoLocalPart);
437 + }
438 + else if (elementLength === 0) {
439 + // Fatal error
440 + updateResult(internals.diagnoses.errDotEnd);
441 + }
442 + // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1 the maximum total length of a user name or other local-part is 64
443 + // octets
444 + else if (Buffer.byteLength(parseData.local, 'utf8') > 64) {
445 + updateResult(internals.diagnoses.rfc5322LocalTooLong);
446 + }
447 + // http://tools.ietf.org/html/rfc5322#section-3.4.1 comments and folding white space SHOULD NOT be used around "@" in the
448 + // addr-spec
449 + //
450 + // http://tools.ietf.org/html/rfc2119
451 + // 4. SHOULD NOT this phrase, or the phrase "NOT RECOMMENDED" mean that there may exist valid reasons in particular
452 + // circumstances when the particular behavior is acceptable or even useful, but the full implications should be understood
453 + // and the case carefully weighed before implementing any behavior described with this label.
454 + else if (context.prev === internals.components.contextComment || context.prev === internals.components.contextFWS) {
455 + updateResult(internals.diagnoses.deprecatedCFWSNearAt);
456 + }
457 +
458 + // Clear everything down for the domain parsing
459 + context.now = internals.components.domain;
460 + context.stack[0] = internals.components.domain;
461 + elementCount = 0;
462 + elementLength = 0;
463 + assertEnd = false; // CFWS can only appear at the end of the element
464 + break;
465 +
466 + // ATEXT
467 + default:
468 + // http://tools.ietf.org/html/rfc5322#section-3.2.3
469 + // atext = ALPHA / DIGIT / ; Printable US-ASCII
470 + // "!" / "#" / ; characters not including
471 + // "$" / "%" / ; specials. Used for atoms.
472 + // "&" / "'" /
473 + // "*" / "+" /
474 + // "-" / "/" /
475 + // "=" / "?" /
476 + // "^" / "_" /
477 + // "`" / "{" /
478 + // "|" / "}" /
479 + // "~"
480 + if (assertEnd) {
481 + // We have encountered atext where it is no longer valid
482 + switch (context.prev) {
483 + case internals.components.contextComment:
484 + case internals.components.contextFWS:
485 + updateResult(internals.diagnoses.errATEXTAfterCFWS);
486 + break;
487 +
488 + case internals.components.contextQuotedString:
489 + updateResult(internals.diagnoses.errATEXTAfterQS);
490 + break;
491 +
492 + // $lab:coverage:off$
493 + default:
494 + throw new Error('more atext found where none is allowed, but unrecognized prev context: ' + context.prev);
495 + // $lab:coverage:on$
496 + }
497 + }
498 + else {
499 + context.prev = context.now;
500 + charCode = token.codePointAt(0);
501 +
502 + // Especially if charCode == 10
503 + if (internals.specials(charCode) || internals.c0Controls(charCode) || internals.c1Controls(charCode)) {
504 +
505 + // Fatal error
506 + updateResult(internals.diagnoses.errExpectingATEXT);
507 + }
508 +
509 + parseData.local += token;
510 + atomData.locals[elementCount] += token;
511 + elementLength += Buffer.byteLength(token, 'utf8');
512 + }
513 + }
514 +
515 + break;
516 +
517 + case internals.components.domain:
518 + // http://tools.ietf.org/html/rfc5322#section-3.4.1
519 + // domain = dot-atom / domain-literal / obs-domain
520 + //
521 + // dot-atom = [CFWS] dot-atom-text [CFWS]
522 + //
523 + // dot-atom-text = 1*atext *("." 1*atext)
524 + //
525 + // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
526 + //
527 + // dtext = %d33-90 / ; Printable US-ASCII
528 + // %d94-126 / ; characters not including
529 + // obs-dtext ; "[", "]", or "\"
530 + //
531 + // obs-domain = atom *("." atom)
532 + //
533 + // atom = [CFWS] 1*atext [CFWS]
534 +
535 + // http://tools.ietf.org/html/rfc5321#section-4.1.2
536 + // Mailbox = Local-part "@" ( Domain / address-literal )
537 + //
538 + // Domain = sub-domain *("." sub-domain)
539 + //
540 + // address-literal = "[" ( IPv4-address-literal /
541 + // IPv6-address-literal /
542 + // General-address-literal ) "]"
543 + // ; See Section 4.1.3
544 +
545 + // http://tools.ietf.org/html/rfc5322#section-3.4.1
546 + // Note: A liberal syntax for the domain portion of addr-spec is
547 + // given here. However, the domain portion contains addressing
548 + // information specified by and used in other protocols (e.g.,
549 + // [RFC1034], [RFC1035], [RFC1123], [RFC5321]). It is therefore
550 + // incumbent upon implementations to conform to the syntax of
551 + // addresses for the context in which they are used.
552 + //
553 + // is_email() author's note: it's not clear how to interpret this in
554 + // he context of a general email address validator. The conclusion I
555 + // have reached is this: "addressing information" must comply with
556 + // RFC 5321 (and in turn RFC 1035), anything that is "semantically
557 + // invisible" must comply only with RFC 5322.
558 + switch (token) {
559 + // Comment
560 + case '(':
561 + if (elementLength === 0) {
562 + // Comments at the start of the domain are deprecated in the text, comments at the start of a subdomain are obs-domain
563 + // http://tools.ietf.org/html/rfc5322#section-3.4.1
564 + updateResult(elementCount === 0 ? internals.diagnoses.deprecatedCFWSNearAt : internals.diagnoses.deprecatedComment);
565 + }
566 + else {
567 + // We can't start a comment mid-element, better be at the end
568 + assertEnd = true;
569 + updateResult(internals.diagnoses.cfwsComment);
570 + }
571 +
572 + context.stack.push(context.now);
573 + context.now = internals.components.contextComment;
574 + break;
575 +
576 + // Next dot-atom element
577 + case '.':
578 + const punycodeLength = Punycode.encode(atomData.domains[elementCount]).length;
579 + if (elementLength === 0) {
580 + // Another dot, already? Fatal error.
581 + updateResult(elementCount === 0 ? internals.diagnoses.errDotStart : internals.diagnoses.errConsecutiveDots);
582 + }
583 + else if (hyphenFlag) {
584 + // Previous subdomain ended in a hyphen. Fatal error.
585 + updateResult(internals.diagnoses.errDomainHyphenEnd);
586 + }
587 + else if (punycodeLength > 63) {
588 + // RFC 5890 specifies that domain labels that are encoded using the Punycode algorithm
589 + // must adhere to the <= 63 octet requirement.
590 + // This includes string prefixes from the Punycode algorithm.
591 + //
592 + // https://tools.ietf.org/html/rfc5890#section-2.3.2.1
593 + // labels 63 octets or less
594 +
595 + updateResult(internals.diagnoses.rfc5322LabelTooLong);
596 + }
597 +
598 + // CFWS is OK again now we're at the beginning of an element (although
599 + // it may be obsolete CFWS)
600 + assertEnd = false;
601 + elementLength = 0;
602 + ++elementCount;
603 + atomData.domains[elementCount] = '';
604 + parseData.domain += token;
605 +
606 + break;
607 +
608 + // Domain literal
609 + case '[':
610 + if (parseData.domain.length === 0) {
611 + // Domain literal must be the only component
612 + assertEnd = true;
613 + elementLength += Buffer.byteLength(token, 'utf8');
614 + context.stack.push(context.now);
615 + context.now = internals.components.literal;
616 + parseData.domain += token;
617 + atomData.domains[elementCount] += token;
618 + parseData.literal = '';
619 + }
620 + else {
621 + // Fatal error
622 + updateResult(internals.diagnoses.errExpectingATEXT);
623 + }
624 +
625 + break;
626 +
627 + // Folding white space
628 + case '\r':
629 + if (emailLength === ++i || email[i] !== '\n') {
630 + // Fatal error
631 + updateResult(internals.diagnoses.errCRNoLF);
632 + break;
633 + }
634 +
635 + // Fallthrough
636 +
637 + case ' ':
638 + case '\t':
639 + if (elementLength === 0) {
640 + updateResult(elementCount === 0 ? internals.diagnoses.deprecatedCFWSNearAt : internals.diagnoses.deprecatedFWS);
641 + }
642 + else {
643 + // We can't start FWS in the middle of an element, so this better be the end
644 + updateResult(internals.diagnoses.cfwsFWS);
645 + assertEnd = true;
646 + }
647 +
648 + context.stack.push(context.now);
649 + context.now = internals.components.contextFWS;
650 + prevToken = token;
651 + break;
652 +
653 + // This must be ATEXT
654 + default:
655 + // RFC 5322 allows any atext...
656 + // http://tools.ietf.org/html/rfc5322#section-3.2.3
657 + // atext = ALPHA / DIGIT / ; Printable US-ASCII
658 + // "!" / "#" / ; characters not including
659 + // "$" / "%" / ; specials. Used for atoms.
660 + // "&" / "'" /
661 + // "*" / "+" /
662 + // "-" / "/" /
663 + // "=" / "?" /
664 + // "^" / "_" /
665 + // "`" / "{" /
666 + // "|" / "}" /
667 + // "~"
668 +
669 + // But RFC 5321 only allows letter-digit-hyphen to comply with DNS rules
670 + // (RFCs 1034 & 1123)
671 + // http://tools.ietf.org/html/rfc5321#section-4.1.2
672 + // sub-domain = Let-dig [Ldh-str]
673 + //
674 + // Let-dig = ALPHA / DIGIT
675 + //
676 + // Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
677 + //
678 + if (assertEnd) {
679 + // We have encountered ATEXT where it is no longer valid
680 + switch (context.prev) {
681 + case internals.components.contextComment:
682 + case internals.components.contextFWS:
683 + updateResult(internals.diagnoses.errATEXTAfterCFWS);
684 + break;
685 +
686 + case internals.components.literal:
687 + updateResult(internals.diagnoses.errATEXTAfterDomainLiteral);
688 + break;
689 +
690 + // $lab:coverage:off$
691 + default:
692 + throw new Error('more atext found where none is allowed, but unrecognized prev context: ' + context.prev);
693 + // $lab:coverage:on$
694 + }
695 + }
696 +
697 + charCode = token.codePointAt(0);
698 + // Assume this token isn't a hyphen unless we discover it is
699 + hyphenFlag = false;
700 +
701 + if (internals.specials(charCode) || internals.c0Controls(charCode) || internals.c1Controls(charCode)) {
702 + // Fatal error
703 + updateResult(internals.diagnoses.errExpectingATEXT);
704 + }
705 + else if (token === '-') {
706 + if (elementLength === 0) {
707 + // Hyphens cannot be at the beginning of a subdomain, fatal error
708 + updateResult(internals.diagnoses.errDomainHyphenStart);
709 + }
710 +
711 + hyphenFlag = true;
712 + }
713 + // Check if it's a neither a number nor a latin/unicode letter
714 + else if (charCode < 48 || (charCode > 122 && charCode < 192) || (charCode > 57 && charCode < 65) || (charCode > 90 && charCode < 97)) {
715 + // This is not an RFC 5321 subdomain, but still OK by RFC 5322
716 + updateResult(internals.diagnoses.rfc5322Domain);
717 + }
718 +
719 + parseData.domain += token;
720 + atomData.domains[elementCount] += token;
721 + elementLength += Buffer.byteLength(token, 'utf8');
722 + }
723 +
724 + break;
725 +
726 + // Domain literal
727 + case internals.components.literal:
728 + // http://tools.ietf.org/html/rfc5322#section-3.4.1
729 + // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
730 + //
731 + // dtext = %d33-90 / ; Printable US-ASCII
732 + // %d94-126 / ; characters not including
733 + // obs-dtext ; "[", "]", or "\"
734 + //
735 + // obs-dtext = obs-NO-WS-CTL / quoted-pair
736 + switch (token) {
737 + // End of domain literal
738 + case ']':
739 + if (maxResult < internals.categories.deprecated) {
740 + // Could be a valid RFC 5321 address literal, so let's check
741 +
742 + // http://tools.ietf.org/html/rfc5321#section-4.1.2
743 + // address-literal = "[" ( IPv4-address-literal /
744 + // IPv6-address-literal /
745 + // General-address-literal ) "]"
746 + // ; See Section 4.1.3
747 + //
748 + // http://tools.ietf.org/html/rfc5321#section-4.1.3
749 + // IPv4-address-literal = Snum 3("." Snum)
750 + //
751 + // IPv6-address-literal = "IPv6:" IPv6-addr
752 + //
753 + // General-address-literal = Standardized-tag ":" 1*dcontent
754 + //
755 + // Standardized-tag = Ldh-str
756 + // ; Standardized-tag MUST be specified in a
757 + // ; Standards-Track RFC and registered with IANA
758 + //
759 + // dcontent = %d33-90 / ; Printable US-ASCII
760 + // %d94-126 ; excl. "[", "\", "]"
761 + //
762 + // Snum = 1*3DIGIT
763 + // ; representing a decimal integer
764 + // ; value in the range 0 through 255
765 + //
766 + // IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
767 + //
768 + // IPv6-hex = 1*4HEXDIG
769 + //
770 + // IPv6-full = IPv6-hex 7(":" IPv6-hex)
771 + //
772 + // IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::"
773 + // [IPv6-hex *5(":" IPv6-hex)]
774 + // ; The "::" represents at least 2 16-bit groups of
775 + // ; zeros. No more than 6 groups in addition to the
776 + // ; "::" may be present.
777 + //
778 + // IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
779 + //
780 + // IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
781 + // [IPv6-hex *3(":" IPv6-hex) ":"]
782 + // IPv4-address-literal
783 + // ; The "::" represents at least 2 16-bit groups of
784 + // ; zeros. No more than 4 groups in addition to the
785 + // ; "::" and IPv4-address-literal may be present.
786 +
787 + let index = -1;
788 + let addressLiteral = parseData.literal;
789 + const matchesIP = internals.regex.ipV4.exec(addressLiteral);
790 +
791 + // Maybe extract IPv4 part from the end of the address-literal
792 + if (matchesIP) {
793 + index = matchesIP.index;
794 + if (index !== 0) {
795 + // Convert IPv4 part to IPv6 format for futher testing
796 + addressLiteral = addressLiteral.slice(0, index) + '0:0';
797 + }
798 + }
799 +
800 + if (index === 0) {
801 + // Nothing there except a valid IPv4 address, so...
802 + updateResult(internals.diagnoses.rfc5321AddressLiteral);
803 + }
804 + else if (addressLiteral.slice(0, 5).toLowerCase() !== 'ipv6:') {
805 + updateResult(internals.diagnoses.rfc5322DomainLiteral);
806 + }
807 + else {
808 + const match = addressLiteral.slice(5);
809 + let maxGroups = internals.maxIPv6Groups;
810 + const groups = match.split(':');
811 + index = match.indexOf('::');
812 +
813 + if (!~index) {
814 + // Need exactly the right number of groups
815 + if (groups.length !== maxGroups) {
816 + updateResult(internals.diagnoses.rfc5322IPv6GroupCount);
817 + }
818 + }
819 + else if (index !== match.lastIndexOf('::')) {
820 + updateResult(internals.diagnoses.rfc5322IPv62x2xColon);
821 + }
822 + else {
823 + if (index === 0 || index === match.length - 2) {
824 + // RFC 4291 allows :: at the start or end of an address with 7 other groups in addition
825 + ++maxGroups;
826 + }
827 +
828 + if (groups.length > maxGroups) {
829 + updateResult(internals.diagnoses.rfc5322IPv6MaxGroups);
830 + }
831 + else if (groups.length === maxGroups) {
832 + // Eliding a single "::"
833 + updateResult(internals.diagnoses.deprecatedIPv6);
834 + }
835 + }
836 +
837 + // IPv6 testing strategy
838 + if (match[0] === ':' && match[1] !== ':') {
839 + updateResult(internals.diagnoses.rfc5322IPv6ColonStart);
840 + }
841 + else if (match[match.length - 1] === ':' && match[match.length - 2] !== ':') {
842 + updateResult(internals.diagnoses.rfc5322IPv6ColonEnd);
843 + }
844 + else if (internals.checkIpV6(groups)) {
845 + updateResult(internals.diagnoses.rfc5321AddressLiteral);
846 + }
847 + else {
848 + updateResult(internals.diagnoses.rfc5322IPv6BadCharacter);
849 + }
850 + }
851 + }
852 + else {
853 + updateResult(internals.diagnoses.rfc5322DomainLiteral);
854 + }
855 +
856 + parseData.domain += token;
857 + atomData.domains[elementCount] += token;
858 + elementLength += Buffer.byteLength(token, 'utf8');
859 + context.prev = context.now;
860 + context.now = context.stack.pop();
861 + break;
862 +
863 + case '\\':
864 + updateResult(internals.diagnoses.rfc5322DomainLiteralOBSDText);
865 + context.stack.push(context.now);
866 + context.now = internals.components.contextQuotedPair;
867 + break;
868 +
869 + // Folding white space
870 + case '\r':
871 + if (emailLength === ++i || email[i] !== '\n') {
872 + updateResult(internals.diagnoses.errCRNoLF);
873 + break;
874 + }
875 +
876 + // Fallthrough
877 +
878 + case ' ':
879 + case '\t':
880 + updateResult(internals.diagnoses.cfwsFWS);
881 +
882 + context.stack.push(context.now);
883 + context.now = internals.components.contextFWS;
884 + prevToken = token;
885 + break;
886 +
887 + // DTEXT
888 + default:
889 + // http://tools.ietf.org/html/rfc5322#section-3.4.1
890 + // dtext = %d33-90 / ; Printable US-ASCII
891 + // %d94-126 / ; characters not including
892 + // obs-dtext ; "[", "]", or "\"
893 + //
894 + // obs-dtext = obs-NO-WS-CTL / quoted-pair
895 + //
896 + // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
897 + // %d11 / ; characters that do not
898 + // %d12 / ; include the carriage
899 + // %d14-31 / ; return, line feed, and
900 + // %d127 ; white space characters
901 + charCode = token.codePointAt(0);
902 +
903 + // '\r', '\n', ' ', and '\t' have already been parsed above
904 + if ((charCode !== 127 && internals.c1Controls(charCode)) || charCode === 0 || token === '[') {
905 + // Fatal error
906 + updateResult(internals.diagnoses.errExpectingDTEXT);
907 + break;
908 + }
909 + else if (internals.c0Controls(charCode) || charCode === 127) {
910 + updateResult(internals.diagnoses.rfc5322DomainLiteralOBSDText);
911 + }
912 +
913 + parseData.literal += token;
914 + parseData.domain += token;
915 + atomData.domains[elementCount] += token;
916 + elementLength += Buffer.byteLength(token, 'utf8');
917 + }
918 +
919 + break;
920 +
921 + // Quoted string
922 + case internals.components.contextQuotedString:
923 + // http://tools.ietf.org/html/rfc5322#section-3.2.4
924 + // quoted-string = [CFWS]
925 + // DQUOTE *([FWS] qcontent) [FWS] DQUOTE
926 + // [CFWS]
927 + //
928 + // qcontent = qtext / quoted-pair
929 + switch (token) {
930 + // Quoted pair
931 + case '\\':
932 + context.stack.push(context.now);
933 + context.now = internals.components.contextQuotedPair;
934 + break;
935 +
936 + // Folding white space. Spaces are allowed as regular characters inside a quoted string - it's only FWS if we include '\t' or '\r\n'
937 + case '\r':
938 + if (emailLength === ++i || email[i] !== '\n') {
939 + // Fatal error
940 + updateResult(internals.diagnoses.errCRNoLF);
941 + break;
942 + }
943 +
944 + // Fallthrough
945 +
946 + case '\t':
947 + // http://tools.ietf.org/html/rfc5322#section-3.2.2
948 + // Runs of FWS, comment, or CFWS that occur between lexical tokens in
949 + // a structured header field are semantically interpreted as a single
950 + // space character.
951 +
952 + // http://tools.ietf.org/html/rfc5322#section-3.2.4
953 + // the CRLF in any FWS/CFWS that appears within the quoted-string [is]
954 + // semantically "invisible" and therefore not part of the
955 + // quoted-string
956 +
957 + parseData.local += ' ';
958 + atomData.locals[elementCount] += ' ';
959 + elementLength += Buffer.byteLength(token, 'utf8');
960 +
961 + updateResult(internals.diagnoses.cfwsFWS);
962 + context.stack.push(context.now);
963 + context.now = internals.components.contextFWS;
964 + prevToken = token;
965 + break;
966 +
967 + // End of quoted string
968 + case '"':
969 + parseData.local += token;
970 + atomData.locals[elementCount] += token;
971 + elementLength += Buffer.byteLength(token, 'utf8');
972 + context.prev = context.now;
973 + context.now = context.stack.pop();
974 + break;
975 +
976 + // QTEXT
977 + default:
978 + // http://tools.ietf.org/html/rfc5322#section-3.2.4
979 + // qtext = %d33 / ; Printable US-ASCII
980 + // %d35-91 / ; characters not including
981 + // %d93-126 / ; "\" or the quote character
982 + // obs-qtext
983 + //
984 + // obs-qtext = obs-NO-WS-CTL
985 + //
986 + // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
987 + // %d11 / ; characters that do not
988 + // %d12 / ; include the carriage
989 + // %d14-31 / ; return, line feed, and
990 + // %d127 ; white space characters
991 + charCode = token.codePointAt(0);
992 +
993 + if ((charCode !== 127 && internals.c1Controls(charCode)) || charCode === 0 || charCode === 10) {
994 + updateResult(internals.diagnoses.errExpectingQTEXT);
995 + }
996 + else if (internals.c0Controls(charCode) || charCode === 127) {
997 + updateResult(internals.diagnoses.deprecatedQTEXT);
998 + }
999 +
1000 + parseData.local += token;
1001 + atomData.locals[elementCount] += token;
1002 + elementLength += Buffer.byteLength(token, 'utf8');
1003 + }
1004 +
1005 + // http://tools.ietf.org/html/rfc5322#section-3.4.1
1006 + // If the string can be represented as a dot-atom (that is, it contains
1007 + // no characters other than atext characters or "." surrounded by atext
1008 + // characters), then the dot-atom form SHOULD be used and the quoted-
1009 + // string form SHOULD NOT be used.
1010 +
1011 + break;
1012 + // Quoted pair
1013 + case internals.components.contextQuotedPair:
1014 + // http://tools.ietf.org/html/rfc5322#section-3.2.1
1015 + // quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
1016 + //
1017 + // VCHAR = %d33-126 ; visible (printing) characters
1018 + // WSP = SP / HTAB ; white space
1019 + //
1020 + // obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR)
1021 + //
1022 + // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
1023 + // %d11 / ; characters that do not
1024 + // %d12 / ; include the carriage
1025 + // %d14-31 / ; return, line feed, and
1026 + // %d127 ; white space characters
1027 + //
1028 + // i.e. obs-qp = "\" (%d0-8, %d10-31 / %d127)
1029 + charCode = token.codePointAt(0);
1030 +
1031 + if (charCode !== 127 && internals.c1Controls(charCode)) {
1032 + // Fatal error
1033 + updateResult(internals.diagnoses.errExpectingQPair);
1034 + }
1035 + else if ((charCode < 31 && charCode !== 9) || charCode === 127) {
1036 + // ' ' and '\t' are allowed
1037 + updateResult(internals.diagnoses.deprecatedQP);
1038 + }
1039 +
1040 + // At this point we know where this qpair occurred so we could check to see if the character actually needed to be quoted at all.
1041 + // http://tools.ietf.org/html/rfc5321#section-4.1.2
1042 + // the sending system SHOULD transmit the form that uses the minimum quoting possible.
1043 +
1044 + context.prev = context.now;
1045 + // End of qpair
1046 + context.now = context.stack.pop();
1047 + const escapeToken = '\\' + token;
1048 +
1049 + switch (context.now) {
1050 + case internals.components.contextComment:
1051 + break;
1052 +
1053 + case internals.components.contextQuotedString:
1054 + parseData.local += escapeToken;
1055 + atomData.locals[elementCount] += escapeToken;
1056 +
1057 + // The maximum sizes specified by RFC 5321 are octet counts, so we must include the backslash
1058 + elementLength += 2;
1059 + break;
1060 +
1061 + case internals.components.literal:
1062 + parseData.domain += escapeToken;
1063 + atomData.domains[elementCount] += escapeToken;
1064 +
1065 + // The maximum sizes specified by RFC 5321 are octet counts, so we must include the backslash
1066 + elementLength += 2;
1067 + break;
1068 +
1069 + // $lab:coverage:off$
1070 + default:
1071 + throw new Error('quoted pair logic invoked in an invalid context: ' + context.now);
1072 + // $lab:coverage:on$
1073 + }
1074 + break;
1075 +
1076 + // Comment
1077 + case internals.components.contextComment:
1078 + // http://tools.ietf.org/html/rfc5322#section-3.2.2
1079 + // comment = "(" *([FWS] ccontent) [FWS] ")"
1080 + //
1081 + // ccontent = ctext / quoted-pair / comment
1082 + switch (token) {
1083 + // Nested comment
1084 + case '(':
1085 + // Nested comments are ok
1086 + context.stack.push(context.now);
1087 + context.now = internals.components.contextComment;
1088 + break;
1089 +
1090 + // End of comment
1091 + case ')':
1092 + context.prev = context.now;
1093 + context.now = context.stack.pop();
1094 + break;
1095 +
1096 + // Quoted pair
1097 + case '\\':
1098 + context.stack.push(context.now);
1099 + context.now = internals.components.contextQuotedPair;
1100 + break;
1101 +
1102 + // Folding white space
1103 + case '\r':
1104 + if (emailLength === ++i || email[i] !== '\n') {
1105 + // Fatal error
1106 + updateResult(internals.diagnoses.errCRNoLF);
1107 + break;
1108 + }
1109 +
1110 + // Fallthrough
1111 +
1112 + case ' ':
1113 + case '\t':
1114 + updateResult(internals.diagnoses.cfwsFWS);
1115 +
1116 + context.stack.push(context.now);
1117 + context.now = internals.components.contextFWS;
1118 + prevToken = token;
1119 + break;
1120 +
1121 + // CTEXT
1122 + default:
1123 + // http://tools.ietf.org/html/rfc5322#section-3.2.3
1124 + // ctext = %d33-39 / ; Printable US-ASCII
1125 + // %d42-91 / ; characters not including
1126 + // %d93-126 / ; "(", ")", or "\"
1127 + // obs-ctext
1128 + //
1129 + // obs-ctext = obs-NO-WS-CTL
1130 + //
1131 + // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
1132 + // %d11 / ; characters that do not
1133 + // %d12 / ; include the carriage
1134 + // %d14-31 / ; return, line feed, and
1135 + // %d127 ; white space characters
1136 + charCode = token.codePointAt(0);
1137 +
1138 + if (charCode === 0 || charCode === 10 || (charCode !== 127 && internals.c1Controls(charCode))) {
1139 + // Fatal error
1140 + updateResult(internals.diagnoses.errExpectingCTEXT);
1141 + break;
1142 + }
1143 + else if (internals.c0Controls(charCode) || charCode === 127) {
1144 + updateResult(internals.diagnoses.deprecatedCTEXT);
1145 + }
1146 + }
1147 +
1148 + break;
1149 +
1150 + // Folding white space
1151 + case internals.components.contextFWS:
1152 + // http://tools.ietf.org/html/rfc5322#section-3.2.2
1153 + // FWS = ([*WSP CRLF] 1*WSP) / obs-FWS
1154 + // ; Folding white space
1155 +
1156 + // But note the erratum:
1157 + // http://www.rfc-editor.org/errata_search.php?rfc=5322&eid=1908:
1158 + // In the obsolete syntax, any amount of folding white space MAY be
1159 + // inserted where the obs-FWS rule is allowed. This creates the
1160 + // possibility of having two consecutive "folds" in a line, and
1161 + // therefore the possibility that a line which makes up a folded header
1162 + // field could be composed entirely of white space.
1163 + //
1164 + // obs-FWS = 1*([CRLF] WSP)
1165 +
1166 + if (prevToken === '\r') {
1167 + if (token === '\r') {
1168 + // Fatal error
1169 + updateResult(internals.diagnoses.errFWSCRLFx2);
1170 + break;
1171 + }
1172 +
1173 + if (++crlfCount > 1) {
1174 + // Multiple folds => obsolete FWS
1175 + updateResult(internals.diagnoses.deprecatedFWS);
1176 + }
1177 + else {
1178 + crlfCount = 1;
1179 + }
1180 + }
1181 +
1182 + switch (token) {
1183 + case '\r':
1184 + if (emailLength === ++i || email[i] !== '\n') {
1185 + // Fatal error
1186 + updateResult(internals.diagnoses.errCRNoLF);
1187 + }
1188 +
1189 + break;
1190 +
1191 + case ' ':
1192 + case '\t':
1193 + break;
1194 +
1195 + default:
1196 + if (prevToken === '\r') {
1197 + // Fatal error
1198 + updateResult(internals.diagnoses.errFWSCRLFEnd);
1199 + }
1200 +
1201 + crlfCount = 0;
1202 +
1203 + // End of FWS
1204 + context.prev = context.now;
1205 + context.now = context.stack.pop();
1206 +
1207 + // Look at this token again in the parent context
1208 + --i;
1209 + }
1210 +
1211 + prevToken = token;
1212 + break;
1213 +
1214 + // Unexpected context
1215 + // $lab:coverage:off$
1216 + default:
1217 + throw new Error('unknown context: ' + context.now);
1218 + // $lab:coverage:on$
1219 + } // Primary state machine
1220 +
1221 + if (maxResult > internals.categories.rfc5322) {
1222 + // Fatal error, no point continuing
1223 + break;
1224 + }
1225 + } // Token loop
1226 +
1227 + // Check for errors
1228 + if (maxResult < internals.categories.rfc5322) {
1229 + const punycodeLength = Punycode.encode(parseData.domain).length;
1230 + // Fatal errors
1231 + if (context.now === internals.components.contextQuotedString) {
1232 + updateResult(internals.diagnoses.errUnclosedQuotedString);
1233 + }
1234 + else if (context.now === internals.components.contextQuotedPair) {
1235 + updateResult(internals.diagnoses.errBackslashEnd);
1236 + }
1237 + else if (context.now === internals.components.contextComment) {
1238 + updateResult(internals.diagnoses.errUnclosedComment);
1239 + }
1240 + else if (context.now === internals.components.literal) {
1241 + updateResult(internals.diagnoses.errUnclosedDomainLiteral);
1242 + }
1243 + else if (token === '\r') {
1244 + updateResult(internals.diagnoses.errFWSCRLFEnd);
1245 + }
1246 + else if (parseData.domain.length === 0) {
1247 + updateResult(internals.diagnoses.errNoDomain);
1248 + }
1249 + else if (elementLength === 0) {
1250 + updateResult(internals.diagnoses.errDotEnd);
1251 + }
1252 + else if (hyphenFlag) {
1253 + updateResult(internals.diagnoses.errDomainHyphenEnd);
1254 + }
1255 +
1256 + // Other errors
1257 + else if (punycodeLength > 255) {
1258 + // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.2
1259 + // The maximum total length of a domain name or number is 255 octets.
1260 + updateResult(internals.diagnoses.rfc5322DomainTooLong);
1261 + }
1262 + else if (Buffer.byteLength(parseData.local, 'utf8') + punycodeLength + /* '@' */ 1 > 254) {
1263 + // http://tools.ietf.org/html/rfc5321#section-4.1.2
1264 + // Forward-path = Path
1265 + //
1266 + // Path = "<" [ A-d-l ":" ] Mailbox ">"
1267 + //
1268 + // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
1269 + // The maximum total length of a reverse-path or forward-path is 256 octets (including the punctuation and element separators).
1270 + //
1271 + // Thus, even without (obsolete) routing information, the Mailbox can only be 254 characters long. This is confirmed by this verified
1272 + // erratum to RFC 3696:
1273 + //
1274 + // http://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690
1275 + // However, there is a restriction in RFC 2821 on the length of an address in MAIL and RCPT commands of 254 characters. Since
1276 + // addresses that do not fit in those fields are not normally useful, the upper limit on address lengths should normally be considered
1277 + // to be 254.
1278 + updateResult(internals.diagnoses.rfc5322TooLong);
1279 + }
1280 + else if (elementLength > 63) {
1281 + // http://tools.ietf.org/html/rfc1035#section-2.3.4
1282 + // labels 63 octets or less
1283 + updateResult(internals.diagnoses.rfc5322LabelTooLong);
1284 + }
1285 + else if (options.minDomainAtoms && atomData.domains.length < options.minDomainAtoms) {
1286 + updateResult(internals.diagnoses.errDomainTooShort);
1287 + }
1288 + else if (options.tldWhitelist || options.tldBlacklist) {
1289 + const tldAtom = atomData.domains[elementCount];
1290 +
1291 + if (!internals.validDomain(tldAtom, options)) {
1292 + updateResult(internals.diagnoses.errUnknownTLD);
1293 + }
1294 + }
1295 + } // Check for errors
1296 +
1297 + // Finish
1298 + if (maxResult < internals.categories.dnsWarn) {
1299 + // Per RFC 5321, domain atoms are limited to letter-digit-hyphen, so we only need to check code <= 57 to check for a digit
1300 + const code = atomData.domains[elementCount].codePointAt(0);
1301 +
1302 + if (code <= 57) {
1303 + updateResult(internals.diagnoses.rfc5321TLDNumeric);
1304 + }
1305 + }
1306 +
1307 + if (maxResult < threshold) {
1308 + maxResult = internals.diagnoses.valid;
1309 + }
1310 +
1311 + const finishResult = diagnose ? maxResult : maxResult < internals.defaultThreshold;
1312 +
1313 + if (callback) {
1314 + callback(finishResult);
1315 + }
1316 +
1317 + return finishResult;
1318 +};
1319 +
1320 +
1321 +exports.diagnoses = internals.validate.diagnoses = (function () {
1322 +
1323 + const diag = {};
1324 + const keys = Object.keys(internals.diagnoses);
1325 + for (let i = 0; i < keys.length; ++i) {
1326 + const key = keys[i];
1327 + diag[key] = internals.diagnoses[key];
1328 + }
1329 +
1330 + return diag;
1331 +})();
1332 +
1333 +
1334 +exports.normalize = internals.normalize = function (email) {
1335 +
1336 + // $lab:coverage:off$
1337 + if (process.version[1] === '4' && email.indexOf('\u0000') >= 0) {
1338 + return internals.nulNormalize(email);
1339 + }
1340 + // $lab:coverage:on$
1341 +
1342 +
1343 + return email.normalize('NFC');
1344 +};
1 +{
2 + "_from": "isemail@3.x.x",
3 + "_id": "isemail@3.1.1",
4 + "_inBundle": false,
5 + "_integrity": "sha512-mVjAjvdPkpwXW61agT2E9AkGoegZO7SdJGCezWwxnETL58f5KwJ4vSVAMBUL5idL6rTlYAIGkX3n4suiviMLNw==",
6 + "_location": "/isemail",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "isemail@3.x.x",
12 + "name": "isemail",
13 + "escapedName": "isemail",
14 + "rawSpec": "3.x.x",
15 + "saveSpec": null,
16 + "fetchSpec": "3.x.x"
17 + },
18 + "_requiredBy": [
19 + "/joi"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/isemail/-/isemail-3.1.1.tgz",
22 + "_shasum": "e8450fe78ff1b48347db599122adcd0668bd92b5",
23 + "_spec": "isemail@3.x.x",
24 + "_where": "/Users/jeminlee/git-projects/node-practice/express-demo/node_modules/joi",
25 + "bugs": {
26 + "url": "https://github.com/hapijs/isemail/issues"
27 + },
28 + "bundleDependencies": false,
29 + "dependencies": {
30 + "punycode": "2.x.x"
31 + },
32 + "deprecated": false,
33 + "description": "Validate an email address according to RFCs 5321, 5322, and others",
34 + "devDependencies": {
35 + "code": "3.x.x",
36 + "lab": "15.x.x"
37 + },
38 + "engines": {
39 + "node": ">=4.0.0"
40 + },
41 + "homepage": "https://github.com/hapijs/isemail#readme",
42 + "keywords": [
43 + "isemail",
44 + "validation",
45 + "check",
46 + "checking",
47 + "verification",
48 + "email",
49 + "address",
50 + "email address"
51 + ],
52 + "license": "BSD-3-Clause",
53 + "main": "lib/index.js",
54 + "name": "isemail",
55 + "repository": {
56 + "type": "git",
57 + "url": "git://github.com/hapijs/isemail.git"
58 + },
59 + "scripts": {
60 + "test": "lab -a code -t 100 -L -m 5000",
61 + "test-cov-html": "lab -a code -r html -o coverage.html -m 5000"
62 + },
63 + "types": "lib/index.d.ts",
64 + "version": "3.1.1"
65 +}
1 +Breaking changes are documented using GitHub issues, see [issues labeled "release notes"](https://github.com/hapijs/joi/issues?q=is%3Aissue+label%3A%22release+notes%22).
2 +
3 +If you want changes of a specific minor or patch release, you can browse the [GitHub milestones](https://github.com/hapijs/joi/milestones?state=closed&direction=asc&sort=due_date).
1 +Copyright (c) 2012-2017, Project contributors
2 +Copyright (c) 2012-2014, Walmart
3 +All rights reserved.
4 +
5 +Redistribution and use in source and binary forms, with or without
6 +modification, are permitted provided that the following conditions are met:
7 + * Redistributions of source code must retain the above copyright
8 + notice, this list of conditions and the following disclaimer.
9 + * Redistributions in binary form must reproduce the above copyright
10 + notice, this list of conditions and the following disclaimer in the
11 + documentation and/or other materials provided with the distribution.
12 + * The names of any contributors may not be used to endorse or promote
13 + products derived from this software without specific prior written
14 + permission.
15 +
16 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
20 +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 +
27 + * * *
28 +
29 +The complete list of contributors can be found at: https://github.com/hapijs/joi/graphs/contributors
1 +![joi Logo](https://raw.github.com/hapijs/joi/master/images/joi.png)
2 +
3 +Object schema description language and validator for JavaScript objects.
4 +
5 +[![npm version](https://badge.fury.io/js/joi.svg)](http://badge.fury.io/js/joi)
6 +[![Build Status](https://travis-ci.org/hapijs/joi.svg?branch=master)](https://travis-ci.org/hapijs/joi)
7 +[![NSP Status](https://nodesecurity.io/orgs/hapijs/projects/0394bf83-b5bc-410b-878c-e8cf1b92033e/badge)](https://nodesecurity.io/orgs/hapijs/projects/0394bf83-b5bc-410b-878c-e8cf1b92033e)
8 +[![Known Vulnerabilities](https://snyk.io/test/github/hapijs/joi/badge.svg)](https://snyk.io/test/github/hapijs/joi)
9 +
10 +Lead Maintainer: [Nicolas Morel](https://github.com/marsup)
11 +
12 +# Introduction
13 +
14 +Imagine you run facebook and you want visitors to sign up on the website with real names and not something like `l337_p@nda` in the first name field. How would you define the limitations of what can be inputted and validate it against the set rules?
15 +
16 +This is joi, joi allows you to create *blueprints* or *schemas* for JavaScript objects (an object that stores information) to ensure *validation* of key information.
17 +
18 +# API
19 +See the detailed [API Reference](https://github.com/hapijs/joi/blob/v13.1.2/API.md).
20 +
21 +# Example
22 +
23 +```javascript
24 +const Joi = require('joi');
25 +
26 +const schema = Joi.object().keys({
27 + username: Joi.string().alphanum().min(3).max(30).required(),
28 + password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
29 + access_token: [Joi.string(), Joi.number()],
30 + birthyear: Joi.number().integer().min(1900).max(2013),
31 + email: Joi.string().email()
32 +}).with('username', 'birthyear').without('password', 'access_token');
33 +
34 +// Return result.
35 +const result = Joi.validate({ username: 'abc', birthyear: 1994 }, schema);
36 +// result.error === null -> valid
37 +
38 +// You can also pass a callback which will be called synchronously with the validation result.
39 +Joi.validate({ username: 'abc', birthyear: 1994 }, schema, function (err, value) { }); // err === null -> valid
40 +
41 +```
42 +
43 +The above schema defines the following constraints:
44 +* `username`
45 + * a required string
46 + * must contain only alphanumeric characters
47 + * at least 3 characters long but no more than 30
48 + * must be accompanied by `birthyear`
49 +* `password`
50 + * an optional string
51 + * must satisfy the custom regex
52 + * cannot appear together with `access_token`
53 +* `access_token`
54 + * an optional, unconstrained string or number
55 +* `birthyear`
56 + * an integer between 1900 and 2013
57 +* `email`
58 + * a valid email address string
59 +
60 +# Usage
61 +
62 +Usage is a two steps process. First, a schema is constructed using the provided types and constraints:
63 +
64 +```javascript
65 +const schema = {
66 + a: Joi.string()
67 +};
68 +```
69 +
70 +Note that **joi** schema objects are immutable which means every additional rule added (e.g. `.min(5)`) will return a
71 +new schema object.
72 +
73 +Then the value is validated against the schema:
74 +
75 +```javascript
76 +const {error, value} = Joi.validate({ a: 'a string' }, schema);
77 +
78 +// or
79 +
80 +Joi.validate({ a: 'a string' }, schema, function (err, value) { });
81 +```
82 +
83 +If the input is valid, then the error will be `null`, otherwise it will be an Error object.
84 +
85 +The schema can be a plain JavaScript object where every key is assigned a **joi** type, or it can be a **joi** type directly:
86 +
87 +```javascript
88 +const schema = Joi.string().min(10);
89 +```
90 +
91 +If the schema is a **joi** type, the `schema.validate(value, callback)` can be called directly on the type. When passing a non-type schema object,
92 +the module converts it internally to an object() type equivalent to:
93 +
94 +```javascript
95 +const schema = Joi.object().keys({
96 + a: Joi.string()
97 +});
98 +```
99 +
100 +When validating a schema:
101 +
102 +* Values (or keys in case of objects) are optional by default.
103 +
104 + ```javascript
105 + Joi.validate(undefined, Joi.string()); // validates fine
106 + ```
107 +
108 + To disallow this behavior, you can either set the schema as `required()`, or set `presence` to `"required"` when passing `options`:
109 +
110 + ```javascript
111 + Joi.validate(undefined, Joi.string().required());
112 + // or
113 + Joi.validate(undefined, Joi.string(), /* options */ { presence: "required" });
114 + ```
115 +
116 +* Strings are utf-8 encoded by default.
117 +* Rules are defined in an additive fashion and evaluated in order after whitelist and blacklist checks.
118 +
119 +# Browsers
120 +
121 +Joi doesn't directly support browsers, but you could use [joi-browser](https://github.com/jeffbski/joi-browser) for an ES5 build of Joi that works in browsers, or as a source of inspiration for your own builds.
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +const Ref = require('./ref');
7 +
8 +// Type modules are delay-loaded to prevent circular dependencies
9 +
10 +
11 +// Declare internals
12 +
13 +const internals = {};
14 +
15 +
16 +exports.schema = function (Joi, config) {
17 +
18 + if (config !== undefined && config !== null && typeof config === 'object') {
19 +
20 + if (config.isJoi) {
21 + return config;
22 + }
23 +
24 + if (Array.isArray(config)) {
25 + return Joi.alternatives().try(config);
26 + }
27 +
28 + if (config instanceof RegExp) {
29 + return Joi.string().regex(config);
30 + }
31 +
32 + if (config instanceof Date) {
33 + return Joi.date().valid(config);
34 + }
35 +
36 + return Joi.object().keys(config);
37 + }
38 +
39 + if (typeof config === 'string') {
40 + return Joi.string().valid(config);
41 + }
42 +
43 + if (typeof config === 'number') {
44 + return Joi.number().valid(config);
45 + }
46 +
47 + if (typeof config === 'boolean') {
48 + return Joi.boolean().valid(config);
49 + }
50 +
51 + if (Ref.isRef(config)) {
52 + return Joi.valid(config);
53 + }
54 +
55 + Hoek.assert(config === null, 'Invalid schema content:', config);
56 +
57 + return Joi.valid(null);
58 +};
59 +
60 +
61 +exports.ref = function (id) {
62 +
63 + return Ref.isRef(id) ? id : Ref.create(id);
64 +};
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +const Language = require('./language');
7 +
8 +
9 +// Declare internals
10 +
11 +const internals = {
12 + annotations: Symbol('joi-annotations')
13 +};
14 +
15 +internals.stringify = function (value, wrapArrays) {
16 +
17 + const type = typeof value;
18 +
19 + if (value === null) {
20 + return 'null';
21 + }
22 +
23 + if (type === 'string') {
24 + return value;
25 + }
26 +
27 + if (value instanceof exports.Err || type === 'function' || type === 'symbol') {
28 + return value.toString();
29 + }
30 +
31 + if (type === 'object') {
32 + if (Array.isArray(value)) {
33 + let partial = '';
34 +
35 + for (let i = 0; i < value.length; ++i) {
36 + partial = partial + (partial.length ? ', ' : '') + internals.stringify(value[i], wrapArrays);
37 + }
38 +
39 + return wrapArrays ? '[' + partial + ']' : partial;
40 + }
41 +
42 + return value.toString();
43 + }
44 +
45 + return JSON.stringify(value);
46 +};
47 +
48 +exports.Err = class {
49 +
50 + constructor(type, context, state, options, flags, message, template) {
51 +
52 + this.isJoi = true;
53 + this.type = type;
54 + this.context = context || {};
55 + this.context.key = state.path[state.path.length - 1];
56 + this.context.label = state.key;
57 + this.path = state.path;
58 + this.options = options;
59 + this.flags = flags;
60 + this.message = message;
61 + this.template = template;
62 +
63 + const localized = this.options.language;
64 +
65 + if (this.flags.label) {
66 + this.context.label = this.flags.label;
67 + }
68 + else if (localized && // language can be null for arrays exclusion check
69 + (this.context.label === '' ||
70 + this.context.label === null)) {
71 + this.context.label = localized.root || Language.errors.root;
72 + }
73 + }
74 +
75 + toString() {
76 +
77 + if (this.message) {
78 + return this.message;
79 + }
80 +
81 + let format;
82 +
83 + if (this.template) {
84 + format = this.template;
85 + }
86 +
87 + const localized = this.options.language;
88 +
89 + format = format || Hoek.reach(localized, this.type) || Hoek.reach(Language.errors, this.type);
90 +
91 + if (format === undefined) {
92 + return `Error code "${this.type}" is not defined, your custom type is missing the correct language definition`;
93 + }
94 +
95 + let wrapArrays = Hoek.reach(localized, 'messages.wrapArrays');
96 + if (typeof wrapArrays !== 'boolean') {
97 + wrapArrays = Language.errors.messages.wrapArrays;
98 + }
99 +
100 + if (format === null) {
101 + const childrenString = internals.stringify(this.context.reason, wrapArrays);
102 + if (wrapArrays) {
103 + return childrenString.slice(1, -1);
104 + }
105 + return childrenString;
106 + }
107 +
108 + const hasKey = /\{\{\!?label\}\}/.test(format);
109 + const skipKey = format.length > 2 && format[0] === '!' && format[1] === '!';
110 +
111 + if (skipKey) {
112 + format = format.slice(2);
113 + }
114 +
115 + if (!hasKey && !skipKey) {
116 + const localizedKey = Hoek.reach(localized, 'key');
117 + if (typeof localizedKey === 'string') {
118 + format = localizedKey + format;
119 + }
120 + else {
121 + format = Hoek.reach(Language.errors, 'key') + format;
122 + }
123 + }
124 +
125 + return format.replace(/\{\{(\!?)([^}]+)\}\}/g, ($0, isSecure, name) => {
126 +
127 + const value = Hoek.reach(this.context, name);
128 + const normalized = internals.stringify(value, wrapArrays);
129 + return (isSecure && this.options.escapeHtml ? Hoek.escapeHtml(normalized) : normalized);
130 + });
131 + }
132 +
133 +};
134 +
135 +
136 +exports.create = function (type, context, state, options, flags, message, template) {
137 +
138 + return new exports.Err(type, context, state, options, flags, message, template);
139 +};
140 +
141 +
142 +exports.process = function (errors, object) {
143 +
144 + if (!errors || !errors.length) {
145 + return null;
146 + }
147 +
148 + // Construct error
149 +
150 + let message = '';
151 + const details = [];
152 +
153 + const processErrors = function (localErrors, parent) {
154 +
155 + for (let i = 0; i < localErrors.length; ++i) {
156 + const item = localErrors[i];
157 +
158 + if (item instanceof Error) {
159 + return item;
160 + }
161 +
162 + if (item.flags.error && typeof item.flags.error !== 'function') {
163 + return item.flags.error;
164 + }
165 +
166 + let itemMessage;
167 + if (parent === undefined) {
168 + itemMessage = item.toString();
169 + message = message + (message ? '. ' : '') + itemMessage;
170 + }
171 +
172 + // Do not push intermediate errors, we're only interested in leafs
173 +
174 + if (item.context.reason && item.context.reason.length) {
175 + const override = processErrors(item.context.reason, item.path);
176 + if (override) {
177 + return override;
178 + }
179 + }
180 + else {
181 + details.push({
182 + message: itemMessage || item.toString(),
183 + path: item.path,
184 + type: item.type,
185 + context: item.context
186 + });
187 + }
188 + }
189 + };
190 +
191 + const override = processErrors(errors);
192 + if (override) {
193 + return override;
194 + }
195 +
196 + const error = new Error(message);
197 + error.isJoi = true;
198 + error.name = 'ValidationError';
199 + error.details = details;
200 + error._object = object;
201 + error.annotate = internals.annotate;
202 + return error;
203 +};
204 +
205 +
206 +// Inspired by json-stringify-safe
207 +internals.safeStringify = function (obj, spaces) {
208 +
209 + return JSON.stringify(obj, internals.serializer(), spaces);
210 +};
211 +
212 +internals.serializer = function () {
213 +
214 + const keys = [];
215 + const stack = [];
216 +
217 + const cycleReplacer = (key, value) => {
218 +
219 + if (stack[0] === value) {
220 + return '[Circular ~]';
221 + }
222 +
223 + return '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']';
224 + };
225 +
226 + return function (key, value) {
227 +
228 + if (stack.length > 0) {
229 + const thisPos = stack.indexOf(this);
230 + if (~thisPos) {
231 + stack.length = thisPos + 1;
232 + keys.length = thisPos + 1;
233 + keys[thisPos] = key;
234 + }
235 + else {
236 + stack.push(this);
237 + keys.push(key);
238 + }
239 +
240 + if (~stack.indexOf(value)) {
241 + value = cycleReplacer.call(this, key, value);
242 + }
243 + }
244 + else {
245 + stack.push(value);
246 + }
247 +
248 + if (value) {
249 + const annotations = value[internals.annotations];
250 + if (annotations) {
251 + if (Array.isArray(value)) {
252 + const annotated = [];
253 +
254 + for (let i = 0; i < value.length; ++i) {
255 + if (annotations.errors[i]) {
256 + annotated.push(`_$idx$_${annotations.errors[i].sort().join(', ')}_$end$_`);
257 + }
258 + annotated.push(value[i]);
259 + }
260 +
261 + value = annotated;
262 + }
263 + else {
264 + const errorKeys = Object.keys(annotations.errors);
265 + for (let i = 0; i < errorKeys.length; ++i) {
266 + const errorKey = errorKeys[i];
267 + value[`${errorKey}_$key$_${annotations.errors[errorKey].sort().join(', ')}_$end$_`] = value[errorKey];
268 + value[errorKey] = undefined;
269 + }
270 +
271 + const missingKeys = Object.keys(annotations.missing);
272 + for (let i = 0; i < missingKeys.length; ++i) {
273 + const missingKey = missingKeys[i];
274 + value[`_$miss$_${missingKey}|${annotations.missing[missingKey]}_$end$_`] = '__missing__';
275 + }
276 + }
277 +
278 + return value;
279 + }
280 + }
281 +
282 + if (value === Infinity || value === -Infinity || Number.isNaN(value) ||
283 + typeof value === 'function' || typeof value === 'symbol') {
284 + return '[' + value.toString() + ']';
285 + }
286 +
287 + return value;
288 + };
289 +};
290 +
291 +
292 +internals.annotate = function (stripColorCodes) {
293 +
294 + const redFgEscape = stripColorCodes ? '' : '\u001b[31m';
295 + const redBgEscape = stripColorCodes ? '' : '\u001b[41m';
296 + const endColor = stripColorCodes ? '' : '\u001b[0m';
297 +
298 + if (typeof this._object !== 'object') {
299 + return this.details[0].message;
300 + }
301 +
302 + const obj = Hoek.clone(this._object || {});
303 +
304 + for (let i = this.details.length - 1; i >= 0; --i) { // Reverse order to process deepest child first
305 + const pos = i + 1;
306 + const error = this.details[i];
307 + const path = error.path;
308 + let ref = obj;
309 + for (let j = 0; ; ++j) {
310 + const seg = path[j];
311 +
312 + if (ref.isImmutable) {
313 + ref = ref.clone(); // joi schemas are not cloned by hoek, we have to take this extra step
314 + }
315 +
316 + if (j + 1 < path.length &&
317 + ref[seg] &&
318 + typeof ref[seg] !== 'string') {
319 +
320 + ref = ref[seg];
321 + }
322 + else {
323 + const refAnnotations = ref[internals.annotations] = ref[internals.annotations] || { errors: {}, missing: {} };
324 + const value = ref[seg];
325 + const cacheKey = seg || error.context.label;
326 +
327 + if (value !== undefined) {
328 + refAnnotations.errors[cacheKey] = refAnnotations.errors[cacheKey] || [];
329 + refAnnotations.errors[cacheKey].push(pos);
330 + }
331 + else {
332 + refAnnotations.missing[cacheKey] = pos;
333 + }
334 +
335 + break;
336 + }
337 + }
338 + }
339 +
340 + const replacers = {
341 + key: /_\$key\$_([, \d]+)_\$end\$_\"/g,
342 + missing: /\"_\$miss\$_([^\|]+)\|(\d+)_\$end\$_\"\: \"__missing__\"/g,
343 + arrayIndex: /\s*\"_\$idx\$_([, \d]+)_\$end\$_\",?\n(.*)/g,
344 + specials: /"\[(NaN|Symbol.*|-?Infinity|function.*|\(.*)\]"/g
345 + };
346 +
347 + let message = internals.safeStringify(obj, 2)
348 + .replace(replacers.key, ($0, $1) => `" ${redFgEscape}[${$1}]${endColor}`)
349 + .replace(replacers.missing, ($0, $1, $2) => `${redBgEscape}"${$1}"${endColor}${redFgEscape} [${$2}]: -- missing --${endColor}`)
350 + .replace(replacers.arrayIndex, ($0, $1, $2) => `\n${$2} ${redFgEscape}[${$1}]${endColor}`)
351 + .replace(replacers.specials, ($0, $1) => $1);
352 +
353 + message = `${message}\n${redFgEscape}`;
354 +
355 + for (let i = 0; i < this.details.length; ++i) {
356 + const pos = i + 1;
357 + message = `${message}\n[${pos}] ${this.details[i].message}`;
358 + }
359 +
360 + message = message + endColor;
361 +
362 + return message;
363 +};
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +const Any = require('./types/any');
7 +const Cast = require('./cast');
8 +const Errors = require('./errors');
9 +const Lazy = require('./types/lazy');
10 +const Ref = require('./ref');
11 +
12 +
13 +// Declare internals
14 +
15 +const internals = {
16 + alternatives: require('./types/alternatives'),
17 + array: require('./types/array'),
18 + boolean: require('./types/boolean'),
19 + binary: require('./types/binary'),
20 + date: require('./types/date'),
21 + func: require('./types/func'),
22 + number: require('./types/number'),
23 + object: require('./types/object'),
24 + string: require('./types/string')
25 +};
26 +
27 +internals.applyDefaults = function (schema) {
28 +
29 + Hoek.assert(this, 'Must be invoked on a Joi instance.');
30 +
31 + if (this._defaults) {
32 + schema = this._defaults(schema);
33 + }
34 +
35 + schema._currentJoi = this;
36 +
37 + return schema;
38 +};
39 +
40 +internals.root = function () {
41 +
42 + const any = new Any();
43 +
44 + const root = any.clone();
45 + Any.prototype._currentJoi = root;
46 + root._currentJoi = root;
47 +
48 + root.any = function (...args) {
49 +
50 + Hoek.assert(args.length === 0, 'Joi.any() does not allow arguments.');
51 +
52 + return internals.applyDefaults.call(this, any);
53 + };
54 +
55 + root.alternatives = root.alt = function (...args) {
56 +
57 + const alternatives = internals.applyDefaults.call(this, internals.alternatives);
58 + return args.length ? alternatives.try.apply(alternatives, args) : alternatives;
59 + };
60 +
61 + root.array = function (...args) {
62 +
63 + Hoek.assert(args.length === 0, 'Joi.array() does not allow arguments.');
64 +
65 + return internals.applyDefaults.call(this, internals.array);
66 + };
67 +
68 + root.boolean = root.bool = function (...args) {
69 +
70 + Hoek.assert(args.length === 0, 'Joi.boolean() does not allow arguments.');
71 +
72 + return internals.applyDefaults.call(this, internals.boolean);
73 + };
74 +
75 + root.binary = function (...args) {
76 +
77 + Hoek.assert(args.length === 0, 'Joi.binary() does not allow arguments.');
78 +
79 + return internals.applyDefaults.call(this, internals.binary);
80 + };
81 +
82 + root.date = function (...args) {
83 +
84 + Hoek.assert(args.length === 0, 'Joi.date() does not allow arguments.');
85 +
86 + return internals.applyDefaults.call(this, internals.date);
87 + };
88 +
89 + root.func = function (...args) {
90 +
91 + Hoek.assert(args.length === 0, 'Joi.func() does not allow arguments.');
92 +
93 + return internals.applyDefaults.call(this, internals.func);
94 + };
95 +
96 + root.number = function (...args) {
97 +
98 + Hoek.assert(args.length === 0, 'Joi.number() does not allow arguments.');
99 +
100 + return internals.applyDefaults.call(this, internals.number);
101 + };
102 +
103 + root.object = function (...args) {
104 +
105 + const object = internals.applyDefaults.call(this, internals.object);
106 + return args.length ? object.keys(...args) : object;
107 + };
108 +
109 + root.string = function (...args) {
110 +
111 + Hoek.assert(args.length === 0, 'Joi.string() does not allow arguments.');
112 +
113 + return internals.applyDefaults.call(this, internals.string);
114 + };
115 +
116 + root.ref = function (...args) {
117 +
118 + return Ref.create(...args);
119 + };
120 +
121 + root.isRef = function (ref) {
122 +
123 + return Ref.isRef(ref);
124 + };
125 +
126 + root.validate = function (value, ...args /*, [schema], [options], callback */) {
127 +
128 + const last = args[args.length - 1];
129 + const callback = typeof last === 'function' ? last : null;
130 +
131 + const count = args.length - (callback ? 1 : 0);
132 + if (count === 0) {
133 + return any.validate(value, callback);
134 + }
135 +
136 + const options = count === 2 ? args[1] : {};
137 + const schema = root.compile(args[0]);
138 +
139 + return schema._validateWithOptions(value, options, callback);
140 + };
141 +
142 + root.describe = function (...args) {
143 +
144 + const schema = args.length ? root.compile(args[0]) : any;
145 + return schema.describe();
146 + };
147 +
148 + root.compile = function (schema) {
149 +
150 + try {
151 + return Cast.schema(this, schema);
152 + }
153 + catch (err) {
154 + if (err.hasOwnProperty('path')) {
155 + err.message = err.message + '(' + err.path + ')';
156 + }
157 + throw err;
158 + }
159 + };
160 +
161 + root.assert = function (value, schema, message) {
162 +
163 + root.attempt(value, schema, message);
164 + };
165 +
166 + root.attempt = function (value, schema, message) {
167 +
168 + const result = root.validate(value, schema);
169 + const error = result.error;
170 + if (error) {
171 + if (!message) {
172 + if (typeof error.annotate === 'function') {
173 + error.message = error.annotate();
174 + }
175 + throw error;
176 + }
177 +
178 + if (!(message instanceof Error)) {
179 + if (typeof error.annotate === 'function') {
180 + error.message = `${message} ${error.annotate()}`;
181 + }
182 + throw error;
183 + }
184 +
185 + throw message;
186 + }
187 +
188 + return result.value;
189 + };
190 +
191 + root.reach = function (schema, path) {
192 +
193 + Hoek.assert(schema && schema instanceof Any, 'you must provide a joi schema');
194 + Hoek.assert(typeof path === 'string', 'path must be a string');
195 +
196 + if (path === '') {
197 + return schema;
198 + }
199 +
200 + const parts = path.split('.');
201 + const children = schema._inner.children;
202 + if (!children) {
203 + return;
204 + }
205 +
206 + const key = parts[0];
207 + for (let i = 0; i < children.length; ++i) {
208 + const child = children[i];
209 + if (child.key === key) {
210 + return this.reach(child.schema, path.substr(key.length + 1));
211 + }
212 + }
213 + };
214 +
215 + root.lazy = function (fn) {
216 +
217 + return Lazy.set(fn);
218 + };
219 +
220 + root.defaults = function (fn) {
221 +
222 + Hoek.assert(typeof fn === 'function', 'Defaults must be a function');
223 +
224 + let joi = Object.create(this.any());
225 + joi = fn(joi);
226 +
227 + Hoek.assert(joi && joi instanceof this.constructor, 'defaults() must return a schema');
228 +
229 + Object.assign(joi, this, joi.clone()); // Re-add the types from `this` but also keep the settings from joi's potential new defaults
230 +
231 + joi._defaults = (schema) => {
232 +
233 + if (this._defaults) {
234 + schema = this._defaults(schema);
235 + Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema');
236 + }
237 +
238 + schema = fn(schema);
239 + Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema');
240 + return schema;
241 + };
242 +
243 + return joi;
244 + };
245 +
246 + root.extend = function (...args) {
247 +
248 + const extensions = Hoek.flatten(args);
249 + Hoek.assert(extensions.length > 0, 'You need to provide at least one extension');
250 +
251 + this.assert(extensions, root.extensionsSchema);
252 +
253 + const joi = Object.create(this.any());
254 + Object.assign(joi, this);
255 +
256 + for (let i = 0; i < extensions.length; ++i) {
257 + let extension = extensions[i];
258 +
259 + if (typeof extension === 'function') {
260 + extension = extension(joi);
261 + }
262 +
263 + this.assert(extension, root.extensionSchema);
264 +
265 + const base = (extension.base || this.any()).clone(); // Cloning because we're going to override language afterwards
266 + const ctor = base.constructor;
267 + const type = class extends ctor { // eslint-disable-line no-loop-func
268 +
269 + constructor() {
270 +
271 + super();
272 + if (extension.base) {
273 + Object.assign(this, base);
274 + }
275 +
276 + this._type = extension.name;
277 +
278 + if (extension.language) {
279 + this._settings = this._settings || { language: {} };
280 + this._settings.language = Hoek.applyToDefaults(this._settings.language, {
281 + [extension.name]: extension.language
282 + });
283 + }
284 + }
285 +
286 + };
287 +
288 + if (extension.coerce) {
289 + type.prototype._coerce = function (value, state, options) {
290 +
291 + if (ctor.prototype._coerce) {
292 + const baseRet = ctor.prototype._coerce.call(this, value, state, options);
293 +
294 + if (baseRet.errors) {
295 + return baseRet;
296 + }
297 +
298 + value = baseRet.value;
299 + }
300 +
301 + const ret = extension.coerce.call(this, value, state, options);
302 + if (ret instanceof Errors.Err) {
303 + return { value, errors: ret };
304 + }
305 +
306 + return { value: ret };
307 + };
308 + }
309 + if (extension.pre) {
310 + type.prototype._base = function (value, state, options) {
311 +
312 + if (ctor.prototype._base) {
313 + const baseRet = ctor.prototype._base.call(this, value, state, options);
314 +
315 + if (baseRet.errors) {
316 + return baseRet;
317 + }
318 +
319 + value = baseRet.value;
320 + }
321 +
322 + const ret = extension.pre.call(this, value, state, options);
323 + if (ret instanceof Errors.Err) {
324 + return { value, errors: ret };
325 + }
326 +
327 + return { value: ret };
328 + };
329 + }
330 +
331 + if (extension.rules) {
332 + for (let j = 0; j < extension.rules.length; ++j) {
333 + const rule = extension.rules[j];
334 + const ruleArgs = rule.params ?
335 + (rule.params instanceof Any ? rule.params._inner.children.map((k) => k.key) : Object.keys(rule.params)) :
336 + [];
337 + const validateArgs = rule.params ? Cast.schema(this, rule.params) : null;
338 +
339 + type.prototype[rule.name] = function (...rArgs) { // eslint-disable-line no-loop-func
340 +
341 + if (rArgs.length > ruleArgs.length) {
342 + throw new Error('Unexpected number of arguments');
343 + }
344 +
345 + let hasRef = false;
346 + let arg = {};
347 +
348 + for (let k = 0; k < ruleArgs.length; ++k) {
349 + arg[ruleArgs[k]] = rArgs[k];
350 + if (!hasRef && Ref.isRef(rArgs[k])) {
351 + hasRef = true;
352 + }
353 + }
354 +
355 + if (validateArgs) {
356 + arg = joi.attempt(arg, validateArgs);
357 + }
358 +
359 + let schema;
360 + if (rule.validate) {
361 + const validate = function (value, state, options) {
362 +
363 + return rule.validate.call(this, arg, value, state, options);
364 + };
365 +
366 + schema = this._test(rule.name, arg, validate, {
367 + description: rule.description,
368 + hasRef
369 + });
370 + }
371 + else {
372 + schema = this.clone();
373 + }
374 +
375 + if (rule.setup) {
376 + const newSchema = rule.setup.call(schema, arg);
377 + if (newSchema !== undefined) {
378 + Hoek.assert(newSchema instanceof Any, `Setup of extension Joi.${this._type}().${rule.name}() must return undefined or a Joi object`);
379 + schema = newSchema;
380 + }
381 + }
382 +
383 + return schema;
384 + };
385 + }
386 + }
387 +
388 + if (extension.describe) {
389 + type.prototype.describe = function () {
390 +
391 + const description = ctor.prototype.describe.call(this);
392 + return extension.describe.call(this, description);
393 + };
394 + }
395 +
396 + const instance = new type();
397 + joi[extension.name] = function () {
398 +
399 + return internals.applyDefaults.call(this, instance);
400 + };
401 + }
402 +
403 + return joi;
404 + };
405 +
406 + root.extensionSchema = internals.object.keys({
407 + base: internals.object.type(Any, 'Joi object'),
408 + name: internals.string.required(),
409 + coerce: internals.func.arity(3),
410 + pre: internals.func.arity(3),
411 + language: internals.object,
412 + describe: internals.func.arity(1),
413 + rules: internals.array.items(internals.object.keys({
414 + name: internals.string.required(),
415 + setup: internals.func.arity(1),
416 + validate: internals.func.arity(4),
417 + params: [
418 + internals.object.pattern(/.*/, internals.object.type(Any, 'Joi object')),
419 + internals.object.type(internals.object.constructor, 'Joi object')
420 + ],
421 + description: [internals.string, internals.func.arity(1)]
422 + }).or('setup', 'validate'))
423 + }).strict();
424 +
425 + root.extensionsSchema = internals.array.items([internals.object, internals.func.arity(1)]).strict();
426 +
427 + root.version = require('../package.json').version;
428 +
429 + return root;
430 +};
431 +
432 +
433 +module.exports = internals.root();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +
6 +// Declare internals
7 +
8 +const internals = {};
9 +
10 +
11 +exports.errors = {
12 + root: 'value',
13 + key: '"{{!label}}" ',
14 + messages: {
15 + wrapArrays: true
16 + },
17 + any: {
18 + unknown: 'is not allowed',
19 + invalid: 'contains an invalid value',
20 + empty: 'is not allowed to be empty',
21 + required: 'is required',
22 + allowOnly: 'must be one of {{valids}}',
23 + default: 'threw an error when running default method'
24 + },
25 + alternatives: {
26 + base: 'not matching any of the allowed alternatives',
27 + child: null
28 + },
29 + array: {
30 + base: 'must be an array',
31 + includes: 'at position {{pos}} does not match any of the allowed types',
32 + includesSingle: 'single value of "{{!label}}" does not match any of the allowed types',
33 + includesOne: 'at position {{pos}} fails because {{reason}}',
34 + includesOneSingle: 'single value of "{{!label}}" fails because {{reason}}',
35 + includesRequiredUnknowns: 'does not contain {{unknownMisses}} required value(s)',
36 + includesRequiredKnowns: 'does not contain {{knownMisses}}',
37 + includesRequiredBoth: 'does not contain {{knownMisses}} and {{unknownMisses}} other required value(s)',
38 + excludes: 'at position {{pos}} contains an excluded value',
39 + excludesSingle: 'single value of "{{!label}}" contains an excluded value',
40 + min: 'must contain at least {{limit}} items',
41 + max: 'must contain less than or equal to {{limit}} items',
42 + length: 'must contain {{limit}} items',
43 + ordered: 'at position {{pos}} fails because {{reason}}',
44 + orderedLength: 'at position {{pos}} fails because array must contain at most {{limit}} items',
45 + ref: 'references "{{ref}}" which is not a positive integer',
46 + sparse: 'must not be a sparse array',
47 + unique: 'position {{pos}} contains a duplicate value'
48 + },
49 + boolean: {
50 + base: 'must be a boolean'
51 + },
52 + binary: {
53 + base: 'must be a buffer or a string',
54 + min: 'must be at least {{limit}} bytes',
55 + max: 'must be less than or equal to {{limit}} bytes',
56 + length: 'must be {{limit}} bytes'
57 + },
58 + date: {
59 + base: 'must be a number of milliseconds or valid date string',
60 + format: 'must be a string with one of the following formats {{format}}',
61 + strict: 'must be a valid date',
62 + min: 'must be larger than or equal to "{{limit}}"',
63 + max: 'must be less than or equal to "{{limit}}"',
64 + isoDate: 'must be a valid ISO 8601 date',
65 + timestamp: {
66 + javascript: 'must be a valid timestamp or number of milliseconds',
67 + unix: 'must be a valid timestamp or number of seconds'
68 + },
69 + ref: 'references "{{ref}}" which is not a date'
70 + },
71 + function: {
72 + base: 'must be a Function',
73 + arity: 'must have an arity of {{n}}',
74 + minArity: 'must have an arity greater or equal to {{n}}',
75 + maxArity: 'must have an arity lesser or equal to {{n}}',
76 + ref: 'must be a Joi reference',
77 + class: 'must be a class'
78 + },
79 + lazy: {
80 + base: '!!schema error: lazy schema must be set',
81 + schema: '!!schema error: lazy schema function must return a schema'
82 + },
83 + object: {
84 + base: 'must be an object',
85 + child: '!!child "{{!child}}" fails because {{reason}}',
86 + min: 'must have at least {{limit}} children',
87 + max: 'must have less than or equal to {{limit}} children',
88 + length: 'must have {{limit}} children',
89 + allowUnknown: '!!"{{!child}}" is not allowed',
90 + with: '!!"{{mainWithLabel}}" missing required peer "{{peerWithLabel}}"',
91 + without: '!!"{{mainWithLabel}}" conflict with forbidden peer "{{peerWithLabel}}"',
92 + missing: 'must contain at least one of {{peersWithLabels}}',
93 + xor: 'contains a conflict between exclusive peers {{peersWithLabels}}',
94 + or: 'must contain at least one of {{peersWithLabels}}',
95 + and: 'contains {{presentWithLabels}} without its required peers {{missingWithLabels}}',
96 + nand: '!!"{{mainWithLabel}}" must not exist simultaneously with {{peersWithLabels}}',
97 + assert: '!!"{{ref}}" validation failed because "{{ref}}" failed to {{message}}',
98 + rename: {
99 + multiple: 'cannot rename child "{{from}}" because multiple renames are disabled and another key was already renamed to "{{to}}"',
100 + override: 'cannot rename child "{{from}}" because override is disabled and target "{{to}}" exists',
101 + regex: {
102 + multiple: 'cannot rename children {{from}} because multiple renames are disabled and another key was already renamed to "{{to}}"',
103 + override: 'cannot rename children {{from}} because override is disabled and target "{{to}}" exists'
104 + }
105 + },
106 + type: 'must be an instance of "{{type}}"',
107 + schema: 'must be a Joi instance'
108 + },
109 + number: {
110 + base: 'must be a number',
111 + min: 'must be larger than or equal to {{limit}}',
112 + max: 'must be less than or equal to {{limit}}',
113 + less: 'must be less than {{limit}}',
114 + greater: 'must be greater than {{limit}}',
115 + float: 'must be a float or double',
116 + integer: 'must be an integer',
117 + negative: 'must be a negative number',
118 + positive: 'must be a positive number',
119 + precision: 'must have no more than {{limit}} decimal places',
120 + ref: 'references "{{ref}}" which is not a number',
121 + multiple: 'must be a multiple of {{multiple}}'
122 + },
123 + string: {
124 + base: 'must be a string',
125 + min: 'length must be at least {{limit}} characters long',
126 + max: 'length must be less than or equal to {{limit}} characters long',
127 + length: 'length must be {{limit}} characters long',
128 + alphanum: 'must only contain alpha-numeric characters',
129 + token: 'must only contain alpha-numeric and underscore characters',
130 + regex: {
131 + base: 'with value "{{!value}}" fails to match the required pattern: {{pattern}}',
132 + name: 'with value "{{!value}}" fails to match the {{name}} pattern',
133 + invert: {
134 + base: 'with value "{{!value}}" matches the inverted pattern: {{pattern}}',
135 + name: 'with value "{{!value}}" matches the inverted {{name}} pattern'
136 + }
137 + },
138 + email: 'must be a valid email',
139 + uri: 'must be a valid uri',
140 + uriRelativeOnly: 'must be a valid relative uri',
141 + uriCustomScheme: 'must be a valid uri with a scheme matching the {{scheme}} pattern',
142 + isoDate: 'must be a valid ISO 8601 date',
143 + guid: 'must be a valid GUID',
144 + hex: 'must only contain hexadecimal characters',
145 + base64: 'must be a valid base64 string',
146 + hostname: 'must be a valid hostname',
147 + normalize: 'must be unicode normalized in the {{form}} form',
148 + lowercase: 'must only contain lowercase characters',
149 + uppercase: 'must only contain uppercase characters',
150 + trim: 'must not have leading or trailing whitespace',
151 + creditCard: 'must be a credit card',
152 + ref: 'references "{{ref}}" which is not a number',
153 + ip: 'must be a valid ip address with a {{cidr}} CIDR',
154 + ipVersion: 'must be a valid ip address of one of the following versions {{version}} with a {{cidr}} CIDR'
155 + }
156 +};
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +
7 +
8 +// Declare internals
9 +
10 +const internals = {};
11 +
12 +
13 +exports.create = function (key, options) {
14 +
15 + Hoek.assert(typeof key === 'string', 'Invalid reference key:', key);
16 +
17 + const settings = Hoek.clone(options); // options can be reused and modified
18 +
19 + const ref = function (value, validationOptions) {
20 +
21 + return Hoek.reach(ref.isContext ? validationOptions.context : value, ref.key, settings);
22 + };
23 +
24 + ref.isContext = (key[0] === ((settings && settings.contextPrefix) || '$'));
25 + ref.key = (ref.isContext ? key.slice(1) : key);
26 + ref.path = ref.key.split((settings && settings.separator) || '.');
27 + ref.depth = ref.path.length;
28 + ref.root = ref.path[0];
29 + ref.isJoi = true;
30 +
31 + ref.toString = function () {
32 +
33 + return (ref.isContext ? 'context:' : 'ref:') + ref.key;
34 + };
35 +
36 + return ref;
37 +};
38 +
39 +
40 +exports.isRef = function (ref) {
41 +
42 + return typeof ref === 'function' && ref.isJoi;
43 +};
44 +
45 +
46 +exports.push = function (array, ref) {
47 +
48 + if (exports.isRef(ref) &&
49 + !ref.isContext) {
50 +
51 + array.push(ref.root);
52 + }
53 +};
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Joi = require('../');
6 +
7 +
8 +// Declare internals
9 +
10 +const internals = {};
11 +
12 +exports.options = Joi.object({
13 + abortEarly: Joi.boolean(),
14 + convert: Joi.boolean(),
15 + allowUnknown: Joi.boolean(),
16 + skipFunctions: Joi.boolean(),
17 + stripUnknown: [Joi.boolean(), Joi.object({ arrays: Joi.boolean(), objects: Joi.boolean() }).or('arrays', 'objects')],
18 + language: Joi.object(),
19 + presence: Joi.string().only('required', 'optional', 'forbidden', 'ignore'),
20 + raw: Joi.boolean(),
21 + context: Joi.object(),
22 + strip: Joi.boolean(),
23 + noDefaults: Joi.boolean(),
24 + escapeHtml: Joi.boolean()
25 +}).strict();
1 +'use strict';
2 +
3 +const Ref = require('./ref');
4 +
5 +module.exports = class Set {
6 +
7 + constructor() {
8 +
9 + this._set = [];
10 + }
11 +
12 + add(value, refs) {
13 +
14 + if (!Ref.isRef(value) && this.has(value, null, null, false)) {
15 +
16 + return;
17 + }
18 +
19 + if (refs !== undefined) { // If it's a merge, we don't have any refs
20 + Ref.push(refs, value);
21 + }
22 +
23 + this._set.push(value);
24 + return this;
25 + }
26 +
27 + merge(add, remove) {
28 +
29 + for (let i = 0; i < add._set.length; ++i) {
30 + this.add(add._set[i]);
31 + }
32 +
33 + for (let i = 0; i < remove._set.length; ++i) {
34 + this.remove(remove._set[i]);
35 + }
36 +
37 + return this;
38 + }
39 +
40 + remove(value) {
41 +
42 + this._set = this._set.filter((item) => value !== item);
43 + return this;
44 + }
45 +
46 + has(value, state, options, insensitive) {
47 +
48 + for (let i = 0; i < this._set.length; ++i) {
49 + let items = this._set[i];
50 +
51 + if (state && Ref.isRef(items)) { // Only resolve references if there is a state, otherwise it's a merge
52 + items = items(state.reference || state.parent, options);
53 + }
54 +
55 + if (!Array.isArray(items)) {
56 + items = [items];
57 + }
58 +
59 + for (let j = 0; j < items.length; ++j) {
60 + const item = items[j];
61 + if (typeof value !== typeof item) {
62 + continue;
63 + }
64 +
65 + if (value === item ||
66 + (value instanceof Date && item instanceof Date && value.getTime() === item.getTime()) ||
67 + (insensitive && typeof value === 'string' && value.toLowerCase() === item.toLowerCase()) ||
68 + (Buffer.isBuffer(value) && Buffer.isBuffer(item) && value.length === item.length && value.toString('binary') === item.toString('binary'))) {
69 +
70 + return true;
71 + }
72 + }
73 + }
74 +
75 + return false;
76 + }
77 +
78 + values(options) {
79 +
80 + if (options && options.stripUndefined) {
81 + const values = [];
82 +
83 + for (let i = 0; i < this._set.length; ++i) {
84 + const item = this._set[i];
85 + if (item !== undefined) {
86 + values.push(item);
87 + }
88 + }
89 +
90 + return values;
91 + }
92 +
93 + return this._set.slice();
94 + }
95 +
96 + slice() {
97 +
98 + const newSet = new Set();
99 + newSet._set = this._set.slice();
100 +
101 + return newSet;
102 + }
103 +
104 + concat(source) {
105 +
106 + const newSet = new Set();
107 + newSet._set = this._set.concat(source._set);
108 +
109 + return newSet;
110 + }
111 +};
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +const Any = require('../any');
7 +const Cast = require('../../cast');
8 +const Ref = require('../../ref');
9 +
10 +
11 +// Declare internals
12 +
13 +const internals = {};
14 +
15 +
16 +internals.Alternatives = class extends Any {
17 +
18 + constructor() {
19 +
20 + super();
21 + this._type = 'alternatives';
22 + this._invalids.remove(null);
23 + this._inner.matches = [];
24 + }
25 +
26 + _base(value, state, options) {
27 +
28 + let errors = [];
29 + const il = this._inner.matches.length;
30 + const baseType = this._baseType;
31 +
32 + for (let i = 0; i < il; ++i) {
33 + const item = this._inner.matches[i];
34 + if (!item.schema) {
35 + const schema = item.peek || item.is;
36 + const input = item.is ? item.ref(state.reference || state.parent, options) : value;
37 + const failed = schema._validate(input, null, options, state.parent).errors;
38 +
39 + if (failed) {
40 + if (item.otherwise) {
41 + return item.otherwise._validate(value, state, options);
42 + }
43 + }
44 + else if (item.then) {
45 + return item.then._validate(value, state, options);
46 + }
47 +
48 + if (i === (il - 1) && baseType) {
49 + return baseType._validate(value, state, options);
50 + }
51 +
52 + continue;
53 + }
54 +
55 + const result = item.schema._validate(value, state, options);
56 + if (!result.errors) { // Found a valid match
57 + return result;
58 + }
59 +
60 + errors = errors.concat(result.errors);
61 + }
62 +
63 + if (errors.length) {
64 + return { errors: this.createError('alternatives.child', { reason: errors }, state, options) };
65 + }
66 +
67 + return { errors: this.createError('alternatives.base', null, state, options) };
68 + }
69 +
70 + try(...schemas) {
71 +
72 + schemas = Hoek.flatten(schemas);
73 + Hoek.assert(schemas.length, 'Cannot add other alternatives without at least one schema');
74 +
75 + const obj = this.clone();
76 +
77 + for (let i = 0; i < schemas.length; ++i) {
78 + const cast = Cast.schema(this._currentJoi, schemas[i]);
79 + if (cast._refs.length) {
80 + obj._refs = obj._refs.concat(cast._refs);
81 + }
82 + obj._inner.matches.push({ schema: cast });
83 + }
84 +
85 + return obj;
86 + }
87 +
88 + when(condition, options) {
89 +
90 + let schemaCondition = false;
91 + Hoek.assert(Ref.isRef(condition) || typeof condition === 'string' || (schemaCondition = condition instanceof Any), 'Invalid condition:', condition);
92 + Hoek.assert(options, 'Missing options');
93 + Hoek.assert(typeof options === 'object', 'Invalid options');
94 + if (schemaCondition) {
95 + Hoek.assert(!options.hasOwnProperty('is'), '"is" can not be used with a schema condition');
96 + }
97 + else {
98 + Hoek.assert(options.hasOwnProperty('is'), 'Missing "is" directive');
99 + }
100 + Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"');
101 +
102 + const obj = this.clone();
103 + let is;
104 + if (!schemaCondition) {
105 + is = Cast.schema(this._currentJoi, options.is);
106 +
107 + if (options.is === null || !(Ref.isRef(options.is) || options.is instanceof Any)) {
108 +
109 + // Only apply required if this wasn't already a schema or a ref, we'll suppose people know what they're doing
110 + is = is.required();
111 + }
112 + }
113 +
114 + const item = {
115 + ref: schemaCondition ? null : Cast.ref(condition),
116 + peek: schemaCondition ? condition : null,
117 + is,
118 + then: options.then !== undefined ? Cast.schema(this._currentJoi, options.then) : undefined,
119 + otherwise: options.otherwise !== undefined ? Cast.schema(this._currentJoi, options.otherwise) : undefined
120 + };
121 +
122 + if (obj._baseType) {
123 +
124 + item.then = item.then && obj._baseType.concat(item.then);
125 + item.otherwise = item.otherwise && obj._baseType.concat(item.otherwise);
126 + }
127 +
128 + if (!schemaCondition) {
129 + Ref.push(obj._refs, item.ref);
130 + obj._refs = obj._refs.concat(item.is._refs);
131 + }
132 +
133 + if (item.then && item.then._refs) {
134 + obj._refs = obj._refs.concat(item.then._refs);
135 + }
136 +
137 + if (item.otherwise && item.otherwise._refs) {
138 + obj._refs = obj._refs.concat(item.otherwise._refs);
139 + }
140 +
141 + obj._inner.matches.push(item);
142 +
143 + return obj;
144 + }
145 +
146 + describe() {
147 +
148 + const description = Any.prototype.describe.call(this);
149 + const alternatives = [];
150 + for (let i = 0; i < this._inner.matches.length; ++i) {
151 + const item = this._inner.matches[i];
152 + if (item.schema) {
153 +
154 + // try()
155 +
156 + alternatives.push(item.schema.describe());
157 + }
158 + else {
159 +
160 + // when()
161 +
162 + const when = item.is ? {
163 + ref: item.ref.toString(),
164 + is: item.is.describe()
165 + } : {
166 + peek: item.peek.describe()
167 + };
168 +
169 + if (item.then) {
170 + when.then = item.then.describe();
171 + }
172 +
173 + if (item.otherwise) {
174 + when.otherwise = item.otherwise.describe();
175 + }
176 +
177 + alternatives.push(when);
178 + }
179 + }
180 +
181 + description.alternatives = alternatives;
182 + return description;
183 + }
184 +
185 +};
186 +
187 +
188 +module.exports = new internals.Alternatives();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +const Ref = require('../../ref');
7 +const Errors = require('../../errors');
8 +let Alternatives = null; // Delay-loaded to prevent circular dependencies
9 +let Cast = null;
10 +
11 +
12 +// Declare internals
13 +
14 +const internals = {
15 + Set: require('../../set')
16 +};
17 +
18 +
19 +internals.defaults = {
20 + abortEarly: true,
21 + convert: true,
22 + allowUnknown: false,
23 + skipFunctions: false,
24 + stripUnknown: false,
25 + language: {},
26 + presence: 'optional',
27 + strip: false,
28 + noDefaults: false,
29 + escapeHtml: false
30 +
31 + // context: null
32 +};
33 +
34 +
35 +module.exports = internals.Any = class {
36 +
37 + constructor() {
38 +
39 + Cast = Cast || require('../../cast');
40 +
41 + this.isJoi = true;
42 + this._type = 'any';
43 + this._settings = null;
44 + this._valids = new internals.Set();
45 + this._invalids = new internals.Set();
46 + this._tests = [];
47 + this._refs = [];
48 + this._flags = {
49 + /*
50 + presence: 'optional', // optional, required, forbidden, ignore
51 + allowOnly: false,
52 + allowUnknown: undefined,
53 + default: undefined,
54 + forbidden: false,
55 + encoding: undefined,
56 + insensitive: false,
57 + trim: false,
58 + normalize: undefined, // NFC, NFD, NFKC, NFKD
59 + case: undefined, // upper, lower
60 + empty: undefined,
61 + func: false,
62 + raw: false
63 + */
64 + };
65 +
66 + this._description = null;
67 + this._unit = null;
68 + this._notes = [];
69 + this._tags = [];
70 + this._examples = [];
71 + this._meta = [];
72 +
73 + this._inner = {}; // Hash of arrays of immutable objects
74 + }
75 +
76 + get schemaType() {
77 +
78 + return this._type;
79 + }
80 +
81 + createError(type, context, state, options, flags = this._flags) {
82 +
83 + return Errors.create(type, context, state, options, flags);
84 + }
85 +
86 + createOverrideError(type, context, state, options, message, template) {
87 +
88 + return Errors.create(type, context, state, options, this._flags, message, template);
89 + }
90 +
91 + checkOptions(options) {
92 +
93 + const Schemas = require('../../schemas');
94 + const result = Schemas.options.validate(options);
95 + if (result.error) {
96 + throw new Error(result.error.details[0].message);
97 + }
98 + }
99 +
100 + clone() {
101 +
102 + const obj = Object.create(Object.getPrototypeOf(this));
103 +
104 + obj.isJoi = true;
105 + obj._currentJoi = this._currentJoi;
106 + obj._type = this._type;
107 + obj._settings = internals.concatSettings(this._settings);
108 + obj._baseType = this._baseType;
109 + obj._valids = Hoek.clone(this._valids);
110 + obj._invalids = Hoek.clone(this._invalids);
111 + obj._tests = this._tests.slice();
112 + obj._refs = this._refs.slice();
113 + obj._flags = Hoek.clone(this._flags);
114 +
115 + obj._description = this._description;
116 + obj._unit = this._unit;
117 + obj._notes = this._notes.slice();
118 + obj._tags = this._tags.slice();
119 + obj._examples = this._examples.slice();
120 + obj._meta = this._meta.slice();
121 +
122 + obj._inner = {};
123 + const inners = Object.keys(this._inner);
124 + for (let i = 0; i < inners.length; ++i) {
125 + const key = inners[i];
126 + obj._inner[key] = this._inner[key] ? this._inner[key].slice() : null;
127 + }
128 +
129 + return obj;
130 + }
131 +
132 + concat(schema) {
133 +
134 + Hoek.assert(schema instanceof internals.Any, 'Invalid schema object');
135 + Hoek.assert(this._type === 'any' || schema._type === 'any' || schema._type === this._type, 'Cannot merge type', this._type, 'with another type:', schema._type);
136 +
137 + let obj = this.clone();
138 +
139 + if (this._type === 'any' && schema._type !== 'any') {
140 +
141 + // Reset values as if we were "this"
142 + const tmpObj = schema.clone();
143 + const keysToRestore = ['_settings', '_valids', '_invalids', '_tests', '_refs', '_flags', '_description', '_unit',
144 + '_notes', '_tags', '_examples', '_meta', '_inner'];
145 +
146 + for (let i = 0; i < keysToRestore.length; ++i) {
147 + tmpObj[keysToRestore[i]] = obj[keysToRestore[i]];
148 + }
149 +
150 + obj = tmpObj;
151 + }
152 +
153 + obj._settings = obj._settings ? internals.concatSettings(obj._settings, schema._settings) : schema._settings;
154 + obj._valids.merge(schema._valids, schema._invalids);
155 + obj._invalids.merge(schema._invalids, schema._valids);
156 + obj._tests = obj._tests.concat(schema._tests);
157 + obj._refs = obj._refs.concat(schema._refs);
158 + Hoek.merge(obj._flags, schema._flags);
159 +
160 + obj._description = schema._description || obj._description;
161 + obj._unit = schema._unit || obj._unit;
162 + obj._notes = obj._notes.concat(schema._notes);
163 + obj._tags = obj._tags.concat(schema._tags);
164 + obj._examples = obj._examples.concat(schema._examples);
165 + obj._meta = obj._meta.concat(schema._meta);
166 +
167 + const inners = Object.keys(schema._inner);
168 + const isObject = obj._type === 'object';
169 + for (let i = 0; i < inners.length; ++i) {
170 + const key = inners[i];
171 + const source = schema._inner[key];
172 + if (source) {
173 + const target = obj._inner[key];
174 + if (target) {
175 + if (isObject && key === 'children') {
176 + const keys = {};
177 +
178 + for (let j = 0; j < target.length; ++j) {
179 + keys[target[j].key] = j;
180 + }
181 +
182 + for (let j = 0; j < source.length; ++j) {
183 + const sourceKey = source[j].key;
184 + if (keys[sourceKey] >= 0) {
185 + target[keys[sourceKey]] = {
186 + key: sourceKey,
187 + schema: target[keys[sourceKey]].schema.concat(source[j].schema)
188 + };
189 + }
190 + else {
191 + target.push(source[j]);
192 + }
193 + }
194 + }
195 + else {
196 + obj._inner[key] = obj._inner[key].concat(source);
197 + }
198 + }
199 + else {
200 + obj._inner[key] = source.slice();
201 + }
202 + }
203 + }
204 +
205 + return obj;
206 + }
207 +
208 + _test(name, arg, func, options) {
209 +
210 + const obj = this.clone();
211 + obj._tests.push({ func, name, arg, options });
212 + return obj;
213 + }
214 +
215 + options(options) {
216 +
217 + Hoek.assert(!options.context, 'Cannot override context');
218 + this.checkOptions(options);
219 +
220 + const obj = this.clone();
221 + obj._settings = internals.concatSettings(obj._settings, options);
222 + return obj;
223 + }
224 +
225 + strict(isStrict) {
226 +
227 + const obj = this.clone();
228 + obj._settings = obj._settings || {};
229 + obj._settings.convert = isStrict === undefined ? false : !isStrict;
230 + return obj;
231 + }
232 +
233 + raw(isRaw) {
234 +
235 + const value = isRaw === undefined ? true : isRaw;
236 +
237 + if (this._flags.raw === value) {
238 + return this;
239 + }
240 +
241 + const obj = this.clone();
242 + obj._flags.raw = value;
243 + return obj;
244 + }
245 +
246 + error(err) {
247 +
248 + Hoek.assert(err && (err instanceof Error || typeof err === 'function'), 'Must provide a valid Error object or a function');
249 +
250 + const obj = this.clone();
251 + obj._flags.error = err;
252 + return obj;
253 + }
254 +
255 + allow(...values) {
256 +
257 + const obj = this.clone();
258 + values = Hoek.flatten(values);
259 + for (let i = 0; i < values.length; ++i) {
260 + const value = values[i];
261 +
262 + Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');
263 + obj._invalids.remove(value);
264 + obj._valids.add(value, obj._refs);
265 + }
266 + return obj;
267 + }
268 +
269 + valid(...values) {
270 +
271 + const obj = this.allow(...values);
272 + obj._flags.allowOnly = true;
273 + return obj;
274 + }
275 +
276 + invalid(...values) {
277 +
278 + const obj = this.clone();
279 + values = Hoek.flatten(values);
280 + for (let i = 0; i < values.length; ++i) {
281 + const value = values[i];
282 +
283 + Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');
284 + obj._valids.remove(value);
285 + obj._invalids.add(value, obj._refs);
286 + }
287 +
288 + return obj;
289 + }
290 +
291 + required() {
292 +
293 + if (this._flags.presence === 'required') {
294 + return this;
295 + }
296 +
297 + const obj = this.clone();
298 + obj._flags.presence = 'required';
299 + return obj;
300 + }
301 +
302 + optional() {
303 +
304 + if (this._flags.presence === 'optional') {
305 + return this;
306 + }
307 +
308 + const obj = this.clone();
309 + obj._flags.presence = 'optional';
310 + return obj;
311 + }
312 +
313 +
314 + forbidden() {
315 +
316 + if (this._flags.presence === 'forbidden') {
317 + return this;
318 + }
319 +
320 + const obj = this.clone();
321 + obj._flags.presence = 'forbidden';
322 + return obj;
323 + }
324 +
325 +
326 + strip() {
327 +
328 + if (this._flags.strip) {
329 + return this;
330 + }
331 +
332 + const obj = this.clone();
333 + obj._flags.strip = true;
334 + return obj;
335 + }
336 +
337 + applyFunctionToChildren(children, fn, args, root) {
338 +
339 + children = [].concat(children);
340 +
341 + if (children.length !== 1 || children[0] !== '') {
342 + root = root ? (root + '.') : '';
343 +
344 + const extraChildren = (children[0] === '' ? children.slice(1) : children).map((child) => {
345 +
346 + return root + child;
347 + });
348 +
349 + throw new Error('unknown key(s) ' + extraChildren.join(', '));
350 + }
351 +
352 + return this[fn].apply(this, args);
353 + }
354 +
355 + default(value, description) {
356 +
357 + if (typeof value === 'function' &&
358 + !Ref.isRef(value)) {
359 +
360 + if (!value.description &&
361 + description) {
362 +
363 + value.description = description;
364 + }
365 +
366 + if (!this._flags.func) {
367 + Hoek.assert(typeof value.description === 'string' && value.description.length > 0, 'description must be provided when default value is a function');
368 + }
369 + }
370 +
371 + const obj = this.clone();
372 + obj._flags.default = value;
373 + Ref.push(obj._refs, value);
374 + return obj;
375 + }
376 +
377 + empty(schema) {
378 +
379 + const obj = this.clone();
380 + if (schema === undefined) {
381 + delete obj._flags.empty;
382 + }
383 + else {
384 + obj._flags.empty = Cast.schema(this._currentJoi, schema);
385 + }
386 + return obj;
387 + }
388 +
389 + when(condition, options) {
390 +
391 + Hoek.assert(options && typeof options === 'object', 'Invalid options');
392 + Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"');
393 +
394 + const then = options.hasOwnProperty('then') ? this.concat(Cast.schema(this._currentJoi, options.then)) : undefined;
395 + const otherwise = options.hasOwnProperty('otherwise') ? this.concat(Cast.schema(this._currentJoi, options.otherwise)) : undefined;
396 +
397 + Alternatives = Alternatives || require('../alternatives');
398 +
399 + const alternativeOptions = { then, otherwise };
400 + if (Object.prototype.hasOwnProperty.call(options, 'is')) {
401 + alternativeOptions.is = options.is;
402 + }
403 + const obj = Alternatives.when(condition, alternativeOptions);
404 + obj._flags.presence = 'ignore';
405 + obj._baseType = this;
406 +
407 + return obj;
408 + }
409 +
410 + description(desc) {
411 +
412 + Hoek.assert(desc && typeof desc === 'string', 'Description must be a non-empty string');
413 +
414 + const obj = this.clone();
415 + obj._description = desc;
416 + return obj;
417 + }
418 +
419 + notes(notes) {
420 +
421 + Hoek.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array');
422 +
423 + const obj = this.clone();
424 + obj._notes = obj._notes.concat(notes);
425 + return obj;
426 + }
427 +
428 + tags(tags) {
429 +
430 + Hoek.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array');
431 +
432 + const obj = this.clone();
433 + obj._tags = obj._tags.concat(tags);
434 + return obj;
435 + }
436 +
437 + meta(meta) {
438 +
439 + Hoek.assert(meta !== undefined, 'Meta cannot be undefined');
440 +
441 + const obj = this.clone();
442 + obj._meta = obj._meta.concat(meta);
443 + return obj;
444 + }
445 +
446 + example(...args) {
447 +
448 + Hoek.assert(args.length === 1, 'Missing example');
449 + const value = args[0];
450 +
451 + const obj = this.clone();
452 + obj._examples.push(value);
453 + return obj;
454 + }
455 +
456 + unit(name) {
457 +
458 + Hoek.assert(name && typeof name === 'string', 'Unit name must be a non-empty string');
459 +
460 + const obj = this.clone();
461 + obj._unit = name;
462 + return obj;
463 + }
464 +
465 + _prepareEmptyValue(value) {
466 +
467 + if (typeof value === 'string' && this._flags.trim) {
468 + return value.trim();
469 + }
470 +
471 + return value;
472 + }
473 +
474 + _validate(value, state, options, reference) {
475 +
476 + const originalValue = value;
477 +
478 + // Setup state and settings
479 +
480 + state = state || { key: '', path: [], parent: null, reference };
481 +
482 + if (this._settings) {
483 + options = internals.concatSettings(options, this._settings);
484 + }
485 +
486 + let errors = [];
487 + const finish = () => {
488 +
489 + let finalValue;
490 +
491 + if (value !== undefined) {
492 + finalValue = this._flags.raw ? originalValue : value;
493 + }
494 + else if (options.noDefaults) {
495 + finalValue = value;
496 + }
497 + else if (Ref.isRef(this._flags.default)) {
498 + finalValue = this._flags.default(state.parent, options);
499 + }
500 + else if (typeof this._flags.default === 'function' &&
501 + !(this._flags.func && !this._flags.default.description)) {
502 +
503 + let args;
504 +
505 + if (state.parent !== null &&
506 + this._flags.default.length > 0) {
507 +
508 + args = [Hoek.clone(state.parent), options];
509 + }
510 +
511 + const defaultValue = internals._try(this._flags.default, args);
512 + finalValue = defaultValue.value;
513 + if (defaultValue.error) {
514 + errors.push(this.createError('any.default', { error: defaultValue.error }, state, options));
515 + }
516 + }
517 + else {
518 + finalValue = Hoek.clone(this._flags.default);
519 + }
520 +
521 + if (errors.length && typeof this._flags.error === 'function') {
522 + const change = this._flags.error.call(this, errors);
523 +
524 + if (typeof change === 'string') {
525 + errors = [this.createOverrideError('override', { reason: errors }, state, options, change)];
526 + }
527 + else {
528 + errors = [].concat(change)
529 + .map((err) => {
530 +
531 + return err instanceof Error ?
532 + err :
533 + this.createOverrideError(err.type || 'override', err.context, state, options, err.message, err.template);
534 + });
535 + }
536 + }
537 +
538 + return {
539 + value: this._flags.strip ? undefined : finalValue,
540 + finalValue,
541 + errors: errors.length ? errors : null
542 + };
543 + };
544 +
545 + if (this._coerce) {
546 + const coerced = this._coerce.call(this, value, state, options);
547 + if (coerced.errors) {
548 + value = coerced.value;
549 + errors = errors.concat(coerced.errors);
550 + return finish(); // Coerced error always aborts early
551 + }
552 +
553 + value = coerced.value;
554 + }
555 +
556 + if (this._flags.empty && !this._flags.empty._validate(this._prepareEmptyValue(value), null, internals.defaults).errors) {
557 + value = undefined;
558 + }
559 +
560 + // Check presence requirements
561 +
562 + const presence = this._flags.presence || options.presence;
563 + if (presence === 'optional') {
564 + if (value === undefined) {
565 + const isDeepDefault = this._flags.hasOwnProperty('default') && this._flags.default === undefined;
566 + if (isDeepDefault && this._type === 'object') {
567 + value = {};
568 + }
569 + else {
570 + return finish();
571 + }
572 + }
573 + }
574 + else if (presence === 'required' &&
575 + value === undefined) {
576 +
577 + errors.push(this.createError('any.required', null, state, options));
578 + return finish();
579 + }
580 + else if (presence === 'forbidden') {
581 + if (value === undefined) {
582 + return finish();
583 + }
584 +
585 + errors.push(this.createError('any.unknown', null, state, options));
586 + return finish();
587 + }
588 +
589 + // Check allowed and denied values using the original value
590 +
591 + if (this._valids.has(value, state, options, this._flags.insensitive)) {
592 + return finish();
593 + }
594 +
595 + if (this._invalids.has(value, state, options, this._flags.insensitive)) {
596 + errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', null, state, options));
597 + if (options.abortEarly ||
598 + value === undefined) { // No reason to keep validating missing value
599 +
600 + return finish();
601 + }
602 + }
603 +
604 + // Convert value and validate type
605 +
606 + if (this._base) {
607 + const base = this._base.call(this, value, state, options);
608 + if (base.errors) {
609 + value = base.value;
610 + errors = errors.concat(base.errors);
611 + return finish(); // Base error always aborts early
612 + }
613 +
614 + if (base.value !== value) {
615 + value = base.value;
616 +
617 + // Check allowed and denied values using the converted value
618 +
619 + if (this._valids.has(value, state, options, this._flags.insensitive)) {
620 + return finish();
621 + }
622 +
623 + if (this._invalids.has(value, state, options, this._flags.insensitive)) {
624 + errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', null, state, options));
625 + if (options.abortEarly) {
626 + return finish();
627 + }
628 + }
629 + }
630 + }
631 +
632 + // Required values did not match
633 +
634 + if (this._flags.allowOnly) {
635 + errors.push(this.createError('any.allowOnly', { valids: this._valids.values({ stripUndefined: true }) }, state, options));
636 + if (options.abortEarly) {
637 + return finish();
638 + }
639 + }
640 +
641 + // Helper.validate tests
642 +
643 + for (let i = 0; i < this._tests.length; ++i) {
644 + const test = this._tests[i];
645 + const ret = test.func.call(this, value, state, options);
646 + if (ret instanceof Errors.Err) {
647 + errors.push(ret);
648 + if (options.abortEarly) {
649 + return finish();
650 + }
651 + }
652 + else {
653 + value = ret;
654 + }
655 + }
656 +
657 + return finish();
658 + }
659 +
660 + _validateWithOptions(value, options, callback) {
661 +
662 + if (options) {
663 + this.checkOptions(options);
664 + }
665 +
666 + const settings = internals.concatSettings(internals.defaults, options);
667 + const result = this._validate(value, null, settings);
668 + const errors = Errors.process(result.errors, value);
669 +
670 + if (callback) {
671 + return callback(errors, result.value);
672 + }
673 +
674 + return {
675 + error: errors,
676 + value: result.value,
677 + then(resolve, reject) {
678 +
679 + if (errors) {
680 + return Promise.reject(errors).catch(reject);
681 + }
682 +
683 + return Promise.resolve(result.value).then(resolve);
684 + },
685 + catch(reject) {
686 +
687 + if (errors) {
688 + return Promise.reject(errors).catch(reject);
689 + }
690 +
691 + return Promise.resolve(result.value);
692 + }
693 + };
694 + }
695 +
696 + validate(value, options, callback) {
697 +
698 + if (typeof options === 'function') {
699 + return this._validateWithOptions(value, null, options);
700 + }
701 +
702 + return this._validateWithOptions(value, options, callback);
703 + }
704 +
705 + describe() {
706 +
707 + const description = {
708 + type: this._type
709 + };
710 +
711 + const flags = Object.keys(this._flags);
712 + if (flags.length) {
713 + if (['empty', 'default', 'lazy', 'label'].some((flag) => this._flags.hasOwnProperty(flag))) {
714 + description.flags = {};
715 + for (let i = 0; i < flags.length; ++i) {
716 + const flag = flags[i];
717 + if (flag === 'empty') {
718 + description.flags[flag] = this._flags[flag].describe();
719 + }
720 + else if (flag === 'default') {
721 + if (Ref.isRef(this._flags[flag])) {
722 + description.flags[flag] = this._flags[flag].toString();
723 + }
724 + else if (typeof this._flags[flag] === 'function') {
725 + description.flags[flag] = {
726 + description: this._flags[flag].description,
727 + function : this._flags[flag]
728 + };
729 + }
730 + else {
731 + description.flags[flag] = this._flags[flag];
732 + }
733 + }
734 + else if (flag === 'lazy' || flag === 'label') {
735 + // We don't want it in the description
736 + }
737 + else {
738 + description.flags[flag] = this._flags[flag];
739 + }
740 + }
741 + }
742 + else {
743 + description.flags = this._flags;
744 + }
745 + }
746 +
747 + if (this._settings) {
748 + description.options = Hoek.clone(this._settings);
749 + }
750 +
751 + if (this._baseType) {
752 + description.base = this._baseType.describe();
753 + }
754 +
755 + if (this._description) {
756 + description.description = this._description;
757 + }
758 +
759 + if (this._notes.length) {
760 + description.notes = this._notes;
761 + }
762 +
763 + if (this._tags.length) {
764 + description.tags = this._tags;
765 + }
766 +
767 + if (this._meta.length) {
768 + description.meta = this._meta;
769 + }
770 +
771 + if (this._examples.length) {
772 + description.examples = this._examples;
773 + }
774 +
775 + if (this._unit) {
776 + description.unit = this._unit;
777 + }
778 +
779 + const valids = this._valids.values();
780 + if (valids.length) {
781 + description.valids = valids.map((v) => {
782 +
783 + return Ref.isRef(v) ? v.toString() : v;
784 + });
785 + }
786 +
787 + const invalids = this._invalids.values();
788 + if (invalids.length) {
789 + description.invalids = invalids.map((v) => {
790 +
791 + return Ref.isRef(v) ? v.toString() : v;
792 + });
793 + }
794 +
795 + description.rules = [];
796 +
797 + for (let i = 0; i < this._tests.length; ++i) {
798 + const validator = this._tests[i];
799 + const item = { name: validator.name };
800 +
801 + if (validator.arg !== void 0) {
802 + item.arg = Ref.isRef(validator.arg) ? validator.arg.toString() : validator.arg;
803 + }
804 +
805 + const options = validator.options;
806 + if (options) {
807 + if (options.hasRef) {
808 + item.arg = {};
809 + const keys = Object.keys(validator.arg);
810 + for (let j = 0; j < keys.length; ++j) {
811 + const key = keys[j];
812 + const value = validator.arg[key];
813 + item.arg[key] = Ref.isRef(value) ? value.toString() : value;
814 + }
815 + }
816 +
817 + if (typeof options.description === 'string') {
818 + item.description = options.description;
819 + }
820 + else if (typeof options.description === 'function') {
821 + item.description = options.description(item.arg);
822 + }
823 + }
824 +
825 + description.rules.push(item);
826 + }
827 +
828 + if (!description.rules.length) {
829 + delete description.rules;
830 + }
831 +
832 + const label = this._getLabel();
833 + if (label) {
834 + description.label = label;
835 + }
836 +
837 + return description;
838 + }
839 +
840 + label(name) {
841 +
842 + Hoek.assert(name && typeof name === 'string', 'Label name must be a non-empty string');
843 +
844 + const obj = this.clone();
845 + obj._flags.label = name;
846 + return obj;
847 + }
848 +
849 + _getLabel(def) {
850 +
851 + return this._flags.label || def;
852 + }
853 +
854 +};
855 +
856 +
857 +internals.Any.prototype.isImmutable = true; // Prevents Hoek from deep cloning schema objects
858 +
859 +// Aliases
860 +
861 +internals.Any.prototype.only = internals.Any.prototype.equal = internals.Any.prototype.valid;
862 +internals.Any.prototype.disallow = internals.Any.prototype.not = internals.Any.prototype.invalid;
863 +internals.Any.prototype.exist = internals.Any.prototype.required;
864 +
865 +
866 +internals._try = function (fn, args) {
867 +
868 + let err;
869 + let result;
870 +
871 + try {
872 + result = fn.apply(null, args);
873 + }
874 + catch (e) {
875 + err = e;
876 + }
877 +
878 + return {
879 + value: result,
880 + error: err
881 + };
882 +};
883 +
884 +internals.concatSettings = function (target, source) {
885 +
886 + // Used to avoid cloning context
887 +
888 + if (!target &&
889 + !source) {
890 +
891 + return null;
892 + }
893 +
894 + const obj = Object.assign({}, target);
895 +
896 + if (source) {
897 + const sKeys = Object.keys(source);
898 + for (let i = 0; i < sKeys.length; ++i) {
899 + const key = sKeys[i];
900 + if (key !== 'language' ||
901 + !obj.hasOwnProperty(key)) {
902 +
903 + obj[key] = source[key];
904 + }
905 + else {
906 + obj[key] = Hoek.applyToDefaults(obj[key], source[key]);
907 + }
908 + }
909 + }
910 +
911 + return obj;
912 +};
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Any = require('../any');
6 +const Cast = require('../../cast');
7 +const Ref = require('../../ref');
8 +const Hoek = require('hoek');
9 +
10 +
11 +// Declare internals
12 +
13 +const internals = {};
14 +
15 +
16 +internals.fastSplice = function (arr, i) {
17 +
18 + let pos = i;
19 + while (pos < arr.length) {
20 + arr[pos++] = arr[pos];
21 + }
22 +
23 + --arr.length;
24 +};
25 +
26 +
27 +internals.Array = class extends Any {
28 +
29 + constructor() {
30 +
31 + super();
32 + this._type = 'array';
33 + this._inner.items = [];
34 + this._inner.ordereds = [];
35 + this._inner.inclusions = [];
36 + this._inner.exclusions = [];
37 + this._inner.requireds = [];
38 + this._flags.sparse = false;
39 + }
40 +
41 + _base(value, state, options) {
42 +
43 + const result = {
44 + value
45 + };
46 +
47 + if (typeof value === 'string' &&
48 + options.convert) {
49 +
50 + internals.safeParse(value, result);
51 + }
52 +
53 + let isArray = Array.isArray(result.value);
54 + const wasArray = isArray;
55 + if (options.convert && this._flags.single && !isArray) {
56 + result.value = [result.value];
57 + isArray = true;
58 + }
59 +
60 + if (!isArray) {
61 + result.errors = this.createError('array.base', null, state, options);
62 + return result;
63 + }
64 +
65 + if (this._inner.inclusions.length ||
66 + this._inner.exclusions.length ||
67 + this._inner.requireds.length ||
68 + this._inner.ordereds.length ||
69 + !this._flags.sparse) {
70 +
71 + // Clone the array so that we don't modify the original
72 + if (wasArray) {
73 + result.value = result.value.slice(0);
74 + }
75 +
76 + result.errors = this._checkItems.call(this, result.value, wasArray, state, options);
77 +
78 + if (result.errors && wasArray && options.convert && this._flags.single) {
79 +
80 + // Attempt a 2nd pass by putting the array inside one.
81 + const previousErrors = result.errors;
82 +
83 + result.value = [result.value];
84 + result.errors = this._checkItems.call(this, result.value, wasArray, state, options);
85 +
86 + if (result.errors) {
87 +
88 + // Restore previous errors and value since this didn't validate either.
89 + result.errors = previousErrors;
90 + result.value = result.value[0];
91 + }
92 + }
93 + }
94 +
95 + return result;
96 + }
97 +
98 + _checkItems(items, wasArray, state, options) {
99 +
100 + const errors = [];
101 + let errored;
102 +
103 + const requireds = this._inner.requireds.slice();
104 + const ordereds = this._inner.ordereds.slice();
105 + const inclusions = this._inner.inclusions.concat(requireds);
106 +
107 + let il = items.length;
108 + for (let i = 0; i < il; ++i) {
109 + errored = false;
110 + const item = items[i];
111 + let isValid = false;
112 + const key = wasArray ? i : state.key;
113 + const path = wasArray ? state.path.concat(i) : state.path;
114 + const localState = { key, path, parent: state.parent, reference: state.reference };
115 + let res;
116 +
117 + // Sparse
118 +
119 + if (!this._flags.sparse && item === undefined) {
120 + errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options));
121 +
122 + if (options.abortEarly) {
123 + return errors;
124 + }
125 +
126 + ordereds.shift();
127 +
128 + continue;
129 + }
130 +
131 + // Exclusions
132 +
133 + for (let j = 0; j < this._inner.exclusions.length; ++j) {
134 + res = this._inner.exclusions[j]._validate(item, localState, {}); // Not passing options to use defaults
135 +
136 + if (!res.errors) {
137 + errors.push(this.createError(wasArray ? 'array.excludes' : 'array.excludesSingle', { pos: i, value: item }, { key: state.key, path: localState.path }, options));
138 + errored = true;
139 +
140 + if (options.abortEarly) {
141 + return errors;
142 + }
143 +
144 + ordereds.shift();
145 +
146 + break;
147 + }
148 + }
149 +
150 + if (errored) {
151 + continue;
152 + }
153 +
154 + // Ordered
155 + if (this._inner.ordereds.length) {
156 + if (ordereds.length > 0) {
157 + const ordered = ordereds.shift();
158 + res = ordered._validate(item, localState, options);
159 + if (!res.errors) {
160 + if (ordered._flags.strip) {
161 + internals.fastSplice(items, i);
162 + --i;
163 + --il;
164 + }
165 + else if (!this._flags.sparse && res.value === undefined) {
166 + errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options));
167 +
168 + if (options.abortEarly) {
169 + return errors;
170 + }
171 +
172 + continue;
173 + }
174 + else {
175 + items[i] = res.value;
176 + }
177 + }
178 + else {
179 + errors.push(this.createError('array.ordered', { pos: i, reason: res.errors, value: item }, { key: state.key, path: localState.path }, options));
180 + if (options.abortEarly) {
181 + return errors;
182 + }
183 + }
184 + continue;
185 + }
186 + else if (!this._inner.items.length) {
187 + errors.push(this.createError('array.orderedLength', { pos: i, limit: this._inner.ordereds.length }, { key: state.key, path: localState.path }, options));
188 + if (options.abortEarly) {
189 + return errors;
190 + }
191 + continue;
192 + }
193 + }
194 +
195 + // Requireds
196 +
197 + const requiredChecks = [];
198 + let jl = requireds.length;
199 + for (let j = 0; j < jl; ++j) {
200 + res = requiredChecks[j] = requireds[j]._validate(item, localState, options);
201 + if (!res.errors) {
202 + items[i] = res.value;
203 + isValid = true;
204 + internals.fastSplice(requireds, j);
205 + --j;
206 + --jl;
207 +
208 + if (!this._flags.sparse && res.value === undefined) {
209 + errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options));
210 +
211 + if (options.abortEarly) {
212 + return errors;
213 + }
214 + }
215 +
216 + break;
217 + }
218 + }
219 +
220 + if (isValid) {
221 + continue;
222 + }
223 +
224 + // Inclusions
225 +
226 + const stripUnknown = options.stripUnknown
227 + ? (options.stripUnknown === true ? true : !!options.stripUnknown.arrays)
228 + : false;
229 +
230 + jl = inclusions.length;
231 + for (let j = 0; j < jl; ++j) {
232 + const inclusion = inclusions[j];
233 +
234 + // Avoid re-running requireds that already didn't match in the previous loop
235 + const previousCheck = requireds.indexOf(inclusion);
236 + if (previousCheck !== -1) {
237 + res = requiredChecks[previousCheck];
238 + }
239 + else {
240 + res = inclusion._validate(item, localState, options);
241 +
242 + if (!res.errors) {
243 + if (inclusion._flags.strip) {
244 + internals.fastSplice(items, i);
245 + --i;
246 + --il;
247 + }
248 + else if (!this._flags.sparse && res.value === undefined) {
249 + errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options));
250 + errored = true;
251 + }
252 + else {
253 + items[i] = res.value;
254 + }
255 + isValid = true;
256 + break;
257 + }
258 + }
259 +
260 + // Return the actual error if only one inclusion defined
261 + if (jl === 1) {
262 + if (stripUnknown) {
263 + internals.fastSplice(items, i);
264 + --i;
265 + --il;
266 + isValid = true;
267 + break;
268 + }
269 +
270 + errors.push(this.createError(wasArray ? 'array.includesOne' : 'array.includesOneSingle', { pos: i, reason: res.errors, value: item }, { key: state.key, path: localState.path }, options));
271 + errored = true;
272 +
273 + if (options.abortEarly) {
274 + return errors;
275 + }
276 +
277 + break;
278 + }
279 + }
280 +
281 + if (errored) {
282 + continue;
283 + }
284 +
285 + if (this._inner.inclusions.length && !isValid) {
286 + if (stripUnknown) {
287 + internals.fastSplice(items, i);
288 + --i;
289 + --il;
290 + continue;
291 + }
292 +
293 + errors.push(this.createError(wasArray ? 'array.includes' : 'array.includesSingle', { pos: i, value: item }, { key: state.key, path: localState.path }, options));
294 +
295 + if (options.abortEarly) {
296 + return errors;
297 + }
298 + }
299 + }
300 +
301 + if (requireds.length) {
302 + this._fillMissedErrors.call(this, errors, requireds, state, options);
303 + }
304 +
305 + if (ordereds.length) {
306 + this._fillOrderedErrors.call(this, errors, ordereds, state, options);
307 + }
308 +
309 + return errors.length ? errors : null;
310 + }
311 +
312 + describe() {
313 +
314 + const description = Any.prototype.describe.call(this);
315 +
316 + if (this._inner.ordereds.length) {
317 + description.orderedItems = [];
318 +
319 + for (let i = 0; i < this._inner.ordereds.length; ++i) {
320 + description.orderedItems.push(this._inner.ordereds[i].describe());
321 + }
322 + }
323 +
324 + if (this._inner.items.length) {
325 + description.items = [];
326 +
327 + for (let i = 0; i < this._inner.items.length; ++i) {
328 + description.items.push(this._inner.items[i].describe());
329 + }
330 + }
331 +
332 + return description;
333 + }
334 +
335 + items(...schemas) {
336 +
337 + const obj = this.clone();
338 +
339 + Hoek.flatten(schemas).forEach((type, index) => {
340 +
341 + try {
342 + type = Cast.schema(this._currentJoi, type);
343 + }
344 + catch (castErr) {
345 + if (castErr.hasOwnProperty('path')) {
346 + castErr.path = index + '.' + castErr.path;
347 + }
348 + else {
349 + castErr.path = index;
350 + }
351 + castErr.message = castErr.message + '(' + castErr.path + ')';
352 + throw castErr;
353 + }
354 +
355 + obj._inner.items.push(type);
356 +
357 + if (type._flags.presence === 'required') {
358 + obj._inner.requireds.push(type);
359 + }
360 + else if (type._flags.presence === 'forbidden') {
361 + obj._inner.exclusions.push(type.optional());
362 + }
363 + else {
364 + obj._inner.inclusions.push(type);
365 + }
366 + });
367 +
368 + return obj;
369 + }
370 +
371 + ordered(...schemas) {
372 +
373 + const obj = this.clone();
374 +
375 + Hoek.flatten(schemas).forEach((type, index) => {
376 +
377 + try {
378 + type = Cast.schema(this._currentJoi, type);
379 + }
380 + catch (castErr) {
381 + if (castErr.hasOwnProperty('path')) {
382 + castErr.path = index + '.' + castErr.path;
383 + }
384 + else {
385 + castErr.path = index;
386 + }
387 + castErr.message = castErr.message + '(' + castErr.path + ')';
388 + throw castErr;
389 + }
390 + obj._inner.ordereds.push(type);
391 + });
392 +
393 + return obj;
394 + }
395 +
396 + min(limit) {
397 +
398 + const isRef = Ref.isRef(limit);
399 +
400 + Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference');
401 +
402 + return this._test('min', limit, function (value, state, options) {
403 +
404 + let compareTo;
405 + if (isRef) {
406 + compareTo = limit(state.reference || state.parent, options);
407 +
408 + if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) {
409 + return this.createError('array.ref', { ref: limit.key }, state, options);
410 + }
411 + }
412 + else {
413 + compareTo = limit;
414 + }
415 +
416 + if (value.length >= compareTo) {
417 + return value;
418 + }
419 +
420 + return this.createError('array.min', { limit, value }, state, options);
421 + });
422 + }
423 +
424 + max(limit) {
425 +
426 + const isRef = Ref.isRef(limit);
427 +
428 + Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference');
429 +
430 + return this._test('max', limit, function (value, state, options) {
431 +
432 + let compareTo;
433 + if (isRef) {
434 + compareTo = limit(state.reference || state.parent, options);
435 +
436 + if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) {
437 + return this.createError('array.ref', { ref: limit.key }, state, options);
438 + }
439 + }
440 + else {
441 + compareTo = limit;
442 + }
443 +
444 + if (value.length <= compareTo) {
445 + return value;
446 + }
447 +
448 + return this.createError('array.max', { limit, value }, state, options);
449 + });
450 + }
451 +
452 + length(limit) {
453 +
454 + const isRef = Ref.isRef(limit);
455 +
456 + Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference');
457 +
458 + return this._test('length', limit, function (value, state, options) {
459 +
460 + let compareTo;
461 + if (isRef) {
462 + compareTo = limit(state.reference || state.parent, options);
463 +
464 + if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) {
465 + return this.createError('array.ref', { ref: limit.key }, state, options);
466 + }
467 + }
468 + else {
469 + compareTo = limit;
470 + }
471 +
472 + if (value.length === compareTo) {
473 + return value;
474 + }
475 +
476 + return this.createError('array.length', { limit, value }, state, options);
477 + });
478 + }
479 +
480 + unique(comparator) {
481 +
482 + Hoek.assert(comparator === undefined ||
483 + typeof comparator === 'function' ||
484 + typeof comparator === 'string', 'comparator must be a function or a string');
485 +
486 + const settings = {};
487 +
488 + if (typeof comparator === 'string') {
489 + settings.path = comparator;
490 + }
491 + else if (typeof comparator === 'function') {
492 + settings.comparator = comparator;
493 + }
494 +
495 + return this._test('unique', settings, function (value, state, options) {
496 +
497 + const found = {
498 + string: {},
499 + number: {},
500 + undefined: {},
501 + boolean: {},
502 + object: new Map(),
503 + function: new Map(),
504 + custom: new Map()
505 + };
506 +
507 + const compare = settings.comparator || Hoek.deepEqual;
508 +
509 + for (let i = 0; i < value.length; ++i) {
510 + const item = settings.path ? Hoek.reach(value[i], settings.path) : value[i];
511 + const records = settings.comparator ? found.custom : found[typeof item];
512 +
513 + // All available types are supported, so it's not possible to reach 100% coverage without ignoring this line.
514 + // I still want to keep the test for future js versions with new types (eg. Symbol).
515 + if (/* $lab:coverage:off$ */ records /* $lab:coverage:on$ */) {
516 + if (records instanceof Map) {
517 + const entries = records.entries();
518 + let current;
519 + while (!(current = entries.next()).done) {
520 + if (compare(current.value[0], item)) {
521 + const localState = {
522 + key: state.key,
523 + path: state.path.concat(i),
524 + parent: state.parent,
525 + reference: state.reference
526 + };
527 +
528 + const context = {
529 + pos: i,
530 + value: value[i],
531 + dupePos: current.value[1],
532 + dupeValue: value[current.value[1]]
533 + };
534 +
535 + if (settings.path) {
536 + context.path = settings.path;
537 + }
538 +
539 + return this.createError('array.unique', context, localState, options);
540 + }
541 + }
542 +
543 + records.set(item, i);
544 + }
545 + else {
546 + if (records[item] !== undefined) {
547 + const localState = {
548 + key: state.key,
549 + path: state.path.concat(i),
550 + parent: state.parent,
551 + reference: state.reference
552 + };
553 +
554 + const context = {
555 + pos: i,
556 + value: value[i],
557 + dupePos: records[item],
558 + dupeValue: value[records[item]]
559 + };
560 +
561 + if (settings.path) {
562 + context.path = settings.path;
563 + }
564 +
565 + return this.createError('array.unique', context, localState, options);
566 + }
567 +
568 + records[item] = i;
569 + }
570 + }
571 + }
572 +
573 + return value;
574 + });
575 + }
576 +
577 + sparse(enabled) {
578 +
579 + const value = enabled === undefined ? true : !!enabled;
580 +
581 + if (this._flags.sparse === value) {
582 + return this;
583 + }
584 +
585 + const obj = this.clone();
586 + obj._flags.sparse = value;
587 + return obj;
588 + }
589 +
590 + single(enabled) {
591 +
592 + const value = enabled === undefined ? true : !!enabled;
593 +
594 + if (this._flags.single === value) {
595 + return this;
596 + }
597 +
598 + const obj = this.clone();
599 + obj._flags.single = value;
600 + return obj;
601 + }
602 +
603 + _fillMissedErrors(errors, requireds, state, options) {
604 +
605 + const knownMisses = [];
606 + let unknownMisses = 0;
607 + for (let i = 0; i < requireds.length; ++i) {
608 + const label = requireds[i]._getLabel();
609 + if (label) {
610 + knownMisses.push(label);
611 + }
612 + else {
613 + ++unknownMisses;
614 + }
615 + }
616 +
617 + if (knownMisses.length) {
618 + if (unknownMisses) {
619 + errors.push(this.createError('array.includesRequiredBoth', { knownMisses, unknownMisses }, { key: state.key, path: state.path }, options));
620 + }
621 + else {
622 + errors.push(this.createError('array.includesRequiredKnowns', { knownMisses }, { key: state.key, path: state.path }, options));
623 + }
624 + }
625 + else {
626 + errors.push(this.createError('array.includesRequiredUnknowns', { unknownMisses }, { key: state.key, path: state.path }, options));
627 + }
628 + }
629 +
630 +
631 + _fillOrderedErrors(errors, ordereds, state, options) {
632 +
633 + const requiredOrdereds = [];
634 +
635 + for (let i = 0; i < ordereds.length; ++i) {
636 + const presence = Hoek.reach(ordereds[i], '_flags.presence');
637 + if (presence === 'required') {
638 + requiredOrdereds.push(ordereds[i]);
639 + }
640 + }
641 +
642 + if (requiredOrdereds.length) {
643 + this._fillMissedErrors.call(this, errors, requiredOrdereds, state, options);
644 + }
645 + }
646 +
647 +};
648 +
649 +
650 +internals.safeParse = function (value, result) {
651 +
652 + try {
653 + const converted = JSON.parse(value);
654 + if (Array.isArray(converted)) {
655 + result.value = converted;
656 + }
657 + }
658 + catch (e) { }
659 +};
660 +
661 +
662 +module.exports = new internals.Array();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Any = require('../any');
6 +const Hoek = require('hoek');
7 +
8 +
9 +// Declare internals
10 +
11 +const internals = {};
12 +
13 +
14 +internals.Binary = class extends Any {
15 +
16 + constructor() {
17 +
18 + super();
19 + this._type = 'binary';
20 + }
21 +
22 + _base(value, state, options) {
23 +
24 + const result = {
25 + value
26 + };
27 +
28 + if (typeof value === 'string' &&
29 + options.convert) {
30 +
31 + try {
32 + result.value = new Buffer(value, this._flags.encoding);
33 + }
34 + catch (e) {
35 + }
36 + }
37 +
38 + result.errors = Buffer.isBuffer(result.value) ? null : this.createError('binary.base', null, state, options);
39 + return result;
40 + }
41 +
42 + encoding(encoding) {
43 +
44 + Hoek.assert(Buffer.isEncoding(encoding), 'Invalid encoding:', encoding);
45 +
46 + if (this._flags.encoding === encoding) {
47 + return this;
48 + }
49 +
50 + const obj = this.clone();
51 + obj._flags.encoding = encoding;
52 + return obj;
53 + }
54 +
55 + min(limit) {
56 +
57 + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
58 +
59 + return this._test('min', limit, function (value, state, options) {
60 +
61 + if (value.length >= limit) {
62 + return value;
63 + }
64 +
65 + return this.createError('binary.min', { limit, value }, state, options);
66 + });
67 + }
68 +
69 + max(limit) {
70 +
71 + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
72 +
73 + return this._test('max', limit, function (value, state, options) {
74 +
75 + if (value.length <= limit) {
76 + return value;
77 + }
78 +
79 + return this.createError('binary.max', { limit, value }, state, options);
80 + });
81 + }
82 +
83 + length(limit) {
84 +
85 + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
86 +
87 + return this._test('length', limit, function (value, state, options) {
88 +
89 + if (value.length === limit) {
90 + return value;
91 + }
92 +
93 + return this.createError('binary.length', { limit, value }, state, options);
94 + });
95 + }
96 +
97 +};
98 +
99 +
100 +module.exports = new internals.Binary();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Any = require('../any');
6 +const Hoek = require('hoek');
7 +
8 +
9 +// Declare internals
10 +
11 +const internals = {
12 + Set: require('../../set')
13 +};
14 +
15 +
16 +internals.Boolean = class extends Any {
17 + constructor() {
18 +
19 + super();
20 + this._type = 'boolean';
21 + this._flags.insensitive = true;
22 + this._inner.truthySet = new internals.Set();
23 + this._inner.falsySet = new internals.Set();
24 + }
25 +
26 + _base(value, state, options) {
27 +
28 + const result = {
29 + value
30 + };
31 +
32 + if (typeof value === 'string' &&
33 + options.convert) {
34 +
35 + const normalized = this._flags.insensitive ? value.toLowerCase() : value;
36 + result.value = (normalized === 'true' ? true
37 + : (normalized === 'false' ? false : value));
38 + }
39 +
40 + if (typeof result.value !== 'boolean') {
41 + result.value = (this._inner.truthySet.has(value, null, null, this._flags.insensitive) ? true
42 + : (this._inner.falsySet.has(value, null, null, this._flags.insensitive) ? false : value));
43 + }
44 +
45 + result.errors = (typeof result.value === 'boolean') ? null : this.createError('boolean.base', null, state, options);
46 + return result;
47 + }
48 +
49 + truthy(...values) {
50 +
51 + const obj = this.clone();
52 + values = Hoek.flatten(values);
53 + for (let i = 0; i < values.length; ++i) {
54 + const value = values[i];
55 +
56 + Hoek.assert(value !== undefined, 'Cannot call truthy with undefined');
57 + obj._inner.truthySet.add(value);
58 + }
59 + return obj;
60 + }
61 +
62 + falsy(...values) {
63 +
64 + const obj = this.clone();
65 + values = Hoek.flatten(values);
66 + for (let i = 0; i < values.length; ++i) {
67 + const value = values[i];
68 +
69 + Hoek.assert(value !== undefined, 'Cannot call falsy with undefined');
70 + obj._inner.falsySet.add(value);
71 + }
72 + return obj;
73 + }
74 +
75 + insensitive(enabled) {
76 +
77 + const insensitive = enabled === undefined ? true : !!enabled;
78 +
79 + if (this._flags.insensitive === insensitive) {
80 + return this;
81 + }
82 +
83 + const obj = this.clone();
84 + obj._flags.insensitive = insensitive;
85 + return obj;
86 + }
87 +
88 + describe() {
89 +
90 + const description = Any.prototype.describe.call(this);
91 + description.truthy = [true].concat(this._inner.truthySet.values());
92 + description.falsy = [false].concat(this._inner.falsySet.values());
93 + return description;
94 + }
95 +};
96 +
97 +
98 +module.exports = new internals.Boolean();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Any = require('../any');
6 +const Ref = require('../../ref');
7 +const Hoek = require('hoek');
8 +
9 +
10 +// Declare internals
11 +
12 +const internals = {};
13 +
14 +internals.isoDate = /^(?:[-+]\d{2})?(?:\d{4}(?!\d{2}\b))(?:(-?)(?:(?:0[1-9]|1[0-2])(?:\1(?:[12]\d|0[1-9]|3[01]))?|W(?:[0-4]\d|5[0-2])(?:-?[1-7])?|(?:00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[1-6])))(?![T]$|[T][\d]+Z$)(?:[T\s](?:(?:(?:[01]\d|2[0-3])(?:(:?)[0-5]\d)?|24\:?00)(?:[.,]\d+(?!:))?)(?:\2[0-5]\d(?:[.,]\d+)?)?(?:[Z]|(?:[+-])(?:[01]\d|2[0-3])(?::?[0-5]\d)?)?)?)?$/;
15 +internals.invalidDate = new Date('');
16 +internals.isIsoDate = (() => {
17 +
18 + const isoString = internals.isoDate.toString();
19 +
20 + return (date) => {
21 +
22 + return date && (date.toString() === isoString);
23 + };
24 +})();
25 +
26 +internals.Date = class extends Any {
27 +
28 + constructor() {
29 +
30 + super();
31 + this._type = 'date';
32 + }
33 +
34 + _base(value, state, options) {
35 +
36 + const result = {
37 + value: (options.convert && internals.Date.toDate(value, this._flags.format, this._flags.timestamp, this._flags.multiplier)) || value
38 + };
39 +
40 + if (result.value instanceof Date && !isNaN(result.value.getTime())) {
41 + result.errors = null;
42 + }
43 + else if (!options.convert) {
44 + result.errors = this.createError('date.strict', null, state, options);
45 + }
46 + else {
47 + let type;
48 + if (internals.isIsoDate(this._flags.format)) {
49 + type = 'isoDate';
50 + }
51 + else if (this._flags.timestamp) {
52 + type = `timestamp.${this._flags.timestamp}`;
53 + }
54 + else {
55 + type = 'base';
56 + }
57 +
58 + result.errors = this.createError(`date.${type}`, null, state, options);
59 + }
60 +
61 + return result;
62 + }
63 +
64 + static toDate(value, format, timestamp, multiplier) {
65 +
66 + if (value instanceof Date) {
67 + return value;
68 + }
69 +
70 + if (typeof value === 'string' ||
71 + (typeof value === 'number' && !isNaN(value) && isFinite(value))) {
72 +
73 + if (typeof value === 'string' &&
74 + /^[+-]?\d+(\.\d+)?$/.test(value)) {
75 +
76 + value = parseFloat(value);
77 + }
78 +
79 + let date;
80 + if (format && internals.isIsoDate(format)) {
81 + date = format.test(value) ? new Date(value) : internals.invalidDate;
82 + }
83 + else if (timestamp && multiplier) {
84 + date = /^\s*$/.test(value) ? internals.invalidDate : new Date(value * multiplier);
85 + }
86 + else {
87 + date = new Date(value);
88 + }
89 +
90 + if (!isNaN(date.getTime())) {
91 + return date;
92 + }
93 + }
94 +
95 + return null;
96 + }
97 +
98 + iso() {
99 +
100 + if (this._flags.format === internals.isoDate) {
101 + return this;
102 + }
103 +
104 + const obj = this.clone();
105 + obj._flags.format = internals.isoDate;
106 + return obj;
107 + }
108 +
109 + timestamp(type = 'javascript') {
110 +
111 + const allowed = ['javascript', 'unix'];
112 + Hoek.assert(allowed.includes(type), '"type" must be one of "' + allowed.join('", "') + '"');
113 +
114 + if (this._flags.timestamp === type) {
115 + return this;
116 + }
117 +
118 + const obj = this.clone();
119 + obj._flags.timestamp = type;
120 + obj._flags.multiplier = type === 'unix' ? 1000 : 1;
121 + return obj;
122 + }
123 +
124 + _isIsoDate(value) {
125 +
126 + return internals.isoDate.test(value);
127 + }
128 +
129 +};
130 +
131 +internals.compare = function (type, compare) {
132 +
133 + return function (date) {
134 +
135 + const isNow = date === 'now';
136 + const isRef = Ref.isRef(date);
137 +
138 + if (!isNow && !isRef) {
139 + date = internals.Date.toDate(date);
140 + }
141 +
142 + Hoek.assert(date, 'Invalid date format');
143 +
144 + return this._test(type, date, function (value, state, options) {
145 +
146 + let compareTo;
147 + if (isNow) {
148 + compareTo = Date.now();
149 + }
150 + else if (isRef) {
151 + compareTo = internals.Date.toDate(date(state.reference || state.parent, options));
152 +
153 + if (!compareTo) {
154 + return this.createError('date.ref', { ref: date.key }, state, options);
155 + }
156 +
157 + compareTo = compareTo.getTime();
158 + }
159 + else {
160 + compareTo = date.getTime();
161 + }
162 +
163 + if (compare(value.getTime(), compareTo)) {
164 + return value;
165 + }
166 +
167 + return this.createError('date.' + type, { limit: new Date(compareTo) }, state, options);
168 + });
169 + };
170 +};
171 +internals.Date.prototype.min = internals.compare('min', (value, date) => value >= date);
172 +internals.Date.prototype.max = internals.compare('max', (value, date) => value <= date);
173 +
174 +
175 +module.exports = new internals.Date();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +const ObjectType = require('../object');
7 +const Ref = require('../../ref');
8 +
9 +
10 +// Declare internals
11 +
12 +const internals = {};
13 +
14 +
15 +internals.Func = class extends ObjectType.constructor {
16 +
17 + constructor() {
18 +
19 + super();
20 + this._flags.func = true;
21 + }
22 +
23 + arity(n) {
24 +
25 + Hoek.assert(Number.isSafeInteger(n) && n >= 0, 'n must be a positive integer');
26 +
27 + return this._test('arity', n, function (value, state, options) {
28 +
29 + if (value.length === n) {
30 + return value;
31 + }
32 +
33 + return this.createError('function.arity', { n }, state, options);
34 + });
35 + }
36 +
37 + minArity(n) {
38 +
39 + Hoek.assert(Number.isSafeInteger(n) && n > 0, 'n must be a strict positive integer');
40 +
41 + return this._test('minArity', n, function (value, state, options) {
42 +
43 + if (value.length >= n) {
44 + return value;
45 + }
46 +
47 + return this.createError('function.minArity', { n }, state, options);
48 + });
49 + }
50 +
51 + maxArity(n) {
52 +
53 + Hoek.assert(Number.isSafeInteger(n) && n >= 0, 'n must be a positive integer');
54 +
55 + return this._test('maxArity', n, function (value, state, options) {
56 +
57 + if (value.length <= n) {
58 + return value;
59 + }
60 +
61 + return this.createError('function.maxArity', { n }, state, options);
62 + });
63 + }
64 +
65 + ref() {
66 +
67 + return this._test('ref', null, function (value, state, options) {
68 +
69 + if (Ref.isRef(value)) {
70 + return value;
71 + }
72 +
73 + return this.createError('function.ref', null, state, options);
74 + });
75 + }
76 +
77 + class() {
78 +
79 + return this._test('class', null, function (value, state, options) {
80 +
81 + if ((/^\s*class\s/).test(value.toString())) {
82 + return value;
83 + }
84 +
85 + return this.createError('function.class', null, state, options);
86 + });
87 + }
88 +};
89 +
90 +module.exports = new internals.Func();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Any = require('../any');
6 +const Hoek = require('hoek');
7 +
8 +
9 +// Declare internals
10 +
11 +const internals = {};
12 +
13 +
14 +internals.Lazy = class extends Any {
15 +
16 + constructor() {
17 +
18 + super();
19 + this._type = 'lazy';
20 + }
21 +
22 + _base(value, state, options) {
23 +
24 + const result = { value };
25 + const lazy = this._flags.lazy;
26 +
27 + if (!lazy) {
28 + result.errors = this.createError('lazy.base', null, state, options);
29 + return result;
30 + }
31 +
32 + const schema = lazy();
33 +
34 + if (!(schema instanceof Any)) {
35 + result.errors = this.createError('lazy.schema', null, state, options);
36 + return result;
37 + }
38 +
39 + return schema._validate(value, state, options);
40 + }
41 +
42 + set(fn) {
43 +
44 + Hoek.assert(typeof fn === 'function', 'You must provide a function as first argument');
45 +
46 + const obj = this.clone();
47 + obj._flags.lazy = fn;
48 + return obj;
49 + }
50 +
51 +};
52 +
53 +module.exports = new internals.Lazy();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Any = require('../any');
6 +const Ref = require('../../ref');
7 +const Hoek = require('hoek');
8 +
9 +
10 +// Declare internals
11 +
12 +const internals = {
13 + precisionRx: /(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/
14 +};
15 +
16 +
17 +internals.Number = class extends Any {
18 +
19 + constructor() {
20 +
21 + super();
22 + this._type = 'number';
23 + this._invalids.add(Infinity);
24 + this._invalids.add(-Infinity);
25 + }
26 +
27 + _base(value, state, options) {
28 +
29 + const result = {
30 + errors: null,
31 + value
32 + };
33 +
34 + if (typeof value === 'string' &&
35 + options.convert) {
36 +
37 + const number = parseFloat(value);
38 + result.value = (isNaN(number) || !isFinite(value)) ? NaN : number;
39 + }
40 +
41 + const isNumber = typeof result.value === 'number' && !isNaN(result.value);
42 +
43 + if (options.convert && 'precision' in this._flags && isNumber) {
44 +
45 + // This is conceptually equivalent to using toFixed but it should be much faster
46 + const precision = Math.pow(10, this._flags.precision);
47 + result.value = Math.round(result.value * precision) / precision;
48 + }
49 +
50 + result.errors = isNumber ? null : this.createError('number.base', null, state, options);
51 + return result;
52 + }
53 +
54 + multiple(base) {
55 +
56 + const isRef = Ref.isRef(base);
57 +
58 + if (!isRef) {
59 + Hoek.assert(typeof base === 'number' && isFinite(base), 'multiple must be a number');
60 + Hoek.assert(base > 0, 'multiple must be greater than 0');
61 + }
62 +
63 + return this._test('multiple', base, function (value, state, options) {
64 +
65 + const divisor = isRef ? base(state.reference || state.parent, options) : base;
66 +
67 + if (isRef && (typeof divisor !== 'number' || !isFinite(divisor))) {
68 + return this.createError('number.ref', { ref: base.key }, state, options);
69 + }
70 +
71 + if (value % divisor === 0) {
72 + return value;
73 + }
74 +
75 + return this.createError('number.multiple', { multiple: base, value }, state, options);
76 + });
77 + }
78 +
79 + integer() {
80 +
81 + return this._test('integer', undefined, function (value, state, options) {
82 +
83 + return Number.isSafeInteger(value) ? value : this.createError('number.integer', { value }, state, options);
84 + });
85 + }
86 +
87 + negative() {
88 +
89 + return this._test('negative', undefined, function (value, state, options) {
90 +
91 + if (value < 0) {
92 + return value;
93 + }
94 +
95 + return this.createError('number.negative', { value }, state, options);
96 + });
97 + }
98 +
99 + positive() {
100 +
101 + return this._test('positive', undefined, function (value, state, options) {
102 +
103 + if (value > 0) {
104 + return value;
105 + }
106 +
107 + return this.createError('number.positive', { value }, state, options);
108 + });
109 + }
110 +
111 + precision(limit) {
112 +
113 + Hoek.assert(Number.isSafeInteger(limit), 'limit must be an integer');
114 + Hoek.assert(!('precision' in this._flags), 'precision already set');
115 +
116 + const obj = this._test('precision', limit, function (value, state, options) {
117 +
118 + const places = value.toString().match(internals.precisionRx);
119 + const decimals = Math.max((places[1] ? places[1].length : 0) - (places[2] ? parseInt(places[2], 10) : 0), 0);
120 + if (decimals <= limit) {
121 + return value;
122 + }
123 +
124 + return this.createError('number.precision', { limit, value }, state, options);
125 + });
126 +
127 + obj._flags.precision = limit;
128 + return obj;
129 + }
130 +
131 +};
132 +
133 +
134 +internals.compare = function (type, compare) {
135 +
136 + return function (limit) {
137 +
138 + const isRef = Ref.isRef(limit);
139 + const isNumber = typeof limit === 'number' && !isNaN(limit);
140 +
141 + Hoek.assert(isNumber || isRef, 'limit must be a number or reference');
142 +
143 + return this._test(type, limit, function (value, state, options) {
144 +
145 + let compareTo;
146 + if (isRef) {
147 + compareTo = limit(state.reference || state.parent, options);
148 +
149 + if (!(typeof compareTo === 'number' && !isNaN(compareTo))) {
150 + return this.createError('number.ref', { ref: limit.key }, state, options);
151 + }
152 + }
153 + else {
154 + compareTo = limit;
155 + }
156 +
157 + if (compare(value, compareTo)) {
158 + return value;
159 + }
160 +
161 + return this.createError('number.' + type, { limit: compareTo, value }, state, options);
162 + });
163 + };
164 +};
165 +
166 +
167 +internals.Number.prototype.min = internals.compare('min', (value, limit) => value >= limit);
168 +internals.Number.prototype.max = internals.compare('max', (value, limit) => value <= limit);
169 +internals.Number.prototype.greater = internals.compare('greater', (value, limit) => value > limit);
170 +internals.Number.prototype.less = internals.compare('less', (value, limit) => value < limit);
171 +
172 +
173 +module.exports = new internals.Number();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +const Topo = require('topo');
7 +const Any = require('../any');
8 +const Errors = require('../../errors');
9 +const Cast = require('../../cast');
10 +
11 +
12 +// Declare internals
13 +
14 +const internals = {};
15 +
16 +
17 +internals.Object = class extends Any {
18 +
19 + constructor() {
20 +
21 + super();
22 + this._type = 'object';
23 + this._inner.children = null;
24 + this._inner.renames = [];
25 + this._inner.dependencies = [];
26 + this._inner.patterns = [];
27 + }
28 +
29 + _base(value, state, options) {
30 +
31 + let target = value;
32 + const errors = [];
33 + const finish = () => {
34 +
35 + return {
36 + value: target,
37 + errors: errors.length ? errors : null
38 + };
39 + };
40 +
41 + if (typeof value === 'string' &&
42 + options.convert) {
43 +
44 + value = internals.safeParse(value);
45 + }
46 +
47 + const type = this._flags.func ? 'function' : 'object';
48 + if (!value ||
49 + typeof value !== type ||
50 + Array.isArray(value)) {
51 +
52 + errors.push(this.createError(type + '.base', null, state, options));
53 + return finish();
54 + }
55 +
56 + // Skip if there are no other rules to test
57 +
58 + if (!this._inner.renames.length &&
59 + !this._inner.dependencies.length &&
60 + !this._inner.children && // null allows any keys
61 + !this._inner.patterns.length) {
62 +
63 + target = value;
64 + return finish();
65 + }
66 +
67 + // Ensure target is a local copy (parsed) or shallow copy
68 +
69 + if (target === value) {
70 + if (type === 'object') {
71 + target = Object.create(Object.getPrototypeOf(value));
72 + }
73 + else {
74 + target = function (...args) {
75 +
76 + return value.apply(this, args);
77 + };
78 +
79 + target.prototype = Hoek.clone(value.prototype);
80 + }
81 +
82 + const valueKeys = Object.keys(value);
83 + for (let i = 0; i < valueKeys.length; ++i) {
84 + target[valueKeys[i]] = value[valueKeys[i]];
85 + }
86 + }
87 + else {
88 + target = value;
89 + }
90 +
91 + // Rename keys
92 +
93 + const renamed = {};
94 + for (let i = 0; i < this._inner.renames.length; ++i) {
95 + const rename = this._inner.renames[i];
96 +
97 + if (rename.isRegExp) {
98 + const targetKeys = Object.keys(target);
99 + const matchedTargetKeys = [];
100 +
101 + for (let j = 0; j < targetKeys.length; ++j) {
102 + if (rename.from.test(targetKeys[j])) {
103 + matchedTargetKeys.push(targetKeys[j]);
104 + }
105 + }
106 +
107 + const allUndefined = matchedTargetKeys.every((key) => target[key] === undefined);
108 + if (rename.options.ignoreUndefined && allUndefined) {
109 + continue;
110 + }
111 +
112 + if (!rename.options.multiple &&
113 + renamed[rename.to]) {
114 +
115 + errors.push(this.createError('object.rename.regex.multiple', { from: matchedTargetKeys, to: rename.to }, state, options));
116 + if (options.abortEarly) {
117 + return finish();
118 + }
119 + }
120 +
121 + if (Object.prototype.hasOwnProperty.call(target, rename.to) &&
122 + !rename.options.override &&
123 + !renamed[rename.to]) {
124 +
125 + errors.push(this.createError('object.rename.regex.override', { from: matchedTargetKeys, to: rename.to }, state, options));
126 + if (options.abortEarly) {
127 + return finish();
128 + }
129 + }
130 +
131 + if (allUndefined) {
132 + delete target[rename.to];
133 + }
134 + else {
135 + target[rename.to] = target[matchedTargetKeys[matchedTargetKeys.length - 1]];
136 + }
137 +
138 + renamed[rename.to] = true;
139 +
140 + if (!rename.options.alias) {
141 + for (let j = 0; j < matchedTargetKeys.length; ++j) {
142 + delete target[matchedTargetKeys[j]];
143 + }
144 + }
145 + }
146 + else {
147 + if (rename.options.ignoreUndefined && target[rename.from] === undefined) {
148 + continue;
149 + }
150 +
151 + if (!rename.options.multiple &&
152 + renamed[rename.to]) {
153 +
154 + errors.push(this.createError('object.rename.multiple', { from: rename.from, to: rename.to }, state, options));
155 + if (options.abortEarly) {
156 + return finish();
157 + }
158 + }
159 +
160 + if (Object.prototype.hasOwnProperty.call(target, rename.to) &&
161 + !rename.options.override &&
162 + !renamed[rename.to]) {
163 +
164 + errors.push(this.createError('object.rename.override', { from: rename.from, to: rename.to }, state, options));
165 + if (options.abortEarly) {
166 + return finish();
167 + }
168 + }
169 +
170 + if (target[rename.from] === undefined) {
171 + delete target[rename.to];
172 + }
173 + else {
174 + target[rename.to] = target[rename.from];
175 + }
176 +
177 + renamed[rename.to] = true;
178 +
179 + if (!rename.options.alias) {
180 + delete target[rename.from];
181 + }
182 + }
183 + }
184 +
185 + // Validate schema
186 +
187 + if (!this._inner.children && // null allows any keys
188 + !this._inner.patterns.length &&
189 + !this._inner.dependencies.length) {
190 +
191 + return finish();
192 + }
193 +
194 + const unprocessed = Hoek.mapToObject(Object.keys(target));
195 +
196 + if (this._inner.children) {
197 + const stripProps = [];
198 +
199 + for (let i = 0; i < this._inner.children.length; ++i) {
200 + const child = this._inner.children[i];
201 + const key = child.key;
202 + const item = target[key];
203 +
204 + delete unprocessed[key];
205 +
206 + const localState = { key, path: state.path.concat(key), parent: target, reference: state.reference };
207 + const result = child.schema._validate(item, localState, options);
208 + if (result.errors) {
209 + errors.push(this.createError('object.child', { key, child: child.schema._getLabel(key), reason: result.errors }, localState, options));
210 +
211 + if (options.abortEarly) {
212 + return finish();
213 + }
214 + }
215 + else {
216 + if (child.schema._flags.strip || (result.value === undefined && result.value !== item)) {
217 + stripProps.push(key);
218 + target[key] = result.finalValue;
219 + }
220 + else if (result.value !== undefined) {
221 + target[key] = result.value;
222 + }
223 + }
224 + }
225 +
226 + for (let i = 0; i < stripProps.length; ++i) {
227 + delete target[stripProps[i]];
228 + }
229 + }
230 +
231 + // Unknown keys
232 +
233 + let unprocessedKeys = Object.keys(unprocessed);
234 + if (unprocessedKeys.length &&
235 + this._inner.patterns.length) {
236 +
237 + for (let i = 0; i < unprocessedKeys.length; ++i) {
238 + const key = unprocessedKeys[i];
239 + const localState = { key, path: state.path.concat(key), parent: target, reference: state.reference };
240 + const item = target[key];
241 +
242 + for (let j = 0; j < this._inner.patterns.length; ++j) {
243 + const pattern = this._inner.patterns[j];
244 +
245 + if (pattern.regex.test(key)) {
246 + delete unprocessed[key];
247 +
248 + const result = pattern.rule._validate(item, localState, options);
249 + if (result.errors) {
250 + errors.push(this.createError('object.child', { key, child: pattern.rule._getLabel(key), reason: result.errors }, localState, options));
251 +
252 + if (options.abortEarly) {
253 + return finish();
254 + }
255 + }
256 +
257 + target[key] = result.value;
258 + }
259 + }
260 + }
261 +
262 + unprocessedKeys = Object.keys(unprocessed);
263 + }
264 +
265 + if ((this._inner.children || this._inner.patterns.length) && unprocessedKeys.length) {
266 + if ((options.stripUnknown && this._flags.allowUnknown !== true) ||
267 + options.skipFunctions) {
268 +
269 + const stripUnknown = options.stripUnknown
270 + ? (options.stripUnknown === true ? true : !!options.stripUnknown.objects)
271 + : false;
272 +
273 +
274 + for (let i = 0; i < unprocessedKeys.length; ++i) {
275 + const key = unprocessedKeys[i];
276 +
277 + if (stripUnknown) {
278 + delete target[key];
279 + delete unprocessed[key];
280 + }
281 + else if (typeof target[key] === 'function') {
282 + delete unprocessed[key];
283 + }
284 + }
285 +
286 + unprocessedKeys = Object.keys(unprocessed);
287 + }
288 +
289 + if (unprocessedKeys.length &&
290 + (this._flags.allowUnknown !== undefined ? !this._flags.allowUnknown : !options.allowUnknown)) {
291 +
292 + for (let i = 0; i < unprocessedKeys.length; ++i) {
293 + const unprocessedKey = unprocessedKeys[i];
294 + errors.push(this.createError('object.allowUnknown', { child: unprocessedKey }, { key: unprocessedKey, path: state.path.concat(unprocessedKey) }, options, {}));
295 + }
296 + }
297 + }
298 +
299 + // Validate dependencies
300 +
301 + for (let i = 0; i < this._inner.dependencies.length; ++i) {
302 + const dep = this._inner.dependencies[i];
303 + const err = internals[dep.type].call(this, dep.key !== null && target[dep.key], dep.peers, target, { key: dep.key, path: dep.key === null ? state.path : state.path.concat(dep.key) }, options);
304 + if (err instanceof Errors.Err) {
305 + errors.push(err);
306 + if (options.abortEarly) {
307 + return finish();
308 + }
309 + }
310 + }
311 +
312 + return finish();
313 + }
314 +
315 + keys(schema) {
316 +
317 + Hoek.assert(schema === null || schema === undefined || typeof schema === 'object', 'Object schema must be a valid object');
318 + Hoek.assert(!schema || !(schema instanceof Any), 'Object schema cannot be a joi schema');
319 +
320 + const obj = this.clone();
321 +
322 + if (!schema) {
323 + obj._inner.children = null;
324 + return obj;
325 + }
326 +
327 + const children = Object.keys(schema);
328 +
329 + if (!children.length) {
330 + obj._inner.children = [];
331 + return obj;
332 + }
333 +
334 + const topo = new Topo();
335 + if (obj._inner.children) {
336 + for (let i = 0; i < obj._inner.children.length; ++i) {
337 + const child = obj._inner.children[i];
338 +
339 + // Only add the key if we are not going to replace it later
340 + if (!children.includes(child.key)) {
341 + topo.add(child, { after: child._refs, group: child.key });
342 + }
343 + }
344 + }
345 +
346 + for (let i = 0; i < children.length; ++i) {
347 + const key = children[i];
348 + const child = schema[key];
349 + try {
350 + const cast = Cast.schema(this._currentJoi, child);
351 + topo.add({ key, schema: cast }, { after: cast._refs, group: key });
352 + }
353 + catch (castErr) {
354 + if (castErr.hasOwnProperty('path')) {
355 + castErr.path = key + '.' + castErr.path;
356 + }
357 + else {
358 + castErr.path = key;
359 + }
360 + throw castErr;
361 + }
362 + }
363 +
364 + obj._inner.children = topo.nodes;
365 +
366 + return obj;
367 + }
368 +
369 + unknown(allow) {
370 +
371 + const value = allow !== false;
372 +
373 + if (this._flags.allowUnknown === value) {
374 + return this;
375 + }
376 +
377 + const obj = this.clone();
378 + obj._flags.allowUnknown = value;
379 + return obj;
380 + }
381 +
382 + length(limit) {
383 +
384 + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
385 +
386 + return this._test('length', limit, function (value, state, options) {
387 +
388 + if (Object.keys(value).length === limit) {
389 + return value;
390 + }
391 +
392 + return this.createError('object.length', { limit }, state, options);
393 + });
394 + }
395 +
396 + min(limit) {
397 +
398 + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
399 +
400 + return this._test('min', limit, function (value, state, options) {
401 +
402 + if (Object.keys(value).length >= limit) {
403 + return value;
404 + }
405 +
406 + return this.createError('object.min', { limit }, state, options);
407 + });
408 + }
409 +
410 + max(limit) {
411 +
412 + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
413 +
414 + return this._test('max', limit, function (value, state, options) {
415 +
416 + if (Object.keys(value).length <= limit) {
417 + return value;
418 + }
419 +
420 + return this.createError('object.max', { limit }, state, options);
421 + });
422 + }
423 +
424 + pattern(pattern, schema) {
425 +
426 + Hoek.assert(pattern instanceof RegExp, 'Invalid regular expression');
427 + Hoek.assert(schema !== undefined, 'Invalid rule');
428 +
429 + pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags
430 +
431 + try {
432 + schema = Cast.schema(this._currentJoi, schema);
433 + }
434 + catch (castErr) {
435 + if (castErr.hasOwnProperty('path')) {
436 + castErr.message = castErr.message + '(' + castErr.path + ')';
437 + }
438 +
439 + throw castErr;
440 + }
441 +
442 +
443 + const obj = this.clone();
444 + obj._inner.patterns.push({ regex: pattern, rule: schema });
445 + return obj;
446 + }
447 +
448 + schema() {
449 +
450 + return this._test('schema', null, function (value, state, options) {
451 +
452 + if (value instanceof Any) {
453 + return value;
454 + }
455 +
456 + return this.createError('object.schema', null, state, options);
457 + });
458 + }
459 +
460 + with(key, peers) {
461 +
462 + Hoek.assert(arguments.length === 2, 'Invalid number of arguments, expected 2.');
463 +
464 + return this._dependency('with', key, peers);
465 + }
466 +
467 + without(key, peers) {
468 +
469 + Hoek.assert(arguments.length === 2, 'Invalid number of arguments, expected 2.');
470 +
471 + return this._dependency('without', key, peers);
472 + }
473 +
474 + xor(...peers) {
475 +
476 + peers = Hoek.flatten(peers);
477 + return this._dependency('xor', null, peers);
478 + }
479 +
480 + or(...peers) {
481 +
482 + peers = Hoek.flatten(peers);
483 + return this._dependency('or', null, peers);
484 + }
485 +
486 + and(...peers) {
487 +
488 + peers = Hoek.flatten(peers);
489 + return this._dependency('and', null, peers);
490 + }
491 +
492 + nand(...peers) {
493 +
494 + peers = Hoek.flatten(peers);
495 + return this._dependency('nand', null, peers);
496 + }
497 +
498 + requiredKeys(...children) {
499 +
500 + children = Hoek.flatten(children);
501 + return this.applyFunctionToChildren(children, 'required');
502 + }
503 +
504 + optionalKeys(...children) {
505 +
506 + children = Hoek.flatten(children);
507 + return this.applyFunctionToChildren(children, 'optional');
508 + }
509 +
510 + forbiddenKeys(...children) {
511 +
512 + children = Hoek.flatten(children);
513 + return this.applyFunctionToChildren(children, 'forbidden');
514 + }
515 +
516 + rename(from, to, options) {
517 +
518 + Hoek.assert(typeof from === 'string' || from instanceof RegExp, 'Rename missing the from argument');
519 + Hoek.assert(typeof to === 'string', 'Rename missing the to argument');
520 + Hoek.assert(to !== from, 'Cannot rename key to same name:', from);
521 +
522 + for (let i = 0; i < this._inner.renames.length; ++i) {
523 + Hoek.assert(this._inner.renames[i].from !== from, 'Cannot rename the same key multiple times');
524 + }
525 +
526 + const obj = this.clone();
527 +
528 + obj._inner.renames.push({
529 + from,
530 + to,
531 + options: Hoek.applyToDefaults(internals.renameDefaults, options || {}),
532 + isRegExp: from instanceof RegExp
533 + });
534 +
535 + return obj;
536 + }
537 +
538 + applyFunctionToChildren(children, fn, args, root) {
539 +
540 + children = [].concat(children);
541 + Hoek.assert(children.length > 0, 'expected at least one children');
542 +
543 + const groupedChildren = internals.groupChildren(children);
544 + let obj;
545 +
546 + if ('' in groupedChildren) {
547 + obj = this[fn].apply(this, args);
548 + delete groupedChildren[''];
549 + }
550 + else {
551 + obj = this.clone();
552 + }
553 +
554 + if (obj._inner.children) {
555 + root = root ? (root + '.') : '';
556 +
557 + for (let i = 0; i < obj._inner.children.length; ++i) {
558 + const child = obj._inner.children[i];
559 + const group = groupedChildren[child.key];
560 +
561 + if (group) {
562 + obj._inner.children[i] = {
563 + key: child.key,
564 + _refs: child._refs,
565 + schema: child.schema.applyFunctionToChildren(group, fn, args, root + child.key)
566 + };
567 +
568 + delete groupedChildren[child.key];
569 + }
570 + }
571 + }
572 +
573 + const remaining = Object.keys(groupedChildren);
574 + Hoek.assert(remaining.length === 0, 'unknown key(s)', remaining.join(', '));
575 +
576 + return obj;
577 + }
578 +
579 + _dependency(type, key, peers) {
580 +
581 + peers = [].concat(peers);
582 + for (let i = 0; i < peers.length; ++i) {
583 + Hoek.assert(typeof peers[i] === 'string', type, 'peers must be a string or array of strings');
584 + }
585 +
586 + const obj = this.clone();
587 + obj._inner.dependencies.push({ type, key, peers });
588 + return obj;
589 + }
590 +
591 + describe(shallow) {
592 +
593 + const description = Any.prototype.describe.call(this);
594 +
595 + if (description.rules) {
596 + for (let i = 0; i < description.rules.length; ++i) {
597 + const rule = description.rules[i];
598 + // Coverage off for future-proof descriptions, only object().assert() is use right now
599 + if (/* $lab:coverage:off$ */rule.arg &&
600 + typeof rule.arg === 'object' &&
601 + rule.arg.schema &&
602 + rule.arg.ref /* $lab:coverage:on$ */) {
603 + rule.arg = {
604 + schema: rule.arg.schema.describe(),
605 + ref: rule.arg.ref.toString()
606 + };
607 + }
608 + }
609 + }
610 +
611 + if (this._inner.children &&
612 + !shallow) {
613 +
614 + description.children = {};
615 + for (let i = 0; i < this._inner.children.length; ++i) {
616 + const child = this._inner.children[i];
617 + description.children[child.key] = child.schema.describe();
618 + }
619 + }
620 +
621 + if (this._inner.dependencies.length) {
622 + description.dependencies = Hoek.clone(this._inner.dependencies);
623 + }
624 +
625 + if (this._inner.patterns.length) {
626 + description.patterns = [];
627 +
628 + for (let i = 0; i < this._inner.patterns.length; ++i) {
629 + const pattern = this._inner.patterns[i];
630 + description.patterns.push({ regex: pattern.regex.toString(), rule: pattern.rule.describe() });
631 + }
632 + }
633 +
634 + if (this._inner.renames.length > 0) {
635 + description.renames = Hoek.clone(this._inner.renames);
636 + }
637 +
638 + return description;
639 + }
640 +
641 + assert(ref, schema, message) {
642 +
643 + ref = Cast.ref(ref);
644 + Hoek.assert(ref.isContext || ref.depth > 1, 'Cannot use assertions for root level references - use direct key rules instead');
645 + message = message || 'pass the assertion test';
646 +
647 + try {
648 + schema = Cast.schema(this._currentJoi, schema);
649 + }
650 + catch (castErr) {
651 + if (castErr.hasOwnProperty('path')) {
652 + castErr.message = castErr.message + '(' + castErr.path + ')';
653 + }
654 +
655 + throw castErr;
656 + }
657 +
658 + const key = ref.path[ref.path.length - 1];
659 + const path = ref.path.join('.');
660 +
661 + return this._test('assert', { schema, ref }, function (value, state, options) {
662 +
663 + const result = schema._validate(ref(value), null, options, value);
664 + if (!result.errors) {
665 + return value;
666 + }
667 +
668 + const localState = Hoek.merge({}, state);
669 + localState.key = key;
670 + localState.path = ref.path;
671 + return this.createError('object.assert', { ref: path, message }, localState, options);
672 + });
673 + }
674 +
675 + type(constructor, name = constructor.name) {
676 +
677 + Hoek.assert(typeof constructor === 'function', 'type must be a constructor function');
678 + const typeData = {
679 + name,
680 + ctor: constructor
681 + };
682 +
683 + return this._test('type', typeData, function (value, state, options) {
684 +
685 + if (value instanceof constructor) {
686 + return value;
687 + }
688 +
689 + return this.createError('object.type', { type: typeData.name }, state, options);
690 + });
691 + }
692 +};
693 +
694 +internals.safeParse = function (value) {
695 +
696 + try {
697 + return JSON.parse(value);
698 + }
699 + catch (parseErr) {}
700 +
701 + return value;
702 +};
703 +
704 +
705 +internals.renameDefaults = {
706 + alias: false, // Keep old value in place
707 + multiple: false, // Allow renaming multiple keys into the same target
708 + override: false // Overrides an existing key
709 +};
710 +
711 +
712 +internals.groupChildren = function (children) {
713 +
714 + children.sort();
715 +
716 + const grouped = {};
717 +
718 + for (let i = 0; i < children.length; ++i) {
719 + const child = children[i];
720 + Hoek.assert(typeof child === 'string', 'children must be strings');
721 + const group = child.split('.')[0];
722 + const childGroup = grouped[group] = (grouped[group] || []);
723 + childGroup.push(child.substring(group.length + 1));
724 + }
725 +
726 + return grouped;
727 +};
728 +
729 +
730 +internals.keysToLabels = function (schema, keys) {
731 +
732 + const children = schema._inner.children;
733 +
734 + if (!children) {
735 + return keys;
736 + }
737 +
738 + const findLabel = function (key) {
739 +
740 + const matchingChild = children.find((child) => child.key === key);
741 + return matchingChild ? matchingChild.schema._getLabel(key) : key;
742 + };
743 +
744 + if (Array.isArray(keys)) {
745 + return keys.map(findLabel);
746 + }
747 +
748 + return findLabel(keys);
749 +};
750 +
751 +
752 +internals.with = function (value, peers, parent, state, options) {
753 +
754 + if (value === undefined) {
755 + return value;
756 + }
757 +
758 + for (let i = 0; i < peers.length; ++i) {
759 + const peer = peers[i];
760 + if (!Object.prototype.hasOwnProperty.call(parent, peer) ||
761 + parent[peer] === undefined) {
762 +
763 + return this.createError('object.with', {
764 + main: state.key,
765 + mainWithLabel: internals.keysToLabels(this, state.key),
766 + peer,
767 + peerWithLabel: internals.keysToLabels(this, peer)
768 + }, state, options);
769 + }
770 + }
771 +
772 + return value;
773 +};
774 +
775 +
776 +internals.without = function (value, peers, parent, state, options) {
777 +
778 + if (value === undefined) {
779 + return value;
780 + }
781 +
782 + for (let i = 0; i < peers.length; ++i) {
783 + const peer = peers[i];
784 + if (Object.prototype.hasOwnProperty.call(parent, peer) &&
785 + parent[peer] !== undefined) {
786 +
787 + return this.createError('object.without', {
788 + main: state.key,
789 + mainWithLabel: internals.keysToLabels(this, state.key),
790 + peer,
791 + peerWithLabel: internals.keysToLabels(this, peer)
792 + }, state, options);
793 + }
794 + }
795 +
796 + return value;
797 +};
798 +
799 +
800 +internals.xor = function (value, peers, parent, state, options) {
801 +
802 + const present = [];
803 + for (let i = 0; i < peers.length; ++i) {
804 + const peer = peers[i];
805 + if (Object.prototype.hasOwnProperty.call(parent, peer) &&
806 + parent[peer] !== undefined) {
807 +
808 + present.push(peer);
809 + }
810 + }
811 +
812 + if (present.length === 1) {
813 + return value;
814 + }
815 +
816 + const context = { peers, peersWithLabels: internals.keysToLabels(this, peers) };
817 +
818 + if (present.length === 0) {
819 + return this.createError('object.missing', context, state, options);
820 + }
821 +
822 + return this.createError('object.xor', context, state, options);
823 +};
824 +
825 +
826 +internals.or = function (value, peers, parent, state, options) {
827 +
828 + for (let i = 0; i < peers.length; ++i) {
829 + const peer = peers[i];
830 + if (Object.prototype.hasOwnProperty.call(parent, peer) &&
831 + parent[peer] !== undefined) {
832 + return value;
833 + }
834 + }
835 +
836 + return this.createError('object.missing', {
837 + peers,
838 + peersWithLabels: internals.keysToLabels(this, peers)
839 + }, state, options);
840 +};
841 +
842 +
843 +internals.and = function (value, peers, parent, state, options) {
844 +
845 + const missing = [];
846 + const present = [];
847 + const count = peers.length;
848 + for (let i = 0; i < count; ++i) {
849 + const peer = peers[i];
850 + if (!Object.prototype.hasOwnProperty.call(parent, peer) ||
851 + parent[peer] === undefined) {
852 +
853 + missing.push(peer);
854 + }
855 + else {
856 + present.push(peer);
857 + }
858 + }
859 +
860 + const aon = (missing.length === count || present.length === count);
861 +
862 + if (!aon) {
863 +
864 + return this.createError('object.and', {
865 + present,
866 + presentWithLabels: internals.keysToLabels(this, present),
867 + missing,
868 + missingWithLabels: internals.keysToLabels(this, missing)
869 + }, state, options);
870 + }
871 +};
872 +
873 +
874 +internals.nand = function (value, peers, parent, state, options) {
875 +
876 + const present = [];
877 + for (let i = 0; i < peers.length; ++i) {
878 + const peer = peers[i];
879 + if (Object.prototype.hasOwnProperty.call(parent, peer) &&
880 + parent[peer] !== undefined) {
881 +
882 + present.push(peer);
883 + }
884 + }
885 +
886 + const values = Hoek.clone(peers);
887 + const main = values.splice(0, 1)[0];
888 + const allPresent = (present.length === peers.length);
889 + return allPresent ? this.createError('object.nand', {
890 + main,
891 + mainWithLabel: internals.keysToLabels(this, main),
892 + peers: values,
893 + peersWithLabels: internals.keysToLabels(this, values)
894 + }, state, options) : null;
895 +};
896 +
897 +
898 +module.exports = new internals.Object();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Net = require('net');
6 +const Hoek = require('hoek');
7 +let Isemail; // Loaded on demand
8 +const Any = require('../any');
9 +const Ref = require('../../ref');
10 +const JoiDate = require('../date');
11 +const Uri = require('./uri');
12 +const Ip = require('./ip');
13 +
14 +// Declare internals
15 +
16 +const internals = {
17 + uriRegex: Uri.createUriRegex(),
18 + ipRegex: Ip.createIpRegex(['ipv4', 'ipv6', 'ipvfuture'], 'optional'),
19 + guidBrackets: {
20 + '{': '}', '[': ']', '(': ')', '': ''
21 + },
22 + guidVersions: {
23 + uuidv1: '1',
24 + uuidv2: '2',
25 + uuidv3: '3',
26 + uuidv4: '4',
27 + uuidv5: '5'
28 + },
29 + cidrPresences: ['required', 'optional', 'forbidden'],
30 + normalizationForms: ['NFC', 'NFD', 'NFKC', 'NFKD']
31 +};
32 +
33 +internals.String = class extends Any {
34 +
35 + constructor() {
36 +
37 + super();
38 + this._type = 'string';
39 + this._invalids.add('');
40 + }
41 +
42 + _base(value, state, options) {
43 +
44 + if (typeof value === 'string' &&
45 + options.convert) {
46 +
47 + if (this._flags.normalize) {
48 + value = value.normalize(this._flags.normalize);
49 + }
50 +
51 + if (this._flags.case) {
52 + value = (this._flags.case === 'upper' ? value.toLocaleUpperCase() : value.toLocaleLowerCase());
53 + }
54 +
55 + if (this._flags.trim) {
56 + value = value.trim();
57 + }
58 +
59 + if (this._inner.replacements) {
60 +
61 + for (let i = 0; i < this._inner.replacements.length; ++i) {
62 + const replacement = this._inner.replacements[i];
63 + value = value.replace(replacement.pattern, replacement.replacement);
64 + }
65 + }
66 +
67 + if (this._flags.truncate) {
68 + for (let i = 0; i < this._tests.length; ++i) {
69 + const test = this._tests[i];
70 + if (test.name === 'max') {
71 + value = value.slice(0, test.arg);
72 + break;
73 + }
74 + }
75 + }
76 + }
77 +
78 + return {
79 + value,
80 + errors: (typeof value === 'string') ? null : this.createError('string.base', { value }, state, options)
81 + };
82 + }
83 +
84 + insensitive() {
85 +
86 + if (this._flags.insensitive) {
87 + return this;
88 + }
89 +
90 + const obj = this.clone();
91 + obj._flags.insensitive = true;
92 + return obj;
93 + }
94 +
95 + creditCard() {
96 +
97 + return this._test('creditCard', undefined, function (value, state, options) {
98 +
99 + let i = value.length;
100 + let sum = 0;
101 + let mul = 1;
102 +
103 + while (i--) {
104 + const char = value.charAt(i) * mul;
105 + sum = sum + (char - (char > 9) * 9);
106 + mul = mul ^ 3;
107 + }
108 +
109 + const check = (sum % 10 === 0) && (sum > 0);
110 + return check ? value : this.createError('string.creditCard', { value }, state, options);
111 + });
112 + }
113 +
114 + regex(pattern, patternOptions) {
115 +
116 + Hoek.assert(pattern instanceof RegExp, 'pattern must be a RegExp');
117 +
118 + const patternObject = {
119 + pattern: new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined) // Future version should break this and forbid unsupported regex flags
120 + };
121 +
122 + if (typeof patternOptions === 'string') {
123 + patternObject.name = patternOptions;
124 + }
125 + else if (typeof patternOptions === 'object') {
126 + patternObject.invert = !!patternOptions.invert;
127 +
128 + if (patternOptions.name) {
129 + patternObject.name = patternOptions.name;
130 + }
131 + }
132 +
133 + const errorCode = ['string.regex', patternObject.invert ? '.invert' : '', patternObject.name ? '.name' : '.base'].join('');
134 +
135 + return this._test('regex', patternObject, function (value, state, options) {
136 +
137 + const patternMatch = patternObject.pattern.test(value);
138 +
139 + if (patternMatch ^ patternObject.invert) {
140 + return value;
141 + }
142 +
143 + return this.createError(errorCode, { name: patternObject.name, pattern: patternObject.pattern, value }, state, options);
144 + });
145 + }
146 +
147 + alphanum() {
148 +
149 + return this._test('alphanum', undefined, function (value, state, options) {
150 +
151 + if (/^[a-zA-Z0-9]+$/.test(value)) {
152 + return value;
153 + }
154 +
155 + return this.createError('string.alphanum', { value }, state, options);
156 + });
157 + }
158 +
159 + token() {
160 +
161 + return this._test('token', undefined, function (value, state, options) {
162 +
163 + if (/^\w+$/.test(value)) {
164 + return value;
165 + }
166 +
167 + return this.createError('string.token', { value }, state, options);
168 + });
169 + }
170 +
171 + email(isEmailOptions) {
172 +
173 + if (isEmailOptions) {
174 + Hoek.assert(typeof isEmailOptions === 'object', 'email options must be an object');
175 + Hoek.assert(typeof isEmailOptions.checkDNS === 'undefined', 'checkDNS option is not supported');
176 + Hoek.assert(typeof isEmailOptions.tldWhitelist === 'undefined' ||
177 + typeof isEmailOptions.tldWhitelist === 'object', 'tldWhitelist must be an array or object');
178 + Hoek.assert(
179 + typeof isEmailOptions.minDomainAtoms === 'undefined' ||
180 + Number.isSafeInteger(isEmailOptions.minDomainAtoms) &&
181 + isEmailOptions.minDomainAtoms > 0,
182 + 'minDomainAtoms must be a positive integer'
183 + );
184 + Hoek.assert(
185 + typeof isEmailOptions.errorLevel === 'undefined' ||
186 + typeof isEmailOptions.errorLevel === 'boolean' ||
187 + (
188 + Number.isSafeInteger(isEmailOptions.errorLevel) &&
189 + isEmailOptions.errorLevel >= 0
190 + ),
191 + 'errorLevel must be a non-negative integer or boolean'
192 + );
193 + }
194 +
195 + return this._test('email', isEmailOptions, function (value, state, options) {
196 +
197 + Isemail = Isemail || require('isemail');
198 +
199 + try {
200 + const result = Isemail.validate(value, isEmailOptions);
201 + if (result === true || result === 0) {
202 + return value;
203 + }
204 + }
205 + catch (e) { }
206 +
207 + return this.createError('string.email', { value }, state, options);
208 + });
209 + }
210 +
211 + ip(ipOptions = {}) {
212 +
213 + let regex = internals.ipRegex;
214 + Hoek.assert(typeof ipOptions === 'object', 'options must be an object');
215 +
216 + if (ipOptions.cidr) {
217 + Hoek.assert(typeof ipOptions.cidr === 'string', 'cidr must be a string');
218 + ipOptions.cidr = ipOptions.cidr.toLowerCase();
219 +
220 + Hoek.assert(Hoek.contain(internals.cidrPresences, ipOptions.cidr), 'cidr must be one of ' + internals.cidrPresences.join(', '));
221 +
222 + // If we only received a `cidr` setting, create a regex for it. But we don't need to create one if `cidr` is "optional" since that is the default
223 + if (!ipOptions.version && ipOptions.cidr !== 'optional') {
224 + regex = Ip.createIpRegex(['ipv4', 'ipv6', 'ipvfuture'], ipOptions.cidr);
225 + }
226 + }
227 + else {
228 +
229 + // Set our default cidr strategy
230 + ipOptions.cidr = 'optional';
231 + }
232 +
233 + let versions;
234 + if (ipOptions.version) {
235 + if (!Array.isArray(ipOptions.version)) {
236 + ipOptions.version = [ipOptions.version];
237 + }
238 +
239 + Hoek.assert(ipOptions.version.length >= 1, 'version must have at least 1 version specified');
240 +
241 + versions = [];
242 + for (let i = 0; i < ipOptions.version.length; ++i) {
243 + let version = ipOptions.version[i];
244 + Hoek.assert(typeof version === 'string', 'version at position ' + i + ' must be a string');
245 + version = version.toLowerCase();
246 + Hoek.assert(Ip.versions[version], 'version at position ' + i + ' must be one of ' + Object.keys(Ip.versions).join(', '));
247 + versions.push(version);
248 + }
249 +
250 + // Make sure we have a set of versions
251 + versions = Hoek.unique(versions);
252 +
253 + regex = Ip.createIpRegex(versions, ipOptions.cidr);
254 + }
255 +
256 + return this._test('ip', ipOptions, function (value, state, options) {
257 +
258 + if (regex.test(value)) {
259 + return value;
260 + }
261 +
262 + if (versions) {
263 + return this.createError('string.ipVersion', { value, cidr: ipOptions.cidr, version: versions }, state, options);
264 + }
265 +
266 + return this.createError('string.ip', { value, cidr: ipOptions.cidr }, state, options);
267 + });
268 + }
269 +
270 + uri(uriOptions) {
271 +
272 + let customScheme = '';
273 + let allowRelative = false;
274 + let relativeOnly = false;
275 + let regex = internals.uriRegex;
276 +
277 + if (uriOptions) {
278 + Hoek.assert(typeof uriOptions === 'object', 'options must be an object');
279 +
280 + if (uriOptions.scheme) {
281 + Hoek.assert(uriOptions.scheme instanceof RegExp || typeof uriOptions.scheme === 'string' || Array.isArray(uriOptions.scheme), 'scheme must be a RegExp, String, or Array');
282 +
283 + if (!Array.isArray(uriOptions.scheme)) {
284 + uriOptions.scheme = [uriOptions.scheme];
285 + }
286 +
287 + Hoek.assert(uriOptions.scheme.length >= 1, 'scheme must have at least 1 scheme specified');
288 +
289 + // Flatten the array into a string to be used to match the schemes.
290 + for (let i = 0; i < uriOptions.scheme.length; ++i) {
291 + const scheme = uriOptions.scheme[i];
292 + Hoek.assert(scheme instanceof RegExp || typeof scheme === 'string', 'scheme at position ' + i + ' must be a RegExp or String');
293 +
294 + // Add OR separators if a value already exists
295 + customScheme = customScheme + (customScheme ? '|' : '');
296 +
297 + // If someone wants to match HTTP or HTTPS for example then we need to support both RegExp and String so we don't escape their pattern unknowingly.
298 + if (scheme instanceof RegExp) {
299 + customScheme = customScheme + scheme.source;
300 + }
301 + else {
302 + Hoek.assert(/[a-zA-Z][a-zA-Z0-9+-\.]*/.test(scheme), 'scheme at position ' + i + ' must be a valid scheme');
303 + customScheme = customScheme + Hoek.escapeRegex(scheme);
304 + }
305 + }
306 + }
307 +
308 + if (uriOptions.allowRelative) {
309 + allowRelative = true;
310 + }
311 +
312 + if (uriOptions.relativeOnly) {
313 + relativeOnly = true;
314 + }
315 + }
316 +
317 + if (customScheme || allowRelative || relativeOnly) {
318 + regex = Uri.createUriRegex(customScheme, allowRelative, relativeOnly);
319 + }
320 +
321 + return this._test('uri', uriOptions, function (value, state, options) {
322 +
323 + if (regex.test(value)) {
324 + return value;
325 + }
326 +
327 + if (relativeOnly) {
328 + return this.createError('string.uriRelativeOnly', { value }, state, options);
329 + }
330 +
331 + if (customScheme) {
332 + return this.createError('string.uriCustomScheme', { scheme: customScheme, value }, state, options);
333 + }
334 +
335 + return this.createError('string.uri', { value }, state, options);
336 + });
337 + }
338 +
339 + isoDate() {
340 +
341 + return this._test('isoDate', undefined, function (value, state, options) {
342 +
343 + if (JoiDate._isIsoDate(value)) {
344 + if (!options.convert) {
345 + return value;
346 + }
347 +
348 + const d = new Date(value);
349 + if (!isNaN(d.getTime())) {
350 + return d.toISOString();
351 + }
352 + }
353 +
354 + return this.createError('string.isoDate', { value }, state, options);
355 + });
356 + }
357 +
358 + guid(guidOptions) {
359 +
360 + let versionNumbers = '';
361 +
362 + if (guidOptions && guidOptions.version) {
363 + if (!Array.isArray(guidOptions.version)) {
364 + guidOptions.version = [guidOptions.version];
365 + }
366 +
367 + Hoek.assert(guidOptions.version.length >= 1, 'version must have at least 1 valid version specified');
368 + const versions = new Set();
369 +
370 + for (let i = 0; i < guidOptions.version.length; ++i) {
371 + let version = guidOptions.version[i];
372 + Hoek.assert(typeof version === 'string', 'version at position ' + i + ' must be a string');
373 + version = version.toLowerCase();
374 + const versionNumber = internals.guidVersions[version];
375 + Hoek.assert(versionNumber, 'version at position ' + i + ' must be one of ' + Object.keys(internals.guidVersions).join(', '));
376 + Hoek.assert(!(versions.has(versionNumber)), 'version at position ' + i + ' must not be a duplicate.');
377 +
378 + versionNumbers += versionNumber;
379 + versions.add(versionNumber);
380 + }
381 + }
382 +
383 + const guidRegex = new RegExp(`^([\\[{\\(]?)[0-9A-F]{8}([:-]?)[0-9A-F]{4}\\2?[${versionNumbers || '0-9A-F'}][0-9A-F]{3}\\2?[${versionNumbers ? '89AB' : '0-9A-F'}][0-9A-F]{3}\\2?[0-9A-F]{12}([\\]}\\)]?)$`, 'i');
384 +
385 + return this._test('guid', guidOptions, function (value, state, options) {
386 +
387 + const results = guidRegex.exec(value);
388 +
389 + if (!results) {
390 + return this.createError('string.guid', { value }, state, options);
391 + }
392 +
393 + // Matching braces
394 + if (internals.guidBrackets[results[1]] !== results[results.length - 1]) {
395 + return this.createError('string.guid', { value }, state, options);
396 + }
397 +
398 + return value;
399 + });
400 + }
401 +
402 + hex() {
403 +
404 + const regex = /^[a-f0-9]+$/i;
405 +
406 + return this._test('hex', regex, function (value, state, options) {
407 +
408 + if (regex.test(value)) {
409 + return value;
410 + }
411 +
412 + return this.createError('string.hex', { value }, state, options);
413 + });
414 + }
415 +
416 + base64(base64Options = {}) {
417 +
418 + // Validation.
419 + Hoek.assert(typeof base64Options === 'object', 'base64 options must be an object');
420 + Hoek.assert(typeof base64Options.paddingRequired === 'undefined' || typeof base64Options.paddingRequired === 'boolean',
421 + 'paddingRequired must be boolean');
422 +
423 + // Determine if padding is required.
424 + const paddingRequired = base64Options.paddingRequired === false ?
425 + base64Options.paddingRequired
426 + : base64Options.paddingRequired || true;
427 +
428 + // Set validation based on preference.
429 + const regex = paddingRequired ?
430 + // Padding is required.
431 + /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/
432 + // Padding is optional.
433 + : /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}(==)?|[A-Za-z0-9+\/]{3}=?)?$/;
434 +
435 + return this._test('base64', regex, function (value, state, options) {
436 +
437 + if (regex.test(value)) {
438 + return value;
439 + }
440 +
441 + return this.createError('string.base64', { value }, state, options);
442 + });
443 + }
444 +
445 + hostname() {
446 +
447 + const regex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
448 +
449 + return this._test('hostname', undefined, function (value, state, options) {
450 +
451 + if ((value.length <= 255 && regex.test(value)) ||
452 + Net.isIPv6(value)) {
453 +
454 + return value;
455 + }
456 +
457 + return this.createError('string.hostname', { value }, state, options);
458 + });
459 + }
460 +
461 + normalize(form = 'NFC') {
462 +
463 + Hoek.assert(Hoek.contain(internals.normalizationForms, form), 'normalization form must be one of ' + internals.normalizationForms.join(', '));
464 +
465 + const obj = this._test('normalize', form, function (value, state, options) {
466 +
467 + if (options.convert ||
468 + value === value.normalize(form)) {
469 +
470 + return value;
471 + }
472 +
473 + return this.createError('string.normalize', { value, form }, state, options);
474 + });
475 +
476 + obj._flags.normalize = form;
477 + return obj;
478 + }
479 +
480 + lowercase() {
481 +
482 + const obj = this._test('lowercase', undefined, function (value, state, options) {
483 +
484 + if (options.convert ||
485 + value === value.toLocaleLowerCase()) {
486 +
487 + return value;
488 + }
489 +
490 + return this.createError('string.lowercase', { value }, state, options);
491 + });
492 +
493 + obj._flags.case = 'lower';
494 + return obj;
495 + }
496 +
497 + uppercase() {
498 +
499 + const obj = this._test('uppercase', undefined, function (value, state, options) {
500 +
501 + if (options.convert ||
502 + value === value.toLocaleUpperCase()) {
503 +
504 + return value;
505 + }
506 +
507 + return this.createError('string.uppercase', { value }, state, options);
508 + });
509 +
510 + obj._flags.case = 'upper';
511 + return obj;
512 + }
513 +
514 + trim() {
515 +
516 + const obj = this._test('trim', undefined, function (value, state, options) {
517 +
518 + if (options.convert ||
519 + value === value.trim()) {
520 +
521 + return value;
522 + }
523 +
524 + return this.createError('string.trim', { value }, state, options);
525 + });
526 +
527 + obj._flags.trim = true;
528 + return obj;
529 + }
530 +
531 + replace(pattern, replacement) {
532 +
533 + if (typeof pattern === 'string') {
534 + pattern = new RegExp(Hoek.escapeRegex(pattern), 'g');
535 + }
536 +
537 + Hoek.assert(pattern instanceof RegExp, 'pattern must be a RegExp');
538 + Hoek.assert(typeof replacement === 'string', 'replacement must be a String');
539 +
540 + // This can not be considere a test like trim, we can't "reject"
541 + // anything from this rule, so just clone the current object
542 + const obj = this.clone();
543 +
544 + if (!obj._inner.replacements) {
545 + obj._inner.replacements = [];
546 + }
547 +
548 + obj._inner.replacements.push({
549 + pattern,
550 + replacement
551 + });
552 +
553 + return obj;
554 + }
555 +
556 + truncate(enabled) {
557 +
558 + const value = enabled === undefined ? true : !!enabled;
559 +
560 + if (this._flags.truncate === value) {
561 + return this;
562 + }
563 +
564 + const obj = this.clone();
565 + obj._flags.truncate = value;
566 + return obj;
567 + }
568 +
569 +};
570 +
571 +internals.compare = function (type, compare) {
572 +
573 + return function (limit, encoding) {
574 +
575 + const isRef = Ref.isRef(limit);
576 +
577 + Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference');
578 + Hoek.assert(!encoding || Buffer.isEncoding(encoding), 'Invalid encoding:', encoding);
579 +
580 + return this._test(type, limit, function (value, state, options) {
581 +
582 + let compareTo;
583 + if (isRef) {
584 + compareTo = limit(state.reference || state.parent, options);
585 +
586 + if (!Number.isSafeInteger(compareTo)) {
587 + return this.createError('string.ref', { ref: limit.key }, state, options);
588 + }
589 + }
590 + else {
591 + compareTo = limit;
592 + }
593 +
594 + if (compare(value, compareTo, encoding)) {
595 + return value;
596 + }
597 +
598 + return this.createError('string.' + type, { limit: compareTo, value, encoding }, state, options);
599 + });
600 + };
601 +};
602 +
603 +
604 +internals.String.prototype.min = internals.compare('min', (value, limit, encoding) => {
605 +
606 + const length = encoding ? Buffer.byteLength(value, encoding) : value.length;
607 + return length >= limit;
608 +});
609 +
610 +
611 +internals.String.prototype.max = internals.compare('max', (value, limit, encoding) => {
612 +
613 + const length = encoding ? Buffer.byteLength(value, encoding) : value.length;
614 + return length <= limit;
615 +});
616 +
617 +
618 +internals.String.prototype.length = internals.compare('length', (value, limit, encoding) => {
619 +
620 + const length = encoding ? Buffer.byteLength(value, encoding) : value.length;
621 + return length === limit;
622 +});
623 +
624 +// Aliases
625 +
626 +internals.String.prototype.uuid = internals.String.prototype.guid;
627 +
628 +module.exports = new internals.String();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const RFC3986 = require('./rfc3986');
6 +
7 +
8 +// Declare internals
9 +
10 +const internals = {
11 + Ip: {
12 + cidrs: {
13 + ipv4: {
14 + required: '\\/(?:' + RFC3986.ipv4Cidr + ')',
15 + optional: '(?:\\/(?:' + RFC3986.ipv4Cidr + '))?',
16 + forbidden: ''
17 + },
18 + ipv6: {
19 + required: '\\/' + RFC3986.ipv6Cidr,
20 + optional: '(?:\\/' + RFC3986.ipv6Cidr + ')?',
21 + forbidden: ''
22 + },
23 + ipvfuture: {
24 + required: '\\/' + RFC3986.ipv6Cidr,
25 + optional: '(?:\\/' + RFC3986.ipv6Cidr + ')?',
26 + forbidden: ''
27 + }
28 + },
29 + versions: {
30 + ipv4: RFC3986.IPv4address,
31 + ipv6: RFC3986.IPv6address,
32 + ipvfuture: RFC3986.IPvFuture
33 + }
34 + }
35 +};
36 +
37 +
38 +internals.Ip.createIpRegex = function (versions, cidr) {
39 +
40 + let regex;
41 + for (let i = 0; i < versions.length; ++i) {
42 + const version = versions[i];
43 + if (!regex) {
44 + regex = '^(?:' + internals.Ip.versions[version] + internals.Ip.cidrs[version][cidr];
45 + }
46 + else {
47 + regex += '|' + internals.Ip.versions[version] + internals.Ip.cidrs[version][cidr];
48 + }
49 + }
50 +
51 + return new RegExp(regex + ')$');
52 +};
53 +
54 +module.exports = internals.Ip;
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +
6 +// Delcare internals
7 +
8 +const internals = {
9 + rfc3986: {}
10 +};
11 +
12 +
13 +internals.generate = function () {
14 +
15 + /**
16 + * elements separated by forward slash ("/") are alternatives.
17 + */
18 + const or = '|';
19 +
20 + /**
21 + * Rule to support zero-padded addresses.
22 + */
23 + const zeroPad = '0?';
24 +
25 + /**
26 + * DIGIT = %x30-39 ; 0-9
27 + */
28 + const digit = '0-9';
29 + const digitOnly = '[' + digit + ']';
30 +
31 + /**
32 + * ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
33 + */
34 + const alpha = 'a-zA-Z';
35 + const alphaOnly = '[' + alpha + ']';
36 +
37 + /**
38 + * IPv4
39 + * cidr = DIGIT ; 0-9
40 + * / %x31-32 DIGIT ; 10-29
41 + * / "3" %x30-32 ; 30-32
42 + */
43 + internals.rfc3986.ipv4Cidr = digitOnly + or + '[1-2]' + digitOnly + or + '3' + '[0-2]';
44 +
45 + /**
46 + * IPv6
47 + * cidr = DIGIT ; 0-9
48 + * / %x31-39 DIGIT ; 10-99
49 + * / "1" %x0-1 DIGIT ; 100-119
50 + * / "12" %x0-8 ; 120-128
51 + */
52 + internals.rfc3986.ipv6Cidr = '(?:' + zeroPad + zeroPad + digitOnly + or + zeroPad + '[1-9]' + digitOnly + or + '1' + '[01]' + digitOnly + or + '12[0-8])';
53 +
54 + /**
55 + * HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
56 + */
57 + const hexDigit = digit + 'A-Fa-f';
58 + const hexDigitOnly = '[' + hexDigit + ']';
59 +
60 + /**
61 + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
62 + */
63 + const unreserved = alpha + digit + '-\\._~';
64 +
65 + /**
66 + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
67 + */
68 + const subDelims = '!\\$&\'\\(\\)\\*\\+,;=';
69 +
70 + /**
71 + * pct-encoded = "%" HEXDIG HEXDIG
72 + */
73 + const pctEncoded = '%' + hexDigit;
74 +
75 + /**
76 + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
77 + */
78 + const pchar = unreserved + pctEncoded + subDelims + ':@';
79 + const pcharOnly = '[' + pchar + ']';
80 +
81 + /**
82 + * dec-octet = DIGIT ; 0-9
83 + * / %x31-39 DIGIT ; 10-99
84 + * / "1" 2DIGIT ; 100-199
85 + * / "2" %x30-34 DIGIT ; 200-249
86 + * / "25" %x30-35 ; 250-255
87 + */
88 + const decOctect = '(?:' + zeroPad + zeroPad + digitOnly + or + zeroPad + '[1-9]' + digitOnly + or + '1' + digitOnly + digitOnly + or + '2' + '[0-4]' + digitOnly + or + '25' + '[0-5])';
89 +
90 + /**
91 + * IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
92 + */
93 + internals.rfc3986.IPv4address = '(?:' + decOctect + '\\.){3}' + decOctect;
94 +
95 + /**
96 + * h16 = 1*4HEXDIG ; 16 bits of address represented in hexadecimal
97 + * ls32 = ( h16 ":" h16 ) / IPv4address ; least-significant 32 bits of address
98 + * IPv6address = 6( h16 ":" ) ls32
99 + * / "::" 5( h16 ":" ) ls32
100 + * / [ h16 ] "::" 4( h16 ":" ) ls32
101 + * / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
102 + * / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
103 + * / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
104 + * / [ *4( h16 ":" ) h16 ] "::" ls32
105 + * / [ *5( h16 ":" ) h16 ] "::" h16
106 + * / [ *6( h16 ":" ) h16 ] "::"
107 + */
108 + const h16 = hexDigitOnly + '{1,4}';
109 + const ls32 = '(?:' + h16 + ':' + h16 + '|' + internals.rfc3986.IPv4address + ')';
110 + const IPv6SixHex = '(?:' + h16 + ':){6}' + ls32;
111 + const IPv6FiveHex = '::(?:' + h16 + ':){5}' + ls32;
112 + const IPv6FourHex = '(?:' + h16 + ')?::(?:' + h16 + ':){4}' + ls32;
113 + const IPv6ThreeHex = '(?:(?:' + h16 + ':){0,1}' + h16 + ')?::(?:' + h16 + ':){3}' + ls32;
114 + const IPv6TwoHex = '(?:(?:' + h16 + ':){0,2}' + h16 + ')?::(?:' + h16 + ':){2}' + ls32;
115 + const IPv6OneHex = '(?:(?:' + h16 + ':){0,3}' + h16 + ')?::' + h16 + ':' + ls32;
116 + const IPv6NoneHex = '(?:(?:' + h16 + ':){0,4}' + h16 + ')?::' + ls32;
117 + const IPv6NoneHex2 = '(?:(?:' + h16 + ':){0,5}' + h16 + ')?::' + h16;
118 + const IPv6NoneHex3 = '(?:(?:' + h16 + ':){0,6}' + h16 + ')?::';
119 + internals.rfc3986.IPv6address = '(?:' + IPv6SixHex + or + IPv6FiveHex + or + IPv6FourHex + or + IPv6ThreeHex + or + IPv6TwoHex + or + IPv6OneHex + or + IPv6NoneHex + or + IPv6NoneHex2 + or + IPv6NoneHex3 + ')';
120 +
121 + /**
122 + * IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
123 + */
124 + internals.rfc3986.IPvFuture = 'v' + hexDigitOnly + '+\\.[' + unreserved + subDelims + ':]+';
125 +
126 + /**
127 + * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
128 + */
129 + internals.rfc3986.scheme = alphaOnly + '[' + alpha + digit + '+-\\.]*';
130 +
131 + /**
132 + * userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
133 + */
134 + const userinfo = '[' + unreserved + pctEncoded + subDelims + ':]*';
135 +
136 + /**
137 + * IP-literal = "[" ( IPv6address / IPvFuture ) "]"
138 + */
139 + const IPLiteral = '\\[(?:' + internals.rfc3986.IPv6address + or + internals.rfc3986.IPvFuture + ')\\]';
140 +
141 + /**
142 + * reg-name = *( unreserved / pct-encoded / sub-delims )
143 + */
144 + const regName = '[' + unreserved + pctEncoded + subDelims + ']{0,255}';
145 +
146 + /**
147 + * host = IP-literal / IPv4address / reg-name
148 + */
149 + const host = '(?:' + IPLiteral + or + internals.rfc3986.IPv4address + or + regName + ')';
150 +
151 + /**
152 + * port = *DIGIT
153 + */
154 + const port = digitOnly + '*';
155 +
156 + /**
157 + * authority = [ userinfo "@" ] host [ ":" port ]
158 + */
159 + const authority = '(?:' + userinfo + '@)?' + host + '(?::' + port + ')?';
160 +
161 + /**
162 + * segment = *pchar
163 + * segment-nz = 1*pchar
164 + * path = path-abempty ; begins with "/" or is empty
165 + * / path-absolute ; begins with "/" but not "//"
166 + * / path-noscheme ; begins with a non-colon segment
167 + * / path-rootless ; begins with a segment
168 + * / path-empty ; zero characters
169 + * path-abempty = *( "/" segment )
170 + * path-absolute = "/" [ segment-nz *( "/" segment ) ]
171 + * path-rootless = segment-nz *( "/" segment )
172 + */
173 + const segment = pcharOnly + '*';
174 + const segmentNz = pcharOnly + '+';
175 + const segmentNzNc = '[' + unreserved + pctEncoded + subDelims + '@' + ']+';
176 + const pathEmpty = '';
177 + const pathAbEmpty = '(?:\\/' + segment + ')*';
178 + const pathAbsolute = '\\/(?:' + segmentNz + pathAbEmpty + ')?';
179 + const pathRootless = segmentNz + pathAbEmpty;
180 + const pathNoScheme = segmentNzNc + pathAbEmpty;
181 +
182 + /**
183 + * hier-part = "//" authority path
184 + */
185 + internals.rfc3986.hierPart = '(?:' + '(?:\\/\\/' + authority + pathAbEmpty + ')' + or + pathAbsolute + or + pathRootless + ')';
186 +
187 + /**
188 + * relative-part = "//" authority path-abempty
189 + * / path-absolute
190 + * / path-noscheme
191 + * / path-empty
192 + */
193 + internals.rfc3986.relativeRef = '(?:' + '(?:\\/\\/' + authority + pathAbEmpty + ')' + or + pathAbsolute + or + pathNoScheme + or + pathEmpty + ')';
194 +
195 + /**
196 + * query = *( pchar / "/" / "?" )
197 + */
198 + internals.rfc3986.query = '[' + pchar + '\\/\\?]*(?=#|$)'; //Finish matching either at the fragment part or end of the line.
199 +
200 + /**
201 + * fragment = *( pchar / "/" / "?" )
202 + */
203 + internals.rfc3986.fragment = '[' + pchar + '\\/\\?]*';
204 +};
205 +
206 +
207 +internals.generate();
208 +
209 +module.exports = internals.rfc3986;
1 +'use strict';
2 +
3 +// Load Modules
4 +
5 +const RFC3986 = require('./rfc3986');
6 +
7 +
8 +// Declare internals
9 +
10 +const internals = {
11 + Uri: {
12 + createUriRegex: function (optionalScheme, allowRelative, relativeOnly) {
13 +
14 + let scheme = RFC3986.scheme;
15 + let prefix;
16 +
17 + if (relativeOnly) {
18 + prefix = '(?:' + RFC3986.relativeRef + ')';
19 + }
20 + else {
21 + // If we were passed a scheme, use it instead of the generic one
22 + if (optionalScheme) {
23 +
24 + // Have to put this in a non-capturing group to handle the OR statements
25 + scheme = '(?:' + optionalScheme + ')';
26 + }
27 +
28 + const withScheme = '(?:' + scheme + ':' + RFC3986.hierPart + ')';
29 +
30 + prefix = allowRelative ? '(?:' + withScheme + '|' + RFC3986.relativeRef + ')' : withScheme;
31 + }
32 +
33 + /**
34 + * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
35 + *
36 + * OR
37 + *
38 + * relative-ref = relative-part [ "?" query ] [ "#" fragment ]
39 + */
40 + return new RegExp('^' + prefix + '(?:\\?' + RFC3986.query + ')?' + '(?:#' + RFC3986.fragment + ')?$');
41 + }
42 + }
43 +};
44 +
45 +
46 +module.exports = internals.Uri;
1 +{
2 + "_from": "joi",
3 + "_id": "joi@13.1.2",
4 + "_inBundle": false,
5 + "_integrity": "sha512-bZZSQYW5lPXenOfENvgCBPb9+H6E6MeNWcMtikI04fKphj5tvFL9TOb+H2apJzbCrRw/jebjTH8z6IHLpBytGg==",
6 + "_location": "/joi",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "tag",
10 + "registry": true,
11 + "raw": "joi",
12 + "name": "joi",
13 + "escapedName": "joi",
14 + "rawSpec": "",
15 + "saveSpec": null,
16 + "fetchSpec": "latest"
17 + },
18 + "_requiredBy": [
19 + "#USER",
20 + "/"
21 + ],
22 + "_resolved": "https://registry.npmjs.org/joi/-/joi-13.1.2.tgz",
23 + "_shasum": "b2db260323cc7f919fafa51e09e2275bd089a97e",
24 + "_spec": "joi",
25 + "_where": "/Users/jeminlee/git-projects/node-practice/express-demo",
26 + "bugs": {
27 + "url": "https://github.com/hapijs/joi/issues"
28 + },
29 + "bundleDependencies": false,
30 + "dependencies": {
31 + "hoek": "5.x.x",
32 + "isemail": "3.x.x",
33 + "topo": "3.x.x"
34 + },
35 + "deprecated": false,
36 + "description": "Object schema validation",
37 + "devDependencies": {
38 + "code": "5.x.x",
39 + "hapitoc": "1.x.x",
40 + "lab": "15.x.x"
41 + },
42 + "engines": {
43 + "node": ">=8.9.0"
44 + },
45 + "homepage": "https://github.com/hapijs/joi",
46 + "keywords": [
47 + "hapi",
48 + "schema",
49 + "validation"
50 + ],
51 + "license": "BSD-3-Clause",
52 + "main": "lib/index.js",
53 + "name": "joi",
54 + "repository": {
55 + "type": "git",
56 + "url": "git://github.com/hapijs/joi.git"
57 + },
58 + "scripts": {
59 + "test": "lab -t 100 -a code -L",
60 + "test-cov-html": "lab -r html -o coverage.html -a code",
61 + "test-debug": "lab -a code",
62 + "toc": "hapitoc",
63 + "version": "npm run toc && git add API.md README.md"
64 + },
65 + "version": "13.1.2"
66 +}
1 +Copyright Mathias Bynens <https://mathiasbynens.be/>
2 +
3 +Permission is hereby granted, free of charge, to any person obtaining
4 +a copy of this software and associated documentation files (the
5 +"Software"), to deal in the Software without restriction, including
6 +without limitation the rights to use, copy, modify, merge, publish,
7 +distribute, sublicense, and/or sell copies of the Software, and to
8 +permit persons to whom the Software is furnished to do so, subject to
9 +the following conditions:
10 +
11 +The above copyright notice and this permission notice shall be
12 +included in all copies or substantial portions of the Software.
13 +
14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1 +# Punycode.js [![Build status](https://travis-ci.org/bestiejs/punycode.js.svg?branch=master)](https://travis-ci.org/bestiejs/punycode.js) [![Code coverage status](http://img.shields.io/codecov/c/github/bestiejs/punycode.js.svg)](https://codecov.io/gh/bestiejs/punycode.js) [![Dependency status](https://gemnasium.com/bestiejs/punycode.js.svg)](https://gemnasium.com/bestiejs/punycode.js)
2 +
3 +Punycode.js is a robust Punycode converter that fully complies to [RFC 3492](https://tools.ietf.org/html/rfc3492) and [RFC 5891](https://tools.ietf.org/html/rfc5891).
4 +
5 +This JavaScript library is the result of comparing, optimizing and documenting different open-source implementations of the Punycode algorithm:
6 +
7 +* [The C example code from RFC 3492](https://tools.ietf.org/html/rfc3492#appendix-C)
8 +* [`punycode.c` by _Markus W. Scherer_ (IBM)](http://opensource.apple.com/source/ICU/ICU-400.42/icuSources/common/punycode.c)
9 +* [`punycode.c` by _Ben Noordhuis_](https://github.com/bnoordhuis/punycode/blob/master/punycode.c)
10 +* [JavaScript implementation by _some_](http://stackoverflow.com/questions/183485/can-anyone-recommend-a-good-free-javascript-for-punycode-to-unicode-conversion/301287#301287)
11 +* [`punycode.js` by _Ben Noordhuis_](https://github.com/joyent/node/blob/426298c8c1c0d5b5224ac3658c41e7c2a3fe9377/lib/punycode.js) (note: [not fully compliant](https://github.com/joyent/node/issues/2072))
12 +
13 +This project was [bundled](https://github.com/joyent/node/blob/master/lib/punycode.js) with Node.js from [v0.6.2+](https://github.com/joyent/node/compare/975f1930b1...61e796decc) until [v7](https://github.com/nodejs/node/pull/7941) (soft-deprecated).
14 +
15 +The current version supports recent versions of Node.js only. It provides a CommonJS module and an ES6 module. For the old version that offers the same functionality with broader support, including Rhino, Ringo, Narwhal, and web browsers, see [v1.4.1](https://github.com/bestiejs/punycode.js/releases/tag/v1.4.1).
16 +
17 +## Installation
18 +
19 +Via [npm](https://www.npmjs.com/):
20 +
21 +```bash
22 +npm install punycode --save
23 +```
24 +
25 +In [Node.js](https://nodejs.org/):
26 +
27 +```js
28 +const punycode = require('punycode');
29 +```
30 +
31 +## API
32 +
33 +### `punycode.decode(string)`
34 +
35 +Converts a Punycode string of ASCII symbols to a string of Unicode symbols.
36 +
37 +```js
38 +// decode domain name parts
39 +punycode.decode('maana-pta'); // 'mañana'
40 +punycode.decode('--dqo34k'); // '☃-⌘'
41 +```
42 +
43 +### `punycode.encode(string)`
44 +
45 +Converts a string of Unicode symbols to a Punycode string of ASCII symbols.
46 +
47 +```js
48 +// encode domain name parts
49 +punycode.encode('mañana'); // 'maana-pta'
50 +punycode.encode('☃-⌘'); // '--dqo34k'
51 +```
52 +
53 +### `punycode.toUnicode(input)`
54 +
55 +Converts a Punycode string representing a domain name or an email address to Unicode. Only the Punycoded parts of the input will be converted, i.e. it doesn’t matter if you call it on a string that has already been converted to Unicode.
56 +
57 +```js
58 +// decode domain names
59 +punycode.toUnicode('xn--maana-pta.com');
60 +// → 'mañana.com'
61 +punycode.toUnicode('xn----dqo34k.com');
62 +// → '☃-⌘.com'
63 +
64 +// decode email addresses
65 +punycode.toUnicode('джумла@xn--p-8sbkgc5ag7bhce.xn--ba-lmcq');
66 +// → 'джумла@джpумлатест.bрфa'
67 +```
68 +
69 +### `punycode.toASCII(input)`
70 +
71 +Converts a lowercased Unicode string representing a domain name or an email address to Punycode. Only the non-ASCII parts of the input will be converted, i.e. it doesn’t matter if you call it with a domain that’s already in ASCII.
72 +
73 +```js
74 +// encode domain names
75 +punycode.toASCII('mañana.com');
76 +// → 'xn--maana-pta.com'
77 +punycode.toASCII('☃-⌘.com');
78 +// → 'xn----dqo34k.com'
79 +
80 +// encode email addresses
81 +punycode.toASCII('джумла@джpумлатест.bрфa');
82 +// → 'джумла@xn--p-8sbkgc5ag7bhce.xn--ba-lmcq'
83 +```
84 +
85 +### `punycode.ucs2`
86 +
87 +#### `punycode.ucs2.decode(string)`
88 +
89 +Creates an array containing the numeric code point values of each Unicode symbol in the string. While [JavaScript uses UCS-2 internally](https://mathiasbynens.be/notes/javascript-encoding), this function will convert a pair of surrogate halves (each of which UCS-2 exposes as separate characters) into a single code point, matching UTF-16.
90 +
91 +```js
92 +punycode.ucs2.decode('abc');
93 +// → [0x61, 0x62, 0x63]
94 +// surrogate pair for U+1D306 TETRAGRAM FOR CENTRE:
95 +punycode.ucs2.decode('\uD834\uDF06');
96 +// → [0x1D306]
97 +```
98 +
99 +#### `punycode.ucs2.encode(codePoints)`
100 +
101 +Creates a string based on an array of numeric code point values.
102 +
103 +```js
104 +punycode.ucs2.encode([0x61, 0x62, 0x63]);
105 +// → 'abc'
106 +punycode.ucs2.encode([0x1D306]);
107 +// → '\uD834\uDF06'
108 +```
109 +
110 +### `punycode.version`
111 +
112 +A string representing the current Punycode.js version number.
113 +
114 +## Author
115 +
116 +| [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
117 +|---|
118 +| [Mathias Bynens](https://mathiasbynens.be/) |
119 +
120 +## License
121 +
122 +Punycode.js is available under the [MIT](https://mths.be/mit) license.
1 +{
2 + "_from": "punycode@2.x.x",
3 + "_id": "punycode@2.1.0",
4 + "_inBundle": false,
5 + "_integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=",
6 + "_location": "/punycode",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "punycode@2.x.x",
12 + "name": "punycode",
13 + "escapedName": "punycode",
14 + "rawSpec": "2.x.x",
15 + "saveSpec": null,
16 + "fetchSpec": "2.x.x"
17 + },
18 + "_requiredBy": [
19 + "/isemail"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz",
22 + "_shasum": "5f863edc89b96db09074bad7947bf09056ca4e7d",
23 + "_spec": "punycode@2.x.x",
24 + "_where": "/Users/jeminlee/git-projects/node-practice/express-demo/node_modules/isemail",
25 + "author": {
26 + "name": "Mathias Bynens",
27 + "url": "https://mathiasbynens.be/"
28 + },
29 + "bugs": {
30 + "url": "https://github.com/bestiejs/punycode.js/issues"
31 + },
32 + "bundleDependencies": false,
33 + "contributors": [
34 + {
35 + "name": "Mathias Bynens",
36 + "url": "https://mathiasbynens.be/"
37 + }
38 + ],
39 + "deprecated": false,
40 + "description": "A robust Punycode converter that fully complies to RFC 3492 and RFC 5891, and works on nearly all JavaScript platforms.",
41 + "devDependencies": {
42 + "codecov": "^1.0.1",
43 + "istanbul": "^0.4.1",
44 + "mocha": "^2.5.3"
45 + },
46 + "engines": {
47 + "node": ">=6"
48 + },
49 + "files": [
50 + "LICENSE-MIT.txt",
51 + "punycode.js",
52 + "punycode.es6.js"
53 + ],
54 + "homepage": "https://mths.be/punycode",
55 + "jsnext:main": "punycode.es6.js",
56 + "jspm": {
57 + "map": {
58 + "./punycode.js": {
59 + "node": "@node/punycode"
60 + }
61 + }
62 + },
63 + "keywords": [
64 + "punycode",
65 + "unicode",
66 + "idn",
67 + "idna",
68 + "dns",
69 + "url",
70 + "domain"
71 + ],
72 + "license": "MIT",
73 + "main": "punycode.js",
74 + "name": "punycode",
75 + "repository": {
76 + "type": "git",
77 + "url": "git+https://github.com/bestiejs/punycode.js.git"
78 + },
79 + "scripts": {
80 + "prepublish": "node scripts/prepublish.js",
81 + "test": "mocha tests"
82 + },
83 + "version": "2.1.0"
84 +}
1 +'use strict';
2 +
3 +/** Highest positive signed 32-bit float value */
4 +const maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1
5 +
6 +/** Bootstring parameters */
7 +const base = 36;
8 +const tMin = 1;
9 +const tMax = 26;
10 +const skew = 38;
11 +const damp = 700;
12 +const initialBias = 72;
13 +const initialN = 128; // 0x80
14 +const delimiter = '-'; // '\x2D'
15 +
16 +/** Regular expressions */
17 +const regexPunycode = /^xn--/;
18 +const regexNonASCII = /[^\0-\x7E]/; // non-ASCII chars
19 +const regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; // RFC 3490 separators
20 +
21 +/** Error messages */
22 +const errors = {
23 + 'overflow': 'Overflow: input needs wider integers to process',
24 + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
25 + 'invalid-input': 'Invalid input'
26 +};
27 +
28 +/** Convenience shortcuts */
29 +const baseMinusTMin = base - tMin;
30 +const floor = Math.floor;
31 +const stringFromCharCode = String.fromCharCode;
32 +
33 +/*--------------------------------------------------------------------------*/
34 +
35 +/**
36 + * A generic error utility function.
37 + * @private
38 + * @param {String} type The error type.
39 + * @returns {Error} Throws a `RangeError` with the applicable error message.
40 + */
41 +function error(type) {
42 + throw new RangeError(errors[type]);
43 +}
44 +
45 +/**
46 + * A generic `Array#map` utility function.
47 + * @private
48 + * @param {Array} array The array to iterate over.
49 + * @param {Function} callback The function that gets called for every array
50 + * item.
51 + * @returns {Array} A new array of values returned by the callback function.
52 + */
53 +function map(array, fn) {
54 + const result = [];
55 + let length = array.length;
56 + while (length--) {
57 + result[length] = fn(array[length]);
58 + }
59 + return result;
60 +}
61 +
62 +/**
63 + * A simple `Array#map`-like wrapper to work with domain name strings or email
64 + * addresses.
65 + * @private
66 + * @param {String} domain The domain name or email address.
67 + * @param {Function} callback The function that gets called for every
68 + * character.
69 + * @returns {Array} A new string of characters returned by the callback
70 + * function.
71 + */
72 +function mapDomain(string, fn) {
73 + const parts = string.split('@');
74 + let result = '';
75 + if (parts.length > 1) {
76 + // In email addresses, only the domain name should be punycoded. Leave
77 + // the local part (i.e. everything up to `@`) intact.
78 + result = parts[0] + '@';
79 + string = parts[1];
80 + }
81 + // Avoid `split(regex)` for IE8 compatibility. See #17.
82 + string = string.replace(regexSeparators, '\x2E');
83 + const labels = string.split('.');
84 + const encoded = map(labels, fn).join('.');
85 + return result + encoded;
86 +}
87 +
88 +/**
89 + * Creates an array containing the numeric code points of each Unicode
90 + * character in the string. While JavaScript uses UCS-2 internally,
91 + * this function will convert a pair of surrogate halves (each of which
92 + * UCS-2 exposes as separate characters) into a single code point,
93 + * matching UTF-16.
94 + * @see `punycode.ucs2.encode`
95 + * @see <https://mathiasbynens.be/notes/javascript-encoding>
96 + * @memberOf punycode.ucs2
97 + * @name decode
98 + * @param {String} string The Unicode input string (UCS-2).
99 + * @returns {Array} The new array of code points.
100 + */
101 +function ucs2decode(string) {
102 + const output = [];
103 + let counter = 0;
104 + const length = string.length;
105 + while (counter < length) {
106 + const value = string.charCodeAt(counter++);
107 + if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
108 + // It's a high surrogate, and there is a next character.
109 + const extra = string.charCodeAt(counter++);
110 + if ((extra & 0xFC00) == 0xDC00) { // Low surrogate.
111 + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
112 + } else {
113 + // It's an unmatched surrogate; only append this code unit, in case the
114 + // next code unit is the high surrogate of a surrogate pair.
115 + output.push(value);
116 + counter--;
117 + }
118 + } else {
119 + output.push(value);
120 + }
121 + }
122 + return output;
123 +}
124 +
125 +/**
126 + * Creates a string based on an array of numeric code points.
127 + * @see `punycode.ucs2.decode`
128 + * @memberOf punycode.ucs2
129 + * @name encode
130 + * @param {Array} codePoints The array of numeric code points.
131 + * @returns {String} The new Unicode string (UCS-2).
132 + */
133 +const ucs2encode = array => String.fromCodePoint(...array);
134 +
135 +/**
136 + * Converts a basic code point into a digit/integer.
137 + * @see `digitToBasic()`
138 + * @private
139 + * @param {Number} codePoint The basic numeric code point value.
140 + * @returns {Number} The numeric value of a basic code point (for use in
141 + * representing integers) in the range `0` to `base - 1`, or `base` if
142 + * the code point does not represent a value.
143 + */
144 +const basicToDigit = function(codePoint) {
145 + if (codePoint - 0x30 < 0x0A) {
146 + return codePoint - 0x16;
147 + }
148 + if (codePoint - 0x41 < 0x1A) {
149 + return codePoint - 0x41;
150 + }
151 + if (codePoint - 0x61 < 0x1A) {
152 + return codePoint - 0x61;
153 + }
154 + return base;
155 +};
156 +
157 +/**
158 + * Converts a digit/integer into a basic code point.
159 + * @see `basicToDigit()`
160 + * @private
161 + * @param {Number} digit The numeric value of a basic code point.
162 + * @returns {Number} The basic code point whose value (when used for
163 + * representing integers) is `digit`, which needs to be in the range
164 + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
165 + * used; else, the lowercase form is used. The behavior is undefined
166 + * if `flag` is non-zero and `digit` has no uppercase form.
167 + */
168 +const digitToBasic = function(digit, flag) {
169 + // 0..25 map to ASCII a..z or A..Z
170 + // 26..35 map to ASCII 0..9
171 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
172 +};
173 +
174 +/**
175 + * Bias adaptation function as per section 3.4 of RFC 3492.
176 + * https://tools.ietf.org/html/rfc3492#section-3.4
177 + * @private
178 + */
179 +const adapt = function(delta, numPoints, firstTime) {
180 + let k = 0;
181 + delta = firstTime ? floor(delta / damp) : delta >> 1;
182 + delta += floor(delta / numPoints);
183 + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
184 + delta = floor(delta / baseMinusTMin);
185 + }
186 + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
187 +};
188 +
189 +/**
190 + * Converts a Punycode string of ASCII-only symbols to a string of Unicode
191 + * symbols.
192 + * @memberOf punycode
193 + * @param {String} input The Punycode string of ASCII-only symbols.
194 + * @returns {String} The resulting string of Unicode symbols.
195 + */
196 +const decode = function(input) {
197 + // Don't use UCS-2.
198 + const output = [];
199 + const inputLength = input.length;
200 + let i = 0;
201 + let n = initialN;
202 + let bias = initialBias;
203 +
204 + // Handle the basic code points: let `basic` be the number of input code
205 + // points before the last delimiter, or `0` if there is none, then copy
206 + // the first basic code points to the output.
207 +
208 + let basic = input.lastIndexOf(delimiter);
209 + if (basic < 0) {
210 + basic = 0;
211 + }
212 +
213 + for (let j = 0; j < basic; ++j) {
214 + // if it's not a basic code point
215 + if (input.charCodeAt(j) >= 0x80) {
216 + error('not-basic');
217 + }
218 + output.push(input.charCodeAt(j));
219 + }
220 +
221 + // Main decoding loop: start just after the last delimiter if any basic code
222 + // points were copied; start at the beginning otherwise.
223 +
224 + for (let index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
225 +
226 + // `index` is the index of the next character to be consumed.
227 + // Decode a generalized variable-length integer into `delta`,
228 + // which gets added to `i`. The overflow checking is easier
229 + // if we increase `i` as we go, then subtract off its starting
230 + // value at the end to obtain `delta`.
231 + let oldi = i;
232 + for (let w = 1, k = base; /* no condition */; k += base) {
233 +
234 + if (index >= inputLength) {
235 + error('invalid-input');
236 + }
237 +
238 + const digit = basicToDigit(input.charCodeAt(index++));
239 +
240 + if (digit >= base || digit > floor((maxInt - i) / w)) {
241 + error('overflow');
242 + }
243 +
244 + i += digit * w;
245 + const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
246 +
247 + if (digit < t) {
248 + break;
249 + }
250 +
251 + const baseMinusT = base - t;
252 + if (w > floor(maxInt / baseMinusT)) {
253 + error('overflow');
254 + }
255 +
256 + w *= baseMinusT;
257 +
258 + }
259 +
260 + const out = output.length + 1;
261 + bias = adapt(i - oldi, out, oldi == 0);
262 +
263 + // `i` was supposed to wrap around from `out` to `0`,
264 + // incrementing `n` each time, so we'll fix that now:
265 + if (floor(i / out) > maxInt - n) {
266 + error('overflow');
267 + }
268 +
269 + n += floor(i / out);
270 + i %= out;
271 +
272 + // Insert `n` at position `i` of the output.
273 + output.splice(i++, 0, n);
274 +
275 + }
276 +
277 + return String.fromCodePoint(...output);
278 +};
279 +
280 +/**
281 + * Converts a string of Unicode symbols (e.g. a domain name label) to a
282 + * Punycode string of ASCII-only symbols.
283 + * @memberOf punycode
284 + * @param {String} input The string of Unicode symbols.
285 + * @returns {String} The resulting Punycode string of ASCII-only symbols.
286 + */
287 +const encode = function(input) {
288 + const output = [];
289 +
290 + // Convert the input in UCS-2 to an array of Unicode code points.
291 + input = ucs2decode(input);
292 +
293 + // Cache the length.
294 + let inputLength = input.length;
295 +
296 + // Initialize the state.
297 + let n = initialN;
298 + let delta = 0;
299 + let bias = initialBias;
300 +
301 + // Handle the basic code points.
302 + for (const currentValue of input) {
303 + if (currentValue < 0x80) {
304 + output.push(stringFromCharCode(currentValue));
305 + }
306 + }
307 +
308 + let basicLength = output.length;
309 + let handledCPCount = basicLength;
310 +
311 + // `handledCPCount` is the number of code points that have been handled;
312 + // `basicLength` is the number of basic code points.
313 +
314 + // Finish the basic string with a delimiter unless it's empty.
315 + if (basicLength) {
316 + output.push(delimiter);
317 + }
318 +
319 + // Main encoding loop:
320 + while (handledCPCount < inputLength) {
321 +
322 + // All non-basic code points < n have been handled already. Find the next
323 + // larger one:
324 + let m = maxInt;
325 + for (const currentValue of input) {
326 + if (currentValue >= n && currentValue < m) {
327 + m = currentValue;
328 + }
329 + }
330 +
331 + // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
332 + // but guard against overflow.
333 + const handledCPCountPlusOne = handledCPCount + 1;
334 + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
335 + error('overflow');
336 + }
337 +
338 + delta += (m - n) * handledCPCountPlusOne;
339 + n = m;
340 +
341 + for (const currentValue of input) {
342 + if (currentValue < n && ++delta > maxInt) {
343 + error('overflow');
344 + }
345 + if (currentValue == n) {
346 + // Represent delta as a generalized variable-length integer.
347 + let q = delta;
348 + for (let k = base; /* no condition */; k += base) {
349 + const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
350 + if (q < t) {
351 + break;
352 + }
353 + const qMinusT = q - t;
354 + const baseMinusT = base - t;
355 + output.push(
356 + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
357 + );
358 + q = floor(qMinusT / baseMinusT);
359 + }
360 +
361 + output.push(stringFromCharCode(digitToBasic(q, 0)));
362 + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
363 + delta = 0;
364 + ++handledCPCount;
365 + }
366 + }
367 +
368 + ++delta;
369 + ++n;
370 +
371 + }
372 + return output.join('');
373 +};
374 +
375 +/**
376 + * Converts a Punycode string representing a domain name or an email address
377 + * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
378 + * it doesn't matter if you call it on a string that has already been
379 + * converted to Unicode.
380 + * @memberOf punycode
381 + * @param {String} input The Punycoded domain name or email address to
382 + * convert to Unicode.
383 + * @returns {String} The Unicode representation of the given Punycode
384 + * string.
385 + */
386 +const toUnicode = function(input) {
387 + return mapDomain(input, function(string) {
388 + return regexPunycode.test(string)
389 + ? decode(string.slice(4).toLowerCase())
390 + : string;
391 + });
392 +};
393 +
394 +/**
395 + * Converts a Unicode string representing a domain name or an email address to
396 + * Punycode. Only the non-ASCII parts of the domain name will be converted,
397 + * i.e. it doesn't matter if you call it with a domain that's already in
398 + * ASCII.
399 + * @memberOf punycode
400 + * @param {String} input The domain name or email address to convert, as a
401 + * Unicode string.
402 + * @returns {String} The Punycode representation of the given domain name or
403 + * email address.
404 + */
405 +const toASCII = function(input) {
406 + return mapDomain(input, function(string) {
407 + return regexNonASCII.test(string)
408 + ? 'xn--' + encode(string)
409 + : string;
410 + });
411 +};
412 +
413 +/*--------------------------------------------------------------------------*/
414 +
415 +/** Define the public API */
416 +const punycode = {
417 + /**
418 + * A string representing the current Punycode.js version number.
419 + * @memberOf punycode
420 + * @type String
421 + */
422 + 'version': '2.1.0',
423 + /**
424 + * An object of methods to convert from JavaScript's internal character
425 + * representation (UCS-2) to Unicode code points, and back.
426 + * @see <https://mathiasbynens.be/notes/javascript-encoding>
427 + * @memberOf punycode
428 + * @type Object
429 + */
430 + 'ucs2': {
431 + 'decode': ucs2decode,
432 + 'encode': ucs2encode
433 + },
434 + 'decode': decode,
435 + 'encode': encode,
436 + 'toASCII': toASCII,
437 + 'toUnicode': toUnicode
438 +};
439 +
440 +export default punycode;
1 +'use strict';
2 +
3 +/** Highest positive signed 32-bit float value */
4 +const maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1
5 +
6 +/** Bootstring parameters */
7 +const base = 36;
8 +const tMin = 1;
9 +const tMax = 26;
10 +const skew = 38;
11 +const damp = 700;
12 +const initialBias = 72;
13 +const initialN = 128; // 0x80
14 +const delimiter = '-'; // '\x2D'
15 +
16 +/** Regular expressions */
17 +const regexPunycode = /^xn--/;
18 +const regexNonASCII = /[^\0-\x7E]/; // non-ASCII chars
19 +const regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; // RFC 3490 separators
20 +
21 +/** Error messages */
22 +const errors = {
23 + 'overflow': 'Overflow: input needs wider integers to process',
24 + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
25 + 'invalid-input': 'Invalid input'
26 +};
27 +
28 +/** Convenience shortcuts */
29 +const baseMinusTMin = base - tMin;
30 +const floor = Math.floor;
31 +const stringFromCharCode = String.fromCharCode;
32 +
33 +/*--------------------------------------------------------------------------*/
34 +
35 +/**
36 + * A generic error utility function.
37 + * @private
38 + * @param {String} type The error type.
39 + * @returns {Error} Throws a `RangeError` with the applicable error message.
40 + */
41 +function error(type) {
42 + throw new RangeError(errors[type]);
43 +}
44 +
45 +/**
46 + * A generic `Array#map` utility function.
47 + * @private
48 + * @param {Array} array The array to iterate over.
49 + * @param {Function} callback The function that gets called for every array
50 + * item.
51 + * @returns {Array} A new array of values returned by the callback function.
52 + */
53 +function map(array, fn) {
54 + const result = [];
55 + let length = array.length;
56 + while (length--) {
57 + result[length] = fn(array[length]);
58 + }
59 + return result;
60 +}
61 +
62 +/**
63 + * A simple `Array#map`-like wrapper to work with domain name strings or email
64 + * addresses.
65 + * @private
66 + * @param {String} domain The domain name or email address.
67 + * @param {Function} callback The function that gets called for every
68 + * character.
69 + * @returns {Array} A new string of characters returned by the callback
70 + * function.
71 + */
72 +function mapDomain(string, fn) {
73 + const parts = string.split('@');
74 + let result = '';
75 + if (parts.length > 1) {
76 + // In email addresses, only the domain name should be punycoded. Leave
77 + // the local part (i.e. everything up to `@`) intact.
78 + result = parts[0] + '@';
79 + string = parts[1];
80 + }
81 + // Avoid `split(regex)` for IE8 compatibility. See #17.
82 + string = string.replace(regexSeparators, '\x2E');
83 + const labels = string.split('.');
84 + const encoded = map(labels, fn).join('.');
85 + return result + encoded;
86 +}
87 +
88 +/**
89 + * Creates an array containing the numeric code points of each Unicode
90 + * character in the string. While JavaScript uses UCS-2 internally,
91 + * this function will convert a pair of surrogate halves (each of which
92 + * UCS-2 exposes as separate characters) into a single code point,
93 + * matching UTF-16.
94 + * @see `punycode.ucs2.encode`
95 + * @see <https://mathiasbynens.be/notes/javascript-encoding>
96 + * @memberOf punycode.ucs2
97 + * @name decode
98 + * @param {String} string The Unicode input string (UCS-2).
99 + * @returns {Array} The new array of code points.
100 + */
101 +function ucs2decode(string) {
102 + const output = [];
103 + let counter = 0;
104 + const length = string.length;
105 + while (counter < length) {
106 + const value = string.charCodeAt(counter++);
107 + if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
108 + // It's a high surrogate, and there is a next character.
109 + const extra = string.charCodeAt(counter++);
110 + if ((extra & 0xFC00) == 0xDC00) { // Low surrogate.
111 + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
112 + } else {
113 + // It's an unmatched surrogate; only append this code unit, in case the
114 + // next code unit is the high surrogate of a surrogate pair.
115 + output.push(value);
116 + counter--;
117 + }
118 + } else {
119 + output.push(value);
120 + }
121 + }
122 + return output;
123 +}
124 +
125 +/**
126 + * Creates a string based on an array of numeric code points.
127 + * @see `punycode.ucs2.decode`
128 + * @memberOf punycode.ucs2
129 + * @name encode
130 + * @param {Array} codePoints The array of numeric code points.
131 + * @returns {String} The new Unicode string (UCS-2).
132 + */
133 +const ucs2encode = array => String.fromCodePoint(...array);
134 +
135 +/**
136 + * Converts a basic code point into a digit/integer.
137 + * @see `digitToBasic()`
138 + * @private
139 + * @param {Number} codePoint The basic numeric code point value.
140 + * @returns {Number} The numeric value of a basic code point (for use in
141 + * representing integers) in the range `0` to `base - 1`, or `base` if
142 + * the code point does not represent a value.
143 + */
144 +const basicToDigit = function(codePoint) {
145 + if (codePoint - 0x30 < 0x0A) {
146 + return codePoint - 0x16;
147 + }
148 + if (codePoint - 0x41 < 0x1A) {
149 + return codePoint - 0x41;
150 + }
151 + if (codePoint - 0x61 < 0x1A) {
152 + return codePoint - 0x61;
153 + }
154 + return base;
155 +};
156 +
157 +/**
158 + * Converts a digit/integer into a basic code point.
159 + * @see `basicToDigit()`
160 + * @private
161 + * @param {Number} digit The numeric value of a basic code point.
162 + * @returns {Number} The basic code point whose value (when used for
163 + * representing integers) is `digit`, which needs to be in the range
164 + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
165 + * used; else, the lowercase form is used. The behavior is undefined
166 + * if `flag` is non-zero and `digit` has no uppercase form.
167 + */
168 +const digitToBasic = function(digit, flag) {
169 + // 0..25 map to ASCII a..z or A..Z
170 + // 26..35 map to ASCII 0..9
171 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
172 +};
173 +
174 +/**
175 + * Bias adaptation function as per section 3.4 of RFC 3492.
176 + * https://tools.ietf.org/html/rfc3492#section-3.4
177 + * @private
178 + */
179 +const adapt = function(delta, numPoints, firstTime) {
180 + let k = 0;
181 + delta = firstTime ? floor(delta / damp) : delta >> 1;
182 + delta += floor(delta / numPoints);
183 + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
184 + delta = floor(delta / baseMinusTMin);
185 + }
186 + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
187 +};
188 +
189 +/**
190 + * Converts a Punycode string of ASCII-only symbols to a string of Unicode
191 + * symbols.
192 + * @memberOf punycode
193 + * @param {String} input The Punycode string of ASCII-only symbols.
194 + * @returns {String} The resulting string of Unicode symbols.
195 + */
196 +const decode = function(input) {
197 + // Don't use UCS-2.
198 + const output = [];
199 + const inputLength = input.length;
200 + let i = 0;
201 + let n = initialN;
202 + let bias = initialBias;
203 +
204 + // Handle the basic code points: let `basic` be the number of input code
205 + // points before the last delimiter, or `0` if there is none, then copy
206 + // the first basic code points to the output.
207 +
208 + let basic = input.lastIndexOf(delimiter);
209 + if (basic < 0) {
210 + basic = 0;
211 + }
212 +
213 + for (let j = 0; j < basic; ++j) {
214 + // if it's not a basic code point
215 + if (input.charCodeAt(j) >= 0x80) {
216 + error('not-basic');
217 + }
218 + output.push(input.charCodeAt(j));
219 + }
220 +
221 + // Main decoding loop: start just after the last delimiter if any basic code
222 + // points were copied; start at the beginning otherwise.
223 +
224 + for (let index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
225 +
226 + // `index` is the index of the next character to be consumed.
227 + // Decode a generalized variable-length integer into `delta`,
228 + // which gets added to `i`. The overflow checking is easier
229 + // if we increase `i` as we go, then subtract off its starting
230 + // value at the end to obtain `delta`.
231 + let oldi = i;
232 + for (let w = 1, k = base; /* no condition */; k += base) {
233 +
234 + if (index >= inputLength) {
235 + error('invalid-input');
236 + }
237 +
238 + const digit = basicToDigit(input.charCodeAt(index++));
239 +
240 + if (digit >= base || digit > floor((maxInt - i) / w)) {
241 + error('overflow');
242 + }
243 +
244 + i += digit * w;
245 + const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
246 +
247 + if (digit < t) {
248 + break;
249 + }
250 +
251 + const baseMinusT = base - t;
252 + if (w > floor(maxInt / baseMinusT)) {
253 + error('overflow');
254 + }
255 +
256 + w *= baseMinusT;
257 +
258 + }
259 +
260 + const out = output.length + 1;
261 + bias = adapt(i - oldi, out, oldi == 0);
262 +
263 + // `i` was supposed to wrap around from `out` to `0`,
264 + // incrementing `n` each time, so we'll fix that now:
265 + if (floor(i / out) > maxInt - n) {
266 + error('overflow');
267 + }
268 +
269 + n += floor(i / out);
270 + i %= out;
271 +
272 + // Insert `n` at position `i` of the output.
273 + output.splice(i++, 0, n);
274 +
275 + }
276 +
277 + return String.fromCodePoint(...output);
278 +};
279 +
280 +/**
281 + * Converts a string of Unicode symbols (e.g. a domain name label) to a
282 + * Punycode string of ASCII-only symbols.
283 + * @memberOf punycode
284 + * @param {String} input The string of Unicode symbols.
285 + * @returns {String} The resulting Punycode string of ASCII-only symbols.
286 + */
287 +const encode = function(input) {
288 + const output = [];
289 +
290 + // Convert the input in UCS-2 to an array of Unicode code points.
291 + input = ucs2decode(input);
292 +
293 + // Cache the length.
294 + let inputLength = input.length;
295 +
296 + // Initialize the state.
297 + let n = initialN;
298 + let delta = 0;
299 + let bias = initialBias;
300 +
301 + // Handle the basic code points.
302 + for (const currentValue of input) {
303 + if (currentValue < 0x80) {
304 + output.push(stringFromCharCode(currentValue));
305 + }
306 + }
307 +
308 + let basicLength = output.length;
309 + let handledCPCount = basicLength;
310 +
311 + // `handledCPCount` is the number of code points that have been handled;
312 + // `basicLength` is the number of basic code points.
313 +
314 + // Finish the basic string with a delimiter unless it's empty.
315 + if (basicLength) {
316 + output.push(delimiter);
317 + }
318 +
319 + // Main encoding loop:
320 + while (handledCPCount < inputLength) {
321 +
322 + // All non-basic code points < n have been handled already. Find the next
323 + // larger one:
324 + let m = maxInt;
325 + for (const currentValue of input) {
326 + if (currentValue >= n && currentValue < m) {
327 + m = currentValue;
328 + }
329 + }
330 +
331 + // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
332 + // but guard against overflow.
333 + const handledCPCountPlusOne = handledCPCount + 1;
334 + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
335 + error('overflow');
336 + }
337 +
338 + delta += (m - n) * handledCPCountPlusOne;
339 + n = m;
340 +
341 + for (const currentValue of input) {
342 + if (currentValue < n && ++delta > maxInt) {
343 + error('overflow');
344 + }
345 + if (currentValue == n) {
346 + // Represent delta as a generalized variable-length integer.
347 + let q = delta;
348 + for (let k = base; /* no condition */; k += base) {
349 + const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
350 + if (q < t) {
351 + break;
352 + }
353 + const qMinusT = q - t;
354 + const baseMinusT = base - t;
355 + output.push(
356 + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
357 + );
358 + q = floor(qMinusT / baseMinusT);
359 + }
360 +
361 + output.push(stringFromCharCode(digitToBasic(q, 0)));
362 + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
363 + delta = 0;
364 + ++handledCPCount;
365 + }
366 + }
367 +
368 + ++delta;
369 + ++n;
370 +
371 + }
372 + return output.join('');
373 +};
374 +
375 +/**
376 + * Converts a Punycode string representing a domain name or an email address
377 + * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
378 + * it doesn't matter if you call it on a string that has already been
379 + * converted to Unicode.
380 + * @memberOf punycode
381 + * @param {String} input The Punycoded domain name or email address to
382 + * convert to Unicode.
383 + * @returns {String} The Unicode representation of the given Punycode
384 + * string.
385 + */
386 +const toUnicode = function(input) {
387 + return mapDomain(input, function(string) {
388 + return regexPunycode.test(string)
389 + ? decode(string.slice(4).toLowerCase())
390 + : string;
391 + });
392 +};
393 +
394 +/**
395 + * Converts a Unicode string representing a domain name or an email address to
396 + * Punycode. Only the non-ASCII parts of the domain name will be converted,
397 + * i.e. it doesn't matter if you call it with a domain that's already in
398 + * ASCII.
399 + * @memberOf punycode
400 + * @param {String} input The domain name or email address to convert, as a
401 + * Unicode string.
402 + * @returns {String} The Punycode representation of the given domain name or
403 + * email address.
404 + */
405 +const toASCII = function(input) {
406 + return mapDomain(input, function(string) {
407 + return regexNonASCII.test(string)
408 + ? 'xn--' + encode(string)
409 + : string;
410 + });
411 +};
412 +
413 +/*--------------------------------------------------------------------------*/
414 +
415 +/** Define the public API */
416 +const punycode = {
417 + /**
418 + * A string representing the current Punycode.js version number.
419 + * @memberOf punycode
420 + * @type String
421 + */
422 + 'version': '2.1.0',
423 + /**
424 + * An object of methods to convert from JavaScript's internal character
425 + * representation (UCS-2) to Unicode code points, and back.
426 + * @see <https://mathiasbynens.be/notes/javascript-encoding>
427 + * @memberOf punycode
428 + * @type Object
429 + */
430 + 'ucs2': {
431 + 'decode': ucs2decode,
432 + 'encode': ucs2encode
433 + },
434 + 'decode': decode,
435 + 'encode': encode,
436 + 'toASCII': toASCII,
437 + 'toUnicode': toUnicode
438 +};
439 +
440 +module.exports = punycode;
1 +Copyright (c) 2012-2016, Project contributors
2 +Copyright (c) 2012-2014, Walmart
3 +All rights reserved.
4 +
5 +Redistribution and use in source and binary forms, with or without
6 +modification, are permitted provided that the following conditions are met:
7 + * Redistributions of source code must retain the above copyright
8 + notice, this list of conditions and the following disclaimer.
9 + * Redistributions in binary form must reproduce the above copyright
10 + notice, this list of conditions and the following disclaimer in the
11 + documentation and/or other materials provided with the distribution.
12 + * The names of any contributors may not be used to endorse or promote
13 + products derived from this software without specific prior written
14 + permission.
15 +
16 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
20 +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 +
27 + * * *
28 +
29 +The complete list of contributors can be found at: https://github.com/hapijs/topo/graphs/contributors
1 +# topo
2 +
3 +Topological sorting with grouping support.
4 +
5 +[![Build Status](https://secure.travis-ci.org/hapijs/topo.svg?branch=master)](http://travis-ci.org/hapijs/topo)
6 +
7 +Lead Maintainer: [Devin Ivy](https://github.com/devinivy)
8 +
9 +## Usage
10 +
11 +See the [API Reference](API.md)
12 +
13 +**Example**
14 +```node
15 +const Topo = require('topo');
16 +
17 +const morning = new Topo();
18 +
19 +morning.add('Nap', { after: ['breakfast', 'prep'] });
20 +
21 +morning.add([
22 + 'Make toast',
23 + 'Pour juice'
24 +], { before: 'breakfast', group: 'prep' });
25 +
26 +morning.add('Eat breakfast', { group: 'breakfast' });
27 +
28 +morning.nodes; // ['Make toast', 'Pour juice', 'Eat breakfast', 'Nap']
29 +```
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +
7 +
8 +// Declare internals
9 +
10 +const internals = {};
11 +
12 +
13 +exports = module.exports = internals.Topo = function () {
14 +
15 + this._items = [];
16 + this.nodes = [];
17 +};
18 +
19 +
20 +internals.Topo.prototype.add = function (nodes, options) {
21 +
22 + options = options || {};
23 +
24 + // Validate rules
25 +
26 + const before = [].concat(options.before || []);
27 + const after = [].concat(options.after || []);
28 + const group = options.group || '?';
29 + const sort = options.sort || 0; // Used for merging only
30 +
31 + Hoek.assert(before.indexOf(group) === -1, 'Item cannot come before itself:', group);
32 + Hoek.assert(before.indexOf('?') === -1, 'Item cannot come before unassociated items');
33 + Hoek.assert(after.indexOf(group) === -1, 'Item cannot come after itself:', group);
34 + Hoek.assert(after.indexOf('?') === -1, 'Item cannot come after unassociated items');
35 +
36 + ([].concat(nodes)).forEach((node, i) => {
37 +
38 + const item = {
39 + seq: this._items.length,
40 + sort,
41 + before,
42 + after,
43 + group,
44 + node
45 + };
46 +
47 + this._items.push(item);
48 + });
49 +
50 + // Insert event
51 +
52 + const error = this._sort();
53 + Hoek.assert(!error, 'item', (group !== '?' ? 'added into group ' + group : ''), 'created a dependencies error');
54 +
55 + return this.nodes;
56 +};
57 +
58 +
59 +internals.Topo.prototype.merge = function (others) {
60 +
61 + others = [].concat(others);
62 + for (let i = 0; i < others.length; ++i) {
63 + const other = others[i];
64 + if (other) {
65 + for (let j = 0; j < other._items.length; ++j) {
66 + const item = Hoek.shallow(other._items[j]);
67 + this._items.push(item);
68 + }
69 + }
70 + }
71 +
72 + // Sort items
73 +
74 + this._items.sort(internals.mergeSort);
75 + for (let i = 0; i < this._items.length; ++i) {
76 + this._items[i].seq = i;
77 + }
78 +
79 + const error = this._sort();
80 + Hoek.assert(!error, 'merge created a dependencies error');
81 +
82 + return this.nodes;
83 +};
84 +
85 +
86 +internals.mergeSort = function (a, b) {
87 +
88 + return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);
89 +};
90 +
91 +
92 +internals.Topo.prototype._sort = function () {
93 +
94 + // Construct graph
95 +
96 + const graph = {};
97 + const graphAfters = Object.create(null); // A prototype can bungle lookups w/ false positives
98 + const groups = Object.create(null);
99 +
100 + for (let i = 0; i < this._items.length; ++i) {
101 + const item = this._items[i];
102 + const seq = item.seq; // Unique across all items
103 + const group = item.group;
104 +
105 + // Determine Groups
106 +
107 + groups[group] = groups[group] || [];
108 + groups[group].push(seq);
109 +
110 + // Build intermediary graph using 'before'
111 +
112 + graph[seq] = item.before;
113 +
114 + // Build second intermediary graph with 'after'
115 +
116 + const after = item.after;
117 + for (let j = 0; j < after.length; ++j) {
118 + graphAfters[after[j]] = (graphAfters[after[j]] || []).concat(seq);
119 + }
120 + }
121 +
122 + // Expand intermediary graph
123 +
124 + let graphNodes = Object.keys(graph);
125 + for (let i = 0; i < graphNodes.length; ++i) {
126 + const node = graphNodes[i];
127 + const expandedGroups = [];
128 +
129 + const graphNodeItems = Object.keys(graph[node]);
130 + for (let j = 0; j < graphNodeItems.length; ++j) {
131 + const group = graph[node][graphNodeItems[j]];
132 + groups[group] = groups[group] || [];
133 +
134 + for (let k = 0; k < groups[group].length; ++k) {
135 + expandedGroups.push(groups[group][k]);
136 + }
137 + }
138 + graph[node] = expandedGroups;
139 + }
140 +
141 + // Merge intermediary graph using graphAfters into final graph
142 +
143 + const afterNodes = Object.keys(graphAfters);
144 + for (let i = 0; i < afterNodes.length; ++i) {
145 + const group = afterNodes[i];
146 +
147 + if (groups[group]) {
148 + for (let j = 0; j < groups[group].length; ++j) {
149 + const node = groups[group][j];
150 + graph[node] = graph[node].concat(graphAfters[group]);
151 + }
152 + }
153 + }
154 +
155 + // Compile ancestors
156 +
157 + let children;
158 + const ancestors = {};
159 + graphNodes = Object.keys(graph);
160 + for (let i = 0; i < graphNodes.length; ++i) {
161 + const node = graphNodes[i];
162 + children = graph[node];
163 +
164 + for (let j = 0; j < children.length; ++j) {
165 + ancestors[children[j]] = (ancestors[children[j]] || []).concat(node);
166 + }
167 + }
168 +
169 + // Topo sort
170 +
171 + const visited = {};
172 + const sorted = [];
173 +
174 + for (let i = 0; i < this._items.length; ++i) { // Really looping thru item.seq values out of order
175 + let next = i;
176 +
177 + if (ancestors[i]) {
178 + next = null;
179 + for (let j = 0; j < this._items.length; ++j) { // As above, these are item.seq values
180 + if (visited[j] === true) {
181 + continue;
182 + }
183 +
184 + if (!ancestors[j]) {
185 + ancestors[j] = [];
186 + }
187 +
188 + const shouldSeeCount = ancestors[j].length;
189 + let seenCount = 0;
190 + for (let k = 0; k < shouldSeeCount; ++k) {
191 + if (visited[ancestors[j][k]]) {
192 + ++seenCount;
193 + }
194 + }
195 +
196 + if (seenCount === shouldSeeCount) {
197 + next = j;
198 + break;
199 + }
200 + }
201 + }
202 +
203 + if (next !== null) {
204 + visited[next] = true;
205 + sorted.push(next);
206 + }
207 + }
208 +
209 + if (sorted.length !== this._items.length) {
210 + return new Error('Invalid dependencies');
211 + }
212 +
213 + const seqIndex = {};
214 + for (let i = 0; i < this._items.length; ++i) {
215 + const item = this._items[i];
216 + seqIndex[item.seq] = item;
217 + }
218 +
219 + const sortedNodes = [];
220 + this._items = sorted.map((value) => {
221 +
222 + const sortedItem = seqIndex[value];
223 + sortedNodes.push(sortedItem.node);
224 + return sortedItem;
225 + });
226 +
227 + this.nodes = sortedNodes;
228 +};
1 +{
2 + "_from": "topo@3.x.x",
3 + "_id": "topo@3.0.0",
4 + "_inBundle": false,
5 + "_integrity": "sha512-Tlu1fGlR90iCdIPURqPiufqAlCZYzLjHYVVbcFWDMcX7+tK8hdZWAfsMrD/pBul9jqHHwFjNdf1WaxA9vTRRhw==",
6 + "_location": "/topo",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "topo@3.x.x",
12 + "name": "topo",
13 + "escapedName": "topo",
14 + "rawSpec": "3.x.x",
15 + "saveSpec": null,
16 + "fetchSpec": "3.x.x"
17 + },
18 + "_requiredBy": [
19 + "/joi"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/topo/-/topo-3.0.0.tgz",
22 + "_shasum": "37e48c330efeac784538e0acd3e62ca5e231fe7a",
23 + "_spec": "topo@3.x.x",
24 + "_where": "/Users/jeminlee/git-projects/node-practice/express-demo/node_modules/joi",
25 + "bugs": {
26 + "url": "https://github.com/hapijs/topo/issues"
27 + },
28 + "bundleDependencies": false,
29 + "dependencies": {
30 + "hoek": "5.x.x"
31 + },
32 + "deprecated": false,
33 + "description": "Topological sorting with grouping support",
34 + "devDependencies": {
35 + "code": "5.x.x",
36 + "lab": "14.x.x"
37 + },
38 + "engines": {
39 + "node": ">=8.0.0"
40 + },
41 + "homepage": "https://github.com/hapijs/topo#readme",
42 + "keywords": [
43 + "topological",
44 + "sort",
45 + "toposort",
46 + "topsort"
47 + ],
48 + "license": "BSD-3-Clause",
49 + "main": "lib/index.js",
50 + "name": "topo",
51 + "repository": {
52 + "type": "git",
53 + "url": "git://github.com/hapijs/topo.git"
54 + },
55 + "scripts": {
56 + "test": "lab -a code -t 100 -L",
57 + "test-cov-html": "lab -a code -t 100 -L -r html -o coverage.html"
58 + },
59 + "version": "3.0.0"
60 +}
...@@ -159,6 +159,11 @@ ...@@ -159,6 +159,11 @@
159 "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 159 "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
160 "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 160 "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
161 }, 161 },
162 + "hoek": {
163 + "version": "5.0.3",
164 + "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.3.tgz",
165 + "integrity": "sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw=="
166 + },
162 "http-errors": { 167 "http-errors": {
163 "version": "1.6.2", 168 "version": "1.6.2",
164 "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 169 "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
...@@ -197,6 +202,24 @@ ...@@ -197,6 +202,24 @@
197 "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", 202 "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz",
198 "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" 203 "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs="
199 }, 204 },
205 + "isemail": {
206 + "version": "3.1.1",
207 + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.1.1.tgz",
208 + "integrity": "sha512-mVjAjvdPkpwXW61agT2E9AkGoegZO7SdJGCezWwxnETL58f5KwJ4vSVAMBUL5idL6rTlYAIGkX3n4suiviMLNw==",
209 + "requires": {
210 + "punycode": "2.1.0"
211 + }
212 + },
213 + "joi": {
214 + "version": "13.1.2",
215 + "resolved": "https://registry.npmjs.org/joi/-/joi-13.1.2.tgz",
216 + "integrity": "sha512-bZZSQYW5lPXenOfENvgCBPb9+H6E6MeNWcMtikI04fKphj5tvFL9TOb+H2apJzbCrRw/jebjTH8z6IHLpBytGg==",
217 + "requires": {
218 + "hoek": "5.0.3",
219 + "isemail": "3.1.1",
220 + "topo": "3.0.0"
221 + }
222 + },
200 "media-typer": { 223 "media-typer": {
201 "version": "0.3.0", 224 "version": "0.3.0",
202 "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 225 "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
...@@ -267,6 +290,11 @@ ...@@ -267,6 +290,11 @@
267 "ipaddr.js": "1.6.0" 290 "ipaddr.js": "1.6.0"
268 } 291 }
269 }, 292 },
293 + "punycode": {
294 + "version": "2.1.0",
295 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz",
296 + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0="
297 + },
270 "qs": { 298 "qs": {
271 "version": "6.5.1", 299 "version": "6.5.1",
272 "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 300 "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
...@@ -334,6 +362,14 @@ ...@@ -334,6 +362,14 @@
334 "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 362 "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
335 "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 363 "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
336 }, 364 },
365 + "topo": {
366 + "version": "3.0.0",
367 + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.0.tgz",
368 + "integrity": "sha512-Tlu1fGlR90iCdIPURqPiufqAlCZYzLjHYVVbcFWDMcX7+tK8hdZWAfsMrD/pBul9jqHHwFjNdf1WaxA9vTRRhw==",
369 + "requires": {
370 + "hoek": "5.0.3"
371 + }
372 + },
337 "type-is": { 373 "type-is": {
338 "version": "1.6.16", 374 "version": "1.6.16",
339 "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 375 "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
10 "author": "", 10 "author": "",
11 "license": "ISC", 11 "license": "ISC",
12 "dependencies": { 12 "dependencies": {
13 - "express": "^4.16.3" 13 + "express": "^4.16.3",
14 + "joi": "^13.1.2"
14 } 15 }
15 } 16 }
......