ata-validator 1.52.0 → 1.54.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.
@@ -6,44 +6,11 @@
6
6
  // checks the interpreted engine calls, so a bundle without the code generator
7
7
  // (ata-validator/lite) does not carry them.
8
8
 
9
- const { DURATION_RE, URI_TEMPLATE_EXPR, URI_FAST } = require('./formats');
9
+ // The three patterns below are read from lib/formats.js when a source that
10
+ // needs one is written, not when this file loads: a compile with no uri,
11
+ // uri-template or duration format then does not load formats.js at all.
12
+ const _formats = () => require('./formats');
10
13
 
11
- const uriCharsSource = (v, from) =>
12
- `for(let _ri=${from};_ri<${v}.length;_ri++){const _rc=${v}.charCodeAt(_ri);` +
13
- 'if(_rc<33||_rc>126||_rc===34||_rc===60||_rc===62||_rc===92||_rc===94||_rc===96||_rc===123||_rc===124||_rc===125)return false;' +
14
- `if(_rc===37){const _h1=${v}.charCodeAt(_ri+1),_h2=${v}.charCodeAt(_ri+2);` +
15
- 'if(!((_h1>=48&&_h1<=57)||(_h1>=97&&_h1<=102)||(_h1>=65&&_h1<=70))||' +
16
- '!((_h2>=48&&_h2<=57)||(_h2>=97&&_h2<=102)||(_h2>=65&&_h2<=70)))return false;_ri+=2}}';
17
- const uriAuthoritySource = (v, start) =>
18
- `if(${v}.charCodeAt(${start})===47&&${v}.charCodeAt(${start}+1)===47){` +
19
- `let _ae=${v}.length;` +
20
- `for(let _ai=${start}+2;_ai<${v}.length;_ai++){const _ac=${v}.charCodeAt(_ai);` +
21
- 'if(_ac===47||_ac===63||_ac===35){_ae=_ai;break}}' +
22
- `const _au=${v}.slice(${start}+2,_ae);const _aat=_au.lastIndexOf('@');` +
23
- 'if(_aat!==-1&&/[[\\]]/.test(_au.slice(0,_aat)))return false;' +
24
- 'const _hp=_aat===-1?_au:_au.slice(_aat+1);' +
25
- 'if(_hp.charCodeAt(0)===91){const _cl=_hp.indexOf("]");' +
26
- 'if(_cl===-1)return false;const _rs=_hp.slice(_cl+1);' +
27
- 'if(_rs!==""){if(_rs.charCodeAt(0)!==58||!/^[0-9]*$/.test(_rs.slice(1)))return false}}' +
28
- 'else{const _co=_hp.lastIndexOf(":");' +
29
- 'if(_co!==-1){if(_hp.indexOf(":")!==_co)return false;' +
30
- 'if(!/^[0-9]*$/.test(_hp.slice(_co+1)))return false}}}';
31
- const iriCharsSource = (v, from) =>
32
- `for(let _ii=${from};_ii<${v}.length;_ii++){const _ic=${v}.charCodeAt(_ii);` +
33
- 'if(_ic===127||(_ic<127&&(_ic<33||_ic===34||_ic===60||_ic===62||_ic===92||_ic===94||_ic===96||_ic===123||_ic===124||_ic===125)))return false;' +
34
- `if(_ic===37){const _j1=${v}.charCodeAt(_ii+1),_j2=${v}.charCodeAt(_ii+2);` +
35
- 'if(!((_j1>=48&&_j1<=57)||(_j1>=97&&_j1<=102)||(_j1>=65&&_j1<=70))||' +
36
- '!((_j2>=48&&_j2<=57)||(_j2>=97&&_j2<=102)||(_j2>=65&&_j2<=70)))return false;_ii+=2}}';
37
- function iriSource (v, isStr) {
38
- return guard(v, isStr, `const _n=${v}.length;if(_n===0)return false;` +
39
- `const _f=${v}.charCodeAt(0);if(!((_f>=97&&_f<=122)||(_f>=65&&_f<=90)))return false;` +
40
- `let _co=-1;for(let _i=1;_i<_n;_i++){const _c=${v}.charCodeAt(_i);if(_c===58){_co=_i;break}` +
41
- `if(!((_c>=48&&_c<=57)||(_c>=97&&_c<=122)||(_c>=65&&_c<=90)||_c===43||_c===45||_c===46))return false}` +
42
- `if(_co===-1)return false;` +
43
- iriCharsSource(v, '_co+1') +
44
- uriAuthoritySource(v, '_co+1'));
45
- }
46
- function iriReferenceSource (v, isStr) { return guard(v, isStr, iriCharsSource(v, '0')); }
47
14
  function idnEmailSource (v, isStr) {
48
15
  const inner =
49
16
  `const _at=${v}.lastIndexOf('@');` +
@@ -57,6 +24,7 @@ function idnEmailSource (v, isStr) {
57
24
  `else{` +
58
25
  `if(_dm.charCodeAt(0)===46||_dm.charCodeAt(_dm.length-1)===46)return false;` +
59
26
  `if(_dm.indexOf('..')!==-1)return false;` +
27
+ `if(_dm.charCodeAt(0)===45||_dm.charCodeAt(_dm.length-1)===45||_dm.indexOf('-.')!==-1||_dm.indexOf('.-')!==-1)return false;` +
60
28
  `for(let _di=0;_di<_dm.length;_di++){const _dc=_dm.charCodeAt(_di);` +
61
29
  `if(_dc<=32||_dc===127||_dc===64)return false}` +
62
30
  `}` +
@@ -180,31 +148,34 @@ function timeSource (v, isStr) {
180
148
  'if(_u!==1439)return false}');
181
149
  }
182
150
 
183
- // The same walk as `uri` above, as source, for the code generator to hoist
184
- // once per compiled function and call. Two copies of one algorithm is what
185
- // this file has always done for every format, so they sit together here and
186
- // tests/test_uri_helper_parity.js holds them to the same answer over a corpus
187
- // built from the walk's own boundaries.
188
- const URI_HELPER_TABLES = 'const _uct=new Uint8Array(128);for(let _i=33;_i<127;_i++)_uct[_i]=1;' +
189
- '_uct[34]=_uct[60]=_uct[62]=_uct[92]=_uct[94]=_uct[96]=_uct[123]=_uct[124]=_uct[125]=0;' +
190
- 'const _uch=new Uint8Array(128);for(let _i=48;_i<58;_i++)_uch[_i]=1;' +
191
- 'for(let _i=97;_i<103;_i++)_uch[_i]=1;for(let _i=65;_i<71;_i++)_uch[_i]=1;' +
192
- 'const _ucs=new Uint8Array(128);for(let _i=48;_i<58;_i++)_ucs[_i]=1;' +
193
- 'for(let _i=97;_i<123;_i++)_ucs[_i]=1;for(let _i=65;_i<91;_i++)_ucs[_i]=1;' +
194
- '_ucs[43]=1;_ucs[45]=1;_ucs[46]=1;const _ufa=/' + URI_FAST.source + '/';
195
- const URI_HELPER_BODY = 'if(_ufa.test(_s))return true;const _n=_s.length;if(_n===0)return false;let _c=_s.charCodeAt(0);if(_c>127||_ucs[_c]===0||(_c>=48&&_c<=57)||_c===43||_c===45||_c===46)return false;let _co=-1;for(let _i=1;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c===58){_co=_i;break}if(_c>127||_ucs[_c]===0)return false}if(_co===-1)return false;let _i=_co+1;let _ae=_n;if(_s.charCodeAt(_i)===47&&_s.charCodeAt(_i+1)===47){const _as=_i+2;let _at=-1,_fc=-1,_lc=-1,_br=0,_ba=0,_hs=_as;_ae=-1;for(_i=_as;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===47||_c===63||_c===35){_ae=_i;break}if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2;continue}if(_c===64){_ba=_br;_at=_i;_fc=-1;_lc=-1;_hs=_i+1}else if(_c===58){if(_fc===-1)_fc=_i;_lc=_i}else if(_c===91||_c===93){_br=1}}if(_ae===-1)_ae=_n;if(_at!==-1&&_ba)return false;if(_s.charCodeAt(_hs)===91){let _cl=-1;for(let _j=_hs+1;_j<_ae;_j++){if(_s.charCodeAt(_j)===93){_cl=_j;break}}if(_cl===-1)return false;if(_cl+1!==_ae){if(_s.charCodeAt(_cl+1)!==58)return false;for(let _j=_cl+2;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}}else if(_lc!==-1){if(_fc!==_lc)return false;for(let _j=_lc+1;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}_i=_ae}for(;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2}}return true;';
151
+ // The URI family: the walk in lib/uri-core.js, emitted as its factory's source
152
+ // and built once per compiled function (uriHelperSource, hoisted under `name`),
153
+ // so a standalone module carries the same walk the interpreted engine runs.
154
+ // tests/test_uri_helper_parity.js holds the two to one answer over a corpus
155
+ // built from the walk's own boundaries. `uri` keeps the regex for the common
156
+ // shape in front of the walk; the others go straight to it with their mode.
157
+ // The walk takes the IPv6 check for a bracketed host as its argument, and
158
+ // here that is ipv6Source as a function, so the module carries one copy.
159
+ // The factory's text with the spaces around punctuation taken out, about
160
+ // 40% shorter, since it lands in every generated function and standalone
161
+ // module that checks a URI. No string literal inside holds a space, and every
162
+ // statement ends in a semicolon or a brace, so the joins are safe;
163
+ // tests/test_uri_helper_parity.js runs this text against the function.
164
+ let _uriSrc = null
165
+ const uriWalkText = () => _uriSrc !== null ? _uriSrc : (_uriSrc = require('./uri-core').__ataUri.toString().replace(/\s*([=,(){};<>+\-&|?:!])\s*/g, '$1'))
166
+ const uriWalkSource = () => `(${uriWalkText()})(function(_s){${ipv6Source('_s', true)}return true})`
167
+ // `_uri(s)` is the uri check, `_urif(s, mode)` the other three (the
168
+ // expression in front for an iri), `_uriw(s, mode)` the walk alone.
196
169
  const uriHelperSource = (name) =>
197
- URI_HELPER_TABLES + ';function ' + name + '(_s){' + URI_HELPER_BODY + '}';
198
-
199
- function uriSource (v, isStr) {
200
- return guard(v, isStr, `const _n=${v}.length;if(_n===0)return false;` +
201
- `const _f=${v}.charCodeAt(0);if(!((_f>=97&&_f<=122)||(_f>=65&&_f<=90)))return false;` +
202
- `let _co=-1;for(let _i=1;_i<_n;_i++){const _c=${v}.charCodeAt(_i);if(_c===58){_co=_i;break}` +
203
- `if(!((_c>=48&&_c<=57)||(_c>=97&&_c<=122)||(_c>=65&&_c<=90)||_c===43||_c===45||_c===46))return false}` +
204
- `if(_co===-1)return false;` +
205
- uriCharsSource(v, '_co+1') +
206
- uriAuthoritySource(v, '_co+1'));
207
- }
170
+ 'const _ufa=/' + _formats().URI_FAST.source + '/;const ' + name + 'w=' + uriWalkSource() + ';' +
171
+ 'function ' + name + '(_s){if(_ufa.test(_s))return true;return ' + name + 'w(_s,1)}' +
172
+ 'function ' + name + 'f(_s,_m){if((_m&1)===1&&_ufa.test(_s))return true;return ' + name + 'w(_s,_m)}'
173
+ // The inline forms, for a caller with no function to hoist into: the walk is
174
+ // built in place, which costs a factory per call, so the generators hoist.
175
+ function uriSource (v, isStr) { return guard(v, isStr, `if(!/${_formats().URI_FAST.source}/.test(${v})&&!${uriWalkSource()}(${v},1))return false`) }
176
+ function uriReferenceSource (v, isStr) { return guard(v, isStr, `if(!${uriWalkSource()}(${v},0))return false`) }
177
+ function iriSource (v, isStr) { return guard(v, isStr, `if(!/${_formats().URI_FAST.source}/.test(${v})&&!${uriWalkSource()}(${v},3))return false`) }
178
+ function iriReferenceSource (v, isStr) { return guard(v, isStr, `if(!${uriWalkSource()}(${v},2))return false`) }
208
179
 
209
180
  function ipv6Source (v, isStr) {
210
181
  return guard(v, isStr, `const _n=${v}.length;if(_n<2||_n>45)return false;let _end=_n,_g=0;` +
@@ -245,6 +216,9 @@ function uriTemplateSource (v, isStr) {
245
216
  `const _c=${v}.charCodeAt(_i);` +
246
217
  `if(_c===125)return false;` +
247
218
  `if(_c!==123&&(_c<=32||_c===127))return false;` +
219
+ `if(_c===37){const _h1=${v}.charCodeAt(_i+1),_h2=${v}.charCodeAt(_i+2);` +
220
+ 'if(!((_h1>=48&&_h1<=57)||(_h1>=97&&_h1<=102)||(_h1>=65&&_h1<=70))||' +
221
+ '!((_h2>=48&&_h2<=57)||(_h2>=97&&_h2<=102)||(_h2>=65&&_h2<=70)))return false;_i+=3;continue}' +
248
222
  `if(_c!==123){_i++;continue}` +
249
223
  // `_ute`, not `_e`: this source is spliced into a generated function whose
250
224
  // error accumulator is `_e`, and a `const _e` here shadowed it, so
@@ -254,7 +228,7 @@ function uriTemplateSource (v, isStr) {
254
228
  `if(_ute===-1)return false;` +
255
229
  `const _x=${v}.slice(_i+1,_ute);` +
256
230
  `if(_x===''||_x.indexOf('{')!==-1)return false;` +
257
- `if(!${URI_TEMPLATE_EXPR.toString()}.test(_x))return false;` +
231
+ `if(!${_formats().URI_TEMPLATE_EXPR.toString()}.test(_x))return false;` +
258
232
  `_i=_ute+1` +
259
233
  `}`;
260
234
  return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`;
@@ -296,7 +270,10 @@ function emailSource (v, isStr) {
296
270
  `const _n0=${v}.length;let _at;` +
297
271
  `if(${v}.charCodeAt(0)===34){` +
298
272
  `_at=${v}.lastIndexOf('@');` +
299
- `if(_at<2||_at===_n0-1||_at>64||${v}.charCodeAt(_at-1)!==34)return false` +
273
+ `if(_at<2||_at===_n0-1||_at>64||${v}.charCodeAt(_at-1)!==34)return false;` +
274
+ `for(let _qi=1;_qi<_at-1;_qi++){const _qc=${v}.charCodeAt(_qi);` +
275
+ `if(_qc===92){const _qp=${v}.charCodeAt(_qi+1);if(_qp<32||_qp>126||_qi+1>=_at-1)return false;_qi++;continue}` +
276
+ `if(_qc<32||_qc>126||_qc===34)return false}` +
300
277
  `}else{` +
301
278
  `if(${v}.charCodeAt(0)===46)return false;` +
302
279
  `_at=-1;let _pd=false;` +
@@ -310,14 +287,14 @@ function emailSource (v, isStr) {
310
287
  `const _dm=${v}.slice(_at+1);` +
311
288
  `if(_dm.charCodeAt(_dm.length-1)!==93||_dm.length<=2)return false;` +
312
289
  `const _in=_dm.slice(1,-1);` +
313
- `if(_in.startsWith('IPv6:')){const _i6=_in.slice(5);${ipv6Source('_i6', true)}}` +
290
+ `if((_in.charCodeAt(0)|32)===105&&(_in.charCodeAt(1)|32)===112&&(_in.charCodeAt(2)|32)===118&&_in.charCodeAt(3)===54&&_in.charCodeAt(4)===58){const _i6=_in.slice(5);${ipv6Source('_i6', true)}}` +
314
291
  `else{${ipv4Source('_in', true)}}` +
315
292
  `}` +
316
293
  `else{${hostnameRangeSource(v, '_at+1', `${v}.length`)}}`;
317
294
  return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`;
318
295
  }
319
296
  function durationSource (v, isStr) {
320
- const inner = `if(!${DURATION_RE.toString()}.test(${v}))return false`;
297
+ const inner = `if(!${_formats().DURATION_RE.toString()}.test(${v}))return false`;
321
298
  return isStr ? inner : `if(typeof ${v}==='string'&&${inner.slice(3)}`;
322
299
  }
323
- module.exports = { uriHelperSource, uriCharsSource, uriAuthoritySource, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uriSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
300
+ module.exports = { uriHelperSource, uriSource, uriReferenceSource, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uriSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
package/lib/formats.js CHANGED
@@ -245,34 +245,6 @@ function hostname (s, from, to) {
245
245
  return labelLength !== 0 && previous !== 45;
246
246
  }
247
247
 
248
- // Character classes as tables rather than comparison chains. Nine `===` tests
249
- // per character more than doubled the cost of the scan they guarded, measured
250
- // against the same loop doing a single range check; an indexed byte read costs
251
- // the same whatever the class holds.
252
- const URI_CHAR = new Uint8Array(128);
253
- for (let i = 33; i < 127; i++) URI_CHAR[i] = 1;
254
- // " < > \ ^ ` { | } are printable and still not allowed in a URI.
255
- for (const c of [34, 60, 62, 92, 94, 96, 123, 124, 125]) URI_CHAR[c] = 0;
256
- const HEX_CHAR = new Uint8Array(128);
257
- for (let i = 48; i < 58; i++) HEX_CHAR[i] = 1;
258
- for (let i = 97; i < 103; i++) HEX_CHAR[i] = 1;
259
- for (let i = 65; i < 71; i++) HEX_CHAR[i] = 1;
260
- // Scheme characters: letters, digits, "+", "-", ".".
261
- const SCHEME_CHAR = new Uint8Array(128);
262
- for (let i = 48; i < 58; i++) SCHEME_CHAR[i] = 1;
263
- for (let i = 97; i < 123; i++) SCHEME_CHAR[i] = 1;
264
- for (let i = 65; i < 91; i++) SCHEME_CHAR[i] = 1;
265
- SCHEME_CHAR[43] = 1; SCHEME_CHAR[45] = 1; SCHEME_CHAR[46] = 1;
266
-
267
- // A scheme followed by a colon, an optional authority, and no character a URI
268
- // cannot hold anywhere after the colon.
269
- //
270
- // One walk answers all of it. The authority's landmarks, the last "@", the
271
- // colons after it and whether a bracket appeared before it, are recorded while
272
- // the characters are being checked, so the authority is never cut out of the
273
- // string or scanned again. Reading it out with `slice` and asking the copy for
274
- // `lastIndexOf` and two regular expressions allocated a string per URI, and a
275
- // document carrying a handful of URLs paid that per field.
276
248
  // The shape almost every URI in a document has, as one regular expression: a
277
249
  // scheme, then either "//" and a host with no userinfo, port, brackets or
278
250
  // percent escapes, or no authority at all, then a tail of characters RFC 3986
@@ -285,78 +257,18 @@ SCHEME_CHAR[43] = 1; SCHEME_CHAR[45] = 1; SCHEME_CHAR[46] = 1;
285
257
  // group merely optional, "http://[::1" could skip it and match "//[::1" as a
286
258
  // path, accepting a string the walk rejects for its unclosed bracket.
287
259
  // tests/test_uri_fast_path.js holds the subset relation.
288
- const URI_FAST = /^[A-Za-z][A-Za-z0-9+.\-]*:(?:\/\/[!$&-.0-9;=A-Z_a-z~]*(?:[\/?#][!#$&-;=?-\[\]_a-z~]*)?|(?!\/\/)[!#$&-;=?-\[\]_a-z~]*)$/;
289
-
290
- function uri (s) {
291
- if (URI_FAST.test(s)) return true;
292
- const n = s.length;
293
- if (n === 0) return false;
294
- let c = s.charCodeAt(0);
295
- // A scheme opens with a letter, never a digit or a sign.
296
- if (c > 127 || SCHEME_CHAR[c] === 0 || (c >= 48 && c <= 57) || c === 43 || c === 45 || c === 46) return false;
297
- let colon = -1;
298
- for (let i = 1; i < n; i++) {
299
- c = s.charCodeAt(i);
300
- if (c === 58) { colon = i; break; }
301
- if (c > 127 || SCHEME_CHAR[c] === 0) return false;
302
- }
303
- if (colon === -1) return false;
304
-
305
- let i = colon + 1;
306
- let authEnd = n;
307
- if (s.charCodeAt(i) === 47 && s.charCodeAt(i + 1) === 47) {
308
- const authStart = i + 2;
309
- let at = -1, firstColon = -1, lastColon = -1, sawBracket = 0, bracketBeforeAt = 0;
310
- let hostStart = authStart;
311
- authEnd = -1;
312
- for (i = authStart; i < n; i++) {
313
- c = s.charCodeAt(i);
314
- if (c > 127 || URI_CHAR[c] === 0) return false;
315
- if (c === 47 || c === 63 || c === 35) { authEnd = i; break; }
316
- if (c === 37) {
317
- const h1 = s.charCodeAt(i + 1), h2 = s.charCodeAt(i + 2);
318
- // A "%" at the end of the string reads as NaN, which no comparison
319
- // admits; `<= 127` is written that way so NaN takes the reject.
320
- if (!(h1 <= 127) || !(h2 <= 127) || HEX_CHAR[h1] === 0 || HEX_CHAR[h2] === 0) return false;
321
- i += 2;
322
- continue;
323
- }
324
- // The last "@" ends userinfo, so everything recorded before it belongs
325
- // to a part that has its own rules and is dropped here.
326
- if (c === 64) { bracketBeforeAt = sawBracket; at = i; firstColon = -1; lastColon = -1; hostStart = i + 1; }
327
- else if (c === 58) { if (firstColon === -1) firstColon = i; lastColon = i; }
328
- else if (c === 91 || c === 93) { sawBracket = 1; }
329
- }
330
- if (authEnd === -1) authEnd = n;
331
- // Brackets belong to the host, so one before the "@" is not userinfo.
332
- if (at !== -1 && bracketBeforeAt) return false;
333
- if (s.charCodeAt(hostStart) === 91) { // '['
334
- let close = -1;
335
- for (let j = hostStart + 1; j < authEnd; j++) { if (s.charCodeAt(j) === 93) { close = j; break; } }
336
- if (close === -1) return false;
337
- if (close + 1 !== authEnd) {
338
- if (s.charCodeAt(close + 1) !== 58) return false;
339
- if (!allDigits(s, close + 2, authEnd)) return false;
340
- }
341
- } else if (lastColon !== -1) {
342
- // A host holding more than one colon is an IPv6 address, and those must
343
- // be bracketed. Without that rule the last group reads as a port number.
344
- if (firstColon !== lastColon) return false;
345
- if (!allDigits(s, lastColon + 1, authEnd)) return false;
346
- }
347
- i = authEnd;
348
- }
349
- for (; i < n; i++) {
350
- c = s.charCodeAt(i);
351
- if (c > 127 || URI_CHAR[c] === 0) return false;
352
- if (c === 37) {
353
- const h1 = s.charCodeAt(i + 1), h2 = s.charCodeAt(i + 2);
354
- if (!(h1 <= 127) || !(h2 <= 127) || HEX_CHAR[h1] === 0 || HEX_CHAR[h2] === 0) return false;
355
- i += 2;
356
- }
357
- }
358
- return true;
359
- }
260
+ const URI_FAST = /^[A-Za-z][A-Za-z0-9+.\-]*:(?:\/\/[!$&-.0-9;=A-Z_a-z~]*(?::[0-9]*)?(?:[\/?][!$&-;=?-Z_a-z~]*)?|(?!\/\/)[!$&-;=?-Z_a-z~]*)(?:#[!$&-;=?-Z_a-z~]*)?$/;
261
+
262
+ // The walk behind the expression, shared with the code generator
263
+ // (lib/uri-core.js). An IRI is a URI that also admits non-ASCII, so the
264
+ // expression answers its common shapes too and anything else goes to the
265
+ // walk. A reference goes straight to the walk, which opens with a scan for
266
+ // the plain shapes.
267
+ const _uriWalk = require('./uri-core').__ataUri(ipv6);
268
+ function uri (s) { return URI_FAST.test(s) || _uriWalk(s, 1); }
269
+ function uriReference (s) { return _uriWalk(s, 0); }
270
+ function iri (s) { return URI_FAST.test(s) || _uriWalk(s, 3); }
271
+ function iriReference (s) { return _uriWalk(s, 2); }
360
272
 
361
273
  // The characters a URI cannot hold: C0 controls, space, DEL, and the rest of
362
274
  // Unicode whitespace. Asking for that set directly costs more than it looks:
@@ -374,128 +286,6 @@ const NON_PRINTABLE = /[^\u0021-\u007e]/;
374
286
  // " < > \ ^ ` { | }, plus everything below 0x21 and above 0x7e. The old check
375
287
  // only looked for whitespace and non-printables, so a backslash or a lone
376
288
  // percent sign passed as a URI.
377
- function uriChar (c) {
378
- if (c < 33 || c > 126) return false;
379
- return !(c === 34 || c === 60 || c === 62 || c === 92 ||
380
- c === 94 || c === 96 || c === 123 || c === 124 || c === 125);
381
- }
382
- function isHex (c) {
383
- return (c >= 48 && c <= 57) || (c >= 97 && c <= 102) || (c >= 65 && c <= 70);
384
- }
385
- // The same set through the URI_CHAR and HEX_CHAR tables: one load per
386
- // character, where uriChar() was a call and up to eleven comparisons, and on
387
- // SchemaStore's SARIF samples, full of URIs, that loop was a sixth of what the
388
- // interpreted engine spent. A "%" at the end reads past the string, NaN,
389
- // which fails the bound as it failed isHex().
390
- function uriChars (s, from) {
391
- for (let i = from; i < s.length; i++) {
392
- const c = s.charCodeAt(i);
393
- if (c > 127 || URI_CHAR[c] === 0) return false;
394
- if (c === 37) { // '%'
395
- const h1 = s.charCodeAt(i + 1), h2 = s.charCodeAt(i + 2);
396
- if (!(h1 <= 127) || !(h2 <= 127) || HEX_CHAR[h1] === 0 || HEX_CHAR[h2] === 0) return false;
397
- i += 2;
398
- }
399
- }
400
- return true;
401
- }
402
-
403
- function allDigits (s, from, to) {
404
- for (let i = from; i < to; i++) {
405
- const c = s.charCodeAt(i);
406
- if (c < 48 || c > 57) return false;
407
- }
408
- return true;
409
- }
410
-
411
- // The authority sits between "//" and the next "/", "?" or "#". A bracketed
412
- // host must close, and a port is digits.
413
- //
414
- // Everything here reads the original string through index bounds. Cutting the
415
- // authority out with `slice` and asking it for `lastIndexOf` and two regular
416
- // expressions allocated a string for every URI validated, and a document
417
- // carrying a handful of URLs paid that per field; the authority of an ordinary
418
- // URL is a dozen characters, so walking it twice costs less than copying it
419
- // once.
420
- function uriAuthority (s, start) {
421
- if (s.charCodeAt(start) !== 47 || s.charCodeAt(start + 1) !== 47) return true;
422
- const n = s.length;
423
- const authStart = start + 2;
424
- let authEnd = n;
425
- for (let i = authStart; i < n; i++) {
426
- const c = s.charCodeAt(i);
427
- if (c === 47 || c === 63 || c === 35) { authEnd = i; break; }
428
- }
429
- // The last "@" splits userinfo from the host. Brackets belong to the host,
430
- // so one before the "@" is not userinfo.
431
- let at = -1;
432
- for (let i = authEnd - 1; i >= authStart; i--) {
433
- if (s.charCodeAt(i) === 64) { at = i; break; }
434
- }
435
- if (at !== -1) {
436
- for (let i = authStart; i < at; i++) {
437
- const c = s.charCodeAt(i);
438
- if (c === 91 || c === 93) return false;
439
- }
440
- }
441
- const hpStart = at === -1 ? authStart : at + 1;
442
- if (s.charCodeAt(hpStart) === 91) { // '['
443
- let close = -1;
444
- for (let i = hpStart + 1; i < authEnd; i++) {
445
- if (s.charCodeAt(i) === 93) { close = i; break; }
446
- }
447
- if (close === -1) return false;
448
- if (close + 1 === authEnd) return true;
449
- if (s.charCodeAt(close + 1) !== 58) return false;
450
- return allDigits(s, close + 2, authEnd);
451
- }
452
- let firstColon = -1;
453
- let lastColon = -1;
454
- for (let i = hpStart; i < authEnd; i++) {
455
- if (s.charCodeAt(i) === 58) {
456
- if (firstColon === -1) firstColon = i;
457
- lastColon = i;
458
- }
459
- }
460
- if (lastColon === -1) return true;
461
- // A host holding more than one colon is an IPv6 address, and those must be
462
- // bracketed. Without that rule the last group reads as a port number.
463
- if (firstColon !== lastColon) return false;
464
- return allDigits(s, lastColon + 1, authEnd);
465
- }
466
-
467
- // An IRI is a URI that also admits non-ASCII. The ASCII rules are identical,
468
- // so the same character test runs with everything above 0x7e allowed through.
469
- function iriChar (c) { return c > 126 ? c !== 127 : uriChar(c); }
470
- function iriChars (s, from) {
471
- for (let i = from; i < s.length; i++) {
472
- const c = s.charCodeAt(i);
473
- if (!iriChar(c)) return false;
474
- if (c === 37) {
475
- if (!isHex(s.charCodeAt(i + 1)) || !isHex(s.charCodeAt(i + 2))) return false;
476
- i += 2;
477
- }
478
- }
479
- return true;
480
- }
481
-
482
- function iri (s) {
483
- const n = s.length;
484
- if (n === 0) return false;
485
- const first = s.charCodeAt(0);
486
- if (!((first >= 97 && first <= 122) || (first >= 65 && first <= 90))) return false;
487
- let colon = -1;
488
- for (let i = 1; i < n; i++) {
489
- const c = s.charCodeAt(i);
490
- if (c === 58) { colon = i; break; }
491
- if (!(isDigit(c) || (c >= 97 && c <= 122) || (c >= 65 && c <= 90) ||
492
- c === 43 || c === 45 || c === 46)) return false;
493
- }
494
- if (colon === -1) return false;
495
- return iriChars(s, colon + 1) && uriAuthority(s, colon + 1);
496
- }
497
- function iriReference (s) { return iriChars(s, 0); }
498
-
499
289
  // An internationalised mailbox differs from a mailbox only in admitting
500
290
  // non-ASCII on both sides. The separator stays the ASCII "@": a fullwidth one
501
291
  // is a character in the local part, not a separator.
@@ -514,6 +304,10 @@ function idnEmail (s) {
514
304
  }
515
305
  if (domain.charCodeAt(0) === 46 || domain.charCodeAt(domain.length - 1) === 46) return false;
516
306
  if (domain.indexOf('..') !== -1) return false;
307
+ // No label starts or ends with a hyphen (RFC 5321 sub-domain, as for
308
+ // `hostname`): with the dots checked above, that is a hyphen at either end
309
+ // or next to a dot.
310
+ if (domain.charCodeAt(0) === 45 || domain.charCodeAt(domain.length - 1) === 45 || domain.indexOf('-.') !== -1 || domain.indexOf('.-') !== -1) return false;
517
311
  for (let i = 0; i < domain.length; i++) {
518
312
  const c = domain.charCodeAt(i);
519
313
  if (c <= 32 || c === 127 || c === 64) return false;
@@ -612,10 +406,6 @@ function time (s) {
612
406
  }
613
407
 
614
408
  // Anything without a control character or whitespace.
615
- function uriReference (s) {
616
- return uriChars(s, 0);
617
- }
618
-
619
409
  // --- RFC 6901 JSON pointers -------------------------------------------------
620
410
  // A pointer is empty or a run of "/"-prefixed tokens, and the only escape is
621
411
  // "~" followed by 0 or 1. Everything else, including "#", is not a pointer:
@@ -652,14 +442,21 @@ function relativeJsonPointer(s) {
652
442
  // expression: an optional operator, then a comma-separated list of varspecs,
653
443
  // each a possibly dotted name of varchars with an optional ":n" prefix length
654
444
  // or "*" explode.
655
- const URI_TEMPLATE_EXPR = /^(?:[+#./;?&=,!@|]?)(?:[A-Za-z0-9_%]|%[0-9A-Fa-f]{2})+(?:\.(?:[A-Za-z0-9_%]|%[0-9A-Fa-f]{2})+)*(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[A-Za-z0-9_%]|%[0-9A-Fa-f]{2})+(?:\.(?:[A-Za-z0-9_%]|%[0-9A-Fa-f]{2})+)*(?::[1-9][0-9]{0,3}|\*)?)*$/;
445
+ const URI_TEMPLATE_EXPR = /^(?:[+#./;?&=,!@|]?)(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2})+(?:\.(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2})+)*(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2})+(?:\.(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2})+)*(?::[1-9][0-9]{0,3}|\*)?)*$/;
446
+ function isHexCode (c) { return (c >= 48 && c <= 57) || (c >= 97 && c <= 102) || (c >= 65 && c <= 70); }
656
447
  function uriTemplate(s) {
657
448
  let i = 0;
658
449
  while (i < s.length) {
659
450
  const c = s.charCodeAt(i);
660
451
  if (c === 125) return false; // '}' with no opening brace
661
- // A literal excludes the controls, the space and DEL.
452
+ // A literal excludes the controls, the space and DEL, and a "%" in it
453
+ // introduces two hex digits.
662
454
  if (c !== 123 && (c <= 32 || c === 127)) return false;
455
+ if (c === 37) {
456
+ const h1 = s.charCodeAt(i + 1), h2 = s.charCodeAt(i + 2);
457
+ if (!(h1 <= 127) || !(h2 <= 127) || !isHexCode(h1) || !isHexCode(h2)) return false;
458
+ i += 3; continue;
459
+ }
663
460
  if (c !== 123) { i++; continue; } // not '{'
664
461
  const end = s.indexOf('}', i + 1);
665
462
  if (end === -1) return false;
@@ -698,6 +495,14 @@ function email(s) {
698
495
  at = s.lastIndexOf('@');
699
496
  if (at < 2 || at === n - 1 || at > 64) return false;
700
497
  if (s.charCodeAt(at - 1) !== 34) return false;
498
+ // Inside the quotes: printable ASCII other than the quote and the
499
+ // backslash, or a backslash followed by one printable ASCII character
500
+ // (RFC 5321 quoted-pairSMTP); "\©" is not a quoted pair.
501
+ for (let i = 1; i < at - 1; i++) {
502
+ const c = s.charCodeAt(i);
503
+ if (c === 92) { const q = s.charCodeAt(i + 1); if (q < 32 || q > 126 || i + 1 >= at - 1) return false; i++; continue; }
504
+ if (c < 32 || c > 126 || c === 34) return false;
505
+ }
701
506
  } else {
702
507
  if (s.charCodeAt(0) === 46) return false; // a leading dot is not a mailbox
703
508
  at = -1;
@@ -718,8 +523,9 @@ function email(s) {
718
523
  }
719
524
  if (s.charCodeAt(at + 1) === 91) { // '['
720
525
  if (s.charCodeAt(n - 1) !== 93 || n - at - 1 <= 2) return false;
721
- // IPv6 literals are tagged; anything else in brackets is an IPv4 address.
722
- if (s.startsWith('IPv6:', at + 2)) return ipv6(s.slice(at + 7, n - 1));
526
+ // IPv6 literals are tagged, in any case; anything else in brackets is an
527
+ // IPv4 address. `| 32` lowers an ASCII letter and leaves "6" and ":".
528
+ if ((s.charCodeAt(at + 2) | 32) === 105 && (s.charCodeAt(at + 3) | 32) === 112 && (s.charCodeAt(at + 4) | 32) === 118 && s.charCodeAt(at + 5) === 54 && s.charCodeAt(at + 6) === 58) return ipv6(s.slice(at + 7, n - 1));
723
529
  return ipv4(s.slice(at + 2, n - 1));
724
530
  }
725
531
  return hostname(s, at + 1, n);
@@ -732,4 +538,4 @@ function email(s) {
732
538
  const DURATION_RE = /^P(?:\d+W|(?:\d+Y(?:\d+M(?:\d+D)?)?|\d+M(?:\d+D)?|\d+D)(?:T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S))?|T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S))$/;
733
539
  function duration (s) { return DURATION_RE.test(s); }
734
540
 
735
- module.exports = { IPV4, IPV6_FULL, ipv4Range, URI_FAST, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, uriChars, uriAuthority, iri, iriReference, idnEmail, DURATION_RE, URI_TEMPLATE_EXPR };
541
+ module.exports = { IPV4, IPV6_FULL, ipv4Range, URI_FAST, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, iri, iriReference, idnEmail, DURATION_RE, URI_TEMPLATE_EXPR };
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ // What compat reads the reference's way while the interpreted engine walks a
4
+ // document: coercion where each `type` is checked, and the discriminator in
5
+ // place of `oneOf`. compat installs this (lib/interpreter-core.js, setCompat),
6
+ // so an application that never imports compat does not bundle it.
7
+
8
+ function isDiscriminated(schema) {
9
+ const d = schema.discriminator;
10
+ return d !== null && typeof d === 'object' && typeof d.propertyName === 'string';
11
+ }
12
+
13
+ // The reference's coercion: the first type in the list the value converts to,
14
+ // after unwrapping a one-element array in 'array' mode, which then converts
15
+ // the element.
16
+ const NO_COERCION = Symbol('no coercion');
17
+ function fitsType(d, t) {
18
+ switch (t) {
19
+ case 'string': return typeof d === 'string';
20
+ case 'number': return typeof d === 'number' && isFinite(d);
21
+ case 'integer': return typeof d === 'number' && Number.isInteger(d);
22
+ case 'boolean': return typeof d === 'boolean';
23
+ case 'null': return d === null;
24
+ case 'array': return Array.isArray(d);
25
+ case 'object': return d !== null && typeof d === 'object' && !Array.isArray(d);
26
+ default: return false;
27
+ }
28
+ }
29
+ // Whether an unwrapped element is kept as it is. The reference tests a list
30
+ // of types here with every type at once, so a list of two or more keeps
31
+ // nothing (`number` stands in for `integer` beside it), and the element goes
32
+ // on to be converted, as one of a single type does when it does not fit.
33
+ function unwrappedFits(d, types) {
34
+ let list = types;
35
+ if (list.length > 1 && list.includes('number')) list = list.filter((t) => t !== 'integer');
36
+ if (list.length > 1 && list.includes('array') && list.includes('object')) return false;
37
+ for (const t of list) if (!fitsType(d, t)) return false;
38
+ return true;
39
+ }
40
+ function coerceValue(data, types, arrayMode) {
41
+ if (arrayMode && Array.isArray(data) && data.length === 1) {
42
+ data = data[0];
43
+ if (unwrappedFits(data, types)) return data;
44
+ }
45
+ const kind = data === null ? 'null' : typeof data;
46
+ for (const t of types) {
47
+ switch (t) {
48
+ case 'number':
49
+ case 'integer':
50
+ if (kind === 'boolean' || kind === 'null') return +data;
51
+ // eslint-disable-next-line eqeqeq
52
+ if (kind === 'string' && data !== '' && data == +data && (t === 'number' || !(data % 1))) return +data;
53
+ break;
54
+ case 'string':
55
+ if (kind === 'number' || kind === 'boolean') return '' + data;
56
+ if (kind === 'null') return '';
57
+ break;
58
+ case 'boolean':
59
+ if (data === 'false' || data === 0 || data === null) return false;
60
+ if (data === 'true' || data === 1) return true;
61
+ break;
62
+ case 'null':
63
+ if (data === '' || data === 0 || data === false) return null;
64
+ break;
65
+ case 'array':
66
+ if (arrayMode && (kind === 'string' || kind === 'number' || kind === 'boolean' || kind === 'null')) return [data];
67
+ break;
68
+ }
69
+ }
70
+ return NO_COERCION;
71
+ }
72
+
73
+ module.exports = function install({ NOERRORS, DISCARD, err, mergeAnnotations, dataCtx }) {
74
+ // The discriminator in place of oneOf: the branch the tag names is applied,
75
+ // and a tag that is not a string, or names no branch, is the error.
76
+ function discriminate(interp, P, data, base, dynScope, errors, instancePath, schemaPath, stack, local) {
77
+ const d = discrMap(interp, P, base);
78
+ const tag = data[d.tagName];
79
+ if (typeof tag !== 'string') {
80
+ if (errors !== NOERRORS) errors.push(err('discriminator', 'discriminator', instancePath, schemaPath + '/discriminator', { error: 'tag', tag: d.tagName, tagValue: tag }, `tag "${d.tagName}" must be string`));
81
+ return false;
82
+ }
83
+ const i = d.map.get(tag);
84
+ if (i === undefined) {
85
+ if (errors !== NOERRORS) errors.push(err('discriminator', 'discriminator', instancePath, schemaPath + '/discriminator', { error: 'mapping', tag: d.tagName, tagValue: tag }, `value of tag "${d.tagName}" must be in oneOf`));
86
+ return false;
87
+ }
88
+ const sub = local !== DISCARD ? { props: null, items: null } : DISCARD;
89
+ const ok = interp.eval(P.oneOf[i], data, base, dynScope, errors, instancePath, schemaPath + '/oneOf/' + i, stack, sub);
90
+ if (ok && local !== DISCARD) mergeAnnotations(local, sub);
91
+ return ok;
92
+ }
93
+
94
+ // Tag value to branch index, read from each branch's `properties`, through
95
+ // a `$ref`, with the reference's rules: a `const` or `enum` of unique
96
+ // strings, and the tag required by the node or by every branch.
97
+ function discrMap(interp, P, base) {
98
+ if (interp._discr === null) interp._discr = new Map();
99
+ let d = interp._discr.get(P);
100
+ if (d !== undefined) return d;
101
+ const tagName = P.schema.discriminator.propertyName;
102
+ if (P.schema.discriminator.mapping !== undefined) throw new Error('discriminator: mapping is not supported');
103
+ const hasRequired = (s) => s !== null && typeof s === 'object' && Array.isArray(s.required) && s.required.includes(tagName);
104
+ let topRequired = hasRequired(P.schema);
105
+ const map = new Map();
106
+ for (let i = 0; i < P.oneOf.length; i++) {
107
+ let node = P.oneOf[i];
108
+ if (node !== null && typeof node === 'object' && node.ref !== null && node.ref !== undefined && (node.schema.properties === undefined)) {
109
+ const r = interp.resolveRefCached(node, base);
110
+ if (r.child === undefined) throw new Error(`discriminator: cannot resolve $ref ${node.ref}`);
111
+ node = r.child;
112
+ }
113
+ const sch = node !== null && typeof node === 'object' ? node.schema : undefined;
114
+ const propSch = sch && sch.properties ? sch.properties[tagName] : undefined;
115
+ if (propSch === null || typeof propSch !== 'object') throw new Error(`discriminator: oneOf subschemas (or referenced schemas) must have "properties/${tagName}"`);
116
+ topRequired = topRequired || hasRequired(sch);
117
+ const values = propSch.const !== undefined ? [propSch.const] : Array.isArray(propSch.enum) ? propSch.enum : null;
118
+ if (values === null) throw new Error(`discriminator: "properties/${tagName}" must have "const" or "enum"`);
119
+ for (const v of values) {
120
+ if (typeof v !== 'string' || map.has(v)) throw new Error(`discriminator: "${tagName}" values must be unique strings`);
121
+ map.set(v, i);
122
+ }
123
+ }
124
+ if (!topRequired) throw new Error(`discriminator: "${tagName}" must be required`);
125
+ d = { tagName, map };
126
+ interp._discr.set(P, d);
127
+ return d;
128
+ }
129
+
130
+ // Coerces the value at `instancePath` to the node's type and writes it where
131
+ // it sits; at the root, where it sits nowhere, the walk carries it on.
132
+ function coerceAt(interp, P, data, instancePath) {
133
+ const to = coerceValue(data, P.typeNames, interp.coerce === 'array');
134
+ if (to === NO_COERCION) return data;
135
+ if (instancePath === '') interp._ctxRoot = to;
136
+ else {
137
+ const ctx = dataCtx(instancePath, '', '');
138
+ if (ctx.parentData !== null && typeof ctx.parentData === 'object') ctx.parentData[ctx.parentDataProperty] = to;
139
+ }
140
+ return to;
141
+ }
142
+
143
+ return { isDiscriminated, discriminate, coerceAt };
144
+ };