Commit f049a77c5962a0e41723bc83900656ece821d916

Authored by Jay Berkenbilt
1 parent 04fc7c4b

Add additional information when listing attachments

ChangeLog
1 1 2022-05-30 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Include additional information in --list-attachments --verbose
  4 + and in --json --json-key=attachments.
  5 +
3 6 * Add QUtil::qpdf_time_to_iso8601 and QUtil::pdf_time_to_iso8601
4 7 for converting PDF/qpdf timestamps to ISO-8601 date format.
5 8  
... ...
... ... @@ -72,9 +72,6 @@ Remaining work:
72 72  
73 73 * --encryption: show recovered user password when available
74 74  
75   - * --list-attachments: add information from --verbose. Add to a
76   - "details" subkey.
77   -
78 75 * Consider having --check, --show-encryption, etc., just select the
79 76 right keys when in json mode. I don't think I want check on by
80 77 default, so that might be different.
... ...
job.sums
... ... @@ -14,4 +14,4 @@ libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a947431
14 14 libqpdf/qpdf/auto_job_json_init.hh 5f6b53e3c81d4b54ce5c4cf9c3f52d0c02f987c53bf8841c0280367bad23e335
15 15 libqpdf/qpdf/auto_job_schema.hh 9d543cd4a43eafffc2c4b8a6fee29e399c271c52cb6f7d417ae5497b3c1127dc
16 16 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580
17   -manual/cli.rst e7c35f8183d015d7fe074e38baed4c89bad827fd9c23b4cafd73d562df82ab1b
  17 +manual/cli.rst 82ead389c03bbf5e0498bd0571a11dc06544d591f4e4454c00322e3473fc556d
... ...
libqpdf/QPDFJob.cc
... ... @@ -993,8 +993,16 @@ QPDFJob::doListAttachments(QPDF&amp; pdf)
993 993 }
994 994 cout << " all data streams:" << std::endl;
995 995 for (auto i2: efoh->getEmbeddedFileStreams().ditems()) {
  996 + auto efs = QPDFEFStreamObjectHelper(i2.second);
996 997 cout << " " << i2.first << " -> "
997   - << i2.second.getObjGen() << std::endl;
  998 + << efs.getObjectHandle().getObjGen() << std::endl;
  999 + cout << " creation date: " << efs.getCreationDate()
  1000 + << std::endl
  1001 + << " modification date: " << efs.getModDate()
  1002 + << std::endl
  1003 + << " mime type: " << efs.getSubtype() << std::endl
  1004 + << " checksum: "
  1005 + << QUtil::hex_encode(efs.getChecksum()) << std::endl;
998 1006 }
999 1007 });
1000 1008 }
... ... @@ -1445,6 +1453,22 @@ QPDFJob::doJSONEncrypt(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1445 1453 void
1446 1454 QPDFJob::doJSONAttachments(Pipeline* p, bool& first, QPDF& pdf)
1447 1455 {
  1456 + auto to_iso8601 = [](std::string const& d) {
  1457 + // Convert PDF date to iso8601 if not empty; if empty, return
  1458 + // empty.
  1459 + std::string iso8601;
  1460 + QUtil::pdf_time_to_iso8601(d, iso8601);
  1461 + return iso8601;
  1462 + };
  1463 +
  1464 + auto null_or_string = [](std::string const& s) {
  1465 + if (s.empty()) {
  1466 + return JSON::makeNull();
  1467 + } else {
  1468 + return JSON::makeString(s);
  1469 + }
  1470 + };
  1471 +
1448 1472 JSON j_attachments = JSON::makeDictionary();
1449 1473 QPDFEmbeddedFileDocumentHelper efdh(pdf);
1450 1474 for (auto const& iter: efdh.getEmbeddedFiles()) {
... ... @@ -1459,6 +1483,31 @@ QPDFJob::doJSONAttachments(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1459 1483 j_details.addDictionaryMember(
1460 1484 "preferredcontents",
1461 1485 JSON::makeString(fsoh->getEmbeddedFileStream().unparse()));
  1486 + j_details.addDictionaryMember(
  1487 + "description", null_or_string(fsoh->getDescription()));
  1488 + auto j_names =
  1489 + j_details.addDictionaryMember("names", JSON::makeDictionary());
  1490 + for (auto const& i2: fsoh->getFilenames()) {
  1491 + j_names.addDictionaryMember(i2.first, JSON::makeString(i2.second));
  1492 + }
  1493 + auto j_streams =
  1494 + j_details.addDictionaryMember("streams", JSON::makeDictionary());
  1495 + for (auto i2: fsoh->getEmbeddedFileStreams().ditems()) {
  1496 + auto efs = QPDFEFStreamObjectHelper(i2.second);
  1497 + auto j_stream =
  1498 + j_streams.addDictionaryMember(i2.first, JSON::makeDictionary());
  1499 + j_stream.addDictionaryMember(
  1500 + "creationdate",
  1501 + null_or_string(to_iso8601(efs.getCreationDate())));
  1502 + j_stream.addDictionaryMember(
  1503 + "modificationdate",
  1504 + null_or_string(to_iso8601(efs.getCreationDate())));
  1505 + j_stream.addDictionaryMember(
  1506 + "mimetype", null_or_string(efs.getSubtype()));
  1507 + j_stream.addDictionaryMember(
  1508 + "checksum",
  1509 + null_or_string(QUtil::hex_encode(efs.getChecksum())));
  1510 + }
1462 1511 }
1463 1512 JSON::writeDictionaryItem(p, first, "attachments", j_attachments, 0);
1464 1513 }
... ... @@ -1640,7 +1689,19 @@ QPDFJob::json_schema(int json_version, std::set&lt;std::string&gt;* keys)
1640 1689 "<attachment-key>": {
1641 1690 "filespec": "object containing the file spec",
1642 1691 "preferredcontents": "most preferred embedded file stream",
1643   - "preferredname": "most preferred file name"
  1692 + "preferredname": "most preferred file name",
  1693 + "description": "description of attachment",
  1694 + "names": {
  1695 + "<name-key>": "file name for key"
  1696 + },
  1697 + "streams": {
  1698 + "<stream-key>": {
  1699 + "creationdate": "ISO-8601 creation date or null",
  1700 + "modificationdate": "ISO-8601 modification date or null",
  1701 + "mimetype": "mime type or null",
  1702 + "checksum": "MD5 checksum or null"
  1703 + }
  1704 + }
1644 1705 }
1645 1706 })"));
1646 1707 }
... ...
manual/cli.rst
... ... @@ -3124,7 +3124,10 @@ Related Options
3124 3124 :qpdf:ref:`--verbose`, additional information, including preferred
3125 3125 file name, description, dates, and more are also displayed. The key
3126 3126 is usually but not always equal to the file name and is needed by
3127   - some of the other options. See also :ref:`attachments`.
  3127 + some of the other options. See also :ref:`attachments`. Note that
  3128 + this option displays dates in PDF timestamp syntax. When attachment
  3129 + information is included in json output (see :ref:`--json`), dates
  3130 + are shown in ISO-8601 format.
