source: protocols/jabber/jabber_util.c @ b194fe7

Last change on this file since b194fe7 was 03e6c52, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-10-22T23:46:44Z

Change 704 introduced one tiny bug where a Jabber resource doesn't get
deleted properly when it logs out if it's the last one online for that
user. Fixing that now.

  • Property mode set to 100644
File size: 20.2 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                        int st = jabber_buddy_remove_bare( ic, full_jid );
583                        g_free( full_jid );
584                        return st;
585                }
586                else if( s == NULL || bud->resource == NULL )
587                {
588                        /* Tried to remove a bare JID while this JID does seem
589                           to have resources... (Or the opposite.) *sigh* */
590                        g_free( full_jid );
591                        return 0;
592                }
593                else
594                {
595                        for( bi = bud; bi; bi = (prev=bi)->next )
596                                if( strcmp( bi->resource, s + 1 ) == 0 )
597                                        break;
598                       
599                        g_free( full_jid );
600                       
601                        if( bi )
602                        {
603                                if( prev )
604                                        prev->next = bi->next;
605                                else
606                                        /* Don't think this should ever happen anymore. */
607                                        g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next );
608                               
609                                g_free( bi->ext_jid );
610                                g_free( bi->full_jid );
611                                g_free( bi->away_message );
612                                g_free( bi );
613                               
614                                return 1;
615                        }
616                        else
617                        {
618                                return 0;
619                        }
620                }
621        }
622        else
623        {
624                g_free( full_jid );
625                return 0;
626        }
627}
628
629/* Remove a buddy completely; removes all resources that belong to the
630   specified bare JID. Use this when removing someone from the contact
631   list, for example. */
632int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid )
633{
634        struct jabber_data *jd = ic->proto_data;
635        struct jabber_buddy *bud, *next;
636       
637        if( strchr( bare_jid, '/' ) )
638                return 0;
639       
640        if( ( bud = jabber_buddy_by_jid( ic, bare_jid, GET_BUDDY_FIRST ) ) )
641        {
642                /* Most important: Remove the hash reference. We don't know
643                   this buddy anymore. */
644                g_hash_table_remove( jd->buddies, bud->bare_jid );
645                g_free( bud->bare_jid );
646               
647                /* Deallocate the linked list of resources. */
648                while( bud )
649                {
650                        /* ext_jid && anonymous means that this buddy is
651                           specific to one groupchat (the one we're
652                           currently cleaning up) so it can be deleted
653                           completely. */
654                        if( bud->ext_jid && bud->flags & JBFLAG_IS_ANONYMOUS )
655                                imcb_remove_buddy( ic, bud->ext_jid, NULL );
656                       
657                        next = bud->next;
658                        g_free( bud->ext_jid );
659                        g_free( bud->full_jid );
660                        g_free( bud->away_message );
661                        g_free( bud );
662                        bud = next;
663                }
664               
665                return 1;
666        }
667        else
668        {
669                return 0;
670        }
671}
672
673static gboolean jabber_buddy_remove_all_cb( gpointer key, gpointer value, gpointer data )
674{
675        struct jabber_buddy *bud, *next;
676       
677        bud = value;
678        while( bud )
679        {
680                next = bud->next;
681                g_free( bud->ext_jid );
682                g_free( bud->full_jid );
683                g_free( bud->away_message );
684                g_free( bud );
685                bud = next;
686        }
687       
688        return TRUE;
689}
690
691void jabber_buddy_remove_all( struct im_connection *ic )
692{
693        struct jabber_data *jd = ic->proto_data;
694       
695        g_hash_table_foreach_remove( jd->buddies, jabber_buddy_remove_all_cb, NULL );
696        g_hash_table_destroy( jd->buddies );
697}
698
699time_t jabber_get_timestamp( struct xt_node *xt )
700{
701        struct xt_node *c;
702        char *s = NULL;
703        struct tm tp;
704       
705        for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
706        {
707                if( ( s = xt_find_attr( c, "xmlns" ) ) && strcmp( s, XMLNS_DELAY ) == 0 )
708                        break;
709        }
710       
711        if( !c || !( s = xt_find_attr( c, "stamp" ) ) )
712                return 0;
713       
714        memset( &tp, 0, sizeof( tp ) );
715        if( sscanf( s, "%4d%2d%2dT%2d:%2d:%2d", &tp.tm_year, &tp.tm_mon, &tp.tm_mday,
716                                                &tp.tm_hour, &tp.tm_min, &tp.tm_sec ) != 6 )
717                return 0;
718       
719        tp.tm_year -= 1900;
720        tp.tm_mon --;
721       
722        return mktime_utc( &tp );
723}
724
725struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns )
726{
727        struct jabber_error *err;
728        struct xt_node *c;
729        char *s;
730       
731        if( node == NULL )
732                return NULL;
733       
734        err = g_new0( struct jabber_error, 1 );
735        err->type = xt_find_attr( node, "type" );
736       
737        for( c = node->children; c; c = c->next )
738        {
739                if( !( s = xt_find_attr( c, "xmlns" ) ) ||
740                    strcmp( s, xmlns ) != 0 )
741                        continue;
742               
743                if( strcmp( c->name, "text" ) != 0 )
744                {
745                        err->code = c->name;
746                }
747                /* Only use the text if it doesn't have an xml:lang attribute,
748                   if it's empty or if it's set to something English. */
749                else if( !( s = xt_find_attr( c, "xml:lang" ) ) ||
750                         !*s || strncmp( s, "en", 2 ) == 0 )
751                {
752                        err->text = c->text;
753                }
754        }
755       
756        return err;
757}
758
759void jabber_error_free( struct jabber_error *err )
760{
761        g_free( err );
762}
Note: See TracBrowser for help on using the repository browser.