source: branches/1.5x/daemon/remote.c @ 8352

Last change on this file since 8352 was 8352, checked in by charles, 13 years ago

(1.5x) add a session_id cookie to the rpc server

  • Property svn:keywords set to Date Rev Author Id
File size: 45.1 KB
Line 
1/*
2 * This file Copyright (C) 2008-2009 Charles Kerr <charles@transmissionbt.com>
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: remote.c 8352 2009-05-08 02:38:08Z charles $
11 */
12
13#include <errno.h>
14#include <math.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h> /* strcmp */
18
19#ifdef WIN32
20 #include <direct.h> /* getcwd */
21#else
22 #include <unistd.h> /* getcwd */
23#endif
24
25#include <libevent/event.h>
26
27#define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */
28#include <curl/curl.h>
29
30#include <libtransmission/transmission.h>
31#include <libtransmission/bencode.h>
32#include <libtransmission/rpcimpl.h>
33#include <libtransmission/json.h>
34#include <libtransmission/tr-getopt.h>
35#include <libtransmission/utils.h>
36#include <libtransmission/version.h>
37
38#define MY_NAME "transmission-remote"
39#define DEFAULT_HOST "localhost"
40#define DEFAULT_PORT atoi(TR_DEFAULT_RPC_PORT_STR)
41
42enum { TAG_SESSION, TAG_LIST, TAG_DETAILS, TAG_FILES, TAG_PEERS };
43
44static const char*
45getUsage( void )
46{
47    return
48        "Transmission " LONG_VERSION_STRING
49        "  http://www.transmissionbt.com/\n"
50        "A fast and easy BitTorrent client\n"
51        "\n"
52        "Usage: " MY_NAME
53        " [host] [options]\n"
54        "       "
55        MY_NAME " [port] [options]\n"
56                "       "
57        MY_NAME " [host:port] [options]\n"
58                "\n"
59                "See the man page for detailed explanations and many examples.";
60}
61
62static tr_option opts[] =
63{
64    { 'a', "add",                  "Add torrent files by filename or URL", "a",  0, NULL },
65    { 'b', "debug",                "Print debugging information", "b",  0, NULL },
66    { 'd', "downlimit",            "Set the maximum global download speed in KB/s", "d",  1, "<speed>" },
67    { 'D', "no-downlimit",         "Don't limit the global download speed", "D",  0, NULL },
68    { 910, "encryption-required",  "Encrypt all peer connections", "er", 0, NULL },
69    { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL },
70    { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
71    { 'f', "files",                "List the current torrent(s)' files", "f",  0, NULL },
72    { 'g', "get",                  "Mark files for download", "g",  1, "<files>" },
73    { 'G', "no-get",               "Mark files for not downloading", "G",  1, "<files>" },
74    { 'i', "info",                 "Show the current torrent(s)' details", "i",  0, NULL },
75    { 920, "session-info",         "Show the session's details", "si", 0, NULL },
76    { 'l', "list",                 "List all torrents", "l",  0, NULL },
77    { 'm', "portmap",              "Enable portmapping via NAT-PMP or UPnP", "m",  0, NULL },
78    { 'M', "no-portmap",           "Disable portmapping", "M",  0, NULL },
79    { 'n', "auth",                 "Set authentication info", "n",  1, "<username:password>" },
80    { 'N', "netrc",                "Set authentication info from a .netrc file", "N",  1, "<filename>" },
81    { 'p', "port",                 "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", 1, "<port>" },
82    { 900, "priority-high",        "Set the files' priorities as high", "ph", 1, "<files>" },
83    { 901, "priority-normal",      "Set the files' priorities as normal", "pn", 1, "<files>" },
84    { 902, "priority-low",         "Set the files' priorities as low", "pl", 1, "<files>" },
85    { 'r', "remove",               "Remove the current torrent(s)", "r",  0, NULL },
86    { 930, "peers",                "Set the current torrent(s)' maximum number of peers each", "pr", 1, "<max>" },
87    { 931, "global-peers",         "Set the global maximum number of peers", "gpr", 1, "<max>" },
88    { 'R', "remove-and-delete",    "Remove the current torrent(s) and delete local data", NULL, 0, NULL },
89    { 's', "start",                "Start the current torrent(s)", "s",  0, NULL },
90    { 'S', "stop",                 "Stop the current torrent(s)", "S",  0, NULL },
91    { 't', "torrent",              "Set the current torrent(s)", "t",  1, "<torrent>" },
92    { 'u', "uplimit",              "Set the maximum global upload speed in KB/s", "u",  1, "<speed>" },
93    { 'U', "no-uplimit",           "Don't limit the global upload speed", "U",  0, NULL },
94    { 'v', "verify",               "Verify the current torrent(s)", "v",  0, NULL },
95    { 'V', "version",              "Show version number and exit", "V", 0, NULL },
96    { 'w', "download-dir",         "Set the default download folder", "w",  1, "<path>" },
97    { 'x', "pex",                  "Enable peer exchange (PEX)", "x",  0, NULL },
98    { 'X', "no-pex",               "Disable peer exchange (PEX)", "X",  0, NULL },
99    { 940, "peer-info",            "List the current torrent(s)' peers", "pi",  0, NULL },
100    {   0, NULL,                   NULL, NULL, 0, NULL }
101};
102
103static void
104showUsage( void )
105{
106    tr_getopt_usage( MY_NAME, getUsage( ), opts );
107    exit( 0 );
108}
109
110static int
111numarg( const char * arg )
112{
113    char *     end = NULL;
114    const long num = strtol( arg, &end, 10 );
115
116    if( *end )
117    {
118        fprintf( stderr, "Not a number: \"%s\"\n", arg );
119        showUsage( );
120    }
121    return num;
122}
123
124static char * reqs[256]; /* arbitrary max */
125static int    reqCount = 0;
126static int    debug = 0;
127static char * auth = NULL;
128static char * netrc = NULL;
129static char * cookies = NULL;
130
131static char*
132tr_getcwd( void )
133{
134    char buf[2048];
135    *buf = '\0';
136#ifdef WIN32
137    _getcwd( buf, sizeof( buf ) );
138#else
139    getcwd( buf, sizeof( buf ) );
140#endif
141    return tr_strdup( buf );
142}
143
144static char*
145absolutify( const char * path )
146{
147    char * buf;
148
149    if( *path == '/' )
150        buf = tr_strdup( path );
151    else {
152        char * cwd = tr_getcwd( );
153        buf = tr_buildPath( cwd, path, NULL );
154        tr_free( cwd );
155    }
156
157    return buf;
158}
159
160static char*
161getEncodedMetainfo( const char * filename )
162{
163    size_t    len = 0;
164    char *    b64 = NULL;
165    uint8_t * buf = tr_loadFile( filename, &len );
166
167    if( buf )
168    {
169        b64 = tr_base64_encode( buf, len, NULL );
170        tr_free( buf );
171    }
172    return b64;
173}
174
175static void
176addIdArg( tr_benc *    args,
177          const char * id )
178{
179    if( !*id )
180    {
181        fprintf(
182            stderr,
183            "No torrent specified!  Please use the -t option first.\n" );
184        id = "-1"; /* no torrent will have this ID, so should be a no-op */
185    }
186    if( strcmp( id, "all" ) )
187    {
188        tr_rpc_parse_list_str( tr_bencDictAdd( args,
189                                               "ids" ), id, strlen( id ) );
190    }
191}
192
193static void
194addFiles( tr_benc *    args,
195          const char * key,
196          const char * arg )
197{
198    tr_benc * files = tr_bencDictAddList( args, key, 100 );
199
200    if( !*arg )
201    {
202        fprintf( stderr, "No files specified!\n" );
203        arg = "-1"; /* no file will have this index, so should be a no-op */
204    }
205    if( strcmp( arg, "all" ) )
206    {
207        int i;
208        int valueCount;
209        int * values = tr_parseNumberRange( arg, -1, &valueCount );
210        for( i=0; i<valueCount; ++i )
211            tr_bencListAddInt( files, values[i] );
212        tr_free( values );
213    }
214}
215
216#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) )
217
218static const char * files_keys[] = {
219    "files",
220    "name",
221    "priorities",
222    "wanted"
223};
224
225static const char * details_keys[] = {
226    "activityDate",
227    "addedDate",
228    "announceResponse",
229    "announceURL",
230    "comment",
231    "corruptEver",
232    "creator",
233    "dateCreated",
234    "doneDate",
235    "downloadDir",
236    "downloadedEver",
237    "errorString",
238    "eta",
239    "hashString",
240    "haveUnchecked",
241    "haveValid",
242    "id",
243    "isPrivate",
244    "lastAnnounceTime",
245    "lastScrapeTime",
246    "leechers",
247    "leftUntilDone",
248    "name",
249    "nextAnnounceTime",
250    "nextScrapeTime",
251    "peersConnected",
252    "peersGettingFromUs",
253    "peersSendingToUs",
254    "pieceCount",
255    "pieceSize",
256    "rateDownload",
257    "rateUpload",
258    "recheckProgress",
259    "scrapeResponse",
260    "seeders",
261    "sizeWhenDone",
262    "startDate",
263    "status",
264    "timesCompleted",
265    "totalSize",
266    "uploadedEver",
267    "webseeds",
268    "webseedsSendingToUs"
269};
270
271static const char * list_keys[] = {
272    "error",
273    "errorString",
274    "eta",
275    "id",
276    "leftUntilDone",
277    "name",
278    "peersGettingFromUs",
279    "peersSendingToUs",
280    "rateDownload",
281    "rateUpload",
282    "sizeWhenDone",
283    "status",
284    "uploadRatio"
285};
286
287static void
288readargs( int           argc,
289          const char ** argv )
290{
291    int          c;
292    int          addingTorrents = 0;
293    const char * optarg;
294    char         id[4096];
295
296    *id = '\0';
297
298    while( ( c = tr_getopt( getUsage( ), argc, argv, opts, &optarg ) ) )
299    {
300        int     i, n;
301        int     addArg = TRUE;
302        tr_benc top, *args, *fields;
303        tr_bencInitDict( &top, 3 );
304        args = tr_bencDictAddDict( &top, "arguments", 0 );
305
306        switch( c )
307        {
308            case TR_OPT_UNK:
309                if( addingTorrents )
310                {
311                    char * tmp = getEncodedMetainfo( optarg );
312                    if( tmp )
313                    {
314                        tr_bencDictAddStr( &top, "method", "torrent-add" );
315                        tr_bencDictAddStr( args, "metainfo", tmp );
316                        tr_free( tmp );
317                    }
318                    else
319                    {
320                        tr_bencDictAddStr( &top, "method", "torrent-add" );
321                        tr_bencDictAddStr( args, "filename", optarg );
322                    }
323                }
324                else
325                {
326                    fprintf( stderr, "Unknown option: %s\n", optarg );
327                    addArg = FALSE;
328                }
329                break;
330
331            case 'a':
332                addingTorrents = 1;
333                addArg = FALSE;
334                break;
335
336            case 'b':
337                debug = 1;
338                addArg = FALSE;
339                break;
340
341            case 'd':
342                tr_bencDictAddStr( &top, "method", "session-set" );
343                tr_bencDictAddInt( args, "speed-limit-down", numarg( optarg ) );
344                tr_bencDictAddInt( args, "speed-limit-down-enabled", 1 );
345                break;
346
347            case 'D':
348                tr_bencDictAddStr( &top, "method", "session-set" );
349                tr_bencDictAddInt( args, "speed-limit-down-enabled", 0 );
350                break;
351
352            case 'f':
353                tr_bencDictAddStr( &top, "method", "torrent-get" );
354                tr_bencDictAddInt( &top, "tag", TAG_FILES );
355                addIdArg( args, id );
356                n = TR_N_ELEMENTS( files_keys );
357                fields = tr_bencDictAddList( args, "fields", n );
358                for( i = 0; i < n; ++i )
359                    tr_bencListAddStr( fields, files_keys[i] );
360                break;
361
362            case 'g':
363                tr_bencDictAddStr( &top, "method", "torrent-set" );
364                addIdArg( args, id );
365                addFiles( args, "files-wanted", optarg );
366                break;
367
368            case 'G':
369                tr_bencDictAddStr( &top, "method", "torrent-set" );
370                addIdArg( args, id );
371                addFiles( args, "files-unwanted", optarg );
372                break;
373
374            case 'i':
375                tr_bencDictAddStr( &top, "method", "torrent-get" );
376                tr_bencDictAddInt( &top, "tag", TAG_DETAILS );
377                addIdArg( args, id );
378                n = TR_N_ELEMENTS( details_keys );
379                fields = tr_bencDictAddList( args, "fields", n );
380                for( i = 0; i < n; ++i )
381                    tr_bencListAddStr( fields, details_keys[i] );
382                break;
383
384            case 'l':
385                tr_bencDictAddStr( &top, "method", "torrent-get" );
386                tr_bencDictAddInt( &top, "tag", TAG_LIST );
387                n = TR_N_ELEMENTS( list_keys );
388                fields = tr_bencDictAddList( args, "fields", n );
389                for( i = 0; i < n; ++i )
390                    tr_bencListAddStr( fields, list_keys[i] );
391                break;
392
393            case 'm':
394                tr_bencDictAddStr( &top, "method", "session-set" );
395                tr_bencDictAddInt( args, "port-forwarding-enabled", 1 );
396                break;
397
398            case 'M':
399                tr_bencDictAddStr( &top, "method", "session-set" );
400                tr_bencDictAddInt( args, "port-forwarding-enabled", 0 );
401                break;
402
403            case 'n':
404                auth = tr_strdup( optarg );
405                addArg = FALSE;
406                break;
407
408            case 'N':
409                netrc = tr_strdup( optarg );
410                addArg = FALSE;
411                break;
412
413            case 'p':
414                tr_bencDictAddStr( &top, "method", "session-set" );
415                tr_bencDictAddInt( args, "port", numarg( optarg ) );
416                break;
417
418            case 'r':
419                tr_bencDictAddStr( &top, "method", "torrent-remove" );
420                addIdArg( args, id );
421                break;
422
423            case 'R':
424                tr_bencDictAddStr( &top, "method", "torrent-remove" );
425                addIdArg( args, id );
426                tr_bencDictAddInt( args, "delete-local-data", 1 );
427                break;
428
429            case 's':
430                tr_bencDictAddStr( &top, "method", "torrent-start" );
431                addIdArg( args, id );
432                break;
433
434            case 'S':
435                tr_bencDictAddStr( &top, "method", "torrent-stop" );
436                addIdArg( args, id );
437                break;
438
439            case 't':
440                tr_strlcpy( id, optarg, sizeof( id ) );
441                addArg = FALSE;
442                break;
443
444            case 'u':
445                tr_bencDictAddStr( &top, "method", "session-set" );
446                tr_bencDictAddInt( args, "speed-limit-up", numarg( optarg ) );
447                tr_bencDictAddInt( args, "speed-limit-up-enabled", 1 );
448                break;
449
450            case 'U':
451                tr_bencDictAddStr( &top, "method", "session-set" );
452                tr_bencDictAddInt( args, "speed-limit-up-enabled", 0 );
453                break;
454
455            case 'v':
456                tr_bencDictAddStr( &top, "method", "torrent-verify" );
457                addIdArg( args, id );
458                break;
459
460            case 'V':
461                fprintf( stderr, "Transmission %s\n", LONG_VERSION_STRING );
462                exit( 0 );
463                break;
464
465            case 'w': {
466                char * path = absolutify( optarg );
467                tr_bencDictAddStr( &top, "method", "session-set" );
468                tr_bencDictAddStr( args, "download-dir", path );
469                tr_free( path );
470                break;
471            }
472
473            case 'x':
474                tr_bencDictAddStr( &top, "method", "session-set" );
475                tr_bencDictAddInt( args, "pex-allowed", 1 );
476                break;
477
478            case 'X':
479                tr_bencDictAddStr( &top, "method", "session-set" );
480                tr_bencDictAddInt( args, "pex-allowed", 0 );
481                break;
482
483            case 900:
484                tr_bencDictAddStr( &top, "method", "torrent-set" );
485                addIdArg( args, id );
486                addFiles( args, "priority-high", optarg );
487                break;
488
489            case 901:
490                tr_bencDictAddStr( &top, "method", "torrent-set" );
491                addIdArg( args, id );
492                addFiles( args, "priority-normal", optarg );
493                break;
494
495            case 902:
496                tr_bencDictAddStr( &top, "method", "torrent-set" );
497                addIdArg( args, id );
498                addFiles( args, "priority-low", optarg );
499                break;
500
501            case 910:
502                tr_bencDictAddStr( &top, "method", "session-set" );
503                tr_bencDictAddStr( args, "encryption", "required" );
504                break;
505
506            case 911:
507                tr_bencDictAddStr( &top, "method", "session-set" );
508                tr_bencDictAddStr( args, "encryption", "preferred" );
509                break;
510
511            case 912:
512                tr_bencDictAddStr( &top, "method", "session-set" );
513                tr_bencDictAddStr( args, "encryption", "tolerated" );
514                break;
515
516            case 920:
517                tr_bencDictAddStr( &top, "method", "session-get" );
518                tr_bencDictAddInt( &top, "tag", TAG_SESSION );
519                break;
520
521            case 930:
522                tr_bencDictAddStr( &top, "method", "torrent-set" );
523                addIdArg( args, id );
524                tr_bencDictAddInt( args, "peer-limit", atoi(optarg) );
525                break;
526
527            case 931:
528                tr_bencDictAddStr( &top, "method", "session-set" );
529                tr_bencDictAddInt( args, "peer-limit", atoi(optarg) );
530                break;
531
532            case 940:
533                tr_bencDictAddStr( &top, "method", "torrent-get" );
534                tr_bencDictAddInt( &top, "tag", TAG_PEERS );
535                addIdArg( args, id );
536                fields = tr_bencDictAddList( args, "fields", 1 );
537                tr_bencListAddStr( fields, "peers" );
538                break;
539
540            case TR_OPT_ERR:
541                fprintf( stderr, "invalid option\n" );
542                showUsage( );
543                break;
544
545            default:
546                fprintf( stderr, "got opt [%d]\n", (int)c );
547                showUsage( );
548                break;
549        }
550
551        if( addArg )
552        {
553            struct evbuffer * buf = tr_getBuffer( );
554            reqs[reqCount++] = tr_strdup( tr_bencSaveAsJSON( &top, buf ) );
555            tr_releaseBuffer( buf );
556        }
557
558        tr_bencFree( &top );
559    }
560}
561
562/* [host:port] or [host] or [port] */
563static void
564getHostAndPort( int *   argc,
565                char ** argv,
566                char ** host,
567                int *   port )
568{
569    if( *argv[1] != '-' )
570    {
571        int          i;
572        const char * s = argv[1];
573        const char * delim = strchr( s, ':' );
574        if( delim )   /* user passed in both host and port */
575        {
576            *host = tr_strndup( s, delim - s );
577            *port = atoi( delim + 1 );
578        }
579        else
580        {
581            char *    end;
582            const int i = strtol( s, &end, 10 );
583            if( !*end ) /* user passed in a port */
584                *port = i;
585            else /* user passed in a host */
586                *host = tr_strdup( s );
587        }
588
589        *argc -= 1;
590        for( i = 1; i < *argc; ++i )
591            argv[i] = argv[i + 1];
592    }
593}
594
595static size_t
596writeFunc( void * ptr,
597           size_t size,
598           size_t nmemb,
599           void * buf )
600{
601    const size_t byteCount = size * nmemb;
602
603    evbuffer_add( buf, ptr, byteCount );
604    return byteCount;
605}
606
607static void
608etaToString( char *  buf,
609             size_t  buflen,
610             int64_t eta )
611{
612    if( eta < 0 ) tr_snprintf( buf, buflen, "Unknown" );
613    else if( eta < 60 ) tr_snprintf( buf, buflen, "%" PRId64 "sec", eta );
614    else if( eta <
615            ( 60 * 60 ) ) tr_snprintf( buf, buflen, "%" PRId64 " min",
616                                       eta / 60 );
617    else if( eta <
618            ( 60 * 60 * 24 ) ) tr_snprintf( buf, buflen, "%" PRId64 " hrs",
619                                           eta / ( 60 * 60 ) );
620    else tr_snprintf( buf, buflen, "%" PRId64 " days", eta / ( 60 * 60 * 24 ) );
621}
622
623#define KILOBYTE_FACTOR 1024.0
624#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
625#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
626
627static char*
628strlratio2( char * buf, double ratio, size_t buflen )
629{
630    if( (int)ratio == TR_RATIO_NA )
631        tr_strlcpy( buf, "None", buflen );
632    else if( (int)ratio == TR_RATIO_INF )
633        tr_strlcpy( buf, "Inf", buflen );
634    else if( ratio < 10.0 )
635        tr_snprintf( buf, buflen, "%'.2f", ratio );
636    else if( ratio < 100.0 )
637        tr_snprintf( buf, buflen, "%'.1f", ratio );
638    else
639        tr_snprintf( buf, buflen, "%'.0f", ratio );
640    return buf;
641}
642
643static char*
644strlratio( char * buf,
645           double numerator,
646           double denominator,
647           size_t buflen )
648{
649    double ratio;
650
651    if( denominator )
652        ratio = numerator / denominator;
653    else if( numerator )
654        ratio = TR_RATIO_INF;
655    else
656        ratio = TR_RATIO_NA;
657
658    return strlratio2( buf, ratio, buflen );
659}
660
661static char*
662strlsize( char *  buf, int64_t size, size_t  buflen )
663{
664    if( !size )
665        tr_strlcpy( buf, "None", buflen );
666    else if( size < (int64_t)KILOBYTE_FACTOR )
667        tr_snprintf( buf, buflen, "%'" PRId64 " bytes", (int64_t)size );
668    else
669    {
670        double displayed_size;
671        if( size < (int64_t)MEGABYTE_FACTOR )
672        {
673            displayed_size = (double) size / KILOBYTE_FACTOR;
674            tr_snprintf( buf, buflen, "%'.1f KB", displayed_size );
675        }
676        else if( size < (int64_t)GIGABYTE_FACTOR )
677        {
678            displayed_size = (double) size / MEGABYTE_FACTOR;
679            tr_snprintf( buf, buflen, "%'.1f MB", displayed_size );
680        }
681        else
682        {
683            displayed_size = (double) size / GIGABYTE_FACTOR;
684            tr_snprintf( buf, buflen, "%'.1f GB", displayed_size );
685        }
686    }
687    return buf;
688}
689
690static char*
691getStatusString( tr_benc * t, char * buf, size_t buflen )
692{
693    int64_t status;
694
695    if( !tr_bencDictFindInt( t, "status", &status ) )
696    {
697        *buf = '\0';
698    }
699    else switch( status )
700    {
701        case TR_STATUS_STOPPED:
702            tr_strlcpy( buf, "Stopped", buflen );
703            break;
704
705        case TR_STATUS_CHECK_WAIT:
706        case TR_STATUS_CHECK: {
707            const char * str = status == TR_STATUS_CHECK_WAIT
708                             ? "Will Verify"
709                             : "Verifying";
710            double percent;
711            if( tr_bencDictFindDouble( t, "recheckProgress", &percent ) )
712                tr_snprintf( buf, buflen, "%s (%.0f%%)", str, floor(percent*100.0) );
713            else
714                tr_strlcpy( buf, str, buflen );
715
716            break;
717        }
718
719        case TR_STATUS_DOWNLOAD:
720        case TR_STATUS_SEED: {
721            int64_t fromUs = 0;
722            int64_t toUs = 0;
723            tr_bencDictFindInt( t, "peersGettingFromUs", &fromUs );
724            tr_bencDictFindInt( t, "peersSendingToUs", &toUs );
725            if( fromUs && toUs )
726                tr_strlcpy( buf, "Up & Down", buflen );
727            else if( toUs )
728                tr_strlcpy( buf, "Downloading", buflen );
729            else if( fromUs ) {
730                int64_t leftUntilDone = 0;
731                tr_bencDictFindInt( t, "leftUntilDone", &leftUntilDone );
732                if( leftUntilDone > 0 )
733                    tr_strlcpy( buf, "Uploading", buflen );
734                else
735                    tr_strlcpy( buf, "Seeding", buflen );
736            } else {
737                tr_strlcpy( buf, "Idle", buflen );
738            }
739            break;
740        }
741    }
742
743    return buf;
744}
745
746static const char*
747getTrackerDateStr( const time_t t, tr_bool isStopped )
748{
749    const char * str;
750    switch( t ) {
751        case 0: str = isStopped ? "None (Stopped)\n" : "None\n"; break;
752        case 1: str = "In Progress\n"; break;
753        default: str = ctime( &t ); break;
754    }
755    return str;
756}
757
758static void
759printSession( tr_benc * top )
760{
761    tr_benc *args;
762    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
763    {
764        const char * str;
765        int64_t      i;
766
767        printf( "VERSION\n" );
768        if( tr_bencDictFindStr( args,  "version", &str ) )
769            printf( "  Daemon version: %s\n", str );
770        if( tr_bencDictFindInt( args, "rpc-version", &i ) )
771            printf( "  RPC version: %" PRId64 "\n", i );
772        if( tr_bencDictFindInt( args, "rpc-version-minimum", &i ) )
773            printf( "  RPC minimum version: %" PRId64 "\n", i );
774        printf( "\n" );
775
776        printf( "TRANSFER\n" );
777        if( tr_bencDictFindStr( args,  "download-dir", &str ) )
778            printf( "  Download directory: %s\n", str );
779        if( tr_bencDictFindInt( args, "port", &i ) )
780            printf( "  Listenport: %" PRId64 "\n", i );
781        if( tr_bencDictFindInt( args, "port-forwarding-enabled", &i ) )
782            printf( "  Portforwarding enabled: %s\n", ( i ? "Yes" : "No" ) );
783        if( tr_bencDictFindInt( args, "pex-allowed", &i ) )
784            printf( "  Peer exchange allowed: %s\n", ( i ? "Yes" : "No" ) );
785        if( tr_bencDictFindStr( args,  "encryption", &str ) )
786            printf( "  Encryption: %s\n", str );
787        printf( "\n" );
788
789        printf( "LIMITS\n" );
790        if( tr_bencDictFindInt( args, "peer-limit", &i ) )
791            printf( "  Peer limit: %" PRId64 "\n", i );
792        if( tr_bencDictFindInt( args, "speed-limit-down-enabled", &i ) )
793            printf( "  Downloadlimit enabled: %s\n", ( i ? "Yes" : "No" ) );
794        if( tr_bencDictFindInt( args, "speed-limit-down", &i ) )
795            printf( "  Downloadlimit: %6" PRId64 " KB/sec\n", i );
796        if( tr_bencDictFindInt( args, "speed-limit-up-enabled", &i ) )
797            printf( "  Uploadlimit enabled:   %s\n", ( i ? "Yes" : "No" ) );
798        if( tr_bencDictFindInt( args, "speed-limit-up", &i ) )
799            printf( "  Uploadlimit:   %6" PRId64 " KB/sec\n", i );
800    }
801}
802
803static void
804printDetails( tr_benc * top )
805{
806    tr_benc *args, *torrents;
807
808    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
809      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
810    {
811        int ti, tCount;
812        for( ti = 0, tCount = tr_bencListSize( torrents ); ti < tCount;
813             ++ti )
814        {
815            tr_benc *    t = tr_bencListChild( torrents, ti );
816            tr_benc *    l;
817            const char * str;
818            char         buf[512];
819            char         buf2[512];
820            int64_t      i, j, k;
821            tr_bool      isStopped;
822
823            isStopped = tr_bencDictFindInt( t, "status", &i ) && (i==TR_STATUS_STOPPED);
824
825            printf( "NAME\n" );
826            if( tr_bencDictFindInt( t, "id", &i ) )
827                printf( "  Id: %" PRId64 "\n", i );
828            if( tr_bencDictFindStr( t, "name", &str ) )
829                printf( "  Name: %s\n", str );
830            if( tr_bencDictFindStr( t, "hashString", &str ) )
831                printf( "  Hash: %s\n", str );
832            printf( "\n" );
833
834            printf( "TRANSFER\n" );
835            getStatusString( t, buf, sizeof( buf ) );
836            printf( "  State: %s\n", buf );
837
838            if( tr_bencDictFindStr( t, "downloadDir", &str ) )
839                printf( "  Location: %s\n", str );
840
841            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
842              && tr_bencDictFindInt( t, "leftUntilDone", &j ) )
843            {
844                strlratio( buf, 100.0 * ( i - j ), i, sizeof( buf ) );
845                printf( "  Percent Done: %s%%\n", buf );
846            }
847
848            if( tr_bencDictFindInt( t, "eta", &i ) )
849            {
850                etaToString( buf, sizeof( buf ), i );
851                printf( "  ETA: %s\n", buf );
852            }
853            if( tr_bencDictFindInt( t, "rateDownload", &i ) )
854                printf( "  Download Speed: %.1f KB/s\n", i / 1024.0 );
855            if( tr_bencDictFindInt( t, "rateUpload", &i ) )
856                printf( "  Upload Speed: %.1f KB/s\n", i / 1024.0 );
857            if( tr_bencDictFindInt( t, "haveUnchecked", &i )
858              && tr_bencDictFindInt( t, "haveValid", &j ) )
859            {
860                strlsize( buf, i + j, sizeof( buf ) );
861                strlsize( buf2, j, sizeof( buf2 ) );
862                printf( "  Have: %s (%s verified)\n", buf, buf2 );
863            }
864
865            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
866              && tr_bencDictFindInt( t, "totalSize", &j ) )
867            {
868                strlsize( buf, j, sizeof( buf ) );
869                strlsize( buf2, i, sizeof( buf2 ) );
870                printf( "  Total size: %s (%s wanted)\n", buf, buf2 );
871            }
872            if( tr_bencDictFindInt( t, "downloadedEver", &i )
873              && tr_bencDictFindInt( t, "uploadedEver", &j ) )
874            {
875                strlsize( buf, i, sizeof( buf ) );
876                printf( "  Downloaded: %s\n", buf );
877                strlsize( buf, j, sizeof( buf ) );
878                printf( "  Uploaded: %s\n", buf );
879                strlratio( buf, j, i, sizeof( buf ) );
880                printf( "  Ratio: %s\n", buf );
881            }
882            if( tr_bencDictFindInt( t, "corruptEver", &i ) )
883            {
884                strlsize( buf, i, sizeof( buf ) );
885                printf( "  Corrupt DL: %s\n", buf );
886            }
887            if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str )
888                printf( "  Error: %s\n", str );
889
890            if( tr_bencDictFindInt( t, "peersConnected", &i )
891              && tr_bencDictFindInt( t, "peersGettingFromUs", &j )
892              && tr_bencDictFindInt( t, "peersSendingToUs", &k ) )
893            {
894                printf(
895                    "  Peers: "
896                    "connected to %" PRId64 ", "
897                                            "uploading to %" PRId64
898                    ", "
899                    "downloading from %"
900                    PRId64 "\n",
901                    i, j, k );
902            }
903
904            if( tr_bencDictFindList( t, "webseeds", &l )
905              && tr_bencDictFindInt( t, "webseedsSendingToUs", &i ) )
906            {
907                const int64_t n = tr_bencListSize( l );
908                if( n > 0 )
909                    printf(
910                        "  Web Seeds: downloading from %" PRId64 " of %"
911                        PRId64
912                        " web seeds\n", i, n );
913            }
914            printf( "\n" );
915
916            printf( "HISTORY\n" );
917            if( tr_bencDictFindInt( t, "addedDate", &i ) && i )
918            {
919                const time_t tt = i;
920                printf( "  Date added:      %s", ctime( &tt ) );
921            }
922            if( tr_bencDictFindInt( t, "doneDate", &i ) && i )
923            {
924                const time_t tt = i;
925                printf( "  Date finished:   %s", ctime( &tt ) );
926            }
927            if( tr_bencDictFindInt( t, "startDate", &i ) && i )
928            {
929                const time_t tt = i;
930                printf( "  Date started:    %s", ctime( &tt ) );
931            }
932            if( tr_bencDictFindInt( t, "activityDate", &i ) && i )
933            {
934                const time_t tt = i;
935                printf( "  Latest activity: %s", ctime( &tt ) );
936            }
937            printf( "\n" );
938
939            printf( "TRACKER\n" );
940            if( tr_bencDictFindInt( t, "lastAnnounceTime", &i ) )
941                printf( "  Latest announce: %s", getTrackerDateStr( (time_t)i, isStopped ) );
942            if( tr_bencDictFindStr( t, "announceURL", &str ) )
943                printf( "  Announce URL: %s\n", str );
944            if( tr_bencDictFindStr( t, "announceResponse", &str ) && str && *str )
945                printf( "  Announce response: %s\n", str );
946            if( tr_bencDictFindInt( t, "nextAnnounceTime", &i ) )
947                printf( "  Next announce:   %s", getTrackerDateStr( (time_t)i, isStopped ) );
948            if( tr_bencDictFindInt( t, "lastScrapeTime", &i ) )
949                printf( "  Latest scrape:   %s", getTrackerDateStr( (time_t)i, isStopped ) );
950            if( tr_bencDictFindStr( t, "scrapeResponse", &str ) )
951                printf( "  Scrape response: %s\n", str );
952            if( tr_bencDictFindInt( t, "nextScrapeTime", &i ) )
953                printf( "  Next scrape:     %s", getTrackerDateStr( (time_t)i, isStopped ) );
954            if( tr_bencDictFindInt( t, "seeders", &i ) && tr_bencDictFindInt( t, "leechers", &j ) )
955                printf( "  Tracker knows of %" PRId64 " seeders and %" PRId64 " leechers\n", i, j );
956            if( tr_bencDictFindInt( t, "timesCompleted", &i ) )
957                printf( "  Tracker has seen %" PRId64 " clients complete this torrent\n", i );
958            printf( "\n" );
959
960            printf( "ORIGINS\n" );
961            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i )
962            {
963                const time_t tt = i;
964                printf( "  Date created: %s", ctime( &tt ) );
965            }
966            if( tr_bencDictFindInt( t, "isPrivate", &i ) )
967                printf( "  Public torrent: %s\n", ( i ? "No" : "Yes" ) );
968            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
969                printf( "  Comment: %s\n", str );
970            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
971                printf( "  Creator: %s\n", str );
972            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
973                printf( "  Piece Count: %" PRId64 "\n", i );
974            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
975                printf( "  Piece Size: %" PRId64 "\n", i );
976        }
977    }
978}
979
980static void
981printFileList( tr_benc * top )
982{
983    tr_benc *args, *torrents;
984
985    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
986      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
987    {
988        int i, in;
989        for( i = 0, in = tr_bencListSize( torrents ); i < in; ++i )
990        {
991            tr_benc *    d = tr_bencListChild( torrents, i );
992            tr_benc *    files, *priorities, *wanteds;
993            const char * name;
994            if( tr_bencDictFindStr( d, "name", &name )
995              && tr_bencDictFindList( d, "files", &files )
996              && tr_bencDictFindList( d, "priorities", &priorities )
997              && tr_bencDictFindList( d, "wanted", &wanteds ) )
998            {
999                int j = 0, jn = tr_bencListSize( files );
1000                printf( "%s (%d files):\n", name, jn );
1001                printf( "%3s  %4s %8s %3s %9s  %s\n", "#", "Done",
1002                        "Priority", "Get", "Size",
1003                        "Name" );
1004                for( j = 0, jn = tr_bencListSize( files ); j < jn; ++j )
1005                {
1006                    int64_t      have;
1007                    int64_t      length;
1008                    int64_t      priority;
1009                    int64_t      wanted;
1010                    const char * filename;
1011                    tr_benc *    file = tr_bencListChild( files, j );
1012                    if( tr_bencDictFindInt( file, "length", &length )
1013                      && tr_bencDictFindStr( file, "name", &filename )
1014                      && tr_bencDictFindInt( file, "bytesCompleted", &have )
1015                      && tr_bencGetInt( tr_bencListChild( priorities,
1016                                                          j ), &priority )
1017                      && tr_bencGetInt( tr_bencListChild( wanteds,
1018                                                          j ), &wanted ) )
1019                    {
1020                        char         sizestr[64];
1021                        double       percent = (double)have / length;
1022                        const char * pristr;
1023                        strlsize( sizestr, length, sizeof( sizestr ) );
1024                        switch( priority )
1025                        {
1026                            case TR_PRI_LOW:
1027                                pristr = "Low"; break;
1028
1029                            case TR_PRI_HIGH:
1030                                pristr = "High"; break;
1031
1032                            default:
1033                                pristr = "Normal"; break;
1034                        }
1035                        printf( "%3d: %3.0f%% %-8s %-3s %9s  %s\n",
1036                                j,
1037                                floor( 100.0 * percent ),
1038                                pristr,
1039                                ( wanted ? "Yes" : "No" ),
1040                                sizestr,
1041                                filename );
1042                    }
1043                }
1044            }
1045        }
1046    }
1047}
1048
1049static void
1050printPeersImpl( tr_benc * peers )
1051{
1052    int i, n;
1053    printf( "%-20s  %-12s  %-5s %-6s  %-6s  %s\n",
1054            "Address", "Flags", "Done", "Down", "Up", "Client" );
1055    for( i = 0, n = tr_bencListSize( peers ); i < n; ++i )
1056    {
1057        double progress;
1058        const char * address, * client, * flagstr;
1059        int64_t rateToClient, rateToPeer;
1060        tr_benc * d = tr_bencListChild( peers, i );
1061
1062        if( tr_bencDictFindStr( d, "address", &address )
1063          && tr_bencDictFindStr( d, "clientName", &client )
1064          && tr_bencDictFindDouble( d, "progress", &progress )
1065          && tr_bencDictFindStr( d, "flagStr", &flagstr )
1066          && tr_bencDictFindInt( d, "rateToClient", &rateToClient )
1067          && tr_bencDictFindInt( d, "rateToPeer", &rateToPeer ) )
1068        {
1069            printf( "%-20s  %-12s  %-5.1f %6.1f  %6.1f  %s\n",
1070                    address, flagstr, (progress*100.0),
1071                    rateToClient / 1024.0,
1072                    rateToPeer / 1024.0,
1073                    client );
1074        }
1075    }
1076}
1077
1078static void
1079printPeers( tr_benc * top )
1080{
1081    tr_benc *args, *torrents;
1082
1083    if( tr_bencDictFindDict( top, "arguments", &args )
1084      && tr_bencDictFindList( args, "torrents", &torrents ) )
1085    {
1086        int i, n;
1087        for( i=0, n=tr_bencListSize( torrents ); i<n; ++i )
1088        {
1089            tr_benc * peers;
1090            tr_benc * torrent = tr_bencListChild( torrents, i );
1091            if( tr_bencDictFindList( torrent, "peers", &peers ) ) {
1092                printPeersImpl( peers );
1093                if( i+1<n )
1094                    printf( "\n" );
1095            }
1096        }
1097    }
1098}
1099
1100static void
1101printTorrentList( tr_benc * top )
1102{
1103    tr_benc *args, *list;
1104
1105    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1106      && ( tr_bencDictFindList( args, "torrents", &list ) ) )
1107    {
1108        int i, n;
1109        int64_t total_up = 0, total_down = 0, total_size = 0;
1110        char haveStr[32];
1111
1112        printf( "%-4s   %-4s  %9s  %-8s  %6s  %6s  %-5s  %-11s  %s\n",
1113                "ID", "Done", "Have", "ETA", "Up", "Down", "Ratio", "Status",
1114                "Name" );
1115        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
1116        {
1117            int64_t      id, eta, status, up, down;
1118            int64_t      sizeWhenDone, leftUntilDone;
1119            double       ratio;
1120            const char * name;
1121            tr_benc *   d = tr_bencListChild( list, i );
1122            if( tr_bencDictFindInt( d, "eta", &eta )
1123              && tr_bencDictFindInt( d, "id", &id )
1124              && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
1125              && tr_bencDictFindStr( d, "name", &name )
1126              && tr_bencDictFindInt( d, "rateDownload", &down )
1127              && tr_bencDictFindInt( d, "rateUpload", &up )
1128              && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
1129              && tr_bencDictFindInt( d, "status", &status )
1130              && tr_bencDictFindDouble( d, "uploadRatio", &ratio ) )
1131            {
1132                char etaStr[16];
1133                char statusStr[64];
1134                char ratioStr[32];
1135                char doneStr[8];
1136                int64_t error;
1137                char errorMark;
1138
1139                if( sizeWhenDone )
1140                    tr_snprintf( doneStr, sizeof( doneStr ), "%d%%", (int)( 100.0 * ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ) );
1141                else
1142                    tr_strlcpy( doneStr, "n/a", sizeof( doneStr ) );
1143
1144                strlsize( haveStr, sizeWhenDone - leftUntilDone, sizeof( haveStr ) );
1145
1146                if( leftUntilDone )
1147                    etaToString( etaStr, sizeof( etaStr ), eta );
1148                else
1149                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
1150                if( tr_bencDictFindInt( d, "error", &error ) && error )
1151                    errorMark = '*';
1152                else
1153                    errorMark = ' ';
1154                printf(
1155                    "%4d%c  %4s  %9s  %-8s  %6.1f  %6.1f  %5s  %-11s  %s\n",
1156                    (int)id, errorMark,
1157                    doneStr,
1158                    haveStr,
1159                    etaStr,
1160                    up / 1024.0,
1161                    down / 1024.0,
1162                    strlratio2( ratioStr, ratio, sizeof( ratioStr ) ),
1163                    getStatusString( d, statusStr, sizeof( statusStr ) ),
1164                    name );
1165
1166                total_up += up;
1167                total_down += down;
1168                total_size += sizeWhenDone - leftUntilDone;
1169            }
1170        }
1171
1172        printf( "Sum:         %9s             %6.1f  %6.1f\n",
1173                strlsize( haveStr, total_size, sizeof( haveStr ) ),
1174                total_up / 1024.0,
1175                total_down / 1024.0 );
1176    }
1177}
1178
1179static void
1180processResponse( const char * host,
1181                 int          port,
1182                 const void * response,
1183                 size_t       len )
1184{
1185    tr_benc top;
1186
1187    if( debug )
1188        fprintf( stderr, "got response:\n--------\n%*.*s\n--------\n",
1189                 (int)len, (int)len, (const char*) response );
1190
1191    if( tr_jsonParse( response, len, &top, NULL ) )
1192        tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len,
1193                 (int)len, (char*)response );
1194    else
1195    {
1196        int64_t      tag = -1;
1197        const char * str;
1198        tr_bencDictFindInt( &top, "tag", &tag );
1199
1200        switch( tag )
1201        {
1202            case TAG_SESSION:
1203                printSession( &top ); break;
1204
1205            case TAG_FILES:
1206                printFileList( &top ); break;
1207
1208            case TAG_DETAILS:
1209                printDetails( &top ); break;
1210
1211            case TAG_LIST:
1212                printTorrentList( &top ); break;
1213
1214            case TAG_PEERS:
1215                printPeers( &top ); break;
1216
1217            default:
1218                if( tr_bencDictFindStr( &top, "result", &str ) )
1219                    printf( "%s:%d responded: \"%s\"\n", host, port, str );
1220        }
1221
1222        tr_bencFree( &top );
1223    }
1224}
1225
1226/* very basic handling of cookies: when we get Set-Cookie, throw out all
1227 * the previous cookies...  T only uses one cookie (session_id) */
1228static size_t
1229parseResponseHeader( void *ptr, size_t size, size_t nmemb, void * stream UNUSED )
1230{
1231    const char * line = ptr;
1232    const size_t linelen = size * nmemb;
1233    const char * lineend = line + linelen;
1234    const char * key = "Set-Cookie: ";
1235    const size_t keylen = strlen( key );
1236    if( ( linelen >= keylen ) && !memcmp( line, key, keylen ) ) {
1237        const char * begin = line + keylen;
1238        const char * end = begin;
1239        while(( end!=lineend ) && !strchr("\r\n",*end))
1240            ++end;
1241        tr_free( cookies );
1242        cookies = tr_strndup( begin, end-begin );
1243    }
1244
1245    return linelen;
1246}
1247
1248static CURL*
1249tr_curl_easy_init( struct evbuffer * writebuf )
1250{
1251    CURL * curl = curl_easy_init( );
1252    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
1253    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
1254    curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
1255    curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader );
1256    curl_easy_setopt( curl, CURLOPT_POST, 1 );
1257    curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
1258    curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
1259    curl_easy_setopt( curl, CURLOPT_TIMEOUT, 60L );
1260    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
1261#ifdef HAVE_ZLIB
1262    curl_easy_setopt( curl, CURLOPT_ENCODING, "deflate" );
1263#endif
1264    if( cookies )
1265        curl_easy_setopt( curl, CURLOPT_COOKIE, cookies );
1266    if( netrc )
1267        curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
1268    if( auth )
1269        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
1270    return curl;
1271}
1272
1273static void
1274processRequests( const char *  host,
1275                 int           port,
1276                 const char ** reqs,
1277                 int           reqCount )
1278{
1279    int i;
1280    CURL * curl = NULL;
1281    struct evbuffer * buf = evbuffer_new( );
1282    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
1283
1284    for( i=0; i<reqCount; ++i )
1285    {
1286        CURLcode res;
1287        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
1288
1289        if( curl == NULL )
1290        {
1291            curl = tr_curl_easy_init( buf );
1292            curl_easy_setopt( curl, CURLOPT_URL, url );
1293        }
1294
1295        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
1296
1297        if( debug )
1298            fprintf( stderr, "posting:\n--------\n%s\n--------\n", reqs[i] );
1299        if( ( res = curl_easy_perform( curl ) ) )
1300            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
1301        else {
1302            long response;
1303            curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
1304            switch( response ) {
1305                case 200:
1306                    processResponse( host, port, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
1307                    break;
1308                case 409:
1309                    /* session_id cookie expired.  by the time we reach line this our
1310                     * curl header func has already found the new session_id, so make
1311                     * a new CURL* and try again... */
1312                    curl_easy_cleanup( curl );
1313                    curl = NULL;
1314                    --i;
1315                    break;
1316                default:
1317                    fprintf( stderr, "Unexpected response: %s\n", (char*)EVBUFFER_DATA(buf) );
1318                    break;
1319            }
1320        }
1321    }
1322
1323    /* cleanup */
1324    tr_free( url );
1325    evbuffer_free( buf );
1326    if( curl != NULL )
1327        curl_easy_cleanup( curl );
1328}
1329
1330int
1331main( int     argc,
1332      char ** argv )
1333{
1334    int    i;
1335    int    port = DEFAULT_PORT;
1336    char * host = NULL;
1337
1338    if( argc < 2 )
1339        showUsage( );
1340
1341    getHostAndPort( &argc, argv, &host, &port );
1342    if( host == NULL )
1343        host = tr_strdup( DEFAULT_HOST );
1344
1345    readargs( argc, (const char**)argv );
1346    if( reqCount )
1347        processRequests( host, port, (const char**)reqs, reqCount );
1348    else
1349        showUsage( );
1350
1351    for( i = 0; i < reqCount; ++i )
1352        tr_free( reqs[i] );
1353
1354    tr_free( host );
1355    return 0;
1356}
1357
Note: See TracBrowser for help on using the repository browser.