1 /* follow_stream_dialog.cpp
2 *
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include "follow_stream_dialog.h"
11 #include <ui_follow_stream_dialog.h>
12
13 #include "main_window.h"
14 #include "wireshark_application.h"
15
16 #include "frame_tvbuff.h"
17 #include "epan/follow.h"
18 #include "epan/dissectors/packet-tcp.h"
19 #include "epan/dissectors/packet-udp.h"
20 #include "epan/dissectors/packet-dccp.h"
21 #include "epan/dissectors/packet-http2.h"
22 #include "epan/dissectors/packet-quic.h"
23 #include "epan/prefs.h"
24 #include "epan/addr_resolv.h"
25 #include "epan/charsets.h"
26 #include "epan/epan_dissect.h"
27 #include "epan/tap.h"
28
29 #include "ui/alert_box.h"
30 #include "ui/simple_dialog.h"
31 #include <wsutil/utf8_entities.h>
32 #include <wsutil/ws_assert.h>
33
34 #include "wsutil/file_util.h"
35 #include "wsutil/str_util.h"
36 #include "ui/version_info.h"
37
38 #include "ws_symbol_export.h"
39
40 #include <ui/qt/utils/color_utils.h>
41 #include <ui/qt/utils/qt_ui_utils.h>
42
43 #include "progress_frame.h"
44
45 #include "ui/qt/widgets/wireshark_file_dialog.h"
46
47 #include <QElapsedTimer>
48 #include <QKeyEvent>
49 #include <QMessageBox>
50 #include <QPrintDialog>
51 #include <QPrinter>
52 #include <QScrollBar>
53 #include <QTextCodec>
54
55 // To do:
56 // - Show text while tapping.
57 // - Instead of calling QMessageBox, display the error message in the text
58 // box and disable the appropriate controls.
59 // - Add a progress bar and connect captureCaptureUpdateContinue to it
60 // - User's Guide documents the "Raw" type as "same as ASCII, but saving binary
61 // data". However it currently displays hex-encoded data.
62
63 // Matches SplashOverlay.
64 static int info_update_freq_ = 100;
65
FollowStreamDialog(QWidget & parent,CaptureFile & cf,follow_type_t type)66 FollowStreamDialog::FollowStreamDialog(QWidget &parent, CaptureFile &cf, follow_type_t type) :
67 WiresharkDialog(parent, cf),
68 ui(new Ui::FollowStreamDialog),
69 b_find_(NULL),
70 follow_type_(type),
71 follower_(NULL),
72 show_type_(SHOW_ASCII),
73 truncated_(false),
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)
84 {
85 ui->setupUi(this);
86 loadGeometry(parent.width() * 2 / 3, parent.height());
87
88 switch(type)
89 {
90 case FOLLOW_TCP:
91 follower_ = get_follow_by_name("TCP");
92 break;
93 case FOLLOW_TLS:
94 follower_ = get_follow_by_name("TLS");
95 break;
96 case FOLLOW_UDP:
97 follower_ = get_follow_by_name("UDP");
98 break;
99 case FOLLOW_DCCP:
100 follower_ = get_follow_by_name("DCCP");
101 break;
102 case FOLLOW_HTTP:
103 follower_ = get_follow_by_name("HTTP");
104 break;
105 case FOLLOW_HTTP2:
106 follower_ = get_follow_by_name("HTTP2");
107 break;
108 case FOLLOW_QUIC:
109 follower_ = get_follow_by_name("QUIC");
110 break;
111 case FOLLOW_SIP:
112 follower_ = get_follow_by_name("SIP");
113 break;
114 default :
115 ws_assert_not_reached();
116 }
117
118 memset(&follow_info_, 0, sizeof(follow_info_));
119 follow_info_.show_stream = BOTH_HOSTS;
120 follow_info_.substream_id = SUBSTREAM_UNUSED;
121
122 ui->teStreamContent->installEventFilter(this);
123
124 connect(ui->leFind, SIGNAL(useRegexFind(bool)), this, SLOT(useRegexFind(bool)));
125
126 QComboBox *cbcs = ui->cbCharset;
127 cbcs->blockSignals(true);
128 cbcs->addItem(tr("ASCII"), SHOW_ASCII);
129 cbcs->addItem(tr("C Arrays"), SHOW_CARRAY);
130 cbcs->addItem(tr("EBCDIC"), SHOW_EBCDIC);
131 cbcs->addItem(tr("Hex Dump"), SHOW_HEXDUMP);
132 cbcs->addItem(tr("Raw"), SHOW_RAW);
133 // UTF-8 is guaranteed to exist as a QTextCodec
134 cbcs->addItem(tr("UTF-8"), SHOW_CODEC);
135 cbcs->addItem(tr("YAML"), SHOW_YAML);
136 cbcs->blockSignals(false);
137
138 b_filter_out_ = ui->buttonBox->addButton(tr("Filter Out This Stream"), QDialogButtonBox::ActionRole);
139 connect(b_filter_out_, SIGNAL(clicked()), this, SLOT(filterOut()));
140
141 b_print_ = ui->buttonBox->addButton(tr("Print"), QDialogButtonBox::ActionRole);
142 connect(b_print_, SIGNAL(clicked()), this, SLOT(printStream()));
143
144 b_save_ = ui->buttonBox->addButton(tr("Save as…"), QDialogButtonBox::ActionRole);
145 connect(b_save_, SIGNAL(clicked()), this, SLOT(saveAs()));
146
147 b_back_ = ui->buttonBox->addButton(tr("Back"), QDialogButtonBox::ActionRole);
148 connect(b_back_, SIGNAL(clicked()), this, SLOT(backButton()));
149
150 ProgressFrame::addToButtonBox(ui->buttonBox, &parent);
151
152 connect(ui->buttonBox, SIGNAL(helpRequested()), this, SLOT(helpButton()));
153 connect(ui->teStreamContent, SIGNAL(mouseMovedToTextCursorPosition(int)),
154 this, SLOT(fillHintLabel(int)));
155 connect(ui->teStreamContent, SIGNAL(mouseClickedOnTextCursorPosition(int)),
156 this, SLOT(goToPacketForTextPos(int)));
157
158 fillHintLabel(-1);
159 }
160
~FollowStreamDialog()161 FollowStreamDialog::~FollowStreamDialog()
162 {
163 delete ui;
164 resetStream(); // Frees payload
165 }
166
addCodecs(const QMap<QString,QTextCodec * > & codecMap)167 void FollowStreamDialog::addCodecs(const QMap<QString, QTextCodec *> &codecMap)
168 {
169 // Make the combobox respect max visible items?
170 //ui->cbCharset->setStyleSheet("QComboBox { combobox-popup: 0;}");
171 ui->cbCharset->insertSeparator(ui->cbCharset->count());
172 #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
173 for (const auto &codec : qAsConst(codecMap)) {
174 #else
175 foreach (const QTextCodec *codec, codecMap) {
176 #endif
177 // This is already in the menu and handled separately
178 if (codec->name() != "US-ASCII" && codec->name() != "UTF-8")
179 ui->cbCharset->addItem(tr(codec->name()), SHOW_CODEC);
180 }
181 }
182
183 void FollowStreamDialog::printStream()
184 {
185 #ifndef QT_NO_PRINTER
186 QPrinter printer(QPrinter::HighResolution);
187 QPrintDialog dialog(&printer, this);
188 if (dialog.exec() == QDialog::Accepted)
189 ui->teStreamContent->print(&printer);
190 #endif
191 }
192
193 void FollowStreamDialog::fillHintLabel(int text_pos)
194 {
195 QString hint;
196 int pkt = -1;
197
198 if (text_pos >= 0) {
199 QMap<int, guint32>::iterator it = text_pos_to_packet_.upperBound(text_pos);
200 if (it != text_pos_to_packet_.end()) {
201 pkt = it.value();
202 }
203 }
204
205 if (pkt > 0) {
206 hint = QString(tr("Packet %1. ")).arg(pkt);
207 }
208
209 hint += tr("%Ln <span style=\"color: %1; background-color:%2\">client</span> pkt(s), ", "", client_packet_count_)
210 .arg(ColorUtils::fromColorT(prefs.st_client_fg).name())
211 .arg(ColorUtils::fromColorT(prefs.st_client_bg).name())
212 + tr("%Ln <span style=\"color: %1; background-color:%2\">server</span> pkt(s), ", "", server_packet_count_)
213 .arg(ColorUtils::fromColorT(prefs.st_server_fg).name())
214 .arg(ColorUtils::fromColorT(prefs.st_server_bg).name())
215 + tr("%Ln turn(s).", "", turns_);
216
217 if (pkt > 0) {
218 hint.append(QString(tr(" Click to select.")));
219 }
220
221 hint.prepend("<small><i>");
222 hint.append("</i></small>");
223 ui->hintLabel->setText(hint);
224 }
225
226 void FollowStreamDialog::goToPacketForTextPos(int text_pos)
227 {
228 int pkt = -1;
229 if (file_closed_) {
230 return;
231 }
232
233 if (text_pos >= 0) {
234 QMap<int, guint32>::iterator it = text_pos_to_packet_.upperBound(text_pos);
235 if (it != text_pos_to_packet_.end()) {
236 pkt = it.value();
237 }
238 }
239
240 if (pkt > 0) {
241 emit goToPacket(pkt);
242 }
243 }
244
245 void FollowStreamDialog::updateWidgets(bool follow_in_progress)
246 {
247 bool enable = !follow_in_progress;
248 if (file_closed_) {
249 ui->teStreamContent->setEnabled(true);
250 enable = false;
251 }
252
253 ui->cbDirections->setEnabled(enable);
254 ui->cbCharset->setEnabled(enable);
255 ui->streamNumberSpinBox->setEnabled(enable);
256 if (follow_type_ == FOLLOW_HTTP2 || follow_type_ == FOLLOW_QUIC) {
257 ui->subStreamNumberSpinBox->setEnabled(enable);
258 }
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);
264
265 WiresharkDialog::updateWidgets();
266 }
267
268 void FollowStreamDialog::useRegexFind(bool use_regex)
269 {
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:"));
275 }
276
277 void FollowStreamDialog::findText(bool go_back)
278 {
279 if (ui->leFind->text().isEmpty()) return;
280
281 bool found;
282 if (use_regex_find_) {
283 QRegExp regex(ui->leFind->text());
284 found = ui->teStreamContent->find(regex);
285 } else {
286 found = ui->teStreamContent->find(ui->leFind->text());
287 }
288
289 if (found) {
290 ui->teStreamContent->setFocus();
291 } else if (go_back) {
292 ui->teStreamContent->moveCursor(QTextCursor::Start);
293 findText(false);
294 }
295 }
296
297 void FollowStreamDialog::saveAs()
298 {
299 QString file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Stream Content As…")));
300 if (file_name.isEmpty()) {
301 return;
302 }
303
304 QFile file(file_name);
305 if (!file.open(QIODevice::WriteOnly)) {
306 open_failure_alert_box(file_name.toUtf8().constData(), errno, TRUE);
307 return;
308 }
309
310 // Unconditionally save data as UTF-8 (even if data is decoded otherwise).
311 QByteArray bytes = ui->teStreamContent->toPlainText().toUtf8();
312 if (show_type_ == SHOW_RAW) {
313 // The "Raw" format is currently displayed as hex data and needs to be
314 // converted to binary data.
315 bytes = QByteArray::fromHex(bytes);
316 }
317
318 QDataStream out(&file);
319 out.writeRawData(bytes.constData(), bytes.size());
320 }
321
322 void FollowStreamDialog::helpButton()
323 {
324 wsApp->helpTopicAction(HELP_FOLLOW_STREAM_DIALOG);
325 }
326
327 void FollowStreamDialog::backButton()
328 {
329 if (terminating_)
330 return;
331
332 output_filter_ = previous_filter_;
333
334 close();
335 }
336
337 void FollowStreamDialog::filterOut()
338 {
339 if (terminating_)
340 return;
341
342 output_filter_ = filter_out_filter_;
343
344 close();
345 }
346
347 void FollowStreamDialog::close()
348 {
349 terminating_ = true;
350
351 // Update filter - Use:
352 // previous_filter if 'Close' (passed in follow() method)
353 // filter_out_filter_ if 'Filter Out This Stream' (built by appending !current_stream to previous_filter)
354 // leave filter alone if window closed. (current stream)
355 emit updateFilter(output_filter_, TRUE);
356
357 WiresharkDialog::close();
358 }
359
360 void FollowStreamDialog::on_cbDirections_currentIndexChanged(int idx)
361 {
362 switch(idx)
363 {
364 case 0 :
365 follow_info_.show_stream = BOTH_HOSTS;
366 break;
367 case 1 :
368 follow_info_.show_stream = FROM_SERVER;
369 break;
370 case 2 :
371 follow_info_.show_stream = FROM_CLIENT;
372 break;
373 default:
374 return;
375 }
376
377 readStream();
378 }
379
380 void FollowStreamDialog::on_cbCharset_currentIndexChanged(int idx)
381 {
382 if (idx < 0) return;
383 show_type_ = static_cast<show_type_t>(ui->cbCharset->itemData(idx).toInt());
384 readStream();
385 }
386
387 void FollowStreamDialog::on_bFind_clicked()
388 {
389 findText();
390 }
391
392 void FollowStreamDialog::on_leFind_returnPressed()
393 {
394 findText();
395 }
396
397 void FollowStreamDialog::on_streamNumberSpinBox_valueChanged(int stream_num)
398 {
399 if (file_closed_) return;
400
401 int sub_stream_num = 0;
402 ui->subStreamNumberSpinBox->blockSignals(true);
403 sub_stream_num = ui->subStreamNumberSpinBox->value();
404 ui->subStreamNumberSpinBox->blockSignals(false);
405
406 gboolean ok;
407 if (ui->subStreamNumberSpinBox->isVisible()) {
408 /* We need to find a suitable sub stream for the new stream */
409 guint sub_stream_num_new = static_cast<guint>(sub_stream_num);
410 if (sub_stream_num < 0) {
411 // Stream ID 0 should always exist as it is used for control messages.
412 sub_stream_num_new = 0;
413 ok = TRUE;
414 } else if (follow_type_ == FOLLOW_HTTP2) {
415 ok = http2_get_stream_id_ge(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
416 if (!ok) {
417 ok = http2_get_stream_id_le(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
418 }
419 } else if (follow_type_ == FOLLOW_QUIC) {
420 ok = quic_get_stream_id_ge(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
421 if (!ok) {
422 ok = quic_get_stream_id_le(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
423 }
424 } else {
425 // Should not happen, this field is only visible for suitable protocols.
426 return;
427 }
428 sub_stream_num = static_cast<gint>(sub_stream_num_new);
429 } else {
430 ok = true;
431 }
432
433 if (stream_num >= 0 && ok) {
434 follow(previous_filter_, true, stream_num, sub_stream_num);
435 previous_sub_stream_num_ = sub_stream_num;
436 }
437 }
438
439
440 void FollowStreamDialog::on_subStreamNumberSpinBox_valueChanged(int sub_stream_num)
441 {
442 if (file_closed_) return;
443
444 int stream_num = 0;
445 ui->streamNumberSpinBox->blockSignals(true);
446 stream_num = ui->streamNumberSpinBox->value();
447 ui->streamNumberSpinBox->blockSignals(false);
448
449 guint sub_stream_num_new = static_cast<guint>(sub_stream_num);
450 gboolean ok;
451 /* previous_sub_stream_num_ is a hack to track which buttons was pressed without event handling */
452 if (sub_stream_num < 0) {
453 // Stream ID 0 should always exist as it is used for control messages.
454 sub_stream_num_new = 0;
455 ok = TRUE;
456 } else if (follow_type_ == FOLLOW_HTTP2) {
457 if (previous_sub_stream_num_ < sub_stream_num) {
458 ok = http2_get_stream_id_ge(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
459 } else {
460 ok = http2_get_stream_id_le(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
461 }
462 } else if (follow_type_ == FOLLOW_QUIC) {
463 if (previous_sub_stream_num_ < sub_stream_num) {
464 ok = quic_get_stream_id_ge(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
465 } else {
466 ok = quic_get_stream_id_le(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
467 }
468 } else {
469 // Should not happen, this field is only visible for suitable protocols.
470 return;
471 }
472 sub_stream_num = static_cast<gint>(sub_stream_num_new);
473
474 if (ok) {
475 follow(previous_filter_, true, stream_num, sub_stream_num);
476 previous_sub_stream_num_ = sub_stream_num;
477 }
478 }
479
480 void FollowStreamDialog::on_buttonBox_rejected()
481 {
482 // Ignore the close button if FollowStreamDialog::close() is running.
483 if (terminating_)
484 return;
485
486 WiresharkDialog::reject();
487 }
488
489 void FollowStreamDialog::removeStreamControls()
490 {
491 ui->horizontalLayout->removeItem(ui->streamNumberSpacer);
492 ui->streamNumberLabel->setVisible(false);
493 ui->streamNumberSpinBox->setVisible(false);
494 ui->subStreamNumberLabel->setVisible(false);
495 ui->subStreamNumberSpinBox->setVisible(false);
496 }
497
498 void FollowStreamDialog::resetStream()
499 {
500 GList *cur;
501 follow_record_t *follow_record;
502
503 filter_out_filter_.clear();
504 text_pos_to_packet_.clear();
505 if (!data_out_filename_.isEmpty()) {
506 ws_unlink(data_out_filename_.toUtf8().constData());
507 }
508 for (cur = follow_info_.payload; cur; cur = gxx_list_next(cur)) {
509 follow_record = gxx_list_data(follow_record_t *, cur);
510 if (follow_record->data) {
511 g_byte_array_free(follow_record->data, TRUE);
512 }
513 g_free(follow_record);
514 }
515 g_list_free(follow_info_.payload);
516
517 //Only TCP stream uses fragments
518 if (follow_type_ == FOLLOW_TCP) {
519 for (cur = follow_info_.fragments[0]; cur; cur = gxx_list_next(cur)) {
520 follow_record = gxx_list_data(follow_record_t *, cur);
521 if (follow_record->data) {
522 g_byte_array_free(follow_record->data, TRUE);
523 }
524 g_free(follow_record);
525 }
526 follow_info_.fragments[0] = Q_NULLPTR;
527 for (cur = follow_info_.fragments[1]; cur; cur = gxx_list_next(cur)) {
528 follow_record = gxx_list_data(follow_record_t *, cur);
529 if (follow_record->data) {
530 g_byte_array_free(follow_record->data, TRUE);
531 }
532 g_free(follow_record);
533 }
534 follow_info_.fragments[1] = Q_NULLPTR;
535 }
536
537 free_address(&follow_info_.client_ip);
538 free_address(&follow_info_.server_ip);
539 follow_info_.payload = Q_NULLPTR;
540 follow_info_.client_port = 0;
541 }
542
543 frs_return_t
544 FollowStreamDialog::readStream()
545 {
546
547 ui->teStreamContent->clear();
548 text_pos_to_packet_.clear();
549
550 truncated_ = false;
551 frs_return_t ret;
552
553 client_buffer_count_ = 0;
554 server_buffer_count_ = 0;
555 client_packet_count_ = 0;
556 server_packet_count_ = 0;
557 last_packet_ = 0;
558 turns_ = 0;
559
560 switch(follow_type_) {
561
562 case FOLLOW_TCP :
563 case FOLLOW_UDP :
564 case FOLLOW_DCCP :
565 case FOLLOW_HTTP :
566 case FOLLOW_HTTP2:
567 case FOLLOW_QUIC:
568 case FOLLOW_TLS :
569 case FOLLOW_SIP :
570 ret = readFollowStream();
571 break;
572
573 default :
574 ws_assert_not_reached();
575 ret = (frs_return_t)0;
576 break;
577 }
578
579 ui->teStreamContent->moveCursor(QTextCursor::Start);
580
581 return ret;
582 }
583
584 void
585 FollowStreamDialog::followStream()
586 {
587 readStream();
588 }
589
590 const int FollowStreamDialog::max_document_length_ = 500 * 1000 * 1000; // Just a guess
591 void FollowStreamDialog::addText(QString text, gboolean is_from_server, guint32 packet_num, gboolean colorize)
592 {
593 if (truncated_) {
594 return;
595 }
596
597 int char_count = ui->teStreamContent->document()->characterCount();
598 if (char_count + text.length() > max_document_length_) {
599 text.truncate(max_document_length_ - char_count);
600 truncated_ = true;
601 }
602
603 setUpdatesEnabled(false);
604 int cur_pos = ui->teStreamContent->verticalScrollBar()->value();
605 ui->teStreamContent->moveCursor(QTextCursor::End);
606
607 QTextCharFormat tcf = ui->teStreamContent->currentCharFormat();
608 if (!colorize) {
609 tcf.setBackground(palette().window().color());
610 tcf.setForeground(palette().windowText().color());
611 } else if (is_from_server) {
612 tcf.setForeground(ColorUtils::fromColorT(prefs.st_server_fg));
613 tcf.setBackground(ColorUtils::fromColorT(prefs.st_server_bg));
614 } else {
615 tcf.setForeground(ColorUtils::fromColorT(prefs.st_client_fg));
616 tcf.setBackground(ColorUtils::fromColorT(prefs.st_client_bg));
617 }
618 ui->teStreamContent->setCurrentCharFormat(tcf);
619
620 ui->teStreamContent->insertPlainText(text);
621 text_pos_to_packet_[ui->teStreamContent->textCursor().anchor()] = packet_num;
622
623 if (truncated_) {
624 tcf = ui->teStreamContent->currentCharFormat();
625 tcf.setBackground(palette().window().color());
626 tcf.setForeground(palette().windowText().color());
627 ui->teStreamContent->insertPlainText("\n" + tr("[Stream output truncated]"));
628 ui->teStreamContent->moveCursor(QTextCursor::End);
629 } else {
630 ui->teStreamContent->verticalScrollBar()->setValue(cur_pos);
631 }
632 setUpdatesEnabled(true);
633 }
634
635 // The following keyboard shortcuts should work (although
636 // they may not work consistently depending on focus):
637 // / (slash), Ctrl-F - Focus and highlight the search box
638 // Ctrl-G, Ctrl-N, F3 - Find next
639 // Should we make it so that typing any text starts searching?
640 bool FollowStreamDialog::eventFilter(QObject *, QEvent *event)
641 {
642 if (ui->teStreamContent->hasFocus() && event->type() == QEvent::KeyPress) {
643 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
644 if (keyEvent->matches(QKeySequence::SelectAll) || keyEvent->matches(QKeySequence::Copy)
645 || keyEvent->text().isEmpty()) {
646 return false;
647 }
648 ui->leFind->setFocus();
649 if (keyEvent->matches(QKeySequence::Find)) {
650 return true;
651 } else if (keyEvent->matches(QKeySequence::FindNext)) {
652 findText();
653 return true;
654 }
655 }
656
657 return false;
658 }
659
660 void FollowStreamDialog::keyPressEvent(QKeyEvent *event)
661 {
662 if (ui->leFind->hasFocus()) {
663 if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
664 findText();
665 return;
666 }
667 } else {
668 if (event->key() == Qt::Key_Slash || event->matches(QKeySequence::Find)) {
669 ui->leFind->setFocus();
670 ui->leFind->selectAll();
671 }
672 return;
673 }
674
675 if (event->key() == Qt::Key_F3 || (event->key() == Qt::Key_N && event->modifiers() & Qt::ControlModifier)) {
676 findText();
677 return;
678 }
679
680 QDialog::keyPressEvent(event);
681 }
682
683 static inline void sanitize_buffer(char *buffer, size_t nchars) {
684 for (size_t i = 0; i < nchars; i++) {
685 if (buffer[i] == '\n' || buffer[i] == '\r' || buffer[i] == '\t')
686 continue;
687 if (! g_ascii_isprint((guchar)buffer[i])) {
688 buffer[i] = '.';
689 }
690 }
691 }
692
693 frs_return_t
694 FollowStreamDialog::showBuffer(char *buffer, size_t nchars, gboolean is_from_server, guint32 packet_num,
695 nstime_t abs_ts, guint32 *global_pos)
696 {
697 gchar initbuf[256];
698 guint32 current_pos;
699 static const gchar hexchars[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
700
701 switch (show_type_) {
702
703 case SHOW_EBCDIC:
704 {
705 /* If our native arch is ASCII, call: */
706 EBCDIC_to_ASCII((guint8*)buffer, (guint) nchars);
707 sanitize_buffer(buffer, nchars);
708 QByteArray ba = QByteArray(buffer, (int)nchars);
709 addText(ba, is_from_server, packet_num);
710 break;
711 }
712
713 case SHOW_ASCII:
714 {
715 /* If our native arch is EBCDIC, call:
716 * ASCII_TO_EBCDIC(buffer, nchars);
717 */
718 sanitize_buffer(buffer, nchars);
719 QByteArray ba = QByteArray(buffer, (int)nchars);
720 addText(ba, is_from_server, packet_num);
721 break;
722 }
723
724 case SHOW_CODEC:
725 {
726 // This assumes that multibyte characters don't span packets in the
727 // stream. To handle that case properly (which might occur with fixed
728 // block sizes, e.g. transferring over TFTP, we would need to create
729 // two stateful QTextDecoders, one for each direction, presumably in
730 // on_cbCharset_currentIndexChanged()
731 QTextCodec *codec = QTextCodec::codecForName(ui->cbCharset->currentText().toUtf8());
732 QByteArray ba = QByteArray(buffer, (int)nchars);
733 QString decoded = codec->toUnicode(ba);
734 addText(decoded, is_from_server, packet_num);
735 break;
736 }
737
738 case SHOW_HEXDUMP:
739 current_pos = 0;
740 while (current_pos < nchars) {
741 gchar hexbuf[256];
742 int i;
743 gchar *cur = hexbuf, *ascii_start;
744
745 /* is_from_server indentation : put 4 spaces at the
746 * beginning of the string */
747 /* XXX - We might want to prepend each line with "C" or "S" instead. */
748 if (is_from_server && follow_info_.show_stream == BOTH_HOSTS) {
749 memset(cur, ' ', 4);
750 cur += 4;
751 }
752 cur += g_snprintf(cur, 20, "%08X ", *global_pos);
753 /* 49 is space consumed by hex chars */
754 ascii_start = cur + 49 + 2;
755 for (i = 0; i < 16 && current_pos + i < nchars; i++) {
756 *cur++ =
757 hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
758 *cur++ =
759 hexchars[buffer[current_pos + i] & 0x0f];
760 *cur++ = ' ';
761 if (i == 7)
762 *cur++ = ' ';
763 }
764 /* Fill it up if column isn't complete */
765 while (cur < ascii_start)
766 *cur++ = ' ';
767
768 /* Now dump bytes as text */
769 for (i = 0; i < 16 && current_pos + i < nchars; i++) {
770 *cur++ =
771 (g_ascii_isprint((guchar)buffer[current_pos + i]) ?
772 buffer[current_pos + i] : '.');
773 if (i == 7) {
774 *cur++ = ' ';
775 }
776 }
777 current_pos += i;
778 (*global_pos) += i;
779 *cur++ = '\n';
780 *cur = 0;
781
782 addText(hexbuf, is_from_server, packet_num);
783 }
784 break;
785
786 case SHOW_CARRAY:
787 current_pos = 0;
788 g_snprintf(initbuf, sizeof(initbuf), "char peer%d_%d[] = { /* Packet %u */\n",
789 is_from_server ? 1 : 0,
790 is_from_server ? server_buffer_count_++ : client_buffer_count_++,
791 packet_num);
792 addText(initbuf, is_from_server, packet_num);
793
794 while (current_pos < nchars) {
795 gchar hexbuf[256];
796 int i, cur;
797
798 cur = 0;
799 for (i = 0; i < 8 && current_pos + i < nchars; i++) {
800 /* Prepend entries with "0x" */
801 hexbuf[cur++] = '0';
802 hexbuf[cur++] = 'x';
803 hexbuf[cur++] =
804 hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
805 hexbuf[cur++] =
806 hexchars[buffer[current_pos + i] & 0x0f];
807
808 /* Delimit array entries with a comma */
809 if (current_pos + i + 1 < nchars)
810 hexbuf[cur++] = ',';
811
812 hexbuf[cur++] = ' ';
813 }
814
815 /* Terminate the array if we are at the end */
816 if (current_pos + i == nchars) {
817 hexbuf[cur++] = '}';
818 hexbuf[cur++] = ';';
819 }
820
821 current_pos += i;
822 (*global_pos) += i;
823 hexbuf[cur++] = '\n';
824 hexbuf[cur] = 0;
825 addText(hexbuf, is_from_server, packet_num);
826 }
827 break;
828
829 case SHOW_YAML:
830 {
831 QString yaml_text;
832
833 const int base64_raw_len = 57; // Encodes to 76 bytes, common in RFCs
834 current_pos = 0;
835
836 if (last_packet_ == 0) {
837 // Header with general info about peers
838 const char *hostname0 = address_to_name(&follow_info_.client_ip);
839 const char *hostname1 = address_to_name(&follow_info_.server_ip);
840
841 char *port0 = get_follow_port_to_display(follower_)(NULL, follow_info_.client_port);
842 char *port1 = get_follow_port_to_display(follower_)(NULL, follow_info_.server_port);
843
844 addText("peers:\n", false, 0, false);
845
846 addText(QString(
847 " - peer: 0\n"
848 " host: %1\n"
849 " port: %2\n")
850 .arg(hostname0)
851 .arg(port0), false, 0);
852
853 addText(QString(
854 " - peer: 1\n"
855 " host: %1\n"
856 " port: %2\n")
857 .arg(hostname1)
858 .arg(port1), true, 0);
859
860 wmem_free(NULL, port0);
861 wmem_free(NULL, port1);
862
863 addText("packets:\n", false, 0, false);
864 }
865
866 if (packet_num != last_packet_) {
867 yaml_text.append(QString(" - packet: %1\n")
868 .arg(packet_num));
869 yaml_text.append(QString(" peer: %1\n")
870 .arg(is_from_server ? 1 : 0));
871 yaml_text.append(QString(" index: %1\n")
872 .arg(is_from_server ? server_buffer_count_++ : client_buffer_count_++));
873 yaml_text.append(QString(" timestamp: %1.%2\n")
874 .arg(abs_ts.secs)
875 .arg(abs_ts.nsecs, 9, 10, QChar('0')));
876 yaml_text.append(QString(" data: !!binary |\n"));
877 }
878 while (current_pos < nchars) {
879 int len = current_pos + base64_raw_len < nchars ? base64_raw_len : (int) nchars - current_pos;
880 QByteArray base64_data(&buffer[current_pos], len);
881
882 yaml_text += " " + base64_data.toBase64() + "\n";
883
884 current_pos += len;
885 (*global_pos) += len;
886 }
887 addText(yaml_text, is_from_server, packet_num);
888 break;
889 }
890
891 case SHOW_RAW:
892 {
893 QByteArray ba = QByteArray(buffer, (int)nchars).toHex();
894 ba += '\n';
895 addText(ba, is_from_server, packet_num);
896 break;
897 }
898 }
899
900 if (last_packet_ == 0) {
901 last_from_server_ = is_from_server;
902 }
903
904 if (packet_num != last_packet_) {
905 last_packet_ = packet_num;
906 if (is_from_server) {
907 server_packet_count_++;
908 } else {
909 client_packet_count_++;
910 }
911 if (last_from_server_ != is_from_server) {
912 last_from_server_ = is_from_server;
913 turns_++;
914 }
915 }
916
917 return FRS_OK;
918 }
919
920 bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index, guint stream_num, guint sub_stream_num)
921 {
922 QString follow_filter;
923 const char *hostname0 = NULL, *hostname1 = NULL;
924 char *port0 = NULL, *port1 = NULL;
925 QString server_to_client_string;
926 QString client_to_server_string;
927 QString both_directions_string;
928 gboolean is_follower = FALSE;
929
930 resetStream();
931
932 if (file_closed_)
933 {
934 QMessageBox::warning(this, tr("No capture file."), tr("Please make sure you have a capture file opened."));
935 return false;
936 }
937
938 if (cap_file_.capFile()->edt == NULL)
939 {
940 QMessageBox::warning(this, tr("Error following stream."), tr("Capture file invalid."));
941 return false;
942 }
943
944 if (!use_stream_index) {
945 is_follower = proto_is_frame_protocol(cap_file_.capFile()->edt->pi.layers, proto_get_protocol_filter_name(get_follow_proto_id(follower_)));
946 if (!is_follower) {
947 QMessageBox::warning(this, tr("Error following stream."), tr("Please make sure you have a %1 packet selected.").arg
948 (proto_get_protocol_short_name(find_protocol_by_id(get_follow_proto_id(follower_)))));
949 return false;
950 }
951 }
952
953 if (follow_type_ == FOLLOW_TLS || follow_type_ == FOLLOW_HTTP)
954 {
955 /* we got tls/http so we can follow */
956 removeStreamControls();
957 }
958
959 follow_reset_stream(&follow_info_);
960
961 /* Create a new filter that matches all packets in the TCP stream,
962 and set the display filter entry accordingly */
963 if (use_stream_index) {
964 follow_filter = gchar_free_to_qstring(get_follow_index_func(follower_)(stream_num, sub_stream_num));
965 } else {
966 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));
967 }
968 if (follow_filter.isEmpty()) {
969 if (follow_type_ == FOLLOW_QUIC) {
970 QMessageBox::warning(this,
971 tr("Error creating filter for this stream."),
972 tr("QUIC streams not found on the selected packet."));
973 } else {
974 QMessageBox::warning(this,
975 tr("Error creating filter for this stream."),
976 tr("A transport or network layer header is needed."));
977 }
978 return false;
979 }
980
981 previous_filter_ = previous_filter;
982 /* append the negation */
983 if (!previous_filter.isEmpty()) {
984 filter_out_filter_ = QString("%1 and !(%2)")
985 .arg(previous_filter).arg(follow_filter);
986 }
987 else
988 {
989 filter_out_filter_ = QString("!(%1)").arg(follow_filter);
990 }
991
992 follow_info_.substream_id = sub_stream_num;
993
994 /* data will be passed via tap callback*/
995 if (!registerTapListener(get_follow_tap_string(follower_), &follow_info_,
996 follow_filter.toUtf8().constData(),
997 0, NULL, get_follow_tap_handler(follower_), NULL)) {
998 return false;
999 }
1000
1001 /* disable substream spin box for all protocols except HTTP2 and QUIC */
1002 ui->subStreamNumberSpinBox->blockSignals(true);
1003 ui->subStreamNumberSpinBox->setEnabled(false);
1004 ui->subStreamNumberSpinBox->setValue(0);
1005 ui->subStreamNumberSpinBox->setKeyboardTracking(false);
1006 ui->subStreamNumberSpinBox->blockSignals(false);
1007 ui->subStreamNumberSpinBox->setVisible(false);
1008 ui->subStreamNumberLabel->setVisible(false);
1009
1010 switch (follow_type_)
1011 {
1012 case FOLLOW_TCP:
1013 {
1014 int stream_count = get_tcp_stream_count();
1015 ui->streamNumberSpinBox->blockSignals(true);
1016 ui->streamNumberSpinBox->setMaximum(stream_count-1);
1017 ui->streamNumberSpinBox->setValue(stream_num);
1018 ui->streamNumberSpinBox->blockSignals(false);
1019 ui->streamNumberSpinBox->setToolTip(tr("%Ln total stream(s).", "", stream_count));
1020 ui->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());
1021
1022 break;
1023 }
1024 case FOLLOW_UDP:
1025 {
1026 int stream_count = get_udp_stream_count();
1027 ui->streamNumberSpinBox->blockSignals(true);
1028 ui->streamNumberSpinBox->setMaximum(stream_count-1);
1029 ui->streamNumberSpinBox->setValue(stream_num);
1030 ui->streamNumberSpinBox->blockSignals(false);
1031 ui->streamNumberSpinBox->setToolTip(tr("%Ln total stream(s).", "", stream_count));
1032 ui->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());
1033
1034 break;
1035 }
1036 case FOLLOW_DCCP:
1037 {
1038 int stream_count = get_dccp_stream_count();
1039 ui->streamNumberSpinBox->blockSignals(true);
1040 ui->streamNumberSpinBox->setMaximum(stream_count-1);
1041 ui->streamNumberSpinBox->setValue(stream_num);
1042 ui->streamNumberSpinBox->blockSignals(false);
1043 ui->streamNumberSpinBox->setToolTip(tr("%Ln total stream(s).", "", stream_count));
1044 ui->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());
1045
1046 break;
1047 }
1048 case FOLLOW_HTTP2:
1049 {
1050 int stream_count = get_tcp_stream_count();
1051 ui->streamNumberSpinBox->blockSignals(true);
1052 ui->streamNumberSpinBox->setMaximum(stream_count-1);
1053 ui->streamNumberSpinBox->setValue(stream_num);
1054 ui->streamNumberSpinBox->blockSignals(false);
1055 ui->streamNumberSpinBox->setToolTip(tr("%Ln total stream(s).", "", stream_count));
1056 ui->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());
1057
1058 guint substream_max_id = 0;
1059 http2_get_stream_id_le(static_cast<guint>(stream_num), G_MAXINT32, &substream_max_id);
1060 stream_count = static_cast<gint>(substream_max_id);
1061 ui->subStreamNumberSpinBox->blockSignals(true);
1062 ui->subStreamNumberSpinBox->setEnabled(true);
1063 ui->subStreamNumberSpinBox->setMaximum(stream_count);
1064 ui->subStreamNumberSpinBox->setValue(sub_stream_num);
1065 ui->subStreamNumberSpinBox->blockSignals(false);
1066 ui->subStreamNumberSpinBox->setToolTip(tr("%Ln total sub stream(s).", "", stream_count));
1067 ui->subStreamNumberSpinBox->setToolTip(ui->subStreamNumberSpinBox->toolTip());
1068 ui->subStreamNumberSpinBox->setVisible(true);
1069 ui->subStreamNumberLabel->setVisible(true);
1070
1071 break;
1072 }
1073 case FOLLOW_QUIC:
1074 {
1075 int stream_count = get_quic_connections_count();
1076 ui->streamNumberSpinBox->blockSignals(true);
1077 ui->streamNumberSpinBox->setMaximum(stream_count-1);
1078 ui->streamNumberSpinBox->setValue(stream_num);
1079 ui->streamNumberSpinBox->blockSignals(false);
1080 ui->streamNumberSpinBox->setToolTip(tr("Total number of QUIC connections: %Ln", "", stream_count));
1081 ui->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());
1082
1083 guint substream_max_id = 0;
1084 quic_get_stream_id_le(static_cast<guint>(stream_num), G_MAXINT32, &substream_max_id);
1085 stream_count = static_cast<gint>(substream_max_id);
1086 ui->subStreamNumberSpinBox->blockSignals(true);
1087 ui->subStreamNumberSpinBox->setEnabled(true);
1088 ui->subStreamNumberSpinBox->setMaximum(stream_count);
1089 ui->subStreamNumberSpinBox->setValue(sub_stream_num);
1090 ui->subStreamNumberSpinBox->blockSignals(false);
1091 ui->subStreamNumberSpinBox->setToolTip(tr("Max QUIC Stream ID for the selected connection: %Ln", "", stream_count));
1092 ui->subStreamNumberSpinBox->setToolTip(ui->subStreamNumberSpinBox->toolTip());
1093 ui->subStreamNumberSpinBox->setVisible(true);
1094 ui->subStreamNumberLabel->setVisible(true);
1095
1096 break;
1097 }
1098 case FOLLOW_TLS:
1099 case FOLLOW_HTTP:
1100 /* No extra handling */
1101 break;
1102 case FOLLOW_SIP:
1103 {
1104 /* There are no more streams */
1105 ui->streamNumberSpinBox->setEnabled(false);
1106 ui->streamNumberSpinBox->blockSignals(true);
1107 ui->streamNumberSpinBox->setMaximum(0);
1108 ui->streamNumberSpinBox->setValue(0);
1109 ui->streamNumberSpinBox->blockSignals(false);
1110 ui->streamNumberSpinBox->setToolTip(tr("No streams"));
1111 ui->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());
1112
1113 break;
1114 }
1115 }
1116
1117 beginRetapPackets();
1118 updateWidgets(true);
1119
1120 /* Run the display filter so it goes in effect - even if it's the
1121 same as the previous display filter. */
1122 emit updateFilter(follow_filter, TRUE);
1123
1124 removeTapListeners();
1125
1126 hostname0 = address_to_name(&follow_info_.client_ip);
1127 hostname1 = address_to_name(&follow_info_.server_ip);
1128
1129 port0 = get_follow_port_to_display(follower_)(NULL, follow_info_.client_port);
1130 port1 = get_follow_port_to_display(follower_)(NULL, follow_info_.server_port);
1131
1132 server_to_client_string =
1133 QString("%1:%2 %3 %4:%5 (%6)")
1134 .arg(hostname0).arg(port0)
1135 .arg(UTF8_RIGHTWARDS_ARROW)
1136 .arg(hostname1).arg(port1)
1137 .arg(gchar_free_to_qstring(format_size(
1138 follow_info_.bytes_written[0],
1139 format_size_unit_bytes|format_size_prefix_si)));
1140
1141 client_to_server_string =
1142 QString("%1:%2 %3 %4:%5 (%6)")
1143 .arg(hostname1).arg(port1)
1144 .arg(UTF8_RIGHTWARDS_ARROW)
1145 .arg(hostname0).arg(port0)
1146 .arg(gchar_free_to_qstring(format_size(
1147 follow_info_.bytes_written[1],
1148 format_size_unit_bytes|format_size_prefix_si)));
1149
1150 wmem_free(NULL, port0);
1151 wmem_free(NULL, port1);
1152
1153 both_directions_string = tr("Entire conversation (%1)")
1154 .arg(gchar_free_to_qstring(format_size(
1155 follow_info_.bytes_written[0] + follow_info_.bytes_written[1],
1156 format_size_unit_bytes|format_size_prefix_si)));
1157 setWindowSubtitle(tr("Follow %1 Stream (%2)").arg(proto_get_protocol_short_name(find_protocol_by_id(get_follow_proto_id(follower_))))
1158 .arg(follow_filter));
1159
1160 ui->cbDirections->blockSignals(true);
1161 ui->cbDirections->clear();
1162 ui->cbDirections->addItem(both_directions_string);
1163 ui->cbDirections->addItem(client_to_server_string);
1164 ui->cbDirections->addItem(server_to_client_string);
1165 ui->cbDirections->blockSignals(false);
1166
1167 followStream();
1168 fillHintLabel(-1);
1169
1170 updateWidgets(false);
1171 endRetapPackets();
1172
1173 if (prefs.restore_filter_after_following_stream) {
1174 emit updateFilter(previous_filter_, TRUE);
1175 }
1176
1177 return true;
1178 }
1179
1180 void FollowStreamDialog::captureFileClosed()
1181 {
1182 QString tooltip = tr("File closed.");
1183 ui->streamNumberSpinBox->setToolTip(tooltip);
1184 ui->streamNumberLabel->setToolTip(tooltip);
1185 WiresharkDialog::captureFileClosed();
1186 }
1187
1188 /*
1189 * XXX - the routine pointed to by "print_line_fcn_p" doesn't get handed lines,
1190 * it gets handed bufferfuls. That's fine for "follow_write_raw()"
1191 * and "follow_add_to_gtk_text()", but, as "follow_print_text()" calls
1192 * the "print_line()" routine from "print.c", and as that routine might
1193 * genuinely expect to be handed a line (if, for example, it's using
1194 * some OS or desktop environment's printing API, and that API expects
1195 * to be handed lines), "follow_print_text()" should probably accumulate
1196 * lines in a buffer and hand them "print_line()". (If there's a
1197 * complete line in a buffer - i.e., there's nothing of the line in
1198 * the previous buffer or the next buffer - it can just hand that to
1199 * "print_line()" after filtering out non-printables, as an
1200 * optimization.)
1201 *
1202 * This might or might not be the reason why C arrays display
1203 * correctly but get extra blank lines very other line when printed.
1204 */
1205 frs_return_t
1206 FollowStreamDialog::readFollowStream()
1207 {
1208 guint32 global_client_pos = 0, global_server_pos = 0;
1209 guint32 *global_pos;
1210 gboolean skip;
1211 GList* cur;
1212 frs_return_t frs_return;
1213 follow_record_t *follow_record;
1214 QElapsedTimer elapsed_timer;
1215
1216 elapsed_timer.start();
1217
1218 for (cur = g_list_last(follow_info_.payload); cur; cur = g_list_previous(cur)) {
1219 if (dialogClosed()) break;
1220
1221 follow_record = (follow_record_t *)cur->data;
1222 skip = FALSE;
1223 if (!follow_record->is_server) {
1224 global_pos = &global_client_pos;
1225 if (follow_info_.show_stream == FROM_SERVER) {
1226 skip = TRUE;
1227 }
1228 } else {
1229 global_pos = &global_server_pos;
1230 if (follow_info_.show_stream == FROM_CLIENT) {
1231 skip = TRUE;
1232 }
1233 }
1234
1235 QByteArray buffer;
1236 if (!skip) {
1237 // We want a deep copy.
1238 buffer.clear();
1239 buffer.append((const char *) follow_record->data->data,
1240 follow_record->data->len);
1241 frs_return = showBuffer(
1242 buffer.data(),
1243 follow_record->data->len,
1244 follow_record->is_server,
1245 follow_record->packet_num,
1246 follow_record->abs_ts,
1247 global_pos);
1248 if (frs_return == FRS_PRINT_ERROR)
1249 return frs_return;
1250 if (elapsed_timer.elapsed() > info_update_freq_) {
1251 fillHintLabel(ui->teStreamContent->textCursor().position());
1252 wsApp->processEvents();
1253 elapsed_timer.start();
1254 }
1255 }
1256 }
1257
1258 return FRS_OK;
1259 }
1260