source: trunk/libtransmission/metainfo.c @ 11599

Last change on this file since 11599 was 11599, checked in by charles, 11 years ago

(trunk) Join the 21st century and use only 1 space at the end sentences. This commit is nearly as important as the semi-annual ones that remove trailing spaces from the ends of lines of code... :)

  • Property svn:keywords set to Date Rev Author Id
File size: 17.7 KB
Line 
1/*
2 * This file Copyright (C) 2009-2010 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 11599 2010-12-27 19:18:17Z charles $
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 int
387is_rfc2396_alnum( char ch )
388{
389    return ( '0' <= ch && ch <= '9' )
390        || ( 'A' <= ch && ch <= 'Z' )
391        || ( 'a' <= ch && ch <= 'z' )
392        || ch == '.'
393        || ch == '-'
394        || ch == '_'
395        || ch == '~';
396}
397
398static void
399escape( char * out, const uint8_t * in, size_t in_len ) /* rfc2396 */
400{
401    const uint8_t *end = in + in_len;
402
403    while( in != end )
404        if( is_rfc2396_alnum( *in ) )
405            *out++ = (char) *in++;
406        else
407            out += tr_snprintf( out, 4, "%%%02x", (unsigned int)*in++ );
408
409    *out = '\0';
410}
411
412static const char*
413tr_metainfoParseImpl( const tr_session  * session,
414                      tr_info           * inf,
415                      tr_bool           * hasInfoDict,
416                      int               * infoDictLength,
417                      const tr_benc     * meta_in )
418{
419    int64_t         i;
420    size_t          raw_len;
421    const char *    str;
422    const uint8_t * raw;
423    tr_benc *       d;
424    tr_benc *       infoDict = NULL;
425    tr_benc *       meta = (tr_benc *) meta_in;
426    tr_bool         b;
427    tr_bool         isMagnet = FALSE;
428
429    /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
430     * from the Metainfo file. Note that the value will be a bencoded
431     * dictionary, given the definition of the info key above. */
432    b = tr_bencDictFindDict( meta, "info", &infoDict );
433    if( hasInfoDict != NULL )
434        *hasInfoDict = b;
435    if( !b )
436    {
437        /* no info dictionary... is this a magnet link? */
438        if( tr_bencDictFindDict( meta, "magnet-info", &d ) )
439        {
440            isMagnet = TRUE;
441
442            /* get the info-hash */
443            if( !tr_bencDictFindRaw( d, "info_hash", &raw, &raw_len ) )
444                return "info_hash";
445            if( raw_len != SHA_DIGEST_LENGTH )
446                return "info_hash";
447            memcpy( inf->hash, raw, raw_len );
448            tr_sha1_to_hex( inf->hashString, inf->hash );
449            escape( inf->hashEscaped, inf->hash, SHA_DIGEST_LENGTH );
450
451            /* maybe get the display name */
452            if( tr_bencDictFindStr( d, "display-name", &str ) ) {
453                tr_free( inf->name );
454                inf->name = tr_strdup( str );
455            }
456
457            if( !inf->name )
458                inf->name = tr_strdup( inf->hashString );
459        }
460        else /* not a magnet link and has no info dict... */
461        {
462            return "info";
463        }
464    }
465    else
466    {
467        int len;
468        char * bstr = tr_bencToStr( infoDict, TR_FMT_BENC, &len );
469        tr_sha1( inf->hash, bstr, len, NULL );
470        tr_sha1_to_hex( inf->hashString, inf->hash );
471        escape( inf->hashEscaped, inf->hash, SHA_DIGEST_LENGTH );
472
473        if( infoDictLength != NULL )
474            *infoDictLength = len;
475
476        tr_free( bstr );
477    }
478
479    /* name */
480    if( !isMagnet ) {
481        if( !tr_bencDictFindStr( infoDict, "name.utf-8", &str ) )
482            if( !tr_bencDictFindStr( infoDict, "name", &str ) )
483                str = "";
484        if( !str || !*str )
485            return "name";
486        tr_free( inf->name );
487        inf->name = tr_utf8clean( str, -1 );
488    }
489
490    /* comment */
491    if( !tr_bencDictFindStr( meta, "comment.utf-8", &str ) )
492        if( !tr_bencDictFindStr( meta, "comment", &str ) )
493            str = "";
494    tr_free( inf->comment );
495    inf->comment = tr_utf8clean( str, -1 );
496
497    /* created by */
498    if( !tr_bencDictFindStr( meta, "created by.utf-8", &str ) )
499        if( !tr_bencDictFindStr( meta, "created by", &str ) )
500            str = "";
501    tr_free( inf->creator );
502    inf->creator = tr_utf8clean( str, -1 );
503
504    /* creation date */
505    if( !tr_bencDictFindInt( meta, "creation date", &i ) )
506        i = 0;
507    inf->dateCreated = i;
508
509    /* private */
510    if( !tr_bencDictFindInt( infoDict, "private", &i ) )
511        if( !tr_bencDictFindInt( meta, "private", &i ) )
512            i = 0;
513    inf->isPrivate = i != 0;
514
515    /* piece length */
516    if( !isMagnet ) {
517        if( !tr_bencDictFindInt( infoDict, "piece length", &i ) || ( i < 1 ) )
518            return "piece length";
519        inf->pieceSize = i;
520    }
521
522    /* pieces */
523    if( !isMagnet ) {
524        if( !tr_bencDictFindRaw( infoDict, "pieces", &raw, &raw_len ) )
525            return "pieces";
526        if( raw_len % SHA_DIGEST_LENGTH )
527            return "pieces";
528        inf->pieceCount = raw_len / SHA_DIGEST_LENGTH;
529        inf->pieces = tr_new0( tr_piece, inf->pieceCount );
530        for( i = 0; i < inf->pieceCount; ++i )
531            memcpy( inf->pieces[i].hash, &raw[i * SHA_DIGEST_LENGTH],
532                    SHA_DIGEST_LENGTH );
533    }
534
535    /* files */
536    if( !isMagnet ) {
537        if( ( str = parseFiles( inf, tr_bencDictFind( infoDict, "files" ),
538                                     tr_bencDictFind( infoDict, "length" ) ) ) )
539            return str;
540        if( !inf->fileCount || !inf->totalSize )
541            return "files";
542        if( (uint64_t) inf->pieceCount !=
543           ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize )
544            return "files";
545    }
546
547    /* get announce or announce-list */
548    if( ( str = getannounce( inf, meta ) ) )
549        return str;
550
551    /* get the url-list */
552    geturllist( inf, meta );
553
554    /* filename of Transmission's copy */
555    tr_free( inf->torrent );
556    inf->torrent = session ?  getTorrentFilename( session, inf ) : NULL;
557
558    return NULL;
559}
560
561tr_bool
562tr_metainfoParse( const tr_session * session,
563                  const tr_benc    * meta_in,
564                  tr_info          * inf,
565                  tr_bool          * hasInfoDict,
566                  int              * infoDictLength )
567{
568    const char * badTag = tr_metainfoParseImpl( session,
569                                                inf,
570                                                hasInfoDict,
571                                                infoDictLength,
572                                                meta_in );
573    const tr_bool success = badTag == NULL;
574
575    if( badTag )
576    {
577        tr_nerr( inf->name, _( "Invalid metadata entry \"%s\"" ), badTag );
578        tr_metainfoFree( inf );
579    }
580
581    return success;
582}
583
584void
585tr_metainfoFree( tr_info * inf )
586{
587    int i;
588    tr_file_index_t ff;
589
590    for( i = 0; i < inf->webseedCount; ++i )
591        tr_free( inf->webseeds[i] );
592
593    for( ff = 0; ff < inf->fileCount; ++ff )
594        tr_free( inf->files[ff].name );
595
596    tr_free( inf->webseeds );
597    tr_free( inf->pieces );
598    tr_free( inf->files );
599    tr_free( inf->comment );
600    tr_free( inf->creator );
601    tr_free( inf->torrent );
602    tr_free( inf->name );
603
604    for( i = 0; i < inf->trackerCount; ++i )
605    {
606        tr_free( inf->trackers[i].announce );
607        tr_free( inf->trackers[i].scrape );
608    }
609    tr_free( inf->trackers );
610
611    memset( inf, '\0', sizeof( tr_info ) );
612}
613
614void
615tr_metainfoRemoveSaved( const tr_session * session,
616                        const tr_info *   inf )
617{
618    char * filename;
619
620    filename = getTorrentFilename( session, inf );
621    unlink( filename );
622    tr_free( filename );
623
624    filename = getOldTorrentFilename( session, inf );
625    unlink( filename );
626    tr_free( filename );
627}
Note: See TracBrowser for help on using the repository browser.