From 7ad35134c001397f34986b62706797195efa393e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B8=D0=BB=D1=8F=D0=BD=20=D0=9F=D0=B0=D0=BB=D0=B0?= =?UTF-8?q?=D1=83=D0=B7=D0=BE=D0=B2?= Date: Sun, 21 Mar 2021 18:59:40 +0200 Subject: [PATCH 1/3] Reorganize list_calendars() and list_addressbooks() to enable editing the properties over AJAX. - merge imap/http_carddav.js into imap/http_caldav.js and make the latter more generic, in order to serve both CalDAV and CardDAV administration. - all calendars are numbered in the generated HTML, starting from zero and each JavaScript function receives as parameter the calendar/addressbook number, on which to enact an action. --- Makefile.am | 5 +- imap/http_caldav.c | 46 +++++++------- imap/http_caldav.js | 142 ++++++++++++++++++++----------------------- imap/http_carddav.c | 37 +++++------ imap/http_carddav.js | Bin 5740 -> 0 bytes 5 files changed, 108 insertions(+), 122 deletions(-) delete mode 100644 imap/http_carddav.js diff --git a/Makefile.am b/Makefile.am index 1a5e29e1db..9068481b3d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -118,7 +118,6 @@ MAINTAINERCLEANFILES = \ doc/legacy/murder.png \ doc/legacy/netnews.png \ imap/http_caldav_js.h \ - imap/http_carddav_js.h \ man/imapd.conf.5 \ man/sieveshell.1 \ sieve/addr.h \ @@ -249,7 +248,6 @@ AM_LDFLAGS += $(HTTP_LIBS) BUILT_SOURCES += \ imap/http_caldav_js.h \ - imap/http_carddav_js.h \ imap/tz_err.c \ imap/tz_err.h @@ -343,7 +341,6 @@ EXTRA_DIST = \ doc \ docsrc \ imap/http_caldav.js \ - imap/http_carddav.js \ imap/http_err.et \ imap/imap_err.et \ imap/jmap_err.et \ @@ -1279,7 +1276,7 @@ imap_httpd_SOURCES = \ imap/smtpclient.c \ imap/spool.c \ imap/sync_support.c \ - imap/sync_support.h \ + imap/sync_support.h \ imap/xcal.c \ imap/xcal.h \ imap/xml_support.c \ diff --git a/imap/http_caldav.c b/imap/http_caldav.c index 5e2c04df41..d523b8af06 100644 --- a/imap/http_caldav.c +++ b/imap/http_caldav.c @@ -2247,9 +2247,8 @@ static int list_calendars(struct transaction_t *txn) buf_printf_markup(body, level, ""); buf_printf_markup(body, level, "
" - " ", - base_path); + " onclick='createCollection()'>" + " "); buf_printf_markup(body, --level, ""); buf_printf_markup(body, --level, ""); @@ -2293,8 +2292,8 @@ static int list_calendars(struct transaction_t *txn) } /* Calendar name */ - buf_printf_markup(body, level++, ""); - buf_printf_markup(body, level, "%s%s%s", i, + buf_printf_markup(body, level++, "", i, cal->shortname); + buf_printf_markup(body, level, "%s%s%s", (cal->flags & CAL_IS_DEFAULT) ? "" : "", displayname, (cal->flags & CAL_IS_DEFAULT) ? "" : ""); @@ -2303,9 +2302,8 @@ static int list_calendars(struct transaction_t *txn) /* Supported components list */ buf_printf_markup(body, level++, ""); buf_printf_markup(body, level++, - "", i); for (comp = cal_comps; comp->name; comp++) { buf_printf_markup(body, level, "%s", (cal->types & comp->type) ? " selected" : "", @@ -2324,29 +2322,31 @@ static int list_calendars(struct transaction_t *txn) base_path, cal->shortname); /* Delete button */ - buf_printf_markup(body, level, - "", - !(cal->flags & CAL_CAN_DELETE) ? " disabled" : "", - base_path, cal->shortname, i); + if (cal->flags & CAL_IS_DEFAULT) + buf_printf_markup(body, level, + "Default Calendar"); + else if (cal->flags & CAL_CAN_DELETE) + buf_printf_markup(body, level, + "", i); + else + buf_printf_markup(body, level, ""); /* Public (shared) checkbox */ buf_printf_markup(body, level, - "" + "" "Public", - !(cal->flags & CAL_CAN_ADMIN) ? " disabled" : "", - (cal->flags & CAL_IS_PUBLIC) ? " checked" : "", - base_path, cal->shortname); + (cal->flags & CAL_CAN_ADMIN) ? "" : " disabled", + (cal->flags & CAL_IS_PUBLIC) ? " checked" : "", i); /* Transparent checkbox */ buf_printf_markup(body, level, - "" + "" "Transparent", - !(cal->flags & CAL_CAN_ADMIN) ? " disabled" : "", - (cal->flags & CAL_IS_TRANSP) ? " checked" : "", - base_path, cal->shortname); + (cal->flags & CAL_CAN_ADMIN) ? "" : " disabled", + (cal->flags & CAL_IS_TRANSP) ? " checked" : "", i); buf_printf_markup(body, --level, ""); } diff --git a/imap/http_caldav.js b/imap/http_caldav.js index 6ed2a74a2d..f58c6154d8 100644 --- a/imap/http_caldav.js +++ b/imap/http_caldav.js @@ -1,4 +1,4 @@ -/* http_caldav.js -- Admin functions for calendar list +/* http_caldav.js -- Admin functions for addressbook and calendar list * * Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved. * @@ -44,8 +44,33 @@ // XML constants for requests var XML_DAV_NS = 'DAV:'; +const calendar = new URL(window.location.href).pathname[5] == 'c'; var XML_CALDAV_NS = 'urn:ietf:params:xml:ns:caldav'; -var X_CLIENT = 'Cyrus/%s'; // Version filled in by printf() in http_caldav.c +const X_CLIENT = 'Cyrus/%s'; // Version filled in by printf() in http_caldav.c/http_carddav.c +const XML_CALCARD_NS = 'urn:ietf:params:xml:ns:ca' + (calendar ? 'ldav' : 'rddav'); +const DESCRIPTION = (calendar ? 'calendar' : 'addressbook') + '-description'; + +function n(i) { + const h = window.location.href; + return (h[h.length - 1] == '/' ? h : h + '/') + (i !== undefined ? document.getElementById(i).dataset.url : ''); +} + +function propupdate(set, i) { + const doc = document.implementation.createDocument(XML_DAV_NS, "propertyupdate", null); + const props = doc.createElementNS(XML_DAV_NS, "prop"); + const op = doc.createElementNS(XML_DAV_NS, set ? "set" : "remove"); + doc.documentElement.appendChild(op); + op.appendChild(props); + + // Send PROPPATCH request (minimal response) + const req = new XMLHttpRequest(); + req.open('PROPPATCH', n(i)); + req.setRequestHeader('X-Client', X_CLIENT); + req.setRequestHeader('Prefer', 'return=minimal'); + req.submit = () => req.send(doc); + + return [doc, props, req]; +} // Calculate hash of a string @@ -61,18 +86,17 @@ function strHash(str) { } -// Create a new calendar collection using data from 'create' form -function createCalendar(baseurl) { +// Create a new collection using data from 'create' form +function createCollection() { var create = document.forms.create.elements; - if (create.name.value.length === 0) { - window.alert('New calendar MUST have a name'); - } + if (!create.name.value) + return window.alert('New ' + (calendar ? 'calendar' : 'addressbook') + ' MUST have a name'); // Generate calendar collection name var now = new Date(); var rand = Math.random() * 1000000; - var url = baseurl + strHash(baseurl).toString(16) + + const url = n() + strHash(n()).toString(16) + '-' + strHash(create.name.value).toString(16) + '-' + now.getTime() + '-' + rand.toFixed(0); @@ -88,27 +112,27 @@ function createCalendar(baseurl) { var prop = doc.createElementNS(XML_DAV_NS, "D:resourcetype"); prop.appendChild(doc.createElementNS(XML_DAV_NS, "D:collection")); - prop.appendChild(doc.createElementNS(XML_CALDAV_NS, "C:calendar")); + prop.appendChild(doc.createElementNS(XML_CALCARD_NS, calendar ? "C:calendar" : "C:addressbook")); props.appendChild(prop); prop = doc.createElementNS(XML_DAV_NS, "D:displayname"); prop.appendChild(doc.createTextNode(create.name.value)); props.appendChild(prop); - if (create.desc.value.length !== 0) { - prop = doc.createElementNS(XML_CALDAV_NS, "C:calendar-description"); + if (create.desc.value) { + prop = doc.createElementNS(XML_CALCARD_NS, DESCRIPTION); prop.appendChild(doc.createTextNode(create.desc.value)); props.appendChild(prop); } - if (create.tzid && create.tzid.value.length !== 0) { + if (create.tzid?.value) { prop = doc.createElementNS(XML_CALDAV_NS, "C:calendar-timezone-id"); prop.appendChild(doc.createTextNode(create.tzid.value)); props.appendChild(prop); } var compset = null; - for (var i = 0; i < create.comp.length; i++) { + for (let i = 0; calendar && i < create.comp.length; i++) { if (create.comp[i].checked) { var comp = doc.createElementNS(XML_CALDAV_NS, "C:comp"); comp.setAttribute("name", create.comp[i].value); @@ -125,18 +149,16 @@ function createCalendar(baseurl) { // Send MKCOL request (minimal response) var req = new XMLHttpRequest(); - req.open('MKCOL', url, false); + req.open('MKCOL', url); req.setRequestHeader('X-Client', X_CLIENT); req.setRequestHeader('Prefer', 'return=minimal'); + req.addEventListener('load', () => document.location.reload()); req.send(doc); - - // Refresh calendar list - document.location.replace(baseurl); } -// [Un]share a calendar collection ([un]readable by 'anyone') -function shareCalendar(url, share) { +// [Un]share a calendar/addressbook collection ([un]readable by 'anyone') +function share(i, share) { // Build DAV sharing document var doc = document.implementation.createDocument(XML_DAV_NS, "D:share-resource", null); @@ -156,7 +178,7 @@ function shareCalendar(url, share) { // Send POST request var req = new XMLHttpRequest(); - req.open('POST', url); + req.open('POST', n(i)); req.setRequestHeader('X-Client', X_CLIENT); req.setRequestHeader('Content-Type', 'application/davsharing+xml'); req.send(doc); @@ -164,43 +186,25 @@ function shareCalendar(url, share) { // Make a calendar collection transparent/opaque -function transpCalendar(url, transp) { - // Build PROPPATCH document - var doc = document.implementation.createDocument(XML_DAV_NS, - "D:propertyupdate", null); - var propupdate = doc.documentElement; - var props = doc.createElementNS(XML_DAV_NS, "D:prop"); - var caltransp = doc.createElementNS(XML_CALDAV_NS, - "C:schedule-calendar-transp"); - props.appendChild(caltransp); - - var op; - if (transp) { - op = doc.createElementNS(XML_DAV_NS, "D:set"); - caltransp.appendChild(doc.createElementNS(XML_CALDAV_NS, - "C:transparent")); - } - else { - op = doc.createElementNS(XML_DAV_NS, "D:remove"); - } +function transpCalendar(i, transp) { + const pu = propupdate(transp, i); + const caltransp = pu[0].createElementNS(XML_CALDAV_NS, + "C:schedule-calendar-transp"); + pu[1].appendChild(caltransp); - op.appendChild(props); - propupdate.appendChild(op); + if (transp) + caltransp.appendChild(pu[0].createElementNS(XML_CALDAV_NS, + "C:transparent")); - // Send PROPPATCH request (minimal response) - var req = new XMLHttpRequest(); - req.open('PROPPATCH', url); - req.setRequestHeader('X-Client', X_CLIENT); - req.setRequestHeader('Prefer', 'return=minimal'); - req.send(doc); + pu[2].submit(); } // Adjust supported components on a calendar collection -function compsetCalendar(url, id, comps) { +function compsetCalendar(id, comps) { if (!window.confirm('Are you sure you want to change' + ' component types on calendar \"' + - document.getElementById(id).innerText + '\"?')) { + document.getElementById(id).children[0].innerText + '\"?')) { // Reset selected options for (var i = 0; i < comps.length; i++) { @@ -209,51 +213,35 @@ function compsetCalendar(url, id, comps) { return; } - // Build PROPPATCH document - var doc = document.implementation.createDocument(XML_DAV_NS, - "D:propertyupdate", null); - var propupdate = doc.documentElement; - var props = doc.createElementNS(XML_DAV_NS, "D:prop"); - var compset = doc.createElementNS(XML_CALDAV_NS, - "C:supported-calendar-component-set"); + const pu = propupdate(true, id); + const compset = pu[0].createElementNS(XML_CALDAV_NS, + "C:supported-calendar-component-set"); compset.setAttribute("force", "yes"); - props.appendChild(compset); + pu[1].appendChild(compset); - var op = doc.createElementNS(XML_DAV_NS, "D:set"); + const op = pu[0].createElementNS(XML_DAV_NS, "D:set"); for (var i = 0; i < comps.length; i++) { if (comps[i].selected) { - var comp = doc.createElementNS(XML_CALDAV_NS, "C:comp"); + const comp = pu[0].createElementNS(XML_CALDAV_NS, "C:comp"); comp.setAttribute("name", comps[i].value); compset.appendChild(comp); } comps[i].defaultSelected = comps[i].selected; } - op.appendChild(props); - propupdate.appendChild(op); - - // Send PROPPATCH request (minimal response) - var req = new XMLHttpRequest(); - req.open('PROPPATCH', url); - req.setRequestHeader('X-Client', X_CLIENT); - req.setRequestHeader('Prefer', 'return=minimal'); - req.send(doc); + pu[2].submit(); } -// Delete a calendar collection -function deleteCalendar(url, id) { - if (window.confirm('Are you sure you want to delete calendar \"' + - document.getElementById(id).innerText + '\"?')) { +function deleteCollection(i) { + if (window.confirm('Are you sure you want to delete ' + (calendar ? 'calendar \"' : 'addressbook \"') + + document.getElementById(i).children[0].innerText + '\"?')) { // Send DELETE request var req = new XMLHttpRequest(); - req.open('DELETE', url, false); + req.open('DELETE', n(i)); req.setRequestHeader('X-Client', X_CLIENT); + req.addEventListener('load', () => document.location.reload()); req.send(null); - - // Refresh calendar list - var baseurl = url.substring(0, url.lastIndexOf("/") + 1); - document.location.replace(baseurl); } } diff --git a/imap/http_carddav.c b/imap/http_carddav.c index e33783dd2a..a1583b427f 100644 --- a/imap/http_carddav.c +++ b/imap/http_carddav.c @@ -1146,7 +1146,7 @@ static int list_addressbooks(struct transaction_t *txn) struct list_addr_rock lrock; const char *proto = NULL; const char *host = NULL; -#include "imap/http_carddav_js.h" +#include "imap/http_caldav_js.h" /* stat() mailboxes.db for Last-Modified and ETag */ snprintf(mboxlist, MAX_MAILBOX_PATH, "%s%s", config_dir, FNAME_MBOXLIST); @@ -1201,8 +1201,8 @@ static int list_addressbooks(struct transaction_t *txn) buf_printf_markup(body, level, "%s", "Available Addressbooks"); buf_printf_markup(body, level++, ""); buf_printf_markup(body, level++, "