source: trunk/libtransmission/metainfo.c @ 11189

Last change on this file since 11189 was 11189, checked in by livings124, 12 years ago

#3535 remove unnecessary escape characters and use lowercase in escaped letters in info_hash

  • Property svn:keywords set to Date Rev Author Id
File size: 17.8 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 11189 2010-08-31 11:49:09Z livings124 $
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        || ch == '.'
392        || ch == '-'
393        || ch == '_'
394        || ch == '~';
395}
396
397static void
398escape( char * out, const uint8_t * in, size_t in_len ) /* rfc2396 */
399{
400    const uint8_t *end = in + in_len;
401
402    while( in != end )
403        if( is_rfc2396_alnum( *in ) )
404            *out++ = (char) *in++;
405        else
406            out += tr_snprintf( out, 4, "%%%02x", (unsigned int)*in++ );
407
408    *out = '\0';
409}
410
411static const char*
412tr_metainfoParseImpl( const tr_session  * session,
413                      tr_info           * inf,
414                      tr_bool           * hasInfoDict,
415                      int               * infoDictLength,
416                      const tr_benc     * meta_in )
417{
418    int64_t         i;
419    size_t          raw_len;
420    const char *    str;
421    const uint8_t * raw;
422    tr_benc *       d;
423    tr_benc *       infoDict = NULL;
424    tr_benc *       meta = (tr_benc *) meta_in;
425    tr_bool         b;
426    tr_bool         isMagnet = FALSE;
427
428    /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
429     * from the Metainfo file. Note that the value will be a bencoded
430     * dictionary, given the definition of the info key above. */
431    b = tr_bencDictFindDict( meta, "info", &infoDict );
432    if( hasInfoDict != NULL )
433        *hasInfoDict = b;
434    if( !b )
435    {
436        /* no info dictionary... is this a magnet link? */
437        if( tr_bencDictFindDict( meta, "magnet-info", &d ) )
438        {
439            isMagnet = TRUE;
440
441            /* get the info-hash */
442            if( !tr_bencDictFindRaw( d, "info_hash", &raw, &raw_len ) )
443                return "info_hash";
444            if( raw_len != SHA_DIGEST_LENGTH )
445                return "info_hash";
446            memcpy( inf->hash, raw, raw_len );
447            tr_sha1_to_hex( inf->hashString, inf->hash );
448            escape( inf->hashEscaped, inf->hash, SHA_DIGEST_LENGTH );
449
450            /* maybe get the display name */
451         
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.