source: trunk/libtransmission/announcer-http.c @ 13868

Last change on this file since 13868 was 13868, checked in by jordan, 9 years ago

make all the log functions/structs/enums use a single 'tr_log' namespace, such as tr_logGetQueue, tr_logAddInfo, tr_logIsLevelActive

  • Property svn:keywords set to Date Rev Author Id
File size: 15.5 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2 (b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: announcer-http.c 13868 2013-01-25 23:34:20Z jordan $
11 */
12
13#include <limits.h> /* USHRT_MAX */
14#include <stdio.h> /* fprintf () */
15#include <stdlib.h> /* getenv () */
16#include <string.h> /* strchr (), memcmp (), memcpy () */
17
18#include <event2/buffer.h>
19#include <event2/http.h> /* for HTTP_OK */
20
21#define __LIBTRANSMISSION_ANNOUNCER_MODULE___
22
23#include "transmission.h"
24#include "announcer-common.h"
25#include "log.h"
26#include "net.h" /* tr_globalIPv6 () */
27#include "peer-mgr.h" /* pex */
28#include "torrent.h"
29#include "trevent.h" /* tr_runInEventThread () */
30#include "utils.h"
31#include "variant.h"
32#include "web.h" /* tr_http_escape () */
33
34#define dbgmsg(name, ...) \
35  do \
36    { \
37      if (tr_logGetDeepEnabled ()) \
38        tr_logAddDeep (__FILE__, __LINE__, name, __VA_ARGS__); \
39    } \
40  while (0)
41
42/****
43*****
44*****  ANNOUNCE
45*****
46****/
47
48static const char*
49get_event_string (const tr_announce_request * req)
50{
51    if (req->partial_seed)
52        if (req->event != TR_ANNOUNCE_EVENT_STOPPED)
53            return "paused";
54
55    return tr_announce_event_get_string (req->event);
56}
57
58static char*
59announce_url_new (const tr_session * session, const tr_announce_request * req)
60{
61    const char * str;
62    const unsigned char * ipv6;
63    struct evbuffer * buf = evbuffer_new ();
64    char escaped_info_hash[SHA_DIGEST_LENGTH*3 + 1];
65
66    tr_http_escape_sha1 (escaped_info_hash, req->info_hash);
67
68    evbuffer_expand (buf, 1024);
69
70    evbuffer_add_printf (buf, "%s"
71                              "%c"
72                              "info_hash=%s"
73                              "&peer_id=%*.*s"
74                              "&port=%d"
75                              "&uploaded=%" PRIu64
76                              "&downloaded=%" PRIu64
77                              "&left=%" PRIu64
78                              "&numwant=%d"
79                              "&key=%x"
80                              "&compact=1"
81                              "&supportcrypto=1",
82                              req->url,
83                              strchr (req->url, '?') ? '&' : '?',
84                              escaped_info_hash,
85                              PEER_ID_LEN, PEER_ID_LEN, req->peer_id,
86                              req->port,
87                              req->up,
88                              req->down,
89                              req->leftUntilComplete,
90                              req->numwant,
91                              req->key);
92
93    if (session->encryptionMode == TR_ENCRYPTION_REQUIRED)
94        evbuffer_add_printf (buf, "&requirecrypto=1");
95
96    if (req->corrupt)
97        evbuffer_add_printf (buf, "&corrupt=%" PRIu64, req->corrupt);
98
99    str = get_event_string (req);
100    if (str && *str)
101        evbuffer_add_printf (buf, "&event=%s", str);
102
103    str = req->tracker_id_str;
104    if (str && *str)
105        evbuffer_add_printf (buf, "&trackerid=%s", str);
106
107    /* There are two incompatible techniques for announcing an IPv6 address.
108       BEP-7 suggests adding an "ipv6=" parameter to the announce URL,
109       while OpenTracker requires that peers announce twice, once over IPv4
110       and once over IPv6.
111
112       To be safe, we should do both: add the "ipv6=" parameter and
113       announce twice. At any rate, we're already computing our IPv6
114       address (for the LTEP handshake), so this comes for free. */
115
116    ipv6 = tr_globalIPv6 ();
117    if (ipv6) {
118        char ipv6_readable[INET6_ADDRSTRLEN];
119        evutil_inet_ntop (AF_INET6, ipv6, ipv6_readable, INET6_ADDRSTRLEN);
120        evbuffer_add_printf (buf, "&ipv6=");
121        tr_http_escape (buf, ipv6_readable, -1, true);
122    }
123
124    return evbuffer_free_to_str (buf);
125}
126
127static tr_pex*
128listToPex (tr_variant * peerList, size_t * setme_len)
129{
130    size_t i;
131    size_t n;
132    const size_t len = tr_variantListSize (peerList);
133    tr_pex * pex = tr_new0 (tr_pex, len);
134
135    for (i=n=0; i<len; ++i)
136    {
137        int64_t port;
138        const char * ip;
139        tr_address addr;
140        tr_variant * peer = tr_variantListChild (peerList, i);
141
142        if (peer == NULL)
143            continue;
144        if (!tr_variantDictFindStr (peer, TR_KEY_ip, &ip, NULL))
145            continue;
146        if (!tr_address_from_string (&addr, ip))
147            continue;
148        if (!tr_variantDictFindInt (peer, TR_KEY_port, &port))
149            continue;
150        if ((port < 0) || (port > USHRT_MAX))
151            continue;
152        if (!tr_address_is_valid_for_peers (&addr, port))
153            continue;
154
155        pex[n].addr = addr;
156        pex[n].port = htons ((uint16_t)port);
157        ++n;
158    }
159
160    *setme_len = n;
161    return pex;
162}
163
164struct announce_data
165{
166    tr_announce_response response;
167    tr_announce_response_func * response_func;
168    void * response_func_user_data;
169    char log_name[128];
170};
171
172static void
173on_announce_done_eventthread (void * vdata)
174{
175    struct announce_data * data = vdata;
176
177    if (data->response_func != NULL)
178        data->response_func (&data->response, data->response_func_user_data);
179
180    tr_free (data->response.pex6);
181    tr_free (data->response.pex);
182    tr_free (data->response.tracker_id_str);
183    tr_free (data->response.warning);
184    tr_free (data->response.errmsg);
185    tr_free (data);
186}
187
188
189static void
190on_announce_done (tr_session   * session,
191                  bool           did_connect,
192                  bool           did_timeout,
193                  long           response_code,
194                  const void   * msg,
195                  size_t         msglen,
196                  void         * vdata)
197{
198    tr_announce_response * response;
199    struct announce_data * data = vdata;
200
201    response = &data->response;
202    response->did_connect = did_connect;
203    response->did_timeout = did_timeout;
204    dbgmsg (data->log_name, "Got announce response");
205
206    if (response_code != HTTP_OK)
207    {
208        const char * fmt = _("Tracker gave HTTP response code %1$ld (%2$s)");
209        const char * response_str = tr_webGetResponseStr (response_code);
210        response->errmsg = tr_strdup_printf (fmt, response_code, response_str);
211    }
212    else
213    {
214        tr_variant benc;
215        const bool variant_loaded = !tr_variantFromBenc (&benc, msg, msglen);
216
217        if (getenv ("TR_CURL_VERBOSE") != NULL)
218        {
219            if (!variant_loaded)
220                fprintf (stderr, "%s", "Announce response was not in benc format\n");
221            else {
222                int i, len;
223                char * str = tr_variantToStr (&benc, TR_VARIANT_FMT_JSON, &len);
224                fprintf (stderr, "%s", "Announce response:\n< ");
225                for (i=0; i<len; ++i)
226                    fputc (str[i], stderr);
227                fputc ('\n', stderr);
228                tr_free (str);
229            }
230        }
231
232        if (variant_loaded && tr_variantIsDict (&benc))
233        {
234            int64_t i;
235            size_t len;
236            tr_variant * tmp;
237            const char * str;
238            const uint8_t * raw;
239
240            if (tr_variantDictFindStr (&benc, TR_KEY_failure_reason, &str, &len))
241                response->errmsg = tr_strndup (str, len);
242
243            if (tr_variantDictFindStr (&benc, TR_KEY_warning_message, &str, &len))
244                response->warning = tr_strndup (str, len);
245
246            if (tr_variantDictFindInt (&benc, TR_KEY_interval, &i))
247                response->interval = i;
248
249            if (tr_variantDictFindInt (&benc, TR_KEY_min_interval, &i))
250                response->min_interval = i;
251
252            if (tr_variantDictFindStr (&benc, TR_KEY_tracker_id, &str, &len))
253                response->tracker_id_str = tr_strndup (str, len);
254
255            if (tr_variantDictFindInt (&benc, TR_KEY_complete, &i))
256                response->seeders = i;
257
258            if (tr_variantDictFindInt (&benc, TR_KEY_incomplete, &i))
259                response->leechers = i;
260
261            if (tr_variantDictFindInt (&benc, TR_KEY_downloaded, &i))
262                response->downloads = i;
263
264            if (tr_variantDictFindRaw (&benc, TR_KEY_peers6, &raw, &len)) {
265                dbgmsg (data->log_name, "got a peers6 length of %zu", len);
266                response->pex6 = tr_peerMgrCompact6ToPex (raw, len,
267                                              NULL, 0, &response->pex6_count);
268            }
269
270            if (tr_variantDictFindRaw (&benc, TR_KEY_peers, &raw, &len)) {
271                dbgmsg (data->log_name, "got a compact peers length of %zu", len);
272                response->pex = tr_peerMgrCompactToPex (raw, len,
273                                               NULL, 0, &response->pex_count);
274            } else if (tr_variantDictFindList (&benc, TR_KEY_peers, &tmp)) {
275                response->pex = listToPex (tmp, &response->pex_count);
276                dbgmsg (data->log_name, "got a peers list with %zu entries",
277                        response->pex_count);
278            }
279        }
280
281        if (variant_loaded)
282            tr_variantFree (&benc);
283    }
284
285    tr_runInEventThread (session, on_announce_done_eventthread, data);
286}
287
288void
289tr_tracker_http_announce (tr_session                 * session,
290                          const tr_announce_request  * request,
291                          tr_announce_response_func    response_func,
292                          void                       * response_func_user_data)
293{
294    struct announce_data * d;
295    char * url = announce_url_new (session, request);
296
297    d = tr_new0 (struct announce_data, 1);
298    d->response.seeders = -1;
299    d->response.leechers = -1;
300    d->response.downloads = -1;
301    d->response_func = response_func;
302    d->response_func_user_data = response_func_user_data;
303    memcpy (d->response.info_hash, request->info_hash, SHA_DIGEST_LENGTH);
304    tr_strlcpy (d->log_name, request->log_name, sizeof (d->log_name));
305
306    dbgmsg (request->log_name, "Sending announce to libcurl: \"%s\"", url);
307    tr_webRun (session, url, NULL, NULL, on_announce_done, d);
308
309    tr_free (url);
310}
311
312/****
313*****
314*****  SCRAPE
315*****
316****/
317
318struct scrape_data
319{
320    tr_scrape_response response;
321    tr_scrape_response_func * response_func;
322    void * response_func_user_data;
323    char log_name[128];
324};
325
326static void
327on_scrape_done_eventthread (void * vdata)
328{
329    struct scrape_data * data = vdata;
330
331    if (data->response_func != NULL)
332        data->response_func (&data->response, data->response_func_user_data);
333
334    tr_free (data->response.errmsg);
335    tr_free (data->response.url);
336    tr_free (data);
337}
338
339static void
340on_scrape_done (tr_session   * session,
341                bool           did_connect,
342                bool           did_timeout,
343                long           response_code,
344                const void   * msg,
345                size_t         msglen,
346                void         * vdata)
347{
348    tr_scrape_response * response;
349    struct scrape_data * data = vdata;
350
351    response = &data->response;
352    response->did_connect = did_connect;
353    response->did_timeout = did_timeout;
354    dbgmsg (data->log_name, "Got scrape response for \"%s\"", response->url);
355
356    if (response_code != HTTP_OK)
357    {
358        const char * fmt = _("Tracker gave HTTP response code %1$ld (%2$s)");
359        const char * response_str = tr_webGetResponseStr (response_code);
360        response->errmsg = tr_strdup_printf (fmt, response_code, response_str);
361    }
362    else
363    {
364        tr_variant top;
365        int64_t intVal;
366        tr_variant * files;
367        tr_variant * flags;
368        size_t len;
369        const char * str;
370        const bool variant_loaded = !tr_variantFromBenc (&top, msg, msglen);
371
372        if (getenv ("TR_CURL_VERBOSE") != NULL)
373        {
374            if (!variant_loaded)
375                fprintf (stderr, "%s", "Scrape response was not in benc format\n");
376            else {
377                int i, len;
378                char * str = tr_variantToStr (&top, TR_VARIANT_FMT_JSON, &len);
379                fprintf (stderr, "%s", "Scrape response:\n< ");
380                for (i=0; i<len; ++i)
381                    fputc (str[i], stderr);
382                fputc ('\n', stderr);
383                tr_free (str);
384            }
385        }
386
387        if (variant_loaded)
388        {
389            if (tr_variantDictFindStr (&top, TR_KEY_failure_reason, &str, &len))
390                response->errmsg = tr_strndup (str, len);
391
392            if (tr_variantDictFindDict (&top, TR_KEY_flags, &flags))
393                if (tr_variantDictFindInt (flags, TR_KEY_min_request_interval, &intVal))
394                    response->min_request_interval = intVal;
395
396            if (tr_variantDictFindDict (&top, TR_KEY_files, &files))
397            {
398                int i = 0;
399
400                for (;;)
401                {
402                    int j;
403                    tr_quark key;
404                    tr_variant * val;
405
406                    /* get the next "file" */
407                    if (!tr_variantDictChild (files, i++, &key, &val))
408                        break;
409
410                    /* populate the corresponding row in our response array */
411                    for (j=0; j<response->row_count; ++j)
412                    {
413                        struct tr_scrape_response_row * row = &response->rows[j];
414                        if (!memcmp (tr_quark_get_string(key,NULL), row->info_hash, SHA_DIGEST_LENGTH))
415                        {
416                            if (tr_variantDictFindInt (val, TR_KEY_complete, &intVal))
417                                row->seeders = intVal;
418                            if (tr_variantDictFindInt (val, TR_KEY_incomplete, &intVal))
419                                row->leechers = intVal;
420                            if (tr_variantDictFindInt (val, TR_KEY_downloaded, &intVal))
421                                row->downloads = intVal;
422                            if (tr_variantDictFindInt (val, TR_KEY_downloaders, &intVal))
423                                row->downloaders = intVal;
424                            break;
425                        }
426                    }
427                }
428            }
429
430            tr_variantFree (&top);
431        }
432    }
433
434    tr_runInEventThread (session, on_scrape_done_eventthread, data);
435}
436
437static char *
438scrape_url_new (const tr_scrape_request * req)
439{
440    int i;
441    char delimiter;
442    struct evbuffer * buf = evbuffer_new ();
443
444    evbuffer_add_printf (buf, "%s", req->url);
445    delimiter = strchr (req->url, '?') ? '&' : '?';
446    for (i=0; i<req->info_hash_count; ++i)
447    {
448        char str[SHA_DIGEST_LENGTH*3 + 1];
449        tr_http_escape_sha1 (str, req->info_hash[i]);
450        evbuffer_add_printf (buf, "%cinfo_hash=%s", delimiter, str);
451        delimiter = '&';
452    }
453
454    return evbuffer_free_to_str (buf);
455}
456
457void
458tr_tracker_http_scrape (tr_session               * session,
459                        const tr_scrape_request  * request,
460                        tr_scrape_response_func    response_func,
461                        void                     * response_func_user_data)
462{
463    int i;
464    struct scrape_data * d;
465    char * url = scrape_url_new (request);
466
467    d = tr_new0 (struct scrape_data, 1);
468    d->response.url = tr_strdup (request->url);
469    d->response_func = response_func;
470    d->response_func_user_data = response_func_user_data;
471    d->response.row_count = request->info_hash_count;
472    for (i=0; i<d->response.row_count; ++i)
473    {
474        memcpy (d->response.rows[i].info_hash, request->info_hash[i], SHA_DIGEST_LENGTH);
475        d->response.rows[i].seeders = -1;
476        d->response.rows[i].leechers = -1;
477        d->response.rows[i].downloads = -1;
478    }
479    tr_strlcpy (d->log_name, request->log_name, sizeof (d->log_name));
480
481    dbgmsg (request->log_name, "Sending scrape to libcurl: \"%s\"", url);
482    tr_webRun (session, url, NULL, NULL, on_scrape_done, d);
483
484    tr_free (url);
485}
Note: See TracBrowser for help on using the repository browser.