Rename package to sharing-twitter-multi (maemo- prefix is unnecessary)
[maemo-sharing-twitter-multi:maemo-sharing-twitter-multi.git] / src / util.c
1 /*
2  * This file is part of sharing-twitter-multi
3  *
4  * Copyright (C) 2010 Igalia, S.L.
5  * Authors: Alberto Garcia <agarcia@igalia.com>
6  *
7  * This library is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * version 3 as published by the Free Software Foundation.
10  *
11  * This library is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this software. If not, see <http://www.gnu.org/licenses/>
17  */
18
19 #include "util.h"
20
21 #include <oauth.h>
22 #include <string.h>
23 #include <sharing-http.h>
24 #include <conicconnectionevent.h>
25
26 #define PARAM_ACCESS_TOKEN        "twitter-access-token"
27 #define PARAM_ACCESS_SECRET       "twitter-access-secret"
28 #define PARAM_REQUEST_TOKEN       "twitter-request-token"
29 #define PARAM_REQUEST_SECRET      "twitter-request-secret"
30 #define PARAM_PIN                 "twitter-pin"
31
32 #define CONSUMER_KEY              "WmyOdRu3svydhjw2SKgqZA"
33 #define CONSUMER_SECRET           "psF3jiC2uMVWG7P1sd2bVEhFmHmWJOUTcbvevqbrcc"
34
35 static GHashTable *
36 parse_reply                             (const gchar *buffer)
37 {
38   GHashTable *hash;
39   gint i;
40   gchar **lines;
41
42   hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
43   lines = g_strsplit (buffer, "&", 0);
44
45   for (i = 0; lines[i] != NULL; i++)
46     {
47       gchar **line = g_strsplit (lines[i], "=", 2);
48       if (line[0] != NULL && line[1] != NULL)
49         {
50           gchar *key = g_strstrip (g_strdup (line[0]));
51           gchar *val = g_strstrip (g_strdup (line[1]));
52           if (key[0] != '\0' && val[0] != '\0')
53             {
54               g_hash_table_insert (hash, key, val);
55             }
56           else
57             {
58               g_free (key);
59               g_free (val);
60             }
61         }
62       g_strfreev (line);
63     }
64   g_strfreev (lines);
65
66   return hash;
67 }
68
69 static void
70 string_replace                          (GString     *str,
71                                          const gchar *old,
72                                          const gchar *new)
73 {
74   gint oldlen = strlen (old);
75   gint newlen = strlen (new);
76   const gchar *cur = str->str;
77   while ((cur = strstr (cur, old)) != NULL)
78     {
79       gint position = cur - str->str;
80       g_string_erase (str, position, oldlen);
81       g_string_insert (str, position, new);
82       cur = str->str + position + newlen;
83     }
84 }
85
86 static gchar *
87 get_oauth_signature_valist              (const gchar *proto,
88                                          const gchar *url,
89                                          const gchar *access_secret,
90                                          va_list      args)
91 {
92   GString *base;
93   gchar *retvalue;
94   const gchar *param;
95
96   base = g_string_sized_new (300);
97
98   g_string_assign (base, proto);
99   g_string_append_c (base, '&');
100   g_string_append (base, url);
101   string_replace (base, ":", "%3A");
102   string_replace (base, "/", "%2F");
103   g_string_append_c (base, '&');
104
105   param = va_arg (args, gchar *);
106   while (param != NULL)
107     {
108       const gchar *previous;
109       const gchar *value = va_arg (args, gchar *);
110
111       g_return_val_if_fail (value != NULL, NULL);
112
113       g_string_append (base, param);
114       g_string_append (base, "%3D");
115       g_string_append (base, value);
116
117       previous = param;
118       param = va_arg (args, gchar *);
119
120       if (param != NULL)
121         {
122           g_assert (g_strcmp0 (previous, param) <= 0);
123           g_string_append (base, "%26");
124         }
125     }
126
127   {
128     gchar *secret, *str;
129
130     secret = g_strconcat (CONSUMER_SECRET "&", access_secret, NULL);
131     str = oauth_sign_hmac_sha1 (base->str, secret);
132     g_free (secret);
133
134     g_string_free (base, TRUE);
135
136     retvalue = g_uri_escape_string (str, "", FALSE);
137     g_free (str);
138   }
139
140   return retvalue;
141 }
142
143 static gchar *
144 get_oauth_signature                     (const gchar *proto,
145                                          const gchar *url,
146                                          const gchar *access_secret,
147                                          ...)
148 {
149   gchar *retvalue;
150   va_list args;
151   va_start (args, access_secret);
152   retvalue = get_oauth_signature_valist (proto, url, access_secret, args);
153   va_end (args);
154   return retvalue;
155 }
156
157 /*
158   Receives a URL and a NULL-terminated list of (param,value) and
159   returns the full URL signed with the 'oauth_signature' parameter
160  */
161 static gchar *
162 get_signed_url                          (const gchar *url,
163                                          ...)
164 {
165   GString *final_url;
166   gchar *signature;
167   const gchar *param;
168   va_list args;
169
170   final_url = g_string_sized_new (300);
171   g_string_assign (final_url, url);
172   g_string_append_c (final_url, '?');
173
174   va_start (args, url);
175   param = va_arg (args, gchar *);
176   while (param != NULL)
177     {
178       const gchar *value = va_arg (args, gchar *);
179
180       g_string_append (final_url, param);
181       g_string_append_c (final_url, '=');
182       g_string_append (final_url, value);
183
184       param = va_arg (args, gchar *);
185       g_string_append_c (final_url, '&');
186     }
187   va_end (args);
188
189   va_start (args, url);
190   signature = get_oauth_signature_valist ("GET", url, "", args);
191   va_end (args);
192
193   g_string_append (final_url, "oauth_signature=");
194   g_string_append (final_url, signature);
195
196   g_free (signature);
197
198   return g_string_free (final_url, FALSE);
199 }
200
201 static gchar *
202 get_twitter_request_token_url           (void)
203 {
204   gchar *timestamp, *nonce, *url;
205
206   timestamp = g_strdup_printf ("%lu", time (NULL));
207   nonce = oauth_gen_nonce ();
208
209   url = get_signed_url (TWITTER_REQUEST_TOKEN_URL,
210                         "oauth_callback", "oob",
211                         "oauth_consumer_key", CONSUMER_KEY,
212                         "oauth_nonce", nonce,
213                         "oauth_signature_method", "HMAC-SHA1",
214                         "oauth_timestamp", timestamp,
215                         "oauth_version", "1.0",
216                         NULL);
217
218   g_free (timestamp);
219   g_free (nonce);
220
221   return url;
222 }
223
224 static gchar *
225 get_access_token_url                    (gchar *request_token,
226                                          gchar *pin)
227 {
228   gchar *timestamp, *nonce, *url;
229   g_return_val_if_fail (request_token && pin, NULL);
230
231   timestamp = g_strdup_printf ("%lu", time (NULL));
232   nonce = oauth_gen_nonce ();
233
234   url = get_signed_url (TWITTER_ACCESS_TOKEN_URL,
235                         "oauth_callback", "oob",
236                         "oauth_consumer_key", CONSUMER_KEY,
237                         "oauth_nonce", nonce,
238                         "oauth_signature_method", "HMAC-SHA1",
239                         "oauth_timestamp", timestamp,
240                         "oauth_token", request_token,
241                         "oauth_verifier", pin,
242                         "oauth_version", "1.0",
243                         NULL);
244   g_free (timestamp);
245   g_free (nonce);
246
247   return url;
248 }
249
250 static gboolean
251 get_twitter_access_token                (gchar  *request_token,
252                                          gchar  *pin,
253                                          gchar **access_token,
254                                          gchar **access_secret,
255                                          gchar **screen_name)
256 {
257   gchar *url;
258
259   g_return_val_if_fail (request_token && pin, FALSE);
260   g_return_val_if_fail (access_token && access_secret && screen_name, FALSE);
261
262   *access_token = *access_secret = *screen_name = NULL;
263
264   url = get_access_token_url (request_token, pin);
265
266   if (url)
267     {
268       SharingHTTP *http;
269       SharingHTTPRunResponse ret;
270       http = sharing_http_new ();
271       ret = sharing_http_run (http, url);
272
273       if (ret == SHARING_HTTP_RUNRES_SUCCESS)
274         {
275           GHashTable *t;
276           gsize len;
277           const gchar *reply, *token, *secret, *name;
278
279           reply = sharing_http_get_res_body (http, &len);
280           t = parse_reply (reply);
281
282           token  = g_hash_table_lookup (t, "oauth_token");
283           secret = g_hash_table_lookup (t, "oauth_token_secret");
284           name   = g_hash_table_lookup (t, "screen_name");
285
286           if (token && secret && name)
287             {
288               *access_token  = g_strdup (token);
289               *access_secret = g_strdup (secret);
290               *screen_name   = g_strdup (name);
291             }
292
293           g_hash_table_destroy (t);
294         }
295
296       g_free (url);
297       sharing_http_unref (http);
298     }
299
300   return (*access_token != NULL);
301 }
302
303 static gboolean
304 get_twitter_request_token               (gchar **token,
305                                          gchar **secret)
306 {
307   gchar *url;
308   SharingHTTP *http;
309   SharingHTTPRunResponse ret;
310
311   g_return_val_if_fail (token != NULL && secret != NULL, FALSE);
312
313   *token = *secret = NULL;
314
315   url = get_twitter_request_token_url ();
316   http = sharing_http_new ();
317   ret = sharing_http_run (http, url);
318
319   if (ret == SHARING_HTTP_RUNRES_SUCCESS)
320     {
321       GHashTable *t;
322       gsize len;
323       const gchar *reply, *oauth_token, *oauth_token_secret;
324
325       reply = sharing_http_get_res_body (http, &len);
326       t = parse_reply (reply);
327
328       oauth_token = g_hash_table_lookup (t, "oauth_token");
329       oauth_token_secret = g_hash_table_lookup (t, "oauth_token_secret");
330
331       if (oauth_token && oauth_token_secret)
332         {
333           *token = g_strdup (oauth_token);
334           *secret = g_strdup (oauth_token_secret);
335         }
336
337       g_hash_table_destroy (t);
338     }
339
340   g_free (url);
341   sharing_http_unref (http);
342
343   return (*token != NULL);
344 }
345
346 typedef struct {
347   SharingAccount *account;
348   gchar *token;
349   gchar *secret;
350   TwitterGetAuthUrlCb callback;
351   gpointer cbdata;
352   gboolean online;
353 } TwitterGetAuthUrlData;
354
355 static gboolean
356 twitter_get_auth_url_idle               (gpointer data)
357 {
358   TwitterGetAuthUrlData *d = data;
359   gchar *url = NULL;
360   if (d->online && get_twitter_request_token (&(d->token), &(d->secret)))
361     {
362       url = g_strconcat (TWITTER_AUTHORIZE_URL, "?oauth_token=", d->token, NULL);
363       sharing_account_set_param (d->account, PARAM_REQUEST_TOKEN, d->token);
364       sharing_account_set_param (d->account, PARAM_REQUEST_SECRET, d->secret);
365     }
366   d->callback (url, d->cbdata);
367   g_free (url);
368   g_free (d->token);
369   g_free (d->secret);
370   g_slice_free (TwitterGetAuthUrlData, d);
371   return FALSE;
372 }
373
374 static void
375 twitter_get_auth_url_connection         (ConIcConnection      *conn,
376                                          ConIcConnectionEvent *event,
377                                          gpointer              data)
378 {
379   TwitterGetAuthUrlData *d = data;
380   g_signal_handlers_disconnect_by_func (conn, twitter_get_auth_url_connection, data);
381   d->online = con_ic_connection_event_get_status (event) == CON_IC_STATUS_CONNECTED;
382   gdk_threads_add_timeout (500, twitter_get_auth_url_idle, d);
383 }
384
385 void
386 twitter_get_auth_url                    (SharingAccount      *account,
387                                          ConIcConnection     *con,
388                                          TwitterGetAuthUrlCb  callback,
389                                          gpointer             cbdata)
390 {
391   TwitterGetAuthUrlData *d;
392
393   g_return_if_fail (account != NULL && callback != NULL);
394
395   d = g_slice_new0 (TwitterGetAuthUrlData);
396   d->account  = account;
397   d->callback = callback;
398   d->cbdata   = cbdata;
399
400   g_signal_connect (con, "connection-event",
401                     G_CALLBACK (twitter_get_auth_url_connection), d);
402   con_ic_connection_connect (con, CON_IC_CONNECT_FLAG_NONE);
403 }
404
405 void
406 twitter_account_set_pin                 (SharingAccount *account,
407                                          const gchar    *pin)
408 {
409   g_return_if_fail (account != NULL);
410
411   sharing_account_set_param (account, PARAM_PIN, pin);
412 }
413
414 gboolean
415 twitter_account_validate                (SharingAccount *account)
416 {
417   gboolean valid_account;
418
419   {
420     gchar *token, *secret, *name;
421
422     token = sharing_account_get_param (account, PARAM_ACCESS_TOKEN);
423     secret = sharing_account_get_param (account, PARAM_ACCESS_SECRET);
424     name = sharing_account_get_username (account);
425
426     /* An account with these three parameters is valid */
427     valid_account = (token && secret && name);
428
429     g_free (token);
430     g_free (secret);
431     g_free (name);
432   }
433
434   if (!valid_account)
435     {
436       gchar *req_token, *pin;
437
438       req_token = sharing_account_get_param (account, PARAM_REQUEST_TOKEN);
439       pin = sharing_account_get_param (account, PARAM_PIN);
440
441       /* With the request token and the pin, we can obtain the access token */
442       if (req_token && pin)
443         {
444           gchar *token, *secret, *name;
445
446           get_twitter_access_token (req_token, pin, &token, &secret, &name);
447
448           if (token && secret && name)
449             {
450               /* Store the new access token and the user name */
451               sharing_account_set_param (account, PARAM_ACCESS_TOKEN, token);
452               sharing_account_set_param (account, PARAM_ACCESS_SECRET, secret);
453               sharing_account_set_username (account, name);
454
455               /* Clear the request token and the pin, we don't need them anymore */
456               sharing_account_set_param (account, PARAM_REQUEST_TOKEN, NULL);
457               sharing_account_set_param (account, PARAM_REQUEST_SECRET, NULL);
458               sharing_account_set_param (account, PARAM_PIN, NULL);
459
460               valid_account = TRUE;
461             }
462
463           g_free (token);
464           g_free (secret);
465           g_free (name);
466         }
467
468       g_free (req_token);
469       g_free (pin);
470     }
471
472   return valid_account;
473 }
474
475 gchar *
476 twitter_get_verify_credentials_header   (SharingAccount *account)
477 {
478   gchar *token, *secret, *hdr = NULL;
479
480   g_return_val_if_fail (account != NULL, NULL);
481
482   token = sharing_account_get_param (account, PARAM_ACCESS_TOKEN);
483   secret = sharing_account_get_param (account, PARAM_ACCESS_SECRET);
484
485   if (token && secret)
486     {
487       gchar *sig, *nonce, *timestamp;
488
489       timestamp = g_strdup_printf ("%lu", time (NULL));
490       nonce = oauth_gen_nonce ();
491
492       sig = get_oauth_signature ("GET", TWITTER_VERIFY_CREDENTIALS_URL, secret,
493                                  "oauth_callback", "oob",
494                                  "oauth_consumer_key", CONSUMER_KEY,
495                                  "oauth_nonce", nonce,
496                                  "oauth_signature_method", "HMAC-SHA1",
497                                  "oauth_timestamp", timestamp,
498                                  "oauth_token", token,
499                                  "oauth_version", "1.0",
500                                  NULL);
501
502       hdr = g_strconcat ("X-Verify-Credentials-Authorization: OAuth realm=\"http://api.twitter.com/\", "
503                          "oauth_callback=\"oob\", "
504                          "oauth_consumer_key=\"" CONSUMER_KEY "\", "
505                          "oauth_nonce=\"", nonce, "\", "
506                          "oauth_signature_method=\"HMAC-SHA1\", "
507                          "oauth_timestamp=\"", timestamp, "\", "
508                          "oauth_token=\"", token, "\", "
509                          "oauth_version=\"1.0\", "
510                          "oauth_signature=\"", sig, "\"", NULL);
511
512       g_free (sig);
513       g_free (timestamp);
514       g_free (nonce);
515     }
516
517   g_free (token);
518   g_free (secret);
519
520   return hdr;
521 }
522
523 gboolean
524 twitter_update_status                   (const gchar    *status,
525                                          SharingAccount *account)
526 {
527   gchar *token, *secret;
528   gboolean retvalue = FALSE;
529
530   g_return_val_if_fail (status && account, FALSE);
531
532   token = sharing_account_get_param (account, PARAM_ACCESS_TOKEN);
533   secret = sharing_account_get_param (account, PARAM_ACCESS_SECRET);
534
535   if (token && secret)
536     {
537       gchar *sig, *hdr, *timestamp, *nonce;
538       SharingHTTPRunResponse httpret;
539       SharingHTTP *http;
540
541       timestamp = g_strdup_printf ("%lu", time (NULL));
542       nonce = oauth_gen_nonce ();
543
544       sig = get_oauth_signature ("POST", TWITTER_UPDATE_STATUS_URL, secret,
545                                  "oauth_callback", "oob",
546                                  "oauth_consumer_key", CONSUMER_KEY,
547                                  "oauth_nonce", nonce,
548                                  "oauth_signature_method", "HMAC-SHA1",
549                                  "oauth_timestamp", timestamp,
550                                  "oauth_token", token,
551                                  "oauth_version", "1.0",
552                                  NULL);
553
554       hdr = g_strconcat ("OAuth oauth_callback=\"oob\", "
555                          "oauth_consumer_key=\"" CONSUMER_KEY "\", "
556                          "oauth_nonce=\"", nonce, "\", "
557                          "oauth_signature_method=\"HMAC-SHA1\", "
558                          "oauth_timestamp=\"", timestamp, "\", "
559                          "oauth_token=\"", token, "\", "
560                          "oauth_version=\"1.0\", "
561                          "oauth_signature=\"", sig, "\"", NULL);
562
563       http = sharing_http_new ();
564       sharing_http_add_req_multipart_data (http, "status", status, -1, "text/plain");
565       sharing_http_add_req_header (http, "Authorization", hdr);
566       sharing_http_add_req_header (http, "Expect", "");
567       httpret = sharing_http_run (http, TWITTER_UPDATE_STATUS_URL);
568       sharing_http_unref (http);
569
570       retvalue = (httpret == SHARING_HTTP_RUNRES_SUCCESS);
571
572       g_free (sig);
573       g_free (hdr);
574       g_free (timestamp);
575       g_free (nonce);
576     }
577
578   g_free (token);
579   g_free (secret);
580
581   return retvalue;
582 }