source: trunk/libtransmission/torrent-magnet.c @ 10842

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

(trunk) #3339 "crash when download some magnet links" -- fixed

File size: 11.0 KB
Line 
1/*
2 * This file Copyright (C) 2009-2010 Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id:$
11 */
12
13#include <assert.h>
14#include <event.h> /* struct evbuffer */
15#include <stdio.h> /* remove() */
16
17#include "transmission.h"
18#include "bencode.h"
19#include "crypto.h"
20#include "magnet.h"
21#include "metainfo.h"
22#include "resume.h"
23#include "torrent.h"
24#include "torrent-magnet.h"
25#include "utils.h"
26#include "web.h"
27
28#define dbgmsg( tor, ... ) \
29    do { \
30        if( tr_deepLoggingIsActive( ) ) \
31            tr_deepLog( __FILE__, __LINE__, tor->info.name, __VA_ARGS__ ); \
32    } while( 0 )
33
34/***
35****
36***/
37
38enum
39{
40    /* don't ask for the same metadata piece more than this often */
41    MIN_REPEAT_INTERVAL_SECS = 3
42};
43
44struct metadata_node
45{
46    time_t requestedAt;
47    int piece;
48};
49
50struct tr_incomplete_metadata
51{
52    uint8_t * metadata;
53    int metadata_size;
54    int pieceCount;
55
56    /** sorted from least to most recently requested */
57    struct metadata_node * piecesNeeded;
58    int piecesNeededCount;
59};
60
61static void
62incompleteMetadataFree( struct tr_incomplete_metadata * m )
63{
64    tr_free( m->metadata );
65    tr_free( m->piecesNeeded );
66    tr_free( m );
67}
68
69void
70tr_torrentSetMetadataSizeHint( tr_torrent * tor, int size )
71{
72    if( !tr_torrentHasMetadata( tor ) )
73    {
74        if( tor->incompleteMetadata == NULL )
75        {
76            int i;
77            struct tr_incomplete_metadata * m;
78            const int n = ( size + ( METADATA_PIECE_SIZE - 1 ) ) / METADATA_PIECE_SIZE;
79            dbgmsg( tor, "metadata is %d bytes in %d pieces", size, n );
80
81            m = tr_new( struct tr_incomplete_metadata, 1 );
82            m->pieceCount = n;
83            m->metadata = tr_new( uint8_t, size );
84            m->metadata_size = size;
85            m->piecesNeededCount = n;
86            m->piecesNeeded = tr_new( struct metadata_node, n );
87
88            for( i=0; i<n; ++i ) {
89                m->piecesNeeded[i].piece = i;
90                m->piecesNeeded[i].requestedAt = 0;
91            }
92
93            tor->incompleteMetadata = m;
94        }
95    }
96}
97
98static int
99findInfoDictOffset( const tr_torrent * tor )
100{
101    size_t fileLen;
102    uint8_t * fileContents;
103    int offset = 0;
104
105    /* load the file, and find the info dict's offset inside the file */
106    if(( fileContents = tr_loadFile( tor->info.torrent, &fileLen )))
107    {
108        tr_benc top;
109
110        if( !tr_bencParse( fileContents, fileContents + fileLen, &top, NULL ) )
111        {
112            tr_benc * infoDict;
113
114            if( tr_bencDictFindDict( &top, "info", &infoDict ) )
115            {
116                int infoLen;
117                char * infoContents = tr_bencToStr( infoDict, TR_FMT_BENC, &infoLen );
118                const uint8_t * i = (const uint8_t*) tr_memmem( (char*)fileContents, fileLen, infoContents, infoLen );
119                offset = i == NULL ? i - fileContents : 0;
120                tr_free( infoContents );
121            }
122
123            tr_bencFree( &top );
124        }
125
126        tr_free( fileContents );
127    }
128
129    return offset;
130}
131
132static void
133ensureInfoDictOffsetIsCached( tr_torrent * tor )
134{
135    assert( tr_torrentHasMetadata( tor ) );
136
137    if( !tor->infoDictOffsetIsCached )
138    {
139        tor->infoDictOffset = findInfoDictOffset( tor );
140        tor->infoDictOffsetIsCached = TRUE;
141    }
142}
143
144void*
145tr_torrentGetMetadataPiece( tr_torrent * tor, int piece, int * len )
146{
147    char * ret = NULL;
148
149    assert( tr_isTorrent( tor ) );
150    assert( piece >= 0 );
151    assert( len != NULL );
152
153    if( tr_torrentHasMetadata( tor ) )
154    {
155        FILE * fp;
156
157        ensureInfoDictOffsetIsCached( tor );
158
159        assert( tor->infoDictLength > 0 );
160        assert( tor->infoDictOffset >= 0 );
161
162        fp = fopen( tor->info.torrent, "rb" );
163        if( fp != NULL )
164        {
165            const int o = piece  * METADATA_PIECE_SIZE;
166
167            if( !fseek( fp, tor->infoDictOffset + o, SEEK_SET ) )
168            {
169                const int l = o + METADATA_PIECE_SIZE <= tor->infoDictLength
170                            ? METADATA_PIECE_SIZE
171                            : tor->infoDictLength - o;
172
173                if( 0<l && l<=METADATA_PIECE_SIZE )
174                {
175                    char * buf = tr_new( char, l );
176                    const int n = fread( buf, 1, l, fp );
177                    if( n == l )
178                    {
179                        *len = l;
180                        ret = buf;
181                        buf = NULL;
182                    }
183
184                    tr_free( buf );
185                }
186            }
187
188            fclose( fp );
189        }
190    }
191
192    return ret;
193}
194
195void
196tr_torrentSetMetadataPiece( tr_torrent  * tor, int piece, const void  * data, int len )
197{
198    int i;
199    struct tr_incomplete_metadata * m;
200    const int offset = piece * METADATA_PIECE_SIZE;
201
202    assert( tr_isTorrent( tor ) );
203
204    dbgmsg( tor, "got metadata piece %d", piece );
205
206    /* are we set up to download metadata? */
207    m = tor->incompleteMetadata;
208    if( m == NULL )
209        return;
210
211    /* does this data pass the smell test? */
212    if( offset + len > m->metadata_size )
213        return;
214
215    /* do we need this piece? */
216    for( i=0; i<m->piecesNeededCount; ++i )
217        if( m->piecesNeeded[i].piece == piece )
218            break;
219    if( i==m->piecesNeededCount )
220        return;
221
222    memcpy( m->metadata + offset, data, len );
223
224    tr_removeElementFromArray( m->piecesNeeded, i,
225                               sizeof( struct metadata_node ),
226                               m->piecesNeededCount-- );
227
228    dbgmsg( tor, "saving metainfo piece %d... %d remain", piece, m->piecesNeededCount );
229
230    /* are we done? */
231    if( m->piecesNeededCount == 0 )
232    {
233        tr_bool success = FALSE;
234        tr_bool checksumPassed = FALSE;
235        tr_bool metainfoParsed = FALSE;
236        uint8_t sha1[SHA_DIGEST_LENGTH];
237
238        /* we've got a complete set of metainfo... see if it passes the checksum test */
239        dbgmsg( tor, "metainfo piece %d was the last one", piece );
240        tr_sha1( sha1, m->metadata, m->metadata_size, NULL );
241        if(( checksumPassed = !memcmp( sha1, tor->info.hash, SHA_DIGEST_LENGTH )))
242        {
243            /* checksum passed; now try to parse it as benc */
244            tr_benc infoDict;
245            const int err = tr_bencLoad( m->metadata, m->metadata_size, &infoDict, NULL );
246            dbgmsg( tor, "err is %d", err );
247            if(( metainfoParsed = !err ))
248            {
249                /* yay we have bencoded metainfo... merge it into our .torrent file */
250                tr_benc newMetainfo;
251                char * path = tr_strdup( tor->info.torrent );
252
253                if( !tr_bencLoadFile( &newMetainfo, TR_FMT_BENC, path ) )
254                {
255                    tr_bool hasInfo;
256                    tr_info info;
257                    int infoDictLength;
258
259                    /* remove any old .torrent and .resume files */
260                    remove( path );
261                    tr_torrentRemoveResume( tor );
262
263                    dbgmsg( tor, "Saving completed metadata to \"%s\"", path );
264                    tr_bencMergeDicts( tr_bencDictAddDict( &newMetainfo, "info", 0 ), &infoDict );
265
266                    memset( &info, 0, sizeof( tr_info ) );
267                    success = tr_metainfoParse( tor->session, &newMetainfo, &info, &hasInfo, &infoDictLength );
268
269                    if( success && !tr_getBlockSize( info.pieceSize ) )
270                    {
271                        tr_torrentSetLocalError( tor, _( "Magnet torrent's metadata is not usable" ) );
272                        success = FALSE;
273                    }
274
275                    if( success )
276                    {
277                        /* keep the new info */
278                        tor->info = info;
279                        tor->infoDictLength = infoDictLength;
280
281                        /* save the new .torrent file */
282                        tr_bencToFile( &newMetainfo, TR_FMT_BENC, tor->info.torrent );
283                        tr_sessionSetTorrentFile( tor->session, tor->info.hashString, tor->info.torrent );
284                        tr_torrentGotNewInfoDict( tor );
285                        tr_torrentSetDirty( tor );
286                    }
287
288                    tr_bencFree( &newMetainfo );
289                }
290
291                tr_bencFree( &infoDict );
292                tr_free( path );
293            }
294        }
295
296        if( success )
297        {
298            incompleteMetadataFree( tor->incompleteMetadata );
299            tor->incompleteMetadata = NULL;
300        }
301        else /* drat. */
302        {
303            const int n = m->pieceCount;
304            for( i=0; i<n; ++i )
305            {
306                m->piecesNeeded[i].piece = i;
307                m->piecesNeeded[i].requestedAt = 0;
308            }
309            m->piecesNeededCount = n;
310            dbgmsg( tor, "metadata error; trying again. %d pieces left", n );
311
312            tr_err( "magnet status: checksum passed %d, metainfo parsed %d",
313                    (int)checksumPassed, (int)metainfoParsed );
314        }
315    }
316}
317
318tr_bool
319tr_torrentGetNextMetadataRequest( tr_torrent * tor, time_t now, int * setme_piece )
320{
321    tr_bool have_request = FALSE;
322    struct tr_incomplete_metadata * m;
323
324    assert( tr_isTorrent( tor ) );
325
326    m = tor->incompleteMetadata;
327
328    if( ( m != NULL )
329        && ( m->piecesNeededCount > 0 )
330        && ( m->piecesNeeded[0].requestedAt + MIN_REPEAT_INTERVAL_SECS < now ) )
331    {
332        int i;
333        const int piece = m->piecesNeeded[0].piece;
334
335        tr_removeElementFromArray( m->piecesNeeded, 0,
336                                   sizeof( struct metadata_node ),
337                                   m->piecesNeededCount-- );
338
339        i = m->piecesNeededCount++;
340        m->piecesNeeded[i].piece = piece;
341        m->piecesNeeded[i].requestedAt = now;
342
343        dbgmsg( tor, "next piece to request: %d", piece );
344        *setme_piece = piece;
345        have_request = TRUE;
346    }
347
348    return have_request;
349}
350
351float
352tr_torrentGetMetadataPercent( const tr_torrent * tor )
353{
354    float ret;
355
356    if( tr_torrentHasMetadata( tor ) )
357        ret = 1.0;
358    else {
359        const struct tr_incomplete_metadata * m = tor->incompleteMetadata;
360        if( m == NULL )
361            ret = 0.0;
362        else
363            ret = (m->pieceCount - m->piecesNeededCount) / (float)m->pieceCount;
364    }
365
366    return ret;
367}
368
369char*
370tr_torrentGetMagnetLink( const tr_torrent * tor )
371{
372    int i;
373    char * ret;
374    const char * name;
375    struct evbuffer * s;
376
377    assert( tr_isTorrent( tor ) );
378
379    s = evbuffer_new( );
380    evbuffer_add_printf( s, "magnet:?xt=urn:btih:%s", tor->info.hashString );
381    name = tr_torrentName( tor );
382    if( name && *name )
383    {
384        evbuffer_add_printf( s, "%s", "&dn=" );
385        tr_http_escape( s, tr_torrentName( tor ), -1, TRUE );
386    }
387    for( i=0; i<tor->info.trackerCount; ++i )
388    {
389        evbuffer_add_printf( s, "%s", "&tr=" );
390        tr_http_escape( s, tor->info.trackers[i].announce, -1, TRUE );
391    }
392
393    ret = tr_strndup( EVBUFFER_DATA( s ), EVBUFFER_LENGTH( s ) );
394    evbuffer_free( s );
395    return ret;
396}
Note: See TracBrowser for help on using the repository browser.