@embedpdf/engine-core 3.0.0-next.12 → 3.0.0-next.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/dist/{AbortError-CRSooKsk.cjs → AbortError-Cq2igpZ6.cjs} +2 -2
- package/dist/{AbortError-CRSooKsk.cjs.map → AbortError-Cq2igpZ6.cjs.map} +1 -1
- package/dist/{AbortError-8-9JAX1Y.js → AbortError-DHqPBnYc.js} +2 -2
- package/dist/{AbortError-8-9JAX1Y.js.map → AbortError-DHqPBnYc.js.map} +1 -1
- package/dist/{AnnotationListSnapshot-x6tKulNV.d.cts → AnnotationListSnapshot-D3mFut1e.d.cts} +1155 -171
- package/dist/{AnnotationListSnapshot-x6tKulNV.d.ts → AnnotationListSnapshot-D3mFut1e.d.ts} +1155 -171
- package/dist/{DocumentSecurityService-CK0pejAK.d.cts → DocumentSecurityService-ClTZc0zO.d.cts} +2 -2
- package/dist/{DocumentSecurityService-9MawYxP-.d.ts → DocumentSecurityService-_JRjqvoI.d.ts} +2 -2
- package/dist/{Engine-Ds0ytbl6.d.ts → Engine-Bx0NOfmd.d.cts} +166 -11
- package/dist/{Engine--0XF0UAK.d.cts → Engine-DxtpCYcP.d.ts} +166 -11
- package/dist/{PageInsertBlankInput-CzShuSJI.cjs → PageInsertBlankInput-CqXBmFhN.cjs} +37 -2
- package/dist/PageInsertBlankInput-CqXBmFhN.cjs.map +1 -0
- package/dist/{PageInsertBlankInput-WKY0tr6S.js → PageInsertBlankInput-cHq9-i-z.js} +37 -2
- package/dist/PageInsertBlankInput-cHq9-i-z.js.map +1 -0
- package/dist/{builders-8Hl1AYNU.cjs → builders-Bqm8yTpi.cjs} +1470 -4
- package/dist/builders-Bqm8yTpi.cjs.map +1 -0
- package/dist/{builders-DOjcKDWQ.js → builders-Dqo2X-dt.js} +1351 -5
- package/dist/builders-Dqo2X-dt.js.map +1 -0
- package/dist/conformance.cjs +1079 -13
- package/dist/conformance.cjs.map +1 -1
- package/dist/conformance.d.cts +70 -3
- package/dist/conformance.d.ts +70 -3
- package/dist/conformance.js +1079 -14
- package/dist/conformance.js.map +1 -1
- package/dist/{fold-C7I5GpBz.cjs → fold-2V29cxCV.cjs} +2 -2
- package/dist/{fold-C7I5GpBz.cjs.map → fold-2V29cxCV.cjs.map} +1 -1
- package/dist/{fold-BfyxxwS6.js → fold-CdEJTBTn.js} +2 -2
- package/dist/{fold-BfyxxwS6.js.map → fold-CdEJTBTn.js.map} +1 -1
- package/dist/index.cjs +33 -4
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -5
- package/dist/{resolver-D-jumvgM.cjs → resolver-CYVNV4hn.cjs} +216 -10
- package/dist/resolver-CYVNV4hn.cjs.map +1 -0
- package/dist/{resolver-CHG4waXL.js → resolver-inAVzsWt.js} +157 -11
- package/dist/resolver-inAVzsWt.js.map +1 -0
- package/dist/runtime.cjs +34 -5
- package/dist/runtime.d.cts +192 -5
- package/dist/runtime.d.ts +192 -5
- package/dist/runtime.js +6 -6
- package/dist/{schemas-DOyVEPk7.js → schemas-BLARNCgx.js} +420 -4
- package/dist/schemas-BLARNCgx.js.map +1 -0
- package/dist/{schemas-RChsAMWw.cjs → schemas-DIqFMmrC.cjs} +581 -3
- package/dist/schemas-DIqFMmrC.cjs.map +1 -0
- package/dist/wire.cjs +228 -12
- package/dist/wire.cjs.map +1 -1
- package/dist/wire.d.cts +415 -4
- package/dist/wire.d.ts +415 -4
- package/dist/wire.js +199 -13
- package/dist/wire.js.map +1 -1
- package/package.json +1 -1
- package/dist/PageInsertBlankInput-CzShuSJI.cjs.map +0 -1
- package/dist/PageInsertBlankInput-WKY0tr6S.js.map +0 -1
- package/dist/builders-8Hl1AYNU.cjs.map +0 -1
- package/dist/builders-DOjcKDWQ.js.map +0 -1
- package/dist/resolver-CHG4waXL.js.map +0 -1
- package/dist/resolver-D-jumvgM.cjs.map +0 -1
- package/dist/schemas-DOyVEPk7.js.map +0 -1
- package/dist/schemas-RChsAMWw.cjs.map +0 -1
package/dist/conformance.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { f as pdfQuadBounds, i as EngineError, n as foldText, u as isRotatedGeometryRun } from "./fold-
|
|
2
|
-
import { f as EngineErrorCode, o as sliceTextByChars } from "./PageInsertBlankInput-
|
|
3
|
-
import { t as AbortError } from "./AbortError-
|
|
4
|
-
import {
|
|
1
|
+
import { f as pdfQuadBounds, i as EngineError, n as foldText, u as isRotatedGeometryRun } from "./fold-CdEJTBTn.js";
|
|
2
|
+
import { f as EngineErrorCode, o as sliceTextByChars } from "./PageInsertBlankInput-cHq9-i-z.js";
|
|
3
|
+
import { t as AbortError } from "./AbortError-DHqPBnYc.js";
|
|
4
|
+
import { B as FormImportResultSchema, Ct as PageTextSnapshotSchema, Et as RedactionApplyResultSchema, H as FormSetValueResultSchema, Nt as SearchSliceSchema, V as FormRepairResultSchema, at as PageGeometrySnapshotSchema, bt as PageRotateResultSchema, cn as FormSnapshotSchema, d as AnnotationListPageSnapshotSchema, f as AnnotationListSnapshotAllPagesSchema, ft as PageMoveResultSchema, it as PageFlattenResultSchema, l as AnnotationFlattenResultSchema, m as AnnotationUpdateResultSchema, mn as AnnotationDTOSchema, mt as PageNameResultSchema, o as AnnotationCreateResultSchema, p as AnnotationMoveResultSchema, qr as DocumentActionsSnapshotSchema, s as AnnotationDeleteResultSchema, tt as PageDeleteResultSchema, ut as PageListSnapshotSchema } from "./schemas-BLARNCgx.js";
|
|
5
5
|
//#region src/conformance/runMetadataConformance.ts
|
|
6
6
|
/**
|
|
7
7
|
* The conformance harness is transport-agnostic. It takes the test runner as
|
|
@@ -388,7 +388,7 @@ function runActionsConformance(runner, opts) {
|
|
|
388
388
|
if (engine) await engine.destroy();
|
|
389
389
|
});
|
|
390
390
|
test("reads catalog additional actions and preserves /CO identity", async () => {
|
|
391
|
-
const doc = await open(engine, opts, opts.fixtures.document);
|
|
391
|
+
const doc = await open$1(engine, opts, opts.fixtures.document);
|
|
392
392
|
try {
|
|
393
393
|
expect(Boolean(doc.actions)).toBe(true);
|
|
394
394
|
const snapshot = await doc.actions.read();
|
|
@@ -409,7 +409,7 @@ function runActionsConformance(runner, opts) {
|
|
|
409
409
|
}
|
|
410
410
|
});
|
|
411
411
|
test("attaches page /AA to PageLayout", async () => {
|
|
412
|
-
const doc = await open(engine, opts, opts.fixtures.page);
|
|
412
|
+
const doc = await open$1(engine, opts, opts.fixtures.page);
|
|
413
413
|
try {
|
|
414
414
|
const pages = await doc.pages.list();
|
|
415
415
|
expect(pages.pages[0].actions?.open?.root?.type).toBe("goto-embedded");
|
|
@@ -421,7 +421,7 @@ function runActionsConformance(runner, opts) {
|
|
|
421
421
|
}
|
|
422
422
|
});
|
|
423
423
|
test("attaches activation actions to AnnotationBase, including links", async () => {
|
|
424
|
-
const doc = await open(engine, opts, opts.fixtures.annotation);
|
|
424
|
+
const doc = await open$1(engine, opts, opts.fixtures.annotation);
|
|
425
425
|
try {
|
|
426
426
|
const firstPage = (await doc.pages.list()).pages[0];
|
|
427
427
|
const snapshot = await doc.page(firstPage.pageObjectNumber).annotations.list();
|
|
@@ -438,7 +438,7 @@ function runActionsConformance(runner, opts) {
|
|
|
438
438
|
test("classifies a /S /JavaScript link target, script staying on base.actions", async () => {
|
|
439
439
|
const fixture = opts.fixtures.javascriptLink;
|
|
440
440
|
if (!fixture) return;
|
|
441
|
-
const doc = await open(engine, opts, fixture);
|
|
441
|
+
const doc = await open$1(engine, opts, fixture);
|
|
442
442
|
try {
|
|
443
443
|
const firstPage = (await doc.pages.list()).pages[0];
|
|
444
444
|
const link = (await doc.page(firstPage.pageObjectNumber).annotations.list()).annotations.find((annotation) => annotation.subtype === "link");
|
|
@@ -451,7 +451,7 @@ function runActionsConformance(runner, opts) {
|
|
|
451
451
|
}
|
|
452
452
|
});
|
|
453
453
|
test("keeps field actions separate from merged widget events", async () => {
|
|
454
|
-
const doc = await open(engine, opts, opts.fixtures.field);
|
|
454
|
+
const doc = await open$1(engine, opts, opts.fixtures.field);
|
|
455
455
|
try {
|
|
456
456
|
const form = await doc.forms.list();
|
|
457
457
|
expect(form.fields).toHaveLength(1);
|
|
@@ -465,7 +465,7 @@ function runActionsConformance(runner, opts) {
|
|
|
465
465
|
}
|
|
466
466
|
});
|
|
467
467
|
test("carries interpreter payloads on every executable node", async () => {
|
|
468
|
-
const doc = await open(engine, opts, opts.fixtures.payloads);
|
|
468
|
+
const doc = await open$1(engine, opts, opts.fixtures.payloads);
|
|
469
469
|
try {
|
|
470
470
|
const page = (await doc.pages.list()).pages[0];
|
|
471
471
|
const snapshot = await doc.page(page.pageObjectNumber).annotations.list();
|
|
@@ -626,7 +626,7 @@ function runActionsConformance(runner, opts) {
|
|
|
626
626
|
}
|
|
627
627
|
});
|
|
628
628
|
test("degrades unreadable payloads to unknown with payload-dropped", async () => {
|
|
629
|
-
const doc = await open(engine, opts, opts.fixtures.payloads);
|
|
629
|
+
const doc = await open$1(engine, opts, opts.fixtures.payloads);
|
|
630
630
|
try {
|
|
631
631
|
const page = (await doc.pages.list()).pages[0];
|
|
632
632
|
const snapshot = await doc.page(page.pageObjectNumber).annotations.list();
|
|
@@ -650,7 +650,7 @@ function runActionsConformance(runner, opts) {
|
|
|
650
650
|
}
|
|
651
651
|
});
|
|
652
652
|
test("reads a destination-form /OpenAction as openDestination", async () => {
|
|
653
|
-
const doc = await open(engine, opts, opts.fixtures.openDestination);
|
|
653
|
+
const doc = await open$1(engine, opts, opts.fixtures.openDestination);
|
|
654
654
|
try {
|
|
655
655
|
expect(Boolean(doc.actions)).toBe(true);
|
|
656
656
|
const snapshot = await doc.actions.read();
|
|
@@ -670,7 +670,7 @@ function runActionsConformance(runner, opts) {
|
|
|
670
670
|
});
|
|
671
671
|
});
|
|
672
672
|
}
|
|
673
|
-
async function open(engine, opts, fixture) {
|
|
673
|
+
async function open$1(engine, opts, fixture) {
|
|
674
674
|
if (opts.openKind === "bytes") return engine.open({
|
|
675
675
|
kind: "bytes",
|
|
676
676
|
id: fixture.id,
|
|
@@ -1518,6 +1518,287 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1518
1518
|
await doc.close();
|
|
1519
1519
|
}
|
|
1520
1520
|
});
|
|
1521
|
+
test("rich text: a plain draft reads back body-style paragraphs and a body from its font", async () => {
|
|
1522
|
+
const doc = await openFixture$18(engine, opts);
|
|
1523
|
+
try {
|
|
1524
|
+
const created = await doc.page(fix.pageObjectNumber).annotations.create({
|
|
1525
|
+
subtype: "free-text",
|
|
1526
|
+
intent: "free-text",
|
|
1527
|
+
contents: "Plain\rtext",
|
|
1528
|
+
rect: shapeRect,
|
|
1529
|
+
fontFamily: "helvetica-bold",
|
|
1530
|
+
fontSize: 18,
|
|
1531
|
+
textAlign: "center",
|
|
1532
|
+
color: {
|
|
1533
|
+
r: 0,
|
|
1534
|
+
g: 0,
|
|
1535
|
+
b: 0
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
expect(created.created.subtype).toBe("free-text");
|
|
1539
|
+
if (created.created.subtype === "free-text") {
|
|
1540
|
+
const rich = created.created.richText;
|
|
1541
|
+
expect(created.created.fontFamily).toBe("helvetica-bold");
|
|
1542
|
+
expect(rich.body.family).toBe("Helvetica");
|
|
1543
|
+
expect(rich.body.weight).toBe(700);
|
|
1544
|
+
expect(rich.body.italic).toBe(false);
|
|
1545
|
+
expect(rich.body.size).toBe(18);
|
|
1546
|
+
expect(rich.body.align).toBe("center");
|
|
1547
|
+
expect(rich.paragraphs).toEqual([{ runs: [{ text: "Plain" }] }, { runs: [{ text: "text" }] }]);
|
|
1548
|
+
}
|
|
1549
|
+
} finally {
|
|
1550
|
+
await doc.close();
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
test("rich text: a draft with runs round-trips the body, the runs and the plain projection", async () => {
|
|
1554
|
+
const doc = await openFixture$18(engine, opts);
|
|
1555
|
+
try {
|
|
1556
|
+
const page = doc.page(fix.pageObjectNumber);
|
|
1557
|
+
const created = await page.annotations.create({
|
|
1558
|
+
subtype: "free-text",
|
|
1559
|
+
intent: "free-text",
|
|
1560
|
+
rect: shapeRect,
|
|
1561
|
+
fontFamily: "helvetica",
|
|
1562
|
+
fontSize: 12,
|
|
1563
|
+
textAlign: "left",
|
|
1564
|
+
color: {
|
|
1565
|
+
r: 0,
|
|
1566
|
+
g: 0,
|
|
1567
|
+
b: 255
|
|
1568
|
+
},
|
|
1569
|
+
richText: {
|
|
1570
|
+
body: {
|
|
1571
|
+
family: "Helvetica",
|
|
1572
|
+
size: 18,
|
|
1573
|
+
color: "#102030"
|
|
1574
|
+
},
|
|
1575
|
+
paragraphs: [{ runs: [
|
|
1576
|
+
{ text: "Hello " },
|
|
1577
|
+
{
|
|
1578
|
+
text: "bold",
|
|
1579
|
+
style: { weight: 700 }
|
|
1580
|
+
},
|
|
1581
|
+
{
|
|
1582
|
+
text: " red",
|
|
1583
|
+
style: { color: "#FF0000" }
|
|
1584
|
+
}
|
|
1585
|
+
] }, {
|
|
1586
|
+
align: "center",
|
|
1587
|
+
runs: [{ text: "H" }, {
|
|
1588
|
+
text: "2",
|
|
1589
|
+
style: { script: "sub" }
|
|
1590
|
+
}]
|
|
1591
|
+
}]
|
|
1592
|
+
}
|
|
1593
|
+
});
|
|
1594
|
+
expect(AnnotationCreateResultSchema.safeParse(created).success).toBe(true);
|
|
1595
|
+
expect(created.created.subtype).toBe("free-text");
|
|
1596
|
+
if (created.created.subtype === "free-text") {
|
|
1597
|
+
const dto = created.created;
|
|
1598
|
+
expect(dto.contents).toBe("Hello bold red\rH2");
|
|
1599
|
+
expect(dto.fontFamily).toBe("helvetica");
|
|
1600
|
+
expect(dto.fontSize).toBe(18);
|
|
1601
|
+
expect(dto.color).toMatchObject({
|
|
1602
|
+
r: 0,
|
|
1603
|
+
g: 0,
|
|
1604
|
+
b: 255
|
|
1605
|
+
});
|
|
1606
|
+
expect(dto.richText.body.color).toBe("#102030");
|
|
1607
|
+
expect(dto.richText.paragraphs[0].runs).toEqual([
|
|
1608
|
+
{ text: "Hello " },
|
|
1609
|
+
{
|
|
1610
|
+
text: "bold",
|
|
1611
|
+
style: { weight: 700 }
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
text: " red",
|
|
1615
|
+
style: { color: "#FF0000" }
|
|
1616
|
+
}
|
|
1617
|
+
]);
|
|
1618
|
+
expect(dto.richText.paragraphs[1].align).toBe("center");
|
|
1619
|
+
expect(dto.richText.paragraphs[1].runs[1]).toEqual({
|
|
1620
|
+
text: "2",
|
|
1621
|
+
style: { script: "sub" }
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
const listed = (await page.annotations.list()).annotations.find((a) => a.index === created.created.index);
|
|
1625
|
+
expect(listed?.subtype).toBe("free-text");
|
|
1626
|
+
if (listed?.subtype === "free-text") expect(listed.richText.paragraphs[0].runs[1]).toEqual({
|
|
1627
|
+
text: "bold",
|
|
1628
|
+
style: { weight: 700 }
|
|
1629
|
+
});
|
|
1630
|
+
} finally {
|
|
1631
|
+
await doc.close();
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
test("rich text: paragraphs-only patch keeps the body; contents-only rewrites body-style paragraphs", async () => {
|
|
1635
|
+
const doc = await openFixture$18(engine, opts);
|
|
1636
|
+
try {
|
|
1637
|
+
const page = doc.page(fix.pageObjectNumber);
|
|
1638
|
+
const ref = (await page.annotations.create({
|
|
1639
|
+
subtype: "free-text",
|
|
1640
|
+
intent: "free-text",
|
|
1641
|
+
rect: shapeRect,
|
|
1642
|
+
fontFamily: "helvetica",
|
|
1643
|
+
fontSize: 14,
|
|
1644
|
+
textAlign: "center",
|
|
1645
|
+
color: {
|
|
1646
|
+
r: 0,
|
|
1647
|
+
g: 0,
|
|
1648
|
+
b: 0
|
|
1649
|
+
},
|
|
1650
|
+
richText: {
|
|
1651
|
+
body: {
|
|
1652
|
+
family: "Helvetica",
|
|
1653
|
+
size: 14
|
|
1654
|
+
},
|
|
1655
|
+
paragraphs: [{ runs: [{ text: "a" }, {
|
|
1656
|
+
text: "b",
|
|
1657
|
+
style: { weight: 700 }
|
|
1658
|
+
}] }]
|
|
1659
|
+
}
|
|
1660
|
+
})).created.ref;
|
|
1661
|
+
const restyled = await page.annotations.update(ref, {
|
|
1662
|
+
subtype: "free-text",
|
|
1663
|
+
richText: { paragraphs: [{ runs: [{
|
|
1664
|
+
text: "ab",
|
|
1665
|
+
style: { italic: true }
|
|
1666
|
+
}, { text: "c" }] }] }
|
|
1667
|
+
});
|
|
1668
|
+
expect(AnnotationUpdateResultSchema.safeParse(restyled).success).toBe(true);
|
|
1669
|
+
expect(restyled.updated.subtype).toBe("free-text");
|
|
1670
|
+
if (restyled.updated.subtype === "free-text") {
|
|
1671
|
+
expect(restyled.updated.contents).toBe("abc");
|
|
1672
|
+
expect(restyled.updated.richText.body.size).toBe(14);
|
|
1673
|
+
expect(restyled.updated.richText.body.align).toBe("center");
|
|
1674
|
+
expect(restyled.updated.textAlign).toBe("center");
|
|
1675
|
+
expect(restyled.updated.richText.paragraphs[0].runs[0]).toEqual({
|
|
1676
|
+
text: "ab",
|
|
1677
|
+
style: { italic: true }
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
const rewritten = await page.annotations.update(ref, {
|
|
1681
|
+
subtype: "free-text",
|
|
1682
|
+
contents: "one\rtwo"
|
|
1683
|
+
});
|
|
1684
|
+
expect(rewritten.updated.subtype).toBe("free-text");
|
|
1685
|
+
if (rewritten.updated.subtype === "free-text") {
|
|
1686
|
+
expect(rewritten.updated.contents).toBe("one\rtwo");
|
|
1687
|
+
expect(rewritten.updated.richText.paragraphs).toEqual([{ runs: [{ text: "one" }] }, { runs: [{ text: "two" }] }]);
|
|
1688
|
+
expect(rewritten.updated.richText.body.size).toBe(14);
|
|
1689
|
+
}
|
|
1690
|
+
expect(rewritten.meta.weakRefsInvalidated).toBe(false);
|
|
1691
|
+
} finally {
|
|
1692
|
+
await doc.close();
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
test("rich text: fontSize / fontColor / fontFamily move the body while runs keep their deltas", async () => {
|
|
1696
|
+
const doc = await openFixture$18(engine, opts);
|
|
1697
|
+
try {
|
|
1698
|
+
const page = doc.page(fix.pageObjectNumber);
|
|
1699
|
+
const created = await page.annotations.create({
|
|
1700
|
+
subtype: "free-text",
|
|
1701
|
+
intent: "free-text",
|
|
1702
|
+
rect: shapeRect,
|
|
1703
|
+
fontFamily: "helvetica",
|
|
1704
|
+
fontSize: 14,
|
|
1705
|
+
textAlign: "left",
|
|
1706
|
+
color: {
|
|
1707
|
+
r: 0,
|
|
1708
|
+
g: 0,
|
|
1709
|
+
b: 0
|
|
1710
|
+
},
|
|
1711
|
+
richText: {
|
|
1712
|
+
body: {
|
|
1713
|
+
family: "Helvetica",
|
|
1714
|
+
size: 14
|
|
1715
|
+
},
|
|
1716
|
+
paragraphs: [{ runs: [{ text: "a" }, {
|
|
1717
|
+
text: "b",
|
|
1718
|
+
style: { size: 30 }
|
|
1719
|
+
}] }]
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
const updated = await page.annotations.update(created.created.ref, {
|
|
1723
|
+
subtype: "free-text",
|
|
1724
|
+
fontSize: 20,
|
|
1725
|
+
fontColor: {
|
|
1726
|
+
r: 255,
|
|
1727
|
+
g: 0,
|
|
1728
|
+
b: 0
|
|
1729
|
+
},
|
|
1730
|
+
fontFamily: "times-bold"
|
|
1731
|
+
});
|
|
1732
|
+
expect(updated.updated.subtype).toBe("free-text");
|
|
1733
|
+
if (updated.updated.subtype === "free-text") {
|
|
1734
|
+
const dto = updated.updated;
|
|
1735
|
+
expect(dto.fontSize).toBe(20);
|
|
1736
|
+
expect(dto.fontFamily).toBe("times-bold");
|
|
1737
|
+
expect(dto.richText.body.size).toBe(20);
|
|
1738
|
+
expect(dto.richText.body.color).toBe("#FF0000");
|
|
1739
|
+
expect(dto.richText.body.family).toBe("Times");
|
|
1740
|
+
expect(dto.richText.body.weight).toBe(700);
|
|
1741
|
+
expect(dto.richText.paragraphs[0].runs[1]).toEqual({
|
|
1742
|
+
text: "b",
|
|
1743
|
+
style: { size: 30 }
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
} finally {
|
|
1747
|
+
await doc.close();
|
|
1748
|
+
}
|
|
1749
|
+
});
|
|
1750
|
+
test("rich text: contents and richText that disagree reject with InvalidArg; agreeing writes the runs", async () => {
|
|
1751
|
+
const doc = await openFixture$18(engine, opts);
|
|
1752
|
+
try {
|
|
1753
|
+
const page = doc.page(fix.pageObjectNumber);
|
|
1754
|
+
const created = await page.annotations.create({
|
|
1755
|
+
subtype: "free-text",
|
|
1756
|
+
intent: "free-text",
|
|
1757
|
+
contents: "x",
|
|
1758
|
+
rect: shapeRect,
|
|
1759
|
+
fontFamily: "helvetica",
|
|
1760
|
+
fontSize: 14,
|
|
1761
|
+
textAlign: "left",
|
|
1762
|
+
color: {
|
|
1763
|
+
r: 0,
|
|
1764
|
+
g: 0,
|
|
1765
|
+
b: 0
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
const ref = created.created.ref;
|
|
1769
|
+
let caught;
|
|
1770
|
+
try {
|
|
1771
|
+
await page.annotations.update(ref, {
|
|
1772
|
+
subtype: "free-text",
|
|
1773
|
+
contents: "stale",
|
|
1774
|
+
richText: { paragraphs: [{ runs: [{ text: "fresh" }] }] }
|
|
1775
|
+
});
|
|
1776
|
+
} catch (err) {
|
|
1777
|
+
caught = err;
|
|
1778
|
+
}
|
|
1779
|
+
expect(EngineError.is(caught, EngineErrorCode.InvalidArg)).toBe(true);
|
|
1780
|
+
const after = (await page.annotations.list()).annotations.find((a) => a.index === created.created.index);
|
|
1781
|
+
expect(after?.contents).toBe("x");
|
|
1782
|
+
const agreed = await page.annotations.update(ref, {
|
|
1783
|
+
subtype: "free-text",
|
|
1784
|
+
contents: "fresh",
|
|
1785
|
+
richText: { paragraphs: [{ runs: [{ text: "fr" }, {
|
|
1786
|
+
text: "esh",
|
|
1787
|
+
style: { weight: 700 }
|
|
1788
|
+
}] }] }
|
|
1789
|
+
});
|
|
1790
|
+
expect(agreed.updated.subtype).toBe("free-text");
|
|
1791
|
+
if (agreed.updated.subtype === "free-text") {
|
|
1792
|
+
expect(agreed.updated.contents).toBe("fresh");
|
|
1793
|
+
expect(agreed.updated.richText.paragraphs[0].runs[1]).toEqual({
|
|
1794
|
+
text: "esh",
|
|
1795
|
+
style: { weight: 700 }
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
} finally {
|
|
1799
|
+
await doc.close();
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1521
1802
|
test("create redact (area + text) round-trips label + colour fields", async () => {
|
|
1522
1803
|
const doc = await openFixture$18(engine, opts);
|
|
1523
1804
|
try {
|
|
@@ -6674,6 +6955,790 @@ function diffAnnotation(i, a, b, errs) {
|
|
|
6674
6955
|
if (a.nm !== b.nm) errs.push(`annotations[${i}].nm mismatch: ${a.nm} vs ${b.nm}`);
|
|
6675
6956
|
}
|
|
6676
6957
|
//#endregion
|
|
6677
|
-
|
|
6958
|
+
//#region src/conformance/runSignatureConformance.ts
|
|
6959
|
+
/**
|
|
6960
|
+
* Digital signatures, read side. The service is OPTIONAL on the contract
|
|
6961
|
+
* (`downloadLayer?` pattern) — the suite runs only where the engine
|
|
6962
|
+
* provides it.
|
|
6963
|
+
*
|
|
6964
|
+
* Invariants:
|
|
6965
|
+
* 1. Byte facts describe the LOADED bytes: a revision is a byte prefix,
|
|
6966
|
+
* a whole-revision signature seals exactly one, the digest over its
|
|
6967
|
+
* `/ByteRange` is what the file's bytes hash to, and an unsaved edit
|
|
6968
|
+
* changes none of it.
|
|
6969
|
+
* 2. Protection is derived from the signatures in the file and applied
|
|
6970
|
+
* as document-derived capabilities: a certification with permission 2
|
|
6971
|
+
* takes page assembly and annotation writes away, a FieldMDP freezes
|
|
6972
|
+
* the fields it names, a rewrite save is refused. Under
|
|
6973
|
+
* `signedDocumentPolicy: 'permit'` none of that applies and the
|
|
6974
|
+
* analysis is unchanged.
|
|
6975
|
+
* 3. The same answers come from a plain session and from a layer
|
|
6976
|
+
* session over the same bytes.
|
|
6977
|
+
*/
|
|
6978
|
+
function runSignatureConformance(runner, opts) {
|
|
6979
|
+
const { describe, test, beforeAll, afterAll, expect } = runner;
|
|
6980
|
+
describe(`signatures conformance: ${opts.label}`, () => {
|
|
6981
|
+
let engine;
|
|
6982
|
+
let permitEngine = null;
|
|
6983
|
+
let supported = false;
|
|
6984
|
+
beforeAll(async () => {
|
|
6985
|
+
engine = await opts.makeEngine();
|
|
6986
|
+
const probe = await open(engine, opts, opts.fixtures.unsignedForm);
|
|
6987
|
+
supported = probe.signatures !== void 0;
|
|
6988
|
+
await probe.close();
|
|
6989
|
+
if (supported && opts.makePermitEngine) permitEngine = await opts.makePermitEngine();
|
|
6990
|
+
});
|
|
6991
|
+
afterAll(async () => {
|
|
6992
|
+
if (permitEngine) await permitEngine.destroy();
|
|
6993
|
+
if (engine) await engine.destroy();
|
|
6994
|
+
});
|
|
6995
|
+
test("an unsigned form has no signatures and no protection; version() hashes the loaded bytes", async () => {
|
|
6996
|
+
if (!supported) return;
|
|
6997
|
+
const bytes = await opts.fixtures.unsignedForm.bytes();
|
|
6998
|
+
const doc = await open(engine, opts, opts.fixtures.unsignedForm);
|
|
6999
|
+
try {
|
|
7000
|
+
const snapshot = await doc.signatures.list();
|
|
7001
|
+
expect(snapshot.chainValid).toBe(true);
|
|
7002
|
+
expect(snapshot.signatures).toEqual([]);
|
|
7003
|
+
expect(snapshot.revisions.length >= 1).toBe(true);
|
|
7004
|
+
expect(snapshot.protection.judged).toBeNull();
|
|
7005
|
+
expect(snapshot.protection.enforced).toBeNull();
|
|
7006
|
+
expect(snapshot.protection.certification).toBeNull();
|
|
7007
|
+
expect(snapshot.protection.fieldLocks).toEqual([]);
|
|
7008
|
+
expect(doc.security.allows("doc.pages.assemble")).toBe(true);
|
|
7009
|
+
if (doc.version) {
|
|
7010
|
+
const version = await doc.version();
|
|
7011
|
+
expect(version.byteLength).toBe(bytes.byteLength);
|
|
7012
|
+
expect(version.sha256).toBe(await sha256Hex(bytes));
|
|
7013
|
+
}
|
|
7014
|
+
let caught;
|
|
7015
|
+
try {
|
|
7016
|
+
await doc.signatures.contents({
|
|
7017
|
+
kind: "fqn",
|
|
7018
|
+
name: "no-such-field"
|
|
7019
|
+
});
|
|
7020
|
+
} catch (err) {
|
|
7021
|
+
caught = err;
|
|
7022
|
+
}
|
|
7023
|
+
expect(EngineError.is(caught, EngineErrorCode.NotFound)).toBe(true);
|
|
7024
|
+
} finally {
|
|
7025
|
+
await doc.close();
|
|
7026
|
+
}
|
|
7027
|
+
});
|
|
7028
|
+
test("two approval signatures: revisions, coverage, digests, contents, revision bytes", async () => {
|
|
7029
|
+
if (!supported) return;
|
|
7030
|
+
const fixture = opts.fixtures.twoApprovals;
|
|
7031
|
+
const bytes = await fixture.bytes();
|
|
7032
|
+
const doc = await open(engine, opts, fixture);
|
|
7033
|
+
try {
|
|
7034
|
+
const snapshot = await doc.signatures.list();
|
|
7035
|
+
expect(snapshot.chainValid).toBe(true);
|
|
7036
|
+
expect(snapshot.revisions).toHaveLength(3);
|
|
7037
|
+
expect(snapshot.signatures).toHaveLength(2);
|
|
7038
|
+
expect(snapshot.revisions[2].end).toBe(bytes.byteLength);
|
|
7039
|
+
for (const [i, sig] of snapshot.signatures.entries()) {
|
|
7040
|
+
expect(sig.index).toBe(i);
|
|
7041
|
+
expect(sig.signed).toBe(true);
|
|
7042
|
+
expect(sig.kind).toBe("signature");
|
|
7043
|
+
expect(sig.field.kind).toBe("objectNumber");
|
|
7044
|
+
expect(sig.fieldName).toBe(fixture.fieldNames[i]);
|
|
7045
|
+
expect(sig.coverage).toBe("whole-revision");
|
|
7046
|
+
expect(sig.revisionIndex).toBe(i + 1);
|
|
7047
|
+
expect(snapshot.revisions[i + 1].signatureIndex).toBe(i);
|
|
7048
|
+
expect(sig.subFilter).toBe("ETSI.CAdES.detached");
|
|
7049
|
+
expect(typeof sig.signer.claimedTime).toBe("string");
|
|
7050
|
+
expect(sig.docMdp).toBeNull();
|
|
7051
|
+
expect(sig.catalogCertification).toBe(false);
|
|
7052
|
+
expect(sig.contentsSize > 0).toBe(true);
|
|
7053
|
+
expect(sig.byteRange[0]).toBe(0);
|
|
7054
|
+
expect(sig.byteRange[2] + sig.byteRange[3]).toBe(snapshot.revisions[i + 1].end);
|
|
7055
|
+
}
|
|
7056
|
+
expect(snapshot.protection.judged).toBe("annotate");
|
|
7057
|
+
expect(snapshot.protection.enforced).toBeNull();
|
|
7058
|
+
expect(snapshot.protection.certification).toBeNull();
|
|
7059
|
+
expect(snapshot.protection.fieldLocks).toEqual([]);
|
|
7060
|
+
const first = snapshot.signatures[0];
|
|
7061
|
+
const contents = await doc.signatures.contents(first.field);
|
|
7062
|
+
expect(contents.byteLength).toBe(first.contentsSize);
|
|
7063
|
+
expect(contents[0]).toBe(48);
|
|
7064
|
+
const [a, b, c, d] = first.byteRange;
|
|
7065
|
+
const sealed = await doc.signatures.revisionBytes(1);
|
|
7066
|
+
expect(sealed.byteLength).toBe(snapshot.revisions[1].end);
|
|
7067
|
+
expect(bytesEqual(sealed, bytes.subarray(0, sealed.byteLength))).toBe(true);
|
|
7068
|
+
const signedBytes = concat(sealed.subarray(a, a + b), sealed.subarray(c, c + d));
|
|
7069
|
+
const digest = await doc.signatures.digest(first.field, "sha256");
|
|
7070
|
+
expect(digest.byteLength).toBe(32);
|
|
7071
|
+
expect(toHex(digest)).toBe(await sha256Hex(signedBytes));
|
|
7072
|
+
expect((await doc.signatures.digest(first.field, "sha512")).byteLength).toBe(64);
|
|
7073
|
+
const byName = await doc.signatures.digest({
|
|
7074
|
+
kind: "fqn",
|
|
7075
|
+
name: first.fieldName
|
|
7076
|
+
}, "sha256");
|
|
7077
|
+
expect(toHex(byName)).toBe(toHex(digest));
|
|
7078
|
+
const text = (await doc.forms.list()).fields.find((f) => f.name === fixture.textField);
|
|
7079
|
+
expect(text).toBeTruthy();
|
|
7080
|
+
await doc.forms.setValue({
|
|
7081
|
+
kind: "objectNumber",
|
|
7082
|
+
fieldObjectNumber: text.fieldObjectNumber
|
|
7083
|
+
}, {
|
|
7084
|
+
type: "text",
|
|
7085
|
+
value: "unsaved"
|
|
7086
|
+
});
|
|
7087
|
+
const again = await doc.signatures.list();
|
|
7088
|
+
expect(again.revisions).toEqual(snapshot.revisions);
|
|
7089
|
+
expect(again.signatures.map((s) => [
|
|
7090
|
+
s.coverage,
|
|
7091
|
+
s.revisionIndex,
|
|
7092
|
+
s.byteRange
|
|
7093
|
+
])).toEqual(snapshot.signatures.map((s) => [
|
|
7094
|
+
s.coverage,
|
|
7095
|
+
s.revisionIndex,
|
|
7096
|
+
s.byteRange
|
|
7097
|
+
]));
|
|
7098
|
+
expect(toHex(await doc.signatures.digest(first.field, "sha256"))).toBe(toHex(digest));
|
|
7099
|
+
if (doc.version) expect((await doc.version()).sha256).toBe(await sha256Hex(bytes));
|
|
7100
|
+
} finally {
|
|
7101
|
+
await doc.close();
|
|
7102
|
+
}
|
|
7103
|
+
});
|
|
7104
|
+
test("a certification takes structural capabilities away; permit lifts it", async () => {
|
|
7105
|
+
if (!supported) return;
|
|
7106
|
+
const fixture = opts.fixtures.certified;
|
|
7107
|
+
const doc = await open(engine, opts, fixture);
|
|
7108
|
+
try {
|
|
7109
|
+
const snapshot = await doc.signatures.list();
|
|
7110
|
+
const signed = snapshot.signatures.filter((s) => s.signed);
|
|
7111
|
+
expect(signed).toHaveLength(1);
|
|
7112
|
+
const sig = signed[0];
|
|
7113
|
+
expect(sig.fieldName).toBe(fixture.fieldName);
|
|
7114
|
+
expect(sig.coverage).toBe("whole-revision");
|
|
7115
|
+
expect(sig.docMdp).toBe(2);
|
|
7116
|
+
expect(sig.catalogCertification).toBe(true);
|
|
7117
|
+
expect(snapshot.protection.judged).toBe("fill");
|
|
7118
|
+
expect(snapshot.protection.enforced).toBe("fill");
|
|
7119
|
+
expect(snapshot.protection.certification).toEqual({
|
|
7120
|
+
signatureIndex: sig.index,
|
|
7121
|
+
permission: 2
|
|
7122
|
+
});
|
|
7123
|
+
expect(doc.security.allows("doc.forms.read")).toBe(true);
|
|
7124
|
+
expect(doc.security.allows("doc.forms.fill")).toBe(true);
|
|
7125
|
+
expect(doc.security.allows("doc.annotate.modify")).toBe(false);
|
|
7126
|
+
expect(doc.security.allows("doc.pages.assemble")).toBe(false);
|
|
7127
|
+
expect(doc.security.allows("doc.redact")).toBe(false);
|
|
7128
|
+
const list = await doc.pages.list();
|
|
7129
|
+
let caught;
|
|
7130
|
+
try {
|
|
7131
|
+
await doc.pages.delete([list.pages[0].pageObjectNumber]);
|
|
7132
|
+
} catch (err) {
|
|
7133
|
+
caught = err;
|
|
7134
|
+
}
|
|
7135
|
+
expect(EngineError.is(caught, EngineErrorCode.ProtectedDocument)).toBe(true);
|
|
7136
|
+
caught = void 0;
|
|
7137
|
+
try {
|
|
7138
|
+
await doc.download({ mode: "rewrite" });
|
|
7139
|
+
} catch (err) {
|
|
7140
|
+
caught = err;
|
|
7141
|
+
}
|
|
7142
|
+
expect(EngineError.is(caught, EngineErrorCode.ProtectedDocument)).toBe(true);
|
|
7143
|
+
const saved = await doc.download();
|
|
7144
|
+
expect(saved.byteLength >= snapshot.revisions[1].end).toBe(true);
|
|
7145
|
+
} finally {
|
|
7146
|
+
await doc.close();
|
|
7147
|
+
}
|
|
7148
|
+
if (!permitEngine) return;
|
|
7149
|
+
const permitted = await open(permitEngine, opts, fixture);
|
|
7150
|
+
try {
|
|
7151
|
+
const snapshot = await permitted.signatures.list();
|
|
7152
|
+
expect(snapshot.protection.judged).toBe("fill");
|
|
7153
|
+
expect(snapshot.protection.enforced).toBe("fill");
|
|
7154
|
+
expect(permitted.security.allows("doc.pages.assemble")).toBe(true);
|
|
7155
|
+
const rewritten = await permitted.download({ mode: "rewrite" });
|
|
7156
|
+
expect(rewritten.byteLength > 0).toBe(true);
|
|
7157
|
+
} finally {
|
|
7158
|
+
await permitted.close();
|
|
7159
|
+
}
|
|
7160
|
+
});
|
|
7161
|
+
test("a FieldMDP freezes the fields it names; permit lifts it", async () => {
|
|
7162
|
+
if (!supported) return;
|
|
7163
|
+
const fixture = opts.fixtures.fieldMdp;
|
|
7164
|
+
const doc = await open(engine, opts, fixture);
|
|
7165
|
+
const lockedRef = {
|
|
7166
|
+
kind: "fqn",
|
|
7167
|
+
name: fixture.lockedField
|
|
7168
|
+
};
|
|
7169
|
+
try {
|
|
7170
|
+
const snapshot = await doc.signatures.list();
|
|
7171
|
+
const sig = snapshot.signatures[0];
|
|
7172
|
+
expect(sig.fieldName).toBe(fixture.fieldName);
|
|
7173
|
+
expect(sig.fieldMdp).toEqual({
|
|
7174
|
+
action: "include",
|
|
7175
|
+
fields: [fixture.lockedField]
|
|
7176
|
+
});
|
|
7177
|
+
expect(snapshot.protection.judged).toBe("annotate");
|
|
7178
|
+
expect(snapshot.protection.enforced).toBeNull();
|
|
7179
|
+
expect(snapshot.protection.fieldLocks[0]).toEqual({
|
|
7180
|
+
signatureIndex: 0,
|
|
7181
|
+
source: "fieldmdp",
|
|
7182
|
+
spec: {
|
|
7183
|
+
action: "include",
|
|
7184
|
+
fields: [fixture.lockedField]
|
|
7185
|
+
}
|
|
7186
|
+
});
|
|
7187
|
+
for (const lock of snapshot.protection.fieldLocks) {
|
|
7188
|
+
expect(lock.signatureIndex).toBe(0);
|
|
7189
|
+
expect(lock.spec).toEqual({
|
|
7190
|
+
action: "include",
|
|
7191
|
+
fields: [fixture.lockedField]
|
|
7192
|
+
});
|
|
7193
|
+
}
|
|
7194
|
+
expect(doc.security.allows("doc.forms.fill")).toBe(true);
|
|
7195
|
+
let caught;
|
|
7196
|
+
try {
|
|
7197
|
+
await doc.forms.setValue(lockedRef, {
|
|
7198
|
+
type: "text",
|
|
7199
|
+
value: "changed"
|
|
7200
|
+
});
|
|
7201
|
+
} catch (err) {
|
|
7202
|
+
caught = err;
|
|
7203
|
+
}
|
|
7204
|
+
expect(EngineError.is(caught, EngineErrorCode.ProtectedDocument)).toBe(true);
|
|
7205
|
+
const field = await doc.forms.get(lockedRef);
|
|
7206
|
+
expect(field.family).toBe("text");
|
|
7207
|
+
expect(field.value === "changed").toBe(false);
|
|
7208
|
+
} finally {
|
|
7209
|
+
await doc.close();
|
|
7210
|
+
}
|
|
7211
|
+
if (!permitEngine) return;
|
|
7212
|
+
const permitted = await open(permitEngine, opts, fixture);
|
|
7213
|
+
try {
|
|
7214
|
+
const result = await permitted.forms.setValue(lockedRef, {
|
|
7215
|
+
type: "text",
|
|
7216
|
+
value: "changed"
|
|
7217
|
+
});
|
|
7218
|
+
expect(result.field.name).toBe(fixture.lockedField);
|
|
7219
|
+
} finally {
|
|
7220
|
+
await permitted.close();
|
|
7221
|
+
}
|
|
7222
|
+
});
|
|
7223
|
+
test("well-formed ranges that seal no revision are partial, with no revision index", async () => {
|
|
7224
|
+
if (!supported) return;
|
|
7225
|
+
const doc = await open(engine, opts, opts.fixtures.partialChain);
|
|
7226
|
+
try {
|
|
7227
|
+
const snapshot = await doc.signatures.list();
|
|
7228
|
+
expect(snapshot.chainValid).toBe(true);
|
|
7229
|
+
expect(snapshot.revisions).toHaveLength(3);
|
|
7230
|
+
expect(snapshot.signatures).toHaveLength(2);
|
|
7231
|
+
for (const sig of snapshot.signatures) {
|
|
7232
|
+
expect(sig.signed).toBe(true);
|
|
7233
|
+
expect(sig.coverage).toBe("partial");
|
|
7234
|
+
expect(sig.revisionIndex).toBeNull();
|
|
7235
|
+
expect(sig.contentsSize).toBe(13);
|
|
7236
|
+
expect((await doc.signatures.contents(sig.field)).byteLength).toBe(13);
|
|
7237
|
+
expect((await doc.signatures.digest(sig.field, "sha256")).byteLength).toBe(32);
|
|
7238
|
+
}
|
|
7239
|
+
expect(snapshot.revisions.every((r) => r.signatureIndex === null)).toBe(true);
|
|
7240
|
+
expect(snapshot.protection.judged).toBe("annotate");
|
|
7241
|
+
expect(snapshot.protection.enforced).toBeNull();
|
|
7242
|
+
let caught;
|
|
7243
|
+
try {
|
|
7244
|
+
await doc.signatures.revisionBytes(3);
|
|
7245
|
+
} catch (err) {
|
|
7246
|
+
caught = err;
|
|
7247
|
+
}
|
|
7248
|
+
expect(EngineError.is(caught, EngineErrorCode.NotFound)).toBe(true);
|
|
7249
|
+
} finally {
|
|
7250
|
+
await doc.close();
|
|
7251
|
+
}
|
|
7252
|
+
});
|
|
7253
|
+
if (opts.openKind !== "id") {
|
|
7254
|
+
runSigningTests(runner, opts, () => engine, () => supported);
|
|
7255
|
+
runAnalysisTests(runner, opts, () => engine, () => permitEngine, () => supported);
|
|
7256
|
+
}
|
|
7257
|
+
});
|
|
7258
|
+
}
|
|
7259
|
+
function runAnalysisTests(runner, opts, engineOf, permitEngineOf, supported) {
|
|
7260
|
+
const { test, expect } = runner;
|
|
7261
|
+
test("analyze: a form fill and a second signature after an approval are permitted", async () => {
|
|
7262
|
+
if (!supported()) return;
|
|
7263
|
+
const doc = await open(engineOf(), opts, opts.fixtures.twoApprovals);
|
|
7264
|
+
try {
|
|
7265
|
+
const analysis = await doc.signatures.analyze({ since: { signatureIndex: 0 } });
|
|
7266
|
+
expect(analysis.mode).toBe("authoritative");
|
|
7267
|
+
expect(analysis.basis.source).toBe("persisted");
|
|
7268
|
+
expect(analysis.since).toEqual({
|
|
7269
|
+
revisionIndex: 1,
|
|
7270
|
+
signatureIndex: 0
|
|
7271
|
+
});
|
|
7272
|
+
expect(analysis.until.revisionIndex).toBe(2);
|
|
7273
|
+
expect(analysis.steps).toHaveLength(1);
|
|
7274
|
+
const [step] = analysis.steps;
|
|
7275
|
+
expect(step.levelInForce).toBe("annotate");
|
|
7276
|
+
expect(step.verdict).toBe("permitted");
|
|
7277
|
+
expect(step.changes.length > 0).toBe(true);
|
|
7278
|
+
const rules = new Set(step.findings.filter((f) => f.verdict === "permitted").map((f) => f.rule));
|
|
7279
|
+
expect(rules.has("form-fill")).toBe(true);
|
|
7280
|
+
expect(rules.has("signature-added")).toBe(true);
|
|
7281
|
+
expect(step.findings.filter((f) => f.verdict === "forbidden")).toEqual([]);
|
|
7282
|
+
expect(analysis.verdict).toBe("permitted");
|
|
7283
|
+
const latest = await doc.signatures.analyze({ since: { signatureIndex: 1 } });
|
|
7284
|
+
expect(latest.steps).toHaveLength(0);
|
|
7285
|
+
expect(latest.verdict).toBe("unchanged");
|
|
7286
|
+
const historic = await doc.signatures.analyze({
|
|
7287
|
+
since: { revisionIndex: 0 },
|
|
7288
|
+
until: { revisionIndex: 1 }
|
|
7289
|
+
});
|
|
7290
|
+
expect(historic.steps).toHaveLength(1);
|
|
7291
|
+
expect(historic.verdict).toBe("permitted");
|
|
7292
|
+
} finally {
|
|
7293
|
+
await doc.close();
|
|
7294
|
+
}
|
|
7295
|
+
});
|
|
7296
|
+
test("analyze: the working copy is one more revision; a partial signature cannot anchor", async () => {
|
|
7297
|
+
if (!supported()) return;
|
|
7298
|
+
const fx = opts.fixtures.unsignedSigField;
|
|
7299
|
+
const doc = await open(engineOf(), opts, fx);
|
|
7300
|
+
try {
|
|
7301
|
+
const prepared = await doc.signatures.prepare({
|
|
7302
|
+
field: {
|
|
7303
|
+
kind: "fqn",
|
|
7304
|
+
name: fx.fieldName
|
|
7305
|
+
},
|
|
7306
|
+
certify: { permission: 2 }
|
|
7307
|
+
});
|
|
7308
|
+
await doc.signatures.complete({
|
|
7309
|
+
signingId: prepared.signingId,
|
|
7310
|
+
cms: FAKE_CMS,
|
|
7311
|
+
expectedVersion: prepared.expectedVersion
|
|
7312
|
+
});
|
|
7313
|
+
const clean = await doc.signatures.analyze({
|
|
7314
|
+
since: { signatureIndex: 0 },
|
|
7315
|
+
until: "working-copy"
|
|
7316
|
+
});
|
|
7317
|
+
expect(clean.steps).toHaveLength(0);
|
|
7318
|
+
expect(clean.verdict).toBe("unchanged");
|
|
7319
|
+
await doc.forms.setValue({
|
|
7320
|
+
kind: "fqn",
|
|
7321
|
+
name: fx.textField
|
|
7322
|
+
}, {
|
|
7323
|
+
type: "text",
|
|
7324
|
+
value: "draft"
|
|
7325
|
+
});
|
|
7326
|
+
const persisted = await doc.signatures.analyze({ since: { signatureIndex: 0 } });
|
|
7327
|
+
expect(persisted.steps).toHaveLength(0);
|
|
7328
|
+
const working = await doc.signatures.analyze({
|
|
7329
|
+
since: { signatureIndex: 0 },
|
|
7330
|
+
until: "working-copy"
|
|
7331
|
+
});
|
|
7332
|
+
expect(working.basis.source).toBe("working-copy");
|
|
7333
|
+
expect(working.steps).toHaveLength(1);
|
|
7334
|
+
expect(working.steps[0].levelInForce).toBe("fill");
|
|
7335
|
+
expect(working.verdict).toBe("permitted");
|
|
7336
|
+
expect(working.steps[0].findings.some((f) => f.rule === "form-fill" && f.verdict === "permitted")).toBe(true);
|
|
7337
|
+
} finally {
|
|
7338
|
+
await doc.close();
|
|
7339
|
+
}
|
|
7340
|
+
const partial = await open(engineOf(), opts, opts.fixtures.partialChain);
|
|
7341
|
+
try {
|
|
7342
|
+
expect(await caughtCode(() => partial.signatures.analyze({ since: { signatureIndex: 0 } }))).toBe(EngineErrorCode.InvalidArg);
|
|
7343
|
+
const byRevision = await partial.signatures.analyze({ since: { revisionIndex: 0 } });
|
|
7344
|
+
expect(byRevision.later.revisionCount).toBe(2);
|
|
7345
|
+
expect(byRevision.steps).toHaveLength(0);
|
|
7346
|
+
const inFull = await partial.signatures.analyze({
|
|
7347
|
+
since: { revisionIndex: 0 },
|
|
7348
|
+
detail: "full"
|
|
7349
|
+
});
|
|
7350
|
+
expect(inFull.steps).toHaveLength(2);
|
|
7351
|
+
expect(inFull.current.verdict).toBe(byRevision.current.verdict);
|
|
7352
|
+
} finally {
|
|
7353
|
+
await partial.close();
|
|
7354
|
+
}
|
|
7355
|
+
});
|
|
7356
|
+
test("analyze: a page edit under a certification and a locked field edit are forbidden", async () => {
|
|
7357
|
+
if (!supported()) return;
|
|
7358
|
+
const permit = permitEngineOf();
|
|
7359
|
+
if (!permit) return;
|
|
7360
|
+
const certified = await open(permit, opts, opts.fixtures.certified);
|
|
7361
|
+
let reopened = null;
|
|
7362
|
+
try {
|
|
7363
|
+
const list = await certified.pages.list();
|
|
7364
|
+
await certified.pages.rotate([list.pages[0].pageObjectNumber], 90);
|
|
7365
|
+
const working = await certified.signatures.analyze({
|
|
7366
|
+
since: { signatureIndex: 0 },
|
|
7367
|
+
until: "working-copy"
|
|
7368
|
+
});
|
|
7369
|
+
expect(working.verdict).toBe("forbidden");
|
|
7370
|
+
const bytes = await certified.download();
|
|
7371
|
+
reopened = await reopen(permit, opts, `${opts.fixtures.certified.id}-tampered`, bytes);
|
|
7372
|
+
const analysis = await reopened.signatures.analyze({ since: { signatureIndex: 0 } });
|
|
7373
|
+
expect(analysis.verdict).toBe("forbidden");
|
|
7374
|
+
expect(analysis.steps[0].findings.some((f) => f.verdict === "forbidden")).toBe(true);
|
|
7375
|
+
} finally {
|
|
7376
|
+
if (reopened) await reopened.close();
|
|
7377
|
+
await certified.close();
|
|
7378
|
+
}
|
|
7379
|
+
const locked = await open(permit, opts, opts.fixtures.fieldMdp);
|
|
7380
|
+
try {
|
|
7381
|
+
await locked.forms.setValue({
|
|
7382
|
+
kind: "fqn",
|
|
7383
|
+
name: opts.fixtures.fieldMdp.lockedField
|
|
7384
|
+
}, {
|
|
7385
|
+
type: "text",
|
|
7386
|
+
value: "tampered"
|
|
7387
|
+
});
|
|
7388
|
+
const working = await locked.signatures.analyze({
|
|
7389
|
+
since: { signatureIndex: 0 },
|
|
7390
|
+
until: "working-copy"
|
|
7391
|
+
});
|
|
7392
|
+
expect(working.verdict).toBe("forbidden");
|
|
7393
|
+
expect(working.steps[0].findings.some((f) => f.rule === "field-lock")).toBe(true);
|
|
7394
|
+
} finally {
|
|
7395
|
+
await locked.close();
|
|
7396
|
+
}
|
|
7397
|
+
});
|
|
7398
|
+
}
|
|
7399
|
+
/** A minimal DER SEQUENCE standing in for a CMS: the engine's job ends at the bytes. */
|
|
7400
|
+
const FAKE_CMS = new Uint8Array([
|
|
7401
|
+
48,
|
|
7402
|
+
6,
|
|
7403
|
+
2,
|
|
7404
|
+
1,
|
|
7405
|
+
1,
|
|
7406
|
+
2,
|
|
7407
|
+
1,
|
|
7408
|
+
2
|
|
7409
|
+
]);
|
|
7410
|
+
async function caughtCode(run) {
|
|
7411
|
+
try {
|
|
7412
|
+
await run();
|
|
7413
|
+
return null;
|
|
7414
|
+
} catch (err) {
|
|
7415
|
+
return err instanceof EngineError ? err.code : "not-an-engine-error";
|
|
7416
|
+
}
|
|
7417
|
+
}
|
|
7418
|
+
function runSigningTests(runner, opts, engineOf, supported) {
|
|
7419
|
+
const { test, expect } = runner;
|
|
7420
|
+
const fx = () => opts.fixtures.unsignedSigField;
|
|
7421
|
+
const sigRef = () => ({
|
|
7422
|
+
kind: "fqn",
|
|
7423
|
+
name: fx().fieldName
|
|
7424
|
+
});
|
|
7425
|
+
const textRef = () => ({
|
|
7426
|
+
kind: "fqn",
|
|
7427
|
+
name: fx().textField
|
|
7428
|
+
});
|
|
7429
|
+
test("sign: prepare seals a candidate, complete installs it, download returns it verbatim", async () => {
|
|
7430
|
+
if (!supported()) return;
|
|
7431
|
+
const engine = engineOf();
|
|
7432
|
+
const doc = await open(engine, opts, fx());
|
|
7433
|
+
const events = [];
|
|
7434
|
+
const unsubscribe = doc.events.subscribe((event) => events.push(event.type));
|
|
7435
|
+
let reopened = null;
|
|
7436
|
+
try {
|
|
7437
|
+
const v0 = await doc.version();
|
|
7438
|
+
const before = await doc.signatures.list();
|
|
7439
|
+
expect(before.revisions).toHaveLength(1);
|
|
7440
|
+
expect(before.signatures).toHaveLength(1);
|
|
7441
|
+
expect(before.signatures[0].signed).toBe(false);
|
|
7442
|
+
expect(before.protection.judged).toBeNull();
|
|
7443
|
+
const prepared = await doc.signatures.prepare({
|
|
7444
|
+
field: sigRef(),
|
|
7445
|
+
subFilter: "ETSI.CAdES.detached",
|
|
7446
|
+
digest: "sha256",
|
|
7447
|
+
contentsSize: 1024,
|
|
7448
|
+
attribution: { reason: "conformance" }
|
|
7449
|
+
});
|
|
7450
|
+
expect(prepared.digest.byteLength).toBe(32);
|
|
7451
|
+
expect(prepared.algorithm).toBe("sha256");
|
|
7452
|
+
expect(prepared.subFilter).toBe("ETSI.CAdES.detached");
|
|
7453
|
+
expect(prepared.contentsSize).toBe(1024);
|
|
7454
|
+
expect(prepared.byteRange[0]).toBe(0);
|
|
7455
|
+
expect(prepared.byteRange[2] > prepared.byteRange[1]).toBe(true);
|
|
7456
|
+
expect(prepared.expectedVersion.baseSha256).toBe(v0.sha256);
|
|
7457
|
+
expect(await caughtCode(() => doc.signatures.prepare({ field: sigRef() }))).toBe(EngineErrorCode.SigningPending);
|
|
7458
|
+
expect(await caughtCode(() => doc.forms.setValue(textRef(), {
|
|
7459
|
+
type: "text",
|
|
7460
|
+
value: "x"
|
|
7461
|
+
}))).toBe(EngineErrorCode.SigningPending);
|
|
7462
|
+
const during = await doc.signatures.list();
|
|
7463
|
+
expect(during.signatures[0].signed).toBe(false);
|
|
7464
|
+
expect((await doc.version()).sha256).toBe(v0.sha256);
|
|
7465
|
+
expect(await caughtCode(() => doc.signatures.complete({
|
|
7466
|
+
signingId: prepared.signingId,
|
|
7467
|
+
cms: FAKE_CMS,
|
|
7468
|
+
expectedVersion: {
|
|
7469
|
+
baseSha256: prepared.expectedVersion.baseSha256,
|
|
7470
|
+
editsVersion: prepared.expectedVersion.editsVersion + 1
|
|
7471
|
+
}
|
|
7472
|
+
}))).toBe(EngineErrorCode.SigningVersionMismatch);
|
|
7473
|
+
const result = await doc.signatures.complete({
|
|
7474
|
+
signingId: prepared.signingId,
|
|
7475
|
+
cms: FAKE_CMS,
|
|
7476
|
+
expectedVersion: prepared.expectedVersion
|
|
7477
|
+
});
|
|
7478
|
+
expect(result.status).toBe("completed");
|
|
7479
|
+
expect(result.signature.signed).toBe(true);
|
|
7480
|
+
expect(result.signature.fieldName).toBe(fx().fieldName);
|
|
7481
|
+
expect(result.signature.coverage).toBe("whole-revision");
|
|
7482
|
+
expect(result.signature.revisionIndex).toBe(1);
|
|
7483
|
+
expect(result.signature.subFilter).toBe("ETSI.CAdES.detached");
|
|
7484
|
+
expect(result.signature.signer.reason).toBe("conformance");
|
|
7485
|
+
expect(result.signature.contentsSize).toBe(FAKE_CMS.byteLength);
|
|
7486
|
+
expect(result.previous).toEqual(prepared.expectedVersion);
|
|
7487
|
+
expect(result.version.sha256 === v0.sha256).toBe(false);
|
|
7488
|
+
expect(result.protection.judged).toBe("annotate");
|
|
7489
|
+
expect(result.protection.enforced).toBeNull();
|
|
7490
|
+
expect(result.meta.affectedPages.length >= 1).toBe(true);
|
|
7491
|
+
expect(await doc.version()).toEqual(result.version);
|
|
7492
|
+
const after = await doc.signatures.list();
|
|
7493
|
+
expect(after.revisions).toHaveLength(2);
|
|
7494
|
+
expect(after.signatures[0].signed).toBe(true);
|
|
7495
|
+
expect(after.signatures[0].revisionIndex).toBe(1);
|
|
7496
|
+
expect(after.revisions[1].signatureIndex).toBe(0);
|
|
7497
|
+
expect(toHex(await doc.signatures.digest(sigRef(), "sha256"))).toBe(toHex(prepared.digest));
|
|
7498
|
+
expect(bytesEqual(await doc.signatures.contents(sigRef()), FAKE_CMS)).toBe(true);
|
|
7499
|
+
const bytes = await doc.download();
|
|
7500
|
+
expect(bytes.byteLength).toBe(result.version.byteLength);
|
|
7501
|
+
expect(await sha256Hex(bytes)).toBe(result.version.sha256);
|
|
7502
|
+
reopened = await reopen(engine, opts, `${fx().id}-signed`, bytes);
|
|
7503
|
+
const fresh = await reopened.signatures.list();
|
|
7504
|
+
expect(fresh.revisions).toHaveLength(2);
|
|
7505
|
+
expect(fresh.signatures[0].coverage).toBe("whole-revision");
|
|
7506
|
+
expect(toHex(await reopened.signatures.digest(sigRef(), "sha256"))).toBe(toHex(prepared.digest));
|
|
7507
|
+
const replay = await doc.signatures.complete({
|
|
7508
|
+
signingId: prepared.signingId,
|
|
7509
|
+
cms: FAKE_CMS,
|
|
7510
|
+
expectedVersion: prepared.expectedVersion
|
|
7511
|
+
});
|
|
7512
|
+
expect(replay.status).toBe("already-completed");
|
|
7513
|
+
expect(replay.version).toEqual(result.version);
|
|
7514
|
+
expect((await doc.signatures.abort(prepared.signingId)).status).toBe("already-completed");
|
|
7515
|
+
expect((await doc.signatures.abort("never-prepared")).status).toBe("unknown");
|
|
7516
|
+
await doc.forms.setValue(textRef(), {
|
|
7517
|
+
type: "text",
|
|
7518
|
+
value: "after"
|
|
7519
|
+
});
|
|
7520
|
+
expect(await caughtCode(() => doc.signatures.prepare({ field: sigRef() }))).toBe(EngineErrorCode.SignatureRefused);
|
|
7521
|
+
expect(events.filter((t) => t === "signature.prepared")).toHaveLength(1);
|
|
7522
|
+
expect(events.filter((t) => t === "signature.completed")).toHaveLength(1);
|
|
7523
|
+
expect(events.filter((t) => t === "document.versioned")).toHaveLength(1);
|
|
7524
|
+
} finally {
|
|
7525
|
+
unsubscribe();
|
|
7526
|
+
if (reopened) await reopened.close();
|
|
7527
|
+
await doc.close();
|
|
7528
|
+
}
|
|
7529
|
+
});
|
|
7530
|
+
test("sign: unsaved edits are sealed in the signature's own revision (layer session)", async () => {
|
|
7531
|
+
if (!supported()) return;
|
|
7532
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7533
|
+
try {
|
|
7534
|
+
await doc.forms.setValue(textRef(), {
|
|
7535
|
+
type: "text",
|
|
7536
|
+
value: "unsaved"
|
|
7537
|
+
});
|
|
7538
|
+
const prepared = await doc.signatures.prepare({ field: sigRef() });
|
|
7539
|
+
const result = await doc.signatures.complete({
|
|
7540
|
+
signingId: prepared.signingId,
|
|
7541
|
+
cms: FAKE_CMS,
|
|
7542
|
+
expectedVersion: prepared.expectedVersion
|
|
7543
|
+
});
|
|
7544
|
+
expect(result.signature.revisionIndex).toBe(1);
|
|
7545
|
+
const after = await doc.signatures.list();
|
|
7546
|
+
expect(after.revisions).toHaveLength(2);
|
|
7547
|
+
const text = await doc.forms.get(textRef());
|
|
7548
|
+
expect(text.value).toBe("unsaved");
|
|
7549
|
+
const bytes = await doc.download();
|
|
7550
|
+
expect(await sha256Hex(bytes)).toBe(result.version.sha256);
|
|
7551
|
+
} finally {
|
|
7552
|
+
await doc.close();
|
|
7553
|
+
}
|
|
7554
|
+
});
|
|
7555
|
+
test("sign: a certification protects the document from the moment it completes", async () => {
|
|
7556
|
+
if (!supported()) return;
|
|
7557
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7558
|
+
try {
|
|
7559
|
+
expect(doc.security.allows("doc.pages.assemble")).toBe(true);
|
|
7560
|
+
const prepared = await doc.signatures.prepare({
|
|
7561
|
+
field: sigRef(),
|
|
7562
|
+
certify: { permission: 2 },
|
|
7563
|
+
subFilter: "adbe.pkcs7.detached"
|
|
7564
|
+
});
|
|
7565
|
+
const result = await doc.signatures.complete({
|
|
7566
|
+
signingId: prepared.signingId,
|
|
7567
|
+
cms: FAKE_CMS,
|
|
7568
|
+
expectedVersion: prepared.expectedVersion
|
|
7569
|
+
});
|
|
7570
|
+
expect(result.signature.docMdp).toBe(2);
|
|
7571
|
+
expect(result.signature.catalogCertification).toBe(true);
|
|
7572
|
+
expect(result.protection.judged).toBe("fill");
|
|
7573
|
+
expect(result.protection.enforced).toBe("fill");
|
|
7574
|
+
expect(result.protection.certification).toEqual({
|
|
7575
|
+
signatureIndex: result.signature.index,
|
|
7576
|
+
permission: 2
|
|
7577
|
+
});
|
|
7578
|
+
expect(doc.security.allows("doc.pages.assemble")).toBe(false);
|
|
7579
|
+
expect(doc.security.allows("doc.forms.fill")).toBe(true);
|
|
7580
|
+
const list = await doc.pages.list();
|
|
7581
|
+
expect(await caughtCode(() => doc.pages.delete([list.pages[0].pageObjectNumber]))).toBe(EngineErrorCode.ProtectedDocument);
|
|
7582
|
+
expect(await caughtCode(() => doc.download({ mode: "rewrite" }))).toBe(EngineErrorCode.ProtectedDocument);
|
|
7583
|
+
await doc.forms.setValue(textRef(), {
|
|
7584
|
+
type: "text",
|
|
7585
|
+
value: "filled"
|
|
7586
|
+
});
|
|
7587
|
+
const after = await doc.signatures.list();
|
|
7588
|
+
expect(after.signatures[0].coverage).toBe("whole-revision");
|
|
7589
|
+
} finally {
|
|
7590
|
+
await doc.close();
|
|
7591
|
+
}
|
|
7592
|
+
});
|
|
7593
|
+
test("sign: a lock freezes the named fields and is mirrored on the field", async () => {
|
|
7594
|
+
if (!supported()) return;
|
|
7595
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7596
|
+
try {
|
|
7597
|
+
const prepared = await doc.signatures.prepare({
|
|
7598
|
+
field: sigRef(),
|
|
7599
|
+
lock: {
|
|
7600
|
+
action: "include",
|
|
7601
|
+
fields: [fx().textField]
|
|
7602
|
+
}
|
|
7603
|
+
});
|
|
7604
|
+
const result = await doc.signatures.complete({
|
|
7605
|
+
signingId: prepared.signingId,
|
|
7606
|
+
cms: FAKE_CMS,
|
|
7607
|
+
expectedVersion: prepared.expectedVersion
|
|
7608
|
+
});
|
|
7609
|
+
expect(result.signature.fieldMdp).toEqual({
|
|
7610
|
+
action: "include",
|
|
7611
|
+
fields: [fx().textField]
|
|
7612
|
+
});
|
|
7613
|
+
expect(result.signature.lock).toEqual({
|
|
7614
|
+
action: "include",
|
|
7615
|
+
fields: [fx().textField]
|
|
7616
|
+
});
|
|
7617
|
+
expect(result.protection.fieldLocks.length >= 1).toBe(true);
|
|
7618
|
+
expect(await caughtCode(() => doc.forms.setValue(textRef(), {
|
|
7619
|
+
type: "text",
|
|
7620
|
+
value: "nope"
|
|
7621
|
+
}))).toBe(EngineErrorCode.ProtectedDocument);
|
|
7622
|
+
} finally {
|
|
7623
|
+
await doc.close();
|
|
7624
|
+
}
|
|
7625
|
+
});
|
|
7626
|
+
test("sign: abort discards the candidate and lifts the fence", async () => {
|
|
7627
|
+
if (!supported()) return;
|
|
7628
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7629
|
+
try {
|
|
7630
|
+
const v0 = await doc.version();
|
|
7631
|
+
const prepared = await doc.signatures.prepare({ field: sigRef() });
|
|
7632
|
+
expect((await doc.signatures.abort(prepared.signingId)).status).toBe("aborted");
|
|
7633
|
+
expect((await doc.signatures.list()).signatures[0].signed).toBe(false);
|
|
7634
|
+
expect((await doc.version()).sha256).toBe(v0.sha256);
|
|
7635
|
+
await doc.forms.setValue(textRef(), {
|
|
7636
|
+
type: "text",
|
|
7637
|
+
value: "free again"
|
|
7638
|
+
});
|
|
7639
|
+
expect(await caughtCode(() => doc.signatures.complete({
|
|
7640
|
+
signingId: prepared.signingId,
|
|
7641
|
+
cms: FAKE_CMS,
|
|
7642
|
+
expectedVersion: prepared.expectedVersion
|
|
7643
|
+
}))).toBe(EngineErrorCode.NotFound);
|
|
7644
|
+
} finally {
|
|
7645
|
+
await doc.close();
|
|
7646
|
+
}
|
|
7647
|
+
});
|
|
7648
|
+
test("sign: artwork from a PDF page lands on the widget", async () => {
|
|
7649
|
+
if (!supported()) return;
|
|
7650
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7651
|
+
try {
|
|
7652
|
+
const artwork = await opts.fixtures.artwork.bytes();
|
|
7653
|
+
const prepared = await doc.signatures.prepare({
|
|
7654
|
+
field: sigRef(),
|
|
7655
|
+
appearance: {
|
|
7656
|
+
pdf: artwork,
|
|
7657
|
+
pageIndex: 0
|
|
7658
|
+
}
|
|
7659
|
+
});
|
|
7660
|
+
const result = await doc.signatures.complete({
|
|
7661
|
+
signingId: prepared.signingId,
|
|
7662
|
+
cms: FAKE_CMS,
|
|
7663
|
+
expectedVersion: prepared.expectedVersion
|
|
7664
|
+
});
|
|
7665
|
+
expect(result.signature.coverage).toBe("whole-revision");
|
|
7666
|
+
expect(result.signature.widget).toBeTruthy();
|
|
7667
|
+
const widget = (await doc.page(result.signature.widget.pageObjectNumber).annotations.list()).annotations.find((a) => a.ref.kind === "objectNumber" && a.ref.annotObjectNumber === result.signature.widget.annotObjectNumber);
|
|
7668
|
+
expect(widget).toBeTruthy();
|
|
7669
|
+
} finally {
|
|
7670
|
+
await doc.close();
|
|
7671
|
+
}
|
|
7672
|
+
});
|
|
7673
|
+
if (opts.openKind === "layerBytes") test("sign: a layer session keeps its kind, with an empty layer over the new base", async () => {
|
|
7674
|
+
if (!supported()) return;
|
|
7675
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7676
|
+
try {
|
|
7677
|
+
const prepared = await doc.signatures.prepare({ field: sigRef() });
|
|
7678
|
+
const result = await doc.signatures.complete({
|
|
7679
|
+
signingId: prepared.signingId,
|
|
7680
|
+
cms: FAKE_CMS,
|
|
7681
|
+
expectedVersion: prepared.expectedVersion
|
|
7682
|
+
});
|
|
7683
|
+
const layer = await doc.downloadLayer();
|
|
7684
|
+
expect(layer.byteLength > 0).toBe(true);
|
|
7685
|
+
expect(await sha256Hex(await doc.download())).toBe(result.version.sha256);
|
|
7686
|
+
} finally {
|
|
7687
|
+
await doc.close();
|
|
7688
|
+
}
|
|
7689
|
+
});
|
|
7690
|
+
}
|
|
7691
|
+
async function reopen(engine, opts, id, bytes) {
|
|
7692
|
+
if (opts.openKind === "layerBytes") return engine.open({
|
|
7693
|
+
kind: "layerBytes",
|
|
7694
|
+
id,
|
|
7695
|
+
baseBytes: bytes
|
|
7696
|
+
});
|
|
7697
|
+
return engine.open({
|
|
7698
|
+
kind: "bytes",
|
|
7699
|
+
id,
|
|
7700
|
+
bytes
|
|
7701
|
+
});
|
|
7702
|
+
}
|
|
7703
|
+
async function open(engine, opts, fixture) {
|
|
7704
|
+
if (opts.openKind === "bytes") return engine.open({
|
|
7705
|
+
kind: "bytes",
|
|
7706
|
+
id: fixture.id,
|
|
7707
|
+
bytes: await fixture.bytes()
|
|
7708
|
+
});
|
|
7709
|
+
if (opts.openKind === "layerBytes") return engine.open({
|
|
7710
|
+
kind: "layerBytes",
|
|
7711
|
+
id: `${fixture.id}-layer`,
|
|
7712
|
+
baseBytes: await fixture.bytes()
|
|
7713
|
+
});
|
|
7714
|
+
return engine.open({
|
|
7715
|
+
kind: "id",
|
|
7716
|
+
id: fixture.cloudId ?? fixture.id
|
|
7717
|
+
});
|
|
7718
|
+
}
|
|
7719
|
+
function concat(a, b) {
|
|
7720
|
+
const out = new Uint8Array(a.byteLength + b.byteLength);
|
|
7721
|
+
out.set(a, 0);
|
|
7722
|
+
out.set(b, a.byteLength);
|
|
7723
|
+
return out;
|
|
7724
|
+
}
|
|
7725
|
+
function bytesEqual(a, b) {
|
|
7726
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
7727
|
+
for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
|
|
7728
|
+
return true;
|
|
7729
|
+
}
|
|
7730
|
+
function toHex(bytes) {
|
|
7731
|
+
let out = "";
|
|
7732
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
7733
|
+
return out;
|
|
7734
|
+
}
|
|
7735
|
+
async function sha256Hex(bytes) {
|
|
7736
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
7737
|
+
copy.set(bytes);
|
|
7738
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", copy);
|
|
7739
|
+
return toHex(new Uint8Array(digest));
|
|
7740
|
+
}
|
|
7741
|
+
//#endregion
|
|
7742
|
+
export { TEXT_DIVERGENCE_CASES, diffAnnotationListSnapshot, diffAnnotationListSnapshotAll, runActionsConformance, runAnnotationAppearanceConformance, runAnnotationAppearanceExportConformance, runAnnotationFlattenConformance, runAnnotationMutationConformance, runAnnotationReadConformance, runAttachmentConformance, runDocumentEventsConformance, runFormConformance, runMetadataConformance, runNamedPagesConformance, runPageDeleteConformance, runPageExtractConformance, runPageFlattenConformance, runPageGeometryOrientationConformance, runPageInsertBlankConformance, runPageInsertConformance, runPageReorderConformance, runPageRotateConformance, runPageTextConformance, runPieceInfoConformance, runRedactionApplyConformance, runSearchConformance, runSignatureConformance, runTextDivergenceConformance };
|
|
6678
7743
|
|
|
6679
7744
|
//# sourceMappingURL=conformance.js.map
|