Browse Source

Merge pull request #7926 from Snuffleupagus/api-onPassword-abort/throw-Promise

[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)
Jonas Jenwald 8 years ago committed by GitHub
parent
commit
aabfb7788a
  1. 14
      src/core/document.js
  2. 8
      src/core/obj.js
  3. 21
      src/core/pdf_manager.js
  4. 79
      src/core/worker.js
  5. 45
      src/display/api.js
  6. 51
      test/unit/api_spec.js

14
src/core/document.js

@ -370,22 +370,20 @@ var PDFDocument = (function PDFDocumentClosure() {
var EMPTY_FINGERPRINT = '\x00\x00\x00\x00\x00\x00\x00' + var EMPTY_FINGERPRINT = '\x00\x00\x00\x00\x00\x00\x00' +
'\x00\x00\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)) { if (isStream(arg)) {
init.call(this, pdfManager, arg, password); stream = arg;
} else if (isArrayBuffer(arg)) { } else if (isArrayBuffer(arg)) {
init.call(this, pdfManager, new Stream(arg), password); stream = new Stream(arg);
} else { } else {
error('PDFDocument: Unknown argument type'); error('PDFDocument: Unknown argument type');
} }
}
function init(pdfManager, stream, password) {
assert(stream.length > 0, 'stream must have data'); assert(stream.length > 0, 'stream must have data');
this.pdfManager = pdfManager; this.pdfManager = pdfManager;
this.stream = stream; this.stream = stream;
var xref = new XRef(this.stream, password, pdfManager); this.xref = new XRef(stream, pdfManager);
this.xref = xref;
} }
function find(stream, needle, limit, backwards) { function find(stream, needle, limit, backwards) {

8
src/core/obj.js

@ -758,13 +758,13 @@ var Catalog = (function CatalogClosure() {
})(); })();
var XRef = (function XRefClosure() { var XRef = (function XRefClosure() {
function XRef(stream, password) { function XRef(stream, pdfManager) {
this.stream = stream; this.stream = stream;
this.pdfManager = pdfManager;
this.entries = []; this.entries = [];
this.xrefstms = Object.create(null); this.xrefstms = Object.create(null);
// prepare the XRef cache // prepare the XRef cache
this.cache = []; this.cache = [];
this.password = password;
this.stats = { this.stats = {
streamTypes: [], streamTypes: [],
fontTypes: [] fontTypes: []
@ -789,7 +789,7 @@ var XRef = (function XRefClosure() {
trailerDict.assignXref(this); trailerDict.assignXref(this);
this.trailer = trailerDict; this.trailer = trailerDict;
var encrypt = trailerDict.get('Encrypt'); var encrypt = trailerDict.get('Encrypt');
if (encrypt) { if (isDict(encrypt)) {
var ids = trailerDict.get('ID'); var ids = trailerDict.get('ID');
var fileId = (ids && ids.length) ? ids[0] : ''; var fileId = (ids && ids.length) ? ids[0] : '';
// The 'Encrypt' dictionary itself should not be encrypted, and by // The 'Encrypt' dictionary itself should not be encrypted, and by
@ -798,7 +798,7 @@ var XRef = (function XRefClosure() {
// objects (fixes issue7665.pdf). // objects (fixes issue7665.pdf).
encrypt.suppressEncryption = true; encrypt.suppressEncryption = true;
this.encrypt = new CipherTransformFactory(encrypt, fileId, this.encrypt = new CipherTransformFactory(encrypt, fileId,
this.password); this.pdfManager.password);
} }
// get the root dictionary (catalog) object // get the root dictionary (catalog) object

21
src/core/pdf_manager.js

@ -52,6 +52,10 @@ var BasePdfManager = (function BasePdfManagerClosure() {
return this._docId; return this._docId;
}, },
get password() {
return this._password;
},
get docBaseUrl() { get docBaseUrl() {
var docBaseUrl = null; var docBaseUrl = null;
if (this._docBaseUrl) { if (this._docBaseUrl) {
@ -106,15 +110,7 @@ var BasePdfManager = (function BasePdfManagerClosure() {
}, },
updatePassword: function BasePdfManager_updatePassword(password) { updatePassword: function BasePdfManager_updatePassword(password) {
this.pdfDocument.xref.password = this.password = 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() { terminate: function BasePdfManager_terminate() {
@ -129,10 +125,11 @@ var LocalPdfManager = (function LocalPdfManagerClosure() {
function LocalPdfManager(docId, data, password, evaluatorOptions, function LocalPdfManager(docId, data, password, evaluatorOptions,
docBaseUrl) { docBaseUrl) {
this._docId = docId; this._docId = docId;
this._password = password;
this._docBaseUrl = docBaseUrl; this._docBaseUrl = docBaseUrl;
this.evaluatorOptions = evaluatorOptions; this.evaluatorOptions = evaluatorOptions;
var stream = new Stream(data); var stream = new Stream(data);
this.pdfDocument = new PDFDocument(this, stream, password); this.pdfDocument = new PDFDocument(this, stream);
this._loadedStreamCapability = createPromiseCapability(); this._loadedStreamCapability = createPromiseCapability();
this._loadedStreamCapability.resolve(stream); this._loadedStreamCapability.resolve(stream);
} }
@ -179,6 +176,7 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() {
function NetworkPdfManager(docId, pdfNetworkStream, args, evaluatorOptions, function NetworkPdfManager(docId, pdfNetworkStream, args, evaluatorOptions,
docBaseUrl) { docBaseUrl) {
this._docId = docId; this._docId = docId;
this._password = args.password;
this._docBaseUrl = docBaseUrl; this._docBaseUrl = docBaseUrl;
this.msgHandler = args.msgHandler; this.msgHandler = args.msgHandler;
this.evaluatorOptions = evaluatorOptions; this.evaluatorOptions = evaluatorOptions;
@ -191,8 +189,7 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() {
rangeChunkSize: args.rangeChunkSize rangeChunkSize: args.rangeChunkSize
}; };
this.streamManager = new ChunkedStreamManager(pdfNetworkStream, params); this.streamManager = new ChunkedStreamManager(pdfNetworkStream, params);
this.pdfDocument = new PDFDocument(this, this.streamManager.getStream(), this.pdfDocument = new PDFDocument(this, this.streamManager.getStream());
args.password);
} }
Util.inherit(NetworkPdfManager, BasePdfManager, { Util.inherit(NetworkPdfManager, BasePdfManager, {

79
src/core/worker.js

@ -663,19 +663,25 @@ var WorkerMessageHandler = {
return pdfManagerCapability.promise; return pdfManagerCapability.promise;
} }
var setupDoc = function(data) { function setupDoc(data) {
var onSuccess = function(doc) { function onSuccess(doc) {
ensureNotTerminated(); ensureNotTerminated();
handler.send('GetDoc', { pdfInfo: doc }); handler.send('GetDoc', { pdfInfo: doc });
}; }
var onFailure = function(e) { function onFailure(e) {
if (e instanceof PasswordException) { if (e instanceof PasswordException) {
if (e.code === PasswordResponses.NEED_PASSWORD) { var task = new WorkerTask('PasswordException: response ' + e.code);
handler.send('NeedPassword', e); startWorkerTask(task);
} else if (e.code === PasswordResponses.INCORRECT_PASSWORD) {
handler.send('IncorrectPassword', e); 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) { } else if (e instanceof InvalidPDFException) {
handler.send('InvalidPDF', e); handler.send('InvalidPDF', e);
} else if (e instanceof MissingPDFException) { } else if (e instanceof MissingPDFException) {
@ -686,7 +692,27 @@ var WorkerMessageHandler = {
handler.send('UnknownError', handler.send('UnknownError',
new UnknownErrorException(e.message, e.toString())); 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(); ensureNotTerminated();
@ -714,33 +740,8 @@ var WorkerMessageHandler = {
pdfManager.onLoadedStream().then(function(stream) { pdfManager.onLoadedStream().then(function(stream) {
handler.send('DataLoaded', { length: stream.bytes.byteLength }); handler.send('DataLoaded', { length: stream.bytes.byteLength });
}); });
}).then(function pdfManagerReady() { }).then(pdfManagerReady, onFailure);
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);
};
handler.on('GetPage', function wphSetupGetPage(data) { handler.on('GetPage', function wphSetupGetPage(data) {
return pdfManager.getPage(data.pageIndex).then(function(page) { 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) { handler.on('GetAnnotations', function wphSetupGetAnnotations(data) {
return pdfManager.getPage(data.pageIndex).then(function(page) { return pdfManager.getPage(data.pageIndex).then(function(page) {
return pdfManager.ensure(page, 'getAnnotationsData', [data.intent]); return pdfManager.ensure(page, 'getAnnotationsData', [data.intent]);

45
src/display/api.js

@ -1419,6 +1419,7 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.destroyed = false; this.destroyed = false;
this.destroyCapability = null; this.destroyCapability = null;
this._passwordCapability = null;
this.pageCache = []; this.pageCache = [];
this.pagePromises = []; this.pagePromises = [];
@ -1435,6 +1436,11 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.destroyed = true; this.destroyed = true;
this.destroyCapability = createPromiseCapability(); this.destroyCapability = createPromiseCapability();
if (this._passwordCapability) {
this._passwordCapability.reject(
new Error('Worker was destroyed during onPassword callback'));
}
var waitOn = []; var waitOn = [];
// We need to wait for all renderings to be completed, e.g. // We need to wait for all renderings to be completed, e.g.
// timeout/rAF can take a long time. // timeout/rAF can take a long time.
@ -1464,13 +1470,9 @@ var WorkerTransport = (function WorkerTransportClosure() {
return this.destroyCapability.promise; return this.destroyCapability.promise;
}, },
setupMessageHandler: setupMessageHandler: function WorkerTransport_setupMessageHandler() {
function WorkerTransport_setupMessageHandler() {
var messageHandler = this.messageHandler; var messageHandler = this.messageHandler;
var loadingTask = this.loadingTask;
function updatePassword(password) {
messageHandler.send('UpdatePassword', password);
}
var pdfDataRangeTransport = this.pdfDataRangeTransport; var pdfDataRangeTransport = this.pdfDataRangeTransport;
if (pdfDataRangeTransport) { if (pdfDataRangeTransport) {
@ -1508,24 +1510,27 @@ var WorkerTransport = (function WorkerTransportClosure() {
loadingTask._capability.resolve(pdfDocument); loadingTask._capability.resolve(pdfDocument);
}, this); }, this);
messageHandler.on('NeedPassword', messageHandler.on('PasswordRequest',
function transportNeedPassword(exception) { function transportPasswordRequest(exception) {
var loadingTask = this.loadingTask; this._passwordCapability = createPromiseCapability();
if (loadingTask.onPassword) { if (loadingTask.onPassword) {
return loadingTask.onPassword(updatePassword, var updatePassword = function (password) {
PasswordResponses.NEED_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( return this._passwordCapability.promise;
new PasswordException(exception.message, exception.code));
}, this); }, this);
messageHandler.on('IncorrectPassword', messageHandler.on('PasswordException',
function transportIncorrectPassword(exception) { function transportPasswordException(exception) {
var loadingTask = this.loadingTask;
if (loadingTask.onPassword) {
return loadingTask.onPassword(updatePassword,
PasswordResponses.INCORRECT_PASSWORD);
}
loadingTask._capability.reject( loadingTask._capability.reject(
new PasswordException(exception.message, exception.code)); new PasswordException(exception.message, exception.code));
}, this); }, this);

51
test/unit/api_spec.js

@ -191,7 +191,7 @@ describe('api', function() {
}); });
var result1 = passwordNeededLoadingTask.promise.then(function () { var result1 = passwordNeededLoadingTask.promise.then(function () {
done.fail('shall fail with no password'); done.fail('shall fail with no password');
return passwordNeededLoadingTask.destroy(); return Promise.reject(new Error('loadingTask should be rejected'));
}, function (data) { }, function (data) {
expect(data instanceof PasswordException).toEqual(true); expect(data instanceof PasswordException).toEqual(true);
expect(data.code).toEqual(PasswordResponses.NEED_PASSWORD); expect(data.code).toEqual(PasswordResponses.NEED_PASSWORD);
@ -203,7 +203,7 @@ describe('api', function() {
}); });
var result2 = passwordIncorrectLoadingTask.promise.then(function () { var result2 = passwordIncorrectLoadingTask.promise.then(function () {
done.fail('shall fail with wrong password'); done.fail('shall fail with wrong password');
return passwordNeededLoadingTask.destroy(); return Promise.reject(new Error('loadingTask should be rejected'));
}, function (data) { }, function (data) {
expect(data instanceof PasswordException).toEqual(true); expect(data instanceof PasswordException).toEqual(true);
expect(data.code).toEqual(PasswordResponses.INCORRECT_PASSWORD); expect(data.code).toEqual(PasswordResponses.INCORRECT_PASSWORD);
@ -224,6 +224,53 @@ describe('api', function() {
done.fail(reason); 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() { describe('PDFWorker', function() {

Loading…
Cancel
Save