3128 3131  
3129 3132 .. qpdf:option:: --show-attachment=key
3130 3133  
... ...
manual/release-notes.rst
... ... @@ -96,6 +96,13 @@ For a detailed list of changes, please see the file
96 96 - See :ref:`breaking-crypto-api` for specific details, and see
97 97 :ref:`weak-crypto` for a general discussion.
98 98  
  99 + - CLI Enhancements
  100 +
  101 + - ``qpdf --list-attachments --verbose`` include some additional
  102 + information about attachments. Additional information about
  103 + attachments is also included in the ``attachments`` json key
  104 + with ``--json``.
  105 +
99 106 - Library Enhancements
100 107  
101 108 - New methods ``insertItemAndGet``, ``appendItemAndGet``,
... ... @@ -120,6 +127,9 @@ For a detailed list of changes, please see the file
120 127 - Add new ``Pipeline`` type ``Pl_String`` to append to a
121 128 ``std::string``.
122 129  
  130 + - Add methods to QUtil for converting PDF timestamps and QPDFTime
  131 + objects to ISO-8601 timestamps.
  132 +
123 133 - Enhance JSON class to better support incrementally reading and
124 134 writing large amounts of data without having to keep everything
125 135 in memory.
... ...
qpdf/qtest/attachments.test
... ... @@ -189,7 +189,8 @@ $td-&gt;runtest(&quot;add attachments: current date&quot;,
189 189 $td->NORMALIZE_NEWLINES);
190 190 $td->runtest("list attachments",
191 191 {$td->COMMAND =>
192   - "qpdf --password=u --list-attachments a.pdf --verbose"},
  192 + "qpdf --password=u --list-attachments a.pdf --verbose",
  193 + $td->FILTER => "perl filter-attachment-date.pl"},
