Close #88: Export to multiple files

Extend the ExportFile dialog by a checkbox to
switch between "all-in-one-file" or every item in a separate
file. It is only visible if more than one file is selected.
Activating the checkbox will:
 - Change the file-input into a directory-input and cut off the
   file name
 - Disabling all formats that are intended for multiple items.
De-activating the checkbox will:
 - Change the directory-input back into a file-input
   and append the previously erased file name again.
 - Disabling all export-formats that are only useful for single items.
This commit is contained in:
Christian Hohnstaedt 2024-09-13 14:44:51 +02:00
parent 71f28ef0e3
commit 46c1b78577
9 changed files with 120 additions and 36 deletions

View File

@ -718,10 +718,6 @@ int db_base::exportFlags(const QModelIndexList &indexes) const
int disabled_flags = 0;
foreach(const QModelIndex &idx, indexes)
disabled_flags |= exportFlags(idx);
if (indexes.size() > 1)
disabled_flags |= F_SINGLE;
else
disabled_flags |= F_MULTI;
return disabled_flags;
}

View File

@ -384,5 +384,9 @@ QString fingerprint(const QByteArray &data, const EVP_MD *type)
void update_workingdir(const QString &file)
{
Settings["workingdir"] = QFileInfo(file).absolutePath();
QFileInfo fi(file);
if (fi.isDir())
Settings["workingdir"] = fi.absoluteFilePath();
else
Settings["workingdir"] = fi.absolutePath();
}

View File

@ -62,16 +62,16 @@ void pki_export::init_elements()
elements = QList<pki_export*> {
new pki_export( 1, x509, "crt", "PEM", F_PEM | F_USUAL | F_SINGLE | F_CLIPBOARD, tr("PEM Text format with headers")),
new pki_export( 3, x509, "pem", tr("PEM selected"), F_PEM | F_MULTI, tr("Concatenated list of all selected items in one PEM text file")),
new pki_export( 3, x509, "pem", tr("PEM selected"), F_PEM | F_MULTI, tr("Concatenated list of all selected certificates in one PEM text file")),
new pki_export( 2, x509, "pem", tr("PEM chain"), F_PEM | F_USUAL | F_CHAIN | F_SINGLE | F_CLIPBOARD, tr("Concatenated text format of the complete certificate chain in one PEM file")),
new pki_export( 4, x509, "ovpn","OpenVPN", F_PEM | F_OVPN | F_CHAIN | F_PRIVATE | F_SINGLE, tr("The complete certificate chain and the private key of the selected certificate with tags usable in OpenVPN configuration files")),
new pki_export( 4, x509, "ovpn", "OpenVPN", F_PEM | F_OVPN | F_CHAIN | F_PRIVATE | F_SINGLE, tr("The complete certificate chain and the private key of the selected certificate with tags usable in OpenVPN configuration files")),
new pki_export( 6, x509, "pem", tr("PEM + key"), F_PEM | F_PRIVATE| F_SINGLE | F_CLIPBOARD, tr("Concatenation of the certificate and the unencrypted private key in one PEM file")),
new pki_export( 7, x509, "pem", "PEM + PKCS#8", F_PEM | F_PKCS8 | F_PRIVATE | F_CRYPT | F_CLIPBOARD, tr("Concatenation of the certificate and the encrypted private key in PKCS#8 format in one file")),
new pki_export(40, x509, "pem", "All unusable", F_PEM | F_UNUSABLE, tr("Concatenation of all expired or revoked certificates in one PEM file")),
new pki_export( 7, x509, "pem", "PEM + PKCS#8", F_PEM | F_PKCS8 | F_PRIVATE | F_CRYPT | F_SINGLE | F_CLIPBOARD, tr("Concatenation of the certificate and the encrypted private key in PKCS#8 format in one file")),
new pki_export(40, x509, "pem", tr("All unusable"), F_PEM | F_UNUSABLE, tr("Concatenation of all expired or revoked certificates in one PEM file")),
new pki_export( 8, x509, "p7b", "PKCS #7", F_PKCS7 | F_USUAL | F_SINGLE, tr("PKCS#7 encoded single certificate")),
new pki_export(10, x509, "p7b", "PKCS #7", F_PKCS7 | F_USUAL | F_MULTI, tr("All selected certificates encoded in one PKCS#7 file")),
new pki_export(12, x509, "p7b", tr("PKCS #7 chain"), F_PKCS7 | F_USUAL | F_CHAIN | F_SINGLE, tr("PKCS#7 encoded complete certificate chain")),
new pki_export(41, x509, "p7b", "PKCS #7 Unusable", F_PKCS7 | F_UNUSABLE, tr("PKCS#7 encoded collection of all expired or revoked certificates")),
new pki_export(41, x509, "p7b", tr("PKCS #7 unusable"), F_PKCS7 | F_UNUSABLE, tr("PKCS#7 encoded collection of all expired or revoked certificates")),
new pki_export(13, x509, "cer", "DER", F_DER | F_SINGLE, tr("Binary DER encoded certificate")),
new pki_export(14, x509, "pfx", tr("PKCS #12 chain"),F_PKCS12 | F_USUAL | F_CHAIN | F_CRYPT | F_PRIVATE | F_SINGLE, tr("The complete certificate chain and the private key as encrypted PKCS#12 file")),
new pki_export(15, x509, "pfx", tr("PKCS #12"), F_PKCS12 | F_USUAL | F_CRYPT | F_PRIVATE | F_SINGLE, tr("The certificate and the private key as encrypted PKCS#12 file")),

View File

@ -187,14 +187,14 @@ void test_main::exportFormat()
idx = certs->index(certs->getByName("Root CA"));
list << idx;
QCOMPARE(certs->exportFlags(idx) , F_CHAIN | F_PRIVATE);
QCOMPARE(certs->exportFlags(list) , F_CHAIN | F_PRIVATE | F_MULTI);
QCOMPARE(certs->exportFlags(list) , F_CHAIN | F_PRIVATE);
// Inter CA 1: All export options permitted
// Together with "Root CA" in "list": No chain, private or single
idx = certs->index(certs->getByName("Inter CA 1"));
list << idx;
QCOMPARE(certs->exportFlags(idx) , 0);
QCOMPARE(certs->exportFlags(list) , F_CHAIN | F_PRIVATE | F_SINGLE);
QCOMPARE(certs->exportFlags(list) , F_CHAIN | F_PRIVATE);
// Endentity has no private key and id no CA
idx = certs->index(certs->getByName("Endentity"));

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>589</width>
<height>343</height>
<height>366</height>
</rect>
</property>
<property name="windowTitle">
@ -22,6 +22,7 @@
<font>
<family>Arial</family>
<pointsize>14</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
<underline>false</underline>
@ -70,7 +71,7 @@
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="TextLabel2">
<widget class="QLabel" name="descriptionLabel">
<property name="text">
<string>Name</string>
</property>
@ -84,7 +85,7 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="TextLabel14">
<widget class="QLabel" name="filenameLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Minimum">
<horstretch>0</horstretch>
@ -114,6 +115,13 @@
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="separateFiles">
<property name="text">
<string>Each Item in a separate file</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">

View File

@ -23,9 +23,8 @@ ExportDialog::ExportDialog(QWidget *w, const QString &title,
const QString &filt, const QModelIndexList &indexes,
const QPixmap &img, QList<const pki_export*> types,
const QString &help_ctx)
: QDialog(w ? w : mainwin)
: QDialog(w ? w : mainwin), filter(filt), savedFile(), alltypes(types)
{
QList<const pki_export*> usual, normal;
QString fname = "selected_items";
setupUi(this);
setWindowTitle(XCA_TITLE);
@ -35,24 +34,37 @@ ExportDialog::ExportDialog(QWidget *w, const QString &title,
description->setText(pki->getIntName());
fname = pki->getUnderlinedName();
}
separateFiles->hide();
setupExportFormat(F_MULTI);
} else {
// Plural form not required for < 2 items
// Will only be called for 2 or more items
description->setText(tr("%n selected item(s)", "", indexes.size()));
setupExportFormat(F_SINGLE);
}
description->setReadOnly(true);
image->setPixmap(img);
label->setText(title);
mainwin->helpdlg->register_ctxhelp_button(this, help_ctx);
QString fn = Settings["workingdir"] +
fname + "." + types[0]->extension;
filename->setText(nativeSeparator(fn));
fname = Settings["workingdir"] + fname + "." + types[0]->extension;
filename->setText(nativeSeparator(fname));
filter = tr("All files ( * )") + ";;" + filter;
filenameLabelOrig = filenameLabel->text();
}
filter = tr("All files ( * )") + ";;" + filt;
foreach(const pki_export *t, types) {
void ExportDialog::setupExportFormat(int disable_flag)
{
QList<const pki_export*> usual, normal;
for (const pki_export *t : alltypes) {
if (t->flags & disable_flag)
continue;
if (t->flags & F_USUAL)
usual << t;
else
normal << t;
}
exportFormat->clear();
foreach(const pki_export *t, usual + normal) {
exportFormat->addItem(QString("%1 (*.%2)").
arg(t->desc).arg(t->extension), QVariant(t->id));
@ -71,10 +83,15 @@ ExportDialog::~ExportDialog()
void ExportDialog::on_fileBut_clicked()
{
QString s = QFileDialog::getSaveFileName(this, QString(),
filename->text(), filter, NULL,
QFileDialog::DontConfirmOverwrite);
QString s;
if (separateFiles->isChecked()) {
s = QFileDialog::getExistingDirectory(this, QString(), filename->text(),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
} else {
s = QFileDialog::getSaveFileName(this, QString(),
filename->text(), filter, NULL,
QFileDialog::DontConfirmOverwrite);
}
if (!s.isEmpty())
filename->setText(nativeSeparator(s));
}
@ -99,7 +116,8 @@ void ExportDialog::on_exportFormat_activated(int selected)
bool ExportDialog::mayWriteFile(const QString &fname)
{
if (QFile::exists(fname)) {
QFileInfo fi(fname);
if (fi.exists() && !fi.isDir()) {
xcaWarningBox msg(NULL,
tr("The file: '%1' already exists!").arg(fname));
msg.addButton(QMessageBox::Ok, tr("Overwrite"));
@ -145,3 +163,21 @@ void ExportDialog::on_exportFormat_highlighted(int index)
infoBox->setText(x->help);
pemComment->setEnabled(x->flags & F_PEM);
}
void ExportDialog::on_separateFiles_clicked(bool checked)
{
if (checked) {
filenameLabel->setText(tr("Directory"));
QFileInfo fi(filename->text());
savedFile = fi.fileName();
filename->setText(nativeSeparator(fi.path()));
setupExportFormat(F_MULTI);
} else {
filenameLabel->setText(filenameLabelOrig);
if (!savedFile.isEmpty()) {
QString completefile = filename->text() + "/" + savedFile;
filename->setText(nativeSeparator(completefile));
}
setupExportFormat(F_SINGLE);
}
}

View File

@ -20,7 +20,8 @@ class ExportDialog: public QDialog, public Ui::ExportDialog
Q_OBJECT
protected:
QString filter{};
QString filter{}, savedFile{}, filenameLabelOrig{};
QList<const pki_export*> alltypes;
public:
ExportDialog(QWidget *w, const QString &title, const QString &filt,
@ -30,9 +31,11 @@ class ExportDialog: public QDialog, public Ui::ExportDialog
~ExportDialog();
static bool mayWriteFile(const QString &fname);
const pki_export *export_type(int idx = -1) const;
void setupExportFormat(int disable_flag);
public slots:
void on_fileBut_clicked();
void on_separateFiles_clicked(bool checked);
void on_exportFormat_activated(int);
void on_exportFormat_highlighted(int index);
void accept();

View File

@ -26,6 +26,8 @@
#include "ExportDialog.h"
#include "ImportMulti.h"
#include "lib/load_obj.h"
#include "lib/pass_info.h"
#include "lib/PwDialogCore.h"
#include "ui_ItemProperties.h"
@ -480,24 +482,57 @@ void XcaTreeView::changeView()
show();
}
void XcaTreeView::exportItems(const QModelIndexList &indexes,
const QString &filename, const pki_export *xport)
{
XFile file(filename);
if (xport->match_all(F_PRIVATE))
file.open_key();
else
file.open_write();
basemodel->exportItems(indexes, xport, file);
}
void XcaTreeView::exportItems(const QModelIndexList &indexes)
{
if (!basemodel || indexes.empty())
return;
ExportDialog *dlg = exportDialog(indexes);
if (dlg && dlg->exec()) {
try {
QString fname = dlg->filename->text();
const pki_export *xport = dlg->export_type();
XFile file(dlg->filename->text());
if (xport->match_all(F_PRIVATE))
file.open_key();
else
file.open_write();
if (dlg->separateFiles->isChecked()) {
Passwd pass;
if (xport->match_all(F_CRYPT)) {
// Plural form not required for < 2 items
// Will only be called for 2 or more items
pass_info p(tr("Export Password"),
tr("Please enter the password to encrypt all %n "
"exported private key(s) in:\n%1", "",
indexes.size()).arg(fname),
this);
basemodel->exportItems(indexes, xport, file);
// Ask for an encryption password once
if (PwDialogCore::execute(&p, &pass, true) != 1)
return;
}
for (QModelIndex idx : indexes) {
pki_base *pki = db_base::fromIndex(idx);
// Will be cleared by PwDialogCore
PwDialogCore::cmdline_passwd = pass;
QString fn = pki->get_dump_filename(fname,
QString(".%1").arg(xport->extension));
exportItems(QModelIndexList() << idx, fn, xport);
}
PwDialogCore::cmdline_passwd.cleanse();
} else {
exportItems(indexes, fname, xport);
}
} catch (errorEx &err) {
XCA_ERROR(err);
}

View File

@ -68,6 +68,8 @@ class XcaTreeView: public QTreeView
virtual void exportItems(const QModelIndexList &indexes);
virtual void load_default(load_base *load);
virtual ExportDialog *exportDialog(const QModelIndexList &indexes);
virtual void exportItems(const QModelIndexList &indexes,
const QString &filename, const pki_export *xport);
public slots:
void changeView();