1 /*
2 * librest - RESTful web services access
3 * Copyright (c) 2008, 2009, Intel Corporation.
4 *
5 * Authors: Rob Bradford <rob@linux.intel.com>
6 * Ross Burton <ross@linux.intel.com>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms and conditions of the GNU Lesser General Public License,
10 * version 2.1, as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 */
22
23 /*
24 * TODO:
25 *
26 * Convenience API for authentication so that the user doesn't have to parse the
27 * XML themselves.
28 */
29
30 #include <config.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <rest/rest-proxy.h>
34 #include <libsoup/soup.h>
35 #include "flickr-proxy.h"
36 #include "flickr-proxy-private.h"
37 #include "flickr-proxy-call.h"
38
39 G_DEFINE_TYPE (FlickrProxy, flickr_proxy, REST_TYPE_PROXY)
40
41 enum {
42 PROP_0,
43 PROP_API_KEY,
44 PROP_SHARED_SECRET,
45 PROP_TOKEN,
46 };
47
48 GQuark
flickr_proxy_error_quark(void)49 flickr_proxy_error_quark (void)
50 {
51 return g_quark_from_static_string ("rest-flickr-proxy");
52 }
53
54 static RestProxyCall *
_new_call(RestProxy * proxy)55 _new_call (RestProxy *proxy)
56 {
57 RestProxyCall *call;
58
59 call = g_object_new (FLICKR_TYPE_PROXY_CALL,
60 "proxy", proxy,
61 NULL);
62
63 return call;
64 }
65
66 static void
flickr_proxy_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)67 flickr_proxy_get_property (GObject *object, guint property_id,
68 GValue *value, GParamSpec *pspec)
69 {
70 FlickrProxyPrivate *priv = FLICKR_PROXY_GET_PRIVATE (object);
71
72 switch (property_id) {
73 case PROP_API_KEY:
74 g_value_set_string (value, priv->api_key);
75 break;
76 case PROP_SHARED_SECRET:
77 g_value_set_string (value, priv->shared_secret);
78 break;
79 case PROP_TOKEN:
80 g_value_set_string (value, priv->token);
81 break;
82 default:
83 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
84 }
85 }
86
87 static void
flickr_proxy_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)88 flickr_proxy_set_property (GObject *object, guint property_id,
89 const GValue *value, GParamSpec *pspec)
90 {
91 FlickrProxyPrivate *priv = FLICKR_PROXY_GET_PRIVATE (object);
92
93 switch (property_id) {
94 case PROP_API_KEY:
95 if (priv->api_key)
96 g_free (priv->api_key);
97 priv->api_key = g_value_dup_string (value);
98 break;
99 case PROP_SHARED_SECRET:
100 if (priv->shared_secret)
101 g_free (priv->shared_secret);
102 priv->shared_secret = g_value_dup_string (value);
103 break;
104 case PROP_TOKEN:
105 if (priv->token)
106 g_free (priv->token);
107 priv->token = g_value_dup_string (value);
108 break;
109 default:
110 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
111 }
112 }
113
114 static void
flickr_proxy_finalize(GObject * object)115 flickr_proxy_finalize (GObject *object)
116 {
117 FlickrProxyPrivate *priv = FLICKR_PROXY_GET_PRIVATE (object);
118
119 g_free (priv->api_key);
120 g_free (priv->shared_secret);
121 g_free (priv->token);
122
123 G_OBJECT_CLASS (flickr_proxy_parent_class)->finalize (object);
124 }
125
126 #ifndef G_PARAM_STATIC_STRINGS
127 #define G_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)
128 #endif
129
130 static void
flickr_proxy_class_init(FlickrProxyClass * klass)131 flickr_proxy_class_init (FlickrProxyClass *klass)
132 {
133 GObjectClass *object_class = G_OBJECT_CLASS (klass);
134 RestProxyClass *proxy_class = REST_PROXY_CLASS (klass);
135 GParamSpec *pspec;
136
137 g_type_class_add_private (klass, sizeof (FlickrProxyPrivate));
138
139 object_class->get_property = flickr_proxy_get_property;
140 object_class->set_property = flickr_proxy_set_property;
141 object_class->finalize = flickr_proxy_finalize;
142
143 proxy_class->new_call = _new_call;
144
145 pspec = g_param_spec_string ("api-key", "api-key",
146 "The API key", NULL,
147 G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_STATIC_STRINGS);
148 g_object_class_install_property (object_class,
149 PROP_API_KEY,
150 pspec);
151
152 pspec = g_param_spec_string ("shared-secret", "shared-secret",
153 "The shared secret", NULL,
154 G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_STATIC_STRINGS);
155 g_object_class_install_property (object_class,
156 PROP_SHARED_SECRET,
157 pspec);
158
159 pspec = g_param_spec_string ("token", "token",
160 "The request or access token", NULL,
161 G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS);
162 g_object_class_install_property (object_class,
163 PROP_TOKEN,
164 pspec);
165 }
166
167 static void
flickr_proxy_init(FlickrProxy * self)168 flickr_proxy_init (FlickrProxy *self)
169 {
170 self->priv = FLICKR_PROXY_GET_PRIVATE (self);
171 }
172
173 RestProxy *
flickr_proxy_new(const char * api_key,const char * shared_secret)174 flickr_proxy_new (const char *api_key,
175 const char *shared_secret)
176 {
177 return flickr_proxy_new_with_token (api_key,
178 shared_secret,
179 NULL);
180 }
181
182 RestProxy *
flickr_proxy_new_with_token(const char * api_key,const char * shared_secret,const char * token)183 flickr_proxy_new_with_token (const char *api_key,
184 const char *shared_secret,
185 const char *token)
186 {
187 return g_object_new (FLICKR_TYPE_PROXY,
188 "api-key", api_key,
189 "shared-secret", shared_secret,
190 "token", token,
191 "url-format", "https://%s.flickr.com/services/%s/",
192 "binding-required", TRUE,
193 NULL);
194 }
195
196 /**
197 * flickr_proxy_get_api_key:
198 * @proxy: an #FlickrProxy
199 *
200 * Get the API key.
201 *
202 * Returns: the API key. This string is owned by #FlickrProxy and should not be
203 * freed.
204 */
205 const char *
flickr_proxy_get_api_key(FlickrProxy * proxy)206 flickr_proxy_get_api_key (FlickrProxy *proxy)
207 {
208 FlickrProxyPrivate *priv = FLICKR_PROXY_GET_PRIVATE (proxy);
209 return priv->api_key;
210 }
211
212 /**
213 * flickr_proxy_get_shared_secret:
214 * @proxy: an #FlickrProxy
215 *
216 * Get the shared secret for authentication.
217 *
218 * Returns: the shared secret. This string is owned by #FlickrProxy and should not be
219 * freed.
220 */
221 const char *
flickr_proxy_get_shared_secret(FlickrProxy * proxy)222 flickr_proxy_get_shared_secret (FlickrProxy *proxy)
223 {
224 FlickrProxyPrivate *priv = FLICKR_PROXY_GET_PRIVATE (proxy);
225 return priv->shared_secret;
226 }
227
228 /**
229 * flickr_proxy_get_token:
230 * @proxy: an #FlickrProxy
231 *
232 * Get the current token.
233 *
234 * Returns: the token, or %NULL if there is no token yet. This string is owned
235 * by #FlickrProxy and should not be freed.
236 */
237 const char *
flickr_proxy_get_token(FlickrProxy * proxy)238 flickr_proxy_get_token (FlickrProxy *proxy)
239 {
240 FlickrProxyPrivate *priv = FLICKR_PROXY_GET_PRIVATE (proxy);
241 return priv->token;
242 }
243
244 /**
245 * flickr_proxy_set_token:
246 * @proxy: an #FlickrProxy
247 * @token: the access token
248 *
249 * Set the token.
250 */
251 void
flickr_proxy_set_token(FlickrProxy * proxy,const char * token)252 flickr_proxy_set_token (FlickrProxy *proxy, const char *token)
253 {
254 FlickrProxyPrivate *priv;
255
256 g_return_if_fail (FLICKR_IS_PROXY (proxy));
257 priv = FLICKR_PROXY_GET_PRIVATE (proxy);
258
259 if (priv->token)
260 g_free (priv->token);
261
262 priv->token = g_strdup (token);
263 }
264
265 char *
flickr_proxy_sign(FlickrProxy * proxy,GHashTable * params)266 flickr_proxy_sign (FlickrProxy *proxy, GHashTable *params)
267 {
268 FlickrProxyPrivate *priv;
269 GList *keys;
270 char *md5;
271 GChecksum *checksum;
272
273 g_return_val_if_fail (FLICKR_IS_PROXY (proxy), NULL);
274 g_return_val_if_fail (params, NULL);
275
276 priv = FLICKR_PROXY_GET_PRIVATE (proxy);
277
278 checksum = g_checksum_new (G_CHECKSUM_MD5);
279 g_checksum_update (checksum, (guchar *)priv->shared_secret, -1);
280
281 keys = g_hash_table_get_keys (params);
282 keys = g_list_sort (keys, (GCompareFunc)strcmp);
283
284 while (keys) {
285 const char *key, *value;
286
287 key = keys->data;
288 value = g_hash_table_lookup (params, key);
289
290 g_checksum_update (checksum, (guchar *)key, -1);
291 g_checksum_update (checksum, (guchar *)value, -1);
292
293 keys = g_list_delete_link (keys, keys);
294 }
295
296 md5 = g_strdup (g_checksum_get_string (checksum));
297 g_checksum_free (checksum);
298
299 return md5;
300 }
301
302 char *
flickr_proxy_build_login_url(FlickrProxy * proxy,const char * frob,const char * perms)303 flickr_proxy_build_login_url (FlickrProxy *proxy,
304 const char *frob,
305 const char *perms)
306 {
307 SoupURI *uri;
308 GHashTable *params;
309 char *sig, *s;
310
311 g_return_val_if_fail (FLICKR_IS_PROXY (proxy), NULL);
312
313 uri = soup_uri_new ("http://flickr.com/services/auth/");
314 params = g_hash_table_new (g_str_hash, g_str_equal);
315
316 g_hash_table_insert (params, "api_key", proxy->priv->api_key);
317 g_hash_table_insert (params, "perms", (gpointer)perms);
318
319 if (frob)
320 g_hash_table_insert (params, "frob", (gpointer)frob);
321
322 sig = flickr_proxy_sign (proxy, params);
323 g_hash_table_insert (params, "api_sig", sig);
324
325 soup_uri_set_query_from_form (uri, params);
326
327 s = soup_uri_to_string (uri, FALSE);
328
329 g_free (sig);
330 g_hash_table_destroy (params);
331 soup_uri_free (uri);
332
333 return s;
334 }
335
336 /**
337 * flickr_proxy_is_successful:
338 * @root: The root node of a parsed Flickr response
339 * @error: #GError to set if the response was an error
340 *
341 * Examines the Flickr response and if it not a successful reply, set @error and
342 * return FALSE.
343 *
344 * Returns: %TRUE if this response is successful, %FALSE otherwise.
345 */
346 gboolean
flickr_proxy_is_successful(RestXmlNode * root,GError ** error)347 flickr_proxy_is_successful (RestXmlNode *root, GError **error)
348 {
349 RestXmlNode *node;
350
351 g_return_val_if_fail (root, FALSE);
352
353 if (strcmp (root->name, "rsp") != 0) {
354 g_set_error (error, FLICKR_PROXY_ERROR, 0,
355 "Unexpected response from Flickr (root node %s)",
356 root->name);
357 return FALSE;
358 }
359
360 if (strcmp (rest_xml_node_get_attr (root, "stat"), "ok") != 0) {
361 node = rest_xml_node_find (root, "err");
362 g_set_error_literal (error,FLICKR_PROXY_ERROR,
363 atoi (rest_xml_node_get_attr (node, "code")),
364 rest_xml_node_get_attr (node, "msg"));
365 return FALSE;
366 }
367
368 return TRUE;
369 }
370
371 /**
372 * flickr_proxy_new_upload:
373 * @proxy: a valid #FlickrProxy
374 *
375 * Create a new #RestProxyCall that can be used for uploading.
376 *
377 * See http://www.flickr.com/services/api/upload.api.html for details on
378 * uploading to Flickr.
379 *
380 * Returns: (type FlickrProxyCall) (transfer full): a new #FlickrProxyCall
381 */
382 RestProxyCall *
flickr_proxy_new_upload(FlickrProxy * proxy)383 flickr_proxy_new_upload (FlickrProxy *proxy)
384 {
385 g_return_val_if_fail (FLICKR_IS_PROXY (proxy), NULL);
386
387 return g_object_new (FLICKR_TYPE_PROXY_CALL,
388 "proxy", proxy,
389 "upload", TRUE,
390 NULL);
391 }
392
393 /**
394 * flickr_proxy_new_upload_for_file:
395 * @proxy: a valid #FlickrProxy
396 * @filename: the file to upload
397 * @error: #GError to set on error
398
399 * Create a new #RestProxyCall that can be used for uploading. @filename will
400 * be set as the "photo" parameter for you, avoiding you from having to open the
401 * file and determine the MIME type.
402 *
403 * Note that this function can in theory block.
404 *
405 * See http://www.flickr.com/services/api/upload.api.html for details on
406 * uploading to Flickr.
407 *
408 * Returns: (type FlickrProxyCall) (transfer full): a new #FlickrProxyCall
409 */
410 RestProxyCall *
flickr_proxy_new_upload_for_file(FlickrProxy * proxy,const char * filename,GError ** error)411 flickr_proxy_new_upload_for_file (FlickrProxy *proxy, const char *filename, GError **error)
412 {
413 GMappedFile *map;
414 GError *err = NULL;
415 char *basename = NULL, *content_type = NULL;
416 RestParam *param;
417 RestProxyCall *call = NULL;
418
419 g_return_val_if_fail (FLICKR_IS_PROXY (proxy), NULL);
420 g_return_val_if_fail (filename, NULL);
421
422 /* Open the file */
423 map = g_mapped_file_new (filename, FALSE, &err);
424 if (err) {
425 g_propagate_error (error, err);
426 return NULL;
427 }
428
429 /* Get the file information */
430 basename = g_path_get_basename (filename);
431 content_type = g_content_type_guess (filename,
432 (const guchar*) g_mapped_file_get_contents (map),
433 g_mapped_file_get_length (map),
434 NULL);
435
436 /* Make the call */
437 call = flickr_proxy_new_upload (proxy);
438 param = rest_param_new_with_owner ("photo",
439 g_mapped_file_get_contents (map),
440 g_mapped_file_get_length (map),
441 content_type,
442 basename,
443 map,
444 (GDestroyNotify)g_mapped_file_unref);
445 rest_proxy_call_add_param_full (call, param);
446
447 g_free (basename);
448 g_free (content_type);
449
450 return call;
451 }
452
453 #if BUILD_TESTS
454 void
test_flickr_error(void)455 test_flickr_error (void)
456 {
457 RestXmlParser *parser;
458 RestXmlNode *root;
459 GError *error;
460 const char test_1[] = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
461 "<rsp stat=\"ok\"><auth></auth></rsp>";
462 const char test_2[] = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
463 "<foobar/>";
464 const char test_3[] = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
465 "<rsp stat=\"fail\"><err code=\"108\" msg=\"Invalid frob\" /></rsp>";
466
467 parser = rest_xml_parser_new ();
468
469 root = rest_xml_parser_parse_from_data (parser, test_1, sizeof (test_1) - 1);
470 error = NULL;
471 flickr_proxy_is_successful (root, &error);
472 g_assert_no_error (error);
473 rest_xml_node_unref (root);
474
475 error = NULL;
476 root = rest_xml_parser_parse_from_data (parser, test_2, sizeof (test_2) - 1);
477 flickr_proxy_is_successful (root, &error);
478 g_assert_error (error, FLICKR_PROXY_ERROR, 0);
479 g_error_free (error);
480 rest_xml_node_unref (root);
481
482 error = NULL;
483 root = rest_xml_parser_parse_from_data (parser, test_3, sizeof (test_3) - 1);
484 flickr_proxy_is_successful (root, &error);
485 g_assert_error (error, FLICKR_PROXY_ERROR, 108);
486 g_error_free (error);
487 rest_xml_node_unref (root);
488 }
489 #endif
490