Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / jabber / jutil.c
blobc4353db097091cb63b24d9c9fc16fff3bfbcaf25
1 /*
2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include "internal.h"
24 #include "account.h"
25 #include "conversation.h"
26 #include "debug.h"
27 #include "server.h"
28 #include "util.h"
29 #include "xmlnode.h"
31 #include "chat.h"
32 #include "presence.h"
33 #include "jutil.h"
35 #include "ciphers/sha1hash.h"
36 #include "ciphers/sha256hash.h"
37 #include "ciphers/md5hash.h"
39 #ifdef USE_IDN
40 #include <idna.h>
41 #include <stringprep.h>
42 static char idn_buffer[1024];
43 #endif
45 #ifdef USE_IDN
46 static gboolean jabber_nodeprep(char *str, size_t buflen)
48 return stringprep_xmpp_nodeprep(str, buflen) == STRINGPREP_OK;
51 static gboolean jabber_resourceprep(char *str, size_t buflen)
53 return stringprep_xmpp_resourceprep(str, buflen) == STRINGPREP_OK;
56 static JabberID*
57 jabber_idn_validate(const char *str, const char *at, const char *slash,
58 const char *null)
60 const char *node = NULL;
61 const char *domain = NULL;
62 const char *resource = NULL;
63 int node_len = 0;
64 int domain_len = 0;
65 int resource_len = 0;
66 char *out;
67 JabberID *jid;
69 /* Ensure no parts are > 1023 bytes */
70 if (at) {
71 node = str;
72 node_len = at - str;
74 domain = at + 1;
75 if (slash) {
76 domain_len = slash - (at + 1);
77 resource = slash + 1;
78 resource_len = null - (slash + 1);
79 } else {
80 domain_len = null - (at + 1);
82 } else {
83 domain = str;
85 if (slash) {
86 domain_len = slash - str;
87 resource = slash + 1;
88 resource_len = null - (slash + 1);
89 } else {
90 domain_len = null - str;
94 if (node && node_len > 1023)
95 return NULL;
96 if (domain_len > 1023)
97 return NULL;
98 if (resource && resource_len > 1023)
99 return NULL;
101 jid = g_new0(JabberID, 1);
103 if (node) {
104 strncpy(idn_buffer, node, node_len);
105 idn_buffer[node_len] = '\0';
107 if (!jabber_nodeprep(idn_buffer, sizeof(idn_buffer))) {
108 jabber_id_free(jid);
109 jid = NULL;
110 goto out;
113 jid->node = g_strdup(idn_buffer);
116 /* domain *must* be here */
117 strncpy(idn_buffer, domain, domain_len);
118 idn_buffer[domain_len] = '\0';
119 if (domain[0] == '[') { /* IPv6 address */
120 gboolean valid = FALSE;
122 if (idn_buffer[domain_len - 1] == ']') {
123 idn_buffer[domain_len - 1] = '\0';
124 valid = purple_ipv6_address_is_valid(idn_buffer + 1);
127 if (!valid) {
128 jabber_id_free(jid);
129 jid = NULL;
130 goto out;
133 jid->domain = g_strndup(domain, domain_len);
134 } else {
135 /* Apply nameprep */
136 if (stringprep_nameprep(idn_buffer, sizeof(idn_buffer)) != STRINGPREP_OK) {
137 jabber_id_free(jid);
138 jid = NULL;
139 goto out;
142 /* And now ToASCII */
143 if (idna_to_ascii_8z(idn_buffer, &out, IDNA_USE_STD3_ASCII_RULES) != IDNA_SUCCESS) {
144 jabber_id_free(jid);
145 jid = NULL;
146 goto out;
149 /* This *MUST* be freed using 'free', not 'g_free' */
150 free(out);
151 jid->domain = g_strdup(idn_buffer);
154 if (resource) {
155 strncpy(idn_buffer, resource, resource_len);
156 idn_buffer[resource_len] = '\0';
158 if (!jabber_resourceprep(idn_buffer, sizeof(idn_buffer))) {
159 jabber_id_free(jid);
160 jid = NULL;
161 goto out;
162 } else
163 jid->resource = g_strdup(idn_buffer);
166 out:
167 return jid;
170 #endif /* USE_IDN */
172 gboolean jabber_nodeprep_validate(const char *str)
174 #ifdef USE_IDN
175 gboolean result;
176 #else
177 const char *c;
178 #endif
180 if(!str)
181 return TRUE;
183 if(strlen(str) > 1023)
184 return FALSE;
186 #ifdef USE_IDN
187 strncpy(idn_buffer, str, sizeof(idn_buffer) - 1);
188 idn_buffer[sizeof(idn_buffer) - 1] = '\0';
189 result = jabber_nodeprep(idn_buffer, sizeof(idn_buffer));
190 return result;
191 #else /* USE_IDN */
192 c = str;
193 while(c && *c) {
194 gunichar ch = g_utf8_get_char(c);
195 if(ch == '\"' || ch == '&' || ch == '\'' || ch == '/' || ch == ':' ||
196 ch == '<' || ch == '>' || ch == '@' || !g_unichar_isgraph(ch)) {
197 return FALSE;
199 c = g_utf8_next_char(c);
202 return TRUE;
203 #endif /* USE_IDN */
206 gboolean jabber_domain_validate(const char *str)
208 const char *c;
209 size_t len;
211 if(!str)
212 return TRUE;
214 len = strlen(str);
215 if (len > 1023)
216 return FALSE;
218 c = str;
220 if (*c == '[') {
221 /* Check if str is a valid IPv6 identifier */
222 gboolean valid = FALSE;
224 if (*(c + len - 1) != ']')
225 return FALSE;
227 /* Ugly, but in-place */
228 *(gchar *)(c + len - 1) = '\0';
229 valid = purple_ipv6_address_is_valid(c + 1);
230 *(gchar *)(c + len - 1) = ']';
232 return valid;
235 while(c && *c) {
236 gunichar ch = g_utf8_get_char(c);
237 /* The list of characters allowed in domain names is pretty small */
238 if ((ch <= 0x7F && !( (ch >= 'a' && ch <= 'z')
239 || (ch >= '0' && ch <= '9')
240 || (ch >= 'A' && ch <= 'Z')
241 || ch == '.'
242 || ch == '-' )) || (ch >= 0x80 && !g_unichar_isgraph(ch)))
243 return FALSE;
245 c = g_utf8_next_char(c);
248 return TRUE;
251 gboolean jabber_resourceprep_validate(const char *str)
253 #ifdef USE_IDN
254 gboolean result;
255 #else
256 const char *c;
257 #endif
259 if(!str)
260 return TRUE;
262 if(strlen(str) > 1023)
263 return FALSE;
265 #ifdef USE_IDN
266 strncpy(idn_buffer, str, sizeof(idn_buffer) - 1);
267 idn_buffer[sizeof(idn_buffer) - 1] = '\0';
268 result = jabber_resourceprep(idn_buffer, sizeof(idn_buffer));
269 return result;
270 #else /* USE_IDN */
271 c = str;
272 while(c && *c) {
273 gunichar ch = g_utf8_get_char(c);
274 if(!g_unichar_isgraph(ch) && ch != ' ')
275 return FALSE;
277 c = g_utf8_next_char(c);
280 return TRUE;
281 #endif /* USE_IDN */
284 char *jabber_saslprep(const char *in)
286 #ifdef USE_IDN
287 char *out;
289 g_return_val_if_fail(in != NULL, NULL);
290 g_return_val_if_fail(strlen(in) <= sizeof(idn_buffer) - 1, NULL);
292 strncpy(idn_buffer, in, sizeof(idn_buffer) - 1);
293 idn_buffer[sizeof(idn_buffer) - 1] = '\0';
295 if (STRINGPREP_OK != stringprep(idn_buffer, sizeof(idn_buffer), 0,
296 stringprep_saslprep)) {
297 memset(idn_buffer, 0, sizeof(idn_buffer));
298 return NULL;
301 out = g_strdup(idn_buffer);
302 memset(idn_buffer, 0, sizeof(idn_buffer));
303 return out;
304 #else /* USE_IDN */
305 /* TODO: Something better than disallowing all non-ASCII characters */
306 /* TODO: Is this even correct? */
307 const guchar *c;
309 c = (const guchar *)in;
310 for ( ; *c; ++c) {
311 if (*c > 0x7f || /* Non-ASCII characters */
312 *c == 0x7f || /* ASCII Delete character */
313 (*c < 0x20 && *c != '\t' && *c != '\n' && *c != '\r'))
314 /* ASCII control characters */
315 return NULL;
318 return g_strdup(in);
319 #endif /* USE_IDN */
322 static JabberID*
323 jabber_id_new_internal(const char *str, gboolean allow_terminating_slash)
325 const char *at = NULL;
326 const char *slash = NULL;
327 const char *c;
328 gboolean needs_validation = FALSE;
329 #if 0
330 gboolean node_is_required = FALSE;
331 #endif
332 #ifndef USE_IDN
333 char *node = NULL;
334 char *domain;
335 #endif
336 JabberID *jid;
338 if (!str)
339 return NULL;
341 for (c = str; *c != '\0'; c++)
343 switch (*c) {
344 case '@':
345 if (!slash) {
346 if (at) {
347 /* Multiple @'s in the node/domain portion, not a valid JID! */
348 return NULL;
350 if (c == str) {
351 /* JIDs cannot start with @ */
352 return NULL;
354 if (c[1] == '\0') {
355 /* JIDs cannot end with @ */
356 return NULL;
358 at = c;
360 break;
362 case '/':
363 if (!slash) {
364 if (c == str) {
365 /* JIDs cannot start with / */
366 return NULL;
368 if (c[1] == '\0' && !allow_terminating_slash) {
369 /* JIDs cannot end with / */
370 return NULL;
372 slash = c;
374 break;
376 default:
377 /* characters allowed everywhere */
378 if ((*c >= 'a' && *c <= 'z')
379 || (*c >= '0' && *c <= '9')
380 || (*c >= 'A' && *c <= 'Z')
381 || *c == '.' || *c == '-')
382 /* We're good */
383 break;
385 #if 0
386 if (slash != NULL) {
387 /* characters allowed only in the resource */
388 if (implement_me)
389 /* We're good */
390 break;
393 /* characters allowed only in the node */
394 if (implement_me) {
396 * Ok, this character is valid, but only if it's a part
397 * of the node and not the domain. But we don't know
398 * if "c" is a part of the node or the domain until after
399 * we've found the @. So set a flag for now and check
400 * that we found an @ later.
402 node_is_required = TRUE;
403 break;
405 #endif
408 * Hmm, this character is a bit more exotic. Better fall
409 * back to using the more expensive UTF-8 compliant
410 * stringprep functions.
412 needs_validation = TRUE;
413 break;
417 #if 0
418 if (node_is_required && at == NULL)
419 /* Found invalid characters in the domain */
420 return NULL;
421 #endif
423 if (!needs_validation) {
424 /* JID is made of only ASCII characters--just lowercase and return */
425 jid = g_new0(JabberID, 1);
427 if (at) {
428 jid->node = g_ascii_strdown(str, at - str);
429 if (slash) {
430 jid->domain = g_ascii_strdown(at + 1, slash - (at + 1));
431 if (*(slash + 1))
432 jid->resource = g_strdup(slash + 1);
433 } else {
434 jid->domain = g_ascii_strdown(at + 1, -1);
436 } else {
437 if (slash) {
438 jid->domain = g_ascii_strdown(str, slash - str);
439 if (*(slash + 1))
440 jid->resource = g_strdup(slash + 1);
441 } else {
442 jid->domain = g_ascii_strdown(str, -1);
445 return jid;
449 * If we get here, there are some non-ASCII chars in the string, so
450 * we'll need to validate it, normalize, and finally do a full jabber
451 * nodeprep on the jid.
454 if (!g_utf8_validate(str, -1, NULL))
455 return NULL;
457 #ifdef USE_IDN
458 return jabber_idn_validate(str, at, slash, c /* points to the null */);
459 #else /* USE_IDN */
461 jid = g_new0(JabberID, 1);
463 /* normalization */
464 if(at) {
465 node = g_utf8_casefold(str, at-str);
466 if(slash) {
467 domain = g_utf8_casefold(at+1, slash-(at+1));
468 if (*(slash + 1))
469 jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
470 } else {
471 domain = g_utf8_casefold(at+1, -1);
473 } else {
474 if(slash) {
475 domain = g_utf8_casefold(str, slash-str);
476 if (*(slash + 1))
477 jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
478 } else {
479 domain = g_utf8_casefold(str, -1);
483 if (node) {
484 jid->node = g_utf8_normalize(node, -1, G_NORMALIZE_NFKC);
485 g_free(node);
488 if (domain) {
489 jid->domain = g_utf8_normalize(domain, -1, G_NORMALIZE_NFKC);
490 g_free(domain);
493 /* and finally the jabber nodeprep */
494 if(!jabber_nodeprep_validate(jid->node) ||
495 !jabber_domain_validate(jid->domain) ||
496 !jabber_resourceprep_validate(jid->resource)) {
497 jabber_id_free(jid);
498 return NULL;
501 return jid;
502 #endif /* USE_IDN */
505 void
506 jabber_id_free(JabberID *jid)
508 if(jid) {
509 g_free(jid->node);
510 g_free(jid->domain);
511 g_free(jid->resource);
512 g_free(jid);
517 gboolean
518 jabber_id_equal(const JabberID *jid1, const JabberID *jid2)
520 if (!jid1 && !jid2) {
521 /* Both are null therefore equal */
522 return TRUE;
525 if (!jid1 || !jid2) {
526 /* One is null, other is non-null, therefore not equal */
527 return FALSE;
530 return purple_strequal(jid1->node, jid2->node) &&
531 purple_strequal(jid1->domain, jid2->domain) &&
532 purple_strequal(jid1->resource, jid2->resource);
535 char *jabber_get_domain(const char *in)
537 JabberID *jid = jabber_id_new(in);
538 char *out;
540 if (!jid)
541 return NULL;
543 out = g_strdup(jid->domain);
544 jabber_id_free(jid);
546 return out;
549 char *jabber_get_resource(const char *in)
551 JabberID *jid = jabber_id_new(in);
552 char *out;
554 if(!jid)
555 return NULL;
557 out = g_strdup(jid->resource);
558 jabber_id_free(jid);
560 return out;
563 JabberID *
564 jabber_id_to_bare_jid(const JabberID *jid)
566 JabberID *result = g_new0(JabberID, 1);
568 result->node = g_strdup(jid->node);
569 result->domain = g_strdup(jid->domain);
571 return result;
574 char *
575 jabber_get_bare_jid(const char *in)
577 JabberID *jid = jabber_id_new(in);
578 char *out;
580 if (!jid)
581 return NULL;
582 out = jabber_id_get_bare_jid(jid);
583 jabber_id_free(jid);
585 return out;
588 char *
589 jabber_id_get_bare_jid(const JabberID *jid)
591 g_return_val_if_fail(jid != NULL, NULL);
593 return g_strconcat(jid->node ? jid->node : "",
594 jid->node ? "@" : "",
595 jid->domain,
596 NULL);
599 char *
600 jabber_id_get_full_jid(const JabberID *jid)
602 g_return_val_if_fail(jid != NULL, NULL);
604 return g_strconcat(jid->node ? jid->node : "",
605 jid->node ? "@" : "",
606 jid->domain,
607 jid->resource ? "/" : "",
608 jid->resource ? jid->resource : "",
609 NULL);
612 gboolean
613 jabber_jid_is_domain(const char *jid)
615 const char *c;
617 for (c = jid; *c; ++c) {
618 if (*c == '@' || *c == '/')
619 return FALSE;
622 return TRUE;
626 JabberID *
627 jabber_id_new(const char *str)
629 return jabber_id_new_internal(str, FALSE);
632 const char *jabber_normalize(const PurpleAccount *account, const char *in)
634 PurpleConnection *gc = NULL;
635 JabberStream *js = NULL;
636 static char buf[3072]; /* maximum legal length of a jabber jid */
637 JabberID *jid;
639 if (account)
640 gc = purple_account_get_connection(account);
641 if (gc)
642 js = purple_connection_get_protocol_data(gc);
644 jid = jabber_id_new_internal(in, TRUE);
645 if(!jid)
646 return NULL;
648 if(js && jid->node && jid->resource &&
649 jabber_chat_find(js, jid->node, jid->domain))
650 g_snprintf(buf, sizeof(buf), "%s@%s/%s", jid->node, jid->domain,
651 jid->resource);
652 else
653 g_snprintf(buf, sizeof(buf), "%s%s%s", jid->node ? jid->node : "",
654 jid->node ? "@" : "", jid->domain);
656 jabber_id_free(jid);
658 return buf;
661 gboolean
662 jabber_is_own_server(JabberStream *js, const char *str)
664 JabberID *jid;
665 gboolean equal;
667 if (str == NULL)
668 return FALSE;
670 g_return_val_if_fail(*str != '\0', FALSE);
672 jid = jabber_id_new(str);
673 if (!jid)
674 return FALSE;
676 equal = (jid->node == NULL &&
677 g_str_equal(jid->domain, js->user->domain) &&
678 jid->resource == NULL);
679 jabber_id_free(jid);
680 return equal;
683 gboolean
684 jabber_is_own_account(JabberStream *js, const char *str)
686 JabberID *jid;
687 gboolean equal;
689 if (str == NULL)
690 return TRUE;
692 g_return_val_if_fail(*str != '\0', FALSE);
694 jid = jabber_id_new(str);
695 if (!jid)
696 return FALSE;
698 equal = (purple_strequal(jid->node, js->user->node) &&
699 g_str_equal(jid->domain, js->user->domain) &&
700 (jid->resource == NULL ||
701 g_str_equal(jid->resource, js->user->resource)));
702 jabber_id_free(jid);
703 return equal;
706 static const struct {
707 const char *status_id; /* link to core */
708 const char *show; /* The show child's cdata in a presence stanza */
709 const char *readable; /* readable representation */
710 JabberBuddyState state;
711 } jabber_statuses[] = {
712 { "offline", NULL, N_("Offline"), JABBER_BUDDY_STATE_UNAVAILABLE },
713 { "available", NULL, N_("Available"), JABBER_BUDDY_STATE_ONLINE},
714 { "freeforchat", "chat", N_("Chatty"), JABBER_BUDDY_STATE_CHAT },
715 { "away", "away", N_("Away"), JABBER_BUDDY_STATE_AWAY },
716 { "extended_away", "xa", N_("Extended Away"), JABBER_BUDDY_STATE_XA },
717 { "dnd", "dnd", N_("Do Not Disturb"), JABBER_BUDDY_STATE_DND },
718 { "error", NULL, N_("Error"), JABBER_BUDDY_STATE_ERROR }
721 const char *
722 jabber_buddy_state_get_name(const JabberBuddyState state)
724 gsize i;
725 for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i)
726 if (jabber_statuses[i].state == state)
727 return _(jabber_statuses[i].readable);
729 return _("Unknown");
732 JabberBuddyState
733 jabber_buddy_status_id_get_state(const char *id)
735 gsize i;
736 if (!id)
737 return JABBER_BUDDY_STATE_UNKNOWN;
739 for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i)
740 if (g_str_equal(id, jabber_statuses[i].status_id))
741 return jabber_statuses[i].state;
743 return JABBER_BUDDY_STATE_UNKNOWN;
746 JabberBuddyState jabber_buddy_show_get_state(const char *id)
748 gsize i;
750 g_return_val_if_fail(id != NULL, JABBER_BUDDY_STATE_UNKNOWN);
752 for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i)
753 if (jabber_statuses[i].show && g_str_equal(id, jabber_statuses[i].show))
754 return jabber_statuses[i].state;
756 purple_debug_warning("jabber", "Invalid value of presence <show/> "
757 "attribute: %s\n", id);
758 return JABBER_BUDDY_STATE_UNKNOWN;
761 const char *
762 jabber_buddy_state_get_show(JabberBuddyState state)
764 gsize i;
765 for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i)
766 if (state == jabber_statuses[i].state)
767 return jabber_statuses[i].show;
769 return NULL;
772 const char *
773 jabber_buddy_state_get_status_id(JabberBuddyState state)
775 gsize i;
776 for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i)
777 if (state == jabber_statuses[i].state)
778 return jabber_statuses[i].status_id;
780 return NULL;
783 char *
784 jabber_calculate_data_hash(gconstpointer data, size_t len,
785 const gchar *hash_algo)
787 PurpleHash *hash = NULL;
788 static gchar digest[129]; /* 512 bits hex + \0 */
790 if (g_str_equal(hash_algo, "sha1"))
791 hash = purple_sha1_hash_new();
792 else if (g_str_equal(hash_algo, "sha256"))
793 hash = purple_sha256_hash_new();
794 else if (g_str_equal(hash_algo, "md5"))
795 hash = purple_md5_hash_new();
797 if (hash == NULL)
799 purple_debug_error("jabber", "Unexpected hashing algorithm %s requested\n", hash_algo);
800 g_return_val_if_reached(NULL);
803 /* Hash the data */
804 purple_hash_append(hash, data, len);
805 if (!purple_hash_digest_to_str(hash, digest, sizeof(digest)))
807 purple_debug_error("jabber", "Failed to get digest for %s cipher.\n",
808 hash_algo);
809 g_return_val_if_reached(NULL);
811 g_object_unref(G_OBJECT(hash));
813 return g_strdup(digest);