diff --git a/src/core/pdf_manager.js b/src/core/pdf_manager.js index c8ffaede7..fecb150c1 100644 --- a/src/core/pdf_manager.js +++ b/src/core/pdf_manager.js @@ -107,14 +107,6 @@ 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; }, terminate: function BasePdfManager_terminate() { 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() {