Kerberos: add kerberos_inject_longterm_key() helper function
[wireshark-sm.git] / ui / qt / follow_stream_dialog.cpp
blob177c2209c97ba481fd85cf2b7b123619e0eb7717
1 /* follow_stream_dialog.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
8 */
10 #include "follow_stream_dialog.h"
11 #include <ui_follow_stream_dialog.h>
13 #include "main_application.h"
14 #include "main_window.h"
16 #include "frame_tvbuff.h"
17 #include "epan/follow.h"
18 #include "epan/prefs.h"
19 #include "epan/addr_resolv.h"
20 #include "epan/charsets.h"
21 #include "epan/epan_dissect.h"
22 #include "epan/tap.h"
24 #include "ui/alert_box.h"
25 #include "ui/simple_dialog.h"
26 #include <ui/recent.h>
27 #include <wsutil/utf8_entities.h>
28 #include <wsutil/ws_assert.h>
30 #include "wsutil/file_util.h"
31 #include "wsutil/str_util.h"
32 #include "wsutil/filesystem.h"
34 #include "ws_symbol_export.h"
36 #include <ui/qt/utils/color_utils.h>
37 #include <ui/qt/utils/qt_ui_utils.h>
39 #include "progress_frame.h"
41 #include "ui/qt/widgets/wireshark_file_dialog.h"
43 #include <QElapsedTimer>
44 #include <QKeyEvent>
45 #include <QMessageBox>
46 #include <QMutex>
47 #include <QPrintDialog>
48 #include <QPrinter>
49 #include <QScrollBar>
50 #include <QTextCodec>
52 // To do:
53 // - Show text while tapping.
54 // - Instead of calling QMessageBox, display the error message in the text
55 // box and disable the appropriate controls.
56 // - Add a progress bar and connect captureCaptureUpdateContinue to it
58 // Matches SplashOverlay.
59 static int info_update_freq_ = 100;
61 // Handle the loop breaking notification properly
62 static QMutex loop_break_mutex;
64 // Indicates that a Follow Stream is currently running
65 static bool isReadRunning;
67 Q_DECLARE_METATYPE(bytes_show_type)
69 FollowStreamDialog::FollowStreamDialog(QWidget &parent, CaptureFile &cf, int proto_id) :
70 WiresharkDialog(parent, cf),
71 ui(new Ui::FollowStreamDialog),
72 b_find_(NULL),
73 follower_(NULL),
74 client_buffer_count_(0),
75 server_buffer_count_(0),
76 client_packet_count_(0),
77 server_packet_count_(0),
78 last_packet_(0),
79 last_from_server_(0),
80 turns_(0),
81 use_regex_find_(false),
82 terminating_(false),
83 previous_sub_stream_num_(0)
85 ui->setupUi(this);
86 loadGeometry(parent.width() * 2 / 3, parent.height());
88 ui->streamNumberSpinBox->setStyleSheet("QSpinBox { min-width: 2em; }");
89 ui->subStreamNumberSpinBox->setStyleSheet("QSpinBox { min-width: 2em; }");
90 ui->streamNumberSpinBox->setKeyboardTracking(false);
91 ui->subStreamNumberSpinBox->setKeyboardTracking(false);
93 follower_ = get_follow_by_proto_id(proto_id);
94 if (follower_ == NULL) {
95 ws_assert_not_reached();
98 memset(&follow_info_, 0, sizeof(follow_info_));
99 follow_info_.show_stream = BOTH_HOSTS;
100 follow_info_.substream_id = SUBSTREAM_UNUSED;
102 nstime_set_zero(&last_ts_);
104 ui->teStreamContent->installEventFilter(this);
106 connect(ui->leFind, SIGNAL(useRegexFind(bool)), this, SLOT(useRegexFind(bool)));
108 QComboBox *cbcs = ui->cbCharset;
109 cbcs->blockSignals(true);
110 cbcs->addItem(tr("ASCII"), SHOW_ASCII);
111 cbcs->addItem(tr("C Arrays"), SHOW_CARRAY);
112 cbcs->addItem(tr("EBCDIC"), SHOW_EBCDIC);
113 cbcs->addItem(tr("Hex Dump"), SHOW_HEXDUMP);
114 cbcs->addItem(tr("Raw"), SHOW_RAW);
115 // UTF-8 is guaranteed to exist as a QTextCodec
116 cbcs->addItem(tr("UTF-8"), SHOW_CODEC);
117 cbcs->addItem(tr("YAML"), SHOW_YAML);
118 cbcs->setCurrentIndex(cbcs->findData(recent.gui_follow_show));
119 cbcs->blockSignals(false);
121 ui->deltaComboBox->setCurrentIndex(recent.gui_follow_delta);
123 b_filter_out_ = ui->buttonBox->addButton(tr("Filter Out This Stream"), QDialogButtonBox::ActionRole);
124 connect(b_filter_out_, &QPushButton::clicked, this, &FollowStreamDialog::filterOut);
126 b_print_ = ui->buttonBox->addButton(tr("Print"), QDialogButtonBox::ActionRole);
127 connect(b_print_, &QPushButton::clicked, this, &FollowStreamDialog::printStream);
129 b_save_ = ui->buttonBox->addButton(tr("Save as…"), QDialogButtonBox::ActionRole);
130 connect(b_save_, &QPushButton::clicked, this, &FollowStreamDialog::saveAs);
132 b_back_ = ui->buttonBox->addButton(tr("Back"), QDialogButtonBox::ActionRole);
133 connect(b_back_, &QPushButton::clicked, this, &FollowStreamDialog::backButton);
135 ProgressFrame::addToButtonBox(ui->buttonBox, &parent);
137 connect(ui->cbDirections, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
138 this, &FollowStreamDialog::cbDirectionsCurrentIndexChanged);
139 connect(ui->cbCharset, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
140 this, &FollowStreamDialog::cbCharsetCurrentIndexChanged);
141 connect(ui->deltaComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
142 this, &FollowStreamDialog::deltaComboBoxCurrentIndexChanged);
144 connect(ui->streamNumberSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
145 this, &FollowStreamDialog::streamNumberSpinBoxValueChanged);
146 connect(ui->subStreamNumberSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
147 this, &FollowStreamDialog::subStreamNumberSpinBoxValueChanged);
149 connect(ui->buttonBox, &QDialogButtonBox::helpRequested, this, &FollowStreamDialog::helpButton);
150 connect(ui->teStreamContent, &FollowStreamText::mouseMovedToPacket,
151 this, &FollowStreamDialog::fillHintLabel);
152 connect(ui->teStreamContent, &FollowStreamText::mouseClickedOnPacket,
153 this, &FollowStreamDialog::goToPacketForTextPos);
155 connect(ui->bFind, &QPushButton::clicked, this, &FollowStreamDialog::bFindClicked);
156 connect(ui->leFind, &FindLineEdit::returnPressed, this, &FollowStreamDialog::leFindReturnPressed);
158 connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &FollowStreamDialog::buttonBoxRejected);
160 fillHintLabel();
163 FollowStreamDialog::~FollowStreamDialog()
165 delete ui;
166 resetStream(); // Frees payload
169 void FollowStreamDialog::addCodecs(const QMap<QString, QTextCodec *> &codecMap)
171 // Make the combobox respect max visible items?
172 //ui->cbCharset->setStyleSheet("QComboBox { combobox-popup: 0;}");
173 ui->cbCharset->insertSeparator(ui->cbCharset->count());
174 for (const auto &codec : codecMap) {
175 // This is already in the menu and handled separately
176 if (codec->name() != "US-ASCII" && codec->name() != "UTF-8")
177 ui->cbCharset->addItem(tr(codec->name()), SHOW_CODEC);
181 void FollowStreamDialog::printStream()
183 #ifndef QT_NO_PRINTER
184 QPrinter printer(QPrinter::HighResolution);
185 QPrintDialog dialog(&printer, this);
186 if (dialog.exec() == QDialog::Accepted)
187 ui->teStreamContent->print(&printer);
188 #endif
191 void FollowStreamDialog::fillHintLabel(int pkt)
193 QString hint;
195 bool is_stratoshark = strcmp(get_configuration_namespace(), "Stratoshark") == 0;
197 if (is_stratoshark) {
198 if (pkt > 0) {
199 hint = tr("Event %1. ").arg(pkt);
202 hint += tr("%Ln <span style=\"color: %1; background-color:%2\">reads</span>, ", "", client_packet_count_)
203 .arg(ColorUtils::fromColorT(prefs.st_client_fg).name(),
204 ColorUtils::fromColorT(prefs.st_client_bg).name())
205 + tr("%Ln <span style=\"color: %1; background-color:%2\">writes</span>, ", "", server_packet_count_)
206 .arg(ColorUtils::fromColorT(prefs.st_server_fg).name(),
207 ColorUtils::fromColorT(prefs.st_server_bg).name())
208 + tr("%Ln turn(s).", "", turns_);
209 } else {
210 if (pkt > 0) {
211 hint = tr("Packet %1. ").arg(pkt);
214 hint += tr("%Ln <span style=\"color: %1; background-color:%2\">client</span> pkt(s), ", "", client_packet_count_)
215 .arg(ColorUtils::fromColorT(prefs.st_client_fg).name(),
216 ColorUtils::fromColorT(prefs.st_client_bg).name())
217 + tr("%Ln <span style=\"color: %1; background-color:%2\">server</span> pkt(s), ", "", server_packet_count_)
218 .arg(ColorUtils::fromColorT(prefs.st_server_fg).name(),
219 ColorUtils::fromColorT(prefs.st_server_bg).name())
220 + tr("%Ln turn(s).", "", turns_);
223 if (pkt > 0) {
224 hint.append(tr(" Click to select."));
227 hint.prepend("<small><i>");
228 hint.append("</i></small>");
229 ui->hintLabel->setText(hint);
232 void FollowStreamDialog::goToPacketForTextPos(int pkt)
234 if (file_closed_) {
235 return;
238 if (pkt > 0) {
239 emit goToPacket(pkt);
243 void FollowStreamDialog::updateWidgets(bool follow_in_progress)
245 // XXX: If follow_in_progress set cursor to Qt::BusyCursor or WaitCursor,
246 // otherwise unset cursor?
247 bool enable = !follow_in_progress;
248 if (file_closed_) {
249 ui->teStreamContent->setEnabled(true);
250 enable = false;
253 ui->cbDirections->setEnabled(enable);
254 ui->cbCharset->setEnabled(enable);
255 ui->streamNumberSpinBox->setReadOnly(!enable);
256 if (get_follow_sub_stream_id_func(follower_) != NULL) {
257 ui->subStreamNumberSpinBox->setReadOnly(!enable);
259 ui->leFind->setEnabled(enable);
260 ui->bFind->setEnabled(enable);
261 b_filter_out_->setEnabled(enable);
262 b_print_->setEnabled(enable);
263 b_save_->setEnabled(enable);
265 WiresharkDialog::updateWidgets();
268 void FollowStreamDialog::useRegexFind(bool use_regex)
270 use_regex_find_ = use_regex;
271 if (use_regex_find_)
272 ui->lFind->setText(tr("Regex Find:"));
273 else
274 ui->lFind->setText(tr("Find:"));
277 // This only calls itself with go_back false, so never recurses more than once.
278 // NOLINTNEXTLINE(misc-no-recursion)
279 void FollowStreamDialog::findText(bool go_back)
281 if (ui->leFind->text().isEmpty()) return;
283 bool found;
285 QTextDocument::FindFlags options;
286 if (ui->caseCheckBox->isChecked()) {
287 options |= QTextDocument::FindCaseSensitively;
289 if (use_regex_find_) {
290 // https://bugreports.qt.io/browse/QTBUG-88721
291 // QPlainTextEdit::find() searches case-insensitively unless
292 // QTextDocument::FindCaseSensitively is explicitly given.
293 // This *does* apply to QRegularExpression (overriding
294 // CaseInsensitiveOption), but not QRegExp.
296 // QRegularExpression and QRegExp do not support Perl's /i, but
297 // the former at least does support the mode modifiers (?i) and
298 // (?-i), which can override QTextDocument::FindCaseSensitively.
299 QRegularExpression regex(ui->leFind->text(), QRegularExpression::UseUnicodePropertiesOption);
300 found = ui->teStreamContent->find(regex, options);
301 } else {
302 found = ui->teStreamContent->find(ui->leFind->text(), options);
305 if (found) {
306 ui->teStreamContent->setFocus();
307 } else if (go_back) {
308 ui->teStreamContent->moveCursor(QTextCursor::Start);
309 findText(false);
313 void FollowStreamDialog::saveAs()
315 QString file_name = WiresharkFileDialog::getSaveFileName(this, mainApp->windowTitleString(tr("Save Stream Content As…")));
316 if (file_name.isEmpty()) {
317 return;
320 QFile file(file_name);
321 if (!file.open(QIODevice::WriteOnly)) {
322 open_failure_alert_box(file_name.toUtf8().constData(), errno, true);
323 return;
326 // XXX: What if truncated_ is true? We should save the entire stream.
327 // Unconditionally save data as UTF-8 (even if data is decoded otherwise).
328 QByteArray bytes = ui->teStreamContent->toPlainText().toUtf8();
329 if (recent.gui_follow_show == SHOW_RAW) {
330 // The "Raw" format is currently displayed as hex data and needs to be
331 // converted to binary data. fromHex() skips over non hex characters
332 // including line breaks, which is what we want.
333 bytes = QByteArray::fromHex(bytes);
336 QDataStream out(&file);
337 out.writeRawData(bytes.constData(), static_cast<int>(bytes.size()));
340 void FollowStreamDialog::helpButton()
342 mainApp->helpTopicAction(HELP_FOLLOW_STREAM_DIALOG);
345 void FollowStreamDialog::backButton()
347 if (terminating_)
348 return;
350 output_filter_ = previous_filter_;
352 close();
355 void FollowStreamDialog::filterOut()
357 if (terminating_)
358 return;
360 output_filter_ = filter_out_filter_;
362 close();
365 void FollowStreamDialog::close()
367 terminating_ = true;
369 // Update filter - Use:
370 // previous_filter if 'Close' (passed in follow() method)
371 // filter_out_filter_ if 'Filter Out This Stream' (built by appending !current_stream to previous_filter)
372 // leave filter alone if window closed. (current stream)
373 emit updateFilter(output_filter_, true);
375 WiresharkDialog::close();
378 void FollowStreamDialog::cbDirectionsCurrentIndexChanged(int idx)
380 switch(idx)
382 case 0 :
383 follow_info_.show_stream = BOTH_HOSTS;
384 break;
385 case 1 :
386 follow_info_.show_stream = FROM_SERVER;
387 break;
388 case 2 :
389 follow_info_.show_stream = FROM_CLIENT;
390 break;
391 default:
392 return;
395 readStream();
398 void FollowStreamDialog::cbCharsetCurrentIndexChanged(int idx)
400 if (idx < 0) return;
401 recent.gui_follow_show = ui->cbCharset->currentData().value<bytes_show_type>();
403 switch (recent.gui_follow_show) {
404 case SHOW_EBCDIC:
405 case SHOW_ASCII:
406 case SHOW_CODEC:
407 ui->deltaComboBox->setEnabled(true);
408 break;
409 default:
410 ui->deltaComboBox->setEnabled(false);
413 readStream();
416 void FollowStreamDialog::deltaComboBoxCurrentIndexChanged(int idx)
418 if (idx < 0) return;
419 recent.gui_follow_delta = static_cast<follow_delta_type>(ui->deltaComboBox->currentIndex());
421 readStream();
424 void FollowStreamDialog::bFindClicked()
426 findText();
429 void FollowStreamDialog::leFindReturnPressed()
431 findText();
434 void FollowStreamDialog::streamNumberSpinBoxValueChanged(int stream_num)
436 if (file_closed_) return;
438 int sub_stream_num = 0;
439 ui->subStreamNumberSpinBox->blockSignals(true);
440 sub_stream_num = ui->subStreamNumberSpinBox->value();
441 ui->subStreamNumberSpinBox->blockSignals(false);
443 bool ok;
444 if (ui->subStreamNumberSpinBox->isVisible()) {
445 /* We need to find a suitable sub stream for the new stream */
446 follow_sub_stream_id_func sub_stream_func;
447 sub_stream_func = get_follow_sub_stream_id_func(follower_);
449 if (sub_stream_func == NULL) {
450 // Should not happen, this field is only visible for suitable protocols.
451 return;
454 unsigned sub_stream_num_new = static_cast<unsigned>(sub_stream_num);
455 if (sub_stream_num < 0) {
456 // Stream ID 0 should always exist as it is used for control messages.
457 // XXX: That is only guaranteed for HTTP2. For example, in QUIC,
458 // stream ID 0 is a normal stream used by the first standard client-
459 // initiated bidirectional stream (if it exists, and it might not)
460 // and we might have a stream (connection) but only the CRYPTO
461 // stream, which does not have a (sub) stream ID.
462 // What should we do if there is a stream with no substream to
463 // follow? Right now the substream spinbox is left active and
464 // the user can change the value to no effect.
465 sub_stream_num_new = 0;
466 ok = true;
467 } else {
468 ok = sub_stream_func(static_cast<unsigned>(stream_num), sub_stream_num_new, false, &sub_stream_num_new);
469 if (!ok) {
470 ok = sub_stream_func(static_cast<unsigned>(stream_num), sub_stream_num_new, true, &sub_stream_num_new);
473 sub_stream_num = static_cast<int>(sub_stream_num_new);
474 } else {
475 /* XXX: For HTTP and TLS, we use the TCP stream index, and really should
476 * return false if the TCP stream doesn't have HTTP or TLS. (Or we could
477 * switch to having separate HTTP and TLS stream numbers.)
479 ok = true;
482 if (stream_num >= 0 && ok) {
483 follow(previous_filter_, true, stream_num, sub_stream_num);
484 previous_sub_stream_num_ = sub_stream_num;
489 void FollowStreamDialog::subStreamNumberSpinBoxValueChanged(int sub_stream_num)
491 if (file_closed_) return;
493 int stream_num = 0;
494 ui->streamNumberSpinBox->blockSignals(true);
495 stream_num = ui->streamNumberSpinBox->value();
496 ui->streamNumberSpinBox->blockSignals(false);
498 follow_sub_stream_id_func sub_stream_func;
499 sub_stream_func = get_follow_sub_stream_id_func(follower_);
501 if (sub_stream_func == NULL) {
502 // Should not happen, this field is only visible for suitable protocols.
503 return;
506 unsigned sub_stream_num_new = static_cast<unsigned>(sub_stream_num);
507 bool ok;
508 /* previous_sub_stream_num_ is a hack to track which buttons was pressed without event handling */
509 if (sub_stream_num < 0) {
510 // Stream ID 0 should always exist as it is used for control messages.
511 // XXX: That is only guaranteed for HTTP2, see above.
512 sub_stream_num_new = 0;
513 ok = true;
514 } else if (previous_sub_stream_num_ < sub_stream_num) {
515 ok = sub_stream_func(static_cast<unsigned>(stream_num), sub_stream_num_new, false, &sub_stream_num_new);
516 } else {
517 ok = sub_stream_func(static_cast<unsigned>(stream_num), sub_stream_num_new, true, &sub_stream_num_new);
519 sub_stream_num = static_cast<int>(sub_stream_num_new);
521 if (ok) {
522 follow(previous_filter_, true, stream_num, sub_stream_num);
523 previous_sub_stream_num_ = sub_stream_num;
527 void FollowStreamDialog::buttonBoxRejected()
529 // Ignore the close button if FollowStreamDialog::close() is running.
530 if (terminating_)
531 return;
533 WiresharkDialog::reject();
536 void FollowStreamDialog::removeStreamControls()
538 ui->horizontalLayout->removeItem(ui->streamNumberSpacer);
539 ui->streamNumberLabel->setVisible(false);
540 ui->streamNumberSpinBox->setVisible(false);
541 ui->subStreamNumberLabel->setVisible(false);
542 ui->subStreamNumberSpinBox->setVisible(false);
545 void FollowStreamDialog::resetStream(void *tap_data)
547 follow_info_t *follow_info = static_cast<follow_info_t*>(tap_data);
548 follow_reset_stream(follow_info);
549 // If we ever draw the text while tapping (instead of only after
550 // the tap finishes), reset the GUI here too.
553 void FollowStreamDialog::resetStream()
555 FollowStreamDialog::resetStream(&follow_info_);
558 void FollowStreamDialog::readStream()
561 // interrupt any reading already running
562 loop_break_mutex.lock();
563 isReadRunning = false;
564 loop_break_mutex.unlock();
566 double scroll_ratio = 0.0;
567 int doc_length = ui->teStreamContent->verticalScrollBar()->maximum() + ui->teStreamContent->verticalScrollBar()->pageStep();
568 if (doc_length > 0) {
569 scroll_ratio = static_cast<double>(ui->teStreamContent->verticalScrollBar()->value()) / doc_length;
572 ui->teStreamContent->clear();
573 switch (recent.gui_follow_show) {
575 case SHOW_CARRAY:
576 case SHOW_HEXDUMP:
577 case SHOW_YAML:
578 /* We control the width and insert line breaks in these formats. */
579 ui->teStreamContent->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
580 break;
582 default:
583 /* Everything else might have extremely long lines without whitespace,
584 * (SHOW_RAW almost surely so), and QTextEdit is O(N^2) trying
585 * to search for word boundaries on long lines when adding text.
587 ui->teStreamContent->setWordWrapMode(QTextOption::WrapAnywhere);
590 client_buffer_count_ = 0;
591 server_buffer_count_ = 0;
592 client_packet_count_ = 0;
593 server_packet_count_ = 0;
594 last_packet_ = 0;
595 turns_ = 0;
597 if (!follower_) {
598 ws_assert_not_reached();
601 readFollowStream();
603 ui->teStreamContent->moveCursor(QTextCursor::Start);
605 doc_length = ui->teStreamContent->verticalScrollBar()->maximum() + ui->teStreamContent->verticalScrollBar()->pageStep();
606 ui->teStreamContent->verticalScrollBar()->setValue(doc_length * scroll_ratio);
609 void
610 FollowStreamDialog::followStream()
612 readStream();
615 void FollowStreamDialog::addText(QString text, bool is_from_server, uint32_t packet_num, bool colorize)
617 ui->teStreamContent->addText(std::move(text), is_from_server, packet_num, colorize);
620 // The following keyboard shortcuts should work (although
621 // they may not work consistently depending on focus):
622 // / (slash), Ctrl-F - Focus and highlight the search box
623 // Ctrl-G, Ctrl-N, F3 - Find next
624 // Should we make it so that typing any text starts searching?
625 bool FollowStreamDialog::eventFilter(QObject *, QEvent *event)
627 if (ui->teStreamContent->hasFocus() && event->type() == QEvent::KeyPress) {
628 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
629 if (keyEvent->matches(QKeySequence::SelectAll) || keyEvent->matches(QKeySequence::Copy)
630 || keyEvent->text().isEmpty()) {
631 return false;
633 ui->leFind->setFocus();
634 if (keyEvent->matches(QKeySequence::Find)) {
635 return true;
636 } else if (keyEvent->matches(QKeySequence::FindNext)) {
637 findText();
638 return true;
642 return false;
645 void FollowStreamDialog::keyPressEvent(QKeyEvent *event)
647 if (ui->leFind->hasFocus()) {
648 if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
649 findText();
650 return;
652 } else {
653 if (event->key() == Qt::Key_Slash || event->matches(QKeySequence::Find)) {
654 ui->leFind->setFocus();
655 ui->leFind->selectAll();
657 return;
660 if (event->key() == Qt::Key_F3 || (event->key() == Qt::Key_N && event->modifiers() & Qt::ControlModifier)) {
661 findText();
662 return;
665 QDialog::keyPressEvent(event);
668 // Replaces non printable ASCII characters in the QByteArray with .
669 // Causes buffer to detach/deep copy *only* if a character has to be
670 // replaced.
671 static inline void sanitize_buffer(QByteArray &buffer, size_t nchars) {
672 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
673 for (int i = 0; i < (int)nchars; i++) {
674 #else
675 for (qsizetype i = 0; i < (qsizetype)nchars; i++) {
676 #endif
677 if (buffer.at(i) == '\n' || buffer.at(i) == '\r' || buffer.at(i) == '\t')
678 continue;
679 if (! g_ascii_isprint((unsigned char)buffer.at(i))) {
680 buffer[i] = '.';
685 void FollowStreamDialog::showBuffer(QByteArray &buffer, size_t nchars, bool is_from_server, uint32_t packet_num,
686 nstime_t abs_ts, uint32_t *global_pos)
688 char initbuf[256];
689 uint32_t current_pos;
690 static const char hexchars[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
691 bool show_delta = false;
693 if (last_packet_ == 0) {
694 last_from_server_ = is_from_server;
695 } else {
696 if (recent.gui_follow_delta == FOLLOW_DELTA_ALL ||
697 (recent.gui_follow_delta == FOLLOW_DELTA_TURN && last_from_server_ != is_from_server)) {
698 show_delta = true;
702 double delta = 0.0;
703 if (!nstime_is_zero(&abs_ts)) {
704 // packet-tcp.c and possibly other dissectors can return a zero abs_ts when
705 // a fragment is missing.
706 nstime_t delta_ts;
707 nstime_delta(&delta_ts, &abs_ts, &last_ts_);
708 delta = nstime_to_sec(&delta_ts);
709 last_ts_ = abs_ts;
712 switch (recent.gui_follow_show) {
714 case SHOW_EBCDIC:
716 /* If our native arch is ASCII, call: */
717 EBCDIC_to_ASCII((uint8_t*)buffer.data(), (unsigned) nchars);
718 if (show_delta) {
719 ui->teStreamContent->addDeltaTime(delta);
721 if (show_delta || last_from_server_ != is_from_server) {
722 addText("\n", is_from_server, packet_num);
724 sanitize_buffer(buffer, nchars);
725 addText(buffer, is_from_server, packet_num);
726 break;
729 case SHOW_ASCII:
731 /* If our native arch is EBCDIC, call:
732 * ASCII_TO_EBCDIC(buffer, nchars);
734 if (show_delta) {
735 ui->teStreamContent->addDeltaTime(delta);
737 if (show_delta || last_from_server_ != is_from_server) {
738 addText("\n", is_from_server, packet_num);
740 sanitize_buffer(buffer, nchars);
741 addText(buffer, is_from_server, packet_num);
742 break;
745 case SHOW_CODEC:
747 if (show_delta) {
748 ui->teStreamContent->addDeltaTime(delta);
750 if (show_delta || last_from_server_ != is_from_server) {
751 addText("\n", is_from_server, packet_num);
753 // This assumes that multibyte characters don't span packets in the
754 // stream. To handle that case properly (which might occur with fixed
755 // block sizes, e.g. transferring over TFTP, we would need to create
756 // two stateful QTextDecoders, one for each direction, presumably in
757 // on_cbCharset_currentIndexChanged()
758 QTextCodec *codec = QTextCodec::codecForName(ui->cbCharset->currentText().toUtf8());
759 addText(codec->toUnicode(buffer), is_from_server, packet_num);
760 break;
763 case SHOW_HEXDUMP:
764 current_pos = 0;
765 while (current_pos < nchars) {
766 char hexbuf[256];
767 int i;
768 char *cur = hexbuf, *ascii_start;
770 /* is_from_server indentation : put 4 spaces at the
771 * beginning of the string */
772 /* XXX - We might want to prepend each line with "C" or "S" instead. */
773 if (is_from_server && follow_info_.show_stream == BOTH_HOSTS) {
774 memset(cur, ' ', 4);
775 cur += 4;
777 cur += snprintf(cur, 20, "%08X ", *global_pos);
778 /* 49 is space consumed by hex chars */
779 ascii_start = cur + 49 + 2;
780 for (i = 0; i < 16 && current_pos + i < nchars; i++) {
781 *cur++ =
782 hexchars[(buffer.at(current_pos + i) & 0xf0) >> 4];
783 *cur++ =
784 hexchars[buffer.at(current_pos + i) & 0x0f];
785 *cur++ = ' ';
786 if (i == 7)
787 *cur++ = ' ';
789 /* Fill it up if column isn't complete */
790 while (cur < ascii_start)
791 *cur++ = ' ';
793 /* Now dump bytes as text */
794 for (i = 0; i < 16 && current_pos + i < nchars; i++) {
795 *cur++ =
796 (g_ascii_isprint((unsigned char)buffer.at(current_pos + i)) ?
797 buffer.at(current_pos + i) : '.');
798 if (i == 7) {
799 *cur++ = ' ';
802 current_pos += i;
803 (*global_pos) += i;
804 *cur++ = '\n';
805 *cur = 0;
807 addText(hexbuf, is_from_server, packet_num);
809 break;
811 case SHOW_CARRAY:
812 current_pos = 0;
813 snprintf(initbuf, sizeof(initbuf), "char peer%d_%d[] = { /* Packet %u */\n",
814 is_from_server ? 1 : 0,
815 is_from_server ? server_buffer_count_++ : client_buffer_count_++,
816 packet_num);
817 addText(initbuf, is_from_server, packet_num);
819 while (current_pos < nchars) {
820 char hexbuf[256];
821 int i, cur;
823 cur = 0;
824 for (i = 0; i < 8 && current_pos + i < nchars; i++) {
825 /* Prepend entries with "0x" */
826 hexbuf[cur++] = '0';
827 hexbuf[cur++] = 'x';
828 hexbuf[cur++] =
829 hexchars[(buffer.at(current_pos + i) & 0xf0) >> 4];
830 hexbuf[cur++] =
831 hexchars[buffer.at(current_pos + i) & 0x0f];
833 /* Delimit array entries with a comma */
834 if (current_pos + i + 1 < nchars)
835 hexbuf[cur++] = ',';
837 hexbuf[cur++] = ' ';
840 /* Terminate the array if we are at the end */
841 if (current_pos + i == nchars) {
842 hexbuf[cur++] = '}';
843 hexbuf[cur++] = ';';
846 current_pos += i;
847 (*global_pos) += i;
848 hexbuf[cur++] = '\n';
849 hexbuf[cur] = 0;
850 addText(hexbuf, is_from_server, packet_num);
852 break;
854 case SHOW_YAML:
856 QString yaml_text;
858 const int base64_raw_len = 57; // Encodes to 76 bytes, common in RFCs
859 current_pos = 0;
861 if (last_packet_ == 0) {
862 // Header with general info about peers
863 const char *hostname0 = address_to_name(&follow_info_.client_ip);
864 const char *hostname1 = address_to_name(&follow_info_.server_ip);
866 char *port0 = get_follow_port_to_display(follower_)(NULL, follow_info_.client_port);
867 char *port1 = get_follow_port_to_display(follower_)(NULL, follow_info_.server_port);
869 addText("peers:\n", false, 0, false);
871 addText(QString(
872 " - peer: 0\n"
873 " host: %1\n"
874 " port: %2\n")
875 .arg(hostname0, port0), false, 0);
877 addText(QString(
878 " - peer: 1\n"
879 " host: %1\n"
880 " port: %2\n")
881 .arg(hostname1, port1), true, 0);
883 wmem_free(NULL, port0);
884 wmem_free(NULL, port1);
886 addText("packets:\n", false, 0, false);
889 if (packet_num != last_packet_) {
890 yaml_text.append(QStringLiteral(" - packet: %1\n")
891 .arg(packet_num));
892 yaml_text.append(QStringLiteral(" peer: %1\n")
893 .arg(is_from_server ? 1 : 0));
894 yaml_text.append(QStringLiteral(" index: %1\n")
895 .arg(is_from_server ? server_buffer_count_++ : client_buffer_count_++));
896 yaml_text.append(QStringLiteral(" timestamp: %1.%2\n")
897 .arg(abs_ts.secs)
898 .arg(abs_ts.nsecs, 9, 10, QChar('0')));
899 yaml_text.append(QStringLiteral(" data: !!binary |\n"));
901 while (current_pos < nchars) {
902 int len = current_pos + base64_raw_len < nchars ? base64_raw_len : (int) nchars - current_pos;
903 QByteArray base64_data(&buffer.constData()[current_pos], len);
905 /* XXX: GCC 12.1 has a bogus stringop-overread warning using the Qt
906 * conversions from QByteArray to QString at -O2 and higher due to
907 * computing a branch that will never be taken.
909 #if WS_IS_AT_LEAST_GNUC_VERSION(12,1)
910 DIAG_OFF(stringop-overread)
911 #endif
912 yaml_text += " " + base64_data.toBase64() + "\n";
913 #if WS_IS_AT_LEAST_GNUC_VERSION(12,1)
914 DIAG_ON(stringop-overread)
915 #endif
917 current_pos += len;
918 (*global_pos) += len;
920 addText(std::move(yaml_text), is_from_server, packet_num);
921 break;
924 case SHOW_RAW:
926 addText(buffer.toHex() + '\n', is_from_server, packet_num);
927 break;
930 default:
931 /* The other Show types are supported in Show Packet Bytes but
932 * not here in Follow. (XXX: Maybe some could be added?)
934 ws_assert_not_reached();
937 if (packet_num != last_packet_) {
938 last_packet_ = packet_num;
939 if (is_from_server) {
940 server_packet_count_++;
941 } else {
942 client_packet_count_++;
944 if (last_from_server_ != is_from_server) {
945 last_from_server_ = is_from_server;
946 turns_++;
951 bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index, unsigned stream_num, unsigned sub_stream_num)
953 QString follow_filter;
954 const char *hostname0 = NULL, *hostname1 = NULL;
955 char *port0 = NULL, *port1 = NULL;
956 QString server_to_client_string;
957 QString client_to_server_string;
958 QString both_directions_string;
959 bool is_follower = false;
960 int stream_count;
961 follow_stream_count_func stream_count_func = NULL;
963 if (file_closed_)
965 QMessageBox::warning(this, tr("No capture file."), tr("Please make sure you have a capture file opened."));
966 return false;
969 if (!use_stream_index) {
970 if (cap_file_.capFile()->edt == NULL)
972 QMessageBox::warning(this, tr("Error following stream."), tr("Capture file invalid."));
973 return false;
975 is_follower = proto_is_frame_protocol(cap_file_.capFile()->edt->pi.layers, proto_get_protocol_filter_name(get_follow_proto_id(follower_)));
976 if (!is_follower) {
977 QMessageBox::warning(this, tr("Error following stream."), tr("Please make sure you have a %1 packet selected.").arg
978 (proto_get_protocol_short_name(find_protocol_by_id(get_follow_proto_id(follower_)))));
979 return false;
983 /* Create a new filter that matches all packets in the TCP stream,
984 and set the display filter entry accordingly */
985 if (use_stream_index) {
986 follow_filter = gchar_free_to_qstring(get_follow_index_func(follower_)(stream_num, sub_stream_num));
987 } else {
988 follow_filter = gchar_free_to_qstring(get_follow_conv_func(follower_)(cap_file_.capFile()->edt, &cap_file_.capFile()->edt->pi, &stream_num, &sub_stream_num));
990 if (follow_filter.isEmpty()) {
991 // XXX: This error probably has to do with tunneling (#18231), where
992 // the addresses or ports changed after the TCP or UDP layer.
993 // (The appropriate layer must be present, or else the GUI
994 // doesn't allow the option to be selected.)
995 QMessageBox::warning(this,
996 tr("Error creating filter for this stream."),
997 tr("%1 stream not found on the selected packet.").arg(proto_get_protocol_short_name(find_protocol_by_id(get_follow_proto_id(follower_)))));
998 return false;
1001 previous_filter_ = previous_filter;
1002 /* append the negation */
1003 if (!previous_filter.isEmpty()) {
1004 filter_out_filter_ = QStringLiteral("%1 and !(%2)")
1005 .arg(previous_filter, follow_filter);
1007 else
1009 filter_out_filter_ = QStringLiteral("!(%1)").arg(follow_filter);
1012 follow_info_.substream_id = sub_stream_num;
1014 /* data will be passed via tap callback*/
1015 if (!registerTapListener(get_follow_tap_string(follower_), &follow_info_,
1016 follow_filter.toUtf8().constData(),
1017 0, FollowStreamDialog::resetStream,
1018 get_follow_tap_handler(follower_), NULL)) {
1019 return false;
1022 stream_count_func = get_follow_stream_count_func(follower_);
1024 if (stream_count_func == NULL) {
1025 removeStreamControls();
1026 } else {
1027 stream_count = stream_count_func();
1028 ui->streamNumberSpinBox->blockSignals(true);
1029 ui->streamNumberSpinBox->setMaximum(stream_count-1);
1030 ui->streamNumberSpinBox->setValue(stream_num);
1031 ui->streamNumberSpinBox->blockSignals(false);
1032 ui->streamNumberSpinBox->setToolTip(tr("%Ln total stream(s).", "", stream_count));
1033 ui->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());
1036 follow_sub_stream_id_func sub_stream_func;
1037 sub_stream_func = get_follow_sub_stream_id_func(follower_);
1038 if (sub_stream_func != NULL) {
1039 unsigned substream_max_id = 0;
1040 sub_stream_func(static_cast<unsigned>(stream_num), INT32_MAX, true, &substream_max_id);
1041 stream_count = static_cast<int>(substream_max_id);
1042 ui->subStreamNumberSpinBox->blockSignals(true);
1043 ui->subStreamNumberSpinBox->setEnabled(true);
1044 ui->subStreamNumberSpinBox->setMaximum(stream_count);
1045 ui->subStreamNumberSpinBox->setValue(sub_stream_num);
1046 ui->subStreamNumberSpinBox->blockSignals(false);
1047 ui->subStreamNumberSpinBox->setToolTip(tr("Max sub stream ID for the selected stream: %Ln", "", stream_count));
1048 ui->subStreamNumberSpinBox->setToolTip(ui->subStreamNumberSpinBox->toolTip());
1049 ui->subStreamNumberSpinBox->setVisible(true);
1050 ui->subStreamNumberLabel->setVisible(true);
1051 } else {
1052 /* disable substream spin box for protocols without substreams */
1053 ui->subStreamNumberSpinBox->blockSignals(true);
1054 ui->subStreamNumberSpinBox->setEnabled(false);
1055 ui->subStreamNumberSpinBox->setValue(0);
1056 ui->subStreamNumberSpinBox->setKeyboardTracking(false);
1057 ui->subStreamNumberSpinBox->blockSignals(false);
1058 ui->subStreamNumberSpinBox->setVisible(false);
1059 ui->subStreamNumberLabel->setVisible(false);
1062 beginRetapPackets();
1063 updateWidgets(true);
1065 /* Run the display filter so it goes in effect - even if it's the
1066 same as the previous display filter. */
1067 /* XXX: This forces a cf_filter_packets() - but if a rescan (or something else
1068 * that sets cf->read_lock) is in progress, this will queue the filter
1069 * and return immediately. It will also cause a rescan in progress to
1070 * stop and restart with the new filter. That also applies to this rescan;
1071 * changing the main display filter (from the main window, or from, e.g.
1072 * another FollowStreamDialog) will cause this to restart and reset the
1073 * tap.
1075 * Other tapping dialogs call cf_retap_packets (which retaps but doesn't
1076 * set the main display filter, freeze the packet list, etc.), which
1077 * has somewhat different behavior when another dialog tries to retap,
1078 * but also results in the taps being reset mid tap.
1080 * Either way, we should be event driven and listening for CaptureEvents
1081 * instead of drawing after this returns. (Or like other taps, draw
1082 * periodically in a callback, provided that can be done without causing
1083 * issues with changing the Decode As type.)
1085 emit updateFilter(follow_filter, true);
1087 removeTapListeners();
1089 bool is_stratoshark = strcmp(get_configuration_namespace(), "Stratoshark") == 0;
1091 if (is_stratoshark) {
1092 server_to_client_string =
1093 tr("Read activity(%6)")
1094 .arg(gchar_free_to_qstring(format_size(
1095 follow_info_.bytes_written[0],
1096 FORMAT_SIZE_UNIT_BYTES, FORMAT_SIZE_PREFIX_SI)));
1098 client_to_server_string =
1099 tr("Write activity(%6)")
1100 .arg(gchar_free_to_qstring(format_size(
1101 follow_info_.bytes_written[1],
1102 FORMAT_SIZE_UNIT_BYTES, FORMAT_SIZE_PREFIX_SI)));
1104 both_directions_string = tr("Entire I/O activity (%1)")
1105 .arg(gchar_free_to_qstring(format_size(
1106 follow_info_.bytes_written[0] + follow_info_.bytes_written[1],
1107 FORMAT_SIZE_UNIT_BYTES, FORMAT_SIZE_PREFIX_SI)));
1108 } else {
1109 hostname0 = address_to_name(&follow_info_.client_ip);
1110 hostname1 = address_to_name(&follow_info_.server_ip);
1112 port0 = get_follow_port_to_display(follower_)(NULL, follow_info_.client_port);
1113 port1 = get_follow_port_to_display(follower_)(NULL, follow_info_.server_port);
1115 server_to_client_string =
1116 QStringLiteral("%1:%2 %3 %4:%5 (%6)")
1117 .arg(hostname0, port0)
1118 .arg(UTF8_RIGHTWARDS_ARROW)
1119 .arg(hostname1, port1)
1120 .arg(gchar_free_to_qstring(format_size(
1121 follow_info_.bytes_written[0],
1122 FORMAT_SIZE_UNIT_BYTES, FORMAT_SIZE_PREFIX_SI)));
1124 client_to_server_string =
1125 QStringLiteral("%1:%2 %3 %4:%5 (%6)")
1126 .arg(hostname1, port1)
1127 .arg(UTF8_RIGHTWARDS_ARROW)
1128 .arg(hostname0, port0)
1129 .arg(gchar_free_to_qstring(format_size(
1130 follow_info_.bytes_written[1],
1131 FORMAT_SIZE_UNIT_BYTES, FORMAT_SIZE_PREFIX_SI)));
1133 wmem_free(NULL, port0);
1134 wmem_free(NULL, port1);
1136 both_directions_string = tr("Entire conversation (%1)")
1137 .arg(gchar_free_to_qstring(format_size(
1138 follow_info_.bytes_written[0] + follow_info_.bytes_written[1],
1139 FORMAT_SIZE_UNIT_BYTES, FORMAT_SIZE_PREFIX_SI)));
1142 setWindowSubtitle(tr("Follow %1 Stream (%2)").arg(proto_get_protocol_short_name(find_protocol_by_id(get_follow_proto_id(follower_))),
1143 follow_filter));
1145 ui->cbDirections->blockSignals(true);
1146 ui->cbDirections->clear();
1147 ui->cbDirections->addItem(both_directions_string);
1148 ui->cbDirections->addItem(client_to_server_string);
1149 ui->cbDirections->addItem(server_to_client_string);
1150 ui->cbDirections->blockSignals(false);
1152 followStream();
1153 fillHintLabel();
1155 updateWidgets(false);
1156 endRetapPackets();
1158 if (prefs.restore_filter_after_following_stream) {
1159 emit updateFilter(previous_filter_, true);
1162 return true;
1165 void FollowStreamDialog::captureFileClosed()
1167 QString tooltip = tr("File closed.");
1168 ui->streamNumberSpinBox->setToolTip(tooltip);
1169 ui->streamNumberLabel->setToolTip(tooltip);
1170 WiresharkDialog::captureFileClosed();
1173 void FollowStreamDialog::readFollowStream()
1175 uint32_t global_client_pos = 0, global_server_pos = 0;
1176 uint32_t *global_pos;
1177 bool skip;
1178 GList* cur;
1179 follow_record_t *follow_record;
1180 QElapsedTimer elapsed_timer;
1181 QByteArray buffer;
1183 elapsed_timer.start();
1185 loop_break_mutex.lock();
1186 isReadRunning = true;
1187 loop_break_mutex.unlock();
1189 for (cur = g_list_last(follow_info_.payload); cur; cur = g_list_previous(cur)) {
1190 if (dialogClosed() || !isReadRunning) break;
1192 follow_record = (follow_record_t *)cur->data;
1193 skip = false;
1194 if (!follow_record->is_server) {
1195 global_pos = &global_client_pos;
1196 if (follow_info_.show_stream == FROM_SERVER) {
1197 skip = true;
1199 } else {
1200 global_pos = &global_server_pos;
1201 if (follow_info_.show_stream == FROM_CLIENT) {
1202 skip = true;
1206 if (!skip) {
1207 // This will only detach / deep copy if the buffer data is
1208 // modified. Try to avoid doing that as much as possible
1209 // (and avoid new memory allocations that have to be freed).
1210 buffer.setRawData((char*)follow_record->data->data, follow_record->data->len);
1211 showBuffer(
1212 buffer,
1213 follow_record->data->len,
1214 follow_record->is_server,
1215 follow_record->packet_num,
1216 follow_record->abs_ts,
1217 global_pos);
1218 if (elapsed_timer.elapsed() > info_update_freq_) {
1219 fillHintLabel(ui->teStreamContent->currentPacket());
1220 mainApp->processEvents();
1221 elapsed_timer.start();
1226 loop_break_mutex.lock();
1227 isReadRunning = false;
1228 loop_break_mutex.unlock();