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

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

Follow more common whitespace style conventions in the C code (libtransmission, daemon, utils, cli, gtk).

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