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 &current_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 &current_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