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
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"
49 struct sipe_file_transfer_lync
{
50 struct sipe_file_transfer
public;
58 guint bytes_left_in_chunk
;
62 guint buffer_read_pos
;
66 struct sipe_media_call
*call
;
68 void (*call_reject_parent_cb
)(struct sipe_media_call
*call
,
71 #define SIPE_FILE_TRANSFER ((struct sipe_file_transfer *) ft_private)
72 #define SIPE_FILE_TRANSFER_PRIVATE ((struct sipe_file_transfer_lync *) ft)
75 SIPE_XDATA_DATA_CHUNK
= 0x00,
76 SIPE_XDATA_START_OF_STREAM
= 0x01,
77 SIPE_XDATA_END_OF_STREAM
= 0x02
80 #define XDATA_HEADER_SIZE sizeof (guint8) + sizeof (guint16)
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
);
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",
103 sipe_media_get_sip_dialog(ft_private
->call
),
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
: "",
122 ft_private
, callback
);
126 mime_mixed_cb(gpointer user_data
, const GSList
*fields
, const gchar
*body
,
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
);
137 const sipe_xml
*node
;
139 ft_private
->request_id
= sipe_xml_int_attribute(xml
,
141 ft_private
->request_id
);
143 node
= sipe_xml_child(xml
, "publishFile/fileInfo/name");
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");
151 g_free(ft_private
->id
);
152 ft_private
->id
= sipe_xml_data(node
);
155 node
= sipe_xml_child(xml
, "publishFile/fileInfo/size");
157 gchar
*size_str
= sipe_xml_data(node
);
159 ft_private
->file_size
= atoi(size_str
);
166 } else if (g_str_has_prefix(ctype
, "application/sdp")) {
167 g_free(ft_private
->sdp
);
168 ft_private
->sdp
= g_strndup(body
, length
);
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\">"
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
,
195 ft_private
->file_name
),
200 create_pipe(int pipefd
[2])
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. */
207 if (pipe(pipefd
) != 0) {
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
);
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
;
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
);
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
);
241 xdata_end_of_stream_cb(SIPE_UNUSED_PARAMETER
struct sipe_media_stream
*stream
,
242 guint8
*buffer
, gsize len
)
245 SIPE_DEBUG_INFO("Received end of stream for requestId : %s", buffer
);
249 xdata_got_header_cb(struct sipe_media_stream
*stream
,
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
)));
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
);
265 case SIPE_XDATA_DATA_CHUNK
:
266 SIPE_DEBUG_INFO("Received new data chunk of size %d",
268 ft_private
->bytes_left_in_chunk
= size
;
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
);
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. */
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
);
297 ft_private
->buffer_read_pos
+= written
;
298 } else if (written
< 0 && errno
!= EAGAIN
) {
299 SIPE_DEBUG_ERROR_NOFORMAT("Error while writing into "
301 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER
);
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
,
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
);
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
,
326 xdata_got_header_cb
);
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
;
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
);
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>"
364 "</fileTransferProgress>"
367 send_ms_filetransfer_msg(g_strdup_printf(FILETRANSFER_PROGRESS
,
369 ft_private
->request_id
,
370 ft_private
->file_size
- 1),
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
;
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
);
398 sipe_backend_ft_cancel_remote(&ft_private
->public);
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\"/>"
408 "<transferId>%d</transferId>"
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
,
423 ft_private
->file_name
),
427 stream
= sipe_core_media_get_stream_by_id(ft_private
->call
, "data");
429 stream
->read_cb
= NULL
;
434 ft_lync_deallocate(struct sipe_file_transfer
*ft
)
436 struct sipe_media_call
*call
= SIPE_FILE_TRANSFER_PRIVATE
->call
;
439 sipe_backend_media_hangup(call
->backend_private
, TRUE
);
444 process_incoming_invite_ft_lync(struct sipe_core_private
*sipe_private
,
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
);
461 /* Replace multipart message body with the selected SDP part and
462 * initialize media session as if invited to a media call. */
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
);
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
);
498 process_response_incoming(struct sipe_file_transfer_lync
*ft_private
,
502 guint request_id
= sipe_xml_int_attribute(xml
, "requestId", 0);
504 if (request_id
!= ft_private
->request_id
) {
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
);
518 process_incoming_info_ft_lync(struct sipe_core_private
*sipe_private
,
521 struct sipe_media_call
*call
;
522 struct sipe_file_transfer_lync
*ft_private
;
525 call
= g_hash_table_lookup(sipe_private
->media_calls
,
526 sipmsg_find_header(msg
, "Call-ID"));
531 ft_private
= ft_private_from_call(call
);
536 xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
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
);