1 /* export_object_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 "export_object_dialog.h"
11 #include <ui_export_object_dialog.h>
12
13 #include <ui/alert_box.h>
14 #include <wsutil/utf8_entities.h>
15
16 #include "wireshark_application.h"
17 #include "ui/qt/widgets/wireshark_file_dialog.h"
18 #include <ui/qt/widgets/export_objects_view.h>
19 #include <ui/qt/models/export_objects_model.h>
20 #include <ui/qt/utils/qt_ui_utils.h>
21
22 #include <QDialogButtonBox>
23 #include <QMessageBox>
24 #include <QMimeDatabase>
25 #include <QPushButton>
26 #include <QComboBox>
27 #include <QDir>
28 #include <QFile>
29 #include <QFileInfo>
30 #include <QDesktopServices>
31
ExportObjectDialog(QWidget & parent,CaptureFile & cf,register_eo_t * eo)32 ExportObjectDialog::ExportObjectDialog(QWidget &parent, CaptureFile &cf, register_eo_t* eo) :
33 WiresharkDialog(parent, cf),
34 eo_ui_(new Ui::ExportObjectDialog),
35 save_bt_(NULL),
36 save_all_bt_(NULL),
37 model_(eo, this),
38 proxyModel_(this)
39 {
40 QPushButton *close_bt;
41
42 eo_ui_->setupUi(this);
43 setAttribute(Qt::WA_DeleteOnClose, true);
44
45 proxyModel_.setSourceModel(&model_);
46 eo_ui_->objectTree->setModel(&proxyModel_);
47
48 proxyModel_.setFilterFixedString("");
49 proxyModel_.setFilterCaseSensitivity(Qt::CaseInsensitive);
50 proxyModel_.setFilterKeyColumn(-1);
51
52 #if defined(Q_OS_MAC)
53 eo_ui_->progressLabel->setAttribute(Qt::WA_MacSmallSize, true);
54 eo_ui_->progressBar->setAttribute(Qt::WA_MacSmallSize, true);
55 #endif
56
57 connect(&model_, SIGNAL(rowsInserted(QModelIndex,int,int)),
58 this, SLOT(modelDataChanged(QModelIndex, int, int)));
59 connect(&model_, SIGNAL(modelReset()), this, SLOT(modelRowsReset()));
60 connect(eo_ui_->filterLine, &QLineEdit::textChanged, &proxyModel_, &ExportObjectProxyModel::setTextFilterString);
61 connect(eo_ui_->objectTree, &ExportObjectsTreeView::currentIndexChanged, this, &ExportObjectDialog::currentHasChanged);
62
63 save_bt_ = eo_ui_->buttonBox->button(QDialogButtonBox::Save);
64 save_all_bt_ = eo_ui_->buttonBox->button(QDialogButtonBox::SaveAll);
65 close_bt = eo_ui_->buttonBox->button(QDialogButtonBox::Close);
66 if (eo_ui_->buttonBox->button(QDialogButtonBox::Open))
67 {
68 QPushButton * open = eo_ui_->buttonBox->button(QDialogButtonBox::Open);
69 open->setText(tr("Preview"));
70 open->setEnabled(false);
71 }
72
73 contentTypes << tr("All Content-Types");
74 eo_ui_->cmbContentType->addItems(contentTypes);
75
76 setWindowTitle(wsApp->windowTitleString(QStringList() << tr("Export") << tr("%1 object list").arg(proto_get_protocol_short_name(find_protocol_by_id(get_eo_proto_id(eo))))));
77
78 if (save_bt_) save_bt_->setEnabled(false);
79 if (save_all_bt_) save_all_bt_->setEnabled(false);
80 if (close_bt) close_bt->setDefault(true);
81
82 connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
83 this, SLOT(captureEvent(CaptureEvent)));
84 }
85
~ExportObjectDialog()86 ExportObjectDialog::~ExportObjectDialog()
87 {
88 delete eo_ui_;
89 model_.removeTap();
90 removeTapListeners();
91 }
92
currentHasChanged(QModelIndex current)93 void ExportObjectDialog::currentHasChanged(QModelIndex current)
94 {
95 if (current.isValid())
96 {
97 QModelIndex sibl = current.sibling(current.row(), ExportObjectModel::colPacket);
98 if (eo_ui_->buttonBox->button(QDialogButtonBox::Open))
99 {
100 QString mime_type = sibl.sibling(current.row(), ExportObjectModel::colContent).data().toString();
101 eo_ui_->buttonBox->button(QDialogButtonBox::Open)->setEnabled(mimeTypeIsPreviewable(mime_type));
102 }
103 wsApp->gotoFrame(sibl.data().toInt());
104 }
105 }
106
mimeTypeIsPreviewable(QString mime_type)107 bool ExportObjectDialog::mimeTypeIsPreviewable(QString mime_type)
108 {
109 // XXX This excludes everything except HTTP. Maybe that's a good thing?
110 // Take care when adding to this, e.g. text/html or image/svg might contain JavaScript.
111 QStringList previewable_mime_types = QStringList()
112 << "text/plain"
113 << "image/gif" << "image/jpeg" << "image/png";
114
115 if (previewable_mime_types.contains(mime_type)) {
116 return true;
117 }
118 return false;
119 }
120
modelDataChanged(const QModelIndex &,int from,int to)121 void ExportObjectDialog::modelDataChanged(const QModelIndex&, int from, int to)
122 {
123 bool contentTypes_changed = false;
124 bool enabled = (model_.rowCount() > 0);
125 if (save_bt_) save_bt_->setEnabled(enabled);
126 if (save_all_bt_) save_all_bt_->setEnabled(enabled);
127
128 for (int row = from; row <= to; row++)
129 {
130 QModelIndex idx = model_.index(row, ExportObjectModel::colContent);
131 if (idx.isValid())
132 {
133 QString dataType = idx.data().toString();
134 if (dataType.length() > 0 && ! contentTypes.contains(dataType))
135 {
136 contentTypes << dataType;
137 contentTypes_changed = true;
138 }
139 }
140 }
141 if (contentTypes_changed) {
142 contentTypes.sort(Qt::CaseInsensitive);
143 QString selType = eo_ui_->cmbContentType->currentText();
144 eo_ui_->cmbContentType->clear();
145 eo_ui_->cmbContentType->addItem(tr("All Content-Types"));
146 eo_ui_->cmbContentType->addItems(contentTypes);
147 if (contentTypes.contains(selType) )
148 eo_ui_->cmbContentType->setCurrentText(selType);
149 }
150 }
151
modelRowsReset()152 void ExportObjectDialog::modelRowsReset()
153 {
154 contentTypes.clear();
155 eo_ui_->cmbContentType->clear();
156 eo_ui_->cmbContentType->addItem(tr("All Content-Types"));
157
158 if (save_bt_) save_bt_->setEnabled(false);
159 if (save_all_bt_) save_all_bt_->setEnabled(false);
160 }
161
show()162 void ExportObjectDialog::show()
163 {
164 /* Data will be gathered via a tap callback */
165 if (!registerTapListener(model_.getTapListenerName(), model_.getTapData(), NULL, 0,
166 ExportObjectModel::resetTap,
167 model_.getTapPacketFunc(),
168 NULL)) {
169 return;
170 }
171
172 QDialog::show();
173 cap_file_.retapPackets();
174 eo_ui_->progressFrame->hide();
175 for (int i = 0; i < eo_ui_->objectTree->model()->columnCount(); i++)
176 eo_ui_->objectTree->resizeColumnToContents(i);
177
178 eo_ui_->objectTree->sortByColumn(ExportObjectModel::colPacket, Qt::AscendingOrder);
179 }
180
keyPressEvent(QKeyEvent * evt)181 void ExportObjectDialog::keyPressEvent(QKeyEvent *evt)
182 {
183 if(evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return)
184 return;
185 QDialog::keyPressEvent(evt);
186 }
187
accept()188 void ExportObjectDialog::accept()
189 {
190 // Don't close the dialog.
191 }
192
captureEvent(CaptureEvent e)193 void ExportObjectDialog::captureEvent(CaptureEvent e)
194 {
195 if ((e.captureContext() == CaptureEvent::File) &&
196 (e.eventType() == CaptureEvent::Closing))
197 {
198 close();
199 }
200 }
201
on_buttonBox_helpRequested()202 void ExportObjectDialog::on_buttonBox_helpRequested()
203 {
204 wsApp->helpTopicAction(HELP_EXPORT_OBJECT_LIST);
205 }
206
on_buttonBox_clicked(QAbstractButton * button)207 void ExportObjectDialog::on_buttonBox_clicked(QAbstractButton *button)
208 {
209 switch (eo_ui_->buttonBox->standardButton(button)) {
210 case QDialogButtonBox::Save:
211 saveCurrentEntry();
212 break;
213 case QDialogButtonBox::SaveAll:
214 saveAllEntries();
215 break;
216 case QDialogButtonBox::Open:
217 {
218 QString temp;
219 saveCurrentEntry(&temp);
220
221 if (temp.length() > 0) {
222 QMimeDatabase mime_db;
223 QMimeType mime_type = mime_db.mimeTypeForFile(temp, QMimeDatabase::MatchContent);
224 if (mimeTypeIsPreviewable(mime_type.name())) {
225 QDesktopServices::openUrl(QUrl(QString("file:///").append(temp), QUrl::TolerantMode));
226 } else {
227 desktop_show_in_folder(temp);
228 }
229
230 }
231 break;
232 }
233 default: // Help, Cancel
234 break;
235 }
236 }
237
on_cmbContentType_currentIndexChanged(int index)238 void ExportObjectDialog::on_cmbContentType_currentIndexChanged(int index)
239 {
240 QString filterString = index <= 0 ? "" : eo_ui_->cmbContentType->currentText();
241 proxyModel_.setContentFilterString(filterString);
242
243 }
244
saveCurrentEntry(QString * tempFile)245 void ExportObjectDialog::saveCurrentEntry(QString *tempFile)
246 {
247 QDir path(wsApp->lastOpenDir());
248
249 QModelIndex proxyIndex = eo_ui_->objectTree->currentIndex();
250 if (!proxyIndex.isValid())
251 return;
252
253 QModelIndex current = proxyModel_.mapToSource(proxyIndex);
254 if (!current.isValid())
255 return;
256
257 QString entry_filename = current.sibling(current.row(), ExportObjectModel::colFilename).data().toString();
258 if (entry_filename.isEmpty())
259 return;
260
261 QString file_name;
262 if (!tempFile)
263 {
264 GString *safe_filename = eo_massage_str(entry_filename.toUtf8().constData(), EXPORT_OBJECT_MAXFILELEN, 0);
265 file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Object As…")),
266 safe_filename->str);
267 g_string_free(safe_filename, TRUE);
268 } else {
269 QString path = QDir::tempPath().append("/").append(entry_filename);
270 /* This means, the system must remove the file! */
271 file_name = path;
272 if (QFileInfo::exists(path))
273 QFile::remove(path);
274 *tempFile = path;
275 }
276
277 model_.saveEntry(current, file_name);
278 }
279
saveAllEntries()280 void ExportObjectDialog::saveAllEntries()
281 {
282 QDir save_in_dir(wsApp->lastOpenDir());
283 QString save_in_path;
284
285 //
286 // We want the user to be able to specify a directory in which
287 // to drop files for all the objects, not a file name.
288 //
289 // XXX - what we *really* want is something that asks the user
290 // for an existing directory *but* lets them create a new
291 // directory in the process. That's what we get on macOS,
292 // as the native dialog is used, and it supports that; does
293 // that also work on Windows and with Qt's own dialog?
294 //
295 save_in_path = WiresharkFileDialog::getExistingDirectory(this, wsApp->windowTitleString(tr("Save All Objects In…")),
296 save_in_dir.canonicalPath(),
297 QFileDialog::ShowDirsOnly);
298
299 if (save_in_path.length() < 1)
300 return;
301
302 model_.saveAllEntries(save_in_path);
303 }
304