Merge pull request #1698 from saebekassebil/fixpassword

Support password protected PDF files (Better commiting)
This commit is contained in:
Yury Delendik 2012-05-17 13:07:37 -07:00
commit df3efc0aa0
9 changed files with 127 additions and 39 deletions

View File

@ -29,3 +29,4 @@ page_of=af {{pageCount}}
no_outline=Ingen dokumentoversigt tilgængelig no_outline=Ingen dokumentoversigt tilgængelig
open_file.title=Åbn fil open_file.title=Åbn fil
text_annotation_type=[{{type}} Kommentar] text_annotation_type=[{{type}} Kommentar]
request_password=PDF filen er beskyttet med et kodeord:

View File

@ -42,3 +42,4 @@ zoom_in_label=Zoom In
zoom.title=Zoom zoom.title=Zoom
thumb_page_title=Page {{page}} thumb_page_title=Page {{page}}
thumb_page_canvas=Thumbnail of Page {{page}} thumb_page_canvas=Thumbnail of Page {{page}}
request_password=PDF is protected by a password:

View File

@ -7,20 +7,46 @@
* is used, which means it must follow the same origin rules that any XHR does * is used, which means it must follow the same origin rules that any XHR does
* e.g. No cross domain requests without CORS. * e.g. No cross domain requests without CORS.
* *
* @param {string|TypedAray} source Either a url to a PDF is located or a * @param {string|TypedAray|object} source Can be an url to where a PDF is
* typed array (Uint8Array) already populated with data. * located, a typed array (Uint8Array) already populated with data or
* @param {Object} headers An object containing the http headers like this: * and parameter object with the following possible fields:
* { Authorization: "BASIC XXX" }. * - url - The URL of the PDF.
* - data - A typed array with PDF data.
* - httpHeaders - Basic authentication headers.
* - password - For decrypting password-protected PDFs.
*
* @return {Promise} A promise that is resolved with {PDFDocumentProxy} object. * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
*/ */
PDFJS.getDocument = function getDocument(source, headers) { PDFJS.getDocument = function getDocument(source) {
var url, data, headers, password, parameters = {};
if (typeof source === 'string') {
url = source;
} else if (isArrayBuffer(source)) {
data = source;
} else if (typeof source === 'object') {
url = source.url;
data = source.data;
headers = source.httpHeaders;
password = source.password;
parameters.password = password || null;
if (!url && !data)
error('Invalid parameter array, need either .data or .url');
} else {
error('Invalid parameter in getDocument, need either Uint8Array, ' +
'string or a parameter object');
}
var promise = new PDFJS.Promise(); var promise = new PDFJS.Promise();
var transport = new WorkerTransport(promise); var transport = new WorkerTransport(promise);
if (typeof source === 'string') { if (data) {
// assuming the data is array, instantiating directly from it
transport.sendData(data, parameters);
} else if (url) {
// fetch url // fetch url
PDFJS.getPdf( PDFJS.getPdf(
{ {
url: source, url: url,
progress: function getPDFProgress(evt) { progress: function getPDFProgress(evt) {
if (evt.lengthComputable) if (evt.lengthComputable)
promise.progress({ promise.progress({
@ -35,12 +61,10 @@ PDFJS.getDocument = function getDocument(source, headers) {
headers: headers headers: headers
}, },
function getPDFLoad(data) { function getPDFLoad(data) {
transport.sendData(data); transport.sendData(data, parameters);
}); });
} else {
// assuming the source is array, instantiating directly from it
transport.sendData(source);
} }
return promise; return promise;
}; };
@ -122,6 +146,11 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
}); });
return promise; return promise;
}, },
isEncrypted: function PDFDocumentProxy_isEncrypted() {
var promise = new PDFJS.Promise();
promise.resolve(this.pdfInfo.encrypted);
return promise;
},
destroy: function PDFDocumentProxy_destroy() { destroy: function PDFDocumentProxy_destroy() {
this.transport.destroy(); this.transport.destroy();
} }
@ -467,6 +496,14 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.workerReadyPromise.resolve(pdfDocument); this.workerReadyPromise.resolve(pdfDocument);
}, this); }, this);
messageHandler.on('NeedPassword', function transportPassword(data) {
this.workerReadyPromise.reject(data.exception.message, data.exception);
}, this);
messageHandler.on('IncorrectPassword', function transportBadPass(data) {
this.workerReadyPromise.reject(data.exception.message, data.exception);
}, this);
messageHandler.on('GetPage', function transportPage(data) { messageHandler.on('GetPage', function transportPage(data) {
var pageInfo = data.pageInfo; var pageInfo = data.pageInfo;
var page = new PDFPageProxy(pageInfo, this); var page = new PDFPageProxy(pageInfo, this);
@ -569,8 +606,8 @@ var WorkerTransport = (function WorkerTransportClosure() {
}); });
}, },
sendData: function WorkerTransport_sendData(data) { sendData: function WorkerTransport_sendData(data, params) {
this.messageHandler.send('GetDocRequest', data); this.messageHandler.send('GetDocRequest', {data: data, params: params});
}, },
getPage: function WorkerTransport_getPage(pageNumber, promise) { getPage: function WorkerTransport_getPage(pageNumber, promise) {

View File

@ -320,19 +320,19 @@ var Page = (function PageClosure() {
* `PDFDocument` objects on the main thread created. * `PDFDocument` objects on the main thread created.
*/ */
var PDFDocument = (function PDFDocumentClosure() { var PDFDocument = (function PDFDocumentClosure() {
function PDFDocument(arg, callback) { function PDFDocument(arg, password) {
if (isStream(arg)) if (isStream(arg))
init.call(this, arg); init.call(this, arg, password);
else if (isArrayBuffer(arg)) else if (isArrayBuffer(arg))
init.call(this, new Stream(arg)); init.call(this, new Stream(arg), password);
else else
error('PDFDocument: Unknown argument type'); error('PDFDocument: Unknown argument type');
} }
function init(stream) { function init(stream, password) {
assertWellFormed(stream.length > 0, 'stream must have data'); assertWellFormed(stream.length > 0, 'stream must have data');
this.stream = stream; this.stream = stream;
this.setup(); this.setup(password);
this.acroForm = this.catalog.catDict.get('AcroForm'); this.acroForm = this.catalog.catDict.get('AcroForm');
} }
@ -423,11 +423,12 @@ var PDFDocument = (function PDFDocumentClosure() {
} }
// May not be a PDF file, continue anyway. // May not be a PDF file, continue anyway.
}, },
setup: function PDFDocument_setup(ownerPassword, userPassword) { setup: function PDFDocument_setup(password) {
this.checkHeader(); this.checkHeader();
var xref = new XRef(this.stream, var xref = new XRef(this.stream,
this.startXRef, this.startXRef,
this.mainXRefEntriesOffset); this.mainXRefEntriesOffset,
password);
this.xref = xref; this.xref = xref;
this.catalog = new Catalog(xref); this.catalog = new Catalog(xref);
}, },

View File

@ -556,7 +556,9 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes, var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
ownerPassword, userPassword, flags, ownerPassword, userPassword, flags,
revision, keyLength, encryptMetadata); revision, keyLength, encryptMetadata);
if (!encryptionKey && password) { if (!encryptionKey && !password) {
throw new PasswordException('No password given', 'needpassword');
} else if (!encryptionKey && password) {
// Attempting use the password as an owner password // Attempting use the password as an owner password
var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword, var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword,
revision, keyLength); revision, keyLength);
@ -566,7 +568,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
} }
if (!encryptionKey) if (!encryptionKey)
error('incorrect password or encryption data'); throw new PasswordException('Incorrect Password', 'incorrectpassword');
this.encryptionKey = encryptionKey; this.encryptionKey = encryptionKey;

View File

@ -298,7 +298,7 @@ var Catalog = (function CatalogClosure() {
})(); })();
var XRef = (function XRefClosure() { var XRef = (function XRefClosure() {
function XRef(stream, startXRef, mainXRefEntriesOffset) { function XRef(stream, startXRef, mainXRefEntriesOffset, password) {
this.stream = stream; this.stream = stream;
this.entries = []; this.entries = [];
this.xrefstms = {}; this.xrefstms = {};
@ -311,8 +311,7 @@ var XRef = (function XRefClosure() {
var encrypt = trailerDict.get('Encrypt'); var encrypt = trailerDict.get('Encrypt');
if (encrypt) { if (encrypt) {
var fileId = trailerDict.get('ID'); var fileId = trailerDict.get('ID');
this.encrypt = new CipherTransformFactory(encrypt, this.encrypt = new CipherTransformFactory(encrypt, fileId[0], password);
fileId[0] /*, password */);
} }
// get the root dictionary (catalog) object // get the root dictionary (catalog) object

View File

@ -58,6 +58,19 @@ function shadow(obj, prop, value) {
return value; return value;
} }
var PasswordException = (function PasswordExceptionClosure() {
function PasswordException(msg, code) {
this.name = 'PasswordException';
this.message = msg;
this.code = code;
}
PasswordException.prototype = new Error();
PasswordException.constructor = PasswordException;
return PasswordException;
})();
function bytesToString(bytes) { function bytesToString(bytes) {
var str = ''; var str = '';
var length = bytes.length; var length = bytes.length;
@ -456,7 +469,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
} }
this.isResolved = true; this.isResolved = true;
this.data = data || null; this.data = (typeof data !== 'undefined') ? data : null;
var callbacks = this.callbacks; var callbacks = this.callbacks;
for (var i = 0, ii = callbacks.length; i < ii; i++) { for (var i = 0, ii = callbacks.length; i < ii; i++) {
@ -471,7 +484,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
} }
}, },
reject: function Promise_reject(reason) { reject: function Promise_reject(reason, exception) {
if (this.isRejected) { if (this.isRejected) {
error('A Promise can be rejected only once ' + this.name); error('A Promise can be rejected only once ' + this.name);
} }
@ -484,7 +497,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
var errbacks = this.errbacks; var errbacks = this.errbacks;
for (var i = 0, ii = errbacks.length; i < ii; i++) { for (var i = 0, ii = errbacks.length; i < ii; i++) {
errbacks[i].call(null, reason); errbacks[i].call(null, reason, exception);
} }
}, },

View File

@ -88,14 +88,35 @@ var WorkerMessageHandler = {
handler.on('GetDocRequest', function wphSetupDoc(data) { handler.on('GetDocRequest', function wphSetupDoc(data) {
// Create only the model of the PDFDoc, which is enough for // Create only the model of the PDFDoc, which is enough for
// processing the content of the pdf. // processing the content of the pdf.
pdfModel = new PDFDocument(new Stream(data)); var pdfData = data.data;
var pdfPassword = data.params.password;
try {
pdfModel = new PDFDocument(new Stream(pdfData), pdfPassword);
} catch (e) {
if (e instanceof PasswordException) {
if (e.code === 'needpassword') {
handler.send('NeedPassword', {
exception: e
});
} else if (e.code === 'incorrectpassword') {
handler.send('IncorrectPassword', {
exception: e
});
}
return;
} else {
throw e;
}
}
var doc = { var doc = {
numPages: pdfModel.numPages, numPages: pdfModel.numPages,
fingerprint: pdfModel.getFingerprint(), fingerprint: pdfModel.getFingerprint(),
destinations: pdfModel.catalog.destinations, destinations: pdfModel.catalog.destinations,
outline: pdfModel.catalog.documentOutline, outline: pdfModel.catalog.documentOutline,
info: pdfModel.getDocumentInfo(), info: pdfModel.getDocumentInfo(),
metadata: pdfModel.catalog.metadata metadata: pdfModel.catalog.metadata,
encrypted: !!pdfModel.xref.encrypt
}; };
handler.send('GetDoc', {pdfInfo: doc}); handler.send('GetDoc', {pdfInfo: doc});
}); });

View File

@ -331,10 +331,15 @@ var PDFView = {
return currentPageNumber; return currentPageNumber;
}, },
open: function pdfViewOpen(url, scale) { open: function pdfViewOpen(url, scale, password) {
this.url = url; var parameters = {password: password};
if (typeof url === 'string') { // URL
document.title = decodeURIComponent(getFileName(url)) || url; this.url = url;
document.title = decodeURIComponent(getFileName(url)) || url;
parameters.url = url;
} else if (url && 'byteLength' in url) { // ArrayBuffer
parameters.data = url;
}
if (!PDFView.loadingBar) { if (!PDFView.loadingBar) {
PDFView.loadingBar = new ProgressBar('#loadingBar', {}); PDFView.loadingBar = new ProgressBar('#loadingBar', {});
@ -342,12 +347,23 @@ var PDFView = {
var self = this; var self = this;
self.loading = true; self.loading = true;
PDFJS.getDocument(url).then( PDFJS.getDocument(parameters).then(
function getDocumentCallback(pdfDocument) { function getDocumentCallback(pdfDocument) {
self.load(pdfDocument, scale); self.load(pdfDocument, scale);
self.loading = false; self.loading = false;
}, },
function getDocumentError(message, exception) { function getDocumentError(message, exception) {
if (exception.name === 'PasswordException') {
if (exception.code === 'needpassword') {
var promptString = mozL10n.get('request_password', null,
'PDF is protected by a password:');
password = prompt(promptString);
if (password && password.length > 0) {
return PDFView.open(url, scale, password);
}
}
}
var loadingIndicator = document.getElementById('loading'); var loadingIndicator = document.getElementById('loading');
loadingIndicator.textContent = mozL10n.get('loading_error_indicator', loadingIndicator.textContent = mozL10n.get('loading_error_indicator',
null, 'Error'); null, 'Error');
@ -1531,10 +1547,7 @@ window.addEventListener('change', function webViewerChange(evt) {
for (var i = 0; i < data.length; i++) for (var i = 0; i < data.length; i++)
uint8Array[i] = data.charCodeAt(i); uint8Array[i] = data.charCodeAt(i);
// TODO using blob instead? PDFView.open(uint8Array, 0);
PDFJS.getDocument(uint8Array).then(function(pdfDocument) {
PDFView.load(pdfDocument);
});
}; };
// Read as a binary string since "readAsArrayBuffer" is not yet // Read as a binary string since "readAsArrayBuffer" is not yet