Mercurial > hg
view mcabber/libjabber/jid.c @ 1283:2faf179166f3
Implement XEP-0202 (Entity Time)
Mcabber now answers urn:xmpp:time IQ requests.
author | Mikael Berthe <mikael@lilotux.net> |
---|---|
date | Sat, 25 Aug 2007 22:48:59 +0200 |
parents | c3ae9251c197 |
children |
line wrap: on
line source
/* * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Jabber * Copyright (C) 1998-1999 The Jabber Team http://jabber.org/ */ /** * @file jid.c * @brief representation and normalization of JabberIDs */ #include "jabber.h" #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 ß 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 ß 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 ß 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; /* 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 1; } /* 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 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; } jid jid_new(pool p, char *idstr) { char *server, *resource, *type, *str; jid id; if(p == NULL || idstr == NULL || strlen(idstr) == 0) return NULL; /* user@server/resource */ str = pstrdup(p, idstr); id = pmalloco(p,sizeof(struct jid_struct)); id->p = p; resource = strstr(str,"/"); if(resource != NULL) { *resource = '\0'; ++resource; if(strlen(resource) > 0) id->resource = resource; }else{ resource = str + strlen(str); /* point to end */ } type = strstr(str,":"); if(type != NULL && type < resource) { *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; }else{ *server = '\0'; ++server; id->server = server; if(strlen(str) > 0) id->user = str; } return jid_safe(id); } void jid_set(jid id, char *str, int item) { char *old; if(id == NULL) return; /* invalidate the cached copy */ id->full = NULL; switch(item) { case JID_RESOURCE: 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_node(id)) id->user = old; /* revert if invalid */ break; case JID_SERVER: old = id->server; id->server = pstrdup(id->p, str); if(_jid_safe_domain(id)) id->server = old; /* revert if invalid */ break; } } char *jid_full(jid id) { spool s; if(id == NULL) return NULL; /* use cached copy */ if(id->full != NULL) return id->full; s = spool_new(id->p); if(id->user != NULL) spooler(s, id->user,"@",s); spool_add(s, id->server); if(id->resource != NULL) spooler(s, "/",id->resource,s); id->full = spool_print(s); return id->full; } /* parses a /resource?name=value&foo=bar into an xmlnode representing <resource name="value" foo="bar"/> */ xmlnode jid_xres(jid id) { char *cur, *qmark, *amp, *eq; xmlnode x; if(id == NULL || id->resource == NULL) return NULL; cur = pstrdup(id->p, id->resource); qmark = strstr(cur, "?"); if(qmark == NULL) return NULL; *qmark = '\0'; qmark++; x = _xmlnode_new(id->p, cur, NTYPE_TAG); cur = qmark; while(cur != '\0') { eq = strstr(cur, "="); if(eq == NULL) break; *eq = '\0'; eq++; amp = strstr(eq, "&"); if(amp != NULL) { *amp = '\0'; amp++; } xmlnode_put_attrib(x,cur,eq); if(amp != NULL) cur = amp; else break; } return x; } /* local utils */ int _jid_nullstrcmp(char *a, char *b) { if(a == NULL && b == NULL) return 0; if(a == NULL || b == NULL) return -1; return strcmp(a,b); } int _jid_nullstrcasecmp(char *a, char *b) { if(a == NULL && b == NULL) return 0; if(a == NULL || b == NULL) return -1; return strcasecmp(a,b); } int jid_cmp(jid a, jid b) { if(a == NULL || b == NULL) return -1; if(_jid_nullstrcmp(a->resource, b->resource) != 0) return -1; if(_jid_nullstrcasecmp(a->user, b->user) != 0) return -1; if(_jid_nullstrcmp(a->server, b->server) != 0) return -1; return 0; } /* suggested by Anders Qvist <quest@valdez.netg.se> */ int jid_cmpx(jid a, jid b, int parts) { if(a == NULL || b == NULL) 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; if(parts & JID_SERVER && _jid_nullstrcmp(a->server, b->server) != 0) return -1; return 0; } /* makes a copy of b in a's pool, requires a valid a first! */ jid jid_append(jid a, jid b) { jid next; if(a == NULL) return NULL; if(b == NULL) 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; } return a; } xmlnode jid_nodescan(jid id, xmlnode x) { xmlnode cur; pool p; jid tmp; if(id == NULL || xmlnode_get_firstchild(x) == NULL) return NULL; p = pool_new(); for(cur = xmlnode_get_firstchild(x); cur != NULL; cur = xmlnode_get_nextsibling(cur)) { if(xmlnode_get_type(cur) != NTYPE_TAG) continue; tmp = jid_new(p,xmlnode_get_attrib(cur,"jid")); if(tmp == NULL) continue; 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; }