Commit accb891b4f785b94ad9d2a6d415d9987a13747c9
1 parent
832d792e
Add attachment information to the json output
Showing
17 changed files
with
99 additions
and
2 deletions
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 "\n"; |
| 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<std::string>* 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& pdf, Options& o, JSON& 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& pdf, Options& 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->runtest("page operations on form xobject", |
| 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->runtest("list attachments verbose", |
| 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
qpdf/qtest/qpdf/json-field-types---show-encryption-key.out
qpdf/qtest/qpdf/json-field-types.out
qpdf/qtest/qpdf/json-image-streams-all.out
qpdf/qtest/qpdf/json-image-streams-small.out
qpdf/qtest/qpdf/json-image-streams-specialized.out
qpdf/qtest/qpdf/json-image-streams.out
qpdf/qtest/qpdf/json-outlines-with-actions.out
qpdf/qtest/qpdf/json-outlines-with-old-root-dests.out
qpdf/qtest/qpdf/json-page-labels-and-outlines.out
qpdf/qtest/qpdf/json-page-labels-num-tree.out
qpdf/qtest/qpdf/page_api_2-json.out
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 | +} | ... | ... |