source: trunk/libtransmission/metainfo.c @ 6603

Last change on this file since 6603 was 6603, checked in by charles, 13 years ago

more metainfo cleanup

  • Property svn:keywords set to Date Rev Author Id
File size: 13.5 KB
Line 
1/******************************************************************************
2 * $Id: metainfo.c 6603 2008-08-20 21:01:17Z 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
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <unistd.h> /* unlink, stat */
32
33#include <event.h> /* struct evbuffer */
34
35#include "transmission.h"
36#include "bencode.h"
37#include "crypto.h" /* tr_sha1 */
38#include "metainfo.h"
39#include "platform.h"
40#include "utils.h"
41
42/***
43****
44***/
45
46static char*
47getTorrentFilename( const tr_handle  * handle,
48                    const tr_info    * inf )
49{
50    return tr_strdup_printf( "%s%c%s.%16.16s.torrent",
51                             tr_getTorrentDir( handle ),
52                             TR_PATH_DELIMITER,
53                             inf->name,
54                             inf->hashString );
55}
56
57static char*
58getOldTorrentFilename( const tr_handle * handle,
59                       const tr_info   * inf )
60{
61    char * ret;
62    struct evbuffer * buf = evbuffer_new( );
63
64    evbuffer_add_printf( buf, "%s%c%s", tr_getTorrentDir( handle ),
65                                        TR_PATH_DELIMITER,
66                                        inf->hashString );
67    if( handle->tag )
68        evbuffer_add_printf( buf, "-%s", handle->tag );
69
70    ret = tr_strndup( EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
71    evbuffer_free( buf );
72    return ret;
73}
74
75void
76tr_metainfoMigrate( tr_handle * handle,
77                    tr_info   * inf )
78{
79    struct stat new_sb;
80    char * new_name = getTorrentFilename( handle, inf );
81
82    if( stat( new_name, &new_sb ) || ( ( new_sb.st_mode & S_IFMT ) != S_IFREG ) )
83    {
84        char * old_name = getOldTorrentFilename( handle, inf );
85        size_t contentLen;
86        uint8_t * content;
87
88        tr_mkdirp( tr_getTorrentDir( handle ), 0777 );
89        if(( content = tr_loadFile( old_name, &contentLen )))
90        {
91            FILE * out;
92            errno = 0;
93            out = fopen( new_name, "wb+" );
94            if( !out )
95            {
96                tr_nerr( inf->name, _( "Couldn't create \"%1$s\": %2$s" ), new_name, tr_strerror( errno ) );
97            }
98            else
99            {
100                if( fwrite( content, sizeof( uint8_t ), contentLen, out ) == contentLen )
101                {
102                    tr_free( inf->torrent );
103                    inf->torrent = tr_strdup( new_name );
104                    tr_sessionSetTorrentFile( handle, inf->hashString, new_name );
105                    unlink( old_name );
106                }
107                fclose( out );
108            }
109        }
110
111        tr_free( content );
112        tr_free( old_name );
113    }
114
115    tr_free( new_name );
116}
117
118/***
119****
120***/
121
122static int
123getfile( char ** setme, const char * root, tr_benc * path )
124{
125    int err;
126
127    if( !tr_bencIsList( path ) )
128    {
129        err = TR_EINVALID;
130    }
131    else
132    {
133        struct evbuffer * buf = evbuffer_new( );
134        int n = tr_bencListSize( path );
135        int i;
136
137        evbuffer_add( buf, root, strlen( root ) );
138        for( i=0; i<n; ++i ) {
139            const char * str;
140            if( tr_bencGetStr( tr_bencListChild( path, i ), &str ) && strcmp( str, ".." ) ) {
141                evbuffer_add( buf, TR_PATH_DELIMITER_STR, 1 );
142                evbuffer_add( buf, str, strlen( str ) );
143            }
144        }
145
146        *setme = tr_strndup( EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
147        /* fprintf( stderr, "[%s]\n", *setme ); */
148        evbuffer_free( buf );
149        err = TR_OK;
150    }
151
152    return err;
153}
154
155static const char*
156parseFiles( tr_info * inf, tr_benc * files, tr_benc * length )
157{
158    tr_file_index_t i;
159
160    inf->totalSize = 0;
161
162    if( tr_bencIsList( files ) ) /* multi-file mode */
163    {
164        inf->isMultifile = 1;
165        inf->fileCount   = tr_bencListSize( files );
166        inf->files       = tr_new0( tr_file, inf->fileCount );
167
168        for( i=0; i<inf->fileCount; ++i )
169        {
170            tr_benc * file;
171            tr_benc * path;
172            int64_t length;
173
174            file = tr_bencListChild( files, i );
175            if( !tr_bencIsDict( file ) )
176                return "files";
177
178            if( !tr_bencDictFindList( file, "path.utf-8", &path ) )
179                if( !tr_bencDictFindList( file, "path", &path ) )
180                    return "path";
181
182            if( getfile( &inf->files[i].name, inf->name, path ) )
183                return "path";
184
185            if( !tr_bencDictFindInt( file, "length", &length ) )
186                return "length";
187
188            inf->files[i].length = length;
189            inf->totalSize      += length;
190        }
191    }
192    else if( tr_bencIsInt( length ) ) /* single-file mode */
193    {
194        inf->isMultifile      = 0;
195        inf->fileCount        = 1;
196        inf->files            = tr_new0( tr_file, 1 );
197        inf->files[0].name    = tr_strdup( inf->name );
198        inf->files[0].length  = length->val.i;
199        inf->totalSize       += length->val.i;
200    }
201    else
202    {
203        return "length";
204    }
205
206    return NULL;
207}
208
209static char *
210announceToScrape( const char * announce )
211{
212    char * scrape = NULL;
213    const char * s;
214
215    /* To derive the scrape URL use the following steps:
216     * Begin with the announce URL. Find the last '/' in it.
217     * If the text immediately following that '/' isn't 'announce'
218     * it will be taken as a sign that that tracker doesn't support
219     * the scrape convention. If it does, substitute 'scrape' for
220     * 'announce' to find the scrape page.  */
221    if((( s = strrchr( announce, '/' ))) && !strncmp( ++s, "announce", 8 ))
222    {
223        struct evbuffer * buf = evbuffer_new( );
224        evbuffer_add( buf, announce, s-announce );
225        evbuffer_add( buf, "scrape", 6 );
226        evbuffer_add_printf( buf, "%s", s+8 );
227        scrape = tr_strdup( ( char * ) EVBUFFER_DATA( buf ) );
228        evbuffer_free( buf );
229    }
230
231    return scrape;
232}
233
234static const char*
235getannounce( tr_info * inf, tr_benc * meta )
236{
237    const char * str;
238    tr_tracker_info * trackers = NULL;
239    int trackerCount = 0;
240    tr_benc * tiers;
241
242    /* Announce-list */
243    if( tr_bencDictFindList( meta, "announce-list", &tiers ) )
244    {
245        int n;
246        int i, j;
247
248        n = 0;
249        for( i=0; i<tiers->val.l.count; ++i )
250            n += tiers->val.l.vals[i].val.l.count;
251
252        trackers = tr_new0( tr_tracker_info, n );
253        trackerCount = 0;
254
255        for( i=0; i<tiers->val.l.count; ++i ) {
256            const tr_benc * tier = &tiers->val.l.vals[i];
257            for( j=0; tr_bencIsList(tier) && j<tier->val.l.count; ++j ) {
258                const tr_benc * a = &tier->val.l.vals[j];
259                if( tr_bencIsString( a ) && tr_httpIsValidURL( a->val.s.s ) ) {
260                    tr_tracker_info * t = trackers + trackerCount++;
261                    t->tier = i;
262                    t->announce = tr_strndup( a->val.s.s, a->val.s.i );
263                    t->scrape = announceToScrape( a->val.s.s );
264                    /*fprintf( stderr, "tier %d: %s\n", i, a->val.s.s );*/
265                }
266            }
267        }
268
269        /* did we use any of the tiers? */
270        if( !trackerCount ) {
271            tr_free( trackers );
272            trackers = NULL;
273        }
274    }
275
276    /* Regular announce value */
277    if( !trackerCount
278        && tr_bencDictFindStr( meta, "announce", &str )
279        && tr_httpIsValidURL( str ) )
280    {
281        trackers = tr_new0( tr_tracker_info, 1 );
282        trackers[trackerCount].tier = 0;
283        trackers[trackerCount].announce = tr_strdup( str );
284        trackers[trackerCount++].scrape = announceToScrape( str );
285        /*fprintf( stderr, "single announce: [%s]\n", str );*/
286    }
287
288    inf->trackers = trackers;
289    inf->trackerCount = trackerCount;
290
291    return inf->trackerCount ? NULL : "announce";
292}
293
294static void
295geturllist( tr_info * inf, tr_benc * meta )
296{
297    benc_val_t * urls;
298
299    if( tr_bencDictFindList( meta, "url-list", &urls ) )
300    {
301        int i;
302        const char * url;
303        const int n = tr_bencListSize( urls );
304
305        inf->webseedCount = 0;
306        inf->webseeds = tr_new0( char*, n );
307
308        for( i=0; i<n; ++i )
309            if( tr_bencGetStr( tr_bencListChild( urls, i ), &url ) )
310                inf->webseeds[inf->webseedCount++] = tr_strdup( url );
311    }
312}
313
314static const char*
315tr_metainfoParseImpl( const tr_handle  * handle,
316                      tr_info          * inf,
317                      const tr_benc    * meta_in )
318{
319    int64_t i;
320    size_t raw_len;
321    const char * str;
322    const uint8_t * raw;
323    tr_benc * beInfo;
324    tr_benc * meta = (tr_benc *) meta_in;
325
326    /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
327     * from the Metainfo file. Note that the value will be a bencoded
328     * dictionary, given the definition of the info key above. */
329    if( !tr_bencDictFindDict( meta, "info", &beInfo ) )
330        return "info";
331    else {
332        int len;
333        char * str = tr_bencSave( beInfo, &len );
334        tr_sha1( inf->hash, str, len, NULL );
335        tr_sha1_to_hex( inf->hashString, inf->hash );
336        tr_free( str );
337    }
338
339    /* name */
340    if( !tr_bencDictFindStr( beInfo, "name.utf-8", &str ) )
341        if( !tr_bencDictFindStr( beInfo, "name", &str ) )
342            str = "";
343    if( !str || !*str )
344        return "name";
345    tr_free( inf->name );
346    inf->name = tr_strdup( str );
347
348    /* comment */
349    if( !tr_bencDictFindStr( meta, "comment.utf-8", &str ) )
350        if( !tr_bencDictFindStr( meta, "comment", &str ) )
351            str = "";
352    tr_free( inf->comment );
353    inf->comment = tr_strdup( str );
354   
355    /* created by */
356    if( !tr_bencDictFindStr( meta, "created by.utf-8", &str ) )
357        if( !tr_bencDictFindStr( meta, "created by", &str ) )
358            str = "";
359    tr_free( inf->creator );
360    inf->creator = tr_strdup( str );
361   
362    /* creation date */
363    if( !tr_bencDictFindInt( meta, "creation date", &i ) )
364        i = 0;
365    inf->dateCreated = i;
366   
367    /* private */
368    if( !tr_bencDictFindInt( beInfo, "private", &i ) )
369        if( !tr_bencDictFindInt( meta, "private", &i ) )
370            i = 0;
371    inf->isPrivate = i != 0;
372   
373    /* piece length */
374    if( !tr_bencDictFindInt( beInfo, "piece length", &i ) || ( i < 1 ) )
375        return "piece length";
376    inf->pieceSize = i;
377
378    /* pieces */
379    if( !tr_bencDictFindRaw( beInfo, "pieces", &raw, &raw_len ) || ( raw_len % SHA_DIGEST_LENGTH ) )
380        return "pieces";
381    inf->pieceCount = raw_len / SHA_DIGEST_LENGTH;
382    inf->pieces = tr_new0( tr_piece, inf->pieceCount );
383    for ( i=0; i<inf->pieceCount; ++i )
384        memcpy( inf->pieces[i].hash, &raw[i*SHA_DIGEST_LENGTH], SHA_DIGEST_LENGTH );
385
386    /* files */
387    if(( str = parseFiles( inf, tr_bencDictFind( beInfo, "files" ), tr_bencDictFind( beInfo, "length" ))))
388        return str;
389    if( !inf->fileCount || !inf->totalSize )
390        return "files";
391    if( (uint64_t) inf->pieceCount != ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize )
392        return "files";
393
394    /* get announce or announce-list */
395    if(( str = getannounce( inf, meta ) ))
396        return str;
397
398    /* get the url-list */
399    geturllist( inf, meta );
400
401    /* filename of Transmission's copy */
402    tr_free( inf->torrent );
403    inf->torrent = getTorrentFilename( handle, inf );
404
405    return NULL;
406}
407
408int
409tr_metainfoParse( const tr_handle  * handle,
410                  tr_info          * inf,
411                  const tr_benc    * meta_in )
412{
413    const char * badTag = tr_metainfoParseImpl( handle, inf, meta_in );
414    if( badTag )
415    {
416        tr_nerr( inf->name, _( "Invalid metadata entry \"%s\"" ), badTag );
417        tr_metainfoFree( inf );
418        return TR_EINVALID;
419    }
420    return TR_OK;
421}
422
423void
424tr_metainfoFree( tr_info * inf )
425{
426    tr_file_index_t ff;
427    int i;
428
429    for( i=0; i<inf->webseedCount; ++i )
430        tr_free( inf->webseeds[i] );
431
432    for( ff=0; ff<inf->fileCount; ++ff )
433        tr_free( inf->files[ff].name );
434
435    tr_free( inf->webseeds );
436    tr_free( inf->pieces );
437    tr_free( inf->files );
438    tr_free( inf->comment );
439    tr_free( inf->creator );
440    tr_free( inf->torrent );
441    tr_free( inf->name );
442   
443    for( i=0; i<inf->trackerCount; ++i ) {
444        tr_free( inf->trackers[i].announce );
445        tr_free( inf->trackers[i].scrape );
446    }
447    tr_free( inf->trackers );
448
449    memset( inf, '\0', sizeof(tr_info) );
450}
451
452void
453tr_metainfoRemoveSaved( const tr_handle * handle,
454                        const tr_info   * inf )
455{
456    char * filename;
457
458    filename = getTorrentFilename( handle, inf );
459    unlink( filename );
460    tr_free( filename );
461
462    filename = getOldTorrentFilename( handle, inf );
463    unlink( filename );
464    tr_free( filename );
465}
Note: See TracBrowser for help on using the repository browser.