Subject: bug#50749: 28.0.50; three gnus-icalendar issues w.r.t. RFC5546 and party crashers

From: Alexandre Duret-Lutz <[email-masked]>
Date: Thu, 23 Sep 2021 09:47:59 +0200

Hi,

At my job, some colleagues regularly send iCal invitations to mailing
lists, and expect the recipients of these mailing lists to accept the
invitation individually to indicate whether they are coming or not.

The iCal invitations only lists the mailing list has an "attendee", not
the all final recipients.

When I receive such an invitation in Gnus,
gnus-icalendar-event->gnus-calendar will tell me that
"You are not listed as an attendee", and rightly so.

However the code in gnus-icalendar-event:inline-reply-buttons
prevents me from replying to the invitation by hiding the
buttons.  This is not desired: I still want to reply.

RFC5546 specifically allows users not listed as attendee to reply to an
invitation.  Section 3.2.3 refers to those as a party crashers.

Patch 0002 fixes this issue, which is the most important to me.  In that
patch, I was hesitant to remove the (lwarn...) message.  I have tested
that party crashing work with invitations sent to me (indirectly) by
Outlook users.  It does not work with Google Calendar, but I think the
issue is on their side (someone else reported this on stackoverflow
at https://stackoverflow.com/questions/66519665/ )

While working on that and discovering RFC5546 (which is completely new to
me), I found two other issues in the gnus-icalendar code.

One is that when replying to an iCal invitation, the mail should be sent
to the "organizer" of that event, not to the sender of the email.  This
matters for forwarded invitations, for instance (and forwarded
invitations in turn are related to supporting party crashers).  Also
some calendar systems can use dedicated email addresses to handle those
events: Google Calendar, for instance, uses mail addresses in
"[email-masked]" for secondary calendars, and lists those
as organizers of events in these calendars (but so far, mail replies to
those event seems simply ignored, I'll try to find a place where to
report that).

Patch 0001 fixes the recipient of the invitation response.

Finally, while displaying the mail sent for party-crasher responses
(after patch 0002), I noticed that gnus-icalendar-event->gnus-calendar
was still telling me that "You are not listed as an attendee", despite
actually being the only attendee listed.  This is because the code
assumes that a missing ROLE parameter on the ATTENDEE line signifies
NON-PARTICIPANT.  That's not what RFC5546 specifies in section 4.2.1:
the default value for the "ROLE" parameter is "REQ-PARTICIPANT" and it
need not be enumerated.  Patch 0003 fixes that.

>From 04ed215bfa211620b6bd157fa745cd59f6a891ed Mon Sep 17 00:00:00 2001
From: Alexandre Duret-Lutz <[email-masked]>
Date: Wed, 22 Sep 2021 16:30:21 +0200
Subject: [PATCH 1/3] reply to the organizer of an ical event

RFC5546 specifies that participant status (accepted, tentative,
declined) should be sent to the organizer of the event.  That
organizer is not necessarily the sender of the invitation; for
instance Google Calendar uses custom email addresses to receive these
notifications.

* lisp/gnus/gnus-icalendar.el (gnus-icalendar-send-buffer-by-mail):
Replace the default recipient of the reply by the organizer of the
event.
(gnus-icalendar-reply) Pass that organizer to the previous function.
---
 lisp/gnus/gnus-icalendar.el | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el
index b6e5e7f786..514feff284 100644
--- a/lisp/gnus/gnus-icalendar.el
+++ b/lisp/gnus/gnus-icalendar.el
@@ -847,10 +847,14 @@ gnus-icalendar-insert-button
        button t
        gnus-data ,data))))
 
-(defun gnus-icalendar-send-buffer-by-mail (buffer-name subject)
+(defun gnus-icalendar-send-buffer-by-mail (buffer-name subject organizer)
   (let ((message-signature nil))
     (with-current-buffer gnus-summary-buffer
       (gnus-summary-reply)
+      ;; Reply to the organizer, not to whoever sent the invitation. person
+      ;; Some calendar systems use specific email address as organizer to
+      ;; receive these responses.
+      (message-replace-header "To" organizer)
       (message-goto-body)
       (mml-insert-multipart "alternative")
       (mml-insert-empty-tag 'part 'type "text/plain")
@@ -866,7 +870,8 @@ gnus-icalendar-reply
          (event (caddr data))
          (reply (gnus-icalendar-with-decoded-handle handle
                   (gnus-icalendar-event-reply-from-buffer
-                   (current-buffer) status (gnus-icalendar-identities)))))
+                   (current-buffer) status (gnus-icalendar-identities))))
+         (organizer (gnus-icalendar-event:organizer event)))
 
     (when reply
       (cl-labels
@@ -883,7 +888,7 @@ gnus-icalendar-reply
             (delete-region (point-min) (point-max))
             (insert reply)
             (fold-icalendar-buffer)
-            (gnus-icalendar-send-buffer-by-mail (buffer-name) subject))
+            (gnus-icalendar-send-buffer-by-mail (buffer-name) subject organizer))
 
           ;; Back in article buffer
           (setq-local gnus-icalendar-reply-status status)
-- 
2.33.0

