source: lib/xmltree.c @ eabe6d4

Last change on this file since eabe6d4 was 9ead105, checked in by Wilmer van der Gaast <wilmer@…>, at 2014-10-17T22:37:41Z

Bunch of merges from dx.

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