wireshark/ui/qt/display_filter_expression_d...

456 lines
16 KiB
C++

/* display_filter_expression_dialog.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 "display_filter_expression_dialog.h"
#include <ui_display_filter_expression_dialog.h>
#include <epan/proto.h>
#include <epan/range.h>
#include <epan/tfs.h>
#include <epan/value_string.h>
#include <wsutil/utf8_entities.h>
#include <ui/qt/utils/qt_ui_utils.h>
#include "wireshark_application.h"
#include <ui/qt/utils/variant_pointer.h>
#include <QPushButton>
#include <QDialogButtonBox>
#include <QListWidgetItem>
#include <QTreeWidgetItem>
// To do:
// - Speed up initialization.
// - Speed up search.
enum {
proto_type_ = 1000,
field_type_
};
enum {
present_op_ = 1000,
eq_op_,
ne_op_,
gt_op_,
lt_op_,
ge_op_,
le_op_,
contains_op_,
matches_op_,
in_op_
};
DisplayFilterExpressionDialog::DisplayFilterExpressionDialog(QWidget *parent) :
GeometryStateDialog(parent),
ui(new Ui::DisplayFilterExpressionDialog),
ftype_(FT_NONE),
field_(NULL)
{
ui->setupUi(this);
if (parent) loadGeometry(parent->width() * 2 / 3, parent->height());
setAttribute(Qt::WA_DeleteOnClose, true);
setWindowTitle(wsApp->windowTitleString(tr("Display Filter Expression")));
setWindowIcon(wsApp->normalIcon());
proto_initialize_all_prefixes();
ui->fieldTreeWidget->setToolTip(ui->fieldLabel->toolTip());
ui->searchLineEdit->setToolTip(ui->searchLabel->toolTip());
ui->relationListWidget->setToolTip(ui->relationLabel->toolTip());
ui->valueLineEdit->setToolTip(ui->valueLabel->toolTip());
ui->enumListWidget->setToolTip(ui->enumLabel->toolTip());
ui->rangeLineEdit->setToolTip(ui->rangeLabel->toolTip());
// Relation list
new QListWidgetItem("is present", ui->relationListWidget, present_op_);
new QListWidgetItem("==", ui->relationListWidget, eq_op_);
new QListWidgetItem("!=", ui->relationListWidget, ne_op_);
new QListWidgetItem(">", ui->relationListWidget, gt_op_);
new QListWidgetItem("<", ui->relationListWidget, lt_op_);
new QListWidgetItem(">=", ui->relationListWidget, ge_op_);
new QListWidgetItem("<=", ui->relationListWidget, le_op_);
new QListWidgetItem("contains", ui->relationListWidget, contains_op_);
new QListWidgetItem("matches", ui->relationListWidget, matches_op_);
new QListWidgetItem("in", ui->relationListWidget, in_op_);
value_label_pfx_ = ui->valueLabel->text();
connect(ui->valueLineEdit, SIGNAL(textEdited(QString)), this, SLOT(updateWidgets()));
connect(ui->rangeLineEdit, SIGNAL(textEdited(QString)), this, SLOT(updateWidgets()));
// Trigger updateWidgets
ui->fieldTreeWidget->selectionModel()->clear();
fillTree();
}
DisplayFilterExpressionDialog::~DisplayFilterExpressionDialog()
{
delete ui;
}
// Nearly identical to SupportedProtocolsDialog::fillTree.
void DisplayFilterExpressionDialog::fillTree()
{
void *proto_cookie;
QList <QTreeWidgetItem *> proto_list;
for (int proto_id = proto_get_first_protocol(&proto_cookie); proto_id != -1;
proto_id = proto_get_next_protocol(&proto_cookie)) {
protocol_t *protocol = find_protocol_by_id(proto_id);
if (!proto_is_protocol_enabled(protocol)) continue;
QTreeWidgetItem *proto_ti = new QTreeWidgetItem(proto_type_);
QString label = QString("%1 " UTF8_MIDDLE_DOT " %3")
.arg(proto_get_protocol_short_name(protocol))
.arg(proto_get_protocol_long_name(protocol));
proto_ti->setText(0, label);
proto_ti->setData(0, Qt::UserRole, QVariant::fromValue(proto_id));
proto_list << proto_ti;
}
wsApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers, 1);
ui->fieldTreeWidget->invisibleRootItem()->addChildren(proto_list);
ui->fieldTreeWidget->sortByColumn(0, Qt::AscendingOrder);
int field_count = 0;
foreach (QTreeWidgetItem *proto_ti, proto_list) {
void *field_cookie;
int proto_id = proto_ti->data(0, Qt::UserRole).toInt();
QList <QTreeWidgetItem *> field_list;
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; // Ignore duplicate names.
QTreeWidgetItem *field_ti = new QTreeWidgetItem(field_type_);
QString label = QString("%1 " UTF8_MIDDLE_DOT " %3").arg(hfinfo->abbrev).arg(hfinfo->name);
field_ti->setText(0, label);
field_ti->setData(0, Qt::UserRole, VariantPointer<header_field_info>::asQVariant(hfinfo));
field_list << field_ti;
field_count++;
if (field_count % 10000 == 0) {
wsApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers, 1);
}
}
std::sort(field_list.begin(), field_list.end());
proto_ti->addChildren(field_list);
}
wsApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers, 1);
ui->fieldTreeWidget->sortByColumn(0, Qt::AscendingOrder);
updateWidgets();
}
void DisplayFilterExpressionDialog::updateWidgets()
{
bool rel_enable = field_ != NULL;
ui->relationLabel->setEnabled(rel_enable);
ui->relationListWidget->setEnabled(rel_enable);
ui->hintLabel->clear();
bool value_enable = false;
bool enum_enable = false;
bool enum_multi_enable = false;
bool range_enable = false;
QString filter;
if (field_) {
filter = field_;
QListWidgetItem *rli = ui->relationListWidget->currentItem();
if (rli && rli->type() != present_op_) {
value_enable = true;
if (ftype_can_slice(ftype_)) {
range_enable = true;
}
enum_enable = ui->enumListWidget->count() > 0;
filter.append(QString(" %1").arg(rli->text()));
}
if (value_enable && !ui->valueLineEdit->text().isEmpty()) {
if (rli && rli->type() == in_op_) {
filter.append(QString(" {%1}").arg(ui->valueLineEdit->text()));
enum_multi_enable = enum_enable;
} else {
if (ftype_ == FT_STRING) {
filter.append(QString(" \"%1\"").arg(ui->valueLineEdit->text()));
} else {
filter.append(QString(" %1").arg(ui->valueLineEdit->text()));
}
}
}
}
ui->valueLabel->setEnabled(value_enable);
ui->valueLineEdit->setEnabled(value_enable);
ui->enumLabel->setEnabled(enum_enable);
ui->enumListWidget->setEnabled(enum_enable);
ui->enumListWidget->setSelectionMode(enum_multi_enable ?
QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection);
ui->rangeLabel->setEnabled(range_enable);
ui->rangeLineEdit->setEnabled(range_enable);
ui->displayFilterLineEdit->setText(filter);
QString hint = "<small><i>";
if (ui->fieldTreeWidget->selectedItems().count() < 1) {
hint.append(tr("Select a field name to get started"));
} else if (ui->displayFilterLineEdit->syntaxState() != SyntaxLineEdit::Valid) {
hint.append(ui->displayFilterLineEdit->syntaxErrorMessage());
} else {
hint.append(tr("Click OK to insert this filter"));
}
hint.append("</i></small>");
ui->hintLabel->setText(hint);
QPushButton *ok_bt = ui->buttonBox->button(QDialogButtonBox::Ok);
if (ok_bt) {
bool ok_enable = !(ui->displayFilterLineEdit->text().isEmpty()
|| (ui->displayFilterLineEdit->syntaxState() == SyntaxLineEdit::Invalid));
ok_bt->setEnabled(ok_enable);
}
}
void DisplayFilterExpressionDialog::fillEnumBooleanValues(const true_false_string *tfs)
{
if (!tfs) tfs = &tfs_true_false;
QListWidgetItem *eli = new QListWidgetItem(tfs->true_string, ui->enumListWidget);
eli->setData(Qt::UserRole, QString("1"));
eli = new QListWidgetItem(tfs->false_string, ui->enumListWidget);
eli->setData(Qt::UserRole, QString("0"));
}
void DisplayFilterExpressionDialog::fillEnumIntValues(const _value_string *vals, int base)
{
if (!vals) return;
for (int i = 0; vals[i].strptr != NULL; i++) {
QListWidgetItem *eli = new QListWidgetItem(vals[i].strptr, ui->enumListWidget);
eli->setData(Qt::UserRole, int_to_qstring(vals[i].value, 0, base));
}
}
void DisplayFilterExpressionDialog::fillEnumInt64Values(const _val64_string *vals64, int base)
{
if (!vals64) return;
for (int i = 0; vals64[i].strptr != NULL; i++) {
QListWidgetItem *eli = new QListWidgetItem(vals64[i].strptr, ui->enumListWidget);
eli->setData(Qt::UserRole, int_to_qstring(vals64[i].value, 0, base));
}
}
void DisplayFilterExpressionDialog::fillEnumRangeValues(const _range_string *rvals)
{
if (!rvals) return;
for (int i = 0; rvals[i].strptr != NULL; i++) {
QString range_text = rvals[i].strptr;
// Tell the user which values are valid here. Default to value_min below.
if (rvals[i].value_min != rvals[i].value_max) {
range_t range;
range.nranges = 1;
range.ranges[0].low = rvals[i].value_min;
range.ranges[0].high = rvals[i].value_max;
range_text.append(QString(" (%1 valid)").arg(range_to_qstring(&range)));
}
QListWidgetItem *eli = new QListWidgetItem(range_text, ui->enumListWidget);
eli->setData(Qt::UserRole, QString::number(rvals[i].value_min));
}
}
void DisplayFilterExpressionDialog::on_fieldTreeWidget_itemSelectionChanged()
{
ftype_ = FT_NONE;
field_ = NULL;
QTreeWidgetItem *cur_fti = NULL;
if (ui->fieldTreeWidget->selectedItems().count() > 0) {
cur_fti = ui->fieldTreeWidget->selectedItems()[0];
}
ui->valueLineEdit->clear();
ui->enumListWidget->clear();
ui->rangeLineEdit->clear();
if (cur_fti && cur_fti->type() == proto_type_) {
ftype_ = FT_PROTOCOL;
field_ = proto_get_protocol_filter_name(cur_fti->data(0, Qt::UserRole).toInt());
} else if (cur_fti && cur_fti->type() == field_type_) {
header_field_info *hfinfo = VariantPointer<header_field_info>::asPtr(cur_fti->data(0, Qt::UserRole));
if (hfinfo) {
ftype_ = hfinfo->type;
field_ = hfinfo->abbrev;
switch(ftype_) {
case FT_BOOLEAN:
// Let the user select the "True" and "False" values.
fillEnumBooleanValues((const true_false_string *)hfinfo->strings);
break;
case FT_UINT8:
case FT_UINT16:
case FT_UINT24:
case FT_UINT32:
case FT_INT8:
case FT_INT16:
case FT_INT24:
case FT_INT32:
{
int base;
switch (hfinfo->display & FIELD_DISPLAY_E_MASK) {
case BASE_HEX:
case BASE_HEX_DEC:
base = 16;
break;
case BASE_OCT:
base = 8;
break;
default:
base = 10;
break;
}
// Let the user select from a list of value_string or range_string values.
if (hfinfo->strings && ! ((hfinfo->display & FIELD_DISPLAY_E_MASK) == BASE_CUSTOM)) {
if (hfinfo->display & BASE_RANGE_STRING) {
fillEnumRangeValues((const range_string *)hfinfo->strings);
} else if (hfinfo->display & BASE_VAL64_STRING) {
const val64_string *vals = (const val64_string *)hfinfo->strings;
fillEnumInt64Values(vals, base);
} else { // Plain old value_string / VALS
const value_string *vals = (const value_string *)hfinfo->strings;
if (hfinfo->display & BASE_EXT_STRING)
vals = VALUE_STRING_EXT_VS_P((const value_string_ext *)vals);
fillEnumIntValues(vals, base);
}
}
break;
}
default:
break;
}
}
}
if (ui->enumListWidget->count() > 0) {
ui->enumListWidget->setCurrentRow(0);
}
bool all_show = field_ != NULL;
for (int i = 0; i < ui->relationListWidget->count(); i++) {
QListWidgetItem *li = ui->relationListWidget->item(i);
switch (li->type()) {
case eq_op_:
case in_op_:
li->setHidden(!ftype_can_eq(ftype_) && !(ftype_can_slice(ftype_) && ftype_can_eq(FT_BYTES)));
break;
case ne_op_:
li->setHidden(!ftype_can_ne(ftype_) && !(ftype_can_slice(ftype_) && ftype_can_ne(FT_BYTES)));
break;
case gt_op_:
li->setHidden(!ftype_can_gt(ftype_) && !(ftype_can_slice(ftype_) && ftype_can_gt(FT_BYTES)));
break;
case lt_op_:
li->setHidden(!ftype_can_lt(ftype_) && !(ftype_can_slice(ftype_) && ftype_can_lt(FT_BYTES)));
break;
case ge_op_:
li->setHidden(!ftype_can_ge(ftype_) && !(ftype_can_slice(ftype_) && ftype_can_ge(FT_BYTES)));
break;
case le_op_:
li->setHidden(!ftype_can_le(ftype_) && !(ftype_can_slice(ftype_) && ftype_can_le(FT_BYTES)));
break;
case contains_op_:
li->setHidden(!ftype_can_contains(ftype_) && !(ftype_can_slice(ftype_) && ftype_can_contains(FT_BYTES)));
break;
case matches_op_:
li->setHidden(!ftype_can_matches(ftype_) && !(ftype_can_slice(ftype_) && ftype_can_matches(FT_BYTES)));
break;
default:
li->setHidden(!all_show);
break;
}
}
if (all_show) {
// Select "==" if it's present and we have a value, "is present" otherwise
int row = ui->relationListWidget->count() > 1 && ui->enumListWidget->count() > 0 ? 1 : 0;
ui->relationListWidget->setCurrentRow(row);
}
if (ftype_ != FT_NONE) {
ui->valueLabel->setText(QString("%1 (%2)")
.arg(value_label_pfx_)
.arg(ftype_pretty_name(ftype_)));
} else {
ui->valueLabel->setText(value_label_pfx_);
}
updateWidgets();
}
void DisplayFilterExpressionDialog::on_relationListWidget_itemSelectionChanged()
{
updateWidgets();
}
void DisplayFilterExpressionDialog::on_enumListWidget_itemSelectionChanged()
{
QStringList values;
QList<QListWidgetItem *> items = ui->enumListWidget->selectedItems();
QList<QListWidgetItem *>::const_iterator it = items.constBegin();
while (it != items.constEnd())
{
values << (*it)->data(Qt::UserRole).toString();
++it;
}
ui->valueLineEdit->setText(values.join(" "));
updateWidgets();
}
void DisplayFilterExpressionDialog::on_searchLineEdit_textChanged(const QString &search_re)
{
ui->fieldTreeWidget->setUpdatesEnabled(false);
QTreeWidgetItemIterator it(ui->fieldTreeWidget);
QRegExp regex(search_re, Qt::CaseInsensitive);
while (*it) {
bool hidden = true;
if (search_re.isEmpty() || (*it)->text(0).contains(regex)) {
hidden = false;
if ((*it)->type() == field_type_) {
(*it)->parent()->setHidden(false);
}
}
(*it)->setHidden(hidden);
++it;
}
ui->fieldTreeWidget->setUpdatesEnabled(true);
}
void DisplayFilterExpressionDialog::on_buttonBox_accepted()
{
emit insertDisplayFilter(ui->displayFilterLineEdit->text());
}
void DisplayFilterExpressionDialog::on_buttonBox_helpRequested()
{
wsApp->helpTopicAction(HELP_FILTER_EXPRESSION_DIALOG);
}