1 /* profile_model.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 <errno.h>
13
14 #include "glib.h"
15 #include "ui/profile.h"
16 #include "ui/recent.h"
17 #include "wsutil/filesystem.h"
18 #include "epan/prefs.h"
19
20 #include <ui/qt/models/profile_model.h>
21
22 #include <ui/qt/utils/color_utils.h>
23 #include <ui/qt/utils/qt_ui_utils.h>
24 #include <ui/qt/utils/wireshark_zip_helper.h>
25
26 #include <QDir>
27 #include <QFont>
28 #include <QTemporaryDir>
29
30 Q_LOGGING_CATEGORY(profileLogger, "wireshark.profiles")
31
ProfileSortModel(QObject * parent)32 ProfileSortModel::ProfileSortModel(QObject * parent):
33 QSortFilterProxyModel (parent),
34 ft_(ProfileSortModel::AllProfiles),
35 ftext_(QString())
36 {}
37
lessThan(const QModelIndex & source_left,const QModelIndex & source_right) const38 bool ProfileSortModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
39 {
40 QModelIndex left = source_left;
41 if (source_left.column() != ProfileModel::COL_NAME)
42 left = source_left.sibling(source_left.row(), ProfileModel::COL_NAME);
43
44 QModelIndex right = source_right;
45 if (source_right.column() != ProfileModel::COL_NAME)
46 right = source_right.sibling(source_right.row(), ProfileModel::COL_NAME);
47
48 bool igL = left.data(ProfileModel::DATA_IS_GLOBAL).toBool();
49 bool igR = right.data(ProfileModel::DATA_IS_GLOBAL).toBool();
50
51 if (left.data(ProfileModel::DATA_STATUS).toInt() == PROF_STAT_DEFAULT)
52 igL = true;
53 if (right.data(ProfileModel::DATA_STATUS).toInt() == PROF_STAT_DEFAULT)
54 igR = true;
55
56 if (igL && ! igR)
57 return true;
58 else if (! igL && igR)
59 return false;
60 else if (igL && igR)
61 {
62 if (left.data(ProfileModel::DATA_STATUS) == PROF_STAT_DEFAULT)
63 return true;
64 }
65
66 if (left.data().toString().compare(right.data().toString()) <= 0)
67 return true;
68
69 return false;
70 }
71
setFilterType(FilterType ft)72 void ProfileSortModel::setFilterType(FilterType ft)
73 {
74 ft_ = ft;
75 invalidateFilter();
76 }
77
setFilterString(QString txt)78 void ProfileSortModel::setFilterString(QString txt)
79 {
80 ftext_ = ! txt.isEmpty() ? txt : "";
81 invalidateFilter();
82 }
83
filterTypes()84 QStringList ProfileSortModel::filterTypes()
85 {
86 QMap<int, QString> filter_types_;
87 filter_types_.insert(ProfileSortModel::AllProfiles, tr("All profiles"));
88 filter_types_.insert(ProfileSortModel::PersonalProfiles, tr("Personal profiles"));
89 filter_types_.insert(ProfileSortModel::GlobalProfiles, tr("Global profiles"));
90
91 return filter_types_.values();
92 }
93
filterAcceptsRow(int source_row,const QModelIndex &) const94 bool ProfileSortModel::filterAcceptsRow(int source_row, const QModelIndex &) const
95 {
96 bool accept = true;
97 QModelIndex idx = sourceModel()->index(source_row, ProfileModel::COL_NAME);
98
99 if (ft_ != ProfileSortModel::AllProfiles)
100 {
101 bool gl = idx.data(ProfileModel::DATA_IS_GLOBAL).toBool();
102 if (ft_ == ProfileSortModel::PersonalProfiles && gl)
103 accept = false;
104 else if (ft_ == ProfileSortModel::GlobalProfiles && ! gl)
105 accept = false;
106 }
107
108 if (ftext_.length() > 0)
109 {
110 QString name = idx.data().toString();
111 if (! name.contains(ftext_, Qt::CaseInsensitive))
112 accept = false;
113 }
114
115 return accept;
116 }
117
ProfileModel(QObject * parent)118 ProfileModel::ProfileModel(QObject * parent) :
119 QAbstractTableModel(parent)
120 {
121 /* Store preset profile name */
122 set_profile_ = get_profile_name();
123
124 reset_default_ = false;
125 profiles_imported_ = false;
126
127 last_set_row_ = 0;
128
129 /* Set filenames for profiles */
130 GList *files, *file;
131 files = g_hash_table_get_keys(const_cast<GHashTable *>(allowed_profile_filenames()));
132 file = g_list_first(files);
133 while (file) {
134 profile_files_ << static_cast<char *>(file->data);
135 file = gxx_list_next(file);
136 }
137 g_list_free(files);
138
139 loadProfiles();
140 }
141
loadProfiles()142 void ProfileModel::loadProfiles()
143 {
144 emit beginResetModel();
145
146 bool refresh = profiles_.count() > 0;
147
148 if (refresh)
149 profiles_.clear();
150 else
151 init_profile_list();
152
153 GList *fl_entry = edited_profile_list();
154 while (fl_entry && fl_entry->data)
155 {
156 profiles_ << reinterpret_cast<profile_def *>(fl_entry->data);
157 fl_entry = gxx_list_next(fl_entry);
158 }
159
160 emit endResetModel();
161 }
162
entry(profile_def * ref) const163 GList * ProfileModel::entry(profile_def *ref) const
164 {
165 GList *fl_entry = edited_profile_list();
166 while (fl_entry && fl_entry->data)
167 {
168 profile_def *profile = reinterpret_cast<profile_def *>(fl_entry->data);
169 if (QString(ref->name).compare(profile->name) == 0 &&
170 QString(ref->reference).compare(profile->reference) == 0 &&
171 ref->is_global == profile->is_global &&
172 ref->status == profile->status)
173 {
174 return fl_entry;
175 }
176
177 fl_entry = gxx_list_next(fl_entry);
178 }
179
180 return Q_NULLPTR;
181 }
182
at(int row) const183 GList *ProfileModel::at(int row) const
184 {
185 if (row < 0 || row >= profiles_.count())
186 return Q_NULLPTR;
187
188 profile_def * prof = profiles_.at(row);
189 return entry(prof);
190 }
191
changesPending() const192 bool ProfileModel::changesPending() const
193 {
194 if (reset_default_)
195 return true;
196
197 if (g_list_length(edited_profile_list()) != g_list_length(current_profile_list()))
198 return true;
199
200 bool pending = false;
201 GList *fl_entry = edited_profile_list();
202 while (fl_entry && fl_entry->data && ! pending) {
203 profile_def *profile = reinterpret_cast<profile_def *>(fl_entry->data);
204 pending = (profile->status == PROF_STAT_NEW || profile->status == PROF_STAT_CHANGED || profile->status == PROF_STAT_COPY);
205 fl_entry = gxx_list_next(fl_entry);
206 }
207
208 return pending;
209 }
210
importPending() const211 bool ProfileModel::importPending() const
212 {
213 return profiles_imported_;
214 }
215
userProfilesExist() const216 bool ProfileModel::userProfilesExist() const
217 {
218 bool user_exists = false;
219 for (int cnt = 0; cnt < rowCount() && ! user_exists; cnt++)
220 {
221 QModelIndex idx = index(cnt, ProfileModel::COL_NAME);
222 if (! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! idx.data(ProfileModel::DATA_IS_DEFAULT).toBool())
223 user_exists = true;
224 }
225
226 return user_exists;
227 }
228
rowCount(const QModelIndex &) const229 int ProfileModel::rowCount(const QModelIndex &) const
230 {
231 return profiles_.count();
232 }
233
columnCount(const QModelIndex &) const234 int ProfileModel::columnCount(const QModelIndex &) const
235 {
236 return static_cast<int>(_LAST_ENTRY);
237 }
238
guard(const QModelIndex & index) const239 profile_def * ProfileModel::guard(const QModelIndex &index) const
240 {
241 if (! index.isValid())
242 return Q_NULLPTR;
243
244 return guard(index.row());
245 }
246
guard(int row) const247 profile_def * ProfileModel::guard(int row) const
248 {
249 if (row < 0 || profiles_.count() <= row)
250 return Q_NULLPTR;
251
252 if (! edited_profile_list())
253 {
254 static_cast<QList<profile_def *>>(profiles_).clear();
255 return Q_NULLPTR;
256 }
257
258 return profiles_.value(row, Q_NULLPTR);
259 }
260
dataDisplay(const QModelIndex & index) const261 QVariant ProfileModel::dataDisplay(const QModelIndex &index) const
262 {
263 profile_def * prof = guard(index);
264 if (! prof)
265 return QVariant();
266
267 switch (index.column())
268 {
269 case COL_NAME:
270 return QString(prof->name);
271 case COL_TYPE:
272 if (prof->status == PROF_STAT_DEFAULT)
273 return tr("Default");
274 else if (prof->is_global)
275 return tr("Global");
276 else
277 return tr("Personal");
278 default:
279 break;
280 }
281
282 return QVariant();
283 }
284
dataFontRole(const QModelIndex & index) const285 QVariant ProfileModel::dataFontRole(const QModelIndex &index) const
286 {
287 if (! index.isValid() || profiles_.count() <= index.row())
288 return QVariant();
289
290 profile_def * prof = guard(index.row());
291 if (! prof)
292 return QVariant();
293
294 QFont font;
295
296 if (prof->is_global)
297 font.setItalic(true);
298
299 if (! prof->is_global && ! checkDuplicate(index))
300 {
301 if ((set_profile_.compare(prof->name) == 0 && prof->status == PROF_STAT_EXISTS) ||
302 (set_profile_.compare(prof->reference) == 0 && prof->status == PROF_STAT_CHANGED) )
303 font.setBold(true);
304 }
305
306 if (prof->status == PROF_STAT_DEFAULT && reset_default_)
307 font.setStrikeOut(true);
308
309 return font;
310 }
311
checkIfDeleted(int row) const312 bool ProfileModel::checkIfDeleted(int row) const
313 {
314 QModelIndex idx = index(row, ProfileModel::COL_NAME);
315 return checkIfDeleted(idx);
316 }
317
checkIfDeleted(const QModelIndex & index) const318 bool ProfileModel::checkIfDeleted(const QModelIndex &index) const
319 {
320 profile_def * prof = guard(index);
321 if (! prof)
322 return false;
323
324 QStringList deletedNames;
325
326 GList * current = current_profile_list();
327
328 /* search the current list as long as we have not found anything */
329 while (current)
330 {
331 bool found = false;
332 GList * edited = edited_profile_list();
333 profile_def * profcurr = static_cast<profile_def *>(current->data);
334
335 if (! profcurr->is_global && profcurr->status != PROF_STAT_DEFAULT)
336 {
337 while (edited && ! found)
338 {
339 profile_def * profed = static_cast<profile_def *>(edited->data);
340 if (! profed->is_global && profed->status != PROF_STAT_DEFAULT)
341 {
342 if (g_strcmp0(profcurr->name, profed->name) == 0 || g_strcmp0(profcurr->name, profed->reference) == 0)
343 {
344 if (profed->status == profcurr->status && prof->status != PROF_STAT_NEW && prof->status != PROF_STAT_COPY)
345 found = true;
346 }
347 }
348
349 edited = gxx_list_next(edited);
350 }
351
352 /* profile has been deleted, check if it has the name we ask for */
353 if (! found)
354 deletedNames << profcurr->name;
355 }
356
357 if (profcurr->is_global && deletedNames.contains(profcurr->name))
358 deletedNames.removeAll(profcurr->name);
359
360 current = gxx_list_next(current);
361 }
362
363 if (deletedNames.contains(prof->name))
364 return true;
365
366 return false;
367 }
368
checkInvalid(const QModelIndex & index) const369 bool ProfileModel::checkInvalid(const QModelIndex &index) const
370 {
371 profile_def * prof = guard(index);
372 if (! prof)
373 return false;
374
375 int ref = this->findAsReference(prof->name);
376 if (ref == index.row())
377 return false;
378
379 profile_def * pg = guard(ref);
380 if (pg && pg->status == PROF_STAT_CHANGED && g_strcmp0(pg->name, pg->reference) != 0 && ! prof->is_global)
381 return true;
382
383 return false;
384 }
385
checkDuplicate(const QModelIndex & index,bool isOriginalToDuplicate) const386 bool ProfileModel::checkDuplicate(const QModelIndex &index, bool isOriginalToDuplicate) const
387 {
388 profile_def * prof = guard(index);
389 if (! prof || (! isOriginalToDuplicate && prof->status == PROF_STAT_EXISTS) )
390 return false;
391
392 QList<int> rows = this->findAllByNameAndVisibility(prof->name, prof->is_global, false);
393 int found = 0;
394 profile_def * check = Q_NULLPTR;
395 for (int cnt = 0; cnt < rows.count(); cnt++)
396 {
397 int row = rows.at(cnt);
398
399 if (row == index.row())
400 continue;
401
402 check = guard(row);
403 if (! check || (isOriginalToDuplicate && check->status == PROF_STAT_EXISTS) )
404 continue;
405
406 found++;
407 }
408
409 if (found > 0)
410 return true;
411 return false;
412 }
413
dataBackgroundRole(const QModelIndex & index) const414 QVariant ProfileModel::dataBackgroundRole(const QModelIndex &index) const
415 {
416 if (! index.isValid() || profiles_.count() <= index.row())
417 return QVariant();
418
419 profile_def * prof = guard(index.row());
420 if (! prof)
421 return QVariant();
422
423 if (prof->status == PROF_STAT_DEFAULT && reset_default_)
424 return ColorUtils::fromColorT(&prefs.gui_text_deprecated);
425
426 if (prof->status != PROF_STAT_DEFAULT && ! prof->is_global)
427 {
428 /* Highlights errorneous line */
429 if (checkInvalid(index) || checkIfDeleted(index) || checkDuplicate(index) || ! checkNameValidity(prof->name))
430 return ColorUtils::fromColorT(&prefs.gui_text_invalid);
431
432 /* Highlights line, which has been duplicated by another index */
433 if (checkDuplicate(index, true))
434 return ColorUtils::fromColorT(&prefs.gui_text_valid);
435 }
436
437 return QVariant();
438 }
439
dataToolTipRole(const QModelIndex & idx) const440 QVariant ProfileModel::dataToolTipRole(const QModelIndex &idx) const
441 {
442 if (! idx.isValid() || profiles_.count() <= idx.row())
443 return QVariant();
444
445 profile_def * prof = guard(idx.row());
446 if (! prof)
447 return QVariant();
448
449 if (prof->is_global)
450 return tr("This is a system provided profile");
451 else
452 return dataPath(idx);
453 }
454
dataPath(const QModelIndex & index) const455 QVariant ProfileModel::dataPath(const QModelIndex &index) const
456 {
457 if (! index.isValid() || profiles_.count() <= index.row())
458 return QVariant();
459
460 profile_def * prof = guard(index.row());
461 if (! prof)
462 return QVariant();
463
464 if (checkInvalid(index))
465 {
466 int ref = this->findAsReference(prof->name);
467 if (ref != index.row() && ref >= 0)
468 {
469 profile_def * prof = guard(ref);
470 QString msg = tr("A profile change for this name is pending");
471 if (prof)
472 msg.append(tr(" (See: %1)").arg(prof->name));
473 return msg;
474 }
475
476 return tr("This is an invalid profile definition");
477 }
478
479 if ((prof->status == PROF_STAT_NEW || prof->status == PROF_STAT_CHANGED || prof->status == PROF_STAT_COPY) && checkDuplicate(index))
480 return tr("A profile already exists with this name");
481
482 if (checkIfDeleted(index))
483 {
484 return tr("A profile with this name is being deleted");
485 }
486
487 if (prof->is_import)
488 return tr("Imported profile");
489
490 switch (prof->status)
491 {
492 case PROF_STAT_DEFAULT:
493 if (!reset_default_)
494 return gchar_free_to_qstring(get_persconffile_path("", FALSE));
495 else
496 return tr("Resetting to default");
497 case PROF_STAT_EXISTS:
498 {
499 QString profile_path;
500 if (prof->is_global) {
501 profile_path = gchar_free_to_qstring(get_global_profiles_dir());
502 } else {
503 profile_path = gchar_free_to_qstring(get_profiles_dir());
504 }
505 profile_path.append("/").append(prof->name);
506 return profile_path;
507 }
508 case PROF_STAT_NEW:
509 {
510 QString errMsg;
511
512 if (! checkNameValidity(prof->name, &errMsg))
513 return errMsg;
514 else
515 return tr("Created from default settings");
516 }
517 case PROF_STAT_CHANGED:
518 {
519 QString msg;
520 if (! ProfileModel::checkNameValidity(QString(prof->name), &msg))
521 return msg;
522
523 if (prof->reference)
524 return tr("Renamed from: %1").arg(prof->reference);
525
526 return QVariant();
527 }
528 case PROF_STAT_COPY:
529 {
530 QString msg;
531
532 /* this should always be the case, but just as a precaution it is checked */
533 if (prof->reference)
534 {
535 msg = tr("Copied from: %1").arg(prof->reference);
536 QString appendix;
537
538 /* A global profile is neither deleted or removed, only system provided is allowed as appendix */
539 if (profile_exists(prof->reference, TRUE) && prof->from_global)
540 appendix = tr("system provided");
541 /* A default model as reference can neither be deleted or renamed, so skip if the reference was one */
542 else if (! index.data(ProfileModel::DATA_IS_DEFAULT).toBool())
543 {
544 /* find a non-global, non-default profile which could be referenced by this one. Those are the only
545 * ones which could be renamed or deleted */
546 int row = this->findByNameAndVisibility(prof->reference, false, true);
547 profile_def * ref = guard(row);
548
549 /* The reference is itself a copy of the original, therefore it is not accepted */
550 if (ref && (ref->status == PROF_STAT_COPY || ref->status == PROF_STAT_NEW) && QString(ref->name).compare(prof->reference) != 0)
551 ref = Q_NULLPTR;
552
553 /* found no other profile, original one had to be deleted */
554 if (! ref || row == index.row() || checkIfDeleted(row))
555 {
556 appendix = tr("deleted");
557 }
558 /* found another profile, so the reference had been renamed, it the status is changed */
559 else if (ref && ref->status == PROF_STAT_CHANGED)
560 {
561 appendix = tr("renamed to %1").arg(ref->name);
562 }
563 }
564
565 if (appendix.length() > 0)
566 msg.append(QString(" (%1)").arg(appendix));
567 }
568
569 return msg;
570 }
571 }
572
573 return QVariant();
574 }
575
data(const QModelIndex & index,int role) const576 QVariant ProfileModel::data(const QModelIndex &index, int role) const
577 {
578 profile_def * prof = guard(index);
579 if (! prof)
580 return QVariant();
581
582 switch (role)
583 {
584 case Qt::DisplayRole:
585 return dataDisplay(index);
586 case Qt::FontRole:
587 return dataFontRole(index);
588 case Qt::BackgroundRole:
589 return dataBackgroundRole(index);
590 case Qt::ToolTipRole:
591 return dataToolTipRole(index);
592 case ProfileModel::DATA_STATUS:
593 return QVariant::fromValue(prof->status);
594 case ProfileModel::DATA_IS_DEFAULT:
595 return QVariant::fromValue(prof->status == PROF_STAT_DEFAULT);
596 case ProfileModel::DATA_IS_GLOBAL:
597 return QVariant::fromValue(prof->is_global);
598 case ProfileModel::DATA_IS_SELECTED:
599 {
600 QModelIndex selected = activeProfile();
601 profile_def * selprof = guard(selected);
602 if (selprof)
603 {
604 if (selprof && selprof->is_global != prof->is_global)
605 return QVariant::fromValue(false);
606
607 if (selprof && strcmp(selprof->name, prof->name) == 0)
608 return QVariant::fromValue(true);
609 }
610 return QVariant::fromValue(false);
611 }
612 case ProfileModel::DATA_PATH:
613 return dataPath(index);
614 case ProfileModel::DATA_INDEX_VALUE_IS_URL:
615 if (index.column() <= ProfileModel::COL_TYPE)
616 return QVariant::fromValue(false);
617 return QVariant::fromValue(true);
618 case ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION:
619 if (prof->status == PROF_STAT_NEW || prof->status == PROF_STAT_COPY
620 || (prof->status == PROF_STAT_DEFAULT && reset_default_)
621 || prof->status == PROF_STAT_CHANGED || prof->is_import)
622 return QVariant::fromValue(false);
623 else
624 return QVariant::fromValue(true);
625
626 default:
627 break;
628 }
629
630 return QVariant();
631 }
632
headerData(int section,Qt::Orientation orientation,int role) const633 QVariant ProfileModel::headerData(int section, Qt::Orientation orientation, int role) const
634 {
635 if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
636 {
637 switch (section)
638 {
639 case COL_NAME:
640 return tr("Profile");
641 case COL_TYPE:
642 return tr("Type");
643 default:
644 break;
645 }
646 }
647
648 return QVariant();
649 }
650
flags(const QModelIndex & index) const651 Qt::ItemFlags ProfileModel::flags(const QModelIndex &index) const
652 {
653 Qt::ItemFlags fl = QAbstractTableModel::flags(index);
654
655 profile_def * prof = guard(index);
656 if (! prof)
657 return fl;
658
659 if (index.column() == ProfileModel::COL_NAME && prof->status != PROF_STAT_DEFAULT && ! prof->is_global)
660 fl |= Qt::ItemIsEditable;
661
662 return fl;
663 }
664
findByName(QString name)665 int ProfileModel::findByName(QString name)
666 {
667 int row = findByNameAndVisibility(name, false);
668 if (row < 0)
669 row = findByNameAndVisibility(name, true);
670
671 return row;
672 }
673
findAsReference(QString reference) const674 int ProfileModel::findAsReference(QString reference) const
675 {
676 int found = -1;
677 if (reference.length() <= 0)
678 return found;
679
680 for (int cnt = 0; cnt < profiles_.count() && found < 0; cnt++)
681 {
682 profile_def * prof = guard(cnt);
683 if (prof && reference.compare(prof->reference) == 0)
684 found = cnt;
685 }
686
687 return found;
688 }
689
findByNameAndVisibility(QString name,bool isGlobal,bool searchReference) const690 int ProfileModel::findByNameAndVisibility(QString name, bool isGlobal, bool searchReference) const
691 {
692 QList<int> result = findAllByNameAndVisibility(name, isGlobal, searchReference);
693 return result.count() == 0 ? -1 : result.at(0);
694 }
695
findAllByNameAndVisibility(QString name,bool isGlobal,bool searchReference) const696 QList<int> ProfileModel::findAllByNameAndVisibility(QString name, bool isGlobal, bool searchReference) const
697 {
698 QList<int> result;
699
700 for (int cnt = 0; cnt < profiles_.count(); cnt++)
701 {
702 profile_def * prof = guard(cnt);
703 if (prof && static_cast<bool>(prof->is_global) == isGlobal)
704 {
705 if (name.compare(prof->name) == 0 || (searchReference && name.compare(prof->reference) == 0) )
706 result << cnt;
707 }
708 }
709
710 return result;
711
712 }
713
addNewProfile(QString name)714 QModelIndex ProfileModel::addNewProfile(QString name)
715 {
716 int cnt = 1;
717 QString newName = name;
718 while (findByNameAndVisibility(newName) >= 0)
719 {
720 newName = QString("%1 %2").arg(name).arg(QString::number(cnt));
721 cnt++;
722 }
723
724 add_to_profile_list(newName.toUtf8().constData(), newName.toUtf8().constData(), PROF_STAT_NEW, FALSE, FALSE, FALSE);
725 loadProfiles();
726
727 return index(findByName(newName), COL_NAME);
728 }
729
duplicateEntry(QModelIndex idx,int new_status)730 QModelIndex ProfileModel::duplicateEntry(QModelIndex idx, int new_status)
731 {
732 profile_def * prof = guard(idx);
733 if (! prof)
734 return QModelIndex();
735
736 /* only new and copied stati can be set */
737 if (new_status != PROF_STAT_NEW && new_status != PROF_STAT_COPY)
738 new_status = PROF_STAT_COPY;
739
740 /* this is a copy from a personal profile, check if the original has been a
741 * new profile or a preexisting one. In the case of a new profile, restart
742 * with the state PROF_STAT_NEW */
743 if (prof->status == PROF_STAT_COPY && ! prof->from_global)
744 {
745 int row = findByNameAndVisibility(prof->reference, false);
746 profile_def * copyParent = guard(row);
747 if (copyParent && copyParent->status == PROF_STAT_NEW)
748 return duplicateEntry(index(row, ProfileModel::COL_NAME), PROF_STAT_NEW);
749 }
750
751 /* Rules for figuring out the name to copy from:
752 *
753 * General, use copy name
754 * If status of copy is new or changed => use copy reference
755 * If copy is non global and status of copy is != changed, use original parent name
756 */
757 QString parent = prof->name;
758 if (prof->status == PROF_STAT_CHANGED)
759 parent = prof->reference;
760 else if (! prof->is_global && prof->status != PROF_STAT_NEW && prof->status != PROF_STAT_CHANGED)
761 parent = get_profile_parent (prof->name);
762
763 if (parent.length() == 0)
764 return QModelIndex();
765
766 /* parent references the parent profile to be used, parentName is the base for the new name */
767 QString parentName = parent;
768 /* the user has changed the profile name, therefore this is also the name to be used */
769 if (prof->status != PROF_STAT_EXISTS)
770 parentName = prof->name;
771
772 /* check to ensure we do not end up with (copy) (copy) (copy) ... */
773 QRegExp rx("\\s+(\\(\\s*" + tr("copy", "noun") + "\\s*\\d*\\))");
774 if (rx.indexIn(parentName) >= 0)
775 parentName.replace(rx, "");
776
777 QString new_name;
778 /* if copy is global and name has not been used before, use that, else create first copy */
779 if (prof->is_global && findByNameAndVisibility(parentName) < 0)
780 new_name = QString(prof->name);
781 else
782 new_name = QString("%1 (%2)").arg(parentName).arg(tr("copy", "noun"));
783
784 /* check if copy already exists and iterate, until an unused version is found */
785 int cnt = 1;
786 while (findByNameAndVisibility(new_name) >= 0)
787 {
788 new_name = QString("%1 (%2 %3)").arg(parentName).arg(tr("copy", "noun")).arg(QString::number(cnt));
789 cnt++;
790 }
791
792 /* if this would be a copy, but the original is already a new one, this is a copy as well */
793 if (new_status == PROF_STAT_COPY && prof->status == PROF_STAT_NEW)
794 new_status = PROF_STAT_NEW;
795
796 /* add element */
797 add_to_profile_list(new_name.toUtf8().constData(), parent.toUtf8().constData(), new_status, FALSE, prof->from_global ? prof->from_global : prof->is_global, FALSE);
798
799 /* reload profile list in model */
800 loadProfiles();
801
802 int row = findByNameAndVisibility(new_name, false);
803 /* sanity check, if adding the profile went correctly */
804 if (row < 0 || row == idx.row())
805 return QModelIndex();
806
807 /* return the index of the profile */
808 return index(row, COL_NAME);
809 }
810
deleteEntry(QModelIndex idx)811 void ProfileModel::deleteEntry(QModelIndex idx)
812 {
813 if (! idx.isValid())
814 return;
815
816 QModelIndexList temp;
817 temp << idx;
818 deleteEntries(temp);
819 }
820
deleteEntries(QModelIndexList idcs)821 void ProfileModel::deleteEntries(QModelIndexList idcs)
822 {
823 bool changes = false;
824
825 QList<int> indeces;
826 foreach (QModelIndex idx, idcs)
827 {
828 if (! indeces.contains(idx.row()) && ! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool())
829 indeces << idx.row();
830 }
831 /* Security blanket. This ensures, that we start deleting from the end and do not get any issues iterating the list */
832 std::sort(indeces.begin(), indeces.end(), std::less<int>());
833
834 foreach (int row, indeces)
835 {
836 profile_def * prof = guard(row);
837 if (! prof)
838 continue;
839
840 if (prof->is_global)
841 continue;
842
843 if (prof->status == PROF_STAT_DEFAULT)
844 {
845 reset_default_ = ! reset_default_;
846 }
847 else
848 {
849 GList * fl_entry = entry(prof);
850 if (fl_entry)
851 {
852 changes = true;
853 remove_from_profile_list(fl_entry);
854 }
855 }
856 }
857
858 if (changes)
859 loadProfiles();
860
861 if (reset_default_)
862 {
863 emit layoutAboutToBeChanged();
864 emit dataChanged(index(0, 0), index(rowCount(), columnCount()));
865 emit layoutChanged();
866 }
867 }
868
resetDefault() const869 bool ProfileModel::resetDefault() const
870 {
871 return reset_default_;
872 }
873
doResetModel(bool reset_import)874 void ProfileModel::doResetModel(bool reset_import)
875 {
876 reset_default_ = false;
877 if (reset_import)
878 profiles_imported_ = false;
879
880 loadProfiles();
881 }
882
activeProfile() const883 QModelIndex ProfileModel::activeProfile() const
884 {
885 QList<int> rows = this->findAllByNameAndVisibility(set_profile_, false, true);
886 foreach (int row, rows)
887 {
888 profile_def * prof = profiles_.at(row);
889 if (prof->is_global || checkDuplicate(index(row, ProfileModel::COL_NAME)) )
890 return QModelIndex();
891
892 if ((set_profile_.compare(prof->name) == 0 && (prof->status == PROF_STAT_EXISTS || prof->status == PROF_STAT_DEFAULT) ) ||
893 (set_profile_.compare(prof->reference) == 0 && prof->status == PROF_STAT_CHANGED) )
894 return index(row, ProfileModel::COL_NAME);
895 }
896
897 return QModelIndex();
898 }
899
setData(const QModelIndex & idx,const QVariant & value,int role)900 bool ProfileModel::setData(const QModelIndex &idx, const QVariant &value, int role)
901 {
902 last_set_row_ = -1;
903
904 if (role != Qt::EditRole || ! value.isValid() || value.toString().isEmpty())
905 return false;
906
907 QString newValue = value.toString();
908 profile_def * prof = guard(idx);
909 if (! prof || prof->status == PROF_STAT_DEFAULT)
910 return false;
911
912 last_set_row_ = idx.row();
913
914 QString current(prof->name);
915 if (current.compare(newValue) != 0)
916 {
917 g_free(prof->name);
918 prof->name = qstring_strdup(newValue);
919
920 if (prof->reference && g_strcmp0(prof->name, prof->reference) == 0 && ! (prof->status == PROF_STAT_NEW || prof->status == PROF_STAT_COPY)) {
921 prof->status = PROF_STAT_EXISTS;
922 } else if (prof->status == PROF_STAT_EXISTS) {
923 prof->status = PROF_STAT_CHANGED;
924 }
925 emit itemChanged(idx);
926 }
927
928 loadProfiles();
929
930 return true;
931 }
932
lastSetRow() const933 int ProfileModel::lastSetRow() const
934 {
935 return last_set_row_;
936 }
937
copyTempToProfile(QString tempPath,QString profilePath,bool * wasEmpty)938 bool ProfileModel::copyTempToProfile(QString tempPath, QString profilePath, bool * wasEmpty)
939 {
940 bool was_empty = true;
941
942 QDir profileDir(profilePath);
943 if (! profileDir.mkpath(profilePath) || ! QFile::exists(tempPath))
944 return false;
945
946 QDir tempProfile(tempPath);
947 tempProfile.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
948 QFileInfoList files = tempProfile.entryInfoList();
949 if (files.count() <= 0)
950 return false;
951
952 int created = 0;
953 foreach (QFileInfo finfo, files)
954 {
955 QString tempFile = finfo.absoluteFilePath();
956 QString profileFile = profilePath + "/" + finfo.fileName();
957
958 if (! profile_files_.contains(finfo.fileName()))
959 {
960 was_empty = false;
961 continue;
962 }
963
964 if (! QFile::exists(tempFile) || QFile::exists(profileFile))
965 continue;
966
967 if (QFile::copy(tempFile, profileFile))
968 created++;
969 }
970
971 if (wasEmpty)
972 *wasEmpty = was_empty;
973
974 if (created > 0)
975 return true;
976
977 return false;
978 }
979
uniquePaths(QFileInfoList lst)980 QFileInfoList ProfileModel::uniquePaths(QFileInfoList lst)
981 {
982 QStringList files;
983 QFileInfoList newLst;
984
985 foreach (QFileInfo entry, lst)
986 {
987 if (! files.contains(entry.absoluteFilePath()))
988 {
989 if (entry.exists() && entry.isDir())
990 {
991 newLst << entry.absoluteFilePath();
992 files << entry.absoluteFilePath();
993 }
994 }
995 }
996
997 return newLst;
998 }
999
filterProfilePath(QString path,QFileInfoList ent,bool fromZip)1000 QFileInfoList ProfileModel::filterProfilePath(QString path, QFileInfoList ent, bool fromZip)
1001 {
1002 QFileInfoList result = ent;
1003 QDir temp(path);
1004 temp.setSorting(QDir::Name);
1005 temp.setFilter(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
1006 QFileInfoList entries = temp.entryInfoList();
1007 if (! fromZip)
1008 entries << QFileInfo(path);
1009 foreach (QFileInfo entry, entries)
1010 {
1011 QDir fPath(entry.absoluteFilePath());
1012 fPath.setSorting(QDir::Name);
1013 fPath.setFilter(QDir::Files | QDir::NoSymLinks);
1014 QFileInfoList fEntries = fPath.entryInfoList();
1015 bool found = false;
1016 for (int cnt = 0; cnt < fEntries.count() && ! found; cnt++)
1017 {
1018 if (config_file_exists_with_entries(fEntries[cnt].absoluteFilePath().toUtf8().constData(), '#'))
1019 found = true;
1020 }
1021
1022 if (found)
1023 {
1024 result.append(entry);
1025 }
1026 else
1027 {
1028 if (path.compare(entry.absoluteFilePath()) != 0)
1029 result.append(filterProfilePath(entry.absoluteFilePath(), result, fromZip));
1030 }
1031 }
1032
1033 return result;
1034 }
1035
1036 #ifdef HAVE_MINIZIP
exportFileList(QModelIndexList items)1037 QStringList ProfileModel::exportFileList(QModelIndexList items)
1038 {
1039 QStringList result;
1040
1041 foreach(QModelIndex idx, items)
1042 {
1043 profile_def * prof = guard(idx);
1044 if (! prof || prof->is_global || QString(prof->name).compare(DEFAULT_PROFILE) == 0)
1045 continue;
1046
1047 if (! idx.data(ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION).toBool())
1048 continue;
1049
1050 QString path = idx.data(ProfileModel::DATA_PATH).toString();
1051 QDir temp(path);
1052 temp.setSorting(QDir::Name);
1053 temp.setFilter(QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot);
1054 QFileInfoList entries = temp.entryInfoList();
1055 foreach (QFileInfo fi, entries)
1056 result << fi.absoluteFilePath();
1057 }
1058
1059 return result;
1060 }
1061
exportProfiles(QString filename,QModelIndexList items,QString * err)1062 bool ProfileModel::exportProfiles(QString filename, QModelIndexList items, QString *err)
1063 {
1064 if (changesPending())
1065 {
1066 if (err)
1067 err->append(tr("Exporting profiles while changes are pending is not allowed"));
1068 return false;
1069 }
1070
1071 /* Write recent file for current profile before exporting */
1072 write_profile_recent();
1073
1074 QStringList files = exportFileList(items);
1075 if (files.count() == 0)
1076 {
1077 if (err)
1078 err->append((tr("No profiles found to export")));
1079 return false;
1080 }
1081
1082 if (WiresharkZipHelper::zip(filename, files, gchar_free_to_qstring(get_profiles_dir()) + "/") )
1083 return true;
1084
1085 return false;
1086 }
1087
1088 /* This check runs BEFORE the file has been unzipped! */
acceptFile(QString fileName,int fileSize)1089 bool ProfileModel::acceptFile(QString fileName, int fileSize)
1090 {
1091 if (fileName.toLower().endsWith(".zip"))
1092 return false;
1093
1094 /* Arbitrary maximum config file size accepted: 256MB */
1095 if (fileSize > 1024 * 1024 * 256)
1096 return false;
1097
1098 return true;
1099 }
1100
cleanName(QString fileName)1101 QString ProfileModel::cleanName(QString fileName)
1102 {
1103 QStringList parts = fileName.split("/");
1104 QString temp = parts[parts.count() - 1].replace(QRegExp("[" + QRegExp::escape(illegalCharacters()) + "]"), QString("_") );
1105 temp = parts.join("/");
1106 return temp;
1107 }
1108
importProfilesFromZip(QString filename,int * skippedCnt,QStringList * result)1109 int ProfileModel::importProfilesFromZip(QString filename, int * skippedCnt, QStringList *result)
1110 {
1111 QTemporaryDir dir;
1112 #if 0
1113 dir.setAutoRemove(false);
1114 g_printerr("Temp dir for unzip: %s\n", dir.path().toUtf8().constData());
1115 #endif
1116
1117 int cnt = 0;
1118 if (dir.isValid())
1119 {
1120 WiresharkZipHelper::unzip(filename, dir.path(), &ProfileModel::acceptFile, &ProfileModel::cleanName);
1121 cnt = importProfilesFromDir(dir.path(), skippedCnt, true, result);
1122 }
1123
1124 return cnt;
1125 }
1126 #endif
1127
importProfilesFromDir(QString dirname,int * skippedCnt,bool fromZip,QStringList * result)1128 int ProfileModel::importProfilesFromDir(QString dirname, int * skippedCnt, bool fromZip, QStringList *result)
1129 {
1130 int count = 0;
1131 int skipped = 0;
1132 QDir profileDir(gchar_free_to_qstring(get_profiles_dir()));
1133 QDir dir(dirname);
1134
1135 if (skippedCnt)
1136 *skippedCnt = 0;
1137
1138 if (dir.exists())
1139 {
1140 QFileInfoList entries = uniquePaths(filterProfilePath(dirname, QFileInfoList(), fromZip));
1141
1142 int entryCount = 0;
1143 foreach (QFileInfo fentry, entries)
1144 {
1145 if (fentry.fileName().length() <= 0)
1146 continue;
1147
1148 bool wasEmpty = true;
1149 bool success = false;
1150
1151 entryCount++;
1152
1153 QString profilePath = profileDir.absolutePath() + "/" + fentry.fileName();
1154 QString tempPath = fentry.absoluteFilePath();
1155
1156 if (fentry.fileName().compare(DEFAULT_PROFILE, Qt::CaseInsensitive) == 0 || QFile::exists(profilePath))
1157 {
1158 skipped++;
1159 continue;
1160 }
1161
1162 if (result)
1163 *result << fentry.fileName();
1164
1165 success = copyTempToProfile(tempPath, profilePath, &wasEmpty);
1166 if (success)
1167 {
1168 count++;
1169 add_to_profile_list(fentry.fileName().toUtf8().constData(), fentry.fileName().toUtf8().constData(), PROF_STAT_NEW, FALSE, FALSE, TRUE);
1170 }
1171 else if (! wasEmpty && QFile::exists(profilePath))
1172 {
1173 QDir dh(profilePath);
1174 dh.rmdir(profilePath);
1175 }
1176 }
1177
1178 }
1179
1180 if (count > 0)
1181 {
1182 profiles_imported_ = true;
1183 loadProfiles();
1184 }
1185
1186 if (skippedCnt)
1187 *skippedCnt = skipped;
1188
1189 return count;
1190 }
1191
markAsImported(QStringList importedItems)1192 void ProfileModel::markAsImported(QStringList importedItems)
1193 {
1194 if (importedItems.count() <= 0)
1195 return;
1196
1197 profiles_imported_ = true;
1198
1199 foreach (QString item, importedItems)
1200 {
1201 int row = findByNameAndVisibility(item, false);
1202 profile_def * prof = guard(row);
1203 if (! prof)
1204 continue;
1205
1206 prof->is_import = true;
1207 }
1208 }
1209
clearImported(QString * msg)1210 bool ProfileModel::clearImported(QString *msg)
1211 {
1212 QList<int> rows;
1213 bool result = true;
1214 for (int cnt = 0; cnt < rowCount(); cnt++)
1215 {
1216 profile_def * prof = guard(cnt);
1217 if (prof && prof->is_import && ! rows.contains(cnt))
1218 rows << cnt;
1219 }
1220 /* Security blanket. This ensures, that we start deleting from the end and do not get any issues iterating the list */
1221 std::sort(rows.begin(), rows.end(), std::less<int>());
1222
1223 char * ret_path = Q_NULLPTR;
1224 for (int cnt = 0; cnt < rows.count() && result; cnt++)
1225 {
1226 int row = rows.at(cnt);
1227 if (delete_persconffile_profile (index(row, ProfileModel::COL_NAME).data().toString().toUtf8().constData(), &ret_path) != 0)
1228 {
1229 if (msg)
1230 {
1231 QString errmsg = QString("%1\n\"%2\":\n%3").arg(tr("Can't delete profile directory")).arg(ret_path).arg(g_strerror(errno));
1232 msg->append(errmsg);
1233 }
1234
1235 result = false;
1236 }
1237 }
1238
1239 return result;
1240 }
1241
illegalCharacters()1242 QString ProfileModel::illegalCharacters()
1243 {
1244 #ifdef _WIN32
1245 /* According to https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions */
1246 return QString("<>:\"/\\|?*");
1247 #else
1248 return QDir::separator();
1249 #endif
1250
1251 }
1252
checkNameValidity(QString name,QString * msg)1253 bool ProfileModel::checkNameValidity(QString name, QString *msg)
1254 {
1255 QString message;
1256 bool invalid = false;
1257 QString msgChars;
1258
1259 QString invalid_dir_chars = illegalCharacters();
1260
1261 for (int cnt = 0; cnt < invalid_dir_chars.length() && ! invalid; cnt++)
1262 {
1263 msgChars += invalid_dir_chars[cnt] + " ";
1264 if (name.contains(invalid_dir_chars[cnt]))
1265 invalid = true;
1266 }
1267 #ifdef _WIN32
1268 if (invalid)
1269 {
1270 message = tr("A profile name cannot contain the following characters: %1").arg(msgChars);
1271 }
1272
1273 if (message.isEmpty() && (name.startsWith('.') || name.endsWith('.')) )
1274 message = tr("A profile cannot start or end with a period (.)");
1275 #else
1276 if (invalid)
1277 message = tr("A profile name cannot contain the '/' character");
1278 #endif
1279
1280 if (! message.isEmpty()) {
1281 if (msg)
1282 msg->append(message);
1283 return false;
1284 }
1285
1286 return true;
1287 }
1288
activeProfileName()1289 QString ProfileModel::activeProfileName()
1290 {
1291 ProfileModel model;
1292 QModelIndex idx = model.activeProfile();
1293 return idx.data(ProfileModel::COL_NAME).toString();
1294 }
1295
activeProfilePath()1296 QString ProfileModel::activeProfilePath()
1297 {
1298 ProfileModel model;
1299 QModelIndex idx = model.activeProfile();
1300 return idx.data(ProfileModel::DATA_PATH).toString();
1301 }
1302