comparison libpurple/protocols/jabber/ibb.c @ 23999:b4ec5481a67a

Implements file transfers using in-band bytestreams for XMPP using XEP-0047 Refs #6183
author Marcus Lundblad <ml@update.uu.se>
date Sat, 06 Sep 2008 07:49:05 +0000
parents
children dc01f9b0aaa3
comparison
equal deleted inserted replaced
23998:ac3db6ba7078 23999:b4ec5481a67a
1 /*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU Library General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA
15 */
16
17 #include "ibb.h"
18 #include "debug.h"
19 #include "xmlnode.h"
20
21 #include <glib.h>
22 #include <string.h>
23
24 #define JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE 4096
25
26 static GHashTable *jabber_ibb_sessions = NULL;
27 static GList *open_handlers = NULL;
28
29 JabberIBBSession *
30 jabber_ibb_session_create(JabberStream *js, const gchar *sid, const gchar *who,
31 gpointer user_data)
32 {
33 JabberIBBSession *sess = g_new0(JabberIBBSession, 1);
34
35 if (!sess) {
36 purple_debug_error("jabber", "Could not allocate IBB session object\n");
37 return NULL;
38 }
39
40 sess->js = js;
41 if (sid) {
42 sess->sid = g_strdup(sid);
43 } else {
44 sess->sid = g_strdup(jabber_get_next_id(js));
45 }
46 sess->who = g_strdup(who);
47 sess->block_size = JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE;
48 sess->state = JABBER_IBB_SESSION_NOT_OPENED;
49 sess->user_data = user_data;
50
51 g_hash_table_insert(jabber_ibb_sessions, sess->sid, sess);
52
53 return sess;
54 }
55
56 JabberIBBSession *
57 jabber_ibb_session_create_from_xmlnode(JabberStream *js, xmlnode *packet,
58 gpointer user_data)
59 {
60 JabberIBBSession *sess = NULL;
61 xmlnode *open = xmlnode_get_child_with_namespace(packet, "open",
62 XEP_0047_NAMESPACE);
63 const gchar *sid = xmlnode_get_attrib(open, "sid");
64 const gchar *block_size = xmlnode_get_attrib(open, "block-size");
65 gsize block_size_int;
66
67 if (!open) {
68 return NULL;
69 }
70
71 sess = g_new0(JabberIBBSession, 1);
72
73 if (!sess) {
74 purple_debug_error("jabber", "Could not allocate IBB session object\n");
75 return NULL;
76 }
77
78 if (!sid || !block_size) {
79 purple_debug_error("jabber",
80 "IBB session open tag requires sid and block-size attributes\n");
81 g_free(sess);
82 return NULL;
83 }
84
85 block_size_int = atoi(block_size);
86 sess->js = js;
87 sess->sid = g_strdup(sid);
88 sess->id = g_strdup(xmlnode_get_attrib(packet, "id"));
89 sess->who = g_strdup(xmlnode_get_attrib(packet, "from"));
90 sess->block_size = block_size_int;
91 /* if we create a session from an incoming <open/> request, it means the
92 session is immediatly open... */
93 sess->state = JABBER_IBB_SESSION_OPENED;
94 sess->user_data = user_data;
95
96 g_hash_table_insert(jabber_ibb_sessions, sess->sid, sess);
97
98 return sess;
99 }
100
101 void
102 jabber_ibb_session_destroy(JabberIBBSession *sess)
103 {
104 purple_debug_info("jabber", "IBB: destroying session %lx %s\n", sess,
105 sess->sid);
106
107 if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) {
108 jabber_ibb_session_close(sess);
109 }
110
111 purple_debug_info("jabber", "IBB: last_iq_id: %lx\n", sess->last_iq_id);
112 if (sess->last_iq_id) {
113 purple_debug_info("jabber", "IBB: removing callback for <iq/> %s\n",
114 sess->last_iq_id);
115 jabber_iq_remove_callback_by_id(jabber_ibb_session_get_js(sess),
116 sess->last_iq_id);
117 g_free(sess->last_iq_id);
118 sess->last_iq_id = NULL;
119 }
120
121 g_hash_table_remove(jabber_ibb_sessions, sess->sid);
122 g_free(sess->sid);
123 g_free(sess->who);
124 g_free(sess);
125 }
126
127 const gchar *
128 jabber_ibb_session_get_sid(const JabberIBBSession *sess)
129 {
130 return sess->sid;
131 }
132
133 JabberStream *
134 jabber_ibb_session_get_js(JabberIBBSession *sess)
135 {
136 return sess->js;
137 }
138
139 const gchar *
140 jabber_ibb_session_get_who(const JabberIBBSession *sess)
141 {
142 return sess->who;
143 }
144
145 guint16
146 jabber_ibb_session_get_send_seq(const JabberIBBSession *sess)
147 {
148 return sess->send_seq;
149 }
150
151 guint16
152 jabber_ibb_session_get_recv_seq(const JabberIBBSession *sess)
153 {
154 return sess->recv_seq;
155 }
156
157 JabberIBBSessionState
158 jabber_ibb_session_get_state(const JabberIBBSession *sess)
159 {
160 return sess->state;
161 }
162
163 gsize
164 jabber_ibb_session_get_block_size(const JabberIBBSession *sess)
165 {
166 return sess->block_size;
167 }
168
169 void
170 jabber_ibb_session_set_block_size(JabberIBBSession *sess, gsize size)
171 {
172 if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_NOT_OPENED) {
173 sess->block_size = size;
174 } else {
175 purple_debug_error("jabber",
176 "Can't set block size on an open IBB session\n");
177 }
178 }
179
180 gpointer
181 jabber_ibb_session_get_user_data(JabberIBBSession *sess)
182 {
183 return sess->user_data;
184 }
185
186 void
187 jabber_ibb_session_set_opened_callback(JabberIBBSession *sess,
188 JabberIBBOpenedCallback *cb)
189 {
190 sess->opened_cb = cb;
191 }
192
193 void
194 jabber_ibb_session_set_data_sent_callback(JabberIBBSession *sess,
195 JabberIBBSentCallback *cb)
196 {
197 sess->data_sent_cb = cb;
198 }
199
200 void
201 jabber_ibb_session_set_closed_callback(JabberIBBSession *sess,
202 JabberIBBClosedCallback *cb)
203 {
204 sess->closed_cb = cb;
205 }
206
207 void
208 jabber_ibb_session_set_data_received_callback(JabberIBBSession *sess,
209 JabberIBBDataCallback *cb)
210 {
211 sess->data_received_cb = cb;
212 }
213
214 void
215 jabber_ibb_session_set_error_callback(JabberIBBSession *sess,
216 JabberIBBErrorCallback *cb)
217 {
218 sess->error_cb = cb;
219 }
220
221 static void
222 jabber_ibb_session_opened_cb(JabberStream *js, xmlnode *packet, gpointer data)
223 {
224 JabberIBBSession *sess = (JabberIBBSession *) data;
225
226 sess->state = JABBER_IBB_SESSION_OPENED;
227
228 if (sess->opened_cb) {
229 sess->opened_cb(sess);
230 }
231 }
232
233 void
234 jabber_ibb_session_open(JabberIBBSession *sess)
235 {
236 if (jabber_ibb_session_get_state(sess) != JABBER_IBB_SESSION_NOT_OPENED) {
237 purple_debug_error("jabber",
238 "jabber_ibb_session called on an already open stream\n");
239 } else {
240 JabberIq *set = jabber_iq_new(sess->js, JABBER_IQ_SET);
241 xmlnode *open = xmlnode_new("open");
242 gchar block_size[10];
243
244 xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
245 xmlnode_set_namespace(open, XEP_0047_NAMESPACE);
246 xmlnode_set_attrib(open, "sid", jabber_ibb_session_get_sid(sess));
247 g_snprintf(block_size, sizeof(block_size), "%ld",
248 jabber_ibb_session_get_block_size(sess));
249 xmlnode_set_attrib(open, "block-size", block_size);
250 xmlnode_insert_child(set->node, open);
251
252 jabber_iq_set_callback(set, jabber_ibb_session_opened_cb, sess);
253
254 jabber_iq_send(set);
255 }
256 }
257
258 void
259 jabber_ibb_session_close(JabberIBBSession *sess)
260 {
261 JabberIBBSessionState state = jabber_ibb_session_get_state(sess);
262
263 if (state != JABBER_IBB_SESSION_OPENED && state != JABBER_IBB_SESSION_ERROR) {
264 purple_debug_error("jabber",
265 "jabber_ibb_session_close called on a session that has not been"
266 "opened\n");
267 } else {
268 JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess),
269 JABBER_IQ_SET);
270 xmlnode *close = xmlnode_new("close");
271
272 xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
273 xmlnode_set_namespace(close, XEP_0047_NAMESPACE);
274 xmlnode_set_attrib(close, "sid", jabber_ibb_session_get_sid(sess));
275 xmlnode_insert_child(set->node, close);
276 jabber_iq_send(set);
277 sess->state = JABBER_IBB_SESSION_CLOSED;
278 }
279 }
280
281 void
282 jabber_ibb_session_accept(JabberIBBSession *sess)
283 {
284 JabberIq *result = jabber_iq_new(jabber_ibb_session_get_js(sess),
285 JABBER_IQ_RESULT);
286
287 xmlnode_set_attrib(result->node, "to", jabber_ibb_session_get_who(sess));
288 jabber_iq_set_id(result, sess->id);
289 jabber_iq_send(result);
290 sess->state = JABBER_IBB_SESSION_OPENED;
291 }
292
293 static void
294 jabber_ibb_session_send_acknowledge_cb(JabberStream *js, xmlnode *packet, gpointer data)
295 {
296 JabberIBBSession *sess = (JabberIBBSession *) data;
297 xmlnode *error = xmlnode_get_child(packet, "error");
298
299 if (sess) {
300 /* reset callback */
301 if (sess->last_iq_id) {
302 g_free(sess->last_iq_id);
303 sess->last_iq_id = NULL;
304 }
305
306 if (error) {
307 jabber_ibb_session_close(sess);
308 sess->state = JABBER_IBB_SESSION_ERROR;
309
310 if (sess->error_cb) {
311 sess->error_cb(sess);
312 }
313 } else {
314 if (sess->data_sent_cb) {
315 sess->data_sent_cb(sess);
316 }
317 }
318 } else {
319 /* the session has gone away, it was probably cancelled */
320 purple_debug_info("jabber",
321 "got response from send data, but IBB session is no longer active\n");
322 }
323 }
324
325 void
326 jabber_ibb_session_send_data(JabberIBBSession *sess, gpointer data, gsize size)
327 {
328 JabberIBBSessionState state = jabber_ibb_session_get_state(sess);
329
330 if (state != JABBER_IBB_SESSION_OPENED) {
331 purple_debug_error("jabber",
332 "trying to send data on a non-open IBB session\n");
333 } else if (size > jabber_ibb_session_get_block_size(sess)) {
334 purple_debug_error("jabber",
335 "trying to send a too large packet in the IBB session\n");
336 } else {
337 JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess),
338 JABBER_IQ_SET);
339 xmlnode *data_element = xmlnode_new("data");
340 char *base64 = purple_base64_encode(data, size);
341 char seq[10];
342 g_snprintf(seq, sizeof(seq), "%d", jabber_ibb_session_get_send_seq(sess));
343
344 xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
345 xmlnode_set_namespace(data_element, XEP_0047_NAMESPACE);
346 xmlnode_set_attrib(data_element, "sid", jabber_ibb_session_get_sid(sess));
347 xmlnode_set_attrib(data_element, "seq", seq);
348 xmlnode_insert_data(data_element, base64, strlen(base64));
349
350 xmlnode_insert_child(set->node, data_element);
351
352 purple_debug_info("jabber",
353 "IBB: setting send <iq/> callback for session %lx %s\n", sess,
354 sess->sid);
355 jabber_iq_set_callback(set, jabber_ibb_session_send_acknowledge_cb, sess);
356 sess->last_iq_id = g_strdup(xmlnode_get_attrib(set->node, "id"));
357 purple_debug_info("jabber", "IBB: set sess->last_iq_id: %lx %lx\n",
358 sess->last_iq_id, xmlnode_get_attrib(set->node, "id"));
359 jabber_iq_send(set);
360
361 g_free(base64);
362 (sess->send_seq)++;
363 }
364 }
365
366 void
367 jabber_ibb_parse(JabberStream *js, xmlnode *packet)
368 {
369 xmlnode *data = xmlnode_get_child_with_namespace(packet, "data",
370 XEP_0047_NAMESPACE);
371 xmlnode *close = xmlnode_get_child_with_namespace(packet, "close",
372 XEP_0047_NAMESPACE);
373 xmlnode *open = xmlnode_get_child_with_namespace(packet, "open",
374 XEP_0047_NAMESPACE);
375 const gchar *sid =
376 data ? xmlnode_get_attrib(data, "sid") :
377 close ? xmlnode_get_attrib(close, "sid") : NULL;
378 JabberIBBSession *sess =
379 sid ? g_hash_table_lookup(jabber_ibb_sessions, sid) : NULL;
380 const gchar *who = xmlnode_get_attrib(packet, "from");
381
382 if (sess) {
383
384 if (strcmp(who, jabber_ibb_session_get_who(sess)) != 0) {
385 /* the iq comes from a different JID than the remote JID of the
386 session, ignore it */
387 purple_debug_error("jabber",
388 "Got IBB iq from wrong JID, ignoring\n");
389 } else if (data) {
390 guint16 seq = atoi(xmlnode_get_attrib(data, "seq"));
391
392 /* reject the data, and set the session in error if we get an
393 out-of-order packet */
394 if (seq == jabber_ibb_session_get_recv_seq(sess)) {
395 /* sequence # is the expected... */
396 JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT);
397
398 jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
399 xmlnode_set_attrib(result->node, "to",
400 xmlnode_get_attrib(packet, "from"));
401
402 if (sess->data_received_cb) {
403 gchar *base64 = xmlnode_get_data(data);
404 gsize size;
405 gpointer rawdata = purple_base64_decode(base64, &size);
406
407 g_free(base64);
408
409 if (rawdata) {
410 if (size > jabber_ibb_session_get_block_size(sess)) {
411 purple_debug_error("jabber",
412 "IBB: received a too large packet\n");
413 } else {
414 sess->data_received_cb(sess, rawdata, size);
415 }
416 g_free(rawdata);
417 } else {
418 purple_debug_error("jabber",
419 "IBB: invalid BASE64 data received\n");
420 }
421 }
422
423 (sess->recv_seq)++;
424 jabber_iq_send(result);
425
426 } else {
427 purple_debug_error("jabber",
428 "Received an out-of-order IBB packet\n");
429 sess->state = JABBER_IBB_SESSION_ERROR;
430
431 if (sess->error_cb) {
432 sess->error_cb(sess);
433 }
434 }
435 } else if (close) {
436 sess->state = JABBER_IBB_SESSION_CLOSED;
437 purple_debug_info("jabber", "IBB: received close\n");
438
439 if (sess->closed_cb) {
440 purple_debug_info("jabber", "IBB: calling closed handler\n");
441 sess->closed_cb(sess);
442 }
443
444 } else {
445 /* this should never happen */
446 purple_debug_error("jabber", "Received bogus iq for IBB session\n");
447 }
448 } else if (open) {
449 JabberIq *result;
450 const GList *iterator;
451
452 /* run all open handlers registered until one returns true */
453 for (iterator = open_handlers ; iterator ;
454 iterator = g_list_next(iterator)) {
455 JabberIBBOpenHandler *handler = (JabberIBBOpenHandler *) iterator->data;
456
457 if (handler(js, packet)) {
458 result = jabber_iq_new(js, JABBER_IQ_RESULT);
459 xmlnode_set_attrib(result->node, "to",
460 xmlnode_get_attrib(packet, "from"));
461 jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
462 jabber_iq_send(result);
463 return;
464 }
465 }
466 } else {
467 /* send error reply */
468 JabberIq *result = jabber_iq_new(js, JABBER_IQ_ERROR);
469 xmlnode *error = xmlnode_new("error");
470 xmlnode *item_not_found = xmlnode_new("item-not-found");
471
472 xmlnode_set_namespace(item_not_found,
473 "urn:ietf:params:xml:ns:xmpp-stanzas");
474 xmlnode_set_attrib(error, "code", "440");
475 xmlnode_set_attrib(error, "type", "cancel");
476 jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
477 xmlnode_set_attrib(result->node, "to",
478 xmlnode_get_attrib(packet, "from"));
479 xmlnode_insert_child(error, item_not_found);
480 xmlnode_insert_child(result->node, error);
481
482 jabber_iq_send(result);
483 }
484 }
485
486 void
487 jabber_ibb_register_open_handler(JabberIBBOpenHandler *cb)
488 {
489 open_handlers = g_list_append(open_handlers, cb);
490 }
491
492 void
493 jabber_ibb_unregister_open_handler(JabberIBBOpenHandler *cb)
494 {
495 open_handlers = g_list_remove(open_handlers, cb);
496 }
497
498 void
499 jabber_ibb_init(void)
500 {
501 jabber_ibb_sessions = g_hash_table_new(g_str_hash, g_str_equal);
502 }
503
504 void
505 jabber_ibb_uninit(void)
506 {
507 g_hash_table_destroy(jabber_ibb_sessions);
508 g_list_free(open_handlers);
509 jabber_ibb_sessions = NULL;
510 open_handlers = NULL;
511 }
512
513
514