mailschema 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -119
- package/bin/mailschema.js +40 -53
- package/dist/context.jsonld +50 -0
- package/dist/contract.schema.json +206 -0
- package/dist/core/artifacts.d.ts +11 -36
- package/dist/core/artifacts.js +21 -69
- package/dist/core/bundled.d.ts +4 -4
- package/dist/core/bundled.js +7 -7
- package/dist/core/contract.d.ts +59 -56
- package/dist/core/contract.js +224 -298
- package/dist/core/description.d.ts +45 -0
- package/dist/core/description.js +47 -0
- package/dist/core/implementation.d.ts +32 -0
- package/dist/core/implementation.js +25 -0
- package/dist/core/index.d.ts +7 -6
- package/dist/core/index.js +9 -9
- package/dist/core/json.d.ts +23 -0
- package/dist/core/{document.js → json.js} +61 -51
- package/dist/core/patterns.d.ts +12 -0
- package/dist/core/patterns.js +165 -0
- package/dist/core/sha256.d.ts +2 -0
- package/dist/core/sha256.js +59 -0
- package/dist/core.schema.json +210 -0
- package/dist/implementation.schema.json +173 -0
- package/dist/index.d.ts +4 -9
- package/dist/index.js +2 -14
- package/dist/profile.json +27 -0
- package/package.json +9 -10
- package/dist/contribution.schema.json +0 -874
- package/dist/core/binding.d.ts +0 -24
- package/dist/core/binding.js +0 -50
- package/dist/core/document.d.ts +0 -24
- package/dist/core/documents.d.ts +0 -68
- package/dist/core/documents.js +0 -145
- package/dist/core/limits.d.ts +0 -19
- package/dist/core/limits.js +0 -55
- package/dist/core/pointer.d.ts +0 -12
- package/dist/core/pointer.js +0 -48
- package/dist/core/schemas.d.ts +0 -21
- package/dist/core/schemas.js +0 -125
- package/dist/core/types.d.ts +0 -127
- package/dist/core/types.js +0 -2
- package/dist/forms-0.1.schema.json +0 -329
- package/dist/map-0.2.jsonld +0 -91
- package/dist/map-0.2.schema.json +0 -1033
- package/dist/map.d.ts +0 -8
- package/dist/map.js +0 -18
- package/dist/model.d.ts +0 -95
- package/dist/model.js +0 -5
- package/dist/type-contract-0.2.schema.json +0 -179
- package/dist/validation.d.ts +0 -15
- package/dist/validation.js +0 -64
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface ImplementationRecord {
|
|
2
|
+
service: string;
|
|
3
|
+
maintainer: {
|
|
4
|
+
name: string;
|
|
5
|
+
url: string;
|
|
6
|
+
};
|
|
7
|
+
type: {
|
|
8
|
+
id: string;
|
|
9
|
+
version: string;
|
|
10
|
+
contractDigest: string;
|
|
11
|
+
};
|
|
12
|
+
operations: string[];
|
|
13
|
+
binding: string;
|
|
14
|
+
artifact?: {
|
|
15
|
+
url: string;
|
|
16
|
+
digest: string;
|
|
17
|
+
digestMode: 'canonical-json' | 'bytes';
|
|
18
|
+
};
|
|
19
|
+
format?: string;
|
|
20
|
+
status: 'Draft' | 'Experimental' | 'Stable' | 'Deprecated' | 'Superseded';
|
|
21
|
+
documentation: string;
|
|
22
|
+
evidence: {
|
|
23
|
+
kind: 'declaration' | 'test-report';
|
|
24
|
+
url: string;
|
|
25
|
+
summary: string;
|
|
26
|
+
}[];
|
|
27
|
+
successor?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Why the value is not an implementation record, or no reasons. */
|
|
30
|
+
export declare function implementationErrors(value: unknown): string[];
|
|
31
|
+
/** The record in the bytes or text, or InvalidDocument with every reason it is not one. */
|
|
32
|
+
export declare function parseImplementation(input: string | Uint8Array): ImplementationRecord;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// An implementation record: a Registry declaration or report of a service's support for exact
|
|
2
|
+
// contract versions.
|
|
3
|
+
import { CONTRACT_MAX_BYTES, createValidator, implementationSchema, schemaErrors, } from "./artifacts.js";
|
|
4
|
+
import { InvalidDocument, parse } from "./json.js";
|
|
5
|
+
const validate = createValidator().compile(implementationSchema);
|
|
6
|
+
/** Why the value is not an implementation record, or no reasons. */
|
|
7
|
+
export function implementationErrors(value) {
|
|
8
|
+
if (!validate(value))
|
|
9
|
+
return schemaErrors(validate.errors);
|
|
10
|
+
const errors = [];
|
|
11
|
+
const { origin } = new URL(value.service);
|
|
12
|
+
if (value.service !== origin && value.service !== `${origin}/`)
|
|
13
|
+
errors.push('/service: must be an HTTPS origin, not an operation path');
|
|
14
|
+
if (new Set(value.operations).size !== value.operations.length)
|
|
15
|
+
errors.push('/operations: identifiers must be unique');
|
|
16
|
+
return errors;
|
|
17
|
+
}
|
|
18
|
+
/** The record in the bytes or text, or InvalidDocument with every reason it is not one. */
|
|
19
|
+
export function parseImplementation(input) {
|
|
20
|
+
const value = parse(input, CONTRACT_MAX_BYTES);
|
|
21
|
+
const errors = implementationErrors(value);
|
|
22
|
+
if (errors.length)
|
|
23
|
+
throw new InvalidDocument('The value is not an implementation record.', errors);
|
|
24
|
+
return value;
|
|
25
|
+
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { InvalidDocument, parse, canonicalize, digest } from './
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export
|
|
1
|
+
export { PROFILE, CONTEXT, DESCRIPTION_MEDIA_TYPE, DESCRIPTION_MAX_BYTES, CONTRACT_MAX_BYTES, EFFECTS, FORMATS, coreSchema, contractSchema, implementationSchema, } from './artifacts.ts';
|
|
2
|
+
export { InvalidDocument, MAX_DEPTH, decodeUtf8, parse, canonicalize, digest } from './json.ts';
|
|
3
|
+
export { sha256 } from './sha256.ts';
|
|
4
|
+
export { descriptionErrors, parseDescription, type Description } from './description.ts';
|
|
5
|
+
export { CAPABILITY_KINDS, Contract, contractErrors, type CapabilityKind, type ContractDocument, type Operation, } from './contract.ts';
|
|
6
|
+
export { implementationErrors, parseImplementation, type ImplementationRecord, } from './implementation.ts';
|
|
7
|
+
export { unportablePattern } from './patterns.ts';
|
package/dist/core/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// Mail Action Protocol 0.
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
1
|
+
// Mail Action Protocol 0.3 processing: one implementation, run unchanged by the repository's
|
|
2
|
+
// site and checks and shipped as the npm package.
|
|
3
|
+
export { PROFILE, CONTEXT, DESCRIPTION_MEDIA_TYPE, DESCRIPTION_MAX_BYTES, CONTRACT_MAX_BYTES, EFFECTS, FORMATS, coreSchema, contractSchema, implementationSchema, } from "./artifacts.js";
|
|
4
|
+
export { InvalidDocument, MAX_DEPTH, decodeUtf8, parse, canonicalize, digest } from "./json.js";
|
|
5
|
+
export { sha256 } from "./sha256.js";
|
|
6
|
+
export { descriptionErrors, parseDescription } from "./description.js";
|
|
7
|
+
export { CAPABILITY_KINDS, Contract, contractErrors, } from "./contract.js";
|
|
8
|
+
export { implementationErrors, parseImplementation, } from "./implementation.js";
|
|
9
|
+
export { unportablePattern } from "./patterns.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** A document that is not MAP JSON, or not the document it claims to be. */
|
|
2
|
+
export declare class InvalidDocument extends Error {
|
|
3
|
+
readonly errors: string[];
|
|
4
|
+
constructor(message: string, errors?: string[]);
|
|
5
|
+
}
|
|
6
|
+
export declare const MAX_DEPTH = 32;
|
|
7
|
+
/** The bytes as text, refusing anything but well-formed UTF-8. */
|
|
8
|
+
export declare function decodeUtf8(input: Uint8Array): string;
|
|
9
|
+
/**
|
|
10
|
+
* Parse MAP JSON from its original text: UTF-8 I-JSON (RFC 7493) of at most `maxBytes`, with
|
|
11
|
+
* no byte order mark, duplicate member names, lone surrogates, noncharacters or U+0000, no
|
|
12
|
+
* container deeper than 32 counting the root as 1, and every number a finite token of at most
|
|
13
|
+
* 64 characters within ±(2^53−1) that does not underflow to zero. These rules apply to the
|
|
14
|
+
* original tokens, before a general parser could erase them.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parse(input: string | Uint8Array, maxBytes: number): unknown;
|
|
17
|
+
/**
|
|
18
|
+
* The RFC 8785 canonical form of a JSON value: members sorted by their UTF-16 code units,
|
|
19
|
+
* ECMAScript's minimal string escapes and its number format.
|
|
20
|
+
*/
|
|
21
|
+
export declare function canonicalize(value: unknown): string;
|
|
22
|
+
/** `sha-256:` and the SHA-256 of the value's RFC 8785 form, as MAP identifies contracts. */
|
|
23
|
+
export declare const digest: (value: unknown) => string;
|
|
@@ -1,38 +1,46 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// MAP JSON: I-JSON parsed from its original text, and the RFC 8785 digests that identify
|
|
2
|
+
// contracts and descriptions.
|
|
3
|
+
import { sha256 } from "./sha256.js";
|
|
4
|
+
/** A document that is not MAP JSON, or not the document it claims to be. */
|
|
4
5
|
export class InvalidDocument extends Error {
|
|
6
|
+
errors;
|
|
7
|
+
constructor(message, errors = [message]) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.errors = errors;
|
|
10
|
+
}
|
|
5
11
|
}
|
|
6
|
-
export const MAX_BYTES = 64 * 1024;
|
|
7
12
|
export const MAX_DEPTH = 32;
|
|
13
|
+
const MAX_NUMBER_TOKEN = 64;
|
|
8
14
|
const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
9
|
-
|
|
10
|
-
const
|
|
15
|
+
const NUMBER = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/;
|
|
16
|
+
const ZERO = /^-?0(?:\.0+)?(?:[eE][+-]?[0-9]+)?$/;
|
|
17
|
+
const forbidden = (point) => point === 0 || (point >= 0xfdd0 && point <= 0xfdef) || (point & 0xfffe) === 0xfffe;
|
|
18
|
+
/** The bytes as text, refusing anything but well-formed UTF-8. */
|
|
19
|
+
export function decodeUtf8(input) {
|
|
20
|
+
try {
|
|
21
|
+
// The byte order mark is kept, so parsing refuses it.
|
|
22
|
+
return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(input);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new InvalidDocument('The JSON is not valid UTF-8.');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
11
28
|
/**
|
|
12
|
-
* Parse
|
|
13
|
-
* byte order mark,
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
29
|
+
* Parse MAP JSON from its original text: UTF-8 I-JSON (RFC 7493) of at most `maxBytes`, with
|
|
30
|
+
* no byte order mark, duplicate member names, lone surrogates, noncharacters or U+0000, no
|
|
31
|
+
* container deeper than 32 counting the root as 1, and every number a finite token of at most
|
|
32
|
+
* 64 characters within ±(2^53−1) that does not underflow to zero. These rules apply to the
|
|
33
|
+
* original tokens, before a general parser could erase them.
|
|
17
34
|
*/
|
|
18
|
-
export function parse(input) {
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// The byte order mark is kept, so the parser refuses it.
|
|
25
|
-
text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(input);
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
throw new InvalidDocument('The MAP document is not valid UTF-8.');
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (new TextEncoder().encode(text).length > MAX_BYTES)
|
|
32
|
-
throw new InvalidDocument(`The MAP document exceeds ${MAX_BYTES} bytes.`);
|
|
35
|
+
export function parse(input, maxBytes) {
|
|
36
|
+
const text = typeof input === 'string' ? input : decodeUtf8(input);
|
|
37
|
+
if (text.charCodeAt(0) === 0xfeff)
|
|
38
|
+
throw new InvalidDocument('A byte order mark is not allowed.');
|
|
39
|
+
if (new TextEncoder().encode(text).length > maxBytes)
|
|
40
|
+
throw new InvalidDocument(`The JSON exceeds ${maxBytes} bytes.`);
|
|
33
41
|
let index = 0;
|
|
34
|
-
const fail = (
|
|
35
|
-
throw new InvalidDocument(`Invalid MAP JSON at offset ${index}: ${
|
|
42
|
+
const fail = (reason) => {
|
|
43
|
+
throw new InvalidDocument(`Invalid MAP JSON at offset ${index}: ${reason}.`);
|
|
36
44
|
};
|
|
37
45
|
const space = () => {
|
|
38
46
|
while (' \t\n\r'.includes(text[index] ?? '-'))
|
|
@@ -55,13 +63,12 @@ export function parse(input) {
|
|
|
55
63
|
}
|
|
56
64
|
if (LONE_SURROGATE.test(value))
|
|
57
65
|
fail('lone surrogate');
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
fail('a noncharacter in a string');
|
|
66
|
+
for (const character of value)
|
|
67
|
+
if (forbidden(character.codePointAt(0)))
|
|
68
|
+
fail('U+0000 or a noncharacter');
|
|
62
69
|
return value;
|
|
63
70
|
};
|
|
64
|
-
// `depth` counts the
|
|
71
|
+
// `depth` counts the containers enclosing the value.
|
|
65
72
|
const value = (depth) => {
|
|
66
73
|
space();
|
|
67
74
|
const next = text[index];
|
|
@@ -131,15 +138,23 @@ export function parse(input) {
|
|
|
131
138
|
}
|
|
132
139
|
if (next === '"')
|
|
133
140
|
return string();
|
|
134
|
-
const literal
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
fail('
|
|
142
|
-
|
|
141
|
+
for (const literal of ['true', 'false', 'null'])
|
|
142
|
+
if (text.startsWith(literal, index)) {
|
|
143
|
+
index += literal.length;
|
|
144
|
+
return JSON.parse(literal);
|
|
145
|
+
}
|
|
146
|
+
const token = NUMBER.exec(text.slice(index))?.[0];
|
|
147
|
+
if (token === undefined)
|
|
148
|
+
return fail('unexpected token');
|
|
149
|
+
index += token.length;
|
|
150
|
+
if (token.length > MAX_NUMBER_TOKEN)
|
|
151
|
+
fail(`a number token longer than ${MAX_NUMBER_TOKEN} characters`);
|
|
152
|
+
const number = Number(token);
|
|
153
|
+
if (!Number.isFinite(number) || Math.abs(number) > Number.MAX_SAFE_INTEGER)
|
|
154
|
+
fail('a number outside the I-JSON range');
|
|
155
|
+
if (number === 0 && !ZERO.test(token))
|
|
156
|
+
fail('a number that underflows to zero');
|
|
157
|
+
return number;
|
|
143
158
|
};
|
|
144
159
|
const result = value(0);
|
|
145
160
|
space();
|
|
@@ -158,8 +173,7 @@ const ESCAPES = {
|
|
|
158
173
|
};
|
|
159
174
|
/**
|
|
160
175
|
* The RFC 8785 canonical form of a JSON value: members sorted by their UTF-16 code units,
|
|
161
|
-
* ECMAScript's minimal string escapes and its number format.
|
|
162
|
-
* left out and an undefined array item is null, as JSON.stringify writes them.
|
|
176
|
+
* ECMAScript's minimal string escapes and its number format.
|
|
163
177
|
*/
|
|
164
178
|
export function canonicalize(value) {
|
|
165
179
|
if (value === null || typeof value === 'boolean')
|
|
@@ -172,19 +186,15 @@ export function canonicalize(value) {
|
|
|
172
186
|
if (typeof value === 'string')
|
|
173
187
|
return `"${value.replace(/["\\\u0000-\u001f]/g, (char) => ESCAPES[char] ?? `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`)}"`;
|
|
174
188
|
if (Array.isArray(value))
|
|
175
|
-
return `[${value.map(
|
|
189
|
+
return `[${value.map(canonicalize).join(',')}]`;
|
|
176
190
|
if (typeof value === 'object') {
|
|
177
191
|
const members = value;
|
|
178
192
|
return `{${Object.keys(members)
|
|
179
|
-
.filter((name) => members[name] !== undefined)
|
|
180
193
|
.sort()
|
|
181
194
|
.map((name) => `${canonicalize(name)}:${canonicalize(members[name])}`)
|
|
182
195
|
.join(',')}}`;
|
|
183
196
|
}
|
|
184
197
|
throw new TypeError(`${typeof value} is not a JSON value.`);
|
|
185
198
|
}
|
|
186
|
-
/**
|
|
187
|
-
|
|
188
|
-
* descriptions, contracts and pinned schemas.
|
|
189
|
-
*/
|
|
190
|
-
export const digest = (value) => `sha-256:${createHash('sha256').update(canonicalize(value)).digest('hex')}`;
|
|
199
|
+
/** `sha-256:` and the SHA-256 of the value's RFC 8785 form, as MAP identifies contracts. */
|
|
200
|
+
export const digest = (value) => `sha-256:${sha256(canonicalize(value))}`;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a pattern is outside the portable subset, or undefined. The subset is printable ASCII
|
|
3
|
+
* text, with any other code point written as \uXXXX. It has literal characters and escaped
|
|
4
|
+
* syntax characters; \t, \n, \f, \r and \d, the ASCII digits; non-empty classes of literal or
|
|
5
|
+
* escaped members and ranges, with a literal hyphen only first or last; (?: groups;
|
|
6
|
+
* alternation; ^ and $; and one *, +, ?, {n}, {n,} or {n,m} after an atom. Other class
|
|
7
|
+
* escapes and the dot match different characters in different engines, and brackets, && or a
|
|
8
|
+
* stray brace inside a class, or a quantifier on a quantifier, are read differently or refused
|
|
9
|
+
* by one of them. ^ and $ anchor the whole value, as in ECMA-262; an engine whose anchors also
|
|
10
|
+
* match at line breaks must read them so.
|
|
11
|
+
*/
|
|
12
|
+
export declare function unportablePattern(pattern: string): string | undefined;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// The regular expressions a contract's schemas may use: the subset that ECMA-262 (with the
|
|
2
|
+
// u flag) and other engines read alike, so every implementation enforces the same contract.
|
|
3
|
+
const SYNTAX_CHARACTERS = '^$\\.*+?()[]{}|/';
|
|
4
|
+
const CONTROL_ESCAPES = { t: 0x09, n: 0x0a, f: 0x0c, r: 0x0d };
|
|
5
|
+
const QUANTIFIERS = '*+?{';
|
|
6
|
+
/**
|
|
7
|
+
* Why a pattern is outside the portable subset, or undefined. The subset is printable ASCII
|
|
8
|
+
* text, with any other code point written as \uXXXX. It has literal characters and escaped
|
|
9
|
+
* syntax characters; \t, \n, \f, \r and \d, the ASCII digits; non-empty classes of literal or
|
|
10
|
+
* escaped members and ranges, with a literal hyphen only first or last; (?: groups;
|
|
11
|
+
* alternation; ^ and $; and one *, +, ?, {n}, {n,} or {n,m} after an atom. Other class
|
|
12
|
+
* escapes and the dot match different characters in different engines, and brackets, && or a
|
|
13
|
+
* stray brace inside a class, or a quantifier on a quantifier, are read differently or refused
|
|
14
|
+
* by one of them. ^ and $ anchor the whole value, as in ECMA-262; an engine whose anchors also
|
|
15
|
+
* match at line breaks must read them so.
|
|
16
|
+
*/
|
|
17
|
+
export function unportablePattern(pattern) {
|
|
18
|
+
if (!/^[\x20-\x7e]*$/.test(pattern))
|
|
19
|
+
return 'a character outside printable ASCII';
|
|
20
|
+
let at = 0;
|
|
21
|
+
// One escaped code point, the digit class outside a class, or a problem.
|
|
22
|
+
const escape = (inClass) => {
|
|
23
|
+
const next = pattern[at + 1];
|
|
24
|
+
if (next === undefined)
|
|
25
|
+
return 'a trailing backslash';
|
|
26
|
+
if (next === 'u') {
|
|
27
|
+
const digits = pattern.slice(at + 2, at + 6);
|
|
28
|
+
if (!/^[0-9a-fA-F]{4}$/.test(digits))
|
|
29
|
+
return 'a malformed \\u escape';
|
|
30
|
+
const code = parseInt(digits, 16);
|
|
31
|
+
if (code >= 0xd800 && code <= 0xdfff)
|
|
32
|
+
return 'a surrogate \\u escape';
|
|
33
|
+
at += 6;
|
|
34
|
+
return code;
|
|
35
|
+
}
|
|
36
|
+
at += 2;
|
|
37
|
+
if (next === 'd' && !inClass)
|
|
38
|
+
return -1;
|
|
39
|
+
if (Object.hasOwn(CONTROL_ESCAPES, next))
|
|
40
|
+
return CONTROL_ESCAPES[next];
|
|
41
|
+
if (SYNTAX_CHARACTERS.includes(next) || (inClass && next === '-'))
|
|
42
|
+
return next.charCodeAt(0);
|
|
43
|
+
return `the escape \\${next}`;
|
|
44
|
+
};
|
|
45
|
+
const characterClass = () => {
|
|
46
|
+
at += pattern[at + 1] === '^' ? 2 : 1;
|
|
47
|
+
// Each member's code point, or null for an unescaped hyphen.
|
|
48
|
+
const members = [];
|
|
49
|
+
while (pattern[at] !== ']') {
|
|
50
|
+
const char = pattern[at];
|
|
51
|
+
if (char === undefined)
|
|
52
|
+
return 'an unclosed character class';
|
|
53
|
+
if (char === '[')
|
|
54
|
+
return 'a bracket inside a class';
|
|
55
|
+
if (char === '&' && pattern[at + 1] === '&')
|
|
56
|
+
return '&& inside a class';
|
|
57
|
+
if (char === '\\') {
|
|
58
|
+
const code = escape(true);
|
|
59
|
+
if (typeof code === 'string')
|
|
60
|
+
return code;
|
|
61
|
+
members.push(code);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
members.push(char === '-' ? null : char.charCodeAt(0));
|
|
65
|
+
at += 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
at += 1;
|
|
69
|
+
if (!members.length)
|
|
70
|
+
return 'an empty character class';
|
|
71
|
+
for (let index = 0; index < members.length; index += 1) {
|
|
72
|
+
const low = members[index];
|
|
73
|
+
if (low === null) {
|
|
74
|
+
if (index !== 0 && index !== members.length - 1)
|
|
75
|
+
return 'an ambiguous hyphen in a class';
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (members[index + 1] !== null || index + 2 >= members.length)
|
|
79
|
+
continue;
|
|
80
|
+
const high = members[index + 2];
|
|
81
|
+
if (high === null)
|
|
82
|
+
return 'an ambiguous hyphen in a class';
|
|
83
|
+
if (high < low)
|
|
84
|
+
return 'a class range out of order';
|
|
85
|
+
index += 2;
|
|
86
|
+
if (members[index + 1] === null && index + 1 !== members.length - 1)
|
|
87
|
+
return 'an ambiguous hyphen in a class';
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const quantifier = () => {
|
|
91
|
+
if (pattern[at] === '{') {
|
|
92
|
+
const bounds = /^\{([0-9]{1,4})(?:(,)([0-9]{1,4})?)?\}/.exec(pattern.slice(at));
|
|
93
|
+
if (!bounds)
|
|
94
|
+
return 'a malformed {n,m} quantifier';
|
|
95
|
+
if (bounds[3] !== undefined && Number(bounds[3]) < Number(bounds[1]))
|
|
96
|
+
return 'a {n,m} quantifier with m below n';
|
|
97
|
+
at += bounds[0].length;
|
|
98
|
+
}
|
|
99
|
+
else
|
|
100
|
+
at += 1;
|
|
101
|
+
const next = pattern[at];
|
|
102
|
+
if (next !== undefined && QUANTIFIERS.includes(next))
|
|
103
|
+
return 'a quantifier on a quantifier';
|
|
104
|
+
};
|
|
105
|
+
const disjunction = (depth) => {
|
|
106
|
+
let quantifiable = false;
|
|
107
|
+
while (at < pattern.length) {
|
|
108
|
+
const char = pattern[at];
|
|
109
|
+
if (char === '|') {
|
|
110
|
+
at += 1;
|
|
111
|
+
quantifiable = false;
|
|
112
|
+
}
|
|
113
|
+
else if (char === ')') {
|
|
114
|
+
if (depth === 0)
|
|
115
|
+
return 'an unmatched )';
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
else if (char === '^' || char === '$') {
|
|
119
|
+
at += 1;
|
|
120
|
+
quantifiable = false;
|
|
121
|
+
}
|
|
122
|
+
else if (QUANTIFIERS.includes(char)) {
|
|
123
|
+
if (!quantifiable)
|
|
124
|
+
return char === '{' ? 'an unescaped {' : 'a quantifier with nothing to repeat';
|
|
125
|
+
const problem = quantifier();
|
|
126
|
+
if (problem)
|
|
127
|
+
return problem;
|
|
128
|
+
quantifiable = false;
|
|
129
|
+
}
|
|
130
|
+
else if (char === '(') {
|
|
131
|
+
if (!pattern.startsWith('(?:', at))
|
|
132
|
+
return 'a group other than (?:';
|
|
133
|
+
at += 3;
|
|
134
|
+
const problem = disjunction(depth + 1);
|
|
135
|
+
if (problem)
|
|
136
|
+
return problem;
|
|
137
|
+
if (pattern[at] !== ')')
|
|
138
|
+
return 'an unclosed group';
|
|
139
|
+
at += 1;
|
|
140
|
+
quantifiable = true;
|
|
141
|
+
}
|
|
142
|
+
else if (char === '[') {
|
|
143
|
+
const problem = characterClass();
|
|
144
|
+
if (problem)
|
|
145
|
+
return problem;
|
|
146
|
+
quantifiable = true;
|
|
147
|
+
}
|
|
148
|
+
else if (char === '\\') {
|
|
149
|
+
const code = escape(false);
|
|
150
|
+
if (typeof code === 'string')
|
|
151
|
+
return code;
|
|
152
|
+
quantifiable = true;
|
|
153
|
+
}
|
|
154
|
+
else if (char === '.')
|
|
155
|
+
return 'an unescaped dot';
|
|
156
|
+
else if (char === ']' || char === '}')
|
|
157
|
+
return `an unescaped ${char}`;
|
|
158
|
+
else {
|
|
159
|
+
at += 1;
|
|
160
|
+
quantifiable = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
return disjunction(0);
|
|
165
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// SHA-256 (FIPS 180-4) over bytes, so the core needs no platform crypto and runs the same
|
|
2
|
+
// in Node, browsers and workers.
|
|
3
|
+
const K = Uint32Array.from([
|
|
4
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
5
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
6
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
7
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
8
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
9
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
10
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
11
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
12
|
+
]);
|
|
13
|
+
const rotate = (value, bits) => (value >>> bits) | (value << (32 - bits));
|
|
14
|
+
/** The lowercase hexadecimal SHA-256 of the bytes, or of the text's UTF-8 encoding. */
|
|
15
|
+
export function sha256(input) {
|
|
16
|
+
const bytes = typeof input === 'string' ? new TextEncoder().encode(input) : input;
|
|
17
|
+
// The message, a 1 bit, zeros, and its length in bits as a 64-bit big-endian integer.
|
|
18
|
+
const padded = new Uint8Array(Math.ceil((bytes.length + 9) / 64) * 64);
|
|
19
|
+
padded.set(bytes);
|
|
20
|
+
padded[bytes.length] = 0x80;
|
|
21
|
+
const view = new DataView(padded.buffer);
|
|
22
|
+
view.setUint32(padded.length - 8, Math.floor(bytes.length / 0x20000000));
|
|
23
|
+
view.setUint32(padded.length - 4, (bytes.length * 8) >>> 0);
|
|
24
|
+
const hash = Uint32Array.from([
|
|
25
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
26
|
+
]);
|
|
27
|
+
const w = new Uint32Array(64);
|
|
28
|
+
for (let block = 0; block < padded.length; block += 64) {
|
|
29
|
+
for (let t = 0; t < 16; t += 1)
|
|
30
|
+
w[t] = view.getUint32(block + t * 4);
|
|
31
|
+
for (let t = 16; t < 64; t += 1) {
|
|
32
|
+
const s0 = rotate(w[t - 15], 7) ^ rotate(w[t - 15], 18) ^ (w[t - 15] >>> 3);
|
|
33
|
+
const s1 = rotate(w[t - 2], 17) ^ rotate(w[t - 2], 19) ^ (w[t - 2] >>> 10);
|
|
34
|
+
w[t] = w[t - 16] + s0 + w[t - 7] + s1;
|
|
35
|
+
}
|
|
36
|
+
let [a, b, c, d, e, f, g, h] = hash;
|
|
37
|
+
for (let t = 0; t < 64; t += 1) {
|
|
38
|
+
const t1 = h + (rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25)) + ((e & f) ^ (~e & g)) + K[t] + w[t];
|
|
39
|
+
const t2 = (rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22)) + ((a & b) ^ (a & c) ^ (b & c));
|
|
40
|
+
h = g;
|
|
41
|
+
g = f;
|
|
42
|
+
f = e;
|
|
43
|
+
e = (d + t1) >>> 0;
|
|
44
|
+
d = c;
|
|
45
|
+
c = b;
|
|
46
|
+
b = a;
|
|
47
|
+
a = (t1 + t2) >>> 0;
|
|
48
|
+
}
|
|
49
|
+
hash[0] += a;
|
|
50
|
+
hash[1] += b;
|
|
51
|
+
hash[2] += c;
|
|
52
|
+
hash[3] += d;
|
|
53
|
+
hash[4] += e;
|
|
54
|
+
hash[5] += f;
|
|
55
|
+
hash[6] += g;
|
|
56
|
+
hash[7] += h;
|
|
57
|
+
}
|
|
58
|
+
return Array.from(hash, (word) => word.toString(16).padStart(8, '0')).join('');
|
|
59
|
+
}
|