193 194 {$td->FILE => "list-attachments-4.out", $td->EXIT_STATUS => 0},
194 195 $td->NORMALIZE_NEWLINES);
195 196 # The object to show here is the one in list-attachments-4.out
... ...
qpdf/qtest/json.test
... ... @@ -26,6 +26,7 @@ my @json_files = (
26 26 ['field-types', ['--show-encryption-key']],
27 27 ['image-streams', ['--decode-level=all']],
28 28 ['image-streams', ['--decode-level=specialized']],
  29 + ['attachment-fields', []],
29 30 ['page-labels-and-outlines', ['--json-key=objects']],
30 31 ['page-labels-and-outlines', ['--json-key=pages']],
31 32 ['page-labels-and-outlines', ['--json-key=pagelabels']],
... ...
qpdf/qtest/qpdf/attachment-fields.pdf 0 → 100644
  1 +%PDF-1.3
  2 +%¿÷¢þ
  3 +%QDF-1.0
  4 +
  5 +%% Original object ID: 1 0
  6 +1 0 obj
  7 +<<
  8 + /Names <<
  9 + /EmbeddedFiles 2 0 R
  10 + >>
  11 + /PageMode /UseAttachments
  12 + /Pages 3 0 R
  13 + /Type /Catalog
  14 +>>
  15 +endobj
  16 +
  17 +%% Original object ID: 2 0
  18 +2 0 obj
  19 +<<
  20 + /Names [
  21 + (a.txt)
  22 + 4 0 R
  23 + <feff03c0002e007400780074>
  24 + 5 0 R
  25 + ]
  26 +>>
  27 +endobj
  28 +
  29 +%% Original object ID: 3 0
  30 +3 0 obj
  31 +<<
  32 + /Count 1
  33 + /Kids [
  34 + 6 0 R
  35 + ]
  36 + /Type /Pages
  37 +>>
  38 +endobj
  39 +
  40 +%% Original object ID: 4 0
  41 +4 0 obj
  42 +<<
  43 + /EF <<
  44 + /F 7 0 R
  45 + /UF 7 0 R
  46 + >>
  47 + /F (a.txt)
  48 + /Type /Filespec
  49 + /UF (a.txt)
  50 +>>
  51 +endobj
  52 +
  53 +%% Original object ID: 5 0
  54 +5 0 obj
  55 +<<
  56 + /Desc (Two filenames)
  57 + /EF <<
  58 + /F 9 0 R
  59 + /UF 9 0 R
  60 + >>
  61 + /F (pi.txt)
  62 + /Type /Filespec
  63 + /UF <feff03c0002e007400780074>
  64 +>>
  65 +endobj
  66 +
  67 +%% Page 1
  68 +%% Original object ID: 6 0
  69 +6 0 obj
  70 +<<
  71 + /Contents 11 0 R
  72 + /MediaBox [
  73 + 0
  74 + 0
  75 + 612
  76 + 792
  77 + ]
  78 + /Parent 3 0 R
  79 + /Resources <<
  80 + /Font <<
  81 + /F1 13 0 R
  82 + >>
  83 + /ProcSet 14 0 R
  84 + >>
  85 + /Type /Page
  86 +>>
  87 +endobj
  88 +
  89 +%% Original object ID: 7 0
  90 +7 0 obj
  91 +<<
  92 + /Params <<
  93 + /Size 7
  94 + >>
  95 + /Type /EmbeddedFile
  96 + /Length 8 0 R
  97 +>>
  98 +stream
  99 +potato
  100 +endstream
  101 +endobj
  102 +
  103 +8 0 obj
  104 +7
  105 +endobj
  106 +
  107 +%% Original object ID: 8 0
  108 +9 0 obj
  109 +<<
  110 + /Params <<
  111 + /CheckSum <e561f9248d7563d15dd93457b02ebbb6>
  112 + /CreationDate (D:20220530094116-05'00')
  113 + /ModDate (D:20220530094116-05'00')
  114 + /Size 7
  115 + >>
  116 + /Subtype /text#2fplain
  117 + /Type /EmbeddedFile
  118 + /Length 10 0 R
  119 +>>
  120 +stream
  121 +potato
  122 +endstream
  123 +endobj
  124 +
  125 +10 0 obj
  126 +7
  127 +endobj
  128 +
  129 +%% Contents for page 1
  130 +%% Original object ID: 9 0
  131 +11 0 obj
  132 +<<
  133 + /Length 12 0 R
  134 +>>
  135 +stream
  136 +BT
  137 + /F1 24 Tf
  138 + 72 720 Td
  139 + (Potato) Tj
  140 +ET
  141 +endstream
  142 +endobj
  143 +
  144 +12 0 obj
  145 +44
  146 +endobj
  147 +
  148 +%% Original object ID: 10 0
  149 +13 0 obj
  150 +<<
  151 + /BaseFont /Helvetica
  152 + /Encoding /WinAnsiEncoding
  153 + /Subtype /Type1
  154 + /Type /Font
  155 +>>
  156 +endobj
  157 +
  158 +%% Original object ID: 11 0
  159 +14 0 obj
  160 +[
  161 + /PDF
  162 + /Text
  163 +]
  164 +endobj
  165 +
  166 +xref
  167 +0 15
  168 +0000000000 65535 f
  169 +0000000052 00000 n
  170 +0000000203 00000 n
  171 +0000000330 00000 n
  172 +0000000429 00000 n
  173 +0000000564 00000 n
  174 +0000000753 00000 n
  175 +0000000975 00000 n
  176 +0000001089 00000 n
  177 +0000001134 00000 n
  178 +0000001406 00000 n
  179 +0000001475 00000 n
  180 +0000001576 00000 n
  181 +0000001624 00000 n
  182 +0000001759 00000 n
  183 +trailer <<
  184 + /Root 1 0 R
  185 + /Size 15
  186 + /ID [<3d1a89e08f9d6b3e1f787d04fceed440><4e13de38230bb10a03e49285a742c392>]
  187 +>>
  188 +startxref
  189 +1795
  190 +%%EOF
... ...
qpdf/qtest/qpdf/filter-attachment-date.pl 0 → 100644
  1 +use warnings;
  2 +use strict;
  3 +
  4 +while (<>)
  5 +{
  6 + s/D:\d{14}\S+/<date>/;
  7 + print;
  8 +}
... ...
qpdf/qtest/qpdf/json-attachment-fields-v1.out 0 → 100644
  1 +{
  2 + "version": 1,
  3 + "parameters": {
  4 + "decodelevel": "generalized"
  5 + },
  6 + "pages": [
  7 + {
  8 + "contents": [
  9 + "11 0 R"
  10 + ],
  11 + "images": [],
  12 + "label": null,
  13 + "object": "6 0 R",
  14 + "outlines": [],
  15 + "pageposfrom1": 1
  16 + }
  17 + ],
  18 + "pagelabels": [],
  19 + "acroform": {
  20 + "fields": [],
  21 + "hasacroform": false,
  22 + "needappearances": false
  23 + },
  24 + "attachments": {
  25 + "a.txt": {
  26 + "description": null,
  27 + "filespec": "4 0 R",
  28 + "names": {
  29 + "/F": "a.txt",
  30 + "/UF": "a.txt"
  31 + },
  32 + "preferredcontents": "7 0 R",
  33 + "preferredname": "a.txt",
  34 + "streams": {
  35 + "/F": {
  36 + "checksum": null,
  37 + "creationdate": null,
  38 + "mimetype": null,
  39 + "modificationdate": null
  40 + },
  41 + "/UF": {
  42 + "checksum": null,
  43 + "creationdate": null,
  44 + "mimetype": null,
  45 + "modificationdate": null
  46 + }
  47 + }
  48 + },
  49 + "π.txt": {
  50 + "description": "Two filenames",
  51 + "filespec": "5 0 R",
  52 + "names": {
  53 + "/F": "pi.txt",
  54 + "/UF": "π.txt"
  55 + },
  56 + "preferredcontents": "9 0 R",
  57 + "preferredname": "π.txt",
  58 + "streams": {
  59 + "/F": {
  60 + "checksum": "e561f9248d7563d15dd93457b02ebbb6",
  61 + "creationdate": "2022-05-30T09:41:16-05:00",
  62 + "mimetype": "text/plain",
  63 + "modificationdate": "2022-05-30T09:41:16-05:00"
  64 + },
  65 + "/UF": {
  66 + "checksum": "e561f9248d7563d15dd93457b02ebbb6",
  67 + "creationdate": "2022-05-30T09:41:16-05:00",
  68 + "mimetype": "text/plain",
  69 + "modificationdate": "2022-05-30T09:41:16-05:00"
  70 + }
  71 + }
  72 + }
  73 + },
  74 + "encrypt": {
  75 + "capabilities": {
  76 + "accessibility": true,
  77 + "extract": true,
  78 + "moddifyannotations": true,
  79 + "modify": true,
  80 + "modifyassembly": true,
  81 + "modifyforms": true,
  82 + "modifyother": true,
  83 + "printhigh": true,
  84 + "printlow": true
  85 + },
  86 + "encrypted": false,
  87 + "ownerpasswordmatched": false,
  88 + "parameters": {
  89 + "P": 0,
  90 + "R": 0,
  91 + "V": 0,
  92 + "bits": 0,
  93 + "filemethod": "none",
  94 + "key": null,
  95 + "method": "none",
  96 + "streammethod": "none",
  97 + "stringmethod": "none"
  98 + },
  99 + "userpasswordmatched": false
  100 + },
  101 + "outlines": [],
  102 + "objects": {
  103 + "1 0 R": {
  104 + "/Names": {
  105 + "/EmbeddedFiles": "2 0 R"
  106 + },
  107 + "/PageMode": "/UseAttachments",
  108 + "/Pages": "3 0 R",
  109 + "/Type": "/Catalog"
  110 + },
  111 + "2 0 R": {
  112 + "/Names": [
  113 + "a.txt",
  114 + "4 0 R",
  115 + "π.txt",
  116 + "5 0 R"
  117 + ]
  118 + },
  119 + "3 0 R": {
  120 + "/Count": 1,
  121 + "/Kids": [
  122 + "6 0 R"
  123 + ],
  124 + "/Type": "/Pages"
  125 + },
  126 + "4 0 R": {
  127 + "/EF": {
  128 + "/F": "7 0 R",
  129 + "/UF": "7 0 R"
  130 + },
  131 + "/F": "a.txt",
  132 + "/Type": "/Filespec",
  133 + "/UF": "a.txt"
  134 + },
  135 + "5 0 R": {
  136 + "/Desc": "Two filenames",
  137 + "/EF": {
  138 + "/F": "9 0 R",
  139 + "/UF": "9 0 R"
  140 + },
  141 + "/F": "pi.txt",
  142 + "/Type": "/Filespec",
  143 + "/UF": "π.txt"
  144 + },
  145 + "6 0 R": {
  146 + "/Contents": "11 0 R",
  147 + "/MediaBox": [
  148 + 0,
  149 + 0,
  150 + 612,
  151 + 792
  152 + ],
  153 + "/Parent": "3 0 R",
  154 + "/Resources": {
  155 + "/Font": {
  156 + "/F1": "13 0 R"
  157 + },
  158 + "/ProcSet": "14 0 R"
  159 + },
  160 + "/Type": "/Page"
  161 + },
  162 + "7 0 R": {
  163 + "/Length": "8 0 R",
  164 + "/Params": {
  165 + "/Size": 7
  166 + },
  167 + "/Type": "/EmbeddedFile"
  168 + },
  169 + "8 0 R": 7,
  170 + "9 0 R": {
  171 + "/Length": "10 0 R",
  172 + "/Params": {
  173 + "/CheckSum": "åaù$“ucÑ]Ù4W°.»¶",
  174 + "/CreationDate": "D:20220530094116-05'00'",
  175 + "/ModDate": "D:20220530094116-05'00'",
  176 + "/Size": 7
  177 + },
  178 + "/Subtype": "/text#2fplain",
  179 + "/Type": "/EmbeddedFile"
  180 + },
  181 + "10 0 R": 7,
  182 + "11 0 R": {
  183 + "/Length": "12 0 R"
  184 + },
  185 + "12 0 R": 44,
  186 + "13 0 R": {
  187 + "/BaseFont": "/Helvetica",
  188 + "/Encoding": "/WinAnsiEncoding",
  189 + "/Subtype": "/Type1",
  190 + "/Type": "/Font"
  191 + },
  192 + "14 0 R": [
  193 + "/PDF",
  194 + "/Text"
  195 + ],
  196 + "trailer": {
  197 + "/ID": [
  198 + "=ˆ›à‘šk>˜x}\u0004üîÔ@",
  199 + "N\u0013Þ8#\u000b±\n\u0003ä™–§BÙ"
  200 + ],
  201 + "/Root": "1 0 R",
  202 + "/Size": 15
  203 + }
  204 + },
  205 + "objectinfo": {
  206 + "1 0 R": {
  207 + "stream": {
  208 + "filter": null,
  209 + "is": false,
  210 + "length": null
  211 + }
  212 + },
  213 + "2 0 R": {
  214 + "stream": {
  215 + "filter": null,
  216 + "is": false,
  217 + "length": null
  218 + }
  219 + },
  220 + "3 0 R": {
  221 + "stream": {
  222 + "filter": null,
  223 + "is": false,
  224 + "length": null
  225 + }
  226 + },
  227 + "4 0 R": {
  228 + "stream": {
  229 + "filter": null,
  230 + "is": false,
  231 + "length": null
  232 + }
  233 + },
  234 + "5 0 R": {
  235 + "stream": {
  236 + "filter": null,
  237 + "is": false,
  238 + "length": null
  239 + }
  240 + },
  241 + "6 0 R": {
  242 + "stream": {
  243 + "filter": null,
  244 + "is": false,
  245 + "length": null
  246 + }
  247 + },
  248 + "7 0 R": {
  249 + "stream": {
  250 + "filter": null,
  251 + "is": true,
  252 + "length": 7
  253 + }
  254 + },
  255 + "8 0 R": {
  256 + "stream": {
  257 + "filter": null,
  258 + "is": false,
  259 + "length": null
  260 + }
  261 + },
  262 + "9 0 R": {
  263 + "stream": {
  264 + "filter": null,
  265 + "is": true,
  266 + "length": 7
  267 + }
  268 + },
  269 + "10 0 R": {
  270 + "stream": {
  271 + "filter": null,
  272 + "is": false,
  273 + "length": null
  274 + }
  275 + },
  276 + "11 0 R": {
  277 + "stream": {
  278 + "filter": null,
  279 + "is": true,
  280 + "length": 44
  281 + }
  282 + },
  283 + "12 0 R": {
  284 + "stream": {
  285 + "filter": null,
  286 + "is": false,
  287 + "length": null
  288 + }
  289 + },
  290 + "13 0 R": {
  291 + "stream": {
  292 + "filter": null,
  293 + "is": false,
  294 + "length": null
  295 + }
  296 + },
  297 + "14 0 R": {
  298 + "stream": {
  299 + "filter": null,
  300 + "is": false,
  301 + "length": null
  302 + }
  303 + }
  304 + }
  305 +}
... ...
qpdf/qtest/qpdf/json-attachment-fields-v2.out 0 → 100644
  1 +{
  2 + "version": 2,
  3 + "parameters": {
  4 + "decodelevel": "generalized"
  5 + },
  6 + "pages": [
  7 + {
  8 + "contents": [
  9 + "11 0 R"
  10 + ],
  11 + "images": [],
  12 + "label": null,
  13 + "object": "6 0 R",
  14 + "outlines": [],
  15 + "pageposfrom1": 1
  16 + }
  17 + ],
  18 + "pagelabels": [],
  19 + "acroform": {
  20 + "fields": [],
  21 + "hasacroform": false,
  22 + "needappearances": false
  23 + },
  24 + "attachments": {
  25 + "a.txt": {
  26 + "description": null,
  27 + "filespec": "4 0 R",
  28 + "names": {
  29 + "/F": "a.txt",
  30 + "/UF": "a.txt"
  31 + },
  32 + "preferredcontents": "7 0 R",
  33 + "preferredname": "a.txt",
  34 + "streams": {
  35 + "/F": {
  36 + "checksum": null,
  37 + "creationdate": null,
  38 + "mimetype": null,
  39 + "modificationdate": null
  40 + },
  41 + "/UF": {
  42 + "checksum": null,
  43 + "creationdate": null,
  44 + "mimetype": null,
  45 + "modificationdate": null
  46 + }
  47 + }
  48 + },
  49 + "π.txt": {
  50 + "description": "Two filenames",
  51 + "filespec": "5 0 R",
  52 + "names": {
  53 + "/F": "pi.txt",
  54 + "/UF": "π.txt"
  55 + },
  56 + "preferredcontents": "9 0 R",
  57 + "preferredname": "π.txt",
  58 + "streams": {
  59 + "/F": {
  60 + "checksum": "e561f9248d7563d15dd93457b02ebbb6",
  61 + "creationdate": "2022-05-30T09:41:16-05:00",
  62 + "mimetype": "text/plain",
  63 + "modificationdate": "2022-05-30T09:41:16-05:00"
  64 + },
  65 + "/UF": {
  66 + "checksum": "e561f9248d7563d15dd93457b02ebbb6",
  67 + "creationdate": "2022-05-30T09:41:16-05:00",
  68 + "mimetype": "text/plain",
  69 + "modificationdate": "2022-05-30T09:41:16-05:00"
  70 + }
  71 + }
  72 + }
  73 + },
  74 + "encrypt": {
  75 + "capabilities": {
  76 + "accessibility": true,
  77 + "extract": true,
  78 + "modify": true,
  79 + "modifyannotations": true,
  80 + "modifyassembly": true,
  81 + "modifyforms": true,
  82 + "modifyother": true,
  83 + "printhigh": true,
  84 + "printlow": true
  85 + },
  86 + "encrypted": false,
  87 + "ownerpasswordmatched": false,
  88 + "parameters": {
  89 + "P": 0,
  90 + "R": 0,
  91 + "V": 0,
  92 + "bits": 0,
  93 + "filemethod": "none",
  94 + "key": null,
  95 + "method": "none",
  96 + "streammethod": "none",
  97 + "stringmethod": "none"
  98 + },
  99 + "userpasswordmatched": false
  100 + },
  101 + "outlines": [],
  102 + "objects": {
  103 + "obj:1 0 R": {
  104 + "value": {
  105 + "/Names": {
  106 + "/EmbeddedFiles": "2 0 R"
  107 + },
  108 + "/PageMode": "/UseAttachments",
  109 + "/Pages": "3 0 R",
  110 + "/Type": "/Catalog"
  111 + }
  112 + },
  113 + "obj:2 0 R": {
  114 + "value": {
  115 + "/Names": [
  116 + "u:a.txt",
  117 + "4 0 R",
  118 + "u:π.txt",
  119 + "5 0 R"
  120 + ]
  121 + }
  122 + },
  123 + "obj:3 0 R": {
  124 + "value": {
  125 + "/Count": 1,
  126 + "/Kids": [
  127 + "6 0 R"
  128 + ],
  129 + "/Type": "/Pages"
  130 + }
  131 + },
  132 + "obj:4 0 R": {
  133 + "value": {
  134 + "/EF": {
  135 + "/F": "7 0 R",
  136 + "/UF": "7 0 R"
  137 + },
  138 + "/F": "u:a.txt",
  139 + "/Type": "/Filespec",
  140 + "/UF": "u:a.txt"
  141 + }
  142 + },
  143 + "obj:5 0 R": {
  144 + "value": {
  145 + "/Desc": "u:Two filenames",
  146 + "/EF": {
  147 + "/F": "9 0 R",
  148 + "/UF": "9 0 R"
  149 + },
  150 + "/F": "u:pi.txt",
  151 + "/Type": "/Filespec",
  152 + "/UF": "u:π.txt"
  153 + }
  154 + },
  155 + "obj:6 0 R": {
  156 + "value": {
  157 + "/Contents": "11 0 R",
  158 + "/MediaBox": [
  159 + 0,
  160 + 0,
  161 + 612,
  162 + 792
  163 + ],
  164 + "/Parent": "3 0 R",
  165 + "/Resources": {
  166 + "/Font": {
  167 + "/F1": "13 0 R"
  168 + },
  169 + "/ProcSet": "14 0 R"
  170 + },
  171 + "/Type": "/Page"
  172 + }
  173 + },
  174 + "obj:7 0 R": {
  175 + "stream": {
  176 + "dict": {
  177 + "/Length": "8 0 R",
  178 + "/Params": {
  179 + "/Size": 7
  180 + },
  181 + "/Type": "/EmbeddedFile"
  182 + }
  183 + }
  184 + },
  185 + "obj:8 0 R": {
  186 + "value": 7
  187 + },
  188 + "obj:9 0 R": {
  189 + "stream": {
  190 + "dict": {
  191 + "/Length": "10 0 R",
  192 + "/Params": {
  193 + "/CheckSum": "b:e561f9248d7563d15dd93457b02ebbb6",
  194 + "/CreationDate": "u:D:20220530094116-05'00'",
  195 + "/ModDate": "u:D:20220530094116-05'00'",
  196 + "/Size": 7
  197 + },
  198 + "/Subtype": "/text/plain",
  199 + "/Type": "/EmbeddedFile"
  200 + }
  201 + }
  202 + },
  203 + "obj:10 0 R": {
  204 + "value": 7
  205 + },
  206 + "obj:11 0 R": {
  207 + "stream": {
  208 + "dict": {
  209 + "/Length": "12 0 R"
  210 + }
  211 + }
  212 + },
  213 + "obj:12 0 R": {
  214 + "value": 44
  215 + },
  216 + "obj:13 0 R": {
  217 + "value": {
  218 + "/BaseFont": "/Helvetica",
  219 + "/Encoding": "/WinAnsiEncoding",
  220 + "/Subtype": "/Type1",
  221 + "/Type": "/Font"
  222 + }
  223 + },
  224 + "obj:14 0 R": {
  225 + "value": [
  226 + "/PDF",
  227 + "/Text"
  228 + ]
  229 + },
  230 + "trailer": {
  231 + "value": {
  232 + "/ID": [
  233 + "b:3d1a89e08f9d6b3e1f787d04fceed440",
  234 + "b:4e13de38230bb10a03e49285a742c392"
  235 + ],
  236 + "/Root": "1 0 R",
  237 + "/Size": 15
  238 + }
  239 + }
  240 + }
  241 +}
... ...
qpdf/qtest/qpdf/list-attachments-1.out
... ... @@ -5,7 +5,15 @@ auto-1 -&gt; 8,0
5 5 /UF -> auto-1
6 6 all data streams:
7 7 /F -> 8,0
  8 + creation date: D:20210210091359-05'00'
  9 + modification date: D:20210210141359Z
  10 + mime type: text/plain
  11 + checksum: a857d18d3fc23ad412122ef040733331
8 12 /UF -> 8,0
  13 + creation date: D:20210210091359-05'00'
  14 + modification date: D:20210210141359Z
  15 + mime type: text/plain
  16 + checksum: a857d18d3fc23ad412122ef040733331
9 17 auto-3 -> 10,0
10 18 description: two words
11 19 preferred name: auto-Three.txt
... ... @@ -14,7 +22,15 @@ auto-3 -&gt; 10,0
14 22 /UF -> auto-Three.txt
15 23 all data streams:
16 24 /F -> 10,0
  25 + creation date: D:20210210091359-05'00'
  26 + modification date: D:20210210141359Z
  27 + mime type:
  28 + checksum: d6c7ac7cf295ae133fea186cfd068dab
17 29 /UF -> 10,0
  30 + creation date: D:20210210091359-05'00'
  31 + modification date: D:20210210141359Z
  32 + mime type:
  33 + checksum: d6c7ac7cf295ae133fea186cfd068dab
18 34 auto-Two -> 12,0
19 35 preferred name: auto-2
20 36 all names:
... ... @@ -22,4 +38,12 @@ auto-Two -&gt; 12,0
22 38 /UF -> auto-2
23 39 all data streams:
24 40 /F -> 12,0
  41 + creation date: D:20210210091359-05'00'
  42 + modification date: D:20210210141359Z
  43 + mime type:
  44 + checksum: 9f991a5669c47a94f9350f53e3953e57
25 45 /UF -> 12,0
  46 + creation date: D:20210210091359-05'00'
  47 + modification date: D:20210210141359Z
  48 + mime type:
  49 + checksum: 9f991a5669c47a94f9350f53e3953e57
... ...
qpdf/qtest/qpdf/list-attachments-2.out
... ... @@ -5,7 +5,15 @@
5 5 /UF -> auto-1
6 6 all data streams:
7 7 /F -> 11,0
  8 + creation date: D:20210210091359-05'00'
  9 + modification date: D:20210210141359Z
  10 + mime type: text/plain
  11 + checksum: a857d18d3fc23ad412122ef040733331
8 12 /UF -> 11,0
  13 + creation date: D:20210210091359-05'00'
  14 + modification date: D:20210210141359Z
  15 + mime type: text/plain
  16 + checksum: a857d18d3fc23ad412122ef040733331
9 17 1-auto-3 -> 13,0
10 18 description: two words
11 19 preferred name: auto-Three.txt
... ... @@ -14,7 +22,15 @@
14 22 /UF -> auto-Three.txt
15 23 all data streams:
16 24 /F -> 13,0
  25 + creation date: D:20210210091359-05'00'
  26 + modification date: D:20210210141359Z
  27 + mime type:
  28 + checksum: d6c7ac7cf295ae133fea186cfd068dab
17 29 /UF -> 13,0
  30 + creation date: D:20210210091359-05'00'
  31 + modification date: D:20210210141359Z
  32 + mime type:
  33 + checksum: d6c7ac7cf295ae133fea186cfd068dab
18 34 1-auto-Two -> 15,0
19 35 preferred name: auto-2
20 36 all names:
... ... @@ -22,7 +38,15 @@
22 38 /UF -> auto-2
23 39 all data streams:
24 40 /F -> 15,0
  41 + creation date: D:20210210091359-05'00'
  42 + modification date: D:20210210141359Z
  43 + mime type:
  44 + checksum: 9f991a5669c47a94f9350f53e3953e57
25 45 /UF -> 15,0
  46 + creation date: D:20210210091359-05'00'
  47 + modification date: D:20210210141359Z
  48 + mime type:
  49 + checksum: 9f991a5669c47a94f9350f53e3953e57
26 50 auto-1 -> 17,0
27 51 preferred name: auto-1
28 52 all names:
... ... @@ -30,7 +54,15 @@ auto-1 -&gt; 17,0
30 54 /UF -> auto-1
31 55 all data streams:
32 56 /F -> 17,0
  57 + creation date: D:20210210091359-05'00'
  58 + modification date: D:20210210141359Z
  59 + mime type: text/plain
  60 + checksum: a857d18d3fc23ad412122ef040733331
33 61 /UF -> 17,0
  62 + creation date: D:20210210091359-05'00'
  63 + modification date: D:20210210141359Z
  64 + mime type: text/plain
  65 + checksum: a857d18d3fc23ad412122ef040733331
34 66 auto-3 -> 19,0
35 67 description: two words
36 68 preferred name: auto-Three.txt
... ... @@ -39,7 +71,15 @@ auto-3 -&gt; 19,0
39 71 /UF -> auto-Three.txt
40 72 all data streams:
41 73 /F -> 19,0
  74 + creation date: D:20210210091359-05'00'
  75 + modification date: D:20210210141359Z
  76 + mime type:
  77 + checksum: d6c7ac7cf295ae133fea186cfd068dab
42 78 /UF -> 19,0
  79 + creation date: D:20210210091359-05'00'
  80 + modification date: D:20210210141359Z
  81 + mime type:
  82 + checksum: d6c7ac7cf295ae133fea186cfd068dab
43 83 auto-Two -> 21,0
44 84 preferred name: auto-2
45 85 all names:
... ... @@ -47,4 +87,12 @@ auto-Two -&gt; 21,0
47 87 /UF -> auto-2
48 88 all data streams:
49 89 /F -> 21,0
  90 + creation date: D:20210210091359-05'00'
  91 + modification date: D:20210210141359Z
  92 + mime type:
  93 + checksum: 9f991a5669c47a94f9350f53e3953e57
50 94 /UF -> 21,0
  95 + creation date: D:20210210091359-05'00'
  96 + modification date: D:20210210141359Z
  97 + mime type:
  98 + checksum: 9f991a5669c47a94f9350f53e3953e57
... ...
qpdf/qtest/qpdf/list-attachments-3.out
... ... @@ -5,7 +5,15 @@ auto-1 -&gt; 8,0
5 5 /UF -> auto-2
6 6 all data streams:
7 7 /F -> 8,0
  8 + creation date: D:20210210091359-05'00'
  9 + modification date: D:20210210141359Z
  10 + mime type:
  11 + checksum: 9f991a5669c47a94f9350f53e3953e57
8 12 /UF -> 8,0
  13 + creation date: D:20210210091359-05'00'
  14 + modification date: D:20210210141359Z
  15 + mime type:
  16 + checksum: 9f991a5669c47a94f9350f53e3953e57
9 17 auto-3 -> 10,0
10 18 description: two words
11 19 preferred name: auto-Three.txt
... ... @@ -14,7 +22,15 @@ auto-3 -&gt; 10,0
14 22 /UF -> auto-Three.txt
15 23 all data streams:
16 24 /F -> 10,0
  25 + creation date: D:20210210091359-05'00'
  26 + modification date: D:20210210141359Z
  27 + mime type:
  28 + checksum: d6c7ac7cf295ae133fea186cfd068dab
17 29 /UF -> 10,0
  30 + creation date: D:20210210091359-05'00'
  31 + modification date: D:20210210141359Z
  32 + mime type:
  33 + checksum: d6c7ac7cf295ae133fea186cfd068dab
18 34 auto-Two -> 12,0
19 35 preferred name: auto-2
20 36 all names:
... ... @@ -22,4 +38,12 @@ auto-Two -&gt; 12,0
22 38 /UF -> auto-2
23 39 all data streams:
24 40 /F -> 12,0
  41 + creation date: D:20210210091359-05'00'
  42 + modification date: D:20210210141359Z
  43 + mime type:
  44 + checksum: 9f991a5669c47a94f9350f53e3953e57
25 45 /UF -> 12,0
  46 + creation date: D:20210210091359-05'00'
  47 + modification date: D:20210210141359Z
  48 + mime type:
  49 + checksum: 9f991a5669c47a94f9350f53e3953e57
... ...
qpdf/qtest/qpdf/list-attachments-4.out
... ... @@ -5,4 +5,12 @@ auto-1 -&gt; 6,0
5 5 /UF -> auto-1
6 6 all data streams:
7 7 /F -> 6,0
  8 + creation date: <date>
  9 + modification date: <date>
  10 + mime type:
  11 + checksum: a857d18d3fc23ad412122ef040733331
8 12 /UF -> 6,0
  13 + creation date: <date>
  14 + modification date: <date>
  15 + mime type:
  16 + checksum: a857d18d3fc23ad412122ef040733331
... ...
qpdf/qtest/qpdf/test76-json.out
... ... @@ -5,19 +5,76 @@
5 5 },
6 6 "attachments": {
7 7 "att1": {
  8 + "description": "some text",
8 9 "filespec": "4 0 R",
  10 + "names": {
  11 + "/F": "att1.txt",
  12 + "/UF": "att1.txt"
  13 + },
9 14 "preferredcontents": "8 0 R",
10   - "preferredname": "att1.txt"
  15 + "preferredname": "att1.txt",
  16 + "streams": {
  17 + "/F": {
  18 + "checksum": "2e10f186a4cdf5be438747f4bdc2d4d4",
  19 + "creationdate": "2021-02-07T19:11:21-05:00",
  20 + "mimetype": "text/plain",
  21 + "modificationdate": "2021-02-07T19:11:21-05:00"
  22 + },
  23 + "/UF": {
  24 + "checksum": "2e10f186a4cdf5be438747f4bdc2d4d4",
  25 + "creationdate": "2021-02-07T19:11:21-05:00",
  26 + "mimetype": "text/plain",
  27 + "modificationdate": "2021-02-07T19:11:21-05:00"
  28 + }
  29 + }
11 30 },
12 31 "att2": {
  32 + "description": null,
13 33 "filespec": "5 0 R",
  34 + "names": {
  35 + "/F": "att2.txt",
  36 + "/UF": "att2.txt"
  37 + },
14 38 "preferredcontents": "10 0 R",
15   - "preferredname": "att2.txt"
  39 + "preferredname": "att2.txt",
  40 + "streams": {
  41 + "/F": {
  42 + "checksum": "2fce9c8228e360ba9b04a1bd1bf63d6b",
  43 + "creationdate": null,
  44 + "mimetype": "text/plain",
  45 + "modificationdate": null
  46 + },
  47 + "/UF": {
  48 + "checksum": "2fce9c8228e360ba9b04a1bd1bf63d6b",
  49 + "creationdate": null,
  50 + "mimetype": "text/plain",
  51 + "modificationdate": null
  52 + }
  53 + }
16 54 },
17 55 "att3": {
  56 + "description": null,
18 57 "filespec": "6 0 R",
  58 + "names": {
  59 + "/F": "att3.txt",
  60 + "/UF": "π.txt"
  61 + },
19 62 "preferredcontents": "12 0 R",
20   - "preferredname": "π.txt"
  63 + "preferredname": "π.txt",
  64 + "streams": {
  65 + "/F": {
  66 + "checksum": "2236c155b1d62b7f00285bba081d4336",
  67 + "creationdate": null,
  68 + "mimetype": "text/plain",
  69 + "modificationdate": null
  70 + },
  71 + "/UF": {
  72 + "checksum": "2236c155b1d62b7f00285bba081d4336",
  73 + "creationdate": null,
  74 + "mimetype": "text/plain",
  75 + "modificationdate": null
  76 + }
  77 + }
21 78 }
22 79 }
23 80 }
... ...
qpdf/qtest/qpdf/test76-list-verbose.out
... ... @@ -6,7 +6,15 @@ att1 -&gt; 8,0
6 6 /UF -> att1.txt
7 7 all data streams:
8 8 /F -> 8,0
  9 + creation date: D:20210207191121-05'00'
  10 + modification date: D:20210208001122Z
  11 + mime type: text/plain
  12 + checksum: 2e10f186a4cdf5be438747f4bdc2d4d4
