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 |