diff --git a/src/xmpp/capabilities.c b/src/xmpp/capabilities.c index f773a559..6310352e 100644 --- a/src/xmpp/capabilities.c +++ b/src/xmpp/capabilities.c @@ -73,7 +73,7 @@ static char *my_sha1; static void _save_cache(void); static EntityCapabilities* _caps_by_ver(const char *const ver); static EntityCapabilities* _caps_by_jid(const char *const jid); -EntityCapabilities* _caps_copy(EntityCapabilities *caps); +static EntityCapabilities* _caps_copy(EntityCapabilities *caps); void caps_init(void) @@ -106,6 +106,66 @@ caps_init(void) my_sha1 = NULL; } +GList* +caps_get_features(void) +{ + GList *result = NULL; + + GList *curr = prof_features; + while (curr) { + result = g_list_append(result, curr->data); + curr = g_list_next(curr); + } + + GList *plugin_features = plugins_get_disco_features(); + curr = plugin_features; + while (curr) { + result = g_list_append(result, curr->data); + curr = g_list_next(curr); + } + + return result; +} + +EntityCapabilities* +caps_create(const char *const category, const char *const type, const char *const name, + const char *const software, const char *const software_version, + const char *const os, const char *const os_version, + GSList *features) +{ + EntityCapabilities *result = (EntityCapabilities *)malloc(sizeof(EntityCapabilities)); + + if (category || type || name) { + DiscoIdentity *identity = (DiscoIdentity*)malloc(sizeof(DiscoIdentity)); + identity->category = category ? strdup(category) : NULL; + identity->type = type ? strdup(type) : NULL; + identity->name = name ? strdup(name) : NULL; + result->identity = identity; + } else { + result->identity = NULL; + } + + if (software || software_version || os || os_version) { + SoftwareVersion *software_versionp = (SoftwareVersion*)malloc(sizeof(SoftwareVersion)); + software_versionp->software = software ? strdup(software) : NULL; + software_versionp->software_version = software_version ? strdup(software_version) : NULL; + software_versionp->os = os ? strdup(os) : NULL; + software_versionp->os_version = os_version ? strdup(os_version) : NULL; + result->software_version = software_versionp; + } else { + result->software_version = NULL; + } + + result->features = NULL; + GSList *curr = features; + while (curr) { + result->features = g_slist_append(result->features, strdup(curr->data)); + curr = g_slist_next(curr); + } + + return result; +} + void caps_add_by_ver(const char *const ver, EntityCapabilities *caps) { @@ -175,70 +235,11 @@ caps_map_jid_to_ver(const char *const jid, const char *const ver) } gboolean -caps_contains(const char *const ver) +caps_cache_contains(const char *const ver) { return (g_key_file_has_group(cache, ver)); } -static EntityCapabilities* -_caps_by_ver(const char *const ver) -{ - if (g_key_file_has_group(cache, ver)) { - EntityCapabilities *new_caps = malloc(sizeof(struct entity_capabilities_t)); - - char *category = g_key_file_get_string(cache, ver, "category", NULL); - char *type = g_key_file_get_string(cache, ver, "type", NULL); - char *name = g_key_file_get_string(cache, ver, "name", NULL); - if (category || type || name) { - DiscoIdentity *identity = malloc(sizeof(struct disco_identity_t)); - identity->category = category; - identity->type = type; - identity->name = name; - new_caps->identity = identity; - } else { - new_caps->identity = NULL; - } - - char *software = g_key_file_get_string(cache, ver, "software", NULL); - char *software_version = g_key_file_get_string(cache, ver, "software_version", NULL); - char *os = g_key_file_get_string(cache, ver, "os", NULL); - char *os_version = g_key_file_get_string(cache, ver, "os_version", NULL); - if (software || software_version || os || os_version) { - SoftwareVersion *software_versionp = malloc(sizeof(struct software_version_t)); - software_versionp->software = software; - software_versionp->software_version = software_version; - software_versionp->os = os; - software_versionp->os_version = os_version; - new_caps->software_version = software_versionp; - } else { - new_caps->software_version = NULL; - } - - gsize features_len = 0; - gchar **features = g_key_file_get_string_list(cache, ver, "features", &features_len, NULL); - if (features && features_len > 0) { - GSList *features_list = NULL; - int i; - for (i = 0; i < features_len; i++) { - features_list = g_slist_append(features_list, strdup(features[i])); - } - new_caps->features = features_list; - g_strfreev(features); - } else { - new_caps->features = NULL; - } - return new_caps; - } else { - return NULL; - } -} - -static EntityCapabilities* -_caps_by_jid(const char *const jid) -{ - return g_hash_table_lookup(jid_to_caps, jid); -} - EntityCapabilities* caps_lookup(const char *const jid) { @@ -261,287 +262,12 @@ caps_lookup(const char *const jid) return NULL; } -EntityCapabilities* -_caps_copy(EntityCapabilities *caps) -{ - if (!caps) { - return NULL; - } else { - EntityCapabilities *result = (EntityCapabilities *)malloc(sizeof(EntityCapabilities)); - - if (caps->identity) { - DiscoIdentity *identity = (DiscoIdentity*)malloc(sizeof(DiscoIdentity)); - identity->category = caps->identity->category ? strdup(caps->identity->category) : NULL; - identity->type = caps->identity->type ? strdup(caps->identity->type) : NULL; - identity->name = caps->identity->name ? strdup(caps->identity->name) : NULL; - result->identity = identity; - } else { - result->identity = NULL; - } - - if (caps->software_version) { - SoftwareVersion *software_version = (SoftwareVersion*)malloc(sizeof(SoftwareVersion)); - software_version->software = caps->software_version->software ? strdup(caps->software_version->software) : NULL; - software_version->software_version = caps->software_version->software_version ? strdup(caps->software_version->software_version) : NULL; - software_version->os = caps->software_version->os ? strdup(caps->software_version->os) : NULL; - software_version->os_version = caps->software_version->os_version ? strdup(caps->software_version->os_version) : NULL; - } else { - result->software_version = NULL; - } - - result->features = NULL; - GSList *curr = caps->features; - while (curr) { - result->features = g_slist_append(result->features, strdup(curr->data)); - curr = g_slist_next(curr); - } - - return result; - } -} - -char* -caps_create_sha1_str(xmpp_stanza_t *const query) -{ - GSList *identities = NULL; - GSList *features = NULL; - GSList *form_names = NULL; - GHashTable *forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)form_destroy); - - xmpp_stanza_t *child = xmpp_stanza_get_children(query); - while (child) { - if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_IDENTITY) == 0) { - const char *category = xmpp_stanza_get_attribute(child, "category"); - const char *type = xmpp_stanza_get_attribute(child, "type"); - const char *lang = xmpp_stanza_get_attribute(child, "xml:lang"); - const char *name = xmpp_stanza_get_attribute(child, "name"); - - GString *identity_str = g_string_new(category); - g_string_append(identity_str, "/"); - if (type) { - g_string_append(identity_str, type); - } - g_string_append(identity_str, "/"); - if (lang) { - g_string_append(identity_str, lang); - } - g_string_append(identity_str, "/"); - if (name) { - g_string_append(identity_str, name); - } - g_string_append(identity_str, "<"); - identities = g_slist_insert_sorted(identities, g_strdup(identity_str->str), (GCompareFunc)strcmp); - g_string_free(identity_str, TRUE); - } else if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_FEATURE) == 0) { - const char *feature_str = xmpp_stanza_get_attribute(child, "var"); - features = g_slist_insert_sorted(features, g_strdup(feature_str), (GCompareFunc)strcmp); - } else if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_X) == 0) { - if (g_strcmp0(xmpp_stanza_get_ns(child), STANZA_NS_DATA) == 0) { - DataForm *form = form_create(child); - char *form_type = form_get_form_type_field(form); - form_names = g_slist_insert_sorted(form_names, g_strdup(form_type), (GCompareFunc)strcmp); - g_hash_table_insert(forms, g_strdup(form_type), form); - } - } - child = xmpp_stanza_get_next(child); - } - - GString *s = g_string_new(""); - - GSList *curr = identities; - while (curr) { - g_string_append(s, curr->data); - curr = g_slist_next(curr); - } - - curr = features; - while (curr) { - g_string_append(s, curr->data); - g_string_append(s, "<"); - curr = g_slist_next(curr); - } - - curr = form_names; - while (curr) { - DataForm *form = g_hash_table_lookup(forms, curr->data); - char *form_type = form_get_form_type_field(form); - g_string_append(s, form_type); - g_string_append(s, "<"); - - GSList *sorted_fields = form_get_non_form_type_fields_sorted(form); - GSList *curr_field = sorted_fields; - while (curr_field) { - FormField *field = curr_field->data; - g_string_append(s, field->var); - g_string_append(s, "<"); - - GSList *sorted_values = form_get_field_values_sorted(field); - GSList *curr_value = sorted_values; - while (curr_value) { - g_string_append(s, curr_value->data); - g_string_append(s, "<"); - curr_value = g_slist_next(curr_value); - } - g_slist_free(sorted_values); - curr_field = g_slist_next(curr_field); - } - g_slist_free(sorted_fields); - - curr = g_slist_next(curr); - } - - char *result = p_sha1_hash(s->str); - - g_string_free(s, TRUE); - g_slist_free_full(identities, g_free); - g_slist_free_full(features, g_free); - g_slist_free_full(form_names, g_free); - g_hash_table_destroy(forms); - - return result; -} - -EntityCapabilities* -caps_create(xmpp_stanza_t *query) -{ - char *software = NULL; - char *software_version = NULL; - char *os = NULL; - char *os_version = NULL; - - xmpp_stanza_t *softwareinfo = xmpp_stanza_get_child_by_ns(query, STANZA_NS_DATA); - if (softwareinfo) { - DataForm *form = form_create(softwareinfo); - FormField *formField = NULL; - - char *form_type = form_get_form_type_field(form); - if (g_strcmp0(form_type, STANZA_DATAFORM_SOFTWARE) == 0) { - GSList *field = form->fields; - while (field) { - formField = field->data; - if (formField->values) { - if (strcmp(formField->var, "software") == 0) { - software = strdup(formField->values->data); - } else if (strcmp(formField->var, "software_version") == 0) { - software_version = strdup(formField->values->data); - } else if (strcmp(formField->var, "os") == 0) { - os = strdup(formField->values->data); - } else if (strcmp(formField->var, "os_version") == 0) { - os_version = strdup(formField->values->data); - } - } - field = g_slist_next(field); - } - } - - form_destroy(form); - } - - xmpp_stanza_t *child = xmpp_stanza_get_children(query); - GSList *identity_stanzas = NULL; - GSList *features = NULL; - while (child) { - if (g_strcmp0(xmpp_stanza_get_name(child), "feature") == 0) { - features = g_slist_append(features, strdup(xmpp_stanza_get_attribute(child, "var"))); - } - if (g_strcmp0(xmpp_stanza_get_name(child), "identity") == 0) { - identity_stanzas = g_slist_append(identity_stanzas, child); - } - - child = xmpp_stanza_get_next(child); - } - - // find identity by locale - const gchar* const *langs = g_get_language_names(); - int num_langs = g_strv_length((gchar**)langs); - xmpp_stanza_t *found = NULL; - GSList *curr_identity = identity_stanzas; - while (curr_identity) { - xmpp_stanza_t *id_stanza = curr_identity->data; - const char *stanza_lang = xmpp_stanza_get_attribute(id_stanza, "xml:lang"); - if (stanza_lang) { - int i = 0; - for (i = 0; i < num_langs; i++) { - if (g_strcmp0(langs[i], stanza_lang) == 0) { - found = id_stanza; - break; - } - } - } - if (found) { - break; - } - curr_identity = g_slist_next(curr_identity); - } - - // not lang match, use default with no lang - if (!found) { - curr_identity = identity_stanzas; - while (curr_identity) { - xmpp_stanza_t *id_stanza = curr_identity->data; - const char *stanza_lang = xmpp_stanza_get_attribute(id_stanza, "xml:lang"); - if (!stanza_lang) { - found = id_stanza; - break; - } - - curr_identity = g_slist_next(curr_identity); - } - } - - // no matching lang, no identity without lang, use first - if (!found) { - if (identity_stanzas) { - found = identity_stanzas->data; - } - } - - g_slist_free(identity_stanzas); - - const char *category = NULL; - const char *type = NULL; - const char *name = NULL; - if (found) { - category = xmpp_stanza_get_attribute(found, "category"); - type = xmpp_stanza_get_attribute(found, "type"); - name = xmpp_stanza_get_attribute(found, "name"); - } - - EntityCapabilities *new_caps = malloc(sizeof(struct entity_capabilities_t)); - - if (category || type || name) { - DiscoIdentity *identity = malloc(sizeof(struct disco_identity_t)); - identity->category = category ? strdup(category) : NULL; - identity->type = type ? strdup(type) : NULL; - identity->name = name ? strdup(name) : NULL; - new_caps->identity = identity; - } else { - new_caps->identity = NULL; - } - - if (software || software_version || os || os_version) { - SoftwareVersion *software_versionp = malloc(sizeof(struct software_version_t)); - software_versionp->software = software; - software_versionp->software_version = software_version; - software_versionp->os = os; - software_versionp->os_version = os_version; - } - - if (features) { - new_caps->features = features; - } else { - new_caps->features = NULL; - } - - return new_caps; -} - char* caps_get_my_sha1(xmpp_ctx_t *const ctx) { if (my_sha1 == NULL) { - xmpp_stanza_t *query = caps_create_query_response_stanza(ctx); - my_sha1 = caps_create_sha1_str(query); + xmpp_stanza_t *query = stanza_create_caps_query_element(ctx); + my_sha1 = stanza_create_caps_sha1_from_query(query); xmpp_stanza_release(query); } @@ -557,62 +283,6 @@ caps_reset_ver(void) } } -xmpp_stanza_t* -caps_create_query_response_stanza(xmpp_ctx_t *const ctx) -{ - xmpp_stanza_t *query = xmpp_stanza_new(ctx); - xmpp_stanza_set_name(query, STANZA_NAME_QUERY); - xmpp_stanza_set_ns(query, XMPP_NS_DISCO_INFO); - - xmpp_stanza_t *identity = xmpp_stanza_new(ctx); - xmpp_stanza_set_name(identity, "identity"); - xmpp_stanza_set_attribute(identity, "category", "client"); - xmpp_stanza_set_attribute(identity, "type", "console"); - - GString *name_str = g_string_new("Profanity "); - g_string_append(name_str, PACKAGE_VERSION); - if (strcmp(PACKAGE_STATUS, "development") == 0) { -#ifdef HAVE_GIT_VERSION - g_string_append(name_str, "dev."); - g_string_append(name_str, PROF_GIT_BRANCH); - g_string_append(name_str, "."); - g_string_append(name_str, PROF_GIT_REVISION); -#else - g_string_append(name_str, "dev"); -#endif - } - xmpp_stanza_set_attribute(identity, "name", name_str->str); - g_string_free(name_str, TRUE); - xmpp_stanza_add_child(query, identity); - xmpp_stanza_release(identity); - - GList *curr = prof_features; - while (curr) { - xmpp_stanza_t *feature = xmpp_stanza_new(ctx); - xmpp_stanza_set_name(feature, STANZA_NAME_FEATURE); - xmpp_stanza_set_attribute(feature, STANZA_ATTR_VAR, curr->data); - xmpp_stanza_add_child(query, feature); - xmpp_stanza_release(feature); - - curr = g_list_next(curr); - } - - GList *plugin_features = plugins_get_disco_features(); - curr = plugin_features; - while (curr) { - xmpp_stanza_t *feature = xmpp_stanza_new(ctx); - xmpp_stanza_set_name(feature, STANZA_NAME_FEATURE); - xmpp_stanza_set_attribute(feature, STANZA_ATTR_VAR, curr->data); - xmpp_stanza_add_child(query, feature); - xmpp_stanza_release(feature); - - curr = g_list_next(curr); - } - g_list_free(plugin_features); - - return query; -} - void caps_close(void) { @@ -626,6 +296,77 @@ caps_close(void) prof_features = NULL; } +static EntityCapabilities* +_caps_by_ver(const char *const ver) +{ + if (!g_key_file_has_group(cache, ver)) { + return NULL; + } + + char *category = g_key_file_get_string(cache, ver, "category", NULL); + char *type = g_key_file_get_string(cache, ver, "type", NULL); + char *name = g_key_file_get_string(cache, ver, "name", NULL); + + char *software = g_key_file_get_string(cache, ver, "software", NULL); + char *software_version = g_key_file_get_string(cache, ver, "software_version", NULL); + char *os = g_key_file_get_string(cache, ver, "os", NULL); + char *os_version = g_key_file_get_string(cache, ver, "os_version", NULL); + + gsize features_len = 0; + gchar **features_list = g_key_file_get_string_list(cache, ver, "features", &features_len, NULL); + GSList *features = NULL; + if (features_list && features_len > 0) { + int i; + for (i = 0; i < features_len; i++) { + features = g_slist_append(features, features_list[i]); + } + } + + EntityCapabilities *result = caps_create( + category, type, name, + software, software_version, os, os_version, + features); + + g_free(category); + g_free(type); + g_free(name); + g_free(software); + g_free(software_version); + g_free(os); + g_free(os_version); + if (features_list) { + g_strfreev(features_list); + } + g_slist_free(features); + + return result; +} + +static EntityCapabilities* +_caps_by_jid(const char *const jid) +{ + return g_hash_table_lookup(jid_to_caps, jid); +} + +static EntityCapabilities* +_caps_copy(EntityCapabilities *caps) +{ + if (!caps) { + return NULL; + } + + const char *const categoty = caps->identity ? caps->identity->category : NULL; + const char *const type = caps->identity ? caps->identity->type : NULL; + const char *const name = caps->identity ? caps->identity->name : NULL; + + const char *const software = caps->software_version ? caps->software_version->software : NULL; + const char *const software_version = caps->software_version ? caps->software_version->software_version : NULL; + const char *const os = caps->software_version ? caps->software_version->os : NULL; + const char *const os_version = caps->software_version ? caps->software_version->os_version : NULL; + + return caps_create(categoty, type, name, software, software_version, os, os_version, caps->features); +} + static void _disco_identity_destroy(DiscoIdentity *disco_identity) { diff --git a/src/xmpp/capabilities.h b/src/xmpp/capabilities.h index 88563fcc..9b77734e 100644 --- a/src/xmpp/capabilities.h +++ b/src/xmpp/capabilities.h @@ -49,14 +49,15 @@ void caps_init(void); +EntityCapabilities* caps_create(const char *const category, const char *const type, const char *const name, + const char *const software, const char *const software_version, + const char *const os, const char *const os_version, + GSList *features); void caps_add_by_ver(const char *const ver, EntityCapabilities *caps); void caps_add_by_jid(const char *const jid, EntityCapabilities *caps); void caps_map_jid_to_ver(const char *const jid, const char *const ver); -gboolean caps_contains(const char *const ver); - -char* caps_create_sha1_str(xmpp_stanza_t *const query); -xmpp_stanza_t* caps_create_query_response_stanza(xmpp_ctx_t *const ctx); -EntityCapabilities* caps_create(xmpp_stanza_t *query); +gboolean caps_cache_contains(const char *const ver); +GList* caps_get_features(void); char* caps_get_my_sha1(xmpp_ctx_t *const ctx); #endif diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c index 2d27be0c..345eefae 100644 --- a/src/xmpp/iq.c +++ b/src/xmpp/iq.c @@ -733,7 +733,7 @@ _caps_response_id_handler(xmpp_stanza_t *const stanza, void *const userdata) // validate sha1 gchar **split = g_strsplit(node, "#", -1); char *given_sha1 = split[1]; - char *generated_sha1 = caps_create_sha1_str(query); + char *generated_sha1 = stanza_create_caps_sha1_from_query(query); if (g_strcmp0(given_sha1, generated_sha1) != 0) { log_warning("Generated sha-1 does not match given:"); @@ -742,11 +742,11 @@ _caps_response_id_handler(xmpp_stanza_t *const stanza, void *const userdata) } else { log_info("Valid SHA-1 hash found: %s", given_sha1); - if (caps_contains(given_sha1)) { + if (caps_cache_contains(given_sha1)) { log_info("Capabilties already cached: %s", given_sha1); } else { log_info("Capabilities not cached: %s, storing", given_sha1); - EntityCapabilities *capabilities = caps_create(query); + EntityCapabilities *capabilities = stanza_create_caps_from_query_element(query); caps_add_by_ver(given_sha1, capabilities); caps_destroy(capabilities); } @@ -810,7 +810,7 @@ _caps_response_for_jid_id_handler(xmpp_stanza_t *const stanza, void *const userd } log_info("Associating capabilities with: %s", jid); - EntityCapabilities *capabilities = caps_create(query); + EntityCapabilities *capabilities = stanza_create_caps_from_query_element(query); caps_add_by_jid(jid, capabilities); free(jid); @@ -870,11 +870,11 @@ _caps_response_legacy_id_handler(xmpp_stanza_t *const stanza, void *const userda // nodes match if (g_strcmp0(expected_node, node) == 0) { log_info("Legacy capabilities, nodes match %s", node); - if (caps_contains(node)) { + if (caps_cache_contains(node)) { log_info("Capabilties already cached: %s", node); } else { log_info("Capabilities not cached: %s, storing", node); - EntityCapabilities *capabilities = caps_create(query); + EntityCapabilities *capabilities = stanza_create_caps_from_query_element(query); caps_add_by_ver(node, capabilities); caps_destroy(capabilities); } @@ -1349,7 +1349,7 @@ _disco_info_get_handler(xmpp_stanza_t *const stanza) xmpp_stanza_set_id(response, xmpp_stanza_get_id(stanza)); xmpp_stanza_set_attribute(response, STANZA_ATTR_TO, from); xmpp_stanza_set_type(response, STANZA_TYPE_RESULT); - xmpp_stanza_t *query = caps_create_query_response_stanza(ctx); + xmpp_stanza_t *query = stanza_create_caps_query_element(ctx); if (node_str) { xmpp_stanza_set_attribute(query, STANZA_ATTR_NODE, node_str); } diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c index 6ba3573a..00d50039 100644 --- a/src/xmpp/presence.c +++ b/src/xmpp/presence.c @@ -553,7 +553,7 @@ _handle_caps(const char *const jid, XMPPCaps *caps) if (g_strcmp0(caps->hash, "sha-1") == 0) { log_info("Hash %s supported", caps->hash); if (caps->ver) { - if (caps_contains(caps->ver)) { + if (caps_cache_contains(caps->ver)) { log_info("Capabilities cache hit: %s, for %s.", caps->ver, jid); caps_map_jid_to_ver(jid, caps->ver); } else { @@ -666,7 +666,7 @@ _send_caps_request(char *node, char *caps_key, char *id, char *from) if (node) { log_debug("Node string: %s.", node); - if (!caps_contains(caps_key)) { + if (!caps_cache_contains(caps_key)) { log_debug("Capabilities not cached for '%s', sending discovery IQ.", from); xmpp_stanza_t *iq = stanza_create_disco_info_iq(ctx, id, from, node); iq_send_stanza(iq); diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c index 18c2bf66..0853a345 100644 --- a/src/xmpp/stanza.c +++ b/src/xmpp/stanza.c @@ -36,6 +36,10 @@ #include "config.h" +#ifdef HAVE_GIT_VERSION +#include "gitversion.h" +#endif + #include #include #include @@ -1052,6 +1056,51 @@ stanza_create_room_config_submit_iq(xmpp_ctx_t *ctx, const char *const room, Dat return iq; } +xmpp_stanza_t* +stanza_create_caps_query_element(xmpp_ctx_t *ctx) +{ + xmpp_stanza_t *query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, STANZA_NAME_QUERY); + xmpp_stanza_set_ns(query, XMPP_NS_DISCO_INFO); + + xmpp_stanza_t *identity = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(identity, "identity"); + xmpp_stanza_set_attribute(identity, "category", "client"); + xmpp_stanza_set_attribute(identity, "type", "console"); + + GString *name_str = g_string_new("Profanity "); + g_string_append(name_str, PACKAGE_VERSION); + if (strcmp(PACKAGE_STATUS, "development") == 0) { +#ifdef HAVE_GIT_VERSION + g_string_append(name_str, "dev."); + g_string_append(name_str, PROF_GIT_BRANCH); + g_string_append(name_str, "."); + g_string_append(name_str, PROF_GIT_REVISION); +#else + g_string_append(name_str, "dev"); +#endif + } + xmpp_stanza_set_attribute(identity, "name", name_str->str); + g_string_free(name_str, TRUE); + xmpp_stanza_add_child(query, identity); + xmpp_stanza_release(identity); + + GList *features = caps_get_features(); + GList *curr = features; + while (curr) { + xmpp_stanza_t *feature = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(feature, STANZA_NAME_FEATURE); + xmpp_stanza_set_attribute(feature, STANZA_ATTR_VAR, curr->data); + xmpp_stanza_add_child(query, feature); + xmpp_stanza_release(feature); + + curr = g_list_next(curr); + } + g_list_free(features); + + return query; +} + gboolean stanza_contains_chat_state(xmpp_stanza_t *stanza) { @@ -1084,6 +1133,107 @@ stanza_create_ping_iq(xmpp_ctx_t *ctx, const char *const target) return iq; } +char* +stanza_create_caps_sha1_from_query(xmpp_stanza_t *const query) +{ + GSList *identities = NULL; + GSList *features = NULL; + GSList *form_names = NULL; + GHashTable *forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)form_destroy); + + xmpp_stanza_t *child = xmpp_stanza_get_children(query); + while (child) { + if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_IDENTITY) == 0) { + const char *category = xmpp_stanza_get_attribute(child, "category"); + const char *type = xmpp_stanza_get_attribute(child, "type"); + const char *lang = xmpp_stanza_get_attribute(child, "xml:lang"); + const char *name = xmpp_stanza_get_attribute(child, "name"); + + GString *identity_str = g_string_new(category); + g_string_append(identity_str, "/"); + if (type) { + g_string_append(identity_str, type); + } + g_string_append(identity_str, "/"); + if (lang) { + g_string_append(identity_str, lang); + } + g_string_append(identity_str, "/"); + if (name) { + g_string_append(identity_str, name); + } + g_string_append(identity_str, "<"); + identities = g_slist_insert_sorted(identities, g_strdup(identity_str->str), (GCompareFunc)strcmp); + g_string_free(identity_str, TRUE); + } else if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_FEATURE) == 0) { + const char *feature_str = xmpp_stanza_get_attribute(child, "var"); + features = g_slist_insert_sorted(features, g_strdup(feature_str), (GCompareFunc)strcmp); + } else if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_X) == 0) { + if (g_strcmp0(xmpp_stanza_get_ns(child), STANZA_NS_DATA) == 0) { + DataForm *form = form_create(child); + char *form_type = form_get_form_type_field(form); + form_names = g_slist_insert_sorted(form_names, g_strdup(form_type), (GCompareFunc)strcmp); + g_hash_table_insert(forms, g_strdup(form_type), form); + } + } + child = xmpp_stanza_get_next(child); + } + + GString *s = g_string_new(""); + + GSList *curr = identities; + while (curr) { + g_string_append(s, curr->data); + curr = g_slist_next(curr); + } + + curr = features; + while (curr) { + g_string_append(s, curr->data); + g_string_append(s, "<"); + curr = g_slist_next(curr); + } + + curr = form_names; + while (curr) { + DataForm *form = g_hash_table_lookup(forms, curr->data); + char *form_type = form_get_form_type_field(form); + g_string_append(s, form_type); + g_string_append(s, "<"); + + GSList *sorted_fields = form_get_non_form_type_fields_sorted(form); + GSList *curr_field = sorted_fields; + while (curr_field) { + FormField *field = curr_field->data; + g_string_append(s, field->var); + g_string_append(s, "<"); + + GSList *sorted_values = form_get_field_values_sorted(field); + GSList *curr_value = sorted_values; + while (curr_value) { + g_string_append(s, curr_value->data); + g_string_append(s, "<"); + curr_value = g_slist_next(curr_value); + } + g_slist_free(sorted_values); + curr_field = g_slist_next(curr_field); + } + g_slist_free(sorted_fields); + + curr = g_slist_next(curr); + } + + char *result = p_sha1_hash(s->str); + + g_string_free(s, TRUE); + g_slist_free_full(identities, g_free); + g_slist_free_full(features, g_free); + g_slist_free_full(form_names, g_free); + g_hash_table_destroy(forms); + + return result; +} + GDateTime* stanza_get_delay(xmpp_stanza_t *const stanza) { @@ -1573,6 +1723,118 @@ stanza_parse_caps(xmpp_stanza_t *const stanza) return caps; } +EntityCapabilities* +stanza_create_caps_from_query_element(xmpp_stanza_t *query) +{ + char *software = NULL; + char *software_version = NULL; + char *os = NULL; + char *os_version = NULL; + + xmpp_stanza_t *softwareinfo = xmpp_stanza_get_child_by_ns(query, STANZA_NS_DATA); + if (softwareinfo) { + DataForm *form = form_create(softwareinfo); + FormField *formField = NULL; + + char *form_type = form_get_form_type_field(form); + if (g_strcmp0(form_type, STANZA_DATAFORM_SOFTWARE) == 0) { + GSList *field = form->fields; + while (field) { + formField = field->data; + if (formField->values) { + if (strcmp(formField->var, "software") == 0) { + software = formField->values->data; + } else if (strcmp(formField->var, "software_version") == 0) { + software_version = formField->values->data; + } else if (strcmp(formField->var, "os") == 0) { + os = formField->values->data; + } else if (strcmp(formField->var, "os_version") == 0) { + os_version = formField->values->data; + } + } + field = g_slist_next(field); + } + } + + form_destroy(form); + } + + xmpp_stanza_t *child = xmpp_stanza_get_children(query); + GSList *identity_stanzas = NULL; + GSList *features = NULL; + while (child) { + if (g_strcmp0(xmpp_stanza_get_name(child), "feature") == 0) { + features = g_slist_append(features, strdup(xmpp_stanza_get_attribute(child, "var"))); + } + if (g_strcmp0(xmpp_stanza_get_name(child), "identity") == 0) { + identity_stanzas = g_slist_append(identity_stanzas, child); + } + + child = xmpp_stanza_get_next(child); + } + + // find identity by locale + const gchar* const *langs = g_get_language_names(); + int num_langs = g_strv_length((gchar**)langs); + xmpp_stanza_t *found = NULL; + GSList *curr_identity = identity_stanzas; + while (curr_identity) { + xmpp_stanza_t *id_stanza = curr_identity->data; + const char *stanza_lang = xmpp_stanza_get_attribute(id_stanza, "xml:lang"); + if (stanza_lang) { + int i = 0; + for (i = 0; i < num_langs; i++) { + if (g_strcmp0(langs[i], stanza_lang) == 0) { + found = id_stanza; + break; + } + } + } + if (found) { + break; + } + curr_identity = g_slist_next(curr_identity); + } + + // not lang match, use default with no lang + if (!found) { + curr_identity = identity_stanzas; + while (curr_identity) { + xmpp_stanza_t *id_stanza = curr_identity->data; + const char *stanza_lang = xmpp_stanza_get_attribute(id_stanza, "xml:lang"); + if (!stanza_lang) { + found = id_stanza; + break; + } + + curr_identity = g_slist_next(curr_identity); + } + } + + // no matching lang, no identity without lang, use first + if (!found) { + if (identity_stanzas) { + found = identity_stanzas->data; + } + } + + g_slist_free(identity_stanzas); + + const char *category = NULL; + const char *type = NULL; + const char *name = NULL; + if (found) { + category = xmpp_stanza_get_attribute(found, "category"); + type = xmpp_stanza_get_attribute(found, "type"); + name = xmpp_stanza_get_attribute(found, "name"); + } + + EntityCapabilities *result = caps_create(category, type, name, software, software_version, os, os_version, features); + g_slist_free_full(features, free); + + return result; +} + char* stanza_get_error_message(xmpp_stanza_t *stanza) { @@ -1712,7 +1974,7 @@ stanza_attach_caps(xmpp_ctx_t *const ctx, xmpp_stanza_t *const presence) xmpp_stanza_t *caps = xmpp_stanza_new(ctx); xmpp_stanza_set_name(caps, STANZA_NAME_C); xmpp_stanza_set_ns(caps, STANZA_NS_CAPS); - xmpp_stanza_t *query = caps_create_query_response_stanza(ctx); + xmpp_stanza_t *query = stanza_create_caps_query_element(ctx); char *sha1 = caps_get_my_sha1(ctx); xmpp_stanza_set_attribute(caps, STANZA_ATTR_HASH, "sha-1"); diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h index bc649107..806ba615 100644 --- a/src/xmpp/stanza.h +++ b/src/xmpp/stanza.h @@ -291,6 +291,10 @@ void stanza_attach_caps(xmpp_ctx_t *const ctx, xmpp_stanza_t *const presence); void stanza_attach_show(xmpp_ctx_t *const ctx, xmpp_stanza_t *const presence, const char *const show); void stanza_attach_status(xmpp_ctx_t *const ctx, xmpp_stanza_t *const presence, const char *const status); +xmpp_stanza_t* stanza_create_caps_query_element(xmpp_ctx_t *ctx); +char* stanza_create_caps_sha1_from_query(xmpp_stanza_t *const query); +EntityCapabilities* stanza_create_caps_from_query_element(xmpp_stanza_t *query); + const char* stanza_get_presence_string_from_type(resource_presence_t presence_type); xmpp_stanza_t* stanza_create_software_version_iq(xmpp_ctx_t *ctx, const char *const fulljid); xmpp_stanza_t* stanza_create_disco_items_iq(xmpp_ctx_t *ctx, const char *const id, const char *const jid);