diff mcabber/libjabber/jid.c @ 417:c3ae9251c197

Sync libjabber with upstream Sync with jabberd-1.4.4.
author Mikael Berthe <mikael@lilotux.net>
date Thu, 01 Sep 2005 23:29:21 +0200
parents bf3d6e241714
children
line wrap: on
line diff
--- a/mcabber/libjabber/jid.c	Thu Sep 01 21:18:19 2005 +0200
+++ b/mcabber/libjabber/jid.c	Thu Sep 01 23:29:21 2005 +0200
@@ -17,30 +17,474 @@
  *  Copyright (C) 1998-1999 The Jabber Team http://jabber.org/
  */
 
+/**
+ * @file jid.c
+ * @brief representation and normalization of JabberIDs
+ */
+
 #include "jabber.h"
 
-jid jid_safe(jid id)
-{
+#ifdef LIBIDN
+
+#  include <stringprep.h>
+
+
+/**
+ * @brief datastructure to build the stringprep caches
+ */
+typedef struct _jid_prep_entry_st {
+    char *preped;	/**< the result of the preparation, NULL if unchanged */
+    time_t last_used;	/**< when this result has last been successfully used */
+    unsigned int used_count; /**< how often this result has been successfully used */
+    int size;		/**< the min buffer size needed to hold the result (strlen+1) */
+} *_jid_prep_entry_t;
+
+/**
+ * @brief string preparation cache
+ */
+typedef struct _jid_prep_cache_st {
+    xht hashtable;	/**< the hash table containing the preped strings */
+    pth_mutex_t mutex;	/**< mutex controling the access to the hashtable */
+    const Stringprep_profile *profile;
+    			/**< the stringprep profile used for this cache */
+} *_jid_prep_cache_t;
+
+/**
+ * stringprep cache containging already preped nodes
+ *
+ * we are using global caches here for two reasons:
+ * - I do not see why different instances would want
+ *   to have different caches as we are always doing
+ *   the same
+ * - For per instance caches I would have to modify the
+ *   interface of the jid_*() functions which would break
+ *   compatibility with transports
+ */
+_jid_prep_cache_t _jid_prep_cache_node = NULL;
+
+/**
+ * stringprep cache containing already preped domains
+ */
+_jid_prep_cache_t _jid_prep_cache_domain = NULL;
+
+/**
+ * stringprep cache containing already preped resources
+ */
+_jid_prep_cache_t _jid_prep_cache_resource = NULL;
+
+/**
+ * walker for cleaning up stringprep caches
+ *
+ * @param h the hash we are walking through
+ * @param key the key of this item
+ * @param val the value of this item
+ * @param arg delete entries older as this unix timestamp
+ */
+void _jid_clean_walker(xht h, const char *key, void *val, void *arg) {
+    time_t *keep_newer_as = (time_t*)arg;
+    _jid_prep_entry_t entry = (_jid_prep_entry_t)val;
+
+    if (entry == NULL)
+	return;
+
+    if (entry->last_used <= *keep_newer_as) {
+	xhash_zap(h, key);
+	if (entry->preped != NULL)
+	    free(entry->preped);
+	free(entry);
+
+	/* sorry, I have to cast the const away */
+	/* any idea how I could delete the key else? */
+	if (key != NULL)
+	    free((void*)key);
+    }
+}
+
+/**
+ * walk through a single stringprep cache and check which entries have expired
+ */
+void _jid_clean_single_cache(_jid_prep_cache_t cache, time_t keep_newer_as) {
+    /* acquire the lock on the cache */
+    pth_mutex_acquire(&(cache->mutex), FALSE, NULL);
+
+    /* walk over all entries */
+    xhash_walk(cache->hashtable, _jid_clean_walker, (void*)&keep_newer_as);
+
+    /* we're done, release the lock on the cache */
+    pth_mutex_release(&(cache->mutex));
+}
+
+/**
+ * walk through the stringprep caches and check which entries have expired
+ */
+void jid_clean_cache() {
+    /* XXX make this configurable? */
+    time_t keep_newer_as = time(NULL) - 900;
+
+    /* cleanup the nodeprep cache */
+    _jid_clean_single_cache(_jid_prep_cache_node, keep_newer_as);
+    
+    /* cleanup the domain preparation cache */
+    _jid_clean_single_cache(_jid_prep_cache_domain, keep_newer_as);
+    
+    /* cleanup the resourceprep cache */
+    _jid_clean_single_cache(_jid_prep_cache_resource, keep_newer_as);
+}
+
+/**
+ * caching wrapper around a stringprep function
+ *
+ * @param in_out_buffer buffer containing what has to be stringpreped and that gets the result
+ * @param max_len size of the buffer
+ * @param cache the used cache, defining also the used stringprep profile
+ * @return the return code of the stringprep call
+ */
+int _jid_cached_stringprep(char *in_out_buffer, int max_len, _jid_prep_cache_t cache) {
+    _jid_prep_entry_t preped;
+    int result = STRINGPREP_OK;
+
+    /* check that the cache already exists
+     * we can not do anything as we don't know which profile has to be used */
+    if (cache == NULL) {
+	return STRINGPREP_UNKNOWN_PROFILE;
+    }
+
+    /* is there something that has to be stringpreped? */
+    if (in_out_buffer == NULL) {
+	return STRINGPREP_OK;
+    }
+
+    /* acquire the lock on the cache */
+    pth_mutex_acquire(&(cache->mutex), FALSE, NULL);
+
+    /* check if the requested preparation has already been done */
+    preped = (_jid_prep_entry_t)xhash_get(cache->hashtable, in_out_buffer);
+    if (preped != NULL) {
+	/* we already prepared this argument */
+	if (preped->size <= max_len) {
+	    /* we can use the result */
+
+	    /* update the statistic */
+	    preped->used_count++;
+	    preped->last_used = time(NULL);
+
+	    /* do we need to copy the result? */
+	    if (preped->preped != NULL) {
+		/* copy the result */
+		strcpy(in_out_buffer, preped->preped);
+	    }
+
+	    result = STRINGPREP_OK;
+	} else {
+	    /* we need a bigger buffer */
+	    result = STRINGPREP_TOO_SMALL_BUFFER;
+	}
+	
+	/* we're done, release the lock on the cache */
+	pth_mutex_release(&(cache->mutex));
+    } else {
+	char *original;
+
+	/* stringprep needs time, release the lock on the cache for the meantime */
+	pth_mutex_release(&(cache->mutex));
+
+	/* we have to keep the key */
+	original = strdup(in_out_buffer);
+	
+	/* try to prepare the string */
+	result = stringprep(in_out_buffer, max_len, STRINGPREP_NO_UNASSIGNED, cache->profile);
+
+	/* did we manage to prepare the string? */
+	if (result == STRINGPREP_OK && original != NULL) {
+	    /* generate an entry for the cache */
+	    preped = (_jid_prep_entry_t)malloc(sizeof(struct _jid_prep_entry_st));
+	    if (preped != NULL) {
+		/* has there been modified something? */
+		if (j_strcmp(in_out_buffer, original) == 0) {
+		    /* no, we don't need to store a copy of the original string */
+		    preped->preped = NULL;
+		} else {
+		    /* yes, store the stringpreped string */
+		    preped->preped = strdup(in_out_buffer);
+		}
+		preped->last_used = time(NULL);
+		preped->used_count = 1;
+		preped->size = strlen(in_out_buffer)+1;
+
+		/* acquire the lock on the cache again */
+		pth_mutex_acquire(&(cache->mutex), FALSE, NULL);
+
+		/* store the entry in the cache */
+		xhash_put(cache->hashtable, original, preped);
+
+		/* we're done, release the lock on the cache */
+		pth_mutex_release(&(cache->mutex));
+	    } else {
+		/* we don't need the copy of the key, if there is no memory to store it */
+		free(original);
+	    }
+	} else {
+	    /* we don't need the copy of the original value */
+	    if (original != NULL)
+		free(original);
+	}
+    }
+
+    return result;
+}
+
+/**
+ * free a single stringprep cache
+ *
+ * @param cache the cache to free
+ */
+void _jid_stop_single_cache(_jid_prep_cache_t *cache) {
+    if (*cache == NULL)
+	return;
+
+    _jid_clean_single_cache(*cache, time(NULL));
+    
+    pth_mutex_acquire(&((*cache)->mutex), FALSE, NULL);
+    xhash_free((*cache)->hashtable);
+
+    free(*cache);
+
+    *cache = NULL;
+}
+
+/**
+ * init a single stringprep cache
+ *
+ * @param cache the cache to init
+ * @param prime the prime used to init the hashtable
+ * @param profile profile used to prepare the strings
+ */
+void _jid_init_single_cache(_jid_prep_cache_t *cache, int prime, const Stringprep_profile *profile) {
+    /* do not init a cache twice */
+    if (*cache == NULL) {
+	*cache = (_jid_prep_cache_t)malloc(sizeof(struct _jid_prep_cache_st));
+	pth_mutex_init(&((*cache)->mutex));
+	(*cache)->hashtable = xhash_new(prime);
+	(*cache)->profile = profile;
+    }
+}
+
+/**
+ * free the stringprep caches
+ */
+void jid_stop_caching() {
+    _jid_stop_single_cache(&_jid_prep_cache_node);
+    _jid_stop_single_cache(&_jid_prep_cache_domain);
+    _jid_stop_single_cache(&_jid_prep_cache_resource);
+}
+
+/**
+ * init the stringprep caches
+ * (do not call this twice at the same time, we do not have the mutexes yet)
+ */
+void jid_init_cache() {
+    /* init the nodeprep cache */
+    _jid_init_single_cache(&_jid_prep_cache_node, 2003, stringprep_xmpp_nodeprep);
+
+    /* init the nameprep cache (domains) */
+    _jid_init_single_cache(&_jid_prep_cache_domain, 2003, stringprep_nameprep);
+
+    /* init the resourceprep cache */
+    _jid_init_single_cache(&_jid_prep_cache_resource, 2003, stringprep_xmpp_resourceprep);
+}
+
+/**
+ * nameprep the domain identifier in a JID and check if it is valid
+ *
+ * @param jid data structure holding the JID
+ * @return 0 if JID is valid, non zero otherwise
+ */
+int _jid_safe_domain(jid id) {
+    int result=0;
+
+    /* there must be a domain identifier */
+    if (j_strlen(id->server) == 0)
+	return 1;
+
+    /* nameprep the domain identifier */
+    result = _jid_cached_stringprep(id->server, strlen(id->server)+1, _jid_prep_cache_domain);
+    if (result == STRINGPREP_TOO_SMALL_BUFFER) {
+	/* nameprep wants to expand the string, e.g. conversion from &szlig; to ss */
+	size_t biggerbuffersize = 1024;
+	char *biggerbuffer = pmalloc(id->p, biggerbuffersize);
+	if (biggerbuffer == NULL)
+	    return 1;
+	strcpy(biggerbuffer, id->server);
+	result = _jid_cached_stringprep(biggerbuffer, biggerbuffersize, _jid_prep_cache_domain);
+	id->server = biggerbuffer;
+    }
+    if (result != STRINGPREP_OK)
+	return 1;
+
+    /* the namepreped domain must not be longer than 1023 bytes */
+    if (j_strlen(id->server) > 1023)
+	return 1;
+
+    /* if nothing failed, the domain is valid */
+    return 0;
+}
+
+/**
+ * nodeprep the node identifier in a JID and check if it is valid
+ *
+ * @param jid data structure holding the JID
+ * @return 0 if JID is valid, non zero otherwise
+ */
+int _jid_safe_node(jid id) {
+    int result=0;
+
+    /* it is valid to have no node identifier in the JID */
+    if (id->user == NULL)
+	return 0;
+
+    /* nodeprep */
+    result = _jid_cached_stringprep(id->user, strlen(id->user)+1, _jid_prep_cache_node);
+    if (result == STRINGPREP_TOO_SMALL_BUFFER) {
+	/* nodeprep wants to expand the string, e.g. conversion from &szlig; to ss */
+	size_t biggerbuffersize = 1024;
+	char *biggerbuffer = pmalloc(id->p, biggerbuffersize);
+	if (biggerbuffer == NULL)
+	    return 1;
+	strcpy(biggerbuffer, id->user);
+	result = _jid_cached_stringprep(biggerbuffer, biggerbuffersize, _jid_prep_cache_node);
+	id->user = biggerbuffer;
+    }
+    if (result != STRINGPREP_OK)
+	return 1;
+
+    /* the nodepreped node must not be longer than 1023 bytes */
+    if (j_strlen(id->user) > 1023)
+	return 1;
+
+    /* if nothing failed, the node is valid */
+    return 0;
+}
+
+/**
+ * resourceprep the resource identifier in a JID and check if it is valid
+ *
+ * @param jid data structure holding the JID
+ * @return 0 if JID is valid, non zero otherwise
+ */
+int _jid_safe_resource(jid id) {
+    int result=0;
+
+    /* it is valid to have no resource identifier in the JID */
+    if (id->resource == NULL)
+	return 0;
+
+    /* resource prep the resource identifier */
+    result = _jid_cached_stringprep(id->resource, strlen(id->resource)+1, _jid_prep_cache_resource);
+    if (result == STRINGPREP_TOO_SMALL_BUFFER) {
+	/* resourceprep wants to expand the string, e.g. conversion from &szlig; to ss */
+	size_t biggerbuffersize = 1024;
+	char *biggerbuffer = pmalloc(id->p, biggerbuffersize);
+	if (biggerbuffer == NULL)
+	    return 1;
+	strcpy(biggerbuffer, id->resource);
+	result = _jid_cached_stringprep(id->resource, strlen(id->resource)+1, _jid_prep_cache_resource);
+	id->resource = biggerbuffer;
+    }
+    if (result != STRINGPREP_OK)
+	return 1;
+
+    /* the resourcepreped node must not be longer than 1023 bytes */
+    if (j_strlen(id->resource) > 1023)
+	return 1;
+
+    /* if nothing failed, the resource is valid */
+    return 0;
+
+}
+
+#else /* no LIBIDN */
+
+/**
+ * check if the domain identifier in a JID is valid
+ *
+ * @param jid data structure holding the JID
+ * @return 0 if domain is valid, non zero otherwise
+ */
+int _jid_safe_domain(jid id) {
     char *str;
 
-    if(strlen(id->server) == 0 || strlen(id->server) > 255)
-	return NULL;
+    /* there must be a domain identifier */
+    if (j_strlen(id->server) == 0)
+	return 1;
+
+    /* and it must not be longer than 1023 bytes */
+    if (strlen(id->server) > 1023)
+	return 1;
 
     /* lowercase the hostname, make sure it's valid characters */
     for(str = id->server; *str != '\0'; str++)
     {
-	*str = tolower(*str);
-	if(!(isalnum(*str) || *str == '.' || *str == '-' || *str == '_')) return NULL;
+        *str = tolower(*str);
+        if(!(isalnum(*str) || *str == '.' || *str == '-' || *str == '_')) return 1;
     }
 
-    /* cut off the user */
-    if(id->user != NULL && strlen(id->user) > 64)
-	id->user[64] = '\0';
+    /* otherwise it's okay as far as we can tell without LIBIDN */
+    return 0;
+}
+
+/**
+ * check if the node identifier in a JID is valid
+ *
+ * @param jid data structure holding the JID
+ * @return 0 if node is valid, non zero otherwise
+ */
+int _jid_safe_node(jid id) {
+    char *str;
+
+    /* node identifiers may not be longer than 1023 bytes */
+    if (j_strlen(id->user) > 1023)
+	return 1;
 
     /* check for low and invalid ascii characters in the username */
     if(id->user != NULL)
-	for(str = id->user; *str != '\0'; str++)
-	    if(*str <= 32 || *str == ':' || *str == '@' || *str == '<' || *str == '>' || *str == '\'' || *str == '"' || *str == '&') return NULL;
+        for(str = id->user; *str != '\0'; str++)
+            if(*str <= 32 || *str == ':' || *str == '@' || *str == '<' || *str == '>' || *str == '\'' || *str == '"' || *str == '&') return 1;
+
+    /* otherwise it's okay as far as we can tell without LIBIDN */
+    return 0;
+}
+
+/**
+ * check if the resource identifier in a JID is valid
+ *
+ * @param jid data structure holding the JID
+ * @return 0 if resource is valid, non zero otherwise
+ */
+int _jid_safe_resource(jid id) {
+    /* resources may not be longer than 1023 bytes */
+    if (j_strlen(id->resource) > 1023)
+	return 1;
+
+    /* otherwise it's okay as far as we can tell without LIBIDN */
+    return 0;
+}
+
+#endif
+
+/**
+ * nodeprep/nameprep/resourceprep the JID and check if it is valid
+ *
+ * @param jid data structure holding the JID
+ * @return NULL if the JID is invalid, pointer to the jid otherwise
+ */
+jid jid_safe(jid id)
+{
+    if (_jid_safe_domain(id))
+	return NULL;
+    if (_jid_safe_node(id))
+	return NULL;
+    if (_jid_safe_resource(id))
+	return NULL;
 
     return id;
 }
@@ -51,46 +495,44 @@
     jid id;
 
     if(p == NULL || idstr == NULL || strlen(idstr) == 0)
-	return NULL;
+        return NULL;
 
     /* user@server/resource */
 
     str = pstrdup(p, idstr);
 
-    id = pmalloc(p,sizeof(struct jid_struct));
-    id->full = id->server = id->user = id->resource = NULL;
+    id = pmalloco(p,sizeof(struct jid_struct));
     id->p = p;
-    id->next = NULL;
 
     resource = strstr(str,"/");
     if(resource != NULL)
     {
-	*resource = '\0';
-	++resource;
-	if(strlen(resource) > 0)
-	    id->resource = resource;
+        *resource = '\0';
+        ++resource;
+        if(strlen(resource) > 0)
+            id->resource = resource;
     }else{
-	resource = str + strlen(str); /* point to end */
+        resource = str + strlen(str); /* point to end */
     }
 
     type = strstr(str,":");
     if(type != NULL && type < resource)
     {
-	*type = '\0';
-	++type;
-	str = type; /* ignore the type: prefix */
+        *type = '\0';
+        ++type;
+        str = type; /* ignore the type: prefix */
     }
 
     server = strstr(str,"@");
     if(server == NULL || server > resource)
     { /* if there's no @, it's just the server address */
-	id->server = str;
+        id->server = str;
     }else{
-	*server = '\0';
-	++server;
-	id->server = server;
-	if(strlen(str) > 0)
-	    id->user = str;
+        *server = '\0';
+        ++server;
+        id->server = server;
+        if(strlen(str) > 0)
+            id->user = str;
     }
 
     return jid_safe(id);
@@ -101,7 +543,7 @@
     char *old;
 
     if(id == NULL)
-	return;
+        return;
 
     /* invalidate the cached copy */
     id->full = NULL;
@@ -109,26 +551,29 @@
     switch(item)
     {
     case JID_RESOURCE:
-	if(str != NULL && strlen(str) != 0)
-	    id->resource = pstrdup(id->p, str);
-	else
-	    id->resource = NULL;
-	break;
+	old = id->resource;
+        if(str != NULL && strlen(str) != 0)
+            id->resource = pstrdup(id->p, str);
+        else
+            id->resource = NULL;
+        if(_jid_safe_resource(id))
+            id->resource = old; /* revert if invalid */
+        break;
     case JID_USER:
-	old = id->user;
-	if(str != NULL && strlen(str) != 0)
-	    id->user = pstrdup(id->p, str);
-	else
-	    id->user = NULL;
-	if(jid_safe(id) == NULL)
-	    id->user = old; /* revert if invalid */
-	break;
+        old = id->user;
+        if(str != NULL && strlen(str) != 0)
+            id->user = pstrdup(id->p, str);
+        else
+            id->user = NULL;
+        if(_jid_safe_node(id))
+            id->user = old; /* revert if invalid */
+        break;
     case JID_SERVER:
-	old = id->server;
-	id->server = pstrdup(id->p, str);
-	if(jid_safe(id) == NULL)
-	    id->server = old; /* revert if invalid */
-	break;
+        old = id->server;
+        id->server = pstrdup(id->p, str);
+        if(_jid_safe_domain(id))
+            id->server = old; /* revert if invalid */
+        break;
     }
 
 }
@@ -138,21 +583,21 @@
     spool s;
 
     if(id == NULL)
-	return NULL;
+        return NULL;
 
     /* use cached copy */
     if(id->full != NULL)
-	return id->full;
+        return id->full;
 
     s = spool_new(id->p);
 
     if(id->user != NULL)
-	spooler(s, id->user,"@",s);
+        spooler(s, id->user,"@",s);
 
     spool_add(s, id->server);
 
     if(id->resource != NULL)
-	spooler(s, "/",id->resource,s);
+        spooler(s, "/",id->resource,s);
 
     id->full = spool_print(s);
     return id->full;
@@ -177,24 +622,24 @@
     cur = qmark;
     while(cur != '\0')
     {
-	eq = strstr(cur, "=");
-	if(eq == NULL) break;
-	*eq = '\0';
-	eq++;
+        eq = strstr(cur, "=");
+        if(eq == NULL) break;
+        *eq = '\0';
+        eq++;
 
-	amp = strstr(eq, "&");
-	if(amp != NULL)
-	{
-	    *amp = '\0';
-	    amp++;
-	}
+        amp = strstr(eq, "&");
+        if(amp != NULL)
+        {
+            *amp = '\0';
+            amp++;
+        }
 
-	xmlnode_put_attrib(x,cur,eq);
+        xmlnode_put_attrib(x,cur,eq);
 
-	if(amp != NULL)
-	    cur = amp;
-	else
-	    break;
+        if(amp != NULL)
+            cur = amp;
+        else
+            break;
     }
 
     return x;
