rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / jabber / ibb.c
blob7e62320ec0c2b83c078b4a559186a3df4db62571
1 /*
2 * purple - Handling of XEP-0047: In-Band Bytestreams.
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 #include "internal.h"
25 #include "ibb.h"
26 #include "debug.h"
27 #include "xmlnode.h"
29 #define JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE 4096
31 static GHashTable *jabber_ibb_sessions = NULL;
32 static GList *open_handlers = NULL;
34 JabberIBBSession *
35 jabber_ibb_session_create(JabberStream *js, const gchar *sid, const gchar *who,
36 gpointer user_data)
38 JabberIBBSession *sess = g_new0(JabberIBBSession, 1);
39 sess->js = js;
40 if (sid) {
41 sess->sid = g_strdup(sid);
42 } else {
43 sess->sid = jabber_get_next_id(js);
45 sess->who = g_strdup(who);
46 sess->block_size = JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE;
47 sess->state = JABBER_IBB_SESSION_NOT_OPENED;
48 sess->user_data = user_data;
50 g_hash_table_insert(jabber_ibb_sessions, sess->sid, sess);
52 return sess;
55 JabberIBBSession *
56 jabber_ibb_session_create_from_xmlnode(JabberStream *js, const char *from,
57 const char *id, PurpleXmlNode *open, gpointer user_data)
59 JabberIBBSession *sess = NULL;
60 const gchar *sid = purple_xmlnode_get_attrib(open, "sid");
61 const gchar *block_size = purple_xmlnode_get_attrib(open, "block-size");
63 if (!open) {
64 return NULL;
67 if (!sid || !block_size) {
68 purple_debug_error("jabber",
69 "IBB session open tag requires sid and block-size attributes\n");
70 return NULL;
73 sess = jabber_ibb_session_create(js, sid, from, user_data);
74 sess->id = g_strdup(id);
75 sess->block_size = atoi(block_size);
76 /* if we create a session from an incoming <open/> request, it means the
77 session is immediatly open... */
78 sess->state = JABBER_IBB_SESSION_OPENED;
80 return sess;
83 void
84 jabber_ibb_session_destroy(JabberIBBSession *sess)
86 purple_debug_info("jabber", "IBB: destroying session %p %s\n", sess,
87 sess->sid);
89 if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) {
90 jabber_ibb_session_close(sess);
93 if (sess->last_iq_id) {
94 purple_debug_info("jabber", "IBB: removing callback for <iq/> %s\n",
95 sess->last_iq_id);
96 jabber_iq_remove_callback_by_id(jabber_ibb_session_get_js(sess),
97 sess->last_iq_id);
98 g_free(sess->last_iq_id);
99 sess->last_iq_id = NULL;
102 g_hash_table_remove(jabber_ibb_sessions, sess->sid);
103 g_free(sess->id);
104 g_free(sess->sid);
105 g_free(sess->who);
106 g_free(sess);
109 const gchar *
110 jabber_ibb_session_get_sid(const JabberIBBSession *sess)
112 return sess->sid;
115 JabberStream *
116 jabber_ibb_session_get_js(JabberIBBSession *sess)
118 return sess->js;
121 const gchar *
122 jabber_ibb_session_get_who(const JabberIBBSession *sess)
124 return sess->who;
127 guint16
128 jabber_ibb_session_get_send_seq(const JabberIBBSession *sess)
130 return sess->send_seq;
133 guint16
134 jabber_ibb_session_get_recv_seq(const JabberIBBSession *sess)
136 return sess->recv_seq;
139 JabberIBBSessionState
140 jabber_ibb_session_get_state(const JabberIBBSession *sess)
142 return sess->state;
145 gsize
146 jabber_ibb_session_get_block_size(const JabberIBBSession *sess)
148 return sess->block_size;
151 void
152 jabber_ibb_session_set_block_size(JabberIBBSession *sess, gsize size)
154 if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_NOT_OPENED) {
155 sess->block_size = size;
156 } else {
157 purple_debug_error("jabber",
158 "Can't set block size on an open IBB session\n");
162 gsize
163 jabber_ibb_session_get_max_data_size(const JabberIBBSession *sess)
165 return (gsize) floor((sess->block_size - 2) * (float) 3 / 4);
168 gpointer
169 jabber_ibb_session_get_user_data(JabberIBBSession *sess)
171 return sess->user_data;
174 void
175 jabber_ibb_session_set_opened_callback(JabberIBBSession *sess,
176 JabberIBBOpenedCallback *cb)
178 sess->opened_cb = cb;
181 void
182 jabber_ibb_session_set_data_sent_callback(JabberIBBSession *sess,
183 JabberIBBSentCallback *cb)
185 sess->data_sent_cb = cb;
188 void
189 jabber_ibb_session_set_closed_callback(JabberIBBSession *sess,
190 JabberIBBClosedCallback *cb)
192 sess->closed_cb = cb;
195 void
196 jabber_ibb_session_set_data_received_callback(JabberIBBSession *sess,
197 JabberIBBDataCallback *cb)
199 sess->data_received_cb = cb;
202 void
203 jabber_ibb_session_set_error_callback(JabberIBBSession *sess,
204 JabberIBBErrorCallback *cb)
206 sess->error_cb = cb;
209 static void
210 jabber_ibb_session_opened_cb(JabberStream *js, const char *from,
211 JabberIqType type, const char *id,
212 PurpleXmlNode *packet, gpointer data)
214 JabberIBBSession *sess = (JabberIBBSession *) data;
216 if (type == JABBER_IQ_ERROR) {
217 sess->state = JABBER_IBB_SESSION_ERROR;
218 } else {
219 sess->state = JABBER_IBB_SESSION_OPENED;
222 if (sess->opened_cb) {
223 sess->opened_cb(sess);
227 void
228 jabber_ibb_session_open(JabberIBBSession *sess)
230 if (jabber_ibb_session_get_state(sess) != JABBER_IBB_SESSION_NOT_OPENED) {
231 purple_debug_error("jabber",
232 "jabber_ibb_session called on an already open stream\n");
233 } else {
234 JabberIq *set = jabber_iq_new(sess->js, JABBER_IQ_SET);
235 PurpleXmlNode *open = purple_xmlnode_new("open");
236 gchar block_size[10];
238 purple_xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
239 purple_xmlnode_set_namespace(open, NS_IBB);
240 purple_xmlnode_set_attrib(open, "sid", jabber_ibb_session_get_sid(sess));
241 g_snprintf(block_size, sizeof(block_size), "%" G_GSIZE_FORMAT,
242 jabber_ibb_session_get_block_size(sess));
243 purple_xmlnode_set_attrib(open, "block-size", block_size);
244 purple_xmlnode_insert_child(set->node, open);
246 jabber_iq_set_callback(set, jabber_ibb_session_opened_cb, sess);
248 jabber_iq_send(set);
252 void
253 jabber_ibb_session_close(JabberIBBSession *sess)
255 JabberIBBSessionState state = jabber_ibb_session_get_state(sess);
257 if (state != JABBER_IBB_SESSION_OPENED && state != JABBER_IBB_SESSION_ERROR) {
258 purple_debug_error("jabber",
259 "jabber_ibb_session_close called on a session that has not been"
260 "opened\n");
261 } else {
262 JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess),
263 JABBER_IQ_SET);
264 PurpleXmlNode *close = purple_xmlnode_new("close");
266 purple_xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
267 purple_xmlnode_set_namespace(close, NS_IBB);
268 purple_xmlnode_set_attrib(close, "sid", jabber_ibb_session_get_sid(sess));
269 purple_xmlnode_insert_child(set->node, close);
270 jabber_iq_send(set);
271 sess->state = JABBER_IBB_SESSION_CLOSED;
275 void
276 jabber_ibb_session_accept(JabberIBBSession *sess)
278 JabberIq *result = jabber_iq_new(jabber_ibb_session_get_js(sess),
279 JABBER_IQ_RESULT);
281 purple_xmlnode_set_attrib(result->node, "to", jabber_ibb_session_get_who(sess));
282 jabber_iq_set_id(result, sess->id);
283 jabber_iq_send(result);
284 sess->state = JABBER_IBB_SESSION_OPENED;
287 static void
288 jabber_ibb_session_send_acknowledge_cb(JabberStream *js, const char *from,
289 JabberIqType type, const char *id,
290 PurpleXmlNode *packet, gpointer data)
292 JabberIBBSession *sess = (JabberIBBSession *) data;
294 if (sess) {
295 /* reset callback */
296 g_free(sess->last_iq_id);
297 sess->last_iq_id = NULL;
299 if (type == JABBER_IQ_ERROR) {
300 jabber_ibb_session_close(sess);
301 sess->state = JABBER_IBB_SESSION_ERROR;
303 if (sess->error_cb) {
304 sess->error_cb(sess);
306 } else {
307 if (sess->data_sent_cb) {
308 sess->data_sent_cb(sess);
311 } else {
312 /* the session has gone away, it was probably cancelled */
313 purple_debug_info("jabber",
314 "got response from send data, but IBB session is no longer active\n");
318 void
319 jabber_ibb_session_send_data(JabberIBBSession *sess, gconstpointer data,
320 gsize size)
322 JabberIBBSessionState state = jabber_ibb_session_get_state(sess);
324 purple_debug_info("jabber", "sending data block of %" G_GSIZE_FORMAT " bytes on IBB stream\n",
325 size);
327 if (state != JABBER_IBB_SESSION_OPENED) {
328 purple_debug_error("jabber",
329 "trying to send data on a non-open IBB session\n");
330 } else if (size > jabber_ibb_session_get_max_data_size(sess)) {
331 purple_debug_error("jabber",
332 "trying to send a too large packet in the IBB session\n");
333 } else {
334 JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess),
335 JABBER_IQ_SET);
336 PurpleXmlNode *data_element = purple_xmlnode_new("data");
337 char *base64 = g_base64_encode(data, size);
338 char seq[10];
339 g_snprintf(seq, sizeof(seq), "%u", jabber_ibb_session_get_send_seq(sess));
341 purple_xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
342 purple_xmlnode_set_namespace(data_element, NS_IBB);
343 purple_xmlnode_set_attrib(data_element, "sid", jabber_ibb_session_get_sid(sess));
344 purple_xmlnode_set_attrib(data_element, "seq", seq);
345 purple_xmlnode_insert_data(data_element, base64, -1);
347 purple_xmlnode_insert_child(set->node, data_element);
349 purple_debug_info("jabber",
350 "IBB: setting send <iq/> callback for session %p %s\n", sess,
351 sess->sid);
352 jabber_iq_set_callback(set, jabber_ibb_session_send_acknowledge_cb, sess);
353 sess->last_iq_id = g_strdup(purple_xmlnode_get_attrib(set->node, "id"));
354 purple_debug_info("jabber", "IBB: set sess->last_iq_id: %s\n",
355 sess->last_iq_id);
356 jabber_iq_send(set);
358 g_free(base64);
359 (sess->send_seq)++;
363 static void
364 jabber_ibb_send_error_response(JabberStream *js, const char *to, const char *id)
366 JabberIq *result = jabber_iq_new(js, JABBER_IQ_ERROR);
367 PurpleXmlNode *error = purple_xmlnode_new("error");
368 PurpleXmlNode *item_not_found = purple_xmlnode_new("item-not-found");
370 purple_xmlnode_set_namespace(item_not_found, NS_XMPP_STANZAS);
371 purple_xmlnode_set_attrib(error, "code", "440");
372 purple_xmlnode_set_attrib(error, "type", "cancel");
373 jabber_iq_set_id(result, id);
374 purple_xmlnode_set_attrib(result->node, "to", to);
375 purple_xmlnode_insert_child(error, item_not_found);
376 purple_xmlnode_insert_child(result->node, error);
378 jabber_iq_send(result);
381 void
382 jabber_ibb_parse(JabberStream *js, const char *who, JabberIqType type,
383 const char *id, PurpleXmlNode *child)
385 const char *name = child->name;
386 gboolean data = purple_strequal(name, "data");
387 gboolean close = purple_strequal(name, "close");
388 gboolean open = purple_strequal(name, "open");
389 const gchar *sid = (data || close) ?
390 purple_xmlnode_get_attrib(child, "sid") : NULL;
391 JabberIBBSession *sess =
392 sid ? g_hash_table_lookup(jabber_ibb_sessions, sid) : NULL;
394 if (sess) {
396 if (!purple_strequal(who, jabber_ibb_session_get_who(sess))) {
397 /* the iq comes from a different JID than the remote JID of the
398 session, ignore it */
399 purple_debug_error("jabber",
400 "Got IBB iq from wrong JID, ignoring\n");
401 } else if (data) {
402 const gchar *seq_attr = purple_xmlnode_get_attrib(child, "seq");
403 guint16 seq = (seq_attr ? atoi(seq_attr) : 0);
405 /* reject the data, and set the session in error if we get an
406 out-of-order packet */
407 if (seq_attr && seq == jabber_ibb_session_get_recv_seq(sess)) {
408 /* sequence # is the expected... */
409 JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT);
411 jabber_iq_set_id(result, id);
412 purple_xmlnode_set_attrib(result->node, "to", who);
414 if (sess->data_received_cb) {
415 gchar *base64 = purple_xmlnode_get_data(child);
416 gsize size;
417 gpointer rawdata = g_base64_decode(base64, &size);
419 g_free(base64);
421 if (rawdata) {
422 purple_debug_info("jabber",
423 "got %" G_GSIZE_FORMAT " bytes of data on IBB stream\n",
424 size);
425 /* we accept other clients to send up to block-size
426 of _unencoded_ data, since there's been some confusions
427 regarding the interpretation of this attribute
428 (including previous versions of libpurple) */
429 if (size > jabber_ibb_session_get_block_size(sess)) {
430 purple_debug_error("jabber",
431 "IBB: received a too large packet\n");
432 if (sess->error_cb)
433 sess->error_cb(sess);
434 g_free(rawdata);
435 return;
436 } else {
437 purple_debug_info("jabber",
438 "calling IBB callback for received data\n");
439 sess->data_received_cb(sess, rawdata, size);
441 g_free(rawdata);
442 } else {
443 purple_debug_error("jabber",
444 "IBB: invalid BASE64 data received\n");
445 if (sess->error_cb)
446 sess->error_cb(sess);
447 return;
452 (sess->recv_seq)++;
453 jabber_iq_send(result);
455 } else {
456 purple_debug_error("jabber",
457 "Received an out-of-order/invalid IBB packet\n");
458 sess->state = JABBER_IBB_SESSION_ERROR;
460 if (sess->error_cb) {
461 sess->error_cb(sess);
464 } else if (close) {
465 sess->state = JABBER_IBB_SESSION_CLOSED;
466 purple_debug_info("jabber", "IBB: received close\n");
468 if (sess->closed_cb) {
469 purple_debug_info("jabber", "IBB: calling closed handler\n");
470 sess->closed_cb(sess);
473 } else if (open) {
474 JabberIq *result;
475 const GList *iterator;
477 /* run all open handlers registered until one returns true */
478 for (iterator = open_handlers ; iterator ;
479 iterator = g_list_next(iterator)) {
480 JabberIBBOpenHandler *handler = iterator->data;
482 if (handler(js, who, id, child)) {
483 result = jabber_iq_new(js, JABBER_IQ_RESULT);
484 purple_xmlnode_set_attrib(result->node, "to", who);
485 jabber_iq_set_id(result, id);
486 jabber_iq_send(result);
487 return;
490 /* no open callback returned success, reject */
491 jabber_ibb_send_error_response(js, who, id);
492 } else {
493 /* send error reply */
494 jabber_ibb_send_error_response(js, who, id);
498 void
499 jabber_ibb_register_open_handler(JabberIBBOpenHandler *cb)
501 open_handlers = g_list_append(open_handlers, cb);
504 void
505 jabber_ibb_unregister_open_handler(JabberIBBOpenHandler *cb)
507 open_handlers = g_list_remove(open_handlers, cb);
510 void
511 jabber_ibb_init(void)
513 jabber_ibb_sessions = g_hash_table_new(g_str_hash, g_str_equal);
515 jabber_add_feature(NS_IBB, NULL);
517 jabber_iq_register_handler("close", NS_IBB, jabber_ibb_parse);
518 jabber_iq_register_handler("data", NS_IBB, jabber_ibb_parse);
519 jabber_iq_register_handler("open", NS_IBB, jabber_ibb_parse);
522 void
523 jabber_ibb_uninit(void)
525 g_hash_table_destroy(jabber_ibb_sessions);
526 g_list_free(open_handlers);
527 jabber_ibb_sessions = NULL;
528 open_handlers = NULL;