1 /*
2  * This file Copyright (C) 2008-2014 Mnemosyne LLC
3  *
4  * It may be used under the GNU GPL versions 2 or 3
5  * or any future license endorsed by Mnemosyne LLC.
6  *
7  */
8 
9 #include <assert.h>
10 #include <ctype.h> /* isspace */
11 #include <math.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h> /* strcmp */
15 
16 #include <event2/buffer.h>
17 #include <event2/util.h>
18 
19 #define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */
20 #include <curl/curl.h>
21 
22 #include <libtransmission/transmission.h>
23 #include <libtransmission/crypto-utils.h>
24 #include <libtransmission/error.h>
25 #include <libtransmission/file.h>
26 #include <libtransmission/log.h>
27 #include <libtransmission/rpcimpl.h>
28 #include <libtransmission/tr-getopt.h>
29 #include <libtransmission/utils.h>
30 #include <libtransmission/variant.h>
31 #include <libtransmission/version.h>
32 
33 #define MY_NAME "transmission-remote"
34 #define DEFAULT_HOST "localhost"
35 #define DEFAULT_PORT atoi(TR_DEFAULT_RPC_PORT_STR)
36 #define DEFAULT_URL TR_DEFAULT_RPC_URL_STR "rpc/"
37 
38 #define ARGUMENTS TR_KEY_arguments
39 
40 #define MEM_K 1024
41 #define MEM_B_STR "B"
42 #define MEM_K_STR "KiB"
43 #define MEM_M_STR "MiB"
44 #define MEM_G_STR "GiB"
45 #define MEM_T_STR "TiB"
46 
47 #define DISK_K 1000
48 #define DISK_B_STR "B"
49 #define DISK_K_STR "kB"
50 #define DISK_M_STR "MB"
51 #define DISK_G_STR "GB"
52 #define DISK_T_STR "TB"
53 
54 #define SPEED_K 1000
55 #define SPEED_B_STR "B/s"
56 #define SPEED_K_STR "kB/s"
57 #define SPEED_M_STR "MB/s"
58 #define SPEED_G_STR "GB/s"
59 #define SPEED_T_STR "TB/s"
60 
61 /***
62 ****
63 ****  Display Utilities
64 ****
65 ***/
66 
etaToString(char * buf,size_t buflen,int64_t eta)67 static void etaToString(char* buf, size_t buflen, int64_t eta)
68 {
69     if (eta < 0)
70     {
71         tr_snprintf(buf, buflen, "Unknown");
72     }
73     else if (eta < 60)
74     {
75         tr_snprintf(buf, buflen, "%" PRId64 " sec", eta);
76     }
77     else if (eta < (60 * 60))
78     {
79         tr_snprintf(buf, buflen, "%" PRId64 " min", eta / 60);
80     }
81     else if (eta < (60 * 60 * 24))
82     {
83         tr_snprintf(buf, buflen, "%" PRId64 " hrs", eta / (60 * 60));
84     }
85     else
86     {
87         tr_snprintf(buf, buflen, "%" PRId64 " days", eta / (60 * 60 * 24));
88     }
89 }
90 
tr_strltime(char * buf,int seconds,size_t buflen)91 static char* tr_strltime(char* buf, int seconds, size_t buflen)
92 {
93     int days;
94     int hours;
95     int minutes;
96     int total_seconds;
97     char b[128];
98     char d[128];
99     char h[128];
100     char m[128];
101     char s[128];
102     char t[128];
103 
104     if (seconds < 0)
105     {
106         seconds = 0;
107     }
108 
109     total_seconds = seconds;
110     days = seconds / 86400;
111     hours = (seconds % 86400) / 3600;
112     minutes = (seconds % 3600) / 60;
113     seconds = (seconds % 3600) % 60;
114 
115     tr_snprintf(d, sizeof(d), "%d %s", days, days == 1 ? "day" : "days");
116     tr_snprintf(h, sizeof(h), "%d %s", hours, hours == 1 ? "hour" : "hours");
117     tr_snprintf(m, sizeof(m), "%d %s", minutes, minutes == 1 ? "minute" : "minutes");
118     tr_snprintf(s, sizeof(s), "%d %s", seconds, seconds == 1 ? "second" : "seconds");
119     tr_snprintf(t, sizeof(t), "%d %s", total_seconds, total_seconds == 1 ? "second" : "seconds");
120 
121     if (days != 0)
122     {
123         if (days >= 4 || hours == 0)
124         {
125             tr_strlcpy(b, d, sizeof(b));
126         }
127         else
128         {
129             tr_snprintf(b, sizeof(b), "%s, %s", d, h);
130         }
131     }
132     else if (hours != 0)
133     {
134         if (hours >= 4 || minutes == 0)
135         {
136             tr_strlcpy(b, h, sizeof(b));
137         }
138         else
139         {
140             tr_snprintf(b, sizeof(b), "%s, %s", h, m);
141         }
142     }
143     else if (minutes != 0)
144     {
145         if (minutes >= 4 || seconds == 0)
146         {
147             tr_strlcpy(b, m, sizeof(b));
148         }
149         else
150         {
151             tr_snprintf(b, sizeof(b), "%s, %s", m, s);
152         }
153     }
154     else
155     {
156         tr_strlcpy(b, s, sizeof(b));
157     }
158 
159     tr_snprintf(buf, buflen, "%s (%s)", b, t);
160     return buf;
161 }
162 
strlpercent(char * buf,double x,size_t buflen)163 static char* strlpercent(char* buf, double x, size_t buflen)
164 {
165     return tr_strpercent(buf, x, buflen);
166 }
167 
strlratio2(char * buf,double ratio,size_t buflen)168 static char* strlratio2(char* buf, double ratio, size_t buflen)
169 {
170     return tr_strratio(buf, buflen, ratio, "Inf");
171 }
172 
strlratio(char * buf,int64_t numerator,int64_t denominator,size_t buflen)173 static char* strlratio(char* buf, int64_t numerator, int64_t denominator, size_t buflen)
174 {
175     double ratio;
176 
177     if (denominator != 0)
178     {
179         ratio = numerator / (double)denominator;
180     }
181     else if (numerator != 0)
182     {
183         ratio = TR_RATIO_INF;
184     }
185     else
186     {
187         ratio = TR_RATIO_NA;
188     }
189 
190     return strlratio2(buf, ratio, buflen);
191 }
192 
strlmem(char * buf,int64_t bytes,size_t buflen)193 static char* strlmem(char* buf, int64_t bytes, size_t buflen)
194 {
195     if (bytes == 0)
196     {
197         tr_strlcpy(buf, "None", buflen);
198     }
199     else
200     {
201         tr_formatter_mem_B(buf, bytes, buflen);
202     }
203 
204     return buf;
205 }
206 
strlsize(char * buf,int64_t bytes,size_t buflen)207 static char* strlsize(char* buf, int64_t bytes, size_t buflen)
208 {
209     if (bytes < 0)
210     {
211         tr_strlcpy(buf, "Unknown", buflen);
212     }
213     else if (bytes == 0)
214     {
215         tr_strlcpy(buf, "None", buflen);
216     }
217     else
218     {
219         tr_formatter_size_B(buf, bytes, buflen);
220     }
221 
222     return buf;
223 }
224 
225 enum
226 {
227     TAG_SESSION,
228     TAG_STATS,
229     TAG_DETAILS,
230     TAG_FILES,
231     TAG_LIST,
232     TAG_PEERS,
233     TAG_PIECES,
234     TAG_PORTTEST,
235     TAG_TORRENT_ADD,
236     TAG_TRACKERS
237 };
238 
getUsage(void)239 static char const* getUsage(void)
240 {
241     return MY_NAME " " LONG_VERSION_STRING "\n"
242         "A fast and easy BitTorrent client\n"
243         "https://transmissionbt.com/\n"
244         "\n"
245         "Usage: " MY_NAME " [host] [options]\n"
246         "       " MY_NAME " [port] [options]\n"
247         "       " MY_NAME " [host:port] [options]\n"
248         "       " MY_NAME " [http(s?)://host:port/transmission/] [options]\n"
249         "\n"
250         "See the man page for detailed explanations and many examples.";
251 }
252 
253 /***
254 ****
255 ****  Command-Line Arguments
256 ****
257 ***/
258 
259 static tr_option opts[] =
260 {
261     { 'a', "add", "Add torrent files by filename or URL", "a", false, NULL },
262     { 970, "alt-speed", "Use the alternate Limits", "as", false, NULL },
263     { 971, "no-alt-speed", "Don't use the alternate Limits", "AS", false, NULL },
264     { 972, "alt-speed-downlimit", "max alternate download speed (in "SPEED_K_STR ")", "asd", true, "<speed>" },
265     { 973, "alt-speed-uplimit", "max alternate upload speed (in "SPEED_K_STR ")", "asu", true, "<speed>" },
266     { 974, "alt-speed-scheduler", "Use the scheduled on/off times", "asc", false, NULL },
267     { 975, "no-alt-speed-scheduler", "Don't use the scheduled on/off times", "ASC", false, NULL },
268     { 976, "alt-speed-time-begin", "Time to start using the alt speed limits (in hhmm)", NULL, true, "<time>" },
269     { 977, "alt-speed-time-end", "Time to stop using the alt speed limits (in hhmm)", NULL, true, "<time>" },
270     { 978, "alt-speed-days", "Numbers for any/all days of the week - eg. \"1-7\"", NULL, true, "<days>" },
271     { 963, "blocklist-update", "Blocklist update", NULL, false, NULL },
272     { 'c', "incomplete-dir", "Where to store new torrents until they're complete", "c", true, "<dir>" },
273     { 'C', "no-incomplete-dir", "Don't store incomplete torrents in a different location", "C", false, NULL },
274     { 'b', "debug", "Print debugging information", "b", false, NULL },
275     { 'd', "downlimit", "Set the max download speed in "SPEED_K_STR " for the current torrent(s) or globally", "d", true,
276         "<speed>" },
277     { 'D', "no-downlimit", "Disable max download speed for the current torrent(s) or globally", "D", false, NULL },
278     { 'e', "cache", "Set the maximum size of the session's memory cache (in " MEM_M_STR ")", "e", true, "<size>" },
279     { 910, "encryption-required", "Encrypt all peer connections", "er", false, NULL },
280     { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", false, NULL },
281     { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", false, NULL },
282     { 850, "exit", "Tell the transmission session to shut down", NULL, false, NULL },
283     { 940, "files", "List the current torrent(s)' files", "f", false, NULL },
284     { 'g', "get", "Mark files for download", "g", true, "<files>" },
285     { 'G', "no-get", "Mark files for not downloading", "G", true, "<files>" },
286     { 'i', "info", "Show the current torrent(s)' details", "i", false, NULL },
287     { 940, "info-files", "List the current torrent(s)' files", "if", false, NULL },
288     { 941, "info-peers", "List the current torrent(s)' peers", "ip", false, NULL },
289     { 942, "info-pieces", "List the current torrent(s)' pieces", "ic", false, NULL },
290     { 943, "info-trackers", "List the current torrent(s)' trackers", "it", false, NULL },
291     { 920, "session-info", "Show the session's details", "si", false, NULL },
292     { 921, "session-stats", "Show the session's statistics", "st", false, NULL },
293     { 'l', "list", "List all torrents", "l", false, NULL },
294     { 'L', "labels", "Set the current torrents' labels", "L", true, "<label[,label...]>" },
295     { 960, "move", "Move current torrent's data to a new folder", NULL, true, "<path>" },
296     { 961, "find", "Tell Transmission where to find a torrent's data", NULL, true, "<path>" },
297     { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", false, NULL },
298     { 'M', "no-portmap", "Disable portmapping", "M", false, NULL },
299     { 'n', "auth", "Set username and password", "n", true, "<user:pw>" },
300     { 810, "authenv", "Set authentication info from the TR_AUTH environment variable (user:pw)", "ne", false, NULL },
301     { 'N', "netrc", "Set authentication info from a .netrc file", "N", true, "<file>" },
302     { 820, "ssl", "Use SSL when talking to daemon", NULL, false, NULL },
303     { 'o', "dht", "Enable distributed hash tables (DHT)", "o", false, NULL },
304     { 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", false, NULL },
305     { 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", true, "<port>" },
306     { 962, "port-test", "Port testing", "pt", false, NULL },
307     { 'P', "random-port", "Random port for incoming peers", "P", false, NULL },
308     { 900, "priority-high", "Try to download these file(s) first", "ph", true, "<files>" },
309     { 901, "priority-normal", "Try to download these file(s) normally", "pn", true, "<files>" },
310     { 902, "priority-low", "Try to download these file(s) last", "pl", true, "<files>" },
311     { 700, "bandwidth-high", "Give this torrent first chance at available bandwidth", "Bh", false, NULL },
312     { 701, "bandwidth-normal", "Give this torrent bandwidth left over by high priority torrents", "Bn", false, NULL },
313     { 702, "bandwidth-low", "Give this torrent bandwidth left over by high and normal priority torrents", "Bl", false, NULL },
314     { 600, "reannounce", "Reannounce the current torrent(s)", NULL, false, NULL },
315     { 'r', "remove", "Remove the current torrent(s)", "r", false, NULL },
316     { 930, "peers", "Set the maximum number of peers for the current torrent(s) or globally", "pr", true, "<max>" },
317     { 840, "remove-and-delete", "Remove the current torrent(s) and delete local data", "rad", false, NULL },
318     { 800, "torrent-done-script", "Specify a script to run when a torrent finishes", NULL, true, "<file>" },
319     { 801, "no-torrent-done-script", "Don't run a script when torrents finish", NULL, false, NULL },
320     { 950, "seedratio", "Let the current torrent(s) seed until a specific ratio", "sr", true, "ratio" },
321     { 951, "seedratio-default", "Let the current torrent(s) use the global seedratio settings", "srd", false, NULL },
322     { 952, "no-seedratio", "Let the current torrent(s) seed regardless of ratio", "SR", false, NULL },
323     { 953, "global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio",
324         "gsr", true, "ratio" },
325     { 954, "no-global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio",
326         "GSR", false, NULL },
327     { 710, "tracker-add", "Add a tracker to a torrent", "td", true, "<tracker>" },
328     { 712, "tracker-remove", "Remove a tracker from a torrent", "tr", true, "<trackerId>" },
329     { 's', "start", "Start the current torrent(s)", "s", false, NULL },
330     { 'S', "stop", "Stop the current torrent(s)", "S", false, NULL },
331     { 't', "torrent", "Set the current torrent(s)", "t", true, "<torrent>" },
332     { 990, "start-paused", "Start added torrents paused", NULL, false, NULL },
333     { 991, "no-start-paused", "Start added torrents unpaused", NULL, false, NULL },
334     { 992, "trash-torrent", "Delete torrents after adding", NULL, false, NULL },
335     { 993, "no-trash-torrent", "Do not delete torrents after adding", NULL, false, NULL },
336     { 984, "honor-session", "Make the current torrent(s) honor the session limits", "hl", false, NULL },
337     { 985, "no-honor-session", "Make the current torrent(s) not honor the session limits", "HL", false, NULL },
338     { 'u', "uplimit", "Set the max upload speed in "SPEED_K_STR " for the current torrent(s) or globally", "u", true,
339         "<speed>" },
340     { 'U', "no-uplimit", "Disable max upload speed for the current torrent(s) or globally", "U", false, NULL },
341     { 830, "utp", "Enable uTP for peer connections", NULL, false, NULL },
342     { 831, "no-utp", "Disable uTP for peer connections", NULL, false, NULL },
343     { 'v', "verify", "Verify the current torrent(s)", "v", false, NULL },
344     { 'V', "version", "Show version number and exit", "V", false, NULL },
345     { 'w', "download-dir", "When used in conjunction with --add, set the new torrent's download folder. "
346         "Otherwise, set the default download folder", "w", true, "<path>" },
347     { 'x', "pex", "Enable peer exchange (PEX)", "x", false, NULL },
348     { 'X', "no-pex", "Disable peer exchange (PEX)", "X", false, NULL },
349     { 'y', "lpd", "Enable local peer discovery (LPD)", "y", false, NULL },
350     { 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", false, NULL },
351     { 941, "peer-info", "List the current torrent(s)' peers", "pi", false, NULL },
352     { 0, NULL, NULL, NULL, false, NULL }
353 };
354 
showUsage(void)355 static void showUsage(void)
356 {
357     tr_getopt_usage(MY_NAME, getUsage(), opts);
358 }
359 
numarg(char const * arg)360 static int numarg(char const* arg)
361 {
362     char* end = NULL;
363     long const num = strtol(arg, &end, 10);
364 
365     if (*end != '\0')
366     {
367         fprintf(stderr, "Not a number: \"%s\"\n", arg);
368         showUsage();
369         exit(EXIT_FAILURE);
370     }
371 
372     return num;
373 }
374 
375 enum
376 {
377     MODE_TORRENT_START = (1 << 0),
378     MODE_TORRENT_STOP = (1 << 1),
379     MODE_TORRENT_VERIFY = (1 << 2),
380     MODE_TORRENT_REANNOUNCE = (1 << 3),
381     MODE_TORRENT_SET = (1 << 4),
382     MODE_TORRENT_GET = (1 << 5),
383     MODE_TORRENT_ADD = (1 << 6),
384     MODE_TORRENT_REMOVE = (1 << 7),
385     MODE_TORRENT_SET_LOCATION = (1 << 8),
386     MODE_SESSION_SET = (1 << 9),
387     MODE_SESSION_GET = (1 << 10),
388     MODE_SESSION_STATS = (1 << 11),
389     MODE_SESSION_CLOSE = (1 << 12),
390     MODE_BLOCKLIST_UPDATE = (1 << 13),
391     MODE_PORT_TEST = (1 << 14)
392 };
393 
getOptMode(int val)394 static int getOptMode(int val)
395 {
396     switch (val)
397     {
398     case TR_OPT_ERR:
399     case TR_OPT_UNK:
400     case 'a': /* add torrent */
401     case 'b': /* debug */
402     case 'n': /* auth */
403     case 810: /* authenv */
404     case 'N': /* netrc */
405     case 820: /* UseSSL */
406     case 't': /* set current torrent */
407     case 'V': /* show version number */
408         return 0;
409 
410     case 'c': /* incomplete-dir */
411     case 'C': /* no-incomplete-dir */
412     case 'e': /* cache */
413     case 'm': /* portmap */
414     case 'M': /* "no-portmap */
415     case 'o': /* dht */
416     case 'O': /* no-dht */
417     case 'p': /* incoming peer port */
418     case 'P': /* random incoming peer port */
419     case 'x': /* pex */
420     case 'X': /* no-pex */
421     case 'y': /* lpd */
422     case 'Y': /* no-lpd */
423     case 800: /* torrent-done-script */
424     case 801: /* no-torrent-done-script */
425     case 830: /* utp */
426     case 831: /* no-utp */
427     case 970: /* alt-speed */
428     case 971: /* no-alt-speed */
429     case 972: /* alt-speed-downlimit */
430     case 973: /* alt-speed-uplimit */
431     case 974: /* alt-speed-scheduler */
432     case 975: /* no-alt-speed-scheduler */
433     case 976: /* alt-speed-time-begin */
434     case 977: /* alt-speed-time-end */
435     case 978: /* alt-speed-days */
436     case 910: /* encryption-required */
437     case 911: /* encryption-preferred */
438     case 912: /* encryption-tolerated */
439     case 953: /* global-seedratio */
440     case 954: /* no-global-seedratio */
441     case 990: /* start-paused */
442     case 991: /* no-start-paused */
443     case 992: /* trash-torrent */
444     case 993: /* no-trash-torrent */
445         return MODE_SESSION_SET;
446 
447     case 'L': /* labels */
448     case 712: /* tracker-remove */
449     case 950: /* seedratio */
450     case 951: /* seedratio-default */
451     case 952: /* no-seedratio */
452     case 984: /* honor-session */
453     case 985: /* no-honor-session */
454         return MODE_TORRENT_SET;
455 
456     case 920: /* session-info */
457         return MODE_SESSION_GET;
458 
459     case 'g': /* get */
460     case 'G': /* no-get */
461     case 700: /* torrent priority-high */
462     case 701: /* torrent priority-normal */
463     case 702: /* torrent priority-low */
464     case 710: /* tracker-add */
465     case 900: /* file priority-high */
466     case 901: /* file priority-normal */
467     case 902: /* file priority-low */
468         return MODE_TORRENT_SET | MODE_TORRENT_ADD;
469 
470     case 961: /* find */
471         return MODE_TORRENT_SET_LOCATION | MODE_TORRENT_ADD;
472 
473     case 'i': /* info */
474     case 'l': /* list all torrents */
475     case 940: /* info-files */
476     case 941: /* info-peer */
477     case 942: /* info-pieces */
478     case 943: /* info-tracker */
479         return MODE_TORRENT_GET;
480 
481     case 'd': /* download speed limit */
482     case 'D': /* no download speed limit */
483     case 'u': /* upload speed limit */
484     case 'U': /* no upload speed limit */
485     case 930: /* peers */
486         return MODE_SESSION_SET | MODE_TORRENT_SET;
487 
488     case 's': /* start */
489         return MODE_TORRENT_START | MODE_TORRENT_ADD;
490 
491     case 'S': /* stop */
492         return MODE_TORRENT_STOP | MODE_TORRENT_ADD;
493 
494     case 'w': /* download-dir */
495         return MODE_SESSION_SET | MODE_TORRENT_ADD;
496 
497     case 850: /* session-close */
498         return MODE_SESSION_CLOSE;
499 
500     case 963: /* blocklist-update */
501         return MODE_BLOCKLIST_UPDATE;
502 
503     case 921: /* session-stats */
504         return MODE_SESSION_STATS;
505 
506     case 'v': /* verify */
507         return MODE_TORRENT_VERIFY;
508 
509     case 600: /* reannounce */
510         return MODE_TORRENT_REANNOUNCE;
511 
512     case 962: /* port-test */
513         return MODE_PORT_TEST;
514 
515     case 'r': /* remove */
516     case 840: /* remove and delete */
517         return MODE_TORRENT_REMOVE;
518 
519     case 960: /* move */
520         return MODE_TORRENT_SET_LOCATION;
521 
522     default:
523         fprintf(stderr, "unrecognized argument %d\n", val);
524         assert("unrecognized argument" && 0);
525         return 0;
526     }
527 }
528 
529 static bool debug = false;
530 static char* auth = NULL;
531 static char* netrc = NULL;
532 static char* sessionId = NULL;
533 static bool UseSSL = false;
534 
getEncodedMetainfo(char const * filename)535 static char* getEncodedMetainfo(char const* filename)
536 {
537     size_t len = 0;
538     char* b64 = NULL;
539     uint8_t* buf = tr_loadFile(filename, &len, NULL);
540 
541     if (buf != NULL)
542     {
543         b64 = tr_base64_encode(buf, len, NULL);
544         tr_free(buf);
545     }
546 
547     return b64;
548 }
549 
addIdArg(tr_variant * args,char const * id,char const * fallback)550 static void addIdArg(tr_variant* args, char const* id, char const* fallback)
551 {
552     if (tr_str_is_empty(id))
553     {
554         id = fallback;
555 
556         if (tr_str_is_empty(id))
557         {
558             fprintf(stderr, "No torrent specified!  Please use the -t option first.\n");
559             id = "-1"; /* no torrent will have this ID, so will act as a no-op */
560         }
561     }
562 
563     if (tr_strcmp0(id, "active") == 0)
564     {
565         tr_variantDictAddStr(args, TR_KEY_ids, "recently-active");
566     }
567     else if (strcmp(id, "all") != 0)
568     {
569         bool isList = strchr(id, ',') != NULL || strchr(id, '-') != NULL;
570         bool isNum = true;
571 
572         for (char const* pch = id; isNum && *pch != '\0'; ++pch)
573         {
574             isNum = isdigit(*pch);
575         }
576 
577         if (isNum || isList)
578         {
579             tr_rpc_parse_list_str(tr_variantDictAdd(args, TR_KEY_ids), id, strlen(id));
580         }
581         else
582         {
583             tr_variantDictAddStr(args, TR_KEY_ids, id); /* it's a torrent sha hash */
584         }
585     }
586 }
587 
addTime(tr_variant * args,tr_quark const key,char const * arg)588 static void addTime(tr_variant* args, tr_quark const key, char const* arg)
589 {
590     int time;
591     bool success = false;
592 
593     if (arg != NULL && strlen(arg) == 4)
594     {
595         char const hh[3] = { arg[0], arg[1], '\0' };
596         char const mm[3] = { arg[2], arg[3], '\0' };
597         int const hour = atoi(hh);
598         int const min = atoi(mm);
599 
600         if (0 <= hour && hour < 24 && 0 <= min && min < 60)
601         {
602             time = min + (hour * 60);
603             success = true;
604         }
605     }
606 
607     if (success)
608     {
609         tr_variantDictAddInt(args, key, time);
610     }
611     else
612     {
613         fprintf(stderr, "Please specify the time of day in 'hhmm' format.\n");
614     }
615 }
616 
addDays(tr_variant * args,tr_quark const key,char const * arg)617 static void addDays(tr_variant* args, tr_quark const key, char const* arg)
618 {
619     int days = 0;
620 
621     if (arg != NULL)
622     {
623         int valueCount;
624         int* values;
625 
626         values = tr_parseNumberRange(arg, TR_BAD_SIZE, &valueCount);
627 
628         for (int i = 0; i < valueCount; ++i)
629         {
630             if (values[i] < 0 || values[i] > 7)
631             {
632                 continue;
633             }
634 
635             if (values[i] == 7)
636             {
637                 values[i] = 0;
638             }
639 
640             days |= 1 << values[i];
641         }
642 
643         tr_free(values);
644     }
645 
646     if (days != 0)
647     {
648         tr_variantDictAddInt(args, key, days);
649     }
650     else
651     {
652         fprintf(stderr, "Please specify the days of the week in '1-3,4,7' format.\n");
653     }
654 }
655 
addLabels(tr_variant * args,char const * arg)656 static void addLabels(tr_variant* args, char const* arg)
657 {
658     tr_variant* labels;
659     if (!tr_variantDictFindList(args, TR_KEY_labels, &labels))
660     {
661         labels = tr_variantDictAddList(args, TR_KEY_labels, 10);
662     }
663 
664     char* argcpy = tr_strdup(arg);
665     char* const tmp = argcpy; /* save copied string start pointer to free later */
666     char* token;
667     while ((token = tr_strsep(&argcpy, ",")) != NULL)
668     {
669         tr_strstrip(token);
670         if (!tr_str_is_empty(token))
671         {
672             tr_variantListAddStr(labels, token);
673         }
674     }
675 
676     tr_free(tmp);
677 }
678 
addFiles(tr_variant * args,tr_quark const key,char const * arg)679 static void addFiles(tr_variant* args, tr_quark const key, char const* arg)
680 {
681     tr_variant* files = tr_variantDictAddList(args, key, 100);
682 
683     if (tr_str_is_empty(arg))
684     {
685         fprintf(stderr, "No files specified!\n");
686         arg = "-1"; /* no file will have this index, so should be a no-op */
687     }
688 
689     if (strcmp(arg, "all") != 0)
690     {
691         int valueCount;
692         int* values = tr_parseNumberRange(arg, TR_BAD_SIZE, &valueCount);
693 
694         for (int i = 0; i < valueCount; ++i)
695         {
696             tr_variantListAddInt(files, values[i]);
697         }
698 
699         tr_free(values);
700     }
701 }
702 
703 static tr_quark const files_keys[] =
704 {
705     TR_KEY_files,
706     TR_KEY_name,
707     TR_KEY_priorities,
708     TR_KEY_wanted
709 };
710 
711 static tr_quark const details_keys[] =
712 {
713     TR_KEY_activityDate,
714     TR_KEY_addedDate,
715     TR_KEY_bandwidthPriority,
716     TR_KEY_comment,
717     TR_KEY_corruptEver,
718     TR_KEY_creator,
719     TR_KEY_dateCreated,
720     TR_KEY_desiredAvailable,
721     TR_KEY_doneDate,
722     TR_KEY_downloadDir,
723     TR_KEY_downloadedEver,
724     TR_KEY_downloadLimit,
725     TR_KEY_downloadLimited,
726     TR_KEY_error,
727     TR_KEY_errorString,
728     TR_KEY_eta,
729     TR_KEY_hashString,
730     TR_KEY_haveUnchecked,
731     TR_KEY_haveValid,
732     TR_KEY_honorsSessionLimits,
733     TR_KEY_id,
734     TR_KEY_isFinished,
735     TR_KEY_isPrivate,
736     TR_KEY_labels,
737     TR_KEY_leftUntilDone,
738     TR_KEY_magnetLink,
739     TR_KEY_name,
740     TR_KEY_peersConnected,
741     TR_KEY_peersGettingFromUs,
742     TR_KEY_peersSendingToUs,
743     TR_KEY_peer_limit,
744     TR_KEY_pieceCount,
745     TR_KEY_pieceSize,
746     TR_KEY_rateDownload,
747     TR_KEY_rateUpload,
748     TR_KEY_recheckProgress,
749     TR_KEY_secondsDownloading,
750     TR_KEY_secondsSeeding,
751     TR_KEY_seedRatioMode,
752     TR_KEY_seedRatioLimit,
753     TR_KEY_sizeWhenDone,
754     TR_KEY_startDate,
755     TR_KEY_status,
756     TR_KEY_totalSize,
757     TR_KEY_uploadedEver,
758     TR_KEY_uploadLimit,
759     TR_KEY_uploadLimited,
760     TR_KEY_webseeds,
761     TR_KEY_webseedsSendingToUs
762 };
763 
764 static tr_quark const list_keys[] =
765 {
766     TR_KEY_error,
767     TR_KEY_errorString,
768     TR_KEY_eta,
769     TR_KEY_id,
770     TR_KEY_isFinished,
771     TR_KEY_leftUntilDone,
772     TR_KEY_name,
773     TR_KEY_peersGettingFromUs,
774     TR_KEY_peersSendingToUs,
775     TR_KEY_rateDownload,
776     TR_KEY_rateUpload,
777     TR_KEY_sizeWhenDone,
778     TR_KEY_status,
779     TR_KEY_uploadRatio
780 };
781 
writeFunc(void * ptr,size_t size,size_t nmemb,void * buf)782 static size_t writeFunc(void* ptr, size_t size, size_t nmemb, void* buf)
783 {
784     size_t const byteCount = size * nmemb;
785     evbuffer_add(buf, ptr, byteCount);
786     return byteCount;
787 }
788 
789 /* look for a session id in the header in case the server gives back a 409 */
parseResponseHeader(void * ptr,size_t size,size_t nmemb,void * stream UNUSED)790 static size_t parseResponseHeader(void* ptr, size_t size, size_t nmemb, void* stream UNUSED)
791 {
792     char const* line = ptr;
793     size_t const line_len = size * nmemb;
794     char const* key = TR_RPC_SESSION_ID_HEADER ": ";
795     size_t const key_len = strlen(key);
796 
797     if (line_len >= key_len && evutil_ascii_strncasecmp(line, key, key_len) == 0)
798     {
799         char const* begin = line + key_len;
800         char const* end = begin;
801 
802         while (!isspace(*end))
803         {
804             ++end;
805         }
806 
807         tr_free(sessionId);
808         sessionId = tr_strndup(begin, end - begin);
809     }
810 
811     return line_len;
812 }
813 
getTimeoutSecs(char const * req)814 static long getTimeoutSecs(char const* req)
815 {
816     if (strstr(req, "\"method\":\"blocklist-update\"") != NULL)
817     {
818         return 300L;
819     }
820 
821     return 60L; /* default value */
822 }
823 
getStatusString(tr_variant * t,char * buf,size_t buflen)824 static char* getStatusString(tr_variant* t, char* buf, size_t buflen)
825 {
826     int64_t status;
827     bool boolVal;
828 
829     if (!tr_variantDictFindInt(t, TR_KEY_status, &status))
830     {
831         *buf = '\0';
832     }
833     else
834     {
835         switch (status)
836         {
837         case TR_STATUS_DOWNLOAD_WAIT:
838         case TR_STATUS_SEED_WAIT:
839             tr_strlcpy(buf, "Queued", buflen);
840             break;
841 
842         case TR_STATUS_STOPPED:
843             if (tr_variantDictFindBool(t, TR_KEY_isFinished, &boolVal) && boolVal)
844             {
845                 tr_strlcpy(buf, "Finished", buflen);
846             }
847             else
848             {
849                 tr_strlcpy(buf, "Stopped", buflen);
850             }
851 
852             break;
853 
854         case TR_STATUS_CHECK_WAIT:
855         case TR_STATUS_CHECK:
856             {
857                 char const* str = status == TR_STATUS_CHECK_WAIT ? "Will Verify" : "Verifying";
858                 double percent;
859 
860                 if (tr_variantDictFindReal(t, TR_KEY_recheckProgress, &percent))
861                 {
862                     tr_snprintf(buf, buflen, "%s (%.0f%%)", str, (floor)(percent * 100.0));
863                 }
864                 else
865                 {
866                     tr_strlcpy(buf, str, buflen);
867                 }
868 
869                 break;
870             }
871 
872         case TR_STATUS_DOWNLOAD:
873         case TR_STATUS_SEED:
874             {
875                 int64_t fromUs = 0;
876                 int64_t toUs = 0;
877                 tr_variantDictFindInt(t, TR_KEY_peersGettingFromUs, &fromUs);
878                 tr_variantDictFindInt(t, TR_KEY_peersSendingToUs, &toUs);
879 
880                 if (fromUs != 0 && toUs != 0)
881                 {
882                     tr_strlcpy(buf, "Up & Down", buflen);
883                 }
884                 else if (toUs != 0)
885                 {
886                     tr_strlcpy(buf, "Downloading", buflen);
887                 }
888                 else if (fromUs != 0)
889                 {
890                     int64_t leftUntilDone = 0;
891                     tr_variantDictFindInt(t, TR_KEY_leftUntilDone, &leftUntilDone);
892 
893                     if (leftUntilDone > 0)
894                     {
895                         tr_strlcpy(buf, "Uploading", buflen);
896                     }
897                     else
898                     {
899                         tr_strlcpy(buf, "Seeding", buflen);
900                     }
901                 }
902                 else
903                 {
904                     tr_strlcpy(buf, "Idle", buflen);
905                 }
906 
907                 break;
908             }
909 
910         default:
911             tr_strlcpy(buf, "Unknown", buflen);
912             break;
913         }
914     }
915 
916     return buf;
917 }
918 
919 static char const* bandwidthPriorityNames[] =
920 {
921     "Low",
922     "Normal",
923     "High",
924     "Invalid"
925 };
926 
printDetails(tr_variant * top)927 static void printDetails(tr_variant* top)
928 {
929     tr_variant* args;
930     tr_variant* torrents;
931 
932     if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &torrents))
933     {
934         for (int ti = 0, tCount = tr_variantListSize(torrents); ti < tCount; ++ti)
935         {
936             tr_variant* t = tr_variantListChild(torrents, ti);
937             tr_variant* l;
938             char const* str;
939             char buf[512];
940             char buf2[512];
941             int64_t i;
942             int64_t j;
943             int64_t k;
944             bool boolVal;
945             double d;
946 
947             printf("NAME\n");
948 
949             if (tr_variantDictFindInt(t, TR_KEY_id, &i))
950             {
951                 printf("  Id: %" PRId64 "\n", i);
952             }
953 
954             if (tr_variantDictFindStr(t, TR_KEY_name, &str, NULL))
955             {
956                 printf("  Name: %s\n", str);
957             }
958 
959             if (tr_variantDictFindStr(t, TR_KEY_hashString, &str, NULL))
960             {
961                 printf("  Hash: %s\n", str);
962             }
963 
964             if (tr_variantDictFindStr(t, TR_KEY_magnetLink, &str, NULL))
965             {
966                 printf("  Magnet: %s\n", str);
967             }
968 
969             if (tr_variantDictFindList(t, TR_KEY_labels, &l))
970             {
971                 int const n = tr_variantListSize(l);
972                 char const* str;
973                 printf("  Labels: ");
974                 for (int i = 0; i < n; i++)
975                 {
976                     if (tr_variantGetStr(tr_variantListChild(l, i), &str, NULL))
977                     {
978                         printf(i == 0 ? "%s" : ", %s", str);
979                     }
980                 }
981 
982                 printf("\n");
983             }
984 
985             printf("\n");
986 
987             printf("TRANSFER\n");
988             getStatusString(t, buf, sizeof(buf));
989             printf("  State: %s\n", buf);
990 
991             if (tr_variantDictFindStr(t, TR_KEY_downloadDir, &str, NULL))
992             {
993                 printf("  Location: %s\n", str);
994             }
995 
996             if (tr_variantDictFindInt(t, TR_KEY_sizeWhenDone, &i) && tr_variantDictFindInt(t, TR_KEY_leftUntilDone, &j))
997             {
998                 strlpercent(buf, 100.0 * (i - j) / i, sizeof(buf));
999                 printf("  Percent Done: %s%%\n", buf);
1000             }
1001 
1002             if (tr_variantDictFindInt(t, TR_KEY_eta, &i))
1003             {
1004                 printf("  ETA: %s\n", tr_strltime(buf, i, sizeof(buf)));
1005             }
1006 
1007             if (tr_variantDictFindInt(t, TR_KEY_rateDownload, &i))
1008             {
1009                 printf("  Download Speed: %s\n", tr_formatter_speed_KBps(buf, i / (double)tr_speed_K, sizeof(buf)));
1010             }
1011 
1012             if (tr_variantDictFindInt(t, TR_KEY_rateUpload, &i))
1013             {
1014                 printf("  Upload Speed: %s\n", tr_formatter_speed_KBps(buf, i / (double)tr_speed_K, sizeof(buf)));
1015             }
1016 
1017             if (tr_variantDictFindInt(t, TR_KEY_haveUnchecked, &i) && tr_variantDictFindInt(t, TR_KEY_haveValid, &j))
1018             {
1019                 strlsize(buf, i + j, sizeof(buf));
1020                 strlsize(buf2, j, sizeof(buf2));
1021                 printf("  Have: %s (%s verified)\n", buf, buf2);
1022             }
1023 
1024             if (tr_variantDictFindInt(t, TR_KEY_sizeWhenDone, &i))
1025             {
1026                 if (i < 1)
1027                 {
1028                     printf("  Availability: None\n");
1029                 }
1030 
1031                 if (tr_variantDictFindInt(t, TR_KEY_desiredAvailable, &j) && tr_variantDictFindInt(t, TR_KEY_leftUntilDone, &k))
1032                 {
1033                     j += i - k;
1034                     strlpercent(buf, 100.0 * j / i, sizeof(buf));
1035                     printf("  Availability: %s%%\n", buf);
1036                 }
1037 
1038                 if (tr_variantDictFindInt(t, TR_KEY_totalSize, &j))
1039                 {
1040                     strlsize(buf2, i, sizeof(buf2));
1041                     strlsize(buf, j, sizeof(buf));
1042                     printf("  Total size: %s (%s wanted)\n", buf, buf2);
1043                 }
1044             }
1045 
1046             if (tr_variantDictFindInt(t, TR_KEY_downloadedEver, &i) && tr_variantDictFindInt(t, TR_KEY_uploadedEver, &j))
1047             {
1048                 strlsize(buf, i, sizeof(buf));
1049                 printf("  Downloaded: %s\n", buf);
1050                 strlsize(buf, j, sizeof(buf));
1051                 printf("  Uploaded: %s\n", buf);
1052                 strlratio(buf, j, i, sizeof(buf));
1053                 printf("  Ratio: %s\n", buf);
1054             }
1055 
1056             if (tr_variantDictFindInt(t, TR_KEY_corruptEver, &i))
1057             {
1058                 strlsize(buf, i, sizeof(buf));
1059                 printf("  Corrupt DL: %s\n", buf);
1060             }
1061 
1062             if (tr_variantDictFindStr(t, TR_KEY_errorString, &str, NULL) && !tr_str_is_empty(str) &&
1063                 tr_variantDictFindInt(t, TR_KEY_error, &i) && i != 0)
1064             {
1065                 switch (i)
1066                 {
1067                 case TR_STAT_TRACKER_WARNING:
1068                     printf("  Tracker gave a warning: %s\n", str);
1069                     break;
1070 
1071                 case TR_STAT_TRACKER_ERROR:
1072                     printf("  Tracker gave an error: %s\n", str);
1073                     break;
1074 
1075                 case TR_STAT_LOCAL_ERROR:
1076                     printf("  Error: %s\n", str);
1077                     break;
1078 
1079                 default:
1080                     break; /* no error */
1081                 }
1082             }
1083 
1084             if (tr_variantDictFindInt(t, TR_KEY_peersConnected, &i) &&
1085                 tr_variantDictFindInt(t, TR_KEY_peersGettingFromUs, &j) &&
1086                 tr_variantDictFindInt(t, TR_KEY_peersSendingToUs, &k))
1087             {
1088                 printf("  Peers: connected to %" PRId64 ", uploading to %" PRId64 ", downloading from %" PRId64 "\n", i, j, k);
1089             }
1090 
1091             if (tr_variantDictFindList(t, TR_KEY_webseeds, &l) && tr_variantDictFindInt(t, TR_KEY_webseedsSendingToUs, &i))
1092             {
1093                 int64_t const n = tr_variantListSize(l);
1094 
1095                 if (n > 0)
1096                 {
1097                     printf("  Web Seeds: downloading from %" PRId64 " of %" PRId64 " web seeds\n", i, n);
1098                 }
1099             }
1100 
1101             printf("\n");
1102 
1103             printf("HISTORY\n");
1104 
1105             if (tr_variantDictFindInt(t, TR_KEY_addedDate, &i) && i != 0)
1106             {
1107                 time_t const tt = i;
1108                 printf("  Date added:       %s", ctime(&tt));
1109             }
1110 
1111             if (tr_variantDictFindInt(t, TR_KEY_doneDate, &i) && i != 0)
1112             {
1113                 time_t const tt = i;
1114                 printf("  Date finished:    %s", ctime(&tt));
1115             }
1116 
1117             if (tr_variantDictFindInt(t, TR_KEY_startDate, &i) && i != 0)
1118             {
1119                 time_t const tt = i;
1120                 printf("  Date started:     %s", ctime(&tt));
1121             }
1122 
1123             if (tr_variantDictFindInt(t, TR_KEY_activityDate, &i) && i != 0)
1124             {
1125                 time_t const tt = i;
1126                 printf("  Latest activity:  %s", ctime(&tt));
1127             }
1128 
1129             if (tr_variantDictFindInt(t, TR_KEY_secondsDownloading, &i) && i > 0)
1130             {
1131                 printf("  Downloading Time: %s\n", tr_strltime(buf, i, sizeof(buf)));
1132             }
1133 
1134             if (tr_variantDictFindInt(t, TR_KEY_secondsSeeding, &i) && i > 0)
1135             {
1136                 printf("  Seeding Time:     %s\n", tr_strltime(buf, i, sizeof(buf)));
1137             }
1138 
1139             printf("\n");
1140 
1141             printf("ORIGINS\n");
1142 
1143             if (tr_variantDictFindInt(t, TR_KEY_dateCreated, &i) && i != 0)
1144             {
1145                 time_t const tt = i;
1146                 printf("  Date created: %s", ctime(&tt));
1147             }
1148 
1149             if (tr_variantDictFindBool(t, TR_KEY_isPrivate, &boolVal))
1150             {
1151                 printf("  Public torrent: %s\n", (boolVal ? "No" : "Yes"));
1152             }
1153 
1154             if (tr_variantDictFindStr(t, TR_KEY_comment, &str, NULL) && !tr_str_is_empty(str))
1155             {
1156                 printf("  Comment: %s\n", str);
1157             }
1158 
1159             if (tr_variantDictFindStr(t, TR_KEY_creator, &str, NULL) && !tr_str_is_empty(str))
1160             {
1161                 printf("  Creator: %s\n", str);
1162             }
1163 
1164             if (tr_variantDictFindInt(t, TR_KEY_pieceCount, &i))
1165             {
1166                 printf("  Piece Count: %" PRId64 "\n", i);
1167             }
1168 
1169             if (tr_variantDictFindInt(t, TR_KEY_pieceSize, &i))
1170             {
1171                 printf("  Piece Size: %s\n", strlmem(buf, i, sizeof(buf)));
1172             }
1173 
1174             printf("\n");
1175 
1176             printf("LIMITS & BANDWIDTH\n");
1177 
1178             if (tr_variantDictFindBool(t, TR_KEY_downloadLimited, &boolVal) &&
1179                 tr_variantDictFindInt(t, TR_KEY_downloadLimit, &i))
1180             {
1181                 printf("  Download Limit: ");
1182 
1183                 if (boolVal)
1184                 {
1185                     printf("%s\n", tr_formatter_speed_KBps(buf, i, sizeof(buf)));
1186                 }
1187                 else
1188                 {
1189                     printf("Unlimited\n");
1190                 }
1191             }
1192 
1193             if (tr_variantDictFindBool(t, TR_KEY_uploadLimited, &boolVal) && tr_variantDictFindInt(t, TR_KEY_uploadLimit, &i))
1194             {
1195                 printf("  Upload Limit: ");
1196 
1197                 if (boolVal)
1198                 {
1199                     printf("%s\n", tr_formatter_speed_KBps(buf, i, sizeof(buf)));
1200                 }
1201                 else
1202                 {
1203                     printf("Unlimited\n");
1204                 }
1205             }
1206 
1207             if (tr_variantDictFindInt(t, TR_KEY_seedRatioMode, &i))
1208             {
1209                 switch (i)
1210                 {
1211                 case TR_RATIOLIMIT_GLOBAL:
1212                     printf("  Ratio Limit: Default\n");
1213                     break;
1214 
1215                 case TR_RATIOLIMIT_SINGLE:
1216                     if (tr_variantDictFindReal(t, TR_KEY_seedRatioLimit, &d))
1217                     {
1218                         printf("  Ratio Limit: %s\n", strlratio2(buf, d, sizeof(buf)));
1219                     }
1220 
1221                     break;
1222 
1223                 case TR_RATIOLIMIT_UNLIMITED:
1224                     printf("  Ratio Limit: Unlimited\n");
1225                     break;
1226 
1227                 default:
1228                     break;
1229                 }
1230             }
1231 
1232             if (tr_variantDictFindBool(t, TR_KEY_honorsSessionLimits, &boolVal))
1233             {
1234                 printf("  Honors Session Limits: %s\n", (boolVal ? "Yes" : "No"));
1235             }
1236 
1237             if (tr_variantDictFindInt(t, TR_KEY_peer_limit, &i))
1238             {
1239                 printf("  Peer limit: %" PRId64 "\n", i);
1240             }
1241 
1242             if (tr_variantDictFindInt(t, TR_KEY_bandwidthPriority, &i))
1243             {
1244                 printf("  Bandwidth Priority: %s\n", bandwidthPriorityNames[(i + 1) & 3]);
1245             }
1246 
1247             printf("\n");
1248         }
1249     }
1250 }
1251 
printFileList(tr_variant * top)1252 static void printFileList(tr_variant* top)
1253 {
1254     tr_variant* args;
1255     tr_variant* torrents;
1256 
1257     if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &torrents))
1258     {
1259         for (int i = 0, in = tr_variantListSize(torrents); i < in; ++i)
1260         {
1261             tr_variant* d = tr_variantListChild(torrents, i);
1262             tr_variant* files;
1263             tr_variant* priorities;
1264             tr_variant* wanteds;
1265             char const* name;
1266 
1267             if (tr_variantDictFindStr(d, TR_KEY_name, &name, NULL) && tr_variantDictFindList(d, TR_KEY_files, &files) &&
1268                 tr_variantDictFindList(d, TR_KEY_priorities, &priorities) && tr_variantDictFindList(d, TR_KEY_wanted, &wanteds))
1269             {
1270                 int const jn = tr_variantListSize(files);
1271                 printf("%s (%d files):\n", name, jn);
1272                 printf("%3s  %4s %8s %3s %9s  %s\n", "#", "Done", "Priority", "Get", "Size", "Name");
1273 
1274                 for (int j = 0; j < jn; ++j)
1275                 {
1276                     int64_t have;
1277                     int64_t length;
1278                     int64_t priority;
1279                     bool wanted;
1280                     char const* filename;
1281                     tr_variant* file = tr_variantListChild(files, j);
1282 
1283                     if (tr_variantDictFindInt(file, TR_KEY_length, &length) &&
1284                         tr_variantDictFindStr(file, TR_KEY_name, &filename, NULL) &&
1285                         tr_variantDictFindInt(file, TR_KEY_bytesCompleted, &have) &&
1286                         tr_variantGetInt(tr_variantListChild(priorities, j), &priority) &&
1287                         tr_variantGetBool(tr_variantListChild(wanteds, j), &wanted))
1288                     {
1289                         char sizestr[64];
1290                         double percent = (double)have / length;
1291                         char const* pristr;
1292                         strlsize(sizestr, length, sizeof(sizestr));
1293 
1294                         switch (priority)
1295                         {
1296                         case TR_PRI_LOW:
1297                             pristr = "Low";
1298                             break;
1299 
1300                         case TR_PRI_HIGH:
1301                             pristr = "High";
1302                             break;
1303 
1304                         default:
1305                             pristr = "Normal";
1306                             break;
1307                         }
1308 
1309                         printf("%3d: %3.0f%% %-8s %-3s %9s  %s\n", j, (floor)(100.0 * percent), pristr, wanted ? "Yes" : "No",
1310                             sizestr, filename);
1311                     }
1312                 }
1313             }
1314         }
1315     }
1316 }
1317 
printPeersImpl(tr_variant * peers)1318 static void printPeersImpl(tr_variant* peers)
1319 {
1320     printf("%-40s  %-12s  %-5s %-6s  %-6s  %s\n", "Address", "Flags", "Done", "Down", "Up", "Client");
1321 
1322     for (int i = 0, n = tr_variantListSize(peers); i < n; ++i)
1323     {
1324         double progress;
1325         char const* address;
1326         char const* client;
1327         char const* flagstr;
1328         int64_t rateToClient;
1329         int64_t rateToPeer;
1330         tr_variant* d = tr_variantListChild(peers, i);
1331 
1332         if (tr_variantDictFindStr(d, TR_KEY_address, &address, NULL) &&
1333             tr_variantDictFindStr(d, TR_KEY_clientName, &client, NULL) &&
1334             tr_variantDictFindReal(d, TR_KEY_progress, &progress) &&
1335             tr_variantDictFindStr(d, TR_KEY_flagStr, &flagstr, NULL) &&
1336             tr_variantDictFindInt(d, TR_KEY_rateToClient, &rateToClient) &&
1337             tr_variantDictFindInt(d, TR_KEY_rateToPeer, &rateToPeer))
1338         {
1339             printf("%-40s  %-12s  %-5.1f %6.1f  %6.1f  %s\n", address, flagstr, (progress * 100.0),
1340                 rateToClient / (double)tr_speed_K, rateToPeer / (double)tr_speed_K, client);
1341         }
1342     }
1343 }
1344 
printPeers(tr_variant * top)1345 static void printPeers(tr_variant* top)
1346 {
1347     tr_variant* args;
1348     tr_variant* torrents;
1349 
1350     if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &torrents))
1351     {
1352         for (int i = 0, n = tr_variantListSize(torrents); i < n; ++i)
1353         {
1354             tr_variant* peers;
1355             tr_variant* torrent = tr_variantListChild(torrents, i);
1356 
1357             if (tr_variantDictFindList(torrent, TR_KEY_peers, &peers))
1358             {
1359                 printPeersImpl(peers);
1360 
1361                 if (i + 1 < n)
1362                 {
1363                     printf("\n");
1364                 }
1365             }
1366         }
1367     }
1368 }
1369 
printPiecesImpl(uint8_t const * raw,size_t rawlen,size_t j)1370 static void printPiecesImpl(uint8_t const* raw, size_t rawlen, size_t j)
1371 {
1372     size_t len;
1373     char* str = tr_base64_decode(raw, rawlen, &len);
1374     printf("  ");
1375 
1376     for (size_t i = 0, k = 0; k < len; ++k)
1377     {
1378         for (int e = 0; i < j && e < 8; ++e, ++i)
1379         {
1380             printf("%c", (str[k] & (1 << (7 - e))) != 0 ? '1' : '0');
1381         }
1382 
1383         printf(" ");
1384 
1385         if (i % 64 == 0)
1386         {
1387             printf("\n  ");
1388         }
1389     }
1390 
1391     printf("\n");
1392     tr_free(str);
1393 }
1394 
printPieces(tr_variant * top)1395 static void printPieces(tr_variant* top)
1396 {
1397     tr_variant* args;
1398     tr_variant* torrents;
1399 
1400     if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &torrents))
1401     {
1402         for (int i = 0, n = tr_variantListSize(torrents); i < n; ++i)
1403         {
1404             int64_t j;
1405             uint8_t const* raw;
1406             size_t rawlen;
1407             tr_variant* torrent = tr_variantListChild(torrents, i);
1408 
1409             if (tr_variantDictFindRaw(torrent, TR_KEY_pieces, &raw, &rawlen) &&
1410                 tr_variantDictFindInt(torrent, TR_KEY_pieceCount, &j))
1411             {
1412                 assert(j >= 0);
1413                 printPiecesImpl(raw, rawlen, (size_t)j);
1414 
1415                 if (i + 1 < n)
1416                 {
1417                     printf("\n");
1418                 }
1419             }
1420         }
1421     }
1422 }
1423 
printPortTest(tr_variant * top)1424 static void printPortTest(tr_variant* top)
1425 {
1426     tr_variant* args;
1427 
1428     if (tr_variantDictFindDict(top, TR_KEY_arguments, &args))
1429     {
1430         bool boolVal;
1431 
1432         if (tr_variantDictFindBool(args, TR_KEY_port_is_open, &boolVal))
1433         {
1434             printf("Port is open: %s\n", boolVal ? "Yes" : "No");
1435         }
1436     }
1437 }
1438 
printTorrentList(tr_variant * top)1439 static void printTorrentList(tr_variant* top)
1440 {
1441     tr_variant* args;
1442     tr_variant* list;
1443 
1444     if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &list))
1445     {
1446         int64_t total_size = 0;
1447         double total_up = 0;
1448         double total_down = 0;
1449         char haveStr[32];
1450 
1451         printf("%6s   %-4s  %9s  %-8s  %6s  %6s  %-5s  %-11s  %s\n", "ID", "Done", "Have", "ETA", "Up", "Down", "Ratio",
1452             "Status", "Name");
1453 
1454         for (int i = 0, n = tr_variantListSize(list); i < n; ++i)
1455         {
1456             int64_t id;
1457             int64_t eta;
1458             int64_t status;
1459             int64_t up;
1460             int64_t down;
1461             int64_t sizeWhenDone;
1462             int64_t leftUntilDone;
1463             double ratio;
1464             char const* name;
1465             tr_variant* d = tr_variantListChild(list, i);
1466 
1467             if (tr_variantDictFindInt(d, TR_KEY_eta, &eta) &&
1468                 tr_variantDictFindInt(d, TR_KEY_id, &id) &&
1469                 tr_variantDictFindInt(d, TR_KEY_leftUntilDone, &leftUntilDone) &&
1470                 tr_variantDictFindStr(d, TR_KEY_name, &name, NULL) &&
1471                 tr_variantDictFindInt(d, TR_KEY_rateDownload, &down) &&
1472                 tr_variantDictFindInt(d, TR_KEY_rateUpload, &up) &&
1473                 tr_variantDictFindInt(d, TR_KEY_sizeWhenDone, &sizeWhenDone) &&
1474                 tr_variantDictFindInt(d, TR_KEY_status, &status) &&
1475                 tr_variantDictFindReal(d, TR_KEY_uploadRatio, &ratio))
1476             {
1477                 char etaStr[16];
1478                 char statusStr[64];
1479                 char ratioStr[32];
1480                 char doneStr[8];
1481                 int64_t error;
1482                 char errorMark;
1483 
1484                 if (sizeWhenDone != 0)
1485                 {
1486                     tr_snprintf(doneStr, sizeof(doneStr), "%d%%", (int)(100.0 * (sizeWhenDone - leftUntilDone) / sizeWhenDone));
1487                 }
1488                 else
1489                 {
1490                     tr_strlcpy(doneStr, "n/a", sizeof(doneStr));
1491                 }
1492 
1493                 strlsize(haveStr, sizeWhenDone - leftUntilDone, sizeof(haveStr));
1494 
1495                 if (leftUntilDone != 0 || eta != -1)
1496                 {
1497                     etaToString(etaStr, sizeof(etaStr), eta);
1498                 }
1499                 else
1500                 {
1501                     tr_snprintf(etaStr, sizeof(etaStr), "Done");
1502                 }
1503 
1504                 if (tr_variantDictFindInt(d, TR_KEY_error, &error) && error)
1505                 {
1506                     errorMark = '*';
1507                 }
1508                 else
1509                 {
1510                     errorMark = ' ';
1511                 }
1512 
1513                 printf("%6d%c  %4s  %9s  %-8s  %6.1f  %6.1f  %5s  %-11s  %s\n", (int)id, errorMark, doneStr, haveStr, etaStr,
1514                     up / (double)tr_speed_K, down / (double)tr_speed_K, strlratio2(ratioStr, ratio, sizeof(ratioStr)),
1515                     getStatusString(d, statusStr, sizeof(statusStr)), name);
1516 
1517                 total_up += up;
1518                 total_down += down;
1519                 total_size += sizeWhenDone - leftUntilDone;
1520             }
1521         }
1522 
1523         printf("Sum:           %9s            %6.1f  %6.1f\n", strlsize(haveStr, total_size, sizeof(haveStr)),
1524             total_up / (double)tr_speed_K, total_down / (double)tr_speed_K);
1525     }
1526 }
1527 
printTrackersImpl(tr_variant * trackerStats)1528 static void printTrackersImpl(tr_variant* trackerStats)
1529 {
1530     char buf[512];
1531     tr_variant* t;
1532 
1533     for (int i = 0; (t = tr_variantListChild(trackerStats, i)) != NULL; ++i)
1534     {
1535         int64_t downloadCount;
1536         bool hasAnnounced;
1537         bool hasScraped;
1538         char const* host;
1539         int64_t id;
1540         bool isBackup;
1541         int64_t lastAnnouncePeerCount;
1542         char const* lastAnnounceResult;
1543         int64_t lastAnnounceStartTime;
1544         bool lastAnnounceSucceeded;
1545         int64_t lastAnnounceTime;
1546         bool lastAnnounceTimedOut;
1547         char const* lastScrapeResult;
1548         bool lastScrapeSucceeded;
1549         int64_t lastScrapeStartTime;
1550         int64_t lastScrapeTime;
1551         bool lastScrapeTimedOut;
1552         int64_t leecherCount;
1553         int64_t nextAnnounceTime;
1554         int64_t nextScrapeTime;
1555         int64_t seederCount;
1556         int64_t tier;
1557         int64_t announceState;
1558         int64_t scrapeState;
1559 
1560         if (tr_variantDictFindInt(t, TR_KEY_downloadCount, &downloadCount) &&
1561             tr_variantDictFindBool(t, TR_KEY_hasAnnounced, &hasAnnounced) &&
1562             tr_variantDictFindBool(t, TR_KEY_hasScraped, &hasScraped) &&
1563             tr_variantDictFindStr(t, TR_KEY_host, &host, NULL) &&
1564             tr_variantDictFindInt(t, TR_KEY_id, &id) &&
1565             tr_variantDictFindBool(t, TR_KEY_isBackup, &isBackup) &&
1566             tr_variantDictFindInt(t, TR_KEY_announceState, &announceState) &&
1567             tr_variantDictFindInt(t, TR_KEY_scrapeState, &scrapeState) &&
1568             tr_variantDictFindInt(t, TR_KEY_lastAnnouncePeerCount, &lastAnnouncePeerCount) &&
1569             tr_variantDictFindStr(t, TR_KEY_lastAnnounceResult, &lastAnnounceResult, NULL) &&
1570             tr_variantDictFindInt(t, TR_KEY_lastAnnounceStartTime, &lastAnnounceStartTime) &&
1571             tr_variantDictFindBool(t, TR_KEY_lastAnnounceSucceeded, &lastAnnounceSucceeded) &&
1572             tr_variantDictFindInt(t, TR_KEY_lastAnnounceTime, &lastAnnounceTime) &&
1573             tr_variantDictFindBool(t, TR_KEY_lastAnnounceTimedOut, &lastAnnounceTimedOut) &&
1574             tr_variantDictFindStr(t, TR_KEY_lastScrapeResult, &lastScrapeResult, NULL) &&
1575             tr_variantDictFindInt(t, TR_KEY_lastScrapeStartTime, &lastScrapeStartTime) &&
1576             tr_variantDictFindBool(t, TR_KEY_lastScrapeSucceeded, &lastScrapeSucceeded) &&
1577             tr_variantDictFindInt(t, TR_KEY_lastScrapeTime, &lastScrapeTime) &&
1578             tr_variantDictFindBool(t, TR_KEY_lastScrapeTimedOut, &lastScrapeTimedOut) &&
1579             tr_variantDictFindInt(t, TR_KEY_leecherCount, &leecherCount) &&
1580             tr_variantDictFindInt(t, TR_KEY_nextAnnounceTime, &nextAnnounceTime) &&
1581             tr_variantDictFindInt(t, TR_KEY_nextScrapeTime, &nextScrapeTime) &&
1582             tr_variantDictFindInt(t, TR_KEY_seederCount, &seederCount) &&
1583             tr_variantDictFindInt(t, TR_KEY_tier, &tier))
1584         {
1585             time_t const now = time(NULL);
1586 
1587             printf("\n");
1588             printf("  Tracker %d: %s\n", (int)(id), host);
1589 
1590             if (isBackup)
1591             {
1592                 printf("  Backup on tier %d\n", (int)tier);
1593             }
1594             else
1595             {
1596                 printf("  Active in tier %d\n", (int)tier);
1597             }
1598 
1599             if (!isBackup)
1600             {
1601                 if (hasAnnounced && announceState != TR_TRACKER_INACTIVE)
1602                 {
1603                     tr_strltime(buf, now - lastAnnounceTime, sizeof(buf));
1604 
1605                     if (lastAnnounceSucceeded)
1606                     {
1607                         printf("  Got a list of %d peers %s ago\n", (int)lastAnnouncePeerCount, buf);
1608                     }
1609                     else if (lastAnnounceTimedOut)
1610                     {
1611                         printf("  Peer list request timed out; will retry\n");
1612                     }
1613                     else
1614                     {
1615                         printf("  Got an error \"%s\" %s ago\n", lastAnnounceResult, buf);
1616                     }
1617                 }
1618 
1619                 switch (announceState)
1620                 {
1621                 case TR_TRACKER_INACTIVE:
1622                     printf("  No updates scheduled\n");
1623                     break;
1624 
1625                 case TR_TRACKER_WAITING:
1626                     tr_strltime(buf, nextAnnounceTime - now, sizeof(buf));
1627                     printf("  Asking for more peers in %s\n", buf);
1628                     break;
1629 
1630                 case TR_TRACKER_QUEUED:
1631                     printf("  Queued to ask for more peers\n");
1632                     break;
1633 
1634                 case TR_TRACKER_ACTIVE:
1635                     tr_strltime(buf, now - lastAnnounceStartTime, sizeof(buf));
1636                     printf("  Asking for more peers now... %s\n", buf);
1637                     break;
1638                 }
1639 
1640                 if (hasScraped)
1641                 {
1642                     tr_strltime(buf, now - lastScrapeTime, sizeof(buf));
1643 
1644                     if (lastScrapeSucceeded)
1645                     {
1646                         printf("  Tracker had %d seeders and %d leechers %s ago\n", (int)seederCount, (int)leecherCount, buf);
1647                     }
1648                     else if (lastScrapeTimedOut)
1649                     {
1650                         printf("  Tracker scrape timed out; will retry\n");
1651                     }
1652                     else
1653                     {
1654                         printf("  Got a scrape error \"%s\" %s ago\n", lastScrapeResult, buf);
1655                     }
1656                 }
1657 
1658                 switch (scrapeState)
1659                 {
1660                 case TR_TRACKER_INACTIVE:
1661                     break;
1662 
1663                 case TR_TRACKER_WAITING:
1664                     tr_strltime(buf, nextScrapeTime - now, sizeof(buf));
1665                     printf("  Asking for peer counts in %s\n", buf);
1666                     break;
1667 
1668                 case TR_TRACKER_QUEUED:
1669                     printf("  Queued to ask for peer counts\n");
1670                     break;
1671 
1672                 case TR_TRACKER_ACTIVE:
1673                     tr_strltime(buf, now - lastScrapeStartTime, sizeof(buf));
1674                     printf("  Asking for peer counts now... %s\n", buf);
1675                     break;
1676                 }
1677             }
1678         }
1679     }
1680 }
1681 
printTrackers(tr_variant * top)1682 static void printTrackers(tr_variant* top)
1683 {
1684     tr_variant* args;
1685     tr_variant* torrents;
1686 
1687     if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &torrents))
1688     {
1689         for (int i = 0, n = tr_variantListSize(torrents); i < n; ++i)
1690         {
1691             tr_variant* trackerStats;
1692             tr_variant* torrent = tr_variantListChild(torrents, i);
1693 
1694             if (tr_variantDictFindList(torrent, TR_KEY_trackerStats, &trackerStats))
1695             {
1696                 printTrackersImpl(trackerStats);
1697 
1698                 if (i + 1 < n)
1699                 {
1700                     printf("\n");
1701                 }
1702             }
1703         }
1704     }
1705 }
1706 
printSession(tr_variant * top)1707 static void printSession(tr_variant* top)
1708 {
1709     tr_variant* args;
1710 
1711     if (tr_variantDictFindDict(top, TR_KEY_arguments, &args))
1712     {
1713         int64_t i;
1714         char buf[64];
1715         bool boolVal;
1716         char const* str;
1717 
1718         printf("VERSION\n");
1719 
1720         if (tr_variantDictFindStr(args, TR_KEY_version, &str, NULL))
1721         {
1722             printf("  Daemon version: %s\n", str);
1723         }
1724 
1725         if (tr_variantDictFindInt(args, TR_KEY_rpc_version, &i))
1726         {
1727             printf("  RPC version: %" PRId64 "\n", i);
1728         }
1729 
1730         if (tr_variantDictFindInt(args, TR_KEY_rpc_version_minimum, &i))
1731         {
1732             printf("  RPC minimum version: %" PRId64 "\n", i);
1733         }
1734 
1735         printf("\n");
1736 
1737         printf("CONFIG\n");
1738 
1739         if (tr_variantDictFindStr(args, TR_KEY_config_dir, &str, NULL))
1740         {
1741             printf("  Configuration directory: %s\n", str);
1742         }
1743 
1744         if (tr_variantDictFindStr(args, TR_KEY_download_dir, &str, NULL))
1745         {
1746             printf("  Download directory: %s\n", str);
1747         }
1748 
1749         if (tr_variantDictFindInt(args, TR_KEY_peer_port, &i))
1750         {
1751             printf("  Listenport: %" PRId64 "\n", i);
1752         }
1753 
1754         if (tr_variantDictFindBool(args, TR_KEY_port_forwarding_enabled, &boolVal))
1755         {
1756             printf("  Portforwarding enabled: %s\n", boolVal ? "Yes" : "No");
1757         }
1758 
1759         if (tr_variantDictFindBool(args, TR_KEY_utp_enabled, &boolVal))
1760         {
1761             printf("  uTP enabled: %s\n", (boolVal ? "Yes" : "No"));
1762         }
1763 
1764         if (tr_variantDictFindBool(args, TR_KEY_dht_enabled, &boolVal))
1765         {
1766             printf("  Distributed hash table enabled: %s\n", boolVal ? "Yes" : "No");
1767         }
1768 
1769         if (tr_variantDictFindBool(args, TR_KEY_lpd_enabled, &boolVal))
1770         {
1771             printf("  Local peer discovery enabled: %s\n", boolVal ? "Yes" : "No");
1772         }
1773 
1774         if (tr_variantDictFindBool(args, TR_KEY_pex_enabled, &boolVal))
1775         {
1776             printf("  Peer exchange allowed: %s\n", boolVal ? "Yes" : "No");
1777         }
1778 
1779         if (tr_variantDictFindStr(args, TR_KEY_encryption, &str, NULL))
1780         {
1781             printf("  Encryption: %s\n", str);
1782         }
1783 
1784         if (tr_variantDictFindInt(args, TR_KEY_cache_size_mb, &i))
1785         {
1786             printf("  Maximum memory cache size: %s\n", tr_formatter_mem_MB(buf, i, sizeof(buf)));
1787         }
1788 
1789         printf("\n");
1790 
1791         {
1792             bool altEnabled;
1793             bool altTimeEnabled;
1794             bool upEnabled;
1795             bool downEnabled;
1796             bool seedRatioLimited;
1797             int64_t altDown;
1798             int64_t altUp;
1799             int64_t altBegin;
1800             int64_t altEnd;
1801             int64_t altDay;
1802             int64_t upLimit;
1803             int64_t downLimit;
1804             int64_t peerLimit;
1805             double seedRatioLimit;
1806 
1807             if (tr_variantDictFindInt(args, TR_KEY_alt_speed_down, &altDown) &&
1808                 tr_variantDictFindBool(args, TR_KEY_alt_speed_enabled, &altEnabled) &&
1809                 tr_variantDictFindInt(args, TR_KEY_alt_speed_time_begin, &altBegin) &&
1810                 tr_variantDictFindBool(args, TR_KEY_alt_speed_time_enabled, &altTimeEnabled) &&
1811                 tr_variantDictFindInt(args, TR_KEY_alt_speed_time_end, &altEnd) &&
1812                 tr_variantDictFindInt(args, TR_KEY_alt_speed_time_day, &altDay) &&
1813                 tr_variantDictFindInt(args, TR_KEY_alt_speed_up, &altUp) &&
1814                 tr_variantDictFindInt(args, TR_KEY_peer_limit_global, &peerLimit) &&
1815                 tr_variantDictFindInt(args, TR_KEY_speed_limit_down, &downLimit) &&
1816                 tr_variantDictFindBool(args, TR_KEY_speed_limit_down_enabled, &downEnabled) &&
1817                 tr_variantDictFindInt(args, TR_KEY_speed_limit_up, &upLimit) &&
1818                 tr_variantDictFindBool(args, TR_KEY_speed_limit_up_enabled, &upEnabled) &&
1819                 tr_variantDictFindReal(args, TR_KEY_seedRatioLimit, &seedRatioLimit) &&
1820                 tr_variantDictFindBool(args, TR_KEY_seedRatioLimited, &seedRatioLimited))
1821             {
1822                 char buf[128];
1823                 char buf2[128];
1824                 char buf3[128];
1825 
1826                 printf("LIMITS\n");
1827                 printf("  Peer limit: %" PRId64 "\n", peerLimit);
1828 
1829                 if (seedRatioLimited)
1830                 {
1831                     strlratio2(buf, seedRatioLimit, sizeof(buf));
1832                 }
1833                 else
1834                 {
1835                     tr_strlcpy(buf, "Unlimited", sizeof(buf));
1836                 }
1837 
1838                 printf("  Default seed ratio limit: %s\n", buf);
1839 
1840                 if (altEnabled)
1841                 {
1842                     tr_formatter_speed_KBps(buf, altUp, sizeof(buf));
1843                 }
1844                 else if (upEnabled)
1845                 {
1846                     tr_formatter_speed_KBps(buf, upLimit, sizeof(buf));
1847                 }
1848                 else
1849                 {
1850                     tr_strlcpy(buf, "Unlimited", sizeof(buf));
1851                 }
1852 
1853                 printf("  Upload speed limit: %s (%s limit: %s; %s turtle limit: %s)\n", buf,
1854                     upEnabled ? "Enabled" : "Disabled", tr_formatter_speed_KBps(buf2, upLimit, sizeof(buf2)),
1855                     altEnabled ? "Enabled" : "Disabled", tr_formatter_speed_KBps(buf3, altUp, sizeof(buf3)));
1856 
1857                 if (altEnabled)
1858                 {
1859                     tr_formatter_speed_KBps(buf, altDown, sizeof(buf));
1860                 }
1861                 else if (downEnabled)
1862                 {
1863                     tr_formatter_speed_KBps(buf, downLimit, sizeof(buf));
1864                 }
1865                 else
1866                 {
1867                     tr_strlcpy(buf, "Unlimited", sizeof(buf));
1868                 }
1869 
1870                 printf("  Download speed limit: %s (%s limit: %s; %s turtle limit: %s)\n", buf,
1871                     downEnabled ? "Enabled" : "Disabled", tr_formatter_speed_KBps(buf2, downLimit, sizeof(buf2)),
1872                     altEnabled ? "Enabled" : "Disabled", tr_formatter_speed_KBps(buf3, altDown, sizeof(buf3)));
1873 
1874                 if (altTimeEnabled)
1875                 {
1876                     printf("  Turtle schedule: %02d:%02d - %02d:%02d  ", (int)(altBegin / 60), (int)(altBegin % 60),
1877                         (int)(altEnd / 60), (int)(altEnd % 60));
1878 
1879                     if ((altDay & TR_SCHED_SUN) != 0)
1880                     {
1881                         printf("Sun ");
1882                     }
1883 
1884                     if ((altDay & TR_SCHED_MON) != 0)
1885                     {
1886                         printf("Mon ");
1887                     }
1888 
1889                     if ((altDay & TR_SCHED_TUES) != 0)
1890                     {
1891                         printf("Tue ");
1892                     }
1893 
1894                     if ((altDay & TR_SCHED_WED) != 0)
1895                     {
1896                         printf("Wed ");
1897                     }
1898 
1899                     if ((altDay & TR_SCHED_THURS) != 0)
1900                     {
1901                         printf("Thu ");
1902                     }
1903 
1904                     if ((altDay & TR_SCHED_FRI) != 0)
1905                     {
1906                         printf("Fri ");
1907                     }
1908 
1909                     if ((altDay & TR_SCHED_SAT) != 0)
1910                     {
1911                         printf("Sat ");
1912                     }
1913 
1914                     printf("\n");
1915                 }
1916             }
1917         }
1918 
1919         printf("\n");
1920 
1921         printf("MISC\n");
1922 
1923         if (tr_variantDictFindBool(args, TR_KEY_start_added_torrents, &boolVal))
1924         {
1925             printf("  Autostart added torrents: %s\n", boolVal ? "Yes" : "No");
1926         }
1927 
1928         if (tr_variantDictFindBool(args, TR_KEY_trash_original_torrent_files, &boolVal))
1929         {
1930             printf("  Delete automatically added torrents: %s\n", boolVal ? "Yes" : "No");
1931         }
1932     }
1933 }
1934 
printSessionStats(tr_variant * top)1935 static void printSessionStats(tr_variant* top)
1936 {
1937     tr_variant* args;
1938     tr_variant* d;
1939 
1940     if (tr_variantDictFindDict(top, TR_KEY_arguments, &args))
1941     {
1942         char buf[512];
1943         int64_t up;
1944         int64_t down;
1945         int64_t secs;
1946         int64_t sessions;
1947 
1948         if (tr_variantDictFindDict(args, TR_KEY_current_stats, &d) && tr_variantDictFindInt(d, TR_KEY_uploadedBytes, &up) &&
1949             tr_variantDictFindInt(d, TR_KEY_downloadedBytes, &down) && tr_variantDictFindInt(d, TR_KEY_secondsActive, &secs))
1950         {
1951             printf("\nCURRENT SESSION\n");
1952             printf("  Uploaded:   %s\n", strlsize(buf, up, sizeof(buf)));
1953             printf("  Downloaded: %s\n", strlsize(buf, down, sizeof(buf)));
1954             printf("  Ratio:      %s\n", strlratio(buf, up, down, sizeof(buf)));
1955             printf("  Duration:   %s\n", tr_strltime(buf, secs, sizeof(buf)));
1956         }
1957 
1958         if (tr_variantDictFindDict(args, TR_KEY_cumulative_stats, &d) &&
1959             tr_variantDictFindInt(d, TR_KEY_sessionCount, &sessions) &&
1960             tr_variantDictFindInt(d, TR_KEY_uploadedBytes, &up) &&
1961             tr_variantDictFindInt(d, TR_KEY_downloadedBytes, &down) &&
1962             tr_variantDictFindInt(d, TR_KEY_secondsActive, &secs))
1963         {
1964             printf("\nTOTAL\n");
1965             printf("  Started %lu times\n", (unsigned long)sessions);
1966             printf("  Uploaded:   %s\n", strlsize(buf, up, sizeof(buf)));
1967             printf("  Downloaded: %s\n", strlsize(buf, down, sizeof(buf)));
1968             printf("  Ratio:      %s\n", strlratio(buf, up, down, sizeof(buf)));
1969             printf("  Duration:   %s\n", tr_strltime(buf, secs, sizeof(buf)));
1970         }
1971     }
1972 }
1973 
1974 static char id[4096];
1975 
processResponse(char const * rpcurl,void const * response,size_t len)1976 static int processResponse(char const* rpcurl, void const* response, size_t len)
1977 {
1978     tr_variant top;
1979     int status = EXIT_SUCCESS;
1980 
1981     if (debug)
1982     {
1983         fprintf(stderr, "got response (len %d):\n--------\n%*.*s\n--------\n", (int)len, (int)len, (int)len,
1984             (char const*)response);
1985     }
1986 
1987     if (tr_variantFromJson(&top, response, len) != 0)
1988     {
1989         tr_logAddNamedError(MY_NAME, "Unable to parse response \"%*.*s\"", (int)len, (int)len, (char const*)response);
1990         status |= EXIT_FAILURE;
1991     }
1992     else
1993     {
1994         int64_t tag = -1;
1995         char const* str;
1996 
1997         if (tr_variantDictFindStr(&top, TR_KEY_result, &str, NULL))
1998         {
1999             if (strcmp(str, "success") != 0)
2000             {
2001                 printf("Error: %s\n", str);
2002                 status |= EXIT_FAILURE;
2003             }
2004             else
2005             {
2006                 tr_variantDictFindInt(&top, TR_KEY_tag, &tag);
2007 
2008                 switch (tag)
2009                 {
2010                 case TAG_SESSION:
2011                     printSession(&top);
2012                     break;
2013 
2014                 case TAG_STATS:
2015                     printSessionStats(&top);
2016                     break;
2017 
2018                 case TAG_DETAILS:
2019                     printDetails(&top);
2020                     break;
2021 
2022                 case TAG_FILES:
2023                     printFileList(&top);
2024                     break;
2025 
2026                 case TAG_LIST:
2027                     printTorrentList(&top);
2028                     break;
2029 
2030                 case TAG_PEERS:
2031                     printPeers(&top);
2032                     break;
2033 
2034                 case TAG_PIECES:
2035                     printPieces(&top);
2036                     break;
2037 
2038                 case TAG_PORTTEST:
2039                     printPortTest(&top);
2040                     break;
2041 
2042                 case TAG_TRACKERS:
2043                     printTrackers(&top);
2044                     break;
2045 
2046                 case TAG_TORRENT_ADD:
2047                     {
2048                         int64_t i;
2049                         tr_variant* b = &top;
2050 
2051                         if (tr_variantDictFindDict(&top, ARGUMENTS, &b) &&
2052                             tr_variantDictFindDict(b, TR_KEY_torrent_added, &b) &&
2053                             tr_variantDictFindInt(b, TR_KEY_id, &i))
2054                         {
2055                             tr_snprintf(id, sizeof(id), "%" PRId64, i);
2056                         }
2057 
2058                         /* fall-through to default: to give success or failure msg */
2059                         TR_GNUC_FALLTHROUGH;
2060                     }
2061 
2062                 default:
2063                     if (!tr_variantDictFindStr(&top, TR_KEY_result, &str, NULL))
2064                     {
2065                         status |= EXIT_FAILURE;
2066                     }
2067                     else
2068                     {
2069                         printf("%s responded: \"%s\"\n", rpcurl, str);
2070 
2071                         if (strcmp(str, "success") != 0)
2072                         {
2073                             status |= EXIT_FAILURE;
2074                         }
2075                     }
2076                 }
2077 
2078                 tr_variantFree(&top);
2079             }
2080         }
2081         else
2082         {
2083             status |= EXIT_FAILURE;
2084         }
2085     }
2086 
2087     return status;
2088 }
2089 
tr_curl_easy_init(struct evbuffer * writebuf)2090 static CURL* tr_curl_easy_init(struct evbuffer* writebuf)
2091 {
2092     CURL* curl = curl_easy_init();
2093     curl_easy_setopt(curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING);
2094     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc);
2095     curl_easy_setopt(curl, CURLOPT_WRITEDATA, writebuf);
2096     curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, parseResponseHeader);
2097     curl_easy_setopt(curl, CURLOPT_POST, 1);
2098     curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
2099     curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
2100     curl_easy_setopt(curl, CURLOPT_VERBOSE, debug);
2101     curl_easy_setopt(curl, CURLOPT_ENCODING, ""); /* "" tells curl to fill in the blanks with what it was compiled to support */
2102 
2103     if (netrc != NULL)
2104     {
2105         curl_easy_setopt(curl, CURLOPT_NETRC_FILE, netrc);
2106     }
2107 
2108     if (auth != NULL)
2109     {
2110         curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
2111     }
2112 
2113     if (UseSSL)
2114     {
2115         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); /* do not verify subject/hostname */
2116         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); /* since most certs will be self-signed, do not verify against CA */
2117     }
2118 
2119     if (sessionId != NULL)
2120     {
2121         char* h = tr_strdup_printf("%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId);
2122         struct curl_slist* custom_headers = curl_slist_append(NULL, h);
2123         tr_free(h);
2124 
2125         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, custom_headers);
2126         curl_easy_setopt(curl, CURLOPT_PRIVATE, custom_headers);
2127     }
2128 
2129     return curl;
2130 }
2131 
tr_curl_easy_cleanup(CURL * curl)2132 static void tr_curl_easy_cleanup(CURL* curl)
2133 {
2134     struct curl_slist* custom_headers = NULL;
2135     curl_easy_getinfo(curl, CURLINFO_PRIVATE, &custom_headers);
2136 
2137     curl_easy_cleanup(curl);
2138 
2139     if (custom_headers != NULL)
2140     {
2141         curl_slist_free_all(custom_headers);
2142     }
2143 }
2144 
flush(char const * rpcurl,tr_variant ** benc)2145 static int flush(char const* rpcurl, tr_variant** benc)
2146 {
2147     CURLcode res;
2148     CURL* curl;
2149     int status = EXIT_SUCCESS;
2150     struct evbuffer* buf = evbuffer_new();
2151     char* json = tr_variantToStr(*benc, TR_VARIANT_FMT_JSON_LEAN, NULL);
2152     char* rpcurl_http = tr_strdup_printf(UseSSL ? "https://%s" : "http://%s", rpcurl);
2153 
2154     curl = tr_curl_easy_init(buf);
2155     curl_easy_setopt(curl, CURLOPT_URL, rpcurl_http);
2156     curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
2157     curl_easy_setopt(curl, CURLOPT_TIMEOUT, getTimeoutSecs(json));
2158 
2159     if (debug)
2160     {
2161         fprintf(stderr, "posting:\n--------\n%s\n--------\n", json);
2162     }
2163 
2164     if ((res = curl_easy_perform(curl)) != CURLE_OK)
2165     {
2166         tr_logAddNamedError(MY_NAME, " (%s) %s", rpcurl_http, curl_easy_strerror(res));
2167         status |= EXIT_FAILURE;
2168     }
2169     else
2170     {
2171         long response;
2172         curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
2173 
2174         switch (response)
2175         {
2176         case 200:
2177             status |= processResponse(rpcurl, (char const*)evbuffer_pullup(buf, -1), evbuffer_get_length(buf));
2178             break;
2179 
2180         case 409:
2181             /* Session id failed. Our curl header func has already
2182              * pulled the new session id from this response's headers,
2183              * build a new CURL* and try again */
2184             tr_curl_easy_cleanup(curl);
2185             curl = NULL;
2186             status |= flush(rpcurl, benc);
2187             benc = NULL;
2188             break;
2189 
2190         default:
2191             evbuffer_add(buf, "", 1);
2192             fprintf(stderr, "Unexpected response: %s\n", evbuffer_pullup(buf, -1));
2193             status |= EXIT_FAILURE;
2194             break;
2195         }
2196     }
2197 
2198     /* cleanup */
2199     tr_free(rpcurl_http);
2200     tr_free(json);
2201     evbuffer_free(buf);
2202 
2203     if (curl != NULL)
2204     {
2205         tr_curl_easy_cleanup(curl);
2206     }
2207 
2208     if (benc != NULL)
2209     {
2210         tr_variantFree(*benc);
2211         tr_free(*benc);
2212         *benc = NULL;
2213     }
2214 
2215     return status;
2216 }
2217 
ensure_sset(tr_variant ** sset)2218 static tr_variant* ensure_sset(tr_variant** sset)
2219 {
2220     tr_variant* args;
2221 
2222     if (*sset != NULL)
2223     {
2224         args = tr_variantDictFind(*sset, ARGUMENTS);
2225     }
2226     else
2227     {
2228         *sset = tr_new0(tr_variant, 1);
2229         tr_variantInitDict(*sset, 3);
2230         tr_variantDictAddStr(*sset, TR_KEY_method, "session-set");
2231         args = tr_variantDictAddDict(*sset, ARGUMENTS, 0);
2232     }
2233 
2234     return args;
2235 }
2236 
ensure_tset(tr_variant ** tset)2237 static tr_variant* ensure_tset(tr_variant** tset)
2238 {
2239     tr_variant* args;
2240 
2241     if (*tset != NULL)
2242     {
2243         args = tr_variantDictFind(*tset, ARGUMENTS);
2244     }
2245     else
2246     {
2247         *tset = tr_new0(tr_variant, 1);
2248         tr_variantInitDict(*tset, 3);
2249         tr_variantDictAddStr(*tset, TR_KEY_method, "torrent-set");
2250         args = tr_variantDictAddDict(*tset, ARGUMENTS, 1);
2251     }
2252 
2253     return args;
2254 }
2255 
processArgs(char const * rpcurl,int argc,char const * const * argv)2256 static int processArgs(char const* rpcurl, int argc, char const* const* argv)
2257 {
2258     int c;
2259     int status = EXIT_SUCCESS;
2260     char const* optarg;
2261     tr_variant* sset = NULL;
2262     tr_variant* tset = NULL;
2263     tr_variant* tadd = NULL;
2264 
2265     *id = '\0';
2266 
2267     while ((c = tr_getopt(getUsage(), argc, argv, opts, &optarg)) != TR_OPT_DONE)
2268     {
2269         int const stepMode = getOptMode(c);
2270 
2271         if (stepMode == 0) /* meta commands */
2272         {
2273             switch (c)
2274             {
2275             case 'a': /* add torrent */
2276                 if (sset != NULL)
2277                 {
2278                     status |= flush(rpcurl, &sset);
2279                 }
2280 
2281                 if (tadd != NULL)
2282                 {
2283                     status |= flush(rpcurl, &tadd);
2284                 }
2285 
2286                 if (tset != NULL)
2287                 {
2288                     addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2289                     status |= flush(rpcurl, &tset);
2290                 }
2291 
2292                 tadd = tr_new0(tr_variant, 1);
2293                 tr_variantInitDict(tadd, 3);
2294                 tr_variantDictAddStr(tadd, TR_KEY_method, "torrent-add");
2295                 tr_variantDictAddInt(tadd, TR_KEY_tag, TAG_TORRENT_ADD);
2296                 tr_variantDictAddDict(tadd, ARGUMENTS, 0);
2297                 break;
2298 
2299             case 'b': /* debug */
2300                 debug = true;
2301                 break;
2302 
2303             case 'n': /* auth */
2304                 auth = tr_strdup(optarg);
2305                 break;
2306 
2307             case 810: /* authenv */
2308                 auth = tr_env_get_string("TR_AUTH", NULL);
2309 
2310                 if (auth == NULL)
2311                 {
2312                     fprintf(stderr, "The TR_AUTH environment variable is not set\n");
2313                     exit(0);
2314                 }
2315 
2316                 break;
2317 
2318             case 'N': /* netrc */
2319                 netrc = tr_strdup(optarg);
2320                 break;
2321 
2322             case 820: /* UseSSL */
2323                 UseSSL = true;
2324                 break;
2325 
2326             case 't': /* set current torrent */
2327                 if (tadd != NULL)
2328                 {
2329                     status |= flush(rpcurl, &tadd);
2330                 }
2331 
2332                 if (tset != NULL)
2333                 {
2334                     addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2335                     status |= flush(rpcurl, &tset);
2336                 }
2337 
2338                 tr_strlcpy(id, optarg, sizeof(id));
2339                 break;
2340 
2341             case 'V': /* show version number */
2342                 fprintf(stderr, "%s %s\n", MY_NAME, LONG_VERSION_STRING);
2343                 exit(0);
2344                 break;
2345 
2346             case TR_OPT_ERR:
2347                 fprintf(stderr, "invalid option\n");
2348                 showUsage();
2349                 status |= EXIT_FAILURE;
2350                 break;
2351 
2352             case TR_OPT_UNK:
2353                 if (tadd != NULL)
2354                 {
2355                     tr_variant* args = tr_variantDictFind(tadd, ARGUMENTS);
2356                     char* tmp = getEncodedMetainfo(optarg);
2357 
2358                     if (tmp != NULL)
2359                     {
2360                         tr_variantDictAddStr(args, TR_KEY_metainfo, tmp);
2361                     }
2362                     else
2363                     {
2364                         tr_variantDictAddStr(args, TR_KEY_filename, optarg);
2365                     }
2366 
2367                     tr_free(tmp);
2368                 }
2369                 else
2370                 {
2371                     fprintf(stderr, "Unknown option: %s\n", optarg);
2372                     status |= EXIT_FAILURE;
2373                 }
2374 
2375                 break;
2376             }
2377         }
2378         else if (stepMode == MODE_TORRENT_GET)
2379         {
2380             tr_variant* top = tr_new0(tr_variant, 1);
2381             tr_variant* args;
2382             tr_variant* fields;
2383             tr_variantInitDict(top, 3);
2384             tr_variantDictAddStr(top, TR_KEY_method, "torrent-get");
2385             args = tr_variantDictAddDict(top, ARGUMENTS, 0);
2386             fields = tr_variantDictAddList(args, TR_KEY_fields, 0);
2387 
2388             if (tset != NULL)
2389             {
2390                 addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2391                 status |= flush(rpcurl, &tset);
2392             }
2393 
2394             switch (c)
2395             {
2396             case 'i':
2397                 tr_variantDictAddInt(top, TR_KEY_tag, TAG_DETAILS);
2398 
2399                 for (size_t i = 0; i < TR_N_ELEMENTS(details_keys); ++i)
2400                 {
2401                     tr_variantListAddQuark(fields, details_keys[i]);
2402                 }
2403 
2404                 addIdArg(args, id, NULL);
2405                 break;
2406 
2407             case 'l':
2408                 tr_variantDictAddInt(top, TR_KEY_tag, TAG_LIST);
2409 
2410                 for (size_t i = 0; i < TR_N_ELEMENTS(list_keys); ++i)
2411                 {
2412                     tr_variantListAddQuark(fields, list_keys[i]);
2413                 }
2414 
2415                 addIdArg(args, id, "all");
2416                 break;
2417 
2418             case 940:
2419                 tr_variantDictAddInt(top, TR_KEY_tag, TAG_FILES);
2420 
2421                 for (size_t i = 0; i < TR_N_ELEMENTS(files_keys); ++i)
2422                 {
2423                     tr_variantListAddQuark(fields, files_keys[i]);
2424                 }
2425 
2426                 addIdArg(args, id, NULL);
2427                 break;
2428 
2429             case 941:
2430                 tr_variantDictAddInt(top, TR_KEY_tag, TAG_PEERS);
2431                 tr_variantListAddStr(fields, "peers");
2432                 addIdArg(args, id, NULL);
2433                 break;
2434 
2435             case 942:
2436                 tr_variantDictAddInt(top, TR_KEY_tag, TAG_PIECES);
2437                 tr_variantListAddStr(fields, "pieces");
2438                 tr_variantListAddStr(fields, "pieceCount");
2439                 addIdArg(args, id, NULL);
2440                 break;
2441 
2442             case 943:
2443                 tr_variantDictAddInt(top, TR_KEY_tag, TAG_TRACKERS);
2444                 tr_variantListAddStr(fields, "trackerStats");
2445                 addIdArg(args, id, NULL);
2446                 break;
2447 
2448             default:
2449                 assert("unhandled value" && 0);
2450             }
2451 
2452             status |= flush(rpcurl, &top);
2453         }
2454         else if (stepMode == MODE_SESSION_SET)
2455         {
2456             tr_variant* args = ensure_sset(&sset);
2457 
2458             switch (c)
2459             {
2460             case 800:
2461                 tr_variantDictAddStr(args, TR_KEY_script_torrent_done_filename, optarg);
2462                 tr_variantDictAddBool(args, TR_KEY_script_torrent_done_enabled, true);
2463                 break;
2464 
2465             case 801:
2466                 tr_variantDictAddBool(args, TR_KEY_script_torrent_done_enabled, false);
2467                 break;
2468 
2469             case 970:
2470                 tr_variantDictAddBool(args, TR_KEY_alt_speed_enabled, true);
2471                 break;
2472 
2473             case 971:
2474                 tr_variantDictAddBool(args, TR_KEY_alt_speed_enabled, false);
2475                 break;
2476 
2477             case 972:
2478                 tr_variantDictAddInt(args, TR_KEY_alt_speed_down, numarg(optarg));
2479                 break;
2480 
2481             case 973:
2482                 tr_variantDictAddInt(args, TR_KEY_alt_speed_up, numarg(optarg));
2483                 break;
2484 
2485             case 974:
2486                 tr_variantDictAddBool(args, TR_KEY_alt_speed_time_enabled, true);
2487                 break;
2488 
2489             case 975:
2490                 tr_variantDictAddBool(args, TR_KEY_alt_speed_time_enabled, false);
2491                 break;
2492 
2493             case 976:
2494                 addTime(args, TR_KEY_alt_speed_time_begin, optarg);
2495                 break;
2496 
2497             case 977:
2498                 addTime(args, TR_KEY_alt_speed_time_end, optarg);
2499                 break;
2500 
2501             case 978:
2502                 addDays(args, TR_KEY_alt_speed_time_day, optarg);
2503                 break;
2504 
2505             case 'c':
2506                 tr_variantDictAddStr(args, TR_KEY_incomplete_dir, optarg);
2507                 tr_variantDictAddBool(args, TR_KEY_incomplete_dir_enabled, true);
2508                 break;
2509 
2510             case 'C':
2511                 tr_variantDictAddBool(args, TR_KEY_incomplete_dir_enabled, false);
2512                 break;
2513 
2514             case 'e':
2515                 tr_variantDictAddInt(args, TR_KEY_cache_size_mb, atoi(optarg));
2516                 break;
2517 
2518             case 910:
2519                 tr_variantDictAddStr(args, TR_KEY_encryption, "required");
2520                 break;
2521 
2522             case 911:
2523                 tr_variantDictAddStr(args, TR_KEY_encryption, "preferred");
2524                 break;
2525 
2526             case 912:
2527                 tr_variantDictAddStr(args, TR_KEY_encryption, "tolerated");
2528                 break;
2529 
2530             case 'm':
2531                 tr_variantDictAddBool(args, TR_KEY_port_forwarding_enabled, true);
2532                 break;
2533 
2534             case 'M':
2535                 tr_variantDictAddBool(args, TR_KEY_port_forwarding_enabled, false);
2536                 break;
2537 
2538             case 'o':
2539                 tr_variantDictAddBool(args, TR_KEY_dht_enabled, true);
2540                 break;
2541 
2542             case 'O':
2543                 tr_variantDictAddBool(args, TR_KEY_dht_enabled, false);
2544                 break;
2545 
2546             case 830:
2547                 tr_variantDictAddBool(args, TR_KEY_utp_enabled, true);
2548                 break;
2549 
2550             case 831:
2551                 tr_variantDictAddBool(args, TR_KEY_utp_enabled, false);
2552                 break;
2553 
2554             case 'p':
2555                 tr_variantDictAddInt(args, TR_KEY_peer_port, numarg(optarg));
2556                 break;
2557 
2558             case 'P':
2559                 tr_variantDictAddBool(args, TR_KEY_peer_port_random_on_start, true);
2560                 break;
2561 
2562             case 'x':
2563                 tr_variantDictAddBool(args, TR_KEY_pex_enabled, true);
2564                 break;
2565 
2566             case 'X':
2567                 tr_variantDictAddBool(args, TR_KEY_pex_enabled, false);
2568                 break;
2569 
2570             case 'y':
2571                 tr_variantDictAddBool(args, TR_KEY_lpd_enabled, true);
2572                 break;
2573 
2574             case 'Y':
2575                 tr_variantDictAddBool(args, TR_KEY_lpd_enabled, false);
2576                 break;
2577 
2578             case 953:
2579                 tr_variantDictAddReal(args, TR_KEY_seedRatioLimit, atof(optarg));
2580                 tr_variantDictAddBool(args, TR_KEY_seedRatioLimited, true);
2581                 break;
2582 
2583             case 954:
2584                 tr_variantDictAddBool(args, TR_KEY_seedRatioLimited, false);
2585                 break;
2586 
2587             case 990:
2588                 tr_variantDictAddBool(args, TR_KEY_start_added_torrents, false);
2589                 break;
2590 
2591             case 991:
2592                 tr_variantDictAddBool(args, TR_KEY_start_added_torrents, true);
2593                 break;
2594 
2595             case 992:
2596                 tr_variantDictAddBool(args, TR_KEY_trash_original_torrent_files, true);
2597                 break;
2598 
2599             case 993:
2600                 tr_variantDictAddBool(args, TR_KEY_trash_original_torrent_files, false);
2601                 break;
2602 
2603             default:
2604                 assert("unhandled value" && 0);
2605                 break;
2606             }
2607         }
2608         else if (stepMode == (MODE_SESSION_SET | MODE_TORRENT_SET))
2609         {
2610             tr_variant* targs = NULL;
2611             tr_variant* sargs = NULL;
2612 
2613             if (!tr_str_is_empty(id))
2614             {
2615                 targs = ensure_tset(&tset);
2616             }
2617             else
2618             {
2619                 sargs = ensure_sset(&sset);
2620             }
2621 
2622             switch (c)
2623             {
2624             case 'd':
2625                 if (targs != NULL)
2626                 {
2627                     tr_variantDictAddInt(targs, TR_KEY_downloadLimit, numarg(optarg));
2628                     tr_variantDictAddBool(targs, TR_KEY_downloadLimited, true);
2629                 }
2630                 else
2631                 {
2632                     tr_variantDictAddInt(sargs, TR_KEY_speed_limit_down, numarg(optarg));
2633                     tr_variantDictAddBool(sargs, TR_KEY_speed_limit_down_enabled, true);
2634                 }
2635 
2636                 break;
2637 
2638             case 'D':
2639                 if (targs != NULL)
2640                 {
2641                     tr_variantDictAddBool(targs, TR_KEY_downloadLimited, false);
2642                 }
2643                 else
2644                 {
2645                     tr_variantDictAddBool(sargs, TR_KEY_speed_limit_down_enabled, false);
2646                 }
2647 
2648                 break;
2649 
2650             case 'u':
2651                 if (targs != NULL)
2652                 {
2653                     tr_variantDictAddInt(targs, TR_KEY_uploadLimit, numarg(optarg));
2654                     tr_variantDictAddBool(targs, TR_KEY_uploadLimited, true);
2655                 }
2656                 else
2657                 {
2658                     tr_variantDictAddInt(sargs, TR_KEY_speed_limit_up, numarg(optarg));
2659                     tr_variantDictAddBool(sargs, TR_KEY_speed_limit_up_enabled, true);
2660                 }
2661 
2662                 break;
2663 
2664             case 'U':
2665                 if (targs != NULL)
2666                 {
2667                     tr_variantDictAddBool(targs, TR_KEY_uploadLimited, false);
2668                 }
2669                 else
2670                 {
2671                     tr_variantDictAddBool(sargs, TR_KEY_speed_limit_up_enabled, false);
2672                 }
2673 
2674                 break;
2675 
2676             case 930:
2677                 if (targs != NULL)
2678                 {
2679                     tr_variantDictAddInt(targs, TR_KEY_peer_limit, atoi(optarg));
2680                 }
2681                 else
2682                 {
2683                     tr_variantDictAddInt(sargs, TR_KEY_peer_limit_global, atoi(optarg));
2684                 }
2685 
2686                 break;
2687 
2688             default:
2689                 assert("unhandled value" && 0);
2690                 break;
2691             }
2692         }
2693         else if (stepMode == MODE_TORRENT_SET)
2694         {
2695             tr_variant* args = ensure_tset(&tset);
2696 
2697             switch (c)
2698             {
2699             case 'L':
2700                 addLabels(args, optarg);
2701                 break;
2702 
2703             case 712:
2704                 tr_variantListAddInt(tr_variantDictAddList(args, TR_KEY_trackerRemove, 1), atoi(optarg));
2705                 break;
2706 
2707             case 950:
2708                 tr_variantDictAddReal(args, TR_KEY_seedRatioLimit, atof(optarg));
2709                 tr_variantDictAddInt(args, TR_KEY_seedRatioMode, TR_RATIOLIMIT_SINGLE);
2710                 break;
2711 
2712             case 951:
2713                 tr_variantDictAddInt(args, TR_KEY_seedRatioMode, TR_RATIOLIMIT_GLOBAL);
2714                 break;
2715 
2716             case 952:
2717                 tr_variantDictAddInt(args, TR_KEY_seedRatioMode, TR_RATIOLIMIT_UNLIMITED);
2718                 break;
2719 
2720             case 984:
2721                 tr_variantDictAddBool(args, TR_KEY_honorsSessionLimits, true);
2722                 break;
2723 
2724             case 985:
2725                 tr_variantDictAddBool(args, TR_KEY_honorsSessionLimits, false);
2726                 break;
2727 
2728             default:
2729                 assert("unhandled value" && 0);
2730                 break;
2731             }
2732         }
2733         else if (stepMode == (MODE_TORRENT_SET | MODE_TORRENT_ADD))
2734         {
2735             tr_variant* args;
2736 
2737             if (tadd != NULL)
2738             {
2739                 args = tr_variantDictFind(tadd, ARGUMENTS);
2740             }
2741             else
2742             {
2743                 args = ensure_tset(&tset);
2744             }
2745 
2746             switch (c)
2747             {
2748             case 'g':
2749                 addFiles(args, TR_KEY_files_wanted, optarg);
2750                 break;
2751 
2752             case 'G':
2753                 addFiles(args, TR_KEY_files_unwanted, optarg);
2754                 break;
2755 
2756             case 900:
2757                 addFiles(args, TR_KEY_priority_high, optarg);
2758                 break;
2759 
2760             case 901:
2761                 addFiles(args, TR_KEY_priority_normal, optarg);
2762                 break;
2763 
2764             case 902:
2765                 addFiles(args, TR_KEY_priority_low, optarg);
2766                 break;
2767 
2768             case 700:
2769                 tr_variantDictAddInt(args, TR_KEY_bandwidthPriority, 1);
2770                 break;
2771 
2772             case 701:
2773                 tr_variantDictAddInt(args, TR_KEY_bandwidthPriority, 0);
2774                 break;
2775 
2776             case 702:
2777                 tr_variantDictAddInt(args, TR_KEY_bandwidthPriority, -1);
2778                 break;
2779 
2780             case 710:
2781                 tr_variantListAddStr(tr_variantDictAddList(args, TR_KEY_trackerAdd, 1), optarg);
2782                 break;
2783 
2784             default:
2785                 assert("unhandled value" && 0);
2786                 break;
2787             }
2788         }
2789         else if (c == 961) /* set location */
2790         {
2791             if (tadd != NULL)
2792             {
2793                 tr_variant* args = tr_variantDictFind(tadd, ARGUMENTS);
2794                 tr_variantDictAddStr(args, TR_KEY_download_dir, optarg);
2795             }
2796             else
2797             {
2798                 tr_variant* args;
2799                 tr_variant* top = tr_new0(tr_variant, 1);
2800                 tr_variantInitDict(top, 2);
2801                 tr_variantDictAddStr(top, TR_KEY_method, "torrent-set-location");
2802                 args = tr_variantDictAddDict(top, ARGUMENTS, 3);
2803                 tr_variantDictAddStr(args, TR_KEY_location, optarg);
2804                 tr_variantDictAddBool(args, TR_KEY_move, false);
2805                 addIdArg(args, id, NULL);
2806                 status |= flush(rpcurl, &top);
2807                 break;
2808             }
2809         }
2810         else
2811         {
2812             switch (c)
2813             {
2814             case 920: /* session-info */
2815                 {
2816                     tr_variant* top = tr_new0(tr_variant, 1);
2817                     tr_variantInitDict(top, 2);
2818                     tr_variantDictAddStr(top, TR_KEY_method, "session-get");
2819                     tr_variantDictAddInt(top, TR_KEY_tag, TAG_SESSION);
2820                     status |= flush(rpcurl, &top);
2821                     break;
2822                 }
2823 
2824             case 's': /* start */
2825                 {
2826                     if (tadd != NULL)
2827                     {
2828                         tr_variantDictAddBool(tr_variantDictFind(tadd, TR_KEY_arguments), TR_KEY_paused, false);
2829                     }
2830                     else
2831                     {
2832                         tr_variant* top = tr_new0(tr_variant, 1);
2833                         tr_variantInitDict(top, 2);
2834                         tr_variantDictAddStr(top, TR_KEY_method, "torrent-start");
2835                         addIdArg(tr_variantDictAddDict(top, ARGUMENTS, 1), id, NULL);
2836                         status |= flush(rpcurl, &top);
2837                     }
2838 
2839                     break;
2840                 }
2841 
2842             case 'S': /* stop */
2843                 {
2844                     if (tadd != NULL)
2845                     {
2846                         tr_variantDictAddBool(tr_variantDictFind(tadd, TR_KEY_arguments), TR_KEY_paused, true);
2847                     }
2848                     else
2849                     {
2850                         tr_variant* top = tr_new0(tr_variant, 1);
2851                         tr_variantInitDict(top, 2);
2852                         tr_variantDictAddStr(top, TR_KEY_method, "torrent-stop");
2853                         addIdArg(tr_variantDictAddDict(top, ARGUMENTS, 1), id, NULL);
2854                         status |= flush(rpcurl, &top);
2855                     }
2856 
2857                     break;
2858                 }
2859 
2860             case 'w':
2861                 {
2862                     tr_variant* args = tadd != NULL ? tr_variantDictFind(tadd, TR_KEY_arguments) : ensure_sset(&sset);
2863                     tr_variantDictAddStr(args, TR_KEY_download_dir, optarg);
2864                     break;
2865                 }
2866 
2867             case 850:
2868                 {
2869                     tr_variant* top = tr_new0(tr_variant, 1);
2870                     tr_variantInitDict(top, 1);
2871                     tr_variantDictAddStr(top, TR_KEY_method, "session-close");
2872                     status |= flush(rpcurl, &top);
2873                     break;
2874                 }
2875 
2876             case 963:
2877                 {
2878                     tr_variant* top = tr_new0(tr_variant, 1);
2879                     tr_variantInitDict(top, 1);
2880                     tr_variantDictAddStr(top, TR_KEY_method, "blocklist-update");
2881                     status |= flush(rpcurl, &top);
2882                     break;
2883                 }
2884 
2885             case 921:
2886                 {
2887                     tr_variant* top = tr_new0(tr_variant, 1);
2888                     tr_variantInitDict(top, 2);
2889                     tr_variantDictAddStr(top, TR_KEY_method, "session-stats");
2890                     tr_variantDictAddInt(top, TR_KEY_tag, TAG_STATS);
2891                     status |= flush(rpcurl, &top);
2892                     break;
2893                 }
2894 
2895             case 962:
2896                 {
2897                     tr_variant* top = tr_new0(tr_variant, 1);
2898                     tr_variantInitDict(top, 2);
2899                     tr_variantDictAddStr(top, TR_KEY_method, "port-test");
2900                     tr_variantDictAddInt(top, TR_KEY_tag, TAG_PORTTEST);
2901                     status |= flush(rpcurl, &top);
2902                     break;
2903                 }
2904 
2905             case 600:
2906                 {
2907                     tr_variant* top;
2908 
2909                     if (tset != NULL)
2910                     {
2911                         addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2912                         status |= flush(rpcurl, &tset);
2913                     }
2914 
2915                     top = tr_new0(tr_variant, 1);
2916                     tr_variantInitDict(top, 2);
2917                     tr_variantDictAddStr(top, TR_KEY_method, "torrent-reannounce");
2918                     addIdArg(tr_variantDictAddDict(top, ARGUMENTS, 1), id, NULL);
2919                     status |= flush(rpcurl, &top);
2920                     break;
2921                 }
2922 
2923             case 'v':
2924                 {
2925                     tr_variant* top;
2926 
2927                     if (tset != NULL)
2928                     {
2929                         addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2930                         status |= flush(rpcurl, &tset);
2931                     }
2932 
2933                     top = tr_new0(tr_variant, 1);
2934                     tr_variantInitDict(top, 2);
2935                     tr_variantDictAddStr(top, TR_KEY_method, "torrent-verify");
2936                     addIdArg(tr_variantDictAddDict(top, ARGUMENTS, 1), id, NULL);
2937                     status |= flush(rpcurl, &top);
2938                     break;
2939                 }
2940 
2941             case 'r':
2942             case 840:
2943                 {
2944                     tr_variant* args;
2945                     tr_variant* top = tr_new0(tr_variant, 1);
2946                     tr_variantInitDict(top, 2);
2947                     tr_variantDictAddStr(top, TR_KEY_method, "torrent-remove");
2948                     args = tr_variantDictAddDict(top, ARGUMENTS, 2);
2949                     tr_variantDictAddBool(args, TR_KEY_delete_local_data, c == 840);
2950                     addIdArg(args, id, NULL);
2951                     status |= flush(rpcurl, &top);
2952                     break;
2953                 }
2954 
2955             case 960:
2956                 {
2957                     tr_variant* args;
2958                     tr_variant* top = tr_new0(tr_variant, 1);
2959                     tr_variantInitDict(top, 2);
2960                     tr_variantDictAddStr(top, TR_KEY_method, "torrent-set-location");
2961                     args = tr_variantDictAddDict(top, ARGUMENTS, 3);
2962                     tr_variantDictAddStr(args, TR_KEY_location, optarg);
2963                     tr_variantDictAddBool(args, TR_KEY_move, true);
2964                     addIdArg(args, id, NULL);
2965                     status |= flush(rpcurl, &top);
2966                     break;
2967                 }
2968 
2969             default:
2970                 {
2971                     fprintf(stderr, "got opt [%d]\n", c);
2972                     showUsage();
2973                     break;
2974                 }
2975             }
2976         }
2977     }
2978 
2979     if (tadd != NULL)
2980     {
2981         status |= flush(rpcurl, &tadd);
2982     }
2983 
2984     if (tset != NULL)
2985     {
2986         addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2987         status |= flush(rpcurl, &tset);
2988     }
2989 
2990     if (sset != NULL)
2991     {
2992         status |= flush(rpcurl, &sset);
2993     }
2994 
2995     return status;
2996 }
2997 
2998 /* [host:port] or [host] or [port] or [http(s?)://host:port/transmission/] */
getHostAndPortAndRpcUrl(int * argc,char ** argv,char ** host,int * port,char ** rpcurl)2999 static void getHostAndPortAndRpcUrl(int* argc, char** argv, char** host, int* port, char** rpcurl)
3000 {
3001     if (*argv[1] == '-')
3002     {
3003         return;
3004     }
3005 
3006     char const* const s = argv[1];
3007 
3008     if (strncmp(s, "http://", 7) == 0) /* user passed in http rpc url */
3009     {
3010         *rpcurl = tr_strdup_printf("%s/rpc/", s + 7);
3011     }
3012     else if (strncmp(s, "https://", 8) == 0) /* user passed in https rpc url */
3013     {
3014         UseSSL = true;
3015         *rpcurl = tr_strdup_printf("%s/rpc/", s + 8);
3016     }
3017     else
3018     {
3019         char const* const first_colon = strchr(s, ':');
3020         char const* const last_colon = strrchr(s, ':');
3021 
3022         if (last_colon != NULL && ((*s == '[' && *(last_colon - 1) == ']') || first_colon == last_colon))
3023         {
3024             /* user passed in both host and port */
3025             *host = tr_strndup(s, last_colon - s);
3026             *port = atoi(last_colon + 1);
3027         }
3028         else
3029         {
3030             char* end;
3031             int const i = strtol(s, &end, 10);
3032 
3033             if (*end == '\0') /* user passed in a port */
3034             {
3035                 *port = i;
3036             }
3037             else /* user passed in a host */
3038             {
3039                 if (last_colon != NULL && first_colon != last_colon && (*s != '[' || *(s + strlen(s) - 1) != ']'))
3040                 {
3041                     /* looks like IPv6; let's add brackets as we'll be appending the port later on */
3042                     *host = tr_strdup_printf("[%s]", s);
3043                 }
3044                 else
3045                 {
3046                     *host = tr_strdup(s);
3047                 }
3048             }
3049         }
3050     }
3051 
3052     *argc -= 1;
3053 
3054     for (int i = 1; i < *argc; ++i)
3055     {
3056         argv[i] = argv[i + 1];
3057     }
3058 }
3059 
tr_main(int argc,char * argv[])3060 int tr_main(int argc, char* argv[])
3061 {
3062     int port = DEFAULT_PORT;
3063     char* host = NULL;
3064     char* rpcurl = NULL;
3065     int exit_status = EXIT_SUCCESS;
3066 
3067     if (argc < 2)
3068     {
3069         showUsage();
3070         return EXIT_FAILURE;
3071     }
3072 
3073     tr_formatter_mem_init(MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR);
3074     tr_formatter_size_init(DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR);
3075     tr_formatter_speed_init(SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR);
3076 
3077     getHostAndPortAndRpcUrl(&argc, argv, &host, &port, &rpcurl);
3078 
3079     if (host == NULL)
3080     {
3081         host = tr_strdup(DEFAULT_HOST);
3082     }
3083 
3084     if (rpcurl == NULL)
3085     {
3086         rpcurl = tr_strdup_printf("%s:%d%s", host, port, DEFAULT_URL);
3087     }
3088 
3089     exit_status = processArgs(rpcurl, argc, (char const* const*)argv);
3090 
3091     tr_free(host);
3092     tr_free(rpcurl);
3093     return exit_status;
3094 }
3095