source: protocols/purple/ft.c @ b1b9453

Last change on this file since b1b9453 was 30d598c, checked in by dequis <dx@…>, at 2017-01-29T22:40:09Z

purple: Fix crash on ft requests from unknown contacts

Followup to 701ab81 (included in 3.5) which was a partial fix which only
improved things for non-libpurple file transfers (that is, just jabber)

  • Property mode set to 100644
File size: 10.8 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  libpurple module - File transfer stuff                                   *
5*                                                                           *
6*  Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net>              *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24/* Do file transfers via disk for now, since libpurple was really designed
25   for straight-to/from disk fts and is only just learning how to pass the
26   file contents the the UI instead (2.6.0 and higher it seems, and with
27   varying levels of success). */
28
29#include "bitlbee.h"
30#include "bpurple.h"
31
32#include <stdarg.h>
33
34#include <glib.h>
35#include <purple.h>
36
37struct prpl_xfer_data {
38        PurpleXfer *xfer;
39        file_transfer_t *ft;
40        struct im_connection *ic;
41        int fd;
42        char *fn, *handle;
43        gboolean ui_wants_data;
44        int timeout;
45};
46
47static file_transfer_t *next_ft;
48
49struct im_connection *purple_ic_by_pa(PurpleAccount *pa);
50static gboolean prplcb_xfer_new_send_cb(gpointer data, gint fd, b_input_condition cond);
51static gboolean prpl_xfer_write_request(struct file_transfer *ft);
52
53
54/* Receiving files (IM->UI): */
55static void prpl_xfer_accept(struct file_transfer *ft)
56{
57        struct prpl_xfer_data *px = ft->data;
58
59        purple_xfer_request_accepted(px->xfer, NULL);
60        prpl_xfer_write_request(ft);
61}
62
63static void prpl_xfer_canceled(struct file_transfer *ft, char *reason)
64{
65        struct prpl_xfer_data *px = ft->data;
66
67        if (px->xfer) {
68                if (!purple_xfer_is_completed(px->xfer) && !purple_xfer_is_canceled(px->xfer)) {
69                        purple_xfer_cancel_local(px->xfer);
70                }
71                px->xfer->ui_data = NULL;
72                purple_xfer_unref(px->xfer);
73                px->xfer = NULL;
74        }
75}
76
77static void prpl_xfer_free(struct file_transfer *ft)
78{
79        struct prpl_xfer_data *px = ft->data;
80        struct purple_data *pd = px->ic->proto_data;
81
82        pd->filetransfers = g_slist_remove(pd->filetransfers, px);
83
84        if (px->xfer) {
85                px->xfer->ui_data = NULL;
86                purple_xfer_unref(px->xfer);
87        }
88
89        if (px->timeout) {
90                b_event_remove(px->timeout);
91        }
92
93        g_free(px->fn);
94        g_free(px->handle);
95        if (px->fd >= 0) {
96                close(px->fd);
97        }
98        g_free(px);
99}
100
101static void prplcb_xfer_new(PurpleXfer *xfer)
102{
103        purple_xfer_ref(xfer);
104
105        if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) {
106                struct prpl_xfer_data *px = g_new0(struct prpl_xfer_data, 1);
107                struct purple_data *pd;
108
109                xfer->ui_data = px;
110                px->xfer = xfer;
111                px->fn = mktemp(g_strdup("/tmp/bitlbee-purple-ft.XXXXXX"));
112                px->fd = -1;
113                px->ic = purple_ic_by_pa(xfer->account);
114
115                pd = px->ic->proto_data;
116                pd->filetransfers = g_slist_prepend(pd->filetransfers, px);
117
118                purple_xfer_set_local_filename(xfer, px->fn);
119
120                /* Sadly the xfer struct is still empty ATM so come back after
121                   the caller is done. */
122                b_timeout_add(0, prplcb_xfer_new_send_cb, xfer);
123        } else {
124                struct file_transfer *ft = next_ft;
125                struct prpl_xfer_data *px = ft->data;
126
127                xfer->ui_data = px;
128                px->xfer = xfer;
129
130                next_ft = NULL;
131        }
132}
133
134static gboolean prplcb_xfer_new_send_cb(gpointer data, gint fd, b_input_condition cond)
135{
136        PurpleXfer *xfer = data;
137        struct im_connection *ic = purple_ic_by_pa(xfer->account);
138        struct prpl_xfer_data *px = xfer->ui_data;
139        PurpleBuddy *buddy;
140        const char *who;
141
142        buddy = purple_find_buddy(xfer->account, xfer->who);
143        who = buddy ? purple_buddy_get_name(buddy) : xfer->who;
144
145        /* TODO(wilmer): After spreading some more const goodness in BitlBee,
146           remove the evil cast below. */
147        px->ft = imcb_file_send_start(ic, (char *) who, xfer->filename, xfer->size);
148
149        if (!px->ft) {
150                return FALSE;
151        }
152        px->ft->data = px;
153
154        px->ft->accept = prpl_xfer_accept;
155        px->ft->canceled = prpl_xfer_canceled;
156        px->ft->free = prpl_xfer_free;
157        px->ft->write_request = prpl_xfer_write_request;
158
159        return FALSE;
160}
161
162gboolean try_write_to_ui(gpointer data, gint fd, b_input_condition cond)
163{
164        struct file_transfer *ft = data;
165        struct prpl_xfer_data *px = ft->data;
166        struct stat fs;
167        off_t tx_bytes;
168
169        /* If we don't have the file opened yet, there's no data so wait. */
170        if (px->fd < 0 || !px->ui_wants_data) {
171                return FALSE;
172        }
173
174        tx_bytes = lseek(px->fd, 0, SEEK_CUR);
175        fstat(px->fd, &fs);
176
177        if (fs.st_size > tx_bytes) {
178                char buf[1024];
179                size_t n = MIN(fs.st_size - tx_bytes, sizeof(buf));
180
181                if (read(px->fd, buf, n) == n && ft->write(ft, buf, n)) {
182                        px->ui_wants_data = FALSE;
183                } else {
184                        purple_xfer_cancel_local(px->xfer);
185                        imcb_file_canceled(px->ic, ft, "Read error");
186                }
187        }
188
189        if (lseek(px->fd, 0, SEEK_CUR) == px->xfer->size) {
190                /*purple_xfer_end( px->xfer );*/
191                imcb_file_finished(px->ic, ft);
192        }
193
194        return FALSE;
195}
196
197/* UI calls this when its buffer is empty and wants more data to send to the user. */
198static gboolean prpl_xfer_write_request(struct file_transfer *ft)
199{
200        struct prpl_xfer_data *px = ft->data;
201
202        px->ui_wants_data = TRUE;
203        try_write_to_ui(ft, 0, 0);
204
205        return FALSE;
206}
207
208
209static void prplcb_xfer_destroy(PurpleXfer *xfer)
210{
211        struct prpl_xfer_data *px = xfer->ui_data;
212
213        if (px) {
214                px->xfer = NULL;
215        }
216}
217
218static void prplcb_xfer_progress(PurpleXfer *xfer, double percent)
219{
220        struct prpl_xfer_data *px = xfer->ui_data;
221
222        if (px == NULL) {
223                return;
224        }
225
226        if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
227                if (*px->fn) {
228                        char *slash;
229
230                        unlink(px->fn);
231                        if ((slash = strrchr(px->fn, '/'))) {
232                                *slash = '\0';
233                                rmdir(px->fn);
234                        }
235                        *px->fn = '\0';
236                }
237
238                return;
239        }
240
241        if (px->fd == -1 && percent > 0) {
242                /* Weeeeeeeee, we're getting data! That means the file exists
243                   by now so open it and start sending to the UI. */
244                px->fd = open(px->fn, O_RDONLY);
245
246                /* Unlink it now, because we don't need it after this. */
247                unlink(px->fn);
248        }
249
250        if (percent < 1) {
251                try_write_to_ui(px->ft, 0, 0);
252        } else {
253                /* Another nice problem: If we have the whole file, it only
254                   gets closed when we return. Problem: There may still be
255                   stuff buffered and not written, we'll only see it after
256                   the caller close()s the file. So poll the file after that. */
257                b_timeout_add(0, try_write_to_ui, px->ft);
258        }
259}
260
261static void prplcb_xfer_cancel_remote(PurpleXfer *xfer)
262{
263        struct prpl_xfer_data *px = xfer->ui_data;
264
265        if (px && px->ft) {
266                imcb_file_canceled(px->ic, px->ft, "Canceled by remote end");
267        } else if (px) {
268                /* px->ft == NULL for sends, because of the two stages. :-/ */
269                imcb_error(px->ic, "File transfer cancelled by remote end");
270        }
271}
272
273
274/* Sending files (UI->IM): */
275static gboolean prpl_xfer_write(struct file_transfer *ft, char *buffer, unsigned int len);
276static gboolean purple_transfer_request_cb(gpointer data, gint fd, b_input_condition cond);
277
278void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle)
279{
280        struct prpl_xfer_data *px = g_new0(struct prpl_xfer_data, 1);
281        struct purple_data *pd;
282        char *dir, *basename;
283
284        ft->data = px;
285        px->ft = ft;
286        px->ft->free = prpl_xfer_free;
287
288        dir = g_strdup("/tmp/bitlbee-purple-ft.XXXXXX");
289        if (!mkdtemp(dir)) {
290                imcb_error(ic, "Could not create temporary file for file transfer");
291                g_free(px);
292                g_free(dir);
293                return;
294        }
295
296        if ((basename = strrchr(ft->file_name, '/'))) {
297                basename++;
298        } else {
299                basename = ft->file_name;
300        }
301        px->fn = g_strdup_printf("%s/%s", dir, basename);
302        px->fd = open(px->fn, O_WRONLY | O_CREAT, 0600);
303        g_free(dir);
304
305        if (px->fd < 0) {
306                imcb_error(ic, "Could not create temporary file for file transfer");
307                g_free(px);
308                g_free(px->fn);
309                return;
310        }
311
312        px->ic = ic;
313        px->handle = g_strdup(handle);
314
315        pd = px->ic->proto_data;
316        pd->filetransfers = g_slist_prepend(pd->filetransfers, px);
317
318        imcb_log(ic,
319                 "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait...");
320
321        px->timeout = b_timeout_add(0, purple_transfer_request_cb, ft);
322}
323
324static void purple_transfer_forward(struct file_transfer *ft)
325{
326        struct prpl_xfer_data *px = ft->data;
327        struct purple_data *pd = px->ic->proto_data;
328
329        /* xfer_new() will pick up this variable. It's a hack but we're not
330           multi-threaded anyway. */
331        next_ft = ft;
332        serv_send_file(purple_account_get_connection(pd->account),
333                   px->handle, px->fn);
334}
335
336static gboolean purple_transfer_request_cb(gpointer data, gint fd, b_input_condition cond)
337{
338        file_transfer_t *ft = data;
339        struct prpl_xfer_data *px = ft->data;
340
341        px->timeout = 0;
342
343        if (ft->write == NULL) {
344                ft->write = prpl_xfer_write;
345                imcb_file_recv_start(px->ic, ft);
346        }
347
348        ft->write_request(ft);
349
350        return FALSE;
351}
352
353static gboolean prpl_xfer_write(struct file_transfer *ft, char *buffer, unsigned int len)
354{
355        struct prpl_xfer_data *px = ft->data;
356
357        if (write(px->fd, buffer, len) != len) {
358                imcb_file_canceled(px->ic, ft, "Error while writing temporary file");
359                return FALSE;
360        }
361
362        if (lseek(px->fd, 0, SEEK_CUR) >= ft->file_size) {
363                close(px->fd);
364                px->fd = -1;
365
366                purple_transfer_forward(ft);
367                imcb_file_finished(px->ic, ft);
368                px->ft = NULL;
369        } else {
370                px->timeout = b_timeout_add(0, purple_transfer_request_cb, ft);
371        }
372
373        return TRUE;
374}
375
376void purple_transfer_cancel_all(struct im_connection *ic)
377{
378        struct purple_data *pd = ic->proto_data;
379
380        while (pd->filetransfers) {
381                struct prpl_xfer_data *px = pd->filetransfers->data;
382
383                if (px->ft) {
384                        imcb_file_canceled(ic, px->ft, "Logging out");
385                }
386
387                pd->filetransfers = g_slist_remove(pd->filetransfers, px);
388        }
389}
390
391
392
393PurpleXferUiOps bee_xfer_uiops =
394{
395        prplcb_xfer_new,           /* new_xfer */
396        prplcb_xfer_destroy,       /* destroy */
397        NULL,                      /* add_xfer */
398        prplcb_xfer_progress,      /* update_progress */
399        NULL,                      /* cancel_local */
400        prplcb_xfer_cancel_remote, /* cancel_remote */
401        NULL,                      /* ui_write */
402        NULL,                      /* ui_read */
403        NULL,                      /* data_not_sent */
404};
Note: See TracBrowser for help on using the repository browser.