Commit message: core: Validate SSL certificates for all connections

This prevents MitM attacks which use spoofed SSL certificates.

Note that this bumps our libsoup requirement to 2.37.91.

Closes: https://bugzilla.gnome.org/show_bug.cgi?id=671535
CVE: CVE-2012-1177
CWE: CWE-20
Vulnerability description: libgdata before 0.10.2 and 0.11.x before 0.11.1 does not validate SSL certificates, which allows remote attackers to obtain user names and passwords via a man-in-the-middle (MITM) attack with a spoofed certificate.

Project: libgdata

gdata_service_update_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error)
{
	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
	GDataEntry *entry;

	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	g_warn_if_fail (g_simple_async_result_get_source_tag (result) == gdata_service_update_entry_async);

	if (g_simple_async_result_propagate_error (result, error) == TRUE)
		return NULL;

	entry = g_simple_async_result_get_op_res_gpointer (result);
	if (entry != NULL)
		return g_object_ref (entry);

	g_assert_not_reached ();
}

Project: libgdata

_gdata_service_get_log_level (void)
{
	static int level = -1;

	if (level < 0) {
		const gchar *envvar = g_getenv ("LIBGDATA_DEBUG");
		if (envvar != NULL)
			level = atoi (envvar);
		level = MIN (MAX (level, 0), GDATA_LOG_FULL_UNREDACTED);
	}

	return level;
}

Project: libgdata

gdata_service_get_authorizer (GDataService *self)
{
	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);

	return self->priv->authorizer;
}

Project: libgdata

message_request_queued_cb (SoupSession *session, SoupMessage *message, MessageData *data)
{
	if (message == data->message) {
#if GLIB_CHECK_VERSION (2, 31, 0)
		g_mutex_unlock (&(data->mutex));
#else
		g_static_mutex_unlock (&(data->mutex));
#endif
	}
}

Project: libgdata

_gdata_service_actually_send_message (SoupSession *session, SoupMessage *message, GCancellable *cancellable, GError **error)
{
	MessageData data;
	gulong cancel_signal = 0, request_queued_signal = 0;

	/* Hold references to the session and message so they can't be freed by other threads. For example, if the SoupSession was freed by another
	 * thread while we were making a request, the request would be unexpectedly cancelled. See bgo#650835 for an example of this breaking things.
	 */
	g_object_ref (session);
	g_object_ref (message);

	/* Listen for cancellation */
	if (cancellable != NULL) {
#if GLIB_CHECK_VERSION (2, 31, 0)
		g_mutex_init (&(data.mutex));
#else
		g_static_mutex_init (&(data.mutex));
#endif
		data.session = session;
		data.message = message;

		cancel_signal = g_cancellable_connect (cancellable, (GCallback) message_cancel_cb, &data, NULL);
		request_queued_signal = g_signal_connect (session, "request-queued", (GCallback) message_request_queued_cb, &data);

		/* We lock this mutex until the message has been queued by the session (i.e. it's unlocked in the request-queued callback), and require
		 * the mutex to be held to cancel the message. Consequently, if the message is cancelled (in another thread) any time between this lock
		 * and the request being queued, the cancellation will wait until the request has been queued before taking effect.
		 * This is a little ugly, but is the only way I can think of to avoid a race condition between calling soup_session_cancel_message()
		 * and soup_session_send_message(), as the former doesn't have any effect until the request has been queued, and once the latter has
		 * returned, all network activity has been finished so cancellation is pointless. */
#if GLIB_CHECK_VERSION (2, 31, 0)
		g_mutex_lock (&(data.mutex));
#else
		g_static_mutex_lock (&(data.mutex));
#endif
	}

	/* Only send the message if it hasn't already been cancelled. There is no race condition here for the above reasons: if the cancellable has
	 * been cancelled, it's because it was cancelled before we called g_cancellable_connect().
	 *
	 * Otherwise, manually set the message's status code to SOUP_STATUS_CANCELLED, as the message was cancelled before even being queued to be
	 * sent. */
	if (cancellable == NULL || g_cancellable_is_cancelled (cancellable) == FALSE)
		soup_session_send_message (session, message);
	else
		soup_message_set_status (message, SOUP_STATUS_CANCELLED);

	/* Clean up the cancellation code */
	if (cancellable != NULL) {
		g_signal_handler_disconnect (session, request_queued_signal);

		if (cancel_signal != 0)
			g_cancellable_disconnect (cancellable, cancel_signal);

#if GLIB_CHECK_VERSION (2, 31, 0)
		g_mutex_clear (&(data.mutex));
#else
		g_static_mutex_free (&(data.mutex));
#endif
	}

	/* Set the cancellation error if applicable. We can't assume that our GCancellable has been cancelled just because the message has;
	 * libsoup may internally cancel messages if, for example, the proxy URI of the SoupSession is changed.
	 * libsoup also sometimes seems to return a SOUP_STATUS_IO_ERROR when we cancel a message, even though we've specified SOUP_STATUS_CANCELLED
	 * at cancellation time. Ho Hum. */
	g_assert (message->status_code != SOUP_STATUS_NONE);

	if (message->status_code == SOUP_STATUS_CANCELLED ||
	    (message->status_code == SOUP_STATUS_IO_ERROR && cancellable != NULL && g_cancellable_is_cancelled (cancellable) == TRUE)) {
		/* We hackily create and cancel a new GCancellable so that we can set the error using it and therefore save ourselves a translatable
		 * string and the associated maintenance. */
		GCancellable *error_cancellable = g_cancellable_new ();
		g_cancellable_cancel (error_cancellable);
		g_assert (g_cancellable_set_error_if_cancelled (error_cancellable, error) == TRUE);
		g_object_unref (error_cancellable);

		/* As per the above comment, force the status to be SOUP_STATUS_CANCELLED. */
		soup_message_set_status (message, SOUP_STATUS_CANCELLED);
	}

	/* Free things */
	g_object_unref (message);
	g_object_unref (session);
}

Project: libgdata

gdata_service_init (GDataService *self)
{
	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_SERVICE, GDataServicePrivate);
	self->priv->session = _gdata_service_build_session ();

	/* Debug log handling */
	g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, (GLogFunc) debug_handler, self);

	/* Proxy the SoupSession's proxy-uri and timeout properties */
	g_signal_connect (self->priv->session, "notify::proxy-uri", (GCallback) notify_proxy_uri_cb, self);
	g_signal_connect (self->priv->session, "notify::timeout", (GCallback) notify_timeout_cb, self);
}

Project: libgdata

gdata_service_delete_entry (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry, GCancellable *cancellable, GError **error)
{
	GDataLink *_link;
	SoupMessage *message;
	guint status;
	gchar *fixed_uri;

	g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE);
	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), FALSE);
	g_return_val_if_fail (GDATA_IS_ENTRY (entry), FALSE);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	/* Get the edit URI. We have to fix it to always use HTTPS as YouTube videos appear to incorrectly return a HTTP URI as their edit URI. */
	_link = gdata_entry_look_up_link (entry, GDATA_LINK_EDIT);
	g_assert (_link != NULL);

	fixed_uri = _gdata_service_fix_uri_scheme (gdata_link_get_uri (_link));
	message = _gdata_service_build_message (self, domain, SOUP_METHOD_DELETE, fixed_uri, gdata_entry_get_etag (entry), TRUE);
	g_free (fixed_uri);

	/* Send the message */
	status = _gdata_service_send_message (self, message, cancellable, error);

	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
		/* Redirect error or cancelled */
		g_object_unref (message);
		return FALSE;
	} else if (status != SOUP_STATUS_OK) {
		/* Error */
		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self);
		g_assert (klass->parse_error_response != NULL);
		klass->parse_error_response (self, GDATA_OPERATION_DELETION, status, message->reason_phrase, message->response_body->data,
		                             message->response_body->length, error);
		g_object_unref (message);
		return FALSE;
	}

	g_object_unref (message);

	return TRUE;
}

Project: libgdata

gdata_service_update_entry_async (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry,
                                  GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
{
	GSimpleAsyncResult *result;
	UpdateEntryAsyncData *data;

	g_return_if_fail (GDATA_IS_SERVICE (self));
	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
	g_return_if_fail (GDATA_IS_ENTRY (entry));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));

	data = g_slice_new (UpdateEntryAsyncData);
	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
	data->entry = g_object_ref (entry);

	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_update_entry_async);
	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) update_entry_async_data_free);
	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) update_entry_thread, G_PRIORITY_DEFAULT, cancellable);
	g_object_unref (result);
}

Project: libgdata

notify_proxy_uri_cb (GObject *gobject, GParamSpec *pspec, GObject *self)
{
	g_object_notify (self, "proxy-uri");
}

Project: libgdata

gdata_service_query_single_entry_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *entry_id, GDataQuery *query,
                                        GType entry_type, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
{
	GSimpleAsyncResult *result;
	QuerySingleEntryAsyncData *data;

	g_return_if_fail (GDATA_IS_SERVICE (self));
	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
	g_return_if_fail (entry_id != NULL);
	g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
	g_return_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE);
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
	g_return_if_fail (callback != NULL);

	data = g_slice_new (QuerySingleEntryAsyncData);
	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
	data->query = (query != NULL) ? g_object_ref (query) : NULL;
	data->entry_id = g_strdup (entry_id);
	data->entry_type = entry_type;

	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_query_single_entry_async);
	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) query_single_entry_async_data_free);
	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) query_single_entry_thread, G_PRIORITY_DEFAULT, cancellable);
	g_object_unref (result);
}

Project: libgdata

_gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query,
                      GCancellable *cancellable, GError **error)
{
	SoupMessage *message;
	guint status;
	const gchar *etag = NULL;

	/* Append the ETag header if possible */
	if (query != NULL)
		etag = gdata_query_get_etag (query);

	/* Build the message */
	if (query != NULL) {
		gchar *query_uri = gdata_query_get_query_uri (query, feed_uri);
		message = _gdata_service_build_message (self, domain, SOUP_METHOD_GET, query_uri, etag, FALSE);
		g_free (query_uri);
	} else {
		message = _gdata_service_build_message (self, domain, SOUP_METHOD_GET, feed_uri, etag, FALSE);
	}

	/* Note that cancellation only applies to network activity; not to the processing done afterwards */
	status = _gdata_service_send_message (self, message, cancellable, error);

	if (status == SOUP_STATUS_NOT_MODIFIED || status == SOUP_STATUS_CANCELLED) {
		/* Not modified (ETag has worked), or cancelled (in which case the error has been set) */
		g_object_unref (message);
		return NULL;
	} else if (status != SOUP_STATUS_OK) {
		/* Error */
		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self);
		g_assert (klass->parse_error_response != NULL);
		klass->parse_error_response (self, GDATA_OPERATION_QUERY, status, message->reason_phrase, message->response_body->data,
		                             message->response_body->length, error);
		g_object_unref (message);
		return NULL;
	}

	return message;
}

Project: libgdata

gdata_service_query_single_entry (GDataService *self, GDataAuthorizationDomain *domain, const gchar *entry_id, GDataQuery *query, GType entry_type,
                                  GCancellable *cancellable, GError **error)
{
	GDataEntryClass *klass;
	GDataEntry *entry;
	gchar *entry_uri;
	SoupMessage *message;

	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
	g_return_val_if_fail (entry_id != NULL, NULL);
	g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
	g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE, NULL);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	/* Query for just the specified entry */
	klass = GDATA_ENTRY_CLASS (g_type_class_ref (entry_type));
	g_assert (klass->get_entry_uri != NULL);

	entry_uri = klass->get_entry_uri (entry_id);
	message = _gdata_service_query (GDATA_SERVICE (self), domain, entry_uri, query, cancellable, error);
	g_free (entry_uri);

	if (message == NULL) {
		g_type_class_unref (klass);
		return NULL;
	}

	g_assert (message->response_body->data != NULL);
	entry = GDATA_ENTRY (gdata_parsable_new_from_xml (entry_type, message->response_body->data, message->response_body->length, error));
	g_object_unref (message);
	g_type_class_unref (klass);

	return entry;
}

Project: libgdata

__gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type,
                       GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error,
                       gboolean is_async)
{
	GDataServiceClass *klass;
	GDataFeed *feed;
	SoupMessage *message;

	message = _gdata_service_query (self, domain, feed_uri, query, cancellable, error);
	if (message == NULL)
		return NULL;

	g_assert (message->response_body->data != NULL);
	klass = GDATA_SERVICE_GET_CLASS (self);
	feed = _gdata_feed_new_from_xml (klass->feed_type, message->response_body->data, message->response_body->length, entry_type,
	                                 progress_callback, progress_user_data, is_async, error);
	g_object_unref (message);

	if (feed == NULL)
		return NULL;

	/* Update the query with the feed's ETag */
	if (query != NULL && feed != NULL && gdata_feed_get_etag (feed) != NULL)
		gdata_query_set_etag (query, gdata_feed_get_etag (feed));

	/* Update the query with the next and previous URIs from the feed */
	if (query != NULL && feed != NULL) {
		GDataLink *_link;

		_link = gdata_feed_look_up_link (feed, "next");
		if (_link != NULL)
			_gdata_query_set_next_uri (query, gdata_link_get_uri (_link));
		_link = gdata_feed_look_up_link (feed, "previous");
		if (_link != NULL)
			_gdata_query_set_previous_uri (query, gdata_link_get_uri (_link));
	}

	return feed;
}

