summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDongHun Kwak <dh0128.kwak@samsung.com>2016-11-21 14:39:07 +0900
committerDongHun Kwak <dh0128.kwak@samsung.com>2016-11-21 14:39:08 +0900
commit5c1d53fa67372eddb895aa8fa63e312c38cd9029 (patch)
tree6f0a9346ffafe6a7f7e9a3ac02281b8efe7c9a8c
parentf61cd1fa36fc1529f22b2fa7855d3ff6b9e33762 (diff)
downloadqpdf-5c1d53fa67372eddb895aa8fa63e312c38cd9029.tar.gz
qpdf-5c1d53fa67372eddb895aa8fa63e312c38cd9029.tar.bz2
qpdf-5c1d53fa67372eddb895aa8fa63e312c38cd9029.zip
Imported Upstream version 4.0.0
Change-Id: Ic246819002cfe4aa9660c4ca2b05add1b8dab626 Signed-off-by: DongHun Kwak <dh0128.kwak@samsung.com>
-rw-r--r--ChangeLog143
-rw-r--r--README29
-rw-r--r--README.maintainer12
-rw-r--r--TODO90
-rwxr-xr-xconfigure18
-rw-r--r--configure.ac2
-rw-r--r--doc/qpdf-manual.html215
-rw-r--r--doc/qpdf-manual.pdfbin146522 -> 159042 bytes
-rw-r--r--include/qpdf/Buffer.hh2
-rw-r--r--include/qpdf/BufferInputSource.hh8
-rw-r--r--include/qpdf/Constants.h2
-rw-r--r--include/qpdf/DLL.h2
-rw-r--r--include/qpdf/FileInputSource.hh8
-rw-r--r--include/qpdf/InputSource.hh8
-rw-r--r--include/qpdf/Pipeline.hh10
-rw-r--r--include/qpdf/Pl_Buffer.hh2
-rw-r--r--include/qpdf/Pl_Concatenate.hh8
-rw-r--r--include/qpdf/Pl_Count.hh2
-rw-r--r--include/qpdf/Pl_Discard.hh2
-rw-r--r--include/qpdf/Pl_Flate.hh2
-rw-r--r--include/qpdf/Pl_StdioFile.hh2
-rw-r--r--include/qpdf/PointerHolder.hh2
-rw-r--r--include/qpdf/QPDF.hh89
-rw-r--r--include/qpdf/QPDFExc.hh2
-rw-r--r--include/qpdf/QPDFObject.hh2
-rw-r--r--include/qpdf/QPDFObjectHandle.hh2
-rw-r--r--include/qpdf/QPDFTokenizer.hh2
-rw-r--r--include/qpdf/QPDFWriter.hh50
-rw-r--r--include/qpdf/QPDFXRefEntry.hh2
-rw-r--r--include/qpdf/QTC.hh2
-rw-r--r--include/qpdf/QUtil.hh14
-rw-r--r--include/qpdf/Types.h8
-rw-r--r--include/qpdf/qpdf-c.h28
-rw-r--r--ispell-words112
-rw-r--r--libqpdf.map2
-rw-r--r--libqpdf.pc.in1
-rw-r--r--libqpdf/OffsetInputSource.cc61
-rw-r--r--libqpdf/Pl_AES_PDF.cc112
-rw-r--r--libqpdf/Pl_SHA2.cc164
-rw-r--r--libqpdf/QPDF.cc145
-rw-r--r--libqpdf/QPDFWriter.cc611
-rw-r--r--libqpdf/QPDF_Stream.cc250
-rw-r--r--libqpdf/QPDF_encryption.cc801
-rw-r--r--libqpdf/QPDF_optimization.cc101
-rw-r--r--libqpdf/QUtil.cc39
-rw-r--r--libqpdf/build.mk19
-rw-r--r--libqpdf/qpdf-c.cc48
-rw-r--r--libqpdf/qpdf/OffsetInputSource.hh29
-rw-r--r--libqpdf/qpdf/Pl_AES_PDF.hh28
-rw-r--r--libqpdf/qpdf/Pl_SHA2.hh50
-rw-r--r--libqpdf/qpdf/QPDF_Stream.hh3
-rw-r--r--libqpdf/sha2.c690
-rw-r--r--libqpdf/sha2big.c247
-rw-r--r--libqpdf/sph/md_helper.c346
-rw-r--r--libqpdf/sph/sph_sha2.h378
-rw-r--r--libqpdf/sph/sph_types.h1976
-rw-r--r--libtests/aes.cc116
-rw-r--r--libtests/build.mk3
-rw-r--r--libtests/qtest/sha2.test18
-rw-r--r--libtests/qtest/sha2/sha2.out9
-rw-r--r--libtests/sha2.cc69
-rwxr-xr-xmake_dist1
-rwxr-xr-xmake_windows_releases2
-rwxr-xr-xmake_windows_releases-msvc1
-rw-r--r--manual/qpdf-manual.xml289
-rw-r--r--qpdf.spec2
-rw-r--r--qpdf/qpdf-ctest.c40
-rw-r--r--qpdf/qpdf.cc122
-rw-r--r--qpdf/qpdf.testcov25
-rw-r--r--qpdf/qtest/qpdf.test266
-rw-r--r--qpdf/qtest/qpdf/V5R5.out20
-rw-r--r--qpdf/qtest/qpdf/V5R6.out20
-rw-r--r--qpdf/qtest/qpdf/attachments.out6
-rw-r--r--qpdf/qtest/qpdf/c-decrypt-R5-with-user.pdfbin0 -> 9731 bytes
-rw-r--r--qpdf/qtest/qpdf/c-decrypt-R6-with-owner.pdfbin0 -> 9731 bytes
-rw-r--r--qpdf/qtest/qpdf/c-min-version.out4
-rw-r--r--qpdf/qtest/qpdf/c-r5-in.pdfbin0 -> 11195 bytes
-rw-r--r--qpdf/qtest/qpdf/c-r5.out20
-rw-r--r--qpdf/qtest/qpdf/c-r6-in.pdfbin0 -> 11195 bytes
-rw-r--r--qpdf/qtest/qpdf/c-r6.out20
-rw-r--r--qpdf/qtest/qpdf/custom-pipeline.pdfbin0 -> 799 bytes
-rw-r--r--qpdf/qtest/qpdf/damaged-stream-c-check.out6
-rwxr-xr-xqpdf/qtest/qpdf/diff-encrypted2
-rw-r--r--qpdf/qtest/qpdf/enc-XI-R6,V5,O=master.pdfbin0 -> 10933 bytes
-rw-r--r--qpdf/qtest/qpdf/enc-XI-R6,V5,U=attachment,encrypted-attachments.pdfbin0 -> 17868 bytes
-rw-r--r--qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,O=master.pdfbin0 -> 15670 bytes
-rw-r--r--qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdfbin0 -> 22400 bytes
-rw-r--r--qpdf/qtest/qpdf/enc-XI-attachments-base.pdfbin0 -> 20751 bytes
-rw-r--r--qpdf/qtest/qpdf/enc-XI-base.pdfbin0 -> 14041 bytes
-rw-r--r--qpdf/qtest/qpdf/enc-XI-long-password.pdfbin0 -> 10915 bytes
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-force-1.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-force-1.6.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-force-1.7.1.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-force-1.7.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-force-1.7.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-force-1.8.0.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-force-1.8.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.pdfbin0 -> 865 bytes
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.qdf107
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-force-1.8.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-min-1.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-min-1.6.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-min-1.7.1.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-min-1.7.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-min-1.7.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-min-1.8.0.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-min-1.8.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-min-1.8.5.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-min-1.8.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-force-1.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-force-1.6.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.1.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.0.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.pdfbin0 -> 923 bytes
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.qdf111
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-min-1.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-min-1.6.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.1.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.0.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.5.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe-other.pdf104
-rw-r--r--qpdf/qtest/qpdf/extensions-adbe.pdf106
-rw-r--r--qpdf/qtest/qpdf/extensions-none-force-1.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-force-1.6.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-force-1.7.1.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-force-1.7.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-force-1.7.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-force-1.8.0.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-force-1.8.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-force-1.8.5.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-force-1.8.5.pdfbin0 -> 865 bytes
-rw-r--r--qpdf/qtest/qpdf/extensions-none-force-1.8.5.qdf107
-rw-r--r--qpdf/qtest/qpdf/extensions-none-force-1.8.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-min-1.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-min-1.6.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-min-1.7.1.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-min-1.7.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-min-1.7.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-min-1.8.0.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-min-1.8.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-min-1.8.5.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-none-min-1.8.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-force-1.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-force-1.6.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-force-1.7.1.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-force-1.7.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-force-1.7.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-force-1.8.0.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-force-1.8.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-force-1.8.5.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-force-1.8.5.pdfbin0 -> 923 bytes
-rw-r--r--qpdf/qtest/qpdf/extensions-other-force-1.8.5.qdf111
-rw-r--r--qpdf/qtest/qpdf/extensions-other-force-1.8.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-min-1.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-min-1.6.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-min-1.7.1.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-min-1.7.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-min-1.7.3.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-min-1.8.0.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-min-1.8.2.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-min-1.8.5.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other-min-1.8.out4
-rw-r--r--qpdf/qtest/qpdf/extensions-other.pdf98
-rw-r--r--qpdf/qtest/qpdf/good13.qdf46
-rw-r--r--qpdf/qtest/qpdf/good5.qdf50
-rw-r--r--qpdf/qtest/qpdf/good8.qdf50
-rw-r--r--qpdf/qtest/qpdf/leading-junk.out17
-rw-r--r--qpdf/qtest/qpdf/leading-junk.pdfbin0 -> 13670 bytes
-rw-r--r--qpdf/qtest/qpdf/lin-special.disable.expbin3178 -> 3092 bytes
-rw-r--r--qpdf/qtest/qpdf/lin-special.generate.expbin2808 -> 2849 bytes
-rw-r--r--qpdf/qtest/qpdf/lin-special.preserve.expbin3178 -> 3092 bytes
-rw-r--r--qpdf/qtest/qpdf/obj0-check.out6
-rw-r--r--qpdf/qtest/qpdf/object-stream.disable.expbin1310 -> 1285 bytes
-rw-r--r--qpdf/qtest/qpdf/object-stream.generate.expbin1536 -> 1373 bytes
-rw-r--r--qpdf/qtest/qpdf/object-stream.preserve.expbin1536 -> 1373 bytes
-rw-r--r--qpdf/qtest/qpdf/test4-1.qdf58
-rw-r--r--qpdf/qtest/qpdf/unfilterable-with-crypt-after.out4
-rw-r--r--qpdf/qtest/qpdf/unfilterable-with-crypt-before.out4
-rw-r--r--qpdf/qtest/qpdf/unfilterable-with-crypt.pdfbin0 -> 17868 bytes
-rw-r--r--qpdf/qtest/qpdf/unreferenced-indirect-scalar.outbin1402 -> 1405 bytes
-rw-r--r--qpdf/qtest/qpdf/zero-offset.out5
-rw-r--r--qpdf/qtest/qpdf/zero-offset.pdfbin0 -> 80688 bytes
-rw-r--r--qpdf/test_driver.cc163
193 files changed, 8641 insertions, 838 deletions
diff --git a/ChangeLog b/ChangeLog
index fd15bda..71369f9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,146 @@
+2012-12-31 Jay Berkenbilt <ejb@ql.org>
+
+ * 4.0.0: release
+
+ * Add new methods qpdf_get_pdf_extension_level,
+ qpdf_set_r5_encryption_parameters,
+ qpdf_set_r6_encryption_parameters,
+ qpdf_set_minimum_pdf_version_and_extension, and
+ qpdf_force_pdf_version_and_extension to support new functionality
+ from the C API.
+
+2012-12-30 Jay Berkenbilt <ejb@ql.org>
+
+ * Fix long-standing bug that could theoretically have resulted in
+ possible misinterpretation of decode parameters in streams. As
+ far as I can tell, it is extremely unlikely that files with the
+ characteristics that would have triggered the bug actually exist
+ in cases that qpdf versions prior to 4.0.0 could have read.
+ Unencrypted files with encrypted attachments would have triggered
+ this bug, but qpdf versions prior to 4.0.0 already refused to open
+ such files.
+
+ * Fix long-standing bug in which a stream that used a crypt
+ filter and was otherwise not filterable by qpdf would be decrypted
+ properly but would retain the crypt filter indication in the
+ file. There are no known ways to create files like this, so it is
+ unlikely that anyone ever hit this bug.
+
+2012-12-29 Jay Berkenbilt <ejb@ql.org>
+
+ * Add read/write support for both the deprecated Acrobat IX
+ encryption format and the Acrobat X/PDF 2.0 encryption format
+ using 256-bit AES keys. Using the Acrobat IX format (R=5) forces
+ the version of the file to 1.7 with extension level 3. Using the
+ PDF 2.0 format (R=6) forces it to 1.7 extension level 8.
+
+ * Add new method QPDF::getEncryptionKey to return the actual
+ encryption key used for encryption of data in the file. The key
+ is returned as a std::string.
+
+ * Non-compatible API change: change signature of
+ QPDF::compute_data_key to take the R and V values from the
+ encryption dictionary. There is no reason for any application
+ code to call this method since handling of encryption is done
+ automatically by the qpdf libary. It is used internally by
+ QPDFWriter.
+
+ * Support reading and decryption of files whose main text is not
+ encrypted but whose attachments are. More generally, support the
+ case of files and streams encrypted differently with some
+ limitations, described in the documentation. This was not
+ previously supported due to lack of test files, but I created test
+ files using a trial version of Acrobat XI to fully implement this
+ case.
+
+ * Incorporate sha2 code from sphlib 3.0. See README for
+ licensing. Create private pipeline class for computing hashes
+ with sha256, sha384, and sha512.
+
+ * Allow specification of initialization vector when using AES
+ filtering. This is required to compute the hash used in /R=6 (PDF
+ 2.0) encryption.
+
+2012-12-28 Jay Berkenbilt <ejb@ql.org>
+
+ * Add random number generation functions to QUtil.
+
+ * Fix old bug that could cause an infinite loop if user password
+ recovery methods were called and a password contained the "("
+ character (which happens to be the first byte of padding used by
+ older PDF encryption formats). This bug was noticed while reading
+ code and would not happen under ordinary usage patterns even if
+ the password contained that character.
+
+2012-12-27 Jay Berkenbilt <ejb@ql.org>
+
+ * Add awareness of extension level to PDF Version methods for both
+ reading and writing. This includes adding method
+ QPDF::getExtensionLevel and new versions of
+ QPDFWriter::setMinimumPDFVersion and QPDFWriter::forcePDFVersion
+ that support extension levels. The qpdf command-line tool
+ interprets version numbers of the form x.y.z as version x.y at
+ extension level z.
+
+ * Update AES classes to support use of 256-bit keys.
+
+ * Non-compatible API change: Removed public method
+ QPDF::flattenScalarReferences. Instead, just flatten the scalar
+ references we actually need to flatten. Flattening scalar
+ references was a wrong decision years ago and has occasionally
+ caused other problems, among which were that it caused qpdf to
+ visit otherwise unreferenced and possibly erroneous objects in the
+ file when it didn't have to. There's no reason that any
+ non-internal code would have had to call this.
+
+ * Non-compatible API change: Removed public method
+ QPDF::decodeStreams which was previously used by qpdf --check but
+ is no longer used. The decodeStreams method could generate false
+ positives since it would attempt to access all objects in the file
+ including those that were not referenced. There's no reason that
+ any non-internal code would have had to call this.
+
+ * Non-compatible API change: Removed public method
+ QPDF::trimTrailerForWrite, which was only intended for use by
+ QPDFWriter and which is no longer used.
+
+2012-12-26 Jay Berkenbilt <ejb@ql.org>
+
+ * Add new fields to QPDF::EncryptionData to support newer
+ encryption formats (V=5, R=5 and R=6)
+
+ * Non-compatible API change: Change public nested class
+ QPDF::EncryptionData to make all member fields private and to add
+ method calls. This is a non-compatible API change, but changing
+ EncryptionData is necessary to support newer encryption formats,
+ and making this change will prevent the need from making a
+ non-compatible change in the future if new fields are added. A
+ public nested class should never have had public members to begin
+ with.
+
+2012-12-25 Jay Berkenbilt <ejb@ql.org>
+
+ * Allow PDF header to appear anywhere in the first 1024 bytes of
+ the file as recommended in the implementation notes of the Adobe
+ version of the PDF spec.
+
+2012-11-20 Jay Berkenbilt <ejb@ql.org>
+
+ * Add zlib and libpcre to Requires.private in the pkg-config file
+ to support static linking. Thanks Tobias Hoffmann for pointing
+ out the omission.
+
+ * Ignore (with warning) non-freed objects in the xref table whose
+ offset is 0. Some PDF producers (incorrectly) do this. See
+ https://bugs.linuxfoundation.org/show_bug.cgi?id=1081.
+
+2012-09-23 Jay Berkenbilt <ejb@ql.org>
+
+ * Add public methods QPDF::processInputSource and
+ QPDFWriter::setOutputPipeline to allow users to read from custom
+ input sources and to write to custom pipelines. This allows the
+ maximum flexibility in sources for reading and writing PDF files.
+
2012-09-06 Jay Berkenbilt <ejb@ql.org>
* 3.0.2: release
diff --git a/README b/README
index 2d82a22..e73d669 100644
--- a/README
+++ b/README
@@ -2,7 +2,7 @@ This is the QPDF package. Information about it can be found at
http://qpdf.sourceforge.net. The source code repository is hosted
at github: https://github.com/qpdf/qpdf.
-QPDF is copyright (c) 2005-2012 Jay Berkenbilt
+QPDF is copyright (c) 2005-2013 Jay Berkenbilt
This software may be distributed under the terms of version 2 of the
Artistic License which may be found in the source distribution as
@@ -48,6 +48,33 @@ obtained from
http://www.efgh.com/software/rijndael.htm
http://www.efgh.com/software/rijndael.txt
+The embedded sha2 code comes from sphlib 3.0
+
+ http://www.saphir2.com/sphlib/
+
+That code has the following license:
+
+ Copyright (c) 2007-2011 Projet RNRT SAPHIR
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
Building on UNIX/Linux
======================
diff --git a/README.maintainer b/README.maintainer
index b150eeb..4be45f5 100644
--- a/README.maintainer
+++ b/README.maintainer
@@ -28,7 +28,7 @@ Release Reminders
You can generate these by running valgrind with --gen-suppressions=yes.
- * Check all open issues in the sourceforge trackers.
+ * Check all open issues in the sourceforge trackers and on github.
* If any interfaces were added or changed, check C API to see whether
changes are appropriate there as well.
@@ -58,7 +58,7 @@ Release Reminders
done or retargeted.
* Each year, update copyright notices. Just search for Copyright.
- Last updated: 2012.
+ Last updated: 2013.
* To construct a source distribution from a pristine checkout,
make_dist does the following:
@@ -91,8 +91,10 @@ Release Reminders
libraries. Run ./make_windows_releases from there. You will need
to have zip in your path.
+ * Before releasing, rebuild and test debian package.
+
* Remember to copy README-what-to-download.txt separately onto the
- download area and make it the default for Windows platforms.
+ download area.
* Remember to update the web page including putting new documentation
in the "files" subdirectory of the website on sourceforge.net.
@@ -104,7 +106,9 @@ Release Reminders
git tag -s release-qpdf-$version HEAD -m'qpdf $version'
* When releasing on sourceforge, external-libs distributions go in
- external-libs/yyyymmdd, and qpdf distributions go in qpdf/vvv
+ external-libs/yyyymmdd, and qpdf distributions go in qpdf/vvv.
+ Make the source package the default for all but Windows, and make
+ the 32-bit mingw build the default for Windows.
General Build Stuff
diff --git a/TODO b/TODO
index ff3ecc8..ec797aa 100644
--- a/TODO
+++ b/TODO
@@ -1,59 +1,22 @@
General
=======
- * See if I can support the encryption format used with /R 5 /V 5,
- even though a qpdf-announce subscriber with an adobe.com email
- address mentioned that this is deprecated. There is also a new
- encryption format coming in a future release, which may be better
- to support. As of the qpdf 3.0 release, the specification was not
- publicly available yet.
+ * Improve the random number seed to make it more secure so that we
+ have stronger random numbers, particularly when multiple files are
+ generated in the same second. This code may need to be
+ OS-specific. Probably we should add a method in QUtil to seed with
+ a strong random number and call this automatically the first time
+ QUtil::random() is called.
* Consider the possibility of doing something locale-aware to support
non-ASCII passwords. Update documentation if this is done.
- * Look for %PDF header somewhere within the first 1024 bytes of the
- file. Also accept headers of the form "%!PS−Adobe−N.n PDF−M.m".
- See Implementation notes 13 and 14 in appendix H of the PDF 1.7
- specification. This is bug 3267974.
-
* Consider impact of article threads on page splitting/merging.
Subramanyam provided a test file; see ../misc/article-threads.pdf.
Email Q-Count: 431864 from 2009-11-03. Other things to consider:
outlines, page labels, thumbnails, zones. There are probably
others.
- * See whether it's possible to remove the call to
- flattenScalarReferences. I can't easily figure out why I do it,
- but removing it causes strange test failures in linearization. I
- would have to study the optimization and linearization code to
- figure out why I added this to begin with and what in the code
- assumes it's the case. For enqueueObject and unparseChild in
- QPDFWriter, simply removing the checks for indirect scalars seems
- sufficient. Looking back at the branch in the apex epub
- repository, before flattening scalar references, there was special
- case code in QPDFWriter to avoid writing out indirect nulls. It's
- still not obvious to me why I did it though.
-
- To pursue this, remove the call to flattenScalarReferences in
- QPDFWriter.cc and disable the logic_error exceptions for indirect
- scalars. Just search for flattenScalarReferences in QPDFWriter.cc
- since the logic errors have comments that mention
- flattenScalarReferences. Then run the test suite. Several files
- that explicitly test flattening of scalar references fail, but the
- indirect scalars are properly preserved and written. But then
- there are some linearized files that have a bunch of unreferenced
- objects that contain scalars. Need to figure out what these are
- and why they're there. Maybe they're objects that used to be
- stream lengths. Probably we just need to make sure don't traverse
- through a stream's /Length stream when enqueueing stream
- dictionaries. This could potentially happen with any object that
- QPDFWriter replaces when writing out files. Such objects would be
- orphaned in the newly written file. This could be fixed, but it
- may not be worth fixing.
-
- If flattenScalarReferences is removed, a new method will be needed
- for checking PDF files.
-
* See if we can avoid preserving unreferenced objects in object
streams even when preserving the object streams.
@@ -95,31 +58,30 @@ Index: QPDFWriter.cc
}
------------------------------
- * Handle embedded files. PDF Reference 1.7 section 3.10, "File
- Specifications", discusses this. Once we can definitely recognize
- all embedded files in a document, we can update the encryption
- code to handle it properly. In QPDF_encryption.cc, search for
- cf_file. Remove exception thrown if cf_file is different from
- cf_stream, and write code in the stream decryption section to use
- cf_file instead of cf_stream. In general, add interfaces to get
- the list of embedded files and to extract them. To handle general
- embedded files associated with the whole document, follow root ->
- /Names -> /EmbeddedFiles -> /Names to get to the file specification
- dictionaries. Then, in each file specification dictionary, follow
- /EF -> /F to the actual stream. There may be other places file
- specification dictionaries may appear, and there are also /RF keys
- with related files, so reread section 3.10 carefully.
+ * Provide APIs for embedded files. See *attachments*.pdf in test
+ suite. The private method findAttachmentStreams finds at least
+ cases for modern versions of Adobe Reader (>= 1.7, maybe earlier).
+ PDF Reference 1.7 section 3.10, "File Specifications", discusses
+ this.
+
+ A sourceforge user asks if qpdf can handle extracting and embedded
+ resources and references these tools, which may be useful as a
+ reference.
+
+ http://multivalent.sourceforge.net/Tools/pdf/Extract.html
+ http://multivalent.sourceforge.net/Tools/pdf/Embed.html
* The description of Crypt filters is unclear with respect to how to
use them to override /StmF for specific streams. I'm not sure
whether qpdf will do the right thing for any specific individual
- streams that might have crypt filters. The specification seems to
- imply that only embedded file streams and metadata streams can have
- crypt filters, and there are already special cases in the code to
- handle those. Most likely, it won't be a problem, but someday
- someone may find a file that qpdf doesn't work on because of crypt
- filters. There is an example in the spec of using a crypt filter
- on a metadata stream.
+ streams that might have crypt filters, but I believe it does based
+ on my testing of a limited subset. The specification seems to imply
+ that only embedded file streams and metadata streams can have crypt
+ filters, and there are already special cases in the code to handle
+ those. Most likely, it won't be a problem, but someday someone may
+ find a file that qpdf doesn't work on because of crypt filters.
+ There is an example in the spec of using a crypt filter on a
+ metadata stream.
For now, we notice /Crypt filters and decode parameters consistent
with the example in the PDF specification, and the right thing
diff --git a/configure b/configure
index 2b33603..0a7280f 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for qpdf 3.0.2.
+# Generated by GNU Autoconf 2.69 for qpdf 4.0.0.
#
#
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
@@ -587,8 +587,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='qpdf'
PACKAGE_TARNAME='qpdf'
-PACKAGE_VERSION='3.0.2'
-PACKAGE_STRING='qpdf 3.0.2'
+PACKAGE_VERSION='4.0.0'
+PACKAGE_STRING='qpdf 4.0.0'
PACKAGE_BUGREPORT=''
PACKAGE_URL=''
@@ -1307,7 +1307,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures qpdf 3.0.2 to adapt to many kinds of systems.
+\`configure' configures qpdf 4.0.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1372,7 +1372,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of qpdf 3.0.2:";;
+ short | recursive ) echo "Configuration of qpdf 4.0.0:";;
esac
cat <<\_ACEOF
@@ -1506,7 +1506,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-qpdf configure 3.0.2
+qpdf configure 4.0.0
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2046,7 +2046,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by qpdf $as_me 3.0.2, which was
+It was created by qpdf $as_me 4.0.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -16390,7 +16390,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by qpdf $as_me 3.0.2, which was
+This file was extended by qpdf $as_me 4.0.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -16456,7 +16456,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-qpdf config.status 3.0.2
+qpdf config.status 4.0.0
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
diff --git a/configure.ac b/configure.ac
index 20a27a9..dff0bd1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ dnl Process this file with autoconf to produce a configure script.
dnl This config.in requires autoconf 2.5 or greater.
AC_PREREQ([2.68])
-AC_INIT([qpdf],[3.0.2])
+AC_INIT([qpdf],[4.0.0])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_FILES([autoconf.mk])
diff --git a/doc/qpdf-manual.html b/doc/qpdf-manual.html
index 060d6e9..51f005a 100644
--- a/doc/qpdf-manual.html
+++ b/doc/qpdf-manual.html
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>QPDF Manual</title><link rel="stylesheet" type="text/css" href="stylesheet.css" /><meta name="generator" content="DocBook XSL Stylesheets V1.76.1" /></head><body><div class="book" title="QPDF Manual"><div class="titlepage"><div><div><h1 class="title"><a id="idm5674192"></a>QPDF Manual</h1></div><div><h2 class="subtitle">For QPDF Version 3.0.2, September 6, 2012</h2></div><div><div class="author"><h3 class="author"><span class="firstname">Jay</span> <span class="surname">Berkenbilt</span></h3></div></div><div><p class="copyright">Copyright © 2005–2012 Jay Berkenbilt</p></div></div><hr /></div><div class="toc"><p><strong>Table of Contents</strong></p><dl><dt><span class="preface"><a href="#acknowledgments">General Information</a></span></dt><dt><span class="chapter"><a href="#ref.overview">1. What is QPDF?</a></span></dt><dt><span class="chapter"><a href="#ref.installing">2. Building and Installing QPDF</a></span></dt><dd><dl><dt><span class="sect1"><a href="#ref.prerequisites">2.1. System Requirements</a></span></dt><dt><span class="sect1"><a href="#ref.building">2.2. Build Instructions</a></span></dt></dl></dd><dt><span class="chapter"><a href="#ref.using">3. Running QPDF</a></span></dt><dd><dl><dt><span class="sect1"><a href="#ref.invocation">3.1. Basic Invocation</a></span></dt><dt><span class="sect1"><a href="#ref.basic-options">3.2. Basic Options</a></span></dt><dt><span class="sect1"><a href="#ref.encryption-options">3.3. Encryption Options</a></span></dt><dt><span class="sect1"><a href="#ref.page-selection">3.4. Page Selection Options</a></span></dt><dt><span class="sect1"><a href="#ref.advanced-transformation">3.5. Advanced Transformation Options</a></span></dt><dt><span class="sect1"><a href="#ref.testing-options">3.6. Testing, Inspection, and Debugging Options</a></span></dt></dl></dd><dt><span class="chapter"><a href="#ref.qdf">4. QDF Mode</a></span></dt><dt><span class="chapter"><a href="#ref.using-library">5. Using the QPDF Library</a></span></dt><dt><span class="chapter"><a href="#ref.design">6. Design and Library Notes</a></span></dt><dd><dl><dt><span class="sect1"><a href="#ref.design.intro">6.1. Introduction</a></span></dt><dt><span class="sect1"><a href="#ref.design-goals">6.2. Design Goals</a></span></dt><dt><span class="sect1"><a href="#ref.encryption">6.3. Encryption</a></span></dt><dt><span class="sect1"><a href="#ref.adding-and-remove-pages">6.4. Adding and Removing Pages</a></span></dt><dt><span class="sect1"><a href="#ref.reserved-objects">6.5. Reserving Object Numbers</a></span></dt><dt><span class="sect1"><a href="#ref.foreign-objects">6.6. Copying Objects From Other PDF Files</a></span></dt><dt><span class="sect1"><a href="#ref.rewriting">6.7. Writing PDF Files</a></span></dt><dt><span class="sect1"><a href="#ref.filtered-streams">6.8. Filtered Streams</a></span></dt></dl></dd><dt><span class="chapter"><a href="#ref.linearization">7. Linearization</a></span></dt><dd><dl><dt><span class="sect1"><a href="#ref.linearization-strategy">7.1. Basic Strategy for Linearization</a></span></dt><dt><span class="sect1"><a href="#ref.linearized.preparation">7.2. Preparing For Linearization</a></span></dt><dt><span class="sect1"><a href="#ref.optimization">7.3. Optimization</a></span></dt><dt><span class="sect1"><a href="#ref.linearization.writing">7.4. Writing Linearized Files</a></span></dt><dt><span class="sect1"><a href="#ref.linearization-data">7.5. Calculating Linearization Data</a></span></dt><dt><span class="sect1"><a href="#ref.linearization-issues">7.6. Known Issues with Linearization</a></span></dt><dt><span class="sect1"><a href="#ref.linearization-debugging">7.7. Debugging Note</a></span></dt></dl></dd><dt><span class="chapter"><a href="#ref.object-and-xref-streams">8. Object and Cross-Reference Streams</a></span></dt><dd><dl><dt><span class="sect1"><a href="#ref.object-streams">8.1. Object Streams</a></span></dt><dt><span class="sect1"><a href="#ref.xref-streams">8.2. Cross-Reference Streams</a></span></dt><dd><dl><dt><span class="sect2"><a href="#ref.xref-stream-data">8.2.1. Cross-Reference Stream Data</a></span></dt></dl></dd><dt><span class="sect1"><a href="#ref.object-streams-linearization">8.3. Implications for Linearized Files</a></span></dt><dt><span class="sect1"><a href="#ref.object-stream-implementation">8.4. Implementation Notes</a></span></dt></dl></dd><dt><span class="appendix"><a href="#ref.release-notes">A. Release Notes</a></span></dt><dt><span class="appendix"><a href="#ref.upgrading-to-2.1">B. Upgrading from 2.0 to 2.1</a></span></dt><dt><span class="appendix"><a href="#ref.upgrading-to-3.0">C. Upgrading to 3.0</a></span></dt></dl></div><div class="preface" title="General Information"><div class="titlepage"><div><div><h2 class="title"><a id="acknowledgments"></a>General Information</h2></div></div></div><p>
+<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>QPDF Manual</title><link rel="stylesheet" type="text/css" href="stylesheet.css" /><meta name="generator" content="DocBook XSL Stylesheets V1.76.1" /></head><body><div class="book" title="QPDF Manual"><div class="titlepage"><div><div><h1 class="title"><a id="idm5653216"></a>QPDF Manual</h1></div><div><h2 class="subtitle">For QPDF Version 4.0.0, December 31, 2012</h2></div><div><div class="author"><h3 class="author"><span class="firstname">Jay</span> <span class="surname">Berkenbilt</span></h3></div></div><div><p class="copyright">Copyright © 2005–2013 Jay Berkenbilt</p></div></div><hr /></div><div class="toc"><p><strong>Table of Contents</strong></p><dl><dt><span class="preface"><a href="#acknowledgments">General Information</a></span></dt><dt><span class="chapter"><a href="#ref.overview">1. What is QPDF?</a></span></dt><dt><span class="chapter"><a href="#ref.installing">2. Building and Installing QPDF</a></span></dt><dd><dl><dt><span class="sect1"><a href="#ref.prerequisites">2.1. System Requirements</a></span></dt><dt><span class="sect1"><a href="#ref.building">2.2. Build Instructions</a></span></dt></dl></dd><dt><span class="chapter"><a href="#ref.using">3. Running QPDF</a></span></dt><dd><dl><dt><span class="sect1"><a href="#ref.invocation">3.1. Basic Invocation</a></span></dt><dt><span class="sect1"><a href="#ref.basic-options">3.2. Basic Options</a></span></dt><dt><span class="sect1"><a href="#ref.encryption-options">3.3. Encryption Options</a></span></dt><dt><span class="sect1"><a href="#ref.page-selection">3.4. Page Selection Options</a></span></dt><dt><span class="sect1"><a href="#ref.advanced-transformation">3.5. Advanced Transformation Options</a></span></dt><dt><span class="sect1"><a href="#ref.testing-options">3.6. Testing, Inspection, and Debugging Options</a></span></dt></dl></dd><dt><span class="chapter"><a href="#ref.qdf">4. QDF Mode</a></span></dt><dt><span class="chapter"><a href="#ref.using-library">5. Using the QPDF Library</a></span></dt><dt><span class="chapter"><a href="#ref.design">6. Design and Library Notes</a></span></dt><dd><dl><dt><span class="sect1"><a href="#ref.design.intro">6.1. Introduction</a></span></dt><dt><span class="sect1"><a href="#ref.design-goals">6.2. Design Goals</a></span></dt><dt><span class="sect1"><a href="#ref.encryption">6.3. Encryption</a></span></dt><dt><span class="sect1"><a href="#ref.adding-and-remove-pages">6.4. Adding and Removing Pages</a></span></dt><dt><span class="sect1"><a href="#ref.reserved-objects">6.5. Reserving Object Numbers</a></span></dt><dt><span class="sect1"><a href="#ref.foreign-objects">6.6. Copying Objects From Other PDF Files</a></span></dt><dt><span class="sect1"><a href="#ref.rewriting">6.7. Writing PDF Files</a></span></dt><dt><span class="sect1"><a href="#ref.filtered-streams">6.8. Filtered Streams</a></span></dt></dl></dd><dt><span class="chapter"><a href="#ref.linearization">7. Linearization</a></span></dt><dd><dl><dt><span class="sect1"><a href="#ref.linearization-strategy">7.1. Basic Strategy for Linearization</a></span></dt><dt><span class="sect1"><a href="#ref.linearized.preparation">7.2. Preparing For Linearization</a></span></dt><dt><span class="sect1"><a href="#ref.optimization">7.3. Optimization</a></span></dt><dt><span class="sect1"><a href="#ref.linearization.writing">7.4. Writing Linearized Files</a></span></dt><dt><span class="sect1"><a href="#ref.linearization-data">7.5. Calculating Linearization Data</a></span></dt><dt><span class="sect1"><a href="#ref.linearization-issues">7.6. Known Issues with Linearization</a></span></dt><dt><span class="sect1"><a href="#ref.linearization-debugging">7.7. Debugging Note</a></span></dt></dl></dd><dt><span class="chapter"><a href="#ref.object-and-xref-streams">8. Object and Cross-Reference Streams</a></span></dt><dd><dl><dt><span class="sect1"><a href="#ref.object-streams">8.1. Object Streams</a></span></dt><dt><span class="sect1"><a href="#ref.xref-streams">8.2. Cross-Reference Streams</a></span></dt><dd><dl><dt><span class="sect2"><a href="#ref.xref-stream-data">8.2.1. Cross-Reference Stream Data</a></span></dt></dl></dd><dt><span class="sect1"><a href="#ref.object-streams-linearization">8.3. Implications for Linearized Files</a></span></dt><dt><span class="sect1"><a href="#ref.object-stream-implementation">8.4. Implementation Notes</a></span></dt></dl></dd><dt><span class="appendix"><a href="#ref.release-notes">A. Release Notes</a></span></dt><dt><span class="appendix"><a href="#ref.upgrading-to-2.1">B. Upgrading from 2.0 to 2.1</a></span></dt><dt><span class="appendix"><a href="#ref.upgrading-to-3.0">C. Upgrading to 3.0</a></span></dt><dt><span class="appendix"><a href="#ref.upgrading-to-4.0">D. Upgrading to 4.0</a></span></dt></dl></div><div class="preface" title="General Information"><div class="titlepage"><div><div><h2 class="title"><a id="acknowledgments"></a>General Information</h2></div></div></div><p>
QPDF is a program that does structural, content-preserving
transformations on PDF files. QPDF's website is located at <a class="ulink" href="http://qpdf.sourceforge.net/" target="_top">http://qpdf.sourceforge.net/</a>.
QPDF's source code is hosted on github at <a class="ulink" href="https://github.com/qpdf/qpdf" target="_top">https://github.com/qpdf/qpdf</a>.
@@ -253,8 +253,8 @@ make
empty strings.
</p><p>
The value for
- <code class="option"><em class="replaceable"><code>key-length</code></em></code> may be 40
- or 128. The restriction flags are dependent upon key length.
+ <code class="option"><em class="replaceable"><code>key-length</code></em></code> may be 40,
+ 128, or 256. The restriction flags are dependent upon key length.
When no additional restrictions are given, the default is to be
fully permissive.
</p><p>
@@ -321,6 +321,25 @@ make
in testing qpdf itself. This option also forces the PDF
version to be at least 1.5.
</p></dd></dl></div><p>
+ If <code class="option"><em class="replaceable"><code>key-length</code></em></code> is 256,
+ the minimum PDF version is 1.7 with extension level 8, and the
+ AES-based encryption format used is the PDF 2.0 encryption method
+ supported by Acrobat X. the same options are available as with
+ 128 bits with the following exceptions:
+ </p><div class="variablelist"><dl><dt><span class="term"><code class="option">--use-aes</code></span></dt><dd><p>
+ This option is not available with 256-bit keys. AES is always
+ used with 256-bit encryption keys.
+ </p></dd><dt><span class="term"><code class="option">--force-V4</code></span></dt><dd><p>
+ This option is not available with 256 keys.
+ </p></dd><dt><span class="term"><code class="option">--force-R5</code></span></dt><dd><p>
+ If specified, qpdf sets the minimum version to 1.7 at
+ extension level 3 and writes the deprecated encryption format
+ used by Acrobat version IX. This option should not be used in
+ practice to generate PDF files that will be in general use,
+ but it can be useful to generate files if you are trying to
+ test proper support in another application for PDF files
+ encrypted in this way.
+ </p></dd></dl></div><p>
The default for each permission option is to be fully permissive.
</p></div><div class="sect1" title="3.4. Page Selection Options"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="ref.page-selection"></a>3.4. Page Selection Options</h2></div></div></div><p>
Starting with qpdf 3.0, it is possible to split and merge PDF
@@ -471,17 +490,31 @@ outfile.pdf</code>
used. It is seldom necessary to use this option since qpdf
will automatically increase the version as needed when adding
features that require newer PDF readers.
+ </p><p>
+ The version number may be expressed in the form
+ <em class="replaceable"><code>major.minor.extension-level</code></em>, in
+ which case the version is interpreted as
+ <em class="replaceable"><code>major.minor</code></em> at extension level
+ <em class="replaceable"><code>extension-level</code></em>. For example,
+ version <code class="literal">1.7.8</code> represents version 1.7 at
+ extension level 8. Note that minimal syntax checking is done
+ on the command line.
</p></dd><dt><span class="term"><code class="option">--force-version=<em class="replaceable"><code>version</code></em></code></span></dt><dd><p>
This option forces the PDF version to be the exact version
specified <span class="emphasis"><em>even when the file may have content that
- is not supported in that version</em></span>. In some cases,
- forcing the output file's PDF version to be lower than that of
- the input file will cause qpdf to disable certain features of
- the document. Specifically, AES encryption is disabled if the
- version is less than 1.6, cleartext metadata and object
- streams are disabled if less than 1.5, 128-bit encryption keys
- are disabled if less than 1.4, and all encryption is disabled
- if less than 1.3. Even with these precautions, qpdf won't be
+ is not supported in that version</em></span>. The version
+ number is interpreted in the same way as with
+ <code class="option">--min-version</code> so that extension levels can be
+ set. In some cases, forcing the output file's PDF version to
+ be lower than that of the input file will cause qpdf to
+ disable certain features of the document. Specifically,
+ 256-bit keys are disabled if the version is less than 1.7 with
+ extension level 8 (except R5 is disabled if less than 1.7 with
+ extension level 3), AES encryption is disabled if the version
+ is less than 1.6, cleartext metadata and object streams are
+ disabled if less than 1.5, 128-bit encryption keys are
+ disabled if less than 1.4, and all encryption is disabled if
+ less than 1.3. Even with these precautions, qpdf won't be
able to do things like eliminate use of newer image
compression schemes, transparency groups, or other features
that may have been added in more recent versions of PDF.
@@ -927,7 +960,7 @@ outfile.pdf</code>
password-protected files. QPDF does not enforce encryption
parameters and will treat user and owner passwords equivalently.
Either password may be used to access an encrypted file.
- <sup>[<a id="idp382528" href="#ftn.idp382528" class="footnote">1</a>]</sup>
+ <sup>[<a id="idp395824" href="#ftn.idp395824" class="footnote">1</a>]</sup>
<code class="classname">QPDF</code> will allow recovery of a user password
given an owner password. The input PDF file must be seekable.
(Output files written by <code class="classname">QPDFWriter</code> need
@@ -1037,6 +1070,29 @@ outfile.pdf</code>
strings given an encryption key. This is used by
<code class="classname">QPDFWriter</code> when it rewrites encrypted
files.
+ </p><p>
+ When copying encrypted files, unless otherwise directed, qpdf will
+ preserve any encryption in force in the original file. qpdf can
+ do this with either the user or the owner password. There is no
+ difference in capability based on which password is used. When 40
+ or 128 bit encryption keys are used, the user password can be
+ recovered with the owner password. With 256 keys, the user and
+ owner passwords are used independently to encrypt the actual
+ encryption key, so while either can be used, the owner password
+ can no longer be used to recover the user password.
+ </p><p>
+ Starting with version 4.0.0, qpdf can read files that are not
+ encrypted but that contain encrypted attachments, but it cannot
+ write such files. qpdf also requires the password to be specified
+ in order to open the file, not just to extract attachments, since
+ once the file is open, all decryption is handled transparently.
+ When copying files like this while preserving encryption, qpdf
+ will apply the file's encryption to everything in the file, not
+ just to the attachments. When decrypting the file, qpdf will
+ decrypt the attachments. In general, when copying PDF files with
+ multiple encryption formats, qpdf will choose the newest format.
+ The only exception to this is that clear-text metadata will be
+ preserved as clear-text if it is that way in the original file.
</p></div><div class="sect1" title="6.4. Adding and Removing Pages"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="ref.adding-and-remove-pages"></a>6.4. Adding and Removing Pages</h2></div></div></div><p>
While qpdf's API has supported adding and modifying objects for
some time, version 3.0 introduces specific methods for adding and
@@ -1222,7 +1278,7 @@ outfile.pdf</code>
filter should write to whatever type of output is required. The
<code class="classname">QPDF</code> class has an interface to write raw or
filtered stream contents to a given pipeline.
- </p></div><div class="footnotes"><br /><hr width="100" align="left" /><div class="footnote"><p><sup>[<a id="ftn.idp382528" href="#idp382528" class="para">1</a>] </sup>
+ </p></div><div class="footnotes"><br /><hr width="100" align="left" /><div class="footnote"><p><sup>[<a id="ftn.idp395824" href="#idp395824" class="para">1</a>] </sup>
As pointed out earlier, the intention is not for qpdf to be used
to bypass security on files. but as any open source PDF consumer
may be easily modified to bypass basic PDF document security,
@@ -1585,7 +1641,130 @@ print "\n";
</p></div></div><div class="appendix" title="Appendix A. Release Notes"><div class="titlepage"><div><div><h2 class="title"><a id="ref.release-notes"></a>Appendix A. Release Notes</h2></div></div></div><p>
For a detailed list of changes, please see the file
<code class="filename">ChangeLog</code> in the source distribution.
- </p><div class="variablelist"><dl><dt><span class="term">3.0.2: September 6, 2012</span></dt><dd><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
+ </p><div class="variablelist"><dl><dt><span class="term">4.0.0: December 31, 2012</span></dt><dd><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
+ Major enhancement: support has been added for newer encryption
+ schemes supported by version X of Adobe Acrobat. This
+ includes use of 127-character passwords, 256-bit encryption
+ keys, and the encryption scheme specified in ISO 32000-2, the
+ PDF 2.0 specification. This scheme can be chosen from the
+ command line by specifying use of 256-bit keys. qpdf also
+ supports the deprecated encryption method used by Acrobat IX.
+ This encryption style has known security weaknesses and should
+ not be used in practice. However, such files exist “in
+ the wild,†so support for this scheme is still useful.
+ New methods
+ <code class="function">QPDFWriter::setR6EncryptionParameters</code>
+ (for the PDF 2.0 scheme) and
+ <code class="function">QPDFWriter::setR5EncryptionParameters</code>
+ (for the deprecated scheme) have been added to enable these
+ new encryption schemes. Corresponding functions have been
+ added to the C API as well.
+ </p></li><li class="listitem"><p>
+ Full support for Adobe extension levels in PDF version
+ information. Starting with PDF version 1.7, corresponding to
+ ISO 32000, Adobe adds new functionality by increasing the
+ extension level rather than increasing the version. This
+ support includes addition of the
+ <code class="function">QPDF::getExtensionLevel</code> method for
+ retrieving the document's extension level, addition of
+ versions of
+ <code class="function">QPDFWriter::setMinimumPDFVersion</code> and
+ <code class="function">QPDFWriter::forcePDFVersion</code> that accept
+ an extension level, and extended syntax for specifying forced
+ and minimum versions on the command line as described in <a class="xref" href="#ref.advanced-transformation" title="3.5. Advanced Transformation Options">Section 3.5, “Advanced Transformation Optionsâ€</a>. Corresponding
+ functions have been added to the C API as well.
+ </p></li><li class="listitem"><p>
+ Minor fixes to prevent qpdf from referencing objects in the
+ file that are not referenced in the file's overall structure.
+ Most files don't have any such objects, but some files have
+ contain unreferenced objects with errors, so these fixes
+ prevent qpdf from needlessly rejecting or complaining about
+ such objects.
+ </p></li><li class="listitem"><p>
+ Add new generalized methods for reading and writing files
+ from/to programmer-defined sources. The method
+ <code class="function">QPDF::processInputSource</code> allows the
+ programmer to use any input source for the input file, and
+ <code class="function">QPDFWriter::setOutputPipeline</code> allows the
+ programmer to write the output file through any pipeline.
+ These methods would make it possible to perform any number of
+ specialized operations, such as accessing external storage
+ systems, creating bindings for qpdf in other programming
+ languages that have their own I/O systems, etc.
+ </p></li><li class="listitem"><p>
+ Add new method <code class="function">QPDF::getEncryptionKey</code> for
+ retrieving the underlying encryption key used in the file.
+ </p></li><li class="listitem"><p>
+ This release includes a small handful of non-compatible API
+ changes. While effort is made to avoid such changes, all the
+ non-compatible API changes in this version were to parts of
+ the API that would likely never be used outside the library
+ itself. In all cases, the altered methods or structures were
+ parts of the <code class="classname">QPDF</code> that were public to
+ enable them to be called from either
+ <code class="classname">QPDFWriter</code> or were part of validation
+ code that was over-zealous in reporting problems in parts of
+ the file that would not ordinarily be referenced. In no case
+ did any of the removed methods do anything worse that falsely
+ report error conditions in files that were broken in ways that
+ didn't matter. The following public parts of the
+ <code class="classname">QPDF</code> class were changed in a
+ non-compatible way:
+ </p><div class="itemizedlist"><ul class="itemizedlist" type="circle"><li class="listitem"><p>
+ Updated nested <code class="classname">QPDF::EncryptionData</code>
+ class to add fields needed by the newer encryption formats,
+ member variables changed to private so that future changes
+ will not require breaking backward compatibility.
+ </p></li><li class="listitem"><p>
+ Added additional parameters to
+ <code class="function">compute_data_key</code>, which is used by
+ <code class="classname">QPDFWriter</code> to compute the encryption
+ key used to encrypt a specific object.
+ </p></li><li class="listitem"><p>
+ Removed the method
+ <code class="function">flattenScalarReferences</code>. This method
+ was previously used prior to writing a new PDF file, but it
+ has the undesired side effect of causing qpdf to read
+ objects in the file that were not referenced. Some
+ otherwise files have unreferenced objects with errors in
+ them, so this could cause qpdf to reject files that would
+ be accepted by virtually all other PDF readers. In fact,
+ qpdf relied on only a very small part of what
+ flattenScalarReferences did, so only this part has been
+ preserved, and it is now done directly inside
+ <code class="classname">QPDFWriter</code>.
+ </p></li><li class="listitem"><p>
+ Removed the method <code class="function">decodeStreams</code>.
+ This method was used by the <code class="option">--check</code> option
+ of the <span class="command"><strong>qpdf</strong></span> command-line tool to force
+ all streams in the file to be decoded, but it also suffered
+ from the problem of opening otherwise unreferenced streams
+ and thus could report false positive. The
+ <code class="option">--check</code> option now causes qpdf to go
+ through all the motions of writing a new file based on the
+ original one, so it will always reference and check exactly
+ those parts of a file that any ordinary viewer would check.
+ </p></li><li class="listitem"><p>
+ Removed the method
+ <code class="function">trimTrailerForWrite</code>. This method was
+ used by <code class="classname">QPDFWriter</code> to modify the
+ original QPDF object by removing fields from the trailer
+ dictionary that wouldn't apply to the newly written file.
+ This functionality, though generally harmless, was a poor
+ implementation and has been replaced by having QPDFWriter
+ filter these out when copying the trailer rather than
+ modifying the original QPDF object. (Note that qpdf never
+ modifies the original file itself.)
+ </p></li></ul></div><p>
+ </p></li><li class="listitem"><p>
+ Allow the PDF header to appear anywhere in the first 1024
+ bytes of the file. This is consistent with what other readers
+ do.
+ </p></li><li class="listitem"><p>
+ Fix the <span class="command"><strong>pkg-config</strong></span> files to list zlib and
+ pcre in <code class="function">Requires.private</code> to better
+ support static linking using <span class="command"><strong>pkg-config</strong></span>.
+ </p></li></ul></div></dd></dl></div><div class="variablelist"><dl><dt><span class="term">3.0.2: September 6, 2012</span></dt><dd><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
Bug fix: <code class="function">QPDFWriter::setOutputMemory</code> did
not work when not used with
<code class="function">QPDFWriter::setStaticID</code>, which made it
@@ -2047,4 +2226,12 @@ print "\n";
previously been smaller types. This change was required to
support files larger than two gigabytes in size.
</p></li></ul></div><p>
+ </p></div><div class="appendix" title="Appendix D. Upgrading to 4.0"><div class="titlepage"><div><div><h2 class="title"><a id="ref.upgrading-to-4.0"></a>Appendix D. Upgrading to 4.0</h2></div></div></div><p>
+ While version 4.0 includes a few non-compatible API changes, it is
+ very unlikely that anyone's code would have used any of those parts
+ of the API since they generally required information that would
+ only be available inside the library. In the unlikely event that
+ you should run into trouble, please see the ChangeLog. See also
+ <a class="xref" href="#ref.release-notes" title="Appendix A. Release Notes">Appendix A, <em>Release Notes</em></a> for a complete list of the
+ non-compatible API changes made in this version.
</p></div></div></body></html>
diff --git a/doc/qpdf-manual.pdf b/doc/qpdf-manual.pdf
index 75f6223..91323bf 100644
--- a/doc/qpdf-manual.pdf
+++ b/doc/qpdf-manual.pdf
Binary files differ
diff --git a/include/qpdf/Buffer.hh b/include/qpdf/Buffer.hh
index 7fabe9a..22e1f34 100644
--- a/include/qpdf/Buffer.hh
+++ b/include/qpdf/Buffer.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/BufferInputSource.hh b/include/qpdf/BufferInputSource.hh
index 48f6e3a..222f71e 100644
--- a/include/qpdf/BufferInputSource.hh
+++ b/include/qpdf/BufferInputSource.hh
@@ -1,3 +1,11 @@
+/* Copyright (c) 2005-2013 Jay Berkenbilt
+ *
+ * This file is part of qpdf. This software may be distributed under
+ * the terms of version 2 of the Artistic License which may be found
+ * in the source distribution. It is provided "as is" without express
+ * or implied warranty.
+ */
+
#ifndef __QPDF_BUFFERINPUTSOURCE_HH__
#define __QPDF_BUFFERINPUTSOURCE_HH__
diff --git a/include/qpdf/Constants.h b/include/qpdf/Constants.h
index 176392e..6d34020 100644
--- a/include/qpdf/Constants.h
+++ b/include/qpdf/Constants.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2005-2012 Jay Berkenbilt
+/* Copyright (c) 2005-2013 Jay Berkenbilt
*
* This file is part of qpdf. This software may be distributed under
* the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/DLL.h b/include/qpdf/DLL.h
index 640e4f6..c2c85dc 100644
--- a/include/qpdf/DLL.h
+++ b/include/qpdf/DLL.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2005-2012 Jay Berkenbilt
+/* Copyright (c) 2005-2013 Jay Berkenbilt
*
* This file is part of qpdf. This software may be distributed under
* the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/FileInputSource.hh b/include/qpdf/FileInputSource.hh
index 6129326..001af17 100644
--- a/include/qpdf/FileInputSource.hh
+++ b/include/qpdf/FileInputSource.hh
@@ -1,3 +1,11 @@
+/* Copyright (c) 2005-2013 Jay Berkenbilt
+ *
+ * This file is part of qpdf. This software may be distributed under
+ * the terms of version 2 of the Artistic License which may be found
+ * in the source distribution. It is provided "as is" without express
+ * or implied warranty.
+ */
+
#ifndef __QPDF_FILEINPUTSOURCE_HH__
#define __QPDF_FILEINPUTSOURCE_HH__
diff --git a/include/qpdf/InputSource.hh b/include/qpdf/InputSource.hh
index 782d888..510daac 100644
--- a/include/qpdf/InputSource.hh
+++ b/include/qpdf/InputSource.hh
@@ -1,3 +1,11 @@
+/* Copyright (c) 2005-2013 Jay Berkenbilt
+ *
+ * This file is part of qpdf. This software may be distributed under
+ * the terms of version 2 of the Artistic License which may be found
+ * in the source distribution. It is provided "as is" without express
+ * or implied warranty.
+ */
+
#ifndef __QPDF_INPUTSOURCE_HH__
#define __QPDF_INPUTSOURCE_HH__
diff --git a/include/qpdf/Pipeline.hh b/include/qpdf/Pipeline.hh
index 2de17ad..aa86003 100644
--- a/include/qpdf/Pipeline.hh
+++ b/include/qpdf/Pipeline.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
@@ -44,7 +44,13 @@ class Pipeline
// Subclasses should implement write and finish to do their jobs
// and then, if they are not end-of-line pipelines, call
- // getNext()->write or getNext()->finish.
+ // getNext()->write or getNext()->finish. It would be really nice
+ // if write could take unsigned char const*, but this would make
+ // it much more difficult to write pipelines around legacy
+ // interfaces whose calls don't want pointers to const data. As a
+ // rule, pipelines should generally not be modifying the data
+ // passed to them. They should, instead, create new data to pass
+ // downstream.
QPDF_DLL
virtual void write(unsigned char* data, size_t len) = 0;
QPDF_DLL
diff --git a/include/qpdf/Pl_Buffer.hh b/include/qpdf/Pl_Buffer.hh
index 57473fd..3019181 100644
--- a/include/qpdf/Pl_Buffer.hh
+++ b/include/qpdf/Pl_Buffer.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/Pl_Concatenate.hh b/include/qpdf/Pl_Concatenate.hh
index e8fd64e..568eb04 100644
--- a/include/qpdf/Pl_Concatenate.hh
+++ b/include/qpdf/Pl_Concatenate.hh
@@ -1,3 +1,11 @@
+/* Copyright (c) 2005-2013 Jay Berkenbilt
+ *
+ * This file is part of qpdf. This software may be distributed under
+ * the terms of version 2 of the Artistic License which may be found
+ * in the source distribution. It is provided "as is" without express
+ * or implied warranty.
+ */
+
#ifndef __PL_CONCATENATE_HH__
#define __PL_CONCATENATE_HH__
diff --git a/include/qpdf/Pl_Count.hh b/include/qpdf/Pl_Count.hh
index 7c5a418..d44e688 100644
--- a/include/qpdf/Pl_Count.hh
+++ b/include/qpdf/Pl_Count.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/Pl_Discard.hh b/include/qpdf/Pl_Discard.hh
index 79b8564..3110b45 100644
--- a/include/qpdf/Pl_Discard.hh
+++ b/include/qpdf/Pl_Discard.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/Pl_Flate.hh b/include/qpdf/Pl_Flate.hh
index 31e3de3..188a79b 100644
--- a/include/qpdf/Pl_Flate.hh
+++ b/include/qpdf/Pl_Flate.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/Pl_StdioFile.hh b/include/qpdf/Pl_StdioFile.hh
index 0a0f15d..f91e572 100644
--- a/include/qpdf/Pl_StdioFile.hh
+++ b/include/qpdf/Pl_StdioFile.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/PointerHolder.hh b/include/qpdf/PointerHolder.hh
index a2f33e9..a1f756b 100644
--- a/include/qpdf/PointerHolder.hh
+++ b/include/qpdf/PointerHolder.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh
index e594a44..d436c37 100644
--- a/include/qpdf/QPDF.hh
+++ b/include/qpdf/QPDF.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
@@ -72,6 +72,13 @@ class QPDF
char const* buf, size_t length,
char const* password = 0);
+ // Parse a PDF file loaded from a custom InputSource. If you have
+ // your own method of retrieving a PDF file, you can subclass
+ // InputSource and use this method.
+ QPDF_DLL
+ void processInputSource(PointerHolder<InputSource>,
+ char const* password = 0);
+
// Create a QPDF object for an empty PDF. This PDF has no pages
// or objects other than a minimal trailer, a document catalog,
// and a /Pages tree containing zero pages. Pages and other
@@ -137,6 +144,8 @@ class QPDF
QPDF_DLL
std::string getPDFVersion() const;
QPDF_DLL
+ int getExtensionLevel();
+ QPDF_DLL
QPDFObjectHandle getTrailer();
QPDF_DLL
QPDFObjectHandle getRoot();
@@ -215,12 +224,16 @@ class QPDF
// Encryption support
- enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes };
- struct EncryptionData
+ enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes, e_aesv3 };
+ class EncryptionData
{
+ public:
+
// This class holds data read from the encryption dictionary.
EncryptionData(int V, int R, int Length_bytes, int P,
std::string const& O, std::string const& U,
+ std::string const& OE, std::string const& UE,
+ std::string const& Perms,
std::string const& id1, bool encrypt_metadata) :
V(V),
R(R),
@@ -228,17 +241,47 @@ class QPDF
P(P),
O(O),
U(U),
+ OE(OE),
+ UE(UE),
+ Perms(Perms),
id1(id1),
encrypt_metadata(encrypt_metadata)
{
}
+ int getV() const;
+ int getR() const;
+ int getLengthBytes() const;
+ int getP() const;
+ std::string const& getO() const;
+ std::string const& getU() const;
+ std::string const& getOE() const;
+ std::string const& getUE() const;
+ std::string const& getPerms() const;
+ std::string const& getId1() const;
+ bool getEncryptMetadata() const;
+
+ void setO(std::string const&);
+ void setU(std::string const&);
+ void setV5EncryptionParameters(std::string const& O,
+ std::string const& OE,
+ std::string const& U,
+ std::string const& UE,
+ std::string const& Perms);
+
+ private:
+ EncryptionData(EncryptionData const&);
+ EncryptionData& operator=(EncryptionData const&);
+
int V;
int R;
int Length_bytes;
int P;
std::string O;
std::string U;
+ std::string OE;
+ std::string UE;
+ std::string Perms;
std::string id1;
bool encrypt_metadata;
};
@@ -283,7 +326,7 @@ class QPDF
QPDF_DLL
static std::string compute_data_key(
std::string const& encryption_key, int objid, int generation,
- bool use_aes);
+ bool use_aes, int encryption_V, int encryption_R);
QPDF_DLL
static std::string compute_encryption_key(
std::string const& password, EncryptionData const& data);
@@ -294,6 +337,14 @@ class QPDF
int V, int R, int key_len, int P, bool encrypt_metadata,
std::string const& id1,
std::string& O, std::string& U);
+ QPDF_DLL
+ static void compute_encryption_parameters_V5(
+ char const* user_password, char const* owner_password,
+ int V, int R, int key_len, int P, bool encrypt_metadata,
+ std::string const& id1,
+ std::string& encryption_key,
+ std::string& O, std::string& U,
+ std::string& OE, std::string& UE, std::string& Perms);
// Return the full user password as stored in the PDF file. If
// you are attempting to recover the user password in a
// user-presentable form, call getTrimmedUserPassword() instead.
@@ -302,6 +353,10 @@ class QPDF
// Return human-readable form of user password.
QPDF_DLL
std::string getTrimmedUserPassword() const;
+ // Return the previously computed or retrieved encryption key for
+ // this file
+ QPDF_DLL
+ std::string getEncryptionKey() const;
// Linearization support
@@ -345,24 +400,8 @@ class QPDF
void optimize(std::map<int, int> const& object_stream_data,
bool allow_changes = true);
- // Replace all references to indirect objects that are "scalars"
- // (i.e., things that don't have children: not arrays, streams, or
- // dictionaries) with direct objects.
- QPDF_DLL
- void flattenScalarReferences();
-
- // Decode all streams, discarding the output. Used to check
- // correctness of stream encoding.
- QPDF_DLL
- void decodeStreams();
-
// For QPDFWriter:
- // Remove /ID, /Encrypt, and /Prev keys from the trailer
- // dictionary since these are regenerated during write.
- QPDF_DLL
- void trimTrailerForWrite();
-
// Get lists of all objects in order according to the part of a
// linearized file that they belong to.
QPDF_DLL
@@ -577,6 +616,7 @@ class QPDF
int& act_objid, int& act_generation);
PointerHolder<QPDFObject> resolve(int objid, int generation);
void resolveObjectsInStream(int obj_stream_number);
+ void findAttachmentStreams();
// Calls finish() on the pipeline when done but does not delete it
void pipeStreamData(int objid, int generation,
@@ -600,6 +640,13 @@ class QPDF
void initializeEncryption();
std::string getKeyForObject(int objid, int generation, bool use_aes);
void decryptString(std::string&, int objid, int generation);
+ static std::string compute_encryption_key_from_password(
+ std::string const& password, EncryptionData const& data);
+ static std::string recover_encryption_key_with_password(
+ std::string const& password, EncryptionData const& data);
+ static std::string recover_encryption_key_with_password(
+ std::string const& password, EncryptionData const& data,
+ bool& perms_valid);
void decryptStream(
Pipeline*& pipeline, int objid, int generation,
QPDFObjectHandle& stream_dict,
@@ -953,6 +1000,7 @@ class QPDF
std::ostream* err_stream;
bool attempt_recovery;
int encryption_V;
+ int encryption_R;
bool encrypt_metadata;
std::map<std::string, encryption_method_e> crypt_filters;
encryption_method_e cf_stream;
@@ -977,6 +1025,7 @@ class QPDF
PointerHolder<QPDFObjectHandle::StreamDataProvider> copied_streams;
// copied_stream_data_provider is owned by copied_streams
CopiedStreamDataProvider* copied_stream_data_provider;
+ std::set<ObjGen> attachment_streams;
// Linearization data
qpdf_offset_t first_xref_item_offset; // actual value from file
diff --git a/include/qpdf/QPDFExc.hh b/include/qpdf/QPDFExc.hh
index 30d576e..c0a0598 100644
--- a/include/qpdf/QPDFExc.hh
+++ b/include/qpdf/QPDFExc.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/QPDFObject.hh b/include/qpdf/QPDFObject.hh
index 4626eb6..8bb6102 100644
--- a/include/qpdf/QPDFObject.hh
+++ b/include/qpdf/QPDFObject.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh
index 0375b83..75912e7 100644
--- a/include/qpdf/QPDFObjectHandle.hh
+++ b/include/qpdf/QPDFObjectHandle.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/QPDFTokenizer.hh b/include/qpdf/QPDFTokenizer.hh
index 6b385b4..1835fcb 100644
--- a/include/qpdf/QPDFTokenizer.hh
+++ b/include/qpdf/QPDFTokenizer.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/QPDFWriter.hh b/include/qpdf/QPDFWriter.hh
index 2c1c32f..e8b744d 100644
--- a/include/qpdf/QPDFWriter.hh
+++ b/include/qpdf/QPDFWriter.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
@@ -95,6 +95,15 @@ class QPDFWriter
QPDF_DLL
Buffer* getBuffer();
+ // Supply your own pipeline object. Output will be written to
+ // this pipeline, and QPDFWriter will call finish() on the
+ // pipeline. It is the caller's responsibility to manage the
+ // memory for the pipeline. The pipeline is never deleted by
+ // QPDFWriter, which makes it possible for you to call additional
+ // methods on the pipeline after the writing is finished.
+ QPDF_DLL
+ void setOutputPipeline(Pipeline*);
+
// Setting Parameters
// Set the value of object stream mode. In disable mode, we never
@@ -144,6 +153,8 @@ class QPDFWriter
// streams are used.
QPDF_DLL
void setMinimumPDFVersion(std::string const&);
+ QPDF_DLL
+ void setMinimumPDFVersion(std::string const&, int extension_level);
// Force the PDF version of the output file to be a given version.
// Use of this function may create PDF files that will not work
@@ -162,6 +173,8 @@ class QPDFWriter
// object streams.
QPDF_DLL
void forcePDFVersion(std::string const&);
+ QPDF_DLL
+ void forcePDFVersion(std::string const&, int extension_level);
// Provide additional text to insert in the PDF file somewhere
// near the beginning of the file. This can be used to add
@@ -210,8 +223,9 @@ class QPDFWriter
// content normalization. Note that setting R2 encryption
// parameters sets the PDF version to at least 1.3, setting R3
// encryption parameters pushes the PDF version number to at least
- // 1.4, and setting R4 parameters pushes the version to at least
- // 1.5, or if AES is used, 1.6.
+ // 1.4, setting R4 parameters pushes the version to at least 1.5,
+ // or if AES is used, 1.6, and setting R5 or R6 parameters pushes
+ // the version to at least 1.7 with extension level 3.
QPDF_DLL
void setR2EncryptionParameters(
char const* user_password, char const* owner_password,
@@ -228,6 +242,21 @@ class QPDFWriter
bool allow_accessibility, bool allow_extract,
qpdf_r3_print_e print, qpdf_r3_modify_e modify,
bool encrypt_metadata, bool use_aes);
+ // R5 is deprecated. Do not use it for production use. Writing
+ // R5 is supported by qpdf primarily to generate test files for
+ // applications that may need to test R5 support.
+ QPDF_DLL
+ void setR5EncryptionParameters(
+ char const* user_password, char const* owner_password,
+ bool allow_accessibility, bool allow_extract,
+ qpdf_r3_print_e print, qpdf_r3_modify_e modify,
+ bool encrypt_metadata);
+ QPDF_DLL
+ void setR6EncryptionParameters(
+ char const* user_password, char const* owner_password,
+ bool allow_accessibility, bool allow_extract,
+ qpdf_r3_print_e print, qpdf_r3_modify_e modify,
+ bool encrypt_metadata_aes);
// Create linearized output. Disables qdf mode, content
// normalization, and stream prefiltering.
@@ -277,7 +306,8 @@ class QPDFWriter
char const* user_password, char const* owner_password,
bool allow_accessibility, bool allow_extract,
qpdf_r3_print_e print, qpdf_r3_modify_e modify);
- void disableIncompatibleEncryption(int major, int minor);
+ void disableIncompatibleEncryption(int major, int minor,
+ int extension_level);
void parseVersion(std::string const& version, int& major, int& minor) const;
int compareVersions(int major1, int minor1, int major2, int minor2) const;
void setEncryptionParameters(
@@ -286,10 +316,14 @@ class QPDFWriter
void setEncryptionParametersInternal(
int V, int R, int key_len, long P,
std::string const& O, std::string const& U,
- std::string const& id1, std::string const& user_password);
+ std::string const& OE, std::string const& UE, std::string const& Perms,
+ std::string const& id1, std::string const& user_password,
+ std::string const& encryption_key);
void setDataKey(int objid);
int openObject(int objid = 0);
void closeObject(int objid);
+ QPDFObjectHandle getTrimmedTrailer();
+ void prepareFileForWrite();
void writeStandard();
void writeLinearized();
void enqueuePart(std::vector<QPDFObjectHandle>& part);
@@ -361,11 +395,17 @@ class QPDFWriter
bool encrypt_metadata;
bool encrypt_use_aes;
std::map<std::string, std::string> encryption_dictionary;
+ int encryption_V;
+ int encryption_R;
std::string id1; // for /ID key of
std::string id2; // trailer dictionary
+ std::string final_pdf_version;
+ int final_extension_level;
std::string min_pdf_version;
+ int min_extension_level;
std::string forced_pdf_version;
+ int forced_extension_level;
std::string extra_header_text;
int encryption_dict_objid;
std::string cur_data_key;
diff --git a/include/qpdf/QPDFXRefEntry.hh b/include/qpdf/QPDFXRefEntry.hh
index f27b434..93dac02 100644
--- a/include/qpdf/QPDFXRefEntry.hh
+++ b/include/qpdf/QPDFXRefEntry.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/QTC.hh b/include/qpdf/QTC.hh
index 1433742..c8ee6ad 100644
--- a/include/qpdf/QTC.hh
+++ b/include/qpdf/QTC.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
diff --git a/include/qpdf/QUtil.hh b/include/qpdf/QUtil.hh
index cdeefd7..ddba97d 100644
--- a/include/qpdf/QUtil.hh
+++ b/include/qpdf/QUtil.hh
@@ -1,4 +1,4 @@
-// Copyright (c) 2005-2012 Jay Berkenbilt
+// Copyright (c) 2005-2013 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
@@ -83,6 +83,18 @@ namespace QUtil
// encoding for the unicode value passed in.
QPDF_DLL
std::string toUTF8(unsigned long uval);
+
+ // Wrapper around random from stdlib. Calls srandom automatically
+ // the first time it is called.
+ QPDF_DLL
+ long random();
+
+ // Wrapper around srandom from stdlib.
+ QPDF_DLL
+ void srandom(unsigned int seed);
+
+ QPDF_DLL
+ void initializeWithRandomBytes(unsigned char* data, size_t len);
};
#endif // __QUTIL_HH__
diff --git a/include/qpdf/Types.h b/include/qpdf/Types.h
index 0d6b8a2..146acc2 100644
--- a/include/qpdf/Types.h
+++ b/include/qpdf/Types.h
@@ -1,3 +1,11 @@
+/* Copyright (c) 2005-2013 Jay Berkenbilt
+ *
+ * This file is part of qpdf. This software may be distributed under
+ * the terms of version 2 of the Artistic License which may be found
+ * in the source distribution. It is provided "as is" without express
+ * or implied warranty.
+ */
+
#ifndef __QPDFTYPES_H__
#define __QPDFTYPES_H__
diff --git a/include/qpdf/qpdf-c.h b/include/qpdf/qpdf-c.h
index beba231..ee4fa58 100644
--- a/include/qpdf/qpdf-c.h
+++ b/include/qpdf/qpdf-c.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2005-2012 Jay Berkenbilt
+/* Copyright (c) 2005-2013 Jay Berkenbilt
*
* This file is part of qpdf. This software may be distributed under
* the terms of version 2 of the Artistic License which may be found
@@ -213,6 +213,10 @@ extern "C" {
QPDF_DLL
char const* qpdf_get_pdf_version(qpdf_data qpdf);
+ /* Return the extension level of the PDF file. */
+ QPDF_DLL
+ int qpdf_get_pdf_extension_level(qpdf_data qpdf);
+
/* Return the user password. If the file is opened using the
* owner password, the user password may be retrieved using this
* function. If the file is opened using the user password, this
@@ -359,14 +363,36 @@ extern "C" {
QPDF_BOOL encrypt_metadata, QPDF_BOOL use_aes);
QPDF_DLL
+ void qpdf_set_r5_encryption_parameters(
+ qpdf_data qpdf, char const* user_password, char const* owner_password,
+ QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract,
+ enum qpdf_r3_print_e print, enum qpdf_r3_modify_e modify,
+ QPDF_BOOL encrypt_metadata);
+
+ QPDF_DLL
+ void qpdf_set_r6_encryption_parameters(
+ qpdf_data qpdf, char const* user_password, char const* owner_password,
+ QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract,
+ enum qpdf_r3_print_e print, enum qpdf_r3_modify_e modify,
+ QPDF_BOOL encrypt_metadata);
+
+ QPDF_DLL
void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value);
QPDF_DLL
void qpdf_set_minimum_pdf_version(qpdf_data qpdf, char const* version);
QPDF_DLL
+ void qpdf_set_minimum_pdf_version_and_extension(
+ qpdf_data qpdf, char const* version, int extension_level);
+
+ QPDF_DLL
void qpdf_force_pdf_version(qpdf_data qpdf, char const* version);
+ QPDF_DLL
+ void qpdf_force_pdf_version_and_extension(
+ qpdf_data qpdf, char const* version, int extension_level);
+
/* Do actual write operation. */
QPDF_DLL
QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf);
diff --git a/ispell-words b/ispell-words
index 51f70a1..0029212 100644
--- a/ispell-words
+++ b/ispell-words
@@ -1,9 +1,15 @@
+aa
aaa
ab
+abacc
abc
ABCD
+abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnom
+abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnom
abcde
abcdefABCDEF
+abcdefghbcdefghicdefghijdefghijkefghijklfghijklmg
+abcdefghbcdefghicdefghijdefghijkefghijklfghijklmg
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghi
abÏ
@@ -17,6 +23,7 @@ acroread
activatePipelineStack
ActiveState
acyclic
+adbe
addPage
addPageAt
addPageContents
@@ -24,8 +31,12 @@ addToTable
adjustAESStreamLength
admon
Adobeâ
+ae
+aeadb
aes
AESv
+aesv
+afa
ageneration
AHx
AIX
@@ -46,6 +57,7 @@ aobjid
apexcovantage
API
APIs
+appendable
appendItem
appendString
arg
@@ -85,16 +97,24 @@ autoheader
autolabel
automake
autotools
+ba
backref
backreference
backrefs
+badBits
badLength
BADMAGIC
BADOPTION
+baeca
BaseFont
basename
+BaseVersion
+bb
+bc
+beb
Berkenbilt
betweenTokens
+bf
binmode
BitsPerComponent
bitstream
@@ -110,12 +130,14 @@ BT
buf
BufferInputSource
buflen
+bufp
bufpl
bufsize
BUGREPORT
buildrules
bw
bytesNeeded
+ca
calculateHOutline
calculateHPageOffset
calculateHSharedObject
@@ -123,16 +145,22 @@ calculateLinearizationData
calculateXrefStreamPadding
callHello
CAPTURECOUNT
+cb
cbc
cc
ccase
+ccc
CCF
CCITTFaxDecode
cd
+cdc
cdict
ce
+cec
+ced
cerr
cf
+cfea
CFM
ch
ChangeLog
@@ -161,6 +189,7 @@ clearPipelineStack
cleartext
closeObject
cmd
+codepage
codepoint
ColorSpace
com
@@ -192,17 +221,26 @@ CryptFilterDecodeParms
cso
csoe
css
+cstdio
cstr
cstring
ctest
+ctx
ctype
cygwin
+da
daae
+dae
+db
+dc
DCT
DCTDecode
+dd
+ddaf
ddd
de
debian
+dec
declspec
DecodeParms
decodeRow
@@ -233,6 +271,7 @@ DIR
dirname
disableCBC
disableIncompatibleEncryption
+disablePadding
dist
distclean
dlfcn
@@ -257,8 +296,17 @@ dumpLinearizationDataInternal
dwHighDateTime
dwLowDateTime
DWORD
+ea
+eadb
earlychange
EarlyChange
+eb
+ebae
+ecc
+ecedd
+eded
+eeb
+eeee
EF
EFF
efgh
@@ -266,6 +314,7 @@ EI
elif
elt
EmbeddedFiles
+embeddedFiles
emptyPDF
en
encodeDataIncrementally
@@ -303,14 +352,18 @@ esize
exc
exe
exp
+ExtensionLevel
extern
fb
fBqpdf
+fc
+fcc
fclose
fcntl
fd
+feebbd
ferror
-FF
+ff
ffff
fflush
fghij
@@ -322,10 +375,12 @@ FileInputSource
fileno
filenow
filep
+Filespec
FILETIME
filetrailer
filterCompressedObjects
findAndSkipNextEOL
+findAttachmentStreams
findPage
fIoptions
fIoutfilename
@@ -354,7 +409,9 @@ fullinfo
fullpad
func
fwrite
+Gagic
GajiÄ
+Gajic
gcc
gen
generateHintStream
@@ -377,16 +434,20 @@ getCount
getDataChecksum
getDict
getDictAsMap
+getEncryptionKey
+getEncryptMetadata
getenv
GetEnvironmentVariable
getErrorCode
getErrorMessage
+getExtensionLevel
getFileChecksum
getFilename
getFilePosition
getFirstChar
getGeneration
getHexDigest
+getId
getIntValue
getItem
getKey
@@ -395,6 +456,7 @@ getKeys
getLastChar
getLastOffset
getLength
+getLengthBytes
getLinearizationOffset
getLinearizedParts
getMatch
@@ -403,20 +465,26 @@ getName
getNext
getNItems
getNumericValue
+getO
getObject
getObjectByID
getObjectID
getObjectStreamData
getObjStreamIndex
getObjStreamNumber
+getOE
getOffset
getOffsetLength
getOwningQPDF
+getP
getPaddedUserPassword
getPageContents
getPageImages
getPDFVersion
+getPerms
getPointer
+getR
+getRawDigest
getRawStreamData
getRawValue
getRealValue
@@ -428,13 +496,16 @@ getStringValue
GetSystemTime
getToken
getTrailer
+getTrimmedTrailer
getTrimmedUserPassword
getTrimmedUserPassword's
getType
-GETU
+getU
+getUE
getUncompressedObject
getUserPassword
getUTF
+getV
getVal
getValue
getWarnings
@@ -466,6 +537,8 @@ hexstrings
HGeneric
hh
HighPart
+hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstn
+hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstn
hlen
Hoffmann
HOi
@@ -511,6 +584,7 @@ initializeEncryption
initializePipelineStack
initializeSpecialStreams
initializeVector
+initializeWithRandomBytes
inline
inode
inputLen
@@ -655,6 +729,7 @@ msvc
MSVC's
msys
multibyte
+multiline
multithreaded
Mutator
mutators
@@ -723,6 +798,8 @@ ObjStm
ObjUser
objusers
oc
+OE
+OffsetInputSource
og
ogs
oiter
@@ -743,11 +820,13 @@ ostream
ostringstream
OtherPage
ou
+OUE
ous
outbuf
OUTDOC
outfile
outfilename
+outlength
outputLengthNextN
ovecsize
ovector
@@ -760,6 +839,7 @@ pageobj
PageSpec
para
param
+params
parms
parseInternal
parseVersion
@@ -768,12 +848,15 @@ pathsep
Pavlyuk
pb
pbytes
+pc
pcre
pcreapi
pdf
PDFâ
+PDFContext
+PDFDocEncoding
pdfDumpInfoDict
-PDFS
+PDFs
pdlin
pe
perl
@@ -783,6 +866,7 @@ php
pipeStreamData
pipeStringAndFinish
Pkey
+pkg
PKI
pl
plaintext
@@ -798,6 +882,7 @@ pre
precompiled
prefilering
prefiltering
+prepareFileForWrite
presentCharacter
presentEOF
preserveObjectStreams
@@ -806,6 +891,7 @@ printability
printf
processChar
processFile
+processInputSource
processMemoryFile
processRow
processXRefStream
@@ -814,6 +900,7 @@ ProcSet
procsets
programlisting
provideStreamData
+proxied
PSâ
pt
pthread
@@ -905,6 +992,8 @@ replaceOrRemoveKey
replaceReserved
replaceStreamData
reserveObjects
+resetBits
+resolveLiteral
resolveObjectsInStream
retargeted
retested
@@ -929,6 +1018,7 @@ RStream
RunLengthDecode
runtest
sAlT
+SASLprep
se
sed
seekable
@@ -943,22 +1033,26 @@ setContentNormalization
setDataKey
setEncryptionParameters
setEncryptionParametersInternal
+setExtraHeaderText
setFile
setFilename
setFromVector
setIgnoreXRefStreams
setItem
+setIV
setLastObjectDescription
setLastOffset
setLinearization
setLineBuf
setMinimumPDFVersion
setmode
+setO
setObjectStreamMode
setObjGen
setOutputFile
setOutputFilename
setOutputMemory
+setOutputPipeline
setOutputStreams
setPreserveEncryption
setQDFMode
@@ -969,7 +1063,10 @@ setStreamDataMode
setSuppressOriginalObjectIDs
setSuppressWarnings
setTrailer
+setU
+setV
setvbuf
+sha
shallowCopy
showLinearizationData
showXRefTable
@@ -978,6 +1075,7 @@ skipToNextByte
soe
sourceforge
SourceForge
+sph
sprintf
srand
srandom
@@ -1006,6 +1104,7 @@ StreamDataProvider
strerror
StrF
StringDecrypter
+stringprep
stripesize
strlen
strncmp
@@ -1064,6 +1163,7 @@ tt
txt
uc
udata
+UE
uinow
uint
uiter
@@ -1076,6 +1176,7 @@ uncompresesd
uncompress
uncompressing
undef
+understandDecodeParams
unencrypted
unfilterable
ungetc
@@ -1100,6 +1201,7 @@ updatePagesCache
url
UseOutlines
useStaticIV
+useZeroIV
USLetter
usr
utf
@@ -1123,6 +1225,7 @@ Vitaliy
Vkey
vlen
voidpf
+vp
vvv
wb
werror
@@ -1192,11 +1295,14 @@ xrefTable
xsl
XSLTPROC
XXX
+xy
yn
yuiop
yyyymmdd
z's
zalloc
+Zarko
+Zarko's
zdata
Zeroize
zeroizing
diff --git a/libqpdf.map b/libqpdf.map
index 857f56c..ab23bd9 100644
--- a/libqpdf.map
+++ b/libqpdf.map
@@ -1,4 +1,4 @@
-LIBQPDF_8 {
+LIBQPDF_10 {
global:
*;
};
diff --git a/libqpdf.pc.in b/libqpdf.pc.in
index c765900..5d1a867 100644
--- a/libqpdf.pc.in
+++ b/libqpdf.pc.in
@@ -6,5 +6,6 @@ includedir=@includedir@
Name: libqpdf
Description: PDF transformation library
Version: @PACKAGE_VERSION@
+Requires.private: zlib, libpcre
Libs: -L${libdir} -lqpdf
Cflags: -I${includedir}
diff --git a/libqpdf/OffsetInputSource.cc b/libqpdf/OffsetInputSource.cc
new file mode 100644
index 0000000..c1ec410
--- /dev/null
+++ b/libqpdf/OffsetInputSource.cc
@@ -0,0 +1,61 @@
+#include <qpdf/OffsetInputSource.hh>
+
+OffsetInputSource::OffsetInputSource(PointerHolder<InputSource> proxied,
+ qpdf_offset_t global_offset) :
+ proxied(proxied),
+ global_offset(global_offset)
+{
+}
+
+OffsetInputSource::~OffsetInputSource()
+{
+}
+
+qpdf_offset_t
+OffsetInputSource::findAndSkipNextEOL()
+{
+ return this->proxied->findAndSkipNextEOL() - this->global_offset;
+}
+
+std::string const&
+OffsetInputSource::getName() const
+{
+ return this->proxied->getName();
+}
+
+qpdf_offset_t
+OffsetInputSource::tell()
+{
+ return this->proxied->tell() - this->global_offset;
+}
+
+void
+OffsetInputSource::seek(qpdf_offset_t offset, int whence)
+{
+ if (whence == SEEK_SET)
+ {
+ this->proxied->seek(offset + global_offset, whence);
+ }
+ else
+ {
+ this->proxied->seek(offset, whence);
+ }
+}
+
+void
+OffsetInputSource::rewind()
+{
+ seek(0, SEEK_SET);
+}
+
+size_t
+OffsetInputSource::read(char* buffer, size_t length)
+{
+ return this->proxied->read(buffer, length);
+}
+
+void
+OffsetInputSource::unreadCh(char ch)
+{
+ this->proxied->unreadCh(ch);
+}
diff --git a/libqpdf/Pl_AES_PDF.cc b/libqpdf/Pl_AES_PDF.cc
index 0f73c09..5287610 100644
--- a/libqpdf/Pl_AES_PDF.cc
+++ b/libqpdf/Pl_AES_PDF.cc
@@ -6,28 +6,29 @@
#include <qpdf/rijndael.h>
#include <string>
#include <stdlib.h>
-#include <qpdf/qpdf-config.h>
-#ifndef HAVE_RANDOM
-# define random rand
-# define srandom srand
-#endif
bool Pl_AES_PDF::use_static_iv = false;
Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next,
- bool encrypt, unsigned char const key[key_size]) :
+ bool encrypt, unsigned char const* key,
+ unsigned int key_bytes) :
Pipeline(identifier, next),
encrypt(encrypt),
cbc_mode(true),
first(true),
offset(0),
- nrounds(0)
+ nrounds(0),
+ use_zero_iv(false),
+ use_specified_iv(false),
+ disable_padding(false)
{
- static int const keybits = 128;
- assert(key_size == KEYLENGTH(keybits));
- assert(sizeof(this->rk) / sizeof(uint32_t) == RKLENGTH(keybits));
- std::memcpy(this->key, key, key_size);
- std::memset(this->rk, 0, sizeof(this->rk));
+ unsigned int keybits = 8 * key_bytes;
+ assert(key_bytes == KEYLENGTH(keybits));
+ this->key = new unsigned char[key_bytes];
+ this->rk = new uint32_t[RKLENGTH(keybits)];
+ unsigned int rk_bytes = RKLENGTH(keybits) * sizeof(uint32_t);
+ std::memcpy(this->key, key, key_bytes);
+ std::memset(this->rk, 0, rk_bytes);
std::memset(this->inbuf, 0, this->buf_size);
std::memset(this->outbuf, 0, this->buf_size);
std::memset(this->cbc_block, 0, this->buf_size);
@@ -44,7 +45,33 @@ Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next,
Pl_AES_PDF::~Pl_AES_PDF()
{
- // nothing needed
+ delete [] this->key;
+ delete [] this->rk;
+}
+
+void
+Pl_AES_PDF::useZeroIV()
+{
+ this->use_zero_iv = true;
+}
+
+void
+Pl_AES_PDF::disablePadding()
+{
+ this->disable_padding = true;
+}
+
+void
+Pl_AES_PDF::setIV(unsigned char const* iv, size_t bytes)
+{
+ if (bytes != this->buf_size)
+ {
+ throw std::logic_error(
+ "Pl_AES_PDF: specified initialization vector"
+ " size in bytes must be " + QUtil::int_to_string(bytes));
+ }
+ this->use_specified_iv = true;
+ memcpy(this->specified_iv, iv, bytes);
}
void
@@ -90,13 +117,16 @@ Pl_AES_PDF::finish()
{
flush(false);
}
- // Pad as described in section 3.5.1 of version 1.7 of the PDF
- // specification, including providing an entire block of padding
- // if the input was a multiple of 16 bytes.
- unsigned char pad = (unsigned char) (this->buf_size - this->offset);
- memset(this->inbuf + this->offset, pad, pad);
- this->offset = this->buf_size;
- flush(false);
+ if (! this->disable_padding)
+ {
+ // Pad as described in section 3.5.1 of version 1.7 of the PDF
+ // specification, including providing an entire block of padding
+ // if the input was a multiple of 16 bytes.
+ unsigned char pad = (unsigned char) (this->buf_size - this->offset);
+ memset(this->inbuf + this->offset, pad, pad);
+ this->offset = this->buf_size;
+ flush(false);
+ }
}
else
{
@@ -112,7 +142,7 @@ Pl_AES_PDF::finish()
this->buf_size - this->offset);
this->offset = this->buf_size;
}
- flush(true);
+ flush(! this->disable_padding);
}
getNext()->finish();
}
@@ -120,16 +150,18 @@ Pl_AES_PDF::finish()
void
Pl_AES_PDF::initializeVector()
{
- static bool seeded_random = false;
- if (! seeded_random)
+ if (use_zero_iv)
{
- // Seed the random number generator with something simple, but
- // just to be interesting, don't use the unmodified current
- // time....
- srandom((int)QUtil::get_current_time() ^ 0xcccc);
- seeded_random = true;
+ for (unsigned int i = 0; i < this->buf_size; ++i)
+ {
+ this->cbc_block[i] = 0;
+ }
}
- if (use_static_iv)
+ else if (use_specified_iv)
+ {
+ std::memcpy(this->cbc_block, this->specified_iv, this->buf_size);
+ }
+ else if (use_static_iv)
{
for (unsigned int i = 0; i < this->buf_size; ++i)
{
@@ -138,10 +170,7 @@ Pl_AES_PDF::initializeVector()
}
else
{
- for (unsigned int i = 0; i < this->buf_size; ++i)
- {
- this->cbc_block[i] = (unsigned char)((random() & 0xff0) >> 4);
- }
+ QUtil::initializeWithRandomBytes(this->cbc_block, this->buf_size);
}
}
@@ -157,12 +186,21 @@ Pl_AES_PDF::flush(bool strip_padding)
{
if (encrypt)
{
- // Set cbc_block to a random initialization vector and
- // write it to the output stream
+ // Set cbc_block to the initialization vector, and if
+ // not zero, write it to the output stream.
initializeVector();
- getNext()->write(this->cbc_block, this->buf_size);
+ if (! (this->use_zero_iv || this->use_specified_iv))
+ {
+ getNext()->write(this->cbc_block, this->buf_size);
+ }
}
- else
+ else if (this->use_zero_iv || this->use_specified_iv)
+ {
+ // Initialize vector with zeroes; zero vector was not
+ // written to the beginning of the input file.
+ initializeVector();
+ }
+ else
{
// Take the first block of input as the initialization
// vector. There's nothing to write at this time.
diff --git a/libqpdf/Pl_SHA2.cc b/libqpdf/Pl_SHA2.cc
new file mode 100644
index 0000000..018f411
--- /dev/null
+++ b/libqpdf/Pl_SHA2.cc
@@ -0,0 +1,164 @@
+#include <qpdf/Pl_SHA2.hh>
+#include <stdexcept>
+#include <cstdio>
+#include <qpdf/PointerHolder.hh>
+
+Pl_SHA2::Pl_SHA2(int bits, Pipeline* next) :
+ Pipeline("sha2", next),
+ in_progress(false),
+ bits(0)
+{
+ if (bits)
+ {
+ resetBits(bits);
+ }
+}
+
+Pl_SHA2::~Pl_SHA2()
+{
+}
+
+void
+Pl_SHA2::badBits()
+{
+ throw std::logic_error("Pl_SHA2 has unexpected value for bits");
+}
+
+void
+Pl_SHA2::write(unsigned char* buf, size_t len)
+{
+ if (! this->in_progress)
+ {
+ switch (bits)
+ {
+ case 256:
+ sph_sha256_init(&this->ctx256);
+ break;
+ case 384:
+ sph_sha384_init(&this->ctx384);
+ break;
+ case 512:
+ sph_sha512_init(&this->ctx512);
+ break;
+ default:
+ badBits();
+ break;
+ }
+ this->in_progress = true;
+ }
+
+ // Write in chunks in case len is too big to fit in an int.
+ // Assume int is at least 32 bits.
+ static size_t const max_bytes = 1 << 30;
+ size_t bytes_left = len;
+ unsigned char* data = buf;
+ while (bytes_left > 0)
+ {
+ size_t bytes = (bytes_left >= max_bytes ? max_bytes : bytes_left);
+ switch (bits)
+ {
+ case 256:
+ sph_sha256(&this->ctx256, data, bytes);
+ break;
+ case 384:
+ sph_sha384(&this->ctx384, data, bytes);
+ break;
+ case 512:
+ sph_sha512(&this->ctx512, data, bytes);
+ break;
+ default:
+ badBits();
+ break;
+ }
+ bytes_left -= bytes;
+ data += bytes;
+ }
+
+ if (this->getNext(true))
+ {
+ this->getNext()->write(buf, len);
+ }
+}
+
+void
+Pl_SHA2::finish()
+{
+ if (this->getNext(true))
+ {
+ this->getNext()->finish();
+ }
+ switch (bits)
+ {
+ case 256:
+ sph_sha256_close(&this->ctx256, sha256sum);
+ break;
+ case 384:
+ sph_sha384_close(&this->ctx384, sha384sum);
+ break;
+ case 512:
+ sph_sha512_close(&this->ctx512, sha512sum);
+ break;
+ default:
+ badBits();
+ break;
+ }
+ this->in_progress = false;
+}
+
+void
+Pl_SHA2::resetBits(int bits)
+{
+ if (this->in_progress)
+ {
+ throw std::logic_error(
+ "bit reset requested for in-progress SHA2 Pipeline");
+ }
+ if (! ((bits == 256) || (bits == 384) || (bits == 512)))
+ {
+ throw std::logic_error("Pl_SHA2 called with bits != 256, 384, or 512");
+ }
+ this->bits = bits;
+}
+
+std::string
+Pl_SHA2::getRawDigest()
+{
+ std::string result;
+ switch (bits)
+ {
+ case 256:
+ result = std::string((char*)this->sha256sum, sizeof(this->sha256sum));
+ break;
+ case 384:
+ result = std::string((char*)this->sha384sum, sizeof(this->sha384sum));
+ break;
+ case 512:
+ result = std::string((char*)this->sha512sum, sizeof(this->sha512sum));
+ break;
+ default:
+ badBits();
+ break;
+ }
+ return result;
+}
+
+std::string
+Pl_SHA2::getHexDigest()
+{
+ if (this->in_progress)
+ {
+ throw std::logic_error(
+ "digest requested for in-progress SHA2 Pipeline");
+ }
+ std::string raw = getRawDigest();
+ size_t raw_size = raw.length();
+ size_t hex_size = 1 + (2 * raw_size);
+ PointerHolder<char> bufp(true, new char[hex_size]);
+ char* buf = bufp.getPointer();
+ buf[hex_size - 1] = '\0';
+ for (unsigned int i = 0; i < raw_size; ++i)
+ {
+ std::sprintf(buf + i * 2, "%02x", (unsigned char)raw[i]);
+ }
+ return buf;
+}
diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc
index efef337..a779c23 100644
--- a/libqpdf/QPDF.cc
+++ b/libqpdf/QPDF.cc
@@ -13,12 +13,13 @@
#include <qpdf/Pl_Discard.hh>
#include <qpdf/FileInputSource.hh>
#include <qpdf/BufferInputSource.hh>
+#include <qpdf/OffsetInputSource.hh>
#include <qpdf/QPDFExc.hh>
#include <qpdf/QPDF_Null.hh>
#include <qpdf/QPDF_Dictionary.hh>
-std::string QPDF::qpdf_version = "3.0.2";
+std::string QPDF::qpdf_version = "4.0.0";
static char const* EMPTY_PDF =
"%PDF-1.3\n"
@@ -96,6 +97,7 @@ QPDF::QPDF() :
err_stream(&std::cerr),
attempt_recovery(true),
encryption_V(0),
+ encryption_R(0),
encrypt_metadata(true),
cf_stream(e_none),
cf_string(e_none),
@@ -138,9 +140,8 @@ void
QPDF::processFile(char const* filename, char const* password)
{
FileInputSource* fi = new FileInputSource();
- this->file = fi;
fi->setFilename(filename);
- parse(password);
+ processInputSource(fi, password);
}
void
@@ -148,9 +149,8 @@ QPDF::processFile(char const* description, FILE* filep,
bool close_file, char const* password)
{
FileInputSource* fi = new FileInputSource();
- this->file = fi;
fi->setFile(description, filep, close_file);
- parse(password);
+ processInputSource(fi, password);
}
void
@@ -158,10 +158,18 @@ QPDF::processMemoryFile(char const* description,
char const* buf, size_t length,
char const* password)
{
- this->file =
+ processInputSource(
new BufferInputSource(description,
new Buffer((unsigned char*)buf, length),
- true);
+ true),
+ password);
+}
+
+void
+QPDF::processInputSource(PointerHolder<InputSource> source,
+ char const* password)
+{
+ this->file = source;
parse(password);
}
@@ -207,7 +215,7 @@ QPDF::getWarnings()
void
QPDF::parse(char const* password)
{
- PCRE header_re("^%PDF-(1.\\d+)\\b");
+ PCRE header_re("\\A((?s).*?)%PDF-(1.\\d+)\\b");
PCRE eof_re("(?s:startxref\\s+(\\d+)\\s+%%EOF\\b)");
if (password)
@@ -215,11 +223,26 @@ QPDF::parse(char const* password)
this->provided_password = password;
}
- std::string line = this->file->readLine(20);
+ // Find the header anywhere in the first 1024 bytes of the file,
+ // plus add a little extra space for the header itself.
+ char buffer[1045];
+ memset(buffer, '\0', sizeof(buffer));
+ this->file->read(buffer, sizeof(buffer) - 1);
+ std::string line(buffer);
PCRE::Match m1 = header_re.match(line.c_str());
if (m1)
{
- this->pdf_version = m1.getMatch(1);
+ size_t global_offset = m1.getMatch(1).length();
+ if (global_offset != 0)
+ {
+ // Empirical evidence strongly suggests that when there is
+ // leading material prior to the PDF header, all explicit
+ // offsets in the file are such that 0 points to the
+ // beginning of the header.
+ QTC::TC("qpdf", "QPDF global offset");
+ this->file = new OffsetInputSource(this->file, global_offset);
+ }
+ this->pdf_version = m1.getMatch(2);
if (atof(this->pdf_version.c_str()) < 1.2)
{
this->tokenizer.allowPoundAnywhereInName();
@@ -292,6 +315,7 @@ QPDF::parse(char const* password)
}
initializeEncryption();
+ findAttachmentStreams();
}
void
@@ -1247,6 +1271,21 @@ QPDF::readObjectAtOffset(bool try_recovery,
int& objid, int& generation)
{
setLastObjectDescription(description, exp_objid, exp_generation);
+
+ // Special case: if offset is 0, just return null. Some PDF
+ // writers, in particular "Mac OS X 10.7.5 Quartz PDFContext", may
+ // store deleted objects in the xref table as "0000000000 00000
+ // n", which is not correct, but it won't hurt anything for to
+ // ignore these.
+ if (offset == 0)
+ {
+ QTC::TC("qpdf", "QPDF bogus 0 offset", 0);
+ warn(QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
+ this->last_object_description, 0,
+ "object has offset 0"));
+ return QPDFObjectHandle::newNull();
+ }
+
this->file->seek(offset, SEEK_SET);
QPDFTokenizer::Token tobjid = readToken(this->file);
@@ -1823,28 +1862,6 @@ QPDF::swapObjects(int objid1, int generation1, int objid2, int generation2)
this->obj_cache[og2] = t;
}
-void
-QPDF::trimTrailerForWrite()
-{
- // Note that removing the encryption dictionary does not interfere
- // with reading encrypted files. QPDF loads all the information
- // it needs from the encryption dictionary at the beginning and
- // never looks at it again.
- this->trailer.removeKey("/ID");
- this->trailer.removeKey("/Encrypt");
- this->trailer.removeKey("/Prev");
-
- // Remove all trailer keys that potentially come from a
- // cross-reference stream
- this->trailer.removeKey("/Index");
- this->trailer.removeKey("/W");
- this->trailer.removeKey("/Length");
- this->trailer.removeKey("/Filter");
- this->trailer.removeKey("/DecodeParms");
- this->trailer.removeKey("/Type");
- this->trailer.removeKey("/XRefStm");
-}
-
std::string
QPDF::getFilename() const
{
@@ -1857,6 +1874,30 @@ QPDF::getPDFVersion() const
return this->pdf_version;
}
+int
+QPDF::getExtensionLevel()
+{
+ int result = 0;
+ QPDFObjectHandle obj = getRoot();
+ if (obj.hasKey("/Extensions"))
+ {
+ obj = obj.getKey("/Extensions");
+ if (obj.isDictionary() && obj.hasKey("/ADBE"))
+ {
+ obj = obj.getKey("/ADBE");
+ if (obj.isDictionary() && obj.hasKey("/ExtensionLevel"))
+ {
+ obj = obj.getKey("/ExtensionLevel");
+ if (obj.isInteger())
+ {
+ result = obj.getIntValue();
+ }
+ }
+ }
+ }
+ return result;
+}
+
QPDFObjectHandle
QPDF::getTrailer()
{
@@ -2032,18 +2073,36 @@ QPDF::pipeStreamData(int objid, int generation,
}
void
-QPDF::decodeStreams()
+QPDF::findAttachmentStreams()
{
- for (std::map<ObjGen, QPDFXRefEntry>::iterator iter =
- this->xref_table.begin();
- iter != this->xref_table.end(); ++iter)
+ QPDFObjectHandle root = getRoot();
+ QPDFObjectHandle names = root.getKey("/Names");
+ if (! names.isDictionary())
{
- ObjGen const& og = (*iter).first;
- QPDFObjectHandle obj = getObjectByID(og.obj, og.gen);
- if (obj.isStream())
- {
- Pl_Discard pl;
- obj.pipeStreamData(&pl, true, false, false);
- }
+ return;
+ }
+ QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles");
+ if (! embeddedFiles.isDictionary())
+ {
+ return;
+ }
+ names = embeddedFiles.getKey("/Names");
+ if (! names.isArray())
+ {
+ return;
+ }
+ for (int i = 0; i < names.getArrayNItems(); ++i)
+ {
+ QPDFObjectHandle item = names.getArrayItem(i);
+ if (item.isDictionary() &&
+ item.getKey("/Type").isName() &&
+ (item.getKey("/Type").getName() == "/Filespec") &&
+ item.getKey("/EF").isDictionary() &&
+ item.getKey("/EF").getKey("/F").isStream())
+ {
+ QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
+ this->attachment_streams.insert(
+ ObjGen(stream.getObjectID(), stream.getGeneration()));
+ }
}
}
diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc
index eb08488..a1949a9 100644
--- a/libqpdf/QPDFWriter.cc
+++ b/libqpdf/QPDFWriter.cc
@@ -64,6 +64,11 @@ QPDFWriter::init()
object_stream_mode = qpdf_o_preserve;
encrypt_metadata = true;
encrypt_use_aes = false;
+ min_extension_level = 0;
+ final_extension_level = 0;
+ forced_extension_level = 0;
+ encryption_V = 0;
+ encryption_R = 0;
encryption_dict_objid = 0;
next_objid = 1;
cur_stream_length_id = 0;
@@ -135,6 +140,13 @@ QPDFWriter::getBuffer()
}
void
+QPDFWriter::setOutputPipeline(Pipeline* p)
+{
+ this->filename = "custom pipeline";
+ initializePipelineStack(p);
+}
+
+void
QPDFWriter::setObjectStreamMode(qpdf_object_stream_e mode)
{
this->object_stream_mode = mode;
@@ -163,10 +175,19 @@ QPDFWriter::setQDFMode(bool val)
void
QPDFWriter::setMinimumPDFVersion(std::string const& version)
{
+ setMinimumPDFVersion(version, 0);
+}
+
+void
+QPDFWriter::setMinimumPDFVersion(std::string const& version,
+ int extension_level)
+{
bool set_version = false;
+ bool set_extension_level = false;
if (this->min_pdf_version.empty())
{
set_version = true;
+ set_extension_level = true;
}
else
{
@@ -176,10 +197,22 @@ QPDFWriter::setMinimumPDFVersion(std::string const& version)
int min_minor = 0;
parseVersion(version, old_major, old_minor);
parseVersion(this->min_pdf_version, min_major, min_minor);
- if (compareVersions(old_major, old_minor, min_major, min_minor) > 0)
+ int compare = compareVersions(
+ old_major, old_minor, min_major, min_minor);
+ if (compare > 0)
{
- QTC::TC("qpdf", "QPDFWriter increasing minimum version");
+ QTC::TC("qpdf", "QPDFWriter increasing minimum version",
+ extension_level == 0 ? 0 : 1);
set_version = true;
+ set_extension_level = true;
+ }
+ else if (compare == 0)
+ {
+ if (extension_level > this->min_extension_level)
+ {
+ QTC::TC("qpdf", "QPDFWriter increasing extension level");
+ set_extension_level = true;
+ }
}
}
@@ -187,12 +220,24 @@ QPDFWriter::setMinimumPDFVersion(std::string const& version)
{
this->min_pdf_version = version;
}
+ if (set_extension_level)
+ {
+ this->min_extension_level = extension_level;
+ }
}
void
QPDFWriter::forcePDFVersion(std::string const& version)
{
+ forcePDFVersion(version, 0);
+}
+
+void
+QPDFWriter::forcePDFVersion(std::string const& version,
+ int extension_level)
+{
this->forced_pdf_version = version;
+ this->forced_extension_level = extension_level;
}
void
@@ -301,6 +346,38 @@ QPDFWriter::setR4EncryptionParameters(
}
void
+QPDFWriter::setR5EncryptionParameters(
+ char const* user_password, char const* owner_password,
+ bool allow_accessibility, bool allow_extract,
+ qpdf_r3_print_e print, qpdf_r3_modify_e modify,
+ bool encrypt_metadata)
+{
+ std::set<int> clear;
+ interpretR3EncryptionParameters(
+ clear, user_password, owner_password,
+ allow_accessibility, allow_extract, print, modify);
+ this->encrypt_use_aes = true;
+ this->encrypt_metadata = encrypt_metadata;
+ setEncryptionParameters(user_password, owner_password, 5, 5, 32, clear);
+}
+
+void
+QPDFWriter::setR6EncryptionParameters(
+ char const* user_password, char const* owner_password,
+ bool allow_accessibility, bool allow_extract,
+ qpdf_r3_print_e print, qpdf_r3_modify_e modify,
+ bool encrypt_metadata)
+{
+ std::set<int> clear;
+ interpretR3EncryptionParameters(
+ clear, user_password, owner_password,
+ allow_accessibility, allow_extract, print, modify);
+ this->encrypt_use_aes = true;
+ this->encrypt_metadata = encrypt_metadata;
+ setEncryptionParameters(user_password, owner_password, 5, 6, 32, clear);
+}
+
+void
QPDFWriter::interpretR3EncryptionParameters(
std::set<int>& clear,
char const* user_password, char const* owner_password,
@@ -383,6 +460,12 @@ QPDFWriter::setEncryptionParameters(
bits_to_clear.insert(1);
bits_to_clear.insert(2);
+ if (R > 3)
+ {
+ // Bit 10 is deprecated and should always be set.
+ bits_to_clear.erase(10);
+ }
+
int P = 0;
// Create the complement of P, then invert.
for (std::set<int>::iterator iter = bits_to_clear.begin();
@@ -395,11 +478,26 @@ QPDFWriter::setEncryptionParameters(
generateID();
std::string O;
std::string U;
- QPDF::compute_encryption_O_U(
- user_password, owner_password, V, R, key_len, P,
- this->encrypt_metadata, this->id1, O, U);
+ std::string OE;
+ std::string UE;
+ std::string Perms;
+ std::string encryption_key;
+ if (V < 5)
+ {
+ QPDF::compute_encryption_O_U(
+ user_password, owner_password, V, R, key_len, P,
+ this->encrypt_metadata, this->id1, O, U);
+ }
+ else
+ {
+ QPDF::compute_encryption_parameters_V5(
+ user_password, owner_password, V, R, key_len, P,
+ this->encrypt_metadata, this->id1,
+ encryption_key, O, U, OE, UE, Perms);
+ }
setEncryptionParametersInternal(
- V, R, key_len, P, O, U, this->id1, user_password);
+ V, R, key_len, P, O, U, OE, UE, Perms,
+ this->id1, user_password, encryption_key);
}
void
@@ -427,32 +525,31 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf)
}
if (V >= 4)
{
- if (encrypt.hasKey("/CF") &&
- encrypt.getKey("/CF").isDictionary() &&
- encrypt.hasKey("/StmF") &&
- encrypt.getKey("/StmF").isName())
- {
- // Determine whether to use AES from StmF. QPDFWriter
- // can't write files with different StrF and StmF.
- QPDFObjectHandle CF = encrypt.getKey("/CF");
- QPDFObjectHandle StmF = encrypt.getKey("/StmF");
- if (CF.hasKey(StmF.getName()) &&
- CF.getKey(StmF.getName()).isDictionary())
- {
- QPDFObjectHandle StmF_data = CF.getKey(StmF.getName());
- if (StmF_data.hasKey("/CFM") &&
- StmF_data.getKey("/CFM").isName() &&
- StmF_data.getKey("/CFM").getName() == "/AESV2")
- {
- this->encrypt_use_aes = true;
- }
- }
- }
+ // When copying encryption parameters, use AES even if the
+ // original file did not. Acrobat doesn't create files
+ // with V >= 4 that don't use AES, and the logic of
+ // figuring out whether AES is used or not is complicated
+ // with /StmF, /StrF, and /EFF all potentially having
+ // different values.
+ this->encrypt_use_aes = true;
}
QTC::TC("qpdf", "QPDFWriter copy encrypt metadata",
this->encrypt_metadata ? 0 : 1);
QTC::TC("qpdf", "QPDFWriter copy use_aes",
this->encrypt_use_aes ? 0 : 1);
+ std::string OE;
+ std::string UE;
+ std::string Perms;
+ std::string encryption_key;
+ if (V >= 5)
+ {
+ QTC::TC("qpdf", "QPDFWriter copy V5");
+ OE = encrypt.getKey("/OE").getStringValue();
+ UE = encrypt.getKey("/UE").getStringValue();
+ Perms = encrypt.getKey("/Perms").getStringValue();
+ encryption_key = qpdf.getEncryptionKey();
+ }
+
setEncryptionParametersInternal(
V,
encrypt.getKey("/R").getIntValue(),
@@ -460,13 +557,18 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf)
encrypt.getKey("/P").getIntValue(),
encrypt.getKey("/O").getStringValue(),
encrypt.getKey("/U").getStringValue(),
+ OE,
+ UE,
+ Perms,
this->id1, // this->id1 == the other file's id1
- qpdf.getPaddedUserPassword());
+ qpdf.getPaddedUserPassword(),
+ encryption_key);
}
}
void
-QPDFWriter::disableIncompatibleEncryption(int major, int minor)
+QPDFWriter::disableIncompatibleEncryption(int major, int minor,
+ int extension_level)
{
if (! this->encrypted)
{
@@ -503,6 +605,15 @@ QPDFWriter::disableIncompatibleEncryption(int major, int minor)
disable = true;
}
}
+ else if ((compareVersions(major, minor, 1, 7) < 0) ||
+ ((compareVersions(major, minor, 1, 7) == 0) &&
+ extension_level < 3))
+ {
+ if ((V >= 5) || (R >= 5))
+ {
+ disable = true;
+ }
+ }
}
if (disable)
{
@@ -562,8 +673,12 @@ void
QPDFWriter::setEncryptionParametersInternal(
int V, int R, int key_len, long P,
std::string const& O, std::string const& U,
- std::string const& id1, std::string const& user_password)
+ std::string const& OE, std::string const& UE, std::string const& Perms,
+ std::string const& id1, std::string const& user_password,
+ std::string const& encryption_key)
{
+ this->encryption_V = V;
+ this->encryption_R = R;
encryption_dictionary["/Filter"] = "/Standard";
encryption_dictionary["/V"] = QUtil::int_to_string(V);
encryption_dictionary["/Length"] = QUtil::int_to_string(key_len * 8);
@@ -571,44 +686,71 @@ QPDFWriter::setEncryptionParametersInternal(
encryption_dictionary["/P"] = QUtil::int_to_string(P);
encryption_dictionary["/O"] = QPDF_String(O).unparse(true);
encryption_dictionary["/U"] = QPDF_String(U).unparse(true);
- setMinimumPDFVersion("1.3");
- if (R == 3)
+ if (V >= 5)
{
- setMinimumPDFVersion("1.4");
+ encryption_dictionary["/OE"] = QPDF_String(OE).unparse(true);
+ encryption_dictionary["/UE"] = QPDF_String(UE).unparse(true);
+ encryption_dictionary["/Perms"] = QPDF_String(Perms).unparse(true);
+ }
+ if (R >= 6)
+ {
+ setMinimumPDFVersion("1.7", 8);
+ }
+ else if (R == 5)
+ {
+ setMinimumPDFVersion("1.7", 3);
}
- else if (R >= 4)
+ else if (R == 4)
{
setMinimumPDFVersion(this->encrypt_use_aes ? "1.6" : "1.5");
}
+ else if (R == 3)
+ {
+ setMinimumPDFVersion("1.4");
+ }
+ else
+ {
+ setMinimumPDFVersion("1.3");
+ }
if ((R >= 4) && (! encrypt_metadata))
{
encryption_dictionary["/EncryptMetadata"] = "false";
}
- if (V == 4)
+ if ((V == 4) || (V == 5))
{
// The spec says the value for the crypt filter key can be
// anything, and xpdf seems to agree. However, Adobe Reader
// won't open our files unless we use /StdCF.
encryption_dictionary["/StmF"] = "/StdCF";
encryption_dictionary["/StrF"] = "/StdCF";
- std::string method = (this->encrypt_use_aes ? "/AESV2" : "/V2");
+ std::string method = (this->encrypt_use_aes
+ ? ((V < 5) ? "/AESV2" : "/AESV3")
+ : "/V2");
encryption_dictionary["/CF"] =
"<< /StdCF << /AuthEvent /DocOpen /CFM " + method + " >> >>";
}
this->encrypted = true;
QPDF::EncryptionData encryption_data(
- V, R, key_len, P, O, U, id1, this->encrypt_metadata);
- this->encryption_key = QPDF::compute_encryption_key(
- user_password, encryption_data);
+ V, R, key_len, P, O, U, OE, UE, Perms, id1, this->encrypt_metadata);
+ if (V < 5)
+ {
+ this->encryption_key = QPDF::compute_encryption_key(
+ user_password, encryption_data);
+ }
+ else
+ {
+ this->encryption_key = encryption_key;
+ }
}
void
QPDFWriter::setDataKey(int objid)
{
this->cur_data_key = QPDF::compute_data_key(
- this->encryption_key, objid, 0, this->encrypt_use_aes);
+ this->encryption_key, objid, 0,
+ this->encrypt_use_aes, this->encryption_V, this->encryption_R);
}
int
@@ -745,13 +887,14 @@ QPDFWriter::pushEncryptionFilter()
{
p = new Pl_AES_PDF(
"aes stream encryption", this->pipeline, true,
- (unsigned char*) this->cur_data_key.c_str());
+ (unsigned char*) this->cur_data_key.c_str(),
+ (unsigned int)this->cur_data_key.length());
}
else
{
p = new Pl_RC4("rc4 stream encryption", this->pipeline,
(unsigned char*) this->cur_data_key.c_str(),
- (int)this->cur_data_key.length());
+ (unsigned int)this->cur_data_key.length());
}
pushPipeline(p);
}
@@ -827,16 +970,6 @@ QPDFWriter::enqueueObject(QPDFObjectHandle object)
{
// This is a place-holder object for an object stream
}
- else if (object.isScalar())
- {
- // flattenScalarReferences is supposed to have removed all
- // indirect scalars.
- throw std::logic_error(
- "INTERNAL ERROR: QPDFWriter::enqueueObject: indirect scalar: " +
- std::string(this->filename) + " " +
- QUtil::int_to_string(object.getObjectID()) + " " +
- QUtil::int_to_string(object.getGeneration()));
- }
int objid = object.getObjectID();
if (obj_renumber.count(objid) == 0)
@@ -909,15 +1042,6 @@ QPDFWriter::unparseChild(QPDFObjectHandle child, int level, int flags)
}
if (child.isIndirect())
{
- if (child.isScalar())
- {
- // flattenScalarReferences is supposed to have removed all
- // indirect scalars.
- throw std::logic_error(
- "INTERNAL ERROR: QPDFWriter::unparseChild: indirect scalar: " +
- QUtil::int_to_string(child.getObjectID()) + " " +
- QUtil::int_to_string(child.getGeneration()));
- }
int old_id = child.getObjectID();
int new_id = obj_renumber[old_id];
writeString(QUtil::int_to_string(new_id));
@@ -933,7 +1057,7 @@ void
QPDFWriter::writeTrailer(trailer_e which, int size, bool xref_stream,
qpdf_offset_t prev)
{
- QPDFObjectHandle trailer = pdf.getTrailer();
+ QPDFObjectHandle trailer = getTrimmedTrailer();
if (! xref_stream)
{
writeString("trailer <<");
@@ -1011,6 +1135,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
unsigned int flags, size_t stream_length,
bool compress)
{
+ int old_id = object.getObjectID();
unsigned int child_flags = flags & ~f_stream;
std::string indent;
@@ -1043,32 +1168,199 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
}
else if (object.isDictionary())
{
+ // Make a shallow copy of this object so we can modify it
+ // safely without affecting the original. This code makes
+ // assumptions about things that are made true in
+ // prepareFileForWrite, such as that certain things are direct
+ // objects so that replacing them doesn't leave unreferenced
+ // objects in the output.
+ object = object.shallowCopy();
+
+ // Handle special cases for specific dictionaries.
+
+ // Extensions dictionaries.
+
+ // We have one of several cases:
+ //
+ // * We need ADBE
+ // - We already have Extensions
+ // - If it has the right ADBE, preserve it
+ // - Otherwise, replace ADBE
+ // - We don't have Extensions: create one from scratch
+ // * We don't want ADBE
+ // - We already have Extensions
+ // - If it only has ADBE, remove it
+ // - If it has other things, keep those and remove ADBE
+ // - We have no extensions: no action required
+ //
+ // Before writing, we guarantee that /Extensions, if present,
+ // is direct through the ADBE dictionary, so we can modify in
+ // place.
+
+ bool is_root = false;
+ bool have_extensions_other = false;
+ bool have_extensions_adbe = false;
+
+ QPDFObjectHandle extensions;
+ if (old_id == pdf.getRoot().getObjectID())
+ {
+ is_root = true;
+ if (object.hasKey("/Extensions") &&
+ object.getKey("/Extensions").isDictionary())
+ {
+ extensions = object.getKey("/Extensions");
+ }
+ }
+
+ if (extensions.isInitialized())
+ {
+ std::set<std::string> keys = extensions.getKeys();
+ if (keys.count("/ADBE") > 0)
+ {
+ have_extensions_adbe = true;
+ keys.erase("/ADBE");
+ }
+ if (keys.size() > 0)
+ {
+ have_extensions_other = true;
+ }
+ }
+
+ bool need_extensions_adbe = (this->final_extension_level > 0);
+
+ if (is_root)
+ {
+ if (need_extensions_adbe)
+ {
+ if (! (have_extensions_other || have_extensions_adbe))
+ {
+ // We need Extensions and don't have it. Create
+ // it here.
+ QTC::TC("qpdf", "QPDFWriter create Extensions",
+ this->qdf_mode ? 0 : 1);
+ extensions = QPDFObjectHandle::newDictionary();
+ object.replaceKey("/Extensions", extensions);
+ }
+ }
+ else if (! have_extensions_other)
+ {
+ // We have Extensions dictionary and don't want one.
+ if (have_extensions_adbe)
+ {
+ QTC::TC("qpdf", "QPDFWriter remove existing Extensions");
+ object.removeKey("/Extensions");
+ extensions = QPDFObjectHandle(); // uninitialized
+ }
+ }
+ }
+
+ if (extensions.isInitialized())
+ {
+ QTC::TC("qpdf", "QPDFWriter preserve Extensions");
+ QPDFObjectHandle adbe = extensions.getKey("/ADBE");
+ if (adbe.isDictionary() &&
+ adbe.hasKey("/BaseVersion") &&
+ adbe.getKey("/BaseVersion").isName() &&
+ (adbe.getKey("/BaseVersion").getName() ==
+ "/" + this->final_pdf_version) &&
+ adbe.hasKey("/ExtensionLevel") &&
+ adbe.getKey("/ExtensionLevel").isInteger() &&
+ (adbe.getKey("/ExtensionLevel").getIntValue() ==
+ this->final_extension_level))
+ {
+ QTC::TC("qpdf", "QPDFWriter preserve ADBE");
+ }
+ else
+ {
+ if (need_extensions_adbe)
+ {
+ extensions.replaceKey(
+ "/ADBE",
+ QPDFObjectHandle::parse(
+ "<< /BaseVersion /" + this->final_pdf_version +
+ " /ExtensionLevel " +
+ QUtil::int_to_string(this->final_extension_level) +
+ " >>"));
+ }
+ else
+ {
+ QTC::TC("qpdf", "QPDFWriter remove ADBE");
+ extensions.removeKey("/ADBE");
+ }
+ }
+ }
+
+ // Stream dictionaries.
+
+ if (flags & f_stream)
+ {
+ // Suppress /Length since we will write it manually
+ object.removeKey("/Length");
+
+ if (flags & f_filtered)
+ {
+ // We will supply our own filter and decode
+ // parameters.
+ object.removeKey("/Filter");
+ object.removeKey("/DecodeParms");
+ }
+ else
+ {
+ // Make sure, no matter what else we have, that we
+ // don't have /Crypt in the output filters.
+ QPDFObjectHandle filter = object.getKey("/Filter");
+ QPDFObjectHandle decode_parms = object.getKey("/DecodeParms");
+ if (filter.isOrHasName("/Crypt"))
+ {
+ if (filter.isName())
+ {
+ object.removeKey("/Filter");
+ object.removeKey("/DecodeParms");
+ }
+ else
+ {
+ int idx = -1;
+ for (int i = 0; i < filter.getArrayNItems(); ++i)
+ {
+ QPDFObjectHandle item = filter.getArrayItem(i);
+ if (item.isName() && item.getName() == "/Crypt")
+ {
+ idx = i;
+ break;
+ }
+ }
+ if (idx >= 0)
+ {
+ // If filter is an array, then the code in
+ // QPDF_Stream has already verified that
+ // DecodeParms and Filters are arrays of
+ // the same length, but if they weren't
+ // for some reason, eraseItem does type
+ // and bounds checking.
+ QTC::TC("qpdf", "QPDFWriter remove Crypt");
+ filter.eraseItem(idx);
+ decode_parms.eraseItem(idx);
+ }
+ }
+ }
+ }
+ }
+
writeString("<<");
writeStringQDF("\n");
+
std::set<std::string> keys = object.getKeys();
for (std::set<std::string>::iterator iter = keys.begin();
iter != keys.end(); ++iter)
{
- // I'm not fully clear on /Crypt keys in /DecodeParms. If
- // one is found, we refuse to filter, so we should be
- // safe.
std::string const& key = *iter;
- if ((flags & f_filtered) &&
- ((key == "/Filter") ||
- (key == "/DecodeParms")))
- {
- continue;
- }
- if ((flags & f_stream) && (key == "/Length"))
- {
- continue;
- }
+
writeStringQDF(indent);
writeStringQDF(" ");
writeStringNoQDF(" ");
writeString(QPDF_Name::normalizeName(key));
writeString(" ");
- unparseChild(object.getKey(key), level + 1, child_flags);
+ unparseChild(object.getKey(key), level + 1, child_flags);
writeStringQDF("\n");
}
@@ -1105,7 +1397,6 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
else if (object.isStream())
{
// Write stream data to a buffer.
- int old_id = object.getObjectID();
int new_id = obj_renumber[old_id];
if (! this->direct_stream_lengths)
{
@@ -1214,7 +1505,8 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
{
Pl_Buffer bufpl("encrypted string");
Pl_AES_PDF pl("aes encrypt string", &bufpl, true,
- (unsigned char const*)this->cur_data_key.c_str());
+ (unsigned char const*)this->cur_data_key.c_str(),
+ (unsigned int)this->cur_data_key.length());
pl.write((unsigned char*) val.c_str(), val.length());
pl.finish();
Buffer* buf = bufpl.getBuffer();
@@ -1640,6 +1932,151 @@ QPDFWriter::generateObjectStreams()
}
}
+QPDFObjectHandle
+QPDFWriter::getTrimmedTrailer()
+{
+ // Remove keys from the trailer that necessarily have to be
+ // replaced when writing the file.
+
+ QPDFObjectHandle trailer = pdf.getTrailer().shallowCopy();
+
+ // Remove encryption keys
+ trailer.removeKey("/ID");
+ trailer.removeKey("/Encrypt");
+
+ // Remove modification information
+ trailer.removeKey("/Prev");
+
+ // Remove all trailer keys that potentially come from a
+ // cross-reference stream
+ trailer.removeKey("/Index");
+ trailer.removeKey("/W");
+ trailer.removeKey("/Length");
+ trailer.removeKey("/Filter");
+ trailer.removeKey("/DecodeParms");
+ trailer.removeKey("/Type");
+ trailer.removeKey("/XRefStm");
+
+ return trailer;
+}
+
+void
+QPDFWriter::prepareFileForWrite()
+{
+ // Do a traversal of the entire PDF file structure replacing all
+ // indirect objects that QPDFWriter wants to be direct. This
+ // includes stream lengths, stream filtering parameters, and
+ // document extension level information. Also replace all
+ // indirect null references with direct nulls. This way, the only
+ // indirect nulls queued for output will be object stream place
+ // holders.
+
+ std::list<QPDFObjectHandle> queue;
+ queue.push_back(getTrimmedTrailer());
+ std::set<int> visited;
+
+ while (! queue.empty())
+ {
+ QPDFObjectHandle node = queue.front();
+ queue.pop_front();
+ if (node.isIndirect())
+ {
+ if (visited.count(node.getObjectID()) > 0)
+ {
+ continue;
+ }
+ visited.insert(node.getObjectID());
+ }
+
+ if (node.isArray())
+ {
+ int nitems = node.getArrayNItems();
+ for (int i = 0; i < nitems; ++i)
+ {
+ QPDFObjectHandle oh = node.getArrayItem(i);
+ if (oh.isIndirect() && oh.isNull())
+ {
+ QTC::TC("qpdf", "QPDFWriter flatten array null");
+ oh.makeDirect();
+ node.setArrayItem(i, oh);
+ }
+ else if (! oh.isScalar())
+ {
+ queue.push_back(oh);
+ }
+ }
+ }
+ else if (node.isDictionary() || node.isStream())
+ {
+ bool is_stream = false;
+ bool is_root = false;
+ QPDFObjectHandle dict = node;
+ if (node.isStream())
+ {
+ is_stream = true;
+ dict = node.getDict();
+ }
+ else if (pdf.getRoot().getObjectID() == node.getObjectID())
+ {
+ is_root = true;
+ }
+
+ std::set<std::string> keys = dict.getKeys();
+ for (std::set<std::string>::iterator iter = keys.begin();
+ iter != keys.end(); ++iter)
+ {
+ std::string const& key = *iter;
+ QPDFObjectHandle oh = dict.getKey(key);
+ bool add_to_queue = true;
+ if (is_stream)
+ {
+ if (oh.isIndirect() &&
+ ((key == "/Length") ||
+ (key == "/Filter") ||
+ (key == "/DecodeParms")))
+ {
+ QTC::TC("qpdf", "QPDFWriter make stream key direct");
+ add_to_queue = false;
+ oh.makeDirect();
+ dict.replaceKey(key, oh);
+ }
+ }
+ else if (is_root)
+ {
+ if ((key == "/Extensions") && (oh.isDictionary()))
+ {
+ bool extensions_indirect = false;
+ if (oh.isIndirect())
+ {
+ QTC::TC("qpdf", "QPDFWriter make Extensions direct");
+ extensions_indirect = true;
+ add_to_queue = false;
+ oh = oh.shallowCopy();
+ dict.replaceKey(key, oh);
+ }
+ if (oh.hasKey("/ADBE"))
+ {
+ QPDFObjectHandle adbe = oh.getKey("/ADBE");
+ if (adbe.isIndirect())
+ {
+ QTC::TC("qpdf", "QPDFWriter make ADBE direct",
+ extensions_indirect ? 0 : 1);
+ adbe.makeDirect();
+ oh.replaceKey("/ADBE", adbe);
+ }
+ }
+ }
+ }
+
+ if (add_to_queue)
+ {
+ queue.push_back(oh);
+ }
+ }
+ }
+ }
+}
+
void
QPDFWriter::write()
{
@@ -1686,7 +2123,8 @@ QPDFWriter::write()
int major = 0;
int minor = 0;
parseVersion(this->forced_pdf_version, major, minor);
- disableIncompatibleEncryption(major, minor);
+ disableIncompatibleEncryption(major, minor,
+ this->forced_extension_level);
if (compareVersions(major, minor, 1, 5) < 0)
{
QTC::TC("qpdf", "QPDFWriter forcing object stream disable");
@@ -1778,8 +2216,7 @@ QPDFWriter::write()
generateID();
- pdf.trimTrailerForWrite();
- pdf.flattenScalarReferences();
+ prepareFileForWrite();
if (this->linearized)
{
@@ -1834,16 +2271,18 @@ QPDFWriter::writeEncryptionDictionary()
void
QPDFWriter::writeHeader()
{
- setMinimumPDFVersion(pdf.getPDFVersion());
- std::string version = this->min_pdf_version;
+ setMinimumPDFVersion(pdf.getPDFVersion(), pdf.getExtensionLevel());
+ this->final_pdf_version = this->min_pdf_version;
+ this->final_extension_level = this->min_extension_level;
if (! this->forced_pdf_version.empty())
{
QTC::TC("qpdf", "QPDFWriter using forced PDF version");
- version = this->forced_pdf_version;
+ this->final_pdf_version = this->forced_pdf_version;
+ this->final_extension_level = this->forced_extension_level;
}
writeString("%PDF-");
- writeString(version);
+ writeString(this->final_pdf_version);
// This string of binary characters would not be valid UTF-8, so
// it really should be treated as binary.
writeString("\n%\xbf\xf7\xa2\xfe\n");
@@ -2427,7 +2866,7 @@ QPDFWriter::writeStandard()
writeString(this->extra_header_text);
// Put root first on queue.
- QPDFObjectHandle trailer = pdf.getTrailer();
+ QPDFObjectHandle trailer = getTrimmedTrailer();
enqueueObject(trailer.getKey("/Root"));
// Next place any other objects referenced from the trailer
diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc
index 970ee58..88b8e8f 100644
--- a/libqpdf/QPDF_Stream.cc
+++ b/libqpdf/QPDF_Stream.cc
@@ -91,6 +91,80 @@ QPDF_Stream::getRawStreamData()
}
bool
+QPDF_Stream::understandDecodeParams(
+ std::string const& filter, QPDFObjectHandle decode_obj,
+ int& predictor, int& columns, bool& early_code_change)
+{
+ bool filterable = true;
+ std::set<std::string> keys = decode_obj.getKeys();
+ for (std::set<std::string>::iterator iter = keys.begin();
+ iter != keys.end(); ++iter)
+ {
+ std::string const& key = *iter;
+ if ((filter == "/FlateDecode") && (key == "/Predictor"))
+ {
+ QPDFObjectHandle predictor_obj = decode_obj.getKey(key);
+ if (predictor_obj.isInteger())
+ {
+ predictor = predictor_obj.getIntValue();
+ if (! ((predictor == 1) || (predictor == 12)))
+ {
+ filterable = false;
+ }
+ }
+ else
+ {
+ filterable = false;
+ }
+ }
+ else if ((filter == "/LZWDecode") && (key == "/EarlyChange"))
+ {
+ QPDFObjectHandle earlychange_obj = decode_obj.getKey(key);
+ if (earlychange_obj.isInteger())
+ {
+ int earlychange = earlychange_obj.getIntValue();
+ early_code_change = (earlychange == 1);
+ if (! ((earlychange == 0) || (earlychange == 1)))
+ {
+ filterable = false;
+ }
+ }
+ else
+ {
+ filterable = false;
+ }
+ }
+ else if (key == "/Columns")
+ {
+ QPDFObjectHandle columns_obj = decode_obj.getKey(key);
+ if (columns_obj.isInteger())
+ {
+ columns = columns_obj.getIntValue();
+ }
+ else
+ {
+ filterable = false;
+ }
+ }
+ else if ((filter == "/Crypt") &&
+ (((key == "/Type") || (key == "/Name")) &&
+ (decode_obj.getKey("/Type").isNull() ||
+ (decode_obj.getKey("/Type").isName() &&
+ (decode_obj.getKey("/Type").getName() ==
+ "/CryptFilterDecodeParms")))))
+ {
+ // we handle this in decryptStream
+ }
+ else
+ {
+ filterable = false;
+ }
+ }
+
+ return filterable;
+}
+
+bool
QPDF_Stream::filterable(std::vector<std::string>& filters,
int& predictor, int& columns,
bool& early_code_change)
@@ -110,106 +184,6 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
filter_abbreviations["/DCT"] = "/DCTDecode";
}
- // Initialize values to their defaults as per the PDF spec
- predictor = 1;
- columns = 0;
- early_code_change = true;
-
- bool filterable = true;
-
- // See if we can support any decode parameters that are specified.
-
- QPDFObjectHandle decode_obj =
- this->stream_dict.getKey("/DecodeParms");
- if (decode_obj.isNull())
- {
- // no problem
- }
- else if (decode_obj.isDictionary())
- {
- std::set<std::string> keys = decode_obj.getKeys();
- for (std::set<std::string>::iterator iter = keys.begin();
- iter != keys.end(); ++iter)
- {
- std::string const& key = *iter;
- if (key == "/Predictor")
- {
- QPDFObjectHandle predictor_obj = decode_obj.getKey(key);
- if (predictor_obj.isInteger())
- {
- predictor = predictor_obj.getIntValue();
- if (! ((predictor == 1) || (predictor == 12)))
- {
- filterable = false;
- }
- }
- else
- {
- filterable = false;
- }
- }
- else if (key == "/EarlyChange")
- {
- QPDFObjectHandle earlychange_obj = decode_obj.getKey(key);
- if (earlychange_obj.isInteger())
- {
- int earlychange = earlychange_obj.getIntValue();
- early_code_change = (earlychange == 1);
- if (! ((earlychange == 0) || (earlychange == 1)))
- {
- filterable = false;
- }
- }
- else
- {
- filterable = false;
- }
- }
- else if (key == "/Columns")
- {
- QPDFObjectHandle columns_obj = decode_obj.getKey(key);
- if (columns_obj.isInteger())
- {
- columns = columns_obj.getIntValue();
- }
- else
- {
- filterable = false;
- }
- }
- else if (((key == "/Type") || (key == "/Name")) &&
- decode_obj.getKey("/Type").isName() &&
- (decode_obj.getKey("/Type").getName() ==
- "/CryptFilterDecodeParms"))
- {
- // we handle this in decryptStream
- }
- else
- {
- filterable = false;
- }
- }
- }
- else
- {
- // Ignore for now -- some filter types, like CCITTFaxDecode,
- // use types other than dictionary for this.
- QTC::TC("qpdf", "QPDF_Stream ignore non-dictionary DecodeParms");
-
- filterable = false;
- }
-
- if ((predictor > 1) && (columns == 0))
- {
- // invalid
- filterable = false;
- }
-
- if (! filterable)
- {
- return false;
- }
-
// Check filters
QPDFObjectHandle filter_obj = this->stream_dict.getKey("/Filter");
@@ -254,8 +228,7 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
"stream filter type is not name or array");
}
- // `filters' now contains a list of filters to be applied in
- // order. See which ones we can support.
+ bool filterable = true;
for (std::vector<std::string>::iterator iter = filters.begin();
iter != filters.end(); ++iter)
@@ -278,6 +251,79 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
}
}
+ if (! filterable)
+ {
+ return false;
+ }
+
+ // `filters' now contains a list of filters to be applied in
+ // order. See which ones we can support.
+
+ // Initialize values to their defaults as per the PDF spec
+ predictor = 1;
+ columns = 0;
+ early_code_change = true;
+
+ // See if we can support any decode parameters that are specified.
+
+ QPDFObjectHandle decode_obj = this->stream_dict.getKey("/DecodeParms");
+ std::vector<QPDFObjectHandle> decode_parms;
+ if (decode_obj.isArray())
+ {
+ for (int i = 0; i < decode_obj.getArrayNItems(); ++i)
+ {
+ decode_parms.push_back(decode_obj.getArrayItem(i));
+ }
+ }
+ else
+ {
+ for (unsigned int i = 0; i < filters.size(); ++i)
+ {
+ decode_parms.push_back(decode_obj);
+ }
+ }
+
+ if (decode_parms.size() != filters.size())
+ {
+ throw QPDFExc(qpdf_e_damaged_pdf, qpdf->getFilename(),
+ "", this->offset,
+ "stream /DecodeParms length is"
+ " inconsistent with filters");
+ }
+
+ for (unsigned int i = 0; i < filters.size(); ++i)
+ {
+ QPDFObjectHandle decode_item = decode_parms[i];
+ if (decode_item.isNull())
+ {
+ // okay
+ }
+ else if (decode_item.isDictionary())
+ {
+ if (! understandDecodeParams(
+ filters[i], decode_item,
+ predictor, columns, early_code_change))
+ {
+ filterable = false;
+ }
+ }
+ else
+ {
+ filterable = false;
+ }
+ }
+
+ if ((predictor > 1) && (columns == 0))
+ {
+ // invalid
+ filterable = false;
+ }
+
+ if (! filterable)
+ {
+ return false;
+ }
+
return filterable;
}
diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc
index c73a47b..60d54b7 100644
--- a/libqpdf/QPDF_encryption.cc
+++ b/libqpdf/QPDF_encryption.cc
@@ -10,6 +10,7 @@
#include <qpdf/Pl_RC4.hh>
#include <qpdf/Pl_AES_PDF.hh>
#include <qpdf/Pl_Buffer.hh>
+#include <qpdf/Pl_SHA2.hh>
#include <qpdf/RC4.hh>
#include <qpdf/MD5.hh>
@@ -23,11 +24,110 @@ static unsigned char const padding_string[] = {
0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a
};
-static unsigned int const O_key_bytes = sizeof(MD5::Digest);
static unsigned int const key_bytes = 32;
+// V4 key lengths apply to V <= 4
+static unsigned int const OU_key_bytes_V4 = sizeof(MD5::Digest);
+
+static unsigned int const OU_key_bytes_V5 = 48;
+static unsigned int const OUE_key_bytes_V5 = 32;
+static unsigned int const Perms_key_bytes_V5 = 16;
+
+int
+QPDF::EncryptionData::getV() const
+{
+ return this->V;
+}
+
+int
+QPDF::EncryptionData::getR() const
+{
+ return this->R;
+}
+
+int
+QPDF::EncryptionData::getLengthBytes() const
+{
+ return this->Length_bytes;
+}
+
+int
+QPDF::EncryptionData::getP() const
+{
+ return this->P;
+}
+
+std::string const&
+QPDF::EncryptionData::getO() const
+{
+ return this->O;
+}
+
+std::string const&
+QPDF::EncryptionData::getU() const
+{
+ return this->U;
+}
+
+std::string const&
+QPDF::EncryptionData::getOE() const
+{
+ return this->OE;
+}
+
+std::string const&
+QPDF::EncryptionData::getUE() const
+{
+ return this->UE;
+}
+
+std::string const&
+QPDF::EncryptionData::getPerms() const
+{
+ return this->Perms;
+}
+
+std::string const&
+QPDF::EncryptionData::getId1() const
+{
+ return this->id1;
+}
+
+bool
+QPDF::EncryptionData::getEncryptMetadata() const
+{
+ return this->encrypt_metadata;
+}
+
void
-pad_or_truncate_password(std::string const& password, char k1[key_bytes])
+QPDF::EncryptionData::setO(std::string const& O)
+{
+ this->O = O;
+}
+
+void
+QPDF::EncryptionData::setU(std::string const& U)
+{
+ this->U = U;
+}
+
+void
+QPDF::EncryptionData::setV5EncryptionParameters(
+ std::string const& O,
+ std::string const& OE,
+ std::string const& U,
+ std::string const& UE,
+ std::string const& Perms)
+{
+ this->O = O;
+ this->OE = OE;
+ this->U = U;
+ this->UE = UE;
+ this->Perms = Perms;
+}
+
+static void
+pad_or_truncate_password_V4(std::string const& password, char k1[key_bytes])
{
int password_bytes = std::min(key_bytes, (unsigned int)password.length());
int pad_bytes = key_bytes - password_bytes;
@@ -48,25 +148,37 @@ QPDF::trim_user_password(std::string& user_password)
return;
}
- char const* p = 0;
- while ((p = strchr(cstr, '\x28')) != 0)
+ char const* p1 = cstr;
+ char const* p2 = 0;
+ while ((p2 = strchr(p1, '\x28')) != 0)
{
- if (memcmp(p, padding_string, len - (p - cstr)) == 0)
+ if (memcmp(p2, padding_string, len - (p2 - cstr)) == 0)
{
- user_password = user_password.substr(0, p - cstr);
+ user_password = user_password.substr(0, p2 - cstr);
return;
}
+ else
+ {
+ QTC::TC("qpdf", "QPDF_encryption skip 0x28");
+ p1 = p2 + 1;
+ }
}
}
static std::string
-pad_or_truncate_password(std::string const& password)
+pad_or_truncate_password_V4(std::string const& password)
{
char k1[key_bytes];
- pad_or_truncate_password(password, k1);
+ pad_or_truncate_password_V4(password, k1);
return std::string(k1, key_bytes);
}
+static std::string
+truncate_password_V5(std::string const& password)
+{
+ return password.substr(0, std::min((size_t)127, password.length()));
+}
+
static void
iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations)
{
@@ -100,15 +212,146 @@ iterate_rc4(unsigned char* data, int data_len,
delete [] key;
}
+static std::string
+process_with_aes(std::string const& key,
+ bool encrypt,
+ std::string const& data,
+ size_t outlength = 0,
+ unsigned int repetitions = 1,
+ unsigned char const* iv = 0,
+ size_t iv_length = 0)
+{
+ Pl_Buffer buffer("buffer");
+ Pl_AES_PDF aes("aes", &buffer, encrypt,
+ (unsigned char const*)key.c_str(),
+ (unsigned int)key.length());
+ if (iv)
+ {
+ aes.setIV(iv, iv_length);
+ }
+ else
+ {
+ aes.useZeroIV();
+ }
+ aes.disablePadding();
+ for (unsigned int i = 0; i < repetitions; ++i)
+ {
+ aes.write((unsigned char*)data.c_str(), data.length());
+ }
+ aes.finish();
+ PointerHolder<Buffer> bufp = buffer.getBuffer();
+ if (outlength == 0)
+ {
+ outlength = bufp->getSize();
+ }
+ else
+ {
+ outlength = std::min(outlength, bufp->getSize());
+ }
+ return std::string((char const*)bufp->getBuffer(), outlength);
+}
+
+static std::string
+hash_V5(std::string const& password,
+ std::string const& salt,
+ std::string const& udata,
+ QPDF::EncryptionData const& data)
+{
+ Pl_SHA2 hash(256);
+ hash.write((unsigned char*)password.c_str(), password.length());
+ hash.write((unsigned char*)salt.c_str(), salt.length());
+ hash.write((unsigned char*)udata.c_str(), udata.length());
+ hash.finish();
+ std::string K = hash.getRawDigest();
+
+ std::string result;
+ if (data.getR() < 6)
+ {
+ result = K;
+ }
+ else
+ {
+ // Algorithm 2.B from ISO 32000-1 chapter 7: Computing a hash
+
+ int round_number = 0;
+ bool done = false;
+ while (! done)
+ {
+ // The hash algorithm has us setting K initially to the R5
+ // value and then repeating a series of steps 64 times
+ // before starting with the termination case testing. The
+ // wording of the specification is very unclear as to the
+ // exact number of times it should be run since the
+ // wording about whether the initial setup counts as round
+ // 0 or not is ambiguous. This code counts the initial
+ // setup (R5) value as round 0, which appears to be
+ // correct. This was determined to be correct by
+ // increasing or decreasing the number of rounds by 1 or 2
+ // from this value and generating 20 test files. In this
+ // interpretation, all the test files worked with Adobe
+ // Reader X. In the other configurations, many of the
+ // files did not work, and we were accurately able to
+ // predict which files didn't work by looking at the
+ // conditions under which we terminated repetition.
+
+ ++round_number;
+ std::string K1 = password + K + udata;
+ assert(K.length() >= 32);
+ std::string E = process_with_aes(
+ K.substr(0, 16), true, K1, 0, 64,
+ (unsigned char*)K.substr(16, 16).c_str(), 16);
+
+ // E_mod_3 is supposed to be mod 3 of the first 16 bytes
+ // of E taken as as a (128-bit) big-endian number. Since
+ // (xy mod n) is equal to ((x mod n) + (y mod n)) mod n
+ // and since 256 mod n is 1, we can just take the sums of
+ // the the mod 3s of each byte to get the same result.
+ int E_mod_3 = 0;
+ for (unsigned int i = 0; i < 16; ++i)
+ {
+ E_mod_3 += (unsigned char)E[i];
+ }
+ E_mod_3 %= 3;
+ int next_hash = ((E_mod_3 == 0) ? 256 :
+ (E_mod_3 == 1) ? 384 :
+ 512);
+ Pl_SHA2 hash(next_hash);
+ hash.write((unsigned char*)E.c_str(), E.length());
+ hash.finish();
+ K = hash.getRawDigest();
+
+ if (round_number >= 64)
+ {
+ unsigned int ch = (unsigned int)((unsigned char) *(E.rbegin()));
+
+ if (ch <= (unsigned int)(round_number - 32))
+ {
+ done = true;
+ }
+ }
+ }
+ result = K.substr(0, 32);
+ }
+
+ return result;
+}
+
std::string
QPDF::compute_data_key(std::string const& encryption_key,
- int objid, int generation,
- bool use_aes)
+ int objid, int generation, bool use_aes,
+ int encryption_V, int encryption_R)
{
// Algorithm 3.1 from the PDF 1.7 Reference Manual
std::string result = encryption_key;
+ if (encryption_V >= 5)
+ {
+ // Algorithm 3.1a (PDF 1.7 extension level 3): just use
+ // encryption key straight.
+ return result;
+ }
+
// Append low three bytes of object ID and low two bytes of generation
result += (char) (objid & 0xff);
result += (char) ((objid >> 8) & 0xff);
@@ -132,36 +375,69 @@ std::string
QPDF::compute_encryption_key(
std::string const& password, EncryptionData const& data)
{
+ if (data.getV() >= 5)
+ {
+ // For V >= 5, the encryption key is generated and stored in
+ // the file, encrypted separately with both user and owner
+ // passwords.
+ return recover_encryption_key_with_password(password, data);
+ }
+ else
+ {
+ // For V < 5, the encryption key is derived from the user
+ // password.
+ return compute_encryption_key_from_password(password, data);
+ }
+}
+
+std::string
+QPDF::compute_encryption_key_from_password(
+ std::string const& password, EncryptionData const& data)
+{
// Algorithm 3.2 from the PDF 1.7 Reference Manual
+ // This code does not properly handle Unicode passwords.
+ // Passwords are supposed to be converted from OS codepage
+ // characters to PDFDocEncoding. Unicode passwords are supposed
+ // to be converted to OS codepage before converting to
+ // PDFDocEncoding. We instead require the password to be
+ // presented in its final form.
+
MD5 md5;
md5.encodeDataIncrementally(
- pad_or_truncate_password(password).c_str(), key_bytes);
- md5.encodeDataIncrementally(data.O.c_str(), key_bytes);
+ pad_or_truncate_password_V4(password).c_str(), key_bytes);
+ md5.encodeDataIncrementally(data.getO().c_str(), key_bytes);
char pbytes[4];
- pbytes[0] = (char) (data.P & 0xff);
- pbytes[1] = (char) ((data.P >> 8) & 0xff);
- pbytes[2] = (char) ((data.P >> 16) & 0xff);
- pbytes[3] = (char) ((data.P >> 24) & 0xff);
+ int P = data.getP();
+ pbytes[0] = (char) (P & 0xff);
+ pbytes[1] = (char) ((P >> 8) & 0xff);
+ pbytes[2] = (char) ((P >> 16) & 0xff);
+ pbytes[3] = (char) ((P >> 24) & 0xff);
md5.encodeDataIncrementally(pbytes, 4);
- md5.encodeDataIncrementally(data.id1.c_str(), (int)data.id1.length());
- if ((data.R >= 4) && (! data.encrypt_metadata))
+ md5.encodeDataIncrementally(data.getId1().c_str(),
+ (int)data.getId1().length());
+ if ((data.getR() >= 4) && (! data.getEncryptMetadata()))
{
char bytes[4];
memset(bytes, 0xff, 4);
md5.encodeDataIncrementally(bytes, 4);
}
MD5::Digest digest;
- iterate_md5_digest(md5, digest, ((data.R >= 3) ? 50 : 0));
- return std::string((char*)digest, data.Length_bytes);
+ iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0));
+ return std::string((char*)digest, data.getLengthBytes());
}
static void
compute_O_rc4_key(std::string const& user_password,
std::string const& owner_password,
QPDF::EncryptionData const& data,
- unsigned char key[O_key_bytes])
+ unsigned char key[OU_key_bytes_V4])
{
+ if (data.getV() >= 5)
+ {
+ throw std::logic_error(
+ "compute_O_rc4_key called for file with V >= 5");
+ }
std::string password = owner_password;
if (password.empty())
{
@@ -169,10 +445,10 @@ compute_O_rc4_key(std::string const& user_password,
}
MD5 md5;
md5.encodeDataIncrementally(
- pad_or_truncate_password(password).c_str(), key_bytes);
+ pad_or_truncate_password_V4(password).c_str(), key_bytes);
MD5::Digest digest;
- iterate_md5_digest(md5, digest, ((data.R >= 3) ? 50 : 0));
- memcpy(key, digest, O_key_bytes);
+ iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0));
+ memcpy(key, digest, OU_key_bytes_V4);
}
static std::string
@@ -182,13 +458,14 @@ compute_O_value(std::string const& user_password,
{
// Algorithm 3.3 from the PDF 1.7 Reference Manual
- unsigned char O_key[O_key_bytes];
+ unsigned char O_key[OU_key_bytes_V4];
compute_O_rc4_key(user_password, owner_password, data, O_key);
char upass[key_bytes];
- pad_or_truncate_password(user_password, upass);
+ pad_or_truncate_password_V4(user_password, upass);
iterate_rc4((unsigned char*) upass, key_bytes,
- O_key, data.Length_bytes, (data.R >= 3) ? 20 : 1, false);
+ O_key, data.getLengthBytes(),
+ (data.getR() >= 3) ? 20 : 1, false);
return std::string(upass, key_bytes);
}
@@ -201,9 +478,9 @@ compute_U_value_R2(std::string const& user_password,
std::string k1 = QPDF::compute_encryption_key(user_password, data);
char udata[key_bytes];
- pad_or_truncate_password("", udata);
+ pad_or_truncate_password_V4("", udata);
iterate_rc4((unsigned char*) udata, key_bytes,
- (unsigned char*)k1.c_str(), data.Length_bytes, 1, false);
+ (unsigned char*)k1.c_str(), data.getLengthBytes(), 1, false);
return std::string(udata, key_bytes);
}
@@ -217,12 +494,13 @@ compute_U_value_R3(std::string const& user_password,
std::string k1 = QPDF::compute_encryption_key(user_password, data);
MD5 md5;
md5.encodeDataIncrementally(
- pad_or_truncate_password("").c_str(), key_bytes);
- md5.encodeDataIncrementally(data.id1.c_str(), (int)data.id1.length());
+ pad_or_truncate_password_V4("").c_str(), key_bytes);
+ md5.encodeDataIncrementally(data.getId1().c_str(),
+ (int)data.getId1().length());
MD5::Digest digest;
md5.digest(digest);
iterate_rc4(digest, sizeof(MD5::Digest),
- (unsigned char*) k1.c_str(), data.Length_bytes, 20, false);
+ (unsigned char*) k1.c_str(), data.getLengthBytes(), 20, false);
char result[key_bytes];
memcpy(result, digest, sizeof(MD5::Digest));
// pad with arbitrary data -- make it consistent for the sake of
@@ -238,7 +516,7 @@ static std::string
compute_U_value(std::string const& user_password,
QPDF::EncryptionData const& data)
{
- if (data.R >= 3)
+ if (data.getR() >= 3)
{
return compute_U_value_R3(user_password, data);
}
@@ -247,40 +525,214 @@ compute_U_value(std::string const& user_password,
}
static bool
-check_user_password(std::string const& user_password,
- QPDF::EncryptionData const& data)
+check_user_password_V4(std::string const& user_password,
+ QPDF::EncryptionData const& data)
{
// Algorithm 3.6 from the PDF 1.7 Reference Manual
std::string u_value = compute_U_value(user_password, data);
- int to_compare = ((data.R >= 3) ? sizeof(MD5::Digest) : key_bytes);
- return (memcmp(data.U.c_str(), u_value.c_str(), to_compare) == 0);
+ int to_compare = ((data.getR() >= 3) ? sizeof(MD5::Digest)
+ : key_bytes);
+ return (memcmp(data.getU().c_str(), u_value.c_str(), to_compare) == 0);
}
static bool
-check_owner_password(std::string& user_password,
- std::string const& owner_password,
- QPDF::EncryptionData const& data)
+check_user_password_V5(std::string const& user_password,
+ QPDF::EncryptionData const& data)
+{
+ // Algorithm 3.11 from the PDF 1.7 extension level 3
+
+ std::string user_data = data.getU().substr(0, 32);
+ std::string validation_salt = data.getU().substr(32, 8);
+ std::string password = truncate_password_V5(user_password);
+ return (hash_V5(password, validation_salt, "", data) == user_data);
+}
+
+static bool
+check_user_password(std::string const& user_password,
+ QPDF::EncryptionData const& data)
+{
+ if (data.getV() < 5)
+ {
+ return check_user_password_V4(user_password, data);
+ }
+ else
+ {
+ return check_user_password_V5(user_password, data);
+ }
+}
+
+static bool
+check_owner_password_V4(std::string& user_password,
+ std::string const& owner_password,
+ QPDF::EncryptionData const& data)
{
// Algorithm 3.7 from the PDF 1.7 Reference Manual
- unsigned char key[O_key_bytes];
+ unsigned char key[OU_key_bytes_V4];
compute_O_rc4_key(user_password, owner_password, data, key);
unsigned char O_data[key_bytes];
- memcpy(O_data, (unsigned char*) data.O.c_str(), key_bytes);
- iterate_rc4(O_data, key_bytes, key, data.Length_bytes,
- (data.R >= 3) ? 20 : 1, true);
+ memcpy(O_data, (unsigned char*) data.getO().c_str(), key_bytes);
+ iterate_rc4(O_data, key_bytes, key, data.getLengthBytes(),
+ (data.getR() >= 3) ? 20 : 1, true);
std::string new_user_password =
- std::string((char*)O_data, key_bytes);
+ std::string((char*)O_data, key_bytes);
bool result = false;
if (check_user_password(new_user_password, data))
{
- result = true;
- user_password = new_user_password;
+ result = true;
+ user_password = new_user_password;
}
return result;
}
+static bool
+check_owner_password_V5(std::string const& owner_password,
+ QPDF::EncryptionData const& data)
+{
+ // Algorithm 3.12 from the PDF 1.7 extension level 3
+
+ std::string user_data = data.getU().substr(0, 48);
+ std::string owner_data = data.getO().substr(0, 32);
+ std::string validation_salt = data.getO().substr(32, 8);
+ std::string password = truncate_password_V5(owner_password);
+ return (hash_V5(password, validation_salt, user_data,
+ data) == owner_data);
+}
+
+static bool
+check_owner_password(std::string& user_password,
+ std::string const& owner_password,
+ QPDF::EncryptionData const& data)
+{
+ if (data.getV() < 5)
+ {
+ return check_owner_password_V4(user_password, owner_password, data);
+ }
+ else
+ {
+ return check_owner_password_V5(owner_password, data);
+ }
+}
+
+std::string
+QPDF::recover_encryption_key_with_password(
+ std::string const& password, EncryptionData const& data)
+{
+ // Disregard whether Perms is valid.
+ bool disregard;
+ return recover_encryption_key_with_password(password, data, disregard);
+}
+
+static void
+compute_U_UE_value_V5(std::string const& user_password,
+ std::string const& encryption_key,
+ QPDF::EncryptionData const& data,
+ std::string& U, std::string& UE)
+{
+ // Algorithm 3.8 from the PDF 1.7 extension level 3
+ char k[16];
+ QUtil::initializeWithRandomBytes((unsigned char*) k, sizeof(k));
+ std::string validation_salt(k, 8);
+ std::string key_salt(k + 8, 8);
+ U = hash_V5(user_password, validation_salt, "", data) +
+ validation_salt + key_salt;
+ std::string intermediate_key = hash_V5(user_password, key_salt, "", data);
+ UE = process_with_aes(intermediate_key, true, encryption_key);
+}
+
+static void
+compute_O_OE_value_V5(std::string const& owner_password,
+ std::string const& encryption_key,
+ QPDF::EncryptionData const& data,
+ std::string const& U,
+ std::string& O, std::string& OE)
+{
+ // Algorithm 3.9 from the PDF 1.7 extension level 3
+ char k[16];
+ QUtil::initializeWithRandomBytes((unsigned char*) k, sizeof(k));
+ std::string validation_salt(k, 8);
+ std::string key_salt(k + 8, 8);
+ O = hash_V5(owner_password, validation_salt, U, data) +
+ validation_salt + key_salt;
+ std::string intermediate_key = hash_V5(owner_password, key_salt, U, data);
+ OE = process_with_aes(intermediate_key, true, encryption_key);
+}
+
+void
+compute_Perms_value_V5_clear(std::string const& encryption_key,
+ QPDF::EncryptionData const& data,
+ unsigned char k[16])
+{
+ // From algorithm 3.10 from the PDF 1.7 extension level 3
+ unsigned long long extended_perms = 0xffffffff00000000LL | data.getP();
+ for (int i = 0; i < 8; ++i)
+ {
+ k[i] = (unsigned char) (extended_perms & 0xff);
+ extended_perms >>= 8;
+ }
+ k[8] = data.getEncryptMetadata() ? 'T' : 'F';
+ k[9] = 'a';
+ k[10] = 'd';
+ k[11] = 'b';
+ QUtil::initializeWithRandomBytes(k + 12, 4);
+}
+
+static std::string
+compute_Perms_value_V5(std::string const& encryption_key,
+ QPDF::EncryptionData const& data)
+{
+ // Algorithm 3.10 from the PDF 1.7 extension level 3
+ unsigned char k[16];
+ compute_Perms_value_V5_clear(encryption_key, data, k);
+ return process_with_aes(encryption_key, true,
+ std::string((char const*) k, sizeof(k)));
+}
+
+std::string
+QPDF::recover_encryption_key_with_password(
+ std::string const& password, EncryptionData const& data,
+ bool& perms_valid)
+{
+ // Algorithm 3.2a from the PDF 1.7 extension level 3
+
+ // This code does not handle Unicode passwords correctly.
+ // Empirical evidence suggests that most viewers don't. We are
+ // supposed to process the input string with the SASLprep (RFC
+ // 4013) profile of stringprep (RFC 3454) and then convert the
+ // result to UTF-8.
+
+ perms_valid = false;
+ std::string key_password = truncate_password_V5(password);
+ std::string key_salt;
+ std::string user_data;
+ std::string encrypted_file_key;
+ if (check_owner_password_V5(key_password, data))
+ {
+ key_salt = data.getO().substr(40, 8);
+ user_data = data.getU().substr(0, 48);
+ encrypted_file_key = data.getOE().substr(0, 32);
+ }
+ else if (check_user_password_V5(key_password, data))
+ {
+ key_salt = data.getU().substr(40, 8);
+ encrypted_file_key = data.getUE().substr(0, 32);
+ }
+ std::string intermediate_key =
+ hash_V5(key_password, key_salt, user_data, data);
+ std::string file_key =
+ process_with_aes(intermediate_key, false, encrypted_file_key);
+
+ // Decrypt Perms and check against expected value
+ std::string perms_check =
+ process_with_aes(file_key, false, data.getPerms(), 12);
+ unsigned char k[16];
+ compute_Perms_value_V5_clear(file_key, data, k);
+ perms_valid = (memcmp(perms_check.c_str(), k, 12) == 0);
+
+ return file_key;
+}
+
QPDF::encryption_method_e
QPDF::interpretCF(QPDFObjectHandle cf)
{
@@ -384,29 +836,70 @@ QPDF::initializeEncryption()
std::string U = encryption_dict.getKey("/U").getStringValue();
unsigned int P = (unsigned int) encryption_dict.getKey("/P").getIntValue();
- if (! (((R == 2) || (R == 3) || (R == 4)) &&
- ((V == 1) || (V == 2) || (V == 4))))
+ // If supporting new encryption R/V values, remember to update
+ // error message inside this if statement.
+ if (! (((R >= 2) && (R <= 6)) &&
+ ((V == 1) || (V == 2) || (V == 4) || (V == 5))))
{
throw QPDFExc(qpdf_e_unsupported, this->file->getName(),
"encryption dictionary", this->file->getLastOffset(),
- "Unsupported /R or /V in encryption dictionary");
+ "Unsupported /R or /V in encryption dictionary; R = " +
+ QUtil::int_to_string(R) + " (max 6), V = " +
+ QUtil::int_to_string(V) + " (max 5)");
}
this->encryption_V = V;
+ this->encryption_R = R;
- if (! ((O.length() == key_bytes) && (U.length() == key_bytes)))
+ // OE, UE, and Perms are only present if V >= 5.
+ std::string OE;
+ std::string UE;
+ std::string Perms;
+
+ if (V < 5)
{
- throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
- "encryption dictionary", this->file->getLastOffset(),
- "incorrect length for /O and/or /P in "
- "encryption dictionary");
+ if (! ((O.length() == key_bytes) && (U.length() == key_bytes)))
+ {
+ throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
+ "encryption dictionary", this->file->getLastOffset(),
+ "incorrect length for /O and/or /U in "
+ "encryption dictionary");
+ }
+ }
+ else
+ {
+ if (! (encryption_dict.getKey("/OE").isString() &&
+ encryption_dict.getKey("/UE").isString() &&
+ encryption_dict.getKey("/Perms").isString()))
+ {
+ throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
+ "encryption dictionary", this->file->getLastOffset(),
+ "some V=5 encryption dictionary parameters are "
+ "missing or the wrong type");
+ }
+ OE = encryption_dict.getKey("/OE").getStringValue();
+ UE = encryption_dict.getKey("/UE").getStringValue();
+ Perms = encryption_dict.getKey("/Perms").getStringValue();
+
+ if ((O.length() < OU_key_bytes_V5) ||
+ (U.length() < OU_key_bytes_V5) ||
+ (OE.length() < OUE_key_bytes_V5) ||
+ (UE.length() < OUE_key_bytes_V5) ||
+ (Perms.length() < Perms_key_bytes_V5))
+ {
+ throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
+ "encryption dictionary", this->file->getLastOffset(),
+ "incorrect length for some of"
+ " /O, /U, /OE, /UE, or /Perms in"
+ " encryption dictionary");
+ }
}
int Length = 40;
if (encryption_dict.getKey("/Length").isInteger())
{
Length = encryption_dict.getKey("/Length").getIntValue();
- if ((Length % 8) || (Length < 40) || (Length > 128))
+ if ((Length % 8) || (Length < 40) || (Length > 256))
{
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
"encryption dictionary", this->file->getLastOffset(),
@@ -421,7 +914,7 @@ QPDF::initializeEncryption()
encryption_dict.getKey("/EncryptMetadata").getBoolValue();
}
- if (V == 4)
+ if ((V == 4) || (V == 5))
{
QPDFObjectHandle CF = encryption_dict.getKey("/CF");
std::set<std::string> keys = CF.getKeys();
@@ -446,6 +939,11 @@ QPDF::initializeEncryption()
QTC::TC("qpdf", "QPDF_encryption CFM AESV2");
method = e_aes;
}
+ else if (method_name == "/AESV3")
+ {
+ QTC::TC("qpdf", "QPDF_encryption CFM AESV3");
+ method = e_aesv3;
+ }
else
{
// Don't complain now -- maybe we won't need
@@ -470,35 +968,15 @@ QPDF::initializeEncryption()
{
this->cf_file = this->cf_stream;
}
- if (this->cf_file != this->cf_stream)
- {
- // The issue for qpdf is that it can't tell the difference
- // between an embedded file stream and a regular stream.
- // Search for a comment containing cf_file. To fix this,
- // we need files with encrypted embedded files and
- // non-encrypted native streams and vice versa. Also if
- // it is possible for them to be encrypted in different
- // ways, we should have some of those too. In cases where
- // we can detect whether a stream is encrypted or not, we
- // might want to try to detecet that automatically in
- // defense of possible logic errors surrounding detection
- // of embedded file streams, unless that's really clear
- // from the specification.
- throw QPDFExc(qpdf_e_unsupported, this->file->getName(),
- "encryption dictionary", this->file->getLastOffset(),
- "This document has embedded files that are"
- " encrypted differently from the rest of the file."
- " qpdf does not presently support this due to"
- " lack of test data; if possible, please submit"
- " a bug report that includes this file.");
- }
}
- EncryptionData data(V, R, Length / 8, P, O, U, id1, this->encrypt_metadata);
+
+ EncryptionData data(V, R, Length / 8, P, O, U, OE, UE, Perms,
+ id1, this->encrypt_metadata);
if (check_owner_password(
this->user_password, this->provided_password, data))
{
// password supplied was owner password; user_password has
- // been initialized
+ // been initialized for V < 5
}
else if (check_user_password(this->provided_password, data))
{
@@ -510,7 +988,30 @@ QPDF::initializeEncryption()
"", 0, "invalid password");
}
- this->encryption_key = compute_encryption_key(this->user_password, data);
+ if (V < 5)
+ {
+ // For V < 5, the user password is encrypted with the owner
+ // password, and the user password is always used for
+ // computing the encryption key.
+ this->encryption_key = compute_encryption_key(
+ this->user_password, data);
+ }
+ else
+ {
+ // For V >= 5, either password can be used independently to
+ // compute the encryption key, and neither password can be
+ // used to recover the other.
+ bool perms_valid;
+ this->encryption_key = recover_encryption_key_with_password(
+ this->provided_password, data, perms_valid);
+ if (! perms_valid)
+ {
+ warn(QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
+ "encryption dictionary", this->file->getLastOffset(),
+ "/Perms field in encryption dictionary"
+ " doesn't match expected value"));
+ }
+ }
}
std::string
@@ -526,7 +1027,8 @@ QPDF::getKeyForObject(int objid, int generation, bool use_aes)
(generation == this->cached_key_generation)))
{
this->cached_object_encryption_key =
- compute_data_key(this->encryption_key, objid, generation, use_aes);
+ compute_data_key(this->encryption_key, objid, generation,
+ use_aes, this->encryption_V, this->encryption_R);
this->cached_key_objid = objid;
this->cached_key_generation = generation;
}
@@ -542,7 +1044,7 @@ QPDF::decryptString(std::string& str, int objid, int generation)
return;
}
bool use_aes = false;
- if (this->encryption_V == 4)
+ if (this->encryption_V >= 4)
{
switch (this->cf_string)
{
@@ -553,6 +1055,10 @@ QPDF::decryptString(std::string& str, int objid, int generation)
use_aes = true;
break;
+ case e_aesv3:
+ use_aes = true;
+ break;
+
case e_rc4:
break;
@@ -576,10 +1082,10 @@ QPDF::decryptString(std::string& str, int objid, int generation)
if (use_aes)
{
QTC::TC("qpdf", "QPDF_encryption aes decode string");
- assert(key.length() == Pl_AES_PDF::key_size);
Pl_Buffer bufpl("decrypted string");
Pl_AES_PDF pl("aes decrypt string", &bufpl, false,
- (unsigned char const*)key.c_str());
+ (unsigned char const*)key.c_str(),
+ (unsigned int)key.length());
pl.write((unsigned char*)str.c_str(), str.length());
pl.finish();
PointerHolder<Buffer> buf = bufpl.getBuffer();
@@ -628,23 +1134,53 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
return;
}
bool use_aes = false;
- if (this->encryption_V == 4)
+ if (this->encryption_V >= 4)
{
encryption_method_e method = e_unknown;
std::string method_source = "/StmF from /Encrypt dictionary";
- if (stream_dict.getKey("/Filter").isOrHasName("/Crypt") &&
- stream_dict.getKey("/DecodeParms").isDictionary())
- {
- QPDFObjectHandle decode_parms = stream_dict.getKey("/DecodeParms");
- if (decode_parms.getKey("/Type").isName() &&
- (decode_parms.getKey("/Type").getName() ==
- "/CryptFilterDecodeParms"))
- {
- QTC::TC("qpdf", "QPDF_encryption stream crypt filter");
- method = interpretCF(decode_parms.getKey("/Name"));
- method_source = "stream's Crypt decode parameters";
- }
+ if (stream_dict.getKey("/Filter").isOrHasName("/Crypt"))
+ {
+ if (stream_dict.getKey("/DecodeParms").isDictionary())
+ {
+ QPDFObjectHandle decode_parms =
+ stream_dict.getKey("/DecodeParms");
+ if (decode_parms.getKey("/Type").isName() &&
+ (decode_parms.getKey("/Type").getName() ==
+ "/CryptFilterDecodeParms"))
+ {
+ QTC::TC("qpdf", "QPDF_encryption stream crypt filter");
+ method = interpretCF(decode_parms.getKey("/Name"));
+ method_source = "stream's Crypt decode parameters";
+ }
+ }
+ else if (stream_dict.getKey("/DecodeParms").isArray() &&
+ stream_dict.getKey("/Filter").isArray())
+ {
+ QPDFObjectHandle filter = stream_dict.getKey("/Filter");
+ QPDFObjectHandle decode = stream_dict.getKey("/DecodeParms");
+ if (filter.getArrayNItems() == decode.getArrayNItems())
+ {
+ for (int i = 0; i < filter.getArrayNItems(); ++i)
+ {
+ if (filter.getArrayItem(i).isName() &&
+ (filter.getArrayItem(i).getName() == "/Crypt"))
+ {
+ QPDFObjectHandle crypt_params =
+ decode.getArrayItem(i);
+ if (crypt_params.isDictionary() &&
+ crypt_params.getKey("/Name").isName())
+ {
+ QTC::TC("qpdf", "QPDF_encrypt crypt array");
+ method = interpretCF(
+ crypt_params.getKey("/Name"));
+ method_source = "stream's Crypt "
+ "decode parameters (array)";
+ }
+ }
+ }
+ }
+ }
}
if (method == e_unknown)
@@ -656,12 +1192,15 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
}
else
{
- // NOTE: We should should use cf_file if this is an
- // embedded file, but we can't yet detect embedded
- // file streams as such. When fixing, search for all
- // occurrences of cf_file to find a reference to this
- // comment.
- method = this->cf_stream;
+ if (this->attachment_streams.count(
+ ObjGen(objid, generation)) > 0)
+ {
+ method = this->cf_file;
+ }
+ else
+ {
+ method = this->cf_stream;
+ }
}
}
use_aes = false;
@@ -675,6 +1214,10 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
use_aes = true;
break;
+ case e_aesv3:
+ use_aes = true;
+ break;
+
case e_rc4:
break;
@@ -696,15 +1239,16 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
if (use_aes)
{
QTC::TC("qpdf", "QPDF_encryption aes decode stream");
- assert(key.length() == Pl_AES_PDF::key_size);
pipeline = new Pl_AES_PDF("AES stream decryption", pipeline,
- false, (unsigned char*) key.c_str());
+ false, (unsigned char*) key.c_str(),
+ (unsigned int) key.length());
}
else
{
QTC::TC("qpdf", "QPDF_encryption rc4 decode stream");
pipeline = new Pl_RC4("RC4 stream decryption", pipeline,
- (unsigned char*) key.c_str(), (int)key.length());
+ (unsigned char*) key.c_str(),
+ (unsigned int) key.length());
}
heap.push_back(pipeline);
}
@@ -715,10 +1259,37 @@ QPDF::compute_encryption_O_U(
int V, int R, int key_len, int P, bool encrypt_metadata,
std::string const& id1, std::string& O, std::string& U)
{
- EncryptionData data(V, R, key_len, P, "", "", id1, encrypt_metadata);
- data.O = compute_O_value(user_password, owner_password, data);
- O = data.O;
- U = compute_U_value(user_password, data);
+ if (V >= 5)
+ {
+ throw std::logic_error(
+ "compute_encryption_O_U called for file with V >= 5");
+ }
+ EncryptionData data(V, R, key_len, P, "", "", "", "", "",
+ id1, encrypt_metadata);
+ data.setO(compute_O_value(user_password, owner_password, data));
+ O = data.getO();
+ data.setU(compute_U_value(user_password, data));
+ U = data.getU();
+}
+
+void
+QPDF::compute_encryption_parameters_V5(
+ char const* user_password, char const* owner_password,
+ int V, int R, int key_len, int P, bool encrypt_metadata,
+ std::string const& id1,
+ std::string& encryption_key,
+ std::string& O, std::string& U,
+ std::string& OE, std::string& UE, std::string& Perms)
+{
+ EncryptionData data(V, R, key_len, P, "", "", "", "", "",
+ id1, encrypt_metadata);
+ unsigned char k[key_bytes];
+ QUtil::initializeWithRandomBytes(k, key_bytes);
+ encryption_key = std::string((char const*)k, key_bytes);
+ compute_U_UE_value_V5(user_password, encryption_key, data, U, UE);
+ compute_O_OE_value_V5(owner_password, encryption_key, data, U, O, OE);
+ Perms = compute_Perms_value_V5(encryption_key, data);
+ data.setV5EncryptionParameters(O, OE, U, UE, Perms);
}
std::string const&
@@ -735,6 +1306,12 @@ QPDF::getTrimmedUserPassword() const
return result;
}
+std::string
+QPDF::getEncryptionKey() const
+{
+ return this->encryption_key;
+}
+
bool
QPDF::isEncrypted() const
{
diff --git a/libqpdf/QPDF_optimization.cc b/libqpdf/QPDF_optimization.cc
index 67f147f..f832a88 100644
--- a/libqpdf/QPDF_optimization.cc
+++ b/libqpdf/QPDF_optimization.cc
@@ -59,103 +59,6 @@ QPDF::ObjUser::operator<(ObjUser const& rhs) const
}
void
-QPDF::flattenScalarReferences()
-{
- // Do a traversal of the entire PDF file structure replacing all
- // indirect objects that are not arrays, streams, or dictionaries
- // with direct objects.
-
- std::list<QPDFObjectHandle> queue;
- queue.push_back(this->trailer);
- std::set<ObjGen> visited;
-
- // Add every object in the xref table to the queue. This ensures
- // that we flatten scalar references in unreferenced objects.
- // This becomes important if we are preserving object streams in a
- // file that has unreferenced objects in its object streams. (See
- // QPDF bug 2974522 at SourceForge.)
- for (std::map<ObjGen, QPDFXRefEntry>::iterator iter =
- this->xref_table.begin();
- iter != this->xref_table.end(); ++iter)
- {
- ObjGen const& og = (*iter).first;
- queue.push_back(getObjectByID(og.obj, og.gen));
- }
-
- while (! queue.empty())
- {
- QPDFObjectHandle node = queue.front();
- queue.pop_front();
- if (node.isIndirect())
- {
- ObjGen og(node.getObjectID(), node.getGeneration());
- if (visited.count(og) > 0)
- {
- continue;
- }
- visited.insert(og);
- }
-
- if (node.isArray())
- {
- int nitems = node.getArrayNItems();
- for (int i = 0; i < nitems; ++i)
- {
- QPDFObjectHandle oh = node.getArrayItem(i);
- if (oh.isScalar())
- {
- if (oh.isIndirect())
- {
- QTC::TC("qpdf", "QPDF opt flatten array scalar");
- oh.makeDirect();
- node.setArrayItem(i, oh);
- }
- }
- else
- {
- queue.push_back(oh);
- }
- }
- }
- else if (node.isDictionary() || node.isStream())
- {
- QPDFObjectHandle dict = node;
- if (node.isStream())
- {
- dict = node.getDict();
- }
- std::set<std::string> keys = dict.getKeys();
- for (std::set<std::string>::iterator iter = keys.begin();
- iter != keys.end(); ++iter)
- {
- std::string const& key = *iter;
- QPDFObjectHandle oh = dict.getKey(key);
- if (oh.isNull())
- {
- // QPDF_Dictionary.getKeys() never returns null
- // keys.
- throw std::logic_error(
- "INTERNAL ERROR: dictionary with null key found");
- }
- else if (oh.isScalar())
- {
- if (oh.isIndirect())
- {
- QTC::TC("qpdf", "QPDF opt flatten dict scalar");
- oh.makeDirect();
- dict.replaceKey(key, oh);
- }
- }
- else
- {
- queue.push_back(oh);
- }
- }
- }
- }
-}
-
-void
QPDF::optimize(std::map<int, int> const& object_stream_data,
bool allow_changes)
{
@@ -304,9 +207,7 @@ QPDF::pushInheritedAttributesToPageInternal(
}
else
{
- // Don't defeat flattenScalarReferences which
- // would have already been called by this
- // time.
+ // It's okay to copy scalars.
QTC::TC("qpdf", "QPDF opt inherited scalar");
}
}
diff --git a/libqpdf/QUtil.cc b/libqpdf/QUtil.cc
index 2014308..3cdfdc4 100644
--- a/libqpdf/QUtil.cc
+++ b/libqpdf/QUtil.cc
@@ -333,3 +333,42 @@ QUtil::toUTF8(unsigned long uval)
return result;
}
+
+long
+QUtil::random()
+{
+ static bool seeded_random = false;
+ if (! seeded_random)
+ {
+ // Seed the random number generator with something simple, but
+ // just to be interesting, don't use the unmodified current
+ // time....
+ QUtil::srandom((int)QUtil::get_current_time() ^ 0xcccc);
+ seeded_random = true;
+ }
+
+#ifdef HAVE_RANDOM
+ return ::random();
+#else
+ return rand();
+#endif
+}
+
+void
+QUtil::srandom(unsigned int seed)
+{
+#ifdef HAVE_RANDOM
+ ::srandom(seed);
+#else
+ srand(seed);
+#endif
+}
+
+void
+QUtil::initializeWithRandomBytes(unsigned char* data, size_t len)
+{
+ for (size_t i = 0; i < len; ++i)
+ {
+ data[i] = (unsigned char)((QUtil::random() & 0xff0) >> 4);
+ }
+}
diff --git a/libqpdf/build.mk b/libqpdf/build.mk
index 6debf10..d64fde5 100644
--- a/libqpdf/build.mk
+++ b/libqpdf/build.mk
@@ -12,6 +12,7 @@ SRCS_libqpdf = \
libqpdf/FileInputSource.cc \
libqpdf/InputSource.cc \
libqpdf/MD5.cc \
+ libqpdf/OffsetInputSource.cc \
libqpdf/PCRE.cc \
libqpdf/Pipeline.cc \
libqpdf/Pl_AES_PDF.cc \
@@ -27,6 +28,7 @@ SRCS_libqpdf = \
libqpdf/Pl_PNGFilter.cc \
libqpdf/Pl_QPDFTokenizer.cc \
libqpdf/Pl_RC4.cc \
+ libqpdf/Pl_SHA2.cc \
libqpdf/Pl_StdioFile.cc \
libqpdf/QPDF.cc \
libqpdf/QPDFExc.cc \
@@ -53,18 +55,27 @@ SRCS_libqpdf = \
libqpdf/QUtil.cc \
libqpdf/RC4.cc \
libqpdf/qpdf-c.cc \
- libqpdf/rijndael.cc
+ libqpdf/rijndael.cc \
+ libqpdf/sha2.c \
+ libqpdf/sha2big.c
# -----
-OBJS_libqpdf = $(call src_to_lobj,$(SRCS_libqpdf))
+CCSRCS_libqpdf = $(filter %.cc,$(SRCS_libqpdf))
+CSRCS_libqpdf = $(filter %.c,$(SRCS_libqpdf))
+
+CCOBJS_libqpdf = $(call src_to_lobj,$(CCSRCS_libqpdf))
+COBJS_libqpdf = $(call c_src_to_lobj,$(CSRCS_libqpdf))
+OBJS_libqpdf = $(CCOBJS_libqpdf) $(COBJS_libqpdf)
ifeq ($(GENDEPS),1)
-include $(call lobj_to_dep,$(OBJS_libqpdf))
endif
-$(OBJS_libqpdf): libqpdf/$(OUTPUT_DIR)/%.$(LOBJ): libqpdf/%.cc
+$(CCOBJS_libqpdf): libqpdf/$(OUTPUT_DIR)/%.$(LOBJ): libqpdf/%.cc
$(call libcompile,$<,$(INCLUDES_libqpdf))
+$(COBJS_libqpdf): libqpdf/$(OUTPUT_DIR)/%.$(LOBJ): libqpdf/%.c
+ $(call c_libcompile,$<,$(INCLUDES_libqpdf))
# Last three arguments to makelib are CURRENT,REVISION,AGE.
#
@@ -80,4 +91,4 @@ $(OBJS_libqpdf): libqpdf/$(OUTPUT_DIR)/%.$(LOBJ): libqpdf/%.cc
# * Otherwise, increment REVISION
$(TARGETS_libqpdf): $(OBJS_libqpdf)
- $(call makelib,$(OBJS_libqpdf),$@,$(LDFLAGS),$(LIBS),9,0,1)
+ $(call makelib,$(OBJS_libqpdf),$@,$(LDFLAGS),$(LIBS),10,0,0)
diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc
index 0312ae5..a46df63 100644
--- a/libqpdf/qpdf-c.cc
+++ b/libqpdf/qpdf-c.cc
@@ -288,6 +288,12 @@ char const* qpdf_get_pdf_version(qpdf_data qpdf)
return qpdf->tmp_string.c_str();
}
+int qpdf_get_pdf_extension_level(qpdf_data qpdf)
+{
+ QTC::TC("qpdf", "qpdf-c called qpdf_get_pdf_extension_level");
+ return qpdf->qpdf->getExtensionLevel();
+}
+
char const* qpdf_get_user_password(qpdf_data qpdf)
{
QTC::TC("qpdf", "qpdf-c called qpdf_get_user_password");
@@ -566,6 +572,32 @@ void qpdf_set_r4_encryption_parameters(
encrypt_metadata, use_aes);
}
+void qpdf_set_r5_encryption_parameters(
+ qpdf_data qpdf, char const* user_password, char const* owner_password,
+ QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract,
+ qpdf_r3_print_e print, qpdf_r3_modify_e modify,
+ QPDF_BOOL encrypt_metadata)
+{
+ QTC::TC("qpdf", "qpdf-c called qpdf_set_r5_encryption_parameters");
+ qpdf->qpdf_writer->setR5EncryptionParameters(
+ user_password, owner_password,
+ allow_accessibility, allow_extract, print, modify,
+ encrypt_metadata);
+}
+
+void qpdf_set_r6_encryption_parameters(
+ qpdf_data qpdf, char const* user_password, char const* owner_password,
+ QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract,
+ qpdf_r3_print_e print, qpdf_r3_modify_e modify,
+ QPDF_BOOL encrypt_metadata)
+{
+ QTC::TC("qpdf", "qpdf-c called qpdf_set_r6_encryption_parameters");
+ qpdf->qpdf_writer->setR6EncryptionParameters(
+ user_password, owner_password,
+ allow_accessibility, allow_extract, print, modify,
+ encrypt_metadata);
+}
+
void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value)
{
QTC::TC("qpdf", "qpdf-c called qpdf_set_linearization");
@@ -574,14 +606,26 @@ void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value)
void qpdf_set_minimum_pdf_version(qpdf_data qpdf, char const* version)
{
+ qpdf_set_minimum_pdf_version_and_extension(qpdf, version, 0);
+}
+
+void qpdf_set_minimum_pdf_version_and_extension(
+ qpdf_data qpdf, char const* version, int extension_level)
+{
QTC::TC("qpdf", "qpdf-c called qpdf_set_minimum_pdf_version");
- qpdf->qpdf_writer->setMinimumPDFVersion(version);
+ qpdf->qpdf_writer->setMinimumPDFVersion(version, extension_level);
}
void qpdf_force_pdf_version(qpdf_data qpdf, char const* version)
{
+ qpdf_force_pdf_version_and_extension(qpdf, version, 0);
+}
+
+void qpdf_force_pdf_version_and_extension(
+ qpdf_data qpdf, char const* version, int extension_level)
+{
QTC::TC("qpdf", "qpdf-c called qpdf_force_pdf_version");
- qpdf->qpdf_writer->forcePDFVersion(version);
+ qpdf->qpdf_writer->forcePDFVersion(version, extension_level);
}
QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf)
diff --git a/libqpdf/qpdf/OffsetInputSource.hh b/libqpdf/qpdf/OffsetInputSource.hh
new file mode 100644
index 0000000..aedc574
--- /dev/null
+++ b/libqpdf/qpdf/OffsetInputSource.hh
@@ -0,0 +1,29 @@
+#ifndef __QPDF_OFFSETINPUTSOURCE_HH__
+#define __QPDF_OFFSETINPUTSOURCE_HH__
+
+// This class implements an InputSource that proxies for an underlying
+// input source but offset a specific number of bytes.
+
+#include <qpdf/InputSource.hh>
+#include <qpdf/PointerHolder.hh>
+
+class OffsetInputSource: public InputSource
+{
+ public:
+ OffsetInputSource(PointerHolder<InputSource>, qpdf_offset_t global_offset);
+ virtual ~OffsetInputSource();
+
+ virtual qpdf_offset_t findAndSkipNextEOL();
+ virtual std::string const& getName() const;
+ virtual qpdf_offset_t tell();
+ virtual void seek(qpdf_offset_t offset, int whence);
+ virtual void rewind();
+ virtual size_t read(char* buffer, size_t length);
+ virtual void unreadCh(char ch);
+
+ private:
+ PointerHolder<InputSource> proxied;
+ qpdf_offset_t global_offset;
+};
+
+#endif // __QPDF_OFFSETINPUTSOURCE_HH__
diff --git a/libqpdf/qpdf/Pl_AES_PDF.hh b/libqpdf/qpdf/Pl_AES_PDF.hh
index 3947506..9aa73ad 100644
--- a/libqpdf/qpdf/Pl_AES_PDF.hh
+++ b/libqpdf/qpdf/Pl_AES_PDF.hh
@@ -7,17 +7,16 @@
# include <stdint.h>
#endif
-// This pipeline implements AES-128 with CBC and block padding as
-// specified in the PDF specification.
+// This pipeline implements AES-128 and AES-256 with CBC and block
+// padding as specified in the PDF specification.
class Pl_AES_PDF: public Pipeline
{
public:
- // key_data should be a pointer to key_size bytes of data
- static unsigned int const key_size = 16;
QPDF_DLL
+ // key should be a pointer to key_bytes bytes of data
Pl_AES_PDF(char const* identifier, Pipeline* next,
- bool encrypt, unsigned char const key[key_size]);
+ bool encrypt, unsigned char const* key, unsigned int key_bytes);
QPDF_DLL
virtual ~Pl_AES_PDF();
@@ -26,6 +25,17 @@ class Pl_AES_PDF: public Pipeline
QPDF_DLL
virtual void finish();
+ // Use zero initialization vector; needed for AESV3
+ QPDF_DLL
+ void useZeroIV();
+ // Disable padding; needed for AESV3
+ QPDF_DLL
+ void disablePadding();
+ // Specify an initialization vector, which will not be included in
+ // the output.
+ QPDF_DLL
+ void setIV(unsigned char const* iv, size_t bytes);
+
// For testing only; PDF always uses CBC
QPDF_DLL
void disableCBC();
@@ -44,12 +54,16 @@ class Pl_AES_PDF: public Pipeline
bool cbc_mode;
bool first;
size_t offset; // offset into memory buffer
- unsigned char key[key_size];
- uint32_t rk[key_size + 28];
+ unsigned char* key;
+ uint32_t* rk;
unsigned char inbuf[buf_size];
unsigned char outbuf[buf_size];
unsigned char cbc_block[buf_size];
+ unsigned char specified_iv[buf_size];
unsigned int nrounds;
+ bool use_zero_iv;
+ bool use_specified_iv;
+ bool disable_padding;
};
#endif // __PL_AES_PDF_HH__
diff --git a/libqpdf/qpdf/Pl_SHA2.hh b/libqpdf/qpdf/Pl_SHA2.hh
new file mode 100644
index 0000000..8ff4723
--- /dev/null
+++ b/libqpdf/qpdf/Pl_SHA2.hh
@@ -0,0 +1,50 @@
+#ifndef __PL_SHA2_HH__
+#define __PL_SHA2_HH__
+
+// Bits must be a supported number of bits, currently only 256, 384,
+// or 512. Passing 0 as bits leaves the pipeline uncommitted, in
+// which case resetBits must be called before the pipeline is used.
+// If a next is provided, this pipeline sends its output to its
+// successor unmodified. After calling finish, the SHA2 checksum of
+// the data that passed through the pipeline is available.
+
+// This pipeline is reusable; i.e., it is safe to call write() after
+// calling finish(). The first call to write() after a call to
+// finish() initializes a new SHA2 object. resetBits may also be
+// called between finish and the next call to write.
+
+#include <qpdf/Pipeline.hh>
+#include <sph/sph_sha2.h>
+
+class Pl_SHA2: public Pipeline
+{
+ public:
+ QPDF_DLL
+ Pl_SHA2(int bits = 0, Pipeline* next = 0);
+ QPDF_DLL
+ virtual ~Pl_SHA2();
+ QPDF_DLL
+ virtual void write(unsigned char*, size_t);
+ QPDF_DLL
+ virtual void finish();
+ QPDF_DLL
+ void resetBits(int bits);
+ QPDF_DLL
+ std::string getHexDigest();
+ QPDF_DLL
+ std::string getRawDigest();
+
+ private:
+ void badBits();
+
+ bool in_progress;
+ int bits;
+ sph_sha256_context ctx256;
+ sph_sha384_context ctx384;
+ sph_sha512_context ctx512;
+ unsigned char sha256sum[32];
+ unsigned char sha384sum[48];
+ unsigned char sha512sum[64];
+};
+
+#endif // __PL_SHA2_HH__
diff --git a/libqpdf/qpdf/QPDF_Stream.hh b/libqpdf/qpdf/QPDF_Stream.hh
index 34eacee..6e5dacf 100644
--- a/libqpdf/qpdf/QPDF_Stream.hh
+++ b/libqpdf/qpdf/QPDF_Stream.hh
@@ -45,6 +45,9 @@ class QPDF_Stream: public QPDFObject
void replaceFilterData(QPDFObjectHandle const& filter,
QPDFObjectHandle const& decode_parms,
size_t length);
+ bool understandDecodeParams(
+ std::string const& filter, QPDFObjectHandle decode_params,
+ int& predictor, int& columns, bool& early_code_change);
bool filterable(std::vector<std::string>& filters,
int& predictor, int& columns, bool& early_code_change);
diff --git a/libqpdf/sha2.c b/libqpdf/sha2.c
new file mode 100644
index 0000000..45fdd7e
--- /dev/null
+++ b/libqpdf/sha2.c
@@ -0,0 +1,690 @@
+/* $Id: sha2.c 227 2010-06-16 17:28:38Z tp $ */
+/*
+ * SHA-224 / SHA-256 implementation.
+ *
+ * ==========================(LICENSE BEGIN)============================
+ *
+ * Copyright (c) 2007-2010 Projet RNRT SAPHIR
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ===========================(LICENSE END)=============================
+ *
+ * @author Thomas Pornin <thomas.pornin@cryptolog.com>
+ */
+
+#include <stddef.h>
+#include <string.h>
+
+#include "sph/sph_sha2.h"
+
+#if SPH_SMALL_FOOTPRINT && !defined SPH_SMALL_FOOTPRINT_SHA2
+#define SPH_SMALL_FOOTPRINT_SHA2 1
+#endif
+
+#define CH(X, Y, Z) ((((Y) ^ (Z)) & (X)) ^ (Z))
+#define MAJ(X, Y, Z) (((Y) & (Z)) | (((Y) | (Z)) & (X)))
+
+#define ROTR SPH_ROTR32
+
+#define BSG2_0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
+#define BSG2_1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
+#define SSG2_0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SPH_T32((x) >> 3))
+#define SSG2_1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SPH_T32((x) >> 10))
+
+static const sph_u32 H224[8] = {
+ SPH_C32(0xC1059ED8), SPH_C32(0x367CD507), SPH_C32(0x3070DD17),
+ SPH_C32(0xF70E5939), SPH_C32(0xFFC00B31), SPH_C32(0x68581511),
+ SPH_C32(0x64F98FA7), SPH_C32(0xBEFA4FA4)
+};
+
+static const sph_u32 H256[8] = {
+ SPH_C32(0x6A09E667), SPH_C32(0xBB67AE85), SPH_C32(0x3C6EF372),
+ SPH_C32(0xA54FF53A), SPH_C32(0x510E527F), SPH_C32(0x9B05688C),
+ SPH_C32(0x1F83D9AB), SPH_C32(0x5BE0CD19)
+};
+
+/*
+ * The SHA2_ROUND_BODY defines the body for a SHA-224 / SHA-256
+ * compression function implementation. The "in" parameter should
+ * evaluate, when applied to a numerical input parameter from 0 to 15,
+ * to an expression which yields the corresponding input block. The "r"
+ * parameter should evaluate to an array or pointer expression
+ * designating the array of 8 words which contains the input and output
+ * of the compression function.
+ */
+
+#if SPH_SMALL_FOOTPRINT_SHA2
+
+static const sph_u32 K[64] = {
+ SPH_C32(0x428A2F98), SPH_C32(0x71374491),
+ SPH_C32(0xB5C0FBCF), SPH_C32(0xE9B5DBA5),
+ SPH_C32(0x3956C25B), SPH_C32(0x59F111F1),
+ SPH_C32(0x923F82A4), SPH_C32(0xAB1C5ED5),
+ SPH_C32(0xD807AA98), SPH_C32(0x12835B01),
+ SPH_C32(0x243185BE), SPH_C32(0x550C7DC3),
+ SPH_C32(0x72BE5D74), SPH_C32(0x80DEB1FE),
+ SPH_C32(0x9BDC06A7), SPH_C32(0xC19BF174),
+ SPH_C32(0xE49B69C1), SPH_C32(0xEFBE4786),
+ SPH_C32(0x0FC19DC6), SPH_C32(0x240CA1CC),
+ SPH_C32(0x2DE92C6F), SPH_C32(0x4A7484AA),
+ SPH_C32(0x5CB0A9DC), SPH_C32(0x76F988DA),
+ SPH_C32(0x983E5152), SPH_C32(0xA831C66D),
+ SPH_C32(0xB00327C8), SPH_C32(0xBF597FC7),
+ SPH_C32(0xC6E00BF3), SPH_C32(0xD5A79147),
+ SPH_C32(0x06CA6351), SPH_C32(0x14292967),
+ SPH_C32(0x27B70A85), SPH_C32(0x2E1B2138),
+ SPH_C32(0x4D2C6DFC), SPH_C32(0x53380D13),
+ SPH_C32(0x650A7354), SPH_C32(0x766A0ABB),
+ SPH_C32(0x81C2C92E), SPH_C32(0x92722C85),
+ SPH_C32(0xA2BFE8A1), SPH_C32(0xA81A664B),
+ SPH_C32(0xC24B8B70), SPH_C32(0xC76C51A3),
+ SPH_C32(0xD192E819), SPH_C32(0xD6990624),
+ SPH_C32(0xF40E3585), SPH_C32(0x106AA070),
+ SPH_C32(0x19A4C116), SPH_C32(0x1E376C08),
+ SPH_C32(0x2748774C), SPH_C32(0x34B0BCB5),
+ SPH_C32(0x391C0CB3), SPH_C32(0x4ED8AA4A),
+ SPH_C32(0x5B9CCA4F), SPH_C32(0x682E6FF3),
+ SPH_C32(0x748F82EE), SPH_C32(0x78A5636F),
+ SPH_C32(0x84C87814), SPH_C32(0x8CC70208),
+ SPH_C32(0x90BEFFFA), SPH_C32(0xA4506CEB),
+ SPH_C32(0xBEF9A3F7), SPH_C32(0xC67178F2)
+};
+
+#define SHA2_MEXP1(in, pc) do { \
+ W[pc] = in(pc); \
+ } while (0)
+
+#define SHA2_MEXP2(in, pc) do { \
+ W[(pc) & 0x0F] = SPH_T32(SSG2_1(W[((pc) - 2) & 0x0F]) \
+ + W[((pc) - 7) & 0x0F] \
+ + SSG2_0(W[((pc) - 15) & 0x0F]) + W[(pc) & 0x0F]); \
+ } while (0)
+
+#define SHA2_STEPn(n, a, b, c, d, e, f, g, h, in, pc) do { \
+ sph_u32 t1, t2; \
+ SHA2_MEXP ## n(in, pc); \
+ t1 = SPH_T32(h + BSG2_1(e) + CH(e, f, g) \
+ + K[pcount + (pc)] + W[(pc) & 0x0F]); \
+ t2 = SPH_T32(BSG2_0(a) + MAJ(a, b, c)); \
+ d = SPH_T32(d + t1); \
+ h = SPH_T32(t1 + t2); \
+ } while (0)
+
+#define SHA2_STEP1(a, b, c, d, e, f, g, h, in, pc) \
+ SHA2_STEPn(1, a, b, c, d, e, f, g, h, in, pc)
+#define SHA2_STEP2(a, b, c, d, e, f, g, h, in, pc) \
+ SHA2_STEPn(2, a, b, c, d, e, f, g, h, in, pc)
+
+#define SHA2_ROUND_BODY(in, r) do { \
+ sph_u32 A, B, C, D, E, F, G, H; \
+ sph_u32 W[16]; \
+ unsigned pcount; \
+ \
+ A = (r)[0]; \
+ B = (r)[1]; \
+ C = (r)[2]; \
+ D = (r)[3]; \
+ E = (r)[4]; \
+ F = (r)[5]; \
+ G = (r)[6]; \
+ H = (r)[7]; \
+ pcount = 0; \
+ SHA2_STEP1(A, B, C, D, E, F, G, H, in, 0); \
+ SHA2_STEP1(H, A, B, C, D, E, F, G, in, 1); \
+ SHA2_STEP1(G, H, A, B, C, D, E, F, in, 2); \
+ SHA2_STEP1(F, G, H, A, B, C, D, E, in, 3); \
+ SHA2_STEP1(E, F, G, H, A, B, C, D, in, 4); \
+ SHA2_STEP1(D, E, F, G, H, A, B, C, in, 5); \
+ SHA2_STEP1(C, D, E, F, G, H, A, B, in, 6); \
+ SHA2_STEP1(B, C, D, E, F, G, H, A, in, 7); \
+ SHA2_STEP1(A, B, C, D, E, F, G, H, in, 8); \
+ SHA2_STEP1(H, A, B, C, D, E, F, G, in, 9); \
+ SHA2_STEP1(G, H, A, B, C, D, E, F, in, 10); \
+ SHA2_STEP1(F, G, H, A, B, C, D, E, in, 11); \
+ SHA2_STEP1(E, F, G, H, A, B, C, D, in, 12); \
+ SHA2_STEP1(D, E, F, G, H, A, B, C, in, 13); \
+ SHA2_STEP1(C, D, E, F, G, H, A, B, in, 14); \
+ SHA2_STEP1(B, C, D, E, F, G, H, A, in, 15); \
+ for (pcount = 16; pcount < 64; pcount += 16) { \
+ SHA2_STEP2(A, B, C, D, E, F, G, H, in, 0); \
+ SHA2_STEP2(H, A, B, C, D, E, F, G, in, 1); \
+ SHA2_STEP2(G, H, A, B, C, D, E, F, in, 2); \
+ SHA2_STEP2(F, G, H, A, B, C, D, E, in, 3); \
+ SHA2_STEP2(E, F, G, H, A, B, C, D, in, 4); \
+ SHA2_STEP2(D, E, F, G, H, A, B, C, in, 5); \
+ SHA2_STEP2(C, D, E, F, G, H, A, B, in, 6); \
+ SHA2_STEP2(B, C, D, E, F, G, H, A, in, 7); \
+ SHA2_STEP2(A, B, C, D, E, F, G, H, in, 8); \
+ SHA2_STEP2(H, A, B, C, D, E, F, G, in, 9); \
+ SHA2_STEP2(G, H, A, B, C, D, E, F, in, 10); \
+ SHA2_STEP2(F, G, H, A, B, C, D, E, in, 11); \
+ SHA2_STEP2(E, F, G, H, A, B, C, D, in, 12); \
+ SHA2_STEP2(D, E, F, G, H, A, B, C, in, 13); \
+ SHA2_STEP2(C, D, E, F, G, H, A, B, in, 14); \
+ SHA2_STEP2(B, C, D, E, F, G, H, A, in, 15); \
+ } \
+ (r)[0] = SPH_T32((r)[0] + A); \
+ (r)[1] = SPH_T32((r)[1] + B); \
+ (r)[2] = SPH_T32((r)[2] + C); \
+ (r)[3] = SPH_T32((r)[3] + D); \
+ (r)[4] = SPH_T32((r)[4] + E); \
+ (r)[5] = SPH_T32((r)[5] + F); \
+ (r)[6] = SPH_T32((r)[6] + G); \
+ (r)[7] = SPH_T32((r)[7] + H); \
+ } while (0)
+
+#else
+
+#define SHA2_ROUND_BODY(in, r) do { \
+ sph_u32 A, B, C, D, E, F, G, H, T1, T2; \
+ sph_u32 W00, W01, W02, W03, W04, W05, W06, W07; \
+ sph_u32 W08, W09, W10, W11, W12, W13, W14, W15; \
+ \
+ A = (r)[0]; \
+ B = (r)[1]; \
+ C = (r)[2]; \
+ D = (r)[3]; \
+ E = (r)[4]; \
+ F = (r)[5]; \
+ G = (r)[6]; \
+ H = (r)[7]; \
+ W00 = in(0); \
+ T1 = SPH_T32(H + BSG2_1(E) + CH(E, F, G) \
+ + SPH_C32(0x428A2F98) + W00); \
+ T2 = SPH_T32(BSG2_0(A) + MAJ(A, B, C)); \
+ D = SPH_T32(D + T1); \
+ H = SPH_T32(T1 + T2); \
+ W01 = in(1); \
+ T1 = SPH_T32(G + BSG2_1(D) + CH(D, E, F) \
+ + SPH_C32(0x71374491) + W01); \
+ T2 = SPH_T32(BSG2_0(H) + MAJ(H, A, B)); \
+ C = SPH_T32(C + T1); \
+ G = SPH_T32(T1 + T2); \
+ W02 = in(2); \
+ T1 = SPH_T32(F + BSG2_1(C) + CH(C, D, E) \
+ + SPH_C32(0xB5C0FBCF) + W02); \
+ T2 = SPH_T32(BSG2_0(G) + MAJ(G, H, A)); \
+ B = SPH_T32(B + T1); \
+ F = SPH_T32(T1 + T2); \
+ W03 = in(3); \
+ T1 = SPH_T32(E + BSG2_1(B) + CH(B, C, D) \
+ + SPH_C32(0xE9B5DBA5) + W03); \
+ T2 = SPH_T32(BSG2_0(F) + MAJ(F, G, H)); \
+ A = SPH_T32(A + T1); \
+ E = SPH_T32(T1 + T2); \
+ W04 = in(4); \
+ T1 = SPH_T32(D + BSG2_1(A) + CH(A, B, C) \
+ + SPH_C32(0x3956C25B) + W04); \
+ T2 = SPH_T32(BSG2_0(E) + MAJ(E, F, G)); \
+ H = SPH_T32(H + T1); \
+ D = SPH_T32(T1 + T2); \
+ W05 = in(5); \
+ T1 = SPH_T32(C + BSG2_1(H) + CH(H, A, B) \
+ + SPH_C32(0x59F111F1) + W05); \
+ T2 = SPH_T32(BSG2_0(D) + MAJ(D, E, F)); \
+ G = SPH_T32(G + T1); \
+ C = SPH_T32(T1 + T2); \
+ W06 = in(6); \
+ T1 = SPH_T32(B + BSG2_1(G) + CH(G, H, A) \
+ + SPH_C32(0x923F82A4) + W06); \
+ T2 = SPH_T32(BSG2_0(C) + MAJ(C, D, E)); \
+ F = SPH_T32(F + T1); \
+ B = SPH_T32(T1 + T2); \
+ W07 = in(7); \
+ T1 = SPH_T32(A + BSG2_1(F) + CH(F, G, H) \
+ + SPH_C32(0xAB1C5ED5) + W07); \
+ T2 = SPH_T32(BSG2_0(B) + MAJ(B, C, D)); \
+ E = SPH_T32(E + T1); \
+ A = SPH_T32(T1 + T2); \
+ W08 = in(8); \
+ T1 = SPH_T32(H + BSG2_1(E) + CH(E, F, G) \
+ + SPH_C32(0xD807AA98) + W08); \
+ T2 = SPH_T32(BSG2_0(A) + MAJ(A, B, C)); \
+ D = SPH_T32(D + T1); \
+ H = SPH_T32(T1 + T2); \
+ W09 = in(9); \
+ T1 = SPH_T32(G + BSG2_1(D) + CH(D, E, F) \
+ + SPH_C32(0x12835B01) + W09); \
+ T2 = SPH_T32(BSG2_0(H) + MAJ(H, A, B)); \
+ C = SPH_T32(C + T1); \
+ G = SPH_T32(T1 + T2); \
+ W10 = in(10); \
+ T1 = SPH_T32(F + BSG2_1(C) + CH(C, D, E) \
+ + SPH_C32(0x243185BE) + W10); \
+ T2 = SPH_T32(BSG2_0(G) + MAJ(G, H, A)); \
+ B = SPH_T32(B + T1); \
+ F = SPH_T32(T1 + T2); \
+ W11 = in(11); \
+ T1 = SPH_T32(E + BSG2_1(B) + CH(B, C, D) \
+ + SPH_C32(0x550C7DC3) + W11); \
+ T2 = SPH_T32(BSG2_0(F) + MAJ(F, G, H)); \
+ A = SPH_T32(A + T1); \
+ E = SPH_T32(T1 + T2); \
+ W12 = in(12); \
+ T1 = SPH_T32(D + BSG2_1(A) + CH(A, B, C) \
+ + SPH_C32(0x72BE5D74) + W12); \
+ T2 = SPH_T32(BSG2_0(E) + MAJ(E, F, G)); \
+ H = SPH_T32(H + T1); \
+ D = SPH_T32(T1 + T2); \
+ W13 = in(13); \
+ T1 = SPH_T32(C + BSG2_1(H) + CH(H, A, B) \
+ + SPH_C32(0x80DEB1FE) + W13); \
+ T2 = SPH_T32(BSG2_0(D) + MAJ(D, E, F)); \
+ G = SPH_T32(G + T1); \
+ C = SPH_T32(T1 + T2); \
+ W14 = in(14); \
+ T1 = SPH_T32(B + BSG2_1(G) + CH(G, H, A) \
+ + SPH_C32(0x9BDC06A7) + W14); \
+ T2 = SPH_T32(BSG2_0(C) + MAJ(C, D, E)); \
+ F = SPH_T32(F + T1); \
+ B = SPH_T32(T1 + T2); \
+ W15 = in(15); \
+ T1 = SPH_T32(A + BSG2_1(F) + CH(F, G, H) \
+ + SPH_C32(0xC19BF174) + W15); \
+ T2 = SPH_T32(BSG2_0(B) + MAJ(B, C, D)); \
+ E = SPH_T32(E + T1); \
+ A = SPH_T32(T1 + T2); \
+ W00 = SPH_T32(SSG2_1(W14) + W09 + SSG2_0(W01) + W00); \
+ T1 = SPH_T32(H + BSG2_1(E) + CH(E, F, G) \
+ + SPH_C32(0xE49B69C1) + W00); \
+ T2 = SPH_T32(BSG2_0(A) + MAJ(A, B, C)); \
+ D = SPH_T32(D + T1); \
+ H = SPH_T32(T1 + T2); \
+ W01 = SPH_T32(SSG2_1(W15) + W10 + SSG2_0(W02) + W01); \
+ T1 = SPH_T32(G + BSG2_1(D) + CH(D, E, F) \
+ + SPH_C32(0xEFBE4786) + W01); \
+ T2 = SPH_T32(BSG2_0(H) + MAJ(H, A, B)); \
+ C = SPH_T32(C + T1); \
+ G = SPH_T32(T1 + T2); \
+ W02 = SPH_T32(SSG2_1(W00) + W11 + SSG2_0(W03) + W02); \
+ T1 = SPH_T32(F + BSG2_1(C) + CH(C, D, E) \
+ + SPH_C32(0x0FC19DC6) + W02); \
+ T2 = SPH_T32(BSG2_0(G) + MAJ(G, H, A)); \
+ B = SPH_T32(B + T1); \
+ F = SPH_T32(T1 + T2); \
+ W03 = SPH_T32(SSG2_1(W01) + W12 + SSG2_0(W04) + W03); \
+ T1 = SPH_T32(E + BSG2_1(B) + CH(B, C, D) \
+ + SPH_C32(0x240CA1CC) + W03); \
+ T2 = SPH_T32(BSG2_0(F) + MAJ(F, G, H)); \
+ A = SPH_T32(A + T1); \
+ E = SPH_T32(T1 + T2); \
+ W04 = SPH_T32(SSG2_1(W02) + W13 + SSG2_0(W05) + W04); \
+ T1 = SPH_T32(D + BSG2_1(A) + CH(A, B, C) \
+ + SPH_C32(0x2DE92C6F) + W04); \
+ T2 = SPH_T32(BSG2_0(E) + MAJ(E, F, G)); \
+ H = SPH_T32(H + T1); \
+ D = SPH_T32(T1 + T2); \
+ W05 = SPH_T32(SSG2_1(W03) + W14 + SSG2_0(W06) + W05); \
+ T1 = SPH_T32(C + BSG2_1(H) + CH(H, A, B) \
+ + SPH_C32(0x4A7484AA) + W05); \
+ T2 = SPH_T32(BSG2_0(D) + MAJ(D, E, F)); \
+ G = SPH_T32(G + T1); \
+ C = SPH_T32(T1 + T2); \
+ W06 = SPH_T32(SSG2_1(W04) + W15 + SSG2_0(W07) + W06); \
+ T1 = SPH_T32(B + BSG2_1(G) + CH(G, H, A) \
+ + SPH_C32(0x5CB0A9DC) + W06); \
+ T2 = SPH_T32(BSG2_0(C) + MAJ(C, D, E)); \
+ F = SPH_T32(F + T1); \
+ B = SPH_T32(T1 + T2); \
+ W07 = SPH_T32(SSG2_1(W05) + W00 + SSG2_0(W08) + W07); \
+ T1 = SPH_T32(A + BSG2_1(F) + CH(F, G, H) \
+ + SPH_C32(0x76F988DA) + W07); \
+ T2 = SPH_T32(BSG2_0(B) + MAJ(B, C, D)); \
+ E = SPH_T32(E + T1); \
+ A = SPH_T32(T1 + T2); \
+ W08 = SPH_T32(SSG2_1(W06) + W01 + SSG2_0(W09) + W08); \
+ T1 = SPH_T32(H + BSG2_1(E) + CH(E, F, G) \
+ + SPH_C32(0x983E5152) + W08); \
+ T2 = SPH_T32(BSG2_0(A) + MAJ(A, B, C)); \
+ D = SPH_T32(D + T1); \
+ H = SPH_T32(T1 + T2); \
+ W09 = SPH_T32(SSG2_1(W07) + W02 + SSG2_0(W10) + W09); \
+ T1 = SPH_T32(G + BSG2_1(D) + CH(D, E, F) \
+ + SPH_C32(0xA831C66D) + W09); \
+ T2 = SPH_T32(BSG2_0(H) + MAJ(H, A, B)); \
+ C = SPH_T32(C + T1); \
+ G = SPH_T32(T1 + T2); \
+ W10 = SPH_T32(SSG2_1(W08) + W03 + SSG2_0(W11) + W10); \
+ T1 = SPH_T32(F + BSG2_1(C) + CH(C, D, E) \
+ + SPH_C32(0xB00327C8) + W10); \
+ T2 = SPH_T32(BSG2_0(G) + MAJ(G, H, A)); \
+ B = SPH_T32(B + T1); \
+ F = SPH_T32(T1 + T2); \
+ W11 = SPH_T32(SSG2_1(W09) + W04 + SSG2_0(W12) + W11); \
+ T1 = SPH_T32(E + BSG2_1(B) + CH(B, C, D) \
+ + SPH_C32(0xBF597FC7) + W11); \
+ T2 = SPH_T32(BSG2_0(F) + MAJ(F, G, H)); \
+ A = SPH_T32(A + T1); \
+ E = SPH_T32(T1 + T2); \
+ W12 = SPH_T32(SSG2_1(W10) + W05 + SSG2_0(W13) + W12); \
+ T1 = SPH_T32(D + BSG2_1(A) + CH(A, B, C) \
+ + SPH_C32(0xC6E00BF3) + W12); \
+ T2 = SPH_T32(BSG2_0(E) + MAJ(E, F, G)); \
+ H = SPH_T32(H + T1); \
+ D = SPH_T32(T1 + T2); \
+ W13 = SPH_T32(SSG2_1(W11) + W06 + SSG2_0(W14) + W13); \
+ T1 = SPH_T32(C + BSG2_1(H) + CH(H, A, B) \
+ + SPH_C32(0xD5A79147) + W13); \
+ T2 = SPH_T32(BSG2_0(D) + MAJ(D, E, F)); \
+ G = SPH_T32(G + T1); \
+ C = SPH_T32(T1 + T2); \
+ W14 = SPH_T32(SSG2_1(W12) + W07 + SSG2_0(W15) + W14); \
+ T1 = SPH_T32(B + BSG2_1(G) + CH(G, H, A) \
+ + SPH_C32(0x06CA6351) + W14); \
+ T2 = SPH_T32(BSG2_0(C) + MAJ(C, D, E)); \
+ F = SPH_T32(F + T1); \
+ B = SPH_T32(T1 + T2); \
+ W15 = SPH_T32(SSG2_1(W13) + W08 + SSG2_0(W00) + W15); \
+ T1 = SPH_T32(A + BSG2_1(F) + CH(F, G, H) \
+ + SPH_C32(0x14292967) + W15); \
+ T2 = SPH_T32(BSG2_0(B) + MAJ(B, C, D)); \
+ E = SPH_T32(E + T1); \
+ A = SPH_T32(T1 + T2); \
+ W00 = SPH_T32(SSG2_1(W14) + W09 + SSG2_0(W01) + W00); \
+ T1 = SPH_T32(H + BSG2_1(E) + CH(E, F, G) \
+ + SPH_C32(0x27B70A85) + W00); \
+ T2 = SPH_T32(BSG2_0(A) + MAJ(A, B, C)); \
+ D = SPH_T32(D + T1); \
+ H = SPH_T32(T1 + T2); \
+ W01 = SPH_T32(SSG2_1(W15) + W10 + SSG2_0(W02) + W01); \
+ T1 = SPH_T32(G + BSG2_1(D) + CH(D, E, F) \
+ + SPH_C32(0x2E1B2138) + W01); \
+ T2 = SPH_T32(BSG2_0(H) + MAJ(H, A, B)); \
+ C = SPH_T32(C + T1); \
+ G = SPH_T32(T1 + T2); \
+ W02 = SPH_T32(SSG2_1(W00) + W11 + SSG2_0(W03) + W02); \
+ T1 = SPH_T32(F + BSG2_1(C) + CH(C, D, E) \
+ + SPH_C32(0x4D2C6DFC) + W02); \
+ T2 = SPH_T32(BSG2_0(G) + MAJ(G, H, A)); \
+ B = SPH_T32(B + T1); \
+ F = SPH_T32(T1 + T2); \
+ W03 = SPH_T32(SSG2_1(W01) + W12 + SSG2_0(W04) + W03); \
+ T1 = SPH_T32(E + BSG2_1(B) + CH(B, C, D) \
+ + SPH_C32(0x53380D13) + W03); \
+ T2 = SPH_T32(BSG2_0(F) + MAJ(F, G, H)); \
+ A = SPH_T32(A + T1); \
+ E = SPH_T32(T1 + T2); \
+ W04 = SPH_T32(SSG2_1(W02) + W13 + SSG2_0(W05) + W04); \
+ T1 = SPH_T32(D + BSG2_1(A) + CH(A, B, C) \
+ + SPH_C32(0x650A7354) + W04); \
+ T2 = SPH_T32(BSG2_0(E) + MAJ(E, F, G)); \
+ H = SPH_T32(H + T1); \
+ D = SPH_T32(T1 + T2); \
+ W05 = SPH_T32(SSG2_1(W03) + W14 + SSG2_0(W06) + W05); \
+ T1 = SPH_T32(C + BSG2_1(H) + CH(H, A, B) \
+ + SPH_C32(0x766A0ABB) + W05); \
+ T2 = SPH_T32(BSG2_0(D) + MAJ(D, E, F)); \
+ G = SPH_T32(G + T1); \
+ C = SPH_T32(T1 + T2); \
+ W06 = SPH_T32(SSG2_1(W04) + W15 + SSG2_0(W07) + W06); \
+ T1 = SPH_T32(B + BSG2_1(G) + CH(G, H, A) \
+ + SPH_C32(0x81C2C92E) + W06); \
+ T2 = SPH_T32(BSG2_0(C) + MAJ(C, D, E)); \
+ F = SPH_T32(F + T1); \
+ B = SPH_T32(T1 + T2); \
+ W07 = SPH_T32(SSG2_1(W05) + W00 + SSG2_0(W08) + W07); \
+ T1 = SPH_T32(A + BSG2_1(F) + CH(F, G, H) \
+ + SPH_C32(0x92722C85) + W07); \
+ T2 = SPH_T32(BSG2_0(B) + MAJ(B, C, D)); \
+ E = SPH_T32(E + T1); \
+ A = SPH_T32(T1 + T2); \
+ W08 = SPH_T32(SSG2_1(W06) + W01 + SSG2_0(W09) + W08); \
+ T1 = SPH_T32(H + BSG2_1(E) + CH(E, F, G) \
+ + SPH_C32(0xA2BFE8A1) + W08); \
+ T2 = SPH_T32(BSG2_0(A) + MAJ(A, B, C)); \
+ D = SPH_T32(D + T1); \
+ H = SPH_T32(T1 + T2); \
+ W09 = SPH_T32(SSG2_1(W07) + W02 + SSG2_0(W10) + W09); \
+ T1 = SPH_T32(G + BSG2_1(D) + CH(D, E, F) \
+ + SPH_C32(0xA81A664B) + W09); \
+ T2 = SPH_T32(BSG2_0(H) + MAJ(H, A, B)); \
+ C = SPH_T32(C + T1); \
+ G = SPH_T32(T1 + T2); \
+ W10 = SPH_T32(SSG2_1(W08) + W03 + SSG2_0(W11) + W10); \
+ T1 = SPH_T32(F + BSG2_1(C) + CH(C, D, E) \
+ + SPH_C32(0xC24B8B70) + W10); \
+ T2 = SPH_T32(BSG2_0(G) + MAJ(G, H, A)); \
+ B = SPH_T32(B + T1); \
+ F = SPH_T32(T1 + T2); \
+ W11 = SPH_T32(SSG2_1(W09) + W04 + SSG2_0(W12) + W11); \
+ T1 = SPH_T32(E + BSG2_1(B) + CH(B, C, D) \
+ + SPH_C32(0xC76C51A3) + W11); \
+ T2 = SPH_T32(BSG2_0(F) + MAJ(F, G, H)); \
+ A = SPH_T32(A + T1); \
+ E = SPH_T32(T1 + T2); \
+ W12 = SPH_T32(SSG2_1(W10) + W05 + SSG2_0(W13) + W12); \
+ T1 = SPH_T32(D + BSG2_1(A) + CH(A, B, C) \
+ + SPH_C32(0xD192E819) + W12); \
+ T2 = SPH_T32(BSG2_0(E) + MAJ(E, F, G)); \
+ H = SPH_T32(H + T1); \
+ D = SPH_T32(T1 + T2); \
+ W13 = SPH_T32(SSG2_1(W11) + W06 + SSG2_0(W14) + W13); \
+ T1 = SPH_T32(C + BSG2_1(H) + CH(H, A, B) \
+ + SPH_C32(0xD6990624) + W13); \
+ T2 = SPH_T32(BSG2_0(D) + MAJ(D, E, F)); \
+ G = SPH_T32(G + T1); \
+ C = SPH_T32(T1 + T2); \
+ W14 = SPH_T32(SSG2_1(W12) + W07 + SSG2_0(W15) + W14); \
+ T1 = SPH_T32(B + BSG2_1(G) + CH(G, H, A) \
+ + SPH_C32(0xF40E3585) + W14); \
+ T2 = SPH_T32(BSG2_0(C) + MAJ(C, D, E)); \
+ F = SPH_T32(F + T1); \
+ B = SPH_T32(T1 + T2); \
+ W15 = SPH_T32(SSG2_1(W13) + W08 + SSG2_0(W00) + W15); \
+ T1 = SPH_T32(A + BSG2_1(F) + CH(F, G, H) \
+ + SPH_C32(0x106AA070) + W15); \
+ T2 = SPH_T32(BSG2_0(B) + MAJ(B, C, D)); \
+ E = SPH_T32(E + T1); \
+ A = SPH_T32(T1 + T2); \
+ W00 = SPH_T32(SSG2_1(W14) + W09 + SSG2_0(W01) + W00); \
+ T1 = SPH_T32(H + BSG2_1(E) + CH(E, F, G) \
+ + SPH_C32(0x19A4C116) + W00); \
+ T2 = SPH_T32(BSG2_0(A) + MAJ(A, B, C)); \
+ D = SPH_T32(D + T1); \
+ H = SPH_T32(T1 + T2); \
+ W01 = SPH_T32(SSG2_1(W15) + W10 + SSG2_0(W02) + W01); \
+ T1 = SPH_T32(G + BSG2_1(D) + CH(D, E, F) \
+ + SPH_C32(0x1E376C08) + W01); \
+ T2 = SPH_T32(BSG2_0(H) + MAJ(H, A, B)); \
+ C = SPH_T32(C + T1); \
+ G = SPH_T32(T1 + T2); \
+ W02 = SPH_T32(SSG2_1(W00) + W11 + SSG2_0(W03) + W02); \
+ T1 = SPH_T32(F + BSG2_1(C) + CH(C, D, E) \
+ + SPH_C32(0x2748774C) + W02); \
+ T2 = SPH_T32(BSG2_0(G) + MAJ(G, H, A)); \
+ B = SPH_T32(B + T1); \
+ F = SPH_T32(T1 + T2); \
+ W03 = SPH_T32(SSG2_1(W01) + W12 + SSG2_0(W04) + W03); \
+ T1 = SPH_T32(E + BSG2_1(B) + CH(B, C, D) \
+ + SPH_C32(0x34B0BCB5) + W03); \
+ T2 = SPH_T32(BSG2_0(F) + MAJ(F, G, H)); \
+ A = SPH_T32(A + T1); \
+ E = SPH_T32(T1 + T2); \
+ W04 = SPH_T32(SSG2_1(W02) + W13 + SSG2_0(W05) + W04); \
+ T1 = SPH_T32(D + BSG2_1(A) + CH(A, B, C) \
+ + SPH_C32(0x391C0CB3) + W04); \
+ T2 = SPH_T32(BSG2_0(E) + MAJ(E, F, G)); \
+ H = SPH_T32(H + T1); \
+ D = SPH_T32(T1 + T2); \
+ W05 = SPH_T32(SSG2_1(W03) + W14 + SSG2_0(W06) + W05); \
+ T1 = SPH_T32(C + BSG2_1(H) + CH(H, A, B) \
+ + SPH_C32(0x4ED8AA4A) + W05); \
+ T2 = SPH_T32(BSG2_0(D) + MAJ(D, E, F)); \
+ G = SPH_T32(G + T1); \
+ C = SPH_T32(T1 + T2); \
+ W06 = SPH_T32(SSG2_1(W04) + W15 + SSG2_0(W07) + W06); \
+ T1 = SPH_T32(B + BSG2_1(G) + CH(G, H, A) \
+ + SPH_C32(0x5B9CCA4F) + W06); \
+ T2 = SPH_T32(BSG2_0(C) + MAJ(C, D, E)); \
+ F = SPH_T32(F + T1); \
+ B = SPH_T32(T1 + T2); \
+ W07 = SPH_T32(SSG2_1(W05) + W00 + SSG2_0(W08) + W07); \
+ T1 = SPH_T32(A + BSG2_1(F) + CH(F, G, H) \
+ + SPH_C32(0x682E6FF3) + W07); \
+ T2 = SPH_T32(BSG2_0(B) + MAJ(B, C, D)); \
+ E = SPH_T32(E + T1); \
+ A = SPH_T32(T1 + T2); \
+ W08 = SPH_T32(SSG2_1(W06) + W01 + SSG2_0(W09) + W08); \
+ T1 = SPH_T32(H + BSG2_1(E) + CH(E, F, G) \
+ + SPH_C32(0x748F82EE) + W08); \
+ T2 = SPH_T32(BSG2_0(A) + MAJ(A, B, C)); \
+ D = SPH_T32(D + T1); \
+ H = SPH_T32(T1 + T2); \
+ W09 = SPH_T32(SSG2_1(W07) + W02 + SSG2_0(W10) + W09); \
+ T1 = SPH_T32(G + BSG2_1(D) + CH(D, E, F) \
+ + SPH_C32(0x78A5636F) + W09); \
+ T2 = SPH_T32(BSG2_0(H) + MAJ(H, A, B)); \
+ C = SPH_T32(C + T1); \
+ G = SPH_T32(T1 + T2); \
+ W10 = SPH_T32(SSG2_1(W08) + W03 + SSG2_0(W11) + W10); \
+ T1 = SPH_T32(F + BSG2_1(C) + CH(C, D, E) \
+ + SPH_C32(0x84C87814) + W10); \
+ T2 = SPH_T32(BSG2_0(G) + MAJ(G, H, A)); \
+ B = SPH_T32(B + T1); \
+ F = SPH_T32(T1 + T2); \
+ W11 = SPH_T32(SSG2_1(W09) + W04 + SSG2_0(W12) + W11); \
+ T1 = SPH_T32(E + BSG2_1(B) + CH(B, C, D) \
+ + SPH_C32(0x8CC70208) + W11); \
+ T2 = SPH_T32(BSG2_0(F) + MAJ(F, G, H)); \
+ A = SPH_T32(A + T1); \
+ E = SPH_T32(T1 + T2); \
+ W12 = SPH_T32(SSG2_1(W10) + W05 + SSG2_0(W13) + W12); \
+ T1 = SPH_T32(D + BSG2_1(A) + CH(A, B, C) \
+ + SPH_C32(0x90BEFFFA) + W12); \
+ T2 = SPH_T32(BSG2_0(E) + MAJ(E, F, G)); \
+ H = SPH_T32(H + T1); \
+ D = SPH_T32(T1 + T2); \
+ W13 = SPH_T32(SSG2_1(W11) + W06 + SSG2_0(W14) + W13); \
+ T1 = SPH_T32(C + BSG2_1(H) + CH(H, A, B) \
+ + SPH_C32(0xA4506CEB) + W13); \
+ T2 = SPH_T32(BSG2_0(D) + MAJ(D, E, F)); \
+ G = SPH_T32(G + T1); \
+ C = SPH_T32(T1 + T2); \
+ W14 = SPH_T32(SSG2_1(W12) + W07 + SSG2_0(W15) + W14); \
+ T1 = SPH_T32(B + BSG2_1(G) + CH(G, H, A) \
+ + SPH_C32(0xBEF9A3F7) + W14); \
+ T2 = SPH_T32(BSG2_0(C) + MAJ(C, D, E)); \
+ F = SPH_T32(F + T1); \
+ B = SPH_T32(T1 + T2); \
+ W15 = SPH_T32(SSG2_1(W13) + W08 + SSG2_0(W00) + W15); \
+ T1 = SPH_T32(A + BSG2_1(F) + CH(F, G, H) \
+ + SPH_C32(0xC67178F2) + W15); \
+ T2 = SPH_T32(BSG2_0(B) + MAJ(B, C, D)); \
+ E = SPH_T32(E + T1); \
+ A = SPH_T32(T1 + T2); \
+ (r)[0] = SPH_T32((r)[0] + A); \
+ (r)[1] = SPH_T32((r)[1] + B); \
+ (r)[2] = SPH_T32((r)[2] + C); \
+ (r)[3] = SPH_T32((r)[3] + D); \
+ (r)[4] = SPH_T32((r)[4] + E); \
+ (r)[5] = SPH_T32((r)[5] + F); \
+ (r)[6] = SPH_T32((r)[6] + G); \
+ (r)[7] = SPH_T32((r)[7] + H); \
+ } while (0)
+
+#endif
+
+/*
+ * One round of SHA-224 / SHA-256. The data must be aligned for 32-bit access.
+ */
+static void
+sha2_round(const unsigned char *data, sph_u32 r[8])
+{
+#define SHA2_IN(x) sph_dec32be_aligned(data + (4 * (x)))
+ SHA2_ROUND_BODY(SHA2_IN, r);
+#undef SHA2_IN
+}
+
+/* see sph_sha2.h */
+void
+sph_sha224_init(void *cc)
+{
+ sph_sha224_context *sc;
+
+ sc = cc;
+ memcpy(sc->val, H224, sizeof H224);
+#if SPH_64
+ sc->count = 0;
+#else
+ sc->count_high = sc->count_low = 0;
+#endif
+}
+
+/* see sph_sha2.h */
+void
+sph_sha256_init(void *cc)
+{
+ sph_sha256_context *sc;
+
+ sc = cc;
+ memcpy(sc->val, H256, sizeof H256);
+#if SPH_64
+ sc->count = 0;
+#else
+ sc->count_high = sc->count_low = 0;
+#endif
+}
+
+#define RFUN sha2_round
+#define HASH sha224
+#define BE32 1
+#include "sph/md_helper.c"
+
+/* see sph_sha2.h */
+void
+sph_sha224_close(void *cc, void *dst)
+{
+ sha224_close(cc, dst, 7);
+ sph_sha224_init(cc);
+}
+
+/* see sph_sha2.h */
+void
+sph_sha224_addbits_and_close(void *cc, unsigned ub, unsigned n, void *dst)
+{
+ sha224_addbits_and_close(cc, ub, n, dst, 7);
+ sph_sha224_init(cc);
+}
+
+/* see sph_sha2.h */
+void
+sph_sha256_close(void *cc, void *dst)
+{
+ sha224_close(cc, dst, 8);
+ sph_sha256_init(cc);
+}
+
+/* see sph_sha2.h */
+void
+sph_sha256_addbits_and_close(void *cc, unsigned ub, unsigned n, void *dst)
+{
+ sha224_addbits_and_close(cc, ub, n, dst, 8);
+ sph_sha256_init(cc);
+}
+
+/* see sph_sha2.h */
+void
+sph_sha224_comp(const sph_u32 msg[16], sph_u32 val[8])
+{
+#define SHA2_IN(x) msg[x]
+ SHA2_ROUND_BODY(SHA2_IN, val);
+#undef SHA2_IN
+}
diff --git a/libqpdf/sha2big.c b/libqpdf/sha2big.c
new file mode 100644
index 0000000..e4aadbd
--- /dev/null
+++ b/libqpdf/sha2big.c
@@ -0,0 +1,247 @@
+/* $Id: sha2big.c 216 2010-06-08 09:46:57Z tp $ */
+/*
+ * SHA-384 / SHA-512 implementation.
+ *
+ * ==========================(LICENSE BEGIN)============================
+ *
+ * Copyright (c) 2007-2010 Projet RNRT SAPHIR
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ===========================(LICENSE END)=============================
+ *
+ * @author Thomas Pornin <thomas.pornin@cryptolog.com>
+ */
+
+#include <stddef.h>
+#include <string.h>
+
+#include "sph/sph_sha2.h"
+
+#if SPH_64
+
+#define CH(X, Y, Z) ((((Y) ^ (Z)) & (X)) ^ (Z))
+#define MAJ(X, Y, Z) (((X) & (Y)) | (((X) | (Y)) & (Z)))
+
+#define ROTR64 SPH_ROTR64
+
+#define BSG5_0(x) (ROTR64(x, 28) ^ ROTR64(x, 34) ^ ROTR64(x, 39))
+#define BSG5_1(x) (ROTR64(x, 14) ^ ROTR64(x, 18) ^ ROTR64(x, 41))
+#define SSG5_0(x) (ROTR64(x, 1) ^ ROTR64(x, 8) ^ SPH_T64((x) >> 7))
+#define SSG5_1(x) (ROTR64(x, 19) ^ ROTR64(x, 61) ^ SPH_T64((x) >> 6))
+
+static const sph_u64 K512[80] = {
+ SPH_C64(0x428A2F98D728AE22), SPH_C64(0x7137449123EF65CD),
+ SPH_C64(0xB5C0FBCFEC4D3B2F), SPH_C64(0xE9B5DBA58189DBBC),
+ SPH_C64(0x3956C25BF348B538), SPH_C64(0x59F111F1B605D019),
+ SPH_C64(0x923F82A4AF194F9B), SPH_C64(0xAB1C5ED5DA6D8118),
+ SPH_C64(0xD807AA98A3030242), SPH_C64(0x12835B0145706FBE),
+ SPH_C64(0x243185BE4EE4B28C), SPH_C64(0x550C7DC3D5FFB4E2),
+ SPH_C64(0x72BE5D74F27B896F), SPH_C64(0x80DEB1FE3B1696B1),
+ SPH_C64(0x9BDC06A725C71235), SPH_C64(0xC19BF174CF692694),
+ SPH_C64(0xE49B69C19EF14AD2), SPH_C64(0xEFBE4786384F25E3),
+ SPH_C64(0x0FC19DC68B8CD5B5), SPH_C64(0x240CA1CC77AC9C65),
+ SPH_C64(0x2DE92C6F592B0275), SPH_C64(0x4A7484AA6EA6E483),
+ SPH_C64(0x5CB0A9DCBD41FBD4), SPH_C64(0x76F988DA831153B5),
+ SPH_C64(0x983E5152EE66DFAB), SPH_C64(0xA831C66D2DB43210),
+ SPH_C64(0xB00327C898FB213F), SPH_C64(0xBF597FC7BEEF0EE4),
+ SPH_C64(0xC6E00BF33DA88FC2), SPH_C64(0xD5A79147930AA725),
+ SPH_C64(0x06CA6351E003826F), SPH_C64(0x142929670A0E6E70),
+ SPH_C64(0x27B70A8546D22FFC), SPH_C64(0x2E1B21385C26C926),
+ SPH_C64(0x4D2C6DFC5AC42AED), SPH_C64(0x53380D139D95B3DF),
+ SPH_C64(0x650A73548BAF63DE), SPH_C64(0x766A0ABB3C77B2A8),
+ SPH_C64(0x81C2C92E47EDAEE6), SPH_C64(0x92722C851482353B),
+ SPH_C64(0xA2BFE8A14CF10364), SPH_C64(0xA81A664BBC423001),
+ SPH_C64(0xC24B8B70D0F89791), SPH_C64(0xC76C51A30654BE30),
+ SPH_C64(0xD192E819D6EF5218), SPH_C64(0xD69906245565A910),
+ SPH_C64(0xF40E35855771202A), SPH_C64(0x106AA07032BBD1B8),
+ SPH_C64(0x19A4C116B8D2D0C8), SPH_C64(0x1E376C085141AB53),
+ SPH_C64(0x2748774CDF8EEB99), SPH_C64(0x34B0BCB5E19B48A8),
+ SPH_C64(0x391C0CB3C5C95A63), SPH_C64(0x4ED8AA4AE3418ACB),
+ SPH_C64(0x5B9CCA4F7763E373), SPH_C64(0x682E6FF3D6B2B8A3),
+ SPH_C64(0x748F82EE5DEFB2FC), SPH_C64(0x78A5636F43172F60),
+ SPH_C64(0x84C87814A1F0AB72), SPH_C64(0x8CC702081A6439EC),
+ SPH_C64(0x90BEFFFA23631E28), SPH_C64(0xA4506CEBDE82BDE9),
+ SPH_C64(0xBEF9A3F7B2C67915), SPH_C64(0xC67178F2E372532B),
+ SPH_C64(0xCA273ECEEA26619C), SPH_C64(0xD186B8C721C0C207),
+ SPH_C64(0xEADA7DD6CDE0EB1E), SPH_C64(0xF57D4F7FEE6ED178),
+ SPH_C64(0x06F067AA72176FBA), SPH_C64(0x0A637DC5A2C898A6),
+ SPH_C64(0x113F9804BEF90DAE), SPH_C64(0x1B710B35131C471B),
+ SPH_C64(0x28DB77F523047D84), SPH_C64(0x32CAAB7B40C72493),
+ SPH_C64(0x3C9EBE0A15C9BEBC), SPH_C64(0x431D67C49C100D4C),
+ SPH_C64(0x4CC5D4BECB3E42B6), SPH_C64(0x597F299CFC657E2A),
+ SPH_C64(0x5FCB6FAB3AD6FAEC), SPH_C64(0x6C44198C4A475817)
+};
+
+static const sph_u64 H384[8] = {
+ SPH_C64(0xCBBB9D5DC1059ED8), SPH_C64(0x629A292A367CD507),
+ SPH_C64(0x9159015A3070DD17), SPH_C64(0x152FECD8F70E5939),
+ SPH_C64(0x67332667FFC00B31), SPH_C64(0x8EB44A8768581511),
+ SPH_C64(0xDB0C2E0D64F98FA7), SPH_C64(0x47B5481DBEFA4FA4)
+};
+
+static const sph_u64 H512[8] = {
+ SPH_C64(0x6A09E667F3BCC908), SPH_C64(0xBB67AE8584CAA73B),
+ SPH_C64(0x3C6EF372FE94F82B), SPH_C64(0xA54FF53A5F1D36F1),
+ SPH_C64(0x510E527FADE682D1), SPH_C64(0x9B05688C2B3E6C1F),
+ SPH_C64(0x1F83D9ABFB41BD6B), SPH_C64(0x5BE0CD19137E2179)
+};
+
+/*
+ * This macro defines the body for a SHA-384 / SHA-512 compression function
+ * implementation. The "in" parameter should evaluate, when applied to a
+ * numerical input parameter from 0 to 15, to an expression which yields
+ * the corresponding input block. The "r" parameter should evaluate to
+ * an array or pointer expression designating the array of 8 words which
+ * contains the input and output of the compression function.
+ *
+ * SHA-512 is hard for the compiler. If the loop is completely unrolled,
+ * then the code will be quite huge (possibly more than 100 kB), and the
+ * performance will be degraded due to cache misses on the code. We
+ * unroll only eight steps, which avoids all needless copies when
+ * 64-bit registers are swapped.
+ */
+
+#define SHA3_STEP(A, B, C, D, E, F, G, H, i) do { \
+ sph_u64 T1, T2; \
+ T1 = SPH_T64(H + BSG5_1(E) + CH(E, F, G) + K512[i] + W[i]); \
+ T2 = SPH_T64(BSG5_0(A) + MAJ(A, B, C)); \
+ D = SPH_T64(D + T1); \
+ H = SPH_T64(T1 + T2); \
+ } while (0)
+
+#define SHA3_ROUND_BODY(in, r) do { \
+ int i; \
+ sph_u64 A, B, C, D, E, F, G, H; \
+ sph_u64 W[80]; \
+ \
+ for (i = 0; i < 16; i ++) \
+ W[i] = in(i); \
+ for (i = 16; i < 80; i ++) \
+ W[i] = SPH_T64(SSG5_1(W[i - 2]) + W[i - 7] \
+ + SSG5_0(W[i - 15]) + W[i - 16]); \
+ A = (r)[0]; \
+ B = (r)[1]; \
+ C = (r)[2]; \
+ D = (r)[3]; \
+ E = (r)[4]; \
+ F = (r)[5]; \
+ G = (r)[6]; \
+ H = (r)[7]; \
+ for (i = 0; i < 80; i += 8) { \
+ SHA3_STEP(A, B, C, D, E, F, G, H, i + 0); \
+ SHA3_STEP(H, A, B, C, D, E, F, G, i + 1); \
+ SHA3_STEP(G, H, A, B, C, D, E, F, i + 2); \
+ SHA3_STEP(F, G, H, A, B, C, D, E, i + 3); \
+ SHA3_STEP(E, F, G, H, A, B, C, D, i + 4); \
+ SHA3_STEP(D, E, F, G, H, A, B, C, i + 5); \
+ SHA3_STEP(C, D, E, F, G, H, A, B, i + 6); \
+ SHA3_STEP(B, C, D, E, F, G, H, A, i + 7); \
+ } \
+ (r)[0] = SPH_T64((r)[0] + A); \
+ (r)[1] = SPH_T64((r)[1] + B); \
+ (r)[2] = SPH_T64((r)[2] + C); \
+ (r)[3] = SPH_T64((r)[3] + D); \
+ (r)[4] = SPH_T64((r)[4] + E); \
+ (r)[5] = SPH_T64((r)[5] + F); \
+ (r)[6] = SPH_T64((r)[6] + G); \
+ (r)[7] = SPH_T64((r)[7] + H); \
+ } while (0)
+
+/*
+ * One round of SHA-384 / SHA-512. The data must be aligned for 64-bit access.
+ */
+static void
+sha3_round(const unsigned char *data, sph_u64 r[8])
+{
+#define SHA3_IN(x) sph_dec64be_aligned(data + (8 * (x)))
+ SHA3_ROUND_BODY(SHA3_IN, r);
+#undef SHA3_IN
+}
+
+/* see sph_sha3.h */
+void
+sph_sha384_init(void *cc)
+{
+ sph_sha384_context *sc;
+
+ sc = cc;
+ memcpy(sc->val, H384, sizeof H384);
+ sc->count = 0;
+}
+
+/* see sph_sha3.h */
+void
+sph_sha512_init(void *cc)
+{
+ sph_sha512_context *sc;
+
+ sc = cc;
+ memcpy(sc->val, H512, sizeof H512);
+ sc->count = 0;
+}
+
+#define RFUN sha3_round
+#define HASH sha384
+#define BE64 1
+#include "sph/md_helper.c"
+
+/* see sph_sha3.h */
+void
+sph_sha384_close(void *cc, void *dst)
+{
+ sha384_close(cc, dst, 6);
+ sph_sha384_init(cc);
+}
+
+/* see sph_sha3.h */
+void
+sph_sha384_addbits_and_close(void *cc, unsigned ub, unsigned n, void *dst)
+{
+ sha384_addbits_and_close(cc, ub, n, dst, 6);
+ sph_sha384_init(cc);
+}
+
+/* see sph_sha3.h */
+void
+sph_sha512_close(void *cc, void *dst)
+{
+ sha384_close(cc, dst, 8);
+ sph_sha512_init(cc);
+}
+
+/* see sph_sha3.h */
+void
+sph_sha512_addbits_and_close(void *cc, unsigned ub, unsigned n, void *dst)
+{
+ sha384_addbits_and_close(cc, ub, n, dst, 8);
+ sph_sha512_init(cc);
+}
+
+/* see sph_sha3.h */
+void
+sph_sha384_comp(const sph_u64 msg[16], sph_u64 val[8])
+{
+#define SHA3_IN(x) msg[x]
+ SHA3_ROUND_BODY(SHA3_IN, val);
+#undef SHA3_IN
+}
+
+#endif
diff --git a/libqpdf/sph/md_helper.c b/libqpdf/sph/md_helper.c
new file mode 100644
index 0000000..5384f03
--- /dev/null
+++ b/libqpdf/sph/md_helper.c
@@ -0,0 +1,346 @@
+/* $Id: md_helper.c 216 2010-06-08 09:46:57Z tp $ */
+/*
+ * This file contains some functions which implement the external data
+ * handling and padding for Merkle-Damgard hash functions which follow
+ * the conventions set out by MD4 (little-endian) or SHA-1 (big-endian).
+ *
+ * API: this file is meant to be included, not compiled as a stand-alone
+ * file. Some macros must be defined:
+ * RFUN name for the round function
+ * HASH "short name" for the hash function
+ * BE32 defined for big-endian, 32-bit based (e.g. SHA-1)
+ * LE32 defined for little-endian, 32-bit based (e.g. MD5)
+ * BE64 defined for big-endian, 64-bit based (e.g. SHA-512)
+ * LE64 defined for little-endian, 64-bit based (no example yet)
+ * PW01 if defined, append 0x01 instead of 0x80 (for Tiger)
+ * BLEN if defined, length of a message block (in bytes)
+ * PLW1 if defined, length is defined on one 64-bit word only (for Tiger)
+ * PLW4 if defined, length is defined on four 64-bit words (for WHIRLPOOL)
+ * SVAL if defined, reference to the context state information
+ *
+ * BLEN is used when a message block is not 16 (32-bit or 64-bit) words:
+ * this is used for instance for Tiger, which works on 64-bit words but
+ * uses 512-bit message blocks (eight 64-bit words). PLW1 and PLW4 are
+ * ignored if 32-bit words are used; if 64-bit words are used and PLW1 is
+ * set, then only one word (64 bits) will be used to encode the input
+ * message length (in bits), otherwise two words will be used (as in
+ * SHA-384 and SHA-512). If 64-bit words are used and PLW4 is defined (but
+ * not PLW1), four 64-bit words will be used to encode the message length
+ * (in bits). Note that regardless of those settings, only 64-bit message
+ * lengths are supported (in bits): messages longer than 2 Exabytes will be
+ * improperly hashed (this is unlikely to happen soon: 2 Exabytes is about
+ * 2 millions Terabytes, which is huge).
+ *
+ * If CLOSE_ONLY is defined, then this file defines only the sph_XXX_close()
+ * function. This is used for Tiger2, which is identical to Tiger except
+ * when it comes to the padding (Tiger2 uses the standard 0x80 byte instead
+ * of the 0x01 from original Tiger).
+ *
+ * The RFUN function is invoked with two arguments, the first pointing to
+ * aligned data (as a "const void *"), the second being state information
+ * from the context structure. By default, this state information is the
+ * "val" field from the context, and this field is assumed to be an array
+ * of words ("sph_u32" or "sph_u64", depending on BE32/LE32/BE64/LE64).
+ * from the context structure. The "val" field can have any type, except
+ * for the output encoding which assumes that it is an array of "sph_u32"
+ * values. By defining NO_OUTPUT, this last step is deactivated; the
+ * includer code is then responsible for writing out the hash result. When
+ * NO_OUTPUT is defined, the third parameter to the "close()" function is
+ * ignored.
+ *
+ * ==========================(LICENSE BEGIN)============================
+ *
+ * Copyright (c) 2007-2010 Projet RNRT SAPHIR
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ===========================(LICENSE END)=============================
+ *
+ * @author Thomas Pornin <thomas.pornin@cryptolog.com>
+ */
+
+#ifdef _MSC_VER
+#pragma warning (disable: 4146)
+#endif
+
+#undef SPH_XCAT
+#define SPH_XCAT(a, b) SPH_XCAT_(a, b)
+#undef SPH_XCAT_
+#define SPH_XCAT_(a, b) a ## b
+
+#undef SPH_BLEN
+#undef SPH_WLEN
+#if defined BE64 || defined LE64
+#define SPH_BLEN 128U
+#define SPH_WLEN 8U
+#else
+#define SPH_BLEN 64U
+#define SPH_WLEN 4U
+#endif
+
+#ifdef BLEN
+#undef SPH_BLEN
+#define SPH_BLEN BLEN
+#endif
+
+#undef SPH_MAXPAD
+#if defined PLW1
+#define SPH_MAXPAD (SPH_BLEN - SPH_WLEN)
+#elif defined PLW4
+#define SPH_MAXPAD (SPH_BLEN - (SPH_WLEN << 2))
+#else
+#define SPH_MAXPAD (SPH_BLEN - (SPH_WLEN << 1))
+#endif
+
+#undef SPH_VAL
+#undef SPH_NO_OUTPUT
+#ifdef SVAL
+#define SPH_VAL SVAL
+#define SPH_NO_OUTPUT 1
+#else
+#define SPH_VAL sc->val
+#endif
+
+#ifndef CLOSE_ONLY
+
+#ifdef SPH_UPTR
+static void
+SPH_XCAT(HASH, _short)(void *cc, const void *data, size_t len)
+#else
+void
+SPH_XCAT(sph_, HASH)(void *cc, const void *data, size_t len)
+#endif
+{
+ SPH_XCAT(sph_, SPH_XCAT(HASH, _context)) *sc;
+ unsigned current;
+
+ sc = cc;
+#if SPH_64
+ current = (unsigned)sc->count & (SPH_BLEN - 1U);
+#else
+ current = (unsigned)sc->count_low & (SPH_BLEN - 1U);
+#endif
+ while (len > 0) {
+ unsigned clen;
+#if !SPH_64
+ sph_u32 clow, clow2;
+#endif
+
+ clen = SPH_BLEN - current;
+ if (clen > len)
+ clen = len;
+ memcpy(sc->buf + current, data, clen);
+ data = (const unsigned char *)data + clen;
+ current += clen;
+ len -= clen;
+ if (current == SPH_BLEN) {
+ RFUN(sc->buf, SPH_VAL);
+ current = 0;
+ }
+#if SPH_64
+ sc->count += clen;
+#else
+ clow = sc->count_low;
+ clow2 = SPH_T32(clow + clen);
+ sc->count_low = clow2;
+ if (clow2 < clow)
+ sc->count_high ++;
+#endif
+ }
+}
+
+#ifdef SPH_UPTR
+void
+SPH_XCAT(sph_, HASH)(void *cc, const void *data, size_t len)
+{
+ SPH_XCAT(sph_, SPH_XCAT(HASH, _context)) *sc;
+ unsigned current;
+ size_t orig_len;
+#if !SPH_64
+ sph_u32 clow, clow2;
+#endif
+
+ if (len < (2 * SPH_BLEN)) {
+ SPH_XCAT(HASH, _short)(cc, data, len);
+ return;
+ }
+ sc = cc;
+#if SPH_64
+ current = (unsigned)sc->count & (SPH_BLEN - 1U);
+#else
+ current = (unsigned)sc->count_low & (SPH_BLEN - 1U);
+#endif
+ if (current > 0) {
+ unsigned t;
+
+ t = SPH_BLEN - current;
+ SPH_XCAT(HASH, _short)(cc, data, t);
+ data = (const unsigned char *)data + t;
+ len -= t;
+ }
+#if !SPH_UNALIGNED
+ if (((SPH_UPTR)data & (SPH_WLEN - 1U)) != 0) {
+ SPH_XCAT(HASH, _short)(cc, data, len);
+ return;
+ }
+#endif
+ orig_len = len;
+ while (len >= SPH_BLEN) {
+ RFUN(data, SPH_VAL);
+ len -= SPH_BLEN;
+ data = (const unsigned char *)data + SPH_BLEN;
+ }
+ if (len > 0)
+ memcpy(sc->buf, data, len);
+#if SPH_64
+ sc->count += (sph_u64)orig_len;
+#else
+ clow = sc->count_low;
+ clow2 = SPH_T32(clow + orig_len);
+ sc->count_low = clow2;
+ if (clow2 < clow)
+ sc->count_high ++;
+ /*
+ * This code handles the improbable situation where "size_t" is
+ * greater than 32 bits, and yet we do not have a 64-bit type.
+ */
+ orig_len >>= 12;
+ orig_len >>= 10;
+ orig_len >>= 10;
+ sc->count_high += orig_len;
+#endif
+}
+#endif
+
+#endif
+
+/*
+ * Perform padding and produce result. The context is NOT reinitialized
+ * by this function.
+ */
+static void
+SPH_XCAT(HASH, _addbits_and_close)(void *cc,
+ unsigned ub, unsigned n, void *dst, unsigned rnum)
+{
+ SPH_XCAT(sph_, SPH_XCAT(HASH, _context)) *sc;
+ unsigned current, u;
+#if !SPH_64
+ sph_u32 low, high;
+#endif
+
+ sc = cc;
+#if SPH_64
+ current = (unsigned)sc->count & (SPH_BLEN - 1U);
+#else
+ current = (unsigned)sc->count_low & (SPH_BLEN - 1U);
+#endif
+#ifdef PW01
+ sc->buf[current ++] = (0x100 | (ub & 0xFF)) >> (8 - n);
+#else
+ {
+ unsigned z;
+
+ z = 0x80 >> n;
+ sc->buf[current ++] = ((ub & -z) | z) & 0xFF;
+ }
+#endif
+ if (current > SPH_MAXPAD) {
+ memset(sc->buf + current, 0, SPH_BLEN - current);
+ RFUN(sc->buf, SPH_VAL);
+ memset(sc->buf, 0, SPH_MAXPAD);
+ } else {
+ memset(sc->buf + current, 0, SPH_MAXPAD - current);
+ }
+#if defined BE64
+#if defined PLW1
+ sph_enc64be_aligned(sc->buf + SPH_MAXPAD,
+ SPH_T64(sc->count << 3) + (sph_u64)n);
+#elif defined PLW4
+ memset(sc->buf + SPH_MAXPAD, 0, 2 * SPH_WLEN);
+ sph_enc64be_aligned(sc->buf + SPH_MAXPAD + 2 * SPH_WLEN,
+ sc->count >> 61);
+ sph_enc64be_aligned(sc->buf + SPH_MAXPAD + 3 * SPH_WLEN,
+ SPH_T64(sc->count << 3) + (sph_u64)n);
+#else
+ sph_enc64be_aligned(sc->buf + SPH_MAXPAD, sc->count >> 61);
+ sph_enc64be_aligned(sc->buf + SPH_MAXPAD + SPH_WLEN,
+ SPH_T64(sc->count << 3) + (sph_u64)n);
+#endif
+#elif defined LE64
+#if defined PLW1
+ sph_enc64le_aligned(sc->buf + SPH_MAXPAD,
+ SPH_T64(sc->count << 3) + (sph_u64)n);
+#elif defined PLW1
+ sph_enc64le_aligned(sc->buf + SPH_MAXPAD,
+ SPH_T64(sc->count << 3) + (sph_u64)n);
+ sph_enc64le_aligned(sc->buf + SPH_MAXPAD + SPH_WLEN, sc->count >> 61);
+ memset(sc->buf + SPH_MAXPAD + 2 * SPH_WLEN, 0, 2 * SPH_WLEN);
+#else
+ sph_enc64le_aligned(sc->buf + SPH_MAXPAD,
+ SPH_T64(sc->count << 3) + (sph_u64)n);
+ sph_enc64le_aligned(sc->buf + SPH_MAXPAD + SPH_WLEN, sc->count >> 61);
+#endif
+#else
+#if SPH_64
+#ifdef BE32
+ sph_enc64be_aligned(sc->buf + SPH_MAXPAD,
+ SPH_T64(sc->count << 3) + (sph_u64)n);
+#else
+ sph_enc64le_aligned(sc->buf + SPH_MAXPAD,
+ SPH_T64(sc->count << 3) + (sph_u64)n);
+#endif
+#else
+ low = sc->count_low;
+ high = SPH_T32((sc->count_high << 3) | (low >> 29));
+ low = SPH_T32(low << 3) + (sph_u32)n;
+#ifdef BE32
+ sph_enc32be(sc->buf + SPH_MAXPAD, high);
+ sph_enc32be(sc->buf + SPH_MAXPAD + SPH_WLEN, low);
+#else
+ sph_enc32le(sc->buf + SPH_MAXPAD, low);
+ sph_enc32le(sc->buf + SPH_MAXPAD + SPH_WLEN, high);
+#endif
+#endif
+#endif
+ RFUN(sc->buf, SPH_VAL);
+#ifdef SPH_NO_OUTPUT
+ (void)dst;
+ (void)rnum;
+ (void)u;
+#else
+ for (u = 0; u < rnum; u ++) {
+#if defined BE64
+ sph_enc64be((unsigned char *)dst + 8 * u, sc->val[u]);
+#elif defined LE64
+ sph_enc64le((unsigned char *)dst + 8 * u, sc->val[u]);
+#elif defined BE32
+ sph_enc32be((unsigned char *)dst + 4 * u, sc->val[u]);
+#else
+ sph_enc32le((unsigned char *)dst + 4 * u, sc->val[u]);
+#endif
+ }
+#endif
+}
+
+static void
+SPH_XCAT(HASH, _close)(void *cc, void *dst, unsigned rnum)
+{
+ SPH_XCAT(HASH, _addbits_and_close)(cc, 0, 0, dst, rnum);
+}
diff --git a/libqpdf/sph/sph_sha2.h b/libqpdf/sph/sph_sha2.h
new file mode 100644
index 0000000..4bff9cd
--- /dev/null
+++ b/libqpdf/sph/sph_sha2.h
@@ -0,0 +1,378 @@
+/* $Id: sph_sha2.h 216 2010-06-08 09:46:57Z tp $ */
+/**
+ * SHA-224, SHA-256, SHA-384 and SHA-512 interface.
+ *
+ * SHA-256 has been published in FIPS 180-2, now amended with a change
+ * notice to include SHA-224 as well (which is a simple variation on
+ * SHA-256). SHA-384 and SHA-512 are also defined in FIPS 180-2. FIPS
+ * standards can be found at:
+ * http://csrc.nist.gov/publications/fips/
+ *
+ * ==========================(LICENSE BEGIN)============================
+ *
+ * Copyright (c) 2007-2010 Projet RNRT SAPHIR
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ===========================(LICENSE END)=============================
+ *
+ * @file sph_sha2.h
+ * @author Thomas Pornin <thomas.pornin@cryptolog.com>
+ */
+
+#ifndef SPH_SHA2_H__
+#define SPH_SHA2_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+#include "sph_types.h"
+
+/**
+ * Output size (in bits) for SHA-224.
+ */
+#define SPH_SIZE_sha224 224
+
+/**
+ * Output size (in bits) for SHA-256.
+ */
+#define SPH_SIZE_sha256 256
+
+/**
+ * This structure is a context for SHA-224 computations: it contains the
+ * intermediate values and some data from the last entered block. Once
+ * a SHA-224 computation has been performed, the context can be reused for
+ * another computation.
+ *
+ * The contents of this structure are private. A running SHA-224 computation
+ * can be cloned by copying the context (e.g. with a simple
+ * <code>memcpy()</code>).
+ */
+typedef struct {
+#ifndef DOXYGEN_IGNORE
+ unsigned char buf[64]; /* first field, for alignment */
+ sph_u32 val[8];
+#if SPH_64
+ sph_u64 count;
+#else
+ sph_u32 count_high, count_low;
+#endif
+#endif
+} sph_sha224_context;
+
+/**
+ * This structure is a context for SHA-256 computations. It is identical
+ * to the SHA-224 context. However, a context is initialized for SHA-224
+ * <strong>or</strong> SHA-256, but not both (the internal IV is not the
+ * same).
+ */
+typedef sph_sha224_context sph_sha256_context;
+
+/**
+ * Initialize a SHA-224 context. This process performs no memory allocation.
+ *
+ * @param cc the SHA-224 context (pointer to
+ * a <code>sph_sha224_context</code>)
+ */
+void sph_sha224_init(void *cc);
+
+/**
+ * Process some data bytes. It is acceptable that <code>len</code> is zero
+ * (in which case this function does nothing).
+ *
+ * @param cc the SHA-224 context
+ * @param data the input data
+ * @param len the input data length (in bytes)
+ */
+void sph_sha224(void *cc, const void *data, size_t len);
+
+/**
+ * Terminate the current SHA-224 computation and output the result into the
+ * provided buffer. The destination buffer must be wide enough to
+ * accomodate the result (28 bytes). The context is automatically
+ * reinitialized.
+ *
+ * @param cc the SHA-224 context
+ * @param dst the destination buffer
+ */
+void sph_sha224_close(void *cc, void *dst);
+
+/**
+ * Add a few additional bits (0 to 7) to the current computation, then
+ * terminate it and output the result in the provided buffer, which must
+ * be wide enough to accomodate the result (28 bytes). If bit number i
+ * in <code>ub</code> has value 2^i, then the extra bits are those
+ * numbered 7 downto 8-n (this is the big-endian convention at the byte
+ * level). The context is automatically reinitialized.
+ *
+ * @param cc the SHA-224 context
+ * @param ub the extra bits
+ * @param n the number of extra bits (0 to 7)
+ * @param dst the destination buffer
+ */
+void sph_sha224_addbits_and_close(void *cc, unsigned ub, unsigned n, void *dst);
+
+/**
+ * Apply the SHA-224 compression function on the provided data. The
+ * <code>msg</code> parameter contains the 16 32-bit input blocks,
+ * as numerical values (hence after the big-endian decoding). The
+ * <code>val</code> parameter contains the 8 32-bit input blocks for
+ * the compression function; the output is written in place in this
+ * array.
+ *
+ * @param msg the message block (16 values)
+ * @param val the function 256-bit input and output
+ */
+void sph_sha224_comp(const sph_u32 msg[16], sph_u32 val[8]);
+
+/**
+ * Initialize a SHA-256 context. This process performs no memory allocation.
+ *
+ * @param cc the SHA-256 context (pointer to
+ * a <code>sph_sha256_context</code>)
+ */
+void sph_sha256_init(void *cc);
+
+#ifdef DOXYGEN_IGNORE
+/**
+ * Process some data bytes, for SHA-256. This function is identical to
+ * <code>sha_224()</code>
+ *
+ * @param cc the SHA-224 context
+ * @param data the input data
+ * @param len the input data length (in bytes)
+ */
+void sph_sha256(void *cc, const void *data, size_t len);
+#endif
+
+#ifndef DOXYGEN_IGNORE
+#define sph_sha256 sph_sha224
+#endif
+
+/**
+ * Terminate the current SHA-256 computation and output the result into the
+ * provided buffer. The destination buffer must be wide enough to
+ * accomodate the result (32 bytes). The context is automatically
+ * reinitialized.
+ *
+ * @param cc the SHA-256 context
+ * @param dst the destination buffer
+ */
+void sph_sha256_close(void *cc, void *dst);
+
+/**
+ * Add a few additional bits (0 to 7) to the current computation, then
+ * terminate it and output the result in the provided buffer, which must
+ * be wide enough to accomodate the result (32 bytes). If bit number i
+ * in <code>ub</code> has value 2^i, then the extra bits are those
+ * numbered 7 downto 8-n (this is the big-endian convention at the byte
+ * level). The context is automatically reinitialized.
+ *
+ * @param cc the SHA-256 context
+ * @param ub the extra bits
+ * @param n the number of extra bits (0 to 7)
+ * @param dst the destination buffer
+ */
+void sph_sha256_addbits_and_close(void *cc, unsigned ub, unsigned n, void *dst);
+
+#ifdef DOXYGEN_IGNORE
+/**
+ * Apply the SHA-256 compression function on the provided data. This
+ * function is identical to <code>sha224_comp()</code>.
+ *
+ * @param msg the message block (16 values)
+ * @param val the function 256-bit input and output
+ */
+void sph_sha256_comp(const sph_u32 msg[16], sph_u32 val[8]);
+#endif
+
+#ifndef DOXYGEN_IGNORE
+#define sph_sha256_comp sph_sha224_comp
+#endif
+
+#if SPH_64
+
+/**
+ * Output size (in bits) for SHA-384.
+ */
+#define SPH_SIZE_sha384 384
+
+/**
+ * Output size (in bits) for SHA-512.
+ */
+#define SPH_SIZE_sha512 512
+
+/**
+ * This structure is a context for SHA-384 computations: it contains the
+ * intermediate values and some data from the last entered block. Once
+ * a SHA-384 computation has been performed, the context can be reused for
+ * another computation.
+ *
+ * The contents of this structure are private. A running SHA-384 computation
+ * can be cloned by copying the context (e.g. with a simple
+ * <code>memcpy()</code>).
+ */
+typedef struct {
+#ifndef DOXYGEN_IGNORE
+ unsigned char buf[128]; /* first field, for alignment */
+ sph_u64 val[8];
+ sph_u64 count;
+#endif
+} sph_sha384_context;
+
+/**
+ * Initialize a SHA-384 context. This process performs no memory allocation.
+ *
+ * @param cc the SHA-384 context (pointer to
+ * a <code>sph_sha384_context</code>)
+ */
+void sph_sha384_init(void *cc);
+
+/**
+ * Process some data bytes. It is acceptable that <code>len</code> is zero
+ * (in which case this function does nothing).
+ *
+ * @param cc the SHA-384 context
+ * @param data the input data
+ * @param len the input data length (in bytes)
+ */
+void sph_sha384(void *cc, const void *data, size_t len);
+
+/**
+ * Terminate the current SHA-384 computation and output the result into the
+ * provided buffer. The destination buffer must be wide enough to
+ * accomodate the result (48 bytes). The context is automatically
+ * reinitialized.
+ *
+ * @param cc the SHA-384 context
+ * @param dst the destination buffer
+ */
+void sph_sha384_close(void *cc, void *dst);
+
+/**
+ * Add a few additional bits (0 to 7) to the current computation, then
+ * terminate it and output the result in the provided buffer, which must
+ * be wide enough to accomodate the result (48 bytes). If bit number i
+ * in <code>ub</code> has value 2^i, then the extra bits are those
+ * numbered 7 downto 8-n (this is the big-endian convention at the byte
+ * level). The context is automatically reinitialized.
+ *
+ * @param cc the SHA-384 context
+ * @param ub the extra bits
+ * @param n the number of extra bits (0 to 7)
+ * @param dst the destination buffer
+ */
+void sph_sha384_addbits_and_close(void *cc, unsigned ub, unsigned n, void *dst);
+
+/**
+ * Apply the SHA-384 compression function on the provided data. The
+ * <code>msg</code> parameter contains the 16 64-bit input blocks,
+ * as numerical values (hence after the big-endian decoding). The
+ * <code>val</code> parameter contains the 8 64-bit input blocks for
+ * the compression function; the output is written in place in this
+ * array.
+ *
+ * @param msg the message block (16 values)
+ * @param val the function 512-bit input and output
+ */
+void sph_sha384_comp(const sph_u64 msg[16], sph_u64 val[8]);
+
+/**
+ * This structure is a context for SHA-512 computations. It is identical
+ * to the SHA-384 context. However, a context is initialized for SHA-384
+ * <strong>or</strong> SHA-512, but not both (the internal IV is not the
+ * same).
+ */
+typedef sph_sha384_context sph_sha512_context;
+
+/**
+ * Initialize a SHA-512 context. This process performs no memory allocation.
+ *
+ * @param cc the SHA-512 context (pointer to
+ * a <code>sph_sha512_context</code>)
+ */
+void sph_sha512_init(void *cc);
+
+#ifdef DOXYGEN_IGNORE
+/**
+ * Process some data bytes, for SHA-512. This function is identical to
+ * <code>sph_sha384()</code>.
+ *
+ * @param cc the SHA-384 context
+ * @param data the input data
+ * @param len the input data length (in bytes)
+ */
+void sph_sha512(void *cc, const void *data, size_t len);
+#endif
+
+#ifndef DOXYGEN_IGNORE
+#define sph_sha512 sph_sha384
+#endif
+
+/**
+ * Terminate the current SHA-512 computation and output the result into the
+ * provided buffer. The destination buffer must be wide enough to
+ * accomodate the result (64 bytes). The context is automatically
+ * reinitialized.
+ *
+ * @param cc the SHA-512 context
+ * @param dst the destination buffer
+ */
+void sph_sha512_close(void *cc, void *dst);
+
+/**
+ * Add a few additional bits (0 to 7) to the current computation, then
+ * terminate it and output the result in the provided buffer, which must
+ * be wide enough to accomodate the result (64 bytes). If bit number i
+ * in <code>ub</code> has value 2^i, then the extra bits are those
+ * numbered 7 downto 8-n (this is the big-endian convention at the byte
+ * level). The context is automatically reinitialized.
+ *
+ * @param cc the SHA-512 context
+ * @param ub the extra bits
+ * @param n the number of extra bits (0 to 7)
+ * @param dst the destination buffer
+ */
+void sph_sha512_addbits_and_close(void *cc, unsigned ub, unsigned n, void *dst);
+
+#ifdef DOXYGEN_IGNORE
+/**
+ * Apply the SHA-512 compression function. This function is identical to
+ * <code>sph_sha384_comp()</code>.
+ *
+ * @param msg the message block (16 values)
+ * @param val the function 512-bit input and output
+ */
+void sph_sha512_comp(const sph_u64 msg[16], sph_u64 val[8]);
+#endif
+
+#ifndef DOXYGEN_IGNORE
+#define sph_sha512_comp sph_sha384_comp
+#endif
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libqpdf/sph/sph_types.h b/libqpdf/sph/sph_types.h
new file mode 100644
index 0000000..7295b0b
--- /dev/null
+++ b/libqpdf/sph/sph_types.h
@@ -0,0 +1,1976 @@
+/* $Id: sph_types.h 260 2011-07-21 01:02:38Z tp $ */
+/**
+ * Basic type definitions.
+ *
+ * This header file defines the generic integer types that will be used
+ * for the implementation of hash functions; it also contains helper
+ * functions which encode and decode multi-byte integer values, using
+ * either little-endian or big-endian conventions.
+ *
+ * This file contains a compile-time test on the size of a byte
+ * (the <code>unsigned char</code> C type). If bytes are not octets,
+ * i.e. if they do not have a size of exactly 8 bits, then compilation
+ * is aborted. Architectures where bytes are not octets are relatively
+ * rare, even in the embedded devices market. We forbid non-octet bytes
+ * because there is no clear convention on how octet streams are encoded
+ * on such systems.
+ *
+ * ==========================(LICENSE BEGIN)============================
+ *
+ * Copyright (c) 2007-2010 Projet RNRT SAPHIR
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ===========================(LICENSE END)=============================
+ *
+ * @file sph_types.h
+ * @author Thomas Pornin <thomas.pornin@cryptolog.com>
+ */
+
+#ifndef SPH_TYPES_H__
+#define SPH_TYPES_H__
+
+#include <limits.h>
+
+/*
+ * All our I/O functions are defined over octet streams. We do not know
+ * how to handle input data if bytes are not octets.
+ */
+#if CHAR_BIT != 8
+#error This code requires 8-bit bytes
+#endif
+
+/* ============= BEGIN documentation block for Doxygen ============ */
+
+#ifdef DOXYGEN_IGNORE
+
+/** @mainpage sphlib C code documentation
+ *
+ * @section overview Overview
+ *
+ * <code>sphlib</code> is a library which contains implementations of
+ * various cryptographic hash functions. These pages have been generated
+ * with <a href="http://www.doxygen.org/index.html">doxygen</a> and
+ * document the API for the C implementations.
+ *
+ * The API is described in appropriate header files, which are available
+ * in the "Files" section. Each hash function family has its own header,
+ * whose name begins with <code>"sph_"</code> and contains the family
+ * name. For instance, the API for the RIPEMD hash functions is available
+ * in the header file <code>sph_ripemd.h</code>.
+ *
+ * @section principles API structure and conventions
+ *
+ * @subsection io Input/output conventions
+ *
+ * In all generality, hash functions operate over strings of bits.
+ * Individual bits are rarely encountered in C programming or actual
+ * communication protocols; most protocols converge on the ubiquitous
+ * "octet" which is a group of eight bits. Data is thus expressed as a
+ * stream of octets. The C programming language contains the notion of a
+ * "byte", which is a data unit managed under the type <code>"unsigned
+ * char"</code>. The C standard prescribes that a byte should hold at
+ * least eight bits, but possibly more. Most modern architectures, even
+ * in the embedded world, feature eight-bit bytes, i.e. map bytes to
+ * octets.
+ *
+ * Nevertheless, for some of the implemented hash functions, an extra
+ * API has been added, which allows the input of arbitrary sequences of
+ * bits: when the computation is about to be closed, 1 to 7 extra bits
+ * can be added. The functions for which this API is implemented include
+ * the SHA-2 functions and all SHA-3 candidates.
+ *
+ * <code>sphlib</code> defines hash function which may hash octet streams,
+ * i.e. streams of bits where the number of bits is a multiple of eight.
+ * The data input functions in the <code>sphlib</code> API expect data
+ * as anonymous pointers (<code>"const void *"</code>) with a length
+ * (of type <code>"size_t"</code>) which gives the input data chunk length
+ * in bytes. A byte is assumed to be an octet; the <code>sph_types.h</code>
+ * header contains a compile-time test which prevents compilation on
+ * architectures where this property is not met.
+ *
+ * The hash function output is also converted into bytes. All currently
+ * implemented hash functions have an output width which is a multiple of
+ * eight, and this is likely to remain true for new designs.
+ *
+ * Most hash functions internally convert input data into 32-bit of 64-bit
+ * words, using either little-endian or big-endian conversion. The hash
+ * output also often consists of such words, which are encoded into output
+ * bytes with a similar endianness convention. Some hash functions have
+ * been only loosely specified on that subject; when necessary,
+ * <code>sphlib</code> has been tested against published "reference"
+ * implementations in order to use the same conventions.
+ *
+ * @subsection shortname Function short name
+ *
+ * Each implemented hash function has a "short name" which is used
+ * internally to derive the identifiers for the functions and context
+ * structures which the function uses. For instance, MD5 has the short
+ * name <code>"md5"</code>. Short names are listed in the next section,
+ * for the implemented hash functions. In subsequent sections, the
+ * short name will be assumed to be <code>"XXX"</code>: replace with the
+ * actual hash function name to get the C identifier.
+ *
+ * Note: some functions within the same family share the same core
+ * elements, such as update function or context structure. Correspondingly,
+ * some of the defined types or functions may actually be macros which
+ * transparently evaluate to another type or function name.
+ *
+ * @subsection context Context structure
+ *
+ * Each implemented hash fonction has its own context structure, available
+ * under the type name <code>"sph_XXX_context"</code> for the hash function
+ * with short name <code>"XXX"</code>. This structure holds all needed
+ * state for a running hash computation.
+ *
+ * The contents of these structures are meant to be opaque, and private
+ * to the implementation. However, these contents are specified in the
+ * header files so that application code which uses <code>sphlib</code>
+ * may access the size of those structures.
+ *
+ * The caller is responsible for allocating the context structure,
+ * whether by dynamic allocation (<code>malloc()</code> or equivalent),
+ * static allocation (a global permanent variable), as an automatic
+ * variable ("on the stack"), or by any other mean which ensures proper
+ * structure alignment. <code>sphlib</code> code performs no dynamic
+ * allocation by itself.
+ *
+ * The context must be initialized before use, using the
+ * <code>sph_XXX_init()</code> function. This function sets the context
+ * state to proper initial values for hashing.
+ *
+ * Since all state data is contained within the context structure,
+ * <code>sphlib</code> is thread-safe and reentrant: several hash
+ * computations may be performed in parallel, provided that they do not
+ * operate on the same context. Moreover, a running computation can be
+ * cloned by copying the context (with a simple <code>memcpy()</code>):
+ * the context and its clone are then independant and may be updated
+ * with new data and/or closed without interfering with each other.
+ * Similarly, a context structure can be moved in memory at will:
+ * context structures contain no pointer, in particular no pointer to
+ * themselves.
+ *
+ * @subsection dataio Data input
+ *
+ * Hashed data is input with the <code>sph_XXX()</code> fonction, which
+ * takes as parameters a pointer to the context, a pointer to the data
+ * to hash, and the number of data bytes to hash. The context is updated
+ * with the new data.
+ *
+ * Data can be input in one or several calls, with arbitrary input lengths.
+ * However, it is best, performance wise, to input data by relatively big
+ * chunks (say a few kilobytes), because this allows <code>sphlib</code> to
+ * optimize things and avoid internal copying.
+ *
+ * When all data has been input, the context can be closed with
+ * <code>sph_XXX_close()</code>. The hash output is computed and written
+ * into the provided buffer. The caller must take care to provide a
+ * buffer of appropriate length; e.g., when using SHA-1, the output is
+ * a 20-byte word, therefore the output buffer must be at least 20-byte
+ * long.
+ *
+ * For some hash functions, the <code>sph_XXX_addbits_and_close()</code>
+ * function can be used instead of <code>sph_XXX_close()</code>. This
+ * function can take a few extra <strong>bits</strong> to be added at
+ * the end of the input message. This allows hashing messages with a
+ * bit length which is not a multiple of 8. The extra bits are provided
+ * as an unsigned integer value, and a bit count. The bit count must be
+ * between 0 and 7, inclusive. The extra bits are provided as bits 7 to
+ * 0 (bits of numerical value 128, 64, 32... downto 0), in that order.
+ * For instance, to add three bits of value 1, 1 and 0, the unsigned
+ * integer will have value 192 (1*128 + 1*64 + 0*32) and the bit count
+ * will be 3.
+ *
+ * The <code>SPH_SIZE_XXX</code> macro is defined for each hash function;
+ * it evaluates to the function output size, expressed in bits. For instance,
+ * <code>SPH_SIZE_sha1</code> evaluates to <code>160</code>.
+ *
+ * When closed, the context is automatically reinitialized and can be
+ * immediately used for another computation. It is not necessary to call
+ * <code>sph_XXX_init()</code> after a close. Note that
+ * <code>sph_XXX_init()</code> can still be called to "reset" a context,
+ * i.e. forget previously input data, and get back to the initial state.
+ *
+ * @subsection alignment Data alignment
+ *
+ * "Alignment" is a property of data, which is said to be "properly
+ * aligned" when its emplacement in memory is such that the data can
+ * be optimally read by full words. This depends on the type of access;
+ * basically, some hash functions will read data by 32-bit or 64-bit
+ * words. <code>sphlib</code> does not mandate such alignment for input
+ * data, but using aligned data can substantially improve performance.
+ *
+ * As a rule, it is best to input data by chunks whose length (in bytes)
+ * is a multiple of eight, and which begins at "generally aligned"
+ * addresses, such as the base address returned by a call to
+ * <code>malloc()</code>.
+ *
+ * @section functions Implemented functions
+ *
+ * We give here the list of implemented functions. They are grouped by
+ * family; to each family corresponds a specific header file. Each
+ * individual function has its associated "short name". Please refer to
+ * the documentation for that header file to get details on the hash
+ * function denomination and provenance.
+ *
+ * Note: the functions marked with a '(64)' in the list below are
+ * available only if the C compiler provides an integer type of length
+ * 64 bits or more. Such a type is mandatory in the latest C standard
+ * (ISO 9899:1999, aka "C99") and is present in several older compilers
+ * as well, so chances are that such a type is available.
+ *
+ * - HAVAL family: file <code>sph_haval.h</code>
+ * - HAVAL-128/3 (128-bit, 3 passes): short name: <code>haval128_3</code>
+ * - HAVAL-128/4 (128-bit, 4 passes): short name: <code>haval128_4</code>
+ * - HAVAL-128/5 (128-bit, 5 passes): short name: <code>haval128_5</code>
+ * - HAVAL-160/3 (160-bit, 3 passes): short name: <code>haval160_3</code>
+ * - HAVAL-160/4 (160-bit, 4 passes): short name: <code>haval160_4</code>
+ * - HAVAL-160/5 (160-bit, 5 passes): short name: <code>haval160_5</code>
+ * - HAVAL-192/3 (192-bit, 3 passes): short name: <code>haval192_3</code>
+ * - HAVAL-192/4 (192-bit, 4 passes): short name: <code>haval192_4</code>
+ * - HAVAL-192/5 (192-bit, 5 passes): short name: <code>haval192_5</code>
+ * - HAVAL-224/3 (224-bit, 3 passes): short name: <code>haval224_3</code>
+ * - HAVAL-224/4 (224-bit, 4 passes): short name: <code>haval224_4</code>
+ * - HAVAL-224/5 (224-bit, 5 passes): short name: <code>haval224_5</code>
+ * - HAVAL-256/3 (256-bit, 3 passes): short name: <code>haval256_3</code>
+ * - HAVAL-256/4 (256-bit, 4 passes): short name: <code>haval256_4</code>
+ * - HAVAL-256/5 (256-bit, 5 passes): short name: <code>haval256_5</code>
+ * - MD2: file <code>sph_md2.h</code>, short name: <code>md2</code>
+ * - MD4: file <code>sph_md4.h</code>, short name: <code>md4</code>
+ * - MD5: file <code>sph_md5.h</code>, short name: <code>md5</code>
+ * - PANAMA: file <code>sph_panama.h</code>, short name: <code>panama</code>
+ * - RadioGatun family: file <code>sph_radiogatun.h</code>
+ * - RadioGatun[32]: short name: <code>radiogatun32</code>
+ * - RadioGatun[64]: short name: <code>radiogatun64</code> (64)
+ * - RIPEMD family: file <code>sph_ripemd.h</code>
+ * - RIPEMD: short name: <code>ripemd</code>
+ * - RIPEMD-128: short name: <code>ripemd128</code>
+ * - RIPEMD-160: short name: <code>ripemd160</code>
+ * - SHA-0: file <code>sph_sha0.h</code>, short name: <code>sha0</code>
+ * - SHA-1: file <code>sph_sha1.h</code>, short name: <code>sha1</code>
+ * - SHA-2 family, 32-bit hashes: file <code>sph_sha2.h</code>
+ * - SHA-224: short name: <code>sha224</code>
+ * - SHA-256: short name: <code>sha256</code>
+ * - SHA-384: short name: <code>sha384</code> (64)
+ * - SHA-512: short name: <code>sha512</code> (64)
+ * - Tiger family: file <code>sph_tiger.h</code>
+ * - Tiger: short name: <code>tiger</code> (64)
+ * - Tiger2: short name: <code>tiger2</code> (64)
+ * - WHIRLPOOL family: file <code>sph_whirlpool.h</code>
+ * - WHIRLPOOL-0: short name: <code>whirlpool0</code> (64)
+ * - WHIRLPOOL-1: short name: <code>whirlpool1</code> (64)
+ * - WHIRLPOOL: short name: <code>whirlpool</code> (64)
+ *
+ * The fourteen second-round SHA-3 candidates are also implemented;
+ * when applicable, the implementations follow the "final" specifications
+ * as published for the third round of the SHA-3 competition (BLAKE,
+ * Groestl, JH, Keccak and Skein have been tweaked for third round).
+ *
+ * - BLAKE family: file <code>sph_blake.h</code>
+ * - BLAKE-224: short name: <code>blake224</code>
+ * - BLAKE-256: short name: <code>blake256</code>
+ * - BLAKE-384: short name: <code>blake384</code>
+ * - BLAKE-512: short name: <code>blake512</code>
+ * - BMW (Blue Midnight Wish) family: file <code>sph_bmw.h</code>
+ * - BMW-224: short name: <code>bmw224</code>
+ * - BMW-256: short name: <code>bmw256</code>
+ * - BMW-384: short name: <code>bmw384</code> (64)
+ * - BMW-512: short name: <code>bmw512</code> (64)
+ * - CubeHash family: file <code>sph_cubehash.h</code> (specified as
+ * CubeHash16/32 in the CubeHash specification)
+ * - CubeHash-224: short name: <code>cubehash224</code>
+ * - CubeHash-256: short name: <code>cubehash256</code>
+ * - CubeHash-384: short name: <code>cubehash384</code>
+ * - CubeHash-512: short name: <code>cubehash512</code>
+ * - ECHO family: file <code>sph_echo.h</code>
+ * - ECHO-224: short name: <code>echo224</code>
+ * - ECHO-256: short name: <code>echo256</code>
+ * - ECHO-384: short name: <code>echo384</code>
+ * - ECHO-512: short name: <code>echo512</code>
+ * - Fugue family: file <code>sph_fugue.h</code>
+ * - Fugue-224: short name: <code>fugue224</code>
+ * - Fugue-256: short name: <code>fugue256</code>
+ * - Fugue-384: short name: <code>fugue384</code>
+ * - Fugue-512: short name: <code>fugue512</code>
+ * - Groestl family: file <code>sph_groestl.h</code>
+ * - Groestl-224: short name: <code>groestl224</code>
+ * - Groestl-256: short name: <code>groestl256</code>
+ * - Groestl-384: short name: <code>groestl384</code>
+ * - Groestl-512: short name: <code>groestl512</code>
+ * - Hamsi family: file <code>sph_hamsi.h</code>
+ * - Hamsi-224: short name: <code>hamsi224</code>
+ * - Hamsi-256: short name: <code>hamsi256</code>
+ * - Hamsi-384: short name: <code>hamsi384</code>
+ * - Hamsi-512: short name: <code>hamsi512</code>
+ * - JH family: file <code>sph_jh.h</code>
+ * - JH-224: short name: <code>jh224</code>
+ * - JH-256: short name: <code>jh256</code>
+ * - JH-384: short name: <code>jh384</code>
+ * - JH-512: short name: <code>jh512</code>
+ * - Keccak family: file <code>sph_keccak.h</code>
+ * - Keccak-224: short name: <code>keccak224</code>
+ * - Keccak-256: short name: <code>keccak256</code>
+ * - Keccak-384: short name: <code>keccak384</code>
+ * - Keccak-512: short name: <code>keccak512</code>
+ * - Luffa family: file <code>sph_luffa.h</code>
+ * - Luffa-224: short name: <code>luffa224</code>
+ * - Luffa-256: short name: <code>luffa256</code>
+ * - Luffa-384: short name: <code>luffa384</code>
+ * - Luffa-512: short name: <code>luffa512</code>
+ * - Shabal family: file <code>sph_shabal.h</code>
+ * - Shabal-192: short name: <code>shabal192</code>
+ * - Shabal-224: short name: <code>shabal224</code>
+ * - Shabal-256: short name: <code>shabal256</code>
+ * - Shabal-384: short name: <code>shabal384</code>
+ * - Shabal-512: short name: <code>shabal512</code>
+ * - SHAvite-3 family: file <code>sph_shavite.h</code>
+ * - SHAvite-224 (nominally "SHAvite-3 with 224-bit output"):
+ * short name: <code>shabal224</code>
+ * - SHAvite-256 (nominally "SHAvite-3 with 256-bit output"):
+ * short name: <code>shabal256</code>
+ * - SHAvite-384 (nominally "SHAvite-3 with 384-bit output"):
+ * short name: <code>shabal384</code>
+ * - SHAvite-512 (nominally "SHAvite-3 with 512-bit output"):
+ * short name: <code>shabal512</code>
+ * - SIMD family: file <code>sph_simd.h</code>
+ * - SIMD-224: short name: <code>simd224</code>
+ * - SIMD-256: short name: <code>simd256</code>
+ * - SIMD-384: short name: <code>simd384</code>
+ * - SIMD-512: short name: <code>simd512</code>
+ * - Skein family: file <code>sph_skein.h</code>
+ * - Skein-224 (nominally specified as Skein-512-224): short name:
+ * <code>skein224</code> (64)
+ * - Skein-256 (nominally specified as Skein-512-256): short name:
+ * <code>skein256</code> (64)
+ * - Skein-384 (nominally specified as Skein-512-384): short name:
+ * <code>skein384</code> (64)
+ * - Skein-512 (nominally specified as Skein-512-512): short name:
+ * <code>skein512</code> (64)
+ *
+ * For the second-round SHA-3 candidates, the functions are as specified
+ * for round 2, i.e. with the "tweaks" that some candidates added
+ * between round 1 and round 2. Also, some of the submitted packages for
+ * round 2 contained errors, in the specification, reference code, or
+ * both. <code>sphlib</code> implements the corrected versions.
+ */
+
+/** @hideinitializer
+ * Unsigned integer type whose length is at least 32 bits; on most
+ * architectures, it will have a width of exactly 32 bits. Unsigned C
+ * types implement arithmetics modulo a power of 2; use the
+ * <code>SPH_T32()</code> macro to ensure that the value is truncated
+ * to exactly 32 bits. Unless otherwise specified, all macros and
+ * functions which accept <code>sph_u32</code> values assume that these
+ * values fit on 32 bits, i.e. do not exceed 2^32-1, even on architectures
+ * where <code>sph_u32</code> is larger than that.
+ */
+typedef __arch_dependant__ sph_u32;
+
+/** @hideinitializer
+ * Signed integer type corresponding to <code>sph_u32</code>; it has
+ * width 32 bits or more.
+ */
+typedef __arch_dependant__ sph_s32;
+
+/** @hideinitializer
+ * Unsigned integer type whose length is at least 64 bits; on most
+ * architectures which feature such a type, it will have a width of
+ * exactly 64 bits. C99-compliant platform will have this type; it
+ * is also defined when the GNU compiler (gcc) is used, and on
+ * platforms where <code>unsigned long</code> is large enough. If this
+ * type is not available, then some hash functions which depends on
+ * a 64-bit type will not be available (most notably SHA-384, SHA-512,
+ * Tiger and WHIRLPOOL).
+ */
+typedef __arch_dependant__ sph_u64;
+
+/** @hideinitializer
+ * Signed integer type corresponding to <code>sph_u64</code>; it has
+ * width 64 bits or more.
+ */
+typedef __arch_dependant__ sph_s64;
+
+/**
+ * This macro expands the token <code>x</code> into a suitable
+ * constant expression of type <code>sph_u32</code>. Depending on
+ * how this type is defined, a suffix such as <code>UL</code> may
+ * be appended to the argument.
+ *
+ * @param x the token to expand into a suitable constant expression
+ */
+#define SPH_C32(x)
+
+/**
+ * Truncate a 32-bit value to exactly 32 bits. On most systems, this is
+ * a no-op, recognized as such by the compiler.
+ *
+ * @param x the value to truncate (of type <code>sph_u32</code>)
+ */
+#define SPH_T32(x)
+
+/**
+ * Rotate a 32-bit value by a number of bits to the left. The rotate
+ * count must reside between 1 and 31. This macro assumes that its
+ * first argument fits in 32 bits (no extra bit allowed on machines where
+ * <code>sph_u32</code> is wider); both arguments may be evaluated
+ * several times.
+ *
+ * @param x the value to rotate (of type <code>sph_u32</code>)
+ * @param n the rotation count (between 1 and 31, inclusive)
+ */
+#define SPH_ROTL32(x, n)
+
+/**
+ * Rotate a 32-bit value by a number of bits to the left. The rotate
+ * count must reside between 1 and 31. This macro assumes that its
+ * first argument fits in 32 bits (no extra bit allowed on machines where
+ * <code>sph_u32</code> is wider); both arguments may be evaluated
+ * several times.
+ *
+ * @param x the value to rotate (of type <code>sph_u32</code>)
+ * @param n the rotation count (between 1 and 31, inclusive)
+ */
+#define SPH_ROTR32(x, n)
+
+/**
+ * This macro is defined on systems for which a 64-bit type has been
+ * detected, and is used for <code>sph_u64</code>.
+ */
+#define SPH_64
+
+/**
+ * This macro is defined on systems for the "native" integer size is
+ * 64 bits (64-bit values fit in one register).
+ */
+#define SPH_64_TRUE
+
+/**
+ * This macro expands the token <code>x</code> into a suitable
+ * constant expression of type <code>sph_u64</code>. Depending on
+ * how this type is defined, a suffix such as <code>ULL</code> may
+ * be appended to the argument. This macro is defined only if a
+ * 64-bit type was detected and used for <code>sph_u64</code>.
+ *
+ * @param x the token to expand into a suitable constant expression
+ */
+#define SPH_C64(x)
+
+/**
+ * Truncate a 64-bit value to exactly 64 bits. On most systems, this is
+ * a no-op, recognized as such by the compiler. This macro is defined only
+ * if a 64-bit type was detected and used for <code>sph_u64</code>.
+ *
+ * @param x the value to truncate (of type <code>sph_u64</code>)
+ */
+#define SPH_T64(x)
+
+/**
+ * Rotate a 64-bit value by a number of bits to the left. The rotate
+ * count must reside between 1 and 63. This macro assumes that its
+ * first argument fits in 64 bits (no extra bit allowed on machines where
+ * <code>sph_u64</code> is wider); both arguments may be evaluated
+ * several times. This macro is defined only if a 64-bit type was detected
+ * and used for <code>sph_u64</code>.
+ *
+ * @param x the value to rotate (of type <code>sph_u64</code>)
+ * @param n the rotation count (between 1 and 63, inclusive)
+ */
+#define SPH_ROTL64(x, n)
+
+/**
+ * Rotate a 64-bit value by a number of bits to the left. The rotate
+ * count must reside between 1 and 63. This macro assumes that its
+ * first argument fits in 64 bits (no extra bit allowed on machines where
+ * <code>sph_u64</code> is wider); both arguments may be evaluated
+ * several times. This macro is defined only if a 64-bit type was detected
+ * and used for <code>sph_u64</code>.
+ *
+ * @param x the value to rotate (of type <code>sph_u64</code>)
+ * @param n the rotation count (between 1 and 63, inclusive)
+ */
+#define SPH_ROTR64(x, n)
+
+/**
+ * This macro evaluates to <code>inline</code> or an equivalent construction,
+ * if available on the compilation platform, or to nothing otherwise. This
+ * is used to declare inline functions, for which the compiler should
+ * endeavour to include the code directly in the caller. Inline functions
+ * are typically defined in header files as replacement for macros.
+ */
+#define SPH_INLINE
+
+/**
+ * This macro is defined if the platform has been detected as using
+ * little-endian convention. This implies that the <code>sph_u32</code>
+ * type (and the <code>sph_u64</code> type also, if it is defined) has
+ * an exact width (i.e. exactly 32-bit, respectively 64-bit).
+ */
+#define SPH_LITTLE_ENDIAN
+
+/**
+ * This macro is defined if the platform has been detected as using
+ * big-endian convention. This implies that the <code>sph_u32</code>
+ * type (and the <code>sph_u64</code> type also, if it is defined) has
+ * an exact width (i.e. exactly 32-bit, respectively 64-bit).
+ */
+#define SPH_BIG_ENDIAN
+
+/**
+ * This macro is defined if 32-bit words (and 64-bit words, if defined)
+ * can be read from and written to memory efficiently in little-endian
+ * convention. This is the case for little-endian platforms, and also
+ * for the big-endian platforms which have special little-endian access
+ * opcodes (e.g. Ultrasparc).
+ */
+#define SPH_LITTLE_FAST
+
+/**
+ * This macro is defined if 32-bit words (and 64-bit words, if defined)
+ * can be read from and written to memory efficiently in big-endian
+ * convention. This is the case for little-endian platforms, and also
+ * for the little-endian platforms which have special big-endian access
+ * opcodes.
+ */
+#define SPH_BIG_FAST
+
+/**
+ * On some platforms, this macro is defined to an unsigned integer type
+ * into which pointer values may be cast. The resulting value can then
+ * be tested for being a multiple of 2, 4 or 8, indicating an aligned
+ * pointer for, respectively, 16-bit, 32-bit or 64-bit memory accesses.
+ */
+#define SPH_UPTR
+
+/**
+ * When defined, this macro indicates that unaligned memory accesses
+ * are possible with only a minor penalty, and thus should be prefered
+ * over strategies which first copy data to an aligned buffer.
+ */
+#define SPH_UNALIGNED
+
+/**
+ * Byte-swap a 32-bit word (i.e. <code>0x12345678</code> becomes
+ * <code>0x78563412</code>). This is an inline function which resorts
+ * to inline assembly on some platforms, for better performance.
+ *
+ * @param x the 32-bit value to byte-swap
+ * @return the byte-swapped value
+ */
+static inline sph_u32 sph_bswap32(sph_u32 x);
+
+/**
+ * Byte-swap a 64-bit word. This is an inline function which resorts
+ * to inline assembly on some platforms, for better performance. This
+ * function is defined only if a suitable 64-bit type was found for
+ * <code>sph_u64</code>
+ *
+ * @param x the 64-bit value to byte-swap
+ * @return the byte-swapped value
+ */
+static inline sph_u64 sph_bswap64(sph_u64 x);
+
+/**
+ * Decode a 16-bit unsigned value from memory, in little-endian convention
+ * (least significant byte comes first).
+ *
+ * @param src the source address
+ * @return the decoded value
+ */
+static inline unsigned sph_dec16le(const void *src);
+
+/**
+ * Encode a 16-bit unsigned value into memory, in little-endian convention
+ * (least significant byte comes first).
+ *
+ * @param dst the destination buffer
+ * @param val the value to encode
+ */
+static inline void sph_enc16le(void *dst, unsigned val);
+
+/**
+ * Decode a 16-bit unsigned value from memory, in big-endian convention
+ * (most significant byte comes first).
+ *
+ * @param src the source address
+ * @return the decoded value
+ */
+static inline unsigned sph_dec16be(const void *src);
+
+/**
+ * Encode a 16-bit unsigned value into memory, in big-endian convention
+ * (most significant byte comes first).
+ *
+ * @param dst the destination buffer
+ * @param val the value to encode
+ */
+static inline void sph_enc16be(void *dst, unsigned val);
+
+/**
+ * Decode a 32-bit unsigned value from memory, in little-endian convention
+ * (least significant byte comes first).
+ *
+ * @param src the source address
+ * @return the decoded value
+ */
+static inline sph_u32 sph_dec32le(const void *src);
+
+/**
+ * Decode a 32-bit unsigned value from memory, in little-endian convention
+ * (least significant byte comes first). This function assumes that the
+ * source address is suitably aligned for a direct access, if the platform
+ * supports such things; it can thus be marginally faster than the generic
+ * <code>sph_dec32le()</code> function.
+ *
+ * @param src the source address
+ * @return the decoded value
+ */
+static inline sph_u32 sph_dec32le_aligned(const void *src);
+
+/**
+ * Encode a 32-bit unsigned value into memory, in little-endian convention
+ * (least significant byte comes first).
+ *
+ * @param dst the destination buffer
+ * @param val the value to encode
+ */
+static inline void sph_enc32le(void *dst, sph_u32 val);
+
+/**
+ * Encode a 32-bit unsigned value into memory, in little-endian convention
+ * (least significant byte comes first). This function assumes that the
+ * destination address is suitably aligned for a direct access, if the
+ * platform supports such things; it can thus be marginally faster than
+ * the generic <code>sph_enc32le()</code> function.
+ *
+ * @param dst the destination buffer
+ * @param val the value to encode
+ */
+static inline void sph_enc32le_aligned(void *dst, sph_u32 val);
+
+/**
+ * Decode a 32-bit unsigned value from memory, in big-endian convention
+ * (most significant byte comes first).
+ *
+ * @param src the source address
+ * @return the decoded value
+ */
+static inline sph_u32 sph_dec32be(const void *src);
+
+/**
+ * Decode a 32-bit unsigned value from memory, in big-endian convention
+ * (most significant byte comes first). This function assumes that the
+ * source address is suitably aligned for a direct access, if the platform
+ * supports such things; it can thus be marginally faster than the generic
+ * <code>sph_dec32be()</code> function.
+ *
+ * @param src the source address
+ * @return the decoded value
+ */
+static inline sph_u32 sph_dec32be_aligned(const void *src);
+
+/**
+ * Encode a 32-bit unsigned value into memory, in big-endian convention
+ * (most significant byte comes first).
+ *
+ * @param dst the destination buffer
+ * @param val the value to encode
+ */
+static inline void sph_enc32be(void *dst, sph_u32 val);
+
+/**
+ * Encode a 32-bit unsigned value into memory, in big-endian convention
+ * (most significant byte comes first). This function assumes that the
+ * destination address is suitably aligned for a direct access, if the
+ * platform supports such things; it can thus be marginally faster than
+ * the generic <code>sph_enc32be()</code> function.
+ *
+ * @param dst the destination buffer
+ * @param val the value to encode
+ */
+static inline void sph_enc32be_aligned(void *dst, sph_u32 val);
+
+/**
+ * Decode a 64-bit unsigned value from memory, in little-endian convention
+ * (least significant byte comes first). This function is defined only
+ * if a suitable 64-bit type was detected and used for <code>sph_u64</code>.
+ *
+ * @param src the source address
+ * @return the decoded value
+ */
+static inline sph_u64 sph_dec64le(const void *src);
+
+/**
+ * Decode a 64-bit unsigned value from memory, in little-endian convention
+ * (least significant byte comes first). This function assumes that the
+ * source address is suitably aligned for a direct access, if the platform
+ * supports such things; it can thus be marginally faster than the generic
+ * <code>sph_dec64le()</code> function. This function is defined only
+ * if a suitable 64-bit type was detected and used for <code>sph_u64</code>.
+ *
+ * @param src the source address
+ * @return the decoded value
+ */
+static inline sph_u64 sph_dec64le_aligned(const void *src);
+
+/**
+ * Encode a 64-bit unsigned value into memory, in little-endian convention
+ * (least significant byte comes first). This function is defined only
+ * if a suitable 64-bit type was detected and used for <code>sph_u64</code>.
+ *
+ * @param dst the destination buffer
+ * @param val the value to encode
+ */
+static inline void sph_enc64le(void *dst, sph_u64 val);
+
+/**
+ * Encode a 64-bit unsigned value into memory, in little-endian convention
+ * (least significant byte comes first). This function assumes that the
+ * destination address is suitably aligned for a direct access, if the
+ * platform supports such things; it can thus be marginally faster than
+ * the generic <code>sph_enc64le()</code> function. This function is defined
+ * only if a suitable 64-bit type was detected and used for
+ * <code>sph_u64</code>.
+ *
+ * @param dst the destination buffer
+ * @param val the value to encode
+ */
+static inline void sph_enc64le_aligned(void *dst, sph_u64 val);
+
+/**
+ * Decode a 64-bit unsigned value from memory, in big-endian convention
+ * (most significant byte comes first). This function is defined only
+ * if a suitable 64-bit type was detected and used for <code>sph_u64</code>.
+ *
+ * @param src the source address
+ * @return the decoded value
+ */
+static inline sph_u64 sph_dec64be(const void *src);
+
+/**
+ * Decode a 64-bit unsigned value from memory, in big-endian convention
+ * (most significant byte comes first). This function assumes that the
+ * source address is suitably aligned for a direct access, if the platform
+ * supports such things; it can thus be marginally faster than the generic
+ * <code>sph_dec64be()</code> function. This function is defined only
+ * if a suitable 64-bit type was detected and used for <code>sph_u64</code>.
+ *
+ * @param src the source address
+ * @return the decoded value
+ */
+static inline sph_u64 sph_dec64be_aligned(const void *src);
+
+/**
+ * Encode a 64-bit unsigned value into memory, in big-endian convention
+ * (most significant byte comes first). This function is defined only
+ * if a suitable 64-bit type was detected and used for <code>sph_u64</code>.
+ *
+ * @param dst the destination buffer
+ * @param val the value to encode
+ */
+static inline void sph_enc64be(void *dst, sph_u64 val);
+
+/**
+ * Encode a 64-bit unsigned value into memory, in big-endian convention
+ * (most significant byte comes first). This function assumes that the
+ * destination address is suitably aligned for a direct access, if the
+ * platform supports such things; it can thus be marginally faster than
+ * the generic <code>sph_enc64be()</code> function. This function is defined
+ * only if a suitable 64-bit type was detected and used for
+ * <code>sph_u64</code>.
+ *
+ * @param dst the destination buffer
+ * @param val the value to encode
+ */
+static inline void sph_enc64be_aligned(void *dst, sph_u64 val);
+
+#endif
+
+/* ============== END documentation block for Doxygen ============= */
+
+#ifndef DOXYGEN_IGNORE
+
+/*
+ * We want to define the types "sph_u32" and "sph_u64" which hold
+ * unsigned values of at least, respectively, 32 and 64 bits. These
+ * tests should select appropriate types for most platforms. The
+ * macro "SPH_64" is defined if the 64-bit is supported.
+ */
+
+#undef SPH_64
+#undef SPH_64_TRUE
+
+#if defined __STDC__ && __STDC_VERSION__ >= 199901L
+
+/*
+ * On C99 implementations, we can use <stdint.h> to get an exact 64-bit
+ * type, if any, or otherwise use a wider type (which must exist, for
+ * C99 conformance).
+ */
+
+#include <stdint.h>
+
+#ifdef UINT32_MAX
+typedef uint32_t sph_u32;
+typedef int32_t sph_s32;
+#else
+typedef uint_fast32_t sph_u32;
+typedef int_fast32_t sph_s32;
+#endif
+#if !SPH_NO_64
+#ifdef UINT64_MAX
+typedef uint64_t sph_u64;
+typedef int64_t sph_s64;
+#else
+typedef uint_fast64_t sph_u64;
+typedef int_fast64_t sph_s64;
+#endif
+#endif
+
+#define SPH_C32(x) ((sph_u32)(x))
+#if !SPH_NO_64
+#define SPH_C64(x) ((sph_u64)(x))
+#define SPH_64 1
+#endif
+
+#else
+
+/*
+ * On non-C99 systems, we use "unsigned int" if it is wide enough,
+ * "unsigned long" otherwise. This supports all "reasonable" architectures.
+ * We have to be cautious: pre-C99 preprocessors handle constants
+ * differently in '#if' expressions. Hence the shifts to test UINT_MAX.
+ */
+
+#if ((UINT_MAX >> 11) >> 11) >= 0x3FF
+
+typedef unsigned int sph_u32;
+typedef int sph_s32;
+
+#define SPH_C32(x) ((sph_u32)(x ## U))
+
+#else
+
+typedef unsigned long sph_u32;
+typedef long sph_s32;
+
+#define SPH_C32(x) ((sph_u32)(x ## UL))
+
+#endif
+
+#if !SPH_NO_64
+
+/*
+ * We want a 64-bit type. We use "unsigned long" if it is wide enough (as
+ * is common on 64-bit architectures such as AMD64, Alpha or Sparcv9),
+ * "unsigned long long" otherwise, if available. We use ULLONG_MAX to
+ * test whether "unsigned long long" is available; we also know that
+ * gcc features this type, even if the libc header do not know it.
+ */
+
+#if ((ULONG_MAX >> 31) >> 31) >= 3
+
+typedef unsigned long sph_u64;
+typedef long sph_s64;
+
+#define SPH_C64(x) ((sph_u64)(x ## UL))
+
+#define SPH_64 1
+
+#elif ((ULLONG_MAX >> 31) >> 31) >= 3 || defined __GNUC__
+
+typedef unsigned long long sph_u64;
+typedef long long sph_s64;
+
+#define SPH_C64(x) ((sph_u64)(x ## ULL))
+
+#define SPH_64 1
+
+#else
+
+/*
+ * No 64-bit type...
+ */
+
+#endif
+
+#endif
+
+#endif
+
+/*
+ * If the "unsigned long" type has length 64 bits or more, then this is
+ * a "true" 64-bit architectures. This is also true with Visual C on
+ * amd64, even though the "long" type is limited to 32 bits.
+ */
+#if SPH_64 && (((ULONG_MAX >> 31) >> 31) >= 3 || defined _M_X64)
+#define SPH_64_TRUE 1
+#endif
+
+/*
+ * Implementation note: some processors have specific opcodes to perform
+ * a rotation. Recent versions of gcc recognize the expression above and
+ * use the relevant opcodes, when appropriate.
+ */
+
+#define SPH_T32(x) ((x) & SPH_C32(0xFFFFFFFF))
+#define SPH_ROTL32(x, n) SPH_T32(((x) << (n)) | ((x) >> (32 - (n))))
+#define SPH_ROTR32(x, n) SPH_ROTL32(x, (32 - (n)))
+
+#if SPH_64
+
+#define SPH_T64(x) ((x) & SPH_C64(0xFFFFFFFFFFFFFFFF))
+#define SPH_ROTL64(x, n) SPH_T64(((x) << (n)) | ((x) >> (64 - (n))))
+#define SPH_ROTR64(x, n) SPH_ROTL64(x, (64 - (n)))
+
+#endif
+
+#ifndef DOXYGEN_IGNORE
+/*
+ * Define SPH_INLINE to be an "inline" qualifier, if available. We define
+ * some small macro-like functions which benefit greatly from being inlined.
+ */
+#if (defined __STDC__ && __STDC_VERSION__ >= 199901L) || defined __GNUC__
+#define SPH_INLINE inline
+#elif defined _MSC_VER
+#define SPH_INLINE __inline
+#else
+#define SPH_INLINE
+#endif
+#endif
+
+/*
+ * We define some macros which qualify the architecture. These macros
+ * may be explicit set externally (e.g. as compiler parameters). The
+ * code below sets those macros if they are not already defined.
+ *
+ * Most macros are boolean, thus evaluate to either zero or non-zero.
+ * The SPH_UPTR macro is special, in that it evaluates to a C type,
+ * or is not defined.
+ *
+ * SPH_UPTR if defined: unsigned type to cast pointers into
+ *
+ * SPH_UNALIGNED non-zero if unaligned accesses are efficient
+ * SPH_LITTLE_ENDIAN non-zero if architecture is known to be little-endian
+ * SPH_BIG_ENDIAN non-zero if architecture is known to be big-endian
+ * SPH_LITTLE_FAST non-zero if little-endian decoding is fast
+ * SPH_BIG_FAST non-zero if big-endian decoding is fast
+ *
+ * If SPH_UPTR is defined, then encoding and decoding of 32-bit and 64-bit
+ * values will try to be "smart". Either SPH_LITTLE_ENDIAN or SPH_BIG_ENDIAN
+ * _must_ be non-zero in those situations. The 32-bit and 64-bit types
+ * _must_ also have an exact width.
+ *
+ * SPH_SPARCV9_GCC_32 UltraSPARC-compatible with gcc, 32-bit mode
+ * SPH_SPARCV9_GCC_64 UltraSPARC-compatible with gcc, 64-bit mode
+ * SPH_SPARCV9_GCC UltraSPARC-compatible with gcc
+ * SPH_I386_GCC x86-compatible (32-bit) with gcc
+ * SPH_I386_MSVC x86-compatible (32-bit) with Microsoft Visual C
+ * SPH_AMD64_GCC x86-compatible (64-bit) with gcc
+ * SPH_AMD64_MSVC x86-compatible (64-bit) with Microsoft Visual C
+ * SPH_PPC32_GCC PowerPC, 32-bit, with gcc
+ * SPH_PPC64_GCC PowerPC, 64-bit, with gcc
+ *
+ * TODO: enhance automatic detection, for more architectures and compilers.
+ * Endianness is the most important. SPH_UNALIGNED and SPH_UPTR help with
+ * some very fast functions (e.g. MD4) when using unaligned input data.
+ * The CPU-specific-with-GCC macros are useful only for inline assembly,
+ * normally restrained to this header file.
+ */
+
+/*
+ * 32-bit x86, aka "i386 compatible".
+ */
+#if defined __i386__ || defined _M_IX86
+
+#define SPH_DETECT_UNALIGNED 1
+#define SPH_DETECT_LITTLE_ENDIAN 1
+#define SPH_DETECT_UPTR sph_u32
+#ifdef __GNUC__
+#define SPH_DETECT_I386_GCC 1
+#endif
+#ifdef _MSC_VER
+#define SPH_DETECT_I386_MSVC 1
+#endif
+
+/*
+ * 64-bit x86, hereafter known as "amd64".
+ */
+#elif defined __x86_64 || defined _M_X64
+
+#define SPH_DETECT_UNALIGNED 1
+#define SPH_DETECT_LITTLE_ENDIAN 1
+#define SPH_DETECT_UPTR sph_u64
+#ifdef __GNUC__
+#define SPH_DETECT_AMD64_GCC 1
+#endif
+#ifdef _MSC_VER
+#define SPH_DETECT_AMD64_MSVC 1
+#endif
+
+/*
+ * 64-bit Sparc architecture (implies v9).
+ */
+#elif ((defined __sparc__ || defined __sparc) && defined __arch64__) \
+ || defined __sparcv9
+
+#define SPH_DETECT_BIG_ENDIAN 1
+#define SPH_DETECT_UPTR sph_u64
+#ifdef __GNUC__
+#define SPH_DETECT_SPARCV9_GCC_64 1
+#define SPH_DETECT_LITTLE_FAST 1
+#endif
+
+/*
+ * 32-bit Sparc.
+ */
+#elif (defined __sparc__ || defined __sparc) \
+ && !(defined __sparcv9 || defined __arch64__)
+
+#define SPH_DETECT_BIG_ENDIAN 1
+#define SPH_DETECT_UPTR sph_u32
+#if defined __GNUC__ && defined __sparc_v9__
+#define SPH_DETECT_SPARCV9_GCC_32 1
+#define SPH_DETECT_LITTLE_FAST 1
+#endif
+
+/*
+ * ARM, little-endian.
+ */
+#elif defined __arm__ && __ARMEL__
+
+#define SPH_DETECT_LITTLE_ENDIAN 1
+
+/*
+ * MIPS, little-endian.
+ */
+#elif MIPSEL || _MIPSEL || __MIPSEL || __MIPSEL__
+
+#define SPH_DETECT_LITTLE_ENDIAN 1
+
+/*
+ * MIPS, big-endian.
+ */
+#elif MIPSEB || _MIPSEB || __MIPSEB || __MIPSEB__
+
+#define SPH_DETECT_BIG_ENDIAN 1
+
+/*
+ * PowerPC.
+ */
+#elif defined __powerpc__ || defined __POWERPC__ || defined __ppc__ \
+ || defined _ARCH_PPC
+
+/*
+ * Note: we do not declare cross-endian access to be "fast": even if
+ * using inline assembly, implementation should still assume that
+ * keeping the decoded word in a temporary is faster than decoding
+ * it again.
+ */
+#if defined __GNUC__
+#if SPH_64_TRUE
+#define SPH_DETECT_PPC64_GCC 1
+#else
+#define SPH_DETECT_PPC32_GCC 1
+#endif
+#endif
+
+#if defined __BIG_ENDIAN__ || defined _BIG_ENDIAN
+#define SPH_DETECT_BIG_ENDIAN 1
+#elif defined __LITTLE_ENDIAN__ || defined _LITTLE_ENDIAN
+#define SPH_DETECT_LITTLE_ENDIAN 1
+#endif
+
+/*
+ * Itanium, 64-bit.
+ */
+#elif defined __ia64 || defined __ia64__ \
+ || defined __itanium__ || defined _M_IA64
+
+#if defined __BIG_ENDIAN__ || defined _BIG_ENDIAN
+#define SPH_DETECT_BIG_ENDIAN 1
+#else
+#define SPH_DETECT_LITTLE_ENDIAN 1
+#endif
+#if defined __LP64__ || defined _LP64
+#define SPH_DETECT_UPTR sph_u64
+#else
+#define SPH_DETECT_UPTR sph_u32
+#endif
+
+#endif
+
+#if defined SPH_DETECT_SPARCV9_GCC_32 || defined SPH_DETECT_SPARCV9_GCC_64
+#define SPH_DETECT_SPARCV9_GCC 1
+#endif
+
+#if defined SPH_DETECT_UNALIGNED && !defined SPH_UNALIGNED
+#define SPH_UNALIGNED SPH_DETECT_UNALIGNED
+#endif
+#if defined SPH_DETECT_UPTR && !defined SPH_UPTR
+#define SPH_UPTR SPH_DETECT_UPTR
+#endif
+#if defined SPH_DETECT_LITTLE_ENDIAN && !defined SPH_LITTLE_ENDIAN
+#define SPH_LITTLE_ENDIAN SPH_DETECT_LITTLE_ENDIAN
+#endif
+#if defined SPH_DETECT_BIG_ENDIAN && !defined SPH_BIG_ENDIAN
+#define SPH_BIG_ENDIAN SPH_DETECT_BIG_ENDIAN
+#endif
+#if defined SPH_DETECT_LITTLE_FAST && !defined SPH_LITTLE_FAST
+#define SPH_LITTLE_FAST SPH_DETECT_LITTLE_FAST
+#endif
+#if defined SPH_DETECT_BIG_FAST && !defined SPH_BIG_FAST
+#define SPH_BIG_FAST SPH_DETECT_BIG_FAST
+#endif
+#if defined SPH_DETECT_SPARCV9_GCC_32 && !defined SPH_SPARCV9_GCC_32
+#define SPH_SPARCV9_GCC_32 SPH_DETECT_SPARCV9_GCC_32
+#endif
+#if defined SPH_DETECT_SPARCV9_GCC_64 && !defined SPH_SPARCV9_GCC_64
+#define SPH_SPARCV9_GCC_64 SPH_DETECT_SPARCV9_GCC_64
+#endif
+#if defined SPH_DETECT_SPARCV9_GCC && !defined SPH_SPARCV9_GCC
+#define SPH_SPARCV9_GCC SPH_DETECT_SPARCV9_GCC
+#endif
+#if defined SPH_DETECT_I386_GCC && !defined SPH_I386_GCC
+#define SPH_I386_GCC SPH_DETECT_I386_GCC
+#endif
+#if defined SPH_DETECT_I386_MSVC && !defined SPH_I386_MSVC
+#define SPH_I386_MSVC SPH_DETECT_I386_MSVC
+#endif
+#if defined SPH_DETECT_AMD64_GCC && !defined SPH_AMD64_GCC
+#define SPH_AMD64_GCC SPH_DETECT_AMD64_GCC
+#endif
+#if defined SPH_DETECT_AMD64_MSVC && !defined SPH_AMD64_MSVC
+#define SPH_AMD64_MSVC SPH_DETECT_AMD64_MSVC
+#endif
+#if defined SPH_DETECT_PPC32_GCC && !defined SPH_PPC32_GCC
+#define SPH_PPC32_GCC SPH_DETECT_PPC32_GCC
+#endif
+#if defined SPH_DETECT_PPC64_GCC && !defined SPH_PPC64_GCC
+#define SPH_PPC64_GCC SPH_DETECT_PPC64_GCC
+#endif
+
+#if SPH_LITTLE_ENDIAN && !defined SPH_LITTLE_FAST
+#define SPH_LITTLE_FAST 1
+#endif
+#if SPH_BIG_ENDIAN && !defined SPH_BIG_FAST
+#define SPH_BIG_FAST 1
+#endif
+
+#if defined SPH_UPTR && !(SPH_LITTLE_ENDIAN || SPH_BIG_ENDIAN)
+#error SPH_UPTR defined, but endianness is not known.
+#endif
+
+#if SPH_I386_GCC && !SPH_NO_ASM
+
+/*
+ * On x86 32-bit, with gcc, we use the bswapl opcode to byte-swap 32-bit
+ * values.
+ */
+
+static SPH_INLINE sph_u32
+sph_bswap32(sph_u32 x)
+{
+ __asm__ __volatile__ ("bswapl %0" : "=r" (x) : "0" (x));
+ return x;
+}
+
+#if SPH_64
+
+static SPH_INLINE sph_u64
+sph_bswap64(sph_u64 x)
+{
+ return ((sph_u64)sph_bswap32((sph_u32)x) << 32)
+ | (sph_u64)sph_bswap32((sph_u32)(x >> 32));
+}
+
+#endif
+
+#elif SPH_AMD64_GCC && !SPH_NO_ASM
+
+/*
+ * On x86 64-bit, with gcc, we use the bswapl opcode to byte-swap 32-bit
+ * and 64-bit values.
+ */
+
+static SPH_INLINE sph_u32
+sph_bswap32(sph_u32 x)
+{
+ __asm__ __volatile__ ("bswapl %0" : "=r" (x) : "0" (x));
+ return x;
+}
+
+#if SPH_64
+
+static SPH_INLINE sph_u64
+sph_bswap64(sph_u64 x)
+{
+ __asm__ __volatile__ ("bswapq %0" : "=r" (x) : "0" (x));
+ return x;
+}
+
+#endif
+
+/*
+ * Disabled code. Apparently, Microsoft Visual C 2005 is smart enough
+ * to generate proper opcodes for endianness swapping with the pure C
+ * implementation below.
+ *
+
+#elif SPH_I386_MSVC && !SPH_NO_ASM
+
+static __inline sph_u32 __declspec(naked) __fastcall
+sph_bswap32(sph_u32 x)
+{
+ __asm {
+ bswap ecx
+ mov eax,ecx
+ ret
+ }
+}
+
+#if SPH_64
+
+static SPH_INLINE sph_u64
+sph_bswap64(sph_u64 x)
+{
+ return ((sph_u64)sph_bswap32((sph_u32)x) << 32)
+ | (sph_u64)sph_bswap32((sph_u32)(x >> 32));
+}
+
+#endif
+
+ *
+ * [end of disabled code]
+ */
+
+#else
+
+static SPH_INLINE sph_u32
+sph_bswap32(sph_u32 x)
+{
+ x = SPH_T32((x << 16) | (x >> 16));
+ x = ((x & SPH_C32(0xFF00FF00)) >> 8)
+ | ((x & SPH_C32(0x00FF00FF)) << 8);
+ return x;
+}
+
+#if SPH_64
+
+/**
+ * Byte-swap a 64-bit value.
+ *
+ * @param x the input value
+ * @return the byte-swapped value
+ */
+static SPH_INLINE sph_u64
+sph_bswap64(sph_u64 x)
+{
+ x = SPH_T64((x << 32) | (x >> 32));
+ x = ((x & SPH_C64(0xFFFF0000FFFF0000)) >> 16)
+ | ((x & SPH_C64(0x0000FFFF0000FFFF)) << 16);
+ x = ((x & SPH_C64(0xFF00FF00FF00FF00)) >> 8)
+ | ((x & SPH_C64(0x00FF00FF00FF00FF)) << 8);
+ return x;
+}
+
+#endif
+
+#endif
+
+#if SPH_SPARCV9_GCC && !SPH_NO_ASM
+
+/*
+ * On UltraSPARC systems, native ordering is big-endian, but it is
+ * possible to perform little-endian read accesses by specifying the
+ * address space 0x88 (ASI_PRIMARY_LITTLE). Basically, either we use
+ * the opcode "lda [%reg]0x88,%dst", where %reg is the register which
+ * contains the source address and %dst is the destination register,
+ * or we use "lda [%reg+imm]%asi,%dst", which uses the %asi register
+ * to get the address space name. The latter format is better since it
+ * combines an addition and the actual access in a single opcode; but
+ * it requires the setting (and subsequent resetting) of %asi, which is
+ * slow. Some operations (i.e. MD5 compression function) combine many
+ * successive little-endian read accesses, which may share the same
+ * %asi setting. The macros below contain the appropriate inline
+ * assembly.
+ */
+
+#define SPH_SPARCV9_SET_ASI \
+ sph_u32 sph_sparcv9_asi; \
+ __asm__ __volatile__ ( \
+ "rd %%asi,%0\n\twr %%g0,0x88,%%asi" : "=r" (sph_sparcv9_asi));
+
+#define SPH_SPARCV9_RESET_ASI \
+ __asm__ __volatile__ ("wr %%g0,%0,%%asi" : : "r" (sph_sparcv9_asi));
+
+#define SPH_SPARCV9_DEC32LE(base, idx) ({ \
+ sph_u32 sph_sparcv9_tmp; \
+ __asm__ __volatile__ ("lda [%1+" #idx "*4]%%asi,%0" \
+ : "=r" (sph_sparcv9_tmp) : "r" (base)); \
+ sph_sparcv9_tmp; \
+ })
+
+#endif
+
+static SPH_INLINE void
+sph_enc16be(void *dst, unsigned val)
+{
+ ((unsigned char *)dst)[0] = (val >> 8);
+ ((unsigned char *)dst)[1] = val;
+}
+
+static SPH_INLINE unsigned
+sph_dec16be(const void *src)
+{
+ return ((unsigned)(((const unsigned char *)src)[0]) << 8)
+ | (unsigned)(((const unsigned char *)src)[1]);
+}
+
+static SPH_INLINE void
+sph_enc16le(void *dst, unsigned val)
+{
+ ((unsigned char *)dst)[0] = val;
+ ((unsigned char *)dst)[1] = val >> 8;
+}
+
+static SPH_INLINE unsigned
+sph_dec16le(const void *src)
+{
+ return (unsigned)(((const unsigned char *)src)[0])
+ | ((unsigned)(((const unsigned char *)src)[1]) << 8);
+}
+
+/**
+ * Encode a 32-bit value into the provided buffer (big endian convention).
+ *
+ * @param dst the destination buffer
+ * @param val the 32-bit value to encode
+ */
+static SPH_INLINE void
+sph_enc32be(void *dst, sph_u32 val)
+{
+#if defined SPH_UPTR
+#if SPH_UNALIGNED
+#if SPH_LITTLE_ENDIAN
+ val = sph_bswap32(val);
+#endif
+ *(sph_u32 *)dst = val;
+#else
+ if (((SPH_UPTR)dst & 3) == 0) {
+#if SPH_LITTLE_ENDIAN
+ val = sph_bswap32(val);
+#endif
+ *(sph_u32 *)dst = val;
+ } else {
+ ((unsigned char *)dst)[0] = (val >> 24);
+ ((unsigned char *)dst)[1] = (val >> 16);
+ ((unsigned char *)dst)[2] = (val >> 8);
+ ((unsigned char *)dst)[3] = val;
+ }
+#endif
+#else
+ ((unsigned char *)dst)[0] = (val >> 24);
+ ((unsigned char *)dst)[1] = (val >> 16);
+ ((unsigned char *)dst)[2] = (val >> 8);
+ ((unsigned char *)dst)[3] = val;
+#endif
+}
+
+/**
+ * Encode a 32-bit value into the provided buffer (big endian convention).
+ * The destination buffer must be properly aligned.
+ *
+ * @param dst the destination buffer (32-bit aligned)
+ * @param val the value to encode
+ */
+static SPH_INLINE void
+sph_enc32be_aligned(void *dst, sph_u32 val)
+{
+#if SPH_LITTLE_ENDIAN
+ *(sph_u32 *)dst = sph_bswap32(val);
+#elif SPH_BIG_ENDIAN
+ *(sph_u32 *)dst = val;
+#else
+ ((unsigned char *)dst)[0] = (val >> 24);
+ ((unsigned char *)dst)[1] = (val >> 16);
+ ((unsigned char *)dst)[2] = (val >> 8);
+ ((unsigned char *)dst)[3] = val;
+#endif
+}
+
+/**
+ * Decode a 32-bit value from the provided buffer (big endian convention).
+ *
+ * @param src the source buffer
+ * @return the decoded value
+ */
+static SPH_INLINE sph_u32
+sph_dec32be(const void *src)
+{
+#if defined SPH_UPTR
+#if SPH_UNALIGNED
+#if SPH_LITTLE_ENDIAN
+ return sph_bswap32(*(const sph_u32 *)src);
+#else
+ return *(const sph_u32 *)src;
+#endif
+#else
+ if (((SPH_UPTR)src & 3) == 0) {
+#if SPH_LITTLE_ENDIAN
+ return sph_bswap32(*(const sph_u32 *)src);
+#else
+ return *(const sph_u32 *)src;
+#endif
+ } else {
+ return ((sph_u32)(((const unsigned char *)src)[0]) << 24)
+ | ((sph_u32)(((const unsigned char *)src)[1]) << 16)
+ | ((sph_u32)(((const unsigned char *)src)[2]) << 8)
+ | (sph_u32)(((const unsigned char *)src)[3]);
+ }
+#endif
+#else
+ return ((sph_u32)(((const unsigned char *)src)[0]) << 24)
+ | ((sph_u32)(((const unsigned char *)src)[1]) << 16)
+ | ((sph_u32)(((const unsigned char *)src)[2]) << 8)
+ | (sph_u32)(((const unsigned char *)src)[3]);
+#endif
+}
+
+/**
+ * Decode a 32-bit value from the provided buffer (big endian convention).
+ * The source buffer must be properly aligned.
+ *
+ * @param src the source buffer (32-bit aligned)
+ * @return the decoded value
+ */
+static SPH_INLINE sph_u32
+sph_dec32be_aligned(const void *src)
+{
+#if SPH_LITTLE_ENDIAN
+ return sph_bswap32(*(const sph_u32 *)src);
+#elif SPH_BIG_ENDIAN
+ return *(const sph_u32 *)src;
+#else
+ return ((sph_u32)(((const unsigned char *)src)[0]) << 24)
+ | ((sph_u32)(((const unsigned char *)src)[1]) << 16)
+ | ((sph_u32)(((const unsigned char *)src)[2]) << 8)
+ | (sph_u32)(((const unsigned char *)src)[3]);
+#endif
+}
+
+/**
+ * Encode a 32-bit value into the provided buffer (little endian convention).
+ *
+ * @param dst the destination buffer
+ * @param val the 32-bit value to encode
+ */
+static SPH_INLINE void
+sph_enc32le(void *dst, sph_u32 val)
+{
+#if defined SPH_UPTR
+#if SPH_UNALIGNED
+#if SPH_BIG_ENDIAN
+ val = sph_bswap32(val);
+#endif
+ *(sph_u32 *)dst = val;
+#else
+ if (((SPH_UPTR)dst & 3) == 0) {
+#if SPH_BIG_ENDIAN
+ val = sph_bswap32(val);
+#endif
+ *(sph_u32 *)dst = val;
+ } else {
+ ((unsigned char *)dst)[0] = val;
+ ((unsigned char *)dst)[1] = (val >> 8);
+ ((unsigned char *)dst)[2] = (val >> 16);
+ ((unsigned char *)dst)[3] = (val >> 24);
+ }
+#endif
+#else
+ ((unsigned char *)dst)[0] = val;
+ ((unsigned char *)dst)[1] = (val >> 8);
+ ((unsigned char *)dst)[2] = (val >> 16);
+ ((unsigned char *)dst)[3] = (val >> 24);
+#endif
+}
+
+/**
+ * Encode a 32-bit value into the provided buffer (little endian convention).
+ * The destination buffer must be properly aligned.
+ *
+ * @param dst the destination buffer (32-bit aligned)
+ * @param val the value to encode
+ */
+static SPH_INLINE void
+sph_enc32le_aligned(void *dst, sph_u32 val)
+{
+#if SPH_LITTLE_ENDIAN
+ *(sph_u32 *)dst = val;
+#elif SPH_BIG_ENDIAN
+ *(sph_u32 *)dst = sph_bswap32(val);
+#else
+ ((unsigned char *)dst)[0] = val;
+ ((unsigned char *)dst)[1] = (val >> 8);
+ ((unsigned char *)dst)[2] = (val >> 16);
+ ((unsigned char *)dst)[3] = (val >> 24);
+#endif
+}
+
+/**
+ * Decode a 32-bit value from the provided buffer (little endian convention).
+ *
+ * @param src the source buffer
+ * @return the decoded value
+ */
+static SPH_INLINE sph_u32
+sph_dec32le(const void *src)
+{
+#if defined SPH_UPTR
+#if SPH_UNALIGNED
+#if SPH_BIG_ENDIAN
+ return sph_bswap32(*(const sph_u32 *)src);
+#else
+ return *(const sph_u32 *)src;
+#endif
+#else
+ if (((SPH_UPTR)src & 3) == 0) {
+#if SPH_BIG_ENDIAN
+#if SPH_SPARCV9_GCC && !SPH_NO_ASM
+ sph_u32 tmp;
+
+ /*
+ * "__volatile__" is needed here because without it,
+ * gcc-3.4.3 miscompiles the code and performs the
+ * access before the test on the address, thus triggering
+ * a bus error...
+ */
+ __asm__ __volatile__ (
+ "lda [%1]0x88,%0" : "=r" (tmp) : "r" (src));
+ return tmp;
+/*
+ * On PowerPC, this turns out not to be worth the effort: the inline
+ * assembly makes GCC optimizer uncomfortable, which tends to nullify
+ * the decoding gains.
+ *
+ * For most hash functions, using this inline assembly trick changes
+ * hashing speed by less than 5% and often _reduces_ it. The biggest
+ * gains are for MD4 (+11%) and CubeHash (+30%). For all others, it is
+ * less then 10%. The speed gain on CubeHash is probably due to the
+ * chronic shortage of registers that CubeHash endures; for the other
+ * functions, the generic code appears to be efficient enough already.
+ *
+#elif (SPH_PPC32_GCC || SPH_PPC64_GCC) && !SPH_NO_ASM
+ sph_u32 tmp;
+
+ __asm__ __volatile__ (
+ "lwbrx %0,0,%1" : "=r" (tmp) : "r" (src));
+ return tmp;
+ */
+#else
+ return sph_bswap32(*(const sph_u32 *)src);
+#endif
+#else
+ return *(const sph_u32 *)src;
+#endif
+ } else {
+ return (sph_u32)(((const unsigned char *)src)[0])
+ | ((sph_u32)(((const unsigned char *)src)[1]) << 8)
+ | ((sph_u32)(((const unsigned char *)src)[2]) << 16)
+ | ((sph_u32)(((const unsigned char *)src)[3]) << 24);
+ }
+#endif
+#else
+ return (sph_u32)(((const unsigned char *)src)[0])
+ | ((sph_u32)(((const unsigned char *)src)[1]) << 8)
+ | ((sph_u32)(((const unsigned char *)src)[2]) << 16)
+ | ((sph_u32)(((const unsigned char *)src)[3]) << 24);
+#endif
+}
+
+/**
+ * Decode a 32-bit value from the provided buffer (little endian convention).
+ * The source buffer must be properly aligned.
+ *
+ * @param src the source buffer (32-bit aligned)
+ * @return the decoded value
+ */
+static SPH_INLINE sph_u32
+sph_dec32le_aligned(const void *src)
+{
+#if SPH_LITTLE_ENDIAN
+ return *(const sph_u32 *)src;
+#elif SPH_BIG_ENDIAN
+#if SPH_SPARCV9_GCC && !SPH_NO_ASM
+ sph_u32 tmp;
+
+ __asm__ __volatile__ ("lda [%1]0x88,%0" : "=r" (tmp) : "r" (src));
+ return tmp;
+/*
+ * Not worth it generally.
+ *
+#elif (SPH_PPC32_GCC || SPH_PPC64_GCC) && !SPH_NO_ASM
+ sph_u32 tmp;
+
+ __asm__ __volatile__ ("lwbrx %0,0,%1" : "=r" (tmp) : "r" (src));
+ return tmp;
+ */
+#else
+ return sph_bswap32(*(const sph_u32 *)src);
+#endif
+#else
+ return (sph_u32)(((const unsigned char *)src)[0])
+ | ((sph_u32)(((const unsigned char *)src)[1]) << 8)
+ | ((sph_u32)(((const unsigned char *)src)[2]) << 16)
+ | ((sph_u32)(((const unsigned char *)src)[3]) << 24);
+#endif
+}
+
+#if SPH_64
+
+/**
+ * Encode a 64-bit value into the provided buffer (big endian convention).
+ *
+ * @param dst the destination buffer
+ * @param val the 64-bit value to encode
+ */
+static SPH_INLINE void
+sph_enc64be(void *dst, sph_u64 val)
+{
+#if defined SPH_UPTR
+#if SPH_UNALIGNED
+#if SPH_LITTLE_ENDIAN
+ val = sph_bswap64(val);
+#endif
+ *(sph_u64 *)dst = val;
+#else
+ if (((SPH_UPTR)dst & 7) == 0) {
+#if SPH_LITTLE_ENDIAN
+ val = sph_bswap64(val);
+#endif
+ *(sph_u64 *)dst = val;
+ } else {
+ ((unsigned char *)dst)[0] = (val >> 56);
+ ((unsigned char *)dst)[1] = (val >> 48);
+ ((unsigned char *)dst)[2] = (val >> 40);
+ ((unsigned char *)dst)[3] = (val >> 32);
+ ((unsigned char *)dst)[4] = (val >> 24);
+ ((unsigned char *)dst)[5] = (val >> 16);
+ ((unsigned char *)dst)[6] = (val >> 8);
+ ((unsigned char *)dst)[7] = val;
+ }
+#endif
+#else
+ ((unsigned char *)dst)[0] = (val >> 56);
+ ((unsigned char *)dst)[1] = (val >> 48);
+ ((unsigned char *)dst)[2] = (val >> 40);
+ ((unsigned char *)dst)[3] = (val >> 32);
+ ((unsigned char *)dst)[4] = (val >> 24);
+ ((unsigned char *)dst)[5] = (val >> 16);
+ ((unsigned char *)dst)[6] = (val >> 8);
+ ((unsigned char *)dst)[7] = val;
+#endif
+}
+
+/**
+ * Encode a 64-bit value into the provided buffer (big endian convention).
+ * The destination buffer must be properly aligned.
+ *
+ * @param dst the destination buffer (64-bit aligned)
+ * @param val the value to encode
+ */
+static SPH_INLINE void
+sph_enc64be_aligned(void *dst, sph_u64 val)
+{
+#if SPH_LITTLE_ENDIAN
+ *(sph_u64 *)dst = sph_bswap64(val);
+#elif SPH_BIG_ENDIAN
+ *(sph_u64 *)dst = val;
+#else
+ ((unsigned char *)dst)[0] = (val >> 56);
+ ((unsigned char *)dst)[1] = (val >> 48);
+ ((unsigned char *)dst)[2] = (val >> 40);
+ ((unsigned char *)dst)[3] = (val >> 32);
+ ((unsigned char *)dst)[4] = (val >> 24);
+ ((unsigned char *)dst)[5] = (val >> 16);
+ ((unsigned char *)dst)[6] = (val >> 8);
+ ((unsigned char *)dst)[7] = val;
+#endif
+}
+
+/**
+ * Decode a 64-bit value from the provided buffer (big endian convention).
+ *
+ * @param src the source buffer
+ * @return the decoded value
+ */
+static SPH_INLINE sph_u64
+sph_dec64be(const void *src)
+{
+#if defined SPH_UPTR
+#if SPH_UNALIGNED
+#if SPH_LITTLE_ENDIAN
+ return sph_bswap64(*(const sph_u64 *)src);
+#else
+ return *(const sph_u64 *)src;
+#endif
+#else
+ if (((SPH_UPTR)src & 7) == 0) {
+#if SPH_LITTLE_ENDIAN
+ return sph_bswap64(*(const sph_u64 *)src);
+#else
+ return *(const sph_u64 *)src;
+#endif
+ } else {
+ return ((sph_u64)(((const unsigned char *)src)[0]) << 56)
+ | ((sph_u64)(((const unsigned char *)src)[1]) << 48)
+ | ((sph_u64)(((const unsigned char *)src)[2]) << 40)
+ | ((sph_u64)(((const unsigned char *)src)[3]) << 32)
+ | ((sph_u64)(((const unsigned char *)src)[4]) << 24)
+ | ((sph_u64)(((const unsigned char *)src)[5]) << 16)
+ | ((sph_u64)(((const unsigned char *)src)[6]) << 8)
+ | (sph_u64)(((const unsigned char *)src)[7]);
+ }
+#endif
+#else
+ return ((sph_u64)(((const unsigned char *)src)[0]) << 56)
+ | ((sph_u64)(((const unsigned char *)src)[1]) << 48)
+ | ((sph_u64)(((const unsigned char *)src)[2]) << 40)
+ | ((sph_u64)(((const unsigned char *)src)[3]) << 32)
+ | ((sph_u64)(((const unsigned char *)src)[4]) << 24)
+ | ((sph_u64)(((const unsigned char *)src)[5]) << 16)
+ | ((sph_u64)(((const unsigned char *)src)[6]) << 8)
+ | (sph_u64)(((const unsigned char *)src)[7]);
+#endif
+}
+
+/**
+ * Decode a 64-bit value from the provided buffer (big endian convention).
+ * The source buffer must be properly aligned.
+ *
+ * @param src the source buffer (64-bit aligned)
+ * @return the decoded value
+ */
+static SPH_INLINE sph_u64
+sph_dec64be_aligned(const void *src)
+{
+#if SPH_LITTLE_ENDIAN
+ return sph_bswap64(*(const sph_u64 *)src);
+#elif SPH_BIG_ENDIAN
+ return *(const sph_u64 *)src;
+#else
+ return ((sph_u64)(((const unsigned char *)src)[0]) << 56)
+ | ((sph_u64)(((const unsigned char *)src)[1]) << 48)
+ | ((sph_u64)(((const unsigned char *)src)[2]) << 40)
+ | ((sph_u64)(((const unsigned char *)src)[3]) << 32)
+ | ((sph_u64)(((const unsigned char *)src)[4]) << 24)
+ | ((sph_u64)(((const unsigned char *)src)[5]) << 16)
+ | ((sph_u64)(((const unsigned char *)src)[6]) << 8)
+ | (sph_u64)(((const unsigned char *)src)[7]);
+#endif
+}
+
+/**
+ * Encode a 64-bit value into the provided buffer (little endian convention).
+ *
+ * @param dst the destination buffer
+ * @param val the 64-bit value to encode
+ */
+static SPH_INLINE void
+sph_enc64le(void *dst, sph_u64 val)
+{
+#if defined SPH_UPTR
+#if SPH_UNALIGNED
+#if SPH_BIG_ENDIAN
+ val = sph_bswap64(val);
+#endif
+ *(sph_u64 *)dst = val;
+#else
+ if (((SPH_UPTR)dst & 7) == 0) {
+#if SPH_BIG_ENDIAN
+ val = sph_bswap64(val);
+#endif
+ *(sph_u64 *)dst = val;
+ } else {
+ ((unsigned char *)dst)[0] = val;
+ ((unsigned char *)dst)[1] = (val >> 8);
+ ((unsigned char *)dst)[2] = (val >> 16);
+ ((unsigned char *)dst)[3] = (val >> 24);
+ ((unsigned char *)dst)[4] = (val >> 32);
+ ((unsigned char *)dst)[5] = (val >> 40);
+ ((unsigned char *)dst)[6] = (val >> 48);
+ ((unsigned char *)dst)[7] = (val >> 56);
+ }
+#endif
+#else
+ ((unsigned char *)dst)[0] = val;
+ ((unsigned char *)dst)[1] = (val >> 8);
+ ((unsigned char *)dst)[2] = (val >> 16);
+ ((unsigned char *)dst)[3] = (val >> 24);
+ ((unsigned char *)dst)[4] = (val >> 32);
+ ((unsigned char *)dst)[5] = (val >> 40);
+ ((unsigned char *)dst)[6] = (val >> 48);
+ ((unsigned char *)dst)[7] = (val >> 56);
+#endif
+}
+
+/**
+ * Encode a 64-bit value into the provided buffer (little endian convention).
+ * The destination buffer must be properly aligned.
+ *
+ * @param dst the destination buffer (64-bit aligned)
+ * @param val the value to encode
+ */
+static SPH_INLINE void
+sph_enc64le_aligned(void *dst, sph_u64 val)
+{
+#if SPH_LITTLE_ENDIAN
+ *(sph_u64 *)dst = val;
+#elif SPH_BIG_ENDIAN
+ *(sph_u64 *)dst = sph_bswap64(val);
+#else
+ ((unsigned char *)dst)[0] = val;
+ ((unsigned char *)dst)[1] = (val >> 8);
+ ((unsigned char *)dst)[2] = (val >> 16);
+ ((unsigned char *)dst)[3] = (val >> 24);
+ ((unsigned char *)dst)[4] = (val >> 32);
+ ((unsigned char *)dst)[5] = (val >> 40);
+ ((unsigned char *)dst)[6] = (val >> 48);
+ ((unsigned char *)dst)[7] = (val >> 56);
+#endif
+}
+
+/**
+ * Decode a 64-bit value from the provided buffer (little endian convention).
+ *
+ * @param src the source buffer
+ * @return the decoded value
+ */
+static SPH_INLINE sph_u64
+sph_dec64le(const void *src)
+{
+#if defined SPH_UPTR
+#if SPH_UNALIGNED
+#if SPH_BIG_ENDIAN
+ return sph_bswap64(*(const sph_u64 *)src);
+#else
+ return *(const sph_u64 *)src;
+#endif
+#else
+ if (((SPH_UPTR)src & 7) == 0) {
+#if SPH_BIG_ENDIAN
+#if SPH_SPARCV9_GCC_64 && !SPH_NO_ASM
+ sph_u64 tmp;
+
+ __asm__ __volatile__ (
+ "ldxa [%1]0x88,%0" : "=r" (tmp) : "r" (src));
+ return tmp;
+/*
+ * Not worth it generally.
+ *
+#elif SPH_PPC32_GCC && !SPH_NO_ASM
+ return (sph_u64)sph_dec32le_aligned(src)
+ | ((sph_u64)sph_dec32le_aligned(
+ (const char *)src + 4) << 32);
+#elif SPH_PPC64_GCC && !SPH_NO_ASM
+ sph_u64 tmp;
+
+ __asm__ __volatile__ (
+ "ldbrx %0,0,%1" : "=r" (tmp) : "r" (src));
+ return tmp;
+ */
+#else
+ return sph_bswap64(*(const sph_u64 *)src);
+#endif
+#else
+ return *(const sph_u64 *)src;
+#endif
+ } else {
+ return (sph_u64)(((const unsigned char *)src)[0])
+ | ((sph_u64)(((const unsigned char *)src)[1]) << 8)
+ | ((sph_u64)(((const unsigned char *)src)[2]) << 16)
+ | ((sph_u64)(((const unsigned char *)src)[3]) << 24)
+ | ((sph_u64)(((const unsigned char *)src)[4]) << 32)
+ | ((sph_u64)(((const unsigned char *)src)[5]) << 40)
+ | ((sph_u64)(((const unsigned char *)src)[6]) << 48)
+ | ((sph_u64)(((const unsigned char *)src)[7]) << 56);
+ }
+#endif
+#else
+ return (sph_u64)(((const unsigned char *)src)[0])
+ | ((sph_u64)(((const unsigned char *)src)[1]) << 8)
+ | ((sph_u64)(((const unsigned char *)src)[2]) << 16)
+ | ((sph_u64)(((const unsigned char *)src)[3]) << 24)
+ | ((sph_u64)(((const unsigned char *)src)[4]) << 32)
+ | ((sph_u64)(((const unsigned char *)src)[5]) << 40)
+ | ((sph_u64)(((const unsigned char *)src)[6]) << 48)
+ | ((sph_u64)(((const unsigned char *)src)[7]) << 56);
+#endif
+}
+
+/**
+ * Decode a 64-bit value from the provided buffer (little endian convention).
+ * The source buffer must be properly aligned.
+ *
+ * @param src the source buffer (64-bit aligned)
+ * @return the decoded value
+ */
+static SPH_INLINE sph_u64
+sph_dec64le_aligned(const void *src)
+{
+#if SPH_LITTLE_ENDIAN
+ return *(const sph_u64 *)src;
+#elif SPH_BIG_ENDIAN
+#if SPH_SPARCV9_GCC_64 && !SPH_NO_ASM
+ sph_u64 tmp;
+
+ __asm__ __volatile__ ("ldxa [%1]0x88,%0" : "=r" (tmp) : "r" (src));
+ return tmp;
+/*
+ * Not worth it generally.
+ *
+#elif SPH_PPC32_GCC && !SPH_NO_ASM
+ return (sph_u64)sph_dec32le_aligned(src)
+ | ((sph_u64)sph_dec32le_aligned((const char *)src + 4) << 32);
+#elif SPH_PPC64_GCC && !SPH_NO_ASM
+ sph_u64 tmp;
+
+ __asm__ __volatile__ ("ldbrx %0,0,%1" : "=r" (tmp) : "r" (src));
+ return tmp;
+ */
+#else
+ return sph_bswap64(*(const sph_u64 *)src);
+#endif
+#else
+ return (sph_u64)(((const unsigned char *)src)[0])
+ | ((sph_u64)(((const unsigned char *)src)[1]) << 8)
+ | ((sph_u64)(((const unsigned char *)src)[2]) << 16)
+ | ((sph_u64)(((const unsigned char *)src)[3]) << 24)
+ | ((sph_u64)(((const unsigned char *)src)[4]) << 32)
+ | ((sph_u64)(((const unsigned char *)src)[5]) << 40)
+ | ((sph_u64)(((const unsigned char *)src)[6]) << 48)
+ | ((sph_u64)(((const unsigned char *)src)[7]) << 56);
+#endif
+}
+
+#endif
+
+#endif /* Doxygen excluded block */
+
+#endif
diff --git a/libtests/aes.cc b/libtests/aes.cc
index ad2f0dd..381148c 100644
--- a/libtests/aes.cc
+++ b/libtests/aes.cc
@@ -8,52 +8,86 @@
static void usage()
{
- std::cerr << "Usage: aes [+-]cbc { -encrypt | -decrypt }"
- << " hex-key infile outfile" << std::endl;
+ std::cerr << "Usage: aes options hex-key infile outfile" << std::endl
+ << " -cbc -- disable CBC mode" << std::endl
+ << " +cbc -- enable CBC mode" << std::endl
+ << " -encrypt -- encrypt" << std::endl
+ << " -decrypt -- decrypt CBC mode" << std::endl
+ << " -zero-iv -- use zero initialization vector" << std::endl
+ << " -static-iv -- use static initialization vector" << std::endl
+ << " -no-padding -- disable padding" << std::endl
+ << "Options must precede key and file names." << std::endl;
exit(2);
}
int main(int argc, char* argv[])
{
- if (argc != 6)
- {
- usage();
- }
-
- char* cbc = argv[1];
- char* action = argv[2];
- char* hexkey = argv[3];
- char* infilename = argv[4];
- char* outfilename = argv[5];
-
+ bool encrypt = true;
bool cbc_mode = true;
- if (strcmp(cbc, "-cbc") == 0)
- {
- cbc_mode = false;
- }
- else if (strcmp(cbc, "+cbc") != 0)
- {
- usage();
- }
+ char* hexkey = 0;
+ char* infilename = 0;
+ char* outfilename = 0;
+ bool zero_iv = false;
+ bool static_iv = false;
+ bool disable_padding = false;
- bool encrypt = true;
- if (strcmp(action, "-decrypt") == 0)
+ for (int i = 1; i < argc; ++i)
{
- encrypt = false;
+ char* arg = argv[i];
+ if ((arg[0] == '-') || (arg[0] == '+'))
+ {
+ if (strcmp(arg, "-cbc") == 0)
+ {
+ cbc_mode = false;
+ }
+ else if (strcmp(arg, "+cbc") == 0)
+ {
+ cbc_mode = true;
+ }
+ else if (strcmp(arg, "-decrypt") == 0)
+ {
+ encrypt = false;
+ }
+ else if (strcmp(arg, "-encrypt") == 0)
+ {
+ encrypt = true;
+ }
+ else if (strcmp(arg, "-zero-iv") == 0)
+ {
+ zero_iv = true;
+ }
+ else if (strcmp(arg, "-static-iv") == 0)
+ {
+ static_iv = true;
+ }
+ else if (strcmp(arg, "-no-padding") == 0)
+ {
+ disable_padding = true;
+ }
+ else
+ {
+ usage();
+ }
+ }
+ else if (argc == i + 3)
+ {
+ hexkey = argv[i];
+ infilename = argv[i+1];
+ outfilename = argv[i+2];
+ break;
+ }
+ else
+ {
+ usage();
+ }
}
- else if (strcmp(action, "-encrypt") != 0)
+ if (outfilename == 0)
{
- usage();
+ usage();
}
unsigned int hexkeylen = (unsigned int)strlen(hexkey);
unsigned int keylen = hexkeylen / 2;
- if (keylen != Pl_AES_PDF::key_size)
- {
- std::cerr << "key length must be " << Pl_AES_PDF::key_size
- << " bytes" << std::endl;
- exit(2);
- }
FILE* infile = fopen(infilename, "rb");
if (infile == 0)
@@ -69,7 +103,7 @@ int main(int argc, char* argv[])
exit(2);
}
- unsigned char key[Pl_AES_PDF::key_size];
+ unsigned char* key = new unsigned char[keylen];
for (unsigned int i = 0; i < strlen(hexkey); i += 2)
{
char t[3];
@@ -82,11 +116,25 @@ int main(int argc, char* argv[])
}
Pl_StdioFile* out = new Pl_StdioFile("stdout", outfile);
- Pl_AES_PDF* aes = new Pl_AES_PDF("aes_128_cbc", out, encrypt, key);
+ Pl_AES_PDF* aes = new Pl_AES_PDF("aes_128_cbc", out, encrypt, key, keylen);
+ delete [] key;
+ key = 0;
if (! cbc_mode)
{
aes->disableCBC();
}
+ if (zero_iv)
+ {
+ aes->useZeroIV();
+ }
+ else if (static_iv)
+ {
+ aes->useStaticIV();
+ }
+ if (disable_padding)
+ {
+ aes->disablePadding();
+ }
// 16 < buffer size, buffer_size is not a multiple of 8 for testing
unsigned char buf[83];
diff --git a/libtests/build.mk b/libtests/build.mk
index 6464502..7a53595 100644
--- a/libtests/build.mk
+++ b/libtests/build.mk
@@ -12,7 +12,8 @@ BINS_libtests = \
png_filter \
pointer_holder \
qutil \
- rc4
+ rc4 \
+ sha2
TARGETS_libtests = $(foreach B,$(BINS_libtests),libtests/$(OUTPUT_DIR)/$(call binname,$(B)))
diff --git a/libtests/qtest/sha2.test b/libtests/qtest/sha2.test
new file mode 100644
index 0000000..34d668f
--- /dev/null
+++ b/libtests/qtest/sha2.test
@@ -0,0 +1,18 @@
+#!/usr/bin/env perl
+require 5.008;
+BEGIN { $^W = 1; }
+use strict;
+
+chdir("sha2") or die "chdir testdir failed: $!\n";
+
+require TestDriver;
+
+my $td = new TestDriver('sha2');
+
+$td->runtest("sha2",
+ {$td->COMMAND => "sha2"},
+ {$td->FILE => "sha2.out",
+ $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+
+$td->report(1);
diff --git a/libtests/qtest/sha2/sha2.out b/libtests/qtest/sha2/sha2.out
new file mode 100644
index 0000000..702264f
--- /dev/null
+++ b/libtests/qtest/sha2/sha2.out
@@ -0,0 +1,9 @@
+256 short: passed
+256 long: passed
+256 million: passed
+384 short: passed
+384 long: passed
+384 million: passed
+512 short: passed
+512 long: passed
+512 million: passed
diff --git a/libtests/sha2.cc b/libtests/sha2.cc
new file mode 100644
index 0000000..2b9aac3
--- /dev/null
+++ b/libtests/sha2.cc
@@ -0,0 +1,69 @@
+#include <qpdf/Pl_SHA2.hh>
+#include <iostream>
+#include <stdlib.h>
+#include <string.h>
+#include <qpdf/QUtil.hh>
+
+static void test(Pl_SHA2& sha2, char const* description, int bits,
+ char const* input, std::string const& output)
+{
+ sha2.resetBits(bits);
+ sha2.write((unsigned char*) input, strlen(input));
+ sha2.finish();
+ std::cout << description << ": ";
+ if (output == sha2.getHexDigest())
+ {
+ std::cout << "passed\n";
+ }
+ else
+ {
+ std::cout << "failed\n"
+ << " expected: " << output << "\n"
+ << " actual: " << sha2.getHexDigest() << "\n";
+ }
+}
+
+int main( int argc, char *argv[] )
+{
+ Pl_SHA2 sha2;
+ char million_a[1000001];
+ memset(million_a, 'a', 1000000);
+ million_a[1000000] = '\0';
+ test(sha2, "256 short", 256,
+ "abc",
+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
+ test(sha2, "256 long", 256,
+ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1");
+ test(sha2, "256 million", 256,
+ million_a,
+ "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0");
+ test(sha2, "384 short", 384,
+ "abc",
+ "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded163"
+ "1a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7");
+ test(sha2, "384 long", 384,
+ "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
+ "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+ "09330c33f71147e83d192fc782cd1b4753111b173b3b05d2"
+ "2fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039");
+ test(sha2, "384 million", 384,
+ million_a,
+ "9d0e1809716474cb086e834e310a4a1ced149e9c00f24852"
+ "7972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985");
+ test(sha2, "512 short", 512,
+ "abc",
+ "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a"
+ "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f");
+ test(sha2, "512 long", 512,
+ "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
+ "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+ "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018"
+ "501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909");
+ test(sha2, "512 million", 512,
+ million_a,
+ "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973eb"
+ "de0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b");
+
+ return 0;
+}
diff --git a/make_dist b/make_dist
index fb1436e..de6d3f1 100755
--- a/make_dist
+++ b/make_dist
@@ -119,6 +119,7 @@ if ($run_tests)
{
cd($srcdir);
run("./configure");
+ run("make -j8");
run("make check");
cd($pwd);
}
diff --git a/make_windows_releases b/make_windows_releases
index dff7931..2ea0dbe 100755
--- a/make_windows_releases
+++ b/make_windows_releases
@@ -12,9 +12,11 @@ PATH=$cwd/libqpdf/build:$PATH
rm -rf install-mingw* install-msvc*
./config-mingw64
+make -j8
make check install
make distclean
./config-mingw32
+make -j8
make check install
make distclean
diff --git a/make_windows_releases-msvc b/make_windows_releases-msvc
index 40a3979..702ab83 100755
--- a/make_windows_releases-msvc
+++ b/make_windows_releases-msvc
@@ -17,5 +17,6 @@ cwd=`pwd`
PATH=$cwd/libqpdf/build:$PATH
./config-msvc $w
+make -j8
make check install
make distclean
diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml
index 0bdc32c..b5e2251 100644
--- a/manual/qpdf-manual.xml
+++ b/manual/qpdf-manual.xml
@@ -5,8 +5,8 @@
<!ENTITY mdash "&#x2014;">
<!ENTITY ndash "&#x2013;">
<!ENTITY nbsp "&#xA0;">
-<!ENTITY swversion "3.0.2">
-<!ENTITY lastreleased "September 6, 2012">
+<!ENTITY swversion "4.0.0">
+<!ENTITY lastreleased "December 31, 2012">
]>
<book>
<bookinfo>
@@ -16,7 +16,7 @@
<firstname>Jay</firstname><surname>Berkenbilt</surname>
</author>
<copyright>
- <year>2005&ndash;2012</year>
+ <year>2005&ndash;2013</year>
<holder>Jay Berkenbilt</holder>
</copyright>
</bookinfo>
@@ -402,8 +402,8 @@ make
</para>
<para>
The value for
- <option><replaceable>key-length</replaceable></option> may be 40
- or 128. The restriction flags are dependent upon key length.
+ <option><replaceable>key-length</replaceable></option> may be 40,
+ 128, or 256. The restriction flags are dependent upon key length.
When no additional restrictions are given, the default is to be
fully permissive.
</para>
@@ -565,6 +565,44 @@ make
</listitem>
</varlistentry>
</variablelist>
+ If <option><replaceable>key-length</replaceable></option> is 256,
+ the minimum PDF version is 1.7 with extension level 8, and the
+ AES-based encryption format used is the PDF 2.0 encryption method
+ supported by Acrobat X. the same options are available as with
+ 128 bits with the following exceptions:
+ <variablelist>
+ <varlistentry>
+ <term><option>--use-aes</option></term>
+ <listitem>
+ <para>
+ This option is not available with 256-bit keys. AES is always
+ used with 256-bit encryption keys.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--force-V4</option></term>
+ <listitem>
+ <para>
+ This option is not available with 256 keys.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--force-R5</option></term>
+ <listitem>
+ <para>
+ If specified, qpdf sets the minimum version to 1.7 at
+ extension level 3 and writes the deprecated encryption format
+ used by Acrobat version IX. This option should not be used in
+ practice to generate PDF files that will be in general use,
+ but it can be useful to generate files if you are trying to
+ test proper support in another application for PDF files
+ encrypted in this way.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
The default for each permission option is to be fully permissive.
</para>
</sect1>
@@ -796,6 +834,16 @@ outfile.pdf</option>
will automatically increase the version as needed when adding
features that require newer PDF readers.
</para>
+ <para>
+ The version number may be expressed in the form
+ <replaceable>major.minor.extension-level</replaceable>, in
+ which case the version is interpreted as
+ <replaceable>major.minor</replaceable> at extension level
+ <replaceable>extension-level</replaceable>. For example,
+ version <literal>1.7.8</literal> represents version 1.7 at
+ extension level 8. Note that minimal syntax checking is done
+ on the command line.
+ </para>
</listitem>
</varlistentry>
<varlistentry>
@@ -804,14 +852,19 @@ outfile.pdf</option>
<para>
This option forces the PDF version to be the exact version
specified <emphasis>even when the file may have content that
- is not supported in that version</emphasis>. In some cases,
- forcing the output file's PDF version to be lower than that of
- the input file will cause qpdf to disable certain features of
- the document. Specifically, AES encryption is disabled if the
- version is less than 1.6, cleartext metadata and object
- streams are disabled if less than 1.5, 128-bit encryption keys
- are disabled if less than 1.4, and all encryption is disabled
- if less than 1.3. Even with these precautions, qpdf won't be
+ is not supported in that version</emphasis>. The version
+ number is interpreted in the same way as with
+ <option>--min-version</option> so that extension levels can be
+ set. In some cases, forcing the output file's PDF version to
+ be lower than that of the input file will cause qpdf to
+ disable certain features of the document. Specifically,
+ 256-bit keys are disabled if the version is less than 1.7 with
+ extension level 8 (except R5 is disabled if less than 1.7 with
+ extension level 3), AES encryption is disabled if the version
+ is less than 1.6, cleartext metadata and object streams are
+ disabled if less than 1.5, 128-bit encryption keys are
+ disabled if less than 1.4, and all encryption is disabled if
+ less than 1.3. Even with these precautions, qpdf won't be
able to do things like eliminate use of newer image
compression schemes, transparency groups, or other features
that may have been added in more recent versions of PDF.
@@ -1592,6 +1645,31 @@ outfile.pdf</option>
<classname>QPDFWriter</classname> when it rewrites encrypted
files.
</para>
+ <para>
+ When copying encrypted files, unless otherwise directed, qpdf will
+ preserve any encryption in force in the original file. qpdf can
+ do this with either the user or the owner password. There is no
+ difference in capability based on which password is used. When 40
+ or 128 bit encryption keys are used, the user password can be
+ recovered with the owner password. With 256 keys, the user and
+ owner passwords are used independently to encrypt the actual
+ encryption key, so while either can be used, the owner password
+ can no longer be used to recover the user password.
+ </para>
+ <para>
+ Starting with version 4.0.0, qpdf can read files that are not
+ encrypted but that contain encrypted attachments, but it cannot
+ write such files. qpdf also requires the password to be specified
+ in order to open the file, not just to extract attachments, since
+ once the file is open, all decryption is handled transparently.
+ When copying files like this while preserving encryption, qpdf
+ will apply the file's encryption to everything in the file, not
+ just to the attachments. When decrypting the file, qpdf will
+ decrypt the attachments. In general, when copying PDF files with
+ multiple encryption formats, qpdf will choose the newest format.
+ The only exception to this is that clear-text metadata will be
+ preserved as clear-text if it is that way in the original file.
+ </para>
</sect1>
<sect1 id="ref.adding-and-remove-pages">
<title>Adding and Removing Pages</title>
@@ -2383,6 +2461,179 @@ print "\n";
</para>
<variablelist>
<varlistentry>
+ <term>4.0.0: December 31, 2012</term>
+ <listitem>
+ <itemizedlist>
+ <listitem>
+ <para>
+ Major enhancement: support has been added for newer encryption
+ schemes supported by version X of Adobe Acrobat. This
+ includes use of 127-character passwords, 256-bit encryption
+ keys, and the encryption scheme specified in ISO 32000-2, the
+ PDF 2.0 specification. This scheme can be chosen from the
+ command line by specifying use of 256-bit keys. qpdf also
+ supports the deprecated encryption method used by Acrobat IX.
+ This encryption style has known security weaknesses and should
+ not be used in practice. However, such files exist &ldquo;in
+ the wild,&rdquo; so support for this scheme is still useful.
+ New methods
+ <function>QPDFWriter::setR6EncryptionParameters</function>
+ (for the PDF 2.0 scheme) and
+ <function>QPDFWriter::setR5EncryptionParameters</function>
+ (for the deprecated scheme) have been added to enable these
+ new encryption schemes. Corresponding functions have been
+ added to the C API as well.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Full support for Adobe extension levels in PDF version
+ information. Starting with PDF version 1.7, corresponding to
+ ISO 32000, Adobe adds new functionality by increasing the
+ extension level rather than increasing the version. This
+ support includes addition of the
+ <function>QPDF::getExtensionLevel</function> method for
+ retrieving the document's extension level, addition of
+ versions of
+ <function>QPDFWriter::setMinimumPDFVersion</function> and
+ <function>QPDFWriter::forcePDFVersion</function> that accept
+ an extension level, and extended syntax for specifying forced
+ and minimum versions on the command line as described in <xref
+ linkend="ref.advanced-transformation"/>. Corresponding
+ functions have been added to the C API as well.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Minor fixes to prevent qpdf from referencing objects in the
+ file that are not referenced in the file's overall structure.
+ Most files don't have any such objects, but some files have
+ contain unreferenced objects with errors, so these fixes
+ prevent qpdf from needlessly rejecting or complaining about
+ such objects.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Add new generalized methods for reading and writing files
+ from/to programmer-defined sources. The method
+ <function>QPDF::processInputSource</function> allows the
+ programmer to use any input source for the input file, and
+ <function>QPDFWriter::setOutputPipeline</function> allows the
+ programmer to write the output file through any pipeline.
+ These methods would make it possible to perform any number of
+ specialized operations, such as accessing external storage
+ systems, creating bindings for qpdf in other programming
+ languages that have their own I/O systems, etc.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Add new method <function>QPDF::getEncryptionKey</function> for
+ retrieving the underlying encryption key used in the file.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ This release includes a small handful of non-compatible API
+ changes. While effort is made to avoid such changes, all the
+ non-compatible API changes in this version were to parts of
+ the API that would likely never be used outside the library
+ itself. In all cases, the altered methods or structures were
+ parts of the <classname>QPDF</classname> that were public to
+ enable them to be called from either
+ <classname>QPDFWriter</classname> or were part of validation
+ code that was over-zealous in reporting problems in parts of
+ the file that would not ordinarily be referenced. In no case
+ did any of the removed methods do anything worse that falsely
+ report error conditions in files that were broken in ways that
+ didn't matter. The following public parts of the
+ <classname>QPDF</classname> class were changed in a
+ non-compatible way:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Updated nested <classname>QPDF::EncryptionData</classname>
+ class to add fields needed by the newer encryption formats,
+ member variables changed to private so that future changes
+ will not require breaking backward compatibility.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Added additional parameters to
+ <function>compute_data_key</function>, which is used by
+ <classname>QPDFWriter</classname> to compute the encryption
+ key used to encrypt a specific object.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Removed the method
+ <function>flattenScalarReferences</function>. This method
+ was previously used prior to writing a new PDF file, but it
+ has the undesired side effect of causing qpdf to read
+ objects in the file that were not referenced. Some
+ otherwise files have unreferenced objects with errors in
+ them, so this could cause qpdf to reject files that would
+ be accepted by virtually all other PDF readers. In fact,
+ qpdf relied on only a very small part of what
+ flattenScalarReferences did, so only this part has been
+ preserved, and it is now done directly inside
+ <classname>QPDFWriter</classname>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Removed the method <function>decodeStreams</function>.
+ This method was used by the <option>--check</option> option
+ of the <command>qpdf</command> command-line tool to force
+ all streams in the file to be decoded, but it also suffered
+ from the problem of opening otherwise unreferenced streams
+ and thus could report false positive. The
+ <option>--check</option> option now causes qpdf to go
+ through all the motions of writing a new file based on the
+ original one, so it will always reference and check exactly
+ those parts of a file that any ordinary viewer would check.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Removed the method
+ <function>trimTrailerForWrite</function>. This method was
+ used by <classname>QPDFWriter</classname> to modify the
+ original QPDF object by removing fields from the trailer
+ dictionary that wouldn't apply to the newly written file.
+ This functionality, though generally harmless, was a poor
+ implementation and has been replaced by having QPDFWriter
+ filter these out when copying the trailer rather than
+ modifying the original QPDF object. (Note that qpdf never
+ modifies the original file itself.)
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Allow the PDF header to appear anywhere in the first 1024
+ bytes of the file. This is consistent with what other readers
+ do.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Fix the <command>pkg-config</command> files to list zlib and
+ pcre in <function>Requires.private</function> to better
+ support static linking using <command>pkg-config</command>.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
<term>3.0.2: September 6, 2012</term>
<listitem>
<itemizedlist>
@@ -3274,4 +3525,16 @@ print "\n";
</itemizedlist>
</para>
</appendix>
+ <appendix id="ref.upgrading-to-4.0">
+ <title>Upgrading to 4.0</title>
+ <para>
+ While version 4.0 includes a few non-compatible API changes, it is
+ very unlikely that anyone's code would have used any of those parts
+ of the API since they generally required information that would
+ only be available inside the library. In the unlikely event that
+ you should run into trouble, please see the ChangeLog. See also
+ <xref linkend="ref.release-notes"/> for a complete list of the
+ non-compatible API changes made in this version.
+ </para>
+ </appendix>
</book>
diff --git a/qpdf.spec b/qpdf.spec
index 3f32939..bf434e8 100644
--- a/qpdf.spec
+++ b/qpdf.spec
@@ -1,6 +1,6 @@
Summary: Command-line tools and library for transforming PDF files
Name: qpdf
-Version: 3.0.2
+Version: 4.0.0
Release: 1%{?dist}
License: Artistic
Group: System Environment/Libraries
diff --git a/qpdf/qpdf-ctest.c b/qpdf/qpdf-ctest.c
index 072e5a7..58fcb00 100644
--- a/qpdf/qpdf-ctest.c
+++ b/qpdf/qpdf-ctest.c
@@ -106,6 +106,10 @@ static void test01(char const* infile,
{
qpdf_read(qpdf, infile, password);
printf("version: %s\n", qpdf_get_pdf_version(qpdf));
+ if (qpdf_get_pdf_extension_level(qpdf) > 0)
+ {
+ printf("extension level: %d\n", qpdf_get_pdf_extension_level(qpdf));
+ }
printf("linearized: %d\n", qpdf_is_linearized(qpdf));
printf("encrypted: %d\n", qpdf_is_encrypted(qpdf));
if (qpdf_is_encrypted(qpdf))
@@ -304,7 +308,7 @@ static void test14(char const* infile,
qpdf_read(qpdf, infile, password);
qpdf_init_write(qpdf, outfile);
qpdf_set_static_ID(qpdf, QPDF_TRUE);
- qpdf_set_minimum_pdf_version(qpdf, "1.6");
+ qpdf_set_minimum_pdf_version_and_extension(qpdf, "1.7", 8);
qpdf_write(qpdf);
qpdf_init_write(qpdf, outfile2);
qpdf_set_static_ID(qpdf, QPDF_TRUE);
@@ -374,6 +378,38 @@ static void test16(char const* infile,
report_errors();
}
+static void test17(char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* outfile2)
+{
+ qpdf_read(qpdf, infile, password);
+ qpdf_init_write(qpdf, outfile);
+ qpdf_set_static_ID(qpdf, QPDF_TRUE);
+ qpdf_set_static_aes_IV(qpdf, QPDF_TRUE);
+ qpdf_set_r5_encryption_parameters(
+ qpdf, "user3", "owner3", QPDF_TRUE, QPDF_TRUE,
+ qpdf_r3p_low, qpdf_r3m_all, QPDF_TRUE);
+ qpdf_write(qpdf);
+ report_errors();
+}
+
+static void test18(char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* outfile2)
+{
+ qpdf_read(qpdf, infile, password);
+ qpdf_init_write(qpdf, outfile);
+ qpdf_set_static_ID(qpdf, QPDF_TRUE);
+ qpdf_set_static_aes_IV(qpdf, QPDF_TRUE);
+ qpdf_set_r6_encryption_parameters(
+ qpdf, "user4", "owner4", QPDF_TRUE, QPDF_TRUE,
+ qpdf_r3p_low, qpdf_r3m_all, QPDF_TRUE);
+ qpdf_write(qpdf);
+ report_errors();
+}
+
int main(int argc, char* argv[])
{
char* p = 0;
@@ -430,6 +466,8 @@ int main(int argc, char* argv[])
(n == 14) ? test14 :
(n == 15) ? test15 :
(n == 16) ? test16 :
+ (n == 17) ? test17 :
+ (n == 18) ? test18 :
0);
if (fn == 0)
diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc
index 2835a96..5a4c408 100644
--- a/qpdf/qpdf.cc
+++ b/qpdf/qpdf.cc
@@ -8,6 +8,7 @@
#include <qpdf/QUtil.hh>
#include <qpdf/QTC.hh>
#include <qpdf/Pl_StdioFile.hh>
+#include <qpdf/Pl_Discard.hh>
#include <qpdf/PointerHolder.hh>
#include <qpdf/QPDF.hh>
@@ -96,7 +97,7 @@ Note that -- terminates parsing of encryption flags.\n\
Either or both of the user password and the owner password may be\n\
empty strings.\n\
\n\
-key-length may be 40 or 128\n\
+key-length may be 40, 128, or 256\n\
\n\
Additional flags are dependent upon key length.\n\
\n\
@@ -117,6 +118,11 @@ Additional flags are dependent upon key length.\n\
--use-aes=[yn] indicates whether to use AES encryption\n\
--force-V4 forces use of V=4 encryption handler\n\
\n\
+ If 256, options are the same as 128 with these exceptions:\n\
+ --force-V4 this option is not available with 256-bit keys\n\
+ --use-aes this option is always on with 256-bit keys\n\
+ --force-R5 forces use of deprecated R=5 encryption\n\
+\n\
print-opt may be:\n\
\n\
full allow full printing\n\
@@ -188,6 +194,9 @@ familiar with the PDF file format or who are PDF developers.\n\
--min-version=version sets the minimum PDF version of the output file\n\
--force-version=version forces this to be the PDF version of the output file\n\
\n\
+Version numbers may be expressed as major.minor.extension-level, so 1.7.3\n\
+means PDF version 1.7 at extension level 3.\n\
+\n\
Values for stream data options:\n\
\n\
compress recompress stream data when possible (default)\n\
@@ -279,6 +288,9 @@ static std::string show_encryption_method(QPDF::encryption_method_e method)
case QPDF::e_aes:
result = "AESv2";
break;
+ case QPDF::e_aesv3:
+ result = "AESv3";
+ break;
// no default so gcc will warn for missing case
}
return result;
@@ -481,7 +493,8 @@ parse_encrypt_options(
bool& r2_print, bool& r2_modify, bool& r2_extract, bool& r2_annotate,
bool& r3_accessibility, bool& r3_extract,
qpdf_r3_print_e& r3_print, qpdf_r3_modify_e& r3_modify,
- bool& force_V4, bool& cleartext_metadata, bool& use_aes)
+ bool& force_V4, bool& cleartext_metadata, bool& use_aes,
+ bool& force_R5)
{
if (cur_arg + 3 >= argc)
{
@@ -498,9 +511,14 @@ parse_encrypt_options(
{
keylen = 128;
}
+ else if (len_str == "256")
+ {
+ keylen = 256;
+ use_aes = true;
+ }
else
{
- usage("encryption key length must be 40 or 128");
+ usage("encryption key length must be 40, 128, or 256");
}
while (1)
{
@@ -732,15 +750,30 @@ parse_encrypt_options(
{
usage("--force-V4 does not take a parameter");
}
- if (keylen == 40)
+ if (keylen != 128)
{
- usage("--force-V4 is invalid for 40-bit keys");
+ usage("--force-V4 is invalid only for 128-bit keys");
}
else
{
force_V4 = true;
}
}
+ else if (strcmp(arg, "force-R5") == 0)
+ {
+ if (parameter)
+ {
+ usage("--force-R5 does not take a parameter");
+ }
+ if (keylen != 256)
+ {
+ usage("--force-R5 is invalid only for 256-bit keys");
+ }
+ else
+ {
+ force_R5 = true;
+ }
+ }
else if (strcmp(arg, "use-aes") == 0)
{
if (parameter == 0)
@@ -761,10 +794,16 @@ parse_encrypt_options(
{
usage("invalid -use-aes parameter");
}
- if (keylen == 40)
+ if ((keylen == 40) && result)
{
usage("use-aes is invalid for 40-bit keys");
}
+ else if ((keylen == 256) && (! result))
+ {
+ // qpdf would happily create files encrypted with RC4
+ // using /V=5, but Adobe reader can't read them.
+ usage("use-aes can't be disabled with 256-bit keys");
+ }
else
{
use_aes = result;
@@ -837,6 +876,21 @@ QPDFPageData::QPDFPageData(QPDF* qpdf, char const* range) :
this->selected_pages = parse_numrange(range, (int)this->orig_pages.size());
}
+static void parse_version(std::string const& full_version_string,
+ std::string& version, int& extension_level)
+{
+ PointerHolder<char> vp(true, QUtil::copy_string(full_version_string));
+ char* v = vp.getPointer();
+ char* p1 = strchr(v, '.');
+ char* p2 = (p1 ? strchr(1 + p1, '.') : 0);
+ if (p2 && *(p2 + 1))
+ {
+ *p2++ = '\0';
+ extension_level = atoi(p2);
+ }
+ version = v;
+}
+
int main(int argc, char* argv[])
{
whoami = QUtil::getWhoami(argv[0]);
@@ -862,7 +916,7 @@ int main(int argc, char* argv[])
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
std::cout
<< whoami << " version " << QPDF::QPDFVersion() << std::endl
- << "Copyright (c) 2005-2012 Jay Berkenbilt"
+ << "Copyright (c) 2005-2013 Jay Berkenbilt"
<< std::endl
<< "This software may be distributed under the terms of version 2 of the"
<< std::endl
@@ -902,6 +956,7 @@ int main(int argc, char* argv[])
qpdf_r3_print_e r3_print = qpdf_r3p_full;
qpdf_r3_modify_e r3_modify = qpdf_r3m_all;
bool force_V4 = false;
+ bool force_R5 = false;
bool cleartext_metadata = false;
bool use_aes = false;
@@ -985,7 +1040,7 @@ int main(int argc, char* argv[])
user_password, owner_password, keylen,
r2_print, r2_modify, r2_extract, r2_annotate,
r3_accessibility, r3_extract, r3_print, r3_modify,
- force_V4, cleartext_metadata, use_aes);
+ force_V4, cleartext_metadata, use_aes, force_R5);
encrypt = true;
decrypt = false;
copy_encryption = false;
@@ -1369,8 +1424,14 @@ int main(int argc, char* argv[])
std::cout << "checking " << infilename << std::endl;
try
{
- std::cout << "PDF Version: " << pdf.getPDFVersion()
- << std::endl;
+ int extension_level = pdf.getExtensionLevel();
+ std::cout << "PDF Version: " << pdf.getPDFVersion();
+ if (extension_level > 0)
+ {
+ std::cout << " extension level "
+ << pdf.getExtensionLevel();
+ }
+ std::cout << std::endl;
::show_encryption(pdf);
if (pdf.isLinearized())
{
@@ -1381,12 +1442,14 @@ int main(int argc, char* argv[])
else
{
std::cout << "File is not linearized\n";
- // calling flattenScalarReferences causes full
- // traversal of file, so any structural errors
- // would be exposed.
- pdf.flattenScalarReferences();
- // Also explicitly decode all streams.
- pdf.decodeStreams();
+ // Write the file no nowhere, uncompressing
+ // streams. This causes full file traversal
+ // and decoding of all streams we can decode.
+ QPDFWriter w(pdf);
+ Pl_Discard discard;
+ w.setOutputPipeline(&discard);
+ w.setStreamDataMode(qpdf_s_uncompress);
+ w.write();
okay = true;
}
}
@@ -1585,6 +1648,23 @@ int main(int argc, char* argv[])
r3_accessibility, r3_extract, r3_print, r3_modify);
}
}
+ else if (keylen == 256)
+ {
+ if (force_R5)
+ {
+ w.setR5EncryptionParameters(
+ user_password.c_str(), owner_password.c_str(),
+ r3_accessibility, r3_extract, r3_print, r3_modify,
+ !cleartext_metadata);
+ }
+ else
+ {
+ w.setR6EncryptionParameters(
+ user_password.c_str(), owner_password.c_str(),
+ r3_accessibility, r3_extract, r3_print, r3_modify,
+ !cleartext_metadata);
+ }
+ }
else
{
throw std::logic_error("bad encryption keylen");
@@ -1600,11 +1680,17 @@ int main(int argc, char* argv[])
}
if (! min_version.empty())
{
- w.setMinimumPDFVersion(min_version);
+ std::string version;
+ int extension_level = 0;
+ parse_version(min_version, version, extension_level);
+ w.setMinimumPDFVersion(version, extension_level);
}
if (! force_version.empty())
{
- w.forcePDFVersion(force_version);
+ std::string version;
+ int extension_level = 0;
+ parse_version(force_version, version, extension_level);
+ w.forcePDFVersion(version, extension_level);
}
w.write();
}
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index f966337..a0578f2 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -29,8 +29,7 @@ QPDF lin outlines in part 1
QPDF lin nshared_total > nshared_first_page 1
QPDF lin part 8 empty 1
QPDF lin check shared past first page 0
-QPDF opt flatten array scalar 0
-QPDF opt flatten dict scalar 0
+QPDFWriter flatten array null 0
main QTest implicit 0
main QTest indirect 1
main QTest null 0
@@ -117,7 +116,6 @@ qpdf unable to filter 0
QPDF_String non-trivial UTF-16 0
QPDF xref overwrite object 0
QPDF decoding error warning 0
-QPDF_Stream ignore non-dictionary DecodeParms 0
qpdf-c called qpdf_init 0
qpdf-c called qpdf_cleanup 0
qpdf-c called qpdf_more_warnings 0
@@ -152,7 +150,7 @@ qpdf-c called qpdf_allow_modify_form 0
qpdf-c called qpdf_allow_modify_annotation 0
qpdf-c called qpdf_allow_modify_other 0
qpdf-c called qpdf_allow_modify_all 0
-QPDFWriter increasing minimum version 0
+QPDFWriter increasing minimum version 1
QPDFWriter using forced PDF version 0
qpdf-c called qpdf_set_minimum_pdf_version 0
qpdf-c called qpdf_force_pdf_version 0
@@ -242,3 +240,22 @@ QPDF_Tokenizer EOF reading token 0
QPDF_Tokenizer EOF reading appendable token 0
QPDFWriter extra header text no newline 0
QPDFWriter extra header text add newline 0
+QPDF bogus 0 offset 0
+QPDF global offset 0
+QPDFWriter make stream key direct 0
+QPDFWriter copy V5 0
+QPDFWriter increasing extension level 0
+QPDFWriter make Extensions direct 0
+QPDFWriter make ADBE direct 1
+QPDFWriter preserve Extensions 0
+QPDFWriter create Extensions 1
+QPDFWriter remove ADBE 0
+QPDFWriter remove existing Extensions 0
+QPDFWriter preserve ADBE 0
+QPDF_encryption skip 0x28 0
+QPDF_encrypt crypt array 0
+QPDF_encryption CFM AESV3 0
+QPDFWriter remove Crypt 0
+qpdf-c called qpdf_get_pdf_extension_level 0
+qpdf-c called qpdf_set_r5_encryption_parameters 0
+qpdf-c called qpdf_set_r6_encryption_parameters 0
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index d12f064..bf62cee 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -111,6 +111,55 @@ $td->runtest("new stream",
show_ntests();
# ----------
+$td->notify("--- Extensions Dictionary Tests ---");
+my @ext_inputs = ('minimal.pdf', 'extensions-adbe.pdf',
+ 'extensions-other.pdf', 'extensions-adbe-other.pdf');
+my @new_versions = ('1.3', '1.6', '1.7.1', '1.7.2', '1.7.3',
+ '1.8', '1.8.0', '1.8.2', '1.8.5');
+$n_tests += (4 * @new_versions + 3) * @ext_inputs;
+foreach my $input (@ext_inputs)
+{
+ my $base = $input;
+ $base =~ s/\.pdf$//;
+ if ($base eq 'minimal')
+ {
+ $base = 'extensions-none';
+ }
+ foreach my $version (@new_versions)
+ {
+ foreach my $op (qw(min force))
+ {
+ $td->runtest("$input: $op version to $version",
+ {$td->COMMAND =>
+ "qpdf --static-id" .
+ " --$op-version=$version $input a.pdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+ $td->runtest("check version information ($op $version)",
+ {$td->COMMAND => "test_driver 34 a.pdf"},
+ {$td->FILE => "$base-$op-$version.out",
+ $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+ if (($op eq 'force') && ($version eq '1.8.5'))
+ {
+ # Look at the actual file for a few cases to make sure
+ # qdf and non-qdf output are okay
+ $td->runtest("check file",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "$base-$op-$version.pdf"});
+ $td->runtest("$input: $op version to $version",
+ {$td->COMMAND =>
+ "qpdf --qdf --static-id" .
+ " --$op-version=$version $input a.qdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+ $td->runtest("check file",
+ {$td->FILE => "a.qdf"},
+ {$td->FILE => "$base-$op-$version.qdf"});
+ }
+ }
+ }
+}
+show_ntests();
+# ----------
$td->notify("--- Page API Tests ---");
$n_tests += 9;
@@ -147,9 +196,10 @@ $td->runtest("remove page we don't have",
{$td->COMMAND => "test_driver 22 page_api_1.pdf"},
{$td->FILE => "page_api_1.out2", $td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
+show_ntests();
# ----------
$td->notify("--- Miscellaneous Tests ---");
-$n_tests += 53;
+$n_tests += 57;
$td->runtest("qpdf version",
{$td->COMMAND => "qpdf --version"},
@@ -265,8 +315,8 @@ $td->runtest("C API: min/force versions",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("C check version 1",
- {$td->COMMAND => "qpdf --check a.pdf"},
- {$td->FILE => "min-version.out",
+ {$td->COMMAND => "qpdf-ctest 1 a.pdf '' ''"},
+ {$td->FILE => "c-min-version.out",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("C check version 2",
@@ -403,6 +453,21 @@ $td->runtest("check output",
$td->runtest("check output",
{$td->FILE => "d.pdf"},
{$td->FILE => "extra-header-lin-newline.pdf"});
+$td->runtest("output to custom pipeline",
+ {$td->COMMAND => "test_driver 33 minimal.pdf"},
+ {$td->STRING => "test 33 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check output",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "custom-pipeline.pdf"});
+$td->runtest("object with zero offset",
+ {$td->COMMAND => "qpdf --check zero-offset.pdf"},
+ {$td->FILE => "zero-offset.out", $td->EXIT_STATUS => 3},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check file with leading junk",
+ {$td->COMMAND => "qpdf --check leading-junk.pdf"},
+ {$td->FILE => "leading-junk.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
show_ntests();
# ----------
@@ -451,6 +516,7 @@ foreach my $d (@nrange_tests)
$td->NORMALIZE_NEWLINES);
}
+show_ntests();
# ----------
$td->notify("--- Merging and Splitting ---");
$n_tests += 6;
@@ -498,6 +564,7 @@ $td->runtest("avoid respecification of password",
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "pages-copy-encryption.pdf"});
+show_ntests();
# ----------
$td->notify("--- PDF From Scratch ---");
$n_tests += 2;
@@ -509,6 +576,7 @@ $td->runtest("basic qpdf from scratch",
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "from-scratch-0.pdf"});
+show_ntests();
# ----------
$td->notify("--- Copy Foreign Objects ---");
$n_tests += 7;
@@ -531,6 +599,7 @@ $td->runtest("copy objects error",
{$td->FILE => "copy-foreign-objects-errors.out",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
+show_ntests();
# ----------
$td->notify("--- Error Condition Tests ---");
# $n_tests incremented after initialization of badfiles below.
@@ -863,7 +932,7 @@ for (my $n = 16; $n <= 19; ++$n)
show_ntests();
# ----------
$td->notify("--- Specific File Tests ---");
-$n_tests += 4;
+$n_tests += 5;
$n_compare_pdfs += 1;
# Special PDF files that caused problems at some point
@@ -892,6 +961,10 @@ $td->runtest("damaged stream",
{$td->COMMAND => "qpdf --check damaged-stream.pdf"},
{$td->FILE => "damaged-stream.out", $td->EXIT_STATUS => 3},
$td->NORMALIZE_NEWLINES);
+$td->runtest("damaged stream (C)",
+ {$td->COMMAND => "qpdf-ctest 2 damaged-stream.pdf '' a.pdf"},
+ {$td->FILE => "damaged-stream-c-check.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
show_ntests();
# ----------
@@ -1121,9 +1194,15 @@ foreach my $base (@to_linearize)
foreach my $omode (qw(disable preserve generate))
{
my $oarg = "-object-streams=$omode";
+ my $sdarg = "";
+ if (($base eq 'lin-special') || ($base eq 'object-stream'))
+ {
+ $sdarg = "--stream-data=uncompress";
+ }
$td->runtest("linearize $base ($omode)",
{$td->COMMAND =>
- "qpdf -linearize $oarg --static-id $base.pdf a.pdf"},
+ "qpdf -linearize $oarg $sdarg" .
+ " --static-id $base.pdf a.pdf"},
{$td->STRING => "",
$td->EXIT_STATUS => 0});
$td->runtest("check linearization",
@@ -1140,12 +1219,12 @@ foreach my $base (@to_linearize)
# the table values.
$td->runtest("relinearize $base 1",
{$td->COMMAND =>
- "qpdf -linearize --static-id a.pdf b.pdf"},
+ "qpdf -linearize $sdarg --static-id a.pdf b.pdf"},
{$td->STRING => "",
$td->EXIT_STATUS => 0});
$td->runtest("relinearize $base 2",
{$td->COMMAND =>
- "qpdf -linearize --static-id b.pdf c.pdf"},
+ "qpdf -linearize $sdarg --static-id b.pdf c.pdf"},
{$td->STRING => "",
$td->EXIT_STATUS => 0});
$td->runtest("compare files ($omode)",
@@ -1171,6 +1250,10 @@ $td->notify("--- Encryption Tests ---");
# resulting files were saved and manually checked with Acrobat 5.0 to
# ensure that the security settings were as intended.
+# The enc-XI-file.pdf files were treated the same way but with Acrobat
+# XI instead of Acrobat 5.0. They were used to create test files with
+# newer encryption formats.
+
# Values: basename, password, encryption flags, /P Encrypt key,
# extract-for-accessibility, extract-for-any-purpose,
# print-low-res, print-high-res, modify-assembly, modify-forms,
@@ -1214,9 +1297,25 @@ my @encrypted_files =
'', -4,
1, 1, 1, 1, 1, 1, 1, 1, 1],
['long-password', 'asdf asdf asdf asdf asdf asdf qwer'],
- ['long-password', 'asdf asdf asdf asdf asdf asdf qw']);
+ ['long-password', 'asdf asdf asdf asdf asdf asdf qw'],
+ ['XI-base', ''],
+ ['XI-R6,V5,O=master', '',
+ '-extract=n -print=none -modify=assembly', -2368,
+ 1, 0, 0, 0, 1, 0, 0, 0, 0],
+ ['XI-R6,V5,O=master', 'master',
+ '-extract=n -print=none -modify=assembly', -2368,
+ 1, 0, 0, 0, 1, 0, 0, 0, 0],
+ ['XI-R6,V5,U=view,O=master', 'view',
+ '-print=low', -2052,
+ 1, 1, 1, 0, 1, 1, 1, 1, 1],
+ ['XI-R6,V5,U=view,O=master', 'master',
+ '-print=low', -2052,
+ 1, 1, 1, 0, 1, 1, 1, 1, 1],
+ ['XI-long-password', 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm'],
+ ['XI-long-password', 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcv'],
+ );
-$n_tests += 3 + (2 * (@encrypted_files)) + (6 * (@encrypted_files - 3)) + 9;
+$n_tests += 5 + (2 * (@encrypted_files)) + (6 * (@encrypted_files - 6)) + 9;
$td->runtest("encrypted file",
{$td->COMMAND => "test_driver 2 U25A0.pdf"},
@@ -1233,6 +1332,19 @@ $td->runtest("recheck encrypted file",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
+# Test that long passwords that are one character too short fail. We
+# test the truncation cases in the loop below by using passwords
+# longer than the supported length.
+$td->runtest("significant password characters (V < 5)",
+ {$td->COMMAND => "qpdf --check enc-long-password.pdf" .
+ " --password='asdf asdf asdf asdf asdf asdf q'"},
+ {$td->REGEXP => ".*invalid password.*", $td->EXIT_STATUS => 2});
+$td->runtest("significant password characters (V = 5)",
+ {$td->COMMAND => "qpdf --check enc-XI-long-password.pdf" .
+ " --password=qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxc"},
+ {$td->REGEXP => ".*invalid password.*", $td->EXIT_STATUS => 2});
+
+my $enc_base = undef;
foreach my $d (@encrypted_files)
{
my ($file, $pass, $xeflags, $P,
@@ -1251,17 +1363,26 @@ foreach my $d (@encrypted_files)
"modify annotations: " . &$f($modifyannot) . "\n" .
"modify other: " . &$f($modifyother) . "\n" .
"modify anything: " . &$f($modifyall) . "\n";
+ if ($file =~ m/XI-/)
+ {
+ $enc_details .=
+ "stream encryption method: AESv3\n" .
+ "string encryption method: AESv3\n" .
+ "file encryption method: AESv3\n";
+ }
# Test writing to stdout
$td->runtest("decrypt $file",
{$td->COMMAND =>
- "qpdf --static-id -qdf --no-original-object-ids" .
+ "qpdf --static-id -qdf --object-streams=disable" .
+ " --no-original-object-ids" .
" --password=\"$pass\" enc-$file.pdf -" .
" > $file.enc"},
{$td->STRING => "",
$td->EXIT_STATUS => 0});
- if ($file eq 'base')
+ if ($file =~ m/base$/)
{
+ $enc_base = $file;
$td->runtest("check ID",
{$td->COMMAND => "perl check-ID.pl $file.enc"},
{$td->STRING => "ID okay\n",
@@ -1271,20 +1392,27 @@ foreach my $d (@encrypted_files)
else
{
$td->runtest("check against base",
- {$td->COMMAND => "./diff-encrypted base.enc $file.enc"},
+ {$td->COMMAND =>
+ "./diff-encrypted $enc_base.enc $file.enc"},
{$td->STRING => "okay\n",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
}
- if ($file =~ m/^R(\d),V(\d)(?:,U=(\w+))?(?:,O=(\w+))?$/)
+ if ($file =~ m/^(?:XI-)?R(\d),V(\d)(?:,U=(\w+))?(?:,O=(\w+))?$/)
{
my $R = $1;
my $V = $2;
my $upass = $3 || "";
my $opass = $4 || "";
- my $bits = (($V == 2) ? 128 : 40);
+ my $bits = (($V == 5) ? 256 : ($V == 2) ? 128 : 40);
my $eflags = "-encrypt \"$upass\" \"$opass\" $bits $xeflags --";
+ if (($pass ne $upass) && ($V >= 5))
+ {
+ # V >= 5 can no longer recover user password with owner
+ # password.
+ $upass = "";
+ }
$td->runtest("encrypt $file",
{$td->COMMAND =>
"qpdf --static-id --no-original-object-ids -qdf" .
@@ -1344,29 +1472,52 @@ $td->runtest("C API: invalid password",
$td->NORMALIZE_NEWLINES);
my @cenc = (
- [11, 'hybrid-xref.pdf', "''", 'r2', ""],
- [12, 'hybrid-xref.pdf', "''", 'r3', ""],
- [15, 'hybrid-xref.pdf', "''", 'r4', ""],
+ [11, 'hybrid-xref.pdf', "''", 'r2', "", ""],
+ [12, 'hybrid-xref.pdf', "''", 'r3', "", ""],
+ [15, 'hybrid-xref.pdf', "''", 'r4', "", ""],
+ [17, 'hybrid-xref.pdf', "''", 'r5', "", "owner3"],
+ [18, 'hybrid-xref.pdf', "''", 'r6', "", "user4"],
[13, 'c-r2.pdf', 'user1', 'decrypt with user',
- "user password: user1\n"],
+ "user password: user1\n", ""],
[13, 'c-r3.pdf', 'owner2', 'decrypt with owner',
- "user password: user2\n"],
+ "user password: user2\n", ""],
+ [13, 'c-r5-in.pdf', 'user3', 'decrypt R5 with user',
+ "user password: user3\n", ""],
+ [13, 'c-r6-in.pdf', 'owner4', 'decrypt R6 with owner',
+ "user password: \n", ""],
);
$n_tests += 2 * @cenc;
foreach my $d (@cenc)
{
- my ($n, $infile, $pass, $description, $output) = @$d;
+ my ($n, $infile, $pass, $description, $output, $checkpass) = @$d;
my $outfile = $description;
$outfile =~ s/ /-/g;
- $outfile = "c-$outfile.pdf";
+ my $pdf_outfile = "c-$outfile.pdf";
+ my $check_outfile = "c-$outfile.out";
$td->runtest("C API encryption: $description",
{$td->COMMAND => "qpdf-ctest $n $infile $pass a.pdf"},
{$td->STRING => $output, $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
- $td->runtest("check $description",
- {$td->FILE => "a.pdf"},
- {$td->FILE => $outfile});
+ if (-f $pdf_outfile)
+ {
+ $td->runtest("check $description content",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => $pdf_outfile});
+ }
+ else
+ {
+ # QPDF doesn't provide any way to make the random bits in
+ # /Perms static, so we have no way to predictably create a
+ # /V=5 encrypted file. It's not worth adding this...the test
+ # suite is adequate without having a statically predictable
+ # file.
+ $td->runtest("check $description",
+ {$td->COMMAND =>
+ "qpdf --check a.pdf --password=$checkpass"},
+ {$td->FILE => $check_outfile, $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+ }
}
# Test combinations of linearization and encryption. Note that we do
@@ -1409,7 +1560,7 @@ $td->runtest("check linearization",
$td->NORMALIZE_NEWLINES);
# Test AES encryption in various ways.
-$n_tests += 14;
+$n_tests += 18;
$td->runtest("encrypt with AES",
{$td->COMMAND => "qpdf --encrypt '' '' 128 --use-aes=y --" .
" enc-base.pdf a.pdf"},
@@ -1469,6 +1620,24 @@ $td->runtest("make sure there is no xref stream",
{$td->COMMAND => "grep /ObjStm b.pdf | wc -l"},
{$td->REGEXP => "\\s*0\\s*", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
+$td->runtest("encrypt with V=5,R=5",
+ {$td->COMMAND =>
+ "qpdf --encrypt user owner 256 --force-R5 -- " .
+ "minimal.pdf a.pdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+$td->runtest("check encryption",
+ {$td->COMMAND => "qpdf --check a.pdf --password=owner"},
+ {$td->FILE => "V5R5.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("encrypt with V=5,R=6",
+ {$td->COMMAND =>
+ "qpdf --encrypt user owner 256 -- " .
+ "minimal.pdf a.pdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+$td->runtest("check encryption",
+ {$td->COMMAND => "qpdf --check a.pdf --password=user"},
+ {$td->FILE => "V5R6.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
# Look at some actual V4 files
$n_tests += 14;
@@ -1550,6 +1719,53 @@ $td->runtest("compare qdf",
{$td->STRING => "okay\n", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
+# Files with attachments
+my @attachments = (
+ 'enc-XI-attachments-base.pdf',
+ 'enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf',
+ 'enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf');
+$n_tests += 4 * @attachments + 3;
+foreach my $f (@attachments)
+{
+ my $pass = '';
+ my $tpass = '';
+ if ($f =~ m/U=([^,\.]+)/)
+ {
+ $pass = "--password=$1";
+ $tpass = $1;
+ }
+ $td->runtest("decrypt $f",
+ {$td->COMMAND => "qpdf --decrypt $pass $f a.pdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+ $td->runtest("extract attachments",
+ {$td->COMMAND => "test_driver 35 a.pdf"},
+ {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+ $td->runtest("copy $f",
+ {$td->COMMAND => "qpdf $pass $f a.pdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+ $td->runtest("extract attachments",
+ {$td->COMMAND => "test_driver 35 a.pdf $tpass"},
+ {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+}
+$td->runtest("unfilterable with crypt",
+ {$td->COMMAND =>
+ "test_driver 36 unfilterable-with-crypt.pdf attachment"},
+ {$td->FILE => "unfilterable-with-crypt-before.out",
+ $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+unlink "a.pdf";
+$td->runtest("decrypt file",
+ {$td->COMMAND => "qpdf -decrypt --password=attachment" .
+ " unfilterable-with-crypt.pdf a.pdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+$td->runtest("copy of unfilterable with crypt",
+ {$td->COMMAND =>
+ "test_driver 36 a.pdf attachment"},
+ {$td->FILE => "unfilterable-with-crypt-after.out",
+ $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
show_ntests();
# ----------
diff --git a/qpdf/qtest/qpdf/V5R5.out b/qpdf/qtest/qpdf/V5R5.out
new file mode 100644
index 0000000..91fcd99
--- /dev/null
+++ b/qpdf/qtest/qpdf/V5R5.out
@@ -0,0 +1,20 @@
+checking a.pdf
+PDF Version: 1.7 extension level 3
+R = 5
+P = -4
+User password =
+extract for accessibility: allowed
+extract for any purpose: allowed
+print low resolution: allowed
+print high resolution: allowed
+modify document assembly: allowed
+modify forms: allowed
+modify annotations: allowed
+modify other: allowed
+modify anything: allowed
+stream encryption method: AESv3
+string encryption method: AESv3
+file encryption method: AESv3
+File is not linearized
+No syntax or stream encoding errors found; the file may still contain
+errors that qpdf cannot detect
diff --git a/qpdf/qtest/qpdf/V5R6.out b/qpdf/qtest/qpdf/V5R6.out
new file mode 100644
index 0000000..fa5569a
--- /dev/null
+++ b/qpdf/qtest/qpdf/V5R6.out
@@ -0,0 +1,20 @@
+checking a.pdf
+PDF Version: 1.7 extension level 8
+R = 6
+P = -4
+User password = user
+extract for accessibility: allowed
+extract for any purpose: allowed
+print low resolution: allowed
+print high resolution: allowed
+modify document assembly: allowed
+modify forms: allowed
+modify annotations: allowed
+modify other: allowed
+modify anything: allowed
+stream encryption method: AESv3
+string encryption method: AESv3
+file encryption method: AESv3
+File is not linearized
+No syntax or stream encoding errors found; the file may still contain
+errors that qpdf cannot detect
diff --git a/qpdf/qtest/qpdf/attachments.out b/qpdf/qtest/qpdf/attachments.out
new file mode 100644
index 0000000..b4caa33
--- /dev/null
+++ b/qpdf/qtest/qpdf/attachments.out
@@ -0,0 +1,6 @@
+attachment1.txt:
+This is the first attachment.
+--END--
+attachment2.png:
+.PNG........IHDR...1 (2620 bytes)--END--
+test 35 done
diff --git a/qpdf/qtest/qpdf/c-decrypt-R5-with-user.pdf b/qpdf/qtest/qpdf/c-decrypt-R5-with-user.pdf
new file mode 100644
index 0000000..e3ff67e
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-decrypt-R5-with-user.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/c-decrypt-R6-with-owner.pdf b/qpdf/qtest/qpdf/c-decrypt-R6-with-owner.pdf
new file mode 100644
index 0000000..8e5d9b8
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-decrypt-R6-with-owner.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/c-min-version.out b/qpdf/qtest/qpdf/c-min-version.out
new file mode 100644
index 0000000..94558ac
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-min-version.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 8
+linearized: 0
+encrypted: 0
diff --git a/qpdf/qtest/qpdf/c-r5-in.pdf b/qpdf/qtest/qpdf/c-r5-in.pdf
new file mode 100644
index 0000000..19c67a3
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-r5-in.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/c-r5.out b/qpdf/qtest/qpdf/c-r5.out
new file mode 100644
index 0000000..b8789e2
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-r5.out
@@ -0,0 +1,20 @@
+checking a.pdf
+PDF Version: 1.7 extension level 3
+R = 5
+P = -2052
+User password =
+extract for accessibility: allowed
+extract for any purpose: allowed
+print low resolution: allowed
+print high resolution: not allowed
+modify document assembly: allowed
+modify forms: allowed
+modify annotations: allowed
+modify other: allowed
+modify anything: allowed
+stream encryption method: AESv3
+string encryption method: AESv3
+file encryption method: AESv3
+File is not linearized
+No syntax or stream encoding errors found; the file may still contain
+errors that qpdf cannot detect
diff --git a/qpdf/qtest/qpdf/c-r6-in.pdf b/qpdf/qtest/qpdf/c-r6-in.pdf
new file mode 100644
index 0000000..d8aa5d6
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-r6-in.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/c-r6.out b/qpdf/qtest/qpdf/c-r6.out
new file mode 100644
index 0000000..1f99b97
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-r6.out
@@ -0,0 +1,20 @@
+checking a.pdf
+PDF Version: 1.7 extension level 8
+R = 6
+P = -2052
+User password = user4
+extract for accessibility: allowed
+extract for any purpose: allowed
+print low resolution: allowed
+print high resolution: not allowed
+modify document assembly: allowed
+modify forms: allowed
+modify annotations: allowed
+modify other: allowed
+modify anything: allowed
+stream encryption method: AESv3
+string encryption method: AESv3
+file encryption method: AESv3
+File is not linearized
+No syntax or stream encoding errors found; the file may still contain
+errors that qpdf cannot detect
diff --git a/qpdf/qtest/qpdf/custom-pipeline.pdf b/qpdf/qtest/qpdf/custom-pipeline.pdf
new file mode 100644
index 0000000..b8c692e
--- /dev/null
+++ b/qpdf/qtest/qpdf/custom-pipeline.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/damaged-stream-c-check.out b/qpdf/qtest/qpdf/damaged-stream-c-check.out
index b172362..501806d 100644
--- a/qpdf/qtest/qpdf/damaged-stream-c-check.out
+++ b/qpdf/qtest/qpdf/damaged-stream-c-check.out
@@ -1 +1,5 @@
-warning: damaged-stream.pdf: offset 426: error decoding stream data for object 5 0: LZWDecoder: bad code received
+warning: damaged-stream.pdf (file position 426): error decoding stream data for object 5 0: LZWDecoder: bad code received
+ code: 5
+ file: damaged-stream.pdf
+ pos : 426
+ text: error decoding stream data for object 5 0: LZWDecoder: bad code received
diff --git a/qpdf/qtest/qpdf/diff-encrypted b/qpdf/qtest/qpdf/diff-encrypted
index a68822e..d730536 100755
--- a/qpdf/qtest/qpdf/diff-encrypted
+++ b/qpdf/qtest/qpdf/diff-encrypted
@@ -1,5 +1,5 @@
#!/bin/sh
-lines=$(expr + $(diff $1 $2 | egrep '^[<>]' | egrep -v 'Date' | wc -l))
+lines=$(expr + $(diff $1 $2 | egrep '^[<>]' | egrep -v '(Date|InstanceID)' | wc -l))
if [ "$lines" = "0" ]; then
echo okay
else
diff --git a/qpdf/qtest/qpdf/enc-XI-R6,V5,O=master.pdf b/qpdf/qtest/qpdf/enc-XI-R6,V5,O=master.pdf
new file mode 100644
index 0000000..a3774a4
--- /dev/null
+++ b/qpdf/qtest/qpdf/enc-XI-R6,V5,O=master.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf b/qpdf/qtest/qpdf/enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf
new file mode 100644
index 0000000..7c1a02f
--- /dev/null
+++ b/qpdf/qtest/qpdf/enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,O=master.pdf b/qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,O=master.pdf
new file mode 100644
index 0000000..2bb7265
--- /dev/null
+++ b/qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,O=master.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf b/qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf
new file mode 100644
index 0000000..5adff44
--- /dev/null
+++ b/qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/enc-XI-attachments-base.pdf b/qpdf/qtest/qpdf/enc-XI-attachments-base.pdf
new file mode 100644
index 0000000..4356b7e
--- /dev/null
+++ b/qpdf/qtest/qpdf/enc-XI-attachments-base.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/enc-XI-base.pdf b/qpdf/qtest/qpdf/enc-XI-base.pdf
new file mode 100644
index 0000000..695c463
--- /dev/null
+++ b/qpdf/qtest/qpdf/enc-XI-base.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/enc-XI-long-password.pdf b/qpdf/qtest/qpdf/enc-XI-long-password.pdf
new file mode 100644
index 0000000..f4f206e
--- /dev/null
+++ b/qpdf/qtest/qpdf/enc-XI-long-password.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/extensions-adbe-force-1.3.out b/qpdf/qtest/qpdf/extensions-adbe-force-1.3.out
new file mode 100644
index 0000000..3795c64
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-force-1.3.out
@@ -0,0 +1,4 @@
+version: 1.3
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-force-1.6.out b/qpdf/qtest/qpdf/extensions-adbe-force-1.6.out
new file mode 100644
index 0000000..762063b
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-force-1.6.out
@@ -0,0 +1,4 @@
+version: 1.6
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-force-1.7.1.out b/qpdf/qtest/qpdf/extensions-adbe-force-1.7.1.out
new file mode 100644
index 0000000..9616863
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-force-1.7.1.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 1
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 1 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-force-1.7.2.out b/qpdf/qtest/qpdf/extensions-adbe-force-1.7.2.out
new file mode 100644
index 0000000..4571bf2
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-force-1.7.2.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 /URL (http://something.adobe.com) >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-force-1.7.3.out b/qpdf/qtest/qpdf/extensions-adbe-force-1.7.3.out
new file mode 100644
index 0000000..7632120
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-force-1.7.3.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 3
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-force-1.8.0.out b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.0.out
new file mode 100644
index 0000000..491fdb7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.0.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-force-1.8.2.out b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.2.out
new file mode 100644
index 0000000..7823a1d
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.2.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 2
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 2 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.out b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.out
new file mode 100644
index 0000000..2d78af7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 5
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.pdf b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.pdf
new file mode 100644
index 0000000..7dbab51
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.qdf b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.qdf
new file mode 100644
index 0000000..efd08ba
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.qdf
@@ -0,0 +1,107 @@
+%PDF-1.8
+%¿÷¢þ
+%QDF-1.0
+
+%% Original object ID: 1 0
+1 0 obj
+<<
+ /Extensions <<
+ /ADBE <<
+ /BaseVersion /1.8
+ /ExtensionLevel 5
+ >>
+ >>
+ /Pages 2 0 R
+ /Type /Catalog
+>>
+endobj
+
+%% Original object ID: 2 0
+2 0 obj
+<<
+ /Count 1
+ /Kids [
+ 3 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+%% Original object ID: 3 0
+3 0 obj
+<<
+ /Contents 4 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 6 0 R
+ >>
+ /ProcSet 7 0 R
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+%% Original object ID: 4 0
+4 0 obj
+<<
+ /Length 5 0 R
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+5 0 obj
+44
+endobj
+
+%% Original object ID: 6 0
+6 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+%% Original object ID: 7 0
+7 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+xref
+0 8
+0000000000 65535 f
+0000000052 00000 n
+0000000223 00000 n
+0000000332 00000 n
+0000000574 00000 n
+0000000673 00000 n
+0000000719 00000 n
+0000000864 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 8
+ /ID [<e42c124696c09bd2cacaf7196e9c88a0><31415926535897932384626433832795>]
+>>
+startxref
+899
+%%EOF
diff --git a/qpdf/qtest/qpdf/extensions-adbe-force-1.8.out b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.out
new file mode 100644
index 0000000..491fdb7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-force-1.8.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-min-1.3.out b/qpdf/qtest/qpdf/extensions-adbe-min-1.3.out
new file mode 100644
index 0000000..4571bf2
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-min-1.3.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 /URL (http://something.adobe.com) >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-min-1.6.out b/qpdf/qtest/qpdf/extensions-adbe-min-1.6.out
new file mode 100644
index 0000000..4571bf2
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-min-1.6.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 /URL (http://something.adobe.com) >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-min-1.7.1.out b/qpdf/qtest/qpdf/extensions-adbe-min-1.7.1.out
new file mode 100644
index 0000000..4571bf2
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-min-1.7.1.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 /URL (http://something.adobe.com) >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-min-1.7.2.out b/qpdf/qtest/qpdf/extensions-adbe-min-1.7.2.out
new file mode 100644
index 0000000..4571bf2
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-min-1.7.2.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 /URL (http://something.adobe.com) >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-min-1.7.3.out b/qpdf/qtest/qpdf/extensions-adbe-min-1.7.3.out
new file mode 100644
index 0000000..7632120
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-min-1.7.3.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 3
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-min-1.8.0.out b/qpdf/qtest/qpdf/extensions-adbe-min-1.8.0.out
new file mode 100644
index 0000000..491fdb7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-min-1.8.0.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-min-1.8.2.out b/qpdf/qtest/qpdf/extensions-adbe-min-1.8.2.out
new file mode 100644
index 0000000..7823a1d
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-min-1.8.2.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 2
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 2 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-min-1.8.5.out b/qpdf/qtest/qpdf/extensions-adbe-min-1.8.5.out
new file mode 100644
index 0000000..2d78af7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-min-1.8.5.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 5
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-min-1.8.out b/qpdf/qtest/qpdf/extensions-adbe-min-1.8.out
new file mode 100644
index 0000000..491fdb7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-min-1.8.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-force-1.3.out b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.3.out
new file mode 100644
index 0000000..1446741
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.3.out
@@ -0,0 +1,4 @@
+version: 1.3
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-force-1.6.out b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.6.out
new file mode 100644
index 0000000..0cc726a
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.6.out
@@ -0,0 +1,4 @@
+version: 1.6
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.1.out b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.1.out
new file mode 100644
index 0000000..a714b14
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.1.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 1
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 1 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.2.out b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.2.out
new file mode 100644
index 0000000..ed0bf57
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.2.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.3.out b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.3.out
new file mode 100644
index 0000000..f13ea01
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.7.3.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 3
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.0.out b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.0.out
new file mode 100644
index 0000000..d121666
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.0.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.2.out b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.2.out
new file mode 100644
index 0000000..00858aa
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.2.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 2
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 2 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.out b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.out
new file mode 100644
index 0000000..dcf87e8
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 5
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.pdf b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.pdf
new file mode 100644
index 0000000..39ea71e
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.qdf b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.qdf
new file mode 100644
index 0000000..e391af0
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.qdf
@@ -0,0 +1,111 @@
+%PDF-1.8
+%¿÷¢þ
+%QDF-1.0
+
+%% Original object ID: 1 0
+1 0 obj
+<<
+ /Extensions <<
+ /ADBE <<
+ /BaseVersion /1.8
+ /ExtensionLevel 5
+ >>
+ /Potato <<
+ /BaseVersion /3.14159
+ /ExtensionLevel 16059
+ >>
+ >>
+ /Pages 2 0 R
+ /Type /Catalog
+>>
+endobj
+
+%% Original object ID: 2 0
+2 0 obj
+<<
+ /Count 1
+ /Kids [
+ 3 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+%% Original object ID: 3 0
+3 0 obj
+<<
+ /Contents 4 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 6 0 R
+ >>
+ /ProcSet 7 0 R
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+%% Original object ID: 4 0
+4 0 obj
+<<
+ /Length 5 0 R
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+5 0 obj
+44
+endobj
+
+%% Original object ID: 6 0
+6 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+%% Original object ID: 7 0
+7 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+xref
+0 8
+0000000000 65535 f
+0000000052 00000 n
+0000000301 00000 n
+0000000410 00000 n
+0000000652 00000 n
+0000000751 00000 n
+0000000797 00000 n
+0000000942 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 8
+ /ID [<484577389048fa45fc00a1f5b434efa5><31415926535897932384626433832795>]
+>>
+startxref
+977
+%%EOF
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.out b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.out
new file mode 100644
index 0000000..d121666
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-min-1.3.out b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.3.out
new file mode 100644
index 0000000..ed0bf57
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.3.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-min-1.6.out b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.6.out
new file mode 100644
index 0000000..ed0bf57
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.6.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.1.out b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.1.out
new file mode 100644
index 0000000..ed0bf57
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.1.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.2.out b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.2.out
new file mode 100644
index 0000000..ed0bf57
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.2.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.3.out b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.3.out
new file mode 100644
index 0000000..f13ea01
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.7.3.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 3
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.0.out b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.0.out
new file mode 100644
index 0000000..d121666
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.0.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.2.out b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.2.out
new file mode 100644
index 0000000..00858aa
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.2.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 2
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 2 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.5.out b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.5.out
new file mode 100644
index 0000000..dcf87e8
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.5.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 5
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.out b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.out
new file mode 100644
index 0000000..d121666
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other-min-1.8.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-adbe-other.pdf b/qpdf/qtest/qpdf/extensions-adbe-other.pdf
new file mode 100644
index 0000000..367f376
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe-other.pdf
@@ -0,0 +1,104 @@
+%PDF-1.7
+%¿÷¢þ
+%QDF-1.0
+
+1 0 obj
+<<
+ /Pages 2 0 R
+ /Type /Catalog
+ /Extensions <<
+ /ADBE 8 0 R
+ /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >>
+ >>
+>>
+endobj
+
+2 0 obj
+<<
+ /Count 1
+ /Kids [
+ 3 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+3 0 obj
+<<
+ /Contents 4 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 6 0 R
+ >>
+ /ProcSet 7 0 R
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+4 0 obj
+<<
+ /Length 5 0 R
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+5 0 obj
+44
+endobj
+
+6 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+7 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+8 0 obj
+<< /BaseVersion /1.7 /ExtensionLevel 2 >>
+endobj
+
+xref
+0 9
+0000000000 65535 f
+0000000025 00000 n
+0000000179 00000 n
+0000000261 00000 n
+0000000476 00000 n
+0000000575 00000 n
+0000000594 00000 n
+0000000712 00000 n
+0000000747 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 9
+ /ID [<484577389048fa45fc00a1f5b434efa5><484577389048fa45fc00a1f5b434efa5>]
+>>
+startxref
+805
+%%EOF
diff --git a/qpdf/qtest/qpdf/extensions-adbe.pdf b/qpdf/qtest/qpdf/extensions-adbe.pdf
new file mode 100644
index 0000000..4d13cf7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-adbe.pdf
@@ -0,0 +1,106 @@
+%PDF-1.7
+%¿÷¢þ
+%QDF-1.0
+
+1 0 obj
+<<
+ /Pages 2 0 R
+ /Type /Catalog
+ /Extensions 8 0 R
+>>
+endobj
+
+2 0 obj
+<<
+ /Count 1
+ /Kids [
+ 3 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+3 0 obj
+<<
+ /Contents 4 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 6 0 R
+ >>
+ /ProcSet 7 0 R
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+4 0 obj
+<<
+ /Length 5 0 R
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+5 0 obj
+44
+endobj
+
+6 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+7 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+8 0 obj
+<< /ADBE 9 0 R >>
+endobj
+
+9 0 obj
+<< /BaseVersion /1.7 /ExtensionLevel 2 /URL (http://something.adobe.com) >>
+endobj
+
+xref
+0 10
+0000000000 65535 f
+0000000025 00000 n
+0000000099 00000 n
+0000000181 00000 n
+0000000396 00000 n
+0000000495 00000 n
+0000000514 00000 n
+0000000632 00000 n
+0000000667 00000 n
+0000000701 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 10
+ /ID [<e42c124696c09bd2cacaf7196e9c88a0><e42c124696c09bd2cacaf7196e9c88a0>]
+>>
+startxref
+793
+%%EOF
diff --git a/qpdf/qtest/qpdf/extensions-none-force-1.3.out b/qpdf/qtest/qpdf/extensions-none-force-1.3.out
new file mode 100644
index 0000000..3795c64
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-force-1.3.out
@@ -0,0 +1,4 @@
+version: 1.3
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-force-1.6.out b/qpdf/qtest/qpdf/extensions-none-force-1.6.out
new file mode 100644
index 0000000..762063b
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-force-1.6.out
@@ -0,0 +1,4 @@
+version: 1.6
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-force-1.7.1.out b/qpdf/qtest/qpdf/extensions-none-force-1.7.1.out
new file mode 100644
index 0000000..9616863
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-force-1.7.1.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 1
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 1 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-force-1.7.2.out b/qpdf/qtest/qpdf/extensions-none-force-1.7.2.out
new file mode 100644
index 0000000..234701c
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-force-1.7.2.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-force-1.7.3.out b/qpdf/qtest/qpdf/extensions-none-force-1.7.3.out
new file mode 100644
index 0000000..7632120
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-force-1.7.3.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 3
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-force-1.8.0.out b/qpdf/qtest/qpdf/extensions-none-force-1.8.0.out
new file mode 100644
index 0000000..491fdb7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-force-1.8.0.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-force-1.8.2.out b/qpdf/qtest/qpdf/extensions-none-force-1.8.2.out
new file mode 100644
index 0000000..7823a1d
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-force-1.8.2.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 2
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 2 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-force-1.8.5.out b/qpdf/qtest/qpdf/extensions-none-force-1.8.5.out
new file mode 100644
index 0000000..2d78af7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-force-1.8.5.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 5
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-force-1.8.5.pdf b/qpdf/qtest/qpdf/extensions-none-force-1.8.5.pdf
new file mode 100644
index 0000000..52f5623
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-force-1.8.5.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/extensions-none-force-1.8.5.qdf b/qpdf/qtest/qpdf/extensions-none-force-1.8.5.qdf
new file mode 100644
index 0000000..230c1c0
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-force-1.8.5.qdf
@@ -0,0 +1,107 @@
+%PDF-1.8
+%¿÷¢þ
+%QDF-1.0
+
+%% Original object ID: 1 0
+1 0 obj
+<<
+ /Extensions <<
+ /ADBE <<
+ /BaseVersion /1.8
+ /ExtensionLevel 5
+ >>
+ >>
+ /Pages 2 0 R
+ /Type /Catalog
+>>
+endobj
+
+%% Original object ID: 2 0
+2 0 obj
+<<
+ /Count 1
+ /Kids [
+ 3 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+%% Original object ID: 3 0
+3 0 obj
+<<
+ /Contents 4 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 6 0 R
+ >>
+ /ProcSet 7 0 R
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+%% Original object ID: 4 0
+4 0 obj
+<<
+ /Length 5 0 R
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+5 0 obj
+44
+endobj
+
+%% Original object ID: 6 0
+6 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+%% Original object ID: 5 0
+7 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+xref
+0 8
+0000000000 65535 f
+0000000052 00000 n
+0000000223 00000 n
+0000000332 00000 n
+0000000574 00000 n
+0000000673 00000 n
+0000000719 00000 n
+0000000864 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 8
+ /ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
+>>
+startxref
+899
+%%EOF
diff --git a/qpdf/qtest/qpdf/extensions-none-force-1.8.out b/qpdf/qtest/qpdf/extensions-none-force-1.8.out
new file mode 100644
index 0000000..491fdb7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-force-1.8.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-min-1.3.out b/qpdf/qtest/qpdf/extensions-none-min-1.3.out
new file mode 100644
index 0000000..3795c64
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-min-1.3.out
@@ -0,0 +1,4 @@
+version: 1.3
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-min-1.6.out b/qpdf/qtest/qpdf/extensions-none-min-1.6.out
new file mode 100644
index 0000000..762063b
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-min-1.6.out
@@ -0,0 +1,4 @@
+version: 1.6
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-min-1.7.1.out b/qpdf/qtest/qpdf/extensions-none-min-1.7.1.out
new file mode 100644
index 0000000..9616863
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-min-1.7.1.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 1
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 1 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-min-1.7.2.out b/qpdf/qtest/qpdf/extensions-none-min-1.7.2.out
new file mode 100644
index 0000000..234701c
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-min-1.7.2.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-min-1.7.3.out b/qpdf/qtest/qpdf/extensions-none-min-1.7.3.out
new file mode 100644
index 0000000..7632120
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-min-1.7.3.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 3
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-min-1.8.0.out b/qpdf/qtest/qpdf/extensions-none-min-1.8.0.out
new file mode 100644
index 0000000..491fdb7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-min-1.8.0.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-min-1.8.2.out b/qpdf/qtest/qpdf/extensions-none-min-1.8.2.out
new file mode 100644
index 0000000..7823a1d
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-min-1.8.2.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 2
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 2 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-min-1.8.5.out b/qpdf/qtest/qpdf/extensions-none-min-1.8.5.out
new file mode 100644
index 0000000..2d78af7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-min-1.8.5.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 5
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-none-min-1.8.out b/qpdf/qtest/qpdf/extensions-none-min-1.8.out
new file mode 100644
index 0000000..491fdb7
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-none-min-1.8.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+null
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-force-1.3.out b/qpdf/qtest/qpdf/extensions-other-force-1.3.out
new file mode 100644
index 0000000..1446741
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-force-1.3.out
@@ -0,0 +1,4 @@
+version: 1.3
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-force-1.6.out b/qpdf/qtest/qpdf/extensions-other-force-1.6.out
new file mode 100644
index 0000000..0cc726a
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-force-1.6.out
@@ -0,0 +1,4 @@
+version: 1.6
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-force-1.7.1.out b/qpdf/qtest/qpdf/extensions-other-force-1.7.1.out
new file mode 100644
index 0000000..a714b14
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-force-1.7.1.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 1
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 1 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-force-1.7.2.out b/qpdf/qtest/qpdf/extensions-other-force-1.7.2.out
new file mode 100644
index 0000000..ed0bf57
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-force-1.7.2.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-force-1.7.3.out b/qpdf/qtest/qpdf/extensions-other-force-1.7.3.out
new file mode 100644
index 0000000..f13ea01
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-force-1.7.3.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 3
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-force-1.8.0.out b/qpdf/qtest/qpdf/extensions-other-force-1.8.0.out
new file mode 100644
index 0000000..d121666
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-force-1.8.0.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-force-1.8.2.out b/qpdf/qtest/qpdf/extensions-other-force-1.8.2.out
new file mode 100644
index 0000000..00858aa
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-force-1.8.2.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 2
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 2 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-force-1.8.5.out b/qpdf/qtest/qpdf/extensions-other-force-1.8.5.out
new file mode 100644
index 0000000..dcf87e8
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-force-1.8.5.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 5
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-force-1.8.5.pdf b/qpdf/qtest/qpdf/extensions-other-force-1.8.5.pdf
new file mode 100644
index 0000000..8bf4fe0
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-force-1.8.5.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/extensions-other-force-1.8.5.qdf b/qpdf/qtest/qpdf/extensions-other-force-1.8.5.qdf
new file mode 100644
index 0000000..a4ffae3
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-force-1.8.5.qdf
@@ -0,0 +1,111 @@
+%PDF-1.8
+%¿÷¢þ
+%QDF-1.0
+
+%% Original object ID: 1 0
+1 0 obj
+<<
+ /Extensions <<
+ /ADBE <<
+ /BaseVersion /1.8
+ /ExtensionLevel 5
+ >>
+ /Potato <<
+ /BaseVersion /3.14159
+ /ExtensionLevel 16059
+ >>
+ >>
+ /Pages 2 0 R
+ /Type /Catalog
+>>
+endobj
+
+%% Original object ID: 2 0
+2 0 obj
+<<
+ /Count 1
+ /Kids [
+ 3 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+%% Original object ID: 3 0
+3 0 obj
+<<
+ /Contents 4 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 6 0 R
+ >>
+ /ProcSet 7 0 R
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+%% Original object ID: 4 0
+4 0 obj
+<<
+ /Length 5 0 R
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+5 0 obj
+44
+endobj
+
+%% Original object ID: 6 0
+6 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+%% Original object ID: 7 0
+7 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+xref
+0 8
+0000000000 65535 f
+0000000052 00000 n
+0000000301 00000 n
+0000000410 00000 n
+0000000652 00000 n
+0000000751 00000 n
+0000000797 00000 n
+0000000942 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 8
+ /ID [<369e89600ee1a6c4c7e73533610180c2><31415926535897932384626433832795>]
+>>
+startxref
+977
+%%EOF
diff --git a/qpdf/qtest/qpdf/extensions-other-force-1.8.out b/qpdf/qtest/qpdf/extensions-other-force-1.8.out
new file mode 100644
index 0000000..d121666
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-force-1.8.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-min-1.3.out b/qpdf/qtest/qpdf/extensions-other-min-1.3.out
new file mode 100644
index 0000000..0f57677
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-min-1.3.out
@@ -0,0 +1,4 @@
+version: 1.5
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-min-1.6.out b/qpdf/qtest/qpdf/extensions-other-min-1.6.out
new file mode 100644
index 0000000..0cc726a
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-min-1.6.out
@@ -0,0 +1,4 @@
+version: 1.6
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-min-1.7.1.out b/qpdf/qtest/qpdf/extensions-other-min-1.7.1.out
new file mode 100644
index 0000000..a714b14
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-min-1.7.1.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 1
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 1 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-min-1.7.2.out b/qpdf/qtest/qpdf/extensions-other-min-1.7.2.out
new file mode 100644
index 0000000..ed0bf57
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-min-1.7.2.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 2
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 2 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-min-1.7.3.out b/qpdf/qtest/qpdf/extensions-other-min-1.7.3.out
new file mode 100644
index 0000000..f13ea01
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-min-1.7.3.out
@@ -0,0 +1,4 @@
+version: 1.7
+extension level: 3
+<< /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-min-1.8.0.out b/qpdf/qtest/qpdf/extensions-other-min-1.8.0.out
new file mode 100644
index 0000000..d121666
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-min-1.8.0.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-min-1.8.2.out b/qpdf/qtest/qpdf/extensions-other-min-1.8.2.out
new file mode 100644
index 0000000..00858aa
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-min-1.8.2.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 2
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 2 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-min-1.8.5.out b/qpdf/qtest/qpdf/extensions-other-min-1.8.5.out
new file mode 100644
index 0000000..dcf87e8
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-min-1.8.5.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 5
+<< /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other-min-1.8.out b/qpdf/qtest/qpdf/extensions-other-min-1.8.out
new file mode 100644
index 0000000..d121666
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other-min-1.8.out
@@ -0,0 +1,4 @@
+version: 1.8
+extension level: 0
+<< /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >> >>
+test 34 done
diff --git a/qpdf/qtest/qpdf/extensions-other.pdf b/qpdf/qtest/qpdf/extensions-other.pdf
new file mode 100644
index 0000000..e863981
--- /dev/null
+++ b/qpdf/qtest/qpdf/extensions-other.pdf
@@ -0,0 +1,98 @@
+%PDF-1.5
+%¿÷¢þ
+%QDF-1.0
+
+1 0 obj
+<<
+ /Pages 2 0 R
+ /Type /Catalog
+ /Extensions <<
+ /Potato << /BaseVersion /3.14159 /ExtensionLevel 16059 >>
+ >>
+>>
+endobj
+
+2 0 obj
+<<
+ /Count 1
+ /Kids [
+ 3 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+3 0 obj
+<<
+ /Contents 4 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 6 0 R
+ >>
+ /ProcSet 7 0 R
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+4 0 obj
+<<
+ /Length 5 0 R
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+5 0 obj
+44
+endobj
+
+6 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+7 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+xref
+0 8
+0000000000 65535 f
+0000000025 00000 n
+0000000163 00000 n
+0000000245 00000 n
+0000000460 00000 n
+0000000559 00000 n
+0000000578 00000 n
+0000000696 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 8
+ /ID [<369e89600ee1a6c4c7e73533610180c2><369e89600ee1a6c4c7e73533610180c2>]
+>>
+startxref
+731
+%%EOF
diff --git a/qpdf/qtest/qpdf/good13.qdf b/qpdf/qtest/qpdf/good13.qdf
index f8993a8..36bd923 100644
--- a/qpdf/qtest/qpdf/good13.qdf
+++ b/qpdf/qtest/qpdf/good13.qdf
@@ -18,7 +18,7 @@ endobj
<01020300040560>
(AB)
]
- /indirect (hello)
+ /indirect 4 0 R
/nesting <<
/a [
1
@@ -58,17 +58,22 @@ endobj
<<
/Count 1
/Kids [
- 4 0 R
+ 5 0 R
]
/Type /Pages
>>
endobj
+%% Original object ID: 8 0
+4 0 obj
+(hello)
+endobj
+
%% Page 1
%% Original object ID: 3 0
-4 0 obj
+5 0 obj
<<
- /Contents 5 0 R
+ /Contents 6 0 R
/MediaBox [
0
0
@@ -78,9 +83,9 @@ endobj
/Parent 3 0 R
/Resources <<
/Font <<
- /F1 7 0 R
+ /F1 8 0 R
>>
- /ProcSet 8 0 R
+ /ProcSet 9 0 R
>>
/Type /Page
>>
@@ -88,9 +93,9 @@ endobj
%% Contents for page 1
%% Original object ID: 4 0
-5 0 obj
+6 0 obj
<<
- /Length 6 0 R
+ /Length 7 0 R
>>
stream
BT
@@ -101,12 +106,12 @@ ET
endstream
endobj
-6 0 obj
+7 0 obj
44
endobj
%% Original object ID: 6 0
-7 0 obj
+8 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
@@ -117,7 +122,7 @@ endobj
endobj
%% Original object ID: 5 0
-8 0 obj
+9 0 obj
[
/PDF
/Text
@@ -125,22 +130,23 @@ endobj
endobj
xref
-0 9
+0 10
0000000000 65535 f
0000000052 00000 n
0000000133 00000 n
-0000000578 00000 n
-0000000687 00000 n
-0000000929 00000 n
-0000001028 00000 n
-0000001074 00000 n
-0000001219 00000 n
+0000000576 00000 n
+0000000675 00000 n
+0000000736 00000 n
+0000000978 00000 n
+0000001077 00000 n
+0000001123 00000 n
+0000001268 00000 n
trailer <<
/QTest 2 0 R
/Root 1 0 R
- /Size 9
+ /Size 10
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
-1254
+1303
%%EOF
diff --git a/qpdf/qtest/qpdf/good5.qdf b/qpdf/qtest/qpdf/good5.qdf
index e830781..b868de8 100644
--- a/qpdf/qtest/qpdf/good5.qdf
+++ b/qpdf/qtest/qpdf/good5.qdf
@@ -5,17 +5,22 @@
%% Original object ID: 1 0
1 0 obj
<<
- /Pages 2 0 R
+ /Pages 3 0 R
/Type /Catalog
>>
endobj
-%% Original object ID: 2 0
+%% Original object ID: 7 0
2 0 obj
+true
+endobj
+
+%% Original object ID: 2 0
+3 0 obj
<<
/Count 1
/Kids [
- 3 0 R
+ 4 0 R
]
/Type /Pages
>>
@@ -23,21 +28,21 @@ endobj
%% Page 1
%% Original object ID: 3 0
-3 0 obj
+4 0 obj
<<
- /Contents 4 0 R
+ /Contents 5 0 R
/MediaBox [
0
0
612
792
]
- /Parent 2 0 R
+ /Parent 3 0 R
/Resources <<
/Font <<
- /F1 6 0 R
+ /F1 7 0 R
>>
- /ProcSet 7 0 R
+ /ProcSet 8 0 R
>>
/Type /Page
>>
@@ -45,9 +50,9 @@ endobj
%% Contents for page 1
%% Original object ID: 4 0
-4 0 obj
+5 0 obj
<<
- /Length 5 0 R
+ /Length 6 0 R
>>
stream
BT
@@ -58,12 +63,12 @@ ET
endstream
endobj
-5 0 obj
+6 0 obj
44
endobj
%% Original object ID: 6 0
-6 0 obj
+7 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
@@ -74,7 +79,7 @@ endobj
endobj
%% Original object ID: 5 0
-7 0 obj
+8 0 obj
[
/PDF
/Text
@@ -82,21 +87,22 @@ endobj
endobj
xref
-0 8
+0 9
0000000000 65535 f
0000000052 00000 n
0000000133 00000 n
-0000000242 00000 n
-0000000484 00000 n
-0000000583 00000 n
-0000000629 00000 n
-0000000774 00000 n
+0000000181 00000 n
+0000000290 00000 n
+0000000532 00000 n
+0000000631 00000 n
+0000000677 00000 n
+0000000822 00000 n
trailer <<
- /QTest true
+ /QTest 2 0 R
/Root 1 0 R
- /Size 8
+ /Size 9
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
-809
+857
%%EOF
diff --git a/qpdf/qtest/qpdf/good8.qdf b/qpdf/qtest/qpdf/good8.qdf
index a8f5297..0eac9f7 100644
--- a/qpdf/qtest/qpdf/good8.qdf
+++ b/qpdf/qtest/qpdf/good8.qdf
@@ -5,17 +5,22 @@
%% Original object ID: 1 0
1 0 obj
<<
- /Pages 2 0 R
+ /Pages 3 0 R
/Type /Catalog
>>
endobj
-%% Original object ID: 2 0
+%% Original object ID: 7 0
2 0 obj
+3.14159
+endobj
+
+%% Original object ID: 2 0
+3 0 obj
<<
/Count 1
/Kids [
- 3 0 R
+ 4 0 R
]
/Type /Pages
>>
@@ -23,21 +28,21 @@ endobj
%% Page 1
%% Original object ID: 3 0
-3 0 obj
+4 0 obj
<<
- /Contents 4 0 R
+ /Contents 5 0 R
/MediaBox [
0
0
612
792
]
- /Parent 2 0 R
+ /Parent 3 0 R
/Resources <<
/Font <<
- /F1 6 0 R
+ /F1 7 0 R
>>
- /ProcSet 7 0 R
+ /ProcSet 8 0 R
>>
/Type /Page
>>
@@ -45,9 +50,9 @@ endobj
%% Contents for page 1
%% Original object ID: 4 0
-4 0 obj
+5 0 obj
<<
- /Length 5 0 R
+ /Length 6 0 R
>>
stream
BT
@@ -58,12 +63,12 @@ ET
endstream
endobj
-5 0 obj
+6 0 obj
44
endobj
%% Original object ID: 6 0
-6 0 obj
+7 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
@@ -74,7 +79,7 @@ endobj
endobj
%% Original object ID: 5 0
-7 0 obj
+8 0 obj
[
/PDF
/Text
@@ -82,21 +87,22 @@ endobj
endobj
xref
-0 8
+0 9
0000000000 65535 f
0000000052 00000 n
0000000133 00000 n
-0000000242 00000 n
-0000000484 00000 n
-0000000583 00000 n
-0000000629 00000 n
-0000000774 00000 n
+0000000184 00000 n
+0000000293 00000 n
+0000000535 00000 n
+0000000634 00000 n
+0000000680 00000 n
+0000000825 00000 n
trailer <<
- /QTest 3.14159
+ /QTest 2 0 R
/Root 1 0 R
- /Size 8
+ /Size 9
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
-809
+860
%%EOF
diff --git a/qpdf/qtest/qpdf/leading-junk.out b/qpdf/qtest/qpdf/leading-junk.out
new file mode 100644
index 0000000..58847c9
--- /dev/null
+++ b/qpdf/qtest/qpdf/leading-junk.out
@@ -0,0 +1,17 @@
+checking leading-junk.pdf
+PDF Version: 1.4
+R = 3
+P = -4
+User password =
+extract for accessibility: allowed
+extract for any purpose: allowed
+print low resolution: allowed
+print high resolution: allowed
+modify document assembly: allowed
+modify forms: allowed
+modify annotations: allowed
+modify other: allowed
+modify anything: allowed
+File is linearized
+No syntax or stream encoding errors found; the file may still contain
+errors that qpdf cannot detect
diff --git a/qpdf/qtest/qpdf/leading-junk.pdf b/qpdf/qtest/qpdf/leading-junk.pdf
new file mode 100644
index 0000000..2b2a0a2
--- /dev/null
+++ b/qpdf/qtest/qpdf/leading-junk.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/lin-special.disable.exp b/qpdf/qtest/qpdf/lin-special.disable.exp
index 852c008..14c2eae 100644
--- a/qpdf/qtest/qpdf/lin-special.disable.exp
+++ b/qpdf/qtest/qpdf/lin-special.disable.exp
Binary files differ
diff --git a/qpdf/qtest/qpdf/lin-special.generate.exp b/qpdf/qtest/qpdf/lin-special.generate.exp
index c07bab2..af7cb06 100644
--- a/qpdf/qtest/qpdf/lin-special.generate.exp
+++ b/qpdf/qtest/qpdf/lin-special.generate.exp
Binary files differ
diff --git a/qpdf/qtest/qpdf/lin-special.preserve.exp b/qpdf/qtest/qpdf/lin-special.preserve.exp
index 852c008..14c2eae 100644
--- a/qpdf/qtest/qpdf/lin-special.preserve.exp
+++ b/qpdf/qtest/qpdf/lin-special.preserve.exp
Binary files differ
diff --git a/qpdf/qtest/qpdf/obj0-check.out b/qpdf/qtest/qpdf/obj0-check.out
index f0a71b6..d3b5c2b 100644
--- a/qpdf/qtest/qpdf/obj0-check.out
+++ b/qpdf/qtest/qpdf/obj0-check.out
@@ -1,7 +1,7 @@
+WARNING: obj0.pdf: file is damaged
+WARNING: obj0.pdf (object 1 0, file position 77): expected n n obj
+WARNING: obj0.pdf: Attempting to reconstruct cross-reference table
checking obj0.pdf
PDF Version: 1.3
File is not encrypted
File is not linearized
-WARNING: obj0.pdf: file is damaged
-WARNING: obj0.pdf (object 1 0, file position 77): expected n n obj
-WARNING: obj0.pdf: Attempting to reconstruct cross-reference table
diff --git a/qpdf/qtest/qpdf/object-stream.disable.exp b/qpdf/qtest/qpdf/object-stream.disable.exp
index e2ee713..a05e048 100644
--- a/qpdf/qtest/qpdf/object-stream.disable.exp
+++ b/qpdf/qtest/qpdf/object-stream.disable.exp
Binary files differ
diff --git a/qpdf/qtest/qpdf/object-stream.generate.exp b/qpdf/qtest/qpdf/object-stream.generate.exp
index 87e2f5d..3b16f12 100644
--- a/qpdf/qtest/qpdf/object-stream.generate.exp
+++ b/qpdf/qtest/qpdf/object-stream.generate.exp
Binary files differ
diff --git a/qpdf/qtest/qpdf/object-stream.preserve.exp b/qpdf/qtest/qpdf/object-stream.preserve.exp
index 87e2f5d..3b16f12 100644
--- a/qpdf/qtest/qpdf/object-stream.preserve.exp
+++ b/qpdf/qtest/qpdf/object-stream.preserve.exp
Binary files differ
diff --git a/qpdf/qtest/qpdf/test4-1.qdf b/qpdf/qtest/qpdf/test4-1.qdf
index b22fd6f..130cad7 100644
--- a/qpdf/qtest/qpdf/test4-1.qdf
+++ b/qpdf/qtest/qpdf/test4-1.qdf
@@ -39,8 +39,8 @@ endobj
<<
/A 5 0 R
/B 6 0 R
- /Subject (Subject)
- /Title (Some Title Is Here)
+ /Subject 7 0 R
+ /Title 8 0 R
>>
endobj
@@ -49,7 +49,7 @@ endobj
<<
/Count 1
/Kids [
- 7 0 R
+ 9 0 R
]
/Type /Pages
>>
@@ -72,11 +72,21 @@ endobj
>>
endobj
+%% Original object ID: 10 0
+7 0 obj
+(Subject)
+endobj
+
+%% Original object ID: 9 0
+8 0 obj
+(Some Title Is Here)
+endobj
+
%% Page 1
%% Original object ID: 3 0
-7 0 obj
+9 0 obj
<<
- /Contents 8 0 R
+ /Contents 10 0 R
/MediaBox [
0
0
@@ -86,9 +96,9 @@ endobj
/Parent 4 0 R
/Resources <<
/Font <<
- /F1 10 0 R
+ /F1 12 0 R
>>
- /ProcSet 11 0 R
+ /ProcSet 13 0 R
>>
/Type /Page
>>
@@ -96,9 +106,9 @@ endobj
%% Contents for page 1
%% Original object ID: 4 0
-8 0 obj
+10 0 obj
<<
- /Length 9 0 R
+ /Length 11 0 R
>>
stream
BT
@@ -109,12 +119,12 @@ ET
endstream
endobj
-9 0 obj
+11 0 obj
44
endobj
%% Original object ID: 6 0
-10 0 obj
+12 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
@@ -125,7 +135,7 @@ endobj
endobj
%% Original object ID: 7 0
-11 0 obj
+13 0 obj
[
/PDF
/Text
@@ -133,26 +143,28 @@ endobj
endobj
xref
-0 12
+0 14
0000000000 65535 f
0000000052 00000 n
0000000134 00000 n
0000000353 00000 n
-0000000475 00000 n
-0000000575 00000 n
-0000000635 00000 n
-0000000714 00000 n
-0000000958 00000 n
-0000001057 00000 n
-0000001103 00000 n
-0000001249 00000 n
+0000000456 00000 n
+0000000556 00000 n
+0000000616 00000 n
+0000000686 00000 n
+0000000739 00000 n
+0000000813 00000 n
+0000001058 00000 n
+0000001159 00000 n
+0000001206 00000 n
+0000001352 00000 n
trailer <<
/Info 2 0 R
/QTest 3 0 R
/Root 1 0 R
- /Size 12
+ /Size 14
/ID [<c61bd35bada064f61e0a56aa9588064e><31415926535897932384626433832795>]
>>
startxref
-1285
+1388
%%EOF
diff --git a/qpdf/qtest/qpdf/unfilterable-with-crypt-after.out b/qpdf/qtest/qpdf/unfilterable-with-crypt-after.out
new file mode 100644
index 0000000..ac52313
--- /dev/null
+++ b/qpdf/qtest/qpdf/unfilterable-with-crypt-after.out
@@ -0,0 +1,4 @@
+<< /DL 30 /DecodeParms [ null ] /Filter [ /ZlateDecode ] /Length 39 /Params << /CheckSum <c4f73a3ba2b5fef86a4085d6f006eacd> /CreationDate (D:20121229172641-05'00') /ModDate (D:20121229172600) /Size 30 >> /Subtype /text#2fplain >>attachment1.txt:
+This is the first attachment.
+--END--
+test 36 done
diff --git a/qpdf/qtest/qpdf/unfilterable-with-crypt-before.out b/qpdf/qtest/qpdf/unfilterable-with-crypt-before.out
new file mode 100644
index 0000000..36e2852
--- /dev/null
+++ b/qpdf/qtest/qpdf/unfilterable-with-crypt-before.out
@@ -0,0 +1,4 @@
+<< /DL 30 /DecodeParms [ << /Name /StdCF >> null ] /Filter [ /Crypt /ZlateDecode ] /Length 64 /Params << /CheckSum <c4f73a3ba2b5fef86a4085d6f006eacd> /CreationDate (D:20121229172641-05'00') /ModDate (D:20121229172600) /Size 30 >> /Subtype /text#2fplain >>attachment1.txt:
+This is the first attachment.
+--END--
+test 36 done
diff --git a/qpdf/qtest/qpdf/unfilterable-with-crypt.pdf b/qpdf/qtest/qpdf/unfilterable-with-crypt.pdf
new file mode 100644
index 0000000..970ea53
--- /dev/null
+++ b/qpdf/qtest/qpdf/unfilterable-with-crypt.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/unreferenced-indirect-scalar.out b/qpdf/qtest/qpdf/unreferenced-indirect-scalar.out
index c1bf4a9..af07031 100644
--- a/qpdf/qtest/qpdf/unreferenced-indirect-scalar.out
+++ b/qpdf/qtest/qpdf/unreferenced-indirect-scalar.out
Binary files differ
diff --git a/qpdf/qtest/qpdf/zero-offset.out b/qpdf/qtest/qpdf/zero-offset.out
new file mode 100644
index 0000000..df99146
--- /dev/null
+++ b/qpdf/qtest/qpdf/zero-offset.out
@@ -0,0 +1,5 @@
+checking zero-offset.pdf
+PDF Version: 1.3
+File is not encrypted
+File is not linearized
+WARNING: zero-offset.pdf (object 27 0): object has offset 0
diff --git a/qpdf/qtest/qpdf/zero-offset.pdf b/qpdf/qtest/qpdf/zero-offset.pdf
new file mode 100644
index 0000000..ff59b45
--- /dev/null
+++ b/qpdf/qtest/qpdf/zero-offset.pdf
Binary files differ
diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc
index 311097f..239464c 100644
--- a/qpdf/test_driver.cc
+++ b/qpdf/test_driver.cc
@@ -21,7 +21,7 @@ static char const* whoami = 0;
void usage()
{
- std::cerr << "Usage: " << whoami << " n filename1 [filename2]"
+ std::cerr << "Usage: " << whoami << " n filename1 [arg2]"
<< std::endl;
exit(2);
}
@@ -82,12 +82,29 @@ static QPDFObjectHandle createPageContents(QPDF& pdf, std::string const& text)
return QPDFObjectHandle::newStream(&pdf, contents);
}
-void runtest(int n, char const* filename1, char const* filename2)
+void runtest(int n, char const* filename1, char const* arg2)
{
// Most tests here are crafted to work on specific files. Look at
// the test suite to see how the test is invoked to find the file
// that the test is supposed to operate on.
+ if (n == 0)
+ {
+ // Throw in some random test cases that don't fit anywhere
+ // else. This is in addition to whatever else is going on in
+ // test 0.
+
+ // The code to trim user passwords looks for 0x28 (which is
+ // "(") since it marks the beginning of the padding. Exercise
+ // the code to make sure it skips over 0x28 characters that
+ // aren't part of padding.
+ std::string password(
+ "1234567890123456789012(45678\x28\xbf\x4e\x5e");
+ assert(password.length() == 32);
+ QPDF::trim_user_password(password);
+ assert(password == "1234567890123456789012(45678");
+ }
+
QPDF pdf;
PointerHolder<char> file_buf;
FILE* filep = 0;
@@ -95,7 +112,12 @@ void runtest(int n, char const* filename1, char const* filename2)
{
pdf.setAttemptRecovery(false);
}
- if (n % 2 == 0)
+ if (((n == 35) || (n == 36)) && (arg2 != 0))
+ {
+ // arg2 is password
+ pdf.processFile(filename1, arg2);
+ }
+ else if (n % 2 == 0)
{
if (n % 4 == 0)
{
@@ -936,9 +958,9 @@ void runtest(int n, char const* filename1, char const* filename2)
// Copy qtest without crossing page boundaries. Should get O1
// and O2 and their streams but not O3 or any other pages.
- assert(filename2 != 0);
+ assert(arg2 != 0);
QPDF newpdf;
- newpdf.processFile(filename2);
+ newpdf.processFile(arg2);
QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
newpdf.getTrailer().replaceKey(
"/QTest", newpdf.copyForeignObject(qtest));
@@ -956,9 +978,9 @@ void runtest(int n, char const* filename1, char const* filename2)
// that O3 points to. Also, inherited object will have been
// pushed down and will be preserved.
- assert(filename2 != 0);
+ assert(arg2 != 0);
QPDF newpdf;
- newpdf.processFile(filename2);
+ newpdf.processFile(arg2);
QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
QPDFObjectHandle O3 = qtest.getKey("/O3");
newpdf.addPage(O3, false);
@@ -976,9 +998,9 @@ void runtest(int n, char const* filename1, char const* filename2)
// Should get qtest plus only the O3 page and the page that O3
// points to. Inherited objects should be preserved.
- assert(filename2 != 0);
+ assert(arg2 != 0);
QPDF newpdf;
- newpdf.processFile(filename2);
+ newpdf.processFile(arg2);
QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
QPDFObjectHandle O3 = qtest.getKey("/O3");
newpdf.addPage(O3.getKey("/OtherPage"), false);
@@ -1016,9 +1038,9 @@ void runtest(int n, char const* filename1, char const* filename2)
else if (n == 29)
{
// Detect mixed objects in QPDFWriter
- assert(filename2 != 0);
+ assert(arg2 != 0);
QPDF other;
- other.processFile(filename2);
+ other.processFile(arg2);
// Should use copyForeignObject instead
other.getTrailer().replaceKey(
"/QTest", pdf.getTrailer().getKey("/QTest"));
@@ -1036,9 +1058,9 @@ void runtest(int n, char const* filename1, char const* filename2)
}
else if (n == 30)
{
- assert(filename2 != 0);
+ assert(arg2 != 0);
QPDF encrypted;
- encrypted.processFile(filename2, "user");
+ encrypted.processFile(arg2, "user");
QPDFWriter w(pdf, "b.pdf");
w.setStreamDataMode(qpdf_s_preserve);
w.copyEncryptionParameters(encrypted);
@@ -1112,6 +1134,117 @@ void runtest(int n, char const* filename1, char const* filename2)
w.write();
}
}
+ else if (n == 33)
+ {
+ // Test writing to a custom pipeline
+ Pl_Buffer p("buffer");
+ QPDFWriter w(pdf);
+ w.setStaticID(true);
+ w.setOutputPipeline(&p);
+ w.write();
+ PointerHolder<Buffer> b = p.getBuffer();
+ FILE* f = QUtil::fopen_wrapper("open a.pdf",
+ fopen("a.pdf", "wb"));
+ fwrite(b->getBuffer(), b->getSize(), 1, f);
+ fclose(f);
+ }
+ else if (n == 34)
+ {
+ // Look at Extensions dictionary
+ std::cout << "version: " << pdf.getPDFVersion() << std::endl
+ << "extension level: " << pdf.getExtensionLevel() << std::endl
+ << pdf.getRoot().getKey("/Extensions").unparse() << std::endl;
+ }
+ else if (n == 35)
+ {
+ // Extract attachments
+
+ std::map<std::string, PointerHolder<Buffer> > attachments;
+ QPDFObjectHandle root = pdf.getRoot();
+ QPDFObjectHandle names = root.getKey("/Names");
+ QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles");
+ names = embeddedFiles.getKey("/Names");
+ for (int i = 0; i < names.getArrayNItems(); ++i)
+ {
+ QPDFObjectHandle item = names.getArrayItem(i);
+ if (item.isDictionary() &&
+ item.getKey("/Type").isName() &&
+ (item.getKey("/Type").getName() == "/Filespec") &&
+ item.getKey("/EF").isDictionary() &&
+ item.getKey("/EF").getKey("/F").isStream())
+ {
+ std::string filename = item.getKey("/F").getStringValue();
+ QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
+ attachments[filename] = stream.getStreamData();
+ }
+ }
+ for (std::map<std::string, PointerHolder<Buffer> >::iterator iter =
+ attachments.begin(); iter != attachments.end(); ++iter)
+ {
+ std::string const& filename = (*iter).first;
+ std::string data = std::string(
+ (char const*)(*iter).second->getBuffer(),
+ (*iter).second->getSize());
+ bool is_binary = false;
+ for (size_t i = 0; i < data.size(); ++i)
+ {
+ if (data[i] < 0)
+ {
+ is_binary = true;
+ break;
+ }
+ }
+ if (is_binary)
+ {
+ std::string t;
+ for (size_t i = 0; i < std::min(data.size(), (size_t)20); ++i)
+ {
+ if ((data[i] >= 32) && (data[i] <= 126))
+ {
+ t += data[i];
+ }
+ else
+ {
+ t += ".";
+ }
+ }
+ t += " (" + QUtil::int_to_string(data.size()) + " bytes)";
+ data = t;
+ }
+ std::cout << filename << ":\n" << data << "--END--\n";
+ }
+ }
+ else if (n == 36)
+ {
+ // Extract raw unfilterable attachment
+
+ QPDFObjectHandle root = pdf.getRoot();
+ QPDFObjectHandle names = root.getKey("/Names");
+ QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles");
+ names = embeddedFiles.getKey("/Names");
+ for (int i = 0; i < names.getArrayNItems(); ++i)
+ {
+ QPDFObjectHandle item = names.getArrayItem(i);
+ if (item.isDictionary() &&
+ item.getKey("/Type").isName() &&
+ (item.getKey("/Type").getName() == "/Filespec") &&
+ item.getKey("/EF").isDictionary() &&
+ item.getKey("/EF").getKey("/F").isStream() &&
+ (item.getKey("/F").getStringValue() == "attachment1.txt"))
+ {
+ std::string filename = item.getKey("/F").getStringValue();
+ QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
+ Pl_Buffer p1("buffer");
+ Pl_Flate p2("compress", &p1, Pl_Flate::a_inflate);
+ stream.pipeStreamData(&p2, false, false, false);
+ PointerHolder<Buffer> buf = p1.getBuffer();
+ std::string data = std::string(
+ (char const*)buf->getBuffer(), buf->getSize());
+ std::cout << stream.getDict().unparse()
+ << filename << ":\n" << data << "--END--\n";
+ }
+ }
+ }
else
{
throw std::runtime_error(std::string("invalid test ") +
@@ -1151,8 +1284,8 @@ int main(int argc, char* argv[])
{
int n = atoi(argv[1]);
char const* filename1 = argv[2];
- char const* filename2 = argv[3];
- runtest(n, filename1, filename2);
+ char const* arg2 = argv[3];
+ runtest(n, filename1, arg2);
}
catch (std::exception& e)
{