Support for multiple structured postal addresses, structured name, nickname.
[libgcal.git] / src / gcal.c
blobeab4ea37895a6260edd92d4171dc567297ee913d
1 /*
2 Copyright (c) 2008 Instituto Nokia de Tecnologia
3 All rights reserved.
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
8 * Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13 * Neither the name of the INdT nor the names of its contributors
14 may be used to endorse or promote products derived from this software
15 without specific prior written permission.
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 POSSIBILITY OF SUCH DAMAGE.
29 /**
30 * @file gcal.c
31 * @author Adenilson Cavalcanti da Silva <adenilson.silva@indt.org.br>
32 * @date Mon Mar 3 12:32:11 2008
34 * @brief Base file for a gcalendar service access library.
36 * \todo:
37 * High priority
38 * - retrieve a single event
39 * - soft/hard edits/deletes (use 'If-Match: *' instead of ETag)
40 * - unit test for languages with special characters (it works already)
41 * - batch operation (add/edit/delete): will require some new public functions
43 * Lower priority
44 * - allow user to subscribe to another person calendar
45 * - enable user list and access available calendars
46 * - enable user create/edit/delete calendars
47 * - enable user to operate in another calendar than its default private one
48 * - support hosted accounts (e.g. foobar.com hosted in google)
49 * - think a way to securely store passwords
50 * - more utests
51 * - provide option to use another XML parser (maybe expat?)
54 #ifdef HAVE_CONFIG_H
55 #include "config.h"
56 #else
57 #define _GNU_SOURCE
58 #endif
60 #include <string.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <curl/curl.h>
65 #include "internal_gcal.h"
66 #include "gcal.h"
67 #include "gcal_parser.h"
68 #include "msvc_hacks.h"
70 #ifdef GCAL_DEBUG_CURL
71 #include "curl_debug_gcal.h"
72 #endif
74 static void reset_buffer(struct gcal_resource *ptr)
76 if (ptr->buffer)
77 free(ptr->buffer);
78 ptr->length = 256;
79 ptr->buffer = (char *) calloc(ptr->length, sizeof(char));
80 ptr->previous_length = 0;
83 struct gcal_resource *gcal_construct(gservice mode)
85 struct gcal_resource *ptr;
86 ptr = malloc(sizeof(struct gcal_resource));
87 if (!ptr)
88 goto exit;
90 ptr->has_xml = 0;
91 ptr->document = NULL;
92 ptr->user = NULL;
93 ptr->domain = NULL;
94 ptr->url = NULL;
95 ptr->auth = NULL;
96 ptr->buffer = NULL;
97 reset_buffer(ptr);
98 ptr->curl = curl_easy_init();
99 ptr->http_code = 0;
100 ptr->curl_msg = NULL;
101 ptr->http_code = 0;
102 ptr->internal_status = 0;
103 ptr->fout_log = NULL;
104 ptr->max_results = strdup(GCAL_UPPER);
105 ptr->timezone = NULL;
106 ptr->location = NULL;
107 ptr->deleted = HIDE;
108 ptr->store_xml_entry = 0;
110 if (!(ptr->buffer) || (!(ptr->curl)) || (!ptr->max_results)) {
111 if (ptr->max_results)
112 free(ptr->max_results);
113 gcal_destroy(ptr);
114 ptr = NULL;
115 goto exit;
118 /* Initializes to google calendar as default */
119 if (gcal_set_service(ptr, mode)) {
120 free(ptr);
121 ptr = NULL;
124 exit:
125 return ptr;
128 int gcal_set_service(struct gcal_resource *gcalobj, gservice mode)
130 int result = 0;
132 if (gcalobj) {
133 if (mode == GCALENDAR)
134 strcpy(gcalobj->service, "cl");
135 else if (mode == GCONTACT)
136 strcpy(gcalobj->service, "cp");
137 else
138 result = -1;
142 return result;
146 void clean_buffer(struct gcal_resource *gcal_obj)
148 if (gcal_obj) {
149 memset(gcal_obj->buffer, 0, gcal_obj->length);
150 gcal_obj->previous_length = 0;
154 void gcal_destroy(struct gcal_resource *gcal_obj)
156 if (!gcal_obj)
157 return;
159 if (gcal_obj->buffer)
160 free(gcal_obj->buffer);
161 if (gcal_obj->curl)
162 curl_easy_cleanup(gcal_obj->curl);
163 if (gcal_obj->auth)
164 free(gcal_obj->auth);
165 if (gcal_obj->url)
166 free(gcal_obj->url);
167 if (gcal_obj->user)
168 free(gcal_obj->user);
169 if (gcal_obj->document)
170 clean_dom_document(gcal_obj->document);
171 if (gcal_obj->curl_msg)
172 free(gcal_obj->curl_msg);
173 if (gcal_obj->fout_log)
174 fclose(gcal_obj->fout_log);
175 if (gcal_obj->max_results)
176 free(gcal_obj->max_results);
177 if (gcal_obj->timezone)
178 free(gcal_obj->timezone);
179 if (gcal_obj->location)
180 free(gcal_obj->location);
181 if (gcal_obj->domain)
182 free(gcal_obj->domain);
184 free(gcal_obj);
188 static size_t write_cb(void *ptr, size_t count, size_t chunk_size, void *data)
191 size_t size = count * chunk_size;
192 struct gcal_resource *gcal_ptr = (struct gcal_resource *)data;
193 int current_length = strlen(gcal_ptr->buffer);
194 char *ptr_tmp;
196 if (size > (gcal_ptr->length - current_length - 1)) {
197 gcal_ptr->length = current_length + size + 1;
198 /* TODO: is it save to continue reallocing more memory?
199 * what happens if the gcalendar list is *really* big?
200 * how big can it be? Maybe I should use another write
201 * callback
202 * when requesting the Atom feed (one that will treat the
203 * the stream as its being read and not store it in memory).
205 ptr_tmp = realloc(gcal_ptr->buffer, gcal_ptr->length);
207 if (!ptr_tmp) {
208 if (gcal_ptr->fout_log)
209 fprintf(gcal_ptr->fout_log,
210 "write_cb: Failed relloc!\n");
211 goto exit;
214 gcal_ptr->buffer = ptr_tmp;
217 strncat(gcal_ptr->buffer, (char *)ptr, size);
219 exit:
220 return size;
223 static int check_request_error(struct gcal_resource *gcalobj, int code,
224 int expected_answer)
226 int result = 0;
227 CURL *curl_ctx = gcalobj->curl;
229 curl_easy_getinfo(curl_ctx, CURLINFO_HTTP_CODE,
230 &(gcalobj->http_code));
231 if (code || (gcalobj->http_code != expected_answer)) {
233 if (gcalobj->curl_msg)
234 free(gcalobj->curl_msg);
236 gcalobj->curl_msg = strdup(curl_easy_strerror(code));
238 if (gcalobj->fout_log)
239 fprintf(gcalobj->fout_log, "%s\n%s%s\n%s%d\n",
240 "check_request_error: failed request.",
241 "Curl code: ", gcalobj->curl_msg,
242 "HTTP code: ", (int)gcalobj->http_code);
243 result = -1;
246 return result;
249 static int common_upload(struct gcal_resource *gcalobj,
250 char *header, char *header2, char *header3,
251 char *header4,
252 struct curl_slist **curl_headers,
253 const char *gdata_version)
255 int result = -1;
256 CURL *curl_ctx = gcalobj->curl;
257 struct curl_slist *response_headers = NULL;
259 #ifdef GCAL_DEBUG_CURL
260 struct data_curl_debug flag;
261 flag.trace_ascii = 1;
262 curl_easy_setopt(gcalobj->curl, CURLOPT_DEBUGFUNCTION,
263 curl_debug_gcal_trace);
264 curl_easy_setopt(gcalobj->curl, CURLOPT_DEBUGDATA, &flag);
265 curl_easy_setopt(gcalobj->curl, CURLOPT_VERBOSE, 1);
266 #endif
268 /* To support Google Data API 2.0 */
269 response_headers = curl_slist_append(response_headers,
270 gdata_version);
272 if (header)
273 response_headers = curl_slist_append(response_headers, header);
274 if (header2)
275 response_headers = curl_slist_append(response_headers, header2);
276 if (header3)
277 response_headers = curl_slist_append(response_headers, header3);
278 if (header4)
279 response_headers = curl_slist_append(response_headers, header4);
281 if (!response_headers)
282 return result;
284 *curl_headers = response_headers;
286 curl_easy_setopt(curl_ctx, CURLOPT_HTTPHEADER, response_headers);
287 curl_easy_setopt(curl_ctx, CURLOPT_WRITEFUNCTION, write_cb);
288 curl_easy_setopt(curl_ctx, CURLOPT_WRITEDATA, (void *)gcalobj);
290 return result = 0;
293 int http_post(struct gcal_resource *gcalobj, const char *url,
294 char *header, char *header2, char *header3,
295 char *header4,
296 char *post_data, unsigned int length,
297 const int expected_answer,
298 const char *gdata_version)
300 int result = -1;
301 CURLcode res;
302 struct curl_slist *response_headers = NULL;
303 CURL *curl_ctx;
304 if (!gcalobj)
305 goto exit;
307 curl_ctx = gcalobj->curl;
308 result = common_upload(gcalobj, header, header2, header3, header4,
309 &response_headers,
310 gdata_version);
311 if (result)
312 goto exit;
314 /* It seems deprecated, as long I set POSTFIELDS */
315 curl_easy_setopt(curl_ctx, CURLOPT_POST, 1);
316 curl_easy_setopt(curl_ctx, CURLOPT_URL, url);
317 if (post_data) {
318 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDS, post_data);
319 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE,
320 length);
322 else
323 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE, 0);
325 res = curl_easy_perform(curl_ctx);
326 result = check_request_error(gcalobj, res, expected_answer);
328 /* cleanup */
329 curl_slist_free_all(response_headers);
331 exit:
332 return result;
336 static int http_put(struct gcal_resource *gcalobj, const char *url,
337 char *header, char *header2, char *header3,
338 char *header4,
339 char *post_data, unsigned int length,
340 const int expected_answer,
341 const char *gdata_version)
343 int result = -1;
344 CURLcode res;
345 struct curl_slist *response_headers = NULL;
346 CURL *curl_ctx;
347 if (!gcalobj)
348 goto exit;
350 curl_ctx = gcalobj->curl;
351 result = common_upload(gcalobj, header, header2, header3, header4,
352 &response_headers,
353 gdata_version);
354 if (result)
355 goto exit;
357 curl_easy_setopt(curl_ctx, CURLOPT_URL, url);
358 /* Tells curl that I want to PUT */
359 curl_easy_setopt(gcalobj->curl, CURLOPT_CUSTOMREQUEST, "PUT");
361 if (post_data) {
362 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDS, post_data);
363 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE,
364 length);
366 else
367 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE, 0);
371 res = curl_easy_perform(curl_ctx);
372 result = check_request_error(gcalobj, res, expected_answer);
374 /* cleanup */
375 curl_slist_free_all(response_headers);
377 /* Restores curl context to previous standard mode */
378 curl_easy_setopt(gcalobj->curl, CURLOPT_CUSTOMREQUEST, NULL);
380 exit:
381 return result;
385 int gcal_get_authentication(struct gcal_resource *gcalobj,
386 char *user, char *password)
389 int post_len = 0;
390 char *post = NULL;
391 int result = -1;
392 char *tmp = NULL;
393 char *buffer = NULL;
394 char *enc_user = NULL;
395 char *enc_password = NULL;
397 if (!gcalobj || !user || !password)
398 goto exit;
400 /* Must cleanup HTTP buffer between requests */
401 clean_buffer(gcalobj);
403 /* Properly encode user and password */
404 enc_user = curl_easy_escape(gcalobj->curl, user, strlen(user));
405 enc_password = curl_easy_escape(gcalobj->curl, password,
406 strlen(password));
407 if ((!enc_password) || (!enc_user))
408 goto cleanup;
410 post_len = strlen(enc_user) + strlen(enc_password) +
411 sizeof(ACCOUNT_TYPE) +
412 sizeof(EMAIL_FIELD) +
413 sizeof(PASSWD_FIELD) + sizeof(SERVICE_FIELD) +
414 strlen(gcalobj->service) + sizeof(CLIENT_SOURCE)
415 + 5; /* thanks to 4 '&' between fields + null character */
416 post = (char *) malloc(post_len);
417 if (!post)
418 goto cleanup;
420 snprintf(post, post_len - 1,
421 "%s&"
422 "%s%s&"
423 "%s%s&"
424 "%s%s&"
425 "%s",
426 ACCOUNT_TYPE,
427 EMAIL_FIELD, enc_user,
428 PASSWD_FIELD, enc_password,
429 SERVICE_FIELD, gcalobj->service,
430 CLIENT_SOURCE);
432 result = http_post(gcalobj, GCAL_URL,
433 "Content-Type: application/x-www-form-urlencoded",
434 NULL, NULL, NULL, post, strlen(post),
435 GCAL_DEFAULT_ANSWER,
436 "GData-Version: 2");
438 if ((tmp = strstr(user, "@"))) {
439 if (!(buffer = strdup(user)))
440 goto cleanup;
442 buffer[tmp - user] = '\0';
443 if (!(gcalobj->user = strdup(buffer)))
444 goto cleanup;
446 ++tmp;
447 if (!(gcalobj->domain = strdup(tmp)))
448 goto cleanup;
450 free(buffer);
451 } else {
452 gcalobj->user = strdup(user);
453 gcalobj->domain = strdup("gmail.com");
456 if (result)
457 goto cleanup;
459 /* gcalendar server returns a string like this:
460 * SID=value\n
461 * LSID=value\n
462 * Auth=value\n
463 * and we only need the authorization token to login later
464 * without the '\r\n' in the end of string.
465 * TODO: move this to a distinct function and write utests.
467 if (gcalobj->auth)
468 free(gcalobj->auth);
470 gcalobj->auth = strstr(gcalobj->buffer, HEADER_AUTH);
471 gcalobj->auth = strdup(gcalobj->auth + strlen(HEADER_AUTH));
472 if (!gcalobj->auth)
473 goto cleanup;
475 tmp = strstr(gcalobj->auth, "\n");
476 if (tmp)
477 *tmp = '\0';
479 result = 0;
481 cleanup:
482 if (enc_user)
483 curl_free(enc_user);
484 if (enc_password)
485 curl_free(enc_password);
486 if (post)
487 free(post);
489 exit:
490 return result;
494 int get_follow_redirection(struct gcal_resource *gcalobj, const char *url,
495 void *cb_download, const char *gdata_version)
497 struct curl_slist *response_headers = NULL;
498 int length = 0;
499 int result = -1;
500 char *tmp_buffer = NULL;
501 void *downloader = NULL;
503 if (cb_download == NULL)
504 downloader = write_cb;
505 else
506 downloader = cb_download;
508 /* Must cleanup HTTP buffer between requests */
509 clean_buffer(gcalobj);
511 if (!gcalobj->auth)
512 goto exit;
513 length = strlen(gcalobj->auth) + sizeof(HEADER_GET) + 1;
514 tmp_buffer = (char *) malloc(length);
515 if (!tmp_buffer)
516 goto exit;
517 snprintf(tmp_buffer, length - 1, "%s%s", HEADER_GET, gcalobj->auth);
519 /* To support Google Data API 2.0 */
520 response_headers = curl_slist_append(response_headers,
521 gdata_version);
523 response_headers = curl_slist_append(response_headers, tmp_buffer);
524 if (!response_headers)
525 return result;
527 curl_easy_setopt(gcalobj->curl, CURLOPT_HTTPGET, 1);
528 curl_easy_setopt(gcalobj->curl, CURLOPT_HTTPHEADER, response_headers);
529 curl_easy_setopt(gcalobj->curl, CURLOPT_URL, url);
530 curl_easy_setopt(gcalobj->curl, CURLOPT_WRITEFUNCTION, downloader);
531 curl_easy_setopt(gcalobj->curl, CURLOPT_WRITEDATA, (void *)gcalobj);
533 result = curl_easy_perform(gcalobj->curl);
535 if (!(strcmp(gcalobj->service, "cp"))) {
536 /* For contacts, there is *not* redirection. */
537 if (!(result = check_request_error(gcalobj, result,
538 GCAL_DEFAULT_ANSWER))) {
539 result = 0;
540 goto cleanup;
542 } else if (!(strcmp(gcalobj->service, "cl"))) {
543 /* For calendar, it *must* be redirection */
544 if (check_request_error(gcalobj, result,
545 GCAL_REDIRECT_ANSWER)) {
546 result = -1;
547 goto cleanup;
549 } else {
550 /* No valid service, just exit. */
551 result = -1;
552 goto cleanup;
555 /* It will extract and follow the first 'REF' link in the stream */
556 if (gcalobj->url) {
557 free(gcalobj->url);
558 gcalobj->url = NULL;
561 if (get_the_url(gcalobj->buffer, gcalobj->length, &gcalobj->url)) {
562 result = -1;
563 goto cleanup;
566 clean_buffer(gcalobj);
567 curl_easy_setopt(gcalobj->curl, CURLOPT_URL, gcalobj->url);
568 result = curl_easy_perform(gcalobj->curl);
569 if ((result = check_request_error(gcalobj, result,
570 GCAL_DEFAULT_ANSWER))) {
571 result = -1;
572 goto cleanup;
575 cleanup:
577 if (tmp_buffer)
578 free(tmp_buffer);
579 if (response_headers)
580 curl_slist_free_all(response_headers);
582 exit:
583 return result;
587 static char *mount_query_url(struct gcal_resource *gcalobj,
588 const char *parameters, ...)
590 va_list ap;
591 char *result = NULL, *query_param = NULL, *ptr_tmp = NULL;
592 int length;
593 char query_separator[] = "&";
594 char query_init[] = "?";
595 /* By default, google contacts are not ordered */
596 char contact_order[] = "&orderby=lastmodified";
597 if (!gcalobj)
598 goto exit;
600 if ((!gcalobj->user))
601 goto exit;
603 /* TODO: put the google service type string in an array. */
604 if (!(strcmp(gcalobj->service, "cl"))) {
605 if (gcalobj->max_results)
606 length = sizeof(GCAL_EVENT_START) +
607 sizeof(GCAL_DELIMITER) +
608 strlen(gcalobj->domain) +
609 sizeof(GCAL_EVENT_END) +
610 sizeof(query_init) +
611 strlen(gcalobj->max_results) +
612 strlen(gcalobj->user) + 1;
613 else
614 length = sizeof(GCAL_EVENT_START) +
615 sizeof(GCAL_DELIMITER) +
616 strlen(gcalobj->domain) +
617 sizeof(GCAL_EVENT_END) +
618 sizeof(query_init) +
619 strlen(gcalobj->user) + 1;
622 else if (!(strcmp(gcalobj->service, "cp"))) {
623 if (gcalobj->max_results)
624 length = sizeof(GCONTACT_START) +
625 sizeof(GCAL_DELIMITER) +
626 strlen(gcalobj->domain) +
627 sizeof(GCONTACT_END) +
628 sizeof(query_init) +
629 strlen(gcalobj->max_results) +
630 strlen(gcalobj->user) +
631 sizeof(contact_order) + 1;
632 else
633 length = sizeof(GCONTACT_START) +
634 sizeof(GCAL_DELIMITER) +
635 strlen(gcalobj->domain) +
636 sizeof(GCONTACT_END) +
637 sizeof(query_init) +
638 strlen(gcalobj->user) + 1;
640 } else
641 goto exit;
643 result = (char *)malloc(length);
644 if (!result)
645 goto exit;
647 if (!(strcmp(gcalobj->service, "cl"))) {
648 /* This is a basic query URL: must have the google service
649 * address plus the number of max-results returned.
651 if (gcalobj->max_results)
652 snprintf(result, length - 1, "%s%s%s%s%s%s%s",
653 GCAL_EVENT_START, gcalobj->user,
654 GCAL_DELIMITER, gcalobj->domain,
655 GCAL_EVENT_END, query_init,
656 gcalobj->max_results);
657 else
658 snprintf(result, length - 1, "%s%s%s%s%s%s",
659 GCAL_EVENT_START, gcalobj->user,
660 GCAL_DELIMITER, gcalobj->domain,
661 GCAL_EVENT_END, query_init);
663 } else if (!(strcmp(gcalobj->service, "cp"))) {
664 if (gcalobj->max_results)
665 snprintf(result, length - 1, "%s%s%s%s%s%s%s%s",
666 GCONTACT_START, gcalobj->user,
667 GCAL_DELIMITER, gcalobj->domain,
668 GCONTACT_END, query_init,
669 gcalobj->max_results,
670 contact_order);
671 else
672 snprintf(result, length - 1, "%s%s%s%s%s%s",
673 GCONTACT_START, gcalobj->user,
674 GCAL_DELIMITER, gcalobj->domain,
675 GCONTACT_END, query_init);
678 /* For extra query parameters, add "&param_1&param_2&...&param_n" */
679 if (parameters) {
680 length += strlen(parameters) + sizeof(query_separator);
681 ptr_tmp = realloc(result, length);
682 if (!ptr_tmp)
683 goto cleanup;
684 result = ptr_tmp;
685 strncat(result, query_separator, sizeof(query_separator));
686 strncat(result, parameters, strlen(parameters));
688 va_start(ap, parameters);
689 while ((query_param = va_arg(ap, char *))) {
690 length += strlen(query_param) + sizeof(query_separator);
691 ptr_tmp = realloc(result, length);
692 if (!ptr_tmp)
693 goto cleanup;
694 result = ptr_tmp;
696 strncat(result, query_separator,
697 sizeof(query_separator));
698 strncat(result, query_param, strlen(query_param));
703 goto exit;
705 cleanup:
706 if (result)
707 free(result);
708 result = NULL;
710 exit:
711 va_end(ap);
712 return result;
715 int gcal_dump(struct gcal_resource *gcalobj, const char *gdata_version)
717 int result = -1;
718 char *buffer = NULL;
720 if (!gcalobj)
721 goto exit;
722 /* Failed to get authentication token */
723 if (!gcalobj->auth)
724 goto exit;
726 buffer = mount_query_url(gcalobj, NULL);
727 if (!buffer)
728 goto exit;
730 result = get_follow_redirection(gcalobj, buffer, NULL, gdata_version);
732 if (!result)
733 gcalobj->has_xml = 1;
735 free(buffer);
736 exit:
738 return result;
741 int gcal_calendar_list(struct gcal_resource *gcalobj)
743 int result;
744 result = get_follow_redirection(gcalobj, GCAL_LIST, NULL,
745 "GData-Version: 2");
746 /* TODO: parse the Atom feed */
748 return result;
751 int gcal_entry_number(struct gcal_resource *gcalobj)
753 int result = -1;
755 if (!gcalobj)
756 goto exit;
757 /* Failed to get authentication token */
758 if (!gcalobj->auth)
759 goto exit;
761 if (!gcalobj->buffer || !gcalobj->has_xml)
762 goto exit;
764 gcalobj->document = build_dom_document(gcalobj->buffer);
765 if (!gcalobj->document)
766 goto exit;
768 result = get_entries_number(gcalobj->document);
769 clean_dom_document(gcalobj->document);
770 gcalobj->document = NULL;
772 exit:
773 return result;
776 struct gcal_event *gcal_get_entries(struct gcal_resource *gcalobj,
777 size_t *length)
780 int result = -1, i;
781 struct gcal_event *ptr_res = NULL;
783 if (!gcalobj)
784 goto exit;
786 if (!gcalobj->buffer || !gcalobj->has_xml)
787 goto exit;
789 gcalobj->document = build_dom_document(gcalobj->buffer);
790 if (!gcalobj->document)
791 goto exit;
793 result = get_entries_number(gcalobj->document);
794 if (result == -1)
795 goto cleanup;
797 ptr_res = malloc(sizeof(struct gcal_event) * result);
798 if (!ptr_res)
799 goto cleanup;
800 memset(ptr_res, 0, sizeof(struct gcal_event) * result);
802 *length = result;
804 for (i = 0; i < result; ++i) {
805 gcal_init_event((ptr_res + i));
806 if (gcalobj->store_xml_entry)
807 (ptr_res + i)->common.store_xml = 1;
810 result = extract_all_entries(gcalobj->document, ptr_res, result);
811 if (result == -1) {
812 free(ptr_res);
813 ptr_res = NULL;
816 cleanup:
817 clean_dom_document(gcalobj->document);
818 gcalobj->document = NULL;
820 exit:
822 return ptr_res;
826 static void clean_string(char *ptr_str)
828 if (ptr_str)
829 free(ptr_str);
832 void gcal_init_event(struct gcal_event *entry)
834 if (!entry)
835 return;
837 entry->common.store_xml = 0;
838 entry->common.title = entry->common.id = NULL;
839 entry->common.edit_uri = entry->common.etag = NULL;
840 entry->common.xml = entry->common.updated = NULL;
841 entry->content = entry->dt_recurrent = entry->dt_start = NULL;
842 entry->dt_end = entry->where = entry->status = NULL;
847 void gcal_destroy_entry(struct gcal_event *entry)
849 if (!entry)
850 return;
852 clean_string(entry->common.title);
853 clean_string(entry->common.id);
854 clean_string(entry->common.edit_uri);
855 clean_string(entry->common.etag);
856 clean_string(entry->common.updated);
857 clean_string(entry->common.xml);
859 clean_string(entry->content);
860 clean_string(entry->dt_recurrent);
861 clean_string(entry->dt_start);
862 clean_string(entry->dt_end);
863 clean_string(entry->where);
864 clean_string(entry->status);
868 void gcal_destroy_entries(struct gcal_event *entries, size_t length)
870 size_t i = 0;
871 if (!entries)
872 return;
874 for (; i < length; ++i)
875 gcal_destroy_entry((entries + i));
877 free(entries);
880 /* This function makes possible to share code between 'add'
881 * and 'edit' events.
883 int up_entry(char *data2post, unsigned int m_length,
884 struct gcal_resource *gcalobj,
885 const char *url_server, char *etag,
886 HTTP_CMD up_mode, char *content_type,
887 int expected_code)
889 int result = -1;
890 int length = 0;
891 char *h_auth = NULL, *h_length = NULL, *tmp, *content;
892 const char header[] = "Content-length: ";
893 int (*up_callback)(struct gcal_resource *, const char *,
894 char *, char *, char *, char *,
895 char *, unsigned int, const int,
896 const char *);
898 if (!data2post || !gcalobj)
899 goto exit;
901 if (!gcalobj->auth)
902 goto exit;
904 if (up_mode == POST)
905 up_callback = http_post;
906 else if (up_mode == PUT)
907 up_callback = http_put;
908 else
909 goto exit;
911 /* Must cleanup HTTP buffer between requests */
912 clean_buffer(gcalobj);
914 /* Mounts content length and authentication header strings */
915 length = m_length + strlen(header) + 1;
916 h_length = (char *) malloc(length) ;
917 if (!h_length)
918 goto exit;
919 strncpy(h_length, header, sizeof(header));
920 tmp = h_length + sizeof(header) - 1;
921 snprintf(tmp, length - (sizeof(header) + 1), "%d", m_length);
924 length = strlen(gcalobj->auth) + sizeof(HEADER_GET) + 1;
925 h_auth = (char *) malloc(length);
926 if (!h_auth)
927 goto exit;
928 snprintf(h_auth, length - 1, "%s%s", HEADER_GET, gcalobj->auth);
931 if (!content_type)
932 content = "Content-Type: application/atom+xml";
933 else
934 content = content_type;
936 /* Post the data */
937 if (!(strcmp(gcalobj->service, "cp"))) {
938 /* For contacts, there is *not* redirection. */
939 result = up_callback(gcalobj, url_server,
940 content,
941 h_length,
942 h_auth,
943 etag,
944 data2post, m_length,
945 expected_code,
946 "GData-Version: 3.0");
947 if (!result) {
949 result = 0;
950 goto cleanup;
952 } else if (!(strcmp(gcalobj->service, "cl"))) {
953 /* For calendar, it *must* be redirection */
954 result = up_callback(gcalobj, url_server,
955 content,
956 h_length,
957 h_auth,
958 etag,
959 data2post, m_length,
960 GCAL_REDIRECT_ANSWER,
961 "GData-Version: 2");
962 if (result == -1) {
963 /* XXX: there is one report where google server
964 * doesn't always return redirection.
966 if (gcalobj->http_code == expected_code)
967 result = 0;
969 goto cleanup;
971 } else
972 goto cleanup;
975 if (gcalobj->url) {
976 free(gcalobj->url);
977 gcalobj->url = NULL;
980 if (get_the_url(gcalobj->buffer, gcalobj->length, &gcalobj->url))
981 goto cleanup;
983 clean_buffer(gcalobj);
985 /* Add gsessionid to post URL */
986 if (!(strcmp(gcalobj->service, "cp"))) {
987 result = up_callback(gcalobj, gcalobj->url,
988 "Content-Type: application/atom+xml",
989 h_length,
990 h_auth,
991 etag,
992 data2post, m_length,
993 expected_code,
994 "GData-Version: 3.0");
995 } else if (!(strcmp(gcalobj->service, "cl"))) {
996 result = up_callback(gcalobj, gcalobj->url,
997 "Content-Type: application/atom+xml",
998 h_length,
999 h_auth,
1000 etag,
1001 data2post, m_length,
1002 expected_code,
1003 "GData-Version: 2");
1004 } else
1005 goto cleanup;
1007 if (result == -1) {
1008 if (gcalobj->fout_log) {
1009 fprintf(gcalobj->fout_log,
1010 "result = %s\n", gcalobj->buffer);
1011 fprintf(gcalobj->fout_log,
1012 "\nurl = %s\nh_length = %s\nh_auth = %s"
1013 "\ndata2post =%s%d\n",
1014 gcalobj->url, h_length, h_auth, data2post,
1015 m_length);
1017 goto cleanup;
1020 cleanup:
1022 if (h_length)
1023 free(h_length);
1024 if (h_auth)
1025 free(h_auth);
1027 exit:
1028 return result;
1031 int gcal_create_event(struct gcal_resource *gcalobj,
1032 struct gcal_event *entries,
1033 struct gcal_event *updated)
1035 int result = -1, length;
1036 char *xml_entry = NULL;
1038 if ((!entries) || (!gcalobj))
1039 return result;
1041 result = xmlentry_create(entries, &xml_entry, &length);
1042 if (result == -1)
1043 goto exit;
1045 result = up_entry(xml_entry, strlen(xml_entry),
1046 gcalobj, GCAL_EDIT_URL, NULL,
1047 POST, NULL, GCAL_EDIT_ANSWER);
1048 if (result)
1049 goto cleanup;
1051 /* Copy raw XML */
1052 if (gcalobj->store_xml_entry) {
1053 if (entries->common.xml)
1054 free(entries->common.xml);
1055 if (!(entries->common.xml = strdup(gcalobj->buffer)))
1056 goto cleanup;
1059 /* Parse buffer and create the new contact object */
1060 if (!updated)
1061 goto cleanup;
1062 result = -2;
1063 gcalobj->document = build_dom_document(gcalobj->buffer);
1064 if (!gcalobj->document)
1065 goto cleanup;
1067 /* There is only one 'entry' in the buffer */
1068 result = extract_all_entries(gcalobj->document, updated, 1);
1069 if (result == -1)
1070 goto xmlclean;
1072 result = 0;
1074 xmlclean:
1075 clean_dom_document(gcalobj->document);
1076 gcalobj->document = NULL;
1078 cleanup:
1079 if (xml_entry)
1080 free(xml_entry);
1082 exit:
1083 return result;
1086 int gcal_delete_event(struct gcal_resource *gcalobj,
1087 struct gcal_event *entry)
1089 int result = -1, length;
1090 char *h_auth;
1092 if ((!entry) || (!gcalobj) || (!gcalobj->auth))
1093 goto exit;
1095 /* Must cleanup HTTP buffer between requests */
1096 clean_buffer(gcalobj);
1098 length = strlen(gcalobj->auth) + sizeof(HEADER_GET) + 1;
1099 h_auth = (char *) malloc(length);
1100 if (!h_auth)
1101 goto exit;
1102 snprintf(h_auth, length - 1, "%s%s", HEADER_GET, gcalobj->auth);
1104 curl_easy_setopt(gcalobj->curl, CURLOPT_CUSTOMREQUEST, "DELETE");
1105 result = http_post(gcalobj, entry->common.edit_uri,
1106 "Content-Type: application/atom+xml",
1107 /* Google Data API 2.0 requires ETag */
1108 "If-Match: *",
1109 h_auth,
1110 NULL, NULL, 0, GCAL_REDIRECT_ANSWER,
1111 "GData-Version: 2");
1113 if (result == -1) {
1114 /* XXX: there is one report where google server
1115 * doesn't always return redirection and deletes
1116 * the entry right away!
1118 if (gcalobj->http_code == GCAL_DEFAULT_ANSWER)
1119 result = 0;
1121 goto cleanup;
1124 /* Get the gsessionid redirect URL */
1125 if (gcalobj->url) {
1126 free(gcalobj->url);
1127 gcalobj->url = NULL;
1129 if (get_the_url(gcalobj->buffer, gcalobj->length, &gcalobj->url))
1130 goto cleanup;
1132 result = http_post(gcalobj, gcalobj->url,
1133 "Content-Type: application/atom+xml",
1134 /* Google Data API 2.0 requires ETag */
1135 "If-Match: *",
1136 h_auth,
1137 NULL, NULL, 0, GCAL_DEFAULT_ANSWER,
1138 "GData-Version: 2");
1140 cleanup:
1141 /* Restores curl context to previous standard mode */
1142 curl_easy_setopt(gcalobj->curl, CURLOPT_CUSTOMREQUEST, NULL);
1144 if (h_auth)
1145 free(h_auth);
1147 exit:
1149 return result;
1153 int gcal_edit_event(struct gcal_resource *gcalobj,
1154 struct gcal_event *entry,
1155 struct gcal_event *updated)
1158 int result = -1, length;
1159 char *xml_entry = NULL;
1161 if ((!entry) || (!gcalobj))
1162 goto exit;
1164 result = xmlentry_create(entry, &xml_entry, &length);
1165 if (result == -1)
1166 goto exit;
1168 result = up_entry(xml_entry, strlen(xml_entry),
1169 gcalobj, entry->common.edit_uri,
1170 /* Google Data API 2.0 requires ETag */
1171 "If-Match: *",
1172 PUT, NULL, GCAL_DEFAULT_ANSWER);
1173 if (result)
1174 goto cleanup;
1176 /* Copy raw XML */
1177 if (gcalobj->store_xml_entry) {
1178 if (entry->common.xml)
1179 free(entry->common.xml);
1180 if (!(entry->common.xml = strdup(gcalobj->buffer)))
1181 goto cleanup;
1184 /* Parse buffer and create the new contact object */
1185 if (!updated)
1186 goto cleanup;
1187 result = -2;
1188 gcalobj->document = build_dom_document(gcalobj->buffer);
1189 if (!gcalobj->document)
1190 goto cleanup;
1192 /* There is only one 'entry' in the buffer */
1193 result = extract_all_entries(gcalobj->document, updated, 1);
1194 if (result == -1)
1195 goto xmlclean;
1197 result = 0;
1199 xmlclean:
1200 clean_dom_document(gcalobj->document);
1201 gcalobj->document = NULL;
1203 cleanup:
1204 if (xml_entry)
1205 free(xml_entry);
1207 exit:
1208 return result;
1211 char *gcal_access_buffer(struct gcal_resource *gcalobj)
1213 char *result = NULL;
1214 if (gcalobj)
1215 if (gcalobj->buffer)
1216 result = gcalobj->buffer;
1218 return result;
1223 int get_mili_timestamp(char *timestamp, size_t length, char *atimezone)
1225 struct tm *loctime;
1226 time_t curtime;
1227 struct timeval detail_time;
1228 char buffer[12];
1230 if (!timestamp || length < TIMESTAMP_SIZE)
1231 return -1;
1233 curtime = time(NULL);
1234 loctime = localtime(&curtime);
1235 gettimeofday(&detail_time, NULL);
1237 strftime(timestamp, length - 1, "%FT%T", loctime);
1238 snprintf(buffer, sizeof(buffer) - 1, ".%03d",
1239 (int)detail_time.tv_usec/1000);
1241 strncat(timestamp, buffer, length);
1242 if (atimezone)
1243 strncat(timestamp, atimezone, length);
1244 else
1245 strncat(timestamp, "Z", length);
1248 return 0;
1252 /* TODO: move most of this code to a generic 'query' function, since
1253 * quering for updated entries is just a query with a set of
1254 * parameters.
1256 int gcal_query_updated(struct gcal_resource *gcalobj, char *timestamp,
1257 const char *gdata_version)
1259 int result = -1;
1260 char *query_url = NULL;
1261 char *query_timestamp = NULL;
1262 char query_updated_param[] = "updated-min=";
1263 char query_zone_param[] = "ctz=";
1264 char *buffer1 = NULL, *buffer2 = NULL, *buffer3 = NULL;
1265 char *ptr, *hour_const = NULL;
1266 size_t length = 0;
1268 if (!gcalobj)
1269 goto exit;
1271 /* Failed to get authentication token */
1272 if (!gcalobj->auth)
1273 goto exit;
1275 length = TIMESTAMP_MAX_SIZE + sizeof(query_updated_param) + 1;
1276 buffer1 = (char *) malloc(length);
1277 if (!buffer1)
1278 goto exit;
1280 if (!timestamp) {
1281 query_timestamp = (char *)malloc(TIMESTAMP_MAX_SIZE);
1282 if (!query_timestamp)
1283 goto cleanup;
1284 result = get_mili_timestamp(query_timestamp, TIMESTAMP_MAX_SIZE,
1285 gcalobj->timezone);
1286 if (result)
1287 goto cleanup;
1289 result = -1;
1291 /* Change the hour to 06:00AM plus the timezone when
1292 * available.
1294 ptr = query_timestamp + strlen(query_timestamp);
1295 if (gcalobj->timezone) {
1296 hour_const = "06:00:00.000";
1297 ptr -= strlen(hour_const) + strlen(gcalobj->timezone);
1299 else {
1300 hour_const = "06:00:00.000Z";
1301 ptr -= strlen(hour_const);
1304 while (*hour_const)
1305 *ptr++ = *hour_const++;
1307 } else if (timestamp) {
1308 query_timestamp = strdup(timestamp);
1309 if (!query_timestamp)
1310 goto cleanup;
1313 strcpy(buffer1, query_updated_param);
1314 strncat(buffer1, query_timestamp, strlen(query_timestamp));
1316 /* 'showdeleted' is only valid for google contacts */
1317 if ((gcalobj->deleted == SHOW) &&
1318 (!(strcmp(gcalobj->service, "cp")))) {
1319 ptr = strdup("showdeleted=true");
1320 if (!ptr)
1321 goto cleanup;
1323 /* Set the query string to the available buffer parameter */
1324 if (!buffer2)
1325 buffer2 = ptr;
1326 else if (!buffer3)
1327 buffer3 = ptr;
1330 /* Add location to query (if set) */
1331 if (gcalobj->location) {
1332 length = strlen(gcalobj->location) +
1333 sizeof(query_zone_param) + 1;
1334 ptr = (char *) malloc(length);
1335 if (!ptr)
1336 goto cleanup;
1338 strcpy(ptr, query_zone_param);
1339 strcat(ptr, gcalobj->location);
1341 /* Set the query string to the available buffer parameter */
1342 if (!buffer2)
1343 buffer2 = ptr;
1344 else if (!buffer3)
1345 buffer3 = ptr;
1349 /* TODO: implement URL encoding i.e. RFC1738 using
1350 * 'curl_easy_escape'.
1352 query_url = mount_query_url(gcalobj, buffer1, buffer2, buffer3, NULL);
1353 if (!query_url)
1354 goto cleanup;
1356 result = get_follow_redirection(gcalobj, query_url, NULL, gdata_version);
1357 if (!result)
1358 gcalobj->has_xml = 1;
1360 cleanup:
1362 if (query_timestamp)
1363 free(query_timestamp);
1364 if (buffer1)
1365 free(buffer1);
1366 if (buffer2)
1367 free(buffer2);
1368 if (buffer3)
1369 free(buffer3);
1370 if (query_url)
1371 free(query_url);
1373 exit:
1374 return result;
1378 int gcal_set_timezone(struct gcal_resource *gcalobj, char *atimezone)
1380 int result = -1;
1381 if ((!gcalobj) || (!atimezone))
1382 goto exit;
1384 if (gcalobj->timezone)
1385 free(gcalobj->timezone);
1387 gcalobj->timezone = strdup(atimezone);
1388 if (gcalobj->timezone)
1389 result = 0;
1391 exit:
1392 return result;
1395 int gcal_set_location(struct gcal_resource *gcalobj, char *location)
1397 int result = -1;
1398 if ((!gcalobj) || (!location))
1399 goto exit;
1401 if (gcalobj->location)
1402 free(gcalobj->location);
1404 gcalobj->location = strdup(location);
1405 if (gcalobj->location)
1406 result = 0;
1408 exit:
1409 return result;
1413 void gcal_set_store_xml(struct gcal_resource *gcalobj, char flag)
1415 if ((!gcalobj))
1416 return;
1418 gcalobj->store_xml_entry = flag;
1421 void gcal_set_proxy(struct gcal_resource *gcalobj, char *proxy)
1423 if ((!gcalobj) || (!proxy)) {
1424 if (gcalobj->fout_log)
1425 fprintf(gcalobj->fout_log, "Invalid proxy!\n");
1426 return;
1427 } else
1428 if (gcalobj->fout_log)
1429 fprintf(gcalobj->fout_log, "\n\nproxy: %s\n\n", proxy);
1431 curl_easy_setopt(gcalobj->curl, CURLOPT_PROXY, proxy);
1435 void gcal_deleted(struct gcal_resource *gcalobj, display_deleted_entries opt)
1437 if (!gcalobj)
1438 return;
1440 if (opt == SHOW)
1441 gcalobj->deleted = SHOW;
1442 else if (opt == HIDE)
1443 gcalobj->deleted = HIDE;
1444 else if (gcalobj->fout_log)
1445 fprintf(gcalobj->fout_log, "gcal_deleted: invalid option:%d\n",
1446 opt);
1450 int gcal_query(struct gcal_resource *gcalobj, const char *parameters,
1451 const char *gdata_version)
1453 char *query_url = NULL, *ptr_tmp;
1454 int result = -1;
1456 if ((!gcalobj) || (!parameters))
1457 goto exit;
1459 /* Swaps the max-results internal member for NULL. This makes
1460 * possible a generic query with user defined max-results.
1462 ptr_tmp = gcalobj->max_results;
1463 gcalobj->max_results = NULL;
1464 query_url = mount_query_url(gcalobj, parameters, NULL);
1465 gcalobj->max_results = ptr_tmp;
1466 if (!query_url)
1467 goto exit;
1469 result = get_follow_redirection(gcalobj, query_url, NULL,
1470 gdata_version);
1472 if (!result)
1473 gcalobj->has_xml = 1;
1475 if (query_url)
1476 free(query_url);
1477 exit:
1479 return result;
1482 char *gcal_get_id(struct gcal_entry *entry)
1484 if (entry)
1485 return entry->id;
1487 return NULL;
1490 char *gcal_get_xml(struct gcal_entry *entry)
1492 if (entry)
1493 return entry->xml;
1495 return NULL;
1498 char gcal_get_deleted(struct gcal_entry *entry)
1501 if (entry)
1502 return entry->deleted;
1504 return -1;
1507 char *gcal_get_updated(struct gcal_entry *entry)
1509 if (entry)
1510 return entry->updated;
1512 return NULL;
1516 char *gcal_get_title(struct gcal_entry *entry)
1518 if (entry)
1519 return entry->title;
1521 return NULL;
1525 char *gcal_get_url(struct gcal_entry *entry)
1527 if (entry)
1528 return entry->edit_uri;
1530 return NULL;
1533 char *gcal_get_etag(struct gcal_entry *entry)
1535 if (entry)
1536 return entry->etag;
1538 return NULL;