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: 94.5% 208 / 0 / 220
Functions: 100.0% 11 / 0 / 11
Branches: 86.4% 324 / 0 / 375

service/CalDavXml.cpp
Line Branch Exec Source
1 #include "CalDavXml.h"
2
3 #include <QUrl>
4
5 #include "DavXmlHelper.h"
6 #include "data/ICalendar.h"
7 #include "data/ICalEventCodec.h"
8 #include "data/ICalTodoCodec.h"
9
10 #include <QDomDocument>
11 #include <QTextStream>
12 #include <QLoggingCategory>
13
14
4/7
✓ Branch 2 → 3 taken 15 times.
✓ Branch 2 → 8 taken 1085 times.
✓ Branch 4 → 5 taken 15 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 15 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
1100 Q_LOGGING_CATEGORY(lcCalDav, "mailjd.caldav")
15
16 using namespace DavXmlHelper;
17
18 namespace CalDavXml {
19
20 namespace {
21 10113 QString localTagName(const QDomElement &element) {
22
1/1
✓ Branch 2 → 3 taken 10113 times.
10113 const QString tag = element.tagName();
23
1/1
✓ Branch 6 → 7 taken 10113 times.
20226 return tag.mid(tag.indexOf(QLatin1Char(':')) + 1);
24 10113 }
25
26 4445 QDomElement firstChildByLocalName(const QDomElement &parent, const QString &name) {
27
6/8
✓ Branch 4 → 5 taken 4445 times.
✓ Branch 15 → 16 taken 3933 times.
✓ Branch 16 → 17 taken 3933 times.
✓ Branch 21 → 22 taken 8378 times.
✓ Branch 22 → 8 taken 7250 times.
✓ Branch 22 → 23 taken 1128 times.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
8378 for (QDomElement child = parent.firstChildElement(); !child.isNull(); child = child.nextSiblingElement())
28
3/3
✓ Branch 8 → 9 taken 7250 times.
✓ Branch 11 → 12 taken 3317 times.
✓ Branch 11 → 13 taken 3933 times.
8378 if (localTagName(child) == name) return child;
29 1128 return {};
30 }
31
32 // "HTTP/1.1 404 Not Found" -> 404; 0 when absent or malformed.
33 1798 int statusCodeOf(const QDomElement &status) {
34
3/3
✓ Branch 2 → 3 taken 1798 times.
✓ Branch 3 → 4 taken 1798 times.
✓ Branch 7 → 8 taken 1798 times.
3596 const QStringList parts = status.text().simplified().split(QLatin1Char(' '));
35
3/3
✓ Branch 11 → 12 taken 868 times.
✓ Branch 11 → 15 taken 930 times.
✓ Branch 13 → 14 taken 868 times.
3596 return parts.size() >= 2 ? parts.at(1).toInt() : 0;
36 1798 }
37
38 759 bool hasCalendarData(const QDomElement &propstat) {
39
1/1
✓ Branch 5 → 6 taken 759 times.
759 const QDomElement prop = firstChildByLocalName(propstat, QStringLiteral("prop"));
40
1/1
✓ Branch 11 → 12 taken 759 times.
759 const QDomElement data = firstChildByLocalName(prop, QStringLiteral("calendar-data"));
41
11/15
✓ Branch 14 → 15 taken 759 times.
✓ Branch 15 → 16 taken 603 times.
✓ Branch 15 → 21 taken 156 times.
✓ Branch 16 → 17 taken 603 times.
✓ Branch 17 → 18 taken 603 times.
✓ Branch 19 → 20 taken 601 times.
✓ Branch 19 → 21 taken 2 times.
✓ Branch 23 → 24 taken 603 times.
✓ Branch 23 → 25 taken 156 times.
✓ Branch 25 → 26 taken 603 times.
✓ Branch 25 → 27 taken 156 times.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 45 not taken.
✗ Branch 47 → 48 not taken.
✗ Branch 47 → 49 not taken.
1518 return !data.isNull() && !data.text().trimmed().isEmpty();
42 759 }
43
44 1246 bool validateICalendarText(const QString &icalText,
45 const QString &componentName,
46 QString *error) {
47
1/1
✓ Branch 2 → 3 taken 1246 times.
1246 const QString trimmed = icalText.trimmed();
48
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1246 times.
1246 if (trimmed.isEmpty())
49 ✗ return true;
50
51
6/11
✓ Branch 9 → 10 taken 1246 times.
✓ Branch 10 → 11 taken 1244 times.
✓ Branch 10 → 16 taken 2 times.
✓ Branch 24 → 25 taken 1246 times.
✗ Branch 24 → 26 not taken.
✓ Branch 26 → 27 taken 3 times.
✓ Branch 26 → 36 taken 1243 times.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
✗ Branch 87 → 88 not taken.
✗ Branch 87 → 89 not taken.
4982 if (!trimmed.contains(QStringLiteral("BEGIN:VCALENDAR")) ||
52
8/13
✓ Branch 14 → 15 taken 1244 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 1243 times.
✓ Branch 18 → 19 taken 1244 times.
✓ Branch 18 → 20 taken 2 times.
✓ Branch 20 → 21 taken 1244 times.
✓ Branch 20 → 22 taken 2 times.
✓ Branch 22 → 23 taken 1246 times.
✗ Branch 22 → 24 not taken.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
2490 !trimmed.contains(QStringLiteral("END:VCALENDAR"))) {
53
1/2
✓ Branch 27 → 28 taken 3 times.
✗ Branch 27 → 35 not taken.
3 if (error)
54 3 *error = QStringLiteral("Invalid iCalendar response");
55 3 return false;
56 }
57
58
1/1
✓ Branch 40 → 41 taken 1243 times.
2486 const QString beginComponent = QStringLiteral("BEGIN:%1").arg(componentName);
59
1/1
✓ Branch 47 → 48 taken 1243 times.
2486 const QString endComponent = QStringLiteral("END:%1").arg(componentName);
60
8/8
✓ Branch 50 → 51 taken 1243 times.
✓ Branch 51 → 52 taken 665 times.
✓ Branch 51 → 55 taken 578 times.
✓ Branch 52 → 53 taken 665 times.
✓ Branch 53 → 54 taken 2 times.
✓ Branch 53 → 55 taken 663 times.
✓ Branch 56 → 57 taken 2 times.
✓ Branch 56 → 69 taken 1241 times.
1243 if (trimmed.contains(beginComponent) && !trimmed.contains(endComponent)) {
61
1/2
✓ Branch 57 → 58 taken 2 times.
✗ Branch 57 → 68 not taken.
2 if (error)
62 4 *error = QStringLiteral("Invalid iCalendar %1 component")
63
1/1
✓ Branch 62 → 63 taken 2 times.
4 .arg(componentName);
64 2 return false;
65 }
66
67 1241 return true;
68 1246 }
69 } // namespace
70
71 220 bool summarizeMultistatus(const QByteArray &xml, MultistatusSummary *summary,
72 bool requireCalendarData, const QString &collectionPath) {
73 215 const auto pathOf = [](const QString &href) {
74
2/2
✓ Branch 2 → 3 taken 215 times.
✓ Branch 4 → 5 taken 215 times.
215 QString path = QUrl(href).path(QUrl::FullyEncoded);
75
4/4
✓ Branch 7 → 8 taken 213 times.
✓ Branch 10 → 11 taken 428 times.
✓ Branch 11 → 7 taken 213 times.
✓ Branch 11 → 12 taken 215 times.
428 while (path.endsWith(QLatin1Char('/'))) path.chop(1);
76 215 return path;
77 ✗ };
78
3/3
✓ Branch 3 → 4 taken 14 times.
✓ Branch 3 → 5 taken 206 times.
✓ Branch 5 → 6 taken 206 times.
220 const QString collection = collectionPath.isEmpty() ? QString() : pathOf(collectionPath);
79
1/1
✓ Branch 6 → 7 taken 220 times.
220 QDomDocument doc;
80
3/3
✓ Branch 7 → 8 taken 220 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 219 times.
220 if (!DavXmlHelper::parseInto(doc, xml)) return false;
81
1/1
✓ Branch 10 → 11 taken 219 times.
219 const QDomElement root = doc.documentElement();
82
3/3
✓ Branch 14 → 15 taken 219 times.
✓ Branch 15 → 16 taken 219 times.
✓ Branch 16 → 17 taken 219 times.
219 summary->token = firstChildByLocalName(root, QStringLiteral("sync-token")).text().trimmed();
83
4/4
✓ Branch 25 → 26 taken 219 times.
✓ Branch 139 → 140 taken 1308 times.
✓ Branch 140 → 29 taken 1089 times.
✓ Branch 140 → 141 taken 219 times.
1308 for (QDomElement response = root.firstChildElement(); !response.isNull();
84
2/2
✓ Branch 133 → 134 taken 1089 times.
✓ Branch 134 → 135 taken 1089 times.
1089 response = response.nextSiblingElement()) {
85
3/3
✓ Branch 30 → 31 taken 1089 times.
✓ Branch 33 → 34 taken 179 times.
✓ Branch 33 → 35 taken 910 times.
1111 if (localTagName(response) != QLatin1String("response")) continue;
86
3/3
✓ Branch 38 → 39 taken 910 times.
✓ Branch 39 → 40 taken 910 times.
✓ Branch 40 → 41 taken 910 times.
910 const QString href = firstChildByLocalName(response, QStringLiteral("href")).text().trimmed();
87
2/2
✓ Branch 48 → 49 taken 910 times.
✓ Branch 49 → 50 taken 910 times.
910 const int status = statusCodeOf(firstChildByLocalName(response, QStringLiteral("status")));
88
2/2
✓ Branch 53 → 54 taken 9 times.
✓ Branch 53 → 67 taken 901 times.
910 if (status == 507) {
89
7/11
✓ Branch 55 → 56 taken 9 times.
✗ Branch 55 → 59 not taken.
✓ Branch 56 → 57 taken 9 times.
✓ Branch 58 → 59 taken 8 times.
✓ Branch 58 → 60 taken 1 time.
✓ Branch 61 → 62 taken 9 times.
✗ Branch 61 → 63 not taken.
✓ Branch 63 → 64 taken 8 times.
✓ Branch 63 → 65 taken 1 time.
✗ Branch 190 → 191 not taken.
✗ Branch 190 → 192 not taken.
9 if (collection.isEmpty() || pathOf(href) == collection) summary->truncated = true;
90
1/1
✓ Branch 65 → 66 taken 1 time.
1 else summary->failed << href; // S106-052: one member, not the page
91 9 continue;
92 }
93
3/3
✓ Branch 67 → 68 taken 8 times.
✓ Branch 67 → 70 taken 893 times.
✓ Branch 68 → 69 taken 8 times.
901 if (status == 404) { summary->removed << href; continue; }
94
5/7
✓ Branch 70 → 71 taken 5 times.
✓ Branch 70 → 75 taken 888 times.
✓ Branch 71 → 72 taken 5 times.
✗ Branch 71 → 73 not taken.
✓ Branch 72 → 73 taken 5 times.
✗ Branch 72 → 75 not taken.
✓ Branch 73 → 74 taken 5 times.
893 if (status != 0 && (status < 200 || status >= 300)) { summary->failed << href; continue; }
95 888 bool complete = false;
96
7/8
✓ Branch 77 → 78 taken 888 times.
✓ Branch 115 → 116 taken 2662 times.
✓ Branch 116 → 117 taken 1774 times.
✓ Branch 116 → 119 taken 888 times.
✓ Branch 117 → 118 taken 1774 times.
✗ Branch 117 → 119 not taken.
✓ Branch 120 → 81 taken 1774 times.
✓ Branch 120 → 121 taken 888 times.
2662 for (QDomElement propstat = response.firstChildElement(); !propstat.isNull() && !complete;
97
2/2
✓ Branch 109 → 110 taken 1774 times.
✓ Branch 110 → 111 taken 1774 times.
1774 propstat = propstat.nextSiblingElement()) {
98
3/3
✓ Branch 82 → 83 taken 1774 times.
✓ Branch 85 → 86 taken 886 times.
✓ Branch 85 → 87 taken 888 times.
1774 if (localTagName(propstat) != QLatin1String("propstat")) continue;
99
2/2
✓ Branch 90 → 91 taken 888 times.
✓ Branch 91 → 92 taken 888 times.
888 const int code = statusCodeOf(firstChildByLocalName(propstat, QStringLiteral("status")));
100
5/6
✓ Branch 95 → 96 taken 846 times.
✓ Branch 95 → 98 taken 42 times.
✓ Branch 96 → 97 taken 846 times.
✗ Branch 96 → 99 not taken.
✓ Branch 97 → 98 taken 845 times.
✓ Branch 97 → 99 taken 1 time.
888 const bool ok = code == 0 || (code >= 200 && code < 300);
101
7/7
✓ Branch 100 → 101 taken 887 times.
✓ Branch 100 → 105 taken 1 time.
✓ Branch 101 → 102 taken 759 times.
✓ Branch 101 → 104 taken 128 times.
✓ Branch 102 → 103 taken 759 times.
✓ Branch 103 → 104 taken 601 times.
✓ Branch 103 → 105 taken 158 times.
888 complete = ok && (!requireCalendarData || hasCalendarData(propstat));
102 888 }
103
2/2
✓ Branch 122 → 123 taken 729 times.
✓ Branch 122 → 124 taken 159 times.
888 if (complete)
104
1/1
✓ Branch 123 → 125 taken 729 times.
729 summary->listed << href;
105 else
106
1/1
✓ Branch 124 → 125 taken 159 times.
159 summary->failed << href;
107 1129 }
108 219 return true;
109 220 }
110
111 233 bool parseEvents(const QByteArray &xmlData, QList<CalendarEvent> *events, QString *error) {
112
1/2
✓ Branch 2 → 3 taken 233 times.
✗ Branch 2 → 4 not taken.
233 if (events)
113
1/1
✓ Branch 3 → 4 taken 233 times.
233 events->clear();
114
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 14 taken 233 times.
233 if (!events) {
115 ✗ if (error)
116 ✗ *error = QStringLiteral("Invalid parser output");
117 ✗ return false;
118 }
119
120 // Parse the multistatus XML to extract calendar-data
121
1/1
✓ Branch 14 → 15 taken 233 times.
233 QDomDocument doc;
122 // T-511: Check XML parse result
123
3/3
✓ Branch 15 → 16 taken 233 times.
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 36 taken 231 times.
233 if (!DavXmlHelper::parseInto(doc, xmlData)) {
124
5/5
✓ Branch 17 → 18 taken 2 times.
✓ Branch 21 → 22 taken 2 times.
✓ Branch 22 → 23 taken 2 times.
✓ Branch 26 → 19 taken 2 times.
✓ Branch 26 → 27 taken 2 times.
4 qCWarning(lcCalDav) << "Failed to parse XML response";
125
1/2
✓ Branch 27 → 28 taken 2 times.
✗ Branch 27 → 35 not taken.
2 if (error)
126 2 *error = QStringLiteral("Invalid XML response");
127 2 return false;
128 }
129
130
1/1
✓ Branch 39 → 40 taken 231 times.
231 auto responses = findElementsByLocalNameDoc(doc, QStringLiteral("response"));
131
132
2/2
✓ Branch 179 → 43 taken 799 times.
✓ Branch 179 → 180 taken 228 times.
1027 for (int i = 0; i < responses.count(); ++i) {
133
2/2
✓ Branch 43 → 44 taken 799 times.
✓ Branch 44 → 45 taken 799 times.
799 QDomElement resp = responses.at(i).toElement();
134
135 799 QString href;
136
1/1
✓ Branch 49 → 50 taken 799 times.
799 auto hrefNodes = findElementsByLocalName(resp, QStringLiteral("href"));
137
2/2
✓ Branch 53 → 54 taken 795 times.
✓ Branch 53 → 64 taken 4 times.
799 if (!hrefNodes.isEmpty())
138
4/4
✓ Branch 54 → 55 taken 795 times.
✓ Branch 55 → 56 taken 795 times.
✓ Branch 56 → 57 taken 795 times.
✓ Branch 57 → 58 taken 795 times.
795 href = hrefNodes.at(0).toElement().text().trimmed();
139
140 // Get etag
141 799 QString etag;
142
1/1
✓ Branch 67 → 68 taken 799 times.
799 auto etagNodes = findElementsByLocalName(resp, QStringLiteral("getetag"));
143
2/2
✓ Branch 71 → 72 taken 777 times.
✓ Branch 71 → 80 taken 22 times.
799 if (!etagNodes.isEmpty())
144
3/3
✓ Branch 72 → 73 taken 777 times.
✓ Branch 73 → 74 taken 777 times.
✓ Branch 74 → 75 taken 777 times.
777 etag = etagNodes.at(0).toElement().text();
145
146 // Get calendar-data
147 799 QString icalText;
148
1/1
✓ Branch 83 → 84 taken 799 times.
799 auto dataNodes = findElementsByLocalName(resp, QStringLiteral("calendar-data"));
149
2/2
✓ Branch 87 → 88 taken 174 times.
✓ Branch 87 → 89 taken 625 times.
799 if (dataNodes.isEmpty())
150 174 continue;
151
3/3
✓ Branch 89 → 90 taken 625 times.
✓ Branch 90 → 91 taken 625 times.
✓ Branch 91 → 92 taken 625 times.
625 icalText = dataNodes.at(0).toElement().text();
152
2/2
✓ Branch 97 → 98 taken 3 times.
✓ Branch 97 → 99 taken 622 times.
625 if (icalText.isEmpty())
153 3 continue;
154
3/3
✓ Branch 102 → 103 taken 622 times.
✓ Branch 105 → 106 taken 3 times.
✓ Branch 105 → 107 taken 619 times.
622 if (!validateICalendarText(icalText, QStringLiteral("VEVENT"), error))
155 3 return false;
156
157 // Sprint 93 S93.1.1: the codec keeps the component tree, so every
158 // event carries its original VEVENT (icalRaw) and resource text for a
159 // lossless write-back; TZIDs resolve through the resource's VTIMEZONEs.
160 619 ICalEventCodec::Resource resource;
161 619 QString resourceError;
162
2/3
✓ Branch 107 → 108 taken 619 times.
✗ Branch 108 → 109 not taken.
✓ Branch 108 → 122 taken 619 times.
619 if (!ICalEventCodec::parseResource(icalText, &resource, &resourceError)) {
163 // T-79.C2/L12: one malformed resource must not abort the whole
164 // calendar's sync — skip it and keep the remaining items.
165 ✗ qCWarning(lcCalDav) << "Skipping unparseable calendar resource" << href
166 ✗ << resourceError;
167 ✗ continue;
168 ✗ }
169
4/4
✓ Branch 122 → 123 taken 619 times.
✓ Branch 123 → 124 taken 619 times.
✓ Branch 131 → 125 taken 614 times.
✓ Branch 131 → 132 taken 619 times.
1233 for (CalendarEvent &event : resource.events) {
170 614 event.etag = etag;
171 614 event.resourceHref = href;
172
1/1
✓ Branch 128 → 129 taken 614 times.
614 events->append(event);
173 }
174 1879 }
175
176 228 return true;
177 233 }
178
179 236 bool parseTasks(const QByteArray &xmlData, QList<CalendarTask> *tasks, QString *error) {
180
1/2
✓ Branch 2 → 3 taken 236 times.
✗ Branch 2 → 4 not taken.
236 if (tasks)
181
1/1
✓ Branch 3 → 4 taken 236 times.
236 tasks->clear();
182
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 14 taken 236 times.
236 if (!tasks) {
183 ✗ if (error)
184 ✗ *error = QStringLiteral("Invalid parser output");
185 ✗ return false;
186 }
187
188
1/1
✓ Branch 14 → 15 taken 236 times.
236 QDomDocument doc;
189 // T-511: Check XML parse result
190
3/3
✓ Branch 15 → 16 taken 236 times.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 36 taken 235 times.
236 if (!DavXmlHelper::parseInto(doc, xmlData)) {
191
5/5
✓ Branch 17 → 18 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 26 → 19 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
2 qCWarning(lcCalDav) << "Failed to parse XML response";
192
1/2
✓ Branch 27 → 28 taken 1 time.
✗ Branch 27 → 35 not taken.
1 if (error)
193 1 *error = QStringLiteral("Invalid XML response");
194 1 return false;
195 }
196
197
1/1
✓ Branch 39 → 40 taken 235 times.
235 auto responses = findElementsByLocalNameDoc(doc, QStringLiteral("response"));
198
199
2/2
✓ Branch 196 → 43 taken 796 times.
✓ Branch 196 → 197 taken 233 times.
1029 for (int i = 0; i < responses.count(); ++i) {
200
2/2
✓ Branch 43 → 44 taken 796 times.
✓ Branch 44 → 45 taken 796 times.
796 QDomElement resp = responses.at(i).toElement();
201
202 796 QString href;
203
1/1
✓ Branch 49 → 50 taken 796 times.
796 auto hrefNodes = findElementsByLocalName(resp, QStringLiteral("href"));
204
2/2
✓ Branch 53 → 54 taken 793 times.
✓ Branch 53 → 64 taken 3 times.
796 if (!hrefNodes.isEmpty())
205
4/4
✓ Branch 54 → 55 taken 793 times.
✓ Branch 55 → 56 taken 793 times.
✓ Branch 56 → 57 taken 793 times.
✓ Branch 57 → 58 taken 793 times.
793 href = hrefNodes.at(0).toElement().text().trimmed();
206
207 796 QString etag;
208
1/1
✓ Branch 67 → 68 taken 796 times.
796 auto etagNodes = findElementsByLocalName(resp, QStringLiteral("getetag"));
209
2/2
✓ Branch 71 → 72 taken 746 times.
✓ Branch 71 → 80 taken 50 times.
796 if (!etagNodes.isEmpty())
210
3/3
✓ Branch 72 → 73 taken 746 times.
✓ Branch 73 → 74 taken 746 times.
✓ Branch 74 → 75 taken 746 times.
746 etag = etagNodes.at(0).toElement().text();
211
212 796 QString icalText;
213
1/1
✓ Branch 83 → 84 taken 796 times.
796 auto dataNodes = findElementsByLocalName(resp, QStringLiteral("calendar-data"));
214
2/2
✓ Branch 87 → 88 taken 171 times.
✓ Branch 87 → 89 taken 625 times.
796 if (dataNodes.isEmpty())
215 171 continue;
216
3/3
✓ Branch 89 → 90 taken 625 times.
✓ Branch 90 → 91 taken 625 times.
✓ Branch 91 → 92 taken 625 times.
625 icalText = dataNodes.at(0).toElement().text();
217
2/2
✓ Branch 97 → 98 taken 1 time.
✓ Branch 97 → 99 taken 624 times.
625 if (icalText.isEmpty())
218 1 continue;
219
3/3
✓ Branch 102 → 103 taken 624 times.
✓ Branch 105 → 106 taken 2 times.
✓ Branch 105 → 107 taken 622 times.
624 if (!validateICalendarText(icalText, QStringLiteral("VTODO"), error))
220 2 return false;
221
222 622 ICalTodoCodec::Resource resource;
223 622 QString resourceError;
224
3/3
✓ Branch 107 → 108 taken 622 times.
✓ Branch 108 → 109 taken 1 time.
✓ Branch 108 → 122 taken 621 times.
622 if (!ICalTodoCodec::parseResource(icalText, &resource, &resourceError)) {
225 // S106-050: like events (T-79.C2), one malformed task resource is
226 // skipped — it used to abort the whole calendar's task page, and the
227 // sync re-failed while the resource existed.
228
7/7
✓ Branch 109 → 110 taken 1 time.
✓ Branch 113 → 114 taken 1 time.
✓ Branch 114 → 115 taken 1 time.
✓ Branch 115 → 116 taken 1 time.
✓ Branch 116 → 117 taken 1 time.
✓ Branch 120 → 111 taken 1 time.
✓ Branch 120 → 121 taken 1 time.
2 qCWarning(lcCalDav) << "Skipping unparseable task resource" << href << resourceError;
229 1 continue;
230 1 }
231
4/4
✓ Branch 122 → 123 taken 621 times.
✓ Branch 123 → 124 taken 621 times.
✓ Branch 148 → 125 taken 54 times.
✓ Branch 148 → 149 taken 621 times.
675 for (auto task : resource.tasks) {
232 // Exceptions stay in resourceIcal; the list projects the master.
233
4/5
✓ Branch 130 → 131 taken 54 times.
✓ Branch 131 → 132 taken 54 times.
✓ Branch 132 → 133 taken 54 times.
✗ Branch 137 → 138 not taken.
✓ Branch 137 → 139 taken 54 times.
54 if (ICal::parseFirst(task.icalRaw, QStringLiteral("VTODO")).hasProperty("RECURRENCE-ID")) continue;
234 54 task.etag = etag;
235 54 task.resourceHref = href;
236
1/1
✓ Branch 141 → 142 taken 54 times.
54 tasks->append(task);
237 54 }
238 1848 }
239
240 233 return true;
241 236 }
242
243 113 QList<CalendarInfo> parseCalendars(const QDomDocument &doc) {
244 113 QList<CalendarInfo> calendars;
245
1/1
✓ Branch 5 → 6 taken 113 times.
113 auto responses = findElementsByLocalNameDoc(doc, QStringLiteral("response"));
246
2/2
✓ Branch 204 → 9 taken 237 times.
✓ Branch 204 → 205 taken 113 times.
350 for (int i = 0; i < responses.count(); ++i) {
247
2/2
✓ Branch 9 → 10 taken 237 times.
✓ Branch 10 → 11 taken 237 times.
237 QDomElement resp = responses.at(i).toElement();
248
249 // Check for calendar resourcetype
250 // S106-051: a calendar has a resourcetype CHILD element whose local
251 // name is exactly "calendar" (RFC 4791 §4.2) — the former substring test
252 // on the serialized subtree also took calendar-proxy-read/-write,
253 // notification collections and xmlns declarations for calendars.
254 237 bool isCalendar = false;
255
1/1
✓ Branch 15 → 16 taken 237 times.
237 auto resourceTypes = findElementsByLocalName(resp, QStringLiteral("resourcetype"));
256
5/6
✓ Branch 50 → 51 taken 237 times.
✓ Branch 50 → 53 taken 237 times.
✓ Branch 51 → 52 taken 237 times.
✗ Branch 51 → 53 not taken.
✓ Branch 54 → 19 taken 237 times.
✓ Branch 54 → 55 taken 237 times.
474 for (int j = 0; j < resourceTypes.count() && !isCalendar; ++j) {
257
5/5
✓ Branch 19 → 20 taken 237 times.
✓ Branch 22 → 23 taken 237 times.
✓ Branch 45 → 46 taken 435 times.
✓ Branch 46 → 27 taken 414 times.
✓ Branch 46 → 47 taken 21 times.
435 for (QDomElement type = resourceTypes.at(j).firstChildElement(); !type.isNull();
258
2/2
✓ Branch 39 → 40 taken 198 times.
✓ Branch 40 → 41 taken 198 times.
198 type = type.nextSiblingElement()) {
259
3/3
✓ Branch 27 → 28 taken 414 times.
✓ Branch 35 → 36 taken 216 times.
✓ Branch 35 → 37 taken 198 times.
828 if (localNameOf(type).compare(QStringLiteral("calendar"), Qt::CaseInsensitive) == 0) {
260 216 isCalendar = true;
261 216 break;
262 }
263 237 }
264 }
265
266
2/2
✓ Branch 55 → 56 taken 21 times.
✓ Branch 55 → 57 taken 216 times.
237 if (!isCalendar)
267 21 continue;
268
269 // Extract href
270 216 QString href;
271
1/1
✓ Branch 60 → 61 taken 216 times.
216 auto hrefs = findElementsByLocalName(resp, QStringLiteral("href"));
272
2/2
✓ Branch 64 → 65 taken 215 times.
✓ Branch 64 → 73 taken 1 time.
216 if (!hrefs.isEmpty())
273
3/3
✓ Branch 65 → 66 taken 215 times.
✓ Branch 66 → 67 taken 215 times.
✓ Branch 67 → 68 taken 215 times.
215 href = hrefs.at(0).toElement().text();
274
275 // Extract displayname
276 216 QString displayName;
277
1/1
✓ Branch 76 → 77 taken 216 times.
216 auto nameNodes = findElementsByLocalName(resp, QStringLiteral("displayname"));
278
2/2
✓ Branch 80 → 81 taken 208 times.
✓ Branch 80 → 91 taken 8 times.
216 if (!nameNodes.isEmpty())
279
4/4
✓ Branch 81 → 82 taken 208 times.
✓ Branch 82 → 83 taken 208 times.
✓ Branch 83 → 84 taken 208 times.
✓ Branch 84 → 85 taken 208 times.
208 displayName = nameNodes.at(0).toElement().text().trimmed();
280
281 // Fallback: name from path
282
2/2
✓ Branch 92 → 93 taken 9 times.
✓ Branch 92 → 106 taken 207 times.
216 if (displayName.isEmpty()) {
283 9 displayName = href;
284
3/3
✓ Branch 96 → 97 taken 9 times.
✓ Branch 97 → 98 taken 7 times.
✓ Branch 97 → 99 taken 2 times.
9 if (displayName.endsWith(QLatin1Char('/')))
285
1/1
✓ Branch 98 → 99 taken 7 times.
7 displayName.chop(1);
286 displayName =
287
1/1
✓ Branch 102 → 103 taken 9 times.
9 displayName.mid(displayName.lastIndexOf(QLatin1Char('/')) + 1);
288 }
289
290 // Extract calendar-color (namespace-agnostic via localName())
291 216 QString color;
292 QDomElement colorEl =
293
1/1
✓ Branch 109 → 110 taken 216 times.
216 findFirstElementByLocalName(resp, QStringLiteral("calendar-color"));
294
3/3
✓ Branch 112 → 113 taken 216 times.
✓ Branch 113 → 114 taken 35 times.
✓ Branch 113 → 131 taken 181 times.
216 if (!colorEl.isNull()) {
295
2/2
✓ Branch 114 → 115 taken 35 times.
✓ Branch 115 → 116 taken 35 times.
35 color = colorEl.text().trimmed();
296 // Nextcloud sometimes returns #RRGGBBAA — strip alpha
297
6/7
✓ Branch 120 → 121 taken 7 times.
✓ Branch 120 → 125 taken 28 times.
✓ Branch 122 → 123 taken 7 times.
✓ Branch 123 → 124 taken 7 times.
✗ Branch 123 → 125 not taken.
✓ Branch 126 → 127 taken 7 times.
✓ Branch 126 → 131 taken 28 times.
35 if (color.length() == 9 && color.startsWith('#'))
298
1/1
✓ Branch 127 → 128 taken 7 times.
7 color = color.left(7);
299 }
300
301 // Extract CTag (prefix-agnostic: Radicale writes "CS:getctag").
302 const QString ctag =
303
3/3
✓ Branch 134 → 135 taken 216 times.
✓ Branch 135 → 136 taken 216 times.
✓ Branch 136 → 137 taken 216 times.
216 findFirstElementByLocalName(resp, QStringLiteral("getctag")).text().trimmed();
304
305 // S93.5.8: a collection without write privilege (RFC 3744
306 // current-user-privilege-set; DAV:write, write-content or all) is
307 // read-only; servers that do not report privileges stay writable.
308
1/1
✓ Branch 141 → 142 taken 216 times.
216 const bool readOnly = readOnlyFromPrivileges(resp);
309
310
2/2
✓ Branch 143 → 144 taken 215 times.
✓ Branch 143 → 185 taken 1 time.
216 if (!href.isEmpty()) {
311 215 CalendarInfo cal;
312 215 cal.path = href;
313 215 cal.displayName = displayName;
314 215 cal.color = color;
315 215 cal.ctag = ctag;
316 215 cal.readOnly = readOnly;
317
1/1
✓ Branch 151 → 152 taken 215 times.
215 const auto componentSet = findFirstElementByLocalName(resp, QStringLiteral("supported-calendar-component-set"));
318
1/1
✓ Branch 157 → 158 taken 215 times.
215 const auto components = findElementsByLocalName(componentSet, QStringLiteral("comp"));
319
2/2
✓ Branch 179 → 161 taken 63 times.
✓ Branch 179 → 180 taken 215 times.
278 for (int j = 0; j < components.size(); ++j)
320
5/5
✓ Branch 161 → 162 taken 63 times.
✓ Branch 162 → 163 taken 63 times.
✓ Branch 167 → 168 taken 63 times.
✓ Branch 168 → 169 taken 63 times.
✓ Branch 169 → 170 taken 63 times.
126 cal.supportedComponents << components.at(j).toElement().attribute(QStringLiteral("name")).toUpper();
321
1/1
✓ Branch 180 → 181 taken 215 times.
215 calendars.append(cal);
322 215 }
323 258 }
324 113 return calendars;
325 113 }
326
327 } // namespace CalDavXml
328