diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/capabilities.c | 230 | ||||
-rw-r--r-- | src/capabilities.h | 41 | ||||
-rw-r--r-- | src/command.c | 9 | ||||
-rw-r--r-- | src/common.c | 26 | ||||
-rw-r--r-- | src/common.h | 1 | ||||
-rw-r--r-- | src/contact.c | 91 | ||||
-rw-r--r-- | src/contact.h | 5 | ||||
-rw-r--r-- | src/contact_list.c | 20 | ||||
-rw-r--r-- | src/contact_list.h | 2 | ||||
-rw-r--r-- | src/jabber.c | 258 | ||||
-rw-r--r-- | src/jabber.h | 2 | ||||
-rw-r--r-- | src/muc.c | 6 | ||||
-rw-r--r-- | src/muc.h | 3 | ||||
-rw-r--r-- | src/profanity.c | 22 | ||||
-rw-r--r-- | src/profanity.h | 7 | ||||
-rw-r--r-- | src/stanza.c | 195 | ||||
-rw-r--r-- | src/stanza.h | 32 | ||||
-rw-r--r-- | src/ui.h | 4 | ||||
-rw-r--r-- | src/windows.c | 226 |
19 files changed, 1031 insertions, 149 deletions
diff --git a/src/capabilities.c b/src/capabilities.c new file mode 100644 index 00000000..56ca93ea --- /dev/null +++ b/src/capabilities.c @@ -0,0 +1,230 @@ +/* + * capabilities.c + * + * Copyright (C) 2012, 2013 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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 3 of the License, or + * (at your option) any later version. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <string.h> + +#include <glib.h> +#include <openssl/evp.h> +#include <strophe.h> + +#include "config.h" +#include "common.h" +#include "capabilities.h" +#include "stanza.h" + +static GHashTable *capabilities; + +static void _caps_destroy(Capabilities *caps); + +void +caps_init(void) +{ + capabilities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)_caps_destroy); +} + +void +caps_add(const char * const caps_str, const char * const client) +{ + Capabilities *new_caps = malloc(sizeof(struct capabilities_t)); + + if (client != NULL) { + new_caps->client = strdup(client); + } else { + new_caps->client = NULL; + } + + g_hash_table_insert(capabilities, strdup(caps_str), new_caps); +} + +gboolean +caps_contains(const char * const caps_str) +{ + return (g_hash_table_lookup(capabilities, caps_str) != NULL); +} + +Capabilities * +caps_get(const char * const caps_str) +{ + return g_hash_table_lookup(capabilities, caps_str); +} + +char * +caps_get_sha1_str(xmpp_stanza_t *query) +{ + GSList *identities = NULL; + GSList *features = NULL; + GSList *form_names = NULL; + GHashTable *forms = g_hash_table_new(g_str_hash, g_str_equal); + + GString *s = g_string_new(""); + + xmpp_stanza_t *child = xmpp_stanza_get_children(query); + while (child != NULL) { + if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_IDENTITY) == 0) { + char *category = xmpp_stanza_get_attribute(child, "category"); + char *type = xmpp_stanza_get_attribute(child, "type"); + char *lang = xmpp_stanza_get_attribute(child, "xml:lang"); + char *name = xmpp_stanza_get_attribute(child, "name"); + + GString *identity_str = g_string_new(category); + g_string_append(identity_str, "/"); + if (type != NULL) { + g_string_append(identity_str, type); + } + g_string_append(identity_str, "/"); + if (lang != NULL) { + g_string_append(identity_str, lang); + } + g_string_append(identity_str, "/"); + if (name != NULL) { + g_string_append(identity_str, name); + } + g_string_append(identity_str, "<"); + identities = g_slist_insert_sorted(identities, identity_str->str, (GCompareFunc)octet_compare); + } else if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_FEATURE) == 0) { + char *feature_str = xmpp_stanza_get_attribute(child, "var"); + features = g_slist_insert_sorted(features, feature_str, (GCompareFunc)octet_compare); + } else if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_X) == 0) { + if (strcmp(xmpp_stanza_get_ns(child), STANZA_NS_DATA) == 0) { + DataForm *form = stanza_get_form(child); + form_names = g_slist_insert_sorted(form_names, form->form_type, (GCompareFunc)octet_compare); + g_hash_table_insert(forms, form->form_type, form); + } + } + child = xmpp_stanza_get_next(child); + } + + GSList *curr = identities; + while (curr != NULL) { + g_string_append(s, curr->data); + curr = g_slist_next(curr); + } + + curr = features; + while (curr != NULL) { + g_string_append(s, curr->data); + g_string_append(s, "<"); + curr = g_slist_next(curr); + } + + curr = form_names; + while (curr != NULL) { + DataForm *form = g_hash_table_lookup(forms, curr->data); + g_string_append(s, form->form_type); + g_string_append(s, "<"); + + GSList *curr_field = form->fields; + while (curr_field != NULL) { + FormField *field = curr_field->data; + g_string_append(s, field->var); + GSList *curr_value = field->values; + while (curr_value != NULL) { + g_string_append(s, curr_value->data); + g_string_append(s, "<"); + curr_value = g_slist_next(curr_value); + } + curr_field = g_slist_next(curr_value); + } + } + + EVP_MD_CTX mdctx; + const EVP_MD *md; + + unsigned char md_value[EVP_MAX_MD_SIZE]; + unsigned int md_len; + OpenSSL_add_all_digests(); + md = EVP_get_digestbyname("SHA1"); + EVP_MD_CTX_init(&mdctx); + EVP_DigestInit_ex(&mdctx, md, NULL); + EVP_DigestUpdate(&mdctx, s->str, strlen(s->str)); + EVP_DigestFinal_ex(&mdctx, md_value, &md_len); + EVP_MD_CTX_cleanup(&mdctx); + + char *result = g_base64_encode(md_value, md_len); + + g_string_free(s, TRUE); + g_slist_free(identities); + g_slist_free(features); + + return result; +} + +xmpp_stanza_t * +caps_get_query_response_stanza(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", "pc"); + + GString *name_str = g_string_new("Profanity "); + g_string_append(name_str, PACKAGE_VERSION); + if (strcmp(PACKAGE_STATUS, "development") == 0) { + g_string_append(name_str, "dev"); + } + xmpp_stanza_set_attribute(identity, "name", name_str->str); + + xmpp_stanza_t *feature_caps = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(feature_caps, STANZA_NAME_FEATURE); + xmpp_stanza_set_attribute(feature_caps, STANZA_ATTR_VAR, STANZA_NS_CAPS); + + xmpp_stanza_t *feature_discoinfo = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(feature_discoinfo, STANZA_NAME_FEATURE); + xmpp_stanza_set_attribute(feature_discoinfo, STANZA_ATTR_VAR, XMPP_NS_DISCO_INFO); + + xmpp_stanza_t *feature_muc = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(feature_muc, STANZA_NAME_FEATURE); + xmpp_stanza_set_attribute(feature_muc, STANZA_ATTR_VAR, STANZA_NS_MUC); + + xmpp_stanza_t *feature_version = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(feature_version, STANZA_NAME_FEATURE); + xmpp_stanza_set_attribute(feature_version, STANZA_ATTR_VAR, STANZA_NS_VERSION); + + xmpp_stanza_add_child(query, identity); + xmpp_stanza_add_child(query, feature_muc); + xmpp_stanza_add_child(query, feature_discoinfo); + xmpp_stanza_add_child(query, feature_caps); + xmpp_stanza_add_child(query, feature_version); + + return query; +} + +void +caps_close(void) +{ + g_hash_table_destroy(capabilities); +} + +static void +_caps_destroy(Capabilities *caps) +{ + if (caps != NULL) { + FREE_SET_NULL(caps->client); + FREE_SET_NULL(caps); + } +} diff --git a/src/capabilities.h b/src/capabilities.h new file mode 100644 index 00000000..ad160826 --- /dev/null +++ b/src/capabilities.h @@ -0,0 +1,41 @@ +/* + * capabilities.h + * + * Copyright (C) 2012, 2013 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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 3 of the License, or + * (at your option) any later version. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef CAPABILITIES_H +#define CAPABILITIES_H + +#include <glib.h> +#include <strophe.h> + +typedef struct capabilities_t { + char *client; +} Capabilities; + +void caps_init(void); +void caps_add(const char * const caps_str, const char * const client); +gboolean caps_contains(const char * const caps_str); +Capabilities* caps_get(const char * const caps_str); +char* caps_get_sha1_str(xmpp_stanza_t * const query); +xmpp_stanza_t* caps_get_query_response_stanza(xmpp_ctx_t *ctx); +void caps_close(void); + +#endif diff --git a/src/command.c b/src/command.c index 4e79b00d..229d0145 100644 --- a/src/command.c +++ b/src/command.c @@ -1518,6 +1518,7 @@ _cmd_who(gchar **args, struct cmd_help_t help) // not in groupchat window } else { + cons_show(""); GSList *list = get_contact_list(); // no arg, show all contacts @@ -1673,7 +1674,7 @@ _cmd_info(gchar **args, struct cmd_help_t help) } else { if (win_current_is_groupchat()) { if (usr != NULL) { - win_room_show_status(usr); + win_room_show_info(usr); } else { win_current_show("You must specify a nickname."); } @@ -1681,17 +1682,17 @@ _cmd_info(gchar **args, struct cmd_help_t help) if (usr != NULL) { win_current_show("No parameter required when in chat."); } else { - win_show_status(); + win_show_info(); } } else if (win_current_is_private()) { if (usr != NULL) { win_current_show("No parameter required when in chat."); } else { - win_private_show_status(); + win_private_show_info(); } } else { if (usr != NULL) { - cons_show_status(usr); + cons_show_info(usr); } else { cons_show("Usage: %s", help.usage); } diff --git a/src/common.c b/src/common.c index d79bff64..f0e14f45 100644 --- a/src/common.c +++ b/src/common.c @@ -167,3 +167,29 @@ prof_getline(FILE *stream) free(buf); return s; } + +int +octet_compare(unsigned char *str1, unsigned char *str2) +{ + if ((strcmp((char *)str1, "") == 0) && (strcmp((char *)str2, "") == 0)) { + return 0; + } + + if ((strcmp((char *)str1, "") == 0) && (strcmp((char *)str2, "") != 0)) { + return -1; + } + + if ((strcmp((char *)str1, "") != 0) && (strcmp((char *)str2, "") == 0)) { + return 1; + } + + if (str1[0] == str2[0]) { + return octet_compare(&str1[1], &str2[1]); + } + + if (str1[0] < str2[0]) { + return -1; + } + + return 1; +} diff --git a/src/common.h b/src/common.h index e12045ad..5c615256 100644 --- a/src/common.h +++ b/src/common.h @@ -51,5 +51,6 @@ char * str_replace(const char *string, const char *substr, int str_contains(char str[], int size, char ch); char* encode_xml(const char * const xml); char * prof_getline(FILE *stream); +int octet_compare(unsigned char *str1, unsigned char *str2); #endif diff --git a/src/contact.c b/src/contact.c index ef4b807d..aad7f630 100644 --- a/src/contact.c +++ b/src/contact.c @@ -25,6 +25,7 @@ #include <glib.h> +#include "common.h" #include "contact.h" struct p_contact_t { @@ -33,6 +34,7 @@ struct p_contact_t { char *presence; char *status; char *subscription; + char *caps_str; gboolean pending_out; GDateTime *last_activity; }; @@ -40,7 +42,8 @@ struct p_contact_t { PContact p_contact_new(const char * const jid, const char * const name, const char * const presence, const char * const status, - const char * const subscription, gboolean pending_out) + const char * const subscription, gboolean pending_out, + const char * const caps_str) { PContact contact = malloc(sizeof(struct p_contact_t)); contact->jid = strdup(jid); @@ -64,10 +67,14 @@ p_contact_new(const char * const jid, const char * const name, if (subscription != NULL) contact->subscription = strdup(subscription); else - contact->subscription = strdup("none");; + contact->subscription = strdup("none"); - contact->pending_out = pending_out; + if (caps_str != NULL) + contact->caps_str = strdup(caps_str); + else + contact->caps_str = NULL; + contact->pending_out = pending_out; contact->last_activity = NULL; return contact; @@ -76,37 +83,18 @@ p_contact_new(const char * const jid, const char * const name, void p_contact_free(PContact contact) { - if (contact->jid != NULL) { - free(contact->jid); - contact->jid = NULL; - } - - if (contact->name != NULL) { - free(contact->name); - contact->name = NULL; - } - - if (contact->presence != NULL) { - free(contact->presence); - contact->presence = NULL; - } - - if (contact->status != NULL) { - free(contact->status); - contact->status = NULL; - } - - if (contact->subscription != NULL) { - free(contact->subscription); - contact->subscription = NULL; - } + FREE_SET_NULL(contact->jid); + FREE_SET_NULL(contact->name); + FREE_SET_NULL(contact->presence); + FREE_SET_NULL(contact->status); + FREE_SET_NULL(contact->subscription); + FREE_SET_NULL(contact->caps_str); if (contact->last_activity != NULL) { g_date_time_unref(contact->last_activity); } - free(contact); - contact = NULL; + FREE_SET_NULL(contact); } const char * @@ -151,17 +139,17 @@ p_contact_last_activity(const PContact contact) return contact->last_activity; } +const char * +p_contact_caps_str(const PContact contact) +{ + return contact->caps_str; +} + void p_contact_set_presence(const PContact contact, const char * const presence) { - if (contact->presence != NULL) { - free(contact->presence); - contact->presence = NULL; - } - - if (presence == NULL) { - contact->presence = NULL; - } else { + FREE_SET_NULL(contact->presence); + if (presence != NULL) { contact->presence = strdup(presence); } } @@ -169,14 +157,8 @@ p_contact_set_presence(const PContact contact, const char * const presence) void p_contact_set_status(const PContact contact, const char * const status) { - if (contact->status != NULL) { - free(contact->status); - contact->status = NULL; - } - - if (status == NULL) { - contact->status = NULL; - } else { + FREE_SET_NULL(contact->status); + if (status != NULL) { contact->status = strdup(status); } } @@ -184,14 +166,8 @@ p_contact_set_status(const PContact contact, const char * const status) void p_contact_set_subscription(const PContact contact, const char * const subscription) { - if (contact->subscription != NULL) { - free(contact->subscription); - contact->subscription = NULL; - } - - if (subscription == NULL) { - contact->subscription = strdup("none"); - } else { + FREE_SET_NULL(contact->subscription); + if (subscription != NULL) { contact->subscription = strdup(subscription); } } @@ -214,3 +190,12 @@ p_contact_set_last_activity(const PContact contact, GDateTime *last_activity) contact->last_activity = g_date_time_ref(last_activity); } } + +void +p_contact_set_caps_str(const PContact contact, const char * const caps_str) +{ + FREE_SET_NULL(contact->caps_str); + if (caps_str != NULL) { + contact->caps_str = strdup(caps_str); + } +} diff --git a/src/contact.h b/src/contact.h index a2842976..0e9ec42e 100644 --- a/src/contact.h +++ b/src/contact.h @@ -27,18 +27,21 @@ typedef struct p_contact_t *PContact; PContact p_contact_new(const char * const jid, const char * const name, const char * const presence, const char * const status, - const char * const subscription, gboolean pending_out); + const char * const subscription, gboolean pending_out, + const char * const caps_str); void p_contact_free(PContact contact); const char* p_contact_jid(PContact contact); const char* p_contact_name(PContact contact); const char* p_contact_presence(PContact contact); const char* p_contact_status(PContact contact); const char* p_contact_subscription(const PContact contact); +const char* p_contact_caps_str(const PContact contact); GDateTime* p_contact_last_activity(const PContact contact); gboolean p_contact_pending_out(const PContact contact); void p_contact_set_presence(const PContact contact, const char * const presence); void p_contact_set_status(const PContact contact, const char * const status); void p_contact_set_subscription(const PContact contact, const char * const subscription); +void p_contact_set_caps_str(const PContact contact, const char * const caps_str); void p_contact_set_pending_out(const PContact contact, gboolean pending_out); void p_contact_set_last_activity(const PContact contact, GDateTime *last_activity); diff --git a/src/contact_list.c b/src/contact_list.c index 2c9bc220..50a63475 100644 --- a/src/contact_list.c +++ b/src/contact_list.c @@ -70,7 +70,7 @@ contact_list_add(const char * const jid, const char * const name, if (contact == NULL) { contact = p_contact_new(jid, name, presence, status, subscription, - pending_out); + pending_out, NULL); g_hash_table_insert(contacts, strdup(jid), contact); p_autocomplete_add(ac, strdup(jid)); added = TRUE; @@ -87,9 +87,9 @@ contact_list_remove(const char * const jid) gboolean contact_list_update_contact(const char * const jid, const char * const presence, - const char * const status, GDateTime *last_activity) + const char * const status, GDateTime *last_activity, const char * const caps_str) { - gboolean changed = FALSE; + gboolean presence_changed = FALSE; PContact contact = g_hash_table_lookup(contacts, jid); if (contact == NULL) { @@ -98,20 +98,24 @@ contact_list_update_contact(const char * const jid, const char * const presence, if (g_strcmp0(p_contact_presence(contact), presence) != 0) { p_contact_set_presence(contact, presence); - changed = TRUE; + presence_changed = TRUE; } if (g_strcmp0(p_contact_status(contact), status) != 0) { p_contact_set_status(contact, status); - changed = TRUE; + presence_changed = TRUE; } if (!_datetimes_equal(p_contact_last_activity(contact), last_activity)) { p_contact_set_last_activity(contact, last_activity); - changed = TRUE; + presence_changed = TRUE; } - return changed; + if (g_strcmp0(p_contact_caps_str(contact), caps_str) != 0) { + p_contact_set_caps_str(contact, caps_str); + } + + return presence_changed; } void @@ -122,7 +126,7 @@ contact_list_update_subscription(const char * const jid, if (contact == NULL) { contact = p_contact_new(jid, NULL, "offline", NULL, subscription, - pending_out); + pending_out, NULL); g_hash_table_insert(contacts, strdup(jid), contact); } else { p_contact_set_subscription(contact, subscription); diff --git a/src/contact_list.h b/src/contact_list.h index da95aeac..43350631 100644 --- a/src/contact_list.h +++ b/src/contact_list.h @@ -36,7 +36,7 @@ gboolean contact_list_add(const char * const jid, const char * const name, const char * const presence, const char * const status, const char * const subscription, gboolean pending_out); gboolean contact_list_update_contact(const char * const jid, const char * const presence, - const char * const status, GDateTime *last_activity); + const char * const status, GDateTime *last_activity, const char * const caps_str); void contact_list_update_subscription(const char * const jid, const char * const subscription, gboolean pending_out); gboolean contact_list_has_pending_subscriptions(void); diff --git a/src/jabber.c b/src/jabber.c index 2d69ebec..244921bf 100644 --- a/src/jabber.c +++ b/src/jabber.c @@ -26,6 +26,7 @@ #include <strophe.h> +#include "capabilities.h" #include "chat_session.h" #include "common.h" #include "contact_list.h" @@ -84,9 +85,16 @@ static int _iq_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata); static int _roster_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata); +static int _disco_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata); +static int _disco_request_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata); +static int _version_request_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata); static int _presence_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata); static int _ping_timed_handler(xmpp_conn_t * const conn, void * const userdata); +static char * _handle_presence_caps(xmpp_stanza_t * const stanza); void jabber_init(const int disable_tls) @@ -414,6 +422,16 @@ jabber_update_presence(jabber_presence_t status, const char * const msg, xmpp_stanza_add_child(presence, query); } + // add caps + xmpp_stanza_t *caps = xmpp_stanza_new(jabber_conn.ctx); + xmpp_stanza_set_name(caps, STANZA_NAME_C); + xmpp_stanza_set_ns(caps, STANZA_NS_CAPS); + xmpp_stanza_set_attribute(caps, STANZA_ATTR_HASH, "sha-1"); + xmpp_stanza_set_attribute(caps, STANZA_ATTR_NODE, "http://www.profanity.im"); + xmpp_stanza_t *query = caps_get_query_response_stanza(jabber_conn.ctx); + char *sha1 = caps_get_sha1_str(query); + xmpp_stanza_set_attribute(caps, STANZA_ATTR_VER, sha1); + xmpp_stanza_add_child(presence, caps); xmpp_send(jabber_conn.conn, presence); // send presence for each room @@ -783,11 +801,24 @@ _iq_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata) { char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID); + char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); // handle the initial roster request - if ((id != NULL) && (strcmp(id, "roster") == 0)) { + if (g_strcmp0(id, "roster") == 0) { return _roster_handler(conn, stanza, userdata); + // handle disco responses + } else if ((id != NULL) && (g_str_has_prefix(id, "disco")) && + (g_strcmp0(type, "result") == 0)) { + return _disco_response_handler(conn, stanza, userdata); + + // handle disco requests + } else if (stanza_is_caps_request(stanza)) { + return _disco_request_handler(conn, stanza, userdata); + + } else if (stanza_is_version_request(stanza)) { + return _version_request_handler(conn, stanza, userdata); + // handle iq } else { char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); @@ -877,8 +908,8 @@ _iq_handler(xmpp_conn_t * const conn, } static int -_roster_handler(xmpp_conn_t * const conn, - xmpp_stanza_t * const stanza, void * const userdata) +_roster_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) { xmpp_stanza_t *query, *item; char *type = xmpp_stanza_get_type(stanza); @@ -921,6 +952,148 @@ _roster_handler(xmpp_conn_t * const conn, } static int +_version_request_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + + char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + char *id = xmpp_stanza_get_id(stanza); + + if (from != NULL) { + xmpp_stanza_t *response = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(response, STANZA_NAME_IQ); + if (id != NULL) { + xmpp_stanza_set_id(response, id); + } + xmpp_stanza_set_attribute(response, STANZA_ATTR_TO, from); + xmpp_stanza_set_type(response, STANZA_TYPE_RESULT); + + xmpp_stanza_t *query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, STANZA_NAME_QUERY); + xmpp_stanza_set_ns(query, STANZA_NS_VERSION); + + xmpp_stanza_t *name = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(name, "name"); + xmpp_stanza_t *name_txt = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(name_txt, "Profanity"); + xmpp_stanza_add_child(name, name_txt); + + xmpp_stanza_t *version = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(version, "version"); + xmpp_stanza_t *version_txt = xmpp_stanza_new(ctx); + GString *version_str = g_string_new(PACKAGE_VERSION); + if (strcmp(PACKAGE_STATUS, "development") == 0) { + g_string_append(version_str, "dev"); + } + xmpp_stanza_set_text(version_txt, version_str->str); + xmpp_stanza_add_child(version, version_txt); + + xmpp_stanza_add_child(query, name); + xmpp_stanza_add_child(query, version); + xmpp_stanza_add_child(response, query); + + xmpp_send(conn, response); + } + + return 1; +} + +static int +_disco_request_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + + xmpp_stanza_t *incoming_query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + char *node_str = xmpp_stanza_get_attribute(incoming_query, STANZA_ATTR_NODE); + char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + + if (from != NULL && node_str != NULL) { + xmpp_stanza_t *response = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(response, STANZA_NAME_IQ); + 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_get_query_response_stanza(ctx); + xmpp_stanza_set_attribute(query, STANZA_ATTR_NODE, node_str); + xmpp_stanza_add_child(response, query); + xmpp_send(conn, response); + } + + return 1; +} + +static int +_disco_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *type = xmpp_stanza_get_type(stanza); + char *id = xmpp_stanza_get_id(stanza); + + if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) { + log_error("Roster query failed"); + return 1; + } else { + xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + char *node = xmpp_stanza_get_attribute(query, STANZA_ATTR_NODE); + if (node == NULL) { + return 1; + } + + char *caps_key = NULL; + + // xep-0115 + if (g_strcmp0(id, "disco") == 0) { + caps_key = node; + + // validate sha1 + gchar **split = g_strsplit(node, "#", -1); + char *given_sha1 = split[1]; + char *generated_sha1 = caps_get_sha1_str(query); + + if (g_strcmp0(given_sha1, generated_sha1) != 0) { + log_info("Invalid SHA1 recieved for caps."); + return 1; + } + // non supported hash, or legacy caps + } else { + caps_key = id + 6; + } + + // already cached + if (caps_contains(caps_key)) { + log_info("Client info already cached."); + return 1; + } + + xmpp_stanza_t *identity = xmpp_stanza_get_child_by_name(query, "identity"); + + if (identity == NULL) { + return 1; + } + + const char *category = xmpp_stanza_get_attribute(identity, "category"); + if (category == NULL) { + return 1; + } + + if (strcmp(category, "client") != 0) { + return 1; + } + + const char *name = xmpp_stanza_get_attribute(identity, "name"); + if (name == 0) { + return 1; + } + + caps_add(caps_key, name); + + return 1; + } +} + +static int _ping_timed_handler(xmpp_conn_t * const conn, void * const userdata) { if (jabber_conn.conn_status == JABBER_CONNECTED) { @@ -974,6 +1147,7 @@ _room_presence_handler(const char * const jid, xmpp_stanza_t * const stanza) } else { char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); char *show_str, *status_str; + char *caps_key = _handle_presence_caps(stanza); xmpp_stanza_t *status = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_STATUS); if (status != NULL) { @@ -999,18 +1173,18 @@ _room_presence_handler(const char * const jid, xmpp_stanza_t * const stanza) show_str = "online"; } if (!muc_get_roster_received(room)) { - muc_add_to_roster(room, nick, show_str, status_str); + muc_add_to_roster(room, nick, show_str, status_str, caps_key); } else { char *old_nick = muc_complete_roster_nick_change(room, nick); if (old_nick != NULL) { - muc_add_to_roster(room, nick, show_str, status_str); + muc_add_to_roster(room, nick, show_str, status_str, caps_key); prof_handle_room_member_nick_change(room, old_nick, nick); } else { if (!muc_nick_in_roster(room, nick)) { - prof_handle_room_member_online(room, nick, show_str, status_str); + prof_handle_room_member_online(room, nick, show_str, status_str, caps_key); } else { - prof_handle_room_member_presence(room, nick, show_str, status_str); + prof_handle_room_member_presence(room, nick, show_str, status_str, caps_key); } } } @@ -1023,6 +1197,72 @@ _room_presence_handler(const char * const jid, xmpp_stanza_t * const stanza) return 1; } +static char * +_handle_presence_caps(xmpp_stanza_t * const stanza) +{ + char *caps_key = NULL; + char *node = NULL; + char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + if (stanza_contains_caps(stanza)) { + char *hash_type = stanza_caps_get_hash(stanza); + + // xep-0115 + if (hash_type != NULL) { + + // supported hash + if (strcmp(hash_type, "sha-1") == 0) { + node = stanza_get_caps_str(stanza); + caps_key = node; + + if (node != NULL) { + if (!caps_contains(caps_key)) { + xmpp_stanza_t *iq = stanza_create_disco_iq(jabber_conn.ctx, "disco", from, node); + xmpp_send(jabber_conn.conn, iq); + xmpp_stanza_release(iq); + } + } + + // unsupported hash + } else { + node = stanza_get_caps_str(stanza); + caps_key = from; + + if (node != NULL) { + if (!caps_contains(caps_key)) { + GString *id = g_string_new("disco_"); + g_string_append(id, from); + xmpp_stanza_t *iq = stanza_create_disco_iq(jabber_conn.ctx, id->str, from, node); + xmpp_send(jabber_conn.conn, iq); + xmpp_stanza_release(iq); + g_string_free(id, TRUE); + } + } + } + + return strdup(caps_key); + + //ignore or handle legacy caps + } else { + node = stanza_get_caps_str(stanza); + caps_key = from; + + if (node != NULL) { + if (!caps_contains(caps_key)) { + GString *id = g_string_new("disco_"); + g_string_append(id, from); + xmpp_stanza_t *iq = stanza_create_disco_iq(jabber_conn.ctx, id->str, from, node); + xmpp_send(jabber_conn.conn, iq); + xmpp_stanza_release(iq); + g_string_free(id, TRUE); + } + } + + return caps_key; + } + } + return NULL; +} + static int _presence_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata) @@ -1055,6 +1295,8 @@ _presence_handler(xmpp_conn_t * const conn, g_date_time_unref(now); } + char *caps_key = _handle_presence_caps(stanza); + xmpp_stanza_t *status = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_STATUS); if (status != NULL) status_str = xmpp_stanza_get_text(status); @@ -1069,7 +1311,7 @@ _presence_handler(xmpp_conn_t * const conn, show_str = "online"; if (strcmp(my_jid->barejid, from_jid->barejid) !=0) { - prof_handle_contact_online(from_jid->barejid, show_str, status_str, last_activity); + prof_handle_contact_online(from_jid->barejid, show_str, status_str, last_activity, caps_key); } } else if (strcmp(type, STANZA_TYPE_UNAVAILABLE) == 0) { if (strcmp(my_jid->barejid, from_jid->barejid) !=0) { diff --git a/src/jabber.h b/src/jabber.h index e1494851..02e540fc 100644 --- a/src/jabber.h +++ b/src/jabber.h @@ -23,6 +23,8 @@ #ifndef JABBER_H #define JABBER_H +#include <strophe.h> + #include "accounts.h" #include "jid.h" diff --git a/src/muc.c b/src/muc.c index ece77818..efe22abd 100644 --- a/src/muc.c +++ b/src/muc.c @@ -204,7 +204,8 @@ muc_nick_in_roster(const char * const room, const char * const nick) */ gboolean muc_add_to_roster(const char * const room, const char * const nick, - const char * const show, const char * const status) + const char * const show, const char * const status, + const char * const caps_str) { ChatRoom *chat_room = g_hash_table_lookup(rooms, room); gboolean updated = FALSE; @@ -219,8 +220,7 @@ muc_add_to_roster(const char * const room, const char * const nick, (g_strcmp0(p_contact_status(old), status) != 0)) { updated = TRUE; } - - PContact contact = p_contact_new(nick, NULL, show, status, NULL, FALSE); + PContact contact = p_contact_new(nick, NULL, show, status, NULL, FALSE, caps_str); g_hash_table_replace(chat_room->roster, strdup(nick), contact); } diff --git a/src/muc.h b/src/muc.h index 8411cb39..8b8ed018 100644 --- a/src/muc.h +++ b/src/muc.h @@ -39,7 +39,8 @@ void muc_complete_room_nick_change(const char * const room, const char * const nick); gboolean muc_add_to_roster(const char * const room, const char * const nick, - const char * const show, const char * const status); + const char * const show, const char * const status, + const char * const caps_str); void muc_remove_from_roster(const char * const room, const char * const nick); GList * muc_get_roster(const char * const room); PAutocomplete muc_get_roster_ac(const char * const room); diff --git a/src/profanity.c b/src/profanity.c index 91d8be78..47707595 100644 --- a/src/profanity.c +++ b/src/profanity.c @@ -30,6 +30,7 @@ #include <glib.h> #include "accounts.h" +#include "capabilities.h" #include "chat_log.h" #include "chat_session.h" #include "command.h" @@ -311,9 +312,9 @@ prof_handle_room_roster_complete(const char * const room) void prof_handle_room_member_presence(const char * const room, const char * const nick, const char * const show, - const char * const status) + const char * const status, const char * const caps_str) { - gboolean updated = muc_add_to_roster(room, nick, show, status); + gboolean updated = muc_add_to_roster(room, nick, show, status, caps_str); if (updated) { win_show_room_member_presence(room, nick, show, status); @@ -323,9 +324,10 @@ prof_handle_room_member_presence(const char * const room, void prof_handle_room_member_online(const char * const room, const char * const nick, - const char * const show, const char * const status) + const char * const show, const char * const status, + const char * const caps_str) { - muc_add_to_roster(room, nick, show, status); + muc_add_to_roster(room, nick, show, status, caps_str); win_show_room_member_online(room, nick, show, status); win_current_page_off(); } @@ -347,11 +349,11 @@ prof_handle_leave_room(const char * const room) void prof_handle_contact_online(char *contact, char *show, char *status, - GDateTime *last_activity) + GDateTime *last_activity, char *caps_str) { - gboolean updated = contact_list_update_contact(contact, show, status, last_activity); + gboolean presence_changed = contact_list_update_contact(contact, show, status, last_activity, caps_str); - if (updated) { + if (presence_changed) { PContact result = contact_list_get_contact(contact); if (p_contact_subscription(result) != NULL) { if (strcmp(p_contact_subscription(result), "none") != 0) { @@ -365,9 +367,9 @@ prof_handle_contact_online(char *contact, char *show, char *status, void prof_handle_contact_offline(char *contact, char *show, char *status) { - gboolean updated = contact_list_update_contact(contact, "offline", status, NULL); + gboolean presence_changed = contact_list_update_contact(contact, "offline", status, NULL, NULL); - if (updated) { + if (presence_changed) { PContact result = contact_list_get_contact(contact); if (p_contact_subscription(result) != NULL) { if (strcmp(p_contact_subscription(result), "none") != 0) { @@ -541,6 +543,7 @@ _init(const int disable_tls, char *log_level) theme_init(theme); g_free(theme); ui_init(); + caps_init(); jabber_init(disable_tls); cmd_init(); log_info("Initialising contact list"); @@ -553,6 +556,7 @@ _shutdown(void) { jabber_disconnect(); contact_list_free(); + caps_close(); ui_close(); chat_log_close(); prefs_close(); diff --git a/src/profanity.h b/src/profanity.h index 69f4adf5..a28bd292 100644 --- a/src/profanity.h +++ b/src/profanity.h @@ -34,7 +34,7 @@ void prof_handle_disconnect(const char * const jid); void prof_handle_failed_login(void); void prof_handle_typing(char *from); void prof_handle_contact_online(char *contact, char *show, char *status, - GDateTime *last_activity); + GDateTime *last_activity, char *caps_str); void prof_handle_contact_offline(char *contact, char *show, char *status); void prof_handle_incoming_message(char *from, char *message, gboolean priv); void prof_handle_delayed_message(char *from, char *message, GTimeVal tv_stamp, @@ -51,12 +51,13 @@ void prof_handle_room_subject(const char * const room_jid, const char * const subject); void prof_handle_room_roster_complete(const char * const room); void prof_handle_room_member_online(const char * const room, - const char * const nick, const char * const show, const char * const status); + const char * const nick, const char * const show, const char * const status, + const char * const caps_str); void prof_handle_room_member_offline(const char * const room, const char * const nick, const char * const show, const char * const status); void prof_handle_room_member_presence(const char * const room, const char * const nick, const char * const show, - const char * const status); + const char * const status, const char * const caps_str); void prof_handle_leave_room(const char * const room); void prof_handle_room_member_nick_change(const char * const room, const char * const old_nick, const char * const nick); diff --git a/src/stanza.c b/src/stanza.c index 7bf9414a..5e90cf90 100644 --- a/src/stanza.c +++ b/src/stanza.c @@ -29,6 +29,8 @@ #include "common.h" #include "stanza.h" +static int _field_compare(FormField *f1, FormField *f2); + xmpp_stanza_t * stanza_create_chat_state(xmpp_ctx_t *ctx, const char * const recipient, const char * const state) @@ -178,6 +180,27 @@ stanza_create_roster_iq(xmpp_ctx_t *ctx) return iq; } +xmpp_stanza_t * +stanza_create_disco_iq(xmpp_ctx_t *ctx, const char * const id, const char * const to, + const char * const node) +{ + xmpp_stanza_t *iq = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(iq, STANZA_NAME_IQ); + xmpp_stanza_set_type(iq, STANZA_TYPE_GET); + xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, to); + xmpp_stanza_set_id(iq, id); + + 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_set_attribute(query, STANZA_ATTR_NODE, node); + + xmpp_stanza_add_child(iq, query); + xmpp_stanza_release(query); + + return iq; +} + gboolean stanza_contains_chat_state(xmpp_stanza_t *stanza) { @@ -391,3 +414,175 @@ stanza_get_idle_time(xmpp_stanza_t * const stanza) return result; } } + +gboolean +stanza_contains_caps(xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *caps = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_C); + + if (caps == NULL) { + return FALSE; + } + + if (strcmp(xmpp_stanza_get_ns(caps), STANZA_NS_CAPS) != 0) { + return FALSE; + } + + return TRUE; +} + +gboolean +stanza_is_version_request(xmpp_stanza_t * const stanza) +{ + char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); + + if (g_strcmp0(type, STANZA_TYPE_GET) != 0) { + return FALSE; + } + + xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + + if (query == NULL) { + return FALSE; + } + + char *ns = xmpp_stanza_get_ns(query); + + if (ns == NULL) { + return FALSE; + } + + if (strcmp(ns, STANZA_NS_VERSION) != 0) { + return FALSE; + } + + return TRUE; +} + +gboolean +stanza_is_caps_request(xmpp_stanza_t * const stanza) +{ + char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE); + + if (g_strcmp0(type, STANZA_TYPE_GET) != 0) { + return FALSE; + } + + xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + + if (query == NULL) { + return FALSE; + } + + char *ns = xmpp_stanza_get_ns(query); + + if (ns == NULL) { + return FALSE; + } + + if (strcmp(ns, XMPP_NS_DISCO_INFO) != 0) { + return FALSE; + } + + return TRUE; +} + +char * +stanza_caps_get_hash(xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *caps = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_C); + + if (caps == NULL) { + return NULL; + } + + if (strcmp(xmpp_stanza_get_ns(caps), STANZA_NS_CAPS) != 0) { + return NULL; + } + + char *result = xmpp_stanza_get_attribute(caps, STANZA_ATTR_HASH); + + return result; + +} + +char * +stanza_get_caps_str(xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *caps = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_C); + + if (caps == NULL) { + return NULL; + } + + if (strcmp(xmpp_stanza_get_ns(caps), STANZA_NS_CAPS) != 0) { + return NULL; + } + + char *node = xmpp_stanza_get_attribute(caps, STANZA_ATTR_NODE); + char *ver = xmpp_stanza_get_attribute(caps, STANZA_ATTR_VER); + + if ((node == NULL) || (ver == NULL)) { + return NULL; + } + + GString *caps_gstr = g_string_new(node); + g_string_append(caps_gstr, "#"); + g_string_append(caps_gstr, ver); + char *caps_str = caps_gstr->str; + g_string_free(caps_gstr, FALSE); + + return caps_str; +} + +DataForm * +stanza_get_form(xmpp_stanza_t * const stanza) +{ + DataForm *result = NULL; + + xmpp_stanza_t *child = xmpp_stanza_get_children(stanza); + + if (child != NULL) { + result = malloc(sizeof(struct data_form_t)); + result->form_type = NULL; + result->fields = NULL; + } + + //handle fields + while (child != NULL) { + char *var = xmpp_stanza_get_attribute(child, "var"); + + // handle FORM_TYPE + if (g_strcmp0(var, "FORM_TYPE")) { + xmpp_stanza_t *value = xmpp_stanza_get_child_by_name(child, "value"); + char *value_text = xmpp_stanza_get_text(value); + result->form_type = strdup(value_text); + + // handle regular fields + } else { + FormField *field = malloc(sizeof(struct form_field_t)); + field->var = strdup(var); + field->values = NULL; + xmpp_stanza_t *value = xmpp_stanza_get_children(child); + + // handle values + while (value != NULL) { + char *text = xmpp_stanza_get_text(value); + field->values = g_slist_insert_sorted(field->values, text, (GCompareFunc)octet_compare); + value = xmpp_stanza_get_next(value); + } + + result->fields = g_slist_insert_sorted(result->fields, field, (GCompareFunc)_field_compare); + } + + child = xmpp_stanza_get_next(child); + } + + return result; +} + +static int +_field_compare(FormField *f1, FormField *f2) +{ + return octet_compare((unsigned char *)f1->var, (unsigned char *)f2->var); +} diff --git a/src/stanza.h b/src/stanza.h index a0a22908..497a7388 100644 --- a/src/stanza.h +++ b/src/stanza.h @@ -47,6 +47,9 @@ #define STANZA_NAME_TEXT "text" #define STANZA_NAME_SUBJECT "subject" #define STANZA_NAME_ITEM "item" +#define STANZA_NAME_C "c" +#define STANZA_NAME_IDENTITY "identity" +#define STANZA_NAME_FEATURE "feature" #define STANZA_TYPE_CHAT "chat" #define STANZA_TYPE_GROUPCHAT "groupchat" @@ -72,6 +75,10 @@ #define STANZA_ATTR_ASK "ask" #define STANZA_ATTR_ID "id" #define STANZA_ATTR_SECONDS "seconds" +#define STANZA_ATTR_NODE "node" +#define STANZA_ATTR_VER "ver" +#define STANZA_ATTR_VAR "var" +#define STANZA_ATTR_HASH "hash" #define STANZA_TEXT_AWAY "away" #define STANZA_TEXT_DND "dnd" @@ -82,8 +89,21 @@ #define STANZA_NS_CHATSTATES "http://jabber.org/protocol/chatstates" #define STANZA_NS_MUC "http://jabber.org/protocol/muc" #define STANZA_NS_MUC_USER "http://jabber.org/protocol/muc#user" +#define STANZA_NS_CAPS "http://jabber.org/protocol/caps" #define STANZA_NS_PING "urn:xmpp:ping" #define STANZA_NS_LASTACTIVITY "jabber:iq:last" +#define STANZA_NS_DATA "jabber:x:data" +#define STANZA_NS_VERSION "jabber:iq:version" + +typedef struct form_field_t { + char *var; + GSList *values; +} FormField; + +typedef struct data_form_t { + char *form_type; + GSList *fields; +} DataForm; xmpp_stanza_t* stanza_create_chat_state(xmpp_ctx_t *ctx, const char * const recipient, const char * const state); @@ -106,6 +126,8 @@ xmpp_stanza_t* stanza_create_presence(xmpp_ctx_t *ctx, const char * const show, xmpp_stanza_t* stanza_create_roster_iq(xmpp_ctx_t *ctx); xmpp_stanza_t* stanza_create_ping_iq(xmpp_ctx_t *ctx); +xmpp_stanza_t* stanza_create_disco_iq(xmpp_ctx_t *ctx, const char * const id, + const char * const to, const char * const node); gboolean stanza_contains_chat_state(xmpp_stanza_t *stanza); @@ -115,8 +137,16 @@ gboolean stanza_is_muc_self_presence(xmpp_stanza_t * const stanza, const char * const self_jid); gboolean stanza_is_room_nick_change(xmpp_stanza_t * const stanza); -char* stanza_get_new_nick(xmpp_stanza_t * const stanza); +char * stanza_get_new_nick(xmpp_stanza_t * const stanza); int stanza_get_idle_time(xmpp_stanza_t * const stanza); +char * stanza_get_caps_str(xmpp_stanza_t * const stanza); +gboolean stanza_contains_caps(xmpp_stanza_t * const stanza); +char * stanza_caps_get_hash(xmpp_stanza_t * const stanza); +gboolean stanza_is_caps_request(xmpp_stanza_t * const stanza); + +gboolean stanza_is_version_request(xmpp_stanza_t * const stanza); + +DataForm * stanza_get_form(xmpp_stanza_t * const stanza); #endif diff --git a/src/ui.h b/src/ui.h index b69c1f19..1f8b81d5 100644 --- a/src/ui.h +++ b/src/ui.h @@ -133,8 +133,11 @@ void win_show_room_nick_change(const char * const room, const char * const nick) void win_show_room_member_presence(const char * const room, const char * const nick, const char * const show, const char * const status); void win_room_show_status(const char * const contact); +void win_room_show_info(const char * const contact); void win_show_status(void); void win_private_show_status(void); +void win_show_info(void); +void win_private_show_info(void); // console window actions void cons_about(void); @@ -161,6 +164,7 @@ void cons_show_contacts(GSList * list); void cons_check_version(gboolean not_available_msg); void cons_show_wins(void); void cons_show_status(const char * const contact); +void cons_show_info(const char * const contact); void cons_show_themes(GSList *themes); // status bar actions diff --git a/src/windows.c b/src/windows.c index e0811dad..f281be24 100644 --- a/src/windows.c +++ b/src/windows.c @@ -44,6 +44,7 @@ #include <ncurses.h> #endif +#include "capabilities.h" #include "chat_log.h" #include "chat_session.h" #include "command.h" @@ -109,8 +110,11 @@ static void _win_resize_all(void); static gint _win_get_unread(void); static void _win_show_history(WINDOW *win, int win_index, const char * const contact); +static void _win_show_info(WINDOW *win, PContact pcontact); static gboolean _new_release(char *found_version); static void _ui_draw_win_title(void); +static void _presence_colour_on(WINDOW *win, const char * const presence); +static void _presence_colour_off(WINDOW *win, const char * const presence); static void _notify(const char * const message, int timeout, const char * const category); @@ -850,35 +854,9 @@ win_show_room_roster(const char * const room, GList *roster, const char * const const char const *name = p_contact_jid(member); const char const *show = p_contact_presence(member); - if (strcmp(show, "away") == 0) { - wattron(win, COLOUR_AWAY); - } else if (strcmp(show, "chat") == 0) { - wattron(win, COLOUR_CHAT); - } else if (strcmp(show, "dnd") == 0) { - wattron(win, COLOUR_DND); - } else if (strcmp(show, "xa") == 0) { - wattron(win, COLOUR_XA); - } else if (strcmp(show, "online") == 0) { - wattron(win, COLOUR_ONLINE); - } else { - wattron(win, COLOUR_OFFLINE); - } - + _presence_colour_on(win, show); wprintw(win, "%s", name); - - if (strcmp(show, "away") == 0) { - wattroff(win, COLOUR_AWAY); - } else if (strcmp(show, "chat") == 0) { - wattroff(win, COLOUR_CHAT); - } else if (strcmp(show, "dnd") == 0) { - wattroff(win, COLOUR_DND); - } else if (strcmp(show, "xa") == 0) { - wattroff(win, COLOUR_XA); - } else if (strcmp(show, "online") == 0) { - wattroff(win, COLOUR_ONLINE); - } else { - wattroff(win, COLOUR_OFFLINE); - } + _presence_colour_off(win, show); if (roster->next != NULL) { wprintw(win, ", "); @@ -1181,6 +1159,57 @@ cons_show_wins(void) } void +win_room_show_info(const char * const contact) +{ + PContact pcontact = muc_get_participant(win_current_get_recipient(), contact); + + if (pcontact != NULL) { + _win_show_info(current->win, pcontact); + } else { + win_current_show("No such participant \"%s\" in room.", contact); + } + +} + +void +cons_show_info(const char * const contact) +{ + PContact pcontact = contact_list_get_contact(contact); + + if (pcontact != NULL) { + _win_show_info(console->win, pcontact); + } else { + cons_show("No such contact \"%s\" in roster.", contact); + } +} + +void +win_show_info(void) +{ + PContact pcontact = contact_list_get_contact(win_current_get_recipient()); + + if (pcontact != NULL) { + _win_show_info(current->win, pcontact); + } else { + win_current_show("No such contact \"%s\" in roster.", win_current_get_recipient()); + } +} + +void +win_private_show_info(void) +{ + Jid *jid = jid_create(win_current_get_recipient()); + + PContact pcontact = muc_get_participant(jid->barejid, jid->resourcepart); + + if (pcontact != NULL) { + _win_show_info(current->win, pcontact); + } else { + win_current_show("No such participant \"%s\" in room.", jid->resourcepart); + } +} + +void cons_show_status(const char * const contact) { PContact pcontact = contact_list_get_contact(contact); @@ -2030,6 +2059,42 @@ _win_resize_all(void) } static void +_presence_colour_on(WINDOW *win, const char * const presence) +{ + if (g_strcmp0(presence, "online") == 0) { + wattron(win, COLOUR_ONLINE); + } else if (g_strcmp0(presence, "away") == 0) { + wattron(win, COLOUR_AWAY); + } else if (g_strcmp0(presence, "chat") == 0) { + wattron(win, COLOUR_CHAT); + } else if (g_strcmp0(presence, "dnd") == 0) { + wattron(win, COLOUR_DND); + } else if (g_strcmp0(presence, "xa") == 0) { + wattron(win, COLOUR_XA); + } else { + wattron(win, COLOUR_OFFLINE); + } +} + +static void +_presence_colour_off(WINDOW *win, const char * const presence) +{ + if (g_strcmp0(presence, "online") == 0) { + wattroff(win, COLOUR_ONLINE); + } else if (g_strcmp0(presence, "away") == 0) { + wattroff(win, COLOUR_AWAY); + } else if (g_strcmp0(presence, "chat") == 0) { + wattroff(win, COLOUR_CHAT); + } else if (g_strcmp0(presence, "dnd") == 0) { + wattroff(win, COLOUR_DND); + } else if (g_strcmp0(presence, "xa") == 0) { + wattroff(win, COLOUR_XA); + } else { + wattroff(win, COLOUR_OFFLINE); + } +} + +static void _show_status_string(WINDOW *win, const char * const from, const char * const show, const char * const status, GDateTime *last_activity, const char * const pre, @@ -2138,21 +2203,7 @@ _win_show_contact(ProfWin *window, PContact contact) GDateTime *last_activity = p_contact_last_activity(contact); _win_show_time(window->win, '-'); - - if (strcmp(presence, "online") == 0) { - wattron(window->win, COLOUR_ONLINE); - } else if (strcmp(presence, "away") == 0) { - wattron(window->win, COLOUR_AWAY); - } else if (strcmp(presence, "chat") == 0) { - wattron(window->win, COLOUR_CHAT); - } else if (strcmp(presence, "dnd") == 0) { - wattron(window->win, COLOUR_DND); - } else if (strcmp(presence, "xa") == 0) { - wattron(window->win, COLOUR_XA); - } else { - wattron(window->win, COLOUR_OFFLINE); - } - + _presence_colour_on(window->win, presence); wprintw(window->win, "%s", jid); if (name != NULL) { @@ -2186,20 +2237,7 @@ _win_show_contact(ProfWin *window, PContact contact) } wprintw(window->win, "\n"); - - if (strcmp(presence, "online") == 0) { - wattroff(window->win, COLOUR_ONLINE); - } else if (strcmp(presence, "away") == 0) { - wattroff(window->win, COLOUR_AWAY); - } else if (strcmp(presence, "chat") == 0) { - wattroff(window->win, COLOUR_CHAT); - } else if (strcmp(presence, "dnd") == 0) { - wattroff(window->win, COLOUR_DND); - } else if (strcmp(presence, "xa") == 0) { - wattroff(window->win, COLOUR_XA); - } else { - wattroff(window->win, COLOUR_OFFLINE); - } + _presence_colour_off(window->win, presence); } static void @@ -2331,6 +2369,80 @@ _win_show_history(WINDOW *win, int win_index, const char * const contact) } } +static void +_win_show_info(WINDOW *win, PContact pcontact) +{ + const char *jid = p_contact_jid(pcontact); + const char *name = p_contact_name(pcontact); + const char *presence = p_contact_presence(pcontact); + const char *status = p_contact_status(pcontact); + const char *sub = p_contact_subscription(pcontact); + const char *caps_str = p_contact_caps_str(pcontact); + GDateTime *last_activity = p_contact_last_activity(pcontact); + + _win_show_time(win, '-'); + wprintw(win, "\n"); + _win_show_time(win, '-'); + _presence_colour_on(win, presence); + wprintw(win, "%s:\n", jid); + _presence_colour_off(win, presence); + + if (name != NULL) { + _win_show_time(win, '-'); + wprintw(win, "Name : %s\n", name); + } + + if (sub != NULL) { + _win_show_time(win, '-'); + wprintw(win, "Subscription : %s\n", sub); + } + + _win_show_time(win, '-'); + wprintw(win, "Presence : "); + _presence_colour_on(win, presence); + wprintw(win, "%s\n", presence); + _presence_colour_off(win, presence); + + if (status != NULL) { + _win_show_time(win, '-'); + wprintw(win, "Message : %s\n", status); + } + + if (last_activity != NULL) { + GDateTime *now = g_date_time_new_now_local(); + GTimeSpan span = g_date_time_difference(now, last_activity); + + _win_show_time(win, '-'); + wprintw(win, "Last activity : "); + + int hours = span / G_TIME_SPAN_HOUR; + span = span - hours * G_TIME_SPAN_HOUR; + if (hours > 0) { + wprintw(win, "%dh", hours); + } + + int minutes = span / G_TIME_SPAN_MINUTE; + span = span - minutes * G_TIME_SPAN_MINUTE; + wprintw(win, "%dm", minutes); + + int seconds = span / G_TIME_SPAN_SECOND; + wprintw(win, "%ds", seconds); + + wprintw(win, "\n"); + + g_date_time_unref(now); + } + + if (caps_str != NULL) { + Capabilities *caps = caps_get(caps_str); + if ((caps != NULL) && (caps->client != NULL)) { + _win_show_time(win, '-'); + wprintw(win, "Client : %s\n", caps->client); + } + } + +} + void _set_current(int index) { |