Coverage Report

Created: 2026-04-01 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/ft.c
Line
Count
Source
1
/**
2
 * @file ft.c File Transfer API
3
 */
4
5
/* purple
6
 *
7
 * Purple is the legal property of its developers, whose names are too numerous
8
 * to list here.  Please refer to the COPYRIGHT file distributed with this
9
 * source distribution.
10
 *
11
 * This program is free software; you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by
13
 * the Free Software Foundation; either version 2 of the License, or
14
 * (at your option) any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 * along with this program; if not, write to the Free Software
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
24
 *
25
 */
26
#include "internal.h"
27
#include "dbus-maybe.h"
28
#include "ft.h"
29
#include "glibcompat.h"
30
#include "network.h"
31
#include "notify.h"
32
#include "prefs.h"
33
#include "proxy.h"
34
#include "request.h"
35
#include "util.h"
36
#include "debug.h"
37
38
0
#define FT_INITIAL_BUFFER_SIZE 4096
39
#define FT_MAX_BUFFER_SIZE     65535
40
41
static PurpleXferUiOps *xfer_ui_ops = NULL;
42
static GList *xfers;
43
44
/*
45
 * A hack to store more data since we can't extend the size of PurpleXfer
46
 * easily.
47
 */
48
static GHashTable *xfers_data = NULL;
49
50
typedef struct _PurpleXferPrivData {
51
  /*
52
   * Used to moderate the file transfer when either the read/write ui_ops are
53
   * set or fd is not set. In those cases, the UI/prpl call the respective
54
   * function, which is somewhat akin to a fd watch being triggered.
55
   */
56
  enum {
57
    PURPLE_XFER_READY_NONE = 0x0,
58
    PURPLE_XFER_READY_UI   = 0x1,
59
    PURPLE_XFER_READY_PRPL = 0x2,
60
  } ready;
61
62
  /* TODO: Should really use a PurpleCircBuffer for this. */
63
  GByteArray *buffer;
64
65
  gpointer thumbnail_data;    /**< thumbnail image */
66
  gsize thumbnail_size;
67
  gchar *thumbnail_mimetype;
68
} PurpleXferPrivData;
69
70
static int purple_xfer_choose_file(PurpleXfer *xfer);
71
72
static void
73
purple_xfer_priv_data_destroy(gpointer data)
74
0
{
75
0
  PurpleXferPrivData *priv = data;
76
77
0
  if (priv->buffer)
78
0
    g_byte_array_free(priv->buffer, TRUE);
79
80
0
  g_free(priv->thumbnail_data);
81
82
0
  g_free(priv->thumbnail_mimetype);
83
84
0
  g_free(priv);
85
0
}
86
87
static const gchar *
88
purple_xfer_status_type_to_string(PurpleXferStatusType type)
89
0
{
90
0
  static const struct {
91
0
    PurpleXferStatusType type;
92
0
    const char *name;
93
0
  } type_names[] = {
94
0
    { PURPLE_XFER_STATUS_UNKNOWN, "unknown" },
95
0
    { PURPLE_XFER_STATUS_NOT_STARTED, "not started" },
96
0
    { PURPLE_XFER_STATUS_ACCEPTED, "accepted" },
97
0
    { PURPLE_XFER_STATUS_STARTED, "started" },
98
0
    { PURPLE_XFER_STATUS_DONE, "done" },
99
0
    { PURPLE_XFER_STATUS_CANCEL_LOCAL, "cancelled locally" },
100
0
    { PURPLE_XFER_STATUS_CANCEL_REMOTE, "cancelled remotely" }
101
0
  };
102
0
  gsize i;
103
104
0
  for (i = 0; i < G_N_ELEMENTS(type_names); ++i)
105
0
    if (type_names[i].type == type)
106
0
      return type_names[i].name;
107
108
0
  return "invalid state";
109
0
}
110
111
GList *
112
purple_xfers_get_all()
113
0
{
114
0
  return xfers;
115
0
}
116
117
PurpleXfer *
118
purple_xfer_new(PurpleAccount *account, PurpleXferType type, const char *who)
119
0
{
120
0
  PurpleXfer *xfer;
121
0
  PurpleXferUiOps *ui_ops;
122
0
  PurpleXferPrivData *priv;
123
124
0
  g_return_val_if_fail(type    != PURPLE_XFER_UNKNOWN, NULL);
125
0
  g_return_val_if_fail(account != NULL,              NULL);
126
0
  g_return_val_if_fail(who     != NULL,              NULL);
127
128
0
  xfer = g_new0(PurpleXfer, 1);
129
0
  PURPLE_DBUS_REGISTER_POINTER(xfer, PurpleXfer);
130
131
0
  xfer->ref = 1;
132
0
  xfer->type    = type;
133
0
  xfer->account = account;
134
0
  xfer->who     = g_strdup(who);
135
0
  xfer->ui_ops  = ui_ops = purple_xfers_get_ui_ops();
136
0
  xfer->message = NULL;
137
0
  xfer->current_buffer_size = FT_INITIAL_BUFFER_SIZE;
138
0
  xfer->fd = -1;
139
140
0
  priv = g_new0(PurpleXferPrivData, 1);
141
0
  priv->ready = PURPLE_XFER_READY_NONE;
142
143
0
  if (ui_ops && ui_ops->data_not_sent) {
144
    /* If the ui will handle unsent data no need for buffer */
145
0
    priv->buffer = NULL;
146
0
  } else {
147
0
    priv->buffer = g_byte_array_sized_new(FT_INITIAL_BUFFER_SIZE);
148
0
  }
149
150
0
  g_hash_table_insert(xfers_data, xfer, priv);
151
152
0
  ui_ops = purple_xfer_get_ui_ops(xfer);
153
154
0
  if (ui_ops != NULL && ui_ops->new_xfer != NULL)
155
0
    ui_ops->new_xfer(xfer);
156
157
0
  xfers = g_list_prepend(xfers, xfer);
158
159
0
  if (purple_debug_is_verbose())
160
0
    purple_debug_info("xfer", "new %p [%d]\n", xfer, xfer->ref);
161
162
0
  return xfer;
163
0
}
164
165
static void
166
purple_xfer_destroy(PurpleXfer *xfer)
167
0
{
168
0
  PurpleXferUiOps *ui_ops;
169
170
0
  g_return_if_fail(xfer != NULL);
171
172
0
  if (purple_debug_is_verbose())
173
0
    purple_debug_info("xfer", "destroyed %p [%d]\n", xfer, xfer->ref);
174
175
  /* Close the file browser, if it's open */
176
0
  purple_request_close_with_handle(xfer);
177
178
0
  if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_STARTED)
179
0
    purple_xfer_cancel_local(xfer);
180
181
0
  ui_ops = purple_xfer_get_ui_ops(xfer);
182
183
0
  if (ui_ops != NULL && ui_ops->destroy != NULL)
184
0
    ui_ops->destroy(xfer);
185
186
0
  g_free(xfer->who);
187
0
  g_free(xfer->filename);
188
0
  g_free(xfer->remote_ip);
189
0
  g_free(xfer->local_filename);
190
191
0
  g_hash_table_remove(xfers_data, xfer);
192
193
0
  PURPLE_DBUS_UNREGISTER_POINTER(xfer);
194
0
  xfers = g_list_remove(xfers, xfer);
195
0
  g_free(xfer);
196
0
}
197
198
void
199
purple_xfer_ref(PurpleXfer *xfer)
200
0
{
201
0
  g_return_if_fail(xfer != NULL);
202
203
0
  xfer->ref++;
204
205
0
  if (purple_debug_is_verbose())
206
0
    purple_debug_info("xfer", "ref'd %p [%d]\n", xfer, xfer->ref);
207
0
}
208
209
void
210
purple_xfer_unref(PurpleXfer *xfer)
211
0
{
212
0
  g_return_if_fail(xfer != NULL);
213
0
  g_return_if_fail(xfer->ref > 0);
214
215
0
  xfer->ref--;
216
217
0
  if (purple_debug_is_verbose())
218
0
    purple_debug_info("xfer", "unref'd %p [%d]\n", xfer, xfer->ref);
219
220
0
  if (xfer->ref == 0)
221
0
    purple_xfer_destroy(xfer);
222
0
}
223
224
static void
225
purple_xfer_set_status(PurpleXfer *xfer, PurpleXferStatusType status)
226
0
{
227
0
  g_return_if_fail(xfer != NULL);
228
229
0
  if (purple_debug_is_verbose())
230
0
    purple_debug_info("xfer", "Changing status of xfer %p from %s to %s\n",
231
0
        xfer, purple_xfer_status_type_to_string(xfer->status),
232
0
        purple_xfer_status_type_to_string(status));
233
234
0
  if (xfer->status == status)
235
0
    return;
236
237
0
  xfer->status = status;
238
239
0
  if(xfer->type == PURPLE_XFER_SEND) {
240
0
    switch(status) {
241
0
      case PURPLE_XFER_STATUS_ACCEPTED:
242
0
        purple_signal_emit(purple_xfers_get_handle(), "file-send-accept", xfer);
243
0
        break;
244
0
      case PURPLE_XFER_STATUS_STARTED:
245
0
        purple_signal_emit(purple_xfers_get_handle(), "file-send-start", xfer);
246
0
        break;
247
0
      case PURPLE_XFER_STATUS_DONE:
248
0
        purple_signal_emit(purple_xfers_get_handle(), "file-send-complete", xfer);
249
0
        break;
250
0
      case PURPLE_XFER_STATUS_CANCEL_LOCAL:
251
0
      case PURPLE_XFER_STATUS_CANCEL_REMOTE:
252
0
        purple_signal_emit(purple_xfers_get_handle(), "file-send-cancel", xfer);
253
0
        break;
254
0
      default:
255
0
        break;
256
0
    }
257
0
  } else if(xfer->type == PURPLE_XFER_RECEIVE) {
258
0
    switch(status) {
259
0
      case PURPLE_XFER_STATUS_ACCEPTED:
260
0
        purple_signal_emit(purple_xfers_get_handle(), "file-recv-accept", xfer);
261
0
        break;
262
0
      case PURPLE_XFER_STATUS_STARTED:
263
0
        purple_signal_emit(purple_xfers_get_handle(), "file-recv-start", xfer);
264
0
        break;
265
0
      case PURPLE_XFER_STATUS_DONE:
266
0
        purple_signal_emit(purple_xfers_get_handle(), "file-recv-complete", xfer);
267
0
        break;
268
0
      case PURPLE_XFER_STATUS_CANCEL_LOCAL:
269
0
      case PURPLE_XFER_STATUS_CANCEL_REMOTE:
270
0
        purple_signal_emit(purple_xfers_get_handle(), "file-recv-cancel", xfer);
271
0
        break;
272
0
      default:
273
0
        break;
274
0
    }
275
0
  }
276
0
}
277
278
static void
279
purple_xfer_conversation_write_internal(PurpleXfer *xfer,
280
  const char *message, gboolean is_error, gboolean print_thumbnail)
281
0
{
282
0
  PurpleConversation *conv = NULL;
283
0
  PurpleMessageFlags flags = PURPLE_MESSAGE_SYSTEM;
284
0
  char *escaped;
285
0
  gconstpointer thumbnail_data;
286
0
  gsize size;
287
288
0
  g_return_if_fail(xfer != NULL);
289
0
  g_return_if_fail(message != NULL);
290
291
0
  thumbnail_data = purple_xfer_get_thumbnail(xfer, &size);
292
293
0
  conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, xfer->who,
294
0
                         purple_xfer_get_account(xfer));
295
296
0
  if (conv == NULL)
297
0
    return;
298
299
0
  escaped = g_markup_escape_text(message, -1);
300
301
0
  if (is_error)
302
0
    flags |= PURPLE_MESSAGE_ERROR;
303
304
0
  if (print_thumbnail && thumbnail_data) {
305
0
    gchar *message_with_img;
306
0
    gpointer data = g_memdup2(thumbnail_data, size);
307
0
    int id = purple_imgstore_add_with_id(data, size, NULL);
308
309
0
    message_with_img =
310
0
      g_strdup_printf("<img id='%d'> %s", id, escaped);
311
0
    purple_conversation_write(conv, NULL, message_with_img, flags,
312
0
      time(NULL));
313
0
    purple_imgstore_unref_by_id(id);
314
0
    g_free(message_with_img);
315
0
  } else {
316
0
    purple_conversation_write(conv, NULL, escaped, flags, time(NULL));
317
0
  }
318
0
  g_free(escaped);
319
0
}
320
321
void
322
purple_xfer_conversation_write(PurpleXfer *xfer, gchar *message,
323
  gboolean is_error)
324
0
{
325
0
  purple_xfer_conversation_write_internal(xfer, message, is_error, FALSE);
326
0
}
327
328
/* maybe this one should be exported publically? */
329
static void
330
purple_xfer_conversation_write_with_thumbnail(PurpleXfer *xfer,
331
  const gchar *message)
332
0
{
333
0
  purple_xfer_conversation_write_internal(xfer, message, FALSE, TRUE);
334
0
}
335
336
337
static void purple_xfer_show_file_error(PurpleXfer *xfer, const char *filename)
338
0
{
339
0
  int err = errno;
340
0
  gchar *msg = NULL, *utf8;
341
0
  PurpleXferType xfer_type = purple_xfer_get_type(xfer);
342
0
  PurpleAccount *account = purple_xfer_get_account(xfer);
343
344
0
  utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
345
0
  switch(xfer_type) {
346
0
    case PURPLE_XFER_SEND:
347
0
      msg = g_strdup_printf(_("Error reading %s: \n%s.\n"),
348
0
                  utf8, g_strerror(err));
349
0
      break;
350
0
    case PURPLE_XFER_RECEIVE:
351
0
      msg = g_strdup_printf(_("Error writing %s: \n%s.\n"),
352
0
                  utf8, g_strerror(err));
353
0
      break;
354
0
    default:
355
0
      msg = g_strdup_printf(_("Error accessing %s: \n%s.\n"),
356
0
                  utf8, g_strerror(err));
357
0
      break;
358
0
  }
359
0
  g_free(utf8);
360
361
0
  purple_xfer_conversation_write(xfer, msg, TRUE);
362
0
  purple_xfer_error(xfer_type, account, xfer->who, msg);
363
0
  g_free(msg);
364
0
}
365
366
static void
367
purple_xfer_choose_file_ok_cb(void *user_data, const char *filename)
368
0
{
369
0
  PurpleXfer *xfer;
370
0
  PurpleXferType type;
371
0
  struct stat st;
372
0
  gchar *dir;
373
374
0
  xfer = (PurpleXfer *)user_data;
375
0
  type = purple_xfer_get_type(xfer);
376
377
0
  if (g_stat(filename, &st) != 0) {
378
    /* File not found. */
379
0
    if (type == PURPLE_XFER_RECEIVE) {
380
0
#ifndef _WIN32
381
0
      int mode = W_OK;
382
#else
383
      int mode = F_OK;
384
#endif
385
0
      dir = g_path_get_dirname(filename);
386
387
0
      if (g_access(dir, mode) == 0) {
388
0
        purple_xfer_request_accepted(xfer, filename);
389
0
      } else {
390
0
        purple_xfer_ref(xfer);
391
0
        purple_notify_message(
392
0
          NULL, PURPLE_NOTIFY_MSG_ERROR, NULL,
393
0
          _("Directory is not writable."), NULL,
394
0
          (PurpleNotifyCloseCallback)purple_xfer_choose_file, xfer);
395
0
      }
396
397
0
      g_free(dir);
398
0
    }
399
0
    else {
400
0
      purple_xfer_show_file_error(xfer, filename);
401
0
      purple_xfer_cancel_local(xfer);
402
0
    }
403
0
  }
404
0
  else if ((type == PURPLE_XFER_SEND) && (st.st_size == 0)) {
405
406
0
    purple_notify_error(NULL, NULL,
407
0
              _("Cannot send a file of 0 bytes."), NULL);
408
409
0
    purple_xfer_cancel_local(xfer);
410
0
  }
411
0
  else if ((type == PURPLE_XFER_SEND) && S_ISDIR(st.st_mode)) {
412
    /*
413
     * XXX - Sending a directory should be valid for some protocols.
414
     */
415
0
    purple_notify_error(NULL, NULL,
416
0
              _("Cannot send a directory."), NULL);
417
418
0
    purple_xfer_cancel_local(xfer);
419
0
  }
420
0
  else if ((type == PURPLE_XFER_RECEIVE) && S_ISDIR(st.st_mode)) {
421
0
    char *msg, *utf8;
422
0
    utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
423
0
    msg = g_strdup_printf(
424
0
          _("%s is not a regular file. Cowardly refusing to overwrite it.\n"), utf8);
425
0
    g_free(utf8);
426
0
    purple_notify_error(NULL, NULL, msg, NULL);
427
0
    g_free(msg);
428
0
    purple_xfer_request_denied(xfer);
429
0
  }
430
0
  else if (type == PURPLE_XFER_SEND) {
431
0
#ifndef _WIN32
432
0
    int mode = R_OK;
433
#else
434
    int mode = F_OK;
435
#endif
436
437
0
    if (g_access(filename, mode) == 0) {
438
0
      purple_xfer_request_accepted(xfer, filename);
439
0
    } else {
440
0
      purple_xfer_ref(xfer);
441
0
      purple_notify_message(
442
0
        NULL, PURPLE_NOTIFY_MSG_ERROR, NULL,
443
0
        _("File is not readable."), NULL,
444
0
        (PurpleNotifyCloseCallback)purple_xfer_choose_file, xfer);
445
0
    }
446
0
  }
447
0
  else {
448
0
    purple_xfer_request_accepted(xfer, filename);
449
0
  }
450
451
0
  purple_xfer_unref(xfer);
452
0
}
453
454
static void
455
purple_xfer_choose_file_cancel_cb(void *user_data, const char *filename)
456
0
{
457
0
  PurpleXfer *xfer = (PurpleXfer *)user_data;
458
459
0
  purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL);
460
0
  if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)
461
0
    purple_xfer_cancel_local(xfer);
462
0
  else
463
0
    purple_xfer_request_denied(xfer);
464
0
  purple_xfer_unref(xfer);
465
0
}
466
467
static int
468
purple_xfer_choose_file(PurpleXfer *xfer)
469
0
{
470
0
  purple_request_file(xfer, NULL, purple_xfer_get_filename(xfer),
471
0
            (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE),
472
0
            G_CALLBACK(purple_xfer_choose_file_ok_cb),
473
0
            G_CALLBACK(purple_xfer_choose_file_cancel_cb),
474
0
            purple_xfer_get_account(xfer), xfer->who, NULL,
475
0
            xfer);
476
477
0
  return 0;
478
0
}
479
480
static int
481
cancel_recv_cb(PurpleXfer *xfer)
482
0
{
483
0
  purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL);
484
0
  purple_xfer_request_denied(xfer);
485
0
  purple_xfer_unref(xfer);
486
487
0
  return 0;
488
0
}
489
490
static void
491
purple_xfer_ask_recv(PurpleXfer *xfer)
492
0
{
493
0
  char *buf, *size_buf;
494
0
  size_t size;
495
0
  gconstpointer thumb;
496
0
  gsize thumb_size;
497
498
  /* If we have already accepted the request, ask the destination file
499
     name directly */
500
0
  if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_ACCEPTED) {
501
0
    PurpleBuddy *buddy = purple_find_buddy(xfer->account, xfer->who);
502
503
0
    if (purple_xfer_get_filename(xfer) != NULL)
504
0
    {
505
0
      size = purple_xfer_get_size(xfer);
506
0
      size_buf = purple_str_size_to_units(size);
507
0
      buf = g_strdup_printf(_("%s wants to send you %s (%s)"),
508
0
              buddy ? purple_buddy_get_alias(buddy) : xfer->who,
509
0
              purple_xfer_get_filename(xfer), size_buf);
510
0
      g_free(size_buf);
511
0
    }
512
0
    else
513
0
    {
514
0
      buf = g_strdup_printf(_("%s wants to send you a file"),
515
0
            buddy ? purple_buddy_get_alias(buddy) : xfer->who);
516
0
    }
517
518
0
    if (xfer->message != NULL)
519
0
      serv_got_im(purple_account_get_connection(xfer->account),
520
0
                 xfer->who, xfer->message, 0, time(NULL));
521
522
0
    if ((thumb = purple_xfer_get_thumbnail(xfer, &thumb_size))) {
523
0
      purple_request_accept_cancel_with_icon(xfer, NULL, buf, NULL,
524
0
        PURPLE_DEFAULT_ACTION_NONE, xfer->account, xfer->who, NULL,
525
0
        thumb, thumb_size, xfer,
526
0
        G_CALLBACK(purple_xfer_choose_file),
527
0
        G_CALLBACK(cancel_recv_cb));
528
0
    } else {
529
0
      purple_request_accept_cancel(xfer, NULL, buf, NULL,
530
0
        PURPLE_DEFAULT_ACTION_NONE, xfer->account, xfer->who, NULL,
531
0
        xfer, G_CALLBACK(purple_xfer_choose_file),
532
0
        G_CALLBACK(cancel_recv_cb));
533
0
    }
534
535
0
    g_free(buf);
536
0
  } else
537
0
    purple_xfer_choose_file(xfer);
538
0
}
539
540
static int
541
ask_accept_ok(PurpleXfer *xfer)
542
0
{
543
0
  purple_xfer_request_accepted(xfer, NULL);
544
545
0
  return 0;
546
0
}
547
548
static int
549
ask_accept_cancel(PurpleXfer *xfer)
550
0
{
551
0
  purple_xfer_request_denied(xfer);
552
0
  purple_xfer_unref(xfer);
553
554
0
  return 0;
555
0
}
556
557
static void
558
purple_xfer_ask_accept(PurpleXfer *xfer)
559
0
{
560
0
  char *buf, *buf2 = NULL;
561
0
  PurpleBuddy *buddy = purple_find_buddy(xfer->account, xfer->who);
562
563
0
  buf = g_strdup_printf(_("Accept file transfer request from %s?"),
564
0
          buddy ? purple_buddy_get_alias(buddy) : xfer->who);
565
0
  if (purple_xfer_get_remote_ip(xfer) &&
566
0
    purple_xfer_get_remote_port(xfer))
567
0
    buf2 = g_strdup_printf(_("A file is available for download from:\n"
568
0
           "Remote host: %s\nRemote port: %d"),
569
0
             purple_xfer_get_remote_ip(xfer),
570
0
             purple_xfer_get_remote_port(xfer));
571
0
  purple_request_accept_cancel(xfer, NULL, buf, buf2,
572
0
                 PURPLE_DEFAULT_ACTION_NONE,
573
0
                 xfer->account, xfer->who, NULL,
574
0
                 xfer,
575
0
                 G_CALLBACK(ask_accept_ok),
576
0
                 G_CALLBACK(ask_accept_cancel));
577
0
  g_free(buf);
578
0
  g_free(buf2);
579
0
}
580
581
void
582
purple_xfer_request(PurpleXfer *xfer)
583
0
{
584
0
  g_return_if_fail(xfer != NULL);
585
0
  g_return_if_fail(xfer->ops.init != NULL);
586
587
0
  purple_xfer_ref(xfer);
588
589
0
  if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE)
590
0
  {
591
0
    purple_signal_emit(purple_xfers_get_handle(), "file-recv-request", xfer);
592
0
    if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL)
593
0
    {
594
      /* The file-transfer was cancelled by a plugin */
595
0
      purple_xfer_cancel_local(xfer);
596
0
    }
597
0
    else if (purple_xfer_get_filename(xfer) ||
598
0
               purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_ACCEPTED)
599
0
    {
600
0
      gchar* message = NULL;
601
0
      PurpleBuddy *buddy = purple_find_buddy(xfer->account, xfer->who);
602
603
0
      message = g_strdup_printf(_("%s is offering to send file %s"),
604
0
        buddy ? purple_buddy_get_alias(buddy) : xfer->who, purple_xfer_get_filename(xfer));
605
0
      purple_xfer_conversation_write_with_thumbnail(xfer, message);
606
0
      g_free(message);
607
608
      /* Ask for a filename to save to if it's not already given by a plugin */
609
0
      if (xfer->local_filename == NULL)
610
0
        purple_xfer_ask_recv(xfer);
611
0
    }
612
0
    else
613
0
    {
614
0
      purple_xfer_ask_accept(xfer);
615
0
    }
616
0
  }
617
0
  else
618
0
  {
619
0
    purple_xfer_choose_file(xfer);
620
0
  }
621
0
}
622
623
void
624
purple_xfer_request_accepted(PurpleXfer *xfer, const char *filename)
625
0
{
626
0
  PurpleXferType type;
627
0
  struct stat st;
628
0
  char *msg, *utf8, *base;
629
0
  PurpleAccount *account;
630
0
  PurpleBuddy *buddy;
631
632
0
  if (xfer == NULL)
633
0
    return;
634
635
0
  type = purple_xfer_get_type(xfer);
636
0
  account = purple_xfer_get_account(xfer);
637
638
0
  purple_debug_misc("xfer", "request accepted for %p\n", xfer);
639
640
0
  if (!filename && type == PURPLE_XFER_RECEIVE) {
641
0
    xfer->status = PURPLE_XFER_STATUS_ACCEPTED;
642
0
    xfer->ops.init(xfer);
643
0
    return;
644
0
  } else {
645
0
    g_return_if_fail(filename != NULL);
646
0
  }
647
648
0
  buddy = purple_find_buddy(account, xfer->who);
649
650
0
  if (type == PURPLE_XFER_SEND) {
651
    /* Sending a file */
652
    /* Check the filename. */
653
0
    PurpleXferUiOps *ui_ops;
654
0
    ui_ops = purple_xfer_get_ui_ops(xfer);
655
656
#ifdef _WIN32
657
    if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\"))
658
#else
659
0
    if (g_strrstr(filename, "../"))
660
0
#endif
661
0
    {
662
0
      utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
663
664
0
      msg = g_strdup_printf(_("%s is not a valid filename.\n"), utf8);
665
0
      purple_xfer_error(type, account, xfer->who, msg);
666
0
      g_free(utf8);
667
0
      g_free(msg);
668
669
0
      purple_xfer_unref(xfer);
670
0
      return;
671
0
    }
672
673
0
    if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) {
674
0
      if (g_stat(filename, &st) == -1) {
675
0
        purple_xfer_show_file_error(xfer, filename);
676
0
        purple_xfer_unref(xfer);
677
0
        return;
678
0
      }
679
680
0
      purple_xfer_set_local_filename(xfer, filename);
681
0
      purple_xfer_set_size(xfer, st.st_size);
682
0
    } else {
683
0
      purple_xfer_set_local_filename(xfer, filename);
684
0
    }
685
686
0
    base = g_path_get_basename(filename);
687
0
    utf8 = g_filename_to_utf8(base, -1, NULL, NULL, NULL);
688
0
    g_free(base);
689
0
    purple_xfer_set_filename(xfer, utf8);
690
691
0
    msg = g_strdup_printf(_("Offering to send %s to %s"),
692
0
        utf8, buddy ? purple_buddy_get_alias(buddy) : xfer->who);
693
0
    g_free(utf8);
694
0
    purple_xfer_conversation_write(xfer, msg, FALSE);
695
0
    g_free(msg);
696
0
  }
