6 * Copyright (C) 2014-2017 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
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"
46 #include "sipe-utils.h"
51 struct sipe_file_transfer_lync
{
52 struct sipe_file_transfer
public;
60 guint bytes_left_in_chunk
;
64 guint buffer_read_pos
;
67 int backend_pipe_write_source_id
;
69 struct sipe_core_private
*sipe_private
;
70 struct sipe_media_call
*call
;
72 void (*call_reject_parent_cb
)(struct sipe_media_call
*call
,
75 #define SIPE_FILE_TRANSFER ((struct sipe_file_transfer *) ft_private)
76 #define SIPE_FILE_TRANSFER_PRIVATE ((struct sipe_file_transfer_lync *) ft)
79 SIPE_XDATA_DATA_CHUNK
= 0x00,
80 SIPE_XDATA_START_OF_STREAM
= 0x01,
81 SIPE_XDATA_END_OF_STREAM
= 0x02
84 #define XDATA_HEADER_SIZE sizeof (guint8) + sizeof (guint16)
87 sipe_file_transfer_lync_free(struct sipe_file_transfer_lync
*ft_private
)
91 our_pipe_end
= sipe_backend_ft_is_incoming(SIPE_FILE_TRANSFER
) ? 1 : 0;
93 if (ft_private
->backend_pipe
[our_pipe_end
] != 0) {
94 // Backend is responsible for closing the pipe's other end.
95 close(ft_private
->backend_pipe
[our_pipe_end
]);
98 g_free(ft_private
->file_name
);
99 g_free(ft_private
->sdp
);
100 g_free(ft_private
->id
);
102 if (ft_private
->backend_pipe_write_source_id
) {
103 g_source_remove(ft_private
->backend_pipe_write_source_id
);
110 send_ms_filetransfer_msg(char *body
, struct sipe_file_transfer_lync
*ft_private
,
111 TransCallback callback
)
113 sip_transport_info(sipe_media_get_sipe_core_private(ft_private
->call
),
114 "Content-Type: application/ms-filetransfer+xml\r\n",
116 sipe_media_get_sip_dialog(ft_private
->call
),
123 send_ms_filetransfer_response(struct sipe_file_transfer_lync
*ft_private
,
124 const gchar
*code
, const gchar
*reason
,
125 TransCallback callback
)
127 static const gchar
*RESPONSE_STR
=
128 "<response xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" requestId=\"%d\" code=\"%s\" %s%s%s/>";
130 send_ms_filetransfer_msg(g_strdup_printf(RESPONSE_STR
,
131 ft_private
->request_id
, code
,
132 reason
? "reason=\"" : "",
133 reason
? reason
: "",
135 ft_private
, callback
);
139 mime_mixed_cb(gpointer user_data
, const GSList
*fields
, const gchar
*body
,
142 struct sipe_file_transfer_lync
*ft_private
= user_data
;
143 const gchar
*ctype
= sipe_utils_nameval_find(fields
, "Content-Type");
145 /* Lync 2010 file transfer */
146 if (g_str_has_prefix(ctype
, "application/ms-filetransfer+xml")) {
147 sipe_xml
*xml
= sipe_xml_parse(body
, length
);
150 const sipe_xml
*node
;
152 ft_private
->request_id
= sipe_xml_int_attribute(xml
,
154 ft_private
->request_id
);
156 node
= sipe_xml_child(xml
, "publishFile/fileInfo/name");
158 g_free(ft_private
->file_name
);
159 ft_private
->file_name
= sipe_xml_data(node
);
162 node
= sipe_xml_child(xml
, "publishFile/fileInfo/id");
164 g_free(ft_private
->id
);
165 ft_private
->id
= sipe_xml_data(node
);
168 node
= sipe_xml_child(xml
, "publishFile/fileInfo/size");
170 gchar
*size_str
= sipe_xml_data(node
);
172 ft_private
->file_size
= atoi(size_str
);
179 } else if (g_str_has_prefix(ctype
, "application/sdp")) {
180 g_free(ft_private
->sdp
);
181 ft_private
->sdp
= g_strndup(body
, length
);
186 candidate_pairs_established_cb(struct sipe_media_stream
*stream
)
188 struct sipe_file_transfer_lync
*ft_private
;
189 static const gchar
*DOWNLOAD_FILE_REQUEST
=
190 "<request xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" requestId=\"%d\">"
199 g_return_if_fail(sipe_strequal(stream
->id
, "data"));
201 ft_private
= sipe_media_stream_get_data(stream
);
203 send_ms_filetransfer_response(ft_private
, "success", NULL
, NULL
);
205 send_ms_filetransfer_msg(g_strdup_printf(DOWNLOAD_FILE_REQUEST
,
206 ++ft_private
->request_id
,
208 ft_private
->file_name
),
213 create_pipe(int pipefd
[2])
216 #error "Pipes not implemented for Windows"
217 /* Those interested in porting the code may use Pidgin's wpurple_input_pipe() in
218 * win32dep.c as an inspiration. */
220 if (pipe(pipefd
) != 0) {
224 /* @TODO: ignoring potential error return - how to handle? */
225 (void) fcntl(pipefd
[0], F_SETFL
, fcntl(pipefd
[0], F_GETFL
) | O_NONBLOCK
);
226 (void) fcntl(pipefd
[1], F_SETFL
, fcntl(pipefd
[1], F_GETFL
) | O_NONBLOCK
);
233 xdata_start_of_stream_cb(struct sipe_media_stream
*stream
,
234 guint8
*buffer
, gsize len
)
236 struct sipe_file_transfer_lync
*ft_private
=
237 sipe_media_stream_get_data(stream
);
238 struct sipe_backend_fd
*fd
;
241 SIPE_DEBUG_INFO("Received new stream for requestId : %s", buffer
);
243 if (!create_pipe(ft_private
->backend_pipe
)) {
244 SIPE_DEBUG_ERROR_NOFORMAT("Couldn't create backend pipe");
245 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER
);
249 fd
= sipe_backend_fd_from_int(ft_private
->backend_pipe
[0]);
250 sipe_backend_ft_start(SIPE_FILE_TRANSFER
, fd
, NULL
, 0);
251 sipe_backend_fd_free(fd
);
255 xdata_end_of_stream_cb(SIPE_UNUSED_PARAMETER
struct sipe_media_stream
*stream
,
256 guint8
*buffer
, gsize len
)
259 SIPE_DEBUG_INFO("Received end of stream for requestId : %s", buffer
);
263 xdata_got_header_cb(struct sipe_media_stream
*stream
,
265 SIPE_UNUSED_PARAMETER gsize len
)
267 struct sipe_file_transfer_lync
*ft_private
=
268 sipe_media_stream_get_data(stream
);
270 guint8 type
= buffer
[0];
271 guint16 size
= (buffer
[1] << 8) + buffer
[2]; /* stored as big-endian */
274 case SIPE_XDATA_START_OF_STREAM
:
275 sipe_media_stream_read_async(stream
,
276 ft_private
->buffer
, size
,
277 xdata_start_of_stream_cb
);
279 case SIPE_XDATA_DATA_CHUNK
:
280 SIPE_DEBUG_INFO("Received new data chunk of size %d",
282 ft_private
->bytes_left_in_chunk
= size
;
284 /* We'll read the data when read_cb is called again. */
285 case SIPE_XDATA_END_OF_STREAM
:
286 sipe_media_stream_read_async(stream
,
287 ft_private
->buffer
, size
,
288 xdata_end_of_stream_cb
);
294 read_cb(struct sipe_media_stream
*stream
)
296 struct sipe_file_transfer_lync
*ft_private
=
297 sipe_media_stream_get_data(stream
);
299 if (ft_private
->buffer_read_pos
< ft_private
->buffer_len
) {
300 /* Have data in buffer, write them to the backend. */
306 buffer
= ft_private
->buffer
+ ft_private
->buffer_read_pos
;
307 len
= ft_private
->buffer_len
- ft_private
->buffer_read_pos
;
308 written
= write(ft_private
->backend_pipe
[1], buffer
, len
);
311 ft_private
->buffer_read_pos
+= written
;
312 } else if (written
< 0 && errno
!= EAGAIN
) {
313 SIPE_DEBUG_ERROR_NOFORMAT("Error while writing into "
315 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER
);
318 } else if (ft_private
->bytes_left_in_chunk
!= 0) {
319 /* Have data from the sender, replenish our buffer with it. */
321 ft_private
->buffer_len
= MIN(ft_private
->bytes_left_in_chunk
,
322 sizeof (ft_private
->buffer
));
324 ft_private
->buffer_len
=
325 sipe_backend_media_stream_read(stream
,
327 ft_private
->buffer_len
);
329 ft_private
->bytes_left_in_chunk
-= ft_private
->buffer_len
;
330 ft_private
->buffer_read_pos
= 0;
332 SIPE_DEBUG_INFO("Read %d bytes. %d left in this chunk.",
333 ft_private
->buffer_len
, ft_private
->bytes_left_in_chunk
);
335 /* No data available. This is either stream start, beginning of
336 * chunk, or stream end. */
338 sipe_media_stream_read_async(stream
, ft_private
->buffer
,
340 xdata_got_header_cb
);
345 ft_lync_incoming_init(struct sipe_file_transfer
*ft
,
346 SIPE_UNUSED_PARAMETER
const gchar
*filename
,
347 SIPE_UNUSED_PARAMETER gsize size
,
348 SIPE_UNUSED_PARAMETER
const gchar
*who
)
350 struct sipe_media_call
*call
= SIPE_FILE_TRANSFER_PRIVATE
->call
;
353 sipe_backend_media_accept(call
->backend_private
, TRUE
);
358 ft_lync_request_denied(struct sipe_file_transfer
*ft
)
360 struct sipe_file_transfer_lync
*ft_private
= SIPE_FILE_TRANSFER_PRIVATE
;
361 struct sipe_media_call
*call
;
363 g_return_if_fail(ft_private
);
365 call
= ft_private
->call
;
367 if (call
&& call
->backend_private
) {
368 sipe_backend_media_reject(call
->backend_private
, TRUE
);
372 static struct sipe_file_transfer_lync
*
373 ft_private_from_call(struct sipe_media_call
*call
)
375 struct sipe_media_stream
*stream
=
376 sipe_core_media_get_stream_by_id(call
, "data");
377 g_return_val_if_fail(stream
, NULL
);
379 return sipe_media_stream_get_data(stream
);
383 send_transfer_progress(struct sipe_file_transfer_lync
*ft_private
)
385 static const gchar
*FILETRANSFER_PROGRESS
=
386 "<notify xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" notifyId=\"%d\">"
387 "<fileTransferProgress>"
388 "<transferId>%d</transferId>"
393 "</fileTransferProgress>"
396 send_ms_filetransfer_msg(g_strdup_printf(FILETRANSFER_PROGRESS
,
398 ft_private
->request_id
,
399 ft_private
->file_size
- 1),
404 ft_lync_end(struct sipe_file_transfer
*ft
)
406 send_transfer_progress(SIPE_FILE_TRANSFER_PRIVATE
);
412 call_reject_cb(struct sipe_media_call
*call
, gboolean local
)
414 struct sipe_file_transfer_lync
*ft_private
= ft_private_from_call(call
);
415 g_return_if_fail(ft_private
);
417 if (ft_private
->call_reject_parent_cb
) {
418 ft_private
->call_reject_parent_cb(call
, local
);
422 sipe_backend_ft_cancel_remote(&ft_private
->public);
427 ft_lync_incoming_cancelled(struct sipe_file_transfer
*ft
)
429 static const gchar
*FILETRANSFER_CANCEL_REQUEST
=
430 "<request xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" requestId=\"%d\"/>"
432 "<transferId>%d</transferId>"
440 struct sipe_file_transfer_lync
*ft_private
= SIPE_FILE_TRANSFER_PRIVATE
;
441 struct sipe_media_stream
*stream
;
443 send_ms_filetransfer_msg(g_strdup_printf(FILETRANSFER_CANCEL_REQUEST
,
444 ft_private
->request_id
+ 1,
445 ft_private
->request_id
,
447 ft_private
->file_name
),
451 stream
= sipe_core_media_get_stream_by_id(ft_private
->call
, "data");
453 stream
->read_cb
= NULL
;
456 sipe_backend_media_hangup(ft_private
->call
->backend_private
, FALSE
);
460 process_incoming_invite_ft_lync(struct sipe_core_private
*sipe_private
,
463 struct sipe_file_transfer_lync
*ft_private
;
464 struct sipe_media_stream
*stream
;
466 ft_private
= g_new0(struct sipe_file_transfer_lync
, 1);
467 sipe_mime_parts_foreach(sipmsg_find_header(msg
, "Content-Type"),
468 msg
->body
, mime_mixed_cb
, ft_private
);
470 if (!ft_private
->file_name
|| !ft_private
->file_size
|| !ft_private
->sdp
) {
471 sip_transport_response(sipe_private
, msg
, 488, "Not Acceptable Here", NULL
);
472 sipe_file_transfer_lync_free(ft_private
);
476 /* Use the selected SDP part of multipart SIP message to initialize
478 ft_private
->call
= process_incoming_invite_call(sipe_private
, msg
,
479 sdpmsg_parse_msg(ft_private
->sdp
));
480 g_free(ft_private
->sdp
);
481 ft_private
->sdp
= NULL
;
483 if (!ft_private
->call
) {
484 sip_transport_response(sipe_private
, msg
, 500, "Server Internal Error", NULL
);
485 sipe_file_transfer_lync_free(ft_private
);
489 ft_private
->public.ft_init
= ft_lync_incoming_init
;
490 ft_private
->public.ft_request_denied
= ft_lync_request_denied
;
491 ft_private
->public.ft_cancelled
= ft_lync_incoming_cancelled
;
492 ft_private
->public.ft_end
= ft_lync_end
;
494 ft_private
->call_reject_parent_cb
= ft_private
->call
->call_reject_cb
;
495 ft_private
->call
->call_reject_cb
= call_reject_cb
;
497 stream
= sipe_core_media_get_stream_by_id(ft_private
->call
, "data");
499 stream
->candidate_pairs_established_cb
= candidate_pairs_established_cb
;
500 stream
->read_cb
= read_cb
;
501 sipe_media_stream_add_extra_attribute(stream
, "recvonly", NULL
);
502 sipe_media_stream_set_data(stream
, ft_private
,
503 (GDestroyNotify
)sipe_file_transfer_lync_free
);
505 sipe_backend_ft_incoming(SIPE_CORE_PUBLIC
, SIPE_FILE_TRANSFER
,
506 ft_private
->call
->with
,
507 ft_private
->file_name
,
508 ft_private
->file_size
);
510 sip_transport_response(sipe_private
, msg
, 500, "Server Internal Error", NULL
);
511 sipe_file_transfer_lync_free(ft_private
);
517 process_response_incoming(struct sipe_file_transfer_lync
*ft_private
,
521 guint request_id
= sipe_xml_int_attribute(xml
, "requestId", 0);
523 if (request_id
!= ft_private
->request_id
) {
527 attr
= sipe_xml_attribute(xml
, "code");
528 if (sipe_strequal(attr
, "failure")) {
529 const gchar
*reason
= sipe_xml_attribute(xml
, "reason");
530 if (sipe_strequal(reason
, "requestCancelled")) {
531 sipe_backend_ft_cancel_remote(SIPE_FILE_TRANSFER
);
537 write_chunk(struct sipe_media_stream
*stream
,
538 guint8 type
, guint16 len
, const gchar
*buffer
)
540 guint16 len_be
= GUINT16_TO_BE(len
);
542 sipe_media_stream_write(stream
, &type
, sizeof (guint8
));
543 sipe_media_stream_write(stream
, (guint8
*)&len_be
, sizeof (guint16
));
544 sipe_media_stream_write(stream
, (guint8
*)buffer
, len
);
548 send_file_chunk(SIPE_UNUSED_PARAMETER GIOChannel
*source
,
549 SIPE_UNUSED_PARAMETER GIOCondition condition
,
552 struct sipe_file_transfer_lync
*ft_private
= data
;
553 struct sipe_media_call
*call
= ft_private
->call
;
554 struct sipe_media_stream
*stream
;
557 stream
= sipe_core_media_get_stream_by_id(call
, "data");
559 SIPE_DEBUG_ERROR_NOFORMAT("Couldn't find data stream");
560 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER
);
561 ft_private
->backend_pipe_write_source_id
= 0;
562 return FALSE
; /* G_SOURCE_REMOVE */
565 if (!sipe_media_stream_is_writable(stream
)) {
566 return TRUE
; /* G_SOURCE_CONTINUE */
569 bytes_read
= read(ft_private
->backend_pipe
[0],
570 ft_private
->buffer
, sizeof (ft_private
->buffer
));
571 if (bytes_read
> 0) {
572 write_chunk(stream
, SIPE_XDATA_DATA_CHUNK
,
573 bytes_read
, (const gchar
*)ft_private
->buffer
);
574 } else if (bytes_read
== 0) {
575 /* EOF, write end of stream */
576 gchar
*request_id_str
;
578 request_id_str
= g_strdup_printf("%u", ft_private
->request_id
);
579 write_chunk(stream
, SIPE_XDATA_END_OF_STREAM
,
580 strlen(request_id_str
), request_id_str
);
581 g_free(request_id_str
);
583 return FALSE
; /* G_SOURCE_REMOVE */
586 return TRUE
; /* G_SOURCE_CONTINUE */
590 start_writing(struct sipe_file_transfer_lync
*ft_private
)
592 struct sipe_media_stream
*stream
;
593 gchar
*request_id_str
;
594 struct sipe_backend_fd
*fd
;
597 stream
= sipe_core_media_get_stream_by_id(ft_private
->call
, "data");
602 if (!create_pipe(ft_private
->backend_pipe
)) {
603 SIPE_DEBUG_ERROR_NOFORMAT("Couldn't create backend pipe");
604 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER
);
608 request_id_str
= g_strdup_printf("%u", ft_private
->request_id
);
609 write_chunk(stream
, SIPE_XDATA_START_OF_STREAM
,
610 strlen(request_id_str
), request_id_str
);
611 g_free(request_id_str
);
613 channel
= g_io_channel_unix_new(ft_private
->backend_pipe
[0]);
614 ft_private
->backend_pipe_write_source_id
= g_io_add_watch(channel
,
618 g_io_channel_unref(channel
);
620 fd
= sipe_backend_fd_from_int(ft_private
->backend_pipe
[1]);
621 sipe_backend_ft_start(SIPE_FILE_TRANSFER
, fd
, NULL
, 0);
622 sipe_backend_fd_free(fd
);
626 process_request(struct sipe_file_transfer_lync
*ft_private
, sipe_xml
*xml
)
628 static const gchar
*DOWNLOAD_PENDING_RESPONSE
=
629 "<response xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" "
630 "requestId=\"%u\" code=\"pending\"/>";
632 if (sipe_xml_child(xml
, "downloadFile")) {
633 ft_private
->request_id
=
634 atoi(sipe_xml_attribute(xml
, "requestId"));
636 send_ms_filetransfer_msg(g_strdup_printf(DOWNLOAD_PENDING_RESPONSE
,
637 ft_private
->request_id
),
640 start_writing(ft_private
);
645 process_notify(struct sipe_file_transfer_lync
*ft_private
, sipe_xml
*xml
)
647 static const gchar
*DOWNLOAD_SUCCESS_RESPONSE
=
648 "<response xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" "
649 "requestId=\"%u\" code=\"success\"/>";
651 const sipe_xml
*progress_node
= sipe_xml_child(xml
, "fileTransferProgress");
654 gchar
*to_str
= sipe_xml_data(sipe_xml_child(progress_node
, "bytesReceived/to"));
656 if (atoi(to_str
) == (int)(ft_private
->file_size
- 1)) {
657 send_ms_filetransfer_msg(g_strdup_printf(DOWNLOAD_SUCCESS_RESPONSE
,
658 ft_private
->request_id
),
660 sipe_backend_media_hangup(ft_private
->call
->backend_private
, TRUE
);
667 process_incoming_info_ft_lync(struct sipe_core_private
*sipe_private
,
670 struct sipe_media_call
*call
;
671 struct sipe_file_transfer_lync
*ft_private
;
674 call
= g_hash_table_lookup(sipe_private
->media_calls
,
675 sipmsg_find_header(msg
, "Call-ID"));
680 ft_private
= ft_private_from_call(call
);
685 xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
690 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
692 if (sipe_backend_ft_is_incoming(SIPE_FILE_TRANSFER
)) {
693 if (sipe_strequal(sipe_xml_name(xml
), "response")) {
694 process_response_incoming(ft_private
, xml
);
697 if (sipe_strequal(sipe_xml_name(xml
), "request")) {
698 process_request(ft_private
, xml
);
699 } else if (sipe_strequal(sipe_xml_name(xml
), "notify")) {
700 process_notify(ft_private
, xml
);
708 append_publish_file_invite(struct sipe_media_call
*call
,
709 struct sipe_file_transfer_lync
*ft_private
)
711 static const gchar
*PUBLISH_FILE_REQUEST
=
712 "Content-Type: application/ms-filetransfer+xml\r\n"
713 "Content-Transfer-Encoding: 7bit\r\n"
714 "Content-Disposition: render; handling=optional\r\n"
716 "<request xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" "
720 "<id>{6244F934-2EB1-443F-8E2C-48BA64AF463D}</id>"
728 ft_private
->request_id
=
729 ++ft_private
->sipe_private
->ms_filetransfer_request_id
;
731 body
= g_strdup_printf(PUBLISH_FILE_REQUEST
, ft_private
->request_id
,
732 ft_private
->file_name
, ft_private
->file_size
);
734 sipe_media_add_extra_invite_section(call
, "multipart/mixed", body
);
738 ft_lync_outgoing_init(struct sipe_file_transfer
*ft
, const gchar
*filename
,
739 gsize size
, SIPE_UNUSED_PARAMETER
const gchar
*who
)
741 struct sipe_core_private
*sipe_private
=
742 SIPE_FILE_TRANSFER_PRIVATE
->sipe_private
;
743 struct sipe_file_transfer_lync
*ft_private
= SIPE_FILE_TRANSFER_PRIVATE
;
744 struct sipe_media_call
*call
;
745 struct sipe_media_stream
*stream
;
747 ft_private
->file_name
= g_strdup(filename
);
748 ft_private
->file_size
= size
;
750 call
= sipe_media_call_new(sipe_private
, who
, NULL
, SIPE_ICE_RFC_5245
,
751 SIPE_MEDIA_CALL_NO_UI
);
753 ft_private
->call
= call
;
755 ft_private
->call_reject_parent_cb
= call
->call_reject_cb
;
756 call
->call_reject_cb
= call_reject_cb
;
758 stream
= sipe_media_stream_add(call
, "data", SIPE_MEDIA_APPLICATION
,
759 SIPE_ICE_RFC_5245
, TRUE
, 0);
761 sipe_backend_notify_error(SIPE_CORE_PUBLIC
,
763 _("Error creating data stream"));
765 sipe_backend_media_hangup(call
->backend_private
, FALSE
);
766 sipe_backend_ft_cancel_local(ft
);
770 sipe_media_stream_add_extra_attribute(stream
, "sendonly", NULL
);
771 sipe_media_stream_add_extra_attribute(stream
, "mid", "1");
772 sipe_media_stream_set_data(stream
, ft
,
773 (GDestroyNotify
)sipe_file_transfer_lync_free
);
774 append_publish_file_invite(call
, ft_private
);
777 struct sipe_file_transfer
*
778 sipe_file_transfer_lync_new_outgoing(struct sipe_core_private
*sipe_private
)
780 struct sipe_file_transfer_lync
*ft_private
;
782 ft_private
= g_new0(struct sipe_file_transfer_lync
, 1);
784 ft_private
->sipe_private
= sipe_private
;
785 ft_private
->public.ft_init
= ft_lync_outgoing_init
;
787 return SIPE_FILE_TRANSFER
;