presentation/RuleMatcher.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "RuleMatcher.h" | ||
| 2 | |||
| 3 | #include <QRegularExpression> | ||
| 4 | |||
| 5 | using Sieve::Condition; | ||
| 6 | |||
| 7 | namespace { | ||
| 8 | |||
| 9 | // i;ascii-casemap folds only A–Z (RFC 4790); i;unicode-casemap folds fully. | ||
| 10 | 10906 | QString fold(const QString &text, const QString &comparator) { | |
| 11 |
2/2✓ Branch 4 → 5 taken 24 times.
✓ Branch 4 → 6 taken 10882 times.
|
10906 | if (comparator == QLatin1String("i;octet")) |
| 12 | 24 | return text; | |
| 13 |
2/2✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 11 taken 10880 times.
|
10882 | if (comparator == QLatin1String("i;unicode-casemap")) |
| 14 |
1/1✓ Branch 9 → 10 taken 2 times.
|
2 | return text.toCaseFolded(); |
| 15 | 10880 | QString out = text; | |
| 16 |
4/4✓ Branch 12 → 13 taken 10880 times.
✓ Branch 13 → 14 taken 10880 times.
✓ Branch 32 → 15 taken 211727 times.
✓ Branch 32 → 33 taken 10880 times.
|
222607 | for (QChar &c : out) |
| 17 |
6/6✓ Branch 19 → 20 taken 175037 times.
✓ Branch 19 → 26 taken 36690 times.
✓ Branch 24 → 25 taken 1694 times.
✓ Branch 24 → 26 taken 173343 times.
✓ Branch 27 → 28 taken 1694 times.
✓ Branch 27 → 31 taken 210033 times.
|
211727 | if (c >= QLatin1Char('A') && c <= QLatin1Char('Z')) |
| 18 | 1694 | c = QChar(c.unicode() + 32); | |
| 19 | 10880 | return out; | |
| 20 | 10880 | } | |
| 21 | |||
| 22 | // Sieve :matches — '*' any run, '?' one character, '\\' escapes the next | ||
| 23 | // one. S106-323: iterative, backtracking only to the last '*' — O(value × | ||
| 24 | // pattern); the recursive form was exponential on "*a*a*a*…b" patterns. | ||
| 25 | 13 | bool globMatch(const QString &value, const QString &pattern) { | |
| 26 | 13 | qsizetype vi = 0, pi = 0, starPi = -1, starVi = 0; | |
| 27 |
2/2✓ Branch 37 → 3 taken 311 times.
✓ Branch 37 → 38 taken 12 times.
|
323 | while (vi < value.size()) { |
| 28 |
2/2✓ Branch 4 → 5 taken 295 times.
✓ Branch 4 → 33 taken 16 times.
|
311 | if (pi < pattern.size()) { |
| 29 | 295 | const QChar p = pattern.at(pi); | |
| 30 |
2/2✓ Branch 9 → 10 taken 23 times.
✓ Branch 9 → 11 taken 272 times.
|
295 | if (p == QLatin1Char('*')) { |
| 31 | 23 | starPi = pi++; | |
| 32 | 23 | starVi = vi; | |
| 33 | 82 | continue; | |
| 34 | } | ||
| 35 |
2/2✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 16 taken 269 times.
|
272 | if (p == QLatin1Char('?')) { |
| 36 | 3 | ++vi; | |
| 37 | 3 | ++pi; | |
| 38 | 3 | continue; | |
| 39 | } | ||
| 40 | 269 | QChar literal = p; | |
| 41 | 269 | qsizetype step = 1; | |
| 42 |
5/6✓ Branch 19 → 20 taken 6 times.
✓ Branch 19 → 23 taken 263 times.
✓ Branch 21 → 22 taken 6 times.
✗ Branch 21 → 23 not taken.
✓ Branch 24 → 25 taken 6 times.
✓ Branch 24 → 27 taken 263 times.
|
269 | if (p == QLatin1Char('\\') && pi + 1 < pattern.size()) { |
| 43 | 6 | literal = pattern.at(pi + 1); | |
| 44 | 6 | step = 2; | |
| 45 | } | ||
| 46 |
2/2✓ Branch 29 → 30 taken 56 times.
✓ Branch 29 → 31 taken 213 times.
|
269 | if (value.at(vi) == literal) { |
| 47 | 56 | ++vi; | |
| 48 | 56 | pi += step; | |
| 49 | 56 | continue; | |
| 50 | } | ||
| 51 | } | ||
| 52 |
2/2✓ Branch 33 → 34 taken 1 time.
✓ Branch 33 → 35 taken 228 times.
|
229 | if (starPi < 0) |
| 53 | 1 | return false; | |
| 54 | 228 | pi = starPi + 1; | |
| 55 | 228 | vi = ++starVi; | |
| 56 | } | ||
| 57 |
6/6✓ Branch 41 → 42 taken 5 times.
✓ Branch 41 → 48 taken 8 times.
✓ Branch 46 → 47 taken 1 time.
✓ Branch 46 → 48 taken 4 times.
✓ Branch 49 → 39 taken 1 time.
✓ Branch 49 → 50 taken 12 times.
|
13 | while (pi < pattern.size() && pattern.at(pi) == QLatin1Char('*')) |
| 58 | 1 | ++pi; | |
| 59 | 12 | return pi == pattern.size(); | |
| 60 | } | ||
| 61 | |||
| 62 | 3494 | QStringList addressParts(const QStringList &addresses, const QString &part) { | |
| 63 | 3494 | QStringList result; | |
| 64 |
2/2✓ Branch 29 → 4 taken 3462 times.
✓ Branch 29 → 30 taken 3494 times.
|
6956 | for (const QString &address : addresses) { |
| 65 | 3462 | const int at = address.lastIndexOf(QLatin1Char('@')); | |
| 66 |
2/2✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 17 taken 3461 times.
|
3462 | if (part == QLatin1String("localpart")) |
| 67 |
3/4✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
|
1 | result.append(at < 0 ? address : address.left(at)); |
| 68 |
2/2✓ Branch 19 → 20 taken 385 times.
✓ Branch 19 → 26 taken 3076 times.
|
3461 | else if (part == QLatin1String("domain")) |
| 69 |
3/4✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 385 times.
✓ Branch 22 → 23 taken 385 times.
✓ Branch 23 → 24 taken 385 times.
|
385 | result.append(at < 0 ? QString() : address.mid(at + 1)); |
| 70 | else | ||
| 71 |
1/1✓ Branch 26 → 27 taken 3076 times.
|
3076 | result.append(address); |
| 72 | } | ||
| 73 | 3494 | return result; | |
| 74 | ✗ | } | |
| 75 | |||
| 76 | } // namespace | ||
| 77 | |||
| 78 | 3601 | QStringList RuleMatcher::addresses(const QString &headerValue) { | |
| 79 | // S106-324: a quoted local part ("foo bar"@example.com, RFC 5322) is an | ||
| 80 | // address too; the server's address test sees it. | ||
| 81 |
4/7✓ Branch 2 → 3 taken 7 times.
✓ Branch 2 → 15 taken 3594 times.
✓ Branch 4 → 5 taken 7 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 7 times.
✗ Branch 91 → 92 not taken.
✗ Branch 91 → 93 not taken.
|
3608 | static const QRegularExpression angle(QStringLiteral("<((?:\"[^\"\\r\\n]*\"|[^<>\\s\"]+)@[^<>\\s]+)>")); |
| 82 |
4/7✓ Branch 15 → 16 taken 7 times.
✓ Branch 15 → 28 taken 3594 times.
✓ Branch 17 → 18 taken 7 times.
✗ Branch 17 → 28 not taken.
✓ Branch 22 → 23 taken 7 times.
✗ Branch 101 → 102 not taken.
✗ Branch 101 → 103 not taken.
|
3608 | static const QRegularExpression bare(QStringLiteral("((?:\"[^\"\\r\\n]*\"|[^\\s<>,;\"']+)@[^\\s<>,;\"']+)")); |
| 83 | 3601 | QStringList result; | |
| 84 | // Split on commas outside quotes; then take <addr> or a bare address. | ||
| 85 | 3601 | QStringList entries; | |
| 86 | 3601 | QString current; | |
| 87 | 3601 | bool quoted = false; | |
| 88 |
2/2✓ Branch 48 → 30 taken 104479 times.
✓ Branch 48 → 49 taken 3601 times.
|
108080 | for (const QChar c : headerValue) { |
| 89 |
2/2✓ Branch 33 → 34 taken 14 times.
✓ Branch 33 → 35 taken 104465 times.
|
104479 | if (c == QLatin1Char('"')) |
| 90 | 14 | quoted = !quoted; | |
| 91 |
6/6✓ Branch 38 → 39 taken 8 times.
✓ Branch 38 → 41 taken 104471 times.
✓ Branch 39 → 40 taken 4 times.
✓ Branch 39 → 41 taken 4 times.
✓ Branch 42 → 43 taken 4 times.
✓ Branch 42 → 46 taken 104475 times.
|
104479 | if (c == QLatin1Char(',') && !quoted) { |
| 92 |
1/1✓ Branch 43 → 44 taken 4 times.
|
4 | entries.append(current); |
| 93 | 4 | current.clear(); | |
| 94 | 4 | continue; | |
| 95 | } | ||
| 96 |
1/1✓ Branch 46 → 47 taken 104475 times.
|
104475 | current.append(c); |
| 97 | } | ||
| 98 |
1/1✓ Branch 49 → 50 taken 3601 times.
|
3601 | entries.append(current); |
| 99 |
4/4✓ Branch 50 → 51 taken 3601 times.
✓ Branch 51 → 52 taken 3601 times.
✓ Branch 79 → 53 taken 3605 times.
✓ Branch 79 → 80 taken 3601 times.
|
7206 | for (const QString &entry : entries) { |
| 100 |
1/1✓ Branch 55 → 56 taken 3605 times.
|
3605 | const QRegularExpressionMatch inAngle = angle.match(entry); |
| 101 |
3/3✓ Branch 56 → 57 taken 3605 times.
✓ Branch 57 → 58 taken 1970 times.
✓ Branch 57 → 62 taken 1635 times.
|
3605 | if (inAngle.hasMatch()) { |
| 102 |
2/2✓ Branch 58 → 59 taken 1970 times.
✓ Branch 59 → 60 taken 1970 times.
|
1970 | result.append(inAngle.captured(1)); |
| 103 | 1970 | continue; | |
| 104 | } | ||
| 105 |
1/1✓ Branch 63 → 64 taken 1635 times.
|
1635 | const QRegularExpressionMatch plain = bare.match(entry); |
| 106 |
3/3✓ Branch 64 → 65 taken 1635 times.
✓ Branch 65 → 66 taken 1552 times.
✓ Branch 65 → 70 taken 83 times.
|
1635 | if (plain.hasMatch()) |
| 107 |
2/2✓ Branch 66 → 67 taken 1552 times.
✓ Branch 67 → 68 taken 1552 times.
|
1552 | result.append(plain.captured(1)); |
| 108 | 3605 | } | |
| 109 | 3601 | return result; | |
| 110 | 3601 | } | |
| 111 | |||
| 112 | 5086 | bool RuleMatcher::matchValue(const QString &value, const QString &key, const QString &matchType, | |
| 113 | const QString &comparator) { | ||
| 114 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 23 taken 5085 times.
|
5086 | if (matchType == QLatin1String("regex")) { |
| 115 | 1 | QRegularExpression::PatternOptions options = QRegularExpression::NoPatternOption; | |
| 116 |
1/2✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 9 not taken.
|
1 | if (comparator != QLatin1String("i;octet")) |
| 117 | 1 | options |= QRegularExpression::CaseInsensitiveOption; | |
| 118 |
1/1✓ Branch 9 → 10 taken 1 time.
|
1 | const QRegularExpression expression(key, options); |
| 119 |
6/11✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 17 not taken.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 17 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
|
2 | return expression.isValid() && expression.match(value).hasMatch(); |
| 120 | 1 | } | |
| 121 |
1/1✓ Branch 23 → 24 taken 5085 times.
|
5085 | const QString folded = fold(value, comparator); |
| 122 |
1/1✓ Branch 24 → 25 taken 5085 times.
|
5085 | const QString foldedKey = fold(key, comparator); |
| 123 |
2/2✓ Branch 27 → 28 taken 2342 times.
✓ Branch 27 → 30 taken 2743 times.
|
5085 | if (matchType == QLatin1String("contains")) |
| 124 |
1/1✓ Branch 28 → 29 taken 2342 times.
|
2342 | return folded.contains(foldedKey); |
| 125 |
2/2✓ Branch 32 → 33 taken 13 times.
✓ Branch 32 → 34 taken 2730 times.
|
2743 | if (matchType == QLatin1String("matches")) |
| 126 | 13 | return globMatch(folded, foldedKey); | |
| 127 | 2730 | return folded == foldedKey; | |
| 128 | 5085 | } | |
| 129 | |||
| 130 | 5592 | RuleMatcher::Result RuleMatcher::evaluate(const Condition &condition, const Message &message) { | |
| 131 | 5592 | bool matched = false; | |
| 132 |
2/2✓ Branch 2 → 3 taken 4 times.
✓ Branch 2 → 9 taken 5588 times.
|
5592 | if (condition.kind == Condition::Kind::Size) { |
| 133 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 4 times.
|
4 | if (message.size < 0) |
| 134 | ✗ | return Result::Unknown; | |
| 135 |
2/2✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 7 taken 1 time.
|
5 | matched = condition.sizeOver ? quint64(message.size) > condition.sizeLimit |
| 136 | 1 | : quint64(message.size) < condition.sizeLimit; | |
| 137 |
2/2✓ Branch 9 → 10 taken 3 times.
✓ Branch 9 → 24 taken 5585 times.
|
5588 | } else if (condition.kind == Condition::Kind::Body) { |
| 138 |
1/2✓ Branch 10 → 11 taken 3 times.
✗ Branch 10 → 12 not taken.
|
3 | if (!message.bodyKnown) |
| 139 | 3 | return Result::Unknown; | |
| 140 | ✗ | for (const QString &key : condition.values) | |
| 141 | ✗ | matched = matched || matchValue(message.body, key, condition.matchType, condition.comparator); | |
| 142 | } else { | ||
| 143 |
2/2✓ Branch 24 → 25 taken 2173 times.
✓ Branch 24 → 37 taken 3412 times.
|
5585 | if (!message.allHeaders) |
| 144 |
2/2✓ Branch 35 → 27 taken 2173 times.
✓ Branch 35 → 36 taken 1833 times.
|
4006 | for (const QString &header : condition.headers) |
| 145 |
3/3✓ Branch 28 → 29 taken 2173 times.
✓ Branch 31 → 32 taken 340 times.
✓ Branch 31 → 33 taken 1833 times.
|
2173 | if (!message.headers.contains(header.toLower())) |
| 146 | 340 | return Result::Unknown; | |
| 147 | 5245 | QStringList values; | |
| 148 |
2/2✓ Branch 60 → 39 taken 5902 times.
✓ Branch 60 → 61 taken 5245 times.
|
11147 | for (const QString &header : condition.headers) { |
| 149 |
1/1✓ Branch 40 → 41 taken 5902 times.
|
5902 | const QStringList raw = message.headers.value(header.toLower()); |
| 150 |
2/2✓ Branch 43 → 44 taken 4078 times.
✓ Branch 43 → 56 taken 1824 times.
|
5902 | if (condition.kind == Condition::Kind::Address) { |
| 151 |
2/2✓ Branch 54 → 46 taken 3494 times.
✓ Branch 54 → 55 taken 4078 times.
|
7572 | for (const QString &entry : raw) |
| 152 |
3/3✓ Branch 47 → 48 taken 3494 times.
✓ Branch 48 → 49 taken 3494 times.
✓ Branch 49 → 50 taken 3494 times.
|
3494 | values += addressParts(addresses(entry), condition.addressPart); |
| 153 | } else { | ||
| 154 |
1/1✓ Branch 56 → 57 taken 1824 times.
|
1824 | values += raw; |
| 155 | } | ||
| 156 | 5902 | } | |
| 157 |
4/4✓ Branch 61 → 62 taken 5245 times.
✓ Branch 62 → 63 taken 5245 times.
✓ Branch 78 → 64 taken 4949 times.
✓ Branch 78 → 79 taken 4804 times.
|
9753 | for (const QString &value : values) { |
| 158 |
2/2✓ Branch 73 → 67 taken 5075 times.
✓ Branch 73 → 74 taken 4508 times.
|
9583 | for (const QString &key : condition.values) |
| 159 |
3/3✓ Branch 68 → 69 taken 5075 times.
✓ Branch 69 → 70 taken 441 times.
✓ Branch 69 → 71 taken 4634 times.
|
5075 | if (matchValue(value, key, condition.matchType, condition.comparator)) { |
| 160 | 441 | matched = true; | |
| 161 | 441 | break; | |
| 162 | } | ||
| 163 |
2/2✓ Branch 74 → 75 taken 441 times.
✓ Branch 74 → 76 taken 4508 times.
|
4949 | if (matched) |
| 164 | 441 | break; | |
| 165 | } | ||
| 166 | 5245 | } | |
| 167 |
2/2✓ Branch 81 → 82 taken 443 times.
✓ Branch 81 → 83 taken 4806 times.
|
5249 | return matched != condition.negated ? Result::Match : Result::NoMatch; |
| 168 | } | ||
| 169 | |||
| 170 | 5493 | RuleMatcher::Result RuleMatcher::evaluate(const Sieve::Rule &rule, const Message &message) { | |
| 171 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 5492 times.
|
5493 | if (rule.conditions.isEmpty()) |
| 172 | 1 | return Result::Match; | |
| 173 | 5492 | bool unknown = false; | |
| 174 |
2/2✓ Branch 19 → 7 taken 5592 times.
✓ Branch 19 → 20 taken 685 times.
|
6277 | for (const Condition &condition : rule.conditions) { |
| 175 |
1/1✓ Branch 8 → 9 taken 5592 times.
|
5592 | const Result result = evaluate(condition, message); |
| 176 |
2/2✓ Branch 9 → 10 taken 343 times.
✓ Branch 9 → 11 taken 5249 times.
|
5592 | if (result == Result::Unknown) { |
| 177 | 343 | unknown = true; | |
| 178 | 343 | continue; | |
| 179 | } | ||
| 180 |
4/4✓ Branch 11 → 12 taken 5248 times.
✓ Branch 11 → 14 taken 1 time.
✓ Branch 12 → 13 taken 4806 times.
✓ Branch 12 → 14 taken 442 times.
|
5249 | if (rule.matchAll && result == Result::NoMatch) |
| 181 | 4807 | return Result::NoMatch; | |
| 182 |
3/4✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 17 taken 442 times.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 17 not taken.
|
443 | if (!rule.matchAll && result == Result::Match) |
| 183 | 1 | return Result::Match; | |
| 184 | } | ||
| 185 |
2/2✓ Branch 20 → 22 taken 342 times.
✓ Branch 20 → 23 taken 343 times.
|
685 | if (unknown) |
| 186 | 342 | return Result::Unknown; | |
| 187 |
1/2✓ Branch 23 → 24 taken 343 times.
✗ Branch 23 → 25 not taken.
|
343 | return rule.matchAll ? Result::Match : Result::NoMatch; |
| 188 | } | ||
| 189 | |||
| 190 | 668 | QList<int> RuleMatcher::matchingBlocks(const Sieve::Document &document, const Message &message, | |
| 191 | int *skippedAdvanced) { | ||
| 192 | 668 | QList<int> result; | |
| 193 | 668 | int skipped = 0; | |
| 194 |
2/2✓ Branch 17 → 3 taken 6874 times.
✓ Branch 17 → 18 taken 385 times.
|
7259 | for (int i = 0; i < document.blocks.size(); ++i) { |
| 195 | 6874 | const Sieve::Block &block = document.blocks.at(i); | |
| 196 |
2/2✓ Branch 4 → 5 taken 275 times.
✓ Branch 4 → 6 taken 6599 times.
|
6874 | if (block.kind == Sieve::BlockKind::Advanced) { |
| 197 | 275 | ++skipped; | |
| 198 | 275 | continue; | |
| 199 | } | ||
| 200 |
4/4✓ Branch 6 → 7 taken 5476 times.
✓ Branch 6 → 8 taken 1123 times.
✓ Branch 7 → 8 taken 344 times.
✓ Branch 7 → 9 taken 5132 times.
|
6599 | if (block.kind != Sieve::BlockKind::Rule || !block.rule.enabled) |
| 201 | 1467 | continue; | |
| 202 |
3/3✓ Branch 9 → 10 taken 5132 times.
✓ Branch 10 → 11 taken 4847 times.
✓ Branch 10 → 12 taken 285 times.
|
5132 | if (evaluate(block.rule, message) != Result::Match) |
| 203 | 4847 | continue; | |
| 204 |
1/1✓ Branch 12 → 13 taken 285 times.
|
285 | result.append(i); |
| 205 |
2/2✓ Branch 13 → 14 taken 283 times.
✓ Branch 13 → 15 taken 2 times.
|
285 | if (block.rule.stop) |
| 206 | 283 | break; | |
| 207 | } | ||
| 208 |
2/2✓ Branch 18 → 19 taken 236 times.
✓ Branch 18 → 20 taken 432 times.
|
668 | if (skippedAdvanced) |
| 209 | 236 | *skippedAdvanced = skipped; | |
| 210 | 668 | return result; | |
| 211 | ✗ | } | |
| 212 | |||
| 213 | namespace { | ||
| 214 | |||
| 215 | // `broad` matches every value `narrow` matches (same field and comparator). | ||
| 216 | 992 | bool conditionCovers(const Condition &broad, const Condition &narrow) { | |
| 217 |
4/6✓ Branch 2 → 3 taken 550 times.
✓ Branch 2 → 5 taken 442 times.
✓ Branch 3 → 4 taken 550 times.
✗ Branch 3 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 550 times.
|
992 | if (broad.kind != narrow.kind || broad.negated || narrow.negated) |
| 218 |
1/1✓ Branch 5 → 6 taken 442 times.
|
442 | return broad == narrow; |
| 219 |
1/2✗ Branch 7 → 8 not taken.
✓ Branch 7 → 15 taken 550 times.
|
550 | if (broad.kind == Condition::Kind::Size) |
| 220 | ✗ | return broad.sizeOver == narrow.sizeOver && | |
| 221 | ✗ | (broad.sizeOver ? broad.sizeLimit <= narrow.sizeLimit : broad.sizeLimit >= narrow.sizeLimit); | |
| 222 | 550 | QStringList broadHeaders, narrowHeaders; | |
| 223 |
2/2✓ Branch 23 → 17 taken 554 times.
✓ Branch 23 → 24 taken 550 times.
|
1104 | for (const QString &header : broad.headers) |
| 224 |
2/2✓ Branch 18 → 19 taken 554 times.
✓ Branch 19 → 20 taken 554 times.
|
554 | broadHeaders.append(header.toLower()); |
| 225 |
2/2✓ Branch 32 → 26 taken 550 times.
✓ Branch 32 → 33 taken 550 times.
|
1100 | for (const QString &header : narrow.headers) |
| 226 |
2/2✓ Branch 27 → 28 taken 550 times.
✓ Branch 28 → 29 taken 550 times.
|
550 | narrowHeaders.append(header.toLower()); |
| 227 |
4/4✓ Branch 33 → 34 taken 550 times.
✓ Branch 34 → 35 taken 550 times.
✓ Branch 42 → 36 taken 550 times.
✓ Branch 42 → 43 taken 439 times.
|
989 | for (const QString &header : narrowHeaders) |
| 228 |
2/2✓ Branch 38 → 39 taken 111 times.
✓ Branch 38 → 40 taken 439 times.
|
550 | if (!broadHeaders.contains(header)) |
| 229 | 111 | return false; | |
| 230 |
5/6✓ Branch 44 → 45 taken 361 times.
✓ Branch 44 → 47 taken 78 times.
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 48 taken 361 times.
✓ Branch 49 → 50 taken 78 times.
✓ Branch 49 → 51 taken 361 times.
|
439 | if (broad.addressPart != narrow.addressPart || broad.comparator != narrow.comparator) |
| 231 | 78 | return false; | |
| 232 | 361 | const QString &bm = broad.matchType; | |
| 233 | 361 | const QString &nm = narrow.matchType; | |
| 234 |
2/2✓ Branch 100 → 53 taken 361 times.
✓ Branch 100 → 101 taken 94 times.
|
455 | for (const QString &key : narrow.values) { |
| 235 | 361 | bool covered = false; | |
| 236 |
2/2✓ Branch 95 → 56 taken 368 times.
✓ Branch 95 → 96 taken 267 times.
|
635 | for (const QString &broadKey : broad.values) { |
| 237 |
1/1✓ Branch 57 → 58 taken 368 times.
|
368 | const QString a = fold(broadKey, broad.comparator); |
| 238 |
1/1✓ Branch 58 → 59 taken 368 times.
|
368 | const QString b = fold(key, narrow.comparator); |
| 239 |
6/6✓ Branch 60 → 61 taken 263 times.
✓ Branch 60 → 64 taken 105 times.
✓ Branch 62 → 63 taken 17 times.
✓ Branch 62 → 64 taken 246 times.
✓ Branch 65 → 66 taken 17 times.
✓ Branch 65 → 67 taken 351 times.
|
368 | if (bm == nm && a == b) |
| 240 | 17 | covered = true; | |
| 241 |
7/8✓ Branch 69 → 70 taken 267 times.
✓ Branch 69 → 79 taken 84 times.
✓ Branch 72 → 73 taken 63 times.
✓ Branch 72 → 76 taken 204 times.
✓ Branch 75 → 76 taken 63 times.
✗ Branch 75 → 79 not taken.
✓ Branch 80 → 81 taken 77 times.
✓ Branch 80 → 82 taken 274 times.
|
618 | else if (bm == QLatin1String("contains") && (nm == QLatin1String("contains") || nm == QLatin1String("is")) && |
| 242 |
3/3✓ Branch 76 → 77 taken 267 times.
✓ Branch 77 → 78 taken 77 times.
✓ Branch 77 → 79 taken 190 times.
|
267 | b.contains(a)) |
| 243 | 77 | covered = true; | |
| 244 |
2/2✓ Branch 82 → 83 taken 94 times.
✓ Branch 82 → 84 taken 274 times.
|
368 | if (covered) |
| 245 | 94 | break; | |
| 246 | 462 | } | |
| 247 |
2/2✓ Branch 96 → 97 taken 267 times.
✓ Branch 96 → 98 taken 94 times.
|
361 | if (!covered) |
| 248 | 267 | return false; | |
| 249 | } | ||
| 250 | 94 | return true; | |
| 251 | 550 | } | |
| 252 | |||
| 253 | } // namespace | ||
| 254 | |||
| 255 | 910 | bool RuleMatcher::shadows(const Sieve::Rule &earlier, const Sieve::Rule &later) { | |
| 256 |
3/4✓ Branch 2 → 3 taken 910 times.
✗ Branch 2 → 4 not taken.
✓ Branch 3 → 4 taken 12 times.
✓ Branch 3 → 5 taken 898 times.
|
910 | if (!earlier.enabled || !earlier.stop) |
| 257 | 12 | return false; | |
| 258 |
2/2✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 897 times.
|
898 | if (earlier.conditions.isEmpty()) |
| 259 | 1 | return true; | |
| 260 |
1/2✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 897 times.
|
897 | if (later.conditions.isEmpty()) |
| 261 | ✗ | return false; | |
| 262 |
2/2✓ Branch 11 → 12 taken 896 times.
✓ Branch 11 → 39 taken 1 time.
|
897 | if (earlier.matchAll) { |
| 263 | // Every condition of `earlier` must be implied by some condition `later` | ||
| 264 | // requires (later must be allof, or have a single condition). | ||
| 265 |
5/6✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 16 taken 895 times.
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 16 not taken.
✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 19 taken 895 times.
|
896 | if (!later.matchAll && later.conditions.size() > 1) |
| 266 | 1 | return false; | |
| 267 |
2/2✓ Branch 37 → 21 taken 902 times.
✓ Branch 37 → 38 taken 86 times.
|
988 | for (const Condition &broad : earlier.conditions) { |
| 268 | 902 | bool implied = false; | |
| 269 |
2/2✓ Branch 32 → 24 taken 999 times.
✓ Branch 32 → 33 taken 902 times.
|
1901 | for (const Condition &narrow : later.conditions) |
| 270 |
5/5✓ Branch 25 → 26 taken 991 times.
✓ Branch 25 → 28 taken 8 times.
✓ Branch 26 → 27 taken 991 times.
✓ Branch 27 → 28 taken 93 times.
✓ Branch 27 → 29 taken 898 times.
|
999 | implied = implied || conditionCovers(broad, narrow); |
| 271 |
2/2✓ Branch 33 → 34 taken 809 times.
✓ Branch 33 → 35 taken 93 times.
|
902 | if (!implied) |
| 272 | 809 | return false; | |
| 273 | } | ||
| 274 | 86 | return true; | |
| 275 | } | ||
| 276 | // earlier is anyof: one covering condition suffices for an allof (or | ||
| 277 | // single-condition) rule; an anyof rule needs every alternative covered. | ||
| 278 | 1 | const auto coveredByEarlier = [&earlier](const Condition &narrow) { | |
| 279 |
1/2✓ Branch 10 → 4 taken 1 time.
✗ Branch 10 → 11 not taken.
|
1 | for (const Condition &broad : earlier.conditions) |
| 280 |
2/3✓ Branch 5 → 6 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
|
1 | if (conditionCovers(broad, narrow)) |
| 281 | 1 | return true; | |
| 282 | ✗ | return false; | |
| 283 | 1 | }; | |
| 284 |
2/6✗ Branch 39 → 40 not taken.
✓ Branch 39 → 42 taken 1 time.
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 43 not taken.
✓ Branch 44 → 45 taken 1 time.
✗ Branch 44 → 55 not taken.
|
1 | if (later.matchAll || later.conditions.size() == 1) { |
| 285 |
1/2✓ Branch 53 → 47 taken 1 time.
✗ Branch 53 → 54 not taken.
|
1 | for (const Condition &narrow : later.conditions) |
| 286 |
2/3✓ Branch 48 → 49 taken 1 time.
✓ Branch 49 → 50 taken 1 time.
✗ Branch 49 → 51 not taken.
|
1 | if (coveredByEarlier(narrow)) |
| 287 | 1 | return true; | |
| 288 | ✗ | return false; | |
| 289 | } | ||
| 290 | ✗ | for (const Condition &narrow : later.conditions) | |
| 291 | ✗ | if (!coveredByEarlier(narrow)) | |
| 292 | ✗ | return false; | |
| 293 | ✗ | return true; | |
| 294 | } | ||
| 295 |