Commit accb891b4f785b94ad9d2a6d415d9987a13747c9

Authored by Jay Berkenbilt
1 parent 832d792e

Add attachment information to the json output

ChangeLog
1 1 2021-02-10 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Add "attachments" as an additional json key, and add some
  4 + information about attachments to the json output.
  5 +
3 6 * Add new command-line arguments for operating on attachments:
4 7 --list-attachments, --add-attachment, --remove-attachment,
5 8 --copy-attachments-from. See --help and manual for details.
... ...
manual/qpdf-manual.xml
... ... @@ -5104,6 +5104,19 @@ print &quot;\n&quot;;
5104 5104 than using <option>@file</option> for this purpose.
5105 5105 </para>
5106 5106 </listitem>
  5107 + <listitem>
  5108 + <para>
  5109 + Add some information about attachments to the json output,
  5110 + and added <literal>attachments</literal> as an additional
  5111 + json key. The information included here is limited to the
  5112 + preferred name and content stream and a reference to the
  5113 + file spec object. This is enough detail for clients to avoid
  5114 + the hassle of navigating a name tree and provides what is
  5115 + needed for basic enumeration and extraction of attachments.
  5116 + More detailed information can be obtained by following the
  5117 + reference to the file spec object.
  5118 + </para>
  5119 + </listitem>
5107 5120 </itemizedlist>
5108 5121 </listitem>
5109 5122 <listitem>
... ...
qpdf/qpdf.cc
... ... @@ -699,6 +699,22 @@ static JSON json_schema(std::set&lt;std::string&gt;* keys = 0)
699 699 "filemethod",
700 700 JSON::makeString("encryption method for attachments"));
701 701 }
  702 + if (all_keys || keys->count("attachments"))
  703 + {
  704 + JSON attachments = schema.addDictionaryMember(
  705 + "attachments", JSON::makeDictionary());
  706 + JSON details = attachments.addDictionaryMember(
  707 + "<attachment-key>", JSON::makeDictionary());
  708 + details.addDictionaryMember(
  709 + "filespec",
  710 + JSON::makeString("object containing the file spec"));
  711 + details.addDictionaryMember(
  712 + "preferredname",
  713 + JSON::makeString("most preferred file name"));
  714 + details.addDictionaryMember(
  715 + "preferredcontents",
  716 + JSON::makeString("most preferred embedded file stream"));
  717 + }
702 718 return schema;
703 719 }
704 720  
... ... @@ -1114,7 +1130,7 @@ ArgParser::initOptionTable()
1114 1130 // places: json_schema, do_json, and initOptionTable.
1115 1131 char const* json_key_choices[] = {
1116 1132 "objects", "objectinfo", "pages", "pagelabels", "outlines",
1117   - "acroform", "encrypt", 0};
  1133 + "acroform", "encrypt", "attachments", 0};
1118 1134 (*t)["json-key"] = oe_requiredChoices(
1119 1135 &ArgParser::argJsonKey, json_key_choices);
1120 1136 (*t)["json-object"] = oe_requiredParameter(
... ... @@ -4568,6 +4584,28 @@ static void do_json_encrypt(QPDF&amp; pdf, Options&amp; o, JSON&amp; j)
4568 4584 "filemethod", JSON::makeString(s_file_method));
4569 4585 }
4570 4586  
  4587 +static void do_json_attachments(QPDF& pdf, Options& o, JSON& j)
  4588 +{
  4589 + JSON j_attachments = j.addDictionaryMember(
  4590 + "attachments", JSON::makeDictionary());
  4591 + QPDFEmbeddedFileDocumentHelper efdh(pdf);
  4592 + for (auto const& iter: efdh.getEmbeddedFiles())
  4593 + {
  4594 + std::string const& key = iter.first;
  4595 + auto fsoh = iter.second;
  4596 + auto j_details = j_attachments.addDictionaryMember(
  4597 + key, JSON::makeDictionary());
  4598 + j_details.addDictionaryMember(
  4599 + "filespec",
  4600 + JSON::makeString(fsoh->getObjectHandle().unparse()));
  4601 + j_details.addDictionaryMember(
  4602 + "preferredname", JSON::makeString(fsoh->getFilename()));
  4603 + j_details.addDictionaryMember(
  4604 + "preferredcontents",
  4605 + JSON::makeString(fsoh->getEmbeddedFileStream().unparse()));
  4606 + }
  4607 +}
  4608 +
