[poppler] 19 commits - CMakeLists.txt config.h.cmake configure.ac ConfigureChecks.cmake goo/grandom.cc goo/grandom.h goo/Makefile.am poppler/Decrypt.cc poppler/Decrypt.h poppler/PDFDoc.cc poppler/PDFDoc.h poppler/Stream.cc poppler/Stream.h poppler/XRef.cc poppler/XRef.h splash/SplashScreen.cc test/CMakeLists.txt test/Makefile.am test/pdf-fullrewrite.cc utils/pdfinfo.cc utils/pdfunite.cc

Albert Astals Cid aacid at kemper.freedesktop.org
Thu Sep 6 13:15:38 PDT 2012


 CMakeLists.txt          |    2 
 ConfigureChecks.cmake   |    1 
 config.h.cmake          |    3 
 configure.ac            |    1 
 goo/Makefile.am         |    6 
 goo/grandom.cc          |   70 ++++++++
 goo/grandom.h           |   34 +++
 poppler/Decrypt.cc      |  415 +++++++++++++++++++++++++++++++++++++-----------
 poppler/Decrypt.h       |   56 +++++-
 poppler/PDFDoc.cc       |  179 ++++++++++++++------
 poppler/PDFDoc.h        |   27 ++-
 poppler/Stream.cc       |   14 +
 poppler/Stream.h        |    2 
 poppler/XRef.cc         |  233 ++++++++++++++++++++------
 poppler/XRef.h          |   43 ++++
 splash/SplashScreen.cc  |    8 
 test/CMakeLists.txt     |    1 
 test/Makefile.am        |    3 
 test/pdf-fullrewrite.cc |  354 +++++++++++++++++++++++++++++++++++++++-
 utils/pdfinfo.cc        |   25 ++
 utils/pdfunite.cc       |    2 
 21 files changed, 1224 insertions(+), 255 deletions(-)

New commits:
commit 2c41430732f517d4d57e914a315ba315a2545541
Author: Albert Astals Cid <aacid at kde.org>
Date:   Thu Sep 6 22:12:38 2012 +0200

    Add missing licenses

diff --git a/goo/grandom.cc b/goo/grandom.cc
index bafa4b6..1237175 100644
--- a/goo/grandom.cc
+++ b/goo/grandom.cc
@@ -1,6 +1,8 @@
 /*
  * grandom.cc
  *
+ * This file is licensed under the GPLv2 or later
+ *
  * Pseudo-random number generation
  *
  * Copyright (C) 2012 Fabio D'Urso <fabiodurso at hotmail.it>
diff --git a/goo/grandom.h b/goo/grandom.h
index 763e8e0..45fa791 100644
--- a/goo/grandom.h
+++ b/goo/grandom.h
@@ -1,6 +1,8 @@
 /*
  * grandom.h
  *
+ * This file is licensed under the GPLv2 or later
+ *
  * Pseudo-random number generation
  *
  * Copyright (C) 2012 Fabio D'Urso <fabiodurso at hotmail.it>
diff --git a/splash/SplashScreen.cc b/splash/SplashScreen.cc
index 1ae28d4..68ccd7d 100644
--- a/splash/SplashScreen.cc
+++ b/splash/SplashScreen.cc
@@ -12,6 +12,7 @@
 // under GPL version 2 or later
 //
 // Copyright (C) 2009 Albert Astals Cid <aacid at kde.org>
+// Copyright (C) 2012 Fabio D'Urso <fabiodurso at hotmail.it>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
diff --git a/utils/pdfinfo.cc b/utils/pdfinfo.cc
index 928d661..799a35c 100644
--- a/utils/pdfinfo.cc
+++ b/utils/pdfinfo.cc
@@ -18,6 +18,7 @@
 // Copyright (C) 2010 Hib Eris <hib at hiberis.nl>
 // Copyright (C) 2011 Vittal Aithal <vittal.aithal at cognidox.com>
 // Copyright (C) 2012 Adrian Johnson <ajohnson at redneon.com>
+// Copyright (C) 2012 Fabio D'Urso <fabiodurso at hotmail.it>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
commit be88963a5955ac033e7a7d224bdcc4049085a9dc
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Thu Aug 9 13:18:22 2012 +0200

    pdf-fullrewrite: Added support for encrypted documents, checks on output documents, incremental update mode

diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 178b15e..a89a4cf 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -44,6 +44,7 @@ endif (GTK_FOUND)
 
 set (pdf_fullrewrite_SRCS
   pdf-fullrewrite.cc
+  ../utils/parseargs.cc
 )
 add_executable(pdf-fullrewrite ${pdf_fullrewrite_SRCS})
 target_link_libraries(pdf-fullrewrite poppler)
diff --git a/test/Makefile.am b/test/Makefile.am
index 619d671..b3289c7 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -76,7 +76,8 @@ perf_test_LDADD =				\
 	$(X_EXTRA_LIBS)
 
 pdf_fullrewrite_SOURCES = \
-	pdf-fullrewrite.cc
+	pdf-fullrewrite.cc		\
+	../utils/parseargs.cc
 
 pdf_fullrewrite_LDADD = \
 	$(top_builddir)/poppler/libpoppler.la
diff --git a/test/pdf-fullrewrite.cc b/test/pdf-fullrewrite.cc
index 9658e55..2b912f7 100644
--- a/test/pdf-fullrewrite.cc
+++ b/test/pdf-fullrewrite.cc
@@ -3,44 +3,368 @@
 // pdf-fullrewrite.cc
 //
 // Copyright 2007 Julien Rebetez
+// Copyright 2012 Fabio D'Urso
 //
 //========================================================================
-#include "config.h"
-#include <poppler-config.h>
+
 #include "GlobalParams.h"
 #include "Error.h"
+#include "Object.h"
 #include "PDFDoc.h"
+#include "XRef.h"
 #include "goo/GooString.h"
+#include "utils/parseargs.h"
+
+static GBool compareDocuments(PDFDoc *origDoc, PDFDoc *newDoc);
+static GBool compareObjects(Object *objA, Object *objB);
+
+static char ownerPassword[33] = "\001";
+static char userPassword[33] = "\001";
+static GBool forceIncremental = gFalse;
+static GBool checkOutput = gFalse;
+static GBool printHelp = gFalse;
+
+static const ArgDesc argDesc[] = {
+  {"-opw",    argString,   ownerPassword,    sizeof(ownerPassword),
+   "owner password (for encrypted files)"},
+  {"-upw",    argString,   userPassword,     sizeof(userPassword),
+   "user password (for encrypted files)"},
+  {"-i",      argFlag,     &forceIncremental,0,
+   "incremental update mode"},
+  {"-check",  argFlag,     &checkOutput,     0,
+   "verify the generated document"},
+  {"-h",      argFlag,     &printHelp,       0,
+   "print usage information"},
+  {"-help",   argFlag,     &printHelp,       0,
+   "print usage information"},
+  {"--help",  argFlag,     &printHelp,       0,
+   "print usage information"},
+  {"-?",      argFlag,     &printHelp,       0,
+   "print usage information"},
+  {NULL}
+};
 
 int main (int argc, char *argv[])
 {
-  PDFDoc *doc;
-  GooString *inputName, *outputName;
+  PDFDoc *doc = NULL;
+  PDFDoc *docOut = NULL;
+  GooString *inputName = NULL;
+  GooString *outputName = NULL;
+  GooString *ownerPW = NULL;
+  GooString *userPW = NULL;
+  int res = 0;
 
   // parse args
-  if (argc < 3) {
-    fprintf(stderr, "usage: %s INPUT-FILE OUTPUT-FILE\n", argv[0]);
-    return 1;
+  GBool ok = parseArgs(argDesc, &argc, argv);
+  if (!ok || (argc < 3) || printHelp) {
+    printUsage(argv[0], "INPUT-FILE OUTPUT-FILE", argDesc);
+    if (!printHelp) {
+      res = 1;
+    }
+    goto done;
   }
 
   inputName = new GooString(argv[1]);
   outputName = new GooString(argv[2]);
 
-  globalParams = new GlobalParams();
-
-  doc = new PDFDoc(inputName);
+  if (ownerPassword[0] != '\001') {
+    ownerPW = new GooString(ownerPassword);
+  }
+  if (userPassword[0] != '\001') {
+    userPW = new GooString(userPassword);
+  }
 
+  // load input document
+  globalParams = new GlobalParams();
+  doc = new PDFDoc(inputName, ownerPW, userPW);
   if (!doc->isOk()) {
-    delete doc;
-    fprintf(stderr, "Error loading document !\n");
-    return 1;
+    fprintf(stderr, "Error loading input document\n");
+    res = 1;
+    goto done;
   }
 
+  // save it back (in rewrite or incremental update mode)
+  if (doc->saveAs(outputName, forceIncremental ? writeForceIncremental : writeForceRewrite) != 0) {
+    fprintf(stderr, "Error saving document\n");
+    res = 1;
+    goto done;
+  }
 
-  int res = doc->saveAs(outputName, writeForceRewrite);
+  if (checkOutput) {
+    // open the generated document to verify it
+    docOut = new PDFDoc(outputName, ownerPW, userPW);
+    if (!docOut->isOk()) {
+      fprintf(stderr, "Error loading generated document\n");
+      res = 1;
+    } else if (!compareDocuments(doc, docOut)) {
+      fprintf(stderr, "Verification failed\n");
+      res = 1;
+    }
+  } else {
+    delete outputName;
+  }
 
+done:
+  delete docOut;
   delete doc;
   delete globalParams;
-  delete outputName;
+  delete userPW;
+  delete ownerPW;
   return res;
 }
+
+static GBool compareDictionaries(Dict *dictA, Dict *dictB)
+{
+  const int length = dictA->getLength();
+  if (dictB->getLength() != length)
+    return gFalse;
+
+  /* Check that every key in dictA is contained in dictB.
+   * Since keys are unique and we've already checked that dictA and dictB
+   * contain the same number of entries, we don't need to check that every key
+   * in dictB is also contained in dictA */
+  for (int i = 0; i < length; ++i) {
+    Object valA, valB;
+    const char *key = dictA->getKey(i);
+    dictA->getValNF(i, &valA);
+    dictB->lookupNF(key, &valB);
+    if (!compareObjects(&valA, &valB))
+      return gFalse;
+    valA.free();
+    valB.free();
+  }
+
+  return gTrue;
+}
+
+static GBool compareObjects(Object *objA, Object *objB)
+{
+  switch (objA->getType()) {
+    case objBool:
+    {
+      if (objB->getType() != objBool) {
+        return gFalse;
+      } else {
+        return (objA->getBool() == objB->getBool());
+      }
+    }
+    case objInt:
+    case objReal:
+    {
+      if (!objB->isNum()) {
+        return gFalse;
+      } else {
+        // Fuzzy comparison
+        const double diff = objA->getNum() - objB->getNum();
+        return (-0.01 < diff) && (diff < 0.01);
+      }
+    }
+    case objUint:
+    {
+      if (objB->getType() != objUint) {
+        return gFalse;
+      } else {
+        return (objA->getUint() == objB->getUint());
+      }
+    }
+    case objString:
+    {
+      if (objB->getType() != objString) {
+        return gFalse;
+      } else {
+        GooString *strA = objA->getString();
+        GooString *strB = objB->getString();
+        return (strA->cmp(strB) == 0);
+      }
+    }
+    case objName:
+    {
+      if (objB->getType() != objName) {
+        return gFalse;
+      } else {
+        GooString nameA(objA->getName());
+        GooString nameB(objB->getName());
+        return (nameA.cmp(&nameB) == 0);
+      }
+    }
+    case objNull:
+    {
+      if (objB->getType() != objNull) {
+        return gFalse;
+      } else {
+        return gTrue;
+      }
+    }
+    case objArray:
+    {
+      if (objB->getType() != objArray) {
+        return gFalse;
+      } else {
+        Array *arrayA = objA->getArray();
+        Array *arrayB = objB->getArray();
+        const int length = arrayA->getLength();
+        if (arrayB->getLength() != length) {
+          return gFalse;
+        } else {
+          for (int i = 0; i < length; ++i) {
+            Object elemA, elemB;
+            arrayA->getNF(i, &elemA);
+            arrayB->getNF(i, &elemB);
+            if (!compareObjects(&elemA, &elemB)) {
+              return gFalse;
+            }
+            elemA.free();
+            elemB.free();
+          }
+          return gTrue;
+        }
+      }
+    }
+    case objDict:
+    {
+      if (objB->getType() != objDict) {
+        return gFalse;
+      } else {
+        Dict *dictA = objA->getDict();
+        Dict *dictB = objB->getDict();
+        return compareDictionaries(dictA, dictB);
+      }
+    }
+    case objStream:
+    {
+      if (objB->getType() != objStream) {
+        return gFalse;
+      } else {
+        Stream *streamA = objA->getStream();
+        Stream *streamB = objB->getStream();
+        if (!compareDictionaries(streamA->getDict(), streamB->getDict())) {
+          return gFalse;
+        } else {
+          int c;
+          streamA->reset();
+          streamB->reset();
+          do
+          {
+            c = streamA->getChar();
+            if (c != streamB->getChar()) {
+              return gFalse;
+            }
+          } while (c != EOF);
+          return gTrue;
+        }
+      }
+      return gTrue;
+    }
+    case objRef:
+    {
+      if (objB->getType() != objRef) {
+        return gFalse;
+      } else {
+        Ref refA = objA->getRef();
+        Ref refB = objB->getRef();
+        return (refA.num == refB.num) && (refA.gen == refB.gen);
+      }
+    }
+    default:
+    {
+      fprintf(stderr, "compareObjects failed: unexpected object type %u\n", objA->getType());
+      return gFalse;
+    }
+  }
+}
+
+static GBool compareDocuments(PDFDoc *origDoc, PDFDoc *newDoc)
+{
+  GBool result = gTrue;
+  XRef *origXRef = origDoc->getXRef();
+  XRef *newXRef = newDoc->getXRef();
+
+  // Make sure that special flags are set in both documents
+  origXRef->scanSpecialFlags();
+  newXRef->scanSpecialFlags();
+
+  // Compare XRef tables' size
+  const int origNumObjects = origXRef->getNumObjects();
+  const int newNumObjects = newXRef->getNumObjects();
+  if (forceIncremental && origXRef->isXRefStream()) {
+    // In case of incremental update, expect a new entry to be appended to store the new XRef stream
+    if (origNumObjects+1 != newNumObjects) {
+      fprintf(stderr, "XRef table: Unexpected number of entries (%d+1 != %d)\n", origNumObjects, newNumObjects);
+      result = gFalse;
+    }
+  } else {
+    // In all other cases the number of entries must be the same
+    if (origNumObjects != newNumObjects) {
+      fprintf(stderr, "XRef table: Different number of entries (%d != %d)\n", origNumObjects, newNumObjects);
+      result = gFalse;
+    }
+  }
+
+  // Compare each XRef entry
+  const int numObjects = (origNumObjects < newNumObjects) ? origNumObjects : newNumObjects;
+  for (int i = 0; i < numObjects; ++i) {
+    XRefEntryType origType = origXRef->getEntry(i)->type;
+    XRefEntryType newType = newXRef->getEntry(i)->type;
+    const int origGenNum = (origType != xrefEntryCompressed) ? origXRef->getEntry(i)->gen : 0;
+    const int newGenNum = (newType != xrefEntryCompressed) ? newXRef->getEntry(i)->gen : 0;
+
+    // Check that DontRewrite entries are freed in full rewrite mode
+    if (!forceIncremental && origXRef->getEntry(i)->getFlag(XRefEntry::DontRewrite)) {
+      if (newType != xrefEntryFree || origGenNum+1 != newGenNum) {
+        fprintf(stderr, "XRef entry %u: DontRewrite entry was not freed correctly\n", i);
+        result = gFalse;
+      }
+      continue; // There's nothing left to check for this entry
+    }
+
+    // Compare generation numbers
+    // Object num 0 should always have gen 65535 according to specs, but some
+    // documents have it set to 0. We always write 65535 in output
+    if (i != 0) {
+      if (origGenNum != newGenNum) {
+        fprintf(stderr, "XRef entry %u: generation numbers differ (%d != %d)\n", i, origGenNum, newGenNum);
+        result = gFalse;
+        continue;
+      }
+    } else {
+      if (newGenNum != 65535) {
+        fprintf(stderr, "XRef entry %u: generation number was expected to be 65535 (%d != 65535)\n", i, newGenNum);
+        result = gFalse;
+        continue;
+      }
+    }
+
+    // Compare object flags. A failure shows that there's some error in XRef::scanSpecialFlags()
+    if (origXRef->getEntry(i)->flags != newXRef->getEntry(i)->flags) {
+      fprintf(stderr, "XRef entry %u: flags detected by scanSpecialFlags differ (%d != %d)\n", i, origXRef->getEntry(i)->flags, newXRef->getEntry(i)->flags);
+      result = gFalse;
+    }
+
+    // Check that either both are free or both are in use
+    if ((origType == xrefEntryFree) != (newType == xrefEntryFree)) {
+      const char *origStatus = (origType == xrefEntryFree) ? "free" : "in use";
+      const char *newStatus = (newType == xrefEntryFree) ? "free" : "in use";
+      fprintf(stderr, "XRef entry %u: usage status differs (%s != %s)\n", i, origStatus, newStatus);
+      result = gFalse;
+      continue;
+    }
+
+    // Skip free entries
+    if (origType == xrefEntryFree) {
+      continue;
+    }
+
+    // Compare contents
+    Object origObj, newObj;
+    origXRef->fetch(i, origGenNum, &origObj);
+    newXRef->fetch(i, newGenNum, &newObj);
+    if (!compareObjects(&origObj, &newObj)) {
+      fprintf(stderr, "XRef entry %u: contents differ\n", i);
+      result = gFalse;
+    }
+    origObj.free();
+    newObj.free();
+  }
+
+  return result;
+}
commit 381be58e9e0d0e323acbd975a2334eca6d9018fd
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Sat Aug 4 13:00:06 2012 +0200

    pdfinfo: Show info about the encryption algorithm

diff --git a/utils/pdfinfo.cc b/utils/pdfinfo.cc
index d1c077b..928d661 100644
--- a/utils/pdfinfo.cc
+++ b/utils/pdfinfo.cc
@@ -246,11 +246,31 @@ int main(int argc, char *argv[]) {
   // print encryption info
   printf("Encrypted:      ");
   if (doc->isEncrypted()) {
-    printf("yes (print:%s copy:%s change:%s addNotes:%s)\n",
+    Guchar *fileKey;
+    CryptAlgorithm encAlgorithm;
+    int keyLength;
+    doc->getXRef()->getEncryptionParameters(&fileKey, &encAlgorithm, &keyLength);
+
+    const char *encAlgorithmName = "unknown";
+    switch (encAlgorithm)
+    {
+      case cryptRC4:
+	encAlgorithmName = "RC4";
+	break;
+      case cryptAES:
+	encAlgorithmName = "AES";
+	break;
+      case cryptAES256:
+	encAlgorithmName = "AES-256";
+	break;
+    }
+
+    printf("yes (print:%s copy:%s change:%s addNotes:%s algorithm:%s)\n",
 	   doc->okToPrint(gTrue) ? "yes" : "no",
 	   doc->okToCopy(gTrue) ? "yes" : "no",
 	   doc->okToChange(gTrue) ? "yes" : "no",
-	   doc->okToAddNotes(gTrue) ? "yes" : "no");
+	   doc->okToAddNotes(gTrue) ? "yes" : "no",
+	   encAlgorithmName);
   } else {
     printf("no\n");
   }
commit 273e8c896e95b548093159dc8bb14d48ce447053
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Wed Aug 15 18:09:02 2012 +0200

    Mark object streams as DontRewrite
    
    So that they don't get copied in full rewrite mode, because they're
    not referenced from the XRef table we build, and we already
    individually write each object they contain.

diff --git a/poppler/XRef.cc b/poppler/XRef.cc
index 781cc20..80e1205 100644
--- a/poppler/XRef.cc
+++ b/poppler/XRef.cc
@@ -1540,6 +1540,19 @@ void XRef::scanSpecialFlags() {
   std::vector<int> xrefStreamObjNums;
   readXRefUntil(-1 /* read all xref sections */, &xrefStreamObjNums);
 
+  // Mark object streams as DontRewrite, because we write each object
+  // individually in full rewrite mode.
+  for (int i = 0; i < size; ++i) {
+    if (entries[i].type == xrefEntryCompressed) {
+      const int objStmNum = entries[i].offset;
+      if (unlikely(objStmNum < 0 || objStmNum >= size)) {
+        error(errSyntaxError, -1, "Compressed object offset out of xref bounds");
+      } else {
+        getEntry(objStmNum)->setFlag(XRefEntry::DontRewrite, gTrue);
+      }
+    }
+  }
+
   // Mark XRef streams objects as Unencrypted and DontRewrite
   for (size_t i = 0; i < xrefStreamObjNums.size(); ++i) {
     const int objNum = xrefStreamObjNums.at(i);
commit 59db8deaa7b1907831b526de3011dc22d0ffb333
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Thu Aug 9 20:08:44 2012 +0200

    Mark XRef streams as Unencrypted and DontRewrite
    
    - Unencrypted because they are stored in unencrypted form
    - DontRewrite because they must not be copied in full rewrite mode,
      because we always build a new XRef table, and existing XRef streams
      are not referenced any more (ie they become "leaked" objects).
      Furthermore, since readers know that XRef streams' objects are
      unencrypted from the fact that they are XRef streams, but these
      leaked objects are no longer referred as XRef streams, readers would
      think that they are regularly encrypted objects, resulting in
      currupt objects.

diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index a8ba5ae..dd93a64 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -913,6 +913,11 @@ void PDFDoc::saveCompleteRewrite (OutStream* outStr)
           and we don't want the one with num=0 because it has already been added (gen = 65535)*/
       if (ref.gen > 0 && ref.num > 0)
         uxref->add(ref.num, ref.gen, 0, gFalse);
+    } else if (xref->getEntry(i)->getFlag(XRefEntry::DontRewrite)) {
+      // This entry must not be written, put a free entry instead (with incremented gen)
+      ref.num = i;
+      ref.gen = xref->getEntry(i)->gen + 1;
+      uxref->add(ref.num, ref.gen, 0, gFalse);
     } else if (type == xrefEntryUncompressed){ 
       ref.num = i;
       ref.gen = xref->getEntry(i)->gen;
diff --git a/poppler/XRef.cc b/poppler/XRef.cc
index 49d1bf8..781cc20 100644
--- a/poppler/XRef.cc
+++ b/poppler/XRef.cc
@@ -293,7 +293,7 @@ XRef::XRef(BaseStream *strA, Guint pos, Guint mainXRefEntriesOffsetA, GBool *was
   // read the trailer
   str = strA;
   start = str->getStart();
-  prevXRefOffset = pos;
+  prevXRefOffset = mainXRefOffset = pos;
 
   if (reconstruct && !(ok = constructXRef(wasReconstructed)))
   {
@@ -313,7 +313,7 @@ XRef::XRef(BaseStream *strA, Guint pos, Guint mainXRefEntriesOffsetA, GBool *was
     // read the xref table
     } else {
       std::vector<Guint> followedXRefStm;
-      readXRef(&prevXRefOffset, &followedXRefStm);
+      readXRef(&prevXRefOffset, &followedXRefStm, NULL);
 
       // if there was a problem with the xref table,
       // try to reconstruct it
@@ -426,9 +426,20 @@ int XRef::resize(int newSize)
   return size;
 }
 
-// Read one xref table section.  Also reads the associated trailer
-// dictionary, and returns the prev pointer (if any).
-GBool XRef::readXRef(Guint *pos, std::vector<Guint> *followedXRefStm) {
+/* Read one xref table section.  Also reads the associated trailer
+ * dictionary, and returns the prev pointer (if any).
+ * Arguments:
+ *   pos                Points to a Guint containing the offset of the XRef
+ *                      section to be read. If a prev pointer is found, *pos is
+ *                      updated with its value
+ *   followedXRefStm    Used in case of nested readXRef calls to spot circular
+ *                      references in XRefStm pointers
+ *   xrefStreamObjsNum  If not NULL, every time a XRef stream is encountered,
+ *                      its object number is appended
+ * Return value:
+ *   gTrue if a prev pointer is found, otherwise gFalse
+ */
+GBool XRef::readXRef(Guint *pos, std::vector<Guint> *followedXRefStm, std::vector<int> *xrefStreamObjsNum) {
   Parser *parser;
   Object obj;
   GBool more;
@@ -444,10 +455,11 @@ GBool XRef::readXRef(Guint *pos, std::vector<Guint> *followedXRefStm) {
   // parse an old-style xref table
   if (obj.isCmd("xref")) {
     obj.free();
-    more = readXRefTable(parser, pos, followedXRefStm);
+    more = readXRefTable(parser, pos, followedXRefStm, xrefStreamObjsNum);
 
   // parse an xref stream
   } else if (obj.isInt()) {
+    const int objNum = obj.getInt();
     obj.free();
     if (!parser->getObj(&obj, gTrue)->isInt()) {
       goto err1;
@@ -463,6 +475,9 @@ GBool XRef::readXRef(Guint *pos, std::vector<Guint> *followedXRefStm) {
     if (trailerDict.isNone()) {
       xRefStream = gTrue;
     }
+    if (xrefStreamObjsNum) {
+      xrefStreamObjsNum->push_back(objNum);
+    }
     more = readXRefStream(obj.getStream(), pos);
     obj.free();
 
@@ -480,7 +495,7 @@ GBool XRef::readXRef(Guint *pos, std::vector<Guint> *followedXRefStm) {
   return gFalse;
 }
 
-GBool XRef::readXRefTable(Parser *parser, Guint *pos, std::vector<Guint> *followedXRefStm) {
+GBool XRef::readXRefTable(Parser *parser, Guint *pos, std::vector<Guint> *followedXRefStm, std::vector<int> *xrefStreamObjsNum) {
   XRefEntry entry;
   GBool more;
   Object obj, obj2;
@@ -597,7 +612,7 @@ GBool XRef::readXRefTable(Parser *parser, Guint *pos, std::vector<Guint> *follow
     }
     if (ok) {
       followedXRefStm->push_back(pos2);
-      readXRef(&pos2, followedXRefStm);
+      readXRef(&pos2, followedXRefStm, xrefStreamObjsNum);
     }
     if (!ok) {
       obj2.free();
@@ -1386,6 +1401,50 @@ GBool XRef::parseEntry(Guint offset, XRefEntry *entry)
   return r;
 }
 
+/* Traverse all XRef tables and, if untilEntryNum != -1, stop as soon as
+ * untilEntryNum is found, or try to reconstruct the xref table if it's not
+ * present in any xref.
+ * If xrefStreamObjsNum is not NULL, it is filled with the list of the object
+ * numbers of the XRef streams that have been traversed */
+void XRef::readXRefUntil(int untilEntryNum, std::vector<int> *xrefStreamObjsNum)
+{
+  std::vector<Guint> followedPrev;
+  while (prevXRefOffset && (untilEntryNum == -1 || entries[untilEntryNum].type == xrefEntryNone)) {
+    bool followed = false;
+    for (size_t j = 0; j < followedPrev.size(); j++) {
+      if (followedPrev.at(j) == prevXRefOffset) {
+        followed = true;
+        break;
+      }
+    }
+    if (followed) {
+      error(errSyntaxError, -1, "Circular XRef");
+      if (!(ok = constructXRef(NULL))) {
+        errCode = errDamaged;
+      }
+      break;
+    }
+
+    followedPrev.push_back (prevXRefOffset);
+
+    std::vector<Guint> followedXRefStm;
+    if (!readXRef(&prevXRefOffset, &followedXRefStm, xrefStreamObjsNum)) {
+        prevXRefOffset = 0;
+    }
+
+    // if there was a problem with the xref table, or we haven't found the entry
+    // we were looking for, try to reconstruct the xref
+    if (!ok || (!prevXRefOffset && untilEntryNum != -1 && entries[untilEntryNum].type == xrefEntryNone)) {
+        GBool wasReconstructed = false;
+        if (!(ok = constructXRef(&wasReconstructed))) {
+            errCode = errDamaged;
+            break;
+        }
+        break;
+    }
+  }
+}
+
 XRefEntry *XRef::getEntry(int i, GBool complainIfMissing)
 {
   if (entries[i].type == xrefEntryNone) {
@@ -1395,41 +1454,8 @@ XRefEntry *XRef::getEntry(int i, GBool complainIfMissing)
         error(errSyntaxError, -1, "Failed to parse XRef entry [{0:d}].", i);
       }
     } else {
-      std::vector<Guint> followedPrev;
-      while (prevXRefOffset && entries[i].type == xrefEntryNone) {
-        bool followed = false;
-        for (size_t j = 0; j < followedPrev.size(); j++) {
-          if (followedPrev.at(j) == prevXRefOffset) {
-            followed = true;
-            break;
-          }
-        }
-        if (followed) {
-          error(errSyntaxError, -1, "Circular XRef");
-          if (!(ok = constructXRef(NULL))) {
-            errCode = errDamaged;
-          }
-          break;
-        }
-
-        followedPrev.push_back (prevXRefOffset);
-
-        std::vector<Guint> followedXRefStm;
-        if (!readXRef(&prevXRefOffset, &followedXRefStm)) {
-            prevXRefOffset = 0;
-        }
-
-        // if there was a problem with the xref table,
-        // try to reconstruct it
-        if (!ok || (!prevXRefOffset && entries[i].type == xrefEntryNone)) {
-           GBool wasReconstructed = false;
-           if (!(ok = constructXRef(&wasReconstructed))) {
-               errCode = errDamaged;
-               break;
-           }
-           break;
-        }
-      }
+      // Read XRef tables until the entry we're looking for is found
+      readXRefUntil(i);
       
       // We might have reconstructed the xref
       // Check again i is in bounds
@@ -1507,6 +1533,20 @@ void XRef::scanSpecialFlags() {
   }
   scannedSpecialFlags = gTrue;
 
+  // "Rewind" the XRef linked list, so that readXRefUntil re-reads all XRef
+  // tables/streams, even those that had already been parsed
+  prevXRefOffset = mainXRefOffset;
+
+  std::vector<int> xrefStreamObjNums;
+  readXRefUntil(-1 /* read all xref sections */, &xrefStreamObjNums);
+
+  // Mark XRef streams objects as Unencrypted and DontRewrite
+  for (size_t i = 0; i < xrefStreamObjNums.size(); ++i) {
+    const int objNum = xrefStreamObjNums.at(i);
+    getEntry(objNum)->setFlag(XRefEntry::Unencrypted, gTrue);
+    getEntry(objNum)->setFlag(XRefEntry::DontRewrite, gTrue);
+  }
+
   // Mark objects referred from the Encrypt dict as Unencrypted
   Object obj;
   markUnencrypted(trailerDict.dictLookupNF("Encrypt", &obj));
diff --git a/poppler/XRef.h b/poppler/XRef.h
index 64b010b..78c1782 100644
--- a/poppler/XRef.h
+++ b/poppler/XRef.h
@@ -67,7 +67,8 @@ struct XRefEntry {
     Updated,     // Entry was modified
 
     // Special flags -- available only after xref->scanSpecialFlags() is run
-    Unencrypted  // Entry is stored in unencrypted form (meaningless in unencrypted documents)
+    Unencrypted, // Entry is stored in unencrypted form (meaningless in unencrypted documents)
+    DontRewrite  // Entry must not be written back in case of full rewrite
   };
 
   inline GBool getFlag(Flag flag) {
@@ -203,17 +204,19 @@ private:
   Guint prevXRefOffset;		// position of prev XRef section (= next to read)
   Guint mainXRefEntriesOffset;	// offset of entries in main XRef table
   GBool xRefStream;		// true if last XRef section is a stream
+  Guint mainXRefOffset;		// position of the main XRef table/stream
   GBool scannedSpecialFlags;	// true if scanSpecialFlags has been called
 
   void init();
   int reserve(int newSize);
   int resize(int newSize);
-  GBool readXRef(Guint *pos, std::vector<Guint> *followedXRefStm);
-  GBool readXRefTable(Parser *parser, Guint *pos, std::vector<Guint> *followedXRefStm);
+  GBool readXRef(Guint *pos, std::vector<Guint> *followedXRefStm, std::vector<int> *xrefStreamObjsNum);
+  GBool readXRefTable(Parser *parser, Guint *pos, std::vector<Guint> *followedXRefStm, std::vector<int> *xrefStreamObjsNum);
   GBool readXRefStreamSection(Stream *xrefStr, int *w, int first, int n);
   GBool readXRefStream(Stream *xrefStr, Guint *pos);
   GBool constructXRef(GBool *wasReconstructed);
   GBool parseEntry(Guint offset, XRefEntry *entry);
+  void readXRefUntil(int untilEntryNum, std::vector<int> *xrefStreamObjsNum = NULL);
   void markUnencrypted(Object *obj);
 
   class XRefWriter {
commit 116722cc74e267ac44dd5a70924557cdf6f25d02
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Sat Aug 4 12:47:22 2012 +0200

    Encrypt strWeird streams before writing them in PDFDoc::writeObject

diff --git a/poppler/Decrypt.cc b/poppler/Decrypt.cc
index e329a2e..44f6961 100644
--- a/poppler/Decrypt.cc
+++ b/poppler/Decrypt.cc
@@ -326,10 +326,13 @@ BaseCryptStream::BaseCryptStream(Stream *strA, Guchar *fileKey, CryptAlgorithm a
   }
 
   charactersRead = 0;
+  autoDelete = gTrue;
 }
 
 BaseCryptStream::~BaseCryptStream() {
-  delete str;
+  if (autoDelete) {
+    delete str;
+  }
 }
 
 void BaseCryptStream::reset() {
@@ -356,6 +359,10 @@ GBool BaseCryptStream::isBinary(GBool last) {
   return str->isBinary(last);
 }
 
+void BaseCryptStream::setAutoDelete(GBool val) {
+  autoDelete = val;
+}
+
 //------------------------------------------------------------------------
 // EncryptStream
 //------------------------------------------------------------------------
diff --git a/poppler/Decrypt.h b/poppler/Decrypt.h
index c64c8c1..c049f5c 100644
--- a/poppler/Decrypt.h
+++ b/poppler/Decrypt.h
@@ -110,6 +110,7 @@ public:
   virtual int getPos();
   virtual GBool isBinary(GBool last);
   virtual Stream *getUndecodedStream() { return this; }
+  void setAutoDelete(GBool val);
 
 protected:
   CryptAlgorithm algo;
@@ -117,6 +118,7 @@ protected:
   Guchar objKey[32];
   int charactersRead; // so that getPos() can be correct
   int nextCharBuff;   // EOF means not read yet
+  GBool autoDelete;
 
   union {
     DecryptRC4State rc4;
diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index abcef31..a8ba5ae 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -1112,6 +1112,15 @@ void PDFDoc::writeObject (Object* obj, OutStream* outStr, XRef *xRef, Guint numO
         Stream *stream = obj->getStream();
         if (stream->getKind() == strWeird) {
           //we write the stream unencoded => TODO: write stream encoder
+
+          // Encrypt stream
+          EncryptStream *encStream = NULL;
+          if (fileKey) {
+            encStream = new EncryptStream(stream, fileKey, encAlgorithm, keyLength, objNum, objGen);
+            encStream->setAutoDelete(gFalse);
+            stream = encStream;
+          }
+
           stream->reset();
           //recalculate stream length
           tmp = 0;
@@ -1127,6 +1136,7 @@ void PDFDoc::writeObject (Object* obj, OutStream* outStr, XRef *xRef, Guint numO
 
           writeDictionnary (stream->getDict(),outStr, xRef, numOffset, fileKey, encAlgorithm, keyLength, objNum, objGen);
           writeStream (stream,outStr);
+          delete encStream;
           obj1.free();
         } else {
           //raw stream copy
commit 695889c1330ca5b37338b8363dbf233fce936bc6
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Sat Aug 4 12:24:25 2012 +0200

    Encrypt strings before writing them in PDFDoc::writeString

diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index b660ad9..abcef31 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -995,6 +995,24 @@ void PDFDoc::writeRawStream (Stream* str, OutStream* outStr)
 void PDFDoc::writeString (GooString* s, OutStream* outStr, Guchar *fileKey,
                           CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen)
 {
+  // Encrypt string if encryption is enabled
+  GooString *sEnc = NULL;
+  if (fileKey) {
+    Object obj;
+    EncryptStream *enc = new EncryptStream(new MemStream(s->getCString(), 0, s->getLength(), obj.initNull()),
+                                           fileKey, encAlgorithm, keyLength, objNum, objGen);
+    sEnc = new GooString();
+    int c;
+    enc->reset();
+    while ((c = enc->getChar()) != EOF) {
+      sEnc->append((char)c);
+    }
+
+    delete enc;
+    s = sEnc;
+  }
+
+  // Write data
   if (s->hasUnicodeMarker()) {
     //unicode string don't necessary end with \0
     const char* c = s->getCString();
@@ -1026,6 +1044,8 @@ void PDFDoc::writeString (GooString* s, OutStream* outStr, Guchar *fileKey,
     }
     outStr->printf(") ");
   }
+
+  delete sEnc;
 }
 
 Guint PDFDoc::writeObjectHeader (Ref *ref, OutStream* outStr)
commit 4ab8e7be536db40db8a1a4af50dad3ba59c49f14
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Sat Aug 4 02:06:11 2012 +0200

    Propagate encryption parameters to PDFDoc's write functions

diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index 732740c..b660ad9 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -687,7 +687,7 @@ int PDFDoc::savePageAs(GooString *name, int pageNo)
       if (j > 0) outStr->printf(" ");
       Object value; catDict->getValNF(j, &value);
       outStr->printf("/%s ", key);
-      writeObject(&value, outStr, getXRef(), 0);
+      writeObject(&value, outStr, getXRef(), 0, NULL, cryptRC4, 0, 0, 0);
       value.free();
     }
   }
@@ -701,7 +701,7 @@ int PDFDoc::savePageAs(GooString *name, int pageNo)
   outStr->printf("<< /Type /Pages /Kids [ %d 0 R ] /Count 1 ", rootNum + 2);
   if (resourcesObj.isDict()) {
     outStr->printf("/Resources ");
-    writeObject(&resourcesObj, outStr, getXRef(), 0);
+    writeObject(&resourcesObj, outStr, getXRef(), 0, NULL, cryptRC4, 0, 0, 0);
     resourcesObj.free();
   }
   outStr->printf(">>\n");
@@ -719,7 +719,7 @@ int PDFDoc::savePageAs(GooString *name, int pageNo)
       outStr->printf("/Parent %d 0 R", rootNum + 1);
     } else {
       outStr->printf("/%s ", key);
-      writeObject(&value, outStr, getXRef(), 0);
+      writeObject(&value, outStr, getXRef(), 0, NULL, cryptRC4, 0, 0, 0);
     }
     value.free();
   }
@@ -826,6 +826,11 @@ void PDFDoc::saveIncrementalUpdate (OutStream* outStr)
   }
   str->close();
 
+  Guchar *fileKey;
+  CryptAlgorithm encAlgorithm;
+  int keyLength;
+  xref->getEncryptionParameters(&fileKey, &encAlgorithm, &keyLength);
+
   uxref = new XRef();
   uxref->add(0, 65535, 0, gFalse);
   for(int i=0; i<xref->getNumObjects(); i++) {
@@ -841,7 +846,7 @@ void PDFDoc::saveIncrementalUpdate (OutStream* outStr)
         Object obj1;
         xref->fetch(ref.num, ref.gen, &obj1);
         Guint offset = writeObjectHeader(&ref, outStr);
-        writeObject(&obj1, outStr);
+        writeObject(&obj1, outStr, fileKey, encAlgorithm, keyLength, ref.num, ref.gen);
         writeObjectFooter(outStr);
         uxref->add(ref.num, ref.gen, offset, gTrue);
         obj1.free();
@@ -889,6 +894,11 @@ void PDFDoc::saveCompleteRewrite (OutStream* outStr)
   // all objects, including Unencrypted ones.
   xref->scanSpecialFlags();
 
+  Guchar *fileKey;
+  CryptAlgorithm encAlgorithm;
+  int keyLength;
+  xref->getEncryptionParameters(&fileKey, &encAlgorithm, &keyLength);
+
   outStr->printf("%%PDF-%d.%d\r\n",pdfMajorVersion,pdfMinorVersion);
   XRef *uxref = new XRef();
   uxref->add(0, 65535, 0, gFalse);
@@ -908,7 +918,12 @@ void PDFDoc::saveCompleteRewrite (OutStream* outStr)
       ref.gen = xref->getEntry(i)->gen;
       xref->fetch(ref.num, ref.gen, &obj1);
       Guint offset = writeObjectHeader(&ref, outStr);
-      writeObject(&obj1, outStr);
+      // Write unencrypted objects in unencrypted form
+      if (xref->getEntry(i)->getFlag(XRefEntry::Unencrypted)) {
+        writeObject(&obj1, outStr, NULL, cryptRC4, 0, 0, 0);
+      } else {
+        writeObject(&obj1, outStr, fileKey, encAlgorithm, keyLength, ref.num, ref.gen);
+      }
       writeObjectFooter(outStr);
       uxref->add(ref.num, ref.gen, offset, gTrue);
       obj1.free();
@@ -917,7 +932,7 @@ void PDFDoc::saveCompleteRewrite (OutStream* outStr)
       ref.gen = 0; //compressed entries have gen == 0
       xref->fetch(ref.num, ref.gen, &obj1);
       Guint offset = writeObjectHeader(&ref, outStr);
-      writeObject(&obj1, outStr);
+      writeObject(&obj1, outStr, fileKey, encAlgorithm, keyLength, ref.num, ref.gen);
       writeObjectFooter(outStr);
       uxref->add(ref.num, ref.gen, offset, gTrue);
       obj1.free();
@@ -929,7 +944,8 @@ void PDFDoc::saveCompleteRewrite (OutStream* outStr)
   delete uxref;
 }
 
-void PDFDoc::writeDictionnary (Dict* dict, OutStream* outStr, XRef *xRef, Guint numOffset)
+void PDFDoc::writeDictionnary (Dict* dict, OutStream* outStr, XRef *xRef, Guint numOffset, Guchar *fileKey,
+                               CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen)
 {
   Object obj1;
   outStr->printf("<<");
@@ -938,7 +954,7 @@ void PDFDoc::writeDictionnary (Dict* dict, OutStream* outStr, XRef *xRef, Guint
     GooString *keyNameToPrint = keyName.sanitizedName(gFalse /* non ps mode */);
     outStr->printf("/%s ", keyNameToPrint->getCString());
     delete keyNameToPrint;
-    writeObject(dict->getValNF(i, &obj1), outStr, xRef, numOffset);
+    writeObject(dict->getValNF(i, &obj1), outStr, xRef, numOffset, fileKey, encAlgorithm, keyLength, objNum, objGen);
     obj1.free();
   }
   outStr->printf(">> ");
@@ -976,7 +992,8 @@ void PDFDoc::writeRawStream (Stream* str, OutStream* outStr)
   outStr->printf("\r\nendstream\r\n");
 }
 
-void PDFDoc::writeString (GooString* s, OutStream* outStr)
+void PDFDoc::writeString (GooString* s, OutStream* outStr, Guchar *fileKey,
+                          CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen)
 {
   if (s->hasUnicodeMarker()) {
     //unicode string don't necessary end with \0
@@ -1018,7 +1035,8 @@ Guint PDFDoc::writeObjectHeader (Ref *ref, OutStream* outStr)
   return offset;
 }
 
-void PDFDoc::writeObject (Object* obj, OutStream* outStr, XRef *xRef, Guint numOffset)
+void PDFDoc::writeObject (Object* obj, OutStream* outStr, XRef *xRef, Guint numOffset, Guchar *fileKey,
+                          CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen)
 {
   Array *array;
   Object obj1;
@@ -1042,7 +1060,7 @@ void PDFDoc::writeObject (Object* obj, OutStream* outStr, XRef *xRef, Guint numO
       break;
     }
     case objString:
-      writeString(obj->getString(), outStr);
+      writeString(obj->getString(), outStr, fileKey, encAlgorithm, keyLength, objNum, objGen);
       break;
     case objName:
     {
@@ -1059,13 +1077,13 @@ void PDFDoc::writeObject (Object* obj, OutStream* outStr, XRef *xRef, Guint numO
       array = obj->getArray();
       outStr->printf("[");
       for (int i=0; i<array->getLength(); i++) {
-        writeObject(array->getNF(i, &obj1), outStr, xRef, numOffset);
+        writeObject(array->getNF(i, &obj1), outStr, xRef, numOffset, fileKey, encAlgorithm, keyLength, objNum, objGen);
         obj1.free();
       }
       outStr->printf("] ");
       break;
     case objDict:
-      writeDictionnary (obj->getDict(),outStr, xRef, numOffset);
+      writeDictionnary (obj->getDict(), outStr, xRef, numOffset, fileKey, encAlgorithm, keyLength, objNum, objGen);
       break;
     case objStream: 
       {
@@ -1087,7 +1105,7 @@ void PDFDoc::writeObject (Object* obj, OutStream* outStr, XRef *xRef, Guint numO
           stream->getDict()->remove("Filter");
           stream->getDict()->remove("DecodeParms");
 
-          writeDictionnary (stream->getDict(),outStr, xRef, numOffset);
+          writeDictionnary (stream->getDict(),outStr, xRef, numOffset, fileKey, encAlgorithm, keyLength, objNum, objGen);
           writeStream (stream,outStr);
           obj1.free();
         } else {
@@ -1104,7 +1122,7 @@ void PDFDoc::writeObject (Object* obj, OutStream* outStr, XRef *xRef, Guint numO
                 }
               }
           }
-          writeDictionnary (stream->getDict(), outStr, xRef, numOffset);
+          writeDictionnary (stream->getDict(), outStr, xRef, numOffset, fileKey, encAlgorithm, keyLength, objNum, objGen);
           writeRawStream (stream, outStr);
         }
         break;
@@ -1239,7 +1257,7 @@ void PDFDoc::writeXRefTableTrailer(Dict *trailerDict, XRef *uxref, GBool writeAl
 {
   uxref->writeTableToFile( outStr, writeAllEntries );
   outStr->printf( "trailer\r\n");
-  writeDictionnary(trailerDict, outStr, xRef, 0);
+  writeDictionnary(trailerDict, outStr, xRef, 0, NULL, cryptRC4, 0, 0, 0);
   outStr->printf( "\r\nstartxref\r\n");
   outStr->printf( "%i\r\n", uxrefOffset);
   outStr->printf( "%%%%EOF\r\n");
@@ -1257,7 +1275,7 @@ void PDFDoc::writeXRefStreamTrailer (Dict *trailerDict, XRef *uxref, Ref *uxrefS
   MemStream *mStream = new MemStream( stmData.getCString(), 0,
                                       stmData.getLength(), obj1.initDict(trailerDict) );
   writeObjectHeader(uxrefStreamRef, outStr);
-  writeObject(obj1.initStream(mStream), outStr, xRef, 0);
+  writeObject(obj1.initStream(mStream), outStr, xRef, 0, NULL, cryptRC4, 0, 0, 0);
   writeObjectFooter(outStr);
   obj1.free();
 
@@ -1465,7 +1483,7 @@ Guint PDFDoc::writePageObjects(OutStream *outStr, XRef *xRef, Guint numOffset)
       objectsCount++;
       getXRef()->fetch(ref.num - numOffset, ref.gen, &obj);
       Guint offset = writeObjectHeader(&ref, outStr);
-      writeObject(&obj, outStr, xRef, numOffset);
+      writeObject(&obj, outStr, xRef, numOffset, NULL, cryptRC4, 0, 0, 0);
       writeObjectFooter(outStr);
       xRef->add(ref.num, ref.gen, offset, gTrue);
       obj.free();
diff --git a/poppler/PDFDoc.h b/poppler/PDFDoc.h
index fef2991..328b0c7 100644
--- a/poppler/PDFDoc.h
+++ b/poppler/PDFDoc.h
@@ -46,6 +46,7 @@
 
 class GooString;
 class BaseStream;
+enum CryptAlgorithm;
 class OutputDev;
 class Links;
 class LinkAction;
@@ -243,7 +244,8 @@ public:
   void markPageObjects(Dict *pageDict, XRef *xRef, XRef *countRef, Guint numOffset);
   // write all objects used by pageDict to outStr
   Guint writePageObjects(OutStream *outStr, XRef *xRef, Guint numOffset);
-  static void writeObject (Object *obj, OutStream* outStr, XRef *xref, Guint numOffset);
+  static void writeObject (Object *obj, OutStream* outStr, XRef *xref, Guint numOffset, Guchar *fileKey,
+                           CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen);
   static void writeHeader(OutStream *outStr, int major, int minor);
 
   // Ownership goes to the caller
@@ -258,21 +260,25 @@ private:
   // insert referenced objects in XRef
   void markDictionnary (Dict* dict, XRef *xRef, XRef *countRef, Guint numOffset);
   void markObject (Object *obj, XRef *xRef, XRef *countRef, Guint numOffset);
-  static void writeDictionnary (Dict* dict, OutStream* outStr, XRef *xRef, Guint numOffset);
+  static void writeDictionnary (Dict* dict, OutStream* outStr, XRef *xRef, Guint numOffset, Guchar *fileKey,
+                                CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen);
 
   // Write object header to current file stream and return its offset
   static Guint writeObjectHeader (Ref *ref, OutStream* outStr);
   static void writeObjectFooter (OutStream* outStr);
 
-  void writeObject (Object *obj, OutStream* outStr)
-  { writeObject(obj, outStr, getXRef(), 0); }
-  void writeDictionnary (Dict* dict, OutStream* outStr)
-  { writeDictionnary(dict, outStr, getXRef(), 0); }
+  void writeObject (Object *obj, OutStream* outStr, Guchar *fileKey, CryptAlgorithm encAlgorithm,
+                    int keyLength, int objNum, int objGen)
+  { writeObject(obj, outStr, getXRef(), 0, fileKey, encAlgorithm, keyLength, objNum, objGen); }
+  void writeDictionnary (Dict* dict, OutStream* outStr, Guchar *fileKey, CryptAlgorithm encAlgorithm,
+                         int keyLength, int objNum, int objGen)
+  { writeDictionnary(dict, outStr, getXRef(), 0, fileKey, encAlgorithm, keyLength, objNum, objGen); }
   static void writeStream (Stream* str, OutStream* outStr);
   static void writeRawStream (Stream* str, OutStream* outStr);
   void writeXRefTableTrailer (Guint uxrefOffset, XRef *uxref, GBool writeAllEntries,
                               int uxrefSize, OutStream* outStr, GBool incrUpdate);
-  static void writeString (GooString* s, OutStream* outStr);
+  static void writeString (GooString* s, OutStream* outStr, Guchar *fileKey,
+                           CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen);
   void saveIncrementalUpdate (OutStream* outStr);
   void saveCompleteRewrite (OutStream* outStr);
 
diff --git a/poppler/XRef.cc b/poppler/XRef.cc
index 773c3a1..49d1bf8 100644
--- a/poppler/XRef.cc
+++ b/poppler/XRef.cc
@@ -941,6 +941,20 @@ void XRef::setEncryption(int permFlagsA, GBool ownerPasswordOkA,
   encAlgorithm = encAlgorithmA;
 }
 
+void XRef::getEncryptionParameters(Guchar **fileKeyA, CryptAlgorithm *encAlgorithmA,
+                              int *keyLengthA) {
+  if (encrypted) {
+    *fileKeyA = fileKey;
+    *encAlgorithmA = encAlgorithm;
+    *keyLengthA = keyLength;
+  } else {
+    // null encryption parameters
+    *fileKeyA = NULL;
+    *encAlgorithmA = cryptRC4;
+    *keyLengthA = 0;
+  }
+}
+
 GBool XRef::okToPrint(GBool ignoreOwnerPW) {
   return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permPrint);
 }
diff --git a/poppler/XRef.h b/poppler/XRef.h
index ebeaa6a..64b010b 100644
--- a/poppler/XRef.h
+++ b/poppler/XRef.h
@@ -113,6 +113,8 @@ public:
 		     int encVersionA, int encRevisionA,
 		     CryptAlgorithm encAlgorithmA);
 
+  void getEncryptionParameters(Guchar **fileKeyA, CryptAlgorithm *encAlgorithmA, int *keyLengthA);
+
   // Is the file encrypted?
   GBool isEncrypted() { return encrypted; }
 
diff --git a/utils/pdfunite.cc b/utils/pdfunite.cc
index cd71ddc..a16f4dd 100644
--- a/utils/pdfunite.cc
+++ b/utils/pdfunite.cc
@@ -154,7 +154,7 @@ int main (int argc, char *argv[])
         outStr->printf("/Parent %d 0 R", rootNum + 1);
       } else {
         outStr->printf("/%s ", key);
-        PDFDoc::writeObject(&value, outStr, yRef, offsets[i]);
+        PDFDoc::writeObject(&value, outStr, yRef, offsets[i], NULL, cryptRC4, 0, 0, 0);
       }
       value.free();
     }
commit 9e43f9a8bcbee9060309b9679dbcc6b501a79cfb
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Wed Aug 1 16:14:22 2012 +0200

    Separated header and footer write commands from the rest of PDFDoc::writeObject
    
    Because in next patch I'll need to pass the object's num and gen always,
    not only if the object's header and footer need to be written.

diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index 9a008c2..732740c 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -687,7 +687,7 @@ int PDFDoc::savePageAs(GooString *name, int pageNo)
       if (j > 0) outStr->printf(" ");
       Object value; catDict->getValNF(j, &value);
       outStr->printf("/%s ", key);
-      writeObject(&value, NULL, outStr, getXRef(), 0);
+      writeObject(&value, outStr, getXRef(), 0);
       value.free();
     }
   }
@@ -701,7 +701,7 @@ int PDFDoc::savePageAs(GooString *name, int pageNo)
   outStr->printf("<< /Type /Pages /Kids [ %d 0 R ] /Count 1 ", rootNum + 2);
   if (resourcesObj.isDict()) {
     outStr->printf("/Resources ");
-    writeObject(&resourcesObj, NULL, outStr, getXRef(), 0);
+    writeObject(&resourcesObj, outStr, getXRef(), 0);
     resourcesObj.free();
   }
   outStr->printf(">>\n");
@@ -719,7 +719,7 @@ int PDFDoc::savePageAs(GooString *name, int pageNo)
       outStr->printf("/Parent %d 0 R", rootNum + 1);
     } else {
       outStr->printf("/%s ", key);
-      writeObject(&value, NULL, outStr, getXRef(), 0); 
+      writeObject(&value, outStr, getXRef(), 0);
     }
     value.free();
   }
@@ -840,7 +840,9 @@ void PDFDoc::saveIncrementalUpdate (OutStream* outStr)
       if (xref->getEntry(i)->type != xrefEntryFree) {
         Object obj1;
         xref->fetch(ref.num, ref.gen, &obj1);
-        Guint offset = writeObject(&obj1, &ref, outStr);
+        Guint offset = writeObjectHeader(&ref, outStr);
+        writeObject(&obj1, outStr);
+        writeObjectFooter(outStr);
         uxref->add(ref.num, ref.gen, offset, gTrue);
         obj1.free();
       } else {
@@ -905,14 +907,18 @@ void PDFDoc::saveCompleteRewrite (OutStream* outStr)
       ref.num = i;
       ref.gen = xref->getEntry(i)->gen;
       xref->fetch(ref.num, ref.gen, &obj1);
-      Guint offset = writeObject(&obj1, &ref, outStr);
+      Guint offset = writeObjectHeader(&ref, outStr);
+      writeObject(&obj1, outStr);
+      writeObjectFooter(outStr);
       uxref->add(ref.num, ref.gen, offset, gTrue);
       obj1.free();
     } else if (type == xrefEntryCompressed) {
       ref.num = i;
       ref.gen = 0; //compressed entries have gen == 0
       xref->fetch(ref.num, ref.gen, &obj1);
-      Guint offset = writeObject(&obj1, &ref, outStr);
+      Guint offset = writeObjectHeader(&ref, outStr);
+      writeObject(&obj1, outStr);
+      writeObjectFooter(outStr);
       uxref->add(ref.num, ref.gen, offset, gTrue);
       obj1.free();
     }
@@ -932,7 +938,7 @@ void PDFDoc::writeDictionnary (Dict* dict, OutStream* outStr, XRef *xRef, Guint
     GooString *keyNameToPrint = keyName.sanitizedName(gFalse /* non ps mode */);
     outStr->printf("/%s ", keyNameToPrint->getCString());
     delete keyNameToPrint;
-    writeObject(dict->getValNF(i, &obj1), NULL, outStr, xRef, numOffset);
+    writeObject(dict->getValNF(i, &obj1), outStr, xRef, numOffset);
     obj1.free();
   }
   outStr->printf(">> ");
@@ -1005,16 +1011,19 @@ void PDFDoc::writeString (GooString* s, OutStream* outStr)
   }
 }
 
-Guint PDFDoc::writeObject (Object* obj, Ref* ref, OutStream* outStr, XRef *xRef, Guint numOffset)
+Guint PDFDoc::writeObjectHeader (Ref *ref, OutStream* outStr)
+{
+  Guint offset = outStr->getPos();
+  outStr->printf("%i %i obj ", ref->num, ref->gen);
+  return offset;
+}
+
+void PDFDoc::writeObject (Object* obj, OutStream* outStr, XRef *xRef, Guint numOffset)
 {
   Array *array;
   Object obj1;
-  Guint offset = outStr->getPos();
   int tmp;
 
-  if(ref) 
-    outStr->printf("%i %i obj ", ref->num, ref->gen);
-
   switch (obj->getType()) {
     case objBool:
       outStr->printf("%s ", obj->getBool()?"true":"false");
@@ -1050,7 +1059,7 @@ Guint PDFDoc::writeObject (Object* obj, Ref* ref, OutStream* outStr, XRef *xRef,
       array = obj->getArray();
       outStr->printf("[");
       for (int i=0; i<array->getLength(); i++) {
-        writeObject(array->getNF(i, &obj1), NULL,outStr, xRef, numOffset);
+        writeObject(array->getNF(i, &obj1), outStr, xRef, numOffset);
         obj1.free();
       }
       outStr->printf("] ");
@@ -1119,9 +1128,11 @@ Guint PDFDoc::writeObject (Object* obj, Ref* ref, OutStream* outStr, XRef *xRef,
       error(errUnimplemented, -1,"Unhandled objType : {0:d}, please report a bug with a testcase\r\n", obj->getType());
       break;
   }
-  if (ref)
-    outStr->printf("endobj\r\n");
-  return offset;
+}
+
+void PDFDoc::writeObjectFooter (OutStream* outStr)
+{
+  outStr->printf("endobj\r\n");
 }
 
 Dict *PDFDoc::createTrailerDict(int uxrefSize, GBool incrUpdate, Guint startxRef,
@@ -1245,7 +1256,9 @@ void PDFDoc::writeXRefStreamTrailer (Dict *trailerDict, XRef *uxref, Ref *uxrefS
   Object obj1;
   MemStream *mStream = new MemStream( stmData.getCString(), 0,
                                       stmData.getLength(), obj1.initDict(trailerDict) );
-  writeObject(obj1.initStream(mStream), uxrefStreamRef, outStr, xRef, 0);
+  writeObjectHeader(uxrefStreamRef, outStr);
+  writeObject(obj1.initStream(mStream), outStr, xRef, 0);
+  writeObjectFooter(outStr);
   obj1.free();
 
   outStr->printf( "startxref\r\n");
@@ -1451,7 +1464,9 @@ Guint PDFDoc::writePageObjects(OutStream *outStr, XRef *xRef, Guint numOffset)
       ref.gen = xRef->getEntry(n)->gen;
       objectsCount++;
       getXRef()->fetch(ref.num - numOffset, ref.gen, &obj);
-      Guint offset = writeObject(&obj, &ref, outStr, xRef, numOffset);
+      Guint offset = writeObjectHeader(&ref, outStr);
+      writeObject(&obj, outStr, xRef, numOffset);
+      writeObjectFooter(outStr);
       xRef->add(ref.num, ref.gen, offset, gTrue);
       obj.free();
     }
diff --git a/poppler/PDFDoc.h b/poppler/PDFDoc.h
index 468f698..fef2991 100644
--- a/poppler/PDFDoc.h
+++ b/poppler/PDFDoc.h
@@ -243,7 +243,7 @@ public:
   void markPageObjects(Dict *pageDict, XRef *xRef, XRef *countRef, Guint numOffset);
   // write all objects used by pageDict to outStr
   Guint writePageObjects(OutStream *outStr, XRef *xRef, Guint numOffset);
-  static Guint writeObject (Object *obj, Ref *ref, OutStream* outStr, XRef *xref, Guint numOffset);
+  static void writeObject (Object *obj, OutStream* outStr, XRef *xref, Guint numOffset);
   static void writeHeader(OutStream *outStr, int major, int minor);
 
   // Ownership goes to the caller
@@ -260,9 +260,12 @@ private:
   void markObject (Object *obj, XRef *xRef, XRef *countRef, Guint numOffset);
   static void writeDictionnary (Dict* dict, OutStream* outStr, XRef *xRef, Guint numOffset);
 
-  // Add object to current file stream and return the offset of the beginning of the object
-  Guint writeObject (Object *obj, Ref *ref, OutStream* outStr)
-  { return writeObject(obj, ref, outStr, getXRef(), 0); }
+  // Write object header to current file stream and return its offset
+  static Guint writeObjectHeader (Ref *ref, OutStream* outStr);
+  static void writeObjectFooter (OutStream* outStr);
+
+  void writeObject (Object *obj, OutStream* outStr)
+  { writeObject(obj, outStr, getXRef(), 0); }
   void writeDictionnary (Dict* dict, OutStream* outStr)
   { writeDictionnary(dict, outStr, getXRef(), 0); }
   static void writeStream (Stream* str, OutStream* outStr);
diff --git a/utils/pdfunite.cc b/utils/pdfunite.cc
index 212f89b..cd71ddc 100644
--- a/utils/pdfunite.cc
+++ b/utils/pdfunite.cc
@@ -154,7 +154,7 @@ int main (int argc, char *argv[])
         outStr->printf("/Parent %d 0 R", rootNum + 1);
       } else {
         outStr->printf("/%s ", key);
-        PDFDoc::writeObject(&value, NULL, outStr, yRef, offsets[i]);
+        PDFDoc::writeObject(&value, outStr, yRef, offsets[i]);
       }
       value.free();
     }
commit 4d19a002801531b07f11382daaf9880e4691a10e
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Sat Aug 4 01:36:06 2012 +0200

    Do not change encrypted documents' ID, not even in case of full rewrite
    
    Because we will raw-copy encrypted streams, and the ID is part of the
    decryption key.

diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index 667873a..9a008c2 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -1162,11 +1162,13 @@ Dict *PDFDoc::createTrailerDict(int uxrefSize, GBool incrUpdate, Guint startxRef
   }
   obj1.free();
 
+  GBool hasEncrypt = gFalse;
   if (!xRef->getTrailerDict()->isNone()) {
     Object obj2;
     xRef->getTrailerDict()->dictLookupNF("Encrypt", &obj2);
     if (!obj2.isNull()) {
       trailerDict->set("Encrypt", &obj2);
+      hasEncrypt = gTrue;
       obj2.free();
     }
   }
@@ -1180,7 +1182,8 @@ Dict *PDFDoc::createTrailerDict(int uxrefSize, GBool incrUpdate, Guint startxRef
   Object obj2,obj3,obj5;
   obj2.initArray(xRef);
 
-  if (incrUpdate) {
+  // In case of encrypted files, the ID must not be changed because it's used to calculate the key
+  if (incrUpdate || hasEncrypt) {
     Object obj4;
     //only update the second part of the array
     xRef->getTrailerDict()->getDict()->lookup("ID", &obj4);
commit 800b2e37d3c4c73147bf9e11d9f38afe2183ab9d
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Wed Aug 1 14:07:10 2012 +0200

    Initial support for saving encrypted documents
    
    - Do not refuse to save encrypted documents
    - Copy the /Encrypt value in the new document's trailer dictionary
    - Mark indirect objects referred from /Encrypt as not encrypted in XRef::scanSpecialFlags

diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index 0be4307..667873a 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -772,35 +772,13 @@ int PDFDoc::saveAs(OutStream *outStr, PDFWriteMode mode) {
     }
   }
 
-  // we don't support rewriting files with Encrypt at the moment
-  Object obj;
-  xref->getTrailerDict()->getDict()->lookupNF("Encrypt", &obj);
-  if (!obj.isNull())
-  {
-    obj.free();
-    if (!updated && mode == writeStandard) {
-      // simply copy the original file
-      saveWithoutChangesAs (outStr);
-    } else {
-      return errEncrypted;
-    }
-  }
-  else
-  {
-    obj.free();
-
-    if (mode == writeForceRewrite) {
-      saveCompleteRewrite(outStr);
-    } else if (mode == writeForceIncremental) {
-      saveIncrementalUpdate(outStr); 
-    } else { // let poppler decide
-      if(updated) { 
-        saveIncrementalUpdate(outStr);
-      } else {
-        // simply copy the original file
-        saveWithoutChangesAs (outStr);
-      }
-    }
+  if (!updated && mode == writeStandard) {
+    // simply copy the original file
+    saveWithoutChangesAs (outStr);
+  } if (mode == writeForceRewrite) {
+    saveCompleteRewrite(outStr);
+  } else {
+    saveIncrementalUpdate(outStr);
   }
 
   return errNone;
@@ -905,6 +883,10 @@ void PDFDoc::saveIncrementalUpdate (OutStream* outStr)
 
 void PDFDoc::saveCompleteRewrite (OutStream* outStr)
 {
+  // Make sure that special flags are set, because we are going to read
+  // all objects, including Unencrypted ones.
+  xref->scanSpecialFlags();
+
   outStr->printf("%%PDF-%d.%d\r\n",pdfMajorVersion,pdfMinorVersion);
   XRef *uxref = new XRef();
   uxref->add(0, 65535, 0, gFalse);
@@ -1180,6 +1162,15 @@ Dict *PDFDoc::createTrailerDict(int uxrefSize, GBool incrUpdate, Guint startxRef
   }
   obj1.free();
 
+  if (!xRef->getTrailerDict()->isNone()) {
+    Object obj2;
+    xRef->getTrailerDict()->dictLookupNF("Encrypt", &obj2);
+    if (!obj2.isNull()) {
+      trailerDict->set("Encrypt", &obj2);
+      obj2.free();
+    }
+  }
+
   //calculate md5 digest
   Guchar digest[16];
   md5((Guchar*)message.getCString(), message.getLength(), digest);
diff --git a/poppler/XRef.cc b/poppler/XRef.cc
index 1715a5f..773c3a1 100644
--- a/poppler/XRef.cc
+++ b/poppler/XRef.cc
@@ -267,6 +267,7 @@ void XRef::init() {
   objStrs = new PopplerCache(5);
   mainXRefEntriesOffset = 0;
   xRefStream = gFalse;
+  scannedSpecialFlags = gFalse;
 }
 
 XRef::XRef() {
@@ -1049,7 +1050,7 @@ Object *XRef::fetch(int num, int gen, Object *obj, int recursion) {
       delete parser;
       goto err;
     }
-    parser->getObj(obj, gFalse, encrypted ? fileKey : (Guchar *)NULL,
+    parser->getObj(obj, gFalse, (encrypted && !e->getFlag(XRefEntry::Unencrypted)) ? fileKey : NULL,
 		   encAlgorithm, keyLength, num, gen, recursion);
     obj1.free();
     obj2.free();
@@ -1439,4 +1440,63 @@ XRefEntry *XRef::getEntry(int i, GBool complainIfMissing)
   return &entries[i];
 }
 
+// Recursively sets the Unencrypted flag in all referenced xref entries
+void XRef::markUnencrypted(Object *obj) {
+  Object obj1;
+
+  switch (obj->getType()) {
+    case objArray:
+    {
+      Array *array = obj->getArray();
+      for (int i = 0; i < array->getLength(); i++) {
+        markUnencrypted(array->getNF(i, &obj1));
+        obj1.free();
+      }
+      break;
+    }
+    case objStream:
+    case objDict:
+    {
+      Dict *dict;
+      if (obj->getType() == objStream) {
+        Stream *stream = obj->getStream();
+        dict = stream->getDict();
+      } else {
+        dict = obj->getDict();
+      }
+      for (int i = 0; i < dict->getLength(); i++) {
+        markUnencrypted(dict->getValNF(i, &obj1));
+        obj1.free();
+      }
+      break;
+    }
+    case objRef:
+    {
+      Ref ref = obj->getRef();
+      XRefEntry *e = getEntry(ref.num);
+      if (e->getFlag(XRefEntry::Unencrypted))
+        return; // We've already been here: prevent infinite recursion
+      e->setFlag(XRefEntry::Unencrypted, gTrue);
+      fetch(ref.num, ref.gen, &obj1);
+      markUnencrypted(&obj1);
+      obj1.free();
+      break;
+    }
+    default:
+      break;
+  }
+}
+
+void XRef::scanSpecialFlags() {
+  if (scannedSpecialFlags) {
+    return;
+  }
+  scannedSpecialFlags = gTrue;
+
+  // Mark objects referred from the Encrypt dict as Unencrypted
+  Object obj;
+  markUnencrypted(trailerDict.dictLookupNF("Encrypt", &obj));
+  obj.free();
+}
+
 
diff --git a/poppler/XRef.h b/poppler/XRef.h
index 55b013d..ebeaa6a 100644
--- a/poppler/XRef.h
+++ b/poppler/XRef.h
@@ -63,7 +63,11 @@ struct XRefEntry {
   Object obj; //if this entry was updated, obj will contains the updated object
 
   enum Flag {
-    Updated   // Set if the entry was modified
+    // Regular flags
+    Updated,     // Entry was modified
+
+    // Special flags -- available only after xref->scanSpecialFlags() is run
+    Unencrypted  // Entry is stored in unencrypted form (meaningless in unencrypted documents)
   };
 
   inline GBool getFlag(Flag flag) {
@@ -147,6 +151,14 @@ public:
   // Retuns the entry that belongs to the offset
   int getNumEntry(Guint offset);
 
+  // Scans the document and sets special flags in all xref entries. One of those
+  // flags is Unencrypted, which affects how the object is fetched. Therefore,
+  // this function must be called before fetching unencrypted objects (e.g.
+  // Encrypt dictionary, XRef streams). Note that the code that initializes
+  // decryption doesn't need to call this function, because it runs before
+  // decryption is enabled, and therefore the Unencrypted flag is ignored.
+  void scanSpecialFlags();
+
   // Direct access.
   XRefEntry *getEntry(int i, GBool complainIfMissing = gTrue);
   Object *getTrailerDict() { return &trailerDict; }
@@ -189,6 +201,7 @@ private:
   Guint prevXRefOffset;		// position of prev XRef section (= next to read)
   Guint mainXRefEntriesOffset;	// offset of entries in main XRef table
   GBool xRefStream;		// true if last XRef section is a stream
+  GBool scannedSpecialFlags;	// true if scanSpecialFlags has been called
 
   void init();
   int reserve(int newSize);
@@ -199,6 +212,7 @@ private:
   GBool readXRefStream(Stream *xrefStr, Guint *pos);
   GBool constructXRef(GBool *wasReconstructed);
   GBool parseEntry(Guint offset, XRefEntry *entry);
+  void markUnencrypted(Object *obj);
 
   class XRefWriter {
   public:
commit 6647153d47b1d67d2a4d6b90dce2184ab6c7dda6
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Thu Aug 9 12:26:53 2012 +0200

    Added field XRefEntry::flags, and turned XRefEntry::updated into a flag
    
    In next patches I'll add other flags

diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index c6fed72..0be4307 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -766,7 +766,7 @@ int PDFDoc::saveAs(OutStream *outStr, PDFWriteMode mode) {
   // find if we have updated objects
   GBool updated = gFalse;
   for(int i=0; i<xref->getNumObjects(); i++) {
-    if (xref->getEntry(i)->updated) {
+    if (xref->getEntry(i)->getFlag(XRefEntry::Updated)) {
       updated = gTrue;
       break;
     }
@@ -855,7 +855,7 @@ void PDFDoc::saveIncrementalUpdate (OutStream* outStr)
         (xref->getEntry(i)->gen == 0)) //we skip the irrelevant free objects
       continue;
 
-    if (xref->getEntry(i)->updated) { //we have an updated object
+    if (xref->getEntry(i)->getFlag(XRefEntry::Updated)) { //we have an updated object
       Ref ref;
       ref.num = i;
       ref.gen = xref->getEntry(i)->type == xrefEntryCompressed ? 0 : xref->getEntry(i)->gen;
diff --git a/poppler/XRef.cc b/poppler/XRef.cc
index 3564807..1715a5f 100644
--- a/poppler/XRef.cc
+++ b/poppler/XRef.cc
@@ -411,7 +411,7 @@ int XRef::resize(int newSize)
       entries[i].offset = 0xffffffff;
       entries[i].type = xrefEntryNone;
       entries[i].obj.initNull ();
-      entries[i].updated = false;
+      entries[i].flags = 0;
       entries[i].gen = 0;
     }
   } else {
@@ -522,7 +522,7 @@ GBool XRef::readXRefTable(Parser *parser, Guint *pos, std::vector<Guint> *follow
       }
       entry.gen = obj.getInt();
       entry.obj.initNull ();
-      entry.updated = false;
+      entry.flags = 0;
       obj.free();
       parser->getObj(&obj, gTrue);
       if (obj.isCmd("n")) {
@@ -1167,7 +1167,7 @@ void XRef::add(int num, int gen, Guint offs, GBool used) {
       entries[i].offset = 0xffffffff;
       entries[i].type = xrefEntryFree;
       entries[i].obj.initNull ();
-      entries[i].updated = false;
+      entries[i].flags = 0;
       entries[i].gen = 0;
     }
     size = num + 1;
@@ -1175,7 +1175,7 @@ void XRef::add(int num, int gen, Guint offs, GBool used) {
   XRefEntry *e = getEntry(num);
   e->gen = gen;
   e->obj.initNull ();
-  e->updated = false;
+  e->flags = 0;
   if (used) {
     e->type = xrefEntryUncompressed;
     e->offset = offs;
@@ -1193,7 +1193,7 @@ void XRef::setModifiedObject (Object* o, Ref r) {
   XRefEntry *e = getEntry(r.num);
   e->obj.free();
   o->copy(&(e->obj));
-  e->updated = true;
+  e->setFlag(XRefEntry::Updated, gTrue);
 }
 
 Ref XRef::addIndirectObject (Object* o) {
@@ -1218,7 +1218,7 @@ Ref XRef::addIndirectObject (Object* o) {
   }
   e->type = xrefEntryUncompressed;
   o->copy(&e->obj);
-  e->updated = true;
+  e->setFlag(XRefEntry::Updated, gTrue);
 
   Ref r;
   r.num = entryIndexToUse;
@@ -1237,7 +1237,7 @@ void XRef::removeIndirectObject(Ref r) {
   e->obj.free();
   e->type = xrefEntryFree;
   e->gen++;
-  e->updated = true;
+  e->setFlag(XRefEntry::Updated, gTrue);
 }
 
 void XRef::writeXRef(XRef::XRefWriter *writer, GBool writeAllEntries) {
@@ -1359,7 +1359,7 @@ GBool XRef::parseEntry(Guint offset, XRefEntry *entry)
     entry->gen = obj2.getInt();
     entry->type = obj3.isCmd("n") ? xrefEntryUncompressed : xrefEntryFree;
     entry->obj.initNull ();
-    entry->updated = false;
+    entry->flags = 0;
     r = gTrue;
   } else {
     r = gFalse;
@@ -1423,7 +1423,7 @@ XRefEntry *XRef::getEntry(int i, GBool complainIfMissing)
         dummy.offset = 0;
         dummy.gen = -1;
         dummy.type = xrefEntryNone;
-        dummy.updated = false;
+        dummy.flags = 0;
         return &dummy;
       }
 
diff --git a/poppler/XRef.h b/poppler/XRef.h
index 76621ad..55b013d 100644
--- a/poppler/XRef.h
+++ b/poppler/XRef.h
@@ -59,8 +59,26 @@ struct XRefEntry {
   Guint offset;
   int gen;
   XRefEntryType type;
-  bool updated;
+  int flags;
   Object obj; //if this entry was updated, obj will contains the updated object
+
+  enum Flag {
+    Updated   // Set if the entry was modified
+  };
+
+  inline GBool getFlag(Flag flag) {
+    const int mask = (1 << (int)flag);
+    return (flags & mask) != 0;
+  }
+
+  inline void setFlag(Flag flag, GBool value) {
+    const int mask = (1 << (int)flag);
+    if (value) {
+      flags |= mask;
+    } else {
+      flags &= ~mask;
+    }
+  }
 };
 
 class XRef {
commit a284c6c6623587abb7da7e4c171c42e006ea477b
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Sat Aug 4 01:57:41 2012 +0200

    Be able to output overflown integers back
    
    Because Lexer.cc:241 can read them, and we must be able to write them
    back (especially in full rewrite mode).

diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index ce01ca3..c6fed72 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -1040,6 +1040,9 @@ Guint PDFDoc::writeObject (Object* obj, Ref* ref, OutStream* outStr, XRef *xRef,
     case objInt:
       outStr->printf("%i ", obj->getInt());
       break;
+    case objUint:
+      outStr->printf("%u ", obj->getUint());
+      break;
     case objReal:
     {
       GooString s;
commit 53baea19658a27f15c7ed870a24be82b8219ddfe
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Sat Aug 4 01:32:08 2012 +0200

    FlateStream::unfilteredReset should call str->unfilteredReset()
    
    Just like any other FilterStream-derived class does

diff --git a/poppler/Stream.cc b/poppler/Stream.cc
index 4ce6c00..c034cff 100644
--- a/poppler/Stream.cc
+++ b/poppler/Stream.cc
@@ -25,6 +25,7 @@
 // Copyright (C) 2011, 2012 William Bader <williambader at hotmail.com>
 // Copyright (C) 2012 Thomas Freitag <Thomas.Freitag at alfa.de>
 // Copyright (C) 2012 Oliver Sander <sander at mi.fu-berlin.de>
+// Copyright (C) 2012 Fabio D'Urso <fabiodurso at hotmail.it>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -4399,7 +4400,12 @@ FlateStream::~FlateStream() {
   delete str;
 }
 
-void FlateStream::unfilteredReset() {
+void FlateStream::flateReset(GBool unfiltered) {
+  if (unfiltered)
+    str->unfilteredReset();
+  else
+    str->reset();
+
   index = 0;
   remain = 0;
   codeBuf = 0;
@@ -4407,14 +4413,16 @@ void FlateStream::unfilteredReset() {
   compressedBlock = gFalse;
   endOfBlock = gTrue;
   eof = gTrue;
+}
 
-  str->reset();
+void FlateStream::unfilteredReset() {
+  flateReset(gTrue);
 }
 
 void FlateStream::reset() {
   int cmf, flg;
 
-  unfilteredReset();
+  flateReset(gFalse);
 
   // read header
   //~ need to look at window size?
diff --git a/poppler/Stream.h b/poppler/Stream.h
index a270fdf..20b5fd6 100644
--- a/poppler/Stream.h
+++ b/poppler/Stream.h
@@ -21,6 +21,7 @@
 // Copyright (C) 2010 Hib Eris <hib at hiberis.nl>
 // Copyright (C) 2011, 2012 William Bader <williambader at hotmail.com>
 // Copyright (C) 2012 Thomas Freitag <Thomas.Freitag at alfa.de>
+// Copyright (C) 2012 Fabio D'Urso <fabiodurso at hotmail.it>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -970,6 +971,7 @@ public:
   virtual void unfilteredReset ();
 
 private:
+  void flateReset(GBool unfiltered);
   inline int doGetRawChar() {
     int c;
 
commit 4e5fee4e9156480173f05e7b3d0bdf604127d481
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Mon Aug 6 02:08:27 2012 +0200

    Initialize AES encryption with random CBC IV data

diff --git a/poppler/Decrypt.cc b/poppler/Decrypt.cc
index 865bea6..e329a2e 100644
--- a/poppler/Decrypt.cc
+++ b/poppler/Decrypt.cc
@@ -32,6 +32,7 @@
 
 #include <string.h>
 #include "goo/gmem.h"
+#include "goo/grandom.h"
 #include "Decrypt.h"
 #include "Error.h"
 
@@ -366,10 +367,10 @@ EncryptStream::EncryptStream(Stream *strA, Guchar *fileKey, CryptAlgorithm algoA
   // Fill the CBC initialization vector for AES and AES-256
   switch (algo) {
   case cryptAES:
-    memset(state.aes.cbc, 0, 16); // TODO: Nonce
+    grandom_fill(state.aes.cbc, 16);
     break;
   case cryptAES256:
-    memset(state.aes256.cbc, 0, 16); // TODO: Nonce
+    grandom_fill(state.aes256.cbc, 16);
     break;
   default:
     break;
commit 1b008f273359b8df6b64ffa94bb2828e42ffa63e
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Mon Aug 6 02:08:40 2012 +0200

    Replaced srand/rand calls in SplashScreen with grandom calls

diff --git a/splash/SplashScreen.cc b/splash/SplashScreen.cc
index d741246..1ae28d4 100644
--- a/splash/SplashScreen.cc
+++ b/splash/SplashScreen.cc
@@ -28,6 +28,7 @@
 #include <string.h>
 #include <algorithm>
 #include "goo/gmem.h"
+#include "goo/grandom.h"
 #include "SplashMath.h"
 #include "SplashScreen.h"
 
@@ -253,9 +254,6 @@ void SplashScreen::buildSCDMatrix(int r) {
   int *region, *dist;
   int x, y, xx, yy, x0, x1, y0, y1, i, j, d, iMin, dMin, n;
 
-  //~ this should probably happen somewhere else
-  srand(123);
-
   // generate the random space-filling curve
   pts = (SplashScreenPoint *)gmallocn(size * size, sizeof(SplashScreenPoint));
   i = 0;
@@ -267,8 +265,7 @@ void SplashScreen::buildSCDMatrix(int r) {
     }
   }
   for (i = 0; i < size * size; ++i) {
-    j = i + (int)((double)(size * size - i) *
-		  (double)rand() / ((double)RAND_MAX + 1.0));
+    j = i + (int)((double)(size * size - i) * grandom_double());
     x = pts[i].x;
     y = pts[i].y;
     pts[i].x = pts[j].x;
commit faff947d8106048b19ba74dd483b90b8cebb16c7
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Mon Aug 6 02:06:47 2012 +0200

    Added goo/grandom.[cc|h] with POSIX implementation

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6bddf0b..f519639 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -243,6 +243,7 @@ set(poppler_SRCS
   goo/JpegWriter.cc
   goo/ImgWriter.cc
   goo/gstrtod.cc
+  goo/grandom.cc
   fofi/FoFiBase.cc
   fofi/FoFiEncodings.cc
   fofi/FoFiTrueType.cc
@@ -485,6 +486,7 @@ if(ENABLE_XPDF_HEADERS)
     goo/ImgWriter.h
     goo/GooLikely.h
     goo/gstrtod.h
+    goo/grandom.h
     DESTINATION include/poppler/goo)
   if(PNG_FOUND)
     install(FILES
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index 04de970..6e547ed 100644
--- a/ConfigureChecks.cmake
+++ b/ConfigureChecks.cmake
@@ -33,6 +33,7 @@ check_function_exists(localtime_r HAVE_LOCALTIME_R)
 check_function_exists(popen HAVE_POPEN)
 check_function_exists(mkstemp HAVE_MKSTEMP)
 check_function_exists(mkstemps HAVE_MKSTEMPS)
+check_function_exists(rand_r HAVE_RAND_R)
 
 macro(CHECK_FOR_DIR include var)
   check_c_source_compiles(
diff --git a/config.h.cmake b/config.h.cmake
index 3c7b968..cde219f 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -76,6 +76,9 @@
 /* Define to 1 if you have the `mkstemps' function. */
 #cmakedefine HAVE_MKSTEMPS 1
 
+/* Define to 1 if you have the `rand_r' function. */
+#cmakedefine HAVE_RAND_R 1
+
 /* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
 #cmakedefine HAVE_NDIR_H 1
 
diff --git a/configure.ac b/configure.ac
index 62a27b9..48f71c8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -133,6 +133,7 @@ AC_LANG_CPLUSPLUS
 AC_CHECK_DECL(gettimeofday, [AC_CHECK_FUNC(gettimeofday, AC_DEFINE(HAVE_GETTIMEOFDAY, 1, [Defines if gettimeofday is available on your system]))],[],[#include <sys/time.h>])
 AC_CHECK_FUNC(localtime_r, AC_DEFINE(HAVE_LOCALTIME_R, 1, [Defines if localtime_r is available on your system]))
 AC_CHECK_FUNC(gmtime_r, AC_DEFINE(HAVE_GMTIME_R, 1, [Defines if gmtime_r is available on your system]))
+AC_CHECK_FUNC(rand_r, AC_DEFINE(HAVE_RAND_R, 1, [Defines if rand_r is available on your system]))
 
 dnl ##### Check for extra libraries needed by X.  (LynxOS needs this.)
 AC_CHECK_FUNC(gethostbyname)
diff --git a/goo/Makefile.am b/goo/Makefile.am
index f4f9730..0764e79 100644
--- a/goo/Makefile.am
+++ b/goo/Makefile.am
@@ -18,7 +18,8 @@ poppler_goo_include_HEADERS =			\
 	TiffWriter.h				\
 	ImgWriter.h				\
 	GooLikely.h				\
-	gstrtod.h
+	gstrtod.h				\
+	grandom.h
 
 endif
 
@@ -59,4 +60,5 @@ libgoo_la_SOURCES =				\
 	TiffWriter.cc				\
 	ImgWriter.cc				\
 	gtypes_p.h				\
-	gstrtod.cc
+	gstrtod.cc				\
+	grandom.cc
diff --git a/goo/grandom.cc b/goo/grandom.cc
new file mode 100644
index 0000000..bafa4b6
--- /dev/null
+++ b/goo/grandom.cc
@@ -0,0 +1,68 @@
+/*
+ * grandom.cc
+ *
+ * Pseudo-random number generation
+ *
+ * Copyright (C) 2012 Fabio D'Urso <fabiodurso at hotmail.it>
+ */
+
+#include <config.h>
+#include "grandom.h"
+#include "gtypes.h"
+
+#ifdef HAVE_RAND_R // rand_r backend (POSIX)
+
+static GBool initialized = gFalse;
+
+#include <stdlib.h>
+#include <time.h>
+static unsigned int seed;
+
+static void initialize() {
+  if (!initialized) {
+    seed = time(NULL);
+    initialized = gTrue;
+  }
+}
+
+void grandom_fill(Guchar *buff, int size)
+{
+  initialize();
+  while (size--)
+    *buff++ = rand_r(&seed) % 256;
+}
+
+double grandom_double()
+{
+  initialize();
+  return rand_r(&seed) / (1 + (double)RAND_MAX);
+}
+
+#else // srand+rand backend (unsafe, because it may interfere with the application)
+
+static GBool initialized = gFalse;
+
+#include <stdlib.h>
+#include <time.h>
+
+static void initialize() {
+  if (!initialized) {
+    srand(time(NULL));
+    initialized = gTrue;
+  }
+}
+
+void grandom_fill(Guchar *buff, int size)
+{
+  initialize();
+  while (size--)
+    *buff++ = rand() % 256;
+}
+
+double grandom_double()
+{
+  initialize();
+  return rand() / (1 + (double)RAND_MAX);
+}
+
+#endif
diff --git a/goo/grandom.h b/goo/grandom.h
new file mode 100644
index 0000000..763e8e0
--- /dev/null
+++ b/goo/grandom.h
@@ -0,0 +1,32 @@
+/*
+ * grandom.h
+ *
+ * Pseudo-random number generation
+ *
+ * Copyright (C) 2012 Fabio D'Urso <fabiodurso at hotmail.it>
+ */
+
+#ifndef GRANDOM_H
+#define GRANDOM_H
+
+#include "gtypes.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Fills the given buffer with random bytes
+ */
+extern void grandom_fill(Guchar *buff, int size);
+
+/*
+ * Returns a random number in [0,1)
+ */
+extern double grandom_double();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
commit af8d05d1ab89b74e307e90aaf19c750528f5f561
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Fri Aug 3 12:46:06 2012 +0200

    Added encryption support in Decrypt.cc/.h

diff --git a/poppler/Decrypt.cc b/poppler/Decrypt.cc
index 272edc2..865bea6 100644
--- a/poppler/Decrypt.cc
+++ b/poppler/Decrypt.cc
@@ -41,9 +41,11 @@ static Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c);
 static GBool aesReadBlock(Stream  *str, Guchar *in, GBool addPadding);
 
 static void aesKeyExpansion(DecryptAESState *s, Guchar *objKey, int objKeyLen, GBool decrypt);
+static void aesEncryptBlock(DecryptAESState *s, Guchar *in);
 static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last);
 
 static void aes256KeyExpansion(DecryptAES256State *s, Guchar *objKey, int objKeyLen, GBool decrypt);
+static void aes256EncryptBlock(DecryptAES256State *s, Guchar *in);
 static void aes256DecryptBlock(DecryptAES256State *s, Guchar *in, GBool last);
 
 static void sha256(Guchar *msg, int msgLen, Guchar *hash);
@@ -354,6 +356,94 @@ GBool BaseCryptStream::isBinary(GBool last) {
 }
 
 //------------------------------------------------------------------------
+// EncryptStream
+//------------------------------------------------------------------------
+
+EncryptStream::EncryptStream(Stream *strA, Guchar *fileKey, CryptAlgorithm algoA,
+			     int keyLength, int objNum, int objGen):
+  BaseCryptStream(strA, fileKey, algoA, keyLength, objNum, objGen)
+{
+  // Fill the CBC initialization vector for AES and AES-256
+  switch (algo) {
+  case cryptAES:
+    memset(state.aes.cbc, 0, 16); // TODO: Nonce
+    break;
+  case cryptAES256:
+    memset(state.aes256.cbc, 0, 16); // TODO: Nonce
+    break;
+  default:
+    break;
+  }
+}
+
+EncryptStream::~EncryptStream() {
+}
+
+void EncryptStream::reset() {
+  BaseCryptStream::reset();
+
+  switch (algo) {
+  case cryptRC4:
+    state.rc4.x = state.rc4.y = 0;
+    rc4InitKey(objKey, objKeyLength, state.rc4.state);
+    break;
+  case cryptAES:
+    aesKeyExpansion(&state.aes, objKey, objKeyLength, gFalse);
+    memcpy(state.aes.buf, state.aes.cbc, 16); // Copy CBC IV to buf
+    state.aes.bufIdx = 0;
+    state.aes.paddingReached = gFalse;
+    break;
+  case cryptAES256:
+    aes256KeyExpansion(&state.aes256, objKey, objKeyLength, gFalse);
+    memcpy(state.aes256.buf, state.aes256.cbc, 16); // Copy CBC IV to buf
+    state.aes256.bufIdx = 0;
+    state.aes256.paddingReached = gFalse;
+    break;
+  }
+}
+
+int EncryptStream::lookChar() {
+  Guchar in[16];
+  int c;
+
+  if (nextCharBuff != EOF)
+    return nextCharBuff;
+
+  c = EOF; // make gcc happy
+  switch (algo) {
+  case cryptRC4:
+    if ((c = str->getChar()) != EOF) {
+      // RC4 is XOR-based: the decryption algorithm works for encryption too
+      c = rc4DecryptByte(state.rc4.state, &state.rc4.x, &state.rc4.y, (Guchar)c);
+    }
+    break;
+  case cryptAES:
+    if (state.aes.bufIdx == 16 && !state.aes.paddingReached) {
+      state.aes.paddingReached = !aesReadBlock(str, in, gTrue);
+      aesEncryptBlock(&state.aes, in);
+    }
+    if (state.aes.bufIdx == 16) {
+      c = EOF;
+    } else {
+      c = state.aes.buf[state.aes.bufIdx++];
+    }
+    break;
+  case cryptAES256:
+    if (state.aes256.bufIdx == 16 && !state.aes256.paddingReached) {
+      state.aes256.paddingReached = !aesReadBlock(str, in, gTrue);
+      aes256EncryptBlock(&state.aes256, in);
+    }
+    if (state.aes256.bufIdx == 16) {
+      c = EOF;
+    } else {
+      c = state.aes256.buf[state.aes256.bufIdx++];
+    }
+    break;
+  }
+  return (nextCharBuff = c);
+}
+
+//------------------------------------------------------------------------
 // DecryptStream
 //------------------------------------------------------------------------
 
@@ -564,6 +654,14 @@ static inline Guint rotWord(Guint x) {
   return ((x << 8) & 0xffffffff) | (x >> 24);
 }
 
+static inline void subBytes(Guchar *state) {
+  int i;
+
+  for (i = 0; i < 16; ++i) {
+    state[i] = sbox[state[i]];
+  }
+}
+
 static inline void invSubBytes(Guchar *state) {
   int i;
 
@@ -572,6 +670,29 @@ static inline void invSubBytes(Guchar *state) {
   }
 }
 
+static inline void shiftRows(Guchar *state) {
+  Guchar t;
+
+  t = state[4];
+  state[4] = state[5];
+  state[5] = state[6];
+  state[6] = state[7];
+  state[7] = t;
+
+  t = state[8];
+  state[8] = state[10];
+  state[10] = t;
+  t = state[9];
+  state[9] = state[11];
+  state[11] = t;
+
+  t = state[15];
+  state[15] = state[14];
+  state[14] = state[13];
+  state[13] = state[12];
+  state[12] = t;
+}
+
 static inline void invShiftRows(Guchar *state) {
   Guchar t;
 
@@ -595,6 +716,17 @@ static inline void invShiftRows(Guchar *state) {
   state[15] = t;
 }
 
+// {02} \cdot s
+static inline Guchar mul02(Guchar s) {
+  return (s & 0x80) ? ((s << 1) ^ 0x1b) : (s << 1);
+}
+
+// {03} \cdot s
+static inline Guchar mul03(Guchar s) {
+  Guchar s2 = (s & 0x80) ? ((s << 1) ^ 0x1b) : (s << 1);
+  return s ^ s2;
+}
+
 // {09} \cdot s
 static inline Guchar mul09(Guchar s) {
   Guchar s2, s4, s8;
@@ -635,6 +767,22 @@ static inline Guchar mul0e(Guchar s) {
   return s2 ^ s4 ^ s8;
 }
 
+static inline void mixColumns(Guchar *state) {
+  int c;
+  Guchar s0, s1, s2, s3;
+
+  for (c = 0; c < 4; ++c) {
+    s0 = state[c];
+    s1 = state[4+c];
+    s2 = state[8+c];
+    s3 = state[12+c];
+    state[c] =    mul02(s0) ^ mul03(s1) ^ s2 ^ s3;
+    state[4+c] =  s0 ^ mul02(s1) ^ mul03(s2) ^ s3;
+    state[8+c] =  s0 ^ s1 ^ mul02(s2) ^ mul03(s3);
+    state[12+c] = mul03(s0) ^ s1 ^ s2 ^ mul02(s3);
+  }
+}
+
 static inline void invMixColumns(Guchar *state) {
   int c;
   Guchar s0, s1, s2, s3;
@@ -705,6 +853,43 @@ static void aesKeyExpansion(DecryptAESState *s,
   }
 }
 
+static void aesEncryptBlock(DecryptAESState *s, Guchar *in) {
+  int c, round;
+
+  // initial state (input is xor'd with previous output because of CBC)
+  for (c = 0; c < 4; ++c) {
+    s->state[c] = in[4*c] ^ s->buf[4*c];
+    s->state[4+c] = in[4*c+1] ^ s->buf[4*c+1];
+    s->state[8+c] = in[4*c+2] ^ s->buf[4*c+2];
+    s->state[12+c] = in[4*c+3] ^ s->buf[4*c+3];
+  }
+
+  // round 0
+  addRoundKey(s->state, &s->w[0]);
+
+  // rounds 1-9
+  for (round = 1; round <= 9; ++round) {
+    subBytes(s->state);
+    shiftRows(s->state);
+    mixColumns(s->state);
+    addRoundKey(s->state, &s->w[round * 4]);
+  }
+
+  // round 10
+  subBytes(s->state);
+  shiftRows(s->state);
+  addRoundKey(s->state, &s->w[10 * 4]);
+
+  for (c = 0; c < 4; ++c) {
+    s->buf[4*c] = s->state[c];
+    s->buf[4*c+1] = s->state[4+c];
+    s->buf[4*c+2] = s->state[8+c];
+    s->buf[4*c+3] = s->state[12+c];
+  }
+
+  s->bufIdx = 0;
+}
+
 static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last) {
   int c, round, n, i;
 
@@ -792,6 +977,43 @@ static void aes256KeyExpansion(DecryptAES256State *s,
   }
 }
 
+static void aes256EncryptBlock(DecryptAES256State *s, Guchar *in) {
+  int c, round;
+
+  // initial state (input is xor'd with previous output because of CBC)
+  for (c = 0; c < 4; ++c) {
+    s->state[c] = in[4*c] ^ s->buf[4*c];
+    s->state[4+c] = in[4*c+1] ^ s->buf[4*c+1];
+    s->state[8+c] = in[4*c+2] ^ s->buf[4*c+2];
+    s->state[12+c] = in[4*c+3] ^ s->buf[4*c+3];
+  }
+
+  // round 0
+  addRoundKey(s->state, &s->w[0]);
+
+  // rounds 1-13
+  for (round = 1; round <= 13; ++round) {
+    subBytes(s->state);
+    shiftRows(s->state);
+    mixColumns(s->state);
+    addRoundKey(s->state, &s->w[round * 4]);
+  }
+
+  // round 14
+  subBytes(s->state);
+  shiftRows(s->state);
+  addRoundKey(s->state, &s->w[14 * 4]);
+
+  for (c = 0; c < 4; ++c) {
+    s->buf[4*c] = s->state[c];
+    s->buf[4*c+1] = s->state[4+c];
+    s->buf[4*c+2] = s->state[8+c];
+    s->buf[4*c+3] = s->state[12+c];
+  }
+
+  s->bufIdx = 0;
+}
+
 static void aes256DecryptBlock(DecryptAES256State *s, Guchar *in, GBool last) {
   int c, round, n, i;
 
diff --git a/poppler/Decrypt.h b/poppler/Decrypt.h
index 237d551..c64c8c1 100644
--- a/poppler/Decrypt.h
+++ b/poppler/Decrypt.h
@@ -67,6 +67,13 @@ private:
 // Helper classes
 //------------------------------------------------------------------------
 
+/* DecryptRC4State, DecryptAESState, DecryptAES256State are named like this for
+ * historical reasons, but they're used for encryption too.
+ * In case of decryption, the cbc field in AES and AES-256 contains the previous
+ * input block or the CBC initialization vector (IV) if the stream has just been
+ * reset). In case of encryption, it always contains the IV, whereas the
+ * previous output is kept in buf. The paddingReached field is only used in
+ * case of encryption. */
 struct DecryptRC4State {
   Guchar state[256];
   Guchar x, y;
@@ -77,6 +84,7 @@ struct DecryptAESState {
   Guchar state[16];
   Guchar cbc[16];
   Guchar buf[16];
+  GBool paddingReached; // encryption only
   int bufIdx;
 };
 
@@ -85,6 +93,7 @@ struct DecryptAES256State {
   Guchar state[16];
   Guchar cbc[16];
   Guchar buf[16];
+  GBool paddingReached; // encryption only
   int bufIdx;
 };
 
@@ -117,9 +126,19 @@ protected:
 };
 
 //------------------------------------------------------------------------
-// DecryptStream
+// EncryptStream / DecryptStream
 //------------------------------------------------------------------------
 
+class EncryptStream : public BaseCryptStream {
+public:
+
+  EncryptStream(Stream *strA, Guchar *fileKey, CryptAlgorithm algoA,
+                int keyLength, int objNum, int objGen);
+  ~EncryptStream();
+  virtual void reset();
+  virtual int lookChar();
+};
+
 class DecryptStream : public BaseCryptStream {
 public:
 
commit ba6ff179aa78a42a384166ace2df80101cfbe7b9
Author: Fabio D'Urso <fabiodurso at hotmail.it>
Date:   Thu Aug 2 18:56:29 2012 +0200

    Refactoring of Decrypt.cc/.h in preparation for encryption support

diff --git a/poppler/Decrypt.cc b/poppler/Decrypt.cc
index 24af996..272edc2 100644
--- a/poppler/Decrypt.cc
+++ b/poppler/Decrypt.cc
@@ -17,6 +17,7 @@
 // Copyright (C) 2008, 2010 Albert Astals Cid <aacid at kde.org>
 // Copyright (C) 2009 Matthias Franz <matthias at ktug.or.kr>
 // Copyright (C) 2009 David Benjamin <davidben at mit.edu>
+// Copyright (C) 2012 Fabio D'Urso <fabiodurso at hotmail.it>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -34,12 +35,17 @@
 #include "Decrypt.h"
 #include "Error.h"
 
-static void aesKeyExpansion(DecryptAESState *s,
-			    Guchar *objKey, int objKeyLen);
+static void rc4InitKey(Guchar *key, int keyLen, Guchar *state);
+static Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c);
+
+static GBool aesReadBlock(Stream  *str, Guchar *in, GBool addPadding);
+
+static void aesKeyExpansion(DecryptAESState *s, Guchar *objKey, int objKeyLen, GBool decrypt);
 static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last);
-static void aes256KeyExpansion(DecryptAES256State *s,
-			       Guchar *objKey, int objKeyLen);
+
+static void aes256KeyExpansion(DecryptAES256State *s, Guchar *objKey, int objKeyLen, GBool decrypt);
 static void aes256DecryptBlock(DecryptAES256State *s, Guchar *in, GBool last);
+
 static void sha256(Guchar *msg, int msgLen, Guchar *hash);
 
 static const Guchar passwordPad[32] = {
@@ -90,7 +96,7 @@ GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength,
 	memcpy(test + len, ownerKey->getCString() + 40, 8);
 	memcpy(test + len + 8, userKey->getCString(), 48);
 	sha256(test, len + 56, test);
-	aes256KeyExpansion(&state, test, 32);
+	aes256KeyExpansion(&state, test, 32, gTrue);
 	for (i = 0; i < 16; ++i) {
 	  state.cbc[i] = 0;
 	}
@@ -121,7 +127,7 @@ GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength,
 	memcpy(test, userPassword->getCString(), len);
 	memcpy(test + len, userKey->getCString() + 40, 8);
 	sha256(test, len + 8, test);
-	aes256KeyExpansion(&state, test, 32);
+	aes256KeyExpansion(&state, test, 32, gTrue);
 	for (i = 0; i < 16; ++i) {
 	  state.cbc[i] = 0;
 	}
@@ -269,12 +275,11 @@ GBool Decrypt::makeFileKey2(int encVersion, int encRevision, int keyLength,
 }
 
 //------------------------------------------------------------------------
-// DecryptStream
+// BaseCryptStream
 //------------------------------------------------------------------------
 
-DecryptStream::DecryptStream(Stream *strA, Guchar *fileKey,
-			     CryptAlgorithm algoA, int keyLength,
-			     int objNum, int objGen):
+BaseCryptStream::BaseCryptStream(Stream *strA, Guchar *fileKey, CryptAlgorithm algoA,
+				 int keyLength, int objNum, int objGen):
   FilterStream(strA)
 {
   int i;
@@ -320,30 +325,65 @@ DecryptStream::DecryptStream(Stream *strA, Guchar *fileKey,
   charactersRead = 0;
 }
 
-DecryptStream::~DecryptStream() {
+BaseCryptStream::~BaseCryptStream() {
   delete str;
 }
 
+void BaseCryptStream::reset() {
+  charactersRead = 0;
+  nextCharBuff = EOF;
+  str->reset();
+}
+
+int BaseCryptStream::getPos() {
+  return charactersRead;
+}
+
+int BaseCryptStream::getChar() {
+  // Read next character and empty the buffer, so that a new character will be read next time
+  int c = lookChar();
+  nextCharBuff = EOF;
+
+  if (c != EOF)
+    charactersRead++;
+  return c;
+}
+
+GBool BaseCryptStream::isBinary(GBool last) {
+  return str->isBinary(last);
+}
+
+//------------------------------------------------------------------------
+// DecryptStream
+//------------------------------------------------------------------------
+
+DecryptStream::DecryptStream(Stream *strA, Guchar *fileKey, CryptAlgorithm algoA,
+			     int keyLength, int objNum, int objGen):
+  BaseCryptStream(strA, fileKey, algoA, keyLength, objNum, objGen)
+{
+}
+
+DecryptStream::~DecryptStream() {
+}
+
 void DecryptStream::reset() {
   int i;
+  BaseCryptStream::reset();
 
-  charactersRead = 0;
-  str->reset();
   switch (algo) {
   case cryptRC4:
     state.rc4.x = state.rc4.y = 0;
     rc4InitKey(objKey, objKeyLength, state.rc4.state);
-    state.rc4.buf = EOF;
     break;
   case cryptAES:
-    aesKeyExpansion(&state.aes, objKey, objKeyLength);
+    aesKeyExpansion(&state.aes, objKey, objKeyLength, gTrue);
     for (i = 0; i < 16; ++i) {
       state.aes.cbc[i] = str->getChar();
     }
     state.aes.bufIdx = 16;
     break;
   case cryptAES256:
-    aes256KeyExpansion(&state.aes256, objKey, objKeyLength);
+    aes256KeyExpansion(&state.aes256, objKey, objKeyLength, gTrue);
     for (i = 0; i < 16; ++i) {
       state.aes256.cbc[i] = str->getChar();
     }
@@ -352,36 +392,25 @@ void DecryptStream::reset() {
   }
 }
 
-int DecryptStream::getPos() {
-  return charactersRead;
-}
-
-int DecryptStream::getChar() {
+int DecryptStream::lookChar() {
   Guchar in[16];
-  int c, i;
+  int c;
+
+  if (nextCharBuff != EOF)
+    return nextCharBuff;
 
   c = EOF; // make gcc happy
   switch (algo) {
   case cryptRC4:
-    if (state.rc4.buf == EOF) {
-      c = str->getChar();
-      if (c != EOF) {
-	state.rc4.buf = rc4DecryptByte(state.rc4.state, &state.rc4.x,
-				       &state.rc4.y, (Guchar)c);
-      }
+    if ((c = str->getChar()) != EOF) {
+      c = rc4DecryptByte(state.rc4.state, &state.rc4.x, &state.rc4.y, (Guchar)c);
     }
-    c = state.rc4.buf;
-    state.rc4.buf = EOF;
     break;
   case cryptAES:
     if (state.aes.bufIdx == 16) {
-      for (i = 0; i < 16; ++i) {
-	if ((c = str->getChar()) == EOF) {
-	  return EOF;
-	}
-	in[i] = (Guchar)c;
+      if (aesReadBlock(str, in, gFalse)) {
+        aesDecryptBlock(&state.aes, in, str->lookChar() == EOF);
       }
-      aesDecryptBlock(&state.aes, in, str->lookChar() == EOF);
     }
     if (state.aes.bufIdx == 16) {
       c = EOF;
@@ -391,13 +420,9 @@ int DecryptStream::getChar() {
     break;
   case cryptAES256:
     if (state.aes256.bufIdx == 16) {
-      for (i = 0; i < 16; ++i) {
-	if ((c = str->getChar()) == EOF) {
-	  return EOF;
-	}
-	in[i] = (Guchar)c;
+      if (aesReadBlock(str, in, gFalse)) {
+        aes256DecryptBlock(&state.aes256, in, str->lookChar() == EOF);
       }
-      aes256DecryptBlock(&state.aes256, in, str->lookChar() == EOF);
     }
     if (state.aes256.bufIdx == 16) {
       c = EOF;
@@ -406,72 +431,14 @@ int DecryptStream::getChar() {
     }
     break;
   }
-  if (c != EOF)
-    charactersRead++;
-  return c;
-}
-
-int DecryptStream::lookChar() {
-  Guchar in[16];
-  int c, i;
-
-  c = EOF; // make gcc happy
-  switch (algo) {
-  case cryptRC4:
-    if (state.rc4.buf == EOF) {
-      c = str->getChar();
-      if (c != EOF) {
-	state.rc4.buf = rc4DecryptByte(state.rc4.state, &state.rc4.x,
-				       &state.rc4.y, (Guchar)c);
-      }
-    }
-    c = state.rc4.buf;
-    break;
-  case cryptAES:
-    if (state.aes.bufIdx == 16) {
-      for (i = 0; i < 16; ++i) {
-	if ((c = str->getChar()) == EOF) {
-	  return EOF;
-	}
-	in[i] = c;
-      }
-      aesDecryptBlock(&state.aes, in, str->lookChar() == EOF);
-    }
-    if (state.aes.bufIdx == 16) {
-      c = EOF;
-    } else {
-      c = state.aes.buf[state.aes.bufIdx];
-    }
-    break;
-  case cryptAES256:
-    if (state.aes256.bufIdx == 16) {
-      for (i = 0; i < 16; ++i) {
-	if ((c = str->getChar()) == EOF) {
-	  return EOF;
-	}
-	in[i] = c;
-      }
-      aes256DecryptBlock(&state.aes256, in, str->lookChar() == EOF);
-    }
-    if (state.aes256.bufIdx == 16) {
-      c = EOF;
-    } else {
-      c = state.aes256.buf[state.aes256.bufIdx];
-    }
-    break;
-  }
-  return c;
-}
-
-GBool DecryptStream::isBinary(GBool last) {
-  return str->isBinary(last);
+  return (nextCharBuff = c);
 }
 
 //------------------------------------------------------------------------
 // RC4-compatible decryption
 //------------------------------------------------------------------------
 
-void rc4InitKey(Guchar *key, int keyLen, Guchar *state) {
+static void rc4InitKey(Guchar *key, int keyLen, Guchar *state) {
   Guchar index1, index2;
   Guchar t;
   int i;
@@ -492,7 +459,7 @@ void rc4InitKey(Guchar *key, int keyLen, Guchar *state) {
   }
 }
 
-Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c) {
+static Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c) {
   Guchar x1, y1, tx, ty;
 
   x1 = *x = (*x + 1) % 256;
@@ -508,6 +475,32 @@ Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c) {
 // AES decryption
 //------------------------------------------------------------------------
 
+// Returns gFalse if EOF was reached, gTrue otherwise
+static GBool aesReadBlock(Stream *str, Guchar *in, GBool addPadding)
+{
+  int c, i;
+
+  for (i = 0; i < 16; ++i) {
+    if ((c = str->getChar()) != EOF) {
+      in[i] = (Guchar)c;
+    } else {
+      break;
+    }
+  }
+
+  if (i == 16) {
+    return gTrue;
+  } else {
+    if (addPadding) {
+      c = 16 - i;
+      while (i < 16) {
+        in[i++] = (Guchar)c;
+      }
+    }
+    return gFalse;
+  }
+}
+
 static const Guchar sbox[256] = {
   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,
@@ -686,7 +679,7 @@ static inline void addRoundKey(Guchar *state, Guint *w) {
 }
 
 static void aesKeyExpansion(DecryptAESState *s,
-			    Guchar *objKey, int /*objKeyLen*/) {
+			    Guchar *objKey, int /*objKeyLen*/, GBool decrypt) {
   Guint temp;
   int i, round;
 
@@ -703,8 +696,12 @@ static void aesKeyExpansion(DecryptAESState *s,
     }
     s->w[i] = s->w[i-4] ^ temp;
   }
-  for (round = 1; round <= 9; ++round) {
-    invMixColumnsW(&s->w[round * 4]);
+
+  /* In case of decryption, adjust the key schedule for the equivalent inverse cipher */
+  if (decrypt) {
+    for (round = 1; round <= 9; ++round) {
+      invMixColumnsW(&s->w[round * 4]);
+    }
   }
 }
 
@@ -767,7 +764,7 @@ static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last) {
 //------------------------------------------------------------------------
 
 static void aes256KeyExpansion(DecryptAES256State *s,
-			       Guchar *objKey, int objKeyLen) {
+			       Guchar *objKey, int objKeyLen, GBool decrypt) {
   Guint temp;
   int i, round;
 
@@ -786,8 +783,12 @@ static void aes256KeyExpansion(DecryptAES256State *s,
     }
     s->w[i] = s->w[i-8] ^ temp;
   }
-  for (round = 1; round <= 13; ++round) {
-    invMixColumnsW(&s->w[round * 4]);
+
+  /* In case of decryption, adjust the key schedule for the equivalent inverse cipher */
+  if (decrypt) {
+    for (round = 1; round <= 13; ++round) {
+      invMixColumnsW(&s->w[round * 4]);
+    }
   }
 }
 
diff --git a/poppler/Decrypt.h b/poppler/Decrypt.h
index d947f41..237d551 100644
--- a/poppler/Decrypt.h
+++ b/poppler/Decrypt.h
@@ -15,6 +15,7 @@
 //
 // Copyright (C) 2008 Julien Rebetez <julien at fhtagn.net>
 // Copyright (C) 2009 David Benjamin <davidben at mit.edu>
+// Copyright (C) 2012 Fabio D'Urso <fabiodurso at hotmail.it>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -63,13 +64,12 @@ private:
 };
 
 //------------------------------------------------------------------------
-// DecryptStream
+// Helper classes
 //------------------------------------------------------------------------
 
 struct DecryptRC4State {
   Guchar state[256];
   Guchar x, y;
-  int buf;
 };
 
 struct DecryptAESState {
@@ -88,27 +88,26 @@ struct DecryptAES256State {
   int bufIdx;
 };
 
-class DecryptStream: public FilterStream {
+class BaseCryptStream : public FilterStream {
 public:
 
-  DecryptStream(Stream *strA, Guchar *fileKey,
-		CryptAlgorithm algoA, int keyLength,
-		int objNum, int objGen);
-  virtual ~DecryptStream();
+  BaseCryptStream(Stream *strA, Guchar *fileKey, CryptAlgorithm algoA,
+                  int keyLength, int objNum, int objGen);
+  virtual ~BaseCryptStream();
   virtual StreamKind getKind() { return strWeird; }
   virtual void reset();
   virtual int getChar();
-  virtual int lookChar();
+  virtual int lookChar() = 0;
   virtual int getPos();
   virtual GBool isBinary(GBool last);
   virtual Stream *getUndecodedStream() { return this; }
 
-private:
-
+protected:
   CryptAlgorithm algo;
   int objKeyLength;
   Guchar objKey[32];
   int charactersRead; // so that getPos() can be correct
+  int nextCharBuff;   // EOF means not read yet
 
   union {
     DecryptRC4State rc4;
@@ -116,11 +115,23 @@ private:
     DecryptAES256State aes256;
   } state;
 };
+
+//------------------------------------------------------------------------
+// DecryptStream
+//------------------------------------------------------------------------
+
+class DecryptStream : public BaseCryptStream {
+public:
+
+  DecryptStream(Stream *strA, Guchar *fileKey, CryptAlgorithm algoA,
+                int keyLength, int objNum, int objGen);
+  ~DecryptStream();
+  virtual void reset();
+  virtual int lookChar();
+};
  
 //------------------------------------------------------------------------
 
-extern void rc4InitKey(Guchar *key, int keyLen, Guchar *state);
-extern Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c);
 extern void md5(Guchar *msg, int msgLen, Guchar *digest);
 
 #endif


More information about the poppler mailing list