Bonjour,

Je cherche à créer des événements dans un calendrier géré sous Zimbra depuis une application écrite en C#.

J'utilise pour se faire le protocole CalDAV (RFC 4791).

Pour le moment, j'arrive sans problème à interroger le serveur pour trouver l'URI du calendrier de l'utilisateur courant, à lister les événements du calendrier pour un intervalle de dates données.

En revanche, lorsque je tente de créer un événement, ça marche plus : erreur 401.

Voici l'exemple de la RFC :

[fixed]
PUT /home/lisa/calendars/events/qwue23489.ics HTTP/1.1
If-None-Match: *
Host: cal.example.com
Content-Type: text/calendar
Content-Length: xxxx

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
BEGIN:VEVENT
UID:20010712T182145Z-123401@example.com
DTSTAMP:20060712T182145Z
DTSTART:20060714T170000Z
DTEND:20060715T040000Z
SUMMARY:Bastille Day Party
END:VEVENT
END:VCALENDAR
[/fixed]

Donc moi j'ai un peu adapté, en modifiant notamment l'URI du fichier ICS à créer, de façon à ce qu'il corresponde au même pattern que ceux déjà existants (que je récupère en chargement les événements).

[fixed]
PUT /dav/<user>/Calendar/test1.ics HTTP/1.1
If-None-Match: *
Host: <host>
Content-Type: text/calendar
Content-Length: <length>

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
BEGIN:VEVENT
UID:20010712T182145Z-123401@example.com
DTSTAMP:20140712T182145Z
DTSTART:20140714T170000Z
DTEND:20140715T040000Z
SUMMARY:Bastille Day Party
END:VEVENT
END:VCALENDAR
[/fixed]

Mais l'erreur subsiste.

Voici mon code :

Code csharp : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 
        public string CreateEvent(CalDAVEvent evt)
        {
            List<KeyValuePair<string, string>> headers = new List<KeyValuePair<string, string>>();
            headers.Add(new KeyValuePair<string, string>("If-None-Match", "*"));
            return SendQuery("PUT", evt.Serialize(), new Uri(string.Format("{0}test{1}.ics", CalendarAddress.ToString(), evt.ETAG)), headers, "text/calendar");
        }
 
        private string SendQuery(string method, string query, Uri address, List<KeyValuePair<string, string>> headers, string contenttype)
        {
            HttpWebRequest hr = HttpWebRequest.Create(address) as HttpWebRequest;
            hr.Credentials = Credentials;
            hr.Method = method;
            hr.Host = address.Host;
            hr.ContentType = contenttype;
            foreach (KeyValuePair<string, string> kvp in headers)
            {
                hr.Headers.Add(kvp.Key, kvp.Value);
            }
            byte[] bytes = Encoding.UTF8.GetBytes((string)query);
            hr.ContentLength = bytes.Length;
            Stream RequestStream = hr.GetRequestStream();
            RequestStream.Write(bytes, 0, bytes.Length);
            RequestStream.Close();
            HttpWebResponse Response = (HttpWebResponse)hr.GetResponse(); // Ça pète ici
            Stream ResponseStream = Response.GetResponseStream();
 
            byte[] buffer = new byte[4096];
            StringBuilder sb = new StringBuilder();
            int r = 0;
            while ((r = ResponseStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                sb.Append(Encoding.UTF8.GetString(buffer, 0, r));
            }
            ResponseStream.Close();
 
            return sb.ToString();
        }

Pourtant, quand j'appelle SendQuery() pour charger les événements, j'ai pas de souci :
Code csharp : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
        public List<CalDAVEvent> GetEventsFromLastModified(DateTime from)
        {
            List<CalDAVEvent> ret = new List<CalDAVEvent>();
 
            string range = string.Format("<C:prop-filter name=\"DTSTAMP\"><C:time-range start=\"{0}\" end=\"{1}\"/></C:prop-filter>", ISO8601DateTime.ToString(from), ISO8601DateTime.ToString(DateTime.Now.AddDays(1)));
            List<KeyValuePair<string, string>> headers = new List<KeyValuePair<string, string>>();
            headers.Add(new KeyValuePair<string, string>("Depth", "1"));
            string res = SendQuery("REPORT", string.Format("<?xml version=\"1.0\" encoding=\"utf-8\" ?><C:calendar-query xmlns:C=\"urn:ietf:params:xml:ns:caldav\"><D:prop xmlns:D=\"DAV:\"><D:getetag/><C:calendar-data/></D:prop><C:filter><C:comp-filter name=\"VCALENDAR\"><C:comp-filter name=\"VEVENT\">{0}</C:comp-filter></C:comp-filter></C:filter></C:calendar-query>", range), CalendarAddress, headers, "application/xml; charset=\"utf-8\"");
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(res);
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
            nsmgr.AddNamespace("D", "DAV:");
            nsmgr.AddNamespace("C", "urn:ietf:params:xml:ns:caldav");
            foreach (XmlNode el in doc.SelectNodes("/D:multistatus/D:response/D:propstat/D:prop", nsmgr))
            {
                ret.Add(new CalDAVEvent(el.SelectSingleNode("C:calendar-data", nsmgr).InnerText, el.SelectSingleNode("D:getetag", nsmgr).InnerText));
            }
            return ret;
        }

C'est donc bien le PUT ou l'adresse qui lui plaît pas.

J'ai raté un truc dans la RFC ? Notamment en ce qui concerne l'adresse... L'exemple d'ajout a une adresse complètement différente des autres exemple. Donc pas évident de trouver une logique de nommage.
Quand j'essaie de faire Calendar/events/test1.ics j'ai doit à un 404, signe que "events" n'existe pas, et que j'ai bel et bien pas le droit de créer test1.ics dans Calendar.

Si je vire le "If-Not-Match" en header, j'ai toujours l'erreur : d'après la RFC, dans ce cas, je devrais écraser un événement déjà existant sans erreur. Donc c'est pas à cause d'un événement déjà existant...

If the client intends to create a new
non-collection resource, such as a new VEVENT, the client SHOULD use
the HTTP request header "If-None-Match: *" on the PUT request. The
Request-URI on the PUT request MUST include the target collection,
where the resource is to be created, plus the name of the resource in
the last path segment. The "If-None-Match: *" request header ensures
that the client will not inadvertently overwrite an existing resource
if the last path segment turned out to already be used.
J'ai installé sur mon PC le client CalDav "eM Client" et lui il arrive très bien à créer des événements. C'est donc pas une configuration qui bloque sur Zimbra, mais visiblement une subtilité qui m'échappe...