source: trunk/libtransmission/metainfo.c @ 10502

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

(trunk libT) #3136 "slashes in magnet names" -- fixed in trunk for 2.00

  • Property svn:keywords set to Date Rev Author Id
File size: 18.0 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 10502 2010-04-20 23:14:00Z 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 ), NULL );
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               * infoDictOffset,
406                      int               * infoDictLength,
407                      const tr_benc     * meta_in )
408{
409    int64_t         i;
410    size_t          raw_len;
411    const char *    str;
412    const uint8_t * raw;
413    tr_benc *       d;
414    tr_benc *       infoDict = NULL;
415    tr_benc *       meta = (tr_benc *) meta_in;
416    tr_bool         err;
417    tr_bool         b;
418    tr_bool         isMagnet = FALSE;
419
420    /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
421     * from the Metainfo file. Note that the value will be a bencoded
422     * dictionary, given the definition of the info key above. */
423    b = tr_bencDictFindDict( meta, "info", &infoDict );
424    if( hasInfoDict != NULL )
425        *hasInfoDict = b;
426    if( !b )
427    {
428        /* no info dictionary... is this a magnet link? */
429        if( tr_bencDictFindDict( meta, "magnet-info", &d ) )
430        {
431            isMagnet = TRUE;
432
433            /* get the info-hash */
434            if( !tr_bencDictFindRaw( d, "info_hash", &raw, &raw_len ) )
435                return "info_hash";
436            if( raw_len != SHA_DIGEST_LENGTH )
437                return "info_hash";
438            memcpy( inf->hash, raw, raw_len );
439            tr_sha1_to_hex( inf->hashString, inf->hash );
440            escape( inf->hashEscaped, inf->hash, SHA_DIGEST_LENGTH );
441
442            /* maybe get the display name */
443            if( tr_bencDictFindStr( d, "display-name", &str ) ) {
444                tr_free( inf->name );
445                inf->name = tr_strdup( str );
446            }
447        }
448        else /* not a magnet link and has no info dict... */
449        {
450            return "info";
451        }
452    }
453    else
454    {
455        int len;
456        char * bstr = tr_bencToStr( infoDict, TR_FMT_BENC, &len );
457        tr_sha1( inf->hash, bstr, len, NULL );
458        tr_sha1_to_hex( inf->hashString, inf->hash );
459        escape( inf->hashEscaped, inf->hash, SHA_DIGEST_LENGTH );
460
461        if( infoDictLength != NULL )
462            *infoDictLength = len;
463
464        if( infoDictOffset != NULL )
465        {
466            int mlen = 0;
467            char * mstr = tr_bencToStr( meta_in, TR_FMT_BENC, &mlen );
468            const char * offset = tr_memmem( mstr, mlen, bstr, len );
469            if( offset != NULL )
470                *infoDictOffset = offset - mstr;
471            tr_free( mstr );
472            if( offset == NULL )
473                return "info";
474        }
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, &err );
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, &err );
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, &err );
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              * infoDictOffset,
567                  int              * infoDictLength )
568{
569    const char * badTag = tr_metainfoParseImpl( session,
570                                                inf,
571                                                hasInfoDict,
572                                                infoDictOffset,
573                                                infoDictLength,
574                                                meta_in );
575    const tr_bool success = badTag == NULL;
576
577    if( badTag )
578    {
579        tr_nerr( inf->name, _( "Invalid metadata entry \"%s\"" ), badTag );
580        tr_metainfoFree( inf );
581    }
582
583    return success;
584}
585
586void
587tr_metainfoFree( tr_info * inf )
588{
589    int i;
590    tr_file_index_t ff;
591
592    for( i = 0; i < inf->webseedCount; ++i )
593        tr_free( inf->webseeds[i] );
594
595    for( ff = 0; ff < inf->fileCount; ++ff )
596        tr_free( inf->files[ff].name );
597
598    tr_free( inf->webseeds );
599    tr_free( inf->pieces );
600    tr_free( inf->files );
601    tr_free( inf->comment );
602    tr_free( inf->creator );
603    tr_free( inf->torrent );
604    tr_free( inf->name );
605
606    for( i = 0; i < inf->trackerCount; ++i )
607    {
608        tr_free( inf->trackers[i].announce );
609        tr_free( inf->trackers[i].scrape );
610    }
611    tr_free( inf->trackers );
612
613    memset( inf, '\0', sizeof( tr_info ) );
614}
615
616void
617tr_metainfoRemoveSaved( const tr_session * session,
618                        const tr_info *   inf )
619{
620    char * filename;
621
622    filename = getTorrentFilename( session, inf );
623    unlink( filename );
624    tr_free( filename );
625
626    filename = getOldTorrentFilename( session, inf );
627    unlink( filename );
628    tr_free( filename );
629}
Note: See TracBrowser for help on using the repository browser.