Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / jabber / ibb.c
bloba1877a8c5dd0dac6dbc2d9e3f6ad32f007e4d397
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 g_free(sess);
71 return NULL;
74 sess = jabber_ibb_session_create(js, sid, from, user_data);
75 sess->id = g_strdup(id);
76 sess->block_size = atoi(block_size);
77 /* if we create a session from an incoming <open/> request, it means the
78 session is immediatly open... */
79 sess->state = JABBER_IBB_SESSION_OPENED;
81 return sess;
84 void
85 jabber_ibb_session_destroy(JabberIBBSession *sess)
87 purple_debug_info("jabber", "IBB: destroying session %p %s\n", sess,
88 sess->sid);
90 if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) {
91 jabber_ibb_session_close(sess);
94 if (sess->last_iq_id) {
95 purple_debug_info("jabber", "IBB: removing callback for <iq/> %s\n",
96 sess->last_iq_id);
97 jabber_iq_remove_callback_by_id(jabber_ibb_session_get_js(sess),
98 sess->last_iq_id);
99 g_free(sess->last_iq_id);
100 sess->last_iq_id = NULL;
103 g_hash_table_remove(jabber_ibb_sessions, sess->sid);
104 g_free(sess->id);
105 g_free(sess->sid);
106 g_free(sess->who);
107 g_free(sess);
110 const gchar *
111 jabber_ibb_session_get_sid(const JabberIBBSession *sess)
113 return sess->sid;
116 JabberStream *
117 jabber_ibb_session_get_js(JabberIBBSession *sess)
119 return sess->js;
122 const gchar *
123 jabber_ibb_session_get_who(const JabberIBBSession *sess)
125 return sess->who;
128 guint16
129 jabber_ibb_session_get_send_seq(const JabberIBBSession *sess)
131 return sess->send_seq;
134 guint16
135 jabber_ibb_session_get_recv_seq(const JabberIBBSession *sess)
137 return sess->recv_seq;
140 JabberIBBSessionState
141 jabber_ibb_session_get_state(const JabberIBBSession *sess)
143 return sess->state;
146 gsize
147 jabber_ibb_session_get_block_size(const JabberIBBSession *sess)
149 return sess->block_size;
152 void
153 jabber_ibb_session_set_block_size(JabberIBBSession *sess, gsize size)
155 if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_NOT_OPENED) {
156 sess->block_size = size;
157 } else {
158 purple_debug_error("jabber",
159 "Can't set block size on an open IBB session\n");
163 gsize
164 jabber_ibb_session_get_max_data_size(const JabberIBBSession *sess)
166 return (gsize) floor((sess->block_size - 2) * (float) 3 / 4);
169 gpointer
170 jabber_ibb_session_get_user_data(JabberIBBSession *sess)
172 return sess->user_data;
175 void
176 jabber_ibb_session_set_opened_callback(JabberIBBSession *sess,
177 JabberIBBOpenedCallback *cb)
179 sess->opened_cb = cb;
182 void
183 jabber_ibb_session_set_data_sent_callback(JabberIBBSession *sess,
184 JabberIBBSentCallback *cb)
186 sess->data_sent_cb = cb;
189 void
190 jabber_ibb_session_set_closed_callback(JabberIBBSession *sess,
191 JabberIBBClosedCallback *cb)
193 sess->closed_cb = cb;
196 void
197 jabber_ibb_session_set_data_received_callback(JabberIBBSession *sess,
198 JabberIBBDataCallback *cb)
200 sess->data_received_cb = cb;
203 void
204 jabber_ibb_session_set_error_callback(JabberIBBSession *sess,
205 JabberIBBErrorCallback *cb)
207 sess->error_cb = cb;
210 static void
211 jabber_ibb_session_opened_cb(JabberStream *js, const char *from,
212 JabberIqType type, const char *id,
213 PurpleXmlNode *packet, gpointer data)
215 JabberIBBSession *sess = (JabberIBBSession *) data;
217 if (type == JABBER_IQ_ERROR) {
218 sess->state = JABBER_IBB_SESSION_ERROR;
219 } else {
220 sess->state = JABBER_IBB_SESSION_OPENED;
223 if (sess->opened_cb) {
224 sess->opened_cb(sess);
228 void
229 jabber_ibb_session_open(JabberIBBSession *sess)
231 if (jabber_ibb_session_get_state(sess) != JABBER_IBB_SESSION_NOT_OPENED) {
232 purple_debug_error("jabber",
233 "jabber_ibb_session called on an already open stream\n");
234 } else {
235 JabberIq *set = jabber_iq_new(sess->js, JABBER_IQ_SET);
236 PurpleXmlNode *open = purple_xmlnode_new("open");
237 gchar block_size[10];
239 purple_xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
240 purple_xmlnode_set_namespace(open, NS_IBB);
241 purple_xmlnode_set_attrib(open, "sid", jabber_ibb_session_get_sid(sess));
242 g_snprintf(block_size, sizeof(block_size), "%" G_GSIZE_FORMAT,
243 jabber_ibb_session_get_block_size(sess));
244 purple_xmlnode_set_attrib(open, "block-size", block_size);
245 purple_xmlnode_insert_child(set->node, open);
247 jabber_iq_set_callback(set, jabber_ibb_session_opened_cb, sess);
249 jabber_iq_send(set);
253 void
254 jabber_ibb_session_close(JabberIBBSession *sess)
256 JabberIBBSessionState state = jabber_ibb_session_get_state(sess);
258 if (state != JABBER_IBB_SESSION_OPENED && state != JABBER_IBB_SESSION_ERROR) {
259 purple_debug_error("jabber",
260 "jabber_ibb_session_close called on a session that has not been"
261 "opened\n");
262 } else {
263 JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess),
264 JABBER_IQ_SET);
265 PurpleXmlNode *close = purple_xmlnode_new("close");
267 purple_xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
268 purple_xmlnode_set_namespace(close, NS_IBB);
269 purple_xmlnode_set_attrib(close, "sid", jabber_ibb_session_get_sid(sess));
270 purple_xmlnode_insert_child(set->node, close);
271 jabber_iq_send(set);
272 sess->state = JABBER_IBB_SESSION_CLOSED;
276 void
277 jabber_ibb_session_accept(JabberIBBSession *sess)
279 JabberIq *result = jabber_iq_new(jabber_ibb_session_get_js(sess),
280 JABBER_IQ_RESULT);
282 purple_xmlnode_set_attrib(result->node, "to", jabber_ibb_session_get_who(sess));
283 jabber_iq_set_id(result, sess->id);
284 jabber_iq_send(result);
285 sess->state = JABBER_IBB_SESSION_OPENED;
288 static void
289 jabber_ibb_session_send_acknowledge_cb(JabberStream *js, const char *from,
290 JabberIqType type, const char *id,
291 PurpleXmlNode *packet, gpointer data)
293 JabberIBBSession *sess = (JabberIBBSession *) data;
295 if (sess) {
296 /* reset callback */
297 g_free(sess->last_iq_id);
298 sess->last_iq_id = NULL;
300 if (type == JABBER_IQ_ERROR) {
301 jabber_ibb_session_close(sess);
302 sess->state = JABBER_IBB_SESSION_ERROR;
304 if (sess->error_cb) {
305 sess->error_cb(sess);
307 } else {
308 if (sess->data_sent_cb) {
309 sess->data_sent_cb(sess);
312 } else {
313 /* the session has gone away, it was probably cancelled */
314 purple_debug_info("jabber",
315 "got response from send data, but IBB session is no longer active\n");
319 void
320 jabber_ibb_session_send_data(JabberIBBSession *sess, gconstpointer data,
321 gsize size)
323 JabberIBBSessionState state = jabber_ibb_session_get_state(sess);
325 purple_debug_info("jabber", "sending data block of %" G_GSIZE_FORMAT " bytes on IBB stream\n",
326 size);
328 if (state != JABBER_IBB_SESSION_OPENED) {
329 purple_debug_error("jabber",
330 "trying to send data on a non-open IBB session\n");
331 } else if (size > jabber_ibb_session_get_max_data_size(sess)) {
332 purple_debug_error("jabber",
333 "trying to send a too large packet in the IBB session\n");
334 } else {
335 JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess),
336 JABBER_IQ_SET);
337 PurpleXmlNode *data_element = purple_xmlnode_new("data");
338 char *base64 = purple_base64_encode(data, size);
339 char seq[10];
340 g_snprintf(seq, sizeof(seq), "%u", jabber_ibb_session_get_send_seq(sess));
342 purple_xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
343 purple_xmlnode_set_namespace(data_element, NS_IBB);
344 purple_xmlnode_set_attrib(data_element, "sid", jabber_ibb_session_get_sid(sess));
345 purple_xmlnode_set_attrib(data_element, "seq", seq);
346 purple_xmlnode_insert_data(data_element, base64, -1);
348 purple_xmlnode_insert_child(set->node, data_element);
350 purple_debug_info("jabber",
351 "IBB: setting send <iq/> callback for session %p %s\n", sess,
352 sess->sid);
353 jabber_iq_set_callback(set, jabber_ibb_session_send_acknowledge_cb, sess);
354 sess->last_iq_id = g_strdup(purple_xmlnode_get_attrib(set->node, "id"));
355 purple_debug_info("jabber", "IBB: set sess->last_iq_id: %s\n",
356 sess->last_iq_id);
357 jabber_iq_send(set);
359 g_free(base64);
360 (sess->send_seq)++;
364 static void
365 jabber_ibb_send_error_response(JabberStream *js, const char *to, const char *id)
367 JabberIq *result = jabber_iq_new(js, JABBER_IQ_ERROR);
368 PurpleXmlNode *error = purple_xmlnode_new("error");
369 PurpleXmlNode *item_not_found = purple_xmlnode_new("item-not-found");
371 purple_xmlnode_set_namespace(item_not_found, NS_XMPP_STANZAS);
372 purple_xmlnode_set_attrib(error, "code", "440");
373 purple_xmlnode_set_attrib(error, "type", "cancel");
374 jabber_iq_set_id(result, id);
375 purple_xmlnode_set_attrib(result->node, "to", to);
376 purple_xmlnode_insert_child(error, item_not_found);
377 purple_xmlnode_insert_child(result->node, error);
379 jabber_iq_send(result);
382 void
383 jabber_ibb_parse(JabberStream *js, const char *who, JabberIqType type,
384 const char *id, PurpleXmlNode *child)
386 const char *name = child->name;
387 gboolean data = g_str_equal(name, "data");
388 gboolean close = g_str_equal(name, "close");
389 gboolean open = g_str_equal(name, "open");
390 const gchar *sid = (data || close) ?
391 purple_xmlnode_get_attrib(child, "sid") : NULL;
392 JabberIBBSession *sess =
393 sid ? g_hash_table_lookup(jabber_ibb_sessions, sid) : NULL;
395 if (sess) {
397 if (strcmp(who, jabber_ibb_session_get_who(sess)) != 0) {
398 /* the iq comes from a different JID than the remote JID of the
399 session, ignore it */
400 purple_debug_error("jabber",
401 "Got IBB iq from wrong JID, ignoring\n");
402 } else if (data) {
403 const gchar *seq_attr = purple_xmlnode_get_attrib(child, "seq");
404 guint16 seq = (seq_attr ? atoi(seq_attr) : 0);
406 /* reject the data, and set the session in error if we get an
407 out-of-order packet */
408 if (seq_attr && seq == jabber_ibb_session_get_recv_seq(sess)) {
409 /* sequence # is the expected... */
410 JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT);
412 jabber_iq_set_id(result, id);
413 purple_xmlnode_set_attrib(result->node, "to", who);
415 if (sess->data_received_cb) {
416 gchar *base64 = purple_xmlnode_get_data(child);
417 gsize size;
418 gpointer rawdata = purple_base64_decode(base64, &size);
420 g_free(base64);
422 if (rawdata) {
423 purple_debug_info("jabber",
424 "got %" G_GSIZE_FORMAT " bytes of data on IBB stream\n",
425 size);
426 /* we accept other clients to send up to block-size
427 of _unencoded_ data, since there's been some confusions
428 regarding the interpretation of this attribute
429 (including previous versions of libpurple) */
430 if (size > jabber_ibb_session_get_block_size(sess)) {
431 purple_debug_error("jabber",
432 "IBB: received a too large packet\n");
433 if (sess->error_cb)
434 sess->error_cb(sess);
435 g_free(rawdata);
436 return;
437 } else {
438 purple_debug_info("jabber",
439 "calling IBB callback for received data\n");
440 sess->data_received_cb(sess, rawdata, size);
442 g_free(rawdata);
443 } else {
444 purple_debug_error("jabber",
445 "IBB: invalid BASE64 data received\n");
446 if (sess->error_cb)
447 sess->error_cb(sess);
448 return;
453 (sess->recv_seq)++;
454 jabber_iq_send(result);
456 } else {
457 purple_debug_error("jabber",
458 "Received an out-of-order/invalid IBB packet\n");
459 sess->state = JABBER_IBB_SESSION_ERROR;
461 if (sess->error_cb) {
462 sess->error_cb(sess);
465 } else if (close) {
466 sess->state = JABBER_IBB_SESSION_CLOSED;
467 purple_debug_info("jabber", "IBB: received close\n");
469 if (sess->closed_cb) {
470 purple_debug_info("jabber", "IBB: calling closed handler\n");
471 sess->closed_cb(sess);
474 } else if (open) {
475 JabberIq *result;
476 const GList *iterator;
478 /* run all open handlers registered until one returns true */
479 for (iterator = open_handlers ; iterator ;
480 iterator = g_list_next(iterator)) {
481 JabberIBBOpenHandler *handler = iterator->data;
483 if (handler(js, who, id, child)) {
484 result = jabber_iq_new(js, JABBER_IQ_RESULT);
485 purple_xmlnode_set_attrib(result->node, "to", who);
486 jabber_iq_set_id(result, id);
487 jabber_iq_send(result);
488 return;
491 /* no open callback returned success, reject */
492 jabber_ibb_send_error_response(js, who, id);
493 } else {
494 /* send error reply */
495 jabber_ibb_send_error_response(js, who, id);
499 void
500 jabber_ibb_register_open_handler(JabberIBBOpenHandler *cb)
502 open_handlers = g_list_append(open_handlers, cb);
505 void
506 jabber_ibb_unregister_open_handler(JabberIBBOpenHandler *cb)
508 open_handlers = g_list_remove(open_handlers, cb);
511 void
512 jabber_ibb_init(void)
514 jabber_ibb_sessions = g_hash_table_new(g_str_hash, g_str_equal);
516 jabber_add_feature(NS_IBB, NULL);
518 jabber_iq_register_handler("close", NS_IBB, jabber_ibb_parse);
519 jabber_iq_register_handler("data", NS_IBB, jabber_ibb_parse);
520 jabber_iq_register_handler("open", NS_IBB, jabber_ibb_parse);
523 void
524 jabber_ibb_uninit(void)
526 g_hash_table_destroy(jabber_ibb_sessions);
527 g_list_free(open_handlers);
528 jabber_ibb_sessions = NULL;
529 open_handlers = NULL;