Project: libgdata

_gdata_service_build_uri (const gchar *format, ...)
{
	const gchar *p;
	gchar *fixed_uri;
	GString *uri;
	va_list args;

	g_return_val_if_fail (format != NULL, NULL);

	/* Allocate a GString to build the URI in with at least as much space as the format string */
	uri = g_string_sized_new (strlen (format));

	/* Build the URI */
	va_start (args, format);

	for (p = format; *p != '\0'; p++) {
		if (*p != '%') {
			g_string_append_c (uri, *p);
			continue;
		}

		switch(*++p) {
			case 's':
				g_string_append_uri_escaped (uri, va_arg (args, gchar*), NULL, TRUE);
				break;
			case 'p':
				g_string_append (uri, va_arg (args, gchar*));
				break;
			case 'd':
				g_string_append_printf (uri, "%d", va_arg (args, gint));
				break;
			case '%':
				g_string_append_c (uri, '%');
				break;
			default:
				g_error ("Unrecognized format placeholder '%%%c' in format '%s'. This is a programmer error.", *p, format);
				break;
		}
	}

	va_end (args);

	/* Fix the scheme to always be HTTPS */
	fixed_uri = _gdata_service_fix_uri_scheme (uri->str);
	g_string_free (uri, TRUE);

	return fixed_uri;
}

Project: libgdata

gdata_service_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
	GDataServicePrivate *priv = GDATA_SERVICE (object)->priv;

	switch (property_id) {
		case PROP_PROXY_URI:
			g_value_set_boxed (value, gdata_service_get_proxy_uri (GDATA_SERVICE (object)));
			break;
		case PROP_TIMEOUT:
			g_value_set_uint (value, gdata_service_get_timeout (GDATA_SERVICE (object)));
			break;
		case PROP_LOCALE:
			g_value_set_string (value, priv->locale);
			break;
		case PROP_AUTHORIZER:
			g_value_set_object (value, priv->authorizer);
			break;
		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

Project: libgdata

gdata_service_insert_entry_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *upload_uri, GDataEntry *entry,
                                  GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
{
	GSimpleAsyncResult *result;
	InsertEntryAsyncData *data;

	g_return_if_fail (GDATA_IS_SERVICE (self));
	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
	g_return_if_fail (upload_uri != NULL);
	g_return_if_fail (GDATA_IS_ENTRY (entry));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));

	data = g_slice_new (InsertEntryAsyncData);
	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
	data->upload_uri = g_strdup (upload_uri);
	data->entry = g_object_ref (entry);

	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_insert_entry_async);
	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) insert_entry_async_data_free);
	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) insert_entry_thread, G_PRIORITY_DEFAULT, cancellable);
	g_object_unref (result);
}

Project: libgdata

delete_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *cancellable)
{
	gboolean success;
	GError *error = NULL;
	DeleteEntryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);

	/* Delete the entry and return */
	success = gdata_service_delete_entry (service, data->domain, data->entry, cancellable, &error);
	if (success == FALSE) {
		g_simple_async_result_set_from_error (result, error);
		g_error_free (error);
		return;
	}

	/* Replace the entry with the success value */
	g_simple_async_result_set_op_res_gboolean (result, success);
}

Project: libgdata

gdata_service_delete_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error)
{
	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);

	g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE);
	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	g_warn_if_fail (g_simple_async_result_get_source_tag (result) == gdata_service_delete_entry_async);

	if (g_simple_async_result_propagate_error (result, error) == TRUE)
		return FALSE;

	return g_simple_async_result_get_op_res_gboolean (result);
}

Project: libgdata

