Mercurial > hg
view mcabber/mcabber/caps.c @ 2309:e00ae0763468
Do not use litteral command char (':', '/')
author | Mikael Berthe <mikael@lilotux.net> |
---|---|
date | Sat, 11 Feb 2017 12:40:43 +0100 |
parents | f5402d705f67 |
children | ffd0e57e9563 |
line wrap: on
line source
/* * caps.c -- Entity Capabilities Cache for mcabber * * Copyright (C) 2008-2010 Frank Zschockelt <mcabber@freakysoft.de> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <http://www.gnu.org/licenses/>. */ #include <glib.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include "settings.h" #include "utils.h" typedef struct { char *category; char *type; char *name; } identity; typedef struct { GHashTable *fields; } dataform; typedef struct { GHashTable *identities; GHashTable *features; GHashTable *forms; } caps; static GHashTable *caps_cache = NULL; void caps_destroy(gpointer data) { caps *c = data; g_hash_table_destroy(c->identities); g_hash_table_destroy(c->features); g_hash_table_destroy(c->forms); g_free(c); } void identity_destroy(gpointer data) { identity *i = data; g_free(i->category); g_free(i->type); g_free(i->name); g_free(i); } void form_destroy(gpointer data) { dataform *f = data; g_hash_table_destroy(f->fields); g_free(f); } void field_destroy(gpointer data) { GList *v = data; g_list_foreach (v, (GFunc) g_free, NULL); g_list_free (v); } void caps_init(void) { if (!caps_cache) caps_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, caps_destroy); } void caps_free(void) { if (caps_cache) { g_hash_table_destroy(caps_cache); caps_cache = NULL; } } void caps_add(const char *hash) { if (!hash) return; caps *c = g_new0(caps, 1); c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); c->identities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, identity_destroy); c->forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, form_destroy); g_hash_table_replace(caps_cache, g_strdup(hash), c); } void caps_remove(const char *hash) { if (!hash) return; g_hash_table_remove(caps_cache, hash); } /* if hash is not verified, this will bind capabilities set only with bare jid */ void caps_move_to_local(char *hash, char *bjid) { char *orig_hash; caps *c = NULL; if (!hash || !bjid) return; g_hash_table_lookup_extended(caps_cache, hash, (gpointer*)&orig_hash, (gpointer*)&c); if (c) { g_hash_table_steal(caps_cache, hash); g_free(orig_hash); g_hash_table_replace(caps_cache, g_strdup_printf("%s/#%s", bjid, hash), c); // solidus is guaranteed to never appear in bare jid // hash will not appear in base64 encoded hash // sequence "/#" is deterministic separator, and allows to identify local cache entry } } /*if bjid is NULL, it will check only verified hashes */ int caps_has_hash(const char *hash, const char *bjid) { caps *c = NULL; if (!hash) return 0; c = g_hash_table_lookup(caps_cache, hash); if (!c && bjid) { char *key = g_strdup_printf("%s/#%s", bjid, hash); c = g_hash_table_lookup(caps_cache, key); g_free(key); } return (c != NULL); } void caps_add_identity(const char *hash, const char *category, const char *name, const char *type, const char *lang) { caps *c; if (!hash || !category || !type) return; if (!lang) lang = ""; c = g_hash_table_lookup(caps_cache, hash); if (c) { identity *i = g_new0(identity, 1); i->category = g_strdup(category); i->name = g_strdup(name); i->type = g_strdup(type); g_hash_table_replace(c->identities, g_strdup(lang), i); } } void caps_set_identity(char *hash, const char *category, const char *name, const char *type) { caps_add_identity(hash, category, name, type, NULL); } void caps_add_dataform(const char *hash, const char *formtype) { caps *c; if (!formtype) return; c = g_hash_table_lookup(caps_cache, hash); if (c) { dataform *d = g_new0(dataform, 1); char *f = g_strdup(formtype); d->fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, field_destroy); g_hash_table_replace(c->forms, f, d); } } gint _strcmp_sort(gconstpointer a, gconstpointer b) { return g_strcmp0(a, b); } void caps_add_dataform_field(const char *hash, const char *formtype, const char *field, const char *value) { caps *c; if (!formtype || !field || !value) return; c = g_hash_table_lookup(caps_cache, hash); if (c) { dataform *d; d = g_hash_table_lookup(c->forms, formtype); if (d) { gpointer key, val; char *f; GList *v = NULL; if (g_hash_table_lookup_extended(d->fields, field, &key, &val)) { g_hash_table_steal(d->fields, field); g_free(key); v = val; } f = g_strdup(field); v = g_list_insert_sorted(v, g_strdup(value), _strcmp_sort); g_hash_table_replace(d->fields, f, v); } } } void caps_add_feature(const char *hash, const char *feature) { caps *c; if (!hash || !feature) return; c = g_hash_table_lookup(caps_cache, hash); if (c) { char *f = g_strdup(feature); g_hash_table_replace(c->features, f, f); } } /* If hash is verified, then bare jid is ignored. * If there is no globally verified hash, and bare jid is not null, * then local storage for that jid will be checked */ int caps_has_feature(const char *hash, char *feature, char *bjid) { caps *c = NULL; if (!hash || !feature) return 0; c = g_hash_table_lookup(caps_cache, hash); if (!c && bjid) { char *key = g_strdup_printf("%s/#%s", bjid, hash); c = g_hash_table_lookup(caps_cache, key); g_free(key); } if (c) return (g_hash_table_lookup(c->features, feature) != NULL); return 0; } static GFunc _foreach_function; void _caps_foreach_helper(gpointer key, gpointer value, gpointer user_data) { // GFunc func = (GFunc)user_data; _foreach_function(value, user_data); } void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data) { caps *c; if (!hash) return; c = g_hash_table_lookup(caps_cache, hash); if (!c) return; _foreach_function = func; g_hash_table_foreach(c->features, _caps_foreach_helper, user_data); } // Generates the sha1 hash for the special capability "" and returns it const char *caps_generate(void) { GList *features, *langs; GChecksum *sha1; guint8 digest[20]; gsize digest_size = 20; gchar *hash, *old_hash = NULL; caps *old_caps, *c; gpointer key; if (!g_hash_table_lookup_extended(caps_cache, "", &key, (gpointer *)&c)) return NULL; g_hash_table_steal(caps_cache, ""); g_free(key); sha1 = g_checksum_new(G_CHECKSUM_SHA1); langs = g_hash_table_get_keys(c->identities); langs = g_list_sort(langs, _strcmp_sort); { identity *i; GList *lang; char *identity_S; for (lang=langs; lang; lang=lang->next) { i = g_hash_table_lookup(c->identities, lang->data); identity_S = g_strdup_printf("%s/%s/%s/%s<", i->category, i->type, (char *)lang->data, i->name ? i->name : ""); g_checksum_update(sha1, (guchar *)identity_S, -1); g_free(identity_S); } } g_list_free(langs); features = g_hash_table_get_values(c->features); features = g_list_sort(features, _strcmp_sort); { GList *feature; for (feature=features; feature; feature=feature->next) { g_checksum_update(sha1, feature->data, -1); g_checksum_update(sha1, (guchar *)"<", -1); } } g_list_free(features); g_checksum_get_digest(sha1, digest, &digest_size); hash = g_base64_encode(digest, digest_size); g_checksum_free(sha1); g_hash_table_lookup_extended(caps_cache, hash, (gpointer *)&old_hash, (gpointer *)&old_caps); g_hash_table_insert(caps_cache, hash, c); if (old_hash) return old_hash; else return hash; } gboolean caps_verify(const char *hash, char *function) { GList *features, *langs, *forms; GChecksum *checksum; guint8 digest[20]; gsize digest_size = 20; gchar *local_hash; gboolean match = FALSE; caps *c = g_hash_table_lookup(caps_cache, hash); if (!g_strcmp0(function, "sha-1")) { checksum = g_checksum_new(G_CHECKSUM_SHA1); } else if (!g_strcmp0(function, "md5")) { checksum = g_checksum_new(G_CHECKSUM_MD5); digest_size = 16; } else return FALSE; langs = g_hash_table_get_keys(c->identities); langs = g_list_sort(langs, _strcmp_sort); { identity *i; GList *lang; char *identity_S; for (lang=langs; lang; lang=lang->next) { i = g_hash_table_lookup(c->identities, lang->data); identity_S = g_strdup_printf("%s/%s/%s/%s<", i->category, i->type, (char *)lang->data, i->name ? i->name : ""); g_checksum_update(checksum, (guchar *)identity_S, -1); g_free(identity_S); } } g_list_free(langs); features = g_hash_table_get_values(c->features); features = g_list_sort(features, _strcmp_sort); { GList *feature; for (feature=features; feature; feature=feature->next) { g_checksum_update(checksum, feature->data, -1); g_checksum_update(checksum, (guchar *)"<", -1); } } g_list_free(features); forms = g_hash_table_get_keys(c->forms); forms = g_list_sort(forms, _strcmp_sort); { dataform *d; GList *form, *fields; for (form=forms; form; form=form->next) { d = g_hash_table_lookup(c->forms, form->data); g_checksum_update(checksum, form->data, -1); g_checksum_update(checksum, (guchar *)"<", -1); fields = g_hash_table_get_keys(d->fields); fields = g_list_sort(fields, _strcmp_sort); { GList *field; GList *values; for (field=fields; field; field=field->next) { g_checksum_update(checksum, field->data, -1); g_checksum_update(checksum, (guchar *)"<", -1); values = g_hash_table_lookup(d->fields, field->data); { GList *value; for (value=values; value; value=value->next) { g_checksum_update(checksum, value->data, -1); g_checksum_update(checksum, (guchar *)"<", -1); } } } } g_list_free(fields); } } g_list_free(forms); g_checksum_get_digest(checksum, digest, &digest_size); local_hash = g_base64_encode(digest, digest_size); g_checksum_free(checksum); match = !g_strcmp0(hash, local_hash); g_free(local_hash); return match; } static gchar* caps_get_filename(const char* hash) { gchar *hash_fs; gchar *dir = (gchar *) settings_opt_get ("caps_directory"); gchar *file = NULL; if (!dir) goto caps_filename_return; hash_fs = g_strdup (hash); { const gchar *valid_fs = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+="; g_strcanon(hash_fs, valid_fs, '-'); } dir = expand_filename (dir); file = g_strdup_printf ("%s/%s.ini", dir, hash_fs); g_free(dir); g_free(hash_fs); caps_filename_return: return file; } /* Store capabilities set in GKeyFile. To be used with verified hashes only */ void caps_copy_to_persistent(const char* hash, char* xml) { gchar *file; GList *features, *langs, *forms; GKeyFile *key_file; caps *c; int fd; g_free (xml); c = g_hash_table_lookup (caps_cache, hash); if (!c) goto caps_copy_return; file = caps_get_filename (hash); if (!file) goto caps_copy_return; fd = open (file, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); if (fd == -1) goto caps_copy_exists; key_file = g_key_file_new (); g_key_file_set_comment (key_file, NULL, NULL, "This is autogenerated file. Please do not modify.", NULL); langs = g_hash_table_get_keys (c->identities); { identity *i; GList *lang; gchar *group; for (lang=langs; lang; lang=lang->next) { i = g_hash_table_lookup (c->identities, lang->data); group = g_strdup_printf("identity_%s", (gchar *)lang->data); g_key_file_set_string (key_file, group, "category", i->category); g_key_file_set_string (key_file, group, "type", i->type); g_key_file_set_string (key_file, group, "name", i->name); g_free (group); } } g_list_free (langs); features = g_hash_table_get_values (c->features); { GList *feature; gchar **string_list; gint i; i = g_list_length (features); string_list = g_new (gchar*, i + 1); i = 0; for (feature=features; feature; feature=feature->next) { string_list[i] = g_strdup(feature->data); ++i; } string_list[i] = NULL; g_key_file_set_string_list (key_file, "features", "features", (const gchar**)string_list, i); g_strfreev (string_list); } g_list_free (features); forms = g_hash_table_get_keys(c->forms); { dataform *d; GList *form, *fields; gchar *group; for (form=forms; form; form=form->next) { d = g_hash_table_lookup (c->forms, form->data); group = g_strdup_printf ("form_%s", (gchar *)form->data); fields = g_hash_table_get_keys(d->fields); { GList *field; GList *values; for (field=fields; field; field=field->next) { values = g_hash_table_lookup (d->fields, field->data); { GList *value; gchar **string_list; gint i; i = g_list_length (values); string_list = g_new (gchar*, i + 1); i = 0; for (value=values; value; value=value->next) { string_list[i] = g_strdup(value->data); ++i; } string_list[i] = NULL; g_key_file_set_string_list (key_file, group, field->data, (const gchar**)string_list, i); g_strfreev (string_list); } } } g_list_free(fields); g_free (group); } } g_list_free (forms); { gchar *data; gsize length; data = g_key_file_to_data (key_file, &length, NULL); write (fd, data, length); g_free(data); close (fd); } g_key_file_free(key_file); caps_copy_exists: g_free(file); caps_copy_return: return; } /* Restore capabilities from GKeyFile. Hash is not verified afterwards */ gboolean caps_restore_from_persistent (const char* hash) { gchar *file; GKeyFile *key_file; gchar **groups, **group; gboolean restored = FALSE; file = caps_get_filename (hash); if (!file) goto caps_restore_no_file; key_file = g_key_file_new (); if (!g_key_file_load_from_file (key_file, file, G_KEY_FILE_NONE, NULL)) goto caps_restore_bad_file; caps_add(hash); groups = g_key_file_get_groups (key_file, NULL); for (group = groups; *group; ++group) { if (!g_strcmp0(*group, "features")) { gchar **features, **feature; features = g_key_file_get_string_list (key_file, *group, "features", NULL, NULL); for (feature = features; *feature; ++feature) { caps_add_feature(hash, *feature); } g_strfreev (features); } else if (g_str_has_prefix (*group, "identity_")) { gchar *category, *type, *name, *lang; category = g_key_file_get_string(key_file, *group, "category", NULL); type = g_key_file_get_string(key_file, *group, "type", NULL); name = g_key_file_get_string(key_file, *group, "name", NULL); lang = *group + 9; /* "identity_" */ caps_add_identity(hash, category, name, type, lang); g_free(category); g_free(type); g_free(name); } else if (g_str_has_prefix (*group, "form_")) { gchar *formtype; gchar **fields, **field; formtype = *group + 5; /* "form_" */ caps_add_dataform (hash, formtype); fields = g_key_file_get_keys(key_file, *group, NULL, NULL); for (field = fields; *field; ++field) { gchar **values, **value; values = g_key_file_get_string_list (key_file, *group, *field, NULL, NULL); for (value = values; *value; ++value) { caps_add_dataform_field (hash, formtype, *field, *value); } g_strfreev (values); } g_strfreev (fields); } } g_strfreev(groups); restored = TRUE; caps_restore_bad_file: g_key_file_free (key_file); g_free (file); caps_restore_no_file: return restored; } /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */