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: 98.9% 92 / 0 / 93
Functions: 100.0% 6 / 0 / 6
Branches: 84.4% 184 / 0 / 218

service/CalDavSchedulingXml.cpp
Line Branch Exec Source
1 // Sprint 108 S108.8.4/8.5 (RFC 6638): the scheduling half of CalDavXml —
2 // the principal's outbox and addresses, the VFREEBUSY request and the
3 // schedule-response (declared in CalDavXml.h; fuzz target freebusy).
4 #include "CalDavXml.h"
5 #include "DavXmlHelper.h"
6 #include "data/ICalEventCodec.h"
7 #include "data/ICalendar.h"
8
9 #include <QDomDocument>
10 #include <QTimeZone>
11 #include <algorithm>
12
13 using namespace DavXmlHelper;
14
15 namespace CalDavXml {
16
17 // ── Sprint 108: RFC 6638 scheduling ─────────────────────────────────────
18
19 namespace {
20
21 constexpr int kMaxSchedulingAddresses = 50;
22 constexpr int kMaxFreeBusyRecipients = 64;
23 constexpr int kMaxBusyIntervals = 2000;
24 constexpr qsizetype kMaxFreeBusyXml = 1024 * 1024;
25
26 7 QString mailtoAddress(const QString &value) {
27
1/1
✓ Branch 2 → 3 taken 7 times.
7 const QString text = value.trimmed();
28
3/3
✓ Branch 4 → 5 taken 7 times.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 6 times.
7 if (!text.startsWith(QLatin1String("mailto:"), Qt::CaseInsensitive)) return {};
29
3/3
✓ Branch 7 → 8 taken 6 times.
✓ Branch 8 → 9 taken 6 times.
✓ Branch 9 → 10 taken 6 times.
6 const QString address = text.mid(7).trimmed().toLower();
30
3/5
✓ Branch 14 → 15 taken 6 times.
✓ Branch 15 → 16 taken 6 times.
✗ Branch 15 → 19 not taken.
✓ Branch 17 → 18 taken 6 times.
✗ Branch 17 → 19 not taken.
6 return address.contains(QLatin1Char('@')) && address.size() <= 320 ? address : QString();
31 7 }
32
33 10 QDateTime utcValue(const QString &text) {
34
2/2
✓ Branch 5 → 6 taken 10 times.
✓ Branch 6 → 7 taken 10 times.
10 QDateTime value = QDateTime::fromString(text.trimmed(), QStringLiteral("yyyyMMdd'T'HHmmss'Z'"));
35
1/1
✓ Branch 11 → 12 taken 10 times.
10 value.setTimeZone(QTimeZone::UTC);
36 10 return value;
37 ✗ }
38
39 } // namespace
40
41 3 Scheduling parseScheduling(const QDomDocument &doc) {
42 3 Scheduling scheduling;
43
1/1
✓ Branch 2 → 3 taken 3 times.
3 const QDomElement root = doc.documentElement();
44
1/1
✓ Branch 6 → 7 taken 3 times.
3 scheduling.outboxHref = hrefOfProperty(root, QStringLiteral("schedule-outbox-URL"));
45
1/1
✓ Branch 14 → 15 taken 3 times.
3 const QDomElement set = findFirstElementByLocalName(root, QStringLiteral("calendar-user-address-set"));
46
3/3
✓ Branch 17 → 18 taken 3 times.
✓ Branch 18 → 19 taken 2 times.
✓ Branch 18 → 20 taken 1 time.
3 if (set.isNull()) return scheduling;
47
6/6
✓ Branch 20 → 21 taken 1 time.
✓ Branch 60 → 61 taken 3 times.
✓ Branch 61 → 62 taken 3 times.
✓ Branch 64 → 65 taken 4 times.
✓ Branch 65 → 22 taken 3 times.
✓ Branch 65 → 66 taken 1 time.
4 for (QDomNode node = set.firstChild(); !node.isNull(); node = node.nextSibling()) {
48
1/1
✓ Branch 22 → 23 taken 3 times.
3 const QDomElement element = node.toElement();
49
6/12
✓ Branch 23 → 24 taken 3 times.
✓ Branch 24 → 25 taken 3 times.
✗ Branch 24 → 29 not taken.
✓ Branch 26 → 27 taken 3 times.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 3 times.
✓ Branch 31 → 32 taken 3 times.
✗ Branch 31 → 33 not taken.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 3 times.
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 87 not taken.
3 if (element.isNull() || localNameOf(element) != QLatin1String("href")) continue;
50
2/2
✓ Branch 35 → 36 taken 3 times.
✓ Branch 36 → 37 taken 3 times.
3 const QString address = mailtoAddress(element.text());
51
7/7
✓ Branch 39 → 40 taken 2 times.
✓ Branch 39 → 43 taken 1 time.
✓ Branch 41 → 42 taken 1 time.
✓ Branch 41 → 43 taken 1 time.
✓ Branch 44 → 45 taken 1 time.
✓ Branch 44 → 46 taken 2 times.
✓ Branch 45 → 46 taken 1 time.
3 if (!address.isEmpty() && !scheduling.addresses.contains(address)) scheduling.addresses.append(address);
52
1/2
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 49 taken 3 times.
3 if (scheduling.addresses.size() >= kMaxSchedulingAddresses) break;
53 4 }
54 1 return scheduling;
55 3 }
56
57 4 QByteArray freeBusyRequest(const QString &uid, const QString &organizer, const QStringList &attendees,
58 const QDateTime &from, const QDateTime &to, const QDateTime &now) {
59 12 const auto stamp = [](const QDateTime &value) {
60
2/2
✓ Branch 2 → 3 taken 12 times.
✓ Branch 6 → 7 taken 12 times.
24 return value.toUTC().toString(QStringLiteral("yyyyMMdd'T'HHmmss'Z'"));
61 };
62 4 QStringList lines{QStringLiteral("BEGIN:VCALENDAR"), QStringLiteral("VERSION:2.0"),
63 4 QStringLiteral("PRODID:-//MailJD//MailJD Calendar//EN"), QStringLiteral("METHOD:REQUEST"),
64 8 QStringLiteral("BEGIN:VFREEBUSY"), QStringLiteral("UID:") + uid,
65 12 QStringLiteral("DTSTAMP:") + stamp(now), QStringLiteral("DTSTART:") + stamp(from),
66 8 QStringLiteral("DTEND:") + stamp(to),
67
3/5
✓ Branch 37 → 38 taken 4 times.
✓ Branch 44 → 45 taken 40 times.
✓ Branch 44 → 46 taken 4 times.
✗ Branch 116 → 117 not taken.
✗ Branch 116 → 118 not taken.
52 QStringLiteral("ORGANIZER:mailto:") + organizer.trimmed().toLower()};
68
5/5
✓ Branch 66 → 67 taken 4 times.
✓ Branch 67 → 68 taken 4 times.
✓ Branch 68 → 69 taken 4 times.
✓ Branch 85 → 70 taken 5 times.
✓ Branch 85 → 86 taken 4 times.
9 for (const QString &attendee : attendees.mid(0, kMaxFreeBusyRecipients))
69
4/4
✓ Branch 71 → 72 taken 5 times.
✓ Branch 72 → 73 taken 5 times.
✓ Branch 76 → 77 taken 5 times.
✓ Branch 77 → 78 taken 5 times.
14 lines.append(QStringLiteral("ATTENDEE:mailto:") + attendee.trimmed().toLower());
70
2/2
✓ Branch 90 → 91 taken 4 times.
✓ Branch 94 → 95 taken 4 times.
8 lines << QStringLiteral("END:VFREEBUSY") << QStringLiteral("END:VCALENDAR");
71 4 QString text;
72
6/6
✓ Branch 99 → 100 taken 4 times.
✓ Branch 100 → 101 taken 4 times.
✓ Branch 103 → 104 taken 53 times.
✓ Branch 104 → 105 taken 53 times.
✓ Branch 108 → 102 taken 53 times.
✓ Branch 108 → 109 taken 4 times.
57 for (const QString &line : lines) text += ICal::foldLine(line); // each ends in CRLF
73
1/1
✓ Branch 109 → 110 taken 4 times.
8 return text.toUtf8();
74 48 }
75
76 5 QList<FreeBusy> parseFreeBusyResponse(const QByteArray &xml, const QDateTime &from, const QDateTime &to) {
77 5 QList<FreeBusy> out;
78
10/13
✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 11 taken 1 time.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 5 → 6 taken 4 times.
✗ Branch 5 → 11 not taken.
✓ Branch 6 → 7 taken 4 times.
✓ Branch 7 → 8 taken 4 times.
✗ Branch 7 → 11 not taken.
✓ Branch 8 → 9 taken 4 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 4 times.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 4 times.
5 if (xml.size() > kMaxFreeBusyXml || !from.isValid() || !to.isValid() || to <= from) return out;
79
1/1
✓ Branch 15 → 16 taken 4 times.
4 QDomDocument doc;
80
3/3
✓ Branch 16 → 17 taken 4 times.
✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 19 taken 3 times.
4 if (!DavXmlHelper::parseInto(doc, xml)) return out; // no DTD or entities; bounded depth
81
1/1
✓ Branch 22 → 23 taken 3 times.
3 const ElementList responses = findElementsByLocalNameDoc(doc, QStringLiteral("response"));
82
2/2
✓ Branch 275 → 26 taken 4 times.
✓ Branch 275 → 276 taken 3 times.
7 for (int index = 0; index < responses.size(); ++index) {
83
1/2
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 4 times.
4 if (out.size() >= kMaxFreeBusyRecipients) break;
84
2/2
✓ Branch 29 → 30 taken 4 times.
✓ Branch 30 → 31 taken 4 times.
4 const QDomElement response = responses.at(index).toElement();
85
1/1
✓ Branch 35 → 36 taken 4 times.
4 const QDomElement recipient = findFirstElementByLocalName(response, QStringLiteral("recipient"));
86
2/3
✓ Branch 38 → 39 taken 4 times.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 4 times.
4 if (recipient.isNull()) continue; // a DAV:multistatus response, not a schedule-response one
87 4 FreeBusy entry;
88
3/3
✓ Branch 44 → 45 taken 4 times.
✓ Branch 45 → 46 taken 4 times.
✓ Branch 46 → 47 taken 4 times.
4 entry.recipient = mailtoAddress(findFirstElementByLocalName(recipient, QStringLiteral("href")).text());
89
1/2
✗ Branch 54 → 55 not taken.
✓ Branch 54 → 56 taken 4 times.
4 if (entry.recipient.isEmpty()) continue;
90
3/3
✓ Branch 59 → 60 taken 4 times.
✓ Branch 60 → 61 taken 4 times.
✓ Branch 61 → 62 taken 4 times.
4 const QString status = findFirstElementByLocalName(response, QStringLiteral("request-status")).text().trimmed();
91
2/2
✓ Branch 69 → 70 taken 4 times.
✓ Branch 70 → 71 taken 4 times.
4 const QString data = findFirstElementByLocalName(response, QStringLiteral("calendar-data")).text();
92 // The VFREEBUSY sits inside the VCALENDAR (a bare one is taken too).
93
1/1
✓ Branch 77 → 78 taken 4 times.
8 ICal::Component freebusy = ICal::parseFirst(data, QStringLiteral("VCALENDAR"))
94
1/1
✓ Branch 81 → 82 taken 4 times.
8 .componentsNamed(QStringLiteral("VFREEBUSY"))
95
1/1
✓ Branch 82 → 83 taken 4 times.
4 .value(0);
96
3/3
✓ Branch 90 → 91 taken 1 time.
✓ Branch 90 → 100 taken 3 times.
✓ Branch 94 → 95 taken 1 time.
5 if (freebusy.name.isEmpty()) freebusy = ICal::parseFirst(data, QStringLiteral("VFREEBUSY"));
97
4/5
✓ Branch 101 → 102 taken 4 times.
✓ Branch 102 → 103 taken 3 times.
✓ Branch 102 → 107 taken 1 time.
✓ Branch 105 → 106 taken 3 times.
✗ Branch 105 → 107 not taken.
4 entry.known = status.startsWith(QLatin1String("2.")) && freebusy.name == QLatin1String("VFREEBUSY");
98
2/2
✓ Branch 108 → 109 taken 3 times.
✓ Branch 108 → 255 taken 1 time.
4 if (entry.known) {
99 3 QList<std::pair<QDateTime, QDateTime>> busy;
100
5/5
✓ Branch 112 → 113 taken 3 times.
✓ Branch 115 → 116 taken 3 times.
✓ Branch 116 → 117 taken 3 times.
✓ Branch 227 → 118 taken 5 times.
✓ Branch 227 → 228 taken 3 times.
8 for (const ICal::Property &property : freebusy.propertiesNamed(QStringLiteral("FREEBUSY"))) {
101
2/2
✓ Branch 122 → 123 taken 5 times.
✓ Branch 123 → 124 taken 5 times.
5 const QString type = property.parameter(QStringLiteral("FBTYPE")).toUpper();
102
5/6
✓ Branch 128 → 129 taken 2 times.
✓ Branch 128 → 139 taken 3 times.
✓ Branch 131 → 132 taken 2 times.
✗ Branch 131 → 139 not taken.
✓ Branch 134 → 135 taken 1 time.
✓ Branch 134 → 139 taken 1 time.
6 if (!type.isEmpty() && type != QLatin1String("BUSY") && type != QLatin1String("BUSY-TENTATIVE") &&
103
3/4
✓ Branch 137 → 138 taken 1 time.
✗ Branch 137 → 139 not taken.
✓ Branch 140 → 141 taken 1 time.
✓ Branch 140 → 142 taken 4 times.
6 type != QLatin1String("BUSY-UNAVAILABLE"))
104 1 continue;
105
5/5
✓ Branch 145 → 146 taken 4 times.
✓ Branch 146 → 147 taken 4 times.
✓ Branch 147 → 148 taken 4 times.
✓ Branch 217 → 149 taken 5 times.
✓ Branch 217 → 218 taken 4 times.
9 for (const QString &period : property.value.split(QLatin1Char(','))) {
106
1/2
✗ Branch 151 → 152 not taken.
✓ Branch 151 → 153 taken 5 times.
5 if (busy.size() >= kMaxBusyIntervals) break;
107 5 const qsizetype slash = period.indexOf(QLatin1Char('/'));
108
1/2
✗ Branch 156 → 157 not taken.
✓ Branch 156 → 158 taken 5 times.
5 if (slash <= 0) continue;
109
2/2
✓ Branch 158 → 159 taken 5 times.
✓ Branch 159 → 160 taken 5 times.
5 const QDateTime start = utcValue(period.left(slash));
110
2/2
✓ Branch 161 → 162 taken 5 times.
✓ Branch 162 → 163 taken 5 times.
5 QDateTime end = utcValue(period.mid(slash + 1));
111 5 qint64 seconds = 0;
112
14/20
✓ Branch 164 → 165 taken 5 times.
✓ Branch 165 → 166 taken 1 time.
✓ Branch 165 → 172 taken 4 times.
✓ Branch 166 → 167 taken 1 time.
✓ Branch 167 → 168 taken 1 time.
✓ Branch 168 → 169 taken 1 time.
✓ Branch 169 → 170 taken 1 time.
✗ Branch 169 → 172 not taken.
✓ Branch 170 → 171 taken 1 time.
✗ Branch 170 → 172 not taken.
✓ Branch 173 → 174 taken 1 time.
✓ Branch 173 → 175 taken 4 times.
✓ Branch 175 → 176 taken 1 time.
✓ Branch 175 → 177 taken 4 times.
✓ Branch 177 → 178 taken 1 time.
✓ Branch 177 → 183 taken 4 times.
✗ Branch 381 → 382 not taken.
✗ Branch 381 → 383 not taken.
✗ Branch 385 → 386 not taken.
✗ Branch 385 → 387 not taken.
5 if (!end.isValid() && ICalEventCodec::parseDuration(period.mid(slash + 1).trimmed(), &seconds) && seconds > 0)
113
1/1
✓ Branch 179 → 180 taken 1 time.
1 end = start.addSecs(qMin<qint64>(seconds, 366LL * 24 * 3600));
114
11/17
✓ Branch 183 → 184 taken 5 times.
✓ Branch 184 → 185 taken 5 times.
✗ Branch 184 → 196 not taken.
✓ Branch 185 → 186 taken 5 times.
✓ Branch 186 → 187 taken 5 times.
✗ Branch 186 → 196 not taken.
✓ Branch 187 → 188 taken 5 times.
✓ Branch 189 → 190 taken 5 times.
✗ Branch 189 → 196 not taken.
✓ Branch 190 → 191 taken 5 times.
✓ Branch 192 → 193 taken 5 times.
✗ Branch 192 → 196 not taken.
✓ Branch 193 → 194 taken 5 times.
✗ Branch 195 → 196 not taken.
✓ Branch 195 → 197 taken 5 times.
✗ Branch 198 → 199 not taken.
✓ Branch 198 → 200 taken 5 times.
5 if (!start.isValid() || !end.isValid() || end <= start || end <= from || start >= to) continue;
115
3/3
✓ Branch 200 → 201 taken 5 times.
✓ Branch 201 → 202 taken 5 times.
✓ Branch 203 → 204 taken 5 times.
5 busy.append({qMax(start, from), qMin(end, to)});
116 9 }
117 8 }
118
3/3
✓ Branch 229 → 230 taken 3 times.
✓ Branch 230 → 231 taken 3 times.
✓ Branch 231 → 232 taken 3 times.
3 std::sort(busy.begin(), busy.end());
119
4/4
✓ Branch 232 → 233 taken 3 times.
✓ Branch 233 → 234 taken 3 times.
✓ Branch 252 → 235 taken 5 times.
✓ Branch 252 → 253 taken 3 times.
8 for (const auto &interval : busy) {
120
8/8
✓ Branch 237 → 238 taken 2 times.
✓ Branch 237 → 243 taken 3 times.
✓ Branch 238 → 239 taken 2 times.
✓ Branch 239 → 240 taken 2 times.
✓ Branch 241 → 242 taken 1 time.
✓ Branch 241 → 243 taken 1 time.
✓ Branch 244 → 245 taken 1 time.
✓ Branch 244 → 249 taken 4 times.
5 if (!entry.busy.isEmpty() && interval.first <= entry.busy.last().second)
121
3/3
✓ Branch 245 → 246 taken 1 time.
✓ Branch 246 → 247 taken 1 time.
✓ Branch 247 → 248 taken 1 time.
1 entry.busy.last().second = qMax(entry.busy.last().second, interval.second);
122 else
123
1/1
✓ Branch 249 → 250 taken 4 times.
4 entry.busy.append(interval);
124 }
125 3 }
126
1/1
✓ Branch 255 → 256 taken 4 times.
4 out.append(entry);
127 4 }
128 3 return out;
129 4 }
130
131 } // namespace CalDavXml
132
133