gdata_service_class_init (GDataServiceClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

	g_type_class_add_private (klass, sizeof (GDataServicePrivate));

	gobject_class->set_property = gdata_service_set_property;
	gobject_class->get_property = gdata_service_get_property;
	gobject_class->dispose = gdata_service_dispose;
	gobject_class->finalize = gdata_service_finalize;

	klass->api_version = "2";
	klass->feed_type = GDATA_TYPE_FEED;
	klass->append_query_headers = real_append_query_headers;
	klass->parse_error_response = real_parse_error_response;
	klass->get_authorization_domains = NULL; /* equivalent to returning an empty list of domains */

	/**
	 * GDataService:proxy-uri:
	 *
	 * The proxy URI used internally for all network requests.
	 *
	 * Note that if a #GDataAuthorizer is being used with this #GDataService, the authorizer might also need its proxy URI setting.
	 *
	 * Since: 0.2.0
	 **/
	g_object_class_install_property (gobject_class, PROP_PROXY_URI,
	                                 g_param_spec_boxed ("proxy-uri",
	                                                     "Proxy URI", "The proxy URI used internally for all network requests.",
	                                                     SOUP_TYPE_URI,
	                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	/**
	 * GDataService:timeout:
	 *
	 * A timeout, in seconds, for network operations. If the timeout is exceeded, the operation will be cancelled and
	 * %GDATA_SERVICE_ERROR_NETWORK_ERROR will be returned.
	 *
	 * If the timeout is <code class="literal">0</code>, operations will never time out.
	 *
	 * Note that if a #GDataAuthorizer is being used with this #GDataService, the authorizer might also need its timeout setting.
	 *
	 * Since: 0.7.0
	 **/
	g_object_class_install_property (gobject_class, PROP_TIMEOUT,
	                                 g_param_spec_uint ("timeout",
	                                                    "Timeout", "A timeout, in seconds, for network operations.",
	                                                    0, G_MAXUINT, 0,
	                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	/**
	 * GDataService:locale:
	 *
	 * The locale to use for network requests, in Unix locale format. (e.g. "en_GB", "cs", "de_DE".) Use %NULL for the default "C" locale
	 * (typically "en_US").
	 *
	 * Typically, this locale will be used by the server-side software to localise results, such as by translating category names, or by choosing
	 * geographically relevant search results. This will vary from service to service.
	 *
	 * The server-side behaviour is undefined if it doesn't support a given locale.
	 *
	 * Since: 0.7.0
	 **/
	g_object_class_install_property (gobject_class, PROP_LOCALE,
	                                 g_param_spec_string ("locale",
	                                                      "Locale", "The locale to use for network requests, in Unix locale format.",
	                                                      NULL,
	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	/**
	 * GDataService:authorizer:
	 *
	 * An object which implements #GDataAuthorizer. This should have previously been authenticated authorized against this service type (and
	 * potentially other service types). The service will use the authorizer to add an authorization token to each request it performs.
	 *
	 * Your application should call methods on the #GDataAuthorizer object itself in order to authenticate with the Google accounts service and
	 * authorize against this service type. See the documentation for the particular #GDataAuthorizer implementation being used for more details.
	 *
	 * The authorizer for a service can be changed at runtime for a different #GDataAuthorizer object or %NULL without affecting ongoing requests
	 * and operations.
	 *
	 * Note that it's only necessary to set an authorizer on the service if your application is going to make requests of the service which
	 * require authorization. For example, listing the current most popular videos on YouTube does not require authorization, but uploading a
	 * video to YouTube does. It's an unnecessary overhead to require the user to authorize against a service when not strictly required.
	 *
	 * Since: 0.9.0
	 **/
	g_object_class_install_property (gobject_class, PROP_AUTHORIZER,
	                                 g_param_spec_object ("authorizer",
	                                                      "Authorizer", "An authorizer object to provide an authorization token for each request.",
	                                                      GDATA_TYPE_AUTHORIZER,
	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

Project: libgdata

real_append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message)
{
	g_assert (message != NULL);

	/* Set the authorisation header */
	if (self->priv->authorizer != NULL) {
		gdata_authorizer_process_request (self->priv->authorizer, domain, message);

		if (domain != NULL) {
			/* Store the authorisation domain on the message so that we can access it again after refreshing authorisation if necessary.
			 * See _gdata_service_send_message(). */
			g_object_set_data_full (G_OBJECT (message), "gdata-authorization-domain", g_object_ref (domain),
			                        (GDestroyNotify) g_object_unref);
		}
	}

	/* Set the GData-Version header to tell it we want to use the v2 API */
	soup_message_headers_append (message->request_headers, "GData-Version", GDATA_SERVICE_GET_CLASS (self)->api_version);

	/* Set the locale, if it's been set for the service */
	if (self->priv->locale != NULL)
		soup_message_headers_append (message->request_headers, "Accept-Language", self->priv->locale);
}

Project: libgdata

gdata_service_dispose (GObject *object)
{
	GDataServicePrivate *priv = GDATA_SERVICE (object)->priv;

	if (priv->authorizer != NULL)
		g_object_unref (priv->authorizer);
	priv->authorizer = NULL;

	if (priv->session != NULL)
		g_object_unref (priv->session);
	priv->session = NULL;

	/* Chain up to the parent class */
	G_OBJECT_CLASS (gdata_service_parent_class)->dispose (object);
}

Project: libgdata

_gdata_service_secure_strdup (const gchar *str)
{
#ifdef HAVE_GNOME
	return gnome_keyring_memory_strdup (str);
#else /* if !HAVE_GNOME */
	return g_strdup (str);
#endif /* !HAVE_GNOME */
}

Project: libgdata

notify_timeout_cb (GObject *gobject, GParamSpec *pspec, GObject *self)
{
	g_object_notify (self, "timeout");
}

Project: libgdata

real_parse_error_response (GDataService *self, GDataOperationType operation_type, guint status, const gchar *reason_phrase,
                           const gchar *response_body, gint length, GError **error)
{
	/* We prefer to include the @response_body in the error message, but if it's empty, fall back to the @reason_phrase */
	if (response_body == NULL || *response_body == '\0')
		response_body = reason_phrase;

	/* See: http://code.google.com/apis/gdata/docs/2.0/reference.html#HTTPStatusCodes */
	switch (status) {
		case SOUP_STATUS_CANT_RESOLVE:
		case SOUP_STATUS_CANT_CONNECT:
		case SOUP_STATUS_SSL_FAILED:
		case SOUP_STATUS_IO_ERROR:
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NETWORK_ERROR,
			             _("Cannot connect to the service's server."));
			return;
		case SOUP_STATUS_CANT_RESOLVE_PROXY:
		case SOUP_STATUS_CANT_CONNECT_PROXY:
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROXY_ERROR,
			             _("Cannot connect to the proxy server."));
			return;
		case SOUP_STATUS_MALFORMED:
		case SOUP_STATUS_BAD_REQUEST: /* 400 */
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
			             /* Translators: the parameter is an error message returned by the server. */
			             _("Invalid request URI or header, or unsupported nonstandard parameter: %s"), response_body);
			return;
		case SOUP_STATUS_UNAUTHORIZED: /* 401 */
		case SOUP_STATUS_FORBIDDEN: /* 403 */
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
			             /* Translators: the parameter is an error message returned by the server. */
			             _("Authentication required: %s"), response_body);
			return;
		case SOUP_STATUS_NOT_FOUND: /* 404 */
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND,
			             /* Translators: the parameter is an error message returned by the server. */
			             _("The requested resource was not found: %s"), response_body);
			return;
		case SOUP_STATUS_CONFLICT: /* 409 */
		case SOUP_STATUS_PRECONDITION_FAILED: /* 412 */
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_CONFLICT,
			             /* Translators: the parameter is an error message returned by the server. */
			             _("The entry has been modified since it was downloaded: %s"), response_body);
			return;
		case SOUP_STATUS_INTERNAL_SERVER_ERROR: /* 500 */
		default:
			/* We'll fall back to generic errors, below */
			break;
	}

	/* If the error hasn't been handled already, throw a generic error */
	switch (operation_type) {
		case GDATA_OPERATION_AUTHENTICATION:
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
			             /* Translators: the first parameter is an HTTP status,
			              * and the second is an error message returned by the server. */
			             _("Error code %u when authenticating: %s"), status, response_body);
			break;
		case GDATA_OPERATION_QUERY:
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
			             /* Translators: the first parameter is an HTTP status,
			              * and the second is an error message returned by the server. */
			             _("Error code %u when querying: %s"), status, response_body);
			break;
		case GDATA_OPERATION_INSERTION:
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
			             /* Translators: the first parameter is an HTTP status,
			              * and the second is an error message returned by the server. */
			             _("Error code %u when inserting an entry: %s"), status, response_body);
			break;
		case GDATA_OPERATION_UPDATE:
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
			             /* Translators: the first parameter is an HTTP status,
			              * and the second is an error message returned by the server. */
			             _("Error code %u when updating an entry: %s"), status, response_body);
			break;
		case GDATA_OPERATION_DELETION:
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
			             /* Translators: the first parameter is an HTTP status,
			              * and the second is an error message returned by the server. */
			             _("Error code %u when deleting an entry: %s"), status, response_body);
			break;
		case GDATA_OPERATION_DOWNLOAD:
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
			             /* Translators: the first parameter is an HTTP status,
			              * and the second is an error message returned by the server. */
			             _("Error code %u when downloading: %s"), status, response_body);
			break;
		case GDATA_OPERATION_UPLOAD:
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
			             /* Translators: the first parameter is an HTTP status,
			              * and the second is an error message returned by the server. */
			             _("Error code %u when uploading: %s"), status, response_body);
			break;
		case GDATA_OPERATION_BATCH:
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_WITH_BATCH_OPERATION,
			             /* Translators: the first parameter is a HTTP status,
			              * and the second is an error message returned by the server. */
			             _("Error code %u when running a batch operation: %s"), status, response_body);
			break;
		default:
			/* We should not be called with anything other than the above operation types */
			g_assert_not_reached ();
	}
}

Project: libgdata

query_async_data_free (QueryAsyncData *self)
{
	if (self->domain != NULL)
		g_object_unref (self->domain);

	g_free (self->feed_uri);
	if (self->query)
		g_object_unref (self->query);
	if (self->feed)
		g_object_unref (self->feed);

	g_slice_free (QueryAsyncData, self);
}

Project: libgdata

gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry, GCancellable *cancellable, GError **error)
{
	GDataEntry *updated_entry;
	GDataLink *_link;
	SoupMessage *message;
	gchar *upload_data;
	guint status;

	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
	g_return_val_if_fail (GDATA_IS_ENTRY (entry), NULL);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	/* Get the edit URI */
	_link = gdata_entry_look_up_link (entry, GDATA_LINK_EDIT);
	g_assert (_link != NULL);
	message = _gdata_service_build_message (self, domain, SOUP_METHOD_PUT, gdata_link_get_uri (_link), gdata_entry_get_etag (entry), TRUE);

	/* Append the data */
	upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
	soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));

	/* Send the message */
	status = _gdata_service_send_message (self, message, cancellable, error);

	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
		/* Redirect error or cancelled */
		g_object_unref (message);
		return NULL;
	} else if (status != SOUP_STATUS_OK) {
		/* Error */
		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self);
		g_assert (klass->parse_error_response != NULL);
		klass->parse_error_response (self, GDATA_OPERATION_UPDATE, status, message->reason_phrase, message->response_body->data,
		                             message->response_body->length, error);
		g_object_unref (message);
		return NULL;
	}

	/* Parse the XML; create and return a new GDataEntry of the same type as @entry */
	g_assert (message->response_body->data != NULL);
	updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), message->response_body->data, message->response_body->length,
	                                                          error));
	g_object_unref (message);

	return updated_entry;
}

Project: libgdata

query_single_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *cancellable)
{
	GDataEntry *entry;
	GError *error = NULL;
	QuerySingleEntryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);

	/* Execute the query and return */
	entry = gdata_service_query_single_entry (service, data->domain, data->entry_id, data->query, data->entry_type, cancellable, &error);
	if (entry == NULL && error != NULL) {
		g_simple_async_result_set_from_error (result, error);
		g_error_free (error);
		return;
	}

	g_simple_async_result_set_op_res_gpointer (result, entry, (GDestroyNotify) g_object_unref);
}

Project: libgdata

gdata_service_set_authorizer (GDataService *self, GDataAuthorizer *authorizer)
{
	GDataServicePrivate *priv = self->priv;

	g_return_if_fail (GDATA_IS_SERVICE (self));
	g_return_if_fail (authorizer == NULL || GDATA_IS_AUTHORIZER (authorizer));

	if (priv->authorizer != NULL) {
		g_object_unref (priv->authorizer);
	}

	priv->authorizer = authorizer;

	if (priv->authorizer != NULL) {
		g_object_ref (priv->authorizer);
	}

	g_object_notify (G_OBJECT (self), "authorizer");
}

Project: libgdata

update_entry_async_data_free (UpdateEntryAsyncData *data)
{
	if (data->domain != NULL)
		g_object_unref (data->domain);

	if (data->entry != NULL)
		g_object_unref (data->entry);

	g_slice_free (UpdateEntryAsyncData, data);
}

Project: libgdata

gdata_service_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_PROXY_URI:
			gdata_service_set_proxy_uri (GDATA_SERVICE (object), g_value_get_boxed (value));
			break;
		case PROP_TIMEOUT:
			gdata_service_set_timeout (GDATA_SERVICE (object), g_value_get_uint (value));
			break;
		case PROP_LOCALE:
			gdata_service_set_locale (GDATA_SERVICE (object), g_value_get_string (value));
			break;
		case PROP_AUTHORIZER:
			gdata_service_set_authorizer (GDATA_SERVICE (object), g_value_get_object (value));
			break;
		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

Project: libgdata

gdata_service_delete_entry_async (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry,
                                  GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
{
	GSimpleAsyncResult *result;
	DeleteEntryAsyncData *data;

	g_return_if_fail (GDATA_IS_SERVICE (self));
	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
	g_return_if_fail (GDATA_IS_ENTRY (entry));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));

	data = g_slice_new (DeleteEntryAsyncData);
	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
	data->entry = g_object_ref (entry);

	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_delete_entry_async);
	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) delete_entry_async_data_free);
	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) delete_entry_thread, G_PRIORITY_DEFAULT, cancellable);
	g_object_unref (result);
}

Project: libgdata

gdata_service_set_timeout (GDataService *self, guint timeout)
{
	g_return_if_fail (GDATA_IS_SERVICE (self));
	g_object_set (self->priv->session, SOUP_SESSION_TIMEOUT, timeout, NULL);
	g_object_notify (G_OBJECT (self), "timeout");
}

Project: libgdata

gdata_service_insert_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error)
{
	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
	GDataEntry *entry;

	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	g_warn_if_fail (g_simple_async_result_get_source_tag (result) == gdata_service_insert_entry_async);

	if (g_simple_async_result_propagate_error (result, error) == TRUE)
		return NULL;

	entry = g_simple_async_result_get_op_res_gpointer (result);
	if (entry != NULL)
		return g_object_ref (entry);

	g_assert_not_reached ();
}

Project: libgdata

debug_handler (const char *log_domain, GLogLevelFlags log_level, const char *message, gpointer user_data)
{
	if (_gdata_service_get_log_level () != GDATA_LOG_NONE)
		g_log_default_handler (log_domain, log_level, message, NULL);
}

Project: libgdata

gdata_service_set_locale (GDataService *self, const gchar *locale)
{
	g_return_if_fail (GDATA_IS_SERVICE (self));

	g_free (self->priv->locale);
	self->priv->locale = g_strdup (locale);
	g_object_notify (G_OBJECT (self), "locale");
}

Project: libgdata

_gdata_service_fix_uri_scheme (const gchar *uri)
{
	g_return_val_if_fail (uri != NULL && *uri != '\0', NULL);

	/* Ensure we're using the correct scheme (HTTP or HTTPS) */
	if (g_str_has_prefix (uri, "https") == FALSE) {
		gchar *fixed_uri, **pieces;

		pieces = g_strsplit (uri, ":", 2);
		g_assert (pieces[0] != NULL && pieces[1] != NULL && pieces[2] == NULL);

		fixed_uri = g_strconcat ("https:", pieces[1], NULL);

		g_strfreev (pieces);

		return fixed_uri;
	}

	return g_strdup (uri);
}

Project: libgdata

gdata_service_get_authorization_domains (GType service_type)
{
	GDataServiceClass *klass;
	GList *domains = NULL;

	g_return_val_if_fail (g_type_is_a (service_type, GDATA_TYPE_SERVICE), NULL);

	klass = GDATA_SERVICE_CLASS (g_type_class_ref (service_type));
	if (klass->get_authorization_domains != NULL) {
		domains = klass->get_authorization_domains ();
	}
	g_type_class_unref (klass);

	return domains;
}

Project: libgdata

query_single_entry_async_data_free (QuerySingleEntryAsyncData *data)
{
	if (data->domain != NULL)
		g_object_unref (data->domain);

	g_free (data->entry_id);
	if (data->query != NULL)
		g_object_unref (data->query);
	g_slice_free (QuerySingleEntryAsyncData, data);
}

Project: libgdata

update_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *cancellable)
{
	GDataEntry *updated_entry;
	GError *error = NULL;
	UpdateEntryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);

	/* Update the entry and return */
	updated_entry = gdata_service_update_entry (service, data->domain, data->entry, cancellable, &error);
	if (updated_entry == NULL) {
		g_simple_async_result_set_from_error (result, error);
		g_error_free (error);
		return;
	}

	/* Swap the old entry with the new one */
	g_simple_async_result_set_op_res_gpointer (result, updated_entry, (GDestroyNotify) g_object_unref);
}

Project: libgdata

gdata_service_get_timeout (GDataService *self)
{
	guint timeout;

	g_return_val_if_fail (GDATA_IS_SERVICE (self), 0);

	g_object_get (self->priv->session, SOUP_SESSION_TIMEOUT, &timeout, NULL);

	return timeout;
}

Project: libgdata

_gdata_service_get_session (GDataService *self)
{
	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
	return self->priv->session;
}

Project: libgdata

insert_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *cancellable)
{
	GDataEntry *updated_entry;
	GError *error = NULL;
	InsertEntryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);

	/* Insert the entry and return */
	updated_entry = gdata_service_insert_entry (service, data->domain, data->upload_uri, data->entry, cancellable, &error);
	if (updated_entry == NULL) {
		g_simple_async_result_set_from_error (result, error);
		g_error_free (error);
		return;
	}

	/* Swap the old entry with the new one */
	g_simple_async_result_set_op_res_gpointer (result, updated_entry, (GDestroyNotify) g_object_unref);
}

Project: libgdata

_gdata_service_build_session (void)
{
	SoupSession *session = soup_session_sync_new_with_options (SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, NULL);

#ifdef HAVE_GNOME
	soup_session_add_feature_by_type (session, SOUP_TYPE_GNOME_FEATURES_2_26);
#endif /* HAVE_GNOME */

	/* Log all libsoup traffic if debugging's turned on */
	if (_gdata_service_get_log_level () > GDATA_LOG_MESSAGES) {
		SoupLoggerLogLevel level;
		SoupLogger *logger;

		switch (_gdata_service_get_log_level ()) {
			case GDATA_LOG_FULL_UNREDACTED:
			case GDATA_LOG_FULL:
				level = SOUP_LOGGER_LOG_BODY;
				break;
			case GDATA_LOG_HEADERS:
				level = SOUP_LOGGER_LOG_HEADERS;
				break;
			case GDATA_LOG_MESSAGES:
			case GDATA_LOG_NONE:
			default:
				g_assert_not_reached ();
		}

		logger = soup_logger_new (level, -1);
		soup_logger_set_printer (logger, (SoupLoggerPrinter) soup_log_printer, NULL, NULL);

		soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));

		g_object_unref (logger);
	}

	return session;
}

Project: libgdata

gdata_service_finalize (GObject *object)
{
	GDataServicePrivate *priv = GDATA_SERVICE (object)->priv;

	g_free (priv->locale);

	/* Chain up to the parent class */
	G_OBJECT_CLASS (gdata_service_parent_class)->finalize (object);
}

Project: libgdata

gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain, const gchar *upload_uri, GDataEntry *entry,
                            GCancellable *cancellable, GError **error)
{
	GDataEntry *updated_entry;
	SoupMessage *message;
	gchar *upload_data;
	guint status;

	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
	g_return_val_if_fail (upload_uri != NULL, NULL);
	g_return_val_if_fail (GDATA_IS_ENTRY (entry), NULL);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	if (gdata_entry_is_inserted (entry) == TRUE) {
		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
		                     _("The entry has already been inserted."));
		return NULL;
	}

	message = _gdata_service_build_message (self, domain, SOUP_METHOD_POST, upload_uri, NULL, FALSE);

	/* Append the data */
	upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
	soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));

	/* Send the message */
	status = _gdata_service_send_message (self, message, cancellable, error);

	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
		/* Redirect error or cancelled */
		g_object_unref (message);
		return NULL;
	} else if (status != SOUP_STATUS_CREATED) {
		/* Error */
		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self);
		g_assert (klass->parse_error_response != NULL);
		klass->parse_error_response (self, GDATA_OPERATION_INSERTION, status, message->reason_phrase, message->response_body->data,
		                             message->response_body->length, error);
		g_object_unref (message);
		return NULL;
	}

	/* Parse the XML; create and return a new GDataEntry of the same type as @entry */
	g_assert (message->response_body->data != NULL);
	updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), message->response_body->data, message->response_body->length,
	                                                          error));
	g_object_unref (message);

	return updated_entry;
}

Project: libgdata

gdata_service_query_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type,
                           GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
                           GDestroyNotify destroy_progress_user_data, GAsyncReadyCallback callback, gpointer user_data)
{
	GSimpleAsyncResult *result;
	QueryAsyncData *data;

	g_return_if_fail (GDATA_IS_SERVICE (self));
	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
	g_return_if_fail (feed_uri != NULL);
	g_return_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
	g_return_if_fail (callback != NULL);

	data = g_slice_new (QueryAsyncData);
	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
	data->feed_uri = g_strdup (feed_uri);
	data->query = (query != NULL) ? g_object_ref (query) : NULL;
	data->entry_type = entry_type;
	data->feed = NULL;
	data->progress_callback = progress_callback;
	data->progress_user_data = progress_user_data;
	data->destroy_progress_user_data = destroy_progress_user_data;

	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_query_async);
	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) query_async_data_free);
	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) query_thread, G_PRIORITY_DEFAULT, cancellable);
	g_object_unref (result);
}

Project: libgdata

insert_entry_async_data_free (InsertEntryAsyncData *self)
{
	if (self->domain != NULL)
		g_object_unref (self->domain);

	g_free (self->upload_uri);
	if (self->entry)
		g_object_unref (self->entry);

	g_slice_free (InsertEntryAsyncData, self);
}

Project: libgdata

