Implement changes suggested by Zarko and our subsequent conversations:

- Add a way to set the minimum PDF version
 - Add a way to force the PDF version
 - Have isEncrypted return true if an /Encrypt dictionary exists even
   when we can't read the file
 - Allow qpdf_init_write to be called multiple times
 - Update some comments in headers


git-svn-id: svn+q:///qpdf/trunk@748 71b93d88-0707-0410-a8cf-f5a4172ac649
This commit is contained in:
Jay Berkenbilt 2009-10-05 00:42:48 +00:00
parent c1e53f1480
commit c2023db265
13 changed files with 255 additions and 47 deletions

View File

@ -1,5 +1,9 @@
2009-10-04 Jay Berkenbilt <ejb@ql.org>
* Add methods to QPDFWriter and corresponding command line
arguments to qpdf to set the minimum output PDF version and also
to force the version to a particular value.
* libqpdf/QPDF.cc (processXRefStream): warn and ignore extra xref
stream entries when stream is larger than reported size. This
used to be a fatal error. (Fixes qpdf-Bugs-2872265.)

12
TODO
View File

@ -1,15 +1,3 @@
Now
===
* Add functions to set minimum version and to force pdf version
* Make multiple calls to init_write safe; document that write
parameter settings must be repeated
* qpdf_is_encrypted returns false for encrypted file when incorrect
password is given
2.1
===

View File

@ -34,7 +34,13 @@ class Pl_Count;
class QPDFWriter
{
public:
// Passing null as filename means write to stdout
// Passing null as filename means write to stdout. QPDFWriter
// will create a zero-length output file upon construction. If
// write fails, the empty or partially written file will not be
// deleted. This is by design: sometimes the partial file may be
// useful for tracking down problems. If your application doesn't
// want the partially written file to be left behind, you should
// delete it the eventual call to write fails.
DLL_EXPORT
QPDFWriter(QPDF& pdf, char const* filename);
DLL_EXPORT
@ -78,6 +84,30 @@ class QPDFWriter
DLL_EXPORT
void setQDFMode(bool);
// Set the minimum PDF version. If the PDF version of the input
// file (or previously set minimum version) is less than the
// version passed to this method, the PDF version of the output
// file will be set to this value. If the original PDF file's
// version or previously set minimum version is already this
// version or later, the original file's version will be used.
// QPDFWriter automatically sets the minimum version to 1.4 when
// R3 encryption parameters are used, and to 1.5 when object
// streams are used.
DLL_EXPORT
void setMinimumPDFVersion(std::string const&);
// 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
// properly with older PDF viewers. When a PDF version is set
// using this function, qpdf will use this version even if the
// file contains features that are not supported in that version
// of PDF. In other words, you should only use this function if
// you are sure the PDF file in question has no features of newer
// versions of PDF or if you are willing to create files that old
// viewers may try to open but not be able to properly interpret.
DLL_EXPORT
void forcePDFVersion(std::string const&);
// Cause a static /ID value to be generated. Use only in test
// suites.
DLL_EXPORT
@ -241,6 +271,7 @@ class QPDFWriter
std::string id1; // for /ID key of
std::string id2; // trailer dictionary
std::string min_pdf_version;
std::string forced_pdf_version;
int encryption_dict_objid;
std::string cur_data_key;
std::list<PointerHolder<Pipeline> > to_delete;

View File

@ -189,7 +189,12 @@ extern "C" {
/* Supply the name of the file to be written and initialize the
* qpdf_data object to handle writing operations. This function
* also attempts to create the file. The PDF data is not written
* until the call to qpdf_write.
* until the call to qpdf_write. qpdf_init_write may be called
* multiple times for the same qpdf_data object. When
* qpdf_init_write is called, all information from previous calls
* to functions that set write parameters (qpdf_set_linearization,
* etc.) is lost, so any write parameter functions must be called
* again.
*/
DLL_EXPORT
QPDF_ERROR_CODE qpdf_init_write(qpdf_data qpdf, char const* filename);
@ -256,6 +261,12 @@ extern "C" {
DLL_EXPORT
void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value);
DLL_EXPORT
void qpdf_set_minimum_pdf_version(qpdf_data qpdf, char const* version);
DLL_EXPORT
void qpdf_force_pdf_version(qpdf_data qpdf, char const* version);
/* Do actual write operation. */
DLL_EXPORT
QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf);

View File

@ -99,6 +99,37 @@ QPDFWriter::setQDFMode(bool val)
this->qdf_mode = val;
}
void
QPDFWriter::setMinimumPDFVersion(std::string const& version)
{
bool set_version = false;
if (this->min_pdf_version.empty())
{
set_version = true;
}
else
{
float v = atof(version.c_str());
float mv = atof(this->min_pdf_version.c_str());
if (v > mv)
{
QTC::TC("qpdf", "QPDFWriter increasing minimum version");
set_version = true;
}
}
if (set_version)
{
this->min_pdf_version = version;
}
}
void
QPDFWriter::forcePDFVersion(std::string const& version)
{
this->forced_pdf_version = version;
}
void
QPDFWriter::setStaticID(bool val)
{
@ -147,7 +178,7 @@ QPDFWriter::setR2EncryptionParameters(
clear.insert(6);
}
this->min_pdf_version = "1.3";
setMinimumPDFVersion("1.3");
setEncryptionParameters(user_password, owner_password, 1, 2, 5, clear);
}
@ -221,7 +252,7 @@ QPDFWriter::setR3EncryptionParameters(
// no default so gcc warns for missing cases
}
this->min_pdf_version = "1.4";
setMinimumPDFVersion("1.4");
setEncryptionParameters(user_password, owner_password, 2, 3, 16, clear);
}
@ -1361,7 +1392,7 @@ QPDFWriter::write()
if (! this->object_stream_to_objects.empty())
{
this->min_pdf_version = "1.5";
setMinimumPDFVersion("1.5");
}
generateID();
@ -1417,15 +1448,12 @@ QPDFWriter::writeEncryptionDictionary()
void
QPDFWriter::writeHeader()
{
std::string version = pdf.getPDFVersion();
if (! this->min_pdf_version.empty())
setMinimumPDFVersion(pdf.getPDFVersion());
std::string version = this->min_pdf_version;
if (! this->forced_pdf_version.empty())
{
float ov = atof(version.c_str());
float mv = atof(this->min_pdf_version.c_str());
if (mv > ov)
{
version = this->min_pdf_version;
}
QTC::TC("qpdf", "QPDFWriter using forced PDF version");
version = this->forced_pdf_version;
}
writeString("%PDF-");

View File

@ -289,6 +289,11 @@ QPDF::initializeEncryption()
return;
}
// Go ahead and set this->encryption here. That way, isEncrypted
// will return true even if there were errors reading the
// encryption dictionary.
this->encrypted = true;
QPDFObjectHandle id_obj = this->trailer.getKey("/ID");
if (! (id_obj.isArray() &&
(id_obj.getArrayNItems() == 2) &&
@ -377,7 +382,6 @@ QPDF::initializeEncryption()
throw QPDFExc(this->file.getName() + ": invalid password");
}
this->encrypted = true;
this->encryption_key = compute_encryption_key(this->user_password, data);
}

