Skip to content

Don't store complex data in PDFDocument.formInfo, and replace the fields object with a hasFields boolean instead #12483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 20 additions & 13 deletions src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -687,8 +687,15 @@ class PDFDocument {
*/
_hasOnlyDocumentSignatures(fields, recursionDepth = 0) {
const RECURSION_LIMIT = 10;

if (!Array.isArray(fields)) {
return false;
}
return fields.every(field => {
field = this.xref.fetchIfRef(field);
if (!(field instanceof Dict)) {
return false;
}
if (field.has("Kids")) {
if (++recursionDepth > RECURSION_LIMIT) {
warn("_hasOnlyDocumentSignatures: maximum recursion depth reached");
Expand All @@ -708,20 +715,23 @@ class PDFDocument {
}

get formInfo() {
const formInfo = { hasAcroForm: false, hasXfa: false, fields: null };
const formInfo = { hasFields: false, hasAcroForm: false, hasXfa: false };
const acroForm = this.catalog.acroForm;
if (!acroForm) {
return shadow(this, "formInfo", formInfo);
}

try {
const fields = acroForm.get("Fields");
const hasFields = Array.isArray(fields) && fields.length > 0;
formInfo.hasFields = hasFields; // Used by the `fieldObjects` getter.

// The document contains XFA data if the `XFA` entry is a non-empty
// array or stream.
const xfa = acroForm.get("XFA");
const hasXfa =
formInfo.hasXfa =
(Array.isArray(xfa) && xfa.length > 0) ||
(isStream(xfa) && !xfa.isEmpty);
formInfo.hasXfa = hasXfa;

// The document contains AcroForm data if the `Fields` entry is a
// non-empty array and it doesn't consist of only document signatures.
Expand All @@ -730,20 +740,15 @@ class PDFDocument {
// store (invisible) document signatures. This can be detected using
// the first bit of the `SigFlags` integer (see Table 219 in the
// specification).
const fields = acroForm.get("Fields");
const hasFields = Array.isArray(fields) && fields.length > 0;
const sigFlags = acroForm.get("SigFlags");
const hasOnlyDocumentSignatures =
!!(sigFlags & 0x1) && this._hasOnlyDocumentSignatures(fields);
formInfo.hasAcroForm = hasFields && !hasOnlyDocumentSignatures;
if (hasFields) {
formInfo.fields = fields;
}
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
info("Cannot fetch form information.");
warn(`Cannot fetch form information: "${ex}".`);
}
return shadow(this, "formInfo", formInfo);
}
Expand Down Expand Up @@ -939,6 +944,9 @@ class PDFDocument {
: clearPrimitiveCaches();
}

/**
* @private
*/
_collectFieldObjects(name, fieldRef, promises) {
const field = this.xref.fetchIfRef(fieldRef);
if (field.has("T")) {
Expand Down Expand Up @@ -976,19 +984,18 @@ class PDFDocument {
}

get fieldObjects() {
const formInfo = this.formInfo;
if (!formInfo.fields) {
if (!this.formInfo.hasFields) {
return shadow(this, "fieldObjects", Promise.resolve(null));
}

const allFields = Object.create(null);
const fieldPromises = new Map();
for (const fieldRef of formInfo.fields) {
for (const fieldRef of this.catalog.acroForm.get("Fields")) {
this._collectFieldObjects("", fieldRef, fieldPromises);
}

const allPromises = [];
for (const [name, promises] of fieldPromises.entries()) {
for (const [name, promises] of fieldPromises) {
allPromises.push(
Promise.all(promises).then(fields => {
fields = fields.filter(field => field !== null);
Expand Down
5 changes: 3 additions & 2 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -878,8 +878,9 @@ class PDFDocumentProxy {
}

/**
* @returns {Promise<Array<Object>>} A promise that is resolved with an
* {Array<Object>} containing field data for the JS sandbox.
* @returns {Promise<Array<Object> | null>} A promise that is resolved with an
* {Array<Object>} containing /AcroForm field data for the JS sandbox,
* or `null` when no field data is present in the PDF file.
*/
getFieldObjects() {
return this._transport.getFieldObjects();
Expand Down
18 changes: 9 additions & 9 deletions test/unit/document_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe("document", function () {
expect(pdfDocument.formInfo).toEqual({
hasAcroForm: false,
hasXfa: false,
fields: null,
hasFields: false,
});
});

Expand All @@ -77,31 +77,31 @@ describe("document", function () {
expect(pdfDocument.formInfo).toEqual({
hasAcroForm: false,
hasXfa: false,
fields: null,
hasFields: false,
});

acroForm.set("XFA", ["foo", "bar"]);
pdfDocument = getDocument(acroForm);
expect(pdfDocument.formInfo).toEqual({
hasAcroForm: false,
hasXfa: true,
fields: null,
hasFields: false,
});

acroForm.set("XFA", new StringStream(""));
pdfDocument = getDocument(acroForm);
expect(pdfDocument.formInfo).toEqual({
hasAcroForm: false,
hasXfa: false,
fields: null,
hasFields: false,
});

acroForm.set("XFA", new StringStream("non-empty"));
pdfDocument = getDocument(acroForm);
expect(pdfDocument.formInfo).toEqual({
hasAcroForm: false,
hasXfa: true,
fields: null,
hasFields: false,
});
});

Expand All @@ -114,15 +114,15 @@ describe("document", function () {
expect(pdfDocument.formInfo).toEqual({
hasAcroForm: false,
hasXfa: false,
fields: null,
hasFields: false,
});

acroForm.set("Fields", ["foo", "bar"]);
pdfDocument = getDocument(acroForm);
expect(pdfDocument.formInfo).toEqual({
hasAcroForm: true,
hasXfa: false,
fields: ["foo", "bar"],
hasFields: true,
});

// If the first bit of the `SigFlags` entry is set and the `Fields` array
Expand All @@ -133,7 +133,7 @@ describe("document", function () {
expect(pdfDocument.formInfo).toEqual({
hasAcroForm: true,
hasXfa: false,
fields: ["foo", "bar"],
hasFields: true,
});

const annotationDict = new Dict();
Expand All @@ -156,7 +156,7 @@ describe("document", function () {
expect(pdfDocument.formInfo).toEqual({
hasAcroForm: false,
hasXfa: false,
fields: [kidsRef],
hasFields: true,
});
});
});
Expand Down