source: trunk/libtransmission/metainfo.c @ 12127

Last change on this file since 12127 was 12127, checked in by jordan, 11 years ago

(trunk libT) #117 "UDP tracker protocol support (BEP #15)" -- refactor announcer.c so that alternate tracker protocols can be supported.

This commit adds a set of package-visible structs and functions to allow delegating announces and scrapes to different protocol handlers. (Examples: struct tr_announce_request, struct tr_announce_response, struct tr_scrape_request, struct tr_scrape_response.) HTTP is the only protocol handler currently implemented; however, this provides a clean API for other protocol handlers, and having this in trunk will help shake out any bugs in this refactoring.

In addition, logging via the TR_DEBUG_FD environment variable is vastly improved in the announcer module now.

  • Property svn:keywords set to Date Rev Author Id
File size: 17.0 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: metainfo.c 12127 2011-03-11 04:19:01Z jordan $
11 */
12
13#include <assert.h>
14#include <errno.h>
15#include <stdio.h>
16#include <string.h>
17
18#include <sys/types.h>
19#include <sys/stat.h>
20#include <unistd.h> /* unlink, stat */
21
22#include <event2/buffer.h>
23
24#include "transmission.h"
25#include "session.h"
26#include "bencode.h"
27#include "crypto.h" /* tr_sha1 */
28#include "metainfo.h"
29#include "platform.h"
30#include "utils.h"
31
32/***
33****
34***/
35
36char*
37tr_metainfoGetBasename( const tr_info * inf )
38{
39    char *ret, *pch, *name;
40
41    name = tr_strdup( inf->name );
42    for( pch=name; pch && *pch; ++pch )
43        if( *pch == '/' )
44            *pch = '_';
45
46    ret = tr_strdup_printf( "%s.%16.16s", name, inf->hashString );
47
48    tr_free( name );
49    return ret;
50}
51
52static char*
53getTorrentFilename( const tr_session * session,
54                    const tr_info *   inf )
55{
56    char * base = tr_metainfoGetBasename( inf );
57    char * filename = tr_strdup_printf( "%s" TR_PATH_DELIMITER_STR "%s.torrent",
58                                        tr_getTorrentDir( session ), base );
59    tr_free( base );
60    return filename;
61}
62
63static char*
64getOldTorrentFilename( const tr_session * session, const tr_info * inf )
65{
66    int i;
67    char * path;
68    struct stat sb;
69    const int tagCount = 5;
70    const char * tags[] = { "beos", "cli", "daemon", "macosx", "wx" };
71
72    /* test the beos, cli, daemon, macosx, wx tags */
73    for( i=0; i<tagCount; ++i ) {
74        path = tr_strdup_printf( "%s%c%s-%s", tr_getTorrentDir( session ), '/', inf->hashString, tags[i] );
75        if( !stat( path, &sb ) && ( ( sb.st_mode & S_IFMT ) == S_IFREG ) )
76            return path;
77        tr_free( path );
78    }
79
80    /* test a non-tagged file */
81    path = tr_buildPath( tr_getTorrentDir( session ), inf->hashString, NULL );
82    if( !stat( path, &sb ) && ( ( sb.st_mode & S_IFMT ) == S_IFREG ) )
83        return path;
84    tr_free( path );
85
86    /* return the -gtk form by default, since that's the most common case.
87       don't bother testing stat() on it since this is the last candidate
88       and we don't want to return NULL anyway */
89    return tr_strdup_printf( "%s%c%s-%s", tr_getTorrentDir( session ), '/', inf->hashString, "gtk" );
90}
91
92/* this is for really old versions of T and will probably be removed someday */
93void
94tr_metainfoMigrate( tr_session * session,
95                    tr_info *   inf )
96{
97    struct stat new_sb;
98    char *      name = getTorrentFilename( session, inf );
99
100    if( stat( name, &new_sb ) || ( ( new_sb.st_mode & S_IFMT ) != S_IFREG ) )
101    {
102        char *    old_name = getOldTorrentFilename( session, inf );
103        size_t    contentLen;
104        uint8_t * content;
105
106        tr_mkdirp( tr_getTorrentDir( session ), 0777 );
107        if( ( content = tr_loadFile( old_name, &contentLen ) ) )
108        {
109            FILE * out;
110            errno = 0;
111            out = fopen( name, "wb+" );
112            if( !out )
113            {
114                tr_nerr( inf->name, _( "Couldn't create \"%1$s\": %2$s" ),
115                        name, tr_strerror( errno ) );
116            }
117            else
118            {
119                if( fwrite( content, sizeof( uint8_t ), contentLen, out )
120                    == contentLen )
121                {
122                    tr_free( inf->torrent );
123                    inf->torrent = tr_strdup( name );
124                    tr_sessionSetTorrentFile( session, inf->hashString, name );
125                    unlink( old_name );
126                }
127                fclose( out );
128            }
129        }
130
131        tr_free( content );
132        tr_free( old_name );
133    }
134
135    tr_free( name );
136}
137
138/***
139****
140***/
141
142static tr_bool
143path_is_suspicious( const char * path )
144{
145    return ( path == NULL )
146        || ( strstr( path, "../" ) != NULL );
147}
148
149static tr_bool
150getfile( char ** setme, const char * root, tr_benc * path )
151{
152    tr_bool success = FALSE;
153
154    if( tr_bencIsList( path ) )
155    {
156        int i;
157        char * tmp;
158        const int n = tr_bencListSize( path );
159        struct evbuffer * buf = evbuffer_new( );
160
161        evbuffer_add( buf, root, strlen( root ) );
162        for( i = 0; i < n; ++i )
163        {
164            const char * str;
165            if( tr_bencGetStr( tr_bencListChild( path, i ), &str ) )
166            {
167                evbuffer_add( buf, TR_PATH_DELIMITER_STR, 1 );
168                evbuffer_add( buf, str, strlen( str ) );
169            }
170        }
171
172        tmp = evbuffer_free_to_str( buf );
173        *setme = tr_utf8clean( tmp, -1 );
174        tr_free( tmp );
175        /* fprintf( stderr, "[%s]\n", *setme ); */
176        success = TRUE;
177    }
178
179    if( ( *setme != NULL ) && path_is_suspicious( *setme ) )
180    {
181        tr_free( *setme );
182        *setme = NULL;
183        success = FALSE;
184    }
185
186    return success;
187}
188
189static const char*
190parseFiles( tr_info * inf, tr_benc * files, const tr_benc * length )
191{
192    int64_t len;
193
194    inf->totalSize = 0;
195
196    if( tr_bencIsList( files ) ) /* multi-file mode */
197    {
198        tr_file_index_t i;
199
200        inf->isMultifile = 1;
201        inf->fileCount   = tr_bencListSize( files );
202        inf->files       = tr_new0( tr_file, inf->fileCount );
203
204        for( i = 0; i < inf->fileCount; ++i )
205        {
206            tr_benc * file;
207            tr_benc * path;
208
209            file = tr_bencListChild( files, i );
210            if( !tr_bencIsDict( file ) )
211                return "files";
212
213            if( !tr_bencDictFindList( file, "path.utf-8", &path ) )
214                if( !tr_bencDictFindList( file, "path", &path ) )
215                    return "path";
216
217            if( !getfile( &inf->files[i].name, inf->name, path ) )
218                return "path";
219
220            if( !tr_bencDictFindInt( file, "length", &len ) )
221                return "length";
222
223            inf->files[i].length = len;
224            inf->totalSize      += len;
225        }
226    }
227    else if( tr_bencGetInt( length, &len ) ) /* single-file mode */
228    {
229        if( path_is_suspicious( inf->name ) )
230            return "path";
231
232        inf->isMultifile      = 0;
233        inf->fileCount        = 1;
234        inf->files            = tr_new0( tr_file, 1 );
235        inf->files[0].name    = tr_strdup( inf->name );
236        inf->files[0].length  = len;
237        inf->totalSize       += len;
238    }
239    else
240    {
241        return "length";
242    }
243
244    return NULL;
245}
246
247static char *
248tr_convertAnnounceToScrape( const char * announce )
249{
250    char *       scrape = NULL;
251    const char * s;
252
253    /* To derive the scrape URL use the following steps:
254     * Begin with the announce URL. Find the last '/' in it.
255     * If the text immediately following that '/' isn't 'announce'
256     * it will be taken as a sign that that tracker doesn't support
257     * the scrape convention. If it does, substitute 'scrape' for
258     * 'announce' to find the scrape page. */
259    if( ( ( s = strrchr( announce, '/' ) ) ) && !strncmp( ++s, "announce", 8 ) )
260    {
261        const char * prefix = announce;
262        const size_t prefix_len = s - announce;
263        const char * suffix = s + 8;
264        const size_t suffix_len = strlen( suffix );
265        const size_t alloc_len = prefix_len + 6 + suffix_len + 1;
266        char * walk = scrape = tr_new( char, alloc_len );
267        memcpy( walk, prefix, prefix_len ); walk += prefix_len;
268        memcpy( walk, "scrape", 6 );        walk += 6;
269        memcpy( walk, suffix, suffix_len ); walk += suffix_len;
270        *walk++ = '\0';
271        assert( walk - scrape == (int)alloc_len );
272    }
273
274    return scrape;
275}
276
277static const char*
278getannounce( tr_info * inf, tr_benc * meta )
279{
280    const char *      str;
281    tr_tracker_info * trackers = NULL;
282    int               trackerCount = 0;
283    tr_benc *         tiers;
284
285    /* Announce-list */
286    if( tr_bencDictFindList( meta, "announce-list", &tiers ) )
287    {
288        int       n;
289        int       i, j, validTiers;
290        const int numTiers = tr_bencListSize( tiers );
291
292        n = 0;
293        for( i = 0; i < numTiers; ++i )
294            n += tr_bencListSize( tr_bencListChild( tiers, i ) );
295
296        trackers = tr_new0( tr_tracker_info, n );
297
298        for( i = 0, validTiers = 0; i < numTiers; ++i )
299        {
300            tr_benc * tier = tr_bencListChild( tiers, i );
301            const int tierSize = tr_bencListSize( tier );
302            tr_bool anyAdded = FALSE;
303            for( j = 0; j < tierSize; ++j )
304            {
305                if( tr_bencGetStr( tr_bencListChild( tier, j ), &str ) )
306                {
307                    char * url = tr_strstrip( tr_strdup( str ) );
308                    if( tr_urlIsValidTracker( url ) )
309                    {
310                        tr_tracker_info * t = trackers + trackerCount;
311                        t->tier = validTiers;
312                        t->announce = tr_strdup( url );
313                        t->scrape = tr_convertAnnounceToScrape( url );
314                        t->id = trackerCount;
315
316                        anyAdded = TRUE;
317                        ++trackerCount;
318                    }
319                    tr_free( url );
320                }
321            }
322
323            if( anyAdded )
324                ++validTiers;
325        }
326
327        /* did we use any of the tiers? */
328        if( !trackerCount )
329        {
330            tr_free( trackers );
331            trackers = NULL;
332        }
333    }
334
335    /* Regular announce value */
336    if( !trackerCount
337      && tr_bencDictFindStr( meta, "announce", &str ) )
338    {
339        char * url = tr_strstrip( tr_strdup( str ) );
340        if( tr_urlIsValidTracker( url ) )
341        {
342            trackers = tr_new0( tr_tracker_info, 1 );
343            trackers[trackerCount].tier = 0;
344            trackers[trackerCount].announce = tr_strdup( url );
345            trackers[trackerCount].scrape = tr_convertAnnounceToScrape( url );
346            trackers[trackerCount].id = 0;
347            trackerCount++;
348            /*fprintf( stderr, "single announce: [%s]\n", url );*/
349        }
350        tr_free( url );
351    }
352
353    inf->trackers = trackers;
354    inf->trackerCount = trackerCount;
355
356    return NULL;
357}
358
359static void
360geturllist( tr_info * inf,
361            tr_benc * meta )
362{
363    tr_benc * urls;
364    const char * url;
365
366    if( tr_bencDictFindList( meta, "url-list", &urls ) )
367    {
368        int          i;
369        const int    n = tr_bencListSize( urls );
370
371        inf->webseedCount = 0;
372        inf->webseeds = tr_new0( char*, n );
373
374        for( i = 0; i < n; ++i )
375            if( tr_bencGetStr( tr_bencListChild( urls, i ), &url ) )
376                inf->webseeds[inf->webseedCount++] = tr_strdup( url );
377    }
378    else if( tr_bencDictFindStr( meta, "url-list", &url ) ) /* handle single items in webseeds */
379    {
380        inf->webseedCount = 1;
381        inf->webseeds = tr_new0( char*, 1 );
382        inf->webseeds[0] = tr_strdup( url );
383    }
384}
385
386static const char*
387tr_metainfoParseImpl( const tr_session  * session,
388                      tr_info           * inf,
389                      tr_bool           * hasInfoDict,
390                      int               * infoDictLength,
391                      const tr_benc     * meta_in )
392{
393    int64_t         i;
394    size_t          raw_len;
395    const char *    str;
396    const uint8_t * raw;
397    tr_benc *       d;
398    tr_benc *       infoDict = NULL;
399    tr_benc *       meta = (tr_benc *) meta_in;
400    tr_bool         b;
401    tr_bool         isMagnet = FALSE;
402
403    /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
404     * from the Metainfo file. Note that the value will be a bencoded
405     * dictionary, given the definition of the info key above. */
406    b = tr_bencDictFindDict( meta, "info", &infoDict );
407    if( hasInfoDict != NULL )
408        *hasInfoDict = b;
409    if( !b )
410    {
411        /* no info dictionary... is this a magnet link? */
412        if( tr_bencDictFindDict( meta, "magnet-info", &d ) )
413        {
414            isMagnet = TRUE;
415
416            /* get the info-hash */
417            if( !tr_bencDictFindRaw( d, "info_hash", &raw, &raw_len ) )
418                return "info_hash";
419            if( raw_len != SHA_DIGEST_LENGTH )
420                return "info_hash";
421            memcpy( inf->hash, raw, raw_len );
422            tr_sha1_to_hex( inf->hashString, inf->hash );
423
424            /* maybe get the display name */
425            if( tr_bencDictFindStr( d, "display-name", &str ) ) {
426                tr_free( inf->name );
427                inf->name = tr_strdup( str );
428            }
429
430            if( !inf->name )
431                inf->name = tr_strdup( inf->hashString );
432        }
433        else /* not a magnet link and has no info dict... */
434        {
435            return "info";
436        }
437    }
438    else
439    {
440        int len;
441        char * bstr = tr_bencToStr( infoDict, TR_FMT_BENC, &len );
442        tr_sha1( inf->hash, bstr, len, NULL );
443        tr_sha1_to_hex( inf->hashString, inf->hash );
444
445        if( infoDictLength != NULL )
446            *infoDictLength = len;
447
448        tr_free( bstr );
449    }
450
451    /* name */
452    if( !isMagnet ) {
453        if( !tr_bencDictFindStr( infoDict, "name.utf-8", &str ) )
454            if( !tr_bencDictFindStr( infoDict, "name", &str ) )
455                str = "";
456        if( !str || !*str )
457            return "name";
458        tr_free( inf->name );
459        inf->name = tr_utf8clean( str, -1 );
460    }
461
462    /* comment */
463    if( !tr_bencDictFindStr( meta, "comment.utf-8", &str ) )
464        if( !tr_bencDictFindStr( meta, "comment", &str ) )
465            str = "";
466    tr_free( inf->comment );
467    inf->comment = tr_utf8clean( str, -1 );
468
469    /* created by */
470    if( !tr_bencDictFindStr( meta, "created by.utf-8", &str ) )
471        if( !tr_bencDictFindStr( meta, "created by", &str ) )
472            str = "";
473    tr_free( inf->creator );
474    inf->creator = tr_utf8clean( str, -1 );
475
476    /* creation date */
477    if( !tr_bencDictFindInt( meta, "creation date", &i ) )
478        i = 0;
479    inf->dateCreated = i;
480
481    /* private */
482    if( !tr_bencDictFindInt( infoDict, "private", &i ) )
483        if( !tr_bencDictFindInt( meta, "private", &i ) )
484            i = 0;
485    inf->isPrivate = i != 0;
486
487    /* piece length */
488    if( !isMagnet ) {
489        if( !tr_bencDictFindInt( infoDict, "piece length", &i ) || ( i < 1 ) )
490            return "piece length";
491        inf->pieceSize = i;
492    }
493
494    /* pieces */
495    if( !isMagnet ) {
496        if( !tr_bencDictFindRaw( infoDict, "pieces", &raw, &raw_len ) )
497            return "pieces";
498        if( raw_len % SHA_DIGEST_LENGTH )
499            return "pieces";
500        inf->pieceCount = raw_len / SHA_DIGEST_LENGTH;
501        inf->pieces = tr_new0( tr_piece, inf->pieceCount );
502        for( i = 0; i < inf->pieceCount; ++i )
503            memcpy( inf->pieces[i].hash, &raw[i * SHA_DIGEST_LENGTH],
504                    SHA_DIGEST_LENGTH );
505    }
506
507    /* files */
508    if( !isMagnet ) {
509        if( ( str = parseFiles( inf, tr_bencDictFind( infoDict, "files" ),
510                                     tr_bencDictFind( infoDict, "length" ) ) ) )
511            return str;
512        if( !inf->fileCount || !inf->totalSize )
513            return "files";
514        if( (uint64_t) inf->pieceCount !=
515           ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize )
516            return "files";
517    }
518
519    /* get announce or announce-list */
520    if( ( str = getannounce( inf, meta ) ) )
521        return str;
522
523    /* get the url-list */
524    geturllist( inf, meta );
525
526    /* filename of Transmission's copy */
527    tr_free( inf->torrent );
528    inf->torrent = session ?  getTorrentFilename( session, inf ) : NULL;
529
530    return NULL;
531}
532
533tr_bool
534tr_metainfoParse( const tr_session * session,
535                  const tr_benc    * meta_in,
536                  tr_info          * inf,
537                  tr_bool          * hasInfoDict,
538                  int              * infoDictLength )
539{
540    const char * badTag = tr_metainfoParseImpl( session,
541                                                inf,
542                                                hasInfoDict,
543                                                infoDictLength,
544                                                meta_in );
545    const tr_bool success = badTag == NULL;
546
547    if( badTag )
548    {
549        tr_nerr( inf->name, _( "Invalid metadata entry \"%s\"" ), badTag );
550        tr_metainfoFree( inf );
551    }
552
553    return success;
554}
555
556void
557tr_metainfoFree( tr_info * inf )
558{
559    int i;
560    tr_file_index_t ff;
561
562    for( i = 0; i < inf->webseedCount; ++i )
563        tr_free( inf->webseeds[i] );
564
565    for( ff = 0; ff < inf->fileCount; ++ff )
566        tr_free( inf->files[ff].name );
567
568    tr_free( inf->webseeds );
569    tr_free( inf->pieces );
570    tr_free( inf->files );
571    tr_free( inf->comment );
572    tr_free( inf->creator );
573    tr_free( inf->torrent );
574    tr_free( inf->name );
575
576    for( i = 0; i < inf->trackerCount; ++i )
577    {
578        tr_free( inf->trackers[i].announce );
579        tr_free( inf->trackers[i].scrape );
580    }
581    tr_free( inf->trackers );
582
583    memset( inf, '\0', sizeof( tr_info ) );
584}
585
586void
587tr_metainfoRemoveSaved( const tr_session * session,
588                        const tr_info *   inf )
589{
590    char * filename;
591
592    filename = getTorrentFilename( session, inf );
593    unlink( filename );
594    tr_free( filename );
595
596    filename = getOldTorrentFilename( session, inf );
597    unlink( filename );
598    tr_free( filename );
599}
Note: See TracBrowser for help on using the repository browser.