From 2acbcd68b2ba40d01b30c04362504fa9c4fa9a0e Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 4 Oct 2020 16:56:55 +0200 Subject: [PATCH] Tool for gathering info about document --- PdfForQtLib/PdfForQtLib.pro | 2 + PdfForQtLib/sources/pdfaction.h | 2 +- PdfForQtLib/sources/pdfannotation.cpp | 4 + PdfForQtLib/sources/pdfannotation.h | 7 + PdfForQtLib/sources/pdfcatalog.cpp | 13 + PdfForQtLib/sources/pdfcatalog.h | 36 +++ PdfForQtLib/sources/pdfform.h | 3 + PdfForQtLib/sources/pdfjavascriptscanner.cpp | 199 ++++++++++++ PdfForQtLib/sources/pdfjavascriptscanner.h | 86 +++++ .../pdfdocumentpropertiesdialog.cpp | 4 +- PdfTool/PdfTool.pro | 2 + PdfTool/pdftoolabstractapplication.cpp | 71 +++-- PdfTool/pdftoolabstractapplication.h | 18 +- PdfTool/pdftoolattachments.cpp | 2 +- PdfTool/pdftoolinfo.cpp | 296 ++++++++++++++++++ PdfTool/pdftoolinfo.h | 36 +++ PdfTool/pdftoolverifysignatures.cpp | 14 +- 17 files changed, 758 insertions(+), 37 deletions(-) create mode 100644 PdfForQtLib/sources/pdfjavascriptscanner.cpp create mode 100644 PdfForQtLib/sources/pdfjavascriptscanner.h create mode 100644 PdfTool/pdftoolinfo.cpp create mode 100644 PdfTool/pdftoolinfo.h diff --git a/PdfForQtLib/PdfForQtLib.pro b/PdfForQtLib/PdfForQtLib.pro index 95b3919..f69e404 100644 --- a/PdfForQtLib/PdfForQtLib.pro +++ b/PdfForQtLib/PdfForQtLib.pro @@ -54,6 +54,7 @@ SOURCES += \ sources/pdffile.cpp \ sources/pdfform.cpp \ sources/pdfitemmodels.cpp \ + sources/pdfjavascriptscanner.cpp \ sources/pdfjbig2decoder.cpp \ sources/pdfmultimedia.cpp \ sources/pdfobject.cpp \ @@ -108,6 +109,7 @@ HEADERS += \ sources/pdffile.h \ sources/pdfform.h \ sources/pdfitemmodels.h \ + sources/pdfjavascriptscanner.h \ sources/pdfjbig2decoder.h \ sources/pdfmeshqualitysettings.h \ sources/pdfmultimedia.h \ diff --git a/PdfForQtLib/sources/pdfaction.h b/PdfForQtLib/sources/pdfaction.h index ebfe8a5..d84f716 100644 --- a/PdfForQtLib/sources/pdfaction.h +++ b/PdfForQtLib/sources/pdfaction.h @@ -487,7 +487,7 @@ public: const PDFRendition* getRendition() const { return m_rendition.has_value() ? &m_rendition.value() : nullptr; } PDFObjectReference getAnnotation() const { return m_annotation; } Operation getOperation() const { return m_operation; } - const QString& getJavascript() const { return m_javascript; } + const QString& getJavaScript() const { return m_javascript; } private: std::optional m_rendition; diff --git a/PdfForQtLib/sources/pdfannotation.cpp b/PdfForQtLib/sources/pdfannotation.cpp index eeb781f..1e4f3fe 100644 --- a/PdfForQtLib/sources/pdfannotation.cpp +++ b/PdfForQtLib/sources/pdfannotation.cpp @@ -1026,6 +1026,10 @@ PDFAnnotationAdditionalActions PDFAnnotationAdditionalActions::parse(const PDFOb result.m_actions[PageClosed] = PDFAction::parse(storage, dictionary->get("PC")); result.m_actions[PageShow] = PDFAction::parse(storage, dictionary->get("PV")); result.m_actions[PageHide] = PDFAction::parse(storage, dictionary->get("PI")); + result.m_actions[FormFieldModified] = PDFAction::parse(storage, dictionary->get("K")); + result.m_actions[FormFieldFormatted] = PDFAction::parse(storage, dictionary->get("F")); + result.m_actions[FormFieldValidated] = PDFAction::parse(storage, dictionary->get("V")); + result.m_actions[FormFieldCalculated] = PDFAction::parse(storage, dictionary->get("C")); } result.m_actions[Default] = PDFAction::parse(storage, defaultAction); diff --git a/PdfForQtLib/sources/pdfannotation.h b/PdfForQtLib/sources/pdfannotation.h index 0ed0207..3895693 100644 --- a/PdfForQtLib/sources/pdfannotation.h +++ b/PdfForQtLib/sources/pdfannotation.h @@ -394,6 +394,10 @@ public: PageClosed, PageShow, PageHide, + FormFieldModified, + FormFieldFormatted, + FormFieldValidated, + FormFieldCalculated, Default, End }; @@ -405,6 +409,9 @@ public: /// \param action Action type const PDFAction* getAction(Action action) const { return m_actions.at(action).get(); } + /// Returns array with all actions + const std::array& getActions() const { return m_actions; } + /// Parses annotation additional actions from the object. If object is invalid, then /// empty additional actions is constructed. /// \param storage Object storage diff --git a/PdfForQtLib/sources/pdfcatalog.cpp b/PdfForQtLib/sources/pdfcatalog.cpp index 36d6c00..34b6240 100644 --- a/PdfForQtLib/sources/pdfcatalog.cpp +++ b/PdfForQtLib/sources/pdfcatalog.cpp @@ -1120,4 +1120,17 @@ PDFDocumentRequirements::RequirementEntry PDFDocumentRequirements::RequirementEn return entry; } +PDFPageAdditionalActions PDFPageAdditionalActions::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFPageAdditionalActions result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + result.m_actions[Open] = PDFAction::parse(storage, dictionary->get("O")); + result.m_actions[Close] = PDFAction::parse(storage, dictionary->get("C")); + } + + return result; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfcatalog.h b/PdfForQtLib/sources/pdfcatalog.h index 5a6dce0..74846cf 100644 --- a/PdfForQtLib/sources/pdfcatalog.h +++ b/PdfForQtLib/sources/pdfcatalog.h @@ -526,6 +526,38 @@ private: std::vector m_requirements; }; +/// Storage for page additional actions +class PDFPageAdditionalActions +{ +public: + + enum Action + { + Open, + Close, + End + }; + + inline explicit PDFPageAdditionalActions() = default; + + /// Returns action for given type. If action is invalid, + /// or not present, nullptr is returned. + /// \param action Action type + const PDFAction* getAction(Action action) const { return m_actions.at(action).get(); } + + /// Returns array with all actions + const std::array& getActions() const { return m_actions; } + + /// Parses page additional actions from the object. If object is invalid, then + /// empty additional actions is constructed. + /// \param storage Object storage + /// \param object Additional actions object + static PDFPageAdditionalActions parse(const PDFObjectStorage* storage, PDFObject object); + +private: + std::array m_actions; +}; + class PDFFORQTLIBSHARED_EXPORT PDFCatalog { public: @@ -582,6 +614,7 @@ public: const PDFDocumentSecurityStore& getDocumentSecurityStore() const { return m_documentSecurityStore; } const std::vector& getArticleThreads() const { return m_threads; } const PDFAction* getDocumentAction(DocumentAction action) const { return m_documentActions.at(action).get(); } + const auto& getDocumentActions() const { return m_documentActions; } const PDFObject& getMetadata() const { return m_metadata; } const PDFObject& getStructureTreeRoot() const { return m_structureTreeRoot; } const QString& getLanguage() const { return m_language; } @@ -661,6 +694,9 @@ public: /// \returns Rendition, or nullptr PDFObject getNamedRendition(const QByteArray& key) const; + /// Returns all named JavaScript actions + const std::map& getNamedJavaScriptActions() const { return m_namedJavaScriptActions; } + /// Parses catalog from catalog dictionary. If object cannot be parsed, or error occurs, /// then exception is thrown. static PDFCatalog parse(const PDFObject& catalog, const PDFDocument* document); diff --git a/PdfForQtLib/sources/pdfform.h b/PdfForQtLib/sources/pdfform.h index 3960479..fb3cdce 100644 --- a/PdfForQtLib/sources/pdfform.h +++ b/PdfForQtLib/sources/pdfform.h @@ -217,6 +217,9 @@ public: /// \param action Action type const PDFAction* getAction(PDFAnnotationAdditionalActions::Action action) const { return m_additionalActions.getAction(action); } + /// Returns container of actions + const PDFAnnotationAdditionalActions& getActions() const { return m_additionalActions; } + /// Parses form field from the object reference. If some error occurs /// then null pointer is returned, no exception is thrown. /// \param storage Storage diff --git a/PdfForQtLib/sources/pdfjavascriptscanner.cpp b/PdfForQtLib/sources/pdfjavascriptscanner.cpp new file mode 100644 index 0000000..04db402 --- /dev/null +++ b/PdfForQtLib/sources/pdfjavascriptscanner.cpp @@ -0,0 +1,199 @@ +// 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 "pdfjavascriptscanner.h" +#include "pdfaction.h" +#include "pdfform.h" + +namespace pdf +{ + +PDFJavaScriptScanner::PDFJavaScriptScanner(const PDFDocument* document) : + m_document(document) +{ + +} + +PDFJavaScriptScanner::Entries PDFJavaScriptScanner::scan(const std::vector& pages, Options options) const +{ + Entries result; + + auto scanAction = [this, options, &result](PDFJavaScriptEntry::Type type, PDFInteger pageIndex, const PDFAction* action) + { + if (!result.empty() && options.testFlag(FindFirstOnly)) + { + return; + } + + if (action) + { + std::vector actions = action->getActionList(); + for (const PDFAction* a : actions) + { + switch (a->getType()) + { + case ActionType::JavaScript: + { + const PDFActionJavaScript* javascriptAction = dynamic_cast(a); + Q_ASSERT(javascriptAction); + + result.emplace_back(type, pageIndex, javascriptAction->getJavaScript()); + break; + } + + case ActionType::Rendition: + { + const PDFActionRendition* renditionAction = dynamic_cast(a); + Q_ASSERT(renditionAction); + + if (!renditionAction->getJavaScript().isEmpty()) + { + result.emplace_back(type, pageIndex, renditionAction->getJavaScript()); + } + break; + } + + default: + break; + } + + if (!result.empty() && options.testFlag(FindFirstOnly)) + { + break; + } + } + } + }; + + auto scanContainer = [this, options, &scanAction](PDFJavaScriptEntry::Type type, PDFInteger pageIndex, const auto& container) + { + for (const PDFActionPtr& action : container) + { + scanAction(type, pageIndex, action.get()); + } + }; + + const PDFCatalog* catalog = m_document->getCatalog(); + + if (options.testFlag(ScanDocument) && (result.empty() || !options.testFlag(FindFirstOnly))) + { + scanContainer(PDFJavaScriptEntry::Type::Document, -1, catalog->getDocumentActions()); + } + + if (options.testFlag(ScanNamed) && (result.empty() || !options.testFlag(FindFirstOnly))) + { + for (const auto& actionItem : catalog->getNamedJavaScriptActions()) + { + scanAction(PDFJavaScriptEntry::Type::Named, -1, actionItem.second.get()); + } + } + + if (options.testFlag(ScanForm) && (result.empty() || !options.testFlag(FindFirstOnly))) + { + PDFForm form = PDFForm::parse(m_document, catalog->getFormObject()); + if (form.isAcroForm() || form.isXFAForm()) + { + auto fillActions = [this, &scanContainer](const PDFFormField* formField) + { + scanContainer(PDFJavaScriptEntry::Type::Form, -1, formField->getActions().getActions()); + }; + form.apply(fillActions); + } + } + + if (options.testFlag(ScanPage) && (result.empty() || !options.testFlag(FindFirstOnly))) + { + std::vector scannedPages; + if (options.testFlag(AllPages)) + { + scannedPages.resize(m_document->getCatalog()->getPageCount(), 0); + std::iota(scannedPages.begin(), scannedPages.end(), 0); + } + else + { + scannedPages = pages; + } + + for (const PDFInteger pageIndex : scannedPages) + { + if (pageIndex < 0 || pageIndex >= PDFInteger(catalog->getPageCount())) + { + continue; + } + + if (!result.empty() && options.testFlag(FindFirstOnly)) + { + break; + } + + PDFPageAdditionalActions pageActions = PDFPageAdditionalActions::parse(&m_document->getStorage(), catalog->getPage(pageIndex)->getAdditionalActions(&m_document->getStorage())); + scanContainer(PDFJavaScriptEntry::Type::Page, pageIndex, pageActions.getActions()); + + const std::vector& pageAnnotations = catalog->getPage(pageIndex)->getAnnotations(); + for (PDFObjectReference annotationReference : pageAnnotations) + { + PDFAnnotationPtr annotationPtr = PDFAnnotation::parse(&m_document->getStorage(), annotationReference); + if (annotationPtr) + { + switch (annotationPtr->getType()) + { + case AnnotationType::Link: + { + const PDFLinkAnnotation* linkAnnotation = dynamic_cast(annotationPtr.get()); + Q_ASSERT(linkAnnotation); + + scanAction(PDFJavaScriptEntry::Type::Annotation, pageIndex, linkAnnotation->getAction()); + break; + } + + case AnnotationType::Screen: + { + const PDFScreenAnnotation* screenAnnotation = dynamic_cast(annotationPtr.get()); + Q_ASSERT(screenAnnotation); + + scanAction(PDFJavaScriptEntry::Type::Annotation, pageIndex, screenAnnotation->getAction()); + scanContainer(PDFJavaScriptEntry::Type::Annotation, pageIndex, screenAnnotation->getAdditionalActions().getActions()); + break; + } + + case AnnotationType::Widget: + { + const PDFWidgetAnnotation* widgetAnnotation = dynamic_cast(annotationPtr.get()); + Q_ASSERT(widgetAnnotation); + + scanAction(PDFJavaScriptEntry::Type::Annotation, pageIndex, widgetAnnotation->getAction()); + scanContainer(PDFJavaScriptEntry::Type::Annotation, pageIndex, widgetAnnotation->getAdditionalActions().getActions()); + break; + } + + default: + break; + } + } + } + } + } + + return result; +} + +bool PDFJavaScriptScanner::hasJavaScript() const +{ + return !scan({ }, Options(AllPages | FindFirstOnly | ScanDocument | ScanNamed | ScanForm | ScanPage)).empty(); +} + +} // namespace pdf diff --git a/PdfForQtLib/sources/pdfjavascriptscanner.h b/PdfForQtLib/sources/pdfjavascriptscanner.h new file mode 100644 index 0000000..8a54171 --- /dev/null +++ b/PdfForQtLib/sources/pdfjavascriptscanner.h @@ -0,0 +1,86 @@ +// 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 PDFJAVASCRIPTSCANNER_H +#define PDFJAVASCRIPTSCANNER_H + +#include "pdfdocument.h" + +namespace pdf +{ + +struct PDFJavaScriptEntry +{ + enum class Type + { + Invalid, + Document, + Named, + Form, + Page, + Annotation + }; + + explicit PDFJavaScriptEntry() = default; + explicit PDFJavaScriptEntry(Type type, PDFInteger pageIndex, QString javaScript) : + type(type), pageIndex(pageIndex), javaScript(javaScript) + { + + } + + Type type = Type::Invalid; + PDFInteger pageIndex = -1; + QString javaScript; +}; + +/// Scans document for all javascript presence (in actions). Several option +/// can be set, for example, scan only document actions, or stop scanning, +/// when first javascript is found. +class PDFFORQTLIBSHARED_EXPORT PDFJavaScriptScanner +{ +public: + explicit PDFJavaScriptScanner(const PDFDocument* document); + + enum Option + { + AllPages = 0x0001, ///< Scan all pages + FindFirstOnly = 0x0002, ///< Return only first javascript found + ScanDocument = 0x0004, ///< Scan document related actions for javascript + ScanNamed = 0x0008, ///< Scan named javascript in catalog + ScanForm = 0x0010, ///< Scan javascript in form actions + ScanPage = 0x0020, ///< Scan javascript in page annotations + }; + Q_DECLARE_FLAGS(Options, Option) + + using Entries = std::vector; + + /// Scans document for javascript actions using flags + Entries scan(const std::vector& pages, Options options) const; + + /// Returns true, if document has any java script action. Calling + /// this function can be slow. + bool hasJavaScript() const; + +private: + const PDFDocument* m_document; +}; + +} // namespace pdf + +Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFJavaScriptScanner::Options) + +#endif // PDFJAVASCRIPTSCANNER_H diff --git a/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp b/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp index 7d5230b..0f6602f 100644 --- a/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp +++ b/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp @@ -86,11 +86,11 @@ void PDFDocumentPropertiesDialog::initializeProperties(const pdf::PDFDocument* d switch (info->trapped) { case pdf::PDFDocumentInfo::Trapped::True: - trapped = tr("True"); + trapped = tr("Yes"); break; case pdf::PDFDocumentInfo::Trapped::False: - trapped = tr("False"); + trapped = tr("No"); break; case pdf::PDFDocumentInfo::Trapped::Unknown: diff --git a/PdfTool/PdfTool.pro b/PdfTool/PdfTool.pro index 18d6a93..fc78eca 100644 --- a/PdfTool/PdfTool.pro +++ b/PdfTool/PdfTool.pro @@ -43,6 +43,7 @@ SOURCES += \ pdfoutputformatter.cpp \ pdftoolabstractapplication.cpp \ pdftoolattachments.cpp \ + pdftoolinfo.cpp \ pdftoolverifysignatures.cpp \ pdftoolxml.cpp @@ -59,5 +60,6 @@ HEADERS += \ pdfoutputformatter.h \ pdftoolabstractapplication.h \ pdftoolattachments.h \ + pdftoolinfo.h \ pdftoolverifysignatures.h \ pdftoolxml.h diff --git a/PdfTool/pdftoolabstractapplication.cpp b/PdfTool/pdftoolabstractapplication.cpp index 693f3f5..ab3afab 100644 --- a/PdfTool/pdftoolabstractapplication.cpp +++ b/PdfTool/pdftoolabstractapplication.cpp @@ -151,6 +151,11 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser* 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(DateFormat)) + { + parser->addOption(QCommandLineOption("date-format", "Console output date/time format (valid values: short|long|iso|rfc2822).", "date format", "short")); + } + if (optionFlags.testFlag(OpenDocument)) { parser->addOption(QCommandLineOption("pswd", "Password for encrypted document.", "password")); @@ -165,7 +170,6 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser* parser->addOption(QCommandLineOption("ver-no-cert-check", "Disable certificate validation.")); parser->addOption(QCommandLineOption("ver-details", "Print details (including certificate chain, if found).")); parser->addOption(QCommandLineOption("ver-ignore-exp-date", "Ignore certificate expiration date.")); - parser->addOption(QCommandLineOption("ver-date-format", "Console output date/time format (valid values: short|long|iso|rfc2822).", "ver-date-format", "short")); } if (optionFlags.testFlag(XmlExport)) @@ -184,6 +188,11 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser* 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())); } + + if (optionFlags.testFlag(ComputeHashes)) + { + parser->addOption(QCommandLineOption("compute-hashes", "Compute hashes (MD5, SHA1, SHA256...) of document.")); + } } PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser) const @@ -221,6 +230,31 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser options.outputCodec = parser->value("text-codec"); } + if (optionFlags.testFlag(DateFormat)) + { + QString dateFormat = parser->value("date-format"); + if (dateFormat == "short") + { + options.outputDateFormat = Qt::DefaultLocaleShortDate; + } + else if (dateFormat == "long") + { + options.outputDateFormat = Qt::DefaultLocaleLongDate; + } + else if (dateFormat == "iso") + { + options.outputDateFormat = Qt::ISODate; + } + else if (dateFormat == "rfc2822") + { + options.outputDateFormat = Qt::RFC2822Date; + } + else if (!dateFormat.isEmpty()) + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Unknown console date/time format '%1'. Defaulting to short date/time format.").arg(dateFormat), options.outputCodec); + } + } + if (optionFlags.testFlag(OpenDocument)) { options.document = positionalArguments.isEmpty() ? QString() : positionalArguments.front(); @@ -235,28 +269,6 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser options.verificationOmitCertificateCheck = parser->isSet("ver-no-cert-check"); options.verificationPrintCertificateDetails = parser->isSet("ver-details"); options.verificationIgnoreExpirationDate = parser->isSet("ver-ignore-exp-date"); - - QString dateFormat = parser->value("ver-date-format"); - if (dateFormat == "short") - { - options.verificationDateFormat = Qt::DefaultLocaleShortDate; - } - else if (dateFormat == "long") - { - options.verificationDateFormat = Qt::DefaultLocaleLongDate; - } - else if (dateFormat == "iso") - { - options.verificationDateFormat = Qt::ISODate; - } - else if (dateFormat == "rfc2822") - { - options.verificationDateFormat = Qt::RFC2822Date; - } - else if (!dateFormat.isEmpty()) - { - PDFConsole::writeError(PDFToolTranslationContext::tr("Unknown console date/time format '%1'. Defaulting to short date/time format.").arg(dateFormat), options.outputCodec); - } } if (optionFlags.testFlag(XmlExport)) @@ -276,10 +288,15 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser options.attachmentsTargetFile = parser->isSet("att-target-file") ? parser->value("att-target-file") : QString(); } + if (optionFlags.testFlag(ComputeHashes)) + { + options.computeHashes = parser->isSet("compute-hashes"); + } + return options; } -bool PDFToolAbstractApplication::readDocument(const PDFToolOptions& options, pdf::PDFDocument& document) +bool PDFToolAbstractApplication::readDocument(const PDFToolOptions& options, pdf::PDFDocument& document, QByteArray* sourceData) { bool isFirstPasswordAttempt = true; auto passwordCallback = [&options, &isFirstPasswordAttempt](bool* ok) -> QString @@ -294,7 +311,13 @@ bool PDFToolAbstractApplication::readDocument(const PDFToolOptions& options, pdf switch (reader.getReadingResult()) { case pdf::PDFDocumentReader::Result::OK: + { + if (sourceData) + { + *sourceData = reader.getSource(); + } break; + } case pdf::PDFDocumentReader::Result::Cancelled: { diff --git a/PdfTool/pdftoolabstractapplication.h b/PdfTool/pdftoolabstractapplication.h index c304f8e..c112cd3 100644 --- a/PdfTool/pdftoolabstractapplication.h +++ b/PdfTool/pdftoolabstractapplication.h @@ -44,6 +44,9 @@ struct PDFToolOptions PDFOutputFormatter::Style outputStyle = PDFOutputFormatter::Style::Text; QString outputCodec; + // For option 'DateFormat' + Qt::DateFormat outputDateFormat = Qt::DefaultLocaleShortDate; + // For option 'OpenDocument' QString document; QString password; @@ -55,7 +58,6 @@ struct PDFToolOptions bool verificationOmitCertificateCheck = false; bool verificationPrintCertificateDetails = false; bool verificationIgnoreExpirationDate = false; - Qt::DateFormat verificationDateFormat = Qt::DefaultLocaleShortDate; // For option 'XMLExport' bool xmlExportStreams = false; @@ -69,6 +71,9 @@ struct PDFToolOptions QString attachmentsOutputDirectory; QString attachmentsTargetFile; bool attachmentsSaveAll = false; + + // For option 'ComputeHashes' + bool computeHashes = false; }; /// Base class for all applications @@ -101,6 +106,8 @@ public: SignatureVerification = 0x0004, ///< Flags for signature verification, XmlExport = 0x0008, ///< Flags for xml export Attachments = 0x0010, ///< Flags for attachments manipulating + DateFormat = 0x0020, ///< Date format + ComputeHashes = 0x0040, ///< Compute hashes }; Q_DECLARE_FLAGS(Options, Option) @@ -111,7 +118,14 @@ public: void initializeCommandLineParser(QCommandLineParser* parser) const; PDFToolOptions getOptions(QCommandLineParser* parser) const; - bool readDocument(const PDFToolOptions& options, pdf::PDFDocument& document); +protected: + /// Tries to read the document. If document is successfully read, true is returned, + /// if error occurs, then false is returned. Optionally, original document content + /// can also be retrieved. + /// \param options Options + /// \param document Document + /// \param[out] sourceData Pointer, to which source data are stored + bool readDocument(const PDFToolOptions& options, pdf::PDFDocument& document, QByteArray* sourceData = nullptr); }; /// This class stores information about all applications available. Application diff --git a/PdfTool/pdftoolattachments.cpp b/PdfTool/pdftoolattachments.cpp index 7cb43e6..646c470 100644 --- a/PdfTool/pdftoolattachments.cpp +++ b/PdfTool/pdftoolattachments.cpp @@ -202,7 +202,7 @@ int PDFToolAttachmentsApplication::execute(const PDFToolOptions& options) PDFToolAbstractApplication::Options PDFToolAttachmentsApplication::getOptionsFlags() const { - return OpenDocument | Attachments; + return ConsoleFormat | OpenDocument | Attachments; } } // namespace pdftool diff --git a/PdfTool/pdftoolinfo.cpp b/PdfTool/pdftoolinfo.cpp new file mode 100644 index 0000000..7d553cf --- /dev/null +++ b/PdfTool/pdftoolinfo.cpp @@ -0,0 +1,296 @@ +// 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 "pdftoolinfo.h" +#include "pdfform.h" +#include "pdfjavascriptscanner.h" + +#include +#include + +namespace pdftool +{ + +static PDFToolInfoApplication s_infoApplication; + +QString PDFToolInfoApplication::getStandardString(StandardString standardString) const +{ + switch (standardString) + { + case Command: + return "info"; + + case Name: + return PDFToolTranslationContext::tr("Info"); + + case Description: + return PDFToolTranslationContext::tr("Retrieve basic informations about a document."); + + default: + Q_ASSERT(false); + break; + } + + return QString(); +} + +int PDFToolInfoApplication::execute(const PDFToolOptions& options) +{ + pdf::PDFDocument document; + QByteArray sourceData; + if (!readDocument(options, document, &sourceData)) + { + return ErrorDocumentReading; + } + + QLocale locale; + + const pdf::PDFDocumentInfo* info = document.getInfo(); + const pdf::PDFCatalog* catalog = document.getCatalog(); + + PDFOutputFormatter formatter(options.outputStyle, options.outputCodec); + formatter.beginDocument("info", PDFToolTranslationContext::tr("Information about document %1").arg(options.document)); + formatter.endl(); + + formatter.beginTable("properties", PDFToolTranslationContext::tr("Properties:")); + + formatter.beginTableHeaderRow("header"); + formatter.writeTableHeaderColumn("property", PDFToolTranslationContext::tr("Property"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("value", PDFToolTranslationContext::tr("Value"), Qt::AlignLeft); + formatter.endTableHeaderRow(); + + auto writeProperty = [&formatter](const QString& propertyName, const QString& property, const QString& value) + { + formatter.beginTableRow(propertyName); + formatter.writeTableColumn("property", property); + formatter.writeTableColumn("value", value); + formatter.endTableRow(); + }; + + writeProperty("title", PDFToolTranslationContext::tr("Title"), info->title); + writeProperty("subject", PDFToolTranslationContext::tr("Subject"), info->subject); + writeProperty("keywords", PDFToolTranslationContext::tr("Keywords"), info->keywords); + writeProperty("author", PDFToolTranslationContext::tr("Author"), info->author); + writeProperty("creator", PDFToolTranslationContext::tr("Creator"), info->creator); + writeProperty("producer", PDFToolTranslationContext::tr("Producer"), info->producer); + writeProperty("creation-date", PDFToolTranslationContext::tr("Creation date"), info->creationDate.toLocalTime().toString(options.outputDateFormat)); + writeProperty("modified-date", PDFToolTranslationContext::tr("Modified date"), info->modifiedDate.toLocalTime().toString(options.outputDateFormat)); + writeProperty("version", PDFToolTranslationContext::tr("Version"), QString::fromLatin1(document.getVersion())); + + QString trapped; + switch (info->trapped) + { + case pdf::PDFDocumentInfo::Trapped::True: + trapped = PDFToolTranslationContext::tr("Yes"); + break; + + case pdf::PDFDocumentInfo::Trapped::False: + trapped = PDFToolTranslationContext::tr("No"); + break; + + case pdf::PDFDocumentInfo::Trapped::Unknown: + trapped = PDFToolTranslationContext::tr("Unknown"); + break; + + default: + Q_ASSERT(false); + break; + } + + writeProperty("tagged", PDFToolTranslationContext::tr("Tagged"), trapped); + + QString formType; + pdf::PDFForm form = pdf::PDFForm::parse(&document, catalog->getFormObject()); + switch (form.getFormType()) + { + case pdf::PDFForm::FormType::None: + formType = PDFToolTranslationContext::tr("None"); + break; + + case pdf::PDFForm::FormType::AcroForm: + formType = PDFToolTranslationContext::tr("AcroForm"); + break; + + case pdf::PDFForm::FormType::XFAForm: + formType = PDFToolTranslationContext::tr("XFA"); + break; + + default: + Q_ASSERT(false); + break; + } + + writeProperty("form-type", PDFToolTranslationContext::tr("Form type"), formType); + + const pdf::PDFInteger pageCount = catalog->getPageCount(); + writeProperty("page-count", PDFToolTranslationContext::tr("Page count"), locale.toString(pageCount)); + if (pageCount > 0) + { + const pdf::PDFPage* firstPage = catalog->getPage(0); + QSizeF pageSizeMM = firstPage->getRectMM(firstPage->getRotatedMediaBox()).size(); + QPageSize pageSize(pageSizeMM, QPageSize::Millimeter, QString(), QPageSize::FuzzyOrientationMatch); + QString paperSizeString = QString("%1 x %2 mm").arg(locale.toString(pageSizeMM.width()), locale.toString(pageSizeMM.height())); + + writeProperty("paper-format", PDFToolTranslationContext::tr("Paper format"), pageSize.name()); + writeProperty("paper-size", PDFToolTranslationContext::tr("Paper size"), paperSizeString); + } + + if (!info->extra.empty()) + { + for (const auto& item : info->extra) + { + QString key = QString::fromLatin1(item.first); + QVariant valueVariant = item.second; + QString value = (valueVariant.type() == QVariant::DateTime) ? valueVariant.toDateTime().toLocalTime().toString(options.outputDateFormat) : valueVariant.toString(); + writeProperty("custom-property", key, value); + } + } + + writeProperty("file-name", PDFToolTranslationContext::tr("File name"), options.document); + writeProperty("file-size", PDFToolTranslationContext::tr("File size"), locale.toString(sourceData.size())); + + pdf::PDFJavaScriptScanner scanner(&document); + writeProperty("javascript", PDFToolTranslationContext::tr("JavaScript"), scanner.hasJavaScript() ? PDFToolTranslationContext::tr("Yes") : PDFToolTranslationContext::tr("No")); + + const pdf::PDFSecurityHandler* securityHandler = document.getStorage().getSecurityHandler(); + const pdf::EncryptionMode mode = securityHandler->getMode(); + QString modeString; + switch (mode) + { + case pdf::EncryptionMode::None: + modeString = PDFToolTranslationContext::tr("None"); + break; + + case pdf::EncryptionMode::Standard: + modeString = PDFToolTranslationContext::tr("Standard"); + break; + + case pdf::EncryptionMode::Custom: + modeString = PDFToolTranslationContext::tr("Custom"); + break; + + default: + Q_ASSERT(false); + break; + } + + QString authorizationMode; + switch (securityHandler->getAuthorizationResult()) + { + case pdf::PDFSecurityHandler::AuthorizationResult::NoAuthorizationRequired: + authorizationMode = PDFToolTranslationContext::tr("No authorization required"); + break; + + case pdf::PDFSecurityHandler::AuthorizationResult::OwnerAuthorized: + authorizationMode = PDFToolTranslationContext::tr("Authorized as owner"); + break; + + case pdf::PDFSecurityHandler::AuthorizationResult::UserAuthorized: + authorizationMode = PDFToolTranslationContext::tr("Authorized as user"); + break; + + default: + Q_ASSERT(false); + break; + } + + writeProperty("encryption", PDFToolTranslationContext::tr("Encryption"), modeString); + writeProperty("authorized-as", PDFToolTranslationContext::tr("Authorization"), authorizationMode); + + if (securityHandler->getAuthorizationResult() != pdf::PDFSecurityHandler::AuthorizationResult::NoAuthorizationRequired) + { + writeProperty("metadata-encrypted", PDFToolTranslationContext::tr("Metadata encrypted"), securityHandler->isMetadataEncrypted() ? PDFToolTranslationContext::tr("Yes") : PDFToolTranslationContext::tr("No")); + writeProperty("version", PDFToolTranslationContext::tr("Version"), locale.toString(securityHandler->getVersion())); + } + + QStringList permissions; + if (securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::PrintLowResolution)) + { + permissions << PDFToolTranslationContext::tr("Print (low resolution)"); + } + if (securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::PrintHighResolution)) + { + permissions << PDFToolTranslationContext::tr("Print"); + } + if (securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::CopyContent)) + { + permissions << PDFToolTranslationContext::tr("Copy content"); + } + if (securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::Accessibility)) + { + permissions << PDFToolTranslationContext::tr("Accessibility"); + } + if (securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::Assemble)) + { + permissions << PDFToolTranslationContext::tr("Page assembling"); + } + if (securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::Modify)) + { + permissions << PDFToolTranslationContext::tr("Modify content"); + } + if (securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::ModifyInteractiveItems)) + { + permissions << PDFToolTranslationContext::tr("Modify interactive items"); + } + if (securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::ModifyFormFields)) + { + permissions << PDFToolTranslationContext::tr("Form filling"); + } + writeProperty("permissions", PDFToolTranslationContext::tr("Permissions"), permissions.join(", ")); + + formatter.endTable(); + + if (options.computeHashes) + { + formatter.endl(); + + formatter.beginTable("hashes", PDFToolTranslationContext::tr("File hashes:")); + + formatter.beginTableHeaderRow("header"); + formatter.writeTableHeaderColumn("algorithm", PDFToolTranslationContext::tr("Algorithm"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("hash", PDFToolTranslationContext::tr("Hash"), Qt::AlignLeft); + formatter.endTableHeaderRow(); + + auto writeHash = [&formatter, &sourceData](QCryptographicHash::Algorithm algorithm, const QString& algorithmName, const QString& algorithmDescription) + { + formatter.beginTableRow(algorithmName); + formatter.writeTableColumn("algorithm", algorithmDescription); + formatter.writeTableColumn("hash", QString::fromLatin1(QCryptographicHash::hash(sourceData, algorithm).toHex()).toUpper()); + formatter.endTableRow(); + }; + + writeHash(QCryptographicHash::Md5, "MD5", PDFToolTranslationContext::tr("MD5")); + writeHash(QCryptographicHash::Sha1, "SHA1", PDFToolTranslationContext::tr("SHA1")); + writeHash(QCryptographicHash::Sha256, "SHA256", PDFToolTranslationContext::tr("SHA256")); + writeHash(QCryptographicHash::Sha384, "SHA384", PDFToolTranslationContext::tr("SHA384")); + writeHash(QCryptographicHash::Sha512, "SHA512", PDFToolTranslationContext::tr("SHA512")); + + formatter.endTable(); + } + + formatter.endDocument(); + PDFConsole::writeText(formatter.getString(), options.outputCodec); + + return ExitSuccess; +} + +PDFToolAbstractApplication::Options PDFToolInfoApplication::getOptionsFlags() const +{ + return ConsoleFormat | OpenDocument | DateFormat | ComputeHashes; +} + +} // namespace pdftool diff --git a/PdfTool/pdftoolinfo.h b/PdfTool/pdftoolinfo.h new file mode 100644 index 0000000..32a17f6 --- /dev/null +++ b/PdfTool/pdftoolinfo.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 PDFTOOLINFO_H +#define PDFTOOLINFO_H + +#include "pdftoolabstractapplication.h" + +namespace pdftool +{ + +class PDFToolInfoApplication : 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 // PDFTOOLINFO_H diff --git a/PdfTool/pdftoolverifysignatures.cpp b/PdfTool/pdftoolverifysignatures.cpp index 4348d87..ecac64c 100644 --- a/PdfTool/pdftoolverifysignatures.cpp +++ b/PdfTool/pdftoolverifysignatures.cpp @@ -165,8 +165,8 @@ int PDFToolVerifySignaturesApplication::execute(const PDFToolOptions& options) formatter.writeTableColumn("common-name", commonName); formatter.writeTableColumn("cert-status", options.verificationOmitCertificateCheck ? PDFToolTranslationContext::tr("Skipped") : signature.getCertificateStatusText()); formatter.writeTableColumn("signature-status", signature.getSignatureStatusText()); - formatter.writeTableColumn("signing-date", signature.getSignatureDate().isValid() ? signature.getSignatureDate().toLocalTime().toString(options.verificationDateFormat) : QString()); - formatter.writeTableColumn("timestamp-date", signature.getTimestampDate().isValid() ? signature.getTimestampDate().toLocalTime().toString(options.verificationDateFormat) : QString()); + formatter.writeTableColumn("signing-date", signature.getSignatureDate().isValid() ? signature.getSignatureDate().toLocalTime().toString(options.outputDateFormat) : QString()); + formatter.writeTableColumn("timestamp-date", signature.getTimestampDate().isValid() ? signature.getTimestampDate().toLocalTime().toString(options.outputDateFormat) : QString()); formatter.writeTableColumn("hash-algorithm", signature.getHashAlgorithms().join(", ").toUpper()); formatter.writeTableColumn("handler", QString::fromLatin1(signature.getSignatureHandler())); formatter.writeTableColumn("whole-signed", signature.hasFlag(pdf::PDFSignatureVerificationResult::Warning_Signature_NotCoveredBytes) ? PDFToolTranslationContext::tr("No") : PDFToolTranslationContext::tr("Yes")); @@ -194,8 +194,8 @@ int PDFToolVerifySignaturesApplication::execute(const PDFToolOptions& options) formatter.writeText("common-name", PDFToolTranslationContext::tr("Signed by: %1").arg(commonName)); formatter.writeText("certificate-status", PDFToolTranslationContext::tr("Certificate status: %1").arg(options.verificationOmitCertificateCheck ? PDFToolTranslationContext::tr("Skipped") : signature.getCertificateStatusText())); formatter.writeText("signature-status", PDFToolTranslationContext::tr("Signature status: %1").arg(signature.getSignatureStatusText())); - formatter.writeText("signing-date", PDFToolTranslationContext::tr("Signing date: %1").arg(signature.getSignatureDate().isValid() ? signature.getSignatureDate().toLocalTime().toString(options.verificationDateFormat) : QString())); - formatter.writeText("timestamp-date", PDFToolTranslationContext::tr("Timestamp date: %1").arg(signature.getTimestampDate().isValid() ? signature.getTimestampDate().toLocalTime().toString(options.verificationDateFormat) : QString())); + formatter.writeText("signing-date", PDFToolTranslationContext::tr("Signing date: %1").arg(signature.getSignatureDate().isValid() ? signature.getSignatureDate().toLocalTime().toString(options.outputDateFormat) : QString())); + formatter.writeText("timestamp-date", PDFToolTranslationContext::tr("Timestamp date: %1").arg(signature.getTimestampDate().isValid() ? signature.getTimestampDate().toLocalTime().toString(options.outputDateFormat) : QString())); formatter.writeText("hash-algorithm", PDFToolTranslationContext::tr("Hash algorithm: %1").arg(signature.getHashAlgorithms().join(", ").toUpper())); formatter.writeText("handler", PDFToolTranslationContext::tr("Handler: %1").arg(QString::fromLatin1(signature.getSignatureHandler()))); formatter.writeText("whole-signed", PDFToolTranslationContext::tr("Is whole document signed: %1").arg(signature.hasFlag(pdf::PDFSignatureVerificationResult::Warning_Signature_NotCoveredBytes) ? PDFToolTranslationContext::tr("No") : PDFToolTranslationContext::tr("Yes"))); @@ -307,7 +307,7 @@ int PDFToolVerifySignaturesApplication::execute(const PDFToolOptions& options) { formatter.beginTableRow("valid-from"); formatter.writeTableColumn("description", PDFToolTranslationContext::tr("Valid from")); - formatter.writeTableColumn("value", notValidBefore.toString(options.verificationDateFormat)); + formatter.writeTableColumn("value", notValidBefore.toString(options.outputDateFormat)); formatter.endTableRow(); } @@ -315,7 +315,7 @@ int PDFToolVerifySignaturesApplication::execute(const PDFToolOptions& options) { formatter.beginTableRow("valid-to"); formatter.writeTableColumn("description", PDFToolTranslationContext::tr("Valid to")); - formatter.writeTableColumn("value", notValidAfter.toString(options.verificationDateFormat)); + formatter.writeTableColumn("value", notValidAfter.toString(options.outputDateFormat)); formatter.endTableRow(); } @@ -392,7 +392,7 @@ int PDFToolVerifySignaturesApplication::execute(const PDFToolOptions& options) PDFToolAbstractApplication::Options PDFToolVerifySignaturesApplication::getOptionsFlags() const { - return PDFToolAbstractApplication::ConsoleFormat | PDFToolAbstractApplication::OpenDocument | PDFToolAbstractApplication::SignatureVerification; + return PDFToolAbstractApplication::ConsoleFormat | PDFToolAbstractApplication::OpenDocument | PDFToolAbstractApplication::SignatureVerification | PDFToolAbstractApplication::DateFormat; } } // namespace pdftool