diff options
Diffstat (limited to 'ui/qt/models')
79 files changed, 17598 insertions, 0 deletions
diff --git a/ui/qt/models/astringlist_list_model.cpp b/ui/qt/models/astringlist_list_model.cpp new file mode 100644 index 00000000..a0b5fc38 --- /dev/null +++ b/ui/qt/models/astringlist_list_model.cpp @@ -0,0 +1,305 @@ +/* astringlist_list_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <QSortFilterProxyModel> +#include <QStringList> +#include <QPalette> +#include <QApplication> +#include <QBrush> + +#include <ui/qt/models/astringlist_list_model.h> + +#include <ui/qt/utils/color_utils.h> + +AStringListListModel::AStringListListModel(QObject * parent): +QAbstractTableModel(parent) +{} + +AStringListListModel::~AStringListListModel() { display_data_.clear(); } + +void AStringListListModel::appendRow(const QStringList & display_strings, const QString & row_tooltip, const QModelIndex &parent) +{ + QStringList columns = headerColumns(); + if (display_strings.count() != columns.count()) + return; + + emit beginInsertRows(parent, rowCount(), rowCount()); + display_data_ << display_strings; + tooltip_data_ << row_tooltip; + emit endInsertRows(); +} + +int AStringListListModel::rowCount(const QModelIndex &) const +{ + return static_cast<int>(display_data_.count()); +} + +int AStringListListModel::columnCount(const QModelIndex &parent) const +{ + if (rowCount(parent) == 0) + return 0; + + return static_cast<int>(headerColumns().count()); +} + +QVariant AStringListListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical) + return QVariant(); + + QStringList columns = headerColumns(); + if (role == Qt::DisplayRole && section < columns.count()) + return QVariant::fromValue(columns[section]); + + return QVariant(); +} + +QVariant AStringListListModel::data(const QModelIndex &index, int role) const +{ + if (! index.isValid() || index.row() >= rowCount()) + return QVariant(); + + if (role == Qt::DisplayRole) + { + QStringList data = display_data_.at(index.row()); + + if (index.column() < columnCount()) + return QVariant::fromValue(data.at(index.column())); + } + else if (role == Qt::ToolTipRole) + { + QString tooltip = tooltip_data_.at(index.row()); + if (!tooltip.isEmpty()) { + return tooltip; + } + } + + return QVariant(); +} + +AStringListListSortFilterProxyModel::AStringListListSortFilterProxyModel(QObject * parent) +: QSortFilterProxyModel(parent) +{ + filter_ = QString(); + types_[-1] = FilterByContains; +} + +bool AStringListListSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + QString leftData = left.data().toString(); + QString rightData = right.data().toString(); + + if (numericColumns_.contains(left.column()) || numericColumns_.contains(right.column()) ) + { + float leftD = leftData.toFloat(); + float rightD = rightData.toFloat(); + + return leftD < rightD; + } + + return leftData.compare(rightData, sortCaseSensitivity()) < 0; +} + +void AStringListListSortFilterProxyModel::setFilter(const QString & filter) +{ + filter_ = filter; + invalidateFilter(); +} + +static bool AContainsB(const QVariant &a, const QVariant &b, Qt::CaseSensitivity cs) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + if (! a.canConvert<QString>() || ! b.canConvert<QString>()) +#else + if (! a.canConvert(QVariant::String) || ! b.canConvert(QVariant::String)) +#endif + return false; + return a.toString().contains(b.toString(), cs); +} + +static bool AStartsWithB(const QVariant &a, const QVariant &b, Qt::CaseSensitivity cs) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + if (! a.canConvert<QString>() || ! b.canConvert<QString>()) +#else + if (! a.canConvert(QVariant::String) || ! b.canConvert(QVariant::String)) +#endif + return false; + return a.toString().startsWith(b.toString(), cs); +} + +static bool AIsEquivalentToB(const QVariant &a, const QVariant &b, Qt::CaseSensitivity) +{ + return a == b; +} + +bool AStringListListSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + if (columnsToFilter_.count() == 0) + return true; + + foreach(int column, columnsToFilter_) + { + if (column >= columnCount()) + continue; + + QModelIndex chkIdx = sourceModel()->index(sourceRow, column, sourceParent); + QString dataString = chkIdx.data().toString(); + + /* Default is filter by string a contains string b */ + bool (*compareFunc)(const QVariant&, const QVariant&, Qt::CaseSensitivity) = AContainsB; + if (types_.keys().contains(column)) + { + switch (types_.value(column, FilterByContains)) + { + case FilterByStart: + compareFunc = AStartsWithB; + break; + case FilterByEquivalent: + compareFunc = AIsEquivalentToB; + break; + case FilterNone: + return true; + break; + default: + compareFunc = AContainsB; + break; + } + } + + if (compareFunc(dataString, filter_, filterCaseSensitivity())) + return true; + } + + return false; +} + +void AStringListListSortFilterProxyModel::setFilterType(AStringListListFilterType type, int column) +{ + if (column >= -1 && column < columnCount()) + { + if (! types_.keys().contains(column)) + { + types_.insert(column, type); + invalidateFilter(); + } + else if (types_.keys().contains(column) && type != types_[column]) + { + types_[column] = type; + invalidateFilter(); + } + } +} + +void AStringListListSortFilterProxyModel::setColumnToFilter(int column) +{ + if (column < columnCount() && ! columnsToFilter_.contains(column)) + { + columnsToFilter_.append(column); + invalidateFilter(); + } +} + +void AStringListListSortFilterProxyModel::setColumnsToFilter(QList<int> columns) +{ + bool hasBeenAdded = false; + + foreach (int column, columns) { + if (column < columnCount() && ! columnsToFilter_.contains(column)) { + columnsToFilter_.append(column); + hasBeenAdded = true; + } + } + + if (hasBeenAdded) + invalidateFilter(); +} + +void AStringListListSortFilterProxyModel::clearColumnsToFilter() +{ + columnsToFilter_.clear(); + invalidateFilter(); +} + +void AStringListListSortFilterProxyModel::clearHiddenColumns() +{ + hiddenColumns_.clear(); + invalidateFilter(); +} + +void AStringListListSortFilterProxyModel::setColumnToHide(int col) +{ + if (! hiddenColumns_.contains(col) && col > -1 && sourceModel() && sourceModel()->columnCount() > col) + { + hiddenColumns_ << col; + invalidateFilter(); + } +} + +bool AStringListListSortFilterProxyModel::filterAcceptsColumn(int sourceColumn, const QModelIndex &sourceParent) const +{ + QModelIndex realIndex = sourceModel()->index(0, sourceColumn, sourceParent); + + if (! realIndex.isValid()) + return false; + + if (hiddenColumns_.contains(sourceColumn)) + return false; + + return true; +} + +void AStringListListSortFilterProxyModel::clearNumericColumns() +{ + numericColumns_.clear(); + invalidateFilter(); +} + +void AStringListListSortFilterProxyModel::setColumnAsNumeric(int col) +{ + if (! numericColumns_.contains(col) && col > -1 && sourceModel() && sourceModel()->columnCount() > col) + { + numericColumns_ << col; + invalidateFilter(); + } +} + +AStringListListUrlProxyModel::AStringListListUrlProxyModel(QObject * parent): + QIdentityProxyModel(parent) +{} + +void AStringListListUrlProxyModel::setUrlColumn(int column) +{ + if (column < columnCount() && ! urls_.contains(column)) + urls_ << column; +} + +bool AStringListListUrlProxyModel::isUrlColumn(int column) const +{ + return urls_.contains(column); +} + +QVariant AStringListListUrlProxyModel::data(const QModelIndex &index, int role) const +{ + QVariant result = QIdentityProxyModel::data(index, role); + + if (role == Qt::ForegroundRole && urls_.contains(index.column()) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + && result.canConvert<QBrush>()) +#else + && result.canConvert(QVariant::Brush)) +#endif + { + QBrush selected = result.value<QBrush>(); + selected.setColor(ColorUtils::themeLinkBrush().color()); + return selected; + } + + return result; +} diff --git a/ui/qt/models/astringlist_list_model.h b/ui/qt/models/astringlist_list_model.h new file mode 100644 index 00000000..70acc97b --- /dev/null +++ b/ui/qt/models/astringlist_list_model.h @@ -0,0 +1,107 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ASTRINGLIST_LIST_MODEL_H +#define ASTRINGLIST_LIST_MODEL_H + +#include <config.h> + +#include <QAbstractTableModel> +#include <QModelIndex> +#include <QList> +#include <QStringList> +#include <QSortFilterProxyModel> +#include <QIdentityProxyModel> + +class AStringListListModel : public QAbstractTableModel +{ +public: + explicit AStringListListModel(QObject * parent = Q_NULLPTR); + virtual ~AStringListListModel(); + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + // + // This is not protected because we may need to invoke it from + // a wmem_map_foreach() callback implemented as an extern "C" + // static member function of a subclass. wmem_map_foreach() is + // passed, as the user data, a pointer to the class instance to + // which we want to append rows. + // + virtual void appendRow(const QStringList &, const QString & row_tooltip = QString(), const QModelIndex &parent = QModelIndex()); + +protected: + virtual QStringList headerColumns() const = 0; + +private: + QList<QStringList> display_data_; + QStringList tooltip_data_; +}; + +class AStringListListSortFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + + enum AStringListListFilterType + { + FilterByContains = 0, + FilterByStart, + FilterByEquivalent, + FilterNone + }; + Q_ENUM(AStringListListFilterType) + + explicit AStringListListSortFilterProxyModel(QObject * parent = Q_NULLPTR); + + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + virtual bool filterAcceptsColumn(int column, const QModelIndex &sourceParent) const; + + void setFilterType(AStringListListFilterType type, int column = -1); + + void setColumnToFilter(int); + void setColumnsToFilter(QList<int>); + void clearColumnsToFilter(); + + void clearHiddenColumns(); + void setColumnToHide(int col); + + void clearNumericColumns(); + void setColumnAsNumeric(int col); + +public slots: + void setFilter(const QString&); + +private: + QString filter_; + QMap<int, AStringListListFilterType> types_; + QList<int> columnsToFilter_; + QList<int> hiddenColumns_; + QList<int> numericColumns_; +}; + +class AStringListListUrlProxyModel : public QIdentityProxyModel +{ +public: + explicit AStringListListUrlProxyModel(QObject * parent = Q_NULLPTR); + + void setUrlColumn(int); + bool isUrlColumn(int) const; + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + +private: + QList<int> urls_; +}; + +#endif // ASTRINGLIST_LIST_MODEL_H diff --git a/ui/qt/models/atap_data_model.cpp b/ui/qt/models/atap_data_model.cpp new file mode 100644 index 00000000..b7ff508d --- /dev/null +++ b/ui/qt/models/atap_data_model.cpp @@ -0,0 +1,850 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <glib.h> + +#include <epan/tap.h> +#include <epan/conversation.h> +#include <epan/conversation_table.h> +#include <epan/maxmind_db.h> +#include <epan/addr_resolv.h> + +#include <wsutil/utf8_entities.h> +#include <wsutil/nstime.h> +#include <wsutil/str_util.h> + +#include <ui/qt/utils/qt_ui_utils.h> +#include <ui/qt/utils/variant_pointer.h> +#include <ui/qt/main_application.h> +#include <ui/qt/models/atap_data_model.h> +#include <ui/qt/models/timeline_delegate.h> + +#include <QSize> +#include <QVariant> +#include <QWidget> +#include <QDateTime> + +static QString formatString(qlonglong value) +{ + return QLocale().formattedDataSize(value, 0, QLocale::DataSizeSIFormat); +} + +ATapDataModel::ATapDataModel(dataModelType type, int protoId, QString filter, QObject *parent): + QAbstractListModel(parent) +{ + hash_.conv_array = nullptr; + hash_.hashtable = nullptr; + hash_.user_data = this; + + storage_ = nullptr; + _resolveNames = false; + _absoluteTime = false; + _nanoseconds = false; + + _protoId = protoId; + _filter = filter; + + _minRelStartTime = 0; + _maxRelStopTime = 0; + + _type = type; + _disableTap = true; + + QString _tap(proto_get_protocol_filter_name(protoId)); +} + +ATapDataModel::~ATapDataModel() +{ + /* Only remove the tap if we come from a enabled model */ + if (!_disableTap) + remove_tap_listener(hash()); + + if (_type == ATapDataModel::DATAMODEL_ENDPOINT) + reset_endpoint_table_data(&hash_); + else if (_type == ATapDataModel::DATAMODEL_CONVERSATION) + reset_conversation_table_data(&hash_); +} + +int ATapDataModel::protoId() const +{ + return _protoId; +} + +QString ATapDataModel::tap() const +{ + return proto_get_protocol_filter_name(_protoId); +} + +#ifdef HAVE_MAXMINDDB +bool ATapDataModel::hasGeoIPData() +{ + bool coordsFound = false; + int row = 0; + int count = rowCount(); + while (!coordsFound && row < count) + { + QModelIndex idx = index(row, 0); + if (_type == ATapDataModel::DATAMODEL_ENDPOINT) + coordsFound = qobject_cast<EndpointDataModel *>(this)->data(idx, ATapDataModel::GEODATA_AVAILABLE).toBool(); + else if (_type == ATapDataModel::DATAMODEL_CONVERSATION) + coordsFound = qobject_cast<ConversationDataModel *>(this)->data(idx, ATapDataModel::GEODATA_AVAILABLE).toBool(); + row++; + } + + return coordsFound; +} +#endif + +bool ATapDataModel::enableTap() +{ + /* We can't reenable a tap, so just return */ + if (! _disableTap) + return true; + + _disableTap = false; + + /* The errorString is ignored. If this is not working, there is nothing really the user may do about + * it, so the error is only interesting to the developer.*/ + GString * errorString = register_tap_listener(tap().toUtf8().constData(), hash(), _filter.toUtf8().constData(), + TL_IGNORE_DISPLAY_FILTER, &ATapDataModel::tapReset, conversationPacketHandler(), &ATapDataModel::tapDraw, nullptr); + if (errorString && errorString->len > 0) { + g_string_free(errorString, TRUE); + _disableTap = true; + emit tapListenerChanged(false); + return false; + } + + if (errorString) + g_string_free(errorString, TRUE); + + emit tapListenerChanged(true); + + return true; +} + +void ATapDataModel::disableTap() +{ + /* Only remove the tap if we come from a enabled model */ + if (!_disableTap) + remove_tap_listener(hash()); + _disableTap = true; + emit tapListenerChanged(false); +} + +int ATapDataModel::rowCount(const QModelIndex &parent) const +{ + return (storage_ && !parent.isValid()) ? (int) storage_->len : 0; +} + +void ATapDataModel::tapReset(void *tapdata) { + if (! tapdata) + return; + + conv_hash_t *hash = (conv_hash_t*)tapdata; + ATapDataModel * dataModel = qobject_cast<ATapDataModel *>((ATapDataModel *)hash->user_data); + + dataModel->resetData(); +} + +void ATapDataModel::tapDraw(void *tapdata) +{ + if (! tapdata) + return; + + conv_hash_t *hash = (conv_hash_t*)tapdata; + ATapDataModel * dataModel = qobject_cast<ATapDataModel *>((ATapDataModel *)hash->user_data); + + dataModel->updateData(hash->conv_array); +} + +conv_hash_t * ATapDataModel::hash() +{ + return &hash_; +} + +register_ct_t * ATapDataModel::registerTable() const +{ + if (_protoId > -1) + return get_conversation_by_proto_id(_protoId); + + return nullptr; +} + +tap_packet_cb ATapDataModel::conversationPacketHandler() +{ + register_ct_t* table = registerTable(); + if (table) { + if (_type == ATapDataModel::DATAMODEL_ENDPOINT) + return get_endpoint_packet_func(table); + else if (_type == ATapDataModel::DATAMODEL_CONVERSATION) + return get_conversation_packet_func(table); + } + + return nullptr; +} + +void ATapDataModel::resetData() +{ + if (_disableTap) + return; + + beginResetModel(); + storage_ = nullptr; + if (_type == ATapDataModel::DATAMODEL_ENDPOINT) + reset_endpoint_table_data(&hash_); + else if (_type == ATapDataModel::DATAMODEL_CONVERSATION) + reset_conversation_table_data(&hash_); + + _minRelStartTime = 0; + _maxRelStopTime = 0; + + endResetModel(); +} + +void ATapDataModel::updateData(GArray * newData) +{ + if (_disableTap) + return; + + beginResetModel(); + storage_ = newData; + endResetModel(); + + if (_type == ATapDataModel::DATAMODEL_CONVERSATION) + ((ConversationDataModel *)(this))->doDataUpdate(); +} + +bool ATapDataModel::resolveNames() const +{ + return _resolveNames; +} + +void ATapDataModel::setResolveNames(bool resolve) +{ + if (_resolveNames == resolve) + return; + + beginResetModel(); + _resolveNames = resolve; + endResetModel(); +} + +bool ATapDataModel::allowsNameResolution() const +{ + if (_protoId < 0) + return false; + + QStringList mac_protos = QStringList() << "bluetooth" << "eth" << "fddi" + << "sll" << "tr" << "wlan"; + QStringList net_protos = QStringList() << "dccp" << "ip" << "ipv6" + << "jxta" << "mptcp" << "ncp" + << "rsvp" << "sctp" << "sll" + << "tcp" << "udp"; + QStringList transport_protos = QStringList() << "dccp" << "mptcp" << "sctp" + << "tcp" << "udp"; + + QString table_proto = proto_get_protocol_filter_name(_protoId); + + if (mac_protos.contains(table_proto) && gbl_resolv_flags.mac_name) + return true; + if (net_protos.contains(table_proto) && gbl_resolv_flags.network_name) + return true; + if (transport_protos.contains(table_proto) && gbl_resolv_flags.transport_name) + return true; + + return false; +} + +void ATapDataModel::useAbsoluteTime(bool absolute) +{ + if (absolute == _absoluteTime) + return; + + beginResetModel(); + _absoluteTime = absolute; + endResetModel(); +} + +void ATapDataModel::useNanosecondTimestamps(bool nanoseconds) +{ + if (_nanoseconds == nanoseconds) + return; + + beginResetModel(); + _nanoseconds = nanoseconds; + endResetModel(); +} + +void ATapDataModel::setFilter(QString filter) +{ + if (_disableTap) + return; + + _filter = filter; + GString * errorString = set_tap_dfilter(&hash_, !_filter.isEmpty() ? _filter.toUtf8().constData() : nullptr); + if (errorString && errorString->len > 0) { + /* If this fails, chances are that the main system failed as well. Silently exiting as the + * user cannot react to it */ + disableTap(); + } + + if (errorString) + g_string_free(errorString, TRUE); +} + +QString ATapDataModel::filter() const +{ + return _filter; +} + +ATapDataModel::dataModelType ATapDataModel::modelType() const +{ + return _type; +} + +bool ATapDataModel::portsAreHidden() const +{ + return (get_conversation_hide_ports(registerTable())); +} + +bool ATapDataModel::showTotalColumn() const +{ + /* Implemented to ensure future changes may be done more easily */ + return _filter.length() > 0; +} + +EndpointDataModel::EndpointDataModel(int protoId, QString filter, QObject *parent) : + ATapDataModel(ATapDataModel::DATAMODEL_ENDPOINT, protoId, filter, parent) +{} + +int EndpointDataModel::columnCount(const QModelIndex &) const +{ +#ifdef HAVE_MAXMINDDB + int proto_ipv4 = proto_get_id_by_filter_name("ip"); + int proto_ipv6 = proto_get_id_by_filter_name("ipv6"); + if (protoId() == proto_ipv4 || protoId() == proto_ipv6) { + return ENDP_NUM_GEO_COLUMNS; + } +#endif + return ENDP_NUM_COLUMNS; +} + +QVariant EndpointDataModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical) + return QVariant(); + + if (role == Qt::DisplayRole) { + switch (section) { + case ENDP_COLUMN_ADDR: + return tr("Address"); break; + case ENDP_COLUMN_PORT: + return tr("Port"); break; + case ENDP_COLUMN_PACKETS: + return tr("Packets"); break; + case ENDP_COLUMN_BYTES: + return tr("Bytes"); break; + case ENDP_COLUMN_PACKETS_TOTAL: + return tr("Total Packets"); break; + case ENDP_COLUMN_BYTES_TOTAL: + return tr("Percent Filtered"); break; + case ENDP_COLUMN_PKT_AB: + return tr("Tx Packets"); break; + case ENDP_COLUMN_BYTES_AB: + return tr("Tx Bytes"); break; + case ENDP_COLUMN_PKT_BA: + return tr("Rx Packets"); break; + case ENDP_COLUMN_BYTES_BA: + return tr("Rx Bytes"); break; + case ENDP_COLUMN_GEO_COUNTRY: + return tr("Country"); break; + case ENDP_COLUMN_GEO_CITY: + return tr("City"); break; + case ENDP_COLUMN_GEO_LATITUDE: + return tr("Latitude"); break; + case ENDP_COLUMN_GEO_LONGITUDE: + return tr("Longitude"); break; + case ENDP_COLUMN_GEO_AS_NUM: + return tr("AS Number"); break; + case ENDP_COLUMN_GEO_AS_ORG: + return tr("AS Organization"); break; + } + } else if (role == Qt::TextAlignmentRole) { + switch (section) { + case ENDP_COLUMN_ADDR: + case ENDP_COLUMN_GEO_COUNTRY: + case ENDP_COLUMN_GEO_CITY: + case ENDP_COLUMN_GEO_AS_ORG: + return Qt::AlignLeft; + default: + break; + } + return Qt::AlignRight; + } + + return QVariant(); +} + +QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const +{ + if (! idx.isValid()) + return QVariant(); + + // Column text cooked representation. + endpoint_item_t *item = &g_array_index(storage_, endpoint_item_t, idx.row()); + const mmdb_lookup_t *mmdb_lookup = nullptr; +#ifdef HAVE_MAXMINDDB + char addr[WS_INET6_ADDRSTRLEN]; + if (item->myaddress.type == AT_IPv4) { + const ws_in4_addr * ip4 = (const ws_in4_addr *) item->myaddress.data; + mmdb_lookup = maxmind_db_lookup_ipv4(ip4); + ws_inet_ntop4(ip4, addr, sizeof(addr)); + } else if (item->myaddress.type == AT_IPv6) { + const ws_in6_addr * ip6 = (const ws_in6_addr *) item->myaddress.data; + mmdb_lookup = maxmind_db_lookup_ipv6(ip6); + ws_inet_ntop6(ip6, addr, sizeof(addr)); + } else { + addr[0] = '\0'; + } + QString ipAddress(addr); +#endif + + if (role == Qt::DisplayRole || role == ATapDataModel::UNFORMATTED_DISPLAYDATA) { + switch (idx.column()) { + case ENDP_COLUMN_ADDR: { + char* addr_str = get_conversation_address(NULL, &item->myaddress, _resolveNames); + QString q_addr_str(addr_str); + wmem_free(NULL, addr_str); + return q_addr_str; + } + case ENDP_COLUMN_PORT: + if (_resolveNames) { + char* port_str = get_endpoint_port(NULL, item, _resolveNames); + QString q_port_str(port_str); + wmem_free(NULL, port_str); + return q_port_str; + } else { + return quint32(item->port); + } + case ENDP_COLUMN_PACKETS: + { + qlonglong packets = (qlonglong)(item->tx_frames + item->rx_frames); + return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets; + } + case ENDP_COLUMN_BYTES: + return role == Qt::DisplayRole ? formatString((qlonglong)(item->tx_bytes + item->rx_bytes)) : + QVariant((qlonglong)(item->tx_bytes + item->rx_bytes)); + case ENDP_COLUMN_PACKETS_TOTAL: + { + qlonglong packets = 0; + if (showTotalColumn()) + packets = item->tx_frames_total + item->rx_frames_total; + return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets; + } + case ENDP_COLUMN_BYTES_TOTAL: + { + double percent = 0; + if (showTotalColumn()) { + qlonglong totalPackets = (qlonglong)(item->tx_frames_total + item->rx_frames_total); + qlonglong packets = (qlonglong)(item->tx_frames + item->rx_frames); + percent = totalPackets == 0 ? 0 : (double) packets * 100 / (double) totalPackets; + } + QString rounded = QString::number(percent, 'f', 2); + /* Qt guarantees that this roundtrip conversion compares equally, + * so filtering with equality will work as expected. + * Perhaps the UNFORMATTED_DISPLAYDATA role shoud be split + * into one used for raw data export, and one used for comparisons. + */ + return role == Qt::DisplayRole ? rounded + "%" : QVariant(rounded.toDouble()); + } + case ENDP_COLUMN_PKT_AB: + return role == Qt::DisplayRole ? QString("%L1").arg((qlonglong)item->tx_frames) : QVariant((qlonglong) item->tx_frames); + case ENDP_COLUMN_BYTES_AB: + return role == Qt::DisplayRole ? formatString((qlonglong)item->tx_bytes) : QVariant((qlonglong)item->tx_bytes); + case ENDP_COLUMN_PKT_BA: + return role == Qt::DisplayRole ? QString("%L1").arg((qlonglong)item->rx_frames) : QVariant((qlonglong) item->rx_frames); + case ENDP_COLUMN_BYTES_BA: + return role == Qt::DisplayRole ? formatString((qlonglong)item->rx_bytes) : QVariant((qlonglong)item->rx_bytes); + case ENDP_COLUMN_GEO_COUNTRY: + if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->country) { + return QVariant(mmdb_lookup->country); + } + return QVariant(); + case ENDP_COLUMN_GEO_CITY: + if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->city) { + return QVariant(mmdb_lookup->city); + } + return QVariant(); + case ENDP_COLUMN_GEO_LATITUDE: + if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->latitude >= -90.0 && mmdb_lookup->latitude <= 90.0) { + return role == Qt::DisplayRole ? QString("%L1" UTF8_DEGREE_SIGN).arg(mmdb_lookup->latitude) : QVariant(mmdb_lookup->latitude); + } + return QVariant(); + case ENDP_COLUMN_GEO_LONGITUDE: + if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->longitude >= -180.0 && mmdb_lookup->longitude <= 180.0) { + return role == Qt::DisplayRole ? QString("%L1" UTF8_DEGREE_SIGN).arg(mmdb_lookup->longitude) : QVariant(mmdb_lookup->longitude); + } + return QVariant(); + case ENDP_COLUMN_GEO_AS_NUM: + if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->as_number) { + return QVariant(mmdb_lookup->as_number); + } + return QVariant(); + case ENDP_COLUMN_GEO_AS_ORG: + if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->as_org) { + return QVariant(mmdb_lookup->as_org); + } + return QVariant(); + default: + return QVariant(); + } + } else if (role == Qt::TextAlignmentRole) { + switch (idx.column()) { + case ENDP_COLUMN_ADDR: + case ENDP_COLUMN_GEO_COUNTRY: + case ENDP_COLUMN_GEO_CITY: + case ENDP_COLUMN_GEO_AS_ORG: + return Qt::AlignLeft; + default: + break; + } + return Qt::AlignRight; + } else if (role == ATapDataModel::DISPLAY_FILTER) { + return QString(get_endpoint_filter(item)); + } else if (role == ATapDataModel::ROW_IS_FILTERED) { + return (bool)item->filtered && showTotalColumn(); + } +#ifdef HAVE_MAXMINDDB + else if (role == ATapDataModel::GEODATA_AVAILABLE) { + return (bool)(mmdb_lookup && maxmind_db_has_coords(mmdb_lookup)); + } else if (role == ATapDataModel::GEODATA_LOOKUPTABLE) { + return VariantPointer<const mmdb_lookup_t>::asQVariant(mmdb_lookup); + } else if (role == ATapDataModel::GEODATA_ADDRESS) { + return ipAddress; + } +#endif + else if (role == ATapDataModel::PROTO_ID) { + return protoId(); + } else if (role == ATapDataModel::DATA_ADDRESS_TYPE) { + if (idx.column() == EndpointDataModel::ENDP_COLUMN_ADDR) + return (int)item->myaddress.type; + return (int) AT_NONE; + } else if (role == ATapDataModel::DATA_IPV4_INTEGER || role == ATapDataModel::DATA_IPV6_LIST) { + if (idx.column() == EndpointDataModel::ENDP_COLUMN_ADDR) { + if (role == ATapDataModel::DATA_IPV4_INTEGER && item->myaddress.type == AT_IPv4) { + const ws_in4_addr * ip4 = (const ws_in4_addr *) item->myaddress.data; + return (quint32) GUINT32_FROM_BE(*ip4); + } + else if (role == ATapDataModel::DATA_IPV6_LIST && item->myaddress.type == AT_IPv6) { + const ws_in6_addr * ip6 = (const ws_in6_addr *) item->myaddress.data; + QList<quint8> result; + result.reserve(16); + std::copy(ip6->bytes + 0, ip6->bytes + 16, std::back_inserter(result)); + return QVariant::fromValue(result); + } + } + } + + return QVariant(); +} + +ConversationDataModel::ConversationDataModel(int protoId, QString filter, QObject *parent) : + ATapDataModel(ATapDataModel::DATAMODEL_CONVERSATION, protoId, filter, parent) +{} + +void ConversationDataModel::doDataUpdate() +{ + _minRelStartTime = 0; + _maxRelStopTime = 0; + + for (int row = 0; row < rowCount(); row ++) { + conv_item_t *conv_item = &g_array_index(storage_, conv_item_t, row); + + if (row == 0) { + _minRelStartTime = nstime_to_sec(&(conv_item->start_time)); + _maxRelStopTime = nstime_to_sec(&(conv_item->stop_time)); + } else { + double item_rel_start = nstime_to_sec(&(conv_item->start_time)); + if (item_rel_start < _minRelStartTime) { + _minRelStartTime = item_rel_start; + } + + double item_rel_stop = nstime_to_sec(&(conv_item->stop_time)); + if (item_rel_stop > _maxRelStopTime) { + _maxRelStopTime = item_rel_stop; + } + } + } +} + +int ConversationDataModel::columnCount(const QModelIndex &) const +{ + return CONV_NUM_COLUMNS; +} + +QVariant ConversationDataModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical) + return QVariant(); + + if (role == Qt::DisplayRole) { + switch (section) { + case CONV_COLUMN_SRC_ADDR: + return tr("Address A"); break; + case CONV_COLUMN_SRC_PORT: + return tr("Port A"); break; + case CONV_COLUMN_DST_ADDR: + return tr("Address B"); break; + case CONV_COLUMN_DST_PORT: + return tr("Port B"); break; + case CONV_COLUMN_PACKETS: + return tr("Packets"); break; + case CONV_COLUMN_BYTES: + return tr("Bytes"); break; + case CONV_COLUMN_CONV_ID: + return tr("Stream ID"); break; + case CONV_COLUMN_PACKETS_TOTAL: + return tr("Total Packets"); break; + case CONV_COLUMN_BYTES_TOTAL: + return tr("Percent Filtered"); break; + case CONV_COLUMN_PKT_AB: + return tr("Packets A " UTF8_RIGHTWARDS_ARROW " B"); break; + case CONV_COLUMN_BYTES_AB: + return tr("Bytes A " UTF8_RIGHTWARDS_ARROW " B"); break; + case CONV_COLUMN_PKT_BA: + return tr("Packets B " UTF8_RIGHTWARDS_ARROW " A"); break; + case CONV_COLUMN_BYTES_BA: + return tr("Bytes B " UTF8_RIGHTWARDS_ARROW " A"); break; + case CONV_COLUMN_START: + return _absoluteTime ? tr("Abs Start") : tr("Rel Start"); break; + case CONV_COLUMN_DURATION: + return tr("Duration"); break; + case CONV_COLUMN_BPS_AB: + return tr("Bits/s A " UTF8_RIGHTWARDS_ARROW " B"); break; + case CONV_COLUMN_BPS_BA: + return tr("Bits/s B " UTF8_RIGHTWARDS_ARROW " A"); break; + } + } else if (role == Qt::TextAlignmentRole) { + if (section == CONV_COLUMN_SRC_ADDR || section == CONV_COLUMN_DST_ADDR) + return Qt::AlignLeft; + return Qt::AlignRight; + } + + return QVariant(); +} + +static const double min_bw_calc_duration_ = 5 / 1000.0; // seconds + +QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const +{ + if (! idx.isValid()) + return QVariant(); + + // Column text cooked representation. + conv_item_t *conv_item = (conv_item_t *)&g_array_index(storage_, conv_item_t,idx.row()); + + double duration = nstime_to_sec(&conv_item->stop_time) - nstime_to_sec(&conv_item->start_time); + double bps_ab = 0, bps_ba = 0; + bool bpsCalculated = false; + if (duration > min_bw_calc_duration_) { + bps_ab = conv_item->tx_bytes * 8 / duration; + bps_ba = conv_item->rx_bytes * 8 / duration; + bpsCalculated = true; + } + + if (role == Qt::DisplayRole || role == ATapDataModel::UNFORMATTED_DISPLAYDATA) { + switch(idx.column()) { + case CONV_COLUMN_SRC_ADDR: + { + char* addr_str = get_conversation_address(NULL, &conv_item->src_address, _resolveNames); + QString q_addr_str(addr_str); + wmem_free(NULL, addr_str); + return q_addr_str; + } + case CONV_COLUMN_SRC_PORT: + if (_resolveNames) { + char* port_str = get_conversation_port(NULL, conv_item->src_port, conv_item->ctype, _resolveNames); + QString q_port_str(port_str); + wmem_free(NULL, port_str); + return q_port_str; + } else { + return quint32(conv_item->src_port); + } + case CONV_COLUMN_DST_ADDR: + { + char* addr_str = get_conversation_address(NULL, &conv_item->dst_address, _resolveNames); + QString q_addr_str(addr_str); + wmem_free(NULL, addr_str); + return q_addr_str; + } + case CONV_COLUMN_DST_PORT: + if (_resolveNames) { + char* port_str = get_conversation_port(NULL, conv_item->dst_port, conv_item->ctype, _resolveNames); + QString q_port_str(port_str); + wmem_free(NULL, port_str); + return q_port_str; + } else { + return quint32(conv_item->dst_port); + } + case CONV_COLUMN_PACKETS: + { + qlonglong packets = conv_item->tx_frames + conv_item->rx_frames; + return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets; + } + case CONV_COLUMN_BYTES: + return role == Qt::DisplayRole ? formatString((qlonglong)conv_item->tx_bytes + conv_item->rx_bytes) : + QVariant((qlonglong)conv_item->tx_bytes + conv_item->rx_bytes); + case CONV_COLUMN_CONV_ID: + return (int) conv_item->conv_id; + case CONV_COLUMN_PACKETS_TOTAL: + { + qlonglong packets = 0; + if (showTotalColumn()) + packets = conv_item->tx_frames_total + conv_item->rx_frames_total; + + return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets; + } + case CONV_COLUMN_BYTES_TOTAL: + { + double percent = 0; + if (showTotalColumn()) { + qlonglong totalPackets = (qlonglong)(conv_item->tx_frames_total + conv_item->rx_frames_total); + qlonglong packets = (qlonglong)(conv_item->tx_frames + conv_item->rx_frames); + percent = totalPackets == 0 ? 0 : (double) packets * 100 / (double) totalPackets; + } + QString rounded = QString::number(percent, 'f', 2); + /* Qt guarantees that this roundtrip conversion compares equally, + * so filtering with equality will work as expected. + * XXX: Perhaps the UNFORMATTED_DISPLAYDATA role shoud be split + * into one used for raw data export and comparisions with each + * other, and another for comparing with filters? + */ + return role == Qt::DisplayRole ? rounded + "%" : QVariant(rounded.toDouble()); + } + case CONV_COLUMN_PKT_AB: + { + qlonglong packets = conv_item->tx_frames; + return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets; + } + case CONV_COLUMN_BYTES_AB: + return role == Qt::DisplayRole ? formatString((qlonglong)conv_item->tx_bytes) : QVariant((qlonglong)conv_item->tx_bytes); + case CONV_COLUMN_PKT_BA: + { + qlonglong packets = conv_item->rx_frames; + return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets; + } + case CONV_COLUMN_BYTES_BA: + return role == Qt::DisplayRole ? formatString((qlonglong)conv_item->rx_bytes) : QVariant((qlonglong)conv_item->rx_bytes); + case CONV_COLUMN_START: + { + int width = _nanoseconds ? 9 : 6; + + if (_absoluteTime) { + nstime_t *abs_time = &conv_item->start_abs_time; + /* XXX: QDateTime only supports millisecond resolution, + * and we have microseconds or nanoseconds. + * Should we use something else, particularly for exporting + * raw data? GDateTime handles microseconds. + */ + QDateTime abs_dt = QDateTime::fromMSecsSinceEpoch(nstime_to_msec(abs_time)); + if (role == Qt::DisplayRole) { + if (_maxRelStopTime >= 24*60*60) { + return abs_dt.toString(Qt::ISODateWithMs); + } else { + return abs_dt.time().toString(Qt::ISODateWithMs); + } + } else { + return QVariant(abs_dt); + } + } else { + return role == Qt::DisplayRole ? + QString::number(nstime_to_sec(&conv_item->start_time), 'f', width) : + (QVariant)((double) nstime_to_sec(&conv_item->start_time)); + } + } + case CONV_COLUMN_DURATION: + { + int width = _nanoseconds ? 6 : 4; + return role == Qt::DisplayRole ? QString::number(duration, 'f', width) : (QVariant)duration; + } + case CONV_COLUMN_BPS_AB: + return bpsCalculated ? (role == Qt::DisplayRole ? gchar_free_to_qstring(format_size((int64_t)bps_ab, FORMAT_SIZE_UNIT_BITS_S, FORMAT_SIZE_PREFIX_SI)) : QVariant((qlonglong)bps_ab)): QVariant(); + case CONV_COLUMN_BPS_BA: + return bpsCalculated ? (role == Qt::DisplayRole ? gchar_free_to_qstring(format_size((int64_t)bps_ba, FORMAT_SIZE_UNIT_BITS_S, FORMAT_SIZE_PREFIX_SI)) : QVariant((qlonglong)bps_ba)): QVariant(); + } + } else if (role == Qt::ToolTipRole) { + if (idx.column() == CONV_COLUMN_START || idx.column() == CONV_COLUMN_DURATION) + return QObject::tr("Bars show the relative timeline for each conversation."); + } else if (role == Qt::TextAlignmentRole) { + if (idx.column() == CONV_COLUMN_SRC_ADDR || idx.column() == CONV_COLUMN_DST_ADDR) + return Qt::AlignLeft; + return Qt::AlignRight; + } else if (role == ATapDataModel::TIMELINE_DATA) { + struct timeline_span span_data; + span_data.minRelTime = _minRelStartTime; + span_data.maxRelTime = _maxRelStopTime; + span_data.startTime = nstime_to_sec(&conv_item->start_time); + span_data.stopTime = nstime_to_sec(&conv_item->stop_time); + span_data.colStart = CONV_COLUMN_START; + span_data.colDuration = CONV_COLUMN_DURATION; + + if ((_maxRelStopTime - _minRelStartTime) > 0) { + return QVariant::fromValue(span_data); + } + } else if (role == ATapDataModel::ENDPOINT_DATATYPE) { + return (int)(conv_item->ctype); + } else if (role == ATapDataModel::PROTO_ID) { + return protoId(); + } else if (role == ATapDataModel::CONVERSATION_ID) { + return (int)(conv_item->conv_id); + } else if (role == ATapDataModel::ROW_IS_FILTERED) { + return (bool)conv_item->filtered && showTotalColumn(); + } else if (role == ATapDataModel::DATA_ADDRESS_TYPE) { + if (idx.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR || idx.column() == ConversationDataModel::CONV_COLUMN_DST_ADDR) { + address tst_address = idx.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR ? conv_item->src_address : conv_item->dst_address; + return (int)tst_address.type; + } + return (int) AT_NONE; + } else if (role == ATapDataModel::DATA_IPV4_INTEGER || role == ATapDataModel::DATA_IPV6_LIST) { + if (idx.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR || idx.column() == ConversationDataModel::CONV_COLUMN_DST_ADDR) { + address tst_address = idx.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR ? conv_item->src_address : conv_item->dst_address; + if (role == ATapDataModel::DATA_IPV4_INTEGER && tst_address.type == AT_IPv4) { + const ws_in4_addr * ip4 = (const ws_in4_addr *) tst_address.data; + return (quint32) GUINT32_FROM_BE(*ip4); + } + else if (role == ATapDataModel::DATA_IPV6_LIST && tst_address.type == AT_IPv6) { + const ws_in6_addr * ip6 = (const ws_in6_addr *) tst_address.data; + QList<quint8> result; + result.reserve(16); + std::copy(ip6->bytes + 0, ip6->bytes + 16, std::back_inserter(result)); + return QVariant::fromValue(result); + } + } + } + + return QVariant(); +} + +conv_item_t * ConversationDataModel::itemForRow(int row) +{ + if (row < 0 || row >= rowCount()) + return nullptr; + return (conv_item_t *)&g_array_index(storage_, conv_item_t, row); +} + +bool ConversationDataModel::showConversationId(int row) const +{ + if (!storage_ || row < 0 || row >= (int) storage_->len) + return false; + + conv_item_t *conv_item = (conv_item_t *)&g_array_index(storage_, conv_item_t, row); + if (conv_item && (conv_item->ctype == CONVERSATION_TCP || conv_item->ctype == CONVERSATION_UDP)) + return true; + return false; +} diff --git a/ui/qt/models/atap_data_model.h b/ui/qt/models/atap_data_model.h new file mode 100644 index 00000000..38bdbd2f --- /dev/null +++ b/ui/qt/models/atap_data_model.h @@ -0,0 +1,329 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ATAP_DATA_MODEL_H +#define ATAP_DATA_MODEL_H + +#include "config.h" + +#include "glib.h" + +#include <epan/tap.h> +#include <epan/conversation.h> +#include <epan/conversation_table.h> + +#include <QAbstractListModel> + +/** + * @brief DataModel for tap user data + * + * This datamodel provides the management for all tap data for the conversation + * and endpoint dialogs. It predominantly is implemented to work with conversation + * tap data. The management of displaying and correctly presenting the information + * is done in the corresponding type classes + * + * @see EndpointDataModel + * @see ConversationDataModel + */ +class ATapDataModel : public QAbstractListModel +{ + Q_OBJECT +public: + + enum { + DISPLAY_FILTER = Qt::UserRole, + UNFORMATTED_DISPLAYDATA, +#ifdef HAVE_MAXMINDDB + GEODATA_AVAILABLE, + GEODATA_LOOKUPTABLE, + GEODATA_ADDRESS, +#endif + TIMELINE_DATA, + ENDPOINT_DATATYPE, + PROTO_ID, + CONVERSATION_ID, + ROW_IS_FILTERED, + DATA_ADDRESS_TYPE, + DATA_IPV4_INTEGER, + DATA_IPV6_LIST, + }; + + typedef enum { + DATAMODEL_ENDPOINT, + DATAMODEL_CONVERSATION, + DATAMODEL_UNKNOWN + } dataModelType; + + /** + * @brief Construct a new ATapDataModel object + * + * The tap will not be created automatically, but must be enabled by calling enableTap + * + * @param type an element of dataModelType. Either DATAMODEL_ENDPOINT or DATAMODEL_CONVERSATION are supported + * at this time + * @param protoId the protocol id for which the tap is created + * @param filter a potential filter to be used for the tap + * @param parent the parent for the class + * + * @see enableTap + */ + explicit ATapDataModel(dataModelType type, int protoId, QString filter, QObject *parent = nullptr); + virtual ~ATapDataModel(); + + /** + * @brief Number of rows under the given parent in this model, which + * is the total number of rows for the empty QModelIndex, and 0 for + * any valid parent index (as no row has children; this is a flat table.) + * + * @param parent index of parent, QModelIndex() for the root + * @return int the number of rows under the parent + */ + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const = 0; + virtual QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const = 0; + virtual QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const = 0; + + /** + * @brief Returns the name for the tap being used + * + * @return QString the tap name, normally identical with the protocol name + */ + QString tap() const; + + /** + * @brief The protocol id for the tap + * + * @return int the id given in the constructor + */ + int protoId() const; + + /** + * @brief Set the filter string. + * + * A set filter can be reset by providing an empty string + * + * @param filter the filter for the tap + */ + void setFilter(QString filter); + + /** + * @brief Return a filter set for the model + * + * @return QString the filter string for the model + */ + QString filter() const; + + /** + * @brief Is the model set to resolve names in address and ports columns + * + * @return true yes, names will be resolved + * @return false no they won't + */ + bool resolveNames() const; + + /** + * @brief Enable or disable if names should be resolved + * + * @param resolve true if names should be resolved + */ + void setResolveNames(bool resolve); + + /** + * @brief Does the model allow names to be resolved + * + * @return true yes, names may be resolved (set via setResolveNames) + * @return false no, they won't be resolved + * + * @see setResolveNames + * @see resolveNames + */ + bool allowsNameResolution() const; + + /** + * @brief Use absolute time for any column supporting it + * + * @param absolute true to use absolute time values + */ + void useAbsoluteTime(bool absolute); + + /** + * @brief Use nanosecond timestamps if requested + * + * @param nanoseconds use nanosecond timestamps if required and requested + */ + void useNanosecondTimestamps(bool nanoseconds); + + /** + * @brief Are ports hidden for this model + * + * @return true the ports are hidden + * @return false the ports are not hidden + */ + bool portsAreHidden() const; + + /** + * @brief A total column is filled + * + * @return true if the column is filled + * @return false the column is empty + */ + bool showTotalColumn() const; + + /** + * @brief Enable tapping in this model. + * + * This will register the tap listener with the corresponding packet function. + * @note if the tap has not been disabled, this method will do nothing + * + * @return true the tap has been enabled + * @return false the tap has not been enabled + */ + bool enableTap(); + + /** + * @brief Disable the tapping for this model + */ + void disableTap(); + + /** + * @brief Return the model type + * + * @return dataModelType + */ + dataModelType modelType() const; + +#ifdef HAVE_MAXMINDDB + /** + * @brief Does this model have geoip data available + * + * @return true it has + * @return false it has not + */ + bool hasGeoIPData(); +#endif + +signals: + void tapListenerChanged(bool enable); + +protected: + + static void tapReset(void *tapdata); + static void tapDraw(void *tap_data); + + virtual tap_packet_cb conversationPacketHandler(); + + conv_hash_t * hash(); + + void resetData(); + void updateData(GArray * data); + + dataModelType _type; + GArray * storage_; + QString _filter; + + bool _absoluteTime; + bool _nanoseconds; + bool _resolveNames; + bool _disableTap; + + double _minRelStartTime; + double _maxRelStopTime; + + register_ct_t* registerTable() const; + +private: + int _protoId; + + conv_hash_t hash_; +}; + +class EndpointDataModel : public ATapDataModel +{ + Q_OBJECT +public: + + typedef enum + { + ENDP_COLUMN_ADDR, + ENDP_COLUMN_PORT, + ENDP_COLUMN_PACKETS, + ENDP_COLUMN_BYTES, + ENDP_COLUMN_PACKETS_TOTAL, + ENDP_COLUMN_BYTES_TOTAL, + ENDP_COLUMN_PKT_AB, + ENDP_COLUMN_BYTES_AB, + ENDP_COLUMN_PKT_BA, + ENDP_COLUMN_BYTES_BA, + ENDP_NUM_COLUMNS, + ENDP_COLUMN_GEO_COUNTRY = ENDP_NUM_COLUMNS, + ENDP_COLUMN_GEO_CITY, + ENDP_COLUMN_GEO_LATITUDE, + ENDP_COLUMN_GEO_LONGITUDE, + ENDP_COLUMN_GEO_AS_NUM, + ENDP_COLUMN_GEO_AS_ORG, + ENDP_NUM_GEO_COLUMNS + } endpoint_column_type_e; + + explicit EndpointDataModel(int protoId, QString filter, QObject *parent = nullptr); + + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const; + +}; + +class ConversationDataModel : public ATapDataModel +{ + Q_OBJECT +public: + + typedef enum { + CONV_COLUMN_SRC_ADDR, + CONV_COLUMN_SRC_PORT, + CONV_COLUMN_DST_ADDR, + CONV_COLUMN_DST_PORT, + CONV_COLUMN_PACKETS, + CONV_COLUMN_BYTES, + CONV_COLUMN_CONV_ID, + CONV_COLUMN_PACKETS_TOTAL, + CONV_COLUMN_BYTES_TOTAL, + CONV_COLUMN_PKT_AB, + CONV_COLUMN_BYTES_AB, + CONV_COLUMN_PKT_BA, + CONV_COLUMN_BYTES_BA, + CONV_COLUMN_START, + CONV_COLUMN_DURATION, + CONV_COLUMN_BPS_AB, + CONV_COLUMN_BPS_BA, + CONV_NUM_COLUMNS, + CONV_INDEX_COLUMN = CONV_NUM_COLUMNS + } conversation_column_type_e; + + explicit ConversationDataModel(int protoId, QString filter, QObject *parent = nullptr); + + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const; + + void doDataUpdate(); + + conv_item_t * itemForRow(int row); + + /** + * @brief Show the conversation id if available + * + * @return true a conversation id exists + * @return false none available + */ + bool showConversationId(int row = 0) const; + +}; + +#endif // ATAP_DATA_MODEL_H diff --git a/ui/qt/models/cache_proxy_model.cpp b/ui/qt/models/cache_proxy_model.cpp new file mode 100644 index 00000000..9f136882 --- /dev/null +++ b/ui/qt/models/cache_proxy_model.cpp @@ -0,0 +1,100 @@ +/* cache_proxy_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/cache_proxy_model.h> + +CacheProxyModel::CacheProxyModel(QObject *parent) : QIdentityProxyModel(parent) +{ +} + +QVariant CacheProxyModel::data(const QModelIndex &index, int role) const +{ + QModelIndex dataIndex = cache.index(index.row(), index.column()); + if (!dataIndex.isValid()) { + // index is possibly outside columnCount or rowCount + return QVariant(); + } + + if (hasModel()) { + QVariant value = QIdentityProxyModel::data(index, role); + cache.setData(dataIndex, value, role); + return value; + } else { + return cache.data(dataIndex, role); + } +} + +Qt::ItemFlags CacheProxyModel::flags(const QModelIndex &index) const +{ + if (hasModel()) { + return QIdentityProxyModel::flags(index); + } else { + // Override default to prevent editing. + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; + } +} + +QVariant CacheProxyModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (hasModel()) { + QVariant value = QIdentityProxyModel::headerData(section, orientation, role); + cache.setHeaderData(section, orientation, value, role); + return value; + } else { + return cache.headerData(section, orientation, role); + } +} + +int CacheProxyModel::rowCount(const QModelIndex &parent) const +{ + if (hasModel()) { + int count = QIdentityProxyModel::rowCount(parent); + cache.setRowCount(count); + return count; + } else { + return cache.rowCount(parent); + } +} + +int CacheProxyModel::columnCount(const QModelIndex &parent) const +{ + if (hasModel()) { + int count = QIdentityProxyModel::columnCount(parent); + cache.setColumnCount(count); + return count; + } else { + return cache.columnCount(parent); + } +} + +/** + * Sets the source model from which data must be pulled. If newSourceModel is + * NULL, then the cache will be used. + */ +void CacheProxyModel::setSourceModel(QAbstractItemModel *newSourceModel) +{ + if (newSourceModel) { + cache.clear(); + QIdentityProxyModel::setSourceModel(newSourceModel); + connect(newSourceModel, &QAbstractItemModel::modelReset, + this, &CacheProxyModel::resetCacheModel); + } else { + if (sourceModel()) { + // Prevent further updates to source model from invalidating cache. + disconnect(sourceModel(), &QAbstractItemModel::modelReset, + this, &CacheProxyModel::resetCacheModel); + } + QIdentityProxyModel::setSourceModel(&cache); + } +} + +void CacheProxyModel::resetCacheModel() { + cache.clear(); +} diff --git a/ui/qt/models/cache_proxy_model.h b/ui/qt/models/cache_proxy_model.h new file mode 100644 index 00000000..70954b59 --- /dev/null +++ b/ui/qt/models/cache_proxy_model.h @@ -0,0 +1,47 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef CACHE_PROXY_MODEL_H +#define CACHE_PROXY_MODEL_H + +#include <config.h> + +#include <QIdentityProxyModel> +#include <QStandardItemModel> + +/** + * Caches any data read access to the source model, returning an older copy if + * the source model is invalidated. + * + * Only flat data is supported at the moment, tree models (with parents) are + * unsupported. + */ +class CacheProxyModel : public QIdentityProxyModel +{ + Q_OBJECT + +public: + CacheProxyModel(QObject *parent = 0); + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + void setSourceModel(QAbstractItemModel *newSourceModel); + +private: + mutable QStandardItemModel cache; + + bool hasModel() const { return sourceModel() != &cache; } + +private slots: + void resetCacheModel(); +}; +#endif diff --git a/ui/qt/models/coloring_rules_delegate.cpp b/ui/qt/models/coloring_rules_delegate.cpp new file mode 100644 index 00000000..b86ffa97 --- /dev/null +++ b/ui/qt/models/coloring_rules_delegate.cpp @@ -0,0 +1,123 @@ +/* coloring_rules_delegate.cpp + * Delegates for editing various coloring rule fields. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/coloring_rules_delegate.h> +#include <ui/qt/models/coloring_rules_model.h> +#include <ui/qt/widgets/display_filter_edit.h> + +ColoringRulesDelegate::ColoringRulesDelegate(QObject *parent) : QStyledItemDelegate(parent) +{ +} + +QWidget* ColoringRulesDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, + const QModelIndex &index) const +{ + switch (index.column()) + { + case ColoringRulesModel::colName: + { + SyntaxLineEdit *editor = new SyntaxLineEdit(parent); + connect(editor, &SyntaxLineEdit::textChanged, this, &ColoringRulesDelegate::ruleNameChanged); + return editor; + } + + case ColoringRulesModel::colFilter: + return new DisplayFilterEdit(parent); + + default: + Q_ASSERT(FALSE); + return 0; + } + + return 0; +} + +void ColoringRulesDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + switch (index.column()) + { + case ColoringRulesModel::colName: + { + SyntaxLineEdit *syntaxEdit = static_cast<SyntaxLineEdit*>(editor); + syntaxEdit->setText(index.model()->data(index, Qt::EditRole).toString()); + break; + } + case ColoringRulesModel::colFilter: + { + DisplayFilterEdit *displayEdit = static_cast<DisplayFilterEdit*>(editor); + displayEdit->setText(index.model()->data(index, Qt::EditRole).toString()); + break; + } + default: + QStyledItemDelegate::setEditorData(editor, index); + break; + } +} + +void ColoringRulesDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + switch (index.column()) + { + case ColoringRulesModel::colName: + { + SyntaxLineEdit *syntaxEdit = static_cast<SyntaxLineEdit*>(editor); + model->setData(index, syntaxEdit->text(), Qt::EditRole); + if (syntaxEdit->syntaxState() == SyntaxLineEdit::Invalid) { + QString error_text = tr("the \"@\" symbol will be ignored."); + emit invalidField(index, error_text); + } + else + { + emit validField(index); + } + break; + } + case ColoringRulesModel::colFilter: + { + DisplayFilterEdit *displayEdit = static_cast<DisplayFilterEdit*>(editor); + model->setData(index, displayEdit->text(), Qt::EditRole); + if ((displayEdit->syntaxState() == SyntaxLineEdit::Invalid) && + (model->data(model->index(index.row(), ColoringRulesModel::colName), Qt::CheckStateRole) == Qt::Checked)) + { + model->setData(model->index(index.row(), ColoringRulesModel::colName), Qt::Unchecked, Qt::CheckStateRole); + emit invalidField(index, displayEdit->syntaxErrorMessage()); + } + else + { + emit validField(index); + } + break; + } + default: + QStyledItemDelegate::setModelData(editor, model, index); + break; + } +} + +void ColoringRulesDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex&) const +{ + editor->setGeometry(option.rect); +} + +void ColoringRulesDelegate::ruleNameChanged(const QString name) +{ + SyntaxLineEdit *name_edit = qobject_cast<SyntaxLineEdit*>(QObject::sender()); + if (!name_edit) return; + + if (name.isEmpty()) { + name_edit->setSyntaxState(SyntaxLineEdit::Empty); + } else if (name.contains("@")) { + name_edit->setSyntaxState(SyntaxLineEdit::Invalid); + } else { + name_edit->setSyntaxState(SyntaxLineEdit::Valid); + } +} diff --git a/ui/qt/models/coloring_rules_delegate.h b/ui/qt/models/coloring_rules_delegate.h new file mode 100644 index 00000000..24ba2f38 --- /dev/null +++ b/ui/qt/models/coloring_rules_delegate.h @@ -0,0 +1,43 @@ +/** @file + * + * Delegates for editing various coloring rule fields. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef COLORING_RULE_DELEGATE_H +#define COLORING_RULE_DELEGATE_H + +#include <config.h> + +#include <QStyledItemDelegate> +#include <QModelIndex> + +class ColoringRulesDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + ColoringRulesDelegate(QObject *parent = 0); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const; + + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &index) const; + +signals: + void invalidField(const QModelIndex &index, const QString& errMessage) const; + void validField(const QModelIndex &index) const; + +private slots: + void ruleNameChanged(const QString name); +}; +#endif // COLORING_RULE_DELEGATE_H diff --git a/ui/qt/models/coloring_rules_model.cpp b/ui/qt/models/coloring_rules_model.cpp new file mode 100644 index 00000000..dced9c37 --- /dev/null +++ b/ui/qt/models/coloring_rules_model.cpp @@ -0,0 +1,569 @@ +/* coloring_rules_model.cpp + * Data model for coloring rules. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "config.h" + +#include "coloring_rules_model.h" + +#include <errno.h> + +#include "ui/ws_ui_util.h" //for color_filter_add_cb + +#include <ui/qt/utils/color_utils.h> +#include <ui/qt/utils/qt_ui_utils.h> +#include <ui/qt/utils/variant_pointer.h> +#include <ui/qt/utils/wireshark_mime_data.h> + +#include <QMimeData> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonArray> + +ColoringRuleItem::ColoringRuleItem(bool disabled, QString name, QString filter, QColor foreground, QColor background, ColoringRuleItem* parent) + : ModelHelperTreeItem<ColoringRuleItem>(parent), + disabled_(disabled), + name_(name), + filter_(filter), + foreground_(foreground), + background_(background) +{ +} + +ColoringRuleItem::~ColoringRuleItem() +{ + +} + +ColoringRuleItem::ColoringRuleItem(color_filter_t *colorf, ColoringRuleItem* parent) + : ModelHelperTreeItem<ColoringRuleItem>(parent), + disabled_(colorf->disabled), + name_(colorf->filter_name), + filter_(colorf->filter_text), + foreground_(ColorUtils::fromColorT(colorf->fg_color)), + background_(ColorUtils::fromColorT(colorf->bg_color)) +{ +} + +ColoringRuleItem::ColoringRuleItem(const ColoringRuleItem& item) + : ModelHelperTreeItem<ColoringRuleItem>(item.parent_), + disabled_(item.disabled_), + name_(item.name_), + filter_(item.filter_), + foreground_(item.foreground_), + background_(item.background_) +{ +} + +ColoringRuleItem& ColoringRuleItem::operator=(ColoringRuleItem& rhs) +{ + disabled_ = rhs.disabled_; + name_ = rhs.name_; + filter_ = rhs.filter_; + foreground_ = rhs.foreground_; + background_ = rhs.background_; + return *this; +} + +// Callback for color_filters_clone. +void +color_filter_add_cb(color_filter_t *colorf, gpointer user_data) +{ + ColoringRulesModel *model = (ColoringRulesModel*)user_data; + + if (model == NULL) + return; + + model->addColor(colorf); +} + +ColoringRulesModel::ColoringRulesModel(QColor defaultForeground, QColor defaultBackground, QObject *parent) : + QAbstractItemModel(parent), + root_(new ColoringRuleItem(false, "", "", QColor(), QColor(), NULL)), + conversation_colors_(NULL), + defaultForeground_(defaultForeground), + defaultBackground_(defaultBackground) + +{ + color_filters_clone(this, color_filter_add_cb); +} + +ColoringRulesModel::~ColoringRulesModel() +{ + delete root_; + color_filter_list_delete(&conversation_colors_); +} + +GSList *ColoringRulesModel::createColorFilterList() +{ + GSList *cfl = NULL; + for (int row = 0; row < root_->childCount(); row++) + { + ColoringRuleItem* rule = root_->child(row); + if (rule == NULL) + continue; + + color_t fg = ColorUtils::toColorT(rule->foreground_); + color_t bg = ColorUtils::toColorT(rule->background_); + color_filter_t *colorf = color_filter_new(rule->name_.toUtf8().constData(), + rule->filter_.toUtf8().constData(), + &bg, &fg, rule->disabled_); + cfl = g_slist_append(cfl, colorf); + } + + return cfl; +} + +void ColoringRulesModel::addColor(color_filter_t* colorf) +{ + if (!colorf) return; + + if (strstr(colorf->filter_name, CONVERSATION_COLOR_PREFIX) != NULL) { + conversation_colors_ = g_slist_append(conversation_colors_, colorf); + } else { + int count = root_->childCount(); + + beginInsertRows(QModelIndex(), count, count); + ColoringRuleItem* item = new ColoringRuleItem(colorf, root_); + color_filter_delete(colorf); + root_->appendChild(item); + endInsertRows(); + } +} + +void ColoringRulesModel::addColor(bool disabled, QString filter, QColor foreground, QColor background) +{ + //add rule to top of the list + beginInsertRows(QModelIndex(), 0, 0); + ColoringRuleItem* item = new ColoringRuleItem(disabled, tr("New coloring rule"), filter, foreground, background, root_); + root_->prependChild(item); + endInsertRows(); +} + + +bool ColoringRulesModel::importColors(QString filename, QString& err) +{ + bool success = true; + gchar* err_msg = NULL; + if (!color_filters_import(filename.toUtf8().constData(), this, &err_msg, color_filter_add_cb)) { + err = gchar_free_to_qstring(err_msg); + success = false; + } + + return success; +} + +bool ColoringRulesModel::exportColors(QString filename, QString& err) +{ + GSList *cfl = createColorFilterList(); + bool success = true; + gchar* err_msg = NULL; + if (!color_filters_export(filename.toUtf8().constData(), cfl, FALSE, &err_msg)) { + err = gchar_free_to_qstring(err_msg); + success = false; + } + color_filter_list_delete(&cfl); + + return success; +} + +bool ColoringRulesModel::writeColors(QString& err) +{ + GSList *cfl = createColorFilterList(); + bool success = true; + gchar* err_msg = NULL; + if (!color_filters_apply(conversation_colors_, cfl, &err_msg)) { + err = gchar_free_to_qstring(err_msg); + success = false; + } + if (!color_filters_write(cfl, &err_msg)) { + err = QString(tr("Unable to save coloring rules: %1").arg(g_strerror(errno))); + success = false; + g_free(err_msg); + } + color_filter_list_delete(&cfl); + + return success; +} + +bool ColoringRulesModel::insertRows(int row, int count, const QModelIndex& parent) +{ + // sanity check insertion + if (row < 0) + return false; + + beginInsertRows(parent, row, row+(count-1)); + + for (int i = row; i < row + count; i++) + { + ColoringRuleItem* item = new ColoringRuleItem(true, tr("New coloring rule"), "", defaultForeground_, defaultBackground_, root_); + root_->insertChild(i, item); + /* Automatically enable the new coloring rule */ + setData(index(i, colName, parent), Qt::Checked, Qt::CheckStateRole); + } + + endInsertRows(); + return true; +} + +bool ColoringRulesModel::removeRows(int row, int count, const QModelIndex& parent) +{ + if (row < 0) + return false; + + beginRemoveRows(parent, row, row+(count-1)); + for (int i = row; i < row + count; i++) + { + root_->removeChild(row); + } + endRemoveRows(); + + return true; +} + +bool ColoringRulesModel::copyRow(int dst_row, int src_row) +{ + if (src_row < 0 || src_row >= rowCount() || dst_row < 0 || dst_row >= rowCount()) { + return false; + } + + ColoringRuleItem* src_item = root_->child(src_row); + if (src_item == NULL) + return false; + + ColoringRuleItem* dst_item = new ColoringRuleItem(*src_item); + if (dst_item == NULL) + return false; + + beginInsertRows(QModelIndex(), dst_row, dst_row); + root_->insertChild(dst_row, dst_item); + endInsertRows(); + + return true; +} + +Qt::ItemFlags ColoringRulesModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + switch (index.column()) + { + case colName: + flags |= (Qt::ItemIsUserCheckable|Qt::ItemIsEditable); + break; + case colFilter: + flags |= Qt::ItemIsEditable; + break; + } + + if (index.isValid()) + flags |= (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled); + else + flags |= Qt::ItemIsDropEnabled; + + return flags; +} + + +QVariant ColoringRulesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + ColoringRuleItem* rule = root_->child(index.row()); + if (rule == NULL) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + case Qt::EditRole: + switch(index.column()) + { + case colName: + return rule->name_; + case colFilter: + return rule->filter_; + } + break; + case Qt::CheckStateRole: + switch(index.column()) + { + case colName: + return rule->disabled_ ? Qt::Unchecked : Qt::Checked; + } + break; + case Qt::BackgroundRole: + return rule->background_; + case Qt::ForegroundRole: + return rule->foreground_; + } + return QVariant(); +} + +bool ColoringRulesModel::setData(const QModelIndex &dataIndex, const QVariant &value, int role) +{ + if (!dataIndex.isValid()) + return false; + + if (data(dataIndex, role) == value) { + // Data appears unchanged, do not do additional checks. + return true; + } + + ColoringRuleItem* rule = root_->child(dataIndex.row()); + if (rule == NULL) + return false; + + QModelIndex topLeft = dataIndex, + bottomRight = dataIndex; + + switch (role) + { + case Qt::EditRole: + switch (dataIndex.column()) + { + case colName: + rule->name_ = value.toString(); + break; + case colFilter: + rule->filter_ = value.toString(); + break; + default: + return false; + } + break; + case Qt::CheckStateRole: + switch (dataIndex.column()) + { + case colName: + rule->disabled_ = (value.toInt() == Qt::Checked) ? false : true; + break; + default: + return false; + } + break; + case Qt::BackgroundRole: +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + if (!value.canConvert<QColor>()) +#else + if (!value.canConvert(QVariant::Color)) +#endif + return false; + + rule->background_ = QColor(value.toString()); + break; + case Qt::ForegroundRole: +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + if (!value.canConvert<QColor>()) +#else + if (!value.canConvert(QVariant::Color)) +#endif + return false; + + rule->foreground_ = QColor(value.toString()); + break; + case Qt::UserRole: + { + ColoringRuleItem* new_rule = VariantPointer<ColoringRuleItem>::asPtr(value); + *rule = *new_rule; + topLeft = index(dataIndex.row(), colName); + bottomRight = index(dataIndex.row(), colFilter); + break; + } + default: + return false; + } + + QVector<int> roles; + roles << role; + + emit dataChanged(topLeft, bottomRight, roles); + + return true; + +} + +QVariant ColoringRulesModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) + return QVariant(); + + switch ((ColoringRulesColumn)section) { + case colName: + return tr("Name"); + case colFilter: + return tr("Filter"); + default: + break; + } + + return QVariant(); +} + +Qt::DropActions ColoringRulesModel::supportedDropActions() const +{ + return Qt::MoveAction | Qt::CopyAction; +} + +QStringList ColoringRulesModel::mimeTypes() const +{ + return QStringList() << WiresharkMimeData::ColoringRulesMimeType; +} + +QMimeData* ColoringRulesModel::mimeData(const QModelIndexList &indexes) const +{ + //if the list is empty, don't return an empty list + if (indexes.count() == 0) + return NULL; + + QMimeData *mimeData = new QMimeData(); + + QJsonArray data; + foreach (const QModelIndex & index, indexes) + { + if (index.column() == 0) + { + ColoringRuleItem * item = root_->child(index.row()); + QJsonObject entry; + entry["disabled"] = item->disabled_; + entry["name"] = item->name_; + entry["filter"] = item->filter_; + entry["foreground"] = QVariant::fromValue(item->foreground_).toString(); + entry["background"] = QVariant::fromValue(item->background_).toString(); + data.append(entry); + } + } + + QJsonObject dataSet; + dataSet["coloringrules"] = data; + QByteArray encodedData = QJsonDocument(dataSet).toJson(); + + mimeData->setData(WiresharkMimeData::ColoringRulesMimeType, encodedData); + return mimeData; +} + +bool ColoringRulesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) +{ + //clear any previous dragDrop information + dragDropRows_.clear(); + + if (action == Qt::IgnoreAction) + return true; + + if (!data->hasFormat(WiresharkMimeData::ColoringRulesMimeType) || column > 0) + return false; + + int beginRow; + + if (row != -1) + beginRow = row; + else if (parent.isValid()) + beginRow = parent.row(); + else + beginRow = rowCount(); + + QList<QVariant> rules; + + QJsonDocument encodedData = QJsonDocument::fromJson(data->data(WiresharkMimeData::ColoringRulesMimeType)); + if (! encodedData.isObject() || ! encodedData.object().contains("coloringrules")) + return false; + + QJsonArray dataArray = encodedData.object()["coloringrules"].toArray(); + + for (int datarow = 0; datarow < dataArray.count(); datarow++) + { + QJsonObject entry = dataArray.at(datarow).toObject(); + + if (! entry.contains("foreground") || ! entry.contains("background") || ! entry.contains("filter")) + continue; + + QColor fgColor = entry["foreground"].toVariant().value<QColor>(); + QColor bgColor = entry["background"].toVariant().value<QColor>(); + + ColoringRuleItem * item = new ColoringRuleItem( + entry["disabled"].toVariant().toBool(), + entry["name"].toString(), + entry["filter"].toString(), + fgColor, + bgColor, + root_); + rules.append(VariantPointer<ColoringRuleItem>::asQVariant(item)); + } + + insertRows(beginRow, static_cast<int>(rules.count()), QModelIndex()); + for (int i = 0; i < rules.count(); i++) { + QModelIndex idx = index(beginRow, 0, QModelIndex()); + setData(idx, rules[i], Qt::UserRole); + beginRow++; + } + + return true; +} + +QModelIndex ColoringRulesModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + ColoringRuleItem *parent_item, *child_item; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<ColoringRuleItem*>(parent.internalPointer()); + + Q_ASSERT(parent_item); + + child_item = parent_item->child(row); + if (child_item) { + return createIndex(row, column, child_item); + } + + return QModelIndex(); +} + +QModelIndex ColoringRulesModel::parent(const QModelIndex& indexItem) const +{ + if (!indexItem.isValid()) + return QModelIndex(); + + ColoringRuleItem* item = static_cast<ColoringRuleItem*>(indexItem.internalPointer()); + if (item != NULL) { + ColoringRuleItem* parent_item = item->parentItem(); + if (parent_item != NULL) { + if (parent_item == root_) + return QModelIndex(); + + return createIndex(parent_item->row(), 0, parent_item); + } + } + + return QModelIndex(); +} + +int ColoringRulesModel::rowCount(const QModelIndex& parent) const +{ + ColoringRuleItem *parent_item; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<ColoringRuleItem*>(parent.internalPointer()); + + if (parent_item == NULL) + return 0; + + return parent_item->childCount(); +} + +int ColoringRulesModel::columnCount(const QModelIndex&) const +{ + return colColoringRulesMax; +} diff --git a/ui/qt/models/coloring_rules_model.h b/ui/qt/models/coloring_rules_model.h new file mode 100644 index 00000000..f51a1a30 --- /dev/null +++ b/ui/qt/models/coloring_rules_model.h @@ -0,0 +1,102 @@ +/** @file + * + * Data model for coloring rules. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef COLORING_RULES_MODEL_H +#define COLORING_RULES_MODEL_H + +#include <config.h> + +#include <glib.h> +#include <epan/color_filters.h> + +#include <ui/qt/models/tree_model_helpers.h> + +#include <QList> +#include <QColor> +#include <QAbstractTableModel> +#include <QSortFilterProxyModel> + +class ColoringRuleItem : public ModelHelperTreeItem<ColoringRuleItem> +{ +public: + ColoringRuleItem(bool disabled, QString name, QString filter, QColor foreground, QColor background, ColoringRuleItem* parent); + virtual ~ColoringRuleItem(); + + ColoringRuleItem(color_filter_t *colorf, ColoringRuleItem* parent); + ColoringRuleItem(const ColoringRuleItem& item); + + bool disabled_; + QString name_; + QString filter_; + QColor foreground_; + QColor background_; + + ColoringRuleItem& operator=(ColoringRuleItem& rhs); + +}; + +class ColoringRulesModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + ColoringRulesModel(QColor defaultForeground, QColor defaultBackground, QObject *parent); + virtual ~ColoringRulesModel(); + + enum ColoringRulesColumn { + colName = 0, + colFilter, + colColoringRulesMax + }; + + void addColor(color_filter_t* colorf); + void addColor(bool disabled, QString filter, QColor foreground, QColor background); + bool importColors(QString filename, QString& err); + bool exportColors(QString filename, QString& err); + bool writeColors(QString& err); + + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, + const QModelIndex & = QModelIndex()) const; + QModelIndex parent(const QModelIndex &) const; + + //Drag & drop functionality + Qt::DropActions supportedDropActions() const; + QStringList mimeTypes() const; + QMimeData* mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool copyRow(int dst_row, int src_row); + +private: + void populate(); + struct _GSList *createColorFilterList(); + + ColoringRuleItem* root_; + //Save off the conversation colors, do not include in dialog + struct _GSList *conversation_colors_; + + QColor defaultForeground_; + QColor defaultBackground_; + + QList<int> dragDropRows_; +}; + +#endif // COLORING_RULES_MODEL_H diff --git a/ui/qt/models/column_list_model.cpp b/ui/qt/models/column_list_model.cpp new file mode 100644 index 00000000..a0e5c8ba --- /dev/null +++ b/ui/qt/models/column_list_model.cpp @@ -0,0 +1,518 @@ +/* column_list_models.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/column_list_model.h> +#include <ui/qt/utils/qt_ui_utils.h> +#include <ui/qt/widgets/field_filter_edit.h> +#include <ui/qt/widgets/syntax_line_edit.h> +#include <ui/qt/utils/wireshark_mime_data.h> + +#include <glib.h> +#include <epan/column.h> +#include <epan/prefs.h> +#include <epan/proto.h> +#include <ui/preference_utils.h> + +#include <QLineEdit> +#include <QStringList> +#include <QComboBox> + +struct ListElement +{ + QString title; + QString customFields; + int nr; + int type; + int originalType; + int occurrence; + bool displayed; + bool resolved; +}; + +static QList<ListElement> store_; + +ColumnProxyModel::ColumnProxyModel(QObject * parent) : + QSortFilterProxyModel(parent), + showDisplayedOnly_(false) +{} + +bool ColumnProxyModel::filterAcceptsRow(int source_row, const QModelIndex &/*source_parent*/) const +{ + bool displayed = false; + if (sourceModel() && + sourceModel()->index(source_row, ColumnListModel::COL_DISPLAYED).data(ColumnListModel::DisplayedState).toBool()) + displayed = true; + + if (showDisplayedOnly_ && ! displayed) + return false; + + return true; +} + +void ColumnProxyModel::setShowDisplayedOnly(bool set) +{ + showDisplayedOnly_ = set; + invalidateFilter(); +} + +ColumnTypeDelegate::ColumnTypeDelegate(QObject * parent) : + QStyledItemDelegate(parent) +{} + +QWidget *ColumnTypeDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QWidget *editor = nullptr; + + if (index.column() == ColumnListModel::COL_TYPE) + { + QComboBox *cb_editor = new QComboBox(parent); + + for (int i = 0; i < NUM_COL_FMTS; i++) + { + cb_editor->addItem(col_format_desc(i), QVariant(i)); + if (i == index.data().toInt()) + cb_editor->setCurrentIndex(i); + } + + cb_editor->setFrame(false); + editor = cb_editor; + } + else if (index.column() == ColumnListModel::COL_FIELDS) + { + FieldFilterEdit * ff_editor = new FieldFilterEdit(parent); + connect(ff_editor, &FieldFilterEdit::textChanged, ff_editor, &FieldFilterEdit::checkCustomColumn); + ff_editor->setText(index.data().toString()); + editor = ff_editor; + } + else if (index.column() == ColumnListModel::COL_OCCURRENCE) + { + SyntaxLineEdit * sl_editor = new SyntaxLineEdit(parent); + connect(sl_editor, &SyntaxLineEdit::textChanged, sl_editor, &SyntaxLineEdit::checkInteger); + sl_editor->setText(index.data().toString()); + editor = sl_editor; + } + + if (!editor) { + editor = QStyledItemDelegate::createEditor(parent, option, index); + } + editor->setAutoFillBackground(true); + return editor; +} + +void ColumnTypeDelegate::setEditorData(QWidget *editor, + const QModelIndex &index) const +{ + QVariant data = index.model()->data(index); + if (index.column() == ColumnListModel::COL_TYPE) + { + QComboBox *comboBox = static_cast<QComboBox*>(editor); + comboBox->setCurrentText(data.toString()); + } + else if (index.column() == ColumnListModel::COL_FIELDS) + { + if (qobject_cast<FieldFilterEdit *>(editor)) + qobject_cast<FieldFilterEdit *>(editor)->setText(data.toString()); + } + else if (index.column() == ColumnListModel::COL_OCCURRENCE) + { + if (qobject_cast<SyntaxLineEdit *>(editor)) + qobject_cast<SyntaxLineEdit *>(editor)->setText(data.toString()); + } + else + { + if (qobject_cast<QLineEdit *>(editor)) + qobject_cast<QLineEdit *>(editor)->setText(data.toString()); + } +} + +void ColumnTypeDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + if (index.column() == ColumnListModel::COL_TYPE) + { + QComboBox *comboBox = static_cast<QComboBox*>(editor); + bool ok = false; + int value = comboBox->currentData().toInt(&ok); + + if (ok) + model->setData(index, value, Qt::EditRole); + } + else if (index.column() == ColumnListModel::COL_FIELDS) + { + FieldFilterEdit * ffe = qobject_cast<FieldFilterEdit *>(editor); + if (ffe) + { + if (ffe->syntaxState() == SyntaxLineEdit::Valid) { + QModelIndex typeIndex = index.sibling(index.row(), ColumnListModel::COL_TYPE); + model->setData(typeIndex, COL_CUSTOM, Qt::EditRole); + model->setData(index, ffe->text(), Qt::EditRole); + } + else + { + ffe->setText(index.data().toString()); + } + } + + if (index.data().toString().length() == 0) + { + QModelIndex typeIndex = index.sibling(index.row(), ColumnListModel::COL_TYPE); + model->setData(typeIndex, index.data(ColumnListModel::OriginalType).toInt(), Qt::EditRole); + + } + } + else if (index.column() == ColumnListModel::COL_OCCURRENCE) + { + SyntaxLineEdit * sle = qobject_cast<SyntaxLineEdit *>(editor); + bool ok = false; + if (sle) + { + sle->checkInteger(index.data().toString()); + if (sle->syntaxState() == SyntaxLineEdit::Valid) + ok = true; + } + + if (ok) + { + QModelIndex typeIndex = index.sibling(index.row(), ColumnListModel::COL_TYPE); + model->setData(typeIndex, COL_CUSTOM, Qt::EditRole); + model->setData(index, sle->text(), Qt::EditRole); + } + else if (sle) + { + sle->setText(index.data().toString()); + } + + if (index.data().toString().length() == 0) + { + QModelIndex typeIndex = index.sibling(index.row(), ColumnListModel::COL_TYPE); + model->setData(typeIndex, index.data(ColumnListModel::OriginalType).toInt(), Qt::EditRole); + + } + } + else + QStyledItemDelegate::setModelData(editor, model, index); +} + +void ColumnTypeDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &/* index */) const +{ + editor->setGeometry(option.rect); +} + +ColumnListModel::ColumnListModel(QObject * parent): + QAbstractTableModel(parent) +{ + populate(); +} + +QVariant ColumnListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section > ColumnListModel::COL_RESOLVED || orientation != Qt::Horizontal || + role != Qt::DisplayRole) + return QVariant(); + + return headerTitle(section); +} + +int ColumnListModel::rowCount(const QModelIndex &/*parent*/) const +{ + return static_cast<int>(store_.count()); +} + +int ColumnListModel::columnCount(const QModelIndex &/*parent*/) const +{ + return ColumnListModel::COL_RESOLVED + 1; +} + +QString ColumnListModel::headerTitle(int section) const +{ + switch (section) + { + case ColumnListModel::COL_DISPLAYED: + return tr("Displayed"); + case ColumnListModel::COL_TITLE: + return tr("Title"); + case ColumnListModel::COL_TYPE: + return tr("Type"); + case ColumnListModel::COL_FIELDS: + return tr("Fields"); + case ColumnListModel::COL_OCCURRENCE: + return tr("Field Occurrence"); + case ColumnListModel::COL_RESOLVED: + return tr("Resolved"); + } + + return QString(); +} + +void ColumnListModel::populate() +{ + store_.clear(); + + int nr = 0; + + for (GList *cur = g_list_first(prefs.col_list); cur != NULL && cur->data != NULL; cur = cur->next) { + fmt_data *cfmt = (fmt_data *) cur->data; + ListElement ne; + ne.nr = nr; + ne.displayed = cfmt->visible; + ne.title = cfmt->title; + ne.type = ne.originalType = cfmt->fmt; + ne.customFields = cfmt->custom_fields; + ne.occurrence = cfmt->custom_occurrence; + ne.resolved = cfmt->resolved; + + nr++; + store_ << ne; + } +} + +QVariant ColumnListModel::data(const QModelIndex &index, int role) const +{ + if (! index.isValid() || index.column() >= store_.count()) + return QVariant(); + + ListElement ne = store_.at(index.row()); + + if (role == Qt::DisplayRole) + { + switch (index.column()) + { + case COL_DISPLAYED: + case COL_RESOLVED: + return QVariant(); + case ColumnListModel::COL_TITLE: + return ne.title; + case ColumnListModel::COL_TYPE: + return col_format_desc(ne.type); + case ColumnListModel::COL_FIELDS: + return ne.customFields; + case ColumnListModel::COL_OCCURRENCE: + return ne.customFields.length() > 0 ? QVariant::fromValue(ne.occurrence) : QVariant(); + } + } + else if (role == Qt::CheckStateRole) + { + if (index.column() == COL_DISPLAYED) + { + return ne.displayed ? Qt::Checked : Qt::Unchecked; + } + else if (index.column() == COL_RESOLVED) + { + QModelIndex fieldsIndex = index.sibling(index.row(), ColumnListModel::COL_FIELDS); + if (column_prefs_custom_resolve(fieldsIndex.data().toString().toUtf8().constData())) { + return ne.resolved ? Qt::Checked : Qt::Unchecked; + } + } + } + else if (role == Qt::ToolTipRole) + { + if (index.column() == COL_RESOLVED) + { + return tr("<html>Show human-readable strings instead of raw values for fields. Only applicable to custom columns with fields that have value strings.</html>"); + } + } + else if (role == OriginalType) + return QVariant::fromValue(ne.originalType); + else if (role == DisplayedState) + return QVariant::fromValue(ne.displayed); + + return QVariant(); +} + +Qt::ItemFlags ColumnListModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index); + if (index.isValid() && index.row() < store_.count()) + { + ListElement ne = store_.at(index.row()); + + Qt::ItemFlags flags = Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; + + if (index.column() == COL_DISPLAYED || index.column() == COL_RESOLVED) { + flags |= Qt::ItemIsUserCheckable; + } else { + flags |= Qt::ItemIsEditable; + } + + return flags; + } + else + return Qt::ItemIsDropEnabled | defaultFlags; +} + +QStringList ColumnListModel::mimeTypes() const +{ + return QStringList() << WiresharkMimeData::ColumnListMimeType; +} + +QMimeData *ColumnListModel::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *mimeData = new QMimeData; + + int row = -1; + if (indexes.count() > 0) + row = indexes.at(0).row(); + + mimeData->setData(WiresharkMimeData::ColumnListMimeType, QString::number(row).toUtf8()); + return mimeData; +} + +bool ColumnListModel::canDropMimeData(const QMimeData *data, + Qt::DropAction /* action */, int /* row */, int /* column */, const QModelIndex &parent) const +{ + if (parent.isValid() || ! data->hasFormat(WiresharkMimeData::ColumnListMimeType)) + return false; + + return true; +} + +bool ColumnListModel::dropMimeData(const QMimeData *data, + Qt::DropAction action, int row, int column, const QModelIndex &parent) +{ + int moveTo; + + if (!canDropMimeData(data, action, row, column, parent)) + return false; + + if (action == Qt::IgnoreAction || parent.isValid()) + return true; + + if (row != -1) + moveTo = row; + else + moveTo = rowCount(QModelIndex()); + + bool ok = false; + int moveFrom = QString(data->data(WiresharkMimeData::ColumnListMimeType)).toInt(&ok); + if (! ok) + return false; + + if (moveFrom < moveTo) + moveTo = moveTo - 1; + + if (moveTo >= store_.count()) + moveTo = static_cast<int>(store_.count()) - 1; + + beginResetModel(); + store_.move(moveFrom, moveTo); + endResetModel(); + + return true; +} + +Qt::DropActions ColumnListModel::supportedDropActions() const +{ + return Qt::MoveAction; +} + +bool ColumnListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || ! value.isValid()) + return false; + + bool change = false; + if (role == Qt::CheckStateRole && index.column() == ColumnListModel::COL_DISPLAYED) + { + store_[index.row()].displayed = value.toInt() == Qt::Checked ? true : false; + change = true; + } + else if (index.column() == ColumnListModel::COL_TYPE) + { + bool ok = false; + int val = value.toInt(&ok); + if (ok) + store_[index.row()].type = val; + } + else if (index.column() == ColumnListModel::COL_TITLE) + { + store_[index.row()].title = value.toString(); + } + else if (index.column() == ColumnListModel::COL_FIELDS) + { + store_[index.row()].customFields = value.toString(); + } + else if (index.column() == ColumnListModel::COL_OCCURRENCE) + { + bool ok = false; + int val = value.toInt(&ok); + if (ok) + store_[index.row()].occurrence = val; + } + else if (role == Qt::CheckStateRole && index.column() == ColumnListModel::COL_RESOLVED) + { + store_[index.row()].resolved = value.toInt() == Qt::Checked ? true : false; + } + + if (change) + emit dataChanged(index, index); + + return change; +} + +void ColumnListModel::saveColumns() +{ + GList *new_col_list = Q_NULLPTR; + + for (int row = 0; row < store_.count(); row++) + { + fmt_data * cfmt = g_new0(fmt_data, 1); + ListElement elem = store_.at(row); + + cfmt->title = qstring_strdup(elem.title); + cfmt->visible = elem.displayed; + cfmt->fmt = elem.type; + cfmt->resolved = TRUE; + if (cfmt->fmt == COL_CUSTOM) + { + cfmt->custom_fields = qstring_strdup(elem.customFields); + cfmt->custom_occurrence = elem.occurrence; + cfmt->resolved = elem.resolved; + } + + new_col_list = g_list_append(new_col_list, cfmt); + } + + while (prefs.col_list) + column_prefs_remove_link(prefs.col_list); + + prefs.col_list = new_col_list; +} + +void ColumnListModel::addEntry() +{ + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + ListElement elem; + elem.nr = rowCount(); + elem.title = tr("New Column"); + elem.displayed = true; + elem.type = elem.originalType = COL_NUMBER; + elem.occurrence = 0; + elem.customFields = QString(); + elem.resolved = true; + store_ << elem; + endInsertRows(); +} + +void ColumnListModel::deleteEntry(int row) +{ + beginRemoveRows(QModelIndex(), row, row); + store_.removeAt(row); + endRemoveRows(); +} + +void ColumnListModel::reset() +{ + beginResetModel(); + populate(); + endResetModel(); +} diff --git a/ui/qt/models/column_list_model.h b/ui/qt/models/column_list_model.h new file mode 100644 index 00000000..c4739f96 --- /dev/null +++ b/ui/qt/models/column_list_model.h @@ -0,0 +1,96 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef COLUMN_LIST_MODELS_H +#define COLUMN_LIST_MODELS_H + +#include <QAbstractListModel> +#include <QSortFilterProxyModel> +#include <QStyledItemDelegate> +#include <QSortFilterProxyModel> +#include <QMimeData> + +class ColumnProxyModel : public QSortFilterProxyModel +{ +public: + ColumnProxyModel(QObject *parent = Q_NULLPTR); + + void setShowDisplayedOnly(bool set); + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +private: + bool showDisplayedOnly_; +}; + +class ColumnTypeDelegate : public QStyledItemDelegate +{ +public: + ColumnTypeDelegate(QObject * parent = Q_NULLPTR); + + QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const override; + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; + +class ColumnListModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + ColumnListModel(QObject * parent = Q_NULLPTR); + + enum { + COL_DISPLAYED, + COL_TITLE, + COL_TYPE, + COL_FIELDS, + COL_OCCURRENCE, + COL_RESOLVED + }; + + enum { + OriginalType = Qt::UserRole, + DisplayedState + }; + + void saveColumns(); + + void addEntry(); + void deleteEntry(int row); + void reset(); + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + + virtual QStringList mimeTypes() const; + virtual QMimeData *mimeData(const QModelIndexList &indexes) const; + virtual Qt::DropActions supportedDropActions() const; + virtual bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const; + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + +private: + QString headerTitle(int section) const; + + void populate(); +}; + +#endif // COLUMN_LIST_MODELS_H diff --git a/ui/qt/models/credentials_model.cpp b/ui/qt/models/credentials_model.cpp new file mode 100644 index 00000000..8bc649f1 --- /dev/null +++ b/ui/qt/models/credentials_model.cpp @@ -0,0 +1,155 @@ +/* + * credentials_model.h + * + * Copyright 2019 - Dario Lombardo <lomato@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "credentials_model.h" + +#include <file.h> +#include <ui/qt/utils/qt_ui_utils.h> + +CredentialsModel::CredentialsModel(QObject *parent) + :QAbstractListModel(parent) +{ +} + +CredentialsModel::~CredentialsModel() +{ + clear(); +} + +int CredentialsModel::rowCount(const QModelIndex &) const +{ + return static_cast<int>(credentials_.count()); +} + +int CredentialsModel::columnCount(const QModelIndex &) const +{ + return COL_INFO + 1; +} + +QVariant CredentialsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + tap_credential_t * auth = credentials_.at(index.row()); + if (!auth) + return QVariant(); + + + if (role == Qt::DisplayRole) { + switch (index.column()) { + case COL_NUM: + return QVariant::fromValue(auth->num); + case COL_PROTO: + return QString(auth->proto); + case COL_USERNAME: + return QString(auth->username); + case COL_INFO: + return QString(auth->info); + default: + return QVariant(); + } + } + + if (role == Qt::UserRole) { + switch (index.column()) { + case COL_NUM: + if (auth->num > 0) + return QVariant::fromValue(auth->num); + break; + case COL_USERNAME: + if (auth->username_num > 0) + return QVariant::fromValue(auth->username_num); + break; + default: + return QVariant(); + } + } + + if (role == CredentialsModel::ColumnHFID) + return QVariant::fromValue(auth->password_hf_id); + + if (role == Qt::ToolTipRole) { + const QString select_msg(tr("Click to select the packet")); + switch (index.column()) { + case COL_NUM: + if (auth->num > 0) + return select_msg; + break; + case COL_USERNAME: + if (auth->username_num > 0) { + if (auth->username_num != auth->num) + return QString(tr("Click to select the packet with username")); + else + return select_msg; + } else { + return QString(tr("Username not available")); + } + break; + default: + return QVariant(); + } + } + + return QVariant(); +} + +void CredentialsModel::addRecord(const tap_credential_t* auth) +{ + emit beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1); + + tap_credential_t* clone = new tap_credential_t; + clone->num = auth->num; + clone->username_num = auth->username_num; + clone->password_hf_id = auth->password_hf_id; + clone->username = qstring_strdup(auth->username); + clone->proto = auth->proto; + clone->info = qstring_strdup(auth->info); + credentials_.append(clone); + + emit endInsertRows(); +} + +void CredentialsModel::clear() +{ + if (!credentials_.isEmpty()) { + emit beginRemoveRows(QModelIndex(), 0, rowCount() - 1); + for (QList<tap_credential_t*>::iterator itr = credentials_.begin(); itr != credentials_.end(); ++itr) { + g_free((*itr)->username); + g_free((*itr)->info); + delete *itr; + } + credentials_.clear(); + emit endRemoveRows(); + } +} + +QVariant CredentialsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) { + switch (section) { + case COL_NUM: + return QString(tr("Packet No.")); + case COL_PROTO: + return QString(tr("Protocol")); + case COL_USERNAME: + return QString(tr("Username")); + case COL_INFO: + return QString(tr("Additional Info")); + } + } + + return QVariant(); +} diff --git a/ui/qt/models/credentials_model.h b/ui/qt/models/credentials_model.h new file mode 100644 index 00000000..cc0f4d3d --- /dev/null +++ b/ui/qt/models/credentials_model.h @@ -0,0 +1,53 @@ +/** @file + * + * Copyright 2019 - Dario Lombardo <lomato@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef CREDENTIALS_MODELS_H +#define CREDENTIALS_MODELS_H + +#include <QAbstractListModel> +#include <QList> + +#include <epan/tap.h> +#include <capture_file.h> +#include <ui/tap-credentials.h> + +class CredentialsModel : public QAbstractListModel +{ + Q_OBJECT + +public: + CredentialsModel(QObject *parent); + ~CredentialsModel(); + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const ; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + void addRecord(const tap_credential_t *rec); + void clear(); + + enum { + COL_NUM, + COL_PROTO, + COL_USERNAME, + COL_INFO + }; + + enum { + ColumnHFID = Qt::UserRole + 1 + }; + +private: + QList<tap_credential_t*> credentials_; + +}; + +#endif // CREDENTIALS_MODELS_H diff --git a/ui/qt/models/decode_as_delegate.cpp b/ui/qt/models/decode_as_delegate.cpp new file mode 100644 index 00000000..3c300a79 --- /dev/null +++ b/ui/qt/models/decode_as_delegate.cpp @@ -0,0 +1,390 @@ +/* decode_as_delegate.cpp + * Delegates for editing various field types in a Decode As record. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "decode_as_delegate.h" + +#include "epan/decode_as.h" +#include "epan/epan_dissect.h" + +#include <ui/qt/utils/variant_pointer.h> + +#include <QComboBox> +#include <QEvent> +#include <QLineEdit> +#include <QTreeView> + +typedef struct _dissector_info_t { + QString proto_name; + dissector_handle_t dissector_handle; +} dissector_info_t; + +Q_DECLARE_METATYPE(dissector_info_t *) + +DecodeAsDelegate::DecodeAsDelegate(QObject *parent, capture_file *cf) + : QStyledItemDelegate(parent), + cap_file_(cf) +{ + cachePacketProtocols(); +} + +DecodeAsItem* DecodeAsDelegate::indexToField(const QModelIndex &index) const +{ + const QVariant v = index.model()->data(index, Qt::UserRole); + return static_cast<DecodeAsItem*>(v.value<void *>()); +} + +void DecodeAsDelegate::cachePacketProtocols() +{ + //cache the list of potential decode as protocols in the current packet + if (cap_file_ && cap_file_->edt) { + + wmem_list_frame_t * protos = wmem_list_head(cap_file_->edt->pi.layers); + guint8 curr_layer_num = 1; + + while (protos != NULL) { + int proto_id = GPOINTER_TO_INT(wmem_list_frame_data(protos)); + const gchar * proto_name = proto_get_protocol_filter_name(proto_id); + for (GList *cur = decode_as_list; cur; cur = cur->next) { + decode_as_t *entry = (decode_as_t *) cur->data; + if (g_strcmp0(proto_name, entry->name) == 0) { + packet_proto_data_t proto_data; + + proto_data.table_ui_name = get_dissector_table_ui_name(entry->table_name); + proto_data.proto_name = proto_name; + proto_data.curr_layer_num = curr_layer_num; + + packet_proto_list_.append(proto_data); + } + } + protos = wmem_list_frame_next(protos); + curr_layer_num++; + } + } +} + +void DecodeAsDelegate::collectDAProtocols(QSet<QString>& all_protocols, QList<QString>& current_list) const +{ + // If a packet is selected group its tables at the top in order + // from last-dissected to first. + + //gather the initial list + for (GList *cur = decode_as_list; cur; cur = cur->next) { + decode_as_t *entry = (decode_as_t *) cur->data; + const char *table_name = get_dissector_table_ui_name(entry->table_name); + if (table_name) { + all_protocols.insert(get_dissector_table_ui_name(entry->table_name)); + } + } + + //filter out those in selected packet + foreach(packet_proto_data_t proto, packet_proto_list_) + { + current_list.append(proto.table_ui_name); + all_protocols.remove(proto.table_ui_name); + } +} + +//Determine if there are multiple values in the selector field that would +//correspond to using a combo box +bool DecodeAsDelegate::isSelectorCombo(DecodeAsItem* item) const +{ + const gchar *proto_name = NULL; + + foreach(packet_proto_data_t proto, packet_proto_list_) + { + if (g_strcmp0(proto.table_ui_name, item->tableUIName()) == 0) { + proto_name = proto.proto_name; + break; + } + } + + for (GList *cur = decode_as_list; cur; cur = cur->next) { + decode_as_t *entry = (decode_as_t *) cur->data; + if ((g_strcmp0(proto_name, entry->name) == 0) && + (g_strcmp0(item->tableName(), entry->table_name) == 0) && + (cap_file_ && cap_file_->edt)) { + return true; + } + } + + return false; +} + +void DecodeAsDelegate::decodeAddProtocol(const gchar *, const gchar *proto_name, gpointer value, gpointer user_data) +{ + QList<dissector_info_t*>* proto_list = (QList<dissector_info_t*>*)user_data; + + if (!proto_list) + return; + + dissector_info_t *dissector_info = new dissector_info_t(); + dissector_info->proto_name = proto_name; + dissector_info->dissector_handle = (dissector_handle_t) value; + + proto_list->append(dissector_info); +} + +QWidget* DecodeAsDelegate::createEditor(QWidget *parentWidget, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + DecodeAsItem* item = indexToField(index); + QWidget *editor = nullptr; + + switch(index.column()) + { + case DecodeAsModel::colTable: + { + QComboBox *cb_editor = new QComboBox(parentWidget); + QSet<QString> da_set; + QList<QString> packet_list; + QString table_ui_name; + + collectDAProtocols(da_set, packet_list); + + cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents); + + //put the protocols from the packet first in the combo box + foreach (table_ui_name, packet_list) { + cb_editor->addItem(table_ui_name, table_ui_name); + } + if (packet_list.count() > 0) { + cb_editor->insertSeparator(static_cast<int>(packet_list.count())); + } + + //put the rest of the protocols in the combo box + QList<QString> da_list = da_set.values(); + std::sort(da_list.begin(), da_list.end()); + + foreach (table_ui_name, da_list) { + cb_editor->addItem(table_ui_name, table_ui_name); + } + + //Make sure the combo box is at least as wide as the column + QTreeView* parentTree = (QTreeView*)parent(); + int protoColWidth = parentTree->columnWidth(index.column()); + if (protoColWidth > cb_editor->size().width()) + cb_editor->setFixedWidth(protoColWidth); + + editor = cb_editor; + break; + } + case DecodeAsModel::colSelector: + { + QComboBox *cb_editor = NULL; + const gchar *proto_name = NULL; + bool edt_present = cap_file_ && cap_file_->edt; + gint8 curr_layer_num_saved = edt_present ? cap_file_->edt->pi.curr_layer_num : 0; + QList<guint8> proto_layers; + + foreach(packet_proto_data_t proto, packet_proto_list_) + { + if (g_strcmp0(proto.table_ui_name, item->tableUIName()) == 0) { + if (edt_present) { + proto_layers << proto.curr_layer_num; + } + proto_name = proto.proto_name; + } + } + + for (GList *cur = decode_as_list; cur; cur = cur->next) { + decode_as_t *entry = (decode_as_t *) cur->data; + if ((g_strcmp0(proto_name, entry->name) == 0) && + (g_strcmp0(item->tableName(), entry->table_name) == 0)) { + if (edt_present) { + //create a combobox to add the entries from the packet + cb_editor = new QComboBox(parentWidget); + + //Don't limit user to just what's in combo box + cb_editor->setEditable(true); + + cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents); + + //add the current value of the column + const QString& current_value = index.model()->data(index, Qt::EditRole).toString(); + if (!current_value.isEmpty()) + cb_editor->addItem(current_value); + + //get the value(s) from the packet + foreach(guint8 current_layer, proto_layers) { + cap_file_->edt->pi.curr_layer_num = current_layer; + for (uint ni = 0; ni < entry->num_items; ni++) { + if (entry->values[ni].num_values == 1) { // Skip over multi-value ("both") entries + QString entryStr = DecodeAsModel::entryString(entry->table_name, + entry->values[ni].build_values[0](&cap_file_->edt->pi)); + //don't duplicate entries + if (cb_editor->findText(entryStr) < 0) + cb_editor->addItem(entryStr); + } + } + } + cap_file_->edt->pi.curr_layer_num = curr_layer_num_saved; + cb_editor->setCurrentIndex(entry->default_index_value); + + //Make sure the combo box is at least as wide as the column + QTreeView* parentTree = (QTreeView*)parent(); + int protoColWidth = parentTree->columnWidth(index.column()); + if (protoColWidth > cb_editor->size().width()) + cb_editor->setFixedWidth(protoColWidth); + } + break; + } + } + + //if there isn't a need for a combobox, just let user have a text box for direct edit + if (cb_editor) { + editor = cb_editor; + } else { + editor = QStyledItemDelegate::createEditor(parentWidget, option, index); + } + break; + } + + case DecodeAsModel::colProtocol: + { + QComboBox *cb_editor = new QComboBox(parentWidget); + QList<dissector_info_t*> protocols; + + cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents); + + for (GList *cur = decode_as_list; cur; cur = cur->next) { + decode_as_t *entry = (decode_as_t *) cur->data; + if (g_strcmp0(item->tableName(), entry->table_name) == 0) { + entry->populate_list(entry->table_name, decodeAddProtocol, &protocols); + break; + } + } + + // Sort by description in a human-readable way (case-insensitive, etc) + std::sort(protocols.begin(), protocols.end(), [](dissector_info_t* d1, dissector_info_t* d2) { + return d1->proto_name.localeAwareCompare(d2->proto_name) < 0; + }); + + cb_editor->addItem(DECODE_AS_NONE); + cb_editor->insertSeparator(cb_editor->count()); + + for (dissector_info_t* protocol : protocols) + { + // Make it easy to reset to the default dissector + if (protocol->proto_name == item->defaultDissector()) { + cb_editor->insertItem(0, protocol->proto_name, VariantPointer<dissector_info_t>::asQVariant(protocol)); + } else { + cb_editor->addItem(protocol->proto_name, VariantPointer<dissector_info_t>::asQVariant(protocol)); + } + } + + //Make sure the combo box is at least as wide as the column + QTreeView* parentTree = (QTreeView*)parent(); + int protoColWidth = parentTree->columnWidth(index.column()); + if (protoColWidth > cb_editor->size().width()) + cb_editor->setFixedWidth(protoColWidth); + + editor = cb_editor; + break; + } + } + + if (editor) { + editor->setAutoFillBackground(true); + } + return editor; +} + +void DecodeAsDelegate::destroyEditor(QWidget *editor, const QModelIndex &index) const +{ + if (index.column() == DecodeAsModel::colProtocol) { + QComboBox *cb_editor = (QComboBox*)editor; + for (int i=0; i < cb_editor->count(); ++i) { + delete VariantPointer<dissector_info_t>::asPtr(cb_editor->itemData(i)); + } + } + QStyledItemDelegate::destroyEditor(editor, index); +} + +void DecodeAsDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + DecodeAsItem* item = indexToField(index); + + switch(index.column()) + { + case DecodeAsModel::colTable: + case DecodeAsModel::colProtocol: + { + QComboBox *combobox = static_cast<QComboBox *>(editor); + const QString &data = index.model()->data(index, Qt::EditRole).toString(); + combobox->setCurrentText(data); + } + break; + case DecodeAsModel::colSelector: + if (isSelectorCombo(item)) { + QComboBox *combobox = static_cast<QComboBox *>(editor); + const QString &data = index.model()->data(index, Qt::EditRole).toString(); + combobox->setCurrentText(data); + } + else { + QStyledItemDelegate::setEditorData(editor, index); + } + break; + default: + QStyledItemDelegate::setEditorData(editor, index); + break; + } +} + +void DecodeAsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + DecodeAsItem* item = indexToField(index); + + switch(index.column()) + { + case DecodeAsModel::colTable: + { + QComboBox *combobox = static_cast<QComboBox *>(editor); + const QString &data = combobox->currentText(); + model->setData(index, data, Qt::EditRole); + break; + } + case DecodeAsModel::colSelector: + if (isSelectorCombo(item)) { + QComboBox *combobox = static_cast<QComboBox *>(editor); + const QString &data = combobox->currentText(); + model->setData(index, data, Qt::EditRole); + } else { + QStyledItemDelegate::setModelData(editor, model, index); + } + break; + case DecodeAsModel::colProtocol: + { + QComboBox *combobox = static_cast<QComboBox *>(editor); + + //set the dissector handle + QVariant var = combobox->itemData(combobox->currentIndex()); + dissector_info_t* dissector_info = VariantPointer<dissector_info_t>::asPtr(var); + if (dissector_info != NULL) { + model->setData(index, VariantPointer<dissector_handle>::asQVariant(dissector_info->dissector_handle), Qt::EditRole); + } else { + model->setData(index, QVariant(), Qt::EditRole); + } + break; + } + default: + QStyledItemDelegate::setModelData(editor, model, index); + break; + } +} + +#if 0 +// Qt docs suggest overriding updateEditorGeometry, but the defaults seem sane. +void UatDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyledItemDelegate::updateEditorGeometry(editor, option, index); +} +#endif diff --git a/ui/qt/models/decode_as_delegate.h b/ui/qt/models/decode_as_delegate.h new file mode 100644 index 00000000..d0457764 --- /dev/null +++ b/ui/qt/models/decode_as_delegate.h @@ -0,0 +1,59 @@ +/** @file + * + * Delegates for editing various field types in a Decode As record. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef DECODE_AS_DELEGATE_H +#define DECODE_AS_DELEGATE_H + +#include <config.h> +#include <glib.h> + +#include "cfile.h" + +#include <QStyledItemDelegate> +#include <QSet> +#include <QList> +#include <ui/qt/models/decode_as_model.h> + +typedef struct _packet_proto_data_t { + const gchar* proto_name; + const gchar* table_ui_name; + guint8 curr_layer_num; +} packet_proto_data_t; + +class DecodeAsDelegate : public QStyledItemDelegate +{ +public: + DecodeAsDelegate(QObject *parent = 0, capture_file *cf = NULL); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + void destroyEditor(QWidget *editor, const QModelIndex &index) const override; + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const override; + +#if 0 + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &index) const; +#endif + +private: + DecodeAsItem *indexToField(const QModelIndex &index) const; + void collectDAProtocols(QSet<QString>& all_protocols, QList<QString>& current_list) const; + void cachePacketProtocols(); + bool isSelectorCombo(DecodeAsItem* item) const; + + static void decodeAddProtocol(const gchar *table_name, const gchar *proto_name, gpointer value, gpointer user_data); + + capture_file *cap_file_; + QList<packet_proto_data_t> packet_proto_list_; +}; +#endif // DECODE_AS_DELEGATE_H diff --git a/ui/qt/models/decode_as_model.cpp b/ui/qt/models/decode_as_model.cpp new file mode 100644 index 00000000..67e98b75 --- /dev/null +++ b/ui/qt/models/decode_as_model.cpp @@ -0,0 +1,849 @@ +/* decode_as_model.cpp + * Data model for Decode As records. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <errno.h> + +#include "decode_as_model.h" +#include <epan/to_str.h> +#include <epan/decode_as.h> +#include <epan/epan_dissect.h> +#include <epan/packet.h> +#include <epan/prefs.h> +#include <epan/prefs-int.h> +#include <epan/dissectors/packet-dcerpc.h> + +#include <ui/qt/utils/qt_ui_utils.h> +#include <ui/qt/utils/variant_pointer.h> +#include <wsutil/file_util.h> +#include <wsutil/ws_assert.h> + +#include <QVector> + +static const char *DEFAULT_TABLE = "tcp.port"; // Arbitrary +static const char *DEFAULT_UI_TABLE = "TCP port"; // Arbitrary + +DecodeAsItem::DecodeAsItem(const char* table_name, gconstpointer selector) : + tableName_(DEFAULT_TABLE), + tableUIName_(DEFAULT_UI_TABLE), + selectorUint_(0), + selectorString_(""), + selectorDCERPC_(NULL), + default_dissector_(DECODE_AS_NONE), + current_dissector_(DECODE_AS_NONE), + dissector_handle_(NULL) +{ + if (table_name == nullptr) + return; + + init(table_name, selector); +} + +DecodeAsItem::DecodeAsItem(const decode_as_t *entry, gconstpointer selector) : + tableName_(DEFAULT_TABLE), + tableUIName_(DEFAULT_UI_TABLE), + selectorUint_(0), + selectorString_(""), + selectorDCERPC_(NULL), + default_dissector_(DECODE_AS_NONE), + current_dissector_(DECODE_AS_NONE), + dissector_handle_(NULL) +{ + if (entry == nullptr) + return; + + init(entry->table_name, selector); +} + +DecodeAsItem::~DecodeAsItem() +{ +} + +void DecodeAsItem::init(const char* table_name, gconstpointer selector) +{ + tableName_ = table_name; + tableUIName_ = get_dissector_table_ui_name(tableName_); + + dissector_handle_t default_handle = NULL; + ftenum_t selector_type = get_dissector_table_selector_type(tableName_); + if (FT_IS_STRING(selector_type)) { + if (selector != NULL) { + default_handle = dissector_get_default_string_handle(tableName_, (const gchar*)selector); + selectorString_ = QString((const char*)selector); + } + } else if (FT_IS_UINT(selector_type)) { + if (selector != NULL) { + selectorUint_ = GPOINTER_TO_UINT(selector); + default_handle = dissector_get_default_uint_handle(tableName_, selectorUint_); + } + } else if (selector_type == FT_NONE) { + // There is no default for an FT_NONE dissector table + } else if (selector_type == FT_GUID) { + /* Special handling for DCE/RPC dissectors */ + if (strcmp(tableName_, DCERPC_TABLE_NAME) == 0) { + selectorDCERPC_ = (decode_dcerpc_bind_values_t*)(selector); + } + } + + if (default_handle != NULL) { + default_dissector_ = dissector_handle_get_description(default_handle); + // When adding a new record, we set the "current" values equal to + // the default, so the user can easily reset the value. + // The existing value read from the prefs file should already + // be added to the table from reading the prefs file. + // When reading existing values the current dissector should be + // set explicitly to the actual current value. + current_dissector_ = default_dissector_; + dissector_handle_ = default_handle; + } +} + +void DecodeAsItem::setTable(const decode_as_t *entry) +{ + if (entry == nullptr) + return; + + tableName_ = entry->table_name; + tableUIName_ = get_dissector_table_ui_name(entry->table_name); + + /* XXX: Should the selector values be reset (e.g., to 0 and "") + * What if someone tries to change the table to the DCERPC table? + * That doesn't really work without the DCERPC special handling. + */ + + updateHandles(); +} + +void DecodeAsItem::setSelector(const QString &value) +{ + ftenum_t selector_type = get_dissector_table_selector_type(tableName_); + + if (FT_IS_STRING(selector_type)) { + selectorString_ = value; + } else if (FT_IS_UINT(selector_type)) { + selectorUint_ = value.toUInt(Q_NULLPTR, 0); + } + + updateHandles(); +} + +void DecodeAsItem::setDissectorHandle(dissector_handle_t handle) +{ + dissector_handle_ = handle; + if (handle == nullptr) { + current_dissector_ = DECODE_AS_NONE; + } else { + current_dissector_ = dissector_handle_get_description(handle); + } +} + +void DecodeAsItem::updateHandles() +{ + ftenum_t selector_type = get_dissector_table_selector_type(tableName_); + dissector_handle_t default_handle = nullptr; + + if (FT_IS_STRING(selector_type)) { + default_handle = dissector_get_default_string_handle(tableName_, qUtf8Printable(selectorString_)); + } else if (FT_IS_UINT(selector_type)) { + default_handle = dissector_get_default_uint_handle(tableName_, selectorUint_); + } + if (default_handle != nullptr) { + default_dissector_ = dissector_handle_get_description(default_handle); + } else { + default_dissector_ = DECODE_AS_NONE; + } +} + +DecodeAsModel::DecodeAsModel(QObject *parent, capture_file *cf) : + QAbstractTableModel(parent), + cap_file_(cf) +{ +} + +DecodeAsModel::~DecodeAsModel() +{ + foreach(DecodeAsItem* item, decode_as_items_) + delete item; + decode_as_items_.clear(); +} + +Qt::ItemFlags DecodeAsModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemFlags(); + + DecodeAsItem* item = decode_as_items_[index.row()]; + + Qt::ItemFlags flags = QAbstractTableModel::flags(index); + switch(index.column()) + { + case DecodeAsModel::colTable: + case DecodeAsModel::colProtocol: + flags |= Qt::ItemIsEditable; + break; + case DecodeAsModel::colSelector: + { + ftenum_t selector_type = get_dissector_table_selector_type(item->tableName()); + if ((selector_type != FT_NONE) && + (item->selectorDCERPC() == NULL)) + flags |= Qt::ItemIsEditable; + break; + } + } + + return flags; +} + +QVariant DecodeAsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + DecodeAsItem* item; + + switch (role) + { + case Qt::ToolTipRole: + switch (index.column()) + { + case colTable: + return tr("Match using this field"); + case colSelector: + return tr("Change behavior when the field matches this value"); + case colType: + return tr("Field value type (and base, if Integer)"); + case colDefault: + return tr("Default \"Decode As\" behavior"); + case colProtocol: + return tr("Current\"Decode As\" behavior"); + } + return QVariant(); + case Qt::DisplayRole: + case Qt::EditRole: + item = decode_as_items_[index.row()]; + if (item == NULL) + return QVariant(); + + switch (index.column()) + { + case colTable: + return item->tableUIName(); + case colSelector: + { + ftenum_t selector_type = get_dissector_table_selector_type(item->tableName()); + if (FT_IS_UINT(selector_type)) { + return entryString(item->tableName(), GUINT_TO_POINTER(item->selectorUint())); + } else if (FT_IS_STRING(selector_type)) { + return entryString(item->tableName(), (gconstpointer)item->selectorString().toUtf8().constData()); + } else if (selector_type == FT_GUID) { + if (item->selectorDCERPC() != NULL) { + return item->selectorDCERPC()->ctx_id; + } + } + + return DECODE_AS_NONE; + } + case colType: + { + ftenum_t selector_type = get_dissector_table_selector_type(item->tableName()); + + if (FT_IS_STRING(selector_type)) { + return tr("String"); + } else if (FT_IS_UINT(selector_type)) { + QString type_desc = tr("Integer, base "); + switch (get_dissector_table_param(item->tableName())) { + case BASE_OCT: + type_desc.append("8"); + break; + case BASE_DEC: + type_desc.append("10"); + break; + case BASE_HEX: + type_desc.append("16"); + break; + default: + type_desc.append(tr("unknown")); + } + return type_desc; + } else if (selector_type == FT_NONE) { + return tr("<none>"); + } else if (selector_type == FT_GUID) { + if (item->selectorDCERPC() != NULL) { + return QString("ctx_id"); + } else { + return tr("GUID"); + } + } + break; + } + case colDefault: + return item->defaultDissector(); + case colProtocol: + return item->currentDissector(); + } + return QVariant(); + + case Qt::UserRole: + item = decode_as_items_[index.row()]; + return QVariant::fromValue(static_cast<void *>(item)); + } + + return QVariant(); +} + +QVariant DecodeAsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) + return QVariant(); + + switch (section) { + case colTable: + return tr("Field"); + case colSelector: + return tr("Value"); + case colType: + return tr("Type"); + case colDefault: + return tr("Default"); + case colProtocol: + return tr("Current"); + default: + ws_assert_not_reached(); + } + + return QVariant(); +} + +int DecodeAsModel::rowCount(const QModelIndex &parent) const +{ + // there are no children + if (parent.isValid()) { + return 0; + } + + return static_cast<int>(decode_as_items_.count()); +} + +int DecodeAsModel::columnCount(const QModelIndex &parent) const +{ + // there are no children + if (parent.isValid()) { + return 0; + } + + return colDecodeAsMax; +} + +bool DecodeAsModel::setData(const QModelIndex &cur_index, const QVariant &value, int role) +{ + if (!cur_index.isValid()) + return false; + + if (role != Qt::EditRole) + return false; + + if (data(cur_index, role) == value) { + // Data appears unchanged, do not do additional checks. + return true; + } + + DecodeAsItem* item = decode_as_items_[cur_index.row()]; + + switch(cur_index.column()) + { + case DecodeAsModel::colTable: + { + QString valueStr = value.toString(); + //grab the table values from the Decode As list because they are persistent + for (GList *cur = decode_as_list; cur; cur = cur->next) { + decode_as_t *entry = (decode_as_t *) cur->data; + if (valueStr.compare(get_dissector_table_ui_name(entry->table_name)) == 0) { + item->setTable(entry); + //all other columns affected + emit dataChanged(index(cur_index.row(), colSelector), + index(cur_index.row(), colProtocol)); + break; + } + } + } + break; + case DecodeAsModel::colProtocol: + { + dissector_handle_t handle = VariantPointer<dissector_handle>::asPtr(value); + item->setDissectorHandle(handle); + break; + } + case DecodeAsModel::colSelector: + item->setSelector(value.toString()); + emit dataChanged(index(cur_index.row(), colDefault), + index(cur_index.row(), colProtocol)); + break; + } + + return true; +} + +bool DecodeAsModel::insertRows(int row, int count, const QModelIndex &/*parent*/) +{ + // support insertion of just one item for now. + if (count != 1 || row < 0 || row > rowCount()) + return false; + + beginInsertRows(QModelIndex(), row, row); + + DecodeAsItem* item = nullptr; + const decode_as_t *firstEntry = nullptr; + + if (cap_file_ && cap_file_->edt) { + // Populate the new Decode As item with the last protocol layer + // that can support Decode As and has a selector field for that + // present in the frame. + // + // XXX: This treats 0 (for UInts) and empty strings the same as + // the fields for the tables not being present at all. + + wmem_list_frame_t * protos = wmem_list_tail(cap_file_->edt->pi.layers); + gint8 curr_layer_num_saved = cap_file_->edt->pi.curr_layer_num; + guint8 curr_layer_num = wmem_list_count(cap_file_->edt->pi.layers); + + while (protos != NULL && item == nullptr) { + int proto_id = GPOINTER_TO_INT(wmem_list_frame_data(protos)); + const gchar * proto_name = proto_get_protocol_filter_name(proto_id); + for (GList *cur = decode_as_list; cur; cur = cur->next) { + decode_as_t *entry = (decode_as_t *) cur->data; + if (g_strcmp0(proto_name, entry->name) == 0) { + if (firstEntry == nullptr) { + firstEntry = entry; + } + ftenum_t selector_type = get_dissector_table_selector_type(entry->table_name); + // Pick the first value in the packet for the current + // layer for the table + // XXX: What if the Decode As table supports multiple + // values, but the first possible one is 0/NULL? + cap_file_->edt->pi.curr_layer_num = curr_layer_num; + gpointer selector = entry->values[0].build_values[0](&cap_file_->edt->pi); + // FT_NONE tables don't need a value + if (selector != NULL || selector_type == FT_NONE) { + item = new DecodeAsItem(entry, selector); + break; + } + + } + } + protos = wmem_list_frame_prev(protos); + curr_layer_num--; + } + + cap_file_->edt->pi.curr_layer_num = curr_layer_num_saved; + } + + // If we didn't find an entry with a valid selector, create an entry + // from the last table with an empty selector, or an empty entry. + if (item == nullptr) { + item = new DecodeAsItem(firstEntry); + } + decode_as_items_ << item; + + endInsertRows(); + + return true; +} + +bool DecodeAsModel::removeRows(int row, int count, const QModelIndex &/*parent*/) +{ + if (count != 1 || row < 0 || row >= rowCount()) + return false; + + beginRemoveRows(QModelIndex(), row, row); + DecodeAsItem* item = decode_as_items_.takeAt(row); + delete item; + endRemoveRows(); + + return true; +} + +void DecodeAsModel::clearAll() +{ + if (rowCount() < 1) + return; + + beginResetModel(); + foreach(DecodeAsItem* item, decode_as_items_) + delete item; + decode_as_items_.clear(); + endResetModel(); +} + +bool DecodeAsModel::copyRow(int dst_row, int src_row) +{ + if (src_row < 0 || src_row >= rowCount() || dst_row < 0 || dst_row >= rowCount()) { + return false; + } + + DecodeAsItem* src = decode_as_items_[src_row]; + DecodeAsItem* dst = decode_as_items_[dst_row]; + + *dst = *src; + + QVector<int> roles; + roles << Qt::EditRole << Qt::BackgroundRole; + emit dataChanged(index(dst_row, 0), index(dst_row, columnCount()), roles); + + return true; +} + +prefs_set_pref_e DecodeAsModel::readDecodeAsEntry(gchar *key, const gchar *value, void *private_data, gboolean) +{ + DecodeAsModel *model = (DecodeAsModel*)private_data; + if (model == NULL) + return PREFS_SET_OK; + + if (strcmp(key, DECODE_AS_ENTRY) != 0) { + return PREFS_SET_NO_SUCH_PREF; + } + + /* Parse into table, selector, initial, current */ + gchar **values = g_strsplit_set(value, ",", 4); + DecodeAsItem *item = nullptr; + + dissector_table_t dissector_table = find_dissector_table(values[0]); + + QString tableName(values[0]); + // Get the table values from the Decode As list because they are persistent + for (GList *cur = decode_as_list; cur; cur = cur->next) { + decode_as_t *entry = (decode_as_t *) cur->data; + if (tableName.compare(entry->table_name) == 0) { + item = new DecodeAsItem(entry); + break; + } + } + + if (item == nullptr) { + g_strfreev(values); + return PREFS_SET_SYNTAX_ERR; + } + + QString selector(values[1]); + item->setSelector(selector); + + /* The value for the default dissector in the decode_as_entries file + * has no effect other than perhaps making the config file more + * informative when edited manually. + * We will actually display and reset to the programmatic default value. + */ + item->setDissectorHandle(dissector_table_get_dissector_handle(dissector_table, values[3])); + + model->decode_as_items_ << item; + g_strfreev(values); + + return PREFS_SET_OK; +} + +bool DecodeAsModel::copyFromProfile(QString filename, const gchar **err) +{ + FILE *fp = ws_fopen(filename.toUtf8().constData(), "r"); + + if (fp == NULL) { + *err = g_strerror(errno); + return false; + } + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + read_prefs_file(filename.toUtf8().constData(), fp, readDecodeAsEntry, this); + endInsertRows(); + + fclose(fp); + + return true; +} + +QString DecodeAsModel::entryString(const gchar *table_name, gconstpointer value) +{ + QString entry_str; + ftenum_t selector_type = get_dissector_table_selector_type(table_name); + + switch (selector_type) { + + case FT_UINT8: + case FT_UINT16: + case FT_UINT24: + case FT_UINT32: + { + uint num_val = GPOINTER_TO_UINT(value); + switch (get_dissector_table_param(table_name)) { + + case BASE_DEC: + entry_str = QString::number(num_val); + break; + + case BASE_HEX: + int width; + switch (selector_type) { + case FT_UINT8: + width = 2; + break; + case FT_UINT16: + width = 4; + break; + case FT_UINT24: + width = 6; + break; + case FT_UINT32: + width = 8; + break; + + default: + ws_assert_not_reached(); + break; + } + entry_str = QString("%1").arg(int_to_qstring(num_val, width, 16)); + break; + + case BASE_OCT: + entry_str = "0" + QString::number(num_val, 8); + break; + } + break; + } + + case FT_STRING: + case FT_STRINGZ: + case FT_UINT_STRING: + case FT_STRINGZPAD: + case FT_STRINGZTRUNC: + entry_str = (const char *)value; + break; + + case FT_GUID: + //avoid the assert for now + break; + + case FT_NONE: + //doesn't really matter, just avoiding the assert + return "0"; + + default: + ws_assert_not_reached(); + break; + } + return entry_str; +} + +void DecodeAsModel::fillTable() +{ + decode_as_items_.clear(); + beginResetModel(); + + dissector_all_tables_foreach_changed(buildChangedList, this); + decode_dcerpc_add_show_list(buildDceRpcChangedList, this); + + endResetModel(); +} + +void DecodeAsModel::setDissectorHandle(const QModelIndex &index, dissector_handle_t dissector_handle) +{ + DecodeAsItem* item = decode_as_items_[index.row()]; + if (item != NULL) + item->setDissectorHandle(dissector_handle); +} + +void DecodeAsModel::buildChangedList(const gchar *table_name, ftenum_t, gpointer key, gpointer value, gpointer user_data) +{ + DecodeAsModel *model = (DecodeAsModel*)user_data; + if (model == NULL) + return; + + dissector_handle_t current_dh; + DecodeAsItem* item = new DecodeAsItem(table_name, key); + + current_dh = dtbl_entry_get_handle((dtbl_entry_t *)value); + item->setDissectorHandle(current_dh); + + model->decode_as_items_ << item; +} + +void DecodeAsModel::buildDceRpcChangedList(gpointer data, gpointer user_data) +{ + dissector_table_t sub_dissectors; + guid_key guid_val; + decode_dcerpc_bind_values_t *binding = (decode_dcerpc_bind_values_t *)data; + + DecodeAsModel *model = (DecodeAsModel*)user_data; + if (model == NULL) + return; + + DecodeAsItem* item = new DecodeAsItem(DCERPC_TABLE_NAME, binding); + + sub_dissectors = find_dissector_table(DCERPC_TABLE_NAME); + + guid_val.ver = binding->ver; + guid_val.guid = binding->uuid; + item->setDissectorHandle(dissector_get_guid_handle(sub_dissectors, &guid_val)); + + model->decode_as_items_ << item; +} + +typedef QPair<const char *, guint32> UintPair; +typedef QPair<const char *, const char *> CharPtrPair; + +void DecodeAsModel::gatherChangedEntries(const gchar *table_name, + ftenum_t selector_type, gpointer key, gpointer, gpointer user_data) +{ + DecodeAsModel *model = qobject_cast<DecodeAsModel*>((DecodeAsModel*)user_data); + if (model == NULL) + return; + + switch (selector_type) { + case FT_UINT8: + case FT_UINT16: + case FT_UINT24: + case FT_UINT32: + model->changed_uint_entries_ << UintPair(table_name, GPOINTER_TO_UINT(key)); + break; + case FT_NONE: + //need to reset dissector table, so this needs to be in a changed list, + //might as well be the uint one. + model->changed_uint_entries_ << UintPair(table_name, 0); + break; + + case FT_STRING: + case FT_STRINGZ: + case FT_UINT_STRING: + case FT_STRINGZPAD: + case FT_STRINGZTRUNC: + model->changed_string_entries_ << CharPtrPair(table_name, (const char *) key); + break; + default: + break; + } +} + +void DecodeAsModel::applyChanges() +{ + dissector_table_t sub_dissectors; + module_t *module; + pref_t* pref_value; + dissector_handle_t handle; + // Reset all dissector tables, then apply all rules from model. + + // We can't call g_hash_table_removed from g_hash_table_foreach, which + // means we can't call dissector_reset_{string,uint} from + // dissector_all_tables_foreach_changed. Collect changed entries in + // lists and remove them separately. + // + // If dissector_all_tables_remove_changed existed we could call it + // instead. + dissector_all_tables_foreach_changed(gatherChangedEntries, this); + foreach (UintPair uint_entry, changed_uint_entries_) { + /* Set "Decode As preferences" to default values */ + sub_dissectors = find_dissector_table(uint_entry.first); + handle = dissector_get_uint_handle(sub_dissectors, uint_entry.second); + if (handle != NULL) { + module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(handle))); + pref_value = prefs_find_preference(module, uint_entry.first); + if (pref_value != NULL) { + module->prefs_changed_flags |= prefs_get_effect_flags(pref_value); + reset_pref(pref_value); + } + } + + dissector_reset_uint(uint_entry.first, uint_entry.second); + } + changed_uint_entries_.clear(); + foreach (CharPtrPair char_ptr_entry, changed_string_entries_) { + dissector_reset_string(char_ptr_entry.first, char_ptr_entry.second); + } + changed_string_entries_.clear(); + + foreach(DecodeAsItem *item, decode_as_items_) { + decode_as_t *decode_as_entry; + + if (item->currentDissector().isEmpty()) { + continue; + } + + for (GList *cur = decode_as_list; cur; cur = cur->next) { + decode_as_entry = (decode_as_t *) cur->data; + + if (!g_strcmp0(decode_as_entry->table_name, item->tableName())) { + + ftenum_t selector_type = get_dissector_table_selector_type(item->tableName()); + gconstpointer selector_value; + QByteArray byteArray; + + switch (selector_type) { + case FT_UINT8: + case FT_UINT16: + case FT_UINT24: + case FT_UINT32: + selector_value = GUINT_TO_POINTER(item->selectorUint()); + break; + case FT_STRING: + case FT_STRINGZ: + case FT_UINT_STRING: + case FT_STRINGZPAD: + case FT_STRINGZTRUNC: + byteArray = item->selectorString().toUtf8(); + selector_value = (gconstpointer) byteArray.constData(); + break; + case FT_NONE: + //selector value is ignored, but dissector table needs to happen + selector_value = NULL; + break; + case FT_GUID: + if (item->selectorDCERPC() != NULL) { + selector_value = (gconstpointer)item->selectorDCERPC(); + } else { + //TODO: Support normal GUID dissector tables + selector_value = NULL; + } + break; + default: + continue; + } + + if ((item->currentDissector() == item->defaultDissector())) { + decode_as_entry->reset_value(decode_as_entry->table_name, selector_value); + sub_dissectors = find_dissector_table(decode_as_entry->table_name); + + /* For now, only numeric dissector tables can use preferences */ + if (FT_IS_UINT(dissector_table_get_type(sub_dissectors))) { + if (item->dissectorHandle() != NULL) { + module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(item->dissectorHandle()))); + pref_value = prefs_find_preference(module, decode_as_entry->table_name); + if (pref_value != NULL) { + module->prefs_changed_flags |= prefs_get_effect_flags(pref_value); + prefs_remove_decode_as_value(pref_value, item->selectorUint(), TRUE); + } + } + } + break; + } else { + decode_as_entry->change_value(decode_as_entry->table_name, selector_value, item->dissectorHandle(), item->currentDissector().toUtf8().constData()); + sub_dissectors = find_dissector_table(decode_as_entry->table_name); + + /* For now, only numeric dissector tables can use preferences */ + if (item->dissectorHandle() != NULL) { + if (FT_IS_UINT(dissector_table_get_type(sub_dissectors))) { + module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(item->dissectorHandle()))); + pref_value = prefs_find_preference(module, decode_as_entry->table_name); + if (pref_value != NULL) { + module->prefs_changed_flags |= prefs_get_effect_flags(pref_value); + prefs_add_decode_as_value(pref_value, item->selectorUint(), FALSE); + } + } + } + break; + } + } + } + } + prefs_apply_all(); +} diff --git a/ui/qt/models/decode_as_model.h b/ui/qt/models/decode_as_model.h new file mode 100644 index 00000000..cf280965 --- /dev/null +++ b/ui/qt/models/decode_as_model.h @@ -0,0 +1,120 @@ +/** @file + * + * Data model for Decode As records. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef DECODE_AS_MODEL_H +#define DECODE_AS_MODEL_H + +#include <config.h> +#include <glib.h> + +#include <QAbstractItemModel> +#include <QList> + +#include "cfile.h" + +#include <epan/packet.h> +#include <epan/decode_as.h> +#include <epan/dissectors/packet-dcerpc.h> + +class DecodeAsItem +{ +public: + DecodeAsItem(const char *table_name = NULL, gconstpointer selector = NULL); + DecodeAsItem(const decode_as_t *entry, gconstpointer selector = NULL); + virtual ~DecodeAsItem(); + + const gchar* tableName() const { return tableName_; } + const gchar* tableUIName() const { return tableUIName_; } + uint selectorUint() const { return selectorUint_; } + QString selectorString() const { return selectorString_; } + decode_dcerpc_bind_values_t* selectorDCERPC() const { return selectorDCERPC_; } + QString defaultDissector() const { return default_dissector_; } + QString currentDissector() const { return current_dissector_; } + dissector_handle_t dissectorHandle() const { return dissector_handle_; } + void setTable(const decode_as_t *entry); + void setSelector(const QString &value); + void setDissectorHandle(dissector_handle_t handle); + + void updateHandles(); + +private: + void init(const char *table_name, gconstpointer selector = NULL); + + const gchar* tableName_; + const gchar* tableUIName_; + + //save our sanity and not have to worry about memory management + //between (lack of) persistent data in GUI and underlying data + uint selectorUint_; + QString selectorString_; + decode_dcerpc_bind_values_t* selectorDCERPC_; //for special handling of DCE/RPC + + QString default_dissector_; + QString current_dissector_; + dissector_handle_t dissector_handle_; +}; + +class DecodeAsModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + DecodeAsModel(QObject *parent, capture_file *cf = NULL); + virtual ~DecodeAsModel(); + + enum DecodeAsColumn { + colTable = 0, // aka "Field" (or dissector table like "TCP Port") + colSelector, // the actual table value (e.g., port number 80) + colType, // field type (e.g. "Integer, base 16") + colDefault, // aka "initial" protocol chosen by Wireshark + colProtocol, // aka "current" protocol selected by user + colDecodeAsMax //not used + }; + + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + void fillTable(); + + void setDissectorHandle(const QModelIndex &index, dissector_handle_t dissector_handle); + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + void clearAll(); + bool copyRow(int dst_row, int src_row); + bool copyFromProfile(QString filename, const gchar **err); + + static QString entryString(const gchar *table_name, gconstpointer value); + + void applyChanges(); + +protected: + static void buildChangedList(const gchar *table_name, ftenum_t selector_type, + gpointer key, gpointer value, gpointer user_data); + static void buildDceRpcChangedList(gpointer data, gpointer user_data); + static void gatherChangedEntries(const gchar *table_name, ftenum_t selector_type, + gpointer key, gpointer value, gpointer user_data); + static prefs_set_pref_e readDecodeAsEntry(gchar *key, const gchar *value, + void *user_data, gboolean return_range_errors); + +private: + capture_file *cap_file_; + QList<DecodeAsItem *> decode_as_items_; + QList<QPair<const char *, guint32> > changed_uint_entries_; + QList<QPair<const char *, const char *> > changed_string_entries_; +}; + +#endif // DECODE_AS_MODEL_H diff --git a/ui/qt/models/dissector_tables_model.cpp b/ui/qt/models/dissector_tables_model.cpp new file mode 100644 index 00000000..73c757ec --- /dev/null +++ b/ui/qt/models/dissector_tables_model.cpp @@ -0,0 +1,434 @@ +/* dissector_tables_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/dissector_tables_model.h> +#include <epan/ftypes/ftypes.h> +#include <epan/packet.h> + +#include <ui/qt/utils/variant_pointer.h> +#include "main_application.h" + +static const char* CUSTOM_TABLE_NAME = "Custom Tables"; +static const char* INTEGER_TABLE_NAME = "Integer Tables"; +static const char* STRING_TABLE_NAME = "String Tables"; +static const char* HEURISTIC_TABLE_NAME = "Heuristic Tables"; + +class IntegerTablesItem : public DissectorTablesItem +{ +public: + IntegerTablesItem(unsigned int value, QString dissectorDescription, DissectorTablesItem* parent); + virtual ~IntegerTablesItem(); + + virtual bool lessThan(DissectorTablesItem &right) const; + +protected: + unsigned int value_; +}; + + +DissectorTablesItem::DissectorTablesItem(QString tableName, QString dissectorDescription, DissectorTablesItem* parent) : + ModelHelperTreeItem<DissectorTablesItem>(parent), + tableName_(tableName), + dissectorDescription_(dissectorDescription) +{ +} + +DissectorTablesItem::~DissectorTablesItem() +{ +} + +bool DissectorTablesItem::lessThan(DissectorTablesItem &right) const +{ + if (tableName().compare(right.tableName(), Qt::CaseInsensitive) < 0) + return true; + + return false; +} + + +IntegerTablesItem::IntegerTablesItem(unsigned int value, QString dissectorDescription, DissectorTablesItem* parent) + : DissectorTablesItem(QString("%1").arg(value), dissectorDescription, parent) + , value_(value) +{ +} + +IntegerTablesItem::~IntegerTablesItem() +{ +} + +bool IntegerTablesItem::lessThan(DissectorTablesItem &right) const +{ + if (value_ == ((IntegerTablesItem&)right).value_) { + return DissectorTablesItem::lessThan(right); + } + + if (value_ < ((IntegerTablesItem&)right).value_) { + return true; + } + + return false; +} + + + + + + + + +DissectorTablesModel::DissectorTablesModel(QObject *parent) : + QAbstractItemModel(parent), + root_(new DissectorTablesItem(QString("ROOT"), QString("ROOT"), NULL)) +{ + populate(); +} + +DissectorTablesModel::~DissectorTablesModel() +{ + delete root_; +} + +int DissectorTablesModel::rowCount(const QModelIndex &parent) const +{ + DissectorTablesItem *parent_item; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<DissectorTablesItem*>(parent.internalPointer()); + + if (parent_item == NULL) + return 0; + + return parent_item->childCount(); +} + +int DissectorTablesModel::columnCount(const QModelIndex&) const +{ + return colLast; +} + +QModelIndex DissectorTablesModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + DissectorTablesItem* item = static_cast<DissectorTablesItem*>(index.internalPointer()); + if (item != NULL) { + DissectorTablesItem* parent_item = item->parentItem(); + if (parent_item != NULL) { + if (parent_item == root_) + return QModelIndex(); + + return createIndex(parent_item->row(), 0, parent_item); + } + } + + return QModelIndex(); +} + +QModelIndex DissectorTablesModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + DissectorTablesItem *parent_item, *child_item; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<DissectorTablesItem*>(parent.internalPointer()); + + Q_ASSERT(parent_item); + + child_item = parent_item->child(row); + if (child_item) { + return createIndex(row, column, child_item); + } + + return QModelIndex(); +} + +QVariant DissectorTablesModel::data(const QModelIndex &index, int role) const +{ + if ((!index.isValid()) || (role != Qt::DisplayRole)) + return QVariant(); + + DissectorTablesItem* item = static_cast<DissectorTablesItem*>(index.internalPointer()); + if (item == NULL) + return QVariant(); + + switch ((enum DissectorTablesColumn)index.column()) + { + case colTableName: + return item->tableName(); + case colDissectorDescription: + return item->dissectorDescription(); + default: + break; + } + + return QVariant(); +} + +static void gatherProtocolDecodes(const char *, ftenum_t selector_type, gpointer key, gpointer value, gpointer item_ptr) +{ + DissectorTablesItem* pdl_ptr = (DissectorTablesItem*)item_ptr; + if (pdl_ptr == NULL) + return; + + dtbl_entry_t *dtbl_entry = (dtbl_entry_t*)value; + dissector_handle_t handle = dtbl_entry_get_handle(dtbl_entry); + const QString dissector_description = dissector_handle_get_description(handle); + DissectorTablesItem *ti = NULL; + + switch (selector_type) { + case FT_UINT8: + case FT_UINT16: + case FT_UINT24: + case FT_UINT32: + ti = new IntegerTablesItem(GPOINTER_TO_UINT(key), dissector_description, pdl_ptr); + pdl_ptr->prependChild(ti); + break; + + case FT_STRING: + case FT_STRINGZ: + case FT_UINT_STRING: + case FT_STRINGZPAD: + case FT_STRINGZTRUNC: + ti = new DissectorTablesItem((const char *)key, dissector_description, pdl_ptr); + pdl_ptr->prependChild(ti); + break; + + case FT_BYTES: + ti = new DissectorTablesItem(dissector_handle_get_description(handle), dissector_description, pdl_ptr); + pdl_ptr->prependChild(ti); + break; + + default: + break; + } +} + +struct tables_root +{ + DissectorTablesItem* custom_table; + DissectorTablesItem* integer_table; + DissectorTablesItem* string_table; +}; + +static void gatherTableNames(const char *short_name, const char *table_name, gpointer model_ptr) +{ + struct tables_root* tables = (struct tables_root*)model_ptr; + if (model_ptr == NULL) + return; + + ftenum_t selector_type = get_dissector_table_selector_type(short_name); + DissectorTablesItem *dt_ti = NULL; + + switch (selector_type) { + case FT_UINT8: + case FT_UINT16: + case FT_UINT24: + case FT_UINT32: + dt_ti = new DissectorTablesItem(table_name, short_name, tables->integer_table); + tables->integer_table->prependChild(dt_ti); + break; + case FT_STRING: + case FT_STRINGZ: + case FT_UINT_STRING: + case FT_STRINGZPAD: + case FT_STRINGZTRUNC: + dt_ti = new DissectorTablesItem(table_name, short_name, tables->string_table); + tables->string_table->prependChild(dt_ti); + break; + case FT_BYTES: + dt_ti = new DissectorTablesItem(table_name, short_name, tables->custom_table); + tables->custom_table->prependChild(dt_ti); + break; + default: + // Assert? + return; + } + + dissector_table_foreach(short_name, gatherProtocolDecodes, dt_ti); +} + +static void gatherHeurProtocolDecodes(const char *, struct heur_dtbl_entry *dtbl_entry, gpointer list_ptr) +{ + DissectorTablesItem* hdl_ptr = (DissectorTablesItem*)list_ptr; + if (hdl_ptr == NULL) + return; + + if (dtbl_entry->protocol) { + QString longName = proto_get_protocol_long_name(dtbl_entry->protocol); + QString heurDisplayName = dtbl_entry->display_name; + if (! heurDisplayName.isEmpty()) + longName.append(QString(" (%1)").arg(heurDisplayName)); + + DissectorTablesItem *heur = new DissectorTablesItem(longName, proto_get_protocol_short_name(dtbl_entry->protocol), hdl_ptr); + hdl_ptr->prependChild(heur); + } +} + +static void gatherHeurTableNames(const char *table_name, heur_dissector_list *list, gpointer heur_tables) +{ + DissectorTablesItem* table = (DissectorTablesItem*)heur_tables; + if (table == NULL) + return; + + DissectorTablesItem *heur = new DissectorTablesItem(table_name, QString(""), table); + table->prependChild(heur); + + if (list) { + heur_dissector_table_foreach(table_name, gatherHeurProtocolDecodes, heur); + } +} + +void DissectorTablesModel::populate() +{ + beginResetModel(); + + struct tables_root tables; + + tables.custom_table = new DissectorTablesItem(tr(CUSTOM_TABLE_NAME), QString(""), root_); + root_->prependChild(tables.custom_table); + tables.integer_table = new DissectorTablesItem(tr(INTEGER_TABLE_NAME), QString(""), root_); + root_->prependChild(tables.integer_table); + tables.string_table = new DissectorTablesItem(tr(STRING_TABLE_NAME), QString(""), root_); + root_->prependChild(tables.string_table); + + dissector_all_tables_foreach_table(gatherTableNames, &tables, NULL); + + DissectorTablesItem* heuristic_table = new DissectorTablesItem(tr(HEURISTIC_TABLE_NAME), QString(""), root_); + root_->prependChild(heuristic_table); + + dissector_all_heur_tables_foreach_table(gatherHeurTableNames, heuristic_table, NULL); + + endResetModel(); +} + + + + + +DissectorTablesProxyModel::DissectorTablesProxyModel(QObject * parent) +: QSortFilterProxyModel(parent), +tableName_(tr("Table Type")), +dissectorDescription_(), +filter_() +{ +} + +QVariant DissectorTablesProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + + switch ((enum DissectorTablesModel::DissectorTablesColumn)section) { + case DissectorTablesModel::colTableName: + return tableName_; + case DissectorTablesModel::colDissectorDescription: + return dissectorDescription_; + default: + break; + } + } + return QVariant(); +} + +bool DissectorTablesProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + //Use DissectorTablesItem directly for better performance + DissectorTablesItem* left_item = static_cast<DissectorTablesItem*>(left.internalPointer()); + DissectorTablesItem* right_item = static_cast<DissectorTablesItem*>(right.internalPointer()); + + if ((left_item != NULL) && (right_item != NULL)) { + return left_item->lessThan(*right_item); + } + + return false; +} + +bool DissectorTablesProxyModel::filterAcceptItem(DissectorTablesItem& item) const +{ + if (filter_.isEmpty()) + return true; + + if (item.tableName().contains(filter_, Qt::CaseInsensitive) || item.dissectorDescription().contains(filter_, Qt::CaseInsensitive)) + return true; + + DissectorTablesItem *child_item; + for (int child_row = 0; child_row < item.childCount(); child_row++) + { + child_item = item.child(child_row); + if ((child_item != NULL) && (filterAcceptItem(*child_item))) + return true; + } + + return false; +} + +bool DissectorTablesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex nameIdx = sourceModel()->index(sourceRow, DissectorTablesModel::colTableName, sourceParent); + DissectorTablesItem* item = static_cast<DissectorTablesItem*>(nameIdx.internalPointer()); + if (item == NULL) + return false; + + if (filterAcceptItem(*item)) + return true; + + return false; +} + +void DissectorTablesProxyModel::setFilter(const QString& filter) +{ + filter_ = filter; + invalidateFilter(); +} + +void DissectorTablesProxyModel::adjustHeader(const QModelIndex ¤tIndex) +{ + tableName_ = tr("Table Type"); + dissectorDescription_ = QString(); + if (currentIndex.isValid() && currentIndex.parent().isValid()) { + QString table; + + if (currentIndex.parent().parent().isValid()) { + table = data(index(currentIndex.parent().parent().row(), DissectorTablesModel::colTableName), Qt::DisplayRole).toString(); + if ((table.compare(CUSTOM_TABLE_NAME) == 0) || + (table.compare(STRING_TABLE_NAME) == 0)) { + tableName_ = tr("String"); + dissectorDescription_ = tr("Dissector Description"); + } else if (table.compare(INTEGER_TABLE_NAME) == 0) { + tableName_ = tr("Integer"); + dissectorDescription_ = tr("Dissector Description"); + } else if (table.compare(HEURISTIC_TABLE_NAME) == 0) { + tableName_ = tr("Protocol"); + dissectorDescription_ = tr("Short Name"); + } + } else { + table = data(index(currentIndex.parent().row(), DissectorTablesModel::colTableName), Qt::DisplayRole).toString(); + if ((table.compare(CUSTOM_TABLE_NAME) == 0) || + (table.compare(INTEGER_TABLE_NAME) == 0) || + (table.compare(STRING_TABLE_NAME) == 0)) { + tableName_ = tr("Table Name"); + dissectorDescription_ = tr("Selector Name"); + } else if (table.compare(HEURISTIC_TABLE_NAME) == 0) { + tableName_ = tr("Protocol"); + dissectorDescription_ = tr("Short Name"); + } + } + } + + + emit headerDataChanged(Qt::Vertical, 0, 1); +} diff --git a/ui/qt/models/dissector_tables_model.h b/ui/qt/models/dissector_tables_model.h new file mode 100644 index 00000000..40d63f94 --- /dev/null +++ b/ui/qt/models/dissector_tables_model.h @@ -0,0 +1,89 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef DISSECTOR_TABLES_MODEL_H +#define DISSECTOR_TABLES_MODEL_H + +#include <config.h> + +#include <ui/qt/models/tree_model_helpers.h> + +#include <QSortFilterProxyModel> + +class DissectorTablesItem : public ModelHelperTreeItem<DissectorTablesItem> +{ +public: + DissectorTablesItem(QString tableName, QString dissectorDescription, DissectorTablesItem* parent); + virtual ~DissectorTablesItem(); + + QString tableName() const {return tableName_;} + QString dissectorDescription() const {return dissectorDescription_;} + + virtual bool lessThan(DissectorTablesItem &right) const; + +protected: + QString tableName_; + QString dissectorDescription_; +}; + +class DissectorTablesModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit DissectorTablesModel(QObject * parent = Q_NULLPTR); + virtual ~DissectorTablesModel(); + + enum DissectorTablesColumn { + colTableName = 0, + colDissectorDescription, + colLast + }; + + QModelIndex index(int row, int column, + const QModelIndex & = QModelIndex()) const; + QModelIndex parent(const QModelIndex &) const; + QVariant data(const QModelIndex &index, int role) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + void populate(); + +private: + DissectorTablesItem* root_; +}; + +class DissectorTablesProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + explicit DissectorTablesProxyModel(QObject * parent = Q_NULLPTR); + + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + + void adjustHeader(const QModelIndex ¤tIndex); + void setFilter(const QString& filter); + +protected: + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; + bool filterAcceptItem(DissectorTablesItem& item) const; + +private: + + QString tableName_; + QString dissectorDescription_; + QString filter_; +}; + +#endif // DISSECTOR_TABLES_MODEL_H diff --git a/ui/qt/models/enabled_protocols_model.cpp b/ui/qt/models/enabled_protocols_model.cpp new file mode 100644 index 00000000..ecbc47a9 --- /dev/null +++ b/ui/qt/models/enabled_protocols_model.cpp @@ -0,0 +1,520 @@ +/* enabled_protocols_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <QSortFilterProxyModel> + +#include <ui/qt/models/enabled_protocols_model.h> +#include <epan/packet.h> +#include <epan/disabled_protos.h> + +#include <ui/qt/utils/variant_pointer.h> +#include "main_application.h" + +#include <QRegularExpression> + +class ProtocolTreeItem : public EnabledProtocolItem +{ +public: + ProtocolTreeItem(protocol_t* proto, EnabledProtocolItem* parent) + : EnabledProtocolItem(proto_get_protocol_short_name(proto), proto_get_protocol_long_name(proto), proto_is_protocol_enabled(proto), parent), + proto_(proto) + { + + } + + virtual ~ProtocolTreeItem() {} + +protected: + virtual void applyValuePrivate(gboolean value) + { + if (! proto_can_toggle_protocol(proto_get_id(proto_))) { + return; + } + proto_set_decoding(proto_get_id(proto_), value); + } + +private: + protocol_t* proto_; +}; + +class HeuristicTreeItem : public EnabledProtocolItem +{ +public: + HeuristicTreeItem(heur_dtbl_entry_t *heuristic, EnabledProtocolItem* parent) + : EnabledProtocolItem(heuristic->short_name, heuristic->display_name, heuristic->enabled, parent), + heuristic_table_(heuristic) + { + type_ = EnabledProtocolItem::Heuristic; + } + + virtual ~HeuristicTreeItem() {} + +protected: + virtual void applyValuePrivate(gboolean value) + { + heuristic_table_->enabled = value; + } + +private: + heur_dtbl_entry_t *heuristic_table_; +}; + + +EnabledProtocolItem::EnabledProtocolItem(QString name, QString description, bool enabled, EnabledProtocolItem* parent) : + ModelHelperTreeItem<EnabledProtocolItem>(parent), + name_(name), + description_(description), + enabled_(enabled), + enabledInit_(enabled), + type_(EnabledProtocolItem::Standard) +{ +} + +EnabledProtocolItem::~EnabledProtocolItem() +{ +} + +EnabledProtocolItem::EnableProtocolType EnabledProtocolItem::type() const +{ + return type_; +} + +bool EnabledProtocolItem::applyValue() +{ + if (enabledInit_ != enabled_) { + applyValuePrivate(enabled_); + return true; + } + + return false; +} + + + + +EnabledProtocolsModel::EnabledProtocolsModel(QObject *parent) : + QAbstractItemModel(parent), + root_(new ProtocolTreeItem(NULL, NULL)) +{ +} + +EnabledProtocolsModel::~EnabledProtocolsModel() +{ + delete root_; +} + +int EnabledProtocolsModel::rowCount(const QModelIndex &parent) const +{ + EnabledProtocolItem *parent_item; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<EnabledProtocolItem*>(parent.internalPointer()); + + if (parent_item == NULL) + return 0; + + return parent_item->childCount(); +} + +int EnabledProtocolsModel::columnCount(const QModelIndex&) const +{ + return colLast; +} + +QVariant EnabledProtocolsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + + switch ((enum EnabledProtocolsColumn)section) { + case colProtocol: + return tr("Protocol"); + case colDescription: + return tr("Description"); + default: + break; + } + } + return QVariant(); +} + +QModelIndex EnabledProtocolsModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + EnabledProtocolItem* item = static_cast<EnabledProtocolItem*>(index.internalPointer()); + if (item != NULL) { + EnabledProtocolItem* parent_item = item->parentItem(); + if (parent_item != NULL) { + if (parent_item == root_) + return QModelIndex(); + + return createIndex(parent_item->row(), 0, parent_item); + } + } + + return QModelIndex(); +} + +QModelIndex EnabledProtocolsModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + EnabledProtocolItem *parent_item, *child_item; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<EnabledProtocolItem*>(parent.internalPointer()); + + Q_ASSERT(parent_item); + + child_item = parent_item->child(row); + if (child_item) { + return createIndex(row, column, child_item); + } + + return QModelIndex(); +} + +Qt::ItemFlags EnabledProtocolsModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemFlags(); + + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + switch(index.column()) + { + case colProtocol: + flags |= Qt::ItemIsUserCheckable; + break; + default: + break; + } + + return flags; +} + +QVariant EnabledProtocolsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + EnabledProtocolItem* item = static_cast<EnabledProtocolItem*>(index.internalPointer()); + if (item == NULL) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + switch ((enum EnabledProtocolsColumn)index.column()) + { + case colProtocol: + return item->name(); + case colDescription: + return item->description(); + default: + break; + } + break; + case Qt::CheckStateRole: + switch ((enum EnabledProtocolsColumn)index.column()) + { + case colProtocol: + return item->enabled() ? Qt::Checked : Qt::Unchecked; + default: + break; + } + break; + case DATA_PROTOCOL_TYPE: + return QVariant::fromValue(item->type()); + break; + default: + break; + } + + return QVariant(); +} + +bool EnabledProtocolsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + if ((role != Qt::EditRole) && + ((index.column() == colProtocol) && (role != Qt::CheckStateRole))) + return false; + + if (data(index, role) == value) { + // Data appears unchanged, do not do additional checks. + return true; + } + + EnabledProtocolItem* item = static_cast<EnabledProtocolItem*>(index.internalPointer()); + if (item == NULL) + return false; + + item->setEnabled(value.toInt() == Qt::Checked ? true : false); + + return true; +} + +static void addHeuristicItem(gpointer data, gpointer user_data) +{ + heur_dtbl_entry_t* heur = (heur_dtbl_entry_t*)data; + ProtocolTreeItem* protocol_item = (ProtocolTreeItem*)user_data; + + HeuristicTreeItem* heuristic_row = new HeuristicTreeItem(heur, protocol_item); + protocol_item->prependChild(heuristic_row); +} + +void EnabledProtocolsModel::populate() +{ + void *cookie; + protocol_t *protocol; + + beginResetModel(); + + // Iterate over all the protocols + for (int i = proto_get_first_protocol(&cookie); i != -1; i = proto_get_next_protocol(&cookie)) + { + if (proto_can_toggle_protocol(i)) + { + protocol = find_protocol_by_id(i); + if (!proto_is_pino(protocol)) { + ProtocolTreeItem* protocol_row = new ProtocolTreeItem(protocol, root_); + root_->prependChild(protocol_row); + + proto_heuristic_dissector_foreach(protocol, addHeuristicItem, protocol_row); + } + } + } + + endResetModel(); +} + +void EnabledProtocolsModel::applyChanges(bool writeChanges) +{ + bool redissect = false; + + for (int proto_index = 0; proto_index < root_->childCount(); proto_index++) { + EnabledProtocolItem* proto = root_->child(proto_index); + redissect |= proto->applyValue(); + for (int heur_index = 0; heur_index < proto->childCount(); heur_index++) { + EnabledProtocolItem* heur = proto->child(heur_index); + redissect |= heur->applyValue(); + } + } + + if (redissect) { + saveChanges(writeChanges); + } +} + +void EnabledProtocolsModel::disableProtocol(struct _protocol *protocol) +{ + ProtocolTreeItem disabled_proto(protocol, NULL); + disabled_proto.setEnabled(false); + if (disabled_proto.applyValue()) { + saveChanges(); + } +} + +void EnabledProtocolsModel::saveChanges(bool writeChanges) +{ + if (writeChanges) { + save_enabled_and_disabled_lists(); + } + mainApp->emitAppSignal(MainApplication::PacketDissectionChanged); +} + + +EnabledProtocolsProxyModel::EnabledProtocolsProxyModel(QObject * parent) +: QSortFilterProxyModel(parent), +type_(EnabledProtocolsProxyModel::EveryWhere), +protocolType_(EnabledProtocolItem::Any), +filter_() +{} + +bool EnabledProtocolsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + //Use EnabledProtocolItem directly for better performance + EnabledProtocolItem* left_item = static_cast<EnabledProtocolItem*>(left.internalPointer()); + EnabledProtocolItem* right_item = static_cast<EnabledProtocolItem*>(right.internalPointer()); + + if ((left_item != NULL) && (right_item != NULL)) { + + int compare_ret = 0; + + if (left.column() == EnabledProtocolsModel::colProtocol) + compare_ret = left_item->name().compare(right_item->name(), Qt::CaseInsensitive); + else if (left.column() == EnabledProtocolsModel::colDescription) + compare_ret = left_item->description().compare(right_item->description(), Qt::CaseInsensitive); + + if (compare_ret < 0) + return true; + } + + return false; +} + +Qt::ItemFlags EnabledProtocolsProxyModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags flags = Qt::NoItemFlags; + if (index.isValid()) + { + QModelIndex source = mapToSource(index); + if (filterAcceptsSelf(source.row(), source.parent()) ) + { + flags = Qt::ItemIsEnabled; + flags |= Qt::ItemIsSelectable; + flags |= Qt::ItemIsUserCheckable; + } + } + + return flags; +} + +bool EnabledProtocolsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + if (filterAcceptsSelf(sourceRow, sourceParent)) + return true; + +#if 0 + QModelIndex parent = sourceParent; + while (parent.isValid()) + { + if (filterAcceptsSelf(parent.row(), parent.parent())) + return true; + parent = parent.parent(); + } +#endif + + if (filterAcceptsChild(sourceRow, sourceParent)) + return true; + + return false; +} + +bool EnabledProtocolsProxyModel::filterAcceptsSelf(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex nameIdx = sourceModel()->index(sourceRow, EnabledProtocolsModel::colProtocol, sourceParent); + if (! nameIdx.isValid()) + return false; + EnabledProtocolItem* item = static_cast<EnabledProtocolItem*>(nameIdx.internalPointer()); + if (! item) + return false; + + QRegularExpression regex(filter_, QRegularExpression::CaseInsensitiveOption); + if (! regex.isValid()) + return false; + + if (protocolType_ == EnabledProtocolItem::Any || protocolType_ == item->type()) + { + if (type_ != EnabledProtocolsProxyModel::EnabledItems && type_ != EnabledProtocolsProxyModel::DisabledItems) + { + if (! filter_.isEmpty()) + { + if (item->name().contains(regex) && type_ != OnlyDescription) + return true; + + if (item->description().contains(regex) && type_ != OnlyProtocol) + return true; + } + else + return true; + } + else if (filter_.isEmpty() || (! filter_.isEmpty() && (item->name().contains(regex) || item->description().contains(regex)))) + { + if (type_ == EnabledProtocolsProxyModel::EnabledItems && item->enabled()) + return true; + else if (type_ == EnabledProtocolsProxyModel::DisabledItems && ! item->enabled()) + return true; + } + } + + return false; +} + +bool EnabledProtocolsProxyModel::filterAcceptsChild(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex item = sourceModel()->index(sourceRow, EnabledProtocolsModel::colProtocol, sourceParent); + if (! item.isValid()) + return false; + + int childCount = item.model()->rowCount(item); + if (childCount == 0) + return false; + + for (int i = 0; i < childCount; i++) + { + if (filterAcceptsSelf(i, item)) + return true; +#if 0 + /* Recursive search disabled for performance reasons */ + if (filterAcceptsChild(i, item)) + return true; +#endif + } + + return false; +} + +void EnabledProtocolsProxyModel::setFilter(const QString& filter, EnabledProtocolsProxyModel::SearchType type, + EnabledProtocolItem::EnableProtocolType protocolType) +{ + filter_ = filter; + type_ = type; + protocolType_ = protocolType; + invalidateFilter(); +} + +void EnabledProtocolsProxyModel::setItemsEnable(EnabledProtocolsProxyModel::EnableType enableType, QModelIndex parent) +{ + if (! sourceModel()) + return; + + if (! parent.isValid()) + beginResetModel(); + + int rowcount = rowCount(parent); + for (int row = 0; row < rowcount; row++) + { + QModelIndex idx = index(row, EnabledProtocolsModel::colProtocol, parent); + + QModelIndex sIdx = mapToSource(idx); + if (sIdx.isValid()) + { + EnabledProtocolItem* item = static_cast<EnabledProtocolItem*>(sIdx.internalPointer()); + if (item && (protocolType_ == EnabledProtocolItem::Any || protocolType_ == item->type()) ) + { + Qt::CheckState enable = idx.data(Qt::CheckStateRole).value<Qt::CheckState>(); + if (enableType == Enable) + enable = Qt::Checked; + else if (enableType == Disable) + enable = Qt::Unchecked; + else + enable = enable == Qt::Checked ? Qt::Unchecked : Qt::Checked; + + sourceModel()->setData(mapToSource(idx), QVariant::fromValue(enable), Qt::CheckStateRole); + } + } + + setItemsEnable(enableType, idx); + } + + + if (! parent.isValid()) + endResetModel(); +} diff --git a/ui/qt/models/enabled_protocols_model.h b/ui/qt/models/enabled_protocols_model.h new file mode 100644 index 00000000..23b2c1bb --- /dev/null +++ b/ui/qt/models/enabled_protocols_model.h @@ -0,0 +1,143 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ENABLED_PROTOCOLS_MODEL_H +#define ENABLED_PROTOCOLS_MODEL_H + +#include <config.h> + +#include <ui/qt/models/tree_model_helpers.h> + +#include <epan/proto.h> + +#include <QAbstractItemModel> +#include <QSortFilterProxyModel> + +class EnabledProtocolItem : public ModelHelperTreeItem<EnabledProtocolItem> +{ + Q_GADGET +public: + enum EnableProtocolType{ + Any, + Standard, + Heuristic + }; + Q_ENUM(EnableProtocolType) + + EnabledProtocolItem(QString name, QString description, bool enabled, EnabledProtocolItem* parent); + virtual ~EnabledProtocolItem(); + + QString name() const {return name_;} + QString description() const {return description_;} + bool enabled() const {return enabled_;} + void setEnabled(bool enable) {enabled_ = enable;} + + EnableProtocolType type() const; + + bool applyValue(); + +protected: + virtual void applyValuePrivate(gboolean value) = 0; + + QString name_; + QString description_; + bool enabled_; + bool enabledInit_; //value that model starts with to determine change + EnableProtocolType type_; +}; + +class EnabledProtocolsModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit EnabledProtocolsModel(QObject * parent = Q_NULLPTR); + virtual ~EnabledProtocolsModel(); + + enum EnabledProtocolsColumn { + colProtocol = 0, + colDescription, + colLast + }; + + enum EnableProtocolData { + DATA_ENABLE = Qt::UserRole, + DATA_PROTOCOL_TYPE + }; + + QModelIndex index(int row, int column, + const QModelIndex & = QModelIndex()) const; + QModelIndex parent(const QModelIndex &) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + void populate(); + + void applyChanges(bool writeChanges = true); + static void disableProtocol(struct _protocol *protocol); + +protected: + static void saveChanges(bool writeChanges = true); + +private: + EnabledProtocolItem* root_; +}; + +class EnabledProtocolsProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + enum SearchType + { + EveryWhere, + OnlyProtocol, + OnlyDescription, + EnabledItems, + DisabledItems + }; + Q_ENUM(SearchType) + + enum EnableType + { + Enable, + Disable, + Invert + }; + + explicit EnabledProtocolsProxyModel(QObject * parent = Q_NULLPTR); + + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + + void setFilter(const QString& filter, EnabledProtocolsProxyModel::SearchType type, + EnabledProtocolItem::EnableProtocolType protocolType); + + void setItemsEnable(EnabledProtocolsProxyModel::EnableType enable, QModelIndex parent = QModelIndex()); + +protected: + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; + +private: + EnabledProtocolsProxyModel::SearchType type_; + EnabledProtocolItem::EnableProtocolType protocolType_; + QString filter_; + + bool filterAcceptsSelf(int sourceRow, const QModelIndex &sourceParent) const; + bool filterAcceptsChild(int sourceRow, const QModelIndex &sourceParent) const; +}; + +#endif // ENABLED_PROTOCOLS_MODEL_H diff --git a/ui/qt/models/expert_info_model.cpp b/ui/qt/models/expert_info_model.cpp new file mode 100644 index 00000000..017dba3d --- /dev/null +++ b/ui/qt/models/expert_info_model.cpp @@ -0,0 +1,424 @@ +/* expert_info_model.cpp + * Data model for Expert Info tap data. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "expert_info_model.h" + +#include "file.h" +#include <epan/proto.h> + +ExpertPacketItem::ExpertPacketItem(const expert_info_t& expert_info, column_info *cinfo, ExpertPacketItem* parent) : + packet_num_(expert_info.packet_num), + group_(expert_info.group), + severity_(expert_info.severity), + hf_id_(expert_info.hf_index), + protocol_(expert_info.protocol), + summary_(expert_info.summary), + parentItem_(parent) +{ + if (cinfo) { + info_ = col_get_text(cinfo, COL_INFO); + } +} + +ExpertPacketItem::~ExpertPacketItem() +{ + for (int row = 0; row < childItems_.count(); row++) + { + delete childItems_.value(row); + } + + childItems_.clear(); +} + +QString ExpertPacketItem::groupKey(bool group_by_summary, int severity, int group, QString protocol, int expert_hf) +{ + QString key = QString("%1|%2|%3") + .arg(severity) + .arg(group) + .arg(protocol); + if (group_by_summary) { + key += QString("|%1").arg(expert_hf); + } + return key; +} + +QString ExpertPacketItem::groupKey(bool group_by_summary) { + return groupKey(group_by_summary, severity_, group_, protocol_, hf_id_); +} + +void ExpertPacketItem::appendChild(ExpertPacketItem* child, QString hash) +{ + childItems_.append(child); + hashChild_[hash] = child; +} + +ExpertPacketItem* ExpertPacketItem::child(int row) +{ + return childItems_.value(row); +} + +ExpertPacketItem* ExpertPacketItem::child(QString hash) +{ + return hashChild_[hash]; +} + +int ExpertPacketItem::childCount() const +{ + return static_cast<int>(childItems_.count()); +} + +int ExpertPacketItem::row() const +{ + if (parentItem_) + return static_cast<int>(parentItem_->childItems_.indexOf(const_cast<ExpertPacketItem*>(this))); + + return 0; +} + +ExpertPacketItem* ExpertPacketItem::parentItem() +{ + return parentItem_; +} + + + + +ExpertInfoModel::ExpertInfoModel(CaptureFile& capture_file, QObject *parent) : + QAbstractItemModel(parent), + capture_file_(capture_file), + group_by_summary_(true), + root_(createRootItem()) +{ +} + +ExpertInfoModel::~ExpertInfoModel() +{ + delete root_; +} + +void ExpertInfoModel::clear() +{ + beginResetModel(); + + eventCounts_.clear(); + delete root_; + root_ = createRootItem(); + + endResetModel(); +} + +ExpertPacketItem* ExpertInfoModel::createRootItem() +{ + static const char* rootName = "ROOT"; +DIAG_OFF_CAST_AWAY_CONST + static expert_info_t root_expert = { 0, -1, -1, -1, rootName, (gchar*)rootName, NULL }; +DIAG_ON_CAST_AWAY_CONST + + return new ExpertPacketItem(root_expert, NULL, NULL); +} + + + +int ExpertInfoModel::numEvents(enum ExpertSeverity severity) +{ + return eventCounts_[severity]; +} + +QModelIndex ExpertInfoModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + ExpertPacketItem *parent_item, *child_item; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<ExpertPacketItem*>(parent.internalPointer()); + + Q_ASSERT(parent_item); + if (group_by_summary_) { + //don't allow group layer + if (parent_item == root_) { + int row_count = 0; + ExpertPacketItem *grandchild_item; + + for (int subrow = 0; subrow < parent_item->childCount(); subrow++) { + child_item = parent_item->child(subrow); + //summary children are always stored in first child of group + grandchild_item = child_item->child(0); + + if (row_count+grandchild_item->childCount() > row) { + return createIndex(row, column, grandchild_item->child(row-row_count)); + } + row_count += grandchild_item->childCount(); + } + + //shouldn't happen + return QModelIndex(); + } + + int root_level = 0; + ExpertPacketItem *item = parent_item; + while (item != root_) + { + root_level++; + item = item->parentItem(); + } + + if (root_level == 3) { + child_item = parent_item->child(row); + if (child_item) { + return createIndex(row, column, child_item); + } + } + + } else { + child_item = parent_item->child(row); + if (child_item) { + //only allow 2 levels deep + if (((parent_item == root_) || (parent_item->parentItem() == root_))) + return createIndex(row, column, child_item); + } + } + return QModelIndex(); +} + +QModelIndex ExpertInfoModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + ExpertPacketItem *item = static_cast<ExpertPacketItem*>(index.internalPointer()); + ExpertPacketItem *parent_item = item->parentItem(); + + if (group_by_summary_) + { + //don't allow group layer + int root_level = 0; + item = parent_item; + while ((item != root_) && (item != NULL)) + { + root_level++; + item = item->parentItem(); + } + + if (root_level == 3) + return createIndex(parent_item->row(), 0, parent_item); + + } else { + if (parent_item == root_) + return QModelIndex(); + + return createIndex(parent_item->row(), 0, parent_item); + } + + return QModelIndex(); +} + +#if 0 +Qt::ItemFlags ExpertInfoModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + ExpertPacketItem* item = static_cast<ExpertPacketItem*>(index.internalPointer()); + Qt::ItemFlags flags = QAbstractTableModel::flags(index); + + //collapse??? + return flags; +} +#endif + +QVariant ExpertInfoModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::ToolTipRole)) + return QVariant(); + + ExpertPacketItem* item = static_cast<ExpertPacketItem*>(index.internalPointer()); + if (item == NULL) + return QVariant(); + + if (role == Qt::ToolTipRole) + { + QString filterName = proto_registrar_get_abbrev(item->hfId()); + return filterName; + } + else if (role == Qt::DisplayRole) + { + switch ((enum ExpertColumn)index.column()) { + case colSeverity: + return QString(val_to_str_const(item->severity(), expert_severity_vals, "Unknown")); + case colSummary: + if (index.parent().isValid()) + { + if (item->severity() == PI_COMMENT) + return item->summary().simplified(); + if (group_by_summary_) + return item->colInfo().simplified(); + + return item->summary().simplified(); + } + else + { + if (group_by_summary_) + { + if (item->severity() == PI_COMMENT) + return "Packet comments listed below."; + if (item->hfId() != -1) { + return proto_registrar_get_name(item->hfId()); + } else { + return item->summary().simplified(); + } + } + } + return QVariant(); + case colGroup: + return QString(val_to_str_const(item->group(), expert_group_vals, "Unknown")); + case colProtocol: + return item->protocol(); + case colCount: + if (!index.parent().isValid()) + { + return item->childCount(); + } + break; + case colPacket: + return item->packetNum(); + case colHf: + return item->hfId(); + default: + break; + } + } + + return QVariant(); +} + +//GUI helpers +void ExpertInfoModel::setGroupBySummary(bool group_by_summary) +{ + beginResetModel(); + group_by_summary_ = group_by_summary; + endResetModel(); +} + +int ExpertInfoModel::rowCount(const QModelIndex &parent) const +{ + ExpertPacketItem *parent_item; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<ExpertPacketItem*>(parent.internalPointer()); + + if (group_by_summary_) { + int row_count = 0; + + //don't allow group layer + if (parent_item == root_) { + ExpertPacketItem *child_item, *grandchild_item; + + for (int row = 0; row < parent_item->childCount(); row++) { + child_item = parent_item->child(row); + grandchild_item = child_item->child(0); + row_count += grandchild_item->childCount(); + } + + return row_count; + } + + return parent_item->childCount(); + + } else { + //only allow 2 levels deep + if ((parent_item == root_) || (parent_item->parentItem() == root_)) + return parent_item->childCount(); + } + + return 0; +} + +int ExpertInfoModel::columnCount(const QModelIndex&) const +{ + return colLast; +} + +void ExpertInfoModel::addExpertInfo(const struct expert_info_s& expert_info) +{ + QString groupKey = ExpertPacketItem::groupKey(FALSE, expert_info.severity, expert_info.group, QString(expert_info.protocol), expert_info.hf_index); + QString summaryKey = ExpertPacketItem::groupKey(TRUE, expert_info.severity, expert_info.group, QString(expert_info.protocol), expert_info.hf_index); + + ExpertPacketItem* expert_root = root_->child(groupKey); + if (expert_root == NULL) { + ExpertPacketItem *new_item = new ExpertPacketItem(expert_info, &(capture_file_.capFile()->cinfo), root_); + + root_->appendChild(new_item, groupKey); + + expert_root = new_item; + } + + ExpertPacketItem *expert = new ExpertPacketItem(expert_info, &(capture_file_.capFile()->cinfo), expert_root); + expert_root->appendChild(expert, groupKey); + + //add the summary children off of the first child of the root children + ExpertPacketItem* summary_root = expert_root->child(0); + + //make a summary child + ExpertPacketItem* expert_summary_root = summary_root->child(summaryKey); + if (expert_summary_root == NULL) { + ExpertPacketItem *new_summary = new ExpertPacketItem(expert_info, &(capture_file_.capFile()->cinfo), summary_root); + + summary_root->appendChild(new_summary, summaryKey); + expert_summary_root = new_summary; + } + + ExpertPacketItem *expert_summary = new ExpertPacketItem(expert_info, &(capture_file_.capFile()->cinfo), expert_summary_root); + expert_summary_root->appendChild(expert_summary, summaryKey); +} + +void ExpertInfoModel::tapReset(void *eid_ptr) +{ + ExpertInfoModel *model = static_cast<ExpertInfoModel*>(eid_ptr); + if (!model) + return; + + model->clear(); +} + +tap_packet_status ExpertInfoModel::tapPacket(void *eid_ptr, struct _packet_info *pinfo, struct epan_dissect *, const void *data, tap_flags_t) +{ + ExpertInfoModel *model = static_cast<ExpertInfoModel*>(eid_ptr); + const expert_info_t *expert_info = (const expert_info_t *) data; + tap_packet_status status = TAP_PACKET_DONT_REDRAW; + + if (!pinfo || !model || !expert_info) + return TAP_PACKET_DONT_REDRAW; + + model->addExpertInfo(*expert_info); + + status = TAP_PACKET_REDRAW; + + model->eventCounts_[(enum ExpertSeverity)expert_info->severity]++; + + return status; +} + +void ExpertInfoModel::tapDraw(void *eid_ptr) +{ + ExpertInfoModel *model = static_cast<ExpertInfoModel*>(eid_ptr); + if (!model) + return; + + model->beginResetModel(); + model->endResetModel(); +} diff --git a/ui/qt/models/expert_info_model.h b/ui/qt/models/expert_info_model.h new file mode 100644 index 00000000..ada10aea --- /dev/null +++ b/ui/qt/models/expert_info_model.h @@ -0,0 +1,128 @@ +/** @file + * + * Data model for Expert Info tap data. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef EXPERT_INFO_MODEL_H +#define EXPERT_INFO_MODEL_H + +#include <config.h> + +#include <QAbstractItemModel> +#include <QList> +#include <QMap> + +#include <ui/qt/capture_file.h> + +#include <epan/expert.h> +#include <epan/tap.h> +#include <epan/column-utils.h> + +class ExpertPacketItem +{ +public: + ExpertPacketItem(const expert_info_t& expert_info, column_info *cinfo, ExpertPacketItem* parent); + virtual ~ExpertPacketItem(); + + unsigned int packetNum() const { return packet_num_; } + int group() const { return group_; } + int severity() const { return severity_; } + int hfId() const { return hf_id_; } + QString protocol() const { return protocol_; } + QString summary() const { return summary_; } + QString colInfo() const { return info_; } + + static QString groupKey(bool group_by_summary, int severity, int group, QString protocol, int expert_hf); + QString groupKey(bool group_by_summary); + + void appendChild(ExpertPacketItem* child, QString hash); + ExpertPacketItem* child(int row); + ExpertPacketItem* child(QString hash); + int childCount() const; + int row() const; + ExpertPacketItem* parentItem(); + +private: + unsigned int packet_num_; + int group_; + int severity_; + int hf_id_; + // Half-hearted attempt at conserving memory. If this isn't sufficient, + // PacketListRecord interns column strings in a GStringChunk. + QByteArray protocol_; + QByteArray summary_; + QByteArray info_; + + QList<ExpertPacketItem*> childItems_; + ExpertPacketItem* parentItem_; + QHash<QString, ExpertPacketItem*> hashChild_; //optimization for insertion +}; + +class ExpertInfoModel : public QAbstractItemModel +{ +public: + ExpertInfoModel(CaptureFile& capture_file, QObject *parent = 0); + virtual ~ExpertInfoModel(); + + enum ExpertColumn { + colSeverity = 0, + colSummary, + colGroup, + colProtocol, + colCount, + colPacket, + colHf, + colLast + }; + + enum ExpertSeverity { + severityError = PI_ERROR, + severityWarn = PI_WARN, + severityNote = PI_NOTE, + severityChat = PI_CHAT, + severityComment = PI_COMMENT + }; + + QModelIndex index(int row, int column, + const QModelIndex & = QModelIndex()) const; + QModelIndex parent(const QModelIndex &) const; +#if 0 + Qt::ItemFlags flags(const QModelIndex &index) const; +#endif + QVariant data(const QModelIndex &index, int role) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + int numEvents(enum ExpertSeverity severity); + + void clear(); + + //GUI helpers + void setGroupBySummary(bool group_by_summary); + + // Called from tapPacket + void addExpertInfo(const struct expert_info_s& expert_info); + + // Callbacks for register_tap_listener + static void tapReset(void *eid_ptr); + static tap_packet_status tapPacket(void *eid_ptr, struct _packet_info *pinfo, struct epan_dissect *, const void *data, tap_flags_t flags); + static void tapDraw(void *eid_ptr); + +private: + CaptureFile& capture_file_; + + ExpertPacketItem* createRootItem(); + + bool group_by_summary_; + ExpertPacketItem* root_; + + QHash<enum ExpertSeverity, int> eventCounts_; +}; +#endif // EXPERT_INFO_MODEL_H diff --git a/ui/qt/models/expert_info_proxy_model.cpp b/ui/qt/models/expert_info_proxy_model.cpp new file mode 100644 index 00000000..ac13eff5 --- /dev/null +++ b/ui/qt/models/expert_info_proxy_model.cpp @@ -0,0 +1,290 @@ +/* expert_info_model.cpp + * Data model for Expert Info tap data. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/expert_info_model.h> +#include <ui/qt/models/expert_info_proxy_model.h> +#include <ui/qt/utils/color_utils.h> + +#include <QRegularExpression> + +ExpertInfoProxyModel::ExpertInfoProxyModel(QObject *parent) : QSortFilterProxyModel(parent), + severityMode_(Group) +{ +} + +bool ExpertInfoProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + ExpertPacketItem *left_item, + *right_item; + QString leftStr, rightStr; + bool checkPacketNumber = false; + int compare_ret; + + if (source_left.parent().isValid() && source_right.parent().isValid()) { + left_item = static_cast<ExpertPacketItem*>(source_left.parent().internalPointer()); + right_item = static_cast<ExpertPacketItem*>(source_right.parent().internalPointer()); + } else { + left_item = static_cast<ExpertPacketItem*>(source_left.internalPointer()); + right_item = static_cast<ExpertPacketItem*>(source_right.internalPointer()); + } + + if ((left_item != NULL) && (right_item != NULL)) { + switch (source_left.column()) + { + case colProxySeverity: + if (left_item->severity() != right_item->severity()) { + return (left_item->severity() < right_item->severity()); + } + + checkPacketNumber = true; + break; + case colProxySummary: + compare_ret = left_item->summary().compare(right_item->summary()); + if (compare_ret < 0) + return true; + if (compare_ret > 0) + return false; + + checkPacketNumber = true; + break; + case colProxyGroup: + if (left_item->group() != right_item->group()) { + return (left_item->group() < right_item->group()); + } + + checkPacketNumber = true; + break; + case colProxyProtocol: + compare_ret = left_item->protocol().compare(right_item->protocol()); + if (compare_ret < 0) + return true; + if (compare_ret > 0) + return false; + + checkPacketNumber = true; + break; + case colProxyCount: + break; + default: + break; + } + + if (checkPacketNumber) { + return (left_item->packetNum() < right_item->packetNum()); + } + } + + // fallback to string cmp on other fields + return QSortFilterProxyModel::lessThan(source_left, source_right); +} + +QVariant ExpertInfoProxyModel::data(const QModelIndex &proxy_index, int role) const +{ + QModelIndex source_index; + + switch (role) + { + case Qt::BackgroundRole: + { + source_index = mapToSource(proxy_index); + + // only color base row + if (!source_index.isValid() || source_index.parent().isValid()) + return QVariant(); + + ExpertPacketItem* item = static_cast<ExpertPacketItem*>(source_index.internalPointer()); + if (item == NULL) + return QVariant(); + + // provide background color for groups + switch(item->severity()) { + case(PI_COMMENT): + return QBrush(ColorUtils::expert_color_comment); + case(PI_CHAT): + return QBrush(ColorUtils::expert_color_chat); + case(PI_NOTE): + return QBrush(ColorUtils::expert_color_note); + case(PI_WARN): + return QBrush(ColorUtils::expert_color_warn); + case(PI_ERROR): + return QBrush(ColorUtils::expert_color_error); + } + } + break; + case Qt::ForegroundRole: + { + source_index = mapToSource(proxy_index); + + // only color base row + if (!source_index.isValid() || source_index.parent().isValid()) + return QVariant(); + + ExpertPacketItem* item = static_cast<ExpertPacketItem*>(source_index.internalPointer()); + if (item == NULL) + return QVariant(); + + // provide foreground color for groups + switch(item->severity()) { + case(PI_COMMENT): + case(PI_CHAT): + case(PI_NOTE): + case(PI_WARN): + case(PI_ERROR): + return QBrush(ColorUtils::expert_color_foreground); + } + } + break; + case Qt::TextAlignmentRole: + switch (proxy_index.column()) + { + case colProxySeverity: + //packet number should be right aligned + if (source_index.parent().isValid()) + return Qt::AlignRight; + break; + case colProxyCount: + return Qt::AlignRight; + default: + break; + } + return Qt::AlignLeft; + + case Qt::DisplayRole: + source_index = mapToSource(proxy_index); + + switch (proxy_index.column()) + { + case colProxySeverity: + if (source_index.parent().isValid()) + return sourceModel()->data(source_index.sibling(source_index.row(), ExpertInfoModel::colPacket), role); + + return sourceModel()->data(source_index.sibling(source_index.row(), ExpertInfoModel::colSeverity), role); + case colProxySummary: + return sourceModel()->data(source_index.sibling(source_index.row(), ExpertInfoModel::colSummary), role); + case colProxyGroup: + return sourceModel()->data(source_index.sibling(source_index.row(), ExpertInfoModel::colGroup), role); + case colProxyProtocol: + return sourceModel()->data(source_index.sibling(source_index.row(), ExpertInfoModel::colProtocol), role); + case colProxyCount: + //only show counts for parent + if (!source_index.parent().isValid()) { + //because of potential filtering, count is computed manually + unsigned int count = 0; + ExpertPacketItem *child_item, + *item = static_cast<ExpertPacketItem*>(source_index.internalPointer()); + for (int row = 0; row < item->childCount(); row++) { + child_item = item->child(row); + if (child_item == NULL) + continue; + if (filterAcceptItem(*child_item)) + count++; + } + + return count; + } + } + break; + } + + return QSortFilterProxyModel::data(proxy_index, role); +} + +QVariant ExpertInfoProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + + switch ((enum ExpertProxyColumn)section) { + case colProxySeverity: + if (severityMode_ == Packet) + return tr("Packet"); + else + return tr("Severity"); + case colProxySummary: + return tr("Summary"); + case colProxyGroup: + return tr("Group"); + case colProxyProtocol: + return tr("Protocol"); + case colProxyCount: + return tr("Count"); + default: + break; + } + } + return QVariant(); +} + +int ExpertInfoProxyModel::columnCount(const QModelIndex&) const +{ + return colProxyLast; +} + +bool ExpertInfoProxyModel::filterAcceptItem(ExpertPacketItem& item) const +{ + if (hidden_severities_.contains(item.severity())) + return false; + + if (!textFilter_.isEmpty()) { + QRegularExpression regex(textFilter_, QRegularExpression::CaseInsensitiveOption | + QRegularExpression::UseUnicodePropertiesOption); + if (! regex.isValid()) + return false; + + if (item.protocol().contains(regex)) + return true; + + if (item.summary().contains(regex)) + return true; + + if (item.colInfo().contains(regex)) + return true; + + return false; + } + + return true; +} + +bool ExpertInfoProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex severityIdx = sourceModel()->index(sourceRow, ExpertInfoModel::colSeverity, sourceParent); + ExpertPacketItem* item = static_cast<ExpertPacketItem*>(severityIdx.internalPointer()); + if (item == NULL) + return true; + + return filterAcceptItem(*item); +} + +//GUI helpers +void ExpertInfoProxyModel::setSeverityMode(enum SeverityMode mode) +{ + severityMode_ = mode; + emit headerDataChanged(Qt::Vertical, 0, 1); +} + +void ExpertInfoProxyModel::setSeverityFilter(int severity, bool hide) +{ + if (hide) + { + hidden_severities_ << severity; + } + else + { + hidden_severities_.removeOne(severity); + } + + invalidateFilter(); +} + +void ExpertInfoProxyModel::setSummaryFilter(const QString &filter) +{ + textFilter_ = filter; + invalidateFilter(); +} diff --git a/ui/qt/models/expert_info_proxy_model.h b/ui/qt/models/expert_info_proxy_model.h new file mode 100644 index 00000000..411d88d2 --- /dev/null +++ b/ui/qt/models/expert_info_proxy_model.h @@ -0,0 +1,61 @@ +/** @file + * + * Data model for Expert Info tap data. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef EXPERT_INFO_PROXY_MODEL_H +#define EXPERT_INFO_PROXY_MODEL_H + +#include <config.h> + +#include <QSortFilterProxyModel> + +class ExpertPacketItem; + +class ExpertInfoProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + ExpertInfoProxyModel(QObject *parent = 0); + + enum SeverityMode { Group, Packet }; + enum ExpertProxyColumn { + colProxySeverity = 0, + colProxySummary, + colProxyGroup, + colProxyProtocol, + colProxyCount, + colProxyLast + }; + + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + + //GUI helpers + void setSeverityMode(enum SeverityMode); + void setSeverityFilter(int severity, bool hide); + void setSummaryFilter(const QString &filter); + +protected: + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; + bool filterAcceptItem(ExpertPacketItem& item) const; + + enum SeverityMode severityMode_; + QList<int> hidden_severities_; + + QString textFilter_; + +}; + +#endif // EXPERT_INFO_PROXY_MODEL_H diff --git a/ui/qt/models/export_objects_model.cpp b/ui/qt/models/export_objects_model.cpp new file mode 100644 index 00000000..3cf9ec63 --- /dev/null +++ b/ui/qt/models/export_objects_model.cpp @@ -0,0 +1,316 @@ +/* export_objects_model.cpp + * Data model for Export Objects. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "export_objects_model.h" + +#include <ui/qt/utils/qt_ui_utils.h> +#include <ui/qt/utils/variant_pointer.h> +#include <wsutil/filesystem.h> +#include <epan/prefs.h> + +#include <QDir> + +extern "C" { + +static void +object_list_add_entry(void *gui_data, export_object_entry_t *entry) { + export_object_list_gui_t *object_list = (export_object_list_gui_t*)gui_data; + + if (object_list && object_list->model) + object_list->model->addObjectEntry(entry); +} + +static export_object_entry_t* +object_list_get_entry(void *gui_data, int row) { + export_object_list_gui_t *object_list = (export_object_list_gui_t*)gui_data; + + if (object_list && object_list->model) + return object_list->model->objectEntry(row); + + return NULL; +} + +} // extern "C" + + + + +ExportObjectModel::ExportObjectModel(register_eo_t* eo, QObject *parent) : + QAbstractTableModel(parent), + eo_(eo) +{ + eo_gui_data_.model = this; + + export_object_list_.add_entry = object_list_add_entry; + export_object_list_.get_entry = object_list_get_entry; + export_object_list_.gui_data = (void*)&eo_gui_data_; +} + +ExportObjectModel::~ExportObjectModel() +{ + foreach (QVariant v, objects_) { + eo_free_entry(VariantPointer<export_object_entry_t>::asPtr(v)); + } +} + +QVariant ExportObjectModel::data(const QModelIndex &index, int role) const +{ + if ((!index.isValid()) || ((role != Qt::DisplayRole) && (role != Qt::UserRole))) { + return QVariant(); + } + + if (role == Qt::DisplayRole) + { + export_object_entry_t *entry = VariantPointer<export_object_entry_t>::asPtr(objects_.value(index.row())); + if (entry == NULL) + return QVariant(); + + switch(index.column()) + { + case colPacket: + return QString::number(entry->pkt_num); + case colHostname: + return QString::fromUtf8(entry->hostname); + case colContent: + return QString::fromUtf8(entry->content_type); + case colSize: + return file_size_to_qstring(entry->payload_len); + case colFilename: + return QString::fromUtf8(entry->filename); + } + } + else if (role == Qt::UserRole) + { + return objects_.value(index.row()); + } + + return QVariant(); +} + +QVariant ExportObjectModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) + return QVariant(); + + switch (section) { + case colPacket: + return tr("Packet"); + case colHostname: + return tr("Hostname"); + case colContent: + return tr("Content Type"); + case colSize: + return tr("Size"); + case colFilename: + return tr("Filename"); + } + + return QVariant(); +} + +int ExportObjectModel::rowCount(const QModelIndex &parent) const +{ + // there are no children + if (parent.isValid()) { + return 0; + } + + return static_cast<int>(objects_.count()); +} + +int ExportObjectModel::columnCount(const QModelIndex&) const +{ + return colExportObjectMax; +} + +void ExportObjectModel::addObjectEntry(export_object_entry_t *entry) +{ + if (entry == NULL) + return; + + int count = static_cast<int>(objects_.count()); + beginInsertRows(QModelIndex(), count, count); + objects_.append(VariantPointer<export_object_entry_t>::asQVariant(entry)); + endInsertRows(); +} + +export_object_entry_t* ExportObjectModel::objectEntry(int row) +{ + return VariantPointer<export_object_entry_t>::asPtr(objects_.value(row)); +} + +bool ExportObjectModel::saveEntry(QModelIndex &index, QString filename) +{ + if (!index.isValid() || filename.isEmpty()) + return false; + + export_object_entry_t *entry = VariantPointer<export_object_entry_t>::asPtr(objects_.value(index.row())); + if (entry == NULL) + return false; + + if (filename.length() > 0) { + write_file_binary_mode(qUtf8Printable(filename), entry->payload_data, entry->payload_len); + } + + return true; +} + +void ExportObjectModel::saveAllEntries(QString path) +{ + if (path.isEmpty()) + return; + + QDir save_dir(path); + export_object_entry_t *entry; + + for (QList<QVariant>::iterator it = objects_.begin(); it != objects_.end(); ++it) + { + entry = VariantPointer<export_object_entry_t>::asPtr(*it); + if (entry == NULL) + continue; + + guint count = 0; + QString filename; + + do { + GString *safe_filename; + + if (entry->filename) + safe_filename = eo_massage_str(entry->filename, + EXPORT_OBJECT_MAXFILELEN, count); + else { + char generic_name[EXPORT_OBJECT_MAXFILELEN+1]; + const char *ext; + ext = eo_ct2ext(entry->content_type); + snprintf(generic_name, sizeof(generic_name), + "object%u%s%s", entry->pkt_num, ext ? "." : "", + ext ? ext : ""); + safe_filename = eo_massage_str(generic_name, + EXPORT_OBJECT_MAXFILELEN, count); + } + filename = QString::fromUtf8(safe_filename->str); + g_string_free(safe_filename, TRUE); + } while (save_dir.exists(filename) && ++count < prefs.gui_max_export_objects); + write_file_binary_mode(qUtf8Printable(save_dir.filePath(filename)), + entry->payload_data, entry->payload_len); + } +} + +void ExportObjectModel::resetObjects() +{ + export_object_gui_reset_cb reset_cb = get_eo_reset_func(eo_); + + beginResetModel(); + objects_.clear(); + endResetModel(); + + if (reset_cb) + reset_cb(); +} + +// Called by taps +/* Runs at the beginning of tapping only */ +void ExportObjectModel::resetTap(void *tapdata) +{ + export_object_list_t *tap_object = (export_object_list_t *)tapdata; + export_object_list_gui_t *object_list = (export_object_list_gui_t *)tap_object->gui_data; + if (object_list && object_list->model) + object_list->model->resetObjects(); +} + +const char* ExportObjectModel::getTapListenerName() +{ + return get_eo_tap_listener_name(eo_); +} + +void* ExportObjectModel::getTapData() +{ + return &export_object_list_; +} + +tap_packet_cb ExportObjectModel::getTapPacketFunc() +{ + return get_eo_packet_func(eo_); +} + +void ExportObjectModel::removeTap() +{ + eo_gui_data_.model = NULL; +} + + + +ExportObjectProxyModel::ExportObjectProxyModel(QObject * parent) + : QSortFilterProxyModel(parent) +{ + +} + +bool ExportObjectProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + export_object_entry_t *left_entry = VariantPointer<export_object_entry_t>::asPtr(sourceModel()->data(source_left, Qt::UserRole)), + *right_entry = VariantPointer<export_object_entry_t>::asPtr(sourceModel()->data(source_right, Qt::UserRole)); + + if ((left_entry != NULL) && (right_entry != NULL)) + { + switch (source_left.column()) + { + case ExportObjectModel::colPacket: + return left_entry->pkt_num < right_entry->pkt_num; + case ExportObjectModel::colSize: + return left_entry->payload_len < right_entry->payload_len; + case ExportObjectModel::colFilename: + break; + } + } + + return QSortFilterProxyModel::lessThan(source_left, source_right); +} + +void ExportObjectProxyModel::setContentFilterString(QString filter_) +{ + contentFilter_ = filter_; + invalidateFilter(); +} + +void ExportObjectProxyModel::setTextFilterString(QString filter_) +{ + textFilter_ = filter_; + invalidateFilter(); +} + +bool ExportObjectProxyModel::filterAcceptsRow(int source_row, const QModelIndex &/*source_parent*/) const +{ + if (contentFilter_.length() > 0) + { + QModelIndex idx = sourceModel()->index(source_row, ExportObjectModel::colContent); + if (!idx.isValid()) + return false; + + if (contentFilter_.compare(idx.data().toString()) != 0) + return false; + } + + if (textFilter_.length() > 0) + { + QModelIndex hostIdx = sourceModel()->index(source_row, ExportObjectModel::colHostname); + QModelIndex fileIdx = sourceModel()->index(source_row, ExportObjectModel::colFilename); + if (!hostIdx.isValid() || !fileIdx.isValid()) + return false; + + QString host = hostIdx.data().toString(); + QString file = fileIdx.data().toString(); + + if (!host.contains(textFilter_) && !file.contains(textFilter_)) + return false; + } + + return true; +} diff --git a/ui/qt/models/export_objects_model.h b/ui/qt/models/export_objects_model.h new file mode 100644 index 00000000..1aff4345 --- /dev/null +++ b/ui/qt/models/export_objects_model.h @@ -0,0 +1,91 @@ +/** @file + * + * Data model for Export Objects. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef EXPORT_OBJECTS_MODEL_H +#define EXPORT_OBJECTS_MODEL_H + +#include <config.h> + +#include <epan/tap.h> +#include <epan/export_object.h> + +#include <QAbstractTableModel> +#include <QSortFilterProxyModel> +#include <QList> + +typedef struct export_object_list_gui_t { + class ExportObjectModel *model; +} export_object_list_gui_t; + +class ExportObjectModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + ExportObjectModel(register_eo_t* eo, QObject *parent); + virtual ~ExportObjectModel(); + + enum ExportObjectColumn { + colPacket = 0, + colHostname, + colContent, + colSize, + colFilename, + colExportObjectMax + }; + + void addObjectEntry(export_object_entry_t *entry); + export_object_entry_t *objectEntry(int row); + void resetObjects(); + + bool saveEntry(QModelIndex &index, QString filename); + void saveAllEntries(QString path); + + const char* getTapListenerName(); + void* getTapData(); + tap_packet_cb getTapPacketFunc(); + static void resetTap(void *tapdata); + void removeTap(); + + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + +private: + QList<QVariant> objects_; + + export_object_list_t export_object_list_; + export_object_list_gui_t eo_gui_data_; + register_eo_t* eo_; +}; + +class ExportObjectProxyModel : public QSortFilterProxyModel +{ +public: + + explicit ExportObjectProxyModel(QObject * parent = Q_NULLPTR); + + void setContentFilterString(QString contentFilter); + void setTextFilterString(QString textFilter); + +protected: + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + +private: + QString contentFilter_; + QString textFilter_; + +}; + +#endif // EXPORT_OBJECTS_MODEL_H diff --git a/ui/qt/models/fileset_entry_model.cpp b/ui/qt/models/fileset_entry_model.cpp new file mode 100644 index 00000000..8c9f504b --- /dev/null +++ b/ui/qt/models/fileset_entry_model.cpp @@ -0,0 +1,155 @@ +/* fileset_entry_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/fileset_entry_model.h> + +#include "wsutil/utf8_entities.h" + +#include <ui/qt/utils/qt_ui_utils.h> + +#include <QRegularExpression> + +FilesetEntryModel::FilesetEntryModel(QObject * parent) : + QAbstractItemModel(parent) +{} + +QModelIndex FilesetEntryModel::index(int row, int column, const QModelIndex &) const +{ + if (row >= entries_.count() || row < 0 || column > ColumnCount) { + return QModelIndex(); + } + + return createIndex(row, column, const_cast<fileset_entry *>(entries_.at(row))); +} + +int FilesetEntryModel::rowCount(const QModelIndex &) const +{ + return static_cast<int>(entries_.count()); +} + +QVariant FilesetEntryModel::data(const QModelIndex &index, int role) const +{ + if (! index.isValid() || index.row() >= rowCount()) + return QVariant(); + + const fileset_entry *entry = static_cast<fileset_entry*>(index.internalPointer()); + if (role == Qt::DisplayRole && entry) { + switch (index.column()) { + case Name: + return QString(entry->name); + case Created: + { + QString created = nameToDate(entry->name); + if (created.length() < 1) { + /* if this file doesn't follow the file set pattern, */ + /* use the creation time of that file if available */ + /* https://en.wikipedia.org/wiki/ISO_8601 */ + /* + * macOS provides 0 if the file system doesn't support the + * creation time; FreeBSD provides -1. + * + * If this OS doesn't provide the creation time with stat(), + * it will be 0. + */ + if (entry->ctime > 0) { + created = time_tToString(entry->ctime); + } else { + created = UTF8_EM_DASH; + } + } + return created; + } + case Modified: + return time_tToString(entry->mtime); + case Size: + return file_size_to_qstring(entry->size); + default: + break; + } + } else if (role == Qt::ToolTipRole) { + return QString(tr("Open this capture file")); + } else if (role == Qt::TextAlignmentRole) { + switch (index.column()) { + case Size: + // Not perfect but better than nothing. + return Qt::AlignRight; + default: + return Qt::AlignLeft; + } + } + return QVariant(); +} + +QVariant FilesetEntryModel::headerData(int section, Qt::Orientation, int role) const +{ + if (role != Qt::DisplayRole) return QVariant(); + + switch (section) { + case Name: + return tr("Filename"); + case Created: + return tr("Created"); + case Modified: + return tr("Modified"); + case Size: + return tr("Size"); + default: + break; + } + return QVariant(); +} + +void FilesetEntryModel::appendEntry(const fileset_entry *entry) +{ + emit beginInsertRows(QModelIndex(), rowCount(), rowCount()); + entries_ << entry; + emit endInsertRows(); +} + +void FilesetEntryModel::clear() +{ + fileset_delete(); + beginResetModel(); + entries_.clear(); + endResetModel(); +} + +QString FilesetEntryModel::nameToDate(const char *name) const { + QString dn; + + if (!fileset_filename_match_pattern(name)) + return NULL; + + dn = name; + dn.remove(QRegularExpression(".*_")); + dn.truncate(14); + dn.insert(4, '-'); + dn.insert(7, '-'); + dn.insert(10, ' '); + dn.insert(13, ':'); + dn.insert(16, ':'); + return dn; +} + +QString FilesetEntryModel::time_tToString(time_t clock) const +{ + struct tm *local = localtime(&clock); + if (!local) return UTF8_EM_DASH; + + // yyyy-MM-dd HH:mm:ss + // The equivalent QDateTime call is pretty slow here, possibly related to QTBUG-21678 + // and/or QTBUG-41714. + return QString("%1-%2-%3 %4:%5:%6") + .arg(local->tm_year + 1900, 4, 10, QChar('0')) + .arg(local->tm_mon+1, 2, 10, QChar('0')) + .arg(local->tm_mday, 2, 10, QChar('0')) + .arg(local->tm_hour, 2, 10, QChar('0')) + .arg(local->tm_min, 2, 10, QChar('0')) + .arg(local->tm_sec, 2, 10, QChar('0')); +} diff --git a/ui/qt/models/fileset_entry_model.h b/ui/qt/models/fileset_entry_model.h new file mode 100644 index 00000000..aa812b0c --- /dev/null +++ b/ui/qt/models/fileset_entry_model.h @@ -0,0 +1,52 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef FILESET_ENTRY_MODEL_H +#define FILESET_ENTRY_MODEL_H + +#include <config.h> + +#include <glib.h> + +#include <fileset.h> + +#include <QAbstractItemModel> +#include <QModelIndex> +#include <QVector> + +class FilesetEntryModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit FilesetEntryModel(QObject * parent = 0); + + QModelIndex index(int row, int column, const QModelIndex & = QModelIndex()) const; + // Everything is under the root. + virtual QModelIndex parent(const QModelIndex &) const { return QModelIndex(); } + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &) const { return ColumnCount; } + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation, int role = Qt::DisplayRole) const; + + virtual void appendEntry(const fileset_entry *entry); + const fileset_entry *getRowEntry(int row) const { return entries_.value(row, NULL); } + int entryCount() const { return static_cast<int>(entries_.count()); } + // Calls fileset_delete and clears our model data. + void clear(); + +private: + QVector<const fileset_entry *> entries_; + enum Column { Name, Created, Modified, Size, ColumnCount }; + + QString nameToDate(const char *name) const ; + QString time_tToString(time_t clock) const; +}; + +#endif // FILESET_ENTRY_MODEL_H diff --git a/ui/qt/models/filter_list_model.cpp b/ui/qt/models/filter_list_model.cpp new file mode 100644 index 00000000..3ed25839 --- /dev/null +++ b/ui/qt/models/filter_list_model.cpp @@ -0,0 +1,314 @@ +/* filter_list_model.cpp + * Model for all filter types + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <glib.h> + +#include <wsutil/filesystem.h> + +#include <ui/qt/utils/qt_ui_utils.h> +#include <ui/qt/utils/wireshark_mime_data.h> +#include <ui/qt/models/filter_list_model.h> +#include <ui/qt/models/profile_model.h> + +#include <QFile> +#include <QTextStream> +#include <QRegularExpression> +#include <QDir> +#include <QMimeData> + +/* + * Old filter file name. + */ +#define FILTER_FILE_NAME "filters" + +/* + * Capture filter file name. + */ +#define CFILTER_FILE_NAME "cfilters" + +/* + * Display filter file name. + */ +#define DFILTER_FILE_NAME "dfilters" + +FilterListModel::FilterListModel(QObject * parent) : + QAbstractListModel(parent), + type_(FilterListModel::Display) +{ + reload(); +} + +FilterListModel::FilterListModel(FilterListModel::FilterListType type, QObject * parent) : + QAbstractListModel(parent), + type_(type) +{ + reload(); +} + +void FilterListModel::reload() +{ + storage.clear(); + + const char * cfile = (type_ == FilterListModel::Capture) ? CFILTER_FILE_NAME : DFILTER_FILE_NAME; + + /* Try personal config file first */ + QString fileName = gchar_free_to_qstring(get_persconffile_path(cfile, TRUE)); + if (fileName.length() <= 0 || ! QFileInfo::exists(fileName)) + fileName = gchar_free_to_qstring(get_persconffile_path(FILTER_FILE_NAME, TRUE)); + if (fileName.length() <= 0 || ! QFileInfo::exists(fileName)) + fileName = gchar_free_to_qstring(get_datafile_path(cfile)); + if (fileName.length() <= 0 || ! QFileInfo::exists(fileName)) + return; + + QFile file(fileName); + /* Still can use the model, just have to start from an empty set */ + if (! file.open(QIODevice::ReadOnly | QIODevice::Text)) + return; + + QTextStream in(&file); + QRegularExpression rx("\\s*\\\"\\s*(.*?)\\s*\\\"\\s(.*)"); + while (!in.atEnd()) + { + QString data = in.readLine().trimmed(); + /* Filter out lines that do not contain content: + * - Starting with # is a comment + * - Does not start with a quoted string + */ + if (data.startsWith("#") || ! data.trimmed().startsWith("\"")) + continue; + + QStringList content = data.split(QChar('\n')); + foreach (QString line, content) + { + QRegularExpressionMatch match = rx.match(line); + if (match.hasMatch()) { + addFilter(match.captured(1).trimmed(), match.captured(2).trimmed()); + } + } + } +} + +void FilterListModel::setFilterType(FilterListModel::FilterListType type) +{ + type_ = type; + reload(); +} + +FilterListModel::FilterListType FilterListModel::filterType() const +{ + return type_; +} + +int FilterListModel::rowCount(const QModelIndex &/* parent */) const +{ + return static_cast<int>(storage.count()); +} + +int FilterListModel::columnCount(const QModelIndex &/* parent */) const +{ + return 2; +} + +QVariant FilterListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section >= columnCount() || section < 0 || orientation != Qt::Horizontal) + return QVariant(); + + if (role == Qt::DisplayRole) + { + switch (section) { + case ColumnName: + return tr("Filter Name"); + break; + case ColumnExpression: + return tr("Filter Expression"); + break; + } + } + + return QVariant(); +} + +QVariant FilterListModel::data(const QModelIndex &index, int role) const +{ + if (! index.isValid() || index.row() >= rowCount()) + return QVariant(); + + QStringList row = storage.at(index.row()).split("\n"); + if (role == Qt::DisplayRole) + return row.at(index.column()); + + return QVariant(); +} + +bool FilterListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (! index.isValid() || index.row() >= rowCount() || role != Qt::EditRole) + return false; + + QStringList row = storage.at(index.row()).split("\n"); + if (row.count() <= index.column()) + return false; + + if (index.column() == FilterListModel::ColumnName && value.toString().contains("\"")) + return false; + + row[index.column()] = value.toString(); + storage[index.row()] = row.join("\n"); + + return true; +} + +Qt::ItemFlags FilterListModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags fl = QAbstractListModel::flags(index); + fl |= Qt::ItemIsDropEnabled; + + if (! index.isValid() || index.row() >= rowCount()) + return fl; + + fl |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled; + + return fl; +} +QModelIndex FilterListModel::addFilter(QString name, QString expression) +{ + if (name.length() == 0 || expression.length() == 0) + return QModelIndex(); + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + storage << QString("%1\n%2").arg(name).arg(expression); + endInsertRows(); + + return index(rowCount() - 1, 0); +} + +QModelIndex FilterListModel::findByName(QString name) +{ + if (name.length() == 0) + return QModelIndex(); + + for (int cnt = 0; cnt < rowCount(); cnt++) + { + if (storage.at(cnt).startsWith(QString("%1\n").arg(name))) + return index(cnt, 0); + } + + return QModelIndex(); +} + +QModelIndex FilterListModel::findByExpression(QString expression) +{ + if (expression.length() == 0) + return QModelIndex(); + + for (int cnt = 0; cnt < rowCount(); cnt++) + { + if (storage.at(cnt).endsWith(QString("\n%1").arg(expression))) + return index(cnt, 0); + } + + return QModelIndex(); +} + +void FilterListModel::removeFilter(QModelIndex idx) +{ + if (! idx.isValid() || idx.row() >= rowCount()) + return; + + beginRemoveRows(QModelIndex(), idx.row(), idx.row()); + storage.removeAt(idx.row()); + endRemoveRows(); +} + +void FilterListModel::saveList() +{ + QString filename = (type_ == FilterListModel::Capture) ? CFILTER_FILE_NAME : DFILTER_FILE_NAME; + + filename = QString("%1%2%3").arg(ProfileModel::activeProfilePath()).arg("/").arg(filename); + QFile file(filename); + + if (! file.open(QIODevice::WriteOnly | QIODevice::Text)) + return; + + QTextStream out(&file); + for (int row = 0; row < rowCount(); row++) + { + QString line = QString("\"%1\"").arg(index(row, ColumnName).data().toString().trimmed()); + line.append(QString(" %1").arg(index(row, ColumnExpression).data().toString())); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + out << line << Qt::endl; +#else + out << line << endl; +#endif + } + + file.close(); +} + +Qt::DropActions FilterListModel::supportedDropActions() const +{ + return Qt::MoveAction; +} + +QStringList FilterListModel::mimeTypes() const +{ + return QStringList() << WiresharkMimeData::FilterListMimeType; +} + +QMimeData *FilterListModel::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *mimeData = new QMimeData(); + QStringList rows; + + foreach (const QModelIndex &index, indexes) + { + if (! rows.contains(QString::number(index.row()))) + rows << QString::number(index.row()); + } + + mimeData->setData(WiresharkMimeData::FilterListMimeType, rows.join(",").toUtf8()); + return mimeData; +} + +bool FilterListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int /* column */, const QModelIndex & parent) +{ + if (action != Qt::MoveAction) + return true; + + if (! data->hasFormat(WiresharkMimeData::FilterListMimeType)) + return true; + + QStringList rows = QString(data->data(WiresharkMimeData::FilterListMimeType)).split(","); + + int insertRow = parent.isValid() ? parent.row() : row; + + /* for now, only single rows can be selected */ + if (rows.count() > 0) + { + bool ok = false; + int strow = rows[0].toInt(&ok); + if (ok) + { + int storeTo = insertRow; + if (storeTo < 0 || storeTo >= storage.count()) + { + storeTo = static_cast<int>(storage.count()) - 1; + } + + beginResetModel(); + storage.move(strow, storeTo); + endResetModel(); + } + } + + return true; +} diff --git a/ui/qt/models/filter_list_model.h b/ui/qt/models/filter_list_model.h new file mode 100644 index 00000000..4f528d2f --- /dev/null +++ b/ui/qt/models/filter_list_model.h @@ -0,0 +1,71 @@ +/** @file + * + * Model for all filter types + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef FILTER_LIST_MODEL_h +#define FILTER_LIST_MODEL_h + +#include <config.h> + +#include <QAbstractListModel> +#include <QList> +#include <QStringList> + +class FilterListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum FilterListType { + Display, + Capture + }; + + explicit FilterListModel(FilterListType type = FilterListModel::Display, QObject * parent = Q_NULLPTR); + explicit FilterListModel(QObject * parent = Q_NULLPTR); + + enum { + ColumnName, + ColumnExpression + }; + + void setFilterType(FilterListModel::FilterListType type); + FilterListModel::FilterListType filterType() const; + + QModelIndex findByName(QString name); + QModelIndex findByExpression(QString expression); + + QModelIndex addFilter(QString name, QString expression); + void removeFilter(QModelIndex idx); + + void saveList(); + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + + virtual Qt::DropActions supportedDropActions() const override; + virtual QStringList mimeTypes() const override; + virtual QMimeData *mimeData(const QModelIndexList &indexes) const override; + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + +private: + + FilterListModel::FilterListType type_; + + QStringList storage; + + void reload(); +}; + +#endif // FILTER_LIST_MODEL_h diff --git a/ui/qt/models/info_proxy_model.cpp b/ui/qt/models/info_proxy_model.cpp new file mode 100644 index 00000000..7b256b9d --- /dev/null +++ b/ui/qt/models/info_proxy_model.cpp @@ -0,0 +1,119 @@ +/* info_proxy_model.cpp + * Proxy model for displaying an info text at the end of any QAbstractListModel + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <config.h> + +#include <ui/qt/models/info_proxy_model.h> + +#include <QFont> + +InfoProxyModel::InfoProxyModel(QObject * parent) + : QIdentityProxyModel(parent), + column_(-1) +{ +} + +InfoProxyModel::~InfoProxyModel() +{ + infos_.clear(); +} + +void InfoProxyModel::appendInfo(QString info) +{ + if (! infos_.contains(info)) + infos_ << info; +} + +void InfoProxyModel::clearInfos() +{ + infos_.clear(); +} + +int InfoProxyModel::rowCount(const QModelIndex &parent) const +{ + return static_cast<int>(sourceModel()->rowCount(parent) + infos_.count()); +} + +QVariant InfoProxyModel::data (const QModelIndex &index, int role) const +{ + if (! index.isValid()) + return QVariant(); + + if (index.row() < sourceModel()->rowCount()) + return sourceModel()->data(mapToSource(index), role); + + int ifIdx = index.row() - sourceModel()->rowCount(); + if (index.column() != column_ || ifIdx < 0 || ifIdx >= infos_.count()) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + return infos_.at(ifIdx); + break; + case Qt::FontRole: + QFont font = QIdentityProxyModel::data(index, Qt::FontRole).value<QFont>(); + font.setItalic(true); + return font; + } + + return QIdentityProxyModel::data(index, role); +} + +Qt::ItemFlags InfoProxyModel::flags(const QModelIndex &index) const +{ + if (index.row() < sourceModel()->rowCount()) + return sourceModel()->flags(mapToSource(index)); + + return Qt::ItemFlags(); +} + +QModelIndex InfoProxyModel::index(int row, int column, const QModelIndex &parent) const +{ + if (row >= sourceModel()->rowCount() && row < rowCount()) + return createIndex(row, column); + + return QIdentityProxyModel::index(row, column, parent); +} + +QModelIndex InfoProxyModel::mapToSource(const QModelIndex &proxyIndex) const +{ + if (! proxyIndex.isValid()) + return QModelIndex(); + + if (proxyIndex.row() >= sourceModel()->rowCount()) + return QModelIndex(); + + return QIdentityProxyModel::mapToSource(proxyIndex); +} + +QModelIndex InfoProxyModel::mapFromSource(const QModelIndex &fromIndex) const +{ + return QIdentityProxyModel::mapFromSource(fromIndex); +} + +void InfoProxyModel::setColumn(int column) +{ + int old_column = column_; + column_ = column; + + QVector<int> roles; + roles << Qt::DisplayRole; + + if (old_column >= 0) { + //Notify old column has changed + emit dataChanged(index(0, old_column), index(rowCount(), old_column), roles); + } + + if (column_ >= 0) { + //Notify new column has changed + emit dataChanged(index(0, column_), index(rowCount(), column_), roles); + } +} diff --git a/ui/qt/models/info_proxy_model.h b/ui/qt/models/info_proxy_model.h new file mode 100644 index 00000000..f9775b4d --- /dev/null +++ b/ui/qt/models/info_proxy_model.h @@ -0,0 +1,47 @@ +/** @file + * + * Proxy model for displaying an info text at the end of any QAbstractListModel + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef INFO_PROXY_MODEL_H +#define INFO_PROXY_MODEL_H + +#include <config.h> + +#include <QStringList> +#include <QIdentityProxyModel> + +class InfoProxyModel : public QIdentityProxyModel +{ +public: + explicit InfoProxyModel(QObject * parent = 0); + ~InfoProxyModel(); + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const; + + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + + virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const; + virtual QModelIndex mapFromSource(const QModelIndex &fromIndex) const; + + void appendInfo(QString info); + void clearInfos(); + + void setColumn(int column); + +private: + + int column_; + + QStringList infos_; +}; + +#endif // INFO_PROXY_MODEL_H diff --git a/ui/qt/models/interface_sort_filter_model.cpp b/ui/qt/models/interface_sort_filter_model.cpp new file mode 100644 index 00000000..9d9c5dc8 --- /dev/null +++ b/ui/qt/models/interface_sort_filter_model.cpp @@ -0,0 +1,396 @@ +/* interface_sort_filter_model.cpp + * Proxy model for the display of interface data for the interface tree + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/interface_tree_model.h> +#include <ui/qt/models/interface_tree_cache_model.h> +#include <ui/qt/models/interface_sort_filter_model.h> + +#include <glib.h> + +#include <epan/prefs.h> +#include <ui/preference_utils.h> +#include <ui/qt/utils/qt_ui_utils.h> + +#include "main_application.h" + +#include <QAbstractItemModel> + +InterfaceSortFilterModel::InterfaceSortFilterModel(QObject *parent) : + QSortFilterProxyModel(parent) +{ + resetAllFilter(); +} + +void InterfaceSortFilterModel::resetAllFilter() +{ + _filterHidden = true; + _filterTypes = true; + _invertTypeFilter = false; + _storeOnChange = false; + _sortByActivity = false; +#ifdef HAVE_PCAP_REMOTE + _remoteDisplay = true; +#endif + + /* Adding all columns, to have a default setting */ + for (int col = 0; col < IFTREE_COL_MAX; col++) + _columns.append((InterfaceTreeColumns)col); + + invalidateFilter(); + invalidate(); +} + +void InterfaceSortFilterModel::setStoreOnChange(bool storeOnChange) +{ + _storeOnChange = storeOnChange; + if (storeOnChange) + { + connect(mainApp, &MainApplication::preferencesChanged, this, &InterfaceSortFilterModel::resetPreferenceData); + resetPreferenceData(); + } +} + +void InterfaceSortFilterModel::setFilterHidden(bool filter) +{ + _filterHidden = filter; + + invalidate(); +} + + +void InterfaceSortFilterModel::setSortByActivity(bool sort) +{ + _sortByActivity = sort; + invalidate(); +} + +bool InterfaceSortFilterModel::sortByActivity() const +{ + return _sortByActivity; +} + +#ifdef HAVE_PCAP_REMOTE +void InterfaceSortFilterModel::setRemoteDisplay(bool remoteDisplay) +{ + _remoteDisplay = remoteDisplay; + + invalidate(); +} + +bool InterfaceSortFilterModel::remoteDisplay() +{ + return _remoteDisplay; +} + +void InterfaceSortFilterModel::toggleRemoteDisplay() +{ + _remoteDisplay = ! _remoteDisplay; + + if (_storeOnChange) + { + prefs.gui_interfaces_remote_display = ! _remoteDisplay; + + prefs_main_write(); + } + + invalidateFilter(); + invalidate(); +} + +bool InterfaceSortFilterModel::remoteInterfacesExist() +{ + bool exist = false; + + if (sourceModel()->rowCount() == 0) + return exist; + + for (int idx = 0; idx < sourceModel()->rowCount() && ! exist; idx++) + exist = ((InterfaceTreeModel *)sourceModel())->isRemote(idx); + + return exist; +} +#endif + +void InterfaceSortFilterModel::setFilterByType(bool filter, bool invert) +{ + _filterTypes = filter; + _invertTypeFilter = invert; + + invalidate(); +} + +void InterfaceSortFilterModel::resetPreferenceData() +{ + displayHiddenTypes.clear(); + QString stored_prefs(prefs.gui_interfaces_hide_types); + if (stored_prefs.length() > 0) + { + QStringList ifTypesStored = stored_prefs.split(','); + QStringList::const_iterator it = ifTypesStored.constBegin(); + while (it != ifTypesStored.constEnd()) + { + int i_val = (*it).toInt(); + if (! displayHiddenTypes.contains(i_val)) + displayHiddenTypes.append(i_val); + ++it; + } + } + +#if 0 + // Disabled until bug 13354 is fixed + _filterHidden = ! prefs.gui_interfaces_show_hidden; +#endif +#ifdef HAVE_PCAP_REMOTE + _remoteDisplay = prefs.gui_interfaces_remote_display; +#endif + + invalidate(); +} + +bool InterfaceSortFilterModel::filterHidden() const +{ + return _filterHidden; +} + +void InterfaceSortFilterModel::toggleFilterHidden() +{ + _filterHidden = ! _filterHidden; + + if (_storeOnChange) + { + prefs.gui_interfaces_show_hidden = ! _filterHidden; + + prefs_main_write(); + } + + invalidateFilter(); + invalidate(); +} + +bool InterfaceSortFilterModel::filterByType() const +{ + return _filterTypes; +} + +int InterfaceSortFilterModel::interfacesHidden() +{ +#ifdef HAVE_LIBPCAP + if (! global_capture_opts.all_ifaces) + return 0; +#endif + + return sourceModel()->rowCount() - rowCount(); +} + +QList<int> InterfaceSortFilterModel::typesDisplayed() +{ + QList<int> shownTypes; + + if (sourceModel()->rowCount() == 0) + return shownTypes; + + for (int idx = 0; idx < sourceModel()->rowCount(); idx++) + { + int type = ((InterfaceTreeModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_TYPE).toInt(); + bool hidden = ((InterfaceTreeModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_HIDDEN).toBool(); + + if (! hidden) + { + if (! shownTypes.contains(type)) + shownTypes.append(type); + } + } + + return shownTypes; +} + +void InterfaceSortFilterModel::setInterfaceTypeVisible(int ifType, bool visible) +{ + if (visible && displayHiddenTypes.contains(ifType)) + displayHiddenTypes.removeAll(ifType); + else if (! visible && ! displayHiddenTypes.contains(ifType)) + displayHiddenTypes.append(ifType); + else + /* Nothing should have changed */ + return; + + if (_storeOnChange) + { + QString new_pref; + QList<int>::const_iterator it = displayHiddenTypes.constBegin(); + while (it != displayHiddenTypes.constEnd()) + { + new_pref.append(QString("%1,").arg(*it)); + ++it; + } + if (new_pref.length() > 0) + new_pref = new_pref.left(new_pref.length() - 1); + + prefs.gui_interfaces_hide_types = qstring_strdup(new_pref); + + prefs_main_write(); + } + + invalidateFilter(); + invalidate(); +} + +void InterfaceSortFilterModel::toggleTypeVisibility(int ifType) +{ + bool checked = isInterfaceTypeShown(ifType); + + setInterfaceTypeVisible(ifType, checked ? false : true); +} + +bool InterfaceSortFilterModel::isInterfaceTypeShown(int ifType) const +{ + bool result = false; + + if (displayHiddenTypes.size() == 0) + result = true; + else if (! displayHiddenTypes.contains(ifType)) + result = true; + + return ((_invertTypeFilter && ! result) || (! _invertTypeFilter && result) ); +} + +bool InterfaceSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const +{ + QModelIndex realIndex = sourceModel()->index(sourceRow, 0, sourceParent); + + if (! realIndex.isValid()) + return false; + +#ifdef HAVE_LIBPCAP + int idx = realIndex.row(); + + /* No data loaded, we do not display anything */ + if (sourceModel()->rowCount() == 0) + return false; + + int type = -1; + bool hidden = false; + + if (dynamic_cast<InterfaceTreeCacheModel*>(sourceModel()) != 0) + { + type = ((InterfaceTreeCacheModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_TYPE).toInt(); + hidden = ((InterfaceTreeCacheModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_HIDDEN, Qt::UserRole).toBool(); + } + else if (dynamic_cast<InterfaceTreeModel*>(sourceModel()) != 0) + { + type = ((InterfaceTreeModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_TYPE).toInt(); + hidden = ((InterfaceTreeModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_HIDDEN, Qt::UserRole).toBool(); + } + else + return false; + + if (hidden && _filterHidden) + return false; + + if (_filterTypes && ! isInterfaceTypeShown(type)) + { +#ifdef HAVE_PCAP_REMOTE + /* Remote interfaces have the if type IF_WIRED, therefore would be filtered, if not explicitly checked here */ + if (type != IF_WIRED || ! ((InterfaceTreeModel *)sourceModel())->isRemote(idx)) +#endif + return false; + } + +#ifdef HAVE_PCAP_REMOTE + if (((InterfaceTreeModel *)sourceModel())->isRemote(idx)) + { + if (! _remoteDisplay) + return false; + } +#endif + +#endif + + return true; +} + +bool InterfaceSortFilterModel::filterAcceptsColumn(int sourceColumn, const QModelIndex & sourceParent) const +{ + QModelIndex realIndex = sourceModel()->index(0, sourceColumn, sourceParent); + + if (! realIndex.isValid()) + return false; + + if (! _columns.contains((InterfaceTreeColumns)sourceColumn)) + return false; + + return true; +} + +void InterfaceSortFilterModel::setColumns(QList<InterfaceTreeColumns> columns) +{ + _columns.clear(); + _columns.append(columns); +} + +int InterfaceSortFilterModel::mapSourceToColumn(InterfaceTreeColumns mdlIndex) +{ + if (! _columns.contains(mdlIndex)) + return -1; + + return static_cast<int>(_columns.indexOf(mdlIndex, 0)); +} + +QModelIndex InterfaceSortFilterModel::mapToSource(const QModelIndex &proxyIndex) const +{ + if (! proxyIndex.isValid()) + return QModelIndex(); + + if (! sourceModel()) + return QModelIndex(); + + QModelIndex baseIndex = QSortFilterProxyModel::mapToSource(proxyIndex); + QModelIndex newIndex = sourceModel()->index(baseIndex.row(), _columns.at(proxyIndex.column())); + + return newIndex; +} + +QModelIndex InterfaceSortFilterModel::mapFromSource(const QModelIndex &sourceIndex) const +{ + if (! sourceIndex.isValid()) + return QModelIndex(); + else if (! _columns.contains((InterfaceTreeColumns) sourceIndex.column()) ) + return QModelIndex(); + + QModelIndex newIndex = QSortFilterProxyModel::mapFromSource(sourceIndex); + + return index(newIndex.row(), static_cast<int>(_columns.indexOf((InterfaceTreeColumns) sourceIndex.column()))); +} + +QString InterfaceSortFilterModel::interfaceError() +{ + QString result; + + InterfaceTreeModel * sourceModel = dynamic_cast<InterfaceTreeModel *>(this->sourceModel()); + if (sourceModel != NULL) + result = sourceModel->interfaceError(); + + if (result.size() == 0 && rowCount() == 0) + result = tr("No interfaces to be displayed. %1 interfaces hidden.").arg(interfacesHidden()); + + return result; +} + +bool InterfaceSortFilterModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + bool leftActive = source_left.sibling(source_left.row(), InterfaceTreeColumns::IFTREE_COL_ACTIVE).data(Qt::UserRole).toBool(); + bool rightActive = source_right.sibling(source_right.row(), InterfaceTreeColumns::IFTREE_COL_ACTIVE).data(Qt::UserRole).toBool(); + + if (_sortByActivity && rightActive && ! leftActive) + return true; + + return QSortFilterProxyModel::lessThan(source_left, source_right); +} + diff --git a/ui/qt/models/interface_sort_filter_model.h b/ui/qt/models/interface_sort_filter_model.h new file mode 100644 index 00000000..5bf69d9d --- /dev/null +++ b/ui/qt/models/interface_sort_filter_model.h @@ -0,0 +1,87 @@ +/** @file + * + * Proxy model for the display of interface data for the interface tree + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef INTERFACE_SORT_FILTER_MODEL_H +#define INTERFACE_SORT_FILTER_MODEL_H + +#include <config.h> + +#include <ui/qt/models/interface_tree_model.h> + +#include <glib.h> + +#include <QSortFilterProxyModel> + +class InterfaceSortFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + InterfaceSortFilterModel(QObject *parent); + + void setStoreOnChange(bool storeOnChange); + void resetAllFilter(); + + void setFilterHidden(bool filter); + bool filterHidden() const; + int interfacesHidden(); + void toggleFilterHidden(); + + void setSortByActivity(bool sort); + bool sortByActivity() const; + +#ifdef HAVE_PCAP_REMOTE + void setRemoteDisplay(bool remoteDisplay); + bool remoteDisplay(); + void toggleRemoteDisplay(); + bool remoteInterfacesExist(); +#endif + + void setInterfaceTypeVisible(int ifType, bool visible); + bool isInterfaceTypeShown(int ifType) const; + void setFilterByType(bool filter, bool invert = false); + bool filterByType() const; + void toggleTypeVisibility(int ifType); + + QList<int> typesDisplayed(); + + void setColumns(QList<InterfaceTreeColumns> columns); + int mapSourceToColumn(InterfaceTreeColumns mdlIndex); + + QModelIndex mapToSource(const QModelIndex &proxyIndex) const; + QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; + + QString interfaceError(); + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; + bool filterAcceptsColumn(int source_column, const QModelIndex & source_parent) const; + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; + +private: + bool _filterHidden; + bool _filterTypes; + bool _invertTypeFilter; + bool _storeOnChange; + bool _sortByActivity; + +#ifdef HAVE_PCAP_REMOTE + bool _remoteDisplay; +#endif + + QList<int> displayHiddenTypes; + + QList<InterfaceTreeColumns> _columns; + +private slots: + void resetPreferenceData(); +}; + +#endif // INTERFACE_SORT_FILTER_MODEL_H diff --git a/ui/qt/models/interface_tree_cache_model.cpp b/ui/qt/models/interface_tree_cache_model.cpp new file mode 100644 index 00000000..71eda509 --- /dev/null +++ b/ui/qt/models/interface_tree_cache_model.cpp @@ -0,0 +1,584 @@ +/* interface_tree_cache_model.cpp + * Model caching interface changes before sending them to global storage + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + +#include <ui/qt/models/interface_tree_cache_model.h> + +#include "epan/prefs.h" + +#include <ui/qt/utils/qt_ui_utils.h> +#include "ui/capture_globals.h" +#include "wsutil/utf8_entities.h" + +#include "wiretap/wtap.h" + +#include "main_application.h" + +#include <QIdentityProxyModel> + +InterfaceTreeCacheModel::InterfaceTreeCacheModel(QObject *parent) : + QIdentityProxyModel(parent) +{ + /* ATTENTION: This cache model is not intended to be used with anything + * else then InterfaceTreeModel, and will break with anything else + * leading to unintended results. */ + sourceModel = new InterfaceTreeModel(parent); + + QIdentityProxyModel::setSourceModel(sourceModel); + storage = new QMap<int, QMap<InterfaceTreeColumns, QVariant> *>(); + + checkableColumns << IFTREE_COL_HIDDEN << IFTREE_COL_PROMISCUOUSMODE; +#ifdef HAVE_PCAP_CREATE + checkableColumns << IFTREE_COL_MONITOR_MODE; +#endif + + editableColumns << IFTREE_COL_COMMENT << IFTREE_COL_SNAPLEN << IFTREE_COL_PIPE_PATH; + +#ifdef CAN_SET_CAPTURE_BUFFER_SIZE + editableColumns << IFTREE_COL_BUFFERLEN; +#endif +} + +InterfaceTreeCacheModel::~InterfaceTreeCacheModel() +{ +#ifdef HAVE_LIBPCAP + /* This list should only exist, if the dialog is closed, without calling save first */ + newDevices.clear(); +#endif + + delete storage; + delete sourceModel; +} + +QVariant InterfaceTreeCacheModel::getColumnContent(int idx, int col, int role) +{ + return InterfaceTreeCacheModel::data(index(idx, col), role); +} + +#ifdef HAVE_LIBPCAP +void InterfaceTreeCacheModel::reset(int row) +{ + if (row < 0) + { + delete storage; + storage = new QMap<int, QMap<InterfaceTreeColumns, QVariant> *>(); + } + else + { + if (storage->count() > row) + storage->remove(storage->keys().at(row)); + } +} + +void InterfaceTreeCacheModel::saveNewDevices() +{ + QList<interface_t>::const_iterator it = newDevices.constBegin(); + /* idx is used for iterating only over the indices of the new devices. As all new + * devices are stored with an index higher then sourceModel->rowCount(), we start + * only with those storage indices. + * it is just the iterator over the new devices. A new device must not necessarily + * have storage, which will lead to that device not being stored in global_capture_opts */ + for (int idx = sourceModel->rowCount(); it != newDevices.constEnd(); ++it, idx++) + { + interface_t *device = const_cast<interface_t *>(&(*it)); + bool useDevice = false; + + QMap<InterfaceTreeColumns, QVariant> * dataField = storage->value(idx, 0); + /* When devices are being added, they are added using generic values. So only devices + * whose data have been changed should be used from here on out. */ + if (dataField != 0) + { + if (device->if_info.type != IF_PIPE) + { + continue; + } + + if (device->if_info.type == IF_PIPE) + { + QVariant saveValue = dataField->value(IFTREE_COL_PIPE_PATH); + if (saveValue.isValid()) + { + g_free(device->if_info.name); + device->if_info.name = qstring_strdup(saveValue.toString()); + + g_free(device->name); + device->name = qstring_strdup(saveValue.toString()); + + g_free(device->display_name); + device->display_name = qstring_strdup(saveValue.toString()); + useDevice = true; + } + } + + if (useDevice) + g_array_append_val(global_capture_opts.all_ifaces, *device); + + } + + /* All entries of this new devices have been considered */ + storage->remove(idx); + delete dataField; + } + + newDevices.clear(); +} + +void InterfaceTreeCacheModel::save() +{ + if (storage->count() == 0) + return; + + QMap<char**, QStringList> prefStorage; + + /* No devices are hidden until checking "Show" state */ + prefStorage[&prefs.capture_devices_hide] = QStringList(); + + /* Storing new devices first including their changed values */ + saveNewDevices(); + + + for (unsigned int idx = 0; idx < global_capture_opts.all_ifaces->len; idx++) + { + interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx); + + if (! device->name) + continue; + + /* Try to load a saved value row for this index */ + QMap<InterfaceTreeColumns, QVariant> * dataField = storage->value(idx, 0); + + /* Handle the storing of values for this device here */ + if (dataField) + { + QMap<InterfaceTreeColumns, QVariant>::const_iterator it = dataField->constBegin(); + while (it != dataField->constEnd()) + { + InterfaceTreeColumns col = it.key(); + QVariant saveValue = it.value(); + + /* Setting the field values for each individual saved value cannot be generic, as the + * struct cannot be accessed in a generic way. Therefore below, each individually changed + * value has to be handled separately. Comments are stored only in the preference file + * and applied to the data name during loading. Therefore comments are not handled here */ + + if (col == IFTREE_COL_HIDDEN) + { + device->hidden = saveValue.toBool(); + } + else if (device->if_info.type == IF_EXTCAP) + { + /* extcap interfaces do not have the following columns. + * ATTENTION: all generic columns must be added, BEFORE this + * if-clause, or they will be ignored for extcap interfaces */ + } + else if (col == IFTREE_COL_PROMISCUOUSMODE) + { + device->pmode = saveValue.toBool(); + } +#ifdef HAVE_PCAP_CREATE + else if (col == IFTREE_COL_MONITOR_MODE) + { + device->monitor_mode_enabled = saveValue.toBool(); + } +#endif + else if (col == IFTREE_COL_SNAPLEN) + { + int iVal = saveValue.toInt(); + if (iVal != WTAP_MAX_PACKET_SIZE_STANDARD) + { + device->has_snaplen = true; + device->snaplen = iVal; + } + else + { + device->has_snaplen = false; + device->snaplen = WTAP_MAX_PACKET_SIZE_STANDARD; + } + } +#ifdef CAN_SET_CAPTURE_BUFFER_SIZE + else if (col == IFTREE_COL_BUFFERLEN) + { + device->buffer = saveValue.toInt(); + } +#endif + ++it; + } + } + + QVariant content = getColumnContent(idx, IFTREE_COL_HIDDEN, Qt::CheckStateRole); + if (content.isValid() && static_cast<Qt::CheckState>(content.toInt()) == Qt::Unchecked) + prefStorage[&prefs.capture_devices_hide] << QString(device->name); + + content = getColumnContent(idx, IFTREE_COL_COMMENT); + if (content.isValid() && content.toString().size() > 0) + prefStorage[&prefs.capture_devices_descr] << QString("%1(%2)").arg(device->name).arg(content.toString()); + + bool allowExtendedColumns = true; + + if (device->if_info.type == IF_EXTCAP) + allowExtendedColumns = false; + + if (allowExtendedColumns) + { + content = getColumnContent(idx, IFTREE_COL_PROMISCUOUSMODE, Qt::CheckStateRole); + if (content.isValid()) + { + bool value = static_cast<Qt::CheckState>(content.toInt()) == Qt::Checked; + prefStorage[&prefs.capture_devices_pmode] << QString("%1(%2)").arg(device->name).arg(value ? 1 : 0); + } + +#ifdef HAVE_PCAP_CREATE + content = getColumnContent(idx, IFTREE_COL_MONITOR_MODE, Qt::CheckStateRole); + if (content.isValid() && static_cast<Qt::CheckState>(content.toInt()) == Qt::Checked) + prefStorage[&prefs.capture_devices_monitor_mode] << QString(device->name); +#endif + + content = getColumnContent(idx, IFTREE_COL_SNAPLEN); + if (content.isValid()) + { + int value = content.toInt(); + prefStorage[&prefs.capture_devices_snaplen] << + QString("%1:%2(%3)").arg(device->name). + arg(device->has_snaplen ? 1 : 0). + arg(device->has_snaplen ? value : WTAP_MAX_PACKET_SIZE_STANDARD); + } + +#ifdef CAN_SET_CAPTURE_BUFFER_SIZE + content = getColumnContent(idx, IFTREE_COL_BUFFERLEN); + if (content.isValid()) + { + int value = content.toInt(); + if (value != -1) + { + prefStorage[&prefs.capture_devices_buffersize] << + QString("%1(%2)").arg(device->name). + arg(value); + } + } +#endif + } + } + + QMap<char**, QStringList>::const_iterator it = prefStorage.constBegin(); + while (it != prefStorage.constEnd()) + { + char ** key = it.key(); + + g_free(*key); + *key = qstring_strdup(it.value().join(",")); + + ++it; + } + + mainApp->emitAppSignal(MainApplication::LocalInterfacesChanged); +} +#endif + +int InterfaceTreeCacheModel::rowCount(const QModelIndex & parent) const +{ + int totalCount = sourceModel->rowCount(parent); +#ifdef HAVE_LIBPCAP + totalCount += newDevices.size(); +#endif + return totalCount; +} + +bool InterfaceTreeCacheModel::changeIsAllowed(InterfaceTreeColumns col) const +{ + if (editableColumns.contains(col) || checkableColumns.contains(col)) + return true; + return false; +} + +#ifdef HAVE_LIBPCAP +const interface_t * InterfaceTreeCacheModel::lookup(const QModelIndex &index) const +{ + const interface_t * result = 0; + + if (! index.isValid() || ! global_capture_opts.all_ifaces) + return result; + + int idx = index.row(); + + if ((unsigned int) idx >= global_capture_opts.all_ifaces->len) + { + idx = idx - global_capture_opts.all_ifaces->len; + if (idx < newDevices.size()) + result = &newDevices[idx]; + } + else + { + result = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx); + } + + return result; +} +#endif + +/* This checks if the column can be edited for the given index. This differs from + * isAvailableField in such a way, that it is only used in flags and not any + * other method.*/ +bool InterfaceTreeCacheModel::isAllowedToBeEdited(const QModelIndex &index) const +{ +#ifndef HAVE_LIBPCAP + Q_UNUSED(index); +#else + const interface_t * device = lookup(index); + if (device == 0) + return false; + + InterfaceTreeColumns col = (InterfaceTreeColumns) index.column(); + if (device->if_info.type == IF_EXTCAP) + { + /* extcap interfaces do not have those settings */ + if (col == IFTREE_COL_PROMISCUOUSMODE || col == IFTREE_COL_SNAPLEN) + return false; +#ifdef CAN_SET_CAPTURE_BUFFER_SIZE + if (col == IFTREE_COL_BUFFERLEN) + return false; +#endif + } +#endif + return true; +} + +// Whether this field is available for modification and display. +bool InterfaceTreeCacheModel::isAvailableField(const QModelIndex &index) const +{ +#ifndef HAVE_LIBPCAP + Q_UNUSED(index); +#else + const interface_t * device = lookup(index); + + if (device == 0) + return false; + + InterfaceTreeColumns col = (InterfaceTreeColumns) index.column(); + if (col == IFTREE_COL_HIDDEN) + { + // Do not allow default capture interface to be hidden. + if (! g_strcmp0(prefs.capture_device, device->display_name)) + return false; + } +#endif + + return true; +} + +Qt::ItemFlags InterfaceTreeCacheModel::flags(const QModelIndex &index) const +{ + if (! index.isValid()) + return Qt::ItemFlags(); + + Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + + InterfaceTreeColumns col = (InterfaceTreeColumns) index.column(); + + if (changeIsAllowed(col) && isAvailableField(index) && isAllowedToBeEdited(index)) + { + if (checkableColumns.contains(col)) + { + flags |= Qt::ItemIsUserCheckable; + } + else + { + flags |= Qt::ItemIsEditable; + } + } + + return flags; +} + +bool InterfaceTreeCacheModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (! index.isValid()) + return false; + + if (! isAvailableField(index)) + return false; + + int row = index.row(); + InterfaceTreeColumns col = (InterfaceTreeColumns)index.column(); + + if (role == Qt::CheckStateRole || role == Qt::EditRole) + { + if (changeIsAllowed(col) ) + { + QVariant saveValue = value; + + QMap<InterfaceTreeColumns, QVariant> * dataField = 0; + /* obtain the list of already stored changes for this row. If none exist + * create a new storage row for this entry */ + if ((dataField = storage->value(row, 0)) == 0) + { + dataField = new QMap<InterfaceTreeColumns, QVariant>(); + storage->insert(row, dataField); + } + + dataField->insert(col, saveValue); + + return true; + } + } + + return false; +} + +QVariant InterfaceTreeCacheModel::data(const QModelIndex &index, int role) const +{ + if (! index.isValid()) + return QVariant(); + + int row = index.row(); + + InterfaceTreeColumns col = (InterfaceTreeColumns)index.column(); + + if (isAvailableField(index) && isAllowedToBeEdited(index)) + { + if (((role == Qt::DisplayRole || role == Qt::EditRole) && editableColumns.contains(col)) || + (role == Qt::CheckStateRole && checkableColumns.contains(col)) ) + { + QMap<InterfaceTreeColumns, QVariant> * dataField = 0; + if ((dataField = storage->value(row, 0)) != 0) + { + if (dataField->contains(col)) + { + return dataField->value(col, QVariant()); + } + } + } + } + else + { + if (role == Qt::CheckStateRole) + return QVariant(); + else if (role == Qt::DisplayRole) + return QString(UTF8_EM_DASH); + } + + if (row < sourceModel->rowCount()) + { + return sourceModel->data(index, role); + } +#ifdef HAVE_LIBPCAP + else + { + /* Handle all fields, which will have to be displayed for new devices. Only pipes + * are supported at the moment, so the information to be displayed is pretty limited. + * After saving, the devices are stored in global_capture_opts and no longer + * classify as new devices. */ + const interface_t * device = lookup(index); + + if (device != 0) + { + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + if (col == IFTREE_COL_PIPE_PATH || + col == IFTREE_COL_NAME || + col == IFTREE_COL_DESCRIPTION) + { + + QMap<InterfaceTreeColumns, QVariant> * dataField = 0; + if ((dataField = storage->value(row, 0)) != 0 && + dataField->contains(IFTREE_COL_PIPE_PATH)) + { + return dataField->value(IFTREE_COL_PIPE_PATH, QVariant()); + } + else + return QString(device->name); + } + else if (col == IFTREE_COL_TYPE) + { + return QVariant::fromValue((int)device->if_info.type); + } + } + else if (role == Qt::CheckStateRole) + { + if (col == IFTREE_COL_HIDDEN) + { + // Do not allow default capture interface to be hidden. + if (! g_strcmp0(prefs.capture_device, device->display_name)) + return QVariant(); + + /* Hidden is a de-selection, therefore inverted logic here */ + return device->hidden ? Qt::Unchecked : Qt::Checked; + } + } + } + } +#endif + + return QVariant(); +} + +#ifdef HAVE_LIBPCAP +QModelIndex InterfaceTreeCacheModel::index(int row, int column, const QModelIndex &parent) const +{ + if (row >= sourceModel->rowCount() && (row - sourceModel->rowCount()) < newDevices.count()) + { + return createIndex(row, column, (void *)0); + } + + return QIdentityProxyModel::index(row, column, parent); +} + +void InterfaceTreeCacheModel::addDevice(const interface_t * newDevice) +{ + emit beginInsertRows(QModelIndex(), rowCount(), rowCount()); + newDevices << *newDevice; + emit endInsertRows(); +} + +void InterfaceTreeCacheModel::deleteDevice(const QModelIndex &index) +{ + if (! index.isValid()) + return; + + emit beginRemoveRows(QModelIndex(), index.row(), index.row()); + + int row = index.row(); + + /* device is in newDevices */ + if (row >= sourceModel->rowCount()) + { + int newDeviceIdx = row - sourceModel->rowCount(); + + newDevices.removeAt(newDeviceIdx); + if (storage->contains(index.row())) + storage->remove(index.row()); + + /* The storage array has to be resorted, if the index, that was removed + * had been in the middle of the array. Can't start at index.row(), as + * it may not be contained in storage + * We must iterate using a list, not an iterator, otherwise the change + * will fold on itself. */ + QList<int> storageKeys = storage->keys(); + for (int i = 0; i < storageKeys.size(); ++i) + { + int key = storageKeys.at(i); + if (key > index.row()) + { + storage->insert(key - 1, storage->value(key)); + storage->remove(key); + } + } + + emit endRemoveRows(); + } + else + { + interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, row); + capture_opts_free_interface_t(device); + global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, row); + emit endRemoveRows(); + mainApp->emitAppSignal(MainApplication::LocalInterfacesChanged); + } +} +#endif diff --git a/ui/qt/models/interface_tree_cache_model.h b/ui/qt/models/interface_tree_cache_model.h new file mode 100644 index 00000000..9b51c10b --- /dev/null +++ b/ui/qt/models/interface_tree_cache_model.h @@ -0,0 +1,66 @@ +/** @file + * + * Model caching interface changes before sending them to global storage + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef INTERFACE_TREE_CACHE_MODEL_H_ +#define INTERFACE_TREE_CACHE_MODEL_H_ + +#include <ui/qt/models/interface_tree_model.h> + +#include <QMap> +#include <QAbstractItemModel> +#include <QIdentityProxyModel> + +class InterfaceTreeCacheModel : public QIdentityProxyModel +{ +public: + explicit InterfaceTreeCacheModel(QObject *parent); + ~InterfaceTreeCacheModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + Qt::ItemFlags flags(const QModelIndex &index) const; + + QVariant getColumnContent(int idx, int col, int role = Qt::DisplayRole); + +#ifdef HAVE_LIBPCAP + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + + void reset(int row); + void save(); + + void addDevice(const interface_t * newDevice); + void deleteDevice(const QModelIndex &index); +#endif + +private: + InterfaceTreeModel * sourceModel; + +#ifdef HAVE_LIBPCAP + QList<interface_t> newDevices; + + void saveNewDevices(); +#endif + QMap<int, QMap<InterfaceTreeColumns, QVariant> *> * storage; + QList<InterfaceTreeColumns> editableColumns; + QList<InterfaceTreeColumns> checkableColumns; + +#ifdef HAVE_LIBPCAP + const interface_t * lookup(const QModelIndex &index) const; +#endif + + bool changeIsAllowed(InterfaceTreeColumns col) const; + bool isAvailableField(const QModelIndex &index) const; + bool isAllowedToBeEdited(const QModelIndex &index) const; + +}; +#endif /* INTERFACE_TREE_CACHE_MODEL_H_ */ diff --git a/ui/qt/models/interface_tree_model.cpp b/ui/qt/models/interface_tree_model.cpp new file mode 100644 index 00000000..547b9010 --- /dev/null +++ b/ui/qt/models/interface_tree_model.cpp @@ -0,0 +1,545 @@ +/* interface_tree_model.cpp + * Model for the interface data for display in the interface frame + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <ui/qt/models/interface_tree_model.h> + +#ifdef HAVE_LIBPCAP +#include "ui/capture.h" +#include "capture/capture-pcap-util.h" +#include "capture_opts.h" +#include "ui/capture_ui_utils.h" +#include "ui/capture_globals.h" +#endif + +#include "wsutil/filesystem.h" + +#include <ui/qt/utils/qt_ui_utils.h> +#include <ui/qt/utils/stock_icon.h> +#include "main_application.h" + +/* Needed for the meta type declaration of QList<int>* */ +#include <ui/qt/models/sparkline_delegate.h> + +#include "extcap.h" + +const QString InterfaceTreeModel::DefaultNumericValue = QObject::tr("default"); + +/** + * This is the data model for interface trees. It implies, that the index within + * global_capture_opts.all_ifaces is identical to the row. This is always the case, even + * when interfaces are hidden by the proxy model. But for this to work, every access + * to the index from within the view, has to be filtered through the proxy model. + */ +InterfaceTreeModel::InterfaceTreeModel(QObject *parent) : + QAbstractTableModel(parent) +#ifdef HAVE_LIBPCAP + ,stat_cache_(NULL) +#endif +{ + connect(mainApp, &MainApplication::appInitialized, this, &InterfaceTreeModel::interfaceListChanged); + connect(mainApp, &MainApplication::localInterfaceListChanged, this, &InterfaceTreeModel::interfaceListChanged); +} + +InterfaceTreeModel::~InterfaceTreeModel(void) +{ +#ifdef HAVE_LIBPCAP + if (stat_cache_) { + capture_stat_stop(stat_cache_); + stat_cache_ = NULL; + } +#endif // HAVE_LIBPCAP +} + +QString InterfaceTreeModel::interfaceError() +{ +#ifdef HAVE_LIBPCAP + // + // First, see if there was an error fetching the interfaces. + // If so, report it. + // + if (global_capture_opts.ifaces_err != 0) + { + return tr(global_capture_opts.ifaces_err_info); + } + + // + // Otherwise, if there are no rows, there were no interfaces + // found. + // + if (rowCount() == 0) + { + return tr("No interfaces found."); + } + + // + // No error. Return an empty string. + // + return ""; +#else + // + // We were built without pcap support, so we have no notion of + // local interfaces. + // + return tr("This version of Wireshark was built without packet capture support."); +#endif +} + +int InterfaceTreeModel::rowCount(const QModelIndex &) const +{ +#ifdef HAVE_LIBPCAP + return (global_capture_opts.all_ifaces ? global_capture_opts.all_ifaces->len : 0); +#else + /* Currently no interfaces available for libpcap-less builds */ + return 0; +#endif +} + +int InterfaceTreeModel::columnCount(const QModelIndex &) const +{ + /* IFTREE_COL_MAX is not being displayed, it is the definition for the maximum numbers of columns */ + return ((int) IFTREE_COL_MAX); +} + +QVariant InterfaceTreeModel::data(const QModelIndex &index, int role) const +{ +#ifdef HAVE_LIBPCAP + bool interfacesLoaded = true; + if (! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len == 0) + interfacesLoaded = false; + + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + InterfaceTreeColumns col = (InterfaceTreeColumns) index.column(); + + if (interfacesLoaded) + { + interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, row); + + /* Data for display in cell */ + if (role == Qt::DisplayRole) + { + /* Only the name is being displayed */ + if (col == IFTREE_COL_NAME) + { + return QString(device->name); + } + else if (col == IFTREE_COL_DESCRIPTION) + { + return QString(device->friendly_name); + } + else if (col == IFTREE_COL_DISPLAY_NAME) + { + return QString(device->display_name); + } + else if (col == IFTREE_COL_PIPE_PATH) + { + return QString(device->if_info.name); + } + else if (col == IFTREE_COL_CAPTURE_FILTER) + { + if (device->cfilter && strlen(device->cfilter) > 0) + return html_escape(QString(device->cfilter)); + } + else if (col == IFTREE_COL_EXTCAP_PATH) + { + return QString(device->if_info.extcap); + } + else if (col == IFTREE_COL_SNAPLEN) + { + return device->has_snaplen ? QString::number(device->snaplen) : DefaultNumericValue; + } +#ifdef CAN_SET_CAPTURE_BUFFER_SIZE + else if (col == IFTREE_COL_BUFFERLEN) + { + return QString::number(device->buffer); + } +#endif + else if (col == IFTREE_COL_TYPE) + { + return QVariant::fromValue((int)device->if_info.type); + } + else if (col == IFTREE_COL_COMMENT) + { + QString comment = gchar_free_to_qstring(capture_dev_user_descr_find(device->name)); + if (comment.length() > 0) + return comment; + else + return QString(device->if_info.vendor_description); + } + else if (col == IFTREE_COL_DLT) + { + // XXX - this is duplicated in + // InterfaceTreeWidgetItem::updateInterfaceColumns; + // it should be done in common code somewhere. + QString linkname; + if (device->active_dlt == -1) + linkname = "Unknown"; + else { + linkname = QObject::tr("DLT %1").arg(device->active_dlt); + for (GList *list = device->links; list != Q_NULLPTR; list = gxx_list_next(list)) { + link_row *linkr = gxx_list_data(link_row *, list); + if (linkr->dlt == device->active_dlt) { + linkname = linkr->name; + break; + } + } + } + + return linkname; + } + else + { + /* Return empty string for every other DisplayRole */ + return QVariant(); + } + } + else if (role == Qt::CheckStateRole) + { + if (col == IFTREE_COL_HIDDEN) + { + /* Hidden is a de-selection, therefore inverted logic here */ + return device->hidden ? Qt::Unchecked : Qt::Checked; + } + else if (col == IFTREE_COL_PROMISCUOUSMODE) + { + return device->pmode ? Qt::Checked : Qt::Unchecked; + } +#ifdef HAVE_PCAP_CREATE + else if (col == IFTREE_COL_MONITOR_MODE) + { + return device->monitor_mode_enabled ? Qt::Checked : Qt::Unchecked; + } +#endif + } + /* Used by SparkLineDelegate for loading the data for the statistics line */ + else if (role == Qt::UserRole) + { + if (col == IFTREE_COL_STATS) + { + if ((active.contains(device->name) && active[device->name]) && points.contains(device->name)) + return QVariant::fromValue(points[device->name]); + } + else if (col == IFTREE_COL_ACTIVE) + { + if (active.contains(device->name)) + return QVariant::fromValue(active[device->name]); + } + else if (col == IFTREE_COL_HIDDEN) + { + return QVariant::fromValue((bool)device->hidden); + } + } + /* Displays the configuration icon for extcap interfaces */ + else if (role == Qt::DecorationRole) + { + if (col == IFTREE_COL_EXTCAP) + { + if (device->if_info.type == IF_EXTCAP) + return QIcon(StockIcon("x-capture-options")); + } + } + else if (role == Qt::TextAlignmentRole) + { + if (col == IFTREE_COL_EXTCAP) + { + return Qt::AlignRight; + } + } + /* Displays the tooltip for each row */ + else if (role == Qt::ToolTipRole) + { + return toolTipForInterface(row); + } + } +#else + Q_UNUSED(index) + Q_UNUSED(role) +#endif + + return QVariant(); +} + +QVariant InterfaceTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) + { + if (role == Qt::DisplayRole) + { + if (section == IFTREE_COL_HIDDEN) + { + return tr("Show"); + } + else if (section == IFTREE_COL_NAME) + { + return tr("Interface Name"); + } + else if (section == IFTREE_COL_DESCRIPTION) + { + return tr("Friendly Name"); + } + else if (section == IFTREE_COL_DISPLAY_NAME) + { + return tr("Friendly Name"); + } + else if (section == IFTREE_COL_PIPE_PATH) + { + return tr("Local Pipe Path"); + } + else if (section == IFTREE_COL_COMMENT) + { + return tr("Comment"); + } + else if (section == IFTREE_COL_DLT) + { + return tr("Link-Layer Header"); + } + else if (section == IFTREE_COL_PROMISCUOUSMODE) + { + return tr("Promiscuous"); + } + else if (section == IFTREE_COL_SNAPLEN) + { + return tr("Snaplen (B)"); + } +#ifdef CAN_SET_CAPTURE_BUFFER_SIZE + else if (section == IFTREE_COL_BUFFERLEN) + { + return tr("Buffer (MB)"); + } +#endif +#ifdef HAVE_PCAP_CREATE + else if (section == IFTREE_COL_MONITOR_MODE) + { + return tr("Monitor Mode"); + } +#endif + else if (section == IFTREE_COL_CAPTURE_FILTER) + { + return tr("Capture Filter"); + } + } + } + + return QVariant(); +} + +QVariant InterfaceTreeModel::getColumnContent(int idx, int col, int role) +{ + return InterfaceTreeModel::data(index(idx, col), role); +} + +#ifdef HAVE_PCAP_REMOTE +bool InterfaceTreeModel::isRemote(int idx) +{ + interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx); + if (device->remote_opts.src_type == CAPTURE_IFREMOTE) + return true; + return false; +} +#endif + +/** + * The interface list has changed. global_capture_opts.all_ifaces may have been reloaded + * or changed with current data. beginResetModel() and endResetModel() will signalize the + * proxy model and the view, that the data has changed and the view has to reload + */ +void InterfaceTreeModel::interfaceListChanged() +{ + beginResetModel(); + + points.clear(); + active.clear(); + + endResetModel(); +} + +/* + * Displays the tooltip code for the given device index. + */ +QVariant InterfaceTreeModel::toolTipForInterface(int idx) const +{ +#ifdef HAVE_LIBPCAP + if (! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len <= (guint) idx) + return QVariant(); + + interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx); + + QString tt_str = "<p>"; + if (device->no_addresses > 0) + { + tt_str += QString("%1: %2") + .arg(device->no_addresses > 1 ? tr("Addresses") : tr("Address")) + .arg(html_escape(device->addresses)) + .replace('\n', ", "); + } + else if (device->if_info.type == IF_EXTCAP) + { + tt_str = QString(tr("Extcap interface: %1")).arg(get_basename(device->if_info.extcap)); + } + else + { + tt_str = tr("No addresses"); + } + tt_str += "<br/>"; + + QString cfilter = device->cfilter; + if (cfilter.isEmpty()) + { + tt_str += tr("No capture filter"); + } + else + { + tt_str += QString("%1: %2") + .arg(tr("Capture filter")) + .arg(html_escape(cfilter)); + } + tt_str += "</p>"; + + return tt_str; +#else + Q_UNUSED(idx) + + return QVariant(); +#endif +} + +#ifdef HAVE_LIBPCAP +void InterfaceTreeModel::stopStatistic() +{ + if (stat_cache_) + { + capture_stat_stop(stat_cache_); + stat_cache_ = NULL; + } +} +#endif + +void InterfaceTreeModel::updateStatistic(unsigned int idx) +{ +#ifdef HAVE_LIBPCAP + if (! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len <= (guint) idx) + return; + + interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx); + + if (device->if_info.type == IF_PIPE || device->if_info.type == IF_EXTCAP) + return; + + if (!stat_cache_) + { + // Start gathering statistics using dumpcap + // We crash (on macOS at least) if we try to do this from ::showEvent. + stat_cache_ = capture_stat_start(&global_capture_opts); + } + + struct pcap_stat stats; + unsigned diff = 0; + bool isActive = false; + + if (capture_stats(stat_cache_, device->name, &stats)) + { + if ( (int) stats.ps_recv > 0 ) + isActive = true; + + if ((int)(stats.ps_recv - device->last_packets) >= 0) + { + diff = stats.ps_recv - device->last_packets; + device->packet_diff = diff; + } + device->last_packets = stats.ps_recv; + } + + points[device->name].append(diff); + + if (active[device->name] != isActive) + { + emit layoutAboutToBeChanged(); + active[device->name] = isActive; + emit layoutChanged(); + } + + emit dataChanged(index(idx, IFTREE_COL_STATS), index(idx, IFTREE_COL_STATS)); + +#else + Q_UNUSED(idx) +#endif +} + +QItemSelection InterfaceTreeModel::selectedDevices() +{ + QItemSelection mySelection; +#ifdef HAVE_LIBPCAP + for (int idx = 0; idx < rowCount(); idx++) + { + interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx); + + if (device->selected) + { + QModelIndex selectIndex = index(idx, 0); + mySelection.merge( + QItemSelection(selectIndex, index(selectIndex.row(), columnCount() - 1)), + QItemSelectionModel::SelectCurrent + ); + } + } +#endif + return mySelection; +} + +bool InterfaceTreeModel::updateSelectedDevices(QItemSelection sourceSelection) +{ + bool selectionHasChanged = false; +#ifdef HAVE_LIBPCAP + QList<int> selectedIndices; + + QItemSelection::const_iterator it = sourceSelection.constBegin(); + while (it != sourceSelection.constEnd()) + { + QModelIndexList indeces = ((QItemSelectionRange) (*it)).indexes(); + + QModelIndexList::const_iterator cit = indeces.constBegin(); + while (cit != indeces.constEnd()) + { + QModelIndex index = (QModelIndex) (*cit); + if (! selectedIndices.contains(index.row())) + { + selectedIndices.append(index.row()); + } + ++cit; + } + ++it; + } + + global_capture_opts.num_selected = 0; + + for (unsigned int idx = 0; idx < global_capture_opts.all_ifaces->len; idx++) + { + interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx); + if (selectedIndices.contains(idx)) + { + if (! device->selected) + selectionHasChanged = true; + device->selected = TRUE; + global_capture_opts.num_selected++; + } else { + if (device->selected) + selectionHasChanged = true; + device->selected = FALSE; + } + } +#else + Q_UNUSED(sourceSelection) +#endif + return selectionHasChanged; +} diff --git a/ui/qt/models/interface_tree_model.h b/ui/qt/models/interface_tree_model.h new file mode 100644 index 00000000..cdf2ac89 --- /dev/null +++ b/ui/qt/models/interface_tree_model.h @@ -0,0 +1,99 @@ +/** @file + * + * Model for the interface data for display in the interface frame + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef INTERFACE_TREE_MODEL_H +#define INTERFACE_TREE_MODEL_H + +#include <config.h> +#include <wireshark.h> + +#ifdef HAVE_LIBPCAP +#include "ui/capture.h" +#include "ui/capture_globals.h" +#endif + +#include <QAbstractTableModel> +#include <QList> +#include <QMap> +#include <QItemSelection> + +typedef QList<int> PointList; + +enum InterfaceTreeColumns +{ + IFTREE_COL_EXTCAP, + IFTREE_COL_EXTCAP_PATH, + IFTREE_COL_NAME, + IFTREE_COL_DESCRIPTION, + IFTREE_COL_DISPLAY_NAME, + IFTREE_COL_COMMENT, + IFTREE_COL_HIDDEN, + IFTREE_COL_DLT, + IFTREE_COL_PROMISCUOUSMODE, + IFTREE_COL_TYPE, + IFTREE_COL_STATS, + IFTREE_COL_ACTIVE, + IFTREE_COL_SNAPLEN, +#ifdef CAN_SET_CAPTURE_BUFFER_SIZE + IFTREE_COL_BUFFERLEN, +#endif +#ifdef HAVE_PCAP_CREATE + IFTREE_COL_MONITOR_MODE, +#endif + IFTREE_COL_CAPTURE_FILTER, + IFTREE_COL_PIPE_PATH, + IFTREE_COL_MAX /* is not being displayed, it is the definition for the maximum numbers of columns */ +}; + +class InterfaceTreeModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + InterfaceTreeModel(QObject *parent); + ~InterfaceTreeModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + void updateStatistic(unsigned int row); +#ifdef HAVE_LIBPCAP + void stopStatistic(); +#endif + + QString interfaceError(); + QItemSelection selectedDevices(); + bool updateSelectedDevices(QItemSelection sourceSelection); + + QVariant getColumnContent(int idx, int col, int role = Qt::DisplayRole); + +#ifdef HAVE_PCAP_REMOTE + bool isRemote(int idx); +#endif + + static const QString DefaultNumericValue; + +public slots: + void interfaceListChanged(); + +private: + QVariant toolTipForInterface(int idx) const; + QMap<QString, PointList> points; + QMap<QString, bool> active; + +#ifdef HAVE_LIBPCAP + if_stat_cache_t *stat_cache_; +#endif // HAVE_LIBPCAP +}; + +#endif // INTERFACE_TREE_MODEL_H diff --git a/ui/qt/models/manuf_table_model.cpp b/ui/qt/models/manuf_table_model.cpp new file mode 100644 index 00000000..35792273 --- /dev/null +++ b/ui/qt/models/manuf_table_model.cpp @@ -0,0 +1,220 @@ +/* + * manuf_table_model.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "manuf_table_model.h" + +ManufTableItem::ManufTableItem(struct ws_manuf *ptr) : + short_name_(QString::fromUtf8(ptr->short_name)), + long_name_(QString::fromUtf8(ptr->long_name)) +{ + qsizetype size; + switch (ptr->mask) { + case 24: + size = 3; + break; + case 28: + size = 4; + break; + case 36: + size = 5; + break; + default: + ws_assert_not_reached(); + } + // Note: since 'ptr' is not stable, a deep copy is needed. +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + block_bytes_ = QByteArray(reinterpret_cast<const char *>(ptr->block), size); +#else + block_bytes_ = QByteArray(reinterpret_cast<const char *>(ptr->block), static_cast<int>(size)); +#endif + + char buf[64]; + block_name_ = QString::fromUtf8(ws_manuf_block_str(buf, sizeof(buf), ptr)); +} + +ManufTableItem::~ManufTableItem() +{ +} + +ManufTableModel::ManufTableModel(QObject *parent) : QAbstractTableModel(parent) +{ + ws_manuf_iter_t iter; + struct ws_manuf item; + + ws_manuf_iter_init(&iter); + while (ws_manuf_iter_next(&iter, &item)) { + rows_.append(new ManufTableItem(&item)); + } +} + +ManufTableModel::~ManufTableModel() +{ + clear(); +} + +int ManufTableModel::rowCount(const QModelIndex &) const +{ + return static_cast<int>(rows_.count()); +} + +int ManufTableModel::columnCount(const QModelIndex &) const +{ + return NUM_COLS; +} + +QVariant ManufTableModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() >= rows_.size()) + return QVariant(); + + ManufTableItem *item = rows_.at(index.row()); + if (index.column() >= NUM_COLS) + return QVariant(); + + if (role == Qt::DisplayRole) { + switch (index.column()) { + case COL_MAC_PREFIX: + return item->block_name_; + case COL_SHORT_NAME: + return item->short_name_; + case COL_VENDOR_NAME: + return item->long_name_; + default: + return QVariant(); + } + } + + if (role == Qt::UserRole) { + switch (index.column()) { + case COL_MAC_PREFIX: + return item->block_bytes_; + case COL_SHORT_NAME: + return item->short_name_; + case COL_VENDOR_NAME: + return item->long_name_; + default: + return QVariant(); + } + } + + return QVariant(); +} + +void ManufTableModel::addRecord(struct ws_manuf *ptr) +{ + emit beginInsertRows(QModelIndex(), rowCount(), rowCount()); + ManufTableItem *item = new ManufTableItem(ptr); + rows_.append(item); + emit endInsertRows(); +} + +void ManufTableModel::clear() +{ + if (!rows_.isEmpty()) { + emit beginRemoveRows(QModelIndex(), 0, rowCount() - 1); + qDeleteAll(rows_.begin(), rows_.end()); + rows_.clear(); + emit endRemoveRows(); + } +} + +QVariant ManufTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) { + switch (section) { + case COL_MAC_PREFIX: + return QString(tr("Address Block")); + case COL_SHORT_NAME: + return QString(tr("Short Name")); + case COL_VENDOR_NAME: + return QString(tr("Vendor Name")); + } + } + + return QVariant(); +} + + +ManufSortFilterProxyModel::ManufSortFilterProxyModel(QObject* parent) : + QSortFilterProxyModel(parent), + filter_type_(FilterEmpty) +{ +} + +void ManufSortFilterProxyModel::clearFilter() +{ + if (filter_type_ == FilterEmpty) + return; + filter_type_ = FilterEmpty; + invalidateFilter(); +} + +void ManufSortFilterProxyModel::setFilterAddress(const QByteArray &bytes) +{ + filter_type_ = FilterByAddress; + filter_bytes_ = bytes; + invalidateFilter(); +} + +void ManufSortFilterProxyModel::setFilterName(QRegularExpression &name) +{ + filter_type_ = FilterByName; + filter_name_ = name; + invalidateFilter(); +} + +static bool match_filter(const QByteArray &bytes, const QByteArray &mac_block) +{ + if (bytes.size() < mac_block.size()) + return mac_block.startsWith(bytes); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QByteArray prefix = bytes.first(mac_block.size()); +#else + QByteArray prefix = bytes.left(mac_block.size()); +#endif + // Blocks are 3, 4 or 5 bytes wide + if (mac_block.size() > 3) { + // Mask out the last nibble of the bytes for 28 and 36 bit block lengths + // (but not 24 bit OUIs) + prefix[prefix.size() - 1] = prefix[prefix.size() - 1] & 0xF0; + } + return prefix == mac_block; +} + +bool ManufSortFilterProxyModel::filterAddressAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + QModelIndex chkIdx = sourceModel()->index(source_row, ManufTableModel::COL_MAC_PREFIX, source_parent); + QByteArray mac_block = chkIdx.data(Qt::UserRole).toByteArray(); + return match_filter(filter_bytes_, mac_block); +} + +bool ManufSortFilterProxyModel::filterNameAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + QModelIndex chkIdx = sourceModel()->index(source_row, ManufTableModel::COL_VENDOR_NAME, source_parent); + QString vendor_name = chkIdx.data(Qt::UserRole).toString(); + return filter_name_.match(vendor_name).hasMatch(); +} + +bool ManufSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + switch (filter_type_) { + case FilterEmpty: return true; + case FilterByAddress: return filterAddressAcceptsRow(source_row, source_parent); + case FilterByName: return filterNameAcceptsRow(source_row, source_parent); + } + ws_error("unknown filter type %d", filter_type_); +} diff --git a/ui/qt/models/manuf_table_model.h b/ui/qt/models/manuf_table_model.h new file mode 100644 index 00000000..9f850774 --- /dev/null +++ b/ui/qt/models/manuf_table_model.h @@ -0,0 +1,90 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef MANUF_TABLE_MODEL_H +#define MANUF_TABLE_MODEL_H + +#include <QSortFilterProxyModel> +#include <QAbstractTableModel> +#include <QList> + +#include <wireshark.h> +#include <epan/manuf.h> + +class ManufTableItem +{ +public: + ManufTableItem(struct ws_manuf *ptr); + ~ManufTableItem(); + + QByteArray block_bytes_; + QString block_name_; + QString short_name_; + QString long_name_; +}; + +class ManufTableModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + ManufTableModel(QObject *parent); + ~ManufTableModel(); + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const ; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + void addRecord(struct ws_manuf *ptr); + + void clear(); + + enum { + COL_MAC_PREFIX, + COL_SHORT_NAME, + COL_VENDOR_NAME, + NUM_COLS, + }; + +private: + QList<ManufTableItem *> rows_; +}; + +class ManufSortFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + enum ManufProxyFilterType + { + FilterEmpty = 0, + FilterByAddress, + FilterByName, + }; + Q_ENUM(ManufProxyFilterType) + + ManufSortFilterProxyModel(QObject *parent); + + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; + +public slots: + void setFilterAddress(const QByteArray&); + void setFilterName(QRegularExpression&); + void clearFilter(); + +private: + ManufProxyFilterType filter_type_; + QByteArray filter_bytes_; + QRegularExpression filter_name_; + + bool filterAddressAcceptsRow(int source_row, const QModelIndex& source_parent) const; + bool filterNameAcceptsRow(int source_row, const QModelIndex& source_parent) const; +}; + +#endif diff --git a/ui/qt/models/numeric_value_chooser_delegate.cpp b/ui/qt/models/numeric_value_chooser_delegate.cpp new file mode 100644 index 00000000..9bed91b2 --- /dev/null +++ b/ui/qt/models/numeric_value_chooser_delegate.cpp @@ -0,0 +1,93 @@ +/* numeric_value_chooser_delegate.cpp + * Delegate to select a numeric value for a treeview entry + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <ui/qt/models/numeric_value_chooser_delegate.h> + +#include <QStyledItemDelegate> +#include <QSpinBox> + +NumericValueChooserDelegate::NumericValueChooserDelegate(int min, int max, QObject *parent) + : QStyledItemDelegate(parent) +{ + _min = min; + _max = max; + _default = min; +} + +NumericValueChooserDelegate::~NumericValueChooserDelegate() +{ +} + +void NumericValueChooserDelegate::setMinMaxRange(int min, int max) +{ + _min = qMin(min, max); + _max = qMax(min, max); + /* ensure, that the default value is within the new min<->max */ + _default = qMin(_max, qMax(_min, _default)); + _defReturn = QVariant::fromValue(_default); +} + +void NumericValueChooserDelegate::setDefaultValue(int defValue, QVariant defaultReturn) +{ + /* ensure, that the new default value is within min<->max */ + _default = qMin(_max, qMax(_min, defValue)); + _defReturn = defaultReturn; +} + +QWidget* NumericValueChooserDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + if (!index.isValid()) { + return QStyledItemDelegate::createEditor(parent, option, index); + } + + QSpinBox * editor = new QSpinBox(parent); + editor->setMinimum(_min); + editor->setMaximum(_max); + editor->setWrapping(true); + + connect(editor, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, + &NumericValueChooserDelegate::onValueChanged); + + return editor; +} + +void NumericValueChooserDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + if (index.isValid()) + { + bool canConvert = false; + int val = index.data().toInt(&canConvert); + if (! canConvert) + val = _default; + + QSpinBox * spinBox = qobject_cast<QSpinBox *>(editor); + spinBox->setValue(val); + } + else + QStyledItemDelegate::setEditorData(editor, index); +} + +void NumericValueChooserDelegate::setModelData(QWidget *editor, QAbstractItemModel * model, const QModelIndex &index) const +{ + if (index.isValid()) { + QSpinBox * spinBox = qobject_cast<QSpinBox *>(editor); + model->setData(index, _default == spinBox->value() ? _defReturn : QVariant::fromValue(spinBox->value())); + } else { + QStyledItemDelegate::setModelData(editor, model, index); + } +} + +void NumericValueChooserDelegate::onValueChanged(int) +{ + QSpinBox * spinBox = qobject_cast<QSpinBox *>(sender()); + emit commitData(spinBox); +} diff --git a/ui/qt/models/numeric_value_chooser_delegate.h b/ui/qt/models/numeric_value_chooser_delegate.h new file mode 100644 index 00000000..ceda8746 --- /dev/null +++ b/ui/qt/models/numeric_value_chooser_delegate.h @@ -0,0 +1,45 @@ +/** @file + * + * Delegate to select a numeric value for a treeview entry + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef NUMERIC_VALUE_CHOOSER_DELEGATE_H_ +#define NUMERIC_VALUE_CHOOSER_DELEGATE_H_ + + +#include <QStyledItemDelegate> + +class NumericValueChooserDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + NumericValueChooserDelegate(int min = 0, int max = 0, QObject *parent = 0); + ~NumericValueChooserDelegate(); + + void setMinMaxRange(int min, int max); + void setDefaultValue(int defValue, QVariant defaultReturn); + +protected: + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; + +private: + + int _min; + int _max; + int _default; + QVariant _defReturn; + +private slots: + void onValueChanged(int i); +}; + +#endif /* NUMERIC_VALUE_CHOOSER_DELEGATE_H_ */ diff --git a/ui/qt/models/packet_list_model.cpp b/ui/qt/models/packet_list_model.cpp new file mode 100644 index 00000000..0ed61d74 --- /dev/null +++ b/ui/qt/models/packet_list_model.cpp @@ -0,0 +1,1014 @@ +/* packet_list_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <algorithm> +#include <glib.h> +#include <cmath> +#include <stdexcept> + +#include "packet_list_model.h" + +#include "file.h" + +#include <wsutil/nstime.h> +#include <epan/column.h> +#include <epan/expert.h> +#include <epan/prefs.h> + +#include "ui/packet_list_utils.h" +#include "ui/recent.h" + +#include <epan/color_filters.h> +#include "frame_tvbuff.h" + +#include <ui/qt/utils/color_utils.h> +#include <ui/qt/utils/qt_ui_utils.h> +#include "main_application.h" +#include <ui/qt/main_window.h> +#include <ui/qt/main_status_bar.h> +#include <ui/qt/widgets/wireless_timeline.h> + +#include <QColor> +#include <QElapsedTimer> +#include <QFontMetrics> +#include <QModelIndex> +#include <QElapsedTimer> + +// Print timing information +//#define DEBUG_PACKET_LIST_MODEL 1 + +#ifdef DEBUG_PACKET_LIST_MODEL +#include <wsutil/time_util.h> +#endif + +class SortAbort : public std::runtime_error +{ + using std::runtime_error::runtime_error; +}; + +static PacketListModel * glbl_plist_model = Q_NULLPTR; +static const int reserved_packets_ = 100000; + +guint +packet_list_append(column_info *, frame_data *fdata) +{ + if (!glbl_plist_model) + return 0; + + /* fdata should be filled with the stuff we need + * strings are built at display time. + */ + return glbl_plist_model->appendPacket(fdata); +} + +void +packet_list_recreate_visible_rows(void) +{ + if (glbl_plist_model) + glbl_plist_model->recreateVisibleRows(); +} + +PacketListModel::PacketListModel(QObject *parent, capture_file *cf) : + QAbstractItemModel(parent), + number_to_row_(QVector<int>()), + max_row_height_(0), + max_line_count_(1), + idle_dissection_row_(0) +{ + Q_ASSERT(glbl_plist_model == Q_NULLPTR); + glbl_plist_model = this; + setCaptureFile(cf); + + physical_rows_.reserve(reserved_packets_); + visible_rows_.reserve(reserved_packets_); + new_visible_rows_.reserve(1000); + number_to_row_.reserve(reserved_packets_); + + if (qobject_cast<MainWindow *>(mainApp->mainWindow())) + { + MainWindow *mw = qobject_cast<MainWindow *>(mainApp->mainWindow()); + QWidget * wtWidget = mw->findChild<WirelessTimeline *>(); + if (wtWidget && qobject_cast<WirelessTimeline *>(wtWidget)) + { + WirelessTimeline * wt = qobject_cast<WirelessTimeline *>(wtWidget); + connect(this, &PacketListModel::bgColorizationProgress, + wt, &WirelessTimeline::bgColorizationProgress); + } + + } + + connect(this, &PacketListModel::maxLineCountChanged, + this, &PacketListModel::emitItemHeightChanged, + Qt::QueuedConnection); + idle_dissection_timer_ = new QElapsedTimer(); +} + +PacketListModel::~PacketListModel() +{ + delete idle_dissection_timer_; +} + +void PacketListModel::setCaptureFile(capture_file *cf) +{ + cap_file_ = cf; +} + +// Packet list records have no children (for now, at least). +QModelIndex PacketListModel::index(int row, int column, const QModelIndex &) const +{ + if (row >= visible_rows_.count() || row < 0 || !cap_file_ || column >= prefs.num_cols) + return QModelIndex(); + + PacketListRecord *record = visible_rows_[row]; + + return createIndex(row, column, record); +} + +// Everything is under the root. +QModelIndex PacketListModel::parent(const QModelIndex &) const +{ + return QModelIndex(); +} + +int PacketListModel::packetNumberToRow(int packet_num) const +{ + // map 1-based values to 0-based row numbers. Invisible rows are stored as + // the default value (0) and should map to -1. + return number_to_row_.value(packet_num) - 1; +} + +guint PacketListModel::recreateVisibleRows() +{ + beginResetModel(); + visible_rows_.resize(0); + number_to_row_.fill(0); + endResetModel(); + + foreach (PacketListRecord *record, physical_rows_) { + frame_data *fdata = record->frameData(); + + if (fdata->passed_dfilter || fdata->ref_time) { + visible_rows_ << record; + if (static_cast<guint32>(number_to_row_.size()) <= fdata->num) { + number_to_row_.resize(fdata->num + 10000); + } + number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count()); + } + } + if (!visible_rows_.isEmpty()) { + beginInsertRows(QModelIndex(), 0, static_cast<int>(visible_rows_.count()) - 1); + endInsertRows(); + } + idle_dissection_row_ = 0; + return static_cast<guint>(visible_rows_.count()); +} + +void PacketListModel::clear() { + beginResetModel(); + qDeleteAll(physical_rows_); + PacketListRecord::invalidateAllRecords(); + physical_rows_.resize(0); + visible_rows_.resize(0); + new_visible_rows_.resize(0); + number_to_row_.resize(0); + endResetModel(); + max_row_height_ = 0; + max_line_count_ = 1; + idle_dissection_timer_->invalidate(); + idle_dissection_row_ = 0; +} + +void PacketListModel::invalidateAllColumnStrings() +{ + PacketListRecord::invalidateAllRecords(); + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), + QVector<int>() << Qt::DisplayRole); +} + +void PacketListModel::resetColumns() +{ + if (cap_file_) { + PacketListRecord::resetColumns(&cap_file_->cinfo); + } + + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); + emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1); +} + +void PacketListModel::resetColorized() +{ + PacketListRecord::resetColorization(); + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), + QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole); +} + +void PacketListModel::toggleFrameMark(const QModelIndexList &indeces) +{ + if (!cap_file_ || indeces.count() <= 0) + return; + + int sectionMax = columnCount() - 1; + + foreach (QModelIndex index, indeces) { + if (! index.isValid()) + continue; + + PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer()); + if (!record) + continue; + + frame_data *fdata = record->frameData(); + if (!fdata) + continue; + + if (fdata->marked) + cf_unmark_frame(cap_file_, fdata); + else + cf_mark_frame(cap_file_, fdata); + + emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax), + QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole); + } +} + +void PacketListModel::setDisplayedFrameMark(gboolean set) +{ + foreach (PacketListRecord *record, visible_rows_) { + if (set) { + cf_mark_frame(cap_file_, record->frameData()); + } else { + cf_unmark_frame(cap_file_, record->frameData()); + } + } + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), + QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole); +} + +void PacketListModel::toggleFrameIgnore(const QModelIndexList &indeces) +{ + if (!cap_file_ || indeces.count() <= 0) + return; + + int sectionMax = columnCount() - 1; + + foreach (QModelIndex index, indeces) { + if (! index.isValid()) + continue; + + PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer()); + if (!record) + continue; + + frame_data *fdata = record->frameData(); + if (!fdata) + continue; + + if (fdata->ignored) + cf_unignore_frame(cap_file_, fdata); + else + cf_ignore_frame(cap_file_, fdata); + + emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax), + QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole); + } +} + +void PacketListModel::setDisplayedFrameIgnore(gboolean set) +{ + foreach (PacketListRecord *record, visible_rows_) { + if (set) { + cf_ignore_frame(cap_file_, record->frameData()); + } else { + cf_unignore_frame(cap_file_, record->frameData()); + } + } + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), + QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole); +} + +void PacketListModel::toggleFrameRefTime(const QModelIndex &rt_index) +{ + if (!cap_file_ || !rt_index.isValid()) return; + + PacketListRecord *record = static_cast<PacketListRecord*>(rt_index.internalPointer()); + if (!record) return; + + frame_data *fdata = record->frameData(); + if (!fdata) return; + + if (fdata->ref_time) { + fdata->ref_time=0; + cap_file_->ref_time_count--; + } else { + fdata->ref_time=1; + cap_file_->ref_time_count++; + } + cf_reftime_packets(cap_file_); + if (!fdata->ref_time && !fdata->passed_dfilter) { + cap_file_->displayed_count--; + } + record->resetColumns(&cap_file_->cinfo); + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); +} + +void PacketListModel::unsetAllFrameRefTime() +{ + if (!cap_file_) return; + + /* XXX: we might need a progressbar here */ + + foreach (PacketListRecord *record, physical_rows_) { + frame_data *fdata = record->frameData(); + if (fdata->ref_time) { + fdata->ref_time = 0; + } + } + cap_file_->ref_time_count = 0; + cf_reftime_packets(cap_file_); + PacketListRecord::resetColumns(&cap_file_->cinfo); + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); +} + +void PacketListModel::addFrameComment(const QModelIndexList &indices, const QByteArray &comment) +{ + int sectionMax = columnCount() - 1; + frame_data *fdata; + if (!cap_file_) return; + + for (const auto &index : indices) { + if (!index.isValid()) continue; + + PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer()); + if (!record) continue; + + fdata = record->frameData(); + wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata); + wtap_block_add_string_option(pkt_block, OPT_COMMENT, comment.data(), comment.size()); + + if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) { + cap_file_->packet_comment_count++; + expert_update_comment_count(cap_file_->packet_comment_count); + } + + // In case there are coloring rules or columns related to comments. + // (#12519) + // + // XXX: "Does any active coloring rule relate to frame data" + // could be an optimization. For columns, note that + // "col_based_on_frame_data" only applies to built in columns, + // not custom columns based on frame data. (Should we prevent + // custom columns based on frame data from being created, + // substituting them with the other columns?) + // + // Note that there are not currently any fields that depend on + // whether other frames have comments, unlike with time references + // and time shifts ("frame.time_relative", "frame.offset_shift", etc.) + // If there were, then we'd need to reset data for all frames instead + // of just the frames changed. + record->invalidateColorized(); + record->invalidateRecord(); + emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax), + QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole); + } +} + +void PacketListModel::setFrameComment(const QModelIndex &index, const QByteArray &comment, guint c_number) +{ + int sectionMax = columnCount() - 1; + frame_data *fdata; + if (!cap_file_) return; + + if (!index.isValid()) return; + + PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer()); + if (!record) return; + + fdata = record->frameData(); + + wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata); + if (comment.isEmpty()) { + wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, c_number); + if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) { + cap_file_->packet_comment_count--; + expert_update_comment_count(cap_file_->packet_comment_count); + } + } else { + wtap_block_set_nth_string_option_value(pkt_block, OPT_COMMENT, c_number, comment.data(), comment.size()); + cf_set_modified_block(cap_file_, fdata, pkt_block); + } + + record->invalidateColorized(); + record->invalidateRecord(); + emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax), + QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole); +} + +void PacketListModel::deleteFrameComments(const QModelIndexList &indices) +{ + int sectionMax = columnCount() - 1; + frame_data *fdata; + if (!cap_file_) return; + + for (const auto &index : indices) { + if (!index.isValid()) continue; + + PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer()); + if (!record) continue; + + fdata = record->frameData(); + wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata); + guint n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT); + + if (n_comments) { + for (guint i = 0; i < n_comments; i++) { + wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, 0); + } + if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) { + cap_file_->packet_comment_count -= n_comments; + expert_update_comment_count(cap_file_->packet_comment_count); + } + + record->invalidateColorized(); + record->invalidateRecord(); + emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax), + QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole); + } + } +} + +void PacketListModel::deleteAllFrameComments() +{ + int row; + int sectionMax = columnCount() - 1; + if (!cap_file_) return; + + /* XXX: we might need a progressbar here */ + + foreach (PacketListRecord *record, physical_rows_) { + frame_data *fdata = record->frameData(); + wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata); + guint n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT); + + if (n_comments) { + for (guint i = 0; i < n_comments; i++) { + wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, 0); + } + cf_set_modified_block(cap_file_, fdata, pkt_block); + + record->invalidateColorized(); + record->invalidateRecord(); + row = packetNumberToRow(fdata->num); + if (row > -1) { + emit dataChanged(index(row, 0), index(row, sectionMax), + QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole); + } + } + } + cap_file_->packet_comment_count = 0; + expert_update_comment_count(cap_file_->packet_comment_count); +} + +void PacketListModel::setMaximumRowHeight(int height) +{ + max_row_height_ = height; + // As the QTreeView uniformRowHeights documentation says, + // "The height is obtained from the first item in the view. It is + // updated when the data changes on that item." + emit dataChanged(index(0, 0), index(0, columnCount() - 1)); +} + +int PacketListModel::sort_column_; +int PacketListModel::sort_column_is_numeric_; +int PacketListModel::text_sort_column_; +Qt::SortOrder PacketListModel::sort_order_; +capture_file *PacketListModel::sort_cap_file_; +gboolean PacketListModel::stop_flag_; +ProgressFrame *PacketListModel::progress_frame_; +double PacketListModel::comps_; +double PacketListModel::exp_comps_; + +QElapsedTimer busy_timer_; +const int busy_timeout_ = 65; // ms, approximately 15 fps +void PacketListModel::sort(int column, Qt::SortOrder order) +{ + if (!cap_file_ || visible_rows_.count() < 1) return; + if (column < 0) return; + + if (physical_rows_.count() < 1) + return; + + sort_column_ = column; + text_sort_column_ = PacketListRecord::textColumn(column); + sort_order_ = order; + sort_cap_file_ = cap_file_; + + QString col_title = get_column_title(column); + + if (text_sort_column_ >= 0 && (guint)visible_rows_.count() > prefs.gui_packet_list_cached_rows_max) { + /* Column not based on frame data but by column text that requires + * dissection, so to sort in a reasonable amount of time the column + * text needs to be cached. + */ + /* If the sort is being triggered because the columns were already + * sorted and the filter is being cleared (or changed to something + * else with more rows than fit in the cache), then the temporary + * message will be immediately overwritten with the standard capture + * statistics by the packets_bar_update() call after thawing the rows. + * It will still blink yellow, and the user will get the message if + * they then click on the header file (wondering why it didn't sort.) + */ + if (col_title.isEmpty()) { + col_title = tr("Column"); + } + QString temp_msg = tr("%1 can only be sorted with %2 or fewer visible rows; increase cache size in Layout preferences").arg(col_title).arg(prefs.gui_packet_list_cached_rows_max); + mainApp->pushStatus(MainApplication::TemporaryStatus, temp_msg); + return; + } + + /* If we are currently in the middle of reading the capture file, don't + * sort. PacketList::captureFileReadFinished invalidates all the cached + * column strings and then tries to sort again. + * Similarly, claim the read lock because we don't want the file to + * change out from under us while sorting, which can segfault. (Previously + * we ignored user input, but now in order to cancel sorting we don't.) + */ + if (sort_cap_file_->read_lock) { + ws_info("Refusing to sort because capture file is being read"); + /* We shouldn't have to tell the user because we're just deferring + * the sort until PacketList::captureFileReadFinished + */ + return; + } + sort_cap_file_->read_lock = TRUE; + + QString busy_msg; + if (!col_title.isEmpty()) { + busy_msg = tr("Sorting \"%1\"…").arg(col_title); + } else { + busy_msg = tr("Sorting …"); + } + stop_flag_ = FALSE; + comps_ = 0; + /* XXX: The expected number of comparisons is O(N log N), but this could + * be a pretty significant overestimate of the amount of time it takes, + * if there are lots of identical entries. (Especially with string + * comparisons, some comparisons are faster than others.) Better to + * overestimate? + */ + exp_comps_ = log2(visible_rows_.count()) * visible_rows_.count(); + progress_frame_ = nullptr; + if (qobject_cast<MainWindow *>(mainApp->mainWindow())) { + MainWindow *mw = qobject_cast<MainWindow *>(mainApp->mainWindow()); + progress_frame_ = mw->findChild<ProgressFrame *>(); + if (progress_frame_) { + progress_frame_->showProgress(busy_msg, true, false, &stop_flag_, 0); + connect(progress_frame_, &ProgressFrame::stopLoading, + this, &PacketListModel::stopSorting); + } + } + + busy_timer_.start(); + sort_column_is_numeric_ = isNumericColumn(sort_column_); + QVector<PacketListRecord *> sorted_visible_rows_ = visible_rows_; + try { + std::sort(sorted_visible_rows_.begin(), sorted_visible_rows_.end(), recordLessThan); + + beginResetModel(); + visible_rows_.resize(0); + number_to_row_.fill(0); + foreach (PacketListRecord *record, sorted_visible_rows_) { + frame_data *fdata = record->frameData(); + + if (fdata->passed_dfilter || fdata->ref_time) { + visible_rows_ << record; + if (number_to_row_.size() <= (int)fdata->num) { + number_to_row_.resize(fdata->num + 10000); + } + number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count()); + } + } + endResetModel(); + } catch (const SortAbort& e) { + mainApp->pushStatus(MainApplication::TemporaryStatus, e.what()); + } + + if (progress_frame_ != nullptr) { + progress_frame_->hide(); + disconnect(progress_frame_, &ProgressFrame::stopLoading, + this, &PacketListModel::stopSorting); + } + sort_cap_file_->read_lock = FALSE; + + if (cap_file_->current_frame) { + emit goToPacket(cap_file_->current_frame->num); + } +} + +void PacketListModel::stopSorting() +{ + stop_flag_ = TRUE; +} + +bool PacketListModel::isNumericColumn(int column) +{ + if (column < 0) { + return false; + } + switch (sort_cap_file_->cinfo.columns[column].col_fmt) { + case COL_CUMULATIVE_BYTES: /**< 3) Cumulative number of bytes */ + case COL_DELTA_TIME: /**< 5) Delta time */ + case COL_DELTA_TIME_DIS: /**< 8) Delta time displayed*/ + case COL_UNRES_DST_PORT: /**< 10) Unresolved dest port */ + case COL_FREQ_CHAN: /**< 15) IEEE 802.11 (and WiMax?) - Channel */ + case COL_RSSI: /**< 22) IEEE 802.11 - received signal strength */ + case COL_TX_RATE: /**< 23) IEEE 802.11 - TX rate in Mbps */ + case COL_NUMBER: /**< 32) Packet list item number */ + case COL_PACKET_LENGTH: /**< 33) Packet length in bytes */ + case COL_UNRES_SRC_PORT: /**< 41) Unresolved source port */ + return true; + + /* + * Try to sort port numbers as number, if the numeric comparison fails (due + * to name resolution), it will fallback to string comparison. + * */ + case COL_RES_DST_PORT: /**< 10) Resolved dest port */ + case COL_DEF_DST_PORT: /**< 12) Destination port */ + case COL_DEF_SRC_PORT: /**< 37) Source port */ + case COL_RES_SRC_PORT: /**< 40) Resolved source port */ + return true; + + case COL_CUSTOM: + /* handle custom columns below. */ + break; + + default: + return false; + } + + guint num_fields = g_slist_length(sort_cap_file_->cinfo.columns[column].col_custom_fields_ids); + for (guint i = 0; i < num_fields; i++) { + guint *field_idx = (guint *) g_slist_nth_data(sort_cap_file_->cinfo.columns[column].col_custom_fields_ids, i); + header_field_info *hfi = proto_registrar_get_nth(*field_idx); + + /* + * Reject a field when there is no numeric field type or when: + * - there are (value_string) "strings" + * (but do accept fields which have a unit suffix). + * - BASE_HEX or BASE_HEX_DEC (these have a constant width, string + * comparison is faster than conversion to double). + * - BASE_CUSTOM (these can be formatted in any way). + */ + if (!hfi || + (hfi->strings != NULL && !(hfi->display & BASE_UNIT_STRING)) || + !(((FT_IS_INT(hfi->type) || FT_IS_UINT(hfi->type)) && + ((FIELD_DISPLAY(hfi->display) == BASE_DEC) || + (FIELD_DISPLAY(hfi->display) == BASE_OCT) || + (FIELD_DISPLAY(hfi->display) == BASE_DEC_HEX))) || + (hfi->type == FT_DOUBLE) || (hfi->type == FT_FLOAT) || + (hfi->type == FT_BOOLEAN) || (hfi->type == FT_FRAMENUM) || + (hfi->type == FT_RELATIVE_TIME))) { + return false; + } + } + + return true; +} + +bool PacketListModel::recordLessThan(PacketListRecord *r1, PacketListRecord *r2) +{ + int cmp_val = 0; + comps_++; + + // Wherein we try to cram the logic of packet_list_compare_records, + // _packet_list_compare_records, and packet_list_compare_custom from + // gtk/packet_list_store.c into one function + + if (busy_timer_.elapsed() > busy_timeout_) { + if (progress_frame_) { + progress_frame_->setValue(static_cast<int>(comps_/exp_comps_ * 100)); + } + // What's the least amount of processing that we can do which will draw + // the busy indicator? + mainApp->processEvents(QEventLoop::ExcludeSocketNotifiers, 1); + if (stop_flag_) { + throw SortAbort("Sorting aborted"); + } + busy_timer_.restart(); + } + if (sort_column_ < 0) { + // No column. + cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER); + } else if (text_sort_column_ < 0) { + // Column comes directly from frame data + cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), sort_cap_file_->cinfo.columns[sort_column_].col_fmt); + } else { + QString r1String = r1->columnString(sort_cap_file_, sort_column_); + QString r2String = r2->columnString(sort_cap_file_, sort_column_); + // XXX: The naive string comparison compares Unicode code points. + // Proper collation is more expensive + cmp_val = r1String.compare(r2String); + if (cmp_val != 0 && sort_column_is_numeric_) { + // Custom column with numeric data (or something like a port number). + // Attempt to convert to numbers. + // XXX This is slow. Can we avoid doing this? Perhaps the actual + // values used for sorting should be cached too as QVariant[List]. + // If so, we could consider using QCollatorSortKeys or similar + // for strings as well. + bool ok_r1, ok_r2; + double num_r1 = parseNumericColumn(r1String, &ok_r1); + double num_r2 = parseNumericColumn(r2String, &ok_r2); + + if (!ok_r1 && !ok_r2) { + cmp_val = 0; + } else if (!ok_r1 || (ok_r2 && num_r1 < num_r2)) { + // either r1 is invalid (and sort it before others) or both + // r1 and r2 are valid (sort normally) + cmp_val = -1; + } else if (!ok_r2 || (num_r1 > num_r2)) { + cmp_val = 1; + } + } + + if (cmp_val == 0) { + // All else being equal, compare column numbers. + cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER); + } + } + + if (sort_order_ == Qt::AscendingOrder) { + return cmp_val < 0; + } else { + return cmp_val > 0; + } +} + +// Parses a field as a double. Handle values with suffixes ("12ms"), negative +// values ("-1.23") and fields with multiple occurrences ("1,2"). Marks values +// that do not contain any numeric value ("Unknown") as invalid. +double PacketListModel::parseNumericColumn(const QString &val, bool *ok) +{ + QByteArray ba = val.toUtf8(); + const char *strval = ba.constData(); + gchar *end = NULL; + double num = g_ascii_strtod(strval, &end); + *ok = strval != end; + return num; +} + +// ::data is const so we have to make changes here. +void PacketListModel::emitItemHeightChanged(const QModelIndex &ih_index) +{ + if (!ih_index.isValid()) return; + + PacketListRecord *record = static_cast<PacketListRecord*>(ih_index.internalPointer()); + if (!record) return; + + if (record->lineCount() > max_line_count_) { + max_line_count_ = record->lineCount(); + emit itemHeightChanged(ih_index); + } +} + +int PacketListModel::rowCount(const QModelIndex &) const +{ + return static_cast<int>(visible_rows_.count()); +} + +int PacketListModel::columnCount(const QModelIndex &) const +{ + return prefs.num_cols; +} + +QVariant PacketListModel::data(const QModelIndex &d_index, int role) const +{ + if (!d_index.isValid()) + return QVariant(); + + PacketListRecord *record = static_cast<PacketListRecord*>(d_index.internalPointer()); + if (!record) + return QVariant(); + const frame_data *fdata = record->frameData(); + if (!fdata) + return QVariant(); + + switch (role) { + case Qt::TextAlignmentRole: + switch(recent_get_column_xalign(d_index.column())) { + case COLUMN_XALIGN_RIGHT: + return Qt::AlignRight; + break; + case COLUMN_XALIGN_CENTER: + return Qt::AlignCenter; + break; + case COLUMN_XALIGN_LEFT: + return Qt::AlignLeft; + break; + case COLUMN_XALIGN_DEFAULT: + default: + if (right_justify_column(d_index.column(), cap_file_)) { + return Qt::AlignRight; + } + break; + } + return Qt::AlignLeft; + + case Qt::BackgroundRole: + const color_t *color; + if (fdata->ignored) { + color = &prefs.gui_ignored_bg; + } else if (fdata->marked) { + color = &prefs.gui_marked_bg; + } else if (fdata->color_filter && recent.packet_list_colorize) { + const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter; + color = &color_filter->bg_color; + } else { + return QVariant(); + } + return ColorUtils::fromColorT(color); + case Qt::ForegroundRole: + if (fdata->ignored) { + color = &prefs.gui_ignored_fg; + } else if (fdata->marked) { + color = &prefs.gui_marked_fg; + } else if (fdata->color_filter && recent.packet_list_colorize) { + const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter; + color = &color_filter->fg_color; + } else { + return QVariant(); + } + return ColorUtils::fromColorT(color); + case Qt::DisplayRole: + { + int column = d_index.column(); + QString column_string = record->columnString(cap_file_, column, true); + // We don't know an item's sizeHint until we fetch its text here. + // Assume each line count is 1. If the line count changes, emit + // itemHeightChanged which triggers another redraw (including a + // fetch of SizeHintRole and DisplayRole) in the next event loop. + if (column == 0 && record->lineCountChanged() && record->lineCount() > max_line_count_) { + emit maxLineCountChanged(d_index); + } + return column_string; + } + case Qt::SizeHintRole: + { + // If this is the first row and column, return the maximum row height... + if (d_index.row() < 1 && d_index.column() < 1 && max_row_height_ > 0) { + QSize size = QSize(-1, max_row_height_); + return size; + } + // ...otherwise punt so that the item delegate can correctly calculate the item width. + return QVariant(); + } + default: + return QVariant(); + } +} + +QVariant PacketListModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (!cap_file_) return QVariant(); + + if (orientation == Qt::Horizontal && section < prefs.num_cols) { + switch (role) { + case Qt::DisplayRole: + return QVariant::fromValue(QString(get_column_title(section))); + case Qt::ToolTipRole: + return QVariant::fromValue(gchar_free_to_qstring(get_column_tooltip(section))); + case PacketListModel::HEADER_CAN_RESOLVE: + return (bool)resolve_column(section, cap_file_); + default: + break; + } + } + + return QVariant(); +} + +void PacketListModel::flushVisibleRows() +{ + int pos = static_cast<int>(visible_rows_.count()); + + if (new_visible_rows_.count() > 0) { + beginInsertRows(QModelIndex(), pos, pos + static_cast<int>(new_visible_rows_.count())); + foreach (PacketListRecord *record, new_visible_rows_) { + frame_data *fdata = record->frameData(); + + visible_rows_ << record; + if (static_cast<unsigned int>(number_to_row_.size()) <= fdata->num) { + number_to_row_.resize(fdata->num + 10000); + } + number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count()); + } + endInsertRows(); + new_visible_rows_.resize(0); + } +} + +// Fill our column string and colorization cache while the application is +// idle. Try to be as conservative with the CPU and disk as possible. +static const int idle_dissection_interval_ = 5; // ms +void PacketListModel::dissectIdle(bool reset) +{ + if (reset) { +// qDebug() << "=di reset" << idle_dissection_row_; + idle_dissection_row_ = 0; + } else if (!idle_dissection_timer_->isValid()) { + return; + } + + idle_dissection_timer_->restart(); + + int first = idle_dissection_row_; + while (idle_dissection_timer_->elapsed() < idle_dissection_interval_ + && idle_dissection_row_ < physical_rows_.count()) { + ensureRowColorized(idle_dissection_row_); + idle_dissection_row_++; +// if (idle_dissection_row_ % 1000 == 0) qDebug() << "=di row" << idle_dissection_row_; + } + + if (idle_dissection_row_ < physical_rows_.count()) { + QTimer::singleShot(0, this, [=]() { dissectIdle(); }); + } else { + idle_dissection_timer_->invalidate(); + } + + // report colorization progress + emit bgColorizationProgress(first+1, idle_dissection_row_+1); +} + +// XXX Pass in cinfo from packet_list_append so that we can fill in +// line counts? +gint PacketListModel::appendPacket(frame_data *fdata) +{ + PacketListRecord *record = new PacketListRecord(fdata); + qsizetype pos = -1; + +#ifdef DEBUG_PACKET_LIST_MODEL + if (fdata->num % 10000 == 1) { + log_resource_usage(fdata->num == 1, "%u packets", fdata->num); + } +#endif + + physical_rows_ << record; + + if (fdata->passed_dfilter || fdata->ref_time) { + new_visible_rows_ << record; + if (new_visible_rows_.count() < 2) { + // This is the first queued packet. Schedule an insertion for + // the next UI update. + QTimer::singleShot(0, this, &PacketListModel::flushVisibleRows); + } + pos = static_cast<int>( visible_rows_.count() + new_visible_rows_.count() ) - 1; + } + + return static_cast<gint>(pos); +} + +frame_data *PacketListModel::getRowFdata(QModelIndex idx) +{ + if (!idx.isValid()) + return Q_NULLPTR; + return getRowFdata(idx.row()); +} + +frame_data *PacketListModel::getRowFdata(int row) { + if (row < 0 || row >= visible_rows_.count()) + return NULL; + PacketListRecord *record = visible_rows_[row]; + if (!record) + return NULL; + return record->frameData(); +} + +void PacketListModel::ensureRowColorized(int row) +{ + if (row < 0 || row >= visible_rows_.count()) + return; + PacketListRecord *record = visible_rows_[row]; + if (!record) + return; + if (!record->colorized()) { + record->ensureColorized(cap_file_); + } +} + +int PacketListModel::visibleIndexOf(frame_data *fdata) const +{ + int row = 0; + foreach (PacketListRecord *record, visible_rows_) { + if (record->frameData() == fdata) { + return row; + } + row++; + } + + return -1; +} diff --git a/ui/qt/models/packet_list_model.h b/ui/qt/models/packet_list_model.h new file mode 100644 index 00000000..807d8847 --- /dev/null +++ b/ui/qt/models/packet_list_model.h @@ -0,0 +1,130 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef PACKET_LIST_MODEL_H +#define PACKET_LIST_MODEL_H + +#include <config.h> + +#include <stdio.h> + +#include <glib.h> + +#include <epan/packet.h> + +#include <QAbstractItemModel> +#include <QFont> +#include <QVector> + +#include <ui/qt/progress_frame.h> + +#include "packet_list_record.h" + +#include "cfile.h" + +class QElapsedTimer; + +class PacketListModel : public QAbstractItemModel +{ + Q_OBJECT +public: + + enum { + HEADER_CAN_RESOLVE = Qt::UserRole, + }; + + explicit PacketListModel(QObject *parent = 0, capture_file *cf = NULL); + ~PacketListModel(); + void setCaptureFile(capture_file *cf); + QModelIndex index(int row, int column, + const QModelIndex & = QModelIndex()) const; + QModelIndex parent(const QModelIndex &) const; + int packetNumberToRow(int packet_num) const; + guint recreateVisibleRows(); + void clear(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex & = QModelIndex()) const; + QVariant data(const QModelIndex &d_index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + gint appendPacket(frame_data *fdata); + frame_data *getRowFdata(QModelIndex idx); + frame_data *getRowFdata(int row); + void ensureRowColorized(int row); + int visibleIndexOf(frame_data *fdata) const; + /** + * @brief Invalidate any cached column strings. + */ + void invalidateAllColumnStrings(); + /** + * @brief Rebuild columns from settings. + */ + void resetColumns(); + void resetColorized(); + void toggleFrameMark(const QModelIndexList &indeces); + void setDisplayedFrameMark(gboolean set); + void toggleFrameIgnore(const QModelIndexList &indeces); + void setDisplayedFrameIgnore(gboolean set); + void toggleFrameRefTime(const QModelIndex &rt_index); + void unsetAllFrameRefTime(); + void addFrameComment(const QModelIndexList &indices, const QByteArray &comment); + void setFrameComment(const QModelIndex &index, const QByteArray &comment, guint c_number); + void deleteFrameComments(const QModelIndexList &indices); + void deleteAllFrameComments(); + + void setMaximumRowHeight(int height); + +signals: + void goToPacket(int); + void maxLineCountChanged(const QModelIndex &ih_index) const; + void itemHeightChanged(const QModelIndex &ih_index); + + void bgColorizationProgress(int first, int last); + +public slots: + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + void stopSorting(); + void flushVisibleRows(); + void dissectIdle(bool reset = false); + +private: + capture_file *cap_file_; + QList<QString> col_names_; + QVector<PacketListRecord *> physical_rows_; + QVector<PacketListRecord *> visible_rows_; + QVector<PacketListRecord *> new_visible_rows_; + QVector<int> number_to_row_; + + int max_row_height_; // px + int max_line_count_; + + static int sort_column_; + static int sort_column_is_numeric_; + static int text_sort_column_; + static Qt::SortOrder sort_order_; + static capture_file *sort_cap_file_; + static bool recordLessThan(PacketListRecord *r1, PacketListRecord *r2); + static double parseNumericColumn(const QString &val, bool *ok); + + static gboolean stop_flag_; + static ProgressFrame *progress_frame_; + static double exp_comps_; + static double comps_; + + QElapsedTimer *idle_dissection_timer_; + int idle_dissection_row_; + + bool isNumericColumn(int column); + +private slots: + void emitItemHeightChanged(const QModelIndex &ih_index); +}; + +#endif // PACKET_LIST_MODEL_H diff --git a/ui/qt/models/packet_list_record.cpp b/ui/qt/models/packet_list_record.cpp new file mode 100644 index 00000000..9c2c66d1 --- /dev/null +++ b/ui/qt/models/packet_list_record.cpp @@ -0,0 +1,246 @@ +/* packet_list_record.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "packet_list_record.h" + +#include <file.h> + +#include <epan/epan_dissect.h> +#include <epan/column.h> +#include <epan/conversation.h> +#include <epan/wmem_scopes.h> + +#include <epan/color_filters.h> + +#include "frame_tvbuff.h" + +#include <ui/qt/utils/qt_ui_utils.h> + +#include <QStringList> + +QCache<guint32, QStringList> PacketListRecord::col_text_cache_(500); +QMap<int, int> PacketListRecord::cinfo_column_; +unsigned PacketListRecord::rows_color_ver_ = 1; + +PacketListRecord::PacketListRecord(frame_data *frameData) : + fdata_(frameData), + lines_(1), + line_count_changed_(false), + color_ver_(0), + colorized_(false), + conv_index_(0), + read_failed_(false) +{ +} + +PacketListRecord::~PacketListRecord() +{ +} + +void PacketListRecord::ensureColorized(capture_file *cap_file) +{ + // packet_list_store.c:packet_list_get_value + Q_ASSERT(fdata_); + + if (!cap_file) { + return; + } + + bool dissect_color = !colorized_ || ( color_ver_ != rows_color_ver_ ); + if (dissect_color) { + /* Dissect columns only if it won't evict anything from cache */ + bool dissect_columns = col_text_cache_.totalCost() < col_text_cache_.maxCost(); + dissect(cap_file, dissect_columns, dissect_color); + } +} + +// We might want to return a const char * instead. This would keep us from +// creating excessive QByteArrays, e.g. in PacketListModel::recordLessThan. +const QString PacketListRecord::columnString(capture_file *cap_file, int column, bool colorized) +{ + // packet_list_store.c:packet_list_get_value + Q_ASSERT(fdata_); + + if (!cap_file || column < 0 || column >= cap_file->cinfo.num_cols) { + return QString(); + } + + // + // XXX - do we still need to check the colorization, given that we now + // have the ensureColorized() method to ensure that the record is + // properly colorized? + // + bool dissect_color = ( colorized && !colorized_ ) || ( color_ver_ != rows_color_ver_ ); + QStringList *col_text = nullptr; + if (!dissect_color) { + col_text = col_text_cache_.object(fdata_->num); + } + if (col_text == nullptr || column >= col_text->count() || col_text->at(column).isNull()) { + dissect(cap_file, true, dissect_color); + col_text = col_text_cache_.object(fdata_->num); + } + + return col_text ? col_text->at(column) : QString(); +} + +void PacketListRecord::resetColumns(column_info *cinfo) +{ + invalidateAllRecords(); + + if (!cinfo) { + return; + } + + cinfo_column_.clear(); + int i, j; + for (i = 0, j = 0; i < cinfo->num_cols; i++) { + if (!col_based_on_frame_data(cinfo, i)) { + cinfo_column_[i] = j; + j++; + } + } +} + +void PacketListRecord::dissect(capture_file *cap_file, bool dissect_columns, bool dissect_color) +{ + // packet_list_store.c:packet_list_dissect_and_cache_record + epan_dissect_t edt; + column_info *cinfo = NULL; + gboolean create_proto_tree; + wtap_rec rec; /* Record metadata */ + Buffer buf; /* Record data */ + + if (!cap_file) { + return; + } + + if (dissect_columns) { + cinfo = &cap_file->cinfo; + } + + wtap_rec_init(&rec); + ws_buffer_init(&buf, 1514); + if (read_failed_) { + read_failed_ = !cf_read_record_no_alert(cap_file, fdata_, &rec, &buf); + } else { + read_failed_ = !cf_read_record(cap_file, fdata_, &rec, &buf); + } + + if (read_failed_) { + /* + * Error reading the record. + * + * Don't set the color filter for now (we might want + * to colorize it in some fashion to warn that the + * row couldn't be filled in or colorized), and + * set the columns to placeholder values, except + * for the Info column, where we'll put in an + * error message. + */ + if (dissect_columns) { + col_fill_in_error(cinfo, fdata_, FALSE, FALSE /* fill_fd_columns */); + + cacheColumnStrings(cinfo); + } + if (dissect_color) { + fdata_->color_filter = NULL; + colorized_ = true; + } + ws_buffer_free(&buf); + wtap_rec_cleanup(&rec); + return; /* error reading the record */ + } + + /* + * Determine whether we need to create a protocol tree. + * We do if: + * + * we're going to apply a color filter to this packet; + * + * we're need to fill in the columns and we have custom columns + * (which require field values, which currently requires that + * we build a protocol tree). + * + * XXX - field extractors? (Not done for GTK+....) + */ + create_proto_tree = ((dissect_color && color_filters_used()) || + (dissect_columns && (have_custom_cols(cinfo) || + have_field_extractors()))); + + epan_dissect_init(&edt, cap_file->epan, + create_proto_tree, + FALSE /* proto_tree_visible */); + + /* Re-color when the coloring rules are changed via the UI. */ + if (dissect_color) { + color_filters_prime_edt(&edt); + fdata_->need_colorize = 1; + } + if (dissect_columns) + col_custom_prime_edt(&edt, cinfo); + + /* + * XXX - need to catch an OutOfMemoryError exception and + * attempt to recover from it. + */ + epan_dissect_run(&edt, cap_file->cd_t, &rec, + frame_tvbuff_new_buffer(&cap_file->provider, fdata_, &buf), + fdata_, cinfo); + + if (dissect_columns) { + /* "Stringify" non frame_data vals */ + epan_dissect_fill_in_columns(&edt, FALSE, FALSE /* fill_fd_columns */); + cacheColumnStrings(cinfo); + } + + if (dissect_color) { + colorized_ = true; + color_ver_ = rows_color_ver_; + } + + struct conversation * conv = find_conversation_pinfo(&edt.pi, 0); + conv_index_ = ! conv ? 0 : conv->conv_index; + + epan_dissect_cleanup(&edt); + ws_buffer_free(&buf); + wtap_rec_cleanup(&rec); +} + +void PacketListRecord::cacheColumnStrings(column_info *cinfo) +{ + // packet_list_store.c:packet_list_change_record(PacketList *packet_list, PacketListRecord *record, gint col, column_info *cinfo) + if (!cinfo) { + return; + } + + QStringList *col_text = new QStringList(); + + lines_ = 1; + line_count_changed_ = false; + + for (int column = 0; column < cinfo->num_cols; ++column) { + int col_lines = 1; + + QString col_str; + int text_col = cinfo_column_.value(column, -1); + if (text_col < 0) { + col_fill_in_frame_data(fdata_, cinfo, column, FALSE); + } + + col_str = QString(get_column_text(cinfo, column)); + *col_text << col_str; + col_lines = static_cast<int>(col_str.count('\n')); + if (col_lines > lines_) { + lines_ = col_lines; + line_count_changed_ = true; + } + } + + col_text_cache_.insert(fdata_->num, col_text); +} diff --git a/ui/qt/models/packet_list_record.h b/ui/qt/models/packet_list_record.h new file mode 100644 index 00000000..47aa5621 --- /dev/null +++ b/ui/qt/models/packet_list_record.h @@ -0,0 +1,84 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef PACKET_LIST_RECORD_H +#define PACKET_LIST_RECORD_H + +#include <config.h> + +#include <glib.h> + +#include "cfile.h" + +#include <epan/column.h> +#include <epan/packet.h> + +#include <QByteArray> +#include <QCache> +#include <QList> +#include <QVariant> + +struct conversation; +struct _GStringChunk; + +class PacketListRecord +{ +public: + PacketListRecord(frame_data *frameData); + virtual ~PacketListRecord(); + + // Ensure that the record is colorized. + void ensureColorized(capture_file *cap_file); + // Return the string value for a column. Data is cached if possible. + const QString columnString(capture_file *cap_file, int column, bool colorized = false); + frame_data *frameData() const { return fdata_; } + // packet_list->col_to_text in gtk/packet_list_store.c + static int textColumn(int column) { return cinfo_column_.value(column, -1); } + bool colorized() { return colorized_ && (color_ver_ == rows_color_ver_); } + unsigned int conversation() { return conv_index_; } + + int columnTextSize(const char *str); + + void invalidateColorized() { colorized_ = false; } + void invalidateRecord() { col_text_cache_.remove(fdata_->num); } + static void invalidateAllRecords() { col_text_cache_.clear(); } + /* In Qt 6, QCache maxCost is a qsizetype, but the QAbstractItemModel + * number of rows is still an int, so we're limited to INT_MAX anyway. + */ + static void setMaxCache(int cost) { col_text_cache_.setMaxCost(cost); } + static void resetColumns(column_info *cinfo); + static void resetColorization() { rows_color_ver_++; } + + inline int lineCount() { return lines_; } + inline int lineCountChanged() { return line_count_changed_; } + +private: + /** The column text for some columns */ + static QCache<guint32, QStringList> col_text_cache_; + + frame_data *fdata_; + int lines_; + bool line_count_changed_; + static QMap<int, int> cinfo_column_; + + /** Has this record been colorized? */ + static unsigned int rows_color_ver_; + unsigned int color_ver_; + bool colorized_; + + /** Conversation. Used by RelatedPacketDelegate */ + unsigned int conv_index_; + + bool read_failed_; + + void dissect(capture_file *cap_file, bool dissect_columns, bool dissect_color = false); + void cacheColumnStrings(column_info *cinfo); +}; + +#endif // PACKET_LIST_RECORD_H diff --git a/ui/qt/models/path_selection_delegate.cpp b/ui/qt/models/path_selection_delegate.cpp new file mode 100644 index 00000000..df614ef3 --- /dev/null +++ b/ui/qt/models/path_selection_delegate.cpp @@ -0,0 +1,63 @@ +/* path_chooser_delegate.cpp + * Delegate to select a file path for a treeview entry + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/path_selection_delegate.h> +#include <ui/qt/widgets/path_selection_edit.h> + +PathSelectionDelegate::PathSelectionDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + +QWidget* PathSelectionDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const +{ + PathSelectionEdit * editor = new PathSelectionEdit(tr("Open a pipe"), QString(), true, parent); + + connect(editor, &PathSelectionEdit::pathChanged, this, &PathSelectionDelegate::pathHasChanged); + + return editor; +} + +void PathSelectionDelegate::pathHasChanged(QString) +{ + PathSelectionEdit * editor = qobject_cast<PathSelectionEdit *>(sender()); + if (editor) + emit commitData(editor); +} + +void PathSelectionDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const +{ + editor->setGeometry(option.rect); +} + +void PathSelectionDelegate::setEditorData(QWidget *editor, const QModelIndex &idx) const +{ + if (idx.isValid() && qobject_cast<PathSelectionEdit *>(editor) != nullptr) + { + PathSelectionEdit * edit = qobject_cast<PathSelectionEdit *>(editor); + edit->setPath(idx.data().toString()); + } + else + QStyledItemDelegate::setEditorData(editor, idx); +} + +void PathSelectionDelegate::setModelData(QWidget *editor, QAbstractItemModel * model, const QModelIndex &idx) const +{ + if (idx.isValid() && qobject_cast<PathSelectionEdit *>(editor) != nullptr) + { + PathSelectionEdit * edit = qobject_cast<PathSelectionEdit *>(editor); + model->setData(idx, edit->path()); + } + else + { + QStyledItemDelegate::setModelData(editor, model, idx); + } +} + diff --git a/ui/qt/models/path_selection_delegate.h b/ui/qt/models/path_selection_delegate.h new file mode 100644 index 00000000..6d33f116 --- /dev/null +++ b/ui/qt/models/path_selection_delegate.h @@ -0,0 +1,35 @@ +/** @file + * + * Delegate to select a file path for a treeview entry + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef PATH_SELECTION_DELEGATE_H_ +#define PATH_SELECTION_DELEGATE_H_ + +#include <QStyledItemDelegate> + +class PathSelectionDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + PathSelectionDelegate(QObject *parent = 0); + +protected: + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &idx) const override; + void updateEditorGeometry (QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & idx) const override; + void setEditorData(QWidget *editor, const QModelIndex &idx) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &idx) const override; + +protected slots: + void pathHasChanged(QString newPath); + +}; + +#endif /* PATH_SELECTION_DELEGATE_H_ */ diff --git a/ui/qt/models/percent_bar_delegate.cpp b/ui/qt/models/percent_bar_delegate.cpp new file mode 100644 index 00000000..091c5fb0 --- /dev/null +++ b/ui/qt/models/percent_bar_delegate.cpp @@ -0,0 +1,91 @@ +/* percent_bar_delegate.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/percent_bar_delegate.h> + +#include <ui/qt/utils/color_utils.h> + +#include <QApplication> +#include <QPainter> + +static const int bar_em_width_ = 8; +static const double bar_blend_ = 0.15; + +void PercentBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItem option_vi = option; + QStyledItemDelegate::initStyleOption(&option_vi, index); + + // Paint our rect with no text using the current style, then draw our + // bar and text over it. + QStyledItemDelegate::paint(painter, option, index); + + bool ok = false; + double value = index.data(Qt::UserRole).toDouble(&ok); + + if (!ok || !index.data(Qt::DisplayRole).toString().isEmpty()) { + // We don't have a valid value or the item has visible text. + return; + } + + // If our value is out range our caller has a bug. Clamp the graph and + // Print the numeric value so that the bug is obvious. + QString pct_str = QString::number(value, 'f', 1); + if (value < 0) { + value = 0; + } + if (value > 100.0) { + value = 100.0; + } + + if (QApplication::style()->objectName().contains("vista")) { + // QWindowsVistaStyle::drawControl does this internally. Unfortunately there + // doesn't appear to be a more general way to do this. + option_vi.palette.setColor(QPalette::All, QPalette::HighlightedText, + option_vi.palette.color(QPalette::Active, QPalette::Text)); + } + + QPalette::ColorGroup cg = option_vi.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + QColor text_color = option_vi.palette.color(cg, QPalette::Text); + QColor bar_color = ColorUtils::alphaBlend(option_vi.palette.windowText(), + option_vi.palette.window(), bar_blend_); + + if (cg == QPalette::Normal && !(option_vi.state & QStyle::State_Active)) + cg = QPalette::Inactive; + if (option_vi.state & QStyle::State_Selected) { + text_color = option_vi.palette.color(cg, QPalette::HighlightedText); + bar_color = ColorUtils::alphaBlend(option_vi.palette.color(cg, QPalette::Window), + option_vi.palette.color(cg, QPalette::Highlight), + bar_blend_); + } + + painter->save(); + int border_radius = 3; // We use 3 px elsewhere, e.g. filter combos. + QRect pct_rect = option.rect; + pct_rect.adjust(1, 1, -1, -1); + pct_rect.setWidth(((pct_rect.width() * value) / 100.0) + 0.5); + painter->setPen(Qt::NoPen); + painter->setBrush(bar_color); + painter->drawRoundedRect(pct_rect, border_radius, border_radius); + painter->restore(); + + painter->save(); + painter->setPen(text_color); + painter->drawText(option.rect, Qt::AlignCenter, pct_str); + painter->restore(); +} + +QSize PercentBarDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + return QSize(option.fontMetrics.height() * bar_em_width_, + QStyledItemDelegate::sizeHint(option, index).height()); +} diff --git a/ui/qt/models/percent_bar_delegate.h b/ui/qt/models/percent_bar_delegate.h new file mode 100644 index 00000000..4f0d53df --- /dev/null +++ b/ui/qt/models/percent_bar_delegate.h @@ -0,0 +1,52 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef PERCENTBARDELEGATE_H +#define PERCENTBARDELEGATE_H + +/* + * @file Percent bar delegate. + * + * QStyledItemDelegate subclass that will draw a percentage value and a + * single-item bar chart for the specified value. + * + * This is intended to be used in QTreeWidgets to show percentage values. + * To use it, first call setItemDelegate: + * + * myTreeWidget()->setItemDelegateForColumn(col_pct_, new PercentBarDelegate()); + * + * Then, for each QTreeWidgetItem, set a double value using setData: + * + * setData(col_pct_, Qt::UserRole, QVariant::fromValue<double>(packets_ * 100.0 / num_packets)); + * + * If the item data cannot be converted to a valid double value or if its + * text string is non-empty then it will be rendered normally (i.e. the + * percent text and bar will not be drawn). This lets you mix normal and + * percent bar rendering between rows. + */ + +#include <QStyledItemDelegate> + +class PercentBarDelegate : public QStyledItemDelegate +{ +public: + PercentBarDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) { } + + // Make sure QStyledItemDelegate::paint doesn't draw any text. + virtual QString displayText(const QVariant &, const QLocale &) const { return QString(); } + +protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + +}; + +#endif // PERCENTBARDELEGATE_H diff --git a/ui/qt/models/pref_delegate.cpp b/ui/qt/models/pref_delegate.cpp new file mode 100644 index 00000000..e33bb13f --- /dev/null +++ b/ui/qt/models/pref_delegate.cpp @@ -0,0 +1,85 @@ +/* pref_delegate.cpp + * Delegates for editing prefereneces. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/pref_delegate.h> +#include <epan/prefs-int.h> + +#include <ui/qt/manager/preference_manager.h> +#include <ui/qt/manager/wireshark_preference.h> + +AdvancedPrefDelegate::AdvancedPrefDelegate(QObject *parent) : QStyledItemDelegate(parent) +{ +} + +PrefsItem* AdvancedPrefDelegate::indexToPref(const QModelIndex &index) const +{ + const QVariant v = index.model()->data(index, Qt::UserRole); + return VariantPointer<PrefsItem>::asPtr(v); +} + +QWidget *AdvancedPrefDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + PrefsItem* pref; + QString filename; + + switch(index.column()) + { + case AdvancedPrefsModel::colName: + case AdvancedPrefsModel::colStatus: + case AdvancedPrefsModel::colType: + //If user clicks on any of these columns, reset preference back to default + //There is no need to launch an editor + const_cast<QAbstractItemModel*>(index.model())->setData(index, QVariant(), Qt::EditRole); + break; + case AdvancedPrefsModel::colValue: + pref = indexToPref(index); + WiresharkPreference * wspref = PreferenceManager::instance()->getPreference(pref); + if (wspref) { + QWidget *editor = wspref->editor(parent, option, index); + if (editor) { + editor->setAutoFillBackground(true); + } + return editor; + } + break; + } + + return Q_NULLPTR; +} + +void AdvancedPrefDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + PrefsItem* pref = indexToPref(index); + + WiresharkPreference * wspref = PreferenceManager::instance()->getPreference(pref); + if (wspref) + { + wspref->setData(editor, index); + return; + } + + Q_ASSERT(FALSE); +} + +void AdvancedPrefDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + PrefsItem* pref = indexToPref(index); + + WiresharkPreference * wspref = PreferenceManager::instance()->getPreference(pref); + if (wspref) + { + wspref->setModelData(editor, model, index); + return; + } + + Q_ASSERT(FALSE); +} diff --git a/ui/qt/models/pref_delegate.h b/ui/qt/models/pref_delegate.h new file mode 100644 index 00000000..91054e9a --- /dev/null +++ b/ui/qt/models/pref_delegate.h @@ -0,0 +1,37 @@ +/** @file + * + * Delegates for editing prefereneces. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef PREF_DELEGATE_H +#define PREF_DELEGATE_H + +#include <config.h> + +#include <ui/qt/models/pref_models.h> + +#include <QStyledItemDelegate> +#include <QModelIndex> + +class AdvancedPrefDelegate : public QStyledItemDelegate +{ +public: + AdvancedPrefDelegate(QObject *parent = 0); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const; + +private: + PrefsItem* indexToPref(const QModelIndex &index) const; +}; + +#endif // PREF_DELEGATE_H diff --git a/ui/qt/models/pref_models.cpp b/ui/qt/models/pref_models.cpp new file mode 100644 index 00000000..b86230f0 --- /dev/null +++ b/ui/qt/models/pref_models.cpp @@ -0,0 +1,774 @@ +/* pref_models.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/pref_models.h> +#include <ui/qt/utils/qt_ui_utils.h> +#include <epan/prefs-int.h> + +#ifdef HAVE_LIBPCAP +#ifdef _WIN32 +#include "capture/capture-wpcap.h" +#endif /* _WIN32 */ +#endif /* HAVE_LIBPCAP */ + +#include <QFont> +#include <QColor> +#include <QRegularExpression> +#include <QApplication> + +// XXX Should we move this to ui/preference_utils? +static GHashTable * pref_ptr_to_pref_ = NULL; +pref_t *prefFromPrefPtr(void *pref_ptr) +{ + return (pref_t *)g_hash_table_lookup(pref_ptr_to_pref_, (gpointer) pref_ptr); +} + +static void prefInsertPrefPtr(void * pref_ptr, pref_t * pref) +{ + if (! pref_ptr_to_pref_) + pref_ptr_to_pref_ = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL); + + gpointer key = (gpointer) pref_ptr; + gpointer val = (gpointer) pref; + + /* Already existing entries will be ignored */ + if ((void *)g_hash_table_lookup(pref_ptr_to_pref_, key) == NULL) + g_hash_table_insert(pref_ptr_to_pref_, key, val); +} + +PrefsItem::PrefsItem(module_t *module, pref_t *pref, PrefsItem* parent) + : ModelHelperTreeItem<PrefsItem>(parent), + pref_(pref), + module_(module), + name_(module->name ? module->name : module->parent->name), + changed_(false) +{ + if (pref_ != NULL) { + name_ += QString(".%1").arg(prefs_get_name(pref_)); + } +} + +PrefsItem::PrefsItem(const QString name, PrefsItem* parent) + : ModelHelperTreeItem<PrefsItem>(parent), + pref_(NULL), + module_(NULL), + name_(name), + changed_(false) +{ + +} + +PrefsItem::~PrefsItem() +{ +} + +int PrefsItem::getPrefType() const +{ + if (pref_ == NULL) + return 0; + + return prefs_get_type(pref_); +} + +bool PrefsItem::isPrefDefault() const +{ + if (pref_ == NULL) + return true; + + if (changed_ == false) + return prefs_pref_is_default(pref_) ? true : false; + + return false; +} + +QString PrefsItem::getPrefTypeName() const +{ + if (pref_ == NULL) + return ""; + + return QString(prefs_pref_type_name(pref_)); +} + +QString PrefsItem::getModuleName() const +{ + if (module_ == NULL) + return name_; + + return QString(module_->name); +} + +QString PrefsItem::getModuleTitle() const +{ + if ((module_ == NULL) && (pref_ == NULL)) + return name_; + + Q_ASSERT(module_); + + return QString(module_->title); +} + +void PrefsItem::setChanged(bool changed) +{ + changed_ = changed; +} + +PrefsModel::PrefsModel(QObject *parent) : + QAbstractItemModel(parent), + root_(new PrefsItem(QString("ROOT"), NULL)) +{ + populate(); +} + +PrefsModel::~PrefsModel() +{ + delete root_; +} + +int PrefsModel::rowCount(const QModelIndex &parent) const +{ + PrefsItem *parent_item; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<PrefsItem*>(parent.internalPointer()); + + if (parent_item == NULL) + return 0; + + return static_cast<int>(parent_item->childCount()); +} + +int PrefsModel::columnCount(const QModelIndex&) const +{ + return colLast; +} + + +QModelIndex PrefsModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + PrefsItem* item = static_cast<PrefsItem*>(index.internalPointer()); + if (item != NULL) { + PrefsItem* parent_item = item->parentItem(); + if (parent_item != NULL) { + if (parent_item == root_) + return QModelIndex(); + + return createIndex(parent_item->row(), 0, parent_item); + } + } + + return QModelIndex(); +} + +QModelIndex PrefsModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + PrefsItem *parent_item, *child_item; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<PrefsItem*>(parent.internalPointer()); + + Q_ASSERT(parent_item); + + child_item = parent_item->child(row); + if (child_item) { + return createIndex(row, column, child_item); + } + + return QModelIndex(); +} + +QVariant PrefsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::UserRole)) + return QVariant(); + + PrefsItem* item = static_cast<PrefsItem*>(index.internalPointer()); + if (item == NULL) + return QVariant(); + + if (role == Qt::UserRole) + return VariantPointer<PrefsItem>::asQVariant(item); + + switch ((enum PrefsModelColumn)index.column()) { + case colName: + return item->getName(); + + case colStatus: + if (item->getPrefType() == PREF_UAT || item->getPrefType() == PREF_CUSTOM) + return QObject::tr("Unknown"); + + if (item->isPrefDefault()) + return QObject::tr("Default"); + + return QObject::tr("Changed"); + case colType: + return item->getPrefTypeName(); + case colValue: + if (item->getPref() == NULL) + return QVariant(); + + return QString(gchar_free_to_qstring(prefs_pref_to_str(item->getPref(), pref_stashed)).remove(QRegularExpression("\n\t"))); + default: + break; + } + + return QVariant(); +} + +static guint +fill_prefs(module_t *module, gpointer root_ptr) +{ + PrefsItem* root_item = static_cast<PrefsItem*>(root_ptr); + + if ((module == NULL) || (root_item == NULL)) + return 1; + + if (module->numprefs < 1 && !prefs_module_has_submodules(module)) + return 0; + + PrefsItem* module_item = new PrefsItem(module, NULL, root_item); + root_item->prependChild(module_item); + + for (GList *pref_l = module->prefs; pref_l && pref_l->data; pref_l = gxx_list_next(pref_l)) { + pref_t *pref = gxx_list_data(pref_t *, pref_l); + + if (prefs_get_type(pref) == PREF_OBSOLETE || prefs_get_type(pref) == PREF_STATIC_TEXT) + continue; + + const char *type_name = prefs_pref_type_name(pref); + if (!type_name) + continue; + + pref_stash(pref, NULL); + + PrefsItem* item = new PrefsItem(module, pref, module_item); + module_item->prependChild(item); + + // .uat is a void * so it wins the "useful key value" prize. + if (prefs_get_uat_value(pref)) { + prefInsertPrefPtr(prefs_get_uat_value(pref), pref); + } + } + + if (prefs_module_has_submodules(module)) + return prefs_modules_foreach_submodules(module, fill_prefs, module_item); + + return 0; +} + +void PrefsModel::populate() +{ + prefs_modules_foreach_submodules(NULL, fill_prefs, (gpointer)root_); + + //Add the "specially handled" preferences + PrefsItem *appearance_item, *appearance_subitem, *special_item; + + appearance_item = new PrefsItem(typeToString(PrefsModel::Appearance), root_); + root_->prependChild(appearance_item); + + appearance_subitem = new PrefsItem(typeToString(PrefsModel::Layout), appearance_item); + appearance_item->prependChild(appearance_subitem); + appearance_subitem = new PrefsItem(typeToString(PrefsModel::Columns), appearance_item); + appearance_item->prependChild(appearance_subitem); + appearance_subitem = new PrefsItem(typeToString(PrefsModel::FontAndColors), appearance_item); + appearance_item->prependChild(appearance_subitem); + + special_item = new PrefsItem(typeToString(PrefsModel::Capture), root_); + root_->prependChild(special_item); + special_item = new PrefsItem(typeToString(PrefsModel::Expert), root_); + root_->prependChild(special_item); + special_item = new PrefsItem(typeToString(PrefsModel::FilterButtons), root_); + root_->prependChild(special_item); +#ifdef HAVE_LIBGNUTLS + special_item = new PrefsItem(typeToString(PrefsModel::RSAKeys), root_); + root_->prependChild(special_item); +#endif + special_item = new PrefsItem(typeToString(PrefsModel::Advanced), root_); + root_->prependChild(special_item); +} + +QString PrefsModel::typeToString(int type) +{ + QString typeStr; + + switch(type) + { + case Advanced: typeStr = tr("Advanced"); break; + case Appearance: typeStr = tr("Appearance"); break; + case Layout: typeStr = tr("Layout"); break; + case Columns: typeStr = tr("Columns"); break; + case FontAndColors: typeStr = tr("Font and Colors"); break; + case Capture: typeStr = tr("Capture"); break; + case Expert: typeStr = tr("Expert"); break; + case FilterButtons: typeStr = tr("Filter Buttons"); break; + case RSAKeys: typeStr = tr("RSA Keys"); break; + } + + return typeStr; +} + +AdvancedPrefsModel::AdvancedPrefsModel(QObject * parent) +: QSortFilterProxyModel(parent), +filter_(), +show_changed_values_(false), +passwordChar_(QApplication::style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter)) +{ +} + +QVariant AdvancedPrefsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + + switch (section) { + case colName: + return tr("Name"); + case colStatus: + return tr("Status"); + case colType: + return tr("Type"); + case colValue: + return tr("Value"); + default: + break; + } + } + return QVariant(); +} + +QVariant AdvancedPrefsModel::data(const QModelIndex &dataindex, int role) const +{ + if (!dataindex.isValid()) + return QVariant(); + + QModelIndex modelIndex = mapToSource(dataindex); + + PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer()); + if (item == NULL) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + switch ((AdvancedPrefsModelColumn)dataindex.column()) + { + case colName: + if (item->getPref() == NULL) + return item->getModule()->title; + + return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colName, modelIndex.parent()), role); + case colStatus: + if (item->getPref() == NULL) + return QVariant(); + + return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colStatus, modelIndex.parent()), role); + case colType: + if (item->getPref() == NULL) + return QVariant(); + + return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colType, modelIndex.parent()), role); + case colValue: + if (item->getPref() == NULL) + return QVariant(); + + if (PREF_PASSWORD == item->getPrefType()) + { + return QString(sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colValue, modelIndex.parent()), role).toString().size(), passwordChar_); + } else { + return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colValue, modelIndex.parent()), role); + } + default: + break; + } + break; + case Qt::ToolTipRole: + switch ((AdvancedPrefsModelColumn)dataindex.column()) + { + case colName: + if (item->getPref() == NULL) + return QString("<span>%1</span>").arg(item->getModule()->description); + + return QString("<span>%1</span>").arg(prefs_get_description(item->getPref())); + case colStatus: + if (item->getPref() == NULL) + return QVariant(); + + return QObject::tr("Has this preference been changed?"); + case colType: + if (item->getPref() == NULL) { + return QVariant(); + } else { + QString type_desc = gchar_free_to_qstring(prefs_pref_type_description(item->getPref())); + return QString("<span>%1</span>").arg(type_desc); + } + break; + case colValue: + if (item->getPref() == NULL) { + return QVariant(); + } else { + QString default_value = gchar_free_to_qstring(prefs_pref_to_str(item->getPref(), pref_stashed)); + return QString("<span>%1</span>").arg( + default_value.isEmpty() ? default_value : QObject::tr("Default value is empty")); + } + default: + break; + } + break; + case Qt::FontRole: + if (item->getPref() == NULL) + return QVariant(); + + if (!item->isPrefDefault() && + /* UATs and custom preferences are "unknown", that shouldn't mean that they are always bolded */ + item->getPrefType() != PREF_UAT && item->getPrefType() != PREF_CUSTOM) { + QFont font; + font.setBold(true); + return font; + } + break; + case Qt::UserRole: + return sourceModel()->data(modelIndex, role); + default: + break; + } + + return QVariant(); +} + +bool AdvancedPrefsModel::setData(const QModelIndex &dataindex, const QVariant &value, int role) +{ + if ((!dataindex.isValid()) || (role != Qt::EditRole)) + return false; + + QModelIndex modelIndex = mapToSource(dataindex); + + PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer()); + if (item == NULL) + return false; + + if (value.isNull()) { + //reset preference to default + reset_stashed_pref(item->getPref()); + item->setChanged(false); + } else { + item->setChanged(true); + switch (item->getPrefType()) + { + case PREF_DECODE_AS_UINT: + case PREF_UINT: + { + bool ok; + guint new_val = value.toString().toUInt(&ok, prefs_get_uint_base(item->getPref())); + + if (ok) + prefs_set_uint_value(item->getPref(), new_val, pref_stashed); + } + break; + case PREF_BOOL: + prefs_invert_bool_value(item->getPref(), pref_stashed); + break; + case PREF_ENUM: + prefs_set_enum_value(item->getPref(), value.toInt(), pref_stashed); + break; + case PREF_STRING: + prefs_set_string_value(item->getPref(), value.toString().toStdString().c_str(), pref_stashed); + break; + case PREF_PASSWORD: + prefs_set_password_value(item->getPref(), value.toString().toStdString().c_str(), pref_stashed); + break; + case PREF_DECODE_AS_RANGE: + case PREF_RANGE: + prefs_set_stashed_range_value(item->getPref(), value.toString().toUtf8().constData()); + break; + case PREF_SAVE_FILENAME: + case PREF_OPEN_FILENAME: + case PREF_DIRNAME: + prefs_set_string_value(item->getPref(), value.toString().toStdString().c_str(), pref_stashed); + break; + case PREF_COLOR: + { + QColor qc(value.toString()); + color_t color; + + color.red = qc.red() << 8 | qc.red(); + color.green = qc.green() << 8 | qc.green(); + color.blue = qc.blue() << 8 | qc.blue(); + + prefs_set_color_value(item->getPref(), color, pref_stashed); + break; + } + case PREF_CUSTOM: + prefs_set_custom_value(item->getPref(), value.toString().toStdString().c_str(), pref_stashed); + break; + } + } + + QVector<int> roles; + roles << role; + + // The status field may change as well as the value, so mark them for update + emit dataChanged(index(dataindex.row(), 0, dataindex.parent()), + index(dataindex.row(), columnCount() - 1, dataindex.parent()), + roles); + + return true; +} + +Qt::ItemFlags AdvancedPrefsModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemFlags(); + + QModelIndex modelIndex = mapToSource(index); + + PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer()); + if (item == NULL) + return Qt::ItemFlags(); + + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (item->getPref() == NULL) { + /* Base modules aren't changable */ + flags &= ~(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + } else { + flags |= Qt::ItemIsEditable; + } + + return flags; +} + + +int AdvancedPrefsModel::columnCount(const QModelIndex&) const +{ + return colLast; +} + +void AdvancedPrefsModel::setFirstColumnSpanned(QTreeView* tree, const QModelIndex& mIndex) +{ + int childCount, row; + PrefsItem* item; + if (mIndex.isValid()) { + item = VariantPointer<PrefsItem>::asPtr(data(mIndex, Qt::UserRole)); + if (item != NULL) { + childCount = item->childCount(); + if (childCount > 0) { + tree->setFirstColumnSpanned(mIndex.row(), mIndex.parent(), true); + for (row = 0; row < childCount; row++) { + setFirstColumnSpanned(tree, index(row, 0, mIndex)); + } + } + } + } else { + for (row = 0; row < rowCount(); row++) { + setFirstColumnSpanned(tree, index(row, 0)); + } + } +} + +bool AdvancedPrefsModel::filterAcceptItem(PrefsItem& item) const +{ + if (filter_.isEmpty() && !show_changed_values_) + return true; + + QString name, tooltip; + if (item.getPref() == NULL) { + name = item.getModule()->title; + tooltip = item.getModule()->description; + } else { + name = QString(item.getModule()->name ? item.getModule()->name : item.getModule()->parent->name); + name += QString(".%1").arg(prefs_get_name(item.getPref())); + tooltip = prefs_get_description(item.getPref()); + } + + if (show_changed_values_ && item.getPref()) { + // UATs and custom preferences are "unknown", do not show when show_changed_only. + if (item.isPrefDefault() || item.getPrefType() == PREF_UAT || item.getPrefType() == PREF_CUSTOM) { + return false; + } else if (filter_.isEmpty()) { + return true; + } + } + + // Do not match module title or description when having show_changed_only. + if (!(filter_.isEmpty() || (show_changed_values_ && !item.getPref())) && + (name.contains(filter_, Qt::CaseInsensitive) || tooltip.contains(filter_, Qt::CaseInsensitive))) + return true; + + PrefsItem *child_item; + for (int child_row = 0; child_row < item.childCount(); child_row++) + { + child_item = item.child(child_row); + if ((child_item != NULL) && (filterAcceptItem(*child_item))) + return true; + } + + return false; +} + +bool AdvancedPrefsModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex nameIdx = sourceModel()->index(sourceRow, PrefsModel::colName, sourceParent); + PrefsItem* item = static_cast<PrefsItem*>(nameIdx.internalPointer()); + if (item == NULL) + return true; + + //filter out the "special" preferences + if ((item->getModule() == NULL) && (item->getPref() == NULL)) + return false; + + if (filterAcceptItem(*item)) + return true; + + return false; +} + +void AdvancedPrefsModel::setFilter(const QString& filter) +{ + filter_ = filter; + invalidateFilter(); +} + +void AdvancedPrefsModel::setShowChangedValues(bool show_changed_values) +{ + show_changed_values_ = show_changed_values; + invalidateFilter(); +} + + + +ModulePrefsModel::ModulePrefsModel(QObject* parent) + : QSortFilterProxyModel(parent) + , advancedPrefName_(PrefsModel::typeToString(PrefsModel::Advanced)) +{ +} + +QVariant ModulePrefsModel::data(const QModelIndex &dataindex, int role) const +{ + if (!dataindex.isValid()) + return QVariant(); + + QModelIndex modelIndex = mapToSource(dataindex); + + PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer()); + if (item == NULL) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + switch ((ModulePrefsModelColumn)dataindex.column()) + { + case colName: + return item->getModuleTitle(); + default: + break; + } + break; + case Qt::UserRole: + return sourceModel()->data(modelIndex, role); + case ModuleName: + return item->getModuleName(); + default: + break; + } + + return QVariant(); +} +Qt::ItemFlags ModulePrefsModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemFlags(); + + bool disable_capture = true; +#ifdef HAVE_LIBPCAP +#ifdef _WIN32 + /* Is WPcap loaded? */ + if (has_wpcap) { +#endif /* _WIN32 */ + disable_capture = false; +#ifdef _WIN32 + } +#endif /* _WIN32 */ +#endif /* HAVE_LIBPCAP */ + + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (disable_capture) { + QModelIndex modelIndex = mapToSource(index); + + PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer()); + if (item == NULL) + return flags; + + if (item->getName().compare(PrefsModel::typeToString(PrefsModel::Capture)) == 0) { + flags &= (~Qt::ItemIsEnabled); + } + } + + return flags; +} + +int ModulePrefsModel::columnCount(const QModelIndex&) const +{ + return colLast; +} + +bool ModulePrefsModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + PrefsItem* left_item = static_cast<PrefsItem*>(source_left.internalPointer()); + PrefsItem* right_item = static_cast<PrefsItem*>(source_right.internalPointer()); + + if ((left_item != NULL) && (right_item != NULL)) { + QString left_name = left_item->getModuleTitle(), + right_name = right_item->getModuleTitle(); + + //Force "Advanced" preferences to be at bottom of model + if (source_left.isValid() && !source_left.parent().isValid() && + source_right.isValid() && !source_right.parent().isValid()) { + if (left_name.compare(advancedPrefName_) == 0) { + return false; + } + if (right_name.compare(advancedPrefName_) == 0) { + return true; + } + } + + if (left_name.compare(right_name, Qt::CaseInsensitive) < 0) + return true; + } + + return false; +} + +bool ModulePrefsModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex nameIdx = sourceModel()->index(sourceRow, PrefsModel::colName, sourceParent); + PrefsItem* item = static_cast<PrefsItem*>(nameIdx.internalPointer()); + if (item == NULL) + return true; + + if (item->getPref() != NULL) + return false; + + if (item->getModule() != NULL) { + if (!item->getModule()->use_gui) { + return false; + } + } + + return true; +} diff --git a/ui/qt/models/pref_models.h b/ui/qt/models/pref_models.h new file mode 100644 index 00000000..775da0d0 --- /dev/null +++ b/ui/qt/models/pref_models.h @@ -0,0 +1,165 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef PREF_MODELS_H +#define PREF_MODELS_H + +#include <config.h> + +#include <ui/qt/models/tree_model_helpers.h> + +#include <epan/prefs.h> + +#include <QSortFilterProxyModel> +#include <QTreeView> + +class PrefsItem : public ModelHelperTreeItem<PrefsItem> +{ +public: + PrefsItem(module_t *module, pref_t *pref, PrefsItem* parent); + PrefsItem(const QString name, PrefsItem* parent); + virtual ~PrefsItem(); + + QString getName() const {return name_;} + pref_t* getPref() const {return pref_;} + int getPrefType() const; + bool isPrefDefault() const; + QString getPrefTypeName() const; + module_t* getModule() const {return module_;} + QString getModuleName() const; + QString getModuleTitle() const; + void setChanged(bool changed = true); + +private: + pref_t *pref_; + module_t *module_; + QString name_; + //set to true if changed during module manipulation + //Used to determine proper "default" for comparison + bool changed_; +}; + + +class PrefsModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit PrefsModel(QObject * parent = Q_NULLPTR); + virtual ~PrefsModel(); + + enum PrefsModelType { + Advanced = Qt::UserRole, + Appearance, + Layout, + Columns, + FontAndColors, + Capture, + Expert, + FilterButtons, + RSAKeys + }; + + enum PrefsModelColumn { + colName = 0, + colStatus, + colType, + colValue, + colLast + }; + + QModelIndex index(int row, int column, + const QModelIndex & = QModelIndex()) const; + QModelIndex parent(const QModelIndex &) const; + QVariant data(const QModelIndex &index, int role) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + static QString typeToString(int type); + +private: + void populate(); + + PrefsItem* root_; +}; + +class AdvancedPrefsModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + explicit AdvancedPrefsModel(QObject * parent = Q_NULLPTR); + + enum AdvancedPrefsModelColumn { + colName = 0, + colStatus, + colType, + colValue, + colLast + }; + + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + + void setFilter(const QString& filter); + void setShowChangedValues(bool show_changed_values); + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + //Keep the internals of model hidden from tree + void setFirstColumnSpanned(QTreeView* tree, const QModelIndex &index = QModelIndex()); + +protected: + bool filterAcceptItem(PrefsItem& item) const; + +private: + + QString filter_; + bool show_changed_values_; + const QChar passwordChar_; +}; + +class ModulePrefsModel : public QSortFilterProxyModel +{ +public: + + explicit ModulePrefsModel(QObject * parent = Q_NULLPTR); + + enum ModulePrefsModelColumn { + colName = 0, + colLast + }; + + enum ModulePrefsRoles { + ModuleName = Qt::UserRole + 1 + }; + + QVariant data(const QModelIndex &index, int role) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + +protected: + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; + +private: + //cache of the translated "Advanced" preference name + QString advancedPrefName_; +}; + +extern pref_t *prefFromPrefPtr(void *pref_ptr); + +#endif // PREF_MODELS_H diff --git a/ui/qt/models/profile_model.cpp b/ui/qt/models/profile_model.cpp new file mode 100644 index 00000000..206ef389 --- /dev/null +++ b/ui/qt/models/profile_model.cpp @@ -0,0 +1,1299 @@ +/* profile_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <errno.h> + +#include "glib.h" +#include "ui/profile.h" +#include "ui/recent.h" +#include "wsutil/filesystem.h" +#include "epan/prefs.h" + +#include <ui/qt/models/profile_model.h> + +#include <ui/qt/utils/color_utils.h> +#include <ui/qt/utils/qt_ui_utils.h> +#include <ui/qt/utils/wireshark_zip_helper.h> + +#include <QDir> +#include <QFont> +#include <QTemporaryDir> +#include <QRegularExpression> + +Q_LOGGING_CATEGORY(profileLogger, "wireshark.profiles") + +ProfileSortModel::ProfileSortModel(QObject * parent): + QSortFilterProxyModel (parent), + ft_(ProfileSortModel::AllProfiles), + ftext_(QString()) +{} + +bool ProfileSortModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + QModelIndex left = source_left; + if (source_left.column() != ProfileModel::COL_NAME) + left = source_left.sibling(source_left.row(), ProfileModel::COL_NAME); + + QModelIndex right = source_right; + if (source_right.column() != ProfileModel::COL_NAME) + right = source_right.sibling(source_right.row(), ProfileModel::COL_NAME); + + bool igL = left.data(ProfileModel::DATA_IS_GLOBAL).toBool(); + bool igR = right.data(ProfileModel::DATA_IS_GLOBAL).toBool(); + + if (left.data(ProfileModel::DATA_STATUS).toInt() == PROF_STAT_DEFAULT) + igL = true; + if (right.data(ProfileModel::DATA_STATUS).toInt() == PROF_STAT_DEFAULT) + igR = true; + + if (igL && ! igR) + return true; + else if (! igL && igR) + return false; + else if (igL && igR) + { + if (left.data(ProfileModel::DATA_STATUS) == PROF_STAT_DEFAULT) + return true; + } + + if (left.data().toString().compare(right.data().toString()) <= 0) + return true; + + return false; +} + +void ProfileSortModel::setFilterType(FilterType ft) +{ + ft_ = ft; + invalidateFilter(); +} + +void ProfileSortModel::setFilterString(QString txt) +{ + ftext_ = ! txt.isEmpty() ? txt : ""; + invalidateFilter(); +} + +QStringList ProfileSortModel::filterTypes() +{ + QMap<int, QString> filter_types_; + filter_types_.insert(ProfileSortModel::AllProfiles, tr("All profiles")); + filter_types_.insert(ProfileSortModel::PersonalProfiles, tr("Personal profiles")); + filter_types_.insert(ProfileSortModel::GlobalProfiles, tr("Global profiles")); + + return filter_types_.values(); +} + +bool ProfileSortModel::filterAcceptsRow(int source_row, const QModelIndex &) const +{ + bool accept = true; + QModelIndex idx = sourceModel()->index(source_row, ProfileModel::COL_NAME); + + if (ft_ != ProfileSortModel::AllProfiles) + { + bool gl = idx.data(ProfileModel::DATA_IS_GLOBAL).toBool(); + if (ft_ == ProfileSortModel::PersonalProfiles && gl) + accept = false; + else if (ft_ == ProfileSortModel::GlobalProfiles && ! gl) + accept = false; + } + + if (ftext_.length() > 0) + { + QString name = idx.data().toString(); + if (! name.contains(ftext_, Qt::CaseInsensitive)) + accept = false; + } + + return accept; +} + +ProfileModel::ProfileModel(QObject * parent) : + QAbstractTableModel(parent) +{ + /* Store preset profile name */ + set_profile_ = get_profile_name(); + + reset_default_ = false; + profiles_imported_ = false; + + last_set_row_ = 0; + + /* Set filenames for profiles */ + GList *files, *file; + files = g_hash_table_get_keys(const_cast<GHashTable *>(allowed_profile_filenames())); + file = g_list_first(files); + while (file) { + profile_files_ << static_cast<char *>(file->data); + file = gxx_list_next(file); + } + g_list_free(files); + + loadProfiles(); +} + +void ProfileModel::loadProfiles() +{ + beginResetModel(); + + bool refresh = profiles_.count() > 0; + + if (refresh) + profiles_.clear(); + else + init_profile_list(); + + GList *fl_entry = edited_profile_list(); + while (fl_entry && fl_entry->data) + { + profiles_ << reinterpret_cast<profile_def *>(fl_entry->data); + fl_entry = gxx_list_next(fl_entry); + } + + endResetModel(); +} + +GList * ProfileModel::entry(profile_def *ref) const +{ + GList *fl_entry = edited_profile_list(); + while (fl_entry && fl_entry->data) + { + profile_def *profile = reinterpret_cast<profile_def *>(fl_entry->data); + if (QString(ref->name).compare(profile->name) == 0 && + QString(ref->reference).compare(profile->reference) == 0 && + ref->is_global == profile->is_global && + ref->status == profile->status) + { + return fl_entry; + } + + fl_entry = gxx_list_next(fl_entry); + } + + return Q_NULLPTR; +} + +GList *ProfileModel::at(int row) const +{ + if (row < 0 || row >= profiles_.count()) + return Q_NULLPTR; + + profile_def * prof = profiles_.at(row); + return entry(prof); +} + +bool ProfileModel::changesPending() const +{ + if (reset_default_) + return true; + + if (g_list_length(edited_profile_list()) != g_list_length(current_profile_list())) + return true; + + bool pending = false; + GList *fl_entry = edited_profile_list(); + while (fl_entry && fl_entry->data && ! pending) { + profile_def *profile = reinterpret_cast<profile_def *>(fl_entry->data); + pending = (profile->status == PROF_STAT_NEW || profile->status == PROF_STAT_CHANGED || profile->status == PROF_STAT_COPY); + fl_entry = gxx_list_next(fl_entry); + } + + return pending; +} + +bool ProfileModel::importPending() const +{ + return profiles_imported_; +} + +bool ProfileModel::userProfilesExist() const +{ + bool user_exists = false; + for (int cnt = 0; cnt < rowCount() && ! user_exists; cnt++) + { + QModelIndex idx = index(cnt, ProfileModel::COL_NAME); + if (! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! idx.data(ProfileModel::DATA_IS_DEFAULT).toBool()) + user_exists = true; + } + + return user_exists; +} + +int ProfileModel::rowCount(const QModelIndex &) const +{ + return static_cast<int>(profiles_.count()); +} + +int ProfileModel::columnCount(const QModelIndex &) const +{ + return static_cast<int>(_LAST_ENTRY); +} + +profile_def * ProfileModel::guard(const QModelIndex &index) const +{ + if (! index.isValid()) + return Q_NULLPTR; + + return guard(index.row()); +} + +profile_def * ProfileModel::guard(int row) const +{ + if (row < 0 || profiles_.count() <= row) + return Q_NULLPTR; + + if (! edited_profile_list()) + { + static_cast<QList<profile_def *>>(profiles_).clear(); + return Q_NULLPTR; + } + + return profiles_.value(row, Q_NULLPTR); +} + +QVariant ProfileModel::dataDisplay(const QModelIndex &index) const +{ + profile_def * prof = guard(index); + if (! prof) + return QVariant(); + + switch (index.column()) + { + case COL_NAME: + return QString(prof->name); + case COL_TYPE: + if (prof->status == PROF_STAT_DEFAULT) + return tr("Default"); + else if (prof->is_global) + return tr("Global"); + else + return tr("Personal"); + default: + break; + } + + return QVariant(); +} + +QVariant ProfileModel::dataFontRole(const QModelIndex &index) const +{ + if (! index.isValid() || profiles_.count() <= index.row()) + return QVariant(); + + profile_def * prof = guard(index.row()); + if (! prof) + return QVariant(); + + QFont font; + + if (prof->is_global) + font.setItalic(true); + + if (! prof->is_global && ! checkDuplicate(index)) + { + if ((set_profile_.compare(prof->name) == 0 && prof->status == PROF_STAT_EXISTS) || + (set_profile_.compare(prof->reference) == 0 && prof->status == PROF_STAT_CHANGED) ) + font.setBold(true); + } + + if (prof->status == PROF_STAT_DEFAULT && reset_default_) + font.setStrikeOut(true); + + return font; +} + +bool ProfileModel::checkIfDeleted(int row) const +{ + QModelIndex idx = index(row, ProfileModel::COL_NAME); + return checkIfDeleted(idx); +} + +bool ProfileModel::checkIfDeleted(const QModelIndex &index) const +{ + profile_def * prof = guard(index); + if (! prof) + return false; + + QStringList deletedNames; + + GList * current = current_profile_list(); + + /* search the current list as long as we have not found anything */ + while (current) + { + bool found = false; + GList * edited = edited_profile_list(); + profile_def * profcurr = static_cast<profile_def *>(current->data); + + if (! profcurr->is_global && profcurr->status != PROF_STAT_DEFAULT) + { + while (edited && ! found) + { + profile_def * profed = static_cast<profile_def *>(edited->data); + if (! profed->is_global && profed->status != PROF_STAT_DEFAULT) + { + if (g_strcmp0(profcurr->name, profed->name) == 0 || g_strcmp0(profcurr->name, profed->reference) == 0) + { + if (profed->status == profcurr->status && prof->status != PROF_STAT_NEW && prof->status != PROF_STAT_COPY) + found = true; + } + } + + edited = gxx_list_next(edited); + } + + /* profile has been deleted, check if it has the name we ask for */ + if (! found) + deletedNames << profcurr->name; + } + + if (profcurr->is_global && deletedNames.contains(profcurr->name)) + deletedNames.removeAll(profcurr->name); + + current = gxx_list_next(current); + } + + if (deletedNames.contains(prof->name)) + return true; + + return false; +} + +bool ProfileModel::checkInvalid(const QModelIndex &index) const +{ + profile_def * prof = guard(index); + if (! prof) + return false; + + int ref = this->findAsReference(prof->name); + if (ref == index.row()) + return false; + + profile_def * pg = guard(ref); + if (pg && pg->status == PROF_STAT_CHANGED && g_strcmp0(pg->name, pg->reference) != 0 && ! prof->is_global) + return true; + + return false; +} + +bool ProfileModel::checkDuplicate(const QModelIndex &index, bool isOriginalToDuplicate) const +{ + profile_def * prof = guard(index); + if (! prof || (! isOriginalToDuplicate && prof->status == PROF_STAT_EXISTS) ) + return false; + + QList<int> rows = this->findAllByNameAndVisibility(prof->name, prof->is_global, false); + int found = 0; + profile_def * check = Q_NULLPTR; + for (int cnt = 0; cnt < rows.count(); cnt++) + { + int row = rows.at(cnt); + + if (row == index.row()) + continue; + + check = guard(row); + if (! check || (isOriginalToDuplicate && check->status == PROF_STAT_EXISTS) ) + continue; + + found++; + } + + if (found > 0) + return true; + return false; +} + +QVariant ProfileModel::dataBackgroundRole(const QModelIndex &index) const +{ + if (! index.isValid() || profiles_.count() <= index.row()) + return QVariant(); + + profile_def * prof = guard(index.row()); + if (! prof) + return QVariant(); + + if (prof->status == PROF_STAT_DEFAULT && reset_default_) + return ColorUtils::fromColorT(&prefs.gui_text_deprecated); + + if (prof->status != PROF_STAT_DEFAULT && ! prof->is_global) + { + /* Highlights errorneous line */ + if (checkInvalid(index) || checkIfDeleted(index) || checkDuplicate(index) || ! checkNameValidity(prof->name)) + return ColorUtils::fromColorT(&prefs.gui_text_invalid); + + /* Highlights line, which has been duplicated by another index */ + if (checkDuplicate(index, true)) + return ColorUtils::fromColorT(&prefs.gui_text_valid); + } + + return QVariant(); +} + +QVariant ProfileModel::dataToolTipRole(const QModelIndex &idx) const +{ + if (! idx.isValid() || profiles_.count() <= idx.row()) + return QVariant(); + + profile_def * prof = guard(idx.row()); + if (! prof) + return QVariant(); + + if (prof->is_global) + return tr("This is a system provided profile"); + else + return dataPath(idx); +} + +QVariant ProfileModel::dataPath(const QModelIndex &index) const +{ + if (! index.isValid() || profiles_.count() <= index.row()) + return QVariant(); + + profile_def * prof = guard(index.row()); + if (! prof) + return QVariant(); + + if (checkInvalid(index)) + { + int ref = this->findAsReference(prof->name); + if (ref != index.row() && ref >= 0) + { + profile_def * prof = guard(ref); + QString msg = tr("A profile change for this name is pending"); + if (prof) + msg.append(tr(" (See: %1)").arg(prof->name)); + return msg; + } + + return tr("This is an invalid profile definition"); + } + + if ((prof->status == PROF_STAT_NEW || prof->status == PROF_STAT_CHANGED || prof->status == PROF_STAT_COPY) && checkDuplicate(index)) + return tr("A profile already exists with this name"); + + if (checkIfDeleted(index)) + { + return tr("A profile with this name is being deleted"); + } + + if (prof->is_import) + return tr("Imported profile"); + + switch (prof->status) + { + case PROF_STAT_DEFAULT: + if (!reset_default_) + return gchar_free_to_qstring(get_persconffile_path("", FALSE)); + else + return tr("Resetting to default"); + case PROF_STAT_EXISTS: + { + QString profile_path; + if (prof->is_global) { + profile_path = gchar_free_to_qstring(get_global_profiles_dir()); + } else { + profile_path = gchar_free_to_qstring(get_profiles_dir()); + } + profile_path.append("/").append(prof->name); + return QDir::toNativeSeparators(profile_path); + } + case PROF_STAT_NEW: + { + QString errMsg; + + if (! checkNameValidity(prof->name, &errMsg)) + return errMsg; + else + return tr("Created from default settings"); + } + case PROF_STAT_CHANGED: + { + QString msg; + if (! ProfileModel::checkNameValidity(QString(prof->name), &msg)) + return msg; + + if (prof->reference) + return tr("Renamed from: %1").arg(prof->reference); + + return QVariant(); + } + case PROF_STAT_COPY: + { + QString msg; + + /* this should always be the case, but just as a precaution it is checked */ + if (prof->reference) + { + msg = tr("Copied from: %1").arg(prof->reference); + QString appendix; + + /* A global profile is neither deleted or removed, only system provided is allowed as appendix */ + if (profile_exists(prof->reference, TRUE) && prof->from_global) + appendix = tr("system provided"); + /* A default model as reference can neither be deleted or renamed, so skip if the reference was one */ + else if (! index.data(ProfileModel::DATA_IS_DEFAULT).toBool()) + { + /* find a non-global, non-default profile which could be referenced by this one. Those are the only + * ones which could be renamed or deleted */ + int row = this->findByNameAndVisibility(prof->reference, false, true); + profile_def * ref = guard(row); + + /* The reference is itself a copy of the original, therefore it is not accepted */ + if (ref && (ref->status == PROF_STAT_COPY || ref->status == PROF_STAT_NEW) && QString(ref->name).compare(prof->reference) != 0) + ref = Q_NULLPTR; + + /* found no other profile, original one had to be deleted */ + if (! ref || row == index.row() || checkIfDeleted(row)) + { + appendix = tr("deleted"); + } + /* found another profile, so the reference had been renamed, it the status is changed */ + else if (ref && ref->status == PROF_STAT_CHANGED) + { + appendix = tr("renamed to %1").arg(ref->name); + } + } + + if (appendix.length() > 0) + msg.append(QString(" (%1)").arg(appendix)); + } + + return msg; + } + } + + return QVariant(); +} + +QVariant ProfileModel::data(const QModelIndex &index, int role) const +{ + profile_def * prof = guard(index); + if (! prof) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + return dataDisplay(index); + case Qt::FontRole: + return dataFontRole(index); + case Qt::BackgroundRole: + return dataBackgroundRole(index); + case Qt::ToolTipRole: + return dataToolTipRole(index); + case ProfileModel::DATA_STATUS: + return QVariant::fromValue(prof->status); + case ProfileModel::DATA_IS_DEFAULT: + return QVariant::fromValue(prof->status == PROF_STAT_DEFAULT); + case ProfileModel::DATA_IS_GLOBAL: + return QVariant::fromValue(prof->is_global); + case ProfileModel::DATA_IS_SELECTED: + { + QModelIndex selected = activeProfile(); + profile_def * selprof = guard(selected); + if (selprof) + { + if (selprof && selprof->is_global != prof->is_global) + return QVariant::fromValue(false); + + if (selprof && strcmp(selprof->name, prof->name) == 0) + return QVariant::fromValue(true); + } + return QVariant::fromValue(false); + } + case ProfileModel::DATA_PATH: + return dataPath(index); + case ProfileModel::DATA_INDEX_VALUE_IS_URL: + if (index.column() <= ProfileModel::COL_TYPE) + return QVariant::fromValue(false); + return QVariant::fromValue(true); + case ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION: + if (prof->status == PROF_STAT_NEW || prof->status == PROF_STAT_COPY + || (prof->status == PROF_STAT_DEFAULT && reset_default_) + || prof->status == PROF_STAT_CHANGED || prof->is_import) + return QVariant::fromValue(false); + else + return QVariant::fromValue(true); + + default: + break; + } + + return QVariant(); +} + +QVariant ProfileModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case COL_NAME: + return tr("Profile"); + case COL_TYPE: + return tr("Type"); + default: + break; + } + } + + return QVariant(); +} + +Qt::ItemFlags ProfileModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags fl = QAbstractTableModel::flags(index); + + profile_def * prof = guard(index); + if (! prof) + return fl; + + if (index.column() == ProfileModel::COL_NAME && prof->status != PROF_STAT_DEFAULT && ! prof->is_global) + fl |= Qt::ItemIsEditable; + + return fl; +} + +int ProfileModel::findByName(QString name) +{ + int row = findByNameAndVisibility(name, false); + if (row < 0) + row = findByNameAndVisibility(name, true); + + return row; +} + +int ProfileModel::findAsReference(QString reference) const +{ + int found = -1; + if (reference.length() <= 0) + return found; + + for (int cnt = 0; cnt < profiles_.count() && found < 0; cnt++) + { + profile_def * prof = guard(cnt); + if (prof && reference.compare(prof->reference) == 0) + found = cnt; + } + + return found; +} + +int ProfileModel::findByNameAndVisibility(QString name, bool isGlobal, bool searchReference) const +{ + QList<int> result = findAllByNameAndVisibility(name, isGlobal, searchReference); + return result.count() == 0 ? -1 : result.at(0); +} + +QList<int> ProfileModel::findAllByNameAndVisibility(QString name, bool isGlobal, bool searchReference) const +{ + QList<int> result; + + for (int cnt = 0; cnt < profiles_.count(); cnt++) + { + profile_def * prof = guard(cnt); + if (prof && static_cast<bool>(prof->is_global) == isGlobal) + { + if (name.compare(prof->name) == 0 || (searchReference && name.compare(prof->reference) == 0) ) + result << cnt; + } + } + + return result; + +} + +QModelIndex ProfileModel::addNewProfile(QString name) +{ + int cnt = 1; + QString newName = name; + while (findByNameAndVisibility(newName) >= 0) + { + newName = QString("%1 %2").arg(name).arg(QString::number(cnt)); + cnt++; + } + + add_to_profile_list(newName.toUtf8().constData(), newName.toUtf8().constData(), PROF_STAT_NEW, FALSE, FALSE, FALSE); + loadProfiles(); + + return index(findByName(newName), COL_NAME); +} + +QModelIndex ProfileModel::duplicateEntry(QModelIndex idx, int new_status) +{ + profile_def * prof = guard(idx); + if (! prof) + return QModelIndex(); + + /* only new and copied stati can be set */ + if (new_status != PROF_STAT_NEW && new_status != PROF_STAT_COPY) + new_status = PROF_STAT_COPY; + + /* this is a copy from a personal profile, check if the original has been a + * new profile or a preexisting one. In the case of a new profile, restart + * with the state PROF_STAT_NEW */ + if (prof->status == PROF_STAT_COPY && ! prof->from_global) + { + int row = findByNameAndVisibility(prof->reference, false); + profile_def * copyParent = guard(row); + if (copyParent && copyParent->status == PROF_STAT_NEW) + return duplicateEntry(index(row, ProfileModel::COL_NAME), PROF_STAT_NEW); + } + + /* Rules for figuring out the name to copy from: + * + * General, use copy name + * If status of copy is new or changed => use copy reference + * If copy is non global and status of copy is != changed, use original parent name + */ + QString parent = prof->name; + if (prof->status == PROF_STAT_CHANGED) + parent = prof->reference; + else if (! prof->is_global && prof->status != PROF_STAT_NEW && prof->status != PROF_STAT_CHANGED) + parent = get_profile_parent (prof->name); + + if (parent.length() == 0) + return QModelIndex(); + + /* parent references the parent profile to be used, parentName is the base for the new name */ + QString parentName = parent; + /* the user has changed the profile name, therefore this is also the name to be used */ + if (prof->status != PROF_STAT_EXISTS) + parentName = prof->name; + + /* check to ensure we do not end up with (copy) (copy) (copy) ... */ + QRegularExpression rx("\\s+(\\(\\s*" + tr("copy", "noun") + "\\s*\\d*\\))"); + parentName.replace(rx, ""); + + QString new_name; + /* if copy is global and name has not been used before, use that, else create first copy */ + if (prof->is_global && findByNameAndVisibility(parentName) < 0) + new_name = QString(prof->name); + else + new_name = QString("%1 (%2)").arg(parentName).arg(tr("copy", "noun")); + + /* check if copy already exists and iterate, until an unused version is found */ + int cnt = 1; + while (findByNameAndVisibility(new_name) >= 0) + { + new_name = QString("%1 (%2 %3)").arg(parentName).arg(tr("copy", "noun")).arg(QString::number(cnt)); + cnt++; + } + + /* if this would be a copy, but the original is already a new one, this is a copy as well */ + if (new_status == PROF_STAT_COPY && prof->status == PROF_STAT_NEW) + new_status = PROF_STAT_NEW; + + /* add element */ + add_to_profile_list(new_name.toUtf8().constData(), parent.toUtf8().constData(), new_status, FALSE, prof->from_global ? prof->from_global : prof->is_global, FALSE); + + /* reload profile list in model */ + loadProfiles(); + + int row = findByNameAndVisibility(new_name, false); + /* sanity check, if adding the profile went correctly */ + if (row < 0 || row == idx.row()) + return QModelIndex(); + + /* return the index of the profile */ + return index(row, COL_NAME); +} + +void ProfileModel::deleteEntry(QModelIndex idx) +{ + if (! idx.isValid()) + return; + + QModelIndexList temp; + temp << idx; + deleteEntries(temp); +} + +void ProfileModel::deleteEntries(QModelIndexList idcs) +{ + bool changes = false; + + QList<int> indeces; + foreach (QModelIndex idx, idcs) + { + if (! indeces.contains(idx.row()) && ! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool()) + indeces << idx.row(); + } + /* Security blanket. This ensures, that we start deleting from the end and do not get any issues iterating the list */ + std::sort(indeces.begin(), indeces.end(), std::less<int>()); + + foreach (int row, indeces) + { + profile_def * prof = guard(row); + if (! prof) + continue; + + if (prof->is_global) + continue; + + if (prof->status == PROF_STAT_DEFAULT) + { + reset_default_ = ! reset_default_; + } + else + { + GList * fl_entry = entry(prof); + if (fl_entry) + { + changes = true; + remove_from_profile_list(fl_entry); + } + } + } + + if (changes) + loadProfiles(); + + if (reset_default_) + { + emit layoutAboutToBeChanged(); + emit dataChanged(index(0, 0), index(rowCount(), columnCount())); + emit layoutChanged(); + } +} + +bool ProfileModel::resetDefault() const +{ + return reset_default_; +} + +void ProfileModel::doResetModel(bool reset_import) +{ + reset_default_ = false; + if (reset_import) + profiles_imported_ = false; + + loadProfiles(); +} + +QModelIndex ProfileModel::activeProfile() const +{ + QList<int> rows = this->findAllByNameAndVisibility(set_profile_, false, true); + foreach (int row, rows) + { + profile_def * prof = profiles_.at(row); + if (prof->is_global || checkDuplicate(index(row, ProfileModel::COL_NAME)) ) + return QModelIndex(); + + if ((set_profile_.compare(prof->name) == 0 && (prof->status == PROF_STAT_EXISTS || prof->status == PROF_STAT_DEFAULT) ) || + (set_profile_.compare(prof->reference) == 0 && prof->status == PROF_STAT_CHANGED) ) + return index(row, ProfileModel::COL_NAME); + } + + return QModelIndex(); +} + +bool ProfileModel::setData(const QModelIndex &idx, const QVariant &value, int role) +{ + last_set_row_ = -1; + + if (role != Qt::EditRole || ! value.isValid() || value.toString().isEmpty()) + return false; + + QString newValue = value.toString(); + profile_def * prof = guard(idx); + if (! prof || prof->status == PROF_STAT_DEFAULT) + return false; + + last_set_row_ = idx.row(); + + QString current(prof->name); + if (current.compare(newValue) != 0) + { + g_free(prof->name); + prof->name = qstring_strdup(newValue); + + if (prof->reference && g_strcmp0(prof->name, prof->reference) == 0 && ! (prof->status == PROF_STAT_NEW || prof->status == PROF_STAT_COPY)) { + prof->status = PROF_STAT_EXISTS; + } else if (prof->status == PROF_STAT_EXISTS) { + prof->status = PROF_STAT_CHANGED; + } + emit itemChanged(idx); + } + + loadProfiles(); + + return true; +} + +int ProfileModel::lastSetRow() const +{ + return last_set_row_; +} + +bool ProfileModel::copyTempToProfile(QString tempPath, QString profilePath, bool * wasEmpty) +{ + bool was_empty = true; + + QDir profileDir(profilePath); + if (! profileDir.mkpath(profilePath) || ! QFile::exists(tempPath)) + return false; + + QDir tempProfile(tempPath); + tempProfile.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); + QFileInfoList files = tempProfile.entryInfoList(); + if (files.count() <= 0) + return false; + + int created = 0; + foreach (QFileInfo finfo, files) + { + QString tempFile = finfo.absoluteFilePath(); + QString profileFile = profilePath + "/" + finfo.fileName(); + + if (! profile_files_.contains(finfo.fileName())) + { + was_empty = false; + continue; + } + + if (! QFile::exists(tempFile) || QFile::exists(profileFile)) + continue; + + if (QFile::copy(tempFile, profileFile)) + created++; + } + + if (wasEmpty) + *wasEmpty = was_empty; + + if (created > 0) + return true; + + return false; +} + +QFileInfoList ProfileModel::uniquePaths(QFileInfoList lst) +{ + QStringList files; + QFileInfoList newLst; + + foreach (QFileInfo entry, lst) + { + if (! files.contains(entry.absoluteFilePath())) + { + if (entry.exists() && entry.isDir()) + { + newLst << QFileInfo(entry.absoluteFilePath()); + files << entry.absoluteFilePath(); + } + } + } + + return newLst; +} + +QFileInfoList ProfileModel::filterProfilePath(QString path, QFileInfoList ent, bool fromZip) +{ + QFileInfoList result = ent; + QDir temp(path); + temp.setSorting(QDir::Name); + temp.setFilter(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot); + QFileInfoList entries = temp.entryInfoList(); + if (! fromZip) + entries << QFileInfo(path); + foreach (QFileInfo entry, entries) + { + QDir fPath(entry.absoluteFilePath()); + fPath.setSorting(QDir::Name); + fPath.setFilter(QDir::Files | QDir::NoSymLinks); + QFileInfoList fEntries = fPath.entryInfoList(); + bool found = false; + for (int cnt = 0; cnt < fEntries.count() && ! found; cnt++) + { + if (config_file_exists_with_entries(fEntries[cnt].absoluteFilePath().toUtf8().constData(), '#')) + found = true; + } + + if (found) + { + result.append(entry); + } + else + { + if (path.compare(entry.absoluteFilePath()) != 0) + result.append(filterProfilePath(entry.absoluteFilePath(), result, fromZip)); + } + } + + return result; +} + +#ifdef HAVE_MINIZIP +QStringList ProfileModel::exportFileList(QModelIndexList items) +{ + QStringList result; + + foreach(QModelIndex idx, items) + { + profile_def * prof = guard(idx); + if (! prof || 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; + } + + /* Write recent file for current profile before exporting */ + write_profile_recent(); + + 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, gchar_free_to_qstring(get_profiles_dir()) + "/") ) + return true; + + return false; +} + +/* This check runs BEFORE the file has been unzipped! */ +bool ProfileModel::acceptFile(QString fileName, int fileSize) +{ + if (fileName.toLower().endsWith(".zip")) + return false; + + /* Arbitrary maximum config file size accepted: 256MB */ + if (fileSize > 1024 * 1024 * 256) + return false; + + return true; +} + +QString ProfileModel::cleanName(QString fileName) +{ + QStringList parts = fileName.split("/"); + QString temp = parts[parts.count() - 1].replace(QRegularExpression("[" + QRegularExpression::escape(illegalCharacters()) + "]"), QString("_") ); + temp = parts.join("/"); + return temp; +} + +int ProfileModel::importProfilesFromZip(QString filename, int * skippedCnt, QStringList *result) +{ + QTemporaryDir dir; +#if 0 + dir.setAutoRemove(false); + g_printerr("Temp dir for unzip: %s\n", dir.path().toUtf8().constData()); +#endif + + int cnt = 0; + if (dir.isValid()) + { + WiresharkZipHelper::unzip(filename, dir.path(), &ProfileModel::acceptFile, &ProfileModel::cleanName); + cnt = importProfilesFromDir(dir.path(), skippedCnt, true, result); + } + + return cnt; +} +#endif + +int ProfileModel::importProfilesFromDir(QString dirname, int * skippedCnt, bool fromZip, QStringList *result) +{ + int count = 0; + int skipped = 0; + QDir profileDir(gchar_free_to_qstring(get_profiles_dir())); + QDir dir(dirname); + + if (skippedCnt) + *skippedCnt = 0; + + if (dir.exists()) + { + QFileInfoList entries = uniquePaths(filterProfilePath(dirname, QFileInfoList(), fromZip)); + + foreach (QFileInfo fentry, entries) + { + if (fentry.fileName().length() <= 0) + continue; + + bool wasEmpty = true; + bool success = false; + + QString profilePath = profileDir.absolutePath() + "/" + fentry.fileName(); + QString tempPath = fentry.absoluteFilePath(); + + if (fentry.fileName().compare(DEFAULT_PROFILE, Qt::CaseInsensitive) == 0 || QFile::exists(profilePath)) + { + skipped++; + continue; + } + + if (result) + *result << fentry.fileName(); + + success = copyTempToProfile(tempPath, profilePath, &wasEmpty); + if (success) + { + count++; + add_to_profile_list(fentry.fileName().toUtf8().constData(), fentry.fileName().toUtf8().constData(), PROF_STAT_NEW, FALSE, FALSE, TRUE); + } + else if (! wasEmpty && QFile::exists(profilePath)) + { + QDir dh(profilePath); + dh.rmdir(profilePath); + } + } + + } + + if (count > 0) + { + profiles_imported_ = true; + loadProfiles(); + } + + if (skippedCnt) + *skippedCnt = skipped; + + return count; +} + +void ProfileModel::markAsImported(QStringList importedItems) +{ + if (importedItems.count() <= 0) + return; + + profiles_imported_ = true; + + foreach (QString item, importedItems) + { + int row = findByNameAndVisibility(item, false); + profile_def * prof = guard(row); + if (! prof) + continue; + + prof->is_import = true; + } +} + +bool ProfileModel::clearImported(QString *msg) +{ + QList<int> rows; + bool result = true; + for (int cnt = 0; cnt < rowCount(); cnt++) + { + profile_def * prof = guard(cnt); + if (prof && prof->is_import && ! rows.contains(cnt)) + rows << cnt; + } + /* Security blanket. This ensures, that we start deleting from the end and do not get any issues iterating the list */ + std::sort(rows.begin(), rows.end(), std::less<int>()); + + char * ret_path = Q_NULLPTR; + for (int cnt = 0; cnt < rows.count() && result; cnt++) + { + int row = rows.at(cnt); + if (delete_persconffile_profile (index(row, ProfileModel::COL_NAME).data().toString().toUtf8().constData(), &ret_path) != 0) + { + if (msg) + { + QString errmsg = QString("%1\n\"%2\":\n%3").arg(tr("Can't delete profile directory")).arg(ret_path).arg(g_strerror(errno)); + msg->append(errmsg); + } + + result = false; + } + } + + return result; +} + +QString ProfileModel::illegalCharacters() +{ +#ifdef _WIN32 + /* According to https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions */ + return QString("<>:\"/\\|?*"); +#else + return QDir::separator(); +#endif + +} + +bool ProfileModel::checkNameValidity(QString name, QString *msg) +{ + QString message; + bool invalid = false; + QString msgChars; + + QString invalid_dir_chars = illegalCharacters(); + + for (int cnt = 0; cnt < invalid_dir_chars.length() && ! invalid; cnt++) + { + msgChars += invalid_dir_chars[cnt]; + msgChars += ' '; + if (name.contains(invalid_dir_chars[cnt])) + invalid = true; + } +#ifdef _WIN32 + if (invalid) + { + message = tr("A profile name cannot contain the following characters: %1").arg(msgChars); + } + + if (message.isEmpty() && (name.startsWith('.') || name.endsWith('.')) ) + message = tr("A profile cannot start or end with a period (.)"); +#else + if (invalid) + message = tr("A profile name cannot contain the '/' character"); +#endif + + if (! message.isEmpty()) { + if (msg) + msg->append(message); + return false; + } + + return true; +} + +QString ProfileModel::activeProfileName() +{ + ProfileModel model; + QModelIndex idx = model.activeProfile(); + return idx.data(ProfileModel::COL_NAME).toString(); +} + +QString ProfileModel::activeProfilePath() +{ + ProfileModel model; + QModelIndex idx = model.activeProfile(); + return idx.data(ProfileModel::DATA_PATH).toString(); +} diff --git a/ui/qt/models/profile_model.h b/ui/qt/models/profile_model.h new file mode 100644 index 00000000..16febd70 --- /dev/null +++ b/ui/qt/models/profile_model.h @@ -0,0 +1,165 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef PROFILE_MODEL_H +#define PROFILE_MODEL_H + +#include "config.h" +#include "glib.h" + +#include <ui/profile.h> + +#include <QAbstractTableModel> +#include <QSortFilterProxyModel> +#include <QLoggingCategory> +#include <QFileInfoList> + +Q_DECLARE_LOGGING_CATEGORY(profileLogger) + +class ProfileSortModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + ProfileSortModel(QObject *parent = Q_NULLPTR); + + enum FilterType { + AllProfiles = 0, + PersonalProfiles, + GlobalProfiles + }; + + void setFilterType(FilterType ft); + void setFilterString(QString txt = QString()); + + static QStringList filterTypes(); + +protected: + virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + +private: + FilterType ft_; + QString ftext_; +}; + +class ProfileModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit ProfileModel(QObject * parent = Q_NULLPTR); + + enum { + COL_NAME, + COL_TYPE, + _LAST_ENTRY + } columns_; + + enum { + DATA_STATUS = Qt::UserRole, + DATA_IS_DEFAULT, + DATA_IS_GLOBAL, + DATA_IS_SELECTED, + DATA_PATH, + DATA_PATH_IS_NOT_DESCRIPTION, + DATA_INDEX_VALUE_IS_URL + } data_values_; + + // QAbstractItemModel interface + virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex & parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex & idx, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role); + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + + void deleteEntry(QModelIndex idx); + void deleteEntries(QModelIndexList idcs); + + int findByName(QString name); + QModelIndex addNewProfile(QString name); + QModelIndex duplicateEntry(QModelIndex idx, int new_status = PROF_STAT_COPY); + + void doResetModel(bool reset_import = false); + bool resetDefault() const; + + QModelIndex activeProfile() const; + static QString activeProfileName(); + static QString activeProfilePath(); + + GList * at(int row) const; + + bool changesPending() const; + bool importPending() const; + + bool userProfilesExist() const; + +#ifdef HAVE_MINIZIP + bool exportProfiles(QString filename, QModelIndexList items, QString * err = Q_NULLPTR); + int importProfilesFromZip(QString filename, int *skippedCnt = Q_NULLPTR, QStringList *result = Q_NULLPTR); +#endif + int importProfilesFromDir(QString filename, int *skippedCnt = Q_NULLPTR, bool fromZip = false, QStringList *result = Q_NULLPTR); + + static bool checkNameValidity(QString name, QString *msg = Q_NULLPTR); + QList<int> findAllByNameAndVisibility(QString name, bool isGlobal = false, bool searchReference = false) const; + void markAsImported(QStringList importedItems); + bool clearImported(QString *msg = Q_NULLPTR); + + int lastSetRow() const; + + bool checkInvalid(const QModelIndex &index) const; + bool checkIfDeleted(const QModelIndex &index) const; + bool checkIfDeleted(int row) const; + bool checkDuplicate(const QModelIndex &index, bool isOriginalToDuplicate = false) const; + +signals: + void itemChanged(const QModelIndex &idx); + +protected: + static QString illegalCharacters(); + +private: + QList<profile_def *> profiles_; + QStringList profile_files_; + QString set_profile_; + bool reset_default_; + bool profiles_imported_; + + int last_set_row_; + + void loadProfiles(); + profile_def * guard(const QModelIndex &index) const; + profile_def * guard(int row) const; + GList * entry(profile_def *) const; + + int findByNameAndVisibility(QString name, bool isGlobal = false, bool searchReference = false) const; + int findAsReference(QString reference) const; + +#ifdef HAVE_MINIZIP + static bool acceptFile(QString fileName, int fileSize); + static QString cleanName(QString fileName); +#endif + + QVariant dataDisplay(const QModelIndex & idx) const; + QVariant dataFontRole(const QModelIndex & idx) const; + QVariant dataBackgroundRole(const QModelIndex & idx) const; + QVariant dataToolTipRole(const QModelIndex & idx) const; + QVariant dataPath(const QModelIndex & idx) const; + +#ifdef HAVE_MINIZIP + QStringList exportFileList(QModelIndexList items); +#endif + bool copyTempToProfile(QString tempPath, QString profilePath, bool *wasEmpty = Q_NULLPTR); + QFileInfoList filterProfilePath(QString, QFileInfoList ent, bool fromZip); + QFileInfoList uniquePaths(QFileInfoList lst); + +}; + +#endif diff --git a/ui/qt/models/proto_tree_model.cpp b/ui/qt/models/proto_tree_model.cpp new file mode 100644 index 00000000..3ededffd --- /dev/null +++ b/ui/qt/models/proto_tree_model.cpp @@ -0,0 +1,252 @@ +/* proto_tree_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/proto_tree_model.h> + +#include <epan/prefs.h> +#include <wsutil/wslog.h> + +#include <ui/qt/utils/color_utils.h> + +#include <QApplication> +#include <QPalette> +#include <QFont> + +// To do: +// - Add ProtoTreeDelegate +// - Add ProtoTreeModel to CaptureFile + +ProtoTreeModel::ProtoTreeModel(QObject * parent) : + QAbstractItemModel(parent) +{ + root_node_ = new ProtoNode(NULL); +} + +ProtoTreeModel::~ProtoTreeModel() +{ + delete root_node_; +} + +Qt::ItemFlags ProtoTreeModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags item_flags = QAbstractItemModel::flags(index); + if (rowCount(index) < 1) { + item_flags |= Qt::ItemNeverHasChildren; + } + + return item_flags; +} + +QModelIndex ProtoTreeModel::index(int row, int, const QModelIndex &parent) const +{ + ProtoNode *parent_node = root_node_; + + if (parent.isValid()) { + // index is not a top level item. + parent_node = protoNodeFromIndex(parent); + } + + if (! parent_node->isValid()) + return QModelIndex(); + + ProtoNode *child = parent_node->child(row); + if (! child) { + return QModelIndex(); + } + + return createIndex(row, 0, static_cast<void *>(child)); +} + +QModelIndex ProtoTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + ProtoNode *parent_node = protoNodeFromIndex(index)->parentNode(); + return indexFromProtoNode(parent_node); +} + +int ProtoTreeModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return protoNodeFromIndex(parent)->childrenCount(); + } + return root_node_->childrenCount(); +} + +// The QItemDelegate documentation says +// "When displaying items from a custom model in a standard view, it is +// often sufficient to simply ensure that the model returns appropriate +// data for each of the roles that determine the appearance of items in +// views." +// We might want to move this to a delegate regardless. +QVariant ProtoTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + ProtoNode *index_node = protoNodeFromIndex(index); + FieldInformation finfo(index_node); + if (!finfo.isValid()) { + return QVariant(); + } + + switch (role) { + case Qt::DisplayRole: + return index_node->labelText(); + case Qt::BackgroundRole: + { + switch(finfo.flag(PI_SEVERITY_MASK)) { + case(0): + break; + case(PI_COMMENT): + return ColorUtils::expert_color_comment; + case(PI_CHAT): + return ColorUtils::expert_color_chat; + case(PI_NOTE): + return ColorUtils::expert_color_note; + case(PI_WARN): + return ColorUtils::expert_color_warn; + case(PI_ERROR): + return ColorUtils::expert_color_error; + default: + ws_warning("Unhandled severity flag: %u", finfo.flag(PI_SEVERITY_MASK)); + } + if (finfo.headerInfo().type == FT_PROTOCOL) { + return QApplication::palette().window(); + } + return QApplication::palette().base(); + } + case Qt::ForegroundRole: + { + if (finfo.flag(PI_SEVERITY_MASK)) { + return ColorUtils::expert_color_foreground; + } + if (finfo.isLink()) { + return ColorUtils::themeLinkBrush(); + } + if (finfo.headerInfo().type == FT_PROTOCOL) { + return QApplication::palette().windowText(); + } + return QApplication::palette().text(); + } + case Qt::FontRole: + if (finfo.isLink()) { + QFont font; + font.setUnderline(true); + return font; + } + default: + break; + } + + return QVariant(); +} + +void ProtoTreeModel::setRootNode(proto_node *root_node) +{ + beginResetModel(); + delete root_node_; + root_node_ = new ProtoNode(root_node); + endResetModel(); + if (!root_node) return; + + int row_count = root_node_->childrenCount(); + if (row_count < 1) return; + beginInsertRows(QModelIndex(), 0, row_count - 1); + endInsertRows(); +} + +ProtoNode* ProtoTreeModel::protoNodeFromIndex(const QModelIndex &index) const +{ + return static_cast<ProtoNode*>(index.internalPointer()); +} + +QModelIndex ProtoTreeModel::indexFromProtoNode(ProtoNode *index_node) const +{ + if (!index_node) { + return QModelIndex(); + } + + int row = index_node->row(); + + if (!index_node->isValid() || row < 0) { + return QModelIndex(); + } + + return createIndex(row, 0, static_cast<void *>(index_node)); +} + +struct find_hfid_ { + int hfid; + ProtoNode *node; +}; + +bool ProtoTreeModel::foreachFindHfid(ProtoNode *node, gpointer find_hfid_ptr) +{ + struct find_hfid_ *find_hfid = (struct find_hfid_ *) find_hfid_ptr; + if (PNODE_FINFO(node->protoNode()) && PNODE_FINFO(node->protoNode())->hfinfo->id == find_hfid->hfid) { + find_hfid->node = node; + return true; + } + for (int i = 0; i < node->childrenCount(); i++) { + if (foreachFindHfid(node->child(i), find_hfid)) { + return true; + } + } + return false; +} + +QModelIndex ProtoTreeModel::findFirstHfid(int hf_id) +{ + if (!root_node_ || hf_id < 0) return QModelIndex(); + + struct find_hfid_ find_hfid; + find_hfid.hfid = hf_id; + + if (foreachFindHfid(root_node_, &find_hfid) && find_hfid.node->isValid()) { + return indexFromProtoNode(find_hfid.node); + } + return QModelIndex(); +} + +struct find_field_info_ { + field_info *fi; + ProtoNode *node; +}; + +bool ProtoTreeModel::foreachFindField(ProtoNode *node, gpointer find_finfo_ptr) +{ + struct find_field_info_ *find_finfo = (struct find_field_info_ *) find_finfo_ptr; + if (PNODE_FINFO(node->protoNode()) == find_finfo->fi) { + find_finfo->node = node; + return true; + } + for (int i = 0; i < node->childrenCount(); i++) { + if (foreachFindField(node->child(i), find_finfo)) { + return true; + } + } + return false; +} + +QModelIndex ProtoTreeModel::findFieldInformation(FieldInformation *finfo) +{ + if (!root_node_ || !finfo) return QModelIndex(); + field_info * fi = finfo->fieldInfo(); + if (!fi) return QModelIndex(); + + struct find_field_info_ find_finfo; + find_finfo.fi = fi; + + if (foreachFindField(root_node_, &find_finfo) && find_finfo.node->isValid()) { + return indexFromProtoNode(find_finfo.node); + } + return QModelIndex(); +} diff --git a/ui/qt/models/proto_tree_model.h b/ui/qt/models/proto_tree_model.h new file mode 100644 index 00000000..df7cbba8 --- /dev/null +++ b/ui/qt/models/proto_tree_model.h @@ -0,0 +1,48 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef PROTO_TREE_MODEL_H +#define PROTO_TREE_MODEL_H + +#include <ui/qt/utils/field_information.h> +#include <ui/qt/utils/proto_node.h> + +#include <QAbstractItemModel> +#include <QModelIndex> + +class ProtoTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit ProtoTreeModel(QObject * parent = 0); + ~ProtoTreeModel(); + + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + QModelIndex index(int row, int, const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex &index) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &) const { return 1; } + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + // root_node can be NULL. + void setRootNode(proto_node *root_node); + ProtoNode* protoNodeFromIndex(const QModelIndex &index) const; + QModelIndex indexFromProtoNode(ProtoNode *index_node) const; + + QModelIndex findFirstHfid(int hf_id); + QModelIndex findFieldInformation(FieldInformation *finfo); + +private: + ProtoNode *root_node_; + static bool foreachFindHfid(ProtoNode *node, gpointer find_hfid_ptr); + static bool foreachFindField(ProtoNode *node, gpointer find_finfo_ptr); +}; + +#endif // PROTO_TREE_MODEL_H diff --git a/ui/qt/models/related_packet_delegate.cpp b/ui/qt/models/related_packet_delegate.cpp new file mode 100644 index 00000000..885160f4 --- /dev/null +++ b/ui/qt/models/related_packet_delegate.cpp @@ -0,0 +1,373 @@ +/* related_packet_delegate.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/related_packet_delegate.h> +#include "packet_list_record.h" + +#include <ui/qt/main_application.h> + +#include <ui/qt/utils/color_utils.h> + +#include <ui/qt/main_window.h> + +#include <QApplication> +#include <QPainter> + +typedef enum { + CT_NONE, // Not within the selected conversation. + CT_STARTING, // First packet in a conversation. + CT_CONTINUING, // Part of the selected conversation. + CT_BYPASSING, // *Not* part of the selected conversation. + CT_ENDING, // Last packet in a conversation. + CT_NUM_TYPES, +} ct_conversation_trace_type_t; + +// To do: +// - Add other frame types and symbols. If `tshark -G fields | grep FT_FRAMENUM` +// is any indication, we should add "reassembly" and "reassembly error" +// fields. +// - Don't add *too* many frame types and symbols. The goal is context, not +// clutter. +// - Add tooltips. It looks like this needs to be done in ::helpEvent +// or PacketListModel::data. +// - Add "Go -> Next Related" and "Go -> Previous Related"? +// - Apply as filter? + +RelatedPacketDelegate::RelatedPacketDelegate(QWidget *parent) : + QStyledItemDelegate(parent), + conv_(NULL), + current_frame_(0) +{ + clear(); +} + +void RelatedPacketDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + + /* This prevents the drawing of related objects, if multiple lines are being selected */ + if (mainApp && mainApp->mainWindow()) + { + MainWindow * mw = qobject_cast<MainWindow *>(mainApp->mainWindow()); + if (mw && mw->hasSelection()) + { + QStyledItemDelegate::paint(painter, option, index); + return; + } + } + + QStyleOptionViewItem option_vi = option; + QStyledItemDelegate::initStyleOption(&option_vi, index); + int em_w = option_vi.fontMetrics.height(); + int en_w = (em_w + 1) / 2; + int line_w = (option_vi.fontMetrics.lineWidth()); + + option_vi.features |= QStyleOptionViewItem::HasDecoration; + option_vi.decorationSize.setHeight(1); + option_vi.decorationSize.setWidth(em_w); + QStyledItemDelegate::paint(painter, option_vi, index); + + guint32 setup_frame = 0, last_frame = 0; + if (conv_) { + setup_frame = (int) conv_->setup_frame; + last_frame = (int) conv_->last_frame; + } + + const frame_data *fd; + PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer()); + if (!record || (fd = record->frameData()) == NULL) { + return; + } + + ct_conversation_trace_type_t conversation_trace_type = CT_NONE; + ft_framenum_type_t related_frame_type = + related_frames_.contains(fd->num) ? related_frames_[fd->num] : FT_FRAMENUM_NUM_TYPES; + + if (setup_frame > 0 && last_frame > 0 && setup_frame != last_frame) { + if (fd->num == setup_frame) { + conversation_trace_type = CT_STARTING; + } else if (fd->num > setup_frame && fd->num < last_frame) { + conversation_trace_type = + conv_->conv_index == record->conversation() ? CT_CONTINUING : CT_BYPASSING; + } else if (fd->num == last_frame) { + conversation_trace_type = CT_ENDING; + } + } + + painter->save(); + + if (QApplication::style()->objectName().contains("vista")) { + // QWindowsVistaStyle::drawControl does this internally. Unfortunately there + // doesn't appear to be a more general way to do this. + option_vi.palette.setColor(QPalette::All, QPalette::HighlightedText, option_vi.palette.color(QPalette::Active, QPalette::Text)); + } + + QPalette::ColorGroup cg = option_vi.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + QColor fg; + if (cg == QPalette::Normal && !(option_vi.state & QStyle::State_Active)) + cg = QPalette::Inactive; +#if !defined(Q_OS_WIN) + if (option_vi.state & QStyle::State_MouseOver) { + fg = QApplication::palette().text().color(); + } else +#endif + if (option_vi.state & QStyle::State_Selected) { + fg = option_vi.palette.color(cg, QPalette::HighlightedText); + } else { + fg = option_vi.palette.color(cg, QPalette::Text); + } + + fg = ColorUtils::alphaBlend(fg, option_vi.palette.color(cg, QPalette::Base), 0.5); + QPen line_pen(fg); + line_pen.setWidth(line_w); + line_pen.setJoinStyle(Qt::RoundJoin); + + painter->setPen(line_pen); + painter->translate(option_vi.rect.x(), option_vi.rect.y()); + painter->translate(en_w + 0.5, 0.5); + painter->setRenderHint(QPainter::Antialiasing, true); + int height = option_vi.rect.height(); + + // Uncomment to make the boundary visible. +// painter->save(); +// painter->setPen(Qt::darkRed); +// painter->drawRect(QRectF(0.5, 0.5, en_w - 1, height - 1)); +// painter->restore(); + + // The current decorations are based on what looked good and were easy + // to code. + + // It might be useful to have a JACKPOT_MODE define that shows each + // decoration in sequence in order to make it easier to create + // screenshots for the User's Guide. + + // Vertical line. Lower and upper half for the start and end of the + // conversation respectively, solid for conversation member, dashed + // for other packets in the start-end range. + switch (conversation_trace_type) { + case CT_STARTING: + { + QPoint start_line[] = { + QPoint(en_w - 1, height / 2), + QPoint(0, height / 2), + QPoint(0, height) + }; + painter->drawPolyline(start_line, 3); + break; + } + case CT_CONTINUING: + case CT_BYPASSING: + { + painter->save(); + if (conversation_trace_type == CT_BYPASSING) { + // Dashed line as we bypass packets not part of the conv. + QPen other_pen(line_pen); + other_pen.setStyle(Qt::DashLine); + painter->setPen(other_pen); + } + + // analysis overriding mark (three horizontal lines) + if(fd->tcp_snd_manual_analysis) { + int wbound = (en_w - 1) / 2; + + painter->drawLine(-wbound, 1, wbound, 1); + painter->drawLine(-wbound, height / 2, wbound, height / 2); + painter->drawLine(-wbound, height - 2, wbound, height - 2); + } + + painter->drawLine(0, 0, 0, height); + painter->restore(); + break; + } + case CT_ENDING: + { + QPoint end_line[] = { + QPoint(en_w - 1, height / 2), + QPoint(0, height / 2), + QPoint(0, 0) + }; + painter->drawPolyline(end_line, 3); + /* analysis overriding on the last packet of the conversation, + * we mark it with an additional horizontal line only. + * See issue 10725 for example. + */ + // analysis overriding mark (three horizontal lines) + if(fd->tcp_snd_manual_analysis) { + int wbound = (en_w - 1) / 2; + + painter->drawLine(-wbound, 1, wbound, 1); + painter->drawLine(-wbound, height / 2, wbound, height / 2); + } + + break; + } + default: + break; + } + + // Related packet indicator. Rightward arrow for requests, leftward + // arrow for responses, circle for others. + // XXX These are comically oversized when we have multi-line rows. + if (related_frame_type != FT_FRAMENUM_NUM_TYPES) { + painter->setBrush(fg); + switch (related_frame_type) { + // Request and response arrows are moved forward one pixel in order to + // maximize white space between the heads and the conversation line. + case FT_FRAMENUM_REQUEST: + { + int hh = height / 2; + QPoint tail(2 - en_w, hh); + QPoint head(en_w, hh); + drawArrow(painter, tail, head, hh / 2); + break; + } + case FT_FRAMENUM_RESPONSE: + { + int hh = height / 2; + QPoint tail(en_w - 1, hh); + QPoint head(1 - en_w, hh); + drawArrow(painter, tail, head, hh / 2); + break; + } + case FT_FRAMENUM_ACK: + { + QRect bbox (2 - en_w, height / 3, em_w - 2, height / 2); + drawCheckMark(painter, bbox); + break; + } + case FT_FRAMENUM_DUP_ACK: + { + QRect bbox (2 - en_w, (height / 3) - (line_w * 2), em_w - 2, height / 2); + drawCheckMark(painter, bbox); + bbox.moveTop(bbox.top() + (line_w * 3)); + drawCheckMark(painter, bbox); + break; + } + case FT_FRAMENUM_RETRANS_PREV: + { + int hh = height / 2; + QPoint tail(2 - en_w, hh); + QPoint head(en_w, hh); + drawChevrons(painter, tail, head, hh / 2); + break; + } + case FT_FRAMENUM_RETRANS_NEXT: + { + int hh = height / 2; + QPoint tail(en_w - 1, hh); + QPoint head(1 - en_w, hh); + drawChevrons(painter, tail, head, hh / 2); + break; + } + case FT_FRAMENUM_NONE: + default: + painter->drawEllipse(QPointF(0.0, option_vi.rect.height() / 2), 2, 2); + } + } + + painter->restore(); +} + +QSize RelatedPacketDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + /* This prevents the sizeHint for the delegate, if multiple lines are being selected */ + if (mainApp && mainApp->mainWindow()) + { + MainWindow * mw = qobject_cast<MainWindow *>(mainApp->mainWindow()); + if (mw && mw->selectedRows().count() > 1) + return QStyledItemDelegate::sizeHint(option, index); + } + + return QSize(option.fontMetrics.height() + QStyledItemDelegate::sizeHint(option, index).width(), + QStyledItemDelegate::sizeHint(option, index).height()); +} + +void RelatedPacketDelegate::drawArrow(QPainter *painter, const QPoint tail, const QPoint head, int head_size) const +{ + int x_mul = head.x() > tail.x() ? -1 : 1; + QPoint head_points[] = { + head, + QPoint(head.x() + (head_size * x_mul), head.y() + (head_size / 2)), + QPoint(head.x() + (head_size * x_mul), head.y() - (head_size / 2)), + }; + + painter->drawLine(tail.x(), tail.y(), head.x() + (head_size * x_mul), head.y()); + painter->drawPolygon(head_points, 3); +} + +void RelatedPacketDelegate::drawChevrons(QPainter *painter, const QPoint tail, const QPoint head, int head_size) const +{ + int x_mul = head.x() > tail.x() ? -1 : 1; + QPoint head_points1[] = { + head, + QPoint(head.x() + (head_size * x_mul), head.y() + (head_size / 2)), + QPoint(head.x() + (head_size * x_mul), head.y() - (head_size / 2)), + }; + QPoint head2(head.x() + (head_size * x_mul), head.y()); + QPoint head_points2[] = { + head2, + QPoint(head2.x() + (head_size * x_mul), head2.y() + (head_size / 2)), + QPoint(head2.x() + (head_size * x_mul), head2.y() - (head_size / 2)), + }; + + painter->drawPolygon(head_points1, 3); + painter->drawPolygon(head_points2, 3); +} + +void RelatedPacketDelegate::drawCheckMark(QPainter *painter, const QRect bbox) const +{ + QPoint cm_points[] = { + QPoint(bbox.x(), bbox.y() + (bbox.height() / 2)), + QPoint(bbox.x() + (bbox.width() / 4), bbox.y() + (bbox.height() * 3 / 4)), + bbox.topRight() + }; + painter->drawPolyline(cm_points, 3); +} + +void RelatedPacketDelegate::clear() +{ + related_frames_.clear(); + current_frame_ = 0; + conv_ = NULL; +} + +void RelatedPacketDelegate::setCurrentFrame(guint32 current_frame) + { + current_frame_ = current_frame; + foreach (ft_framenum_type_t framenum_type, related_frames_) { + addRelatedFrame(-1, framenum_type); /* No need to check if this element belongs to the hash... */ + } + } + +void RelatedPacketDelegate::addRelatedFrame(int frame_num, ft_framenum_type_t framenum_type) +{ + if (frame_num != -1 && !related_frames_.contains(frame_num)) + related_frames_[frame_num] = framenum_type; + + // Last match wins. Last match might not make sense, however. + if (current_frame_ > 0) { + switch (framenum_type) { + case FT_FRAMENUM_REQUEST: + related_frames_[current_frame_] = FT_FRAMENUM_RESPONSE; + break; + case FT_FRAMENUM_RESPONSE: + related_frames_[current_frame_] = FT_FRAMENUM_REQUEST; + break; + default: + break; + } + } +} + +void RelatedPacketDelegate::setConversation(conversation *conv) +{ + conv_ = conv; +} diff --git a/ui/qt/models/related_packet_delegate.h b/ui/qt/models/related_packet_delegate.h new file mode 100644 index 00000000..927129a0 --- /dev/null +++ b/ui/qt/models/related_packet_delegate.h @@ -0,0 +1,51 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef RELATED_PACKET_DELEGATE_H +#define RELATED_PACKET_DELEGATE_H + +#include <config.h> + +#include "epan/conversation.h" + +#include <QHash> +#include <QStyledItemDelegate> + +class QPainter; +struct conversation; + +class RelatedPacketDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + RelatedPacketDelegate(QWidget *parent = 0); + void clear(); + void setCurrentFrame(guint32 current_frame); + void setConversation(struct conversation *conv); + +public slots: + void addRelatedFrame(int frame_num, ft_framenum_type_t framenum_type = FT_FRAMENUM_NONE); + +protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + +private: + QHash<int, ft_framenum_type_t> related_frames_; + struct conversation *conv_; + guint32 current_frame_; + + void drawArrow(QPainter *painter, const QPoint tail, const QPoint head, int head_size) const; + void drawChevrons(QPainter *painter, const QPoint tail, const QPoint head, int head_size) const; + void drawCheckMark(QPainter *painter, const QRect bbox) const; +}; + +#endif // RELATED_PACKET_DELEGATE_H diff --git a/ui/qt/models/resolved_addresses_models.cpp b/ui/qt/models/resolved_addresses_models.cpp new file mode 100644 index 00000000..48dd2f09 --- /dev/null +++ b/ui/qt/models/resolved_addresses_models.cpp @@ -0,0 +1,210 @@ +/* resolved_addresses_models.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/resolved_addresses_models.h> + +#include <glib.h> + +#include "file.h" + +#include "epan/addr_resolv.h" +#include <wiretap/wtap.h> + +extern "C" +{ + +static void +serv_port_hash_to_qstringlist(gpointer key, gpointer value, gpointer member_ptr) +{ + PortsModel *model = static_cast<PortsModel *>(member_ptr); + serv_port_t *serv_port = (serv_port_t *)value; + guint port = GPOINTER_TO_UINT(key); + + if (serv_port->tcp_name) { + QStringList entries; + + entries << serv_port->tcp_name; + entries << QString::number(port); + entries << "tcp"; + model->appendRow(entries); + } + if (serv_port->udp_name) { + QStringList entries; + + entries << serv_port->udp_name; + entries << QString::number(port); + entries << "udp"; + model->appendRow(entries); + } + if (serv_port->sctp_name) { + QStringList entries; + + entries << serv_port->sctp_name; + entries << QString::number(port); + entries << "sctp"; + model->appendRow(entries); + } + if (serv_port->dccp_name) { + QStringList entries; + + entries << serv_port->dccp_name; + entries << QString::number(port); + entries << "dccp"; + model->appendRow(entries); + } +} + +static void +ipv4_hash_table_resolved_to_list(gpointer, gpointer value, gpointer sl_ptr) +{ + QList<QStringList> *hosts = (QList<QStringList> *) sl_ptr; + hashipv4_t *ipv4_hash_table_entry = (hashipv4_t *) value; + + if ((ipv4_hash_table_entry->flags & NAME_RESOLVED)) { + *hosts << (QStringList() << QString(ipv4_hash_table_entry->ip) << QString(ipv4_hash_table_entry->name)); + } +} + +static void +ipv6_hash_table_resolved_to_list(gpointer, gpointer value, gpointer sl_ptr) +{ + QList<QStringList> *hosts = (QList<QStringList> *) sl_ptr; + hashipv6_t *ipv6_hash_table_entry = (hashipv6_t *) value; + + if ((ipv6_hash_table_entry->flags & NAME_RESOLVED)) { + *hosts << (QStringList() << QString(ipv6_hash_table_entry->ip6) << QString(ipv6_hash_table_entry->name)); + } +} + +static void +eth_hash_to_qstringlist(gpointer, gpointer value, gpointer sl_ptr) +{ + QList<QStringList> *values = (QList<QStringList> *) sl_ptr; + hashether_t* tp = (hashether_t*)value; + + *values << (QStringList() << QString(get_hash_ether_hexaddr(tp)) << QString(get_hash_ether_resolved_name(tp))); +} + +static void +manuf_hash_to_qstringlist(gpointer key, gpointer value, gpointer sl_ptr) +{ + QList<QStringList> *values = (QList<QStringList> *) sl_ptr; + hashmanuf_t *manuf = (hashmanuf_t*)value; + guint eth_as_guint = GPOINTER_TO_UINT(key); + + QString entry = QString("%1:%2:%3") + .arg((eth_as_guint >> 16 & 0xff), 2, 16, QChar('0')) + .arg((eth_as_guint >> 8 & 0xff), 2, 16, QChar('0')) + .arg((eth_as_guint & 0xff), 2, 16, QChar('0')); + + *values << (QStringList() << entry << QString(get_hash_manuf_resolved_name(manuf))); +} + +static void +wka_hash_to_qstringlist(gpointer key, gpointer value, gpointer sl_ptr) +{ + QList<QStringList> *values = (QList<QStringList> *) sl_ptr; + gchar *name = (gchar *)value; + guint8 *eth_addr = (guint8*)key; + + QString entry = QString("%1:%2:%3:%4:%5:%6") + .arg(eth_addr[0], 2, 16, QChar('0')) + .arg(eth_addr[1], 2, 16, QChar('0')) + .arg(eth_addr[2], 2, 16, QChar('0')) + .arg(eth_addr[3], 2, 16, QChar('0')) + .arg(eth_addr[4], 2, 16, QChar('0')) + .arg(eth_addr[5], 2, 16, QChar('0')); + + // We should filter on only those actually resolved, not display + // everything in wka + *values << (QStringList() << entry << QString(name)); +} + +} + +EthernetAddressModel::EthernetAddressModel(QObject * parent): + AStringListListModel(parent) +{ + populate(); +} + +QStringList EthernetAddressModel::headerColumns() const +{ + return QStringList() << tr("Type") << tr("Address") << tr("Name"); +} + +QStringList EthernetAddressModel::filterValues() const +{ + return QStringList() + << tr("All entries") + << tr("Hosts") + << tr("Ethernet Addresses") << tr("Ethernet Manufacturers") + << tr("Ethernet Well-Known Addresses"); +} + +void EthernetAddressModel::populate() +{ + QList<QStringList> hosts; // List of (address, names) + if (wmem_map_t *ipv4_hash_table = get_ipv4_hash_table()) { + wmem_map_foreach(ipv4_hash_table, ipv4_hash_table_resolved_to_list, &hosts); + } + if (wmem_map_t *ipv6_hash_table = get_ipv6_hash_table()) { + wmem_map_foreach(ipv6_hash_table, ipv6_hash_table_resolved_to_list, &hosts); + } + const QString &hosts_label = tr("Hosts"); + foreach (const QStringList &addr_name, hosts) + appendRow(QStringList() << hosts_label << addr_name); + + QList<QStringList> values; + if (wmem_map_t *eth_hashtable = get_eth_hashtable()) { + wmem_map_foreach(eth_hashtable, eth_hash_to_qstringlist, &values); + } + const QString ð_label = tr("Ethernet Addresses"); + foreach (const QStringList &line, values) + appendRow(QStringList() << eth_label << line); + values.clear(); + if (wmem_map_t *eth_hashtable = get_manuf_hashtable()) { + wmem_map_foreach(eth_hashtable, manuf_hash_to_qstringlist, &values); + } + const QString &manuf_label = tr("Ethernet Manufacturers"); + foreach (const QStringList &line, values) + appendRow(QStringList() << manuf_label << line); + values.clear(); + if (wmem_map_t *eth_hashtable = get_wka_hashtable()) { + wmem_map_foreach(eth_hashtable, wka_hash_to_qstringlist, &values); + } + const QString &wka_label = tr("Ethernet Well-Known Addresses"); + foreach (const QStringList &line, values) + appendRow(QStringList() << wka_label << line); +} + +PortsModel::PortsModel(QObject * parent): + AStringListListModel(parent) +{ + populate(); +} + +QStringList PortsModel::filterValues() const +{ + return QStringList() + << tr("All entries") << tr("tcp") << tr("udp") << tr("sctp") << tr("dccp"); +} + +QStringList PortsModel::headerColumns() const +{ + return QStringList() << tr("Name") << tr("Port") << tr("Type"); +} + +void PortsModel::populate() +{ + wmem_map_t *serv_port_hashtable = get_serv_port_hashtable(); + if (serv_port_hashtable) { + wmem_map_foreach(serv_port_hashtable, serv_port_hash_to_qstringlist, this); + } +} diff --git a/ui/qt/models/resolved_addresses_models.h b/ui/qt/models/resolved_addresses_models.h new file mode 100644 index 00000000..32ca1b15 --- /dev/null +++ b/ui/qt/models/resolved_addresses_models.h @@ -0,0 +1,48 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef RESOLVED_ADDRESSES_MODELS_H +#define RESOLVED_ADDRESSES_MODELS_H + +#include <ui/qt/models/astringlist_list_model.h> + +#include <QAbstractListModel> +#include <QSortFilterProxyModel> + +class EthernetAddressModel : public AStringListListModel +{ + Q_OBJECT + +public: + EthernetAddressModel(QObject * parent = Q_NULLPTR); + + QStringList filterValues() const; + +protected: + QStringList headerColumns() const override; + void populate(); + +}; + +class PortsModel : public AStringListListModel +{ + Q_OBJECT + +public: + PortsModel(QObject * parent = Q_NULLPTR); + + QStringList filterValues() const; + +protected: + QStringList headerColumns() const override; + void populate(); + +}; + +#endif // RESOLVED_ADDRESSES_MODELS_H diff --git a/ui/qt/models/sparkline_delegate.cpp b/ui/qt/models/sparkline_delegate.cpp new file mode 100644 index 00000000..457c8295 --- /dev/null +++ b/ui/qt/models/sparkline_delegate.cpp @@ -0,0 +1,109 @@ +/* sparkline_delegate.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/sparkline_delegate.h> + +#include <QPainter> +#include <QApplication> + +#define SPARKLINE_MIN_EM_WIDTH 10 + +void SparkLineDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QList<int> points = qvariant_cast<QList<int> >(index.data(Qt::UserRole)); + int max = 1; + // We typically draw a sparkline alongside some text. Size our + // drawing area based on an Em width. and a bit of eyballing on + // Linux, macOS, and Windows. + int em_w = option.fontMetrics.height(); + int content_w = option.rect.width() - (em_w / 4); + int content_h = option.fontMetrics.ascent() - 1; + int val; + qreal idx = 0.0; + qreal step_w = em_w / 10.0; + qreal steps = content_w / step_w; + QVector<QPointF> fpoints; + + QStyledItemDelegate::paint(painter, option, index); + + if (points.isEmpty() || steps < 1.0 || content_h <= 0) { + return; + } + + while ((qreal) points.length() > steps) { + points.removeFirst(); + } + + foreach (val, points) { + if (val > max) max = val; + } + + foreach (val, points) { + fpoints.append(QPointF(idx, (qreal) content_h - (val * content_h / max))); + idx = idx + step_w; + } + + QStyleOptionViewItem option_vi = option; + QStyledItemDelegate::initStyleOption(&option_vi, index); + + painter->save(); + + if (QApplication::style()->objectName().contains("vista")) { + // QWindowsVistaStyle::drawControl does this internally. Unfortunately there + // doesn't appear to be a more general way to do this. + option_vi.palette.setColor(QPalette::All, QPalette::HighlightedText, option_vi.palette.color(QPalette::Active, QPalette::Text)); + } + + QPalette::ColorGroup cg = option_vi.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(option_vi.state & QStyle::State_Active)) + cg = QPalette::Inactive; +#if defined(Q_OS_WIN) + if (option_vi.state & QStyle::State_Selected) { +#else + if ((option_vi.state & QStyle::State_Selected) && !(option_vi.state & QStyle::State_MouseOver)) { +#endif + painter->setPen(option_vi.palette.color(cg, QPalette::HighlightedText)); + } else { + painter->setPen(option_vi.palette.color(cg, QPalette::Text)); + } + + // As a general rule, aliased painting renders to pixels and + // antialiased painting renders to mathematical coordinates: + // https://doc.qt.io/qt-5/coordsys.html + // Shift our coordinates by 0.5 pixels, otherwise our lines end + // up blurry. + painter->setRenderHint(QPainter::Antialiasing, true); + painter->translate( + option.rect.x() + (em_w / 8) + 0.5, + option.rect.y() + ((option.rect.height() - option.fontMetrics.height()) / 2) + 1 + 0.5); + painter->drawPolyline(QPolygonF(fpoints)); + + // Some sparklines are decorated with dots at the beginning and end. + // Ours look better without in my (gcc) opinion. +// painter->setPen(Qt::NoPen); +// painter->setBrush(option.palette.foreground()); +// painter->drawEllipse(fpoints.first(), 2, 2); + +// painter->setBrush(Qt::red); +// painter->drawEllipse(fpoints.last(), 2, 2); + + painter->restore(); +} + +QSize SparkLineDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const { + return QSize(option.fontMetrics.height() * SPARKLINE_MIN_EM_WIDTH, QStyledItemDelegate::sizeHint(option, index).height()); +} + +QWidget *SparkLineDelegate::createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const +{ + return NULL; +} diff --git a/ui/qt/models/sparkline_delegate.h b/ui/qt/models/sparkline_delegate.h new file mode 100644 index 00000000..ddbe947a --- /dev/null +++ b/ui/qt/models/sparkline_delegate.h @@ -0,0 +1,35 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef SPARKLINE_DELEGATE_H +#define SPARKLINE_DELEGATE_H + +#include <QStyledItemDelegate> + +class SparkLineDelegate : public QStyledItemDelegate +{ +public: + SparkLineDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) {} + +protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; + +signals: + +public slots: + +}; + +Q_DECLARE_METATYPE(QList<int>) + +#endif // SPARKLINE_DELEGATE_H diff --git a/ui/qt/models/supported_protocols_model.cpp b/ui/qt/models/supported_protocols_model.cpp new file mode 100644 index 00000000..04e62572 --- /dev/null +++ b/ui/qt/models/supported_protocols_model.cpp @@ -0,0 +1,261 @@ +/* supported_protocols_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <QSortFilterProxyModel> +#include <QStringList> +#include <QPalette> +#include <QApplication> +#include <QBrush> +#include <QRegularExpression> + +#include <ui/qt/models/supported_protocols_model.h> + +SupportedProtocolsItem::SupportedProtocolsItem(protocol_t* proto, const char *name, const char* filter, ftenum_t ftype, const char* descr, SupportedProtocolsItem* parent) + : ModelHelperTreeItem<SupportedProtocolsItem>(parent), + proto_(proto), + name_(name), + filter_(filter), + ftype_(ftype), + descr_(descr) +{ +} + +SupportedProtocolsItem::~SupportedProtocolsItem() +{ +} + + +SupportedProtocolsModel::SupportedProtocolsModel(QObject *parent) : + QAbstractItemModel(parent), + root_(new SupportedProtocolsItem(NULL, NULL, NULL, FT_NONE, NULL, NULL)), + field_count_(0) +{ +} + +SupportedProtocolsModel::~SupportedProtocolsModel() +{ + delete root_; +} + +int SupportedProtocolsModel::rowCount(const QModelIndex &parent) const +{ + SupportedProtocolsItem *parent_item; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<SupportedProtocolsItem*>(parent.internalPointer()); + + if (parent_item == NULL) + return 0; + + return parent_item->childCount(); +} + +int SupportedProtocolsModel::columnCount(const QModelIndex&) const +{ + return colLast; +} + +QVariant SupportedProtocolsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + + switch ((enum SupportedProtocolsColumn)section) { + case colName: + return tr("Name"); + case colFilter: + return tr("Filter"); + case colType: + return tr("Type"); + case colDescription: + return tr("Description"); + default: + break; + } + } + return QVariant(); +} + +QModelIndex SupportedProtocolsModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + SupportedProtocolsItem* item = static_cast<SupportedProtocolsItem*>(index.internalPointer()); + if (item != NULL) { + SupportedProtocolsItem* parent_item = item->parentItem(); + if (parent_item != NULL) { + if (parent_item == root_) + return QModelIndex(); + + return createIndex(parent_item->row(), 0, parent_item); + } + } + + return QModelIndex(); +} + +QModelIndex SupportedProtocolsModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + SupportedProtocolsItem *parent_item, *child_item; + + if (!parent.isValid()) + parent_item = root_; + else + parent_item = static_cast<SupportedProtocolsItem*>(parent.internalPointer()); + + Q_ASSERT(parent_item); + + child_item = parent_item->child(row); + if (child_item) { + return createIndex(row, column, child_item); + } + + return QModelIndex(); +} + +QVariant SupportedProtocolsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || role != Qt::DisplayRole) + return QVariant(); + + SupportedProtocolsItem* item = static_cast<SupportedProtocolsItem*>(index.internalPointer()); + if (item == NULL) + return QVariant(); + + switch ((enum SupportedProtocolsColumn)index.column()) { + case colName: + return item->name(); + case colFilter: + return item->filter(); + case colType: + if (index.parent().isValid()) + return QString(ftype_pretty_name(item->type())); + + return QVariant(); + case colDescription: + return item->description(); + default: + break; + } + + return QVariant(); +} + +void SupportedProtocolsModel::populate() +{ + void *proto_cookie; + void *field_cookie; + + beginResetModel(); + + SupportedProtocolsItem *protoItem, *fieldItem; + protocol_t *protocol; + + for (int proto_id = proto_get_first_protocol(&proto_cookie); proto_id != -1; + proto_id = proto_get_next_protocol(&proto_cookie)) { + + protocol = find_protocol_by_id(proto_id); + protoItem = new SupportedProtocolsItem(protocol, proto_get_protocol_short_name(protocol), proto_get_protocol_filter_name(proto_id), FT_PROTOCOL, proto_get_protocol_long_name(protocol), root_); + root_->prependChild(protoItem); + + for (header_field_info *hfinfo = proto_get_first_protocol_field(proto_id, &field_cookie); hfinfo != NULL; + hfinfo = proto_get_next_protocol_field(proto_id, &field_cookie)) { + if (hfinfo->same_name_prev_id != -1) + continue; + + fieldItem = new SupportedProtocolsItem(protocol, hfinfo->name, hfinfo->abbrev, hfinfo->type, hfinfo->blurb, protoItem); + protoItem->prependChild(fieldItem); + field_count_++; + } + } + + endResetModel(); +} + + + +SupportedProtocolsProxyModel::SupportedProtocolsProxyModel(QObject * parent) +: QSortFilterProxyModel(parent), +filter_() +{ +} + +bool SupportedProtocolsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + //Use SupportedProtocolsItem directly for better performance + SupportedProtocolsItem* left_item = static_cast<SupportedProtocolsItem*>(left.internalPointer()); + SupportedProtocolsItem* right_item = static_cast<SupportedProtocolsItem*>(right.internalPointer()); + + if ((left_item != NULL) && (right_item != NULL)) { + int compare_ret = left_item->name().compare(right_item->name()); + if (compare_ret < 0) + return true; + } + + return false; +} + +bool SupportedProtocolsProxyModel::filterAcceptItem(SupportedProtocolsItem& item) const +{ + QRegularExpression regex(filter_, QRegularExpression::CaseInsensitiveOption); + if (! regex.isValid()) + return false; + + if (item.name().contains(regex)) + return true; + + if (item.filter().contains(regex)) + return true; + + if (item.description().contains(regex)) + return true; + + return false; +} + +bool SupportedProtocolsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex nameIdx = sourceModel()->index(sourceRow, SupportedProtocolsModel::colName, sourceParent); + SupportedProtocolsItem* item = static_cast<SupportedProtocolsItem*>(nameIdx.internalPointer()); + if (item == NULL) + return true; + + if (!filter_.isEmpty()) { + if (filterAcceptItem(*item)) + return true; + + if (!nameIdx.parent().isValid()) + { + SupportedProtocolsItem* child_item; + for (int row = 0; row < item->childCount(); row++) + { + child_item = item->child(row); + if ((child_item != NULL) && (filterAcceptItem(*child_item))) + return true; + } + } + + return false; + } + + return true; +} + +void SupportedProtocolsProxyModel::setFilter(const QString& filter) +{ + filter_ = filter; + invalidateFilter(); +} diff --git a/ui/qt/models/supported_protocols_model.h b/ui/qt/models/supported_protocols_model.h new file mode 100644 index 00000000..d964ef68 --- /dev/null +++ b/ui/qt/models/supported_protocols_model.h @@ -0,0 +1,97 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef SUPPORTED_PROTOCOLS_MODEL_H +#define SUPPORTED_PROTOCOLS_MODEL_H + +#include <config.h> + +#include <ui/qt/models/tree_model_helpers.h> + +#include <epan/proto.h> + +#include <QSortFilterProxyModel> + +class SupportedProtocolsItem : public ModelHelperTreeItem<SupportedProtocolsItem> +{ +public: + SupportedProtocolsItem(protocol_t* proto, const char *name, const char* filter, ftenum_t ftype, const char* descr, SupportedProtocolsItem* parent); + virtual ~SupportedProtocolsItem(); + + protocol_t* protocol() const {return proto_; } + QString name() const { return name_; } + ftenum_t type() const {return ftype_; } + QString filter() const { return filter_; } + QString description() const { return descr_; } + +private: + protocol_t* proto_; + QString name_; + QString filter_; + ftenum_t ftype_; + QString descr_; +}; + + +class SupportedProtocolsModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit SupportedProtocolsModel(QObject * parent = Q_NULLPTR); + virtual ~SupportedProtocolsModel(); + + enum SupportedProtocolsColumn { + colName = 0, + colFilter, + colType, + colDescription, + colLast + }; + + int fieldCount() {return field_count_;} + + QModelIndex index(int row, int column, + const QModelIndex & = QModelIndex()) const; + QModelIndex parent(const QModelIndex &) const; + QVariant data(const QModelIndex &index, int role) const; + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + void populate(); + +private: + SupportedProtocolsItem* root_; + int field_count_; +}; + +class SupportedProtocolsProxyModel : public QSortFilterProxyModel +{ +public: + + explicit SupportedProtocolsProxyModel(QObject * parent = Q_NULLPTR); + + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + + void setFilter(const QString& filter); + +protected: + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; + bool filterAcceptItem(SupportedProtocolsItem& item) const; + +private: + + QString filter_; +}; + +#endif // SUPPORTED_PROTOCOLS_MODEL_H diff --git a/ui/qt/models/timeline_delegate.cpp b/ui/qt/models/timeline_delegate.cpp new file mode 100644 index 00000000..07a7409c --- /dev/null +++ b/ui/qt/models/timeline_delegate.cpp @@ -0,0 +1,128 @@ +/* timeline_delegate.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/timeline_delegate.h> +#include <ui/qt/models/atap_data_model.h> + +#include <ui/qt/utils/color_utils.h> + +#include <QApplication> +#include <QPainter> +#include <QTreeView> +#include <QAbstractProxyModel> + +// XXX We might want to move this to conversation_dialog.cpp. + +// PercentBarDelegate uses a stronger blend value, but its bars are also +// more of a prominent feature. Make the blend weaker here so that we don't +// obscure our text. +static const double bar_blend_ = 0.08; + +TimelineDelegate::TimelineDelegate(QWidget *parent) : + QStyledItemDelegate(parent) +{ + _dataRole = Qt::UserRole; +} + +void TimelineDelegate::setDataRole(int dataRole) +{ + _dataRole = dataRole; +} + +void TimelineDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItem option_vi = option; + QStyledItemDelegate::initStyleOption(&option_vi, index); + + bool drawBar = false; + struct timeline_span span_px = index.data(_dataRole).value<struct timeline_span>(); + if (_dataRole == ATapDataModel::TIMELINE_DATA) { + double span_s = span_px.maxRelTime - span_px.minRelTime; + QTreeView * tree = qobject_cast<QTreeView *>(parent()); + if (tree) { + QAbstractProxyModel * proxy = qobject_cast<QAbstractProxyModel *>(tree->model()); + if (proxy && proxy->sourceModel()) { + QModelIndex indexStart = proxy->mapFromSource(proxy->sourceModel()->index(0, span_px.colStart)); + int colStart = -1; + int start_px = 0; + if (indexStart.isValid()) { + colStart = indexStart.column(); + start_px = tree->columnWidth(colStart); + } + int colDuration = -1; + int column_px = start_px; + QModelIndex indexDuration = proxy->mapFromSource(proxy->sourceModel()->index(0, span_px.colDuration)); + if (indexDuration.isValid()) { + colDuration = indexDuration.column(); + column_px += tree->columnWidth(colDuration); + } + + span_px.start = ((span_px.startTime - span_px.minRelTime) * column_px) / span_s; + span_px.width = ((span_px.stopTime - span_px.startTime) * column_px) / span_s; + + if (index.column() == colStart) { + drawBar = true; + } else if (index.column() == colDuration) { + drawBar = true; + span_px.start -= start_px; + } + } + } + } + + if (!drawBar) { + QStyledItemDelegate::paint(painter, option, index); + return; + } + + // Paint our rect with no text using the current style, then draw our + // bar and text over it. + option_vi.text = QString(); + QStyle *style = option_vi.widget ? option_vi.widget->style() : QApplication::style(); + style->drawControl(QStyle::CE_ItemViewItem, &option_vi, painter, option_vi.widget); + + if (QApplication::style()->objectName().contains("vista")) { + // QWindowsVistaStyle::drawControl does this internally. Unfortunately there + // doesn't appear to be a more general way to do this. + option_vi.palette.setColor(QPalette::All, QPalette::HighlightedText, + option_vi.palette.color(QPalette::Active, QPalette::Text)); + } + + QPalette::ColorGroup cg = option_vi.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + QColor text_color = option_vi.palette.color(cg, QPalette::Text); + QColor bar_color = ColorUtils::alphaBlend(option_vi.palette.windowText(), + option_vi.palette.window(), bar_blend_); + + if (cg == QPalette::Normal && !(option_vi.state & QStyle::State_Active)) + cg = QPalette::Inactive; + if (option_vi.state & QStyle::State_Selected) { + text_color = option_vi.palette.color(cg, QPalette::HighlightedText); + bar_color = ColorUtils::alphaBlend(option_vi.palette.color(cg, QPalette::Window), + option_vi.palette.color(cg, QPalette::Highlight), + bar_blend_); + } + + painter->save(); + int border_radius = 3; // We use 3 px elsewhere, e.g. filter combos. + QRect timeline_rect = option.rect; + timeline_rect.adjust(span_px.start, 1, 0, -1); + timeline_rect.setWidth(span_px.width); + painter->setClipRect(option.rect); + painter->setPen(Qt::NoPen); + painter->setBrush(bar_color); + painter->drawRoundedRect(timeline_rect, border_radius, border_radius); + painter->restore(); + + painter->save(); + painter->setPen(text_color); + painter->drawText(option.rect, Qt::AlignCenter, index.data(Qt::DisplayRole).toString()); + painter->restore(); +} diff --git a/ui/qt/models/timeline_delegate.h b/ui/qt/models/timeline_delegate.h new file mode 100644 index 00000000..06218b87 --- /dev/null +++ b/ui/qt/models/timeline_delegate.h @@ -0,0 +1,66 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef TIMELINE_DELEGATE_H +#define TIMELINE_DELEGATE_H + +/* + * @file Timeline delegate. + * + * QStyledItemDelegate subclass that will draw a timeline indicator for + * the specified value. + * + * This is intended to be used in QTreeWidgets to show timelines, e.g. for + * conversations. + * To use it, first call setItemDelegate: + * + * myTreeWidget()->setItemDelegateForColumn(col_time_start_, new TimelineDelegate()); + * + * Then, for each QTreeWidgetItem, set or return a timeline_span for the start and end + * of the timeline in pixels relative to the column width. + * + * setData(col_start_, Qt::UserRole, start_span); + * setData(col_end_, Qt::UserRole, end_span); + * + */ + +#include <QStyledItemDelegate> + +// Pixels are relative to item rect and will be clipped. +struct timeline_span { + int start; + int width; + + double startTime; + double stopTime; + double minRelTime; + double maxRelTime; + + int colStart; + int colDuration; +}; + +Q_DECLARE_METATYPE(timeline_span) + +class TimelineDelegate : public QStyledItemDelegate +{ +public: + TimelineDelegate(QWidget *parent = 0); + + void setDataRole(int role); + +protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; +private: + + int _dataRole; +}; + +#endif // TIMELINE_DELEGATE_H diff --git a/ui/qt/models/tree_model_helpers.h b/ui/qt/models/tree_model_helpers.h new file mode 100644 index 00000000..5734b660 --- /dev/null +++ b/ui/qt/models/tree_model_helpers.h @@ -0,0 +1,89 @@ +/** @file + * + * Utility template classes for basic tree model functionality + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef TREE_MODEL_HELPERS_H +#define TREE_MODEL_HELPERS_H + +#include <config.h> +#include <ui/qt/utils/variant_pointer.h> + +#include <QAbstractItemModel> + +//Base class to inherit basic tree item from +template <typename Item> +class ModelHelperTreeItem +{ +public: + ModelHelperTreeItem(Item* parent) + : parent_(parent) + { + } + + virtual ~ModelHelperTreeItem() + { + for (int row = 0; row < childItems_.count(); row++) + { + delete VariantPointer<Item>::asPtr(childItems_.value(row)); + } + + childItems_.clear(); + } + + void appendChild(Item* child) + { + childItems_.append(VariantPointer<Item>::asQVariant(child)); + } + + void prependChild(Item* child) + { + childItems_.prepend(VariantPointer<Item>::asQVariant(child)); + } + + + void insertChild(int row, Item* child) + { + childItems_.insert(row, VariantPointer<Item>::asQVariant(child)); + } + + void removeChild(int row) + { + delete VariantPointer<Item>::asPtr(childItems_.value(row)); + childItems_.removeAt(row); + } + + Item* child(int row) + { + return VariantPointer<Item>::asPtr(childItems_.value(row)); + } + + int childCount() const + { + return static_cast<int>(childItems_.count()); + } + + int row() + { + if (parent_) + { + return static_cast<int>(parent_->childItems_.indexOf(VariantPointer<Item>::asQVariant((Item *)this))); + } + + return 0; + } + + Item* parentItem() {return parent_; } + +protected: + Item* parent_; + QList<QVariant> childItems_; +}; + +#endif // TREE_MODEL_HELPERS_H diff --git a/ui/qt/models/uat_delegate.cpp b/ui/qt/models/uat_delegate.cpp new file mode 100644 index 00000000..4e7d25b3 --- /dev/null +++ b/ui/qt/models/uat_delegate.cpp @@ -0,0 +1,221 @@ +/* uat_delegate.cpp + * Delegates for editing various field types in a UAT record. + * + * Copyright 2016 Peter Wu <peter@lekensteyn.nl> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/uat_delegate.h> +#include <epan/packet.h> // for get_dissector_names() +#include "epan/value_string.h" +#include <wsutil/ws_assert.h> +#include <QRegularExpression> +#include <QComboBox> +#include <QEvent> +#include <QFileDialog> +#include <QLineEdit> +#include <QCheckBox> +#include <QColorDialog> + +#include <ui/qt/widgets/display_filter_edit.h> +#include <ui/qt/widgets/dissector_syntax_line_edit.h> +#include <ui/qt/widgets/field_filter_edit.h> +#include <ui/qt/widgets/editor_file_dialog.h> +#include <ui/qt/widgets/path_selection_edit.h> + +// The Qt docs suggest overriding updateEditorGeometry, but the +// defaults seem sane. + +UatDelegate::UatDelegate(QObject *parent) : QStyledItemDelegate(parent) +{ +} + +uat_field_t *UatDelegate::indexToField(const QModelIndex &index) const +{ + const QVariant v = index.model()->data(index, Qt::UserRole); + return static_cast<uat_field_t *>(v.value<void *>()); +} + +QWidget *UatDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + uat_field_t *field = indexToField(index); + QWidget *editor = nullptr; + + switch (field->mode) { + case PT_TXTMOD_DIRECTORYNAME: + case PT_TXTMOD_FILENAME: + if (index.isValid()) { + QString filename_old = index.model()->data(index, Qt::EditRole).toString(); + PathSelectionEdit * pathEdit = new PathSelectionEdit(field->title, QString(), field->mode != PT_TXTMOD_DIRECTORYNAME, parent); + connect(pathEdit, &PathSelectionEdit::pathChanged, this, &UatDelegate::pathHasChanged); + return pathEdit; + } + break; + case PT_TXTMOD_COLOR: + if (index.isValid()) { + QColor color(index.model()->data(index, Qt::DecorationRole).toString()); + QColorDialog * colorDialog = new QColorDialog(color, parent); + // Don't fall through and set setAutoFillBackground(true) + return colorDialog; + } + break; + + case PT_TXTMOD_ENUM: + { + // Note: the string repr. is written, not the integer value. + QComboBox *cb_editor = new QComboBox(parent); + const value_string *enum_vals = (const value_string *)field->fld_data; + for (int i = 0; enum_vals[i].strptr != nullptr; i++) { + cb_editor->addItem(enum_vals[i].strptr); + } + editor = cb_editor; + cb_editor->setMinimumWidth(cb_editor->minimumSizeHint().width()); + break; + } + + case PT_TXTMOD_DISSECTOR: + { + editor = new DissectorSyntaxLineEdit(parent); + break; + } + + case PT_TXTMOD_STRING: + // TODO add a live validator? Should SyntaxLineEdit be used? + editor = QStyledItemDelegate::createEditor(parent, option, index); + break; + + case PT_TXTMOD_DISPLAY_FILTER: + editor = new DisplayFilterEdit(parent); + break; + + case PT_TXTMOD_PROTO_FIELD: + editor = new FieldFilterEdit(parent); + break; + + case PT_TXTMOD_HEXBYTES: + { + // Requires input of the form "ab cd ef" (with possibly no or a colon + // separator instead of a single whitespace) for the editor to accept. + QRegularExpression hexbytes_regex("([0-9a-f]{2}[ :]?)*"); + hexbytes_regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + // QString types from QStyledItemDelegate are documented to return a + // QLineEdit. Note that Qt returns a subclass from QLineEdit which + // automatically adapts the width to the typed contents. + QLineEdit *le_editor = static_cast<QLineEdit *>( + QStyledItemDelegate::createEditor(parent, option, index)); + le_editor->setValidator(new QRegularExpressionValidator(hexbytes_regex, le_editor)); + editor = le_editor; + break; + } + + case PT_TXTMOD_BOOL: + // model will handle creating checkbox + break; + + case PT_TXTMOD_NONE: + break; + + default: + ws_assert_not_reached(); + break; + } + + if (editor) { + editor->setAutoFillBackground(true); + } + return editor; +} + +void UatDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + uat_field_t *field = indexToField(index); + + switch (field->mode) { + case PT_TXTMOD_DIRECTORYNAME: + case PT_TXTMOD_FILENAME: + if (index.isValid() && qobject_cast<PathSelectionEdit *>(editor)) + qobject_cast<PathSelectionEdit *>(editor)->setPath(index.model()->data(index, Qt::EditRole).toString()); + break; + case PT_TXTMOD_ENUM: + { + QComboBox *combobox = static_cast<QComboBox *>(editor); + const QString &data = index.model()->data(index, Qt::EditRole).toString(); + combobox->setCurrentText(data); + + break; + } + case PT_TXTMOD_COLOR: + { + if (qobject_cast<QColorDialog *>(editor)) + { + QColor color(index.model()->data(index, Qt::DecorationRole).toString()); + qobject_cast<QColorDialog *>(editor)->setCurrentColor(color); + } + break; + } + + default: + QStyledItemDelegate::setEditorData(editor, index); + } +} + +void UatDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + uat_field_t *field = indexToField(index); + + switch (field->mode) { + case PT_TXTMOD_DIRECTORYNAME: + case PT_TXTMOD_FILENAME: + if (index.isValid() && qobject_cast<PathSelectionEdit *>(editor)) + const_cast<QAbstractItemModel *>(index.model())->setData(index, qobject_cast<PathSelectionEdit *>(editor)->path(), Qt::EditRole); + break; + case PT_TXTMOD_ENUM: + { + QComboBox *combobox = static_cast<QComboBox *>(editor); + const QString &data = combobox->currentText(); + model->setData(index, data, Qt::EditRole); + break; + } + case PT_TXTMOD_COLOR: + //do nothing, dialog signals will update table + if (qobject_cast<QColorDialog *>(editor)) + { + QColor newColor = qobject_cast<QColorDialog *>(editor)->currentColor(); + const_cast<QAbstractItemModel *>(index.model())->setData(index, newColor.name(), Qt::EditRole); + } + break; + + default: + QStyledItemDelegate::setModelData(editor, model, index); + } +} + +void UatDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + uat_field_t *field = indexToField(index); + + switch (field->mode) { + case PT_TXTMOD_DIRECTORYNAME: + case PT_TXTMOD_FILENAME: + editor->setGeometry(option.rect); + break; + default: + QStyledItemDelegate::updateEditorGeometry(editor, option, index); + } +} + +void UatDelegate::pathHasChanged(QString) +{ + PathSelectionEdit * editor = qobject_cast<PathSelectionEdit *>(sender()); + if (editor) + emit commitData(editor); +} diff --git a/ui/qt/models/uat_delegate.h b/ui/qt/models/uat_delegate.h new file mode 100644 index 00000000..e57e533d --- /dev/null +++ b/ui/qt/models/uat_delegate.h @@ -0,0 +1,42 @@ +/** @file + * + * Delegates for editing various field types in a UAT record. + * + * Copyright 2016 Peter Wu <peter@lekensteyn.nl> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef UAT_DELEGATE_H +#define UAT_DELEGATE_H + +#include <config.h> +#include <glib.h> +#include <epan/uat-int.h> + +#include <QStyledItemDelegate> +#include <QModelIndex> + +class UatDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + UatDelegate(QObject *parent = 0); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + +protected slots: + void pathHasChanged(QString newPath); + +private: + uat_field_t *indexToField(const QModelIndex &index) const; +}; +#endif // UAT_DELEGATE_H diff --git a/ui/qt/models/uat_model.cpp b/ui/qt/models/uat_model.cpp new file mode 100644 index 00000000..a841ca8c --- /dev/null +++ b/ui/qt/models/uat_model.cpp @@ -0,0 +1,545 @@ +/* uat_model.cpp + * Data model for UAT records. + * + * Copyright 2016 Peter Wu <peter@lekensteyn.nl> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "uat_model.h" +#include <epan/to_str.h> +#include <QBrush> +#include <QDebug> + +UatModel::UatModel(QObject *parent, epan_uat *uat) : + QAbstractTableModel(parent), + uat_(0) +{ + loadUat(uat); +} + +UatModel::UatModel(QObject * parent, QString tableName) : + QAbstractTableModel(parent), + uat_(0) +{ + loadUat(uat_get_table_by_name(tableName.toStdString().c_str())); +} + +void UatModel::loadUat(epan_uat * uat) +{ + uat_ = uat; + + dirty_records.reserve(uat_->raw_data->len); + // Validate existing data such that they can be marked as invalid if necessary. + record_errors.reserve(uat_->raw_data->len); + for (int i = 0; i < (int)uat_->raw_data->len; i++) { + record_errors.push_back(QMap<int, QString>()); + checkRow(i); + // Assume that records are initially not modified. + dirty_records.push_back(false); + } +} + +void UatModel::reloadUat() +{ + beginResetModel(); + loadUat(uat_); + endResetModel(); +} + +bool UatModel::applyChanges(QString &error) +{ + if (uat_->changed) { + gchar *err = NULL; + + if (!uat_save(uat_, &err)) { + error = QString("Error while saving %1: %2").arg(uat_->name).arg(err); + g_free(err); + } + + if (uat_->post_update_cb) { + uat_->post_update_cb(); + } + return true; + } + + return false; +} + +bool UatModel::revertChanges(QString &error) +{ + // Ideally this model should remember the changes made and try to undo them + // to avoid calling post_update_cb. Calling uat_clear + uat_load is a lazy + // option and might fail (e.g. when the UAT file is removed). + if (uat_->changed) { + gchar *err = NULL; + uat_clear(uat_); + if (!uat_load(uat_, NULL, &err)) { + error = QString("Error while loading %1: %2").arg(uat_->name).arg(err); + g_free(err); + } + return true; + } + + return false; +} + +Qt::ItemFlags UatModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemFlags(); + + uat_field_t *field = &uat_->fields[index.column()]; + + Qt::ItemFlags flags = QAbstractTableModel::flags(index); + if (field->mode == PT_TXTMOD_BOOL) + { + flags |= Qt::ItemIsUserCheckable; + } + flags |= Qt::ItemIsEditable; + return flags; +} + +QVariant UatModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + void *rec = UAT_INDEX_PTR(uat_, index.row()); + uat_field_t *field = &uat_->fields[index.column()]; + if (role == Qt::DisplayRole || role == Qt::EditRole) { + char *str = NULL; + guint length = 0; + field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data); + + switch (field->mode) { + case PT_TXTMOD_HEXBYTES: + { + char* temp_str = bytes_to_str(NULL, (const guint8 *) str, length); + g_free(str); + QString qstr(temp_str); + wmem_free(NULL, temp_str); + return qstr; + } + case PT_TXTMOD_BOOL: + case PT_TXTMOD_COLOR: + return QVariant(); + default: + { + QString qstr(str); + g_free(str); + return qstr; + } + } + } + + if ((role == Qt::CheckStateRole) && (field->mode == PT_TXTMOD_BOOL)) + { + char *str = NULL; + guint length = 0; + enum Qt::CheckState state = Qt::Unchecked; + field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data); + if ((g_strcmp0(str, "TRUE") == 0) || + (g_strcmp0(str, "Enabled") == 0)) + state = Qt::Checked; + + g_free(str); + return state; + } + + if (role == Qt::UserRole) { + return QVariant::fromValue(static_cast<void *>(field)); + } + + const QMap<int, QString> &errors = record_errors[index.row()]; + // mark fields that fail the validation. + if (role == Qt::BackgroundRole) { + if (errors.contains(index.column())) { + // TODO is it OK to color cells like this? Maybe some other marker is better? + return QBrush("pink"); + } + return QVariant(); + } + + if ((role == Qt::DecorationRole) && (field->mode == PT_TXTMOD_COLOR)) { + char *str = NULL; + guint length = 0; + field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data); + + return QColor(QString(str)); + } + + // expose error message if any. + if (role == Qt::UserRole + 1) { + if (errors.contains(index.column())) { + return errors[index.column()]; + } + return QVariant(); + } + + return QVariant(); +} + +QModelIndex UatModel::findRowForColumnContent(QVariant columnContent, int columnToCheckAgainst, int role) +{ + if (! columnContent.isValid()) + return QModelIndex(); + + for (int i = 0; i < rowCount(); i++) + { + QVariant r_expr = data(index(i, columnToCheckAgainst), role); + if (r_expr == columnContent) + return index(i, columnToCheckAgainst); + } + + return QModelIndex(); +} + +QVariant UatModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal) { + return QVariant(); + } + + if (role == Qt::ToolTipRole && uat_->fields[section].desc) { + return uat_->fields[section].desc; + } + + if (role == Qt::DisplayRole) { + return uat_->fields[section].title; + } + + return QVariant(); +} + +int UatModel::rowCount(const QModelIndex &parent) const +{ + // there are no children + if (parent.isValid()) { + return 0; + } + + return uat_->raw_data->len; +} + +int UatModel::columnCount(const QModelIndex &parent) const +{ + // there are no children + if (parent.isValid()) { + return 0; + } + + return uat_->ncols; +} + +QModelIndex UatModel::appendEntry(QVariantList rowData) +{ + // Don't add an empty row, or a row with more entries than we have columns, + // but a row with fewer can be added, where the remaining entries are empty + if (rowData.count() == 0 || rowData.count() > columnCount()) + return QModelIndex(); + + QModelIndex newIndex; + int row = rowCount(); + emit beginInsertRows(QModelIndex(), row, row); + + // Initialize with given values + void *record = g_malloc0(uat_->record_size); + for (int col = 0; col < columnCount(); col++) { + uat_field_t *field = &uat_->fields[col]; + + QString data; + if (rowData.count() > col) { + if (field->mode != PT_TXTMOD_BOOL) { + data = rowData[col].toString(); + } else { + if (rowData[col].toInt() == Qt::Checked) { + data = QString("TRUE"); + } else { + data = QString("FALSE"); + } + } + } + + QByteArray bytes = field->mode == PT_TXTMOD_HEXBYTES ? QByteArray::fromHex(data.toUtf8()) : data.toUtf8(); + field->cb.set(record, bytes.constData(), (unsigned) bytes.size(), field->cbdata.set, field->fld_data); + } + uat_insert_record_idx(uat_, row, record); + if (uat_->free_cb) { + uat_->free_cb(record); + } + g_free(record); + + record_errors.insert(row, QMap<int, QString>()); + // a new row is created. For the moment all fields are empty, so validation + // will likely mark everything as invalid. Ideally validation should be + // postponed until the row (in the view) is not selected anymore + checkRow(row); + dirty_records.insert(row, true); + uat_->changed = TRUE; + + emit endInsertRows(); + + newIndex = index(row, 0, QModelIndex()); + + return newIndex; +} + +bool UatModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + uat_field_t *field = &uat_->fields[index.column()]; + + if ((role != Qt::EditRole) && + ((field->mode == PT_TXTMOD_BOOL) && (role != Qt::CheckStateRole))) + return false; + + if (data(index, role) == value) { + // Data appears unchanged, do not do additional checks. + return true; + } + + const int row = index.row(); + void *rec = UAT_INDEX_PTR(uat_, row); + + //qDebug() << "Changing (" << row << "," << index.column() << ") from " << data(index, Qt::EditRole) << " to " << value; + if (field->mode != PT_TXTMOD_BOOL) { + const QByteArray &str = value.toString().toUtf8(); + const QByteArray &bytes = field->mode == PT_TXTMOD_HEXBYTES ? QByteArray::fromHex(str) : str; + field->cb.set(rec, bytes.constData(), (unsigned) bytes.size(), field->cbdata.set, field->fld_data); + } else { + if (value.toInt() == Qt::Checked) { + field->cb.set(rec, "TRUE", 4, field->cbdata.set, field->fld_data); + } else { + field->cb.set(rec, "FALSE", 5, field->cbdata.set, field->fld_data); + } + } + + QVector<int> roles; + roles << role; + + // Check validity of all rows, obtaining a list of columns where the + // validity status has changed. + const QList<int> &updated_cols = checkRow(row); + if (!updated_cols.isEmpty()) { + roles << Qt::BackgroundRole; + //qDebug() << "validation status changed:" << updated_cols; + } + + if (record_errors[row].isEmpty()) { + // If all individual fields are valid, invoke the update callback. This + // might detect additional issues in either individual fields, or the + // combination of them. + if (uat_->update_cb) { + char *err = NULL; + if (!uat_->update_cb(rec, &err)) { + // TODO the error is not exactly on the first column, but we need a way to show the error. + record_errors[row].insert(0, err); + g_free(err); + } + } + } + uat_update_record(uat_, rec, record_errors[row].isEmpty()); + dirty_records[row] = true; + uat_->changed = TRUE; + + if (updated_cols.size() > updated_cols.count(index.column())) { + // The validation status for other columns were also affected by + // changing this field, mark those as dirty! + emit dataChanged(this->index(row, updated_cols.first()), + this->index(row, updated_cols.last()), roles); + } else { + + emit dataChanged(index, index, roles); + } + return true; +} + +bool UatModel::insertRows(int row, int count, const QModelIndex &/*parent*/) +{ + // support insertion of just one item for now. + if (count != 1 || row < 0 || row > rowCount()) + return false; + + beginInsertRows(QModelIndex(), row, row); + + // Initialize with empty values, caller should use setData to populate it. + void *record = g_malloc0(uat_->record_size); + for (int col = 0; col < columnCount(); col++) { + uat_field_t *field = &uat_->fields[col]; + field->cb.set(record, "", 0, field->cbdata.set, field->fld_data); + } + uat_insert_record_idx(uat_, row, record); + if (uat_->free_cb) { + uat_->free_cb(record); + } + g_free(record); + + record_errors.insert(row, QMap<int, QString>()); + // a new row is created. For the moment all fields are empty, so validation + // will likely mark everything as invalid. Ideally validation should be + // postponed until the row (in the view) is not selected anymore + checkRow(row); + dirty_records.insert(row, true); + uat_->changed = TRUE; + endInsertRows(); + return true; +} + +bool UatModel::removeRows(int row, int count, const QModelIndex &/*parent*/) +{ + if (count != 1 || row < 0 || row >= rowCount()) + return false; + + beginRemoveRows(QModelIndex(), row, row); + uat_remove_record_idx(uat_, row); + record_errors.removeAt(row); + dirty_records.removeAt(row); + uat_->changed = TRUE; + endRemoveRows(); + return true; +} + +void UatModel::clearAll() +{ + if (rowCount() < 1) + return; + + beginResetModel(); + uat_clear(uat_); + record_errors.clear(); + dirty_records.clear(); + uat_->changed = TRUE; + endResetModel(); +} + +QModelIndex UatModel::copyRow(QModelIndex original) +{ + if (! original.isValid()) + return QModelIndex(); + + int newRow = rowCount(); + + beginInsertRows(QModelIndex(), newRow, newRow); + + // Initialize with empty values, caller should use setData to populate it. + void *record = g_malloc0(uat_->record_size); + for (int col = 0; col < columnCount(); col++) { + uat_field_t *field = &uat_->fields[col]; + field->cb.set(record, "", 0, field->cbdata.set, field->fld_data); + } + uat_insert_record_idx(uat_, newRow, record); + if (uat_->free_cb) { + uat_->free_cb(record); + } + g_free(record); + + record_errors.insert(newRow, QMap<int, QString>()); + // a new row is created. For the moment all fields are empty, so validation + // will likely mark everything as invalid. Ideally validation should be + // postponed until the row (in the view) is not selected anymore + checkRow(newRow); + dirty_records.insert(newRow, true); + + // the UAT record has been created, now it is filled with the infromation + const void *src_record = UAT_INDEX_PTR(uat_, original.row()); + void *dst_record = UAT_INDEX_PTR(uat_, newRow); + // insertRows always initializes the record with empty value. Before copying + // over the new values, be sure to clear the old fields. + if (uat_->free_cb) { + uat_->free_cb(dst_record); + } + if (uat_->copy_cb) { + uat_->copy_cb(dst_record, src_record, uat_->record_size); + } else { + /* According to documentation of uat_copy_cb_t memcpy should be used if uat_->copy_cb is NULL */ + memcpy(dst_record, src_record, uat_->record_size); + } + gboolean src_valid = g_array_index(uat_->valid_data, gboolean, original.row()); + uat_update_record(uat_, dst_record, src_valid); + record_errors[newRow] = record_errors[original.row()]; + dirty_records[newRow] = true; + + uat_->changed = TRUE; + + endInsertRows(); + + return index(newRow, 0, QModelIndex()); +} + +bool UatModel::moveRow(int src_row, int dst_row) +{ + if (src_row < 0 || src_row >= rowCount() || dst_row < 0 || dst_row >= rowCount()) + return false; + + int dst = src_row < dst_row ? dst_row + 1 : dst_row; + + beginMoveRows(QModelIndex(), src_row, src_row, QModelIndex(), dst); + uat_move_index(uat_, src_row, dst_row); + record_errors.move(src_row, dst_row); + dirty_records.move(src_row, dst_row); + uat_->changed = TRUE; + endMoveRows(); + + return true; +} + +bool UatModel::hasErrors() const +{ + for (int i = 0; i < rowCount(); i++) { + // Ignore errors on unmodified records, these should not prevent the OK + // button from saving modifications to other entries. + if (dirty_records[i] && !record_errors[i].isEmpty()) { + return true; + } + } + return false; +} + +bool UatModel::checkField(int row, int col, char **error) const +{ + uat_field_t *field = &uat_->fields[col]; + void *rec = UAT_INDEX_PTR(uat_, row); + + if (!field->cb.chk) { + return true; + } + + char *str = NULL; + guint length; + field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data); + + bool ok = field->cb.chk(rec, str, length, field->cbdata.chk, field->fld_data, error); + g_free(str); + return ok; +} + +// Validates all fields in the given record, setting error messages as needed. +// Returns the columns that have changed (not the columns with errors). +QList<int> UatModel::checkRow(int row) +{ + Q_ASSERT(0 <= row && row < rowCount()); + + QList<int> changed; + QMap<int, QString> &errors = record_errors[row]; + for (int col = 0; col < columnCount(); col++) { + char *err; + bool error_changed = errors.remove(col) > 0; + if (!checkField(row, col, &err)) { + errors.insert(col, err); + g_free(err); + error_changed = !error_changed; + } + if (error_changed) { + changed << col; + } + } + return changed; +} diff --git a/ui/qt/models/uat_model.h b/ui/qt/models/uat_model.h new file mode 100644 index 00000000..5da647a5 --- /dev/null +++ b/ui/qt/models/uat_model.h @@ -0,0 +1,82 @@ +/** @file + * + * Data model for UAT records. + * + * Copyright 2016 Peter Wu <peter@lekensteyn.nl> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef UAT_MODEL_H +#define UAT_MODEL_H + +#include <config.h> +#include <glib.h> + +#include <QAbstractItemModel> +#include <QList> +#include <QMap> +#include <epan/uat-int.h> + +class UatModel : public QAbstractTableModel +{ +public: + UatModel(QObject *parent, uat_t *uat = 0); + UatModel(QObject *parent, QString tableName); + + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + + QModelIndex appendEntry(QVariantList row); + + QModelIndex copyRow(QModelIndex original); + bool moveRow(int src_row, int dst_row); + + bool moveRow(const QModelIndex &sourceParent, int sourceRow, const QModelIndex &destinationParent, int destinationChild); + + void reloadUat(); + bool hasErrors() const; + void clearAll(); + + /** + * If the UAT has changed, save the contents to file and invoke the UAT + * post_update_cb. + * + * @param error An error while saving changes, if any. + * @return true if anything changed, false otherwise. + */ + bool applyChanges(QString &error); + + /** + * Undo any changes to the UAT. + * + * @param error An error while restoring the original UAT, if any. + * @return true if anything changed, false otherwise. + */ + bool revertChanges(QString &error); + + QModelIndex findRowForColumnContent(QVariant columnContent, int columnToCheckAgainst, int role = Qt::DisplayRole); + +private: + bool checkField(int row, int col, char **error) const; + QList<int> checkRow(int row); + void loadUat(uat_t * uat = 0); + + epan_uat *uat_; + QList<bool> dirty_records; + QList<QMap<int, QString> > record_errors; +}; +#endif // UAT_MODEL_H diff --git a/ui/qt/models/url_link_delegate.cpp b/ui/qt/models/url_link_delegate.cpp new file mode 100644 index 00000000..a2c32bef --- /dev/null +++ b/ui/qt/models/url_link_delegate.cpp @@ -0,0 +1,51 @@ +/* url_link_delegate.cpp + * Delegates for displaying links as links, including elide model + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/models/url_link_delegate.h> + +#include <QPainter> + +#include <ui/qt/utils/color_utils.h> + +UrlLinkDelegate::UrlLinkDelegate(QObject *parent) + : QStyledItemDelegate(parent), + re_col_(-1), + url_re_(new QRegularExpression()) +{} + +UrlLinkDelegate::~UrlLinkDelegate() +{ + delete url_re_; +} + +void UrlLinkDelegate::setColCheck(int column, QString &pattern) +{ + re_col_ = column; + url_re_->setPattern(pattern); +} + +void UrlLinkDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + if (re_col_ >= 0 && url_re_) { + QModelIndex re_idx = index.model()->index(index.row(), re_col_); + QString col_text = index.model()->data(re_idx).toString(); + if (!url_re_->match(col_text).hasMatch()) { + QStyledItemDelegate::paint(painter, option, index); + return; + } + } + + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + opt.font.setUnderline(true); + opt.palette.setColor(QPalette::Text, ColorUtils::themeLinkBrush().color()); + + QStyledItemDelegate::paint(painter, opt, index); +} diff --git a/ui/qt/models/url_link_delegate.h b/ui/qt/models/url_link_delegate.h new file mode 100644 index 00000000..c3539284 --- /dev/null +++ b/ui/qt/models/url_link_delegate.h @@ -0,0 +1,36 @@ +/** @file + * + * Delegates for displaying links as links, including elide model + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef URL_LINK_DELEGATE_H +#define URL_LINK_DELEGATE_H + +#include <QStyledItemDelegate> +#include <QStyleOptionViewItem> +#include <QModelIndex> +#include <QRegularExpression> + +class UrlLinkDelegate : public QStyledItemDelegate +{ +public: + explicit UrlLinkDelegate(QObject *parent = Q_NULLPTR); + ~UrlLinkDelegate(); + // If pattern matches the string in column, render as a URL. + // Otherwise render as plain text. + void setColCheck(int column, QString &pattern); + +protected: + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + +private: + int re_col_; + QRegularExpression *url_re_; +}; +#endif // URL_LINK_DELEGATE_H diff --git a/ui/qt/models/voip_calls_info_model.cpp b/ui/qt/models/voip_calls_info_model.cpp new file mode 100644 index 00000000..23ba46f1 --- /dev/null +++ b/ui/qt/models/voip_calls_info_model.cpp @@ -0,0 +1,249 @@ +/* voip_calls_info_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "voip_calls_info_model.h" +#include <wsutil/utf8_entities.h> +#include <wsutil/ws_assert.h> +#include <ui/qt/utils/qt_ui_utils.h> + +#include <QDateTime> + +VoipCallsInfoModel::VoipCallsInfoModel(QObject *parent) : + QAbstractTableModel(parent), + mTimeOfDay_(false) +{ +} + +voip_calls_info_t *VoipCallsInfoModel::indexToCallInfo(const QModelIndex &index) +{ + return VariantPointer<voip_calls_info_t>::asPtr(index.data(Qt::UserRole)); +} + +QVariant VoipCallsInfoModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + // call_info will be non-NULL since the index is valid + voip_calls_info_t *call_info = static_cast<voip_calls_info_t *>(callinfos_[index.row()]); + + if (role == Qt::UserRole) { + return VariantPointer<voip_calls_info_t>::asQVariant(call_info); + } + + if (role != Qt::DisplayRole) { + return QVariant(); + } + + switch ((Column) index.column()) { + case StartTime: + return timeData(&(call_info->start_fd->abs_ts), &(call_info->start_rel_ts)); + case StopTime: + return timeData(&(call_info->stop_fd->abs_ts), &(call_info->stop_rel_ts)); + case InitialSpeaker: + return address_to_display_qstring(&(call_info->initial_speaker)); + case From: + return QString::fromUtf8(call_info->from_identity); + case To: + return QString::fromUtf8(call_info->to_identity); + case Protocol: + return ((call_info->protocol == VOIP_COMMON) && call_info->protocol_name) ? + call_info->protocol_name : voip_protocol_name[call_info->protocol]; + case Duration: + { + guint callDuration = nstime_to_sec(&(call_info->stop_fd->abs_ts)) - nstime_to_sec(&(call_info->start_fd->abs_ts)); + return QString("%1:%2:%3").arg(callDuration / 3600, 2, 10, QChar('0')).arg((callDuration % 3600) / 60, 2, 10, QChar('0')).arg(callDuration % 60, 2, 10, QChar('0')); + } + case Packets: + return call_info->npackets; + case State: + return QString(voip_call_state_name[call_info->call_state]); + case Comments: + /* Add comments based on the protocol */ + switch (call_info->protocol) { + case VOIP_ISUP: + { + isup_calls_info_t *isup_info = (isup_calls_info_t *)call_info->prot_info; + return QString("%1-%2 %3 %4-%5") + .arg(isup_info->ni) + .arg(isup_info->opc) + .arg(UTF8_RIGHTWARDS_ARROW) + .arg(isup_info->ni) + .arg(isup_info->dpc); + } + break; + case VOIP_H323: + { + h323_calls_info_t *h323_info = (h323_calls_info_t *)call_info->prot_info; + gboolean flag = FALSE; + static const QString on_str = tr("On"); + static const QString off_str = tr("Off"); + if (call_info->call_state == VOIP_CALL_SETUP) { + flag = h323_info->is_faststart_Setup; + } else { + if ((h323_info->is_faststart_Setup) && (h323_info->is_faststart_Proc)) { + flag = TRUE; + } + } + return tr("Tunneling: %1 Fast Start: %2") + .arg(h323_info->is_h245Tunneling ? on_str : off_str) + .arg(flag ? on_str : off_str); + } + break; + case VOIP_COMMON: + default: + return QString::fromUtf8(call_info->call_comment); + } + case ColumnCount: + ws_assert_not_reached(); + } + return QVariant(); +} + +QVariant VoipCallsInfoModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + switch ((Column) section) { + case StartTime: + return tr("Start Time"); + case StopTime: + return tr("Stop Time"); + case InitialSpeaker: + return tr("Initial Speaker"); + case From: + return tr("From"); + case To: + return tr("To"); + case Protocol: + return tr("Protocol"); + case Duration: + return tr("Duration"); + case Packets: + return tr("Packets"); + case State: + return tr("State"); + case Comments: + return tr("Comments"); + case ColumnCount: + ws_assert_not_reached(); + } + } + return QVariant(); +} + +int VoipCallsInfoModel::rowCount(const QModelIndex &parent) const +{ + // there are no children + if (parent.isValid()) { + return 0; + } + + return static_cast<int>(callinfos_.size()); +} + +int VoipCallsInfoModel::columnCount(const QModelIndex &parent) const +{ + // there are no children + if (parent.isValid()) { + return 0; + } + + return ColumnCount; +} + +QVariant VoipCallsInfoModel::timeData(nstime_t *abs_ts, nstime_t *rel_ts) const +{ + if (mTimeOfDay_) { + return QDateTime::fromMSecsSinceEpoch(nstime_to_msec(abs_ts), Qt::LocalTime).toString("yyyy-MM-dd hh:mm:ss"); + } else { + // XXX Pull digit count from capture file precision + return QString::number(nstime_to_sec(rel_ts), 'f', 6); + } +} + +void VoipCallsInfoModel::setTimeOfDay(bool timeOfDay) +{ + mTimeOfDay_ = timeOfDay; + if (rowCount() > 0) { + // Update both the start and stop column in all rows. + emit dataChanged(index(0, StartTime), index(rowCount() - 1, StopTime)); + } +} + +bool VoipCallsInfoModel::timeOfDay() const +{ + return mTimeOfDay_; +} + +void VoipCallsInfoModel::updateCalls(GQueue *callsinfos) +{ + if (callsinfos) { + qsizetype calls = callinfos_.count(); + int cnt = 0; + GList *cur_call; + + // Iterate new callsinfos and replace data in mode if required + cur_call = g_queue_peek_nth_link(callsinfos, 0); + while (cur_call && (cnt < calls)) { + if (callinfos_.at(cnt) != cur_call->data) { + // Data changed, use it + callinfos_.replace(cnt, cur_call->data); + } + cur_call = gxx_list_next(cur_call); + cnt++; + } + + // Add new rows + cur_call = g_queue_peek_nth_link(callsinfos, rowCount()); + guint extra = g_list_length(cur_call); + if (extra > 0) { + beginInsertRows(QModelIndex(), rowCount(), rowCount() + extra - 1); + while (cur_call && cur_call->data) { + voip_calls_info_t *call_info = gxx_list_data(voip_calls_info_t*, cur_call); + callinfos_.push_back(call_info); + cur_call = gxx_list_next(cur_call); + } + endInsertRows(); + } + } +} + +void VoipCallsInfoModel::removeAllCalls() +{ + beginRemoveRows(QModelIndex(), 0, rowCount() - 1); + callinfos_.clear(); + endRemoveRows(); +} + +// Proxy model that allows columns to be sorted. +VoipCallsInfoSortedModel::VoipCallsInfoSortedModel(QObject *parent) : + QSortFilterProxyModel(parent) +{ +} + +bool VoipCallsInfoSortedModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + voip_calls_info_t *a = VoipCallsInfoModel::indexToCallInfo(source_left); + voip_calls_info_t *b = VoipCallsInfoModel::indexToCallInfo(source_right); + + if (a && b) { + switch (source_left.column()) { + case VoipCallsInfoModel::StartTime: + return nstime_cmp(&(a->start_rel_ts), &(b->start_rel_ts)) < 0; + case VoipCallsInfoModel::StopTime: + return nstime_cmp(&(a->stop_rel_ts), &(b->stop_rel_ts)) < 0; + case VoipCallsInfoModel::InitialSpeaker: + return cmp_address(&(a->initial_speaker), &(b->initial_speaker)) < 0; + } + } + + // fallback to string cmp on other fields + return QSortFilterProxyModel::lessThan(source_left, source_right); +} diff --git a/ui/qt/models/voip_calls_info_model.h b/ui/qt/models/voip_calls_info_model.h new file mode 100644 index 00000000..2f8d4007 --- /dev/null +++ b/ui/qt/models/voip_calls_info_model.h @@ -0,0 +1,71 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef VOIP_CALLS_INFO_MODEL_H +#define VOIP_CALLS_INFO_MODEL_H + +#include <config.h> +#include <glib.h> + +#include "ui/voip_calls.h" +#include <ui/qt/utils/variant_pointer.h> + +#include <QAbstractTableModel> +#include <QSortFilterProxyModel> + +class VoipCallsInfoModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + VoipCallsInfoModel(QObject *parent = 0); + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + void setTimeOfDay(bool timeOfDay); + bool timeOfDay() const; + void updateCalls(GQueue *callsinfos); + void removeAllCalls(); + + static voip_calls_info_t *indexToCallInfo(const QModelIndex &index); + + enum Column + { + StartTime, + StopTime, + InitialSpeaker, + From, + To, + Protocol, + Duration, + Packets, + State, + Comments, + ColumnCount /* not an actual column, but used to find max. cols. */ + }; + +private: + QList<void *> callinfos_; + bool mTimeOfDay_; + + QVariant timeData(nstime_t *abs_ts, nstime_t *rel_ts) const; +}; + +class VoipCallsInfoSortedModel : public QSortFilterProxyModel +{ +public: + VoipCallsInfoSortedModel(QObject *parent = 0); + +protected: + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; +}; + +#endif // VOIP_CALLS_INFO_MODEL_H |