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
256 lines
8.4 KiB
C++
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);
|
|
}
|