• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 35159963146

16 Sep 2026 10:55PM UTC coverage: 72.009% (-0.3%) from 72.336%
35159963146

push

github

web-flow
One "qtpass" logging category instead of dbg() and 69 QT_DEBUG guards (#1785)

* One "qtpass" logging category instead of dbg() and 69 QT_DEBUG guards

debughelper.h promised file:line output "only in debug builds" while
src.pro already compiled qDebug out of release builds, and the #ifdef
blocks spliced control flow (an else in trayicon.cpp, an else-if in
sshauthsock.cpp) and have broken the build before (55149eca1, 0b6a80bdf).

Q_LOGGING_CATEGORY(lcQtPass, "qtpass", QtInfoMsg): every dbg() is a
qCDebug(lcQtPass), the debug-only qWarning()s are qCWarning(lcQtPass), the
guards are gone, and QT_NO_DEBUG_OUTPUT is no longer defined for release,
so a shipped build can produce a trace with
QT_LOGGING_RULES="qtpass.debug=true". FAQ entry and fixing skill updated;
tst_util checks the category is quiet until asked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JuQsrHonihp1nARE7bzstc

* Convert the last bare qDebug(); FAQ: key IDs

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JuQsrHonihp1nARE7bzstc

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

36 of 78 new or added lines in 15 files covered. (46.15%)

23 existing lines in 8 files now uncovered.

5513 of 7656 relevant lines covered (72.01%)

60.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.01
/src/storemodel.cpp
1
// SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "storemodel.h"
4
#include "pass.h"
5
#include "pathvalidator.h"
6
#include "qtpasslogging.h"
7
#include "util.h"
8
#include <QApplication>
9
#include <QDebug>
10
#include <QFileSystemModel>
11
#include <QMessageBox>
12
#include <QMimeData>
13
#include <QRegularExpression>
14
#include <QtGlobal>
15

16
auto operator<<(
11 ✔
17
    QDataStream &out,
18
    const dragAndDropInfoPasswordStore &dragAndDropInfoPasswordStore)
19
    -> QDataStream & {
20
  out << static_cast<quint8>(dragAndDropInfoPasswordStore.kind)
11 ✔
21
      << dragAndDropInfoPasswordStore.path;
11 ✔
22
  return out;
11 ✔
23
}
24

25
auto operator>>(QDataStream &in,
14 ✔
26
                dragAndDropInfoPasswordStore &dragAndDropInfoPasswordStore)
27
    -> QDataStream & {
28
  quint8 k;
29
  in >> k >> dragAndDropInfoPasswordStore.path;
14 ✔
30
  switch (k) {
14 ✔
31
  case static_cast<quint8>(dragAndDropInfoPasswordStore::ItemKind::Directory):
3 ✔
32
    dragAndDropInfoPasswordStore.kind =
3 ✔
33
        dragAndDropInfoPasswordStore::ItemKind::Directory;
34
    break;
3 ✔
35
  case static_cast<quint8>(dragAndDropInfoPasswordStore::ItemKind::File):
10 ✔
36
    dragAndDropInfoPasswordStore.kind =
10 ✔
37
        dragAndDropInfoPasswordStore::ItemKind::File;
38
    break;
10 ✔
39
  default:
1 ✔
40
    dragAndDropInfoPasswordStore.kind =
1 ✔
41
        dragAndDropInfoPasswordStore::ItemKind::Unknown;
42
    break;
1 ✔
43
  }
44
  return in;
14 ✔
45
}
46

47
/**
48
 * @brief StoreModel::StoreModel
49
 * SubClass of QSortFilterProxyModel via
50
 * http://www.qtcentre.org/threads/46471-QTreeView-Filter
51
 */
52
StoreModel::StoreModel() { fs = nullptr; }
68 ✔
53

54
void StoreModel::setPass(Pass *pass) { m_pass = pass; }
36 ✔
55

56
/**
57
 * @brief StoreModel::filterAcceptsRow should row be shown, wrapper for
58
 * StoreModel::showThis method.
59
 * @param sourceRow
60
 * @param sourceParent
61
 * @return
62
 */
63
auto StoreModel::filterAcceptsRow(int sourceRow,
228 ✔
64
                                  const QModelIndex &sourceParent) const
65
    -> bool {
66
  QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
228 ✔
67
  return showThis(index);
228 ✔
68
}
69

70
/**
71
 * @brief StoreModel::showThis should a row be shown, based on our search
72
 * criteria.
73
 * @param index
74
 * @return
75
 */
76
auto StoreModel::showThis(const QModelIndex &index) const -> bool {
487 ✔
77
  bool retVal = false;
78
  if (fs == nullptr) {
487 ✔
79
    return retVal;
80
  }
81
  // Gives you the info for number of children with a parent
82
  if (sourceModel()->rowCount(index) > 0) {
486 ✔
83
    for (int nChild = 0; nChild < sourceModel()->rowCount(index); ++nChild) {
258 ✔
84
      QModelIndex childIndex = sourceModel()->index(nChild, 0, index);
258 ✔
85
      if (!childIndex.isValid()) {
86
        break;
87
      }
88
      retVal = showThis(childIndex);
258 ✔
89
      if (retVal) {
258 ✔
90
        break;
91
      }
92
    }
93
  } else {
94
    QModelIndex useIndex = sourceModel()->index(index.row(), 0, index.parent());
228 ✔
95
    QString path = fs->filePath(useIndex);
228 ✔
96
    path = QDir(store).relativeFilePath(path);
456 ✔
97
    if (path.startsWith(".git")) {
456 ✔
98
      return false;
99
    }
100
    path.replace(Util::endsWithGpg(), "");
227 ✔
101
    retVal = path.contains(filterRegularExpression());
227 ✔
102
  }
103
  return retVal;
104
}
105

106
/**
107
 * @brief StoreModel::setModelAndStore update the source model and store.
108
 * @param sourceModel
109
 * @param passStore
110
 */
111
void StoreModel::setModelAndStore(QFileSystemModel *sourceModel,
62 ✔
112
                                  const QString &passStore) {
113
  setSourceModel(sourceModel);
62 ✔
114
  fs = sourceModel;
62 ✔
115
  store = passStore;
62 ✔
116
}
62 ✔
117

118
auto StoreModel::rootIndexFor(const QString &path) -> QModelIndex {
5 ✔
119
  if (fs == nullptr) {
5 ✔
120
    return {};
121
  }
122
  return mapFromSource(fs->setRootPath(QDir::cleanPath(path)));
8 ✔
123
}
124

125
void StoreModel::setStore(const QString &passStore) {
4 ✔
126
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
127
  // beginFilterChange() is available since Qt 6.9, but the Direction-scoped
128
  // endFilterChange(QSortFilterProxyModel::Direction) overload is only
129
  // available since Qt 6.10, which is the preferred API for scoped and more
130
  // efficient filter updates.
131
  beginFilterChange();
132
  store = passStore;
133
  endFilterChange(QSortFilterProxyModel::Direction::Rows);
134
#else
135
  // Direction-scoped filter changes are unavailable before Qt 6.10, so older
136
  // Qt versions must manually invalidate filters. We update the store and
137
  // manually invalidate the filter as a compatibility path.
138
  store = passStore;
4 ✔
139
  invalidateFilter();
4 ✔
140
#endif
141
}
4 ✔
142

143
/**
144
 * @brief StoreModel::data don't show the .gpg at the end of a file.
145
 * @param index
146
 * @param role
147
 * @return
148
 */
149
auto StoreModel::data(const QModelIndex &index, int role) const -> QVariant {
113 ✔
150
  if (!index.isValid()) {
151
    return {};
152
  }
153

154
  QVariant initial_value;
155
  initial_value = QSortFilterProxyModel::data(index, role);
112 ✔
156

157
  if (role == Qt::DisplayRole) {
112 ✔
158
    QString name = initial_value.toString();
23 ✔
159
    name.replace(Util::endsWithGpg(), "");
23 ✔
160
    initial_value.setValue(name);
23 ✔
161
  }
162

163
  return initial_value;
112 ✔
164
}
112 ✔
165

166
/**
167
 * @brief StoreModel::supportedDropActions enable drop.
168
 * @return
169
 */
170
auto StoreModel::supportedDropActions() const -> Qt::DropActions {
1 ✔
171
  return Qt::CopyAction | Qt::MoveAction;
1 ✔
172
}
173

174
/**
175
 * @brief StoreModel::supportedDragActions enable drag.
176
 * @return
177
 */
178
auto StoreModel::supportedDragActions() const -> Qt::DropActions {
1 ✔
179
  return Qt::CopyAction | Qt::MoveAction;
1 ✔
180
}
181

182
/**
183
 * @brief StoreModel::flags
184
 * @param index
185
 * @return
186
 */
187
auto StoreModel::flags(const QModelIndex &index) const -> Qt::ItemFlags {
76 ✔
188
  Qt::ItemFlags defaultFlags = QSortFilterProxyModel::flags(index);
76 ✔
189

190
  if (index.isValid()) {
191
    return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
192
  }
193
  return Qt::ItemIsDropEnabled | defaultFlags;
1 ✔
194
}
195

196
/**
197
 * @brief StoreModel::mimeTypes
198
 * @return
199
 */
200
auto StoreModel::mimeTypes() const -> QStringList {
1 ✔
201
  QStringList types;
1 ✔
202
  types << kStoreDragMimeType;
×
203
  return types;
1 ✔
204
}
205

206
/**
207
 * @brief StoreModel::mimeData
208
 * @param indexes
209
 * @return
210
 */
211
auto StoreModel::mimeData(const QModelIndexList &indexes) const -> QMimeData * {
1 ✔
212
  dragAndDropInfoPasswordStore info;
1 ✔
213

214
  if (indexes.isEmpty())
1 ✔
215
    return nullptr;
216
  QByteArray encodedData;
1 ✔
217
  // only use the first, otherwise we should enable multiselection
218
  QModelIndex index = indexes.at(0);
1 ✔
219
  if (index.isValid()) {
220
    QModelIndex useIndex = mapToSource(index);
1 ✔
221
    const QFileInfo fileInfo = fs->fileInfo(useIndex);
1 ✔
222

223
    if (fileInfo.isDir()) {
1 ✔
224
      info.kind = dragAndDropInfoPasswordStore::ItemKind::Directory;
×
225
    } else if (fileInfo.isFile()) {
1 ✔
226
      info.kind = dragAndDropInfoPasswordStore::ItemKind::File;
1 ✔
227
    }
228
    info.path = fileInfo.absoluteFilePath();
2 ✔
229
    QDataStream stream(&encodedData, QIODevice::WriteOnly);
1 ✔
230
    stream << info;
1 ✔
231
  }
1 ✔
232

233
  auto *mimeData = new QMimeData();
1 ✔
234
  mimeData->setData(kStoreDragMimeType, encodedData);
2 ✔
235
  return mimeData;
236
}
237

238
/**
239
 * @brief StoreModel::canDropMimeData
240
 * @param data
241
 * @param action
242
 * @param row
243
 * @param column
244
 * @param parent
245
 * @return
246
 */
247
auto StoreModel::canDropMimeData(const QMimeData *data, Qt::DropAction action,
11 ✔
248
                                 int row, int column,
249
                                 const QModelIndex &parent) const -> bool {
250
  qCDebug(lcQtPass) << "canDropMimeData" << action << row;
11 ✔
251

252
  const auto parsed = parseDropData(data);
11 ✔
253
  if (!parsed) {
11 ✔
254
    return false;
255
  }
256
  const dragAndDropInfoPasswordStore &info = *parsed;
257

258
  QModelIndex useIndex =
259
      this->index(parent.row(), parent.column(), parent.parent());
8 ✔
260

261
  if (column > 0) {
8 ✔
262
    return false;
263
  }
264

265
  using IK = dragAndDropInfoPasswordStore::ItemKind;
266
  // you can drop a folder on a folder
267
  if (fs->fileInfo(mapToSource(useIndex)).isDir() &&
14 ✔
268
      info.kind == IK::Directory) {
5 ✔
269
    return true;
270
  }
271
  // you can drop a file on a folder
272
  if (fs->fileInfo(mapToSource(useIndex)).isDir() && info.kind == IK::File) {
8 ✔
273
    return true;
274
  }
275
  // you can drop a file on a file
276
  if (fs->fileInfo(mapToSource(useIndex)).isFile() && info.kind == IK::File) {
3 ✔
277
    return true;
278
  }
279

280
  return false;
281
}
282

283
/**
284
 * @brief StoreModel::dropMimeData
285
 * @param data
286
 * @param action
287
 * @param row
288
 * @param column
289
 * @param parent
290
 * @return
291
 */
292
auto StoreModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
3 ✔
293
                              int row, int column, const QModelIndex &parent)
