/src/pidgin/libpurple/protocols/jabber/ibb.c
Line | Count | Source |
1 | | /* |
2 | | * purple - Jabber Service Discovery |
3 | | * |
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. |
7 | | * |
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. |
12 | | * |
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. |
17 | | * |
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 |
21 | | * |
22 | | */ |
23 | | |
24 | | #include "internal.h" |
25 | | #include "ibb.h" |
26 | | #include "debug.h" |
27 | | #include "xmlnode.h" |
28 | | |
29 | 0 | #define JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE 4096 |
30 | | |
31 | | static GHashTable *jabber_ibb_sessions = NULL; |
32 | | static GList *open_handlers = NULL; |
33 | | |
34 | | JabberIBBSession * |
35 | | jabber_ibb_session_create(JabberStream *js, const gchar *sid, const gchar *who, |
36 | | gpointer user_data) |
37 | 0 | { |
38 | 0 | JabberIBBSession *sess = g_new0(JabberIBBSession, 1); |
39 | 0 | sess->js = js; |
40 | 0 | if (sid) { |
41 | 0 | sess->sid = g_strdup(sid); |
42 | 0 | } else { |
43 | 0 | sess->sid = jabber_get_next_id(js); |
44 | 0 | } |
45 | 0 | sess->who = g_strdup(who); |
46 | 0 | sess->block_size = JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE; |
47 | 0 | sess->state = JABBER_IBB_SESSION_NOT_OPENED; |
48 | 0 | sess->user_data = user_data; |
49 | |
|
50 | 0 | g_hash_table_insert(jabber_ibb_sessions, sess->sid, sess); |
51 | |
|
52 | 0 | return sess; |
53 | 0 | } |
54 | | |
55 | | JabberIBBSession * |
56 | | jabber_ibb_session_create_from_xmlnode(JabberStream *js, const char *from, |
57 | | const char *id, xmlnode *open, gpointer user_data) |
58 | 0 | { |
59 | 0 | JabberIBBSession *sess = NULL; |
60 | 0 | const gchar *sid = xmlnode_get_attrib(open, "sid"); |
61 | 0 | const gchar *block_size = xmlnode_get_attrib(open, "block-size"); |
62 | |
|
63 | 0 | if (!open) { |
64 | 0 | return NULL; |
65 | 0 | } |
66 | | |
67 | 0 | if (!sid || !block_size) { |
68 | 0 | purple_debug_error("jabber", |
69 | 0 | "IBB session open tag requires sid and block-size attributes\n"); |
70 | 0 | g_free(sess); |
71 | 0 | return NULL; |
72 | 0 | } |
73 | | |
74 | 0 | sess = jabber_ibb_session_create(js, sid, from, user_data); |
75 | 0 | sess->id = g_strdup(id); |
76 | 0 | 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 | 0 | sess->state = JABBER_IBB_SESSION_OPENED; |
80 | |
|
81 | 0 | return sess; |
82 | 0 | } |
83 | | |
84 | | void |
85 | | jabber_ibb_session_destroy(JabberIBBSession *sess) |
86 | 0 | { |
87 | 0 | purple_debug_info("jabber", "IBB: destroying session %p %s\n", sess, |
88 | 0 | sess->sid); |
89 | |
|
90 | 0 | if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) { |
91 | 0 | jabber_ibb_session_close(sess); |
92 | 0 | } |
93 | |
|
94 | 0 | if (sess->last_iq_id) { |
95 | 0 | purple_debug_info("jabber", "IBB: removing callback for <iq/> %s\n", |
96 | 0 | sess->last_iq_id); |
97 | 0 | jabber_iq_remove_callback_by_id(jabber_ibb_session_get_js(sess), |
98 | 0 | sess->last_iq_id); |
99 | 0 | g_free(sess->last_iq_id); |
100 | 0 | sess->last_iq_id = NULL; |
101 | 0 | } |
102 | |
|
103 | 0 | g_hash_table_remove(jabber_ibb_sessions, sess->sid); |
104 | 0 | g_free(sess->id); |
105 | 0 | g_free(sess->sid); |
106 | 0 | g_free(sess->who); |
107 | 0 | g_free(sess); |
108 | 0 | } |
109 | | |
110 | | const gchar * |
111 | | jabber_ibb_session_get_sid(const JabberIBBSession *sess) |
112 | 0 | { |
113 | 0 | return sess->sid; |
114 | 0 | } |
115 | | |
116 | | JabberStream * |
117 | | jabber_ibb_session_get_js(JabberIBBSession *sess) |
118 | 0 | { |
119 | 0 | return sess->js; |
120 | 0 | } |
121 | | |
122 | | const gchar * |
123 | | jabber_ibb_session_get_who(const JabberIBBSession *sess) |
124 | 0 | { |
125 | 0 | return sess->who; |
126 | 0 | } |
127 | | |
128 | | guint16 |
129 | | jabber_ibb_session_get_send_seq(const JabberIBBSession *sess) |
130 | 0 | { |
131 | 0 | return sess->send_seq; |
132 | 0 | } |
133 | | |
134 | | guint16 |
135 | | jabber_ibb_session_get_recv_seq(const JabberIBBSession *sess) |
136 | 0 | { |
137 | 0 | return sess->recv_seq; |
138 | 0 | } |
139 | | |
140 | | JabberIBBSessionState |
141 | | jabber_ibb_session_get_state(const JabberIBBSession *sess) |
142 | 0 | { |
143 | 0 | return sess->state; |
144 | 0 | } |
145 | | |
146 | | gsize |
147 | | jabber_ibb_session_get_block_size(const JabberIBBSession *sess) |
148 | 0 | { |
149 | 0 | return sess->block_size; |
150 | 0 | } |
151 | | |
152 | | void |
153 | | jabber_ibb_session_set_block_size(JabberIBBSession *sess, gsize size) |
154 | 0 | { |
155 | 0 | if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_NOT_OPENED) { |
156 | 0 | sess->block_size = size; |
157 | 0 | } else { |
158 | 0 | purple_debug_error("jabber", |
159 | 0 | "Can't set block size on an open IBB session\n"); |
160 | 0 | } |
161 | 0 | } |
162 | | |
163 | | gsize |
164 | | jabber_ibb_session_get_max_data_size(const JabberIBBSession *sess) |
165 | 0 | { |
166 | 0 | return (gsize) floor((sess->block_size - 2) * (float) 3 / 4); |
167 | 0 | } |
168 | | |
169 | | gpointer |
170 | | jabber_ibb_session_get_user_data(JabberIBBSession *sess) |
171 | 0 | { |
172 | 0 | return sess->user_data; |
173 | 0 | } |
174 | | |
175 | | void |
176 | | jabber_ibb_session_set_opened_callback(JabberIBBSession *sess, |
177 | | JabberIBBOpenedCallback *cb) |
178 | 0 | { |
179 | 0 | sess->opened_cb = cb; |
180 | 0 | } |
181 | | |
182 | | void |
183 | | jabber_ibb_session_set_data_sent_callback(JabberIBBSession *sess, |
184 | | JabberIBBSentCallback *cb) |
185 | 0 | { |
186 | 0 | sess->data_sent_cb = cb; |
187 | 0 | } |
188 | | |
189 | | void |
190 | | jabber_ibb_session_set_closed_callback(JabberIBBSession *sess, |
191 | | JabberIBBClosedCallback *cb) |
192 | 0 | { |
193 | 0 | sess->closed_cb = cb; |
194 | 0 | } |
195 | | |
196 | | void |
197 | | jabber_ibb_session_set_data_received_callback(JabberIBBSession *sess, |
198 | | JabberIBBDataCallback *cb) |
199 | 0 | { |
200 | 0 | sess->data_received_cb = cb; |
201 | 0 | } |
202 | | |
203 | | void |
204 | | jabber_ibb_session_set_error_callback(JabberIBBSession *sess, |
205 | | JabberIBBErrorCallback *cb) |
206 | 0 | { |
207 | 0 | sess->error_cb = cb; |
208 | 0 | } |
209 | | |
210 | | static void |
211 | | jabber_ibb_session_opened_cb(JabberStream *js, const char *from, |
212 | | JabberIqType type, const char *id, |
213 | | xmlnode *packet, gpointer data) |
214 | 0 | { |
215 | 0 | JabberIBBSession *sess = (JabberIBBSession *) data; |
216 | |
|
217 | 0 | if (type == JABBER_IQ_ERROR) { |
218 | 0 | sess->state = JABBER_IBB_SESSION_ERROR; |
219 | 0 | } else { |
220 | 0 | sess->state = JABBER_IBB_SESSION_OPENED; |
221 | 0 | } |
222 | |
|
223 | 0 | if (sess->opened_cb) { |
224 | 0 | sess->opened_cb(sess); |
225 | 0 | } |
226 | 0 | } |
227 | | |
228 | | void |
229 | | jabber_ibb_session_open(JabberIBBSession *sess) |
230 | 0 | { |
231 | 0 | if (jabber_ibb_session_get_state(sess) != JABBER_IBB_SESSION_NOT_OPENED) { |
232 | 0 | purple_debug_error("jabber", |
233 | 0 | "jabber_ibb_session called on an already open stream\n"); |
234 | 0 | } else { |
235 | 0 | JabberIq *set = jabber_iq_new(sess->js, JABBER_IQ_SET); |
236 | 0 | xmlnode *open = xmlnode_new("open"); |
237 | 0 | gchar block_size[10]; |
238 | |
|
239 | 0 | xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess)); |
240 | 0 | xmlnode_set_namespace(open, NS_IBB); |
241 | 0 | xmlnode_set_attrib(open, "sid", jabber_ibb_session_get_sid(sess)); |
242 | 0 | g_snprintf(block_size, sizeof(block_size), "%" G_GSIZE_FORMAT, |
243 | 0 | jabber_ibb_session_get_block_size(sess)); |
244 | 0 | xmlnode_set_attrib(open, "block-size", block_size); |
245 | 0 | xmlnode_insert_child(set->node, open); |
246 | |
|
247 | 0 | jabber_iq_set_callback(set, jabber_ibb_session_opened_cb, sess); |
248 | |
|
249 | 0 | jabber_iq_send(set); |
250 | 0 | } |
251 | 0 | } |
252 | | |
253 | | void |
254 | | jabber_ibb_session_close(JabberIBBSession *sess) |
255 | 0 | { |
256 | 0 | JabberIBBSessionState state = jabber_ibb_session_get_state(sess); |
257 | |
|
258 | 0 | if (state != JABBER_IBB_SESSION_OPENED && state != JABBER_IBB_SESSION_ERROR) { |
259 | 0 | purple_debug_error("jabber", |
260 | 0 | "jabber_ibb_session_close called on a session that has not been" |
261 | 0 | "opened\n"); |
262 | 0 | } else { |
263 | 0 | JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess), |
264 | 0 | JABBER_IQ_SET); |
265 | 0 | xmlnode *close = xmlnode_new("close"); |
266 | |
|
267 | 0 | xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess)); |
268 | 0 | xmlnode_set_namespace(close, NS_IBB); |
269 | 0 | xmlnode_set_attrib(close, "sid", jabber_ibb_session_get_sid(sess)); |
270 | 0 | xmlnode_insert_child(set->node, close); |
271 | 0 | jabber_iq_send(set); |
272 | 0 | sess->state = JABBER_IBB_SESSION_CLOSED; |
273 | 0 | } |
274 | 0 | } |
275 | | |
276 | | void |
277 | | jabber_ibb_session_accept(JabberIBBSession *sess) |
278 | 0 | { |
279 | 0 | JabberIq *result = jabber_iq_new(jabber_ibb_session_get_js(sess), |
280 | 0 | JABBER_IQ_RESULT); |
281 | |
|
282 | 0 | xmlnode_set_attrib(result->node, "to", jabber_ibb_session_get_who(sess)); |
283 | 0 | jabber_iq_set_id(result, sess->id); |
284 | 0 | jabber_iq_send(result); |
285 | 0 | sess->state = JABBER_IBB_SESSION_OPENED; |
286 | 0 | } |
287 | | |
288 | | static void |
289 | | jabber_ibb_session_send_acknowledge_cb(JabberStream *js, const char *from, |
290 | | JabberIqType type, const char *id, |
291 | | xmlnode *packet, gpointer data) |
292 | 0 | { |
293 | 0 | JabberIBBSession *sess = (JabberIBBSession *) data; |
294 | |
|
295 | 0 | if (sess) { |
296 | | /* reset callback */ |
297 | 0 | if (sess->last_iq_id) { |
298 | 0 | g_free(sess->last_iq_id); |
299 | 0 | sess->last_iq_id = NULL; |
300 | 0 | } |
301 | |
|
302 | 0 | if (type == JABBER_IQ_ERROR) { |
303 | 0 | jabber_ibb_session_close(sess); |
304 | 0 | sess->state = JABBER_IBB_SESSION_ERROR; |
305 | |
|
306 | 0 | if (sess->error_cb) { |
307 | 0 | sess->error_cb(sess); |
308 | 0 | } |
309 | 0 | } else { |
310 | 0 | if (sess->data_sent_cb) { |
311 | 0 | sess->data_sent_cb(sess); |
312 | 0 | } |
313 | 0 | } |
314 | 0 | } else { |
315 | | /* the session has gone away, it was probably cancelled */ |
316 | 0 | purple_debug_info("jabber", |
317 | 0 | "got response from send data, but IBB session is no longer active\n"); |
318 | 0 | } |
319 | 0 | } |
320 | | |
321 | | void |
322 | | jabber_ibb_session_send_data(JabberIBBSession *sess, gconstpointer data, |
323 | | gsize size) |
324 | 0 | { |
325 | 0 | JabberIBBSessionState state = jabber_ibb_session_get_state(sess); |
326 | |
|
327 | 0 | purple_debug_info("jabber", "sending data block of %" G_GSIZE_FORMAT " bytes on IBB stream\n", |
328 | 0 | size); |
329 | |
|
330 | 0 | if (state != JABBER_IBB_SESSION_OPENED) { |
331 | 0 | purple_debug_error("jabber", |
332 | 0 | "trying to send data on a non-open IBB session\n"); |
333 | 0 | } else if (size > jabber_ibb_session_get_max_data_size(sess)) { |
334 | 0 | purple_debug_error("jabber", |
335 | 0 | "trying to send a too large packet in the IBB session\n"); |
336 | 0 | } else { |
337 | 0 | JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess), |
338 | 0 | JABBER_IQ_SET); |
339 | 0 | xmlnode *data_element = xmlnode_new("data"); |
340 | 0 | char *base64 = purple_base64_encode(data, size); |
341 | 0 | char seq[10]; |
342 | 0 | g_snprintf(seq, sizeof(seq), "%u", jabber_ibb_session_get_send_seq(sess)); |
343 | |
|
344 | 0 | xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess)); |
345 | 0 | xmlnode_set_namespace(data_element, NS_IBB); |
346 | 0 | xmlnode_set_attrib(data_element, "sid", jabber_ibb_session_get_sid(sess)); |
347 | 0 | xmlnode_set_attrib(data_element, "seq", seq); |
348 | 0 | xmlnode_insert_data(data_element, base64, -1); |
349 | |
|
350 | 0 | xmlnode_insert_child(set->node, data_element); |
351 | |
|
352 | 0 | purple_debug_info("jabber", |
353 | 0 | "IBB: setting send <iq/> callback for session %p %s\n", sess, |
354 | 0 | sess->sid); |
355 | 0 | jabber_iq_set_callback(set, jabber_ibb_session_send_acknowledge_cb, sess); |
356 | 0 | sess->last_iq_id = g_strdup(xmlnode_get_attrib(set->node, "id")); |
357 | 0 | purple_debug_info("jabber", "IBB: set sess->last_iq_id: %s\n", |
358 | 0 | sess->last_iq_id); |
359 | 0 | jabber_iq_send(set); |
360 | |
|
361 | 0 | g_free(base64); |
362 | 0 | (sess->send_seq)++; |
363 | 0 | } |
364 | 0 | } |
365 | | |
366 | | static void |
367 | | jabber_ibb_send_error_response(JabberStream *js, const char *to, const char *id) |
368 | 0 | { |
369 | 0 | JabberIq *result = jabber_iq_new(js, JABBER_IQ_ERROR); |
370 | 0 | xmlnode *error = xmlnode_new("error"); |
371 | 0 | xmlnode *item_not_found = xmlnode_new("item-not-found"); |
372 | |
|
373 | 0 | xmlnode_set_namespace(item_not_found, NS_XMPP_STANZAS); |
374 | 0 | xmlnode_set_attrib(error, "code", "440"); |
375 | 0 | xmlnode_set_attrib(error, "type", "cancel"); |
376 | 0 | jabber_iq_set_id(result, id); |
377 | 0 | xmlnode_set_attrib(result->node, "to", to); |
378 | 0 | xmlnode_insert_child(error, item_not_found); |
379 | 0 | xmlnode_insert_child(result->node, error); |
380 | |
|
381 | 0 | jabber_iq_send(result); |
382 | 0 | } |
383 | | |
384 | | void |
385 | | jabber_ibb_parse(JabberStream *js, const char *who, JabberIqType type, |
386 | | const char *id, xmlnode *child) |
387 | 0 | { |
388 | 0 | const char *name = child->name; |
389 | 0 | gboolean data = purple_strequal(name, "data"); |
390 | 0 | gboolean close = purple_strequal(name, "close"); |
391 | 0 | gboolean open = purple_strequal(name, "open"); |
392 | 0 | const gchar *sid = (data || close) ? |
393 | 0 | xmlnode_get_attrib(child, "sid") : NULL; |
394 | 0 | JabberIBBSession *sess = |
395 | 0 | sid ? g_hash_table_lookup(jabber_ibb_sessions, sid) : NULL; |
396 | |
|
397 | 0 | if (sess) { |
398 | |
|
399 | 0 | if (!purple_strequal(who, jabber_ibb_session_get_who(sess))) { |
400 | | /* the iq comes from a different JID than the remote JID of the |
401 | | session, ignore it */ |
402 | 0 | purple_debug_error("jabber", |
403 | 0 | "Got IBB iq from wrong JID, ignoring\n"); |
404 | 0 | } else if (data) { |
405 | 0 | const gchar *seq_attr = xmlnode_get_attrib(child, "seq"); |
406 | 0 | guint16 seq = (seq_attr ? atoi(seq_attr) : 0); |
407 | | |
408 | | /* reject the data, and set the session in error if we get an |
409 | | out-of-order packet */ |
410 | 0 | if (seq_attr && seq == jabber_ibb_session_get_recv_seq(sess)) { |
411 | | /* sequence # is the expected... */ |
412 | 0 | JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT); |
413 | |
|
414 | 0 | jabber_iq_set_id(result, id); |
415 | 0 | xmlnode_set_attrib(result->node, "to", who); |
416 | |
|
417 | 0 | if (sess->data_received_cb) { |
418 | 0 | gchar *base64 = xmlnode_get_data(child); |
419 | 0 | gsize size; |
420 | 0 | gpointer rawdata = purple_base64_decode(base64, &size); |
421 | |
|
422 | 0 | g_free(base64); |
423 | |
|
424 | 0 | if (rawdata) { |
425 | 0 | purple_debug_info("jabber", |
426 | 0 | "got %" G_GSIZE_FORMAT " bytes of data on IBB stream\n", |
427 | 0 | size); |
428 | | /* we accept other clients to send up to block-size |
429 | | of _unencoded_ data, since there's been some confusions |
430 | | regarding the interpretation of this attribute |
431 | | (including previous versions of libpurple) */ |
432 | 0 | if (size > jabber_ibb_session_get_block_size(sess)) { |
433 | 0 | purple_debug_error("jabber", |
434 | 0 | "IBB: received a too large packet\n"); |
435 | 0 | if (sess->error_cb) |
436 | 0 | sess->error_cb(sess); |
437 | 0 | g_free(rawdata); |
438 | 0 | return; |
439 | 0 | } else { |
440 | 0 | purple_debug_info("jabber", |
441 | 0 | "calling IBB callback for received data\n"); |
442 | 0 | sess->data_received_cb(sess, rawdata, size); |
443 | 0 | } |
444 | 0 | g_free(rawdata); |
445 | 0 | } else { |
446 | 0 | purple_debug_error("jabber", |
447 | 0 | "IBB: invalid BASE64 data received\n"); |
448 | 0 | if (sess->error_cb) |
449 | 0 | sess->error_cb(sess); |
450 | 0 | return; |
451 | |
|
452 | 0 | } |
453 | 0 | } |
454 | | |
455 | 0 | (sess->recv_seq)++; |
456 | 0 | jabber_iq_send(result); |
457 | |
|
458 | 0 | } else { |
459 | 0 | purple_debug_error("jabber", |
460 | 0 | "Received an out-of-order/invalid IBB packet\n"); |
461 | 0 | sess->state = JABBER_IBB_SESSION_ERROR; |
462 | |
|
463 | 0 | if (sess->error_cb) { |
464 | 0 | sess->error_cb(sess); |
465 | 0 | } |
466 | 0 | } |
467 | 0 | } else if (close) { |
468 | 0 | sess->state = JABBER_IBB_SESSION_CLOSED; |
469 | 0 | purple_debug_info("jabber", "IBB: received close\n"); |
470 | |
|
471 | 0 | if (sess->closed_cb) { |
472 | 0 | purple_debug_info("jabber", "IBB: calling closed handler\n"); |
473 | 0 | sess->closed_cb(sess); |
474 | 0 | } |
475 | 0 | } |
476 | 0 | } else if (open) { |
477 | 0 | JabberIq *result; |
478 | 0 | const GList *iterator; |
479 | | |
480 | | /* run all open handlers registered until one returns true */ |
481 | 0 | for (iterator = open_handlers ; iterator ; |
482 | 0 | iterator = g_list_next(iterator)) { |
483 | 0 | JabberIBBOpenHandler *handler = iterator->data; |
484 | |
|
485 | 0 | if (handler(js, who, id, child)) { |
486 | 0 | result = jabber_iq_new(js, JABBER_IQ_RESULT); |
487 | 0 | xmlnode_set_attrib(result->node, "to", who); |
488 | 0 | jabber_iq_set_id(result, id); |
489 | 0 | jabber_iq_send(result); |
490 | 0 | return; |
491 | 0 | } |
492 | 0 | } |
493 | | /* no open callback returned success, reject */ |
494 | 0 | jabber_ibb_send_error_response(js, who, id); |
495 | 0 | } else { |
496 | | /* send error reply */ |
497 | 0 | jabber_ibb_send_error_response(js, who, id); |
498 | 0 | } |
499 | 0 | } |
500 | | |
501 | | void |
502 | | jabber_ibb_register_open_handler(JabberIBBOpenHandler *cb) |
503 | 0 | { |
504 | 0 | open_handlers = g_list_append(open_handlers, cb); |
505 | 0 | } |
506 | | |
507 | | void |
508 | | jabber_ibb_unregister_open_handler(JabberIBBOpenHandler *cb) |
509 | 0 | { |
510 | 0 | open_handlers = g_list_remove(open_handlers, cb); |
511 | 0 | } |
512 | | |
513 | | void |
514 | | jabber_ibb_init(void) |
515 | 0 | { |
516 | 0 | jabber_ibb_sessions = g_hash_table_new(g_str_hash, g_str_equal); |
517 | |
|
518 | 0 | jabber_add_feature(NS_IBB, NULL); |
519 | |
|
520 | 0 | jabber_iq_register_handler("close", NS_IBB, jabber_ibb_parse); |
521 | 0 | jabber_iq_register_handler("data", NS_IBB, jabber_ibb_parse); |
522 | 0 | jabber_iq_register_handler("open", NS_IBB, jabber_ibb_parse); |
523 | 0 | } |
524 | | |
525 | | void |
526 | | jabber_ibb_uninit(void) |
527 | 0 | { |
528 | 0 | g_hash_table_destroy(jabber_ibb_sessions); |
529 | 0 | g_list_free(open_handlers); |
530 | 0 | jabber_ibb_sessions = NULL; |
531 | | open_handlers = NULL; |
532 | 0 | } |
533 | | |