]> git.parisson.com Git - pdf.js.git/commitdiff
Support password and add the relevant l10n strings
authorJakob Miland <saebekassebil@gmail.com>
Mon, 14 May 2012 18:45:07 +0000 (20:45 +0200)
committerJakob Miland <saebekassebil@gmail.com>
Mon, 14 May 2012 18:45:07 +0000 (20:45 +0200)
l10n/da/viewer.properties
l10n/en-US/viewer.properties
src/api.js
src/core.js
src/crypto.js
src/obj.js
src/util.js
src/worker.js
web/viewer.js

index 6d208db7001650047e9760671b8cd4c35fe0ffa5..e098a79395d59b3231a11486ae6b1ae79437fad0 100644 (file)
@@ -29,3 +29,4 @@ page_of=af {{pageCount}}
 no_outline=Ingen dokumentoversigt tilgængelig
 open_file.title=Åbn fil
 text_annotation_type=[{{type}} Kommentar]
+request_password=PDF filen er beskyttet med et kodeord:
index c8dbe4aba4c16074feef143eab7699093900b8e7..d0ba6ffacfa10084e6b430c1a0ad6ec88fd96c54 100644 (file)
@@ -42,3 +42,4 @@ zoom_in_label=Zoom In
 zoom.title=Zoom
 thumb_page_title=Page {{page}}
 thumb_page_canvas=Thumbnail of Page {{page}}
+request_password=PDF is protected by a password:
index 1efb22caa04504e3085c924c8d80e189354460b9..cd10ee366f12000b1db3a27516b1c75c947f0c29 100644 (file)
@@ -7,20 +7,46 @@
  * is used, which means it must follow the same origin rules that any XHR does
  * e.g. No cross domain requests without CORS.
  *
- * @param {string|TypedAray} source Either a url to a PDF is located or a
- * typed array (Uint8Array) already populated with data.
- * @param {Object} headers An object containing the http headers like this:
- * { Authorization: "BASIC XXX" }.
+ * @param {string|TypedAray|object} source Can be an url to where a PDF is
+ * located, a typed array (Uint8Array) already populated with data or
+ * and parameter object with the following possible fields:
+ *  - 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.
  */
