rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / jabber / jutil.c
blobe54ab55bc6e879c6ba8d5da8580630103319857c
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 #ifdef USE_IDN
36 #include <idna.h>
37 #include <stringprep.h>
38 static char idn_buffer[1024];
39 #endif
41 #ifdef USE_IDN
42 static gboolean jabber_nodeprep(char *str, size_t buflen)
44 return stringprep_xmpp_nodeprep(str, buflen) == STRINGPREP_OK;
47 static gboolean jabber_resourceprep(char *str, size_t buflen)
49 return stringprep_xmpp_resourceprep(str, buflen) == STRINGPREP_OK;
52 static JabberID*
53 jabber_idn_validate(const char *str, const char *at, const char *slash,
54 const char *null)
56 const char *node = NULL;
57 const char *domain = NULL;
58 const char *resource = NULL;
59 int node_len = 0;
60 int domain_len = 0;
61 int resource_len = 0;
62 char *out;
63 JabberID *jid;
65 /* Ensure no parts are > 1023 bytes */
66 if (at) {
67 node = str;
68 node_len = at - str;
70 domain = at + 1;
71 if (slash) {
72 domain_len = slash - (at + 1);
73 resource = slash + 1;
74 resource_len = null - (slash + 1);
75 } else {
76 domain_len = null - (at + 1);
78 } else {
79 domain = str;
81 if (slash) {
82 domain_len = slash - str;
83 resource = slash + 1;
84 resource_len = null - (slash + 1);
85 } else {
86 domain_len = null - str;
90 if (node && node_len > 1023)
91 return NULL;
92 if (domain_len > 1023)
93 return NULL;
94 if (resource && resource_len > 1023)
95 return NULL;
97 jid = g_new0(JabberID, 1);
99 if (node) {
100 strncpy(idn_buffer, node, node_len);
101 idn_buffer[node_len] = '\0';
103 if (!jabber_nodeprep(idn_buffer, sizeof(idn_buffer))) {
104 jabber_id_free(jid);
105 jid = NULL;
106 goto out;
109 jid->node = g_strdup(idn_buffer);
112 /* domain *must* be here */
113 strncpy(idn_buffer, domain, domain_len);
114 idn_buffer[domain_len] = '\0';
115 if (domain[0] == '[') { /* IPv6 address */
116 gboolean valid = FALSE;
118 if (idn_buffer[domain_len - 1] == ']') {
119 idn_buffer[domain_len - 1] = '\0';
120 valid = purple_ipv6_address_is_valid(idn_buffer + 1);
123 if (!valid) {
124 jabber_id_free(jid);
125 jid = NULL;
126 goto out;
129 jid->domain = g_strndup(domain, domain_len);
130 } else {
131 /* Apply nameprep */
132 if (stringprep_nameprep(idn_buffer, sizeof(idn_buffer)) != STRINGPREP_OK) {
133 jabber_id_free(jid);
134 jid = NULL;
135 goto out;
138 /* And now ToASCII */
139 if (idna_to_ascii_8z(idn_buffer, &out, IDNA_USE_STD3_ASCII_RULES) != IDNA_SUCCESS) {
140 jabber_id_free(jid);
141 jid = NULL;
142 goto out;
145 /* This *MUST* be freed using 'free', not 'g_free' */
146 free(out);
147 jid->domain = g_strdup(idn_buffer);
150 if (resource) {
151 strncpy(idn_buffer, resource, resource_len);
152 idn_buffer[resource_len] = '\0';
154 if (!jabber_resourceprep(idn_buffer, sizeof(idn_buffer))) {
155 jabber_id_free(jid);
156 jid = NULL;
157 goto out;
158 } else
159 jid->resource = g_strdup(idn_buffer);
162 out:
163 return jid;
166 #endif /* USE_IDN */
168 gboolean jabber_nodeprep_validate(const char *str)
170 #ifdef USE_IDN
171 gboolean result;
172 #else
173 const char *c;
174 #endif
176 if(!str)
177 return TRUE;
179 if(strlen(str) > 1023)
180 return FALSE;
182 #ifdef USE_IDN
183 strncpy(idn_buffer, str, sizeof(idn_buffer) - 1);
184 idn_buffer[sizeof(idn_buffer) - 1] = '\0';
185 result = jabber_nodeprep(idn_buffer, sizeof(idn_buffer));
186 return result;
187 #else /* USE_IDN */
188 c = str;
189 while(c && *c) {
190 gunichar ch = g_utf8_get_char(c);
191 if(ch == '\"' || ch == '&' || ch == '\'' || ch == '/' || ch == ':' ||
192 ch == '<' || ch == '>' || ch == '@' || !g_unichar_isgraph(ch)) {
193 return FALSE;
195 c = g_utf8_next_char(c);
198 return TRUE;
199 #endif /* USE_IDN */
202 gboolean jabber_domain_validate(const char *str)
204 const char *c;
205 size_t len;
207 if(!str)
208 return TRUE;
210 len = strlen(str);
211 if (len > 1023)
212 return FALSE;
214 c = str;
216 if (*c == '[') {
217 /* Check if str is a valid IPv6 identifier */
218 gboolean valid = FALSE;
220 if (*(c + len - 1) != ']')
221 return FALSE;
223 /* Ugly, but in-place */
224 *(gchar *)(c + len - 1) = '\0';
225 valid = purple_ipv6_address_is_valid(c + 1);
226 *(gchar *)(c + len - 1) = ']';
228 return valid;
231 while(c && *c) {
232 gunichar ch = g_utf8_get_char(c);
233 /* The list of characters allowed in domain names is pretty small */
234 if ((ch <= 0x7F && !( (ch >= 'a' && ch <= 'z')
235 || (ch >= '0' && ch <= '9')
236 || (ch >= 'A' && ch <= 'Z')
237 || ch == '.'
238 || ch == '-' )) || (ch >= 0x80 && !g_unichar_isgraph(ch)))
239 return FALSE;
241 c = g_utf8_next_char(c);
244 return TRUE;
247 gboolean jabber_resourceprep_validate(const char *str)
249 #ifdef USE_IDN
250 gboolean result;
251 #else
252 const char *c;
253 #endif
255 if(!str)
256 return TRUE;
258 if(strlen(str) > 1023)
259 return FALSE;
261 #ifdef USE_IDN
262 strncpy(idn_buffer, str, sizeof(idn_buffer) - 1);
263 idn_buffer[sizeof(idn_buffer) - 1] = '\0';
264 result = jabber_resourceprep(idn_buffer, sizeof(idn_buffer));
265 return result;
266 #else /* USE_IDN */
267 c = str;
268 while(c && *c) {
269 gunichar ch = g_utf8_get_char(c);
270 if(!g_unichar_isgraph(ch) && ch != ' ')
271 return FALSE;
273 c = g_utf8_next_char(c);
276 return TRUE;
277 #endif /* USE_IDN */
280 char *jabber_saslprep(const char *in)
282 #ifdef USE_IDN
283 char *out;
285 g_return_val_if_fail(in != NULL, NULL);
286 g_return_val_if_fail(strlen(in) <= sizeof(idn_buffer) - 1, NULL);
288 strncpy(idn_buffer, in, sizeof(idn_buffer) - 1);
289 idn_buffer[sizeof(idn_buffer) - 1] = '\0';
291 if (STRINGPREP_OK != stringprep(idn_buffer, sizeof(idn_buffer), 0,
292 stringprep_saslprep)) {
293 memset(idn_buffer, 0, sizeof(idn_buffer));
294 return NULL;
297 out = g_strdup(idn_buffer);
298 memset(idn_buffer, 0, sizeof(idn_buffer));
299 return out;
300 #else /* USE_IDN */
301 /* TODO: Something better than disallowing all non-ASCII characters */
302 /* TODO: Is this even correct? */
303 const guchar *c;
305 c = (const guchar *)in;
306 for ( ; *c; ++c) {
307 if (*c > 0x7f || /* Non-ASCII characters */
308 *c == 0x7f || /* ASCII Delete character */
309 (*c < 0x20 && *c != '\t' && *c != '\n' && *c != '\r'))
310 /* ASCII control characters */
311 return NULL;
314 return g_strdup(in);
315 #endif /* USE_IDN */
318 static JabberID*
319 jabber_id_new_internal(const char *str, gboolean allow_terminating_slash)
321 const char *at = NULL;
322 const char *slash = NULL;
323 const char *c;
324 gboolean needs_validation = FALSE;
325 #ifndef USE_IDN
326 char *node = NULL;
327 char *domain;
328 #endif
329 JabberID *jid;
331 if (!str)
332 return NULL;
334 for (c = str; *c != '\0'; c++)
336 switch (*c) {
337 case '@':
338 if (!slash) {
339 if (at) {
340 /* Multiple @'s in the node/domain portion, not a valid JID! */
341 return NULL;
343 if (c == str) {
344 /* JIDs cannot start with @ */
345 return NULL;
347 if (c[1] == '\0') {
348 /* JIDs cannot end with @ */
349 return NULL;
351 at = c;
353 break;
355 case '/':
356 if (!slash) {
357 if (c == str) {
358 /* JIDs cannot start with / */
359 return NULL;
361 if (c[1] == '\0' && !allow_terminating_slash) {
362 /* JIDs cannot end with / */
363 return NULL;
365 slash = c;
367 break;
369 default:
370 /* characters allowed everywhere */
371 if ((*c >= 'a' && *c <= 'z')
372 || (*c >= '0' && *c <= '9')
373 || (*c >= 'A' && *c <= 'Z')
374 || *c == '.' || *c == '-')
375 /* We're good */
376 break;
379 * Hmm, this character is a bit more exotic. Better fall
380 * back to using the more expensive UTF-8 compliant
381 * stringprep functions.
383 needs_validation = TRUE;
384 break;
388 if (!needs_validation) {
389 /* JID is made of only ASCII characters--just lowercase and return */
390 jid = g_new0(JabberID, 1);
392 if (at) {
393 jid->node = g_ascii_strdown(str, at - str);
394 if (slash) {
395 jid->domain = g_ascii_strdown(at + 1, slash - (at + 1));
396 if (*(slash + 1))
397 jid->resource = g_strdup(slash + 1);
398 } else {
399 jid->domain = g_ascii_strdown(at + 1, -1);
401 } else {
402 if (slash) {
403 jid->domain = g_ascii_strdown(str, slash - str);
404 if (*(slash + 1))
405 jid->resource = g_strdup(slash + 1);
406 } else {
407 jid->domain = g_ascii_strdown(str, -1);
410 return jid;
414 * If we get here, there are some non-ASCII chars in the string, so
415 * we'll need to validate it, normalize, and finally do a full jabber
416 * nodeprep on the jid.
419 if (!g_utf8_validate(str, -1, NULL))
420 return NULL;
422 #ifdef USE_IDN
423 return jabber_idn_validate(str, at, slash, c /* points to the null */);
424 #else /* USE_IDN */
426 jid = g_new0(JabberID, 1);
428 /* normalization */
429 if(at) {
430 node = g_utf8_casefold(str, at-str);
431 if(slash) {
432 domain = g_utf8_casefold(at+1, slash-(at+1));
433 if (*(slash + 1))
434 jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
435 } else {
436 domain = g_utf8_casefold(at+1, -1);
438 } else {
439 if(slash) {
440 domain = g_utf8_casefold(str, slash-str);
441 if (*(slash + 1))
442 jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
443 } else {
444 domain = g_utf8_casefold(str, -1);
448 if (node) {
449 jid->node = g_utf8_normalize(node, -1, G_NORMALIZE_NFKC);
450 g_free(node);
453 if (domain) {
454 jid->domain = g_utf8_normalize(domain, -1, G_NORMALIZE_NFKC);
455 g_free(domain);
458 /* and finally the jabber nodeprep */
459 if(!jabber_nodeprep_validate(jid->node) ||
460 !jabber_domain_validate(jid->domain) ||
461 !jabber_resourceprep_validate(jid->resource)) {
462 jabber_id_free(jid);
463 return NULL;
466 return jid;
467 #endif /* USE_IDN */
470 void
471 jabber_id_free(JabberID *jid)
473 if(jid) {
474 g_free(jid->node);
475 g_free(jid->domain);
476 g_free(jid->resource);
477 g_free(jid);
482 gboolean
483 jabber_id_equal(const JabberID *jid1, const JabberID *jid2)
485 if (!jid1 && !jid2) {
486 /* Both are null therefore equal */
487 return TRUE;
490 if (!jid1 || !jid2) {
491 /* One is null, other is non-null, therefore not equal */
492 return FALSE;
495 return purple_strequal(jid1->node, jid2->node) &&
496 purple_strequal(jid1->domain, jid2->domain) &&
497 purple_strequal(jid1->resource, jid2->resource);
500 char *jabber_get_domain(const char *in)
502 JabberID *jid = jabber_id_new(in);
503 char *out;
505 if (!jid)
506 return NULL;
508 out = g_strdup(jid->domain);
509 jabber_id_free(jid);
511 return out;
514 char *jabber_get_resource(const char *in)
516 JabberID *jid = jabber_id_new(in);
517 char *out;
519 if(!jid)
520 return NULL;
522 out = g_strdup(jid->resource);
523 jabber_id_free(jid);
525 return out;
528 JabberID *
529 jabber_id_to_bare_jid(const JabberID *jid)
531 JabberID *result = g_new0(JabberID, 1);
533 result->node = g_strdup(jid->node);
534 result->domain = g_strdup(jid->domain);
536 return result;
539 char *
540 jabber_get_bare_jid(const char *in)
542 JabberID *jid = jabber_id_new(in);
543 char *out;
545 if (!jid)
546 return NULL;
547 out = jabber_id_get_bare_jid(jid);
548 jabber_id_free(jid);
550 return out;
553 char *
554 jabber_id_get_bare_jid(const JabberID *jid)
556 g_return_val_if_fail(jid != NULL, NULL);
558 return g_strconcat(jid->node ? jid->node : "",
559 jid->node ? "@" : "",
560 jid->domain,
561 NULL);
564 char *
565 jabber_id_get_full_jid(const JabberID *jid)
567 g_return_val_if_fail(jid != NULL, NULL);
569 return g_strconcat(jid->node ? jid->node : "",
570 jid->node ? "@" : "",
571 jid->domain,
572 jid->resource ? "/" : "",
573 jid->resource ? jid->resource : "",
574 NULL);
577 gboolean
578 jabber_jid_is_domain(const char *jid)
580 const char *c;
582 for (c = jid; *c; ++c) {
583 if (*c == '@' || *c == '/')
584 return FALSE;
587 return TRUE;
591 JabberID *
592 jabber_id_new(const char *str)
594 return jabber_id_new_internal(str, FALSE);
597 const char *jabber_normalize(const PurpleAccount *account, const char *in)
599 PurpleConnection *gc = NULL;
600 JabberStream *js = NULL;
601 static char buf[3072]; /* maximum legal length of a jabber jid */
602 JabberID *jid;
604 if (account) {
605 gc = purple_account_get_connection((PurpleAccount *)account);
607 if (gc)
608 js = purple_connection_get_protocol_data(gc);
610 jid = jabber_id_new_internal(in, TRUE);
611 if(!jid)
612 return NULL;
614 if(js && jid->node && jid->resource &&
615 jabber_chat_find(js, jid->node, jid->domain))
616 g_snprintf(buf, sizeof(buf), "%s@%s/%s", jid->node, jid->domain,
617 jid->resource);
618 else
619 g_snprintf(buf, sizeof(buf), "%s%s%s", jid->node ? jid->node : "",
620 jid->node ? "@" : "", jid->domain);
622 jabber_id_free(jid);
624 return buf;
627 gboolean
628 jabber_is_own_server(JabberStream *js, const char *str)
630 JabberID *jid;
631 gboolean equal;
633 if (str == NULL)
634 return FALSE;
636 g_return_val_if_fail(*str != '\0', FALSE);
638 jid = jabber_id_new(str);
639 if (!jid)
640 return FALSE;
642 equal = (jid->node == NULL &&
643 purple_strequal(jid->domain, js->user->domain) &&
644 jid->resource == NULL);
645 jabber_id_free(jid);
646 return equal;
649 gboolean
650 jabber_is_own_account(JabberStream *js, const char *str)
652 JabberID *jid;
653 gboolean equal;
655 if (str == NULL)
656 return TRUE;
658 g_return_val_if_fail(*str != '\0', FALSE);
660 jid = jabber_id_new(str);
661 if (!jid)
662 return FALSE;
664 equal = (purple_strequal(jid->node, js->user->node) &&
665 purple_strequal(jid->domain, js->user->domain) &&
666 (jid->resource == NULL ||
667 purple_strequal(jid->resource, js->user->resource)));
668 jabber_id_free(jid);
669 return equal;
672 static const struct {
673 const char *status_id; /* link to core */
674 const char *show; /* The show child's cdata in a presence stanza */
675 const char *readable; /* readable representation */
676 JabberBuddyState state;
677 } jabber_statuses[] = {
678 { "offline", NULL, N_("Offline"), JABBER_BUDDY_STATE_UNAVAILABLE },
679 { "available", NULL, N_("Available"), JABBER_BUDDY_STATE_ONLINE},
680 { "freeforchat", "chat", N_("Chatty"), JABBER_BUDDY_STATE_CHAT },
681 { "away", "away", N_("Away"), JABBER_BUDDY_STATE_AWAY },
682 { "extended_away", "xa", N_("Extended Away"), JABBER_BUDDY_STATE_XA },
683 { "dnd", "dnd", N_("Do Not Disturb"), JABBER_BUDDY_STATE_DND },
684 { "error", NULL, N_("Error"), JABBER_BUDDY_STATE_ERROR }
687 const char *
688 jabber_buddy_state_get_name(const JabberBuddyState state)
690 gsize i;
691 for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i)
692 if (jabber_statuses[i].state == state)
693 return _(jabber_statuses[i].readable);
695 return _("Unknown");
698 JabberBuddyState
699 jabber_buddy_status_id_get_state(const char *id)
701 gsize i;
702 if (!id)
703 return JABBER_BUDDY_STATE_UNKNOWN;
705 for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i)
706 if (purple_strequal(id, jabber_statuses[i].status_id))
707 return jabber_statuses[i].state;
709 return JABBER_BUDDY_STATE_UNKNOWN;
712 JabberBuddyState jabber_buddy_show_get_state(const char *id)
714 gsize i;
716 g_return_val_if_fail(id != NULL, JABBER_BUDDY_STATE_UNKNOWN);
718 for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i)
719 if (jabber_statuses[i].show && purple_strequal(id, jabber_statuses[i].show))
720 return jabber_statuses[i].state;
722 purple_debug_warning("jabber", "Invalid value of presence <show/> "
723 "attribute: %s\n", id);
724 return JABBER_BUDDY_STATE_UNKNOWN;
727 const char *
728 jabber_buddy_state_get_show(JabberBuddyState state)
730 gsize i;
731 for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i)
732 if (state == jabber_statuses[i].state)
733 return jabber_statuses[i].show;
735 return NULL;
738 const char *
739 jabber_buddy_state_get_status_id(JabberBuddyState state)
741 gsize i;
742 for (i = 0; i < G_N_ELEMENTS(jabber_statuses); ++i)
743 if (state == jabber_statuses[i].state)
744 return jabber_statuses[i].status_id;
746 return NULL;