1 /* capture_filter_edit.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later*/
13 #include <epan/proto.h>
15 #include "capture_opts.h"
17 #include <ui/capture_globals.h>
18 #include <ui/filter_files.h>
19 #include <wsutil/utf8_entities.h>
21 #include <ui/qt/widgets/capture_filter_edit.h>
22 #include "capture_filter_syntax_worker.h"
23 #include "filter_dialog.h"
24 #include <ui/qt/widgets/stock_icon_tool_button.h>
25 #include "wireshark_application.h"
30 #include <QMessageBox>
32 #include <QStringListModel>
33 #include <QStyleOptionFrame>
35 #include <ui/qt/utils/qt_ui_utils.h>
38 // - This duplicates some DisplayFilterEdit code.
39 // - We need simplified (button- and dropdown-free) versions for use in dialogs and field-only checking.
41 static const QString libpcap_primitive_chars_ = "-0123456789abcdefghijklmnopqrstuvwxyz";
43 // grep '^[a-z].*return [A-Z].*;$' scanner.l | awk '{gsub(/\|/, "\n") ; print " << \"" $1 "\""}' | sort
44 // Remove "and" and "or".
45 static const QStringList libpcap_primitives_ = QStringList()
46 << "aarp" << "action" << "address1" << "address2" << "address3" << "address4"
47 << "ah" << "arp" << "atalk" << "bcc" << "broadcast" << "byte" << "carp"
48 << "clnp" << "connectmsg" << "csnp" << "decnet" << "direction" << "dpc"
49 << "dst" << "es-is" << "esis" << "esp" << "fddi" << "fisu" << "gateway"
50 << "greater" << "hdpc" << "hfisu" << "hlssu" << "hmsu" << "hopc" << "host"
51 << "hsio" << "hsls" << "icmp" << "icmp6" << "igmp" << "igrp" << "iih" << "ilmic"
52 << "inbound" << "ip" << "ip6" << "ipx" << "is-is" << "isis" << "iso" << "l1"
53 << "l2" << "lane" << "lat" << "len" << "less" << "link" << "llc" << "lsp"
54 << "lssu" << "lsu" << "mask" << "metac" << "metaconnect" << "mopdl" << "moprc"
55 << "mpls" << "msu" << "multicast" << "net" << "netbeui" << "oam" << "oamf4"
56 << "oamf4ec" << "oamf4sc" << "on" << "opc" << "outbound" << "pim"
57 << "port" << "portrange" << "pppoed" << "pppoes" << "proto" << "psnp" << "ra"
58 << "radio" << "rarp" << "reason" << "rnr" << "rset" << "sc" << "sca" << "sctp"
59 << "sio" << "sls" << "snp" << "src" << "srnr" << "stp" << "subtype" << "ta"
60 << "tcp" << "type" << "udp" << "vci" << "vlan" << "vpi" << "vrrp"
63 CaptureFilterEdit::CaptureFilterEdit(QWidget *parent, bool plain) :
64 SyntaxLineEdit(parent),
66 field_name_only_(false),
67 enable_save_action_(false),
70 bookmark_button_(NULL),
74 setAccessibleName(tr("Capture filter entry"));
76 completion_model_ = new QStringListModel(this);
77 setCompleter(new QCompleter(completion_model_, this));
78 setCompletionTokenChars(libpcap_primitive_chars_);
83 bookmark_button_ = new StockIconToolButton(this, "x-capture-filter-bookmark");
84 bookmark_button_->setCursor(Qt::ArrowCursor);
85 bookmark_button_->setMenu(new QMenu(bookmark_button_));
86 bookmark_button_->setPopupMode(QToolButton::InstantPopup);
87 bookmark_button_->setToolTip(tr("Manage saved bookmarks."));
88 bookmark_button_->setIconSize(QSize(14, 14));
89 bookmark_button_->setStyleSheet(
92 " background: transparent;" // Disables platform style on Windows.
95 "QToolButton::menu-indicator { image: none; }"
97 connect(bookmark_button_, SIGNAL(clicked()), this, SLOT(bookmarkClicked()));
101 clear_button_ = new StockIconToolButton(this, "x-filter-clear");
102 clear_button_->setCursor(Qt::ArrowCursor);
103 clear_button_->setToolTip(QString());
104 clear_button_->setIconSize(QSize(14, 14));
105 clear_button_->setStyleSheet(
108 " background: transparent;" // Disables platform style on Windows.
113 connect(clear_button_, SIGNAL(clicked()), this, SLOT(clearFilter()));
116 connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(checkFilter(const QString&)));
119 // Disable the apply button for now
121 apply_button_ = new StockIconToolButton(this, "x-filter-apply");
122 apply_button_->setCursor(Qt::ArrowCursor);
123 apply_button_->setEnabled(false);
124 apply_button_->setToolTip(tr("Apply this filter string to the display."));
125 apply_button_->setIconSize(QSize(24, 14));
126 apply_button_->setStyleSheet(
129 " background: transparent;" // Disables platform style on Windows.
133 connect(apply_button_, SIGNAL(clicked()), this, SLOT(applyCaptureFilter()));
136 connect(this, SIGNAL(returnPressed()), this, SLOT(applyCaptureFilter()));
138 int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
140 if (bookmark_button_) bksz = bookmark_button_->sizeHint();
142 if (clear_button_) cbsz = clear_button_->sizeHint();
144 if (apply_button_) apsz = apply_button_->sizeHint();
146 setStyleSheet(QString(
147 "CaptureFilterEdit {"
148 " padding-left: %1px;"
149 " margin-left: %2px;"
150 " margin-right: %3px;"
155 .arg(cbsz.width() + apsz.width() + frameWidth + 1)
158 QComboBox *cf_combo = qobject_cast<QComboBox *>(parent);
160 connect(cf_combo, SIGNAL(activated(QString)), this, SIGNAL(textEdited(QString)));
163 QThread *syntax_thread = new QThread;
164 syntax_worker_ = new CaptureFilterSyntaxWorker;
165 syntax_worker_->moveToThread(syntax_thread);
166 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(updateBookmarkMenu()));
167 connect(wsApp, SIGNAL(captureFilterListChanged()), this, SLOT(updateBookmarkMenu()));
168 connect(syntax_thread, SIGNAL(started()), syntax_worker_, SLOT(start()));
169 connect(syntax_thread, SIGNAL(started()), this, SLOT(checkFilter()));
170 connect(syntax_worker_, SIGNAL(syntaxResult(QString,int,QString)),
171 this, SLOT(setFilterSyntaxState(QString,int,QString)));
172 connect(syntax_thread, SIGNAL(finished()), syntax_worker_, SLOT(deleteLater()));
173 syntax_thread->start();
174 updateBookmarkMenu();
177 void CaptureFilterEdit::paintEvent(QPaintEvent *evt) {
178 SyntaxLineEdit::paintEvent(evt);
180 if (bookmark_button_) {
181 // Draw the right border by hand. We could try to do this in the
182 // style sheet but it's a pain.
184 QColor divider_color = Qt::gray;
186 QColor divider_color = palette().shadow().color();
188 QPainter painter(this);
189 painter.setPen(divider_color);
190 QRect cr = contentsRect();
191 QSize bksz = bookmark_button_->size();
192 painter.drawLine(bksz.width(), cr.top(), bksz.width(), cr.bottom());
196 void CaptureFilterEdit::resizeEvent(QResizeEvent *)
199 if (clear_button_) cbsz = clear_button_->sizeHint();
201 if (apply_button_) apsz = apply_button_->sizeHint();
203 int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
205 clear_button_->move(contentsRect().right() - frameWidth - cbsz.width() - apsz.width(),
206 contentsRect().top());
207 clear_button_->setMinimumHeight(contentsRect().height());
208 clear_button_->setMaximumHeight(contentsRect().height());
211 apply_button_->move(contentsRect().right() - frameWidth - apsz.width(),
212 contentsRect().top());
213 apply_button_->setMinimumHeight(contentsRect().height());
214 apply_button_->setMaximumHeight(contentsRect().height());
216 if (bookmark_button_) {
217 bookmark_button_->setMinimumHeight(contentsRect().height());
218 bookmark_button_->setMaximumHeight(contentsRect().height());
222 void CaptureFilterEdit::setConflict(bool conflict)
225 //: This is a very long concept that needs to fit into a short space.
226 placeholder_text_ = tr("Multiple filters selected. Override them here or leave this blank to preserve them.");
227 setToolTip(tr("<p>The interfaces you have selected have different capture filters."
228 " Typing a filter here will override them. Doing nothing will"
229 " preserve them.</p>"));
231 placeholder_text_ = QString(tr("Enter a capture filter %1")).arg(UTF8_HORIZONTAL_ELLIPSIS);
232 setToolTip(QString());
234 setPlaceholderText(placeholder_text_);
237 // XXX Make this private along with setConflict.
238 QPair<const QString, bool> CaptureFilterEdit::getSelectedFilter()
241 bool filter_conflict = false;
243 int selected_devices = 0;
245 for (guint i = 0; i < global_capture_opts.all_ifaces->len; i++) {
246 interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, i);
247 if (device->selected) {
249 if (selected_devices == 1) {
250 user_filter = device->cfilter;
252 if (user_filter.compare(device->cfilter)) {
253 filter_conflict = true;
258 #endif // HAVE_LIBPCAP
259 return QPair<const QString, bool>(user_filter, filter_conflict);
262 void CaptureFilterEdit::checkFilter(const QString& filter)
264 setSyntaxState(Busy);
265 popFilterSyntaxStatus();
266 bool empty = filter.isEmpty();
269 if (bookmark_button_) {
272 for (GList *cf_item = get_filter_list_first(CFILTER_LIST); cf_item; cf_item = g_list_next(cf_item)) {
273 if (!cf_item->data) continue;
274 filter_def *cf_def = (filter_def *) cf_item->data;
275 if (!cf_def->name || !cf_def->strval) continue;
277 if (filter.compare(cf_def->strval) == 0) {
283 bookmark_button_->setStockIcon("x-filter-matching-bookmark");
284 if (remove_action_) {
285 remove_action_->setData(text());
286 remove_action_->setVisible(true);
289 bookmark_button_->setStockIcon("x-capture-filter-bookmark");
290 if (remove_action_) {
291 remove_action_->setVisible(false);
295 enable_save_action_ = (!match && !filter.isEmpty());
297 save_action_->setEnabled(false);
302 apply_button_->setEnabled(false);
306 clear_button_->setVisible(!empty);
310 setFilterSyntaxState(filter, Empty, QString());
312 syntax_worker_->checkFilter(filter);
316 void CaptureFilterEdit::checkFilter()
321 void CaptureFilterEdit::updateBookmarkMenu()
323 if (!bookmark_button_)
326 QMenu *bb_menu = bookmark_button_->menu();
329 save_action_ = bb_menu->addAction(tr("Save this filter"));
330 connect(save_action_, SIGNAL(triggered(bool)), this, SLOT(saveFilter()));
331 remove_action_ = bb_menu->addAction(tr("Remove this filter"));
332 connect(remove_action_, SIGNAL(triggered(bool)), this, SLOT(removeFilter()));
333 QAction *manage_action = bb_menu->addAction(tr("Manage Capture Filters"));
334 connect(manage_action, SIGNAL(triggered(bool)), this, SLOT(showFilters()));
335 bb_menu->addSeparator();
337 for (GList *cf_item = get_filter_list_first(CFILTER_LIST); cf_item; cf_item = g_list_next(cf_item)) {
338 if (!cf_item->data) continue;
339 filter_def *cf_def = (filter_def *) cf_item->data;
340 if (!cf_def->name || !cf_def->strval) continue;
342 int one_em = bb_menu->fontMetrics().height();
343 QString prep_text = QString("%1: %2").arg(cf_def->name).arg(cf_def->strval);
344 prep_text = bb_menu->fontMetrics().elidedText(prep_text, Qt::ElideRight, one_em * 40);
346 QAction *prep_action = bb_menu->addAction(prep_text);
347 prep_action->setData(cf_def->strval);
348 connect(prep_action, SIGNAL(triggered(bool)), this, SLOT(prepareFilter()));
354 void CaptureFilterEdit::setFilterSyntaxState(QString filter, int state, QString err_msg)
356 if (filter.compare(text()) == 0) { // The user hasn't changed the filter
357 setSyntaxState((SyntaxState)state);
358 if (!err_msg.isEmpty()) {
359 emit pushFilterSyntaxStatus(err_msg);
363 bool valid = (state != Invalid);
367 save_action_->setEnabled(enable_save_action_);
370 apply_button_->setEnabled(true);
374 emit captureFilterSyntaxChanged(valid);
377 void CaptureFilterEdit::bookmarkClicked()
379 emit addBookmark(text());
382 void CaptureFilterEdit::clearFilter()
385 emit textEdited(text());
388 void CaptureFilterEdit::buildCompletionList(const QString &primitive_word)
390 if (primitive_word.length() < 1) {
391 completion_model_->setStringList(QStringList());
395 // Grab matching capture filters from our parent combo and from the
396 // saved capture filters file. Skip ones that look like single fields
397 // and assume they will be added below.
398 QStringList complex_list;
399 QComboBox *cf_combo = qobject_cast<QComboBox *>(parent());
401 for (int i = 0; i < cf_combo->count() ; i++) {
402 QString recent_filter = cf_combo->itemText(i);
404 if (isComplexFilter(recent_filter)) {
405 complex_list << recent_filter;
409 for (const GList *cf_item = get_filter_list_first(CFILTER_LIST); cf_item; cf_item = g_list_next(cf_item)) {
410 const filter_def *cf_def = (filter_def *) cf_item->data;
411 if (!cf_def || !cf_def->strval) continue;
412 QString saved_filter = cf_def->strval;
414 if (isComplexFilter(saved_filter) && !complex_list.contains(saved_filter)) {
415 complex_list << saved_filter;
419 // libpcap has a small number of primitives so we just add the whole list
420 // sans the current word.
421 QStringList primitive_list = libpcap_primitives_;
422 primitive_list.removeAll(primitive_word);
424 completion_model_->setStringList(complex_list + primitive_list);
425 completer()->setCompletionPrefix(primitive_word);
428 void CaptureFilterEdit::applyCaptureFilter()
430 if (syntaxState() == Invalid) {
437 void CaptureFilterEdit::saveFilter()
439 FilterDialog capture_filter_dlg(window(), FilterDialog::CaptureFilter, text());
440 capture_filter_dlg.exec();
443 void CaptureFilterEdit::removeFilter()
445 QAction *ra = qobject_cast<QAction*>(sender());
446 if (!ra || ra->data().toString().isEmpty()) return;
448 QString remove_filter = ra->data().toString();
450 for (GList *cf_item = get_filter_list_first(CFILTER_LIST); cf_item; cf_item = g_list_next(cf_item)) {
451 if (!cf_item->data) continue;
452 filter_def *cf_def = (filter_def *) cf_item->data;
453 if (!cf_def->name || !cf_def->strval) continue;
455 if (remove_filter.compare(cf_def->strval) == 0) {
456 remove_from_filter_list(CFILTER_LIST, cf_item);
460 save_filter_list(CFILTER_LIST);
462 updateBookmarkMenu();
465 void CaptureFilterEdit::showFilters()
467 FilterDialog capture_filter_dlg(window(), FilterDialog::CaptureFilter);
468 capture_filter_dlg.exec();
471 void CaptureFilterEdit::prepareFilter()
473 QAction *pa = qobject_cast<QAction*>(sender());
474 if (!pa || pa->data().toString().isEmpty()) return;
476 QString filter(pa->data().toString());
478 emit textEdited(filter);
487 * indent-tabs-mode: nil
490 * ex: set shiftwidth=4 tabstop=8 expandtab:
491 * :indentSize=4:tabSize=8:noTabs=true: