2424use Sabre \CalDAV \Xml \Property \ScheduleCalendarTransp ;
2525use Sabre \DAV \Exception \Conflict ;
2626use Sabre \VObject \Component \VCalendar ;
27- use Sabre \VObject \Component \VEvent ;
2827use Sabre \VObject \Component \VTimeZone ;
2928use Sabre \VObject \ITip \Message ;
29+ use Sabre \VObject \ParseException ;
3030use Sabre \VObject \Property ;
3131use Sabre \VObject \Reader ;
3232
@@ -41,6 +41,9 @@ public function __construct(
4141 ) {
4242 }
4343
44+ private const DAV_PROPERTY_USER_ADDRESS = '{http://sabredav.org/ns}email-address ' ;
45+ private const DAV_PROPERTY_USER_ADDRESSES = '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set ' ;
46+
4447 /**
4548 * @return string defining the technical unique key
4649 * @since 13.0.0
@@ -219,58 +222,93 @@ public function createFromStringMinimal(string $name, string $calendarData): voi
219222 * @throws CalendarException
220223 */
221224 public function handleIMipMessage (string $ name , string $ calendarData ): void {
222- $ server = $ this ->getInvitationResponseServer ();
223-
224- /** @var CustomPrincipalPlugin $plugin */
225- $ plugin = $ server ->getServer ()->getPlugin ('auth ' );
226- // we're working around the previous implementation
227- // that only allowed the public system principal to be used
228- // so set the custom principal here
229- $ plugin ->setCurrentPrincipal ($ this ->calendar ->getPrincipalURI ());
230225
231- if (empty ($ this ->calendarInfo ['uri ' ])) {
232- throw new CalendarException ('Could not write to calendar as URI parameter is missing ' );
226+ try {
227+ /** @var VCalendar $vObject|null */
228+ $ vObject = Reader::read ($ calendarData );
229+ } catch (ParseException $ e ) {
230+ throw new CalendarException ('iMip message could not be processed because an error occurred while parsing the iMip message ' , 0 , $ e );
233231 }
234- // Force calendar change URI
235- /** @var Schedule\Plugin $schedulingPlugin */
236- $ schedulingPlugin = $ server ->getServer ()->getPlugin ('caldav-schedule ' );
237- // Let sabre handle the rest
238- $ iTipMessage = new Message ();
239- /** @var VCalendar $vObject */
240- $ vObject = Reader::read ($ calendarData );
241- /** @var VEvent $vEvent */
242- $ vEvent = $ vObject ->{'VEVENT ' };
243-
244- if ($ vObject ->{'METHOD ' } === null ) {
245- throw new CalendarException ('No Method provided for scheduling data. Could not process message ' );
232+ // validate the iMip message
233+ if (!isset ($ vObject ->METHOD )) {
234+ throw new CalendarException ('iMip message contains no valid method ' );
246235 }
247-
248- if (!isset ($ vEvent ->{'ORGANIZER ' }) || !isset ($ vEvent ->{'ATTENDEE ' })) {
249- throw new CalendarException ('Could not process scheduling data, neccessary data missing from ICAL ' );
236+ if (!isset ($ vObject ->VEVENT )) {
237+ throw new CalendarException ('iMip message contains no event ' );
238+ }
239+ if (!isset ($ vObject ->VEVENT ->UID )) {
240+ throw new CalendarException ('iMip message event dose not contain a UID ' );
250241 }
251- $ organizer = $ vEvent ->{'ORGANIZER ' }->getValue ();
252- $ attendee = $ vEvent ->{'ATTENDEE ' }->getValue ();
253-
254- $ iTipMessage ->method = $ vObject ->{'METHOD ' }->getValue ();
255- if ($ iTipMessage ->method === 'REQUEST ' ) {
256- $ iTipMessage ->sender = $ organizer ;
257- $ iTipMessage ->recipient = $ attendee ;
258- } elseif ($ iTipMessage ->method === 'REPLY ' ) {
259- if ($ server ->isExternalAttendee ($ vEvent ->{'ATTENDEE ' }->getValue ())) {
260- $ iTipMessage ->recipient = $ organizer ;
261- } else {
262- $ iTipMessage ->recipient = $ attendee ;
242+ if (!isset ($ vObject ->VEVENT ->ORGANIZER )) {
243+ throw new CalendarException ('iMip message event dose not contain an organizer ' );
244+ }
245+ if (!isset ($ vObject ->VEVENT ->ATTENDEE )) {
246+ throw new CalendarException ('iMip message event dose not contain an attendee ' );
247+ }
248+ if (empty ($ this ->calendarInfo ['uri ' ])) {
249+ throw new CalendarException ('Could not write to calendar as URI parameter is missing ' );
250+ }
251+ // construct dav server
252+ $ server = $ this ->getInvitationResponseServer ();
253+ /** @var CustomPrincipalPlugin $authPlugin */
254+ $ authPlugin = $ server ->getServer ()->getPlugin ('auth ' );
255+ // we're working around the previous implementation
256+ // that only allowed the public system principal to be used
257+ // so set the custom principal here
258+ $ authPlugin ->setCurrentPrincipal ($ this ->calendar ->getPrincipalURI ());
259+ // retrieve all users addresses
260+ $ userProperties = $ server ->getServer ()->getProperties ($ this ->calendar ->getPrincipalURI (), [ self ::DAV_PROPERTY_USER_ADDRESS , self ::DAV_PROPERTY_USER_ADDRESSES ]);
261+ $ userAddress = 'mailto: ' . ($ userProperties [self ::DAV_PROPERTY_USER_ADDRESS ] ?? null );
262+ $ userAddresses = $ userProperties [self ::DAV_PROPERTY_USER_ADDRESSES ]->getHrefs () ?? [];
263+ $ userAddresses = array_map ('strtolower ' , array_map ('urldecode ' , $ userAddresses ));
264+ // validate the method, recipient and sender
265+ $ imipMethod = strtoupper ($ vObject ->METHOD ->getValue ());
266+ if (in_array ($ imipMethod , ['REPLY ' , 'REFRESH ' ], true )) {
267+ // extract sender (REPLY and REFRESH method should only have one attendee)
268+ $ sender = strtolower ($ vObject ->VEVENT ->ATTENDEE ->getValue ());
269+ // extract and verify the recipient
270+ $ recipient = strtolower ($ vObject ->VEVENT ->ORGANIZER ->getValue ());
271+ if (!in_array ($ recipient , $ userAddresses , true )) {
272+ throw new CalendarException ('iMip message dose not contain an organizer that matches the user ' );
273+ }
274+ // if the recipient address is not the same as the user address this means an alias was used
275+ // the iTip broker uses the users primary email address during processing
276+ if ($ userAddress !== $ recipient ) {
277+ $ recipient = $ userAddress ;
278+ }
279+ } elseif (in_array ($ imipMethod , ['PUBLISH ' , 'REQUEST ' , 'ADD ' , 'CANCEL ' ], true )) {
280+ // extract sender
281+ $ sender = strtolower ($ vObject ->VEVENT ->ORGANIZER ->getValue ());
282+ // extract and verify the recipient
283+ foreach ($ vObject ->VEVENT ->ATTENDEE as $ attendee ) {
284+ $ recipient = strtolower ($ attendee ->getValue ());
285+ if (in_array ($ recipient , $ userAddresses , true )) {
286+ break ;
287+ }
288+ $ recipient = null ;
289+ }
290+ if ($ recipient === null ) {
291+ throw new CalendarException ('iMip message dose not contain an attendee that matches the user ' );
292+ }
293+ // if the recipient address is not the same as the user address this means an alias was used
294+ // the iTip broker uses the users primary email address during processing
295+ if ($ userAddress !== $ recipient ) {
296+ $ recipient = $ userAddress ;
263297 }
264- $ iTipMessage ->sender = $ attendee ;
265- } elseif ($ iTipMessage ->method === 'CANCEL ' ) {
266- $ iTipMessage ->recipient = $ attendee ;
267- $ iTipMessage ->sender = $ organizer ;
298+ } else {
299+ throw new CalendarException ('iMip message contains a method that is not supported: ' . $ imipMethod );
268300 }
269- $ iTipMessage ->uid = isset ($ vEvent ->{'UID ' }) ? $ vEvent ->{'UID ' }->getValue () : '' ;
270- $ iTipMessage ->component = 'VEVENT ' ;
271- $ iTipMessage ->sequence = isset ($ vEvent ->{'SEQUENCE ' }) ? (int )$ vEvent ->{'SEQUENCE ' }->getValue () : 0 ;
272- $ iTipMessage ->message = $ vObject ;
273- $ server ->server ->emit ('schedule ' , [$ iTipMessage ]);
301+ // generate the iTip message
302+ $ iTip = new Message ();
303+ $ iTip ->method = $ imipMethod ;
304+ $ iTip ->sender = $ sender ;
305+ $ iTip ->recipient = $ recipient ;
306+ $ iTip ->component = 'VEVENT ' ;
307+ $ iTip ->uid = $ vObject ->VEVENT ->UID ->getValue ();
308+ $ iTip ->sequence = isset ($ vObject ->VEVENT ->SEQUENCE ) ? (int )$ vObject ->VEVENT ->SEQUENCE ->getValue () : 1 ;
309+ $ iTip ->message = $ vObject ;
310+
311+ $ server ->server ->emit ('schedule ' , [$ iTip ]);
274312 }
275313
276314 public function getInvitationResponseServer (): InvitationResponseServer {
0 commit comments