diff --git a/l10n/en-US/chrome.properties b/l10n/en-US/chrome.properties index e42ac790c..a8284958a 100644 --- a/l10n/en-US/chrome.properties +++ b/l10n/en-US/chrome.properties @@ -15,5 +15,6 @@ # Chrome notification bar messages and buttons unsupported_feature=This PDF document might not be displayed correctly. unsupported_feature_forms=This PDF document contains forms. The filling of form fields is not supported. +unsupported_feature_signatures=This PDF document contains digital signatures. Validation of signatures is not supported. open_with_different_viewer=Open With Different Viewer open_with_different_viewer.accessKey=o diff --git a/src/core/annotation.js b/src/core/annotation.js index 1896e8989..f3c670ea0 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -126,6 +126,8 @@ class AnnotationFactory { return new ButtonWidgetAnnotation(parameters); case "Ch": return new ChoiceWidgetAnnotation(parameters); + case "Sig": + return new SignatureWidgetAnnotation(parameters); } warn( `Unimplemented widget field type "${fieldType}", ` + @@ -1151,15 +1153,6 @@ class WidgetAnnotation extends Annotation { data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY); data.hidden = this._hasFlag(data.annotationFlags, AnnotationFlag.HIDDEN); - - // Hide signatures because we cannot validate them, and unset the fieldValue - // since it's (most likely) a `Dict` which is non-serializable and will thus - // cause errors when sending annotations to the main-thread (issue 10347). - if (data.fieldType === "Sig") { - data.fieldValue = null; - this.setFlags(AnnotationFlag.HIDDEN); - data.hidden = true; - } } /** @@ -1201,7 +1194,7 @@ class WidgetAnnotation extends Annotation { getOperatorList(evaluator, task, renderForms, annotationStorage) { // Do not render form elements on the canvas when interactive forms are // enabled. The display layer is responsible for rendering them instead. - if (renderForms) { + if (renderForms && !(this instanceof SignatureWidgetAnnotation)) { return Promise.resolve(new OperatorList()); } @@ -1600,13 +1593,6 @@ class WidgetAnnotation extends Annotation { } getFieldObject() { - if (this.data.fieldType === "Sig") { - return { - id: this.data.id, - value: null, - type: "signature", - }; - } return null; } } @@ -2203,6 +2189,25 @@ class ChoiceWidgetAnnotation extends WidgetAnnotation { } } +class SignatureWidgetAnnotation extends WidgetAnnotation { + constructor(params) { + super(params); + + // Unset the fieldValue since it's (most likely) a `Dict` which is + // non-serializable and will thus cause errors when sending annotations + // to the main-thread (issue 10347). + this.data.fieldValue = null; + } + + getFieldObject() { + return { + id: this.data.id, + value: null, + type: "signature", + }; + } +} + class TextAnnotation extends MarkupAnnotation { constructor(parameters) { const DEFAULT_ICON_SIZE = 22; // px diff --git a/src/core/document.js b/src/core/document.js index ff5c76cb8..ea09f5f60 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -828,7 +828,12 @@ class PDFDocument { } get formInfo() { - const formInfo = { hasFields: false, hasAcroForm: false, hasXfa: false }; + const formInfo = { + hasFields: false, + hasAcroForm: false, + hasXfa: false, + hasSignatures: false, + }; const acroForm = this.catalog.acroForm; if (!acroForm) { return shadow(this, "formInfo", formInfo); @@ -854,9 +859,11 @@ class PDFDocument { // the first bit of the `SigFlags` integer (see Table 219 in the // specification). const sigFlags = acroForm.get("SigFlags"); + const hasSignatures = !!(sigFlags & 0x1); const hasOnlyDocumentSignatures = - !!(sigFlags & 0x1) && this._hasOnlyDocumentSignatures(fields); + hasSignatures && this._hasOnlyDocumentSignatures(fields); formInfo.hasAcroForm = hasFields && !hasOnlyDocumentSignatures; + formInfo.hasSignatures = hasSignatures; } catch (ex) { if (ex instanceof MissingDataException) { throw ex; @@ -894,6 +901,7 @@ class PDFDocument { IsAcroFormPresent: this.formInfo.hasAcroForm, IsXFAPresent: this.formInfo.hasXfa, IsCollectionPresent: !!this.catalog.collection, + IsSignaturesPresent: this.formInfo.hasSignatures, }; let infoDict; diff --git a/src/shared/util.js b/src/shared/util.js index cfa37d033..0cbeda35d 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -315,6 +315,7 @@ const UNSUPPORTED_FEATURES = { unknown: "unknown", forms: "forms", javaScript: "javaScript", + signatures: "signatures", smask: "smask", shadingPattern: "shadingPattern", /** @deprecated unused */ diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 15f105b38..6350e3907 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -85,6 +85,7 @@ !issue9458.pdf !issue9655_reduced.pdf !issue9915_reduced.pdf +!bug854315.pdf !issue9940.pdf !issue10388_reduced.pdf !issue10438_reduced.pdf diff --git a/test/pdfs/bug854315.pdf b/test/pdfs/bug854315.pdf new file mode 100644 index 000000000..d175e8970 Binary files /dev/null and b/test/pdfs/bug854315.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 319959420..8abfcf024 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -389,6 +389,12 @@ "rounds": 1, "type": "load" }, + { "id": "bug854315", + "file": "pdfs/bug854315.pdf", + "md5": "675754c07f71c068d2997905a5456f05", + "rounds": 1, + "type": "eq" + }, { "id": "bug868745", "file": "pdfs/bug868745.pdf", "md5": "86111ea5097dd7daffcdea891ad1b348", diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index d44050f6b..7267c078a 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -1212,6 +1212,7 @@ describe("api", function () { expect(info.IsAcroFormPresent).toEqual(false); expect(info.IsXFAPresent).toEqual(false); expect(info.IsCollectionPresent).toEqual(false); + expect(info.IsSignaturesPresent).toEqual(false); expect(metadata instanceof Metadata).toEqual(true); expect(metadata.get("dc:title")).toEqual("Basic API Test"); @@ -1254,6 +1255,7 @@ describe("api", function () { expect(info.IsAcroFormPresent).toEqual(false); expect(info.IsXFAPresent).toEqual(false); expect(info.IsCollectionPresent).toEqual(false); + expect(info.IsSignaturesPresent).toEqual(false); expect(metadata).toEqual(null); expect(contentDispositionFilename).toEqual(null); @@ -1282,6 +1284,7 @@ describe("api", function () { expect(info.IsAcroFormPresent).toEqual(false); expect(info.IsXFAPresent).toEqual(false); expect(info.IsCollectionPresent).toEqual(false); + expect(info.IsSignaturesPresent).toEqual(false); expect(metadata).toEqual(null); expect(contentDispositionFilename).toEqual(null); diff --git a/test/unit/document_spec.js b/test/unit/document_spec.js index e4109284d..91001e0e5 100644 --- a/test/unit/document_spec.js +++ b/test/unit/document_spec.js @@ -77,6 +77,7 @@ describe("document", function () { const pdfDocument = getDocument(null); expect(pdfDocument.formInfo).toEqual({ hasAcroForm: false, + hasSignatures: false, hasXfa: false, hasFields: false, }); @@ -90,6 +91,7 @@ describe("document", function () { let pdfDocument = getDocument(acroForm); expect(pdfDocument.formInfo).toEqual({ hasAcroForm: false, + hasSignatures: false, hasXfa: false, hasFields: false, }); @@ -98,6 +100,7 @@ describe("document", function () { pdfDocument = getDocument(acroForm); expect(pdfDocument.formInfo).toEqual({ hasAcroForm: false, + hasSignatures: false, hasXfa: true, hasFields: false, }); @@ -106,6 +109,7 @@ describe("document", function () { pdfDocument = getDocument(acroForm); expect(pdfDocument.formInfo).toEqual({ hasAcroForm: false, + hasSignatures: false, hasXfa: false, hasFields: false, }); @@ -114,6 +118,7 @@ describe("document", function () { pdfDocument = getDocument(acroForm); expect(pdfDocument.formInfo).toEqual({ hasAcroForm: false, + hasSignatures: false, hasXfa: true, hasFields: false, }); @@ -127,6 +132,7 @@ describe("document", function () { let pdfDocument = getDocument(acroForm); expect(pdfDocument.formInfo).toEqual({ hasAcroForm: false, + hasSignatures: false, hasXfa: false, hasFields: false, }); @@ -135,6 +141,7 @@ describe("document", function () { pdfDocument = getDocument(acroForm); expect(pdfDocument.formInfo).toEqual({ hasAcroForm: true, + hasSignatures: false, hasXfa: false, hasFields: true, }); @@ -146,6 +153,7 @@ describe("document", function () { pdfDocument = getDocument(acroForm); expect(pdfDocument.formInfo).toEqual({ hasAcroForm: true, + hasSignatures: false, hasXfa: false, hasFields: true, }); @@ -169,6 +177,7 @@ describe("document", function () { pdfDocument = getDocument(acroForm, xref); expect(pdfDocument.formInfo).toEqual({ hasAcroForm: false, + hasSignatures: true, hasXfa: false, hasFields: true, }); diff --git a/web/app.js b/web/app.js index cec82bddd..e4ba9da19 100644 --- a/web/app.js +++ b/web/app.js @@ -1583,6 +1583,11 @@ const PDFViewerApplication = { this._delayedFallback(UNSUPPORTED_FEATURES.forms); } + if (info.IsSignaturesPresent) { + console.warn("Warning: Digital signatures validation is not supported"); + this.fallback(UNSUPPORTED_FEATURES.signatures); + } + // Telemetry labels must be C++ variable friendly. let versionId = "other"; if (KNOWN_VERSIONS.includes(info.PDFFormatVersion)) {