Qt: Export profiles
authorRoland Knall <rknall@gmail.com>
Wed, 24 Jul 2019 21:39:25 +0000 (23:39 +0200)
committerRoland Knall <rknall@gmail.com>
Thu, 25 Jul 2019 18:00:24 +0000 (18:00 +0000)
Allow for the export of profiles. The currently selected profile may be
selected, as well as all user-defined profiles

Fixes: Bug, where invalid data has been written into the profiles not
present inside the original file

Change-Id: I7c6310920a1f3a064cfcedc7774b742ff01c9b9e
Reviewed-on: https://code.wireshark.org/review/34077
Petri-Dish: Roland Knall <rknall@gmail.com>
Tested-by: Petri Dish Buildbot
Reviewed-by: Roland Knall <rknall@gmail.com>
ui/qt/main_status_bar.cpp
ui/qt/models/profile_model.cpp
ui/qt/models/profile_model.h
ui/qt/profile_dialog.cpp
ui/qt/profile_dialog.h
ui/qt/utils/wireshark_zip_helper.cpp
ui/qt/utils/wireshark_zip_helper.h

index a0f4d3a4625ce9a507a22c628dec86ad89454978..0933ce44b5ffef0b6ada895a9a44aa8b894715f0 100644 (file)
@@ -604,6 +604,17 @@ void MainStatusBar::showProfileMenu(const QPoint &global_pos, Qt::MouseButton bu
         action->setProperty("dialog_action_", (int)ProfileDialog::ImportDirProfile);
         connect(action, SIGNAL(triggered()), this, SLOT(manageProfile()));
         ctx_menu_.addMenu(importMenu);
+
+        QMenu * exportMenu = new QMenu(tr("Export"));
+        action = exportMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS" selected entry"));
+        action->setProperty("dialog_action_", (int)ProfileDialog::ExportSingleProfile);
+        action->setEnabled(enable_edit);
+        connect(action, SIGNAL(triggered()), this, SLOT(manageProfile()));
+        action = exportMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS" all user profiles"));
+        action->setProperty("dialog_action_", (int)ProfileDialog::ExportAllProfiles);
+        connect(action, SIGNAL(triggered()), this, SLOT(manageProfile()));
+        ctx_menu_.addMenu(exportMenu);
+
 #else
         action = ctx_menu_.addAction(tr("Import" UTF8_HORIZONTAL_ELLIPSIS));
         action->setProperty("dialog_action_", (int)ProfileDialog::ImportDirProfile);
index 98bb90f953226e1a2a8f6d5bb40c6b78362b5bde..750efb00e5970bcd7e1893834a2b2d3c937a4394 100644 (file)
@@ -700,6 +700,54 @@ QFileInfoList ProfileModel::filterProfilePath(QString path, QFileInfoList ent, b
 }
 
 #ifdef HAVE_MINIZIP
