source: trunk/libtransmission/metainfo.c @ 3426

Last change on this file since 3426 was 3426, checked in by charles, 15 years ago

remove unused flags

  • Property svn:keywords set to Date Rev Author Id
File size: 20.4 KB
Line 
1/******************************************************************************
2 * $Id: metainfo.c 3426 2007-10-15 20:58:39Z charles $
3 *
4 * Copyright (c) 2005-2007 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 <stdlib.h>
29
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <unistd.h> /* unlink, stat */
33
34#include "transmission.h"
35#include "bencode.h"
36#include "crypto.h" /* tr_sha1 */
37#include "http.h" /* tr_httpParseUrl */
38#include "metainfo.h"
39#include "platform.h"
40#include "utils.h"
41
42/***********************************************************************
43 * Local prototypes
44 **********************************************************************/
45static int realparse( tr_info * inf, const uint8_t * buf, size_t len );
46static void savedname( char * name, size_t len, const char * hash,
47                       const char * tag );
48static uint8_t * readtorrent( const char * path, size_t * len );
49static int savetorrent( const char * hash, const char * tag,
50                        const uint8_t * buf, size_t buflen );
51static int getfile( char * buf, int size,
52                    const char * prefix, benc_val_t * name );
53static int getannounce( tr_info * inf, benc_val_t * meta );
54static char * announceToScrape( const char * announce );
55static int parseFiles( tr_info * inf, benc_val_t * name,
56                       benc_val_t * files, benc_val_t * length );
57
58/***********************************************************************
59 * tr_metainfoParse
60 ***********************************************************************
61 *
62 **********************************************************************/
63int
64tr_metainfoParseFile( tr_info * inf, const char * tag,
65                      const char * path, int save )
66{
67    uint8_t * buf;
68    size_t    size;
69
70    /* read the torrent data */
71    buf = readtorrent( path, &size );
72    if( NULL == buf )
73    {
74        return TR_EINVALID;
75    }
76
77    if( realparse( inf, buf, size ) )
78    {
79        free( buf );
80        return TR_EINVALID;
81    }
82
83    if( save )
84    {
85        if( savetorrent( inf->hashString, tag, buf, size ) )
86        {
87            free( buf );
88            return TR_EINVALID;
89        }
90        savedname( inf->torrent, sizeof inf->torrent, inf->hashString, tag );
91    }
92    else
93    {
94        snprintf( inf->torrent, sizeof inf->torrent, "%s", path );
95    }
96
97    free( buf );
98
99    return TR_OK;
100}
101
102int
103tr_metainfoParseData( tr_info * inf, const char * tag,
104                      const uint8_t * data, size_t size, int save )
105{
106    if( realparse( inf, data, size ) )
107    {
108        return TR_EINVALID;
109    }
110
111    if( save )
112    {
113        if( savetorrent( inf->hashString, tag, data, size ) )
114        {
115            return TR_EINVALID;
116        }
117        savedname( inf->torrent, sizeof inf->torrent, inf->hashString, tag );
118    }
119
120    return TR_OK;
121}
122
123int
124tr_metainfoParseHash( tr_info * inf, const char * tag, const char * hash )
125{
126    struct stat sb;
127    uint8_t   * buf;
128    size_t      size;
129    int         save;
130
131    /* check it we should use an old file without a tag */
132    /* XXX this should go away at some point */
133    save = 0;
134    savedname( inf->torrent, sizeof inf->torrent, hash, tag );
135    if( 0 > stat( inf->torrent, &sb ) && ENOENT == errno )
136    {
137        savedname( inf->torrent, sizeof inf->torrent, hash, NULL );
138        if( 0 == stat( inf->torrent, &sb ))
139        {
140            save = 1;
141        }
142    }
143
144    buf = readtorrent( inf->torrent, &size );
145    if( NULL == buf )
146    {
147        return TR_EINVALID;
148    }
149
150    if( realparse( inf, buf, size ) )
151    {
152        free( buf );
153        return TR_EINVALID;
154    }
155
156    /* save a new tagged copy of the old untagged torrent */
157    if( save )
158    {
159        if( savetorrent( hash, tag, buf, size ) )
160        {
161            free( buf );
162            return TR_EINVALID;
163        }
164        savedname( inf->torrent, sizeof inf->torrent, hash, tag );
165    }
166
167    free( buf );
168
169    return TR_OK;
170}
171
172static int
173realparse( tr_info * inf, const uint8_t * buf, size_t size )
174{
175    benc_val_t   meta, * beInfo, * val, * val2;
176    int          i;
177
178    /* Parse bencoded infos */
179    if( tr_bencLoad( buf, size, &meta, NULL ) )
180    {
181        tr_err( "Error while parsing bencoded data [%*.*s]", (int)size, (int)size, (char*)buf );
182        return TR_EINVALID;
183    }
184
185    /* Get info hash */
186    beInfo = tr_bencDictFind( &meta, "info" );
187    if( NULL == beInfo || TYPE_DICT != beInfo->type )
188    {
189        tr_err( "%s \"info\" dictionary", ( beInfo ? "Invalid" : "Missing" ) );
190        tr_bencFree( &meta );
191        return TR_EINVALID;
192    }
193
194    tr_sha1( inf->hash, beInfo->begin, beInfo->end - beInfo->begin, NULL );
195
196    for( i = 0; i < SHA_DIGEST_LENGTH; i++ )
197    {
198        snprintf( inf->hashString + i * 2, sizeof( inf->hashString ) - i * 2,
199                  "%02x", inf->hash[i] );
200    }
201
202    /* Comment info */
203    val = tr_bencDictFindFirst( &meta, "comment.utf-8", "comment", NULL );
204    if( NULL != val && TYPE_STR == val->type )
205    {
206        strlcat_utf8( inf->comment, val->val.s.s, sizeof( inf->comment ), 0 );
207    }
208   
209    /* Creator info */
210    val = tr_bencDictFindFirst( &meta, "created by.utf-8", "created by", NULL );
211    if( NULL != val && TYPE_STR == val->type )
212    {
213        strlcat_utf8( inf->creator, val->val.s.s, sizeof( inf->creator ), 0 );
214    }
215   
216    /* Date created */
217    inf->dateCreated = 0;
218    val = tr_bencDictFind( &meta, "creation date" );
219    if( NULL != val && TYPE_INT == val->type )
220    {
221        inf->dateCreated = val->val.i;
222    }
223   
224    /* Private torrent */
225    val  = tr_bencDictFind( beInfo, "private" );
226    val2 = tr_bencDictFind( &meta,  "private" );
227    if( ( NULL != val  && ( TYPE_INT != val->type  || 0 != val->val.i ) ) ||
228        ( NULL != val2 && ( TYPE_INT != val2->type || 0 != val2->val.i ) ) )
229    {
230        inf->isPrivate = 1;
231    }
232   
233    /* Piece length */
234    val = tr_bencDictFind( beInfo, "piece length" );
235    if( NULL == val || TYPE_INT != val->type )
236    {
237        tr_err( "%s \"piece length\" entry", ( val ? "Invalid" : "Missing" ) );
238        goto fail;
239    }
240    inf->pieceSize = val->val.i;
241
242    /* Hashes */
243    val = tr_bencDictFind( beInfo, "pieces" );
244    if( NULL == val || TYPE_STR != val->type )
245    {
246        tr_err( "%s \"pieces\" entry", ( val ? "Invalid" : "Missing" ) );
247        goto fail;
248    }
249    if( val->val.s.i % SHA_DIGEST_LENGTH )
250    {
251        tr_err( "Invalid \"piece\" string (size is %d)", val->val.s.i );
252        goto fail;
253    }
254    inf->pieceCount = val->val.s.i / SHA_DIGEST_LENGTH;
255
256    inf->pieces = calloc ( inf->pieceCount, sizeof(tr_piece) );
257
258    for ( i=0; i<inf->pieceCount; ++i )
259    {
260        memcpy (inf->pieces[i].hash, &val->val.s.s[i*SHA_DIGEST_LENGTH], SHA_DIGEST_LENGTH);
261    }
262
263    /* get file or top directory name */
264    val = tr_bencDictFindFirst( beInfo, "name.utf-8", "name", NULL );
265    if( parseFiles( inf, tr_bencDictFindFirst( beInfo,
266                                               "name.utf-8", "name", NULL ),
267                    tr_bencDictFind( beInfo, "files" ),
268                    tr_bencDictFind( beInfo, "length" ) ) )
269    {
270        goto fail;
271    }
272
273    if( !inf->fileCount )
274    {
275        tr_err( "Torrent has no files." );
276        goto fail;
277    }
278
279    if( !inf->totalSize )
280    {
281        tr_err( "Torrent is zero bytes long." );
282        goto fail;
283    }
284
285    /* TODO add more tests so we don't crash on weird files */
286
287    if( (uint64_t) inf->pieceCount !=
288        ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize )
289    {
290        tr_err( "Size of hashes and files don't match" );
291        goto fail;
292    }
293
294    /* get announce or announce-list */
295    if( getannounce( inf, &meta ) )
296    {
297        goto fail;
298    }
299
300    tr_bencFree( &meta );
301    return TR_OK;
302
303  fail:
304    tr_metainfoFree( inf );
305    tr_bencFree( &meta );
306    return TR_EINVALID;
307}
308
309void tr_metainfoFree( tr_info * inf )
310{
311    int i, j;
312
313    tr_free( inf->pieces );
314    tr_free( inf->files );
315    tr_free( inf->primaryAddress );
316   
317    for( i=0; i<inf->trackerTiers; ++i ) {
318        for( j=0; j<inf->trackerList[i].count; ++j )
319            tr_trackerInfoClear( &inf->trackerList[i].list[j] );
320        tr_free( inf->trackerList[i].list );
321    }
322    tr_free( inf->trackerList );
323
324    memset( inf, '\0', sizeof(tr_info) );
325}
326
327static int getfile( char * buf, int size,
328                    const char * prefix, benc_val_t * name )
329{
330    benc_val_t  * dir;
331    const char ** list;
332    int           ii, jj;
333
334    if( TYPE_LIST != name->type )
335    {
336        return TR_EINVALID;
337    }
338
339    list = calloc( name->val.l.count, sizeof( list[0] ) );
340    if( NULL == list )
341    {
342        return TR_EINVALID;
343    }
344
345    for( ii = jj = 0; name->val.l.count > ii; ii++ )
346    {
347        dir = &name->val.l.vals[ii];
348        if( TYPE_STR != dir->type )
349        {
350            continue;
351        }
352        if( 0 == strcmp( "..", dir->val.s.s ) )
353        {
354            if( 0 < jj )
355            {
356                jj--;
357            }
358        }
359        else if( 0 != strcmp( ".", dir->val.s.s ) )
360        {
361            list[jj] = dir->val.s.s;
362            jj++;
363        }
364    }
365
366    if( 0 == jj )
367    {
368        free( list );
369        return TR_EINVALID;
370    }
371
372    strlcat_utf8( buf, prefix, size, 0 );
373    for( ii = 0; jj > ii; ii++ )
374    {
375        strlcat_utf8( buf, TR_PATH_DELIMITER_STR, size, 0 );
376        strlcat_utf8( buf, list[ii], size, TR_PATH_DELIMITER );
377    }
378    free( list );
379
380    return TR_OK;
381}
382
383static int getannounce( tr_info * inf, benc_val_t * meta )
384{
385    benc_val_t        * val, * subval, * urlval;
386    char              * address, * announce;
387    int                 ii, jj, port, random, subcount;
388    tr_tracker_info   * sublist;
389    void * swapping;
390
391    /* Announce-list */
392    val = tr_bencDictFind( meta, "announce-list" );
393    if( NULL != val && TYPE_LIST == val->type && 0 < val->val.l.count )
394    {
395        inf->trackerTiers = 0;
396        inf->trackerList = calloc( val->val.l.count,
397                                   sizeof( inf->trackerList[0] ) );
398
399        /* iterate through the announce-list's tiers */
400        for( ii = 0; ii < val->val.l.count; ii++ )
401        {
402            subval = &val->val.l.vals[ii];
403            if( TYPE_LIST != subval->type || 0 >= subval->val.l.count )
404            {
405                continue;
406            }
407            subcount = 0;
408            sublist = calloc( subval->val.l.count, sizeof( sublist[0] ) );
409
410            /* iterate through the tier's items */
411            for( jj = 0; jj < subval->val.l.count; jj++ )
412            {
413                tr_tracker_info tmp;
414
415                urlval = &subval->val.l.vals[jj];
416                if( TYPE_STR != urlval->type ||
417                    tr_trackerInfoInit( &tmp, urlval->val.s.s, urlval->val.s.i ) )
418                {
419                    continue;
420                }
421
422                if( !inf->primaryAddress ) {
423                     char buf[1024];
424                     snprintf( buf, sizeof(buf), "%s:%d", tmp.address, tmp.port );
425                     inf->primaryAddress = tr_strdup( buf );
426                }
427
428                /* place the item info in a random location in the sublist */
429                random = tr_rand( subcount + 1 );
430                if( random != subcount )
431                    sublist[subcount] = sublist[random];
432                sublist[random] = tmp;
433                subcount++;
434            }
435
436            /* just use sublist as-is if it's full */
437            if( subcount == subval->val.l.count )
438            {
439                inf->trackerList[inf->trackerTiers].list = sublist;
440                inf->trackerList[inf->trackerTiers].count = subcount;
441                inf->trackerTiers++;
442            }
443            /* if we skipped some of the tier's items then trim the sublist */
444            else if( 0 < subcount )
445            {
446                inf->trackerList[inf->trackerTiers].list = calloc( subcount, sizeof( sublist[0] ) );
447                memcpy( inf->trackerList[inf->trackerTiers].list, sublist,
448                        sizeof( sublist[0] ) * subcount );
449                inf->trackerList[inf->trackerTiers].count = subcount;
450                inf->trackerTiers++;
451                free( sublist );
452            }
453            /* drop the whole sublist if we didn't use any items at all */
454            else
455            {
456                free( sublist );
457            }
458        }
459
460        /* did we use any of the tiers? */
461        if( 0 == inf->trackerTiers )
462        {
463            tr_inf( "No valid entries in \"announce-list\"" );
464            free( inf->trackerList );
465            inf->trackerList = NULL;
466        }
467        /* trim unused sublist pointers */
468        else if( inf->trackerTiers < val->val.l.count )
469        {
470            swapping = inf->trackerList;
471            inf->trackerList = calloc( inf->trackerTiers,
472                                       sizeof( inf->trackerList[0] ) );
473            memcpy( inf->trackerList, swapping,
474                    sizeof( inf->trackerList[0] ) * inf->trackerTiers );
475            free( swapping );
476        }
477    }
478
479    /* Regular announce value */
480    val = tr_bencDictFind( meta, "announce" );
481    if( NULL == val || TYPE_STR != val->type )
482    {
483        tr_err( "No \"announce\" entry" );
484        return TR_EINVALID;
485    }
486
487    if( !inf->trackerTiers )
488    {
489
490        if( tr_httpParseUrl( val->val.s.s, val->val.s.i,
491                             &address, &port, &announce ) )
492        {
493            tr_err( "Invalid announce URL (%s)", val->val.s.s );
494            return TR_EINVALID;
495        }
496        sublist                   = calloc( 1, sizeof( sublist[0] ) );
497        sublist[0].address        = address;
498        sublist[0].port           = port;
499        sublist[0].announce       = announce;
500        sublist[0].scrape         = announceToScrape( announce );
501        inf->trackerList          = calloc( 1, sizeof( inf->trackerList[0] ) );
502        inf->trackerList[0].list  = sublist;
503        inf->trackerList[0].count = 1;
504        inf->trackerTiers         = 1;
505
506        if( !inf->primaryAddress ) {
507            char buf[1024];
508            snprintf( buf, sizeof(buf), "%s:%d", sublist[0].address, sublist[0].port );
509            inf->primaryAddress = tr_strdup( buf );
510        }
511
512    }
513
514    return TR_OK;
515}
516
517static char * announceToScrape( const char * announce )
518{
519    char old[]  = "announce";
520    int  oldlen = 8;
521    char new[]  = "scrape";
522    int  newlen = 6;
523    char * slash, * scrape;
524    size_t scrapelen, used;
525
526    slash = strrchr( announce, '/' );
527    if( NULL == slash )
528    {
529        return NULL;
530    }
531    slash++;
532   
533    if( 0 != strncmp( slash, old, oldlen ) )
534    {
535        return NULL;
536    }
537
538    scrapelen = strlen( announce ) - oldlen + newlen;
539    scrape = calloc( scrapelen + 1, 1 );
540    if( NULL == scrape )
541    {
542        return NULL;
543    }
544    assert( ( size_t )( slash - announce ) < scrapelen );
545    memcpy( scrape, announce, slash - announce );
546    used = slash - announce;
547    strncat( scrape, new, scrapelen - used );
548    used += newlen;
549    assert( strlen( scrape ) == used );
550    if( used < scrapelen )
551    {
552        assert( strlen( slash + oldlen ) == scrapelen - used );
553        strncat( scrape, slash + oldlen, scrapelen - used );
554    }
555
556    return scrape;
557}
558
559int
560tr_trackerInfoInit( tr_tracker_info  * info,
561                    const char       * address,
562                    int                address_len )
563{
564    int ret = tr_httpParseUrl( address, address_len,
565                               &info->address,
566                               &info->port,
567                               &info->announce );
568    if( !ret )
569        info->scrape = announceToScrape( info->announce );
570
571    return ret;
572}
573
574void
575tr_trackerInfoClear( tr_tracker_info * info )
576{
577    tr_free( info->address );
578    tr_free( info->announce );
579    tr_free( info->scrape );
580    memset( info, '\0', sizeof(tr_tracker_info) );
581}
582
583
584static void
585savedname( char * name, size_t len, const char * hash, const char * tag )
586{
587    const char * torDir = tr_getTorrentsDirectory ();
588
589    if( tag == NULL )
590    {
591        tr_buildPath( name, len, torDir, hash, NULL );
592    }
593    else
594    {
595        char base[1024];
596        snprintf( base, sizeof(base), "%s-%s", hash, tag );
597        tr_buildPath( name, len, torDir, base, NULL );
598    }
599}
600
601void tr_metainfoRemoveSaved( const char * hashString, const char * tag )
602{
603    char file[MAX_PATH_LENGTH];
604
605    savedname( file, sizeof file, hashString, tag );
606    unlink( file );
607}
608
609static uint8_t *
610readtorrent( const char * path, size_t * size )
611{
612    return tr_loadFile( path, size );
613}
614
615/* Save a copy of the torrent file in the saved torrent directory */
616static int
617savetorrent( const char * hash, const char * tag,
618             const uint8_t * buf, size_t buflen )
619{
620    char   path[MAX_PATH_LENGTH];
621    FILE * file;
622
623    savedname( path, sizeof path, hash, tag );
624    file = fopen( path, "wb" );
625    if( !file )
626    {
627        tr_err( "Could not open file (%s) (%s)", path, strerror( errno ) );
628        return TR_EINVALID;
629    }
630    fseek( file, 0, SEEK_SET );
631    if( fwrite( buf, 1, buflen, file ) != buflen )
632    {
633        tr_err( "Could not write file (%s) (%s)", path, strerror( errno ) );
634        fclose( file );
635        return TR_EINVALID;
636    }
637    fclose( file );
638
639    return TR_OK;
640}
641
642static int
643parseFiles( tr_info * inf, benc_val_t * name,
644            benc_val_t * files, benc_val_t * length )
645{
646    benc_val_t * item, * path;
647    int ii;
648
649    if( NULL == name || TYPE_STR != name->type )
650    {
651        tr_err( "%s \"name\" string", ( name ? "Invalid" : "Missing" ) );
652        return TR_EINVALID;
653    }
654
655    strlcat_utf8( inf->name, name->val.s.s, sizeof( inf->name ),
656                  TR_PATH_DELIMITER );
657    if( '\0' == inf->name[0] )
658    {
659        tr_err( "Invalid \"name\" string" );
660        return TR_EINVALID;
661    }
662    inf->totalSize = 0;
663
664    if( files && TYPE_LIST == files->type )
665    {
666        /* Multi-file mode */
667        inf->isMultifile = 1;
668        inf->fileCount = files->val.l.count;
669        inf->files     = calloc( inf->fileCount, sizeof( inf->files[0] ) );
670
671        if( NULL == inf->files )
672        {
673            return TR_EINVALID;
674        }
675
676        for( ii = 0; files->val.l.count > ii; ii++ )
677        {
678            item = &files->val.l.vals[ii];
679            path = tr_bencDictFindFirst( item, "path.utf-8", "path", NULL );
680            if( getfile( inf->files[ii].name, sizeof( inf->files[0].name ),
681                         inf->name, path ) )
682            {
683                tr_err( "%s \"path\" entry",
684                        ( path ? "Invalid" : "Missing" ) );
685                return TR_EINVALID;
686            }
687            length = tr_bencDictFind( item, "length" );
688            if( NULL == length || TYPE_INT != length->type )
689            {
690                tr_err( "%s \"length\" entry",
691                        ( length ? "Invalid" : "Missing" ) );
692                return TR_EINVALID;
693            }
694            inf->files[ii].length = length->val.i;
695            inf->totalSize         += length->val.i;
696        }
697    }
698    else if( NULL != length && TYPE_INT == length->type )
699    {
700        /* Single-file mode */
701        inf->isMultifile = 0;
702        inf->fileCount = 1;
703        inf->files     = calloc( 1, sizeof( inf->files[0] ) );
704
705        if( NULL == inf->files )
706        {
707            return TR_EINVALID;
708        }
709
710        strlcat_utf8( inf->files[0].name, name->val.s.s,
711                      sizeof( inf->files[0].name ), TR_PATH_DELIMITER );
712
713        inf->files[0].length = length->val.i;
714        inf->totalSize      += length->val.i;
715    }
716    else
717    {
718        tr_err( "%s \"files\" entry and %s \"length\" entry",
719                ( files ? "Invalid" : "Missing" ),
720                ( length ? "invalid" : "missing" ) );
721    }
722
723    return TR_OK;
724}
Note: See TracBrowser for help on using the repository browser.