697
0
  else {
698
    /* Receiving a file */
699
0
    xfer->status = PURPLE_XFER_STATUS_ACCEPTED;
700
0
    purple_xfer_set_local_filename(xfer, filename);
701
702
0
    msg = g_strdup_printf(_("Starting transfer of %s from %s"),
703
0
        xfer->filename, buddy ? purple_buddy_get_alias(buddy) : xfer->who);
704
0
    purple_xfer_conversation_write(xfer, msg, FALSE);
705
0
    g_free(msg);
706
0
  }
707
708
0
  purple_xfer_add(xfer);
709
0
  xfer->ops.init(xfer);
710
711
0
}
712
713
void
714
purple_xfer_request_denied(PurpleXfer *xfer)
715
0
{
716
0
  g_return_if_fail(xfer != NULL);
717
718
0
  purple_debug_misc("xfer", "xfer %p denied\n", xfer);
719
720
0
  if (xfer->ops.request_denied != NULL)
721
0
    xfer->ops.request_denied(xfer);
722
723
0
  purple_xfer_unref(xfer);
724
0
}
725
726
PurpleXferType
727
purple_xfer_get_type(const PurpleXfer *xfer)
728
0
{
729
0
  g_return_val_if_fail(xfer != NULL, PURPLE_XFER_UNKNOWN);
730
731
0
  return xfer->type;
732
0
}
733
734
PurpleAccount *
735
purple_xfer_get_account(const PurpleXfer *xfer)
736
0
{
737
0
  g_return_val_if_fail(xfer != NULL, NULL);
738
739
0
  return xfer->account;
740
0
}
741
742
const char *
743
purple_xfer_get_remote_user(const PurpleXfer *xfer)
744
0
{
745
0
  g_return_val_if_fail(xfer != NULL, NULL);
746
0
  return xfer->who;
747
0
}
748
749
PurpleXferStatusType
750
purple_xfer_get_status(const PurpleXfer *xfer)
751
0
{
752
0
  g_return_val_if_fail(xfer != NULL, PURPLE_XFER_STATUS_UNKNOWN);
753
754
0
  return xfer->status;
755
0
}
756
757
/* FIXME: Rename with cancelled for 3.0.0. */
758
gboolean
759
purple_xfer_is_canceled(const PurpleXfer *xfer)
760
0
{
761
0
  g_return_val_if_fail(xfer != NULL, TRUE);
762
763
0
  if ((purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) ||
764
0
      (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_REMOTE))
765
0
    return TRUE;
766
0
  else
767
0
    return FALSE;
768
0
}
769
770
gboolean
771
purple_xfer_is_completed(const PurpleXfer *xfer)
772
0
{
773
0
  g_return_val_if_fail(xfer != NULL, TRUE);
774
775
0
  return (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_DONE);
776
0
}
777
778
const char *
779
purple_xfer_get_filename(const PurpleXfer *xfer)
780
0
{
781
0
  g_return_val_if_fail(xfer != NULL, NULL);
782
783
0
  return xfer->filename;
784
0
}
785
786
const char *
787
purple_xfer_get_local_filename(const PurpleXfer *xfer)
788
0
{
789
0
  g_return_val_if_fail(xfer != NULL, NULL);
790
791
0
  return xfer->local_filename;
792
0
}
793
794
size_t
795
purple_xfer_get_bytes_sent(const PurpleXfer *xfer)
796
0
{
797
0
  g_return_val_if_fail(xfer != NULL, 0);
798
799
0
  return xfer->bytes_sent;
800
0
}
801
802
size_t
803
purple_xfer_get_bytes_remaining(const PurpleXfer *xfer)
804
0
{
805
0
  g_return_val_if_fail(xfer != NULL, 0);
806
807
0
  return xfer->bytes_remaining;
808
0
}
809
810
size_t
811
purple_xfer_get_size(const PurpleXfer *xfer)
812
0
{
813
0
  g_return_val_if_fail(xfer != NULL, 0);
814
815
0
  return xfer->size;
816
0
}
817
818
double
819
purple_xfer_get_progress(const PurpleXfer *xfer)
820
0
{
821
0
  g_return_val_if_fail(xfer != NULL, 0.0);
822
823
0
  if (purple_xfer_get_size(xfer) == 0)
824
0
    return 0.0;
825
826
0
  return ((double)purple_xfer_get_bytes_sent(xfer) /
827
0
      (double)purple_xfer_get_size(xfer));
828
0
}
829
830
unsigned int
831
purple_xfer_get_local_port(const PurpleXfer *xfer)
832
0
{
833
0
  g_return_val_if_fail(xfer != NULL, -1);
834
835
0
  return xfer->local_port;
836
0
}
837
838
const char *
839
purple_xfer_get_remote_ip(const PurpleXfer *xfer)
840
0
{
841
0
  g_return_val_if_fail(xfer != NULL, NULL);
842
843
0
  return xfer->remote_ip;
844
0
}
845
846
unsigned int
847
purple_xfer_get_remote_port(const PurpleXfer *xfer)
848
0
{
849
0
  g_return_val_if_fail(xfer != NULL, -1);
850
851
0
  return xfer->remote_port;
852
0
}
853
854
time_t
855
purple_xfer_get_start_time(const PurpleXfer *xfer)
856
0
{
857
0
  g_return_val_if_fail(xfer != NULL, 0);
858
859
0
  return xfer->start_time;
860
0
}
861
862
time_t
863
purple_xfer_get_end_time(const PurpleXfer *xfer)
864
0
{
865
0
  g_return_val_if_fail(xfer != NULL, 0);
866
867
0
  return xfer->end_time;
868
0
}
869
870
void
871
purple_xfer_set_completed(PurpleXfer *xfer, gboolean completed)
872
0
{
873
0
  PurpleXferUiOps *ui_ops;
874
875
0
  g_return_if_fail(xfer != NULL);
876
877
0
  if (completed == TRUE) {
878
0
    char *msg = NULL;
879
0
    PurpleConversation *conv;
880
881
0
    purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_DONE);
882
883
0
    if (purple_xfer_get_filename(xfer) != NULL)
884
0
    {
885
0
      char *filename = g_markup_escape_text(purple_xfer_get_filename(xfer), -1);
886
0
      if (purple_xfer_get_local_filename(xfer)
887
0
       && purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE)
888
0
      {
889
0
        char *local = g_markup_escape_text(purple_xfer_get_local_filename(xfer), -1);
890
0
        msg = g_strdup_printf(_("Transfer of file <A HREF=\"file://%s\">%s</A> complete"),
891
0
                              local, filename);
892
0
        g_free(local);
893
0
      }
894
0
      else
895
0
        msg = g_strdup_printf(_("Transfer of file %s complete"),
896
0
                              filename);
897
0
      g_free(filename);
898
0
    }
899
0
    else
900
0
      msg = g_strdup(_("File transfer complete"));
901
902
0
    conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, xfer->who,
903
0
                                                 purple_xfer_get_account(xfer));
904
905
0
    if (conv != NULL)
906
0
      purple_conversation_write(conv, NULL, msg, PURPLE_MESSAGE_SYSTEM, time(NULL));
907
0
    g_free(msg);
908
0
  }
909
910
0
  ui_ops = purple_xfer_get_ui_ops(xfer);
911
912
0
  if (ui_ops != NULL && ui_ops->update_progress != NULL)
913
0
    ui_ops->update_progress(xfer, purple_xfer_get_progress(xfer));
914
0
}
915
916
void
917
purple_xfer_set_message(PurpleXfer *xfer, const char *message)
918
0
{
919
0
  g_return_if_fail(xfer != NULL);
920
0
  if (message != xfer->message) {
921
0
    g_free(xfer->message);
922
0
    xfer->message = g_strdup(message);
923
0
  }
924
0
}
925
926
void
927
purple_xfer_set_filename(PurpleXfer *xfer, const char *filename)
928
0
{
929
0
  g_return_if_fail(xfer != NULL);
930
931
0
  if (filename != xfer->filename) {
932
0
    g_free(xfer->filename);
933
0
    xfer->filename = g_strdup(filename);
934
0
  }
935
0
}
936
937
void
938
purple_xfer_set_local_filename(PurpleXfer *xfer, const char *filename)
939
0
{
940
0
  g_return_if_fail(xfer != NULL);
941
942
0
  if (filename != xfer->local_filename) {
943
0
    g_free(xfer->local_filename);
944
0
    xfer->local_filename = g_strdup(filename);
945
0
  }
946
0
}
947
948
void
949
purple_xfer_set_size(PurpleXfer *xfer, size_t size)
950
0
{
951
0
  g_return_if_fail(xfer != NULL);
952
953
0
  xfer->size = size;
954
0
  xfer->bytes_remaining = xfer->size - purple_xfer_get_bytes_sent(xfer);
955
0
}
956
957
void
958
purple_xfer_set_bytes_sent(PurpleXfer *xfer, size_t bytes_sent)
959
0
{
960
0
  g_return_if_fail(xfer != NULL);
961
962
0
  xfer->bytes_sent = bytes_sent;
963
0
  xfer->bytes_remaining = purple_xfer_get_size(xfer) - bytes_sent;
964
0
}
965
966
PurpleXferUiOps *
967
purple_xfer_get_ui_ops(const PurpleXfer *xfer)
968
0
{
969
0
  g_return_val_if_fail(xfer != NULL, NULL);
970
971
0
  return xfer->ui_ops;
972
0
}
973
974
void
975
purple_xfer_set_init_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
976
0
{
977
0
  g_return_if_fail(xfer != NULL);
978
979
0
  xfer->ops.init = fnc;
980
0
}
981
982
void purple_xfer_set_request_denied_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
983
0
{
984
0
  g_return_if_fail(xfer != NULL);
985
986
0
  xfer->ops.request_denied = fnc;
987
0
}
988
989
void
990
purple_xfer_set_read_fnc(PurpleXfer *xfer, gssize (*fnc)(guchar **, PurpleXfer *))
991
0
{
992
0
  g_return_if_fail(xfer != NULL);
993
994
0
  xfer->ops.read = fnc;
995
0
}
996
997
void
998
purple_xfer_set_write_fnc(PurpleXfer *xfer,
999
            gssize (*fnc)(const guchar *, size_t, PurpleXfer *))
1000
0
{
1001
0
  g_return_if_fail(xfer != NULL);
1002
1003
0
  xfer->ops.write = fnc;
1004
0
}
1005
1006
void
1007
purple_xfer_set_ack_fnc(PurpleXfer *xfer,
1008
        void (*fnc)(PurpleXfer *, const guchar *, size_t))
1009
0
{
1010
0
  g_return_if_fail(xfer != NULL);
1011
1012
0
  xfer->ops.ack = fnc;
1013
0
}
1014
1015
void
1016
purple_xfer_set_start_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
1017
0
{
1018
0
  g_return_if_fail(xfer != NULL);
1019
1020
0
  xfer->ops.start = fnc;
1021
0
}
1022
1023
void
1024
purple_xfer_set_end_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
1025
0
{
1026
0
  g_return_if_fail(xfer != NULL);
1027
1028
0
  xfer->ops.end = fnc;
1029
0
}
1030
1031
void
1032
purple_xfer_set_cancel_send_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
1033
0
{
1034
0
  g_return_if_fail(xfer != NULL);
1035
1036
0
  xfer->ops.cancel_send = fnc;
1037
0
}
1038
1039
void
1040
purple_xfer_set_cancel_recv_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
1041
0
{
1042
0
  g_return_if_fail(xfer != NULL);
1043
1044
0
  xfer->ops.cancel_recv = fnc;
1045
0
}
1046
1047
static void
1048
purple_xfer_increase_buffer_size(PurpleXfer *xfer)
1049
0
{
1050
0
  xfer->current_buffer_size = MIN(xfer->current_buffer_size * 1.5,
1051
0
      FT_MAX_BUFFER_SIZE);
1052
0
}
1053
1054
gssize
1055
purple_xfer_read(PurpleXfer *xfer, guchar **buffer)
1056
0
{
1057
0
  gssize s, r;
1058
1059
0
  g_return_val_if_fail(xfer   != NULL, 0);
1060
0
  g_return_val_if_fail(buffer != NULL, 0);
1061
1062
0
  if (purple_xfer_get_size(xfer) == 0)
1063
0
    s = xfer->current_buffer_size;
1064
0
  else
1065
0
    s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size);
1066
1067
0
  if (xfer->ops.read != NULL) {
1068
0
    r = (xfer->ops.read)(buffer, xfer);
1069
0
  }
1070
0
  else {
1071
0
    *buffer = g_malloc0(s);
1072
1073
0
    r = read(xfer->fd, *buffer, s);
1074
0
    if (r < 0 && errno == EAGAIN)
1075
0
      r = 0;
1076
0
    else if (r < 0)
1077
0
      r = -1;
1078
0
    else if (r == 0)
1079
0
      r = -1;
1080
0
  }
1081
1082
0
  if (r >= 0 && (gsize)r == xfer->current_buffer_size)
1083
    /*
1084
     * We managed to read the entire buffer.  This means our this
1085
     * network is fast and our buffer is too small, so make it
1086
     * bigger.
1087
     */
1088
0
    purple_xfer_increase_buffer_size(xfer);
1089
1090
0
  return r;
1091
0
}
1092
1093
gssize
1094
purple_xfer_write(PurpleXfer *xfer, const guchar *buffer, gsize size)
1095
0
{
1096
0
  gssize r, s;
1097
1098
0
  g_return_val_if_fail(xfer   != NULL, 0);
1099
0
  g_return_val_if_fail(buffer != NULL, 0);
1100
0
  g_return_val_if_fail(size   != 0,    0);
1101
1102
0
  s = MIN(purple_xfer_get_bytes_remaining(xfer), size);
1103
1104
0
  if (xfer->ops.write != NULL) {
1105
0
    r = (xfer->ops.write)(buffer, s, xfer);
1106
0
  } else {
1107
0
    r = write(xfer->fd, buffer, s);
1108
0
    if (r < 0 && errno == EAGAIN)
1109
0
      r = 0;
1110
1111
0
    if ((purple_xfer_get_bytes_sent(xfer) + r) >= purple_xfer_get_size(xfer) &&
1112
0
        !purple_xfer_is_completed(xfer)) {
1113
0
      purple_xfer_set_completed(xfer, TRUE);
1114
0
    }
1115
0
  }
1116
1117
0
  return r;
1118
0
}
1119
gboolean
1120
purple_xfer_write_file(PurpleXfer *xfer, const guchar *buffer, gsize size)
1121
0
{
1122
0
  PurpleXferUiOps *ui_ops;
1123
0
  gsize wc;
1124
0
  gboolean fs_known;
1125
1126
0
  g_return_val_if_fail(buffer != NULL, FALSE);
1127
1128
0
  ui_ops = purple_xfer_get_ui_ops(xfer);
1129
0
  fs_known = (purple_xfer_get_size(xfer) > 0);
1130
1131
0
  if (fs_known && size > purple_xfer_get_bytes_remaining(xfer)) {
1132
0
    purple_debug_warning("xfer",
1133
0
      "Got too much data (truncating at %" G_GSIZE_FORMAT
1134
0
      ").\n", purple_xfer_get_size(xfer));
1135
0
    size = purple_xfer_get_bytes_remaining(xfer);
1136
0
  }
1137
1138
0
  if (ui_ops && ui_ops->ui_write)
1139
0
    wc = ui_ops->ui_write(xfer, buffer, size);
1140
0
  else {
1141
0
    if (xfer->dest_fp == NULL) {
1142
0
      purple_debug_error("xfer",
1143
0
        "File is not opened for writing\n");
1144
0
      purple_xfer_cancel_local(xfer);
1145
0
      return FALSE;
1146
0
    }
1147
0
    wc = fwrite(buffer, 1, size, xfer->dest_fp);
1148
0
  }
1149
1150
0
  if (wc != size) {
1151
0
    purple_debug_error("xfer",
1152
0
      "Unable to write whole buffer.\n");
1153
0
    purple_xfer_cancel_local(xfer);
1154
0
    return FALSE;
1155
0
  }
1156
1157
0
  purple_xfer_set_bytes_sent(xfer, purple_xfer_get_bytes_sent(xfer) +
1158
0
    size);
1159
1160
0
  return TRUE;
1161
0
}
1162
1163
gssize
1164
purple_xfer_read_file(PurpleXfer *xfer, guchar *buffer, gsize size)
1165
0
{
1166
0
  PurpleXferUiOps *ui_ops;
1167
0
  gssize got_len;
1168
1169
0
  g_return_val_if_fail(buffer != NULL, FALSE);
1170
1171
0
  ui_ops = purple_xfer_get_ui_ops(xfer);
1172
1173
0
  if (ui_ops && ui_ops->ui_read) {
1174
0
    guchar *buffer_got = NULL;
1175
1176
0
    got_len = ui_ops->ui_read(xfer, &buffer_got, size);
1177
1178
0
    if (got_len >= 0 && (gsize)got_len > size) {
1179
0
      g_free(buffer_got);
1180
0
      purple_debug_error("xfer",
1181
0
        "Got too much data from UI.\n");
1182
0
      purple_xfer_cancel_local(xfer);
1183
0
      return -1;
1184
0
    }
1185
1186
0
    if (got_len > 0)
1187
0
      memcpy(buffer, buffer_got, got_len);
1188
0
    g_free(buffer_got);
1189
0
  } else {
1190
0
    if (xfer->dest_fp == NULL) {
1191
0
      purple_debug_error("xfer",
1192
0
        "File is not opened for reading\n");
1193
0
      purple_xfer_cancel_local(xfer);
1194
0
      return -1;
1195
0
    }
1196
0
    got_len = fread(buffer, 1, size, xfer->dest_fp);
1197
0
    if ((got_len < 0 || (gsize)got_len != size) &&
1198
0
      ferror(xfer->dest_fp))
1199
0
    {
1200
0
      purple_debug_error("xfer",
1201
0
        "Unable to read file.\n");
1202
0
      purple_xfer_cancel_local(xfer);
1203
0
      return -1;
1204
0
    }
1205
0
  }
1206
1207
0
  if (got_len > 0) {
1208
0
    purple_xfer_set_bytes_sent(xfer,
1209
0
      purple_xfer_get_bytes_sent(xfer) + got_len);
1210
0
  }
1211
1212
0
  return got_len;
1213
0
}
1214
1215
static void
1216
do_transfer(PurpleXfer *xfer)
1217
0
{
1218
0
  PurpleXferUiOps *ui_ops;
1219
0
  guchar *buffer = NULL;
1220
0
  gssize r = 0;
1221
1222
0
  ui_ops = purple_xfer_get_ui_ops(xfer);
1223
1224
0
  if (xfer->type == PURPLE_XFER_RECEIVE) {
1225
0
    r = purple_xfer_read(xfer, &buffer);
1226
1227
0
    if (r > 0) {
1228
0
      size_t wc;
1229
0
      if (ui_ops && ui_ops->ui_write)
1230
0
        wc = ui_ops->ui_write(xfer, buffer, r);
1231
0
      else
1232
0
        wc = fwrite(buffer, 1, r, xfer->dest_fp);
1233
1234
0
      if (wc != (gsize)r) {
1235
0
        purple_debug_error("filetransfer", "Unable to write whole buffer.\n");
1236
0
        purple_xfer_cancel_local(xfer);
1237
0
        g_free(buffer);
1238
0
        return;
1239
0
      }
1240
1241
0
      if(xfer->ops.read == NULL) {
1242
0
        if ((purple_xfer_get_size(xfer) > 0) &&
1243
0
            ((purple_xfer_get_bytes_sent(xfer) + r) >= purple_xfer_get_size(xfer))) {
1244
0
          purple_xfer_set_completed(xfer, TRUE);
1245
0
        }
1246
0
      }
1247
1248
0
    } else if(r < 0) {
1249
0
      purple_xfer_cancel_remote(xfer);
1250
0
      g_free(buffer);
1251
0
      return;
1252
0
    }
1253
0
  } else if (xfer->type == PURPLE_XFER_SEND) {
1254
0
    size_t result = 0;
1255
0
    size_t s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size);
1256
0
    PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1257
0
    gboolean read = TRUE;
1258
1259
    /* this is so the prpl can keep the connection open
1260
       if it needs to for some odd reason. */
1261
0
    if (s == 0) {
1262
0
      if (xfer->watcher) {
1263
0
        purple_input_remove(xfer->watcher);
1264
0
        xfer->watcher = 0;
1265
0
      }
1266
0
      return;
1267
0
    }
1268
1269
0
    if (priv->buffer) {
1270
0
      if (priv->buffer->len < s) {
1271
0
        s -= priv->buffer->len;
1272
0
        read = TRUE;
1273
0
      } else {
1274
0
        read = FALSE;
1275
0
      }
1276
0
    }
1277
1278
0
    if (read) {
1279
0
      if (ui_ops && ui_ops->ui_read) {
1280
0
        gssize tmp = ui_ops->ui_read(xfer, &buffer, s);
1281
0
        if (tmp == 0) {
1282
          /*
1283
           * The UI claimed it was ready, but didn't have any data for
1284
           * us...  It will call purple_xfer_ui_ready when ready, which
1285
           * sets back up this watcher.
1286
           */
1287
0
          if (xfer->watcher != 0) {
1288
0
            purple_input_remove(xfer->watcher);
1289
0
            xfer->watcher = 0;
1290
0
          }
1291
1292
          /* Need to indicate the prpl is still ready... */
1293
0
          priv->ready |= PURPLE_XFER_READY_PRPL;
1294
1295
0
          g_return_if_reached();
1296
0
        } else if (tmp < 0) {
1297
0
          purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
1298
0
          purple_xfer_cancel_local(xfer);
1299
0
          return;
1300
0
        }
1301
1302
0
        result = tmp;
1303
0
      } else {
1304
0
        buffer = g_malloc(s);
1305
0
        result = fread(buffer, 1, s, xfer->dest_fp);
1306
0
        if (result != s) {
1307
0
          purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
1308
0
          purple_xfer_cancel_local(xfer);
1309
0
          g_free(buffer);
1310
0
          return;
1311
0
        }
1312
0
      }
1313
0
    }
1314
1315
0
    if (priv->buffer) {
1316
0
      g_byte_array_append(priv->buffer, buffer, result);
1317
0
      g_free(buffer);
1318
0
      buffer = priv->buffer->data;
1319
0
      result = priv->buffer->len;
1320
0
    }
1321
1322
0
    r = purple_xfer_write(xfer, buffer, result);
1323
1324
0
    if (r == -1) {
1325
0
      purple_xfer_cancel_remote(xfer);
1326
0
      if (!priv->buffer)
1327
        /* We don't free buffer if priv->buffer is set, because in
1328
           that case buffer doesn't belong to us. */
1329
0
        g_free(buffer);
1330
0
      return;
1331
0
    } else if (r >= 0 && (gsize)r == result) {
1332
      /*
1333
       * We managed to write the entire buffer.  This means our
1334
       * network is fast and our buffer is too small, so make it
1335
       * bigger.
1336
       */
1337
0
      purple_xfer_increase_buffer_size(xfer);
1338
0
    } else {
1339
0
      if (ui_ops && ui_ops->data_not_sent)
1340
0
        ui_ops->data_not_sent(xfer, buffer + r, result - r);
1341
0
    }
1342
1343
0
    if (priv->buffer) {
1344
      /*
1345
       * Remove what we wrote
1346
       * If we wrote the whole buffer the byte array will be empty
1347
       * Otherwise we'll keep what wasn't sent for next time.
1348
       */
1349
0
      buffer = NULL;
1350
0
      g_byte_array_remove_range(priv->buffer, 0, r);
1351
0
    }
1352
0
  }
1353
1354
0
  if (r > 0) {
1355
0
    if (purple_xfer_get_size(xfer) > 0)
1356
0
      xfer->bytes_remaining -= r;
1357
1358
0
    xfer->bytes_sent += r;
1359
1360
0
    if (xfer->ops.ack != NULL)
1361
0
      xfer->ops.ack(xfer, buffer, r);
1362
1363
0
    g_free(buffer);
1364
1365
0
    if (ui_ops != NULL && ui_ops->update_progress != NULL)
1366
0
      ui_ops->update_progress(xfer,
1367
0
        purple_xfer_get_progress(xfer));
1368
0
  }
1369
1370
0
  if (purple_xfer_is_completed(xfer))
1371
0
    purple_xfer_end(xfer);
1372
0
}
1373
1374
static void
1375
transfer_cb(gpointer data, gint source, PurpleInputCondition condition)
1376
0
{
1377
0
  PurpleXfer *xfer = data;
1378
1379
0
  if (xfer->dest_fp == NULL) {
1380
    /* The UI is moderating its side manually */
1381
0
    PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1382
0
    if (0 == (priv->ready & PURPLE_XFER_READY_UI)) {
1383
0
      priv->ready |= PURPLE_XFER_READY_PRPL;
1384
1385
0
      purple_input_remove(xfer->watcher);
1386
0
      xfer->watcher = 0;
1387
1388
0
      purple_debug_misc("xfer", "prpl is ready on ft %p, waiting for UI\n", xfer);
1389
0
      return;
1390
0
    }
1391
1392
0
    priv->ready = PURPLE_XFER_READY_NONE;
1393
0
  }
1394
1395
0
  do_transfer(xfer);
1396
0
}
1397
1398
static void
1399
begin_transfer(PurpleXfer *xfer, PurpleInputCondition cond)
1400
0
{
1401
0
  PurpleXferType type = purple_xfer_get_type(xfer);
1402
0
  PurpleXferUiOps *ui_ops = purple_xfer_get_ui_ops(xfer);
1403
1404
0
  if (xfer->start_time != 0) {
1405
0
    purple_debug_error("xfer", "Transfer is being started multiple times\n");
1406
0
    g_return_if_reached();
1407
0
  }
1408
1409
0
  if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) {
1410
0
    xfer->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer),
1411
0
                            type == PURPLE_XFER_RECEIVE ? "wb" : "rb");
1412
1413
0
    if (xfer->dest_fp == NULL) {
1414
0
      purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer));
1415
0
      purple_xfer_cancel_local(xfer);
1416
0
      return;
1417
0
    }
1418
1419
0
    if (fseek(xfer->dest_fp, xfer->bytes_sent, SEEK_SET) != 0) {
1420
0
      purple_debug_error("xfer", "couldn't seek\n");
1421
0
      purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer));
1422
0
      purple_xfer_cancel_local(xfer);
1423
0
      return;
1424
0
    }
1425
0
  }
1426
1427
0
  if (xfer->fd != -1)
1428
0
    xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
1429
1430
0
  xfer->start_time = time(NULL);
1431
1432
0
  if (xfer->ops.start != NULL)
1433
0
    xfer->ops.start(xfer);
