source: trunk/libtransmission/metainfo.c @ 11186

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

(trunk libT) #3519 "webseeds don't work" -- patch from gostrc to add support for a single string in the url-list

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