wireshark/ui/qt/export_dissection_dialog.cpp
John Thacker 070a394402 Qt: Enable JSON no duplicate keys options from the GUI
Enable access to the JSON "no-duplicate-keys" option available in
tshark in the GUI as well. Continue to default to it off, same as
in tshark.

As part of this, have the Export Dissections dialog packet format
group box be a stacked widget, allowing different per format
options. Note the current options are only valid for text (and
PostScript, but Export Dissections doesn't do that.) This could allow
support for CSV options, e.g. see #14260.

Have the different format group boxes be responsible for checking
validity and setting the print arguments, to reduce duplicate code
between Export Dissections and Print, and keep those widgets from
having to understand details of the group boxes.

Note that the current "no duplicate keys" format has limitations of
its own, because it doesn't preserve order in cases where there
are multiple siblings of the same field at the same tree level but
not consecutive (i.e., with other fields between them.) They will
be placed together. A different strategy, that involves even more
use of arrays, would be necessary to preserve order. (See also
issue #12958.)

Ping #13904, #19295, #19329
2025-03-05 07:16:13 +01:00

256 lines
8.4 KiB
C++

/* export_dissection_dialog.cpp
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "export_dissection_dialog.h"
#include "ui/alert_box.h"
#include "ui/help_url.h"
#include "ui/util.h"
#include <epan/print.h>
#include <wsutil/filesystem.h>
#include <ui/qt/utils/qt_ui_utils.h>
#include <QStackedWidget>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QPushButton>
#include "main_application.h"
static const QStringList export_extensions = QStringList()
<< ""
<< "txt"
<< ""
<< "csv"
<< "psml"
<< "pdml"
<< "c"
<< "json";
ExportDissectionDialog::ExportDissectionDialog(QWidget *parent, capture_file *cap_file, export_type_e export_type, QString selRange):
WiresharkFileDialog(parent),
export_type_(export_type),
cap_file_(cap_file)
, save_bt_(NULL)
{
setWindowTitle(mainApp->windowTitleString(tr("Export Packet Dissections")));
setDirectory(mainApp->openDialogInitialDir());
setOption(QFileDialog::DontUseNativeDialog, true);
QDialogButtonBox *button_box = findChild<QDialogButtonBox *>();
QGridLayout *fd_grid = qobject_cast<QGridLayout*>(layout());
QHBoxLayout *h_box = new QHBoxLayout();
QStringList name_filters;
int last_row;
setAcceptMode(QFileDialog::AcceptSave);
setLabelText(FileType, tr("Export As:"));
packet_format_stack_ = new PacketFormatStack(this);
// export_type_map_keys() sorts alphabetically. We don't want that.
name_filters
<< tr("Plain text (*.txt)")
<< tr("Comma Separated Values - summary (*.csv)")
<< tr("PSML - summary (*.psml, *.xml)")
<< tr("PDML - details (*.pdml, *.xml)")
<< tr("JSON (*.json)")
<< tr("C Arrays - bytes (*.c, *.h)");
export_type_map_[name_filters[0]] = export_type_text;
export_type_map_[name_filters[1]] = export_type_csv;
export_type_map_[name_filters[2]] = export_type_psml;
export_type_map_[name_filters[3]] = export_type_pdml;
export_type_map_[name_filters[4]] = export_type_json;
export_type_map_[name_filters[5]] = export_type_carrays;
setNameFilters(name_filters);
selectNameFilter(export_type_map_.key(export_type));
exportTypeChanged(export_type_map_.key(export_type));
last_row = fd_grid->rowCount();
fd_grid->addItem(new QSpacerItem(1, 1), last_row, 0);
fd_grid->addLayout(h_box, last_row, 1);
print_args_.file = NULL;
/* Init the export range */
packet_range_init(&print_args_.range, cap_file_);
/* Default to displayed packets */
print_args_.range.process_filtered = true;
packet_range_group_box_.initRange(&print_args_.range, selRange);
h_box->addWidget(&packet_range_group_box_);
h_box->addWidget(packet_format_stack_, 0, Qt::AlignTop);
if (button_box) {
button_box->addButton(QDialogButtonBox::Help);
connect(button_box, &QDialogButtonBox::helpRequested, this, &ExportDissectionDialog::on_buttonBox_helpRequested);
save_bt_ = button_box->button(QDialogButtonBox::Save);
}
if (save_bt_) {
connect(&packet_range_group_box_, &PacketRangeGroupBox::validityChanged,
this, &ExportDissectionDialog::checkValidity);
connect(packet_format_stack_, &PacketFormatStack::formatChanged,
this, &ExportDissectionDialog::checkValidity);
save_bt_->installEventFilter(this);
}
connect(this, &ExportDissectionDialog::filterSelected, this, &ExportDissectionDialog::exportTypeChanged);
// Grow the dialog to account for the extra widgets.
resize(width(), height() + (packet_range_group_box_.height() * 2 / 3));
connect(this, &ExportDissectionDialog::filesSelected, this, &ExportDissectionDialog::dialogAccepted);
}
ExportDissectionDialog::~ExportDissectionDialog()
{
g_free(print_args_.file);
packet_range_cleanup(&print_args_.range);
}
void ExportDissectionDialog::show()
{
if (cap_file_) {
WiresharkFileDialog::show();
}
}
void ExportDissectionDialog::dialogAccepted(const QStringList &selected)
{
if (selected.length() > 0) {
/* writing might take a while, so hide ourselves so the user
* can't click on anything here (this dialog will be closed
* and deleted once this function is done), but can access
* the ProgressDialog in the main window to cancel the export.
*/
hide();
cf_print_status_t status;
QString file_name = QDir::toNativeSeparators(selected[0]);
/* Fill in our print (and export) args */
print_args_.file = qstring_strdup(file_name);
print_args_.format = PR_FMT_TEXT;
print_args_.to_file = true;
print_args_.cmd = NULL;
print_args_.print_summary = true;
print_args_.print_col_headings = true;
print_args_.print_dissections = print_dissections_as_displayed;
print_args_.print_hex = false;
print_args_.print_formfeed = false;
print_args_.no_duplicate_keys = false;
packet_format_stack_->updatePrintArgs(print_args_);
switch (export_type_) {
case export_type_text: /* Text */
print_args_.stream = print_stream_text_new(true, print_args_.file);
if (print_args_.stream == NULL) {
open_failure_alert_box(print_args_.file, errno, true);
return;
}
status = cf_print_packets(cap_file_, &print_args_, true);
break;
case export_type_csv: /* CSV */
status = cf_write_csv_packets(cap_file_, &print_args_);
break;
case export_type_carrays: /* C Arrays */
status = cf_write_carrays_packets(cap_file_, &print_args_);
break;
case export_type_psml: /* PSML */
status = cf_write_psml_packets(cap_file_, &print_args_);
break;
case export_type_pdml: /* PDML */
status = cf_write_pdml_packets(cap_file_, &print_args_);
break;
case export_type_json: /* JSON */
status = cf_write_json_packets(cap_file_, &print_args_);
break;
default:
return;
}
switch (status) {
case CF_PRINT_OK:
break;
case CF_PRINT_OPEN_ERROR:
open_failure_alert_box(print_args_.file, errno, true);
break;
case CF_PRINT_WRITE_ERROR:
write_failure_alert_box(print_args_.file, errno);
break;
}
char *dirname;
/* Save the directory name for future file dialogs. */
dirname = get_dirname(print_args_.file); /* Overwrites file_name data */
set_last_open_dir(dirname);
}
}
void ExportDissectionDialog::exportTypeChanged(QString name_filter)
{
export_type_ = export_type_map_.value(name_filter);
packet_format_stack_->setExportType(export_type_);
checkValidity();
setDefaultSuffix(export_extensions[export_type_]);
}
bool ExportDissectionDialog::isValid()
{
bool valid = true;
if (!packet_range_group_box_.isValid()) valid = false;
if (!packet_format_stack_->isValid()) valid = false;
return valid;
}
void ExportDissectionDialog::checkValidity()
{
if (!save_bt_) return;
save_bt_->setEnabled(isValid());
}
void ExportDissectionDialog::on_buttonBox_helpRequested()
{
mainApp->helpTopicAction(HELP_EXPORT_FILE_DIALOG);
}
bool ExportDissectionDialog::eventFilter(QObject *obj, QEvent *event)
{
// The QFileDialogPrivate will enable the Ok (Open/Save) button when
// anything is typed or selected. We can't catch that beforehand, so
// watch for the enable status change and re-disable it if the
// group boxes are invalid.
// We could do extra work (here and elsewhere) not to disable the button
// if what's selected in the dialog is a directory, but even with save_bt_
// disabled clicking on the directory still opens it.
if (event->type() == QEvent::EnabledChange) {
QPushButton *button = qobject_cast<QPushButton *>(obj);
if (button && button == save_bt_) {
// The button is already changed by the time we get this event.
if (button->isEnabled() && !isValid()) {
button->setEnabled(false);
return true;
}
}
}
return QObject::eventFilter(obj, event);
}