Skip to content

Commit 77b9657

Browse files
committed
XFA - Overwrite AcroForm dictionary when saving if no datasets in XFA (bug 1720179)
- aims to fix https://bugzilla.mozilla.org/show_bug.cgi?id=1720179 - in some pdfs the XFA array in AcroForm dictionary doesn't contain an entry for 'datasets' (which contains saved data), so basically this patch allows to overwrite the AcroForm dictionary with an updated XFA array when doing an incremental update.
1 parent 804abb3 commit 77b9657

File tree

4 files changed

+140
-4
lines changed

4 files changed

+140
-4
lines changed

src/core/catalog.js

+5
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ class Catalog {
130130
return shadow(this, "acroForm", acroForm);
131131
}
132132

133+
get acroFormRef() {
134+
const value = this._catDict.getRaw("AcroForm");
135+
return shadow(this, "acroFormRef", isRef(value) ? value : null);
136+
}
137+
133138
get metadata() {
134139
const streamRef = this._catDict.getRaw("Metadata");
135140
if (!isRef(streamRef)) {

src/core/worker.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ class WorkerMessageHandler {
573573
const promises = [
574574
pdfManager.onLoadedStream(),
575575
pdfManager.ensureCatalog("acroForm"),
576+
pdfManager.ensureCatalog("acroFormRef"),
576577
pdfManager.ensureDoc("xref"),
577578
pdfManager.ensureDoc("startXRef"),
578579
];
@@ -597,6 +598,7 @@ class WorkerMessageHandler {
597598
return Promise.all(promises).then(function ([
598599
stream,
599600
acroForm,
601+
acroFormRef,
600602
xref,
601603
startXRef,
602604
...refs
@@ -621,15 +623,22 @@ class WorkerMessageHandler {
621623
}
622624
}
623625

624-
const xfa = (acroForm instanceof Dict && acroForm.get("XFA")) || [];
626+
const xfa = (acroForm instanceof Dict && acroForm.get("XFA")) || null;
625627
let xfaDatasets = null;
628+
let hasDatasets = false;
626629
if (Array.isArray(xfa)) {
627630
for (let i = 0, ii = xfa.length; i < ii; i += 2) {
628631
if (xfa[i] === "datasets") {
629632
xfaDatasets = xfa[i + 1];
633+
acroFormRef = null;
634+
hasDatasets = true;
630635
}
631636
}
637+
if (xfaDatasets === null) {
638+
xfaDatasets = xref.getNewRef();
639+
}
632640
} else {
641+
acroFormRef = null;
633642
// TODO: Support XFA streams.
634643
warn("Unsupported XFA type.");
635644
}
@@ -666,6 +675,9 @@ class WorkerMessageHandler {
666675
newRefs,
667676
xref,
668677
datasetsRef: xfaDatasets,
678+
hasDatasets,
679+
acroFormRef,
680+
acroForm,
669681
xfaData,
670682
});
671683
});

src/core/writer.js

+59-3
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,54 @@ function writeXFADataForAcroform(str, newRefs) {
146146
return buffer.join("");
147147
}
148148

149-
function updateXFA(xfaData, datasetsRef, newRefs, xref) {
150-
if (datasetsRef === null || xref === null) {
149+
function updateXFA({
150+
xfaData,
151+
datasetsRef,
152+
hasDatasets,
153+
acroFormRef,
154+
acroForm,
155+
newRefs,
156+
xref,
157+
xrefInfo,
158+
}) {
159+
if (xref === null) {
151160
return;
152161
}
162+
163+
if (!hasDatasets) {
164+
if (!acroFormRef) {
165+
warn("XFA - Cannot save it");
166+
return;
167+
}
168+
169+
// We've a XFA array which doesn't contain a datasets entry.
170+
// So we'll update the AcroForm dictionary to have an XFA containing
171+
// the datasets.
172+
const oldXfa = acroForm.get("XFA");
173+
const newXfa = oldXfa.slice();
174+
newXfa.splice(2, 0, "datasets");
175+
newXfa.splice(3, 0, datasetsRef);
176+
177+
acroForm.set("XFA", newXfa);
178+
179+
const encrypt = xref.encrypt;
180+
let transform = null;
181+
if (encrypt) {
182+
transform = encrypt.createCipherTransform(
183+
acroFormRef.num,
184+
acroFormRef.gen
185+
);
186+
}
187+
188+
const buffer = [`${acroFormRef.num} ${acroFormRef.gen} obj\n`];
189+
writeDict(acroForm, buffer, transform);
190+
buffer.push("\n");
191+
192+
acroForm.set("XFA", oldXfa);
193+
194+
newRefs.push({ ref: acroFormRef, data: buffer.join("") });
195+
}
196+
153197
if (xfaData === null) {
154198
const datasets = xref.fetchIfRef(datasetsRef);
155199
xfaData = writeXFADataForAcroform(datasets.getString(), newRefs);
@@ -178,9 +222,21 @@ function incrementalUpdate({
178222
newRefs,
179223
xref = null,
180224
datasetsRef = null,
225+
hasDatasets = false,
226+
acroFormRef = null,
227+
acroForm = null,
181228
xfaData = null,
182229
}) {
183-
updateXFA(xfaData, datasetsRef, newRefs, xref);
230+
updateXFA({
231+
xfaData,
232+
datasetsRef,
233+
hasDatasets,
234+
acroFormRef,
235+
acroForm,
236+
newRefs,
237+
xref,
238+
xrefInfo,
239+
});
184240

185241
const newXref = new Dict(null);
186242
const refForXrefTable = xrefInfo.newRef;

test/unit/writer_spec.js

+63
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,67 @@ describe("Writer", function () {
142142
expect(buffer.join("")).toEqual(expected);
143143
});
144144
});
145+
146+
describe("XFA", function () {
147+
it("should update AcroForm when no datasets in XFA array", function () {
148+
const originalData = new Uint8Array();
149+
const newRefs = [];
150+
151+
const acroForm = new Dict(null);
152+
acroForm.set("XFA", [
153+
"preamble",
154+
Ref.get(123, 0),
155+
"postamble",
156+
Ref.get(456, 0),
157+
]);
158+
const acroFormRef = Ref.get(789, 0);
159+
const datasetsRef = Ref.get(101112, 0);
160+
const xfaData = "<hello>world</hello>";
161+
162+
const xrefInfo = {
163+
newRef: Ref.get(131415, 0),
164+
startXRef: 314,
165+
fileIds: null,
166+
rootRef: null,
167+
infoRef: null,
168+
encryptRef: null,
169+
filename: "foo.pdf",
170+
info: {},
171+
};
172+
173+
let data = incrementalUpdate({
174+
originalData,
175+
xrefInfo,
176+
newRefs,
177+
datasetsRef,
178+
hasDatasets: false,
179+
acroFormRef,
180+
acroForm,
181+
xfaData,
182+
xref: {},
183+
});
184+
data = bytesToString(data);
185+
186+
const expected =
187+
"\n" +
188+
"789 0 obj\n" +
189+
"<< /XFA [(preamble) 123 0 R (datasets) 101112 0 R (postamble) 456 0 R]>>\n" +
190+
"101112 0 obj\n" +
191+
"<< /Type /EmbeddedFile /Length 20>>\n" +
192+
"stream\n" +
193+
"<hello>world</hello>\n" +
194+
"endstream\n" +
195+
"endobj\n" +
196+
"131415 0 obj\n" +
197+
"<< /Size 131416 /Prev 314 /Type /XRef /Index [0 1 789 1 101112 1 131415 1] /W [1 1 2] /Length 16>> stream\n" +
198+
"\u0000\u0001ÿÿ\u0001\u0001\u0000\u0000\u0001T\u0000\u0000\u0001²\u0000\u0000\n" +
199+
"endstream\n" +
200+
"endobj\n" +
201+
"startxref\n" +
202+
"178\n" +
203+
"%%EOF\n";
204+
205+
expect(data).toEqual(expected);
206+
});
207+
});
145208
});

0 commit comments

Comments
 (0)