+QStringList ProfileModel::exportFileList(QModelIndexList items)
+{
+    QStringList result;
+
+    foreach(QModelIndex idx, items)
+    {
+        profile_def * prof = guard(idx.row());
+        if ( prof->is_global || QString(prof->name).compare(DEFAULT_PROFILE) == 0 )
+            continue;
+
+        if ( ! idx.data(ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION).toBool() )
+            continue;
+
+        QString path = idx.data(ProfileModel::DATA_PATH).toString();
+        QDir temp(path);
+        temp.setSorting(QDir::Name);
+        temp.setFilter(QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot);
+        QFileInfoList entries = temp.entryInfoList();
+        foreach ( QFileInfo fi, entries )
+            result << fi.absoluteFilePath();
+    }
+
+    return result;
+}
+
+bool ProfileModel::exportProfiles(QString filename, QModelIndexList items, QString *err)
+{
+    if ( changesPending() )
+    {
+        if ( err )
+            err->append(tr("Exporting profiles while changes are pending is not allowed"));
+        return false;
+    }
+
+    QStringList files = exportFileList(items);
+    if ( files.count() == 0 )
+    {
+        if ( err )
+            err->append((tr("No profiles found to export")));
+        return false;
+    }
+
+    if ( WireSharkZipHelper::zip(filename, files, QString(get_profiles_dir()) + QDir::separator() ) )
+        return true;
+
+    return false;
+}
+
 /* This check runs BEFORE the file has been unzipped! */
 bool ProfileModel::acceptFile(QString fileName, int fileSize)
 {
index 1e7246a02b5c77932d6e7195ccc52c3327bdbcf9..a095211292ecd373461ff6da246e30be8d5d8fe5 100644 (file)
@@ -93,6 +93,8 @@ public:
     bool changesPending() const;
 
 #ifdef HAVE_MINIZIP
+    QStringList exportFileList(QModelIndexList items);
+    bool exportProfiles(QString filename, QModelIndexList items, QString * err = Q_NULLPTR);
     int importProfilesFromZip(QString filename, int *skippedCnt = Q_NULLPTR);
 #endif
     int importProfilesFromDir(QString filename, int *skippedCnt = Q_NULLPTR, bool fromZip = false);
index fb1926094a46ea40b5ef8acb6799dab1921261b5..84baace72be8a850047b9e698efd77de24c4dd80 100644 (file)
 #include <QStandardPaths>
 #include <QKeyEvent>
 #include <QMenu>
+#include <QMessageBox>
+
+#define PROFILE_EXPORT_PROPERTY "export"
+#define PROFILE_EXPORT_ALL "all"
+#define PROFILE_EXPORT_SELECTED "selected"
 
 ProfileDialog::ProfileDialog(QWidget *parent) :
     GeometryStateDialog(parent),
@@ -69,15 +74,26 @@ ProfileDialog::ProfileDialog(QWidget *parent) :
     pd_ui_->lblInfo->setAttribute(Qt::WA_MacSmallSize, true);
 #endif
 
-    import_button_ = pd_ui_->buttonBox->addButton(tr("Import"), QDialogButtonBox::ActionRole);
+    import_button_ = pd_ui_->buttonBox->addButton(tr("Import", "noun"), QDialogButtonBox::ActionRole);
 
 #ifdef HAVE_MINIZIP
+    export_button_ = pd_ui_->buttonBox->addButton(tr("Export", "noun"), QDialogButtonBox::ActionRole);
+
     QMenu * importMenu = new QMenu(import_button_);
     QAction * entry = importMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS " from Zip"));
     connect( entry, &QAction::triggered, this, &ProfileDialog::importFromZip);
     entry = importMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS " from Directory"));
     connect( entry, &QAction::triggered, this, &ProfileDialog::importFromDirectory);
     import_button_->setMenu(importMenu);
+
+    QMenu * exportMenu = new QMenu(export_button_);
+    export_selected_entry_ = exportMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS " selected entry"));
+    export_selected_entry_->setProperty(PROFILE_EXPORT_PROPERTY, PROFILE_EXPORT_SELECTED);
+    connect( export_selected_entry_, &QAction::triggered, this, &ProfileDialog::exportProfiles);
+    entry = exportMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS " all user profiles"));
+    entry->setProperty(PROFILE_EXPORT_PROPERTY, PROFILE_EXPORT_ALL);
+    connect( entry, &QAction::triggered, this, &ProfileDialog::exportProfiles);
+    export_button_->setMenu(exportMenu);
 #else
     connect( import_button_, &QPushButton::clicked, this, &ProfileDialog::importFromDirectory);
 #endif
@@ -152,6 +168,16 @@ int ProfileDialog::execAction(ProfileDialog::ProfileAction profile_action)
     case ImportDirProfile:
         importFromDirectory();
         break;
+    case ExportSingleProfile:
+#ifdef HAVE_MINIZIP
+        exportProfiles();
+#endif
+        break;
+    case ExportAllProfiles:
+#ifdef HAVE_MINIZIP
+        exportProfiles(true);
+#endif
+        break;
     case EditCurrentProfile:
         item = pd_ui_->profileTreeView->currentIndex();
         if (item.isValid()) {
@@ -378,6 +404,42 @@ void ProfileDialog::filterChanged(const QString &text)
 }
 
 #ifdef HAVE_MINIZIP
