No issues found
1 /*
2 * e-caldav-chooser.c
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <webcal://www.gnu.org/licenses/>
16 *
17 */
18
19 #include "e-caldav-chooser.h"
20
21 #include <config.h>
22 #include <glib/gi18n-lib.h>
23
24 #include <libsoup/soup.h>
25 #include <libsoup/soup-gnome.h>
26
27 #include <libxml/tree.h>
28 #include <libxml/xpath.h>
29 #include <libxml/xpathInternals.h>
30
31 #include <libedataserverui/libedataserverui.h>
32
33 #define E_CALDAV_CHOOSER_GET_PRIVATE(obj) \
34 (G_TYPE_INSTANCE_GET_PRIVATE \
35 ((obj), E_TYPE_CALDAV_CHOOSER, ECaldavChooserPrivate))
36
37 #define XC(string) ((xmlChar *) string)
38
39 /* Standard Namespaces */
40 #define NS_WEBDAV "DAV:"
41 #define NS_CALDAV "urn:ietf:params:xml:ns:caldav"
42
43 /* Application-Specific Namespaces */
44 #define NS_CALSRV "http://calendarserver.org/ns/"
45 #define NS_ICAL "http://apple.com/ns/ical/"
46
47 typedef struct _Context Context;
48
49 struct _ECaldavChooserPrivate {
50 ESourceRegistry *registry;
51 ESource *source;
52 ECalClientSourceType source_type;
53 SoupSession *session;
54 GList *user_address_set;
55 gchar *password;
56 };
57
58 struct _Context {
59 SoupSession *session;
60
61 GCancellable *cancellable;
62 gulong cancelled_handler_id;
63
64 GList *user_address_set;
65 };
66
67 enum {
68 PROP_0,
69 PROP_REGISTRY,
70 PROP_SOURCE,
71 PROP_SOURCE_TYPE
72 };
73
74 /* Mainly for readability. */
75 enum {
76 DEPTH_0,
77 DEPTH_1
78 };
79
80 typedef enum {
81 SUPPORTS_VEVENT = 1 << 0,
82 SUPPORTS_VTODO = 1 << 1,
83 SUPPORTS_VJOURNAL = 1 << 2,
84 SUPPORTS_ALL = 0x7
85 } SupportedComponentSet;
86
87 enum {
88 COLUMN_DISPLAY_NAME, /* G_TYPE_STRING */
89 COLUMN_PATH_ENCODED, /* G_TYPE_STRING */
90 COLUMN_PATH_DECODED, /* G_TYPE_STRING */
91 COLUMN_COLOR, /* GDK_TYPE_COLOR */
92 COLUMN_HAS_COLOR, /* G_TYPE_BOOLEAN */
93 NUM_COLUMNS
94 };
95
96 /* Forward Declarations */
97 static void e_caldav_chooser_authenticator_init
98 (ESourceAuthenticatorInterface *interface);
99 static void caldav_chooser_get_collection_details
100 (SoupSession *session,
101 SoupMessage *message,
102 const gchar *path_or_uri,
103 GSimpleAsyncResult *simple);
104
105 G_DEFINE_DYNAMIC_TYPE_EXTENDED (
106 ECaldavChooser,
107 e_caldav_chooser,
108 GTK_TYPE_TREE_VIEW,
109 0,
110 G_IMPLEMENT_INTERFACE_DYNAMIC (
111 E_TYPE_SOURCE_AUTHENTICATOR,
112 e_caldav_chooser_authenticator_init))
113
114 static gconstpointer
115 compat_libxml_output_buffer_get_content (xmlOutputBufferPtr buf,
116 gsize *out_len)
117 {
118 #ifdef LIBXML2_NEW_BUFFER
119 *out_len = xmlOutputBufferGetSize (buf);
120 return xmlOutputBufferGetContent (buf);
121 #else
122 *out_len = buf->buffer->use;
123 return buf->buffer->content;
124 #endif
125 }
126
127 static void
128 context_cancel_message (GCancellable *cancellable,
129 Context *context)
130 {
131 soup_session_abort (context->session);
132 }
133
134 static Context *
135 context_new (ECaldavChooser *chooser,
136 GCancellable *cancellable)
137 {
138 Context *context;
139
140 context = g_slice_new0 (Context);
141 context->session = g_object_ref (chooser->priv->session);
142
143 if (cancellable != NULL) {
144 context->cancellable = g_object_ref (cancellable);
145 context->cancelled_handler_id = g_cancellable_connect (
146 context->cancellable,
147 G_CALLBACK (context_cancel_message),
148 context, (GDestroyNotify) NULL);
149 }
150
151 return context;
152 }
153
154 static void
155 context_free (Context *context)
156 {
157 if (context->session != NULL)
158 g_object_unref (context->session);
159
160 if (context->cancellable != NULL) {
161 g_cancellable_disconnect (
162 context->cancellable,
163 context->cancelled_handler_id);
164 g_object_unref (context->cancellable);
165 }
166
167 g_list_free_full (
168 context->user_address_set,
169 (GDestroyNotify) g_free);
170
171 g_slice_free (Context, context);
172 }
173
174 static void
175 caldav_chooser_redirect (SoupMessage *message,
176 SoupSession *session)
177 {
178 SoupURI *soup_uri;
179 const gchar *location;
180
181 if (!SOUP_STATUS_IS_REDIRECTION (message->status_code))
182 return;
183
184 location = soup_message_headers_get (
185 message->response_headers, "Location");
186
187 if (location == NULL)
188 return;
189
190 soup_uri = soup_uri_new_with_base (
191 soup_message_get_uri (message), location);
192
193 if (soup_uri == NULL) {
194 soup_message_set_status_full (
195 message, SOUP_STATUS_MALFORMED,
196 "Invalid Redirect URL");
197 return;
198 }
199
200 soup_message_set_uri (message, soup_uri);
201 soup_session_requeue_message (session, message);
202
203 soup_uri_free (soup_uri);
204 }
205
206 static G_GNUC_NULL_TERMINATED SoupMessage *
207 caldav_chooser_new_propfind (SoupSession *session,
208 SoupURI *soup_uri,
209 gint depth,
210 ...)
211 {
212 GHashTable *namespaces;
213 SoupMessage *message;
214 xmlDocPtr doc;
215 xmlNodePtr root;
216 xmlNodePtr node;
217 xmlNsPtr ns;
218 xmlOutputBufferPtr output;
219 gconstpointer content;
220 gsize length;
221 gpointer key;
222 va_list va;
223
224 /* Construct the XML content. */
225
226 doc = xmlNewDoc (XC ("1.0"));
227 node = xmlNewDocNode (doc, NULL, XC ("propfind"), NULL);
228
229 /* Build a hash table of namespace URIs to xmlNs structs. */
230 namespaces = g_hash_table_new (NULL, NULL);
231
232 ns = xmlNewNs (node, XC (NS_CALDAV), XC ("C"));
233 g_hash_table_insert (namespaces, (gpointer) NS_CALDAV, ns);
234
235 ns = xmlNewNs (node, XC (NS_CALSRV), XC ("CS"));
236 g_hash_table_insert (namespaces, (gpointer) NS_CALSRV, ns);
237
238 ns = xmlNewNs (node, XC (NS_ICAL), XC ("IC"));
239 g_hash_table_insert (namespaces, (gpointer) NS_ICAL, ns);
240
241 /* Add WebDAV last since we use it below. */
242 ns = xmlNewNs (node, XC (NS_WEBDAV), XC ("D"));
243 g_hash_table_insert (namespaces, (gpointer) NS_WEBDAV, ns);
244
245 xmlSetNs (node, ns);
246 xmlDocSetRootElement (doc, node);
247
248 node = xmlNewTextChild (node, ns, XC ("prop"), NULL);
249
250 va_start (va, depth);
251 while ((key = va_arg (va, gpointer)) != NULL) {
252 xmlChar *name;
253
254 ns = g_hash_table_lookup (namespaces, key);
255 name = va_arg (va, xmlChar *);
256
257 if (ns != NULL && name != NULL)
258 xmlNewTextChild (node, ns, name, NULL);
259 else
260 g_warn_if_reached ();
261 }
262 va_end (va);
263
264 g_hash_table_destroy (namespaces);
265
266 /* Construct the SoupMessage. */
267
268 message = soup_message_new_from_uri (SOUP_METHOD_PROPFIND, soup_uri);
269
270 soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT);
271
272 soup_message_headers_append (
273 message->request_headers,
274 "User-Agent", "Evolution/" VERSION);
275
276 soup_message_headers_append (
277 message->request_headers,
278 "Depth", (depth == 0) ? "0" : "1");
279
280 output = xmlAllocOutputBuffer (NULL);
281
282 root = xmlDocGetRootElement (doc);
283 xmlNodeDumpOutput (output, doc, root, 0, 1, NULL);
284 xmlOutputBufferFlush (output);
285
286 content = compat_libxml_output_buffer_get_content (output, &length);
287
288 soup_message_set_request (
289 message, "application/xml", SOUP_MEMORY_COPY,
290 content, length);
291
292 xmlOutputBufferClose (output);
293
294 soup_message_add_header_handler (
295 message, "got-body", "Location",
296 G_CALLBACK (caldav_chooser_redirect), session);
297
298 return message;
299 }
300
301 static void
302 caldav_chooser_authenticate_cb (SoupSession *session,
303 SoupMessage *message,
304 SoupAuth *auth,
305 gboolean retrying,
306 ECaldavChooser *chooser)
307 {
308 ESource *source;
309 ESourceAuthentication *extension;
310 const gchar *extension_name;
311 const gchar *username;
312 const gchar *password;
313
314 source = e_caldav_chooser_get_source (chooser);
315 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
316 extension = e_source_get_extension (source, extension_name);
317
318 username = e_source_authentication_get_user (extension);
319 password = chooser->priv->password;
320
321 /* If our password was rejected, let the operation fail. */
322 if (retrying)
323 return;
324
325 /* If we don't have a username, let the operation fail. */
326 if (username == NULL || *username == '\0')
327 return;
328
329 /* If we don't have a password, let the operation fail. */
330 if (password == NULL || *password == '\0')
331 return;
332
333 soup_auth_authenticate (auth, username, password);
334 }
335
336 static void
337 caldav_chooser_configure_session (ECaldavChooser *chooser,
338 SoupSession *session)
339 {
340 ESource *source;
341 ESourceWebdav *extension;
342 const gchar *extension_name;
343
344 source = e_caldav_chooser_get_source (chooser);
345 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
346 extension = e_source_get_extension (source, extension_name);
347
348 g_object_bind_property (
349 extension, "ignore-invalid-cert",
350 session, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE,
351 G_BINDING_SYNC_CREATE |
352 G_BINDING_INVERT_BOOLEAN);
353
354 if (g_getenv ("CALDAV_DEBUG") != NULL) {
355 SoupLogger *logger;
356
357 logger = soup_logger_new (
358 SOUP_LOGGER_LOG_BODY, 100 * 1024 * 1024);
359 soup_session_add_feature (
360 session, SOUP_SESSION_FEATURE (logger));
361 g_object_unref (logger);
362 }
363
364 g_object_set (session, SOUP_SESSION_TIMEOUT, 90, NULL);
365
366 /* This adds proxy support. */
367 soup_session_add_feature_by_type (
368 session, SOUP_TYPE_GNOME_FEATURES_2_26);
369
370 g_signal_connect (
371 session, "authenticate",
372 G_CALLBACK (caldav_chooser_authenticate_cb), chooser);
373 }
374
375 static gboolean
376 caldav_chooser_check_successful (SoupMessage *message,
377 GError **error)
378 {
379 GIOErrorEnum error_code;
380
381 /* Loosely copied from the GVFS DAV backend. */
382
383 if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code))
384 return TRUE;
385
386 switch (message->status_code) {
387 case SOUP_STATUS_CANCELLED:
388 error_code = G_IO_ERROR_CANCELLED;
389 break;
390 case SOUP_STATUS_NOT_FOUND:
391 error_code = G_IO_ERROR_NOT_FOUND;
392 break;
393 case SOUP_STATUS_UNAUTHORIZED:
394 case SOUP_STATUS_PAYMENT_REQUIRED:
395 case SOUP_STATUS_FORBIDDEN:
396 error_code = G_IO_ERROR_PERMISSION_DENIED;
397 break;
398 case SOUP_STATUS_REQUEST_TIMEOUT:
399 error_code = G_IO_ERROR_TIMED_OUT;
400 break;
401 case SOUP_STATUS_CANT_RESOLVE:
402 error_code = G_IO_ERROR_HOST_NOT_FOUND;
403 break;
404 case SOUP_STATUS_NOT_IMPLEMENTED:
405 error_code = G_IO_ERROR_NOT_SUPPORTED;
406 break;
407 case SOUP_STATUS_INSUFFICIENT_STORAGE:
408 error_code = G_IO_ERROR_NO_SPACE;
409 break;
410 default:
411 error_code = G_IO_ERROR_FAILED;
412 break;
413 }
414
415 g_set_error (
416 error, G_IO_ERROR, error_code,
417 _("HTTP Error: %s"), message->reason_phrase);
418
419 return FALSE;
420 }
421
422 static xmlDocPtr
423 caldav_chooser_parse_xml (SoupMessage *message,
424 const gchar *expected_name,
425 GError **error)
426 {
427 xmlDocPtr doc;
428 xmlNodePtr root;
429
430 if (!caldav_chooser_check_successful (message, error))
431 return NULL;
432
433 doc = xmlReadMemory (
434 message->response_body->data,
435 message->response_body->length,
436 "response.xml", NULL,
437 XML_PARSE_NONET |
438 XML_PARSE_NOWARNING |
439 XML_PARSE_NOCDATA |
440 XML_PARSE_COMPACT);
441
442 if (doc == NULL) {
443 g_set_error_literal (
444 error, G_IO_ERROR, G_IO_ERROR_FAILED,
445 _("Could not parse response"));
446 return NULL;
447 }
448
449 root = xmlDocGetRootElement (doc);
450
451 if (root == NULL || root->children == NULL) {
452 g_set_error_literal (
453 error, G_IO_ERROR, G_IO_ERROR_FAILED,
454 _("Empty response"));
455 xmlFreeDoc (doc);
456 return NULL;
457 }
458
459 if (g_strcmp0 ((gchar *) root->name, expected_name) != 0) {
460 g_set_error_literal (
461 error, G_IO_ERROR, G_IO_ERROR_FAILED,
462 _("Unexpected reply from server"));
463 xmlFreeDoc (doc);
464 return NULL;
465 }
466
467 return doc;
468 }
469
470 static xmlXPathObjectPtr
471 caldav_chooser_get_xpath (xmlXPathContextPtr xp_ctx,
472 const gchar *path_format,
473 ...)
474 {
475 xmlXPathObjectPtr xp_obj;
476 va_list va;
477 gchar *path;
478
479 va_start (va, path_format);
480 path = g_strdup_vprintf (path_format, va);
481 va_end (va);
482
483 xp_obj = xmlXPathEvalExpression (XC (path), xp_ctx);
484
485 g_free (path);
486
487 if (xp_obj == NULL)
488 return NULL;
489
490 if (xp_obj->type != XPATH_NODESET) {
491 xmlXPathFreeObject (xp_obj);
492 return NULL;
493 }
494
495 if (xmlXPathNodeSetGetLength (xp_obj->nodesetval) == 0) {
496 xmlXPathFreeObject (xp_obj);
497 return NULL;
498 }
499
500 return xp_obj;
501 }
502
503 static gchar *
504 caldav_chooser_get_xpath_string (xmlXPathContextPtr xp_ctx,
505 const gchar *path_format,
506 ...)
507 {
508 xmlXPathObjectPtr xp_obj;
509 va_list va;
510 gchar *path;
511 gchar *expression;
512 gchar *string = NULL;
513
514 va_start (va, path_format);
515 path = g_strdup_vprintf (path_format, va);
516 va_end (va);
517
518 expression = g_strdup_printf ("string(%s)", path);
519 xp_obj = xmlXPathEvalExpression (XC (expression), xp_ctx);
520 g_free (expression);
521
522 g_free (path);
523
524 if (xp_obj == NULL)
525 return NULL;
526
527 if (xp_obj->type == XPATH_STRING)
528 string = g_strdup ((gchar *) xp_obj->stringval);
529
530 /* If the string is empty, return NULL. */
531 if (string != NULL && *string == '\0') {
532 g_free (string);
533 string = NULL;
534 }
535
536 xmlXPathFreeObject (xp_obj);
537
538 return string;
539 }
540
541 static void
542 caldav_chooser_process_user_address_set (xmlXPathContextPtr xp_ctx,
543 Context *context)
544 {
545 xmlXPathObjectPtr xp_obj;
546 gint ii, length;
547
548 /* XXX Is response[1] safe to assume? */
549 xp_obj = caldav_chooser_get_xpath (
550 xp_ctx,
551 "/D:multistatus"
552 "/D:response"
553 "/D:propstat"
554 "/D:prop"
555 "/C:calendar-user-address-set");
556
557 if (xp_obj == NULL)
558 return;
559
560 length = xmlXPathNodeSetGetLength (xp_obj->nodesetval);
561
562 for (ii = 0; ii < length; ii++) {
563 GList *duplicate;
564 const gchar *address;
565 gchar *href;
566
567 href = caldav_chooser_get_xpath_string (
568 xp_ctx,
569 "/D:multistatus"
570 "/D:response"
571 "/D:propstat"
572 "/D:prop"
573 "/C:calendar-user-address-set"
574 "/D:href[%d]", ii + 1);
575
576 if (href == NULL)
577 continue;
578
579 if (!g_str_has_prefix (href, "mailto:")) {
580 g_free (href);
581 continue;
582 }
583
584 /* strlen("mailto:") == 7 */
585 address = href + 7;
586
587 /* Avoid duplicates. */
588 duplicate = g_list_find_custom (
589 context->user_address_set,
590 address, (GCompareFunc) strdup);
591
592 if (duplicate != NULL) {
593 g_free (href);
594 continue;
595 }
596
597 context->user_address_set = g_list_append (
598 context->user_address_set, g_strdup (address));
599
600 g_free (href);
601 }
602
603 xmlXPathFreeObject (xp_obj);
604 }
605
606 static SupportedComponentSet
607 caldav_chooser_get_supported_component_set (xmlXPathContextPtr xp_ctx,
608 gint index)
609 {
610 xmlXPathObjectPtr xp_obj;
611 SupportedComponentSet set = 0;
612 gint ii, length;
613
614 xp_obj = caldav_chooser_get_xpath (
615 xp_ctx,
616 "/D:multistatus"
617 "/D:response[%d]"
618 "/D:propstat"
619 "/D:prop"
620 "/C:supported-calendar-component-set"
621 "/C:comp", index);
622
623 /* If the property is not present, assume all component
624 * types are supported. (RFC 4791, Section 5.2.3) */
625 if (xp_obj == NULL)
626 return SUPPORTS_ALL;
627
628 length = xmlXPathNodeSetGetLength (xp_obj->nodesetval);
629
630 for (ii = 0; ii < length; ii++) {
631 gchar *name;
632
633 name = caldav_chooser_get_xpath_string (
634 xp_ctx,
635 "/D:multistatus"
636 "/D:response[%d]"
637 "/D:propstat"
638 "/D:prop"
639 "/C:supported-calendar-component-set"
640 "/C:comp[%d]"
641 "/@name", index, ii + 1);
642
643 if (name == NULL)
644 continue;
645
646 if (g_ascii_strcasecmp (name, "VEVENT") == 0)
647 set |= SUPPORTS_VEVENT;
648 else if (g_ascii_strcasecmp (name, "VTODO") == 0)
649 set |= SUPPORTS_VTODO;
650 else if (g_ascii_strcasecmp (name, "VJOURNAL") == 0)
651 set |= SUPPORTS_VJOURNAL;
652
653 g_free (name);
654 }
655
656 xmlXPathFreeObject (xp_obj);
657
658 return set;
659 }
660
661 static void
662 caldav_chooser_process_response (SoupSession *session,
663 SoupMessage *message,
664 GSimpleAsyncResult *simple,
665 xmlXPathContextPtr xp_ctx,
666 gint index)
667 {
668 GObject *object;
669 xmlXPathObjectPtr xp_obj;
670 SupportedComponentSet comp_set;
671 ECaldavChooser *chooser;
672 GtkTreeModel *tree_model;
673 GtkTreeIter iter;
674 GdkColor color;
675 gchar *color_spec;
676 gchar *display_name;
677 gchar *href_decoded;
678 gchar *href_encoded;
679 gchar *status_line;
680 guint status;
681 gboolean has_color;
682 gboolean success;
683
684 /* This returns a new reference, for reasons passing understanding. */
685 object = g_async_result_get_source_object (G_ASYNC_RESULT (simple));
686
687 chooser = E_CALDAV_CHOOSER (object);
688 tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (object));
689
690 g_object_unref (object);
691
692 status_line = caldav_chooser_get_xpath_string (
693 xp_ctx,
694 "/D:multistatus"
695 "/D:response[%d]"
696 "/D:propstat"
697 "/D:status",
698 index);
699
700 if (status_line == NULL)
701 return;
702
703 success = soup_headers_parse_status_line (
704 status_line, NULL, &status, NULL);
705
706 g_free (status_line);
707
708 if (!success || status != SOUP_STATUS_OK)
709 return;
710
711 href_encoded = caldav_chooser_get_xpath_string (
712 xp_ctx,
713 "/D:multistatus"
714 "/D:response[%d]"
715 "/D:href",
716 index);
717
718 if (href_encoded == NULL)
719 return;
720
721 href_decoded = soup_uri_decode (href_encoded);
722
723 /* Get the display name or fall back to the href. */
724
725 display_name = caldav_chooser_get_xpath_string (
726 xp_ctx,
727 "/D:multistatus"
728 "/D:response[%d]"
729 "/D:propstat"
730 "/D:prop"
731 "/D:displayname",
732 index);
733
734 if (display_name == NULL) {
735 gchar *href_copy, *cp;
736
737 href_copy = g_strdup (href_decoded);
738
739 /* Use the last non-empty path segment. */
740 while ((cp = strrchr (href_copy, '/')) != NULL) {
741 if (*(cp + 1) == '\0')
742 *cp = '\0';
743 else {
744 display_name = g_strdup (cp + 1);
745 break;
746 }
747 }
748
749 g_free (href_copy);
750 }
751
752 /* Make sure the resource is a calendar. */
753
754 xp_obj = caldav_chooser_get_xpath (
755 xp_ctx,
756 "/D:multistatus"
757 "/D:response[%d]"
758 "/D:propstat"
759 "/D:prop"
760 "/D:resourcetype"
761 "/C:calendar",
762 index);
763
764 if (xp_obj == NULL)
765 goto exit;
766
767 xmlXPathFreeObject (xp_obj);
768
769 /* Get the color specification string. */
770
771 color_spec = caldav_chooser_get_xpath_string (
772 xp_ctx,
773 "/D:multistatus"
774 "/D:response[%d]"
775 "/D:propstat"
776 "/D:prop"
777 "/IC:calendar-color",
778 index);
779
780 if (color_spec != NULL)
781 has_color = gdk_color_parse (color_spec, &color);
782 else
783 has_color = FALSE;
784
785 g_free (color_spec);
786
787 /* Which calendar component types are supported? */
788
789 comp_set = caldav_chooser_get_supported_component_set (xp_ctx, index);
790
791 switch (e_caldav_chooser_get_source_type (chooser)) {
792 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
793 if ((comp_set & SUPPORTS_VEVENT) == 0)
794 goto exit;
795 break;
796 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
797 if ((comp_set & SUPPORTS_VJOURNAL) == 0)
798 goto exit;
799 break;
800 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
801 if ((comp_set & SUPPORTS_VTODO) == 0)
802 goto exit;
803 break;
804 default:
805 goto exit;
806 }
807
808 /* Append a new tree model row. */
809
810 gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
811
812 gtk_list_store_set (
813 GTK_LIST_STORE (tree_model), &iter,
814 COLUMN_DISPLAY_NAME, display_name,
815 COLUMN_PATH_ENCODED, href_encoded,
816 COLUMN_PATH_DECODED, href_decoded,
817 COLUMN_COLOR, has_color ? &color : NULL,
818 COLUMN_HAS_COLOR, has_color,
819 -1);
820
821 exit:
822 g_free (display_name);
823 g_free (href_decoded);
824 g_free (href_encoded);
825 }
826
827 static void
828 caldav_chooser_collection_details_cb (SoupSession *session,
829 SoupMessage *message,
830 GSimpleAsyncResult *simple)
831 {
832 xmlDocPtr doc;
833 xmlXPathContextPtr xp_ctx;
834 xmlXPathObjectPtr xp_obj;
835 GError *error = NULL;
836
837 doc = caldav_chooser_parse_xml (message, "multistatus", &error);
838
839 if (error != NULL) {
840 g_warn_if_fail (doc == NULL);
841 g_simple_async_result_set_from_error (simple, error);
842 g_error_free (error);
843 goto exit;
844 }
845
846 xp_ctx = xmlXPathNewContext (doc);
847 xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV));
848 xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV));
849 xmlXPathRegisterNs (xp_ctx, XC ("CS"), XC (NS_CALSRV));
850 xmlXPathRegisterNs (xp_ctx, XC ("IC"), XC (NS_ICAL));
851
852 xp_obj = caldav_chooser_get_xpath (
853 xp_ctx,
854 "/D:multistatus"
855 "/D:response");
856
857 if (xp_obj != NULL) {
858 gint length, ii;
859
860 length = xmlXPathNodeSetGetLength (xp_obj->nodesetval);
861
862 for (ii = 0; ii < length; ii++)
863 caldav_chooser_process_response (
864 session, message, simple, xp_ctx, ii + 1);
865
866 xmlXPathFreeObject (xp_obj);
867 }
868
869 xmlXPathFreeContext (xp_ctx);
870 xmlFreeDoc (doc);
871
872 exit:
873 /* If we were cancelled then we're in a GCancellable::cancelled
874 * signal handler right now and GCancellable has its mutex locked,
875 * which means calling g_cancellable_disconnect() now will deadlock
876 * when it too tries to acquire the mutex. So defer the GAsyncResult
877 * completion to an idle callback to avoid this deadlock. */
878 g_simple_async_result_complete_in_idle (simple);
879 g_object_unref (simple);
880 }
881
882 static void
883 caldav_chooser_get_collection_details (SoupSession *session,
884 SoupMessage *message,
885 const gchar *path_or_uri,
886 GSimpleAsyncResult *simple)
887 {
888 SoupURI *soup_uri;
889
890 soup_uri = soup_uri_new (path_or_uri);
891 if (!soup_uri ||
892 !soup_uri_get_scheme (soup_uri) ||
893 !soup_uri_get_host (soup_uri) ||
894 !soup_uri_get_path (soup_uri) ||
895 !*soup_uri_get_scheme (soup_uri) ||
896 !*soup_uri_get_host (soup_uri) ||
897 !*soup_uri_get_path (soup_uri)) {
898 /* it's a path only, not full uri */
899 if (soup_uri)
900 soup_uri_free (soup_uri);
901 soup_uri = soup_uri_copy (soup_message_get_uri (message));
902 soup_uri_set_path (soup_uri, path_or_uri);
903 }
904
905 message = caldav_chooser_new_propfind (
906 session, soup_uri, DEPTH_1,
907 NS_WEBDAV, XC ("displayname"),
908 NS_WEBDAV, XC ("resourcetype"),
909 NS_CALDAV, XC ("calendar-description"),
910 NS_CALDAV, XC ("supported-calendar-component-set"),
911 NS_CALDAV, XC ("calendar-user-address-set"),
912 NS_CALSRV, XC ("getctag"),
913 NS_ICAL, XC ("calendar-color"),
914 NULL);
915
916 /* This takes ownership of the message. */
917 soup_session_queue_message (
918 session, message, (SoupSessionCallback)
919 caldav_chooser_collection_details_cb, simple);
920
921 soup_uri_free (soup_uri);
922 }
923
924 static void
925 caldav_chooser_calendar_home_set_cb (SoupSession *session,
926 SoupMessage *message,
927 GSimpleAsyncResult *simple)
928 {
929 Context *context;
930 SoupURI *soup_uri;
931 xmlDocPtr doc;
932 xmlXPathContextPtr xp_ctx;
933 xmlXPathObjectPtr xp_obj;
934 gchar *calendar_home_set;
935 GError *error = NULL;
936
937 context = g_simple_async_result_get_op_res_gpointer (simple);
938
939 doc = caldav_chooser_parse_xml (message, "multistatus", &error);
940
941 /* If we were cancelled then we're in a GCancellable::cancelled
942 * signal handler right now and GCancellable has its mutex locked,
943 * which means calling g_cancellable_disconnect() now will deadlock
944 * when it too tries to acquire the mutex. So defer the GAsyncResult
945 * completion to an idle callback to avoid this deadlock. */
946 if (error != NULL) {
947 g_simple_async_result_set_from_error (simple, error);
948 g_simple_async_result_complete_in_idle (simple);
949 g_object_unref (simple);
950 g_error_free (error);
951 return;
952 }
953
954 g_return_if_fail (doc != NULL);
955
956 xp_ctx = xmlXPathNewContext (doc);
957 xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV));
958 xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV));
959
960 /* Record any "C:calendar-user-address-set" properties. */
961 caldav_chooser_process_user_address_set (xp_ctx, context);
962
963 /* Try to find the calendar home URL using the
964 * following properties in order of preference:
965 *
966 * "C:calendar-home-set"
967 * "D:current-user-principal"
968 * "D:principal-URL"
969 *
970 * If the second or third URL preference is used, rerun
971 * the PROPFIND method on that URL at Depth=1 in hopes
972 * of getting a proper "C:calendar-home-set" property.
973 */
974
975 /* FIXME There can be multiple "D:href" elements for a
976 * "C:calendar-home-set". We're only processing
977 * the first one. Need to iterate over them. */
978
979 calendar_home_set = caldav_chooser_get_xpath_string (
980 xp_ctx,
981 "/D:multistatus"
982 "/D:response"
983 "/D:propstat"
984 "/D:prop"
985 "/C:calendar-home-set"
986 "/D:href");
987
988 if (calendar_home_set != NULL)
989 goto get_collection_details;
990
991 g_free (calendar_home_set);
992
993 calendar_home_set = caldav_chooser_get_xpath_string (
994 xp_ctx,
995 "/D:multistatus"
996 "/D:response"
997 "/D:propstat"
998 "/D:prop"
999 "/D:current-user-principal"
1000 "/D:href");
1001
1002 if (calendar_home_set != NULL)
1003 goto retry_propfind;
1004
1005 g_free (calendar_home_set);
1006
1007 calendar_home_set = caldav_chooser_get_xpath_string (
1008 xp_ctx,
1009 "/D:multistatus"
1010 "/D:response"
1011 "/D:propstat"
1012 "/D:prop"
1013 "/D:principal-URL"
1014 "/D:href");
1015
1016 if (calendar_home_set != NULL)
1017 goto retry_propfind;
1018
1019 g_free (calendar_home_set);
1020 calendar_home_set = NULL;
1021
1022 /* None of the aforementioned properties are present. If the
1023 * user-supplied CalDAV URL is a calendar resource, use that. */
1024
1025 xp_obj = caldav_chooser_get_xpath (
1026 xp_ctx,
1027 "/D:multistatus"
1028 "/D:response"
1029 "/D:propstat"
1030 "/D:prop"
1031 "/D:resourcetype"
1032 "/C:calendar");
1033
1034 if (xp_obj != NULL) {
1035 soup_uri = soup_message_get_uri (message);
1036
1037 if (soup_uri->path != NULL && *soup_uri->path != '\0') {
1038 gchar *slash;
1039
1040 soup_uri = soup_uri_copy (soup_uri);
1041
1042 slash = strrchr (soup_uri->path, '/');
1043 while (slash != NULL && slash != soup_uri->path) {
1044
1045 if (slash[1] != '\0') {
1046 slash[1] = '\0';
1047 calendar_home_set =
1048 g_strdup (soup_uri->path);
1049 break;
1050 }
1051
1052 slash[0] = '\0';
1053 slash = strrchr (soup_uri->path, '/');
1054 }
1055
1056 soup_uri_free (soup_uri);
1057 }
1058
1059 xmlXPathFreeObject (xp_obj);
1060 }
1061
1062 if (calendar_home_set == NULL || *calendar_home_set == '\0') {
1063 g_free (calendar_home_set);
1064 g_simple_async_result_set_error (
1065 simple, G_IO_ERROR, G_IO_ERROR_FAILED,
1066 _("Could not locate user's calendars"));
1067 g_simple_async_result_complete (simple);
1068 g_object_unref (simple);
1069 return;
1070 }
1071
1072 get_collection_details:
1073
1074 xmlXPathFreeContext (xp_ctx);
1075 xmlFreeDoc (doc);
1076
1077 caldav_chooser_get_collection_details (
1078 session, message, calendar_home_set, simple);
1079
1080 g_free (calendar_home_set);
1081
1082 return;
1083
1084 retry_propfind:
1085
1086 xmlXPathFreeContext (xp_ctx);
1087 xmlFreeDoc (doc);
1088
1089 soup_uri = soup_uri_copy (soup_message_get_uri (message));
1090 soup_uri_set_path (soup_uri, calendar_home_set);
1091
1092 /* Note that we omit "D:resourcetype", "D:current-user-principal"
1093 * and "D:principal-URL" in order to short-circuit the recursion. */
1094 message = caldav_chooser_new_propfind (
1095 session, soup_uri, DEPTH_1,
1096 NS_CALDAV, XC ("calendar-home-set"),
1097 NS_CALDAV, XC ("calendar-user-address-set"),
1098 NULL);
1099
1100 /* This takes ownership of the message. */
1101 soup_session_queue_message (
1102 session, message, (SoupSessionCallback)
1103 caldav_chooser_calendar_home_set_cb, simple);
1104
1105 soup_uri_free (soup_uri);
1106
1107 g_free (calendar_home_set);
1108 }
1109
1110 static void
1111 caldav_chooser_set_registry (ECaldavChooser *chooser,
1112 ESourceRegistry *registry)
1113 {
1114 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
1115 g_return_if_fail (chooser->priv->registry == NULL);
1116
1117 chooser->priv->registry = g_object_ref (registry);
1118 }
1119
1120 static void
1121 caldav_chooser_set_source (ECaldavChooser *chooser,
1122 ESource *source)
1123 {
1124 g_return_if_fail (E_IS_SOURCE (source));
1125 g_return_if_fail (chooser->priv->source == NULL);
1126
1127 chooser->priv->source = g_object_ref (source);
1128 }
1129
1130 static void
1131 caldav_chooser_set_source_type (ECaldavChooser *chooser,
1132 ECalClientSourceType source_type)
1133 {
1134 chooser->priv->source_type = source_type;
1135 }
1136
1137 static void
1138 caldav_chooser_set_property (GObject *object,
1139 guint property_id,
1140 const GValue *value,
1141 GParamSpec *pspec)
1142 {
1143 switch (property_id) {
1144 case PROP_REGISTRY:
1145 caldav_chooser_set_registry (
1146 E_CALDAV_CHOOSER (object),
1147 g_value_get_object (value));
1148 return;
1149
1150 case PROP_SOURCE:
1151 caldav_chooser_set_source (
1152 E_CALDAV_CHOOSER (object),
1153 g_value_get_object (value));
1154 return;
1155
1156 case PROP_SOURCE_TYPE:
1157 caldav_chooser_set_source_type (
1158 E_CALDAV_CHOOSER (object),
1159 g_value_get_enum (value));
1160 return;
1161 }
1162
1163 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1164 }
1165
1166 static void
1167 caldav_chooser_get_property (GObject *object,
1168 guint property_id,
1169 GValue *value,
1170 GParamSpec *pspec)
1171 {
1172 switch (property_id) {
1173 case PROP_REGISTRY:
1174 g_value_set_object (
1175 value, e_caldav_chooser_get_registry (
1176 E_CALDAV_CHOOSER (object)));
1177 return;
1178
1179 case PROP_SOURCE:
1180 g_value_set_object (
1181 value, e_caldav_chooser_get_source (
1182 E_CALDAV_CHOOSER (object)));
1183 return;
1184
1185 case PROP_SOURCE_TYPE:
1186 g_value_set_enum (
1187 value, e_caldav_chooser_get_source_type (
1188 E_CALDAV_CHOOSER (object)));
1189 return;
1190 }
1191
1192 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1193 }
1194
1195 static void
1196 caldav_chooser_dispose (GObject *object)
1197 {
1198 ECaldavChooserPrivate *priv;
1199
1200 priv = E_CALDAV_CHOOSER_GET_PRIVATE (object);
1201
1202 if (priv->registry != NULL) {
1203 g_object_unref (priv->registry);
1204 priv->registry = NULL;
1205 }
1206
1207 if (priv->source != NULL) {
1208 g_object_unref (priv->source);
1209 priv->source = NULL;
1210 }
1211
1212 if (priv->session != NULL) {
1213 g_object_unref (priv->session);
1214 priv->session = NULL;
1215 }
1216
1217 /* Chain up to parent's dispose() method. */
1218 G_OBJECT_CLASS (e_caldav_chooser_parent_class)->dispose (object);
1219 }
1220
1221 static void
1222 caldav_chooser_finalize (GObject *object)
1223 {
1224 ECaldavChooserPrivate *priv;
1225
1226 priv = E_CALDAV_CHOOSER_GET_PRIVATE (object);
1227
1228 g_list_free_full (
1229 priv->user_address_set,
1230 (GDestroyNotify) g_free);
1231
1232 g_free (priv->password);
1233
1234 /* Chain up to parent's finalize() method. */
1235 G_OBJECT_CLASS (e_caldav_chooser_parent_class)->finalize (object);
1236 }
1237
1238 static void
1239 caldav_chooser_constructed (GObject *object)
1240 {
1241 ECaldavChooser *chooser;
1242 GtkTreeView *tree_view;
1243 GtkListStore *list_store;
1244 GtkCellRenderer *renderer;
1245 GtkTreeViewColumn *column;
1246 SoupSession *session;
1247
1248 /* Chain up to parent's constructed() method. */
1249 G_OBJECT_CLASS (e_caldav_chooser_parent_class)->constructed (object);
1250
1251 chooser = E_CALDAV_CHOOSER (object);
1252 session = soup_session_async_new ();
1253 caldav_chooser_configure_session (chooser, session);
1254 chooser->priv->session = session;
1255
1256 tree_view = GTK_TREE_VIEW (object);
1257
1258 list_store = gtk_list_store_new (
1259 NUM_COLUMNS,
1260 G_TYPE_STRING, /* COLUMN_DISPLAY_NAME */
1261 G_TYPE_STRING, /* COLUMN_PATH_ENCODED */
1262 G_TYPE_STRING, /* COLUMN_PATH_DECODED */
1263 GDK_TYPE_COLOR, /* COLUMN_COLOR */
1264 G_TYPE_BOOLEAN); /* COLUMN_HAS_COLOR */
1265
1266 gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (list_store));
1267
1268 column = gtk_tree_view_column_new ();
1269 gtk_tree_view_column_set_expand (column, TRUE);
1270 gtk_tree_view_column_set_title (column, _("Name"));
1271 gtk_tree_view_insert_column (tree_view, column, -1);
1272
1273 renderer = e_cell_renderer_color_new ();
1274 gtk_tree_view_column_pack_start (column, renderer, FALSE);
1275 gtk_tree_view_column_set_attributes (
1276 column, renderer,
1277 "color", COLUMN_COLOR,
1278 "visible", COLUMN_HAS_COLOR,
1279 NULL);
1280
1281 renderer = gtk_cell_renderer_text_new ();
1282 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1283 gtk_tree_view_column_set_attributes (
1284 column, renderer,
1285 "text", COLUMN_DISPLAY_NAME,
1286 NULL);
1287
1288 column = gtk_tree_view_column_new ();
1289 gtk_tree_view_column_set_expand (column, FALSE);
1290 gtk_tree_view_column_set_title (column, _("Path"));
1291 gtk_tree_view_insert_column (tree_view, column, -1);
1292
1293 renderer = gtk_cell_renderer_text_new ();
1294 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1295 gtk_tree_view_column_set_attributes (
1296 column, renderer,
1297 "text", COLUMN_PATH_DECODED,
1298 NULL);
1299 }
1300
1301 /* Helper for caldav_chooser_try_password_sync() */
1302 static void
1303 caldav_chooser_try_password_cancelled_cb (GCancellable *cancellable,
1304 SoupSession *session)
1305 {
1306 soup_session_abort (session);
1307 }
1308
1309 static ESourceAuthenticationResult
1310 caldav_chooser_try_password_sync (ESourceAuthenticator *auth,
1311 const GString *password,
1312 GCancellable *cancellable,
1313 GError **error)
1314 {
1315 ECaldavChooser *chooser;
1316 ESourceAuthenticationResult result;
1317 SoupMessage *message;
1318 SoupSession *session;
1319 SoupURI *soup_uri;
1320 ESource *source;
1321 ESourceWebdav *extension;
1322 const gchar *extension_name;
1323 gulong cancel_id = 0;
1324 GError *local_error = NULL;
1325
1326 chooser = E_CALDAV_CHOOSER (auth);
1327
1328 /* Cache the password for later use in our
1329 * SoupSession::authenticate signal handler. */
1330 g_free (chooser->priv->password);
1331 chooser->priv->password = g_strdup (password->str);
1332
1333 /* Create our own SoupSession so we
1334 * can try the password synchronously. */
1335 session = soup_session_sync_new ();
1336 caldav_chooser_configure_session (chooser, session);
1337
1338 source = e_caldav_chooser_get_source (chooser);
1339 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
1340 extension = e_source_get_extension (source, extension_name);
1341
1342 soup_uri = e_source_webdav_dup_soup_uri (extension);
1343 g_return_val_if_fail (soup_uri != NULL, E_SOURCE_AUTHENTICATION_ERROR);
1344
1345 /* Try some simple PROPFIND query. We don't care about the query
1346 * result, only whether the CalDAV server will accept our password. */
1347 message = caldav_chooser_new_propfind (
1348 session, soup_uri, DEPTH_0,
1349 NS_WEBDAV, XC ("resourcetype"),
1350 NULL);
1351
1352 if (G_IS_CANCELLABLE (cancellable))
1353 cancel_id = g_cancellable_connect (
1354 cancellable,
1355 G_CALLBACK (caldav_chooser_try_password_cancelled_cb),
1356 g_object_ref (session),
1357 (GDestroyNotify) g_object_unref);
1358
1359 soup_session_send_message (session, message);
1360
1361 if (cancel_id > 0)
1362 g_cancellable_disconnect (cancellable, cancel_id);
1363
1364 if (caldav_chooser_check_successful (message, &local_error)) {
1365 result = E_SOURCE_AUTHENTICATION_ACCEPTED;
1366
1367 } else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) {
1368 result = E_SOURCE_AUTHENTICATION_REJECTED;
1369 g_clear_error (&local_error);
1370
1371 } else {
1372 result = E_SOURCE_AUTHENTICATION_ERROR;
1373 }
1374
1375 if (local_error != NULL)
1376 g_propagate_error (error, local_error);
1377
1378 g_object_unref (message);
1379 g_object_unref (session);
1380
1381 soup_uri_free (soup_uri);
1382
1383 return result;
1384 }
1385
1386 static void
1387 e_caldav_chooser_class_init (ECaldavChooserClass *class)
1388 {
1389 GObjectClass *object_class;
1390
1391 g_type_class_add_private (class, sizeof (ECaldavChooserPrivate));
1392
1393 object_class = G_OBJECT_CLASS (class);
1394 object_class->set_property = caldav_chooser_set_property;
1395 object_class->get_property = caldav_chooser_get_property;
1396 object_class->dispose = caldav_chooser_dispose;
1397 object_class->finalize = caldav_chooser_finalize;
1398 object_class->constructed = caldav_chooser_constructed;
1399
1400 g_object_class_install_property (
1401 object_class,
1402 PROP_REGISTRY,
1403 g_param_spec_object (
1404 "registry",
1405 "Registry",
1406 "Data source registry",
1407 E_TYPE_SOURCE_REGISTRY,
1408 G_PARAM_READWRITE |
1409 G_PARAM_CONSTRUCT_ONLY));
1410
1411 g_object_class_install_property (
1412 object_class,
1413 PROP_SOURCE,
1414 g_param_spec_object (
1415 "source",
1416 "Source",
1417 "CalDAV data source",
1418 E_TYPE_SOURCE,
1419 G_PARAM_READWRITE |
1420 G_PARAM_CONSTRUCT_ONLY));
1421
1422 g_object_class_install_property (
1423 object_class,
1424 PROP_SOURCE_TYPE,
1425 g_param_spec_enum (
1426 "source-type",
1427 "Source Type",
1428 "The iCalendar object type",
1429 E_TYPE_CAL_CLIENT_SOURCE_TYPE,
1430 E_CAL_CLIENT_SOURCE_TYPE_EVENTS,
1431 G_PARAM_READWRITE |
1432 G_PARAM_CONSTRUCT_ONLY));
1433 }
1434
1435 static void
1436 e_caldav_chooser_class_finalize (ECaldavChooserClass *class)
1437 {
1438 }
1439
1440 static void
1441 e_caldav_chooser_authenticator_init (ESourceAuthenticatorInterface *interface)
1442 {
1443 interface->try_password_sync = caldav_chooser_try_password_sync;
1444 }
1445
1446 static void
1447 e_caldav_chooser_init (ECaldavChooser *chooser)
1448 {
1449 chooser->priv = E_CALDAV_CHOOSER_GET_PRIVATE (chooser);
1450 }
1451
1452 void
1453 e_caldav_chooser_type_register (GTypeModule *type_module)
1454 {
1455 /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
1456 * function, so we have to wrap it with a public function in
1457 * order to register types from a separate compilation unit. */
1458 e_caldav_chooser_register_type (type_module);
1459 }
1460
1461 GtkWidget *
1462 e_caldav_chooser_new (ESourceRegistry *registry,
1463 ESource *source,
1464 ECalClientSourceType source_type)
1465 {
1466 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
1467 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
1468
1469 return g_object_new (
1470 E_TYPE_CALDAV_CHOOSER,
1471 "registry", registry, "source", source,
1472 "source-type", source_type, NULL);
1473 }
1474
1475 ESourceRegistry *
1476 e_caldav_chooser_get_registry (ECaldavChooser *chooser)
1477 {
1478 g_return_val_if_fail (E_IS_CALDAV_CHOOSER (chooser), NULL);
1479
1480 return chooser->priv->registry;
1481 }
1482
1483 ESource *
1484 e_caldav_chooser_get_source (ECaldavChooser *chooser)
1485 {
1486 g_return_val_if_fail (E_IS_CALDAV_CHOOSER (chooser), NULL);
1487
1488 return chooser->priv->source;
1489 }
1490
1491 ECalClientSourceType
1492 e_caldav_chooser_get_source_type (ECaldavChooser *chooser)
1493 {
1494 g_return_val_if_fail (E_IS_CALDAV_CHOOSER (chooser), 0);
1495
1496 return chooser->priv->source_type;
1497 }
1498
1499 void
1500 e_caldav_chooser_populate (ECaldavChooser *chooser,
1501 GCancellable *cancellable,
1502 GAsyncReadyCallback callback,
1503 gpointer user_data)
1504 {
1505 Context *context;
1506 ESource *source;
1507 SoupURI *soup_uri;
1508 SoupMessage *message;
1509 ESourceWebdav *extension;
1510 GtkTreeModel *tree_model;
1511 GSimpleAsyncResult *simple;
1512 const gchar *extension_name;
1513
1514 g_return_if_fail (E_IS_CALDAV_CHOOSER (chooser));
1515
1516 tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser));
1517 gtk_list_store_clear (GTK_LIST_STORE (tree_model));
1518 soup_session_abort (chooser->priv->session);
1519
1520 source = e_caldav_chooser_get_source (chooser);
1521 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
1522 extension = e_source_get_extension (source, extension_name);
1523
1524 soup_uri = e_source_webdav_dup_soup_uri (extension);
1525 g_return_if_fail (soup_uri != NULL);
1526
1527 context = context_new (chooser, cancellable);
1528
1529 simple = g_simple_async_result_new (
1530 G_OBJECT (chooser), callback,
1531 user_data, e_caldav_chooser_populate);
1532
1533 g_simple_async_result_set_op_res_gpointer (
1534 simple, context, (GDestroyNotify) context_free);
1535
1536 message = caldav_chooser_new_propfind (
1537 context->session, soup_uri, DEPTH_0,
1538 NS_WEBDAV, XC ("resourcetype"),
1539 NS_CALDAV, XC ("calendar-home-set"),
1540 NS_CALDAV, XC ("calendar-user-address-set"),
1541 NS_WEBDAV, XC ("current-user-principal"),
1542 NS_WEBDAV, XC ("principal-URL"),
1543 NULL);
1544
1545 /* This takes ownership of the message. */
1546 soup_session_queue_message (
1547 context->session, message, (SoupSessionCallback)
1548 caldav_chooser_calendar_home_set_cb, simple);
1549
1550 soup_uri_free (soup_uri);
1551 }
1552
1553 gboolean
1554 e_caldav_chooser_populate_finish (ECaldavChooser *chooser,
1555 GAsyncResult *result,
1556 GError **error)
1557 {
1558 GSimpleAsyncResult *simple;
1559 Context *context;
1560
1561 g_return_val_if_fail (
1562 g_simple_async_result_is_valid (
1563 result, G_OBJECT (chooser),
1564 e_caldav_chooser_populate), FALSE);
1565
1566 simple = G_SIMPLE_ASYNC_RESULT (result);
1567 context = g_simple_async_result_get_op_res_gpointer (simple);
1568
1569 if (g_simple_async_result_propagate_error (simple, error))
1570 return FALSE;
1571
1572 /* Transfer user addresses to the private struct. */
1573
1574 g_list_free_full (
1575 chooser->priv->user_address_set,
1576 (GDestroyNotify) g_free);
1577
1578 chooser->priv->user_address_set = context->user_address_set;
1579 context->user_address_set = NULL;
1580
1581 return TRUE;
1582 }
1583
1584 gboolean
1585 e_caldav_chooser_apply_selected (ECaldavChooser *chooser)
1586 {
1587 ESourceWebdav *webdav_extension;
1588 GtkTreeSelection *selection;
1589 GtkTreeModel *model;
1590 GtkTreeIter iter;
1591 ESource *source;
1592 GdkColor *color;
1593 gboolean has_color;
1594 gchar *display_name;
1595 gchar *path_encoded;
1596
1597 g_return_val_if_fail (E_IS_CALDAV_CHOOSER (chooser), FALSE);
1598
1599 source = e_caldav_chooser_get_source (chooser);
1600 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser));
1601
1602 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1603 return FALSE;
1604
1605 gtk_tree_model_get (
1606 model, &iter,
1607 COLUMN_DISPLAY_NAME, &display_name,
1608 COLUMN_PATH_ENCODED, &path_encoded,
1609 COLUMN_HAS_COLOR, &has_color,
1610 COLUMN_COLOR, &color,
1611 -1);
1612
1613 /* Sanity check. */
1614 g_warn_if_fail (
1615 (has_color && color != NULL) ||
1616 (!has_color && color == NULL));
1617
1618 webdav_extension = e_source_get_extension (
1619 source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
1620
1621 e_source_set_display_name (source, display_name);
1622
1623 e_source_webdav_set_display_name (webdav_extension, display_name);
1624 e_source_webdav_set_resource_path (webdav_extension, path_encoded);
1625
1626 /* XXX For now just pick the first user address in the list.
1627 * Might be better to compare the list against our own mail
1628 * accounts and give preference to matches (especially if an
1629 * address matches the default mail account), but I'm not sure
1630 * if multiple user addresses are common enough to justify the
1631 * extra effort. */
1632 if (chooser->priv->user_address_set != NULL)
1633 e_source_webdav_set_email_address (
1634 webdav_extension,
1635 chooser->priv->user_address_set->data);
1636
1637 if (has_color) {
1638 ESourceSelectable *selectable_extension;
1639 const gchar *extension_name;
1640 gchar *color_spec;
1641
1642 switch (e_caldav_chooser_get_source_type (chooser)) {
1643 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1644 extension_name = E_SOURCE_EXTENSION_CALENDAR;
1645 break;
1646 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1647 extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
1648 break;
1649 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1650 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
1651 break;
1652 default:
1653 g_return_val_if_reached (TRUE);
1654 }
1655
1656 selectable_extension =
1657 e_source_get_extension (source, extension_name);
1658
1659 color_spec = gdk_color_to_string (color);
1660 e_source_selectable_set_color (
1661 selectable_extension, color_spec);
1662 g_free (color_spec);
1663
1664 gdk_color_free (color);
1665 }
1666
1667 g_free (display_name);
1668 g_free (path_encoded);
1669
1670 return TRUE;
1671 }