From c5566e2668f96c8fd22315d85d93d07a648d444f Mon Sep 17 00:00:00 2001 From: Bertrand d'Aure Date: Fri, 17 Apr 2020 22:01:37 +0200 Subject: [PATCH 1/3] Fix data event parsing & add host --- front/src/config/i18n/en.json | 18 +++++++ front/src/config/i18n/fr.json | 18 +++++++ .../routes/integration/all/caldav/actions.js | 4 ++ .../caldav/lib/calendar/calendar.requests.js | 47 +++++++++++-------- .../calendar/calendar.syncUserCalendars.js | 3 +- .../caldav/lib/calendar/requests.test.js | 6 ++- 6 files changed, 73 insertions(+), 23 deletions(-) diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index 971605d0f1..950a8c2c3f 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -449,6 +449,24 @@ "password": "Password", "passwordInfo": "Generate an app-specific password and use this app-specific password instead of your account password." }, + "google": { + "name": "Google Calendar (deprecated)", + "url": "Google Calendar URL", + "urlInfo": "Let default URL, it will be set automatically.", + "username": "Email", + "usernameInfo": "Enter your Google email adress.", + "password": "Password", + "passwordInfo": "Generate an app-specific password and use this app-specific password instead of your account password." + }, + "synology": { + "name": "Synology Calendar", + "url": "Synology CalDAV URL", + "urlInfo": "In calendar application, click on 'CalDAV Account' and copy 'macOS/iOS' URL.", + "username": "Synology username", + "usernameInfo": "Enter your username.", + "password": "Password", + "passwordInfo": "Enter your password." + }, "other": { "name": "Other", "url": "CalDAV URL", diff --git a/front/src/config/i18n/fr.json b/front/src/config/i18n/fr.json index 742b26a447..8024f7780a 100644 --- a/front/src/config/i18n/fr.json +++ b/front/src/config/i18n/fr.json @@ -26,6 +26,24 @@ "password": "Mot de passe", "passwordInfo": "Générez un mot de passe application et utilisez le à la place du mot de passe de votre compte." }, + "google": { + "name": "Google Agenda (déprécié)", + "url": "URL Google Agenda", + "urlInfo": "Laissez l'URL par défaut, il sera paramétré automatiquement.", + "username": "Email", + "usernameInfo": "Entrez l'adresse email de votre compte Google.", + "password": "Mot de passe", + "passwordInfo": "Générez un mot de passe application et utilisez le à la place du mot de passe de votre compte." + }, + "synology": { + "name": "Calendrier Synology", + "url": "URL CalDAV Synology", + "urlInfo": "Dans l'application Calendrier, cliquez sur 'Compte CalDAV' et copiez l'URL 'macOS/iOS'.", + "username": "Nom d'utilisateur Synology", + "usernameInfo": "Entrez votre nom d'utilisateur.", + "password": "Mot de passe", + "passwordInfo": "Entrez votre mot de passe." + }, "other": { "name": "Autre", "url": "URL CalDAV", diff --git a/front/src/routes/integration/all/caldav/actions.js b/front/src/routes/integration/all/caldav/actions.js index 0af647db71..2d051355f3 100644 --- a/front/src/routes/integration/all/caldav/actions.js +++ b/front/src/routes/integration/all/caldav/actions.js @@ -11,6 +11,10 @@ const actions = store => ({ store.setState({ caldavUrl: 'https://caldav.icloud.com' }); + } else if (e.target.value === 'google') { + store.setState({ + caldavUrl: 'https://www.google.com/calendar/dav' + }); } }, updateCaldavUrl(state, e) { diff --git a/server/services/caldav/lib/calendar/calendar.requests.js b/server/services/caldav/lib/calendar/calendar.requests.js index ab552d0371..6de66517cf 100644 --- a/server/services/caldav/lib/calendar/calendar.requests.js +++ b/server/services/caldav/lib/calendar/calendar.requests.js @@ -1,6 +1,18 @@ const url = require('url'); const logger = require('../../../../utils/logger'); +/** + * @description Search elements as getElementsByTagName with regex instead of string. + * @param {Object} container - Element where execute research. + * @param {RegExp} regex - Regex to search. + * @returns {Array} Elements that match with regex. + * @example + * getElementsByTagRegex(document, /(cal:)?calendar-data/) + */ +function getElementsByTagRegex(container, regex) { + return Array.from(container.getElementsByTagName('*')).filter((elem) => regex.test(elem.tagName)); +} + /** * @description Get calendars from caldav server. * @param {Object} xhr - Request with dav credentials. @@ -105,36 +117,31 @@ async function requestEventsData(xhr, calendarUrl, eventsToUpdate, calDavHost) { `, + transformRequest: (request) => request.setRequestHeader('Content-Type', 'application/xml;charset=utf-8'), }); const eventsData = await xhr.send(req, calendarUrl); - const tags = {}; - switch (calDavHost) { - case 'apple': - tags.response = 'response'; - tags.calendarData = 'calendar-data'; - tags.href = 'href'; - break; - default: - tags.response = 'd:response'; - tags.calendarData = 'cal:calendar-data'; - tags.href = 'd:href'; - break; - } // Extract data from XML response const xmlEvents = new this.xmlDom.DOMParser().parseFromString(eventsData.request.responseText); - const jsonEvents = Array.from(xmlEvents.getElementsByTagName(tags.response)).map((xmlEvent) => { - const event = this.ical.parseICS(xmlEvent.getElementsByTagName(tags.calendarData)[0].childNodes[0].data); - return { - href: xmlEvent.getElementsByTagName(tags.href)[0].childNodes[0].data, - ...event[Object.keys(event)[0]], - }; + const jsonEvents = getElementsByTagRegex(xmlEvents, /^[a-zA-Z]*:?response$/).map((xmlEvent) => { + const event = this.ical.parseICS( + getElementsByTagRegex(xmlEvent, /^[a-zA-Z]*:?calendar-data$/)[0].childNodes[0].data, + ); + for (let j = 0; j < Object.keys(event).length; j += 1) { + if (event[Object.keys(event)[j]].type === 'VEVENT') { + return { + href: getElementsByTagRegex(xmlEvent, /^[a-zA-Z]*:?href$/)[0].childNodes[0].data, + ...event[Object.keys(event)[j]], + }; + } + } + return null; }); // Filter only event objects return jsonEvents.filter((jsonEvent) => { - return jsonEvent.type === 'VEVENT'; + return jsonEvent !== null; }); } diff --git a/server/services/caldav/lib/calendar/calendar.syncUserCalendars.js b/server/services/caldav/lib/calendar/calendar.syncUserCalendars.js index 78cfd12ef2..e39427b18d 100644 --- a/server/services/caldav/lib/calendar/calendar.syncUserCalendars.js +++ b/server/services/caldav/lib/calendar/calendar.syncUserCalendars.js @@ -113,8 +113,7 @@ async function syncUserCalendars(userId) { return this.gladys.calendar.updateEvent(gladysEvents[0].selector, formatedEvent); }), ); - - logger.info(`CalDAV : ${savedEvents.length} events updated.`); + logger.info(`CalDAV : ${savedEvents.length} events updated for calendar ${calendarToUpdate.name}.`); }, { concurrency: 1 }, ); diff --git a/server/test/services/caldav/lib/calendar/requests.test.js b/server/test/services/caldav/lib/calendar/requests.test.js index 7cc19131d3..5c9289f400 100644 --- a/server/test/services/caldav/lib/calendar/requests.test.js +++ b/server/test/services/caldav/lib/calendar/requests.test.js @@ -50,11 +50,13 @@ describe('CalDAV requests', () => { parseFromString: sinon.stub().returns({ getElementsByTagName: sinon.stub().returns([ { + tagName: 'response', getElementsByTagName: sinon .stub() .onFirstCall() .returns([ { + tagName: 'calendar-data', childNodes: [ { data: ` @@ -82,7 +84,9 @@ describe('CalDAV requests', () => { }, ]) .onSecondCall() - .returns([{ childNodes: [{ data: 'https://caldav.host.com/home/personal/event-1.ics' }] }]), + .returns([ + { tagName: 'href', childNodes: [{ data: 'https://caldav.host.com/home/personal/event-1.ics' }] }, + ]), }, ]), }), From c8b9159cddc4d655fbe1486d5f3ecb0c68c930f2 Mon Sep 17 00:00:00 2001 From: Bertrand d'Aure Date: Sat, 25 Apr 2020 16:14:47 +0200 Subject: [PATCH 2/3] Improve code coverage --- server/test/services/caldav/lib/calendar/requests.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/test/services/caldav/lib/calendar/requests.test.js b/server/test/services/caldav/lib/calendar/requests.test.js index 5c9289f400..b4b524a81a 100644 --- a/server/test/services/caldav/lib/calendar/requests.test.js +++ b/server/test/services/caldav/lib/calendar/requests.test.js @@ -250,6 +250,7 @@ describe('CalDAV requests', () => { const xhr = { send: sinon.stub(), }; + const setRequestHeader = sinon.stub(); xhr.send.resolves({ request: { responseText: '' } }); const eventsData = await requests.requestEventsData( xhr, @@ -271,6 +272,9 @@ describe('CalDAV requests', () => { 'other', ); + requests.dav.Request.args[0][0].transformRequest({ setRequestHeader }); + expect(setRequestHeader.args[0]).to.eql(['Content-Type', 'application/xml;charset=utf-8']); + expect(eventsData).to.eql([ { href: 'https://caldav.host.com/home/personal/event-1.ics', From 8f00e1e8a0c1d5e54939d838c867e9f97c6ab53a Mon Sep 17 00:00:00 2001 From: Bertrand d'Aure Date: Thu, 28 May 2020 22:47:46 +0200 Subject: [PATCH 3/3] Improve code coverage --- .../caldav/lib/calendar/requests.test.js | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/server/test/services/caldav/lib/calendar/requests.test.js b/server/test/services/caldav/lib/calendar/requests.test.js index b4b524a81a..ccbbef9195 100644 --- a/server/test/services/caldav/lib/calendar/requests.test.js +++ b/server/test/services/caldav/lib/calendar/requests.test.js @@ -35,15 +35,20 @@ describe('CalDAV requests', () => { Request: sinon.stub().returns({ requestDate: 'request3' }), }, ical: { - parseICS: sinon.stub().returns({ - data: { - type: 'VEVENT', - uid: '49193db9-f666-4947-8ce6-3357ce3b7166', - summary: 'Evenement 1', - start: new Date('2018-06-08'), - end: new Date('2018-06-09'), - }, - }), + parseICS: sinon + .stub() + .onFirstCall() + .returns({ + data: { + type: 'VEVENT', + uid: '49193db9-f666-4947-8ce6-3357ce3b7166', + summary: 'Evenement 1', + start: new Date('2018-06-08'), + end: new Date('2018-06-09'), + }, + }) + .onSecondCall() + .returns({}), }, xmlDom: { DOMParser: sinon.stub().returns({ @@ -88,6 +93,19 @@ describe('CalDAV requests', () => { { tagName: 'href', childNodes: [{ data: 'https://caldav.host.com/home/personal/event-1.ics' }] }, ]), }, + { + tagName: 'response', + getElementsByTagName: sinon.stub().returns([ + { + tagName: 'calendar-data', + childNodes: [ + { + data: '', + }, + ], + }, + ]), + }, ]), }), }),