View File

@ -252,6 +252,12 @@ DLL_EXPORT
QPDF_ERROR_CODE qpdf_init_write(qpdf_data qpdf, char const* filename)
{
QPDF_ERROR_CODE status = QPDF_SUCCESS;
if (qpdf->qpdf_writer)
{
QTC::TC("qpdf", "qpdf-c called qpdf_init_write multiple times");
delete qpdf->qpdf_writer;
qpdf->qpdf_writer = 0;
}
try
{
qpdf->qpdf_writer = new QPDFWriter(*(qpdf->qpdf), filename);
@ -390,6 +396,20 @@ void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value)
qpdf->qpdf_writer->setLinearization(value);
}
DLL_EXPORT
void qpdf_set_minimum_pdf_version(qpdf_data qpdf, char const* version)
{
QTC::TC("qpdf", "qpdf-c called qpdf_set_minimum_pdf_version");
qpdf->qpdf_writer->setMinimumPDFVersion(version);
}
DLL_EXPORT
void qpdf_force_pdf_version(qpdf_data qpdf, char const* version)
{
QTC::TC("qpdf", "qpdf-c called qpdf_force_pdf_version");
qpdf->qpdf_writer->forcePDFVersion(version);
}
DLL_EXPORT
QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf)
{

View File

@ -20,7 +20,8 @@ static void report_errors()
static void test01(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_read(qpdf, infile, password);
printf("version: %s\n", qpdf_get_pdf_version(qpdf));
@ -53,7 +54,8 @@ static void test01(char const* infile,
static void test02(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_set_suppress_warnings(qpdf, QPDF_TRUE);
if (((qpdf_read(qpdf, infile, password) & QPDF_ERRORS) == 0) &&
@ -67,7 +69,8 @@ static void test02(char const* infile,
static void test03(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_read(qpdf, infile, password);
qpdf_init_write(qpdf, outfile);
@ -79,7 +82,8 @@ static void test03(char const* infile,
static void test04(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_set_ignore_xref_streams(qpdf, QPDF_TRUE);
qpdf_read(qpdf, infile, password);
@ -91,7 +95,8 @@ static void test04(char const* infile,
static void test05(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_read(qpdf, infile, password);
qpdf_init_write(qpdf, outfile);
@ -103,7 +108,8 @@ static void test05(char const* infile,
static void test06(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_read(qpdf, infile, password);
qpdf_init_write(qpdf, outfile);
@ -115,7 +121,8 @@ static void test06(char const* infile,
static void test07(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_read(qpdf, infile, password);
qpdf_init_write(qpdf, outfile);
@ -127,7 +134,8 @@ static void test07(char const* infile,
static void test08(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_read(qpdf, infile, password);
qpdf_init_write(qpdf, outfile);
@ -140,7 +148,8 @@ static void test08(char const* infile,
static void test09(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_read(qpdf, infile, password);
qpdf_init_write(qpdf, outfile);
@ -152,7 +161,8 @@ static void test09(char const* infile,
static void test10(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_set_attempt_recovery(qpdf, QPDF_FALSE);
qpdf_read(qpdf, infile, password);
@ -161,7 +171,8 @@ static void test10(char const* infile,
static void test11(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_read(qpdf, infile, password);
qpdf_init_write(qpdf, outfile);
@ -174,7 +185,8 @@ static void test11(char const* infile,
static void test12(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_read(qpdf, infile, password);
qpdf_init_write(qpdf, outfile);
@ -188,7 +200,8 @@ static void test12(char const* infile,
static void test13(char const* infile,
char const* password,
char const* outfile)
char const* outfile,
char const* outfile2)
{
qpdf_read(qpdf, infile, password);
printf("user password: %s\n", qpdf_get_user_password(qpdf));
@ -199,15 +212,33 @@ static void test13(char const* infile,
report_errors();
}
static void test14(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_minimum_pdf_version(qpdf, "1.6");
qpdf_write(qpdf);
qpdf_init_write(qpdf, outfile2);
qpdf_set_static_ID(qpdf, QPDF_TRUE);
qpdf_force_pdf_version(qpdf, "1.4");
qpdf_write(qpdf);
report_errors();
}
int main(int argc, char* argv[])
{
char* whoami = 0;
char* p = 0;
int n = 0;
char const* infile;
char const* password;
char const* outfile;
void (*fn)(char const*, char const*, char const*) = 0;
char const* infile = 0;
char const* password = 0;
char const* outfile = 0;
char const* outfile2 = 0;
void (*fn)(char const*, char const*, char const*, char const*) = 0;
if ((p = strrchr(argv[0], '/')) != NULL)
{
@ -221,7 +252,7 @@ int main(int argc, char* argv[])
{
whoami = argv[0];
}
if (argc != 5)
if (argc < 5)
{
fprintf(stderr, "usage: %s n infile password outfile\n", whoami);
exit(2);
@ -231,6 +262,7 @@ int main(int argc, char* argv[])
infile = argv[2];
password = argv[3];
outfile = argv[4];
outfile2 = (argc > 5 ? argv[5] : 0);
fn = ((n == 1) ? test01 :
(n == 2) ? test02 :
@ -245,6 +277,7 @@ int main(int argc, char* argv[])
(n == 11) ? test11 :
(n == 12) ? test12 :
(n == 13) ? test13 :
(n == 14) ? test14 :
0);
if (fn == 0)
@ -254,7 +287,7 @@ int main(int argc, char* argv[])
}
qpdf = qpdf_init();
fn(infile, password, outfile);
fn(infile, password, outfile, outfile2);
qpdf_cleanup(&qpdf);
assert(qpdf == 0);

View File

@ -103,6 +103,8 @@ familiar with the PDF file format or who are PDF developers.\n\
--object-streams=mode controls handing of object streams\n\
--ignore-xref-streams tells qpdf to ignore any cross-reference streams\n\
--qdf turns on \"QDF mode\" (below)\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\
Values for stream data options:\n\
\n\
@ -119,6 +121,12 @@ Values for object stream mode:\n\
In qdf mode, by default, content normalization is turned on, and the\n\
stream data mode is set to uncompress.\n\
\n\
Setting the minimum PDF version of the output file may raise the version\n\
but will never lower it. Forcing the PDF version of the output file may\n\
set the PDF version to a lower value than actually allowed by the file's\n\
contents. You should only do this if you have no other possible way to\n\
open the file or if you know that the file definitely doesn't include\n\
features not supported later versions.\n\
\n\
Testing, Inspection, and Debugging Options\n\
------------------------------------------\n\
@ -518,6 +526,8 @@ int main(int argc, char* argv[])
QPDFWriter::object_stream_e object_stream_mode = QPDFWriter::o_preserve;
bool ignore_xref_streams = false;
bool qdf_mode = false;
std::string min_version;
std::string force_version;
bool static_id = false;
bool suppress_original_object_id = false;
@ -651,6 +661,24 @@ int main(int argc, char* argv[])
{
qdf_mode = true;
}
else if (strcmp(arg, "min-version") == 0)
{
if (parameter == 0)
{
usage("--min-version be given as"
"--min-version=version");
}
min_version = parameter;
}
else if (strcmp(arg, "force-version") == 0)
{
if (parameter == 0)
{
usage("--force-version be given as"
"--force-version=version");
}
force_version = parameter;
}
else if (strcmp(arg, "static-id") == 0)
{
static_id = true;
@ -977,6 +1005,14 @@ int main(int argc, char* argv[])
{
w.setObjectStreamMode(object_stream_mode);
}
if (! min_version.empty())
{
w.setMinimumPDFVersion(min_version);
}
if (! force_version.empty())
{
w.forcePDFVersion(force_version);
}
w.write();
}
if (! pdf.getWarnings().empty())

View File

@ -153,3 +153,8 @@ 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 using forced PDF version 0
qpdf-c called qpdf_set_minimum_pdf_version 0
qpdf-c called qpdf_force_pdf_version 0
qpdf-c called qpdf_init_write multiple times 0

View File

@ -81,7 +81,7 @@ flush_tiff_cache();
show_ntests();
# ----------
$td->notify("--- Miscellaneous Tests ---");
$n_tests += 7;
$n_tests += 14;
foreach (my $i = 1; $i <= 3; ++$i)
{
@ -115,6 +115,44 @@ $td->runtest("show new xref stream",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
# Min/Force version
$td->runtest("set min version",
{$td->COMMAND => "qpdf --min-version=1.6 good1.pdf a.pdf"},
{$td->STRING => "",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check version",
{$td->COMMAND => "qpdf --check a.pdf"},
{$td->FILE => "min-version.out",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("force version",
{$td->COMMAND => "qpdf --force-version=1.4 a.pdf b.pdf"},
{$td->STRING => "",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check version",
{$td->COMMAND => "qpdf --check b.pdf"},
{$td->FILE => "forced-version.out",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
unlink "a.pdf", "b.pdf" or die;
$td->runtest("C API: min/force versions",
{$td->COMMAND => "qpdf-ctest 14 object-stream.pdf '' a.pdf b.pdf"},
{$td->STRING => "",
$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->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("C check version 2",
{$td->COMMAND => "qpdf --check b.pdf"},
{$td->FILE => "forced-version.out",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
show_ntests();
# ----------
$td->notify("--- Error Condition Tests ---");
@ -883,7 +921,7 @@ foreach my $d (@cenc)
my ($n, $infile, $pass, $description, $output) = @$d;
my $outfile = $description;
$outfile =~ s/ /-/g;
my $outfile = "c-$outfile.pdf";
$outfile = "c-$outfile.pdf";
$td->runtest("C API encryption: $description",
{$td->COMMAND => "qpdf-ctest $n $infile $pass a.pdf"},
{$td->STRING => $output, $td->EXIT_STATUS => 0},

View File

@ -0,0 +1,5 @@
checking b.pdf
PDF Version: 1.4
File is not encrypted
File is not linearized
No errors found

View File

@ -0,0 +1,5 @@
checking a.pdf
PDF Version: 1.6
File is not encrypted
File is not linearized
No errors found