From c676ecb5a0f54677b9f3340c3ef2cf42225453bb Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Mon, 20 Jul 2015 18:25:02 +0200 Subject: [PATCH] Detect scripted auto-print requests Fixes #6106 To avoid future regressions, two new unit tests were added: 1. A new PDF based on the report from #6106, which contains an OpenAction of type JavaScript and a string "this.print({...}". 2. An existing PDF from https://bugzil.la/1001080 (from #4698). Although it does not matter, since we don't execute the JavaScript code, I have also changed "print(true)" to "print({})" since the print method takes an object (not a boolean). See "Printing PDF documents", page 62: http://adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/js_developer_guide.pdf --- src/core/obj.js | 48 ++++++++++++++------------- test/pdfs/.gitignore | 2 ++ test/pdfs/bug1001080.pdf | Bin 0 -> 12423 bytes test/pdfs/issue6106.pdf | 70 +++++++++++++++++++++++++++++++++++++++ test/unit/api_spec.js | 26 +++++++++++++++ web/viewer.js | 2 +- 6 files changed, 124 insertions(+), 24 deletions(-) create mode 100644 test/pdfs/bug1001080.pdf create mode 100644 test/pdfs/issue6106.pdf diff --git a/src/core/obj.js b/src/core/obj.js index f38effe8b..a8bd5d3df 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -534,6 +534,19 @@ var Catalog = (function CatalogClosure() { var obj = this.catDict.get('Names'); var javaScript = []; + function appendIfJavaScriptDict(jsDict) { + var type = jsDict.get('S'); + if (!isName(type) || type.name !== 'JavaScript') { + return; + } + var js = jsDict.get('JS'); + if (isStream(js)) { + js = bytesToString(js.getBytes()); + } else if (!isString(js)) { + return; + } + javaScript.push(stringToPDFString(js)); + } if (obj && obj.has('JavaScript')) { var nameTree = new NameTree(obj.getRaw('JavaScript'), xref); var names = nameTree.getAll(); @@ -544,36 +557,25 @@ var Catalog = (function CatalogClosure() { // We don't really use the JavaScript right now. This code is // defensive so we don't cause errors on document load. var jsDict = names[name]; - if (!isDict(jsDict)) { - continue; + if (isDict(jsDict)) { + appendIfJavaScriptDict(jsDict); } - var type = jsDict.get('S'); - if (!isName(type) || type.name !== 'JavaScript') { - continue; - } - var js = jsDict.get('JS'); - if (!isString(js) && !isStream(js)) { - continue; - } - if (isStream(js)) { - js = bytesToString(js.getBytes()); - } - javaScript.push(stringToPDFString(js)); } } // Append OpenAction actions to javaScript array var openactionDict = this.catDict.get('OpenAction'); - if (isDict(openactionDict)) { - var objType = openactionDict.get('Type'); + if (isDict(openactionDict, 'Action')) { var actionType = openactionDict.get('S'); - var action = openactionDict.get('N'); - var isPrintAction = (isName(objType) && objType.name === 'Action' && - isName(actionType) && actionType.name === 'Named' && - isName(action) && action.name === 'Print'); - - if (isPrintAction) { - javaScript.push('print(true);'); + if (isName(actionType) && actionType.name === 'Named') { + // The named Print action is not a part of the PDF 1.7 specification, + // but is supported by many PDF readers/writers (including Adobe's). + var action = openactionDict.get('N'); + if (isName(action) && action.name === 'Print') { + javaScript.push('print({});'); + } + } else { + appendIfJavaScriptDict(openactionDict); } } diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index a79dc29b0..a186388cc 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -146,4 +146,6 @@ !issue6068.pdf !issue6081.pdf !issue6069.pdf +!issue6106.pdf +!bug1001080.pdf !issue6108.pdf diff --git a/test/pdfs/bug1001080.pdf b/test/pdfs/bug1001080.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0f60ca9d6d5c10bd06916dcabb33509f519e16cd GIT binary patch literal 12423 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 +endobj +2 0 obj +<< +/Type /Pages +/Count 1 +/Kids [4 0 R] +>> +endobj +3 0 obj +<< +/Type /Action +/S /JavaScript +% This is a verbatim copy from a PDF generated by Google Drive (20 July 2015) +/JS (this.print\({bUI:true,bSilent:false,bShrinkToFit:true}\);) +>> +endobj +4 0 obj +<< +/Parent 2 0 R +/Contents 6 0 R +/Type /Page +/Resources 5 0 R +/MediaBox [0 0 400 200] +>> +endobj +5 0 obj +<< +/Font + << + /F1 + << + /Type /Font + /Subtype /Type1 + /BaseFont /Arial + >> + >> +>> +endobj +6 0 obj +<> +stream +BT/F1 20 Tf 70 88 Td(Should trigger a print action.) Tj ET +endstream +endobj +xref +0 7 +0000000000 65535 f +0000000151 00000 n +0000000218 00000 n +0000000275 00000 n +0000000467 00000 n +0000000571 00000 n +0000000685 00000 n +trailer +<< +/Root 1 0 R +/Size 7 +>> +startxref +791 +%%EOF diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 895039beb..474734270 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -147,6 +147,32 @@ describe('api', function() { expect(data).toEqual([]); }); }); + // Keep this in sync with the pattern in viewer.js. The pattern is used to + // detect whether or not to automatically start printing. + var viewerPrintRegExp = /\bprint\s*\(/; + it('gets javascript with printing instructions (Print action)', function() { + // PDF document with "Print" Named action in OpenAction + var pdfUrl = combineUrl(window.location.href, '../pdfs/bug1001080.pdf'); + var promise = PDFJS.getDocument(pdfUrl).then(function(doc) { + return doc.getJavaScript(); + }); + waitsForPromiseResolved(promise, function (data) { + expect(data).toEqual(['print({});']); + expect(data[0]).toMatch(viewerPrintRegExp); + }); + }); + it('gets javascript with printing instructions (JS action)', function() { + // PDF document with "JavaScript" action in OpenAction + var pdfUrl = combineUrl(window.location.href, '../pdfs/issue6106.pdf'); + var promise = PDFJS.getDocument(pdfUrl).then(function(doc) { + return doc.getJavaScript(); + }); + waitsForPromiseResolved(promise, function (data) { + expect(data).toEqual( + ['this.print({bUI:true,bSilent:false,bShrinkToFit:true});']); + expect(data[0]).toMatch(viewerPrintRegExp); + }); + }); it('gets outline', function() { var promise = doc.getOutline(); waitsForPromiseResolved(promise, function(outline) { diff --git a/web/viewer.js b/web/viewer.js index 758281f7f..4e4607edb 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -820,7 +820,7 @@ var PDFViewerApplication = { self.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript); } // Hack to support auto printing. - var regex = /\bprint\s*\(/g; + var regex = /\bprint\s*\(/; for (var i = 0, ii = javaScript.length; i < ii; i++) { var js = javaScript[i]; if (js && regex.test(js)) {