source: trunk/libtransmission/metainfo.c @ 12416

Last change on this file since 12416 was 12416, checked in by jordan, 12 years ago

(trunk libT) #4227 "invalid URLs aren't filtered out of .torrents' webseed lists"

If we can't parse a URL provided in the .torrent files' webseed list, that URL should be discarded.

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