@blamejs/pki 0.8.7 → 0.8.10

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.
@@ -401,7 +401,62 @@ function _settle(opts, E, code, label) {
401
401
  throw E(code, label + " changes which options it carries while they are read, so no set of " +
402
402
  "them can be checked; pass an object whose properties are plain values");
403
403
  }
404
- return opts;
404
+ return _withoutPlantedFields(opts, before);
405
+ }
406
+
407
+ /** @internal A language prototype, as opposed to one the caller built. A defaults object the
408
+ * caller put under their bag is theirs and sits below these in the chain. */
409
+ function _isLanguageProto(o) {
410
+ return o === intrinsic.ObjectProto || o === intrinsic.ArrayProto ||
411
+ o === intrinsic.BufferProto || o === intrinsic.FunctionProto;
412
+ }
413
+
414
+ function _holderOf(obj, k) {
415
+ var visited = new _Set();
416
+ for (var o = obj; o !== null && o !== undefined && !_setHas(visited, o); o = _getPrototypeOf(o)) {
417
+ _setAdd(visited, o);
418
+ if (_getOwnPropertyDescriptor(o, k)) return o;
419
+ }
420
+ return null;
421
+ }
422
+
423
+ /** @internal An option is something the caller supplied, and a name whose descriptor sits on a
424
+ * language prototype was supplied by whoever wrote to that prototype. The verb reads its options
425
+ * with `opts.name`, which walks the chain, so such a name answers as though it had been passed:
426
+ * `historicalMode` treats a revocation as not yet in force and `softFail` waives an undetermined
427
+ * one, each turning a revoked certificate into a valid path. The unknown-key check catches a
428
+ * planted name that is not an option at all; one spelled like a real option passes it.
429
+ *
430
+ * What comes back reads `undefined` for those names and is otherwise the object that was passed:
431
+ * the same prototype chain, the same own fields, and one own `undefined` masking each planted
432
+ * name. So the caller's own field still wins, a defaults object under their bag still supplies its
433
+ * options whatever their type, the reported set of names does not change, and a verb in a polluted
434
+ * process still runs instead of failing on every call. With nothing planted the object is handed
435
+ * back untouched, which is every call outside a process that has been written to.
436
+ *
437
+ * The own fields are carried as OWN fields rather than left to be inherited from the object that
438
+ * was passed. A verb that forwards its options builds the forwarded set by copying own keys, and a
439
+ * bag whose fields had all become inherited arrives at the inner call carrying none of them. */
440
+ function _withoutPlantedFields(obj, names) {
441
+ var planted = null;
442
+ for (var i = 0; i < names.length; i++) {
443
+ if (!_isLanguageProto(_holderOf(obj, names[i]))) continue;
444
+ if (planted === null) planted = [];
445
+ _append(planted, names[i]);
446
+ }
447
+ if (planted === null) return obj;
448
+ var shadow = _create(_getPrototypeOf(obj));
449
+ var own = _ownKeys(obj);
450
+ for (var k = 0; k < own.length; k++) {
451
+ var d = _getOwnPropertyDescriptor(obj, own[k]);
452
+ if (!d) continue;
453
+ _defineProperty(shadow, own[k], d);
454
+ }
455
+ for (var j = 0; j < planted.length; j++) {
456
+ if (_getOwnPropertyDescriptor(obj, planted[j])) continue;
457
+ _defineProperty(shadow, planted[j], { value: undefined, writable: true, enumerable: false, configurable: true });
458
+ }
459
+ return shadow;
405
460
  }
406
461
 
407
462
  /** @internal An injectable callback option, refused unless it is callable. A value that is present
@@ -445,8 +500,26 @@ function assertCallableOption(opts, key, E, code, label, shape) {
445
500
  return assertCallable(opts[key], E, code, label, shape);
446
501
  }
447
502
 
503
+ /** @internal Whether a caller's object says something about itself, as opposed to inheriting the
504
+ * answer. A declaration is a promise the author of that object made, so it has to sit on the object
505
+ * they wrote: reading the flag through the object finds a value on `Function.prototype` that every
506
+ * function in the process carries, and a boundary reading it that way treats every caller as having
507
+ * promised whatever was planted there. The value is read from the descriptor rather than through
508
+ * the object, so an accessor further up the chain cannot answer either.
509
+ *
510
+ * @enforced-by guard-shape-reinlined
511
+ * @guard-shape /\.\s*blocksPrivateAddresses\s*===\s*true/ */
512
+ function declaresOwn(obj, name) {
513
+ if (obj === null || obj === undefined) return false;
514
+ var d;
515
+ try { d = _getOwnPropertyDescriptor(_Object(obj), name); }
516
+ catch (_e) { return false; }
517
+ return !!d && !d.get && !d.set && d.value === true;
518
+ }
519
+
448
520
  module.exports = intrinsic.freeze({
449
521
  assertCallable: assertCallable,
522
+ declaresOwn: declaresOwn,
450
523
  assertCallableOption: assertCallableOption,
451
524
  assertCanonicalOid: assertCanonicalOid,
452
525
  assertKnownKeys: assertKnownKeys,
@@ -96,6 +96,7 @@ var _ObjectFn = Object;
96
96
  var _ObjectProto = Object.prototype;
97
97
  var _BufferProto = Buffer.prototype;
98
98
  var _ArrayProto = Array.prototype;
99
+ var _FunctionProto = Function.prototype;
99
100
  var _Set = Set;
100
101
  var _Map = Map;
101
102
  var _WeakMap = WeakMap;
@@ -216,6 +217,7 @@ module.exports = Object.freeze({
216
217
  ObjectProto: _ObjectProto,
217
218
  BufferProto: _BufferProto,
218
219
  ArrayProto: _ArrayProto,
220
+ FunctionProto: _FunctionProto,
219
221
  setAdd: _setAdd,
220
222
  setHas: _setHas,
221
223
  mapGet: _mapGet,
@@ -7,6 +7,10 @@ var constants = require("./constants");
7
7
  var _isInteger = require("./guard-intrinsic").isInteger;
8
8
  var _freeze = require("./guard-intrinsic").freeze;
9
9
  var _sizeOf = require("./guard-intrinsic").sizeOf;
10
+ var _isFinite = require("./guard-intrinsic").isFinite;
11
+ var _Number = require("./guard-intrinsic").Number;
12
+ var _hrtimeBigint = process.hrtime.bigint;
13
+ function _monotonicNow() { return _Number(_hrtimeBigint() / 1000000n); }
10
14
 
11
15
  // @enforced-by behavioral -- a config-time cap validator has no rename-proof code
12
16
  function cap(value, key, dflt, opts) {
@@ -109,7 +113,42 @@ function budget(limits) {
109
113
  return _freeze(self);
110
114
  }
111
115
 
116
+ /** @internal A wall-clock bound shared across several operations, where a per-operation timeout
117
+ * bounds each one and nothing bounds their sum. `now` is the clock, which a caller supplies so a
118
+ * test drives the bound without waiting; it defaults to the monotonic one, because a wall clock
119
+ * that steps backward would extend the deadline it was meant to enforce.
120
+ *
121
+ * `expired()` answers the question and `remaining()` gives the budget a per-operation timeout
122
+ * should be narrowed to, never below zero, so a caller that hands `remaining()` to a timeout
123
+ * cannot ask for one that outlives the deadline.
124
+ */
125
+ // @enforced-by behavioral -- an elapsed-time comparison has no rename-proof shape that separates a deadline from any other subtraction; the vectors driving a checker to its bound are the guard
126
+ function deadline(totalMs, opts) {
127
+ opts = opts || {};
128
+ var now = typeof opts.now === "function" ? opts.now : _monotonicNow;
129
+ if (!_isInteger(totalMs) || totalMs < 0) {
130
+ throw new TypeError("guard.limits.deadline: the total must be a non-negative integer of milliseconds");
131
+ }
132
+ var started = now();
133
+ if (typeof started !== "number" || !_isFinite(started)) {
134
+ throw new TypeError("guard.limits.deadline: the clock must return a finite number of milliseconds");
135
+ }
136
+ var self = {
137
+ elapsed: function () {
138
+ var at = now();
139
+ return typeof at === "number" && _isFinite(at) && at > started ? at - started : 0;
140
+ },
141
+ remaining: function () {
142
+ var left = totalMs - self.elapsed();
143
+ return left > 0 ? left : 0;
144
+ },
145
+ expired: function () { return self.remaining() === 0; },
146
+ total: function () { return totalMs; },
147
+ };
148
+ return _freeze(self);
149
+ }
150
+
112
151
  module.exports = _freeze({
113
152
  cap: cap, depthCap: depthCap, counter: counter, byteCap: byteCap,
114
- budget: budget, isBudgetExceeded: isBudgetExceeded,
153
+ budget: budget, isBudgetExceeded: isBudgetExceeded, deadline: deadline,
115
154
  });
package/lib/guard-list.js CHANGED
@@ -48,10 +48,35 @@ function allMatch(list, predicate) {
48
48
  return true;
49
49
  }
50
50
 
51
+ // @enforced-by caller-array-copied-live
52
+ function snapshot(list) {
53
+ if (!_isArray(list)) throw new TypeError("guard.list.snapshot: the receiver must be an array");
54
+ var n = list.length;
55
+ var out = [];
56
+ for (var i = 0; i < n; i++) {
57
+ _defineProperty(out, i, { value: list[i], writable: true, enumerable: true, configurable: true });
58
+ }
59
+ return out;
60
+ }
61
+
62
+ // @enforced-by caller-array-copied-live
63
+ function copyMap(list, fn) {
64
+ if (!_isArray(list)) throw new TypeError("guard.list.copyMap: the receiver must be an array");
65
+ if (typeof fn !== "function") throw new TypeError("guard.list.copyMap: the second argument must be a function");
66
+ var n = list.length;
67
+ var out = [];
68
+ for (var i = 0; i < n; i++) {
69
+ _defineProperty(out, i, { value: fn(list[i], i), writable: true, enumerable: true, configurable: true });
70
+ }
71
+ return out;
72
+ }
73
+
51
74
  module.exports = _freeze({
52
75
  append: append,
53
76
  contains: contains,
54
77
  containsAll: containsAll,
55
78
  anyMatches: anyMatches,
56
79
  allMatch: allMatch,
80
+ snapshot: snapshot,
81
+ copyMap: copyMap,
57
82
  });
package/lib/guard-name.js CHANGED
@@ -325,7 +325,6 @@ function _charTable(chars) {
325
325
  for (var i = 0; i < chars.length; i++) t[_charCodeAt(chars, i)] = true;
326
326
  return t;
327
327
  }
328
- var _FQDN_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-");
329
328
  var _DIGDOT_TABLE = _charTable("0123456789.");
330
329
  function _allCharsIn(s, table) {
331
330
  if (s.length === 0) return false;
@@ -344,12 +343,18 @@ function uriSchemeIs(s, scheme) {
344
343
  return lowerAscii(_strSlice(s, 0, scheme.length)) === scheme;
345
344
  }
346
345
 
347
- /** @internal A host name of two or more labels that is not an address, which is the form a URI
348
- * name constraint is compared by (RFC 5280 sec. 4.2.1.10). */
346
+ /** @internal A host name that is not an address, which is the form a URI name constraint is
347
+ * compared by (RFC 5280 sec. 4.2.1.10). The clause holds a URI constraint to "a fully qualified
348
+ * domain name" and names in the same sentence what that excludes: a URI with no authority
349
+ * component, and one whose authority names an IP address. It states no label count, so a single
350
+ * label is a host like any other and `com` constrains a top-level domain. The host is read by its
351
+ * labels rather than by which characters it holds: a character test admits an empty label and a
352
+ * hyphen at a label edge, and the suffix comparison below would then match a base against a name
353
+ * that is not a name. */
349
354
  function isFqdnHost(host) {
355
+ if (typeof host !== "string") return false;
350
356
  var h = stripTrailingDot(host);
351
- if (h === "" || _indexOf(h, ".") === -1) return false;
352
- if (!_allCharsIn(h, _FQDN_TABLE)) return false;
357
+ if (!_isHostName(h)) return false;
353
358
  if (_allCharsIn(h, _DIGDOT_TABLE)) return false;
354
359
  return true;
355
360
  }
@@ -386,6 +391,112 @@ function _isHostName(s) {
386
391
  return true;
387
392
  }
388
393
 
394
+ /** @internal A host name written as labels separated by dots (RFC 1034 sec. 3.5, RFC 1123 sec. 2.1),
395
+ * with no trailing dot: the caller strips the one dot a comparison strips, so a second one is an
396
+ * empty label and is refused. */
397
+ // @enforced-by behavioral -- an LDH label walk has no rename-proof shape that separates it from any other per-character scan; the vectors refusing an empty label, a hyphen at a label edge and a name past 253 octets are the guard
398
+ function isHostName(s) { return typeof s === "string" && _isHostName(s); }
399
+
400
+ /** @internal The labels of `s`, in order, with no trailing-dot handling of its own: the caller
401
+ * normalizes the dot through `stripTrailingDot` so both sides of a comparison are normalized by
402
+ * one symbol. A name carrying an empty label yields an empty string at that position, which a
403
+ * label test then refuses, rather than being dropped. */
404
+ // @enforced-by behavioral -- splitting on a dot has no rename-proof shape; the vectors counting the labels of a name carrying an empty one are the guard
405
+ function hostLabels(s) {
406
+ var out = [], start = 0;
407
+ for (var i = 0; i <= s.length; i++) {
408
+ if (i === s.length || _charCodeAt(s, i) === 0x2e) {
409
+ _append(out, _strSlice(s, start, i));
410
+ start = i + 1;
411
+ }
412
+ }
413
+ return out;
414
+ }
415
+
416
+ function _isAlnumLower(c) { return (c >= 0x61 && c <= 0x7a) || (c >= 0x30 && c <= 0x39); }
417
+
418
+ /** @internal Whether `l` is a well-formed A-label (RFC 5890 sec. 2.3.2.1): the `xn--` prefix
419
+ * followed by groups of lower-case alphanumerics separated by single hyphens, each group non-empty.
420
+ * The label is not decoded, because RFC 5891 sec. 3.1 rule 2 compares a pair of A-labels as
421
+ * case-insensitive ASCII without validating either. */
422
+ // @enforced-by behavioral -- an `xn--` prefix scan has no rename-proof shape; the vectors refusing `xn--`, a trailing hyphen and an empty group are the guard
423
+ function isXnLabel(l) {
424
+ if (typeof l !== "string") return false;
425
+ var n = l.length;
426
+ if (!(n >= 4 && _charCodeAt(l, 0) === 0x78 && _charCodeAt(l, 1) === 0x6e &&
427
+ _charCodeAt(l, 2) === 0x2d && _charCodeAt(l, 3) === 0x2d)) return false;
428
+ var i = 4, g = 0;
429
+ while (i < n && _isAlnumLower(_charCodeAt(l, i))) { i++; g++; }
430
+ if (g < 1) return false;
431
+ while (i < n) {
432
+ if (_charCodeAt(l, i) !== 0x2d) return false;
433
+ i++;
434
+ var g2 = 0;
435
+ while (i < n && _isAlnumLower(_charCodeAt(l, i))) { i++; g2++; }
436
+ if (g2 < 1) return false;
437
+ }
438
+ return true;
439
+ }
440
+
441
+ var _SCHEME_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-");
442
+
443
+ /** @internal The scheme and the host of a URI, read to RFC 3986 sec. 3: the scheme up to its colon,
444
+ * and, when `//` follows that colon, the authority up to the first `/`, `?` or `#`, with userinfo
445
+ * and port removed and an IPv6 literal's brackets stripped. `hasAuthority` is false for a URI
446
+ * written without `//`, which RFC 3986 sec. 3.3 permits and which therefore has no host; a caller
447
+ * that needs a host decides what to do with that rather than being handed a guess. `host` is null
448
+ * when the authority carries none, and `bracketed` says the host arrived inside `[]`, which is the
449
+ * only place an IPv6 literal may appear. Percent-escapes are left as written, so a caller comparing
450
+ * the host decides whether to accept one. */
451
+ // @enforced-by behavioral -- a URI walk has no rename-proof shape that separates it from any other character scan; the vectors reading a scheme, refusing two userinfo separators and stripping a port are the guard
452
+ function uriParts(uri) {
453
+ if (typeof uri !== "string") return null;
454
+ var n = uri.length;
455
+ if (n === 0) return null;
456
+ var c0 = _charCodeAt(uri, 0);
457
+ if (!((c0 >= 0x41 && c0 <= 0x5a) || (c0 >= 0x61 && c0 <= 0x7a))) return null;
458
+ var p = 1;
459
+ while (p < n && _SCHEME_TABLE[_charCodeAt(uri, p)]) p += 1;
460
+ if (_charCodeAt(uri, p) !== 0x3a) return null;
461
+ var scheme = _strSlice(uri, 0, p);
462
+ if (_charCodeAt(uri, p + 1) !== 0x2f || _charCodeAt(uri, p + 2) !== 0x2f) {
463
+ return { scheme: scheme, hasAuthority: false, host: null, bracketed: false };
464
+ }
465
+ p += 3;
466
+ var authStart = p;
467
+ while (p < n) {
468
+ var d = _charCodeAt(uri, p);
469
+ if (d === 0x2f || d === 0x3f || d === 0x23) break;
470
+ p += 1;
471
+ }
472
+ var authority = _strSlice(uri, authStart, p);
473
+ var firstAt = _indexOf(authority, "@");
474
+ if (firstAt !== _lastIndexOf(authority, "@")) return null;
475
+ if (firstAt !== -1) authority = _strSlice(authority, firstAt + 1);
476
+ var host = _strSlice(authority, 0, _portStripIndex(authority));
477
+ if (host === "") return { scheme: scheme, hasAuthority: true, host: null, bracketed: false };
478
+ var hn = host.length;
479
+ if (hn >= 2 && _charCodeAt(host, 0) === 0x5b && _charCodeAt(host, hn - 1) === 0x5d) {
480
+ return { scheme: scheme, hasAuthority: true, host: _strSlice(host, 1, hn - 1), bracketed: true };
481
+ }
482
+ return { scheme: scheme, hasAuthority: true, host: host, bracketed: false };
483
+ }
484
+
485
+ /** @internal Where a `:port` suffix starts, or the length when there is none. RFC 3986 sec. 3.2.3
486
+ * writes `port = *DIGIT`, so a colon with nothing after it is a port too, and leaving that colon on
487
+ * the host would make the authority compare as a name carrying a character no name carries. A
488
+ * bracketed IPv6 literal has colons of its own, so the scan reads digits back from the end and
489
+ * requires the colon immediately before them; the closing bracket is what stops it. */
490
+ function _portStripIndex(s) {
491
+ var i = s.length;
492
+ while (i > 0) {
493
+ var c = _charCodeAt(s, i - 1);
494
+ if (c >= 0x30 && c <= 0x39) i -= 1;
495
+ else break;
496
+ }
497
+ return (i > 0 && _charCodeAt(s, i - 1) === 0x3a) ? i - 1 : s.length;
498
+ }
499
+
389
500
  var _ATEXT_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-/=?^_`{|}~");
390
501
 
391
502
  /** @internal A quoted local part (RFC 5321 sec. 4.1.2 Quoted-string): printable ASCII between
@@ -534,18 +645,23 @@ function constraintBaseRefusal(tag, base) {
534
645
  : "must be a host name, optionally written as a subtree with a leading dot";
535
646
  }
536
647
  /** @internal RFC 5280 sec. 4.2.1.10 says of a URI constraint: "The constraint MUST be specified as
537
- * a fully qualified domain name and MAY specify a host or a domain. Examples would be
538
- * host.example.com and .example.com." Its dNSName paragraph states no such requirement, so a single
539
- * label is a valid dNSName base and is not a valid URI base. A caller meets this rule at the door,
540
- * whether through `pki.trust.anchor` or through the validator's subtree seeds. A base a certificate
541
- * carries in its own extension never reaches a door, and `uriMatch` is what holds it to the rule
542
- * there, answering `path/name-constraint-unsupported` for a host it cannot read as fully qualified.
543
- * `constraintBaseUnreadable` does not judge label count and does not stand in for that check. */
648
+ * a fully qualified domain name and MAY specify a host or a domain", and the same sentence names
649
+ * what that excludes: a URI with no authority component, and one whose authority names an IP
650
+ * address. Its dNSName paragraph states no such requirement, so an address is a valid dNSName base
651
+ * and is not a valid URI base. Neither paragraph states a label count. A caller meets this rule at
652
+ * the door, whether through `pki.trust.anchor` or through the validator's subtree seeds. A base a
653
+ * certificate carries in its own extension never reaches a door, and `uriMatch` holds it to the
654
+ * same `isFqdnHost` this calls, so the door and the comparison cannot answer differently. */
544
655
  if (tag === 6) {
545
- var h = _hostConstraintBody(_toLowerCase(base));
546
- var dotted = _isHostName(h) && _indexOf(h, ".") !== -1 && !_allCharsIn(h, _DIGDOT_TABLE);
547
- return dotted ? null
548
- : "must be a fully qualified domain name, which RFC 5280 sec. 4.2.1.10 requires of a URI constraint and not of a dNSName one";
656
+ /** @internal Only the leading dot comes off here. `isFqdnHost` takes a host as a URI carries
657
+ * it and strips the one trailing dot that makes a name absolute, so stripping it here too
658
+ * would let a base ending in two dots through as one ending in none. */
659
+ var uriBody = _toLowerCase(base);
660
+ if (_charAt(uriBody, 0) === ".") uriBody = _strSlice(uriBody, 1);
661
+ if (isFqdnHost(uriBody)) return null;
662
+ return _allCharsIn(stripTrailingDot(uriBody), _DIGDOT_TABLE)
663
+ ? "must be a domain name rather than an address, RFC 5280 sec. 4.2.1.10 naming an address in a URI's authority as what a URI constraint cannot be compared against"
664
+ : "must be a domain name, which RFC 5280 sec. 4.2.1.10 requires of a URI constraint, optionally written as a subtree with a leading dot";
549
665
  }
550
666
  return null;
551
667
  }
@@ -656,6 +772,7 @@ module.exports = intrinsic.freeze({
656
772
  isAttributeDescriptor: isAttributeDescriptor,
657
773
  lowerAscii: lowerAscii, uriSchemeIs: uriSchemeIs,
658
774
  stripTrailingDot: stripTrailingDot, isFqdnHost: isFqdnHost, splitMailbox: splitMailbox,
775
+ isHostName: isHostName, hostLabels: hostLabels, isXnLabel: isXnLabel, uriParts: uriParts,
659
776
  constraintBaseRefusal: constraintBaseRefusal,
660
777
  constraintBaseUnreadable: constraintBaseUnreadable,
661
778
  });
package/lib/guard-text.js CHANGED
@@ -50,4 +50,36 @@ function keyOf(v) {
50
50
  return typeof v === "string" || typeof v === "number" ? v : undefined;
51
51
  }
52
52
 
53
- module.exports = intrinsic.freeze({ decode: decode, showValue: showValue, keyOf: keyOf });
53
+ /** @internal What a caught value says about itself, as a string, for a boundary that reports a
54
+ * fault it did not cause. The value came from caller code, so every step of reading it is a step
55
+ * the caller chose the behavior of: `message` may be an accessor that throws, a Symbol that
56
+ * refuses string conversion, or an object whose `toString` throws, and the value itself may be a
57
+ * Proxy that refuses every read. A boundary whose whole contract is to turn a fault into a verdict
58
+ * cannot afford any of those to leave it, which is what happens when the description is built by
59
+ * reading `e.message` in the open.
60
+ *
61
+ * Every read is attempted and none is required. A value that answers nothing readable is described
62
+ * by its type, which is what `showValue` would have said about it anyway.
63
+ *
64
+ * @enforced-by guard-shape-reinlined
65
+ * @guard-shape /([\w$]+)\s*&&\s*\1\.message\s*\?\s*\1\.message\s*:|([\w$]+)\.message\s*\|\|\s*\2\b/ */
66
+ function describeThrown(e) {
67
+ var m;
68
+ try { m = e === null || e === undefined ? undefined : e.message; }
69
+ catch (_readFailed) { return _thrownFallback(e); }
70
+ if (typeof m === "string" && m.length > 0) return m;
71
+ if (m === undefined || m === null) return _thrownFallback(e);
72
+ try {
73
+ if (typeof m === "symbol") return intrinsic.String(m);
74
+ return "" + m;
75
+ } catch (_convertFailed) { return _thrownFallback(e); }
76
+ }
77
+
78
+ function _thrownFallback(e) {
79
+ try { return showValue(e); }
80
+ catch (_showFailed) { return "a value that reports nothing readable"; }
81
+ }
82
+
83
+ module.exports = intrinsic.freeze({
84
+ decode: decode, showValue: showValue, keyOf: keyOf, describeThrown: describeThrown,
85
+ });
@@ -134,7 +134,7 @@ function _pemifyAnchor(a) {
134
134
  return a;
135
135
  }
136
136
  function _pemifyAnchors(anchors) {
137
- var list = Array.isArray(anchors) ? anchors.map(_pemifyAnchor) : [_pemifyAnchor(anchors)];
137
+ var list = Array.isArray(anchors) ? guard.list.copyMap(anchors, _pemifyAnchor) : [_pemifyAnchor(anchors)];
138
138
  return list;
139
139
  }
140
140
 
@@ -366,7 +366,11 @@ function _validateProxy(request, defaults, E, C, url) {
366
366
  var originHost = _stripIpv6Brackets(url.hostname);
367
367
  var originAuthHost = nodeNet.isIP(originHost) === 6 ? "[" + originHost + "]" : originHost;
368
368
  var phost = _stripIpv6Brackets(purl.hostname);
369
- return { secure: secure, host: phost, port: purl.port ? +purl.port : (secure ? 443 : 80), originHost: originHost, originAuthority: originAuthHost + ":" + (url.port || 443), auth: auth, proxyTls: proxyTls };
369
+ /** @internal The CONNECT target is the origin's own authority, so an origin URL that states no
370
+ * port takes the default for the origin's scheme, not the proxy's. Defaulting it to 443 opens
371
+ * the tunnel to a port an `http:` origin does not listen on. */
372
+ var originPort = url.port || (url.protocol === "http:" ? 80 : 443);
373
+ return { secure: secure, host: phost, port: purl.port ? +purl.port : (secure ? 443 : 80), originHost: originHost, originAuthority: originAuthHost + ":" + originPort, auth: auth, proxyTls: proxyTls };
370
374
  }
371
375
 
372
376
  function _basicProxyHeader(auth) {
@@ -453,13 +457,30 @@ function httpsTransport(defaults) {
453
457
  var url;
454
458
  try { url = new URL(String(request.url)); }
455
459
  catch (e) { throw E(C("bad-url"), "the request URL did not parse: " + String(request.url), e); }
456
- if (url.protocol !== "https:") throw E(C("insecure-url"), "transport requires https (RFC 7030 sec. 3.3), got " + url.protocol);
460
+ /** @internal https is the floor, and `request.allowPlaintextHttp` is the one thing that moves
461
+ * it, per request and never as a default. It exists for the two objects whose integrity does
462
+ * not come from the transport: a CRL and an OCSP response are signed, their signatures are
463
+ * verified after the fetch, and an https-only revocation fetch needs the responder's own
464
+ * certificate validated first, which is the loop the plaintext form exists to break. Nothing
465
+ * else in this toolkit sets it. Every other guard stays: the address checks, the size cap, the
466
+ * timeout, and the refusal to follow a redirect, which is what would otherwise turn one
467
+ * plaintext hop into a downgrade of the next. */
468
+ var plaintextAllowed = request.allowPlaintextHttp === true;
469
+ if (url.protocol !== "https:" && !(plaintextAllowed && url.protocol === "http:")) {
470
+ throw E(C("insecure-url"), "transport requires https (RFC 7030 sec. 3.3), got " + url.protocol);
471
+ }
457
472
 
458
473
  var reqTls = request.tls || {};
459
474
  var anchors = reqTls.anchors !== undefined ? reqTls.anchors : tlsDefaults.anchors;
460
475
  var useSystem = (reqTls.useSystemStore !== undefined ? reqTls.useSystemStore : tlsDefaults.useSystemStore) === true;
461
476
  var hasAnchors = anchors !== undefined && anchors !== null && !(Array.isArray(anchors) && anchors.length === 0);
462
- if (!hasAnchors && !useSystem) throw E(C("no-trust-anchors"), "no explicit trust anchor and useSystemStore not set -- refusing an unpinned server (RFC 7030 sec. 3.6)");
477
+ /** @internal A trust anchor is what makes an https server identifiable, so a request with
478
+ * neither an anchor nor the system store is refused rather than left unpinned. A plaintext hop
479
+ * negotiates no certificate, so there is nothing for an anchor to pin; what protects the object
480
+ * fetched over one is its own signature, checked after the fetch by the verb that asked. */
481
+ if (url.protocol === "https:" && !hasAnchors && !useSystem) {
482
+ throw E(C("no-trust-anchors"), "no explicit trust anchor and useSystemStore not set -- refusing an unpinned server (RFC 7030 sec. 3.6)");
483
+ }
463
484
 
464
485
  var timeout = _budget(request.timeout !== undefined ? request.timeout : defaults.timeout, "timeout", DEFAULT_TIMEOUT, MAX_TIMEOUT, E, C("bad-input"));
465
486
  var maxBytes = _budget(request.maxResponseBytes !== undefined ? request.maxResponseBytes : defaults.maxResponseBytes, "maxResponseBytes", constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, E, C("bad-input"));
@@ -481,10 +502,11 @@ function httpsTransport(defaults) {
481
502
  }
482
503
 
483
504
  var host = _stripIpv6Brackets(url.hostname);
505
+ var plaintext = url.protocol === "http:";
484
506
  var options = {
485
507
  method: request.method || "GET",
486
508
  hostname: host,
487
- port: url.port || 443,
509
+ port: url.port || (plaintext ? 80 : 443),
488
510
  path: url.pathname + url.search,
489
511
  headers: reqHeaders,
490
512
  minVersion: minVersion,
@@ -528,7 +550,7 @@ function httpsTransport(defaults) {
528
550
  options.lookup = _guardedLookup;
529
551
  }
530
552
 
531
- return { options: options, timeout: timeout, maxBytes: maxBytes, body: body, proxy: proxyInfo, blockPrivate: blockPrivate, callerCsi: callerCsi };
553
+ return { options: options, timeout: timeout, maxBytes: maxBytes, body: body, proxy: proxyInfo, blockPrivate: blockPrivate, callerCsi: callerCsi, plaintext: plaintext };
532
554
  }
533
555
 
534
556
  var _transportFn = function transport(request) {
@@ -561,7 +583,10 @@ function httpsTransport(defaults) {
561
583
 
562
584
  function issue(options) {
563
585
  try {
564
- req = nodeHttps.request(options, function (res) {
586
+ /** @internal A plaintext request goes through `node:http`, which has no TLS options to
587
+ * ignore; `prep.plaintext` is set only where the scheme check let `http:` through. */
588
+ var client = prep.plaintext ? nodeHttp : nodeHttps;
589
+ req = client.request(options, function (res) {
565
590
  var tlsInfo = _tlsInfo(res.socket);
566
591
  var declared = parseInt((res.headers || {})["content-length"], 10);
567
592
  if (Number.isFinite(declared) && declared > prep.maxBytes) { fail(C("response-too-large"), "the declared content-length " + declared + " exceeds the " + prep.maxBytes + "-byte cap (RFC 7030 sec. 6)"); return; }
@@ -673,6 +698,13 @@ function httpsTransport(defaults) {
673
698
  creq = null;
674
699
  socket.on("error", function (e) { fail(_classifyError(e, C), "the tunnel socket failed: " + ((e && e.message) || String(e)), e); });
675
700
  var idHost = prep.options.servername || pinfo.originHost;
701
+ /** @internal A plaintext origin speaks HTTP over the tunnel the CONNECT opened, so the
702
+ * connection the request is issued on is that socket. Handing back a TLS wrapper would
703
+ * start a handshake against a server that does not speak TLS. */
704
+ if (prep.plaintext) {
705
+ issue(Object.assign({}, prep.options, { createConnection: function () { return socket; } }));
706
+ return;
707
+ }
676
708
  issue(Object.assign({}, prep.options, {
677
709
  createConnection: function () {
678
710
  return nodeTls.connect({