data/MailPayloadReader.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "MailPayloadReader.h" | ||
| 2 | #include "MailCache.h" | ||
| 3 | #include <QCryptographicHash> | ||
| 4 | #include <memory> | ||
| 5 | #include <sqlite3.h> | ||
| 6 | |||
| 7 | 81 | MailPayloadReadResult MailPayloadReader::copy(const MailPayloadRequest &request, | |
| 8 | QIODevice &destination, | ||
| 9 | const std::atomic_bool *cancelled) { | ||
| 10 | 81 | MailPayloadReadResult result; | |
| 11 |
4/4✓ Branch 2 → 3 taken 115 times.
✓ Branch 2 → 6 taken 292 times.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 112 times.
|
407 | const auto interrupted = [cancelled] { return cancelled && cancelled->load(std::memory_order_relaxed); }; |
| 12 | 39 | const auto fail = [&result](const QString &error) { | |
| 13 | 39 | result.error = error; | |
| 14 | 39 | return result; | |
| 15 | 81 | }; | |
| 16 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 11 taken 80 times.
|
82 | if (interrupted()) return fail(QStringLiteral("Payload read cancelled")); |
| 17 |
2/2✓ Branch 12 → 13 taken 20 times.
✓ Branch 12 → 31 taken 1 time.
|
21 | if ((request.kind != MailPayloadRequest::Kind::Attachment && request.kind != MailPayloadRequest::Kind::RawSource) || |
| 18 |
6/6✓ Branch 14 → 15 taken 77 times.
✓ Branch 14 → 31 taken 2 times.
✓ Branch 16 → 17 taken 76 times.
✓ Branch 16 → 31 taken 1 time.
✓ Branch 18 → 19 taken 75 times.
✓ Branch 18 → 31 taken 1 time.
|
155 | !request.mailId.isValid() || request.databasePath.isEmpty() || |
| 19 |
5/5✓ Branch 20 → 21 taken 75 times.
✓ Branch 21 → 22 taken 74 times.
✓ Branch 21 → 31 taken 1 time.
✓ Branch 23 → 24 taken 73 times.
✓ Branch 23 → 31 taken 1 time.
|
225 | request.databasePath.size() > 4096 || request.databasePath.contains(QChar(0)) || |
| 20 |
3/3✓ Branch 24 → 25 taken 73 times.
✓ Branch 25 → 26 taken 72 times.
✓ Branch 25 → 31 taken 1 time.
|
147 | request.mailId.accountId.size() > 4096 || !destination.isWritable() || |
| 21 |
7/10✓ Branch 11 → 12 taken 21 times.
✓ Branch 11 → 13 taken 59 times.
✓ Branch 26 → 27 taken 72 times.
✗ Branch 26 → 31 not taken.
✓ Branch 27 → 28 taken 72 times.
✗ Branch 27 → 31 not taken.
✓ Branch 28 → 29 taken 72 times.
✗ Branch 28 → 31 not taken.
✓ Branch 33 → 34 taken 9 times.
✓ Branch 33 → 41 taken 71 times.
|
232 | request.skipBytes < 0 || request.maxBytes < -1 || request.maxBytes == 0 || |
| 22 |
4/4✓ Branch 29 → 30 taken 52 times.
✓ Branch 29 → 32 taken 20 times.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 32 taken 51 times.
|
72 | (request.kind == MailPayloadRequest::Kind::Attachment && request.attachmentId <= 0)) |
| 23 | 9 | return fail(QStringLiteral("Invalid payload request or destination")); | |
| 24 | |||
| 25 | 71 | sqlite3 *rawDatabase = nullptr; | |
| 26 |
1/1✓ Branch 41 → 42 taken 71 times.
|
71 | const auto path = request.databasePath.toUtf8(); |
| 27 |
1/1✓ Branch 43 → 44 taken 71 times.
|
71 | const int opened = sqlite3_open_v2(path.constData(), &rawDatabase, |
| 28 | SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_NOFOLLOW, nullptr); | ||
| 29 | 71 | const std::unique_ptr<sqlite3, decltype(&sqlite3_close)> database(rawDatabase, sqlite3_close); | |
| 30 | 10 | const auto databaseError = [&] { | |
| 31 |
1/1✓ Branch 3 → 4 taken 10 times.
|
10 | return rawDatabase ? QString::fromUtf8(sqlite3_errmsg(rawDatabase)) |
| 32 |
3/7✓ Branch 2 → 3 taken 10 times.
✗ Branch 2 → 6 not taken.
✓ Branch 5 → 9 taken 10 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 10 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
|
30 | : QStringLiteral("Cannot open payload database"); |
| 33 | 71 | }; | |
| 34 |
3/3✓ Branch 45 → 46 taken 9 times.
✓ Branch 45 → 50 taken 62 times.
✓ Branch 46 → 47 taken 9 times.
|
71 | if (opened != SQLITE_OK) return fail(databaseError()); |
| 35 |
1/1✓ Branch 50 → 51 taken 62 times.
|
62 | sqlite3_busy_timeout(rawDatabase, 100); |
| 36 |
2/3✓ Branch 51 → 52 taken 62 times.
✗ Branch 52 → 53 not taken.
✓ Branch 52 → 57 taken 62 times.
|
62 | if (sqlite3_exec(rawDatabase, "PRAGMA cache_size=-2048; PRAGMA mmap_size=0; BEGIN", nullptr, nullptr, nullptr) != SQLITE_OK) |
| 37 | ✗ | return fail(databaseError()); | |
| 38 | 62 | sqlite3_stmt *rawStatement = nullptr; | |
| 39 |
2/3✓ Branch 57 → 58 taken 62 times.
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 63 taken 62 times.
|
62 | if (sqlite3_prepare_v2(rawDatabase, "PRAGMA user_version", -1, &rawStatement, nullptr) != SQLITE_OK) |
| 40 | ✗ | return fail(databaseError()); | |
| 41 | 62 | std::unique_ptr<sqlite3_stmt, decltype(&sqlite3_finalize)> statement(rawStatement, sqlite3_finalize); | |
| 42 |
7/8✓ Branch 64 → 65 taken 62 times.
✓ Branch 65 → 66 taken 62 times.
✗ Branch 65 → 68 not taken.
✓ Branch 66 → 67 taken 62 times.
✓ Branch 67 → 68 taken 1 time.
✓ Branch 67 → 69 taken 61 times.
✓ Branch 70 → 71 taken 1 time.
✓ Branch 70 → 78 taken 61 times.
|
62 | if (sqlite3_step(rawStatement) != SQLITE_ROW || sqlite3_column_int(rawStatement, 0) != MailCache::SchemaVersion) |
| 43 | 1 | return fail(QStringLiteral("Payload reader requires the current initialized cache schema")); | |
| 44 | 61 | statement.reset(); | |
| 45 | |||
| 46 | 61 | const bool attachment = request.kind == MailPayloadRequest::Kind::Attachment; | |
| 47 | 61 | const char *sql = attachment | |
| 48 |
2/2✓ Branch 79 → 80 taken 41 times.
✓ Branch 79 → 81 taken 20 times.
|
61 | ? "SELECT a.id,typeof(a.data) FROM folders f JOIN headers h ON h.folder_id=f.id " |
| 49 | "JOIN attachments a ON a.header_id=h.id WHERE f.account=? AND f.id=? AND h.uid=? AND f.uidvalidity=? AND a.id=?" | ||
| 50 | : "SELECT b.header_id,typeof(b.raw_body) FROM folders f JOIN headers h ON h.folder_id=f.id " | ||
| 51 | "JOIN bodies b ON b.header_id=h.id WHERE f.account=? AND f.id=? AND h.uid=? AND f.uidvalidity=?"; | ||
| 52 | 61 | rawStatement = nullptr; | |
| 53 |
3/3✓ Branch 82 → 83 taken 61 times.
✓ Branch 83 → 84 taken 1 time.
✓ Branch 83 → 88 taken 60 times.
|
61 | if (sqlite3_prepare_v2(rawDatabase, sql, -1, &rawStatement, nullptr) != SQLITE_OK) |
| 54 |
1/1✓ Branch 84 → 85 taken 1 time.
|
1 | return fail(databaseError()); |
| 55 | 60 | statement.reset(rawStatement); | |
| 56 |
1/1✓ Branch 89 → 90 taken 60 times.
|
60 | const auto account = request.mailId.accountId.toUtf8(); |
| 57 |
1/1✓ Branch 92 → 93 taken 60 times.
|
60 | sqlite3_bind_text(rawStatement, 1, account.constData(), int(account.size()), SQLITE_TRANSIENT); |
| 58 |
1/1✓ Branch 93 → 94 taken 60 times.
|
60 | sqlite3_bind_int64(rawStatement, 2, request.mailId.folderId); |
| 59 |
1/1✓ Branch 94 → 95 taken 60 times.
|
60 | sqlite3_bind_int64(rawStatement, 3, request.mailId.uid); |
| 60 |
1/1✓ Branch 95 → 96 taken 60 times.
|
60 | sqlite3_bind_int64(rawStatement, 4, request.uidValidity); |
| 61 |
3/3✓ Branch 96 → 97 taken 40 times.
✓ Branch 96 → 98 taken 20 times.
✓ Branch 97 → 98 taken 40 times.
|
60 | if (attachment) sqlite3_bind_int64(rawStatement, 5, request.attachmentId); |
| 62 |
1/1✓ Branch 98 → 99 taken 60 times.
|
60 | const int selected = sqlite3_step(rawStatement); |
| 63 |
2/2✓ Branch 99 → 100 taken 7 times.
✓ Branch 99 → 110 taken 53 times.
|
60 | if (selected != SQLITE_ROW) |
| 64 |
2/7✓ Branch 100 → 101 taken 7 times.
✗ Branch 100 → 104 not taken.
✗ Branch 104 → 105 not taken.
✓ Branch 107 → 108 taken 7 times.
✗ Branch 107 → 109 not taken.
✗ Branch 214 → 215 not taken.
✗ Branch 214 → 216 not taken.
|
14 | return fail(selected == SQLITE_DONE ? QStringLiteral("Payload is missing or its mailbox identity changed") : databaseError()); |
| 65 |
1/1✓ Branch 110 → 111 taken 53 times.
|
53 | const qint64 row = sqlite3_column_int64(rawStatement, 0); |
| 66 |
2/2✓ Branch 111 → 112 taken 53 times.
✓ Branch 112 → 113 taken 53 times.
|
53 | const QByteArray type(reinterpret_cast<const char *>(sqlite3_column_text(rawStatement, 1))); |
| 67 | // typeof() inspects metadata. length(TEXT) would materialize a corrupt large | ||
| 68 | // value before the type guard can reject it; get the size from the BLOB handle. | ||
| 69 |
2/2✓ Branch 114 → 115 taken 6 times.
✓ Branch 114 → 122 taken 47 times.
|
59 | if (type != "blob") return fail(QStringLiteral("Payload is not available as a BLOB")); |
| 70 | 47 | statement.reset(); | |
| 71 | |||
| 72 | 47 | sqlite3_blob *rawBlob = nullptr; | |
| 73 |
5/5✓ Branch 123 → 124 taken 30 times.
✓ Branch 123 → 125 taken 17 times.
✓ Branch 126 → 127 taken 30 times.
✓ Branch 126 → 128 taken 17 times.
✓ Branch 129 → 130 taken 47 times.
|
47 | if (sqlite3_blob_open(rawDatabase, "main", attachment ? "attachments" : "bodies", |
| 74 |
1/2✗ Branch 130 → 131 not taken.
✓ Branch 130 → 135 taken 47 times.
|
47 | attachment ? "data" : "raw_body", row, 0, &rawBlob) != SQLITE_OK) |
| 75 | ✗ | return fail(databaseError()); | |
| 76 | 47 | const std::unique_ptr<sqlite3_blob, decltype(&sqlite3_blob_close)> blob(rawBlob, sqlite3_blob_close); | |
| 77 |
1/1✓ Branch 136 → 137 taken 47 times.
|
47 | const int size = sqlite3_blob_bytes(rawBlob); |
| 78 | 47 | result.totalBytes = size; | |
| 79 | // The window never materializes skipped bytes; the hash covers the window. | ||
| 80 | 47 | const qint64 start = qMin<qint64>(request.skipBytes, size); | |
| 81 | 47 | const qint64 end = request.maxBytes < 0 | |
| 82 |
2/2✓ Branch 138 → 139 taken 32 times.
✓ Branch 138 → 140 taken 15 times.
|
62 | ? qint64(size) |
| 83 | 15 | : qMin<qint64>(size, start + request.maxBytes); | |
| 84 |
1/1✓ Branch 142 → 143 taken 47 times.
|
47 | QCryptographicHash hash(QCryptographicHash::Sha256); |
| 85 |
1/1✓ Branch 144 → 145 taken 47 times.
|
47 | QByteArray buffer(qMin<qint64>(end - start, ChunkBytes), Qt::Uninitialized); |
| 86 |
2/2✓ Branch 182 → 146 taken 283 times.
✓ Branch 182 → 183 taken 43 times.
|
326 | for (qint64 offset = start; offset < end;) { |
| 87 |
2/2✓ Branch 147 → 148 taken 1 time.
✓ Branch 147 → 155 taken 282 times.
|
284 | if (interrupted()) return fail(QStringLiteral("Payload read cancelled")); |
| 88 | 282 | const int bytes = int(qMin<qint64>(end - offset, ChunkBytes)); | |
| 89 |
3/4✓ Branch 156 → 157 taken 282 times.
✓ Branch 157 → 158 taken 282 times.
✗ Branch 158 → 159 not taken.
✓ Branch 158 → 163 taken 282 times.
|
282 | if (sqlite3_blob_read(rawBlob, buffer.data(), bytes, int(offset)) != SQLITE_OK) |
| 90 | ✗ | return fail(databaseError()); | |
| 91 |
3/3✓ Branch 164 → 165 taken 282 times.
✓ Branch 165 → 166 taken 3 times.
✓ Branch 165 → 178 taken 279 times.
|
282 | if (destination.write(buffer.constData(), bytes) != bytes) |
| 92 |
2/2✓ Branch 170 → 171 taken 3 times.
✓ Branch 171 → 172 taken 3 times.
|
6 | return fail(QStringLiteral("Short payload write: %1").arg(destination.errorString())); |
| 93 | 279 | hash.addData(QByteArrayView(buffer.constData(), bytes)); | |
| 94 | 279 | result.bytes += bytes; | |
| 95 | 279 | offset += bytes; | |
| 96 | } | ||
| 97 |
2/2✓ Branch 184 → 185 taken 1 time.
✓ Branch 184 → 192 taken 42 times.
|
44 | if (interrupted()) return fail(QStringLiteral("Payload read cancelled")); |
| 98 |
2/2✓ Branch 192 → 193 taken 42 times.
✓ Branch 193 → 194 taken 42 times.
|
42 | result.sha256 = hash.result().toHex(); |
| 99 | 42 | result.completed = true; | |
| 100 | 42 | return result; | |
| 101 | 81 | } | |
| 102 |