/* about_dialog.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #include "about_dialog.h" #include #include "main_application.h" #include #include #include #ifdef HAVE_LIBSMI #include #endif #include #include #ifdef HAVE_LUA #include #endif #include "ui/alert_box.h" #include "ui/last_open_dir.h" #include "ui/help_url.h" #include #include "file.h" #include "wsutil/file_util.h" #include "wsutil/tempfile.h" #include "wsutil/plugins.h" #include "wsutil/version_info.h" #include "ui/capture_globals.h" #include "extcap.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include AuthorListModel::AuthorListModel(QObject * parent) : AStringListListModel(parent) { QFile f_authors; f_authors.setFileName(":/about/authors.csv"); f_authors.open(QFile::ReadOnly | QFile::Text); QTextStream ReadFile_authors(&f_authors); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) ReadFile_authors.setEncoding(QStringConverter::Utf8); #else ReadFile_authors.setCodec("UTF-8"); #endif while (!ReadFile_authors.atEnd()) { QString line = ReadFile_authors.readLine(); #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList entry = line.split(",", Qt::SkipEmptyParts); #else QStringList entry = QStringList() << line.section(',', 0, 0) << line.section(',', 1, 1); #endif if (entry.size() == 2) { appendRow(entry); } } f_authors.close(); } AuthorListModel::~AuthorListModel() { } QStringList AuthorListModel::headerColumns() const { return QStringList() << tr("Name") << tr("Email"); } static void plugins_add_description(const char *name, const char *version, const char *types, const char *filename, void *user_data) { QList *plugin_data = (QList *)user_data; QStringList plugin_row = QStringList() << name << version << types << filename; *plugin_data << plugin_row; } PluginListModel::PluginListModel(QObject * parent) : AStringListListModel(parent) { QList plugin_data; #ifdef HAVE_PLUGINS plugins_get_descriptions(plugins_add_description, &plugin_data); #endif #ifdef HAVE_LUA wslua_plugins_get_descriptions(plugins_add_description, &plugin_data); #endif extcap_get_descriptions(plugins_add_description, &plugin_data); typeNames_ << QString(""); foreach(QStringList row, plugin_data) { QString type_name = row.at(2); typeNames_ << type_name; appendRow(row); } typeNames_.sort(); typeNames_.removeDuplicates(); } QStringList PluginListModel::typeNames() const { return typeNames_; } QStringList PluginListModel::headerColumns() const { return QStringList() << tr("Name") << tr("Version") << tr("Type") << tr("Path"); } ShortcutListModel::ShortcutListModel(QObject * parent): AStringListListModel(parent) { QMap > shortcuts; // name -> (shortcut, description) foreach (const QWidget *child, mainApp->mainWindow()->findChildren()) { // Recent items look funny here. if (child->objectName().compare("menuOpenRecentCaptureFile") == 0) continue; foreach (const QAction *action, child->actions()) { if (!action->shortcut().isEmpty()) { QString name = action->text(); name.replace('&', ""); shortcuts[name] = QPair(action->shortcut().toString(QKeySequence::NativeText), action->toolTip()); } } } QStringList names = shortcuts.keys(); names.sort(); foreach (const QString &name, names) { QStringList row; row << shortcuts[name].first << name << shortcuts[name].second; appendRow(row); } } QStringList ShortcutListModel::headerColumns() const { return QStringList() << tr("Shortcut") << tr("Name") << tr("Description"); } FolderListModel::FolderListModel(QObject * parent): AStringListListModel(parent) { /* "file open" */ appendRow(QStringList() << tr("\"File\" dialogs") << get_last_open_dir() << tr("capture files")); /* temp */ appendRow(QStringList() << tr("Temp") << (global_capture_opts.temp_dir && global_capture_opts.temp_dir[0] ? global_capture_opts.temp_dir : g_get_tmp_dir()) << tr("untitled capture files")); /* pers conf */ appendRow(QStringList() << tr("Personal configuration") << gchar_free_to_qstring(get_persconffile_path("", FALSE)) << tr("dfilters, preferences, ethers, …")); /* global conf */ QString dirPath = get_datafile_dir(); if (! dirPath.isEmpty()) { appendRow (QStringList() << tr("Global configuration") << dirPath << tr("dfilters, preferences, manuf, …")); } /* system */ appendRow(QStringList() << tr("System") << get_systemfile_dir() << tr("ethers, ipxnets")); /* program */ appendRow(QStringList() << tr("Program") << get_progfile_dir() << tr("program files")); #ifdef HAVE_PLUGINS /* pers plugins */ appendRow(QStringList() << tr("Personal Plugins") << get_plugins_pers_dir_with_version() << tr("binary plugins")); /* global plugins */ appendRow(QStringList() << tr("Global Plugins") << get_plugins_dir_with_version() << tr("binary plugins")); #endif #ifdef HAVE_LUA /* pers plugins */ appendRow(QStringList() << tr("Personal Lua Plugins") << get_plugins_pers_dir() << tr("Lua scripts")); /* global plugins */ appendRow(QStringList() << tr("Global Lua Plugins") << get_plugins_dir() << tr("Lua scripts")); #endif /* Extcap */ appendRow(QStringList() << tr("Personal Extcap path") << QString(get_extcap_pers_dir()) << tr("external capture (extcap) plugins")); appendRow(QStringList() << tr("Global Extcap path") << QString(get_extcap_dir()) << tr("external capture (extcap) plugins")); #ifdef HAVE_MAXMINDDB /* MaxMind DB */ QStringList maxMindDbPaths = QString(maxmind_db_get_paths()).split(G_SEARCHPATH_SEPARATOR_S); foreach(QString path, maxMindDbPaths) appendRow(QStringList() << tr("MaxMind DB path") << path.trimmed() << tr("MaxMind DB database search path")); #endif #ifdef HAVE_LIBSMI /* SMI MIBs/PIBs */ char *default_mib_path = oid_get_default_mib_path(); QStringList smiPaths = QString(default_mib_path).split(G_SEARCHPATH_SEPARATOR_S); g_free(default_mib_path); foreach(QString path, smiPaths) appendRow(QStringList() << tr("MIB/PIB path") << path.trimmed() << tr("SMI MIB/PIB search path")); #endif #ifdef Q_OS_MAC /* Mac Extras */ QString extras_path = mainApp->applicationDirPath() + "/../Resources/Extras"; appendRow(QStringList() << tr("macOS Extras") << QDir::cleanPath(extras_path) << tr("Extra macOS packages")); #endif } QStringList FolderListModel::headerColumns() const { return QStringList() << tr("Name") << tr("Location") << tr("Typical Files"); } // To do: // - Tweak and enhance ui... AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); QFile f_acknowledgements; QFile f_license; AuthorListModel * authorModel = new AuthorListModel(this); AStringListListSortFilterProxyModel * proxyAuthorModel = new AStringListListSortFilterProxyModel(this); proxyAuthorModel->setSourceModel(authorModel); proxyAuthorModel->setFilterCaseSensitivity(Qt::CaseInsensitive); proxyAuthorModel->setColumnToFilter(0); proxyAuthorModel->setColumnToFilter(1); ui->tblAuthors->setModel(proxyAuthorModel); ui->tblAuthors->setRootIsDecorated(false); ui->tblAuthors->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->tblAuthors, &QTreeView::customContextMenuRequested, this, &AboutDialog::handleCopyMenu); connect(ui->searchAuthors, &QLineEdit::textChanged, proxyAuthorModel, &AStringListListSortFilterProxyModel::setFilter); /* Wireshark tab */ updateWiresharkText(); ui->pte_wireshark->setFrameStyle(QFrame::NoFrame); ui->pte_wireshark->viewport()->setAutoFillBackground(false); /* Check if it is a dev release... (VERSION_MINOR is odd in dev release) */ #if VERSION_MINOR & 1 ui->label_logo->setPixmap(QPixmap(":/about/wssplash_dev.png")); #endif /* Folders */ FolderListModel * folderModel = new FolderListModel(this); AStringListListSortFilterProxyModel * folderProxyModel = new AStringListListSortFilterProxyModel(this); folderProxyModel->setSourceModel(folderModel); folderProxyModel->setColumnToFilter(1); folderProxyModel->setFilterType(AStringListListSortFilterProxyModel::FilterByStart); AStringListListUrlProxyModel * folderDisplayModel = new AStringListListUrlProxyModel(this); folderDisplayModel->setSourceModel(folderProxyModel); folderDisplayModel->setUrlColumn(1); ui->tblFolders->setModel(folderDisplayModel); ui->tblFolders->setRootIsDecorated(false); ui->tblFolders->setItemDelegateForColumn(1, new UrlLinkDelegate(this)); ui->tblFolders->setContextMenuPolicy(Qt::CustomContextMenu); ui->tblFolders->setTextElideMode(Qt::ElideMiddle); ui->tblFolders->setSortingEnabled(true); ui->tblFolders->sortByColumn(0, Qt::AscendingOrder); connect(ui->tblFolders, &QTreeView::customContextMenuRequested, this, &AboutDialog::handleCopyMenu); connect(ui->searchFolders, &QLineEdit::textChanged, folderProxyModel, &AStringListListSortFilterProxyModel::setFilter); connect(ui->tblFolders, &QTreeView::doubleClicked, this, &AboutDialog::urlDoubleClicked); /* Plugins */ ui->label_no_plugins->hide(); PluginListModel * pluginModel = new PluginListModel(this); AStringListListSortFilterProxyModel * pluginFilterModel = new AStringListListSortFilterProxyModel(this); pluginFilterModel->setSourceModel(pluginModel); pluginFilterModel->setColumnToFilter(0); AStringListListSortFilterProxyModel * pluginTypeModel = new AStringListListSortFilterProxyModel(this); pluginTypeModel->setSourceModel(pluginFilterModel); pluginTypeModel->setColumnToFilter(2); ui->tblPlugins->setModel(pluginTypeModel); ui->tblPlugins->setRootIsDecorated(false); UrlLinkDelegate *plugin_delegate = new UrlLinkDelegate(this); script_pattern = QString("\\.(lua|py)$"); plugin_delegate->setColCheck(3, script_pattern); ui->tblPlugins->setItemDelegateForColumn(3, plugin_delegate); ui->cmbType->addItems(pluginModel->typeNames()); ui->tblPlugins->setContextMenuPolicy(Qt::CustomContextMenu); ui->tblPlugins->setTextElideMode(Qt::ElideMiddle); ui->tblPlugins->setSortingEnabled(true); ui->tblPlugins->sortByColumn(0, Qt::AscendingOrder); connect(ui->tblPlugins, &QTreeView::customContextMenuRequested, this, &AboutDialog::handleCopyMenu); connect(ui->searchPlugins, &QLineEdit::textChanged, pluginFilterModel, &AStringListListSortFilterProxyModel::setFilter); connect(ui->cmbType, &QComboBox::currentTextChanged, pluginTypeModel, &AStringListListSortFilterProxyModel::setFilter); if (ui->tblPlugins->model()->rowCount() < 1) { foreach (QWidget *w, ui->tab_plugins->findChildren()) { w->hide(); } ui->label_no_plugins->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); ui->label_no_plugins->setEnabled(false); ui->label_no_plugins->show(); } /* Shortcuts */ ShortcutListModel * shortcutModel = new ShortcutListModel(this); AStringListListSortFilterProxyModel * shortcutProxyModel = new AStringListListSortFilterProxyModel(this); shortcutProxyModel->setSourceModel(shortcutModel); shortcutProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); shortcutProxyModel->setColumnToFilter(1); shortcutProxyModel->setColumnToFilter(2); ui->tblShortcuts->setModel(shortcutProxyModel); ui->tblShortcuts->setRootIsDecorated(false); ui->tblShortcuts->setContextMenuPolicy(Qt::CustomContextMenu); ui->tblShortcuts->setSortingEnabled(true); ui->tblShortcuts->sortByColumn(1, Qt::AscendingOrder); connect(ui->tblShortcuts, &QTreeView::customContextMenuRequested, this, &AboutDialog::handleCopyMenu); connect(ui->searchShortcuts, &QLineEdit::textChanged, shortcutProxyModel, &AStringListListSortFilterProxyModel::setFilter); /* Acknowledgements */ f_acknowledgements.setFileName(":/about/Acknowledgements.md"); f_acknowledgements.open(QFile::ReadOnly | QFile::Text); QTextStream ReadFile_acks(&f_acknowledgements); /* QTextBrowser markdown support added in 5.14. */ #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QTextBrowser *textBrowserAcks = new QTextBrowser(); textBrowserAcks->setMarkdown(ReadFile_acks.readAll()); textBrowserAcks->setReadOnly(true); textBrowserAcks->setOpenExternalLinks(true); textBrowserAcks->moveCursor(QTextCursor::Start); ui->ackVerticalLayout->addWidget(textBrowserAcks); #else QPlainTextEdit *pte = new QPlainTextEdit(); pte->setPlainText(ReadFile_acks.readAll()); pte->setReadOnly(true); pte->moveCursor(QTextCursor::Start); ui->ackVerticalLayout->addWidget(pte); #endif /* License */ f_license.setFileName(":/about/gpl-2.0-standalone.html"); f_license.open(QFile::ReadOnly | QFile::Text); QTextStream ReadFile_license(&f_license); ui->textBrowserLicense->setHtml(ReadFile_license.readAll()); ui->textBrowserLicense->moveCursor(QTextCursor::Start); } AboutDialog::~AboutDialog() { delete ui; } bool AboutDialog::event(QEvent *event) { switch (event->type()) { case QEvent::ApplicationPaletteChange: updateWiresharkText(); break; default: break; } return QDialog::event(event); } void AboutDialog::showEvent(QShowEvent * event) { int one_em = fontMetrics().height(); // Authors: Names slightly narrower than emails. QAbstractItemModel *model = ui->tblAuthors->model(); int column_count = model->columnCount(); if (column_count) { ui->tblAuthors->setColumnWidth(0, (ui->tblAuthors->parentWidget()->width() / column_count) - one_em); } // Folders: First and last to contents. ui->tblFolders->resizeColumnToContents(0); ui->tblFolders->resizeColumnToContents(2); ui->tblFolders->setColumnWidth(1, ui->tblFolders->parentWidget()->width() - (ui->tblFolders->columnWidth(0) + ui->tblFolders->columnWidth(2))); // Plugins: All but the last to contents. model = ui->tblPlugins->model(); for (int col = 0; model && col < model->columnCount() - 1; col++) { ui->tblPlugins->resizeColumnToContents(col); } // Contents + 2 em-widths ui->tblShortcuts->resizeColumnToContents(0); ui->tblShortcuts->setColumnWidth(0, ui->tblShortcuts->columnWidth(0) + (one_em * 2)); ui->tblShortcuts->setColumnWidth(1, one_em * 12); ui->tblShortcuts->resizeColumnToContents(2); QDialog::showEvent(event); } void AboutDialog::updateWiresharkText() { QString vcs_version_info_str = get_ws_vcs_version_info(); QString copyright_info_str = get_copyright_info(); QString license_info_str = get_license_info(); QString comp_info_str = gstring_free_to_qbytearray(get_compiled_version_info(gather_wireshark_qt_compiled_info)); QString runtime_info_str = gstring_free_to_qbytearray(get_runtime_version_info(gather_wireshark_runtime_info)); QString message = ColorUtils::themeLinkStyle(); /* Construct the message string */ message += "

