@blamejs/pki 0.8.30 → 0.8.48
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/CHANGELOG.md +411 -0
- package/README.md +67 -16
- package/bin/pki.js +716 -30
- package/examples/issue-a-certificate.js +90 -0
- package/examples/post-quantum.js +66 -0
- package/examples/read-a-certificate.js +56 -0
- package/examples/revoke-and-check.js +86 -0
- package/examples/sign-and-verify-cms.js +63 -0
- package/index.js +18 -5
- package/lib/acme.js +223 -74
- package/lib/alt-sig.js +441 -0
- package/lib/asn1-der.js +48 -25
- package/lib/attrcert-sign.js +27 -19
- package/lib/cbor-det.js +24 -14
- package/lib/cmc-build.js +9 -2
- package/lib/cmc-verify.js +14 -3
- package/lib/cmp-build.js +17 -5
- package/lib/cmp-session.js +24 -11
- package/lib/cmp-verify.js +28 -19
- package/lib/cms-compress.js +3 -1
- package/lib/cms-decrypt.js +102 -7
- package/lib/cms-encrypt.js +59 -7
- package/lib/cms-sign.js +31 -9
- package/lib/cms-verify.js +22 -18
- package/lib/composite-kem.js +30 -18
- package/lib/composite-sig.js +29 -14
- package/lib/constants.js +28 -0
- package/lib/crl-sign.js +111 -19
- package/lib/crmf-sign.js +167 -15
- package/lib/csr-sign.js +117 -6
- package/lib/ct.js +939 -48
- package/lib/est.js +29 -16
- package/lib/framework-error.js +12 -0
- package/lib/guard-async.js +1 -1
- package/lib/guard-bytes.js +103 -20
- package/lib/guard-crypto.js +205 -3
- package/lib/guard-encoding.js +6 -1
- package/lib/guard-identifier.js +55 -8
- package/lib/guard-intrinsic.js +43 -0
- package/lib/guard-json.js +122 -9
- package/lib/guard-limits.js +12 -1
- package/lib/guard-list.js +63 -8
- package/lib/guard-name.js +167 -20
- package/lib/guard-range.js +43 -5
- package/lib/guard-text.js +41 -0
- package/lib/guard-verdict.js +39 -8
- package/lib/hpke.js +688 -42
- package/lib/http-transport.js +156 -4
- package/lib/identity-match.js +4 -4
- package/lib/inspect.js +448 -2
- package/lib/jose.js +93 -25
- package/lib/key.js +18 -13
- package/lib/lint.js +165 -1
- package/lib/merkle.js +233 -27
- package/lib/ocsp.js +27 -14
- package/lib/oid.js +14 -4
- package/lib/path-validate.js +143 -61
- package/lib/pkcs12-build.js +7 -2
- package/lib/pki-build.js +31 -7
- package/lib/possession.js +575 -0
- package/lib/related-cert.js +708 -0
- package/lib/revocation-fetch.js +6 -6
- package/lib/scep.js +15 -5
- package/lib/schema-c509.js +5 -2
- package/lib/schema-cmc.js +1 -1
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +2 -15
- package/lib/schema-csr.js +69 -0
- package/lib/schema-engine.js +10 -3
- package/lib/schema-pkix.js +267 -4
- package/lib/schema-tsp.js +7 -6
- package/lib/sign-scheme.js +22 -9
- package/lib/sigstore.js +684 -168
- package/lib/sleep.js +8 -1
- package/lib/smime.js +22 -1
- package/lib/tlog.js +1235 -0
- package/lib/tls-cert-compress.js +25 -8
- package/lib/tsp-sign.js +120 -35
- package/lib/tuf.js +1395 -0
- package/lib/validator-tpm.js +4 -1
- package/lib/verify-seam.js +7 -1
- package/lib/webauthn-mds.js +41 -4
- package/lib/webauthn.js +43 -25
- package/lib/webcrypto.js +48 -46
- package/lib/x509-sign.js +225 -19
- package/package.json +2 -1
- package/sbom.cdx.json +6 -6
package/lib/guard-crypto.js
CHANGED
|
@@ -4,12 +4,210 @@
|
|
|
4
4
|
// @internal
|
|
5
5
|
|
|
6
6
|
var nodeCrypto = require("node:crypto");
|
|
7
|
-
var
|
|
7
|
+
var intrinsic = require("./guard-intrinsic");
|
|
8
|
+
var _freeze = intrinsic.freeze;
|
|
9
|
+
|
|
10
|
+
/** @internal The hash operations, captured at load. The prototype is read from an instance rather than
|
|
11
|
+
* named, `node:crypto` exporting no Hash constructor. */
|
|
12
|
+
var _createHash = nodeCrypto.createHash;
|
|
13
|
+
var _hashProto = intrinsic.getPrototypeOf(_createHash("sha256"));
|
|
14
|
+
var _hashUpdate = intrinsic.uncurry(_hashProto.update);
|
|
15
|
+
var _hashDigest = intrinsic.uncurry(_hashProto.digest);
|
|
16
|
+
|
|
17
|
+
/** @internal The MAC operations, captured at load. `node:crypto` exports no Hmac constructor either,
|
|
18
|
+
* and an Hmac does not share the Hash prototype, so it gets its own pair. */
|
|
19
|
+
var _createHmac = nodeCrypto.createHmac;
|
|
20
|
+
var _hmacProto = intrinsic.getPrototypeOf(_createHmac("sha256", "x"));
|
|
21
|
+
var _hmacUpdate = intrinsic.uncurry(_hmacProto.update);
|
|
22
|
+
var _hmacDigest = intrinsic.uncurry(_hmacProto.digest);
|
|
23
|
+
|
|
24
|
+
/** @internal The native signature operations, captured at load. */
|
|
25
|
+
var _nodeVerify = nodeCrypto.verify;
|
|
26
|
+
var _nodeSign = nodeCrypto.sign;
|
|
27
|
+
|
|
28
|
+
/** @internal The constant-time comparison, captured at load. It is the operation a MAC verdict rests
|
|
29
|
+
* on, so a replacement answering `true` accepts any equal-length wrong MAC. */
|
|
30
|
+
var _timingSafeEqual = nodeCrypto.timingSafeEqual;
|
|
31
|
+
|
|
32
|
+
/** @internal The RSA padding and salt-length values, in one home rather than read from the module at
|
|
33
|
+
* each call site. MEASURED: this is NOT a capture against replacement, because there is nothing to
|
|
34
|
+
* capture against. `crypto.constants` is a non-writable, non-configurable property of the module and
|
|
35
|
+
* each value on it is non-writable and non-configurable too, so assignment silently fails,
|
|
36
|
+
* `defineProperty` throws, and replacing the whole object silently fails. What this gives is one place
|
|
37
|
+
* that answers which padding scheme a verb runs under, so a reader does not re-derive the question per
|
|
38
|
+
* module and a new module does not pick a value by copying a neighbor. */
|
|
39
|
+
var _nodeConstants = nodeCrypto.constants;
|
|
40
|
+
var padding = _freeze({
|
|
41
|
+
RSA_PKCS1: _nodeConstants.RSA_PKCS1_PADDING,
|
|
42
|
+
RSA_PKCS1_OAEP: _nodeConstants.RSA_PKCS1_OAEP_PADDING,
|
|
43
|
+
RSA_PKCS1_PSS: _nodeConstants.RSA_PKCS1_PSS_PADDING,
|
|
44
|
+
});
|
|
45
|
+
var saltLength = _freeze({
|
|
46
|
+
AUTO: _nodeConstants.RSA_PSS_SALTLEN_AUTO,
|
|
47
|
+
DIGEST: _nodeConstants.RSA_PSS_SALTLEN_DIGEST,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** @internal The key imports, captured at load. A verifier checks a signature against whatever key
|
|
51
|
+
* object it is handed, so the import decides the verdict as surely as the verifier does: one
|
|
52
|
+
* returning another genuine key of the same family makes a signature made by that key verify against
|
|
53
|
+
* the material a caller pinned. */
|
|
54
|
+
var _createPublicKey = nodeCrypto.createPublicKey;
|
|
55
|
+
var _createPrivateKey = nodeCrypto.createPrivateKey;
|
|
56
|
+
var _createSecretKey = nodeCrypto.createSecretKey;
|
|
57
|
+
|
|
58
|
+
/** @internal The three KeyObject `export` methods, captured at load. `node:crypto` exports no
|
|
59
|
+
* constructor carrying them: `KeyObject.prototype` has no `export`, and `PublicKeyObject`,
|
|
60
|
+
* `PrivateKeyObject` and `SecretKeyObject` each own one. So one sample pair and one secret key are
|
|
61
|
+
* made here to read the three prototypes from, measured at 0.02 ms, and the keys are discarded.
|
|
62
|
+
*
|
|
63
|
+
* The sample is a P-256 pair, and the generation is part of `require`. An algorithm outside the
|
|
64
|
+
* FIPS 140-3 boundary would therefore make the whole package unloadable under `crypto.setFips(1)`:
|
|
65
|
+
* OpenSSL's FIPS provider carries no Ed25519, so the generation raises ERR_OSSL_EVP_UNSUPPORTED
|
|
66
|
+
* before any export is reached, and a caller needing only approved RSA or EC operations could not
|
|
67
|
+
* require the package at all. The prototypes do not depend on the algorithm, measured across the
|
|
68
|
+
* Ed25519, P-256 and RSA pairs, so one approved pair yields both. */
|
|
69
|
+
var _sampleKeyPair = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
70
|
+
var _publicProto = intrinsic.getPrototypeOf(_sampleKeyPair.publicKey);
|
|
71
|
+
var _privateProto = intrinsic.getPrototypeOf(_sampleKeyPair.privateKey);
|
|
72
|
+
var _secretProto = intrinsic.getPrototypeOf(_createSecretKey(intrinsic.bufferAlloc(32)));
|
|
73
|
+
var _publicExport = intrinsic.uncurry(_publicProto.export);
|
|
74
|
+
var _privateExport = intrinsic.uncurry(_privateProto.export);
|
|
75
|
+
var _secretExport = intrinsic.uncurry(_secretProto.export);
|
|
76
|
+
|
|
77
|
+
/** A signature verified through the operation captured at load. Same arguments as `crypto.verify`.
|
|
78
|
+
*
|
|
79
|
+
* `crypto.verify` is an ordinary writable property of the module object, and it was read once per
|
|
80
|
+
* signature at every call site, so a replacement installed after the package loaded decided the
|
|
81
|
+
* verdict: one returning `true` made TUF metadata carrying a 64-byte all-zero signature meet its
|
|
82
|
+
* threshold against a genuine pinned key. That is the whole answer, not a detail of it.
|
|
83
|
+
*
|
|
84
|
+
* The shape excludes its own routing call: `guard.crypto.verify(` contains `crypto.verify(`, so
|
|
85
|
+
* without the lookbehind the detector fires on every consumer it is meant to bless.
|
|
86
|
+
*
|
|
87
|
+
* @enforced-by guard-shape-reinlined
|
|
88
|
+
* @guard-shape (?<!guard\.)(?:nodeCrypto|crypto)\.verify\s*\(
|
|
89
|
+
*/
|
|
90
|
+
function verify(algorithm, data, key, signature) {
|
|
91
|
+
return _nodeVerify(algorithm, data, key, signature);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A signature produced through the operation captured at load. Same arguments as `crypto.sign`.
|
|
95
|
+
*
|
|
96
|
+
* Captured for the reason `verify` above gives: a replaced signer produces a signature over bytes
|
|
97
|
+
* the caller did not ask about, and the key-pair probe in `pki.key` checks a signature it made
|
|
98
|
+
* itself, so a replacement that agreed with its own forgery would pass the probe.
|
|
99
|
+
*
|
|
100
|
+
* @enforced-by guard-shape-reinlined
|
|
101
|
+
* @guard-shape (?<!guard\.)(?:nodeCrypto|crypto)\.sign\s*\(
|
|
102
|
+
*/
|
|
103
|
+
function sign(algorithm, data, key) {
|
|
104
|
+
return _nodeSign(algorithm, data, key);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A digest taken through operations captured at load. `createHash(name).update(b).digest()` reaches
|
|
108
|
+
* `update` and `digest` off the live hash prototype, both ordinary writable properties, so a
|
|
109
|
+
* replacement decides what the digest is OVER. Measured: a replaced `update` that filled the content
|
|
110
|
+
* buffer before forwarding put the digest of other bytes into the attribute set
|
|
111
|
+
* `pki.cms.authenticate` computes its MAC over, and the message then failed to verify against the
|
|
112
|
+
* content it carries. A digest in this toolkit names a key, binds a certificate, or is the value a
|
|
113
|
+
* signature covers, so the operation that computes it is not the caller's to choose.
|
|
114
|
+
*
|
|
115
|
+
* @enforced-by behavioral -- the shape detector for this one is written and NOT armed. Its rule is
|
|
116
|
+
* `createHash\s*\([^)]*\)\s*\.(?:update|digest)\s*\(`, which is rename-proof and would catch the class
|
|
117
|
+
* anywhere, and 19 sites across 11 lib modules still carry it, so arming it now would fail the gate on
|
|
118
|
+
* a clean tree. It arms with that migration. A module that captures `createHash` into a local and then
|
|
119
|
+
* chains off it is the worst of the three states, since it reads as converted and is not: those are
|
|
120
|
+
* gone. Until then the guard is the RED conformance vector: a
|
|
121
|
+
* replaced hash `update` cannot choose the content a message digest covers, driven through
|
|
122
|
+
* `pki.cms.authenticate` and read back with `pki.cms.decrypt`.
|
|
123
|
+
*/
|
|
124
|
+
function digest(name, bytes) {
|
|
125
|
+
return _hashDigest(_hashUpdate(_createHash(name), bytes));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A digest over SEVERAL parts, fed in order through the operations captured at load. It is `digest`
|
|
129
|
+
* for a preimage the caller assembles as a list rather than as one buffer, which keeps a multi-part
|
|
130
|
+
* hash from having to concatenate first: a transparency-log key identifier is a digest over a name, a
|
|
131
|
+
* separator, an algorithm byte and the key, and the identifier is what matches a signature line to a
|
|
132
|
+
* caller's key.
|
|
133
|
+
*
|
|
134
|
+
* A part is passed to the captured `update` as it is, so a string part is read as UTF-8 exactly as
|
|
135
|
+
* `hash.update(string)` would read it.
|
|
136
|
+
*
|
|
137
|
+
* @enforced-by behavioral -- shares the unarmed shape `digest` below describes.
|
|
138
|
+
*/
|
|
139
|
+
function digestParts(name, parts) {
|
|
140
|
+
var h = _createHash(name);
|
|
141
|
+
for (var i = 0; i < parts.length; i++) _hashUpdate(h, parts[i]);
|
|
142
|
+
return _hashDigest(h);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** An extendable-output function squeezed to `outLen` bytes, through the operations captured at load.
|
|
146
|
+
* A single-stage KDF derives a key pair and the HPKE key schedule's secrets this way, so a replaced
|
|
147
|
+
* `update` chooses what the derivation covers and a replaced `digest` chooses the key material itself.
|
|
148
|
+
* Measured: an `update` returning its receiver without forwarding made two X25519/SHAKE128 setups with
|
|
149
|
+
* different encapsulations derive identical exports, so a session key no longer depended on the
|
|
150
|
+
* encapsulation it was negotiated under.
|
|
151
|
+
*
|
|
152
|
+
* @enforced-by behavioral -- shares the unarmed shape `digest` above describes. The guard is the RED
|
|
153
|
+
* conformance vector: two HPKE setups whose encapsulations differ must not export the same secret
|
|
154
|
+
* while the hash prototype is replaced.
|
|
155
|
+
*/
|
|
156
|
+
function xof(name, bytes, outLen) {
|
|
157
|
+
return _hashDigest(_hashUpdate(_createHash(name, { outputLength: outLen }), bytes));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** A MAC over `bytes` under `key`, through the operations captured at load. HKDF-Extract and each
|
|
161
|
+
* HKDF-Expand round are MACs, so a replacement here decides the pseudo-random key an entire key
|
|
162
|
+
* schedule hangs off, and the HPKE and CMC paths both derive their traffic keys through it.
|
|
163
|
+
*
|
|
164
|
+
* @enforced-by behavioral -- the Hmac prototype's `update` and `digest` are ordinary writable
|
|
165
|
+
* properties, with the same absence of a rename-proof shape as `digest` above. The guard is the RED
|
|
166
|
+
* conformance vector over the key schedule.
|
|
167
|
+
*/
|
|
168
|
+
function hmac(name, key, bytes) {
|
|
169
|
+
return _hmacDigest(_hmacUpdate(_createHmac(name, key), bytes));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** A key imported through the operation captured at load. Same arguments as `crypto.createPublicKey`,
|
|
173
|
+
* `crypto.createPrivateKey` and `crypto.createSecretKey`.
|
|
174
|
+
*
|
|
175
|
+
* @enforced-by behavioral -- the shape `nodeCrypto\.create(?:Public|Private|Secret)Key\s*\(` is
|
|
176
|
+
* rename-proof and would catch the class anywhere, and 42 sites across 9 lib modules still reach the
|
|
177
|
+
* module property directly on the key-agreement, KEM and key-format paths, so arming it now would fail
|
|
178
|
+
* the gate on a clean tree. It arms with that migration. Until then the guard is the RED conformance
|
|
179
|
+
* vector: a replaced `crypto.createPublicKey` returning another genuine key cannot substitute the key a
|
|
180
|
+
* signature is verified against, driven through `pki.tlog.verifyNote` and `pki.key.publicFromPrivate`.
|
|
181
|
+
*/
|
|
182
|
+
function publicKey(spec) { return _createPublicKey(spec); }
|
|
183
|
+
|
|
184
|
+
// @enforced-by behavioral -- shares the migration note on `publicKey` above; the signer's own key is
|
|
185
|
+
function privateKey(spec) { return _createPrivateKey(spec); }
|
|
186
|
+
|
|
187
|
+
// @enforced-by behavioral -- shares the migration note on `publicKey` above; a MAC key substituted at
|
|
188
|
+
function secretKey(material) { return _createSecretKey(material); }
|
|
189
|
+
|
|
190
|
+
/** A KeyObject exported through the operation captured at load. `keyObject.export(spec)` reaches the
|
|
191
|
+
* method off the live prototype, so a replacement decides the bytes a caller is handed: one exporting
|
|
192
|
+
* a fixed value made two distinct authorized TUF keys carry the same material identifier, which took a
|
|
193
|
+
* two-key threshold down to one signature. The method is chosen by the key's PROTOTYPE rather than by
|
|
194
|
+
* its `type`, which is itself a replaceable accessor, and an object whose prototype is none of the
|
|
195
|
+
* three is refused rather than guessed at.
|
|
196
|
+
*
|
|
197
|
+
* @enforced-by behavioral -- shares the migration note on the imports above.
|
|
198
|
+
*/
|
|
199
|
+
function exportKey(keyObject, spec) {
|
|
200
|
+
var proto = keyObject === null || keyObject === undefined ? null : intrinsic.getPrototypeOf(keyObject);
|
|
201
|
+
if (proto === _publicProto) return _publicExport(keyObject, spec);
|
|
202
|
+
if (proto === _privateProto) return _privateExport(keyObject, spec);
|
|
203
|
+
if (proto === _secretProto) return _secretExport(keyObject, spec);
|
|
204
|
+
throw new TypeError("guard.crypto.exportKey: expects a node:crypto KeyObject");
|
|
205
|
+
}
|
|
8
206
|
|
|
9
207
|
// @enforced-by guard-shape-reinlined
|
|
10
208
|
// @guard-shape \.timingSafeEqual\s*\(
|
|
11
209
|
function constantTimeEqual(a, b) {
|
|
12
|
-
return a.length === b.length &&
|
|
210
|
+
return a.length === b.length && _timingSafeEqual(a, b);
|
|
13
211
|
}
|
|
14
212
|
|
|
15
213
|
// @enforced-by behavioral -- shares the octet-alignment rule below; a bare
|
|
@@ -25,4 +223,8 @@ function assertOctetAligned(bitString, E, code, label) {
|
|
|
25
223
|
return bitString;
|
|
26
224
|
}
|
|
27
225
|
|
|
28
|
-
module.exports = _freeze({ constantTimeEqual: constantTimeEqual,
|
|
226
|
+
module.exports = _freeze({ constantTimeEqual: constantTimeEqual, digest: digest,
|
|
227
|
+
digestParts: digestParts, xof: xof, hmac: hmac, verify: verify, sign: sign,
|
|
228
|
+
publicKey: publicKey, privateKey: privateKey, secretKey: secretKey, exportKey: exportKey,
|
|
229
|
+
padding: padding, saltLength: saltLength,
|
|
230
|
+
isOctetAligned: isOctetAligned, assertOctetAligned: assertOctetAligned });
|
package/lib/guard-encoding.js
CHANGED
|
@@ -142,7 +142,12 @@ function percentEncode(bytes, E, code, label) {
|
|
|
142
142
|
|
|
143
143
|
/** @internal One path segment of a URI, built from `text` read as its latin1 bytes. Every byte
|
|
144
144
|
* outside the unreserved set becomes a triplet, so a value carrying `/`, `?`, `#` or a percent of
|
|
145
|
-
* its own cannot reach past the segment it was placed in.
|
|
145
|
+
* its own cannot reach past the segment it was placed in.
|
|
146
|
+
*
|
|
147
|
+
* @enforced-by behavioral -- the encoding itself is `percentEncode`, whose shape detector catches a
|
|
148
|
+
* re-inlined triplet anywhere in lib/; what this adds is reading the text as its latin1 bytes, which is a
|
|
149
|
+
* conversion with no shape of its own, pinned by the vector that a character above 0x7f encodes as the
|
|
150
|
+
* bytes latin1 gives it rather than as its UTF-8 pair */
|
|
146
151
|
function uriPathSegment(text, E, code, label) {
|
|
147
152
|
if (typeof text !== "string") {
|
|
148
153
|
throw _rawError(E)(code, (label || "URI path segment") + " must be a string");
|
package/lib/guard-identifier.js
CHANGED
|
@@ -40,7 +40,8 @@ var _Number = intrinsic.Number;
|
|
|
40
40
|
var _BigInt = intrinsic.BigInt;
|
|
41
41
|
var _Object = intrinsic.Object;
|
|
42
42
|
var _BufferProto = intrinsic.BufferProto;
|
|
43
|
-
var
|
|
43
|
+
var guardList = require("./guard-list");
|
|
44
|
+
var _append = guardList.append;
|
|
44
45
|
|
|
45
46
|
function _isDottedDecimal(str) {
|
|
46
47
|
if (str.length === 0) return false;
|
|
@@ -108,9 +109,16 @@ function snapshotOptions(obj, known) {
|
|
|
108
109
|
function optionNames(obj, known) {
|
|
109
110
|
if (obj === null || obj === undefined) return [];
|
|
110
111
|
var out = [];
|
|
111
|
-
|
|
112
|
+
var o = _Object(obj);
|
|
113
|
+
_forEach(_ownKeys(o), function (k) {
|
|
112
114
|
if (typeof k !== "string") return;
|
|
113
|
-
|
|
115
|
+
var recognized = known !== null && known !== undefined && _hasOwn(known, k);
|
|
116
|
+
if (known !== null && known !== undefined && !recognized) return;
|
|
117
|
+
/** @internal The non-thenable mask is the object's machinery rather than one of its options, and a
|
|
118
|
+
* caller building its list of named forms from this found one more form than the object carries.
|
|
119
|
+
* A caller that RECOGNIZES the name means it as content, so the exemption stands aside: the whole
|
|
120
|
+
* mechanism is about a name nothing asked for. */
|
|
121
|
+
if (!recognized && _isOwnStructuralName(o, k, _getOwnPropertyDescriptor(o, k), false)) return;
|
|
114
122
|
_append(out, k);
|
|
115
123
|
});
|
|
116
124
|
return out;
|
|
@@ -127,6 +135,10 @@ function ownOptions(obj) {
|
|
|
127
135
|
var o = _Object(obj);
|
|
128
136
|
_forEach(_ownKeys(o), function (k) {
|
|
129
137
|
if (typeof k !== "string") return;
|
|
138
|
+
/** @internal The copy has a null prototype, so there is no inherited `then` for a non-thenable
|
|
139
|
+
* mask to stand in front of, and copying one in would turn a hidden name holding `undefined` into
|
|
140
|
+
* a visible option the next door reports as unknown. */
|
|
141
|
+
if (_isOwnStructuralName(o, k, _getOwnPropertyDescriptor(o, k), false)) return;
|
|
130
142
|
out[k] = o[k];
|
|
131
143
|
});
|
|
132
144
|
return out;
|
|
@@ -153,7 +165,11 @@ function _requireFactory(E) {
|
|
|
153
165
|
|
|
154
166
|
function _refuseUnenumerable(v, E, code, label) {
|
|
155
167
|
_requireFactory(E);
|
|
156
|
-
|
|
168
|
+
/** @internal A Proxy whose TARGET is a function reports `typeof "function"`, so a receiver tested for
|
|
169
|
+
* `typeof !== "object"` was returned on unexamined: one hid an own `then` through its `ownKeys` trap
|
|
170
|
+
* and the name walk above never saw it. A callable receiver is examined the same way; a plain
|
|
171
|
+
* function is not a Proxy and still passes through. */
|
|
172
|
+
if (v === null || (typeof v !== "object" && typeof v !== "function")) return;
|
|
157
173
|
var visited = new _Set();
|
|
158
174
|
for (var o = v; o && !_setHas(visited, o); o = _getPrototypeOf(o)) {
|
|
159
175
|
_setAdd(visited, o);
|
|
@@ -209,6 +225,10 @@ var _TYPED_ARRAY_PROTO = _getPrototypeOf(Uint8Array.prototype);
|
|
|
209
225
|
var _INTRINSIC_HOLDERS = (function () {
|
|
210
226
|
var m = new _Map([[_TYPED_ARRAY_PROTO, _TYPED_PROTO_MEMBERS]]);
|
|
211
227
|
function pair(ctor, members) { if (ctor && ctor.prototype) _mapSet(m, ctor.prototype, members); }
|
|
228
|
+
/** @internal This map IS the record of what the real intrinsic prototypes were when the package
|
|
229
|
+
* loaded, so reading a global here is the capture rather than a call-time read of one: a
|
|
230
|
+
* replacement installed afterwards cannot change a map already built. */
|
|
231
|
+
// allow:guard-reads-runtime-live -- module-init, see above
|
|
212
232
|
pair(Array, _ARRAY_PROTO_MEMBERS);
|
|
213
233
|
pair(RegExp, _REGEXP_PROTO_MEMBERS);
|
|
214
234
|
_forEach([Map, Set], function (c) { pair(c, _SIZED_PROTO_MEMBERS); });
|
|
@@ -216,6 +236,7 @@ var _INTRINSIC_HOLDERS = (function () {
|
|
|
216
236
|
pair(ArrayBuffer, _ARRAYBUFFER_PROTO_MEMBERS);
|
|
217
237
|
pair(typeof SharedArrayBuffer === "function" ? SharedArrayBuffer : null, _SHARED_PROTO_MEMBERS);
|
|
218
238
|
pair(DataView, _DATAVIEW_PROTO_MEMBERS);
|
|
239
|
+
// allow:guard-reads-runtime-live -- module-init, see above
|
|
219
240
|
pair(Buffer, _BUFFER_PROTO_MEMBERS);
|
|
220
241
|
_forEach(_getOwnPropertyNames(globalThis), function (n) {
|
|
221
242
|
var d = _getOwnPropertyDescriptor(globalThis, n);
|
|
@@ -256,8 +277,24 @@ function _kindMembers(v) {
|
|
|
256
277
|
return [];
|
|
257
278
|
}
|
|
258
279
|
|
|
259
|
-
function _isOwnStructuralName(v, k) {
|
|
260
|
-
|
|
280
|
+
function _isOwnStructuralName(v, k, d, inherited) {
|
|
281
|
+
if (util.types.isRegExp(v) && k === "lastIndex") return true;
|
|
282
|
+
/** @internal The non-thenable mask `guard.verdict.shield` leaves on a value the toolkit hands back:
|
|
283
|
+
* a non-enumerable own `then` holding `undefined`. It is part of the object's machinery rather than
|
|
284
|
+
* its content, and a name holding `undefined` answers nothing to a reader either way, so counting it
|
|
285
|
+
* had the toolkit refuse its OWN output: `pki.jose.parseJson` returns a shielded object, and a
|
|
286
|
+
* caller passing one as an options bag was told `then` was an unknown option while the same document
|
|
287
|
+
* through `JSON.parse` was accepted. An own `then` holding a VALUE is content and is still counted,
|
|
288
|
+
* so a document that really carries one is still reported. */
|
|
289
|
+
/** @internal A DATA descriptor, asked for the way `shield` asks: a descriptor carrying no `value`
|
|
290
|
+
* key is an accessor, and `{get: undefined, set: undefined}` is an accessor whose reads answer
|
|
291
|
+
* `undefined`, which `!d.get && !d.set` alone mistook for data.
|
|
292
|
+
* And a descriptor only means what it says when the object answering for it is a real one. A Proxy
|
|
293
|
+
* reports whatever its trap returns, so one reporting the mask's shape for a `then` that reads 42
|
|
294
|
+
* would have the name hidden and the value live. A Proxy is refused at every door that validates;
|
|
295
|
+
* here it simply does not qualify. */
|
|
296
|
+
return !inherited && k === "then" && !!d && _hasOwn(d, "value") &&
|
|
297
|
+
d.enumerable === false && d.value === undefined && !util.types.isProxy(v);
|
|
261
298
|
}
|
|
262
299
|
|
|
263
300
|
function _looksIntrinsic(d) {
|
|
@@ -292,8 +329,8 @@ function _readableNames(obj) {
|
|
|
292
329
|
if (seen[k]) return;
|
|
293
330
|
if (indexedSelf && typeof k === "string" && _isArrayIndex(k)) return;
|
|
294
331
|
if (indexedSelf && k === "length" && !inherited) return;
|
|
295
|
-
if (_isOwnStructuralName(obj, k)) return;
|
|
296
332
|
var d = _getOwnPropertyDescriptor(o, k);
|
|
333
|
+
if (_isOwnStructuralName(obj, k, d, inherited)) return;
|
|
297
334
|
var here = _holderMembers(o);
|
|
298
335
|
if (inherited && _looksIntrinsic(d) && !_shadowsAnother(obj, k, o) &&
|
|
299
336
|
(here === null ? _indexOf(members, k) !== -1 : _indexOf(here, k) !== -1)) return;
|
|
@@ -364,6 +401,11 @@ function _proxyInChain(v) {
|
|
|
364
401
|
}
|
|
365
402
|
return false;
|
|
366
403
|
}
|
|
404
|
+
/** @internal The predicate form of `assertPlainRecord`, for a caller deciding rather than refusing.
|
|
405
|
+
*
|
|
406
|
+
* @enforced-by behavioral -- a plain-record test has no single rename-proof shape that a legitimate
|
|
407
|
+
* `typeof v === "object"` does not also match; the vectors driving a Proxy, an array, a Buffer and a
|
|
408
|
+
* built-in exotic through it are the guard */
|
|
367
409
|
function isPlainRecord(v) {
|
|
368
410
|
if (v === null || typeof v !== "object") return false;
|
|
369
411
|
if (_proxyInChain(v)) return false;
|
|
@@ -402,7 +444,12 @@ function _descriptorHolder(v, k) {
|
|
|
402
444
|
|
|
403
445
|
// @enforced-by behavioral -- reading an accessor before it is refused has no rename-proof code
|
|
404
446
|
function refuseAccessorFields(obj, names, E, code, label) {
|
|
405
|
-
|
|
447
|
+
/** @internal The count is narrowed where it is taken, as every walk in `guard.list` is. This is a
|
|
448
|
+
* DOOR, and `i < names.length` asked a caller's list for its length on every comparison: a Proxy over
|
|
449
|
+
* `["a", "secret"]` whose length coerced to 2 and then to 0 had this walk pass the accessor named
|
|
450
|
+
* `secret` and return as though it had checked it. */
|
|
451
|
+
var n = guardList.count(names, "refuseAccessorFields");
|
|
452
|
+
for (var i = 0; i < n; i++) {
|
|
406
453
|
var holder = _descriptorHolder(obj, names[i]);
|
|
407
454
|
if (holder && (holder.get || holder.set)) {
|
|
408
455
|
throw E(code, label + " supplies " +
|
package/lib/guard-intrinsic.js
CHANGED
|
@@ -47,8 +47,27 @@ var _mathCeil = Math.ceil;
|
|
|
47
47
|
var _mathMin = Math.min;
|
|
48
48
|
var _mathMax = Math.max;
|
|
49
49
|
var _parseInt = parseInt;
|
|
50
|
+
/** @internal The promise constructor itself, captured at load. `Promise.resolve` and `Promise.reject`
|
|
51
|
+
* are captured below, but they build their promise from the RECEIVER they are called on, so reading
|
|
52
|
+
* the global binding at the call hands the construction to whatever that binding holds. A verdict
|
|
53
|
+
* returned as `Promise.resolve(false)` from a hostile constructor is not a refusal. */
|
|
54
|
+
var _Promise = Promise;
|
|
50
55
|
var _promiseResolve = Promise.resolve;
|
|
51
56
|
var _promiseReject = Promise.reject;
|
|
57
|
+
/** @internal The aggregators, captured alongside them. `all` is what assembles a verdict out of
|
|
58
|
+
* component results, so a replacement that resolves a fabricated array never awaits the components
|
|
59
|
+
* and hands its own values to the caller as the verdict. All four are captured together: a member
|
|
60
|
+
* with no captured form is a member that stays live. */
|
|
61
|
+
var _promiseAll = Promise.all;
|
|
62
|
+
var _promiseAllSettled = Promise.allSettled;
|
|
63
|
+
var _promiseRace = Promise.race;
|
|
64
|
+
var _promiseAny = Promise.any;
|
|
65
|
+
/** @internal The factory methods, captured for the same reason. `withResolvers` hands back the resolve
|
|
66
|
+
* and reject capability for a promise it built, and `try` decides whether the callback runs at all, so
|
|
67
|
+
* a replacement of either chooses what the caller goes on to settle or never settles. Both are
|
|
68
|
+
* present on the supported runtime floor. */
|
|
69
|
+
var _promiseTry = Promise["try"];
|
|
70
|
+
var _promiseWithResolvers = Promise.withResolvers;
|
|
52
71
|
var _asyncIterator = Symbol.asyncIterator;
|
|
53
72
|
|
|
54
73
|
var _arrForEach = uncurry(Array.prototype.forEach);
|
|
@@ -57,11 +76,23 @@ var _arrFilter = uncurry(Array.prototype.filter);
|
|
|
57
76
|
var _arrEvery = uncurry(Array.prototype.every);
|
|
58
77
|
var _arrSome = uncurry(Array.prototype.some);
|
|
59
78
|
var _arrIndexOf = uncurry(Array.prototype.indexOf);
|
|
79
|
+
/** @internal The in-place sort, captured at load. A DER SET OF is ordered by its encoded bytes, so the
|
|
80
|
+
* sort decides the bytes emitted: MEASURED, a replaced `slice` or `sort` made an attribute set encode a
|
|
81
|
+
* member other than the one that was validated, on a path whose output is then signed. */
|
|
60
82
|
var _arrSort = uncurry(Array.prototype.sort);
|
|
61
83
|
var _arrConcat = uncurry(Array.prototype.concat);
|
|
62
84
|
var _arrSlice = uncurry(Array.prototype.slice);
|
|
63
85
|
var _arrJoin = uncurry(Array.prototype.join);
|
|
64
86
|
var _arrPush = uncurry(Array.prototype.push);
|
|
87
|
+
/** @internal The mutators. A list is what a rule is enforced over, so an operation that drops or moves
|
|
88
|
+
* an element decides whether the rule reaches it: a replaced `pop` that popped twice took a signed
|
|
89
|
+
* note's last signature line away with the empty tail element, and the forged signature on that line
|
|
90
|
+
* was never checked. */
|
|
91
|
+
var _arrPop = uncurry(Array.prototype.pop);
|
|
92
|
+
var _arrShift = uncurry(Array.prototype.shift);
|
|
93
|
+
var _arrUnshift = uncurry(Array.prototype.unshift);
|
|
94
|
+
var _arrSplice = uncurry(Array.prototype.splice);
|
|
95
|
+
var _arrReverse = uncurry(Array.prototype.reverse);
|
|
65
96
|
|
|
66
97
|
var _strToUpperCase = uncurry(String.prototype.toUpperCase);
|
|
67
98
|
var _strToLowerCase = uncurry(String.prototype.toLowerCase);
|
|
@@ -179,6 +210,11 @@ module.exports = Object.freeze({
|
|
|
179
210
|
max: _mathMax,
|
|
180
211
|
parseInt: _parseInt,
|
|
181
212
|
push: _arrPush,
|
|
213
|
+
pop: _arrPop,
|
|
214
|
+
shift: _arrShift,
|
|
215
|
+
unshift: _arrUnshift,
|
|
216
|
+
splice: _arrSplice,
|
|
217
|
+
reverse: _arrReverse,
|
|
182
218
|
toUpperCase: _strToUpperCase,
|
|
183
219
|
toLowerCase: _strToLowerCase,
|
|
184
220
|
stringIndexOf: _strIndexOf,
|
|
@@ -186,8 +222,15 @@ module.exports = Object.freeze({
|
|
|
186
222
|
arrayBufferSlice: _abSlice,
|
|
187
223
|
numberToString: _numToString,
|
|
188
224
|
bigIntToString: _bigIntToString,
|
|
225
|
+
Promise: _Promise,
|
|
189
226
|
promiseResolve: _promiseResolve,
|
|
190
227
|
promiseReject: _promiseReject,
|
|
228
|
+
promiseAll: _promiseAll,
|
|
229
|
+
promiseAllSettled: _promiseAllSettled,
|
|
230
|
+
promiseRace: _promiseRace,
|
|
231
|
+
promiseAny: _promiseAny,
|
|
232
|
+
promiseTry: _promiseTry,
|
|
233
|
+
promiseWithResolvers: _promiseWithResolvers,
|
|
191
234
|
asyncIterator: _asyncIterator,
|
|
192
235
|
forEach: _arrForEach,
|
|
193
236
|
map: _arrMap,
|
package/lib/guard-json.js
CHANGED
|
@@ -11,6 +11,10 @@ var _strSlice = require("./guard-intrinsic").uncurry(String.prototype.slice);
|
|
|
11
11
|
var _hasOwn = require("./guard-intrinsic").hasOwn;
|
|
12
12
|
var _Number = require("./guard-intrinsic").Number;
|
|
13
13
|
var _isFinite = require("./guard-intrinsic").isFinite;
|
|
14
|
+
var _BigInt = require("./guard-intrinsic").BigInt;
|
|
15
|
+
/** @internal The largest integer a double holds exactly, as a BigInt, for `spec.exactIntegers`. Past it a
|
|
16
|
+
* value reads back as a different one: 2^53 + 1 converts to 2^53. */
|
|
17
|
+
var _MAX_EXACT = _BigInt(Number.MAX_SAFE_INTEGER);
|
|
14
18
|
var _stringify = require("./guard-intrinsic").stringify;
|
|
15
19
|
var _append = require("./guard-list").append;
|
|
16
20
|
var limits = require("./guard-limits");
|
|
@@ -46,6 +50,9 @@ function parse(input, ErrorClass, spec) {
|
|
|
46
50
|
fail("unexpected token");
|
|
47
51
|
return undefined;
|
|
48
52
|
}
|
|
53
|
+
/** @internal The member name whose value is being parsed, or null at the top level and inside an array.
|
|
54
|
+
* Read only by the number policy below. */
|
|
55
|
+
var currentMember = null;
|
|
49
56
|
function object(depth) {
|
|
50
57
|
i++;
|
|
51
58
|
var out = {};
|
|
@@ -59,7 +66,17 @@ function parse(input, ErrorClass, spec) {
|
|
|
59
66
|
ws();
|
|
60
67
|
if (str[i] !== ":") fail("expected ':'");
|
|
61
68
|
i++;
|
|
62
|
-
|
|
69
|
+
/** @internal The member name is carried into the value so a per-member number policy can apply to
|
|
70
|
+
* the members that need it. A document can be ordinary JSON, where a fractional number is legal
|
|
71
|
+
* somewhere in it, and still read ONE member as an integer: the FIDO metadata BLOB carries
|
|
72
|
+
* biometric false-accept rates as genuine doubles beside a rollback counter that must be an exact
|
|
73
|
+
* integer, so a document-wide rule refuses the real catalogue and no rule at all leaves the
|
|
74
|
+
* counter readable as a rounded spelling. */
|
|
75
|
+
var outerMember = currentMember;
|
|
76
|
+
currentMember = key;
|
|
77
|
+
var memberValue;
|
|
78
|
+
try { memberValue = value(depth + 1); } finally { currentMember = outerMember; }
|
|
79
|
+
_defineProperty(out, key, { value: memberValue, writable: true, enumerable: true, configurable: true });
|
|
63
80
|
ws();
|
|
64
81
|
if (str[i] === ",") { i++; continue; }
|
|
65
82
|
if (str[i] === "}") { i++; return out; }
|
|
@@ -71,13 +88,21 @@ function parse(input, ErrorClass, spec) {
|
|
|
71
88
|
var out = [];
|
|
72
89
|
ws();
|
|
73
90
|
if (str[i] === "]") { i++; return out; }
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
/** @internal The enclosing member's name does not reach the elements. A per-member number policy names
|
|
92
|
+
* a member whose VALUE is an integer, so a member holding an array holds no number for the rule to
|
|
93
|
+
* apply to, and letting the name reach inside would constrain a whole subtree from one name: the
|
|
94
|
+
* document-wide rule in miniature, and the shape that refuses a conforming document. */
|
|
95
|
+
var outerMember = currentMember;
|
|
96
|
+
currentMember = null;
|
|
97
|
+
try {
|
|
98
|
+
for (;;) {
|
|
99
|
+
_append(out, value(depth + 1));
|
|
100
|
+
ws();
|
|
101
|
+
if (str[i] === ",") { i++; continue; }
|
|
102
|
+
if (str[i] === "]") { i++; return out; }
|
|
103
|
+
fail("expected ',' or ']'");
|
|
104
|
+
}
|
|
105
|
+
} finally { currentMember = outerMember; }
|
|
81
106
|
}
|
|
82
107
|
function string() {
|
|
83
108
|
i++;
|
|
@@ -121,18 +146,106 @@ function parse(input, ErrorClass, spec) {
|
|
|
121
146
|
var intLen = i - intStart;
|
|
122
147
|
if (intLen === 0) fail("malformed number");
|
|
123
148
|
if (intLen > 1 && str[intStart] === "0") fail("malformed number");
|
|
149
|
+
var intDigits = _strSlice(str, intStart, i);
|
|
150
|
+
var fracDigits = "";
|
|
151
|
+
var expValue = 0;
|
|
152
|
+
var notAnInteger = false;
|
|
124
153
|
if (str[i] === ".") {
|
|
125
154
|
i++;
|
|
126
155
|
var fracStart = i;
|
|
127
156
|
while (i < n && str[i] >= "0" && str[i] <= "9") i++;
|
|
128
157
|
if (i === fracStart) fail("malformed number");
|
|
158
|
+
fracDigits = _strSlice(str, fracStart, i);
|
|
159
|
+
notAnInteger = true;
|
|
129
160
|
}
|
|
130
161
|
if (str[i] === "e" || str[i] === "E") {
|
|
131
162
|
i++;
|
|
132
|
-
|
|
163
|
+
var expNeg = false;
|
|
164
|
+
if (str[i] === "+") i++;
|
|
165
|
+
else if (str[i] === "-") { expNeg = true; i++; }
|
|
133
166
|
var expStart = i;
|
|
134
167
|
while (i < n && str[i] >= "0" && str[i] <= "9") i++;
|
|
135
168
|
if (i === expStart) fail("malformed number");
|
|
169
|
+
/** @internal The exponent is read as written, with no bound on its spelling. Bounding the character
|
|
170
|
+
* COUNT refuses valid numbers: `1e0000000` is 1 and `0e1000000` is 0, both exactly representable,
|
|
171
|
+
* and the bound applied to every caller whether or not one asked for an integer policy. Nothing
|
|
172
|
+
* below needs the bound, because the exact-integer decision is taken from digit COUNTS and never
|
|
173
|
+
* expands the value, and a magnitude that overflows a double is caught by the `_isFinite` check
|
|
174
|
+
* that every number already passes through. */
|
|
175
|
+
expValue = _Number(_strSlice(str, expStart, i));
|
|
176
|
+
if (expNeg) expValue = -expValue;
|
|
177
|
+
notAnInteger = true;
|
|
178
|
+
}
|
|
179
|
+
/** @internal `spec.integersOnly` refuses a fractional or exponent-form token HERE, while the token is
|
|
180
|
+
* still text. Conversion to a Number loses the distinction: 1.0000000000000001 becomes 1, so a check
|
|
181
|
+
* for an integer afterwards sees one and a format that permits only integers has been read as
|
|
182
|
+
* conforming. A caller asks for this when its own format forbids floating point. */
|
|
183
|
+
if (spec.integersOnly && notAnInteger) {
|
|
184
|
+
fail("a fractional or exponent-form number, where this format admits only integers");
|
|
185
|
+
}
|
|
186
|
+
/** @internal `spec.exactIntegers` is the laxer rule, for a format that is ordinary JSON but will treat
|
|
187
|
+
* this value as an integer. It refuses nothing a producer may legally write: `1e3`, `1.0` and
|
|
188
|
+
* `1500e-2` all denote integers and are accepted. What it refuses is a spelling whose VALUE is not
|
|
189
|
+
* exactly its own integer, which conversion to a Number cannot be asked about afterwards:
|
|
190
|
+
* `1.0000000000000001` becomes 1, so an `isInteger` check made after conversion sees a conforming
|
|
191
|
+
* document and the value the caller reports is not the spelling the producer sent. Where that value
|
|
192
|
+
* is folded back into a binary preimage, as a CT tree size is, the rounded spelling verifies.
|
|
193
|
+
*
|
|
194
|
+
* Decided on the DIGITS, with no float arithmetic: the token is `digits x 10^scale`, so it denotes an
|
|
195
|
+
* integer exactly when the scale is non-negative or when every digit the scale would drop is a zero.
|
|
196
|
+
* The resulting integer must also be one a double holds exactly, since 2^53 + 1 reads back as 2^53
|
|
197
|
+
* and that is the same loss by another route. */
|
|
198
|
+
/** @internal The rule applies to every number when `spec.exactIntegers` is set, and to the named
|
|
199
|
+
* members alone when `spec.exactIntegerMembers` is. The second form is for a document that is
|
|
200
|
+
* ordinary JSON, where a fractional number is legal somewhere in it, but reads one member as an
|
|
201
|
+
* integer: the FIDO metadata BLOB is that, and the live catalogue carries ten genuinely non-integral
|
|
202
|
+
* biometric rates, so the document-wide rule refuses it outright. Verified against the published
|
|
203
|
+
* BLOB rather than against the specification text. */
|
|
204
|
+
var exactWanted = spec.exactIntegers ||
|
|
205
|
+
(spec.exactIntegerMembers && currentMember !== null && _hasOwn(spec.exactIntegerMembers, currentMember));
|
|
206
|
+
if (exactWanted) {
|
|
207
|
+
var digits = intDigits + fracDigits;
|
|
208
|
+
var scale = expValue - fracDigits.length;
|
|
209
|
+
/** @internal Decided from the DIGIT COUNT and the scale, never by expanding the value. Computing
|
|
210
|
+
* `digits x 10^scale` to find out whether it is an integer means building the power first, and an
|
|
211
|
+
* exponent is six digits wide: `0e999999` expands to a million digits and then multiplies by a
|
|
212
|
+
* zero mantissa, so it passed, and a document can carry one such token per few bytes of its size
|
|
213
|
+
* cap. Measured at 1566 ms for a 907-byte CT response before this. Nothing below constructs a
|
|
214
|
+
* BigInt until the value is known to be small. */
|
|
215
|
+
var lead = 0;
|
|
216
|
+
while (lead < digits.length && digits[lead] === "0") lead++;
|
|
217
|
+
if (lead === digits.length) {
|
|
218
|
+
/** @internal Every digit is a zero, so the value is zero whatever the scale, and zero is an
|
|
219
|
+
* exact integer. Taken here because it is also the shape that made the expansion free to
|
|
220
|
+
* request: `0e999999` is this case. */
|
|
221
|
+
var zeroValue = _Number(_strSlice(str, start, i));
|
|
222
|
+
if (!_isFinite(zeroValue)) fail("bad number");
|
|
223
|
+
return zeroValue;
|
|
224
|
+
}
|
|
225
|
+
/** @internal The significant digits, and the trailing zeros among them that the scale may cancel.
|
|
226
|
+
* A negative scale drops that many digits off the end, and the value is an integer only when every
|
|
227
|
+
* digit it drops is a zero. Counted rather than sliced, so a long mantissa costs a scan. */
|
|
228
|
+
var lastSig = digits.length - 1;
|
|
229
|
+
while (lastSig > lead && digits[lastSig] === "0") lastSig--;
|
|
230
|
+
var trailingZeros = digits.length - 1 - lastSig;
|
|
231
|
+
if (scale < 0 && -scale > trailingZeros) {
|
|
232
|
+
fail("a number whose value is not exactly an integer, where this format reads it as one");
|
|
233
|
+
}
|
|
234
|
+
/** @internal The integer's decimal width, from the counts alone. MAX_SAFE_INTEGER is 16 digits, so
|
|
235
|
+
* anything wider than that is outside the exactly-representable range and is refused before a
|
|
236
|
+
* BigInt exists. Only a value already known to be at most 17 digits reaches the arithmetic. */
|
|
237
|
+
var sigCount = lastSig - lead + 1;
|
|
238
|
+
var intWidth = sigCount + scale + trailingZeros;
|
|
239
|
+
if (intWidth > 17) {
|
|
240
|
+
fail("an integer beyond the range a double represents exactly, so the value read back would not be the value written");
|
|
241
|
+
}
|
|
242
|
+
var exactDigits = _strSlice(digits, lead, lastSig + 1);
|
|
243
|
+
var zerosAfter = scale + trailingZeros;
|
|
244
|
+
for (var pad = 0; pad < zerosAfter; pad++) exactDigits += "0";
|
|
245
|
+
var exact = _BigInt(exactDigits);
|
|
246
|
+
if (exact > _MAX_EXACT) {
|
|
247
|
+
fail("an integer beyond the range a double represents exactly, so the value read back would not be the value written");
|
|
248
|
+
}
|
|
136
249
|
}
|
|
137
250
|
var v = _Number(_strSlice(str, start, i));
|
|
138
251
|
if (!_isFinite(v)) fail("bad number");
|
package/lib/guard-limits.js
CHANGED
|
@@ -79,6 +79,12 @@ function BudgetExceeded(message) {
|
|
|
79
79
|
return e;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** @internal Whether a caught error is the budget throw above, which a door converts to its own typed
|
|
83
|
+
* refusal.
|
|
84
|
+
*
|
|
85
|
+
* @enforced-by behavioral -- reading one marker property off a caught error is the shape of any error
|
|
86
|
+
* test; the vector that a door converts a budget throw to its own typed refusal, and rethrows anything
|
|
87
|
+
* else unchanged, is the guard */
|
|
82
88
|
function isBudgetExceeded(e) {
|
|
83
89
|
return !!(e && e.budgetExceeded === true);
|
|
84
90
|
}
|
|
@@ -86,7 +92,12 @@ function isBudgetExceeded(e) {
|
|
|
86
92
|
/** @internal One budget per top-level parse, carried into every nested decode. `exhausted`
|
|
87
93
|
* latches, and the object is frozen, so a caller that catches the throw cannot clear it. A
|
|
88
94
|
* door that re-reads the latch before returning a result therefore still refuses, whatever
|
|
89
|
-
* code between the two caught the exception.
|
|
95
|
+
* code between the two caught the exception.
|
|
96
|
+
*
|
|
97
|
+
* @enforced-by behavioral -- an accumulator that throws past a maximum is the shape of every bounded
|
|
98
|
+
* loop in the toolkit, so no pattern separates this from one; the guard is the vector that the latch
|
|
99
|
+
* stays set after a caller catches the throw, which a re-implementation that tracked the total without
|
|
100
|
+
* latching would fail */
|
|
90
101
|
function budget(limits) {
|
|
91
102
|
var spent = 0;
|
|
92
103
|
var exhausted = false;
|