1 /*
2  * librest - RESTful web services access
3  * Copyright (c) 2010 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 #include <config.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <rest/rest-proxy.h>
27 #include <libsoup/soup.h>
28 #include "lastfm-proxy.h"
29 #include "lastfm-proxy-private.h"
30 #include "lastfm-proxy-call.h"
31 
32 G_DEFINE_TYPE (LastfmProxy, lastfm_proxy, REST_TYPE_PROXY)
33 
34 enum {
35   PROP_0,
36   PROP_API_KEY,
37   PROP_SECRET,
38   PROP_SESSION_KEY,
39 };
40 
41 GQuark
lastfm_proxy_error_quark(void)42 lastfm_proxy_error_quark (void)
43 {
44   return g_quark_from_static_string ("rest-lastfm-proxy");
45 }
46 
47 static RestProxyCall *
_new_call(RestProxy * proxy)48 _new_call (RestProxy *proxy)
49 {
50   RestProxyCall *call;
51 
52   call = g_object_new (LASTFM_TYPE_PROXY_CALL,
53                        "proxy", proxy,
54                        NULL);
55 
56   return call;
57 }
58 
59 static void
lastfm_proxy_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)60 lastfm_proxy_get_property (GObject *object, guint property_id,
61                            GValue *value, GParamSpec *pspec)
62 {
63   LastfmProxyPrivate *priv = LASTFM_PROXY_GET_PRIVATE (object);
64 
65   switch (property_id) {
66   case PROP_API_KEY:
67     g_value_set_string (value, priv->api_key);
68     break;
69   case PROP_SECRET:
70     g_value_set_string (value, priv->secret);
71     break;
72   case PROP_SESSION_KEY:
73     g_value_set_string (value, priv->session_key);
74     break;
75   default:
76     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
77   }
78 }
79 
80 static void
lastfm_proxy_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)81 lastfm_proxy_set_property (GObject *object, guint property_id,
82                            const GValue *value, GParamSpec *pspec)
83 {
84   LastfmProxyPrivate *priv = LASTFM_PROXY_GET_PRIVATE (object);
85 
86   switch (property_id) {
87   case PROP_API_KEY:
88     if (priv->api_key)
89       g_free (priv->api_key);
90     priv->api_key = g_value_dup_string (value);
91     break;
92   case PROP_SECRET:
93     if (priv->secret)
94       g_free (priv->secret);
95     priv->secret = g_value_dup_string (value);
96     break;
97   case PROP_SESSION_KEY:
98     if (priv->session_key)
99       g_free (priv->session_key);
100     priv->session_key = g_value_dup_string (value);
101     break;
102   default:
103     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
104   }
105 }
106 
107 static void
lastfm_proxy_finalize(GObject * object)108 lastfm_proxy_finalize (GObject *object)
109 {
110   LastfmProxyPrivate *priv = LASTFM_PROXY_GET_PRIVATE (object);
111 
112   g_free (priv->api_key);
113   g_free (priv->secret);
114   g_free (priv->session_key);
115 
116   G_OBJECT_CLASS (lastfm_proxy_parent_class)->finalize (object);
117 }
118 
119 #ifndef G_PARAM_STATIC_STRINGS
120 #define G_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)
121 #endif
122 
123 static void
lastfm_proxy_class_init(LastfmProxyClass * klass)124 lastfm_proxy_class_init (LastfmProxyClass *klass)
125 {
126   GObjectClass *object_class = G_OBJECT_CLASS (klass);
127   RestProxyClass *proxy_class = REST_PROXY_CLASS (klass);
128   GParamSpec *pspec;
129 
130   g_type_class_add_private (klass, sizeof (LastfmProxyPrivate));
131 
132   object_class->get_property = lastfm_proxy_get_property;
133   object_class->set_property = lastfm_proxy_set_property;
134   object_class->finalize = lastfm_proxy_finalize;
135 
136   proxy_class->new_call = _new_call;
137 
138   pspec = g_param_spec_string ("api-key", "api-key",
139                                "The API key", NULL,
140                                G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_STATIC_STRINGS);
141   g_object_class_install_property (object_class,
142                                    PROP_API_KEY,
143                                    pspec);
144 
145   pspec = g_param_spec_string ("secret", "secret",
146                                "The API key secret", NULL,
147                                G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_STATIC_STRINGS);
148   g_object_class_install_property (object_class,
149                                    PROP_SECRET,
150                                    pspec);
151 
152   pspec = g_param_spec_string ("session-key", "session-key",
153                                "The session key", NULL,
154                                G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS);
155   g_object_class_install_property (object_class,
156                                    PROP_SESSION_KEY,
157                                    pspec);
158 }
159 
160 static void
lastfm_proxy_init(LastfmProxy * self)161 lastfm_proxy_init (LastfmProxy *self)
162 {
163   self->priv = LASTFM_PROXY_GET_PRIVATE (self);
164 }
165 
166 RestProxy *
lastfm_proxy_new(const char * api_key,const char * secret)167 lastfm_proxy_new (const char *api_key,
168                   const char *secret)
169 {
170   return lastfm_proxy_new_with_session (api_key,
171                                         secret,
172                                         NULL);
173 }
174 
175 RestProxy *
lastfm_proxy_new_with_session(const char * api_key,const char * secret,const char * session_key)176 lastfm_proxy_new_with_session (const char *api_key,
177                                const char *secret,
178                                const char *session_key)
179 {
180   return g_object_new (LASTFM_TYPE_PROXY,
181                        "api-key", api_key,
182                        "secret", secret,
183                        "session-key", session_key,
184                        "url-format", "http://ws.audioscrobbler.com/2.0/",
185                        "binding-required", FALSE,
186                        NULL);
187 }
188 
189 /**
190  * lastfm_proxy_get_api_key:
191  * @proxy: an #LastfmProxy
192  *
193  * Get the API key.
194  *
195  * Returns: the API key. This string is owned by #LastfmProxy and should not be
196  * freed.
197  */
198 const char *
lastfm_proxy_get_api_key(LastfmProxy * proxy)199 lastfm_proxy_get_api_key (LastfmProxy *proxy)
200 {
201   LastfmProxyPrivate *priv = LASTFM_PROXY_GET_PRIVATE (proxy);
202   return priv->api_key;
203 }
204 
205 /**
206  * lastfm_proxy_get_secret:
207  * @proxy: an #LastfmProxy
208  *
209  * Get the secret for authentication.
210  *
211  * Returns: the secret. This string is owned by #LastfmProxy and should not be
212  * freed.
213  */
214 const char *
lastfm_proxy_get_secret(LastfmProxy * proxy)215 lastfm_proxy_get_secret (LastfmProxy *proxy)
216 {
217   LastfmProxyPrivate *priv = LASTFM_PROXY_GET_PRIVATE (proxy);
218   return priv->secret;
219 }
220 
221 /**
222  * lastfm_proxy_get_session_key:
223  * @proxy: an #LastfmProxy
224  *
225  * Get the current session key.
226  *
227  * Returns: the session key, or %NULL if there is no session key yet.  This string is owned
228  * by #LastfmProxy and should not be freed.
229  */
230 const char *
lastfm_proxy_get_session_key(LastfmProxy * proxy)231 lastfm_proxy_get_session_key (LastfmProxy *proxy)
232 {
233   LastfmProxyPrivate *priv = LASTFM_PROXY_GET_PRIVATE (proxy);
234   return priv->session_key;
235 }
236 
237 /**
238  * lastfm_proxy_set_session_key:
239  * @proxy: an #LastfmProxy
240  * @session_key: the access session_key
241  *
242  * Set the session key.
243  */
244 void
lastfm_proxy_set_session_key(LastfmProxy * proxy,const char * session_key)245 lastfm_proxy_set_session_key (LastfmProxy *proxy, const char *session_key)
246 {
247   LastfmProxyPrivate *priv;
248 
249   g_return_if_fail (LASTFM_IS_PROXY (proxy));
250   priv = LASTFM_PROXY_GET_PRIVATE (proxy);
251 
252   if (priv->session_key)
253     g_free (priv->session_key);
254 
255   priv->session_key = g_strdup (session_key);
256 }
257 
258 char *
lastfm_proxy_sign(LastfmProxy * proxy,GHashTable * params)259 lastfm_proxy_sign (LastfmProxy *proxy, GHashTable *params)
260 {
261   LastfmProxyPrivate *priv;
262   GString *s;
263   GList *keys;
264   char *md5;
265 
266   g_return_val_if_fail (LASTFM_IS_PROXY (proxy), NULL);
267   g_return_val_if_fail (params, NULL);
268 
269   priv = LASTFM_PROXY_GET_PRIVATE (proxy);
270 
271   s = g_string_new (NULL);
272 
273   keys = g_hash_table_get_keys (params);
274   keys = g_list_sort (keys, (GCompareFunc)strcmp);
275 
276   while (keys) {
277     const char *key;
278     const char *value;
279 
280     key = keys->data;
281     value = g_hash_table_lookup (params, key);
282 
283     g_string_append_printf (s, "%s%s", key, value);
284 
285     keys = g_list_delete_link (keys, keys);
286   }
287 
288   g_string_append (s, priv->secret);
289 
290   md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, s->str, s->len);
291 
292   g_string_free (s, TRUE);
293 
294   return md5;
295 }
296 
297 char *
lastfm_proxy_build_login_url(LastfmProxy * proxy,const char * token)298 lastfm_proxy_build_login_url (LastfmProxy *proxy, const char *token)
299 {
300   g_return_val_if_fail (LASTFM_IS_PROXY (proxy), NULL);
301   g_return_val_if_fail (token, NULL);
302 
303   return g_strdup_printf ("http://www.last.fm/api/auth/?api_key=%s&token=%s",
304                           proxy->priv->api_key,
305                           token);
306 }
307 
308 /**
309  * lastfm_proxy_is_successful:
310  * @root: The root node of a parsed Lastfm response
311  * @error: #GError to set if the response was an error
312  *
313  * Examines the Lastfm response and if it not a successful reply, set @error and
314  * return FALSE.
315  *
316  * Returns: %TRUE if this response is successful, %FALSE otherwise.
317  */
318 gboolean
lastfm_proxy_is_successful(RestXmlNode * root,GError ** error)319 lastfm_proxy_is_successful (RestXmlNode *root, GError **error)
320 {
321   RestXmlNode *node;
322 
323   g_return_val_if_fail (root, FALSE);
324 
325   if (strcmp (root->name, "lfm") != 0) {
326     g_set_error (error, LASTFM_PROXY_ERROR, 0,
327                  "Unexpected response from Lastfm (root node %s)",
328                  root->name);
329     return FALSE;
330   }
331 
332   if (strcmp (rest_xml_node_get_attr (root, "status"), "ok") != 0) {
333     node = rest_xml_node_find (root, "error");
334     g_set_error_literal (error,LASTFM_PROXY_ERROR,
335                          atoi (rest_xml_node_get_attr (node, "code")),
336                          node->content);
337     return FALSE;
338   }
339 
340   return TRUE;
341 }
342 
343 #if BUILD_TESTS
344 void
test_lastfm_error(void)345 test_lastfm_error (void)
346 {
347   RestXmlParser *parser;
348   RestXmlNode *root;
349   GError *error;
350   const char test_1[] = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
351     "<foobar/>";
352   const char test_2[] = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
353     "<lfm status=\"failed\"><error code=\"10\">Invalid API Key</error></lfm>";
354   const char test_3[] = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
355     "<lfm status=\"ok\">some data</lfm>";
356 
357   parser = rest_xml_parser_new ();
358 
359   error = NULL;
360   root = rest_xml_parser_parse_from_data (parser, test_1, sizeof (test_1) - 1);
361   lastfm_proxy_is_successful (root, &error);
362   g_assert_error (error, LASTFM_PROXY_ERROR, 0);
363   g_error_free (error);
364   rest_xml_node_unref (root);
365 
366   root = rest_xml_parser_parse_from_data (parser, test_2, sizeof (test_2) - 1);
367   error = NULL;
368   lastfm_proxy_is_successful (root, &error);
369   g_assert_error (error, LASTFM_PROXY_ERROR, 10);
370   rest_xml_node_unref (root);
371 
372   error = NULL;
373   root = rest_xml_parser_parse_from_data (parser, test_3, sizeof (test_3) - 1);
374   lastfm_proxy_is_successful (root, &error);
375   g_assert_no_error (error);
376   g_error_free (error);
377   rest_xml_node_unref (root);
378 }
379 #endif
380