294
    -> bool {
295
  if (!canDropMimeData(data, action, row, column, parent)) {
3 ✔
296
    return false;
297
  }
298

299
  if (action == Qt::IgnoreAction) {
3 ✔
300
    return true;
301
  }
302

303
  if (action != Qt::MoveAction && action != Qt::CopyAction) {
3 ✔
304
    return false;
305
  }
306

307
  const auto info = parseDropData(data);
3 ✔
308
  if (!info) {
3 ✔
309
    return false;
310
  }
311

312
  return executeDropAction(*info, action, parent);
3 ✔
313
}
314

315
auto StoreModel::parseDropData(const QMimeData *data)
14 ✔
316
    -> std::optional<dragAndDropInfoPasswordStore> {
317
  if (data == nullptr || !data->hasFormat(kStoreDragMimeType)) {
27 ✔
318
    return std::nullopt;
2 ✔
319
  }
320
  QByteArray encodedData = data->data(kStoreDragMimeType);
24 ✔
321
  if (encodedData.isEmpty()) {
12 ✔
322
    return std::nullopt;
1 ✔
323
  }
324
  QDataStream stream(&encodedData, QIODevice::ReadOnly);
11 ✔
325
  dragAndDropInfoPasswordStore info;
11 ✔
326
  stream >> info;
11 ✔
327
  if (stream.status() != QDataStream::Ok) {
11 ✔
328
    return std::nullopt;
×
329
  }
330
  return info;
331
}
11 ✔
332

333
auto StoreModel::executeDropAction(const dragAndDropInfoPasswordStore &info,
3 ✔
334
                                   Qt::DropAction action,
335
                                   const QModelIndex &parent) -> bool {
336
  QModelIndex destIndex =
337
      this->index(parent.row(), parent.column(), parent.parent());
3 ✔
338
  QFileInfo destFileinfo = fs->fileInfo(mapToSource(destIndex));
3 ✔
339
  QFileInfo srcFileInfo = QFileInfo(info.path);
3 ✔
340

341
  QString cleanedSrc = QDir::cleanPath(srcFileInfo.absoluteFilePath());
3 ✔
342
  QString cleanedDest = QDir::cleanPath(destFileinfo.absoluteFilePath());
3 ✔
343

344
  // Both endpoints must resolve inside the password store after symlink
345
  // resolution. Drop data is encoded by the dragged item but could be
346
  // crafted; canonical-path checks stop drops that would move/copy outside
347
  // the store or follow a symlink out (e.g. a symlink within the store
348
  // pointing at /etc).
349
  if (!PathValidator::isPathInStore(store, cleanedSrc) ||
3 ✔
350
      !PathValidator::isPathInStore(store, cleanedDest)) {
×
351
    qCWarning(lcQtPass)
6 ✔
352
        << "executeDropAction: rejecting drop that escapes the store"
3 ✔
353
        << "(src=" << cleanedSrc << "dest=" << cleanedDest << ")";
3 ✔
354
    return false;
3 ✔
355
  }
356

357
  switch (info.kind) {
×
358
  case dragAndDropInfoPasswordStore::ItemKind::Directory: {
×
359
    // Dropping a folder onto a folder: move/copy it *into* the target.
360
    if (!destFileinfo.isDir()) {
×
361
      return false;
362
    }
363
    const QString destDir =
364
        QDir::cleanPath(QDir(cleanedDest).filePath(srcFileInfo.fileName()));
×
365
    return performDrop(cleanedSrc, destDir, action, false);
×
366
  }
367
  case dragAndDropInfoPasswordStore::ItemKind::File:
×
368
    // File onto a folder drops into it (no clash); file onto an existing
369
    // file asks before overwriting.
370
    if (destFileinfo.isDir()) {
×
371
      return performDrop(cleanedSrc, cleanedDest, action, false);
×
372
    }
373
    return performDrop(
×
374
        cleanedSrc, cleanedDest, action,
375
        QMessageBox::question(
×
376
            qobject_cast<QWidget *>(QObject::parent()), tr("Force overwrite?"),
×
377
            tr("overwrite %1 with %2?").arg(cleanedDest, cleanedSrc),
×
378
            QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes);
UNCOV
379
  default:
×
NEW
380
    qCWarning(lcQtPass)
×
NEW
381
        << "executeDropAction: unexpected ItemKind, ignoring drop";
×
UNCOV
382
    return false;
×
383
  }
384
}
3 ✔
385

386
auto StoreModel::performDrop(const QString &cleanedSrc,
×
387
                             const QString &cleanedDest, Qt::DropAction action,
388
                             bool force) -> bool {
389
  if (!m_pass) {
×
390
    return false;
391
  }
392
  if (action == Qt::MoveAction) {
×
393
    m_pass->Move(cleanedSrc, cleanedDest, force);
×
394
  } else if (action == Qt::CopyAction) {
×
395
    m_pass->Copy(cleanedSrc, cleanedDest, force);
×
396
  }
397
  return true;
398
}
399

400
/**
401
 * @brief StoreModel::lessThan
402
 * @param source_left
403
 * @param source_right
404
 * @return
405
 */
406
auto StoreModel::lessThan(const QModelIndex &source_left,
2 ✔
407
                          const QModelIndex &source_right) const -> bool {
408
/* matches logic in QFileSystemModelSorter::compareNodes() */
409
#ifndef Q_OS_MAC
410
  if (fs && (source_left.column() == 0 || source_left.column() == 1)) {
2 ✔
411
    bool leftD = fs->isDir(source_left);
2 ✔
412
    bool rightD = fs->isDir(source_right);
2 ✔
413

414
    if (leftD ^ rightD) {
2 ✔
415
      return leftD;
416
    }
417
  }
418
#endif
419

420
  return QSortFilterProxyModel::lessThan(source_left, source_right);
1 ✔
421
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc