1 /*
2  * gtalk-file-collection.c - Source for GTalkFileCollection
3  *
4  * Copyright (C) 2010 Collabora Ltd.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20 
21 #include "config.h"
22 #include "gtalk-file-collection.h"
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <glib.h>
28 
29 #define DEBUG_FLAG GABBLE_DEBUG_SHARE
30 
31 #include "debug.h"
32 #include "jingle-share.h"
33 #include "namespaces.h"
34 #include "util.h"
35 
36 #include <nice/agent.h>
37 
38 /*
39  * This GTalk compatible file transfer protocol is a bit complicated, so here
40  * is an explanation on how it works :
41  *
42  * A pseudo-good initial source of information is available here :
43  * http://code.google.com/apis/talk/libjingle/file_share.html
44  *
45  * The current object interaction is like this :
46  *
47  *                GabbleFileTransferChannelManager
48  *                               |
49  *                               |
50  *                               |
51  *                               |
52  *                               * ref
53  *                     GabbleFileTransferChannel
54  *                weakref *         *
55  *                       /           \
56  *                      /             \
57  *                     /               \
58  *                    /                 \
59  *                   /                   \
60  *                  /                     \
61  *                 /                       \
62  *            ref /                         \
63  *      GTalkFileCollection                  \
64  *              |                             \
65  *              |                              \
66  *          ref |                               \
67  *     WockyJingleSession                       \
68  *              |                                 \ (one at a time)
69  *              |                                  \
70  *          ref |                                   \
71  *     GabbleJingleShare  ------------------*- ShareChannel -------- NiceAgent
72  *              |                                                       |
73  *              |                                                       |
74  *         ref  |                           ref                         |
75  *   GabbleTransportGoogle ----------------*- WockyJingleCandidate        PseudoTCP
76  *
77  * The protocol works like this :
78  * Once you receive an invitation, the manifest will contain a number of files
79  * and folders. Some files might have image attributes (width/height) that help
80  * specify that they are images.
81  * If there are images in the invitation, a new ShareChannel gets created and
82  * connectivity must be established. Then on that stream, an HTTP GET request
83  * is sent for each image being transfered with the URL as :
84  * GET <preview-path>/filename?width=X&height=Y
85  * where X and Y are the thumbnail's requested width and height.
86  * The peer should at this point scale down the image to the requested width and
87  * height and send the thumbnail for showing the preview of the image in the FT
88  * UI.
89  * Once the invitation is accepted, a new ShareChannel is created, which will
90  * cause a new NiceAgent to be created and connectivity to be established on that
91  * ShareChannel. The resulting stream is then used as an HTTP server/client to
92  * request files on it using the <source-path> as prefix to the URL.
93  * Simple files are being transferred normally, while directories will be
94  * transferred as a tarball with 'chuncked' Transfer-Encoding since the resulting
95  * size of the tarball isn't known in advance.
96  * Once a file is completely transferred, then the next file is requested on the
97  * same ShareChannel. If all files are transferred, then the <complete> info
98  * action is being sent through the jingle signaling, and the session can then
99  * be terminated safely.
100  *
101  * Since telepathy doesn't currently support image previews, so the 'preview'
102  * ShareChannel is never created with gabble.
103  * Also note that we only create one ShareChannel and we serialize the file
104  * transfers one after the other, they do not each get one ShareChannel and they
105  * cannot be downloaded in parallel.
106  *
107  */
108 
109 static gboolean test_mode = FALSE;
110 
111 void
gtalk_file_collection_set_test_mode(void)112 gtalk_file_collection_set_test_mode (void)
113 {
114   test_mode = TRUE;
115 }
116 
117 G_DEFINE_TYPE (GTalkFileCollection, gtalk_file_collection, G_TYPE_OBJECT);
118 
119 /* properties */
120 enum
121 {
122   PROP_TOKEN = 1,
123   LAST_PROPERTY
124 };
125 
126 typedef enum
127   {
128     HTTP_SERVER_IDLE,
129     HTTP_SERVER_HEADERS,
130     HTTP_SERVER_SEND,
131     HTTP_CLIENT_IDLE,
132     HTTP_CLIENT_RECEIVE,
133     HTTP_CLIENT_HEADERS,
134     HTTP_CLIENT_CHUNK_SIZE,
135     HTTP_CLIENT_CHUNK_END,
136     HTTP_CLIENT_CHUNK_FINAL,
137     HTTP_CLIENT_BODY,
138   } HttpStatus;
139 
140 
141 typedef struct
142 {
143   NiceAgent *agent;
144   guint stream_id;
145   guint component_id;
146   gboolean agent_attached;
147   GabbleJingleShare *content;
148   guint share_channel_id;
149   HttpStatus http_status;
150   gchar *status_line;
151   gboolean is_chunked;
152   guint64 content_length;
153   gchar *write_buffer;
154   guint write_len;
155   gchar *read_buffer;
156   guint read_len;
157 } ShareChannel;
158 
159 
160 typedef enum
161 {
162   GTALK_FT_STATUS_PENDING,
163   GTALK_FT_STATUS_INITIATED,
164   GTALK_FT_STATUS_ACCEPTED,
165   GTALK_FT_STATUS_TRANSFERRING,
166   GTALK_FT_STATUS_WAITING,
167   GTALK_FT_STATUS_TERMINATED
168 } GtalkFtStatus;
169 
170 struct _GTalkFileCollectionPrivate
171 {
172   gboolean dispose_has_run;
173 
174   GtalkFtStatus status;
175   /* GList of weakreffed GabbleFileTransferChannel */
176   GList *channels;
177   /* GHashTable of GabbleFileTransferChannel => GINT_TO_POINTER (gboolean) */
178   /* the weakref to the channel here is held through the GList *channels */
179   GHashTable *channels_reading;
180   /* GHashTable of GabbleFileTransferChannel => GINT_TO_POINTER (gboolean) */
181   /* the weakref to the channel here is held through the GList *channels */
182   GHashTable *channels_usable;
183   GabbleFileTransferChannel *current_channel;
184   WockyJingleFactory *jingle_factory;
185   WockyJingleSession *jingle;
186   /* ICE component id to jingle share channel association
187      GINT_TO_POINTER (candidate->component) => g_slice_new (ShareChannel) */
188   GHashTable *share_channels;
189   gboolean requested;
190   gchar *token;
191 };
192 
193 static void free_share_channel (gpointer data);
194 static void nice_data_received_cb (NiceAgent *agent,
195     guint stream_id, guint component_id, guint len, gchar *buffer,
196     gpointer user_data);
197 static void set_current_channel (GTalkFileCollection *self,
198     GabbleFileTransferChannel *channel);
199 static void channel_disposed (gpointer data, GObject *where_the_object_was);
200 
201 static void
gtalk_file_collection_init(GTalkFileCollection * self)202 gtalk_file_collection_init (GTalkFileCollection *self)
203 {
204   GTalkFileCollectionPrivate *priv =
205      G_TYPE_INSTANCE_GET_PRIVATE (self, GTALK_TYPE_FILE_COLLECTION,
206          GTalkFileCollectionPrivate);
207   gchar buf[16];
208   guint32 *uint_buf = (guint32 *) buf;
209   guint i;
210 
211 
212   DEBUG ("GTalk file collection init called");
213   self->priv = priv;
214 
215   self->priv->status = GTALK_FT_STATUS_PENDING;
216 
217   self->priv->channels_reading = g_hash_table_new_full (NULL, NULL, NULL, NULL);
218   self->priv->channels_usable = g_hash_table_new_full (NULL, NULL, NULL, NULL);
219 
220   self->priv->share_channels = g_hash_table_new_full (NULL, NULL,
221       NULL, free_share_channel);
222 
223   for (i = 0; i < sizeof (buf); i++)
224     buf[i] = g_random_int_range (0, 256);
225 
226   self->priv->token = g_strdup_printf ("%x%x%x%x",
227       uint_buf[0], uint_buf[1], uint_buf[2], uint_buf[3]);
228 
229   /* FIXME: we should start creating a nice agent already and have it start
230      the candidate gathering.. but we don't know which jingle-share transport
231      channel name to assign it to... */
232 
233   priv->dispose_has_run = FALSE;
234 }
235 
236 
237 static void
gtalk_file_collection_dispose(GObject * object)238 gtalk_file_collection_dispose (GObject *object)
239 {
240   GTalkFileCollection *self = GTALK_FILE_COLLECTION (object);
241   GList *i;
242 
243   if (self->priv->dispose_has_run)
244     return;
245 
246   DEBUG ("dispose called");
247   self->priv->dispose_has_run = TRUE;
248 
249   if (self->priv->jingle != NULL)
250     wocky_jingle_session_terminate (self->priv->jingle,
251         WOCKY_JINGLE_REASON_UNKNOWN, NULL, NULL);
252 
253   tp_clear_object (&self->priv->jingle);
254 
255   set_current_channel (self, NULL);
256 
257   tp_clear_pointer (&self->priv->channels_reading, g_hash_table_unref);
258   tp_clear_pointer (&self->priv->channels_usable, g_hash_table_unref);
259   tp_clear_pointer (&self->priv->share_channels, g_hash_table_unref);
260 
261   for (i = self->priv->channels; i; i = i->next)
262     {
263       GabbleFileTransferChannel *channel = i->data;
264       g_object_weak_unref (G_OBJECT (channel), channel_disposed, self);
265     }
266 
267   g_list_free (self->priv->channels);
268 
269   g_free (self->priv->token);
270 
271   if (G_OBJECT_CLASS (gtalk_file_collection_parent_class)->dispose)
272     G_OBJECT_CLASS (gtalk_file_collection_parent_class)->dispose (object);
273 }
274 
275 static void
gtalk_file_collection_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)276 gtalk_file_collection_get_property (GObject *object,
277                                            guint property_id,
278                                            GValue *value,
279                                            GParamSpec *pspec)
280 {
281   GTalkFileCollection *self = GTALK_FILE_COLLECTION (object);
282 
283   switch (property_id)
284     {
285       case PROP_TOKEN:
286         g_value_set_string (value, self->priv->token);
287         break;
288       default:
289         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
290         break;
291     }
292 }
293 
294 
295 static void
gtalk_file_collection_class_init(GTalkFileCollectionClass * cls)296 gtalk_file_collection_class_init (GTalkFileCollectionClass *cls)
297 {
298   GObjectClass *object_class = G_OBJECT_CLASS (cls);
299 
300   g_type_class_add_private (cls, sizeof (GTalkFileCollectionPrivate));
301 
302   object_class->get_property = gtalk_file_collection_get_property;
303   object_class->dispose = gtalk_file_collection_dispose;
304 
305   g_object_class_install_property (object_class, PROP_TOKEN,
306       g_param_spec_string (
307           "token",
308           "Unique token identifiying the FileCollection",
309           "Token identifying a collection of files",
310           "",
311           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
312 }
313 
314 
315 static ShareChannel *
get_share_channel(GTalkFileCollection * self,NiceAgent * agent)316 get_share_channel (GTalkFileCollection *self, NiceAgent *agent)
317 {
318   GHashTableIter iter;
319   gpointer key, value;
320   ShareChannel *ret = NULL;
321 
322   g_hash_table_iter_init (&iter, self->priv->share_channels);
323   while (g_hash_table_iter_next (&iter, &key, &value))
324     {
325       ShareChannel *share_channel = (ShareChannel *) value;
326       if (share_channel->agent == agent)
327         {
328           ret = share_channel;
329           break;
330         }
331     }
332 
333   return ret;
334 }
335 
336 
337 static GabbleFileTransferChannel *
get_channel_by_filename(GTalkFileCollection * self,gchar * filename)338 get_channel_by_filename (GTalkFileCollection *self, gchar *filename)
339 {
340   GList *i;
341 
342   for (i = self->priv->channels; i; i = i->next)
343     {
344       GabbleFileTransferChannel *channel = i->data;
345       gchar *file = NULL;
346 
347       g_object_get (channel,
348           "filename", &file,
349           NULL);
350 
351       if (strcmp (file, filename) == 0)
352         return channel;
353     }
354 
355   return NULL;
356 }
357 
358 static void
set_current_channel(GTalkFileCollection * self,GabbleFileTransferChannel * channel)359 set_current_channel (GTalkFileCollection *self,
360     GabbleFileTransferChannel *channel)
361 {
362   self->priv->current_channel = channel;
363 
364   if (channel != NULL)
365     {
366       gboolean reading = FALSE;
367 
368       gabble_file_transfer_channel_gtalk_file_collection_state_changed (
369           channel, GTALK_FILE_COLLECTION_STATE_OPEN, FALSE);
370       reading = GPOINTER_TO_INT (g_hash_table_lookup (
371               self->priv->channels_reading, channel));
372       gtalk_file_collection_block_reading (self, channel, !reading);
373     }
374 }
375 
376 static gboolean
channel_exists(GTalkFileCollection * self,GabbleFileTransferChannel * channel)377 channel_exists (GTalkFileCollection * self, GabbleFileTransferChannel *channel)
378 {
379   GList *i;
380 
381   for (i = self->priv->channels; i; i = i->next)
382     {
383       if (channel == i->data)
384         return TRUE;
385     }
386 
387   return FALSE;
388 }
389 
390 static void
add_channel(GTalkFileCollection * self,GabbleFileTransferChannel * channel)391 add_channel (GTalkFileCollection * self, GabbleFileTransferChannel *channel)
392 {
393   self->priv->channels = g_list_append (self->priv->channels, channel);
394   g_hash_table_replace (self->priv->channels_reading, channel,
395       GINT_TO_POINTER (FALSE));
396   g_object_weak_ref (G_OBJECT (channel), channel_disposed, self);
397 }
398 
399 static void
del_channel(GTalkFileCollection * self,GabbleFileTransferChannel * channel)400 del_channel (GTalkFileCollection * self, GabbleFileTransferChannel *channel)
401 {
402   g_return_if_fail (channel_exists (self, channel));
403 
404   self->priv->channels = g_list_remove (self->priv->channels, channel);
405   g_hash_table_remove (self->priv->channels_reading, channel);
406   g_hash_table_remove (self->priv->channels_usable, channel);
407   g_object_weak_unref (G_OBJECT (channel), channel_disposed, self);
408   if (self->priv->current_channel == channel)
409     set_current_channel (self, NULL);
410 }
411 
412 static void
jingle_session_state_changed_cb(WockyJingleSession * session,GParamSpec * arg1,GTalkFileCollection * self)413 jingle_session_state_changed_cb (WockyJingleSession *session,
414                                  GParamSpec *arg1,
415                                  GTalkFileCollection *self)
416 {
417   WockyJingleState state;
418   GList *i;
419 
420   DEBUG ("called");
421 
422   g_object_get (session,
423       "state", &state,
424       NULL);
425 
426   switch (state)
427     {
428       case WOCKY_JINGLE_STATE_INVALID:
429       case WOCKY_JINGLE_STATE_PENDING_CREATED:
430         break;
431       case WOCKY_JINGLE_STATE_PENDING_INITIATE_SENT:
432       case WOCKY_JINGLE_STATE_PENDING_INITIATED:
433         for (i = self->priv->channels; i;)
434           {
435             GabbleFileTransferChannel *channel = i->data;
436 
437             i = i->next;
438             gabble_file_transfer_channel_gtalk_file_collection_state_changed (
439                 channel, GTALK_FILE_COLLECTION_STATE_PENDING, FALSE);
440           }
441         break;
442       case WOCKY_JINGLE_STATE_PENDING_ACCEPT_SENT:
443       case WOCKY_JINGLE_STATE_ACTIVE:
444         /* Do not set the channels to OPEN unless we're ready to send/receive
445            data from them */
446         if (self->priv->status == GTALK_FT_STATUS_INITIATED)
447           self->priv->status = GTALK_FT_STATUS_ACCEPTED;
448         for (i = self->priv->channels; i;)
449           {
450             GabbleFileTransferChannel *channel = i->data;
451             gboolean usable;
452 
453             i = i->next;
454 
455             usable = GPOINTER_TO_INT (g_hash_table_lookup (
456                     self->priv->channels_usable, channel));
457             if (usable)
458               gabble_file_transfer_channel_gtalk_file_collection_state_changed (
459                   channel, GTALK_FILE_COLLECTION_STATE_ACCEPTED, FALSE);
460           }
461         break;
462       case WOCKY_JINGLE_STATE_ENDED:
463         /* Do nothing, let the terminated signal set the correct state
464            depending on the termination reason */
465       default:
466         break;
467     }
468 }
469 
470 static void
jingle_session_terminated_cb(WockyJingleSession * session,gboolean local_terminator,WockyJingleReason reason,const gchar * text,gpointer user_data)471 jingle_session_terminated_cb (WockyJingleSession *session,
472                        gboolean local_terminator,
473                        WockyJingleReason reason,
474                        const gchar *text,
475                        gpointer user_data)
476 {
477   GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
478   GList *i;
479 
480   g_assert (session == self->priv->jingle);
481 
482   self->priv->status = GTALK_FT_STATUS_TERMINATED;
483 
484   for (i = self->priv->channels; i;)
485     {
486       GabbleFileTransferChannel *channel = i->data;
487 
488       i = i->next;
489       gabble_file_transfer_channel_gtalk_file_collection_state_changed (
490           channel, GTALK_FILE_COLLECTION_STATE_TERMINATED, local_terminator);
491     }
492 }
493 
494 static void
content_new_remote_candidates_cb(WockyJingleContent * content,GList * clist,gpointer user_data)495 content_new_remote_candidates_cb (WockyJingleContent *content,
496     GList *clist, gpointer user_data)
497 {
498   GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
499   GList *li;
500 
501   DEBUG ("Got new remote candidates : %d", g_list_length (clist));
502 
503   for (li = clist; li; li = li->next)
504     {
505       WockyJingleCandidate *candidate = li->data;
506       NiceCandidate *cand = NULL;
507       ShareChannel *share_channel = NULL;
508       GSList *candidates = NULL;
509 
510       if (candidate->protocol != WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP)
511         {
512           DEBUG ("Ignoring candidate %s because of non-UDP protocol : %d",
513               candidate->username, candidate->protocol);
514           continue;
515         }
516 
517       share_channel = g_hash_table_lookup (self->priv->share_channels,
518           GINT_TO_POINTER (candidate->component));
519       if (share_channel == NULL)
520         {
521           DEBUG ("Ignoring candidate %s because of unknown component id %d",
522               candidate->id, candidate->component);
523           continue;
524         }
525 
526       cand = nice_candidate_new (
527           candidate->type == WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL?
528           NICE_CANDIDATE_TYPE_HOST:
529           candidate->type == WOCKY_JINGLE_CANDIDATE_TYPE_STUN?
530           NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
531           NICE_CANDIDATE_TYPE_RELAYED);
532 
533 
534       cand->transport = WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP;
535       nice_address_init (&cand->addr);
536 
537       if (!nice_address_set_from_string (&cand->addr, candidate->address))
538         {
539           DEBUG ("invalid address '%s' in candidate, skipping",
540               candidate->address ? candidate->address : "<null>");
541           nice_candidate_free (cand);
542           continue;
543         }
544 
545       nice_address_set_port (&cand->addr, candidate->port);
546       cand->priority = candidate->preference * 1000;
547       cand->stream_id = share_channel->stream_id;
548       cand->component_id = share_channel->component_id;
549       /*
550       if (c->id == NULL)
551         candidate_id = g_strdup_printf ("R%d", ++priv->remote_candidate_count);
552       else
553       candidate_id = c->id;*/
554       if (candidate->id != NULL)
555         strncpy (cand->foundation, candidate->id,
556             NICE_CANDIDATE_MAX_FOUNDATION - 1);
557       else if (candidate->username != NULL)
558         strncpy (cand->foundation, candidate->username,
559             NICE_CANDIDATE_MAX_FOUNDATION - 1);
560 
561       cand->username = g_strdup (candidate->username?candidate->username:"");
562       cand->password = g_strdup (candidate->password?candidate->password:"");
563 
564       candidates = g_slist_append (candidates, cand);
565       nice_agent_set_remote_candidates (share_channel->agent,
566           share_channel->stream_id, share_channel->component_id, candidates);
567       g_slist_foreach (candidates, (GFunc)nice_candidate_free, NULL);
568       g_slist_free (candidates);
569     }
570 }
571 
572 static void
nice_candidate_gathering_done(NiceAgent * agent,guint stream_id,gpointer user_data)573 nice_candidate_gathering_done (NiceAgent *agent, guint stream_id,
574     gpointer user_data)
575 {
576   GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
577   ShareChannel *share_channel = get_share_channel (self, agent);
578   WockyJingleContent *content = WOCKY_JINGLE_CONTENT (share_channel->content);
579   GList *candidates = NULL;
580   GList *remote_candidates = NULL;
581   GSList *local_candidates;
582   GSList *li;
583 
584   DEBUG ("libnice candidate gathering done!!!!");
585 
586   /* Send remote candidates to libnice and listen to new signal */
587   remote_candidates = wocky_jingle_content_get_remote_candidates (content);
588   content_new_remote_candidates_cb (content, remote_candidates, self);
589 
590   gabble_signal_connect_weak (content, "new-candidates",
591       (GCallback) content_new_remote_candidates_cb, G_OBJECT (self));
592 
593   /* Send gathered local candidates to the content */
594   local_candidates = nice_agent_get_local_candidates (agent, stream_id,
595       share_channel->component_id);
596 
597   for (li = local_candidates; li; li = li->next)
598     {
599       NiceCandidate *cand = li->data;
600       WockyJingleCandidate *candidate;
601       gchar ip[NICE_ADDRESS_STRING_LEN];
602 
603       nice_address_to_string (&cand->addr, ip);
604 
605       candidate = wocky_jingle_candidate_new (
606           /* protocol */
607           cand->transport == NICE_CANDIDATE_TRANSPORT_UDP?
608           WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP:
609           WOCKY_JINGLE_TRANSPORT_PROTOCOL_TCP,
610           /* candidate type */
611           cand->type == NICE_CANDIDATE_TYPE_HOST?
612           WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL:
613           cand->type == NICE_CANDIDATE_TYPE_RELAYED?
614           WOCKY_JINGLE_CANDIDATE_TYPE_RELAY:
615           WOCKY_JINGLE_CANDIDATE_TYPE_STUN,
616           /* id */
617           cand->foundation,
618           /* component */
619           share_channel->share_channel_id,
620           /* address */
621           ip,
622           /* port */
623           nice_address_get_port (&cand->addr),
624           /* generation */
625           0,
626           /* preference */
627           (gfloat) cand->priority / 1000.0,
628           /* username */
629           cand->username?cand->username:"",
630           /* password */
631           cand->password?cand->password:"",
632           /* network */
633           0);
634 
635       candidates = g_list_prepend (candidates, candidate);
636     }
637 
638   wocky_jingle_content_add_candidates (content, candidates);
639 }
640 
641 static void
nice_component_state_changed(NiceAgent * agent,guint stream_id,guint component_id,guint state,gpointer user_data)642 nice_component_state_changed (NiceAgent *agent,  guint stream_id,
643     guint component_id, guint state, gpointer user_data)
644 {
645   GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
646   ShareChannel *share_channel = get_share_channel (self, agent);
647   WockyJingleContent *content = WOCKY_JINGLE_CONTENT (share_channel->content);
648   WockyJingleTransportState ts = WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED;
649 
650   DEBUG ("libnice component state changed %d!!!!", state);
651 
652   switch (state)
653     {
654       case NICE_COMPONENT_STATE_DISCONNECTED:
655       case NICE_COMPONENT_STATE_GATHERING:
656         ts = WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED;
657         break;
658       case NICE_COMPONENT_STATE_CONNECTING:
659         ts = WOCKY_JINGLE_TRANSPORT_STATE_CONNECTING;
660         break;
661       case NICE_COMPONENT_STATE_CONNECTED:
662       case NICE_COMPONENT_STATE_READY:
663         ts = WOCKY_JINGLE_TRANSPORT_STATE_CONNECTED;
664         break;
665       case NICE_COMPONENT_STATE_FAILED:
666         {
667           GList *i;
668 
669           for (i = self->priv->channels; i;)
670             {
671               GabbleFileTransferChannel *channel = i->data;
672 
673               i = i->next;
674               gabble_file_transfer_channel_gtalk_file_collection_state_changed (
675                   channel, GTALK_FILE_COLLECTION_STATE_CONNECTION_FAILED,
676                   TRUE);
677             }
678           /* return because we don't want to use the content after it
679              has been destroyed.. */
680           return;
681         }
682     }
683   wocky_jingle_content_set_transport_state (content, ts);
684 }
685 
686 static void
get_next_manifest_entry(GTalkFileCollection * self,ShareChannel * share_channel,gboolean error)687 get_next_manifest_entry (GTalkFileCollection *self,
688     ShareChannel *share_channel, gboolean error)
689 {
690   GabbleJingleShareManifest *manifest = NULL;
691   GabbleJingleShareManifestEntry *entry = NULL;
692   GabbleFileTransferChannel *channel = NULL;
693   GList *i;
694 
695   DEBUG ("called");
696 
697   if (self->priv->current_channel != NULL)
698     {
699       if (g_list_length (self->priv->channels) == 1)
700         {
701           WockyJingleContent *content = \
702               WOCKY_JINGLE_CONTENT (share_channel->content);
703 
704           DEBUG ("Received all the files. Transfer is complete");
705           wocky_jingle_content_send_complete (content);
706         }
707 
708       g_hash_table_replace (self->priv->channels_usable,
709           self->priv->current_channel, GINT_TO_POINTER (FALSE));
710       gabble_file_transfer_channel_gtalk_file_collection_state_changed (
711           self->priv->current_channel,
712           error ? GTALK_FILE_COLLECTION_STATE_ERROR:
713           GTALK_FILE_COLLECTION_STATE_COMPLETED, FALSE);
714 
715       set_current_channel (self, NULL);
716     }
717 
718   manifest = gabble_jingle_share_get_manifest (share_channel->content);
719   for (i = manifest->entries; i; i = i->next)
720     {
721       gchar *filename = NULL;
722       gboolean usable;
723 
724       entry = i->data;
725 
726       filename = g_strdup_printf ("%s%s", entry->name,
727           (entry->folder ? ".tar" : ""));
728       channel = get_channel_by_filename (self, filename);
729       g_free (filename);
730       if (channel != NULL)
731         {
732           usable = GPOINTER_TO_INT (g_hash_table_lookup (
733                   self->priv->channels_usable, channel));
734           if (usable)
735             break;
736         }
737       entry = NULL;
738     }
739 
740   self->priv->status = GTALK_FT_STATUS_WAITING;
741 
742 
743   if (entry != NULL)
744     {
745       gchar *buffer = NULL;
746       gchar *source_url = manifest->source_url;
747       guint url_len = (source_url != NULL? strlen (source_url) : 0);
748       gchar *separator = "";
749       gchar *filename = NULL;
750 
751       if (source_url != NULL && source_url[url_len -1] != '/')
752         separator = "/";
753 
754       self->priv->status = GTALK_FT_STATUS_TRANSFERRING;
755 
756       filename = g_uri_escape_string (entry->name, NULL, TRUE);
757 
758       /* The session initiator will always be the full JID of the peer */
759       buffer = g_strdup_printf ("GET %s%s%s HTTP/1.1\r\n"
760           "Connection: Keep-Alive\r\n"
761           "Content-Length: 0\r\n"
762           "Host: %s:0\r\n" /* e.g. alice@example.com/Empathy:0 */
763           "User-Agent: %s\r\n\r\n",
764           (source_url != NULL ? source_url : ""),
765           separator, filename,
766           wocky_jingle_session_get_initiator (self->priv->jingle),
767           PACKAGE_STRING);
768       g_free (filename);
769 
770       /* FIXME: check for success */
771       nice_agent_send (share_channel->agent, share_channel->stream_id,
772           share_channel->component_id, strlen (buffer), buffer);
773       g_free (buffer);
774 
775       share_channel->http_status = HTTP_CLIENT_RECEIVE;
776       /* Block or unblock accordingly */
777       set_current_channel (self, channel);
778     }
779 }
780 
781 static void
nice_component_writable(NiceAgent * agent,guint stream_id,guint component_id,gpointer user_data)782 nice_component_writable (NiceAgent *agent, guint stream_id, guint component_id,
783     gpointer user_data)
784 {
785   GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
786   ShareChannel *share_channel = get_share_channel (self, agent);
787 
788   if (share_channel->http_status == HTTP_CLIENT_IDLE)
789     {
790       get_next_manifest_entry (self, share_channel, FALSE);
791     }
792   else if (share_channel->http_status == HTTP_SERVER_SEND)
793     {
794       if (self->priv->current_channel == NULL)
795         {
796           GList *i;
797 
798           DEBUG ("Unexpected current_channel == NULL!");
799           for (i = self->priv->channels; i;)
800             {
801               GabbleFileTransferChannel *channel = i->data;
802 
803               i = i->next;
804               gabble_file_transfer_channel_gtalk_file_collection_state_changed (
805                   channel, GTALK_FILE_COLLECTION_STATE_ERROR, FALSE);
806             }
807           return;
808         }
809       gabble_file_transfer_channel_gtalk_file_collection_write_blocked (
810           self->priv->current_channel, FALSE);
811       if (share_channel->write_buffer != NULL)
812         {
813           gint ret = nice_agent_send (agent, stream_id, component_id,
814               share_channel->write_len, share_channel->write_buffer);
815 
816           if (ret < 0 || (guint) ret < share_channel->write_len)
817             {
818               gchar *to_free = share_channel->write_buffer;
819 
820               if (ret < 0)
821                 ret = 0;
822 
823               share_channel->write_buffer = g_memdup (
824                   share_channel->write_buffer + ret,
825                   share_channel->write_len - ret);
826               share_channel->write_len = share_channel->write_len - ret;
827               g_free (to_free);
828 
829               gabble_file_transfer_channel_gtalk_file_collection_write_blocked (
830                   self->priv->current_channel, TRUE);
831             }
832           else
833             {
834               g_free (share_channel->write_buffer);
835               share_channel->write_buffer = NULL;
836               share_channel->write_len = 0;
837             }
838         }
839     }
840 
841 }
842 
843 typedef struct
844 {
845   union {
846     gpointer ptr;
847     GTalkFileCollection *self;
848   } u;
849   ShareChannel *share_channel;
850 } GoogleRelaySessionData;
851 
852 static const NiceRelayType relay_type_map[] = {
853     /* WOCKY_JINGLE_RELAY_TYPE_UDP */ NICE_RELAY_TYPE_TURN_UDP,
854     /* WOCKY_JINGLE_RELAY_TYPE_TCP */ NICE_RELAY_TYPE_TURN_TCP,
855     /* WOCKY_JINGLE_RELAY_TYPE_TLS */ NICE_RELAY_TYPE_TURN_TLS,
856 };
857 
858 static void
set_relay_info(gpointer item,gpointer user_data)859 set_relay_info (gpointer item, gpointer user_data)
860 {
861   GoogleRelaySessionData *data = user_data;
862   WockyJingleRelay *relay = item;
863   NiceRelayType type;
864 
865   g_return_if_fail (relay->type < WOCKY_N_JINGLE_RELAY_TYPES);
866   type = relay_type_map[relay->type];
867 
868   nice_agent_set_relay_info (data->share_channel->agent,
869       data->share_channel->stream_id, data->share_channel->component_id,
870       relay->ip, relay->port, relay->username, relay->password, type);
871 }
872 
873 static void
google_relay_session_cb(GPtrArray * relays,gpointer user_data)874 google_relay_session_cb (GPtrArray *relays, gpointer user_data)
875 {
876   GoogleRelaySessionData *data = user_data;
877 
878   if (data->u.self == NULL)
879     {
880       DEBUG ("Received relay session callback but self got destroyed");
881       g_slice_free (GoogleRelaySessionData, data);
882       return;
883     }
884 
885   if (relays != NULL)
886       g_ptr_array_foreach (relays, set_relay_info, user_data);
887 
888   nice_agent_gather_candidates (data->share_channel->agent,
889       data->share_channel->stream_id);
890 
891   g_object_remove_weak_pointer (G_OBJECT (data->u.self), &data->u.ptr);
892   g_slice_free (GoogleRelaySessionData, data);
893 }
894 
895 
896 static void
content_new_share_channel_cb(WockyJingleContent * content,const gchar * name,guint share_channel_id,gpointer user_data)897 content_new_share_channel_cb (WockyJingleContent *content, const gchar *name,
898     guint share_channel_id, gpointer user_data)
899 {
900   GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
901   ShareChannel *share_channel = g_slice_new0 (ShareChannel);
902   NiceAgent *agent = nice_agent_new_reliable (g_main_context_default (),
903       NICE_COMPATIBILITY_GOOGLE);
904   guint stream_id = nice_agent_add_stream (agent, 1);
905   GList *stun_servers;
906   GoogleRelaySessionData *relay_data = NULL;
907 
908   DEBUG ("New Share channel %s was created and linked to id %d", name,
909       share_channel_id);
910 
911   if (test_mode)
912     g_object_set (agent, "upnp", FALSE, NULL);
913 
914   share_channel->agent = agent;
915   share_channel->stream_id = stream_id;
916   share_channel->component_id = NICE_COMPONENT_TYPE_RTP;
917   share_channel->content = GABBLE_JINGLE_SHARE (content);
918   share_channel->share_channel_id = share_channel_id;
919 
920   if (self->priv->requested)
921       share_channel->http_status = HTTP_SERVER_IDLE;
922   else
923       share_channel->http_status = HTTP_CLIENT_IDLE;
924 
925   gabble_signal_connect_weak (agent, "candidate-gathering-done",
926       G_CALLBACK (nice_candidate_gathering_done), G_OBJECT (self));
927 
928   gabble_signal_connect_weak (agent, "component-state-changed",
929       G_CALLBACK (nice_component_state_changed), G_OBJECT (self));
930 
931   gabble_signal_connect_weak (agent, "reliable-transport-writable",
932       G_CALLBACK (nice_component_writable), G_OBJECT (self));
933 
934 
935   /* Add the agent to the hash table before gathering candidates in case the
936      gathering finishes synchronously, and the callback tries to add local
937      candidates to the content, it needs to find the share channel id.. */
938   g_hash_table_insert (self->priv->share_channels,
939       GINT_TO_POINTER (share_channel_id), share_channel);
940 
941   share_channel->agent_attached = TRUE;
942   nice_agent_attach_recv (agent, stream_id, share_channel->component_id,
943       g_main_context_default (), nice_data_received_cb, self);
944 
945   stun_servers = wocky_jingle_info_get_stun_servers (
946       wocky_jingle_factory_get_jingle_info (self->priv->jingle_factory));
947   if (stun_servers != NULL)
948     {
949       WockyStunServer *stun_server = stun_servers->data;
950 
951       g_object_set (agent,
952           "stun-server", stun_server->address,
953           "stun-server-port", (guint) stun_server->port,
954           NULL);
955 
956       g_list_free (stun_servers);
957     }
958 
959   relay_data = g_slice_new0 (GoogleRelaySessionData);
960   relay_data->u.self = self;
961   relay_data->share_channel = share_channel;
962   g_object_add_weak_pointer (G_OBJECT (relay_data->u.self),
963       &relay_data->u.ptr);
964   wocky_jingle_info_create_google_relay_session (
965       wocky_jingle_factory_get_jingle_info (self->priv->jingle_factory), 1,
966       google_relay_session_cb, relay_data);
967 }
968 
969 static void
content_completed(WockyJingleContent * content,gpointer user_data)970 content_completed (WockyJingleContent *content, gpointer user_data)
971 {
972   GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
973   GList *i;
974 
975   DEBUG ("Received content completed");
976 
977   for (i = self->priv->channels; i;)
978     {
979       GabbleFileTransferChannel *channel = i->data;
980 
981       i = i->next;
982       gabble_file_transfer_channel_gtalk_file_collection_state_changed (
983           channel, GTALK_FILE_COLLECTION_STATE_COMPLETED, FALSE);
984     }
985 }
986 
987 static void
free_share_channel(gpointer data)988 free_share_channel (gpointer data)
989 {
990   ShareChannel *share_channel = (ShareChannel *) data;
991 
992   DEBUG ("Freeing jingle Share channel");
993 
994   tp_clear_pointer (&share_channel->write_buffer, g_free);
995   tp_clear_pointer (&share_channel->read_buffer, g_free);
996   g_object_unref (share_channel->agent);
997   g_slice_free (ShareChannel, share_channel);
998 }
999 
1000 
1001 /* If buffer contains a line ending, 0-terminate the first line and
1002  * return a pointer to the beginning of the next line. Otherwise
1003  * return NULL. */
1004 static gchar *
http_read_line(gchar * buffer,guint len)1005 http_read_line (gchar *buffer, guint len)
1006 {
1007   gchar *p = memchr (buffer, '\n', len);
1008 
1009   if (p != NULL)
1010     {
1011       *p = 0;
1012       if (p > buffer && *(p-1) == '\r')
1013         *(p-1) = '\0';
1014       p++;
1015     }
1016 
1017   return p;
1018 }
1019 
1020 static guint
http_data_received(GTalkFileCollection * self,ShareChannel * share_channel,gchar * buffer,guint len)1021 http_data_received (GTalkFileCollection *self, ShareChannel *share_channel,
1022     gchar *buffer, guint len)
1023 {
1024 
1025   switch (share_channel->http_status)
1026     {
1027       case HTTP_SERVER_IDLE:
1028         {
1029           gchar *headers = http_read_line (buffer, len);
1030 
1031           if (headers == NULL)
1032             return 0;
1033 
1034           share_channel->http_status = HTTP_SERVER_HEADERS;
1035           share_channel->status_line = g_strdup (buffer);
1036 
1037           if (self->priv->current_channel != NULL)
1038             {
1039               DEBUG ("Received status line with current channel set");
1040               gabble_file_transfer_channel_gtalk_file_collection_state_changed (
1041                   self->priv->current_channel,
1042                   GTALK_FILE_COLLECTION_STATE_COMPLETED, FALSE);
1043               set_current_channel (self, NULL);
1044             }
1045 
1046           return headers - buffer;
1047         }
1048         break;
1049       case HTTP_SERVER_HEADERS:
1050         {
1051           gchar *line = buffer;
1052           gchar *next_line = http_read_line (buffer, len);
1053 
1054           if (next_line == NULL)
1055             return 0;
1056 
1057           DEBUG ("Found server headers line (%" G_GSIZE_FORMAT ") : %s",
1058               strlen (line), line);
1059           /* FIXME: how about content-length and an actual body ? */
1060           if (line[0] == '\0')
1061             {
1062               gchar *response = NULL;
1063               gchar *get_line = NULL;
1064               GabbleJingleShareManifest *manifest = NULL;
1065               gchar *source_url = NULL;
1066               guint url_len;
1067               gchar *separator = "";
1068               gchar *filename = NULL;
1069               GabbleFileTransferChannel *channel = NULL;
1070 
1071               g_assert (self->priv->current_channel == NULL);
1072 
1073               DEBUG ("Found empty line, received request : %s ",
1074                   share_channel->status_line);
1075 
1076               manifest = gabble_jingle_share_get_manifest (
1077                   share_channel->content);
1078               source_url = manifest->source_url;
1079               url_len = (source_url != NULL? strlen (source_url) : 0);
1080               if (source_url != NULL && source_url[url_len -1] != '/')
1081                 separator = "/";
1082 
1083               get_line = g_strdup_printf ("GET %s%s%%s HTTP/1.1",
1084                   (source_url != NULL ? source_url : ""),
1085                   separator);
1086               filename = g_malloc (strlen (share_channel->status_line));
1087 
1088               if (sscanf (share_channel->status_line, get_line, filename) == 1)
1089                 {
1090                   gchar *unescaped = g_uri_unescape_string (filename, NULL);
1091 
1092                   g_free (filename);
1093                   filename = unescaped;
1094                   channel = get_channel_by_filename (self, filename);
1095                 }
1096 
1097               if (channel != NULL)
1098                 {
1099                   guint64 size;
1100 
1101                   g_object_get (channel,
1102                       "size", &size,
1103                       NULL);
1104 
1105                   DEBUG ("Found valid filename, result : 200");
1106 
1107                   share_channel->http_status = HTTP_SERVER_SEND;
1108                   response = g_strdup_printf ("HTTP/1.1 200\r\n"
1109                       "Connection: Keep-Alive\r\n"
1110                       "Content-Length: %" G_GUINT64_FORMAT "\r\n"
1111                       "Content-Type: application/octet-stream\r\n\r\n",
1112                       size);
1113 
1114                 }
1115               else
1116                 {
1117                   DEBUG ("Unable to find valid filename (%s), result : 404",
1118                       (filename != NULL? filename : ""));
1119 
1120                   share_channel->http_status = HTTP_SERVER_IDLE;
1121                   response = g_strdup_printf ("HTTP/1.1 404\r\n"
1122                       "Connection: Keep-Alive\r\n"
1123                       "Content-Length: 0\r\n\r\n");
1124                 }
1125 
1126               /* FIXME: check for success of nice_agent_send */
1127               nice_agent_send (share_channel->agent, share_channel->stream_id,
1128                   share_channel->component_id, strlen (response), response);
1129 
1130               g_free (response);
1131               g_free (filename);
1132               g_free (get_line);
1133 
1134               /* Now that we sent our response, we can assign the current
1135                  channel which sets it to OPEN (if non NULL) so data can
1136                  start flowing */
1137               self->priv->status = GTALK_FT_STATUS_TRANSFERRING;
1138               set_current_channel (self, channel);
1139             }
1140 
1141           return next_line - buffer;
1142         }
1143         break;
1144       case HTTP_SERVER_SEND:
1145         DEBUG ("received data when we're supposed to be sending data.. "
1146             "not supposed to happen");
1147         break;
1148       case HTTP_CLIENT_IDLE:
1149         DEBUG ("received data when we're supposed to be sending the GET.. "
1150             "not supposed to happen");
1151         break;
1152       case HTTP_CLIENT_RECEIVE:
1153         {
1154           gchar *headers = http_read_line (buffer, len);
1155 
1156           if (headers == NULL)
1157             return 0;
1158 
1159           share_channel->http_status = HTTP_CLIENT_HEADERS;
1160           share_channel->status_line = g_strdup (buffer);
1161 
1162           return headers - buffer;
1163         }
1164       case HTTP_CLIENT_HEADERS:
1165         {
1166           gchar *line = buffer;
1167           gchar *next_line = http_read_line (buffer, len);
1168 
1169           if (next_line == NULL)
1170             return 0;
1171 
1172           DEBUG ("Found client headers line (%" G_GSIZE_FORMAT ") : %s",
1173               strlen (line), line);
1174           if (line[0] == '\0')
1175             {
1176               DEBUG ("Found empty line, GET response : %s",
1177                   share_channel->status_line);
1178 
1179               if (g_str_has_prefix (share_channel->status_line,
1180                       "HTTP/1.1 200"))
1181                 {
1182                   if (share_channel->is_chunked)
1183                     {
1184                       share_channel->http_status = HTTP_CLIENT_CHUNK_SIZE;
1185                     }
1186                   else
1187                     {
1188                       share_channel->http_status = HTTP_CLIENT_BODY;
1189                       if (share_channel->content_length == 0)
1190                         get_next_manifest_entry (self, share_channel, FALSE);
1191                     }
1192                 }
1193               else
1194                 {
1195                   /* We expect content-length to be 0 and no chunks for
1196                      non-200 statuses (404 error) */
1197                   if (share_channel->is_chunked ||
1198                       share_channel->content_length != 0)
1199                     {
1200                       GList *i;
1201 
1202                       DEBUG ("Unexpected body for non-200 error!");
1203                       for (i = self->priv->channels; i;)
1204                         {
1205                           GabbleFileTransferChannel *channel = i->data;
1206 
1207                           i = i->next;
1208                           gabble_file_transfer_channel_gtalk_file_collection_state_changed (
1209                               channel, GTALK_FILE_COLLECTION_STATE_ERROR, FALSE);
1210                         }
1211                     }
1212                   else
1213                     {
1214                       get_next_manifest_entry (self, share_channel, TRUE);
1215                     }
1216                 }
1217             }
1218           else if (!g_ascii_strncasecmp (line, "Content-Length: ", 16))
1219             {
1220               share_channel->is_chunked = FALSE;
1221               /* Check strtoull read all the length */
1222               share_channel->content_length = g_ascii_strtoull (line + 16,
1223                   NULL, 10);
1224               DEBUG ("Found data length : %" G_GUINT64_FORMAT,
1225                   share_channel->content_length);
1226             }
1227           else if (!g_ascii_strcasecmp (line,
1228                   "Transfer-Encoding: chunked"))
1229             {
1230               share_channel->is_chunked = TRUE;
1231               share_channel->content_length = 0;
1232               DEBUG ("Found file is chunked");
1233             }
1234 
1235           return next_line - buffer;
1236         }
1237         break;
1238       case HTTP_CLIENT_CHUNK_SIZE:
1239         {
1240           gchar *line = buffer;
1241           gchar *next_line = http_read_line (buffer, len);
1242 
1243           if (next_line == NULL)
1244             return 0;
1245 
1246           /* FIXME : check validity of strtoul */
1247           share_channel->content_length = strtoul (line, NULL, 16);
1248           if (share_channel->content_length > 0)
1249               share_channel->http_status = HTTP_CLIENT_BODY;
1250           else
1251               share_channel->http_status = HTTP_CLIENT_CHUNK_FINAL;
1252 
1253 
1254           return next_line - buffer;
1255         }
1256         break;
1257       case HTTP_CLIENT_BODY:
1258         {
1259           guint consumed = 0;
1260 
1261           if (len >= share_channel->content_length)
1262             {
1263               if (self->priv->current_channel == NULL)
1264                 {
1265                   GList *i;
1266 
1267                   DEBUG ("Unexpected current_channel == NULL!");
1268                   for (i = self->priv->channels; i;)
1269                     {
1270                       GabbleFileTransferChannel *channel = i->data;
1271 
1272                       i = i->next;
1273                       gabble_file_transfer_channel_gtalk_file_collection_state_changed (
1274                           channel, GTALK_FILE_COLLECTION_STATE_ERROR, FALSE);
1275                     }
1276                   /* FIXME: Who knows what might happen here if we got destroyed
1277                      It shouldn't crash since our object isn't dereferences
1278                      anymore, but.. */
1279                   return len;
1280                 }
1281               consumed = share_channel->content_length;
1282               gabble_file_transfer_channel_gtalk_file_collection_data_received (
1283                   self->priv->current_channel, buffer, consumed);
1284               share_channel->content_length = 0;
1285               if (share_channel->is_chunked)
1286                 share_channel->http_status = HTTP_CLIENT_CHUNK_END;
1287               else
1288                 get_next_manifest_entry (self, share_channel, FALSE);
1289             }
1290           else
1291             {
1292               consumed = len;
1293               share_channel->content_length -= len;
1294               gabble_file_transfer_channel_gtalk_file_collection_data_received (
1295                   self->priv->current_channel, buffer, consumed);
1296             }
1297 
1298           return consumed;
1299         }
1300         break;
1301       case HTTP_CLIENT_CHUNK_END:
1302         {
1303           gchar *chunk = http_read_line (buffer, len);
1304 
1305           if (chunk == NULL)
1306             return 0;
1307 
1308           share_channel->http_status = HTTP_CLIENT_CHUNK_SIZE;
1309 
1310           return chunk - buffer;
1311         }
1312         break;
1313       case HTTP_CLIENT_CHUNK_FINAL:
1314         {
1315           gchar *end = http_read_line (buffer, len);
1316 
1317           if (end == NULL)
1318             return 0;
1319 
1320           share_channel->http_status = HTTP_CLIENT_IDLE;
1321           get_next_manifest_entry (self, share_channel, FALSE);
1322 
1323           return end - buffer;
1324         }
1325         break;
1326     }
1327 
1328   return 0;
1329 }
1330 
1331 static void
nice_data_received_cb(NiceAgent * agent,guint stream_id,guint component_id,guint len,gchar * buffer,gpointer user_data)1332 nice_data_received_cb (NiceAgent *agent,
1333                        guint stream_id,
1334                        guint component_id,
1335                        guint len,
1336                        gchar *buffer,
1337                        gpointer user_data)
1338 {
1339   GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
1340   ShareChannel *share_channel = get_share_channel (self, agent);
1341   gchar *free_buffer = NULL;
1342 
1343   if (share_channel->read_buffer != NULL)
1344     {
1345       gchar *tmp = g_malloc (share_channel->read_len + len);
1346 
1347       memcpy (tmp, share_channel->read_buffer, share_channel->read_len);
1348       memcpy (tmp + share_channel->read_len, buffer, len);
1349 
1350       free_buffer = buffer = tmp;
1351       len += share_channel->read_len;
1352 
1353       g_free (share_channel->read_buffer);
1354       share_channel->read_buffer = NULL;
1355       share_channel->read_len = 0;
1356     }
1357   while (len > 0)
1358     {
1359       guint consumed = http_data_received (self, share_channel, buffer, len);
1360 
1361       if (consumed == 0)
1362         {
1363           share_channel->read_buffer = g_memdup (buffer, len);
1364           share_channel->read_len = len;
1365           break;
1366         }
1367       else
1368         {
1369           /* we assume http_data_received never returns consumed > len */
1370           g_assert (consumed <= len);
1371 
1372           len -= consumed;
1373           buffer += consumed;
1374         }
1375     }
1376 
1377   if (free_buffer != NULL)
1378     g_free (free_buffer);
1379 
1380 }
1381 
1382 static void
set_session(GTalkFileCollection * self,WockyJingleSession * session,WockyJingleContent * content)1383 set_session (GTalkFileCollection * self,
1384     WockyJingleSession *session, WockyJingleContent *content)
1385 {
1386   self->priv->jingle = g_object_ref (session);
1387 
1388   gabble_signal_connect_weak (session, "notify::state",
1389       (GCallback) jingle_session_state_changed_cb, G_OBJECT (self));
1390   gabble_signal_connect_weak (session, "terminated",
1391       (GCallback) jingle_session_terminated_cb, G_OBJECT (self));
1392 
1393   gabble_signal_connect_weak (content, "new-share-channel",
1394       (GCallback) content_new_share_channel_cb, G_OBJECT (self));
1395   gabble_signal_connect_weak (content, "completed",
1396       (GCallback) content_completed, G_OBJECT (self));
1397 
1398   self->priv->status = GTALK_FT_STATUS_PENDING;
1399 }
1400 
1401 GTalkFileCollection *
gtalk_file_collection_new(GabbleFileTransferChannel * channel,WockyJingleFactory * jingle_factory,TpHandle handle,const gchar * jid)1402 gtalk_file_collection_new (GabbleFileTransferChannel *channel,
1403     WockyJingleFactory *jingle_factory, TpHandle handle, const gchar *jid)
1404 {
1405   GTalkFileCollection * self = g_object_new (GTALK_TYPE_FILE_COLLECTION, NULL);
1406   WockyJingleSession *session = NULL;
1407   WockyJingleContent *content = NULL;
1408   gchar *filename;
1409   guint64 size;
1410 
1411   self->priv->jingle_factory = jingle_factory;
1412   self->priv->requested = TRUE;
1413 
1414   session = wocky_jingle_factory_create_session (jingle_factory,
1415       jid, WOCKY_JINGLE_DIALECT_GTALK4, FALSE);
1416 
1417   if (session == NULL)
1418     {
1419       g_object_unref (self);
1420       return NULL;
1421     }
1422 
1423   content = wocky_jingle_session_add_content (session,
1424       WOCKY_JINGLE_MEDIA_TYPE_NONE, WOCKY_JINGLE_CONTENT_SENDERS_BOTH, "share",
1425       NS_GOOGLE_SESSION_SHARE, NS_GOOGLE_TRANSPORT_P2P);
1426 
1427   if (content == NULL)
1428     {
1429       g_object_unref (self);
1430       g_object_unref (session);
1431       return NULL;
1432     }
1433 
1434   g_object_get (channel,
1435       "filename", &filename,
1436       "size", &size,
1437       NULL);
1438   g_object_set (content,
1439       "filename", filename,
1440       "filesize", size,
1441       NULL);
1442 
1443   set_session (self, session, content);
1444 
1445   add_channel (self, channel);
1446 
1447 
1448   return self;
1449 }
1450 
1451 GTalkFileCollection *
gtalk_file_collection_new_from_session(WockyJingleFactory * jingle_factory,WockyJingleSession * session)1452 gtalk_file_collection_new_from_session (WockyJingleFactory *jingle_factory,
1453     WockyJingleSession *session)
1454 {
1455   GTalkFileCollection * self = NULL;
1456   WockyJingleContent *content = NULL;
1457   GList *cs;
1458 
1459   if (wocky_jingle_session_get_content_type (session) !=
1460       GABBLE_TYPE_JINGLE_SHARE)
1461       return NULL;
1462 
1463   cs = wocky_jingle_session_get_contents (session);
1464 
1465   if (cs != NULL)
1466     {
1467       content = WOCKY_JINGLE_CONTENT (cs->data);
1468       g_list_free (cs);
1469     }
1470 
1471   if (content == NULL)
1472     return NULL;
1473 
1474   self = g_object_new (GTALK_TYPE_FILE_COLLECTION, NULL);
1475 
1476   self->priv->jingle_factory = jingle_factory;
1477   self->priv->requested = FALSE;
1478 
1479   set_session (self, session, content);
1480 
1481   return self;
1482 }
1483 
1484 void
gtalk_file_collection_add_channel(GTalkFileCollection * self,GabbleFileTransferChannel * channel)1485 gtalk_file_collection_add_channel (GTalkFileCollection *self,
1486     GabbleFileTransferChannel *channel)
1487 {
1488       add_channel (self, channel);
1489 }
1490 
1491 void
gtalk_file_collection_initiate(GTalkFileCollection * self,GabbleFileTransferChannel * channel)1492 gtalk_file_collection_initiate (GTalkFileCollection *self,
1493     GabbleFileTransferChannel * channel)
1494 {
1495   if (channel_exists (self, channel))
1496     {
1497       g_hash_table_replace (self->priv->channels_reading, channel,
1498           GINT_TO_POINTER (TRUE));
1499       g_hash_table_replace (self->priv->channels_usable, channel,
1500           GINT_TO_POINTER (TRUE));
1501     }
1502 
1503   if (self->priv->status == GTALK_FT_STATUS_PENDING)
1504     {
1505       wocky_jingle_session_accept (self->priv->jingle);
1506       self->priv->status = GTALK_FT_STATUS_INITIATED;
1507     }
1508   else
1509     {
1510       gabble_file_transfer_channel_gtalk_file_collection_state_changed (
1511           channel, GTALK_FILE_COLLECTION_STATE_ACCEPTED, FALSE);
1512     }
1513 
1514 }
1515 
1516 void
gtalk_file_collection_accept(GTalkFileCollection * self,GabbleFileTransferChannel * channel)1517 gtalk_file_collection_accept (GTalkFileCollection *self,
1518     GabbleFileTransferChannel * channel)
1519 {
1520   GList *cs = wocky_jingle_session_get_contents (self->priv->jingle);
1521 
1522   DEBUG ("called");
1523 
1524   if (channel_exists (self, channel))
1525     {
1526       g_hash_table_replace (self->priv->channels_usable, channel,
1527           GINT_TO_POINTER (TRUE));
1528     }
1529 
1530   if (self->priv->status == GTALK_FT_STATUS_PENDING)
1531     {
1532       if (cs != NULL)
1533         {
1534           WockyJingleContent *content = WOCKY_JINGLE_CONTENT (cs->data);
1535           guint initial_id = 0;
1536           guint share_channel_id;
1537 
1538           wocky_jingle_session_accept (self->priv->jingle);
1539           self->priv->status = GTALK_FT_STATUS_ACCEPTED;
1540 
1541           /* The new-share-channel signal will take care of the rest.. */
1542           do
1543             {
1544               gchar *share_channel_name = NULL;
1545 
1546               share_channel_name = g_strdup_printf ("gabble-%d", ++initial_id);
1547               share_channel_id = wocky_jingle_content_create_share_channel (
1548                   content, share_channel_name);
1549               g_free (share_channel_name);
1550             } while (share_channel_id == 0 && initial_id < 10);
1551 
1552           /* FIXME: not assert but actually cancel the FT? */
1553           g_assert (share_channel_id > 0);
1554           g_list_free (cs);
1555         }
1556 
1557     }
1558   else
1559     {
1560       gabble_file_transfer_channel_gtalk_file_collection_state_changed (
1561           channel, GTALK_FILE_COLLECTION_STATE_ACCEPTED, FALSE);
1562     }
1563 
1564   if (self->priv->status == GTALK_FT_STATUS_WAITING)
1565     {
1566       /* FIXME: this and other lookups should not check for channel '1' */
1567       ShareChannel *share_channel = g_hash_table_lookup (
1568           self->priv->share_channels, GINT_TO_POINTER (1));
1569 
1570       get_next_manifest_entry (self, share_channel, FALSE);
1571     }
1572 }
1573 
1574 gboolean
gtalk_file_collection_send_data(GTalkFileCollection * self,GabbleFileTransferChannel * channel,const gchar * data,guint length)1575 gtalk_file_collection_send_data (GTalkFileCollection *self,
1576     GabbleFileTransferChannel *channel, const gchar *data, guint length)
1577 {
1578 
1579   ShareChannel *share_channel = g_hash_table_lookup (self->priv->share_channels,
1580       GINT_TO_POINTER (1));
1581   gint ret;
1582 
1583 
1584   g_return_val_if_fail (self->priv->current_channel == channel, FALSE);
1585 
1586   ret = nice_agent_send (share_channel->agent, share_channel->stream_id,
1587       share_channel->component_id, length, data);
1588 
1589   if (ret < 0 || (guint) ret < length)
1590     {
1591       if (ret < 0)
1592         ret = 0;
1593 
1594       share_channel->write_buffer = g_memdup (data + ret,
1595           length - ret);
1596       share_channel->write_len = length - ret;
1597 
1598       gabble_file_transfer_channel_gtalk_file_collection_write_blocked (channel,
1599           TRUE);
1600     }
1601   return TRUE;
1602 }
1603 
1604 void
gtalk_file_collection_block_reading(GTalkFileCollection * self,GabbleFileTransferChannel * channel,gboolean block)1605 gtalk_file_collection_block_reading (GTalkFileCollection *self,
1606     GabbleFileTransferChannel *channel, gboolean block)
1607 {
1608   ShareChannel *share_channel = g_hash_table_lookup (self->priv->share_channels,
1609       GINT_TO_POINTER (1));
1610 
1611   g_assert (channel_exists (self, channel));
1612 
1613   if (self->priv->status != GTALK_FT_STATUS_TRANSFERRING)
1614     DEBUG ("Channel %p %s reading ", channel, block?"blocks":"unblocks" );
1615 
1616   g_hash_table_replace (self->priv->channels_reading, channel,
1617       GINT_TO_POINTER (!block));
1618 
1619   if (channel == self->priv->current_channel)
1620     {
1621       if (block)
1622         {
1623           if (share_channel && share_channel->agent_attached)
1624             {
1625               nice_agent_attach_recv (share_channel->agent,
1626                   share_channel->stream_id, share_channel->component_id,
1627                   NULL, NULL, NULL);
1628               share_channel->agent_attached = FALSE;
1629             }
1630         }
1631       else
1632         {
1633           if (share_channel && !share_channel->agent_attached)
1634             {
1635               share_channel->agent_attached = TRUE;
1636               nice_agent_attach_recv (share_channel->agent,
1637                   share_channel->stream_id, share_channel->component_id,
1638                   g_main_context_default (), nice_data_received_cb, self);
1639             }
1640         }
1641     }
1642 }
1643 
1644 void
gtalk_file_collection_completed(GTalkFileCollection * self,GabbleFileTransferChannel * channel)1645 gtalk_file_collection_completed (GTalkFileCollection *self,
1646     GabbleFileTransferChannel * channel)
1647 {
1648   ShareChannel *share_channel = g_hash_table_lookup (self->priv->share_channels,
1649       GINT_TO_POINTER (1));
1650 
1651   DEBUG ("called");
1652 
1653   g_return_if_fail (self->priv->current_channel == channel);
1654 
1655   /* We shouldn't set the FT to completed until we receive the 'complete' info
1656      or we receive a new HTTP request otherwise we might terminate the session
1657      and cause a race condition where the peer thinks it got canceled before it
1658      completed. */
1659   share_channel->http_status = HTTP_SERVER_IDLE;
1660   self->priv->status = GTALK_FT_STATUS_WAITING;
1661 }
1662 
1663 void
gtalk_file_collection_terminate(GTalkFileCollection * self,GabbleFileTransferChannel * channel)1664 gtalk_file_collection_terminate (GTalkFileCollection *self,
1665     GabbleFileTransferChannel * channel)
1666 {
1667 
1668   DEBUG ("called");
1669 
1670   if (!channel_exists (self, channel))
1671     return;
1672 
1673   if (self->priv->current_channel == channel)
1674     {
1675 
1676       del_channel (self, channel);
1677 
1678       /* Cancel the whole thing if we terminate the current channel */
1679       if (self->priv->status == GTALK_FT_STATUS_TRANSFERRING)
1680         {
1681 
1682           /* The terminate should call our terminated_cb callback which should
1683              terminate all channels which should unref us which will unref the
1684              jingle session */
1685           self->priv->status = GTALK_FT_STATUS_TERMINATED;
1686           wocky_jingle_session_terminate (self->priv->jingle,
1687               WOCKY_JINGLE_REASON_UNKNOWN, NULL, NULL);
1688           return;
1689         }
1690       return;
1691     }
1692   else
1693     {
1694       del_channel (self, channel);
1695 
1696       /* If this was the last channel, it will cause it to unref us and
1697          the dispose will be called, which will call
1698          wocky_jingle_session_terminate */
1699       gabble_file_transfer_channel_gtalk_file_collection_state_changed (channel,
1700           GTALK_FILE_COLLECTION_STATE_TERMINATED, TRUE);
1701     }
1702 }
1703 
1704 
1705 static void
channel_disposed(gpointer data,GObject * object)1706 channel_disposed (gpointer data, GObject *object)
1707 {
1708   GTalkFileCollection *self = data;
1709   GabbleFileTransferChannel *channel = (GabbleFileTransferChannel *) object;
1710 
1711   DEBUG ("channel %p got destroyed", channel);
1712 
1713   g_return_if_fail (channel_exists (self, channel));
1714 
1715   if (self->priv->current_channel == channel)
1716     {
1717       del_channel (self, channel);
1718 
1719       /* Cancel the whole thing if we terminate the current channel */
1720       if (self->priv->status == GTALK_FT_STATUS_TRANSFERRING)
1721         {
1722 
1723           /* The terminate should call our terminated_cb callback which should
1724              terminate all channels which should unref us which will unref the
1725              jingle session */
1726           self->priv->status = GTALK_FT_STATUS_TERMINATED;
1727           wocky_jingle_session_terminate (self->priv->jingle,
1728               WOCKY_JINGLE_REASON_UNKNOWN, NULL, NULL);
1729           return;
1730         }
1731     }
1732   else
1733     {
1734       del_channel (self, channel);
1735     }
1736 }
1737