+void ProfileDialog::exportProfiles(bool exportAll)
+{
+    QAction * action = qobject_cast<QAction *>(sender());
+    if ( action && action->property(PROFILE_EXPORT_PROPERTY).isValid() )
+        exportAll = action->property(PROFILE_EXPORT_PROPERTY).toString().compare(PROFILE_EXPORT_ALL) == 0;
+
+    QModelIndexList items;
+
+    if ( ! exportAll && pd_ui_->profileTreeView->currentIndex().isValid() )
+        items << sort_model_->mapToSource(pd_ui_->profileTreeView->currentIndex());
+    else if ( exportAll )
+    {
+        for ( int cnt = 0; cnt < sort_model_->rowCount(); cnt++ )
+        {
+            QModelIndex idx = sort_model_->index(cnt, ProfileModel::COL_NAME);
+            if ( ! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! idx.data(ProfileModel::DATA_IS_DEFAULT).toBool() )
+            {
+                items << sort_model_->mapToSource(idx);
+            }
+        }
+    }
+    if ( items.count() == 0 )
+    {
+        QMessageBox::warning(this, tr("Exporting profiles"), tr("No profiles found for export"));
+        return;
+    }
+
+    QString zipFile = QFileDialog::getSaveFileName(this, tr("Select zip file for export"), QString(), tr("Zip File (*.zip)"));
+
+    QString err;
+    if ( model_->exportProfiles(zipFile, items, &err) )
+        QMessageBox::information(this, tr("Exporting profiles"), tr("%Ln profile(s) have been exported", "", items.count()));
+    else
+        QMessageBox::warning(this, tr("Exporting profiles"), QString("%1\n\n%2: %3").arg(tr("An error has occured while exporting profiles")).arg("Error").arg(err));
+}
+
 void ProfileDialog::importFromZip()
 {
     QString zipFile = QFileDialog::getOpenFileName(this, tr("Select zip file for import"), QString(), tr("Zip File (*.zip)"));
index 439f36ed6dc64999ae5db88da85fb4dc62d39e4d..a1afcbb02b0ac05e721a8c15dc0b159b8f8b3f63 100644 (file)
@@ -28,7 +28,10 @@ class ProfileDialog : public GeometryStateDialog
     Q_OBJECT
 
 public:
-    enum ProfileAction { ShowProfiles, NewProfile, ImportZipProfile, ImportDirProfile, EditCurrentProfile, DeleteCurrentProfile };
+    enum ProfileAction {
+        ShowProfiles, NewProfile, ImportZipProfile, ImportDirProfile,
+        ExportSingleProfile, ExportAllProfiles, EditCurrentProfile, DeleteCurrentProfile
+    };
 
     explicit ProfileDialog(QWidget *parent = Q_NULLPTR);
     ~ProfileDialog();
@@ -51,6 +54,10 @@ private:
     Ui::ProfileDialog *pd_ui_;
     QPushButton *ok_button_;
     QPushButton *import_button_;
+#ifdef HAVE_MINIZIP
+    QPushButton *export_button_;
+    QAction *export_selected_entry_;
+#endif
     ProfileModel *model_;
     ProfileSortModel *sort_model_;
 
@@ -60,6 +67,7 @@ private:
 private slots:
     void currentItemChanged();
 #ifdef HAVE_MINIZIP
+    void exportProfiles(bool exportAll = false);
     void importFromZip();
 #endif
     void importFromDirectory();
index 78dc9949fe532435012024f31e886d58bd48732e..033da04da6acb9a7b6bb267c9537b672a1ccc358 100644 (file)
 #include <iosfwd>
 #include <iostream>
 #include <minizip/unzip.h>
+#include <minizip/zip.h>
 
 #include "epan/prefs.h"
 #include "wsutil/file_util.h"
 
 #include <QDataStream>
 #include <QDir>
+#include <QFile>
 #include <QFileInfo>
+#include <QDateTime>
 
 bool WireSharkZipHelper::unzip(QString zipFile, QString directory, bool (*fileCheck)(QString, int))
 {
@@ -75,12 +78,8 @@ bool WireSharkZipHelper::unzip(QString zipFile, QString directory, bool (*fileCh
                         QFile file(filePath);
                         if ( file.open(QIODevice::WriteOnly) )
                         {
-                            QDataStream out(&file);
                             while ( ( err = unzReadCurrentFile(uf, buf, IO_BUF_SIZE) ) != UNZ_EOF )
-                            {
-                                QByteArray buffer(buf, err);
-                                out << buffer;
-                            }
+                                file.write(buf, err);
 
                             file.close();
                         }
@@ -107,6 +106,134 @@ bool WireSharkZipHelper::unzip(QString zipFile, QString directory, bool (*fileCh
     return files > 0 ? true : false;
 }
 
+#ifndef UINT32_MAX
+#define UINT32_MAX  (0xffffffff)
+#endif
+
+/* The following methods are being taken from https://github.com/nmoinvaz/minizip/blob/1.2/minishared.c */
+int invalid_date(const struct tm *ptm)
+{
+#define datevalue_in_range(min, max, value) ((min) <= (value) && (value) <= (max))
+    return (!datevalue_in_range(0, 207, ptm->tm_year) ||
+            !datevalue_in_range(0, 11, ptm->tm_mon) ||
+            !datevalue_in_range(1, 31, ptm->tm_mday) ||
+            !datevalue_in_range(0, 23, ptm->tm_hour) ||
+            !datevalue_in_range(0, 59, ptm->tm_min) ||
+            !datevalue_in_range(0, 59, ptm->tm_sec));
+#undef datevalue_in_range
+}
+
+uint32_t tm_to_dosdate(const struct tm *ptm)
+{
+    struct tm fixed_tm;
+
+    /* Years supported:
+    * [00, 79]      (assumed to be between 2000 and 2079)
+    * [80, 207]     (assumed to be between 1980 and 2107, typical output of old
+                     software that does 'year-1900' to get a double digit year)
+    * [1980, 2107]  (due to the date format limitations, only years between 1980 and 2107 can be stored.)
+    */
+
+    memcpy(&fixed_tm, ptm, sizeof(struct tm));
+    if (fixed_tm.tm_year >= 1980) /* range [1980, 2107] */
+        fixed_tm.tm_year -= 1980;
+    else if (fixed_tm.tm_year >= 80) /* range [80, 99] */
+        fixed_tm.tm_year -= 80;
+    else /* range [00, 79] */
+        fixed_tm.tm_year += 20;
+
+    if (invalid_date(ptm))
+        return 0;
+
+    return (uint32_t)(((fixed_tm.tm_mday) + (32 * (fixed_tm.tm_mon + 1)) + (512 * fixed_tm.tm_year)) << 16) |
+        ((fixed_tm.tm_sec / 2) + (32 * fixed_tm.tm_min) + (2048 * (uint32_t)fixed_tm.tm_hour));
+}
+
+unsigned long qDateToDosDate(QDateTime time)
+{
+    time_t rawtime = time.toTime_t();
+    struct tm * timeinfo;
+
+    timeinfo = localtime(&rawtime);
+    timeinfo->tm_year = time.date().year() - 1900;
+    timeinfo->tm_mon = time.date().month() - 1;
+    timeinfo->tm_mday = time.date().day();
+
+    mktime(timeinfo);
+
+    return tm_to_dosdate(timeinfo);
+}
+
+void WireSharkZipHelper::addFileToZip(zipFile zf, QString filepath, QString fileInZip)
+{
+    QFileInfo fi(filepath);
+    zip_fileinfo zi;
+    int err = ZIP_OK;
+
+    memset(&zi, 0, sizeof(zi));
+
+    QDateTime fTime = fi.lastModified();
+    zi.dosDate = qDateToDosDate(fTime);
+
+    QFile fh(filepath);
+    /* Checks if a large file block has to be written */
+    bool isLarge = ( fh.size() > UINT32_MAX );
+
+    err = zipOpenNewFileInZip3_64(zf, fileInZip.toUtf8().constData(), &zi,
+                                  Q_NULLPTR, 0, Q_NULLPTR, 0, Q_NULLPTR, Z_DEFLATED, 9 , 0,
+                                  -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                  Q_NULLPTR, 0, static_cast<int>(isLarge));
+
+    if ( err != ZIP_OK )
+        return;
+
+    if ( fh.open(QIODevice::ReadOnly) )
+    {
+        char * buf = static_cast<char *>(malloc(IO_BUF_SIZE));
+        while ( ! fh.atEnd() && err == ZIP_OK )
+        {
+            qint64 bytesIn = fh.read(buf, IO_BUF_SIZE);
+            if ( bytesIn > 0 && bytesIn <= IO_BUF_SIZE)
+            {
+                err = zipWriteInFileInZip(zf, buf, (unsigned int) bytesIn);
+            }
+        }
+        fh.close();
+    }
+
+    zipCloseFileInZip(zf);
+}
+
+bool WireSharkZipHelper::zip(QString fileName, QStringList files, QString relativeTo)
+{
+
+    QFileInfo fi(fileName);
+    if ( fi.exists() )
+        QFile::remove(fileName);
+
+    zipFile zf = zipOpen(fileName.toUtf8().constData(), APPEND_STATUS_CREATE);
+    if ( zf == Q_NULLPTR )
+        return false;
+
+    for ( int cnt = 0; cnt < files.count(); cnt++ )
+    {
+        QFileInfo sf(files.at(cnt));
+        QString fileInZip = sf.absoluteFilePath();
+        fileInZip.replace(relativeTo, "");
+        /* Windows cannot open zip files, if the filenames starts with a separator */
+        while ( fileInZip.length() > 0 && fileInZip.startsWith(QDir::separator()) )
+            fileInZip = fileInZip.right(fileInZip.length() - 1);
+
+        WireSharkZipHelper::addFileToZip(zf, sf.absoluteFilePath(), fileInZip);
+
+    }
+
+    if ( zipClose(zf, Q_NULLPTR) )
+        return false;
+
+    return true;
+}
+
 #endif
 
 /*
index 8b232dc9660463bc4e40712490bc7c4cb94efced..bbf17c0709114b8f2d6f64a1e352d30353feb1ce 100644 (file)
 
 #ifdef HAVE_MINIZIP
 
+#include "minizip/zip.h"
+
 class WireSharkZipHelper
 {
 public:
+    static bool zip(QString zipFile, QStringList files, QString relativeTo = QString());
     static bool unzip(QString zipFile, QString directory, bool (*fileCheck)(QString fileName, int fileSize) );
+
+protected:
+    static void addFileToZip(zipFile zf, QString filepath, QString fileInZip);
+
 };
 
 #endif