Version " + html_escape(vcs_version_info_str) + ".

\n"; message += "

" + html_escape(copyright_info_str) + "

\n"; message += "

" + html_escape(license_info_str) + "

\n"; message += "

" + html_escape(comp_info_str) + "

\n"; message += "

" + html_escape(runtime_info_str) + "

\n"; message += "

Check the man page and www.wireshark.org " "for more information.

\n"; ui->pte_wireshark->setHtml(message); /* Save the info for the clipboard copy */ clipboardInfo = ""; clipboardInfo += "Version " + vcs_version_info_str + ".\n\n"; /* XXX: GCC 12.1 has a bogus stringop-overread warning using the Qt * conversions from QByteArray to QString at -O2 and higher due to * computing a branch that will never be taken. */ #if WS_IS_AT_LEAST_GNUC_VERSION(12,1) DIAG_OFF(stringop-overread) #endif clipboardInfo += gstring_free_to_qbytearray(get_compiled_version_info(gather_wireshark_qt_compiled_info)) + "\n"; clipboardInfo += gstring_free_to_qbytearray(get_runtime_version_info(gather_wireshark_runtime_info)) + "\n"; #if WS_IS_AT_LEAST_GNUC_VERSION(12,1) DIAG_ON(stringop-overread) #endif } void AboutDialog::on_copyToClipboard_clicked() { QClipboard * clipBoard = QApplication::clipboard(); clipBoard->setText(clipboardInfo); } void AboutDialog::urlDoubleClicked(const QModelIndex &idx) { if (idx.column() != 1) { return; } QTreeView * table = qobject_cast(sender()); if (! table) return; QString urlText = table->model()->data(idx).toString(); if (urlText.isEmpty()) return; if (! QDir(urlText).exists()) { if (QMessageBox::question(this, tr("The directory does not exist"), QString(tr("Should the directory %1 be created?").arg(urlText))) == QMessageBox::Yes) { if (! QDir().mkpath(urlText)) { QMessageBox::warning(this, tr("The directory could not be created"), QString(tr("The directory %1 could not be created.").arg(urlText))); } } } if (QDir(urlText).exists()) { QUrl url = QUrl::fromLocalFile(urlText); if (url.isValid()) QDesktopServices::openUrl(url); } } void AboutDialog::handleCopyMenu(QPoint pos) { QTreeView * tree = qobject_cast(sender()); if (! tree) return; QModelIndex index = tree->indexAt(pos); if (! index.isValid()) return; QMenu * menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); if (ui->tabWidget->currentWidget() == ui->tab_plugins) { #ifdef Q_OS_MAC QString show_in_str = tr("Show in Finder"); #else QString show_in_str = tr("Show in Folder"); #endif QAction * showInFolderAction = menu->addAction(show_in_str); showInFolderAction->setData(VariantPointer::asQVariant(tree)); connect(showInFolderAction, &QAction::triggered, this, &AboutDialog::showInFolderActionTriggered); } QAction * copyColumnAction = menu->addAction(tr("Copy")); copyColumnAction->setData(VariantPointer::asQVariant(tree)); connect(copyColumnAction, &QAction::triggered, this, &AboutDialog::copyActionTriggered); QModelIndexList selectedRows = tree->selectionModel()->selectedRows(); QAction * copyRowAction = menu->addAction(tr("Copy Row(s)", "", static_cast(selectedRows.count()))); copyRowAction->setData(VariantPointer::asQVariant(tree)); connect(copyRowAction, &QAction::triggered, this, &AboutDialog::copyRowActionTriggered); menu->popup(tree->viewport()->mapToGlobal(pos)); } void AboutDialog::showInFolderActionTriggered() { QAction * sendingAction = qobject_cast(sender()); if (!sendingAction) return; QTreeView * tree = VariantPointer::asPtr(sendingAction->data()); QModelIndexList selectedRows = tree->selectionModel()->selectedRows(); foreach (QModelIndex index, selectedRows) { QString cf_path = tree->model()->index(index.row(), 3).data().toString(); desktop_show_in_folder(cf_path); } } void AboutDialog::copyRowActionTriggered() { copyActionTriggered(true); } void AboutDialog::copyActionTriggered(bool copyRow) { QAction * sendingAction = qobject_cast(sender()); if (! sendingAction) return; QTreeView * tree = VariantPointer::asPtr(sendingAction->data()); QModelIndexList selIndeces = tree->selectionModel()->selectedIndexes(); int copyColumn = -1; if (! copyRow) { QMenu * menu = qobject_cast(sendingAction->parent()); if (menu) { QPoint menuPosOnTable = tree->mapFromGlobal(menu->pos()); QModelIndex clickedIndex = tree->indexAt(menuPosOnTable); if (clickedIndex.isValid()) copyColumn = clickedIndex.column(); } } QString clipdata; if (selIndeces.count() > 0) { int columnCount = tree->model()->columnCount(); QList visitedRows; foreach(QModelIndex index, selIndeces) { if (visitedRows.contains(index.row())) continue; QStringList row; if (copyRow) { for (int cnt = 0; cnt < columnCount; cnt++) { QModelIndex dataIdx = tree->model()->index(index.row(), cnt); row << tree->model()->data(dataIdx).toString(); } } else { if (copyColumn < 0) copyColumn = index.column(); QModelIndex dataIdx = tree->model()->index(index.row(), copyColumn); row << tree->model()->data(dataIdx).toString(); } clipdata.append(row.join("\t\t").append("\n")); visitedRows << index.row(); } } QClipboard * clipBoard = QApplication::clipboard(); clipBoard->setText(clipdata); } void AboutDialog::on_tblPlugins_doubleClicked(const QModelIndex &index) { const int path_col = 3; if (index.column() != path_col) { return; } const int row = index.row(); const QAbstractItemModel *model = index.model(); if (model->index(row, path_col).data().toString().contains(QRegularExpression(script_pattern))) { QDesktopServices::openUrl(QUrl::fromLocalFile(model->index(row, path_col).data().toString())); } }