>From bd4dcf7273107d5855240ee6c9fad4eb9607bd33 Mon Sep 17 00:00:00 2001
From: Alexandre Duret-Lutz <[email-masked]>
Date: Wed, 22 Sep 2021 16:38:42 +0200
Subject: [PATCH 2/3] allow party crashers to respond to ical events

If an ical invitation is sent to a mailing list, the recipients
are probably not listed as attendees.  However there are
legitimate situations where these unlisted (or indirectly listed)
recipients are still expected to respond.  RFC5546 allows that,
calling those respondents "party crashers".

* lisp/gnus/gnus-icalendar.el
(gnus-icalendar-event:inline-reply-buttons): Display the response
buttons even if the user was not found in the list of attendees,
but change the labels of those buttons to make clear they are
not explicitly invited.
(gnus-icalendar-event--build-reply-event-body): Add an attendee
line for the user in case one was not found.
---
 lisp/gnus/gnus-icalendar.el | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el
index 514feff284..348da12db1 100644
--- a/lisp/gnus/gnus-icalendar.el
+++ b/lisp/gnus/gnus-icalendar.el
@@ -345,10 +345,16 @@ gnus-icalendar-event--build-reply-event-body
 
       (mapc #'process-event-line (split-string ical-request "\n"))
 
+      ;; RFC5546 refers to uninvited attendees as "party crashers".
+      ;; This situation is common if the invitation is sent to a group
+      ;; of people via a mailing list.
       (unless (gnus-icalendar-find-if (lambda (x) (string-match "^ATTENDEE" x))
 				      reply-event-lines)
         (lwarn 'gnus-icalendar :warning
-               "Could not find an event attendee matching given identity"))
+               "Could not find an event attendee matching given identity")
+        (push (format "ATTENDEE;RSVP=TRUE;PARTSTAT=%s;CN=%s:MAILTO:%s"
+                      attendee-status user-full-name user-mail-address)
+              reply-event-lines))
 
       (mapconcat #'identity `("BEGIN:VEVENT"
                               ,@(nreverse reply-event-lines)
@@ -902,10 +908,16 @@ gnus-icalendar-sync-event-to-org
   (gnus-icalendar-event:sync-to-org event gnus-icalendar-reply-status))
 
 (cl-defmethod gnus-icalendar-event:inline-reply-buttons ((event gnus-icalendar-event) handle)
-  (when (gnus-icalendar-event:rsvp event)
-    `(("Accept" gnus-icalendar-reply (,handle accepted ,event))
-      ("Tentative" gnus-icalendar-reply (,handle tentative ,event))
-      ("Decline" gnus-icalendar-reply (,handle declined ,event)))))
+  (let ((accept-btn "Accept")
+        (tentative-btn "Tentative")
+        (decline-btn "Decline"))
+    (unless (gnus-icalendar-event:rsvp event)
+      (setq accept-btn "Uninvited Accept"
+            tentative-btn "Uninvited Tentative"
+            decline-btn "Uninvited Decline"))
+    `((,accept-btn gnus-icalendar-reply (,handle accepted ,event))
+      (,tentative-btn gnus-icalendar-reply (,handle tentative ,event))
+      (,decline-btn gnus-icalendar-reply (,handle declined ,event)))))
 
 (cl-defmethod gnus-icalendar-event:inline-reply-buttons ((_event gnus-icalendar-event-reply) _handle)
   "No buttons for REPLY events."
-- 
2.33.0

>From 04e042d3ea9ba8760071e6fee4a648a54027138f Mon Sep 17 00:00:00 2001
From: Alexandre Duret-Lutz <[email-masked]>
Date: Wed, 22 Sep 2021 22:28:28 +0200
Subject: [PATCH 3/3] default role for attendees is REQ-PARTICIPANT

* lisp/gnus/gnus-icalendar.el
(gnus-icalendar-event--get-attendee-names,
gnus-icalendar-event-from-ical): When the ROLE property
is missing from an ATTENDEE line, follow RFC5546 and default
to REQ-PARTICIPANT.
---
 lisp/gnus/gnus-icalendar.el | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el
index 348da12db1..a2ae2a9668 100644
--- a/lisp/gnus/gnus-icalendar.el
+++ b/lisp/gnus/gnus-icalendar.el
@@ -194,7 +194,9 @@ gnus-icalendar-event--get-attendee-names
                           (caddr event))))
 
     (cl-labels
-	((attendee-role (prop) (plist-get (cadr prop) 'ROLE))
+	((attendee-role (prop)
+                        ;; RFC5546: default ROLE is REQ-PARTICIPANT
+                        (or (plist-get (cadr prop) 'ROLE) "REQ-PARTICIPANT"))
 	 (attendee-name
 	  (prop)
 	  (or (plist-get (cadr prop) 'CN)
@@ -225,7 +227,8 @@ gnus-icalendar-event-from-ical
                      (gnus-icalendar-event--find-attendee
                       ical attendee-name-or-email)))
          (attendee-names (gnus-icalendar-event--get-attendee-names ical))
-         (role (plist-get (cadr attendee) 'ROLE))
+         ;; RFC5546: default ROLE is REQ-PARTICIPANT
+         (role (or (plist-get (cadr attendee) 'ROLE) "REQ-PARTICIPANT"))
          (participation-type (pcase role
                                ("REQ-PARTICIPANT" 'required)
                                ("OPT-PARTICIPANT" 'optional)
-- 
2.33.0