source: protocols/jabber/jabber_util.c @ c8eeadd

Last change on this file since c8eeadd was d50e22f, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-06-08T22:22:16Z

Merging memory leak fixes from devel, time to find the ui-fix-specific
leaks.

  • Property mode set to 100644
File size: 20.1 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - Misc. stuff                                              *
5*                                                                           *
6*  Copyright 2006-2010 Wilmer van der Gaast <wilmer@gaast.net>             
7*                                                                           *
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.                                      *
12*                                                                           *
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.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "jabber.h"
25#include "md5.h"
26#include "base64.h"
27
28static unsigned int next_id = 1;
29
30char *set_eval_priority( set_t *set, char *value )
31{
32        account_t *acc = set->data;
33        int i;
34       
35        if( sscanf( value, "%d", &i ) == 1 )
36        {
37                /* Priority is a signed 8-bit integer, according to RFC 3921. */
38                if( i < -128 || i > 127 )
39                        return SET_INVALID;
40        }
41        else
42                return SET_INVALID;
43       
44        /* Only run this stuff if the account is online ATM,
45           and if the setting seems to be acceptable. */
46        if( acc->ic )
47        {
48                /* Although set_eval functions usually are very nice and
49                   convenient, they have one disadvantage: If I would just
50                   call p_s_u() now to send the new prio setting, it would
51                   send the old setting because the set->value gets changed
52                   after the (this) eval returns a non-NULL value.
53                   
54                   So now I can choose between implementing post-set
55                   functions next to evals, or just do this little hack: */
56               
57                g_free( set->value );
58                set->value = g_strdup( value );
59               
60                /* (Yes, sorry, I prefer the hack. :-P) */
61               
62                presence_send_update( acc->ic );
63        }
64       
65        return value;
66}
67
68char *set_eval_tls( set_t *set, char *value )
69{
70        if( g_strcasecmp( value, "try" ) == 0 )
71                return value;
72        else
73                return set_eval_bool( set, value );
74}
75
76struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children )
77{
78        struct xt_node *node;
79       
80        node = xt_new_node( name, NULL, children );
81       
82        if( type )
83                xt_add_attr( node, "type", type );
84        if( to )
85                xt_add_attr( node, "to", to );
86       
87        /* IQ packets should always have an ID, so let's generate one. It
88           might get overwritten by jabber_cache_add() if this packet has
89           to be saved until we receive a response. Cached packets get
90           slightly different IDs so we can recognize them. */
91        if( strcmp( name, "iq" ) == 0 )
92        {
93                char *id = g_strdup_printf( "%s%05x", JABBER_PACKET_ID, ( next_id++ ) & 0xfffff );
94                xt_add_attr( node, "id", id );
95                g_free( id );
96        }
97       
98        return node;
99}
100
101struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code )
102{
103        struct xt_node *node, *c;
104        char *to;
105       
106        /* Create the "defined-condition" tag. */
107        c = xt_new_node( err_cond, NULL, NULL );
108        xt_add_attr( c, "xmlns", XMLNS_STANZA_ERROR );
109       
110        /* Put it in an <error> tag. */
111        c = xt_new_node( "error", NULL, c );
112        xt_add_attr( c, "type", err_type );
113       
114        /* Add the error code, if present */
115        if (err_code)
116                xt_add_attr( c, "code", err_code );
117       
118        /* To make the actual error packet, we copy the original packet and
119           add our <error>/type="error" tag. Including the original packet
120           is recommended, so let's just do it. */
121        node = xt_dup( orig );
122        xt_add_child( node, c );
123        xt_add_attr( node, "type", "error" );
124       
125        /* Return to sender. */
126        if( ( to = xt_find_attr( node, "from" ) ) )
127        {
128                xt_add_attr( node, "to", to );
129                xt_remove_attr( node, "from" );
130        }
131               
132        return node;
133}
134
135/* Cache a node/packet for later use. Mainly useful for IQ packets if you need
136   them when you receive the response. Use this BEFORE sending the packet so
137   it'll get a new id= tag, and do NOT free() the packet after sending it! */
138void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func )
139{
140        struct jabber_data *jd = ic->proto_data;
141        struct jabber_cache_entry *entry = g_new0( struct jabber_cache_entry, 1 );
142        md5_state_t id_hash;
143        md5_byte_t id_sum[16];
144        char *id, *asc_hash;
145       
146        next_id ++;
147       
148        id_hash = jd->cached_id_prefix;
149        md5_append( &id_hash, (md5_byte_t*) &next_id, sizeof( next_id ) );
150        md5_finish( &id_hash, id_sum );
151        asc_hash = base64_encode( id_sum, 12 );
152       
153        id = g_strdup_printf( "%s%s", JABBER_CACHED_ID, asc_hash );
154        xt_add_attr( node, "id", id );
155        g_free( id );
156        g_free( asc_hash );
157       
158        entry->node = node;
159        entry->func = func;
160        entry->saved_at = time( NULL );
161        g_hash_table_insert( jd->node_cache, xt_find_attr( node, "id" ), entry );
162}
163
164void jabber_cache_entry_free( gpointer data )
165{
166        struct jabber_cache_entry *entry = data;
167       
168        xt_free_node( entry->node );
169        g_free( entry );
170}
171
172gboolean jabber_cache_clean_entry( gpointer key, gpointer entry, gpointer nullpointer );
173
174/* This one should be called from time to time (from keepalive, in this case)
175   to make sure things don't stay in the node cache forever. By marking nodes
176   during the first run and deleting marked nodes during a next run, every
177   node should be available in the cache for at least a minute (assuming the
178   function is indeed called every minute). */
179void jabber_cache_clean( struct im_connection *ic )
180{
181        struct jabber_data *jd = ic->proto_data;
182        time_t threshold = time( NULL ) - JABBER_CACHE_MAX_AGE;
183       
184        g_hash_table_foreach_remove( jd->node_cache, jabber_cache_clean_entry, &threshold );
185}
186
187gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer threshold_ )
188{
189        struct jabber_cache_entry *entry = entry_;
190        time_t *threshold = threshold_;
191       
192        return entry->saved_at < *threshold;
193}
194
195xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *node )
196{
197        struct jabber_data *jd = ic->proto_data;
198        struct jabber_cache_entry *entry;
199        char *s;
200       
201        if( ( s = xt_find_attr( node, "id" ) ) == NULL ||
202            strncmp( s, JABBER_CACHED_ID, strlen( JABBER_CACHED_ID ) ) != 0 )
203        {
204                /* Silently ignore it, without an ID (or a non-cache
205                   ID) we don't know how to handle the packet and we
206                   probably don't have to. */
207                return XT_HANDLED;
208        }
209       
210        entry = g_hash_table_lookup( jd->node_cache, s );
211       
212        if( entry == NULL )
213        {
214                /*
215                There's no longer an easy way to see if we generated this
216                one or someone else, and there's a ten-minute timeout anyway,
217                so meh.
218               
219                imcb_log( ic, "Warning: Received %s-%s packet with unknown/expired ID %s!",
220                              node->name, xt_find_attr( node, "type" ) ? : "(no type)", s );
221                */
222        }
223        else if( entry->func )
224        {
225                return entry->func( ic, node, entry->node );
226        }
227       
228        return XT_HANDLED;
229}
230
231const struct jabber_away_state jabber_away_state_list[] =
232{
233        { "away",  "Away" },
234        { "chat",  "Free for Chat" },   /* WTF actually uses this? */
235        { "dnd",   "Do not Disturb" },
236        { "xa",    "Extended Away" },
237        { "",      NULL }
238};
239
240const struct jabber_away_state *jabber_away_state_by_code( char *code )
241{
242        int i;
243       
244        if( code == NULL )
245                return NULL;
246       
247        for( i = 0; jabber_away_state_list[i].full_name; i ++ )
248                if( g_strcasecmp( jabber_away_state_list[i].code, code ) == 0 )
249                        return jabber_away_state_list + i;
250       
251        return NULL;
252}
253
254const struct jabber_away_state *jabber_away_state_by_name( char *name )
255{
256        int i;
257       
258        if( name == NULL )
259                return NULL;
260       
261        for( i = 0; jabber_away_state_list[i].full_name; i ++ )
262                if( g_strcasecmp( jabber_away_state_list[i].full_name, name ) == 0 )
263                        return jabber_away_state_list + i;
264       
265        return NULL;
266}
267
268struct jabber_buddy_ask_data
269{
270        struct im_connection *ic;
271        char *handle;
272        char *realname;
273};
274
275static void jabber_buddy_ask_yes( void *data )
276{
277        struct jabber_buddy_ask_data *bla = data;
278       
279        presence_send_request( bla->ic, bla->handle, "subscribed" );
280       
281        imcb_ask_add( bla->ic, bla->handle, NULL );
282       
283        g_free( bla->handle );
284        g_free( bla );
285}
286
287static void jabber_buddy_ask_no( void *data )
288{
289        struct jabber_buddy_ask_data *bla = data;
290       
291        presence_send_request( bla->ic, bla->handle, "subscribed" );
292       
293        g_free( bla->handle );
294        g_free( bla );
295}
296
297void jabber_buddy_ask( struct im_connection *ic, char *handle )
298{
299        struct jabber_buddy_ask_data *bla = g_new0( struct jabber_buddy_ask_data, 1 );
300        char *buf;
301       
302        bla->ic = ic;
303        bla->handle = g_strdup( handle );
304       
305        buf = g_strdup_printf( "The user %s wants to add you to his/her buddy list.", handle );
306        imcb_ask( ic, buf, bla, jabber_buddy_ask_yes, jabber_buddy_ask_no );
307        g_free( buf );
308}
309
310/* Returns a new string. Don't leak it! */
311char *jabber_normalize( const char *orig )
312{
313        int len, i;
314        char *new;
315       
316        len = strlen( orig );
317        new = g_new( char, len + 1 );
318       
319        /* So it turns out the /resource part is case sensitive. Yeah, and
320           it's Unicode but feck Unicode. :-P So stop once we see a slash. */
321        for( i = 0; i < len && orig[i] != '/' ; i ++ )
322                new[i] = tolower( orig[i] );
323        for( ; orig[i]; i ++ )
324                new[i] = orig[i];
325       
326        new[i] = 0;
327        return new;
328}
329
330/* Adds a buddy/resource to our list. Returns NULL if full_jid is not really a
331   FULL jid or if we already have this buddy/resource. XXX: No, great, actually
332   buddies from transports don't (usually) have resources. So we'll really have
333   to deal with that properly. Set their ->resource property to NULL. Do *NOT*
334   allow to mix this stuff, though... */
335struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_ )
336{
337        struct jabber_data *jd = ic->proto_data;
338        struct jabber_buddy *bud, *new, *bi;
339        char *s, *full_jid;
340       
341        full_jid = jabber_normalize( full_jid_ );
342       
343        if( ( s = strchr( full_jid, '/' ) ) )
344                *s = 0;
345       
346        new = g_new0( struct jabber_buddy, 1 );
347       
348        if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) )
349        {
350                /* The first entry is always a bare JID. If there are more, we
351                   should ignore the first one here. */
352                if( bud->next )
353                        bud = bud->next;
354               
355                /* If this is a transport buddy or whatever, it can't have more
356                   than one instance, so this is always wrong: */
357                if( s == NULL || bud->resource == NULL )
358                {
359                        if( s ) *s = '/';
360                        g_free( new );
361                        g_free( full_jid );
362                        return NULL;
363                }
364               
365                new->bare_jid = bud->bare_jid;
366               
367                /* We already have another resource for this buddy, add the
368                   new one to the list. */
369                for( bi = bud; bi; bi = bi->next )
370                {
371                        /* Check for dupes. */
372                        if( strcmp( bi->resource, s + 1 ) == 0 )
373                        {
374                                *s = '/';
375                                g_free( new );
376                                g_free( full_jid );
377                                return NULL;
378                        }
379                        /* Append the new item to the list. */
380                        else if( bi->next == NULL )
381                        {
382                                bi->next = new;
383                                break;
384                        }
385                }
386        }
387        else
388        {
389                new->full_jid = new->bare_jid = g_strdup( full_jid );
390                g_hash_table_insert( jd->buddies, new->bare_jid, new );
391               
392                if( s )
393                {
394                        new->next = g_new0( struct jabber_buddy, 1 );
395                        new->next->bare_jid = new->bare_jid;
396                        new = new->next;
397                }
398        }
399       
400        if( s )
401        {
402                *s = '/';
403                new->full_jid = full_jid;
404                new->resource = strchr( new->full_jid, '/' ) + 1;
405        }
406        else
407        {
408                /* Let's waste some more bytes of RAM instead of to make
409                   memory management a total disaster here. And it saves
410                   me one g_free() call in this function. :-P */
411                new->full_jid = full_jid;
412        }
413       
414        return new;
415}
416
417/* Finds a buddy from our structures. Can find both full- and bare JIDs. When
418   asked for a bare JID, it uses the "resource_select" setting to see which
419   resource to pick. */
420struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
421{
422        struct jabber_data *jd = ic->proto_data;
423        struct jabber_buddy *bud, *head;
424        char *s, *jid;
425       
426        jid = jabber_normalize( jid_ );
427       
428        if( ( s = strchr( jid, '/' ) ) )
429        {
430                int bare_exists = 0;
431               
432                *s = 0;
433                if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )
434                {
435                        bare_exists = 1;
436                       
437                        if( bud->next )
438                                bud = bud->next;
439                       
440                        /* Just return the first one for this bare JID. */
441                        if( flags & GET_BUDDY_FIRST )
442                        {
443                                *s = '/';
444                                g_free( jid );
445                                return bud;
446                        }
447                       
448                        /* Is this one of those no-resource buddies? */
449                        if( bud->resource == NULL )
450                        {
451                                *s = '/';
452                                g_free( jid );
453                                return NULL;
454                        }
455                       
456                        /* See if there's an exact match. */
457                        for( ; bud; bud = bud->next )
458                                if( strcmp( bud->resource, s + 1 ) == 0 )
459                                        break;
460                }
461               
462                if( bud == NULL && ( flags & GET_BUDDY_CREAT ) &&
463                    ( bare_exists || bee_user_by_handle( ic->bee, ic, jid ) ) )
464                {
465                        *s = '/';
466                        bud = jabber_buddy_add( ic, jid );
467                }
468               
469                g_free( jid );
470                return bud;
471        }
472        else
473        {
474                struct jabber_buddy *best_prio, *best_time;
475                char *set;
476               
477                head = g_hash_table_lookup( jd->buddies, jid );
478                bud = ( head && head->next ) ? head->next : head;
479               
480                g_free( jid );
481               
482                if( bud == NULL )
483                        /* No match. Create it now? */
484                        return ( ( flags & GET_BUDDY_CREAT ) &&
485                                 bee_user_by_handle( ic->bee, ic, jid_ ) ) ?
486                                   jabber_buddy_add( ic, jid_ ) : NULL;
487                else if( bud->resource && ( flags & GET_BUDDY_EXACT ) )
488                        /* We want an exact match, so in thise case there shouldn't be a /resource. */
489                        return NULL;
490                else if( bud->resource == NULL || bud->next == NULL )
491                        /* No need for selection if there's only one option. */
492                        return bud;
493                else if( flags & GET_BUDDY_FIRST )
494                        /* Looks like the caller doesn't care about details. */
495                        return bud;
496                else if( flags & GET_BUDDY_BARE )
497                        return head;
498               
499                best_prio = best_time = bud;
500                for( ; bud; bud = bud->next )
501                {
502                        if( bud->priority > best_prio->priority )
503                                best_prio = bud;
504                        if( bud->last_msg > best_time->last_msg )
505                                best_time = bud;
506                }
507               
508                if( ( set = set_getstr( &ic->acc->set, "resource_select" ) ) == NULL )
509                        return NULL;
510                else if( strcmp( set, "priority" ) == 0 )
511                        return best_prio;
512                else if( flags & GET_BUDDY_BARE_OK ) /* && strcmp( set, "activity" ) == 0 */
513                {
514                        if( best_time->last_msg + set_getint( &ic->acc->set, "activity_timeout" ) >= time( NULL ) )
515                                return best_time;
516                        else
517                                return head;
518                }
519                else
520                        return best_time;
521        }
522}
523
524/* I'm keeping a separate ext_jid attribute to save a JID that makes sense
525   to export to BitlBee. This is mainly for groupchats right now. It's
526   a bit of a hack, but I just think having the user nickname in the hostname
527   part of the hostmask doesn't look nice on IRC. Normally you can convert
528   a normal JID to ext_jid by swapping the part before and after the / and
529   replacing the / with a =. But there should be some stripping (@s are
530   allowed in Jabber nicks...). */
531struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
532{
533        struct jabber_buddy *bud;
534        char *s, *jid;
535       
536        jid = jabber_normalize( jid_ );
537       
538        if( ( s = strchr( jid, '=' ) ) == NULL )
539                return NULL;
540       
541        for( bud = jabber_buddy_by_jid( ic, s + 1, GET_BUDDY_FIRST ); bud; bud = bud->next )
542        {
543                /* Hmmm, could happen if not all people in the chat are anonymized? */
544                if( bud->ext_jid == NULL )
545                        continue;
546               
547                if( strcmp( bud->ext_jid, jid ) == 0 )
548                        break;
549        }
550       
551        g_free( jid );
552       
553        return bud;
554}
555
556/* Remove one specific full JID from our list. Use this when a buddy goes
557   off-line (because (s)he can still be online from a different location.
558   XXX: See above, we should accept bare JIDs too... */
559int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
560{
561        struct jabber_data *jd = ic->proto_data;
562        struct jabber_buddy *bud, *prev = NULL, *bi;
563        char *s, *full_jid;
564       
565        full_jid = jabber_normalize( full_jid_ );
566       
567        if( ( s = strchr( full_jid, '/' ) ) )
568                *s = 0;
569       
570        if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) )
571        {
572                if( bud->next )
573                        bud = (prev=bud)->next;
574               
575                /* If there's only one item in the list (and if the resource
576                   matches), removing it is simple. (And the hash reference
577                   should be removed too!) */
578                if( bud->next == NULL &&
579                    ( ( s == NULL && bud->resource == NULL ) ||
580                      ( bud->resource && s && strcmp( bud->resource, s + 1 ) == 0 ) ) )
581                {
582                        return jabber_buddy_remove_bare( ic, full_jid );
583                }
584                else if( s == NULL || bud->resource == NULL )
585                {
586                        /* Tried to remove a bare JID while this JID does seem
587                           to have resources... (Or the opposite.) *sigh* */
588                        g_free( full_jid );
589                        return 0;
590                }
591                else
592                {
593                        for( bi = bud; bi; bi = (prev=bi)->next )
594                                if( strcmp( bi->resource, s + 1 ) == 0 )
595                                        break;
596                       
597                        g_free( full_jid );
598                       
599                        if( bi )
600                        {
601                                if( prev )
602                                        prev->next = bi->next;
603                                else
604                                        /* Don't think this should ever happen anymore. */
605                                        g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next );
606                               
607                                g_free( bi->ext_jid );
608                                g_free( bi->full_jid );
609                                g_free( bi->away_message );
610                                g_free( bi );
611                               
612                                return 1;
613                        }
614                        else
615                        {
616                                return 0;
617                        }
618                }
619        }
620        else
621        {
622                g_free( full_jid );
623                return 0;
624        }
625}
626
627/* Remove a buddy completely; removes all resources that belong to the
628   specified bare JID. Use this when removing someone from the contact
629   list, for example. */
630int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid )
631{
632        struct jabber_data *jd = ic->proto_data;
633        struct jabber_buddy *bud, *next;
634       
635        if( strchr( bare_jid, '/' ) )
636                return 0;
637       
638        if( ( bud = jabber_buddy_by_jid( ic, bare_jid, GET_BUDDY_FIRST ) ) )
639        {
640                /* Most important: Remove the hash reference. We don't know
641                   this buddy anymore. */
642                g_hash_table_remove( jd->buddies, bud->bare_jid );
643                g_free( bud->bare_jid );
644               
645                /* Deallocate the linked list of resources. */
646                while( bud )
647                {
648                        /* ext_jid && anonymous means that this buddy is
649                           specific to one groupchat (the one we're
650                           currently cleaning up) so it can be deleted
651                           completely. */
652                        if( bud->ext_jid && bud->flags & JBFLAG_IS_ANONYMOUS )
653                                imcb_remove_buddy( ic, bud->ext_jid, NULL );
654                       
655                        next = bud->next;
656                        g_free( bud->ext_jid );
657                        g_free( bud->full_jid );
658                        g_free( bud->away_message );
659                        g_free( bud );
660                        bud = next;
661                }
662               
663                return 1;
664        }
665        else
666        {
667                return 0;
668        }
669}
670
671static gboolean jabber_buddy_remove_all_cb( gpointer key, gpointer value, gpointer data )
672{
673        struct jabber_buddy *bud, *next;
674       
675        bud = value;
676        while( bud )
677        {
678                next = bud->next;
679                g_free( bud->ext_jid );
680                g_free( bud->full_jid );
681                g_free( bud->away_message );
682                g_free( bud );
683                bud = next;
684        }
685       
686        return TRUE;
687}
688
689void jabber_buddy_remove_all( struct im_connection *ic )
690{
691        struct jabber_data *jd = ic->proto_data;
692       
693        g_hash_table_foreach_remove( jd->buddies, jabber_buddy_remove_all_cb, NULL );
694        g_hash_table_destroy( jd->buddies );
695}
696
697time_t jabber_get_timestamp( struct xt_node *xt )
698{
699        struct xt_node *c;
700        char *s = NULL;
701        struct tm tp;
702       
703        for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
704        {
705                if( ( s = xt_find_attr( c, "xmlns" ) ) && strcmp( s, XMLNS_DELAY ) == 0 )
706                        break;
707        }
708       
709        if( !c || !( s = xt_find_attr( c, "stamp" ) ) )
710                return 0;
711       
712        memset( &tp, 0, sizeof( tp ) );
713        if( sscanf( s, "%4d%2d%2dT%2d:%2d:%2d", &tp.tm_year, &tp.tm_mon, &tp.tm_mday,
714                                                &tp.tm_hour, &tp.tm_min, &tp.tm_sec ) != 6 )
715                return 0;
716       
717        tp.tm_year -= 1900;
718        tp.tm_mon --;
719       
720        return mktime_utc( &tp );
721}
722
723struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns )
724{
725        struct jabber_error *err;
726        struct xt_node *c;
727        char *s;
728       
729        if( node == NULL )
730                return NULL;
731       
732        err = g_new0( struct jabber_error, 1 );
733        err->type = xt_find_attr( node, "type" );
734       
735        for( c = node->children; c; c = c->next )
736        {
737                if( !( s = xt_find_attr( c, "xmlns" ) ) ||
738                    strcmp( s, xmlns ) != 0 )
739                        continue;
740               
741                if( strcmp( c->name, "text" ) != 0 )
742                {
743                        err->code = c->name;
744                }
745                /* Only use the text if it doesn't have an xml:lang attribute,
746                   if it's empty or if it's set to something English. */
747                else if( !( s = xt_find_attr( c, "xml:lang" ) ) ||
748                         !*s || strncmp( s, "en", 2 ) == 0 )
749                {
750                        err->text = c->text;
751                }
752        }
753       
754        return err;
755}
756
757void jabber_error_free( struct jabber_error *err )
758{
759        g_free( err );
760}
Note: See TracBrowser for help on using the repository browser.