Commit 402bbe84 authored by Elvis Angelaccio's avatar Elvis Angelaccio
Browse files

Create password-protected archives with Ark

This feature allows Ark to create a password-protected archive from
scratch.
The 7z and rar plugins support both header encryption and entries-only
encryption, while the zip one supports only the latter.

An encrypted archive can be created through the CreateDialog (within Ark) or
through the AddDialog (from e.g. Dolphin).

REVIEW: 120204
FEATURE: 253694
FIXED-IN: 15.08
GUI:
parent 7850a0aa
......@@ -24,6 +24,7 @@
#include "logging.h"
#include "mainwindow.h"
#include "kerfuffle/archive_kerfuffle.h"
#include "kerfuffle/createdialog.h"
#include "kerfuffle/settingspage.h"
#include "part/interface.h"
......@@ -310,17 +311,35 @@ void MainWindow::newArchive()
qCDebug(ARK) << "Supported mimetypes are" << mimeTypes.join(QLatin1String(" "));
QFileDialog dlg(this);
dlg.setMimeTypeFilters(mimeTypes);
QStringList filters = dlg.nameFilters();
filters.sort(Qt::CaseInsensitive);
dlg.setNameFilters(filters);
dlg.setFileMode(QFileDialog::AnyFile);
dlg.setAcceptMode(QFileDialog::AcceptSave);
if (dlg.exec() == QDialog::Accepted) {
QPointer<Kerfuffle::CreateDialog> dialog = new Kerfuffle::CreateDialog(
Q_NULLPTR, // parent
i18n("Create a new Archive"), // caption
QUrl()); // startDir
dialog.data()->show();
dialog.data()->restoreWindowSize();
if (dialog.data()->exec()) {
const QUrl saveFileUrl = dialog.data()->selectedUrls().first();
const QString password = dialog.data()->password();
qCDebug(ARK) << "CreateDialog returned URL:" << saveFileUrl.toString();
qCDebug(ARK) << "CreateDialog returned mime:" << dialog.data()->currentMimeFilter();
m_openArgs.metaData()[QLatin1String( "createNewArchive" )] = QLatin1String( "true" );
openUrl(dlg.selectedUrls().first());
m_openArgs.metaData().remove(QLatin1String( "showExtractDialog" ));
m_openArgs.metaData().remove(QLatin1String( "createNewArchive" ));
m_openArgs.metaData()[QLatin1String("encryptionPassword")] = password;
if (dialog.data()->isHeaderEncryptionChecked()) {
m_openArgs.metaData()[QLatin1String("encryptHeader")] = QLatin1String("true");
}
openUrl(saveFileUrl);
m_openArgs.metaData().remove(QLatin1String("showExtractDialog"));
m_openArgs.metaData().remove(QLatin1String("createNewArchive"));
m_openArgs.metaData().remove(QLatin1String("encryptionPassword"));
m_openArgs.metaData().remove(QLatin1String("encryptHeader"));
}
delete dialog.data();
}
......@@ -7,18 +7,23 @@ set(kerfuffle_SRCS
previewsettingspage.cpp
settingspage.cpp
jobs.cpp
extractiondialog.cpp
adddialog.cpp
queries.cpp
addtoarchive.cpp
cliinterface.cpp
createdialog.cpp
extractiondialog.cpp
adddialog.cpp
queries.cpp
addtoarchive.cpp
cliinterface.cpp
)
kconfig_add_kcfg_files(kerfuffle_SRCS settings.kcfgc)
ki18n_wrap_ui(kerfuffle_SRCS extractiondialog.ui adddialog.ui )
ki18n_wrap_ui(kerfuffle_SRCS extractionsettings.ui)
ki18n_wrap_ui(kerfuffle_SRCS previewsettings.ui)
ki18n_wrap_ui(kerfuffle_SRCS
adddialog.ui
createdialog.ui
extractiondialog.ui
extractionsettings.ui
previewsettings.ui
)
add_library(kerfuffle SHARED ${kerfuffle_SRCS})
generate_export_header(kerfuffle BASE_NAME kerfuffle)
......
......@@ -26,26 +26,18 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "app/logging.h"
#include "adddialog.h"
#include "app/logging.h"
#include "ui_adddialog.h"
#include "kerfuffle/archive_kerfuffle.h"
#include <KFilePlacesModel>
#include <KSharedConfig>
#include <KWindowConfig>
#include <KUrlComboBox>
#include <KMessageBox>
#include <KFileWidget>
#include <QDir>
#include <QFileInfo>
#include <QStandardItemModel>
#include <QPushButton>
#include <QIcon>
#include <QMimeDatabase>
#include <QDebug>
#include <QWindow>
#include <QScreen>
#include <QStandardItemModel>
namespace Kerfuffle
{
......@@ -58,95 +50,25 @@ public:
}
};
AddDialog::AddDialog(const QStringList &itemsToAdd,
QWidget *parent,
AddDialog::AddDialog(QWidget *parent,
const QString &caption,
const QUrl &startDir)
: QDialog(parent, Qt::Dialog)
const QUrl &startDir,
const QStringList &itemsToAdd)
: CreateDialog(parent, caption, startDir)
{
qCDebug(KERFUFFLE) << "AddDialog loaded";
this->setWindowTitle(caption);
QHBoxLayout *hlayout = new QHBoxLayout();
setLayout(hlayout);
fileWidget = new KFileWidget(startDir, this);
hlayout->addWidget(fileWidget);
fileWidget->setMode(KFile::File | KFile::LocalOnly);
fileWidget->setConfirmOverwrite(true);
fileWidget->setOperationMode(KFileWidget::Saving);
connect(fileWidget->okButton(), &QPushButton::clicked, this, &AddDialog::slotOkButtonClicked);
connect(fileWidget, &KFileWidget::accepted, fileWidget, &KFileWidget::accept);
connect(fileWidget, &KFileWidget::accepted, this, &QDialog::accept);
fileWidget->okButton()->show();
fileWidget->cancelButton()->show();
connect(fileWidget->cancelButton(), &QPushButton::clicked, this, &QDialog::reject);
loadConfiguration();
connect(this, &QDialog::accepted, this, &AddDialog::updateDefaultMimeType);
connect(this, &QDialog::finished, this, &AddDialog::slotSaveWindowSize);
// Sidepanel with extra options, disabled for now
/*
m_ui = new AddDialogUI(this);
hlayout->addWidget(m_ui);
m_ui->groupExtraOptions->hide();
m_vlayout->addWidget(m_ui);
setupIconList(itemsToAdd);
*/
// Set up a default name if there's only one file to compress
if (itemsToAdd.size() == 1) {
const QFileInfo fileInfo(itemsToAdd.first());
const QString fileName =
fileInfo.isDir() ? fileInfo.dir().dirName() : fileInfo.baseName();
fileWidget->setSelection(fileName);
}
}
QSize AddDialog::sizeHint() const
{
// Used only when no previous window size has been stored
return QSize(750,450);
}
void AddDialog::loadConfiguration()
{
m_config = KConfigGroup(KSharedConfig::openConfig()->group("AddDialog"));
const QString defaultMimeType = QLatin1String("application/x-compressed-tar");
const QString lastMimeType = m_config.readEntry("LastMimeType", defaultMimeType);
QStringList writeMimeTypes = Kerfuffle::supportedWriteMimeTypes();
// The filters need to be sorted by comment, so create a QMap with
// comment as key (QMaps are always sorted by key) and QMimeType
// as value. Then convert the QMap back to a QStringList. Mimetypes
// with empty comments are discarded.
QMimeDatabase db;
QMap<QString,QMimeType> mimeMap;
foreach (const QString &s, writeMimeTypes) {
QMimeType mime(db.mimeTypeForName(s));
if (!mime.comment().isEmpty()) {
mimeMap[mime.comment()] = mime;
}
}
writeMimeTypes.clear();
QMapIterator<QString,QMimeType> j(mimeMap);
while (j.hasNext()) {
j.next();
writeMimeTypes << j.value().name();
}
if (writeMimeTypes.contains(lastMimeType)) {
fileWidget->setMimeFilter(writeMimeTypes, lastMimeType);
} else {
fileWidget->setMimeFilter(writeMimeTypes, defaultMimeType);
m_fileWidget->setSelection(fileName);
}
}
......@@ -175,48 +97,4 @@ void AddDialog::setupIconList(const QStringList& itemsToAdd)
m_ui->compressList->setModel(listModel);
}
void AddDialog::updateDefaultMimeType()
{
m_config.writeEntry("LastMimeType", fileWidget->currentFilterMimeType().name());
}
QList<QUrl> AddDialog::selectedUrls()
{
return(fileWidget->selectedUrls());
}
QString AddDialog::currentMimeFilter()
{
return(fileWidget->currentMimeFilter());
}
void AddDialog::slotSaveWindowSize()
{
// Save dialog window size
KConfigGroup group(KSharedConfig::openConfig(), "AddDialog");
KWindowConfig::saveWindowSize(windowHandle(), group, KConfigBase::Persistent);
}
void AddDialog::slotOkButtonClicked()
{
// In case the user tries to leave the lineEdit empty:
if (fileWidget->locationEdit()->urls().at(0) == fileWidget->baseUrl().path().left(fileWidget->baseUrl().path().size()-1))
{
KMessageBox::sorry(this, i18n("Please select a filename for the archive."), i18n("No file selected"));
return;
}
// This slot sets the url from text in the lineEdit, asks for overwrite etc, and emits signal accepted
fileWidget->slotOk();
}
void AddDialog::restoreWindowSize()
{
// Restore window size from config file, needs a windowHandle so must be called after show()
KConfigGroup group(KSharedConfig::openConfig(), "AddDialog");
//KWindowConfig::restoreWindowSize(windowHandle(), group);
//KWindowConfig::restoreWindowSize is broken atm., so we need this hack:
const QRect desk = windowHandle()->screen()->geometry();
this->resize(QSize(group.readEntry(QString::fromLatin1("Width %1").arg(desk.width()), windowHandle()->size().width()),
group.readEntry(QString::fromLatin1("Height %1").arg(desk.height()), windowHandle()->size().height())));
}
}
......@@ -29,44 +29,25 @@
#ifndef ADDDIALOG_H
#define ADDDIALOG_H
#include "createdialog.h"
#include "kerfuffle_export.h"
#include <KConfigGroup>
#include <KFileWidget>
#include <QDialog>
namespace Kerfuffle
{
class KERFUFFLE_EXPORT AddDialog : public QDialog
class KERFUFFLE_EXPORT AddDialog : public CreateDialog
{
Q_OBJECT
public:
AddDialog(const QStringList &itemsToAdd,
QWidget *parent,
AddDialog(QWidget *parent,
const QString &caption,
const QUrl &startDir);
QSize sizeHint() const Q_DECL_OVERRIDE;
QList<QUrl> selectedUrls();
QString currentMimeFilter();
const QUrl &startDir,
const QStringList &itemsToAdd);
private:
class AddDialogUI *m_ui;
KConfigGroup m_config;
KFileWidget *fileWidget;
void loadConfiguration();
void setupIconList(const QStringList& itemsToAdd);
public slots:
void restoreWindowSize();
private slots:
void updateDefaultMimeType();
void slotSaveWindowSize();
void slotOkButtonClicked();
};
}
......
<ui version="4.0" >
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddDialog</class>
<widget class="QWidget" name="AddDialog" >
<property name="geometry" >
<widget class="QWidget" name="AddDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>565</width>
<height>113</height>
<height>181</height>
</rect>
</property>
<property name="minimumSize" >
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" >
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupCompressFiles" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="MinimumExpanding" >
<widget class="QGroupBox" name="groupCompressFiles">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title" >
<property name="title">
<string>Files/Folders to Compress</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" >
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QListView" name="compressList" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Expanding" >
<widget class="QListView" name="compressList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="autoFillBackground" >
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShape" >
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth" >
<property name="lineWidth">
<number>0</number>
</property>
<property name="editTriggers" >
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode" >
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="iconSize" >
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="textElideMode" >
<property name="textElideMode">
<enum>Qt::ElideMiddle</enum>
</property>
<property name="verticalScrollMode" >
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="resizeMode" >
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
<property name="viewMode" >
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes" >
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupExtraOptions" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title" >
<string>Extra Compression Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" >
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>Easter egg for the developers:
This is where future versions will have extra compression options for the various compression interfaces.</string>
</property>
<property name="wordWrap" >
<property name="uniformItemSizes">
<bool>true</bool>
</property>
</widget>
......
......@@ -75,15 +75,25 @@ void AddToArchive::setMimeType(const QString & mimeType)
m_mimeType = mimeType;
}
void AddToArchive::setPassword(const QString &password)
{
m_password = password;
}
void AddToArchive::setHeaderEncryptionEnabled(bool enabled)
{
m_enableHeaderEncryption = enabled;
}
bool AddToArchive::showAddDialog()
{
qCDebug(KERFUFFLE) << "Opening add dialog";
QPointer<Kerfuffle::AddDialog> dialog = new Kerfuffle::AddDialog(
m_inputs, // itemsToAdd
Q_NULLPTR, // parent
i18n("Compress to Archive"), // caption
QUrl::fromLocalFile(m_firstPath)); // startDir
QUrl::fromLocalFile(m_firstPath), // startDir
m_inputs); // itemsToAdd
dialog.data()->show();
dialog.data()->restoreWindowSize();
......@@ -94,6 +104,8 @@ bool AddToArchive::showAddDialog()
qCDebug(KERFUFFLE) << "AddDialog returned mime:" << dialog.data()->currentMimeFilter();
setFilename(dialog.data()->selectedUrls().at(0));
setMimeType(dialog.data()->currentMimeFilter());
setPassword(dialog.data()->password());
setHeaderEncryptionEnabled(dialog.data()->isHeaderEncryptionChecked());
}
delete dialog.data();
......@@ -175,6 +187,11 @@ void AddToArchive::slotStartJob()
return;
}
if (!m_password.isEmpty()) {
archive->setPassword(m_password);
archive->enableHeaderEncryption(m_enableHeaderEncryption);
}
if (m_changeToFirstPath) {
if (m_firstPath.isEmpty()) {
qCWarning(KERFUFFLE) << "Weird, this should not happen. no firstpath defined. aborting";
......
......@@ -66,6 +66,8 @@ public slots:
void setAutoFilenameSuffix(const QString& suffix);
void setFilename(const QUrl &path);
void setMimeType(const QString & mimeType);
void setPassword(const QString &password);
void setHeaderEncryptionEnabled(bool enabled);
void start() Q_DECL_OVERRIDE;
private slots:
......@@ -78,8 +80,10 @@ private:
QString m_autoFilenameSuffix;
QString m_firstPath;
QString m_mimeType;
QString m_password;
QStringList m_inputs;
bool m_changeToFirstPath;
bool m_enableHeaderEncryption;
};
}
......
......@@ -287,6 +287,11 @@ void Archive::setPassword(const QString &password)
m_iface->setPassword(password);
}
void Archive::enableHeaderEncryption(bool enable)
{
m_iface->setHeaderEncryptionEnabled(enable);
}
QStringList supportedMimeTypes()
{
const QLatin1String constraint("(exist Library)");
......@@ -341,4 +346,50 @@ QStringList supportedWriteMimeTypes()
return supported;
}
QSet<QString> supportedEncryptEntriesMimeTypes()
{
const QLatin1String constraint("(exist Library) and ([X-KDE-Kerfuffle-EncryptEntries] == true)");
const QLatin1String basePartService("Kerfuffle/Plugin");
const KService::List offers = KServiceTypeTrader::self()->query(basePartService, constraint);
QSet<QString> supported;
foreach (const KService::Ptr& service, offers) {
QStringList mimeTypes = service->serviceTypes();
foreach (const QString& mimeType, mimeTypes) {
if (mimeType != basePartService) {
supported.insert(mimeType);
}
}
}
qCDebug(KERFUFFLE) << "Returning supported mimetypes" << supported;
return supported;
}
QSet<QString> supportedEncryptHeaderMimeTypes()
{
const QLatin1String constraint("(exist Library) and ([X-KDE-Kerfuffle-EncryptHeader] == true)");
const QLatin1String basePartService("Kerfuffle/Plugin");
const KService::List offers = KServiceTypeTrader::self()->query(basePartService, constraint);
QSet<QString> supported;
foreach (const KService::Ptr& service, offers) {
QStringList mimeTypes = service->serviceTypes();
foreach (const QString& mimeType, mimeTypes) {
if (mimeType != basePartService) {
supported.insert(mimeType);
}
}
}
qCDebug(KERFUFFLE) << "Returning supported mimetypes" << supported;
return supported;
}
} // namespace Kerfuffle
......@@ -125,6 +125,7 @@ public:
bool isPasswordProtected();
void setPassword(const QString &password);
void enableHeaderEncryption(bool enable);
private slots:
void onListFinished(KJob*);
......@@ -145,6 +146,8 @@ private:
KERFUFFLE_EXPORT QStringList supportedMimeTypes();
KERFUFFLE_EXPORT QStringList supportedWriteMimeTypes();
KERFUFFLE_EXPORT QSet<QString> supportedEncryptEntriesMimeTypes();
KERFUFFLE_EXPORT QSet<QString> supportedEncryptHeaderMimeTypes();
} // namespace Kerfuffle
......
......@@ -66,6 +66,11 @@ void ReadOnlyArchiveInterface::setPassword(const QString &password)
m_password = password;
}
void ReadOnlyArchiveInterface::setHeaderEncryptionEnabled(bool enabled)
{
m_isHeaderEncryptionEnabled = enabled;
}
QString ReadOnlyArchiveInterface::password() const
{