示例#1
0
static const char*
parseFiles (tr_info * inf, tr_benc * files, const tr_benc * length)
{
  int64_t len;

  inf->totalSize = 0;

  if (tr_bencIsList (files)) /* multi-file mode */
    {
      tr_file_index_t i;
      struct evbuffer * buf = evbuffer_new ();

      inf->isMultifile = 1;
      inf->fileCount = tr_bencListSize (files);
      inf->files = tr_new0 (tr_file, inf->fileCount);

      for (i=0; i<inf->fileCount; i++)
        {
          tr_benc * file;
          tr_benc * path;

          file = tr_bencListChild (files, i);
          if (!tr_bencIsDict (file))
            return "files";

          if (!tr_bencDictFindList (file, "path.utf-8", &path))
            if (!tr_bencDictFindList (file, "path", &path))
              return "path";

          if (!getfile (&inf->files[i].name, inf->name, path, buf))
            return "path";

          if (!tr_bencDictFindInt (file, "length", &len))
            return "length";

          inf->files[i].length = len;
          inf->totalSize      += len;
        }

      evbuffer_free (buf);
    }
  else if (tr_bencGetInt (length, &len)) /* single-file mode */
    {
      if (path_is_suspicious (inf->name))
        return "path";

      inf->isMultifile      = 0;
      inf->fileCount        = 1;
      inf->files            = tr_new0 (tr_file, 1);
      inf->files[0].name    = tr_strdup (inf->name);
      inf->files[0].length  = len;
      inf->totalSize       += len;
    }
  else
    {
      return "length";
    }

  return NULL;
}
示例#2
0
static void
geturllist( tr_info * inf,
            tr_benc * meta )
{
    tr_benc * urls;
    const char * url;

    if( tr_bencDictFindList( meta, "url-list", &urls ) )
    {
        int          i;
        const int    n = tr_bencListSize( urls );

        inf->webseedCount = 0;
        inf->webseeds = tr_new0( char*, n );

        for( i = 0; i < n; ++i )
        {
            if( tr_bencGetStr( tr_bencListChild( urls, i ), &url ) )
            {
                const size_t len = strlen( url );

                if( tr_urlIsValid( url, len  ) )
                    inf->webseeds[inf->webseedCount++] = tr_strndup( url, len );
            }
        }
    }
示例#3
0
static tr_bool
addURL( tr_benc * metainfo, const char * url )
{
    const char * str;
    tr_benc * announce_list;
    tr_bool changed = FALSE;
    tr_bool match = FALSE;

    /* maybe add it to "announce" */
    if( !tr_bencDictFindStr( metainfo, "announce", &str ) )
    {
        printf( "\tAdded \"%s\" in \"announce\"\n", url );
        tr_bencDictAddStr( metainfo, "announce", url );
        changed = TRUE;
    }

    /* see if it's already in announce-list */
    if( tr_bencDictFindList( metainfo, "announce-list", &announce_list ) ) {
        tr_benc * tier;
        int tierCount = 0;
        while(( tier = tr_bencListChild( announce_list, tierCount++ ))) {
            tr_benc * node;
            int nodeCount = 0;
            while(( node = tr_bencListChild( tier, nodeCount++ )))
                if( tr_bencGetStr( node, &str ) && !strcmp( str, url ) )
                    match = TRUE;
        }
    }

    /* if it's not in announce-list, add it now */
    if( !match )
    {
        tr_benc * tier;

        if( !tr_bencDictFindList( metainfo, "announce-list", &announce_list ) )
            announce_list = tr_bencDictAddList( metainfo, "announce-list", 1 );

        tier = tr_bencListAddList( announce_list, 1 );
        tr_bencListAddStr( tier, url );
        printf( "\tAdded \"%s\" to \"announce-list\" tier %zu\n", url, tr_bencListSize( announce_list ) );
        changed = TRUE;
    }

    return changed;
}
示例#4
0
static uint64_t
loadDND( tr_benc *    dict,
         tr_torrent * tor )
{
    uint64_t              ret = 0;
    tr_info *             inf = &tor->info;
    const tr_file_index_t n = inf->fileCount;
    tr_benc *             list = NULL;

    if( tr_bencDictFindList( dict, KEY_DND, &list )
      && ( tr_bencListSize( list ) == n ) )
    {
        int64_t           tmp;
        tr_file_index_t * dl = tr_new( tr_file_index_t, n );
        tr_file_index_t * dnd = tr_new( tr_file_index_t, n );
        tr_file_index_t   i, dlCount = 0, dndCount = 0;

        for( i = 0; i < n; ++i )
        {
            if( tr_bencGetInt( tr_bencListChild( list, i ), &tmp ) && tmp )
                dnd[dndCount++] = i;
            else
                dl[dlCount++] = i;
        }

        if( dndCount )
        {
            tr_torrentInitFileDLs ( tor, dnd, dndCount, FALSE );
            tr_tordbg( tor, "Resume file found %d files listed as dnd",
                       dndCount );
        }
        if( dlCount )
        {
            tr_torrentInitFileDLs ( tor, dl, dlCount, TRUE );
            tr_tordbg( tor,
                       "Resume file found %d files marked for download",
                       dlCount );
        }

        tr_free( dnd );
        tr_free( dl );
        ret = TR_FR_DND;
    }
    else
    {
        tr_tordbg(
            tor,
            "Couldn't load DND flags. DND list (%p) has %zu children; torrent has %d files",
            list, tr_bencListSize( list ), (int)n );
    }

    return ret;
}
示例#5
0
static bool
addURL (tr_benc * metainfo, const char * url)
{
  const char * announce = NULL;
  tr_benc * announce_list = NULL;
  bool changed = false;
  const bool had_announce = tr_bencDictFindStr (metainfo, "announce", &announce);
  const bool had_announce_list = tr_bencDictFindList (metainfo, "announce-list", &announce_list);

  if (!had_announce && !had_announce_list)
    {
      /* this new tracker is the only one, so add it to "announce"... */
      printf ("\tAdded \"%s\" in \"announce\"\n", url);
      tr_bencDictAddStr (metainfo, "announce", url);
      changed = true;
    }
  else
    {
      if (!had_announce_list)
        {
          announce_list = tr_bencDictAddList (metainfo, "announce-list", 2);

          if (had_announce)
            {
              /* we're moving from an 'announce' to an 'announce-list',
               * so copy the old announce URL to the list */
              tr_benc * tier = tr_bencListAddList (announce_list, 1);
              tr_bencListAddStr (tier, announce);
              changed = true;
            }
        }

      /* If the user-specified URL isn't in the announce list yet, add it */
      if (!announce_list_has_url (announce_list, url))
        {
          tr_benc * tier = tr_bencListAddList (announce_list, 1);
          tr_bencListAddStr (tier, url);
          printf ("\tAdded \"%s\" to \"announce-list\" tier %zu\n", url, tr_bencListSize (announce_list));
          changed = true;
        }
    }

  return changed;
}
示例#6
0
static uint64_t
loadFilePriorities( tr_benc * dict, tr_torrent * tor )
{
    tr_benc * list;
    uint64_t ret = 0;
    const tr_file_index_t n = tor->info.fileCount;

    if( tr_bencDictFindList( dict, KEY_FILE_PRIORITIES, &list )
      && ( tr_bencListSize( list ) == n ) )
    {
        int64_t priority;
        tr_file_index_t i;
        for( i = 0; i < n; ++i )
            if( tr_bencGetInt( tr_bencListChild( list, i ), &priority ) )
                tr_torrentInitFilePriority( tor, i, priority );
        ret = TR_FR_FILE_PRIORITIES;
    }

    return ret;
}
示例#7
0
static bool
replaceURL (tr_benc * metainfo, const char * in, const char * out)
{
  const char * str;
  tr_benc * announce_list;
  bool changed = false;

  if (tr_bencDictFindStr (metainfo, "announce", &str) && strstr (str, in))
    {
      char * newstr = replaceSubstr (str, in, out);
      printf ("\tReplaced in \"announce\": \"%s\" --> \"%s\"\n", str, newstr);
      tr_bencDictAddStr (metainfo, "announce", newstr);
      tr_free (newstr);
      changed = true;
    }

  if (tr_bencDictFindList (metainfo, "announce-list", &announce_list))
    {
      tr_benc * tier;
      int tierCount = 0;
      while ((tier = tr_bencListChild (announce_list, tierCount++)))
        {
          tr_benc * node;
          int nodeCount = 0;
          while ((node = tr_bencListChild (tier, nodeCount++)))
            {
              if (tr_bencGetStr (node, &str) && strstr (str, in))
                {
                  char * newstr = replaceSubstr (str, in, out);
                  printf ("\tReplaced in \"announce-list\" tier %d: \"%s\" --> \"%s\"\n", tierCount, str, newstr);
                  tr_bencFree (node);
                  tr_bencInitStr (node, newstr, -1);
                  tr_free (newstr);
                  changed = true;
                }
            }
        }
    }

  return changed;
}
示例#8
0
static uint64_t
loadPriorities( tr_benc *    dict,
                tr_torrent * tor )
{
    uint64_t              ret = 0;
    tr_info *             inf = &tor->info;
    const tr_file_index_t n = inf->fileCount;
    tr_benc *             list;

    if( tr_bencDictFindList( dict, KEY_PRIORITY, &list )
      && ( tr_bencListSize( list ) == n ) )
    {
        int64_t         tmp;
        tr_file_index_t i;
        for( i = 0; i < n; ++i )
            if( tr_bencGetInt( tr_bencListChild( list, i ), &tmp ) )
                inf->files[i].priority = tmp;
        ret = TR_FR_PRIORITY;
    }

    return ret;
}
示例#9
0
static uint64_t
loadProgress( tr_benc * dict, tr_torrent * tor )
{
    size_t i, n;
    uint64_t ret = 0;
    tr_benc * prog;
    const tr_info * inf = tr_torrentInfo( tor );

    for( i=0, n=inf->pieceCount; i<n; ++i )
        inf->pieces[i].timeChecked = 0;

    if( tr_bencDictFindDict( dict, KEY_PROGRESS, &prog ) )
    {
        const char * err;
        const char * str;
        const uint8_t * raw;
        size_t rawlen;
        tr_benc * l;
        tr_benc * b;
        struct tr_bitset bitset = TR_BITSET_INIT;

        if( tr_bencDictFindList( prog, KEY_PROGRESS_CHECKTIME, &l ) )
        {
            /* per-piece timestamps were added in 2.20.
              
               If some of a file's pieces have been checked more recently than
               the file's mtime, and some lest recently, then that file will
               have a list containing timestamps for each piece.
              
               However, the most common use case is that the file doesn't change
               after it's downloaded. To reduce overhead in the .resume file,
               only a single timestamp is saved for the file if *all* or *none*
               of the pieces were tested more recently than the file's mtime. */

            tr_file_index_t fi;

            for( fi=0; fi<inf->fileCount; ++fi )
            {
                tr_benc * b = tr_bencListChild( l, fi );
                const tr_file * f = &inf->files[fi];
                tr_piece * p = &inf->pieces[f->firstPiece];
                const tr_piece * pend = &inf->pieces[f->lastPiece]+1;

                if( tr_bencIsInt( b ) )
                {
                    int64_t t;
                    tr_bencGetInt( b, &t );
                    for( ; p!=pend; ++p )
                        p->timeChecked = (time_t)t;
                }
                else if( tr_bencIsList( b ) )
                {
                    int i = 0;
                    int64_t offset = 0;
                    const int pieces = f->lastPiece + 1 - f->firstPiece;

                    tr_bencGetInt( tr_bencListChild( b, 0 ), &offset );

                    for( i=0; i<pieces; ++i )
                    {
                        int64_t t = 0;
                        tr_bencGetInt( tr_bencListChild( b, i+1 ), &t );
                        inf->pieces[f->firstPiece+i].timeChecked = (time_t)(t ? t + offset : 0);
                    }
                }
            }
        }
        else if( tr_bencDictFindList( prog, KEY_PROGRESS_MTIMES, &l ) )
        {
            tr_file_index_t fi;

            /* Before 2.20, we stored the files' mtimes in the .resume file.
               When loading the .resume file, a torrent's file would be flagged
               as untested if its stored mtime didn't match its real mtime. */

            for( fi=0; fi<inf->fileCount; ++fi )
            {
                int64_t t;

                if( tr_bencGetInt( tr_bencListChild( l, fi ), &t ) )
                {
                    const tr_file * f = &inf->files[fi];
                    tr_piece * p = &inf->pieces[f->firstPiece];
                    const tr_piece * pend = &inf->pieces[f->lastPiece];
                    const time_t mtime = tr_torrentGetFileMTime( tor, fi );
                    const time_t timeChecked = mtime==t ? mtime : 0;

                    for( ; p!=pend; ++p )
                        p->timeChecked = timeChecked;
                }
            }
        }

        err = NULL;

        if(( b = tr_bencDictFind( prog, KEY_PROGRESS_BLOCKS )))
        {
            if( !tr_bitsetFromBenc( &bitset, b ) )
                err = "Invalid value for PIECES";
        }
        else if( tr_bencDictFindStr( prog, KEY_PROGRESS_HAVE, &str ) )
        {
            if( !strcmp( str, "all" ) )
                tr_bitsetSetHaveAll( &bitset );
            else
                err = "Invalid value for HAVE";
        }
        else if( tr_bencDictFindRaw( prog, KEY_PROGRESS_BITFIELD, &raw, &rawlen ) )
        {
            bitset.bitfield.bits = (void*) raw;
            bitset.bitfield.byteCount = rawlen;
            bitset.bitfield.bitCount = rawlen * 8;
        }
        else err = "Couldn't find 'pieces' or 'have' or 'bitfield'";

        if( !err && !tr_cpBlockBitsetInit( &tor->completion, &bitset ) )
            err = "Error loading bitfield";
        if( err != NULL )
            tr_tordbg( tor, "Torrent needs to be verified - %s", err );

        ret = TR_FR_PROGRESS;
    }

    return ret;
}
示例#10
0
static void
on_announce_done( tr_session   * session,
                  bool           did_connect,
                  bool           did_timeout,
                  long           response_code,
                  const void   * msg,
                  size_t         msglen,
                  void         * vdata )
{
    tr_announce_response * response;
    struct announce_data * data = vdata;

    response = &data->response;
    response->did_connect = did_connect;
    response->did_timeout = did_timeout;
    dbgmsg( data->log_name, "Got announce response" );

    if( response_code != HTTP_OK )
    {
        const char * fmt = _( "Tracker gave HTTP response code %1$ld (%2$s)" );
        const char * response_str = tr_webGetResponseStr( response_code );
        response->errmsg = tr_strdup_printf( fmt, response_code, response_str );
    }
    else
    {
        tr_benc benc;
        const int benc_loaded = !tr_bencLoad( msg, msglen, &benc, NULL );

        if( getenv( "TR_CURL_VERBOSE" ) != NULL )
        {
            struct evbuffer * buf = tr_bencToBuf( &benc, TR_FMT_JSON );
            fprintf( stderr, "Announce response:\n< %s\n", evbuffer_pullup( buf, -1 ) );
            tr_free( buf );
        }

        if( benc_loaded && tr_bencIsDict( &benc ) )
        {
            int64_t i;
            size_t rawlen;
            tr_benc * tmp;
            const char * str;
            const uint8_t * raw;

            if( tr_bencDictFindStr( &benc, "failure reason", &str ) )
                response->errmsg = tr_strdup( str );

            if( tr_bencDictFindStr( &benc, "warning message", &str ) )
                response->warning = tr_strdup( str );

            if( tr_bencDictFindInt( &benc, "interval", &i ) )
                response->interval = i;

            if( tr_bencDictFindInt( &benc, "min interval", &i ) )
                response->min_interval = i;

            if( tr_bencDictFindStr( &benc, "tracker id", &str ) )
                response->tracker_id_str = tr_strdup( str );

            if( tr_bencDictFindInt( &benc, "complete", &i ) )
                response->seeders = i;

            if( tr_bencDictFindInt( &benc, "incomplete", &i ) )
                response->leechers = i;

            if( tr_bencDictFindInt( &benc, "downloaded", &i ) )
                response->downloads = i;

            if( tr_bencDictFindRaw( &benc, "peers6", &raw, &rawlen ) ) {
                dbgmsg( data->log_name, "got a peers6 length of %zu", rawlen );
                response->pex6 = tr_peerMgrCompact6ToPex( raw, rawlen,
                                              NULL, 0, &response->pex6_count );
            }

            if( tr_bencDictFindRaw( &benc, "peers", &raw, &rawlen ) ) {
                dbgmsg( data->log_name, "got a compact peers length of %zu", rawlen );
                response->pex = tr_peerMgrCompactToPex( raw, rawlen,
                                               NULL, 0, &response->pex_count );
            } else if( tr_bencDictFindList( &benc, "peers", &tmp ) ) {
                response->pex = listToPex( tmp, &response->pex_count );
                dbgmsg( data->log_name, "got a peers list with %zu entries",
                        response->pex_count );
            }
        }

        if( benc_loaded )
            tr_bencFree( &benc );
    }

    tr_runInEventThread( session, on_announce_done_eventthread, data );
}
示例#11
0
static bool
removeURL (tr_benc * metainfo, const char * url)
{
  const char * str;
  tr_benc * announce_list;
  bool changed = false;

  if (tr_bencDictFindStr (metainfo, "announce", &str) && !strcmp (str, url))
    {
      printf ("\tRemoved \"%s\" from \"announce\"\n", str);
      tr_bencDictRemove (metainfo, "announce");
      changed = true;
    }

  if (tr_bencDictFindList (metainfo, "announce-list", &announce_list))
    {
      tr_benc * tier;
      int tierIndex = 0;
      while ((tier = tr_bencListChild (announce_list, tierIndex)))
        {
          tr_benc * node;
          int nodeIndex = 0;
          while ((node = tr_bencListChild (tier, nodeIndex)))
            {
              if (tr_bencGetStr (node, &str) && !strcmp (str, url))
                {
                  printf ("\tRemoved \"%s\" from \"announce-list\" tier #%d\n", str, (tierIndex+1));
                  tr_bencListRemove (tier, nodeIndex);
                  changed = true;
                }
              else ++nodeIndex;
            }

          if (tr_bencListSize (tier) == 0)
            {
              printf ("\tNo URLs left in tier #%d... removing tier\n", (tierIndex+1));
              tr_bencListRemove (announce_list, tierIndex);
            }
          else
            {
              ++tierIndex;
            }
        }

      if (tr_bencListSize (announce_list) == 0)
        {
          printf ("\tNo tiers left... removing announce-list\n");
          tr_bencDictRemove (metainfo, "announce-list");
        }
    }

  /* if we removed the "announce" field and there's still another track left,
   * use it as the "announce" field */
  if (changed && !tr_bencDictFindStr (metainfo, "announce", &str))
    {
      tr_benc * tier;
      tr_benc * node;

      if ((tier = tr_bencListChild (announce_list, 0)))
        {
          if ((node = tr_bencListChild (tier, 0)))
            {
              if (tr_bencGetStr (node, &str))
                {
                  tr_bencDictAddStr (metainfo, "announce", str);
                  printf ("\tAdded \"%s\" to announce\n", str);
                }
            }
        }
    }

  return changed;
}
示例#12
0
static const char*
getannounce( tr_info * inf, tr_benc * meta )
{
    const char *      str;
    tr_tracker_info * trackers = NULL;
    int               trackerCount = 0;
    tr_benc *         tiers;

    /* Announce-list */
    if( tr_bencDictFindList( meta, "announce-list", &tiers ) )
    {
        int       n;
        int       i, j, validTiers;
        const int numTiers = tr_bencListSize( tiers );

        n = 0;
        for( i = 0; i < numTiers; ++i )
            n += tr_bencListSize( tr_bencListChild( tiers, i ) );

        trackers = tr_new0( tr_tracker_info, n );

        for( i = 0, validTiers = 0; i < numTiers; ++i )
        {
            tr_benc * tier = tr_bencListChild( tiers, i );
            const int tierSize = tr_bencListSize( tier );
            tr_bool anyAdded = FALSE;
            for( j = 0; j < tierSize; ++j )
            {
                if( tr_bencGetStr( tr_bencListChild( tier, j ), &str ) )
                {
                    char * url = tr_strstrip( tr_strdup( str ) );
                    if( tr_urlIsValidTracker( url ) )
                    {
                        tr_tracker_info * t = trackers + trackerCount;
                        t->tier = validTiers;
                        t->announce = tr_strdup( url );
                        t->scrape = tr_convertAnnounceToScrape( url );
                        t->id = trackerCount;

                        anyAdded = TRUE;
                        ++trackerCount;
                    }
                    tr_free( url );
                }
            }

            if( anyAdded )
                ++validTiers;
        }

        /* did we use any of the tiers? */
        if( !trackerCount )
        {
            tr_free( trackers );
            trackers = NULL;
        }
    }

    /* Regular announce value */
    if( !trackerCount
      && tr_bencDictFindStr( meta, "announce", &str ) )
    {
        char * url = tr_strstrip( tr_strdup( str ) );
        if( tr_urlIsValidTracker( url ) )
        {
            trackers = tr_new0( tr_tracker_info, 1 );
            trackers[trackerCount].tier = 0;
            trackers[trackerCount].announce = tr_strdup( url );
            trackers[trackerCount].scrape = tr_convertAnnounceToScrape( url );
            trackers[trackerCount].id = 0;
            trackerCount++;
            /*fprintf( stderr, "single announce: [%s]\n", url );*/
        }
        tr_free( url );
    }

    inf->trackers = trackers;
    inf->trackerCount = trackerCount;

    return NULL;
}
示例#13
0
static uint64_t
loadProgress( tr_benc *    dict,
              tr_torrent * tor )
{
    uint64_t  ret = 0;
    tr_benc * p;

    if( tr_bencDictFindDict( dict, KEY_PROGRESS, &p ) )
    {
        const uint8_t * raw;
        size_t          rawlen;
        tr_benc *       m;
        size_t          n;
        time_t *        curMTimes = tr_torrentGetMTimes( tor, &n );

        if( tr_bencDictFindList( p, KEY_PROGRESS_MTIMES, &m )
          && ( n == tor->info.fileCount )
          && ( n == tr_bencListSize( m ) ) )
        {
            size_t i;
            for( i = 0; i < n; ++i )
            {
                int64_t tmp;
                if( !tr_bencGetInt( tr_bencListChild( m, i ), &tmp ) )
                {
                    tr_tordbg(
                        tor,
                        "File #%zu needs to be verified - couldn't find benc entry",
                        i );
                    tr_torrentSetFileChecked( tor, i, FALSE );
                }
                else
                {
                    const time_t t = (time_t) tmp;
                    if( t == curMTimes[i] )
                        tr_torrentSetFileChecked( tor, i, TRUE );
                    else
                    {
                        tr_tordbg(
                            tor,
                            "File #%zu needs to be verified - times %lu and %lu don't match",
                            i, t, curMTimes[i] );
                        tr_torrentSetFileChecked( tor, i, FALSE );
                    }
                }
            }
        }
        else
        {
            tr_torrentUncheck( tor );
            tr_tordbg(
                tor, "Torrent needs to be verified - unable to find mtimes" );
        }

        if( tr_bencDictFindRaw( p, KEY_PROGRESS_BITFIELD, &raw, &rawlen ) )
        {
            tr_bitfield tmp;
            tmp.byteCount = rawlen;
            tmp.bitCount = tmp.byteCount * 8;
            tmp.bits = (uint8_t*) raw;
            if( !tr_cpBlockBitfieldSet( tor->completion, &tmp ) )
            {
                tr_torrentUncheck( tor );
                tr_tordbg(
                    tor,
                    "Torrent needs to be verified - error loading bitfield" );
            }
        }
        else
        {
            tr_torrentUncheck( tor );
            tr_tordbg(
                tor,
                "Torrent needs to be verified - unable to find bitfield" );
        }

        tr_free( curMTimes );
        ret = TR_FR_PROGRESS;
    }

    return ret;
}
示例#14
0
static const char*
getannounce( tr_info * inf,
             tr_benc * meta )
{
    const char *      str;
    tr_tracker_info * trackers = NULL;
    int               trackerCount = 0;
    tr_benc *         tiers;

    /* Announce-list */
    if( tr_bencDictFindList( meta, "announce-list", &tiers ) )
    {
        int       n;
        int       i, j;
        const int numTiers = tr_bencListSize( tiers );

        n = 0;
        for( i = 0; i < numTiers; ++i )
            n += tr_bencListSize( tr_bencListChild( tiers, i ) );

        trackers = tr_new0( tr_tracker_info, n );
        trackerCount = 0;

        for( i = 0; i < numTiers; ++i )
        {
            tr_benc * tier = tr_bencListChild( tiers, i );
            const int tierSize = tr_bencListSize( tier );
            for( j = 0; j < tierSize; ++j )
            {
                if( tr_bencGetStr( tr_bencListChild( tier, j ), &str ) )
                {
                    char * url = tr_strstrip( tr_strdup( str ) );
                    if( tr_httpIsValidURL( url ) )
                    {
                        tr_tracker_info * t = trackers + trackerCount++;
                        t->tier = i;
                        t->announce = tr_strdup( url );
                        t->scrape = announceToScrape( url );
                    }
                    tr_free( url );
                }
            }
        }

        /* did we use any of the tiers? */
        if( !trackerCount )
        {
            tr_free( trackers );
            trackers = NULL;
        }
    }

    /* Regular announce value */
    if( !trackerCount
      && tr_bencDictFindStr( meta, "announce", &str ) )
    {
        char * url = tr_strstrip( tr_strdup( str ) );
        if( tr_httpIsValidURL( url ) )
        {
            trackers = tr_new0( tr_tracker_info, 1 );
            trackers[trackerCount].tier = 0;
            trackers[trackerCount].announce = tr_strdup( url );
            trackers[trackerCount++].scrape = announceToScrape( url );
            /*fprintf( stderr, "single announce: [%s]\n", url );*/
        }
        tr_free( url );
    }

    inf->trackers = trackers;
    inf->trackerCount = trackerCount;

    return inf->trackerCount ? NULL : "announce";
}