1434
0
}
1435
1436
static void
1437
connect_cb(gpointer data, gint source, const gchar *error_message)
1438
0
{
1439
0
  PurpleXfer *xfer = (PurpleXfer *)data;
1440
1441
0
  if (source < 0) {
1442
0
    purple_xfer_cancel_local(xfer);
1443
0
    return;
1444
0
  }
1445
1446
0
  xfer->fd = source;
1447
1448
0
  begin_transfer(xfer, PURPLE_INPUT_READ);
1449
0
}
1450
1451
void
1452
purple_xfer_ui_ready(PurpleXfer *xfer)
1453
0
{
1454
0
  PurpleInputCondition cond;
1455
0
  PurpleXferType type;
1456
0
  PurpleXferPrivData *priv;
1457
1458
0
  g_return_if_fail(xfer != NULL);
1459
1460
0
  priv = g_hash_table_lookup(xfers_data, xfer);
1461
0
  priv->ready |= PURPLE_XFER_READY_UI;
1462
1463
0
  if (0 == (priv->ready & PURPLE_XFER_READY_PRPL)) {
1464
0
    purple_debug_misc("xfer", "UI is ready on ft %p, waiting for prpl\n", xfer);
1465
0
    return;
1466
0
  }
1467
1468
0
  purple_debug_misc("xfer", "UI (and prpl) ready on ft %p, so proceeding\n", xfer);
1469
1470
0
  type = purple_xfer_get_type(xfer);
1471
0
  if (type == PURPLE_XFER_SEND)
1472
0
    cond = PURPLE_INPUT_WRITE;
1473
0
  else /* if (type == PURPLE_XFER_RECEIVE) */
1474
0
    cond = PURPLE_INPUT_READ;
1475
1476
0
  if (xfer->watcher == 0 && xfer->fd != -1)
1477
0
    xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
1478
1479
0
  priv->ready = PURPLE_XFER_READY_NONE;
1480
1481
0
  do_transfer(xfer);
1482
0
}
1483
1484
void
1485
purple_xfer_prpl_ready(PurpleXfer *xfer)
1486
0
{
1487
0
  PurpleXferPrivData *priv;
1488
1489
0
  g_return_if_fail(xfer != NULL);
1490
1491
0
  priv = g_hash_table_lookup(xfers_data, xfer);
1492
0
  priv->ready |= PURPLE_XFER_READY_PRPL;
1493
1494
  /* I don't think fwrite/fread are ever *not* ready */
1495
0
  if (xfer->dest_fp == NULL && 0 == (priv->ready & PURPLE_XFER_READY_UI)) {
1496
0
    purple_debug_misc("xfer", "prpl is ready on ft %p, waiting for UI\n", xfer);
1497
0
    return;
1498
0
  }
1499
1500
0
  purple_debug_misc("xfer", "Prpl (and UI) ready on ft %p, so proceeding\n", xfer);
1501
1502
0
  priv->ready = PURPLE_XFER_READY_NONE;
1503
1504
0
  do_transfer(xfer);
1505
0
}
1506
1507
void
1508
purple_xfer_start(PurpleXfer *xfer, int fd, const char *ip,
1509
        unsigned int port)
1510
0
{
1511
0
  PurpleInputCondition cond;
1512
0
  PurpleXferType type;
1513
1514
0
  g_return_if_fail(xfer != NULL);
1515
0
  g_return_if_fail(purple_xfer_get_type(xfer) != PURPLE_XFER_UNKNOWN);
1516
1517
0
  type = purple_xfer_get_type(xfer);
1518
1519
0
  purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_STARTED);
1520
1521
  /*
1522
   * FIXME 3.0.0 -- there's too much broken code depending on fd == 0
1523
   * meaning "don't use a real fd"
1524
   */
1525
0
  if (fd == 0)
1526
0
    fd = -1;
1527
1528
0
  if (type == PURPLE_XFER_RECEIVE) {
1529
0
    cond = PURPLE_INPUT_READ;
1530
1531
0
    if (ip != NULL) {
1532
0
      xfer->remote_ip   = g_strdup(ip);
1533
0
      xfer->remote_port = port;
1534
1535
      /* Establish a file descriptor. */
1536
0
      purple_proxy_connect(NULL, xfer->account, xfer->remote_ip,
1537
0
                 xfer->remote_port, connect_cb, xfer);
1538
1539
0
      return;
1540
0
    }
1541
0
    else {
1542
0
      xfer->fd = fd;
1543
0
    }
1544
0
  }
1545
0
  else {
1546
0
    cond = PURPLE_INPUT_WRITE;
1547
1548
0
    xfer->fd = fd;
1549
0
  }
1550
1551
0
  begin_transfer(xfer, cond);
1552
0
}
1553
1554
void
1555
purple_xfer_end(PurpleXfer *xfer)
1556
0
{
1557
0
  g_return_if_fail(xfer != NULL);
1558
1559
  /* See if we are actually trying to cancel this. */
1560
0
  if (!purple_xfer_is_completed(xfer)) {
1561
0
    purple_xfer_cancel_local(xfer);
1562
0
    return;
1563
0
  }
1564
1565
0
  xfer->end_time = time(NULL);
1566
0
  if (xfer->ops.end != NULL)
1567
0
    xfer->ops.end(xfer);
1568
1569
0
  if (xfer->watcher != 0) {
1570
0
    purple_input_remove(xfer->watcher);
1571
0
    xfer->watcher = 0;
1572
0
  }
1573
1574
0
  if (xfer->fd != -1) {
1575
0
    close(xfer->fd);
1576
0
  }
1577
1578
0
  if (xfer->dest_fp != NULL) {
1579
0
    fclose(xfer->dest_fp);
1580
0
    xfer->dest_fp = NULL;
1581
0
  }
1582
1583
0
  purple_xfer_unref(xfer);
1584
0
}
1585
1586
void
1587
purple_xfer_add(PurpleXfer *xfer)
1588
0
{
1589
0
  PurpleXferUiOps *ui_ops;
1590
1591
0
  g_return_if_fail(xfer != NULL);
1592
1593
0
  ui_ops = purple_xfer_get_ui_ops(xfer);
1594
1595
0
  if (ui_ops != NULL && ui_ops->add_xfer != NULL)
1596
0
    ui_ops->add_xfer(xfer);
1597
0
}
1598
1599
void
1600
purple_xfer_cancel_local(PurpleXfer *xfer)
1601
0
{
1602
0
  PurpleXferUiOps *ui_ops;
1603
0
  char *msg = NULL;
1604
1605
0
  g_return_if_fail(xfer != NULL);
1606
1607
  /* TODO: We definitely want to close any open request dialogs associated
1608
     with this transfer.  However, in some cases the request dialog might
1609
     own a reference on the xfer.  This happens at least with the "%s wants
1610
     to send you %s" dialog from purple_xfer_ask_recv().  In these cases
1611
     the ref count will not be decremented when the request dialog is
1612
     closed, so the ref count will never reach 0 and the xfer will never
1613
     be freed.  This is a memleak and should be fixed.  It's not clear what
1614
     the correct fix is.  Probably requests should have a destroy function
1615
     that is called when the request is destroyed.  But also, ref counting
1616
     xfer objects makes this code REALLY complicated.  An alternate fix is
1617
     to not ref count and instead just make sure the object still exists
1618
     when we try to use it. */
1619
0
  purple_request_close_with_handle(xfer);
1620
1621
0
  purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL);
1622
0
  xfer->end_time = time(NULL);
1623
1624
0
  if (purple_xfer_get_filename(xfer) != NULL)
1625
0
  {
1626
0
    msg = g_strdup_printf(_("You cancelled the transfer of %s"),
1627
0
                purple_xfer_get_filename(xfer));
1628
0
  }
1629
0
  else
1630
0
  {
1631
0
    msg = g_strdup(_("File transfer cancelled"));
1632
0
  }
1633
0
  purple_xfer_conversation_write(xfer, msg, FALSE);
1634
0
  g_free(msg);
1635
1636
0
  if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)
1637
0
  {
1638
0
    if (xfer->ops.cancel_send != NULL)
1639
0
      xfer->ops.cancel_send(xfer);
1640
0
  }
1641
0
  else
1642
0
  {
1643
0
    if (xfer->ops.cancel_recv != NULL)
1644
0
      xfer->ops.cancel_recv(xfer);
1645
0
  }
1646
1647
0
  if (xfer->watcher != 0) {
1648
0
    purple_input_remove(xfer->watcher);
1649
0
    xfer->watcher = 0;
1650
0
  }
1651
1652
0
  if (xfer->fd != -1)
1653
0
    close(xfer->fd);
1654
1655
0
  if (xfer->dest_fp != NULL) {
1656
0
    fclose(xfer->dest_fp);
1657
0
    xfer->dest_fp = NULL;
1658
0
  }
1659
1660
0
  ui_ops = purple_xfer_get_ui_ops(xfer);
1661
1662
0
  if (ui_ops != NULL && ui_ops->cancel_local != NULL)
1663
0
    ui_ops->cancel_local(xfer);
1664
1665
0
  xfer->bytes_remaining = 0;
1666
1667
0
  purple_xfer_unref(xfer);
1668
0
}
1669
1670
void
1671
purple_xfer_cancel_remote(PurpleXfer *xfer)
1672
0
{
1673
0
  PurpleXferUiOps *ui_ops;
1674
0
  gchar *msg;
1675
0
  PurpleAccount *account;
1676
0
  PurpleBuddy *buddy;
1677
1678
0
  g_return_if_fail(xfer != NULL);
1679
1680
0
  purple_request_close_with_handle(xfer);
1681
0
  purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_REMOTE);
1682
0
  xfer->end_time = time(NULL);
1683
1684
0
  account = purple_xfer_get_account(xfer);
1685
0
  buddy = purple_find_buddy(account, xfer->who);
1686
1687
0
  if (purple_xfer_get_filename(xfer) != NULL)
1688
0
  {
1689
0
    msg = g_strdup_printf(_("%s cancelled the transfer of %s"),
1690
0
        buddy ? purple_buddy_get_alias(buddy) : xfer->who, purple_xfer_get_filename(xfer));
1691
0
  }
