MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 90.2% 55 / 0 / 61
Functions: 100.0% 6 / 0 / 6
Branches: 81.4% 79 / 0 / 97

data/DatabaseSecurity.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QDir>
4 #include <QFile>
5 #include <QFileInfo>
6 #include <QString>
7
8 #ifdef Q_OS_UNIX
9 #include <sys/stat.h>
10 #include <unistd.h>
11 #endif
12
13 namespace DatabaseSecurity {
14
15 51853 inline bool isOwnedByCurrentUser(const QFileInfo &info) {
16 #ifdef Q_OS_UNIX
17 51853 return info.ownerId() == static_cast<uint>(::geteuid());
18 #else
19 Q_UNUSED(info)
20 return true;
21 #endif
22 }
23
24 11472 inline bool isSharedSystemDirectory(const QString &path) {
25 #ifdef Q_OS_UNIX
26
3/4
✓ Branch 2 → 3 taken 11472 times.
✓ Branch 3 → 4 taken 11472 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 11472 times.
11472 if (QDir::cleanPath(path) == QDir::rootPath())
27 ✗ return true;
28
29 11472 struct stat metadata {};
30
1/1
✓ Branch 9 → 10 taken 11472 times.
11472 const QByteArray nativePath = QFile::encodeName(path);
31
2/4
✓ Branch 12 → 13 taken 11472 times.
✗ Branch 12 → 14 not taken.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 11472 times.
22944 if (::lstat(nativePath.constData(), &metadata) != 0 ||
32
1/2
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 11472 times.
11472 !S_ISDIR(metadata.st_mode))
33 ✗ return true;
34
35 // Sticky directories such as /tmp are deliberately shared trust domains.
36 // Even when MailJD happens to run as their owner (notably uid 0), changing
37 // their mode would damage the system and using them directly would expose
38 // the SQLite filename and sidecars to local races.
39 11472 return (metadata.st_mode & S_ISVTX) != 0;
40 #else
41 Q_UNUSED(path)
42 return false;
43 #endif
44 11472 }
45
46 40395 inline bool restrictOwnedRegularFile(const QString &path) {
47
1/1
✓ Branch 2 → 3 taken 40395 times.
40395 const QFileInfo info(path);
48
10/11
✓ Branch 3 → 4 taken 40395 times.
✓ Branch 4 → 5 taken 40390 times.
✓ Branch 4 → 11 taken 5 times.
✓ Branch 5 → 6 taken 40390 times.
✓ Branch 6 → 7 taken 40390 times.
✗ Branch 6 → 11 not taken.
✓ Branch 7 → 8 taken 40390 times.
✓ Branch 8 → 9 taken 40381 times.
✓ Branch 8 → 11 taken 9 times.
✓ Branch 13 → 14 taken 14 times.
✓ Branch 13 → 15 taken 40381 times.
80776 if (info.isSymLink() || !info.exists() || !info.isFile() ||
49
2/3
✓ Branch 9 → 10 taken 40381 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 40381 times.
40381 !isOwnedByCurrentUser(info)) {
50 14 return false;
51 }
52
1/1
✓ Branch 16 → 17 taken 40381 times.
40381 return QFile::setPermissions(
53 40381 path, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
54 40395 }
55
56 22934 inline bool secureExistingSidecars(const QString &dbPath) {
57 91733 for (const QString &suffix : {QStringLiteral("-wal"),
58 22934 QStringLiteral("-shm"),
59
2/2
✓ Branch 43 → 17 taken 68800 times.
✓ Branch 43 → 44 taken 22933 times.
298139 QStringLiteral("-journal")}) {
60
1/1
✓ Branch 17 → 18 taken 68800 times.
68800 const QString sidecarPath = dbPath + suffix;
61
1/1
✓ Branch 18 → 19 taken 68800 times.
68800 const QFileInfo sidecar(sidecarPath);
62
7/8
✓ Branch 19 → 20 taken 68800 times.
✓ Branch 20 → 21 taken 47441 times.
✓ Branch 20 → 24 taken 21359 times.
✓ Branch 21 → 22 taken 47441 times.
✓ Branch 22 → 23 taken 47441 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 26 taken 47441 times.
✓ Branch 25 → 27 taken 21359 times.
68800 if (!sidecar.exists() && !sidecar.isSymLink())
63 47441 continue;
64
3/3
✓ Branch 27 → 28 taken 21359 times.
✓ Branch 28 → 29 taken 1 time.
✓ Branch 28 → 30 taken 21358 times.
21359 if (!restrictOwnedRegularFile(sidecarPath))
65 1 return false;
66 207978 }
67 22933 return true;
68 }
69
70 11471 inline bool preparePath(const QString &dbPath) {
71
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 11471 times.
11471 if (dbPath == QLatin1String(":memory:"))
72 ✗ return true;
73
74
1/1
✓ Branch 6 → 7 taken 11471 times.
11471 const QFileInfo info(dbPath);
75
1/1
✓ Branch 7 → 8 taken 11471 times.
11471 const QString parentPath = info.absolutePath();
76
1/1
✓ Branch 8 → 9 taken 11471 times.
11471 QFileInfo parentInfo(parentPath);
77
7/8
✓ Branch 9 → 10 taken 11471 times.
✓ Branch 10 → 11 taken 4 times.
✓ Branch 10 → 14 taken 11467 times.
✓ Branch 11 → 12 taken 4 times.
✓ Branch 12 → 13 taken 4 times.
✗ Branch 12 → 14 not taken.
✓ Branch 15 → 16 taken 4 times.
✓ Branch 15 → 24 taken 11467 times.
11471 if (!parentInfo.exists() && !parentInfo.isSymLink()) {
78
3/4
✓ Branch 17 → 18 taken 4 times.
✓ Branch 18 → 19 taken 4 times.
✓ Branch 21 → 22 taken 4 times.
✗ Branch 21 → 23 not taken.
4 if (!QDir().mkpath(parentPath))
79 4 return false;
80 ✗ parentInfo.refresh();
81 }
82
3/4
✓ Branch 24 → 25 taken 11467 times.
✓ Branch 26 → 27 taken 11466 times.
✓ Branch 27 → 28 taken 11466 times.
✗ Branch 27 → 32 not taken.
22933 if (parentInfo.isSymLink() || !parentInfo.isDir() ||
83
6/7
✓ Branch 25 → 26 taken 11466 times.
✓ Branch 25 → 32 taken 1 time.
✓ Branch 28 → 29 taken 11466 times.
✓ Branch 29 → 30 taken 11466 times.
✗ Branch 29 → 32 not taken.
✓ Branch 34 → 35 taken 2 times.
✓ Branch 34 → 36 taken 11465 times.
34399 !isOwnedByCurrentUser(parentInfo) ||
84
3/3
✓ Branch 30 → 31 taken 11466 times.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 33 taken 11465 times.
11466 isSharedSystemDirectory(parentPath))
85 2 return false;
86
1/1
✓ Branch 38 → 39 taken 11465 times.
11465 if (!QFile::setPermissions(
87
1/2
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 11465 times.
22930 parentPath, QFileDevice::ReadOwner | QFileDevice::WriteOwner |
88 QFileDevice::ExeOwner))
89 ✗ return false;
90
91 // Reject sidecars planted while the directory was still too permissive.
92
3/3
✓ Branch 41 → 42 taken 11465 times.
✓ Branch 42 → 43 taken 1 time.
✓ Branch 42 → 44 taken 11464 times.
11465 if (!secureExistingSidecars(dbPath))
93 1 return false;
94
95
7/8
✓ Branch 44 → 45 taken 11464 times.
✓ Branch 45 → 46 taken 3897 times.
✓ Branch 45 → 48 taken 7567 times.
✓ Branch 46 → 47 taken 3897 times.
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 49 taken 3897 times.
✓ Branch 50 → 51 taken 7567 times.
✓ Branch 50 → 53 taken 3897 times.
11464 if (info.exists() || info.isSymLink())
96
1/1
✓ Branch 51 → 52 taken 7567 times.
7567 return restrictOwnedRegularFile(dbPath);
97
98
1/1
✓ Branch 53 → 54 taken 3897 times.
3897 QFile file(dbPath);
99
1/1
✓ Branch 56 → 57 taken 3897 times.
3897 return file.open(QIODevice::WriteOnly | QIODevice::NewOnly,
100 3897 QFileDevice::ReadOwner | QFileDevice::WriteOwner);
101 11471 }
102
103 11469 inline bool restrictExistingFile(const QString &dbPath) {
104
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 11469 times.
11469 if (dbPath == QLatin1String(":memory:"))
105 ✗ return true;
106
107
2/4
✓ Branch 7 → 8 taken 11469 times.
✗ Branch 7 → 11 not taken.
✓ Branch 9 → 10 taken 11469 times.
✗ Branch 9 → 11 not taken.
11469 return restrictOwnedRegularFile(dbPath) && secureExistingSidecars(dbPath);
108 }
109
110 } // namespace DatabaseSecurity
111