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 * - think a way to securely store passwords
50 * - provide option to use another XML parser (maybe expat?)
62 #include <curl/curl.h>
64 #include "internal_gcal.h"
66 #include "gcal_parser.h"
67 #include "msvc_hacks.h"
69 #ifdef GCAL_DEBUG_CURL
70 #include "curl_debug_gcal.h"
73 static void reset_buffer(struct gcal_resource
*ptr
)
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
));
97 ptr
->curl
= curl_easy_init();
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
;
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
);
117 /* Initializes to google calendar as default */
118 if (gcal_set_service(ptr
, mode
)) {
127 int gcal_set_service(struct gcal_resource
*gcalobj
, gservice mode
)
132 if (mode
== GCALENDAR
)
133 strcpy(gcalobj
->service
, "cl");
134 else if (mode
== GCONTACT
)
135 strcpy(gcalobj
->service
, "cp");
145 void clean_buffer(struct gcal_resource
*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
)
158 if (gcal_obj
->buffer
)
159 free(gcal_obj
->buffer
);
161 curl_easy_cleanup(gcal_obj
->curl
);
163 free(gcal_obj
->auth
);
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
);
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
);
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
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
);
207 if (gcal_ptr
->fout_log
)
208 fprintf(gcal_ptr
->fout_log
,
209 "write_cb: Failed relloc!\n");
213 gcal_ptr
->buffer
= ptr_tmp
;
216 strncat(gcal_ptr
->buffer
, (char *)ptr
, size
);
222 static int check_request_error(struct gcal_resource
*gcalobj
, int code
,
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
);
248 static int common_upload(struct gcal_resource
*gcalobj
,
249 char *header
, char *header2
, char *header3
,
251 struct curl_slist
**curl_headers
,
252 const char *gdata_version
)
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);
267 /* To support Google Data API 2.0 */
268 response_headers
= curl_slist_append(response_headers
,
272 response_headers
= curl_slist_append(response_headers
, header
);
274 response_headers
= curl_slist_append(response_headers
, header2
);
276 response_headers
= curl_slist_append(response_headers
, header3
);
278 response_headers
= curl_slist_append(response_headers
, header4
);
280 if (!response_headers
)
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
);
292 int http_post(struct gcal_resource
*gcalobj
, const char *url
,
293 char *header
, char *header2
, char *header3
,
295 char *post_data
, unsigned int length
,
296 const int expected_answer
,
297 const char *gdata_version
)
301 struct curl_slist
*response_headers
= NULL
;
306 curl_ctx
= gcalobj
->curl
;
307 result
= common_upload(gcalobj
, header
, header2
, header3
, header4
,
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
);
317 curl_easy_setopt(curl_ctx
, CURLOPT_POSTFIELDS
, post_data
);
318 curl_easy_setopt(curl_ctx
, CURLOPT_POSTFIELDSIZE
,
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
);
328 curl_slist_free_all(response_headers
);
335 static int http_put(struct gcal_resource
*gcalobj
, const char *url
,
336 char *header
, char *header2
, char *header3
,
338 char *post_data
, unsigned int length
,
339 const int expected_answer
,
340 const char *gdata_version
)
344 struct curl_slist
*response_headers
= NULL
;
349 curl_ctx
= gcalobj
->curl
;
350 result
= common_upload(gcalobj
, header
, header2
, header3
, header4
,
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");
361 curl_easy_setopt(curl_ctx
, CURLOPT_POSTFIELDS
, post_data
);
362 curl_easy_setopt(curl_ctx
, CURLOPT_POSTFIELDSIZE
,
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
);
374 curl_slist_free_all(response_headers
);
376 /* Restores curl context to previous standard mode */
377 curl_easy_setopt(gcalobj
->curl
, CURLOPT_CUSTOMREQUEST
, NULL
);
384 int gcal_get_authentication(struct gcal_resource
*gcalobj
,
385 char *user
, char *password
)
393 char *enc_user
= NULL
;
394 char *enc_password
= NULL
;
396 if (!gcalobj
|| !user
|| !password
)
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
,
406 if ((!enc_password
) || (!enc_user
))
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
);
419 snprintf(post
, post_len
- 1,
426 EMAIL_FIELD
, enc_user
,
427 PASSWD_FIELD
, enc_password
,
428 SERVICE_FIELD
, gcalobj
->service
,
431 result
= http_post(gcalobj
, GCAL_URL
,
432 "Content-Type: application/x-www-form-urlencoded",
433 NULL
, NULL
, NULL
, post
, strlen(post
),
437 if ((tmp
= strstr(user
, "@"))) {
438 if (!(buffer
= strdup(user
)))
441 buffer
[tmp
- user
] = '\0';
442 if (!(gcalobj
->user
= strdup(buffer
)))
446 if (!(gcalobj
->domain
= strdup(tmp
)))
451 gcalobj
->user
= strdup(user
);
452 gcalobj
->domain
= strdup("gmail.com");
458 /* gcalendar server returns a string like this:
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.
469 gcalobj
->auth
= strstr(gcalobj
->buffer
, HEADER_AUTH
);
470 gcalobj
->auth
= strdup(gcalobj
->auth
+ strlen(HEADER_AUTH
));
474 tmp
= strstr(gcalobj
->auth
, "\n");
484 curl_free(enc_password
);
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
;
499 char *tmp_buffer
= NULL
;
500 void *downloader
= NULL
;
502 if (cb_download
== NULL
)
503 downloader
= write_cb
;
505 downloader
= cb_download
;
507 /* Must cleanup HTTP buffer between requests */
508 clean_buffer(gcalobj
);
512 length
= strlen(gcalobj
->auth
) + sizeof(HEADER_GET
) + 1;
513 tmp_buffer
= (char *) malloc(length
);
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
,
522 response_headers
= curl_slist_append(response_headers
, tmp_buffer
);
523 if (!response_headers
)
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
))) {
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
)) {
549 /* No valid service, just exit. */
554 /* It will extract and follow the first 'REF' link in the stream */
560 if (get_the_url(gcalobj
->buffer
, gcalobj
->length
, &gcalobj
->url
)) {
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
))) {
578 if (response_headers
)
579 curl_slist_free_all(response_headers
);
586 static char *mount_query_url(struct gcal_resource
*gcalobj
,
587 const char *parameters
, ...)
590 char *result
= NULL
, *query_param
= NULL
, *ptr_tmp
= NULL
;
592 char query_separator
[] = "&";
593 char query_init
[] = "?";
594 /* By default, google contacts are not ordered */
595 char contact_order
[] = "&orderby=lastmodified";
599 if ((!gcalobj
->user
))
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
) +
610 strlen(gcalobj
->max_results
) +
611 strlen(gcalobj
->user
) + 1;
613 length
= sizeof(GCAL_EVENT_START
) +
614 sizeof(GCAL_DELIMITER
) +
615 strlen(gcalobj
->domain
) +
616 sizeof(GCAL_EVENT_END
) +
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
) +
628 strlen(gcalobj
->max_results
) +
629 strlen(gcalobj
->user
) +
630 sizeof(contact_order
) + 1;
632 length
= sizeof(GCONTACT_START
) +
633 sizeof(GCAL_DELIMITER
) +
634 strlen(gcalobj
->domain
) +
635 sizeof(GCONTACT_END
) +
637 strlen(gcalobj
->user
) + 1;
642 result
= (char *)malloc(length
);
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
);
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
,
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 "¶m_1¶m_2&...¶m_n" */
679 length
+= strlen(parameters
) + sizeof(query_separator
);
680 ptr_tmp
= realloc(result
, length
);
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
);
695 strncat(result
, query_separator
,
696 sizeof(query_separator
));
697 strncat(result
, query_param
, strlen(query_param
));
714 int gcal_dump(struct gcal_resource
*gcalobj
, const char *gdata_version
)
721 /* Failed to get authentication token */
725 buffer
= mount_query_url(gcalobj
, NULL
);
729 result
= get_follow_redirection(gcalobj
, buffer
, NULL
, gdata_version
);
732 gcalobj
->has_xml
= 1;
740 int gcal_calendar_list(struct gcal_resource
*gcalobj
)
743 result
= get_follow_redirection(gcalobj
, GCAL_LIST
, NULL
,
745 /* TODO: parse the Atom feed */
750 int gcal_entry_number(struct gcal_resource
*gcalobj
)
756 /* Failed to get authentication token */
760 if (!gcalobj
->buffer
|| !gcalobj
->has_xml
)
763 gcalobj
->document
= build_dom_document(gcalobj
->buffer
);
764 if (!gcalobj
->document
)
767 result
= get_entries_number(gcalobj
->document
);
768 clean_dom_document(gcalobj
->document
);
769 gcalobj
->document
= NULL
;
775 struct gcal_event
*gcal_get_entries(struct gcal_resource
*gcalobj
,
780 struct gcal_event
*ptr_res
= NULL
;
785 if (!gcalobj
->buffer
|| !gcalobj
->has_xml
)
788 gcalobj
->document
= build_dom_document(gcalobj
->buffer
);
789 if (!gcalobj
->document
)
792 result
= get_entries_number(gcalobj
->document
);
796 ptr_res
= malloc(sizeof(struct gcal_event
) * result
);
799 memset(ptr_res
, 0, sizeof(struct gcal_event
) * 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
);
816 clean_dom_document(gcalobj
->document
);
817 gcalobj
->document
= NULL
;
825 static void clean_string(char *ptr_str
)
831 void gcal_init_event(struct gcal_event
*entry
)
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
)
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
)
873 for (; i
< length
; ++i
)
874 gcal_destroy_entry((entries
+ i
));
879 /* This function makes possible to share code between 'add'
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
,
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,
897 if (!data2post
|| !gcalobj
)
904 up_callback
= http_post
;
905 else if (up_mode
== PUT
)
906 up_callback
= http_put
;
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
) ;
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
);
927 snprintf(h_auth
, length
- 1, "%s%s", HEADER_GET
, gcalobj
->auth
);
931 content
= "Content-Type: application/atom+xml";
933 content
= content_type
;
936 if (!(strcmp(gcalobj
->service
, "cp"))) {
937 /* For contacts, there is *not* redirection. */
938 result
= up_callback(gcalobj
, url_server
,
945 "GData-Version: 3.0");
951 } else if (!(strcmp(gcalobj
->service
, "cl"))) {
952 /* For calendar, it *must* be redirection */
953 result
= up_callback(gcalobj
, url_server
,
959 GCAL_REDIRECT_ANSWER
,
962 /* XXX: there is one report where google server
963 * doesn't always return redirection.
965 if (gcalobj
->http_code
== expected_code
)
979 if (get_the_url(gcalobj
->buffer
, gcalobj
->length
, &gcalobj
->url
))
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",
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",
1000 data2post
, m_length
,
1002 "GData-Version: 2");
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
,
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
))
1040 result
= xmlentry_create(entries
, &xml_entry
, &length
);
1044 result
= up_entry(xml_entry
, strlen(xml_entry
),
1045 gcalobj
, GCAL_EDIT_URL
, NULL
,
1046 POST
, NULL
, GCAL_EDIT_ANSWER
);
1051 if (gcalobj
->store_xml_entry
) {
1052 if (entries
->common
.xml
)
1053 free(entries
->common
.xml
);
1054 if (!(entries
->common
.xml
= strdup(gcalobj
->buffer
)))
1058 /* Parse buffer and create the new contact object */
1062 gcalobj
->document
= build_dom_document(gcalobj
->buffer
);
1063 if (!gcalobj
->document
)
1066 /* There is only one 'entry' in the buffer */
1067 result
= extract_all_entries(gcalobj
->document
, updated
, 1);
1074 clean_dom_document(gcalobj
->document
);
1075 gcalobj
->document
= NULL
;
1085 int gcal_delete_event(struct gcal_resource
*gcalobj
,
1086 struct gcal_event
*entry
)
1088 int result
= -1, length
;
1091 if ((!entry
) || (!gcalobj
) || (!gcalobj
->auth
))
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
);
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 */
1109 NULL
, NULL
, 0, GCAL_REDIRECT_ANSWER
,
1110 "GData-Version: 2");
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
)
1123 /* Get the gsessionid redirect URL */
1126 gcalobj
->url
= NULL
;
1128 if (get_the_url(gcalobj
->buffer
, gcalobj
->length
, &gcalobj
->url
))
1131 result
= http_post(gcalobj
, gcalobj
->url
,
1132 "Content-Type: application/atom+xml",
1133 /* Google Data API 2.0 requires ETag */
1136 NULL
, NULL
, 0, GCAL_DEFAULT_ANSWER
,
1137 "GData-Version: 2");
1140 /* Restores curl context to previous standard mode */
1141 curl_easy_setopt(gcalobj
->curl
, CURLOPT_CUSTOMREQUEST
, NULL
);
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
))
1163 result
= xmlentry_create(entry
, &xml_entry
, &length
);
1167 result
= up_entry(xml_entry
, strlen(xml_entry
),
1168 gcalobj
, entry
->common
.edit_uri
,
1169 /* Google Data API 2.0 requires ETag */
1171 PUT
, NULL
, GCAL_DEFAULT_ANSWER
);
1176 if (gcalobj
->store_xml_entry
) {
1177 if (entry
->common
.xml
)
1178 free(entry
->common
.xml
);
1179 if (!(entry
->common
.xml
= strdup(gcalobj
->buffer
)))
1183 /* Parse buffer and create the new contact object */
1187 gcalobj
->document
= build_dom_document(gcalobj
->buffer
);
1188 if (!gcalobj
->document
)
1191 /* There is only one 'entry' in the buffer */
1192 result
= extract_all_entries(gcalobj
->document
, updated
, 1);
1199 clean_dom_document(gcalobj
->document
);
1200 gcalobj
->document
= NULL
;
1210 char *gcal_access_buffer(struct gcal_resource
*gcalobj
)
1212 char *result
= NULL
;
1214 if (gcalobj
->buffer
)
1215 result
= gcalobj
->buffer
;
1222 int get_mili_timestamp(char *timestamp
, size_t length
, char *atimezone
)
1226 struct timeval detail_time
;
1229 if (!timestamp
|| length
< TIMESTAMP_SIZE
)
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
);
1242 strncat(timestamp
, atimezone
, length
);
1244 strncat(timestamp
, "Z", length
);
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
1255 int gcal_query_updated(struct gcal_resource
*gcalobj
, char *timestamp
,
1256 const char *gdata_version
)
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
;
1270 /* Failed to get authentication token */
1274 length
= TIMESTAMP_MAX_SIZE
+ sizeof(query_updated_param
) + 1;
1275 buffer1
= (char *) malloc(length
);
1280 query_timestamp
= (char *)malloc(TIMESTAMP_MAX_SIZE
);
1281 if (!query_timestamp
)
1283 result
= get_mili_timestamp(query_timestamp
, TIMESTAMP_MAX_SIZE
,
1290 /* Change the hour to 06:00AM plus the timezone when
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
);
1299 hour_const
= "06:00:00.000Z";
1300 ptr
-= strlen(hour_const
);
1304 *ptr
++ = *hour_const
++;
1306 } else if (timestamp
) {
1307 query_timestamp
= strdup(timestamp
);
1308 if (!query_timestamp
)
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");
1322 /* Set the query string to the available buffer parameter */
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
);
1337 strcpy(ptr
, query_zone_param
);
1338 strcat(ptr
, gcalobj
->location
);
1340 /* Set the query string to the available buffer parameter */
1348 /* TODO: implement URL encoding i.e. RFC1738 using
1349 * 'curl_easy_escape'.
1351 query_url
= mount_query_url(gcalobj
, buffer1
, buffer2
, buffer3
, NULL
);
1355 result
= get_follow_redirection(gcalobj
, query_url
, NULL
, gdata_version
);
1357 gcalobj
->has_xml
= 1;
1361 if (query_timestamp
)
1362 free(query_timestamp
);
1377 int gcal_set_timezone(struct gcal_resource
*gcalobj
, char *atimezone
)
1380 if ((!gcalobj
) || (!atimezone
))
1383 if (gcalobj
->timezone
)
1384 free(gcalobj
->timezone
);
1386 gcalobj
->timezone
= strdup(atimezone
);
1387 if (gcalobj
->timezone
)
1394 int gcal_set_location(struct gcal_resource
*gcalobj
, char *location
)
1397 if ((!gcalobj
) || (!location
))
1400 if (gcalobj
->location
)
1401 free(gcalobj
->location
);
1403 gcalobj
->location
= strdup(location
);
1404 if (gcalobj
->location
)
1412 void gcal_set_store_xml(struct gcal_resource
*gcalobj
, char flag
)
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");
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
)
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",
1449 int gcal_query(struct gcal_resource
*gcalobj
, const char *parameters
,
1450 const char *gdata_version
)
1452 char *query_url
= NULL
, *ptr_tmp
;
1455 if ((!gcalobj
) || (!parameters
))
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
;
1468 result
= get_follow_redirection(gcalobj
, query_url
, NULL
,
1472 gcalobj
->has_xml
= 1;
1481 char *gcal_get_id(struct gcal_entry
*entry
)
1489 char *gcal_get_xml(struct gcal_entry
*entry
)
1497 char gcal_get_deleted(struct gcal_entry
*entry
)
1501 return entry
->deleted
;
1506 char *gcal_get_updated(struct gcal_entry
*entry
)
1509 return entry
->updated
;
1515 char *gcal_get_title(struct gcal_entry
*entry
)
1518 return entry
->title
;
1524 char *gcal_get_url(struct gcal_entry
*entry
)
1527 return entry
->edit_uri
;
1532 char *gcal_get_etag(struct gcal_entry
*entry
)