app/core/DavPushSecrets.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "DavPushSecrets.h" | ||
| 2 | |||
| 3 | #include "data/CredentialStore.h" | ||
| 4 | |||
| 5 | #include <QJsonDocument> | ||
| 6 | #include <QJsonObject> | ||
| 7 | #include <QPointer> | ||
| 8 | #include <QRandomGenerator> | ||
| 9 | #include <QRegularExpression> | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | const QString kService = QStringLiteral("davpush"); | ||
| 13 | } // namespace | ||
| 14 | |||
| 15 | 3 | void KeyringDavPushSecretStore::read(QObject *owner, const QString &key, ReadCallback callback) { | |
| 16 |
3/6✓ Branch 2 → 3 taken 3 times.
✓ Branch 3 → 4 taken 3 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 3 times.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
|
3 | auto *store = new CredentialStore(owner); |
| 17 |
1/1✓ Branch 6 → 7 taken 3 times.
|
3 | QPointer<QObject> guard(owner); |
| 18 |
3/5✓ Branch 8 → 9 taken 3 times.
✓ Branch 9 → 10 taken 3 times.
✓ Branch 10 → 11 taken 3 times.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 24 not taken.
|
3 | store->readPassword(kService, key, [store, guard, callback](bool success, const QByteArray &password) { |
| 19 | 2 | store->deleteLater(); | |
| 20 |
5/7✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 14 not taken.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 10 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 10 not taken.
✓ Branch 11 → 12 taken 2 times.
|
2 | if (guard) callback(success && !password.isEmpty(), password); |
| 21 | 2 | }); | |
| 22 | 3 | } | |
| 23 | |||
| 24 | 2 | void KeyringDavPushSecretStore::write(QObject *owner, const QString &key, const QByteArray &value, | |
| 25 | DoneCallback callback) { | ||
| 26 |
3/6✓ Branch 2 → 3 taken 2 times.
✓ Branch 3 → 4 taken 2 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2 times.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
|
2 | auto *store = new CredentialStore(owner); |
| 27 |
1/1✓ Branch 6 → 7 taken 2 times.
|
2 | QPointer<QObject> guard(owner); |
| 28 |
3/5✓ Branch 8 → 9 taken 2 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 10 → 11 taken 2 times.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 24 not taken.
|
2 | store->writePassword(kService, key, value, [store, guard, callback](bool success, const QString &) { |
| 29 | 2 | store->deleteLater(); | |
| 30 |
1/2✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 6 not taken.
|
2 | if (guard) callback(success); |
| 31 | 2 | }); | |
| 32 | 2 | } | |
| 33 | |||
| 34 | 1 | void KeyringDavPushSecretStore::remove(QObject *owner, const QString &key, DoneCallback callback) { | |
| 35 |
3/6✓ Branch 2 → 3 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
|
1 | auto *store = new CredentialStore(owner); |
| 36 |
1/1✓ Branch 6 → 7 taken 1 time.
|
1 | QPointer<QObject> guard(owner); |
| 37 |
1/1✓ Branch 10 → 11 taken 1 time.
|
1 | store->deletePassword( |
| 38 | kService, key, | ||
| 39 |
2/4✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 24 not taken.
|
2 | [store, guard, callback](bool success) { |
| 40 | 1 | store->deleteLater(); | |
| 41 |
1/2✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 6 not taken.
|
1 | if (guard) callback(success); |
| 42 | 1 | }, | |
| 43 | true); | ||
| 44 | 1 | } | |
| 45 | |||
| 46 | 36 | QByteArray DavPushReceiverSecret::serialize() const { | |
| 47 | const QJsonObject object{ | ||
| 48 | 72 | {QStringLiteral("v"), 1}, | |
| 49 | 72 | {QStringLiteral("channel"), channel}, | |
| 50 |
2/2✓ Branch 14 → 15 taken 36 times.
✓ Branch 15 → 16 taken 36 times.
|
72 | {QStringLiteral("private"), QString::fromLatin1(keys.privateKeyDer.toBase64())}, |
| 51 |
1/1✓ Branch 20 → 21 taken 36 times.
|
72 | {QStringLiteral("public"), WebPushPayloadCodec::encodeBase64Url(keys.publicKey)}, |
| 52 |
3/5✓ Branch 25 → 26 taken 36 times.
✓ Branch 29 → 30 taken 180 times.
✓ Branch 29 → 31 taken 36 times.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 55 not taken.
|
252 | {QStringLiteral("auth"), WebPushPayloadCodec::encodeBase64Url(keys.authSecret)}}; |
| 53 |
2/2✓ Branch 45 → 46 taken 36 times.
✓ Branch 46 → 47 taken 36 times.
|
72 | return QJsonDocument(object).toJson(QJsonDocument::Compact); |
| 54 | 252 | } | |
| 55 | |||
| 56 | 16 | std::optional<DavPushReceiverSecret> DavPushReceiverSecret::parse(const QByteArray &json) { | |
| 57 |
2/2✓ Branch 2 → 3 taken 16 times.
✓ Branch 3 → 4 taken 16 times.
|
16 | const QJsonObject object = QJsonDocument::fromJson(json).object(); |
| 58 |
4/4✓ Branch 8 → 9 taken 16 times.
✓ Branch 9 → 10 taken 16 times.
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 14 times.
|
16 | if (object.value(QStringLiteral("v")).toInt() != 1) return std::nullopt; |
| 59 | 14 | DavPushReceiverSecret secret; | |
| 60 |
2/2✓ Branch 18 → 19 taken 14 times.
✓ Branch 19 → 20 taken 14 times.
|
14 | secret.channel = object.value(QStringLiteral("channel")).toString(); |
| 61 |
4/4✓ Branch 29 → 30 taken 14 times.
✓ Branch 30 → 31 taken 14 times.
✓ Branch 31 → 32 taken 14 times.
✓ Branch 32 → 33 taken 14 times.
|
14 | secret.keys.privateKeyDer = QByteArray::fromBase64(object.value(QStringLiteral("private")).toString().toLatin1()); |
| 62 | secret.keys.publicKey = | ||
| 63 |
3/3✓ Branch 43 → 44 taken 14 times.
✓ Branch 44 → 45 taken 14 times.
✓ Branch 45 → 46 taken 14 times.
|
14 | WebPushPayloadCodec::decodeBase64Url(object.value(QStringLiteral("public")).toString()).value_or(QByteArray()); |
| 64 | secret.keys.authSecret = | ||
| 65 |
3/3✓ Branch 59 → 60 taken 14 times.
✓ Branch 60 → 61 taken 14 times.
✓ Branch 61 → 62 taken 14 times.
|
14 | WebPushPayloadCodec::decodeBase64Url(object.value(QStringLiteral("auth")).toString()).value_or(QByteArray()); |
| 66 |
2/2✓ Branch 73 → 74 taken 1 time.
✓ Branch 73 → 75 taken 13 times.
|
14 | if (!secret.isValid()) return std::nullopt; |
| 67 | 13 | return secret; | |
| 68 | 16 | } | |
| 69 | |||
| 70 | 35 | QString DavPushReceiverSecret::newChannel() { | |
| 71 | // ntfy recognises UnifiedPush topics only as "up" + 12 characters; with | ||
| 72 | // subscriber-based rate limiting (used for UnifiedPush) any other name is | ||
| 73 | // refused with 507. 12 uniform characters of [A-Za-z0-9] ≈ 71 bits from | ||
| 74 | // the system CSPRNG (rejection sampling keeps the distribution uniform). | ||
| 75 | static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | ||
| 76 | 35 | QString channel = QStringLiteral("up"); | |
| 77 |
2/2✓ Branch 16 → 7 taken 432 times.
✓ Branch 16 → 17 taken 35 times.
|
467 | while (channel.size() < kChannelLength) { |
| 78 |
2/3✓ Branch 7 → 8 taken 432 times.
✗ Branch 7 → 21 not taken.
✓ Branch 8 → 9 taken 432 times.
|
432 | const quint32 value = QRandomGenerator::system()->generate() & 0xFF; |
| 79 |
2/2✓ Branch 9 → 10 taken 12 times.
✓ Branch 9 → 11 taken 420 times.
|
432 | if (value >= 248) continue; // 248 = 4 × 62 |
| 80 |
1/1✓ Branch 13 → 14 taken 420 times.
|
420 | channel.append(QLatin1Char(alphabet[value % 62])); |
| 81 | } | ||
| 82 | 35 | return channel; | |
| 83 | ✗ | } | |
| 84 | |||
| 85 | 4 | bool DavPushReceiverSecret::hasProviderChannel() const { | |
| 86 |
4/7✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 15 taken 3 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
|
5 | static const QRegularExpression pattern(QStringLiteral("^up[A-Za-z0-9]{12}$")); |
| 87 |
2/2✓ Branch 16 → 17 taken 4 times.
✓ Branch 17 → 18 taken 4 times.
|
4 | return pattern.match(channel).hasMatch(); |
| 88 | } | ||
| 89 |