1 /* show_packet_bytes_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 "show_packet_bytes_dialog.h"
11 #include <ui_show_packet_bytes_dialog.h>
12 
13 #include "main_window.h"
14 #include "wireshark_application.h"
15 #include "ui/qt/widgets/wireshark_file_dialog.h"
16 
17 #include "epan/charsets.h"
18 
19 #include "wsutil/utf8_entities.h"
20 
21 #include <QAction>
22 #include <QImage>
23 #include <QKeyEvent>
24 #include <QMenu>
25 #include <QPrintDialog>
26 #include <QPrinter>
27 #include <QTextCodec>
28 #include <QTextStream>
29 
30 // To do:
31 // - Add show as custom protocol in a Packet Details view
32 // - Use ByteViewText to ShowAsHexDump and supplementary view for custom protocol
33 // - Handle large data blocks
34 
35 ShowPacketBytesDialog::ShowPacketBytesDialog(QWidget &parent, CaptureFile &cf) :
36     WiresharkDialog(parent, cf),
37     ui(new Ui::ShowPacketBytesDialog),
38     finfo_(cf.capFile()->finfo_selected),
39     decode_as_(DecodeAsNone),
40     show_as_(ShowAsASCII),
41     use_regex_find_(false)
42 {
43     ui->setupUi(this);
44     loadGeometry(parent.width() * 2 / 3, parent.height() * 3 / 4);
45 
46     QString field_name = QString("%1 (%2)").arg(finfo_->hfinfo->name, finfo_->hfinfo->abbrev);
47     setWindowSubtitle (field_name);
48 
49     hint_label_ = tr("Frame %1, %2, %Ln byte(s).", "", finfo_->length)
50                      .arg(cf.capFile()->current_frame->num)
51                      .arg(field_name);
52 
53     ui->tePacketBytes->installEventFilter(this);
54 
55     connect(ui->tePacketBytes, SIGNAL(showSelected(int,int)), this, SLOT(showSelected(int,int)));
56     connect(ui->leFind, SIGNAL(useRegexFind(bool)), this, SLOT(useRegexFind(bool)));
57 
58     ui->cbDecodeAs->blockSignals(true);
59     ui->cbDecodeAs->addItem(tr("None"), DecodeAsNone);
60     ui->cbDecodeAs->addItem(tr("Base64"), DecodeAsBASE64);
61     ui->cbDecodeAs->addItem(tr("Compressed"), DecodeAsCompressed);
62     ui->cbDecodeAs->addItem(tr("Hex Digits"), DecodeAsHexDigits);
63     ui->cbDecodeAs->addItem(tr("Quoted-Printable"), DecodeAsQuotedPrintable);
64     ui->cbDecodeAs->addItem(tr("ROT13"), DecodeAsROT13);
65     ui->cbDecodeAs->blockSignals(false);
66 
67     ui->cbShowAs->blockSignals(true);
68     ui->cbShowAs->addItem(tr("ASCII"), ShowAsASCII);
69     ui->cbShowAs->addItem(tr("ASCII & Control"), ShowAsASCIIandControl);
70     ui->cbShowAs->addItem(tr("C Array"), ShowAsCArray);
71     ui->cbShowAs->addItem(tr("EBCDIC"), ShowAsEBCDIC);
72     ui->cbShowAs->addItem(tr("Hex Dump"), ShowAsHexDump);
73     ui->cbShowAs->addItem(tr("HTML"), ShowAsHTML);
74     ui->cbShowAs->addItem(tr("Image"), ShowAsImage);
75     ui->cbShowAs->addItem(tr("Raw"), ShowAsRAW);
76     // UTF-8 is guaranteed to exist as a QTextCodec
77     ui->cbShowAs->addItem(tr("UTF-8"), ShowAsCodec);
78     ui->cbShowAs->addItem(tr("YAML"), ShowAsYAML);
79     ui->cbShowAs->setCurrentIndex(show_as_);
80     ui->cbShowAs->blockSignals(false);
81 
82     ui->sbStart->setMinimum(0);
83     ui->sbEnd->setMaximum(finfo_->length);
84 
85     print_button_ = ui->buttonBox->addButton(tr("Print"), QDialogButtonBox::ActionRole);
86     connect(print_button_, SIGNAL(clicked()), this, SLOT(printBytes()));
87 
88     copy_button_ = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
89     connect(copy_button_, SIGNAL(clicked()), this, SLOT(copyBytes()));
90 
91     save_as_button_ = ui->buttonBox->addButton(tr("Save as…"), QDialogButtonBox::ActionRole);
92     connect(save_as_button_, SIGNAL(clicked()), this, SLOT(saveAs()));
93 
94     connect(ui->buttonBox, SIGNAL(helpRequested()), this, SLOT(helpButton()));
95 
96     setStartAndEnd(0, finfo_->length);
97     updateFieldBytes(true);
98 }
99 
100 ShowPacketBytesDialog::~ShowPacketBytesDialog()
101 {
102     delete ui;
103 }
104 
105 void ShowPacketBytesDialog::addCodecs(const QMap<QString, QTextCodec *> &codecMap)
106 {
107     ui->cbShowAs->blockSignals(true);
108     // Make the combobox respect max visible items?
109     //ui->cbShowAs->setStyleSheet("QComboBox { combobox-popup: 0;}");
110     ui->cbShowAs->insertSeparator(ui->cbShowAs->count());
111 #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
112     for (const auto &codec : qAsConst(codecMap)) {
113 #else
114     foreach (const QTextCodec *codec, codecMap) {
115 #endif
116         // This is already placed in the menu and handled separately
117         if (codec->name() != "US-ASCII" && codec->name() != "UTF-8")
118             ui->cbShowAs->addItem(tr(codec->name()), ShowAsCodec);
119     }
120     ui->cbShowAs->blockSignals(false);
121 }
122 
123 void ShowPacketBytesDialog::showSelected(int start, int end)
124 {
125     if (end == -1) {
126         // end set to -1 means show all packet bytes
127         setStartAndEnd(0, finfo_->length);
128     } else {
129         if (show_as_ == ShowAsRAW) {
130             start /= 2;
131             end = (end + 1) / 2;
132         }
133         setStartAndEnd(start_ + start, start_ + end);
134     }
135     updateFieldBytes();
136 }
137 
138 void ShowPacketBytesDialog::setStartAndEnd(int start, int end)
139 {
140     start_ = start;
141     end_ = end;
142 
143     ui->sbStart->blockSignals(true);
144     ui->sbStart->setMaximum(end_);
145     ui->sbStart->setValue(start_);
146     ui->sbStart->blockSignals(false);
147 
148     ui->sbEnd->blockSignals(true);
149     ui->sbEnd->setMinimum(start_);
150     ui->sbEnd->setValue(end_);
151     ui->sbEnd->blockSignals(false);
152 
153     updateHintLabel();
154 }
155 
156 bool ShowPacketBytesDialog::enableShowSelected()
157 {
158     // "Show Selected" only works when showing all bytes:
159     // - DecodeAs must not alter the number of bytes in the buffer
160     // - ShowAs must show all bytes in the buffer
161 
162     return (((decode_as_ == DecodeAsNone) ||
163              (decode_as_ == DecodeAsROT13)) &&
164             ((show_as_ == ShowAsASCII) ||
165              (show_as_ == ShowAsASCIIandControl) ||
166              (show_as_ == ShowAsEBCDIC) ||
167              (show_as_ == ShowAsRAW)));
168 }
169 
170 void ShowPacketBytesDialog::updateWidgets()
171 {
172     WiresharkDialog::updateWidgets();
173 }
174 
175 void ShowPacketBytesDialog::updateHintLabel()
176 {
177     QString hint = hint_label_;
178 
179     if (start_ > 0 || end_ < finfo_->length) {
180         hint.append(" <span style=\"color: red\">" +
181                     tr("Displaying %Ln byte(s).", "", end_ - start_) +
182                     "</span>");
183     }
184 
185     ui->hintLabel->setText("<small><i>" + hint + "</i></small>");
186 }
187 
188 void ShowPacketBytesDialog::on_sbStart_valueChanged(int value)
189 {
190     start_ = value;
191     ui->sbEnd->setMinimum(value);
192 
193     updateHintLabel();
194     updateFieldBytes();
195 }
196 
197 void ShowPacketBytesDialog::on_sbEnd_valueChanged(int value)
198 {
199     end_ = value;
200     ui->sbStart->setMaximum(value);
201 
202     updateHintLabel();
203     updateFieldBytes();
204 }
205 
206 void ShowPacketBytesDialog::on_cbDecodeAs_currentIndexChanged(int idx)
207 {
208     if (idx < 0) return;
209     decode_as_ = static_cast<DecodeAsType>(ui->cbDecodeAs->itemData(idx).toInt());
210 
211     ui->tePacketBytes->setShowSelectedEnabled(enableShowSelected());
212 
213     updateFieldBytes();
214 }
215 
216 void ShowPacketBytesDialog::on_cbShowAs_currentIndexChanged(int idx)
217 {
218     if (idx < 0) return;
219     show_as_ = static_cast<ShowAsType>(ui->cbShowAs->itemData(idx).toInt());
220 
221     ui->tePacketBytes->setShowSelectedEnabled(enableShowSelected());
222     ui->lFind->setEnabled(true);
223     ui->leFind->setEnabled(true);
224     ui->bFind->setEnabled(true);
225     print_button_->setEnabled(true);
226     copy_button_->setEnabled(true);
227     save_as_button_->setEnabled(true);
228 
229     updatePacketBytes();
230 }
231 
232 void ShowPacketBytesDialog::useRegexFind(bool use_regex)
233 {
234     use_regex_find_ = use_regex;
235     if (use_regex_find_)
236         ui->lFind->setText(tr("Regex Find:"));
237     else
238         ui->lFind->setText(tr("Find:"));
239 }
240 
241 void ShowPacketBytesDialog::findText(bool go_back)
242 {
243     if (ui->leFind->text().isEmpty()) return;
244 
245     bool found;
246     if (use_regex_find_) {
247         QRegExp regex(ui->leFind->text());
248         found = ui->tePacketBytes->find(regex);
249     } else {
250         found = ui->tePacketBytes->find(ui->leFind->text());
251     }
252 
253     if (found) {
254         ui->tePacketBytes->setFocus();
255     } else if (go_back) {
256         ui->tePacketBytes->moveCursor(QTextCursor::Start);
257         findText(false);
258     }
259 }
260 
261 void ShowPacketBytesDialog::printBytes()
262 {
263 #ifndef QT_NO_PRINTER
264     QPrinter printer(QPrinter::HighResolution);
265     QPrintDialog dialog(&printer, this);
266     if (dialog.exec() == QDialog::Accepted)
267         ui->tePacketBytes->print(&printer);
268 #endif
269 }
270 
271 void ShowPacketBytesDialog::copyBytes()
272 {
273     switch (show_as_) {
274 
275     case ShowAsASCII:
276     {
277         QByteArray ba(field_bytes_);
278         sanitizeBuffer(ba, true);
279         wsApp->clipboard()->setText(ba);
280         break;
281     }
282 
283     case ShowAsASCIIandControl:
284     case ShowAsCArray:
285     case ShowAsEBCDIC:
286     case ShowAsHexDump:
287     case ShowAsRAW:
288     case ShowAsYAML:
289         wsApp->clipboard()->setText(ui->tePacketBytes->toPlainText());
290         break;
291 
292     case ShowAsHTML:
293         wsApp->clipboard()->setText(ui->tePacketBytes->toHtml());
294         break;
295 
296     case ShowAsImage:
297         wsApp->clipboard()->setImage(image_);
298         break;
299 
300     case ShowAsCodec:
301         wsApp->clipboard()->setText(ui->tePacketBytes->toPlainText().toUtf8());
302         break;
303     }
304 }
305 
306 void ShowPacketBytesDialog::saveAs()
307 {
308     QString file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Selected Packet Bytes As…")));
309 
310     if (file_name.isEmpty())
311         return;
312 
313     QFile::OpenMode open_mode = QFile::WriteOnly;
314     switch (show_as_) {
315     case ShowAsASCII:
316     case ShowAsASCIIandControl:
317     case ShowAsCArray:
318     // We always save as UTF-8, so set text mode as we would for UTF-8
319     case ShowAsCodec:
320     case ShowAsHexDump:
321     case ShowAsYAML:
322     case ShowAsHTML:
323         open_mode |= QFile::Text;
324     default:
325         break;
326     }
327 
328     QFile file(file_name);
329     file.open(open_mode);
330 
331     switch (show_as_) {
332 
333     case ShowAsASCII:
334     {
335         QByteArray ba(field_bytes_);
336         sanitizeBuffer(ba, true);
337         file.write(ba);
338         break;
339     }
340 
341     case ShowAsASCIIandControl:
342     case ShowAsCArray:
343     case ShowAsEBCDIC:
344     case ShowAsHexDump:
345     case ShowAsYAML:
346     {
347         QTextStream out(&file);
348         out << ui->tePacketBytes->toPlainText();
349         break;
350     }
351 
352     case ShowAsHTML:
353     {
354         QTextStream out(&file);
355         out << ui->tePacketBytes->toHtml();
356         break;
357     }
358 
359     case ShowAsCodec:
360     {
361         QTextStream out(&file);
362         out << ui->tePacketBytes->toPlainText().toUtf8();
363         break;
364     }
365 
366     case ShowAsImage:
367     case ShowAsRAW:
368         file.write(field_bytes_);
369         break;
370     }
371 
372     file.close();
373 }
374 
375 void ShowPacketBytesDialog::helpButton()
376 {
377     wsApp->helpTopicAction(HELP_SHOW_PACKET_BYTES_DIALOG);
378 }
379 
380 void ShowPacketBytesDialog::on_bFind_clicked()
381 {
382     findText();
383 }
384 
385 void ShowPacketBytesDialog::on_leFind_returnPressed()
386 {
387     findText();
388 }
389 
390 // Not sure why we have to do this manually.
391 void ShowPacketBytesDialog::on_buttonBox_rejected()
392 {
393     WiresharkDialog::reject();
394 }
395 
396 // The following keyboard shortcuts should work (although
397 // they may not work consistently depending on focus):
398 // / (slash), Ctrl-F - Focus and highlight the search box
399 // Ctrl-G, Ctrl-N, F3 - Find next
400 // Should we make it so that typing any text starts searching?
401 bool ShowPacketBytesDialog::eventFilter(QObject *, QEvent *event)
402 {
403     if (ui->tePacketBytes->hasFocus() && event->type() == QEvent::KeyPress) {
404         QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
405         if (keyEvent->matches(QKeySequence::SelectAll) || keyEvent->matches(QKeySequence::Copy)
406                 || keyEvent->text().isEmpty()) {
407             return false;
408         }
409         ui->leFind->setFocus();
410         if (keyEvent->matches(QKeySequence::Find)) {
411             return true;
412         } else if (keyEvent->matches(QKeySequence::FindNext)) {
413             findText();
414             return true;
415         }
416     }
417 
418     return false;
419 }
420 
421 void ShowPacketBytesDialog::keyPressEvent(QKeyEvent *event)
422 {
423     if (ui->leFind->hasFocus()) {
424         if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
425             findText();
426             return;
427         }
428     } else {
429         if (event->key() == Qt::Key_Slash || event->matches(QKeySequence::Find)) {
430             ui->leFind->setFocus();
431             ui->leFind->selectAll();
432         }
433         return;
434     }
435 
436     if (event->key() == Qt::Key_F3 || (event->key() == Qt::Key_N && event->modifiers() & Qt::ControlModifier)) {
437         findText();
438         return;
439     }
440 
441     QDialog::keyPressEvent(event);
442 }
443 
444 void ShowPacketBytesDialog::sanitizeBuffer(QByteArray &ba, bool keep_CR)
445 {
446     for (int i = 0; i < ba.length(); i++) {
447         if (ba[i] == '\n' || (keep_CR && ba[i] == '\r'))
448             // Keep LF and optionally CR
449             continue;
450 
451         if (ba[i] == '\0' || g_ascii_isspace(ba[i])) {
452             ba[i] = ' ';
453         } else if (!g_ascii_isprint(ba[i])) {
454             ba.replace(i, 1, UTF8_MIDDLE_DOT);
455             i += sizeof(UTF8_MIDDLE_DOT) - 2;
456         }
457     }
458 }
459 
460 void ShowPacketBytesDialog::symbolizeBuffer(QByteArray &ba)
461 {
462     for (int i = 0; i < ba.length(); i++) {
463         if ((ba[i] < '\0' || ba[i] >= ' ') && ba[i] != (char)0x7f && !g_ascii_isprint(ba[i])) {
464             ba.replace(i, 1, UTF8_MIDDLE_DOT);
465             i += sizeof(UTF8_MIDDLE_DOT) - 2;
466         }
467     }
468 
469     QByteArray symbol(UTF8_SYMBOL_FOR_NULL);
470     for (char i = 0; i < ' '; i++) {
471         ba.replace(i, symbol);
472         symbol[2] = symbol[2] + 1;
473     }
474     symbol[2] = symbol[2] + 1;      // Skip SP
475     ba.replace((char)0x7f, symbol); // DEL
476 }
477 
478 QByteArray ShowPacketBytesDialog::decodeQuotedPrintable(const guint8 *bytes, int length)
479 {
480     QByteArray ba;
481 
482     for (int i = 0; i < length; i++) {
483         if (bytes[i] == '=' && i + 1 < length) {
484             if (bytes[i+1] == '\n') {
485                 i++;     // Soft line break LF
486             } else if (bytes[i+1] == '\r' && i + 2 < length && bytes[i+2] == '\n') {
487                 i += 2;  // Soft line break CRLF
488             } else if (g_ascii_isxdigit(bytes[i+1]) && i + 2 < length && g_ascii_isxdigit(bytes[i+2])) {
489                 ba.append(QByteArray::fromHex(QByteArray((const char *)&bytes[i+1], 2)));
490                 i += 2;  // Valid Quoted-Printable sequence
491             } else {
492                 // Illegal Quoted-Printable, just add byte
493                 ba.append(bytes[i]);
494             }
495         } else {
496             ba.append(bytes[i]);
497         }
498     }
499 
500     return ba;
501 }
502 
503 void ShowPacketBytesDialog::rot13(QByteArray &ba)
504 {
505     for (int i = 0; i < ba.length(); i++) {
506         gchar upper = g_ascii_toupper(ba[i]);
507         if (upper >= 'A' && upper <= 'M') ba[i] = ba[i] + 13;
508         else if (upper >= 'N' && upper <= 'Z') ba[i] = ba[i] - 13;
509     }
510 }
511 
512 void ShowPacketBytesDialog::updateFieldBytes(bool initialization)
513 {
514     int start = finfo_->start + start_;
515     int length = end_ - start_;
516     const guint8 *bytes;
517     gsize new_length = 0;
518 
519     if (!finfo_->ds_tvb)
520         return;
521 
522     switch (decode_as_) {
523 
524     case DecodeAsNone:
525         bytes = tvb_get_ptr(finfo_->ds_tvb, start, -1);
526         field_bytes_ = QByteArray((const char *)bytes, length);
527         break;
528 
529     case DecodeAsBASE64:
530     {
531         bytes = tvb_get_ptr(finfo_->ds_tvb, start, -1);
532         field_bytes_ = QByteArray((const char *)bytes, length);
533         if (field_bytes_.size() > 1) {
534             g_base64_decode_inplace(field_bytes_.data(), &new_length);
535         }
536         field_bytes_.resize((int)new_length);
537         break;
538     }
539 
540     case DecodeAsCompressed:
541     {
542         tvbuff *uncompr_tvb = tvb_uncompress(finfo_->ds_tvb, start, length);
543         if (uncompr_tvb) {
544             bytes = tvb_get_ptr(uncompr_tvb, 0, -1);
545             field_bytes_ = QByteArray((const char *)bytes, tvb_reported_length(uncompr_tvb));
546             tvb_free(uncompr_tvb);
547         } else {
548             field_bytes_.clear();
549         }
550         break;
551     }
552 
553     case DecodeAsHexDigits:
554         bytes = tvb_get_ptr(finfo_->ds_tvb, start, -1);
555         field_bytes_ = QByteArray::fromHex(QByteArray::fromRawData((const char *)bytes, length));
556         break;
557 
558     case DecodeAsQuotedPrintable:
559         bytes = tvb_get_ptr(finfo_->ds_tvb, start, -1);
560         field_bytes_ = decodeQuotedPrintable(bytes, length);
561         break;
562 
563     case DecodeAsROT13:
564         bytes = tvb_get_ptr(finfo_->ds_tvb, start, -1);
565         field_bytes_ = QByteArray((const char *)bytes, length);
566         rot13(field_bytes_);
567         break;
568     }
569 
570     // Try loading as image at startup
571     if (initialization && image_.loadFromData(field_bytes_)) {
572         show_as_ = ShowAsImage;
573         ui->cbShowAs->blockSignals(true);
574         ui->cbShowAs->setCurrentIndex(ShowAsImage);
575         ui->cbShowAs->blockSignals(false);
576     }
577 
578     updatePacketBytes();
579 }
580 
581 void ShowPacketBytesDialog::updatePacketBytes(void)
582 {
583     static const gchar hexchars[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
584 
585     ui->tePacketBytes->clear();
586     ui->tePacketBytes->setCurrentFont(wsApp->monospaceFont());
587 
588     switch (show_as_) {
589 
590     case ShowAsASCII:
591     {
592         QByteArray ba(field_bytes_);
593         sanitizeBuffer(ba, false);
594         ui->tePacketBytes->setLineWrapMode(QTextEdit::WidgetWidth);
595         ui->tePacketBytes->setPlainText(ba);
596         break;
597     }
598 
599     case ShowAsASCIIandControl:
600     {
601         QByteArray ba(field_bytes_);
602         symbolizeBuffer(ba);
603         ui->tePacketBytes->setLineWrapMode(QTextEdit::WidgetWidth);
604         ui->tePacketBytes->setPlainText(ba);
605         break;
606     }
607 
608     case ShowAsCArray:
609     {
610         int pos = 0, len = field_bytes_.length();
611         QString text("char packet_bytes[] = {\n");
612 
613         while (pos < len) {
614             gchar hexbuf[256];
615             char *cur = hexbuf;
616             int i;
617 
618             *cur++ = ' ';
619             for (i = 0; i < 8 && pos + i < len; i++) {
620                 // Prepend entries with " 0x"
621                 *cur++ = ' ';
622                 *cur++ = '0';
623                 *cur++ = 'x';
624                 *cur++ = hexchars[(field_bytes_[pos + i] & 0xf0) >> 4];
625                 *cur++ = hexchars[field_bytes_[pos + i] & 0x0f];
626 
627                 // Delimit array entries with a comma
628                 if (pos + i + 1 < len)
629                     *cur++ = ',';
630             }
631 
632             pos += i;
633             *cur++ = '\n';
634             *cur = 0;
635 
636             text.append(hexbuf);
637         }
638 
639         text.append("};\n");
640         ui->tePacketBytes->setLineWrapMode(QTextEdit::NoWrap);
641         ui->tePacketBytes->setPlainText(text);
642         break;
643     }
644 
645     case ShowAsCodec:
646     {
647         // The QTextCodecs docs say that there's a flag to cause invalid
648         // characters to be replaced with null. It's unclear what happens
649         // in the default case; it might depend on the codec though it
650         // seems that in practice replacement characters are used.
651         QTextCodec *codec = QTextCodec::codecForName(ui->cbShowAs->currentText().toUtf8());
652         QByteArray ba(field_bytes_);
653         QString decoded = codec->toUnicode(ba);
654         ui->tePacketBytes->setLineWrapMode(QTextEdit::WidgetWidth);
655         ui->tePacketBytes->setPlainText(decoded);
656         break;
657     }
658 
659     case ShowAsEBCDIC:
660     {
661         QByteArray ba(field_bytes_);
662         EBCDIC_to_ASCII((guint8*)ba.data(), ba.length());
663         sanitizeBuffer(ba, false);
664         ui->tePacketBytes->setLineWrapMode(QTextEdit::WidgetWidth);
665         ui->tePacketBytes->setPlainText(ba);
666         break;
667     }
668 
669     case ShowAsHexDump:
670     {
671         int pos = 0, len = field_bytes_.length();
672         // Use 16-bit offset if there are <= 65536 bytes, 32-bit offset if there are more
673         unsigned int offset_chars = (len - 1 <= 0xFFFF) ? 4 : 8;
674         QString text;
675         text.reserve((len / 16) * 80);
676 
677         while (pos < len) {
678             char hexbuf[256];
679             char *cur = hexbuf;
680             int i;
681 
682             // Dump offset
683             cur += g_snprintf(cur, 20, "%0*X  ", offset_chars, pos);
684 
685             // Dump bytes as hex
686             for (i = 0; i < 16 && pos + i < len; i++) {
687                 *cur++ = hexchars[(field_bytes_[pos + i] & 0xf0) >> 4];
688                 *cur++ = hexchars[field_bytes_[pos + i] & 0x0f];
689                 *cur++ = ' ';
690                 if (i == 7)
691                     *cur++ = ' ';
692             }
693 
694             while (cur < hexbuf + offset_chars + 53)
695                 *cur++ = ' '; // Fill it up with space to ascii column
696 
697             // Dump bytes as text
698             for (i = 0; i < 16 && pos + i < len; i++) {
699                 if (g_ascii_isprint(field_bytes_[pos + i])) {
700                     *cur++ = field_bytes_[pos + i];
701                 } else {
702                     memcpy(cur, UTF8_MIDDLE_DOT, sizeof(UTF8_MIDDLE_DOT) - 1);
703                     cur += sizeof(UTF8_MIDDLE_DOT) - 1;
704                 }
705                 if (i == 7)
706                     *cur++ = ' ';
707             }
708 
709             pos += i;
710             *cur++ = '\n';
711             *cur = 0;
712 
713             text.append(hexbuf);
714         }
715 
716         ui->tePacketBytes->setLineWrapMode(QTextEdit::NoWrap);
717         ui->tePacketBytes->setPlainText(text);
718         break;
719     }
720 
721     case ShowAsHTML:
722         ui->tePacketBytes->setLineWrapMode(QTextEdit::WidgetWidth);
723         ui->tePacketBytes->setHtml(field_bytes_);
724         break;
725 
726     case ShowAsImage:
727     {
728         ui->lFind->setEnabled(false);
729         ui->leFind->setEnabled(false);
730         ui->bFind->setEnabled(false);
731 
732         ui->tePacketBytes->setLineWrapMode(QTextEdit::WidgetWidth);
733         if (image_.loadFromData(field_bytes_)) {
734             ui->tePacketBytes->textCursor().insertImage(image_);
735         }
736 
737         print_button_->setEnabled(!image_.isNull());
738         copy_button_->setEnabled(!image_.isNull());
739         save_as_button_->setEnabled(!image_.isNull());
740         break;
741     }
742 
743     case ShowAsYAML:
744     {
745         const int base64_raw_len = 57; // Encodes to 76 bytes, common in RFCs
746         int pos = 0, len = field_bytes_.length();
747         QString text("# Packet Bytes: !!binary |\n");
748 
749         while (pos < len) {
750             QByteArray base64_data = field_bytes_.mid(pos, base64_raw_len);
751             pos += base64_data.length();
752             text.append("  " + base64_data.toBase64() + "\n");
753         }
754 
755         ui->tePacketBytes->setLineWrapMode(QTextEdit::NoWrap);
756         ui->tePacketBytes->setPlainText(text);
757         break;
758     }
759 
760     case ShowAsRAW:
761         ui->tePacketBytes->setLineWrapMode(QTextEdit::WidgetWidth);
762         ui->tePacketBytes->setPlainText(field_bytes_.toHex());
763         break;
764     }
765 }
766 
767 void ShowPacketBytesDialog::captureFileClosing()
768 {
769     finfo_ = NULL;  // This will invalidate the source backend
770 
771     WiresharkDialog::captureFileClosing();
772 }
773 
774 void ShowPacketBytesDialog::captureFileClosed()
775 {
776     // We have lost the source backend and must disable all functions
777     // for manipulating decoding and displayed range.
778 
779     ui->tePacketBytes->setMenusEnabled(false);
780     ui->lDecodeAs->setEnabled(false);
781     ui->cbDecodeAs->setEnabled(false);
782     ui->lStart->setEnabled(false);
783     ui->sbStart->setEnabled(false);
784     ui->lEnd->setEnabled(false);
785     ui->sbEnd->setEnabled(false);
786 
787     WiresharkDialog::captureFileClosed();
788 }
789 
790 void ShowPacketBytesTextEdit::contextMenuEvent(QContextMenuEvent *event)
791 {
792     QMenu *menu = createStandardContextMenu();
793     QAction *action;
794 
795     menu->addSeparator();
796 
797     action = menu->addAction(tr("Show Selected"));
798     action->setEnabled(menus_enabled_ && show_selected_enabled_ && textCursor().hasSelection());
799     connect(action, SIGNAL(triggered()), this, SLOT(showSelected()));
800 
801     action = menu->addAction(tr("Show All"));
802     action->setEnabled(menus_enabled_);
803     connect(action, SIGNAL(triggered()), this, SLOT(showAll()));
804 
805     menu->exec(event->globalPos());
806     delete menu;
807 }
808 
809 void ShowPacketBytesTextEdit::showSelected()
810 {
811     QTextCursor cursor = textCursor();
812     int start = cursor.selectionStart();
813     int end = cursor.selectionEnd();
814 
815     emit showSelected(start, end);
816 }
817 
818 void ShowPacketBytesTextEdit::showAll()
819 {
820     emit showSelected(0, -1);
821 }
822