4571 4609 static void do_json(QPDF& pdf, Options& o)
4572 4610 {
4573 4611 JSON j = JSON::makeDictionary();
... ... @@ -4628,6 +4666,10 @@ static void do_json(QPDF&amp; pdf, Options&amp; o)
4628 4666 {
4629 4667 do_json_encrypt(pdf, o, j);
4630 4668 }
  4669 + if (all_keys || o.json_keys.count("attachments"))
  4670 + {
  4671 + do_json_attachments(pdf, o, j);
  4672 + }
4631 4673  
4632 4674 // Check against schema
4633 4675  
... ...
qpdf/qtest/qpdf.test
... ... @@ -523,7 +523,7 @@ $td-&gt;runtest(&quot;page operations on form xobject&quot;,
523 523 show_ntests();
524 524 # ----------
525 525 $td->notify("--- File Attachments ---");
526   -$n_tests += 33;
  526 +$n_tests += 34;
527 527  
528 528 open(F, ">auto-txt") or die;
529 529 print F "from file";
... ... @@ -547,6 +547,10 @@ $td-&gt;runtest(&quot;list attachments verbose&quot;,
547 547 {$td->COMMAND => "qpdf --list-attachments --verbose a.pdf"},
548 548 {$td->FILE => "test76-list-verbose.out", $td->EXIT_STATUS => 0},
549 549 $td->NORMALIZE_NEWLINES);
  550 +$td->runtest("attachments json",
  551 + {$td->COMMAND => "qpdf --json --json-key=attachments a.pdf"},
  552 + {$td->FILE => "test76-json.out", $td->EXIT_STATUS => 0},
  553 + $td->NORMALIZE_NEWLINES);
550 554 $td->runtest("remove attachment (test_driver)",
551 555 {$td->COMMAND => "test_driver 77 test76.pdf"},
552 556 {$td->STRING => "test 77 done\n", $td->EXIT_STATUS => 0},
... ...
qpdf/qtest/qpdf/direct-pages-json.out
... ... @@ -4,6 +4,7 @@
4 4 "hasacroform": false,
5 5 "needappearances": false
6 6 },
  7 + "attachments": {},
7 8 "encrypt": {
8 9 "capabilities": {
9 10 "accessibility": true,
... ...
qpdf/qtest/qpdf/json-field-types---show-encryption-key.out
... ... @@ -385,6 +385,7 @@
385 385 "hasacroform": true,
386 386 "needappearances": true
387 387 },
  388 + "attachments": {},
388 389 "encrypt": {
389 390 "capabilities": {
390 391 "accessibility": true,
... ...
qpdf/qtest/qpdf/json-field-types.out
... ... @@ -385,6 +385,7 @@
385 385 "hasacroform": true,
386 386 "needappearances": true
387 387 },
  388 + "attachments": {},
388 389 "encrypt": {
389 390 "capabilities": {
390 391 "accessibility": true,
... ...
qpdf/qtest/qpdf/json-image-streams-all.out
... ... @@ -4,6 +4,7 @@
4 4 "hasacroform": false,
5 5 "needappearances": false
6 6 },
  7 + "attachments": {},
7 8 "encrypt": {
8 9 "capabilities": {
9 10 "accessibility": true,
... ...
qpdf/qtest/qpdf/json-image-streams-small.out
... ... @@ -4,6 +4,7 @@
4 4 "hasacroform": false,
5 5 "needappearances": false
6 6 },
  7 + "attachments": {},
7 8 "encrypt": {
8 9 "capabilities": {
9 10 "accessibility": true,
... ...
qpdf/qtest/qpdf/json-image-streams-specialized.out
... ... @@ -4,6 +4,7 @@
4 4 "hasacroform": false,
5 5 "needappearances": false
6 6 },
  7 + "attachments": {},
7 8 "encrypt": {
8 9 "capabilities": {
9 10 "accessibility": true,
... ...
qpdf/qtest/qpdf/json-image-streams.out
... ... @@ -4,6 +4,7 @@
4 4 "hasacroform": false,
5 5 "needappearances": false
6 6 },
  7 + "attachments": {},
7 8 "encrypt": {
8 9 "capabilities": {
9 10 "accessibility": true,
... ...
qpdf/qtest/qpdf/json-outlines-with-actions.out
... ... @@ -4,6 +4,7 @@
4 4 "hasacroform": false,
5 5 "needappearances": false
6 6 },
  7 + "attachments": {},
7 8 "encrypt": {
8 9 "capabilities": {
9 10 "accessibility": true,
... ...
qpdf/qtest/qpdf/json-outlines-with-old-root-dests.out
... ... @@ -4,6 +4,7 @@
4 4 "hasacroform": false,
5 5 "needappearances": false
6 6 },
  7 + "attachments": {},
7 8 "encrypt": {
8 9 "capabilities": {
9 10 "accessibility": true,
... ...
qpdf/qtest/qpdf/json-page-labels-and-outlines.out
... ... @@ -4,6 +4,7 @@
4 4 "hasacroform": false,
5 5 "needappearances": false
6 6 },
  7 + "attachments": {},
7 8 "encrypt": {
8 9 "capabilities": {
9 10 "accessibility": true,
... ...
qpdf/qtest/qpdf/json-page-labels-num-tree.out
... ... @@ -4,6 +4,7 @@
4 4 "hasacroform": false,
5 5 "needappearances": false
6 6 },
  7 + "attachments": {},
7 8 "encrypt": {
8 9 "capabilities": {
9 10 "accessibility": true,
... ...
qpdf/qtest/qpdf/page_api_2-json.out
... ... @@ -4,6 +4,7 @@
4 4 "hasacroform": false,
5 5 "needappearances": false
6 6 },
  7 + "attachments": {},
7 8 "encrypt": {
8 9 "capabilities": {
9 10 "accessibility": true,
... ...
qpdf/qtest/qpdf/test76-json.out 0 → 100644
  1 +{
  2 + "attachments": {
  3 + "att1": {
  4 + "filespec": "4 0 R",
  5 + "preferredcontents": "8 0 R",
  6 + "preferredname": "att1.txt"
  7 + },
  8 + "att2": {
  9 + "filespec": "5 0 R",
  10 + "preferredcontents": "10 0 R",
  11 + "preferredname": "att2.txt"
  12 + },
  13 + "att3": {
  14 + "filespec": "6 0 R",
  15 + "preferredcontents": "12 0 R",
  16 + "preferredname": "π.txt"
  17 + }
  18 + },
  19 + "parameters": {
  20 + "decodelevel": "generalized"
  21 + },
  22 + "version": 1
  23 +}
... ...