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