endpoint_dialog.cpp 23.8 KB
Newer Older
1 2 3 4 5 6
/* endpoint_dialog.cpp
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
7 8
 * SPDX-License-Identifier: GPL-2.0-or-later
 */
9 10 11

#include "endpoint_dialog.h"

12
#include <epan/maxmind_db.h>
13

14 15
#include <epan/prefs.h>

16 17 18
#include "ui/recent.h"
#include "ui/traffic_table_ui.h"

19
#include "wsutil/file_util.h"
20
#include "wsutil/pint.h"
21
#include "wsutil/str_util.h"
22
#include <wsutil/utf8_entities.h>
23

24
#include <ui/qt/utils/qt_ui_utils.h>
25 26
#include <ui/qt/utils/variant_pointer.h>
#include <ui/qt/widgets/wireshark_file_dialog.h>
27 28
#include "wireshark_application.h"

29
#include <QCheckBox>
30
#include <QDesktopServices>
31
#include <QDialogButtonBox>
32
#include <QMessageBox>
33
#include <QPushButton>
34
#include <QUrl>
35
#include <QTemporaryFile>
36

37
static const QString table_name_ = QObject::tr("Endpoint");
38
EndpointDialog::EndpointDialog(QWidget &parent, CaptureFile &cf, int cli_proto_id, const char *filter) :
39 40
    TrafficTableDialog(parent, cf, filter, table_name_)
{
41 42 43 44 45 46 47 48 49
#ifdef HAVE_MAXMINDDB
    map_bt_ = buttonBox()->addButton(tr("Map"), QDialogButtonBox::ActionRole);
    map_bt_->setToolTip(tr("Draw IPv4 or IPv6 endpoints on a map."));
    connect(trafficTableTabWidget(), &QTabWidget::currentChanged, this, &EndpointDialog::tabChanged);

    QMenu *map_menu_ = new QMenu(map_bt_);
    QAction *action;
    action = map_menu_->addAction(tr("Open in browser"));
    connect(action, &QAction::triggered, this, &EndpointDialog::openMap);
50
    action = map_menu_->addAction(tr("Save As…"));
51 52 53
    connect(action, &QAction::triggered, this, &EndpointDialog::saveMap);
    map_bt_->setMenu(map_menu_);
#endif
Gerald Combs's avatar
Gerald Combs committed
54 55
    addProgressFrame(&parent);

56 57 58 59 60 61 62 63 64 65 66 67 68
    QList<int> endp_protos;
    for (GList *endp_tab = recent.endpoint_tabs; endp_tab; endp_tab = endp_tab->next) {
        int proto_id = proto_get_id_by_short_name((const char *)endp_tab->data);
        if (proto_id > -1 && !endp_protos.contains(proto_id)) {
            endp_protos.append(proto_id);
        }
    }

    if (endp_protos.isEmpty()) {
        endp_protos = defaultProtos();
    }

    // Bring the command-line specified type to the front.
69
    if ((cli_proto_id > 0) && (get_conversation_by_proto_id(cli_proto_id))) {
70 71 72 73 74 75 76 77 78 79 80
        endp_protos.removeAll(cli_proto_id);
        endp_protos.prepend(cli_proto_id);
    }

    // QTabWidget selects the first item by default.
    foreach (int endp_proto, endp_protos) {
        addTrafficTable(get_conversation_by_proto_id(endp_proto));
    }

    fillTypeMenu(endp_protos);

81 82 83 84 85
    QPushButton *close_bt = buttonBox()->button(QDialogButtonBox::Close);
    if (close_bt) {
        close_bt->setDefault(true);
    }

86 87
    updateWidgets();
//    currentTabChanged();
88

Gerald Combs's avatar
Gerald Combs committed
89
    cap_file_.delayedRetapPackets();
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
}

EndpointDialog::~EndpointDialog()
{
    prefs_clear_string_list(recent.endpoint_tabs);
    recent.endpoint_tabs = NULL;

    EndpointTreeWidget *cur_tree = qobject_cast<EndpointTreeWidget *>(trafficTableTabWidget()->currentWidget());
    foreach (QAction *ea, traffic_type_menu_.actions()) {
        int proto_id = ea->data().value<int>();
        if (proto_id_to_tree_.contains(proto_id) && ea->isChecked()) {
            char *title = g_strdup(proto_get_protocol_short_name(find_protocol_by_id(proto_id)));
            if (proto_id_to_tree_[proto_id] == cur_tree) {
                recent.endpoint_tabs = g_list_prepend(recent.endpoint_tabs, title);
            } else {
                recent.endpoint_tabs = g_list_append(recent.endpoint_tabs, title);
            }
        }
    }
}

111
void EndpointDialog::captureFileClosing()
112
{
113 114 115 116
    // Keep the dialog around but disable any controls that depend
    // on a live capture file.
    for (int i = 0; i < trafficTableTabWidget()->count(); i++) {
        EndpointTreeWidget *cur_tree = qobject_cast<EndpointTreeWidget *>(trafficTableTabWidget()->widget(i));
117 118
        disconnect(cur_tree, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)),
                   this, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)));
119
    }
120 121 122 123 124
    TrafficTableDialog::captureFileClosing();
}

void EndpointDialog::captureFileClosed()
{
125 126
    displayFilterCheckBox()->setEnabled(false);
    enabledTypesPushButton()->setEnabled(false);
127
    TrafficTableDialog::captureFileClosed();
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
}

bool EndpointDialog::addTrafficTable(register_ct_t *table)
{
    int proto_id = get_conversation_proto_id(table);

    if (!table || proto_id_to_tree_.contains(proto_id)) {
        return false;
    }

    EndpointTreeWidget *endp_tree = new EndpointTreeWidget(this, table);

    proto_id_to_tree_[proto_id] = endp_tree;
    const char* table_name = proto_get_protocol_short_name(find_protocol_by_id(proto_id));

    trafficTableTabWidget()->addTab(endp_tree, table_name);

    connect(endp_tree, SIGNAL(titleChanged(QWidget*,QString)),
            this, SLOT(setTabText(QWidget*,QString)));
147 148
    connect(endp_tree, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)),
            this, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)));
149 150
    connect(nameResolutionCheckBox(), SIGNAL(toggled(bool)),
            endp_tree, SLOT(setNameResolutionEnabled(bool)));
151 152 153 154
#ifdef HAVE_MAXMINDDB
    connect(endp_tree, &EndpointTreeWidget::geoIPStatusChanged,
            this, &EndpointDialog::tabChanged);
#endif
155 156

    // XXX Move to ConversationTreeWidget ctor?
Peter Wu's avatar
Peter Wu committed
157
    QByteArray filter_utf8;
158 159
    const char *filter = NULL;
    if (displayFilterCheckBox()->isChecked()) {
160
        filter = cap_file_.capFile()->dfilter;
161
    } else if (!filter_.isEmpty()) {
Peter Wu's avatar
Peter Wu committed
162 163
        filter_utf8 = filter_.toUtf8();
        filter = filter_utf8.constData();
164 165 166 167
    }

    endp_tree->trafficTreeHash()->user_data = endp_tree;

Gerald Combs's avatar
Gerald Combs committed
168 169 170 171
    registerTapListener(proto_get_protocol_filter_name(proto_id), endp_tree->trafficTreeHash(), filter, 0,
                        EndpointTreeWidget::tapReset,
                        get_hostlist_packet_func(table),
                        EndpointTreeWidget::tapDraw);
172 173 174
    return true;
}

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
#ifdef HAVE_MAXMINDDB
void EndpointDialog::tabChanged()
{
    EndpointTreeWidget *cur_tree = qobject_cast<EndpointTreeWidget *>(trafficTableTabWidget()->currentWidget());
    map_bt_->setEnabled(cur_tree && cur_tree->hasGeoIPData());
}

QUrl EndpointDialog::createMap(bool json_only)
{
    EndpointTreeWidget *cur_tree = qobject_cast<EndpointTreeWidget *>(trafficTableTabWidget()->currentWidget());
    if (!cur_tree) {
        return QUrl();
    }

    // Construct list of hosts with a valid MMDB entry.
    QTreeWidgetItemIterator it(cur_tree);
    GPtrArray *hosts_arr = g_ptr_array_new();
    while (*it) {
        const mmdb_lookup_t *geo = VariantPointer<const mmdb_lookup_t>::asPtr((*it)->data(0, Qt::UserRole + 1));
        if (maxmind_db_has_coords(geo)) {
            hostlist_talker_t *host = VariantPointer<hostlist_talker_t>::asPtr((*it)->data(0, Qt::UserRole));
            g_ptr_array_add(hosts_arr, (gpointer)host);
        }
        ++it;
    }
    if (hosts_arr->len == 0) {
        QMessageBox::warning(this, tr("Map file error"), tr("No endpoints available to map"));
        g_ptr_array_free(hosts_arr, TRUE);
        return QUrl();
    }
    g_ptr_array_add(hosts_arr, NULL);
    hostlist_talker_t **hosts = (hostlist_talker_t **)g_ptr_array_free(hosts_arr, FALSE);

208 209
    QString tempname = QString("%1/ipmapXXXXXX.html").arg(QDir::tempPath());
    QTemporaryFile tf(tempname);
210 211 212 213
    if (!tf.open()) {
        QMessageBox::warning(this, tr("Map file error"), tr("Unable to create temporary file"));
        g_free(hosts);
        return QUrl();
214
    }
215 216 217 218 219 220

    //
    // XXX - At least with Qt 5.12 retrieving the name only works when
    // it has been retrieved at least once when the file is open.
    //
    QString tempfilename = tf.fileName();
221
    int fd = tf.handle();
222 223 224 225
    //
    // XXX - QFileDevice.handle() can return -1, but can QTemporaryFile.handle()
    // do so if QTemporaryFile.open() has succeeded?
    //
226 227 228 229 230
    if (fd == -1) {
        QMessageBox::warning(this, tr("Map file error"), tr("Unable to create temporary file"));
        g_free(hosts);
        return QUrl();
    }
231
    // duplicate file descriptor as it is not allowed to perform a fclose before closing QFile
232 233 234 235 236 237 238
    int duped_fd = ws_dup(fd);
    if (duped_fd == -1) {
        QMessageBox::warning(this, tr("Map file error"), tr("Unable to create temporary file"));
        g_free(hosts);
        return QUrl();
    }
    FILE* fp = ws_fdopen(duped_fd, "wb");
239 240 241
    if (fp == NULL) {
        QMessageBox::warning(this, tr("Map file error"), tr("Unable to create temporary file"));
        g_free(hosts);
242
        ws_close(duped_fd);
243 244
        return QUrl();
    }
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259

    gchar *err_str;
    if (!write_endpoint_geoip_map(fp, json_only, hosts, &err_str)) {
        QMessageBox::warning(this, tr("Map file error"), err_str);
        g_free(err_str);
        g_free(hosts);
        fclose(fp);
        return QUrl();
    }
    g_free(hosts);
    if (fclose(fp) == EOF) {
        QMessageBox::warning(this, tr("Map file error"), g_strerror(errno));
        return QUrl();
    }

260
    tf.setAutoRemove(false);
261
    return QUrl::fromLocalFile(tf.fileName());
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
}

void EndpointDialog::openMap()
{
    QUrl map_file = createMap(false);
    if (!map_file.isEmpty()) {
        QDesktopServices::openUrl(map_file);
    }
}

void EndpointDialog::saveMap()
{
    QString destination_file =
        WiresharkFileDialog::getSaveFileName(this, tr("Save Endpoints Map"),
                "ipmap.html",
                "HTML files (*.html);;GeoJSON files (*.json)");
    if (destination_file.isEmpty()) {
        return;
    }
    QUrl map_file = createMap(destination_file.endsWith(".json"));
    if (!map_file.isEmpty()) {
        QString source_file = map_file.toLocalFile();
        QFile::remove(destination_file);
        if (!QFile::rename(source_file, destination_file)) {
            QMessageBox::warning(this, tr("Map file error"),
                    tr("Failed to save map file %1.").arg(destination_file));
            QFile::remove(source_file);
        }
    }
}
#endif

294 295 296 297 298 299 300 301 302 303 304 305 306
void EndpointDialog::on_buttonBox_helpRequested()
{
    wsApp->helpTopicAction(HELP_STATS_ENDPOINTS_DIALOG);
}

void init_endpoint_table(struct register_ct* ct, const char *filter)
{
    wsApp->emitStatCommandSignal("Endpoints", filter, GINT_TO_POINTER(get_conversation_proto_id(ct)));
}

// EndpointTreeWidgetItem
// TrafficTableTreeWidgetItem / QTreeWidgetItem subclass that allows sorting

307
static const char *data_none_ = UTF8_EM_DASH;
308 309 310 311

class EndpointTreeWidgetItem : public TrafficTableTreeWidgetItem
{
public:
312 313
    EndpointTreeWidgetItem(GArray *conv_array, guint conv_idx, bool *resolve_names_ptr) :
        TrafficTableTreeWidgetItem(NULL),
314 315
        conv_array_(conv_array),
        conv_idx_(conv_idx),
316
        resolve_names_ptr_(resolve_names_ptr)
317 318 319 320 321
    {}

    hostlist_talker_t *hostlistTalker() {
        return &g_array_index(conv_array_, hostlist_talker_t, conv_idx_);
    }
322

323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
    virtual QVariant data(int column, int role) const {
        if (role == Qt::DisplayRole) {
            // Column text cooked representation.
            hostlist_talker_t *endp_item = &g_array_index(conv_array_, hostlist_talker_t, conv_idx_);

            bool resolve_names = false;
            if (resolve_names_ptr_ && *resolve_names_ptr_) resolve_names = true;
            switch (column) {
            case ENDP_COLUMN_PACKETS:
                return QString("%L1").arg(endp_item->tx_frames + endp_item->rx_frames);
            case ENDP_COLUMN_BYTES:
                return gchar_free_to_qstring(format_size(endp_item->tx_bytes + endp_item->rx_bytes, format_size_unit_none|format_size_prefix_si));
            case ENDP_COLUMN_PKT_AB:
                return QString("%L1").arg(endp_item->tx_frames);
            case ENDP_COLUMN_BYTES_AB:
                return gchar_free_to_qstring(format_size(endp_item->tx_bytes, format_size_unit_none|format_size_prefix_si));
            case ENDP_COLUMN_PKT_BA:
                return QString("%L1").arg(endp_item->rx_frames);
            case ENDP_COLUMN_BYTES_BA:
                return gchar_free_to_qstring(format_size(endp_item->rx_bytes, format_size_unit_none|format_size_prefix_si));
            default:
344 345 346
                QVariant col_data = colData(column, resolve_names);
                if (col_data.isValid()) return col_data;
                return QVariant(data_none_);
347 348
            }
        }
349 350 351 352 353 354 355
        if (role == Qt::UserRole) {
            hostlist_talker_t *endp_item = &g_array_index(conv_array_, hostlist_talker_t, conv_idx_);
            return VariantPointer<hostlist_talker_t>::asQVariant(endp_item);
        }
        if (role == Qt::UserRole + 1) {
            return VariantPointer<const mmdb_lookup_t>::asQVariant(mmdbLookup());
        }
356
        return QTreeWidgetItem::data(column, role);
357 358
    }

359
    const mmdb_lookup_t *mmdbLookup() const {
360
        hostlist_talker_t *endp_item = &g_array_index(conv_array_, hostlist_talker_t, conv_idx_);
361 362
        const mmdb_lookup_t *mmdb_lookup = NULL;
        if (endp_item->myaddress.type == AT_IPv4) {
363
            mmdb_lookup = maxmind_db_lookup_ipv4((const ws_in4_addr *) endp_item->myaddress.data);
364
        } else if (endp_item->myaddress.type == AT_IPv6) {
365
            mmdb_lookup = maxmind_db_lookup_ipv6((const ws_in6_addr *) endp_item->myaddress.data);
366
        }
367 368 369 370 371 372 373 374
        return mmdb_lookup && mmdb_lookup->found ? mmdb_lookup : NULL;
    }

    // Column text raw representation.
    // Return a string, qulonglong, double, or invalid QVariant representing the raw column data.
    QVariant colData(int col, bool resolve_names) const {
        hostlist_talker_t *endp_item = &g_array_index(conv_array_, hostlist_talker_t, conv_idx_);
        const mmdb_lookup_t *mmdb_lookup = mmdbLookup();
375

376 377
        switch (col) {
        case ENDP_COLUMN_ADDR:
378
        {
379
            char* addr_str = get_conversation_address(NULL, &endp_item->myaddress, resolve_names);
380 381 382
            QString q_addr_str(addr_str);
            wmem_free(NULL, addr_str);
            return q_addr_str;
383
        }
384 385
        case ENDP_COLUMN_PORT:
            if (resolve_names) {
386
                char* port_str = get_conversation_port(NULL, endp_item->port, endp_item->etype, resolve_names);
387 388 389
                QString q_port_str(port_str);
                wmem_free(NULL, port_str);
                return q_port_str;
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
            } else {
                return quint32(endp_item->port);
            }
        case ENDP_COLUMN_PACKETS:
            return quint64(endp_item->tx_frames + endp_item->rx_frames);
        case ENDP_COLUMN_BYTES:
            return quint64(endp_item->tx_bytes + endp_item->rx_bytes);
        case ENDP_COLUMN_PKT_AB:
            return quint64(endp_item->tx_frames);
        case ENDP_COLUMN_BYTES_AB:
            return quint64(endp_item->tx_bytes);
        case ENDP_COLUMN_PKT_BA:
            return quint64(endp_item->rx_frames);
        case ENDP_COLUMN_BYTES_BA:
            return quint64(endp_item->rx_bytes);
405
        case ENDP_COLUMN_GEO_COUNTRY:
406
            if (mmdb_lookup && mmdb_lookup->country) {
407
                return QVariant(mmdb_lookup->country);
408
            }
409 410
            return QVariant();
        case ENDP_COLUMN_GEO_CITY:
411
            if (mmdb_lookup && mmdb_lookup->city) {
412
                return QVariant(mmdb_lookup->city);
413
            }
414 415
            return QVariant();
        case ENDP_COLUMN_GEO_AS_NUM:
416
            if (mmdb_lookup && mmdb_lookup->as_number) {
417
                return QVariant(mmdb_lookup->as_number);
418
            }
419 420
            return QVariant();
        case ENDP_COLUMN_GEO_AS_ORG:
421
            if (mmdb_lookup && mmdb_lookup->as_org) {
422
                return QVariant(mmdb_lookup->as_org);
423
            }
424
            return QVariant();
425 426 427 428 429

        default:
            return QVariant();
        }
    }
430

431 432
    bool operator< (const QTreeWidgetItem &other) const
    {
433 434 435
        const EndpointTreeWidgetItem *other_row = static_cast<const EndpointTreeWidgetItem *>(&other);
        hostlist_talker_t *endp_item = &g_array_index(conv_array_, hostlist_talker_t, conv_idx_);
        hostlist_talker_t *other_item = &g_array_index(other_row->conv_array_, hostlist_talker_t, other_row->conv_idx_);
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455

        int sort_col = treeWidget()->sortColumn();

        switch(sort_col) {
        case ENDP_COLUMN_ADDR:
            return cmp_address(&endp_item->myaddress, &other_item->myaddress) < 0 ? true : false;
        case ENDP_COLUMN_PORT:
            return endp_item->port < other_item->port;
        case ENDP_COLUMN_PACKETS:
            return (endp_item->tx_frames + endp_item->rx_frames) < (other_item->tx_frames + other_item->rx_frames);
        case ENDP_COLUMN_BYTES:
            return (endp_item->tx_bytes + endp_item->rx_bytes) < (other_item->tx_bytes + other_item->rx_bytes);
        case ENDP_COLUMN_PKT_AB:
            return endp_item->tx_frames < other_item->tx_frames;
        case ENDP_COLUMN_BYTES_AB:
            return endp_item->tx_bytes < other_item->tx_bytes;
        case ENDP_COLUMN_PKT_BA:
            return endp_item->rx_frames < other_item->rx_frames;
        case ENDP_COLUMN_BYTES_BA:
            return endp_item->rx_bytes < other_item->rx_bytes;
456 457 458
        case ENDP_COLUMN_GEO_COUNTRY:
        case ENDP_COLUMN_GEO_CITY:
        case ENDP_COLUMN_GEO_AS_ORG:
459
        {
460 461 462 463 464 465 466 467 468 469 470 471 472
            QString this_str = data(sort_col, Qt::DisplayRole).toString();
            QString other_str = other_row->data(sort_col, Qt::DisplayRole).toString();
            return (this_str < other_str);
        }
        case ENDP_COLUMN_GEO_AS_NUM:
        {
            // Valid values first, similar to strings above.
            bool ok;
            unsigned this_asn = colData(sort_col, false).toUInt(&ok);
            if (!ok) this_asn = UINT_MAX;
            unsigned other_asn = other_row->colData(sort_col, false).toUInt(&ok);
            if (!ok) other_asn = UINT_MAX;
            return (this_asn < other_asn);
473 474 475 476 477
        }
        default:
            return false;
        }
    }
478 479 480
private:
    GArray *conv_array_;
    guint conv_idx_;
481
    bool *resolve_names_ptr_;
482 483 484 485 486 487 488 489
};

//
// EndpointTreeWidget
// TrafficTableTreeWidget / QTreeWidget subclass that allows tapping
//

EndpointTreeWidget::EndpointTreeWidget(QWidget *parent, register_ct_t *table) :
490
    TrafficTableTreeWidget(parent, table),
491 492 493
#ifdef HAVE_MAXMINDDB
    has_geoip_data_(false),
#endif
494
    table_address_type_(AT_NONE)
495
{
496
    setColumnCount(ENDP_NUM_COLUMNS);
497
    setUniformRowHeights(true);
498

499 500 501 502 503
    QString proto_filter_name = proto_get_protocol_filter_name(get_conversation_proto_id(table_));
    if (proto_filter_name == "ip") {
        table_address_type_ = AT_IPv4;
    } else if (proto_filter_name == "ipv6") {
        table_address_type_ = AT_IPv6;
504 505 506
    }
    if (get_conversation_hide_ports(table_)) {
        hideColumn(ENDP_COLUMN_PORT);
507
    } else if (proto_filter_name == "ncp") {
508 509 510
        headerItem()->setText(ENDP_COLUMN_PORT, endp_conn_title);
    }

511 512 513
    int column_count = ENDP_NUM_COLUMNS;
    if (table_address_type_ == AT_IPv4 || table_address_type_ == AT_IPv6) {
        column_count = ENDP_NUM_GEO_COLUMNS;
514
    }
515 516 517 518
    for (int col = 0; col < column_count; col++) {
        headerItem()->setText(col, endp_column_titles[col]);
    }

519 520 521 522 523

    int one_en = fontMetrics().height() / 2;
    for (int i = 0; i < columnCount(); i++) {
        switch (i) {
        case ENDP_COLUMN_ADDR:
524
            setColumnWidth(i, one_en * (int) strlen("000.000.000.000"));
525 526
            break;
        case ENDP_COLUMN_PORT:
527
            setColumnWidth(i, one_en * (int) strlen("000000"));
528 529 530 531
            break;
        case ENDP_COLUMN_PACKETS:
        case ENDP_COLUMN_PKT_AB:
        case ENDP_COLUMN_PKT_BA:
532
            setColumnWidth(i, one_en * (int) strlen("00,000"));
533 534 535 536
            break;
        case ENDP_COLUMN_BYTES:
        case ENDP_COLUMN_BYTES_AB:
        case ENDP_COLUMN_BYTES_BA:
537
            setColumnWidth(i, one_en * (int) strlen("000,000"));
538 539
            break;
        default:
540
            setColumnWidth(i, one_en * 15); // Geolocation
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
        }
    }

    QMenu *submenu;

    FilterAction::Action cur_action = FilterAction::ActionApply;
    submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
    foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
        FilterAction *fa = new FilterAction(submenu, cur_action, at);
        submenu->addAction(fa);
        connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
    }

    cur_action = FilterAction::ActionPrepare;
    submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
    foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
        FilterAction *fa = new FilterAction(submenu, cur_action, at);
        submenu->addAction(fa);
        connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
    }

    cur_action = FilterAction::ActionFind;
    submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
    foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
        FilterAction *fa = new FilterAction(submenu, cur_action, at);
        submenu->addAction(fa);
        connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
    }

    cur_action = FilterAction::ActionColorize;
    submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
    foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
        FilterAction *fa = new FilterAction(submenu, cur_action, at);
        submenu->addAction(fa);
        connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
    }

578
    updateItems();
579 580 581 582 583 584 585 586 587 588 589

}

EndpointTreeWidget::~EndpointTreeWidget()
{
    reset_hostlist_table_data(&hash_);
}

void EndpointTreeWidget::tapReset(void *conv_hash_ptr)
{
    conv_hash_t *hash = (conv_hash_t*)conv_hash_ptr;
590
    EndpointTreeWidget *endp_tree = qobject_cast<EndpointTreeWidget *>((EndpointTreeWidget *)hash->user_data);
591 592 593 594 595 596 597 598 599
    if (!endp_tree) return;

    endp_tree->clear();
    reset_hostlist_table_data(&endp_tree->hash_);
}

void EndpointTreeWidget::tapDraw(void *conv_hash_ptr)
{
    conv_hash_t *hash = (conv_hash_t*)conv_hash_ptr;
600
    EndpointTreeWidget *endp_tree = qobject_cast<EndpointTreeWidget *>((EndpointTreeWidget *)hash->user_data);
601 602
    if (!endp_tree) return;

603
    endp_tree->updateItems();
604 605
}

606
void EndpointTreeWidget::updateItems()
607
{
608
    bool resize = topLevelItemCount() < resizeThreshold();
609 610 611 612 613 614 615 616 617 618 619 620
    title_ = proto_get_protocol_short_name(find_protocol_by_id(get_conversation_proto_id(table_)));

    if (hash_.conv_array && hash_.conv_array->len > 0) {
        title_.append(QString(" %1 %2").arg(UTF8_MIDDLE_DOT).arg(hash_.conv_array->len));
    }
    emit titleChanged(this, title_);

    if (!hash_.conv_array) {
        return;
    }

    setSortingEnabled(false);
621 622

    QList<QTreeWidgetItem *>new_items;
623
    for (int i = topLevelItemCount(); i < (int) hash_.conv_array->len; i++) {
624 625
        EndpointTreeWidgetItem *etwi = new EndpointTreeWidgetItem(hash_.conv_array, i, &resolve_names_);
        new_items << etwi;
626 627 628 629 630 631

        for (int col = 0; col < columnCount(); col++) {
            if (col != ENDP_COLUMN_ADDR && col < ENDP_NUM_COLUMNS) {
                etwi->setTextAlignment(col, Qt::AlignRight);
            }
        }
632 633 634 635 636 637 638 639 640 641

#ifdef HAVE_MAXMINDDB
        // Assume that an asynchronous MMDB lookup has completed before (for
        // example, in the dissection tree). If so, then we do not have to check
        // all previous items for availability of any MMDB result.
        if (!has_geoip_data_ && maxmind_db_has_coords(etwi->mmdbLookup())) {
            has_geoip_data_ = true;
            emit geoIPStatusChanged();
        }
#endif
642
    }
643
    addTopLevelItems(new_items);
644 645
    setSortingEnabled(true);

646 647 648 649
    if (resize) {
        for (int col = 0; col < columnCount(); col++) {
            resizeColumnToContents(col);
        }
650 651 652 653 654 655 656 657 658 659 660 661
    }
}

void EndpointTreeWidget::filterActionTriggered()
{
    EndpointTreeWidgetItem *etwi = static_cast<EndpointTreeWidgetItem *>(currentItem());
    FilterAction *fa = qobject_cast<FilterAction *>(QObject::sender());

    if (!fa || !etwi) {
        return;
    }

662
    hostlist_talker_t *endp_item = etwi->hostlistTalker();
663 664 665 666 667 668 669
    if (!endp_item) {
        return;
    }

    QString filter = get_hostlist_filter(endp_item);
    emit filterAction(filter, fa->action(), fa->actionType());
}