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
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
29 #define JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE 4096
31 static GHashTable
*jabber_ibb_sessions
= NULL
;
32 static GList
*open_handlers
= NULL
;
35 jabber_ibb_session_create(JabberStream
*js
, const gchar
*sid
, const gchar
*who
,
38 JabberIBBSession
*sess
= g_new0(JabberIBBSession
, 1);
41 sess
->sid
= g_strdup(sid
);
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
);
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");
67 if (!sid
|| !block_size
) {
68 purple_debug_error("jabber",
69 "IBB session open tag requires sid and block-size attributes\n");
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
;
84 jabber_ibb_session_destroy(JabberIBBSession
*sess
)
86 purple_debug_info("jabber", "IBB: destroying session %p %s\n", sess
,
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",
96 jabber_iq_remove_callback_by_id(jabber_ibb_session_get_js(sess
),
98 g_free(sess
->last_iq_id
);
99 sess
->last_iq_id
= NULL
;
102 g_hash_table_remove(jabber_ibb_sessions
, sess
->sid
);
110 jabber_ibb_session_get_sid(const JabberIBBSession
*sess
)
116 jabber_ibb_session_get_js(JabberIBBSession
*sess
)
122 jabber_ibb_session_get_who(const JabberIBBSession
*sess
)
128 jabber_ibb_session_get_send_seq(const JabberIBBSession
*sess
)
130 return sess
->send_seq
;
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
)
146 jabber_ibb_session_get_block_size(const JabberIBBSession
*sess
)
148 return sess
->block_size
;
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
;
157 purple_debug_error("jabber",
158 "Can't set block size on an open IBB session\n");
163 jabber_ibb_session_get_max_data_size(const JabberIBBSession
*sess
)
165 return (gsize
) floor((sess
->block_size
- 2) * (float) 3 / 4);
169 jabber_ibb_session_get_user_data(JabberIBBSession
*sess
)
171 return sess
->user_data
;
175 jabber_ibb_session_set_opened_callback(JabberIBBSession
*sess
,
176 JabberIBBOpenedCallback
*cb
)
178 sess
->opened_cb
= cb
;
182 jabber_ibb_session_set_data_sent_callback(JabberIBBSession
*sess
,
183 JabberIBBSentCallback
*cb
)
185 sess
->data_sent_cb
= cb
;
189 jabber_ibb_session_set_closed_callback(JabberIBBSession
*sess
,
190 JabberIBBClosedCallback
*cb
)
192 sess
->closed_cb
= cb
;
196 jabber_ibb_session_set_data_received_callback(JabberIBBSession
*sess
,
197 JabberIBBDataCallback
*cb
)
199 sess
->data_received_cb
= cb
;
203 jabber_ibb_session_set_error_callback(JabberIBBSession
*sess
,
204 JabberIBBErrorCallback
*cb
)
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
;
219 sess
->state
= JABBER_IBB_SESSION_OPENED
;
222 if (sess
->opened_cb
) {
223 sess
->opened_cb(sess
);
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");
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
);
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"
262 JabberIq
*set
= jabber_iq_new(jabber_ibb_session_get_js(sess
),
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
);
271 sess
->state
= JABBER_IBB_SESSION_CLOSED
;
276 jabber_ibb_session_accept(JabberIBBSession
*sess
)
278 JabberIq
*result
= jabber_iq_new(jabber_ibb_session_get_js(sess
),
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
;
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
;
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
);
307 if (sess
->data_sent_cb
) {
308 sess
->data_sent_cb(sess
);
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");
319 jabber_ibb_session_send_data(JabberIBBSession
*sess
, gconstpointer data
,
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",
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");
334 JabberIq
*set
= jabber_iq_new(jabber_ibb_session_get_js(sess
),
336 PurpleXmlNode
*data_element
= purple_xmlnode_new("data");
337 char *base64
= g_base64_encode(data
, size
);
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
,
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",
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
);
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
;
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");
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
);
417 gpointer rawdata
= g_base64_decode(base64
, &size
);
422 purple_debug_info("jabber",
423 "got %" G_GSIZE_FORMAT
" bytes of data on IBB stream\n",
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");
433 sess
->error_cb(sess
);
437 purple_debug_info("jabber",
438 "calling IBB callback for received data\n");
439 sess
->data_received_cb(sess
, rawdata
, size
);
443 purple_debug_error("jabber",
444 "IBB: invalid BASE64 data received\n");
446 sess
->error_cb(sess
);
453 jabber_iq_send(result
);
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
);
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
);
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
);
490 /* no open callback returned success, reject */
491 jabber_ibb_send_error_response(js
, who
, id
);
493 /* send error reply */
494 jabber_ibb_send_error_response(js
, who
, id
);
499 jabber_ibb_register_open_handler(JabberIBBOpenHandler
*cb
)
501 open_handlers
= g_list_append(open_handlers
, cb
);
505 jabber_ibb_unregister_open_handler(JabberIBBOpenHandler
*cb
)
507 open_handlers
= g_list_remove(open_handlers
, cb
);
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
);
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
;