source: trunk/libtransmission/metainfo.c @ 6612

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

bencode cleanup: remove unused functions and unnecessary #includes

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