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");
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
;
85 jabber_ibb_session_destroy(JabberIBBSession
*sess
)
87 purple_debug_info("jabber", "IBB: destroying session %p %s\n", sess
,
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",
97 jabber_iq_remove_callback_by_id(jabber_ibb_session_get_js(sess
),
99 g_free(sess
->last_iq_id
);
100 sess
->last_iq_id
= NULL
;
103 g_hash_table_remove(jabber_ibb_sessions
, sess
->sid
);
111 jabber_ibb_session_get_sid(const JabberIBBSession
*sess
)
117 jabber_ibb_session_get_js(JabberIBBSession
*sess
)
123 jabber_ibb_session_get_who(const JabberIBBSession
*sess
)
129 jabber_ibb_session_get_send_seq(const JabberIBBSession
*sess
)
131 return sess
->send_seq
;
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
)
147 jabber_ibb_session_get_block_size(const JabberIBBSession
*sess
)
149 return sess
->block_size
;
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
;
158 purple_debug_error("jabber",
159 "Can't set block size on an open IBB session\n");
164 jabber_ibb_session_get_max_data_size(const JabberIBBSession
*sess
)
166 return (gsize
) floor((sess
->block_size
- 2) * (float) 3 / 4);
170 jabber_ibb_session_get_user_data(JabberIBBSession
*sess
)
172 return sess
->user_data
;
176 jabber_ibb_session_set_opened_callback(JabberIBBSession
*sess
,
177 JabberIBBOpenedCallback
*cb
)
179 sess
->opened_cb
= cb
;
183 jabber_ibb_session_set_data_sent_callback(JabberIBBSession
*sess
,
184 JabberIBBSentCallback
*cb
)
186 sess
->data_sent_cb
= cb
;
190 jabber_ibb_session_set_closed_callback(JabberIBBSession
*sess
,
191 JabberIBBClosedCallback
*cb
)
193 sess
->closed_cb
= cb
;
197 jabber_ibb_session_set_data_received_callback(JabberIBBSession
*sess
,
198 JabberIBBDataCallback
*cb
)
200 sess
->data_received_cb
= cb
;
204 jabber_ibb_session_set_error_callback(JabberIBBSession
*sess
,
205 JabberIBBErrorCallback
*cb
)
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
;
220 sess
->state
= JABBER_IBB_SESSION_OPENED
;
223 if (sess
->opened_cb
) {
224 sess
->opened_cb(sess
);
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");
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
);
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"
263 JabberIq
*set
= jabber_iq_new(jabber_ibb_session_get_js(sess
),
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
);
272 sess
->state
= JABBER_IBB_SESSION_CLOSED
;
277 jabber_ibb_session_accept(JabberIBBSession
*sess
)
279 JabberIq
*result
= jabber_iq_new(jabber_ibb_session_get_js(sess
),
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
;
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
;
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
);
308 if (sess
->data_sent_cb
) {
309 sess
->data_sent_cb(sess
);
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");
320 jabber_ibb_session_send_data(JabberIBBSession
*sess
, gconstpointer data
,
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",
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");
335 JabberIq
*set
= jabber_iq_new(jabber_ibb_session_get_js(sess
),
337 PurpleXmlNode
*data_element
= purple_xmlnode_new("data");
338 char *base64
= purple_base64_encode(data
, size
);
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
,
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",
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
);
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
;
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");
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
);
418 gpointer rawdata
= purple_base64_decode(base64
, &size
);
423 purple_debug_info("jabber",
424 "got %" G_GSIZE_FORMAT
" bytes of data on IBB stream\n",
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");
434 sess
->error_cb(sess
);
438 purple_debug_info("jabber",
439 "calling IBB callback for received data\n");
440 sess
->data_received_cb(sess
, rawdata
, size
);
444 purple_debug_error("jabber",
445 "IBB: invalid BASE64 data received\n");
447 sess
->error_cb(sess
);
454 jabber_iq_send(result
);
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
);
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
);
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
);
491 /* no open callback returned success, reject */
492 jabber_ibb_send_error_response(js
, who
, id
);
494 /* send error reply */
495 jabber_ibb_send_error_response(js
, who
, id
);
500 jabber_ibb_register_open_handler(JabberIBBOpenHandler
*cb
)
502 open_handlers
= g_list_append(open_handlers
, cb
);
506 jabber_ibb_unregister_open_handler(JabberIBBOpenHandler
*cb
)
508 open_handlers
= g_list_remove(open_handlers
, cb
);
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
);
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
;