source: branches/1.2x/libtransmission/metainfo.c @ 5889

Last change on this file since 5889 was 5889, checked in by charles, 14 years ago

#960: backport the crash-on-startup fix from trunk's r5852

  • Property svn:keywords set to Date Rev Author Id
File size: 17.5 KB
Line 
1/******************************************************************************
2 * $Id: metainfo.c 5889 2008-05-21 20:03:31Z 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 <ctype.h> /* isspace */
27#include <errno.h>
28#include <stdio.h>
29#include <stdlib.h>
30
31#include <sys/types.h>
32#include <sys/stat.h>
33#include <unistd.h> /* unlink, stat */
34
35#include <event.h> /* struct evbuffer */
36
37#include "transmission.h"
38#include "bencode.h"
39#include "crypto.h" /* tr_sha1 */
40#include "metainfo.h"
41#include "platform.h"
42#include "trcompat.h" /* strlcpy */
43#include "utils.h"
44
45/***********************************************************************
46 * Local prototypes
47 **********************************************************************/
48static int parseFiles( tr_info * inf, tr_benc * name,
49                       tr_benc * files, tr_benc * length );
50
51/***
52****
53***/
54
55#define WANTBYTES( want, got ) \
56    if( (want) > (got) ) { return; } else { (got) -= (want); }
57static void
58strlcat_utf8( void * dest, const void * src, size_t len, char skip )
59{
60    char       * s      = dest;
61    const char * append = src;
62    const char * p;
63
64    /* don't overwrite the nul at the end */
65    len--;
66
67    /* Go to the end of the destination string */
68    while( s[0] )
69    {
70        s++;
71        len--;
72    }
73
74    /* Now start appending, converting on the fly if necessary */
75    for( p = append; p[0]; )
76    {
77        /* skip over the requested character */
78        if( skip == p[0] )
79        {
80            p++;
81            continue;
82        }
83
84        if( !( p[0] & 0x80 ) )
85        {
86            /* ASCII character */
87            WANTBYTES( 1, len );
88            *(s++) = *(p++);
89            continue;
90        }
91
92        if( ( p[0] & 0xE0 ) == 0xC0 && ( p[1] & 0xC0 ) == 0x80 )
93        {
94            /* 2-bytes UTF-8 character */
95            WANTBYTES( 2, len );
96            *(s++) = *(p++); *(s++) = *(p++);
97            continue;
98        }
99
100        if( ( p[0] & 0xF0 ) == 0xE0 && ( p[1] & 0xC0 ) == 0x80 &&
101            ( p[2] & 0xC0 ) == 0x80 )
102        {
103            /* 3-bytes UTF-8 character */
104            WANTBYTES( 3, len );
105            *(s++) = *(p++); *(s++) = *(p++);
106            *(s++) = *(p++);
107            continue;
108        }
109
110        if( ( p[0] & 0xF8 ) == 0xF0 && ( p[1] & 0xC0 ) == 0x80 &&
111            ( p[2] & 0xC0 ) == 0x80 && ( p[3] & 0xC0 ) == 0x80 )
112        {
113            /* 4-bytes UTF-8 character */
114            WANTBYTES( 4, len );
115            *(s++) = *(p++); *(s++) = *(p++);
116            *(s++) = *(p++); *(s++) = *(p++);
117            continue;
118        }
119
120        /* ISO 8859-1 -> UTF-8 conversion */
121        WANTBYTES( 2, len );
122        *(s++) = 0xC0 | ( ( *p & 0xFF ) >> 6 );
123        *(s++) = 0x80 | ( *(p++) & 0x3F );
124    }
125}
126
127static void
128getTorrentFilename( const tr_handle  * handle,
129                    const tr_info    * inf,
130                    char             * buf,
131                    size_t             buflen )
132{
133    const char * dir = tr_getTorrentDir( handle );
134    char base[MAX_PATH_LENGTH];
135    snprintf( base, sizeof( base ), "%s.%16.16s.torrent", inf->name, inf->hashString );
136    tr_buildPath( buf, buflen, dir, base, NULL );
137}
138
139static void
140getTorrentOldFilename( const tr_handle * handle,
141                       const tr_info   * info,
142                       char            * name,
143                       size_t            len )
144{
145    const char * torDir = tr_getTorrentDir( handle );
146
147    if( !handle->tag )
148    {
149        tr_buildPath( name, len, torDir, info->hashString, NULL );
150    }
151    else
152    {
153        char base[1024];
154        snprintf( base, sizeof(base), "%s-%s", info->hashString, handle->tag );
155        tr_buildPath( name, len, torDir, base, NULL );
156    }
157}
158
159void
160tr_metainfoMigrate( tr_handle * handle,
161                    tr_info   * inf )
162{
163    struct stat new_sb;
164    char new_name[MAX_PATH_LENGTH];
165
166    getTorrentFilename( handle, inf, new_name, sizeof( new_name ) );
167
168    if( stat( new_name, &new_sb ) || ( ( new_sb.st_mode & S_IFMT ) != S_IFREG ) )
169    {
170        char old_name[MAX_PATH_LENGTH];
171        size_t contentLen;
172        uint8_t * content;
173
174        tr_mkdirp( tr_getTorrentDir( handle ), 0777 );
175        getTorrentOldFilename( handle, inf, old_name, sizeof( old_name ) );
176        if(( content = tr_loadFile( old_name, &contentLen )))
177        {
178            FILE * out;
179            errno = 0;
180            out = fopen( new_name, "wb+" );
181            if( !out )
182            {
183                tr_nerr( inf->name, _( "Couldn't create \"%1$s\": %2$s" ), new_name, tr_strerror( errno ) );
184            }
185            else
186            {
187                if( fwrite( content, sizeof( uint8_t ), contentLen, out ) == contentLen )
188                {
189                    tr_free( inf->torrent );
190                    inf->torrent = tr_strdup( new_name );
191                    tr_sessionSetTorrentFile( handle, inf->hashString, new_name );
192                    unlink( old_name );
193                }
194                fclose( out );
195            }
196        }
197
198        tr_free( content );
199    }
200}
201
202static char *
203announceToScrape( const char * announce )
204{
205    char * scrape = NULL;
206    const char * s;
207
208    /* To derive the scrape URL use the following steps:
209     * Begin with the announce URL. Find the last '/' in it.
210     * If the text immediately following that '/' isn't 'announce'
211     * it will be taken as a sign that that tracker doesn't support
212     * the scrape convention. If it does, substitute 'scrape' for
213     * 'announce' to find the scrape page.  */
214    if((( s = strrchr( announce, '/' ))) && !strncmp( ++s, "announce", 8 ))
215    {
216        struct evbuffer * buf = evbuffer_new( );
217        evbuffer_add( buf, announce, s-announce );
218        evbuffer_add( buf, "scrape", 6 );
219        evbuffer_add_printf( buf, "%s", s+8 );
220        scrape = tr_strdup( ( char * ) EVBUFFER_DATA( buf ) );
221        evbuffer_free( buf );
222    }
223
224    return scrape;
225}
226
227static int
228getannounce( tr_info * inf, tr_benc * meta )
229{
230    const char * str;
231    tr_tracker_info * trackers = NULL;
232    int trackerCount = 0;
233    tr_benc * tiers;
234
235    /* Announce-list */
236    if( tr_bencDictFindList( meta, "announce-list", &tiers ) )
237    {
238        int n;
239        int i, j;
240
241        n = 0;
242        for( i=0; i<tiers->val.l.count; ++i )
243            n += tiers->val.l.vals[i].val.l.count;
244
245        trackers = tr_new0( tr_tracker_info, n );
246        trackerCount = 0;
247
248        for( i=0; i<tiers->val.l.count; ++i ) {
249            const tr_benc * tier = &tiers->val.l.vals[i];
250            for( j=0; tr_bencIsList(tier) && j<tier->val.l.count; ++j ) {
251                const tr_benc * a = &tier->val.l.vals[j];
252                if( tr_bencIsString( a ) && tr_httpIsValidURL( a->val.s.s ) ) {
253                    tr_tracker_info * t = trackers + trackerCount++;
254                    t->tier = i;
255                    t->announce = tr_strndup( a->val.s.s, a->val.s.i );
256                    t->scrape = announceToScrape( a->val.s.s );
257                    /*fprintf( stderr, "tier %d: %s\n", i, a->val.s.s );*/
258                }
259            }
260        }
261
262        /* did we use any of the tiers? */
263        if( !trackerCount ) {
264            tr_inf( _( "Invalid metadata entry \"%s\"" ), "announce-list" );
265            tr_free( trackers );
266            trackers = NULL;
267        }
268    }
269
270    /* Regular announce value */
271    if( !trackerCount
272        && tr_bencDictFindStr( meta, "announce", &str )
273        && tr_httpIsValidURL( str ) )
274    {
275        trackers = tr_new0( tr_tracker_info, 1 );
276        trackers[trackerCount].tier = 0;
277        trackers[trackerCount].announce = tr_strdup( str );
278        trackers[trackerCount++].scrape = announceToScrape( str );
279        /*fprintf( stderr, "single announce: [%s]\n", str );*/
280    }
281
282    inf->trackers = trackers;
283    inf->trackerCount = trackerCount;
284
285    return inf->trackerCount ? TR_OK : TR_ERROR;
286}
287
288int
289tr_metainfoParse( const tr_handle  * handle,
290                  tr_info          * inf,
291                  const tr_benc    * meta_in )
292{
293    tr_piece_index_t i;
294    tr_benc * beInfo, * val, * val2;
295    tr_benc * meta = (tr_benc *) meta_in;
296    char buf[4096];
297
298    /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
299     * from the Metainfo file. Note that the value will be a bencoded
300     * dictionary, given the definition of the info key above. */
301    if(( beInfo = tr_bencDictFindType( meta, "info", TYPE_DICT )))
302    {
303        int len;
304        char * str = tr_bencSave( beInfo, &len );
305        tr_sha1( inf->hash, str, len, NULL );
306        tr_free( str );
307    }
308    else
309    {
310        tr_err( _( "Missing metadata entry \"%s\"" ), "info" );
311        return TR_EINVALID;
312    }
313
314    tr_sha1_to_hex( inf->hashString, inf->hash );
315
316    /* comment */
317    memset( buf, '\0', sizeof( buf ) );
318    val = tr_bencDictFindFirst( meta, "comment.utf-8", "comment", NULL );
319    if( tr_bencIsString( val ) )
320        strlcat_utf8( buf, val->val.s.s, sizeof( buf ), 0 );
321    tr_free( inf->comment );
322    inf->comment = tr_strdup( buf );
323   
324    /* creator */
325    memset( buf, '\0', sizeof( buf ) );
326    val = tr_bencDictFindFirst( meta, "created by.utf-8", "created by", NULL );
327    if( tr_bencIsString( val ) )
328        strlcat_utf8( buf, val->val.s.s, sizeof( buf ), 0 );
329    tr_free( inf->creator );
330    inf->creator = tr_strdup( buf );
331   
332    /* Date created */
333    inf->dateCreated = 0;
334    val = tr_bencDictFind( meta, "creation date" );
335    if( tr_bencIsInt( val ) )
336        inf->dateCreated = val->val.i;
337   
338    /* Private torrent */
339    val  = tr_bencDictFind( beInfo, "private" );
340    val2 = tr_bencDictFind( meta,  "private" );
341    if( ( tr_bencIsInt(val) && val->val.i ) ||
342        ( tr_bencIsInt(val2) && val2->val.i ) )
343    {
344        inf->isPrivate = 1;
345    }
346   
347    /* Piece length */
348    val = tr_bencDictFind( beInfo, "piece length" );
349    if( !tr_bencIsInt( val ) )
350    {
351        if( val )
352            tr_err( _( "Invalid metadata entry \"%s\"" ), "piece length" );
353        else
354            tr_err( _( "Missing metadata entry \"%s\"" ), "piece length" );
355        goto fail;
356    }
357    inf->pieceSize = val->val.i;
358
359    /* Hashes */
360    val = tr_bencDictFind( beInfo, "pieces" );
361    if( !tr_bencIsString( val ) )
362    {
363        if( val )
364            tr_err( _( "Invalid metadata entry \"%s\"" ), "pieces" );
365        else
366            tr_err( _( "Missing metadata entry \"%s\"" ), "pieces" );
367        goto fail;
368    }
369    if( val->val.s.i % SHA_DIGEST_LENGTH )
370    {
371        tr_err( _( "Invalid metadata entry \"%s\"" ), "pieces" );
372        goto fail;
373    }
374    inf->pieceCount = val->val.s.i / SHA_DIGEST_LENGTH;
375
376    inf->pieces = calloc ( inf->pieceCount, sizeof(tr_piece) );
377
378    for ( i=0; i<inf->pieceCount; ++i )
379    {
380        memcpy (inf->pieces[i].hash, &val->val.s.s[i*SHA_DIGEST_LENGTH], SHA_DIGEST_LENGTH);
381    }
382
383    /* get file or top directory name */
384    val = tr_bencDictFindFirst( beInfo, "name.utf-8", "name", NULL );
385    if( parseFiles( inf, tr_bencDictFindFirst( beInfo,
386                                               "name.utf-8", "name", NULL ),
387                    tr_bencDictFind( beInfo, "files" ),
388                    tr_bencDictFind( beInfo, "length" ) ) )
389    {
390        goto fail;
391    }
392
393    if( !inf->fileCount || !inf->totalSize )
394    {
395        tr_err( _( "Torrent is corrupt" ) ); /* the content is missing! */
396        goto fail;
397    }
398
399    /* TODO add more tests so we don't crash on weird files */
400
401    if( (uint64_t) inf->pieceCount !=
402        ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize )
403    {
404        tr_err( _( "Torrent is corrupt" ) ); /* size of hashes and files don't match */
405        goto fail;
406    }
407
408    /* get announce or announce-list */
409    if( getannounce( inf, meta ) )
410        goto fail;
411
412    /* filename of Transmission's copy */
413    getTorrentFilename( handle, inf, buf, sizeof( buf ) );
414    tr_free( inf->torrent );
415    inf->torrent = tr_strdup( buf );
416
417    return TR_OK;
418
419  fail:
420    tr_metainfoFree( inf );
421    return TR_EINVALID;
422}
423
424void tr_metainfoFree( tr_info * inf )
425{
426    tr_file_index_t ff;
427    int i;
428
429    for( ff=0; ff<inf->fileCount; ++ff )
430        tr_free( inf->files[ff].name );
431
432    tr_free( inf->pieces );
433    tr_free( inf->files );
434    tr_free( inf->comment );
435    tr_free( inf->creator );
436    tr_free( inf->torrent );
437    tr_free( inf->name );
438   
439    for( i=0; i<inf->trackerCount; ++i ) {
440        tr_free( inf->trackers[i].announce );
441        tr_free( inf->trackers[i].scrape );
442    }
443    tr_free( inf->trackers );
444
445    memset( inf, '\0', sizeof(tr_info) );
446}
447
448static int
449getfile( char ** setme, const char * prefix, tr_benc * name )
450{
451    const char ** list;
452    int           ii, jj;
453    char          buf[4096];
454
455    if( !tr_bencIsList( name ) )
456        return TR_EINVALID;
457
458    list = calloc( name->val.l.count, sizeof( list[0] ) );
459    if( !list )
460        return TR_EINVALID;
461
462    for( ii = jj = 0; name->val.l.count > ii; ii++ )
463    {
464        tr_benc * dir = &name->val.l.vals[ii];
465
466        if( !tr_bencIsString( dir ) )
467            continue;
468
469        if( 0 == strcmp( "..", dir->val.s.s ) )
470        {
471            if( 0 < jj )
472            {
473                jj--;
474            }
475        }
476        else if( 0 != strcmp( ".", dir->val.s.s ) )
477        {
478            list[jj] = dir->val.s.s;
479            jj++;
480        }
481    }
482
483    if( 0 == jj )
484    {
485        free( list );
486        return TR_EINVALID;
487    }
488
489    memset( buf, 0, sizeof( buf ) );
490    strlcat_utf8( buf, prefix, sizeof(buf), 0 );
491    for( ii = 0; jj > ii; ii++ )
492    {
493        strlcat_utf8( buf, TR_PATH_DELIMITER_STR, sizeof(buf), 0 );
494        strlcat_utf8( buf, list[ii], sizeof(buf), TR_PATH_DELIMITER );
495    }
496    free( list );
497
498    tr_free( *setme );
499    *setme = tr_strdup( buf );
500
501    return TR_OK;
502}
503
504void
505tr_metainfoRemoveSaved( const tr_handle * handle,
506                        const tr_info   * inf )
507{
508    char filename[MAX_PATH_LENGTH];
509
510    getTorrentFilename( handle, inf, filename, sizeof( filename ) );
511    unlink( filename );
512
513    getTorrentOldFilename( handle, inf, filename, sizeof( filename ) );
514    unlink( filename );
515}
516
517static int
518parseFiles( tr_info * inf, tr_benc * name,
519            tr_benc * files, tr_benc * length )
520{
521    tr_benc * item, * path;
522    int ii;
523    char buf[4096];
524
525    if( !tr_bencIsString( name ) )
526    {
527        if( name )
528            tr_err( _( "Invalid metadata entry \"%s\"" ), "name" );
529        else
530            tr_err( _( "Missing metadata entry \"%s\"" ), "name" );
531        return TR_EINVALID;
532    }
533
534    memset( buf, 0, sizeof( buf ) );
535    strlcat_utf8( buf, name->val.s.s, sizeof( buf ), 0 );
536    tr_free( inf->name );
537    inf->name = tr_strdup( buf );
538    if( !inf->name || !*inf->name )
539    {
540        tr_err( _( "Invalid metadata entry \"%s\"" ), "name" );
541        return TR_EINVALID;
542    }
543    inf->totalSize = 0;
544
545    if( tr_bencIsList( files ) )
546    {
547        /* Multi-file mode */
548        inf->isMultifile = 1;
549        inf->fileCount = files->val.l.count;
550        inf->files     = calloc( inf->fileCount, sizeof( inf->files[0] ) );
551
552        if( !inf->files )
553            return TR_EINVALID;
554
555        for( ii = 0; files->val.l.count > ii; ii++ )
556        {
557            item = &files->val.l.vals[ii];
558            path = tr_bencDictFindFirst( item, "path.utf-8", "path", NULL );
559            if( getfile( &inf->files[ii].name, inf->name, path ) )
560            {
561                if( path )
562                    tr_err( _( "Invalid metadata entry \"%s\"" ), "path" );
563                else
564                    tr_err( _( "Missing metadata entry \"%s\"" ), "path" );
565                return TR_EINVALID;
566            }
567            length = tr_bencDictFind( item, "length" );
568            if( !tr_bencIsInt( length ) )
569            {
570                if( length )
571                    tr_err( _( "Invalid metadata entry \"%s\"" ), "length" );
572                else
573                    tr_err( _( "Missing metadata entry \"%s\"" ), "length" );
574                return TR_EINVALID;
575            }
576            inf->files[ii].length = length->val.i;
577            inf->totalSize         += length->val.i;
578        }
579    }
580    else if( tr_bencIsInt( length ) )
581    {
582        char buf[4096];
583
584        /* Single-file mode */
585        inf->isMultifile = 0;
586        inf->fileCount = 1;
587        inf->files     = calloc( 1, sizeof( inf->files[0] ) );
588
589        if( !inf->files )
590            return TR_EINVALID;
591
592        memset( buf, 0, sizeof( buf ) );
593        strlcat_utf8( buf, name->val.s.s, sizeof(buf), TR_PATH_DELIMITER );
594        tr_free( inf->files[0].name );
595        inf->files[0].name = tr_strdup( buf );
596
597        inf->files[0].length = length->val.i;
598        inf->totalSize      += length->val.i;
599    }
600    else
601    {
602        tr_err( _( "Invalid or missing metadata entries \"length\" and \"files\"" ) );
603    }
604
605    return TR_OK;
606}
Note: See TracBrowser for help on using the repository browser.