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