13 Commits

Author SHA1 Message Date
Michael R Sweet
084c458974 Strip out non-working LZW from the 1.6.x branch. 2026-01-16 20:21:12 -05:00
Michael R Sweet
a9210c114a Fix 0 key length regression (Issue #149) 2026-01-16 20:19:50 -05:00
Michael R Sweet
a16a3130f8 Fix repaired xref stream offsets and support indirect Contents arrays for pages. 2026-01-16 16:57:45 -05:00
Michael R Sweet
635035efd1 Add an xref table offset array to better detect xref table loops (Issue #148) 2026-01-13 18:40:55 -05:00
Michael R Sweet
0bbdd6aa86 Clarify security policy. 2026-01-13 14:11:44 -05:00
Michael R Sweet
b26d143fcc Refactor PDF encryption handler to work with more files. 2026-01-11 13:37:53 -05:00
Michael R Sweet
6ad96ced0b Add missing range checks to pdfioArrayCopy and pdfioDictCopy. 2026-01-08 15:04:29 -05:00
Michael R Sweet
3f581308a1 Fix Clang warning about signedness.
Update Xcode project settings a la master.
2026-01-08 12:42:05 -05:00
Michael R Sweet
8f7d8a58c4 Bump version to 1.6.2. 2026-01-06 11:39:36 -05:00
Michael R Sweet
fb72b141cd Increase PDFIO_MAX_STRING to 128k (Issue #146) 2026-01-06 11:22:12 -05:00
Michael R Sweet
6b59bffd92 Fix an error propagation bug in _pdfioValueCopy (Issue #146) 2026-01-06 11:20:04 -05:00
Michael R Sweet
1832dfcd3d Fix Coverity status badge link. 2026-01-01 10:07:04 -05:00
Michael R Sweet
e7b74e94f7 Fix build badge. 2026-01-01 10:02:00 -05:00
20 changed files with 407 additions and 212 deletions

View File

@@ -2,6 +2,19 @@ Changes in PDFio
================
v1.6.2 - YYYY-MM-DD
-------------------
- Increased the maximum length of a single string to 128k (Issue #146)
- Added missing range checks to `pdfioArrayCopy` and `pdfioDictCopy`.
- Refactored PDF encryption code to fix unlocking with certain files.
- Improved xref table loop detection (Issue #148)
- Fixed xref reconstruction for objects lacking a `Type` value.
- Fixed `pdfioPageOpenStream` for indirect `Contents` arrays.
- Fixed an error propagation bug when reading too-long values (Issue #146)
- Fixed a Clang warning.
v1.6.1 - 2025-12-26
-------------------

2
NOTICE
View File

@@ -1,6 +1,6 @@
PDFio - PDF Read/Write Library
Copyright © 2021-2025 by Michael R Sweet.
Copyright © 2021-2026 by Michael R Sweet.
(Optional) Exceptions to the Apache 2.0 License:
================================================

View File

@@ -3,8 +3,8 @@ pdfio - PDF Read/Write Library
![Version](https://img.shields.io/github/v/release/michaelrsweet/pdfio?include_prereleases)
![Apache 2.0](https://img.shields.io/github/license/michaelrsweet/pdfio)
[![Build Status](https://img.shields.io/github/workflow/status/michaelrsweet/pdfio/Build)](https://github.com/michaelrsweet/pdfio/actions/workflows/build.yml)
[![Coverity Scan Status](https://img.shields.io/coverity/scan/22385.svg)](https://scan.coverity.com/projects/michaelrsweet-pdfio)
[![Build Status](https://img.shields.io/github/actions/workflow/status/michaelrsweet/pdfio/build.yml)](https://github.com/michaelrsweet/pdfio/actions/workflows/build.yml)
[![Coverity Scan Status](https://img.shields.io/coverity/scan/23194.svg)](https://scan.coverity.com/projects/michaelrsweet-pdfio)
PDFio is a simple C library for reading and writing PDF files. The primary
goals of PDFio are:
@@ -89,7 +89,7 @@ generates a static library that will be installed under "/usr/local" with:
Legal Stuff
-----------
PDFio is Copyright © 2021-2025 by Michael R Sweet.
PDFio is Copyright © 2021-2026 by Michael R Sweet.
This software is licensed under the Apache License Version 2.0 with an
(optional) exception to allow linking against GPL2/LGPL2 software. See the

View File

@@ -5,12 +5,40 @@ This file describes how security issues are reported and handled, and what the
expectations are for security issues reported to this project.
What is a Security Bug?
-----------------------
Not every bug is a security bug.
Certain bugs that might be considered security bugs in a program, such as bugs
that lead to a Denial of Service, are *not* considered security bugs simply
because this project *does not provide a service*. Some might argue that, "my
server uses this library and the bug in this library causes a denial of service
for my server", however it is the responsibility of the *server* to protect
against DoS attacks, not a subordinate library, because only the server knows
what is an appropriate use of memory, CPU, time, and other resources.
Similarly, bugs caused by incorrect API usage such as passing `NULL` pointers
where such pointers are not allowed, passing the wrong kinds of pointers or
objects to an API, or using a private API are not security bugs because they
are not caused by an attacker but by the developer.
Finally, bugs that only exist in unreleased (non-production) or inactive code
are not security bugs because they do not affect ordinary users. See the
[Supported Versions](#supported-versions) section below for more information
about what versions of the project are covered by this security policy.
If the bug you've found falls into one of these three categories, please report
the bug as an the ordinary project issue at
<https://github.com/michaelrsweet/pdfio/issues>.
Reporting a Security Bug
------------------------
For the purposes of this project, a security bug is a software defect that
allows a *local or remote user* to gain unauthorized access or privileges on the
host computer or to cause the software to crash. Such defects should be
host computer or to causes the software to crash. Such defects should be
reported to the project security advisory page at
<https://github.com/michaelrsweet/pdfio/security/advisories>.
@@ -18,11 +46,6 @@ Alternately, security bugs can be reported to "security AT msweet.org" using the
PGP public key below. Expect a response within 5 business days. Any proposed
embargo date should be at least 30 days and no more than 90 days in the future.
> *Note:* If you've found a software defect that allows a *program* to gain
> unauthorized access or privileges on the host computer or causes the program
> to crash, that defect should be reported as an ordinary project issue at
> <https://github.com/michaelrsweet/pdfio/issues>.
Responsible Disclosure
----------------------
@@ -68,6 +91,9 @@ example:
1.0b2
1.0rc1
Pre-release code in a Git branch ("master", "v1.6.x", etc.) is similarly *not*
production release code.
PGP Public Key
--------------

24
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for pdfio 1.6.1.
# Generated by GNU Autoconf 2.71 for pdfio 1.6.2.
#
# Report bugs to <https://github.com/michaelrsweet/pdfio/issues>.
#
@@ -610,8 +610,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='pdfio'
PACKAGE_TARNAME='pdfio'
PACKAGE_VERSION='1.6.1'
PACKAGE_STRING='pdfio 1.6.1'
PACKAGE_VERSION='1.6.2'
PACKAGE_STRING='pdfio 1.6.2'
PACKAGE_BUGREPORT='https://github.com/michaelrsweet/pdfio/issues'
PACKAGE_URL='https://www.msweet.org/pdfio'
@@ -1295,7 +1295,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 pdfio 1.6.1 to adapt to many kinds of systems.
\`configure' configures pdfio 1.6.2 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1361,7 +1361,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of pdfio 1.6.1:";;
short | recursive ) echo "Configuration of pdfio 1.6.2:";;
esac
cat <<\_ACEOF
@@ -1460,7 +1460,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
pdfio configure 1.6.1
pdfio configure 1.6.2
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@@ -1678,7 +1678,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 pdfio $as_me 1.6.1, which was
It was created by pdfio $as_me 1.6.2, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@@ -2434,9 +2434,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
PDFIO_VERSION="1.6.1"
PDFIO_VERSION_MAJOR="`echo 1.6.1 | awk -F. '{print $1}'`"
PDFIO_VERSION_MINOR="`echo 1.6.1 | awk -F. '{printf("%d\n",$2);}'`"
PDFIO_VERSION="1.6.2"
PDFIO_VERSION_MAJOR="`echo 1.6.2 | awk -F. '{print $1}'`"
PDFIO_VERSION_MINOR="`echo 1.6.2 | awk -F. '{printf("%d\n",$2);}'`"
@@ -5115,7 +5115,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 pdfio $as_me 1.6.1, which was
This file was extended by pdfio $as_me 1.6.2, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -5171,7 +5171,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
pdfio config.status 1.6.1
pdfio config.status 1.6.2
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"

View File

@@ -21,7 +21,7 @@ AC_PREREQ([2.70])
dnl Package name and version...
AC_INIT([pdfio], [1.6.1], [https://github.com/michaelrsweet/pdfio/issues], [pdfio], [https://www.msweet.org/pdfio])
AC_INIT([pdfio], [1.6.2], [https://github.com/michaelrsweet/pdfio/issues], [pdfio], [https://www.msweet.org/pdfio])
PDFIO_VERSION="AC_PACKAGE_VERSION"
PDFIO_VERSION_MAJOR="`echo AC_PACKAGE_VERSION | awk -F. '{print $1}'`"

View File

@@ -1,7 +1,7 @@
//
// PDF array functions for PDFio.
//
// Copyright © 2021-2024 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -264,6 +264,10 @@ pdfioArrayCopy(pdfio_file_t *pdf, // I - PDF file
PDFIO_DEBUG("pdfioArrayCopy(pdf=%p, a=%p(%p))\n", (void *)pdf, (void *)a, a ? (void *)a->pdf : NULL);
// Range check input...
if (!pdf || !a)
return (NULL);
// Create the new array...
if ((na = pdfioArrayCreate(pdf)) == NULL)
return (NULL);

View File

@@ -1,7 +1,7 @@
//
// Cryptographic support functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -96,12 +96,14 @@ static uint8_t pdf_passpad[32] = // Padding for passwords
// Local functions...
//
static void decrypt_user_key(pdfio_encryption_t encryption, const uint8_t *file_key, uint8_t user_key[32]);
static void encrypt_user_key(pdfio_encryption_t encryption, const uint8_t *file_key, uint8_t user_key[32]);
static void make_file_key(pdfio_encryption_t encryption, pdfio_permission_t permissions, const unsigned char *file_id, size_t file_idlen, const uint8_t *user_pad, const uint8_t *owner_key, bool encrypt_metadata, uint8_t file_key[16]);
static void make_owner_key(pdfio_encryption_t encryption, const uint8_t *owner_pad, const uint8_t *user_pad, uint8_t owner_key[32]);
static void decrypt_ou_key(pdfio_encryption_t encryption, const uint8_t *file_key, size_t file_keylen, uint8_t ou_key[32]);
static void encrypt_ou_key(pdfio_encryption_t encryption, const uint8_t *file_key, size_t file_keylen, uint8_t ou_key[32]);
static void make_file_key(pdfio_encryption_t encryption, size_t file_keylen, pdfio_permission_t permissions, const unsigned char *file_id, size_t file_idlen, const uint8_t *user_pad, const uint8_t *owner_key, bool encrypt_metadata, uint8_t file_key[16]);
static void make_owner_key(pdfio_encryption_t encryption, size_t file_keylen, const uint8_t *owner_pad, const uint8_t *user_pad, uint8_t owner_key[32]);
static void make_password_key(pdfio_encryption_t encryption, size_t file_keylen, const uint8_t *pad, uint8_t *key);
static void make_user_key(const unsigned char *file_id, size_t file_idlen, uint8_t user_key[32]);
static void pad_password(const char *password, uint8_t pad[32]);
static bool test_password(pdfio_encryption_t encryption, size_t file_keylen, pdfio_permission_t permisions,const unsigned char *file_id, size_t file_idlen, const uint8_t *user_pad, const uint8_t *user_key, const uint8_t *owner_key, bool encrypt_metadata, uint8_t file_key[16]);
//
@@ -139,6 +141,7 @@ _pdfioCryptoLock(
case PDFIO_ENCRYPTION_AES_128 :
// Create the 128-bit encryption keys...
pad_password(user_password, user_pad);
pdf->file_keylen = 16;
if (!owner_password && user_password && *user_password)
{
@@ -152,18 +155,17 @@ _pdfioCryptoLock(
}
// Compute the owner key...
make_owner_key(encryption, owner_pad, user_pad, pdf->owner_key);
make_owner_key(encryption, pdf->file_keylen, owner_pad, user_pad, pdf->owner_key);
pdf->owner_keylen = 32;
// Generate the encryption key
file_id = pdfioArrayGetBinary(pdf->id_array, 0, &file_idlen);
make_file_key(encryption, permissions, file_id, file_idlen, user_pad, pdf->owner_key, pdf->encrypt_metadata, pdf->file_key);
pdf->file_keylen = 16;
make_file_key(encryption, pdf->file_keylen, permissions, file_id, file_idlen, user_pad, pdf->owner_key, pdf->encrypt_metadata, pdf->file_key);
// Generate the user key...
make_user_key(file_id, file_idlen, pdf->user_key);
encrypt_user_key(encryption, pdf->file_key, pdf->user_key);
encrypt_ou_key(encryption, pdf->file_key, pdf->file_keylen, pdf->user_key);
pdf->user_keylen = 32;
// Save everything in the dictionary...
@@ -691,6 +693,11 @@ _pdfioCryptoUnlock(
_pdfioFileError(pdf, "Unsupported encryption V%d R%d.", version, revision);
return (false);
}
else if (length < 40 || length > 128)
{
_pdfioFileError(pdf, "Unsupported key length %d.", length);
return (false);
}
// Grab the remaining values we need to unlock the PDF...
pdf->file_keylen = (size_t)(length / 8);
@@ -733,7 +740,7 @@ _pdfioCryptoUnlock(
return (false);
}
PDFIO_DEBUG("_pdfioCryptoUnlock: user_key[%d]=%02X%02X%02X%02X...%02X%02X%02X%02X\n", (int)user_keylen, user_key[0], user_key[1], user_key[2], user_key[3], user_key[28], user_key[29], user_key[30], user_key[31]);
PDFIO_DEBUG("_pdfioCryptoUnlock: user_key[%d]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", (int)user_keylen, user_key[0], user_key[1], user_key[2], user_key[3], user_key[28], user_key[29], user_key[30], user_key[31]);
memcpy(pdf->user_key, user_key, user_keylen);
pdf->user_keylen = user_keylen;
@@ -744,6 +751,9 @@ _pdfioCryptoUnlock(
return (false);
}
PDFIO_DEBUG("_pdfioCryptoUnlock: P=%d\n", pdf->permissions);
PDFIO_DEBUG("_pdfioCryptoUnlock: file_id(%d)=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", (int)file_idlen, file_id[0], file_id[1], file_id[2], file_id[3], file_id[12], file_id[13], file_id[14], file_id[15]);
// Generate a base hash from known values...
_pdfioCryptoMD5Init(&md5);
_pdfioCryptoMD5Append(&md5, pdf_passpad, 32);
@@ -755,56 +765,34 @@ _pdfioCryptoUnlock(
{
if (pdf->encryption <= PDFIO_ENCRYPTION_AES_128)
{
// RC4 and AES-128 encryption...
uint8_t pad[32], // Padded password
file_key[16], // File key
user_pad[32], // Padded user password
own_user_key[32], // Calculated user key
pdf_user_key[32]; // Decrypted user key
padkey[16], // Password key
file_key[16]; // File key
// Pad the supplied password, if any...
pad_password(password, pad);
// Generate keys to see if things match...
PDFIO_DEBUG("_pdfioCryptoUnlock: Trying %02X%02X%02X%02X...%02X%02X%02X%02X\n", pad[0], pad[1], pad[2], pad[3], pad[28], pad[29], pad[30], pad[31]);
PDFIO_DEBUG("_pdfioCryptoUnlock: P=%d\n", pdf->permissions);
PDFIO_DEBUG("_pdfioCryptoUnlock: Fid(%d)=%02X%02X%02X%02X...%02X%02X%02X%02X\n", (int)file_idlen, file_id[0], file_id[1], file_id[2], file_id[3], file_id[12], file_id[13], file_id[14], file_id[15]);
// Test the user password...
PDFIO_DEBUG("_pdfioCryptoUnlock: Trying <%02X%02X%02X%02X...%02X%02X%02X%02X>\n", pad[0], pad[1], pad[2], pad[3], pad[28], pad[29], pad[30], pad[31]);
make_owner_key(pdf->encryption, pad, pdf->owner_key, user_pad);
PDFIO_DEBUG("_pdfioCryptoUnlock: Upad=%02X%02X%02X%02X...%02X%02X%02X%02X\n", user_pad[0], user_pad[1], user_pad[2], user_pad[3], user_pad[28], user_pad[29], user_pad[30], user_pad[31]);
make_file_key(pdf->encryption, pdf->permissions, file_id, file_idlen, user_pad, pdf->owner_key, pdf->encrypt_metadata, file_key);
PDFIO_DEBUG("_pdfioCryptoUnlock: Fown=%02X%02X%02X%02X...%02X%02X%02X%02X\n", file_key[0], file_key[1], file_key[2], file_key[3], file_key[12], file_key[13], file_key[14], file_key[15]);
make_user_key(file_id, file_idlen, own_user_key);
PDFIO_DEBUG("_pdfioCryptoUnlock: U=%02X%02X%02X%02X...%02X%02X%02X%02X\n", pdf->user_key[0], pdf->user_key[1], pdf->user_key[2], pdf->user_key[3], pdf->user_key[28], pdf->user_key[29], pdf->user_key[30], pdf->user_key[31]);
PDFIO_DEBUG("_pdfioCryptoUnlock: Uown=%02X%02X%02X%02X...%02X%02X%02X%02X\n", own_user_key[0], own_user_key[1], own_user_key[2], own_user_key[3], own_user_key[28], own_user_key[29], own_user_key[30], own_user_key[31]);
if (!memcmp(own_user_key, pdf->user_key, sizeof(own_user_key)))
if (test_password(pdf->encryption, pdf->file_keylen, pdf->permissions, file_id, file_idlen, pad, pdf->user_key, pdf->owner_key, pdf->encrypt_metadata, file_key))
{
// Matches!
memcpy(pdf->file_key, file_key, sizeof(pdf->file_key));
memcpy(pdf->file_key, file_key, pdf->file_keylen);
memcpy(pdf->password, pad, sizeof(pdf->password));
return (true);
}
// Not the owner password, try the user password...
make_file_key(pdf->encryption, pdf->permissions, file_id, file_idlen, pad, pdf->owner_key, pdf->encrypt_metadata, file_key);
PDFIO_DEBUG("_pdfioCryptoUnlock: Fuse=%02X%02X%02X%02X...%02X%02X%02X%02X\n", file_key[0], file_key[1], file_key[2], file_key[3], file_key[12], file_key[13], file_key[14], file_key[15]);
// Test the owner password...
make_password_key(pdf->encryption, pdf->file_keylen, pad, padkey);
make_user_key(file_id, file_idlen, own_user_key);
memcpy(pad, pdf->owner_key, sizeof(pad));
decrypt_ou_key(pdf->encryption, padkey, pdf->file_keylen, pad);
memcpy(pdf_user_key, pdf->user_key, sizeof(pdf_user_key));
decrypt_user_key(pdf->encryption, file_key, pdf_user_key);
PDFIO_DEBUG("_pdfioCryptoUnlock: Uuse=%02X%02X%02X%02X...%02X%02X%02X%02X\n", user_key[0], user_key[1], user_key[2], user_key[3], user_key[28], user_key[29], user_key[30], user_key[31]);
PDFIO_DEBUG("_pdfioCryptoUnlock: Updf=%02X%02X%02X%02X...%02X%02X%02X%02X\n", pdf_user_key[0], pdf_user_key[1], pdf_user_key[2], pdf_user_key[3], pdf_user_key[28], pdf_user_key[29], pdf_user_key[30], pdf_user_key[31]);
if (!memcmp(pad, pdf_user_key, 32) || !memcmp(own_user_key, pdf_user_key, 16))
if (test_password(pdf->encryption, pdf->file_keylen, pdf->permissions, file_id, file_idlen, pad, pdf->user_key, pdf->owner_key, pdf->encrypt_metadata, file_key))
{
// Matches!
memcpy(pdf->file_key, file_key, sizeof(pdf->file_key));
memcpy(pdf->file_key, file_key, pdf->file_keylen);
memcpy(pdf->password, pad, sizeof(pdf->password));
return (true);
@@ -832,14 +820,15 @@ _pdfioCryptoUnlock(
//
// 'decrypt_user_key()' - Decrypt the user key.
// 'decrypt_ou_key()' - Decrypt the user key.
//
static void
decrypt_user_key(
decrypt_ou_key(
pdfio_encryption_t encryption, // I - Type of encryption
const uint8_t *file_key, // I - File encryption key
uint8_t user_key[32]) // IO - User key
size_t file_keylen, // I - Length of file encryption key
uint8_t ou_key[32]) // IO - Owner/User key
{
size_t i, j; // Looping vars
_pdfio_rc4_t rc4; // RC4 encryption context
@@ -849,38 +838,38 @@ decrypt_user_key(
{
// Encrypt the result once...
_pdfioCryptoRC4Init(&rc4, file_key, 5);
_pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32);
_pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32);
}
else
{
// Encrypt the result 20 times...
uint8_t key[16]; // Current encryption key
for (i = 19; i > 0; i --)
for (i = 20; i > 0;)
{
// XOR each byte in the key with the loop counter...
for (j = 0; j < 16; j ++)
i --;
for (j = 0; j < file_keylen; j ++)
key[j] = (uint8_t)(file_key[j] ^ i);
_pdfioCryptoRC4Init(&rc4, key, 16);
_pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32);
_pdfioCryptoRC4Init(&rc4, key, file_keylen);
_pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32);
}
_pdfioCryptoRC4Init(&rc4, file_key, 16);
_pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32);
}
}
//
// 'encrypt_user_key()' - Encrypt the user key.
// 'encrypt_ou_key()' - Encrypt the owner/user key.
//
static void
encrypt_user_key(
encrypt_ou_key(
pdfio_encryption_t encryption, // I - Type of encryption
const uint8_t *file_key, // I - File encryption key
uint8_t user_key[32]) // IO - User key
size_t file_keylen, // I - Length of file encryption key
uint8_t ou_key[32]) // IO - Owner/User key
{
size_t i, j; // Looping vars
_pdfio_rc4_t rc4; // RC4 encryption context
@@ -890,7 +879,7 @@ encrypt_user_key(
{
// Encrypt the result once...
_pdfioCryptoRC4Init(&rc4, file_key, 5);
_pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32);
_pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32);
}
else
{
@@ -900,11 +889,11 @@ encrypt_user_key(
for (i = 0; i < 20; i ++)
{
// XOR each byte in the key with the loop counter...
for (j = 0; j < 16; j ++)
for (j = 0; j < file_keylen; j ++)
key[j] = (uint8_t)(file_key[j] ^ i);
_pdfioCryptoRC4Init(&rc4, key, 16);
_pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32);
_pdfioCryptoRC4Init(&rc4, key, file_keylen);
_pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32);
}
}
}
@@ -917,6 +906,7 @@ encrypt_user_key(
static void
make_file_key(
pdfio_encryption_t encryption, // I - Type of encryption
size_t file_keylen, // I - Encryption key length
pdfio_permission_t permissions, // I - File permissions
const unsigned char *file_id, // I - File ID value
size_t file_idlen, // I - Length of file ID
@@ -962,14 +952,14 @@ make_file_key(
for (i = 0; i < 50; i ++)
{
_pdfioCryptoMD5Init(&md5);
_pdfioCryptoMD5Append(&md5, digest, 16);
_pdfioCryptoMD5Append(&md5, digest, file_keylen);
_pdfioCryptoMD5Finish(&md5, digest);
}
}
PDFIO_DEBUG("make_file_key: file_key[16]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", digest[0], digest[1], digest[2], digest[3], digest[12], digest[13], digest[14], digest[15]);
memcpy(file_key, digest, 16);
memcpy(file_key, digest, file_keylen);
}
@@ -980,57 +970,57 @@ make_file_key(
static void
make_owner_key(
pdfio_encryption_t encryption, // I - Type of encryption
size_t file_keylen, // I - Length of encryption key
const uint8_t *owner_pad, // I - Padded owner password
const uint8_t *user_pad, // I - Padded user password
uint8_t owner_key[32]) // O - Owner key value
{
size_t i, j; // Looping vars
_pdfio_md5_t md5; // MD5 context
uint8_t digest[16]; // 128-bit MD5 digest
_pdfio_rc4_t rc4; // RC4 encryption context
uint8_t key[16]; // Base encryption key
// Hash the owner password...
make_password_key(encryption, file_keylen, owner_pad, key);
// Copy and encrypt the padded user password...
memcpy(owner_key, user_pad, 32);
decrypt_ou_key(encryption, key, file_keylen, owner_key);
}
//
// 'make_password_key()' - Make a password key.
//
static void
make_password_key(
pdfio_encryption_t encryption, // I - Type of encryption
size_t file_keylen, // I - Length of encryption key
const uint8_t *pad, // I - Padded password
uint8_t *key) // O - Key data
{
size_t i; // Looping var
_pdfio_md5_t md5; // MD5 context
uint8_t digest[16]; // 128-bit MD5 digest
// Hash the padded password...
_pdfioCryptoMD5Init(&md5);
_pdfioCryptoMD5Append(&md5, owner_pad, 32);
_pdfioCryptoMD5Append(&md5, pad, 32);
_pdfioCryptoMD5Finish(&md5, digest);
if (encryption != PDFIO_ENCRYPTION_RC4_40)
{
// Hash the hash another 50 times...
for (i = 0; i < 50; i ++)
{
_pdfioCryptoMD5Init(&md5);
_pdfioCryptoMD5Append(&md5, digest, 16);
_pdfioCryptoMD5Append(&md5, digest, file_keylen);
_pdfioCryptoMD5Finish(&md5, digest);
}
}
// Copy and encrypt the padded user password...
memcpy(owner_key, user_pad, 32);
if (encryption == PDFIO_ENCRYPTION_RC4_40)
{
// Encrypt once...
_pdfioCryptoRC4Init(&rc4, digest, 5);
_pdfioCryptoRC4Crypt(&rc4, owner_key, owner_key, 32);
}
else
{
// Encrypt 20 times...
uint8_t encrypt_key[16]; // RC4 encryption key
for (i = 20; i > 0;)
{
// XOR each byte in the digest with the loop counter to make a key...
i --;
for (j = 0; j < sizeof(encrypt_key); j ++)
encrypt_key[j] = (uint8_t)(digest[j] ^ i);
_pdfioCryptoRC4Init(&rc4, encrypt_key, sizeof(encrypt_key));
_pdfioCryptoRC4Crypt(&rc4, owner_key, owner_key, 32);
}
}
// Copy the key portion of the hashed password to the key...
memcpy(key, digest, file_keylen);
}
@@ -1085,3 +1075,52 @@ pad_password(const char *password, // I - Password string or `NULL`
if (len < 32)
memcpy(pad + len, pdf_passpad, 32 - len);
}
//
// 'test_password()' - Test a user password...
//
static bool // O - `true` if password is correct, `false` otherwise
test_password(
pdfio_encryption_t encryption, // I - Type of encryption
size_t file_keylen, // I - Length of encryption key
pdfio_permission_t permissions, // I - File permissions
const unsigned char *file_id, // I - File ID value
size_t file_idlen, // I - Length of file ID
const uint8_t *user_pad, // I - Padded user password
const uint8_t *user_key, // I - User key
const uint8_t *owner_key, // I - Owner key
bool encrypt_metadata,
// I - Encrypt metadata?
uint8_t file_key[16]) // O - Encryption key
{
uint8_t pdf_user_key[32]; // Decrypted user key
// Make the file key...
make_file_key(encryption, file_keylen, permissions, file_id, file_idlen, user_pad, owner_key, encrypt_metadata, file_key);
// Decrypt the user key using the file key...
memcpy(pdf_user_key, user_key, sizeof(pdf_user_key));
decrypt_ou_key(encryption, file_key, file_keylen, pdf_user_key);
PDFIO_DEBUG("test_password: pdf_user_key[32]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", pdf_user_key[0], pdf_user_key[1], pdf_user_key[2], pdf_user_key[3], pdf_user_key[28], pdf_user_key[29], pdf_user_key[30], pdf_user_key[31]);
if (encryption == PDFIO_ENCRYPTION_RC4_40 && !memcmp(pdf_user_key, pdf_passpad, 32))
{
// 40-bit encryption just compares the eecrypted user key matches...
return (true);
}
else
{
// 128-bit encryption needs to match the calculated user key...
uint8_t own_user_key[32]; // Calculated user key
// Calculate what the user key should be...
make_user_key(file_id, file_idlen, own_user_key);
PDFIO_DEBUG("test_password: own_user_key[32]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", own_user_key[0], own_user_key[1], own_user_key[2], own_user_key[3], own_user_key[28], own_user_key[29], own_user_key[30], own_user_key[31]);
// Return whether they match...
return (!memcmp(pdf_user_key, own_user_key, 16));
}
}

View File

@@ -1,7 +1,7 @@
//
// PDF dictionary functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -79,6 +79,10 @@ pdfioDictCopy(pdfio_file_t *pdf, // I - PDF file
PDFIO_DEBUG("pdfioDictCopy(pdf=%p, dict=%p(%p))\n", (void *)pdf, (void *)dict, dict ? (void *)dict->pdf : NULL);
// Range check input...
if (!pdf || !dict)
return (NULL);
// Create the new dictionary...
if ((ndict = pdfioDictCreate(pdf)) == NULL)
return (NULL);
@@ -92,6 +96,8 @@ pdfioDictCopy(pdfio_file_t *pdf, // I - PDF file
// Copy and add each of the source dictionary's key/value pairs...
for (i = dict->num_pairs, p = dict->pairs; i > 0; i --, p ++)
{
PDFIO_DEBUG("pdfioDictCopy: key=\"%s\", value.type=%d\n", p->key, p->value.type);
if (!strcmp(p->key, "Length") && p->value.type == PDFIO_VALTYPE_INDIRECT && dict->pdf != pdf)
{
// Don't use indirect stream lengths for copied objects...
@@ -102,15 +108,28 @@ pdfioDictCopy(pdfio_file_t *pdf, // I - PDF file
if (lenobj)
{
if (lenobj->value.type == PDFIO_VALTYPE_NONE)
_pdfioObjLoad(lenobj);
{
if (!_pdfioObjLoad(lenobj))
{
PDFIO_DEBUG("pdfioDictCopy: Unable to copy value of \"%s\", returning NULL.\n", p->key);
return (NULL);
}
}
v.value.number = lenobj->value.value.number;
}
else
{
v.value.number = 0.0;
}
PDFIO_DEBUG("pdfioDictCopy: Length is %.0f.\n", v.value.number);
}
else if (!_pdfioValueCopy(pdf, &v, dict->pdf, &p->value))
{
PDFIO_DEBUG("pdfioDictCopy: Unable to copy value of \"%s\", returning NULL.\n", p->key);
return (NULL); // Let pdfioFileClose do the cleanup...
}
if (_pdfioStringIsAllocated(dict->pdf, p->key))
key = pdfioStringCreate(pdf, p->key);
@@ -650,8 +669,7 @@ _pdfioDictRead(pdfio_file_t *pdf, // I - PDF file
}
else if (_pdfioDictGetValue(dict, key + 1))
{
// Issue 118: Discard duplicate key/value pairs, in the future this will
// be a warning message...
// Issue 118: Discard duplicate key/value pairs...
_pdfioValueDelete(&value);
if (_pdfioFileError(pdf, "WARNING: Discarding value for duplicate dictionary key '%s'.", key + 1))
continue;

View File

@@ -1,7 +1,7 @@
//
// PDF file functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -2006,6 +2006,9 @@ load_xref(
_pdfio_token_t tb; // Token buffer/stack
off_t line_offset; // Offset to start of line
pdfio_obj_t *pages_obj; // Pages object
size_t num_xrefs = 1; // Number of xref offsets
off_t xrefs[100] = { xref_offset };
// xref offsets
while (!done)
@@ -2471,13 +2474,31 @@ load_xref(
{
done = true;
}
else if (new_offset == xref_offset)
else
{
_pdfioFileError(pdf, "Recursive xref table.");
return (false);
}
// See if we've seen this xref table before...
size_t i; // Looping var
xref_offset = new_offset;
for (i = 0; i < num_xrefs; i ++)
{
if (new_offset == xrefs[i])
{
// Yes, error out...
_pdfioFileError(pdf, "Recursive xref table.");
return (false);
}
}
// No, save it...
if (i >= (sizeof(xrefs) / sizeof(xrefs[0])))
{
// Too many xref tables...
_pdfioFileError(pdf, "Too many xref tables.");
return (false);
}
xrefs[num_xrefs ++] = xref_offset = new_offset;
}
}
// Once we have all of the xref tables loaded, get the important objects and
@@ -2600,19 +2621,19 @@ repair_xref(
_pdfioTokenFlush(&tb);
if (type && !strcmp(line, "stream"))
if (!strcmp(line, "stream"))
{
// Possible object or XRef stream...
obj->stream_offset = _pdfioFileTell(pdf);
if (!strcmp(type, "ObjStm") && num_sobjs < (sizeof(sobjs) / sizeof(sobjs[0])))
if (type && !strcmp(type, "ObjStm") && num_sobjs < (sizeof(sobjs) / sizeof(sobjs[0])))
{
PDFIO_DEBUG("repair_xref: Object stream...\n");
sobjs[num_sobjs] = obj;
num_sobjs ++;
}
if (!strcmp(type, "XRef") && !pdf->trailer_dict)
if (type && !strcmp(type, "XRef") && !pdf->trailer_dict)
{
// Save the trailer dictionary...
pdfio_obj_t *encrypt_obj;

View File

@@ -1,7 +1,7 @@
//
// PDF object functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -69,7 +69,7 @@ pdfioObjCopy(pdfio_file_t *pdf, // I - PDF file
ssize_t bytes; // Bytes read
PDFIO_DEBUG("pdfioObjCopy(pdf=%p, srcobj=%p(%p))\n", (void *)pdf, (void *)srcobj, srcobj ? (void *)srcobj->pdf : NULL);
PDFIO_DEBUG("pdfioObjCopy(pdf=%p, srcobj=%p(%u,%p))\n", (void *)pdf, (void *)srcobj, srcobj ? (unsigned)srcobj->number : 0, srcobj ? (void *)srcobj->pdf : NULL);
// Range check input
if (!pdf || !srcobj)
@@ -77,7 +77,10 @@ pdfioObjCopy(pdfio_file_t *pdf, // I - PDF file
// Load the object value if needed...
if (srcobj->value.type == PDFIO_VALTYPE_NONE)
_pdfioObjLoad(srcobj);
{
if (!_pdfioObjLoad(srcobj))
return (NULL);
}
// See if we have already mapped this object...
if ((dstobj = _pdfioFileFindMappedObj(pdf, srcobj->pdf, srcobj->number)) != NULL)
@@ -544,6 +547,8 @@ pdfioObjOpenStream(pdfio_obj_t *obj, // I - Object
pdfio_stream_t *st; // Stream
PDFIO_DEBUG("pdfioObjOpenStream(obj=%p(%lu), decode=%s)\n", (void *)obj, obj ? (unsigned long)obj->number : 0, decode ? "true" : "false");
// Range check input...
if (!obj)
return (NULL);
@@ -563,7 +568,10 @@ pdfioObjOpenStream(pdfio_obj_t *obj, // I - Object
// No stream if there is no dict or offset to a stream...
if (obj->value.type != PDFIO_VALTYPE_DICT || !obj->stream_offset)
{
PDFIO_DEBUG("pdfioObjOpenStream: value.type=%d, stream_offset=%ld\n", obj->value.type, (long)obj->stream_offset);
return (NULL);
}
// Open the stream...
if ((st = _pdfioStreamOpen(obj, decode)) != NULL)

View File

@@ -105,6 +105,10 @@ pdfioPageOpenStream(
static _pdfio_value_t * // O - Value or NULL on error
get_contents(pdfio_obj_t *page) // I - Page object
{
_pdfio_value_t *contents; // Contents value
pdfio_obj_t *obj; // Contents object
// Range check input...
if (!page)
return (NULL);
@@ -119,5 +123,24 @@ get_contents(pdfio_obj_t *page) // I - Page object
if (page->value.type != PDFIO_VALTYPE_DICT)
return (NULL);
return (_pdfioDictGetValue(page->value.value.dict, "Contents"));
contents = _pdfioDictGetValue(page->value.value.dict, "Contents");
if (contents->type == PDFIO_VALTYPE_INDIRECT)
{
// See if the indirect object is a stream or an array of indirect object
// references...
if ((obj = pdfioFileFindObj(page->pdf, contents->value.indirect.number)) != NULL)
{
if (obj->value.type == PDFIO_VALTYPE_NONE)
{
if (!_pdfioObjLoad(obj))
return (NULL);
}
if (obj->value.type == PDFIO_VALTYPE_ARRAY)
contents = &(obj->value);
}
}
return (contents);
}

View File

@@ -94,7 +94,7 @@
//
# define PDFIO_MAX_DEPTH 32 // Maximum nesting depth for values
# define PDFIO_MAX_STRING 65536 // Maximum length of string
# define PDFIO_MAX_STRING 131072 // Maximum length of string
typedef void (*_pdfio_extfree_t)(void *);
// Extension data free function

View File

@@ -1,7 +1,7 @@
//
// PDF stream functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -615,11 +615,6 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
st->remaining -= st->flate.avail_in;
}
else if (!strcmp(filter, "LZWDecode"))
{
// LZW compression
st->filter = PDFIO_FILTER_LZW;
}
else
{
// Something else we don't support

View File

@@ -397,7 +397,7 @@ _pdfioTokenRead(_pdfio_token_t *tb, // I - Token buffer/stack
{
// UTF-16 string, convert to UTF-8...
PDFIO_DEBUG("_pdfioTokenRead: Converting string to UTF-8.\n", stderr);
_pdfio_utf16cpy(buffer + 1, (unsigned char *)buffer + 1, bufptr - buffer - 1, bufsize - 1);
_pdfio_utf16cpy(buffer + 1, (unsigned char *)buffer + 1, (size_t)(bufptr - buffer - 1), bufsize - 1);
PDFIO_DEBUG("_pdfioTokenRead: Read '%s'.\n", buffer);
return (true);

View File

@@ -1,7 +1,7 @@
//
// PDF value functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -76,7 +76,8 @@ _pdfioValueCopy(pdfio_file_t *pdfdst, // I - Destination PDF file
return (NULL);
case PDFIO_VALTYPE_ARRAY :
vdst->value.array = pdfioArrayCopy(pdfdst, vsrc->value.array);
if ((vdst->value.array = pdfioArrayCopy(pdfdst, vsrc->value.array)) == NULL)
return (NULL);
break;
case PDFIO_VALTYPE_BINARY :
@@ -97,12 +98,14 @@ _pdfioValueCopy(pdfio_file_t *pdfdst, // I - Destination PDF file
return (vdst);
case PDFIO_VALTYPE_DICT :
vdst->value.dict = pdfioDictCopy(pdfdst, vsrc->value.dict);
if ((vdst->value.dict = pdfioDictCopy(pdfdst, vsrc->value.dict)) == NULL)
return (NULL);
break;
case PDFIO_VALTYPE_NAME :
case PDFIO_VALTYPE_STRING :
vdst->value.name = pdfioStringCreate(pdfdst, vsrc->value.name);
if ((vdst->value.name = pdfioStringCreate(pdfdst, vsrc->value.name)) == NULL)
return (NULL);
break;
}

View File

@@ -360,7 +360,7 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_IMPLICIT_FALLTHROUGH = YES;
CLANG_WARN_IMPLICIT_FALLTHROUGH = NO;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
@@ -398,7 +398,7 @@
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -460,7 +460,7 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_IMPLICIT_FALLTHROUGH = YES;
CLANG_WARN_IMPLICIT_FALLTHROUGH = NO;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
@@ -497,7 +497,7 @@
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;

View File

@@ -3,7 +3,7 @@
<metadata>
<id>pdfio_native</id>
<title>PDFio Library for VS2019+</title>
<version>1.6.1</version>
<version>1.6.2</version>
<authors>Michael R Sweet</authors>
<owners>michaelrsweet</owners>
<projectUrl>https://github.com/michaelrsweet/pappl</projectUrl>
@@ -13,10 +13,10 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>PDFio Library for VS2019+</description>
<summary>PDFio is a simple C library for reading and writing PDF files. PDFio is licensed under the Apache License Version 2.0 with an (optional) exception to allow linking against GNU GPL2-only software.</summary>
<copyright>Copyright © 2019-2025 by Michael R Sweet</copyright>
<copyright>Copyright © 2019-2026 by Michael R Sweet</copyright>
<tags>pdf file native</tags>
<dependencies>
<dependency id="pdfio_native.redist" version="1.6.1" />
<dependency id="pdfio_native.redist" version="1.6.2" />
<dependency id="libpng_native.redist" version="1.6.30" />
<dependency id="zlib_native.redist" version="1.2.11" />
</dependencies>

View File

@@ -3,7 +3,7 @@
<metadata>
<id>pdfio_native.redist</id>
<title>PDFio Library for VS2019+</title>
<version>1.6.1</version>
<version>1.6.2</version>
<authors>Michael R Sweet</authors>
<owners>michaelrsweet</owners>
<projectUrl>https://github.com/michaelrsweet/pappl</projectUrl>
@@ -13,7 +13,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>PDFio Library for VS2019+</description>
<summary>PDFio is a simple C library for reading and writing PDF files. This package provides the redistributable content for the PDFio library. PDFio is licensed under the Apache License Version 2.0 with an (optional) exception to allow linking against GNU GPL2-only software.</summary>
<copyright>Copyright © 2019-2025 by Michael R Sweet</copyright>
<copyright>Copyright © 2019-2026 by Michael R Sweet</copyright>
<tags>pdf file native</tags>
<dependencies>
<dependency id="libpng_native.redist" version="1.6.30" />

View File

@@ -1,16 +1,20 @@
//
// Test program for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
//
// Usage:
//
// ./testpdfio
// ./testpdfio OPTIONS [FILENAME {OBJECT-NUMBER,OUT-FILENAME}] ...
//
// ./testpdfio [--verbose] FILENAME [OBJECT-NUMBER] [FILENAME [OBJECT-NUMBER]] ...
// Options:
//
// --help Show help
// --password PASSWORD Set access password
// --verbose Be verbose
//
#include "pdfio-private.h"
@@ -29,7 +33,7 @@
static int do_crypto_tests(void);
static int do_pdfa_tests(void);
static int do_test_file(const char *filename, int objnum, const char *password, bool verbose);
static int do_test_file(const char *filename, const char *outfile, int objnum, const char *password, bool verbose);
static int do_unit_tests(void);
static int draw_image(pdfio_stream_t *st, const char *name, double x, double y, double w, double h, const char *label);
static bool error_cb(pdfio_file_t *pdf, const char *message, bool *error);
@@ -103,14 +107,18 @@ main(int argc, // I - Number of command-line arguments
else if ((i + 1) < argc && isdigit(argv[i + 1][0] & 255))
{
// filename.pdf object-number
if (do_test_file(argv[i], atoi(argv[i + 1]), password, verbose))
if (do_test_file(argv[i], /*outfile*/NULL, atoi(argv[i + 1]), password, verbose))
ret = 1;
i ++;
}
else if (do_test_file(argv[i], 0, password, verbose))
else
{
ret = 1;
if (do_test_file(argv[i], argv[i + 1], /*objnum*/0, password, verbose))
ret = 1;
if (argv[i + 1])
i ++;
}
}
}
@@ -130,6 +138,7 @@ main(int argc, // I - Number of command-line arguments
return (ret);
}
//
// 'do_crypto_tests()' - Test the various cryptographic functions in PDFio.
//
@@ -429,12 +438,15 @@ do_pdfa_tests(void)
static int // O - Exit status
do_test_file(const char *filename, // I - PDF filename
const char *outfile, // I - Output filename, if any
int objnum, // I - Object number to dump, if any
const char *password, // I - Password for file
bool verbose) // I - Be verbose?
{
int status = 0; // Exit status
bool error = false; // Have we shown an error yet?
pdfio_file_t *pdf; // PDF file
pdfio_file_t *pdf, // PDF file
*outpdf; // Output PDF file, if any
size_t n, // Object/page index
num_objs, // Number of objects
num_pages; // Number of pages
@@ -444,7 +456,12 @@ do_test_file(const char *filename, // I - PDF filename
// Try opening the file...
if (!objnum)
testBegin("%s", filename);
{
if (outfile)
testBegin("%s -> %s", filename, outfile);
else
testBegin("%s", filename);
}
if ((pdf = pdfioFileOpen(filename, password_cb, (void *)password, (pdfio_error_cb_t)error_cb, &error)) != NULL)
{
@@ -458,6 +475,7 @@ do_test_file(const char *filename, // I - PDF filename
if ((obj = pdfioFileFindObj(pdf, (size_t)objnum)) == NULL)
{
puts("Not found.");
pdfioFileClose(pdf);
return (1);
}
@@ -465,6 +483,7 @@ do_test_file(const char *filename, // I - PDF filename
{
_pdfioValueDebug(&obj->value, stdout);
putchar('\n');
pdfioFileClose(pdf);
return (0);
}
@@ -474,6 +493,7 @@ do_test_file(const char *filename, // I - PDF filename
{
_pdfioValueDebug(&obj->value, stdout);
putchar('\n');
pdfioFileClose(pdf);
return (0);
}
@@ -481,6 +501,7 @@ do_test_file(const char *filename, // I - PDF filename
fwrite(buffer, 1, (size_t)bytes, stdout);
pdfioStreamClose(st);
pdfioFileClose(pdf);
return (0);
}
@@ -488,56 +509,80 @@ do_test_file(const char *filename, // I - PDF filename
{
testEnd(true);
// Show basic stats...
num_objs = pdfioFileGetNumObjs(pdf);
num_pages = pdfioFileGetNumPages(pdf);
printf(" PDF %s, %d pages, %d objects.\n", pdfioFileGetVersion(pdf), (int)num_pages, (int)num_objs);
if (verbose)
if (outfile)
{
// Show a summary of each page...
for (n = 0; n < num_pages; n ++)
{
if ((obj = pdfioFileGetPage(pdf, n)) == NULL)
{
printf("%s: Unable to get page #%d.\n", filename, (int)n + 1);
}
else
{
pdfio_rect_t media_box; // MediaBox value
memset(&media_box, 0, sizeof(media_box));
dict = pdfioObjGetDict(obj);
if (!pdfioDictGetRect(dict, "MediaBox", &media_box))
// Copy pages to the output file...
if ((outpdf = pdfioFileCreate(outfile, pdfioFileGetVersion(pdf), /*media_box*/NULL, /*crop_box*/NULL, (pdfio_error_cb_t)error_cb, &error)) != NULL)
{
for (n = 0, num_pages = pdfioFileGetNumPages(pdf); n < num_pages; n ++)
{
if (!pdfioPageCopy(outpdf, pdfioFileGetPage(pdf, n)))
{
if ((obj = pdfioDictGetObj(dict, "Parent")) != NULL)
{
dict = pdfioObjGetDict(obj);
pdfioDictGetRect(dict, "MediaBox", &media_box);
}
status = 1;
break;
}
}
printf(" Page #%d (obj %d) is %gx%g.\n", (int)n + 1, (int)pdfioObjGetNumber(obj), media_box.x2, media_box.y2);
}
}
pdfioFileClose(outpdf);
}
}
else
{
// Show basic stats...
num_objs = pdfioFileGetNumObjs(pdf);
num_pages = pdfioFileGetNumPages(pdf);
// Show the associated value with each object...
for (n = 0; n < num_objs; n ++)
printf(" PDF %s, %d pages, %d objects.\n", pdfioFileGetVersion(pdf), (int)num_pages, (int)num_objs);
if (verbose)
{
if ((obj = pdfioFileGetObj(pdf, n)) == NULL)
// Show a summary of each page...
for (n = 0; n < num_pages; n ++)
{
printf(" Unable to get object #%d.\n", (int)n);
}
else
{
dict = pdfioObjGetDict(obj);
if ((obj = pdfioFileGetPage(pdf, n)) == NULL)
{
printf("%s: Unable to get page #%d.\n", filename, (int)n + 1);
}
else
{
pdfio_rect_t media_box; // MediaBox value
printf(" %u %u obj dict=%p(%lu pairs)\n", (unsigned)pdfioObjGetNumber(obj), (unsigned)pdfioObjGetGeneration(obj), (void *)dict, dict ? (unsigned long)dict->num_pairs : 0UL);
fputs(" ", stdout);
_pdfioValueDebug(&obj->value, stdout);
putchar('\n');
memset(&media_box, 0, sizeof(media_box));
dict = pdfioObjGetDict(obj);
if (!pdfioDictGetRect(dict, "MediaBox", &media_box))
{
pdfio_obj_t *parent; // Parent object
while ((parent = pdfioDictGetObj(dict, "Parent")) != NULL)
{
dict = pdfioObjGetDict(parent);
if (pdfioDictGetRect(dict, "MediaBox", &media_box))
break;
}
}
printf(" Page #%d (obj %d) is %gx%g.\n", (int)n + 1, (int)pdfioObjGetNumber(obj), media_box.x2, media_box.y2);
}
}
// Show the associated value with each object...
for (n = 0; n < num_objs; n ++)
{
if ((obj = pdfioFileGetObj(pdf, n)) == NULL)
{
printf(" Unable to get object #%d.\n", (int)n);
status = 1;
}
else
{
dict = pdfioObjGetDict(obj);
printf(" %u %u obj dict=%p(%lu pairs)\n", (unsigned)pdfioObjGetNumber(obj), (unsigned)pdfioObjGetGeneration(obj), (void *)dict, dict ? (unsigned long)dict->num_pairs : 0UL);
fputs(" ", stdout);
_pdfioValueDebug(&obj->value, stdout);
putchar('\n');
}
}
}
}
@@ -545,7 +590,7 @@ do_test_file(const char *filename, // I - PDF filename
// Close the file and return success...
pdfioFileClose(pdf);
return (0);
return (status);
}
else
{
@@ -1739,7 +1784,7 @@ token_peek_cb(const char **s, // IO - Test string
static int // O - Exit status
usage(FILE *fp) // I - Output file
{
fputs("Usage: ./testpdfio [OPTIONS] [FILENAME [OBJNUM]] ...\n", fp);
fputs("Usage: ./testpdfio [OPTIONS] [FILENAME {OBJECT-NUM,OUT-FILENAME}] ...\n", fp);
fputs("Options:\n", fp);
fputs(" --help Show program help.\n", fp);
fputs(" --password PASSWORD Set PDF password.\n", fp);