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