1 /*
2  * This file Copyright (C) 2012-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 <stdio.h> /* fprintf() */
10 #include <string.h> /* strcmp(), strchr(), memcmp() */
11 #include <stdlib.h> /* qsort() */
12 #include <time.h>
13 
14 #define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */
15 #include <curl/curl.h>
16 
17 #include <event2/buffer.h>
18 
19 #include <libtransmission/transmission.h>
20 #include <libtransmission/tr-getopt.h>
21 #include <libtransmission/utils.h>
22 #include <libtransmission/web.h> /* tr_webGetResponseStr() */
23 #include <libtransmission/variant.h>
24 #include <libtransmission/version.h>
25 
26 #include "units.h"
27 
28 #define MY_NAME "transmission-show"
29 #define TIMEOUT_SECS 30
30 
31 static tr_option options[] =
32 {
33     { 'm', "magnet", "Give a magnet link for the specified torrent", "m", false, NULL },
34     { 's', "scrape", "Ask the torrent's trackers how many peers are in the torrent's swarm", "s", false, NULL },
35     { 'u', "unsorted", "Do not sort files by name", "u", false, NULL },
36     { 'V', "version", "Show version number and exit", "V", false, NULL },
37     { 0, NULL, NULL, NULL, false, NULL }
38 };
39 
getUsage(void)40 static char const* getUsage(void)
41 {
42     return "Usage: " MY_NAME " [options] <.torrent file>";
43 }
44 
45 static bool magnetFlag = false;
46 static bool scrapeFlag = false;
47 static bool unsorted = false;
48 static bool showVersion = false;
49 char const* filename = NULL;
50 
parseCommandLine(int argc,char const * const * argv)51 static int parseCommandLine(int argc, char const* const* argv)
52 {
53     int c;
54     char const* optarg;
55 
56     while ((c = tr_getopt(getUsage(), argc, argv, options, &optarg)) != TR_OPT_DONE)
57     {
58         switch (c)
59         {
60         case 'm':
61             magnetFlag = true;
62             break;
63 
64         case 's':
65             scrapeFlag = true;
66             break;
67 
68         case 'u':
69             unsorted = true;
70             break;
71 
72         case 'V':
73             showVersion = true;
74             break;
75 
76         case TR_OPT_UNK:
77             filename = optarg;
78             break;
79 
80         default:
81             return 1;
82         }
83     }
84 
85     return 0;
86 }
87 
doShowMagnet(tr_info const * inf)88 static void doShowMagnet(tr_info const* inf)
89 {
90     char* str = tr_torrentInfoGetMagnetLink(inf);
91     printf("%s", str);
92     tr_free(str);
93 }
94 
compare_files_by_name(void const * va,void const * vb)95 static int compare_files_by_name(void const* va, void const* vb)
96 {
97     tr_file const* a = *(tr_file const* const*)va;
98     tr_file const* b = *(tr_file const* const*)vb;
99     return strcmp(a->name, b->name);
100 }
101 
unix_timestamp_to_str(time_t timestamp)102 static char const* unix_timestamp_to_str(time_t timestamp)
103 {
104     if (timestamp == 0)
105     {
106         return "Unknown";
107     }
108 
109     struct tm const* const local_time = localtime(&timestamp);
110 
111     if (local_time == NULL)
112     {
113         return "Invalid";
114     }
115 
116     static char buffer[32];
117     tr_strlcpy(buffer, asctime(local_time), TR_N_ELEMENTS(buffer));
118 
119     char* const newline_pos = strchr(buffer, '\n');
120 
121     if (newline_pos != NULL)
122     {
123         *newline_pos = '\0';
124     }
125 
126     return buffer;
127 }
128 
showInfo(tr_info const * inf)129 static void showInfo(tr_info const* inf)
130 {
131     char buf[128];
132     tr_file** files;
133     int prevTier = -1;
134 
135     /**
136     ***  General Info
137     **/
138 
139     printf("GENERAL\n\n");
140     printf("  Name: %s\n", inf->name);
141     printf("  Hash: %s\n", inf->hashString);
142     printf("  Created by: %s\n", inf->creator ? inf->creator : "Unknown");
143     printf("  Created on: %s\n", unix_timestamp_to_str(inf->dateCreated));
144 
145     if (!tr_str_is_empty(inf->comment))
146     {
147         printf("  Comment: %s\n", inf->comment);
148     }
149 
150     printf("  Piece Count: %d\n", inf->pieceCount);
151     printf("  Piece Size: %s\n", tr_formatter_mem_B(buf, inf->pieceSize, sizeof(buf)));
152     printf("  Total Size: %s\n", tr_formatter_size_B(buf, inf->totalSize, sizeof(buf)));
153     printf("  Privacy: %s\n", inf->isPrivate ? "Private torrent" : "Public torrent");
154 
155     /**
156     ***  Trackers
157     **/
158 
159     printf("\nTRACKERS\n");
160 
161     for (unsigned int i = 0; i < inf->trackerCount; ++i)
162     {
163         if (prevTier != inf->trackers[i].tier)
164         {
165             prevTier = inf->trackers[i].tier;
166             printf("\n  Tier #%d\n", prevTier + 1);
167         }
168 
169         printf("  %s\n", inf->trackers[i].announce);
170     }
171 
172     /**
173     ***
174     **/
175 
176     if (inf->webseedCount > 0)
177     {
178         printf("\nWEBSEEDS\n\n");
179 
180         for (unsigned int i = 0; i < inf->webseedCount; ++i)
181         {
182             printf("  %s\n", inf->webseeds[i]);
183         }
184     }
185 
186     /**
187     ***  Files
188     **/
189 
190     printf("\nFILES\n\n");
191     files = tr_new(tr_file*, inf->fileCount);
192 
193     for (unsigned int i = 0; i < inf->fileCount; ++i)
194     {
195         files[i] = &inf->files[i];
196     }
197 
198     if (!unsorted)
199     {
200         qsort(files, inf->fileCount, sizeof(tr_file*), compare_files_by_name);
201     }
202 
203     for (unsigned int i = 0; i < inf->fileCount; ++i)
204     {
205         printf("  %s (%s)\n", files[i]->name, tr_formatter_size_B(buf, files[i]->length, sizeof(buf)));
206     }
207 
208     tr_free(files);
209 }
210 
writeFunc(void * ptr,size_t size,size_t nmemb,void * buf)211 static size_t writeFunc(void* ptr, size_t size, size_t nmemb, void* buf)
212 {
213     size_t const byteCount = size * nmemb;
214     evbuffer_add(buf, ptr, byteCount);
215     return byteCount;
216 }
217 
tr_curl_easy_init(struct evbuffer * writebuf)218 static CURL* tr_curl_easy_init(struct evbuffer* writebuf)
219 {
220     CURL* curl = curl_easy_init();
221     curl_easy_setopt(curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING);
222     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc);
223     curl_easy_setopt(curl, CURLOPT_WRITEDATA, writebuf);
224     curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
225     curl_easy_setopt(curl, CURLOPT_VERBOSE, tr_env_key_exists("TR_CURL_VERBOSE"));
226     curl_easy_setopt(curl, CURLOPT_ENCODING, "");
227     return curl;
228 }
229 
doScrape(tr_info const * inf)230 static void doScrape(tr_info const* inf)
231 {
232     for (unsigned int i = 0; i < inf->trackerCount; ++i)
233     {
234         CURL* curl;
235         CURLcode res;
236         struct evbuffer* buf;
237         char const* scrape = inf->trackers[i].scrape;
238         char* url;
239         char escaped[SHA_DIGEST_LENGTH * 3 + 1];
240 
241         if (scrape == NULL)
242         {
243             continue;
244         }
245 
246         tr_http_escape_sha1(escaped, inf->hash);
247 
248         url = tr_strdup_printf("%s%cinfo_hash=%s", scrape, strchr(scrape, '?') != NULL ? '&' : '?', escaped);
249 
250         printf("%s ... ", url);
251         fflush(stdout);
252 
253         buf = evbuffer_new();
254         curl = tr_curl_easy_init(buf);
255         curl_easy_setopt(curl, CURLOPT_URL, url);
256         curl_easy_setopt(curl, CURLOPT_TIMEOUT, TIMEOUT_SECS);
257 
258         if ((res = curl_easy_perform(curl)) != CURLE_OK)
259         {
260             printf("error: %s\n", curl_easy_strerror(res));
261         }
262         else
263         {
264             long response;
265             curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
266 
267             if (response != 200)
268             {
269                 printf("error: unexpected response %ld \"%s\"\n", response, tr_webGetResponseStr(response));
270             }
271             else /* HTTP OK */
272             {
273                 tr_variant top;
274                 tr_variant* files;
275                 bool matched = false;
276                 char const* begin = (char const*)evbuffer_pullup(buf, -1);
277 
278                 if (tr_variantFromBenc(&top, begin, evbuffer_get_length(buf)) == 0)
279                 {
280                     if (tr_variantDictFindDict(&top, TR_KEY_files, &files))
281                     {
282                         int i = 0;
283                         tr_quark key;
284                         tr_variant* val;
285 
286                         while (tr_variantDictChild(files, i, &key, &val))
287                         {
288                             if (memcmp(inf->hash, tr_quark_get_string(key, NULL), SHA_DIGEST_LENGTH) == 0)
289                             {
290                                 int64_t seeders;
291                                 if (!tr_variantDictFindInt(val, TR_KEY_complete, &seeders))
292                                 {
293                                     seeders = -1;
294                                 }
295 
296                                 int64_t leechers;
297                                 if (!tr_variantDictFindInt(val, TR_KEY_incomplete, &leechers))
298                                 {
299                                     leechers = -1;
300                                 }
301 
302                                 printf("%d seeders, %d leechers\n", (int)seeders, (int)leechers);
303                                 matched = true;
304                             }
305 
306                             ++i;
307                         }
308                     }
309 
310                     tr_variantFree(&top);
311                 }
312 
313                 if (!matched)
314                 {
315                     printf("no match\n");
316                 }
317             }
318         }
319 
320         curl_easy_cleanup(curl);
321         evbuffer_free(buf);
322         tr_free(url);
323     }
324 }
325 
tr_main(int argc,char * argv[])326 int tr_main(int argc, char* argv[])
327 {
328     int err;
329     tr_info inf;
330     tr_ctor* ctor;
331 
332     tr_logSetLevel(TR_LOG_ERROR);
333     tr_formatter_mem_init(MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR);
334     tr_formatter_size_init(DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR);
335     tr_formatter_speed_init(SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR);
336 
337     if (parseCommandLine(argc, (char const* const*)argv) != 0)
338     {
339         return EXIT_FAILURE;
340     }
341 
342     if (showVersion)
343     {
344         fprintf(stderr, MY_NAME " " LONG_VERSION_STRING "\n");
345         return EXIT_SUCCESS;
346     }
347 
348     /* make sure the user specified a filename */
349     if (filename == NULL)
350     {
351         fprintf(stderr, "ERROR: No .torrent file specified.\n");
352         tr_getopt_usage(MY_NAME, getUsage(), options);
353         fprintf(stderr, "\n");
354         return EXIT_FAILURE;
355     }
356 
357     /* try to parse the .torrent file */
358     ctor = tr_ctorNew(NULL);
359     tr_ctorSetMetainfoFromFile(ctor, filename);
360     err = tr_torrentParse(ctor, &inf);
361     tr_ctorFree(ctor);
362 
363     if (err != TR_PARSE_OK)
364     {
365         fprintf(stderr, "Error parsing .torrent file \"%s\"\n", filename);
366         return EXIT_FAILURE;
367     }
368 
369     if (magnetFlag)
370     {
371         doShowMagnet(&inf);
372     }
373     else
374     {
375         printf("Name: %s\n", inf.name);
376         printf("File: %s\n", filename);
377         printf("\n");
378         fflush(stdout);
379 
380         if (scrapeFlag)
381         {
382             doScrape(&inf);
383         }
384         else
385         {
386             showInfo(&inf);
387         }
388     }
389 
390     /* cleanup */
391     putc('\n', stdout);
392     tr_metainfoFree(&inf);
393     return EXIT_SUCCESS;
394 }
395