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)
This commit is contained in:
commit
aabfb7788a
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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, {
|
||||||
|
@ -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]);
|
||||||
|
@ -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);
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user