evolution-3.6.4/modules/cal-config-caldav/e-caldav-chooser.c

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 }