source: trunk/libtransmission/metainfo.c @ 7598

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

only increment the tier count for valid tiers

  • Property svn:keywords set to Date Rev Author Id
File size: 14.5 KB
Line 
1/******************************************************************************
2 * $Id: metainfo.c 7598 2009-01-03 21:27:48Z livings124 $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <assert.h>
26#include <errno.h>
27#include <stdio.h>
28#include <string.h>
29
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <unistd.h> /* unlink, stat */
33
34#include <event.h> /* struct evbuffer */
35
36#include "transmission.h"
37#include "session.h"
38#include "bencode.h"
39#include "crypto.h" /* tr_sha1 */
40#include "metainfo.h"
41#include "platform.h"
42#include "utils.h"
43
44/***
45****
46***/
47
48static char*
49getTorrentFilename( const tr_session * session,
50                    const tr_info *   inf )
51{
52    return tr_strdup_printf( "%s%c%s.%16.16s.torrent",
53                             tr_getTorrentDir( session ),
54                             TR_PATH_DELIMITER,
55                             inf->name,
56                             inf->hashString );
57}
58
59static char*
60getOldTorrentFilename( const tr_session * session,
61                       const tr_info *   inf )
62{
63    char *            ret;
64    struct evbuffer * buf = tr_getBuffer( );
65
66    evbuffer_add_printf( buf, "%s%c%s", tr_getTorrentDir( session ),
67                         TR_PATH_DELIMITER,
68                         inf->hashString );
69    if( session->tag )
70        evbuffer_add_printf( buf, "-%s", session->tag );
71
72    ret = tr_strndup( EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
73    tr_releaseBuffer( buf );
74    return ret;
75}
76
77void
78tr_metainfoMigrate( tr_session * session,
79                    tr_info *   inf )
80{
81    struct stat new_sb;
82    char *      name = getTorrentFilename( session, inf );
83
84    if( stat( name, &new_sb ) || ( ( new_sb.st_mode & S_IFMT ) != S_IFREG ) )
85    {
86        char *    old_name = getOldTorrentFilename( session, inf );
87        size_t    contentLen;
88        uint8_t * content;
89
90        tr_mkdirp( tr_getTorrentDir( session ), 0777 );
91        if( ( content = tr_loadFile( old_name, &contentLen ) ) )
92        {
93            FILE * out;
94            errno = 0;
95            out = fopen( name, "wb+" );
96            if( !out )
97            {
98                tr_nerr( inf->name, _( "Couldn't create \"%1$s\": %2$s" ),
99                        name, tr_strerror( errno ) );
100            }
101            else
102            {
103                if( fwrite( content, sizeof( uint8_t ), contentLen, out )
104                    == contentLen )
105                {
106                    tr_free( inf->torrent );
107                    inf->torrent = tr_strdup( name );
108                    tr_sessionSetTorrentFile( session, inf->hashString, name );
109                    unlink( old_name );
110                }
111                fclose( out );
112            }
113        }
114
115        tr_free( content );
116        tr_free( old_name );
117    }
118
119    tr_free( name );
120}
121
122/***
123****
124***/
125
126static int
127getfile( char **      setme,
128         const char * root,
129         tr_benc *    path )
130{
131    int err;
132
133    if( !tr_bencIsList( path ) )
134    {
135        err = TR_EINVALID;
136    }
137    else
138    {
139        struct evbuffer * buf = tr_getBuffer( );
140        int               n = tr_bencListSize( path );
141        int               i;
142
143        evbuffer_add( buf, root, strlen( root ) );
144        for( i = 0; i < n; ++i )
145        {
146            const char * str;
147            if( tr_bencGetStr( tr_bencListChild( path, i ), &str )
148              && strcmp( str, ".." ) )
149            {
150                evbuffer_add( buf, TR_PATH_DELIMITER_STR, 1 );
151                evbuffer_add( buf, str, strlen( str ) );
152            }
153        }
154
155        *setme = tr_strndup( EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
156        /* fprintf( stderr, "[%s]\n", *setme ); */
157        tr_releaseBuffer( buf );
158        err = 0;
159    }
160
161    return err;
162}
163
164static const char*
165parseFiles( tr_info *       inf,
166            tr_benc *       files,
167            const tr_benc * length )
168{
169    int64_t len;
170
171    inf->totalSize = 0;
172
173    if( tr_bencIsList( files ) ) /* multi-file mode */
174    {
175        tr_file_index_t i;
176
177        inf->isMultifile = 1;
178        inf->fileCount   = tr_bencListSize( files );
179        inf->files       = tr_new0( tr_file, inf->fileCount );
180
181        for( i = 0; i < inf->fileCount; ++i )
182        {
183            tr_benc * file;
184            tr_benc * path;
185
186            file = tr_bencListChild( files, i );
187            if( !tr_bencIsDict( file ) )
188                return "files";
189
190            if( !tr_bencDictFindList( file, "path.utf-8", &path ) )
191                if( !tr_bencDictFindList( file, "path", &path ) )
192                    return "path";
193
194            if( getfile( &inf->files[i].name, inf->name, path ) )
195                return "path";
196
197            if( !tr_bencDictFindInt( file, "length", &len ) )
198                return "length";
199
200            inf->files[i].length = len;
201            inf->totalSize      += len;
202        }
203    }
204    else if( tr_bencGetInt( length, &len ) ) /* single-file mode */
205    {
206        inf->isMultifile      = 0;
207        inf->fileCount        = 1;
208        inf->files            = tr_new0( tr_file, 1 );
209        inf->files[0].name    = tr_strdup( inf->name );
210        inf->files[0].length  = len;
211        inf->totalSize       += len;
212    }
213    else
214    {
215        return "length";
216    }
217
218    return NULL;
219}
220
221static char *
222announceToScrape( const char * announce )
223{
224    char *       scrape = NULL;
225    const char * s;
226
227    /* To derive the scrape URL use the following steps:
228     * Begin with the announce URL. Find the last '/' in it.
229     * If the text immediately following that '/' isn't 'announce'
230     * it will be taken as a sign that that tracker doesn't support
231     * the scrape convention. If it does, substitute 'scrape' for
232     * 'announce' to find the scrape page.  */
233    if( ( ( s =
234               strrchr( announce, '/' ) ) ) && !strncmp( ++s, "announce", 8 ) )
235    {
236        struct evbuffer * buf = tr_getBuffer( );
237        evbuffer_add( buf, announce, s - announce );
238        evbuffer_add( buf, "scrape", 6 );
239        evbuffer_add_printf( buf, "%s", s + 8 );
240        scrape = tr_strdup( EVBUFFER_DATA( buf ) );
241        tr_releaseBuffer( buf );
242    }
243
244    return scrape;
245}
246
247static const char*
248getannounce( tr_info * inf,
249             tr_benc * meta )
250{
251    const char *      str;
252    tr_tracker_info * trackers = NULL;
253    int               trackerCount = 0;
254    tr_benc *         tiers;
255
256    /* Announce-list */
257    if( tr_bencDictFindList( meta, "announce-list", &tiers ) )
258    {
259        int       n;
260        int       i, j, validTiers;
261        const int numTiers = tr_bencListSize( tiers );
262
263        n = 0;
264        for( i = 0; i < numTiers; ++i )
265            n += tr_bencListSize( tr_bencListChild( tiers, i ) );
266
267        trackers = tr_new0( tr_tracker_info, n );
268        trackerCount = 0;
269
270        for( i = 0, validTiers = 0; i < numTiers; ++i )
271        {
272            tr_benc * tier = tr_bencListChild( tiers, i );
273            const int tierSize = tr_bencListSize( tier );
274            tr_bool anyAdded = FALSE;
275            for( j = 0; j < tierSize; ++j )
276            {
277                if( tr_bencGetStr( tr_bencListChild( tier, j ), &str ) )
278                {
279                    char * url = tr_strstrip( tr_strdup( str ) );
280                    if( tr_httpIsValidURL( url ) )
281                    {
282                        tr_tracker_info * t = trackers + trackerCount++;
283                        t->tier = validTiers;
284                        t->announce = tr_strdup( url );
285                        t->scrape = announceToScrape( url );
286                       
287                        anyAdded = TRUE;
288                    }
289                    tr_free( url );
290                }
291            }
292           
293            if( anyAdded )
294                ++validTiers;
295        }
296
297        /* did we use any of the tiers? */
298        if( !trackerCount )
299        {
300            tr_free( trackers );
301            trackers = NULL;
302        }
303    }
304
305    /* Regular announce value */
306    if( !trackerCount
307      && tr_bencDictFindStr( meta, "announce", &str ) )
308    {
309        char * url = tr_strstrip( tr_strdup( str ) );
310        if( tr_httpIsValidURL( url ) )
311        {
312            trackers = tr_new0( tr_tracker_info, 1 );
313            trackers[trackerCount].tier = 0;
314            trackers[trackerCount].announce = tr_strdup( url );
315            trackers[trackerCount++].scrape = announceToScrape( url );
316            /*fprintf( stderr, "single announce: [%s]\n", url );*/
317        }
318        tr_free( url );
319    }
320
321    inf->trackers = trackers;
322    inf->trackerCount = trackerCount;
323
324    return inf->trackerCount ? NULL : "announce";
325}
326
327static void
328geturllist( tr_info * inf,
329            tr_benc * meta )
330{
331    tr_benc * urls;
332
333    if( tr_bencDictFindList( meta, "url-list", &urls ) )
334    {
335        int          i;
336        const char * url;
337        const int    n = tr_bencListSize( urls );
338
339        inf->webseedCount = 0;
340        inf->webseeds = tr_new0( char*, n );
341
342        for( i = 0; i < n; ++i )
343            if( tr_bencGetStr( tr_bencListChild( urls, i ), &url ) )
344                inf->webseeds[inf->webseedCount++] = tr_strdup( url );
345    }
346}
347
348static const char*
349tr_metainfoParseImpl( const tr_session * session,
350                      tr_info *         inf,
351                      const tr_benc *   meta_in )
352{
353    int64_t         i;
354    size_t          raw_len;
355    const char *    str;
356    const uint8_t * raw;
357    tr_benc *       beInfo;
358    tr_benc *       meta = (tr_benc *) meta_in;
359
360    /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
361     * from the Metainfo file. Note that the value will be a bencoded
362     * dictionary, given the definition of the info key above. */
363    if( !tr_bencDictFindDict( meta, "info", &beInfo ) )
364        return "info";
365    else
366    {
367        int    len;
368        char * bstr = tr_bencSave( beInfo, &len );
369        tr_sha1( inf->hash, bstr, len, NULL );
370        tr_sha1_to_hex( inf->hashString, inf->hash );
371        tr_free( bstr );
372    }
373
374    /* name */
375    if( !tr_bencDictFindStr( beInfo, "name.utf-8", &str ) )
376        if( !tr_bencDictFindStr( beInfo, "name", &str ) )
377            str = "";
378    if( !str || !*str )
379        return "name";
380    tr_free( inf->name );
381    inf->name = tr_strdup( str );
382
383    /* comment */
384    if( !tr_bencDictFindStr( meta, "comment.utf-8", &str ) )
385        if( !tr_bencDictFindStr( meta, "comment", &str ) )
386            str = "";
387    tr_free( inf->comment );
388    inf->comment = tr_strdup( str );
389
390    /* created by */
391    if( !tr_bencDictFindStr( meta, "created by.utf-8", &str ) )
392        if( !tr_bencDictFindStr( meta, "created by", &str ) )
393            str = "";
394    tr_free( inf->creator );
395    inf->creator = tr_strdup( str );
396
397    /* creation date */
398    if( !tr_bencDictFindInt( meta, "creation date", &i ) )
399        i = 0;
400    inf->dateCreated = i;
401
402    /* private */
403    if( !tr_bencDictFindInt( beInfo, "private", &i ) )
404        if( !tr_bencDictFindInt( meta, "private", &i ) )
405            i = 0;
406    inf->isPrivate = i != 0;
407
408    /* piece length */
409    if( !tr_bencDictFindInt( beInfo, "piece length", &i ) || ( i < 1 ) )
410        return "piece length";
411    inf->pieceSize = i;
412
413    /* pieces */
414    if( !tr_bencDictFindRaw( beInfo, "pieces", &raw,
415                             &raw_len ) || ( raw_len % SHA_DIGEST_LENGTH ) )
416        return "pieces";
417    inf->pieceCount = raw_len / SHA_DIGEST_LENGTH;
418    inf->pieces = tr_new0( tr_piece, inf->pieceCount );
419    for( i = 0; i < inf->pieceCount; ++i )
420        memcpy( inf->pieces[i].hash, &raw[i * SHA_DIGEST_LENGTH],
421                SHA_DIGEST_LENGTH );
422
423    /* files */
424    if( ( str =
425             parseFiles( inf,
426                        tr_bencDictFind( beInfo,
427                                         "files" ),
428                        tr_bencDictFind( beInfo, "length" ) ) ) )
429        return str;
430    if( !inf->fileCount || !inf->totalSize )
431        return "files";
432    if( (uint64_t) inf->pieceCount !=
433       ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize )
434        return "files";
435
436    /* get announce or announce-list */
437    if( ( str = getannounce( inf, meta ) ) )
438        return str;
439
440    /* get the url-list */
441    geturllist( inf, meta );
442
443    /* filename of Transmission's copy */
444    tr_free( inf->torrent );
445    inf->torrent = getTorrentFilename( session, inf );
446
447    return NULL;
448}
449
450int
451tr_metainfoParse( const tr_session * session,
452                  tr_info *         inf,
453                  const tr_benc *   meta_in )
454{
455    const char * badTag = tr_metainfoParseImpl( session, inf, meta_in );
456
457    if( badTag )
458    {
459        tr_nerr( inf->name, _( "Invalid metadata entry \"%s\"" ), badTag );
460        tr_metainfoFree( inf );
461        return TR_EINVALID;
462    }
463    return 0;
464}
465
466void
467tr_metainfoFree( tr_info * inf )
468{
469    tr_file_index_t ff;
470    int             i;
471
472    for( i = 0; i < inf->webseedCount; ++i )
473        tr_free( inf->webseeds[i] );
474
475    for( ff = 0; ff < inf->fileCount; ++ff )
476        tr_free( inf->files[ff].name );
477
478    tr_free( inf->webseeds );
479    tr_free( inf->pieces );
480    tr_free( inf->files );
481    tr_free( inf->comment );
482    tr_free( inf->creator );
483    tr_free( inf->torrent );
484    tr_free( inf->name );
485
486    for( i = 0; i < inf->trackerCount; ++i )
487    {
488        tr_free( inf->trackers[i].announce );
489        tr_free( inf->trackers[i].scrape );
490    }
491    tr_free( inf->trackers );
492
493    memset( inf, '\0', sizeof( tr_info ) );
494}
495
496void
497tr_metainfoRemoveSaved( const tr_session * session,
498                        const tr_info *   inf )
499{
500    char * filename;
501
502    filename = getTorrentFilename( session, inf );
503    unlink( filename );
504    tr_free( filename );
505
506    filename = getOldTorrentFilename( session, inf );
507    unlink( filename );
508    tr_free( filename );
509}
510
Note: See TracBrowser for help on using the repository browser.