Commit 179636571352e754ec3d05ae28a10bf0a4659c9a

Authored by m-holger
Committed by GitHub
2 parents f6ae1ff1 50d385c8

Merge branch 'main' into mslichao/capifreebuf

.idea/cmake.xml
... ... @@ -2,7 +2,6 @@
2 2 <project version="4">
3 3 <component name="CMakeSharedSettings">
4 4 <configurations>
5   - <configuration PROFILE_NAME="Debug" ENABLED="true" CONFIG_NAME="Debug" />
6 5 <configuration PROFILE_NAME="Maintainer" ENABLED="true" CONFIG_NAME="RelWithDebInfo" GENERATION_OPTIONS="-DMAINTAINER_MODE=ON -DBUILD_STATIC_LIBS=OFF" />
7 6 <configuration PROFILE_NAME="Windows" ENABLED="true" CONFIG_NAME="RelWithDebInfo" TOOLCHAIN_NAME="Visual Studio" GENERATION_OPTIONS="-DBUILD_SHARED_LIBS=OFF" />
8 7 </configurations>
... ...
ChangeLog
1 1 2024-09-20 Chao Li <mslichao@outlook.com>
2 2  
3 3 * Add C API qpdf_oh_free_buffer to release memory allocated by
4   - stream data functions
  4 + stream data functions.
  5 +
  6 +2024-08-25 M Holger <m.holger@qpdf.org>
  7 +
  8 + * Add new command-line arguments --remove-metadata and --remove-info
  9 + to exclude document metadata and information from the output PDF
  10 + file. Patially fixes #1145.
5 11  
6 12 2024-08-06 M Holger <m.holger@qpdf.org>
7 13  
... ...
fuzz/CMakeLists.txt
... ... @@ -142,6 +142,10 @@ set(CORPUS_OTHER
142 142 70306b.fuzz
143 143 71624.fuzz
144 144 71689.fuzz
  145 + 99999a.fuzz
  146 + 99999b.fuzz
  147 + 99999c.fuzz
  148 + 99999d.fuzz
145 149 )
146 150  
147 151 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
... ...
fuzz/qpdf_extra/99999a.fuzz 0 → 100644
  1 +%PDF-1.5
  2 +%€€€€
  3 +1 0 obj
  4 +<<
  5 + /Type /Catalog
  6 + /Pages 2 0 R
  7 +>>
  8 +endobj
  9 +2 0 obj
  10 +<<
  11 + /Count 6 Ri
  12 + 0K/ds [3 0 R]
  13 + /Type /Pages
  14 +>>
  15 +endobj
  16 +3 0 obj
  17 +<<
  18 + /Resources <<
  19 + /Font <<
  20 + /F1 5 0 R
  21 + >>
  22 + >>
  23 + /MediaBox [0 0 795 842]
  24 + /Parent 2 0 R
  25 + /Contents 4 0 R
  26 + /Type /Page
  27 +=>
  28 +endobj
  29 +4 0 obj
  30 +<<444444444444444444444444 1 Tr /F1 30 Tf 350 750 Td (foobar) Tj ET
  31 +endstream
  32 +endobj
  33 +5 0 obj
  34 +<<
  35 + /Name /F1
  36 + /BaseFont /Helvetica
  37 + /Type /Font
  38 + /Subtype /Type1
  39 +>>
  40 +e„dobj
  41 +6 0 obj
  42 +<< /Length 6 0 R >>
  43 +stre444444444444444444444444444444<<>>
  44 +endobj
  45 +xref
  46 +0 8
  47 +0000000000 65535 f
  48 +0000000015 00000 n
  49 +0000000066 00000 n
  50 +0000000130 00000 n
  51 +0000000269 00000 n
  52 +0000000362 00000 n
  53 +000000ÎËËÉßÏÏÏ00 n
  54 +0000000500 00000 n
  55 +trailer
  56 +<<
  57 + /Size 713115528178535
  58 + /Root 1 0 R
  59 + /Info 7 0 R
  60 +>>
  61 +startxref
  62 +520
  63 +%%EOF
0 64 \ No newline at end of file
... ...
fuzz/qpdf_extra/99999b.fuzz 0 → 100644
No preview for this file type
fuzz/qpdf_extra/99999c.fuzz 0 → 100644
No preview for this file type
fuzz/qpdf_extra/99999d.fuzz 0 → 100644
No preview for this file type
fuzz/qtest/fuzz.test
... ... @@ -11,7 +11,7 @@ my $td = new TestDriver(&#39;fuzz&#39;);
11 11  
12 12 my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS";
13 13  
14   -my $n_qpdf_files = 79; # increment when adding new files
  14 +my $n_qpdf_files = 83; # increment when adding new files
15 15  
16 16 my @fuzzers = (
17 17 ['ascii85' => 1],
... ...
include/qpdf/QPDFJob.hh
... ... @@ -692,6 +692,8 @@ class QPDFJob
692 692 bool optimize_images{false};
693 693 bool externalize_inline_images{false};
694 694 bool keep_inline_images{false};
  695 + bool remove_info{false};
  696 + bool remove_metadata{false};
695 697 bool remove_page_labels{false};
696 698 size_t oi_min_width{DEFAULT_OI_MIN_WIDTH};
697 699 size_t oi_min_height{DEFAULT_OI_MIN_HEIGHT};
... ...
include/qpdf/auto_job_c_main.hh
... ... @@ -32,6 +32,8 @@ QPDF_DLL Config* progress();
32 32 QPDF_DLL Config* qdf();
33 33 QPDF_DLL Config* rawStreamData();
34 34 QPDF_DLL Config* recompressFlate();
  35 +QPDF_DLL Config* removeInfo();
  36 +QPDF_DLL Config* removeMetadata();
35 37 QPDF_DLL Config* removePageLabels();
36 38 QPDF_DLL Config* reportMemoryUsage();
37 39 QPDF_DLL Config* requiresPassword();
... ...
job.sums
... ... @@ -4,17 +4,17 @@ generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a
4 4 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4
5 5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42
6 6 include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5
7   -include/qpdf/auto_job_c_main.hh dbfc221d1533120d1aa9c361d8d2483dea5fcb1c0fd95144d98d305e64ed32a6
  7 +include/qpdf/auto_job_c_main.hh 84f463237235b2c095b747a4f5dd00f109ee596a1c207b944efb296c0c568cae
8 8 include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa86d83df31051d82506
9 9 include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62
10   -job.yml 53cad86659db6722e8f415aacb19fc51ab81bb1589c3cb8f65ec893bb4bf5566
  10 +job.yml 31935064eca625af7657b23f2f12c614d14751ec0b12702482b1768a04905d22
11 11 libqpdf/qpdf/auto_job_decl.hh 20d6affe1e260f5a1af4f1d82a820b933835440ff03020e877382da2e8dac6c6
12   -libqpdf/qpdf/auto_job_help.hh 74b2982771720927ce7be8f1690720ec65cb9989620493a0c154f50ba2c254e4
13   -libqpdf/qpdf/auto_job_init.hh 19d1da7c4c0c635bd1c5db8d5f17df8edad3442f8eba006adb075cec295fa158
  12 +libqpdf/qpdf/auto_job_help.hh 1e9181f4729a22ff91ab54e2b4a82e6af0c57a8327efb222a4196adb609c1ade
  13 +libqpdf/qpdf/auto_job_init.hh e2a6bb87870c5522a01b15461c9fe909e360f5c7fed06e41acf13a125bd1d03e
14 14 libqpdf/qpdf/auto_job_json_decl.hh 843892c8e8652a86b7eb573893ef24050b7f36fe313f7251874be5cd4cdbe3fd
15   -libqpdf/qpdf/auto_job_json_init.hh a87256c082427ec0318223762472970b2eced535c0c8b0288d45c8cdaaf62f74
16   -libqpdf/qpdf/auto_job_schema.hh 5dac568dff39614e161a0af59a0f328f1e28edf69b96f08bb76fd592d51bb053
  15 +libqpdf/qpdf/auto_job_json_init.hh 344c2fb473f88fe829c93b1efe6c70a0e4796537b8eb35e421d955fff481ba7d
  16 +libqpdf/qpdf/auto_job_schema.hh 6d3eef5137b8828eaa301a1b3cf75cb7bb812aa6e2d8301de865b42d238d7a7c
17 17 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580
18   -manual/cli.rst 94057baba9ecffb4ce19ae61c8fa507ef07209c280fccae97b283c3dfce834e0
19   -manual/qpdf.1 0ec05f1392c160165cdf6adada4de84c0de75bd2fb5762caff4e1372aacada4c
  18 +manual/cli.rst b7f37995f13346518ae7b2ea84836fba13b4da4e1f55be5f2a861f20dea0ccdb
  19 +manual/qpdf.1 59c26635017cba5d142ec3fcc4aebcb91e0cf1355d51365db84f48b21585ad8d
20 20 manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b
... ...
... ... @@ -130,6 +130,8 @@ options:
130 130 - qdf
131 131 - raw-stream-data
132 132 - recompress-flate
  133 + - remove-info
  134 + - remove-metadata
133 135 - remove-page-labels
134 136 - replace-input
135 137 - report-memory-usage
... ... @@ -440,6 +442,8 @@ json:
440 442 - Pages.file:
441 443 Pages.password:
442 444 range:
  445 + remove-info:
  446 + remove-metadata:
443 447 remove-page-labels:
444 448 report-memory-usage:
445 449 rotate:
... ...
libqpdf/QPDF.cc
... ... @@ -832,10 +832,6 @@ std::vector&lt;QPDF::Xref_table::Subsection&gt;
832 832 QPDF::Xref_table::bad_subsections(std::string& line, qpdf_offset_t start)
833 833 {
834 834 std::vector<QPDF::Xref_table::Subsection> result;
835   - qpdf_offset_t f1 = 0;
836   - int f2 = 0;
837   - char type = '\0';
838   -
839 835 file->seek(start, SEEK_SET);
840 836  
841 837 while (true) {
... ... @@ -844,7 +840,7 @@ QPDF::Xref_table::bad_subsections(std::string&amp; line, qpdf_offset_t start)
844 840 auto [obj, num, offset] = result.emplace_back(subsection(line));
845 841 file->seek(offset, SEEK_SET);
846 842 for (qpdf_offset_t i = obj; i - num < obj; ++i) {
847   - if (!read_entry(f1, f2, type)) {
  843 + if (!std::get<0>(read_entry())) {
848 844 QTC::TC("qpdf", "QPDF invalid xref entry");
849 845 throw damaged_table("invalid xref entry (obj=" + std::to_string(i) + ")");
850 846 }
... ... @@ -890,9 +886,13 @@ QPDF::Xref_table::subsections(std::string&amp; line)
890 886 }
891 887 }
892 888  
893   -bool
894   -QPDF::Xref_table::read_bad_entry(qpdf_offset_t& f1, int& f2, char& type)
  889 +// Returns (success, f1, f2, type).
  890 +std::tuple<bool, qpdf_offset_t, int, char>
  891 +QPDF::Xref_table::read_bad_entry()
895 892 {
  893 + qpdf_offset_t f1{0};
  894 + int f2{0};
  895 + char type{'\0'};
896 896 // Reposition after initial read attempt and reread.
897 897 file->seek(file->getLastOffset(), SEEK_SET);
898 898 auto line = file->readLine(30);
... ... @@ -910,7 +910,7 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
910 910 }
911 911 // Require digit
912 912 if (!QUtil::is_digit(*p)) {
913   - return false;
  913 + return {false, 0, 0, '\0'};
914 914 }
915 915 // Gather digits
916 916 std::string f1_str;
... ... @@ -919,7 +919,7 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
919 919 }
920 920 // Require space
921 921 if (!QUtil::is_space(*p)) {
922   - return false;
  922 + return {false, 0, 0, '\0'};
923 923 }
924 924 if (QUtil::is_space(*(p + 1))) {
925 925 QTC::TC("qpdf", "QPDF ignore first extra space in xref entry");
... ... @@ -931,7 +931,7 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
931 931 }
932 932 // Require digit
933 933 if (!QUtil::is_digit(*p)) {
934   - return false;
  934 + return {false, 0, 0, '\0'};
935 935 }
936 936 // Gather digits
937 937 std::string f2_str;
... ... @@ -940,7 +940,7 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
940 940 }
941 941 // Require space
942 942 if (!QUtil::is_space(*p)) {
943   - return false;
  943 + return {false, 0, 0, '\0'};
944 944 }
945 945 if (QUtil::is_space(*(p + 1))) {
946 946 QTC::TC("qpdf", "QPDF ignore second extra space in xref entry");
... ... @@ -953,7 +953,7 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
953 953 if ((*p == 'f') || (*p == 'n')) {
954 954 type = *p;
955 955 } else {
956   - return false;
  956 + return {false, 0, 0, '\0'};
957 957 }
958 958 if ((f1_str.length() != 10) || (f2_str.length() != 5)) {
959 959 QTC::TC("qpdf", "QPDF ignore length error xref entry");
... ... @@ -967,18 +967,23 @@ QPDF::Xref_table::read_bad_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
967 967 f1 = QUtil::string_to_ll(f1_str.c_str());
968 968 f2 = QUtil::string_to_int(f2_str.c_str());
969 969  
970   - return true;
  970 + return {true, f1, f2, type};
971 971 }
972 972  
973 973 // Optimistically read and parse xref entry. If entry is bad, call read_bad_xrefEntry and return
974   -// result.
975   -bool
976   -QPDF::Xref_table::read_entry(qpdf_offset_t& f1, int& f2, char& type)
  974 +// result. Returns (success, f1, f2, type).
  975 +std::tuple<bool, qpdf_offset_t, int, char>
  976 +QPDF::Xref_table::read_entry()
977 977 {
  978 + qpdf_offset_t f1{0};
  979 + int f2{0};
  980 + char type{'\0'};
978 981 std::array<char, 21> line;
  982 + f1 = 0;
  983 + f2 = 0;
979 984 if (file->read(line.data(), 20) != 20) {
980 985 // C++20: [[unlikely]]
981   - return false;
  986 + return {false, 0, 0, '\0'};
982 987 }
983 988 line[20] = '\0';
984 989 char const* p = line.data();
... ... @@ -1002,7 +1007,7 @@ QPDF::Xref_table::read_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
1002 1007 if (!QUtil::is_space(*p++)) {
1003 1008 // Entry doesn't start with space or digit.
1004 1009 // C++20: [[unlikely]]
1005   - return false;
  1010 + return {false, 0, 0, '\0'};
1006 1011 }
1007 1012 // Gather digits. NB No risk of overflow as 99'999 < max int.
1008 1013 while (*p == '0') {
... ... @@ -1019,10 +1024,10 @@ QPDF::Xref_table::read_entry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
1019 1024 // No test for valid line[19].
1020 1025 if (*(++p) && *(++p) && (*p == '\n' || *p == '\r') && f1_len == 10 && f2_len == 5) {
1021 1026 // C++20: [[likely]]
1022   - return true;
  1027 + return {true, f1, f2, type};
1023 1028 }
1024 1029 }
1025   - return read_bad_entry(f1, f2, type);
  1030 + return read_bad_entry();
1026 1031 }
1027 1032  
1028 1033 // Read a single cross-reference table section and associated trailer.
... ... @@ -1052,7 +1057,10 @@ QPDF::Xref_table::process_section(qpdf_offset_t xref_offset)
1052 1057 QTC::TC("qpdf", "QPDF trailer size not integer");
1053 1058 throw qpdf.damagedPDF("trailer", "/Size key in trailer dictionary is not an integer");
1054 1059 }
1055   -
  1060 + if (sz >= static_cast<unsigned int>(max_id_)) {
  1061 + QTC::TC("qpdf", "QPDF trailer size impossibly large");
  1062 + throw qpdf.damagedPDF("trailer", "/Size key in trailer dictionary is impossibly large");
  1063 + }
1056 1064 table.resize(sz);
1057 1065 }
1058 1066  
... ... @@ -1064,10 +1072,8 @@ QPDF::Xref_table::process_section(qpdf_offset_t xref_offset)
1064 1072 first_item_offset_ = file->tell();
1065 1073 }
1066 1074 // For xref_table, these will always be small enough to be ints
1067   - qpdf_offset_t f1 = 0;
1068   - int f2 = 0;
1069   - char type = '\0';
1070   - if (!read_entry(f1, f2, type)) {
  1075 + auto [success, f1, f2, type] = read_entry();
  1076 + if (!success) {
1071 1077 throw damaged_table("invalid xref entry (obj=" + std::to_string(i) + ")");
1072 1078 }
1073 1079 if (type == 'f') {
... ... @@ -1585,8 +1591,7 @@ QPDF::Xref_table::read_trailer()
1585 1591 {
1586 1592 qpdf_offset_t offset = file->tell();
1587 1593 bool empty = false;
1588   - auto object =
1589   - QPDFParser(*file, "trailer", tokenizer, nullptr, &qpdf, true).parse(empty, false);
  1594 + auto object = QPDFParser(*file, "trailer", tokenizer, nullptr, &qpdf, true).parse(empty, false);
1590 1595 if (empty) {
1591 1596 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1592 1597 // actual PDF files and Adobe Reader appears to ignore them.
... ...
libqpdf/QPDFJob.cc
... ... @@ -471,6 +471,21 @@ QPDFJob::createQPDF()
471 471 }
472 472 handleUnderOverlay(pdf);
473 473 handleTransformations(pdf);
  474 + if (m->remove_info) {
  475 + auto trailer = pdf.getTrailer();
  476 + auto mod_date = trailer.getKey("/Info").getKeyIfDict("/ModDate");
  477 + if (mod_date.isNull()) {
  478 + trailer.removeKey("/Info");
  479 + } else {
  480 + auto info = trailer.replaceKeyAndGetNew(
  481 + "/Info", pdf.makeIndirectObject(QPDFObjectHandle::newDictionary()));
  482 + info.replaceKey("/ModDate", mod_date);
  483 + }
  484 + pdf.getRoot().removeKey("/Metadata");
  485 + }
  486 + if (m->remove_metadata) {
  487 + pdf.getRoot().removeKey("/Metadata");
  488 + }
474 489  
475 490 for (auto& foreign: page_heap) {
476 491 if (foreign->anyWarnings()) {
... ...
libqpdf/QPDFJob_config.cc
... ... @@ -511,6 +511,20 @@ QPDFJob::Config::removeAttachment(std::string const&amp; parameter)
511 511 }
512 512  
513 513 QPDFJob::Config*
  514 +QPDFJob::Config::removeInfo()
  515 +{
  516 + o.m->remove_info = true;
  517 + return this;
  518 +}
  519 +
  520 +QPDFJob::Config*
  521 +QPDFJob::Config::removeMetadata()
  522 +{
  523 + o.m->remove_metadata = true;
  524 + return this;
  525 +}
  526 +
  527 +QPDFJob::Config*
514 528 QPDFJob::Config::removePageLabels()
515 529 {
516 530 o.m->remove_page_labels = true;
... ...
libqpdf/QPDFParser.cc
... ... @@ -469,13 +469,14 @@ QPDFParser::fixMissingKeys()
469 469 bool
470 470 QPDFParser::tooManyBadTokens()
471 471 {
472   - if (good_count <= 4) {
473   - if (++bad_count > 5) {
474   - warn("too many errors; giving up on reading object");
475   - return true;
476   - }
477   - } else {
  472 + if (--max_bad_count > 0 && good_count > 4) {
  473 + good_count = 0;
478 474 bad_count = 1;
  475 + return false;
  476 + }
  477 + if (++bad_count > 5) {
  478 + warn("too many errors; giving up on reading object");
  479 + return true;
479 480 }
480 481 good_count = 0;
481 482 return false;
... ...
libqpdf/QPDFTokenizer.cc
... ... @@ -47,7 +47,7 @@ QPDFWordTokenFinder::check()
47 47 // Find a word token matching the given string, preceded by a delimiter, and followed by a
48 48 // delimiter or EOF.
49 49 QPDFTokenizer tokenizer;
50   - QPDFTokenizer::Token t = tokenizer.readToken(is, "finder", true);
  50 + QPDFTokenizer::Token t = tokenizer.readToken(is, "finder", true, str.size() + 2);
51 51 qpdf_offset_t pos = is.tell();
52 52 if (!(t == QPDFTokenizer::Token(QPDFTokenizer::tt_word, str))) {
53 53 QTC::TC("qpdf", "QPDFTokenizer finder found wrong word");
... ...
libqpdf/qpdf/QPDFParser.hh
... ... @@ -83,9 +83,11 @@ class QPDFParser
83 83 std::vector<StackFrame> stack;
84 84 StackFrame* frame;
85 85 // Number of recent bad tokens.
86   - int bad_count = 0;
  86 + int bad_count{0};
  87 + // Number of bad tokens (remaining) before giving up.
  88 + int max_bad_count{15};
87 89 // Number of good tokens since last bad token. Irrelevant if bad_count == 0.
88   - int good_count = 0;
  90 + int good_count{0};
89 91 // Start offset including any leading whitespace.
90 92 qpdf_offset_t start;
91 93 // Number of successive integer tokens.
... ...
libqpdf/qpdf/QPDF_private.hh
... ... @@ -292,8 +292,8 @@ class QPDF::Xref_table
292 292 std::vector<Subsection> subsections(std::string& line);
293 293 std::vector<Subsection> bad_subsections(std::string& line, qpdf_offset_t offset);
294 294 Subsection subsection(std::string const& line);
295   - bool read_entry(qpdf_offset_t& f1, int& f2, char& type);
296   - bool read_bad_entry(qpdf_offset_t& f1, int& f2, char& type);
  295 + std::tuple<bool, qpdf_offset_t, int, char> read_entry();
  296 + std::tuple<bool, qpdf_offset_t, int, char> read_bad_entry();
297 297  
298 298 // Methods to parse streams
299 299 qpdf_offset_t read_stream(qpdf_offset_t offset);
... ...
libqpdf/qpdf/auto_job_help.hh
... ... @@ -414,6 +414,13 @@ Don&#39;t optimize images whose area in pixels is below the specified value.
414 414 )");
415 415 ap.addOptionHelp("--keep-inline-images", "modification", "exclude inline images from optimization", R"(Prevent inline images from being considered by --optimize-images.
416 416 )");
  417 +ap.addOptionHelp("--remove-info", "modification", "remove file information", R"(Exclude file information (except modification date) from the output file.
  418 +)");
  419 +ap.addOptionHelp("--remove-metadata", "modification", "remove metadata", R"(Exclude metadata from the output file.
  420 +)");
  421 +}
  422 +static void add_help_5(QPDFArgParser& ap)
  423 +{
417 424 ap.addOptionHelp("--remove-page-labels", "modification", "remove explicit page numbers", R"(Exclude page labels (explicit page numbers) from the output file.
418 425 )");
419 426 ap.addOptionHelp("--set-page-labels", "modification", "number pages for the entire document", R"(--set-page-labels label-spec ... --
... ... @@ -460,9 +467,6 @@ iv, then the remaining pages with Arabic numerals starting with
460 467 1 and continuing sequentially until the end of the document. For
461 468 additional examples, please consult the manual.
462 469 )");
463   -}
464   -static void add_help_5(QPDFArgParser& ap)
465   -{
466 470 ap.addHelpTopic("encryption", "create encrypted files", R"(Create encrypted files. Usage:
467 471  
468 472 --encrypt \
... ... @@ -641,6 +645,9 @@ ap.addOptionHelp(&quot;--force-R5&quot;, &quot;encryption&quot;, &quot;use unsupported R=5 encryption&quot;, R
641 645 algorithm that existed only in Acrobat version IX. This option
642 646 should not be used except for compatibility testing.
643 647 )");
  648 +}
  649 +static void add_help_6(QPDFArgParser& ap)
  650 +{
644 651 ap.addHelpTopic("page-selection", "select pages from one or more files", R"(Use the --pages option to select pages from multiple files. Usage:
645 652  
646 653 qpdf in.pdf --pages --file=input-file \
... ... @@ -725,9 +732,6 @@ appearance: first underlays, then the original page, then overlays.
725 732  
726 733 Run qpdf --help=page-ranges for help with page ranges.
727 734 )");
728   -}
729   -static void add_help_6(QPDFArgParser& ap)
730   -{
731 735 ap.addOptionHelp("--to", "overlay-underlay", "destination pages for underlay/overlay", R"(--to=page-range
732 736  
733 737 Specify the range of pages in the primary output to apply
... ... @@ -829,6 +833,9 @@ its terminating &quot;--&quot;.
829 833 To copy attachments from a password-protected file, use
830 834 the --password option after the file name.
831 835 )");
  836 +}
  837 +static void add_help_7(QPDFArgParser& ap)
  838 +{
832 839 ap.addOptionHelp("--prefix", "copy-attachments", "key prefix for copying attachments", R"(--prefix=prefix
833 840  
834 841 Prepend a prefix to each key; may be needed if there are
... ... @@ -839,9 +846,6 @@ ap.addHelpTopic(&quot;inspection&quot;, &quot;inspect PDF files&quot;, R&quot;(These options provide tool
839 846 the options in this section are specified, no output file may be
840 847 given.
841 848 )");
842   -}
843   -static void add_help_7(QPDFArgParser& ap)
844   -{
845 849 ap.addOptionHelp("--is-encrypted", "inspection", "silently test whether a file is encrypted", R"(Silently exit with a code indicating the file's encryption status:
846 850  
847 851 0: the file is encrypted
... ... @@ -919,6 +923,9 @@ output as binary data. Get the key with --list-attachments.
919 923 ap.addHelpTopic("json", "JSON output for PDF information", R"(Show information about the PDF file in JSON format. Please see the
920 924 JSON chapter in the qpdf manual for details.
921 925 )");
  926 +}
  927 +static void add_help_8(QPDFArgParser& ap)
  928 +{
922 929 ap.addOptionHelp("--json", "json", "show file in JSON format", R"(--json[=version]
923 930  
924 931 Generate a JSON representation of the file. This is described in
... ... @@ -932,9 +939,6 @@ Describe the format of the JSON output by writing to standard
932 939 output a JSON object with the same keys and with values
933 940 containing descriptive text.
934 941 )");
935   -}
936   -static void add_help_8(QPDFArgParser& ap)
937   -{
938 942 ap.addOptionHelp("--json-key", "json", "limit which keys are in JSON output", R"(--json-key=key
939 943  
940 944 This option is repeatable. If given, only the specified
... ...
libqpdf/qpdf/auto_job_init.hh
... ... @@ -68,6 +68,8 @@ this-&gt;ap.addBare(&quot;progress&quot;, [this](){c_main-&gt;progress();});
68 68 this->ap.addBare("qdf", [this](){c_main->qdf();});
69 69 this->ap.addBare("raw-stream-data", [this](){c_main->rawStreamData();});
70 70 this->ap.addBare("recompress-flate", [this](){c_main->recompressFlate();});
  71 +this->ap.addBare("remove-info", [this](){c_main->removeInfo();});
  72 +this->ap.addBare("remove-metadata", [this](){c_main->removeMetadata();});
71 73 this->ap.addBare("remove-page-labels", [this](){c_main->removePageLabels();});
72 74 this->ap.addBare("replace-input", b(&ArgParser::argReplaceInput));
73 75 this->ap.addBare("report-memory-usage", [this](){c_main->reportMemoryUsage();});
... ...
libqpdf/qpdf/auto_job_json_init.hh
... ... @@ -412,6 +412,12 @@ addParameter([this](std::string const&amp; p) { c_pages-&gt;range(p); });
412 412 popHandler(); // key: range
413 413 popHandler(); // array: .pages[]
414 414 popHandler(); // key: pages
  415 +pushKey("removeInfo");
  416 +addBare([this]() { c_main->removeInfo(); });
  417 +popHandler(); // key: removeInfo
  418 +pushKey("removeMetadata");
  419 +addBare([this]() { c_main->removeMetadata(); });
  420 +popHandler(); // key: removeMetadata
415 421 pushKey("removePageLabels");
416 422 addBare([this]() { c_main->removePageLabels(); });
417 423 popHandler(); // key: removePageLabels
... ...
libqpdf/qpdf/auto_job_schema.hh
... ... @@ -145,6 +145,8 @@ static constexpr char const* JOB_SCHEMA_DATA = R&quot;({
145 145 "range": "page range"
146 146 }
147 147 ],
  148 + "removeInfo": "remove file information",
  149 + "removeMetadata": "remove metadata",
148 150 "removePageLabels": "remove explicit page numbers",
149 151 "reportMemoryUsage": "best effort report of memory usage",
150 152 "rotate": "rotate pages",
... ...
manual/cli.rst
... ... @@ -1773,6 +1773,27 @@ Related Options
1773 1773 Prevent inline images from being included in image optimization
1774 1774 done by :qpdf:ref:`--optimize-images`.
1775 1775  
  1776 +.. qpdf:option:: --remove-info
  1777 +
  1778 + .. help: remove file information
  1779 +
  1780 + Exclude file information (except modification date) from the output file.
  1781 +
  1782 + Exclude file information (except modification date) from the output file by
  1783 + omitting all entries (except ``/ModDate``) from the ``/Info`` dictionary in
  1784 + the document trailer.
  1785 + See also :qpdf:ref:`--remove-metadata`.
  1786 +
  1787 +.. qpdf:option:: --remove-metadata
  1788 +
  1789 + .. help: remove metadata
  1790 +
  1791 + Exclude metadata from the output file.
  1792 +
  1793 + Exclude metadata from the output file by omitting the ``/Metadata``
  1794 + dictionary in the document catalog.
  1795 + See also :qpdf:ref:`--remove-info`.
  1796 +
1776 1797 .. qpdf:option:: --remove-page-labels
1777 1798  
1778 1799 .. help: remove explicit page numbers
... ...
manual/qpdf.1
... ... @@ -530,6 +530,12 @@ Don&#39;t optimize images whose area in pixels is below the specified value.
530 530 .B --keep-inline-images \-\- exclude inline images from optimization
531 531 Prevent inline images from being considered by --optimize-images.
532 532 .TP
  533 +.B --remove-info \-\- remove file information
  534 +Exclude file information (except modification date) from the output file.
  535 +.TP
  536 +.B --remove-metadata \-\- remove metadata
  537 +Exclude metadata from the output file.
  538 +.TP
533 539 .B --remove-page-labels \-\- remove explicit page numbers
534 540 Exclude page labels (explicit page numbers) from the output file.
535 541 .TP
... ...
qpdf/qpdf.testcov
... ... @@ -55,6 +55,7 @@ QPDF invalid xref entry 0
55 55 QPDF missing trailer 0
56 56 QPDF trailer lacks size 0
57 57 QPDF trailer size not integer 0
  58 +QPDF trailer size impossibly large 0
58 59 QPDF trailer prev not integer 0
59 60 QPDFParser bad brace 0
60 61 QPDFParser bad brace in parseRemainder 0
... ...
qpdf/qtest/merge-and-split.test
... ... @@ -14,7 +14,7 @@ cleanup();
14 14  
15 15 my $td = new TestDriver('merge-and-split');
16 16  
17   -my $n_tests = 28;
  17 +my $n_tests = 34;
18 18  
19 19 # Select pages from the same file multiple times including selecting
20 20 # twice from an encrypted file and specifying the password only the
... ... @@ -103,6 +103,39 @@ $td-&gt;runtest(&quot;check output&quot;,
103 103 {$td->COMMAND => "qpdf-test-compare a.pdf remove-labels.pdf"},
104 104 {$td->FILE => "remove-labels.pdf", $td->EXIT_STATUS => 0});
105 105  
  106 +$td->runtest("remove metadata",
  107 + {$td->COMMAND =>
  108 + "qpdf metadata-crypt-filter.pdf a.pdf" .
  109 + " --remove-metadata" .
  110 + " --decrypt" .
  111 + " --static-id"},
  112 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  113 +$td->runtest("check output",
  114 + {$td->FILE => "a.pdf"},
  115 + {$td->FILE => "remove-metadata.pdf"});
  116 +
  117 +$td->runtest("remove info (with moddate)",
  118 + {$td->COMMAND =>
  119 + "qpdf remove-metadata.pdf a.pdf" .
  120 + " --remove-info" .
  121 + " --decrypt" .
  122 + " --static-id"},
  123 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  124 +$td->runtest("check output",
  125 + {$td->FILE => "a.pdf"},
  126 + {$td->FILE => "remove-info.pdf"});
  127 +
  128 +$td->runtest("remove info (without moddate)",
  129 + {$td->COMMAND =>
  130 + "qpdf remove-metadata-no-moddate.pdf a.pdf" .
  131 + " --remove-info" .
  132 + " --decrypt" .
  133 + " --static-id"},
  134 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  135 +$td->runtest("check output",
  136 + {$td->FILE => "a.pdf"},
  137 + {$td->FILE => "remove-info-no-moddate.pdf"});
  138 +
106 139 $td->runtest("split with shared resources",
107 140 {$td->COMMAND =>
108 141 "qpdf --qdf --static-id" .
... ...
qpdf/qtest/qpdf/issue-fuzz.out 0 → 100644
  1 +WARNING: issue-fuzz.pdf: can't find PDF header
  2 +WARNING: issue-fuzz.pdf (xref table, offset 19): accepting invalid xref table entry
  3 +WARNING: issue-fuzz.pdf (trailer, offset 36): unknown token while reading object; treating as string
  4 +WARNING: issue-fuzz.pdf (trailer, offset 53): unexpected >
  5 +WARNING: issue-fuzz.pdf (trailer, offset 54): unknown token while reading object; treating as string
  6 +WARNING: issue-fuzz.pdf (trailer, offset 58): unknown token while reading object; treating as string
  7 +WARNING: issue-fuzz.pdf (trailer, offset 72): unknown token while reading object; treating as string
  8 +WARNING: issue-fuzz.pdf (trailer, offset 36): dictionary ended prematurely; using null as value for last key
  9 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake1
  10 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake2
  11 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake3
  12 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake4
  13 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake5
  14 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake6
  15 +WARNING: issue-fuzz.pdf (trailer, offset 36): expected dictionary key but found non-name object; inserting key /QPDFFake7
  16 +WARNING: issue-fuzz.pdf: file is damaged
  17 +WARNING: issue-fuzz.pdf (trailer, offset 32): /Size key in trailer dictionary is impossibly large
  18 +WARNING: issue-fuzz.pdf: Attempting to reconstruct cross-reference table
  19 +qpdf: issue-fuzz.pdf: unable to find /Root dictionary
... ...
qpdf/qtest/qpdf/issue-fuzz.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/remove-info-no-moddate.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/remove-info.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/remove-metadata-no-moddate.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/remove-metadata.pdf 0 → 100644
No preview for this file type
qpdf/qtest/specific-bugs.test
... ... @@ -38,6 +38,7 @@ my @bug_tests = (
38 38 ["263", "empty xref stream", 2],
39 39 ["335a", "ozz-fuzz-12152", 2],
40 40 ["335b", "ozz-fuzz-14845", 2],
  41 + ["fuzz", "impossibly large trailer /Size"],
41 42 # ["fuzz-16214", "stream in object stream", 3, "--preserve-unreferenced"],
42 43 # When adding to this list, consider adding to CORPUS_FROM_TEST in
43 44 # fuzz/CMakeLists.txt and updating the count in
... ...