Issue #40: Sanitize document

This commit is contained in:
Jakub Melka
2023-02-19 18:36:46 +01:00
parent 4a603c80c0
commit 361ee247e5
19 changed files with 1089 additions and 243 deletions

View File

@@ -44,6 +44,9 @@ add_library(Pdf4QtViewer SHARED
pdfviewermainwindow.ui
pdfviewermainwindowlite.ui
pdfviewersettingsdialog.ui
pdfsanitizedocumentdialog.ui
pdfsanitizedocumentdialog.cpp
pdfsanitizedocumentdialog.h
pdf4qtviewer.qrc
)

View File

@@ -32,6 +32,7 @@
#include "pdfundoredomanager.h"
#include "pdfrendertoimagesdialog.h"
#include "pdfoptimizedocumentdialog.h"
#include "pdfsanitizedocumentdialog.h"
#include "pdfviewersettingsdialog.h"
#include "pdfaboutdialog.h"
#include "pdfrenderingerrorswidget.h"
@@ -447,6 +448,10 @@ void PDFProgramController::initialize(Features features,
{
connect(action, &QAction::triggered, this, &PDFProgramController::onActionOptimizeTriggered);
}
if (QAction* action = m_actionManager->getAction(PDFActionManager::Sanitize))
{
connect(action, &QAction::triggered, this, &PDFProgramController::onActionSanitizeTriggered);
}
if (QAction* action = m_actionManager->getAction(PDFActionManager::Encryption))
{
connect(action, &QAction::triggered, this, &PDFProgramController::onActionEncryptionTriggered);
@@ -1173,6 +1178,18 @@ void PDFProgramController::onActionOptimizeTriggered()
}
}
void PDFProgramController::onActionSanitizeTriggered()
{
PDFSanitizeDocumentDialog dialog(m_pdfDocument.data(), m_mainWindow);
if (dialog.exec() == QDialog::Accepted)
{
pdf::PDFDocumentPointer pointer(new pdf::PDFDocument(dialog.takeSanitizedDocument()));
pdf::PDFModifiedDocument document(qMove(pointer), m_optionalContentActivity, pdf::PDFModifiedDocument::Reset);
onDocumentModified(qMove(document));
}
}
void PDFProgramController::onActionEncryptionTriggered()
{
auto queryPassword = [this](bool* ok)
@@ -1492,6 +1509,7 @@ void PDFProgramController::updateActionsAvailability()
m_actionManager->setEnabled(PDFActionManager::Print, hasValidDocument && canPrint);
m_actionManager->setEnabled(PDFActionManager::RenderToImages, hasValidDocument && canPrint);
m_actionManager->setEnabled(PDFActionManager::Optimize, hasValidDocument);
m_actionManager->setEnabled(PDFActionManager::Sanitize, hasValidDocument);
m_actionManager->setEnabled(PDFActionManager::Encryption, hasValidDocument);
m_actionManager->setEnabled(PDFActionManager::Save, hasValidDocument);
m_actionManager->setEnabled(PDFActionManager::SaveAs, hasValidDocument);

View File

@@ -105,6 +105,7 @@ public:
SendByMail,
RenderToImages,
Optimize,
Sanitize,
Encryption,
FitPage,
FitWidth,
@@ -327,6 +328,7 @@ private:
void onActionSendByEMailTriggered();
void onActionRenderToImagesTriggered();
void onActionOptimizeTriggered();
void onActionSanitizeTriggered();
void onActionEncryptionTriggered();
void onActionFitPageTriggered();
void onActionFitWidthTriggered();

View File

@@ -0,0 +1,164 @@
// Copyright (C) 2023 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT 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
// with the written consent of the copyright owner, any later version.
//
// PDF4QT 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 PDF4QT. If not, see <https://www.gnu.org/licenses/>.
#include "pdfsanitizedocumentdialog.h"
#include "ui_pdfsanitizedocumentdialog.h"
#include "pdfwidgetutils.h"
#include "pdfdocumentwriter.h"
#include "pdfdbgheap.h"
#include <QCheckBox>
#include <QPushButton>
#include <QElapsedTimer>
#include <QtConcurrent/QtConcurrent>
namespace pdfviewer
{
PDFSanitizeDocumentDialog::PDFSanitizeDocumentDialog(const pdf::PDFDocument* document, QWidget* parent) :
QDialog(parent),
ui(new Ui::PDFSanitizeDocumentDialog),
m_document(document),
m_sanitizer(pdf::PDFDocumentSanitizer::All, nullptr),
m_sanitizeButton(nullptr),
m_sanitizationInProgress(false),
m_wasSanitized(false)
{
ui->setupUi(this);
auto addCheckBox = [this](QString text, pdf::PDFDocumentSanitizer::SanitizationFlag flag)
{
QCheckBox* checkBox = new QCheckBox(text, this);
checkBox->setChecked(m_sanitizer.getFlags().testFlag(flag));
connect(checkBox, &QCheckBox::clicked, this, [this, flag](bool checked) { m_sanitizer.setFlags(m_sanitizer.getFlags().setFlag(flag, checked)); });
ui->groupBoxLayout->addWidget(checkBox);
};
addCheckBox(tr("Remove document info"), pdf::PDFDocumentSanitizer::DocumentInfo);
addCheckBox(tr("Remove all metadata"), pdf::PDFDocumentSanitizer::Metadata);
addCheckBox(tr("Remove outline (bookmarks)"), pdf::PDFDocumentSanitizer::Bookmarks);
addCheckBox(tr("Remove file attachments"), pdf::PDFDocumentSanitizer::FileAttachments);
addCheckBox(tr("Remove embedded search index"), pdf::PDFDocumentSanitizer::EmbeddedSearchIndex);
addCheckBox(tr("Remove comments and other markup annotations"), pdf::PDFDocumentSanitizer::MarkupAnnotations);
addCheckBox(tr("Remove page thumbnails"), pdf::PDFDocumentSanitizer::PageThumbnails);
m_sanitizeButton = ui->buttonBox->addButton(tr("Sanitize"), QDialogButtonBox::ActionRole);
connect(m_sanitizeButton, &QPushButton::clicked, this, &PDFSanitizeDocumentDialog::onSanitizeButtonClicked);
connect(&m_sanitizer, &pdf::PDFDocumentSanitizer::sanitizationStarted, this, &PDFSanitizeDocumentDialog::onSanitizationStarted);
connect(&m_sanitizer, &pdf::PDFDocumentSanitizer::sanitizationProgress, this, &PDFSanitizeDocumentDialog::onSanitizationProgress);
connect(&m_sanitizer, &pdf::PDFDocumentSanitizer::sanitizationFinished, this, &PDFSanitizeDocumentDialog::onSanitizationFinished);
connect(this, &PDFSanitizeDocumentDialog::displaySanitizationInfo, this, &PDFSanitizeDocumentDialog::onDisplaySanitizationInfo);
pdf::PDFWidgetUtils::scaleWidget(this, QSize(640, 380));
updateUi();
pdf::PDFWidgetUtils::style(this);
}
PDFSanitizeDocumentDialog::~PDFSanitizeDocumentDialog()
{
Q_ASSERT(!m_sanitizationInProgress);
Q_ASSERT(!m_future.isRunning());
delete ui;
}
void PDFSanitizeDocumentDialog::sanitize()
{
QElapsedTimer timer;
timer.start();
m_sanitizer.setDocument(m_document);
m_sanitizer.sanitize();
m_sanitizedDocument = m_sanitizer.takeSanitizedDocument();
qreal msecsElapsed = timer.nsecsElapsed() / 1000000.0;
timer.invalidate();
m_sanitizationInfo.msecsElapsed = msecsElapsed;
m_sanitizationInfo.bytesBeforeSanitization = pdf::PDFDocumentWriter::getDocumentFileSize(m_document);
m_sanitizationInfo.bytesAfterSanitization = pdf::PDFDocumentWriter::getDocumentFileSize(&m_sanitizedDocument);
Q_EMIT displaySanitizationInfo();
}
void PDFSanitizeDocumentDialog::onSanitizeButtonClicked()
{
Q_ASSERT(!m_sanitizationInProgress);
Q_ASSERT(!m_future.isRunning());
m_sanitizationInProgress = true;
m_future = QtConcurrent::run([this]() { sanitize(); });
updateUi();
}
void PDFSanitizeDocumentDialog::onSanitizationStarted()
{
Q_ASSERT(m_sanitizationInProgress);
ui->logTextEdit->setPlainText(tr("Sanitization started!"));
}
void PDFSanitizeDocumentDialog::onSanitizationProgress(QString progressText)
{
Q_ASSERT(m_sanitizationInProgress);
ui->logTextEdit->setPlainText(QString("%1\n%2").arg(ui->logTextEdit->toPlainText(), progressText));
}
void PDFSanitizeDocumentDialog::onSanitizationFinished()
{
ui->logTextEdit->setPlainText(QString("%1\n%2").arg(ui->logTextEdit->toPlainText(), tr("Sanitization finished!")));
m_future.waitForFinished();
m_sanitizationInProgress = false;
m_wasSanitized = true;
updateUi();
}
void PDFSanitizeDocumentDialog::onDisplaySanitizationInfo()
{
QStringList texts;
texts << tr("Sanitized in %1 msecs").arg(m_sanitizationInfo.msecsElapsed);
if (m_sanitizationInfo.bytesBeforeSanitization != -1 &&
m_sanitizationInfo.bytesAfterSanitization != -1)
{
texts << tr("Bytes before sanitization: %1").arg(m_sanitizationInfo.bytesBeforeSanitization);
texts << tr("Bytes after sanitization: %1").arg(m_sanitizationInfo.bytesAfterSanitization);
texts << tr("Bytes saved by sanitization: %1").arg(m_sanitizationInfo.bytesBeforeSanitization - m_sanitizationInfo.bytesAfterSanitization);
qreal ratio = 100.0;
if (m_sanitizationInfo.bytesBeforeSanitization > 0)
{
ratio = 100.0 * qreal(m_sanitizationInfo.bytesAfterSanitization) / qreal(m_sanitizationInfo.bytesBeforeSanitization);
}
texts << tr("Compression ratio: %1 %").arg(ratio);
}
ui->logTextEdit->setPlainText(QString("%1\n%2").arg(ui->logTextEdit->toPlainText(), texts.join("\n")));
}
void PDFSanitizeDocumentDialog::updateUi()
{
for (QCheckBox* checkBox : findChildren<QCheckBox*>(QString(), Qt::FindChildrenRecursively))
{
checkBox->setEnabled(!m_sanitizationInProgress);
}
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_wasSanitized && !m_sanitizationInProgress);
ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(!m_sanitizationInProgress);
m_sanitizeButton->setEnabled(!m_sanitizationInProgress);
}
} // namespace pdfviewer

View File

@@ -0,0 +1,77 @@
// Copyright (C) 2023 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT 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
// with the written consent of the copyright owner, any later version.
//
// PDF4QT 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 PDF4QT. If not, see <https://www.gnu.org/licenses/>.
#ifndef PDFSANITIZEDOCUMENTDIALOG_H
#define PDFSANITIZEDOCUMENTDIALOG_H
#include "pdfdocumentsanitizer.h"
#include <QDialog>
#include <QFuture>
namespace Ui
{
class PDFSanitizeDocumentDialog;
}
namespace pdfviewer
{
class PDFSanitizeDocumentDialog : public QDialog
{
Q_OBJECT
public:
explicit PDFSanitizeDocumentDialog(const pdf::PDFDocument* document, QWidget* parent);
virtual ~PDFSanitizeDocumentDialog() override;
pdf::PDFDocument takeSanitizedDocument() { return qMove(m_sanitizedDocument); }
signals:
void displaySanitizationInfo();
private:
void sanitize();
void onSanitizeButtonClicked();
void onSanitizationStarted();
void onSanitizationProgress(QString progressText);
void onSanitizationFinished();
void onDisplaySanitizationInfo();
void updateUi();
struct SanitizationInfo
{
qreal msecsElapsed = 0.0;
qint64 bytesBeforeSanitization = -1;
qint64 bytesAfterSanitization = -1;
};
Ui::PDFSanitizeDocumentDialog* ui;
const pdf::PDFDocument* m_document;
pdf::PDFDocumentSanitizer m_sanitizer;
QPushButton* m_sanitizeButton;
bool m_sanitizationInProgress;
bool m_wasSanitized;
QFuture<void> m_future;
pdf::PDFDocument m_sanitizedDocument;
SanitizationInfo m_sanitizationInfo;
};
} // namespace pdfviewer
#endif // PDFSANITIZEDOCUMENTDIALOG_H

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PDFSanitizeDocumentDialog</class>
<widget class="QDialog" name="PDFSanitizeDocumentDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>741</width>
<height>530</height>
</rect>
</property>
<property name="windowTitle">
<string>Document sanitization</string>
</property>
<layout class="QVBoxLayout" name="dialogLayout">
<item>
<widget class="QGroupBox" name="sanitizationSettingsGroupBox">
<property name="title">
<string>Sanitization Settings</string>
</property>
<layout class="QVBoxLayout" name="groupBoxLayout"/>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="logTextEdit">
<property name="undoRedoEnabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PDFSanitizeDocumentDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PDFSanitizeDocumentDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -24,7 +24,6 @@
#include "pdfviewersettingsdialog.h"
#include "pdfdocumentpropertiesdialog.h"
#include "pdfrendertoimagesdialog.h"
#include "pdfoptimizedocumentdialog.h"
#include "pdfdbgheap.h"
#include "pdfdocumentreader.h"
@@ -161,6 +160,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
m_actionManager->setAction(PDFActionManager::SendByMail, ui->actionSend_by_E_Mail);
m_actionManager->setAction(PDFActionManager::RenderToImages, ui->actionRender_to_Images);
m_actionManager->setAction(PDFActionManager::Optimize, ui->actionOptimize);
m_actionManager->setAction(PDFActionManager::Sanitize, ui->actionSanitize);
m_actionManager->setAction(PDFActionManager::Encryption, ui->actionEncryption);
m_actionManager->setAction(PDFActionManager::FitPage, ui->actionFitPage);
m_actionManager->setAction(PDFActionManager::FitWidth, ui->actionFitWidth);

View File

@@ -20,7 +20,7 @@
<x>0</x>
<y>0</y>
<width>770</width>
<height>37</height>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -142,6 +142,7 @@
<addaction name="separator"/>
<addaction name="actionEncryption"/>
<addaction name="actionOptimize"/>
<addaction name="actionSanitize"/>
<addaction name="separator"/>
</widget>
<widget class="QMenu" name="menuInsert">
@@ -635,6 +636,9 @@
<property name="text">
<string>Optimize...</string>
</property>
<property name="statusTip">
<string>Optimizes document to reduce file size.</string>
</property>
</action>
<action name="actionSave_As">
<property name="icon">
@@ -924,6 +928,14 @@
<string>Certificates...</string>
</property>
</action>
<action name="actionSanitize">
<property name="text">
<string>Sanitize...</string>
</property>
<property name="toolTip">
<string>Sanitize document to remove sensitive information.</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>

View File

@@ -24,7 +24,6 @@
#include "pdfviewersettingsdialog.h"
#include "pdfdocumentpropertiesdialog.h"
#include "pdfrendertoimagesdialog.h"
#include "pdfoptimizedocumentdialog.h"
#include "pdfdbgheap.h"
#include "pdfdocumentreader.h"