source: trunk/libtransmission/bencode.c @ 4876

Last change on this file since 4876 was 4876, checked in by charles, 15 years ago

#667: remote crash exploit in bencode parser

  • Property svn:keywords set to Date Rev Author Id
File size: 20.4 KB
Line 
1/******************************************************************************
2 * $Id: bencode.c 4876 2008-01-31 02:24:43Z charles $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <assert.h>
26#include <ctype.h> /* isdigit, isprint */
27#include <errno.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <stdlib.h>
31
32#include <event.h>
33
34#include "transmission.h"
35#include "bencode.h"
36#include "ptrarray.h"
37#include "utils.h"
38
39/**
40***
41**/
42
43int
44tr_bencIsInt( const benc_val_t * val )
45{
46    return val!=NULL && val->type==TYPE_INT;
47}
48
49int
50tr_bencIsString( const benc_val_t * val )
51{
52    return val!=NULL && val->type==TYPE_STR;
53}
54
55int
56tr_bencIsList( const benc_val_t * val )
57{
58    return val!=NULL && val->type==TYPE_LIST;
59}
60
61int
62tr_bencIsDict( const benc_val_t * val )
63{
64    return val!=NULL && val->type==TYPE_DICT;
65}
66
67static int
68isContainer( const benc_val_t * val )
69{
70    return val!=NULL && ( val->type & ( TYPE_DICT | TYPE_LIST ) );
71}
72
73benc_val_t*
74tr_bencListGetNthChild( benc_val_t * val, int i )
75{
76    benc_val_t * ret = NULL;
77    if( tr_bencIsList( val ) && ( i >= 0 ) && ( i < val->val.l.count ) )
78        ret = val->val.l.vals + i;
79    return ret;
80}
81
82
83/**
84***
85**/
86
87/**
88 * The initial i and trailing e are beginning and ending delimiters.
89 * You can have negative numbers such as i-3e. You cannot prefix the
90 * number with a zero such as i04e. However, i0e is valid. 
91 * Example: i3e represents the integer "3"
92 * NOTE: The maximum number of bit of this integer is unspecified,
93 * but to handle it as a signed 64bit integer is mandatory to handle
94 * "large files" aka .torrent for more that 4Gbyte
95 */
96int
97tr_bencParseInt( const uint8_t  * buf,
98                 const uint8_t  * bufend,
99                 const uint8_t ** setme_end, 
100                 int64_t        * setme_val )
101{
102    int err = TR_OK;
103    char * endptr;
104    const void * begin;
105    const void * end;
106    int64_t val;
107
108    if( buf >= bufend )
109        return TR_ERROR;
110    if( *buf != 'i' )
111        return TR_ERROR;
112
113    begin = buf + 1;
114    end = memchr( begin, 'e', (bufend-buf)-1 );
115    if( end == NULL )
116        return TR_ERROR;
117
118    errno = 0;
119    val = strtoll( begin, &endptr, 10 );
120    if( errno || ( endptr != end ) ) /* incomplete parse */
121        err = TR_ERROR;
122    else if( val && *(const char*)begin=='0' ) /* the spec forbids leading zeroes */
123        err = TR_ERROR;
124    else {
125        *setme_end = end + 1;
126        *setme_val = val;
127    }
128
129    return err;
130}
131
132
133/**
134 * Byte strings are encoded as follows:
135 * <string length encoded in base ten ASCII>:<string data>
136 * Note that there is no constant beginning delimiter, and no ending delimiter.
137 * Example: 4:spam represents the string "spam"
138 */
139int
140tr_bencParseStr( const uint8_t  * buf,
141                 const uint8_t  * bufend,
142                 const uint8_t ** setme_end,
143                 uint8_t       ** setme_str,
144                 size_t         * setme_strlen )
145{
146    size_t len;
147    const void * end;
148    char * endptr;
149
150    if( buf >= bufend )
151        return TR_ERROR;
152
153    if( !isdigit( *buf  ) )
154        return TR_ERROR;
155
156    end = memchr( buf, ':', bufend-buf );
157    if( end == NULL )
158        return TR_ERROR;
159
160    errno = 0;
161    len = strtoul( (const char*)buf, &endptr, 10 );
162    if( errno || endptr!=end )
163        return TR_ERROR;
164
165    if( (const uint8_t*)end + 1 + len > bufend )
166        return TR_ERROR;
167
168    *setme_end = end + 1 + len;
169    *setme_str = (uint8_t*) tr_strndup( end + 1, len );
170    *setme_strlen = len;
171    return TR_OK;
172}
173
174/* setting to 1 to help expose bugs with tr_bencListAdd and tr_bencDictAdd */
175#define LIST_SIZE 8 /* number of items to increment list/dict buffer by */
176
177static int
178makeroom( benc_val_t * val, int count )
179{
180    assert( TYPE_LIST == val->type || TYPE_DICT == val->type );
181
182    if( val->val.l.count + count > val->val.l.alloc )
183    {
184        /* We need a bigger boat */
185        const int len = val->val.l.alloc + count +
186            ( count % LIST_SIZE ? LIST_SIZE - ( count % LIST_SIZE ) : 0 );
187        void * new = realloc( val->val.l.vals, len * sizeof( benc_val_t ) );
188        if( NULL == new )
189            return 1;
190
191        val->val.l.alloc = len;
192        val->val.l.vals  = new;
193    }
194
195    return 0;
196}
197
198static benc_val_t*
199getNode( benc_val_t * top, tr_ptrArray * parentStack, int type )
200{
201    benc_val_t * parent;
202
203    assert( top != NULL );
204    assert( parentStack != NULL );
205
206    if( tr_ptrArrayEmpty( parentStack ) )
207        return top;
208
209    parent = tr_ptrArrayBack( parentStack );
210    assert( parent != NULL );
211
212    /* dictionary keys must be strings */
213    if( ( parent->type == TYPE_DICT )
214        && ( type != TYPE_STR )
215        && ( ! ( parent->val.l.count % 2 ) ) )
216        return NULL;
217
218    makeroom( parent, 1 );
219    return parent->val.l.vals + parent->val.l.count++;
220}
221
222/**
223 * this function's awkward stack-based implementation
224 * is to prevent maliciously-crafed bencode data from
225 * smashing our stack through deep recursion. (#667)
226 */
227int
228tr_bencParse( const void  * buf_in,
229              const void  * bufend_in,
230              benc_val_t     * top,
231              const uint8_t ** setme_end )
232{
233    int err;
234    const uint8_t * buf = buf_in;
235    const uint8_t * bufend = bufend_in;
236    tr_ptrArray * parentStack = tr_ptrArrayNew( );
237
238    while( buf != bufend )
239    {
240        if( buf > bufend ) /* no more text to parse... */
241            return 1;
242
243        if( *buf=='i' ) /* int */
244        {
245            int64_t val;
246            const uint8_t * end;
247            int err;
248            benc_val_t * node;
249
250            if(( err = tr_bencParseInt( (const uint8_t*)buf, bufend, &end, &val )))
251                return err;
252
253            node = getNode( top, parentStack, TYPE_INT );
254            if( !node )
255                return TR_ERROR;
256
257            tr_bencInitInt( node, val );
258            buf = end;
259
260            if( tr_ptrArrayEmpty( parentStack ) )
261                break;
262        }
263        else if( *buf=='l' ) /* list */
264        {
265            benc_val_t * node = getNode( top, parentStack, TYPE_LIST );
266            if( !node )
267                return TR_ERROR;
268            tr_bencInit( node, TYPE_LIST );
269            tr_ptrArrayAppend( parentStack, node );
270            ++buf;
271        }
272        else if( *buf=='d' ) /* dict */
273        {
274            benc_val_t * node = getNode( top, parentStack, TYPE_DICT );
275            if( !node )
276                return TR_ERROR;
277            tr_bencInit( node, TYPE_DICT );
278            tr_ptrArrayAppend( parentStack, node );
279            ++buf;
280        }
281        else if( *buf=='e' ) /* end of list or dict */
282        {
283            ++buf;
284            if( tr_ptrArrayEmpty( parentStack ) )
285                return TR_ERROR;
286            tr_ptrArrayPop( parentStack );
287            if( tr_ptrArrayEmpty( parentStack ) )
288                break;
289        }
290        else if( isdigit(*buf) ) /* string? */
291        {
292            const uint8_t * end;
293            uint8_t * str;
294            size_t strlen;
295            int err;
296            benc_val_t * node;
297
298            if(( err = tr_bencParseStr( buf, bufend, &end, &str, &strlen )))
299                return err;
300
301            node = getNode( top, parentStack, TYPE_STR );
302            if( !node )
303                return TR_ERROR;
304
305            tr_bencInitStr( node, str, strlen, 0 );
306            buf = end;
307
308            if( tr_ptrArrayEmpty( parentStack ) )
309                break;
310        }
311        else /* invalid bencoded text... march past it */
312        {
313            ++buf;
314        }
315    }
316
317    err = tr_ptrArrayEmpty( parentStack ) ? 0 : 1;
318
319    if( !err && ( setme_end != NULL ) )
320        *setme_end = buf;
321
322    tr_ptrArrayFree( parentStack, NULL );
323    return err;
324}
325
326int
327tr_bencLoad( const void  * buf_in,
328             int           buflen,
329             benc_val_t  * setme_benc,
330             char       ** setme_end )
331{
332    const uint8_t * buf = buf_in;
333    const uint8_t * end;
334    const int ret = tr_bencParse( buf, buf+buflen, setme_benc, &end );
335    if( !ret && setme_end )
336        *setme_end = (char*) end;
337    return ret;
338}
339
340benc_val_t *
341tr_bencDictFind( benc_val_t * val, const char * key )
342{
343    int len, ii;
344
345    if( val->type != TYPE_DICT )
346    {
347        return NULL;
348    }
349
350    len = strlen( key );
351   
352    for( ii = 0; ii + 1 < val->val.l.count; ii += 2 )
353    {
354        if( TYPE_STR  != val->val.l.vals[ii].type ||
355            len       != val->val.l.vals[ii].val.s.i ||
356            0 != memcmp( val->val.l.vals[ii].val.s.s, key, len ) )
357        {
358            continue;
359        }
360        return &val->val.l.vals[ii+1];
361    }
362
363    return NULL;
364}
365
366benc_val_t*
367tr_bencDictFindType( benc_val_t * val, const char * key, int type )
368{
369    benc_val_t * ret = tr_bencDictFind( val, key );
370    return ret && ret->type == type ? ret : NULL;
371}
372
373int64_t
374tr_bencGetInt ( const benc_val_t * val )
375{
376    assert( tr_bencIsInt( val ) );
377    return val->val.i;
378}
379
380benc_val_t *
381tr_bencDictFindFirst( benc_val_t * val, ... )
382{
383    const char * key;
384    benc_val_t * ret;
385    va_list      ap;
386
387    ret = NULL;
388    va_start( ap, val );
389    while( ( key = va_arg( ap, const char * ) ) )
390    {
391        ret = tr_bencDictFind( val, key );
392        if( NULL != ret )
393        {
394            break;
395        }
396    }
397    va_end( ap );
398
399    return ret;
400}
401
402char *
403tr_bencStealStr( benc_val_t * val )
404{
405    assert( TYPE_STR == val->type );
406    val->val.s.nofree = 1;
407    return val->val.s.s;
408}
409
410void
411_tr_bencInitStr( benc_val_t * val, char * str, int len, int nofree )
412{
413    tr_bencInit( val, TYPE_STR );
414    val->val.s.s      = str;
415    val->val.s.nofree = nofree;
416    if( 0 >= len )
417    {
418        len = ( NULL == str ? 0 : strlen( str ) );
419    }
420    val->val.s.i = len;
421}
422
423int
424tr_bencInitStrDup( benc_val_t * val, const char * str )
425{
426    char * newStr = tr_strdup( str );
427    if( newStr == NULL )
428        return 1;
429
430    _tr_bencInitStr( val, newStr, 0, 0 );
431    return 0;
432}
433
434void
435tr_bencInitInt( benc_val_t * val, int64_t num )
436{
437    tr_bencInit( val, TYPE_INT );
438    val->val.i = num;
439}
440
441int
442tr_bencListReserve( benc_val_t * val, int count )
443{
444    assert( TYPE_LIST == val->type );
445
446    return makeroom( val, count );
447}
448
449int
450tr_bencDictReserve( benc_val_t * val, int count )
451{
452    assert( TYPE_DICT == val->type );
453
454    return makeroom( val, count * 2 );
455}
456
457benc_val_t *
458tr_bencListAdd( benc_val_t * list )
459{
460    benc_val_t * item;
461
462    assert( tr_bencIsList( list ) );
463    assert( list->val.l.count < list->val.l.alloc );
464
465    item = &list->val.l.vals[list->val.l.count];
466    list->val.l.count++;
467    tr_bencInit( item, TYPE_INT );
468
469    return item;
470}
471
472benc_val_t *
473tr_bencDictAdd( benc_val_t * dict, const char * key )
474{
475    benc_val_t * keyval, * itemval;
476
477    assert( tr_bencIsDict( dict ) );
478    assert( dict->val.l.count + 2 <= dict->val.l.alloc );
479
480    keyval = dict->val.l.vals + dict->val.l.count++;
481    tr_bencInitStr( keyval, (char*)key, -1, 1 );
482
483    itemval = dict->val.l.vals + dict->val.l.count++;
484    tr_bencInit( itemval, TYPE_INT );
485
486    return itemval;
487}
488
489
490/***
491****  BENC WALKING
492***/
493
494struct KeyIndex
495{
496    const char * key;
497    int index;
498};
499
500static int
501compareKeyIndex( const void * va, const void * vb )
502{
503    const struct KeyIndex * a = va;
504    const struct KeyIndex * b = vb;
505    return strcmp( a->key, b->key );
506}
507
508struct SaveNode
509{
510    const benc_val_t * val;
511    int valIsSaved;
512    int childCount;
513    int childIndex;
514    int * children;
515};
516
517static struct SaveNode*
518nodeNewDict( const benc_val_t * val )
519{
520    int i, j, n;
521    struct SaveNode * node;
522    struct KeyIndex * indices;
523
524    assert( tr_bencIsDict( val ) );
525
526    n = val->val.l.count;
527    node = tr_new0( struct SaveNode, 1 );
528    node->val = val;
529    node->children = tr_new0( int, n );
530
531    /* ugh, a dictionary's children have to be sorted by key... */
532    indices = tr_new( struct KeyIndex, n );
533    for( i=j=0; i<n; i+=2, ++j ) {
534        indices[j].key = val->val.l.vals[i].val.s.s;
535        indices[j].index = i;
536    }
537    qsort( indices, j, sizeof(struct KeyIndex), compareKeyIndex );
538    for( i=0; i<j; ++i ) {
539        const int index = indices[i].index;
540        node->children[ node->childCount++ ] = index;
541        node->children[ node->childCount++ ] = index + 1;
542    }
543
544    assert( node->childCount == n );
545    tr_free( indices );
546    return node;
547}
548
549static struct SaveNode*
550nodeNewList( const benc_val_t * val )
551{
552    int i, n;
553    struct SaveNode * node;
554
555    assert( tr_bencIsList( val ) );
556
557    n = val->val.l.count;
558    node = tr_new0( struct SaveNode, 1 );
559    node->val = val;
560    node->childCount = n;
561    node->children = tr_new0( int, n );
562    for( i=0; i<n; ++i ) /* a list's children don't need to be reordered */
563        node->children[i] = i;
564
565    return node;
566}
567
568static struct SaveNode*
569nodeNewSimple( const benc_val_t * val )
570{
571    struct SaveNode * node;
572
573    assert( !isContainer( val ) );
574
575    node = tr_new0( struct SaveNode, 1 );
576    node->val = val;
577    return node;
578}
579
580static struct SaveNode*
581nodeNew( const benc_val_t * val )
582{
583    switch( val->type )
584    {
585        case TYPE_INT:
586        case TYPE_STR:
587            return nodeNewSimple( val );
588            break;
589        case TYPE_LIST:
590            return nodeNewList( val );
591            break;
592        case TYPE_DICT:
593            return nodeNewDict( val );
594            break;
595    }
596
597    assert( 0 && "invalid type!" );
598    return NULL;
599}
600
601typedef void (*BencNodeWalkFunc)( const benc_val_t * val, void * user_data );
602
603struct WalkFuncs
604{
605    BencNodeWalkFunc intFunc;
606    BencNodeWalkFunc stringFunc;
607    BencNodeWalkFunc dictBeginFunc;
608    BencNodeWalkFunc listBeginFunc;
609    BencNodeWalkFunc containerEndFunc;
610};
611
612/**
613 * this function's awkward stack-based implementation
614 * is to prevent maliciously-crafed bencode data from
615 * smashing our stack through deep recursion. (#667)
616 */
617static void
618depthFirstWalk( const benc_val_t * top, struct WalkFuncs * walkFuncs, void * user_data )
619{
620    tr_ptrArray * stack = tr_ptrArrayNew( );
621    tr_ptrArrayAppend( stack, nodeNew( top ) );
622
623    while( !tr_ptrArrayEmpty( stack ) )
624    {
625        struct SaveNode * node = tr_ptrArrayBack( stack );
626        const benc_val_t * val;
627
628        if( !node->valIsSaved )
629        {
630            val = node->val;
631            node->valIsSaved = TRUE;
632        }
633        else if( node->childIndex < node->childCount )
634        {
635            const int index = node->children[ node->childIndex++ ];
636            val = node->val->val.l.vals +  index;
637        }
638        else /* done with this node */
639        {
640            if( isContainer( node->val ) )
641                walkFuncs->containerEndFunc( node->val, user_data );
642            tr_ptrArrayPop( stack );
643            tr_free( node->children );
644            tr_free( node );
645            continue;
646        }
647
648        switch( val->type )
649        {
650            case TYPE_INT:
651                walkFuncs->intFunc( val, user_data );
652                break;
653
654            case TYPE_STR:
655                walkFuncs->stringFunc( val, user_data );
656                break;
657
658            case TYPE_LIST:
659                if( val != node->val )
660                    tr_ptrArrayAppend( stack, nodeNew( val ) );
661                else
662                    walkFuncs->listBeginFunc( val, user_data );
663                break;
664
665            case TYPE_DICT:
666                if( val != node->val )
667                    tr_ptrArrayAppend( stack, nodeNew( val ) );
668                else
669                    walkFuncs->dictBeginFunc( val, user_data );
670                break;
671
672            default:
673                assert( 0 && "invalid type!" );
674        }
675    }
676
677    tr_ptrArrayFree( stack, NULL );
678}
679
680/****
681*****
682****/
683
684static void
685saveIntFunc( const benc_val_t * val, void * buf )
686{
687    evbuffer_add_printf( buf, "i%"PRId64"e", tr_bencGetInt(val) );
688}
689static void
690saveStringFunc( const benc_val_t * val, void * user_data )
691{
692    struct evbuffer * out = user_data;
693    evbuffer_add_printf( out, "%i:", val->val.s.i );
694    evbuffer_add( out, val->val.s.s, val->val.s.i );
695}
696static void
697saveDictBeginFunc( const benc_val_t * val UNUSED, void * buf )
698{
699    evbuffer_add_printf( buf, "d" );
700}
701static void
702saveListBeginFunc( const benc_val_t * val UNUSED, void * buf )
703{
704    evbuffer_add_printf( buf, "l" );
705}
706static void
707saveContainerEndFunc( const benc_val_t * val UNUSED, void * buf )
708{
709    evbuffer_add_printf( buf, "e" );
710}
711char*
712tr_bencSave( const benc_val_t * top, int * len )
713{
714    char * ret;
715    struct WalkFuncs walkFuncs;
716    struct evbuffer * out = evbuffer_new( );
717
718    walkFuncs.intFunc = saveIntFunc;
719    walkFuncs.stringFunc = saveStringFunc;
720    walkFuncs.dictBeginFunc = saveDictBeginFunc;
721    walkFuncs.listBeginFunc = saveListBeginFunc;
722    walkFuncs.containerEndFunc = saveContainerEndFunc;
723
724    depthFirstWalk( top, &walkFuncs, out );
725   
726    if( len != NULL )
727        *len = EVBUFFER_LENGTH( out );
728    ret = tr_strndup( (char*) EVBUFFER_DATA( out ), EVBUFFER_LENGTH( out ) );
729    evbuffer_free( out );
730    return ret;
731}
732
733/***
734****
735***/
736
737static void
738freeDummyFunc( const benc_val_t * val UNUSED, void * buf UNUSED  )
739{
740}
741static void
742freeStringFunc( const benc_val_t * val, void * freeme )
743{
744    if( !val->val.s.nofree )
745        tr_ptrArrayAppend( freeme, val->val.s.s );
746}
747static void
748freeContainerBeginFunc( const benc_val_t * val, void * freeme )
749{
750    tr_ptrArrayAppend( freeme, val->val.l.vals );
751}
752void
753tr_bencFree( benc_val_t * val )
754{
755    if( val != NULL )
756    {
757        tr_ptrArray * freeme = tr_ptrArrayNew( );
758        struct WalkFuncs walkFuncs;
759
760        walkFuncs.intFunc = freeDummyFunc;
761        walkFuncs.stringFunc = freeStringFunc;
762        walkFuncs.dictBeginFunc = freeContainerBeginFunc;
763        walkFuncs.listBeginFunc = freeContainerBeginFunc;
764        walkFuncs.containerEndFunc = freeDummyFunc;
765        depthFirstWalk( val, &walkFuncs, freeme );
766
767        tr_ptrArrayFree( freeme, tr_free );
768    }
769}
770
771/***
772****
773***/
774
775struct WalkPrint
776{
777    int depth;
778    FILE * out;
779};
780static void
781printLeadingSpaces( struct WalkPrint * data )
782{
783    const int width = data->depth * 2;
784    fprintf( data->out, "%*.*s", width, width, " " );
785}
786static void
787printIntFunc( const benc_val_t * val, void * vdata )
788{
789    struct WalkPrint * data = vdata;
790    printLeadingSpaces( data );
791    fprintf( data->out, "int:  %"PRId64"\n", tr_bencGetInt(val) );
792}
793static void
794printStringFunc( const benc_val_t * val, void * vdata )
795{
796    int ii;
797    struct WalkPrint * data = vdata;
798    printLeadingSpaces( data );
799    for( ii = 0; val->val.s.i > ii; ii++ )
800    {
801        if( '\\' == val->val.s.s[ii] ) {
802            putc( '\\', data->out );
803            putc( '\\', data->out );
804        } else if( isprint( val->val.s.s[ii] ) ) {
805            putc( val->val.s.s[ii], data->out );
806        } else {
807            fprintf( data->out, "\\x%02x", val->val.s.s[ii] );
808        }
809    }
810}
811static void
812printListBeginFunc( const benc_val_t * val UNUSED, void * vdata )
813{
814    struct WalkPrint * data = vdata;
815    printLeadingSpaces( data );
816    fprintf( data->out, "list\n" );
817    ++data->depth;
818}
819static void
820printDictBeginFunc( const benc_val_t * val UNUSED, void * vdata )
821{
822    struct WalkPrint * data = vdata;
823    printLeadingSpaces( data );
824    fprintf( data->out, "dict\n" );
825    ++data->depth;
826}
827static void
828printContainerEndFunc( const benc_val_t * val UNUSED, void * vdata )
829{
830    struct WalkPrint * data = vdata;
831    --data->depth;
832}
833void
834tr_bencPrint( benc_val_t * val )
835{
836    struct WalkFuncs walkFuncs;
837    struct WalkPrint walkPrint;
838
839    walkFuncs.intFunc = printIntFunc;
840    walkFuncs.stringFunc = printStringFunc;
841    walkFuncs.dictBeginFunc = printDictBeginFunc;
842    walkFuncs.listBeginFunc = printListBeginFunc;
843    walkFuncs.containerEndFunc = printContainerEndFunc;
844
845    walkPrint.out = stderr;
846    walkPrint.depth = 0;
847    depthFirstWalk( val, &walkFuncs, &walkPrint );
848}
Note: See TracBrowser for help on using the repository browser.