9 13 /UF -> 8,0
  14 + creation date: D:20210207191121-05'00'
  15 + modification date: D:20210208001122Z
  16 + mime type: text/plain
  17 + checksum: 2e10f186a4cdf5be438747f4bdc2d4d4
10 18 att2 -> 10,0
11 19 preferred name: att2.txt
12 20 all names:
... ... @@ -14,7 +22,15 @@ att2 -&gt; 10,0
14 22 /UF -> att2.txt
15 23 all data streams:
16 24 /F -> 10,0
  25 + creation date:
  26 + modification date:
  27 + mime type: text/plain
  28 + checksum: 2fce9c8228e360ba9b04a1bd1bf63d6b
17 29 /UF -> 10,0
  30 + creation date:
  31 + modification date:
  32 + mime type: text/plain
  33 + checksum: 2fce9c8228e360ba9b04a1bd1bf63d6b
18 34 att3 -> 12,0
19 35 preferred name: π.txt
20 36 all names:
... ... @@ -22,4 +38,12 @@ att3 -&gt; 12,0
22 38 /UF -> π.txt
23 39 all data streams:
24 40 /F -> 12,0
  41 + creation date:
  42 + modification date:
  43 + mime type: text/plain
  44 + checksum: 2236c155b1d62b7f00285bba081d4336
