source: trunk/libtransmission/metainfo.c @ 7567

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

(trunk libT) Fix sparse warnings: symbol 'XXX' shadows an earlier one

  • Property svn:keywords set to Date Rev Author Id
File size: 14.3 KB
Line 
1/******************************************************************************
2 * $Id: metainfo.c 7567 2009-01-01 18:38:49Z charles $
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;
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; i < numTiers; ++i )
271        {
272            tr_benc * tier = tr_bencListChild( tiers, i );
273            const int tierSize = tr_bencListSize( tier );
274            for( j = 0; j < tierSize; ++j )
275            {
276                if( tr_bencGetStr( tr_bencListChild( tier, j ), &str ) )
277                {
278                    char * url = tr_strstrip( tr_strdup( str ) );
279                    if( tr_httpIsValidURL( url ) )
280                    {
281                        tr_tracker_info * t = trackers + trackerCount++;
282                        t->tier = i;
283                        t->announce = tr_strdup( url );
284                        t->scrape = announceToScrape( url );
285                    }
286                    tr_free( url );
287                }
288            }
289        }
290
291        /* did we use any of the tiers? */
292        if( !trackerCount )
293        {
294            tr_free( trackers );
295            trackers = NULL;
296        }
297    }
298
299    /* Regular announce value */
300    if( !trackerCount
301      && tr_bencDictFindStr( meta, "announce", &str ) )
302    {
303        char * url = tr_strstrip( tr_strdup( str ) );
304        if( tr_httpIsValidURL( url ) )
305        {
306            trackers = tr_new0( tr_tracker_info, 1 );
307            trackers[trackerCount].tier = 0;
308            trackers[trackerCount].announce = tr_strdup( url );
309            trackers[trackerCount++].scrape = announceToScrape( url );
310            /*fprintf( stderr, "single announce: [%s]\n", url );*/
311        }
312        tr_free( url );
313    }
314
315    inf->trackers = trackers;
316    inf->trackerCount = trackerCount;
317
318    return inf->trackerCount ? NULL : "announce";
319}
320
321static void
322geturllist( tr_info * inf,
323            tr_benc * meta )
324{
325    tr_benc * urls;
326
327    if( tr_bencDictFindList( meta, "url-list", &urls ) )
328    {
329        int          i;
330        const char * url;
331        const int    n = tr_bencListSize( urls );
332
333        inf->webseedCount = 0;
334        inf->webseeds = tr_new0( char*, n );
335
336        for( i = 0; i < n; ++i )
337            if( tr_bencGetStr( tr_bencListChild( urls, i ), &url ) )
338                inf->webseeds[inf->webseedCount++] = tr_strdup( url );
339    }
340}
341
342static const char*
343tr_metainfoParseImpl( const tr_session * session,
344                      tr_info *         inf,
345                      const tr_benc *   meta_in )
346{
347    int64_t         i;
348    size_t          raw_len;
349    const char *    str;
350    const uint8_t * raw;
351    tr_benc *       beInfo;
352    tr_benc *       meta = (tr_benc *) meta_in;
353
354    /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
355     * from the Metainfo file. Note that the value will be a bencoded
356     * dictionary, given the definition of the info key above. */
357    if( !tr_bencDictFindDict( meta, "info", &beInfo ) )
358        return "info";
359    else
360    {
361        int    len;
362        char * bstr = tr_bencSave( beInfo, &len );
363        tr_sha1( inf->hash, bstr, len, NULL );
364        tr_sha1_to_hex( inf->hashString, inf->hash );
365        tr_free( bstr );
366    }
367
368    /* name */
369    if( !tr_bencDictFindStr( beInfo, "name.utf-8", &str ) )
370        if( !tr_bencDictFindStr( beInfo, "name", &str ) )
371            str = "";
372    if( !str || !*str )
373        return "name";
374    tr_free( inf->name );
375    inf->name = tr_strdup( str );
376
377    /* comment */
378    if( !tr_bencDictFindStr( meta, "comment.utf-8", &str ) )
379        if( !tr_bencDictFindStr( meta, "comment", &str ) )
380            str = "";
381    tr_free( inf->comment );
382    inf->comment = tr_strdup( str );
383
384    /* created by */
385    if( !tr_bencDictFindStr( meta, "created by.utf-8", &str ) )
386        if( !tr_bencDictFindStr( meta, "created by", &str ) )
387            str = "";
388    tr_free( inf->creator );
389    inf->creator = tr_strdup( str );
390
391    /* creation date */
392    if( !tr_bencDictFindInt( meta, "creation date", &i ) )
393        i = 0;
394    inf->dateCreated = i;
395
396    /* private */
397    if( !tr_bencDictFindInt( beInfo, "private", &i ) )
398        if( !tr_bencDictFindInt( meta, "private", &i ) )
399            i = 0;
400    inf->isPrivate = i != 0;
401
402    /* piece length */
403    if( !tr_bencDictFindInt( beInfo, "piece length", &i ) || ( i < 1 ) )
404        return "piece length";
405    inf->pieceSize = i;
406
407    /* pieces */
408    if( !tr_bencDictFindRaw( beInfo, "pieces", &raw,
409                             &raw_len ) || ( raw_len % SHA_DIGEST_LENGTH ) )
410        return "pieces";
411    inf->pieceCount = raw_len / SHA_DIGEST_LENGTH;
412    inf->pieces = tr_new0( tr_piece, inf->pieceCount );
413    for( i = 0; i < inf->pieceCount; ++i )
414        memcpy( inf->pieces[i].hash, &raw[i * SHA_DIGEST_LENGTH],
415                SHA_DIGEST_LENGTH );
416
417    /* files */
418    if( ( str =
419             parseFiles( inf,
420                        tr_bencDictFind( beInfo,
421                                         "files" ),
422                        tr_bencDictFind( beInfo, "length" ) ) ) )
423        return str;
424    if( !inf->fileCount || !inf->totalSize )
425        return "files";
426    if( (uint64_t) inf->pieceCount !=
427       ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize )
428        return "files";
429
430    /* get announce or announce-list */
431    if( ( str = getannounce( inf, meta ) ) )
432        return str;
433
434    /* get the url-list */
435    geturllist( inf, meta );
436
437    /* filename of Transmission's copy */
438    tr_free( inf->torrent );
439    inf->torrent = getTorrentFilename( session, inf );
440
441    return NULL;
442}
443
444int
445tr_metainfoParse( const tr_session * session,
446                  tr_info *         inf,
447                  const tr_benc *   meta_in )
448{
449    const char * badTag = tr_metainfoParseImpl( session, inf, meta_in );
450
451    if( badTag )
452    {
453        tr_nerr( inf->name, _( "Invalid metadata entry \"%s\"" ), badTag );
454        tr_metainfoFree( inf );
455        return TR_EINVALID;
456    }
457    return 0;
458}
459
460void
461tr_metainfoFree( tr_info * inf )
462{
463    tr_file_index_t ff;
464    int             i;
465
466    for( i = 0; i < inf->webseedCount; ++i )
467        tr_free( inf->webseeds[i] );
468
469    for( ff = 0; ff < inf->fileCount; ++ff )
470        tr_free( inf->files[ff].name );
471
472    tr_free( inf->webseeds );
473    tr_free( inf->pieces );
474    tr_free( inf->files );
475    tr_free( inf->comment );
476    tr_free( inf->creator );
477    tr_free( inf->torrent );
478    tr_free( inf->name );
479
480    for( i = 0; i < inf->trackerCount; ++i )
481    {
482        tr_free( inf->trackers[i].announce );
483        tr_free( inf->trackers[i].scrape );
484    }
485    tr_free( inf->trackers );
486
487    memset( inf, '\0', sizeof( tr_info ) );
488}
489
490void
491tr_metainfoRemoveSaved( const tr_session * session,
492                        const tr_info *   inf )
493{
494    char * filename;
495
496    filename = getTorrentFilename( session, inf );
497    unlink( filename );
498    tr_free( filename );
499
500    filename = getOldTorrentFilename( session, inf );
501    unlink( filename );
502    tr_free( filename );
503}
504
Note: See TracBrowser for help on using the repository browser.