* purple - Jabber Protocol Plugin * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #include "conversation.h" static char idn_buffer[1024]; static gboolean jabber_nodeprep(char *str, size_t buflen) return stringprep_xmpp_nodeprep(str, buflen) == STRINGPREP_OK; static gboolean jabber_resourceprep(char *str, size_t buflen) return stringprep_xmpp_resourceprep(str, buflen) == STRINGPREP_OK; jabber_idn_validate(const char *str, const char *at, const char *slash, const char *domain = NULL; const char *resource = NULL; /* Ensure no parts are > 1023 bytes */ domain_len = slash - (at + 1); resource_len = null - (slash + 1); domain_len = null - (at + 1); domain_len = slash - str; resource_len = null - (slash + 1); if (node && node_len > 1023) if (resource && resource_len > 1023) jid = g_new0(JabberID, 1); strncpy(idn_buffer, node, node_len); idn_buffer[node_len] = '\0'; if (!jabber_nodeprep(idn_buffer, sizeof(idn_buffer))) { jid->node = g_strdup(idn_buffer); /* domain *must* be here */ strncpy(idn_buffer, domain, domain_len); idn_buffer[domain_len] = '\0'; if (domain[0] == '[') { /* IPv6 address */ if (domain_len > 2 && idn_buffer[domain_len - 1] == ']') { idn_buffer[domain_len - 1] = '\0'; addr = g_inet_address_new_from_string(idn_buffer + 1); valid = (g_inet_address_get_family(addr) == jid->domain = g_strndup(domain, domain_len); if (stringprep_nameprep(idn_buffer, sizeof(idn_buffer)) != STRINGPREP_OK) { if (idna_to_ascii_8z(idn_buffer, &out, IDNA_USE_STD3_ASCII_RULES) != IDNA_SUCCESS) { /* This *MUST* be freed using 'free', not 'g_free' */ jid->domain = g_strdup(idn_buffer); strncpy(idn_buffer, resource, resource_len); idn_buffer[resource_len] = '\0'; if (!jabber_resourceprep(idn_buffer, sizeof(idn_buffer))) { jid->resource = g_strdup(idn_buffer); gboolean jabber_nodeprep_validate(const char *str) strncpy(idn_buffer, str, sizeof(idn_buffer) - 1); idn_buffer[sizeof(idn_buffer) - 1] = '\0'; result = jabber_nodeprep(idn_buffer, sizeof(idn_buffer)); gunichar ch = g_utf8_get_char(c); if(ch == '\"' || ch == '&' || ch == '\'' || ch == '/' || ch == ':' || ch == '<' || ch == '>' || ch == '@' || !g_unichar_isgraph(ch)) { gboolean jabber_domain_validate(const char *str) /* Check if str is a valid IPv6 identifier */ if (len <= 2 || *(c + len - 1) != ']') { *(gchar *)(c + len - 1) = '\0'; addr = g_inet_address_new_from_string(c + 1); valid = (g_inet_address_get_family(addr) == G_SOCKET_FAMILY_IPV6); *(gchar *)(c + len - 1) = ']'; gunichar ch = g_utf8_get_char(c); /* The list of characters allowed in domain names is pretty small */ if ((ch <= 0x7F && !( (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || ch == '-' )) || (ch >= 0x80 && !g_unichar_isgraph(ch))) gboolean jabber_resourceprep_validate(const char *str) strncpy(idn_buffer, str, sizeof(idn_buffer) - 1); idn_buffer[sizeof(idn_buffer) - 1] = '\0'; result = jabber_resourceprep(idn_buffer, sizeof(idn_buffer)); gunichar ch = g_utf8_get_char(c); if(!g_unichar_isgraph(ch) && ch != ' ') char *jabber_saslprep(const char *in) g_return_val_if_fail(in != NULL, NULL); g_return_val_if_fail(strlen(in) <= sizeof(idn_buffer) - 1, NULL); strncpy(idn_buffer, in, sizeof(idn_buffer) - 1); idn_buffer[sizeof(idn_buffer) - 1] = '\0'; if (STRINGPREP_OK != stringprep(idn_buffer, sizeof(idn_buffer), 0, memset(idn_buffer, 0, sizeof(idn_buffer)); out = g_strdup(idn_buffer); memset(idn_buffer, 0, sizeof(idn_buffer)); /* TODO: Something better than disallowing all non-ASCII characters */ /* TODO: Is this even correct? */ if (*c > 0x7f || /* Non-ASCII characters */ *c == 0x7f || /* ASCII Delete character */ (*c < 0x20 && *c != '\t' && *c != '\n' && *c != '\r')) /* ASCII control characters */ jabber_id_new_internal(const char *str, gboolean allow_terminating_slash) const char *slash = NULL; gboolean needs_validation = FALSE; for (c = str; *c != '\0'; c++) /* Multiple @'s in the node/domain portion, not a valid JID! */ /* JIDs cannot start with @ */ /* JIDs cannot end with @ */ /* JIDs cannot start with / */ if (c[1] == '\0' && !allow_terminating_slash) { /* JIDs cannot end with / */ /* characters allowed everywhere */ if ((*c >= 'a' && *c <= 'z') || (*c >= '0' && *c <= '9') || (*c >= 'A' && *c <= 'Z') || *c == '.' || *c == '-') * Hmm, this character is a bit more exotic. Better fall * back to using the more expensive UTF-8 compliant /* JID is made of only ASCII characters--just lowercase and return */ jid = g_new0(JabberID, 1); jid->node = g_ascii_strdown(str, at - str); jid->domain = g_ascii_strdown(at + 1, slash - (at + 1)); jid->resource = g_strdup(slash + 1); jid->domain = g_ascii_strdown(at + 1, -1); jid->domain = g_ascii_strdown(str, slash - str); jid->resource = g_strdup(slash + 1); jid->domain = g_ascii_strdown(str, -1); * If we get here, there are some non-ASCII chars in the string, so * we'll need to validate it, normalize, and finally do a full jabber if (!g_utf8_validate(str, -1, NULL)) return jabber_idn_validate(str, at, slash, c /* points to the null */); jid = g_new0(JabberID, 1); node = g_utf8_casefold(str, at-str); domain = g_utf8_casefold(at+1, slash-(at+1)); jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC); domain = g_utf8_casefold(at+1, -1); domain = g_utf8_casefold(str, slash-str); jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC); domain = g_utf8_casefold(str, -1); jid->node = g_utf8_normalize(node, -1, G_NORMALIZE_NFKC); jid->domain = g_utf8_normalize(domain, -1, G_NORMALIZE_NFKC); /* and finally the jabber nodeprep */ if(!jabber_nodeprep_validate(jid->node) || !jabber_domain_validate(jid->domain) || !jabber_resourceprep_validate(jid->resource)) { jabber_id_free(JabberID *jid) jabber_id_equal(const JabberID *jid1, const JabberID *jid2) /* Both are null therefore equal */ /* One is null, other is non-null, therefore not equal */ return purple_strequal(jid1->node, jid2->node) && purple_strequal(jid1->domain, jid2->domain) && purple_strequal(jid1->resource, jid2->resource); char *jabber_get_domain(const char *in) JabberID *jid = jabber_id_new(in); out = g_strdup(jid->domain); char *jabber_get_resource(const char *in) JabberID *jid = jabber_id_new(in); out = g_strdup(jid->resource); jabber_id_to_bare_jid(const JabberID *jid) JabberID *result = g_new0(JabberID, 1); result->node = g_strdup(jid->node); result->domain = g_strdup(jid->domain); jabber_get_bare_jid(const char *in) JabberID *jid = jabber_id_new(in); out = jabber_id_get_bare_jid(jid); jabber_id_get_bare_jid(const JabberID *jid) g_return_val_if_fail(jid != NULL, NULL); return g_strconcat(jid->node ? jid->node : "", jabber_id_get_full_jid(const JabberID *jid) g_return_val_if_fail(jid != NULL, NULL); return g_strconcat(jid->node ? jid->node : "", jid->resource ? "/" : "", jid->resource ? jid->resource : "", jabber_jid_is_domain(const char *jid) if (*c == '@' || *c == '/') jabber_id_new(const char *str) return jabber_id_new_internal(str, FALSE); const char *jabber_normalize(const PurpleAccount *account, const char *in) PurpleConnection *gc = NULL; static char buf[3072]; /* maximum legal length of a jabber jid */ gc = purple_account_get_connection((PurpleAccount *)account); js = purple_connection_get_protocol_data(gc); jid = jabber_id_new_internal(in, TRUE); if(js && jid->node && jid->resource && jabber_chat_find(js, jid->node, jid->domain)) g_snprintf(buf, sizeof(buf), "%s@%s/%s", jid->node, jid->domain, g_snprintf(buf, sizeof(buf), "%s%s%s", jid->node ? jid->node : "", jid->node ? "@" : "", jid->domain); jabber_is_own_server(JabberStream *js, const char *str) g_return_val_if_fail(*str != '\0', FALSE); jid = jabber_id_new(str); equal = (jid->node == NULL && purple_strequal(jid->domain, js->user->domain) && jabber_is_own_account(JabberStream *js, const char *str) g_return_val_if_fail(*str != '\0', FALSE); jid = jabber_id_new(str); equal = (purple_strequal(jid->node, js->user->node) && purple_strequal(jid->domain, js->user->domain) && (jid->resource == NULL || purple_strequal(jid->resource, js->user->resource))); const char *status_id; /* link to core */ const char *show; /* The show child's cdata in a presence stanza */ const char *readable; /* readable representation */ { "offline", NULL, N_("Offline"), JABBER_BUDDY_STATE_UNAVAILABLE }, { "available", NULL, N_("Available"), JABBER_BUDDY_STATE_ONLINE}, { "freeforchat", "chat", N_("Chatty"), JABBER_BUDDY_STATE_CHAT }, { "away", "away", N_("Away"), JABBER_BUDDY_STATE_AWAY }, { "extended_away", "xa", N_("Extended Away"), JABBER_BUDDY_STATE_XA }, { "dnd", "dnd", N_("Do Not Disturb"), JABBER_BUDDY_STATE_DND }, { "error", NULL, N_("Error"), JABBER_BUDDY_STATE_ERROR } jabber_buddy_state_get_name(const JabberBuddyState state) for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i) if (jabber_statuses[i].state == state) return _(jabber_statuses[i].readable); jabber_buddy_status_id_get_state(const char *id) return JABBER_BUDDY_STATE_UNKNOWN; for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i) if (purple_strequal(id, jabber_statuses[i].status_id)) return jabber_statuses[i].state; return JABBER_BUDDY_STATE_UNKNOWN; JabberBuddyState jabber_buddy_show_get_state(const char *id) g_return_val_if_fail(id != NULL, JABBER_BUDDY_STATE_UNKNOWN); for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i) if (jabber_statuses[i].show && purple_strequal(id, jabber_statuses[i].show)) return jabber_statuses[i].state; purple_debug_warning("jabber", "Invalid value of presence <show/> " return JABBER_BUDDY_STATE_UNKNOWN; jabber_buddy_state_get_show(JabberBuddyState state) for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i) if (state == jabber_statuses[i].state) return jabber_statuses[i].show; jabber_buddy_state_get_status_id(JabberBuddyState state) for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i) if (state == jabber_statuses[i].state) return jabber_statuses[i].status_id;