source: trunk/libtransmission/metainfo.c @ 10963

Last change on this file since 10963 was 10963, checked in by charles, 12 years ago

(2.0x trunk) #3397 "checksum errors when downloading files whose names are encoded in iso-8859-1" -- fixed

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