2 Copyright (c) 2008 Instituto Nokia de Tecnologia
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.
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.
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
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
51 * - provide option to use another XML parser (maybe expat?)
63 #include <curl/curl.h>
65 #include "internal_gcal.h"
67 #include "gcal_parser.h"
68 #include "msvc_hacks.h"
70 #ifdef GCAL_DEBUG_CURL
71 #include "curl_debug_gcal.h"
74 static void reset_buffer(struct gcal_resource
*ptr
)
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
));
98 ptr
->curl
= curl_easy_init();
100 ptr
->curl_msg
= NULL
;
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
;
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
);
118 /* Initializes to google calendar as default */
119 if (gcal_set_service(ptr
, mode
)) {
128 int gcal_set_service(struct gcal_resource
*gcalobj
, gservice mode
)
133 if (mode
== GCALENDAR
)
134 strcpy(gcalobj
->service
, "cl");
135 else if (mode
== GCONTACT
)
136 strcpy(gcalobj
->service
, "cp");
146 void clean_buffer(struct gcal_resource
*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
)
159 if (gcal_obj
->buffer
)
160 free(gcal_obj
->buffer
);
162 curl_easy_cleanup(gcal_obj
->curl
);
164 free(gcal_obj
->auth
);
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
);
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
);
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
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
);
208 if (gcal_ptr
->fout_log
)
209 fprintf(gcal_ptr
->fout_log
,
210 "write_cb: Failed relloc!\n");
214 gcal_ptr
->buffer
= ptr_tmp
;
217 strncat(gcal_ptr
->buffer
, (char *)ptr
, size
);
223 static int check_request_error(struct gcal_resource
*gcalobj
, int code
,
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
);
249 static int common_upload(struct gcal_resource
*gcalobj
,
250 char *header
, char *header2
, char *header3
,
252 struct curl_slist
**curl_headers
,
253 const char *gdata_version
)
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);
268 /* To support Google Data API 2.0 */
269 response_headers
= curl_slist_append(response_headers
,
273 response_headers
= curl_slist_append(response_headers
, header
);
275 response_headers
= curl_slist_append(response_headers
, header2
);
277 response_headers
= curl_slist_append(response_headers
, header3
);
279 response_headers
= curl_slist_append(response_headers
, header4
);
281 if (!response_headers
)
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
);
293 int http_post(struct gcal_resource
*gcalobj
, const char *url
,
294 char *header
, char *header2
, char *header3
,
296 char *post_data
, unsigned int length
,
297 const int expected_answer
,
298 const char *gdata_version
)
302 struct curl_slist
*response_headers
= NULL
;
307 curl_ctx
= gcalobj
->curl
;
308 result
= common_upload(gcalobj
, header
, header2
, header3
, header4
,
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
);
318 curl_easy_setopt(curl_ctx
, CURLOPT_POSTFIELDS
, post_data
);
319 curl_easy_setopt(curl_ctx
, CURLOPT_POSTFIELDSIZE
,
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
);
329 curl_slist_free_all(response_headers
);
336 static int http_put(struct gcal_resource
*gcalobj
, const char *url
,
337 char *header
, char *header2
, char *header3
,
339 char *post_data
, unsigned int length
,
340 const int expected_answer
,
341 const char *gdata_version
)
345 struct curl_slist
*response_headers
= NULL
;
350 curl_ctx
= gcalobj
->curl
;
351 result
= common_upload(gcalobj
, header
, header2
, header3
, header4
,
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");
362 curl_easy_setopt(curl_ctx
, CURLOPT_POSTFIELDS
, post_data
);
363 curl_easy_setopt(curl_ctx
, CURLOPT_POSTFIELDSIZE
,
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
);
375 curl_slist_free_all(response_headers
);
377 /* Restores curl context to previous standard mode */
378 curl_easy_setopt(gcalobj
->curl
, CURLOPT_CUSTOMREQUEST
, NULL
);
385 int gcal_get_authentication(struct gcal_resource
*gcalobj
,
386 char *user
, char *password
)
394 char *enc_user
= NULL
;
395 char *enc_password
= NULL
;
397 if (!gcalobj
|| !user
|| !password
)
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
,
407 if ((!enc_password
) || (!enc_user
))
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
);
420 snprintf(post
, post_len
- 1,
427 EMAIL_FIELD
, enc_user
,
428 PASSWD_FIELD
, enc_password
,
429 SERVICE_FIELD
, gcalobj
->service
,
432 result
= http_post(gcalobj
, GCAL_URL
,
433 "Content-Type: application/x-www-form-urlencoded",
434 NULL
, NULL
, NULL
, post
, strlen(post
),
438 if ((tmp
= strstr(user
, "@"))) {
439 if (!(buffer
= strdup(user
)))
442 buffer
[tmp
- user
] = '\0';
443 if (!(gcalobj
->user
= strdup(buffer
)))
447 if (!(gcalobj
->domain
= strdup(tmp
)))
452 gcalobj
->user
= strdup(user
);
453 gcalobj
->domain
= strdup("gmail.com");
459 /* gcalendar server returns a string like this:
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.
470 gcalobj
->auth
= strstr(gcalobj
->buffer
, HEADER_AUTH
);
471 gcalobj
->auth
= strdup(gcalobj
->auth
+ strlen(HEADER_AUTH
));
475 tmp
= strstr(gcalobj
->auth
, "\n");
485 curl_free(enc_password
);
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
;
500 char *tmp_buffer
= NULL
;
501 void *downloader
= NULL
;
503 if (cb_download
== NULL
)
504 downloader
= write_cb
;
506 downloader
= cb_download
;
508 /* Must cleanup HTTP buffer between requests */
509 clean_buffer(gcalobj
);
513 length
= strlen(gcalobj
->auth
) + sizeof(HEADER_GET
) + 1;
514 tmp_buffer
= (char *) malloc(length
);
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
,
523 response_headers
= curl_slist_append(response_headers
, tmp_buffer
);
524 if (!response_headers
)
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
))) {
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
)) {
550 /* No valid service, just exit. */
555 /* It will extract and follow the first 'REF' link in the stream */
561 if (get_the_url(gcalobj
->buffer
, gcalobj
->length
, &gcalobj
->url
)) {
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
))) {
579 if (response_headers
)
580 curl_slist_free_all(response_headers
);
587 static char *mount_query_url(struct gcal_resource
*gcalobj
,
588 const char *parameters
, ...)
591 char *result
= NULL
, *query_param
= NULL
, *ptr_tmp
= NULL
;
593 char query_separator
[] = "&";
594 char query_init
[] = "?";
595 /* By default, google contacts are not ordered */
596 char contact_order
[] = "&orderby=lastmodified";
600 if ((!gcalobj
->user
))
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
) +
611 strlen(gcalobj
->max_results
) +
612 strlen(gcalobj
->user
) + 1;
614 length
= sizeof(GCAL_EVENT_START
) +
615 sizeof(GCAL_DELIMITER
) +
616 strlen(gcalobj
->domain
) +
617 sizeof(GCAL_EVENT_END
) +
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
) +
629 strlen(gcalobj
->max_results
) +
630 strlen(gcalobj
->user
) +
631 sizeof(contact_order
) + 1;
633 length
= sizeof(GCONTACT_START
) +
634 sizeof(GCAL_DELIMITER
) +
635 strlen(gcalobj
->domain
) +
636 sizeof(GCONTACT_END
) +
638 strlen(gcalobj
->user
) + 1;
643 result
= (char *)malloc(length
);
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
);
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
,
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 "¶m_1¶m_2&...¶m_n" */
680 length
+= strlen(parameters
) + sizeof(query_separator
);
681 ptr_tmp
= realloc(result
, length
);
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
);
696 strncat(result
, query_separator
,
697 sizeof(query_separator
));
698 strncat(result
, query_param
, strlen(query_param
));
715 int gcal_dump(struct gcal_resource
*gcalobj
, const char *gdata_version
)
722 /* Failed to get authentication token */
726 buffer
= mount_query_url(gcalobj
, NULL
);
730 result
= get_follow_redirection(gcalobj
, buffer
, NULL
, gdata_version
);
733 gcalobj
->has_xml
= 1;
741 int gcal_calendar_list(struct gcal_resource
*gcalobj
)
744 result
= get_follow_redirection(gcalobj
, GCAL_LIST
, NULL
,
746 /* TODO: parse the Atom feed */
751 int gcal_entry_number(struct gcal_resource
*gcalobj
)
757 /* Failed to get authentication token */
761 if (!gcalobj
->buffer
|| !gcalobj
->has_xml
)
764 gcalobj
->document
= build_dom_document(gcalobj
->buffer
);
765 if (!gcalobj
->document
)
768 result
= get_entries_number(gcalobj
->document
);
769 clean_dom_document(gcalobj
->document
);
770 gcalobj
->document
= NULL
;
776 struct gcal_event
*gcal_get_entries(struct gcal_resource
*gcalobj
,
781 struct gcal_event
*ptr_res
= NULL
;
786 if (!gcalobj
->buffer
|| !gcalobj
->has_xml
)
789 gcalobj
->document
= build_dom_document(gcalobj
->buffer
);
790 if (!gcalobj
->document
)
793 result
= get_entries_number(gcalobj
->document
);
797 ptr_res
= malloc(sizeof(struct gcal_event
) * result
);
800 memset(ptr_res
, 0, sizeof(struct gcal_event
) * 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
);
817 clean_dom_document(gcalobj
->document
);
818 gcalobj
->document
= NULL
;
826 static void clean_string(char *ptr_str
)
832 void gcal_init_event(struct gcal_event
*entry
)
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
)
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
)
874 for (; i
< length
; ++i
)
875 gcal_destroy_entry((entries
+ i
));
880 /* This function makes possible to share code between 'add'
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
,
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,
898 if (!data2post
|| !gcalobj
)
905 up_callback
= http_post
;
906 else if (up_mode
== PUT
)
907 up_callback
= http_put
;
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
) ;
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
);
928 snprintf(h_auth
, length
- 1, "%s%s", HEADER_GET
, gcalobj
->auth
);
932 content
= "Content-Type: application/atom+xml";
934 content
= content_type
;
937 if (!(strcmp(gcalobj
->service
, "cp"))) {
938 /* For contacts, there is *not* redirection. */
939 result
= up_callback(gcalobj
, url_server
,
946 "GData-Version: 3.0");
952 } else if (!(strcmp(gcalobj
->service
, "cl"))) {
953 /* For calendar, it *must* be redirection */
954 result
= up_callback(gcalobj
, url_server
,
960 GCAL_REDIRECT_ANSWER
,
963 /* XXX: there is one report where google server
964 * doesn't always return redirection.
966 if (gcalobj
->http_code
== expected_code
)
980 if (get_the_url(gcalobj
->buffer
, gcalobj
->length
, &gcalobj
->url
))
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",
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",
1001 data2post
, m_length
,
1003 "GData-Version: 2");
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
,
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
))
1041 result
= xmlentry_create(entries
, &xml_entry
, &length
);
1045 result
= up_entry(xml_entry
, strlen(xml_entry
),
1046 gcalobj
, GCAL_EDIT_URL
, NULL
,
1047 POST
, NULL
, GCAL_EDIT_ANSWER
);
1052 if (gcalobj
->store_xml_entry
) {
1053 if (entries
->common
.xml
)
1054 free(entries
->common
.xml
);
1055 if (!(entries
->common
.xml
= strdup(gcalobj
->buffer
)))
1059 /* Parse buffer and create the new contact object */
1063 gcalobj
->document
= build_dom_document(gcalobj
->buffer
);
1064 if (!gcalobj
->document
)
1067 /* There is only one 'entry' in the buffer */
1068 result
= extract_all_entries(gcalobj
->document
, updated
, 1);
1075 clean_dom_document(gcalobj
->document
);
1076 gcalobj
->document
= NULL
;
1086 int gcal_delete_event(struct gcal_resource
*gcalobj
,
1087 struct gcal_event
*entry
)
1089 int result
= -1, length
;
1092 if ((!entry
) || (!gcalobj
) || (!gcalobj
->auth
))
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
);
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 */
1110 NULL
, NULL
, 0, GCAL_REDIRECT_ANSWER
,
1111 "GData-Version: 2");
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
)
1124 /* Get the gsessionid redirect URL */
1127 gcalobj
->url
= NULL
;
1129 if (get_the_url(gcalobj
->buffer
, gcalobj
->length
, &gcalobj
->url
))
1132 result
= http_post(gcalobj
, gcalobj
->url
,
1133 "Content-Type: application/atom+xml",
1134 /* Google Data API 2.0 requires ETag */
1137 NULL
, NULL
, 0, GCAL_DEFAULT_ANSWER
,
1138 "GData-Version: 2");
1141 /* Restores curl context to previous standard mode */
1142 curl_easy_setopt(gcalobj
->curl
, CURLOPT_CUSTOMREQUEST
, NULL
);
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
))
1164 result
= xmlentry_create(entry
, &xml_entry
, &length
);
1168 result
= up_entry(xml_entry
, strlen(xml_entry
),
1169 gcalobj
, entry
->common
.edit_uri
,
1170 /* Google Data API 2.0 requires ETag */
1172 PUT
, NULL
, GCAL_DEFAULT_ANSWER
);
1177 if (gcalobj
->store_xml_entry
) {
1178 if (entry
->common
.xml
)
1179 free(entry
->common
.xml
);
1180 if (!(entry
->common
.xml
= strdup(gcalobj
->buffer
)))
1184 /* Parse buffer and create the new contact object */
1188 gcalobj
->document
= build_dom_document(gcalobj
->buffer
);
1189 if (!gcalobj
->document
)
1192 /* There is only one 'entry' in the buffer */
1193 result
= extract_all_entries(gcalobj
->document
, updated
, 1);
1200 clean_dom_document(gcalobj
->document
);
1201 gcalobj
->document
= NULL
;
1211 char *gcal_access_buffer(struct gcal_resource
*gcalobj
)
1213 char *result
= NULL
;
1215 if (gcalobj
->buffer
)
1216 result
= gcalobj
->buffer
;
1223 int get_mili_timestamp(char *timestamp
, size_t length
, char *atimezone
)
1227 struct timeval detail_time
;
1230 if (!timestamp
|| length
< TIMESTAMP_SIZE
)
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
);
1243 strncat(timestamp
, atimezone
, length
);
1245 strncat(timestamp
, "Z", length
);
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
1256 int gcal_query_updated(struct gcal_resource
*gcalobj
, char *timestamp
,
1257 const char *gdata_version
)
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
;
1271 /* Failed to get authentication token */
1275 length
= TIMESTAMP_MAX_SIZE
+ sizeof(query_updated_param
) + 1;
1276 buffer1
= (char *) malloc(length
);
1281 query_timestamp
= (char *)malloc(TIMESTAMP_MAX_SIZE
);
1282 if (!query_timestamp
)
1284 result
= get_mili_timestamp(query_timestamp
, TIMESTAMP_MAX_SIZE
,
1291 /* Change the hour to 06:00AM plus the timezone when
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
);
1300 hour_const
= "06:00:00.000Z";
1301 ptr
-= strlen(hour_const
);
1305 *ptr
++ = *hour_const
++;
1307 } else if (timestamp
) {
1308 query_timestamp
= strdup(timestamp
);
1309 if (!query_timestamp
)
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");
1323 /* Set the query string to the available buffer parameter */
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
);
1338 strcpy(ptr
, query_zone_param
);
1339 strcat(ptr
, gcalobj
->location
);
1341 /* Set the query string to the available buffer parameter */
1349 /* TODO: implement URL encoding i.e. RFC1738 using
1350 * 'curl_easy_escape'.
1352 query_url
= mount_query_url(gcalobj
, buffer1
, buffer2
, buffer3
, NULL
);
1356 result
= get_follow_redirection(gcalobj
, query_url
, NULL
, gdata_version
);
1358 gcalobj
->has_xml
= 1;
1362 if (query_timestamp
)
1363 free(query_timestamp
);
1378 int gcal_set_timezone(struct gcal_resource
*gcalobj
, char *atimezone
)
1381 if ((!gcalobj
) || (!atimezone
))
1384 if (gcalobj
->timezone
)
1385 free(gcalobj
->timezone
);
1387 gcalobj
->timezone
= strdup(atimezone
);
1388 if (gcalobj
->timezone
)
1395 int gcal_set_location(struct gcal_resource
*gcalobj
, char *location
)
1398 if ((!gcalobj
) || (!location
))
1401 if (gcalobj
->location
)
1402 free(gcalobj
->location
);
1404 gcalobj
->location
= strdup(location
);
1405 if (gcalobj
->location
)
1413 void gcal_set_store_xml(struct gcal_resource
*gcalobj
, char flag
)
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");
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
)
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",
1450 int gcal_query(struct gcal_resource
*gcalobj
, const char *parameters
,
1451 const char *gdata_version
)
1453 char *query_url
= NULL
, *ptr_tmp
;
1456 if ((!gcalobj
) || (!parameters
))
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
;
1469 result
= get_follow_redirection(gcalobj
, query_url
, NULL
,
1473 gcalobj
->has_xml
= 1;
1482 char *gcal_get_id(struct gcal_entry
*entry
)
1490 char *gcal_get_xml(struct gcal_entry
*entry
)
1498 char gcal_get_deleted(struct gcal_entry
*entry
)
1502 return entry
->deleted
;
1507 char *gcal_get_updated(struct gcal_entry
*entry
)
1510 return entry
->updated
;
1516 char *gcal_get_title(struct gcal_entry
*entry
)
1519 return entry
->title
;
1525 char *gcal_get_url(struct gcal_entry
*entry
)
1528 return entry
->edit_uri
;
1533 char *gcal_get_etag(struct gcal_entry
*entry
)