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 <ctype.h> /* isdigit */
10 #include <errno.h>
11 #include <stdlib.h> /* strtol */
12 #include <string.h> /* strcmp */
13 
14 #include <zlib.h>
15 
16 #include <event2/buffer.h>
17 
18 #include "transmission.h"
19 #include "completion.h"
20 #include "crypto-utils.h"
21 #include "error.h"
22 #include "fdlimit.h"
23 #include "file.h"
24 #include "log.h"
25 #include "platform-quota.h" /* tr_device_info_get_free_space() */
26 #include "rpcimpl.h"
27 #include "session.h"
28 #include "session-id.h"
29 #include "stats.h"
30 #include "torrent.h"
31 #include "tr-assert.h"
32 #include "tr-macros.h"
33 #include "utils.h"
34 #include "variant.h"
35 #include "version.h"
36 #include "web.h"
37 
38 #define RPC_VERSION 16
39 #define RPC_VERSION_MIN 1
40 
41 #define RECENTLY_ACTIVE_SECONDS 60
42 
43 #if 0
44 #define dbgmsg(fmt, ...) fprintf(stderr, "%s:%d " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
45 #else
46 #define dbgmsg(...) tr_logAddDeepNamed("RPC", __VA_ARGS__)
47 #endif
48 
49 typedef enum
50 {
51     TR_FORMAT_OBJECT = 0,
52     TR_FORMAT_TABLE
53 }
54 tr_format;
55 
56 /***
57 ****
58 ***/
59 
notify(tr_session * session,int type,tr_torrent * tor)60 static tr_rpc_callback_status notify(tr_session* session, int type, tr_torrent* tor)
61 {
62     tr_rpc_callback_status status = 0;
63 
64     if (session->rpc_func != NULL)
65     {
66         status = (*session->rpc_func)(session, type, tor, session->rpc_func_user_data);
67     }
68 
69     return status;
70 }
71 
72 /***
73 ****
74 ***/
75 
76 /* For functions that can't be immediately executed, like torrentAdd,
77  * this is the callback data used to pass a response to the caller
78  * when the task is complete */
79 struct tr_rpc_idle_data
80 {
81     tr_session* session;
82     tr_variant* response;
83     tr_variant* args_out;
84     tr_rpc_response_func callback;
85     void* callback_user_data;
86 };
87 
tr_idle_function_done(struct tr_rpc_idle_data * data,char const * result)88 static void tr_idle_function_done(struct tr_rpc_idle_data* data, char const* result)
89 {
90     if (result == NULL)
91     {
92         result = "success";
93     }
94 
95     tr_variantDictAddStr(data->response, TR_KEY_result, result);
96 
97     (*data->callback)(data->session, data->response, data->callback_user_data);
98 
99     tr_variantFree(data->response);
100     tr_free(data->response);
101     tr_free(data);
102 }
103 
104 /***
105 ****
106 ***/
107 
getTorrents(tr_session * session,tr_variant * args,int * setmeCount)108 static tr_torrent** getTorrents(tr_session* session, tr_variant* args, int* setmeCount)
109 {
110     int torrentCount = 0;
111     int64_t id;
112     tr_torrent** torrents = NULL;
113     tr_variant* ids;
114     char const* str;
115 
116     if (tr_variantDictFindList(args, TR_KEY_ids, &ids))
117     {
118         int const n = tr_variantListSize(ids);
119 
120         torrents = tr_new0(tr_torrent*, n);
121 
122         for (int i = 0; i < n; ++i)
123         {
124             char const* str;
125             tr_torrent* tor;
126             tr_variant* node = tr_variantListChild(ids, i);
127 
128             if (tr_variantGetInt(node, &id))
129             {
130                 tor = tr_torrentFindFromId(session, id);
131             }
132             else if (tr_variantGetStr(node, &str, NULL))
133             {
134                 tor = tr_torrentFindFromHashString(session, str);
135             }
136             else
137             {
138                 tor = NULL;
139             }
140 
141             if (tor != NULL)
142             {
143                 torrents[torrentCount++] = tor;
144             }
145         }
146     }
147     else if (tr_variantDictFindInt(args, TR_KEY_ids, &id) || tr_variantDictFindInt(args, TR_KEY_id, &id))
148     {
149         tr_torrent* tor;
150         torrents = tr_new0(tr_torrent*, 1);
151 
152         if ((tor = tr_torrentFindFromId(session, id)) != NULL)
153         {
154             torrents[torrentCount++] = tor;
155         }
156     }
157     else if (tr_variantDictFindStr(args, TR_KEY_ids, &str, NULL))
158     {
159         if (strcmp(str, "recently-active") == 0)
160         {
161             tr_torrent* tor = NULL;
162             time_t const now = tr_time();
163             time_t const window = RECENTLY_ACTIVE_SECONDS;
164             int const n = tr_sessionCountTorrents(session);
165             torrents = tr_new0(tr_torrent*, n);
166 
167             while ((tor = tr_torrentNext(session, tor)) != NULL)
168             {
169                 if (tor->anyDate >= now - window)
170                 {
171                     torrents[torrentCount++] = tor;
172                 }
173             }
174         }
175         else
176         {
177             tr_torrent* tor;
178             torrents = tr_new0(tr_torrent*, 1);
179 
180             if ((tor = tr_torrentFindFromHashString(session, str)) != NULL)
181             {
182                 torrents[torrentCount++] = tor;
183             }
184         }
185     }
186     else /* all of them */
187     {
188         torrents = tr_sessionGetTorrents(session, &torrentCount);
189     }
190 
191     *setmeCount = torrentCount;
192     return torrents;
193 }
194 
notifyBatchQueueChange(tr_session * session,tr_torrent ** torrents,int n)195 static void notifyBatchQueueChange(tr_session* session, tr_torrent** torrents, int n)
196 {
197     for (int i = 0; i < n; ++i)
198     {
199         notify(session, TR_RPC_TORRENT_CHANGED, torrents[i]);
200     }
201 
202     notify(session, TR_RPC_SESSION_QUEUE_POSITIONS_CHANGED, NULL);
203 }
204 
queueMoveTop(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)205 static char const* queueMoveTop(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
206     struct tr_rpc_idle_data* idle_data UNUSED)
207 {
208     int n;
209     tr_torrent** torrents = getTorrents(session, args_in, &n);
210     tr_torrentsQueueMoveTop(torrents, n);
211     notifyBatchQueueChange(session, torrents, n);
212     tr_free(torrents);
213     return NULL;
214 }
215 
queueMoveUp(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)216 static char const* queueMoveUp(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
217     struct tr_rpc_idle_data* idle_data UNUSED)
218 {
219     int n;
220     tr_torrent** torrents = getTorrents(session, args_in, &n);
221     tr_torrentsQueueMoveUp(torrents, n);
222     notifyBatchQueueChange(session, torrents, n);
223     tr_free(torrents);
224     return NULL;
225 }
226 
queueMoveDown(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)227 static char const* queueMoveDown(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
228     struct tr_rpc_idle_data* idle_data UNUSED)
229 {
230     int n;
231     tr_torrent** torrents = getTorrents(session, args_in, &n);
232     tr_torrentsQueueMoveDown(torrents, n);
233     notifyBatchQueueChange(session, torrents, n);
234     tr_free(torrents);
235     return NULL;
236 }
237 
queueMoveBottom(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)238 static char const* queueMoveBottom(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
239     struct tr_rpc_idle_data* idle_data UNUSED)
240 {
241     int n;
242     tr_torrent** torrents = getTorrents(session, args_in, &n);
243     tr_torrentsQueueMoveBottom(torrents, n);
244     notifyBatchQueueChange(session, torrents, n);
245     tr_free(torrents);
246     return NULL;
247 }
248 
compareTorrentByQueuePosition(void const * va,void const * vb)249 static int compareTorrentByQueuePosition(void const* va, void const* vb)
250 {
251     tr_torrent const* a = *(tr_torrent const**)va;
252     tr_torrent const* b = *(tr_torrent const**)vb;
253 
254     return a->queuePosition - b->queuePosition;
255 }
256 
torrentStart(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)257 static char const* torrentStart(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
258     struct tr_rpc_idle_data* idle_data UNUSED)
259 {
260     TR_ASSERT(idle_data == NULL);
261 
262     int torrentCount;
263     tr_torrent** torrents = getTorrents(session, args_in, &torrentCount);
264 
265     qsort(torrents, torrentCount, sizeof(tr_torrent*), compareTorrentByQueuePosition);
266 
267     for (int i = 0; i < torrentCount; ++i)
268     {
269         tr_torrent* tor = torrents[i];
270 
271         if (!tor->isRunning)
272         {
273             tr_torrentStart(tor);
274             notify(session, TR_RPC_TORRENT_STARTED, tor);
275         }
276     }
277 
278     tr_free(torrents);
279     return NULL;
280 }
281 
torrentStartNow(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)282 static char const* torrentStartNow(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
283     struct tr_rpc_idle_data* idle_data UNUSED)
284 {
285     TR_ASSERT(idle_data == NULL);
286 
287     int torrentCount;
288     tr_torrent** torrents = getTorrents(session, args_in, &torrentCount);
289 
290     qsort(torrents, torrentCount, sizeof(tr_torrent*), compareTorrentByQueuePosition);
291 
292     for (int i = 0; i < torrentCount; ++i)
293     {
294         tr_torrent* tor = torrents[i];
295 
296         if (!tor->isRunning)
297         {
298             tr_torrentStartNow(tor);
299             notify(session, TR_RPC_TORRENT_STARTED, tor);
300         }
301     }
302 
303     tr_free(torrents);
304     return NULL;
305 }
306 
torrentStop(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)307 static char const* torrentStop(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
308     struct tr_rpc_idle_data* idle_data UNUSED)
309 {
310     TR_ASSERT(idle_data == NULL);
311 
312     int torrentCount;
313     tr_torrent** torrents = getTorrents(session, args_in, &torrentCount);
314 
315     for (int i = 0; i < torrentCount; ++i)
316     {
317         tr_torrent* tor = torrents[i];
318 
319         if (tor->isRunning || tr_torrentIsQueued(tor))
320         {
321             tor->isStopping = true;
322             notify(session, TR_RPC_TORRENT_STOPPED, tor);
323         }
324     }
325 
326     tr_free(torrents);
327     return NULL;
328 }
329 
torrentRemove(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)330 static char const* torrentRemove(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
331     struct tr_rpc_idle_data* idle_data UNUSED)
332 {
333     TR_ASSERT(idle_data == NULL);
334 
335     bool deleteFlag;
336 
337     if (!tr_variantDictFindBool(args_in, TR_KEY_delete_local_data, &deleteFlag))
338     {
339         deleteFlag = false;
340     }
341 
342     tr_rpc_callback_type type = deleteFlag ? TR_RPC_TORRENT_TRASHING : TR_RPC_TORRENT_REMOVING;
343 
344     int torrentCount;
345     tr_torrent** torrents = getTorrents(session, args_in, &torrentCount);
346 
347     for (int i = 0; i < torrentCount; ++i)
348     {
349         tr_torrent* tor = torrents[i];
350         tr_rpc_callback_status const status = notify(session, type, tor);
351 
352         if ((status & TR_RPC_NOREMOVE) == 0)
353         {
354             tr_torrentRemove(tor, deleteFlag, NULL);
355         }
356     }
357 
358     tr_free(torrents);
359     return NULL;
360 }
361 
torrentReannounce(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)362 static char const* torrentReannounce(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
363     struct tr_rpc_idle_data* idle_data UNUSED)
364 {
365     TR_ASSERT(idle_data == NULL);
366 
367     int torrentCount;
368     tr_torrent** torrents = getTorrents(session, args_in, &torrentCount);
369 
370     for (int i = 0; i < torrentCount; ++i)
371     {
372         tr_torrent* tor = torrents[i];
373 
374         if (tr_torrentCanManualUpdate(tor))
375         {
376             tr_torrentManualUpdate(tor);
377             notify(session, TR_RPC_TORRENT_CHANGED, tor);
378         }
379     }
380 
381     tr_free(torrents);
382     return NULL;
383 }
384 
torrentVerify(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)385 static char const* torrentVerify(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
386     struct tr_rpc_idle_data* idle_data UNUSED)
387 {
388     TR_ASSERT(idle_data == NULL);
389 
390     int torrentCount;
391     tr_torrent** torrents = getTorrents(session, args_in, &torrentCount);
392 
393     for (int i = 0; i < torrentCount; ++i)
394     {
395         tr_torrent* tor = torrents[i];
396         tr_torrentVerify(tor, NULL, NULL);
397         notify(session, TR_RPC_TORRENT_CHANGED, tor);
398     }
399 
400     tr_free(torrents);
401     return NULL;
402 }
403 
404 /***
405 ****
406 ***/
407 
addLabels(tr_torrent const * tor,tr_variant * list)408 static void addLabels(tr_torrent const* tor, tr_variant* list)
409 {
410     int const labelsCount = tr_ptrArraySize(&tor->labels);
411     tr_variantInitList(list, labelsCount);
412     char const* const* labels = (char const* const*)tr_ptrArrayBase(&tor->labels);
413     for (int i = 0; i < labelsCount; ++i)
414     {
415         tr_variantListAddStr(list, labels[i]);
416     }
417 }
418 
addFileStats(tr_torrent const * tor,tr_variant * list)419 static void addFileStats(tr_torrent const* tor, tr_variant* list)
420 {
421     tr_file_index_t n;
422     tr_info const* info = tr_torrentInfo(tor);
423     tr_file_stat* files = tr_torrentFiles(tor, &n);
424 
425     for (tr_file_index_t i = 0; i < info->fileCount; ++i)
426     {
427         tr_file const* file = &info->files[i];
428         tr_variant* d = tr_variantListAddDict(list, 3);
429         tr_variantDictAddInt(d, TR_KEY_bytesCompleted, files[i].bytesCompleted);
430         tr_variantDictAddInt(d, TR_KEY_priority, file->priority);
431         tr_variantDictAddBool(d, TR_KEY_wanted, !file->dnd);
432     }
433 
434     tr_torrentFilesFree(files, n);
435 }
436 
addFiles(tr_torrent const * tor,tr_variant * list)437 static void addFiles(tr_torrent const* tor, tr_variant* list)
438 {
439     tr_file_index_t n;
440     tr_info const* info = tr_torrentInfo(tor);
441     tr_file_stat* files = tr_torrentFiles(tor, &n);
442 
443     for (tr_file_index_t i = 0; i < info->fileCount; ++i)
444     {
445         tr_file const* file = &info->files[i];
446         tr_variant* d = tr_variantListAddDict(list, 3);
447         tr_variantDictAddInt(d, TR_KEY_bytesCompleted, files[i].bytesCompleted);
448         tr_variantDictAddInt(d, TR_KEY_length, file->length);
449         tr_variantDictAddStr(d, TR_KEY_name, file->name);
450     }
451 
452     tr_torrentFilesFree(files, n);
453 }
454 
addWebseeds(tr_info const * info,tr_variant * webseeds)455 static void addWebseeds(tr_info const* info, tr_variant* webseeds)
456 {
457     for (unsigned int i = 0; i < info->webseedCount; ++i)
458     {
459         tr_variantListAddStr(webseeds, info->webseeds[i]);
460     }
461 }
462 
addTrackers(tr_info const * info,tr_variant * trackers)463 static void addTrackers(tr_info const* info, tr_variant* trackers)
464 {
465     for (unsigned int i = 0; i < info->trackerCount; ++i)
466     {
467         tr_tracker_info const* t = &info->trackers[i];
468         tr_variant* d = tr_variantListAddDict(trackers, 4);
469         tr_variantDictAddStr(d, TR_KEY_announce, t->announce);
470         tr_variantDictAddInt(d, TR_KEY_id, t->id);
471         tr_variantDictAddStr(d, TR_KEY_scrape, t->scrape);
472         tr_variantDictAddInt(d, TR_KEY_tier, t->tier);
473     }
474 }
475 
addTrackerStats(tr_tracker_stat const * st,int n,tr_variant * list)476 static void addTrackerStats(tr_tracker_stat const* st, int n, tr_variant* list)
477 {
478     for (int i = 0; i < n; ++i)
479     {
480         tr_tracker_stat const* s = &st[i];
481         tr_variant* d = tr_variantListAddDict(list, 26);
482         tr_variantDictAddStr(d, TR_KEY_announce, s->announce);
483         tr_variantDictAddInt(d, TR_KEY_announceState, s->announceState);
484         tr_variantDictAddInt(d, TR_KEY_downloadCount, s->downloadCount);
485         tr_variantDictAddBool(d, TR_KEY_hasAnnounced, s->hasAnnounced);
486         tr_variantDictAddBool(d, TR_KEY_hasScraped, s->hasScraped);
487         tr_variantDictAddStr(d, TR_KEY_host, s->host);
488         tr_variantDictAddInt(d, TR_KEY_id, s->id);
489         tr_variantDictAddBool(d, TR_KEY_isBackup, s->isBackup);
490         tr_variantDictAddInt(d, TR_KEY_lastAnnouncePeerCount, s->lastAnnouncePeerCount);
491         tr_variantDictAddStr(d, TR_KEY_lastAnnounceResult, s->lastAnnounceResult);
492         tr_variantDictAddInt(d, TR_KEY_lastAnnounceStartTime, s->lastAnnounceStartTime);
493         tr_variantDictAddBool(d, TR_KEY_lastAnnounceSucceeded, s->lastAnnounceSucceeded);
494         tr_variantDictAddInt(d, TR_KEY_lastAnnounceTime, s->lastAnnounceTime);
495         tr_variantDictAddBool(d, TR_KEY_lastAnnounceTimedOut, s->lastAnnounceTimedOut);
496         tr_variantDictAddStr(d, TR_KEY_lastScrapeResult, s->lastScrapeResult);
497         tr_variantDictAddInt(d, TR_KEY_lastScrapeStartTime, s->lastScrapeStartTime);
498         tr_variantDictAddBool(d, TR_KEY_lastScrapeSucceeded, s->lastScrapeSucceeded);
499         tr_variantDictAddInt(d, TR_KEY_lastScrapeTime, s->lastScrapeTime);
500         tr_variantDictAddBool(d, TR_KEY_lastScrapeTimedOut, s->lastScrapeTimedOut);
501         tr_variantDictAddInt(d, TR_KEY_leecherCount, s->leecherCount);
502         tr_variantDictAddInt(d, TR_KEY_nextAnnounceTime, s->nextAnnounceTime);
503         tr_variantDictAddInt(d, TR_KEY_nextScrapeTime, s->nextScrapeTime);
504         tr_variantDictAddStr(d, TR_KEY_scrape, s->scrape);
505         tr_variantDictAddInt(d, TR_KEY_scrapeState, s->scrapeState);
506         tr_variantDictAddInt(d, TR_KEY_seederCount, s->seederCount);
507         tr_variantDictAddInt(d, TR_KEY_tier, s->tier);
508     }
509 }
510 
addPeers(tr_torrent * tor,tr_variant * list)511 static void addPeers(tr_torrent* tor, tr_variant* list)
512 {
513     int peerCount;
514     tr_peer_stat* peers = tr_torrentPeers(tor, &peerCount);
515 
516     tr_variantInitList(list, peerCount);
517 
518     for (int i = 0; i < peerCount; ++i)
519     {
520         tr_variant* d = tr_variantListAddDict(list, 16);
521         tr_peer_stat const* peer = peers + i;
522         tr_variantDictAddStr(d, TR_KEY_address, peer->addr);
523         tr_variantDictAddStr(d, TR_KEY_clientName, peer->client);
524         tr_variantDictAddBool(d, TR_KEY_clientIsChoked, peer->clientIsChoked);
525         tr_variantDictAddBool(d, TR_KEY_clientIsInterested, peer->clientIsInterested);
526         tr_variantDictAddStr(d, TR_KEY_flagStr, peer->flagStr);
527         tr_variantDictAddBool(d, TR_KEY_isDownloadingFrom, peer->isDownloadingFrom);
528         tr_variantDictAddBool(d, TR_KEY_isEncrypted, peer->isEncrypted);
529         tr_variantDictAddBool(d, TR_KEY_isIncoming, peer->isIncoming);
530         tr_variantDictAddBool(d, TR_KEY_isUploadingTo, peer->isUploadingTo);
531         tr_variantDictAddBool(d, TR_KEY_isUTP, peer->isUTP);
532         tr_variantDictAddBool(d, TR_KEY_peerIsChoked, peer->peerIsChoked);
533         tr_variantDictAddBool(d, TR_KEY_peerIsInterested, peer->peerIsInterested);
534         tr_variantDictAddInt(d, TR_KEY_port, peer->port);
535         tr_variantDictAddReal(d, TR_KEY_progress, peer->progress);
536         tr_variantDictAddInt(d, TR_KEY_rateToClient, toSpeedBytes(peer->rateToClient_KBps));
537         tr_variantDictAddInt(d, TR_KEY_rateToPeer, toSpeedBytes(peer->rateToPeer_KBps));
538     }
539 
540     tr_torrentPeersFree(peers, peerCount);
541 }
542 
initField(tr_torrent * const tor,tr_info const * const inf,tr_stat const * const st,tr_variant * const initme,tr_quark key)543 static void initField(tr_torrent* const tor, tr_info const* const inf, tr_stat const* const st, tr_variant* const initme,
544     tr_quark key)
545 {
546     char* str;
547 
548     switch (key)
549     {
550     case TR_KEY_activityDate:
551         tr_variantInitInt(initme, st->activityDate);
552         break;
553 
554     case TR_KEY_addedDate:
555         tr_variantInitInt(initme, st->addedDate);
556         break;
557 
558     case TR_KEY_bandwidthPriority:
559         tr_variantInitInt(initme, tr_torrentGetPriority(tor));
560         break;
561 
562     case TR_KEY_comment:
563         tr_variantInitStr(initme, inf->comment != NULL ? inf->comment : "", TR_BAD_SIZE);
564         break;
565 
566     case TR_KEY_corruptEver:
567         tr_variantInitInt(initme, st->corruptEver);
568         break;
569 
570     case TR_KEY_creator:
571         tr_variantInitStr(initme, inf->creator != NULL ? inf->creator : "", TR_BAD_SIZE);
572         break;
573 
574     case TR_KEY_dateCreated:
575         tr_variantInitInt(initme, inf->dateCreated);
576         break;
577 
578     case TR_KEY_desiredAvailable:
579         tr_variantInitInt(initme, st->desiredAvailable);
580         break;
581 
582     case TR_KEY_doneDate:
583         tr_variantInitInt(initme, st->doneDate);
584         break;
585 
586     case TR_KEY_downloadDir:
587         tr_variantInitStr(initme, tr_torrentGetDownloadDir(tor), TR_BAD_SIZE);
588         break;
589 
590     case TR_KEY_downloadedEver:
591         tr_variantInitInt(initme, st->downloadedEver);
592         break;
593 
594     case TR_KEY_downloadLimit:
595         tr_variantInitInt(initme, tr_torrentGetSpeedLimit_KBps(tor, TR_DOWN));
596         break;
597 
598     case TR_KEY_downloadLimited:
599         tr_variantInitBool(initme, tr_torrentUsesSpeedLimit(tor, TR_DOWN));
600         break;
601 
602     case TR_KEY_error:
603         tr_variantInitInt(initme, st->error);
604         break;
605 
606     case TR_KEY_errorString:
607         tr_variantInitStr(initme, st->errorString, TR_BAD_SIZE);
608         break;
609 
610     case TR_KEY_eta:
611         tr_variantInitInt(initme, st->eta);
612         break;
613 
614     case TR_KEY_files:
615         tr_variantInitList(initme, inf->fileCount);
616         addFiles(tor, initme);
617         break;
618 
619     case TR_KEY_fileStats:
620         tr_variantInitList(initme, inf->fileCount);
621         addFileStats(tor, initme);
622         break;
623 
624     case TR_KEY_hashString:
625         tr_variantInitStr(initme, tor->info.hashString, TR_BAD_SIZE);
626         break;
627 
628     case TR_KEY_haveUnchecked:
629         tr_variantInitInt(initme, st->haveUnchecked);
630         break;
631 
632     case TR_KEY_haveValid:
633         tr_variantInitInt(initme, st->haveValid);
634         break;
635 
636     case TR_KEY_honorsSessionLimits:
637         tr_variantInitBool(initme, tr_torrentUsesSessionLimits(tor));
638         break;
639 
640     case TR_KEY_id:
641         tr_variantInitInt(initme, st->id);
642         break;
643 
644     case TR_KEY_editDate:
645         tr_variantInitInt(initme, st->editDate);
646         break;
647 
648     case TR_KEY_isFinished:
649         tr_variantInitBool(initme, st->finished);
650         break;
651 
652     case TR_KEY_isPrivate:
653         tr_variantInitBool(initme, tr_torrentIsPrivate(tor));
654         break;
655 
656     case TR_KEY_isStalled:
657         tr_variantInitBool(initme, st->isStalled);
658         break;
659 
660     case TR_KEY_labels:
661         addLabels(tor, initme);
662         break;
663 
664     case TR_KEY_leftUntilDone:
665         tr_variantInitInt(initme, st->leftUntilDone);
666         break;
667 
668     case TR_KEY_manualAnnounceTime:
669         tr_variantInitInt(initme, st->manualAnnounceTime);
670         break;
671 
672     case TR_KEY_maxConnectedPeers:
673         tr_variantInitInt(initme, tr_torrentGetPeerLimit(tor));
674         break;
675 
676     case TR_KEY_magnetLink:
677         str = tr_torrentGetMagnetLink(tor);
678         tr_variantInitStr(initme, str, TR_BAD_SIZE);
679         tr_free(str);
680         break;
681 
682     case TR_KEY_metadataPercentComplete:
683         tr_variantInitReal(initme, st->metadataPercentComplete);
684         break;
685 
686     case TR_KEY_name:
687         tr_variantInitStr(initme, tr_torrentName(tor), TR_BAD_SIZE);
688         break;
689 
690     case TR_KEY_percentDone:
691         tr_variantInitReal(initme, st->percentDone);
692         break;
693 
694     case TR_KEY_peer_limit:
695         tr_variantInitInt(initme, tr_torrentGetPeerLimit(tor));
696         break;
697 
698     case TR_KEY_peers:
699         addPeers(tor, initme);
700         break;
701 
702     case TR_KEY_peersConnected:
703         tr_variantInitInt(initme, st->peersConnected);
704         break;
705 
706     case TR_KEY_peersFrom:
707         {
708             tr_variantInitDict(initme, 7);
709             int const* f = st->peersFrom;
710             tr_variantDictAddInt(initme, TR_KEY_fromCache, f[TR_PEER_FROM_RESUME]);
711             tr_variantDictAddInt(initme, TR_KEY_fromDht, f[TR_PEER_FROM_DHT]);
712             tr_variantDictAddInt(initme, TR_KEY_fromIncoming, f[TR_PEER_FROM_INCOMING]);
713             tr_variantDictAddInt(initme, TR_KEY_fromLpd, f[TR_PEER_FROM_LPD]);
714             tr_variantDictAddInt(initme, TR_KEY_fromLtep, f[TR_PEER_FROM_LTEP]);
715             tr_variantDictAddInt(initme, TR_KEY_fromPex, f[TR_PEER_FROM_PEX]);
716             tr_variantDictAddInt(initme, TR_KEY_fromTracker, f[TR_PEER_FROM_TRACKER]);
717             break;
718         }
719 
720     case TR_KEY_peersGettingFromUs:
721         tr_variantInitInt(initme, st->peersGettingFromUs);
722         break;
723 
724     case TR_KEY_peersSendingToUs:
725         tr_variantInitInt(initme, st->peersSendingToUs);
726         break;
727 
728     case TR_KEY_pieces:
729         if (tr_torrentHasMetadata(tor))
730         {
731             size_t byte_count = 0;
732             void* bytes = tr_torrentCreatePieceBitfield(tor, &byte_count);
733             char* str = tr_base64_encode(bytes, byte_count, NULL);
734             tr_variantInitStr(initme, str != NULL ? str : "", TR_BAD_SIZE);
735             tr_free(str);
736             tr_free(bytes);
737         }
738         else
739         {
740             tr_variantInitStr(initme, "", 0);
741         }
742 
743         break;
744 
745     case TR_KEY_pieceCount:
746         tr_variantInitInt(initme, inf->pieceCount);
747         break;
748 
749     case TR_KEY_pieceSize:
750         tr_variantInitInt(initme, inf->pieceSize);
751         break;
752 
753     case TR_KEY_priorities:
754         tr_variantInitList(initme, inf->fileCount);
755         for (tr_file_index_t i = 0; i < inf->fileCount; ++i)
756         {
757             tr_variantListAddInt(initme, inf->files[i].priority);
758         }
759 
760         break;
761 
762     case TR_KEY_queuePosition:
763         tr_variantInitInt(initme, st->queuePosition);
764         break;
765 
766     case TR_KEY_etaIdle:
767         tr_variantInitInt(initme, st->etaIdle);
768         break;
769 
770     case TR_KEY_rateDownload:
771         tr_variantInitInt(initme, toSpeedBytes(st->pieceDownloadSpeed_KBps));
772         break;
773 
774     case TR_KEY_rateUpload:
775         tr_variantInitInt(initme, toSpeedBytes(st->pieceUploadSpeed_KBps));
776         break;
777 
778     case TR_KEY_recheckProgress:
779         tr_variantInitReal(initme, st->recheckProgress);
780         break;
781 
782     case TR_KEY_seedIdleLimit:
783         tr_variantInitInt(initme, tr_torrentGetIdleLimit(tor));
784         break;
785 
786     case TR_KEY_seedIdleMode:
787         tr_variantInitInt(initme, tr_torrentGetIdleMode(tor));
788         break;
789 
790     case TR_KEY_seedRatioLimit:
791         tr_variantInitReal(initme, tr_torrentGetRatioLimit(tor));
792         break;
793 
794     case TR_KEY_seedRatioMode:
795         tr_variantInitInt(initme, tr_torrentGetRatioMode(tor));
796         break;
797 
798     case TR_KEY_sizeWhenDone:
799         tr_variantInitInt(initme, st->sizeWhenDone);
800         break;
801 
802     case TR_KEY_startDate:
803         tr_variantInitInt(initme, st->startDate);
804         break;
805 
806     case TR_KEY_status:
807         tr_variantInitInt(initme, st->activity);
808         break;
809 
810     case TR_KEY_secondsDownloading:
811         tr_variantInitInt(initme, st->secondsDownloading);
812         break;
813 
814     case TR_KEY_secondsSeeding:
815         tr_variantInitInt(initme, st->secondsSeeding);
816         break;
817 
818     case TR_KEY_trackers:
819         tr_variantInitList(initme, inf->trackerCount);
820         addTrackers(inf, initme);
821         break;
822 
823     case TR_KEY_trackerStats:
824         {
825             int n;
826             tr_tracker_stat* s = tr_torrentTrackers(tor, &n);
827             tr_variantInitList(initme, n);
828             addTrackerStats(s, n, initme);
829             tr_torrentTrackersFree(s, n);
830             break;
831         }
832 
833     case TR_KEY_torrentFile:
834         tr_variantInitStr(initme, inf->torrent, TR_BAD_SIZE);
835         break;
836 
837     case TR_KEY_totalSize:
838         tr_variantInitInt(initme, inf->totalSize);
839         break;
840 
841     case TR_KEY_uploadedEver:
842         tr_variantInitInt(initme, st->uploadedEver);
843         break;
844 
845     case TR_KEY_uploadLimit:
846         tr_variantInitInt(initme, tr_torrentGetSpeedLimit_KBps(tor, TR_UP));
847         break;
848 
849     case TR_KEY_uploadLimited:
850         tr_variantInitBool(initme, tr_torrentUsesSpeedLimit(tor, TR_UP));
851         break;
852 
853     case TR_KEY_uploadRatio:
854         tr_variantInitReal(initme, st->ratio);
855         break;
856 
857     case TR_KEY_wanted:
858         tr_variantInitList(initme, inf->fileCount);
859 
860         for (tr_file_index_t i = 0; i < inf->fileCount; ++i)
861         {
862             tr_variantListAddInt(initme, inf->files[i].dnd ? 0 : 1);
863         }
864 
865         break;
866 
867     case TR_KEY_webseeds:
868         tr_variantInitList(initme, inf->webseedCount);
869         addWebseeds(inf, initme);
870         break;
871 
872     case TR_KEY_webseedsSendingToUs:
873         tr_variantInitInt(initme, st->webseedsSendingToUs);
874         break;
875 
876     default:
877         break;
878     }
879 }
880 
addTorrentInfo(tr_torrent * tor,tr_format format,tr_variant * entry,tr_quark const * fields,size_t fieldCount)881 static void addTorrentInfo(tr_torrent* tor, tr_format format, tr_variant* entry, tr_quark const* fields, size_t fieldCount)
882 {
883     if (format == TR_FORMAT_TABLE)
884     {
885         tr_variantInitList(entry, fieldCount);
886     }
887     else
888     {
889         tr_variantInitDict(entry, fieldCount);
890     }
891 
892     if (fieldCount > 0)
893     {
894         tr_info const* const inf = tr_torrentInfo(tor);
895         tr_stat const* const st = tr_torrentStat((tr_torrent*)tor);
896 
897         for (size_t i = 0; i < fieldCount; ++i)
898         {
899             tr_variant* child = format == TR_FORMAT_TABLE ?
900                 tr_variantListAdd(entry) :
901                 tr_variantDictAdd(entry, fields[i]);
902 
903             initField(tor, inf, st, child, fields[i]);
904         }
905     }
906 }
907 
torrentGet(tr_session * session,tr_variant * args_in,tr_variant * args_out,struct tr_rpc_idle_data * idle_data UNUSED)908 static char const* torrentGet(tr_session* session, tr_variant* args_in, tr_variant* args_out,
909     struct tr_rpc_idle_data* idle_data UNUSED)
910 {
911     TR_ASSERT(idle_data == NULL);
912 
913     int torrentCount;
914     tr_torrent** torrents = getTorrents(session, args_in, &torrentCount);
915     tr_variant* list = tr_variantDictAddList(args_out, TR_KEY_torrents, torrentCount + 1);
916     tr_variant* fields;
917     char const* strVal;
918     char const* errmsg = NULL;
919     tr_format format;
920 
921     if (tr_variantDictFindStr(args_in, TR_KEY_format, &strVal, NULL) && strcmp(strVal, "table") == 0)
922     {
923         format = TR_FORMAT_TABLE;
924     }
925     else /* default value */
926     {
927         format = TR_FORMAT_OBJECT;
928     }
929 
930     if (tr_variantDictFindStr(args_in, TR_KEY_ids, &strVal, NULL) && strcmp(strVal, "recently-active") == 0)
931     {
932         int n = 0;
933         tr_variant* d;
934         time_t const now = tr_time();
935         int const interval = RECENTLY_ACTIVE_SECONDS;
936         tr_variant* removed_out = tr_variantDictAddList(args_out, TR_KEY_removed, 0);
937 
938         while ((d = tr_variantListChild(&session->removedTorrents, n)) != NULL)
939         {
940             int64_t date;
941             int64_t id;
942 
943             if (tr_variantDictFindInt(d, TR_KEY_date, &date) &&
944                 date >= now - interval &&
945                 tr_variantDictFindInt(d, TR_KEY_id, &id))
946             {
947                 tr_variantListAddInt(removed_out, id);
948             }
949 
950             ++n;
951         }
952     }
953 
954     if (!tr_variantDictFindList(args_in, TR_KEY_fields, &fields))
955     {
956         errmsg = "no fields specified";
957     }
958     else
959     {
960         /* make an array of property name quarks */
961         size_t keyCount = 0;
962         size_t const n = tr_variantListSize(fields);
963         tr_quark* keys = tr_new(tr_quark, n);
964         for (size_t i = 0; i < n; ++i)
965         {
966             size_t len;
967             if (tr_variantGetStr(tr_variantListChild(fields, i), &strVal, &len))
968             {
969                 keys[keyCount++] = tr_quark_new(strVal, len);
970             }
971         }
972 
973         if (format == TR_FORMAT_TABLE)
974         {
975             /* first entry is an array of property names */
976             tr_variant* names = tr_variantListAddList(list, keyCount);
977             for (size_t i = 0; i < keyCount; ++i)
978             {
979                 tr_variantListAddQuark(names, keys[i]);
980             }
981         }
982 
983         for (int i = 0; i < torrentCount; ++i)
984         {
985             addTorrentInfo(torrents[i], format, tr_variantListAdd(list), keys, keyCount);
986         }
987 
988         tr_free(keys);
989     }
990 
991     tr_free(torrents);
992     return errmsg;
993 }
994 
995 /***
996 ****
997 ***/
998 
setLabels(tr_torrent * tor,tr_variant * list)999 static char const* setLabels(tr_torrent* tor, tr_variant* list)
1000 {
1001     int const n = tr_variantListSize(list);
1002     char const* errmsg = NULL;
1003     tr_ptrArray labels = TR_PTR_ARRAY_INIT;
1004     int labelcount = 0;
1005     for (int i = 0; i < n; i++)
1006     {
1007         char const* str;
1008         size_t str_len;
1009         if (tr_variantGetStr(tr_variantListChild(list, i), &str, &str_len) && str != NULL)
1010         {
1011             char* label = tr_strndup(str, str_len);
1012             tr_strstrip(label);
1013             if (tr_str_is_empty(label))
1014             {
1015                 errmsg = "labels cannot be empty";
1016             }
1017 
1018             if (errmsg == NULL && strchr(str, ',') != NULL)
1019             {
1020                 errmsg = "labels cannot contain comma (,) character";
1021             }
1022 
1023             if (errmsg == NULL)
1024             {
1025                 bool dup = false;
1026                 for (int j = 0; j < labelcount; j++)
1027                 {
1028                     if (tr_strcmp0(label, (char*)tr_ptrArrayNth(&labels, j)) == 0)
1029                     {
1030                         dup = true;
1031                         break;
1032                     }
1033                 }
1034 
1035                 if (dup)
1036                 {
1037                     errmsg = "labels cannot contain duplicates";
1038                 }
1039             }
1040 
1041             tr_ptrArrayAppend(&labels, label);
1042             labelcount++;
1043 
1044             if (errmsg != NULL)
1045             {
1046                 break;
1047             }
1048         }
1049     }
1050 
1051     if (errmsg == NULL)
1052     {
1053         tr_torrentSetLabels(tor, &labels);
1054     }
1055 
1056     tr_ptrArrayDestruct(&labels, tr_free);
1057     return errmsg;
1058 }
1059 
setFilePriorities(tr_torrent * tor,int priority,tr_variant * list)1060 static char const* setFilePriorities(tr_torrent* tor, int priority, tr_variant* list)
1061 {
1062     int64_t tmp;
1063     int fileCount = 0;
1064     int const n = tr_variantListSize(list);
1065     char const* errmsg = NULL;
1066     tr_file_index_t* files = tr_new0(tr_file_index_t, tor->info.fileCount);
1067 
1068     if (n != 0)
1069     {
1070         for (int i = 0; i < n; ++i)
1071         {
1072             if (tr_variantGetInt(tr_variantListChild(list, i), &tmp))
1073             {
1074                 if (0 <= tmp && tmp < tor->info.fileCount)
1075                 {
1076                     files[fileCount++] = tmp;
1077                 }
1078                 else
1079                 {
1080                     errmsg = "file index out of range";
1081                 }
1082             }
1083         }
1084     }
1085     else /* if empty set, apply to all */
1086     {
1087         for (tr_file_index_t t = 0; t < tor->info.fileCount; ++t)
1088         {
1089             files[fileCount++] = t;
1090         }
1091     }
1092 
1093     if (fileCount != 0)
1094     {
1095         tr_torrentSetFilePriorities(tor, files, fileCount, priority);
1096     }
1097 
1098     tr_free(files);
1099     return errmsg;
1100 }
1101 
setFileDLs(tr_torrent * tor,bool do_download,tr_variant * list)1102 static char const* setFileDLs(tr_torrent* tor, bool do_download, tr_variant* list)
1103 {
1104     int64_t tmp;
1105     int fileCount = 0;
1106     int const n = tr_variantListSize(list);
1107     char const* errmsg = NULL;
1108     tr_file_index_t* files = tr_new0(tr_file_index_t, tor->info.fileCount);
1109 
1110     if (n != 0) /* if argument list, process them */
1111     {
1112         for (int i = 0; i < n; ++i)
1113         {
1114             if (tr_variantGetInt(tr_variantListChild(list, i), &tmp))
1115             {
1116                 if (0 <= tmp && tmp < tor->info.fileCount)
1117                 {
1118                     files[fileCount++] = tmp;
1119                 }
1120                 else
1121                 {
1122                     errmsg = "file index out of range";
1123                 }
1124             }
1125         }
1126     }
1127     else /* if empty set, apply to all */
1128     {
1129         for (tr_file_index_t t = 0; t < tor->info.fileCount; ++t)
1130         {
1131             files[fileCount++] = t;
1132         }
1133     }
1134 
1135     if (fileCount != 0)
1136     {
1137         tr_torrentSetFileDLs(tor, files, fileCount, do_download);
1138     }
1139 
1140     tr_free(files);
1141     return errmsg;
1142 }
1143 
findAnnounceUrl(tr_tracker_info const * t,int n,char const * url,int * pos)1144 static bool findAnnounceUrl(tr_tracker_info const* t, int n, char const* url, int* pos)
1145 {
1146     bool found = false;
1147 
1148     for (int i = 0; i < n; ++i)
1149     {
1150         if (strcmp(t[i].announce, url) == 0)
1151         {
1152             found = true;
1153 
1154             if (pos != NULL)
1155             {
1156                 *pos = i;
1157             }
1158 
1159             break;
1160         }
1161     }
1162 
1163     return found;
1164 }
1165 
copyTrackers(tr_tracker_info * tgt,tr_tracker_info const * src,int n)1166 static int copyTrackers(tr_tracker_info* tgt, tr_tracker_info const* src, int n)
1167 {
1168     int maxTier = -1;
1169 
1170     for (int i = 0; i < n; ++i)
1171     {
1172         tgt[i].tier = src[i].tier;
1173         tgt[i].announce = tr_strdup(src[i].announce);
1174         maxTier = MAX(maxTier, src[i].tier);
1175     }
1176 
1177     return maxTier;
1178 }
1179 
freeTrackers(tr_tracker_info * trackers,int n)1180 static void freeTrackers(tr_tracker_info* trackers, int n)
1181 {
1182     for (int i = 0; i < n; ++i)
1183     {
1184         tr_free(trackers[i].announce);
1185     }
1186 
1187     tr_free(trackers);
1188 }
1189 
addTrackerUrls(tr_torrent * tor,tr_variant * urls)1190 static char const* addTrackerUrls(tr_torrent* tor, tr_variant* urls)
1191 {
1192     int i;
1193     int n;
1194     int tier;
1195     tr_variant* val;
1196     tr_tracker_info* trackers;
1197     bool changed = false;
1198     tr_info const* inf = tr_torrentInfo(tor);
1199     char const* errmsg = NULL;
1200 
1201     /* make a working copy of the existing announce list */
1202     n = inf->trackerCount;
1203     trackers = tr_new0(tr_tracker_info, n + tr_variantListSize(urls));
1204     tier = copyTrackers(trackers, inf->trackers, n);
1205 
1206     /* and add the new ones */
1207     i = 0;
1208 
1209     while ((val = tr_variantListChild(urls, i)) != NULL)
1210     {
1211         char const* announce = NULL;
1212 
1213         if (tr_variantGetStr(val, &announce, NULL) && tr_urlIsValidTracker(announce) &&
1214             !findAnnounceUrl(trackers, n, announce, NULL))
1215         {
1216             trackers[n].tier = ++tier; /* add a new tier */
1217             trackers[n].announce = tr_strdup(announce);
1218             ++n;
1219             changed = true;
1220         }
1221 
1222         ++i;
1223     }
1224 
1225     if (!changed)
1226     {
1227         errmsg = "invalid argument";
1228     }
1229     else if (!tr_torrentSetAnnounceList(tor, trackers, n))
1230     {
1231         errmsg = "error setting announce list";
1232     }
1233 
1234     freeTrackers(trackers, n);
1235     return errmsg;
1236 }
1237 
replaceTrackers(tr_torrent * tor,tr_variant * urls)1238 static char const* replaceTrackers(tr_torrent* tor, tr_variant* urls)
1239 {
1240     tr_tracker_info* trackers;
1241     bool changed = false;
1242     tr_info const* inf = tr_torrentInfo(tor);
1243     int const n = inf->trackerCount;
1244     char const* errmsg = NULL;
1245 
1246     /* make a working copy of the existing announce list */
1247     trackers = tr_new0(tr_tracker_info, n);
1248     copyTrackers(trackers, inf->trackers, n);
1249 
1250     /* make the substitutions... */
1251     for (size_t i = 0, url_count = tr_variantListSize(urls); i + 1 < url_count; i += 2)
1252     {
1253         size_t len;
1254         int64_t pos;
1255         char const* newval;
1256 
1257         if (tr_variantGetInt(tr_variantListChild(urls, i), &pos) &&
1258             tr_variantGetStr(tr_variantListChild(urls, i + 1), &newval, &len) &&
1259             tr_urlIsValidTracker(newval) && pos < n && pos >= 0)
1260         {
1261             tr_free(trackers[pos].announce);
1262             trackers[pos].announce = tr_strndup(newval, len);
1263             changed = true;
1264         }
1265     }
1266 
1267     if (!changed)
1268     {
1269         errmsg = "invalid argument";
1270     }
1271     else if (!tr_torrentSetAnnounceList(tor, trackers, n))
1272     {
1273         errmsg = "error setting announce list";
1274     }
1275 
1276     freeTrackers(trackers, n);
1277     return errmsg;
1278 }
1279 
removeTrackers(tr_torrent * tor,tr_variant * ids)1280 static char const* removeTrackers(tr_torrent* tor, tr_variant* ids)
1281 {
1282     int i;
1283     int n;
1284     int t = 0;
1285     int dup = -1;
1286     int* tids;
1287     tr_variant* val;
1288     tr_tracker_info* trackers;
1289     bool changed = false;
1290     tr_info const* inf = tr_torrentInfo(tor);
1291     char const* errmsg = NULL;
1292 
1293     /* make a working copy of the existing announce list */
1294     n = inf->trackerCount;
1295     tids = tr_new0(int, n);
1296     trackers = tr_new0(tr_tracker_info, n);
1297     copyTrackers(trackers, inf->trackers, n);
1298 
1299     /* remove the ones specified in the urls list */
1300     i = 0;
1301 
1302     while ((val = tr_variantListChild(ids, i)) != NULL)
1303     {
1304         int64_t pos;
1305 
1306         if (tr_variantGetInt(val, &pos) && 0 <= pos && pos < n)
1307         {
1308             tids[t++] = pos;
1309         }
1310 
1311         ++i;
1312     }
1313 
1314     /* sort trackerIds and remove from largest to smallest so there is no need to recalculate array indicies */
1315     qsort(tids, t, sizeof(int), compareInt);
1316 
1317     while (t-- != 0)
1318     {
1319         /* check for duplicates */
1320         if (tids[t] == dup)
1321         {
1322             continue;
1323         }
1324 
1325         tr_removeElementFromArray(trackers, tids[t], sizeof(tr_tracker_info), n);
1326         --n;
1327 
1328         dup = tids[t];
1329         changed = true;
1330     }
1331 
1332     if (!changed)
1333     {
1334         errmsg = "invalid argument";
1335     }
1336     else if (!tr_torrentSetAnnounceList(tor, trackers, n))
1337     {
1338         errmsg = "error setting announce list";
1339     }
1340 
1341     freeTrackers(trackers, n);
1342     tr_free(tids);
1343     return errmsg;
1344 }
1345 
torrentSet(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)1346 static char const* torrentSet(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
1347     struct tr_rpc_idle_data* idle_data UNUSED)
1348 {
1349     TR_ASSERT(idle_data == NULL);
1350 
1351     int torrentCount;
1352     tr_torrent** torrents = getTorrents(session, args_in, &torrentCount);
1353 
1354     char const* errmsg = NULL;
1355 
1356     for (int i = 0; i < torrentCount; ++i)
1357     {
1358         int64_t tmp;
1359         double d;
1360         tr_variant* tmp_variant;
1361         bool boolVal;
1362         tr_torrent* tor;
1363 
1364         tor = torrents[i];
1365 
1366         if (tr_variantDictFindInt(args_in, TR_KEY_bandwidthPriority, &tmp))
1367         {
1368             if (tr_isPriority(tmp))
1369             {
1370                 tr_torrentSetPriority(tor, tmp);
1371             }
1372         }
1373 
1374         if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_labels, &tmp_variant))
1375         {
1376             errmsg = setLabels(tor, tmp_variant);
1377         }
1378 
1379         if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_files_unwanted, &tmp_variant))
1380         {
1381             errmsg = setFileDLs(tor, false, tmp_variant);
1382         }
1383 
1384         if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_files_wanted, &tmp_variant))
1385         {
1386             errmsg = setFileDLs(tor, true, tmp_variant);
1387         }
1388 
1389         if (tr_variantDictFindInt(args_in, TR_KEY_peer_limit, &tmp))
1390         {
1391             tr_torrentSetPeerLimit(tor, tmp);
1392         }
1393 
1394         if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_priority_high, &tmp_variant))
1395         {
1396             errmsg = setFilePriorities(tor, TR_PRI_HIGH, tmp_variant);
1397         }
1398 
1399         if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_priority_low, &tmp_variant))
1400         {
1401             errmsg = setFilePriorities(tor, TR_PRI_LOW, tmp_variant);
1402         }
1403 
1404         if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_priority_normal, &tmp_variant))
1405         {
1406             errmsg = setFilePriorities(tor, TR_PRI_NORMAL, tmp_variant);
1407         }
1408 
1409         if (tr_variantDictFindInt(args_in, TR_KEY_downloadLimit, &tmp))
1410         {
1411             tr_torrentSetSpeedLimit_KBps(tor, TR_DOWN, tmp);
1412         }
1413 
1414         if (tr_variantDictFindBool(args_in, TR_KEY_downloadLimited, &boolVal))
1415         {
1416             tr_torrentUseSpeedLimit(tor, TR_DOWN, boolVal);
1417         }
1418 
1419         if (tr_variantDictFindBool(args_in, TR_KEY_honorsSessionLimits, &boolVal))
1420         {
1421             tr_torrentUseSessionLimits(tor, boolVal);
1422         }
1423 
1424         if (tr_variantDictFindInt(args_in, TR_KEY_uploadLimit, &tmp))
1425         {
1426             tr_torrentSetSpeedLimit_KBps(tor, TR_UP, tmp);
1427         }
1428 
1429         if (tr_variantDictFindBool(args_in, TR_KEY_uploadLimited, &boolVal))
1430         {
1431             tr_torrentUseSpeedLimit(tor, TR_UP, boolVal);
1432         }
1433 
1434         if (tr_variantDictFindInt(args_in, TR_KEY_seedIdleLimit, &tmp))
1435         {
1436             tr_torrentSetIdleLimit(tor, tmp);
1437         }
1438 
1439         if (tr_variantDictFindInt(args_in, TR_KEY_seedIdleMode, &tmp))
1440         {
1441             tr_torrentSetIdleMode(tor, tmp);
1442         }
1443 
1444         if (tr_variantDictFindReal(args_in, TR_KEY_seedRatioLimit, &d))
1445         {
1446             tr_torrentSetRatioLimit(tor, d);
1447         }
1448 
1449         if (tr_variantDictFindInt(args_in, TR_KEY_seedRatioMode, &tmp))
1450         {
1451             tr_torrentSetRatioMode(tor, tmp);
1452         }
1453 
1454         if (tr_variantDictFindInt(args_in, TR_KEY_queuePosition, &tmp))
1455         {
1456             tr_torrentSetQueuePosition(tor, tmp);
1457         }
1458 
1459         if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_trackerAdd, &tmp_variant))
1460         {
1461             errmsg = addTrackerUrls(tor, tmp_variant);
1462         }
1463 
1464         if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_trackerRemove, &tmp_variant))
1465         {
1466             errmsg = removeTrackers(tor, tmp_variant);
1467         }
1468 
1469         if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_trackerReplace, &tmp_variant))
1470         {
1471             errmsg = replaceTrackers(tor, tmp_variant);
1472         }
1473 
1474         notify(session, TR_RPC_TORRENT_CHANGED, tor);
1475     }
1476 
1477     tr_free(torrents);
1478     return errmsg;
1479 }
1480 
torrentSetLocation(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)1481 static char const* torrentSetLocation(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
1482     struct tr_rpc_idle_data* idle_data UNUSED)
1483 {
1484     TR_ASSERT(idle_data == NULL);
1485 
1486     char const* location = NULL;
1487 
1488     if (!tr_variantDictFindStr(args_in, TR_KEY_location, &location, NULL))
1489     {
1490         return "no location";
1491     }
1492 
1493     if (tr_sys_path_is_relative(location))
1494     {
1495         return "new location path is not absolute";
1496     }
1497 
1498     bool move;
1499     int torrentCount;
1500     tr_torrent** torrents = getTorrents(session, args_in, &torrentCount);
1501 
1502     if (!tr_variantDictFindBool(args_in, TR_KEY_move, &move))
1503     {
1504         move = false;
1505     }
1506 
1507     for (int i = 0; i < torrentCount; ++i)
1508     {
1509         tr_torrent* tor = torrents[i];
1510         tr_torrentSetLocation(tor, location, move, NULL, NULL);
1511         notify(session, TR_RPC_TORRENT_MOVED, tor);
1512     }
1513 
1514     tr_free(torrents);
1515 
1516     return NULL;
1517 }
1518 
1519 /***
1520 ****
1521 ***/
1522 
torrentRenamePathDone(tr_torrent * tor,char const * oldpath,char const * newname,int error,void * user_data)1523 static void torrentRenamePathDone(tr_torrent* tor, char const* oldpath, char const* newname, int error, void* user_data)
1524 {
1525     char const* result;
1526     struct tr_rpc_idle_data* data = user_data;
1527 
1528     tr_variantDictAddInt(data->args_out, TR_KEY_id, tr_torrentId(tor));
1529     tr_variantDictAddStr(data->args_out, TR_KEY_path, oldpath);
1530     tr_variantDictAddStr(data->args_out, TR_KEY_name, newname);
1531 
1532     if (error == 0)
1533     {
1534         result = NULL;
1535     }
1536     else
1537     {
1538         result = tr_strerror(error);
1539     }
1540 
1541     tr_idle_function_done(data, result);
1542 }
1543 
torrentRenamePath(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data)1544 static char const* torrentRenamePath(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
1545     struct tr_rpc_idle_data* idle_data)
1546 {
1547     int torrentCount;
1548     tr_torrent** torrents;
1549     char const* oldpath = NULL;
1550     char const* newname = NULL;
1551     char const* errmsg = NULL;
1552 
1553     tr_variantDictFindStr(args_in, TR_KEY_path, &oldpath, NULL);
1554     tr_variantDictFindStr(args_in, TR_KEY_name, &newname, NULL);
1555     torrents = getTorrents(session, args_in, &torrentCount);
1556 
1557     if (torrentCount == 1)
1558     {
1559         tr_torrentRenamePath(torrents[0], oldpath, newname, torrentRenamePathDone, idle_data);
1560     }
1561     else
1562     {
1563         errmsg = "torrent-rename-path requires 1 torrent";
1564     }
1565 
1566     /* cleanup */
1567     tr_free(torrents);
1568     return errmsg;
1569 }
1570 
1571 /***
1572 ****
1573 ***/
1574 
portTested(tr_session * session UNUSED,bool did_connect UNUSED,bool did_timeout UNUSED,long response_code,void const * response,size_t response_byte_count,void * user_data)1575 static void portTested(tr_session* session UNUSED, bool did_connect UNUSED, bool did_timeout UNUSED, long response_code,
1576     void const* response, size_t response_byte_count, void* user_data)
1577 {
1578     char result[1024];
1579     struct tr_rpc_idle_data* data = user_data;
1580 
1581     if (response_code != 200)
1582     {
1583         tr_snprintf(result, sizeof(result), "portTested: http error %ld: %s", response_code,
1584             tr_webGetResponseStr(response_code));
1585     }
1586     else /* success */
1587     {
1588         bool const isOpen = response_byte_count != 0 && *(char*)response == '1';
1589         tr_variantDictAddBool(data->args_out, TR_KEY_port_is_open, isOpen);
1590         tr_snprintf(result, sizeof(result), "success");
1591     }
1592 
1593     tr_idle_function_done(data, result);
1594 }
1595 
portTest(tr_session * session,tr_variant * args_in UNUSED,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data)1596 static char const* portTest(tr_session* session, tr_variant* args_in UNUSED, tr_variant* args_out UNUSED,
1597     struct tr_rpc_idle_data* idle_data)
1598 {
1599     int const port = tr_sessionGetPeerPort(session);
1600     char* url = tr_strdup_printf("https://portcheck.transmissionbt.com/%d", port);
1601     tr_webRun(session, url, portTested, idle_data);
1602     tr_free(url);
1603     return NULL;
1604 }
1605 
1606 /***
1607 ****
1608 ***/
1609 
gotNewBlocklist(tr_session * session,bool did_connect UNUSED,bool did_timeout UNUSED,long response_code,void const * response,size_t response_byte_count,void * user_data)1610 static void gotNewBlocklist(tr_session* session, bool did_connect UNUSED, bool did_timeout UNUSED, long response_code,
1611     void const* response, size_t response_byte_count, void* user_data)
1612 {
1613     char result[1024];
1614     struct tr_rpc_idle_data* data = user_data;
1615 
1616     *result = '\0';
1617 
1618     if (response_code != 200)
1619     {
1620         tr_snprintf(result, sizeof(result), "gotNewBlocklist: http error %ld: %s", response_code,
1621             tr_webGetResponseStr(response_code));
1622     }
1623     else /* successfully fetched the blocklist... */
1624     {
1625         tr_sys_file_t fd;
1626         int err;
1627         char* filename;
1628         z_stream stream;
1629         char const* configDir = tr_sessionGetConfigDir(session);
1630         size_t const buflen = 1024 * 128; /* 128 KiB buffer */
1631         uint8_t* buf = tr_valloc(buflen);
1632         tr_error* error = NULL;
1633 
1634         /* this is an odd Magic Number required by zlib to enable gz support.
1635            See zlib's inflateInit2() documentation for a full description */
1636         int const windowBits = 15 + 32;
1637 
1638         stream.zalloc = (alloc_func)Z_NULL;
1639         stream.zfree = (free_func)Z_NULL;
1640         stream.opaque = (voidpf)Z_NULL;
1641         stream.next_in = (void*)response;
1642         stream.avail_in = response_byte_count;
1643         inflateInit2(&stream, windowBits);
1644 
1645         filename = tr_buildPath(configDir, "blocklist.tmp.XXXXXX", NULL);
1646         fd = tr_sys_file_open_temp(filename, &error);
1647 
1648         if (fd == TR_BAD_SYS_FILE)
1649         {
1650             tr_snprintf(result, sizeof(result), _("Couldn't save file \"%1$s\": %2$s"), filename, error->message);
1651             tr_error_clear(&error);
1652         }
1653 
1654         for (;;)
1655         {
1656             stream.next_out = (void*)buf;
1657             stream.avail_out = buflen;
1658             err = inflate(&stream, Z_NO_FLUSH);
1659 
1660             if (stream.avail_out < buflen)
1661             {
1662                 if (!tr_sys_file_write(fd, buf, buflen - stream.avail_out, NULL, &error))
1663                 {
1664                     tr_snprintf(result, sizeof(result), _("Couldn't save file \"%1$s\": %2$s"), filename, error->message);
1665                     tr_error_clear(&error);
1666                     break;
1667                 }
1668             }
1669 
1670             if (err != Z_OK)
1671             {
1672                 if (err != Z_STREAM_END && err != Z_DATA_ERROR)
1673                 {
1674                     tr_snprintf(result, sizeof(result), _("Error uncompressing blocklist: %s (%d)"), zError(err), err);
1675                 }
1676 
1677                 break;
1678             }
1679         }
1680 
1681         inflateEnd(&stream);
1682 
1683         if (err == Z_DATA_ERROR) /* couldn't inflate it... it's probably already uncompressed */
1684         {
1685             if (!tr_sys_file_write(fd, response, response_byte_count, NULL, &error))
1686             {
1687                 tr_snprintf(result, sizeof(result), _("Couldn't save file \"%1$s\": %2$s"), filename, error->message);
1688                 tr_error_clear(&error);
1689             }
1690         }
1691 
1692         tr_sys_file_close(fd, NULL);
1693 
1694         if (!tr_str_is_empty(result))
1695         {
1696             tr_logAddError("%s", result);
1697         }
1698         else
1699         {
1700             /* feed it to the session and give the client a response */
1701             int const rule_count = tr_blocklistSetContent(session, filename);
1702             tr_variantDictAddInt(data->args_out, TR_KEY_blocklist_size, rule_count);
1703             tr_snprintf(result, sizeof(result), "success");
1704         }
1705 
1706         tr_sys_path_remove(filename, NULL);
1707         tr_free(filename);
1708         tr_free(buf);
1709     }
1710 
1711     tr_idle_function_done(data, result);
1712 }
1713 
blocklistUpdate(tr_session * session,tr_variant * args_in UNUSED,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data)1714 static char const* blocklistUpdate(tr_session* session, tr_variant* args_in UNUSED, tr_variant* args_out UNUSED,
1715     struct tr_rpc_idle_data* idle_data)
1716 {
1717     tr_webRun(session, session->blocklist_url, gotNewBlocklist, idle_data);
1718     return NULL;
1719 }
1720 
1721 /***
1722 ****
1723 ***/
1724 
addTorrentImpl(struct tr_rpc_idle_data * data,tr_ctor * ctor)1725 static void addTorrentImpl(struct tr_rpc_idle_data* data, tr_ctor* ctor)
1726 {
1727     int err;
1728     int duplicate_id;
1729     char const* result;
1730     tr_torrent* tor;
1731     tr_quark key;
1732 
1733     err = 0;
1734     duplicate_id = 0;
1735     tor = tr_torrentNew(ctor, &err, &duplicate_id);
1736     tr_ctorFree(ctor);
1737 
1738     if (err == 0)
1739     {
1740         key = TR_KEY_torrent_added;
1741         result = NULL;
1742     }
1743     else if (err == TR_PARSE_DUPLICATE)
1744     {
1745         tor = tr_torrentFindFromId(data->session, duplicate_id);
1746         key = TR_KEY_torrent_duplicate;
1747         result = "duplicate torrent";
1748     }
1749     else /* err == TR_PARSE_ERR */
1750     {
1751         key = 0;
1752         result = "invalid or corrupt torrent file";
1753     }
1754 
1755     if (tor != NULL && key != 0)
1756     {
1757         tr_quark const fields[] =
1758         {
1759             TR_KEY_id,
1760             TR_KEY_name,
1761             TR_KEY_hashString
1762         };
1763 
1764         addTorrentInfo(tor, TR_FORMAT_OBJECT, tr_variantDictAdd(data->args_out, key), fields, TR_N_ELEMENTS(fields));
1765 
1766         if (result == NULL)
1767         {
1768             notify(data->session, TR_RPC_TORRENT_ADDED, tor);
1769         }
1770 
1771         result = NULL;
1772     }
1773 
1774     tr_idle_function_done(data, result);
1775 }
1776 
1777 struct add_torrent_idle_data
1778 {
1779     struct tr_rpc_idle_data* data;
1780     tr_ctor* ctor;
1781 };
1782 
gotMetadataFromURL(tr_session * session UNUSED,bool did_connect UNUSED,bool did_timeout UNUSED,long response_code,void const * response,size_t response_byte_count,void * user_data)1783 static void gotMetadataFromURL(tr_session* session UNUSED, bool did_connect UNUSED, bool did_timeout UNUSED, long response_code,
1784     void const* response, size_t response_byte_count, void* user_data)
1785 {
1786     struct add_torrent_idle_data* data = user_data;
1787 
1788     dbgmsg("torrentAdd: HTTP response code was %ld (%s); response length was %zu bytes", response_code,
1789         tr_webGetResponseStr(response_code), response_byte_count);
1790 
1791     if (response_code == 200 || response_code == 221) /* http or ftp success.. */
1792     {
1793         tr_ctorSetMetainfo(data->ctor, response, response_byte_count);
1794         addTorrentImpl(data->data, data->ctor);
1795     }
1796     else
1797     {
1798         char result[1024];
1799         tr_snprintf(result, sizeof(result), "gotMetadataFromURL: http error %ld: %s", response_code,
1800             tr_webGetResponseStr(response_code));
1801         tr_idle_function_done(data->data, result);
1802     }
1803 
1804     tr_free(data);
1805 }
1806 
isCurlURL(char const * filename)1807 static bool isCurlURL(char const* filename)
1808 {
1809     if (filename == NULL)
1810     {
1811         return false;
1812     }
1813 
1814     return strncmp(filename, "ftp://", 6) == 0 || strncmp(filename, "http://", 7) == 0 || strncmp(filename, "https://", 8) == 0;
1815 }
1816 
fileListFromList(tr_variant * list,tr_file_index_t * setmeCount)1817 static tr_file_index_t* fileListFromList(tr_variant* list, tr_file_index_t* setmeCount)
1818 {
1819     size_t const childCount = tr_variantListSize(list);
1820     tr_file_index_t n = 0;
1821     tr_file_index_t* files = tr_new0(tr_file_index_t, childCount);
1822 
1823     for (size_t i = 0; i < childCount; ++i)
1824     {
1825         int64_t intVal;
1826 
1827         if (tr_variantGetInt(tr_variantListChild(list, i), &intVal))
1828         {
1829             files[n++] = (tr_file_index_t)intVal;
1830         }
1831     }
1832 
1833     *setmeCount = n;
1834     return files;
1835 }
1836 
torrentAdd(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data)1837 static char const* torrentAdd(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
1838     struct tr_rpc_idle_data* idle_data)
1839 {
1840     TR_ASSERT(idle_data != NULL);
1841 
1842     char const* filename = NULL;
1843     char const* metainfo_base64 = NULL;
1844 
1845     tr_variantDictFindStr(args_in, TR_KEY_filename, &filename, NULL);
1846     tr_variantDictFindStr(args_in, TR_KEY_metainfo, &metainfo_base64, NULL);
1847 
1848     if (filename == NULL && metainfo_base64 == NULL)
1849     {
1850         return "no filename or metainfo specified";
1851     }
1852 
1853     char const* download_dir = NULL;
1854 
1855     if (tr_variantDictFindStr(args_in, TR_KEY_download_dir, &download_dir, NULL))
1856     {
1857         if (tr_sys_path_is_relative(download_dir))
1858         {
1859             return "download directory path is not absolute";
1860         }
1861     }
1862 
1863     int64_t i;
1864     bool boolVal;
1865     tr_variant* l;
1866     char const* cookies = NULL;
1867     tr_ctor* ctor = tr_ctorNew(session);
1868 
1869     /* set the optional arguments */
1870 
1871     tr_variantDictFindStr(args_in, TR_KEY_cookies, &cookies, NULL);
1872 
1873     if (download_dir != NULL)
1874     {
1875         tr_ctorSetDownloadDir(ctor, TR_FORCE, download_dir);
1876     }
1877 
1878     if (tr_variantDictFindBool(args_in, TR_KEY_paused, &boolVal))
1879     {
1880         tr_ctorSetPaused(ctor, TR_FORCE, boolVal);
1881     }
1882 
1883     if (tr_variantDictFindInt(args_in, TR_KEY_peer_limit, &i))
1884     {
1885         tr_ctorSetPeerLimit(ctor, TR_FORCE, i);
1886     }
1887 
1888     if (tr_variantDictFindInt(args_in, TR_KEY_bandwidthPriority, &i))
1889     {
1890         tr_ctorSetBandwidthPriority(ctor, i);
1891     }
1892 
1893     if (tr_variantDictFindList(args_in, TR_KEY_files_unwanted, &l))
1894     {
1895         tr_file_index_t fileCount;
1896         tr_file_index_t* files = fileListFromList(l, &fileCount);
1897         tr_ctorSetFilesWanted(ctor, files, fileCount, false);
1898         tr_free(files);
1899     }
1900 
1901     if (tr_variantDictFindList(args_in, TR_KEY_files_wanted, &l))
1902     {
1903         tr_file_index_t fileCount;
1904         tr_file_index_t* files = fileListFromList(l, &fileCount);
1905         tr_ctorSetFilesWanted(ctor, files, fileCount, true);
1906         tr_free(files);
1907     }
1908 
1909     if (tr_variantDictFindList(args_in, TR_KEY_priority_low, &l))
1910     {
1911         tr_file_index_t fileCount;
1912         tr_file_index_t* files = fileListFromList(l, &fileCount);
1913         tr_ctorSetFilePriorities(ctor, files, fileCount, TR_PRI_LOW);
1914         tr_free(files);
1915     }
1916 
1917     if (tr_variantDictFindList(args_in, TR_KEY_priority_normal, &l))
1918     {
1919         tr_file_index_t fileCount;
1920         tr_file_index_t* files = fileListFromList(l, &fileCount);
1921         tr_ctorSetFilePriorities(ctor, files, fileCount, TR_PRI_NORMAL);
1922         tr_free(files);
1923     }
1924 
1925     if (tr_variantDictFindList(args_in, TR_KEY_priority_high, &l))
1926     {
1927         tr_file_index_t fileCount;
1928         tr_file_index_t* files = fileListFromList(l, &fileCount);
1929         tr_ctorSetFilePriorities(ctor, files, fileCount, TR_PRI_HIGH);
1930         tr_free(files);
1931     }
1932 
1933     dbgmsg("torrentAdd: filename is \"%s\"", filename ? filename : " (null)");
1934 
1935     if (isCurlURL(filename))
1936     {
1937         struct add_torrent_idle_data* d = tr_new0(struct add_torrent_idle_data, 1);
1938         d->data = idle_data;
1939         d->ctor = ctor;
1940         tr_webRunWithCookies(session, filename, cookies, gotMetadataFromURL, d);
1941     }
1942     else
1943     {
1944         char* fname = tr_strstrip(tr_strdup(filename));
1945 
1946         if (fname == NULL)
1947         {
1948             size_t len;
1949             char* metainfo = tr_base64_decode_str(metainfo_base64, &len);
1950             tr_ctorSetMetainfo(ctor, (uint8_t*)metainfo, len);
1951             tr_free(metainfo);
1952         }
1953         else if (strncmp(fname, "magnet:?", 8) == 0)
1954         {
1955             tr_ctorSetMetainfoFromMagnetLink(ctor, fname);
1956         }
1957         else
1958         {
1959             tr_ctorSetMetainfoFromFile(ctor, fname);
1960         }
1961 
1962         addTorrentImpl(idle_data, ctor);
1963 
1964         tr_free(fname);
1965     }
1966 
1967     return NULL;
1968 }
1969 
1970 /***
1971 ****
1972 ***/
1973 
sessionSet(tr_session * session,tr_variant * args_in,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)1974 static char const* sessionSet(tr_session* session, tr_variant* args_in, tr_variant* args_out UNUSED,
1975     struct tr_rpc_idle_data* idle_data UNUSED)
1976 {
1977     TR_ASSERT(idle_data == NULL);
1978 
1979     char const* download_dir = NULL;
1980     char const* incomplete_dir = NULL;
1981 
1982     if (tr_variantDictFindStr(args_in, TR_KEY_download_dir, &download_dir, NULL))
1983     {
1984         if (tr_sys_path_is_relative(download_dir))
1985         {
1986             return "download directory path is not absolute";
1987         }
1988     }
1989 
1990     if (tr_variantDictFindStr(args_in, TR_KEY_incomplete_dir, &incomplete_dir, NULL))
1991     {
1992         if (tr_sys_path_is_relative(incomplete_dir))
1993         {
1994             return "incomplete torrents directory path is not absolute";
1995         }
1996     }
1997 
1998     int64_t i;
1999     double d;
2000     bool boolVal;
2001     char const* str;
2002 
2003     if (tr_variantDictFindInt(args_in, TR_KEY_cache_size_mb, &i))
2004     {
2005         tr_sessionSetCacheLimit_MB(session, i);
2006     }
2007 
2008     if (tr_variantDictFindInt(args_in, TR_KEY_alt_speed_up, &i))
2009     {
2010         tr_sessionSetAltSpeed_KBps(session, TR_UP, i);
2011     }
2012 
2013     if (tr_variantDictFindInt(args_in, TR_KEY_alt_speed_down, &i))
2014     {
2015         tr_sessionSetAltSpeed_KBps(session, TR_DOWN, i);
2016     }
2017 
2018     if (tr_variantDictFindBool(args_in, TR_KEY_alt_speed_enabled, &boolVal))
2019     {
2020         tr_sessionUseAltSpeed(session, boolVal);
2021     }
2022 
2023     if (tr_variantDictFindInt(args_in, TR_KEY_alt_speed_time_begin, &i))
2024     {
2025         tr_sessionSetAltSpeedBegin(session, i);
2026     }
2027 
2028     if (tr_variantDictFindInt(args_in, TR_KEY_alt_speed_time_end, &i))
2029     {
2030         tr_sessionSetAltSpeedEnd(session, i);
2031     }
2032 
2033     if (tr_variantDictFindInt(args_in, TR_KEY_alt_speed_time_day, &i))
2034     {
2035         tr_sessionSetAltSpeedDay(session, i);
2036     }
2037 
2038     if (tr_variantDictFindBool(args_in, TR_KEY_alt_speed_time_enabled, &boolVal))
2039     {
2040         tr_sessionUseAltSpeedTime(session, boolVal);
2041     }
2042 
2043     if (tr_variantDictFindBool(args_in, TR_KEY_blocklist_enabled, &boolVal))
2044     {
2045         tr_blocklistSetEnabled(session, boolVal);
2046     }
2047 
2048     if (tr_variantDictFindStr(args_in, TR_KEY_blocklist_url, &str, NULL))
2049     {
2050         tr_blocklistSetURL(session, str);
2051     }
2052 
2053     if (download_dir != NULL)
2054     {
2055         tr_sessionSetDownloadDir(session, download_dir);
2056     }
2057 
2058     if (tr_variantDictFindInt(args_in, TR_KEY_queue_stalled_minutes, &i))
2059     {
2060         tr_sessionSetQueueStalledMinutes(session, i);
2061     }
2062 
2063     if (tr_variantDictFindBool(args_in, TR_KEY_queue_stalled_enabled, &boolVal))
2064     {
2065         tr_sessionSetQueueStalledEnabled(session, boolVal);
2066     }
2067 
2068     if (tr_variantDictFindInt(args_in, TR_KEY_download_queue_size, &i))
2069     {
2070         tr_sessionSetQueueSize(session, TR_DOWN, i);
2071     }
2072 
2073     if (tr_variantDictFindBool(args_in, TR_KEY_download_queue_enabled, &boolVal))
2074     {
2075         tr_sessionSetQueueEnabled(session, TR_DOWN, boolVal);
2076     }
2077 
2078     if (incomplete_dir != NULL)
2079     {
2080         tr_sessionSetIncompleteDir(session, incomplete_dir);
2081     }
2082 
2083     if (tr_variantDictFindBool(args_in, TR_KEY_incomplete_dir_enabled, &boolVal))
2084     {
2085         tr_sessionSetIncompleteDirEnabled(session, boolVal);
2086     }
2087 
2088     if (tr_variantDictFindInt(args_in, TR_KEY_peer_limit_global, &i))
2089     {
2090         tr_sessionSetPeerLimit(session, i);
2091     }
2092 
2093     if (tr_variantDictFindInt(args_in, TR_KEY_peer_limit_per_torrent, &i))
2094     {
2095         tr_sessionSetPeerLimitPerTorrent(session, i);
2096     }
2097 
2098     if (tr_variantDictFindBool(args_in, TR_KEY_pex_enabled, &boolVal))
2099     {
2100         tr_sessionSetPexEnabled(session, boolVal);
2101     }
2102 
2103     if (tr_variantDictFindBool(args_in, TR_KEY_dht_enabled, &boolVal))
2104     {
2105         tr_sessionSetDHTEnabled(session, boolVal);
2106     }
2107 
2108     if (tr_variantDictFindBool(args_in, TR_KEY_utp_enabled, &boolVal))
2109     {
2110         tr_sessionSetUTPEnabled(session, boolVal);
2111     }
2112 
2113     if (tr_variantDictFindBool(args_in, TR_KEY_lpd_enabled, &boolVal))
2114     {
2115         tr_sessionSetLPDEnabled(session, boolVal);
2116     }
2117 
2118     if (tr_variantDictFindBool(args_in, TR_KEY_peer_port_random_on_start, &boolVal))
2119     {
2120         tr_sessionSetPeerPortRandomOnStart(session, boolVal);
2121     }
2122 
2123     if (tr_variantDictFindInt(args_in, TR_KEY_peer_port, &i))
2124     {
2125         tr_sessionSetPeerPort(session, i);
2126     }
2127 
2128     if (tr_variantDictFindBool(args_in, TR_KEY_port_forwarding_enabled, &boolVal))
2129     {
2130         tr_sessionSetPortForwardingEnabled(session, boolVal);
2131     }
2132 
2133     if (tr_variantDictFindBool(args_in, TR_KEY_rename_partial_files, &boolVal))
2134     {
2135         tr_sessionSetIncompleteFileNamingEnabled(session, boolVal);
2136     }
2137 
2138     if (tr_variantDictFindReal(args_in, TR_KEY_seedRatioLimit, &d))
2139     {
2140         tr_sessionSetRatioLimit(session, d);
2141     }
2142 
2143     if (tr_variantDictFindBool(args_in, TR_KEY_seedRatioLimited, &boolVal))
2144     {
2145         tr_sessionSetRatioLimited(session, boolVal);
2146     }
2147 
2148     if (tr_variantDictFindInt(args_in, TR_KEY_idle_seeding_limit, &i))
2149     {
2150         tr_sessionSetIdleLimit(session, i);
2151     }
2152 
2153     if (tr_variantDictFindBool(args_in, TR_KEY_idle_seeding_limit_enabled, &boolVal))
2154     {
2155         tr_sessionSetIdleLimited(session, boolVal);
2156     }
2157 
2158     if (tr_variantDictFindBool(args_in, TR_KEY_start_added_torrents, &boolVal))
2159     {
2160         tr_sessionSetPaused(session, !boolVal);
2161     }
2162 
2163     if (tr_variantDictFindBool(args_in, TR_KEY_seed_queue_enabled, &boolVal))
2164     {
2165         tr_sessionSetQueueEnabled(session, TR_UP, boolVal);
2166     }
2167 
2168     if (tr_variantDictFindInt(args_in, TR_KEY_seed_queue_size, &i))
2169     {
2170         tr_sessionSetQueueSize(session, TR_UP, i);
2171     }
2172 
2173     if (tr_variantDictFindStr(args_in, TR_KEY_script_torrent_done_filename, &str, NULL))
2174     {
2175         tr_sessionSetTorrentDoneScript(session, str);
2176     }
2177 
2178     if (tr_variantDictFindBool(args_in, TR_KEY_script_torrent_done_enabled, &boolVal))
2179     {
2180         tr_sessionSetTorrentDoneScriptEnabled(session, boolVal);
2181     }
2182 
2183     if (tr_variantDictFindBool(args_in, TR_KEY_trash_original_torrent_files, &boolVal))
2184     {
2185         tr_sessionSetDeleteSource(session, boolVal);
2186     }
2187 
2188     if (tr_variantDictFindInt(args_in, TR_KEY_speed_limit_down, &i))
2189     {
2190         tr_sessionSetSpeedLimit_KBps(session, TR_DOWN, i);
2191     }
2192 
2193     if (tr_variantDictFindBool(args_in, TR_KEY_speed_limit_down_enabled, &boolVal))
2194     {
2195         tr_sessionLimitSpeed(session, TR_DOWN, boolVal);
2196     }
2197 
2198     if (tr_variantDictFindInt(args_in, TR_KEY_speed_limit_up, &i))
2199     {
2200         tr_sessionSetSpeedLimit_KBps(session, TR_UP, i);
2201     }
2202 
2203     if (tr_variantDictFindBool(args_in, TR_KEY_speed_limit_up_enabled, &boolVal))
2204     {
2205         tr_sessionLimitSpeed(session, TR_UP, boolVal);
2206     }
2207 
2208     if (tr_variantDictFindStr(args_in, TR_KEY_encryption, &str, NULL))
2209     {
2210         if (tr_strcmp0(str, "required") == 0)
2211         {
2212             tr_sessionSetEncryption(session, TR_ENCRYPTION_REQUIRED);
2213         }
2214         else if (tr_strcmp0(str, "tolerated") == 0)
2215         {
2216             tr_sessionSetEncryption(session, TR_CLEAR_PREFERRED);
2217         }
2218         else
2219         {
2220             tr_sessionSetEncryption(session, TR_ENCRYPTION_PREFERRED);
2221         }
2222     }
2223 
2224     notify(session, TR_RPC_SESSION_CHANGED, NULL);
2225 
2226     return NULL;
2227 }
2228 
sessionStats(tr_session * session,tr_variant * args_in UNUSED,tr_variant * args_out,struct tr_rpc_idle_data * idle_data UNUSED)2229 static char const* sessionStats(tr_session* session, tr_variant* args_in UNUSED, tr_variant* args_out,
2230     struct tr_rpc_idle_data* idle_data UNUSED)
2231 {
2232     TR_ASSERT(idle_data == NULL);
2233 
2234     int running = 0;
2235     int total = 0;
2236     tr_variant* d;
2237     tr_session_stats currentStats = TR_SESSION_STATS_INIT;
2238     tr_session_stats cumulativeStats = TR_SESSION_STATS_INIT;
2239     tr_torrent* tor = NULL;
2240 
2241     while ((tor = tr_torrentNext(session, tor)) != NULL)
2242     {
2243         ++total;
2244 
2245         if (tor->isRunning)
2246         {
2247             ++running;
2248         }
2249     }
2250 
2251     tr_sessionGetStats(session, &currentStats);
2252     tr_sessionGetCumulativeStats(session, &cumulativeStats);
2253 
2254     tr_variantDictAddInt(args_out, TR_KEY_activeTorrentCount, running);
2255     tr_variantDictAddReal(args_out, TR_KEY_downloadSpeed, tr_sessionGetPieceSpeed_Bps(session, TR_DOWN));
2256     tr_variantDictAddInt(args_out, TR_KEY_pausedTorrentCount, total - running);
2257     tr_variantDictAddInt(args_out, TR_KEY_torrentCount, total);
2258     tr_variantDictAddReal(args_out, TR_KEY_uploadSpeed, tr_sessionGetPieceSpeed_Bps(session, TR_UP));
2259 
2260     d = tr_variantDictAddDict(args_out, TR_KEY_cumulative_stats, 5);
2261     tr_variantDictAddInt(d, TR_KEY_downloadedBytes, cumulativeStats.downloadedBytes);
2262     tr_variantDictAddInt(d, TR_KEY_filesAdded, cumulativeStats.filesAdded);
2263     tr_variantDictAddInt(d, TR_KEY_secondsActive, cumulativeStats.secondsActive);
2264     tr_variantDictAddInt(d, TR_KEY_sessionCount, cumulativeStats.sessionCount);
2265     tr_variantDictAddInt(d, TR_KEY_uploadedBytes, cumulativeStats.uploadedBytes);
2266 
2267     d = tr_variantDictAddDict(args_out, TR_KEY_current_stats, 5);
2268     tr_variantDictAddInt(d, TR_KEY_downloadedBytes, currentStats.downloadedBytes);
2269     tr_variantDictAddInt(d, TR_KEY_filesAdded, currentStats.filesAdded);
2270     tr_variantDictAddInt(d, TR_KEY_secondsActive, currentStats.secondsActive);
2271     tr_variantDictAddInt(d, TR_KEY_sessionCount, currentStats.sessionCount);
2272     tr_variantDictAddInt(d, TR_KEY_uploadedBytes, currentStats.uploadedBytes);
2273 
2274     return NULL;
2275 }
2276 
addSessionField(tr_session * s,tr_variant * d,tr_quark key)2277 static void addSessionField(tr_session* s, tr_variant* d, tr_quark key)
2278 {
2279     switch (key)
2280     {
2281     case TR_KEY_alt_speed_up:
2282         tr_variantDictAddInt(d, key, tr_sessionGetAltSpeed_KBps(s, TR_UP));
2283         break;
2284 
2285     case TR_KEY_alt_speed_down:
2286         tr_variantDictAddInt(d, key, tr_sessionGetAltSpeed_KBps(s, TR_DOWN));
2287         break;
2288 
2289     case TR_KEY_alt_speed_enabled:
2290         tr_variantDictAddBool(d, key, tr_sessionUsesAltSpeed(s));
2291         break;
2292 
2293     case TR_KEY_alt_speed_time_begin:
2294         tr_variantDictAddInt(d, key, tr_sessionGetAltSpeedBegin(s));
2295         break;
2296 
2297     case TR_KEY_alt_speed_time_end:
2298         tr_variantDictAddInt(d, key, tr_sessionGetAltSpeedEnd(s));
2299         break;
2300 
2301     case TR_KEY_alt_speed_time_day:
2302         tr_variantDictAddInt(d, key, tr_sessionGetAltSpeedDay(s));
2303         break;
2304 
2305     case TR_KEY_alt_speed_time_enabled:
2306         tr_variantDictAddBool(d, key, tr_sessionUsesAltSpeedTime(s));
2307         break;
2308 
2309     case TR_KEY_blocklist_enabled:
2310         tr_variantDictAddBool(d, key, tr_blocklistIsEnabled(s));
2311         break;
2312 
2313     case TR_KEY_blocklist_url:
2314         tr_variantDictAddStr(d, key, tr_blocklistGetURL(s));
2315         break;
2316 
2317     case TR_KEY_cache_size_mb:
2318         tr_variantDictAddInt(d, key, tr_sessionGetCacheLimit_MB(s));
2319         break;
2320 
2321     case TR_KEY_blocklist_size:
2322         tr_variantDictAddInt(d, key, tr_blocklistGetRuleCount(s));
2323         break;
2324 
2325     case TR_KEY_config_dir:
2326         tr_variantDictAddStr(d, key, tr_sessionGetConfigDir(s));
2327         break;
2328 
2329     case TR_KEY_download_dir:
2330         tr_variantDictAddStr(d, key, tr_sessionGetDownloadDir(s));
2331         break;
2332 
2333     case TR_KEY_download_dir_free_space:
2334         tr_variantDictAddInt(d, key, tr_device_info_get_free_space(s->downloadDir));
2335         break;
2336 
2337     case TR_KEY_download_queue_enabled:
2338         tr_variantDictAddBool(d, key, tr_sessionGetQueueEnabled(s, TR_DOWN));
2339         break;
2340 
2341     case TR_KEY_download_queue_size:
2342         tr_variantDictAddInt(d, key, tr_sessionGetQueueSize(s, TR_DOWN));
2343         break;
2344 
2345     case TR_KEY_peer_limit_global:
2346         tr_variantDictAddInt(d, key, tr_sessionGetPeerLimit(s));
2347         break;
2348 
2349     case TR_KEY_peer_limit_per_torrent:
2350         tr_variantDictAddInt(d, key, tr_sessionGetPeerLimitPerTorrent(s));
2351         break;
2352 
2353     case TR_KEY_incomplete_dir:
2354         tr_variantDictAddStr(d, key, tr_sessionGetIncompleteDir(s));
2355         break;
2356 
2357     case TR_KEY_incomplete_dir_enabled:
2358         tr_variantDictAddBool(d, key, tr_sessionIsIncompleteDirEnabled(s));
2359         break;
2360 
2361     case TR_KEY_pex_enabled:
2362         tr_variantDictAddBool(d, key, tr_sessionIsPexEnabled(s));
2363         break;
2364 
2365     case TR_KEY_utp_enabled:
2366         tr_variantDictAddBool(d, key, tr_sessionIsUTPEnabled(s));
2367         break;
2368 
2369     case TR_KEY_dht_enabled:
2370         tr_variantDictAddBool(d, key, tr_sessionIsDHTEnabled(s));
2371         break;
2372 
2373     case TR_KEY_lpd_enabled:
2374         tr_variantDictAddBool(d, key, tr_sessionIsLPDEnabled(s));
2375         break;
2376 
2377     case TR_KEY_peer_port:
2378         tr_variantDictAddInt(d, key, tr_sessionGetPeerPort(s));
2379         break;
2380 
2381     case TR_KEY_peer_port_random_on_start:
2382         tr_variantDictAddBool(d, key, tr_sessionGetPeerPortRandomOnStart(s));
2383         break;
2384 
2385     case TR_KEY_port_forwarding_enabled:
2386         tr_variantDictAddBool(d, key, tr_sessionIsPortForwardingEnabled(s));
2387         break;
2388 
2389     case TR_KEY_rename_partial_files:
2390         tr_variantDictAddBool(d, key, tr_sessionIsIncompleteFileNamingEnabled(s));
2391         break;
2392 
2393     case TR_KEY_rpc_version:
2394         tr_variantDictAddInt(d, key, RPC_VERSION);
2395         break;
2396 
2397     case TR_KEY_rpc_version_minimum:
2398         tr_variantDictAddInt(d, key, RPC_VERSION_MIN);
2399         break;
2400 
2401     case TR_KEY_seedRatioLimit:
2402         tr_variantDictAddReal(d, key, tr_sessionGetRatioLimit(s));
2403         break;
2404 
2405     case TR_KEY_seedRatioLimited:
2406         tr_variantDictAddBool(d, key, tr_sessionIsRatioLimited(s));
2407         break;
2408 
2409     case TR_KEY_idle_seeding_limit:
2410         tr_variantDictAddInt(d, key, tr_sessionGetIdleLimit(s));
2411         break;
2412 
2413     case TR_KEY_idle_seeding_limit_enabled:
2414         tr_variantDictAddBool(d, key, tr_sessionIsIdleLimited(s));
2415         break;
2416 
2417     case TR_KEY_seed_queue_enabled:
2418         tr_variantDictAddBool(d, key, tr_sessionGetQueueEnabled(s, TR_UP));
2419         break;
2420 
2421     case TR_KEY_seed_queue_size:
2422         tr_variantDictAddInt(d, key, tr_sessionGetQueueSize(s, TR_UP));
2423         break;
2424 
2425     case TR_KEY_start_added_torrents:
2426         tr_variantDictAddBool(d, key, !tr_sessionGetPaused(s));
2427         break;
2428 
2429     case TR_KEY_trash_original_torrent_files:
2430         tr_variantDictAddBool(d, key, tr_sessionGetDeleteSource(s));
2431         break;
2432 
2433     case TR_KEY_speed_limit_up:
2434         tr_variantDictAddInt(d, key, tr_sessionGetSpeedLimit_KBps(s, TR_UP));
2435         break;
2436 
2437     case TR_KEY_speed_limit_up_enabled:
2438         tr_variantDictAddBool(d, key, tr_sessionIsSpeedLimited(s, TR_UP));
2439         break;
2440 
2441     case TR_KEY_speed_limit_down:
2442         tr_variantDictAddInt(d, key, tr_sessionGetSpeedLimit_KBps(s, TR_DOWN));
2443         break;
2444 
2445     case TR_KEY_speed_limit_down_enabled:
2446         tr_variantDictAddBool(d, key, tr_sessionIsSpeedLimited(s, TR_DOWN));
2447         break;
2448 
2449     case TR_KEY_script_torrent_done_filename:
2450         tr_variantDictAddStr(d, key, tr_sessionGetTorrentDoneScript(s));
2451         break;
2452 
2453     case TR_KEY_script_torrent_done_enabled:
2454         tr_variantDictAddBool(d, key, tr_sessionIsTorrentDoneScriptEnabled(s));
2455         break;
2456 
2457     case TR_KEY_queue_stalled_enabled:
2458         tr_variantDictAddBool(d, key, tr_sessionGetQueueStalledEnabled(s));
2459         break;
2460 
2461     case TR_KEY_queue_stalled_minutes:
2462         tr_variantDictAddInt(d, key, tr_sessionGetQueueStalledMinutes(s));
2463         break;
2464 
2465     case TR_KEY_units:
2466         tr_formatter_get_units(tr_variantDictAddDict(d, key, 0));
2467         break;
2468 
2469     case TR_KEY_version:
2470         tr_variantDictAddStr(d, key, LONG_VERSION_STRING);
2471         break;
2472 
2473     case TR_KEY_encryption:
2474         {
2475             char const* str;
2476 
2477             switch (tr_sessionGetEncryption(s))
2478             {
2479             case TR_CLEAR_PREFERRED:
2480                 str = "tolerated";
2481                 break;
2482 
2483             case TR_ENCRYPTION_REQUIRED:
2484                 str = "required";
2485                 break;
2486 
2487             default:
2488                 str = "preferred";
2489                 break;
2490             }
2491 
2492             tr_variantDictAddStr(d, key, str);
2493             break;
2494         }
2495 
2496     case TR_KEY_session_id:
2497         tr_variantDictAddStr(d, key, tr_session_id_get_current(s->session_id));
2498         break;
2499     }
2500 }
2501 
sessionGet(tr_session * s,tr_variant * args_in,tr_variant * args_out,struct tr_rpc_idle_data * idle_data UNUSED)2502 static char const* sessionGet(tr_session* s, tr_variant* args_in, tr_variant* args_out,
2503     struct tr_rpc_idle_data* idle_data UNUSED)
2504 {
2505     TR_ASSERT(idle_data == NULL);
2506 
2507     tr_variant* fields;
2508 
2509     if (tr_variantDictFindList(args_in, TR_KEY_fields, &fields))
2510     {
2511         size_t const field_count = tr_variantListSize(fields);
2512 
2513         for (size_t i = 0; i < field_count; ++i)
2514         {
2515             char const* field_name;
2516             size_t field_name_len;
2517             tr_quark field_id;
2518 
2519             if (!tr_variantGetStr(tr_variantListChild(fields, i), &field_name, &field_name_len))
2520             {
2521                 continue;
2522             }
2523 
2524             if (!tr_quark_lookup(field_name, field_name_len, &field_id))
2525             {
2526                 continue;
2527             }
2528 
2529             addSessionField(s, args_out, field_id);
2530         }
2531     }
2532     else
2533     {
2534         for (tr_quark field_id = TR_KEY_NONE + 1; field_id < TR_N_KEYS; ++field_id)
2535         {
2536             addSessionField(s, args_out, field_id);
2537         }
2538     }
2539 
2540     return NULL;
2541 }
2542 
freeSpace(tr_session * session,tr_variant * args_in,tr_variant * args_out,struct tr_rpc_idle_data * idle_data UNUSED)2543 static char const* freeSpace(tr_session* session, tr_variant* args_in, tr_variant* args_out,
2544     struct tr_rpc_idle_data* idle_data UNUSED)
2545 {
2546     int tmperr;
2547     char const* path = NULL;
2548     char const* err = NULL;
2549     int64_t free_space = -1;
2550 
2551     if (!tr_variantDictFindStr(args_in, TR_KEY_path, &path, NULL))
2552     {
2553         return "directory path argument is missing";
2554     }
2555 
2556     if (tr_sys_path_is_relative(path))
2557     {
2558         return "directory path is not absolute";
2559     }
2560 
2561     /* get the free space */
2562     tmperr = errno;
2563     errno = 0;
2564     free_space = tr_sessionGetDirFreeSpace(session, path);
2565 
2566     if (free_space < 0)
2567     {
2568         err = tr_strerror(errno);
2569     }
2570 
2571     errno = tmperr;
2572 
2573     /* response */
2574     if (path != NULL)
2575     {
2576         tr_variantDictAddStr(args_out, TR_KEY_path, path);
2577     }
2578 
2579     tr_variantDictAddInt(args_out, TR_KEY_size_bytes, free_space);
2580     return err;
2581 }
2582 
2583 /***
2584 ****
2585 ***/
2586 
sessionClose(tr_session * session,tr_variant * args_in UNUSED,tr_variant * args_out UNUSED,struct tr_rpc_idle_data * idle_data UNUSED)2587 static char const* sessionClose(tr_session* session, tr_variant* args_in UNUSED, tr_variant* args_out UNUSED,
2588     struct tr_rpc_idle_data* idle_data UNUSED)
2589 {
2590     notify(session, TR_RPC_SESSION_CLOSE, NULL);
2591     return NULL;
2592 }
2593 
2594 /***
2595 ****
2596 ***/
2597 
2598 typedef char const* (* handler)(tr_session*, tr_variant*, tr_variant*, struct tr_rpc_idle_data*);
2599 
2600 static struct method
2601 {
2602     char const* name;
2603     bool immediate;
2604     handler func;
2605 }
2606 methods[] =
2607 {
2608     { "port-test", false, portTest },
2609     { "blocklist-update", false, blocklistUpdate },
2610     { "free-space", true, freeSpace },
2611     { "session-close", true, sessionClose },
2612     { "session-get", true, sessionGet },
2613     { "session-set", true, sessionSet },
2614     { "session-stats", true, sessionStats },
2615     { "torrent-add", false, torrentAdd },
2616     { "torrent-get", true, torrentGet },
2617     { "torrent-remove", true, torrentRemove },
2618     { "torrent-rename-path", false, torrentRenamePath },
2619     { "torrent-set", true, torrentSet },
2620     { "torrent-set-location", true, torrentSetLocation },
2621     { "torrent-start", true, torrentStart },
2622     { "torrent-start-now", true, torrentStartNow },
2623     { "torrent-stop", true, torrentStop },
2624     { "torrent-verify", true, torrentVerify },
2625     { "torrent-reannounce", true, torrentReannounce },
2626     { "queue-move-top", true, queueMoveTop },
2627     { "queue-move-up", true, queueMoveUp },
2628     { "queue-move-down", true, queueMoveDown },
2629     { "queue-move-bottom", true, queueMoveBottom }
2630 };
2631 
noop_response_callback(tr_session * session UNUSED,tr_variant * response UNUSED,void * user_data UNUSED)2632 static void noop_response_callback(tr_session* session UNUSED, tr_variant* response UNUSED, void* user_data UNUSED)
2633 {
2634 }
2635 
tr_rpc_request_exec_json(tr_session * session,tr_variant const * request,tr_rpc_response_func callback,void * callback_user_data)2636 void tr_rpc_request_exec_json(tr_session* session, tr_variant const* request, tr_rpc_response_func callback,
2637     void* callback_user_data)
2638 {
2639     char const* str;
2640     tr_variant* const mutable_request = (tr_variant*)request;
2641     tr_variant* args_in = tr_variantDictFind(mutable_request, TR_KEY_arguments);
2642     char const* result = NULL;
2643     struct method* method = NULL;
2644 
2645     if (callback == NULL)
2646     {
2647         callback = noop_response_callback;
2648     }
2649 
2650     /* parse the request */
2651     if (!tr_variantDictFindStr(mutable_request, TR_KEY_method, &str, NULL))
2652     {
2653         result = "no method name";
2654     }
2655     else
2656     {
2657         for (size_t i = 0; method == NULL && i < TR_N_ELEMENTS(methods); ++i)
2658         {
2659             if (strcmp(str, methods[i].name) == 0)
2660             {
2661                 method = &methods[i];
2662             }
2663         }
2664 
2665         if (method == NULL)
2666         {
2667             result = "method name not recognized";
2668         }
2669     }
2670 
2671     /* if we couldn't figure out which method to use, return an error */
2672     if (result != NULL)
2673     {
2674         int64_t tag;
2675         tr_variant response;
2676 
2677         tr_variantInitDict(&response, 3);
2678         tr_variantDictAddDict(&response, TR_KEY_arguments, 0);
2679         tr_variantDictAddStr(&response, TR_KEY_result, result);
2680 
2681         if (tr_variantDictFindInt(mutable_request, TR_KEY_tag, &tag))
2682         {
2683             tr_variantDictAddInt(&response, TR_KEY_tag, tag);
2684         }
2685 
2686         (*callback)(session, &response, callback_user_data);
2687 
2688         tr_variantFree(&response);
2689     }
2690     else if (method->immediate)
2691     {
2692         int64_t tag;
2693         tr_variant response;
2694         tr_variant* args_out;
2695 
2696         tr_variantInitDict(&response, 3);
2697         args_out = tr_variantDictAddDict(&response, TR_KEY_arguments, 0);
2698         result = (*method->func)(session, args_in, args_out, NULL);
2699 
2700         if (result == NULL)
2701         {
2702             result = "success";
2703         }
2704 
2705         tr_variantDictAddStr(&response, TR_KEY_result, result);
2706 
2707         if (tr_variantDictFindInt(mutable_request, TR_KEY_tag, &tag))
2708         {
2709             tr_variantDictAddInt(&response, TR_KEY_tag, tag);
2710         }
2711 
2712         (*callback)(session, &response, callback_user_data);
2713 
2714         tr_variantFree(&response);
2715     }
2716     else
2717     {
2718         int64_t tag;
2719         struct tr_rpc_idle_data* data = tr_new0(struct tr_rpc_idle_data, 1);
2720         data->session = session;
2721         data->response = tr_new0(tr_variant, 1);
2722         tr_variantInitDict(data->response, 3);
2723 
2724         if (tr_variantDictFindInt(mutable_request, TR_KEY_tag, &tag))
2725         {
2726             tr_variantDictAddInt(data->response, TR_KEY_tag, tag);
2727         }
2728 
2729         data->args_out = tr_variantDictAddDict(data->response, TR_KEY_arguments, 0);
2730         data->callback = callback;
2731         data->callback_user_data = callback_user_data;
2732         result = (*method->func)(session, args_in, data->args_out, data);
2733 
2734         /* Async operation failed prematurely? Invoke callback or else client will not get a reply */
2735         if (result != NULL)
2736         {
2737             tr_idle_function_done(data, result);
2738         }
2739     }
2740 }
2741 
2742 /**
2743  * Munge the URI into a usable form.
2744  *
2745  * We have very loose typing on this to make the URIs as simple as possible:
2746  * - anything not a 'tag' or 'method' is automatically in 'arguments'
2747  * - values that are all-digits are numbers
2748  * - values that are all-digits or commas are number lists
2749  * - all other values are strings
2750  */
tr_rpc_parse_list_str(tr_variant * setme,char const * str,size_t len)2751 void tr_rpc_parse_list_str(tr_variant* setme, char const* str, size_t len)
2752 {
2753     int valueCount;
2754     int* values = tr_parseNumberRange(str, len, &valueCount);
2755 
2756     if (valueCount == 0)
2757     {
2758         tr_variantInitStr(setme, str, len);
2759     }
2760     else if (valueCount == 1)
2761     {
2762         tr_variantInitInt(setme, values[0]);
2763     }
2764     else
2765     {
2766         tr_variantInitList(setme, valueCount);
2767 
2768         for (int i = 0; i < valueCount; ++i)
2769         {
2770             tr_variantListAddInt(setme, values[i]);
2771         }
2772     }
2773 
2774     tr_free(values);
2775 }
2776 
tr_rpc_request_exec_uri(tr_session * session,void const * request_uri,size_t request_uri_len,tr_rpc_response_func callback,void * callback_user_data)2777 void tr_rpc_request_exec_uri(tr_session* session, void const* request_uri, size_t request_uri_len,
2778     tr_rpc_response_func callback, void* callback_user_data)
2779 {
2780     char const* pch;
2781     tr_variant top;
2782     tr_variant* args;
2783     char* request = tr_strndup(request_uri, request_uri_len);
2784 
2785     tr_variantInitDict(&top, 3);
2786     args = tr_variantDictAddDict(&top, TR_KEY_arguments, 0);
2787 
2788     pch = strchr(request, '?');
2789 
2790     if (pch == NULL)
2791     {
2792         pch = request;
2793     }
2794 
2795     while (pch != NULL)
2796     {
2797         char const* delim = strchr(pch, '=');
2798         char const* next = strchr(pch, '&');
2799 
2800         if (delim != NULL)
2801         {
2802             char* key = tr_strndup(pch, (size_t)(delim - pch));
2803             bool isArg = strcmp(key, "method") != 0 && strcmp(key, "tag") != 0;
2804             tr_variant* parent = isArg ? args : &top;
2805 
2806             tr_rpc_parse_list_str(tr_variantDictAdd(parent, tr_quark_new(key, (size_t)(delim - pch))), delim + 1,
2807                 next != NULL ? (size_t)(next - (delim + 1)) : strlen(delim + 1));
2808             tr_free(key);
2809         }
2810 
2811         pch = next != NULL ? next + 1 : NULL;
2812     }
2813 
2814     tr_rpc_request_exec_json(session, &top, callback, callback_user_data);
2815 
2816     /* cleanup */
2817     tr_variantFree(&top);
2818     tr_free(request);
2819 }
2820