valia
Version:
Validation library for TypeScript and JavaScript.
1,481 lines (1,441 loc) • 86.7 kB
JavaScript
class SchemaException extends Error {
constructor(message) {
super(message);
this.name = "SchemaException";
}
}
class SchemaNodeException extends Error {
constructor(code, message, node, nodePath) {
super(message);
this.name = "SchemaNodeException";
this.code = code;
this.node = node;
this.nodePath = nodePath;
}
}
class SchemaDataRejection {
constructor(rootData, rootNode, code, data, node, nodePath) {
this.rootData = rootData;
this.rootNode = rootNode;
this.rootLabel = rootNode.label;
this.code = code;
this.data = data;
this.node = node;
this.nodePath = nodePath;
this.label = node.label;
if (typeof node.message === "function") {
this.message = node.message(code, data, node, nodePath);
}
else {
this.message = node.message;
}
}
}
class SchemaDataAdmission {
constructor(data, node) {
this.data = data;
this.node = node;
this.label = node.label;
}
}
class FormatsManager {
constructor() {
this.store = new Map();
}
add(formats) {
for (const format of formats) {
this.store.set(format.type, format);
}
}
has(type) {
return this.store.has(type);
}
get(type) {
const format = this.store.get(type);
if (!format)
throw new SchemaException("The format is unknown : " + type);
return (format);
}
}
class EventsManager {
constructor() {
this.listeners = new Map();
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
emit(event, ...args) {
const callbacks = this.listeners.get(event);
if (!callbacks)
return;
for (const callback of callbacks) {
callback(...args);
}
}
off(event, callback) {
const listeners = this.listeners.get(event);
if (!listeners)
return;
const index = listeners.indexOf(callback);
if (index !== -1)
listeners.splice(index, 1);
}
}
const nodeSymbol = Symbol("node");
const commonExceptions = {
TYPE_PROPERTY_UNDEFINED: "",
TYPE_PROPERTY_MISDECLARED: "",
TYPE_PROPERTY_MISCONFIGURED: "",
LABEL_PROPERTY_MISDECLARED: "",
MESSAGE_PROPERTY_MISDECLARED: ""
};
function commonMount(managers, node) {
const { type, label, message } = node;
if (!("type" in node)) {
return ("TYPE_PROPERTY_UNDEFINED");
}
if (typeof type !== "string") {
return ("TYPE_PROPERTY_MISDECLARED");
}
if (!managers.formats.has(type)) {
return ("TYPE_PROPERTY_MISCONFIGURED");
}
if (label !== undefined && typeof label !== "string") {
return ("LABEL_PROPERTY_MISDECLARED");
}
if (message !== undefined
&& typeof message !== "string"
&& typeof message !== "function") {
return ("MESSAGE_PROPERTY_MISDECLARED");
}
return (null);
}
function hasNodeSymbol(obj) {
return (typeof obj === "object" && Reflect.has(obj, nodeSymbol));
}
class MounterStack {
constructor(rootNode) {
this.tasks = [];
this.tasks.push({
node: rootNode,
partPath: { explicit: [], implicit: [] },
nodePath: { explicit: [], implicit: [] }
});
}
pushChunk(sourceTask, chunk) {
const prevNodePath = sourceTask.nodePath;
for (let i = 0; i < chunk.length; i++) {
const { node, partPath } = chunk[i];
this.tasks.push({
node,
partPath,
nodePath: {
explicit: partPath.explicit
? prevNodePath.explicit.concat(partPath.explicit)
: prevNodePath.explicit,
implicit: partPath.implicit
? prevNodePath.implicit.concat(partPath.implicit)
: prevNodePath.implicit
}
});
}
}
}
function mounter(managers, rootNode) {
const { formats, events } = managers;
const stack = new MounterStack(rootNode);
while (stack.tasks.length) {
const currentTask = stack.tasks.pop();
const { node, nodePath, partPath } = currentTask;
if (hasNodeSymbol(node)) {
node[nodeSymbol] = {
...node[nodeSymbol],
partPath
};
}
else {
let code = null;
code = commonMount(managers, node);
if (code) {
const message = commonExceptions[code];
throw new SchemaNodeException(code, message, node, nodePath);
}
const format = formats.get(node.type);
const chunkTasks = [];
code = format.mount(chunkTasks, node);
if (code) {
const message = format.exceptions[code];
throw new SchemaNodeException(code, message, node, nodePath);
}
Object.assign(node, {
[nodeSymbol]: {
partPath,
childNodes: chunkTasks.map((task) => task.node)
}
});
Object.freeze(node);
if (chunkTasks.length) {
stack.pushChunk(currentTask, chunkTasks);
}
events.emit("NODE_MOUNTED", node, nodePath);
}
}
events.emit("TREE_MOUNTED", rootNode);
return rootNode;
}
class CheckerStack {
constructor(rootNode, rootData) {
this.tasks = [];
this.hooks = [];
this.tasks.push({
data: rootData,
node: rootNode,
nodePath: { explicit: [], implicit: [] },
closerHook: null
});
}
pushChunk(sourceTask, chunk) {
const prevCloserHook = sourceTask.closerHook;
const prevNodePath = sourceTask.nodePath;
const staskTaskLength = this.tasks.length;
const stackHookLength = this.hooks.length;
const chunkTaskLength = chunk.length;
let chunkTaskIndex = 0, chunkHookCount = 0;
while (chunkTaskIndex < chunkTaskLength) {
const { data, node, hook } = chunk[chunkTaskIndex];
const partPath = node[nodeSymbol].partPath;
let closerHook = prevCloserHook;
if (hook) {
closerHook = {
...hook,
sourceTask,
chunkTaskIndex: staskTaskLength,
branchTaskIndex: staskTaskLength + chunkTaskIndex,
chunkHookIndex: stackHookLength,
branchHookIndex: stackHookLength + chunkHookCount
};
this.hooks.push(closerHook);
chunkHookCount++;
}
this.tasks.push({
data,
node,
nodePath: {
explicit: partPath.explicit
? prevNodePath.explicit.concat(partPath.explicit)
: prevNodePath.explicit,
implicit: partPath.implicit
? prevNodePath.implicit.concat(partPath.implicit)
: prevNodePath.implicit
},
closerHook
});
chunkTaskIndex++;
}
}
playHooks(closerHook, rejection) {
if (!rejection && closerHook.branchTaskIndex !== this.tasks.length) {
return (null);
}
let currentHook = closerHook;
while (currentHook) {
const result = rejection
? currentHook.onReject(rejection)
: currentHook.onAccept();
switch (result.action) {
case "REJECT":
this.tasks.length = currentHook.branchTaskIndex;
this.hooks.length = currentHook.branchHookIndex;
rejection = {
issuerTask: currentHook.sourceTask,
code: result.code
};
break;
case "CANCEL":
if (result.target === "CHUNK") {
this.tasks.length = currentHook.chunkTaskIndex;
this.hooks.length = currentHook.chunkHookIndex;
}
else if (result.target === "BRANCH") {
this.tasks.length = currentHook.branchTaskIndex;
this.hooks.length = currentHook.branchHookIndex;
}
return (null);
}
if (rejection || currentHook.chunkHookIndex === 0)
break;
currentHook = this.hooks[currentHook.chunkHookIndex];
}
return (rejection);
}
}
function checker(managers, rootNode, rootData) {
const { formats, events } = managers;
const stack = new CheckerStack(rootNode, rootData);
let rejection = null;
while (stack.tasks.length) {
const currentTask = stack.tasks.pop();
const { data, node, closerHook } = currentTask;
const format = formats.get(node.type);
const chunkTasks = [];
const code = format.check(chunkTasks, node, data);
if (chunkTasks.length) {
stack.pushChunk(currentTask, chunkTasks);
}
if (code) {
rejection = { issuerTask: currentTask, code };
}
if (closerHook) {
rejection = stack.playHooks(closerHook, rejection);
}
if (rejection)
break;
}
if (rejection) {
const rejectionInstance = new SchemaDataRejection(rootData, rootNode, rejection.code, rejection.issuerTask.data, rejection.issuerTask.node, rejection.issuerTask.nodePath);
events.emit("DATA_REJECTED", rejectionInstance);
return ({
success: false,
rejection: rejectionInstance,
admission: null
});
}
const admissionInstance = new SchemaDataAdmission(rootData, rootNode);
events.emit("DATA_ADMITTED", admissionInstance);
return ({
success: true,
rejection: null,
admission: admissionInstance
});
}
function getInternalTag(target) {
return (Object.prototype.toString.call(target).slice(8, -1));
}
var objectHelpers = /*#__PURE__*/Object.freeze({
__proto__: null,
getInternalTag: getInternalTag
});
function convertBase16ToBase32(input, base32, padding = true) {
const totalChunksLength = Math.floor(input.length / 10) * 10;
let output = "";
let i = 0;
while (i < totalChunksLength) {
const decHigh = parseInt(input.slice(i, i + 5), 16);
const decLow = parseInt(input.slice(i + 5, i + 10), 16);
output += base32[((decHigh >> 15) & 31)]
+ base32[((decHigh >> 10) & 31)]
+ base32[((decHigh >> 5) & 31)]
+ base32[(decHigh & 31)]
+ base32[((decLow >> 15) & 31)]
+ base32[((decLow >> 10) & 31)]
+ base32[((decLow >> 5) & 31)]
+ base32[(decLow & 31)];
i += 10;
}
if (i < input.length) {
const restChunk = input.slice(i, i + 5);
// 4469248 = 00100 01000 01100 10000 00000 = 4 8 12 16 0
const leftShift = (4469248 >> (restChunk.length * 5)) & 31;
const decHigh = parseInt(restChunk, 16) << leftShift;
output += base32[((decHigh >> 15) & 31)]
+ base32[((decHigh >> 10) & 31)];
if (leftShift < 12) {
output += base32[((decHigh >> 5) & 31)]
+ base32[(decHigh & 31)];
}
}
if (i + 5 < input.length) {
const restChunk = input.slice(i + 5, i + 10);
// 4469248 = 00100 01000 01100 10000 00000 = 4 8 12 16 0
const leftShift = (4469248 >> (restChunk.length * 5)) & 31;
const decLow = parseInt(restChunk, 16) << leftShift;
output += base32[((decLow >> 15) & 31)]
+ base32[((decLow >> 10) & 31)];
if (leftShift < 12)
output += base32[((decLow >> 5) & 31)];
if (leftShift < 8)
output += base32[(decLow & 31)];
}
while (padding && output.length % 8 !== 0) {
output += '=';
}
return (output);
}
function convertBase16ToBase64(input, base64, padding) {
const totalChunksLength = Math.floor(input.length / 6) * 6;
let output = "";
let i = 0;
while (i < totalChunksLength) {
const dec = parseInt(input.slice(i, i + 6), 16);
output += (base64[((dec >> 18) & 63)]
+ base64[((dec >> 12) & 63)]
+ base64[((dec >> 6) & 63)]
+ base64[(dec & 63)]);
i += 6;
}
if (i < input.length) {
const restChunk = input.slice(i, i + 6);
// 143016576 = 00100 01000 01100 10000 10100 00000 = 4 8 12 16 20 0
const leftShift = (143016576 >> (restChunk.length * 5)) & 31;
const dec = parseInt(restChunk, 16) << leftShift;
output += base64[((dec >> 18) & 63)]
+ base64[((dec >> 12) & 63)];
if (leftShift < 12)
output += base64[((dec >> 6) & 63)];
if (leftShift < 8)
output += base64[(dec & 63)];
}
while (padding && output.length % 4 !== 0) {
output += '=';
}
return (output);
}
function convertBase32ToBase16(input, base32) {
if (input.endsWith("="))
input = input.slice(0, input.indexOf("="));
const totalChunksLength = Math.floor(input.length / 8) * 8;
const base16 = "0123456789ABCDEF";
let output = "";
let i = 0;
while (i < totalChunksLength) {
const dec = (base32.indexOf(input[i]) << 15)
| (base32.indexOf(input[i + 1]) << 10)
| (base32.indexOf(input[i + 2]) << 5)
| base32.indexOf(input[i + 3]);
output += base16[((dec >> 16) & 15)]
+ base16[((dec >> 12) & 15)]
+ base16[((dec >> 8) & 15)]
+ base16[((dec >> 4) & 15)]
+ base16[(dec & 15)];
i += 4;
}
if (i < input.length) {
const rest = input.slice(i);
const restLength = rest.length;
const dec = ((base32.indexOf(rest[0]) << 15)
| (rest[1] ? base32.indexOf(rest[1]) << 10 : 0)
| (rest[2] ? base32.indexOf(rest[2]) << 5 : 0)
| (rest[3] ? base32.indexOf(rest[3]) : 0));
output += base16[((dec >> 16) & 15)]
+ base16[((dec >> 12) & 15)];
if (restLength > 1) {
output += base16[((dec >> 8) & 15)]
+ base16[((dec >> 4) & 15)];
}
if (restLength > 3) {
output += base16[(dec & 15)];
if (i + 5 >= input.length) {
output += base16[0];
}
}
}
if (i + 5 < input.length) {
const rest = input.slice(i + 5);
const restLength = rest.length;
const dec = ((base32.indexOf(rest[0]) << 15)
| (rest[1] ? base32.indexOf(rest[1]) << 10 : 0)
| (rest[2] ? base32.indexOf(rest[2]) << 5 : 0)
| (rest[3] ? base32.indexOf(rest[3]) : 0));
output += base16[((dec >> 16) & 15)]
+ base16[((dec >> 12) & 15)]
+ base16[((dec >> 8) & 15)];
if (restLength > 2) {
output += base16[((dec >> 4) & 15)]
+ base16[(dec & 15)];
}
}
return (output);
}
function convertBase64ToBase16(input, base64) {
if (input.endsWith("="))
input = input.slice(0, input.indexOf("="));
const totalChunksLength = Math.floor(input.length / 4) * 4;
const base16 = "0123456789ABCDEF";
let output = "";
let i = 0;
while (i < totalChunksLength) {
const dec = (base64.indexOf(input[i]) << 18)
| (base64.indexOf(input[i + 1]) << 12)
| (base64.indexOf(input[i + 2]) << 6)
| base64.indexOf(input[i + 3]);
output += base16[((dec >> 20) & 15)]
+ base16[((dec >> 16) & 15)]
+ base16[((dec >> 12) & 15)]
+ base16[((dec >> 8) & 15)]
+ base16[((dec >> 4) & 15)]
+ base16[(dec & 15)];
i += 4;
}
if (i < input.length) {
const rest = input.slice(i);
const restLength = rest.length;
const dec = ((base64.indexOf(rest[0]) << 18)
| (rest[1] ? base64.indexOf(rest[1]) << 12 : 0)
| (rest[2] ? base64.indexOf(rest[2]) << 6 : 0)
| (rest[3] ? base64.indexOf(rest[3]) : 0));
output += base16[((dec >> 20) & 15)]
+ base16[((dec >> 16) & 15)];
if (restLength > 2) {
output += base16[((dec >> 12) & 15)]
+ base16[((dec >> 8) & 15)];
}
if (restLength > 3) {
output += base16[((dec >> 4) & 15)]
+ base16[(dec & 15)];
}
}
return (output);
}
const base32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
const base32Hex = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
const base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const base64Url = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
function base16ToBase32(str, to = "B32", padding = true) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
if (typeof to !== "string") {
throw new Error("The 'to' argument must be of type string.");
}
if (typeof padding !== "boolean") {
throw new Error("The 'string' argument must be of type boolean.");
}
if (to === "B32")
return (convertBase16ToBase32(str, base32, padding));
if (to === "B32HEX")
return (convertBase16ToBase32(str, base32Hex, padding));
throw new Error("The 'to' argument must be a known string.");
}
function base16ToBase64(str, to = "B64", padding = true) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
if (typeof to !== "string") {
throw new Error("The 'to' argument must be of type string.");
}
if (typeof padding !== "boolean") {
throw new Error("The 'string' argument must be of type boolean.");
}
if (to === "B64")
return (convertBase16ToBase64(str, base64, padding));
if (to === "B64URL")
return (convertBase16ToBase64(str, base64Url, padding));
throw new Error("The 'to' argument must be a known string.");
}
function base32ToBase16(str, from = "B32") {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
if (typeof from !== "string") {
throw new Error("The 'to' argument must be of type string.");
}
if (from === "B32")
return (convertBase32ToBase16(str, base32));
if (from === "B32HEX")
return (convertBase32ToBase16(str, base32Hex));
throw new Error("The 'from' argument must be a known string.");
}
function base64ToBase16(str, from = "B64") {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
if (typeof from !== "string") {
throw new Error("The 'to' argument must be of type string.");
}
if (from === "B64")
return (convertBase64ToBase16(str, base64));
if (from === "B64URL")
return (convertBase64ToBase16(str, base64Url));
throw new Error("The 'from' argument must be a known string.");
}
var stringHelpers = /*#__PURE__*/Object.freeze({
__proto__: null,
base16ToBase32: base16ToBase32,
base16ToBase64: base16ToBase64,
base32ToBase16: base32ToBase16,
base64ToBase16: base64ToBase16
});
const helpers = {
object: objectHelpers,
string: stringHelpers
};
function isObject(x) {
return (x !== null && typeof x === "object");
}
/**
* A plain object is considered as follows:
* - It must not be null.
* - It must be an object.
* - It must have a prototype of `Object.prototype` or `null`.
*/
function isPlainObject(x) {
if (x === null || typeof x !== "object")
return (false);
const prototype = Object.getPrototypeOf(x);
return (prototype === null || prototype === Object.prototype);
}
// ARRAY
function isArray(x) {
return (Array.isArray(x));
}
/**
* A typed array is considered as follows:
* - It must be a view on an ArrayBuffer.
* - It must not be a `DataView`.
*/
function isTypedArray(x) {
return (ArrayBuffer.isView(x) && !(x instanceof DataView));
}
// FUNCTION
function isFunction(x) {
return (getInternalTag(x) === "Function");
}
function isAsyncFunction(x) {
return (getInternalTag(x) === "AsyncFunction");
}
function isGeneratorFunction(x) {
return (getInternalTag(x) === "GeneratorFunction");
}
function isAsyncGeneratorFunction(x) {
return (getInternalTag(x) === "AsyncGeneratorFunction");
}
var objectTesters = /*#__PURE__*/Object.freeze({
__proto__: null,
isArray: isArray,
isAsyncFunction: isAsyncFunction,
isAsyncGeneratorFunction: isAsyncGeneratorFunction,
isFunction: isFunction,
isGeneratorFunction: isGeneratorFunction,
isObject: isObject,
isPlainObject: isPlainObject,
isTypedArray: isTypedArray
});
/**
* Check if all characters in the string are part of the ASCII table.
*
* An empty string will return `false`.
*/
function isAscii(str, options) {
return (RegExp("^[\\x00-\\x7F]+$").test(str));
}
/*
Composition :
DIGIT = %x30-39
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
hexOctet = HEXDIG HEXDIG
uuid = 4*4hexOctet "-"
2*2hexOctet "-"
2*2hexOctet "-"
2*2hexOctet "-"
6*6hexOctet
Sources :
RFC 9562 Section 4 : DIGIT
HEXDIG
hexOctet
UUID -> uuid
Links :
https://datatracker.ietf.org/doc/html/rfc9562#section-4
*/
const extractUuidVersionRegex = new RegExp("^[0-9A-F]{8}-[0-9A-F]{4}-([1-7])[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$", "i");
/**
* **Standard :** RFC 9562
*
* @version 1.0.0
*/
function isUuid(str, options) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
if (options !== undefined) {
if (typeof options !== "object") {
throw new Error("The 'options' argument must be of type object.");
}
if (options?.version !== undefined) {
if (typeof options.version !== "number") {
throw new Error("The 'cidr' property of the 'options' argument must be of type number.");
}
if (options.version < 1 || options.version > 7) {
throw new Error("The 'cidr' property of the 'options' argument must be a number between 1 and 7.");
}
}
}
const execResult = extractUuidVersionRegex.exec(str);
if (!execResult || !execResult[1])
return (false);
if (!options?.version || (execResult[1].codePointAt(0) - 48) === options?.version)
return (true);
return (false);
}
function weakly(callback) {
let ref = null;
return (() => {
if (!ref) {
const obj = callback();
ref = new WeakRef(obj);
return (obj);
}
const value = ref.deref();
if (!value) {
const obj = callback();
ref = new WeakRef(obj);
return (obj);
}
return (value);
});
}
/**
* @see https://www.garykessler.net/library/file_sigs.html
* @see https://en.wikipedia.org/wiki/List_of_file_signatures
*
const signatures = [
// Image
{ ext: "png" as const, offset: 0, flags: ["89504E470D0A1A0A"]},
{ ext: "jpg" as const, offset: 0, flags: ["FFD8FFE0"]},
{ ext: "jp2" as const, offset: 0, flags: ["0000000C6A5020200D0A870A"]},
{ ext: "gif" as const, offset: 0, flags: ["474946383761", "474946383961"]},
{ ext: "webp" as const, offset: 0, flags: ["52494646????????57454250"]},
// Audio
{ ext: "mp3" as const, offset: 0, flags: ["FFFB", "FFF3", "FFF2", "494433"]},
{ ext: "mp4" as const, offset: 4, flags: ["6674797069736F6D", "667479704D534E56"]},
// 3D
{ ext: "stl" as const, offset: 4, flags: ["736F6C6964"]}
];
export function hasFileSignature(hex: string, extensions: Array<(typeof signatures)[number]['ext']>) {
for (let i = 0; i < extensions.length; i++) {
const { offset, flags } = signatures.find(({ ext }) => ext === extensions[i])!;
for (let i = 0; i < flags.length; i++) {
const flag = flags[i];
let j = (flag.length - 1) + offset;
if (j >= hex.length) continue;
while (j >= 0) {
if (flag[j] !== "?" && hex[j] !== flag[j]) break;
j--;
}
if (j === 0) return (true);
}
}
}*/
/**
# IPV4
Composition :
dec-octet = 1*3DIGIT ; Representing a decimal integer value in the range 0 through 255
suffixe = 1*2DIGIT ; Representing a decimal integer value in the range 0 through 32.
IPv4 = dec-octet 3("." dec-octet) ["/" suffixe]
# IPV6
Composition :
HEXDIG = DIGIT / A-F / a-f
IPv6-full = 1*4HEXDIG 7(":" 1*4HEXDIG)
IPv6-comp = [1*4HEXDIG *5(":" 1*4HEXDIG)] "::" [1*4HEXDIG *5(":" 1*4HEXDIG)]
IPv6v4-full = 1*4HEXDIG 5(":" 1*4HEXDIG) ":" IPv4
IPv6v4-comp = [1*4HEXDIG *3(":" 1*4HEXDIG)] "::" [1*4HEXDIG *3(":" 1*4HEXDIG) ":"] IPv4
suffixe = 1*3DIGIT ; Representing a decimal integer value in the range 0 through 128.
IPv6 = (IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp) ["/" suffixe]
*/
const ipV4Seg = "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])";
const ipV4Pattern = `(?:${ipV4Seg}\\.){3}${ipV4Seg}`;
const ipV4Regex = new RegExp(`^${ipV4Pattern}$`);
const ipV4WithCidrExpectedRegex = weakly(() => new RegExp(`^${ipV4Pattern}/(3[0-2]|[12]?[0-9])$`));
const ipV4WithCidrAcceptedRegex = weakly(() => new RegExp(`^${ipV4Pattern}(?:/(3[0-2]|[12]?[0-9]))?$`));
const ipV6Seg = "(?:[0-9a-fA-F]{1,4})";
const ipV6Pattern = "(?:" +
`(?:${ipV6Seg}:){7}(?:${ipV6Seg}|:)|` +
`(?:${ipV6Seg}:){6}(?:${ipV4Pattern}|:${ipV6Seg}|:)|` +
`(?:${ipV6Seg}:){5}(?::${ipV4Pattern}|(?::${ipV6Seg}){1,2}|:)|` +
`(?:${ipV6Seg}:){4}(?:(?::${ipV6Seg}){0,1}:${ipV4Pattern}|(?::${ipV6Seg}){1,3}|:)|` +
`(?:${ipV6Seg}:){3}(?:(?::${ipV6Seg}){0,2}:${ipV4Pattern}|(?::${ipV6Seg}){1,4}|:)|` +
`(?:${ipV6Seg}:){2}(?:(?::${ipV6Seg}){0,3}:${ipV4Pattern}|(?::${ipV6Seg}){1,5}|:)|` +
`(?:${ipV6Seg}:){1}(?:(?::${ipV6Seg}){0,4}:${ipV4Pattern}|(?::${ipV6Seg}){1,6}|:)|` +
`(?::(?:(?::${ipV6Seg}){0,5}:${ipV4Pattern}|(?::${ipV6Seg}){1,7}|:)))`;
const ipV6Regex = new RegExp(`^${ipV6Pattern}$`);
const ipV6WithCidrExpectedRegex = weakly(() => new RegExp(`^${ipV6Pattern}/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$`));
const ipV6WithCidrAcceptedRegex = weakly(() => new RegExp(`^${ipV6Pattern}(?:/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))?$`));
function checkArguments(str, options) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
if (options !== undefined) {
if (typeof options !== "object") {
throw new Error("The 'options' argument must be of type object.");
}
if (options.cidr !== undefined && typeof options.cidr !== "string") {
throw new Error("The 'cidr' property of the 'options' argument must be of type string.");
}
}
}
/**
* **Standard:** No standard
*
* @version 2.0.0
*/
function isIp(str, options) {
checkArguments(str, options);
if (options?.cidr === undefined || options.cidr === "reject") {
if (ipV4Regex.test(str))
return (true);
if (ipV6Regex.test(str))
return (true);
}
else if (options.cidr === "expect") {
if (ipV4WithCidrExpectedRegex().test(str))
return (true);
if (ipV6WithCidrExpectedRegex().test(str))
return (true);
}
else if (options.cidr === "accept") {
if (ipV4WithCidrAcceptedRegex().test(str))
return (true);
if (ipV6WithCidrAcceptedRegex().test(str))
return (true);
}
else {
throw new Error("The 'options.cidr' property must be a known string.");
}
return (false);
}
/**
* **Standard:** No standard
*
* @version 2.0.0
*/
function isIpV4(str, options) {
checkArguments(str, options);
if (options?.cidr === undefined || options.cidr === "reject") {
if (ipV4Regex.test(str))
return (true);
}
else if (options.cidr === "expect") {
if (ipV4WithCidrExpectedRegex().test(str))
return (true);
}
else if (options.cidr === "accept") {
if (ipV4WithCidrAcceptedRegex().test(str))
return (true);
}
else {
throw new Error("The 'options.cidr' property must be a known string.");
}
return (false);
}
/**
* **Standard:** No standard
*
* @version 2.0.0
*/
function isIpV6(str, options) {
checkArguments(str, options);
if (options?.cidr === undefined || options.cidr === "reject") {
if (ipV6Regex.test(str))
return (true);
}
else if (options.cidr === "expect") {
if (ipV6WithCidrExpectedRegex().test(str))
return (true);
}
else if (options.cidr === "accept") {
if (ipV6WithCidrAcceptedRegex().test(str))
return (true);
}
else {
throw new Error("The 'options.cidr' property must be a known string.");
}
return (false);
}
/*
Composition :
letter = %d65-%d90 / %d97-%d122; A-Z / a-z
digit = %x30-39; 0-9
label = letter [*(digit / letter / "-") digit / letter]
domain = label *("." label)
Links :
https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.1
*/
const domainRegex = new RegExp("^[A-Za-z](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\\.[A-Za-z](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*$");
/**
* **Standard :** RFC 1035
*
* @version 1.0.0
*/
function isDomain(str, options) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
return (domainRegex.test(str));
}
/*
Composition :
atom = 1*atext
dot-local = atom *("." atom)
quoted-local = DQUOTE *QcontentSMTP DQUOTE
ip-address = IPv4-address-literal / IPv6-address-literal
general-address = General-address-literal
local = dot-local / quote-local
domain = Domain
address = ip-address / general-address
mailbox = local "@" (domain / address)
Sources :
RFC 5234 Appendix B.1 : DQUOTE
RFC 5322 Section 3.2.3 : atext
RFC 5321 Section 4.1.3 : IPv4-address-literal
IPv6-address-literal
General-address-literal
RFC 5321 Section 4.1.2 : QcontentSMTP
Domain
Links :
https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1
https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.3
https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.3
https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.2
*/
const localDotPattern = "(?:[-!=?A-B\\x23-\\x27\\x2A-\\x2B\\x2F-\\x39\\x5E-\\x7E]+(?:\\.[-!=?A-B\\x23-\\x27\\x2A-\\x2B\\x2F-\\x39\\x5E-\\x7E]+)*)";
const localQuotePattern = "(?:\"(?:[\\x20-\\x21\\x23-\\x5B\\x5D-\\x7E]|\\\\[\\x20-\\x7E])*\")";
const localDotRegex = new RegExp(`^${localDotPattern}$`);
const localDotOrLocalQuoteRegex = weakly(() => new RegExp(`^(?:${localDotPattern}|${localQuotePattern})$`));
const ipAddressRegex = weakly(() => new RegExp(`^\\[(?:IPv6:${ipV6Pattern}|${ipV4Pattern})\\]$`));
const generalAddressRegex = weakly(() => new RegExp(`(?:[a-zA-Z0-9-]*[a-zA-Z0-9]+:[\\x21-\\x5A\\x5E-\\x7E]+)`));
function parseEmail(str) {
const length = str.length;
let i = 0;
// EXTRACT LOCAL
const localStart = i;
if (str[localStart] === "\"") {
while (++i < length) {
if (str[i] === "\\")
i++;
else if (str[i] === "\"") {
i++;
break;
}
}
}
else {
while (i < length && str[i] !== "@")
i++;
}
if (i === localStart || str[i] !== "@")
return (null);
const localEnd = i;
// EXTRACT DOMAIN
const domainStart = ++i;
const domainEnd = length;
if (domainStart === domainEnd)
return (null);
return ({
local: str.slice(localStart, localEnd),
domain: str.slice(domainStart, domainEnd)
});
}
function validateLocal(str, options) {
if (localDotRegex.test(str))
return (true);
if (options?.allowLocalQuote
&& localDotOrLocalQuoteRegex().test(str))
return (true);
return (false);
}
function validateDomain(str, options) {
if (isDomain(str))
return (true);
if (options?.allowIpAddress
&& ipAddressRegex().test(str))
return (true);
if (options?.allowGeneralAddress
&& generalAddressRegex().test(str))
return (true);
return (false);
}
/**
* **Standard :** RFC 5321
*
* @version 2.0.0
*/
function isEmail(str, options) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
if (options !== undefined) {
if (typeof options !== "object") {
throw new Error("The 'options' argument must be of type object.");
}
if (options.allowLocalQuote !== undefined && typeof options.allowLocalQuote !== "boolean") {
throw new Error("The 'allowLocalQuote' property of the 'options' argument must be of type boolean.");
}
if (options.allowIpAddress !== undefined && typeof options.allowIpAddress !== "boolean") {
throw new Error("The 'allowIpAddress' property of the 'options' argument must be of type boolean.");
}
if (options.allowGeneralAddress !== undefined && typeof options.allowGeneralAddress !== "boolean") {
throw new Error("The 'allowGeneralAddress' property of the 'options' argument must be of type boolean.");
}
}
const email = parseEmail(str);
if (!email)
return (false);
// VALIDATE LOCAL
if (!validateLocal(email.local, options))
return (false);
// VALIDATE DOMAIN
if (!validateDomain(email.domain, options))
return (false);
// RFC 5321 4.5.3.1.2 : Length restriction
if (!email.domain.length || email.domain.length > 255)
return (false);
return (true);
}
/*
Composition :
data = pchar
value = value
token = restricted-name
mediatype = [token "/" token] *(";" token "=" value)
dataurl = "data:" [mediatype] [";base64"] "," data
Sources :
RFC 3986 Section 3.3 : pchar
RFC 2045 Section 5.1 : value
RFC 6838 Section 4.2 : restricted-name
Links :
https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
https://datatracker.ietf.org/doc/html/rfc6838#section-4.2
https://datatracker.ietf.org/doc/html/rfc2397#section-3
*/
const paramTokenPattern = "[a-zA-Z0-9!#$%&'*+.^_`{|}~-]+";
const paramTokenQuotePattern = "\"[a-zA-Z0-9!#$%&'()*+,./:;<=>?@\[\\\]^_`{|}~-]+\"";
const valueRegex = new RegExp(`^(?:${paramTokenPattern}|${paramTokenQuotePattern})$`);
const tokenRegex = new RegExp(`^[a-zA-Z0-9](?:[a-zA-Z0-9!#$&^/_.+-]{0,125}[a-zA-Z0-9!#$&^/_.-])?$`);
const dataRegex = new RegExp(`^(?:[a-zA-Z0-9._~!$&'()*+,;=:@-]|%[a-zA-Z0-9]{2})*$`);
function parseDataUrl(str) {
const result = {
data: "",
type: "",
subtype: "",
parameters: [],
isBase64: false
};
let i = 0;
if (!str.startsWith("data:"))
return (null);
i += 5;
if (str[i] !== ";" && str[i] !== ",") {
// EXTRACT TYPE
const typeStart = i;
while (str[i] && str[i] !== "/")
i++;
if (!str[i] || typeStart === i)
return (null);
const typeEnd = i;
// EXTRACT SUBTYPE
const subtypeStart = ++i;
while (str[i] && str[i] !== ";" && str[i] !== ",")
i++;
if (!str[i] || subtypeStart === i)
return (null);
const subtypeEnd = i;
result.type = str.slice(typeStart, typeEnd);
result.subtype = str.slice(subtypeStart, subtypeEnd);
}
// EXTRACT PARAMETERS
while (str[i] && str[i] === ";") {
if (str.startsWith(";base64,", i)) {
result.isBase64 = true;
i += 7;
break;
}
const nameStart = ++i;
while (str[i] && str[i] !== "=")
i++;
if (!str[i] || nameStart === i)
return (null);
const nameEnd = i;
const valueStart = ++i;
if (str[valueStart] === "\"") {
while (str[i] && !(str[i - 1] === "\"" && (str[i] === ";" || str[i] === ",")))
i++;
}
else {
while (str[i] && str[i] !== ";" && str[i] !== ",")
i++;
}
if (!str[i] || valueStart === i)
return (null);
const valueEnd = i;
result.parameters.push({
name: str.slice(nameStart, nameEnd),
value: str.slice(valueStart, valueEnd)
});
}
if (str[i] !== ",")
return (null);
i += 1;
// EXTRACT DATA
if (str[i])
result.data = str.slice(i);
return (result);
}
/**
* **Standard :** RFC 2397 (RFC 2045, RFC 6838, RFC 3986)
*
* @version 2.0.0
*/
function isDataUrl(str, options) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
if (options !== undefined) {
if (typeof options !== "object") {
throw new Error("The 'options' argument must be of type object.");
}
if (options.type !== undefined && !isArray(options.type)) {
throw new Error("The 'type' property of the 'options' argument must be of type string.");
}
if (options.subtype !== undefined && !isArray(options.subtype)) {
throw new Error("The 'subtype' property of the 'options' argument must be of type string.");
}
}
const dataUrl = parseDataUrl(str);
if (!dataUrl)
return (false);
if (dataUrl.type || dataUrl.subtype) {
// CHECK TYPE
if (!tokenRegex.test(dataUrl.type))
return (false);
// RFC 6838 4.2: Length restriction
if (dataUrl.type.length > 127)
return (false);
// CHECK SUBTYPE
if (!tokenRegex.test(dataUrl.subtype))
return (false);
// RFC 6838 4.2: Length restriction
if (dataUrl.subtype.length > 127)
return (false);
}
// CHECK PARAMETERS
for (let i = 0; i < dataUrl.parameters.length; i++) {
const parameter = dataUrl.parameters[i];
if (!tokenRegex.test(parameter.name))
return (false);
if (!valueRegex.test(parameter.value))
return (false);
// RFC 6838 4.3: Identical name restriction and case insensitive
const hasIdenticalName = dataUrl.parameters.some(({ name }, j) => j !== i && name.toLowerCase() === name.toLowerCase());
if (hasIdenticalName)
return (false);
}
// CHECK DATA
if (!dataRegex.test(dataUrl.data))
return (false);
if (options?.type) {
const hasValidType = options.type.some(type => type.toLowerCase() === dataUrl.type.toLowerCase());
if (!hasValidType)
return (false);
}
if (options?.subtype) {
const hasValidSubtype = options.subtype.some(subtype => subtype.toLowerCase() === dataUrl.subtype.toLowerCase());
if (!hasValidSubtype)
return (false);
}
return (true);
}
const base16Regex = new RegExp("^(?:[A-F0-9]{2})*$");
const base32Regex = new RegExp("^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}[=]{6}|[A-Z2-7]{4}[=]{4}|[A-Z2-7]{5}[=]{3}|[A-Z2-7]{6}[=]{2}|[A-Z2-7]{7}[=]{1})?$");
const base32HexRegex = weakly(() => new RegExp("^(?:[0-9A-V]{8})*(?:[0-9A-V]{2}[=]{6}|[0-9A-V]{4}[=]{4}|[0-9A-V]{5}[=]{3}|[0-9A-V]{6}[=]{2}|[0-9A-V]{7}[=]{1})?$"));
const base64Regex = new RegExp("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}[=]{2}|[A-Za-z0-9+/]{3}[=]{1})?$");
const base64UrlRegex = weakly(() => new RegExp("^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}[=]{2}|[A-Za-z0-9_-]{3}[=]{1})?$"));
/**
* **Standard :** RFC 4648
*
* @see https://datatracker.ietf.org/doc/html/rfc4648#section-8
*
* @version 1.0.0
*/
function isBase16(str, options) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
return (str.length % 2 === 0 && base16Regex.test(str));
}
/**
* **Standard :** RFC 4648
*
* @see https://datatracker.ietf.org/doc/html/rfc4648#section-6
*
* @version 1.0.0
*/
function isBase32(str, options) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
return (str.length % 8 === 0 && base32Regex.test(str));
}
/**
* **Standard :** RFC 4648
*
* @see https://datatracker.ietf.org/doc/html/rfc4648#section-7
*
* @version 1.0.0
*/
function isBase32Hex(str, options) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
return (str.length % 8 === 0 && base32HexRegex().test(str));
}
/**
* **Standard :** RFC 4648
*
* @see https://datatracker.ietf.org/doc/html/rfc4648#section-4
*
* @version 1.0.0
*/
function isBase64(str, options) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
return (str.length % 4 == 0 && base64Regex.test(str));
}
/**
* **Standard :** RFC 4648
*
* @see https://datatracker.ietf.org/doc/html/rfc4648#section-5
*
* @version 1.0.0
*/
function isBase64Url(str, options) {
if (typeof str !== "string") {
throw new Error("The 'str' argument must be of type string.");
}
return (str.length % 4 === 0 && base64UrlRegex().test(str));
}
var stringTesters$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
isAscii: isAscii,
isBase16: isBase16,
isBase32: isBase32,
isBase32Hex: isBase32Hex,
isBase64: isBase64,
isBase64Url: isBase64Url,
isDataUrl: isDataUrl,
isDomain: isDomain,
isEmail: isEmail,
isIp: isIp,
isIpV4: isIpV4,
isIpV6: isIpV6,
isUuid: isUuid
});
const testers = {
object: objectTesters,
string: stringTesters$1
};
/**
* Clones the object starting from the root and stops traversing a branch
* when a mounted criteria node is encountered. In such cases, the mounted
* object encountered see its internal properties copied to a new reference
* so that the junction is a unique reference in the tree.
*
* @param src Source object of the clone
* @returns Clone of the source object
*/
function cloner(rootSrc) {
let rootCpy = {};
let stack = [{
src: rootSrc,
cpy: rootCpy
}];
while (stack.length > 0) {
let { src, cpy } = stack.pop();
if (isPlainObject(src)) {
if (hasNodeSymbol(src)) {
cpy = { ...src };
}
else {
const keys = Reflect.ownKeys(src);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (isPlainObject(src[key])) {
if (hasNodeSymbol(src[key])) {
cpy[key] = { ...src[key] };
}
else {
cpy[key] = {};
stack.push({
src: src[key],
cpy: cpy[key]
});
}
}
else if (isArray(src[key])) {
cpy[key] = [];
stack.push({
src: src[key],
cpy: cpy[key]
});
}
else {
cpy[key] = src[key];
}
}
}
}
else if (isArray(src)) {
for (let i = 0; i < src.length; i++) {
const index = i;
if (isPlainObject(src[index])) {
if (hasNodeSymbol(src[index])) {
cpy[i] = { ...src[index] };
}
else {
cpy[i] = {};
stack.push({
src: src[index],
cpy: cpy[index]
});
}
}
else if (isArray(src[index])) {
cpy[index] = [];
stack.push({
src: src[index],
cpy: cpy[index]
});
}
else {
cpy[index] = src[index];
}
}
}
else {
cpy = src;
}
}
return rootCpy;
}
const UndefinedFormat = {
type: "undefined",
exceptions: {},
mount(chunk, criteria) {
return (null);
},
check(chunk, criteria, value) {
if (value !== undefined) {
return ("TYPE_UNDEFINED_UNSATISFIED");
}
return (null);
}
};
const FunctionFormat = {
type: "function",
exceptions: {
NATURE_PROPERTY_MISDECLARED: "The 'nature' property must be of type string.",
NATURE_PROPERTY_STRING_MISCONFIGURED: "The 'nature' property must be a known string.",
NATURE_PROPERTY_ARRAY_MISCONFIGURED: "The array of the 'nature' must have a number of items greater than 0.",
NATURE_PROPERTY_ARRAY_ITEM_MISDECLARED: "The array items of the 'nature' property must be of type string.",
NATURE_PROPERTY_ARRAY_ITEM_MISCONFIGURED: "The array items of the 'nature' property must be known strings."
},
natureBitflags: {
BASIC: 1 << 1,
ASYNC: 1 << 2,
BASIC_GENERATOR: 1 << 3,
ASYNC_GENERATOR: 1 << 4
},
tagBitflags: {
Function: 1 << 1,
AsyncFunction: 1 << 2,
GeneratorFunction: 1 << 3,
AsyncGeneratorFunction: 1 << 4
},
mount(chunk, criteria) {
const { nature } = criteria;
if (nature !== undefined) {
if (typeof nature == "string") {
if (!(nature in this.natureBitflags)) {
return ("NATURE_PROPERTY_STRING_MISCONFIGURED");
}
}
else if (isArray(nature)) {
if (nature.length < 1) {
return ("NATURE_PROPERTY_ARRAY_MISCONFIGURED");
}
for (const item of nature) {
if (typeof item !== "string") {
return ("NATURE_PROPERTY_ARRAY_ITEM_MISCONFIGURED");
}
if (!(item in this.natureBitflags)) {
return ("NATURE_PROPERTY_ARRAY_ITEM_MISCONFIGURED");
}
}
}
else {
return ("NATURE_PROPERTY_MISDECLARED");
}
}
if (isArray(nature)) {
Object.assign(criteria, {
natureBitcode: nature.reduce((code, key) => (code | this.natureBitflags[key]), 0)
});
}
else {
Object.assign(criteria, {
natureBitcode: nature
? this.natureBitflags[nature]
: 0
});
}
return (null);
},
check(chunk, criteria, value) {
if (typeof value !== "function") {
return ("TYPE_FUNCTION_UNSATISFIED");
}
const { natureBitcode } = criteria;
const { tagBitflags } = this;
if (natureBitcode) {
const tag = getInternalTag(value);
const tagBitflag = tagBitflags[tag];
if (!tagBitflag || !(natureBitcode & tagBitflag)) {
return ("NATURE_UNSATISFIED");
}
}
return (null);
}
};
const BooleanFormat = {
type: "boolean",
exceptions: {
LITERAL_PROPERTY_MISDECLARED: "The 'literal' property must be of type boolean."
},
mount(chunk, criteria) {
const { literal } = criteria;
if (literal !== undefined && typeof literal !== "boolean") {
return ("LITERAL_PROPERTY_MISDECLARED");
}
return (null);
},
check(chunk, criteria, value) {
if (typeof value !== "boolean") {
return ("TYPE_BOOLEAN_UNSATISFIED");
}
const { literal } = criteria;
if (literal !== undefined && literal !== value) {
return ("LITERAL_UNSATISFIED");
}
return (null);
}
};
const UnknownFormat = {
type: "unknown",
exceptions: {},
mount(chunk, criteria) {
return (null);
},
check(chunk, criteria, value) {
return (null);
}
};
const SymbolFormat = {
type: "symbol",
exceptions: {
LITERAL_PROPERTY_MISDECLARED: "The 'literal' property must be of type symbol, array or plain object.",
LITERAL_PROPERTY_ARRAY_MISCONFIGURED: "The array of the 'literal' property must have a number of items greater than 0.",
LITERAL_PROPERTY_ARRAY_ITEM_MISDECLARED: "The array items of the 'literal' property must be of type symbol.",
LITERAL