1/* 2 * This file Copyright (C) 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: metainfo.c 13568 2012-10-14 18:10:17Z jordan $ 11 */ 12 13#include <assert.h> 14#include <errno.h> 15#include <stdio.h> /* fopen(), fwrite(), fclose() */ 16#include <string.h> /* strlen() */ 17 18#include <sys/types.h> 19#include <sys/stat.h> 20#include <unistd.h> /* unlink, stat */ 21 22#include <event2/buffer.h> 23 24#include "transmission.h" 25#include "session.h" 26#include "bencode.h" 27#include "crypto.h" /* tr_sha1 */ 28#include "metainfo.h" 29#include "platform.h" /* tr_getTorrentDir() */ 30#include "utils.h" 31 32/*** 33**** 34***/ 35 36char* 37tr_metainfoGetBasename( const tr_info * inf ) 38{ 39 size_t i; 40 const size_t name_len = strlen( inf->name ); 41 char * ret = tr_strdup_printf( "%s.%16.16s", inf->name, inf->hashString ); 42 43 for( i=0; i<name_len; ++i ) 44 if( ret[i] == '/' ) 45 ret[i] = '_'; 46 47 48 return ret; 49} 50 51static char* 52getTorrentFilename( const tr_session * session, const tr_info * inf ) 53{ 54 char * base = tr_metainfoGetBasename( inf ); 55 char * filename = tr_strdup_printf( "%s" TR_PATH_DELIMITER_STR "%s.torrent", 56 tr_getTorrentDir( session ), base ); 57 tr_free( base ); 58 return filename; 59} 60 61static char* 62getOldTorrentFilename( const tr_session * session, const tr_info * inf ) 63{ 64 int i; 65 char * path; 66 struct stat sb; 67 const int tagCount = 5; 68 const char * tags[] = { "beos", "cli", "daemon", "macosx", "wx" }; 69 70 /* test the beos, cli, daemon, macosx, wx tags */ 71 for( i=0; i<tagCount; ++i ) { 72 path = tr_strdup_printf( "%s%c%s-%s", tr_getTorrentDir( session ), '/', inf->hashString, tags[i] ); 73 if( !stat( path, &sb ) && ( ( sb.st_mode & S_IFMT ) == S_IFREG ) ) 74 return path; 75 tr_free( path ); 76 } 77 78 /* test a non-tagged file */ 79 path = tr_buildPath( tr_getTorrentDir( session ), inf->hashString, NULL ); 80 if( !stat( path, &sb ) && ( ( sb.st_mode & S_IFMT ) == S_IFREG ) ) 81 return path; 82 tr_free( path ); 83 84 /* return the -gtk form by default, since that's the most common case. 85 don't bother testing stat() on it since this is the last candidate 86 and we don't want to return NULL anyway */ 87 return tr_strdup_printf( "%s%c%s-%s", tr_getTorrentDir( session ), '/', inf->hashString, "gtk" ); 88} 89 90/* this is for really old versions of T and will probably be removed someday */ 91void 92tr_metainfoMigrate( tr_session * session, 93 tr_info * inf ) 94{ 95 struct stat new_sb; 96 char * name = getTorrentFilename( session, inf ); 97 98 if( stat( name, &new_sb ) || ( ( new_sb.st_mode & S_IFMT ) != S_IFREG ) ) 99 { 100 char * old_name = getOldTorrentFilename( session, inf ); 101 size_t contentLen; 102 uint8_t * content; 103 104 tr_mkdirp( tr_getTorrentDir( session ), 0777 ); 105 if( ( content = tr_loadFile( old_name, &contentLen ) ) ) 106 { 107 FILE * out; 108 errno = 0; 109 out = fopen( name, "wb+" ); 110 if( !out ) 111 { 112 tr_nerr( inf->name, _( "Couldn't create \"%1$s\": %2$s" ), 113 name, tr_strerror( errno ) ); 114 } 115 else 116 { 117 if( fwrite( content, sizeof( uint8_t ), contentLen, out ) 118 == contentLen ) 119 { 120 tr_free( inf->torrent ); 121 inf->torrent = tr_strdup( name ); 122 tr_sessionSetTorrentFile( session, inf->hashString, name ); 123 unlink( old_name ); 124 } 125 fclose( out ); 126 } 127 } 128 129 tr_free( content ); 130 tr_free( old_name ); 131 } 132 133 tr_free( name ); 134} 135 136/*** 137**** 138***/ 139 140static bool 141path_is_suspicious( const char * path ) 142{ 143 return ( path == NULL ) 144 || ( strstr( path, "../" ) != NULL ); 145} 146 147static bool 148getfile( char ** setme, const char * root, tr_benc * path, struct evbuffer * buf ) 149{ 150 bool success = false; 151 152 if( tr_bencIsList( path ) ) 153 { 154 int i; 155 const int n = tr_bencListSize( path ); 156 157 evbuffer_drain( buf, evbuffer_get_length( buf ) ); 158 evbuffer_add( buf, root, strlen( root ) ); 159 for( i = 0; i < n; ++i ) 160 { 161 const char * str; 162 if( tr_bencGetStr( tr_bencListChild( path, i ), &str ) ) 163 { 164 evbuffer_add( buf, TR_PATH_DELIMITER_STR, 1 ); 165 evbuffer_add( buf, str, strlen( str ) ); 166 } 167 } 168 169 *setme = tr_utf8clean( (char*)evbuffer_pullup( buf, -1 ), evbuffer_get_length( buf ) ); 170 /* fprintf( stderr, "[%s]\n", *setme ); */ 171 success = true; 172 } 173 174 if( ( *setme != NULL ) && path_is_suspicious( *setme ) ) 175 { 176 tr_free( *setme ); 177 *setme = NULL; 178 success = false; 179 } 180 181 return success; 182} 183 184static const char* 185parseFiles( tr_info * inf, tr_benc * files, const tr_benc * length ) 186{ 187 int64_t len; 188 189 inf->totalSize = 0; 190 191 if( tr_bencIsList( files ) ) /* multi-file mode */ 192 { 193 tr_file_index_t i; 194 struct evbuffer * buf = evbuffer_new( ); 195 196 inf->isMultifile = 1; 197 inf->fileCount = tr_bencListSize( files ); 198 inf->files = tr_new0( tr_file, inf->fileCount ); 199 200 for( i = 0; i < inf->fileCount; ++i ) 201 { 202 tr_benc * file; 203 tr_benc * path; 204 205 file = tr_bencListChild( files, i ); 206 if( !tr_bencIsDict( file ) ) 207 return "files"; 208 209 if( !tr_bencDictFindList( file, "path.utf-8", &path ) ) 210 if( !tr_bencDictFindList( file, "path", &path ) ) 211 return "path"; 212 213 if( !getfile( &inf->files[i].name, inf->name, path, buf ) ) 214 return "path"; 215 216 if( !tr_bencDictFindInt( file, "length", &len ) ) 217 return "length"; 218 219 inf->files[i].length = len; 220 inf->totalSize += len; 221 } 222 223 evbuffer_free( buf ); 224 } 225 else if( tr_bencGetInt( length, &len ) ) /* single-file mode */ 226 { 227 if( path_is_suspicious( inf->name ) ) 228 return "path"; 229 230 inf->isMultifile = 0; 231 inf->fileCount = 1; 232 inf->files = tr_new0( tr_file, 1 ); 233 inf->files[0].name = tr_strdup( inf->name ); 234 inf->files[0].length = len; 235 inf->totalSize += len; 236 } 237 else 238 { 239 return "length"; 240 } 241 242 return NULL; 243} 244 245static char * 246tr_convertAnnounceToScrape( const char * announce ) 247{ 248 char * scrape = NULL; 249 const char * s; 250 251 /* To derive the scrape URL use the following steps: 252 * Begin with the announce URL. Find the last '/' in it. 253 * If the text immediately following that '/' isn't 'announce' 254 * it will be taken as a sign that that tracker doesn't support 255 * the scrape convention. If it does, substitute 'scrape' for 256 * 'announce' to find the scrape page. */ 257 if( ( ( s = strrchr( announce, '/' ) ) ) && !strncmp( ++s, "announce", 8 ) ) 258 { 259 const char * prefix = announce; 260 const size_t prefix_len = s - announce; 261 const char * suffix = s + 8; 262 const size_t suffix_len = strlen( suffix ); 263 const size_t alloc_len = prefix_len + 6 + suffix_len + 1; 264 char * walk = scrape = tr_new( char, alloc_len ); 265 memcpy( walk, prefix, prefix_len ); walk += prefix_len; 266 memcpy( walk, "scrape", 6 ); walk += 6; 267 memcpy( walk, suffix, suffix_len ); walk += suffix_len; 268 *walk++ = '\0'; 269 assert( walk - scrape == (int)alloc_len ); 270 } 271 /* Some torrents with UDP annouce URLs don't have /announce. */ 272 else if ( !strncmp( announce, "udp:", 4 ) ) 273 { 274 scrape = tr_strdup( announce ); 275 } 276 277 return scrape; 278} 279 280static const char* 281getannounce( tr_info * inf, tr_benc * meta ) 282{ 283 const char * str; 284 tr_tracker_info * trackers = NULL; 285 int trackerCount = 0; 286 tr_benc * tiers; 287 288 /* Announce-list */ 289 if( tr_bencDictFindList( meta, "announce-list", &tiers ) ) 290 { 291 int n; 292 int i, j, validTiers; 293 const int numTiers = tr_bencListSize( tiers ); 294 295 n = 0; 296 for( i = 0; i < numTiers; ++i ) 297 n += tr_bencListSize( tr_bencListChild( tiers, i ) ); 298 299 trackers = tr_new0( tr_tracker_info, n ); 300 301 for( i = 0, validTiers = 0; i < numTiers; ++i ) 302 { 303 tr_benc * tier = tr_bencListChild( tiers, i ); 304 const int tierSize = tr_bencListSize( tier ); 305 bool anyAdded = false; 306 for( j = 0; j < tierSize; ++j ) 307 { 308 if( tr_bencGetStr( tr_bencListChild( tier, j ), &str ) ) 309 { 310 char * url = tr_strstrip( tr_strdup( str ) ); 311 if( !tr_urlIsValidTracker( url ) ) 312 tr_free( url ); 313 else { 314 tr_tracker_info * t = trackers + trackerCount; 315 t->tier = validTiers; 316 t->announce = url; 317 t->scrape = tr_convertAnnounceToScrape( url ); 318 t->id = trackerCount; 319 320 anyAdded = true; 321 ++trackerCount; 322 } 323 } 324 } 325 326 if( anyAdded ) 327 ++validTiers; 328 } 329 330 /* did we use any of the tiers? */ 331 if( !trackerCount ) 332 { 333 tr_free( trackers ); 334 trackers = NULL; 335 } 336 } 337 338 /* Regular announce value */ 339 if( !trackerCount 340 && tr_bencDictFindStr( meta, "announce", &str ) ) 341 { 342 char * url = tr_strstrip( tr_strdup( str ) ); 343 if( !tr_urlIsValidTracker( url ) ) 344 tr_free( url ); 345 else { 346 trackers = tr_new0( tr_tracker_info, 1 ); 347 trackers[trackerCount].tier = 0; 348 trackers[trackerCount].announce = url; 349 trackers[trackerCount].scrape = tr_convertAnnounceToScrape( url ); 350 trackers[trackerCount].id = 0; 351 trackerCount++; 352 /*fprintf( stderr, "single announce: [%s]\n", url );*/ 353 } 354 } 355 356 inf->trackers = trackers; 357 inf->trackerCount = trackerCount; 358 359 return NULL; 360} 361 362/** 363 * @brief Ensure that the URLs for multfile torrents end in a slash. 364 * 365 * See http://bittorrent.org/beps/bep_0019.html#metadata-extension 366 * for background on how the trailing slash is used for "url-list" 367 * fields. 368 * 369 * This function is to workaround some .torrent generators, such as 370 * mktorrent and very old versions of utorrent, that don't add the 371 * trailing slash for multifile torrents if omitted by the end user. 372 */ 373static char* 374fix_webseed_url( const tr_info * inf, const char * url_in ) 375{ 376 size_t len; 377 char * url; 378 char * ret = NULL; 379 380 url = tr_strdup( url_in ); 381 tr_strstrip( url ); 382 len = strlen( url ); 383 384 if( tr_urlIsValid( url, len ) ) 385 { 386 if( ( inf->fileCount > 1 ) && ( len > 0 ) && ( url[len-1] != '/' ) ) 387 ret = tr_strdup_printf( "%*.*s/", (int)len, (int)len, url ); 388 else 389 ret = tr_strndup( url, len ); 390 } 391 392 tr_free( url ); 393 return ret; 394} 395 396static void 397geturllist( tr_info * inf, 398 tr_benc * meta ) 399{ 400 tr_benc * urls; 401 const char * url; 402 403 if( tr_bencDictFindList( meta, "url-list", &urls ) ) 404 { 405 int i; 406 const int n = tr_bencListSize( urls ); 407 408 inf->webseedCount = 0; 409 inf->webseeds = tr_new0( char*, n ); 410 411 for( i = 0; i < n; ++i ) 412 { 413 if( tr_bencGetStr( tr_bencListChild( urls, i ), &url ) ) 414 { 415 char * fixed_url = fix_webseed_url( inf, url ); 416 417 if( fixed_url != NULL ) 418 inf->webseeds[inf->webseedCount++] = fixed_url; 419 } 420 } 421 } 422 else if( tr_bencDictFindStr( meta, "url-list", &url ) ) /* handle single items in webseeds */ 423 { 424 char * fixed_url = fix_webseed_url( inf, url ); 425 426 if( fixed_url != NULL ) 427 { 428 inf->webseedCount = 1; 429 inf->webseeds = tr_new0( char*, 1 ); 430 inf->webseeds[0] = fixed_url; 431 } 432 } 433} 434 435static const char* 436tr_metainfoParseImpl( const tr_session * session, 437 tr_info * inf, 438 bool * hasInfoDict, 439 int * infoDictLength, 440 const tr_benc * meta_in ) 441{ 442 int64_t i; 443 size_t raw_len; 444 const char * str; 445 const uint8_t * raw; 446 tr_benc * d; 447 tr_benc * infoDict = NULL; 448 tr_benc * meta = (tr_benc *) meta_in; 449 bool b; 450 bool isMagnet = false; 451 452 /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key 453 * from the Metainfo file. Note that the value will be a bencoded 454 * dictionary, given the definition of the info key above. */ 455 b = tr_bencDictFindDict( meta, "info", &infoDict ); 456 if( hasInfoDict != NULL ) 457 *hasInfoDict = b; 458 if( !b ) 459 { 460 /* no info dictionary... is this a magnet link? */ 461 if( tr_bencDictFindDict( meta, "magnet-info", &d ) ) 462 { 463 isMagnet = true; 464 465 /* get the info-hash */ 466 if( !tr_bencDictFindRaw( d, "info_hash", &raw, &raw_len ) ) 467 return "info_hash"; 468 if( raw_len != SHA_DIGEST_LENGTH ) 469 return "info_hash"; 470 memcpy( inf->hash, raw, raw_len ); 471 tr_sha1_to_hex( inf->hashString, inf->hash ); 472 473 /* maybe get the display name */ 474 if( tr_bencDictFindStr( d, "display-name", &str ) ) { 475 tr_free( inf->name ); 476 inf->name = tr_strdup( str ); 477 } 478 479 if( !inf->name ) 480 inf->name = tr_strdup( inf->hashString ); 481 } 482 else /* not a magnet link and has no info dict... */ 483 { 484 return "info"; 485 } 486 } 487 else 488 { 489 int len; 490 char * bstr = tr_bencToStr( infoDict, TR_FMT_BENC, &len ); 491 tr_sha1( inf->hash, bstr, len, NULL ); 492 tr_sha1_to_hex( inf->hashString, inf->hash ); 493 494 if( infoDictLength != NULL ) 495 *infoDictLength = len; 496 497 tr_free( bstr ); 498 } 499 500 /* name */ 501 if( !isMagnet ) { 502 if( !tr_bencDictFindStr( infoDict, "name.utf-8", &str ) ) 503 if( !tr_bencDictFindStr( infoDict, "name", &str ) ) 504 str = ""; 505 if( !str || !*str ) 506 return "name"; 507 tr_free( inf->name ); 508 inf->name = tr_utf8clean( str, -1 ); 509 } 510 511 /* comment */ 512 if( !tr_bencDictFindStr( meta, "comment.utf-8", &str ) ) 513 if( !tr_bencDictFindStr( meta, "comment", &str ) ) 514 str = ""; 515 tr_free( inf->comment ); 516 inf->comment = tr_utf8clean( str, -1 ); 517 518 /* created by */ 519 if( !tr_bencDictFindStr( meta, "created by.utf-8", &str ) ) 520 if( !tr_bencDictFindStr( meta, "created by", &str ) ) 521 str = ""; 522 tr_free( inf->creator ); 523 inf->creator = tr_utf8clean( str, -1 ); 524 525 /* creation date */ 526 if( !tr_bencDictFindInt( meta, "creation date", &i ) ) 527 i = 0; 528 inf->dateCreated = i; 529 530 /* private */ 531 if( !tr_bencDictFindInt( infoDict, "private", &i ) ) 532 if( !tr_bencDictFindInt( meta, "private", &i ) ) 533 i = 0; 534 inf->isPrivate = i != 0; 535 536 /* piece length */ 537 if( !isMagnet ) { 538 if( !tr_bencDictFindInt( infoDict, "piece length", &i ) || ( i < 1 ) ) 539 return "piece length"; 540 inf->pieceSize = i; 541 } 542 543 /* pieces */ 544 if( !isMagnet ) { 545 if( !tr_bencDictFindRaw( infoDict, "pieces", &raw, &raw_len ) ) 546 return "pieces"; 547 if( raw_len % SHA_DIGEST_LENGTH ) 548 return "pieces"; 549 inf->pieceCount = raw_len / SHA_DIGEST_LENGTH; 550 inf->pieces = tr_new0( tr_piece, inf->pieceCount ); 551 for( i = 0; i < inf->pieceCount; ++i ) 552 memcpy( inf->pieces[i].hash, &raw[i * SHA_DIGEST_LENGTH], 553 SHA_DIGEST_LENGTH ); 554 } 555 556 /* files */ 557 if( !isMagnet ) { 558 if( ( str = parseFiles( inf, tr_bencDictFind( infoDict, "files" ), 559 tr_bencDictFind( infoDict, "length" ) ) ) ) 560 return str; 561 if( !inf->fileCount || !inf->totalSize ) 562 return "files"; 563 if( (uint64_t) inf->pieceCount != 564 ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize ) 565 return "files"; 566 } 567 568 /* get announce or announce-list */ 569 if( ( str = getannounce( inf, meta ) ) ) 570 return str; 571 572 /* get the url-list */ 573 geturllist( inf, meta ); 574 575 /* filename of Transmission's copy */ 576 tr_free( inf->torrent ); 577 inf->torrent = session ? getTorrentFilename( session, inf ) : NULL; 578 579 return NULL; 580} 581 582bool 583tr_metainfoParse( const tr_session * session, 584 const tr_benc * meta_in, 585 tr_info * inf, 586 bool * hasInfoDict, 587 int * infoDictLength ) 588{ 589 const char * badTag = tr_metainfoParseImpl( session, 590 inf, 591 hasInfoDict, 592 infoDictLength, 593 meta_in ); 594 const bool success = badTag == NULL; 595 596 if( badTag ) 597 { 598 tr_nerr( inf->name, _( "Invalid metadata entry \"%s\"" ), badTag ); 599 tr_metainfoFree( inf ); 600 } 601 602 return success; 603} 604 605void 606tr_metainfoFree( tr_info * inf ) 607{ 608 int i; 609 tr_file_index_t ff; 610 611 for( i = 0; i < inf->webseedCount; ++i ) 612 tr_free( inf->webseeds[i] ); 613 614 for( ff = 0; ff < inf->fileCount; ++ff ) 615 tr_free( inf->files[ff].name ); 616 617 tr_free( inf->webseeds ); 618 tr_free( inf->pieces ); 619 tr_free( inf->files ); 620 tr_free( inf->comment ); 621 tr_free( inf->creator ); 622 tr_free( inf->torrent ); 623 tr_free( inf->name ); 624 625 for( i = 0; i < inf->trackerCount; ++i ) 626 { 627 tr_free( inf->trackers[i].announce ); 628 tr_free( inf->trackers[i].scrape ); 629 } 630 tr_free( inf->trackers ); 631 632 memset( inf, '\0', sizeof( tr_info ) ); 633} 634 635void 636tr_metainfoRemoveSaved( const tr_session * session, const tr_info * inf ) 637{ 638 char * filename; 639 640 filename = getTorrentFilename( session, inf ); 641 unlink( filename ); 642 tr_free( filename ); 643 644 filename = getOldTorrentFilename( session, inf ); 645 unlink( filename ); 646 tr_free( filename ); 647} 648