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: 97.7% 209 / 0 / 214
Functions: 100.0% 17 / 0 / 17
Branches: 77.8% 354 / 0 / 455

data/ImipInvitation.cpp
Line Branch Exec Source
1 #include "ImipInvitation.h"
2 #include "util/HostMatch.h"
3
4 #include "ICalEventCodec.h"
5 #include "ICalTime.h"
6
7 #include <QLoggingCategory>
8 #include <QRegularExpression>
9 #include <QSet>
10
11
4/7
✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 8 taken 46 times.
✓ Branch 4 → 5 taken 9 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 9 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
55 Q_LOGGING_CATEGORY(lcImip, "mailjd.calendar.imip")
12
13 namespace Imip {
14
15 namespace {
16
17 43 ICal::Property makeProperty(const QString &name, const QString &value) {
18 43 ICal::Property property;
19 43 property.name = name;
20 43 property.value = value;
21 43 return property;
22 }
23
24 // The VEVENT the card describes: the series master, else the first one.
25 64 int primaryIndex(const ICal::Component &calendar) {
26 64 int first = -1;
27
2/2
✓ Branch 20 → 3 taken 91 times.
✓ Branch 20 → 21 taken 1 time.
92 for (int i = 0; i < calendar.components.size(); ++i) {
28 91 const ICal::Component &component = calendar.components.at(i);
29
2/2
✓ Branch 6 → 7 taken 28 times.
✓ Branch 6 → 8 taken 63 times.
91 if (component.name != QLatin1String("VEVENT")) continue;
30
2/3
✓ Branch 11 → 12 taken 63 times.
✓ Branch 14 → 15 taken 63 times.
✗ Branch 14 → 16 not taken.
63 if (!component.hasProperty(QStringLiteral("RECURRENCE-ID"))) return i;
31 ✗ if (first < 0) first = i;
32 }
33 1 return first;
34 }
35
36 53 QString meetingLink(const ICal::Component &vevent) {
37 200 for (const QString &name : {QStringLiteral("X-GOOGLE-CONFERENCE"),
38 53 QStringLiteral("X-MICROSOFT-SKYPETEAMSMEETINGURL"),
39
2/2
✓ Branch 34 → 17 taken 152 times.
✓ Branch 34 → 35 taken 48 times.
677 QStringLiteral("URL")}) {
40
2/4
✓ Branch 17 → 18 taken 152 times.
✓ Branch 18 → 19 taken 152 times.
✗ Branch 133 → 134 not taken.
✗ Branch 133 → 135 not taken.
152 const QString value = vevent.value(name).trimmed();
41
5/5
✓ Branch 20 → 21 taken 152 times.
✓ Branch 22 → 23 taken 152 times.
✓ Branch 23 → 24 taken 152 times.
✓ Branch 26 → 27 taken 5 times.
✓ Branch 26 → 28 taken 147 times.
152 if (HostMatch::isSafeMeetingUrl(QUrl::fromEncoded(value.toUtf8(), QUrl::StrictMode))) return value;
42 359 }
43 // Zoom, Teams, Meet and Jitsi put the link only into the text.
44 16 static const QRegularExpression urls(QStringLiteral("https://[^\\s\"<>\\\\]+"),
45
4/7
✓ Branch 41 → 43 taken 8 times.
✓ Branch 41 → 55 taken 40 times.
✓ Branch 44 → 45 taken 8 times.
✗ Branch 44 → 55 not taken.
✓ Branch 49 → 50 taken 8 times.
✗ Branch 151 → 152 not taken.
✗ Branch 151 → 153 not taken.
64 QRegularExpression::CaseInsensitiveOption);
46
2/2
✓ Branch 112 → 65 taken 94 times.
✓ Branch 112 → 113 taken 39 times.
373 for (const QString &name : {QStringLiteral("LOCATION"), QStringLiteral("DESCRIPTION")}) {
47
3/3
✓ Branch 66 → 67 taken 94 times.
✓ Branch 67 → 68 taken 94 times.
✓ Branch 68 → 69 taken 94 times.
94 auto matches = urls.globalMatch(ICal::unescapeText(vevent.value(name)));
48
3/3
✓ Branch 104 → 105 taken 111 times.
✓ Branch 105 → 72 taken 26 times.
✓ Branch 105 → 106 taken 85 times.
111 while (matches.hasNext()) {
49
2/2
✓ Branch 72 → 73 taken 26 times.
✓ Branch 73 → 74 taken 26 times.
26 QString candidate = matches.next().captured(0);
50
7/17
✗ Branch 76 → 77 not taken.
✓ Branch 78 → 79 taken 26 times.
✗ Branch 78 → 86 not taken.
✓ Branch 82 → 83 taken 26 times.
✓ Branch 83 → 84 taken 26 times.
✗ Branch 84 → 85 not taken.
✓ Branch 84 → 86 taken 26 times.
✓ Branch 87 → 88 taken 26 times.
✗ Branch 87 → 89 not taken.
✓ Branch 89 → 90 taken 26 times.
✗ Branch 89 → 91 not taken.
✗ Branch 91 → 76 not taken.
✓ Branch 91 → 92 taken 26 times.
✗ Branch 164 → 165 not taken.
✗ Branch 164 → 166 not taken.
✗ Branch 168 → 169 not taken.
✗ Branch 168 → 170 not taken.
52 while (!candidate.isEmpty() && QStringLiteral(".,;:)>]").contains(candidate.back())) candidate.chop(1);
51
4/4
✓ Branch 92 → 93 taken 26 times.
✓ Branch 93 → 94 taken 26 times.
✓ Branch 95 → 96 taken 9 times.
✓ Branch 95 → 98 taken 17 times.
26 if (HostMatch::isKnownMeetingUrl(QUrl(candidate))) return candidate;
52 26 }
53 238 }
54 39 return {};
55 }
56
57 232 Attendee attendeeFrom(const ICal::Property &property) {
58 232 Attendee attendee;
59
1/1
✓ Branch 2 → 3 taken 232 times.
232 attendee.email = addressOf(property.value);
60
1/1
✓ Branch 8 → 9 taken 232 times.
232 attendee.name = property.parameter(QStringLiteral("CN"));
61
2/2
✓ Branch 16 → 17 taken 232 times.
✓ Branch 17 → 18 taken 232 times.
232 attendee.partstat = property.parameter(QStringLiteral("PARTSTAT")).toUpper();
62
2/2
✓ Branch 24 → 25 taken 59 times.
✓ Branch 24 → 32 taken 173 times.
291 if (attendee.partstat.isEmpty()) attendee.partstat = QStringLiteral("NEEDS-ACTION");
63
2/2
✓ Branch 35 → 36 taken 232 times.
✓ Branch 36 → 37 taken 232 times.
232 attendee.role = property.parameter(QStringLiteral("ROLE")).toUpper();
64
2/2
✓ Branch 45 → 46 taken 232 times.
✓ Branch 46 → 47 taken 232 times.
232 attendee.sentBy = addressOf(property.parameter(QStringLiteral("SENT-BY")));
65
2/2
✓ Branch 53 → 54 taken 135 times.
✓ Branch 53 → 61 taken 97 times.
367 if (attendee.role.isEmpty()) attendee.role = QStringLiteral("REQ-PARTICIPANT");
66 232 return attendee;
67 ✗ }
68
69 // Sets the PARTSTAT of `email` in `vevent`; appends the attendee when it
70 // is missing and `addMissing` is set. Returns whether anything changed.
71 20 bool setAttendeeStatus(ICal::Component *vevent, const QString &email, const QString &name,
72 const QString &partstat, bool addMissing) {
73
2/2
✓ Branch 2 → 3 taken 20 times.
✓ Branch 3 → 4 taken 20 times.
20 const QString wanted = email.trimmed().toLower();
74
4/4
✓ Branch 5 → 6 taken 20 times.
✓ Branch 6 → 7 taken 20 times.
✓ Branch 64 → 8 taken 186 times.
✓ Branch 64 → 65 taken 2 times.
188 for (ICal::Property &property : vevent->properties) {
75
9/11
✓ Branch 11 → 12 taken 29 times.
✓ Branch 11 → 15 taken 157 times.
✓ Branch 12 → 13 taken 29 times.
✓ Branch 14 → 15 taken 11 times.
✓ Branch 14 → 16 taken 18 times.
✓ Branch 17 → 18 taken 29 times.
✓ Branch 17 → 19 taken 157 times.
✓ Branch 19 → 20 taken 168 times.
✓ Branch 19 → 21 taken 18 times.
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 106 not taken.
186 if (property.name != QLatin1String("ATTENDEE") || addressOf(property.value) != wanted) continue;
76
7/18
✓ Branch 24 → 25 taken 18 times.
✓ Branch 25 → 26 taken 18 times.
✓ Branch 27 → 28 taken 2 times.
✓ Branch 27 → 33 taken 16 times.
✓ Branch 41 → 42 taken 18 times.
✗ Branch 41 → 43 not taken.
✓ Branch 43 → 44 taken 18 times.
✗ Branch 43 → 45 not taken.
✓ Branch 45 → 46 taken 18 times.
✗ Branch 45 → 47 not taken.
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 119 not taken.
✗ Branch 121 → 122 not taken.
✗ Branch 121 → 123 not taken.
✗ Branch 125 → 126 not taken.
✗ Branch 125 → 127 not taken.
✗ Branch 129 → 130 not taken.
✗ Branch 129 → 131 not taken.
56 const bool changed = property.parameter(QStringLiteral("PARTSTAT")).toUpper() != partstat ||
77
7/13
✓ Branch 31 → 32 taken 2 times.
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 34 taken 2 times.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 35 → 37 taken 16 times.
✓ Branch 37 → 38 taken 2 times.
✓ Branch 37 → 39 taken 16 times.
✓ Branch 39 → 40 taken 18 times.
✗ Branch 39 → 41 not taken.
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 111 not taken.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
20 property.hasParameter(QStringLiteral("RSVP"));
78
2/2
✓ Branch 47 → 48 taken 2 times.
✓ Branch 47 → 49 taken 16 times.
34 if (!changed) return false;
79
1/1
✓ Branch 52 → 53 taken 16 times.
16 property.setParameter(QStringLiteral("PARTSTAT"), partstat);
80
1/1
✓ Branch 58 → 59 taken 16 times.
16 property.removeParameter(QStringLiteral("RSVP"));
81 16 return true;
82 }
83
2/2
✓ Branch 65 → 67 taken 1 time.
✓ Branch 65 → 68 taken 1 time.
2 if (!addMissing) return false;
84
1/1
✓ Branch 71 → 72 taken 1 time.
2 ICal::Property attendee = makeProperty(QStringLiteral("ATTENDEE"), QStringLiteral("mailto:") + wanted);
85
2/3
✓ Branch 82 → 83 taken 1 time.
✗ Branch 82 → 90 not taken.
✓ Branch 86 → 87 taken 1 time.
2 if (!name.isEmpty()) attendee.setParameter(QStringLiteral("CN"), name);
86
1/1
✓ Branch 93 → 94 taken 1 time.
1 attendee.setParameter(QStringLiteral("PARTSTAT"), partstat);
87
1/1
✓ Branch 96 → 97 taken 1 time.
1 vevent->appendProperty(attendee);
88 1 return true;
89 20 }
90
91 } // namespace
92
93 3806 QString addressOf(const QString &calAddress) {
94
1/1
✓ Branch 2 → 3 taken 3806 times.
3806 QString value = calAddress.trimmed();
95
4/4
✓ Branch 4 → 5 taken 3806 times.
✓ Branch 5 → 6 taken 802 times.
✓ Branch 5 → 10 taken 3004 times.
✓ Branch 6 → 7 taken 802 times.
3806 if (value.startsWith(QLatin1String("mailto:"), Qt::CaseInsensitive)) value = value.mid(7);
96
2/2
✓ Branch 10 → 11 taken 3806 times.
✓ Branch 11 → 12 taken 3806 times.
7612 return value.trimmed().toLower();
97 3806 }
98
99 56 std::optional<Invitation> parse(const QString &text, QString *error) {
100 56 ICalEventCodec::Resource resource;
101
3/3
✓ Branch 2 → 3 taken 56 times.
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 54 times.
56 if (!ICalEventCodec::parseResource(text, &resource, error)) return std::nullopt;
102
1/1
✓ Branch 5 → 6 taken 54 times.
54 const int primary = primaryIndex(resource.calendar);
103
5/6
✓ Branch 6 → 7 taken 53 times.
✓ Branch 6 → 9 taken 1 time.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 53 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 21 taken 53 times.
54 if (primary < 0 || resource.events.isEmpty()) {
104
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 20 taken 1 time.
1 if (error) *error = QStringLiteral("The calendar data contains no appointment.");
105 1 return std::nullopt;
106 }
107 53 const ICal::Component &vevent = resource.calendar.components.at(primary);
108
1/1
✓ Branch 22 → 23 taken 53 times.
53 const ICalTime::ZoneResolver resolver(resource.calendar);
109 53 Invitation invitation;
110
4/4
✓ Branch 25 → 26 taken 2 times.
✓ Branch 25 → 29 taken 51 times.
✓ Branch 32 → 33 taken 2 times.
✓ Branch 32 → 34 taken 51 times.
55 invitation.method = resource.method.isEmpty() ? QStringLiteral("PUBLISH") : resource.method;
111
1/1
✓ Branch 34 → 35 taken 53 times.
53 invitation.event = ICalEventCodec::eventFromComponent(vevent, resolver);
112 53 invitation.uid = invitation.event.uid;
113
3/3
✓ Branch 41 → 42 taken 53 times.
✓ Branch 42 → 43 taken 53 times.
✓ Branch 43 → 44 taken 53 times.
53 invitation.sequence = vevent.value(QStringLiteral("SEQUENCE")).trimmed().toInt();
114
3/3
✓ Branch 51 → 52 taken 53 times.
✓ Branch 54 → 55 taken 42 times.
✓ Branch 54 → 77 taken 11 times.
53 if (const ICal::Property *organizer = vevent.property(QStringLiteral("ORGANIZER"))) {
115
1/1
✓ Branch 55 → 56 taken 42 times.
42 invitation.organizerEmail = addressOf(organizer->value);
116
1/1
✓ Branch 61 → 62 taken 42 times.
84 invitation.organizerName = organizer->parameter(QStringLiteral("CN"));
117
2/2
✓ Branch 69 → 70 taken 42 times.
✓ Branch 70 → 71 taken 42 times.
84 invitation.organizerSentBy = addressOf(organizer->parameter(QStringLiteral("SENT-BY")));
118 }
119
5/5
✓ Branch 80 → 81 taken 53 times.
✓ Branch 83 → 84 taken 53 times.
✓ Branch 84 → 85 taken 53 times.
✓ Branch 92 → 86 taken 76 times.
✓ Branch 92 → 93 taken 53 times.
129 for (const ICal::Property &property : vevent.propertiesNamed(QStringLiteral("ATTENDEE")))
120
2/2
✓ Branch 87 → 88 taken 76 times.
✓ Branch 88 → 89 taken 76 times.
129 invitation.attendees.append(attendeeFrom(property));
121
1/1
✓ Branch 94 → 95 taken 53 times.
53 invitation.meetingUrl = meetingLink(vevent);
122
2/2
✓ Branch 100 → 101 taken 53 times.
✓ Branch 101 → 102 taken 53 times.
53 invitation.comment = ICal::unescapeText(vevent.value(QStringLiteral("COMMENT")));
123 53 invitation.calendar = resource.calendar;
124 53 invitation.events = resource.events;
125
9/9
✓ Branch 109 → 110 taken 53 times.
✓ Branch 113 → 114 taken 53 times.
✓ Branch 114 → 115 taken 53 times.
✓ Branch 115 → 116 taken 53 times.
✓ Branch 116 → 117 taken 53 times.
✓ Branch 117 → 118 taken 53 times.
✓ Branch 118 → 119 taken 53 times.
✓ Branch 128 → 111 taken 53 times.
✓ Branch 128 → 129 taken 53 times.
106 qCInfo(lcImip) << "Parsed" << invitation.method << "sequence" << invitation.sequence << "with"
126
4/4
✓ Branch 120 → 121 taken 53 times.
✓ Branch 121 → 122 taken 53 times.
✓ Branch 123 → 124 taken 53 times.
✓ Branch 124 → 125 taken 53 times.
53 << invitation.attendees.size() << "attendees and" << invitation.events.size() << "events";
127 53 return invitation;
128 56 }
129
130 194 Consistency consistency(const Invitation &invitation, const CalendarEvent *storedMaster,
131 const QString &mailSender) {
132 194 Consistency result;
133
1/1
✓ Branch 2 → 3 taken 194 times.
194 const QString sender = addressOf(mailSender);
134 717 const auto represents = [&sender](const QString &address, const QString &sentBy) {
135
5/7
✓ Branch 3 → 4 taken 638 times.
✓ Branch 3 → 13 taken 79 times.
✓ Branch 4 → 5 taken 638 times.
✓ Branch 6 → 7 taken 493 times.
✓ Branch 6 → 12 taken 145 times.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
1927 return !sender.isEmpty() && (sender == addressOf(address) ||
136
9/11
✓ Branch 8 → 9 taken 210 times.
✓ Branch 8 → 13 taken 283 times.
✓ Branch 9 → 10 taken 210 times.
✓ Branch 11 → 12 taken 28 times.
✓ Branch 11 → 13 taken 182 times.
✓ Branch 14 → 15 taken 210 times.
✓ Branch 14 → 16 taken 507 times.
✓ Branch 16 → 17 taken 638 times.
✓ Branch 16 → 18 taken 79 times.
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 22 not taken.
1927 (!sentBy.isEmpty() && sender == addressOf(sentBy)));
137 194 };
138
1/1
✓ Branch 3 → 4 taken 194 times.
194 result.senderIsOrganizer = represents(invitation.organizerEmail, invitation.organizerSentBy);
139 const ICal::Component stored = storedMaster
140
7/11
✓ Branch 4 → 5 taken 114 times.
✓ Branch 4 → 9 taken 80 times.
✓ Branch 8 → 10 taken 114 times.
✓ Branch 10 → 11 taken 114 times.
✓ Branch 10 → 12 taken 80 times.
✓ Branch 12 → 13 taken 114 times.
✓ Branch 12 → 14 taken 80 times.
✗ Branch 101 → 102 not taken.
✗ Branch 101 → 103 not taken.
✗ Branch 105 → 106 not taken.
✗ Branch 105 → 107 not taken.
308 ? ICal::parseFirst(storedMaster->icalRaw, QStringLiteral("VEVENT")) : ICal::Component();
141
1/1
✓ Branch 17 → 18 taken 194 times.
194 const auto *organizer = stored.property(QStringLiteral("ORGANIZER"));
142
2/2
✓ Branch 20 → 21 taken 114 times.
✓ Branch 20 → 35 taken 80 times.
194 if (storedMaster)
143
5/6
✓ Branch 21 → 22 taken 112 times.
✓ Branch 21 → 29 taken 2 times.
✓ Branch 23 → 24 taken 112 times.
✗ Branch 23 → 29 not taken.
✓ Branch 27 → 28 taken 80 times.
✓ Branch 27 → 29 taken 32 times.
226 result.organizerMatches = organizer && !invitation.organizerEmail.isEmpty() &&
144
6/10
✓ Branch 24 → 25 taken 112 times.
✓ Branch 25 → 26 taken 112 times.
✓ Branch 30 → 31 taken 112 times.
✓ Branch 30 → 32 taken 2 times.
✓ Branch 32 → 33 taken 112 times.
✓ Branch 32 → 34 taken 2 times.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 117 not taken.
✗ Branch 119 → 120 not taken.
✗ Branch 119 → 121 not taken.
226 addressOf(organizer->value) == addressOf(invitation.organizerEmail);
145 194 QList<Attendee> known = invitation.attendees;
146
2/2
✓ Branch 36 → 37 taken 114 times.
✓ Branch 36 → 56 taken 80 times.
194 if (storedMaster) {
147
1/1
✓ Branch 37 → 38 taken 114 times.
114 known.clear();
148
5/5
✓ Branch 41 → 42 taken 114 times.
✓ Branch 44 → 45 taken 114 times.
✓ Branch 45 → 46 taken 114 times.
✓ Branch 53 → 47 taken 156 times.
✓ Branch 53 → 54 taken 114 times.
270 for (const auto &property : stored.propertiesNamed(QStringLiteral("ATTENDEE")))
149
2/2
✓ Branch 48 → 49 taken 156 times.
✓ Branch 49 → 50 taken 156 times.
270 known.append(attendeeFrom(property));
150 }
151
4/4
✓ Branch 56 → 57 taken 194 times.
✓ Branch 57 → 58 taken 194 times.
✓ Branch 79 → 59 taken 254 times.
✓ Branch 79 → 80 taken 194 times.
448 for (const auto &attendee : known) {
152
1/1
✓ Branch 60 → 61 taken 254 times.
254 result.senderIsAttendee |= represents(attendee.email, attendee.sentBy);
153
9/16
✓ Branch 62 → 63 taken 254 times.
✗ Branch 62 → 69 not taken.
✓ Branch 64 → 65 taken 254 times.
✓ Branch 65 → 66 taken 254 times.
✓ Branch 67 → 68 taken 189 times.
✓ Branch 67 → 69 taken 65 times.
✓ Branch 70 → 71 taken 254 times.
✗ Branch 70 → 72 not taken.
✓ Branch 72 → 73 taken 254 times.
✗ Branch 72 → 74 not taken.
✓ Branch 74 → 75 taken 189 times.
✓ Branch 74 → 77 taken 65 times.
✗ Branch 135 → 136 not taken.
✗ Branch 135 → 137 not taken.
✗ Branch 139 → 140 not taken.
✗ Branch 139 → 141 not taken.
254 if (!invitation.attendees.isEmpty() && addressOf(attendee.email) == addressOf(invitation.attendees.first().email)) {
154 189 result.replierIsAttendee = true;
155 // A reply cannot grant itself a new SENT-BY identity; use the stored fact.
156
1/1
✓ Branch 75 → 76 taken 189 times.
189 result.senderIsReplier = represents(attendee.email, attendee.sentBy);
157 }
158 }
159
6/6
✓ Branch 80 → 81 taken 114 times.
✓ Branch 80 → 96 taken 80 times.
✓ Branch 81 → 82 taken 112 times.
✓ Branch 81 → 96 taken 2 times.
✓ Branch 82 → 83 taken 80 times.
✓ Branch 82 → 96 taken 32 times.
194 if (storedMaster && organizer && result.organizerMatches)
160
2/2
✓ Branch 88 → 89 taken 80 times.
✓ Branch 89 → 90 taken 80 times.
80 result.senderIsOrganizer = represents(addressOf(organizer->value),
161
2/2
✓ Branch 86 → 87 taken 80 times.
✓ Branch 87 → 88 taken 80 times.
240 addressOf(organizer->parameter(QStringLiteral("SENT-BY"))));
162 194 return result;
163 194 }
164
165 179 int attendeeIndex(const Invitation &invitation, const QStringList &addresses) {
166 179 QSet<QString> wanted;
167
5/5
✓ Branch 6 → 7 taken 179 times.
✓ Branch 7 → 8 taken 179 times.
✓ Branch 8 → 9 taken 179 times.
✓ Branch 13 → 5 taken 179 times.
✓ Branch 13 → 14 taken 179 times.
358 for (const QString &address : addresses) wanted.insert(address.trimmed().toLower());
168
2/2
✓ Branch 21 → 15 taken 170 times.
✓ Branch 21 → 22 taken 45 times.
215 for (int i = 0; i < invitation.attendees.size(); ++i)
169
2/2
✓ Branch 17 → 18 taken 134 times.
✓ Branch 17 → 19 taken 36 times.
170 if (wanted.contains(invitation.attendees.at(i).email)) return i;
170 45 return -1;
171 179 }
172
173 19 QList<CalendarEvent> eventsToStore(const Invitation &invitation, const QString &email,
174 const QString &name, const QString &partstat) {
175 19 ICal::Component calendar = invitation.calendar;
176 // A stored CalDAV resource never carries METHOD (RFC 4791 §4.1).
177
1/1
✓ Branch 6 → 7 taken 19 times.
19 calendar.removeProperties(QStringLiteral("METHOD"));
178
4/4
✓ Branch 9 → 10 taken 19 times.
✓ Branch 10 → 11 taken 19 times.
✓ Branch 29 → 12 taken 23 times.
✓ Branch 29 → 30 taken 19 times.
42 for (ICal::Component &component : calendar.components)
179
10/13
✓ Branch 15 → 16 taken 19 times.
✓ Branch 15 → 22 taken 4 times.
✓ Branch 16 → 17 taken 19 times.
✓ Branch 18 → 19 taken 14 times.
✓ Branch 18 → 22 taken 5 times.
✓ Branch 20 → 21 taken 14 times.
✗ Branch 20 → 22 not taken.
✓ Branch 23 → 24 taken 19 times.
✓ Branch 23 → 25 taken 4 times.
✓ Branch 25 → 26 taken 14 times.
✓ Branch 25 → 27 taken 9 times.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
23 if (component.name == QLatin1String("VEVENT") && !email.trimmed().isEmpty() && !partstat.isEmpty())
180
1/1
✓ Branch 26 → 27 taken 14 times.
14 setAttendeeStatus(&component, email, name, partstat, true);
181
1/1
✓ Branch 30 → 31 taken 19 times.
19 const QString resourceText = ICal::serialize(calendar);
182
1/1
✓ Branch 31 → 32 taken 19 times.
19 const ICalTime::ZoneResolver resolver(calendar);
183 19 QList<CalendarEvent> rows;
184
4/4
✓ Branch 32 → 33 taken 19 times.
✓ Branch 33 → 34 taken 19 times.
✓ Branch 54 → 35 taken 23 times.
✓ Branch 54 → 55 taken 19 times.
42 for (const ICal::Component &component : calendar.components) {
185
2/2
✓ Branch 38 → 39 taken 4 times.
✓ Branch 38 → 40 taken 19 times.
23 if (component.name != QLatin1String("VEVENT")) continue;
186
1/1
✓ Branch 40 → 41 taken 19 times.
19 CalendarEvent event = ICalEventCodec::eventFromComponent(component, resolver);
187
1/2
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 44 taken 19 times.
19 if (event.uid.isEmpty()) continue;
188 19 event.resourceIcal = resourceText;
189
1/1
✓ Branch 45 → 46 taken 19 times.
19 rows.append(event);
190 19 }
191 19 return rows;
192 19 }
193
194 10 QByteArray buildReply(const Invitation &invitation, const QString &email, const QString &name,
195 const QString &partstat, const QString &comment, const QDateTime &now) {
196
1/1
✓ Branch 2 → 3 taken 10 times.
10 const int primary = primaryIndex(invitation.calendar);
197
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 10 times.
10 if (primary < 0) return {};
198 10 const ICal::Component &request = invitation.calendar.components.at(primary);
199 10 ICal::Component reply;
200 10 reply.name = QStringLiteral("VCALENDAR");
201
1/1
✓ Branch 19 → 20 taken 10 times.
20 reply.appendProperty(makeProperty(QStringLiteral("PRODID"), QStringLiteral("-//MailJD//MailJD Calendar//EN")));
202
1/1
✓ Branch 32 → 33 taken 10 times.
20 reply.appendProperty(makeProperty(QStringLiteral("VERSION"), QStringLiteral("2.0")));
203
1/1
✓ Branch 45 → 46 taken 10 times.
20 reply.appendProperty(makeProperty(QStringLiteral("METHOD"), QStringLiteral("REPLY")));
204 // The copied DTSTART/DTEND may name custom zones.
205
5/5
✓ Branch 54 → 55 taken 10 times.
✓ Branch 57 → 58 taken 10 times.
✓ Branch 58 → 59 taken 10 times.
✓ Branch 64 → 60 taken 6 times.
✓ Branch 64 → 65 taken 10 times.
26 for (const ICal::Component &component : invitation.calendar.componentsNamed(QStringLiteral("VTIMEZONE")))
206
1/1
✓ Branch 61 → 62 taken 6 times.
16 reply.components.append(component);
207 10 ICal::Component vevent;
208 10 vevent.name = QStringLiteral("VEVENT");
209 100 for (const QString &copied : {QStringLiteral("UID"), QStringLiteral("RECURRENCE-ID"),
210 10 QStringLiteral("SEQUENCE"), QStringLiteral("ORGANIZER"),
211 10 QStringLiteral("DTSTART"), QStringLiteral("DTEND"),
212
2/2
✓ Branch 111 → 107 taken 80 times.
✓ Branch 111 → 112 taken 10 times.
270 QStringLiteral("DURATION"), QStringLiteral("SUMMARY")}) {
213
4/4
✓ Branch 107 → 108 taken 80 times.
✓ Branch 108 → 109 taken 59 times.
✓ Branch 108 → 110 taken 21 times.
✓ Branch 109 → 110 taken 59 times.
80 if (const ICal::Property *property = request.property(copied)) vevent.appendProperty(*property);
214 90 }
215
1/1
✓ Branch 127 → 128 taken 10 times.
10 vevent.appendProperty(makeProperty(QStringLiteral("DTSTAMP"),
216
2/2
✓ Branch 118 → 119 taken 10 times.
✓ Branch 122 → 123 taken 10 times.
30 now.toUTC().toString(QStringLiteral("yyyyMMdd'T'HHmmss'Z'"))));
217 10 ICal::Property attendee;
218 10 bool listed = false;
219
4/5
✓ Branch 138 → 139 taken 10 times.
✓ Branch 141 → 142 taken 10 times.
✓ Branch 142 → 143 taken 10 times.
✓ Branch 158 → 144 taken 14 times.
✗ Branch 158 → 159 not taken.
14 for (const ICal::Property &property : request.propertiesNamed(QStringLiteral("ATTENDEE"))) {
220
5/5
✓ Branch 145 → 146 taken 14 times.
✓ Branch 146 → 147 taken 14 times.
✓ Branch 147 → 148 taken 14 times.
✓ Branch 152 → 153 taken 4 times.
✓ Branch 152 → 154 taken 10 times.
14 if (addressOf(property.value) != email.trimmed().toLower()) continue;
221 10 attendee = property;
222 10 listed = true;
223 10 break;
224 10 }
225
1/2
✗ Branch 160 → 161 not taken.
✓ Branch 160 → 189 taken 10 times.
10 if (!listed) {
226 ✗ attendee = makeProperty(QStringLiteral("ATTENDEE"), QStringLiteral("mailto:") + email.trimmed().toLower());
227 ✗ if (!name.isEmpty()) attendee.setParameter(QStringLiteral("CN"), name);
228 }
229
1/1
✓ Branch 192 → 193 taken 10 times.
10 attendee.setParameter(QStringLiteral("PARTSTAT"), partstat);
230
1/1
✓ Branch 198 → 199 taken 10 times.
10 attendee.removeParameter(QStringLiteral("RSVP"));
231
1/1
✓ Branch 201 → 202 taken 10 times.
10 vevent.appendProperty(attendee);
232
3/3
✓ Branch 202 → 203 taken 10 times.
✓ Branch 205 → 206 taken 2 times.
✓ Branch 205 → 219 taken 8 times.
10 if (!comment.trimmed().isEmpty())
233
3/3
✓ Branch 206 → 207 taken 2 times.
✓ Branch 207 → 208 taken 2 times.
✓ Branch 212 → 213 taken 2 times.
4 vevent.appendProperty(makeProperty(QStringLiteral("COMMENT"), ICal::escapeText(comment.trimmed())));
234
1/1
✓ Branch 219 → 220 taken 10 times.
10 reply.components.append(vevent);
235
2/2
✓ Branch 220 → 221 taken 10 times.
✓ Branch 221 → 222 taken 10 times.
10 return ICal::serialize(reply).toUtf8();
236 10 }
237
238 8 QString withAttendeeStatus(const QString &veventText, const QString &email, const QString &partstat) {
239 // S106-144: a REPLY's PARTSTAT comes from a mail; only the RFC 5545
240 // participation states of an event are written into the stored copy.
241 3 static const QStringList kEventStates{QStringLiteral("NEEDS-ACTION"), QStringLiteral("ACCEPTED"),
242 3 QStringLiteral("DECLINED"), QStringLiteral("TENTATIVE"),
243
5/10
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 32 taken 5 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 32 not taken.
✓ Branch 24 → 25 taken 15 times.
✓ Branch 24 → 26 taken 3 times.
✗ Branch 69 → 70 not taken.
✗ Branch 69 → 71 not taken.
✗ Branch 93 → 94 not taken.
✗ Branch 93 → 95 not taken.
29 QStringLiteral("DELEGATED")};
244
3/3
✓ Branch 32 → 33 taken 8 times.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 35 → 47 taken 6 times.
8 if (!kEventStates.contains(partstat.toUpper())) {
245
5/5
✓ Branch 36 → 37 taken 2 times.
✓ Branch 40 → 41 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
✓ Branch 45 → 38 taken 2 times.
✓ Branch 45 → 46 taken 2 times.
4 qCWarning(lcImip) << "Reply with an unknown participation state ignored";
246 2 return {};
247 }
248
1/1
✓ Branch 50 → 51 taken 6 times.
6 ICal::Component vevent = ICal::parseFirst(veventText, QStringLiteral("VEVENT"));
249
1/2
✗ Branch 54 → 55 not taken.
✓ Branch 54 → 56 taken 6 times.
6 if (vevent.name.isEmpty()) return {};
250
4/4
✓ Branch 56 → 57 taken 6 times.
✓ Branch 58 → 59 taken 6 times.
✓ Branch 61 → 62 taken 3 times.
✓ Branch 61 → 63 taken 3 times.
6 if (!setAttendeeStatus(&vevent, email, {}, partstat.toUpper(), false)) return {};
251
1/1
✓ Branch 63 → 64 taken 3 times.
3 return ICal::serialize(vevent);
252 24 }
253
254 12 QStringList changedFields(const CalendarEvent &stored, const CalendarEvent &updated) {
255 12 QStringList fields;
256
1/2
✓ Branch 3 → 4 taken 26 times.
✗ Branch 3 → 5 not taken.
26 const auto instant = [](const QDateTime &value) { return value.isValid() ? value.toUTC() : QDateTime(); };
257
9/15
✓ Branch 2 → 3 taken 12 times.
✗ Branch 2 → 11 not taken.
✓ Branch 3 → 4 taken 12 times.
✓ Branch 4 → 5 taken 12 times.
✓ Branch 5 → 6 taken 12 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 11 taken 11 times.
✓ Branch 19 → 20 taken 12 times.
✗ Branch 19 → 21 not taken.
✓ Branch 21 → 22 taken 11 times.
✓ Branch 21 → 29 taken 1 time.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
25 if (stored.allDay != updated.allDay || instant(stored.dtStart) != instant(updated.dtStart) ||
258
9/15
✓ Branch 7 → 8 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 11 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 11 times.
✓ Branch 17 → 18 taken 12 times.
✗ Branch 17 → 19 not taken.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 73 not taken.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
13 instant(stored.dtEnd) != instant(updated.dtEnd))
259
1/1
✓ Branch 25 → 26 taken 11 times.
11 fields << QStringLiteral("time");
260
3/3
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 38 taken 11 times.
✓ Branch 34 → 35 taken 1 time.
13 if (stored.location != updated.location) fields << QStringLiteral("location");
261
1/3
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 47 taken 12 times.
✗ Branch 43 → 44 not taken.
12 if (stored.summary != updated.summary) fields << QStringLiteral("title");
262
1/3
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 56 taken 12 times.
✗ Branch 52 → 53 not taken.
12 if (stored.rrule != updated.rrule) fields << QStringLiteral("recurrence");
263
3/5
✓ Branch 56 → 57 taken 12 times.
✓ Branch 57 → 58 taken 12 times.
✗ Branch 61 → 62 not taken.
✓ Branch 61 → 69 taken 12 times.
✗ Branch 65 → 66 not taken.
12 if (stored.description.trimmed() != updated.description.trimmed()) fields << QStringLiteral("description");
264 12 return fields;
265 ✗ }
266
267 49 int sequenceOf(const QString &veventText) {
268
1/1
✓ Branch 5 → 6 taken 49 times.
49 const ICal::Component vevent = ICal::parseFirst(veventText, QStringLiteral("VEVENT"));
269
3/3
✓ Branch 11 → 12 taken 49 times.
✓ Branch 12 → 13 taken 49 times.
✓ Branch 13 → 14 taken 49 times.
98 return vevent.value(QStringLiteral("SEQUENCE")).trimmed().toInt();
270 49 }
271
272 } // namespace Imip
273