From b226e35208dd517c5716fa3f542dde946f0b4948 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Fri, 2 Oct 2020 15:14:45 +0200 Subject: [PATCH] Attachments tool --- PdfTool/PdfTool.pro | 2 + PdfTool/pdftoolabstractapplication.cpp | 24 ++- PdfTool/pdftoolabstractapplication.h | 12 +- PdfTool/pdftoolattachments.cpp | 208 +++++++++++++++++++++++++ PdfTool/pdftoolattachments.h | 36 +++++ PdfTool/pdftoolxml.cpp | 2 +- 6 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 PdfTool/pdftoolattachments.cpp create mode 100644 PdfTool/pdftoolattachments.h diff --git a/PdfTool/PdfTool.pro b/PdfTool/PdfTool.pro index d3ff9d8..18d6a93 100644 --- a/PdfTool/PdfTool.pro +++ b/PdfTool/PdfTool.pro @@ -42,6 +42,7 @@ SOURCES += \ main.cpp \ pdfoutputformatter.cpp \ pdftoolabstractapplication.cpp \ + pdftoolattachments.cpp \ pdftoolverifysignatures.cpp \ pdftoolxml.cpp @@ -57,5 +58,6 @@ INSTALLS += application HEADERS += \ pdfoutputformatter.h \ pdftoolabstractapplication.h \ + pdftoolattachments.h \ pdftoolverifysignatures.h \ pdftoolxml.h diff --git a/PdfTool/pdftoolabstractapplication.cpp b/PdfTool/pdftoolabstractapplication.cpp index 559119a..693f3f5 100644 --- a/PdfTool/pdftoolabstractapplication.cpp +++ b/PdfTool/pdftoolabstractapplication.cpp @@ -147,8 +147,8 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser* if (optionFlags.testFlag(ConsoleFormat)) { - parser->addOption(QCommandLineOption("console-format", "Console output text format (valid values: text|xml|html).", "console-format", "text")); - parser->addOption(QCommandLineOption("text-codec", QString("Text codec used when writing text output to redirected standard output. UTF-8 is default."), "text-codec", "UTF-8")); + parser->addOption(QCommandLineOption("console-format", "Console output text format (valid values: text|xml|html).", "format", "text")); + parser->addOption(QCommandLineOption("text-codec", QString("Text codec used when writing text output to redirected standard output. UTF-8 is default."), "text codec", "UTF-8")); } if (optionFlags.testFlag(OpenDocument)) @@ -175,6 +175,15 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser* parser->addOption(QCommandLineOption("xml-use-indent", "Use automatic indent when writing output xml file.")); parser->addOption(QCommandLineOption("xml-always-binary", "Do not try to attempt transform strings to text.")); } + + if (optionFlags.testFlag(Attachments)) + { + parser->addOption(QCommandLineOption("att-save-n", "Save the specified file attached in document. File name is, by default, same as attachment, it can be changed by a switch.", "number", QString())); + parser->addOption(QCommandLineOption("att-save-file", "Save the specified file attached in document. File name is, by default, same as attachment, it can be changed by a switch.", "file", QString())); + parser->addOption(QCommandLineOption("att-save-all", "Save all attachments to target directory.")); + parser->addOption(QCommandLineOption("att-target-dir", "Target directory to which is attachment saved.", "directory", QString())); + parser->addOption(QCommandLineOption("att-target-file", "File, to which is attachment saved.", "target", QString())); + } } PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser) const @@ -215,7 +224,7 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser if (optionFlags.testFlag(OpenDocument)) { options.document = positionalArguments.isEmpty() ? QString() : positionalArguments.front(); - options.password = parser->isSet("pswd") ? parser->value("password") : QString(); + options.password = parser->isSet("pswd") ? parser->value("pswd") : QString(); options.permissiveReading = !parser->isSet("no-permissive-reading"); } @@ -258,6 +267,15 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser options.xmlAlwaysBinaryStrings = parser->isSet("xml-always-binary"); } + if (optionFlags.testFlag(Attachments)) + { + options.attachmentsSaveNumber = parser->isSet("att-save-n") ? parser->value("att-save-n") : QString(); + options.attachmentsSaveFileName = parser->isSet("att-save-file") ? parser->value("att-save-file") : QString(); + options.attachmentsSaveAll = parser->isSet("att-save-all"); + options.attachmentsOutputDirectory = parser->isSet("att-target-dir") ? parser->value("att-target-dir") : QString(); + options.attachmentsTargetFile = parser->isSet("att-target-file") ? parser->value("att-target-file") : QString(); + } + return options; } diff --git a/PdfTool/pdftoolabstractapplication.h b/PdfTool/pdftoolabstractapplication.h index bdf1e66..c304f8e 100644 --- a/PdfTool/pdftoolabstractapplication.h +++ b/PdfTool/pdftoolabstractapplication.h @@ -62,6 +62,13 @@ struct PDFToolOptions bool xmlExportStreamsAsText = false; bool xmlUseIndent = false; bool xmlAlwaysBinaryStrings = false; + + // For option 'Attachments' + QString attachmentsSaveNumber; + QString attachmentsSaveFileName; + QString attachmentsOutputDirectory; + QString attachmentsTargetFile; + bool attachmentsSaveAll = false; }; /// Base class for all applications @@ -75,7 +82,9 @@ public: { ExitSuccess = EXIT_SUCCESS, ErrorNoDocumentSpecified, - ErrorDocumentReading + ErrorDocumentReading, + ErrorInvalidArguments, + ErrorFailedWriteToFile }; enum StandardString @@ -91,6 +100,7 @@ public: OpenDocument = 0x0002, ///< Flags for document reading SignatureVerification = 0x0004, ///< Flags for signature verification, XmlExport = 0x0008, ///< Flags for xml export + Attachments = 0x0010, ///< Flags for attachments manipulating }; Q_DECLARE_FLAGS(Options, Option) diff --git a/PdfTool/pdftoolattachments.cpp b/PdfTool/pdftoolattachments.cpp new file mode 100644 index 0000000..7cb43e6 --- /dev/null +++ b/PdfTool/pdftoolattachments.cpp @@ -0,0 +1,208 @@ +// Copyright (C) 2020 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + +#include "pdftoolattachments.h" +#include "pdfexception.h" + +namespace pdftool +{ + +static PDFToolAttachmentsApplication s_attachmentsApplication; + +QString PDFToolAttachmentsApplication::getStandardString(StandardString standardString) const +{ + switch (standardString) + { + case Command: + return "attachments"; + + case Name: + return PDFToolTranslationContext::tr("Attachments"); + + case Description: + return PDFToolTranslationContext::tr("Show list or save attached files."); + + default: + Q_ASSERT(false); + break; + } + + return QString(); +} + +int PDFToolAttachmentsApplication::execute(const PDFToolOptions& options) +{ + pdf::PDFDocument document; + if (!readDocument(options, document)) + { + return ErrorDocumentReading; + } + + struct FileInfo + { + QString no; + QString fileName; + QString mimeType; + QString mimeTypeDescription; + QString description; + bool isSaved = false; + int packedSize = 0; + const pdf::PDFFileSpecification* specification = nullptr; + }; + + QMimeDatabase mimeDatabase; + + size_t savedFileCount = 0; + size_t no = 1; + std::vector embeddedFiles; + for (const auto& item : document.getCatalog()->getEmbeddedFiles()) + { + const pdf::PDFFileSpecification* file = &item.second; + const pdf::PDFEmbeddedFile* platformFile = file->getPlatformFile(); + if (!file->getPlatformFile() || !platformFile->isValid()) + { + // Ignore invalid files + continue; + } + + FileInfo fileInfo; + fileInfo.no = QString::number(no++); + fileInfo.fileName = file->getPlatformFileName(); + fileInfo.description = file->getDescription(); + fileInfo.isSaved = false; + fileInfo.specification = file; + + QMimeType type = mimeDatabase.mimeTypeForName(platformFile->getSubtype()); + if (!type.isValid()) + { + type = mimeDatabase.mimeTypeForFile(fileInfo.fileName, QMimeDatabase::MatchExtension); + } + + fileInfo.mimeType = type.name(); + fileInfo.mimeTypeDescription = type.comment(); + fileInfo.packedSize = platformFile->getStream()->getContent()->length(); + + if (options.attachmentsSaveAll || + (!options.attachmentsSaveNumber.isEmpty() && fileInfo.no == options.attachmentsSaveNumber) || + (!options.attachmentsSaveFileName.isEmpty() && fileInfo.fileName == options.attachmentsSaveFileName)) + { + fileInfo.isSaved = true; + savedFileCount++; + } + + embeddedFiles.push_back(qMove(fileInfo)); + } + + if (savedFileCount == 0) + { + // Just print a list of embedded files + PDFOutputFormatter formatter(options.outputStyle, options.outputCodec); + formatter.beginDocument("attachments", PDFToolTranslationContext::tr("Attached files of document %1").arg(options.document)); + formatter.endl(); + + formatter.beginTable("overview", PDFToolTranslationContext::tr("Attached files overview")); + + formatter.beginTableHeaderRow("header"); + formatter.writeTableHeaderColumn("no", PDFToolTranslationContext::tr("No."), Qt::AlignLeft); + formatter.writeTableHeaderColumn("file-name", PDFToolTranslationContext::tr("File name"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("mime-type", PDFToolTranslationContext::tr("Mime type"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("mime-type-description", PDFToolTranslationContext::tr("Mime type description"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("description", PDFToolTranslationContext::tr("File description"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("packed-size", PDFToolTranslationContext::tr("Packed size [bytes]"), Qt::AlignRight); + formatter.endTableHeaderRow(); + + int ref = 1; + for (const FileInfo& info : embeddedFiles) + { + formatter.beginTableRow("file", ref); + + formatter.writeTableColumn("no", QString::number(ref)); + formatter.writeTableColumn("file-name", info.fileName); + formatter.writeTableColumn("mime-type", info.mimeType); + formatter.writeTableColumn("mime-type-description", info.mimeTypeDescription); + formatter.writeTableColumn("description", info.description); + formatter.writeTableColumn("packed-size", QString::number(info.packedSize)); + + formatter.endTableRow(); + ++ref; + } + + formatter.endTable(); + + formatter.endDocument(); + PDFConsole::writeText(formatter.getString(), options.outputCodec); + } + else + { + if (savedFileCount > 1 && !options.attachmentsTargetFile.isEmpty()) + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Target file name must not be specified, if multiple files are being saved."), options.outputCodec); + return ErrorInvalidArguments; + } + + for (const FileInfo& info : embeddedFiles) + { + if (!info.isSaved) + { + // This file is not marked to be saved + continue; + } + + QString outputFile = info.fileName; + if (!options.attachmentsTargetFile.isEmpty()) + { + outputFile = options.attachmentsTargetFile; + } + + if (!options.attachmentsOutputDirectory.isEmpty()) + { + outputFile = QString("%1/%2").arg(options.attachmentsOutputDirectory, outputFile); + } + + try + { + QByteArray data = document.getDecodedStream(info.specification->getPlatformFile()->getStream()); + + QFile file(outputFile); + if (file.open(QFile::WriteOnly | QFile::Truncate)) + { + file.write(data); + file.close(); + } + else + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Failed to save attachment to file. %1").arg(file.errorString()), options.outputCodec); + return ErrorFailedWriteToFile; + } + } + catch (pdf::PDFException e) + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Failed to save attachment to file. %1").arg(e.getMessage()), options.outputCodec); + return ErrorFailedWriteToFile; + } + } + } + + return ExitSuccess; +} + +PDFToolAbstractApplication::Options PDFToolAttachmentsApplication::getOptionsFlags() const +{ + return OpenDocument | Attachments; +} + +} // namespace pdftool diff --git a/PdfTool/pdftoolattachments.h b/PdfTool/pdftoolattachments.h new file mode 100644 index 0000000..0768ad0 --- /dev/null +++ b/PdfTool/pdftoolattachments.h @@ -0,0 +1,36 @@ +// Copyright (C) 2020 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + +#ifndef PDFTOOLATTACHMENTS_H +#define PDFTOOLATTACHMENTS_H + +#include "pdftoolabstractapplication.h" + +namespace pdftool +{ + +class PDFToolAttachmentsApplication : public PDFToolAbstractApplication +{ +public: + virtual QString getStandardString(StandardString standardString) const override; + virtual int execute(const PDFToolOptions& options) override; + virtual Options getOptionsFlags() const override; +}; + +} // namespace pdftool + +#endif // PDFTOOLATTACHMENTS_H diff --git a/PdfTool/pdftoolxml.cpp b/PdfTool/pdftoolxml.cpp index f1c54f6..3222eb7 100644 --- a/PdfTool/pdftoolxml.cpp +++ b/PdfTool/pdftoolxml.cpp @@ -172,7 +172,7 @@ void PDFXmlExportVisitor::writeTextOrBinary(const QByteArray& stream, QString na m_writer->writeEndElement(); } -QString PDFToolXmlApplication::getStandardString(PDFToolAbstractApplication::StandardString standardString) const +QString PDFToolXmlApplication::getStandardString(StandardString standardString) const { switch (standardString) {