@blamejs/core 0.15.11 → 0.15.13
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 +4 -0
- package/index.js +2 -0
- package/lib/a2a-tasks.js +7 -23
- package/lib/acme.js +13 -16
- package/lib/agent-event-bus.js +5 -4
- package/lib/agent-idempotency.js +2 -5
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-capability.js +1 -6
- package/lib/ai-dp.js +1 -5
- package/lib/ai-input.js +2 -2
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-read.js +5 -17
- package/lib/archive-tar-read.js +5 -16
- package/lib/archive.js +2 -10
- package/lib/arg-parser.js +7 -6
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +108 -31
- package/lib/audit-chain.js +133 -53
- package/lib/audit-daily-review.js +24 -14
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +257 -0
- package/lib/audit.js +84 -0
- package/lib/auth/access-lock.js +5 -27
- package/lib/auth/dpop.js +23 -35
- package/lib/auth/fido-mds3.js +9 -15
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +13 -15
- package/lib/auth/lockout.js +6 -25
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +3 -3
- package/lib/auth/password.js +7 -1
- package/lib/auth/saml.js +37 -27
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +1 -1
- package/lib/auth/status-list.js +7 -7
- package/lib/auth/step-up.js +6 -12
- package/lib/auth-bot-challenge.js +6 -37
- package/lib/backup/bundle.js +11 -18
- package/lib/backup/index.js +14 -47
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/cache.js +7 -18
- package/lib/cbor.js +2 -1
- package/lib/cdn-cache-control.js +8 -9
- package/lib/cert.js +3 -10
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +5 -1
- package/lib/client-hints.js +10 -10
- package/lib/cloud-events.js +40 -31
- package/lib/cluster-storage.js +2 -38
- package/lib/cluster.js +4 -2
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +67 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +1 -11
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +2 -14
- package/lib/compliance.js +2 -9
- package/lib/config-drift.js +2 -12
- package/lib/content-digest.js +10 -8
- package/lib/cookies.js +5 -11
- package/lib/cose.js +19 -11
- package/lib/cra-report.js +1 -11
- package/lib/crypto-field.js +5 -10
- package/lib/crypto.js +120 -3
- package/lib/csp.js +235 -3
- package/lib/daemon.js +42 -41
- package/lib/data-act.js +19 -9
- package/lib/db-query.js +6 -40
- package/lib/db.js +34 -12
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dsr.js +8 -12
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +14 -26
- package/lib/fda-21cfr11.js +12 -8
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +869 -38
- package/lib/gdpr-ropa.js +4 -13
- package/lib/graphql-federation.js +1 -1
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +1 -0
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +16 -13
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +1 -1
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +18 -62
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +65 -117
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +8 -9
- package/lib/guard-json.js +87 -103
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +2 -1
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +5 -4
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +12 -13
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +8 -11
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +95 -140
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/http-client-cache.js +8 -13
- package/lib/http-client-cookie-jar.js +2 -4
- package/lib/http-client.js +8 -21
- package/lib/http-message-signature.js +26 -8
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +3 -21
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +6 -15
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +28 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-syslog.js +6 -0
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +24 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +3 -2
- package/lib/mail-auth.js +40 -66
- package/lib/mail-bimi.js +19 -39
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +6 -9
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +15 -26
- package/lib/mail-helo.js +2 -3
- package/lib/mail-journal.js +2 -1
- package/lib/mail-mdn.js +4 -3
- package/lib/mail-rbl.js +5 -8
- package/lib/mail-scan.js +2 -2
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +35 -51
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +89 -0
- package/lib/mail-spam-score.js +7 -8
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +11 -11
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +54 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +1 -1
- package/lib/mdoc.js +11 -12
- package/lib/metrics.js +32 -30
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +11 -22
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +79 -54
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +2 -10
- package/lib/middleware/csrf-protect.js +13 -2
- package/lib/middleware/daily-byte-quota.js +6 -26
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +7 -0
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/rate-limit.js +20 -28
- package/lib/middleware/scim-server.js +3 -2
- package/lib/middleware/security-headers.js +9 -1
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/tus-upload.js +12 -27
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +116 -36
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +97 -6
- package/lib/network-dns.js +22 -26
- package/lib/network-dnssec.js +16 -16
- package/lib/network-heartbeat.js +6 -5
- package/lib/network-proxy.js +3 -7
- package/lib/network-smtp-policy.js +29 -41
- package/lib/network-tls.js +74 -55
- package/lib/network.js +2 -6
- package/lib/nis2-report.js +1 -11
- package/lib/nonce-store.js +81 -3
- package/lib/notify.js +7 -12
- package/lib/numeric-bounds.js +22 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +5 -45
- package/lib/object-store/gcs.js +8 -71
- package/lib/object-store/http-put.js +1 -5
- package/lib/object-store/http-request.js +128 -0
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +9 -77
- package/lib/observability-otlp-exporter.js +9 -25
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +43 -37
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +2 -2
- package/lib/parsers/safe-yaml.js +7 -6
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/redact.js +2 -1
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +133 -6
- package/lib/restore.js +5 -22
- package/lib/retention.js +3 -5
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +12 -26
- package/lib/safe-json.js +7 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +12 -26
- package/lib/sandbox-worker.js +9 -2
- package/lib/sandbox.js +3 -2
- package/lib/scheduler.js +5 -12
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +9 -21
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +194 -6
- package/lib/sql.js +225 -78
- package/lib/sse.js +6 -10
- package/lib/static.js +136 -98
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +313 -17
- package/lib/tenant-quota.js +2 -20
- package/lib/tsa.js +11 -15
- package/lib/validate-opts.js +154 -8
- package/lib/vault/seal-pem-file.js +30 -48
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +2 -7
- package/lib/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +43 -18
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +7 -3
- package/lib/worm.js +2 -3
- package/lib/ws-client.js +8 -10
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/observability.js
CHANGED
|
@@ -343,6 +343,98 @@ function safeEvent(name, value, labels) {
|
|
|
343
343
|
catch (_e) { /* hot-path observability sink — drops silent on internal throws */ }
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
+
/**
|
|
347
|
+
* @primitive b.observability.safeEmit
|
|
348
|
+
* @signature b.observability.safeEmit(sink, name, value, labels)
|
|
349
|
+
* @since 0.15.13
|
|
350
|
+
* @related b.observability.safeEvent, b.observability.event
|
|
351
|
+
*
|
|
352
|
+
* The sink-aware sibling of `safeEvent`: emit a metric event to an
|
|
353
|
+
* explicitly-configured observability `sink` (a per-instance
|
|
354
|
+
* observability object) when one is supplied, otherwise fall back to
|
|
355
|
+
* the global registry — each path wrapped in a try/catch so a
|
|
356
|
+
* misconfigured counter never crashes the request that triggered it.
|
|
357
|
+
* Replaces the `_emitObs` + `_safeGlobalObs` helper pair that the auth
|
|
358
|
+
* brute-force modules (bot-challenge / lockout / session-device-binding)
|
|
359
|
+
* each duplicated to route a configured observability instance with a
|
|
360
|
+
* global fallback.
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* b.observability.safeEmit(opts.observability, "auth.lockout.hit", 1,
|
|
364
|
+
* { namespace: ns });
|
|
365
|
+
*/
|
|
366
|
+
function safeEmit(sink, name, value, labels) {
|
|
367
|
+
if (sink) {
|
|
368
|
+
try { sink.event(name, value, labels); }
|
|
369
|
+
catch (_e) { /* per-instance observability sink — drops silent */ }
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
safeEvent(name, value, labels);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @primitive b.observability.makeCounterEmitter
|
|
377
|
+
* @signature b.observability.makeCounterEmitter(sink)
|
|
378
|
+
* @since 0.15.13
|
|
379
|
+
* @status stable
|
|
380
|
+
* @related b.observability.safeEmit, b.observability.namespaced
|
|
381
|
+
*
|
|
382
|
+
* Bind a per-instance counter emitter. Returns `(name, labels)` that
|
|
383
|
+
* increments metric `name` by 1 (with `labels`) on the supplied
|
|
384
|
+
* observability `sink`, drop-silent on a sink throw and falling back to
|
|
385
|
+
* the global tap when `sink` is null. The shorthand every primitive that
|
|
386
|
+
* accepts an `observability` instance wrapped in a private
|
|
387
|
+
* `_emitObs(name, labels)` closure around
|
|
388
|
+
* `safeEmit(obsInst, name, 1, labels)` — build it once with the instance
|
|
389
|
+
* and call the returned emitter.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* var b = require("blamejs");
|
|
393
|
+
* var emit = b.observability.makeCounterEmitter(myObsInstance);
|
|
394
|
+
* emit("auth.lockout.tripped", { actor: "alice" });
|
|
395
|
+
*/
|
|
396
|
+
function makeCounterEmitter(sink) {
|
|
397
|
+
return function (name, labels) {
|
|
398
|
+
safeEmit(sink, name, 1, labels);
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* @primitive b.observability.namespaced
|
|
404
|
+
* @signature b.observability.namespaced(prefix, gateFlag?)
|
|
405
|
+
* @since 0.15.13
|
|
406
|
+
* @status stable
|
|
407
|
+
* @related b.observability.safeEvent, b.observability.event, b.audit.namespaced
|
|
408
|
+
*
|
|
409
|
+
* Build a drop-silent metric emitter bound to one name prefix — the shape every
|
|
410
|
+
* primitive hand-rolled as a private `_emitMetric(verb, n, labels)` closure
|
|
411
|
+
* (`try { observability().safeEvent("ns." + verb, n || 1, labels || {}); }
|
|
412
|
+
* catch {}`). The returned function prefixes `verb` with `prefix + "."`,
|
|
413
|
+
* defaults the value to `1` and labels to `{}`, and routes through `safeEvent`
|
|
414
|
+
* so a misconfigured counter / label name cannot crash the caller. The metric
|
|
415
|
+
* sibling of `b.audit.namespaced`. Metrics emit unconditionally by default;
|
|
416
|
+
* pass `gateFlag === false` to disable a primitive's own metrics in lockstep
|
|
417
|
+
* with its audit (the few primitives that gate both behind one `opts.audit`).
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* var emitMetric = b.observability.namespaced("network.byte_quota");
|
|
421
|
+
* emitMetric("exceeded", 1, { key: k });
|
|
422
|
+
* // → safeEvent("network.byte_quota.exceeded", 1, { key: k })
|
|
423
|
+
* emitMetric("reset");
|
|
424
|
+
* // → safeEvent("network.byte_quota.reset", 1, {})
|
|
425
|
+
*/
|
|
426
|
+
function namespaced(prefix, gateFlag) {
|
|
427
|
+
var on = gateFlag !== false;
|
|
428
|
+
return function (verb, n, labels) {
|
|
429
|
+
if (!on) return;
|
|
430
|
+
// module.exports.safeEvent (late-bound) so a test that stubs
|
|
431
|
+
// b.observability.safeEvent still observes the emit — the closures this
|
|
432
|
+
// replaces all called `observability().safeEvent(...)`.
|
|
433
|
+
try { module.exports.safeEvent(prefix + "." + verb, n || 1, labels || {}); }
|
|
434
|
+
catch (_e) { /* drop-silent — observability sink */ }
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
346
438
|
// timed — convenience wrapper that measures wall-clock duration of a
|
|
347
439
|
// sync or async operation and emits a counter event with
|
|
348
440
|
// duration_ms in the labels. Returns the wrapped function's return
|
|
@@ -870,6 +962,9 @@ module.exports = {
|
|
|
870
962
|
tap: tap,
|
|
871
963
|
event: event,
|
|
872
964
|
safeEvent: safeEvent,
|
|
965
|
+
safeEmit: safeEmit,
|
|
966
|
+
makeCounterEmitter: makeCounterEmitter,
|
|
967
|
+
namespaced: namespaced,
|
|
873
968
|
timed: timed,
|
|
874
969
|
setTap: setTap,
|
|
875
970
|
setRedactor: setRedactor,
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
var validateOpts = require("./validate-opts");
|
|
27
27
|
var schemaWalk = require("./openapi-schema-walk");
|
|
28
|
+
var pick = require("./pick");
|
|
28
29
|
var { defineClass } = require("./framework-error");
|
|
29
30
|
var OpenApiError = defineClass("OpenApiError", { alwaysPermanent: true });
|
|
30
31
|
|
|
@@ -44,7 +45,10 @@ function _extractPathParams(pathTemplate) {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
function PathsBuilder() {
|
|
47
|
-
|
|
48
|
+
// null-proto so a urlPattern keyed into _paths (operator-supplied API path)
|
|
49
|
+
// can never reach Object.prototype — a "__proto__" / "constructor" path lands
|
|
50
|
+
// as an own property, not a prototype mutation (prototype-polluting-assignment).
|
|
51
|
+
this._paths = Object.create(null);
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
// _buildOperation — normalise a single Operation Object from operator
|
|
@@ -133,7 +137,7 @@ PathsBuilder.prototype.add = function (method, urlPattern, opts) {
|
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
139
|
|
|
136
|
-
if (!this._paths[urlPattern]) this._paths[urlPattern] =
|
|
140
|
+
if (!this._paths[urlPattern]) this._paths[urlPattern] = Object.create(null);
|
|
137
141
|
if (this._paths[urlPattern][method.toLowerCase()]) {
|
|
138
142
|
throw new OpenApiError("openapi/duplicate-operation",
|
|
139
143
|
"paths.add: duplicate operation " + method.toUpperCase() + " " + urlPattern);
|
|
@@ -279,7 +283,7 @@ WebhooksBuilder.prototype.add = function (name, method, opts) {
|
|
|
279
283
|
opts = opts || {};
|
|
280
284
|
validateOpts.requireNonEmptyString(name, "webhook.add: name",
|
|
281
285
|
OpenApiError, "openapi/bad-webhook");
|
|
282
|
-
if (name
|
|
286
|
+
if (pick.isPoisonedKey(name)) {
|
|
283
287
|
throw new OpenApiError("openapi/bad-webhook",
|
|
284
288
|
"webhook.add: name must not be a reserved object key (" + JSON.stringify(name) + ")");
|
|
285
289
|
}
|
package/lib/otel-export.js
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
* hand-rolled wire format with no compelling benefit at this scope.
|
|
41
41
|
*/
|
|
42
42
|
var C = require("./constants");
|
|
43
|
+
var boundedMap = require("./bounded-map");
|
|
43
44
|
var canonicalJson = require("./canonical-json");
|
|
44
45
|
var httpClient = require("./http-client");
|
|
45
46
|
var observability = require("./observability");
|
|
@@ -144,11 +145,9 @@ function create(opts) {
|
|
|
144
145
|
if (typeof name !== "string" || name.length === 0) return;
|
|
145
146
|
var v = typeof value === "number" && isFinite(value) ? value : 1;
|
|
146
147
|
var key = _bucketKey(name, attrs);
|
|
147
|
-
var b =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
counters.set(key, b);
|
|
151
|
-
}
|
|
148
|
+
var b = boundedMap.getOrInsert(counters, key, function () {
|
|
149
|
+
return { name: name, attrs: attrs || {}, value: 0, startUnixNano: startUnixNano };
|
|
150
|
+
});
|
|
152
151
|
b.value += v;
|
|
153
152
|
}
|
|
154
153
|
|
|
@@ -157,11 +156,9 @@ function create(opts) {
|
|
|
157
156
|
if (typeof name !== "string" || name.length === 0) return;
|
|
158
157
|
if (typeof value !== "number" || !isFinite(value)) return;
|
|
159
158
|
var key = _bucketKey(name, attrs);
|
|
160
|
-
var b =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
observations.set(key, b);
|
|
164
|
-
}
|
|
159
|
+
var b = boundedMap.getOrInsert(observations, key, function () {
|
|
160
|
+
return { name: name, attrs: attrs || {}, sum: 0, count: 0, min: value, max: value, startUnixNano: startUnixNano };
|
|
161
|
+
});
|
|
165
162
|
b.sum += value;
|
|
166
163
|
b.count += 1;
|
|
167
164
|
if (value < b.min) b.min = value;
|
package/lib/outbox.js
CHANGED
|
@@ -208,29 +208,47 @@ function create(opts) {
|
|
|
208
208
|
"envelope", "connectorName", "connectorVersion", "dbName",
|
|
209
209
|
], "outbox.create");
|
|
210
210
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
211
|
+
validateOpts.shape(opts, {
|
|
212
|
+
externalDb: function (v) {
|
|
213
|
+
if (!v || typeof v.transaction !== "function") {
|
|
214
|
+
throw new OutboxError("outbox/bad-externaldb",
|
|
215
|
+
"outbox.create: externalDb must be the b.externalDb namespace (with transaction/query)");
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
table: function (v) {
|
|
219
|
+
validateOpts.requireNonEmptyString(v,
|
|
220
|
+
"outbox.create: table", OutboxError, "outbox/bad-table");
|
|
221
|
+
// Validate the table identifier at create-time so a bad name throws
|
|
222
|
+
// at boot, not at first query. b.sql re-quotes the name by
|
|
223
|
+
// construction on every emitted statement (the builder owns
|
|
224
|
+
// identifier quoting now).
|
|
225
|
+
_validateTableName(v);
|
|
226
|
+
},
|
|
227
|
+
publisher: function (v) {
|
|
228
|
+
if (typeof v !== "function") {
|
|
229
|
+
throw new OutboxError("outbox/bad-publisher",
|
|
230
|
+
"outbox.create: publisher must be an async function (event) → void");
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
pollIntervalMs: "optional-positive-finite",
|
|
234
|
+
batchSize: "optional-positive-finite",
|
|
235
|
+
maxAttempts: "optional-positive-finite",
|
|
236
|
+
claimReclaimMs: "optional-positive-finite",
|
|
237
|
+
retryBackoff: {
|
|
238
|
+
optional: true,
|
|
239
|
+
shape: {
|
|
240
|
+
initialMs: "optional-positive-finite",
|
|
241
|
+
maxMs: "optional-positive-finite",
|
|
242
|
+
factor: "optional-positive-finite",
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
audit: "optional-boolean",
|
|
246
|
+
name: "optional-string",
|
|
247
|
+
envelope: "optional-string",
|
|
248
|
+
connectorName: "optional-string",
|
|
249
|
+
connectorVersion: "optional-string",
|
|
250
|
+
dbName: "optional-string",
|
|
251
|
+
}, "outbox.create", OutboxError, "outbox/bad-opts");
|
|
234
252
|
|
|
235
253
|
var pollIntervalMs = opts.pollIntervalMs || DEFAULT_POLL_MS;
|
|
236
254
|
var batchSize = opts.batchSize || DEFAULT_BATCH_SIZE;
|
|
@@ -268,20 +286,8 @@ function create(opts) {
|
|
|
268
286
|
return Math.floor(ms);
|
|
269
287
|
}
|
|
270
288
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
catch (_e) { /* drop-silent */ }
|
|
274
|
-
}
|
|
275
|
-
function _emitAudit(action, outcome, metadata) {
|
|
276
|
-
if (!auditOn) return;
|
|
277
|
-
try {
|
|
278
|
-
audit().safeEmit({
|
|
279
|
-
action: action,
|
|
280
|
-
outcome: outcome,
|
|
281
|
-
metadata: metadata || {},
|
|
282
|
-
});
|
|
283
|
-
} catch (_e) { /* drop-silent */ }
|
|
284
|
-
}
|
|
289
|
+
var _emitMetric = observability().namespaced("outbox");
|
|
290
|
+
var _emitAudit = audit().namespaced(null, { audit: auditOn });
|
|
285
291
|
|
|
286
292
|
async function enqueue(event, txn) {
|
|
287
293
|
if (!txn || typeof txn.query !== "function") {
|
package/lib/pagination.js
CHANGED
|
@@ -50,6 +50,7 @@ var nodeCrypto = require("node:crypto");
|
|
|
50
50
|
var C = require("./constants");
|
|
51
51
|
var canonicalJson = require("./canonical-json");
|
|
52
52
|
var bCrypto = require("./crypto");
|
|
53
|
+
var validateOpts = require("./validate-opts");
|
|
53
54
|
var numericBounds = require("./numeric-bounds");
|
|
54
55
|
var safeJson = require("./safe-json");
|
|
55
56
|
var safeSql = require("./safe-sql");
|
|
@@ -85,11 +86,12 @@ function _toBuf(secret) {
|
|
|
85
86
|
|
|
86
87
|
function _b64urlEncode(buf) { return bCrypto.toBase64Url(buf); }
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
var _b64urlDecode = bCrypto.makeBase64UrlDecoder({
|
|
90
|
+
errorClass: PaginationError,
|
|
91
|
+
code: "pagination/bad-cursor",
|
|
92
|
+
typeMessage: "cursor must be a string",
|
|
93
|
+
badMessage: "cursor is not valid base64url",
|
|
94
|
+
});
|
|
93
95
|
|
|
94
96
|
function _tag(secretBuf, stateJson) {
|
|
95
97
|
var h = nodeCrypto.createHash("sha3-512");
|
|
@@ -345,11 +347,8 @@ function _buildKeysetWhere(orderEntries, cursorVals, forward) {
|
|
|
345
347
|
* });
|
|
346
348
|
*/
|
|
347
349
|
async function cursor(query, opts) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
throw new PaginationError("pagination/bad-query",
|
|
351
|
-
"cursor: first arg must be a db Query (must support where, orderBy, limit, all)");
|
|
352
|
-
}
|
|
350
|
+
validateOpts.requireMethods(query, ["where", "orderBy", "limit", "all"],
|
|
351
|
+
"cursor: first arg (db Query)", PaginationError, "pagination/bad-query");
|
|
353
352
|
opts = opts || {};
|
|
354
353
|
if (opts.secret == null) {
|
|
355
354
|
throw new PaginationError("pagination/no-secret",
|
|
@@ -484,11 +483,8 @@ async function cursor(query, opts) {
|
|
|
484
483
|
* page.hasMore; // → true when page < totalPages
|
|
485
484
|
*/
|
|
486
485
|
async function offset(query, opts) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
throw new PaginationError("pagination/bad-query",
|
|
490
|
-
"offset: first arg must be a db Query (must support limit, offset, all, count)");
|
|
491
|
-
}
|
|
486
|
+
validateOpts.requireMethods(query, ["limit", "offset", "all", "count"],
|
|
487
|
+
"offset: first arg (db Query)", PaginationError, "pagination/bad-query");
|
|
492
488
|
opts = opts || {};
|
|
493
489
|
var perPage = _resolveLimit({ limit: opts.perPage, max: opts.max, default: opts.default });
|
|
494
490
|
var page = parseInt(opts.page, 10);
|
package/lib/parsers/safe-env.js
CHANGED
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
*/
|
|
56
56
|
|
|
57
57
|
var C = require("../constants");
|
|
58
|
+
var pick = require("../pick");
|
|
59
|
+
var boundedMap = require("../bounded-map");
|
|
58
60
|
var atomicFile = require("../atomic-file");
|
|
59
61
|
var lazyRequire = require("../lazy-require");
|
|
60
62
|
var numericBounds = require("../numeric-bounds");
|
|
@@ -96,7 +98,6 @@ var DEFAULTS = {
|
|
|
96
98
|
audit: true,
|
|
97
99
|
};
|
|
98
100
|
|
|
99
|
-
var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
100
101
|
|
|
101
102
|
// ---- parse ----
|
|
102
103
|
|
|
@@ -153,7 +154,7 @@ function parse(input, opts) {
|
|
|
153
154
|
if (key.length === 0) {
|
|
154
155
|
throw new SafeEnvError("empty key", "env/empty-key", lineNumber);
|
|
155
156
|
}
|
|
156
|
-
if (
|
|
157
|
+
if (pick.isPoisonedKey(key)) {
|
|
157
158
|
throw new SafeEnvError("forbidden key '" + key + "'", "env/poisoned-key", lineNumber);
|
|
158
159
|
}
|
|
159
160
|
if (!keyShape.test(key)) {
|
|
@@ -162,9 +163,9 @@ function parse(input, opts) {
|
|
|
162
163
|
"env/bad-key-shape", lineNumber
|
|
163
164
|
);
|
|
164
165
|
}
|
|
165
|
-
|
|
166
|
+
boundedMap.requireAbsentMember(seen, key, function () {
|
|
166
167
|
throw new SafeEnvError("duplicate key '" + key + "'", "env/duplicate-key", lineNumber);
|
|
167
|
-
}
|
|
168
|
+
});
|
|
168
169
|
|
|
169
170
|
// Tab at start of value is almost always a paste accident
|
|
170
171
|
if (rest.charAt(0) === "\t") {
|
package/lib/parsers/safe-ini.js
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
var C = require("../constants");
|
|
39
|
+
var pick = require("../pick");
|
|
39
40
|
var numericBounds = require("../numeric-bounds");
|
|
40
41
|
var safeBuffer = require("../safe-buffer");
|
|
41
42
|
var { defineClass } = require("../framework-error");
|
|
@@ -51,7 +52,6 @@ var DEFAULT_MAX_SECTIONS = 500;
|
|
|
51
52
|
var DEFAULT_MAX_KEYS_SECTION = 1_000;
|
|
52
53
|
var DEFAULT_MAX_VALUE_BYTES = C.BYTES.kib(64);
|
|
53
54
|
|
|
54
|
-
var FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
55
55
|
|
|
56
56
|
var TRUE_VALUES = new Set(["true", "yes", "on"]);
|
|
57
57
|
var FALSE_VALUES = new Set(["false", "no", "off"]);
|
|
@@ -136,7 +136,7 @@ function _coerceValue(raw) {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
function _validateKey(name) {
|
|
139
|
-
if (
|
|
139
|
+
if (pick.isPoisonedKey(name)) {
|
|
140
140
|
throw _err("ini/forbidden-key", "key '" + name + "' is reserved (prototype pollution defense)");
|
|
141
141
|
}
|
|
142
142
|
}
|
|
@@ -155,7 +155,7 @@ function _ensureSection(root, sectionPath) {
|
|
|
155
155
|
}
|
|
156
156
|
node = existing;
|
|
157
157
|
} else {
|
|
158
|
-
var child =
|
|
158
|
+
var child = Object.create(null); // null-proto: see root (pollution-safe)
|
|
159
159
|
node[seg] = child;
|
|
160
160
|
node = child;
|
|
161
161
|
}
|
|
@@ -218,7 +218,13 @@ function parse(input, opts) {
|
|
|
218
218
|
"ini.parse: onDuplicate must be 'throw' | 'first' | 'last', got " + JSON.stringify(onDuplicate));
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
// Defense-in-depth against prototype pollution: every parsed node is a
|
|
222
|
+
// null-prototype object, so a key like __proto__ / constructor / prototype
|
|
223
|
+
// (already REFUSED by _validateKey → pick.isPoisonedKey before any write)
|
|
224
|
+
// could only ever land as an own property here, never reach Object.prototype.
|
|
225
|
+
// safe-ini's own reads use Object.prototype.hasOwnProperty.call(node, ...),
|
|
226
|
+
// so a null-proto tree is fully compatible.
|
|
227
|
+
var root = Object.create(null);
|
|
222
228
|
var currentSectionPath = [];
|
|
223
229
|
var currentSection = root;
|
|
224
230
|
var sectionCount = 0;
|
package/lib/parsers/safe-toml.js
CHANGED
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
var C = require("../constants");
|
|
48
|
+
var pick = require("../pick");
|
|
49
|
+
var boundedMap = require("../bounded-map");
|
|
50
|
+
var codepointClass = require("../codepoint-class");
|
|
48
51
|
var numericBounds = require("../numeric-bounds");
|
|
49
52
|
var safeBuffer = require("../safe-buffer");
|
|
50
53
|
var { FrameworkError } = require("../framework-error");
|
|
@@ -76,7 +79,6 @@ var DEFAULTS = {
|
|
|
76
79
|
maxKeys: 50_000,
|
|
77
80
|
};
|
|
78
81
|
|
|
79
|
-
var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
80
82
|
|
|
81
83
|
function parse(input, opts) {
|
|
82
84
|
opts = opts || {};
|
|
@@ -222,7 +224,7 @@ function parse(input, opts) {
|
|
|
222
224
|
|
|
223
225
|
function _parseDottedKey() {
|
|
224
226
|
var segments = [_parseSingleKeySegment()];
|
|
225
|
-
if (
|
|
227
|
+
if (pick.isPoisonedKey(segments[0])) {
|
|
226
228
|
throw _err("forbidden key '" + segments[0] + "'", "toml/poisoned-key");
|
|
227
229
|
}
|
|
228
230
|
while (true) {
|
|
@@ -231,7 +233,7 @@ function parse(input, opts) {
|
|
|
231
233
|
_advance();
|
|
232
234
|
_skipSpacesAndTabs();
|
|
233
235
|
var seg = _parseSingleKeySegment();
|
|
234
|
-
if (
|
|
236
|
+
if (pick.isPoisonedKey(seg)) {
|
|
235
237
|
throw _err("forbidden key '" + seg + "'", "toml/poisoned-key");
|
|
236
238
|
}
|
|
237
239
|
segments.push(seg);
|
|
@@ -305,7 +307,7 @@ function parse(input, opts) {
|
|
|
305
307
|
continue;
|
|
306
308
|
}
|
|
307
309
|
var cc = _peekCode();
|
|
308
|
-
if ((cc
|
|
310
|
+
if (codepointClass.isForbiddenControlChar(cc)) { // C0 (except TAB) + DEL refusal
|
|
309
311
|
throw _err("unescaped control char in string", "toml/bad-string");
|
|
310
312
|
}
|
|
311
313
|
_advance();
|
|
@@ -622,7 +624,7 @@ function parse(input, opts) {
|
|
|
622
624
|
var t = table;
|
|
623
625
|
for (var i = 0; i < segments.length - 1; i++) {
|
|
624
626
|
var seg = segments[i];
|
|
625
|
-
if (
|
|
627
|
+
if (pick.isPoisonedKey(seg)) throw _err("forbidden key segment", "toml/poisoned-key");
|
|
626
628
|
if (Object.prototype.hasOwnProperty.call(t, seg)) {
|
|
627
629
|
var sub = t[seg];
|
|
628
630
|
if (sub == null || typeof sub !== "object" || Array.isArray(sub)) {
|
|
@@ -639,7 +641,7 @@ function parse(input, opts) {
|
|
|
639
641
|
}
|
|
640
642
|
}
|
|
641
643
|
var last = segments[segments.length - 1];
|
|
642
|
-
if (
|
|
644
|
+
if (pick.isPoisonedKey(last)) throw _err("forbidden key segment", "toml/poisoned-key");
|
|
643
645
|
if (Object.prototype.hasOwnProperty.call(t, last)) {
|
|
644
646
|
throw _err("duplicate key '" + last + "'", "toml/duplicate-key");
|
|
645
647
|
}
|
|
@@ -665,7 +667,7 @@ function parse(input, opts) {
|
|
|
665
667
|
var t = root;
|
|
666
668
|
for (var i = 0; i < segments.length; i++) {
|
|
667
669
|
var seg = segments[i];
|
|
668
|
-
if (
|
|
670
|
+
if (pick.isPoisonedKey(seg)) throw _err("forbidden key segment", "toml/poisoned-key");
|
|
669
671
|
var isLast = (i === segments.length - 1);
|
|
670
672
|
if (Object.prototype.hasOwnProperty.call(t, seg)) {
|
|
671
673
|
var sub = t[seg];
|
|
@@ -699,9 +701,9 @@ function parse(input, opts) {
|
|
|
699
701
|
}
|
|
700
702
|
if (isLast && !isAoT) {
|
|
701
703
|
var fullPath = segments.slice(0, i + 1).join(".");
|
|
702
|
-
|
|
704
|
+
boundedMap.requireAbsentMember(definedTables, fullPath, function () {
|
|
703
705
|
throw _err("table '" + fullPath + "' defined twice", "toml/redefine");
|
|
704
|
-
}
|
|
706
|
+
});
|
|
705
707
|
definedTables.add(fullPath);
|
|
706
708
|
current = sub;
|
|
707
709
|
return;
|
package/lib/parsers/safe-xml.js
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
*/
|
|
49
49
|
|
|
50
50
|
var C = require("../constants");
|
|
51
|
+
var pick = require("../pick");
|
|
51
52
|
var numericBounds = require("../numeric-bounds");
|
|
52
53
|
var safeBuffer = require("../safe-buffer");
|
|
53
54
|
var { FrameworkError } = require("../framework-error");
|
|
@@ -93,7 +94,6 @@ var BUILT_IN_ENTITIES = { lt: "<", gt: ">", amp: "&", quot: "\"", apos: "'" };
|
|
|
93
94
|
// the result is always a clean key→value map. Mirrors the
|
|
94
95
|
// __proto__/constructor/prototype rejection the toml / yaml / ini
|
|
95
96
|
// parsers in this family already apply.
|
|
96
|
-
var FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
97
97
|
|
|
98
98
|
function _validateAndCap(name, value, defaultValue, ceiling) {
|
|
99
99
|
if (value === undefined) return defaultValue;
|
|
@@ -199,7 +199,7 @@ function parse(input, opts) {
|
|
|
199
199
|
}
|
|
200
200
|
if (pos === start) throw _err("expected name", "xml/bad-name");
|
|
201
201
|
var parsed = input.substring(start, pos);
|
|
202
|
-
if (
|
|
202
|
+
if (pick.isPoisonedKey(parsed)) {
|
|
203
203
|
throw _err("element/attribute name '" + parsed +
|
|
204
204
|
"' is reserved (prototype-pollution defense)", "xml/forbidden-name");
|
|
205
205
|
}
|
package/lib/parsers/safe-yaml.js
CHANGED
|
@@ -53,6 +53,8 @@
|
|
|
53
53
|
*/
|
|
54
54
|
|
|
55
55
|
var C = require("../constants");
|
|
56
|
+
var pick = require("../pick");
|
|
57
|
+
var boundedMap = require("../bounded-map");
|
|
56
58
|
var numericBounds = require("../numeric-bounds");
|
|
57
59
|
var safeBuffer = require("../safe-buffer");
|
|
58
60
|
var { FrameworkError } = require("../framework-error");
|
|
@@ -85,7 +87,6 @@ var DEFAULTS = {
|
|
|
85
87
|
maxKeys: 50_000,
|
|
86
88
|
};
|
|
87
89
|
|
|
88
|
-
var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
89
90
|
|
|
90
91
|
// YAML 1.2 core-schema scalar resolution. Order matters: null first
|
|
91
92
|
// (covers ~ and empty), then bool, then int (with base prefixes), then
|
|
@@ -333,7 +334,7 @@ function parse(input, opts) {
|
|
|
333
334
|
return _decodeSingleQuoted(raw, lineNumber, col);
|
|
334
335
|
}
|
|
335
336
|
var trimmed = safeBuffer.stripTrailingHspace(raw);
|
|
336
|
-
if (
|
|
337
|
+
if (pick.isPoisonedKey(trimmed)) {
|
|
337
338
|
throw new SafeYamlError("forbidden key '" + trimmed + "'",
|
|
338
339
|
"yaml/poisoned-key", lineNumber, col);
|
|
339
340
|
}
|
|
@@ -368,7 +369,7 @@ function parse(input, opts) {
|
|
|
368
369
|
throw new SafeYamlError("non-string mapping key not supported",
|
|
369
370
|
"yaml/bad-key", ln.lineNumber, ln.indent + 1);
|
|
370
371
|
}
|
|
371
|
-
if (
|
|
372
|
+
if (pick.isPoisonedKey(key)) {
|
|
372
373
|
throw new SafeYamlError("forbidden key '" + key + "'",
|
|
373
374
|
"yaml/poisoned-key", ln.lineNumber, ln.indent + 1);
|
|
374
375
|
}
|
|
@@ -376,10 +377,10 @@ function parse(input, opts) {
|
|
|
376
377
|
throw new SafeYamlError("merge key '<<' not supported (anchor-using feature)",
|
|
377
378
|
"yaml/merge-key-banned", ln.lineNumber, ln.indent + 1);
|
|
378
379
|
}
|
|
379
|
-
|
|
380
|
+
boundedMap.requireAbsentMember(seen, key, function () {
|
|
380
381
|
throw new SafeYamlError("duplicate mapping key '" + key + "'",
|
|
381
382
|
"yaml/duplicate-key", ln.lineNumber, ln.indent + 1);
|
|
382
|
-
}
|
|
383
|
+
});
|
|
383
384
|
seen.add(key);
|
|
384
385
|
_bumpKeys(ln.lineNumber);
|
|
385
386
|
|
|
@@ -563,7 +564,7 @@ function parse(input, opts) {
|
|
|
563
564
|
throw new SafeYamlError("non-string flow-mapping key",
|
|
564
565
|
"yaml/bad-key", lineNumber, col + p);
|
|
565
566
|
}
|
|
566
|
-
if (
|
|
567
|
+
if (pick.isPoisonedKey(key)) {
|
|
567
568
|
throw new SafeYamlError("forbidden key '" + key + "'",
|
|
568
569
|
"yaml/poisoned-key", lineNumber, col + p);
|
|
569
570
|
}
|