From d56cc9c5e82c431306a6f72d0f32d539e6e54e66 Mon Sep 17 00:00:00 2001
From: Saebekassebil <saebekassebil@gmail.com>
Date: Mon, 18 Jul 2011 12:09:26 +0200
Subject: [PATCH 1/6] Updating PartialEvaluator.eval syntax for better
 performance

---
 pdf.js | 41 +++++++++++++----------------------------
 1 file changed, 13 insertions(+), 28 deletions(-)

diff --git a/pdf.js b/pdf.js
index 05330db84..5e2aa87b1 100644
--- a/pdf.js
+++ b/pdf.js
@@ -3561,23 +3561,9 @@ var PartialEvaluator = (function() {
     eval: function(stream, xref, resources, fonts) {
       resources = xref.fetchIfRef(resources) || new Dict();
       var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict();
-
       var parser = new Parser(new Lexer(stream), false);
-      var objpool = [];
-
-      function emitArg(arg) {
-        if (typeof arg == 'object' || typeof arg == 'string') {
-          var index = objpool.length;
-          objpool[index] = arg;
-          return 'objpool[' + index + ']';
-        }
-        return arg;
-      }
-
-      var src = '';
-
-      var args = [];
-      var obj;
+      var args = [], argsArray = [], fnArray = [], obj;
+      
       while (!IsEOF(obj = parser.getObj())) {
         if (IsCmd(obj)) {
           var cmd = obj.cmd;
@@ -3600,9 +3586,9 @@ var PartialEvaluator = (function() {
 
               if ('Form' == type.name) {
                 args[0].code = this.eval(xobj,
-                                         xref,
-                                         xobj.dict.get('Resources'),
-                                         fonts);
+                                            xref,
+                                            xobj.dict.get('Resources'),
+                                            fonts);
               }
             }
           } else if (cmd == 'Tf') { // eagerly collect all fonts
@@ -3622,21 +3608,20 @@ var PartialEvaluator = (function() {
             }
           }
 
-          src += 'this.';
-          src += fn;
-          src += '(';
-          src += args.map(emitArg).join(',');
-          src += ');\n';
-
-          args.length = 0;
+          fnArray.push(fn);
+          argsArray.push(args);
+          args = [];
         } else {
           assertWellFormed(args.length <= 33, 'Too many arguments');
           args.push(obj);
         }
       }
 
-      var fn = Function('objpool', src);
-      return function(gfx) { fn.call(gfx, objpool); };
+      return function(gfx) {
+        for(var i = 0, length = argsArray.length; i < length; i++) {
+          gfx[fnArray[i]].apply(gfx, argsArray[i]);
+        }
+      }
     },
 
     translateFont: function(fontDict, xref, resources) {

From aedd5057c4ae9849140b07ccf0896c75a51ffebe Mon Sep 17 00:00:00 2001
From: Saebekassebil <saebekassebil@gmail.com>
Date: Mon, 18 Jul 2011 15:01:12 +0200
Subject: [PATCH 2/6] Updated syntax

---
 pdf.js | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/pdf.js b/pdf.js
index 5e2aa87b1..64e830f5d 100644
--- a/pdf.js
+++ b/pdf.js
@@ -3585,10 +3585,7 @@ var PartialEvaluator = (function() {
               );
 
               if ('Form' == type.name) {
-                args[0].code = this.eval(xobj,
-                                            xref,
-                                            xobj.dict.get('Resources'),
-                                            fonts);
+                args[0].code = this.eval(xobj, xref, xobj.dict.get('Resources'), fonts);
               }
             }
           } else if (cmd == 'Tf') { // eagerly collect all fonts
@@ -3618,9 +3615,8 @@ var PartialEvaluator = (function() {
       }
 
       return function(gfx) {
-        for(var i = 0, length = argsArray.length; i < length; i++) {
+        for(var i = 0, length = argsArray.length; i < length; i++)
           gfx[fnArray[i]].apply(gfx, argsArray[i]);
-        }
       }
     },
 

From 8d09c0d7a4a1b7815882e99393e7c9a891743992 Mon Sep 17 00:00:00 2001
From: sbarman <sbarman@L3CWZ5T.(none)>
Date: Wed, 20 Jul 2011 16:42:51 -0700
Subject: [PATCH 3/6] removed Fonts class

---
 fonts.js | 74 +++++++++++++++++++++++---------------------------------
 pdf.js   |  9 ++++---
 2 files changed, 36 insertions(+), 47 deletions(-)

diff --git a/fonts.js b/fonts.js
index 607e2aab9..94b93b0da 100755
--- a/fonts.js
+++ b/fonts.js
@@ -21,17 +21,13 @@ var kMaxWaitForFontFace = 1000;
  *      http://cgit.freedesktop.org/poppler/poppler/tree/poppler/GfxFont.cc#n65
  */
 
-var Fonts = (function Fonts() {
+var FontMeasure = (function FontMeasure() {
   var kScalePrecision = 40;
-  var fonts = [];
 
-  if (!isWorker) {
-    var ctx = document.createElement('canvas').getContext('2d');
-    ctx.scale(1 / kScalePrecision, 1);
-  }
+  var ctx = document.createElement('canvas').getContext('2d');
+  ctx.scale(1 / kScalePrecision, 1);
 
-  var fontCount = 0;
-  
+  /*
   function FontInfo(name, data, properties) {
     this.name = name;
     this.data = data;
@@ -40,28 +36,14 @@ var Fonts = (function Fonts() {
     this.loading = true;
     this.sizes = [];
   }
+*/
 
   var current;
   var measureCache;
 
   return {
-    registerFont: function fonts_registerFont(fontName, data, properties) {
-      var font = new FontInfo(fontName, data, properties);
-      fonts.push(font);
-      return font.id;
-    },
-    blacklistFont: function fonts_blacklistFont(fontName) {
-      var id = registerFont(fontName, null, {});
-      markLoaded(fontName);
-      return id;
-    },
-    lookupById: function fonts_lookupById(id) {
-      return fonts[id];
-    },
-    setActive: function fonts_setActive(fontName, fontObj, size) {
-      // |current| can be null is fontName is a built-in font
-      // (e.g. "sans-serif")
-      if (fontObj && (current = fonts[fontObj.id])) {
+    setActive: function fonts_setActive(font, size) {
+      if (current = font) {
         var sizes = current.sizes;
         if (!(measureCache = sizes[size]))
           measureCache = sizes[size] = Object.create(null);
@@ -69,10 +51,12 @@ var Fonts = (function Fonts() {
         measureCache = null
       }
 
-      ctx.font = (size * kScalePrecision) + 'px "' + fontName + '"';
+      ctx.font = (size * kScalePrecision) + 'px "' + font.loadedName + '"';
     },
     measureText: function fonts_measureText(text) {
       var width;
+      // something is wrong with the caching, it is messing up page 12 in
+      // the tracemonkey paper
       if (measureCache && (width = measureCache[text]))
         return width;
       width = ctx.measureText(text).width / kScalePrecision;
@@ -88,9 +72,9 @@ var FontLoader = {
 
   bind: function(fonts, callback) {
     function checkFontsLoaded() {
-      for (var i = 0; i < allIds.length; i++) {
-        var id = allIds[i];
-        if (Fonts.lookupById(id).loading) {
+      for (var i = 0; i < objs.length; i++) {
+        var fontObj = objs[i];
+        if (fontObj.loading) {
           return false;
         }
       }
@@ -102,18 +86,17 @@ var FontLoader = {
       return true;
     }
 
-    var allIds = [];
-    var rules = [], names = [], ids = [];
+    var rules = [], names = [], objs = [];
 
     for (var i = 0; i < fonts.length; i++) {
       var font = fonts[i];
 
       var obj = new Font(font.name, font.file, font.properties);
-      font.fontDict.fontObj = obj;
-      allIds.push(obj.id);
+      obj.loading = true;
+      objs.push(obj);
 
       var str = '';
-      var data = Fonts.lookupById(obj.id).data;
+      var data = obj.data;
       var length = data.length;
       for (var j = 0; j < length; j++)
         str += String.fromCharCode(data[j]);
@@ -122,13 +105,12 @@ var FontLoader = {
       if (rule) {
         rules.push(rule);
         names.push(obj.loadedName);
-        ids.push(obj.id);
       }
     }
 
     this.listeningForFontLoad = false;
     if (!isWorker && rules.length) {
-      FontLoader.prepareFontLoadEvent(rules, names, ids);
+      FontLoader.prepareFontLoadEvent(rules, names, objs);
     }
     
     if (!checkFontsLoaded()) {
@@ -136,14 +118,14 @@ var FontLoader = {
         'pdfjsFontLoad', checkFontsLoaded, false);
     }
 
-    return;
+    return objs;
   },
   // Set things up so that at least one pdfjsFontLoad event is
   // dispatched when all the @font-face |rules| for |names| have been
   // loaded in a subdocument.  It's expected that the load of |rules|
   // has already started in this (outer) document, so that they should
   // be ordered before the load in the subdocument.
-  prepareFontLoadEvent: function(rules, names, ids) {
+  prepareFontLoadEvent: function(rules, names, objs) {
       /** Hack begin */
       // There's no event when a font has finished downloading so the
       // following code is a dirty hack to 'guess' when a font is
@@ -184,8 +166,8 @@ var FontLoader = {
           'message',
           function(e) {
             var fontNames = JSON.parse(e.data);
-            for (var i = 0; i < fontNames.length; ++i) {
-              var font = Fonts.lookupById(fontNames[i].substring(7) | 0);
+            for (var i = 0; i < objs.length; ++i) {
+              var font = objs[i];
               font.loading = false;
             }
             var evt = document.createEvent('Events');
@@ -381,8 +363,7 @@ var Font = (function() {
     // If the font is to be ignored, register it like an already loaded font
     // to avoid the cost of waiting for it be be loaded by the platform.
     if (properties.ignore) {
-      this.id = Fonts.blacklistFont(name);
-      this.loadedName = 'pdfFont' + this.id;
+      this.loadedName = 'Arial';
       return;
     }
 
@@ -412,11 +393,15 @@ var Font = (function() {
     }
     this.data = data;
     this.type = properties.type;
-    this.id = Fonts.registerFont(name, data, properties);
-    this.loadedName = 'pdfFont' + this.id;
+    this.loadedName = getUniqueName();
     this.compositeFont = properties.compositeFont;
   };
 
+  var numFonts = 0;
+  function getUniqueName() {
+    return 'pdfFont' + numFonts++;
+  }
+
   function stringToArray(str) {
     var array = [];
     for (var i = 0; i < str.length; ++i)
@@ -746,6 +731,7 @@ var Font = (function() {
     font: null,
     mimetype: null,
     encoding: null,
+    sizes: [],
 
     checkAndRepair: function font_checkAndRepair(name, font, properties) {
       function readTableEntry(file) {
diff --git a/pdf.js b/pdf.js
index 64e830f5d..51426dce7 100644
--- a/pdf.js
+++ b/pdf.js
@@ -2982,7 +2982,7 @@ var Page = (function() {
       this.compile(gfx, fonts);
       stats.compile = Date.now();
 
-      FontLoader.bind(
+      var fontObjs = FontLoader.bind(
         fonts,
         function() {
           stats.fonts = Date.now();
@@ -2999,6 +2999,9 @@ var Page = (function() {
             continuation(exc);
           });
         });
+
+      for (var i = 0, ii = fonts.length; i < ii; ++i)
+        fonts[i].fontDict.fontObj = fontObjs[i];
     },
 
 
@@ -4093,7 +4096,7 @@ var CanvasGraphics = (function() {
         this.ctx.$setFont(fontName, size);
       } else {
         this.ctx.font = size + 'px "' + fontName + '"';
-        Fonts.setActive(fontName, fontObj, size);
+        FontMeasure.setActive(fontObj, size);
       }
     },
     setTextRenderingMode: function(mode) {
@@ -4145,7 +4148,7 @@ var CanvasGraphics = (function() {
           text = font.charsToUnicode(text);
         }
         ctx.fillText(text, 0, 0);
-        current.x += Fonts.measureText(text);
+        current.x += FontMeasure.measureText(text);
       }
 
       this.ctx.restore();

From daf4979e2f09ea58f670f596d843667a2eaa72f4 Mon Sep 17 00:00:00 2001
From: sbarman <sbarman@L3CWZ5T.(none)>
Date: Wed, 20 Jul 2011 17:10:04 -0700
Subject: [PATCH 4/6] cleanup

---
 fonts.js | 15 +--------------
 1 file changed, 1 insertion(+), 14 deletions(-)

diff --git a/fonts.js b/fonts.js
index 94b93b0da..f33cab9c3 100755
--- a/fonts.js
+++ b/fonts.js
@@ -27,17 +27,6 @@ var FontMeasure = (function FontMeasure() {
   var ctx = document.createElement('canvas').getContext('2d');
   ctx.scale(1 / kScalePrecision, 1);
 
-  /*
-  function FontInfo(name, data, properties) {
-    this.name = name;
-    this.data = data;
-    this.properties = properties;
-    this.id = fontCount++;
-    this.loading = true;
-    this.sizes = [];
-  }
-*/
-
   var current;
   var measureCache;
 
@@ -55,8 +44,6 @@ var FontMeasure = (function FontMeasure() {
     },
     measureText: function fonts_measureText(text) {
       var width;
-      // something is wrong with the caching, it is messing up page 12 in
-      // the tracemonkey paper
       if (measureCache && (width = measureCache[text]))
         return width;
       width = ctx.measureText(text).width / kScalePrecision;
@@ -359,6 +346,7 @@ var Font = (function() {
     this.name = name;
     this.textMatrix = properties.textMatrix || IDENTITY_MATRIX;
     this.encoding = properties.encoding;
+    this.sizes = [];
 
     // If the font is to be ignored, register it like an already loaded font
     // to avoid the cost of waiting for it be be loaded by the platform.
@@ -731,7 +719,6 @@ var Font = (function() {
     font: null,
     mimetype: null,
     encoding: null,
-    sizes: [],
 
     checkAndRepair: function font_checkAndRepair(name, font, properties) {
       function readTableEntry(file) {

From e1e735c4ff1a139ada263b2a06da74f63c61c78f Mon Sep 17 00:00:00 2001
From: notmasteryet <async.processingjs@yahoo.com>
Date: Wed, 20 Jul 2011 23:08:08 -0500
Subject: [PATCH 5/6] AES algorithm implementation, PDF1.5,1.6 crypto
 functionality

---
 crypto.js | 331 ++++++++++++++++++++++++++++++++++++++++++++++++++----
 pdf.js    |   1 -
 2 files changed, 312 insertions(+), 20 deletions(-)

diff --git a/crypto.js b/crypto.js
index c60590777..98f7bb4a1 100644
--- a/crypto.js
+++ b/crypto.js
@@ -40,6 +40,7 @@ var ARCFourCipher = (function() {
       return output;
     }
   };
+  constructor.prototype.decryptBlock = constructor.prototype.encryptBlock;
 
   return constructor;
 })();
@@ -128,6 +129,245 @@ var md5 = (function() {
   return hash;
 })();
 
+var NullCipher = (function() {
+  function constructor() {
+  }
+
+  constructor.prototype = {
+    decryptBlock: function(data) {
+      return data;
+    }
+  };
+
+  return constructor;
+})();
+
+var AES128Cipher = (function() {
+  var rcon = new Uint8Array([
+    0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
+    0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
+    0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
+    0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
+    0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
+    0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
+    0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
+    0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
+    0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
+    0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
+    0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
+    0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
+    0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
+    0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
+    0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
+    0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d]);
+
+  var s = new Uint8Array([
+    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
+    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
+    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
+    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
+    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
+    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
+    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
+    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
+    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
+    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
+    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
+    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
+    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
+    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
+    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
+    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16]);
+
+  var inv_s = new Uint8Array([
+    0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
+    0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
+    0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
+    0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
+    0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
+    0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
+    0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
+    0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
+    0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
+    0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
+    0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
+    0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
+    0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
+    0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
+    0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
+    0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D]);
+
+  var mix = new Uint32Array([
+    0x0, 0xe090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a,
+    0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74,
+    0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6,
+    0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8,
+    0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2,
+    0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57,
+    0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935,
+    0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb,
+    0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x38f5fe7, 0xd8652ec, 0x1f9d45f1,
+    0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf,
+    0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0,
+    0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae,
+    0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94,
+    0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a,
+    0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08,
+    0x2fb5c203, 0x58ae132, 0xb83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d,
+    0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7,
+    0x5c72f5bc, 0x605bed5, 0x80cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9,
+    0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b,
+    0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65,
+    0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f,
+    0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb,
+    0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9,
+    0x23bfa5b2, 0x9808683, 0x7898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47,
+    0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d,
+    0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633,
+    0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a,
+    0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4,
+    0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee,
+    0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510,
+    0x4c6a881b, 0x5e719f06, 0x5078920d, 0xa0fd964, 0x406d46f, 0x161dc372,
+    0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701,
+    0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b,
+    0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75,
+    0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0xc0a67b1, 0x2036aba, 0x10187da7,
+    0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9,
+    0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3,
+    0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0xf853856,
+    0x18c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34,
+    0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca,
+    0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0,
+    0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be,
+    0x91548db5, 0x834f9aa8, 0x8d4697a3]);
+
+  function expandKey128(cipherKey) {
+    var b = 176, result = new Uint8Array(b);
+    result.set(cipherKey);
+    for (var j = 16, i = 1; j < b; ++i) {
+      // RotWord
+      var t1 = result[j - 3], t2 = result[j - 2], t3 = result[j - 1], t4 = result[j - 4];
+      // SubWord
+      t1 = s[t1]; t2 = s[t2]; t3 = s[t3]; t4 = s[t4];
+      // Rcon
+      t1 = t1 ^ rcon[i];
+      for (var n = 0; n < 4; ++n) {
+        result[j] = (t1 ^= result[j - 16]); j++;
+        result[j] = (t2 ^= result[j - 16]); j++;
+        result[j] = (t3 ^= result[j - 16]); j++;
+        result[j] = (t4 ^= result[j - 16]); j++;
+      }
+    }
+    return result;
+  }
+
+  function decrypt128(input, key) {
+    var state = new Uint8Array(16);
+    state.set(input);
+    var i, j, k;
+    var t, u, v;
+    // AddRoundKey
+    for (j = 0, k = 160; j < 16; ++j, ++k)
+      state[j] ^= key[k];
+    for (i = 9; i >= 1; --i) {
+      // InvShiftRows
+      t = state[13]; state[13] = state[9]; state[9] = state[5]; state[5] = state[1]; state[1] = t;
+      t = state[14]; u = state[10]; state[14] = state[6]; state[10] = state[2]; state[6] = t; state[2] = u;
+      t = state[15]; u = state[11]; v = state[7]; state[15] = state[3]; state[11] = t; state[7] = u; state[3] = v;
+      // InvSubBytes
+      for (j = 0; j < 16; ++j)
+        state[j] = inv_s[state[j]];
+      // AddRoundKey
+      for (j = 0, k = i * 16; j < 16; ++j, ++k)
+        state[j] ^= key[k];
+      // InvMixColumns
+      for (j = 0; j < 16; j += 4) {
+        var s0 = mix[state[j]], s1 = mix[state[j + 1]], s2 = mix[state[j + 2]], s3 = mix[state[j + 3]];
+        t = s0 ^ (s1 >>> 8) ^ (s1 << 24) ^ (s2 >>> 16) ^ (s2 << 16) ^ (s3 >>> 24) ^ (s3 << 8);
+        state[j] = (t >>> 24) & 0xFF;
+        state[j + 1] = (t >> 16) & 0xFF;
+        state[j + 2] = (t >> 8) & 0xFF;
+        state[j + 3] = t & 0xFF;
+      }
+    }
+    // InvShiftRows
+    t = state[13]; state[13] = state[9]; state[9] = state[5]; state[5] = state[1]; state[1] = t;
+    t = state[14]; u = state[10]; state[14] = state[6]; state[10] = state[2]; state[6] = t; state[2] = u;
+    t = state[15]; u = state[11]; v = state[7]; state[15] = state[3]; state[11] = t; state[7] = u; state[3] = v;
+    // InvSubBytes
+    for (j = 0; j < 16; ++j)
+      state[j] = inv_s[state[j]];
+    // AddRoundKey
+    for (j = 0; j < 16; ++j)
+      state[j] ^= key[j];
+    return state;
+  }
+
+  function constructor(key) {
+    this.key = expandKey128(key);
+    this.buffer = new Uint8Array(16);
+    this.bufferPosition = 0;
+  }
+
+  function decryptBlock2(data) {
+    var i, j, sourceLength = data.length;
+    var buffer = this.buffer, bufferLength = this.bufferPosition, iv = this.iv;
+    var result = [];
+    for (i = 0; i < sourceLength; ++i) {
+      buffer[bufferLength] = data[i];
+      ++bufferLength;
+      if (bufferLength < 16)
+        continue;
+      // buffer is full, decrypting
+      var plain = decrypt128(buffer, this.key);
+      // xor-ing the IV vector to get plain text
+      for (j = 0; j < 16; ++j)
+        plain[j] ^= iv[j];
+      iv = buffer;
+      result.push(plain);
+      buffer = new Uint8Array(16);
+      bufferLength = 0;
+    }
+    // saving incomplete buffer
+    this.buffer = buffer;
+    this.bufferLength = bufferLength;
+    this.iv = iv;
+    if (result.length == 0)
+      return new Uint8Array([]);
+    if (result.length == 1)
+      return result[0];
+    // combining plain text blocks into one
+    var output = new Uint8Array(16 * result.length);
+    for (i = 0, j = 0; i < result.length; ++i, j += 16)
+      output.set(result[i], j);
+    return output;
+  }
+
+  constructor.prototype = {
+    decryptBlock: function(data) {
+      var i, sourceLength = data.length;
+      var buffer = this.buffer, bufferLength = this.bufferPosition;
+      // waiting for IV values -- they are at the start of the stream
+      for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength)
+        buffer[bufferLength] = data[i];
+      if (bufferLength < 16) {
+        // need more data
+        this.bufferLength = bufferLength;
+        return new Uint8Array([]);
+      }
+      this.iv = buffer;
+      this.buffer = new Uint8Array(16);
+      this.bufferLength = 0;
+      // starting decryption
+      this.decryptBlock = decryptBlock2;
+      return this.decryptBlock(data.subarray(16));
+    }
+  };
+
+  return constructor;
+})();
+
 var CipherTransform = (function() {
   function constructor(stringCipherConstructor, streamCipherConstructor) {
     this.stringCipherConstructor = stringCipherConstructor;
@@ -137,13 +377,13 @@ var CipherTransform = (function() {
     createStream: function(stream) {
       var cipher = new this.streamCipherConstructor();
       return new DecryptStream(stream, function(data) {
-        return cipher.encryptBlock(data);
+        return cipher.decryptBlock(data);
       });
     },
     decryptString: function(s) {
       var cipher = new this.stringCipherConstructor();
       var data = stringToBytes(s);
-      data = cipher.encryptBlock(data);
+      data = cipher.decryptBlock(data);
       return bytesToString(data);
     }
   };
@@ -152,13 +392,13 @@ var CipherTransform = (function() {
 
 var CipherTransformFactory = (function() {
   function prepareKeyData(fileId, password, ownerPassword, userPassword,
-                          flags, revision, keyLength) {
+                          flags, revision, keyLength, encryptMetadata) {
     var defaultPasswordBytes = new Uint8Array([
       0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
       0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
       0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
       0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
-    var hashData = new Uint8Array(88), i = 0, j, n;
+    var hashData = new Uint8Array(100), i = 0, j, n;
     if (password) {
       n = Math.min(32, password.length);
       for (; i < n; ++i)
@@ -177,7 +417,12 @@ var CipherTransformFactory = (function() {
     hashData[i++] = (flags >>> 24) & 0xFF;
     for (j = 0, n = fileId.length; j < n; ++j)
       hashData[i++] = fileId[j];
-    // TODO rev 4, if metadata is not encrypted pass 0xFFFFFF also
+    if (revision >= 4 && !encryptMetadata) {
+      hashData[i++] = 0xFF;
+      hashData[i++] = 0xFF;
+      hashData[i++] = 0xFF;
+      hashData[i++] = 0xFF;
+    }
     var hash = md5(hashData, 0, i);
     var keyLengthInBytes = keyLength >> 3;
     if (revision >= 3) {
@@ -215,6 +460,8 @@ var CipherTransformFactory = (function() {
     return encryptionKey;
   }
 
+  var identityName = new Name('Identity');
+
   function constructor(dict, fileId, password) {
     var filter = dict.get('Filter');
     if (!IsName(filter) || filter.name != 'Standard')
@@ -222,9 +469,9 @@ var CipherTransformFactory = (function() {
     this.dict = dict;
     var algorithm = dict.get('V');
     if (!IsInt(algorithm) ||
-      (algorithm != 1 && algorithm != 2))
+      (algorithm != 1 && algorithm != 2 && algorithm != 4))
       error('unsupported encryption algorithm');
-    // TODO support algorithm 4
+    this.algorithm = algorithm;
     var keyLength = dict.get('Length') || 40;
     if (!IsInt(keyLength) ||
       keyLength < 40 || (keyLength % 8) != 0)
@@ -234,6 +481,7 @@ var CipherTransformFactory = (function() {
     var userPassword = stringToBytes(dict.get('U'));
     var flags = dict.get('P');
     var revision = dict.get('R');
+    var encryptMetadata = dict.get('EncryptMetadata') !== false; // makes true as default value
     var fileIdBytes = stringToBytes(fileId);
     var passwordBytes;
     if (password)
@@ -241,22 +489,67 @@ var CipherTransformFactory = (function() {
 
     this.encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
                                         ownerPassword, userPassword,
-                                        flags, revision, keyLength);
+                                        flags, revision, keyLength, encryptMetadata);
+    if (algorithm == 4) {
+      this.cf = dict.get('CF');
+      this.stmf = dict.get('StmF') || identityName;
+      this.strf = dict.get('StrF') || identityName;
+      this.eff = dict.get('EFF') || this.strf;
+    }
+  }
+
+  function buildObjectKey(num, gen, encryptionKey, isAes) {
+    var key = new Uint8Array(encryptionKey.length + 9), i, n;
+    for (i = 0, n = encryptionKey.length; i < n; ++i)
+      key[i] = encryptionKey[i];
+    key[i++] = num & 0xFF;
+    key[i++] = (num >> 8) & 0xFF;
+    key[i++] = (num >> 16) & 0xFF;
+    key[i++] = gen & 0xFF;
+    key[i++] = (gen >> 8) & 0xFF;
+    if (isAes) {
+      key[i++] = 0x73;
+      key[i++] = 0x41;
+      key[i++] = 0x6C;
+      key[i++] = 0x54;
+    }
+    var hash = md5(key, 0, i);
+    return hash.subarray(0, Math.min(key.length, 16));
+  }
+
+  function buildCipherConstructor(cf, name, num, gen, key) {
+    var cryptFilter = cf.get(name.name);
+    var cfm;
+    if (cryptFilter != null)
+      cfm = cryptFilter.get('CFM');
+    if (!cfm || cfm.name == 'None') {
+      return function() {
+        return new NullCipher();
+      };
+    }
+    if ('V2' == cfm.name) {
+      return function() {
+        return new ARCFourCipher(
+          buildObjectKey(num, gen, key, false));
+      };
+    }
+    if ('AESV2' == cfm.name) {
+      return function() {
+        return new AES128Cipher(
+          buildObjectKey(num, gen, key, true));
+      };
+    }
   }
 
   constructor.prototype = {
     createCipherTransform: function(num, gen) {
-      var encryptionKey = this.encryptionKey;
-      var key = new Uint8Array(encryptionKey.length + 5), i, n;
-      for (i = 0, n = encryptionKey.length; i < n; ++i)
-        key[i] = encryptionKey[i];
-      key[i++] = num & 0xFF;
-      key[i++] = (num >> 8) & 0xFF;
-      key[i++] = (num >> 16) & 0xFF;
-      key[i++] = gen & 0xFF;
-      key[i++] = (gen >> 8) & 0xFF;
-      var hash = md5(key, 0, i);
-      key = hash.subarray(0, Math.min(key.length, 16));
+      if (this.algorithm == 4) {
+        return new CipherTransform(
+          buildCipherConstructor(this.cf, this.stmf, num, gen, this.encryptionKey),
+          buildCipherConstructor(this.cf, this.strf, num, gen, this.encryptionKey));
+      }
+      // algorithms 1 and 2
+      var key = buildObjectKey(num, gen, this.encryptionKey, false);
       var cipherConstructor = function() {
         return new ARCFourCipher(key);
       };
diff --git a/pdf.js b/pdf.js
index a3179406c..df8e3f3cc 100644
--- a/pdf.js
+++ b/pdf.js
@@ -829,7 +829,6 @@ var DecryptStream = (function() {
     for (i = 0; i < n; i++)
       buffer[bufferLength++] = chunk[i];
     this.bufferLength = bufferLength;
-    this.eof = n < chunkSize;
   };
 
   return constructor;

From e9df0bb8caa7484eb4eff81051f3e0294dc068e2 Mon Sep 17 00:00:00 2001
From: notmasteryet <async.processingjs@yahoo.com>
Date: Wed, 20 Jul 2011 23:17:31 -0500
Subject: [PATCH 6/6] link to AESV2 encrypted file

---
 test/pdfs/i9.pdf.link   | 1 +
 test/test_manifest.json | 6 ++++++
 2 files changed, 7 insertions(+)
 create mode 100644 test/pdfs/i9.pdf.link

diff --git a/test/pdfs/i9.pdf.link b/test/pdfs/i9.pdf.link
new file mode 100644
index 000000000..a4a75e771
--- /dev/null
+++ b/test/pdfs/i9.pdf.link
@@ -0,0 +1 @@
+http://www.uscis.gov/files/form/i-9.pdf
diff --git a/test/test_manifest.json b/test/test_manifest.json
index 6c1ff71cd..c5c196397 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -63,5 +63,11 @@
        "file": "pdfs/ArabicCIDTrueType.pdf",
        "rounds": 1,
        "type": "load"
+    },
+    {  "id": "i9-pdf",
+       "file": "pdfs/i9.pdf",
+       "link": true,
+       "rounds": 1,
+       "type": "eq"
     }
 ]