comparison mcabber/mcabber/caps.c @ 1999:51f032d5ca22

Add support for XEP-0115 Entity Capabilities, with offline cache
author Hermitifier
date Mon, 03 Oct 2011 16:00:34 +0200
parents c30fa2baf387
children 76d7c5721210
comparison
equal deleted inserted replaced
1998:41667bc02883 1999:51f032d5ca22
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 * USA 19 * USA
20 */ 20 */
21 21
22 #include <glib.h> 22 #include <glib.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27
28 #include "settings.h"
29 #include "utils.h"
23 30
24 typedef struct { 31 typedef struct {
25 char *category; 32 char *category;
33 char *type;
26 char *name; 34 char *name;
27 char *type; 35 } identity;
36
37 typedef struct {
38 GHashTable *fields;
39 } dataform;
40
41 typedef struct {
42 GHashTable *identities;
28 GHashTable *features; 43 GHashTable *features;
44 GHashTable *forms;
29 } caps; 45 } caps;
30 46
31 static GHashTable *caps_cache = NULL; 47 static GHashTable *caps_cache = NULL;
32 48
33 void caps_destroy(gpointer data) 49 void caps_destroy(gpointer data)
34 { 50 {
35 caps *c = data; 51 caps *c = data;
36 g_free(c->category); 52 g_hash_table_destroy(c->identities);
37 g_free(c->name);
38 g_free(c->type);
39 g_hash_table_destroy(c->features); 53 g_hash_table_destroy(c->features);
54 g_hash_table_destroy(c->forms);
40 g_free(c); 55 g_free(c);
56 }
57
58 void identity_destroy(gpointer data)
59 {
60 identity *i = data;
61 g_free(i->category);
62 g_free(i->type);
63 g_free(i->name);
64 g_free(i);
65 }
66
67 void form_destroy(gpointer data)
68 {
69 dataform *f = data;
70 g_hash_table_destroy(f->fields);
71 g_free(f);
72 }
73
74 void field_destroy(gpointer data)
75 {
76 GList *v = data;
77 g_list_free_full(v, g_free);
41 } 78 }
42 79
43 void caps_init(void) 80 void caps_init(void)
44 { 81 {
45 if (!caps_cache) 82 if (!caps_cache)
53 g_hash_table_destroy(caps_cache); 90 g_hash_table_destroy(caps_cache);
54 caps_cache = NULL; 91 caps_cache = NULL;
55 } 92 }
56 } 93 }
57 94
58 void caps_add(char *hash) 95 void caps_add(const char *hash)
59 { 96 {
60 if (!hash) 97 if (!hash)
61 return; 98 return;
62 caps *c = g_new0(caps, 1); 99 caps *c = g_new0(caps, 1);
63 c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); 100 c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
101 c->identities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, identity_destroy);
102 c->forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, form_destroy);
64 g_hash_table_insert(caps_cache, g_strdup(hash), c); 103 g_hash_table_insert(caps_cache, g_strdup(hash), c);
65 } 104 }
66 105
67 int caps_has_hash(const char *hash) 106 void caps_remove(const char *hash)
68 { 107 {
69 return (hash != NULL && (g_hash_table_lookup(caps_cache, hash) != NULL)); 108 if (!hash)
109 return;
110 g_hash_table_remove(caps_cache, hash);
111 }
112
113 void caps_move_to_local(char *hash, char *bjid)
114 {
115 char *orig_hash;
116 caps *c = NULL;
117 if (!hash || !bjid)
118 return;
119 g_hash_table_lookup_extended(caps_cache, hash, (gpointer*)&orig_hash, (gpointer*)&c);
120 if (c) {
121 g_hash_table_steal(caps_cache, hash);
122 g_free(orig_hash);
123 g_hash_table_replace(caps_cache, g_strdup_printf("%s/#%s", bjid, hash), c);
124 // solidus is guaranteed to never appear in bare jid
125 // hash will not appear in base64 encoded hash
126 // sequence "/#" is deterministic separator, and allows to identify local cache entry
127 }
128 }
129
130 int caps_has_hash(const char *hash, const char *bjid)
131 {
132 caps *c = NULL;
133 if (!hash)
134 return 0;
135 c = g_hash_table_lookup(caps_cache, hash);
136 if (!c && bjid) {
137 char *key = g_strdup_printf("%s/#%s", bjid, hash);
138 c = g_hash_table_lookup(caps_cache, key);
139 g_free(key);
140 }
141 return (c != NULL);
142 }
143
144 void caps_add_identity(const char *hash,
145 const char *category,
146 const char *name,
147 const char *type,
148 const char *lang)
149 {
150 caps *c;
151 if (!hash || !category || !type)
152 return;
153 if (!lang)
154 lang = "";
155
156 c = g_hash_table_lookup(caps_cache, hash);
157 if (c) {
158 identity *i = g_new0(identity, 1);
159
160 i->category = g_strdup(category);
161 i->name = g_strdup(name);
162 i->type = g_strdup(type);
163 g_hash_table_replace(c->identities, g_strdup(lang), i);
164 }
70 } 165 }
71 166
72 void caps_set_identity(char *hash, 167 void caps_set_identity(char *hash,
73 const char *category, 168 const char *category,
74 const char *name, 169 const char *name,
75 const char *type) 170 const char *type)
76 { 171 {
172 caps_add_identity(hash, category, name, type, NULL);
173 }
174
175 void caps_add_dataform(const char *hash, const char *formtype)
176 {
77 caps *c; 177 caps *c;
78 if (!hash || !category || !type) 178 if (!formtype)
79 return; 179 return;
80
81 c = g_hash_table_lookup(caps_cache, hash); 180 c = g_hash_table_lookup(caps_cache, hash);
82 if (c) { 181 if (c) {
83 c->category = g_strdup(category); 182 dataform *d = g_new0(dataform, 1);
84 c->name = g_strdup(name); 183 char *f = g_strdup(formtype);
85 c->type = g_strdup(type); 184
86 } 185 d->fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, field_destroy);
87 } 186 g_hash_table_replace(c->forms, f, d);
88 187 }
89 void caps_add_feature(char *hash, const char *feature) 188 }
189
190 gint _strcmp_sort(gconstpointer a, gconstpointer b)
191 {
192 return g_strcmp0(a, b);
193 }
194
195 void caps_add_dataform_field(const char *hash, const char *formtype,
196 const char *field, const char *value)
197 {
198 caps *c;
199 if (!formtype || !field || !value)
200 return;
201 c = g_hash_table_lookup(caps_cache, hash);
202 if (c) {
203 dataform *d;
204 d = g_hash_table_lookup(c->forms, formtype);
205 if (d) {
206 gpointer key, val;
207 char *f;
208 GList *v = NULL;
209 if (g_hash_table_lookup_extended(d->fields, field, &key, &val)) {
210 g_hash_table_steal(d->fields, field);
211 g_free(key);
212 v = val;
213 }
214 f = g_strdup(field);
215 v = g_list_insert_sorted(v, g_strdup(value), _strcmp_sort);
216 g_hash_table_replace(d->fields, f, v);
217 }
218 }
219 }
220
221 void caps_add_feature(const char *hash, const char *feature)
90 { 222 {
91 caps *c; 223 caps *c;
92 if (!hash || !feature) 224 if (!hash || !feature)
93 return; 225 return;
94 c = g_hash_table_lookup(caps_cache, hash); 226 c = g_hash_table_lookup(caps_cache, hash);
96 char *f = g_strdup(feature); 228 char *f = g_strdup(feature);
97 g_hash_table_replace(c->features, f, f); 229 g_hash_table_replace(c->features, f, f);
98 } 230 }
99 } 231 }
100 232
101 int caps_has_feature(char *hash, char *feature) 233 int caps_has_feature(const char *hash, char *feature, char *bjid)
102 { 234 {
103 caps *c; 235 caps *c = NULL;
104 if (!hash || !feature) 236 if (!hash || !feature)
105 return 0; 237 return 0;
106 c = g_hash_table_lookup(caps_cache, hash); 238 c = g_hash_table_lookup(caps_cache, hash);
239 if (!c && bjid) {
240 char *key = g_strdup_printf("%s/#%s", bjid, hash);
241 c = g_hash_table_lookup(caps_cache, key);
242 g_free(key);
243 }
107 if (c) 244 if (c)
108 return (g_hash_table_lookup(c->features, feature) != NULL); 245 return (g_hash_table_lookup(c->features, feature) != NULL);
109 return 0; 246 return 0;
110 } 247 }
111 248
127 return; 264 return;
128 _foreach_function = func; 265 _foreach_function = func;
129 g_hash_table_foreach(c->features, _caps_foreach_helper, user_data); 266 g_hash_table_foreach(c->features, _caps_foreach_helper, user_data);
130 } 267 }
131 268
132 gint _strcmp_sort(gconstpointer a, gconstpointer b)
133 {
134 return g_strcmp0(a, b);
135 }
136
137 // Generates the sha1 hash for the special capability "" and returns it 269 // Generates the sha1 hash for the special capability "" and returns it
138 const char *caps_generate(void) 270 const char *caps_generate(void)
139 { 271 {
140 char *identity; 272 GList *features, *langs;
141 GList *features;
142 GChecksum *sha1; 273 GChecksum *sha1;
143 guint8 digest[20]; 274 guint8 digest[20];
144 gsize digest_size = 20; 275 gsize digest_size = 20;
145 gchar *hash, *old_hash = NULL; 276 gchar *hash, *old_hash = NULL;
146 caps *old_caps; 277 caps *old_caps;
147 caps *c = g_hash_table_lookup(caps_cache, ""); 278 caps *c = g_hash_table_lookup(caps_cache, "");
148 279
149 g_hash_table_steal(caps_cache, ""); 280 g_hash_table_steal(caps_cache, "");
150 sha1 = g_checksum_new(G_CHECKSUM_SHA1); 281 sha1 = g_checksum_new(G_CHECKSUM_SHA1);
151 identity = g_strdup_printf("%s/%s//%s<", c->category, c->type, 282
152 c->name ? c->name : ""); 283 langs = g_hash_table_get_keys(c->identities);
153 g_checksum_update(sha1, (guchar*)identity, -1); 284 langs = g_list_sort(langs, _strcmp_sort);
154 g_free(identity); 285 {
286 identity *i;
287 GList *lang;
288 char *identity_S;
289 for (lang=langs; lang; lang=lang->next) {
290 i = g_hash_table_lookup(c->identities, lang->data);
291 identity_S = g_strdup_printf("%s/%s/%s/%s<", i->category, i->type,
292 (char *)lang->data, i->name ? i->name : "");
293 g_checksum_update(sha1, (guchar *)identity_S, -1);
294 g_free(identity_S);
295 }
296 }
297 g_list_free(langs);
155 298
156 features = g_hash_table_get_values(c->features); 299 features = g_hash_table_get_values(c->features);
157 features = g_list_sort(features, _strcmp_sort); 300 features = g_list_sort(features, _strcmp_sort);
158 { 301 {
159 GList *feature; 302 GList *feature;
174 return old_hash; 317 return old_hash;
175 else 318 else
176 return hash; 319 return hash;
177 } 320 }
178 321
322 gboolean caps_verify(const char *hash, char *function)
323 {
324 GList *features, *langs, *forms;
325 GChecksum *checksum;
326 guint8 digest[20];
327 gsize digest_size = 20;
328 gchar *local_hash;
329 gboolean match = FALSE;
330 caps *c = g_hash_table_lookup(caps_cache, hash);
331
332 if (!g_strcmp0(function, "sha-1")) {
333 checksum = g_checksum_new(G_CHECKSUM_SHA1);
334 } else if (!g_strcmp0(function, "md5")) {
335 checksum = g_checksum_new(G_CHECKSUM_MD5);
336 digest_size = 16;
337 } else
338 return FALSE;
339
340 langs = g_hash_table_get_keys(c->identities);
341 langs = g_list_sort(langs, _strcmp_sort);
342 {
343 identity *i;
344 GList *lang;
345 char *identity_S;
346 for (lang=langs; lang; lang=lang->next) {
347 i = g_hash_table_lookup(c->identities, lang->data);
348 identity_S = g_strdup_printf("%s/%s/%s/%s<", i->category, i->type,
349 (char *)lang->data, i->name ? i->name : "");
350 g_checksum_update(checksum, (guchar *)identity_S, -1);
351 g_free(identity_S);
352 }
353 }
354 g_list_free(langs);
355
356 features = g_hash_table_get_values(c->features);
357 features = g_list_sort(features, _strcmp_sort);
358 {
359 GList *feature;
360 for (feature=features; feature; feature=feature->next) {
361 g_checksum_update(checksum, feature->data, -1);
362 g_checksum_update(checksum, (guchar *)"<", -1);
363 }
364 }
365 g_list_free(features);
366
367 forms = g_hash_table_get_keys(c->forms);
368 forms = g_list_sort(forms, _strcmp_sort);
369 {
370 dataform *d;
371 GList *form, *fields;
372 for (form=forms; form; form=form->next) {
373 d = g_hash_table_lookup(c->forms, form->data);
374 g_checksum_update(checksum, form->data, -1);
375 g_checksum_update(checksum, (guchar *)"<", -1);
376 fields = g_hash_table_get_keys(d->fields);
377 fields = g_list_sort(fields, _strcmp_sort);
378 {
379 GList *field;
380 GList *values;
381 for (field=fields; field; field=field->next) {
382 g_checksum_update(checksum, field->data, -1);
383 g_checksum_update(checksum, (guchar *)"<", -1);
384 values = g_hash_table_lookup(d->fields, field->data);
385 {
386 GList *value;
387 for (value=values; value; value=value->next) {
388 g_checksum_update(checksum, value->data, -1);
389 g_checksum_update(checksum, (guchar *)"<", -1);
390 }
391 }
392 }
393 }
394 g_list_free(fields);
395 }
396 }
397 g_list_free(forms);
398
399 g_checksum_get_digest(checksum, digest, &digest_size);
400 local_hash = g_base64_encode(digest, digest_size);
401 g_checksum_free(checksum);
402
403 match = !g_strcmp0(hash, local_hash);
404
405 g_free(local_hash);
406 return match;
407 }
408
409 static gchar* caps_get_filename(const char* hash)
410 {
411 gchar *hash_fs = g_strdup (hash);
412 gchar *dir = (gchar *) settings_opt_get ("caps_directory");
413 gchar *file = NULL;
414
415 if (!dir)
416 goto caps_filename_return;
417
418 {
419 const gchar *valid_fs =
420 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+=";
421 g_strcanon(hash_fs, valid_fs, '-');
422 }
423
424 dir = expand_filename (dir);
425 file = g_strdup_printf ("%s/%s.ini", dir, hash_fs);
426 g_free(dir);
427
428 caps_filename_return:
429 g_free(hash_fs);
430 return file;
431 }
432
433 void caps_copy_to_persistent(const char* hash, char* xml)
434 {
435 gchar *file;
436 GList *features, *langs, *forms;
437 GKeyFile *key_file;
438 caps *c;
439 int fd;
440
441 g_free (xml);
442
443 c = g_hash_table_lookup (caps_cache, hash);
444 if (!c)
445 goto caps_copy_return;
446
447 file = caps_get_filename (hash);
448 if (!file)
449 goto caps_copy_return;
450
451 fd = open (file, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
452 if (fd == -1)
453 goto caps_copy_exists;
454
455 key_file = g_key_file_new ();
456
457 langs = g_hash_table_get_keys (c->identities);
458 {
459 identity *i;
460 GList *lang;
461 gchar *group;
462 for (lang=langs; lang; lang=lang->next) {
463 i = g_hash_table_lookup (c->identities, lang->data);
464 group = g_strdup_printf("identity_%s", (gchar *)lang->data);
465 g_key_file_set_string (key_file, group, "category", i->category);
466 g_key_file_set_string (key_file, group, "type", i->type);
467 g_key_file_set_string (key_file, group, "name", i->name);
468 g_free (group);
469 }
470 }
471 g_list_free (langs);
472
473 features = g_hash_table_get_values (c->features);
474 {
475 GList *feature;
476 gchar **string_list;
477 gint i;
478
479 i = g_list_length (features);
480 string_list = g_new (gchar*, i + 1);
481 i = 0;
482 for (feature=features; feature; feature=feature->next) {
483 string_list[i] = g_strdup(feature->data);
484 ++i;
485 }
486 string_list[i] = NULL;
487
488 g_key_file_set_string_list (key_file, "features", "features",
489 (const gchar**)string_list, i);
490 g_strfreev (string_list);
491 }
492 g_list_free (features);
493
494 forms = g_hash_table_get_keys(c->forms);
495 {
496 dataform *d;
497 GList *form, *fields;
498 gchar *group;
499 for (form=forms; form; form=form->next) {
500 d = g_hash_table_lookup (c->forms, form->data);
501 group = g_strdup_printf ("form_%s", (gchar *)form->data);
502 fields = g_hash_table_get_keys(d->fields);
503 {
504 GList *field;
505 GList *values;
506 for (field=fields; field; field=field->next) {
507 values = g_hash_table_lookup (d->fields, field->data);
508 {
509 GList *value;
510 gchar **string_list;
511 gint i;
512 i = g_list_length (values);
513 string_list = g_new (gchar*, i + 1);
514 i = 0;
515 for (value=values; value; value=value->next) {
516 string_list[i] = g_strdup(value->data);
517 ++i;
518 }
519 string_list[i] = NULL;
520
521 g_key_file_set_string_list (key_file, group, field->data,
522 (const gchar**)string_list, i);
523
524 g_strfreev (string_list);
525 }
526 }
527 }
528 g_list_free(fields);
529 g_free (group);
530 }
531 }
532 g_list_free (forms);
533
534 {
535 gchar *data;
536 gsize length;
537 data = g_key_file_to_data (key_file, &length, NULL);
538 write (fd, data, length);
539 g_free(data);
540 close (fd);
541 }
542
543 g_key_file_free(key_file);
544 caps_copy_exists:
545 g_free(file);
546 caps_copy_return:
547 return;
548 }
549
550 gboolean caps_restore_from_persistent (const char* hash)
551 {
552 gchar *file;
553 GKeyFile *key_file;
554 gchar **groups, **group;
555 gboolean restored = FALSE;
556
557 file = caps_get_filename (hash);
558 if (!file)
559 goto caps_restore_no_file;
560
561 key_file = g_key_file_new ();
562 if (!g_key_file_load_from_file (key_file, file, G_KEY_FILE_NONE, NULL))
563 goto caps_restore_bad_file;
564
565 caps_add(hash);
566
567 groups = g_key_file_get_groups (key_file, NULL);
568 for (group = groups; *group; ++group) {
569 if (!g_strcmp0(*group, "features")) {
570 gchar **features, **feature;
571 features = g_key_file_get_string_list (key_file, *group, "features",
572 NULL, NULL);
573 for (feature = features; *feature; ++feature) {
574 caps_add_feature(hash, *feature);
575 }
576
577 g_strfreev (features);
578 } else if (g_str_has_prefix (*group, "identity_")) {
579 gchar *category, *type, *name, *lang;
580
581 category = g_key_file_get_string(key_file, *group, "category", NULL);
582 type = g_key_file_get_string(key_file, *group, "type", NULL);
583 name = g_key_file_get_string(key_file, *group, "name", NULL);
584 lang = *group + 9; /* "identity_" */
585
586 caps_add_identity(hash, category, name, type, lang);
587 g_free(category);
588 g_free(type);
589 g_free(name);
590 } else if (g_str_has_prefix (*group, "form_")) {
591 gchar *formtype;
592 gchar **fields, **field;
593 formtype = *group + 5; /* "form_" */
594 caps_add_dataform (hash, formtype);
595
596 fields = g_key_file_get_keys(key_file, *group, NULL, NULL);
597 for (field = fields; *field; ++field) {
598 gchar **values, **value;
599 values = g_key_file_get_string_list (key_file, *group, *field,
600 NULL, NULL);
601 for (value = values; *value; ++value) {
602 caps_add_dataform_field (hash, formtype, *field, *value);
603 }
604 g_strfreev (values);
605 }
606 g_strfreev (fields);
607 }
608 }
609 g_strfreev(groups);
610 restored = TRUE;
611
612 caps_restore_bad_file:
613 g_key_file_free (key_file);
614 g_free (file);
615 caps_restore_no_file:
616 return restored;
617 }
618
179 /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */ 619 /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */