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