1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * GData Client
4  * Copyright (C) Philip Withnall 2008, 2009, 2010, 2014 <philip@tecnocode.co.uk>
5  *
6  * GData Client is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * GData Client is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /**
21  * SECTION:gdata-service
22  * @short_description: GData service object
23  * @stability: Stable
24  * @include: gdata/gdata-service.h
25  *
26  * #GDataService represents a GData API service, typically a website using the GData API, such as YouTube or Google Calendar. One
27  * #GDataService instance is required to issue queries to the service, handle insertions, updates and deletions, and generally
28  * communicate with the online service.
29  *
30  * If operations performed on a #GDataService need authorization (such as uploading a video to YouTube or querying the user's personal calendar on
31  * Google Calendar), the service needs a #GDataAuthorizer instance set as #GDataService:authorizer. Once the user is appropriately authenticated and
32  * authorized by the #GDataAuthorizer implementation (see the documentation for #GDataAuthorizer for details on how this is achieved for specific
33  * implementations), all operations will be automatically authorized.
34  *
35  * Note that it's not always necessary to supply a #GDataAuthorizer instance to a #GDataService. If the only operations to be performed on the
36  * #GDataService don't need authorization (e.g. they only query public information), setting up a #GDataAuthorizer is just extra overhead. See the
37  * documentation for the operations on individual #GDataService subclasses to see which need authorization and which don't.
38  */
39 
40 #include <config.h>
41 #include <glib.h>
42 #include <glib/gi18n-lib.h>
43 #include <libsoup/soup.h>
44 #include <string.h>
45 #include <stdarg.h>
46 
47 #ifdef HAVE_GNOME
48 #define GCR_API_SUBJECT_TO_CHANGE
49 #include <gcr/gcr-base.h>
50 #endif /* HAVE_GNOME */
51 
52 #include "gdata-service.h"
53 #include "gdata-private.h"
54 #include "gdata-client-login-authorizer.h"
55 #include "gdata-marshal.h"
56 #include "gdata-types.h"
57 
58 GQuark
gdata_service_error_quark(void)59 gdata_service_error_quark (void)
60 {
61 	return g_quark_from_static_string ("gdata-service-error-quark");
62 }
63 
64 static void gdata_service_dispose (GObject *object);
65 static void gdata_service_finalize (GObject *object);
66 static void gdata_service_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
67 static void gdata_service_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
68 static void real_append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message);
69 static void real_parse_error_response (GDataService *self, GDataOperationType operation_type, guint status, const gchar *reason_phrase,
70                                        const gchar *response_body, gint length, GError **error);
71 static GDataFeed *
72 real_parse_feed (GDataService *self,
73                  GDataAuthorizationDomain *domain,
74                  GDataQuery *query,
75                  GType entry_type,
76                  SoupMessage *message,
77                  GCancellable *cancellable,
78                  GDataQueryProgressCallback progress_callback,
79                  gpointer progress_user_data,
80                  GError **error);
81 static void notify_proxy_uri_cb (GObject *gobject, GParamSpec *pspec, GObject *self);
82 static void notify_timeout_cb (GObject *gobject, GParamSpec *pspec, GObject *self);
83 static void debug_handler (const char *log_domain, GLogLevelFlags log_level, const char *message, gpointer user_data);
84 static void soup_log_printer (SoupLogger *logger, SoupLoggerLogLevel level, char direction, const char *data, gpointer user_data);
85 
86 static GDataFeed *__gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query,
87                                          GType entry_type, GCancellable *cancellable, GDataQueryProgressCallback progress_callback,
88                                          gpointer progress_user_data, GError **error);
89 
90 static SoupURI *_get_proxy_uri (GDataService *self);
91 static void _set_proxy_uri (GDataService *self, SoupURI *proxy_uri);
92 
93 struct _GDataServicePrivate {
94 	SoupSession *session;
95 	gchar *locale;
96 	GDataAuthorizer *authorizer;
97 	GProxyResolver *proxy_resolver;
98 };
99 
100 enum {
101 	PROP_PROXY_URI = 1,
102 	PROP_TIMEOUT,
103 	PROP_LOCALE,
104 	PROP_AUTHORIZER,
105 	PROP_PROXY_RESOLVER,
106 };
107 
G_DEFINE_TYPE(GDataService,gdata_service,G_TYPE_OBJECT)108 G_DEFINE_TYPE (GDataService, gdata_service, G_TYPE_OBJECT)
109 
110 static void
111 gdata_service_class_init (GDataServiceClass *klass)
112 {
113 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
114 
115 	g_type_class_add_private (klass, sizeof (GDataServicePrivate));
116 
117 	gobject_class->set_property = gdata_service_set_property;
118 	gobject_class->get_property = gdata_service_get_property;
119 	gobject_class->dispose = gdata_service_dispose;
120 	gobject_class->finalize = gdata_service_finalize;
121 
122 	klass->api_version = "2";
123 	klass->feed_type = GDATA_TYPE_FEED;
124 	klass->append_query_headers = real_append_query_headers;
125 	klass->parse_error_response = real_parse_error_response;
126 	klass->parse_feed = real_parse_feed;
127 	klass->get_authorization_domains = NULL; /* equivalent to returning an empty list of domains */
128 
129 	/**
130 	 * GDataService:proxy-uri:
131 	 *
132 	 * The proxy URI used internally for all network requests.
133 	 *
134 	 * Note that if a #GDataAuthorizer is being used with this #GDataService, the authorizer might also need its proxy URI setting.
135 	 *
136 	 * Since: 0.2.0
137 	 * Deprecated: 0.15.0: Use #GDataService:proxy-resolver instead, which gives more flexibility over the proxy used.
138 	 */
139 	g_object_class_install_property (gobject_class, PROP_PROXY_URI,
140 	                                 g_param_spec_boxed ("proxy-uri",
141 	                                                     "Proxy URI", "The proxy URI used internally for all network requests.",
142 	                                                     SOUP_TYPE_URI,
143 	                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
144 
145 	/**
146 	 * GDataService:timeout:
147 	 *
148 	 * A timeout, in seconds, for network operations. If the timeout is exceeded, the operation will be cancelled and
149 	 * %GDATA_SERVICE_ERROR_NETWORK_ERROR will be returned.
150 	 *
151 	 * If the timeout is <code class="literal">0</code>, operations will never time out.
152 	 *
153 	 * Note that if a #GDataAuthorizer is being used with this #GDataService, the authorizer might also need its timeout setting.
154 	 *
155 	 * Since: 0.7.0
156 	 */
157 	g_object_class_install_property (gobject_class, PROP_TIMEOUT,
158 	                                 g_param_spec_uint ("timeout",
159 	                                                    "Timeout", "A timeout, in seconds, for network operations.",
160 	                                                    0, G_MAXUINT, 0,
161 	                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
162 
163 	/**
164 	 * GDataService:locale:
165 	 *
166 	 * 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
167 	 * (typically "en_US").
168 	 *
169 	 * Typically, this locale will be used by the server-side software to localise results, such as by translating category names, or by choosing
170 	 * geographically relevant search results. This will vary from service to service.
171 	 *
172 	 * The server-side behaviour is undefined if it doesn't support a given locale.
173 	 *
174 	 * Since: 0.7.0
175 	 */
176 	g_object_class_install_property (gobject_class, PROP_LOCALE,
177 	                                 g_param_spec_string ("locale",
178 	                                                      "Locale", "The locale to use for network requests, in Unix locale format.",
179 	                                                      NULL,
180 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
181 
182 	/**
183 	 * GDataService:authorizer:
184 	 *
185 	 * An object which implements #GDataAuthorizer. This should have previously been authenticated authorized against this service type (and
186 	 * potentially other service types). The service will use the authorizer to add an authorization token to each request it performs.
187 	 *
188 	 * Your application should call methods on the #GDataAuthorizer object itself in order to authenticate with the Google accounts service and
189 	 * authorize against this service type. See the documentation for the particular #GDataAuthorizer implementation being used for more details.
190 	 *
191 	 * The authorizer for a service can be changed at runtime for a different #GDataAuthorizer object or %NULL without affecting ongoing requests
192 	 * and operations.
193 	 *
194 	 * 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
195 	 * require authorization. For example, listing the current most popular videos on YouTube does not require authorization, but uploading a
196 	 * video to YouTube does. It's an unnecessary overhead to require the user to authorize against a service when not strictly required.
197 	 *
198 	 * Since: 0.9.0
199 	 */
200 	g_object_class_install_property (gobject_class, PROP_AUTHORIZER,
201 	                                 g_param_spec_object ("authorizer",
202 	                                                      "Authorizer", "An authorizer object to provide an authorization token for each request.",
203 	                                                      GDATA_TYPE_AUTHORIZER,
204 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
205 
206 	/**
207 	 * GDataService:proxy-resolver:
208 	 *
209 	 * The #GProxyResolver used to determine a proxy URI.  Setting this will clear the #GDataService:proxy-uri property.
210 	 *
211 	 * Since: 0.15.0
212 	 */
213 	g_object_class_install_property (gobject_class, PROP_PROXY_RESOLVER,
214 	                                 g_param_spec_object ("proxy-resolver",
215 	                                                      "Proxy Resolver", "A GProxyResolver used to determine a proxy URI.",
216 	                                                      G_TYPE_PROXY_RESOLVER,
217 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
218 }
219 
220 static void
gdata_service_init(GDataService * self)221 gdata_service_init (GDataService *self)
222 {
223 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_SERVICE, GDataServicePrivate);
224 	self->priv->session = _gdata_service_build_session ();
225 
226 	/* Log handling for all message types except debug */
227 	g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_ERROR | G_LOG_LEVEL_INFO | G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_WARNING, (GLogFunc) debug_handler, self);
228 
229 	/* Proxy the SoupSession's proxy-uri and timeout properties */
230 	g_signal_connect (self->priv->session, "notify::proxy-uri", (GCallback) notify_proxy_uri_cb, self);
231 	g_signal_connect (self->priv->session, "notify::timeout", (GCallback) notify_timeout_cb, self);
232 
233 	/* Keep our GProxyResolver synchronized with SoupSession's. */
234 	g_object_bind_property (self->priv->session, "proxy-resolver", self, "proxy-resolver", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
235 }
236 
237 static void
gdata_service_dispose(GObject * object)238 gdata_service_dispose (GObject *object)
239 {
240 	GDataServicePrivate *priv = GDATA_SERVICE (object)->priv;
241 
242 	if (priv->authorizer != NULL)
243 		g_object_unref (priv->authorizer);
244 	priv->authorizer = NULL;
245 
246 	if (priv->session != NULL)
247 		g_object_unref (priv->session);
248 	priv->session = NULL;
249 
250 	g_clear_object (&priv->proxy_resolver);
251 
252 	/* Chain up to the parent class */
253 	G_OBJECT_CLASS (gdata_service_parent_class)->dispose (object);
254 }
255 
256 static void
gdata_service_finalize(GObject * object)257 gdata_service_finalize (GObject *object)
258 {
259 	GDataServicePrivate *priv = GDATA_SERVICE (object)->priv;
260 
261 	g_free (priv->locale);
262 
263 	/* Chain up to the parent class */
264 	G_OBJECT_CLASS (gdata_service_parent_class)->finalize (object);
265 }
266 
267 static void
gdata_service_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)268 gdata_service_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
269 {
270 	GDataServicePrivate *priv = GDATA_SERVICE (object)->priv;
271 
272 	switch (property_id) {
273 		case PROP_PROXY_URI:
274 			g_value_set_boxed (value, _get_proxy_uri (GDATA_SERVICE (object)));
275 			break;
276 		case PROP_TIMEOUT:
277 			g_value_set_uint (value, gdata_service_get_timeout (GDATA_SERVICE (object)));
278 			break;
279 		case PROP_LOCALE:
280 			g_value_set_string (value, priv->locale);
281 			break;
282 		case PROP_AUTHORIZER:
283 			g_value_set_object (value, priv->authorizer);
284 			break;
285 		case PROP_PROXY_RESOLVER:
286 			g_value_set_object (value, priv->proxy_resolver);
287 			break;
288 		default:
289 			/* We don't have any other property... */
290 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
291 			break;
292 	}
293 }
294 
295 static void
gdata_service_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)296 gdata_service_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
297 {
298 	switch (property_id) {
299 		case PROP_PROXY_URI:
300 			_set_proxy_uri (GDATA_SERVICE (object), g_value_get_boxed (value));
301 			break;
302 		case PROP_TIMEOUT:
303 			gdata_service_set_timeout (GDATA_SERVICE (object), g_value_get_uint (value));
304 			break;
305 		case PROP_LOCALE:
306 			gdata_service_set_locale (GDATA_SERVICE (object), g_value_get_string (value));
307 			break;
308 		case PROP_AUTHORIZER:
309 			gdata_service_set_authorizer (GDATA_SERVICE (object), g_value_get_object (value));
310 			break;
311 		case PROP_PROXY_RESOLVER:
312 			gdata_service_set_proxy_resolver (GDATA_SERVICE (object), g_value_get_object (value));
313 			break;
314 		default:
315 			/* We don't have any other property... */
316 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
317 			break;
318 	}
319 }
320 
321 static void
real_append_query_headers(GDataService * self,GDataAuthorizationDomain * domain,SoupMessage * message)322 real_append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message)
323 {
324 	g_assert (message != NULL);
325 
326 	/* Set the authorisation header */
327 	if (self->priv->authorizer != NULL) {
328 		gdata_authorizer_process_request (self->priv->authorizer, domain, message);
329 
330 		if (domain != NULL) {
331 			/* Store the authorisation domain on the message so that we can access it again after refreshing authorisation if necessary.
332 			 * See _gdata_service_send_message(). */
333 			g_object_set_data_full (G_OBJECT (message), "gdata-authorization-domain", g_object_ref (domain),
334 			                        (GDestroyNotify) g_object_unref);
335 		}
336 	}
337 
338 	/* Set the GData-Version header to tell it we want to use the v2 API */
339 	soup_message_headers_append (message->request_headers, "GData-Version", GDATA_SERVICE_GET_CLASS (self)->api_version);
340 
341 	/* Set the locale, if it's been set for the service */
342 	if (self->priv->locale != NULL)
343 		soup_message_headers_append (message->request_headers, "Accept-Language", self->priv->locale);
344 }
345 
346 static void
real_parse_error_response(GDataService * self,GDataOperationType operation_type,guint status,const gchar * reason_phrase,const gchar * response_body,gint length,GError ** error)347 real_parse_error_response (GDataService *self, GDataOperationType operation_type, guint status, const gchar *reason_phrase,
348                            const gchar *response_body, gint length, GError **error)
349 {
350 	/* We prefer to include the @response_body in the error message, but if it's empty, fall back to the @reason_phrase */
351 	if (response_body == NULL || *response_body == '\0')
352 		response_body = reason_phrase;
353 
354 	/* See: http://code.google.com/apis/gdata/docs/2.0/reference.html#HTTPStatusCodes */
355 	switch (status) {
356 		case SOUP_STATUS_CANT_RESOLVE:
357 		case SOUP_STATUS_CANT_CONNECT:
358 		case SOUP_STATUS_SSL_FAILED:
359 		case SOUP_STATUS_IO_ERROR:
360 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NETWORK_ERROR,
361 			             _("Cannot connect to the service’s server."));
362 			return;
363 		case SOUP_STATUS_CANT_RESOLVE_PROXY:
364 		case SOUP_STATUS_CANT_CONNECT_PROXY:
365 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROXY_ERROR,
366 			             _("Cannot connect to the proxy server."));
367 			return;
368 		case SOUP_STATUS_MALFORMED:
369 		case SOUP_STATUS_BAD_REQUEST: /* 400 */
370 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
371 			             /* Translators: the parameter is an error message returned by the server. */
372 			             _("Invalid request URI or header, or unsupported nonstandard parameter: %s"), response_body);
373 			return;
374 		case SOUP_STATUS_UNAUTHORIZED: /* 401 */
375 		case SOUP_STATUS_FORBIDDEN: /* 403 */
376 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
377 			             /* Translators: the parameter is an error message returned by the server. */
378 			             _("Authentication required: %s"), response_body);
379 			return;
380 		case SOUP_STATUS_NOT_FOUND: /* 404 */
381 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND,
382 			             /* Translators: the parameter is an error message returned by the server. */
383 			             _("The requested resource was not found: %s"), response_body);
384 			return;
385 		case SOUP_STATUS_CONFLICT: /* 409 */
386 		case SOUP_STATUS_PRECONDITION_FAILED: /* 412 */
387 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_CONFLICT,
388 			             /* Translators: the parameter is an error message returned by the server. */
389 			             _("The entry has been modified since it was downloaded: %s"), response_body);
390 			return;
391 		case SOUP_STATUS_INTERNAL_SERVER_ERROR: /* 500 */
392 		default:
393 			/* We'll fall back to generic errors, below */
394 			break;
395 	}
396 
397 	/* If the error hasn't been handled already, throw a generic error */
398 	switch (operation_type) {
399 		case GDATA_OPERATION_AUTHENTICATION:
400 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
401 			             /* Translators: the first parameter is an HTTP status,
402 			              * and the second is an error message returned by the server. */
403 			             _("Error code %u when authenticating: %s"), status, response_body);
404 			break;
405 		case GDATA_OPERATION_QUERY:
406 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
407 			             /* Translators: the first parameter is an HTTP status,
408 			              * and the second is an error message returned by the server. */
409 			             _("Error code %u when querying: %s"), status, response_body);
410 			break;
411 		case GDATA_OPERATION_INSERTION:
412 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
413 			             /* Translators: the first parameter is an HTTP status,
414 			              * and the second is an error message returned by the server. */
415 			             _("Error code %u when inserting an entry: %s"), status, response_body);
416 			break;
417 		case GDATA_OPERATION_UPDATE:
418 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
419 			             /* Translators: the first parameter is an HTTP status,
420 			              * and the second is an error message returned by the server. */
421 			             _("Error code %u when updating an entry: %s"), status, response_body);
422 			break;
423 		case GDATA_OPERATION_DELETION:
424 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
425 			             /* Translators: the first parameter is an HTTP status,
426 			              * and the second is an error message returned by the server. */
427 			             _("Error code %u when deleting an entry: %s"), status, response_body);
428 			break;
429 		case GDATA_OPERATION_DOWNLOAD:
430 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
431 			             /* Translators: the first parameter is an HTTP status,
432 			              * and the second is an error message returned by the server. */
433 			             _("Error code %u when downloading: %s"), status, response_body);
434 			break;
435 		case GDATA_OPERATION_UPLOAD:
436 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
437 			             /* Translators: the first parameter is an HTTP status,
438 			              * and the second is an error message returned by the server. */
439 			             _("Error code %u when uploading: %s"), status, response_body);
440 			break;
441 		case GDATA_OPERATION_BATCH:
442 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_WITH_BATCH_OPERATION,
443 			             /* Translators: the first parameter is a HTTP status,
444 			              * and the second is an error message returned by the server. */
445 			             _("Error code %u when running a batch operation: %s"), status, response_body);
446 			break;
447 		default:
448 			/* We should not be called with anything other than the above operation types */
449 			g_assert_not_reached ();
450 	}
451 }
452 
453 /**
454  * gdata_service_is_authorized:
455  * @self: a #GDataService
456  *
457  * Determines whether the service is authorized for all the #GDataAuthorizationDomains it belongs to (as returned by
458  * gdata_service_get_authorization_domains()). If the service's #GDataService:authorizer is %NULL, %FALSE is always returned.
459  *
460  * This is basically a convenience method for checking that the service's #GDataAuthorizer is authorized for all the service's
461  * #GDataAuthorizationDomains.
462  *
463  * Return value: %TRUE if the service is authorized for all its domains, %FALSE otherwise
464  *
465  * Since: 0.9.0
466  */
467 gboolean
gdata_service_is_authorized(GDataService * self)468 gdata_service_is_authorized (GDataService *self)
469 {
470 	GList *domains, *i;
471 	gboolean authorised = TRUE;
472 
473 	g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE);
474 
475 	/* If we don't have an authoriser set, we can't be authorised */
476 	if (self->priv->authorizer == NULL) {
477 		return FALSE;
478 	}
479 
480 	domains = gdata_service_get_authorization_domains (G_OBJECT_TYPE (self));
481 
482 	/* Find any domains which we're not authorised for */
483 	for (i = domains; i != NULL; i = i->next) {
484 		if (gdata_authorizer_is_authorized_for_domain (self->priv->authorizer, GDATA_AUTHORIZATION_DOMAIN (i->data)) == FALSE) {
485 			authorised = FALSE;
486 			break;
487 		}
488 	}
489 
490 	g_list_free (domains);
491 
492 	return authorised;
493 }
494 
495 /**
496  * gdata_service_get_authorizer:
497  * @self: a #GDataService
498  *
499  * Gets the #GDataAuthorizer object currently in use by the service. See the documentation for #GDataService:authorizer for more details.
500  *
501  * Return value: (transfer none): the authorizer object for this service, or %NULL
502  *
503  * Since: 0.9.0
504  */
505 GDataAuthorizer *
gdata_service_get_authorizer(GDataService * self)506 gdata_service_get_authorizer (GDataService *self)
507 {
508 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
509 
510 	return self->priv->authorizer;
511 }
512 
513 /**
514  * gdata_service_set_authorizer:
515  * @self: a #GDataService
516  * @authorizer: a new authorizer object for the service, or %NULL
517  *
518  * Sets #GDataService:authorizer to @authorizer. This may be %NULL if the service will only make requests in future which don't require authorization.
519  * See the documentation for #GDataService:authorizer for more information.
520  *
521  * Since: 0.9.0
522  */
523 void
gdata_service_set_authorizer(GDataService * self,GDataAuthorizer * authorizer)524 gdata_service_set_authorizer (GDataService *self, GDataAuthorizer *authorizer)
525 {
526 	GDataServicePrivate *priv = self->priv;
527 
528 	g_return_if_fail (GDATA_IS_SERVICE (self));
529 	g_return_if_fail (authorizer == NULL || GDATA_IS_AUTHORIZER (authorizer));
530 
531 	if (priv->authorizer != NULL) {
532 		g_object_unref (priv->authorizer);
533 	}
534 
535 	priv->authorizer = authorizer;
536 
537 	if (priv->authorizer != NULL) {
538 		g_object_ref (priv->authorizer);
539 	}
540 
541 	g_object_notify (G_OBJECT (self), "authorizer");
542 }
543 
544 /**
545  * gdata_service_get_authorization_domains:
546  * @service_type: the #GType of the #GDataService subclass to retrieve the authorization domains for
547  *
548  * Retrieves the full list of #GDataAuthorizationDomains which relate to the specified @service_type. All the
549  * #GDataAuthorizationDomains are unique and interned, so can be compared with other domains by simple pointer comparison.
550  *
551  * Note that in addition to this method, #GDataService subclasses may expose some or all of their authorization domains individually by means of
552  * individual accessor functions.
553  *
554  * Return value: (transfer container) (element-type GDataAuthorizationDomain): an unordered list of #GDataAuthorizationDomains; free with
555  * g_list_free()
556  *
557  * Since: 0.9.0
558  */
559 GList *
gdata_service_get_authorization_domains(GType service_type)560 gdata_service_get_authorization_domains (GType service_type)
561 {
562 	GDataServiceClass *klass;
563 	GList *domains = NULL;
564 
565 	g_return_val_if_fail (g_type_is_a (service_type, GDATA_TYPE_SERVICE), NULL);
566 
567 	klass = GDATA_SERVICE_CLASS (g_type_class_ref (service_type));
568 	if (klass->get_authorization_domains != NULL) {
569 		domains = klass->get_authorization_domains ();
570 	}
571 	g_type_class_unref (klass);
572 
573 	return domains;
574 }
575 
576 SoupMessage *
_gdata_service_build_message(GDataService * self,GDataAuthorizationDomain * domain,const gchar * method,const gchar * uri,const gchar * etag,gboolean etag_if_match)577 _gdata_service_build_message (GDataService *self, GDataAuthorizationDomain *domain, const gchar *method, const gchar *uri,
578                               const gchar *etag, gboolean etag_if_match)
579 {
580 	SoupMessage *message;
581 	GDataServiceClass *klass;
582 	SoupURI *_uri;
583 
584 	/* Create the message. Allow changing the HTTPS port just for testing,
585 	 * but require that the URI is always HTTPS for privacy. */
586 	_uri = soup_uri_new (uri);
587 	soup_uri_set_port (_uri, _gdata_service_get_https_port ());
588 	g_assert_cmpstr (soup_uri_get_scheme (_uri), ==, SOUP_URI_SCHEME_HTTPS);
589 	message = soup_message_new_from_uri (method, _uri);
590 	soup_uri_free (_uri);
591 
592 	/* Make sure subclasses set their headers */
593 	klass = GDATA_SERVICE_GET_CLASS (self);
594 	if (klass->append_query_headers != NULL)
595 		klass->append_query_headers (self, domain, message);
596 
597 	/* Append the ETag header if possible */
598 	if (etag != NULL)
599 		soup_message_headers_append (message->request_headers, (etag_if_match == TRUE) ? "If-Match" : "If-None-Match", etag);
600 
601 	return message;
602 }
603 
604 typedef struct {
605 	GMutex mutex; /* mutex to prevent cancellation before the message has been added to the session's message queue */
606 	SoupSession *session;
607 	SoupMessage *message;
608 } MessageData;
609 
610 static void
message_cancel_cb(GCancellable * cancellable,MessageData * data)611 message_cancel_cb (GCancellable *cancellable, MessageData *data)
612 {
613 	g_mutex_lock (&(data->mutex));
614 	soup_session_cancel_message (data->session, data->message, SOUP_STATUS_CANCELLED);
615 	g_mutex_unlock (&(data->mutex));
616 }
617 
618 static void
message_request_queued_cb(SoupSession * session,SoupMessage * message,MessageData * data)619 message_request_queued_cb (SoupSession *session, SoupMessage *message, MessageData *data)
620 {
621 	if (message == data->message) {
622 		g_mutex_unlock (&(data->mutex));
623 	}
624 }
625 
626 /* Synchronously send @message via @service, handling asynchronous cancellation as best we can. If @cancellable has been cancelled before we start
627  * network activity, return without doing any network activity. Otherwise, if @cancellable is cancelled (from another thread) after network activity
628  * has started, we wait until the message has been queued by the session, then cancel the network activity and return as soon as possible.
629  *
630  * If cancellation has been handled, @error is guaranteed to be set to %G_IO_ERROR_CANCELLED. Otherwise, @error is guaranteed to be unset. */
631 void
_gdata_service_actually_send_message(SoupSession * session,SoupMessage * message,GCancellable * cancellable,GError ** error)632 _gdata_service_actually_send_message (SoupSession *session, SoupMessage *message, GCancellable *cancellable, GError **error)
633 {
634 	MessageData data;
635 	gulong cancel_signal = 0, request_queued_signal = 0;
636 
637 	/* 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
638 	 * thread while we were making a request, the request would be unexpectedly cancelled. See bgo#650835 for an example of this breaking things.
639 	 */
640 	g_object_ref (session);
641 	g_object_ref (message);
642 
643 	/* Listen for cancellation */
644 	if (cancellable != NULL) {
645 		g_mutex_init (&(data.mutex));
646 		data.session = session;
647 		data.message = message;
648 
649 		cancel_signal = g_cancellable_connect (cancellable, (GCallback) message_cancel_cb, &data, NULL);
650 		request_queued_signal = g_signal_connect (session, "request-queued", (GCallback) message_request_queued_cb, &data);
651 
652 		/* 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
653 		 * the mutex to be held to cancel the message. Consequently, if the message is cancelled (in another thread) any time between this lock
654 		 * and the request being queued, the cancellation will wait until the request has been queued before taking effect.
655 		 * 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()
656 		 * and soup_session_send_message(), as the former doesn't have any effect until the request has been queued, and once the latter has
657 		 * returned, all network activity has been finished so cancellation is pointless. */
658 		g_mutex_lock (&(data.mutex));
659 	}
660 
661 	/* 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
662 	 * been cancelled, it's because it was cancelled before we called g_cancellable_connect().
663 	 *
664 	 * Otherwise, manually set the message's status code to SOUP_STATUS_CANCELLED, as the message was cancelled before even being queued to be
665 	 * sent. */
666 	if (cancellable == NULL || g_cancellable_is_cancelled (cancellable) == FALSE)
667 		soup_session_send_message (session, message);
668 	else {
669 		if (cancellable != NULL) {
670 			g_mutex_unlock (&data.mutex);
671 		}
672 
673 		soup_message_set_status (message, SOUP_STATUS_CANCELLED);
674 	}
675 
676 	/* Clean up the cancellation code */
677 	if (cancellable != NULL) {
678 		g_signal_handler_disconnect (session, request_queued_signal);
679 
680 		if (cancel_signal != 0)
681 			g_cancellable_disconnect (cancellable, cancel_signal);
682 
683 		g_mutex_clear (&(data.mutex));
684 	}
685 
686 	/* Set the cancellation error if applicable. We can't assume that our GCancellable has been cancelled just because the message has;
687 	 * libsoup may internally cancel messages if, for example, the proxy URI of the SoupSession is changed.
688 	 * libsoup also sometimes seems to return a SOUP_STATUS_IO_ERROR when we cancel a message, even though we've specified SOUP_STATUS_CANCELLED
689 	 * at cancellation time. Ho Hum. */
690 	g_assert (message->status_code != SOUP_STATUS_NONE);
691 
692 	if (message->status_code == SOUP_STATUS_CANCELLED ||
693 	    ((message->status_code == SOUP_STATUS_IO_ERROR || message->status_code == SOUP_STATUS_SSL_FAILED ||
694 	      message->status_code == SOUP_STATUS_CANT_CONNECT || message->status_code == SOUP_STATUS_CANT_RESOLVE) &&
695 	     cancellable != NULL && g_cancellable_is_cancelled (cancellable) == TRUE)) {
696 		/* We hackily create and cancel a new GCancellable so that we can set the error using it and therefore save ourselves a translatable
697 		 * string and the associated maintenance. */
698 		GCancellable *error_cancellable = g_cancellable_new ();
699 		g_cancellable_cancel (error_cancellable);
700 		g_assert (g_cancellable_set_error_if_cancelled (error_cancellable, error) == TRUE);
701 		g_object_unref (error_cancellable);
702 
703 		/* As per the above comment, force the status to be SOUP_STATUS_CANCELLED. */
704 		soup_message_set_status (message, SOUP_STATUS_CANCELLED);
705 	}
706 
707 	/* Free things */
708 	g_object_unref (message);
709 	g_object_unref (session);
710 }
711 
712 guint
_gdata_service_send_message(GDataService * self,SoupMessage * message,GCancellable * cancellable,GError ** error)713 _gdata_service_send_message (GDataService *self, SoupMessage *message, GCancellable *cancellable, GError **error)
714 {
715 	/* Based on code from evolution-data-server's libgdata:
716 	 *  Ebby Wiselyn <ebbywiselyn@gmail.com>
717 	 *  Jason Willis <zenbrother@gmail.com>
718 	 *
719 	 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
720 	 */
721 
722 	soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT);
723 	_gdata_service_actually_send_message (self->priv->session, message, cancellable, error);
724 	soup_message_set_flags (message, 0);
725 
726 	/* Handle redirections specially so we don't lose our custom headers when making the second request */
727 	if (SOUP_STATUS_IS_REDIRECTION (message->status_code)) {
728 		SoupURI *new_uri;
729 		const gchar *new_location;
730 
731 		new_location = soup_message_headers_get_one (message->response_headers, "Location");
732 		g_return_val_if_fail (new_location != NULL, SOUP_STATUS_NONE);
733 
734 		new_uri = soup_uri_new_with_base (soup_message_get_uri (message), new_location);
735 		if (new_uri == NULL) {
736 			gchar *uri_string = soup_uri_to_string (new_uri, FALSE);
737 			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
738 			             /* Translators: the parameter is the URI which is invalid. */
739 			             _("Invalid redirect URI: %s"), uri_string);
740 			g_free (uri_string);
741 			return SOUP_STATUS_NONE;
742 		}
743 
744 		/* Allow overriding the URI for testing. */
745 		soup_uri_set_port (new_uri, _gdata_service_get_https_port ());
746 
747 		soup_message_set_uri (message, new_uri);
748 		soup_uri_free (new_uri);
749 
750 		/* Send the message again */
751 		_gdata_service_actually_send_message (self->priv->session, message, cancellable, error);
752 	}
753 
754 	/* Not authorised, or authorisation has expired. If we were authorised in the first place, attempt to refresh the authorisation and
755 	 * try sending the message again (but only once, so we don't get caught in an infinite loop of denied authorisation errors).
756 	 *
757 	 * Note that we have to re-process the message with the authoriser so that its authorisation headers get updated after the refresh
758 	 * (bgo#653535). */
759 	if (message->status_code == SOUP_STATUS_UNAUTHORIZED ||
760 	    message->status_code == SOUP_STATUS_FORBIDDEN ||
761 	    message->status_code == SOUP_STATUS_NOT_FOUND) {
762 		GDataAuthorizer *authorizer = self->priv->authorizer;
763 
764 		if (authorizer != NULL && gdata_authorizer_refresh_authorization (authorizer, cancellable, NULL) == TRUE) {
765 			GDataAuthorizationDomain *domain;
766 
767 			/* Re-process the request */
768 			domain = g_object_get_data (G_OBJECT (message), "gdata-authorization-domain");
769 			g_assert (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
770 
771 			gdata_authorizer_process_request (authorizer, domain, message);
772 
773 			/* Send the message again */
774 			g_clear_error (error);
775 			_gdata_service_actually_send_message (self->priv->session, message, cancellable, error);
776 		}
777 	}
778 
779 	return message->status_code;
780 }
781 
782 typedef struct {
783 	/* Input */
784 	GDataAuthorizationDomain *domain;
785 	gchar *feed_uri;
786 	GDataQuery *query;
787 	GType entry_type;
788 
789 	/* Output */
790 	GDataQueryProgressCallback progress_callback;
791 	gpointer progress_user_data;
792 	GDestroyNotify destroy_progress_user_data;
793 } QueryAsyncData;
794 
795 static void
query_async_data_free(QueryAsyncData * self)796 query_async_data_free (QueryAsyncData *self)
797 {
798 	if (self->domain != NULL)
799 		g_object_unref (self->domain);
800 
801 	g_free (self->feed_uri);
802 	if (self->query)
803 		g_object_unref (self->query);
804 
805 	g_slice_free (QueryAsyncData, self);
806 }
807 
808 static void
query_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)809 query_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
810 {
811 	GDataService *service = GDATA_SERVICE (source_object);
812 	g_autoptr(GError) error = NULL;
813 	QueryAsyncData *data = task_data;
814 	g_autoptr(GDataFeed) feed = NULL;
815 
816 	/* Execute the query and return */
817 	feed = __gdata_service_query (service, data->domain, data->feed_uri, data->query, data->entry_type, cancellable,
818 	                              data->progress_callback, data->progress_user_data, &error);
819 	if (feed == NULL && error != NULL)
820 		g_task_return_error (task, g_steal_pointer (&error));
821 	else
822 		g_task_return_pointer (task, g_steal_pointer (&feed), g_object_unref);
823 
824 	if (data->destroy_progress_user_data != NULL) {
825 		data->destroy_progress_user_data (data->progress_user_data);
826 	}
827 }
828 
829 /**
830  * gdata_service_query_async:
831  * @self: a #GDataService
832  * @domain: (allow-none): the #GDataAuthorizationDomain the query falls under, or %NULL
833  * @feed_uri: the feed URI to query, including the host name and protocol
834  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
835  * @entry_type: a #GType for the #GDataEntrys to build from the XML
836  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
837  * @progress_callback: (allow-none) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
838  * @progress_user_data: (closure): data to pass to the @progress_callback function
839  * @destroy_progress_user_data: (allow-none): the function to call when @progress_callback will not be called any more, or %NULL. This function will be
840  * called with @progress_user_data as a parameter and can be used to free any memory allocated for it.
841  * @callback: a #GAsyncReadyCallback to call when the query is finished
842  * @user_data: (closure): data to pass to the @callback function
843  *
844  * Queries the service's @feed_uri feed to build a #GDataFeed. @self, @feed_uri and
845  * @query are all reffed/copied when this function is called, so can safely be freed after this function returns.
846  *
847  * For more details, see gdata_service_query(), which is the synchronous version of this function.
848  *
849  * When the operation is finished, @callback will be called. You can then call gdata_service_query_finish()
850  * to get the results of the operation.
851  *
852  * Since: 0.9.1
853  */
854 void
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)855 gdata_service_query_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type,
856                            GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
857                            GDestroyNotify destroy_progress_user_data, GAsyncReadyCallback callback, gpointer user_data)
858 {
859 	g_autoptr(GTask) task = NULL;
860 	QueryAsyncData *data;
861 
862 	g_return_if_fail (GDATA_IS_SERVICE (self));
863 	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
864 	g_return_if_fail (feed_uri != NULL);
865 	g_return_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY));
866 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
867 	g_return_if_fail (callback != NULL);
868 
869 	data = g_slice_new (QueryAsyncData);
870 	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
871 	data->feed_uri = g_strdup (feed_uri);
872 	data->query = (query != NULL) ? g_object_ref (query) : NULL;
873 	data->entry_type = entry_type;
874 	data->progress_callback = progress_callback;
875 	data->progress_user_data = progress_user_data;
876 	data->destroy_progress_user_data = destroy_progress_user_data;
877 
878 	task = g_task_new (self, cancellable, callback, user_data);
879 	g_task_set_source_tag (task, gdata_service_query_async);
880 	g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) query_async_data_free);
881 	g_task_run_in_thread (task, query_thread);
882 }
883 
884 /**
885  * gdata_service_query_finish:
886  * @self: a #GDataService
887  * @async_result: a #GAsyncResult
888  * @error: a #GError, or %NULL
889  *
890  * Finishes an asynchronous query operation started with gdata_service_query_async().
891  *
892  * Return value: (transfer full): a #GDataFeed of query results, or %NULL; unref with g_object_unref()
893  */
894 GDataFeed *
gdata_service_query_finish(GDataService * self,GAsyncResult * async_result,GError ** error)895 gdata_service_query_finish (GDataService *self, GAsyncResult *async_result, GError **error)
896 {
897 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
898 	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
899 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
900 	g_return_val_if_fail (g_task_is_valid (async_result, self), NULL);
901 	g_return_val_if_fail (g_async_result_is_tagged (async_result, gdata_service_query_async), NULL);
902 
903 	return g_task_propagate_pointer (G_TASK (async_result), error);
904 }
905 
906 /* Does the bulk of the work of gdata_service_query. Split out because certain queries (such as that done by
907  * gdata_service_query_single_entry()) only return a single entry, and thus need special parsing code. */
908 SoupMessage *
_gdata_service_query(GDataService * self,GDataAuthorizationDomain * domain,const gchar * feed_uri,GDataQuery * query,GCancellable * cancellable,GError ** error)909 _gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query,
910                       GCancellable *cancellable, GError **error)
911 {
912 	SoupMessage *message;
913 	guint status;
914 	const gchar *etag = NULL;
915 
916 	/* Append the ETag header if possible */
917 	if (query != NULL)
918 		etag = gdata_query_get_etag (query);
919 
920 	/* Build the message */
921 	if (query != NULL) {
922 		gchar *query_uri = gdata_query_get_query_uri (query, feed_uri);
923 		message = _gdata_service_build_message (self, domain, SOUP_METHOD_GET, query_uri, etag, FALSE);
924 		g_free (query_uri);
925 	} else {
926 		message = _gdata_service_build_message (self, domain, SOUP_METHOD_GET, feed_uri, etag, FALSE);
927 	}
928 
929 	/* Note that cancellation only applies to network activity; not to the processing done afterwards */
930 	status = _gdata_service_send_message (self, message, cancellable, error);
931 
932 	if (status == SOUP_STATUS_NOT_MODIFIED || status == SOUP_STATUS_CANCELLED) {
933 		/* Not modified (ETag has worked), or cancelled (in which case the error has been set) */
934 		g_object_unref (message);
935 		return NULL;
936 	} else if (status != SOUP_STATUS_OK) {
937 		/* Error */
938 		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self);
939 		g_assert (klass->parse_error_response != NULL);
940 		klass->parse_error_response (self, GDATA_OPERATION_QUERY, status, message->reason_phrase, message->response_body->data,
941 		                             message->response_body->length, error);
942 		g_object_unref (message);
943 		return NULL;
944 	}
945 
946 	return message;
947 }
948 
949 static GDataFeed *
__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)950 __gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type,
951                        GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
952 {
953 	GDataServiceClass *klass;
954 	SoupMessage *message;
955 	GDataFeed *feed;
956 
957 	klass = GDATA_SERVICE_GET_CLASS (self);
958 
959 	/* Are we off the end of the final page? */
960 	if (query != NULL && _gdata_query_is_finished (query)) {
961 		GTimeVal updated;
962 
963 		/* Build an empty dummy feed to signify the end of the list. */
964 		g_get_current_time (&updated);
965 		return _gdata_feed_new (klass->feed_type, "Empty feed", "feed1",
966 		                        updated.tv_sec);
967 	}
968 
969 	/* Send the request. */
970 	message = _gdata_service_query (self, domain, feed_uri, query, cancellable, error);
971 	if (message == NULL)
972 		return NULL;
973 
974 	g_assert (message->response_body->data != NULL);
975 	g_assert (klass->parse_feed != NULL);
976 
977 	/* Parse the response. */
978 	feed = klass->parse_feed (self, domain, query, entry_type,
979 	                          message, cancellable, progress_callback,
980 	                          progress_user_data, error);
981 
982 	g_object_unref (message);
983 
984 	return feed;
985 }
986 
987 static GDataFeed *
real_parse_feed(GDataService * self,GDataAuthorizationDomain * domain,GDataQuery * query,GType entry_type,SoupMessage * message,GCancellable * cancellable,GDataQueryProgressCallback progress_callback,gpointer progress_user_data,GError ** error)988 real_parse_feed (GDataService *self,
989                  GDataAuthorizationDomain *domain,
990                  GDataQuery *query,
991                  GType entry_type,
992                  SoupMessage *message,
993                  GCancellable *cancellable,
994                  GDataQueryProgressCallback progress_callback,
995                  gpointer progress_user_data,
996                  GError **error)
997 {
998 	GDataServiceClass *klass;
999 	GDataFeed *feed = NULL;
1000 	SoupMessageHeaders *headers;
1001 	const gchar *content_type;
1002 
1003 	klass = GDATA_SERVICE_GET_CLASS (self);
1004 	headers = message->response_headers;
1005 	content_type = soup_message_headers_get_content_type (headers, NULL);
1006 
1007 	if (content_type != NULL && strcmp (content_type, "application/json") == 0) {
1008 		/* Definitely JSON. */
1009 		g_debug("JSON content type detected.");
1010 		feed = _gdata_feed_new_from_json (klass->feed_type, message->response_body->data, message->response_body->length, entry_type,
1011 		                                  progress_callback, progress_user_data, error);
1012 	} else {
1013 		/* Potentially XML. Don't bother checking the Content-Type, since the parser
1014 		 * will fail gracefully if the response body is not valid XML. */
1015 		g_debug("XML content type detected.");
1016 		feed = _gdata_feed_new_from_xml (klass->feed_type, message->response_body->data, message->response_body->length, entry_type,
1017 		                                 progress_callback, progress_user_data, error);
1018 	}
1019 
1020 	/* Update the query with the feed's ETag */
1021 	if (query != NULL && feed != NULL && gdata_feed_get_etag (feed) != NULL)
1022 		gdata_query_set_etag (query, gdata_feed_get_etag (feed));
1023 
1024 	/* Update the query with the next and previous URIs from the feed */
1025 	if (query != NULL && feed != NULL) {
1026 		GDataLink *_link;
1027 		const gchar *token;
1028 
1029 		_gdata_query_clear_pagination (query);
1030 
1031 		/* Atom-style next and previous page links. */
1032 		_link = gdata_feed_look_up_link (feed, "http://www.iana.org/assignments/relation/next");
1033 		if (_link != NULL)
1034 			_gdata_query_set_next_uri (query, gdata_link_get_uri (_link));
1035 		_link = gdata_feed_look_up_link (feed, "http://www.iana.org/assignments/relation/previous");
1036 		if (_link != NULL)
1037 			_gdata_query_set_previous_uri (query, gdata_link_get_uri (_link));
1038 
1039 		/* JSON-style next page token. (There is no previous page
1040 		 * token.) */
1041 		token = gdata_feed_get_next_page_token (feed);
1042 		if (token != NULL)
1043 			_gdata_query_set_next_page_token (query, token);
1044 	}
1045 
1046 	return feed;
1047 }
1048 
1049 /**
1050  * gdata_service_query:
1051  * @self: a #GDataService
1052  * @domain: (allow-none): the #GDataAuthorizationDomain the query falls under, or %NULL
1053  * @feed_uri: the feed URI to query, including the host name and protocol
1054  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
1055  * @entry_type: a #GType for the #GDataEntrys to build from the XML
1056  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
1057  * @progress_callback: (allow-none) (scope call) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
1058  * @progress_user_data: (closure): data to pass to the @progress_callback function
1059  * @error: a #GError, or %NULL
1060  *
1061  * Queries the service's @feed_uri feed to build a #GDataFeed.
1062  *
1063  * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
1064  * If the operation was cancelled before or during network activity, the error %G_IO_ERROR_CANCELLED will be returned. Cancellation has no effect
1065  * after network activity has finished, however, and the query will return successfully (or return an error sent by the server) if it is first
1066  * cancelled after network activity has finished. See the <link linkend="cancellable-support">overview of cancellation</link> for
1067  * more details.
1068  *
1069  * A %GDATA_SERVICE_ERROR_PROTOCOL_ERROR will be returned if the server indicates there is a problem with the query, but subclasses may override
1070  * this and return their own errors. See their documentation for more details.
1071  *
1072  * For each entry in the response feed, @progress_callback will be called in the main thread. If there was an error parsing the XML response,
1073  * a #GDataParserError will be returned.
1074  *
1075  * If the query is successful and the feed supports pagination, @query will be updated with the pagination URIs, and the next or previous page
1076  * can then be loaded by calling gdata_query_next_page() or gdata_query_previous_page() before running the query again.
1077  *
1078  * If the #GDataQuery's ETag is set and it finds a match on the server, %NULL will be returned, but @error will remain unset. Otherwise,
1079  * @query's ETag will be updated with the ETag from the returned feed, if available.
1080  *
1081  * Return value: (transfer full): a #GDataFeed of query results, or %NULL; unref with g_object_unref()
1082  *
1083  * Since: 0.9.0
1084  */
1085 GDataFeed *
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)1086 gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type,
1087                      GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
1088 {
1089 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
1090 	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
1091 	g_return_val_if_fail (feed_uri != NULL, NULL);
1092 	g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY), NULL);
1093 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
1094 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1095 
1096 	return __gdata_service_query (self, domain, feed_uri, query, entry_type, cancellable, progress_callback, progress_user_data, error);
1097 }
1098 
1099 /**
1100  * gdata_service_query_single_entry:
1101  * @self: a #GDataService
1102  * @domain: (allow-none): the #GDataAuthorizationDomain the query falls under, or %NULL
1103  * @entry_id: the entry ID of the desired entry
1104  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
1105  * @entry_type: a #GType for the #GDataEntry to build from the XML
1106  * @cancellable: (allow-none): a #GCancellable, or %NULL
1107  * @error: a #GError, or %NULL
1108  *
1109  * Retrieves information about the single entry with the given @entry_id. @entry_id should be as returned by
1110  * gdata_entry_get_id().
1111  *
1112  * Parameters and errors are as for gdata_service_query(). Most of the properties of @query aren't relevant, and
1113  * will cause a server-side error if used. The most useful property to use is #GDataQuery:etag, which will cause the
1114  * server to not return anything if the entry hasn't been modified since it was given the specified ETag; thus saving
1115  * bandwidth. If the server does not return anything for this reason, gdata_service_query_single_entry() will return
1116  * %NULL, but will not set an error in @error.
1117  *
1118  * Return value: (transfer full): a #GDataEntry, or %NULL; unref with g_object_unref()
1119  *
1120  * Since: 0.9.0
1121  */
1122 GDataEntry *
gdata_service_query_single_entry(GDataService * self,GDataAuthorizationDomain * domain,const gchar * entry_id,GDataQuery * query,GType entry_type,GCancellable * cancellable,GError ** error)1123 gdata_service_query_single_entry (GDataService *self, GDataAuthorizationDomain *domain, const gchar *entry_id, GDataQuery *query, GType entry_type,
1124                                   GCancellable *cancellable, GError **error)
1125 {
1126 	GDataEntryClass *klass;
1127 	GDataEntry *entry;
1128 	gchar *entry_uri;
1129 	SoupMessage *message;
1130 	SoupMessageHeaders *headers;
1131 	const gchar *content_type;
1132 
1133 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
1134 	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
1135 	g_return_val_if_fail (entry_id != NULL, NULL);
1136 	g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
1137 	g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE, NULL);
1138 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
1139 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1140 
1141 	/* Query for just the specified entry */
1142 	klass = GDATA_ENTRY_CLASS (g_type_class_ref (entry_type));
1143 	g_assert (klass->get_entry_uri != NULL);
1144 
1145 	entry_uri = klass->get_entry_uri (entry_id);
1146 	message = _gdata_service_query (GDATA_SERVICE (self), domain, entry_uri, query, cancellable, error);
1147 	g_free (entry_uri);
1148 
1149 	if (message == NULL) {
1150 		g_type_class_unref (klass);
1151 		return NULL;
1152 	}
1153 
1154 	g_assert (message->response_body->data != NULL);
1155 
1156 	headers = message->response_headers;
1157 	content_type = soup_message_headers_get_content_type (headers, NULL);
1158 
1159 	if (g_strcmp0 (content_type, "application/json") == 0) {
1160 		entry = GDATA_ENTRY (gdata_parsable_new_from_json (entry_type, message->response_body->data, message->response_body->length, error));
1161 	} else {
1162 		entry = GDATA_ENTRY (gdata_parsable_new_from_xml (entry_type, message->response_body->data, message->response_body->length, error));
1163 	}
1164 
1165 	g_object_unref (message);
1166 	g_type_class_unref (klass);
1167 
1168 	return entry;
1169 }
1170 
1171 typedef struct {
1172 	GDataAuthorizationDomain *domain;
1173 	gchar *entry_id;
1174 	GDataQuery *query;
1175 	GType entry_type;
1176 } QuerySingleEntryAsyncData;
1177 
1178 static void
query_single_entry_async_data_free(QuerySingleEntryAsyncData * data)1179 query_single_entry_async_data_free (QuerySingleEntryAsyncData *data)
1180 {
1181 	if (data->domain != NULL)
1182 		g_object_unref (data->domain);
1183 
1184 	g_free (data->entry_id);
1185 	if (data->query != NULL)
1186 		g_object_unref (data->query);
1187 	g_slice_free (QuerySingleEntryAsyncData, data);
1188 }
1189 
1190 static void
query_single_entry_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)1191 query_single_entry_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
1192 {
1193 	GDataService *service = GDATA_SERVICE (source_object);
1194 	g_autoptr(GDataEntry) entry = NULL;
1195 	g_autoptr(GError) error = NULL;
1196 	QuerySingleEntryAsyncData *data = task_data;
1197 
1198 	/* Execute the query and return */
1199 	entry = gdata_service_query_single_entry (service, data->domain, data->entry_id, data->query, data->entry_type, cancellable, &error);
1200 	if (entry == NULL && error != NULL)
1201 		g_task_return_error (task, g_steal_pointer (&error));
1202 	else
1203 		g_task_return_pointer (task, g_steal_pointer (&entry), g_object_unref);
1204 }
1205 
1206 /**
1207  * gdata_service_query_single_entry_async:
1208  * @self: a #GDataService
1209  * @domain: (allow-none): the #GDataAuthorizationDomain the query falls under, or %NULL
1210  * @entry_id: the entry ID of the desired entry
1211  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
1212  * @entry_type: a #GType for the #GDataEntry to build from the XML
1213  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
1214  * @callback: a #GAsyncReadyCallback to call when the query is finished
1215  * @user_data: (closure): data to pass to the @callback function
1216  *
1217  * Retrieves information about the single entry with the given @entry_id. @entry_id should be as returned by
1218  * gdata_entry_get_id(). @self, @query and @entry_id are reffed/copied when this
1219  * function is called, so can safely be freed after this function returns.
1220  *
1221  * For more details, see gdata_service_query_single_entry(), which is the synchronous version of this function.
1222  *
1223  * When the operation is finished, @callback will be called. You can then call gdata_service_query_single_entry_finish()
1224  * to get the results of the operation.
1225  *
1226  * Since: 0.9.0
1227  */
1228 void
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)1229 gdata_service_query_single_entry_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *entry_id, GDataQuery *query,
1230                                         GType entry_type, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
1231 {
1232 	g_autoptr(GTask) task = NULL;
1233 	QuerySingleEntryAsyncData *data;
1234 
1235 	g_return_if_fail (GDATA_IS_SERVICE (self));
1236 	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
1237 	g_return_if_fail (entry_id != NULL);
1238 	g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
1239 	g_return_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE);
1240 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
1241 	g_return_if_fail (callback != NULL);
1242 
1243 	data = g_slice_new (QuerySingleEntryAsyncData);
1244 	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
1245 	data->query = (query != NULL) ? g_object_ref (query) : NULL;
1246 	data->entry_id = g_strdup (entry_id);
1247 	data->entry_type = entry_type;
1248 
1249 	task = g_task_new (self, cancellable, callback, user_data);
1250 	g_task_set_source_tag (task, gdata_service_query_single_entry_async);
1251 	g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) query_single_entry_async_data_free);
1252 	g_task_run_in_thread (task, query_single_entry_thread);
1253 }
1254 
1255 /**
1256  * gdata_service_query_single_entry_finish:
1257  * @self: a #GDataService
1258  * @async_result: a #GAsyncResult
1259  * @error: a #GError, or %NULL
1260  *
1261  * Finishes an asynchronous query operation for a single entry, as started with gdata_service_query_single_entry_async().
1262  *
1263  * Return value: (transfer full): a #GDataEntry, or %NULL; unref with g_object_unref()
1264  *
1265  * Since: 0.7.0
1266  */
1267 GDataEntry *
gdata_service_query_single_entry_finish(GDataService * self,GAsyncResult * async_result,GError ** error)1268 gdata_service_query_single_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error)
1269 {
1270 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
1271 	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
1272 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1273 	g_return_val_if_fail (g_task_is_valid (async_result, self), NULL);
1274 	g_return_val_if_fail (g_async_result_is_tagged (async_result, gdata_service_query_single_entry_async), NULL);
1275 
1276 	return g_task_propagate_pointer (G_TASK (async_result), error);
1277 }
1278 
1279 typedef struct {
1280 	GDataAuthorizationDomain *domain;
1281 	gchar *upload_uri;
1282 	GDataEntry *entry;
1283 } InsertEntryAsyncData;
1284 
1285 static void
insert_entry_async_data_free(InsertEntryAsyncData * self)1286 insert_entry_async_data_free (InsertEntryAsyncData *self)
1287 {
1288 	if (self->domain != NULL)
1289 		g_object_unref (self->domain);
1290 
1291 	g_free (self->upload_uri);
1292 	if (self->entry)
1293 		g_object_unref (self->entry);
1294 
1295 	g_slice_free (InsertEntryAsyncData, self);
1296 }
1297 
1298 static void
insert_entry_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)1299 insert_entry_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
1300 {
1301 	GDataService *service = GDATA_SERVICE (source_object);
1302 	g_autoptr(GDataEntry) updated_entry = NULL;
1303 	g_autoptr(GError) error = NULL;
1304 	InsertEntryAsyncData *data = task_data;
1305 
1306 	/* Insert the entry and return */
1307 	updated_entry = gdata_service_insert_entry (service, data->domain, data->upload_uri, data->entry, cancellable, &error);
1308 	if (updated_entry == NULL)
1309 		g_task_return_error (task, g_steal_pointer (&error));
1310 	else
1311 		g_task_return_pointer (task, g_steal_pointer (&updated_entry), g_object_unref);
1312 }
1313 
1314 /**
1315  * gdata_service_insert_entry_async:
1316  * @self: a #GDataService
1317  * @domain: (allow-none): the #GDataAuthorizationDomain the insertion operation falls under, or %NULL
1318  * @upload_uri: the URI to which the upload should be sent
1319  * @entry: the #GDataEntry to insert
1320  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
1321  * @callback: a #GAsyncReadyCallback to call when insertion is finished, or %NULL
1322  * @user_data: (closure): data to pass to the @callback function
1323  *
1324  * Inserts @entry by uploading it to the online service at @upload_uri. @self, @upload_uri and
1325  * @entry are all reffed/copied when this function is called, so can safely be freed after this function returns.
1326  *
1327  * For more details, see gdata_service_insert_entry(), which is the synchronous version of this function.
1328  *
1329  * When the operation is finished, @callback will be called. You can then call gdata_service_insert_entry_finish()
1330  * to get the results of the operation.
1331  *
1332  * Since: 0.9.0
1333  */
1334 void
gdata_service_insert_entry_async(GDataService * self,GDataAuthorizationDomain * domain,const gchar * upload_uri,GDataEntry * entry,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1335 gdata_service_insert_entry_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *upload_uri, GDataEntry *entry,
1336                                   GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
1337 {
1338 	g_autoptr(GTask) task = NULL;
1339 	InsertEntryAsyncData *data;
1340 
1341 	g_return_if_fail (GDATA_IS_SERVICE (self));
1342 	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
1343 	g_return_if_fail (upload_uri != NULL);
1344 	g_return_if_fail (GDATA_IS_ENTRY (entry));
1345 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
1346 
1347 	data = g_slice_new (InsertEntryAsyncData);
1348 	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
1349 	data->upload_uri = g_strdup (upload_uri);
1350 	data->entry = g_object_ref (entry);
1351 
1352 	task = g_task_new (self, cancellable, callback, user_data);
1353 	g_task_set_source_tag (task, gdata_service_insert_entry_async);
1354 	g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) insert_entry_async_data_free);
1355 	g_task_run_in_thread (task, insert_entry_thread);
1356 }
1357 
1358 /**
1359  * gdata_service_insert_entry_finish:
1360  * @self: a #GDataService
1361  * @async_result: a #GAsyncResult
1362  * @error: a #GError, or %NULL
1363  *
1364  * Finishes an asynchronous entry insertion operation started with gdata_service_insert_entry_async().
1365  *
1366  * Return value: (transfer full): an updated #GDataEntry, or %NULL; unref with g_object_unref()
1367  *
1368  * Since: 0.3.0
1369  */
1370 GDataEntry *
gdata_service_insert_entry_finish(GDataService * self,GAsyncResult * async_result,GError ** error)1371 gdata_service_insert_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error)
1372 {
1373 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
1374 	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
1375 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1376 	g_return_val_if_fail (g_task_is_valid (async_result, self), NULL);
1377 	g_return_val_if_fail (g_async_result_is_tagged (async_result, gdata_service_insert_entry_async), NULL);
1378 
1379 	return g_task_propagate_pointer (G_TASK (async_result), error);
1380 }
1381 
1382 /**
1383  * gdata_service_insert_entry:
1384  * @self: a #GDataService
1385  * @domain: (allow-none): the #GDataAuthorizationDomain the insertion operation falls under, or %NULL
1386  * @upload_uri: the URI to which the upload should be sent
1387  * @entry: the #GDataEntry to insert
1388  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
1389  * @error: a #GError, or %NULL
1390  *
1391  * Inserts @entry by uploading it to the online service at @upload_uri. For more information about the concept of inserting entries, see
1392  * the <ulink type="http" url="http://code.google.com/apis/gdata/docs/2.0/basics.html#InsertingEntry">online documentation</ulink> for the GData
1393  * protocol.
1394  *
1395  * The service will return an updated version of the entry, which is the return value of this function on success.
1396  *
1397  * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
1398  * If the operation was cancelled before or during network activity, the error %G_IO_ERROR_CANCELLED will be returned. Cancellation has no effect
1399  * after network activity has finished, however, and the insertion will return successfully (or return an error sent by the server) if it is first
1400  * cancelled after network activity has finished. See the <link linkend="cancellable-support">overview of cancellation</link> for
1401  * more details.
1402  *
1403  * If the entry is marked as already having been inserted a %GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED error will be returned immediately
1404  * (there will be no network requests).
1405  *
1406  * If there is an error inserting the entry, a %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error will be returned. Currently, subclasses
1407  * <emphasis>cannot</emphasis> cannot override this or provide more specific errors.
1408  *
1409  * Return value: (transfer full): an updated #GDataEntry, or %NULL; unref with g_object_unref()
1410  *
1411  * Since: 0.9.0
1412  */
1413 GDataEntry *
gdata_service_insert_entry(GDataService * self,GDataAuthorizationDomain * domain,const gchar * upload_uri,GDataEntry * entry,GCancellable * cancellable,GError ** error)1414 gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain, const gchar *upload_uri, GDataEntry *entry,
1415                             GCancellable *cancellable, GError **error)
1416 {
1417 	GDataEntry *updated_entry;
1418 	SoupMessage *message;
1419 	gchar *upload_data;
1420 	guint status;
1421 	GDataParsableClass *klass;
1422 
1423 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
1424 	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
1425 	g_return_val_if_fail (upload_uri != NULL, NULL);
1426 	g_return_val_if_fail (GDATA_IS_ENTRY (entry), NULL);
1427 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
1428 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1429 
1430 	if (gdata_entry_is_inserted (entry) == TRUE) {
1431 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
1432 		                     _("The entry has already been inserted."));
1433 		return NULL;
1434 	}
1435 
1436 	message = _gdata_service_build_message (self, domain, SOUP_METHOD_POST, upload_uri, NULL, FALSE);
1437 
1438 	/* Append the data */
1439 	klass = GDATA_PARSABLE_GET_CLASS (entry);
1440 	g_assert (klass->get_content_type != NULL);
1441 	if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) {
1442 		upload_data = gdata_parsable_get_json (GDATA_PARSABLE (entry));
1443 		soup_message_set_request (message, "application/json", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
1444 	} else {
1445 		upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
1446 		soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
1447 	}
1448 
1449 	/* Send the message */
1450 	status = _gdata_service_send_message (self, message, cancellable, error);
1451 
1452 	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
1453 		/* Redirect error or cancelled */
1454 		g_object_unref (message);
1455 		return NULL;
1456 	} else if (status != SOUP_STATUS_CREATED && status != SOUP_STATUS_OK) {
1457 		/* Error: for XML APIs Google returns CREATED and for JSON it returns OK. */
1458 		GDataServiceClass *service_klass = GDATA_SERVICE_GET_CLASS (self);
1459 		g_assert (service_klass->parse_error_response != NULL);
1460 		service_klass->parse_error_response (self, GDATA_OPERATION_INSERTION, status, message->reason_phrase, message->response_body->data,
1461 		                                     message->response_body->length, error);
1462 		g_object_unref (message);
1463 		return NULL;
1464 	}
1465 
1466 	/* Parse the XML or JSON according to GDataEntry type; create and return a new GDataEntry of the same type as @entry */
1467 	g_assert (message->response_body->data != NULL);
1468 	if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) {
1469 		updated_entry = GDATA_ENTRY (gdata_parsable_new_from_json (G_OBJECT_TYPE (entry), message->response_body->data,
1470 		                             message->response_body->length, error));
1471 	} else {
1472 		updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), message->response_body->data,
1473 		                             message->response_body->length, error));
1474 	}
1475 	g_object_unref (message);
1476 
1477 	return updated_entry;
1478 }
1479 
1480 typedef struct {
1481 	GDataAuthorizationDomain *domain;
1482 	GDataEntry *entry;
1483 } UpdateEntryAsyncData;
1484 
1485 static void
update_entry_async_data_free(UpdateEntryAsyncData * data)1486 update_entry_async_data_free (UpdateEntryAsyncData *data)
1487 {
1488 	if (data->domain != NULL)
1489 		g_object_unref (data->domain);
1490 
1491 	if (data->entry != NULL)
1492 		g_object_unref (data->entry);
1493 
1494 	g_slice_free (UpdateEntryAsyncData, data);
1495 }
1496 
1497 static void
update_entry_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)1498 update_entry_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
1499 {
1500 	GDataService *service = GDATA_SERVICE (source_object);
1501 	g_autoptr(GDataEntry) updated_entry = NULL;
1502 	g_autoptr(GError) error = NULL;
1503 	UpdateEntryAsyncData *data = task_data;
1504 
1505 	/* Update the entry and return */
1506 	updated_entry = gdata_service_update_entry (service, data->domain, data->entry, cancellable, &error);
1507 	if (updated_entry == NULL)
1508 		g_task_return_error (task, g_steal_pointer (&error));
1509 	else
1510 		g_task_return_pointer (task, g_steal_pointer (&updated_entry), g_object_unref);
1511 }
1512 
1513 /**
1514  * gdata_service_update_entry_async:
1515  * @self: a #GDataService
1516  * @domain: (allow-none): the #GDataAuthorizationDomain the update operation falls under, or %NULL
1517  * @entry: the #GDataEntry to update
1518  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
1519  * @callback: a #GAsyncReadyCallback to call when the update is finished, or %NULL
1520  * @user_data: (closure): data to pass to the @callback function
1521  *
1522  * Updates @entry by PUTting it to its <literal>edit</literal> link's URI. @self and
1523  * @entry are both reffed when this function is called, so can safely be unreffed after this function returns.
1524  *
1525  * For more details, see gdata_service_update_entry(), which is the synchronous version of this function.
1526  *
1527  * When the operation is finished, @callback will be called. You can then call gdata_service_update_entry_finish()
1528  * to get the results of the operation.
1529  *
1530  * Since: 0.9.0
1531  */
1532 void
gdata_service_update_entry_async(GDataService * self,GDataAuthorizationDomain * domain,GDataEntry * entry,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1533 gdata_service_update_entry_async (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry,
1534                                   GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
1535 {
1536 	g_autoptr(GTask) task = NULL;
1537 	UpdateEntryAsyncData *data;
1538 
1539 	g_return_if_fail (GDATA_IS_SERVICE (self));
1540 	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
1541 	g_return_if_fail (GDATA_IS_ENTRY (entry));
1542 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
1543 
1544 	data = g_slice_new (UpdateEntryAsyncData);
1545 	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
1546 	data->entry = g_object_ref (entry);
1547 
1548 	task = g_task_new (task, cancellable, callback, user_data);
1549 	g_task_set_source_tag (task, gdata_service_update_entry_async);
1550 	g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) update_entry_async_data_free);
1551 	g_task_run_in_thread (task, update_entry_thread);
1552 }
1553 
1554 /**
1555  * gdata_service_update_entry_finish:
1556  * @self: a #GDataService
1557  * @async_result: a #GAsyncResult
1558  * @error: a #GError, or %NULL
1559  *
1560  * Finishes an asynchronous entry update operation started with gdata_service_update_entry_async().
1561  *
1562  * Return value: (transfer full): an updated #GDataEntry, or %NULL; unref with g_object_unref()
1563  *
1564  * Since: 0.3.0
1565  */
1566 GDataEntry *
gdata_service_update_entry_finish(GDataService * self,GAsyncResult * async_result,GError ** error)1567 gdata_service_update_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error)
1568 {
1569 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
1570 	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
1571 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1572 	g_return_val_if_fail (g_task_is_valid (async_result, self), NULL);
1573 	g_return_val_if_fail (g_async_result_is_tagged (async_result, gdata_service_update_entry_async), NULL);
1574 
1575 	return g_task_propagate_pointer (G_TASK (async_result), error);
1576 }
1577 
1578 /**
1579  * gdata_service_update_entry:
1580  * @self: a #GDataService
1581  * @domain: (allow-none): the #GDataAuthorizationDomain the update operation falls under, or %NULL
1582  * @entry: the #GDataEntry to update
1583  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
1584  * @error: a #GError, or %NULL
1585  *
1586  * Updates @entry by PUTting it to its <literal>edit</literal> link's URI. For more information about the concept of updating entries, see
1587  * the <ulink type="http" url="http://code.google.com/apis/gdata/docs/2.0/basics.html#UpdatingEntry">online documentation</ulink> for the GData
1588  * protocol.
1589  *
1590  * The service will return an updated version of the entry, which is the return value of this function on success.
1591  *
1592  * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
1593  * If the operation was cancelled before or during network activity, the error %G_IO_ERROR_CANCELLED will be returned. Cancellation has no effect
1594  * after network activity has finished, however, and the update will return successfully (or return an error sent by the server) if it is first
1595  * cancelled after network activity has finished. See the <link linkend="cancellable-support">overview of cancellation</link> for
1596  * more details.
1597  *
1598  * If there is an error updating the entry, a %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error will be returned. Currently, subclasses
1599  * <emphasis>cannot</emphasis> cannot override this or provide more specific errors.
1600  *
1601  * Return value: (transfer full): an updated #GDataEntry, or %NULL; unref with g_object_unref()
1602  *
1603  * Since: 0.9.0
1604  */
1605 GDataEntry *
gdata_service_update_entry(GDataService * self,GDataAuthorizationDomain * domain,GDataEntry * entry,GCancellable * cancellable,GError ** error)1606 gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry, GCancellable *cancellable, GError **error)
1607 {
1608 	GDataEntry *updated_entry;
1609 	GDataLink *_link;
1610 	SoupMessage *message;
1611 	gchar *upload_data;
1612 	guint status;
1613 	GDataParsableClass *klass;
1614 
1615 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
1616 	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
1617 	g_return_val_if_fail (GDATA_IS_ENTRY (entry), NULL);
1618 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
1619 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1620 
1621 	/* Append the data */
1622 	klass = GDATA_PARSABLE_GET_CLASS (entry);
1623 	g_assert (klass->get_content_type != NULL);
1624 	if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) {
1625 		/* Get the edit URI */
1626 		_link = gdata_entry_look_up_link (entry, GDATA_LINK_SELF);
1627 		g_assert (_link != NULL);
1628 		message = _gdata_service_build_message (self, domain, SOUP_METHOD_PUT, gdata_link_get_uri (_link), gdata_entry_get_etag (entry), TRUE);
1629 		upload_data = gdata_parsable_get_json (GDATA_PARSABLE (entry));
1630 		soup_message_set_request (message, "application/json", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
1631 	} else {
1632 		/* Get the edit URI */
1633 		_link = gdata_entry_look_up_link (entry, GDATA_LINK_EDIT);
1634 		g_assert (_link != NULL);
1635 		message = _gdata_service_build_message (self, domain, SOUP_METHOD_PUT, gdata_link_get_uri (_link), gdata_entry_get_etag (entry), TRUE);
1636 		upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
1637 		soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
1638 	}
1639 
1640 	/* Send the message */
1641 	status = _gdata_service_send_message (self, message, cancellable, error);
1642 
1643 	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
1644 		/* Redirect error or cancelled */
1645 		g_object_unref (message);
1646 		return NULL;
1647 	} else if (status != SOUP_STATUS_OK) {
1648 		/* Error */
1649 		GDataServiceClass *service_klass = GDATA_SERVICE_GET_CLASS (self);
1650 		g_assert (service_klass->parse_error_response != NULL);
1651 		service_klass->parse_error_response (self, GDATA_OPERATION_UPDATE, status, message->reason_phrase, message->response_body->data,
1652 		                                     message->response_body->length, error);
1653 		g_object_unref (message);
1654 		return NULL;
1655 	}
1656 
1657 	/* Parse the XML; create and return a new GDataEntry of the same type as @entry */
1658 	if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) {
1659 		updated_entry = GDATA_ENTRY (gdata_parsable_new_from_json (G_OBJECT_TYPE (entry), message->response_body->data,
1660 		                         message->response_body->length, error));
1661 	} else {
1662 		updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), message->response_body->data,
1663 		                             message->response_body->length, error));
1664 	}
1665 	g_object_unref (message);
1666 
1667 	return updated_entry;
1668 }
1669 
1670 typedef struct {
1671 	GDataAuthorizationDomain *domain;
1672 	GDataEntry *entry;
1673 } DeleteEntryAsyncData;
1674 
1675 static void
delete_entry_async_data_free(DeleteEntryAsyncData * data)1676 delete_entry_async_data_free (DeleteEntryAsyncData *data)
1677 {
1678 	if (data->domain != NULL)
1679 		g_object_unref (data->domain);
1680 
1681 	if (data->entry != NULL)
1682 		g_object_unref (data->entry);
1683 
1684 	g_slice_free (DeleteEntryAsyncData, data);
1685 }
1686 
1687 static void
delete_entry_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)1688 delete_entry_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
1689 {
1690 	GDataService *service = GDATA_SERVICE (source_object);
1691 	g_autoptr(GError) error = NULL;
1692 	DeleteEntryAsyncData *data = task_data;
1693 
1694 	/* Delete the entry and return */
1695 	if (!gdata_service_delete_entry (service, data->domain, data->entry, cancellable, &error))
1696 		g_task_return_error (task, g_steal_pointer (&error));
1697 	else
1698 		g_task_return_boolean (task, TRUE);
1699 }
1700 
1701 /**
1702  * gdata_service_delete_entry_async:
1703  * @self: a #GDataService
1704  * @domain: (allow-none): the #GDataAuthorizationDomain the deletion falls under, or %NULL
1705  * @entry: the #GDataEntry to delete
1706  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
1707  * @callback: a #GAsyncReadyCallback to call when deletion is finished, or %NULL
1708  * @user_data: (closure): data to pass to the @callback function
1709  *
1710  * Deletes @entry from the server. @self and @entry are both reffed when this function is called,
1711  * so can safely be unreffed after this function returns.
1712  *
1713  * For more details, see gdata_service_delete_entry(), which is the synchronous version of this function.
1714  *
1715  * When the operation is finished, @callback will be called. You can then call gdata_service_delete_entry_finish()
1716  * to get the results of the operation.
1717  *
1718  * Since: 0.9.0
1719  */
1720 void
gdata_service_delete_entry_async(GDataService * self,GDataAuthorizationDomain * domain,GDataEntry * entry,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1721 gdata_service_delete_entry_async (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry,
1722                                   GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
1723 {
1724 	g_autoptr(GTask) task = NULL;
1725 	DeleteEntryAsyncData *data;
1726 
1727 	g_return_if_fail (GDATA_IS_SERVICE (self));
1728 	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
1729 	g_return_if_fail (GDATA_IS_ENTRY (entry));
1730 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
1731 
1732 	data = g_slice_new (DeleteEntryAsyncData);
1733 	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
1734 	data->entry = g_object_ref (entry);
1735 
1736 	task = g_task_new (self, cancellable, callback, user_data);
1737 	g_task_set_source_tag (task, gdata_service_delete_entry_async);
1738 	g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) delete_entry_async_data_free);
1739 	g_task_run_in_thread (task, delete_entry_thread);
1740 }
1741 
1742 /**
1743  * gdata_service_delete_entry_finish:
1744  * @self: a #GDataService
1745  * @async_result: a #GAsyncResult
1746  * @error: a #GError, or %NULL
1747  *
1748  * Finishes an asynchronous entry deletion operation started with gdata_service_delete_entry_async().
1749  *
1750  * Return value: %TRUE on success, %FALSE otherwise
1751  *
1752  * Since: 0.3.0
1753  */
1754 gboolean
gdata_service_delete_entry_finish(GDataService * self,GAsyncResult * async_result,GError ** error)1755 gdata_service_delete_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error)
1756 {
1757 	g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE);
1758 	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
1759 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1760 	g_return_val_if_fail (g_task_is_valid (async_result, self), FALSE);
1761 	g_return_val_if_fail (g_async_result_is_tagged (async_result, gdata_service_delete_entry_async), FALSE);
1762 
1763 	return g_task_propagate_boolean (G_TASK (async_result), error);
1764 }
1765 
1766 /**
1767  * gdata_service_delete_entry:
1768  * @self: a #GDataService
1769  * @domain: (allow-none): the #GDataAuthorizationDomain the deletion falls under, or %NULL
1770  * @entry: the #GDataEntry to delete
1771  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
1772  * @error: a #GError, or %NULL
1773  *
1774  * Deletes @entry from the server. For more information about the concept of deleting entries, see the
1775  * <ulink type="http" url="http://code.google.com/apis/gdata/docs/2.0/basics.html#DeletingEntry">online documentation</ulink> for the GData
1776  * protocol.
1777  *
1778  * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
1779  * If the operation was cancelled before or during network activity, the error %G_IO_ERROR_CANCELLED will be returned. Cancellation has no effect
1780  * after network activity has finished, however, and the deletion will return successfully (or return an error sent by the server) if it is first
1781  * cancelled after network activity has finished. See the <link linkend="cancellable-support">overview of cancellation</link> for
1782  * more details.
1783  *
1784  * If there is an error deleting the entry, a %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error will be returned. Currently, subclasses
1785  * <emphasis>cannot</emphasis> cannot override this or provide more specific errors.
1786  *
1787  * Return value: %TRUE on success, %FALSE otherwise
1788  *
1789  * Since: 0.9.0
1790  */
1791 gboolean
gdata_service_delete_entry(GDataService * self,GDataAuthorizationDomain * domain,GDataEntry * entry,GCancellable * cancellable,GError ** error)1792 gdata_service_delete_entry (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry, GCancellable *cancellable, GError **error)
1793 {
1794 	GDataLink *_link;
1795 	SoupMessage *message;
1796 	guint status;
1797 	gchar *fixed_uri;
1798 	GDataParsableClass *klass;
1799 
1800 	g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE);
1801 	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), FALSE);
1802 	g_return_val_if_fail (GDATA_IS_ENTRY (entry), FALSE);
1803 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
1804 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1805 
1806 	/* 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. */
1807 	klass = GDATA_PARSABLE_GET_CLASS (entry);
1808 	g_assert (klass->get_content_type != NULL);
1809 	if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) {
1810 		_link = gdata_entry_look_up_link (entry, GDATA_LINK_SELF);
1811 	} else {
1812 		_link = gdata_entry_look_up_link (entry, GDATA_LINK_EDIT);
1813 	}
1814 	g_assert (_link != NULL);
1815 
1816 	fixed_uri = _gdata_service_fix_uri_scheme (gdata_link_get_uri (_link));
1817 	message = _gdata_service_build_message (self, domain, SOUP_METHOD_DELETE, fixed_uri, gdata_entry_get_etag (entry), TRUE);
1818 	g_free (fixed_uri);
1819 
1820 	/* Send the message */
1821 	status = _gdata_service_send_message (self, message, cancellable, error);
1822 
1823 	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
1824 		/* Redirect error or cancelled */
1825 		g_object_unref (message);
1826 		return FALSE;
1827 	} else if (status != SOUP_STATUS_OK && status != SOUP_STATUS_NO_CONTENT) {
1828 		/* Error */
1829 		GDataServiceClass *service_klass = GDATA_SERVICE_GET_CLASS (self);
1830 		g_assert (service_klass->parse_error_response != NULL);
1831 		service_klass->parse_error_response (self, GDATA_OPERATION_DELETION, status, message->reason_phrase, message->response_body->data,
1832 		                                     message->response_body->length, error);
1833 		g_object_unref (message);
1834 		return FALSE;
1835 	}
1836 
1837 	g_object_unref (message);
1838 
1839 	return TRUE;
1840 }
1841 
1842 static void
notify_proxy_uri_cb(GObject * gobject,GParamSpec * pspec,GObject * self)1843 notify_proxy_uri_cb (GObject *gobject, GParamSpec *pspec, GObject *self)
1844 {
1845 	g_object_notify (self, "proxy-uri");
1846 }
1847 
1848 /* Static function which isn't deprecated so we can continue using it internally. */
1849 static SoupURI *
_get_proxy_uri(GDataService * self)1850 _get_proxy_uri (GDataService *self)
1851 {
1852 	SoupURI *proxy_uri;
1853 
1854 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
1855 
1856 	g_object_get (self->priv->session, SOUP_SESSION_PROXY_URI, &proxy_uri, NULL);
1857 	g_object_unref (proxy_uri); /* remove the ref added by g_object_get */
1858 
1859 	return proxy_uri;
1860 }
1861 
1862 /**
1863  * gdata_service_get_proxy_uri:
1864  * @self: a #GDataService
1865  *
1866  * Gets the proxy URI on the #GDataService's #SoupSession.
1867  *
1868  * Return value: (transfer none): the proxy URI, or %NULL
1869  *
1870  * Since: 0.2.0
1871  * Deprecated: 0.15.0: Use gdata_service_get_proxy_resolver() instead, which gives more flexibility over the proxy used.
1872  */
1873 SoupURI *
gdata_service_get_proxy_uri(GDataService * self)1874 gdata_service_get_proxy_uri (GDataService *self)
1875 {
1876 	return _get_proxy_uri (self);
1877 }
1878 
1879 /* Static function which isn't deprecated so we can continue using it internally. */
1880 static void
_set_proxy_uri(GDataService * self,SoupURI * proxy_uri)1881 _set_proxy_uri (GDataService *self, SoupURI *proxy_uri)
1882 {
1883 	g_return_if_fail (GDATA_IS_SERVICE (self));
1884 	g_object_set (self->priv->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
1885 	g_object_notify (G_OBJECT (self), "proxy-uri");
1886 }
1887 
1888 /**
1889  * gdata_service_set_proxy_uri:
1890  * @self: a #GDataService
1891  * @proxy_uri: (allow-none): the proxy URI, or %NULL
1892  *
1893  * Sets the proxy URI on the #SoupSession used internally by the given #GDataService.
1894  * This forces all requests through the given proxy.
1895  *
1896  * If @proxy_uri is %NULL, no proxy will be used.
1897  *
1898  * Note that if a #GDataAuthorizer is being used with this #GDataService, the authorizer might also need its proxy URI setting.
1899  *
1900  * Since: 0.2.0
1901  * Deprecated: 0.15.0: Use gdata_service_set_proxy_resolver() instead, which gives more flexibility over the proxy used.
1902  */
1903 void
gdata_service_set_proxy_uri(GDataService * self,SoupURI * proxy_uri)1904 gdata_service_set_proxy_uri (GDataService *self, SoupURI *proxy_uri)
1905 {
1906 	_set_proxy_uri (self, proxy_uri);
1907 }
1908 
1909 /**
1910  * gdata_service_get_proxy_resolver:
1911  * @self: a #GDataService
1912  *
1913  * Gets the #GProxyResolver on the #GDataService's #SoupSession.
1914  *
1915  * Return value: (transfer none) (allow-none): a #GProxyResolver, or %NULL
1916  *
1917  * Since: 0.15.0
1918  */
1919 GProxyResolver *
gdata_service_get_proxy_resolver(GDataService * self)1920 gdata_service_get_proxy_resolver (GDataService *self)
1921 {
1922 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
1923 
1924 	return self->priv->proxy_resolver;
1925 }
1926 
1927 /**
1928  * gdata_service_set_proxy_resolver:
1929  * @self: a #GDataService
1930  * @proxy_resolver: (allow-none): a #GProxyResolver, or %NULL
1931  *
1932  * Sets the #GProxyResolver on the #SoupSession used internally by the given #GDataService.
1933  *
1934  * Setting this will clear the #GDataService:proxy-uri property.
1935  *
1936  * Since: 0.15.0
1937  */
1938 void
gdata_service_set_proxy_resolver(GDataService * self,GProxyResolver * proxy_resolver)1939 gdata_service_set_proxy_resolver (GDataService *self, GProxyResolver *proxy_resolver)
1940 {
1941 	g_return_if_fail (GDATA_IS_SERVICE (self));
1942 	g_return_if_fail (proxy_resolver == NULL || G_IS_PROXY_RESOLVER (proxy_resolver));
1943 
1944 	if (proxy_resolver != NULL) {
1945 		g_object_ref (proxy_resolver);
1946 	}
1947 
1948 	g_clear_object (&self->priv->proxy_resolver);
1949 	self->priv->proxy_resolver = proxy_resolver;
1950 
1951 	g_object_notify (G_OBJECT (self), "proxy-resolver");
1952 }
1953 
1954 static void
notify_timeout_cb(GObject * gobject,GParamSpec * pspec,GObject * self)1955 notify_timeout_cb (GObject *gobject, GParamSpec *pspec, GObject *self)
1956 {
1957 	g_object_notify (self, "timeout");
1958 }
1959 
1960 /**
1961  * gdata_service_get_timeout:
1962  * @self: a #GDataService
1963  *
1964  * Gets the #GDataService:timeout property; the network timeout, in seconds.
1965  *
1966  * Return value: the timeout, or <code class="literal">0</code>
1967  *
1968  * Since: 0.7.0
1969  */
1970 guint
gdata_service_get_timeout(GDataService * self)1971 gdata_service_get_timeout (GDataService *self)
1972 {
1973 	guint timeout;
1974 
1975 	g_return_val_if_fail (GDATA_IS_SERVICE (self), 0);
1976 
1977 	g_object_get (self->priv->session, SOUP_SESSION_TIMEOUT, &timeout, NULL);
1978 
1979 	return timeout;
1980 }
1981 
1982 /**
1983  * gdata_service_set_timeout:
1984  * @self: a #GDataService
1985  * @timeout: the timeout, or <code class="literal">0</code>
1986  *
1987  * Sets the #GDataService:timeout property; the network timeout, in seconds.
1988  *
1989  * If @timeout is <code class="literal">0</code>, network operations will never time out.
1990  *
1991  * Note that if a #GDataAuthorizer is being used with this #GDataService, the authorizer might also need its timeout setting.
1992  *
1993  * Since: 0.7.0
1994  */
1995 void
gdata_service_set_timeout(GDataService * self,guint timeout)1996 gdata_service_set_timeout (GDataService *self, guint timeout)
1997 {
1998 	g_return_if_fail (GDATA_IS_SERVICE (self));
1999 	g_object_set (self->priv->session, SOUP_SESSION_TIMEOUT, timeout, NULL);
2000 	g_object_notify (G_OBJECT (self), "timeout");
2001 }
2002 
2003 SoupSession *
_gdata_service_get_session(GDataService * self)2004 _gdata_service_get_session (GDataService *self)
2005 {
2006 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
2007 	return self->priv->session;
2008 }
2009 
2010 /*
2011  * _gdata_service_get_scheme:
2012  *
2013  * Returns the name of the scheme to use, which will always be <code class="literal">https</code>. The return type used to vary according to the
2014  * environment variable <code class="literal">LIBGDATA_FORCE_HTTP</code>, but Google has since switched to using HTTPS exclusively.
2015  *
2016  * See <ulink type="http" url="http://googlecode.blogspot.com/2011/03/improving-security-of-google-apis-with.html">Improving the security of Google
2017  * APIs with SSL</ulink>.
2018  *
2019  * Return value: the scheme to use (<code class="literal">https</code>)
2020  *
2021  * Since: 0.6.0
2022  */
2023 const gchar *
_gdata_service_get_scheme(void)2024 _gdata_service_get_scheme (void)
2025 {
2026 	return "https";
2027 }
2028 
2029 /*
2030  * _gdata_service_build_uri:
2031  * @format: a standard printf() format string
2032  * @...: the arguments to insert in the output
2033  *
2034  * Builds a URI from the given @format string, replacing each <code class="literal">%%s</code> format placeholder with a URI-escaped version of the
2035  * corresponding argument, and each <code class="literal">%%p</code> format placeholder with a non-escaped version of the corresponding argument. No
2036  * other printf() format placeholders are supported at the moment except <code class="literal">%%d</code>, which prints a signed integer; and
2037  * <code class="literal">%%</code>, which prints a literal percent symbol.
2038  *
2039  * The returned URI is guaranteed to use the scheme returned by _gdata_service_get_scheme(). The format string, once all the arguments have been
2040  * inserted into it, must include a scheme, but it doesn't matter which one.
2041  *
2042  * Return value: a newly allocated URI string; free with g_free()
2043  */
2044 gchar *
_gdata_service_build_uri(const gchar * format,...)2045 _gdata_service_build_uri (const gchar *format, ...)
2046 {
2047 	const gchar *p;
2048 	gchar *fixed_uri;
2049 	GString *uri;
2050 	va_list args;
2051 
2052 	g_return_val_if_fail (format != NULL, NULL);
2053 
2054 	/* Allocate a GString to build the URI in with at least as much space as the format string */
2055 	uri = g_string_sized_new (strlen (format));
2056 
2057 	/* Build the URI */
2058 	va_start (args, format);
2059 
2060 	for (p = format; *p != '\0'; p++) {
2061 		if (*p != '%') {
2062 			g_string_append_c (uri, *p);
2063 			continue;
2064 		}
2065 
2066 		switch(*++p) {
2067 			case 's':
2068 				g_string_append_uri_escaped (uri, va_arg (args, gchar*), NULL, TRUE);
2069 				break;
2070 			case 'p':
2071 				g_string_append (uri, va_arg (args, gchar*));
2072 				break;
2073 			case 'd':
2074 				g_string_append_printf (uri, "%d", va_arg (args, gint));
2075 				break;
2076 			case '%':
2077 				g_string_append_c (uri, '%');
2078 				break;
2079 			default:
2080 				g_error ("Unrecognized format placeholder '%%%c' in format '%s'. This is a programmer error.", *p, format);
2081 				break;
2082 		}
2083 	}
2084 
2085 	va_end (args);
2086 
2087 	/* Fix the scheme to always be HTTPS */
2088 	fixed_uri = _gdata_service_fix_uri_scheme (uri->str);
2089 	g_string_free (uri, TRUE);
2090 
2091 	return fixed_uri;
2092 }
2093 
2094 /**
2095  * _gdata_service_fix_uri_scheme:
2096  * @uri: an URI with either HTTP or HTTPS as the scheme
2097  *
2098  * Fixes the given URI to always have HTTPS as its scheme.
2099  *
2100  * Return value: (transfer full): the URI with HTTPS as its scheme
2101  *
2102  * Since: 0.9.0
2103  */
2104 gchar *
_gdata_service_fix_uri_scheme(const gchar * uri)2105 _gdata_service_fix_uri_scheme (const gchar *uri)
2106 {
2107 	g_return_val_if_fail (uri != NULL && *uri != '\0', NULL);
2108 
2109 	/* Ensure we're using the correct scheme (HTTP or HTTPS) */
2110 	if (g_str_has_prefix (uri, "https") == FALSE) {
2111 		gchar *fixed_uri, **pieces;
2112 
2113 		pieces = g_strsplit (uri, ":", 2);
2114 		g_assert (pieces[0] != NULL && pieces[1] != NULL && pieces[2] == NULL);
2115 
2116 		fixed_uri = g_strconcat ("https:", pieces[1], NULL);
2117 
2118 		g_strfreev (pieces);
2119 
2120 		return fixed_uri;
2121 	}
2122 
2123 	return g_strdup (uri);
2124 }
2125 
2126 /**
2127  * _gdata_service_get_https_port:
2128  *
2129  * Gets the destination TCP/IP port number which libgdata should use for all outbound HTTPS traffic.
2130  * This defaults to 443, but may be overridden using the <code class="literal">LIBGDATA_HTTPS_PORT</code>
2131  * environment variable. This is intended to allow network traffic to be redirected to a local server for
2132  * unit testing, with a listening port above 1024 so the tests don't need root privileges.
2133  *
2134  * The value returned by this function may change at any time (e.g. between unit tests), so callers must not cache the result.
2135  *
2136  * Return value: port number to use for HTTPS traffic
2137  */
2138 guint
_gdata_service_get_https_port(void)2139 _gdata_service_get_https_port (void)
2140 {
2141 	const gchar *port_string;
2142 
2143 	/* Allow changing the HTTPS port just for testing. */
2144 	port_string = g_getenv ("LIBGDATA_HTTPS_PORT");
2145 	if (port_string != NULL) {
2146 		const gchar *end;
2147 
2148 		guint64 port = g_ascii_strtoull (port_string, (gchar **) &end, 10);
2149 
2150 		if (port != 0 && *end == '\0') {
2151 			g_debug ("Overriding message port to %" G_GUINT64_FORMAT ".", port);
2152 			return port;
2153 		}
2154 	}
2155 
2156 	/* Return the default. */
2157 	return 443;
2158 }
2159 
2160 /*
2161  * debug_handler:
2162  *
2163  * GLib debug message handler, which is passed all messages from g_debug() calls, and decides whether to print them.
2164  */
2165 static void
debug_handler(const char * log_domain,GLogLevelFlags log_level,const char * message,gpointer user_data)2166 debug_handler (const char *log_domain, GLogLevelFlags log_level, const char *message, gpointer user_data)
2167 {
2168 	if (_gdata_service_get_log_level () != GDATA_LOG_NONE)
2169 		g_log_default_handler (log_domain, log_level, message, NULL);
2170 }
2171 
2172 /*
2173  * soup_log_printer:
2174  *
2175  * Log printer for the libsoup logging functionality, which just marshals all soup log output to the standard GLib logging framework
2176  * (and thus to debug_handler(), above).
2177  */
2178 static void
soup_log_printer(SoupLogger * logger,SoupLoggerLogLevel level,char direction,const char * data,gpointer user_data)2179 soup_log_printer (SoupLogger *logger, SoupLoggerLogLevel level, char direction, const char *data, gpointer user_data)
2180 {
2181 	gboolean filter_data;
2182 	gchar *_data = NULL;
2183 
2184 	filter_data = (_gdata_service_get_log_level () > GDATA_LOG_NONE && _gdata_service_get_log_level () < GDATA_LOG_FULL_UNREDACTED) ? TRUE : FALSE;
2185 
2186 	if (filter_data == TRUE) {
2187 		/* Filter out lines which look like they might contain usernames, passwords or auth. tokens. */
2188 		if (direction == '>' && g_str_has_prefix (data, "Authorization: GoogleLogin ") == TRUE) {
2189 			_data = g_strdup ("Authorization: GoogleLogin <redacted>");
2190 		} else if (direction == '>' && g_str_has_prefix (data, "Authorization: OAuth ") == TRUE) {
2191 			_data = g_strdup ("Authorization: OAuth <redacted>");
2192 		} else if (direction == '<' && g_str_has_prefix (data, "Set-Cookie: ") == TRUE) {
2193 			_data = g_strdup ("Set-Cookie: <redacted>");
2194 		} else if (direction == '<' && g_str_has_prefix (data, "Location: ") == TRUE) {
2195 			/* Looks like:
2196 			 * "Location: https://www.google.com/calendar/feeds/default/owncalendars/full?gsessionid=sBjmp05m5i67exYA51XjDA". */
2197 			SoupURI *uri;
2198 			gchar *_uri;
2199 			GHashTable *params;
2200 
2201 			uri = soup_uri_new (data + strlen ("Location: "));
2202 
2203 			if (uri->query != NULL) {
2204 				params = soup_form_decode (uri->query);
2205 
2206 				/* strdup()s are necessary because the hash table's set up to free keys. */
2207 				if (g_hash_table_lookup (params, "gsessionid") != NULL) {
2208 					g_hash_table_insert (params, (gpointer) g_strdup ("gsessionid"), (gpointer) "<redacted>");
2209 				}
2210 
2211 				soup_uri_set_query_from_form (uri, params);
2212 				g_hash_table_destroy (params);
2213 			}
2214 
2215 			_uri = soup_uri_to_string (uri, FALSE);
2216 			_data = g_strconcat ("Location: ", _uri, NULL);
2217 			g_free (_uri);
2218 
2219 			soup_uri_free (uri);
2220 		} else if (direction == '<' && g_str_has_prefix (data, "SID=") == TRUE) {
2221 			_data = g_strdup ("SID=<redacted>");
2222 		} else if (direction == '<' && g_str_has_prefix (data, "LSID=") == TRUE) {
2223 			_data = g_strdup ("LSID=<redacted>");
2224 		} else if (direction == '<' && g_str_has_prefix (data, "Auth=") == TRUE) {
2225 			_data = g_strdup ("Auth=<redacted>");
2226 		} else if (direction == '>' && g_str_has_prefix (data, "accountType=") == TRUE) {
2227 			/* Looks like: "> accountType=HOSTED%5FOR%5FGOOGLE&Email=[e-mail address]&Passwd=[plaintex password]"
2228 			               "&service=[service name]&source=ytapi%2DGNOME%2Dlibgdata%2D444fubtt%2D0". */
2229 			GHashTable *params = soup_form_decode (data);
2230 
2231 			/* strdup()s are necessary because the hash table's set up to free keys. */
2232 			if (g_hash_table_lookup (params, "Email") != NULL) {
2233 				g_hash_table_insert (params, (gpointer) g_strdup ("Email"), (gpointer) "<redacted>");
2234 			}
2235 			if (g_hash_table_lookup (params, "Passwd") != NULL) {
2236 				g_hash_table_insert (params, (gpointer) g_strdup ("Passwd"), (gpointer) "<redacted>");
2237 			}
2238 
2239 			_data = soup_form_encode_hash (params);
2240 
2241 			g_hash_table_destroy (params);
2242 		} else if (direction == '<' && g_str_has_prefix (data, "oauth_token=") == TRUE) {
2243 			/* Looks like: "< oauth_token=4%2FI-WU7sBzKk5GhGlQUF8a_TCZRnb7&oauth_token_secret=qTTTJg3no25auiiWFerzjW4I"
2244 			               "&oauth_callback_confirmed=true". */
2245 			GHashTable *params = soup_form_decode (data);
2246 
2247 			/* strdup()s are necessary because the hash table's set up to free keys. */
2248 			if (g_hash_table_lookup (params, "oauth_token") != NULL) {
2249 				g_hash_table_insert (params, (gpointer) g_strdup ("oauth_token"), (gpointer) "<redacted>");
2250 			}
2251 			if (g_hash_table_lookup (params, "oauth_token_secret") != NULL) {
2252 				g_hash_table_insert (params, (gpointer) g_strdup ("oauth_token_secret"), (gpointer) "<redacted>");
2253 			}
2254 
2255 			_data = soup_form_encode_hash (params);
2256 
2257 			g_hash_table_destroy (params);
2258 		} else if (direction == '>' && g_str_has_prefix (data, "X-GData-Key: key=") == TRUE) {
2259 			/* Looks like: "> X-GData-Key: key=[dev key in hex]". */
2260 			_data = g_strdup ("X-GData-Key: key=<redacted>");
2261 		} else {
2262 			/* Nothing to redact. */
2263 			_data = g_strdup (data);
2264 		}
2265 	} else {
2266 		/* Don't dupe the string. */
2267 		_data = (gchar*) data;
2268 	}
2269 
2270 	/* Log the data. */
2271 	g_debug ("%c %s", direction, _data);
2272 
2273 	if (filter_data == TRUE) {
2274 		g_free (_data);
2275 	}
2276 }
2277 
2278 /**
2279  * _gdata_service_get_log_level:
2280  *
2281  * Returns the logging level for the library, currently set by an environment variable.
2282  *
2283  * Return value: the log level
2284  *
2285  * Since: 0.7.0
2286  */
2287 GDataLogLevel
_gdata_service_get_log_level(void)2288 _gdata_service_get_log_level (void)
2289 {
2290 	static int level = -1;
2291 
2292 	if (level < 0) {
2293 		const gchar *envvar = g_getenv ("LIBGDATA_DEBUG");
2294 		if (envvar != NULL)
2295 			level = atoi (envvar);
2296 		level = MIN (MAX (level, 0), GDATA_LOG_FULL_UNREDACTED);
2297 	}
2298 
2299 	return level;
2300 }
2301 
2302 /* Build a User-Agent value to send to the server.
2303  *
2304  * If we support gzip, we can request gzip from the server by both including
2305  * the appropriate Accept-Encoding header and putting 'gzip' in the User-Agent
2306  * header:
2307  *  - https://developers.google.com/drive/web/performance#gzip
2308  *  - http://googleappsdeveloper.blogspot.co.uk/2011/12/optimizing-bandwidth-usage-with-gzip.html
2309  */
2310 static gchar *
build_user_agent(gboolean supports_gzip)2311 build_user_agent (gboolean supports_gzip)
2312 {
2313 	if (supports_gzip) {
2314 		return g_strdup_printf ("libgdata/%s - gzip", VERSION);
2315 	} else {
2316 		return g_strdup_printf ("libgdata/%s", VERSION);
2317 	}
2318 }
2319 
2320 /**
2321  * _gdata_service_build_session:
2322  *
2323  * Build a new #SoupSession, enabling GNOME features if support has been compiled for them, and adding a log printer which is hooked into
2324  * libgdata's logging functionality.
2325  *
2326  * Return value: a new #SoupSession; unref with g_object_unref()
2327  *
2328  * Since: 0.9.0
2329  */
2330 SoupSession *
_gdata_service_build_session(void)2331 _gdata_service_build_session (void)
2332 {
2333 	SoupSession *session;
2334 	gboolean ssl_strict = TRUE;
2335 	gchar *user_agent;
2336 
2337 	/* Iff LIBGDATA_LAX_SSL_CERTIFICATES=1, relax SSL certificate validation to allow using invalid/unsigned certificates for testing. */
2338 	if (g_strcmp0 (g_getenv ("LIBGDATA_LAX_SSL_CERTIFICATES"), "1") == 0) {
2339 		ssl_strict = FALSE;
2340 	}
2341 
2342 	session = soup_session_new_with_options ("ssl-strict", ssl_strict,
2343 	                                         "timeout", 0,
2344 	                                         NULL);
2345 
2346 	user_agent = build_user_agent (soup_session_has_feature (session, SOUP_TYPE_CONTENT_DECODER));
2347 	g_object_set (session, "user-agent", user_agent, NULL);
2348 	g_free (user_agent);
2349 
2350 	soup_session_add_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER_DEFAULT);
2351 
2352 	/* Log all libsoup traffic if debugging's turned on */
2353 	if (_gdata_service_get_log_level () > GDATA_LOG_MESSAGES) {
2354 		SoupLoggerLogLevel level;
2355 		SoupLogger *logger;
2356 
2357 		switch (_gdata_service_get_log_level ()) {
2358 			case GDATA_LOG_FULL_UNREDACTED:
2359 			case GDATA_LOG_FULL:
2360 				level = SOUP_LOGGER_LOG_BODY;
2361 				break;
2362 			case GDATA_LOG_HEADERS:
2363 				level = SOUP_LOGGER_LOG_HEADERS;
2364 				break;
2365 			case GDATA_LOG_MESSAGES:
2366 			case GDATA_LOG_NONE:
2367 			default:
2368 				g_assert_not_reached ();
2369 		}
2370 
2371 		logger = soup_logger_new (level, -1);
2372 		soup_logger_set_printer (logger, (SoupLoggerPrinter) soup_log_printer, NULL, NULL);
2373 
2374 		soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
2375 
2376 		g_object_unref (logger);
2377 	}
2378 
2379 	return session;
2380 }
2381 
2382 /**
2383  * gdata_service_get_locale:
2384  * @self: a #GDataService
2385  *
2386  * Returns the locale currently being used for network requests, or %NULL if the locale is the default.
2387  *
2388  * Return value: the current locale
2389  *
2390  * Since: 0.7.0
2391  */
2392 const gchar *
gdata_service_get_locale(GDataService * self)2393 gdata_service_get_locale (GDataService *self)
2394 {
2395 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
2396 	return self->priv->locale;
2397 }
2398 
2399 /**
2400  * gdata_service_set_locale:
2401  * @self: a #GDataService
2402  * @locale: (allow-none): the new locale in Unix locale format, or %NULL for the default locale
2403  *
2404  * Set the locale used for network requests to @locale, given in standard Unix locale format. See #GDataService:locale for more details.
2405  *
2406  * Note that while it's possible to change the locale after sending network requests, it is unsupported, as the server-side software may behave
2407  * unexpectedly. The only supported use of this function is after creation of a service, but before any network requests are made.
2408  *
2409  * Since: 0.7.0
2410  */
2411 void
gdata_service_set_locale(GDataService * self,const gchar * locale)2412 gdata_service_set_locale (GDataService *self, const gchar *locale)
2413 {
2414 	g_return_if_fail (GDATA_IS_SERVICE (self));
2415 
2416 	g_free (self->priv->locale);
2417 	self->priv->locale = g_strdup (locale);
2418 	g_object_notify (G_OBJECT (self), "locale");
2419 }
2420 
2421 /*
2422  * _gdata_service_secure_strdup:
2423  * @str: string (which may be in pageable memory) to be duplicated, or %NULL
2424  *
2425  * Duplicate a string into non-pageable memory (if libgdata has been compiled with HAVE_GNOME) or just fall back to g_strdup() (if libgdata hasn't).
2426  * Passing %NULL to this function will cause %NULL to be returned.
2427  *
2428  * Strings allocated using this function must be freed using _gdata_service_secure_strfree().
2429  *
2430  * Return value: non-pageable copy of @str, or %NULL
2431  * Since: 0.11.0
2432  */
2433 GDataSecureString
_gdata_service_secure_strdup(const gchar * str)2434 _gdata_service_secure_strdup (const gchar *str)
2435 {
2436 #ifdef HAVE_GNOME
2437 	return gcr_secure_memory_strdup (str);
2438 #else /* if !HAVE_GNOME */
2439 	return g_strdup (str);
2440 #endif /* !HAVE_GNOME */
2441 }
2442 
2443 /*
2444  * _gdata_service_secure_strndup:
2445  * @str: string (which may be in pageable memory) to be duplicated, or %NULL
2446  * @n_bytes: maximum number of bytes to copy from @str
2447  *
2448  * Duplicate at most @n_bytes bytes from @str into non-pageable memory. See _gdata_service_secure_strdup() for more information; this function is just
2449  * a version of that with the same semantics as strndup().
2450  *
2451  * Return value: non-pageable copy of at most the first @n_bytes bytes of @str, or %NULL
2452  * Since: 0.11.0
2453  */
2454 GDataSecureString
_gdata_service_secure_strndup(const gchar * str,gsize n_bytes)2455 _gdata_service_secure_strndup (const gchar *str, gsize n_bytes)
2456 {
2457 #ifdef HAVE_GNOME
2458 	gsize str_len;
2459 	GDataSecureString duped_str;
2460 
2461 	if (str == NULL) {
2462 		return NULL;
2463 	}
2464 
2465 	str_len = MIN (strlen (str), n_bytes);
2466 	duped_str = (GDataSecureString) gcr_secure_memory_alloc (str_len + 1);
2467 	strncpy (duped_str, str, str_len);
2468 	*(duped_str + str_len) = '\0';
2469 
2470 	return duped_str;
2471 #else /* if !HAVE_GNOME */
2472 	return g_strndup (str, n_bytes);
2473 #endif /* !HAVE_GNOME */
2474 }
2475 
2476 /*
2477  * _gdata_service_secure_strfree:
2478  * @str: a string to free, or %NULL
2479  *
2480  * Free a string which was allocated securely using _gdata_service_secure_strdup().
2481  * Passing %NULL to this function is safe.
2482  *
2483  * Since: 0.11.0
2484  */
2485 void
_gdata_service_secure_strfree(GDataSecureString str)2486 _gdata_service_secure_strfree (GDataSecureString str)
2487 {
2488 #ifdef HAVE_GNOME
2489 	gcr_secure_memory_free (str);
2490 #else /* if !HAVE_GNOME */
2491 	/* Poor man's approximation to non-pageable memory: the best we can do is ensure that we don't leak it in free memory.
2492 	 * This can't guarantee that it hasn't hit disk at some point, but does mean it can't hit disk in future. */
2493 	if (str != NULL) {
2494 		memset (str, 0, strlen (str));
2495 	}
2496 
2497 	g_free (str);
2498 #endif /* !HAVE_GNOME */
2499 }
2500