gdata_service_query_finish (GDataService *self, GAsyncResult *async_result, GError **error)
{
	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
	QueryAsyncData *data;

	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	g_warn_if_fail (g_simple_async_result_get_source_tag (result) == gdata_service_query_async);

	if (g_simple_async_result_propagate_error (result, error) == TRUE)
		return NULL;

	data = g_simple_async_result_get_op_res_gpointer (result);
	if (data->feed != NULL)
		return g_object_ref (data->feed);
	return NULL;
}

Project: libgdata

gdata_service_get_proxy_uri (GDataService *self)
{
	SoupURI *proxy_uri;

	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);

	g_object_get (self->priv->session, SOUP_SESSION_PROXY_URI, &proxy_uri, NULL);
	g_object_unref (proxy_uri); /* remove the ref added by g_object_get */

	return proxy_uri;
}

Project: libgdata

_gdata_service_secure_strfree (GDataSecureString str)
{
#ifdef HAVE_GNOME
	gnome_keyring_memory_free (str);
#else /* if !HAVE_GNOME */
	/* Poor man's approximation to non-pageable memory: the best we can do is ensure that we don't leak it in free memory.
	 * This can't guarantee that it hasn't hit disk at some point, but does mean it can't hit disk in future. */
	if (str != NULL) {
		memset (str, 0, strlen (str));
	}

	g_free (str);
#endif /* !HAVE_GNOME */
}

Project: libgdata

_gdata_service_send_message (GDataService *self, SoupMessage *message, GCancellable *cancellable, GError **error)
{
	/* Based on code from evolution-data-server's libgdata:
	 *  Ebby Wiselyn <ebbywiselyn@gmail.com>
	 *  Jason Willis <zenbrother@gmail.com>
	 *
	 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
	 */

	soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT);
	_gdata_service_actually_send_message (self->priv->session, message, cancellable, error);
	soup_message_set_flags (message, 0);

	/* Handle redirections specially so we don't lose our custom headers when making the second request */
	if (SOUP_STATUS_IS_REDIRECTION (message->status_code)) {
		SoupURI *new_uri;
		const gchar *new_location;

		new_location = soup_message_headers_get_one (message->response_headers, "Location");
		g_return_val_if_fail (new_location != NULL, SOUP_STATUS_NONE);

		new_uri = soup_uri_new_with_base (soup_message_get_uri (message), new_location);
		if (new_uri == NULL) {
			gchar *uri_string = soup_uri_to_string (new_uri, FALSE);
			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
			             /* Translators: the parameter is the URI which is invalid. */
			             _("Invalid redirect URI: %s"), uri_string);
			g_free (uri_string);
			return SOUP_STATUS_NONE;
		}

		soup_message_set_uri (message, new_uri);
		soup_uri_free (new_uri);

		/* Send the message again */
		_gdata_service_actually_send_message (self->priv->session, message, cancellable, error);
	}

	/* Not authorised, or authorisation has expired. If we were authorised in the first place, attempt to refresh the authorisation and
	 * try sending the message again (but only once, so we don't get caught in an infinite loop of denied authorisation errors).
	 *
	 * Note that we have to re-process the message with the authoriser so that its authorisation headers get updated after the refresh
	 * (bgo#653535). */
	if (message->status_code == SOUP_STATUS_UNAUTHORIZED) {
		GDataAuthorizer *authorizer = self->priv->authorizer;

		if (authorizer != NULL && gdata_authorizer_refresh_authorization (authorizer, cancellable, NULL) == TRUE) {
			GDataAuthorizationDomain *domain;

			/* Re-process the request */
			domain = g_object_get_data (G_OBJECT (message), "gdata-authorization-domain");
			g_assert (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));

			gdata_authorizer_process_request (authorizer, domain, message);

			/* Send the message again */
			g_clear_error (error);
			_gdata_service_actually_send_message (self->priv->session, message, cancellable, error);
		}
	}

	return message->status_code;
}

Project: libgdata

query_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *cancellable)
{
	GError *error = NULL;
	QueryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);

	/* Execute the query and return */
	data->feed = __gdata_service_query (service, data->domain, data->feed_uri, data->query, data->entry_type, cancellable,
	                                    data->progress_callback, data->progress_user_data, &error, TRUE);
	if (data->feed == NULL && error != NULL) {
		g_simple_async_result_set_from_error (result, error);
		g_error_free (error);
	}

	if (data->destroy_progress_user_data != NULL) {
		data->destroy_progress_user_data (data->progress_user_data);
	}
}

Project: libgdata

_gdata_service_build_message (GDataService *self, GDataAuthorizationDomain *domain, const gchar *method, const gchar *uri,
                              const gchar *etag, gboolean etag_if_match)
{
	SoupMessage *message;
	GDataServiceClass *klass;

	/* Create the message */
	message = soup_message_new (method, uri);

	/* Make sure subclasses set their headers */
	klass = GDATA_SERVICE_GET_CLASS (self);
	if (klass->append_query_headers != NULL)
		klass->append_query_headers (self, domain, message);

	/* Append the ETag header if possible */
	if (etag != NULL)
		soup_message_headers_append (message->request_headers, (etag_if_match == TRUE) ? "If-Match" : "If-None-Match", etag);

	return message;
}

Project: libgdata

gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type,
                     GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
{
	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
	g_return_val_if_fail (feed_uri != NULL, NULL);
	g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY), NULL);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	return __gdata_service_query (self, domain, feed_uri, query, entry_type, cancellable, progress_callback, progress_user_data, error, FALSE);
}

Project: libgdata

