1 /*
2 * * Copyright (C) 2006-2011 Anders Brander <anders@brander.dk>,
3 * * Anders Kvist <akv@lnxbx.dk> and Klaus Post <klauspost@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include <curl/curl.h>
21 #include <libxml/encoding.h>
22 #include <gtk/gtk.h>
23 #include <glib.h>
24 #include <glib/gstdio.h>
25 #include "rs-facebook-client.h"
26
27 #define HTTP_BOUNDARY "4wncn84cq4ncto874ytnv90w43htn"
28
29 /**
30 * Get a quark used to describe Facebook error domain
31 * @return A quark touse in GErrors from Facebook code
32 */
33 GQuark
rs_facebook_client_error_quark(void)34 rs_facebook_client_error_quark(void)
35 {
36 static GStaticMutex lock = G_STATIC_MUTEX_INIT;
37 static GQuark quark;
38
39 g_static_mutex_lock(&lock);
40 if (!quark)
41 quark = g_quark_from_static_string("rawstudio_facebook_client_error");
42 g_static_mutex_unlock(&lock);
43
44 return quark;
45 }
46
47 struct _RSFacebookClient {
48 GObject parent;
49
50 const gchar *api_key;
51 const gchar *secret;
52
53 gchar *session_key;
54 gchar *auth_token;
55 gchar *auth_url;
56
57 CURL *curl;
58 };
59
G_DEFINE_TYPE(RSFacebookClient,rs_facebook_client,G_TYPE_OBJECT)60 G_DEFINE_TYPE(RSFacebookClient, rs_facebook_client, G_TYPE_OBJECT)
61
62 static void
63 rs_facebook_client_finalize(GObject *object)
64 {
65 RSFacebookClient *facebook = RS_FACEBOOK_CLIENT(object);
66
67 g_free(facebook->session_key);
68 g_free(facebook->auth_token);
69 g_free(facebook->auth_url);
70
71 curl_easy_cleanup(facebook->curl);
72
73 G_OBJECT_CLASS(rs_facebook_client_parent_class)->finalize(object);
74 }
75
76 static void
rs_facebook_client_class_init(RSFacebookClientClass * klass)77 rs_facebook_client_class_init(RSFacebookClientClass *klass)
78 {
79 GObjectClass *object_class = G_OBJECT_CLASS(klass);
80
81 object_class->finalize = rs_facebook_client_finalize;
82 }
83
84 static void
rs_facebook_client_init(RSFacebookClient * facebook)85 rs_facebook_client_init(RSFacebookClient *facebook)
86 {
87 facebook->curl = curl_easy_init();
88 }
89
90 static gchar *
xml_simple_response(const GString * xml,const gchar * needle,const gboolean root)91 xml_simple_response(const GString *xml, const gchar *needle, const gboolean root)
92 {
93 xmlDocPtr doc = xmlParseMemory(xml->str, xml->len);
94 xmlNodePtr cur;
95
96 cur = xmlDocGetRootElement(doc);
97
98 if (!root && cur)
99 cur = cur->xmlChildrenNode;
100
101 gchar *result = NULL;
102
103 while (cur)
104 {
105 if ((!xmlStrcmp(cur->name, BAD_CAST(needle))))
106 result = (gchar *) xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
107
108 cur = cur->next;
109 }
110 return result;
111 }
112
113 static GtkListStore *
xml_album_list_response(const GString * xml)114 xml_album_list_response(const GString *xml)
115 {
116 xmlDocPtr doc = xmlParseMemory(xml->str, xml->len);
117 xmlNodePtr cur, child;
118
119 cur = xmlDocGetRootElement(doc);
120 cur = cur->xmlChildrenNode;
121
122 gchar *name = NULL;
123 gchar *aid = NULL;
124 gchar *type = NULL;
125
126 GtkListStore *albums = NULL;
127 GtkTreeIter iter;
128
129 while (cur)
130 {
131 if ((!xmlStrcmp(cur->name, BAD_CAST("album"))))
132 {
133 child = cur->xmlChildrenNode;
134 while (child)
135 {
136 if ((!xmlStrcmp(child->name, BAD_CAST("name"))))
137 name = (gchar *) xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
138 if ((!xmlStrcmp(child->name, BAD_CAST("aid"))))
139 aid = (gchar *) xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
140 if ((!xmlStrcmp(child->name, BAD_CAST("type"))))
141 type = (gchar *) xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
142 child = child->next;
143 }
144
145 /* We can solely upload photos to normal albums (not profile, wall and such). */
146 if(g_strcmp0(type, "normal") == 0)
147 {
148 if (!albums)
149 albums = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
150 gtk_list_store_append(albums, &iter);
151 gtk_list_store_set(albums, &iter,
152 0, name, /* FIXME: Hardcoded */
153 1, aid, /* FIXME: Hardcoded */
154 -1);
155 }
156 g_free(type);
157 }
158
159 cur = cur->next;
160 }
161 return albums;
162 }
163
164 static gboolean
xml_error(const GString * xml,GError ** error)165 xml_error(const GString *xml, GError **error)
166 {
167 gchar *error_code = xml_simple_response(xml, "error_code", FALSE);
168 gchar *error_msg = xml_simple_response(xml, "error_msg", FALSE);
169
170 if (error_code)
171 {
172 g_set_error(error, RS_FACEBOOK_CLIENT_ERROR_DOMAIN, 0, "%s", error_msg);
173 g_free(error_code);
174 g_free(error_msg);
175 return TRUE;
176 }
177 g_free(error_code);
178 g_free(error_msg);
179 return FALSE;
180 }
181
182 static size_t
write_callback(void * ptr,size_t size,size_t nmemb,void * userp)183 write_callback(void *ptr, size_t size, size_t nmemb, void *userp)
184 {
185 GString *string = (GString *) userp;
186 g_string_append_len(string, (char *) ptr, size * nmemb);
187 return (size * nmemb);
188 }
189
190 static gboolean
facebook_client_request(RSFacebookClient * facebook,const gchar * method,RSFacebookClientParam * param,GString * content,GError ** error)191 facebook_client_request(RSFacebookClient *facebook, const gchar *method, RSFacebookClientParam *param, GString *content, GError **error)
192 {
193 volatile static gint call_id = 0;
194 CURLcode result;
195 struct curl_slist *header = NULL;
196 gint post_length = 0;
197 gchar *post_str;
198
199 /* We start by resetting all CURL parameters */
200 curl_easy_reset(facebook->curl);
201
202 #ifdef fb_debug
203 curl_easy_setopt(facebook->curl, CURLOPT_VERBOSE, TRUE);
204 #endif /* fb_debug */
205
206 g_atomic_int_inc(&call_id);
207
208 curl_easy_setopt(facebook->curl, CURLOPT_URL, "api.facebook.com/restserver.php");
209 rs_facebook_client_param_add_string(param, "api_key", facebook->api_key);
210 rs_facebook_client_param_add_string(param, "method", method);
211 rs_facebook_client_param_add_string(param, "v", "1.0");
212 rs_facebook_client_param_add_integer(param, "call_id", g_atomic_int_get(&call_id));
213
214 /* If we have a session key, we will use it */
215 if(facebook->session_key)
216 rs_facebook_client_param_add_string(param, "session_key", facebook->session_key);
217
218 header = curl_slist_append(header, "Content-Type: multipart/form-data; boundary=" HTTP_BOUNDARY);
219 header = curl_slist_append(header, "MIME-version: 1.0;");
220
221 post_str = rs_facebook_client_param_get_post(param, facebook->secret, HTTP_BOUNDARY, &post_length);
222
223 curl_easy_setopt(facebook->curl, CURLOPT_POST, TRUE);
224 curl_easy_setopt(facebook->curl, CURLOPT_POSTFIELDS, post_str);
225 curl_easy_setopt(facebook->curl, CURLOPT_POSTFIELDSIZE, post_length);
226 curl_easy_setopt(facebook->curl, CURLOPT_WRITEFUNCTION, write_callback);
227 curl_easy_setopt(facebook->curl, CURLOPT_WRITEDATA, content);
228 curl_easy_setopt(facebook->curl, CURLOPT_HTTPHEADER, header);
229 result = curl_easy_perform(facebook->curl);
230
231 curl_slist_free_all(header);
232 g_free(post_str);
233 g_object_unref(param);
234
235 if (xml_error(content, error))
236 return FALSE;
237
238 return (result==0);
239 }
240
241 static const gchar *
facebook_client_get_auth_token(RSFacebookClient * facebook,GError ** error)242 facebook_client_get_auth_token(RSFacebookClient *facebook, GError **error)
243 {
244 static GStaticMutex lock = G_STATIC_MUTEX_INIT;
245
246 g_static_mutex_lock(&lock);
247 if (!facebook->auth_token)
248 {
249 GString *content = g_string_new("");
250 facebook_client_request(facebook, "facebook.auth.createToken", rs_facebook_client_param_new(), content, error);
251 facebook->auth_token = xml_simple_response(content, "auth_createToken_response", TRUE);
252 g_string_free(content, TRUE);
253 }
254 g_static_mutex_unlock(&lock);
255
256 return facebook->auth_token;
257 }
258
259 /**
260 * Initializes a new RSFacebookClient
261 * @param api_key The API key from Facebook
262 * @param secret The secret provided by Facebook
263 * @param session_key The stored session key or NULL if you haven't got one yet
264 * @return A new RSFacebookClient, this must be unreffed
265 */
266 RSFacebookClient *
rs_facebook_client_new(const gchar * api_key,const gchar * secret,const gchar * session_key)267 rs_facebook_client_new(const gchar *api_key, const gchar *secret, const gchar *session_key)
268 {
269 RSFacebookClient *facebook = g_object_new(RS_TYPE_FACEBOOK_CLIENT, NULL);
270
271 facebook->api_key = api_key;
272 facebook->secret = secret;
273
274 rs_facebook_client_set_session_key(facebook, session_key);
275
276 return facebook;
277 }
278
279
280 /**
281 * Get the url that the user must visit to authenticate this application (api_key)
282 * @param facebook A RSFacebookClient
283 * @param base_url A prefix URL, "http://api.facebook.com/login.php" would make sense
284 * @param error NULL or a pointer to a GError * initialized to NULL
285 * @return A URL that the user can visit to authenticate this application. Thisshould not be freed
286 */
287 const gchar *
rs_facebook_client_get_auth_url(RSFacebookClient * facebook,const gchar * base_url,GError ** error)288 rs_facebook_client_get_auth_url(RSFacebookClient *facebook, const gchar *base_url, GError **error)
289 {
290 static GStaticMutex lock = G_STATIC_MUTEX_INIT;
291
292 g_assert(RS_IS_FACEBOOK_CLIENT(facebook));
293
294 g_static_mutex_lock(&lock);
295 if (!facebook->auth_url)
296 facebook->auth_url = g_strdup_printf("%s?api_key=%s&auth_token=%s&req_perms=user_photos", base_url, facebook->api_key, facebook_client_get_auth_token(facebook, error));
297 g_static_mutex_unlock(&lock);
298
299 return facebook->auth_url;
300 }
301
302 /**
303 * Get the session key as returned from Facebook
304 * @param facebook A RSFacebookClient
305 * @param error NULL or a pointer to a GError * initialized to NULL
306 * @return The session key from Facebook or NULL on error
307 */
308 const gchar *
rs_facebook_client_get_session_key(RSFacebookClient * facebook,GError ** error)309 rs_facebook_client_get_session_key(RSFacebookClient *facebook, GError **error)
310 {
311 static GStaticMutex lock = G_STATIC_MUTEX_INIT;
312
313 g_assert(RS_IS_FACEBOOK_CLIENT(facebook));
314
315 g_static_mutex_lock(&lock);
316 RSFacebookClientParam *param = rs_facebook_client_param_new();
317
318 rs_facebook_client_param_add_string(param, "auth_token", facebook->auth_token);
319 GString *content = g_string_new("");
320 facebook_client_request(facebook, "facebook.auth.getSession", param, content, error);
321
322 g_free(facebook->session_key);
323 facebook->session_key = xml_simple_response(content, "session_key", FALSE);
324 g_string_free(content, TRUE);
325 g_static_mutex_unlock(&lock);
326
327 return facebook->session_key;
328 }
329
330 /**
331 * Set the session key, this can be used to cache the session_key
332 * @param facebook A RSFacebookClient
333 * @param session_key A new session key to use
334 */
335 void
rs_facebook_client_set_session_key(RSFacebookClient * facebook,const gchar * session_key)336 rs_facebook_client_set_session_key(RSFacebookClient *facebook, const gchar *session_key)
337 {
338 g_assert(RS_IS_FACEBOOK_CLIENT(facebook));
339
340 g_free(facebook->session_key);
341
342 facebook->session_key = g_strdup(session_key);
343 }
344
345 /**
346 * Check if we are authenticated to Facebook
347 * @param facebook A RSFacebookClient
348 * @param error NULL or a pointer to a GError * initialized to NULL
349 * @return TRUE if we're authenticated both by the Facebook API and by the end-user, FALSE otherwise
350 */
351 gboolean
rs_facebook_client_ping(RSFacebookClient * facebook,GError ** error)352 rs_facebook_client_ping(RSFacebookClient *facebook, GError **error)
353 {
354 gboolean ret = FALSE;
355 g_assert(RS_IS_FACEBOOK_CLIENT(facebook));
356
357 GString *content = g_string_new("");
358 facebook_client_request(facebook, "facebook.users.isAppAdded", rs_facebook_client_param_new(), content, NULL);
359 gchar *result = xml_simple_response(content, "users_isAppAdded_response", TRUE);
360 g_string_free(content, TRUE);
361
362 if (result && g_str_equal(result, "1"))
363 ret = TRUE;
364
365 g_free(result);
366
367 return ret;
368 }
369
370 /**
371 * Upload a photo to Facebook, will be placed in the registered applications default photo folder
372 * @param facebook A RSFacebookClient
373 * @param filename Full path to an image to upload. JPEG, PNG, TIFF accepted
374 * @param caption The caption to use for the image
375 * @param error NULL or a pointer to a GError * initialized to NULL
376 * @return TRUE on success, FALSE otherwise
377 */
378 gboolean
rs_facebook_client_upload_image(RSFacebookClient * facebook,const gchar * filename,const gchar * caption,const gchar * aid,GError ** error)379 rs_facebook_client_upload_image(RSFacebookClient *facebook, const gchar *filename, const gchar *caption, const gchar *aid, GError **error)
380 {
381 g_assert(RS_IS_FACEBOOK_CLIENT(facebook));
382 g_return_val_if_fail(filename != NULL, FALSE);
383 g_return_val_if_fail(g_path_is_absolute(filename), FALSE);
384
385 RSFacebookClientParam *param = rs_facebook_client_param_new();
386
387 struct stat st;
388 g_stat(filename, &st);
389 gchar *filesize = g_strdup_printf("%d", (gint) st.st_size);
390
391 rs_facebook_client_param_add_string(param, "filename", filename);
392 rs_facebook_client_param_add_string(param, "length", filesize);
393
394 if (caption)
395 rs_facebook_client_param_add_string(param, "caption", caption);
396 if (aid)
397 rs_facebook_client_param_add_string(param, "aid", aid);
398
399 GString *content = g_string_new("");
400 facebook_client_request(facebook, "facebook.photos.upload", param, content, error);
401
402 g_string_free(content, TRUE);
403 g_free(filesize);
404
405 return TRUE;
406 }
407
408 /**
409 * Get list of available albums on Facebook account (not profile, wall and so on)
410 * @param facebook A RSFacebookClient
411 * @param error NULL or a pointer to a GError * initialized to NULL
412 * @return a GtkListStore with albums if any, NULL otherwise
413 */
414 GtkListStore *
rs_facebook_client_get_album_list(RSFacebookClient * facebook,GError ** error)415 rs_facebook_client_get_album_list(RSFacebookClient *facebook, GError **error)
416 {
417 g_assert(RS_IS_FACEBOOK_CLIENT(facebook));
418
419 GString *content = g_string_new("");
420 facebook_client_request(facebook, "facebook.photos.getAlbums", rs_facebook_client_param_new(), content, error);
421 GtkListStore *albums = xml_album_list_response(content);
422
423 g_string_free(content, TRUE);
424
425 return albums;
426 }
427
428 gchar *
rs_facebook_client_create_album(RSFacebookClient * facebook,const gchar * album_name)429 rs_facebook_client_create_album(RSFacebookClient *facebook, const gchar *album_name)
430 {
431 g_assert(RS_IS_FACEBOOK_CLIENT(facebook));
432
433 RSFacebookClientParam *param = rs_facebook_client_param_new();
434 rs_facebook_client_param_add_string(param, "name", album_name);
435
436 GString *content = g_string_new("");
437 facebook_client_request(facebook, "facebook.photos.createAlbum", param, content, NULL);
438 gchar *aid = xml_simple_response(content, "aid", FALSE);
439 g_string_free(content, TRUE);
440 return aid;
441 }
442