From 5452cd7396ef419bde39e531b9d1b87468ece6f8 Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Tue, 24 May 2022 17:29:56 -0400 Subject: [PATCH] fix(calendar): filter by matching property values Added test for calendar-query DAV request --- .../Appointments/SOGoAppointmentFolder.m | 10 +-- .../Appointments/SOGoAppointmentObject.m | 8 +-- Tests/lib/WebDAV.js | 14 +++++ Tests/spec/CalDAVPropertiesSpec.js | 61 +++++++++++++++++++ Tests/spec/DAVCalendarAppleiCalSpec.js | 14 +++-- 5 files changed, 94 insertions(+), 13 deletions(-) diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index f0c320826..1cf3681f1 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -793,7 +793,7 @@ static Class iCalEventK = nil; if ([title length]) [baseWhere - addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'", + addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '*%@*'", [title asSafeSQLLikeString]]]; if (component) @@ -1576,14 +1576,14 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir { if ([filters isEqualToString:@"title_Category_Location"] || [filters isEqualToString:@"entireContent"]) { - [baseWhere addObject: [NSString stringWithFormat: @"(c_title isCaseInsensitiveLike: '%%%@%%' OR c_category isCaseInsensitiveLike: '%%%@%%' OR c_location isCaseInsensitiveLike: '%%%@%%')", + [baseWhere addObject: [NSString stringWithFormat: @"(c_title isCaseInsensitiveLike: '*%@*' OR c_category isCaseInsensitiveLike: '*%@*' OR c_location isCaseInsensitiveLike: '*%@*')", [title asSafeSQLLikeString], [title asSafeSQLLikeString], [title asSafeSQLLikeString]]]; } } else - [baseWhere addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'", + [baseWhere addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '*%@*'", [title asSafeSQLLikeString]]]; } @@ -1954,7 +1954,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir filterString = [NSString stringWithFormat: @"(%@ = '')", key]; else filterString - = [NSString stringWithFormat: @"(%@ like '%%%@%%')", key, value]; + = [NSString stringWithFormat: @"(%@ like '*%@*')", key, value]; } else filterString = [NSString stringWithFormat: @"(%@ != '')", key]; @@ -2169,6 +2169,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir return davSQLFieldsTable; } +// CALDAV:calendar-query REPORT +// https://datatracker.ietf.org/doc/html/rfc4791#section-7.8 - (id) davCalendarQuery: (id) queryContext { WOResponse *r; diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 41c56cae4..bd70a7695 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -1731,10 +1731,10 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent ex = [self exceptionWithHTTPStatus: 404 // Not Found reason: @"user does not participate in this calendar event"]; } - else - ex = [self exceptionWithHTTPStatus: 500 // Server Error - reason: @"unable to parse event record"]; - + else + ex = [self exceptionWithHTTPStatus: 500 // Server Error + reason: @"unable to parse event record"]; + return ex; } diff --git a/Tests/lib/WebDAV.js b/Tests/lib/WebDAV.js index 36c2fb09f..66715bbb7 100644 --- a/Tests/lib/WebDAV.js +++ b/Tests/lib/WebDAV.js @@ -11,6 +11,7 @@ import { propfind, calendarMultiGet, + calendarQuery, createCalendarObject, makeCalendar, @@ -408,6 +409,19 @@ class WebDAV { }) } + calendarQuery(resource, filters) { + return calendarQuery({ + url: this.serverUrl + resource, + headers: this.headers, + depth: '1', + props: [ + { name: 'getetag', namespace: DAVNamespace.DAV }, + { name: 'calendar-data', namespace: DAVNamespace.CALDAV }, + ], + filters, + }) + } + calendarMultiGet(resource, filename) { return calendarMultiGet({ url: this.serverUrl + resource, diff --git a/Tests/spec/CalDAVPropertiesSpec.js b/Tests/spec/CalDAVPropertiesSpec.js index 22ab3cfda..a0f2af15e 100644 --- a/Tests/spec/CalDAVPropertiesSpec.js +++ b/Tests/spec/CalDAVPropertiesSpec.js @@ -1,8 +1,10 @@ import config from '../lib/config' import WebDAV from '../lib/WebDAV' +import TestUtility from '../lib/utilities' describe('read and set calendar properties', function() { const webdav = new WebDAV(config.username, config.password) + const utility = new TestUtility(webdav) const resource = `/SOGo/dav/${config.username}/Calendar/test-dav-properties/` beforeEach(async function() { @@ -56,6 +58,65 @@ describe('read and set calendar properties', function() { expect(results[0].status) .withContext(`Setting transparency to ${newValueNode} is successful`) .toBe(207) + }) + it("calendar-query", async function() { + const filename = `new.ics` + const event = `BEGIN:VCALENDAR +PRODID:-//Inverse//Event Generator//EN +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:0 +TRANSP:OPAQUE +UID:1234567890 +SUMMARY:Visit to the museum of fine arts +DTSTART:20090805T100000Z +DTEND:20090805T140000Z +CLASS:PUBLIC +DESCRIPTION:description +LOCATION:location +DTSTAMP:20090805T100000Z +END:VEVENT +END:VCALENDAR` + + let response = await webdav.createCalendarObject(resource, filename, event) + expect(response.status).toBe(201) + + response = await webdav.calendarQuery( + resource, + [ + { + type: 'comp-filter', + attributes: { name: 'VCALENDAR' }, + children: [ + { + type: 'comp-filter', + attributes: { name: 'VEVENT' }, + children: [ + { + type: 'prop-filter', + attributes: { name: 'TITLE' }, + children: [ + { + type: 'text-match', + value: 'museum' + } + ] + } + ] + } + ] + } + ] + ) + expect(response.length) + .withContext(`Number of results from calendar-query`) + .toBe(1) + expect(response[0].status) + .withContext(`HTTP status code of calendar-query`) + .toEqual(207) + expect(utility.componentsAreEqual(response[0].props.calendarData, event)) + .withContext(`Returned vCalendar matches ${filename}`) + .toBe(true) }) }) \ No newline at end of file diff --git a/Tests/spec/DAVCalendarAppleiCalSpec.js b/Tests/spec/DAVCalendarAppleiCalSpec.js index 46b28fa1b..4dcb9ea6b 100644 --- a/Tests/spec/DAVCalendarAppleiCalSpec.js +++ b/Tests/spec/DAVCalendarAppleiCalSpec.js @@ -88,6 +88,15 @@ describe('Apple iCal', function() { .toContain(`/SOGo/dav/${config.username}/`) } + beforeEach(async function() { + await _setMemberSet(config.username, [], 'read') + await _setMemberSet(config.username, [], 'write') + await _setMemberSet(config.subscriber_username, [], 'read') + await _setMemberSet(config.subscriber_username, [], 'write') + await _setMemberSet(config.superuser, [], 'read') + await _setMemberSet(config.superuser, [], 'write') + }) + // iCalTest it(`principal-collection-set: 'DAV' header must be returned with iCal 4`, async function() { @@ -127,11 +136,6 @@ describe('Apple iCal', function() { it(`calendar-proxy as used from iCal`, async function() { let membership, perm, users, proxyFor - await _setMemberSet(config.username, [], 'read') - await _setMemberSet(config.username, [], 'write') - await _setMemberSet(config.subscriber_username, [], 'read') - await _setMemberSet(config.subscriber_username, [], 'write') - membership = await _getMembership(config.username) expect(membership.length) .toBe(0)