gdata_service_set_proxy_uri (GDataService *self, SoupURI *proxy_uri)
{
	g_return_if_fail (GDATA_IS_SERVICE (self));
	g_object_set (self->priv->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
	g_object_notify (G_OBJECT (self), "proxy-uri");
}

Project: libgdata

soup_log_printer (SoupLogger *logger, SoupLoggerLogLevel level, char direction, const char *data, gpointer user_data)
{
	gboolean filter_data;
	gchar *_data = NULL;

	filter_data = (_gdata_service_get_log_level () > GDATA_LOG_NONE && _gdata_service_get_log_level () < GDATA_LOG_FULL_UNREDACTED) ? TRUE : FALSE;

	if (filter_data == TRUE) {
		/* Filter out lines which look like they might contain usernames, passwords or auth. tokens. */
		if (direction == '>' && g_str_has_prefix (data, "Authorization: GoogleLogin ") == TRUE) {
			_data = g_strdup ("Authorization: GoogleLogin <redacted>");
		} else if (direction == '>' && g_str_has_prefix (data, "Authorization: OAuth ") == TRUE) {
			_data = g_strdup ("Authorization: OAuth <redacted>");
		} else if (direction == '<' && g_str_has_prefix (data, "Set-Cookie: ") == TRUE) {
			_data = g_strdup ("Set-Cookie: <redacted>");
		} else if (direction == '<' && g_str_has_prefix (data, "Location: ") == TRUE) {
			/* Looks like:
			 * "Location: https://www.google.com/calendar/feeds/default/owncalendars/full?gsessionid=sBjmp05m5i67exYA51XjDA". */
			SoupURI *uri;
			gchar *_uri;
			GHashTable *params;

			uri = soup_uri_new (data + strlen ("Location: "));

			if (uri->query != NULL) {
				params = soup_form_decode (uri->query);

				/* strdup()s are necessary because the hash table's set up to free keys. */
				if (g_hash_table_lookup (params, "gsessionid") != NULL) {
					g_hash_table_insert (params, (gpointer) g_strdup ("gsessionid"), (gpointer) "<redacted>");
				}

				soup_uri_set_query_from_form (uri, params);
				g_hash_table_destroy (params);
			}

			_uri = soup_uri_to_string (uri, FALSE);
			_data = g_strconcat ("Location: ", _uri, NULL);
			g_free (_uri);

			soup_uri_free (uri);
		} else if (direction == '<' && g_str_has_prefix (data, "SID=") == TRUE) {
			_data = g_strdup ("SID=<redacted>");
		} else if (direction == '<' && g_str_has_prefix (data, "LSID=") == TRUE) {
			_data = g_strdup ("LSID=<redacted>");
		} else if (direction == '<' && g_str_has_prefix (data, "Auth=") == TRUE) {
			_data = g_strdup ("Auth=<redacted>");
		} else if (direction == '>' && g_str_has_prefix (data, "accountType=") == TRUE) {
			/* Looks like: "> accountType=HOSTED%5FOR%5FGOOGLE&Email=[e-mail address]&Passwd=[plaintex password]"
			               "&service=[service name]&source=ytapi%2DGNOME%2Dlibgdata%2D444fubtt%2D0". */
			GHashTable *params = soup_form_decode (data);

			/* strdup()s are necessary because the hash table's set up to free keys. */
			if (g_hash_table_lookup (params, "Email") != NULL) {
				g_hash_table_insert (params, (gpointer) g_strdup ("Email"), (gpointer) "<redacted>");
			}
			if (g_hash_table_lookup (params, "Passwd") != NULL) {
				g_hash_table_insert (params, (gpointer) g_strdup ("Passwd"), (gpointer) "<redacted>");
			}

			_data = soup_form_encode_hash (params);

			g_hash_table_destroy (params);
		} else if (direction == '<' && g_str_has_prefix (data, "oauth_token=") == TRUE) {
			/* Looks like: "< oauth_token=4%2FI-WU7sBzKk5GhGlQUF8a_TCZRnb7&oauth_token_secret=qTTTJg3no25auiiWFerzjW4I"
			               "&oauth_callback_confirmed=true". */
			GHashTable *params = soup_form_decode (data);

			/* strdup()s are necessary because the hash table's set up to free keys. */
			if (g_hash_table_lookup (params, "oauth_token") != NULL) {
				g_hash_table_insert (params, (gpointer) g_strdup ("oauth_token"), (gpointer) "<redacted>");
			}
			if (g_hash_table_lookup (params, "oauth_token_secret") != NULL) {
				g_hash_table_insert (params, (gpointer) g_strdup ("oauth_token_secret"), (gpointer) "<redacted>");
			}

			_data = soup_form_encode_hash (params);

			g_hash_table_destroy (params);
		} else {
			/* Nothing to redact. */
			_data = g_strdup (data);
		}
	} else {
		/* Don't dupe the string. */
		_data = (gchar*) data;
	}

	/* Log the data. */
	g_debug ("%c %s", direction, _data);

	if (filter_data == TRUE) {
		g_free (_data);
	}
}

Project: libgdata

_gdata_service_secure_strndup (const gchar *str, gsize n_bytes)
{
#ifdef HAVE_GNOME
	gsize str_len;
	GDataSecureString duped_str;

	if (str == NULL) {
		return NULL;
	}

	str_len = MIN (strlen (str), n_bytes);
	duped_str = (GDataSecureString) gnome_keyring_memory_alloc (str_len + 1);
	strncpy (duped_str, str, str_len);
	*(duped_str + str_len) = '\0';

	return duped_str;
#else /* if !HAVE_GNOME */
	return g_strndup (str, n_bytes);
#endif /* !HAVE_GNOME */
}

Project: libgdata

gdata_service_query_single_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error)
{
	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
	GDataEntry *entry;

	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	g_warn_if_fail (g_simple_async_result_get_source_tag (result) == gdata_service_query_single_entry_async);

	if (g_simple_async_result_propagate_error (result, error) == TRUE)
		return NULL;

	entry = g_simple_async_result_get_op_res_gpointer (result);
	if (entry != NULL)
		return g_object_ref (entry);
	return NULL;
}

Project: libgdata

gdata_service_get_locale (GDataService *self)
{
	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
	return self->priv->locale;
}

Project: libgdata

delete_entry_async_data_free (DeleteEntryAsyncData *data)
{
	if (data->domain != NULL)
		g_object_unref (data->domain);

	if (data->entry != NULL)
		g_object_unref (data->entry);

	g_slice_free (DeleteEntryAsyncData, data);
}

Project: libgdata

_gdata_service_get_scheme (void)
{
	return "https";
}

Project: libgdata

message_cancel_cb (GCancellable *cancellable, MessageData *data)
{
#if GLIB_CHECK_VERSION (2, 31, 0)
	g_mutex_lock (&(data->mutex));
	soup_session_cancel_message (data->session, data->message, SOUP_STATUS_CANCELLED);
	g_mutex_unlock (&(data->mutex));
#else
	g_static_mutex_lock (&(data->mutex));
	soup_session_cancel_message (data->session, data->message, SOUP_STATUS_CANCELLED);
	g_static_mutex_unlock (&(data->mutex));
#endif
}

Project: libgdata

gdata_service_error_quark (void)
{
	return g_quark_from_static_string ("gdata-service-error-quark");
}

Project: libgdata

gdata_service_is_authorized (GDataService *self)
{
	GList *domains, *i;
	gboolean authorised = TRUE;

	g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE);

	/* If we don't have an authoriser set, we can't be authorised */
	if (self->priv->authorizer == NULL) {
		return FALSE;
	}

	domains = gdata_service_get_authorization_domains (G_OBJECT_TYPE (self));

	/* Find any domains which we're not authorised for */
	for (i = domains; i != NULL; i = i->next) {
		if (gdata_authorizer_is_authorized_for_domain (self->priv->authorizer, GDATA_AUTHORIZATION_DOMAIN (i->data)) == FALSE) {
			authorised = FALSE;
			break;
		}
	}

	g_list_free (domains);

	return authorised;
}