New field: Occupation and some documentation.
[libgcal.git] / src / gcal.c
blob90aadebb2a7a833d982ef23beb692a75fc71e39e
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 * - think a way to securely store passwords
49 * - more utests
50 * - provide option to use another XML parser (maybe expat?)
53 #ifdef HAVE_CONFIG_H
54 #include "config.h"
55 #else
56 #define _GNU_SOURCE
57 #endif
59 #include <string.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <curl/curl.h>
64 #include "internal_gcal.h"
65 #include "gcal.h"
66 #include "gcal_parser.h"
67 #include "msvc_hacks.h"
69 #ifdef GCAL_DEBUG_CURL
70 #include "curl_debug_gcal.h"
71 #endif
73 static void reset_buffer(struct gcal_resource *ptr)
75 if (ptr->buffer)
76 free(ptr->buffer);
77 ptr->length = 256;
78 ptr->buffer = (char *) calloc(ptr->length, sizeof(char));
79 ptr->previous_length = 0;
82 struct gcal_resource *gcal_construct(gservice mode)
84 struct gcal_resource *ptr;
85 ptr = malloc(sizeof(struct gcal_resource));
86 if (!ptr)
87 goto exit;
89 ptr->has_xml = 0;
90 ptr->document = NULL;
91 ptr->user = NULL;
92 ptr->domain = NULL;
93 ptr->url = NULL;
94 ptr->auth = NULL;
95 ptr->buffer = NULL;
96 reset_buffer(ptr);
97 ptr->curl = curl_easy_init();
98 ptr->http_code = 0;
99 ptr->curl_msg = NULL;
100 ptr->http_code = 0;
101 ptr->internal_status = 0;
102 ptr->fout_log = NULL;
103 ptr->max_results = strdup(GCAL_UPPER);
104 ptr->timezone = NULL;
105 ptr->location = NULL;
106 ptr->deleted = HIDE;
107 ptr->store_xml_entry = 0;
109 if (!(ptr->buffer) || (!(ptr->curl)) || (!ptr->max_results)) {
110 if (ptr->max_results)
111 free(ptr->max_results);
112 gcal_destroy(ptr);
113 ptr = NULL;
114 goto exit;
117 /* Initializes to google calendar as default */
118 if (gcal_set_service(ptr, mode)) {
119 free(ptr);
120 ptr = NULL;
123 exit:
124 return ptr;
127 int gcal_set_service(struct gcal_resource *gcalobj, gservice mode)
129 int result = 0;
131 if (gcalobj) {
132 if (mode == GCALENDAR)
133 strcpy(gcalobj->service, "cl");
134 else if (mode == GCONTACT)
135 strcpy(gcalobj->service, "cp");
136 else
137 result = -1;
141 return result;
145 void clean_buffer(struct gcal_resource *gcal_obj)
147 if (gcal_obj) {
148 memset(gcal_obj->buffer, 0, gcal_obj->length);
149 gcal_obj->previous_length = 0;
153 void gcal_destroy(struct gcal_resource *gcal_obj)
155 if (!gcal_obj)
156 return;
158 if (gcal_obj->buffer)
159 free(gcal_obj->buffer);
160 if (gcal_obj->curl)
161 curl_easy_cleanup(gcal_obj->curl);
162 if (gcal_obj->auth)
163 free(gcal_obj->auth);
164 if (gcal_obj->url)
165 free(gcal_obj->url);
166 if (gcal_obj->user)
167 free(gcal_obj->user);
168 if (gcal_obj->document)
169 clean_dom_document(gcal_obj->document);
170 if (gcal_obj->curl_msg)
171 free(gcal_obj->curl_msg);
172 if (gcal_obj->fout_log)
173 fclose(gcal_obj->fout_log);
174 if (gcal_obj->max_results)
175 free(gcal_obj->max_results);
176 if (gcal_obj->timezone)
177 free(gcal_obj->timezone);
178 if (gcal_obj->location)
179 free(gcal_obj->location);
180 if (gcal_obj->domain)
181 free(gcal_obj->domain);
183 free(gcal_obj);
187 static size_t write_cb(void *ptr, size_t count, size_t chunk_size, void *data)
190 size_t size = count * chunk_size;
191 struct gcal_resource *gcal_ptr = (struct gcal_resource *)data;
192 int current_length = strlen(gcal_ptr->buffer);
193 char *ptr_tmp;
195 if (size > (gcal_ptr->length - current_length - 1)) {
196 gcal_ptr->length = current_length + size + 1;
197 /* TODO: is it save to continue reallocing more memory?
198 * what happens if the gcalendar list is *really* big?
199 * how big can it be? Maybe I should use another write
200 * callback
201 * when requesting the Atom feed (one that will treat the
202 * the stream as its being read and not store it in memory).
204 ptr_tmp = realloc(gcal_ptr->buffer, gcal_ptr->length);
206 if (!ptr_tmp) {
207 if (gcal_ptr->fout_log)
208 fprintf(gcal_ptr->fout_log,
209 "write_cb: Failed relloc!\n");
210 goto exit;
213 gcal_ptr->buffer = ptr_tmp;
216 strncat(gcal_ptr->buffer, (char *)ptr, size);
218 exit:
219 return size;
222 static int check_request_error(struct gcal_resource *gcalobj, int code,
223 int expected_answer)
225 int result = 0;
226 CURL *curl_ctx = gcalobj->curl;
228 curl_easy_getinfo(curl_ctx, CURLINFO_HTTP_CODE,
229 &(gcalobj->http_code));
230 if (code || (gcalobj->http_code != expected_answer)) {
232 if (gcalobj->curl_msg)
233 free(gcalobj->curl_msg);
235 gcalobj->curl_msg = strdup(curl_easy_strerror(code));
237 if (gcalobj->fout_log)
238 fprintf(gcalobj->fout_log, "%s\n%s%s\n%s%d\n",
239 "check_request_error: failed request.",
240 "Curl code: ", gcalobj->curl_msg,
241 "HTTP code: ", (int)gcalobj->http_code);
242 result = -1;
245 return result;
248 static int common_upload(struct gcal_resource *gcalobj,
249 char *header, char *header2, char *header3,
250 char *header4,
251 struct curl_slist **curl_headers,
252 const char *gdata_version)
254 int result = -1;
255 CURL *curl_ctx = gcalobj->curl;
256 struct curl_slist *response_headers = NULL;
258 #ifdef GCAL_DEBUG_CURL
259 struct data_curl_debug flag;
260 flag.trace_ascii = 1;
261 curl_easy_setopt(gcalobj->curl, CURLOPT_DEBUGFUNCTION,
262 curl_debug_gcal_trace);
263 curl_easy_setopt(gcalobj->curl, CURLOPT_DEBUGDATA, &flag);
264 curl_easy_setopt(gcalobj->curl, CURLOPT_VERBOSE, 1);
265 #endif
267 /* To support Google Data API 2.0 */
268 response_headers = curl_slist_append(response_headers,
269 gdata_version);
271 if (header)
272 response_headers = curl_slist_append(response_headers, header);
273 if (header2)
274 response_headers = curl_slist_append(response_headers, header2);
275 if (header3)
276 response_headers = curl_slist_append(response_headers, header3);
277 if (header4)
278 response_headers = curl_slist_append(response_headers, header4);
280 if (!response_headers)
281 return result;
283 *curl_headers = response_headers;
285 curl_easy_setopt(curl_ctx, CURLOPT_HTTPHEADER, response_headers);
286 curl_easy_setopt(curl_ctx, CURLOPT_WRITEFUNCTION, write_cb);
287 curl_easy_setopt(curl_ctx, CURLOPT_WRITEDATA, (void *)gcalobj);
289 return result = 0;
292 int http_post(struct gcal_resource *gcalobj, const char *url,
293 char *header, char *header2, char *header3,
294 char *header4,
295 char *post_data, unsigned int length,
296 const int expected_answer,
297 const char *gdata_version)
299 int result = -1;
300 CURLcode res;
301 struct curl_slist *response_headers = NULL;
302 CURL *curl_ctx;
303 if (!gcalobj)
304 goto exit;
306 curl_ctx = gcalobj->curl;
307 result = common_upload(gcalobj, header, header2, header3, header4,
308 &response_headers,
309 gdata_version);
310 if (result)
311 goto exit;
313 /* It seems deprecated, as long I set POSTFIELDS */
314 curl_easy_setopt(curl_ctx, CURLOPT_POST, 1);
315 curl_easy_setopt(curl_ctx, CURLOPT_URL, url);
316 if (post_data) {
317 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDS, post_data);
318 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE,
319 length);
321 else
322 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE, 0);
324 res = curl_easy_perform(curl_ctx);
325 result = check_request_error(gcalobj, res, expected_answer);
327 /* cleanup */
328 curl_slist_free_all(response_headers);
330 exit:
331 return result;
335 static int http_put(struct gcal_resource *gcalobj, const char *url,
336 char *header, char *header2, char *header3,
337 char *header4,
338 char *post_data, unsigned int length,
339 const int expected_answer,
340 const char *gdata_version)
342 int result = -1;
343 CURLcode res;
344 struct curl_slist *response_headers = NULL;
345 CURL *curl_ctx;
346 if (!gcalobj)
347 goto exit;
349 curl_ctx = gcalobj->curl;
350 result = common_upload(gcalobj, header, header2, header3, header4,
351 &response_headers,
352 gdata_version);
353 if (result)
354 goto exit;
356 curl_easy_setopt(curl_ctx, CURLOPT_URL, url);
357 /* Tells curl that I want to PUT */
358 curl_easy_setopt(gcalobj->curl, CURLOPT_CUSTOMREQUEST, "PUT");
360 if (post_data) {
361 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDS, post_data);
362 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE,
363 length);
365 else
366 curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE, 0);
370 res = curl_easy_perform(curl_ctx);
371 result = check_request_error(gcalobj, res, expected_answer);
373 /* cleanup */
374 curl_slist_free_all(response_headers);
376 /* Restores curl context to previous standard mode */
377 curl_easy_setopt(gcalobj->curl, CURLOPT_CUSTOMREQUEST, NULL);
379 exit:
380 return result;
384 int gcal_get_authentication(struct gcal_resource *gcalobj,
385 char *user, char *password)
388 int post_len = 0;
389 char *post = NULL;
390 int result = -1;
391 char *tmp = NULL;
392 char *buffer = NULL;
393 char *enc_user = NULL;
394 char *enc_password = NULL;
396 if (!gcalobj || !user || !password)
397 goto exit;
399 /* Must cleanup HTTP buffer between requests */
400 clean_buffer(gcalobj);
402 /* Properly encode user and password */
403 enc_user = curl_easy_escape(gcalobj->curl, user, strlen(user));
404 enc_password = curl_easy_escape(gcalobj->curl, password,
405 strlen(password));
406 if ((!enc_password) || (!enc_user))
407 goto cleanup;
409 post_len = strlen(enc_user) + strlen(enc_password) +
410 sizeof(ACCOUNT_TYPE) +
411 sizeof(EMAIL_FIELD) +
412 sizeof(PASSWD_FIELD) + sizeof(SERVICE_FIELD) +
413 strlen(gcalobj->service) + sizeof(CLIENT_SOURCE)
414 + 5; /* thanks to 4 '&' between fields + null character */
415 post = (char *) malloc(post_len);
416 if (!post)
417 goto cleanup;
419 snprintf(post, post_len - 1,
420 "%s&"
421 "%s%s&"
422 "%s%s&"
423 "%s%s&"
424 "%s",
425 ACCOUNT_TYPE,
426 EMAIL_FIELD, enc_user,
427 PASSWD_FIELD, enc_password,
428 SERVICE_FIELD, gcalobj->service,
429 CLIENT_SOURCE);
431 result = http_post(gcalobj, GCAL_URL,
432 "Content-Type: application/x-www-form-urlencoded",
433 NULL, NULL, NULL, post, strlen(post),
434 GCAL_DEFAULT_ANSWER,
435 "GData-Version: 2");
437 if ((tmp = strstr(user, "@"))) {
438 if (!(buffer = strdup(user)))
439 goto cleanup;
441 buffer[tmp - user] = '\0';
442 if (!(gcalobj->user = strdup(buffer)))
443 goto cleanup;
445 ++tmp;
446 if (!(gcalobj->domain = strdup(tmp)))
447 goto cleanup;
449 free(buffer);
450 } else {
451 gcalobj->user = strdup(user);
452 gcalobj->domain = strdup("gmail.com");
455 if (result)
456 goto cleanup;
458 /* gcalendar server returns a string like this:
459 * SID=value\n
460 * LSID=value\n
461 * Auth=value\n
462 * and we only need the authorization token to login later
463 * without the '\r\n' in the end of string.
464 * TODO: move this to a distinct function and write utests.
466 if (gcalobj->auth)
467 free(gcalobj->auth);
469 gcalobj->auth = strstr(gcalobj->buffer, HEADER_AUTH);
470 gcalobj->auth = strdup(gcalobj->auth + strlen(HEADER_AUTH));
471 if (!gcalobj->auth)
472 goto cleanup;
474 tmp = strstr(gcalobj->auth, "\n");
475 if (tmp)
476 *tmp = '\0';
478 result = 0;
480 cleanup:
481 if (enc_user)
482 curl_free(enc_user);
483 if (enc_password)
484 curl_free(enc_password);
485 if (post)
486 free(post);
488 exit:
489 return result;
493 int get_follow_redirection(struct gcal_resource *gcalobj, const char *url,
494 void *cb_download, const char *gdata_version)
496 struct curl_slist *response_headers = NULL;
497 int length = 0;
498 int result = -1;
499 char *tmp_buffer = NULL;
500 void *downloader = NULL;
502 if (cb_download == NULL)
503 downloader = write_cb;
504 else
505 downloader = cb_download;
507 /* Must cleanup HTTP buffer between requests */
508 clean_buffer(gcalobj);
510 if (!gcalobj->auth)
511 goto exit;
512 length = strlen(gcalobj->auth) + sizeof(HEADER_GET) + 1;
513 tmp_buffer = (char *) malloc(length);
514 if (!tmp_buffer)
515 goto exit;
516 snprintf(tmp_buffer, length - 1, "%s%s", HEADER_GET, gcalobj->auth);
518 /* To support Google Data API 2.0 */
519 response_headers = curl_slist_append(response_headers,
520 gdata_version);
522 response_headers = curl_slist_append(response_headers, tmp_buffer);
523 if (!response_headers)
524 return result;
526 curl_easy_setopt(gcalobj->curl, CURLOPT_HTTPGET, 1);
527 curl_easy_setopt(gcalobj->curl, CURLOPT_HTTPHEADER, response_headers);
528 curl_easy_setopt(gcalobj->curl, CURLOPT_URL, url);
529 curl_easy_setopt(gcalobj->curl, CURLOPT_WRITEFUNCTION, downloader);
530 curl_easy_setopt(gcalobj->curl, CURLOPT_WRITEDATA, (void *)gcalobj);
532 result = curl_easy_perform(gcalobj->curl);
534 if (!(strcmp(gcalobj->service, "cp"))) {
535 /* For contacts, there is *not* redirection. */
536 if (!(result = check_request_error(gcalobj, result,
537 GCAL_DEFAULT_ANSWER))) {
538 result = 0;
539 goto cleanup;
541 } else if (!(strcmp(gcalobj->service, "cl"))) {
542 /* For calendar, it *must* be redirection */
543 if (check_request_error(gcalobj, result,
544 GCAL_REDIRECT_ANSWER)) {
545 result = -1;
546 goto cleanup;
548 } else {
549 /* No valid service, just exit. */
550 result = -1;
551 goto cleanup;
554 /* It will extract and follow the first 'REF' link in the stream */
555 if (gcalobj->url) {
556 free(gcalobj->url);
557 gcalobj->url = NULL;
560 if (get_the_url(gcalobj->buffer, gcalobj->length, &gcalobj->url)) {
561 result = -1;
562 goto cleanup;
565 clean_buffer(gcalobj);
566 curl_easy_setopt(gcalobj->curl, CURLOPT_URL, gcalobj->url);
567 result = curl_easy_perform(gcalobj->curl);
568 if ((result = check_request_error(gcalobj, result,
569 GCAL_DEFAULT_ANSWER))) {
570 result = -1;
571 goto cleanup;
574 cleanup:
576 if (tmp_buffer)
577 free(tmp_buffer);
578 if (response_headers)
579 curl_slist_free_all(response_headers);
581 exit:
582 return result;
586 static char *mount_query_url(struct gcal_resource *gcalobj,
587 const char *parameters, ...)
589 va_list ap;
590 char *result = NULL, *query_param = NULL, *ptr_tmp = NULL;
591 int length;
592 char query_separator[] = "&";
593 char query_init[] = "?";
594 /* By default, google contacts are not ordered */
595 char contact_order[] = "&orderby=lastmodified";
596 if (!gcalobj)
597 goto exit;
599 if ((!gcalobj->user))
600 goto exit;
602 /* TODO: put the google service type string in an array. */
603 if (!(strcmp(gcalobj->service, "cl"))) {
604 if (gcalobj->max_results)
605 length = sizeof(GCAL_EVENT_START) +
606 sizeof(GCAL_DELIMITER) +
607 strlen(gcalobj->domain) +
608 sizeof(GCAL_EVENT_END) +
609 sizeof(query_init) +
610 strlen(gcalobj->max_results) +
611 strlen(gcalobj->user) + 1;
612 else
613 length = sizeof(GCAL_EVENT_START) +
614 sizeof(GCAL_DELIMITER) +
615 strlen(gcalobj->domain) +
616 sizeof(GCAL_EVENT_END) +
617 sizeof(query_init) +
618 strlen(gcalobj->user) + 1;
621 else if (!(strcmp(gcalobj->service, "cp"))) {
622 if (gcalobj->max_results)
623 length = sizeof(GCONTACT_START) +
624 sizeof(GCAL_DELIMITER) +
625 strlen(gcalobj->domain) +
626 sizeof(GCONTACT_END) +
627 sizeof(query_init) +
628 strlen(gcalobj->max_results) +
629 strlen(gcalobj->user) +
630 sizeof(contact_order) + 1;
631 else
632 length = sizeof(GCONTACT_START) +
633 sizeof(GCAL_DELIMITER) +
634 strlen(gcalobj->domain) +
635 sizeof(GCONTACT_END) +
636 sizeof(query_init) +
637 strlen(gcalobj->user) + 1;
639 } else
640 goto exit;
642 result = (char *)malloc(length);
643 if (!result)
644 goto exit;
646 if (!(strcmp(gcalobj->service, "cl"))) {
647 /* This is a basic query URL: must have the google service
648 * address plus the number of max-results returned.
650 if (gcalobj->max_results)
651 snprintf(result, length - 1, "%s%s%s%s%s%s%s",
652 GCAL_EVENT_START, gcalobj->user,
653 GCAL_DELIMITER, gcalobj->domain,
654 GCAL_EVENT_END, query_init,
655 gcalobj->max_results);
656 else
657 snprintf(result, length - 1, "%s%s%s%s%s%s",
658 GCAL_EVENT_START, gcalobj->user,
659 GCAL_DELIMITER, gcalobj->domain,
660 GCAL_EVENT_END, query_init);
662 } else if (!(strcmp(gcalobj->service, "cp"))) {
663 if (gcalobj->max_results)
664 snprintf(result, length - 1, "%s%s%s%s%s%s%s%s",
665 GCONTACT_START, gcalobj->user,
666 GCAL_DELIMITER, gcalobj->domain,
667 GCONTACT_END, query_init,
668 gcalobj->max_results,
669 contact_order);
670 else
671 snprintf(result, length - 1, "%s%s%s%s%s%s",
672 GCONTACT_START, gcalobj->user,
673 GCAL_DELIMITER, gcalobj->domain,
674 GCONTACT_END, query_init);
677 /* For extra query parameters, add "&param_1&param_2&...&param_n" */
678 if (parameters) {
679 length += strlen(parameters) + sizeof(query_separator);
680 ptr_tmp = realloc(result, length);
681 if (!ptr_tmp)
682 goto cleanup;
683 result = ptr_tmp;
684 strncat(result, query_separator, sizeof(query_separator));
685 strncat(result, parameters, strlen(parameters));
687 va_start(ap, parameters);
688 while ((query_param = va_arg(ap, char *))) {
689 length += strlen(query_param) + sizeof(query_separator);
690 ptr_tmp = realloc(result, length);
691 if (!ptr_tmp)
692 goto cleanup;
693 result = ptr_tmp;
695 strncat(result, query_separator,
696 sizeof(query_separator));
697 strncat(result, query_param, strlen(query_param));
702 goto exit;
704 cleanup:
705 if (result)
706 free(result);
707 result = NULL;
709 exit:
710 va_end(ap);
711 return result;
714 int gcal_dump(struct gcal_resource *gcalobj, const char *gdata_version)
716 int result = -1;
717 char *buffer = NULL;
719 if (!gcalobj)
720 goto exit;
721 /* Failed to get authentication token */
722 if (!gcalobj->auth)
723 goto exit;
725 buffer = mount_query_url(gcalobj, NULL);
726 if (!buffer)
727 goto exit;
729 result = get_follow_redirection(gcalobj, buffer, NULL, gdata_version);
731 if (!result)
732 gcalobj->has_xml = 1;
734 free(buffer);
735 exit:
737 return result;
740 int gcal_calendar_list(struct gcal_resource *gcalobj)
742 int result;
743 result = get_follow_redirection(gcalobj, GCAL_LIST, NULL,
744 "GData-Version: 2");
745 /* TODO: parse the Atom feed */
747 return result;
750 int gcal_entry_number(struct gcal_resource *gcalobj)
752 int result = -1;
754 if (!gcalobj)
755 goto exit;
756 /* Failed to get authentication token */
757 if (!gcalobj->auth)
758 goto exit;
760 if (!gcalobj->buffer || !gcalobj->has_xml)
761 goto exit;
763 gcalobj->document = build_dom_document(gcalobj->buffer);
764 if (!gcalobj->document)
765 goto exit;
767 result = get_entries_number(gcalobj->document);
768 clean_dom_document(gcalobj->document);
769 gcalobj->document = NULL;
771 exit:
772 return result;
775 struct gcal_event *gcal_get_entries(struct gcal_resource *gcalobj,
776 size_t *length)
779 int result = -1, i;
780 struct gcal_event *ptr_res = NULL;
782 if (!gcalobj)
783 goto exit;
785 if (!gcalobj->buffer || !gcalobj->has_xml)
786 goto exit;
788 gcalobj->document = build_dom_document(gcalobj->buffer);
789 if (!gcalobj->document)
790 goto exit;
792 result = get_entries_number(gcalobj->document);
793 if (result == -1)
794 goto cleanup;
796 ptr_res = malloc(sizeof(struct gcal_event) * result);
797 if (!ptr_res)
798 goto cleanup;
799 memset(ptr_res, 0, sizeof(struct gcal_event) * result);
801 *length = result;
803 for (i = 0; i < result; ++i) {
804 gcal_init_event((ptr_res + i));
805 if (gcalobj->store_xml_entry)
806 (ptr_res + i)->common.store_xml = 1;
809 result = extract_all_entries(gcalobj->document, ptr_res, result);
810 if (result == -1) {
811 free(ptr_res);
812 ptr_res = NULL;
815 cleanup:
816 clean_dom_document(gcalobj->document);
817 gcalobj->document = NULL;
819 exit:
821 return ptr_res;
825 static void clean_string(char *ptr_str)
827 if (ptr_str)
828 free(ptr_str);
831 void gcal_init_event(struct gcal_event *entry)
833 if (!entry)
834 return;
836 entry->common.store_xml = 0;
837 entry->common.title = entry->common.id = NULL;
838 entry->common.edit_uri = entry->common.etag = NULL;
839 entry->common.xml = entry->common.updated = NULL;
840 entry->content = entry->dt_recurrent = entry->dt_start = NULL;
841 entry->dt_end = entry->where = entry->status = NULL;
846 void gcal_destroy_entry(struct gcal_event *entry)
848 if (!entry)
849 return;
851 clean_string(entry->common.title);
852 clean_string(entry->common.id);
853 clean_string(entry->common.edit_uri);
854 clean_string(entry->common.etag);
855 clean_string(entry->common.updated);
856 clean_string(entry->common.xml);
858 clean_string(entry->content);
859 clean_string(entry->dt_recurrent);
860 clean_string(entry->dt_start);
861 clean_string(entry->dt_end);
862 clean_string(entry->where);
863 clean_string(entry->status);
867 void gcal_destroy_entries(struct gcal_event *entries, size_t length)
869 size_t i = 0;
870 if (!entries)
871 return;
873 for (; i < length; ++i)
874 gcal_destroy_entry((entries + i));
876 free(entries);
879 /* This function makes possible to share code between 'add'
880 * and 'edit' events.
882 int up_entry(char *data2post, unsigned int m_length,
883 struct gcal_resource *gcalobj,
884 const char *url_server, char *etag,
885 HTTP_CMD up_mode, char *content_type,
886 int expected_code)
888 int result = -1;
889 int length = 0;
890 char *h_auth = NULL, *h_length = NULL, *tmp, *content;
891 const char header[] = "Content-length: ";
892 int (*up_callback)(struct gcal_resource *, const char *,
893 char *, char *, char *, char *,
894 char *, unsigned int, const int,
895 const char *);
897 if (!data2post || !gcalobj)
898 goto exit;
900 if (!gcalobj->auth)
901 goto exit;
903 if (up_mode == POST)
904 up_callback = http_post;
905 else if (up_mode == PUT)
906 up_callback = http_put;
907 else
908 goto exit;
910 /* Must cleanup HTTP buffer between requests */
911 clean_buffer(gcalobj);
913 /* Mounts content length and authentication header strings */
914 length = m_length + strlen(header) + 1;
915 h_length = (char *) malloc(length) ;
916 if (!h_length)
917 goto exit;
918 strncpy(h_length, header, sizeof(header));
919 tmp = h_length + sizeof(header) - 1;
920 snprintf(tmp, length - (sizeof(header) + 1), "%d", m_length);
923 length = strlen(gcalobj->auth) + sizeof(HEADER_GET) + 1;
924 h_auth = (char *) malloc(length);
925 if (!h_auth)
926 goto exit;
927 snprintf(h_auth, length - 1, "%s%s", HEADER_GET, gcalobj->auth);
930 if (!content_type)
931 content = "Content-Type: application/atom+xml";
932 else
933 content = content_type;
935 /* Post the data */
936 if (!(strcmp(gcalobj->service, "cp"))) {
937 /* For contacts, there is *not* redirection. */
938 result = up_callback(gcalobj, url_server,
939 content,
940 h_length,
941 h_auth,
942 etag,
943 data2post, m_length,
944 expected_code,
945 "GData-Version: 3.0");
946 if (!result) {
948 result = 0;
949 goto cleanup;
951 } else if (!(strcmp(gcalobj->service, "cl"))) {
952 /* For calendar, it *must* be redirection */
953 result = up_callback(gcalobj, url_server,
954 content,
955 h_length,
956 h_auth,
957 etag,
958 data2post, m_length,
959 GCAL_REDIRECT_ANSWER,
960 "GData-Version: 2");
961 if (result == -1) {
962 /* XXX: there is one report where google server
963 * doesn't always return redirection.
965 if (gcalobj->http_code == expected_code)
966 result = 0;
968 goto cleanup;
970 } else
971 goto cleanup;
974 if (gcalobj->url) {
975 free(gcalobj->url);
976 gcalobj->url = NULL;
979 if (get_the_url(gcalobj->buffer, gcalobj->length, &gcalobj->url))
980 goto cleanup;
982 clean_buffer(gcalobj);
984 /* Add gsessionid to post URL */
985 if (!(strcmp(gcalobj->service, "cp"))) {
986 result = up_callback(gcalobj, gcalobj->url,
987 "Content-Type: application/atom+xml",
988 h_length,
989 h_auth,
990 etag,
991 data2post, m_length,
992 expected_code,
993 "GData-Version: 3.0");
994 } else if (!(strcmp(gcalobj->service, "cl"))) {
995 result = up_callback(gcalobj, gcalobj->url,
996 "Content-Type: application/atom+xml",
997 h_length,
998 h_auth,
999 etag,
1000 data2post, m_length,
1001 expected_code,
1002 "GData-Version: 2");
1003 } else
1004 goto cleanup;
1006 if (result == -1) {
1007 if (gcalobj->fout_log) {
1008 fprintf(gcalobj->fout_log,
1009 "result = %s\n", gcalobj->buffer);
1010 fprintf(gcalobj->fout_log,
1011 "\nurl = %s\nh_length = %s\nh_auth = %s"
1012 "\ndata2post =%s%d\n",
1013 gcalobj->url, h_length, h_auth, data2post,
1014 m_length);
1016 goto cleanup;
1019 cleanup:
1021 if (h_length)
1022 free(h_length);
1023 if (h_auth)
1024 free(h_auth);
1026 exit:
1027 return result;
1030 int gcal_create_event(struct gcal_resource *gcalobj,
1031 struct gcal_event *entries,
1032 struct gcal_event *updated)
1034 int result = -1, length;
1035 char *xml_entry = NULL;
1037 if ((!entries) || (!gcalobj))
1038 return result;
1040 result = xmlentry_create(entries, &xml_entry, &length);
1041 if (result == -1)
1042 goto exit;
1044 result = up_entry(xml_entry, strlen(xml_entry),
1045 gcalobj, GCAL_EDIT_URL, NULL,
1046 POST, NULL, GCAL_EDIT_ANSWER);
1047 if (result)
1048 goto cleanup;
1050 /* Copy raw XML */
1051 if (gcalobj->store_xml_entry) {
1052 if (entries->common.xml)
1053 free(entries->common.xml);
1054 if (!(entries->common.xml = strdup(gcalobj->buffer)))
1055 goto cleanup;
1058 /* Parse buffer and create the new contact object */
1059 if (!updated)
1060 goto cleanup;
1061 result = -2;
1062 gcalobj->document = build_dom_document(gcalobj->buffer);
1063 if (!gcalobj->document)
1064 goto cleanup;
1066 /* There is only one 'entry' in the buffer */
1067 result = extract_all_entries(gcalobj->document, updated, 1);
1068 if (result == -1)
1069 goto xmlclean;
1071 result = 0;
1073 xmlclean:
1074 clean_dom_document(gcalobj->document);
1075 gcalobj->document = NULL;
1077 cleanup:
1078 if (xml_entry)
1079 free(xml_entry);
1081 exit:
1082 return result;
1085 int gcal_delete_event(struct gcal_resource *gcalobj,
1086 struct gcal_event *entry)
1088 int result = -1, length;
1089 char *h_auth;
1091 if ((!entry) || (!gcalobj) || (!gcalobj->auth))
1092 goto exit;
1094 /* Must cleanup HTTP buffer between requests */
1095 clean_buffer(gcalobj);
1097 length = strlen(gcalobj->auth) + sizeof(HEADER_GET) + 1;
1098 h_auth = (char *) malloc(length);
1099 if (!h_auth)
1100 goto exit;
1101 snprintf(h_auth, length - 1, "%s%s", HEADER_GET, gcalobj->auth);
1103 curl_easy_setopt(gcalobj->curl, CURLOPT_CUSTOMREQUEST, "DELETE");
1104 result = http_post(gcalobj, entry->common.edit_uri,
1105 "Content-Type: application/atom+xml",
1106 /* Google Data API 2.0 requires ETag */
1107 "If-Match: *",
1108 h_auth,
1109 NULL, NULL, 0, GCAL_REDIRECT_ANSWER,
1110 "GData-Version: 2");
1112 if (result == -1) {
1113 /* XXX: there is one report where google server
1114 * doesn't always return redirection and deletes
1115 * the entry right away!
1117 if (gcalobj->http_code == GCAL_DEFAULT_ANSWER)
1118 result = 0;
1120 goto cleanup;
1123 /* Get the gsessionid redirect URL */
1124 if (gcalobj->url) {
1125 free(gcalobj->url);
1126 gcalobj->url = NULL;
1128 if (get_the_url(gcalobj->buffer, gcalobj->length, &gcalobj->url))
1129 goto cleanup;
1131 result = http_post(gcalobj, gcalobj->url,
1132 "Content-Type: application/atom+xml",
1133 /* Google Data API 2.0 requires ETag */
1134 "If-Match: *",
1135 h_auth,
1136 NULL, NULL, 0, GCAL_DEFAULT_ANSWER,
1137 "GData-Version: 2");
1139 cleanup:
1140 /* Restores curl context to previous standard mode */
1141 curl_easy_setopt(gcalobj->curl, CURLOPT_CUSTOMREQUEST, NULL);
1143 if (h_auth)
1144 free(h_auth);
1146 exit:
1148 return result;
1152 int gcal_edit_event(struct gcal_resource *gcalobj,
1153 struct gcal_event *entry,
1154 struct gcal_event *updated)
1157 int result = -1, length;
1158 char *xml_entry = NULL;
1160 if ((!entry) || (!gcalobj))
1161 goto exit;
1163 result = xmlentry_create(entry, &xml_entry, &length);
1164 if (result == -1)
1165 goto exit;
1167 result = up_entry(xml_entry, strlen(xml_entry),
1168 gcalobj, entry->common.edit_uri,
1169 /* Google Data API 2.0 requires ETag */
1170 "If-Match: *",
1171 PUT, NULL, GCAL_DEFAULT_ANSWER);
1172 if (result)
1173 goto cleanup;
1175 /* Copy raw XML */
1176 if (gcalobj->store_xml_entry) {
1177 if (entry->common.xml)
1178 free(entry->common.xml);
1179 if (!(entry->common.xml = strdup(gcalobj->buffer)))
1180 goto cleanup;
1183 /* Parse buffer and create the new contact object */
1184 if (!updated)
1185 goto cleanup;
1186 result = -2;
1187 gcalobj->document = build_dom_document(gcalobj->buffer);
1188 if (!gcalobj->document)
1189 goto cleanup;
1191 /* There is only one 'entry' in the buffer */
1192 result = extract_all_entries(gcalobj->document, updated, 1);
1193 if (result == -1)
1194 goto xmlclean;
1196 result = 0;
1198 xmlclean:
1199 clean_dom_document(gcalobj->document);
1200 gcalobj->document = NULL;
1202 cleanup:
1203 if (xml_entry)
1204 free(xml_entry);
1206 exit:
1207 return result;
1210 char *gcal_access_buffer(struct gcal_resource *gcalobj)
1212 char *result = NULL;
1213 if (gcalobj)
1214 if (gcalobj->buffer)
1215 result = gcalobj->buffer;
1217 return result;
1222 int get_mili_timestamp(char *timestamp, size_t length, char *atimezone)
1224 struct tm *loctime;
1225 time_t curtime;
1226 struct timeval detail_time;
1227 char buffer[12];
1229 if (!timestamp || length < TIMESTAMP_SIZE)
1230 return -1;
1232 curtime = time(NULL);
1233 loctime = localtime(&curtime);
1234 gettimeofday(&detail_time, NULL);
1236 strftime(timestamp, length - 1, "%FT%T", loctime);
1237 snprintf(buffer, sizeof(buffer) - 1, ".%03d",
1238 (int)detail_time.tv_usec/1000);
1240 strncat(timestamp, buffer, length);
1241 if (atimezone)
1242 strncat(timestamp, atimezone, length);
1243 else
1244 strncat(timestamp, "Z", length);
1247 return 0;
1251 /* TODO: move most of this code to a generic 'query' function, since
1252 * quering for updated entries is just a query with a set of
1253 * parameters.
1255 int gcal_query_updated(struct gcal_resource *gcalobj, char *timestamp,
1256 const char *gdata_version)
1258 int result = -1;
1259 char *query_url = NULL;
1260 char *query_timestamp = NULL;
1261 char query_updated_param[] = "updated-min=";
1262 char query_zone_param[] = "ctz=";
1263 char *buffer1 = NULL, *buffer2 = NULL, *buffer3 = NULL;
1264 char *ptr, *hour_const = NULL;
1265 size_t length = 0;
1267 if (!gcalobj)
1268 goto exit;
1270 /* Failed to get authentication token */
1271 if (!gcalobj->auth)
1272 goto exit;
1274 length = TIMESTAMP_MAX_SIZE + sizeof(query_updated_param) + 1;
1275 buffer1 = (char *) malloc(length);
1276 if (!buffer1)
1277 goto exit;
1279 if (!timestamp) {
1280 query_timestamp = (char *)malloc(TIMESTAMP_MAX_SIZE);
1281 if (!query_timestamp)
1282 goto cleanup;
1283 result = get_mili_timestamp(query_timestamp, TIMESTAMP_MAX_SIZE,
1284 gcalobj->timezone);
1285 if (result)
1286 goto cleanup;
1288 result = -1;
1290 /* Change the hour to 06:00AM plus the timezone when
1291 * available.
1293 ptr = query_timestamp + strlen(query_timestamp);
1294 if (gcalobj->timezone) {
1295 hour_const = "06:00:00.000";
1296 ptr -= strlen(hour_const) + strlen(gcalobj->timezone);
1298 else {
1299 hour_const = "06:00:00.000Z";
1300 ptr -= strlen(hour_const);
1303 while (*hour_const)
1304 *ptr++ = *hour_const++;
1306 } else if (timestamp) {
1307 query_timestamp = strdup(timestamp);
1308 if (!query_timestamp)
1309 goto cleanup;
1312 strcpy(buffer1, query_updated_param);
1313 strncat(buffer1, query_timestamp, strlen(query_timestamp));
1315 /* 'showdeleted' is only valid for google contacts */
1316 if ((gcalobj->deleted == SHOW) &&
1317 (!(strcmp(gcalobj->service, "cp")))) {
1318 ptr = strdup("showdeleted=true");
1319 if (!ptr)
1320 goto cleanup;
1322 /* Set the query string to the available buffer parameter */
1323 if (!buffer2)
1324 buffer2 = ptr;
1325 else if (!buffer3)
1326 buffer3 = ptr;
1329 /* Add location to query (if set) */
1330 if (gcalobj->location) {
1331 length = strlen(gcalobj->location) +
1332 sizeof(query_zone_param) + 1;
1333 ptr = (char *) malloc(length);
1334 if (!ptr)
1335 goto cleanup;
1337 strcpy(ptr, query_zone_param);
1338 strcat(ptr, gcalobj->location);
1340 /* Set the query string to the available buffer parameter */
1341 if (!buffer2)
1342 buffer2 = ptr;
1343 else if (!buffer3)
1344 buffer3 = ptr;
1348 /* TODO: implement URL encoding i.e. RFC1738 using
1349 * 'curl_easy_escape'.
1351 query_url = mount_query_url(gcalobj, buffer1, buffer2, buffer3, NULL);
1352 if (!query_url)
1353 goto cleanup;
1355 result = get_follow_redirection(gcalobj, query_url, NULL, gdata_version);
1356 if (!result)
1357 gcalobj->has_xml = 1;
1359 cleanup:
1361 if (query_timestamp)
1362 free(query_timestamp);
1363 if (buffer1)
1364 free(buffer1);
1365 if (buffer2)
1366 free(buffer2);
1367 if (buffer3)
1368 free(buffer3);
1369 if (query_url)
1370 free(query_url);
1372 exit:
1373 return result;
1377 int gcal_set_timezone(struct gcal_resource *gcalobj, char *atimezone)
1379 int result = -1;
1380 if ((!gcalobj) || (!atimezone))
1381 goto exit;
1383 if (gcalobj->timezone)
1384 free(gcalobj->timezone);
1386 gcalobj->timezone = strdup(atimezone);
1387 if (gcalobj->timezone)
1388 result = 0;
1390 exit:
1391 return result;
1394 int gcal_set_location(struct gcal_resource *gcalobj, char *location)
1396 int result = -1;
1397 if ((!gcalobj) || (!location))
1398 goto exit;
1400 if (gcalobj->location)
1401 free(gcalobj->location);
1403 gcalobj->location = strdup(location);
1404 if (gcalobj->location)
1405 result = 0;
1407 exit:
1408 return result;
1412 void gcal_set_store_xml(struct gcal_resource *gcalobj, char flag)
1414 if ((!gcalobj))
1415 return;
1417 gcalobj->store_xml_entry = flag;
1420 void gcal_set_proxy(struct gcal_resource *gcalobj, char *proxy)
1422 if ((!gcalobj) || (!proxy)) {
1423 if (gcalobj->fout_log)
1424 fprintf(gcalobj->fout_log, "Invalid proxy!\n");
1425 return;
1426 } else
1427 if (gcalobj->fout_log)
1428 fprintf(gcalobj->fout_log, "\n\nproxy: %s\n\n", proxy);
1430 curl_easy_setopt(gcalobj->curl, CURLOPT_PROXY, proxy);
1434 void gcal_deleted(struct gcal_resource *gcalobj, display_deleted_entries opt)
1436 if (!gcalobj)
1437 return;
1439 if (opt == SHOW)
1440 gcalobj->deleted = SHOW;
1441 else if (opt == HIDE)
1442 gcalobj->deleted = HIDE;
1443 else if (gcalobj->fout_log)
1444 fprintf(gcalobj->fout_log, "gcal_deleted: invalid option:%d\n",
1445 opt);
1449 int gcal_query(struct gcal_resource *gcalobj, const char *parameters,
1450 const char *gdata_version)
1452 char *query_url = NULL, *ptr_tmp;
1453 int result = -1;
1455 if ((!gcalobj) || (!parameters))
1456 goto exit;
1458 /* Swaps the max-results internal member for NULL. This makes
1459 * possible a generic query with user defined max-results.
1461 ptr_tmp = gcalobj->max_results;
1462 gcalobj->max_results = NULL;
1463 query_url = mount_query_url(gcalobj, parameters, NULL);
1464 gcalobj->max_results = ptr_tmp;
1465 if (!query_url)
1466 goto exit;
1468 result = get_follow_redirection(gcalobj, query_url, NULL,
1469 gdata_version);
1471 if (!result)
1472 gcalobj->has_xml = 1;
1474 if (query_url)
1475 free(query_url);
1476 exit:
1478 return result;
1481 char *gcal_get_id(struct gcal_entry *entry)
1483 if (entry)
1484 return entry->id;
1486 return NULL;
1489 char *gcal_get_xml(struct gcal_entry *entry)
1491 if (entry)
1492 return entry->xml;
1494 return NULL;
1497 char gcal_get_deleted(struct gcal_entry *entry)
1500 if (entry)
1501 return entry->deleted;
1503 return -1;
1506 char *gcal_get_updated(struct gcal_entry *entry)
1508 if (entry)
1509 return entry->updated;
1511 return NULL;
1515 char *gcal_get_title(struct gcal_entry *entry)
1517 if (entry)
1518 return entry->title;
1520 return NULL;
1524 char *gcal_get_url(struct gcal_entry *entry)
1526 if (entry)
1527 return entry->edit_uri;
1529 return NULL;
1532 char *gcal_get_etag(struct gcal_entry *entry)
1534 if (entry)
1535 return entry->etag;
1537 return NULL;