source: lib/xmltree.c @ c8eeadd

Last change on this file since c8eeadd was 3742fb6, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-05-11T23:27:11Z

Implement some kind of ignorant awareness of XML namespaces: Enough to not
break backward compatibility (hopefully) but be able to pick up inappropriate
uses of XML namespace prefixes. Main reason for this change: Fix XMPP typing
notification compatibility with GMail.

  • Property mode set to 100644
File size: 15.3 KB
RevLine 
[8f243ad]1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Simple XML (stream) parse tree handling code (Jabber/XMPP, mainly)       *
5*                                                                           *
6*  Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net>                   *
7*                                                                           *
8*  This library is free software; you can redistribute it and/or            *
9*  modify it under the terms of the GNU Lesser General Public               *
10*  License as published by the Free Software Foundation, version            *
11*  2.1.                                                                     *
12*                                                                           *
13*  This library 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 GNU        *
16*  Lesser General Public License for more details.                          *
17*                                                                           *
18*  You should have received a copy of the GNU Lesser General Public License *
19*  along with this library; if not, write to the Free Software Foundation,  *
20*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
21*                                                                           *
22****************************************************************************/
23
24#include <glib.h>
25#include <string.h>
26#include <unistd.h>
27#include <ctype.h>
28#include <stdio.h>
29
30#include "xmltree.h"
31
32static void xt_start_element( GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer data, GError **error )
33{
34        struct xt_parser *xt = data;
35        struct xt_node *node = g_new0( struct xt_node, 1 ), *nt;
36        int i;
37       
38        node->parent = xt->cur;
39        node->name = g_strdup( element_name );
40       
41        /* First count the number of attributes */
42        for( i = 0; attr_names[i]; i ++ );
43       
44        /* Then allocate a NULL-terminated array. */
45        node->attr = g_new0( struct xt_attr, i + 1 );
46       
47        /* And fill it, saving one variable by starting at the end. */
48        for( i --; i >= 0; i -- )
49        {
50                node->attr[i].key = g_strdup( attr_names[i] );
51                node->attr[i].value = g_strdup( attr_values[i] );
52        }
53       
54        /* Add it to the linked list of children nodes, if we have a current
55           node yet. */
56        if( xt->cur )
57        {
58                if( xt->cur->children )
59                {
60                        for( nt = xt->cur->children; nt->next; nt = nt->next );
61                        nt->next = node;
62                }
63                else
64                {
65                        xt->cur->children = node;
66                }
67        }
68        else if( xt->root )
69        {
70                /* ERROR situation: A second root-element??? */
71        }
72       
73        /* Now this node will be the new current node. */
74        xt->cur = node;
75        /* And maybe this is the root? */
76        if( xt->root == NULL )
77                xt->root = node;
78}
79
80static void xt_text( GMarkupParseContext *ctx, const gchar *text, gsize text_len, gpointer data, GError **error )
81{
82        struct xt_parser *xt = data;
83        struct xt_node *node = xt->cur;
84       
85        if( node == NULL )
86                return;
87       
88        /* FIXME: Does g_renew also OFFICIALLY accept NULL arguments? */
89        node->text = g_renew( char, node->text, node->text_len + text_len + 1 );
90        memcpy( node->text + node->text_len, text, text_len );
91        node->text_len += text_len;
92        /* Zero termination is always nice to have. */
93        node->text[node->text_len] = 0;
94}
95
96static void xt_end_element( GMarkupParseContext *ctx, const gchar *element_name, gpointer data, GError **error )
97{
98        struct xt_parser *xt = data;
99       
100        xt->cur->flags |= XT_COMPLETE;
101        xt->cur = xt->cur->parent;
102}
103
104GMarkupParser xt_parser_funcs =
105{
106        xt_start_element,
107        xt_end_element,
108        xt_text,
109        NULL,
110        NULL
111};
112
[4bbcba3]113struct xt_parser *xt_new( const struct xt_handler_entry *handlers, gpointer data )
[8f243ad]114{
115        struct xt_parser *xt = g_new0( struct xt_parser, 1 );
116       
117        xt->data = data;
[4bbcba3]118        xt->handlers = handlers;
[8f243ad]119        xt_reset( xt );
120       
121        return xt;
122}
123
124/* Reset the parser, flush everything we have so far. For example, we need
125   this for XMPP when doing TLS/SASL to restart the stream. */
126void xt_reset( struct xt_parser *xt )
127{
128        if( xt->parser )
129                g_markup_parse_context_free( xt->parser );
130       
131        xt->parser = g_markup_parse_context_new( &xt_parser_funcs, 0, xt, NULL );
132       
133        if( xt->root )
134        {
135                xt_free_node( xt->root );
136                xt->root = NULL;
137                xt->cur = NULL;
138        }
139}
140
141/* Feed the parser, don't execute any handler. Returns -1 on errors, 0 on
142   end-of-stream and 1 otherwise. */
143int xt_feed( struct xt_parser *xt, char *text, int text_len )
144{
145        if( !g_markup_parse_context_parse( xt->parser, text, text_len, &xt->gerr ) )
146        {
147                return -1;
148        }
149       
150        return !( xt->root && xt->root->flags & XT_COMPLETE );
151}
152
153/* Find completed nodes and see if a handler has to be called. Passing
154   a node isn't necessary if you want to start at the root, just pass
[101d84f]155   NULL. This second argument is needed for recursive calls. */
156int xt_handle( struct xt_parser *xt, struct xt_node *node, int depth )
[8f243ad]157{
158        struct xt_node *c;
159        xt_status st;
160        int i;
161       
[101d84f]162        /* Just in case someone likes infinite loops... */
163        if( xt->root == NULL )
164                return 0;
165       
[8f243ad]166        if( node == NULL )
[101d84f]167                return xt_handle( xt, xt->root, depth );
[8f243ad]168       
[101d84f]169        if( depth != 0 )
170                for( c = node->children; c; c = c->next )
171                        if( !xt_handle( xt, c, depth > 0 ? depth - 1 : depth ) )
172                                return 0;
[8f243ad]173       
174        if( node->flags & XT_COMPLETE && !( node->flags & XT_SEEN ) )
175        {
176                for( i = 0; xt->handlers[i].func; i ++ )
177                {
178                        /* This one is fun! \o/ */
179                       
180                                                /* If handler.name == NULL it means it should always match. */
181                        if( ( xt->handlers[i].name == NULL || 
182                                                /* If it's not, compare. There should always be a name. */
183                              g_strcasecmp( xt->handlers[i].name, node->name ) == 0 ) &&
184                                                /* If handler.parent == NULL, it's a match. */
185                            ( xt->handlers[i].parent == NULL ||
186                                                /* If there's a parent node, see if the name matches. */
187                              ( node->parent ? g_strcasecmp( xt->handlers[i].parent, node->parent->name ) == 0 : 
188                                                /* If there's no parent, the handler should mention <root> as a parent. */
189                                               g_strcasecmp( xt->handlers[i].parent, "<root>" ) == 0 ) ) )
190                        {
191                                st = xt->handlers[i].func( node, xt->data );
192                               
193                                if( st == XT_ABORT )
194                                        return 0;
195                                else if( st != XT_NEXT )
196                                        break;
197                        }
198                }
199               
200                node->flags |= XT_SEEN;
201        }
202       
203        return 1;
204}
205
206/* Garbage collection: Cleans up all nodes that are handled. Useful for
207   streams because there's no reason to keep a complete packet history
208   in memory. */
[101d84f]209void xt_cleanup( struct xt_parser *xt, struct xt_node *node, int depth )
[8f243ad]210{
211        struct xt_node *c, *prev;
212       
[21167d2]213        if( !xt || !xt->root )
214                return;
215       
[8f243ad]216        if( node == NULL )
[101d84f]217                return xt_cleanup( xt, xt->root, depth );
[8f243ad]218       
219        if( node->flags & XT_SEEN && node == xt->root )
220        {
221                xt_free_node( xt->root );
222                xt->root = xt->cur = NULL;
223                /* xt->cur should be NULL already, BTW... */
224               
225                return;
226        }
227       
228        /* c contains the current node, prev the previous node (or NULL).
229           I admit, this one's pretty horrible. */
230        for( c = node->children, prev = NULL; c; prev = c, c = c ? c->next : node->children )
231        {
232                if( c->flags & XT_SEEN )
233                {
234                        /* Remove the node from the linked list. */
235                        if( prev )
236                                prev->next = c->next;
237                        else
238                                node->children = c->next;
239                       
240                        xt_free_node( c );
241                       
242                        /* Since the for loop wants to get c->next, make sure
243                           c points at something that exists (and that c->next
244                           will actually be the next item we should check). c
245                           can be NULL now, if we just removed the first item.
246                           That explains the ? thing in for(). */
247                        c = prev;
248                }
249                else
250                {
251                        /* This node can't be cleaned up yet, but maybe a
252                           subnode can. */
[101d84f]253                        if( depth != 0 )
254                                xt_cleanup( xt, c, depth > 0 ? depth - 1 : depth );
[8f243ad]255                }
256        }
257}
258
259static void xt_to_string_real( struct xt_node *node, GString *str )
260{
261        char *buf;
262        struct xt_node *c;
263        int i;
264       
265        g_string_append_printf( str, "<%s", node->name );
266       
267        for( i = 0; node->attr[i].key; i ++ )
268        {
269                buf = g_markup_printf_escaped( " %s=\"%s\"", node->attr[i].key, node->attr[i].value );
270                g_string_append( str, buf );
271                g_free( buf );
272        }
273       
274        if( node->text == NULL && node->children == NULL )
275        {
276                g_string_append( str, "/>" );
277                return;
278        }
279       
280        g_string_append( str, ">" );
281        if( node->text_len > 0 )
282        {
283                buf = g_markup_escape_text( node->text, node->text_len );
284                g_string_append( str, buf );
285                g_free( buf );
286        }
287       
288        for( c = node->children; c; c = c->next )
289                xt_to_string_real( c, str );
290       
291        g_string_append_printf( str, "</%s>", node->name );
292}
293
294char *xt_to_string( struct xt_node *node )
295{
296        GString *ret;
297        char *real;
298       
299        ret = g_string_new( "" );
300        xt_to_string_real( node, ret );
301       
302        real = ret->str;
303        g_string_free( ret, FALSE );
304       
305        return real;
306}
307
[1bf1ae6]308#ifdef DEBUG
[8f243ad]309void xt_print( struct xt_node *node )
310{
311        int i;
312        struct xt_node *c;
313       
314        /* Indentation */
315        for( c = node; c->parent; c = c->parent )
316                printf( "\t" );
317       
318        /* Start the tag */
319        printf( "<%s", node->name );
320       
321        /* Print the attributes */
322        for( i = 0; node->attr[i].key; i ++ )
323                printf( " %s=\"%s\"", node->attr[i].key, g_markup_escape_text( node->attr[i].value, -1 ) );
324       
325        /* /> in case there's really *nothing* inside this tag, otherwise
326           just >. */
327        /* If this tag doesn't have any content at all... */
328        if( node->text == NULL && node->children == NULL )
329        {
330                printf( "/>\n" );
331                return;
332                /* Then we're finished! */
333        }
334       
335        /* Otherwise... */
336        printf( ">" );
337       
338        /* Only print the text if it contains more than whitespace (TEST). */
339        if( node->text_len > 0 )
340        {
341                for( i = 0; node->text[i] && isspace( node->text[i] ); i ++ );
342                if( node->text[i] )
343                        printf( "%s", g_markup_escape_text( node->text, -1 ) );
344        }
345       
346        if( node->children )
347                printf( "\n" );
348       
349        for( c = node->children; c; c = c->next )
350                xt_print( c );
351       
352        if( node->children )
353                for( c = node; c->parent; c = c->parent )
354                        printf( "\t" );
355       
356        /* Non-empty tag is now finished. */
357        printf( "</%s>\n", node->name );
358}
[1bf1ae6]359#endif
[8f243ad]360
[022df46]361struct xt_node *xt_dup( struct xt_node *node )
362{
363        struct xt_node *dup = g_new0( struct xt_node, 1 );
364        struct xt_node *c, *dc = NULL;
365        int i;
366       
367        /* Let's NOT copy the parent element here BTW! Only do it for children. */
368       
369        dup->name = g_strdup( node->name );
370        dup->flags = node->flags;
371        if( node->text )
372        {
373                dup->text = g_memdup( node->text, node->text_len + 1 );
374                dup->text_len = node->text_len;
375        }
376       
377        /* Count the number of attributes and allocate the new array. */
378        for( i = 0; node->attr[i].key; i ++ );
379        dup->attr = g_new0( struct xt_attr, i + 1 );
380       
381        /* Copy them all! */
382        for( i --; i >= 0; i -- )
383        {
384                dup->attr[i].key = g_strdup( node->attr[i].key );
385                dup->attr[i].value = g_strdup( node->attr[i].value );
386        }
387       
388        /* This nice mysterious loop takes care of the children. */
389        for( c = node->children; c; c = c->next )
390        {
391                if( dc == NULL )
392                        dc = dup->children = xt_dup( c );
393                else
394                        dc = ( dc->next = xt_dup( c ) );
395               
396                dc->parent = dup;
397        }
398       
399        return dup;
400}
401
[8f243ad]402/* Frees a node. This doesn't clean up references to itself from parents! */
403void xt_free_node( struct xt_node *node )
404{
405        int i;
406       
[21167d2]407        if( !node )
408                return;
409       
[8f243ad]410        g_free( node->name );
411        g_free( node->text );
412       
413        for( i = 0; node->attr[i].key; i ++ )
414        {
415                g_free( node->attr[i].key );
416                g_free( node->attr[i].value );
417        }
418        g_free( node->attr );
419       
420        while( node->children )
421        {
422                struct xt_node *next = node->children->next;
423               
424                xt_free_node( node->children );
425                node->children = next;
426        }
427       
428        g_free( node );
429}
430
431void xt_free( struct xt_parser *xt )
432{
[21167d2]433        if( !xt )
434                return;
435       
[8f243ad]436        if( xt->root )
437                xt_free_node( xt->root );
438       
439        g_markup_parse_context_free( xt->parser );
440       
441        g_free( xt );
442}
443
444/* To find a node's child with a specific name, pass the node's children
445   list, not the node itself! The reason you have to do this by hand: So
446   that you can also use this function as a find-next. */
[40ef702]447struct xt_node *xt_find_node( struct xt_node *node, const char *name )
[8f243ad]448{
449        while( node )
450        {
[3742fb6]451                char *colon;
452               
453                if( g_strcasecmp( node->name, name ) == 0 ||
454                    ( ( colon = strchr( node->name, ':' ) ) &&
455                      g_strcasecmp( colon + 1, name ) == 0 ) )
[8f243ad]456                        break;
457               
458                node = node->next;
459        }
460       
461        return node;
462}
463
[40ef702]464char *xt_find_attr( struct xt_node *node, const char *key )
[8f243ad]465{
466        int i;
[3742fb6]467        char *colon;
[8f243ad]468       
[21167d2]469        if( !node )
470                return NULL;
471       
[8f243ad]472        for( i = 0; node->attr[i].key; i ++ )
473                if( g_strcasecmp( node->attr[i].key, key ) == 0 )
474                        break;
475       
[3742fb6]476        /* This is an awful hack that only takes care of namespace prefixes
477           inside a tag. Since IMHO excessive namespace usage in XMPP is
478           massive overkill anyway (this code exists for almost four years
479           now and never really missed it): Meh. */
480        if( !node->attr[i].key && strcmp( key, "xmlns" ) == 0 &&
481            ( colon = strchr( node->name, ':' ) ) )
482        {
483                *colon = '\0';
484                for( i = 0; node->attr[i].key; i ++ )
485                        if( strncmp( node->attr[i].key, "xmlns:", 6 ) == 0 &&
486                            strcmp( node->attr[i].key + 6, node->name ) == 0 )
487                                break;
488                *colon = ':';
489        }
490       
[8f243ad]491        return node->attr[i].value;
492}
493
[94acdd0]494struct xt_node *xt_new_node( char *name, const char *text, struct xt_node *children )
[8f243ad]495{
496        struct xt_node *node, *c;
497       
498        node = g_new0( struct xt_node, 1 );
499        node->name = g_strdup( name );
500        node->children = children;
501        node->attr = g_new0( struct xt_attr, 1 );
502       
503        if( text )
504        {
505                node->text_len = strlen( text );
[4ecdc69]506                node->text = g_memdup( text, node->text_len + 1 );
[8f243ad]507        }
508       
509        for( c = children; c; c = c->next )
510        {
511                if( c->parent != NULL )
512                {
513                        /* ERROR CONDITION: They seem to have a parent already??? */
514                }
515               
516                c->parent = node;
517        }
518       
519        return node;
520}
521
522void xt_add_child( struct xt_node *parent, struct xt_node *child )
523{
524        struct xt_node *node;
525       
526        /* This function can actually be used to add more than one child, so
527           do handle this properly. */
528        for( node = child; node; node = node->next )
529        {
530                if( node->parent != NULL )
531                {
532                        /* ERROR CONDITION: They seem to have a parent already??? */
533                }
534               
535                node->parent = parent;
536        }
537       
538        if( parent->children == NULL )
539        {
540                parent->children = child;
541        }
542        else
543        {
544                for( node = parent->children; node->next; node = node->next );
545                node->next = child;
546        }
547}
548
[40ef702]549void xt_add_attr( struct xt_node *node, const char *key, const char *value )
[8f243ad]550{
551        int i;
552       
[090f1cb]553        /* Now actually it'd be nice if we can also change existing attributes
554           (which actually means this function doesn't have the right name).
555           So let's find out if we have this attribute already... */
556        for( i = 0; node->attr[i].key; i ++ )
557                if( strcmp( node->attr[i].key, key ) == 0 )
558                        break;
559       
560        if( node->attr[i].key == NULL )
561        {
562                /* If not, allocate space for a new attribute. */
563                node->attr = g_renew( struct xt_attr, node->attr, i + 2 );
564                node->attr[i].key = g_strdup( key );
565                node->attr[i+1].key = NULL;
566        }
567        else
568        {
569                /* Otherwise, free the old value before setting the new one. */
570                g_free( node->attr[i].value );
571        }
572       
[8f243ad]573        node->attr[i].value = g_strdup( value );
574}
[259edd4]575
[40ef702]576int xt_remove_attr( struct xt_node *node, const char *key )
[259edd4]577{
578        int i, last;
579       
580        for( i = 0; node->attr[i].key; i ++ )
581                if( strcmp( node->attr[i].key, key ) == 0 )
582                        break;
583       
584        /* If we didn't find the attribute... */
585        if( node->attr[i].key == NULL )
586                return 0;
587       
588        g_free( node->attr[i].key );
589        g_free( node->attr[i].value );
590       
591        /* If it's the last, this is easy: */
592        if( node->attr[i+1].key == NULL )
593        {
594                node->attr[i].key = node->attr[i].value = NULL;
595        }
596        else /* It's also pretty easy, actually. */
597        {
598                /* Find the last item. */
599                for( last = i + 1; node->attr[last+1].key; last ++ );
600               
601                node->attr[i] = node->attr[last];
602                node->attr[last].key = NULL;
603                node->attr[last].value = NULL;
604        }
605       
606        /* Let's not bother with reallocating memory here. It takes time and
607           most packets don't stay in memory for long anyway. */
608       
609        return 1;
610}
Note: See TracBrowser for help on using the repository browser.