[api-minor] Ensure that the getDocument Promise is rejected if the loadingTask is destroyed, or an Error is thrown, inside of the onPassword callback (issue 7806)

This patch also removes the `UpdatePassword` message, in favour of using the `sendWithPromise` method of `MessageHandler`.
Furthermore, the patch also refactors the `BasePdfManager_updatePassword`/`BasePdfManager_passwordChanged` methods (in pdf_manager.js), and the `pdfManagerReady` function (in worker.js).
This commit is contained in:
Jonas Jenwald 2016-12-31 13:59:07 +01:00
parent 59afb4b9f0
commit 27513cd23b
4 changed files with 112 additions and 71 deletions

View File

@ -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() {

View File

@ -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]);

View File

@ -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);

View File

@ -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() {