Merge branch 'master' of https://git.maemo.org/projects/g2-sharing
[g2-sharing] / src / gallery2.c
1 /*
2  * This file is part of sharing-plugin-gallery2
3  *
4  * Copyright (C) 2009 Heikki Kallasjoki. All rights reserved.
5  * Copyright (C) 2008-2009 Nokia Corporation. All rights reserved.
6  *
7  * This code is licensed under a MIT-style license, that can be
8  * found in the file called "COPYING" in the root directory.
9  *
10  */
11
12 #include <stdio.h>
13 #include <string.h>
14 #include <glib.h>
15 #include <osso-log.h>
16 #include <sharing-http.h>
17 #include "gallery2.h"
18
19 /* Helpers: */
20
21 static gchar* url_encode (const gchar* source);
22
23 /**
24  * gallery2_login:
25  * @con: Connection to use
26  * @urlbase: Base URL to the Gallery 2 site
27  * @username: User name
28  * @password: Password
29  * @cookies: Output parameter for any cookies set.
30  * @auth: Output paremeter for the authentication token.
31  *
32  * Logs in to the Gallery 2 service.
33  *
34  * Returns: Validation result.
35  */
36 SharingPluginInterfaceAccountValidateResult
37 gallery2_login (ConIcConnection* con,
38                 const gchar* urlbase, const gchar* username, const gchar* password,
39                 GHashTable** cookies, gchar** auth)
40 {
41         SharingPluginInterfaceAccountValidateResult ret = SHARING_ACCOUNT_VALIDATE_SUCCESS;
42
43         SharingHTTP* http = sharing_http_new ();
44
45         *cookies = 0;
46         *auth = 0;
47
48         /* Do the login request */
49
50         SharingHTTPRunResponse res = 0;
51
52         {
53                 gchar* euser = url_encode (username);
54                 gchar* epass = url_encode (password);
55
56                 gchar* url = g_strdup_printf("%s/main.php?g2_controller=remote:GalleryRemote&"
57                                 "g2_form[cmd]=login&g2_form[protocol_version]=2.0&"
58                                 "g2_form[uname]=%s&g2_form[password]=%s",
59                                 urlbase, euser, epass);
60
61                 g_free (euser);
62                 g_free (epass);
63
64                 sharing_http_set_connection (http, con);
65                 res = sharing_http_run (http, url);
66
67                 g_free (url);
68         }
69
70         /* Parse the response */
71
72         if (res == SHARING_HTTP_RUNRES_SUCCESS && sharing_http_get_res_code (http) == 200)
73         {
74                 /* Split response into lines */
75
76                 gchar** lines = 0;
77
78                 {
79                         gsize content_len = 0;
80                         const gchar* content = sharing_http_get_res_content (http, &content_len);
81                         gchar* c = g_strndup (content, content_len); /* force \0 termination */
82                         lines = g_strsplit_set (c, "\r\n", 0);
83                         g_free (c);
84                 }
85
86                 /* Process the lines */
87
88                 *cookies = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
89
90                 gboolean in_body = FALSE;
91
92                 for (gchar** p = lines; *p; p++)
93                 {
94                         gchar* line = *p;
95
96                         if (!in_body)
97                         {
98                                 if (g_ascii_strncasecmp (line, "Set-Cookie:", 11) == 0)
99                                 {
100                                         /* Extract the key=value part of the cookie */
101
102                                         gchar** data = 0;
103
104                                         {
105                                                 gchar* c = g_strchug (line+11); /* start of cookie data */
106
107                                                 char* end = strchr (c, ';');
108                                                 if (end) *end = 0;
109
110                                                 data = g_strsplit (c, "=", 2);
111                                         }
112
113                                         if (!data[0] || !data[1])
114                                         {
115                                                 /* Bad Set-Cookie: header, ignore */
116                                                 g_strfreev (data);
117                                                 continue;
118                                         }
119
120                                         /* Insert into our table */
121
122                                         g_hash_table_replace (*cookies, data[0], data[1]);
123                                         g_free (data); /* not g_strfreev! strings still used in "cookies" */
124
125                                         continue;
126                                 }
127
128                                 if (g_str_has_prefix (line, "#__GR2PROTO__"))
129                                 {
130                                         in_body = TRUE;
131                                         continue;
132                                 }
133                         }
134                         else
135                         {
136                                 /* Split key=value into fields */
137
138                                 gchar* value = strchr (line, '=');
139                                 if (!value) continue;
140                                 *value = 0;
141                                 value++;
142
143                                 /* Process the necessary parts */
144
145                                 if (strcmp (line, "status") == 0 && strcmp (value, "0") != 0)
146                                 {
147                                         ULOG_ERR_L ("Gallery 2 login auth failed\n");
148                                         ret = SHARING_ACCOUNT_VALIDATE_FAILED;
149                                         break;
150                                 }
151
152                                 if (strcmp (line, "auth_token") == 0)
153                                 {
154                                         *auth = g_strdup (value);
155                                         continue;
156                                 }
157                         }
158                 }
159
160                 g_strfreev (lines);
161         }
162         else
163         {
164                 ULOG_ERR_L ("Gallery 2 login connection failed\n");
165                 ret = SHARING_ACCOUNT_VALIDATE_ERROR_CONNECTION;
166         }
167
168         if (ret != SHARING_ACCOUNT_VALIDATE_SUCCESS)
169         {
170                 if (*cookies) g_hash_table_unref (*cookies);
171                 if (*auth) g_free (auth);
172                 *cookies = 0;
173                 *auth = 0;
174         }
175
176         sharing_http_unref (http);
177         return ret;
178 }
179
180 /**
181  * gallery2_lookup_album:
182  * @con: Connection to use
183  * @urlbase: Base URL to the Gallery 2 site
184  * @albumpath: Slash-separated path to album
185  * @album: Output parameter to hold the album ID
186  * @cookies: Cookies from gallery2_login.
187  * @auth: Authentication token from gallery2_login.
188  *
189  * Retrieves the album id based on an album path.
190  */
191 SharingPluginInterfaceAccountValidateResult
192 gallery2_lookup_album (ConIcConnection* con,
193                 const gchar* urlbase, const gchar* albumpath, gchar** album,
194                 GHashTable* cookies, gchar* auth)
195 {
196         SharingPluginInterfaceAccountValidateResult ret = SHARING_ACCOUNT_VALIDATE_ERROR_UNKNOWN;
197
198         SharingHTTP* http = sharing_http_new ();
199
200         /* Prepare and send the request */
201
202         gchar* url = g_strdup_printf("%s/main.php?g2_controller=remote:GalleryRemote%s%s&"
203                         "g2_form[cmd]=fetch-albums-prune&g2_form[protocol_version]=2.2&g2_form[no_perms]=yes",
204                         urlbase,
205                         auth ? "&g2_authToken=" : "", auth ? auth : "");
206
207         if (cookies)
208         {
209                 GHashTableIter iter;
210                 gpointer key, value;
211                 g_hash_table_iter_init (&iter, cookies);
212                 while (g_hash_table_iter_next (&iter, &key, &value))
213                 {
214                         gchar* hdr = g_strdup_printf("Cookie: %s=%s", (gchar*)key, (gchar*)value);
215                         sharing_http_add_req_header_line (http, hdr);
216                         g_free (hdr);
217                 }
218         }
219
220         sharing_http_set_connection (http, con);
221         SharingHTTPRunResponse res = sharing_http_run (http, url);
222
223         g_free (url);
224         url = 0;
225
226         /* Parse the response into an album map. */
227
228         GHashTable* album_names = 0;  /* map string (display-name) -> GSList [ string (url-name) ] */
229         GHashTable* album_parent = 0; /* map string (url-name) -> string (url-name) */
230         gchar* album_root = 0;        /* root album url-name */
231         gboolean valid = FALSE;       /* true if the maps are usable */
232         char** lines = 0;             /* raw data (response lines) */
233
234         if (res == SHARING_HTTP_RUNRES_SUCCESS && sharing_http_get_res_code (http) == 200)
235         {
236                 {
237                         gsize content_len = 0;
238                         const gchar* content = sharing_http_get_res_body (http, &content_len);
239                         gchar* c = g_strndup (content, content_len); /* force \0 termination */
240                         lines = g_strsplit_set (c, "\r\n", 0);
241                         g_free (c);
242                 }
243
244                 gboolean in_body = FALSE;
245                 gchar* current_ref_num = 0;
246                 gchar* current_url_name = 0;
247
248                 for (gchar** p = lines; *p; p++)
249                 {
250                         gchar* line = *p;
251
252                         if (!in_body)
253                         {
254                                 if (g_str_has_prefix (line, "#__GR2PROTO__"))
255                                 {
256                                         album_names = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_slist_free);
257                                         album_parent = g_hash_table_new (g_str_hash, g_str_equal);
258                                         in_body = TRUE;
259                                 }
260                                 continue;
261                         }
262
263                         gchar* value = strchr (line, '=');
264                         if (!value) continue;
265                         *value = 0;
266                         value++;
267
268                         if (strcmp (line, "status") == 0)
269                         {
270                                 if (strcmp (value, "0") == 0)
271                                         valid = TRUE;
272                                 continue;
273                         }
274                         else if (g_str_has_prefix (line, "album.name."))
275                         {
276                                 current_ref_num = line + 11;
277                                 current_url_name = value;
278                         }
279                         else if (g_str_has_prefix (line, "album.title."))
280                         {
281                                 if (!current_ref_num || strcmp (current_ref_num, line + 12) != 0)
282                                         continue;
283                                 GSList* others = g_hash_table_lookup (album_names, value);
284                                 if (others) g_hash_table_steal (album_names, value);
285                                 g_hash_table_insert (album_names, value, g_slist_prepend (others, current_url_name));
286                         }
287                         else if (g_str_has_prefix (line, "album.parent."))
288                         {
289                                 if (!current_ref_num || strcmp (current_ref_num, line + 13) != 0)
290                                         continue;
291                                 g_hash_table_insert (album_parent, current_url_name, value);
292                                 if (strcmp (value, "0") == 0)
293                                         album_root = current_url_name;
294                         }
295                 }
296         }
297         else
298                 ret = SHARING_ACCOUNT_VALIDATE_ERROR_CONNECTION;
299
300         sharing_http_unref (http);
301
302         /* Find the album we are interested in. */
303
304         *album = 0;
305
306         if (album_names && album_parent && album_root && valid)
307         {
308                 gchar* current_album = album_root;
309                 gboolean seen_parents = FALSE; /* for the root special case */
310                 gboolean found_final = FALSE;  /* to make sure the last real component is found */
311
312                 gchar** components = g_strsplit (albumpath, "/", 0);
313
314                 for (gchar** p = components; *p; p++)
315                 {
316                         if (!**p) continue; /* ignore empty path components */
317                         found_final = FALSE; /* this component needs to be found */
318                         GSList* candidates = g_hash_table_lookup (album_names, *p);
319                         /* bad case: no candidates at all */
320                         if (!candidates) break;
321                         /* special case 1: if only one candidate and no unseen parents, choose that */
322                         if (!seen_parents && !g_slist_next (candidates))
323                         {
324                                 found_final = TRUE;
325                                 current_album = candidates->data;
326                                 continue;
327                         }
328                         /* general case: find a candidate with an acceptable parent */
329                         while (candidates)
330                         {
331                                 gchar* parent = g_hash_table_lookup (album_parent, candidates->data);
332                                 /* suitable parents: current_album, or (if no specified parents) null or 0 (explicit root case) */
333                                 if ((parent && strcmp (parent, current_album) == 0)
334                                                 || (!seen_parents && (!parent || strcmp (parent, "0") == 0)))
335                                 {
336                                         found_final = TRUE;
337                                         current_album = candidates->data;
338                                         break;
339                                 }
340                                 candidates = g_slist_next (candidates); /* try next */
341                         }
342                 }
343
344                 g_strfreev (components);
345
346                 if (found_final)
347                 {
348                         *album = g_strdup(current_album);
349                         ret = SHARING_ACCOUNT_VALIDATE_SUCCESS;
350                 }
351         }
352
353         if (album_names) g_hash_table_unref (album_names);
354         if (album_parent) g_hash_table_unref (album_parent);
355         g_strfreev (lines);
356
357         return ret;
358 }
359
360 /* gallery2_send callback helper declarations */
361
362 struct gallery2_send_record
363 {
364         SharingTransfer* transfer;
365         gdouble progress_start;
366         gdouble progress_end;
367         guint64 media_bytes;
368         gboolean* dms;
369 };
370
371 gboolean gallery2_send_callback (SharingHTTP* http, guint64 bytes_sent, gpointer user_data);
372
373 /**
374  * gallery2_send:
375  * @con: Connection to use
376  * @transfer: Sharing transfer object
377  * @progress_start: Initial state of progress meter
378  * @progress_end: Desired final state of progress meter
379  * @dms: Dead man's switch
380  * @media: Media item to send
381  * @urlbase: Base URL to the Gallery 2 site
382  * @album: Album ID from gallery2_lookup_album.
383  * @cookies: Cookies from gallery2_login.
384  * @auth: Authentication token from gallery2_login.
385  *
386  * Sends a media item to a Gallery 2 service.
387  *
388  * Returns: Send result.
389  */
390 SharingPluginInterfaceSendResult
391 gallery2_send (ConIcConnection* con,
392                 SharingTransfer* transfer, gdouble progress_start, gdouble progress_end, gboolean *dms,
393                 SharingEntryMedia* media,
394                 const gchar* urlbase, const gchar* album, GHashTable* cookies, gchar* auth)
395 {
396         struct gallery2_send_record rec = {
397                 .transfer = transfer,
398                 .progress_start = progress_start,
399                 .progress_end = progress_end,
400                 .media_bytes = 0,
401                 .dms = dms
402         };
403
404         SharingPluginInterfaceSendResult ret = SHARING_SEND_SUCCESS;
405
406         SharingHTTP* http = sharing_http_new ();
407
408         /* Prepare and send the request */
409
410         gchar* media_title = sharing_entry_media_get_title (media);
411         gchar* media_mime = sharing_entry_media_get_mime (media);
412         gchar* media_filename = sharing_entry_media_get_filename (media);
413
414         const gchar* desc = sharing_entry_media_get_desc (media);
415
416         const gchar* title = media_title;
417         if (!title || !*title) title = media_filename;
418         if (!title || !*title) title = "(unknown)";
419
420         gchar* url = 0;
421
422         {
423                 gchar* edesc = (desc && *desc ? url_encode (desc) : 0);
424                 gchar* etitle = url_encode (title);
425                 url = g_strdup_printf("%s/main.php?g2_controller=remote:GalleryRemote%s%s&"
426                                 "g2_form[cmd]=add-item&g2_form[protocol_version]=2.0&"
427                                 "g2_form[set_albumName]=%s&g2_form[caption]=%s"
428                                 "%s%s%s%s",
429                                 urlbase,
430                                 auth ? "&g2_authToken=" : "", auth ? auth : "",
431                                 album, etitle,
432                                 edesc ? "&g2_form[extrafield.Summary]=" : "", edesc ? edesc : "",
433                                 edesc ? "&g2_form[extrafield.Description]=" : "", edesc ? edesc : "");
434                 g_free (etitle);
435                 g_free (edesc);
436         }
437
438         if (cookies)
439         {
440                 GHashTableIter iter;
441                 gpointer key, value;
442                 g_hash_table_iter_init (&iter, cookies);
443                 while (g_hash_table_iter_next (&iter, &key, &value))
444                 {
445                         gchar* hdr = g_strdup_printf("Cookie: %s=%s", (gchar*)key, (gchar*)value);
446                         sharing_http_add_req_header_line (http, hdr);
447                         g_free (hdr);
448                 }
449         }
450
451         sharing_http_add_req_multipart_file_with_filename (http,
452                 "g2_userfile",
453                 sharing_entry_media_get_localpath (media),
454                 media_mime ? media_mime : "image/jpeg",
455                 media_filename ? media_filename : "unknown.jpg");
456
457         g_free (media_title);
458         g_free (media_mime);
459         g_free (media_filename);
460
461         media_title = media_mime = media_filename = 0;
462
463         rec.media_bytes = sharing_entry_media_get_size (media);
464         sharing_http_set_progress_callback (http, gallery2_send_callback, &rec);
465
466         *dms = FALSE;
467         sharing_http_set_connection (http, con);
468         SharingHTTPRunResponse res = sharing_http_run (http, url);
469         *dms = FALSE;
470
471         g_free (url);
472         url = 0;
473
474         /* Parse the response */
475
476         if (res == SHARING_HTTP_RUNRES_SUCCESS && sharing_http_get_res_code (http) == 200)
477         {
478                 gchar** lines = 0;
479
480                 {
481                         gsize content_len = 0;
482                         const gchar* content = sharing_http_get_res_body (http, &content_len);
483                         gchar* c = g_strndup (content, content_len); /* force \0 termination */
484                         lines = g_strsplit_set (c, "\r\n", 0);
485                         g_free (c);
486                 }
487
488                 gboolean in_body = FALSE;
489                 ret = SHARING_SEND_ERROR_UNKNOWN;
490
491                 for (gchar** p = lines; *p; p++)
492                 {
493                         gchar* line = *p;
494
495                         if (!in_body)
496                         {
497                                 if (g_str_has_prefix (line, "#__GR2PROTO__"))
498                                         in_body = TRUE;
499                                 continue;
500                         }
501
502                         gchar* value = strchr (line, '=');
503                         if (!value) continue;
504                         *value = 0;
505                         value++;
506
507                         if (strcmp (line, "status") == 0)
508                         {
509                                 if (strcmp (value, "0") == 0)
510                                         ret = SHARING_SEND_SUCCESS;
511                                 break;
512                         }
513                 }
514
515                 g_strfreev (lines);
516         }
517         else if (res == SHARING_HTTP_RUNRES_CANCELLED)
518                 ret = SHARING_SEND_CANCELLED;
519         else
520                 ret = SHARING_SEND_ERROR_CONNECTION;
521
522         sharing_http_unref (http);
523
524         *dms = FALSE;
525         sharing_transfer_set_progress (transfer, progress_end);
526
527         return ret;
528 }
529
530 /* gallery2_send callback implementation */
531
532 gboolean gallery2_send_callback (SharingHTTP* http, guint64 bytes_sent, gpointer user_data)
533 {
534         struct gallery2_send_record* rec = user_data;
535
536         if (!sharing_transfer_continue (rec->transfer))
537                 return FALSE;
538
539         *rec->dms = FALSE;
540
541         gdouble progress = (rec->progress_start + rec->progress_end) / 2.0;
542
543         if (rec->media_bytes)
544         {
545                 if (bytes_sent >= rec->media_bytes)
546                         progress = rec->progress_end;
547                 else
548                         progress = rec->progress_start + (bytes_sent / (gdouble)rec->media_bytes) * (rec->progress_end - rec->progress_start);
549         }
550
551         sharing_transfer_set_progress (rec->transfer, progress);
552
553         return TRUE;
554 }
555
556 /* Helper implementations */
557
558 static gchar url_encode_hex[16] = {
559         '0', '1', '2', '3', '4', '5', '6', '7',
560         '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
561 };
562
563 static gchar* url_encode (const gchar* source)
564 {
565         gchar* dest = 0;
566         gsize dest_len = 0;
567         const gchar* s;
568         gchar* d;
569
570         /* Count new string length */
571
572         for (s = source; *s; s++)
573         {
574                 dest_len++;
575                 if (!((*s >= '0' && *s <= '9') || (*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z')))
576                         dest_len += 2;
577         }
578
579         /* Build encoded string */
580
581         dest = g_malloc (dest_len + 1);
582
583         for (s = source, d = dest; *s; s++)
584         {
585                 if ((*s >= '0' && *s <= '9') || (*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z'))
586                         *d++ = *s;
587                 else if (*s == ' ')
588                         *d++ = '+';
589                 else
590                 {
591                         *d++ = '%';
592                         *d++ = url_encode_hex[(*s >> 4) & 0xf];
593                         *d++ = url_encode_hex[*s & 0xf];
594                 }
595         }
596
597         *d = 0;
598         return dest;
599 }