diff --git a/src/core/document.js b/src/core/document.js index ed0a8eff0..df8268fdf 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -370,22 +370,20 @@ var PDFDocument = (function PDFDocumentClosure() { var EMPTY_FINGERPRINT = '\x00\x00\x00\x00\x00\x00\x00' + '\x00\x00\x00\x00\x00\x00\x00\x00\x00'; - function PDFDocument(pdfManager, arg, password) { + function PDFDocument(pdfManager, arg) { + var stream; if (isStream(arg)) { - init.call(this, pdfManager, arg, password); + stream = arg; } else if (isArrayBuffer(arg)) { - init.call(this, pdfManager, new Stream(arg), password); + stream = new Stream(arg); } else { error('PDFDocument: Unknown argument type'); } - } - - function init(pdfManager, stream, password) { assert(stream.length > 0, 'stream must have data'); + this.pdfManager = pdfManager; this.stream = stream; - var xref = new XRef(this.stream, password, pdfManager); - this.xref = xref; + this.xref = new XRef(stream, pdfManager); } function find(stream, needle, limit, backwards) { diff --git a/src/core/obj.js b/src/core/obj.js index 84daa2e1e..3cea3f806 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -758,13 +758,13 @@ var Catalog = (function CatalogClosure() { })(); var XRef = (function XRefClosure() { - function XRef(stream, password) { + function XRef(stream, pdfManager) { this.stream = stream; + this.pdfManager = pdfManager; this.entries = []; this.xrefstms = Object.create(null); // prepare the XRef cache this.cache = []; - this.password = password; this.stats = { streamTypes: [], fontTypes: [] @@ -789,7 +789,7 @@ var XRef = (function XRefClosure() { trailerDict.assignXref(this); this.trailer = trailerDict; var encrypt = trailerDict.get('Encrypt'); - if (encrypt) { + if (isDict(encrypt)) { var ids = trailerDict.get('ID'); var fileId = (ids && ids.length) ? ids[0] : ''; // The 'Encrypt' dictionary itself should not be encrypted, and by @@ -798,7 +798,7 @@ var XRef = (function XRefClosure() { // objects (fixes issue7665.pdf). encrypt.suppressEncryption = true; this.encrypt = new CipherTransformFactory(encrypt, fileId, - this.password); + this.pdfManager.password); } // get the root dictionary (catalog) object diff --git a/src/core/pdf_manager.js b/src/core/pdf_manager.js index c8ffaede7..96777e177 100644 --- a/src/core/pdf_manager.js +++ b/src/core/pdf_manager.js @@ -52,6 +52,10 @@ var BasePdfManager = (function BasePdfManagerClosure() { return this._docId; }, + get password() { + return this._password; + }, + get docBaseUrl() { var docBaseUrl = null; if (this._docBaseUrl) { @@ -106,15 +110,7 @@ var BasePdfManager = (function BasePdfManagerClosure() { }, updatePassword: function BasePdfManager_updatePassword(password) { - this.pdfDocument.xref.password = this.password = password; - if (this._passwordChangedCapability) { - this._passwordChangedCapability.resolve(); - } - }, - - passwordChanged: function BasePdfManager_passwordChanged() { - this._passwordChangedCapability = createPromiseCapability(); - return this._passwordChangedCapability.promise; + this._password = password; }, terminate: function BasePdfManager_terminate() { @@ -129,10 +125,11 @@ var LocalPdfManager = (function LocalPdfManagerClosure() { function LocalPdfManager(docId, data, password, evaluatorOptions, docBaseUrl) { this._docId = docId; + this._password = password; this._docBaseUrl = docBaseUrl; this.evaluatorOptions = evaluatorOptions; var stream = new Stream(data); - this.pdfDocument = new PDFDocument(this, stream, password); + this.pdfDocument = new PDFDocument(this, stream); this._loadedStreamCapability = createPromiseCapability(); this._loadedStreamCapability.resolve(stream); } @@ -179,6 +176,7 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { function NetworkPdfManager(docId, pdfNetworkStream, args, evaluatorOptions, docBaseUrl) { this._docId = docId; + this._password = args.password; this._docBaseUrl = docBaseUrl; this.msgHandler = args.msgHandler; this.evaluatorOptions = evaluatorOptions; @@ -191,8 +189,7 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { rangeChunkSize: args.rangeChunkSize }; this.streamManager = new ChunkedStreamManager(pdfNetworkStream, params); - this.pdfDocument = new PDFDocument(this, this.streamManager.getStream(), - args.password); + this.pdfDocument = new PDFDocument(this, this.streamManager.getStream()); } Util.inherit(NetworkPdfManager, BasePdfManager, { diff --git a/src/core/worker.js b/src/core/worker.js index 1aa13ecf4..16cd628cd 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -663,19 +663,25 @@ var WorkerMessageHandler = { return pdfManagerCapability.promise; } - var setupDoc = function(data) { - var onSuccess = function(doc) { + function setupDoc(data) { + function onSuccess(doc) { ensureNotTerminated(); handler.send('GetDoc', { pdfInfo: doc }); - }; + } - var onFailure = function(e) { + function onFailure(e) { if (e instanceof PasswordException) { - if (e.code === PasswordResponses.NEED_PASSWORD) { - handler.send('NeedPassword', e); - } else if (e.code === PasswordResponses.INCORRECT_PASSWORD) { - handler.send('IncorrectPassword', e); - } + var task = new WorkerTask('PasswordException: response ' + e.code); + startWorkerTask(task); + + handler.sendWithPromise('PasswordRequest', e).then(function (data) { + finishWorkerTask(task); + pdfManager.updatePassword(data.password); + pdfManagerReady(); + }).catch(function (ex) { + finishWorkerTask(task); + handler.send('PasswordException', ex); + }.bind(null, e)); } else if (e instanceof InvalidPDFException) { handler.send('InvalidPDF', e); } else if (e instanceof MissingPDFException) { @@ -686,7 +692,27 @@ var WorkerMessageHandler = { handler.send('UnknownError', new UnknownErrorException(e.message, e.toString())); } - }; + } + + function pdfManagerReady() { + ensureNotTerminated(); + + loadDocument(false).then(onSuccess, function loadFailure(ex) { + ensureNotTerminated(); + + // Try again with recoveryMode == true + if (!(ex instanceof XRefParseException)) { + onFailure(ex); + return; + } + pdfManager.requestLoadedStream(); + pdfManager.onLoadedStream().then(function() { + ensureNotTerminated(); + + loadDocument(true).then(onSuccess, onFailure); + }); + }, onFailure); + } ensureNotTerminated(); @@ -714,33 +740,8 @@ var WorkerMessageHandler = { pdfManager.onLoadedStream().then(function(stream) { handler.send('DataLoaded', { length: stream.bytes.byteLength }); }); - }).then(function pdfManagerReady() { - ensureNotTerminated(); - - loadDocument(false).then(onSuccess, function loadFailure(ex) { - ensureNotTerminated(); - - // Try again with recoveryMode == true - if (!(ex instanceof XRefParseException)) { - if (ex instanceof PasswordException) { - // after password exception prepare to receive a new password - // to repeat loading - pdfManager.passwordChanged().then(pdfManagerReady); - } - - onFailure(ex); - return; - } - - pdfManager.requestLoadedStream(); - pdfManager.onLoadedStream().then(function() { - ensureNotTerminated(); - - loadDocument(true).then(onSuccess, onFailure); - }); - }, onFailure); - }, onFailure); - }; + }).then(pdfManagerReady, onFailure); + } handler.on('GetPage', function wphSetupGetPage(data) { return pdfManager.getPage(data.pageIndex).then(function(page) { @@ -824,10 +825,6 @@ var WorkerMessageHandler = { } ); - handler.on('UpdatePassword', function wphSetupUpdatePassword(data) { - pdfManager.updatePassword(data); - }); - handler.on('GetAnnotations', function wphSetupGetAnnotations(data) { return pdfManager.getPage(data.pageIndex).then(function(page) { return pdfManager.ensure(page, 'getAnnotationsData', [data.intent]); diff --git a/src/display/api.js b/src/display/api.js index c60eae177..3d7759593 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -1419,6 +1419,7 @@ var WorkerTransport = (function WorkerTransportClosure() { this.destroyed = false; this.destroyCapability = null; + this._passwordCapability = null; this.pageCache = []; this.pagePromises = []; @@ -1435,6 +1436,11 @@ var WorkerTransport = (function WorkerTransportClosure() { this.destroyed = true; this.destroyCapability = createPromiseCapability(); + if (this._passwordCapability) { + this._passwordCapability.reject( + new Error('Worker was destroyed during onPassword callback')); + } + var waitOn = []; // We need to wait for all renderings to be completed, e.g. // timeout/rAF can take a long time. @@ -1464,13 +1470,9 @@ var WorkerTransport = (function WorkerTransportClosure() { return this.destroyCapability.promise; }, - setupMessageHandler: - function WorkerTransport_setupMessageHandler() { + setupMessageHandler: function WorkerTransport_setupMessageHandler() { var messageHandler = this.messageHandler; - - function updatePassword(password) { - messageHandler.send('UpdatePassword', password); - } + var loadingTask = this.loadingTask; var pdfDataRangeTransport = this.pdfDataRangeTransport; if (pdfDataRangeTransport) { @@ -1508,24 +1510,27 @@ var WorkerTransport = (function WorkerTransportClosure() { loadingTask._capability.resolve(pdfDocument); }, this); - messageHandler.on('NeedPassword', - function transportNeedPassword(exception) { - var loadingTask = this.loadingTask; + messageHandler.on('PasswordRequest', + function transportPasswordRequest(exception) { + this._passwordCapability = createPromiseCapability(); + if (loadingTask.onPassword) { - return loadingTask.onPassword(updatePassword, - PasswordResponses.NEED_PASSWORD); + var updatePassword = function (password) { + this._passwordCapability.resolve({ + password: password, + }); + }.bind(this); + + loadingTask.onPassword(updatePassword, exception.code); + } else { + this._passwordCapability.reject( + new PasswordException(exception.message, exception.code)); } - loadingTask._capability.reject( - new PasswordException(exception.message, exception.code)); + return this._passwordCapability.promise; }, this); - messageHandler.on('IncorrectPassword', - function transportIncorrectPassword(exception) { - var loadingTask = this.loadingTask; - if (loadingTask.onPassword) { - return loadingTask.onPassword(updatePassword, - PasswordResponses.INCORRECT_PASSWORD); - } + messageHandler.on('PasswordException', + function transportPasswordException(exception) { loadingTask._capability.reject( new PasswordException(exception.message, exception.code)); }, this); diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 2320c71fa..f4ec8a63c 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -191,7 +191,7 @@ describe('api', function() { }); var result1 = passwordNeededLoadingTask.promise.then(function () { done.fail('shall fail with no password'); - return passwordNeededLoadingTask.destroy(); + return Promise.reject(new Error('loadingTask should be rejected')); }, function (data) { expect(data instanceof PasswordException).toEqual(true); expect(data.code).toEqual(PasswordResponses.NEED_PASSWORD); @@ -203,7 +203,7 @@ describe('api', function() { }); var result2 = passwordIncorrectLoadingTask.promise.then(function () { done.fail('shall fail with wrong password'); - return passwordNeededLoadingTask.destroy(); + return Promise.reject(new Error('loadingTask should be rejected')); }, function (data) { expect(data instanceof PasswordException).toEqual(true); expect(data.code).toEqual(PasswordResponses.INCORRECT_PASSWORD); @@ -224,6 +224,53 @@ describe('api', function() { done.fail(reason); }); }); + + it('creates pdf doc from password protected PDF file and aborts/throws ' + + 'in the onPassword callback (issue 7806)', function (done) { + var url = new URL('../pdfs/issue3371.pdf', window.location).href; + var passwordNeededLoadingTask = PDFJS.getDocument(url); + var passwordIncorrectLoadingTask = PDFJS.getDocument({ + url: url, password: 'qwerty', + }); + + passwordNeededLoadingTask.onPassword = function (callback, reason) { + if (reason === PasswordResponses.NEED_PASSWORD) { + passwordNeededLoadingTask.destroy(); + return; + } + // Shouldn't get here. + expect(false).toEqual(true); + }; + var result1 = passwordNeededLoadingTask.promise.then(function () { + done.fail('shall fail since the loadingTask should be destroyed'); + return Promise.reject(new Error('loadingTask should be rejected')); + }, function (reason) { + expect(reason instanceof PasswordException).toEqual(true); + expect(reason.code).toEqual(PasswordResponses.NEED_PASSWORD); + }); + + passwordIncorrectLoadingTask.onPassword = function (callback, reason) { + if (reason === PasswordResponses.INCORRECT_PASSWORD) { + throw new Error('Incorrect password'); + } + // Shouldn't get here. + expect(false).toEqual(true); + }; + var result2 = passwordIncorrectLoadingTask.promise.then(function () { + done.fail('shall fail since the onPassword callback should throw'); + return Promise.reject(new Error('loadingTask should be rejected')); + }, function (reason) { + expect(reason instanceof PasswordException).toEqual(true); + expect(reason.code).toEqual(PasswordResponses.INCORRECT_PASSWORD); + return passwordIncorrectLoadingTask.destroy(); + }); + + Promise.all([result1, result2]).then(function () { + done(); + }).catch(function (reason) { + done.fail(reason); + }); + }); }); }); describe('PDFWorker', function() {