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
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
25 #include "conversation.h"
37 #include <stringprep.h>
38 static char idn_buffer
[1024];
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
;
53 jabber_idn_validate(const char *str
, const char *at
, const char *slash
,
56 const char *node
= NULL
;
57 const char *domain
= NULL
;
58 const char *resource
= NULL
;
65 /* Ensure no parts are > 1023 bytes */
72 domain_len
= slash
- (at
+ 1);
74 resource_len
= null
- (slash
+ 1);
76 domain_len
= null
- (at
+ 1);
82 domain_len
= slash
- str
;
84 resource_len
= null
- (slash
+ 1);
86 domain_len
= null
- str
;
90 if (node
&& node_len
> 1023)
92 if (domain_len
> 1023)
94 if (resource
&& resource_len
> 1023)
97 jid
= g_new0(JabberID
, 1);
100 strncpy(idn_buffer
, node
, node_len
);
101 idn_buffer
[node_len
] = '\0';
103 if (!jabber_nodeprep(idn_buffer
, sizeof(idn_buffer
))) {
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);
129 jid
->domain
= g_strndup(domain
, domain_len
);
132 if (stringprep_nameprep(idn_buffer
, sizeof(idn_buffer
)) != STRINGPREP_OK
) {
138 /* And now ToASCII */
139 if (idna_to_ascii_8z(idn_buffer
, &out
, IDNA_USE_STD3_ASCII_RULES
) != IDNA_SUCCESS
) {
145 /* This *MUST* be freed using 'free', not 'g_free' */
147 jid
->domain
= g_strdup(idn_buffer
);
151 strncpy(idn_buffer
, resource
, resource_len
);
152 idn_buffer
[resource_len
] = '\0';
154 if (!jabber_resourceprep(idn_buffer
, sizeof(idn_buffer
))) {
159 jid
->resource
= g_strdup(idn_buffer
);
168 gboolean
jabber_nodeprep_validate(const char *str
)
179 if(strlen(str
) > 1023)
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
));
190 gunichar ch
= g_utf8_get_char(c
);
191 if(ch
== '\"' || ch
== '&' || ch
== '\'' || ch
== '/' || ch
== ':' ||
192 ch
== '<' || ch
== '>' || ch
== '@' || !g_unichar_isgraph(ch
)) {
195 c
= g_utf8_next_char(c
);
202 gboolean
jabber_domain_validate(const char *str
)
217 /* Check if str is a valid IPv6 identifier */
218 gboolean valid
= FALSE
;
220 if (*(c
+ len
- 1) != ']')
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) = ']';
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')
238 || ch
== '-' )) || (ch
>= 0x80 && !g_unichar_isgraph(ch
)))
241 c
= g_utf8_next_char(c
);
247 gboolean
jabber_resourceprep_validate(const char *str
)
258 if(strlen(str
) > 1023)
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
));
269 gunichar ch
= g_utf8_get_char(c
);
270 if(!g_unichar_isgraph(ch
) && ch
!= ' ')
273 c
= g_utf8_next_char(c
);
280 char *jabber_saslprep(const char *in
)
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
));
297 out
= g_strdup(idn_buffer
);
298 memset(idn_buffer
, 0, sizeof(idn_buffer
));
301 /* TODO: Something better than disallowing all non-ASCII characters */
302 /* TODO: Is this even correct? */
305 c
= (const guchar
*)in
;
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 */
319 jabber_id_new_internal(const char *str
, gboolean allow_terminating_slash
)
321 const char *at
= NULL
;
322 const char *slash
= NULL
;
324 gboolean needs_validation
= FALSE
;
334 for (c
= str
; *c
!= '\0'; c
++)
340 /* Multiple @'s in the node/domain portion, not a valid JID! */
344 /* JIDs cannot start with @ */
348 /* JIDs cannot end with @ */
358 /* JIDs cannot start with / */
361 if (c
[1] == '\0' && !allow_terminating_slash
) {
362 /* JIDs cannot end with / */
370 /* characters allowed everywhere */
371 if ((*c
>= 'a' && *c
<= 'z')
372 || (*c
>= '0' && *c
<= '9')
373 || (*c
>= 'A' && *c
<= 'Z')
374 || *c
== '.' || *c
== '-')
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
;
388 if (!needs_validation
) {
389 /* JID is made of only ASCII characters--just lowercase and return */
390 jid
= g_new0(JabberID
, 1);
393 jid
->node
= g_ascii_strdown(str
, at
- str
);
395 jid
->domain
= g_ascii_strdown(at
+ 1, slash
- (at
+ 1));
397 jid
->resource
= g_strdup(slash
+ 1);
399 jid
->domain
= g_ascii_strdown(at
+ 1, -1);
403 jid
->domain
= g_ascii_strdown(str
, slash
- str
);
405 jid
->resource
= g_strdup(slash
+ 1);
407 jid
->domain
= g_ascii_strdown(str
, -1);
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
))
423 return jabber_idn_validate(str
, at
, slash
, c
/* points to the null */);
426 jid
= g_new0(JabberID
, 1);
430 node
= g_utf8_casefold(str
, at
-str
);
432 domain
= g_utf8_casefold(at
+1, slash
-(at
+1));
434 jid
->resource
= g_utf8_normalize(slash
+1, -1, G_NORMALIZE_NFKC
);
436 domain
= g_utf8_casefold(at
+1, -1);
440 domain
= g_utf8_casefold(str
, slash
-str
);
442 jid
->resource
= g_utf8_normalize(slash
+1, -1, G_NORMALIZE_NFKC
);
444 domain
= g_utf8_casefold(str
, -1);
449 jid
->node
= g_utf8_normalize(node
, -1, G_NORMALIZE_NFKC
);
454 jid
->domain
= g_utf8_normalize(domain
, -1, G_NORMALIZE_NFKC
);
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
)) {
471 jabber_id_free(JabberID
*jid
)
476 g_free(jid
->resource
);
483 jabber_id_equal(const JabberID
*jid1
, const JabberID
*jid2
)
485 if (!jid1
&& !jid2
) {
486 /* Both are null therefore equal */
490 if (!jid1
|| !jid2
) {
491 /* One is null, other is non-null, therefore not equal */
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
);
508 out
= g_strdup(jid
->domain
);
514 char *jabber_get_resource(const char *in
)
516 JabberID
*jid
= jabber_id_new(in
);
522 out
= g_strdup(jid
->resource
);
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
);
540 jabber_get_bare_jid(const char *in
)
542 JabberID
*jid
= jabber_id_new(in
);
547 out
= jabber_id_get_bare_jid(jid
);
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
? "@" : "",
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
? "@" : "",
572 jid
->resource
? "/" : "",
573 jid
->resource
? jid
->resource
: "",
578 jabber_jid_is_domain(const char *jid
)
582 for (c
= jid
; *c
; ++c
) {
583 if (*c
== '@' || *c
== '/')
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 */
605 gc
= purple_account_get_connection((PurpleAccount
*)account
);
608 js
= purple_connection_get_protocol_data(gc
);
610 jid
= jabber_id_new_internal(in
, TRUE
);
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
,
619 g_snprintf(buf
, sizeof(buf
), "%s%s%s", jid
->node
? jid
->node
: "",
620 jid
->node
? "@" : "", jid
->domain
);
628 jabber_is_own_server(JabberStream
*js
, const char *str
)
636 g_return_val_if_fail(*str
!= '\0', FALSE
);
638 jid
= jabber_id_new(str
);
642 equal
= (jid
->node
== NULL
&&
643 purple_strequal(jid
->domain
, js
->user
->domain
) &&
644 jid
->resource
== NULL
);
650 jabber_is_own_account(JabberStream
*js
, const char *str
)
658 g_return_val_if_fail(*str
!= '\0', FALSE
);
660 jid
= jabber_id_new(str
);
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
)));
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
}
688 jabber_buddy_state_get_name(const JabberBuddyState state
)
691 for (i
= 0; i
< G_N_ELEMENTS(jabber_statuses
); ++i
)
692 if (jabber_statuses
[i
].state
== state
)
693 return _(jabber_statuses
[i
].readable
);
699 jabber_buddy_status_id_get_state(const char *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
)
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
;
728 jabber_buddy_state_get_show(JabberBuddyState state
)
731 for (i
= 0; i
< G_N_ELEMENTS(jabber_statuses
); ++i
)
732 if (state
== jabber_statuses
[i
].state
)
733 return jabber_statuses
[i
].show
;
739 jabber_buddy_state_get_status_id(JabberBuddyState state
)
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
;