• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-R7000-V1.0.7.12_1.2.5/ap/gpl/transmission/transmission-2.73/libtransmission/
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 13568 2012-10-14 18:10:17Z 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    /* Some torrents with UDP annouce URLs don't have /announce. */
272    else if ( !strncmp( announce, "udp:", 4 ) )
273    {
274        scrape = tr_strdup( announce );
275    }
276
277    return scrape;
278}
279
280static const char*
281getannounce( tr_info * inf, tr_benc * meta )
282{
283    const char *      str;
284    tr_tracker_info * trackers = NULL;
285    int               trackerCount = 0;
286    tr_benc *         tiers;
287
288    /* Announce-list */
289    if( tr_bencDictFindList( meta, "announce-list", &tiers ) )
290    {
291        int       n;
292        int       i, j, validTiers;
293        const int numTiers = tr_bencListSize( tiers );
294
295        n = 0;
296        for( i = 0; i < numTiers; ++i )
297            n += tr_bencListSize( tr_bencListChild( tiers, i ) );
298
299        trackers = tr_new0( tr_tracker_info, n );
300
301        for( i = 0, validTiers = 0; i < numTiers; ++i )
302        {
303            tr_benc * tier = tr_bencListChild( tiers, i );
304            const int tierSize = tr_bencListSize( tier );
305            bool anyAdded = false;
306            for( j = 0; j < tierSize; ++j )
307            {
308                if( tr_bencGetStr( tr_bencListChild( tier, j ), &str ) )
309                {
310                    char * url = tr_strstrip( tr_strdup( str ) );
311                    if( !tr_urlIsValidTracker( url ) )
312                        tr_free( url );
313                    else {
314                        tr_tracker_info * t = trackers + trackerCount;
315                        t->tier = validTiers;
316                        t->announce = url;
317                        t->scrape = tr_convertAnnounceToScrape( url );
318                        t->id = trackerCount;
319
320                        anyAdded = true;
321                        ++trackerCount;
322                    }
323                }
324            }
325
326            if( anyAdded )
327                ++validTiers;
328        }
329
330        /* did we use any of the tiers? */
331        if( !trackerCount )
332        {
333            tr_free( trackers );
334            trackers = NULL;
335        }
336    }
337
338    /* Regular announce value */
339    if( !trackerCount
340      && tr_bencDictFindStr( meta, "announce", &str ) )
341    {
342        char * url = tr_strstrip( tr_strdup( str ) );
343        if( !tr_urlIsValidTracker( url ) )
344            tr_free( url );
345        else {
346            trackers = tr_new0( tr_tracker_info, 1 );
347            trackers[trackerCount].tier = 0;
348            trackers[trackerCount].announce = url;
349            trackers[trackerCount].scrape = tr_convertAnnounceToScrape( url );
350            trackers[trackerCount].id = 0;
351            trackerCount++;
352            /*fprintf( stderr, "single announce: [%s]\n", url );*/
353        }
354    }
355
356    inf->trackers = trackers;
357    inf->trackerCount = trackerCount;
358
359    return NULL;
360}
361
362/**
363 * @brief Ensure that the URLs for multfile torrents end in a slash.
364 *
365 * See http://bittorrent.org/beps/bep_0019.html#metadata-extension
366 * for background on how the trailing slash is used for "url-list"
367 * fields.
368 *
369 * This function is to workaround some .torrent generators, such as
370 * mktorrent and very old versions of utorrent, that don't add the
371 * trailing slash for multifile torrents if omitted by the end user.
372 */
373static char*
374fix_webseed_url( const tr_info * inf, const char * url_in )
375{
376    size_t len;
377    char * url;
378    char * ret = NULL;
379
380    url = tr_strdup( url_in );
381    tr_strstrip( url );
382    len = strlen( url );
383
384    if( tr_urlIsValid( url, len ) )
385    {
386        if( ( inf->fileCount > 1 ) && ( len > 0 ) && ( url[len-1] != '/' ) )
387            ret = tr_strdup_printf( "%*.*s/", (int)len, (int)len, url );
388        else
389            ret = tr_strndup( url, len );
390    }
391
392    tr_free( url );
393    return ret;
394}
395
396static void
397geturllist( tr_info * inf,
398            tr_benc * meta )
399{
400    tr_benc * urls;
401    const char * url;
402
403    if( tr_bencDictFindList( meta, "url-list", &urls ) )
404    {
405        int          i;
406        const int    n = tr_bencListSize( urls );
407
408        inf->webseedCount = 0;
409        inf->webseeds = tr_new0( char*, n );
410
411        for( i = 0; i < n; ++i )
412        {
413            if( tr_bencGetStr( tr_bencListChild( urls, i ), &url ) )
414            {
415                char * fixed_url = fix_webseed_url( inf, url );
416
417                if( fixed_url != NULL )
418                    inf->webseeds[inf->webseedCount++] = fixed_url;
419            }
420        }
421    }
422    else if( tr_bencDictFindStr( meta, "url-list", &url ) ) /* handle single items in webseeds */
423    {
424        char * fixed_url = fix_webseed_url( inf, url );
425
426        if( fixed_url != NULL )
427        {
428            inf->webseedCount = 1;
429            inf->webseeds = tr_new0( char*, 1 );
430            inf->webseeds[0] = fixed_url;
431        }
432    }
433}
434
435static const char*
436tr_metainfoParseImpl( const tr_session  * session,
437                      tr_info           * inf,
438                      bool              * hasInfoDict,
439                      int               * infoDictLength,
440                      const tr_benc     * meta_in )
441{
442    int64_t         i;
443    size_t          raw_len;
444    const char *    str;
445    const uint8_t * raw;
446    tr_benc *       d;
447    tr_benc *       infoDict = NULL;
448    tr_benc *       meta = (tr_benc *) meta_in;
449    bool            b;
450    bool            isMagnet = false;
451
452    /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
453     * from the Metainfo file. Note that the value will be a bencoded
454     * dictionary, given the definition of the info key above. */
455    b = tr_bencDictFindDict( meta, "info", &infoDict );
456    if( hasInfoDict != NULL )
457        *hasInfoDict = b;
458    if( !b )
459    {
460        /* no info dictionary... is this a magnet link? */
461        if( tr_bencDictFindDict( meta, "magnet-info", &d ) )
462        {
463            isMagnet = true;
464
465            /* get the info-hash */
466            if( !tr_bencDictFindRaw( d, "info_hash", &raw, &raw_len ) )
467                return "info_hash";
468            if( raw_len != SHA_DIGEST_LENGTH )
469                return "info_hash";
470            memcpy( inf->hash, raw, raw_len );
471            tr_sha1_to_hex( inf->hashString, inf->hash );
472
473            /* maybe get the display name */
474            if( tr_bencDictFindStr( d, "display-name", &str ) ) {
475                tr_free( inf->name );
476                inf->name = tr_strdup( str );
477            }
478
479            if( !inf->name )
480                inf->name = tr_strdup( inf->hashString );
481        }
482        else /* not a magnet link and has no info dict... */
483        {
484            return "info";
485        }
486    }
487    else
488    {
489        int len;
490        char * bstr = tr_bencToStr( infoDict, TR_FMT_BENC, &len );
491        tr_sha1( inf->hash, bstr, len, NULL );
492        tr_sha1_to_hex( inf->hashString, inf->hash );
493
494        if( infoDictLength != NULL )
495            *infoDictLength = len;
496
497        tr_free( bstr );
498    }
499
500    /* name */
501    if( !isMagnet ) {
502        if( !tr_bencDictFindStr( infoDict, "name.utf-8", &str ) )
503            if( !tr_bencDictFindStr( infoDict, "name", &str ) )
504                str = "";
505        if( !str || !*str )
506            return "name";
507        tr_free( inf->name );
508        inf->name = tr_utf8clean( str, -1 );
509    }
510
511    /* comment */
512    if( !tr_bencDictFindStr( meta, "comment.utf-8", &str ) )
513        if( !tr_bencDictFindStr( meta, "comment", &str ) )
514            str = "";
515    tr_free( inf->comment );
516    inf->comment = tr_utf8clean( str, -1 );
517
518    /* created by */
519    if( !tr_bencDictFindStr( meta, "created by.utf-8", &str ) )
520        if( !tr_bencDictFindStr( meta, "created by", &str ) )
521            str = "";
522    tr_free( inf->creator );
523    inf->creator = tr_utf8clean( str, -1 );
524
525    /* creation date */
526    if( !tr_bencDictFindInt( meta, "creation date", &i ) )
527        i = 0;
528    inf->dateCreated = i;
529
530    /* private */
531    if( !tr_bencDictFindInt( infoDict, "private", &i ) )
532        if( !tr_bencDictFindInt( meta, "private", &i ) )
533            i = 0;
534    inf->isPrivate = i != 0;
535
536    /* piece length */
537    if( !isMagnet ) {
538        if( !tr_bencDictFindInt( infoDict, "piece length", &i ) || ( i < 1 ) )
539            return "piece length";
540        inf->pieceSize = i;
541    }
542
543    /* pieces */
544    if( !isMagnet ) {
545        if( !tr_bencDictFindRaw( infoDict, "pieces", &raw, &raw_len ) )
546            return "pieces";
547        if( raw_len % SHA_DIGEST_LENGTH )
548            return "pieces";
549        inf->pieceCount = raw_len / SHA_DIGEST_LENGTH;
550        inf->pieces = tr_new0( tr_piece, inf->pieceCount );
551        for( i = 0; i < inf->pieceCount; ++i )
552            memcpy( inf->pieces[i].hash, &raw[i * SHA_DIGEST_LENGTH],
553                    SHA_DIGEST_LENGTH );
554    }
555
556    /* files */
557    if( !isMagnet ) {
558        if( ( str = parseFiles( inf, tr_bencDictFind( infoDict, "files" ),
559                                     tr_bencDictFind( infoDict, "length" ) ) ) )
560            return str;
561        if( !inf->fileCount || !inf->totalSize )
562            return "files";
563        if( (uint64_t) inf->pieceCount !=
564           ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize )
565            return "files";
566    }
567
568    /* get announce or announce-list */
569    if( ( str = getannounce( inf, meta ) ) )
570        return str;
571
572    /* get the url-list */
573    geturllist( inf, meta );
574
575    /* filename of Transmission's copy */
576    tr_free( inf->torrent );
577    inf->torrent = session ?  getTorrentFilename( session, inf ) : NULL;
578
579    return NULL;
580}
581
582bool
583tr_metainfoParse( const tr_session * session,
584                  const tr_benc    * meta_in,
585                  tr_info          * inf,
586                  bool             * hasInfoDict,
587                  int              * infoDictLength )
588{
589    const char * badTag = tr_metainfoParseImpl( session,
590                                                inf,
591                                                hasInfoDict,
592                                                infoDictLength,
593                                                meta_in );
594    const bool success = badTag == NULL;
595
596    if( badTag )
597    {
598        tr_nerr( inf->name, _( "Invalid metadata entry \"%s\"" ), badTag );
599        tr_metainfoFree( inf );
600    }
601
602    return success;
603}
604
605void
606tr_metainfoFree( tr_info * inf )
607{
608    int i;
609    tr_file_index_t ff;
610
611    for( i = 0; i < inf->webseedCount; ++i )
612        tr_free( inf->webseeds[i] );
613
614    for( ff = 0; ff < inf->fileCount; ++ff )
615        tr_free( inf->files[ff].name );
616
617    tr_free( inf->webseeds );
618    tr_free( inf->pieces );
619    tr_free( inf->files );
620    tr_free( inf->comment );
621    tr_free( inf->creator );
622    tr_free( inf->torrent );
623    tr_free( inf->name );
624
625    for( i = 0; i < inf->trackerCount; ++i )
626    {
627        tr_free( inf->trackers[i].announce );
628        tr_free( inf->trackers[i].scrape );
629    }
630    tr_free( inf->trackers );
631
632    memset( inf, '\0', sizeof( tr_info ) );
633}
634
635void
636tr_metainfoRemoveSaved( const tr_session * session, const tr_info * inf )
637{
638    char * filename;
639
640    filename = getTorrentFilename( session, inf );
641    unlink( filename );
642    tr_free( filename );
643
644    filename = getOldTorrentFilename( session, inf );
645    unlink( filename );
646    tr_free( filename );
647}
648