@@ -217,7 +662,7 @@
 int jid_cmp(jid a, jid b)
 {
     if(a == NULL || b == NULL)
-	return -1;
+        return -1;
 
     if(_jid_nullstrcmp(a->resource, b->resource) != 0) return -1;
     if(_jid_nullstrcasecmp(a->user, b->user) != 0) return -1;
@@ -230,7 +675,7 @@
 int jid_cmpx(jid a, jid b, int parts)
 {
     if(a == NULL || b == NULL)
-	return -1;
+        return -1;
 
     if(parts & JID_RESOURCE && _jid_nullstrcmp(a->resource, b->resource) != 0) return -1;
     if(parts & JID_USER && _jid_nullstrcasecmp(a->user, b->user) != 0) return -1;
@@ -245,20 +690,20 @@
     jid next;
 
     if(a == NULL)
-	return NULL;
+        return NULL;
 
     if(b == NULL)
-	return a;
+        return a;
 
     next = a;
     while(next != NULL)
     {
-	/* check for dups */
-	if(jid_cmp(next,b) == 0)
-	    break;
-	if(next->next == NULL)
-	    next->next = jid_new(a->p,jid_full(b));
-	next = next->next;
+        /* check for dups */
+        if(jid_cmp(next,b) == 0)
+            break;
+        if(next->next == NULL)
+            next->next = jid_new(a->p,jid_full(b));
+        next = next->next;
     }
     return a;
 }
@@ -274,14 +719,28 @@
     p = pool_new();
     for(cur = xmlnode_get_firstchild(x); cur != NULL; cur = xmlnode_get_nextsibling(cur))
     {
-	if(xmlnode_get_type(cur) != NTYPE_TAG) continue;
+        if(xmlnode_get_type(cur) != NTYPE_TAG) continue;
 
-	tmp = jid_new(p,xmlnode_get_attrib(cur,"jid"));
-	if(tmp == NULL) continue;
+        tmp = jid_new(p,xmlnode_get_attrib(cur,"jid"));
+        if(tmp == NULL) continue;
 
-	if(jid_cmp(tmp,id) == 0) break;
+        if(jid_cmp(tmp,id) == 0) break;
     }
     pool_free(p);
 
     return cur;
 }
+
+jid jid_user(jid a)
+{
+    jid ret;
+
+    if(a == NULL || a->resource == NULL) return a;
+
+    ret = pmalloco(a->p,sizeof(struct jid_struct));
+    ret->p = a->p;
+    ret->user = a->user;
+    ret->server = a->server;
+
+    return ret;
+}