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