]> Pileus Git - lackey/blob - cals/ews.c
Add event parsing for EWS calendars
[lackey] / cals / ews.c
1 /*
2  * Copyright (C) 2016 Andy Spencer <andy753421@gmail.com>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #define _GNU_SOURCE
19 #include <stdio.h>
20 #include <time.h>
21
22 #include <curl/curl.h>
23 #include <expat.h>
24
25 #include "util.h"
26 #include "conf.h"
27 #include "date.h"
28 #include "cal.h"
29
30 /* Local Types */
31 typedef struct ews_t {
32         cal_t         cal;
33         struct ews_t *next;
34
35         // Config
36         char         *username;
37         char         *password;
38         char         *location;
39         char         *domain;
40
41         // Parsing
42         CURL         *curl;
43         XML_Parser    expat;
44         buf_t         buf;
45         event_t      *first_event;
46         event_t      *last_event;
47         event_t      *event;
48         char        **next_text;
49         date_t       *next_date;
50
51         // Debugging
52         int           debug;
53         int           indent;
54 } ews_t;
55
56 /* Static data */
57 static ews_t *calendars;
58 static CURLM *curlm;
59
60 /* SOAP Requests */
61 static char *req_calendar =
62         "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
63         "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
64         "               xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"
65         "               xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"\n"
66         "               xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\"\n"
67         "               xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\">\n"
68         "    <soap:Body>\n"
69         "        <m:FindItem Traversal=\"Shallow\">\n"
70         "            <m:ItemShape>\n"
71         "                <t:BaseShape>IdOnly</t:BaseShape>\n"
72         "                <t:AdditionalProperties>\n"
73         "                    <t:FieldURI FieldURI=\"calendar:CalendarItemType\" />\n"
74         "                    <t:FieldURI FieldURI=\"calendar:Start\" />\n"
75         "                    <t:FieldURI FieldURI=\"calendar:End\" />\n"
76         "                    <t:FieldURI FieldURI=\"item:ItemClass\" />\n"
77         "                    <t:FieldURI FieldURI=\"item:Subject\" />\n"
78         "                </t:AdditionalProperties>\n"
79         "            </m:ItemShape>\n"
80         "            <m:CalendarView StartDate=\"2016-01-01T00:00:00-00:00\"\n"
81         "                            EndDate=\"2018-01-01T00:00:00-00:00\"\n"
82         "                            MaxEntriesReturned=\"1000\" />\n"
83         "            <m:ParentFolderIds>\n"
84         "                <t:DistinguishedFolderId Id=\"calendar\" />\n"
85         "            </m:ParentFolderIds>\n"
86         "        </m:FindItem>\n"
87         "    </soap:Body>\n"
88         "</soap:Envelope>\n"
89 ;
90
91 /* Local functions */
92 static void on_start(void *_cal, const char *tag, const char **attrs)
93 {
94         ews_t *cal = _cal;
95
96         /* Debug print */
97         if (cal->debug) {
98                 printf("%*s%s\n",
99                         cal->indent*4, "", tag);
100                 for (int i = 0; attrs[i]; i += 2) {
101                         printf("%*s %s=\"%s\"\n",
102                                 cal->indent*4+6, "--",
103                                 attrs[i+0], attrs[i+1]);
104                         attrs+=2;
105                 }
106                 cal->indent++;
107         }
108
109         /* Parse items */
110         if (match(tag, "t:CalendarItem")) {
111                 cal->event = new0(event_t);
112                 cal->event->cal = &cal->cal;
113                 if (!cal->first_event) {
114                         cal->first_event = cal->event;
115                         cal->last_event = cal->event;
116                 } else {
117                         cal->event->prev = cal->last_event;
118                         cal->last_event->next = cal->event;
119                         cal->last_event = cal->event;
120                 }
121         } else if (cal->event) {
122                 if (match(tag, "t:Subject"))
123                         cal->next_text = &cal->event->name;
124                 if (match(tag, "t:Start"))
125                         cal->next_date = &cal->event->start;
126                 if (match(tag, "t:End"))
127                         cal->next_date = &cal->event->end;
128         }
129 }
130
131 static void on_end(void *_cal, const char *tag)
132 {
133         ews_t *cal = _cal;
134
135         /* Debug print */
136         if (cal->debug) {
137                 if (cal->buf.len)
138                         printf("%*s \"%s\"\n",
139                                 cal->indent*4+2, "--",
140                                 (char*)cal->buf.data);
141                 cal->indent--;
142         }
143
144         /* Assign strings */
145         if (cal->next_text && cal->buf.len) {
146                 *cal->next_text = strcopy(cal->buf.data);
147         }
148         if (cal->next_date && cal->buf.len) {
149                 int year, month, day, hour, min, sec;
150                 char zone;
151                 int cnt = sscanf(cal->buf.data, "%d-%d-%dT%d:%d:%d%c",
152                                 &year, &month, &day, &hour, &min, &sec, &zone);
153                 if (cnt != 7)
154                         error("Error parsing time: [%s]", cal->buf.data);
155                 if (zone != 'Z')
156                         error("Expected UTC timestamp: [%s]", cal->buf.data);
157                 struct tm tm = {
158                         .tm_year = year-1900,
159                         .tm_mon  = month-1,
160                         .tm_mday = day,
161                         .tm_hour = hour,
162                         .tm_min  = min,
163                         .tm_sec  = sec,
164                 };
165                 *cal->next_date = get_date(timegm(&tm));
166         }
167
168         /* Parse items */
169         if (match(tag, "t:CalendarItem"))
170                 cal->event = NULL;
171         cal->next_text = NULL;
172         cal->next_date = NULL;
173         cal->buf.len = 0;
174 }
175
176 static void on_data(void *_cal, const char *data, int len)
177 {
178         ews_t *cal = _cal;
179         if (cal->debug || cal->next_text || cal->next_date)
180                 append(&cal->buf, data, len);
181 }
182
183 static size_t on_write(void *buf, size_t size, size_t n, ews_t *cal)
184 {
185         int len = size * n;
186         XML_Parse(cal->expat, buf, len, 0);
187         return len;
188 }
189
190 static void sync_ews(ews_t *cal)
191 {
192         CURLcode err;
193         long status;
194         struct curl_slist *hdr = NULL;
195
196         /* Debug output */
197         debug("Loading EWS:");
198         debug("  type     = %s", cal->cal.type);
199         debug("  name     = %s", cal->cal.name);
200         debug("  desc     = %s", cal->cal.desc);
201         debug("  username = %s", cal->username);
202         debug("  password = %s", cal->password);
203         debug("  location = %s", cal->location);
204         debug("  domain   = %s", cal->domain);
205
206         /* Setup Expat */
207         if (!(cal->expat = XML_ParserCreate(NULL)))
208                 error("XML Parser Create");
209         XML_SetUserData(cal->expat, cal);
210         XML_SetStartElementHandler(cal->expat, on_start);
211         XML_SetEndElementHandler(cal->expat, on_end);
212         XML_SetCharacterDataHandler(cal->expat, on_data);
213
214         /* Setup HTTP request */
215         if (!(cal->curl = curl_easy_init()))
216                 error("Curl easy init failed");
217         if (curl_easy_setopt(cal->curl, CURLOPT_URL, cal->location))
218                 error("Curl easy setopt failed");
219         if (curl_easy_setopt(cal->curl, CURLOPT_WRITEFUNCTION, on_write))
220                 error("Curl easy set write function failed");
221         if (curl_easy_setopt(cal->curl, CURLOPT_WRITEDATA, cal))
222                 error("Curl easy set write data failed");
223
224         /* Skip SSL checks */
225         if (curl_easy_setopt(cal->curl, CURLOPT_SSL_VERIFYPEER, 0L))
226                 error("Curl easy set no verify peer failed");
227         if (curl_easy_setopt(cal->curl, CURLOPT_SSL_VERIFYHOST, 0L))
228                 error("Curl easy set no verify hosts failed");
229
230         /* Set login info */
231         if (curl_easy_setopt(cal->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY))
232                 error("Curl easy set http auth failed");
233         if (curl_easy_setopt(cal->curl, CURLOPT_USERNAME, cal->username))
234                 error("Curl easy set username failed");
235         if (curl_easy_setopt(cal->curl, CURLOPT_PASSWORD, cal->password))
236                 error("Curl easy set password failed");
237
238         /* Setup SOAP request */
239         hdr = curl_slist_append(hdr, "Accept: text/xml");
240         hdr = curl_slist_append(hdr, "Content-Type: text/xml");
241         if (curl_easy_setopt(cal->curl, CURLOPT_HTTPHEADER, hdr))
242                 error("Curl easy set http header failed");
243         if (curl_easy_setopt(cal->curl, CURLOPT_POST, 1L))
244                 error("Curl easy set post failed");
245         if (curl_easy_setopt(cal->curl, CURLOPT_POSTFIELDS, req_calendar))
246                 error("Curl easy set post failed");
247
248         /* Curl Easy */
249         if ((err = curl_easy_perform(cal->curl)))
250                 error("Curl easy perform failed: %s", curl_easy_strerror(err));
251         if ((err = curl_easy_getinfo(cal->curl, CURLINFO_RESPONSE_CODE, &status)))
252                 error("Curl easy get info failed: %s", curl_easy_strerror(err));
253
254         /* Output response */
255         if (cal->debug)
256                 printf("EWS -- HTTP Status %ld\n", status);
257         if (cal->debug && !cal->buf.data)
258                 printf("EWS -- No Response Data\n");
259
260         /* Cleanup */
261         curl_easy_cleanup(cal->curl);
262         XML_ParserFree(cal->expat);
263 }
264
265 /* Config parser */
266 void ews_config(const char *group, const char *name, const char *key, const char *value)
267 {
268         ews_t *cal = NULL, *last = NULL;
269
270         /* Make sure it's valid */
271         if (!match(group, "ews") || !name)
272                 return;
273
274         /* Find existing calendar */
275         for (cal = calendars; cal; last = cal, cal = cal->next)
276                 if (match(cal->cal.name, name))
277                         break;
278
279         /* Create new calendar */
280         if (!cal) {
281                 cal = new0(ews_t);
282                 cal->cal.type = "ews";
283                 cal->cal.name = get_name(name);
284                 if (last)
285                         last->next = cal;
286                 else
287                         calendars = cal;
288         }
289
290         /* Set calendar values */
291         if (match(key, "location"))
292                 cal->location = get_string(value);
293         else if (match(key, "username"))
294                 cal->username = get_string(value);
295         else if (match(key, "password"))
296                 cal->password = get_string(value);
297         else if (match(key, "domain"))
298                 cal->domain = get_string(value);
299 }
300
301 /* Cal functions */
302 cal_t *ews_cals(void)
303 {
304         for (ews_t *cal = calendars; cal; cal = cal->next) {
305                 sync_ews(cal);
306                 cal->cal.next = &cal->next->cal;
307         }
308
309         return &calendars->cal;
310 }
311
312 /* Event functions */
313 event_t *ews_events(date_t start, date_t end)
314 {
315         event_t *events = NULL;
316         for (ews_t *cal = calendars; cal; cal = cal->next)
317                 events = merge_events(events, cal->first_event);
318         for (event_t *e = events; e; e = e->next)
319                 debug("Event: %s", e->name);
320         return events;
321 }
322
323 /* Todo functions */
324 todo_t *ews_todos(date_t start, date_t end)
325 {
326         return NULL;
327 }
328
329 /* Test functions */
330 void ews_test(char *url, char *user, char *pass)
331 {
332         ews_t cal = {
333                 .location = url,
334                 .username = user && user[0] ? user : NULL,
335                 .password = pass && pass[0] ? pass : NULL,
336                 .debug    = 1,
337         };
338
339         /* Setup CURL */
340         printf("EWS -- test start\n");
341         if (curl_global_init(CURL_GLOBAL_DEFAULT))
342                 error("Curl global init failed");
343         if (!(curlm = curl_multi_init()))
344                 error("Curl multi init failed");
345
346         /* Sync the calendar */
347         sync_ews(&cal);
348
349         /* Cleanup */
350         curl_multi_cleanup(curlm);
351         curl_global_cleanup();
352         printf("EWS -- test end\n");
353 }