1692
0
  else
1693
0
  {
1694
0
    msg = g_strdup_printf(_("%s cancelled the file transfer"),
1695
0
        buddy ? purple_buddy_get_alias(buddy) : xfer->who);
1696
0
  }
1697
0
  purple_xfer_conversation_write(xfer, msg, TRUE);
1698
0
  purple_xfer_error(purple_xfer_get_type(xfer), account, xfer->who, msg);
1699
0
  g_free(msg);
1700
1701
0
  if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)
1702
0
  {
1703
0
    if (xfer->ops.cancel_send != NULL)
1704
0
      xfer->ops.cancel_send(xfer);
1705
0
  }
1706
0
  else
1707
0
  {
1708
0
    if (xfer->ops.cancel_recv != NULL)
1709
0
      xfer->ops.cancel_recv(xfer);
1710
0
  }
1711
1712
0
  if (xfer->watcher != 0) {
1713
0
    purple_input_remove(xfer->watcher);
1714
0
    xfer->watcher = 0;
1715
0
  }
1716
1717
0
  if (xfer->fd != -1)
1718
0
    close(xfer->fd);
1719
1720
0
  if (xfer->dest_fp != NULL) {
1721
0
    fclose(xfer->dest_fp);
1722
0
    xfer->dest_fp = NULL;
1723
0
  }
1724
1725
0
  ui_ops = purple_xfer_get_ui_ops(xfer);
1726
1727
0
  if (ui_ops != NULL && ui_ops->cancel_remote != NULL)
1728
0
    ui_ops->cancel_remote(xfer);
1729
1730
0
  xfer->bytes_remaining = 0;
1731
1732
0
  purple_xfer_unref(xfer);
1733
0
}
1734
1735
void
1736
purple_xfer_error(PurpleXferType type, PurpleAccount *account, const char *who, const char *msg)
1737
0
{
1738
0
  char *title;
1739
1740
0
  g_return_if_fail(msg  != NULL);
1741
0
  g_return_if_fail(type != PURPLE_XFER_UNKNOWN);
1742
1743
0
  if (account) {
1744
0
    PurpleBuddy *buddy;
1745
0
    buddy = purple_find_buddy(account, who);
1746
0
    if (buddy)
1747
0
      who = purple_buddy_get_alias(buddy);
1748
0
  }
1749
1750
0
  if (type == PURPLE_XFER_SEND)
1751
0
    title = g_strdup_printf(_("File transfer to %s failed."), who);
1752
0
  else
1753
0
    title = g_strdup_printf(_("File transfer from %s failed."), who);
1754
1755
0
  purple_notify_error(NULL, NULL, title, msg);
1756
1757
0
  g_free(title);
1758
0
}
1759
1760
void
1761
purple_xfer_update_progress(PurpleXfer *xfer)
1762
0
{
1763
0
  PurpleXferUiOps *ui_ops;
1764
1765
0
  g_return_if_fail(xfer != NULL);
1766
1767
0
  ui_ops = purple_xfer_get_ui_ops(xfer);
1768
0
  if (ui_ops != NULL && ui_ops->update_progress != NULL)
1769
0
    ui_ops->update_progress(xfer, purple_xfer_get_progress(xfer));
1770
0
}
1771
1772
gconstpointer
1773
purple_xfer_get_thumbnail(const PurpleXfer *xfer, gsize *len)
1774
0
{
1775
0
  PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1776
1777
0
  if (len)
1778
0
    *len = priv->thumbnail_size;
1779
1780
0
  return priv->thumbnail_data;
1781
0
}
1782
1783
const gchar *
1784
purple_xfer_get_thumbnail_mimetype(const PurpleXfer *xfer)
1785
0
{
1786
0
  PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1787
1788
0
  return priv->thumbnail_mimetype;
1789
0
}
1790
1791
void
1792
purple_xfer_set_thumbnail(PurpleXfer *xfer, gconstpointer thumbnail,
1793
  gsize size, const gchar *mimetype)
1794
0
{
1795
0
  PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1796
1797
  /* Hold onto these in case they are equal to passed-in pointers */
1798
0
  gpointer *old_thumbnail_data = priv->thumbnail_data;
1799
0
  gchar *old_mimetype = priv->thumbnail_mimetype;
1800
1801
0
  if (thumbnail && size > 0) {
1802
0
    priv->thumbnail_data = g_memdup2(thumbnail, size);
1803
0
    priv->thumbnail_size = size;
1804
0
    priv->thumbnail_mimetype = g_strdup(mimetype);
1805
0
  } else {
1806
0
    priv->thumbnail_data = NULL;
1807
0
    priv->thumbnail_size = 0;
1808
0
    priv->thumbnail_mimetype = NULL;
1809
0
  }
1810
1811
  /* Now it's safe to free the pointers */
1812
0
  g_free(old_thumbnail_data);
1813
0
  g_free(old_mimetype);
1814
0
}
1815
1816
void
1817
purple_xfer_prepare_thumbnail(PurpleXfer *xfer, const gchar *formats)
1818
0
{
1819
0
  if (xfer->ui_ops->add_thumbnail) {
1820
0
    xfer->ui_ops->add_thumbnail(xfer, formats);
1821
0
  }
1822
0
}
1823
1824
/**************************************************************************
1825
 * File Transfer Subsystem API
1826
 **************************************************************************/
1827
void *
1828
0
purple_xfers_get_handle(void) {
1829
0
  static int handle = 0;
1830
1831
0
  return &handle;
1832
0
}
1833
1834
void
1835
0
purple_xfers_init(void) {
1836
0
  void *handle = purple_xfers_get_handle();
1837
1838
0
  xfers_data = g_hash_table_new_full(g_direct_hash, g_direct_equal,
1839
0
                                     NULL, purple_xfer_priv_data_destroy);
1840
1841
  /* register signals */
1842
0
  purple_signal_register(handle, "file-recv-accept",
1843
0
                       purple_marshal_VOID__POINTER, NULL, 1,
1844
0
                       purple_value_new(PURPLE_TYPE_SUBTYPE,
1845
0
                                      PURPLE_SUBTYPE_XFER));
1846
0
  purple_signal_register(handle, "file-send-accept",
1847
0
                       purple_marshal_VOID__POINTER, NULL, 1,
1848
0
                       purple_value_new(PURPLE_TYPE_SUBTYPE,
1849
0
                                      PURPLE_SUBTYPE_XFER));
1850
0
  purple_signal_register(handle, "file-recv-start",
1851
0
                       purple_marshal_VOID__POINTER, NULL, 1,
1852
0
                       purple_value_new(PURPLE_TYPE_SUBTYPE,
1853
0
                                      PURPLE_SUBTYPE_XFER));
1854
0
  purple_signal_register(handle, "file-send-start",
1855
0
                       purple_marshal_VOID__POINTER, NULL, 1,
1856
0
                       purple_value_new(PURPLE_TYPE_SUBTYPE,
1857
0
                                      PURPLE_SUBTYPE_XFER));
1858
0
  purple_signal_register(handle, "file-send-cancel",
1859
0
                       purple_marshal_VOID__POINTER, NULL, 1,
1860
0
                       purple_value_new(PURPLE_TYPE_SUBTYPE,
1861
0
                                      PURPLE_SUBTYPE_XFER));
1862
0
  purple_signal_register(handle, "file-recv-cancel",
1863
0
                       purple_marshal_VOID__POINTER, NULL, 1,
1864
0
                       purple_value_new(PURPLE_TYPE_SUBTYPE,
1865
0
                                      PURPLE_SUBTYPE_XFER));
1866
0
  purple_signal_register(handle, "file-send-complete",
1867
0
                       purple_marshal_VOID__POINTER, NULL, 1,
1868
0
                       purple_value_new(PURPLE_TYPE_SUBTYPE,
1869
0
                                      PURPLE_SUBTYPE_XFER));
1870
0
  purple_signal_register(handle, "file-recv-complete",
1871
0
                       purple_marshal_VOID__POINTER, NULL, 1,
1872
0
                       purple_value_new(PURPLE_TYPE_SUBTYPE,
1873
0
                                      PURPLE_SUBTYPE_XFER));
1874
0
  purple_signal_register(handle, "file-recv-request",
1875
0
                       purple_marshal_VOID__POINTER, NULL, 1,
1876
0
                       purple_value_new(PURPLE_TYPE_SUBTYPE,
1877
0
                                      PURPLE_SUBTYPE_XFER));
1878
0
}
1879
1880
void
1881
purple_xfers_uninit(void)
1882
0
{
1883
0
  void *handle = purple_xfers_get_handle();
1884
1885
0
  purple_signals_disconnect_by_handle(handle);
1886
0
  purple_signals_unregister_by_instance(handle);
1887
1888
0
  g_hash_table_destroy(xfers_data);
1889
0
  xfers_data = NULL;
1890
0
}
1891
1892
void
1893
0
purple_xfers_set_ui_ops(PurpleXferUiOps *ops) {
1894
0
  xfer_ui_ops = ops;
1895
0
}
1896
1897
PurpleXferUiOps *
1898
0
purple_xfers_get_ui_ops(void) {
1899
0
  return xfer_ui_ops;
1900
0
}