1 /* capture_file_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 "config.h"
11
12 #include "file.h"
13
14 #include <wiretap/wtap.h>
15
16 #include "packet_range_group_box.h"
17 #include "capture_file_dialog.h"
18
19
20 #ifdef Q_OS_WIN
21 #include <windows.h>
22 #include "ui/packet_range.h"
23 #include "ui/win32/file_dlg_win32.h"
24 #else // Q_OS_WIN
25
26 #include <errno.h>
27 #include "wsutil/filesystem.h"
28 #include "wsutil/nstime.h"
29 #include "wsutil/str_util.h"
30 #include "wsutil/utf8_entities.h"
31
32 #include "ui/all_files_wildcard.h"
33
34 #include <QCheckBox>
35 #include <QFileInfo>
36 #include <QGridLayout>
37 #include <QHBoxLayout>
38 #include <QLabel>
39 #include <QLineEdit>
40 #include <QSortFilterProxyModel>
41 #include <QSpacerItem>
42 #include <QVBoxLayout>
43 #endif // ! Q_OS_WIN
44
45 #include <QPushButton>
46 #include <QMessageBox>
47
48 #include "epan/prefs.h"
49 #include <ui/qt/utils/qt_ui_utils.h>
50 #include <wireshark_application.h>
51
CaptureFileDialog(QWidget * parent,capture_file * cf,QString & display_filter)52 CaptureFileDialog::CaptureFileDialog(QWidget *parent, capture_file *cf, QString &display_filter) :
53 WiresharkFileDialog(parent),
54 cap_file_(cf),
55 display_filter_(display_filter),
56 #if !defined(Q_OS_WIN)
57 display_filter_edit_(NULL),
58 default_ft_(-1),
59 save_bt_(NULL),
60 help_topic_(TOPIC_ACTION_NONE)
61 #else
62 file_type_(-1)
63 #endif
64 {
65 switch (prefs.gui_fileopen_style) {
66 case FO_STYLE_LAST_OPENED:
67 /* The user has specified that we should start out in the last directory
68 * we looked in. If we've already opened a file, use its containing
69 * directory, if we could determine it, as the directory, otherwise
70 * use the "last opened" directory saved in the preferences file if
71 * there was one.
72 */
73 setDirectory(wsApp->lastOpenDir());
74 break;
75
76 case FO_STYLE_SPECIFIED:
77 /* The user has specified that we should always start out in a
78 * specified directory; if they've specified that directory,
79 * start out by showing the files in that dir.
80 */
81 if (prefs.gui_fileopen_dir[0] != '\0')
82 setDirectory(prefs.gui_fileopen_dir);
83 break;
84 }
85
86 #if !defined(Q_OS_WIN)
87 // Add extra widgets
88 // https://wiki.qt.io/Qt_project_org_faq#How_can_I_add_widgets_to_my_QFileDialog_instance.3F
89 setOption(QFileDialog::DontUseNativeDialog, true);
90 setOption(QFileDialog::HideNameFilterDetails, true);
91 QGridLayout *fd_grid = qobject_cast<QGridLayout*>(layout());
92 QHBoxLayout *h_box = new QHBoxLayout();
93
94 last_row_ = fd_grid->rowCount();
95
96 fd_grid->addItem(new QSpacerItem(1, 1), last_row_, 0);
97 fd_grid->addLayout(h_box, last_row_, 0, 1, 2);
98 last_row_++;
99
100 // Left and right boxes for controls and preview
101 h_box->addLayout(&left_v_box_);
102 h_box->addLayout(&right_v_box_);
103
104 #else // Q_OS_WIN
105 merge_type_ = 0;
106 #endif // Q_OS_WIN
107 }
108
checkSaveAsWithComments(QWidget * parent,capture_file * cf,int file_type)109 check_savability_t CaptureFileDialog::checkSaveAsWithComments(QWidget *parent, capture_file *cf, int file_type) {
110 guint32 comment_types;
111 bool all_comment_types_supported = true;
112
113 /* What types of comments do we have? */
114 comment_types = cf_comment_types(cf);
115
116 /* Does the file's format support all the comments we have? */
117 if (comment_types & WTAP_COMMENT_PER_SECTION) {
118 if (wtap_file_type_subtype_supports_option(file_type,
119 WTAP_BLOCK_SECTION,
120 OPT_COMMENT) == OPTION_NOT_SUPPORTED)
121 all_comment_types_supported = false;
122 }
123 if (comment_types & WTAP_COMMENT_PER_INTERFACE) {
124 if (wtap_file_type_subtype_supports_option(file_type,
125 WTAP_BLOCK_IF_ID_AND_INFO,
126 OPT_COMMENT) == OPTION_NOT_SUPPORTED)
127 all_comment_types_supported = false;
128 }
129 if (comment_types & WTAP_COMMENT_PER_PACKET) {
130 if (wtap_file_type_subtype_supports_option(file_type,
131 WTAP_BLOCK_PACKET,
132 OPT_COMMENT) == OPTION_NOT_SUPPORTED)
133 all_comment_types_supported = false;
134 }
135 if (all_comment_types_supported) {
136 /* Yes. Let the save happen; we can save all the comments, so
137 there's no need to delete them. */
138 return SAVE;
139 }
140
141 QMessageBox msg_dialog(parent);
142 QPushButton *save_button;
143 QPushButton *discard_button;
144
145 msg_dialog.setIcon(QMessageBox::Question);
146 msg_dialog.setText(tr("This capture file contains comments."));
147 msg_dialog.setStandardButtons(QMessageBox::Cancel);
148
149 /* No. Are there formats in which we can write this file that
150 supports all the comments in this file? */
151 if (wtap_dump_can_write(cf->linktypes, comment_types)) {
152 /* Yes. Offer the user a choice of "Save in a format that
153 supports comments", "Discard comments and save in the
154 format you selected", or "Cancel", meaning "don't bother
155 saving the file at all". */
156 msg_dialog.setInformativeText(tr("The file format you chose doesn't support comments. "
157 "Do you want to save the capture in a format that supports comments "
158 "or discard the comments and save in the format you chose?"));
159 // The predefined roles don't really match the tasks at hand...
160 discard_button = msg_dialog.addButton(tr("Discard comments and save"), QMessageBox::DestructiveRole);
161 save_button = msg_dialog.addButton(tr("Save in another format"), QMessageBox::AcceptRole);
162 msg_dialog.setDefaultButton(save_button);
163 } else {
164 /* No. Offer the user a choice of "Discard comments and
165 save in the format you selected" or "Cancel". */
166 msg_dialog.setInformativeText(tr("No file format in which it can be saved supports comments. "
167 "Do you want to discard the comments and save in the format you chose?"));
168 save_button = NULL;
169 discard_button = msg_dialog.addButton(tr("Discard comments and save"), QMessageBox::DestructiveRole);
170 msg_dialog.setDefaultButton(QMessageBox::Cancel);
171 }
172
173 #if defined(Q_OS_MAC)
174 /*
175 * In macOS, the "default button" is not necessarily the button that
176 * has the input focus; Enter/Return activates the default button, and
177 * the spacebar activates the button that has the input focus, and
178 * they might be different buttons.
179 *
180 * In a "do you want to save" dialog, for example, the "save" button
181 * is the default button, and the "don't save" button has the input
182 * focus, so you can press Enter/Return to save or space not to save
183 * (or Escape to dismiss the dialog).
184 *
185 * In Qt terms, this means "no auto-default", as auto-default makes the
186 * button with the input focus the default button, so that Enter/Return
187 * will activate it.
188 */
189 QList<QAbstractButton *> buttons = msg_dialog.buttons();
190 for (int i = 0; i < buttons.size(); ++i) {
191 QPushButton *button = static_cast<QPushButton *>(buttons.at(i));;
192 button->setAutoDefault(false);
193 }
194
195 /*
196 * It also means that the "don't save" button should be the one
197 * initially given the focus.
198 */
199 discard_button->setFocus();
200 #endif
201
202 msg_dialog.exec();
203 /* According to the Qt doc:
204 * when using QMessageBox with custom buttons, exec() function returns an opaque value.
205 *
206 * Therefore we should use clickedButton() to determine which button was clicked. */
207
208 if (msg_dialog.clickedButton() == save_button) {
209 /* OK, the only other format we support is pcapng. Make that
210 the one and only format in the combo box, and return to
211 let the user continue with the dialog.
212
213 XXX - removing all the formats from the combo box will clear
214 the compressed checkbox; get the current value and restore
215 it.
216
217 XXX - we know pcapng can be compressed; if we ever end up
218 supporting saving comments in a format that *can't* be
219 compressed, such as NetMon format, we must check this. */
220 /* XXX - need a compressed checkbox here! */
221 return SAVE_IN_ANOTHER_FORMAT;
222
223 } else if (msg_dialog.clickedButton() == discard_button) {
224 /* Save without the comments and, if that succeeds, delete the
225 comments. */
226 return SAVE_WITHOUT_COMMENTS;
227 }
228
229 /* Just give up. */
230 return CANCELLED;
231 }
232
233
234 #ifndef Q_OS_WIN
accept()235 void CaptureFileDialog::accept()
236 {
237 //
238 // If this is a dialog for writing files, we want to ensure that
239 // the filename has a valid extension before performing file
240 // existence checks and before closing the dialog.
241 // This isn't necessary for dialogs for reading files; the name
242 // has to exactly match the name of the file you want to open,
243 // and doesn't need to be, and shouldn't be, modified.
244 //
245 // XXX also useful for Windows, but that uses a different dialog...
246 //
247 if (acceptMode() == QFileDialog::AcceptSave) {
248 // HACK: ensure that the filename field does not have the focus,
249 // otherwise selectFile will not change the filename.
250 setFocus();
251 fixFilenameExtension();
252 }
253 WiresharkFileDialog::accept();
254 }
255 #endif // ! Q_OS_WIN
256
257
258 // You have to use open, merge, saveAs, or exportPackets. We should
259 // probably just make each type a subclass.
exec()260 int CaptureFileDialog::exec() {
261 return QDialog::Rejected;
262 }
263
264
265
266 // Windows
267 // We use native file dialogs here, rather than the Qt dialog
268 #ifdef Q_OS_WIN
selectedFileType()269 int CaptureFileDialog::selectedFileType() {
270 return file_type_;
271 }
272
compressionType()273 wtap_compression_type CaptureFileDialog::compressionType() {
274 return compression_type_;
275 }
276
open(QString & file_name,unsigned int & type)277 int CaptureFileDialog::open(QString &file_name, unsigned int &type) {
278 QString title_str = wsApp->windowTitleString(tr("Open Capture File"));
279 GString *fname = g_string_new(file_name.toUtf8().constData());
280 GString *dfilter = g_string_new(display_filter_.toUtf8().constData());
281 gboolean wof_status;
282
283 // XXX Add a widget->HWND routine to qt_ui_utils and use it instead.
284 wof_status = win32_open_file((HWND)parentWidget()->effectiveWinId(), title_str.toStdWString().c_str(), fname, &type, dfilter);
285 file_name = fname->str;
286 display_filter_ = dfilter->str;
287
288 g_string_free(fname, TRUE);
289 g_string_free(dfilter, TRUE);
290
291 return (int) wof_status;
292 }
293
saveAs(QString & file_name,bool must_support_all_comments)294 check_savability_t CaptureFileDialog::saveAs(QString &file_name, bool must_support_all_comments) {
295 QString title_str = wsApp->windowTitleString(tr("Save Capture File As"));
296 GString *fname = g_string_new(file_name.toUtf8().constData());
297 gboolean wsf_status;
298
299 wsf_status = win32_save_as_file((HWND)parentWidget()->effectiveWinId(), title_str.toStdWString().c_str(), cap_file_, fname, &file_type_, &compression_type_, must_support_all_comments);
300 file_name = fname->str;
301
302 g_string_free(fname, TRUE);
303
304 if (wsf_status) {
305 return checkSaveAsWithComments(parentWidget(), cap_file_, file_type_);
306 }
307
308 return CANCELLED;
309 }
310
exportSelectedPackets(QString & file_name,packet_range_t * range,QString selRange)311 check_savability_t CaptureFileDialog::exportSelectedPackets(QString &file_name, packet_range_t *range, QString selRange) {
312 QString title_str = wsApp->windowTitleString(tr("Export Specified Packets"));
313 GString *fname = g_string_new(file_name.toUtf8().constData());
314 gboolean wespf_status;
315
316 if (selRange.length() > 0)
317 {
318 packet_range_convert_selection_str(range, selRange.toUtf8().constData());
319 }
320
321 wespf_status = win32_export_specified_packets_file((HWND)parentWidget()->effectiveWinId(), title_str.toStdWString().c_str(), cap_file_, fname, &file_type_, &compression_type_, range);
322 file_name = fname->str;
323
324 g_string_free(fname, TRUE);
325
326 if (wespf_status) {
327 return checkSaveAsWithComments(parentWidget(), cap_file_, file_type_);
328 }
329
330 return CANCELLED;
331 }
332
merge(QString & file_name)333 int CaptureFileDialog::merge(QString &file_name) {
334 QString title_str = wsApp->windowTitleString(tr("Merge Capture File"));
335 GString *fname = g_string_new(file_name.toUtf8().constData());
336 GString *dfilter = g_string_new(display_filter_.toUtf8().constData());
337 gboolean wmf_status;
338
339
340 wmf_status = win32_merge_file((HWND)parentWidget()->effectiveWinId(), title_str.toStdWString().c_str(), fname, dfilter, &merge_type_);
341 file_name = fname->str;
342 display_filter_ = dfilter->str;
343
344 g_string_free(fname, TRUE);
345 g_string_free(dfilter, TRUE);
346
347 return (int) wmf_status;
348 }
349
mergeType()350 int CaptureFileDialog::mergeType() {
351 return merge_type_;
352 }
353
354 #else // ! Q_OS_WIN
355 // Not Windows
356 // We use the Qt dialogs here
fileExtensionType(int et,bool extension_globs)357 QString CaptureFileDialog::fileExtensionType(int et, bool extension_globs)
358 {
359 QString extension_type_name;
360 QStringList all_wildcards;
361 QStringList no_compression_suffix_wildcards;
362 GSList *extensions_list;
363 GSList *extension;
364
365 extension_type_name = wtap_get_file_extension_type_name(et);
366
367 if (!extension_globs) {
368 return extension_type_name;
369 }
370
371 extensions_list = wtap_get_file_extension_type_extensions(et);
372
373 // Get the list of compression-type extensions.
374 GSList *compression_type_extensions = wtap_get_all_compression_type_extensions_list();
375
376 /* Construct the list of patterns. */
377 for (extension = extensions_list; extension != NULL;
378 extension = g_slist_next(extension)) {
379 QString bare_wc = QString("*.%1").arg((char *)extension->data);
380 all_wildcards << bare_wc;
381
382 // Does this end with a compression suffix?
383 bool ends_with_compression_suffix = false;
384 for (GSList *compression_type_extension = compression_type_extensions;
385 compression_type_extension != NULL;
386 compression_type_extension = g_slist_next(compression_type_extension)) {
387 QString suffix = QString(".") + (char *)compression_type_extension->data;
388 if (bare_wc.endsWith(suffix)) {
389 ends_with_compression_suffix = true;
390 break;
391 }
392 }
393
394 // If it doesn't, add it to the list of wildcards-without-
395 // compression-suffixes.
396 if (!ends_with_compression_suffix)
397 no_compression_suffix_wildcards << bare_wc;
398 }
399 g_slist_free(compression_type_extensions);
400 wtap_free_extensions_list(extensions_list);
401
402 // We set HideNameFilterDetails so that "All Files" and "All Capture
403 // Files" don't show a wildcard list. We want to show the associated
404 // wildcards for individual file types so we add them twice.
405 return QString("%1 (%2) (%3)")
406 .arg(extension_type_name)
407 .arg(no_compression_suffix_wildcards.join(" "))
408 .arg(all_wildcards.join(" "));
409 }
410
411 // Returns " (...)", containing the suffix list suitable for setNameFilters.
412 // All extensions ("pcap", "pcap.gz", etc.) are also returned in "suffixes".
fileType(int ft,QStringList & suffixes)413 QString CaptureFileDialog::fileType(int ft, QStringList &suffixes)
414 {
415 QString filter;
416 GSList *extensions_list;
417
418 filter = " (";
419
420 extensions_list = wtap_get_file_extensions_list(ft, TRUE);
421 if (extensions_list == NULL) {
422 /* This file type doesn't have any particular extension
423 conventionally used for it, so we'll just use a
424 wildcard that matches all file names - even those
425 with no extension, so we don't need to worry about
426 compressed file extensions. */
427 filter += ALL_FILES_WILDCARD;
428 } else {
429 // HACK: at least for Qt 5.10 and before, if the first extension is
430 // empty ("."), it will prevent the default (broken) extension
431 // replacement from being applied in the non-native Save file dialog.
432 filter += '.';
433
434 /* Construct the list of patterns. */
435 for (GSList *extension = extensions_list; extension != NULL;
436 extension = g_slist_next(extension)) {
437 QString suffix((char *)extension->data);
438 filter += " *." + suffix;;
439 suffixes << suffix;
440 }
441 wtap_free_extensions_list(extensions_list);
442 }
443 filter += ')';
444 return filter;
445 }
446
buildFileOpenTypeList()447 QStringList CaptureFileDialog::buildFileOpenTypeList() {
448 QStringList filters;
449 QString filter, sep;
450 GSList *extensions_list;
451 GSList *extension;
452 int et;
453
454 /*
455 * Microsoft's UI guidelines say, of the file filters in open and
456 * save dialogs:
457 *
458 * For meta-filters, remove the file extension list to eliminate
459 * clutter. Examples: "All files," "All pictures," "All music,"
460 * and "All videos."
461 *
462 * On both Windows XP and Windows 7, Wordpad doesn't do that, but
463 * Paint does.
464 *
465 * XXX - on Windows, does Qt do that here? For "All Capture Files",
466 * the filter will be a bit long, so it *really* shouldn't be shown.
467 * What about other platforms?
468 */
469 filters << QString(tr("All Files (" ALL_FILES_WILDCARD ")"));
470
471 /*
472 * Add an "All Capture Files" entry, with all the capture file
473 * extensions we know about.
474 */
475 filter = tr("All Capture Files");
476
477 /*
478 * Construct its list of patterns.
479 */
480 extensions_list = wtap_get_all_capture_file_extensions_list();
481 sep = " (";
482 for (extension = extensions_list; extension != NULL;
483 extension = g_slist_next(extension)) {
484 filter += sep;
485 filter += "*.";
486 filter += (char *)extension->data;
487 sep = " ";
488 }
489 wtap_free_extensions_list(extensions_list);
490 filter += ")";
491 filters << filter;
492
493 /* Include all the file types Wireshark supports. */
494 for (et = 0; et < wtap_get_num_file_type_extensions(); et++) {
495 filters << fileExtensionType(et);
496 }
497
498 return filters;
499 }
500
501 // Replaces or appends an extension based on the current file filter
502 // and compression setting.
503 // Used in dialogs that select a file to write.
fixFilenameExtension()504 void CaptureFileDialog::fixFilenameExtension()
505 {
506 QFileInfo fi(selectedFiles()[0]);
507 QString filename = fi.fileName();
508 if (fi.isDir() || filename.isEmpty()) {
509 // no file selected, or a directory was selected. Ignore.
510 return;
511 }
512
513 QString old_suffix;
514 QString new_suffix(wtap_default_file_extension(selectedFileType()));
515 QStringList valid_extensions = type_suffixes_.value(selectedNameFilter());
516 // Find suffixes such as "pcap" or "pcap.gz" if any
517 if (!fi.suffix().isEmpty()) {
518 QStringList current_suffixes(fi.suffix());
519 int pos = filename.lastIndexOf('.', -2 - current_suffixes.at(0).size());
520 if (pos > 0) {
521 current_suffixes.prepend(filename.right(filename.size() - (pos + 1)));
522 }
523
524 // If the current suffix is valid for the current file type, try to
525 // preserve it. Otherwise use the default file extension (if available).
526 foreach (const QString ¤t_suffix, current_suffixes) {
527 if (valid_extensions.contains(current_suffix)) {
528 old_suffix = current_suffix;
529 new_suffix = current_suffix;
530 break;
531 }
532 }
533 if (old_suffix.isEmpty()) {
534 foreach (const QString ¤t_suffix, current_suffixes) {
535 foreach (const QStringList &suffixes, type_suffixes_.values()) {
536 if (suffixes.contains(current_suffix)) {
537 old_suffix = current_suffix;
538 break;
539 }
540 }
541 if (!old_suffix.isEmpty()) {
542 break;
543 }
544 }
545 }
546 }
547
548 // Fixup the new suffix based on whether we're compressing or not.
549 if (compressionType() == WTAP_UNCOMPRESSED) {
550 // Not compressing; strip off any compression suffix
551 GSList *compression_type_extensions = wtap_get_all_compression_type_extensions_list();
552 for (GSList *compression_type_extension = compression_type_extensions;
553 compression_type_extension != NULL;
554 compression_type_extension = g_slist_next(compression_type_extension)) {
555 QString suffix = QString(".") + (char *)compression_type_extension->data;
556 if (new_suffix.endsWith(suffix)) {
557 //
558 // It ends with this compression suffix; chop it off.
559 //
560 new_suffix.chop(suffix.size());
561 break;
562 }
563 }
564 g_slist_free(compression_type_extensions);
565 } else {
566 // Compressing; append the appropriate compression suffix.
567 QString compressed_file_extension = QString(".") + wtap_compression_type_extension(compressionType());
568 if (valid_extensions.contains(new_suffix + compressed_file_extension)) {
569 new_suffix += compressed_file_extension;
570 }
571 }
572
573 if (!new_suffix.isEmpty() && old_suffix != new_suffix) {
574 filename.chop(old_suffix.size());
575 if (old_suffix.isEmpty()) {
576 filename += '.';
577 }
578 filename += new_suffix;
579 selectFile(filename);
580 }
581 }
582
addPreview(QVBoxLayout & v_box)583 void CaptureFileDialog::addPreview(QVBoxLayout &v_box) {
584 QGridLayout *preview_grid = new QGridLayout();
585 QLabel *lbl;
586
587 preview_labels_.clear();
588 v_box.addLayout(preview_grid);
589
590 preview_grid->setColumnStretch(0, 0);
591 preview_grid->setColumnStretch(1, 10);
592
593 lbl = new QLabel(tr("Format:"));
594 preview_grid->addWidget(lbl, 0, 0);
595 preview_grid->addWidget(&preview_format_, 0, 1);
596 preview_labels_ << lbl << &preview_format_;
597
598 lbl = new QLabel(tr("Size:"));
599 preview_grid->addWidget(lbl, 1, 0);
600 preview_grid->addWidget(&preview_size_, 1, 1);
601 preview_labels_ << lbl << &preview_size_;
602
603 lbl = new QLabel(tr("Start / elapsed:"));
604 preview_grid->addWidget(lbl, 3, 0);
605 preview_grid->addWidget(&preview_first_elapsed_, 3, 1);
606 preview_labels_ << lbl << &preview_first_elapsed_;
607
608 connect(this, SIGNAL(currentChanged(const QString &)), this, SLOT(preview(const QString &)));
609
610 preview("");
611 }
612
addMergeControls(QVBoxLayout & v_box)613 void CaptureFileDialog::addMergeControls(QVBoxLayout &v_box) {
614
615 merge_prepend_.setText(tr("Prepend packets"));
616 merge_prepend_.setToolTip(tr("Insert packets from the selected file before the current file. Packet timestamps will be ignored."));
617 v_box.addWidget(&merge_prepend_, 0, Qt::AlignTop);
618
619 merge_chrono_.setText(tr("Merge chronologically"));
620 merge_chrono_.setToolTip(tr("Insert packets in chronological order."));
621 merge_chrono_.setChecked(true);
622 v_box.addWidget(&merge_chrono_, 0, Qt::AlignTop);
623
624 merge_append_.setText(tr("Append packets"));
625 merge_append_.setToolTip(tr("Insert packets from the selected file after the current file. Packet timestamps will be ignored."));
626 v_box.addWidget(&merge_append_, 0, Qt::AlignTop);
627 }
628
selectedFileType()629 int CaptureFileDialog::selectedFileType() {
630 return type_hash_.value(selectedNameFilter(), WTAP_FILE_TYPE_SUBTYPE_UNKNOWN);
631 }
632
compressionType()633 wtap_compression_type CaptureFileDialog::compressionType() {
634 return compress_.isChecked() ? WTAP_GZIP_COMPRESSED : WTAP_UNCOMPRESSED;
635 }
636
addDisplayFilterEdit()637 void CaptureFileDialog::addDisplayFilterEdit() {
638 QGridLayout *fd_grid = qobject_cast<QGridLayout*>(layout());
639
640 fd_grid->addWidget(new QLabel(tr("Read filter:")), last_row_, 0);
641
642 display_filter_edit_ = new DisplayFilterEdit(this, ReadFilterToApply);
643 display_filter_edit_->setText(display_filter_);
644 fd_grid->addWidget(display_filter_edit_, last_row_, 1);
645 last_row_++;
646 }
647
addFormatTypeSelector(QVBoxLayout & v_box)648 void CaptureFileDialog::addFormatTypeSelector(QVBoxLayout &v_box) {
649 int i;
650 /* Put Auto, as well as pcap and pcapng (which are the first two entries in
651 open_routines), at the top of the file type list. */
652 format_type_.addItem(tr("Automatically detect file type"));
653 for (i = 0; i < 2; i += 1) {
654 format_type_.addItem(open_routines[i].name);
655 }
656 /* Generate a sorted list of the remaining file types. */
657 QStringList routine_names;
658 for ( /* keep using i */ ; open_routines[i].name != NULL; i += 1) {
659 routine_names += QString(open_routines[i].name);
660 }
661 routine_names.sort(Qt::CaseInsensitive);
662 for (i = 0; i < routine_names.size(); i += 1) {
663 format_type_.addItem(routine_names.at(i));
664 }
665
666 v_box.addWidget(&format_type_, 0, Qt::AlignTop);
667 }
668
addGzipControls(QVBoxLayout & v_box)669 void CaptureFileDialog::addGzipControls(QVBoxLayout &v_box) {
670 compress_.setText(tr("Compress with g&zip"));
671 if (cap_file_->compression_type == WTAP_GZIP_COMPRESSED &&
672 wtap_dump_can_compress(default_ft_)) {
673 compress_.setChecked(true);
674 } else {
675 compress_.setChecked(false);
676 }
677 v_box.addWidget(&compress_, 0, Qt::AlignTop);
678 connect(&compress_, &QCheckBox::stateChanged, this, &CaptureFileDialog::fixFilenameExtension);
679
680 }
681
addRangeControls(QVBoxLayout & v_box,packet_range_t * range,QString selRange)682 void CaptureFileDialog::addRangeControls(QVBoxLayout &v_box, packet_range_t *range, QString selRange) {
683 packet_range_group_box_.initRange(range, selRange);
684 v_box.addWidget(&packet_range_group_box_, 0, Qt::AlignTop);
685 }
686
addHelpButton(topic_action_e help_topic)687 QDialogButtonBox *CaptureFileDialog::addHelpButton(topic_action_e help_topic)
688 {
689 // This doesn't appear to be documented anywhere but it seems pretty obvious
690 // and it works.
691 QDialogButtonBox *button_box = findChild<QDialogButtonBox *>();
692
693 help_topic_ = help_topic;
694
695 if (button_box) {
696 button_box->addButton(QDialogButtonBox::Help);
697 connect(button_box, SIGNAL(helpRequested()), this, SLOT(on_buttonBox_helpRequested()));
698 }
699 return button_box;
700 }
701
open(QString & file_name,unsigned int & type)702 int CaptureFileDialog::open(QString &file_name, unsigned int &type) {
703 setWindowTitle(wsApp->windowTitleString(tr("Open Capture File")));
704 setNameFilters(buildFileOpenTypeList());
705 setFileMode(QFileDialog::ExistingFile);
706
707 addFormatTypeSelector(left_v_box_);
708 addDisplayFilterEdit();
709 addPreview(right_v_box_);
710 addHelpButton(HELP_OPEN_DIALOG);
711
712 // Grow the dialog to account for the extra widgets.
713 resize(width(), height() + left_v_box_.minimumSize().height() + display_filter_edit_->minimumSize().height());
714
715 display_filter_.clear();
716
717 if (!file_name.isEmpty()) {
718 selectFile(file_name);
719 }
720
721 if (WiresharkFileDialog::exec() && selectedFiles().length() > 0) {
722 file_name = selectedFiles()[0];
723 type = open_info_name_to_type(qPrintable(format_type_.currentText()));
724 display_filter_.append(display_filter_edit_->text());
725
726 return QDialog::Accepted;
727 } else {
728 return QDialog::Rejected;
729 }
730 }
731
saveAs(QString & file_name,bool must_support_all_comments)732 check_savability_t CaptureFileDialog::saveAs(QString &file_name, bool must_support_all_comments) {
733 setWindowTitle(wsApp->windowTitleString(tr("Save Capture File As")));
734 // XXX There doesn't appear to be a way to use setNameFilters without restricting
735 // what the user can select. We might want to use our own combobox instead and
736 // let the user select anything.
737 setNameFilters(buildFileSaveAsTypeList(must_support_all_comments));
738 setAcceptMode(QFileDialog::AcceptSave);
739 setLabelText(FileType, tr("Save as:"));
740
741 addGzipControls(left_v_box_);
742 addHelpButton(HELP_SAVE_DIALOG);
743
744 // Grow the dialog to account for the extra widgets.
745 resize(width(), height() + left_v_box_.minimumSize().height());
746
747 if (!file_name.isEmpty()) {
748 selectFile(file_name);
749 }
750 connect(this, &QFileDialog::filterSelected, this, &CaptureFileDialog::fixFilenameExtension);
751
752 if (WiresharkFileDialog::exec() && selectedFiles().length() > 0) {
753 int file_type;
754
755 file_name = selectedFiles()[0];
756 file_type = selectedFileType();
757 /* Is the file type bogus? */
758 if (file_type == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) {
759 /* This "should not happen". */
760 QMessageBox msg_dialog;
761
762 msg_dialog.setIcon(QMessageBox::Critical);
763 msg_dialog.setText(tr("Unknown file type returned by save as dialog."));
764 msg_dialog.setInformativeText(tr("Please report this as a Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
765 msg_dialog.exec();
766 return CANCELLED;
767 }
768 return checkSaveAsWithComments(this, cap_file_, file_type);
769 }
770 return CANCELLED;
771 }
772
exportSelectedPackets(QString & file_name,packet_range_t * range,QString selRange)773 check_savability_t CaptureFileDialog::exportSelectedPackets(QString &file_name, packet_range_t *range, QString selRange) {
774 QDialogButtonBox *button_box;
775
776 setWindowTitle(wsApp->windowTitleString(tr("Export Specified Packets")));
777 // XXX See comment in ::saveAs regarding setNameFilters
778 setNameFilters(buildFileSaveAsTypeList(false));
779 setAcceptMode(QFileDialog::AcceptSave);
780 setLabelText(FileType, tr("Export as:"));
781
782 addRangeControls(left_v_box_, range, selRange);
783 addGzipControls(right_v_box_);
784 button_box = addHelpButton(HELP_EXPORT_FILE_DIALOG);
785
786 if (button_box) {
787 save_bt_ = button_box->button(QDialogButtonBox::Save);
788 if (save_bt_) {
789 connect(&packet_range_group_box_, SIGNAL(validityChanged(bool)),
790 save_bt_, SLOT(setEnabled(bool)));
791 }
792 }
793
794 // Grow the dialog to account for the extra widgets.
795 resize(width(), height() + (packet_range_group_box_.height() * 2 / 3));
796
797 if (!file_name.isEmpty()) {
798 selectFile(file_name);
799 }
800 connect(this, &QFileDialog::filterSelected, this, &CaptureFileDialog::fixFilenameExtension);
801
802 if (WiresharkFileDialog::exec() && selectedFiles().length() > 0) {
803 int file_type;
804
805 file_name = selectedFiles()[0];
806 file_type = selectedFileType();
807 /* Is the file type bogus? */
808 if (file_type == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) {
809 /* This "should not happen". */
810 QMessageBox msg_dialog;
811
812 msg_dialog.setIcon(QMessageBox::Critical);
813 msg_dialog.setText(tr("Unknown file type returned by save as dialog."));
814 msg_dialog.setInformativeText(tr("Please report this as a Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
815 msg_dialog.exec();
816 return CANCELLED;
817 }
818 return checkSaveAsWithComments(this, cap_file_, file_type);
819 }
820 return CANCELLED;
821 }
822
merge(QString & file_name)823 int CaptureFileDialog::merge(QString &file_name) {
824 setWindowTitle(wsApp->windowTitleString(tr("Merge Capture File")));
825 setNameFilters(buildFileOpenTypeList());
826 setFileMode(QFileDialog::ExistingFile);
827
828 addDisplayFilterEdit();
829 addMergeControls(left_v_box_);
830 addPreview(right_v_box_);
831 addHelpButton(HELP_MERGE_DIALOG);
832
833 file_name.clear();
834 display_filter_.clear();
835
836 // Grow the dialog to account for the extra widgets.
837 resize(width(), height() + right_v_box_.minimumSize().height() + display_filter_edit_->minimumSize().height());
838
839 if (WiresharkFileDialog::exec() && selectedFiles().length() > 0) {
840 file_name.append(selectedFiles()[0]);
841 display_filter_.append(display_filter_edit_->text());
842
843 return QDialog::Accepted;
844 } else {
845 return QDialog::Rejected;
846 }
847 }
848
buildFileSaveAsTypeList(bool must_support_all_comments)849 QStringList CaptureFileDialog::buildFileSaveAsTypeList(bool must_support_all_comments) {
850 QStringList filters;
851 guint32 required_comment_types;
852 GArray *savable_file_types_subtypes;
853 guint i;
854
855 type_hash_.clear();
856 type_suffixes_.clear();
857
858 /* What types of comments do we have to support? */
859 if (must_support_all_comments)
860 required_comment_types = cf_comment_types(cap_file_); /* all the ones the file has */
861 else
862 required_comment_types = 0; /* none of them */
863
864 /* What types of file can we save this file as? */
865 savable_file_types_subtypes = wtap_get_savable_file_types_subtypes_for_file(cap_file_->cd_t,
866 cap_file_->linktypes,
867 required_comment_types,
868 FT_SORT_BY_DESCRIPTION);
869
870 if (savable_file_types_subtypes != NULL) {
871 int ft;
872 /* OK, we have at least one file type we can save this file as.
873 (If we didn't, we shouldn't have gotten here in the first
874 place.) Add them all to the combo box. */
875 for (i = 0; i < savable_file_types_subtypes->len; i++) {
876 ft = g_array_index(savable_file_types_subtypes, int, i);
877 if (default_ft_ < 1)
878 default_ft_ = ft; /* first file type is the default */
879 QString type_name(wtap_file_type_subtype_description(ft));
880 filters << type_name + fileType(ft, type_suffixes_[type_name]);
881 type_hash_[type_name] = ft;
882 }
883 g_array_free(savable_file_types_subtypes, TRUE);
884 }
885
886 return filters;
887 }
888
mergeType()889 int CaptureFileDialog::mergeType() {
890 if (merge_prepend_.isChecked())
891 return -1;
892 else if (merge_append_.isChecked())
893 return 1;
894
895 return 0;
896 }
897
898 // Slots
899
900
901
902 /* do a preview run on the currently selected capture file */
preview(const QString & path)903 void CaptureFileDialog::preview(const QString & path)
904 {
905 wtap *wth;
906 int err;
907 gchar *err_info;
908 ws_file_preview_stats stats;
909 ws_file_preview_stats_status status;
910 time_t ti_time;
911 struct tm *ti_tm;
912 unsigned int elapsed_time;
913
914 foreach (QLabel *lbl, preview_labels_) {
915 lbl->setEnabled(false);
916 }
917
918 preview_format_.setText(tr(UTF8_EM_DASH));
919 preview_size_.setText(tr(UTF8_EM_DASH));
920 preview_first_elapsed_.setText(tr(UTF8_EM_DASH));
921
922 if (path.length() < 1) {
923 return;
924 }
925
926 if (test_for_directory(path.toUtf8().data()) == EISDIR) {
927 preview_format_.setText(tr("directory"));
928 return;
929 }
930
931 wth = wtap_open_offline(path.toUtf8().data(), WTAP_TYPE_AUTO, &err, &err_info, TRUE);
932 if (wth == NULL) {
933 if (err == WTAP_ERR_FILE_UNKNOWN_FORMAT) {
934 preview_format_.setText(tr("unknown file format"));
935 } else {
936 preview_format_.setText(tr("error opening file"));
937 }
938 return;
939 }
940
941 // Success!
942 foreach (QLabel *lbl, preview_labels_) {
943 lbl->setEnabled(true);
944 }
945
946 // Format
947 preview_format_.setText(QString::fromUtf8(wtap_file_type_subtype_description(wtap_file_type_subtype(wth))));
948
949 // Size
950 gint64 filesize = wtap_file_size(wth, &err);
951 // Finder and Windows Explorer use IEC. What do the various Linux file managers use?
952 QString size_str(gchar_free_to_qstring(format_size(filesize, format_size_unit_bytes|format_size_prefix_iec)));
953
954 status = get_stats_for_preview(wth, &stats, &err, &err_info);
955
956 if (status == PREVIEW_READ_ERROR) {
957 // XXX - give error details?
958 g_free(err_info);
959 preview_size_.setText(tr("%1, error after %Ln data record(s)", "", stats.records)
960 .arg(size_str));
961 return;
962 }
963
964 // Packet count
965 if (status == PREVIEW_TIMED_OUT) {
966 preview_size_.setText(tr("%1, timed out at %Ln data record(s)", "", stats.data_records)
967 .arg(size_str));
968 } else {
969 preview_size_.setText(tr("%1, %Ln data record(s)", "", stats.data_records)
970 .arg(size_str));
971 }
972
973 // First packet + elapsed time
974 QString first_elapsed;
975 if (stats.have_times) {
976 //
977 // We saw at least one record with a time stamp, so we can give
978 // a start time (if we have a mix of records with and without
979 // time stamps, and there were records without time stamps
980 // before the first one with a time stamp, this may be inaccurate).
981 //
982 ti_time = (long)stats.start_time;
983 ti_tm = localtime(&ti_time);
984 first_elapsed = "?";
985 if (ti_tm) {
986 first_elapsed = QString("%1-%2-%3 %4:%5:%6")
987 .arg(ti_tm->tm_year + 1900, 4, 10, QChar('0'))
988 .arg(ti_tm->tm_mon + 1, 2, 10, QChar('0'))
989 .arg(ti_tm->tm_mday, 2, 10, QChar('0'))
990 .arg(ti_tm->tm_hour, 2, 10, QChar('0'))
991 .arg(ti_tm->tm_min, 2, 10, QChar('0'))
992 .arg(ti_tm->tm_sec, 2, 10, QChar('0'));
993 }
994 } else {
995 first_elapsed = tr("unknown");
996 }
997
998 // Elapsed time
999 first_elapsed += " / ";
1000 if (status == PREVIEW_SUCCEEDED && stats.have_times) {
1001 //
1002 // We didn't time out, so we looked at all packets, and we got
1003 // at least one packet with a time stamp, so we can calculate
1004 // an elapsed time from the time stamp of the last packet with
1005 // with a time stamp (if we have a mix of records with and without
1006 // time stamps, and there were records without time stamps after
1007 // the last one with a time stamp, this may be inaccurate).
1008 //
1009 elapsed_time = (unsigned int)(stats.stop_time-stats.start_time);
1010 if (elapsed_time/86400) {
1011 first_elapsed += QString("%1 days ").arg(elapsed_time/86400, 2, 10, QChar('0'));
1012 elapsed_time = elapsed_time % 86400;
1013 }
1014 first_elapsed += QString("%2:%3:%4")
1015 .arg(elapsed_time%86400/3600, 2, 10, QChar('0'))
1016 .arg(elapsed_time%3600/60, 2, 10, QChar('0'))
1017 .arg(elapsed_time%60, 2, 10, QChar('0'));
1018 } else {
1019 first_elapsed += tr("unknown");
1020 }
1021 preview_first_elapsed_.setText(first_elapsed);
1022
1023 wtap_close(wth);
1024 }
1025
on_buttonBox_helpRequested()1026 void CaptureFileDialog::on_buttonBox_helpRequested()
1027 {
1028 if (help_topic_ != TOPIC_ACTION_NONE) wsApp->helpTopicAction(help_topic_);
1029 }
1030
1031 #endif // ! Q_OS_WIN
1032