comparison libpurple/protocols/jabber/caps.c @ 17609:f88b3a093cba

Implemented ad-hoc commands for the buddy action menu (untested), implemented the receiving end of XEP-0115: Entity Capabilities. Note that this seems not to be reliable right now, since some clients seem to have a very broken [read: completely non-functional] implementation (most notably Gajim and the py-transports).
author Andreas Monitzer <pidgin@monitzer.com>
date Sat, 23 Jun 2007 02:57:21 +0000
parents
children 7c79957207c3
comparison
equal deleted inserted replaced
17608:a8b1159fd95b 17609:f88b3a093cba
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