diff --git a/src/api.js b/src/api.js index fee48671a..91e771a5d 100644 --- a/src/api.js +++ b/src/api.js @@ -17,7 +17,7 @@ /* globals CanvasGraphics, combineUrl, createScratchCanvas, error, ErrorFont, Font, FontLoader, globalScope, info, isArrayBuffer, loadJpegStream, MessageHandler, PDFJS, PDFObjects, Promise, StatTimer, warn, - WorkerMessageHandler */ + WorkerMessageHandler, PasswordResponses */ 'use strict'; @@ -39,9 +39,16 @@ * to manually serve range requests for data in the PDF. See viewer.js for * an example of pdfDataRangeTransport's interface. * + * @param {function} passwordCallback is optional. It is used to request a + * password if wrong or no password was provided. The callback receives two + * parameters: function that needs to be called with new password and reason + * (see {PasswordResponses}). + * * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object. */ -PDFJS.getDocument = function getDocument(source, pdfDataRangeTransport) { +PDFJS.getDocument = function getDocument(source, + pdfDataRangeTransport, + passwordCallback) { var workerInitializedPromise, workerReadyPromise, transport; if (typeof source === 'string') { @@ -71,6 +78,7 @@ PDFJS.getDocument = function getDocument(source, pdfDataRangeTransport) { transport = new WorkerTransport(workerInitializedPromise, workerReadyPromise, pdfDataRangeTransport); workerInitializedPromise.then(function transportInitialized() { + transport.passwordCallback = passwordCallback; transport.fetchDocument(params); }); return workerReadyPromise; @@ -482,6 +490,8 @@ var WorkerTransport = (function WorkerTransportClosure() { this.pagePromises = []; this.embeddedFontsUsed = false; + this.passwordCallback = null; + // If worker support isn't disabled explicit and the browser has worker // support, create a new web worker and test if it/the browser fullfills // all requirements to run parts of pdf.js in a web worker. @@ -559,6 +569,10 @@ var WorkerTransport = (function WorkerTransportClosure() { function WorkerTransport_setupMessageHandler(messageHandler) { this.messageHandler = messageHandler; + function updatePassword(password) { + messageHandler.send('UpdatePassword', password); + } + var pdfDataRangeTransport = this.pdfDataRangeTransport; if (pdfDataRangeTransport) { pdfDataRangeTransport.addRangeListener(function(begin, chunk) { @@ -588,10 +602,18 @@ var WorkerTransport = (function WorkerTransportClosure() { }, this); messageHandler.on('NeedPassword', function transportPassword(data) { + if (this.passwordCallback) { + return this.passwordCallback(updatePassword, + PasswordResponses.NEED_PASSWORD); + } this.workerReadyPromise.reject(data.exception.message, data.exception); }, this); messageHandler.on('IncorrectPassword', function transportBadPass(data) { + if (this.passwordCallback) { + return this.passwordCallback(updatePassword, + PasswordResponses.INCORRECT_PASSWORD); + } this.workerReadyPromise.reject(data.exception.message, data.exception); }, this); diff --git a/src/crypto.js b/src/crypto.js index d9a8f98cb..837393352 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -15,7 +15,7 @@ * limitations under the License. */ /* globals bytesToString, DecryptStream, error, isInt, isName, Name, - PasswordException, stringToBytes */ + PasswordException, PasswordResponses, stringToBytes */ 'use strict'; @@ -575,7 +575,8 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata); if (!encryptionKey && !password) { - throw new PasswordException('No password given', 'needpassword'); + throw new PasswordException('No password given', + PasswordResponses.NEED_PASSWORD); } else if (!encryptionKey && password) { // Attempting use the password as an owner password var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword, @@ -586,7 +587,8 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { } if (!encryptionKey) - throw new PasswordException('Incorrect Password', 'incorrectpassword'); + throw new PasswordException('Incorrect Password', + PasswordResponses.INCORRECT_PASSWORD); this.encryptionKey = encryptionKey; diff --git a/src/pdf_manager.js b/src/pdf_manager.js index 2ca02bb8d..7ad995350 100644 --- a/src/pdf_manager.js +++ b/src/pdf_manager.js @@ -56,6 +56,13 @@ var BasePdfManager = (function BasePdfManagerClosure() { requestLoadedStream: function BasePdfManager_requestLoadedStream() { return new NotImplementedException(); + }, + + updatePassword: function BasePdfManager_updatePassword(password) { + this.pdfModel.xref.password = this.password = password; + if (this.passwordChangedPromise) { + this.passwordChangedPromise.resolve(); + } } }; diff --git a/src/util.js b/src/util.js index 8955a5001..eae731d50 100644 --- a/src/util.js +++ b/src/util.js @@ -139,6 +139,11 @@ function shadow(obj, prop, value) { return value; } +var PasswordResponses = PDFJS.PasswordResponses = { + NEED_PASSWORD: 1, + INCORRECT_PASSWORD: 2 +}; + var PasswordException = (function PasswordExceptionClosure() { function PasswordException(msg, code) { this.name = 'PasswordException'; diff --git a/src/worker.js b/src/worker.js index b0817e3f2..9bc43be43 100644 --- a/src/worker.js +++ b/src/worker.js @@ -18,7 +18,7 @@ MissingPDFException, PasswordException, PDFDocument, PDFJS, Promise, Stream, UnknownErrorException, warn, NetworkManager, LocalPdfManager, NetworkPdfManager, XRefParseException, NotImplementedException, - isInt */ + isInt, PasswordResponses */ 'use strict'; @@ -267,11 +267,11 @@ var WorkerMessageHandler = { var onFailure = function(e) { if (e instanceof PasswordException) { - if (e.code === 'needpassword') { + if (e.code === PasswordResponses.NEED_PASSWORD) { handler.send('NeedPassword', { exception: e }); - } else if (e.code === 'incorrectpassword') { + } else if (e.code === PasswordResponses.INCORRECT_PASSWORD) { handler.send('IncorrectPassword', { exception: e }); @@ -291,10 +291,17 @@ var WorkerMessageHandler = { } }; - getPdfManager(data).then(function() { - loadDocument(false).then(onSuccess, function(ex) { + getPdfManager(data).then(function pdfManagerReady() { + loadDocument(false).then(onSuccess, function loadFailure(ex) { // 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.passwordChangedPromise = new Promise(); + pdfManager.passwordChangedPromise.then(pdfManagerReady); + } + onFailure(ex); return; } @@ -349,6 +356,10 @@ var WorkerMessageHandler = { }); }); + handler.on('UpdatePassword', function wphSetupUpdatePassword(data) { + pdfManager.updatePassword(data); + }); + handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) { pdfManager.getPage(data.pageIndex).then(function(page) { pdfManager.ensure(page, 'getAnnotationsData', []).then( diff --git a/web/viewer.js b/web/viewer.js index 81f19e42a..983387693 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -1048,30 +1048,27 @@ var PDFView = { this.pdfDocument = null; var self = this; self.loading = true; - PDFJS.getDocument(parameters, pdfDataRangeTransport).then( + var passwordNeeded = function passwordNeeded(updatePassword, reason) { + var promptString = mozL10n.get('request_password', null, + 'PDF is protected by a password:'); + + if (reason === PDFJS.PasswordResponses.INCORRECT_PASSWORD) { + promptString += '\n' + mozL10n.get('invalid_password', null, + 'Invalid Password.'); + } + + password = prompt(promptString); + if (password && password.length > 0) { + return updatePassword(password); + } + }; + + PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded).then( function getDocumentCallback(pdfDocument) { self.load(pdfDocument, scale); self.loading = false; }, function getDocumentError(message, exception) { - if (exception && exception.name === 'PasswordException') { - if (exception.code === 'needpassword' || - exception.code === 'incorrectpassword') { - var promptString = mozL10n.get('request_password', null, - 'PDF is protected by a password:'); - - if (exception.code === 'incorrectpassword') { - promptString += '\n' + mozL10n.get('invalid_password', null, - 'Invalid Password.'); - } - - password = prompt(promptString); - if (password && password.length > 0) { - return PDFView.open(url, scale, password); - } - } - } - var loadingErrorMessage = mozL10n.get('loading_error', null, 'An error occurred while loading the PDF.');