25 45 /UF -> 12,0
  46 + creation date:
  47 + modification date:
  48 + mime type: text/plain
  49 + checksum: 2236c155b1d62b7f00285bba081d4336
... ...
qpdf/qtest/qpdf/utf16le-attachments.out
... ... @@ -5,4 +5,12 @@ potato.png -&gt; 6,0
5 5 /UF -> π.png
6 6 all data streams:
7 7 /F -> 6,0
  8 + creation date: D:20220215153939-05'00'
  9 + modification date: D:20220215153939-05'00'
  10 + mime type:
  11 + checksum: c55e70c0c72d7eaf01230124fe5ff2d9
8 12 /UF -> 6,0
  13 + creation date: D:20220215153939-05'00'
  14 + modification date: D:20220215153939-05'00'
  15 + mime type:
  16 + checksum: c55e70c0c72d7eaf01230124fe5ff2d9
... ...
qtest/bin/qtest-driver
... ... @@ -218,7 +218,7 @@ if (defined $tc_log)
218 218 print_xml(">\n");
219 219 print_junit("<?xml version=\"1.0\"?>\n" .
220 220 "<testsuites>\n");
221   -my @invalid_test_suites = ();
  221 +my @failed_test_suites = ();
222 222 foreach my $test (@tests)
223 223 {
224 224 print_and_log("\nRunning $test\n");
... ... @@ -228,7 +228,7 @@ foreach my $test (@tests)
228 228 if (scalar(@results) != 5)
229 229 {
230 230 print_and_log("test driver $test returned invalid results\n");
231   - push(@invalid_test_suites, $test);
  231 + push(@failed_test_suites, $test);
232 232 }
233 233 else
234 234 {
... ... @@ -263,6 +263,10 @@ foreach my $test (@tests)
263 263  
264 264 my $passed = (($extra == 0) && ($missing == 0) &&
265 265 ($fails == 0) && ($xpasses == 0));
  266 + if (! $passed)
  267 + {
  268 + push(@failed_test_suites, $test);
  269 + }
266 270  
267 271 print_xml(" <testsummary\n" .
268 272 " overall-outcome=\"" .($passed ? 'pass' : 'fail') . "\"\n".
... ... @@ -284,7 +288,7 @@ tc_do_final_checks();
284 288  
285 289 my $okay = ((($totpasses + $totxfails) == $tottests) &&
286 290 ($errors == 0) && ($totmissing == 0) && ($totextra == 0) &&
287   - ($coverage_okay) && (scalar(@invalid_test_suites) == 0));
  291 + ($coverage_okay) && (scalar(@failed_test_suites) == 0));
288 292  
289 293 print "\n";
290 294 print_and_pad("Overall test suite");
... ... @@ -301,10 +305,10 @@ else
301 305 }
302 306  
303 307 my $summary = "\nTESTS COMPLETE. Summary:\n\n";
304   -if (@invalid_test_suites)
  308 +if (@failed_test_suites)
305 309 {
306   - $summary .= "INVALID TEST SUITES:\n";
307   - foreach my $t (@invalid_test_suites)
  310 + $summary .= "FAILED TEST SUITES:\n";
  311 + foreach my $t (@failed_test_suites)
308 312 {
309 313 $summary .= " $t\n";
310 314 }
... ...