filetransfer: fix memory leak in MIME callback
[siplcs.git] / src / core / sipe-ft-lync.c
blobb3b1a62323e2c911d700d00d3d68e3804da18431
1 /**
2 * @file sipe-ft-lync.c
4 * pidgin-sipe
6 * Copyright (C) 2014-2015 SIPE Project <http://sipe.sourceforge.net/>
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.
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.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include <glib.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
37 #include "sip-transport.h"
38 #include "sipe-backend.h"
39 #include "sipe-common.h"
40 #include "sipe-core.h"
41 #include "sipe-core-private.h"
42 #include "sipe-ft-lync.h"
43 #include "sipe-media.h"
44 #include "sipe-mime.h"
45 #include "sipe-utils.h"
46 #include "sipe-xml.h"
47 #include "sipmsg.h"
49 struct sipe_file_transfer_lync {
50 struct sipe_file_transfer public;
52 gchar *sdp;
53 gchar *file_name;
54 gchar *id;
55 gsize file_size;
56 guint request_id;
58 guint bytes_left_in_chunk;
60 guint8 buffer[2048];
61 guint buffer_len;
62 guint buffer_read_pos;
64 int backend_pipe[2];
66 struct sipe_media_call *call;
68 void (*call_reject_parent_cb)(struct sipe_media_call *call,
69 gboolean local);
71 #define SIPE_FILE_TRANSFER ((struct sipe_file_transfer *) ft_private)
72 #define SIPE_FILE_TRANSFER_PRIVATE ((struct sipe_file_transfer_lync *) ft)
74 typedef enum {
75 SIPE_XDATA_DATA_CHUNK = 0x00,
76 SIPE_XDATA_START_OF_STREAM = 0x01,
77 SIPE_XDATA_END_OF_STREAM = 0x02
78 } SipeXDataMessages;
80 #define XDATA_HEADER_SIZE sizeof (guint8) + sizeof (guint16)
82 static void
83 sipe_file_transfer_lync_free(struct sipe_file_transfer_lync *ft_private)
85 if (ft_private->backend_pipe[1] != 0) {
86 // Backend is responsible for closing the pipe's read end.
87 close(ft_private->backend_pipe[1]);
90 g_free(ft_private->file_name);
91 g_free(ft_private->sdp);
92 g_free(ft_private->id);
93 g_free(ft_private);
96 static void
97 send_ms_filetransfer_msg(char *body, struct sipe_file_transfer_lync *ft_private,
98 TransCallback callback)
100 sip_transport_info(sipe_media_get_sipe_core_private(ft_private->call),
101 "Content-Type: application/ms-filetransfer+xml\r\n",
102 body,
103 sipe_media_get_sip_dialog(ft_private->call),
104 callback);
106 g_free(body);
109 static void
110 send_ms_filetransfer_response(struct sipe_file_transfer_lync *ft_private,
111 const gchar *code, const gchar *reason,
112 TransCallback callback)
114 static const gchar *RESPONSE_STR =
115 "<response xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" requestId=\"%d\" code=\"%s\" %s%s%s/>";
117 send_ms_filetransfer_msg(g_strdup_printf(RESPONSE_STR,
118 ft_private->request_id, code,
119 reason ? "reason=\"" : "",
120 reason ? reason : "",
121 reason ? "\"" : ""),
122 ft_private, callback);
125 static void
126 mime_mixed_cb(gpointer user_data, const GSList *fields, const gchar *body,
127 gsize length)
129 struct sipe_file_transfer_lync *ft_private = user_data;
130 const gchar *ctype = sipe_utils_nameval_find(fields, "Content-Type");
132 /* Lync 2010 file transfer */
133 if (g_str_has_prefix(ctype, "application/ms-filetransfer+xml")) {
134 sipe_xml *xml = sipe_xml_parse(body, length);
136 if (xml) {
137 const sipe_xml *node;
139 ft_private->request_id = sipe_xml_int_attribute(xml,
140 "requestId",
141 ft_private->request_id);
143 node = sipe_xml_child(xml, "publishFile/fileInfo/name");
144 if (node) {
145 g_free(ft_private->file_name);
146 ft_private->file_name = sipe_xml_data(node);
149 node = sipe_xml_child(xml, "publishFile/fileInfo/id");
150 if (node) {
151 g_free(ft_private->id);
152 ft_private->id = sipe_xml_data(node);
155 node = sipe_xml_child(xml, "publishFile/fileInfo/size");
156 if (node) {
157 gchar *size_str = sipe_xml_data(node);
158 if (size_str) {
159 ft_private->file_size = atoi(size_str);
160 g_free(size_str);
164 sipe_xml_free(xml);
166 } else if (g_str_has_prefix(ctype, "application/sdp")) {
167 g_free(ft_private->sdp);
168 ft_private->sdp = g_strndup(body, length);
172 static void
173 candidate_pairs_established_cb(struct sipe_media_stream *stream)
175 struct sipe_file_transfer_lync *ft_private;
176 static const gchar *DOWNLOAD_FILE_REQUEST =
177 "<request xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" requestId=\"%d\">"
178 "<downloadFile>"
179 "<fileInfo>"
180 "<id>%s</id>"
181 "<name>%s</name>"
182 "</fileInfo>"
183 "</downloadFile>"
184 "</request>";
186 g_return_if_fail(sipe_strequal(stream->id, "data"));
188 ft_private = sipe_media_stream_get_data(stream);
190 send_ms_filetransfer_response(ft_private, "success", NULL, NULL);
192 send_ms_filetransfer_msg(g_strdup_printf(DOWNLOAD_FILE_REQUEST,
193 ++ft_private->request_id,
194 ft_private->id,
195 ft_private->file_name),
196 ft_private, NULL);
199 static gboolean
200 create_pipe(int pipefd[2])
202 #ifdef _WIN32
203 #error "Pipes not implemented for Windows"
204 /* Those interested in porting the code may use Pidgin's wpurple_input_pipe() in
205 * win32dep.c as an inspiration. */
206 #else
207 if (pipe(pipefd) != 0) {
208 return FALSE;
211 fcntl(pipefd[0], F_SETFL, fcntl(pipefd[0], F_GETFL) | O_NONBLOCK);
212 fcntl(pipefd[1], F_SETFL, fcntl(pipefd[1], F_GETFL) | O_NONBLOCK);
214 return TRUE;
215 #endif
218 static void
219 xdata_start_of_stream_cb(struct sipe_media_stream *stream,
220 guint8 *buffer, gsize len)
222 struct sipe_file_transfer_lync *ft_private =
223 sipe_media_stream_get_data(stream);
224 struct sipe_backend_fd *fd;
226 buffer[len] = 0;
227 SIPE_DEBUG_INFO("Received new stream for requestId : %s", buffer);
229 if (!create_pipe(ft_private->backend_pipe)) {
230 SIPE_DEBUG_ERROR_NOFORMAT("Couldn't create backend pipe");
231 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER);
232 return;
235 fd = sipe_backend_fd_from_int(ft_private->backend_pipe[0]);
236 sipe_backend_ft_start(SIPE_FILE_TRANSFER, fd, NULL, 0);
237 sipe_backend_fd_free(fd);
240 static void
241 xdata_end_of_stream_cb(SIPE_UNUSED_PARAMETER struct sipe_media_stream *stream,
242 guint8 *buffer, gsize len)
244 buffer[len] = 0;
245 SIPE_DEBUG_INFO("Received end of stream for requestId : %s", buffer);
248 static void
249 xdata_got_header_cb(struct sipe_media_stream *stream,
250 guint8 *buffer,
251 SIPE_UNUSED_PARAMETER gsize len)
253 struct sipe_file_transfer_lync *ft_private =
254 sipe_media_stream_get_data(stream);
256 guint8 type = buffer[0];
257 guint16 size = GUINT16_FROM_BE(*(guint16 *)(buffer + sizeof (guint8)));
259 switch (type) {
260 case SIPE_XDATA_START_OF_STREAM:
261 sipe_media_stream_read_async(stream,
262 ft_private->buffer, size,
263 xdata_start_of_stream_cb);
264 break;
265 case SIPE_XDATA_DATA_CHUNK:
266 SIPE_DEBUG_INFO("Received new data chunk of size %d",
267 size);
268 ft_private->bytes_left_in_chunk = size;
269 break;
270 /* We'll read the data when read_cb is called again. */
271 case SIPE_XDATA_END_OF_STREAM:
272 sipe_media_stream_read_async(stream,
273 ft_private->buffer, size,
274 xdata_end_of_stream_cb);
275 break;
279 static void
280 read_cb(struct sipe_media_stream *stream)
282 struct sipe_file_transfer_lync *ft_private =
283 sipe_media_stream_get_data(stream);
285 if (ft_private->buffer_read_pos < ft_private->buffer_len) {
286 /* Have data in buffer, write them to the backend. */
288 gpointer buffer;
289 size_t len;
290 ssize_t written;
292 buffer = ft_private->buffer + ft_private->buffer_read_pos;
293 len = ft_private->buffer_len - ft_private->buffer_read_pos;
294 written = write(ft_private->backend_pipe[1], buffer, len);
296 if (written > 0) {
297 ft_private->buffer_read_pos += written;
298 } else if (written < 0 && errno != EAGAIN) {
299 SIPE_DEBUG_ERROR_NOFORMAT("Error while writing into "
300 "backend pipe");
301 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER);
302 return;
304 } else if (ft_private->bytes_left_in_chunk != 0) {
305 /* Have data from the sender, replenish our buffer with it. */
307 ft_private->buffer_len = MIN(ft_private->bytes_left_in_chunk,
308 sizeof (ft_private->buffer));
310 ft_private->buffer_len =
311 sipe_backend_media_stream_read(stream,
312 ft_private->buffer,
313 ft_private->buffer_len);
315 ft_private->bytes_left_in_chunk -= ft_private->buffer_len;
316 ft_private->buffer_read_pos = 0;
318 SIPE_DEBUG_INFO("Read %d bytes. %d left in this chunk.",
319 ft_private->buffer_len, ft_private->bytes_left_in_chunk);
320 } else {
321 /* No data available. This is either stream start, beginning of
322 * chunk, or stream end. */
324 sipe_media_stream_read_async(stream, ft_private->buffer,
325 XDATA_HEADER_SIZE,
326 xdata_got_header_cb);
330 static void
331 ft_lync_incoming_init(struct sipe_file_transfer *ft,
332 SIPE_UNUSED_PARAMETER const gchar *filename,
333 SIPE_UNUSED_PARAMETER gsize size,
334 SIPE_UNUSED_PARAMETER const gchar *who)
336 struct sipe_media_call *call = SIPE_FILE_TRANSFER_PRIVATE->call;
338 if (call) {
339 sipe_backend_media_accept(call->backend_private, TRUE);
343 static struct sipe_file_transfer_lync *
344 ft_private_from_call(struct sipe_media_call *call)
346 struct sipe_media_stream *stream =
347 sipe_core_media_get_stream_by_id(call, "data");
348 g_return_val_if_fail(stream, NULL);
350 return sipe_media_stream_get_data(stream);
353 static void
354 send_transfer_progress(struct sipe_file_transfer_lync *ft_private)
356 static const gchar *FILETRANSFER_PROGRESS =
357 "<notify xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" notifyId=\"%d\">"
358 "<fileTransferProgress>"
359 "<transferId>%d</transferId>"
360 "<bytesReceived>"
361 "<from>0</from>"
362 "<to>%d</to>"
363 "</bytesReceived>"
364 "</fileTransferProgress>"
365 "</notify>";
367 send_ms_filetransfer_msg(g_strdup_printf(FILETRANSFER_PROGRESS,
368 rand(),
369 ft_private->request_id,
370 ft_private->file_size - 1),
371 ft_private, NULL);
374 static gboolean
375 ft_lync_end(struct sipe_file_transfer *ft)
377 send_transfer_progress(SIPE_FILE_TRANSFER_PRIVATE);
379 /* Don't let the call be hung up in ft_lync_deallocate() because we
380 * have to wait for BYE from the sender in order for the transfer to be
381 * reported as successful by Lync client. */
382 SIPE_FILE_TRANSFER_PRIVATE->call = NULL;
384 return TRUE;
387 static void
388 call_reject_cb(struct sipe_media_call *call, gboolean local)
390 struct sipe_file_transfer_lync *ft_private = ft_private_from_call(call);
391 g_return_if_fail(ft_private);
393 if (ft_private->call_reject_parent_cb) {
394 ft_private->call_reject_parent_cb(call, local);
397 if (!local) {
398 sipe_backend_ft_cancel_remote(&ft_private->public);
402 static void
403 ft_lync_incoming_cancelled(struct sipe_file_transfer *ft)
405 static const gchar *FILETRANSFER_CANCEL_REQUEST =
406 "<request xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" requestId=\"%d\"/>"
407 "<cancelTransfer>"
408 "<transferId>%d</transferId>"
409 "<fileInfo>"
410 "<id>%s</id>"
411 "<name>%s</name>"
412 "</fileInfo>"
413 "</cancelTransfer>"
414 "</request>";
416 struct sipe_file_transfer_lync *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
417 struct sipe_media_stream *stream;
419 send_ms_filetransfer_msg(g_strdup_printf(FILETRANSFER_CANCEL_REQUEST,
420 ft_private->request_id + 1,
421 ft_private->request_id,
422 ft_private->id,
423 ft_private->file_name),
424 ft_private,
425 NULL);
427 stream = sipe_core_media_get_stream_by_id(ft_private->call, "data");
428 if (stream) {
429 stream->read_cb = NULL;
433 static void
434 ft_lync_deallocate(struct sipe_file_transfer *ft)
436 struct sipe_media_call *call = SIPE_FILE_TRANSFER_PRIVATE->call;
438 if (call) {
439 sipe_backend_media_hangup(call->backend_private, TRUE);
443 void
444 process_incoming_invite_ft_lync(struct sipe_core_private *sipe_private,
445 struct sipmsg *msg)
447 struct sipe_file_transfer_lync *ft_private;
448 struct sipe_media_call *call;
449 struct sipe_media_stream *stream;
451 ft_private = g_new0(struct sipe_file_transfer_lync, 1);
452 sipe_mime_parts_foreach(sipmsg_find_header(msg, "Content-Type"),
453 msg->body, mime_mixed_cb, ft_private);
455 if (!ft_private->file_name || !ft_private->file_size || !ft_private->sdp) {
456 sip_transport_response(sipe_private, msg, 488, "Not Acceptable Here", NULL);
457 sipe_file_transfer_lync_free(ft_private);
458 return;
461 /* Replace multipart message body with the selected SDP part and
462 * initialize media session as if invited to a media call. */
463 g_free(msg->body);
464 msg->body = ft_private->sdp;
465 msg->bodylen = strlen(msg->body);
466 ft_private->sdp = NULL;
468 ft_private->call = process_incoming_invite_call(sipe_private, msg);
469 if (!ft_private->call) {
470 sip_transport_response(sipe_private, msg, 500, "Server Internal Error", NULL);
471 sipe_file_transfer_lync_free(ft_private);
472 return;
475 call = ft_private->call;
477 ft_private->public.ft_init = ft_lync_incoming_init;
478 ft_private->public.ft_cancelled = ft_lync_incoming_cancelled;
479 ft_private->public.ft_end = ft_lync_end;
480 ft_private->public.ft_deallocate = ft_lync_deallocate;
482 ft_private->call_reject_parent_cb = call->call_reject_cb;
483 call->call_reject_cb = call_reject_cb;
485 stream = sipe_core_media_get_stream_by_id(call, "data");
486 stream->candidate_pairs_established_cb = candidate_pairs_established_cb;
487 stream->read_cb = read_cb;
488 sipe_media_stream_add_extra_attribute(stream, "recvonly", NULL);
489 sipe_media_stream_set_data(stream, ft_private,
490 (GDestroyNotify)sipe_file_transfer_lync_free);
492 sipe_backend_ft_incoming(SIPE_CORE_PUBLIC, SIPE_FILE_TRANSFER,
493 call->with, ft_private->file_name,
494 ft_private->file_size);
497 static void
498 process_response_incoming(struct sipe_file_transfer_lync *ft_private,
499 sipe_xml *xml)
501 const gchar *attr;
502 guint request_id = sipe_xml_int_attribute(xml, "requestId", 0);
504 if (request_id != ft_private->request_id) {
505 return;
508 attr = sipe_xml_attribute(xml, "code");
509 if (sipe_strequal(attr, "failure")) {
510 const gchar *reason = sipe_xml_attribute(xml, "reason");
511 if (sipe_strequal(reason, "requestCancelled")) {
512 sipe_backend_ft_cancel_remote(SIPE_FILE_TRANSFER);
517 void
518 process_incoming_info_ft_lync(struct sipe_core_private *sipe_private,
519 struct sipmsg *msg)
521 struct sipe_media_call *call;
522 struct sipe_file_transfer_lync *ft_private;
523 sipe_xml *xml;
525 call = g_hash_table_lookup(sipe_private->media_calls,
526 sipmsg_find_header(msg, "Call-ID"));
527 if (!call) {
528 return;
531 ft_private = ft_private_from_call(call);
532 if (!ft_private) {
533 return;
536 xml = sipe_xml_parse(msg->body, msg->bodylen);
537 if (!xml) {
538 return;
541 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
543 if (sipe_backend_ft_is_incoming(SIPE_FILE_TRANSFER)) {
544 if (sipe_strequal(sipe_xml_name(xml), "response")) {
545 process_response_incoming(ft_private, xml);
549 sipe_xml_free(xml);
553 Local Variables:
554 mode: c
555 c-file-style: "bsd"
556 indent-tabs-mode: t
557 tab-width: 8
558 End: