| |
1 /* |
| |
2 * purple - Jabber Protocol Plugin |
| |
3 * |
| |
4 * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com> |
| |
5 * |
| |
6 * This program is free software; you can redistribute it and/or modify |
| |
7 * it under the terms of the GNU General Public License as published by |
| |
8 * the Free Software Foundation; either version 2 of the License, or |
| |
9 * (at your option) any later version. |
| |
10 * |
| |
11 * This program is distributed in the hope that it will be useful, |
| |
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| |
14 * GNU General Public License for more details. |
| |
15 * |
| |
16 * You should have received a copy of the GNU General Public License |
| |
17 * along with this program; if not, write to the Free Software |
| |
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
19 * |
| |
20 */ |
| |
21 |
| |
22 #include "caps.h" |
| |
23 #include <string.h> |
| |
24 #include "internal.h" |
| |
25 #include "util.h" |
| |
26 #include "iq.h" |
| |
27 |
| |
28 #define JABBER_CAPS_FILENAME "xmpp-caps.xml" |
| |
29 |
| |
30 static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */ |
| |
31 |
| |
32 typedef struct _JabberCapsKey { |
| |
33 char *node; |
| |
34 char *ver; |
| |
35 } JabberCapsKey; |
| |
36 |
| |
37 typedef struct _JabberCapsValueExt { |
| |
38 GList *identities; /* JabberCapsIdentity */ |
| |
39 GList *features; /* char * */ |
| |
40 } JabberCapsValueExt; |
| |
41 |
| |
42 typedef struct _JabberCapsValue { |
| |
43 GList *identities; /* JabberCapsIdentity */ |
| |
44 GList *features; /* char * */ |
| |
45 GHashTable *ext; /* char * -> JabberCapsValueExt */ |
| |
46 } JabberCapsValue; |
| |
47 |
| |
48 static guint jabber_caps_hash(gconstpointer key) { |
| |
49 const JabberCapsKey *name = key; |
| |
50 guint nodehash = g_str_hash(name->node); |
| |
51 guint verhash = g_str_hash(name->ver); |
| |
52 |
| |
53 return nodehash ^ verhash; |
| |
54 } |
| |
55 |
| |
56 static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) { |
| |
57 const JabberCapsKey *name1 = v1; |
| |
58 const JabberCapsKey *name2 = v2; |
| |
59 |
| |
60 return strcmp(name1->node,name2->node) == 0 && strcmp(name1->ver,name2->ver) == 0; |
| |
61 } |
| |
62 |
| |
63 static void jabber_caps_destroy_key(gpointer key) { |
| |
64 JabberCapsKey *keystruct = key; |
| |
65 g_free(keystruct->node); |
| |
66 g_free(keystruct->ver); |
| |
67 g_free(keystruct); |
| |
68 } |
| |
69 |
| |
70 static void jabber_caps_destroy_value(gpointer value) { |
| |
71 JabberCapsValue *valuestruct = value; |
| |
72 while(valuestruct->identities) { |
| |
73 JabberCapsIdentity *id = valuestruct->identities->data; |
| |
74 g_free(id->category); |
| |
75 g_free(id->type); |
| |
76 g_free(id->name); |
| |
77 g_free(id); |
| |
78 |
| |
79 valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities); |
| |
80 } |
| |
81 while(valuestruct->features) { |
| |
82 g_free(valuestruct->features->data); |
| |
83 valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features); |
| |
84 } |
| |
85 g_hash_table_destroy(valuestruct->ext); |
| |
86 g_free(valuestruct); |
| |
87 } |
| |
88 |
| |
89 static void jabber_caps_ext_destroy_value(gpointer value) { |
| |
90 JabberCapsValueExt *valuestruct = value; |
| |
91 while(valuestruct->identities) { |
| |
92 JabberCapsIdentity *id = valuestruct->identities->data; |
| |
93 g_free(id->category); |
| |
94 g_free(id->type); |
| |
95 g_free(id->name); |
| |
96 g_free(id); |
| |
97 |
| |
98 valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities); |
| |
99 } |
| |
100 while(valuestruct->features) { |
| |
101 g_free(valuestruct->features->data); |
| |
102 valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features); |
| |
103 } |
| |
104 g_free(valuestruct); |
| |
105 } |
| |
106 |
| |
107 static void jabber_caps_load(void); |
| |
108 |
| |
109 void jabber_caps_init(void) { |
| |
110 capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, jabber_caps_destroy_key, jabber_caps_destroy_value); |
| |
111 jabber_caps_load(); |
| |
112 } |
| |
113 |
| |
114 static void jabber_caps_load(void) { |
| |
115 xmlnode *capsdata = purple_util_read_xml_from_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache"); |
| |
116 xmlnode *client; |
| |
117 if(!capsdata || strcmp(capsdata->name, "capabilities")) |
| |
118 return; |
| |
119 |
| |
120 for(client = capsdata->child; client; client = client->next) { |
| |
121 if(client->type != XMLNODE_TYPE_TAG) |
| |
122 continue; |
| |
123 if(!strcmp(client->name, "client")) { |
| |
124 JabberCapsKey *key = g_new0(JabberCapsKey, 1); |
| |
125 JabberCapsValue *value = g_new0(JabberCapsValue, 1); |
| |
126 xmlnode *child; |
| |
127 key->node = g_strdup(xmlnode_get_attrib(client,"node")); |
| |
128 key->ver = g_strdup(xmlnode_get_attrib(client,"ver")); |
| |
129 value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value); |
| |
130 for(child = client->child; child; child = child->next) { |
| |
131 if(child->type != XMLNODE_TYPE_TAG) |
| |
132 continue; |
| |
133 if(!strcmp(child->name,"feature")) { |
| |
134 const char *var = xmlnode_get_attrib(child, "var"); |
| |
135 if(!var) |
| |
136 continue; |
| |
137 value->features = g_list_append(value->features,g_strdup(var)); |
| |
138 } else if(!strcmp(child->name,"identity")) { |
| |
139 const char *category = xmlnode_get_attrib(child, "category"); |
| |
140 const char *type = xmlnode_get_attrib(child, "type"); |
| |
141 const char *name = xmlnode_get_attrib(child, "name"); |
| |
142 |
| |
143 JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); |
| |
144 id->category = g_strdup(category); |
| |
145 id->type = g_strdup(type); |
| |
146 id->name = g_strdup(name); |
| |
147 |
| |
148 value->identities = g_list_append(value->identities,id); |
| |
149 } else if(!strcmp(child->name,"ext")) { |
| |
150 const char *identifier = xmlnode_get_attrib(child, "identifier"); |
| |
151 if(identifier) { |
| |
152 xmlnode *extchild; |
| |
153 |
| |
154 JabberCapsValueExt *extvalue = g_new0(JabberCapsValueExt, 1); |
| |
155 |
| |
156 for(extchild = child->child; extchild; extchild = extchild->next) { |
| |
157 if(extchild->type != XMLNODE_TYPE_TAG) |
| |
158 continue; |
| |
159 if(!strcmp(extchild->name,"feature")) { |
| |
160 const char *var = xmlnode_get_attrib(extchild, "var"); |
| |
161 if(!var) |
| |
162 continue; |
| |
163 extvalue->features = g_list_append(extvalue->features,g_strdup(var)); |
| |
164 } else if(!strcmp(extchild->name,"identity")) { |
| |
165 const char *category = xmlnode_get_attrib(extchild, "category"); |
| |
166 const char *type = xmlnode_get_attrib(extchild, "type"); |
| |
167 const char *name = xmlnode_get_attrib(extchild, "name"); |
| |
168 |
| |
169 JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); |
| |
170 id->category = g_strdup(category); |
| |
171 id->type = g_strdup(type); |
| |
172 id->name = g_strdup(name); |
| |
173 |
| |
174 extvalue->identities = g_list_append(extvalue->identities,id); |
| |
175 } |
| |
176 } |
| |
177 g_hash_table_replace(value->ext, g_strdup(identifier), extvalue); |
| |
178 } |
| |
179 } |
| |
180 } |
| |
181 g_hash_table_replace(capstable, key, value); |
| |
182 } |
| |
183 } |
| |
184 } |
| |
185 |
| |
186 static void jabber_caps_store_ext(gpointer key, gpointer value, gpointer user_data) { |
| |
187 const char *extname = key; |
| |
188 JabberCapsValueExt *props = value; |
| |
189 xmlnode *root = user_data; |
| |
190 xmlnode *ext = xmlnode_new_child(root,"ext"); |
| |
191 GList *iter; |
| |
192 |
| |
193 xmlnode_set_attrib(ext,"identifier",extname); |
| |
194 |
| |
195 for(iter = props->identities; iter; iter = g_list_next(iter)) { |
| |
196 JabberCapsIdentity *id = iter->data; |
| |
197 xmlnode *identity = xmlnode_new_child(ext, "identity"); |
| |
198 xmlnode_set_attrib(identity, "category", id->category); |
| |
199 xmlnode_set_attrib(identity, "type", id->type); |
| |
200 xmlnode_set_attrib(identity, "name", id->name); |
| |
201 } |
| |
202 |
| |
203 for(iter = props->features; iter; iter = g_list_next(iter)) { |
| |
204 const char *feat = iter->data; |
| |
205 xmlnode *feature = xmlnode_new_child(ext, "feature"); |
| |
206 xmlnode_set_attrib(feature, "var", feat); |
| |
207 } |
| |
208 } |
| |
209 |
| |
210 static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) { |
| |
211 JabberCapsKey *clientinfo = key; |
| |
212 JabberCapsValue *props = value; |
| |
213 xmlnode *root = user_data; |
| |
214 xmlnode *client = xmlnode_new_child(root,"client"); |
| |
215 GList *iter; |
| |
216 |
| |
217 xmlnode_set_attrib(client,"node",clientinfo->node); |
| |
218 xmlnode_set_attrib(client,"ver",clientinfo->ver); |
| |
219 |
| |
220 for(iter = props->identities; iter; iter = g_list_next(iter)) { |
| |
221 JabberCapsIdentity *id = iter->data; |
| |
222 xmlnode *identity = xmlnode_new_child(client, "identity"); |
| |
223 xmlnode_set_attrib(identity, "category", id->category); |
| |
224 xmlnode_set_attrib(identity, "type", id->type); |
| |
225 xmlnode_set_attrib(identity, "name", id->name); |
| |
226 } |
| |
227 |
| |
228 for(iter = props->features; iter; iter = g_list_next(iter)) { |
| |
229 const char *feat = iter->data; |
| |
230 xmlnode *feature = xmlnode_new_child(client, "feature"); |
| |
231 xmlnode_set_attrib(feature, "var", feat); |
| |
232 } |
| |
233 |
| |
234 g_hash_table_foreach(props->ext,jabber_caps_store_ext,client); |
| |
235 } |
| |
236 |
| |
237 static void jabber_caps_store(void) { |
| |
238 xmlnode *root = xmlnode_new("capabilities"); |
| |
239 g_hash_table_foreach(capstable, jabber_caps_store_client, root); |
| |
240 purple_util_write_data_to_file(JABBER_CAPS_FILENAME, xmlnode_to_formatted_str(root, NULL), -1); |
| |
241 } |
| |
242 |
| |
243 /* this function assumes that all information is available locally */ |
| |
244 static JabberCapsClientInfo *jabber_caps_collect_info(const char *node, const char *ver, GList *ext) { |
| |
245 JabberCapsClientInfo *result = g_new0(JabberCapsClientInfo, 1); |
| |
246 JabberCapsKey *key = g_new0(JabberCapsKey, 1); |
| |
247 JabberCapsValue *caps; |
| |
248 GList *iter; |
| |
249 |
| |
250 key->node = g_strdup(node); |
| |
251 key->ver = g_strdup(ver); |
| |
252 |
| |
253 caps = g_hash_table_lookup(capstable,key); |
| |
254 |
| |
255 g_free(key->node); |
| |
256 g_free(key->ver); |
| |
257 g_free(key); |
| |
258 |
| |
259 /* join all information */ |
| |
260 for(iter = caps->identities; iter; iter = g_list_next(iter)) { |
| |
261 JabberCapsIdentity *id = iter->data; |
| |
262 JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1); |
| |
263 newid->category = g_strdup(id->category); |
| |
264 newid->type = g_strdup(id->type); |
| |
265 newid->name = g_strdup(id->name); |
| |
266 |
| |
267 result->identities = g_list_append(result->identities,newid); |
| |
268 } |
| |
269 for(iter = caps->features; iter; iter = g_list_next(iter)) { |
| |
270 const char *feat = iter->data; |
| |
271 char *newfeat = g_strdup(feat); |
| |
272 |
| |
273 result->features = g_list_append(result->features,newfeat); |
| |
274 } |
| |
275 |
| |
276 for(iter = ext; iter; iter = g_list_next(iter)) { |
| |
277 const char *ext = iter->data; |
| |
278 JabberCapsValueExt *extinfo = g_hash_table_lookup(caps->ext,ext); |
| |
279 |
| |
280 if(extinfo) { |
| |
281 for(iter = extinfo->identities; iter; iter = g_list_next(iter)) { |
| |
282 JabberCapsIdentity *id = iter->data; |
| |
283 JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1); |
| |
284 newid->category = g_strdup(id->category); |
| |
285 newid->type = g_strdup(id->type); |
| |
286 newid->name = g_strdup(id->name); |
| |
287 |
| |
288 result->identities = g_list_append(result->identities,newid); |
| |
289 } |
| |
290 for(iter = extinfo->features; iter; iter = g_list_next(iter)) { |
| |
291 const char *feat = iter->data; |
| |
292 char *newfeat = g_strdup(feat); |
| |
293 |
| |
294 result->features = g_list_append(result->features,newfeat); |
| |
295 } |
| |
296 } |
| |
297 } |
| |
298 return result; |
| |
299 } |
| |
300 |
| |
301 void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo) { |
| |
302 if(!clientinfo) |
| |
303 return; |
| |
304 while(clientinfo->identities) { |
| |
305 JabberCapsIdentity *id = clientinfo->identities->data; |
| |
306 g_free(id->category); |
| |
307 g_free(id->type); |
| |
308 g_free(id->name); |
| |
309 g_free(id); |
| |
310 |
| |
311 clientinfo->identities = g_list_remove_link(clientinfo->identities,clientinfo->identities); |
| |
312 } |
| |
313 while(clientinfo->features) { |
| |
314 char *feat = clientinfo->features->data; |
| |
315 g_free(feat); |
| |
316 |
| |
317 clientinfo->features = g_list_remove_link(clientinfo->features,clientinfo->features); |
| |
318 } |
| |
319 |
| |
320 g_free(clientinfo); |
| |
321 } |
| |
322 |
| |
323 typedef struct _jabber_caps_cbplususerdata { |
| |
324 jabber_caps_get_info_cb cb; |
| |
325 gpointer user_data; |
| |
326 |
| |
327 char *who; |
| |
328 char *node; |
| |
329 char *ver; |
| |
330 GList *ext; |
| |
331 unsigned extOutstanding; |
| |
332 } jabber_caps_cbplususerdata; |
| |
333 |
| |
334 static void jabber_caps_get_info_check_completion(jabber_caps_cbplususerdata *userdata) { |
| |
335 if(userdata->extOutstanding == 0) { |
| |
336 userdata->cb(jabber_caps_collect_info(userdata->node, userdata->ver, userdata->ext), userdata->user_data); |
| |
337 g_free(userdata->who); |
| |
338 g_free(userdata->node); |
| |
339 g_free(userdata->ver); |
| |
340 while(userdata->ext) { |
| |
341 g_free(userdata->ext->data); |
| |
342 userdata->ext = g_list_remove_link(userdata->ext,userdata->ext); |
| |
343 } |
| |
344 g_free(userdata); |
| |
345 } |
| |
346 } |
| |
347 |
| |
348 static void jabber_caps_ext_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { |
| |
349 /* collect data and fetch all exts */ |
| |
350 xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info"); |
| |
351 xmlnode *child; |
| |
352 jabber_caps_cbplususerdata *userdata = data; |
| |
353 JabberCapsKey *clientkey = g_new0(JabberCapsKey, 1); |
| |
354 JabberCapsValue *client; |
| |
355 JabberCapsValueExt *value = g_new0(JabberCapsValueExt, 1); |
| |
356 const char *node = xmlnode_get_attrib(query, "node"); |
| |
357 const char *key; |
| |
358 |
| |
359 --userdata->extOutstanding; |
| |
360 |
| |
361 if(node) { |
| |
362 clientkey->node = g_strdup(userdata->node); |
| |
363 clientkey->ver = g_strdup(userdata->ver); |
| |
364 |
| |
365 client = g_hash_table_lookup(capstable,clientkey); |
| |
366 |
| |
367 g_free(clientkey->node); |
| |
368 g_free(clientkey->ver); |
| |
369 g_free(clientkey); |
| |
370 |
| |
371 /* split node by #, key either points to \0 or the correct ext afterwards */ |
| |
372 for(key = node; key[0] != '\0'; ++key) { |
| |
373 if(key[0] == '#') { |
| |
374 ++key; |
| |
375 break; |
| |
376 } |
| |
377 } |
| |
378 |
| |
379 for(child = query->child; child; child = child->next) { |
| |
380 if(child->type != XMLNODE_TYPE_TAG) |
| |
381 continue; |
| |
382 if(!strcmp(child->name,"feature")) { |
| |
383 const char *var = xmlnode_get_attrib(child, "var"); |
| |
384 if(!var) |
| |
385 continue; |
| |
386 value->features = g_list_append(value->features,g_strdup(var)); |
| |
387 } else if(!strcmp(child->name,"identity")) { |
| |
388 const char *category = xmlnode_get_attrib(child, "category"); |
| |
389 const char *type = xmlnode_get_attrib(child, "type"); |
| |
390 const char *name = xmlnode_get_attrib(child, "name"); |
| |
391 |
| |
392 JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); |
| |
393 id->category = g_strdup(category); |
| |
394 id->type = g_strdup(type); |
| |
395 id->name = g_strdup(name); |
| |
396 |
| |
397 value->identities = g_list_append(value->identities,id); |
| |
398 } |
| |
399 } |
| |
400 g_hash_table_replace(client->ext, g_strdup(key), value); |
| |
401 |
| |
402 jabber_caps_store(); |
| |
403 } |
| |
404 |
| |
405 jabber_caps_get_info_check_completion(userdata); |
| |
406 } |
| |
407 |
| |
408 static void jabber_caps_client_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { |
| |
409 /* collect data and fetch all exts */ |
| |
410 xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info"); |
| |
411 xmlnode *child; |
| |
412 GList *iter; |
| |
413 jabber_caps_cbplususerdata *userdata = data; |
| |
414 JabberCapsKey *key = g_new0(JabberCapsKey, 1); |
| |
415 JabberCapsValue *value = g_new0(JabberCapsValue, 1); |
| |
416 key->node = g_strdup(userdata->node); |
| |
417 key->ver = g_strdup(userdata->ver); |
| |
418 |
| |
419 value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value); |
| |
420 |
| |
421 for(child = query->child; child; child = child->next) { |
| |
422 if(child->type != XMLNODE_TYPE_TAG) |
| |
423 continue; |
| |
424 if(!strcmp(child->name,"feature")) { |
| |
425 const char *var = xmlnode_get_attrib(child, "var"); |
| |
426 if(!var) |
| |
427 continue; |
| |
428 value->features = g_list_append(value->features,g_strdup(var)); |
| |
429 } else if(!strcmp(child->name,"identity")) { |
| |
430 const char *category = xmlnode_get_attrib(child, "category"); |
| |
431 const char *type = xmlnode_get_attrib(child, "type"); |
| |
432 const char *name = xmlnode_get_attrib(child, "name"); |
| |
433 |
| |
434 JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); |
| |
435 id->category = g_strdup(category); |
| |
436 id->type = g_strdup(type); |
| |
437 id->name = g_strdup(name); |
| |
438 |
| |
439 value->identities = g_list_append(value->identities,id); |
| |
440 } |
| |
441 } |
| |
442 g_hash_table_replace(capstable, key, value); |
| |
443 |
| |
444 /* fetch all exts */ |
| |
445 for(iter = userdata->ext; iter; iter = g_list_next(iter)) { |
| |
446 JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); |
| |
447 xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); |
| |
448 char *node = g_strdup_printf("%s#%s", node, (const char*)iter->data); |
| |
449 xmlnode_set_attrib(query, "node", node); |
| |
450 g_free(node); |
| |
451 xmlnode_set_attrib(iq->node, "to", userdata->who); |
| |
452 |
| |
453 jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,userdata); |
| |
454 jabber_iq_send(iq); |
| |
455 } |
| |
456 |
| |
457 jabber_caps_store(); |
| |
458 |
| |
459 jabber_caps_get_info_check_completion(userdata); |
| |
460 } |
| |
461 |
| |
462 void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data) { |
| |
463 JabberCapsValue *client; |
| |
464 JabberCapsKey *key = g_new0(JabberCapsKey, 1); |
| |
465 char *originalext = g_strdup(ext); |
| |
466 char *oneext, *ctx; |
| |
467 jabber_caps_cbplususerdata *userdata = g_new0(jabber_caps_cbplususerdata, 1); |
| |
468 userdata->cb = cb; |
| |
469 userdata->user_data = user_data; |
| |
470 userdata->who = g_strdup(who); |
| |
471 userdata->node = g_strdup(node); |
| |
472 userdata->ver = g_strdup(ver); |
| |
473 |
| |
474 if(originalext) |
| |
475 for(oneext = strtok_r(originalext, " ", &ctx); oneext; oneext = strtok_r(NULL, " ", &ctx)) { |
| |
476 userdata->ext = g_list_append(userdata->ext,g_strdup(oneext)); |
| |
477 ++userdata->extOutstanding; |
| |
478 } |
| |
479 g_free(originalext); |
| |
480 |
| |
481 key->node = g_strdup(node); |
| |
482 key->ver = g_strdup(ver); |
| |
483 |
| |
484 client = g_hash_table_lookup(capstable, key); |
| |
485 |
| |
486 g_free(key->node); |
| |
487 g_free(key->ver); |
| |
488 g_free(key); |
| |
489 |
| |
490 if(!client) { |
| |
491 JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); |
| |
492 xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); |
| |
493 char *nodever = g_strdup_printf("%s#%s", node, ver); |
| |
494 xmlnode_set_attrib(query, "node", nodever); |
| |
495 g_free(nodever); |
| |
496 xmlnode_set_attrib(iq->node, "to", who); |
| |
497 |
| |
498 jabber_iq_set_callback(iq,jabber_caps_client_iqcb,userdata); |
| |
499 jabber_iq_send(iq); |
| |
500 } else { |
| |
501 GList *iter; |
| |
502 /* fetch unknown exts only */ |
| |
503 for(iter = userdata->ext; iter; iter = g_list_next(iter)) { |
| |
504 JabberCapsValueExt *extvalue = g_hash_table_lookup(client->ext, (const char*)iter->data); |
| |
505 JabberIq *iq; |
| |
506 xmlnode *query; |
| |
507 char *nodever; |
| |
508 |
| |
509 if(extvalue) { |
| |
510 /* we already have this ext, don't bother with it */ |
| |
511 --userdata->extOutstanding; |
| |
512 continue; |
| |
513 } |
| |
514 |
| |
515 iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); |
| |
516 query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); |
| |
517 nodever = g_strdup_printf("%s#%s", node, (const char*)iter->data); |
| |
518 xmlnode_set_attrib(query, "node", nodever); |
| |
519 g_free(nodever); |
| |
520 xmlnode_set_attrib(iq->node, "to", who); |
| |
521 |
| |
522 jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,userdata); |
| |
523 jabber_iq_send(iq); |
| |
524 } |
| |
525 /* maybe we have all data available anyways? This is the ideal case where no network traffic is necessary */ |
| |
526 jabber_caps_get_info_check_completion(userdata); |
| |
527 } |
| |
528 } |
| |
529 |