source: trunk/libtransmission/metainfo.c @ 12918

Last change on this file since 12918 was 12848, checked in by jordan, 10 years ago

(trunk libT) #4437 "Multi file webseeds don't work" -- handle multifile torrents with web seed urls that incorrectly don't end with a slash.

  • Property svn:keywords set to Date Rev Author Id
File size: 18.2 KB
RevLine 
[9681]1/*
[11709]2 * This file Copyright (C) Mnemosyne LLC
[1]3 *
[11599]4 * This file is licensed by the GPL version 2. Works owned by the
[9681]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.
[260]9 *
[9681]10 * $Id: metainfo.c 12848 2011-09-06 16:45:48Z jordan $
11 */
[1]12
[3775]13#include <assert.h>
[2544]14#include <errno.h>
[12230]15#include <stdio.h> /* fopen(), fwrite(), fclose() */
16#include <string.h> /* strlen() */
[2544]17
[2328]18#include <sys/types.h>
19#include <sys/stat.h>
20#include <unistd.h> /* unlink, stat */
[2544]21
[11548]22#include <event2/buffer.h>
[5673]23
[1]24#include "transmission.h"
[7476]25#include "session.h"
[2311]26#include "bencode.h"
[3105]27#include "crypto.h" /* tr_sha1 */
[2311]28#include "metainfo.h"
[12228]29#include "platform.h" /* tr_getTorrentDir() */
[2555]30#include "utils.h"
[1]31
[3473]32/***
33****
34***/
35
[10502]36char*
37tr_metainfoGetBasename( const tr_info * inf )
38{
[12293]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 );
[10502]42
[12293]43    for( i=0; i<name_len; ++i )
44        if( ret[i] == '/' )
45            ret[i] = '_';
[11398]46
[10502]47
48    return ret;
49}
50
[6601]51static char*
[12293]52getTorrentFilename( const tr_session * session, const tr_info * inf )
[4323]53{
[10502]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;
[5611]59}
60
[6601]61static char*
[8775]62getOldTorrentFilename( const tr_session * session, const tr_info * inf )
[5611]63{
[8775]64    int i;
65    char * path;
66    struct stat sb;
[8776]67    const int tagCount = 5;
68    const char * tags[] = { "beos", "cli", "daemon", "macosx", "wx" };
[6603]69
[8776]70    /* test the beos, cli, daemon, macosx, wx tags */
[8775]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    }
[6603]77
[8776]78    /* test a non-tagged file */
[8775]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
[8776]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" );
[4323]88}
89
[8465]90/* this is for really old versions of T and will probably be removed someday */
[5611]91void
[7385]92tr_metainfoMigrate( tr_session * session,
[6795]93                    tr_info *   inf )
[5611]94{
95    struct stat new_sb;
[7385]96    char *      name = getTorrentFilename( session, inf );
[4323]97
[6613]98    if( stat( name, &new_sb ) || ( ( new_sb.st_mode & S_IFMT ) != S_IFREG ) )
[5611]99    {
[7385]100        char *    old_name = getOldTorrentFilename( session, inf );
[6795]101        size_t    contentLen;
[5611]102        uint8_t * content;
103
[7385]104        tr_mkdirp( tr_getTorrentDir( session ), 0777 );
[6795]105        if( ( content = tr_loadFile( old_name, &contentLen ) ) )
[5611]106        {
[5852]107            FILE * out;
108            errno = 0;
[6613]109            out = fopen( name, "wb+" );
[5852]110            if( !out )
[5613]111            {
[6613]112                tr_nerr( inf->name, _( "Couldn't create \"%1$s\": %2$s" ),
[6795]113                        name, tr_strerror( errno ) );
[5613]114            }
[5852]115            else
116            {
[6613]117                if( fwrite( content, sizeof( uint8_t ), contentLen, out )
[6795]118                    == contentLen )
[5852]119                {
120                    tr_free( inf->torrent );
[6613]121                    inf->torrent = tr_strdup( name );
[7385]122                    tr_sessionSetTorrentFile( session, inf->hashString, name );
[5852]123                    unlink( old_name );
124                }
125                fclose( out );
126            }
[5611]127        }
128
129        tr_free( content );
[6601]130        tr_free( old_name );
[5611]131    }
[6601]132
[6613]133    tr_free( name );
[5611]134}
135
[6603]136/***
137****
138***/
139
[12204]140static bool
[9830]141path_is_suspicious( const char * path )
[6603]142{
[9830]143    return ( path == NULL )
144        || ( strstr( path, "../" ) != NULL );
145}
146
[12204]147static bool
[12290]148getfile( char ** setme, const char * root, tr_benc * path, struct evbuffer * buf )
[9830]149{
[12204]150    bool success = false;
[6603]151
[8871]152    if( tr_bencIsList( path ) )
[6603]153    {
[9830]154        int i;
155        const int n = tr_bencListSize( path );
[6603]156
[12290]157        evbuffer_drain( buf, evbuffer_get_length( buf ) );
[6603]158        evbuffer_add( buf, root, strlen( root ) );
[6795]159        for( i = 0; i < n; ++i )
160        {
[6603]161            const char * str;
[9830]162            if( tr_bencGetStr( tr_bencListChild( path, i ), &str ) )
[6795]163            {
[6603]164                evbuffer_add( buf, TR_PATH_DELIMITER_STR, 1 );
165                evbuffer_add( buf, str, strlen( str ) );
166            }
167        }
168
[12290]169        *setme = tr_utf8clean( (char*)evbuffer_pullup( buf, -1 ), evbuffer_get_length( buf ) );
[6603]170        /* fprintf( stderr, "[%s]\n", *setme ); */
[12204]171        success = true;
[6603]172    }
173
[9830]174    if( ( *setme != NULL ) && path_is_suspicious( *setme ) )
175    {
176        tr_free( *setme );
177        *setme = NULL;
[12204]178        success = false;
[9830]179    }
180
[8871]181    return success;
[6603]182}
183
184static const char*
[9830]185parseFiles( tr_info * inf, tr_benc * files, const tr_benc * length )
[6603]186{
[6613]187    int64_t len;
[6603]188
189    inf->totalSize = 0;
190
191    if( tr_bencIsList( files ) ) /* multi-file mode */
192    {
[6613]193        tr_file_index_t i;
[12290]194        struct evbuffer * buf = evbuffer_new( );
[6613]195
[6603]196        inf->isMultifile = 1;
197        inf->fileCount   = tr_bencListSize( files );
198        inf->files       = tr_new0( tr_file, inf->fileCount );
199
[6795]200        for( i = 0; i < inf->fileCount; ++i )
[6603]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
[12290]213            if( !getfile( &inf->files[i].name, inf->name, path, buf ) )
[6603]214                return "path";
215
[7567]216            if( !tr_bencDictFindInt( file, "length", &len ) )
[6603]217                return "length";
218
[7567]219            inf->files[i].length = len;
220            inf->totalSize      += len;
[6603]221        }
[12290]222
223        evbuffer_free( buf );
[6603]224    }
[6613]225    else if( tr_bencGetInt( length, &len ) ) /* single-file mode */
[6603]226    {
[9830]227        if( path_is_suspicious( inf->name ) )
228            return "path";
229
[6603]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 );
[6613]234        inf->files[0].length  = len;
235        inf->totalSize       += len;
[6603]236    }
237    else
238    {
239        return "length";
240    }
241
242    return NULL;
243}
244
[5676]245static char *
[9550]246tr_convertAnnounceToScrape( const char * announce )
[5676]247{
[6795]248    char *       scrape = NULL;
[5676]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
[11599]256     * 'announce' to find the scrape page. */
[8688]257    if( ( ( s = strrchr( announce, '/' ) ) ) && !strncmp( ++s, "announce", 8 ) )
[5676]258    {
[8688]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';
[8689]269        assert( walk - scrape == (int)alloc_len );
[5676]270    }
271
272    return scrape;
273}
274
[6603]275static const char*
[9568]276getannounce( tr_info * inf, tr_benc * meta )
[5676]277{
[6795]278    const char *      str;
[5676]279    tr_tracker_info * trackers = NULL;
[6795]280    int               trackerCount = 0;
281    tr_benc *         tiers;
[5676]282
283    /* Announce-list */
284    if( tr_bencDictFindList( meta, "announce-list", &tiers ) )
285    {
[6795]286        int       n;
[7598]287        int       i, j, validTiers;
[6613]288        const int numTiers = tr_bencListSize( tiers );
[5676]289
290        n = 0;
[6795]291        for( i = 0; i < numTiers; ++i )
[6613]292            n += tr_bencListSize( tr_bencListChild( tiers, i ) );
[5676]293
294        trackers = tr_new0( tr_tracker_info, n );
295
[7598]296        for( i = 0, validTiers = 0; i < numTiers; ++i )
[6795]297        {
[6613]298            tr_benc * tier = tr_bencListChild( tiers, i );
299            const int tierSize = tr_bencListSize( tier );
[12204]300            bool anyAdded = false;
[6795]301            for( j = 0; j < tierSize; ++j )
302            {
303                if( tr_bencGetStr( tr_bencListChild( tier, j ), &str ) )
304                {
[6717]305                    char * url = tr_strstrip( tr_strdup( str ) );
[12293]306                    if( !tr_urlIsValidTracker( url ) )
307                        tr_free( url );
308                    else {
[9902]309                        tr_tracker_info * t = trackers + trackerCount;
[7598]310                        t->tier = validTiers;
[12293]311                        t->announce = url;
[9550]312                        t->scrape = tr_convertAnnounceToScrape( url );
[9927]313                        t->id = trackerCount;
[8889]314
[12204]315                        anyAdded = true;
[9902]316                        ++trackerCount;
[6717]317                    }
[5676]318                }
319            }
[8889]320
[7598]321            if( anyAdded )
322                ++validTiers;
[5676]323        }
324
325        /* did we use any of the tiers? */
[6795]326        if( !trackerCount )
327        {
[5676]328            tr_free( trackers );
329            trackers = NULL;
330        }
331    }
332
333    /* Regular announce value */
334    if( !trackerCount
[6795]335      && tr_bencDictFindStr( meta, "announce", &str ) )
[5676]336    {
[6717]337        char * url = tr_strstrip( tr_strdup( str ) );
[12293]338        if( !tr_urlIsValidTracker( url ) )
339            tr_free( url );
340        else {
[6717]341            trackers = tr_new0( tr_tracker_info, 1 );
342            trackers[trackerCount].tier = 0;
[12293]343            trackers[trackerCount].announce = url;
[9902]344            trackers[trackerCount].scrape = tr_convertAnnounceToScrape( url );
[9927]345            trackers[trackerCount].id = 0;
[9902]346            trackerCount++;
[6717]347            /*fprintf( stderr, "single announce: [%s]\n", url );*/
348        }
[5676]349    }
350
351    inf->trackers = trackers;
352    inf->trackerCount = trackerCount;
353
[9568]354    return NULL;
[6603]355}
[5963]356
[12848]357/**
358 * @brief Ensure that the URLs for multfile torrents end in a slash.
359 *
360 * See http://bittorrent.org/beps/bep_0019.html#metadata-extension
361 * for background on how the trailing slash is used for "url-list"
362 * fields.
363 *
364 * This function is to workaround some .torrent generators, such as
365 * mktorrent and very old versions of utorrent, that don't add the
366 * trailing slash for multifile torrents if omitted by the end user.
367 */
368static char*
369fix_webseed_url( const tr_info * inf, const char * url )
370{
371    char * ret = NULL;
372    const size_t len = strlen( url );
373
374    if( tr_urlIsValid( url, len ) )
375    {
376        if( ( inf->fileCount > 1 ) && ( len > 0 ) && ( url[len-1] != '/' ) )
377            ret = tr_strdup_printf( "%*.*s/", (int)len, (int)len, url );
378        else
379            ret = tr_strndup( url, len );
380    }
381
382    return ret;
383}
384
[6603]385static void
[6795]386geturllist( tr_info * inf,
387            tr_benc * meta )
[6603]388{
[6612]389    tr_benc * urls;
[11186]390    const char * url;
[6603]391
392    if( tr_bencDictFindList( meta, "url-list", &urls ) )
393    {
[6795]394        int          i;
395        const int    n = tr_bencListSize( urls );
[6603]396
397        inf->webseedCount = 0;
398        inf->webseeds = tr_new0( char*, n );
399
[6795]400        for( i = 0; i < n; ++i )
[12416]401        {
[6603]402            if( tr_bencGetStr( tr_bencListChild( urls, i ), &url ) )
[12416]403            {
[12848]404                char * fixed_url = fix_webseed_url( inf, url );
[12416]405
[12848]406                if( fixed_url != NULL )
407                    inf->webseeds[inf->webseedCount++] = fixed_url;
[12416]408            }
409        }
[6603]410    }
[11186]411    else if( tr_bencDictFindStr( meta, "url-list", &url ) ) /* handle single items in webseeds */
412    {
[12848]413        char * fixed_url = fix_webseed_url( inf, url );
[12416]414
[12848]415        if( fixed_url != NULL )
[12416]416        {
417            inf->webseedCount = 1;
418            inf->webseeds = tr_new0( char*, 1 );
[12848]419            inf->webseeds[0] = fixed_url;
[12416]420        }
[11186]421    }
[5676]422}
423
[6603]424static const char*
[10084]425tr_metainfoParseImpl( const tr_session  * session,
426                      tr_info           * inf,
[12204]427                      bool              * hasInfoDict,
[10084]428                      int               * infoDictLength,
429                      const tr_benc     * meta_in )
[1]430{
[6795]431    int64_t         i;
432    size_t          raw_len;
433    const char *    str;
[6602]434    const uint8_t * raw;
[10084]435    tr_benc *       d;
436    tr_benc *       infoDict = NULL;
[6795]437    tr_benc *       meta = (tr_benc *) meta_in;
[12204]438    bool            b;
439    bool            isMagnet = false;
[4267]440
441    /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
[6795]442     * from the Metainfo file. Note that the value will be a bencoded
[4267]443     * dictionary, given the definition of the info key above. */
[10084]444    b = tr_bencDictFindDict( meta, "info", &infoDict );
445    if( hasInfoDict != NULL )
446        *hasInfoDict = b;
447    if( !b )
448    {
449        /* no info dictionary... is this a magnet link? */
450        if( tr_bencDictFindDict( meta, "magnet-info", &d ) )
451        {
[12204]452            isMagnet = true;
[10084]453
454            /* get the info-hash */
455            if( !tr_bencDictFindRaw( d, "info_hash", &raw, &raw_len ) )
456                return "info_hash";
457            if( raw_len != SHA_DIGEST_LENGTH )
458                return "info_hash";
459            memcpy( inf->hash, raw, raw_len );
460            tr_sha1_to_hex( inf->hashString, inf->hash );
461
462            /* maybe get the display name */
463            if( tr_bencDictFindStr( d, "display-name", &str ) ) {
464                tr_free( inf->name );
465                inf->name = tr_strdup( str );
466            }
[11103]467
468            if( !inf->name )
469                inf->name = tr_strdup( inf->hashString );
[10084]470        }
471        else /* not a magnet link and has no info dict... */
472        {
473            return "info";
474        }
475    }
[6795]476    else
477    {
[8588]478        int len;
[10084]479        char * bstr = tr_bencToStr( infoDict, TR_FMT_BENC, &len );
[7567]480        tr_sha1( inf->hash, bstr, len, NULL );
[6602]481        tr_sha1_to_hex( inf->hashString, inf->hash );
[9550]482
483        if( infoDictLength != NULL )
484            *infoDictLength = len;
485
[7567]486        tr_free( bstr );
[4267]487    }
[3105]488
[6602]489    /* name */
[10084]490    if( !isMagnet ) {
491        if( !tr_bencDictFindStr( infoDict, "name.utf-8", &str ) )
492            if( !tr_bencDictFindStr( infoDict, "name", &str ) )
493                str = "";
494        if( !str || !*str )
495            return "name";
496        tr_free( inf->name );
[10963]497        inf->name = tr_utf8clean( str, -1 );
[10084]498    }
[310]499
[5041]500    /* comment */
[6602]501    if( !tr_bencDictFindStr( meta, "comment.utf-8", &str ) )
502        if( !tr_bencDictFindStr( meta, "comment", &str ) )
503            str = "";
[5041]504    tr_free( inf->comment );
[10963]505    inf->comment = tr_utf8clean( str, -1 );
[6795]506
[6603]507    /* created by */
[6602]508    if( !tr_bencDictFindStr( meta, "created by.utf-8", &str ) )
509        if( !tr_bencDictFindStr( meta, "created by", &str ) )
510            str = "";
[5041]511    tr_free( inf->creator );
[10963]512    inf->creator = tr_utf8clean( str, -1 );
[6795]513
[6603]514    /* creation date */
[6602]515    if( !tr_bencDictFindInt( meta, "creation date", &i ) )
516        i = 0;
517    inf->dateCreated = i;
[6795]518
[6603]519    /* private */
[10084]520    if( !tr_bencDictFindInt( infoDict, "private", &i ) )
[6602]521        if( !tr_bencDictFindInt( meta, "private", &i ) )
522            i = 0;
523    inf->isPrivate = i != 0;
[6795]524
[6603]525    /* piece length */
[10084]526    if( !isMagnet ) {
527        if( !tr_bencDictFindInt( infoDict, "piece length", &i ) || ( i < 1 ) )
528            return "piece length";
529        inf->pieceSize = i;
530    }
[1]531
[6603]532    /* pieces */
[10084]533    if( !isMagnet ) {
534        if( !tr_bencDictFindRaw( infoDict, "pieces", &raw, &raw_len ) )
535            return "pieces";
536        if( raw_len % SHA_DIGEST_LENGTH )
537            return "pieces";
538        inf->pieceCount = raw_len / SHA_DIGEST_LENGTH;
539        inf->pieces = tr_new0( tr_piece, inf->pieceCount );
540        for( i = 0; i < inf->pieceCount; ++i )
541            memcpy( inf->pieces[i].hash, &raw[i * SHA_DIGEST_LENGTH],
542                    SHA_DIGEST_LENGTH );
543    }
[1]544
[6602]545    /* files */
[10084]546    if( !isMagnet ) {
547        if( ( str = parseFiles( inf, tr_bencDictFind( infoDict, "files" ),
548                                     tr_bencDictFind( infoDict, "length" ) ) ) )
549            return str;
550        if( !inf->fileCount || !inf->totalSize )
551            return "files";
552        if( (uint64_t) inf->pieceCount !=
553           ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize )
554            return "files";
555    }
[2512]556
[1242]557    /* get announce or announce-list */
[6795]558    if( ( str = getannounce( inf, meta ) ) )
[6603]559        return str;
[1242]560
[6073]561    /* get the url-list */
562    geturllist( inf, meta );
563
[5611]564    /* filename of Transmission's copy */
565    tr_free( inf->torrent );
[8122]566    inf->torrent = session ?  getTorrentFilename( session, inf ) : NULL;
[5611]567
[6603]568    return NULL;
569}
570
[12204]571bool
[7385]572tr_metainfoParse( const tr_session * session,
[10084]573                  const tr_benc    * meta_in,
[8871]574                  tr_info          * inf,
[12204]575                  bool             * hasInfoDict,
[10084]576                  int              * infoDictLength )
[6603]577{
[9550]578    const char * badTag = tr_metainfoParseImpl( session,
579                                                inf,
[10084]580                                                hasInfoDict,
[9550]581                                                infoDictLength,
582                                                meta_in );
[12204]583    const bool success = badTag == NULL;
[6795]584
[6603]585    if( badTag )
586    {
587        tr_nerr( inf->name, _( "Invalid metadata entry \"%s\"" ), badTag );
588        tr_metainfoFree( inf );
589    }
[8871]590
591    return success;
[1]592}
593
[6603]594void
595tr_metainfoFree( tr_info * inf )
[1242]596{
[9550]597    int i;
[5329]598    tr_file_index_t ff;
[1242]599
[6795]600    for( i = 0; i < inf->webseedCount; ++i )
[6073]601        tr_free( inf->webseeds[i] );
602
[6795]603    for( ff = 0; ff < inf->fileCount; ++ff )
[5329]604        tr_free( inf->files[ff].name );
[5031]605
[6073]606    tr_free( inf->webseeds );
[2219]607    tr_free( inf->pieces );
608    tr_free( inf->files );
[5034]609    tr_free( inf->comment );
610    tr_free( inf->creator );
[5337]611    tr_free( inf->torrent );
612    tr_free( inf->name );
[6795]613
614    for( i = 0; i < inf->trackerCount; ++i )
615    {
[5673]616        tr_free( inf->trackers[i].announce );
617        tr_free( inf->trackers[i].scrape );
[1242]618    }
[5673]619    tr_free( inf->trackers );
[2494]620
[6795]621    memset( inf, '\0', sizeof( tr_info ) );
[1242]622}
623
[2747]624void
[12293]625tr_metainfoRemoveSaved( const tr_session * session, const tr_info * inf )
[310]626{
[6601]627    char * filename;
[310]628
[7385]629    filename = getTorrentFilename( session, inf );
[5611]630    unlink( filename );
[6601]631    tr_free( filename );
[1564]632
[7385]633    filename = getOldTorrentFilename( session, inf );
[5611]634    unlink( filename );
[6601]635    tr_free( filename );
[1564]636}
Note: See TracBrowser for help on using the repository browser.