filetransfer: use asynchronous stream reads
[siplcs.git] / src / core / sipe-ft-lync.c
blob8dc659f48a8f5fc1a1480b543c356c3767724195
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 #define SIPE_FILE_TRANSFER ((struct sipe_file_transfer *) ft_private)
69 #define SIPE_FILE_TRANSFER_PRIVATE ((struct sipe_file_transfer_lync *) ft)
71 typedef enum {
72 SIPE_XDATA_DATA_CHUNK = 0x00,
73 SIPE_XDATA_START_OF_STREAM = 0x01,
74 SIPE_XDATA_END_OF_STREAM = 0x02
75 } SipeXDataMessages;
77 #define XDATA_HEADER_SIZE sizeof (guint8) + sizeof (guint16)
79 static void
80 sipe_file_transfer_lync_free(struct sipe_file_transfer_lync *ft_private)
82 if (ft_private->backend_pipe[1] != 0) {
83 // Backend is responsible for closing the pipe's read end.
84 close(ft_private->backend_pipe[1]);
87 g_free(ft_private->file_name);
88 g_free(ft_private->sdp);
89 g_free(ft_private->id);
90 g_free(ft_private);
93 static void
94 send_ms_filetransfer_msg(char *body, struct sipe_file_transfer_lync *ft_private,
95 TransCallback callback)
97 sip_transport_info(sipe_media_get_sipe_core_private(ft_private->call),
98 "Content-Type: application/ms-filetransfer+xml\r\n",
99 body,
100 sipe_media_get_sip_dialog(ft_private->call),
101 callback);
103 g_free(body);
106 static void
107 send_ms_filetransfer_response(struct sipe_file_transfer_lync *ft_private,
108 const gchar *code, const gchar *reason,
109 TransCallback callback)
111 static const gchar *RESPONSE_STR =
112 "<response xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" requestId=\"%d\" code=\"%s\" %s%s%s/>";
114 send_ms_filetransfer_msg(g_strdup_printf(RESPONSE_STR,
115 ft_private->request_id, code,
116 reason ? "reason=\"" : "",
117 reason ? reason : "",
118 reason ? "\"" : ""),
119 ft_private, callback);
122 static void
123 mime_mixed_cb(gpointer user_data, const GSList *fields, const gchar *body,
124 gsize length)
126 struct sipe_file_transfer_lync *ft_private = user_data;
127 const gchar *ctype = sipe_utils_nameval_find(fields, "Content-Type");
129 /* Lync 2010 file transfer */
130 if (g_str_has_prefix(ctype, "application/ms-filetransfer+xml")) {
131 sipe_xml *xml = sipe_xml_parse(body, length);
132 const sipe_xml *node;
134 const gchar *request_id_str = sipe_xml_attribute(xml, "requestId");
135 if (request_id_str) {
136 ft_private->request_id = atoi(request_id_str);
139 node = sipe_xml_child(xml, "publishFile/fileInfo/name");
140 if (node) {
141 ft_private->file_name = sipe_xml_data(node);
144 node = sipe_xml_child(xml, "publishFile/fileInfo/id");
145 if (node) {
146 ft_private->id = sipe_xml_data(node);
149 node = sipe_xml_child(xml, "publishFile/fileInfo/size");
150 if (node) {
151 gchar *size_str = sipe_xml_data(node);
152 if (size_str) {
153 ft_private->file_size = atoi(size_str);
154 g_free(size_str);
157 } else if (g_str_has_prefix(ctype, "application/sdp")) {
158 ft_private->sdp = g_strndup(body, length);
162 static void
163 candidate_pair_established_cb(SIPE_UNUSED_PARAMETER struct sipe_media_call *call,
164 struct sipe_media_stream *stream)
166 struct sipe_file_transfer_lync *ft_private;
167 static const gchar *DOWNLOAD_FILE_REQUEST =
168 "<request xmlns=\"http://schemas.microsoft.com/rtc/2009/05/filetransfer\" requestId=\"%d\">"
169 "<downloadFile>"
170 "<fileInfo>"
171 "<id>%s</id>"
172 "<name>%s</name>"
173 "</fileInfo>"
174 "</downloadFile>"
175 "</request>";
177 g_return_if_fail(sipe_strequal(stream->id, "data"));
179 ft_private = sipe_media_stream_get_data(stream);
181 send_ms_filetransfer_response(ft_private, "success", NULL, NULL);
183 send_ms_filetransfer_msg(g_strdup_printf(DOWNLOAD_FILE_REQUEST,
184 ++ft_private->request_id,
185 ft_private->id,
186 ft_private->file_name),
187 ft_private, NULL);
190 static gboolean
191 create_pipe(int pipefd[2])
193 #ifdef _WIN32
194 #error "Pipes not implemented for Windows"
195 /* Those interested in porting the code may use Pidgin's wpurple_input_pipe() in
196 * win32dep.c as an inspiration. */
197 #else
198 if (pipe(pipefd) != 0) {
199 return FALSE;
202 fcntl(pipefd[0], F_SETFL, fcntl(pipefd[0], F_GETFL) | O_NONBLOCK);
203 fcntl(pipefd[1], F_SETFL, fcntl(pipefd[1], F_GETFL) | O_NONBLOCK);
205 return TRUE;
206 #endif
209 static void
210 xdata_start_of_stream_cb(struct sipe_media_stream *stream,
211 guint8 *buffer, gsize len)
213 struct sipe_file_transfer_lync *ft_private =
214 sipe_media_stream_get_data(stream);
215 struct sipe_backend_fd *fd;
217 buffer[len] = 0;
218 SIPE_DEBUG_INFO("Received new stream for requestId : %s", buffer);
220 if (!create_pipe(ft_private->backend_pipe)) {
221 SIPE_DEBUG_ERROR_NOFORMAT("Couldn't create backend pipe");
222 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER);
223 return;
226 fd = sipe_backend_fd_from_int(ft_private->backend_pipe[0]);
227 sipe_backend_ft_start(SIPE_FILE_TRANSFER, fd, NULL, 0);
228 sipe_backend_fd_free(fd);
231 static void
232 xdata_end_of_stream_cb(SIPE_UNUSED_PARAMETER struct sipe_media_stream *stream,
233 guint8 *buffer, gsize len)
235 buffer[len] = 0;
236 SIPE_DEBUG_INFO("Received end of stream for requestId : %s", buffer);
239 static void
240 xdata_got_header_cb(struct sipe_media_stream *stream,
241 guint8 *buffer,
242 SIPE_UNUSED_PARAMETER gsize len)
244 struct sipe_file_transfer_lync *ft_private =
245 sipe_media_stream_get_data(stream);
247 guint8 type = buffer[0];
248 guint16 size = GUINT16_FROM_BE(*(guint16 *)(buffer + sizeof (guint8)));
250 switch (type) {
251 case SIPE_XDATA_START_OF_STREAM:
252 sipe_media_stream_read_async(stream,
253 ft_private->buffer, size,
254 xdata_start_of_stream_cb);
255 break;
256 case SIPE_XDATA_DATA_CHUNK:
257 SIPE_DEBUG_INFO("Received new data chunk of size %d",
258 size);
259 ft_private->bytes_left_in_chunk = size;
260 break;
261 /* We'll read the data when read_cb is called again. */
262 case SIPE_XDATA_END_OF_STREAM:
263 sipe_media_stream_read_async(stream,
264 ft_private->buffer, size,
265 xdata_end_of_stream_cb);
266 break;
270 static void
271 read_cb(struct sipe_media_stream *stream)
273 struct sipe_file_transfer_lync *ft_private =
274 sipe_media_stream_get_data(stream);
276 if (ft_private->buffer_read_pos < ft_private->buffer_len) {
277 /* Have data in buffer, write them to the backend. */
279 gpointer buffer;
280 size_t len;
281 ssize_t written;
283 buffer = ft_private->buffer + ft_private->buffer_read_pos;
284 len = ft_private->buffer_len - ft_private->buffer_read_pos;
285 written = write(ft_private->backend_pipe[1], buffer, len);
287 if (written > 0) {
288 ft_private->buffer_read_pos += written;
289 } else if (written < 0 && errno != EAGAIN) {
290 SIPE_DEBUG_ERROR_NOFORMAT("Error while writing into "
291 "backend pipe");
292 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER);
293 return;
295 } else if (ft_private->bytes_left_in_chunk != 0) {
296 /* Have data from the sender, replenish our buffer with it. */
298 ft_private->buffer_len = MIN(ft_private->bytes_left_in_chunk,
299 sizeof (ft_private->buffer));
301 ft_private->buffer_len =
302 sipe_backend_media_stream_read(stream,
303 ft_private->buffer,
304 ft_private->buffer_len);
306 ft_private->bytes_left_in_chunk -= ft_private->buffer_len;
307 ft_private->buffer_read_pos = 0;
309 SIPE_DEBUG_INFO("Read %d bytes. %d left in this chunk.",
310 ft_private->buffer_len, ft_private->bytes_left_in_chunk);
311 } else {
312 /* No data available. This is either stream start, beginning of
313 * chunk, or stream end. */
315 sipe_media_stream_read_async(stream, ft_private->buffer,
316 XDATA_HEADER_SIZE,
317 xdata_got_header_cb);
321 static void
322 ft_lync_incoming_init(struct sipe_file_transfer *ft,
323 SIPE_UNUSED_PARAMETER const gchar *filename,
324 SIPE_UNUSED_PARAMETER gsize size,
325 SIPE_UNUSED_PARAMETER const gchar *who)
327 struct sipe_media_call *call = SIPE_FILE_TRANSFER_PRIVATE->call;
329 if (call) {
330 sipe_backend_media_accept(call->backend_private, TRUE);
334 static void
335 ft_lync_deallocate(struct sipe_file_transfer *ft)
337 struct sipe_media_call *call = SIPE_FILE_TRANSFER_PRIVATE->call;
339 if (call) {
340 sipe_backend_media_hangup(call->backend_private, TRUE);
342 sipe_file_transfer_lync_free(SIPE_FILE_TRANSFER_PRIVATE);
345 void
346 process_incoming_invite_ft_lync(struct sipe_core_private *sipe_private,
347 struct sipmsg *msg)
349 struct sipe_file_transfer_lync *ft_private;
350 struct sipe_media_call *call;
351 struct sipe_media_stream *stream;
353 ft_private = g_new0(struct sipe_file_transfer_lync, 1);
354 sipe_mime_parts_foreach(sipmsg_find_header(msg, "Content-Type"),
355 msg->body, mime_mixed_cb, ft_private);
357 if (!ft_private->file_name || !ft_private->file_size || !ft_private->sdp) {
358 sip_transport_response(sipe_private, msg, 488, "Not Acceptable Here", NULL);
359 sipe_file_transfer_lync_free(ft_private);
360 return;
363 /* Replace multipart message body with the selected SDP part and
364 * initialize media session as if invited to a media call. */
365 g_free(msg->body);
366 msg->body = ft_private->sdp;
367 msg->bodylen = strlen(msg->body);
368 ft_private->sdp = NULL;
370 ft_private->call = process_incoming_invite_call(sipe_private, msg);
371 if (!ft_private->call) {
372 sip_transport_response(sipe_private, msg, 500, "Server Internal Error", NULL);
373 sipe_file_transfer_lync_free(ft_private);
374 return;
377 call = ft_private->call;
378 call->candidate_pair_established_cb = candidate_pair_established_cb;
380 ft_private->public.ft_init = ft_lync_incoming_init;
381 ft_private->public.ft_deallocate = ft_lync_deallocate;
383 stream = sipe_core_media_get_stream_by_id(call, "data");
384 stream->read_cb = read_cb;
385 sipe_media_stream_add_extra_attribute(stream, "recvonly", NULL);
386 sipe_media_stream_set_data(stream, ft_private, NULL);
388 sipe_backend_ft_incoming(SIPE_CORE_PUBLIC, SIPE_FILE_TRANSFER,
389 call->with, ft_private->file_name,
390 ft_private->file_size);
394 Local Variables:
395 mode: c
396 c-file-style: "bsd"
397 indent-tabs-mode: t
398 tab-width: 8
399 End: