source: trunk/libtransmission/utils.c @ 11398

Last change on this file since 11398 was 11398, checked in by charles, 11 years ago

(trunk libT) add some new bugs to the code so that it will crash when vraa tries to use it

  • Property svn:keywords set to Date Rev Author Id
File size: 39.4 KB
Line 
1/*
2 * This file Copyright (C) 2009-2010 Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: utils.c 11398 2010-11-11 15:31:11Z charles $
11 */
12
13#ifdef HAVE_MEMMEM
14 #define _GNU_SOURCE /* glibc's string.h needs this to pick up memmem */
15#endif
16
17#if defined(SYS_DARWIN)
18 #define HAVE_GETPAGESIZE
19 #define HAVE_ICONV_OPEN
20 #define HAVE_VALLOC
21 #undef HAVE_POSIX_MEMALIGN /* not supported on OS X 10.5 and lower */
22#endif
23
24#include <assert.h>
25#include <ctype.h> /* isalpha(), tolower() */
26#include <errno.h>
27#include <float.h> /* DBL_EPSILON */
28#include <locale.h> /* localeconv() */
29#include <math.h> /* pow(), fabs(), floor() */
30#include <stdarg.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h> /* strerror(), memset(), memmem() */
34#include <time.h> /* nanosleep() */
35
36#ifdef HAVE_ICONV_OPEN
37 #include <iconv.h>
38#endif
39#include <libgen.h> /* basename() */
40#include <sys/time.h>
41#include <sys/types.h>
42#include <sys/stat.h>
43#include <unistd.h> /* stat(), getcwd(), getpagesize() */
44
45#include "event.h"
46
47#ifdef WIN32
48 #include <w32api.h>
49 #define WINVER WindowsXP /* freeaddrinfo(), getaddrinfo(), getnameinfo() */
50 #include <direct.h> /* _getcwd() */
51 #include <windows.h> /* Sleep() */
52#endif
53
54#include "transmission.h"
55#include "bencode.h"
56#include "fdlimit.h"
57#include "ConvertUTF.h"
58#include "list.h"
59#include "net.h"
60#include "utils.h"
61#include "platform.h"
62#include "version.h"
63
64
65time_t transmission_now = 0;
66
67tr_msg_level messageLevel = TR_MSG_ERR;
68static tr_bool        messageQueuing = FALSE;
69static tr_msg_list *  messageQueue = NULL;
70static tr_msg_list ** messageQueueTail = &messageQueue;
71static int            messageQueueCount = 0;
72
73#ifndef WIN32
74    /* make null versions of these win32 functions */
75    static inline int IsDebuggerPresent( void ) { return FALSE; }
76    static inline void OutputDebugString( const void * unused UNUSED ) { }
77#endif
78
79/***
80****
81***/
82
83static tr_lock*
84getMessageLock( void )
85{
86    static tr_lock * l = NULL;
87
88    if( !l )
89        l = tr_lockNew( );
90
91    return l;
92}
93
94FILE*
95tr_getLog( void )
96{
97    static tr_bool initialized = FALSE;
98    static FILE * file = NULL;
99
100    if( !initialized )
101    {
102        const char * str = getenv( "TR_DEBUG_FD" );
103        int          fd = 0;
104        if( str && *str )
105            fd = atoi( str );
106        switch( fd )
107        {
108            case 1:
109                file = stdout; break;
110
111            case 2:
112                file = stderr; break;
113
114            default:
115                file = NULL; break;
116        }
117        initialized = TRUE;
118    }
119
120    return file;
121}
122
123void
124tr_setMessageLevel( tr_msg_level level )
125{
126    messageLevel = level;
127}
128
129tr_msg_level
130tr_getMessageLevel( void )
131{
132    return messageLevel;
133}
134
135void
136tr_setMessageQueuing( tr_bool enabled )
137{
138    messageQueuing = enabled;
139}
140
141tr_bool
142tr_getMessageQueuing( void )
143{
144    return messageQueuing != 0;
145}
146
147tr_msg_list *
148tr_getQueuedMessages( void )
149{
150    tr_msg_list * ret;
151    tr_lockLock( getMessageLock( ) );
152
153    ret = messageQueue;
154    messageQueue = NULL;
155    messageQueueTail = &messageQueue;
156
157    messageQueueCount = 0;
158
159    tr_lockUnlock( getMessageLock( ) );
160    return ret;
161}
162
163void
164tr_freeMessageList( tr_msg_list * list )
165{
166    tr_msg_list * next;
167
168    while( NULL != list )
169    {
170        next = list->next;
171        free( list->message );
172        free( list->name );
173        free( list );
174        list = next;
175    }
176}
177
178/**
179***
180**/
181
182struct tm *
183tr_localtime_r( const time_t *_clock, struct tm *_result )
184{
185#ifdef HAVE_LOCALTIME_R
186    return localtime_r( _clock, _result );
187#else
188    struct tm *p = localtime( _clock );
189    if( p )
190        *(_result) = *p;
191    return p;
192#endif
193}
194
195char*
196tr_getLogTimeStr( char * buf, int buflen )
197{
198    char           tmp[64];
199    struct tm      now_tm;
200    struct timeval tv;
201    time_t         seconds;
202    int            milliseconds;
203
204    gettimeofday( &tv, NULL );
205
206    seconds = tv.tv_sec;
207    tr_localtime_r( &seconds, &now_tm );
208    strftime( tmp, sizeof( tmp ), "%H:%M:%S", &now_tm );
209    milliseconds = (int)( tv.tv_usec / 1000 );
210    tr_snprintf( buf, buflen, "%s.%03d", tmp, milliseconds );
211
212    return buf;
213}
214
215tr_bool
216tr_deepLoggingIsActive( void )
217{
218    static int8_t deepLoggingIsActive = -1;
219
220    if( deepLoggingIsActive < 0 )
221        deepLoggingIsActive = IsDebuggerPresent() || (tr_getLog()!=NULL);
222
223    return deepLoggingIsActive != 0;
224}
225
226void
227tr_deepLog( const char  * file,
228            int           line,
229            const char  * name,
230            const char  * fmt,
231            ... )
232{
233    FILE * fp = tr_getLog( );
234    if( fp || IsDebuggerPresent( ) )
235    {
236        va_list           args;
237        char              timestr[64];
238        struct evbuffer * buf = evbuffer_new( );
239        char *            base = tr_basename( file );
240
241        evbuffer_add_printf( buf, "[%s] ",
242                            tr_getLogTimeStr( timestr, sizeof( timestr ) ) );
243        if( name )
244            evbuffer_add_printf( buf, "%s ", name );
245        va_start( args, fmt );
246        evbuffer_add_vprintf( buf, fmt, args );
247        va_end( args );
248        evbuffer_add_printf( buf, " (%s:%d)\n", base, line );
249        /* FIXME(libevent2) ifdef this out for nonwindows platforms */
250        OutputDebugString( EVBUFFER_DATA( buf ) );
251        if(fp) /* FIXME(libevent2) tr_getLog() should return an fd, then use evbuffer_write() here ) */
252            (void) fwrite( EVBUFFER_DATA( buf ), 1, EVBUFFER_LENGTH( buf ), fp );
253
254        tr_free( base );
255        evbuffer_free( buf );
256    }
257}
258
259/***
260****
261***/
262
263void
264tr_msg( const char * file, int line,
265        tr_msg_level level,
266        const char * name,
267        const char * fmt, ... )
268{
269    const int err = errno; /* message logging shouldn't affect errno */
270    char buf[1024];
271    va_list ap;
272    tr_lockLock( getMessageLock( ) );
273
274    /* build the text message */
275    *buf = '\0';
276    va_start( ap, fmt );
277    evutil_vsnprintf( buf, sizeof( buf ), fmt, ap );
278    va_end( ap );
279
280    OutputDebugString( buf );
281
282    if( *buf )
283    {
284        if( messageQueuing )
285        {
286            tr_msg_list * newmsg;
287            newmsg = tr_new0( tr_msg_list, 1 );
288            newmsg->level = level;
289            newmsg->when = tr_time( );
290            newmsg->message = tr_strdup( buf );
291            newmsg->file = file;
292            newmsg->line = line;
293            newmsg->name = tr_strdup( name );
294
295            *messageQueueTail = newmsg;
296            messageQueueTail = &newmsg->next;
297            ++messageQueueCount;
298
299            if( messageQueueCount > TR_MAX_MSG_LOG )
300            {
301                tr_msg_list * old = messageQueue;
302                messageQueue = old->next;
303                old->next = NULL;
304                tr_freeMessageList(old);
305
306                --messageQueueCount;
307
308                assert( messageQueueCount == TR_MAX_MSG_LOG );
309            }
310        }
311        else
312        {
313            char timestr[64];
314            FILE * fp;
315
316            fp = tr_getLog( );
317            if( fp == NULL )
318                fp = stderr;
319
320            tr_getLogTimeStr( timestr, sizeof( timestr ) );
321
322            if( name )
323                fprintf( fp, "[%s] %s: %s\n", timestr, name, buf );
324            else
325                fprintf( fp, "[%s] %s\n", timestr, buf );
326            fflush( fp );
327        }
328    }
329
330    tr_lockUnlock( getMessageLock( ) );
331    errno = err;
332}
333
334/***
335****
336***/
337
338void*
339tr_malloc( size_t size )
340{
341    return size ? malloc( size ) : NULL;
342}
343
344void*
345tr_malloc0( size_t size )
346{
347    return size ? calloc( 1, size ) : NULL;
348}
349
350void
351tr_free( void * p )
352{
353    if( p != NULL )
354        free( p );
355}
356
357void*
358tr_memdup( const void * src, size_t byteCount )
359{
360    return memcpy( tr_malloc( byteCount ), src, byteCount );
361}
362
363/***
364****
365***/
366
367void
368tr_set_compare( const void * va,
369                size_t aCount,
370                const void * vb,
371                size_t bCount,
372                int compare( const void * a, const void * b ),
373                size_t elementSize,
374                tr_set_func in_a_cb,
375                tr_set_func in_b_cb,
376                tr_set_func in_both_cb,
377                void * userData )
378{
379    const uint8_t * a = (const uint8_t *) va;
380    const uint8_t * b = (const uint8_t *) vb;
381    const uint8_t * aend = a + elementSize * aCount;
382    const uint8_t * bend = b + elementSize * bCount;
383
384    while( a != aend || b != bend )
385    {
386        if( a == aend )
387        {
388            ( *in_b_cb )( (void*)b, userData );
389            b += elementSize;
390        }
391        else if( b == bend )
392        {
393            ( *in_a_cb )( (void*)a, userData );
394            a += elementSize;
395        }
396        else
397        {
398            const int val = ( *compare )( a, b );
399
400            if( !val )
401            {
402                ( *in_both_cb )( (void*)a, userData );
403                a += elementSize;
404                b += elementSize;
405            }
406            else if( val < 0 )
407            {
408                ( *in_a_cb )( (void*)a, userData );
409                a += elementSize;
410            }
411            else if( val > 0 )
412            {
413                ( *in_b_cb )( (void*)b, userData );
414                b += elementSize;
415            }
416        }
417    }
418}
419
420/***
421****
422***/
423
424const char*
425tr_strip_positional_args( const char* str )
426{
427    const char * in = str;
428    static size_t bufsize = 0;
429    static char * buf = NULL;
430    const size_t  len = strlen( str );
431    char *        out;
432
433    if( !buf || ( bufsize < len ) )
434    {
435        bufsize = len * 2 + 1;
436        buf = tr_renew( char, buf, bufsize );
437    }
438
439    for( out = buf; *str; ++str )
440    {
441        *out++ = *str;
442
443        if( ( *str == '%' ) && isdigit( str[1] ) )
444        {
445            const char * tmp = str + 1;
446            while( isdigit( *tmp ) )
447                ++tmp;
448            if( *tmp == '$' )
449                str = tmp[1]=='\'' ? tmp+1 : tmp;
450        }
451
452        if( ( *str == '%' ) && ( str[1] == '\'' ) )
453            str = str + 1;
454
455    }
456    *out = '\0';
457
458    return strcmp( buf, in ) ? buf : in;
459}
460
461/**
462***
463**/
464
465void
466tr_timerAdd( struct event * timer, int seconds, int microseconds )
467{
468    struct timeval tv;
469    tv.tv_sec = seconds;
470    tv.tv_usec = microseconds;
471
472    assert( tv.tv_sec >= 0 );
473    assert( tv.tv_usec >= 0 );
474    assert( tv.tv_usec < 1000000 );
475
476    evtimer_add( timer, &tv );
477}
478
479void
480tr_timerAddMsec( struct event * timer, int msec )
481{
482    const int seconds =  msec / 1000;
483    const int usec = (msec%1000) * 1000;
484    tr_timerAdd( timer, seconds, usec );
485}
486
487/**
488***
489**/
490
491uint8_t *
492tr_loadFile( const char * path,
493             size_t *     size )
494{
495    uint8_t * buf;
496    struct stat  sb;
497    int fd;
498    ssize_t n;
499    const char * const err_fmt = _( "Couldn't read \"%1$s\": %2$s" );
500
501    /* try to stat the file */
502    errno = 0;
503    if( stat( path, &sb ) )
504    {
505        const int err = errno;
506        tr_dbg( err_fmt, path, tr_strerror( errno ) );
507        errno = err;
508        return NULL;
509    }
510
511    if( ( sb.st_mode & S_IFMT ) != S_IFREG )
512    {
513        tr_err( err_fmt, path, _( "Not a regular file" ) );
514        errno = EISDIR;
515        return NULL;
516    }
517
518    /* Load the torrent file into our buffer */
519    fd = tr_open_file_for_scanning( path );
520    if( fd < 0 )
521    {
522        const int err = errno;
523        tr_err( err_fmt, path, tr_strerror( errno ) );
524        errno = err;
525        return NULL;
526    }
527    buf = malloc( sb.st_size + 1 );
528    if( !buf )
529    {
530        const int err = errno;
531        tr_err( err_fmt, path, _( "Memory allocation failed" ) );
532        tr_close_file( fd );
533        errno = err;
534        return NULL;
535    }
536    n = read( fd, buf, (size_t)sb.st_size );
537    if( n == -1 )
538    {
539        const int err = errno;
540        tr_err( err_fmt, path, tr_strerror( errno ) );
541        tr_close_file( fd );
542        free( buf );
543        errno = err;
544        return NULL;
545    }
546
547    tr_close_file( fd );
548    buf[ sb.st_size ] = '\0';
549    *size = sb.st_size;
550    return buf;
551}
552
553char*
554tr_basename( const char * path )
555{
556    char * tmp = tr_strdup( path );
557    char * ret = tr_strdup( basename( tmp ) );
558    tr_free( tmp );
559    return ret;
560}
561
562char*
563tr_dirname( const char * path )
564{
565    char * tmp = tr_strdup( path );
566    char * ret = tr_strdup( dirname( tmp ) );
567    tr_free( tmp );
568    return ret;
569}
570
571int
572tr_mkdir( const char * path,
573          int permissions
574#ifdef WIN32
575                       UNUSED
576#endif
577        )
578{
579#ifdef WIN32
580    if( path && isalpha( path[0] ) && path[1] == ':' && !path[2] )
581        return 0;
582    return mkdir( path );
583#else
584    return mkdir( path, permissions );
585#endif
586}
587
588int
589tr_mkdirp( const char * path_in,
590           int          permissions )
591{
592    char *      path = tr_strdup( path_in );
593    char *      p, * pp;
594    struct stat sb;
595    int         done;
596
597    /* walk past the root */
598    p = path;
599    while( *p == TR_PATH_DELIMITER )
600        ++p;
601
602    pp = p;
603    done = 0;
604    while( ( p =
605                strchr( pp, TR_PATH_DELIMITER ) ) || ( p = strchr( pp, '\0' ) ) )
606    {
607        if( !*p )
608            done = 1;
609        else
610            *p = '\0';
611
612        if( stat( path, &sb ) )
613        {
614            /* Folder doesn't exist yet */
615            if( tr_mkdir( path, permissions ) )
616            {
617                const int err = errno;
618                tr_err( _(
619                           "Couldn't create \"%1$s\": %2$s" ), path,
620                       tr_strerror( err ) );
621                tr_free( path );
622                errno = err;
623                return -1;
624            }
625        }
626        else if( ( sb.st_mode & S_IFMT ) != S_IFDIR )
627        {
628            /* Node exists but isn't a folder */
629            char * buf = tr_strdup_printf( _( "File \"%s\" is in the way" ), path );
630            tr_err( _( "Couldn't create \"%1$s\": %2$s" ), path_in, buf );
631            tr_free( buf );
632            tr_free( path );
633            errno = ENOTDIR;
634            return -1;
635        }
636
637        if( done )
638            break;
639
640        *p = TR_PATH_DELIMITER;
641        p++;
642        pp = p;
643    }
644
645    tr_free( path );
646    return 0;
647}
648
649char*
650tr_buildPath( const char *first_element, ... )
651{
652    size_t bufLen = 0;
653    const char * element;
654    char * buf;
655    char * pch;
656    va_list vl;
657
658    /* pass 1: allocate enough space for the string */
659    va_start( vl, first_element );
660    element = first_element;
661    while( element ) {
662        bufLen += strlen( element ) + 1;
663        element = (const char*) va_arg( vl, const char* );
664    }
665    pch = buf = tr_new( char, bufLen );
666    va_end( vl );
667
668    /* pass 2: build the string piece by piece */
669    va_start( vl, first_element );
670    element = first_element;
671    while( element ) {
672        const size_t elementLen = strlen( element );
673        memcpy( pch, element, elementLen );
674        pch += elementLen;
675        *pch++ = TR_PATH_DELIMITER;
676        element = (const char*) va_arg( vl, const char* );
677    }
678    va_end( vl );
679
680    /* terminate the string.  if nonempty, eat the unwanted trailing slash */
681    if( pch != buf )
682        --pch;
683    *pch++ = '\0';
684
685    /* sanity checks & return */
686    assert( pch - buf == (off_t)bufLen );
687    return buf;
688}
689
690/****
691*****
692****/
693
694char*
695tr_strdup( const void * in )
696{
697    return tr_strndup( in, in ? (int)strlen((const char *)in) : 0 );
698}
699
700char*
701tr_strndup( const void * in, int len )
702{
703    char * out = NULL;
704
705    if( len < 0 )
706    {
707        out = tr_strdup( in );
708    }
709    else if( in )
710    {
711        out = tr_malloc( len + 1 );
712        memcpy( out, in, len );
713        out[len] = '\0';
714    }
715
716    return out;
717}
718
719const char*
720tr_memmem( const char * haystack, size_t haystacklen,
721           const char * needle, size_t needlelen )
722{
723#ifdef HAVE_MEMMEM
724    return memmem( haystack, haystacklen, needle, needlelen );
725#else
726    size_t i;
727    if( !needlelen )
728        return haystack;
729    if( needlelen > haystacklen || !haystack || !needle )
730        return NULL;
731    for( i=0; i<=haystacklen-needlelen; ++i )
732        if( !memcmp( haystack+i, needle, needlelen ) )
733            return haystack+i;
734    return NULL;
735#endif
736}
737
738char*
739tr_strdup_printf( const char * fmt, ... )
740{
741    va_list ap;
742    char * ret;
743    size_t len;
744    char statbuf[2048];
745
746    va_start( ap, fmt );
747    len = evutil_vsnprintf( statbuf, sizeof( statbuf ), fmt, ap );
748    va_end( ap );
749    if( len < sizeof( statbuf ) )
750        ret = tr_strndup( statbuf, len );
751    else {
752        ret = tr_new( char, len + 1 );
753        va_start( ap, fmt );
754        evutil_vsnprintf( ret, len + 1, fmt, ap );
755        va_end( ap );
756    }
757
758    return ret;
759}
760
761const char*
762tr_strerror( int i )
763{
764    const char * ret = strerror( i );
765
766    if( ret == NULL )
767        ret = "Unknown Error";
768    return ret;
769}
770
771/****
772*****
773****/
774
775char*
776tr_strstrip( char * str )
777{
778    if( str != NULL )
779    {
780        size_t pos;
781        size_t len = strlen( str );
782
783        while( len && isspace( str[len - 1] ) )
784            --len;
785
786        for( pos = 0; pos < len && isspace( str[pos] ); )
787            ++pos;
788
789        len -= pos;
790        memmove( str, str + pos, len );
791        str[len] = '\0';
792    }
793
794    return str;
795}
796
797tr_bool
798tr_str_has_suffix( const char *str, const char *suffix )
799{
800    size_t str_len;
801    size_t suffix_len;
802
803    if( !str )
804        return FALSE;
805    if( !suffix )
806        return TRUE;
807
808    str_len = strlen( str );
809    suffix_len = strlen( suffix );
810    if( str_len < suffix_len )
811        return FALSE;
812
813    return !strncasecmp( str + str_len - suffix_len, suffix, suffix_len );
814}
815
816/****
817*****
818****/
819
820uint64_t
821tr_time_msec( void )
822{
823    struct timeval tv;
824
825    gettimeofday( &tv, NULL );
826    return (uint64_t) tv.tv_sec * 1000 + ( tv.tv_usec / 1000 );
827}
828
829void
830tr_wait_msec( long int msec )
831{
832#ifdef WIN32
833    Sleep( (DWORD)msec );
834#else
835    struct timespec ts;
836    ts.tv_sec = msec / 1000;
837    ts.tv_nsec = ( msec % 1000 ) * 1000000;
838    nanosleep( &ts, NULL );
839#endif
840}
841
842/***
843****
844***/
845
846int
847tr_snprintf( char * buf, size_t buflen, const char * fmt, ... )
848{
849    int     len;
850    va_list args;
851
852    va_start( args, fmt );
853    len = evutil_vsnprintf( buf, buflen, fmt, args );
854    va_end( args );
855    return len;
856}
857
858/*
859 * Copy src to string dst of size siz.  At most siz-1 characters
860 * will be copied.  Always NUL terminates (unless siz == 0).
861 * Returns strlen(src); if retval >= siz, truncation occurred.
862 */
863size_t
864tr_strlcpy( char *       dst,
865            const void * src,
866            size_t       siz )
867{
868#ifdef HAVE_STRLCPY
869    return strlcpy( dst, src, siz );
870#else
871    char *      d = dst;
872    const char *s = src;
873    size_t      n = siz;
874
875    assert( s );
876    assert( d );
877
878    /* Copy as many bytes as will fit */
879    if( n != 0 )
880    {
881        while( --n != 0 )
882        {
883            if( ( *d++ = *s++ ) == '\0' )
884                break;
885        }
886    }
887
888    /* Not enough room in dst, add NUL and traverse rest of src */
889    if( n == 0 )
890    {
891        if( siz != 0 )
892            *d = '\0'; /* NUL-terminate dst */
893        while( *s++ )
894            ;
895    }
896
897    return s - (char*)src - 1;  /* count does not include NUL */
898#endif
899}
900
901/***
902****
903***/
904
905double
906tr_getRatio( uint64_t numerator, uint64_t denominator )
907{
908    double ratio;
909
910    if( denominator > 0 )
911        ratio = numerator / (double)denominator;
912    else if( numerator > 0 )
913        ratio = TR_RATIO_INF;
914    else
915        ratio = TR_RATIO_NA;
916
917    return ratio;
918}
919
920void
921tr_sha1_to_hex( char * out, const uint8_t * sha1 )
922{
923    int i;
924    static const char hex[] = "0123456789abcdef";
925
926    for( i=0; i<20; ++i )
927    {
928        const unsigned int val = *sha1++;
929        *out++ = hex[val >> 4];
930        *out++ = hex[val & 0xf];
931    }
932
933    *out = '\0';
934}
935
936void
937tr_hex_to_sha1( uint8_t * out, const char * in )
938{
939    int i;
940    static const char hex[] = "0123456789abcdef";
941
942    for( i=0; i<20; ++i )
943    {
944        const int hi = strchr( hex, tolower( *in++ ) ) - hex;
945        const int lo = strchr( hex, tolower( *in++ ) ) - hex;
946        *out++ = (uint8_t)( (hi<<4) | lo );
947    }
948}
949
950/***
951****
952***/
953
954static tr_bool
955isValidURLChars( const char * url, int url_len )
956{
957    const char * c;
958    const char * end;
959    static const char * rfc2396_valid_chars =
960        "abcdefghijklmnopqrstuvwxyz" /* lowalpha */
961        "ABCDEFGHIJKLMNOPQRSTUVWXYZ" /* upalpha */
962        "0123456789"                 /* digit */
963        "-_.!~*'()"                  /* mark */
964        ";/?:@&=+$,"                 /* reserved */
965        "<>#%<\""                    /* delims */
966        "{}|\\^[]`";                 /* unwise */
967
968    if( url == NULL )
969        return FALSE;
970
971    for( c=url, end=c+url_len; c && *c && c!=end; ++c )
972        if( !strchr( rfc2396_valid_chars, *c ) )
973            return FALSE;
974
975    return TRUE;
976}
977
978/** @brief return TRUE if the url is a http or https url that Transmission understands */
979tr_bool
980tr_urlIsValidTracker( const char * url )
981{
982    tr_bool valid;
983    char * scheme = NULL;
984    const int len = url ? strlen(url) : 0;
985
986    valid = isValidURLChars( url, len )
987         && !tr_urlParse( url, len, &scheme, NULL, NULL, NULL )
988         && ( scheme != NULL )
989         && ( !strcmp(scheme,"http") || !strcmp(scheme,"https") );
990
991    tr_free( scheme );
992    return valid;
993}
994
995/** @brief return TRUE if the url is a http or https or ftp or sftp url that Transmission understands */
996tr_bool
997tr_urlIsValid( const char * url, int url_len )
998{
999    tr_bool valid;
1000    char * scheme = NULL;
1001    if( ( url_len < 0 ) && ( url != NULL ) )
1002        url_len = strlen( url );
1003
1004    valid = isValidURLChars( url, url_len )
1005         && !tr_urlParse( url, url_len, &scheme, NULL, NULL, NULL )
1006         && ( scheme != NULL )
1007         && ( !strcmp(scheme,"http") || !strcmp(scheme,"https") || !strcmp(scheme,"ftp") || !strcmp(scheme,"sftp") );
1008
1009    tr_free( scheme );
1010    return valid;
1011}
1012
1013tr_bool
1014tr_addressIsIP( const char * address )
1015{
1016    tr_address tempAddr;
1017    return tr_pton(address, &tempAddr) != NULL;
1018}
1019
1020int
1021tr_urlParse( const char * url_in,
1022             int          len,
1023             char **      setme_protocol,
1024             char **      setme_host,
1025             int *        setme_port,
1026             char **      setme_path )
1027{
1028    int          err;
1029    int          port = 0;
1030    int          n;
1031    char *       tmp;
1032    char *       pch;
1033    const char * protocol = NULL;
1034    const char * host = NULL;
1035    const char * path = NULL;
1036
1037    tmp = tr_strndup( url_in, len );
1038    if( ( pch = strstr( tmp, "://" ) ) )
1039    {
1040        *pch = '\0';
1041        protocol = tmp;
1042        pch += 3;
1043/*fprintf( stderr, "protocol is [%s]... what's left is [%s]\n", protocol, pch);*/
1044        if( ( n = strcspn( pch, ":/" ) ) )
1045        {
1046            const int havePort = pch[n] == ':';
1047            host = pch;
1048            pch += n;
1049            *pch++ = '\0';
1050/*fprintf( stderr, "host is [%s]... what's left is [%s]\n", host, pch );*/
1051            if( havePort )
1052            {
1053                char * end;
1054                port = strtol( pch, &end, 10 );
1055                pch = end;
1056/*fprintf( stderr, "port is [%d]... what's left is [%s]\n", port, pch );*/
1057            }
1058            path = pch;
1059/*fprintf( stderr, "path is [%s]\n", path );*/
1060        }
1061    }
1062
1063    err = !host || !path || !protocol;
1064
1065    if( !err && !port )
1066    {
1067        if( !strcmp( protocol, "ftp" ) ) port = 21;
1068        if( !strcmp( protocol, "sftp" ) ) port = 22;
1069        if( !strcmp( protocol, "http" ) ) port = 80;
1070        if( !strcmp( protocol, "https" ) ) port = 443;
1071    }
1072
1073    if( !err )
1074    {
1075        if( setme_protocol ) *setme_protocol = tr_strdup( protocol );
1076
1077        if( setme_host ){ ( (char*)host )[-3] = ':'; *setme_host =
1078                              tr_strdup( host ); }
1079        if( setme_path ){ if( path[0] == '/' ) *setme_path = tr_strdup( path );
1080                          else { ( (char*)path )[-1] = '/'; *setme_path = tr_strdup( path - 1 ); } }
1081        if( setme_port ) *setme_port = port;
1082    }
1083
1084
1085    tr_free( tmp );
1086    return err;
1087}
1088
1089#include <string.h>
1090#include <openssl/sha.h>
1091#include <openssl/hmac.h>
1092#include <openssl/evp.h>
1093#include <openssl/bio.h>
1094#include <openssl/buffer.h>
1095
1096char *
1097tr_base64_encode( const void * input, int length, int * setme_len )
1098{
1099    int retlen = 0;
1100    char * ret = NULL;
1101
1102    if( input != NULL )
1103    {
1104        BIO * b64;
1105        BIO * bmem;
1106        BUF_MEM * bptr;
1107
1108        if( length < 1 )
1109            length = (int)strlen( input );
1110
1111        bmem = BIO_new( BIO_s_mem( ) );
1112        b64 = BIO_new( BIO_f_base64( ) );
1113        b64 = BIO_push( b64, bmem );
1114        BIO_write( b64, input, length );
1115        (void) BIO_flush( b64 );
1116        BIO_get_mem_ptr( b64, &bptr );
1117        ret = tr_strndup( bptr->data, bptr->length );
1118        retlen = bptr->length;
1119        BIO_free_all( b64 );
1120    }
1121
1122    if( setme_len )
1123        *setme_len = retlen;
1124
1125    return ret;
1126}
1127
1128char *
1129tr_base64_decode( const void * input,
1130                  int          length,
1131                  int *        setme_len )
1132{
1133    char * ret;
1134    BIO *  b64;
1135    BIO *  bmem;
1136    int    retlen;
1137
1138    if( length < 1 )
1139        length = strlen( input );
1140
1141    ret = tr_new0( char, length );
1142    b64 = BIO_new( BIO_f_base64( ) );
1143    bmem = BIO_new_mem_buf( (unsigned char*)input, length );
1144    bmem = BIO_push( b64, bmem );
1145    retlen = BIO_read( bmem, ret, length );
1146    if( !retlen )
1147    {
1148        /* try again, but with the BIO_FLAGS_BASE64_NO_NL flag */
1149        BIO_free_all( bmem );
1150        b64 = BIO_new( BIO_f_base64( ) );
1151        BIO_set_flags( b64, BIO_FLAGS_BASE64_NO_NL );
1152        bmem = BIO_new_mem_buf( (unsigned char*)input, length );
1153        bmem = BIO_push( b64, bmem );
1154        retlen = BIO_read( bmem, ret, length );
1155    }
1156
1157    if( setme_len )
1158        *setme_len = retlen;
1159
1160    BIO_free_all( bmem );
1161    return ret;
1162}
1163
1164/***
1165****
1166***/
1167
1168void
1169tr_removeElementFromArray( void         * array,
1170                           unsigned int   index_to_remove,
1171                           size_t         sizeof_element,
1172                           size_t         nmemb )
1173{
1174    char * a = (char*) array;
1175
1176    memmove( a + sizeof_element * index_to_remove,
1177             a + sizeof_element * ( index_to_remove  + 1 ),
1178             sizeof_element * ( --nmemb - index_to_remove ) );
1179}
1180
1181int
1182tr_lowerBound( const void * key,
1183               const void * base,
1184               size_t       nmemb,
1185               size_t       size,
1186               int       (* compar)(const void* key, const void* arrayMember),
1187               tr_bool    * exact_match )
1188{
1189    size_t first = 0;
1190    const char * cbase = base;
1191    tr_bool exact = FALSE;
1192
1193    while( nmemb != 0 )
1194    {
1195        const size_t half = nmemb / 2;
1196        const size_t middle = first + half;
1197        const int c = compar( key, cbase + size*middle );
1198
1199        if( c <= 0 ) {
1200            if( c == 0 )
1201                exact = TRUE;
1202            nmemb = half;
1203        } else {
1204            first = middle + 1;
1205            nmemb = nmemb - half - 1;
1206        }
1207    }
1208
1209    *exact_match = exact;
1210
1211    return first;
1212}
1213
1214/***
1215****
1216***/
1217
1218static char*
1219strip_non_utf8( const char * in, size_t inlen )
1220{
1221    char * ret;
1222    const char * end;
1223    const char zero = '\0';
1224    struct evbuffer * buf = evbuffer_new( );
1225
1226    while( !tr_utf8_validate( in, inlen, &end ) )
1227    {
1228        const int good_len = end - in;
1229
1230        evbuffer_add( buf, in, good_len );
1231        inlen -= ( good_len + 1 );
1232        in += ( good_len + 1 );
1233        evbuffer_add( buf, "?", 1 );
1234    }
1235
1236    evbuffer_add( buf, in, inlen );
1237    evbuffer_add( buf, &zero, 1 );
1238    ret = tr_memdup( EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
1239    evbuffer_free( buf );
1240    return ret;
1241}
1242
1243static char*
1244to_utf8( const char * in, size_t inlen )
1245{
1246    char * ret = NULL;
1247
1248#ifdef HAVE_ICONV_OPEN
1249    int i;
1250    const char * encodings[] = { "CURRENT", "ISO-8859-15" };
1251    const int encoding_count = sizeof(encodings) / sizeof(encodings[1]);
1252    const size_t buflen = inlen*4 + 10;
1253    char * out = tr_new( char, buflen );
1254
1255    for( i=0; !ret && i<encoding_count; ++i )
1256    {
1257        char * inbuf = (char*) in;
1258        char * outbuf = out;
1259        size_t inbytesleft = inlen;
1260        size_t outbytesleft = buflen;
1261        const char * test_encoding = encodings[i];
1262
1263        iconv_t cd = iconv_open( "UTF-8", test_encoding );
1264        if( cd != (iconv_t)-1 ) {
1265            if( iconv( cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft ) != (size_t)-1 )
1266                ret = tr_strndup( out, buflen-outbytesleft );
1267            iconv_close( cd );
1268        }
1269    }
1270#endif
1271
1272    if( ret == NULL )
1273        ret = strip_non_utf8( in, inlen );
1274
1275    return ret;
1276}
1277
1278char*
1279tr_utf8clean( const char * str, int max_len )
1280{
1281    char * ret;
1282    const char * end;
1283
1284    if( max_len < 0 )
1285        max_len = (int) strlen( str );
1286
1287    if( tr_utf8_validate( str, max_len, &end  ) )
1288        ret = tr_strndup( str, max_len );
1289    else
1290        ret = to_utf8( str, max_len );
1291
1292    assert( tr_utf8_validate( ret, -1, NULL ) );
1293    return ret;
1294}
1295
1296/***
1297****
1298***/
1299
1300struct number_range
1301{
1302    int low;
1303    int high;
1304};
1305
1306/**
1307 * This should be a single number (ex. "6") or a range (ex. "6-9").
1308 * Anything else is an error and will return failure.
1309 */
1310static tr_bool
1311parseNumberSection( const char * str, int len, struct number_range * setme )
1312{
1313    long a, b;
1314    tr_bool success;
1315    char * end;
1316    const int error = errno;
1317    char * tmp = tr_strndup( str, len );
1318
1319    errno = 0;
1320    a = b = strtol( tmp, &end, 10 );
1321    if( errno || ( end == tmp ) ) {
1322        success = FALSE;
1323    } else if( *end != '-' ) {
1324        b = a;
1325        success = TRUE;
1326    } else {
1327        const char * pch = end + 1;
1328        b = strtol( pch, &end, 10 );
1329        if( errno || ( pch == end ) )
1330            success = FALSE;
1331        else if( *end ) /* trailing data */
1332            success = FALSE;
1333        else
1334            success = TRUE;
1335    }
1336    tr_free( tmp );
1337
1338    setme->low = MIN( a, b );
1339    setme->high = MAX( a, b );
1340
1341    errno = error;
1342    return success;
1343}
1344
1345int
1346compareInt( const void * va, const void * vb )
1347{
1348    const int a = *(const int *)va;
1349    const int b = *(const int *)vb;
1350    return a - b;
1351}
1352
1353/**
1354 * Given a string like "1-4" or "1-4,6,9,14-51", this allocates and returns an
1355 * array of setmeCount ints of all the values in the array.
1356 * For example, "5-8" will return [ 5, 6, 7, 8 ] and setmeCount will be 4.
1357 * It's the caller's responsibility to call tr_free() on the returned array.
1358 * If a fragment of the string can't be parsed, NULL is returned.
1359 */
1360int*
1361tr_parseNumberRange( const char * str_in, int len, int * setmeCount )
1362{
1363    int n = 0;
1364    int * uniq = NULL;
1365    char * str = tr_strndup( str_in, len );
1366    const char * walk;
1367    tr_list * ranges = NULL;
1368    tr_bool success = TRUE;
1369
1370    walk = str;
1371    while( walk && *walk && success ) {
1372        struct number_range range;
1373        const char * pch = strchr( walk, ',' );
1374        if( pch ) {
1375            success = parseNumberSection( walk, pch-walk, &range );
1376            walk = pch + 1;
1377        } else {
1378            success = parseNumberSection( walk, strlen( walk ), &range );
1379            walk += strlen( walk );
1380        }
1381        if( success )
1382            tr_list_append( &ranges, tr_memdup( &range, sizeof( struct number_range ) ) );
1383    }
1384
1385    if( !success )
1386    {
1387        *setmeCount = 0;
1388        uniq = NULL;
1389    }
1390    else
1391    {
1392        int i;
1393        int n2;
1394        tr_list * l;
1395        int * sorted = NULL;
1396
1397        /* build a sorted number array */
1398        n = n2 = 0;
1399        for( l=ranges; l!=NULL; l=l->next ) {
1400            const struct number_range * r = l->data;
1401            n += r->high + 1 - r->low;
1402        }
1403        sorted = tr_new( int, n );
1404        for( l=ranges; l!=NULL; l=l->next ) {
1405            const struct number_range * r = l->data;
1406            int i;
1407            for( i=r->low; i<=r->high; ++i )
1408                sorted[n2++] = i;
1409        }
1410        qsort( sorted, n, sizeof( int ), compareInt );
1411        assert( n == n2 );
1412
1413        /* remove duplicates */
1414        uniq = tr_new( int, n );
1415        for( i=n=0; i<n2; ++i )
1416            if( !n || uniq[n-1] != sorted[i] )
1417                uniq[n++] = sorted[i];
1418
1419        tr_free( sorted );
1420    }
1421
1422    /* cleanup */
1423    tr_list_free( &ranges, tr_free );
1424    tr_free( str );
1425
1426    /* return the result */
1427    *setmeCount = n;
1428    return uniq;
1429}
1430
1431/***
1432****
1433***/
1434
1435double
1436tr_truncd( double x, int precision )
1437{
1438    char * pt;
1439    char buf[128];
1440    const int max_precision = (int) log10( 1.0 / DBL_EPSILON ) - 1;
1441    tr_snprintf( buf, sizeof( buf ), "%.*f", max_precision, x );
1442    if(( pt = strstr( buf, localeconv()->decimal_point )))
1443        pt[precision ? precision+1 : 0] = '\0';
1444    return atof(buf);
1445}
1446
1447char*
1448tr_strtruncd( char * buf, double x, int precision, size_t buflen )
1449{
1450    tr_snprintf( buf, buflen, "%.*f", precision, tr_truncd( x, precision ) );
1451    return buf;
1452}
1453
1454char*
1455tr_strpercent( char * buf, double x, size_t buflen )
1456{
1457    if( x < 10.0 )
1458        tr_strtruncd( buf, x, 2, buflen );
1459    else if( x < 100.0 )
1460        tr_strtruncd( buf, x, 1, buflen );
1461    else
1462        tr_strtruncd( buf, x, 0, buflen );
1463    return buf;
1464}
1465
1466char*
1467tr_strratio( char * buf, size_t buflen, double ratio, const char * infinity )
1468{
1469    if( (int)ratio == TR_RATIO_NA )
1470        tr_strlcpy( buf, _( "None" ), buflen );
1471    else if( (int)ratio == TR_RATIO_INF )
1472        tr_strlcpy( buf, infinity, buflen );
1473    else
1474        tr_strpercent( buf, ratio, buflen );
1475    return buf;
1476}
1477
1478/***
1479****
1480***/
1481
1482int
1483tr_moveFile( const char * oldpath, const char * newpath, tr_bool * renamed )
1484{
1485    int in;
1486    int out;
1487    char * buf;
1488    struct stat st;
1489    off_t bytesLeft;
1490    const size_t buflen = 1024 * 128; /* 128 KiB buffer */
1491
1492    /* make sure the old file exists */
1493    if( stat( oldpath, &st ) ) {
1494        const int err = errno;
1495        errno = err;
1496        return -1;
1497    }
1498    if( !S_ISREG( st.st_mode ) ) {
1499        errno = ENOENT;
1500        return -1;
1501    }
1502    bytesLeft = st.st_size;
1503
1504    /* make sure the target directory exists */
1505    {
1506        char * newdir = tr_dirname( newpath );
1507        int i = tr_mkdirp( newdir, 0777 );
1508        tr_free( newdir );
1509        if( i )
1510            return i;
1511    }
1512
1513    /* they might be on the same filesystem... */
1514    {
1515        const int i = rename( oldpath, newpath );
1516        if( renamed != NULL )
1517            *renamed = i == 0;
1518        if( !i )
1519            return 0;
1520    }
1521
1522    /* copy the file */
1523    in = tr_open_file_for_scanning( oldpath );
1524    out = tr_open_file_for_writing( newpath );
1525    buf = tr_valloc( buflen );
1526    while( bytesLeft > 0 )
1527    {
1528        ssize_t bytesWritten;
1529        const off_t bytesThisPass = MIN( bytesLeft, (off_t)buflen );
1530        const int numRead = read( in, buf, bytesThisPass );
1531        if( numRead < 0 )
1532            break;
1533        bytesWritten = write( out, buf, numRead );
1534        if( bytesWritten < 0 )
1535            break;
1536        bytesLeft -= bytesWritten;
1537    }
1538
1539    /* cleanup */
1540    tr_free( buf );
1541    tr_close_file( out );
1542    tr_close_file( in );
1543    if( bytesLeft != 0 )
1544        return -1;
1545
1546    unlink( oldpath );
1547    return 0;
1548}
1549
1550/***
1551****
1552***/
1553
1554void*
1555tr_valloc( size_t bufLen )
1556{
1557    size_t allocLen;
1558    void * buf = NULL;
1559    static size_t pageSize = 0;
1560
1561    if( !pageSize ) {
1562#ifdef HAVE_GETPAGESIZE
1563        pageSize = (size_t) getpagesize();
1564#else /* guess */
1565        pageSize = 4096;
1566#endif
1567    }
1568
1569    allocLen = pageSize;
1570    while( allocLen < bufLen )
1571        allocLen += pageSize;
1572
1573#ifdef HAVE_POSIX_MEMALIGN
1574    if( !buf )
1575        posix_memalign( &buf, pageSize, allocLen );
1576#endif
1577#ifdef HAVE_VALLOC
1578    if( !buf )
1579        buf = valloc( allocLen );
1580#endif
1581    if( !buf )
1582        buf = malloc( allocLen );
1583
1584    return buf;
1585}
1586
1587char *
1588tr_realpath( const char * path, char * resolved_path )
1589{
1590#ifdef WIN32
1591    /* From a message to the Mingw-msys list, Jun 2, 2005 by Mark Junker. */
1592    if( GetFullPathNameA( path, TR_PATH_MAX, resolved_path, NULL ) == 0 )
1593        return NULL;
1594    return resolved_path;
1595#else
1596    return realpath( path, resolved_path );
1597#endif
1598}
1599
1600/***
1601****
1602****
1603****
1604***/
1605
1606struct formatter_unit
1607{
1608    char * name;
1609    uint64_t value;
1610};
1611
1612struct formatter_units
1613{
1614    struct formatter_unit units[4];
1615};
1616
1617enum { TR_FMT_KB, TR_FMT_MB, TR_FMT_GB, TR_FMT_TB };
1618
1619static void
1620formatter_init( struct formatter_units * units,
1621                unsigned int kilo,
1622                const char * kb, const char * mb,
1623                const char * gb, const char * tb )
1624{
1625    uint64_t value = kilo;
1626    units->units[TR_FMT_KB].name = tr_strdup( kb );
1627    units->units[TR_FMT_KB].value = value;
1628
1629    value *= kilo;
1630    units->units[TR_FMT_MB].name = tr_strdup( mb );
1631    units->units[TR_FMT_MB].value = value;
1632
1633    value *= kilo;
1634    units->units[TR_FMT_GB].name = tr_strdup( gb );
1635    units->units[TR_FMT_GB].value = value;
1636
1637    value *= kilo;
1638    units->units[TR_FMT_TB].name = tr_strdup( tb );
1639    units->units[TR_FMT_TB].value = value;
1640}
1641
1642static char*
1643formatter_get_size_str( const struct formatter_units * u,
1644                        char * buf, uint64_t bytes, size_t buflen )
1645{
1646    int precision;
1647    double value;
1648    const char * units;
1649    const struct formatter_unit * unit;
1650
1651         if( bytes < u->units[1].value ) unit = &u->units[0];
1652    else if( bytes < u->units[2].value ) unit = &u->units[1];
1653    else if( bytes < u->units[3].value ) unit = &u->units[2];
1654    else                                 unit = &u->units[3];
1655
1656    value = (double)bytes / unit->value;
1657    units = unit->name;
1658    if( unit->value == 1 )
1659        precision = 0;
1660    else if( value < 100 )
1661        precision = 2;
1662    else
1663        precision = 1;
1664    tr_snprintf( buf, buflen, "%.*f %s", precision, value, units );
1665    return buf;
1666}
1667
1668static struct formatter_units size_units;
1669
1670void
1671tr_formatter_size_init( unsigned int kilo,
1672                        const char * kb, const char * mb,
1673                        const char * gb, const char * tb )
1674{
1675    formatter_init( &size_units, kilo, kb, mb, gb, tb );
1676}
1677
1678char*
1679tr_formatter_size_B( char * buf, uint64_t bytes, size_t buflen )
1680{
1681    return formatter_get_size_str( &size_units, buf, bytes, buflen );
1682}
1683
1684static struct formatter_units speed_units;
1685
1686unsigned int tr_speed_K = 0u;
1687
1688void
1689tr_formatter_speed_init( unsigned int kilo,
1690                         const char * kb, const char * mb,
1691                         const char * gb, const char * tb )
1692{
1693    tr_speed_K = kilo;
1694    formatter_init( &speed_units, kilo, kb, mb, gb, tb );
1695}
1696
1697char*
1698tr_formatter_speed_KBps( char * buf, double KBps, size_t buflen )
1699{
1700    const double K = speed_units.units[TR_FMT_KB].value;
1701    double speed = KBps;
1702
1703    if( speed <= 999.95 ) /* 0.0 KB to 999.9 KB */
1704        tr_snprintf( buf, buflen, "%.2f %s", speed, speed_units.units[TR_FMT_KB].name );
1705    else {
1706        speed /= K;
1707        if( speed <= 99.995 ) /* 0.98 MB to 99.99 MB */
1708            tr_snprintf( buf, buflen, "%.2f %s", speed, speed_units.units[TR_FMT_MB].name );
1709        else if (speed <= 999.95) /* 100.0 MB to 999.9 MB */
1710            tr_snprintf( buf, buflen, "%.1f %s", speed, speed_units.units[TR_FMT_MB].name );
1711        else {
1712            speed /= K;
1713            tr_snprintf( buf, buflen, "%.1f %s", speed, speed_units.units[TR_FMT_GB].name );
1714        }
1715    }
1716
1717    return buf;
1718}
1719
1720static struct formatter_units mem_units;
1721
1722unsigned int tr_mem_K = 0u;
1723
1724void
1725tr_formatter_mem_init( unsigned int kilo,
1726                       const char * kb, const char * mb,
1727                       const char * gb, const char * tb )
1728{
1729    tr_mem_K = kilo;
1730    formatter_init( &mem_units, kilo, kb, mb, gb, tb );
1731}
1732
1733char*
1734tr_formatter_mem_B( char * buf, uint64_t bytes_per_second, size_t buflen )
1735{
1736    return formatter_get_size_str( &mem_units, buf, bytes_per_second, buflen );
1737}
1738
1739void
1740tr_formatter_get_units( tr_benc * d )
1741{
1742    int i;
1743    tr_benc * l;
1744
1745    tr_bencDictReserve( d, 6 );
1746
1747    tr_bencDictAddInt( d, "memory-bytes", mem_units.units[TR_FMT_KB].value );
1748    l = tr_bencDictAddList( d, "memory-units", 4 );
1749    for( i=0; i<4; i++ ) tr_bencListAddStr( l, mem_units.units[i].name );
1750
1751    tr_bencDictAddInt( d, "size-bytes",   size_units.units[TR_FMT_KB].value );
1752    l = tr_bencDictAddList( d, "size-units", 4 );
1753    for( i=0; i<4; i++ ) tr_bencListAddStr( l, size_units.units[i].name );
1754
1755    tr_bencDictAddInt( d, "speed-bytes",  speed_units.units[TR_FMT_KB].value );
1756    l = tr_bencDictAddList( d, "speed-units", 4 );
1757    for( i=0; i<4; i++ ) tr_bencListAddStr( l, speed_units.units[i].name );
1758}
1759
Note: See TracBrowser for help on using the repository browser.