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, ¤tStats);
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 : ⊤
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