-PDFJS.getDocument = function getDocument(source, headers) {
+PDFJS.getDocument = function getDocument(source) {
+  var url, data, headers, password, parameters = {};
+  if (typeof source === 'string') {
+    url = params;
+  } 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 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
     PDFJS.getPdf(
       {
-        url: source,
+        url: url,
         progress: function getPDFProgress(evt) {
           if (evt.lengthComputable)
             promise.progress({
@@ -35,12 +61,10 @@ PDFJS.getDocument = function getDocument(source, headers) {
         headers: headers
       },
       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;
 };
 
@@ -122,6 +146,11 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
       });
       return promise;
     },
+    isEncrypted: function PDFDocumentProxy_isEncrypted() {
+      var promise = new PDFJS.Promise();
+      promise.resolve(this.pdfInfo.encrypted);
+      return promise;
+    },
     destroy: function PDFDocumentProxy_destroy() {
       this.transport.destroy();
     }
@@ -467,6 +496,14 @@ var WorkerTransport = (function WorkerTransportClosure() {
         this.workerReadyPromise.resolve(pdfDocument);
       }, 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) {
         var pageInfo = data.pageInfo;
         var page = new PDFPageProxy(pageInfo, this);
@@ -569,8 +606,8 @@ var WorkerTransport = (function WorkerTransportClosure() {
       });
     },
 
-    sendData: function WorkerTransport_sendData(data) {
-      this.messageHandler.send('GetDocRequest', data);
+    sendData: function WorkerTransport_sendData(data, params) {
+      this.messageHandler.send('GetDocRequest', {data: data, params: params});
     },
 
     getPage: function WorkerTransport_getPage(pageNumber, promise) {
index 99a8dd16144778b3412c7eb9a1c5c23fb1e603aa..bd8161691477409c17267c773ca8e5420fa1baa9 100644 (file)
@@ -320,19 +320,19 @@ var Page = (function PageClosure() {
  * `PDFDocument` objects on the main thread created.
  */
 var PDFDocument = (function PDFDocumentClosure() {
-  function PDFDocument(arg, callback) {
+  function PDFDocument(arg, password) {
     if (isStream(arg))
-      init.call(this, arg);
+      init.call(this, arg, password);
     else if (isArrayBuffer(arg))
-      init.call(this, new Stream(arg));
+      init.call(this, new Stream(arg), password);
     else
       error('PDFDocument: Unknown argument type');
   }
 
-  function init(stream) {
+  function init(stream, password) {
     assertWellFormed(stream.length > 0, 'stream must have data');
     this.stream = stream;
-    this.setup();
+    this.setup(password);
     this.acroForm = this.catalog.catDict.get('AcroForm');
   }
 
@@ -423,11 +423,12 @@ var PDFDocument = (function PDFDocumentClosure() {
       }
       // May not be a PDF file, continue anyway.
     },
-    setup: function PDFDocument_setup(ownerPassword, userPassword) {
+    setup: function PDFDocument_setup(password) {
       this.checkHeader();
       var xref = new XRef(this.stream,
                           this.startXRef,
-                          this.mainXRefEntriesOffset);
+                          this.mainXRefEntriesOffset,
+                          password);
       this.xref = xref;
       this.catalog = new Catalog(xref);
     },
index dcd820554856a7b2db65fb2ced8f445b95e755dd..c86551f368f40be72174e1912d668e23bc552f42 100644 (file)
@@ -556,7 +556,9 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
     var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
                                        ownerPassword, userPassword, flags,
                                        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
       var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword,
                                                revision, keyLength);
@@ -566,7 +568,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
     }
 
     if (!encryptionKey)
-      error('incorrect password or encryption data');
+      throw new PasswordException('Incorrect Password', 'incorrectpassword');
 
     this.encryptionKey = encryptionKey;
 
index 9b99eb8f7f413225c77ffb05359ec12bbdf48ce7..3432ac68dd6b3686c3f0813111945ee4153715b6 100644 (file)
@@ -298,7 +298,7 @@ var Catalog = (function CatalogClosure() {
 })();
 
 var XRef = (function XRefClosure() {
-  function XRef(stream, startXRef, mainXRefEntriesOffset) {
+  function XRef(stream, startXRef, mainXRefEntriesOffset, password) {
     this.stream = stream;
     this.entries = [];
     this.xrefstms = {};
@@ -311,8 +311,7 @@ var XRef = (function XRefClosure() {
     var encrypt = trailerDict.get('Encrypt');
     if (encrypt) {
       var fileId = trailerDict.get('ID');
-      this.encrypt = new CipherTransformFactory(encrypt,
-                                                fileId[0] /*, password */);
+      this.encrypt = new CipherTransformFactory(encrypt, fileId[0], password);
     }
 
     // get the root dictionary (catalog) object
index 140b18cf112e188bd3205eeb4aa230662d58f8ad..ef40f524b0e78543d5d9fdda6e97a383ca51a737 100644 (file)
@@ -58,6 +58,14 @@ function shadow(obj, prop, value) {
   return value;
 }
 
+function PasswordException(msg, code) {
+  this.name = 'PasswordException';
+  this.message = msg;
+  this.code = code;
+}
+PasswordException.prototype = new Error();
+PasswordException.constructor = PasswordException;
+
 function bytesToString(bytes) {
   var str = '';
   var length = bytes.length;
@@ -456,7 +464,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
       }
 
       this.isResolved = true;
-      this.data = data || null;
+      this.data = (typeof data !== 'undefined') ? data : null;
       var callbacks = this.callbacks;
 
       for (var i = 0, ii = callbacks.length; i < ii; i++) {
@@ -471,7 +479,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
       }
     },
 
-    reject: function Promise_reject(reason) {
+    reject: function Promise_reject(reason, exception) {
       if (this.isRejected) {
         error('A Promise can be rejected only once ' + this.name);
       }
@@ -484,7 +492,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
       var errbacks = this.errbacks;
 
       for (var i = 0, ii = errbacks.length; i < ii; i++) {
-        errbacks[i].call(null, reason);
+        errbacks[i].call(null, reason, exception);
       }
     },
 
index 25f3f52cdc3d45f6e9cfa19e6c839b7651be9f41..84a8298a74b37fedd6d870e00bb80a316e25f7d5 100644 (file)
@@ -88,14 +88,35 @@ var WorkerMessageHandler = {
     handler.on('GetDocRequest', function wphSetupDoc(data) {
       // Create only the model of the PDFDoc, which is enough for
       // 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 = {
         numPages: pdfModel.numPages,
         fingerprint: pdfModel.getFingerprint(),
         destinations: pdfModel.catalog.destinations,
         outline: pdfModel.catalog.documentOutline,
         info: pdfModel.getDocumentInfo(),
-        metadata: pdfModel.catalog.metadata
+        metadata: pdfModel.catalog.metadata,
+        encrypted: !!pdfModel.xref.encrypt
       };
       handler.send('GetDoc', {pdfInfo: doc});
     });
index dcbfcf14e6e14e8074930e16195e472316d7495e..d4897b9563343c3f0aca8f58f5e090898e7613cb 100644 (file)
@@ -331,10 +331,15 @@ var PDFView = {
     return currentPageNumber;
   },
 
-  open: function pdfViewOpen(url, scale) {
-    this.url = url;
-
-    document.title = decodeURIComponent(getFileName(url)) || url;
+  open: function pdfViewOpen(url, scale, password) {
+    var parameters = {password: password};
+    if (typeof url === 'string') {
+      this.url = url;
+      document.title = decodeURIComponent(getFileName(url)) || url;
+      parameters.url = url;
+    } else if (isArrayBuffer(url)) {
+      parameters.data = url;
+    }
 
     if (!PDFView.loadingBar) {
       PDFView.loadingBar = new ProgressBar('#loadingBar', {});
@@ -342,12 +347,23 @@ var PDFView = {
 
     var self = this;
     self.loading = true;
-    PDFJS.getDocument(url).then(
+    PDFJS.getDocument(parameters).then(
       function getDocumentCallback(pdfDocument) {
         self.load(pdfDocument, scale);
         self.loading = false;
       },
       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');
         loadingIndicator.textContent = mozL10n.get('loading_error_indicator',
           null, 'Error');
@@ -1530,10 +1546,7 @@ window.addEventListener('change', function webViewerChange(evt) {
     for (var i = 0; i < data.length; i++)
       uint8Array[i] = data.charCodeAt(i);
 
-    // TODO using blob instead?
-    PDFJS.getDocument(uint8Array).then(function(pdfDocument) {
-      PDFView.load(pdfDocument);
-    });
+    PDFView.open(uint8Array, 0);
   };
 
   // Read as a binary string since "readAsArrayBuffer" is not yet