From 7ef2511ca802c2826df4a414e021d14f3d8b2b6e Mon Sep 17 00:00:00 2001
From: Charles Kerr <charles@transmissionbt.com>
Date: Thu, 24 Apr 2008 01:42:53 +0000
Subject: [PATCH] #377: preliminary https support. this commit probably breaks
 mac and cli and is not for the faint of heart.

---
 cli/Makefile.am                |  16 +-
 configure.ac                   |   3 +
 daemon/Makefile.am             |   7 +-
 gtk/Makefile.am                |   2 +
 gtk/details.c                  |   9 +-
 gtk/tr-core.c                  |   4 +-
 gtk/tr-window.c                |   2 +-
 libtransmission/Makefile.am    |  14 +-
 libtransmission/ipcparse.c     |  24 +-
 libtransmission/metainfo.c     | 249 +++--------
 libtransmission/session.c      |  15 +-
 libtransmission/session.h      |  30 +-
 libtransmission/tracker.c      | 737 ++++++++++++---------------------
 libtransmission/transmission.h |  14 +-
 libtransmission/trevent.c      |   4 +-
 libtransmission/utils.c        |  69 ++-
 libtransmission/utils.h        |   4 +-
 libtransmission/web.c          | 256 ++++++++++++
 libtransmission/web.h          |  34 ++
 19 files changed, 748 insertions(+), 745 deletions(-)
 create mode 100644 libtransmission/web.c
 create mode 100644 libtransmission/web.h

diff --git a/cli/Makefile.am b/cli/Makefile.am
index c8a6ddbdf..96ecb9fb7 100644
--- a/cli/Makefile.am
+++ b/cli/Makefile.am
@@ -1,5 +1,5 @@
 AM_CPPFLAGS = -I$(top_srcdir) $(LIBEVENT_CPPFLAGS)
-AM_CFLAGS = $(OPENSSL_CFLAGS) $(PTHREAD_CFLAGS)
+AM_CFLAGS = $(OPENSSL_CFLAGS) $(LIBCURL_CFLAGS) $(PTHREAD_CFLAGS)
 
 bin_PROGRAMS = transmissioncli
 
@@ -8,9 +8,13 @@ dist_man_MANS = transmissioncli.1
 transmissioncli_SOURCES = transmissioncli.c
 
 transmissioncli_LDADD = \
-    $(top_builddir)/libtransmission/libtransmission.a \
-    $(top_builddir)/third-party/libevent/libevent.la \
-    $(top_builddir)/third-party/libnatpmp/libnatpmp.a \
-    $(top_builddir)/third-party/miniupnp/libminiupnp.a \
-    $(INTLLIBS) $(OPENSSL_LIBS) $(PTHREAD_LIBS) -lm
+  $(top_builddir)/libtransmission/libtransmission.a \
+  $(top_builddir)/third-party/libevent/libevent.la \
+  $(top_builddir)/third-party/libnatpmp/libnatpmp.a \
+  $(top_builddir)/third-party/miniupnp/libminiupnp.a \
+  $(INTLLIBS) \
+  $(OPENSSL_LIBS) \
+  $(LIBCURL_LIBS) \
+  $(PTHREAD_LIBS) \
+  -lm
 
diff --git a/configure.ac b/configure.ac
index f757500df..e74881282 100644
--- a/configure.ac
+++ b/configure.ac
@@ -10,12 +10,14 @@ AC_CONFIG_SRCDIR(libtransmission/transmission.h)
 AM_INIT_AUTOMAKE([1.9 tar-ustar])
 AC_PROG_LIBTOOL
 
+CURL_MINIMUM=7.16.0
 GIO_MINIMUM=2.15.5
 GLIB_MINIMUM=2.6.0
 GTK_MINIMUM=2.6.0
 WX_MINIMUM=2.6.0
 LIBNOTIFY_MINIMUM=0.4.4
 DBUS_GLIB_MINIMUM=0.70
+AC_SUBST(CURL_MINIMUM)
 AC_SUBST(GIO_MINIMUM)
 AC_SUBST(GLIB_MINIMUM)
 AC_SUBST(GTK_MINIMUM)
@@ -41,6 +43,7 @@ ACX_PTHREAD
 AC_SEARCH_LIBS([socket], [socket net])
 AC_SEARCH_LIBS([gethostbyname], [nsl bind])
 PKG_CHECK_MODULES(OPENSSL, [openssl >= 0.9.4])
+PKG_CHECK_MODULES(LIBCURL, [libcurl >= 0.9.4])
 
 AC_SYS_LARGEFILE
 
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index bbe720ae4..bfd06ede1 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -1,5 +1,5 @@
 AM_CPPFLAGS = -I@top_srcdir@ $(LIBEVENT_CPPFLAGS)
-AM_CFLAGS = $(OPENSSL_CFLAGS) $(PTHREAD_CFLAGS)
+AM_CFLAGS = $(OPENSSL_CFLAGS) $(LIBCURL_CFLAGS) $(PTHREAD_CFLAGS)
 
 noinst_LIBRARIES = libdaemon.a
 
@@ -32,7 +32,10 @@ COMMON_LDADD = \
     $(top_builddir)/third-party/miniupnp/libminiupnp.a \
     $(top_builddir)/third-party/libnatpmp/libnatpmp.a \
     $(top_builddir)/third-party/libevent/libevent.la \
-    $(INTLLIBS) $(OPENSSL_LIBS) $(PTHREAD_LIBS) -lm
+    $(INTLLIBS) \
+    $(OPENSSL_LIBS) \
+    $(LIBCURL_LIBS) \
+    $(PTHREAD_LIBS) -lm
 
 transmission_daemon_SOURCES = daemon.c server.c torrents.c
 transmission_daemon_LDADD = $(COMMON_LDADD)
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index f6b7f1782..a42816b2f 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -9,6 +9,7 @@ AM_CPPFLAGS = \
 AM_CFLAGS = \
     $(GTK_CFLAGS) \
     $(OPENSSL_CFLAGS) \
+    $(LIBCURL_CFLAGS) \
     $(PTHREAD_CFLAGS) \
     $(GIO_CFLAGS) \
     $(DBUS_GLIB_CFLAGS) \
@@ -78,6 +79,7 @@ transmission_LDADD = \
     $(LIBNOTIFY_LIBS) \
     $(DBUS_GLIB_LIBS) \
     $(OPENSSL_LIBS) \
+    $(LIBCURL_LIBS) \
     $(PTHREAD_LIBS) -lm
 
 DESKTOP_IN_FILES=transmission.desktop.in
diff --git a/gtk/details.c b/gtk/details.c
index eb5d68253..9ead17b40 100644
--- a/gtk/details.c
+++ b/gtk/details.c
@@ -1119,9 +1119,7 @@ tracker_page_new( TrTorrent * gtor )
     GtkWidget * l;
     int row = 0;
     const char * s;
-    char * tmp;
     struct tracker_page * page = g_new0( struct tracker_page, 1 );
-    const tr_tracker_info * track;
     const tr_info * info = tr_torrent_info (gtor);
 
     page->gtor = gtor;
@@ -1147,14 +1145,9 @@ tracker_page_new( TrTorrent * gtor )
     hig_workarea_add_section_divider( t, &row );
     hig_workarea_add_section_title( t, &row, _( "Announce" ) );
 
-        track = info->trackerList->list;
-        tmp = track->port==80
-          ? g_strdup_printf( "http://%s%s", track->address, track->announce )
-          : g_strdup_printf( "http://%s:%d%s", track->address, track->port, track->announce );
-        l = gtk_label_new( tmp );
+        l = gtk_label_new( info->trackers[0].announce );
         gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
         hig_workarea_add_row (t, &row, _( "Tracker:" ), l, NULL);
-        g_free( tmp );
 
         s = _( "Last announce at:" );
         l = gtk_label_new( NULL );
diff --git a/gtk/tr-core.c b/gtk/tr-core.c
index 7e2a0aca0..aeae38123 100644
--- a/gtk/tr-core.c
+++ b/gtk/tr-core.c
@@ -283,8 +283,8 @@ compareByTracker( GtkTreeModel   * model,
     const tr_torrent *ta, *tb;
     gtk_tree_model_get( model, a, MC_TORRENT_RAW, &ta, -1 );
     gtk_tree_model_get( model, b, MC_TORRENT_RAW, &tb, -1 );
-    return strcmp( tr_torrentInfo(ta)->primaryAddress,
-                   tr_torrentInfo(tb)->primaryAddress );
+    return strcmp( tr_torrentInfo(ta)->trackers[0].announce,
+                   tr_torrentInfo(tb)->trackers[0].announce );
 }
 
 static void
diff --git a/gtk/tr-window.c b/gtk/tr-window.c
index b02102205..26b0866e8 100644
--- a/gtk/tr-window.c
+++ b/gtk/tr-window.c
@@ -274,7 +274,7 @@ checkFilterText( filter_text_mode_t    filter_text_mode,
             break;
 
         case FILTER_TEXT_MODE_TRACKER:
-            pch = g_ascii_strdown( torInfo->primaryAddress, -1 );
+            pch = g_ascii_strdown( torInfo->trackers[0].announce, -1 );
             ret = !text || ( strstr( pch, text ) != NULL );
             g_free( pch );
             break;
diff --git a/libtransmission/Makefile.am b/libtransmission/Makefile.am
index d38216db8..09e762ad2 100644
--- a/libtransmission/Makefile.am
+++ b/libtransmission/Makefile.am
@@ -1,5 +1,5 @@
 AM_CPPFLAGS = -I. -I$(top_srcdir) -I$(top_srcdir)/third-party/ -D__TRANSMISSION__ $(LIBEVENT_CPPFLAGS)
-AM_CFLAGS = $(OPENSSL_CFLAGS) $(PTHREAD_CFLAGS)
+AM_CFLAGS = $(OPENSSL_CFLAGS) $(LIBCURL_CFLAGS) $(PTHREAD_CFLAGS)
 
 noinst_LIBRARIES = libtransmission.a
 
@@ -37,7 +37,8 @@ libtransmission_a_SOURCES = \
     trevent.c \
     upnp.c \
     utils.c \
-    verify.c
+    verify.c \
+    web.c
 
 noinst_HEADERS = \
     bencode.h \
@@ -75,7 +76,8 @@ noinst_HEADERS = \
     trevent.h \
     upnp.h \
     utils.h \
-    verify.h
+    verify.h \
+    web.h
 
 bin_PROGRAMS = benc2php
 
@@ -92,7 +94,11 @@ APPS_LDADD = \
     $(top_builddir)/third-party/miniupnp/libminiupnp.a \
     $(top_builddir)/third-party/libnatpmp/libnatpmp.a \
     $(top_builddir)/third-party/libevent/libevent.la \
-    $(INTLLIBS) $(OPENSSL_LIBS) $(PTHREAD_LIBS) -lm
+    $(INTLLIBS) \
+    $(OPENSSL_LIBS) \
+    $(LIBCURL_LIBS) \
+    $(PTHREAD_LIBS) \
+    -lm
 
 benc2php_SOURCES = benc2php.c
 benc2php_LDADD = $(APPS_LDADD)
diff --git a/libtransmission/ipcparse.c b/libtransmission/ipcparse.c
index a7e711ede..aac3e8b4f 100644
--- a/libtransmission/ipcparse.c
+++ b/libtransmission/ipcparse.c
@@ -531,8 +531,6 @@ static void
 filltracker( tr_benc * val, const tr_tracker_info * tk )
 {
     tr_bencInitDict( val, 4 );
-    tr_bencDictAddStr( val, "address",  tk->address );
-    tr_bencDictAddInt( val, "port",     tk->port );
     tr_bencDictAddStr( val, "announce", tk->announce );
     if( tk->scrape )
         tr_bencDictAddStr( val, "scrape", tk->scrape );
@@ -554,7 +552,7 @@ ipc_addinfo( tr_benc         * list,
              int               types )
 {
     tr_benc * dict;
-    int          ii, jj, kk;
+    int          ii, jj;
     tr_file_index_t ff;
     const tr_info * inf = tr_torrentInfo( tor );
 
@@ -628,17 +626,19 @@ ipc_addinfo( tr_benc         * list,
             case IPC_INF_SIZE:
                 tr_bencInitInt( item, inf->totalSize );
                 break;
-            case IPC_INF_TRACKERS:
-                tr_bencInitList( item, inf->trackerTiers );
-                for( jj = 0; inf->trackerTiers > jj; jj++ )
-                {
-                    tr_benc * tier = tr_bencListAdd( item );
-                    tr_bencInitList( tier, inf->trackerList[jj].count );
-                    for( kk = 0; inf->trackerList[jj].count > kk; kk++ )
-                        filltracker( tr_bencListAdd( tier ),
-                                     &inf->trackerList[jj].list[kk] );
+            case IPC_INF_TRACKERS: {
+                int prevTier = -1;
+                tr_benc * tier = NULL;
+                tr_bencInitList( item, 0 );
+                for( jj=0; jj<inf->trackerCount; ++jj ) {
+                    if( prevTier != inf->trackers[jj].tier ) {
+                        prevTier = inf->trackers[jj].tier;
+                        tier = tr_bencListAddList( item, 0 );
+                    }
+                    filltracker( tr_bencListAdd( tier ), &inf->trackers[jj] );
                 }
                 break;
+            }
             default:
                 assert( 0 );
                 break;
diff --git a/libtransmission/metainfo.c b/libtransmission/metainfo.c
index 3a7c1912b..7f34bc76d 100644
--- a/libtransmission/metainfo.c
+++ b/libtransmission/metainfo.c
@@ -32,6 +32,8 @@
 #include <sys/stat.h>
 #include <unistd.h> /* unlink, stat */
 
+#include <event.h> /* struct evbuffer */
+
 #include "transmission.h"
 #include "bencode.h"
 #include "crypto.h" /* tr_sha1 */
@@ -330,7 +332,7 @@ tr_metainfoParse( const tr_handle  * handle,
 void tr_metainfoFree( tr_info * inf )
 {
     tr_file_index_t ff;
-    int i, j;
+    int i;
 
     for( ff=0; ff<inf->fileCount; ++ff )
         tr_free( inf->files[ff].name );
@@ -341,14 +343,12 @@ void tr_metainfoFree( tr_info * inf )
     tr_free( inf->creator );
     tr_free( inf->torrent );
     tr_free( inf->name );
-    tr_free( inf->primaryAddress );
     
-    for( i=0; i<inf->trackerTiers; ++i ) {
-        for( j=0; j<inf->trackerList[i].count; ++j )
-            tr_trackerInfoClear( &inf->trackerList[i].list[j] );
-        tr_free( inf->trackerList[i].list );
+    for( i=0; i<inf->trackerCount; ++i ) {
+        tr_free( inf->trackers[i].announce );
+        tr_free( inf->trackers[i].scrape );
     }
-    tr_free( inf->trackerList );
+    tr_free( inf->trackers );
 
     memset( inf, '\0', sizeof(tr_info) );
 }
@@ -411,208 +411,95 @@ getfile( char ** setme, const char * prefix, tr_benc * name )
 
 static int getannounce( tr_info * inf, tr_benc * meta )
 {
-    tr_benc           * val, * urlval;
-    char              * address, * announce;
-    int                 ii, jj, port, random;
-    tr_tracker_info   * sublist;
-    void * swapping;
+    const char * str;
+    tr_tracker_info * trackers = NULL;
+    int trackerCount = 0;
+    tr_benc * tiers;
 
     /* Announce-list */
-    val = tr_bencDictFind( meta, "announce-list" );
-    if( tr_bencIsList(val) && 0 < val->val.l.count )
+    if( tr_bencDictFindList( meta, "announce-list", &tiers ) )
     {
-        inf->trackerTiers = 0;
-        inf->trackerList = calloc( val->val.l.count,
-                                   sizeof( inf->trackerList[0] ) );
-
-        /* iterate through the announce-list's tiers */
-        for( ii = 0; ii < val->val.l.count; ii++ )
-        {
-            int subcount = 0;
-            tr_benc * subval = &val->val.l.vals[ii];
-
-            if( !tr_bencIsList(subval) || 0 >= subval->val.l.count )
-                continue;
-
-            sublist = calloc( subval->val.l.count, sizeof( sublist[0] ) );
-
-            /* iterate through the tier's items */
-            for( jj = 0; jj < subval->val.l.count; jj++ )
-            {
-                tr_tracker_info tmp;
-
-                urlval = &subval->val.l.vals[jj];
-                if( TYPE_STR != urlval->type ||
-                    tr_trackerInfoInit( &tmp, urlval->val.s.s, urlval->val.s.i ) )
-                {
-                    continue;
-                }
-
-                if( !inf->primaryAddress ) {
-                     char buf[1024];
-                     snprintf( buf, sizeof(buf), "%s:%d", tmp.address, tmp.port );
-                     inf->primaryAddress = tr_strdup( buf );
+        int n;
+        int i, j;
+
+        n = 0;
+        for( i=0; i<tiers->val.l.count; ++i )
+            n += tiers->val.l.vals[i].val.l.count;
+
+        trackers = tr_new0( tr_tracker_info, n );
+        trackerCount = 0;
+
+        for( i=0; i<tiers->val.l.count; ++i ) {
+            const tr_benc * tier = &tiers->val.l.vals[i];
+            for( j=0; tr_bencIsList(tier) && j<tier->val.l.count; ++j ) {
+                const tr_benc * address = &tier->val.l.vals[j];
+                if( tr_bencIsString( address ) && tr_httpIsValidURL( address->val.s.s ) ) {
+                    trackers[trackerCount].tier = i;
+                    trackers[trackerCount].announce = tr_strndup( address->val.s.s, address->val.s.i );
+                    trackers[trackerCount++].scrape = announceToScrape( address->val.s.s );
+                    /*fprintf( stderr, "tier %d: %s\n", i, address->val.s.s );*/
                 }
-
-                /* place the item info in a random location in the sublist */
-                random = tr_rand( subcount + 1 );
-                if( random != subcount )
-                    sublist[subcount] = sublist[random];
-                sublist[random] = tmp;
-                subcount++;
-            }
-
-            /* just use sublist as-is if it's full */
-            if( subcount == subval->val.l.count )
-            {
-                inf->trackerList[inf->trackerTiers].list = sublist;
-                inf->trackerList[inf->trackerTiers].count = subcount;
-                inf->trackerTiers++;
-            }
-            /* if we skipped some of the tier's items then trim the sublist */
-            else if( 0 < subcount )
-            {
-                inf->trackerList[inf->trackerTiers].list = calloc( subcount, sizeof( sublist[0] ) );
-                memcpy( inf->trackerList[inf->trackerTiers].list, sublist,
-                        sizeof( sublist[0] ) * subcount );
-                inf->trackerList[inf->trackerTiers].count = subcount;
-                inf->trackerTiers++;
-                free( sublist );
-            }
-            /* drop the whole sublist if we didn't use any items at all */
-            else
-            {
-                free( sublist );
             }
         }
 
         /* did we use any of the tiers? */
-        if( 0 == inf->trackerTiers )
-        {
+        if( !trackerCount ) {
             tr_inf( _( "Invalid metadata entry \"%s\"" ), "announce-list" );
-            free( inf->trackerList );
-            inf->trackerList = NULL;
-        }
-        /* trim unused sublist pointers */
-        else if( inf->trackerTiers < val->val.l.count )
-        {
-            swapping = inf->trackerList;
-            inf->trackerList = calloc( inf->trackerTiers,
-                                       sizeof( inf->trackerList[0] ) );
-            memcpy( inf->trackerList, swapping,
-                    sizeof( inf->trackerList[0] ) * inf->trackerTiers );
-            free( swapping );
+            tr_free( trackers );
+            trackers = NULL;
         }
     }
 
     /* Regular announce value */
-    val = tr_bencDictFind( meta, "announce" );
-    if( !tr_bencIsString( val ) )
+    if( !trackerCount
+        && tr_bencDictFindStr( meta, "announce", &str )
+        && tr_httpIsValidURL( str ) )
     {
-        tr_err( _( "Missing metadata entry \"%s\"" ), "announce" );
-        return TR_EINVALID;
-    }
-
-    if( !inf->trackerTiers )
-    {
-        char buf[4096], *pch;
-        strlcpy( buf, val->val.s.s, sizeof( buf ) );
-        pch = buf;
-        while( isspace( *pch ) )
-            ++pch;
-
-        if( tr_httpParseURL( pch, -1, &address, &port, &announce ) )
-        {
-            tr_err( _( "Invalid announce URL \"%s\"" ), val->val.s.s );
-            return TR_EINVALID;
-        }
-        sublist                   = calloc( 1, sizeof( sublist[0] ) );
-        sublist[0].address        = address;
-        sublist[0].port           = port;
-        sublist[0].announce       = announce;
-        sublist[0].scrape         = announceToScrape( announce );
-        inf->trackerList          = calloc( 1, sizeof( inf->trackerList[0] ) );
-        inf->trackerList[0].list  = sublist;
-        inf->trackerList[0].count = 1;
-        inf->trackerTiers         = 1;
-
-        if( !inf->primaryAddress ) {
-            char buf[1024];
-            snprintf( buf, sizeof(buf), "%s:%d", sublist[0].address, sublist[0].port );
-            inf->primaryAddress = tr_strdup( buf );
-        }
-
+        trackers = tr_new0( tr_tracker_info, 1 );
+        trackers[trackerCount].tier = 0;
+        trackers[trackerCount].announce = tr_strdup( str );
+        trackers[trackerCount++].scrape = announceToScrape( str );
+        /*fprintf( stderr, "single announce: [%s]\n", str );*/
     }
 
+    inf->trackers = trackers;
+    inf->trackerCount = trackerCount;
     return TR_OK;
 }
 
-static char * announceToScrape( const char * announce )
+static char *
+announceToScrape( const char * announce )
 {
-    char old[]  = "announce";
-    int  oldlen = 8;
-    char new[]  = "scrape";
-    int  newlen = 6;
-    char * slash, * scrape;
-    size_t scrapelen, used;
-
+    char * scrape = NULL;
+    const char * slash;
+    struct evbuffer * buf;
+
+    /* To derive the scrape URL use the following steps:
+     * Begin with the announce URL. Find the last '/' in it.
+     * If the text immediately following that '/' isn't 'announce'
+     * it will be taken as a sign that that tracker doesn't support
+     * the scrape convention. If it does, substitute 'scrape' for
+     * 'announce' to find the scrape page.  */
+
+    /* is the last slash followed by "announce"? */
     slash = strrchr( announce, '/' );
-    if( NULL == slash )
-    {
+    if( !slash )
         return NULL;
-    }
-    slash++;
-    
-    if( 0 != strncmp( slash, old, oldlen ) )
-    {
+    ++slash;
+    if( strncmp( slash, "announce", 8 ) )
         return NULL;
-    }
 
-    scrapelen = strlen( announce ) - oldlen + newlen;
-    scrape = calloc( scrapelen + 1, 1 );
-    if( NULL == scrape )
-    {
-        return NULL;
-    }
-    assert( ( size_t )( slash - announce ) < scrapelen );
-    memcpy( scrape, announce, slash - announce );
-    used = slash - announce;
-    strncat( scrape, new, scrapelen - used );
-    used += newlen;
-    assert( strlen( scrape ) == used );
-    if( used < scrapelen )
-    {
-        assert( strlen( slash + oldlen ) == scrapelen - used );
-        strncat( scrape, slash + oldlen, scrapelen - used );
-    }
+    /* build the scrape url */
+    buf = evbuffer_new( );
+    evbuffer_add( buf, announce, slash-announce );
+    evbuffer_add( buf, "scrape", 6 );
+    evbuffer_add_printf( buf, "%s", slash+8 );
+    scrape = tr_strdup( ( char * ) EVBUFFER_DATA( buf ) );
+    evbuffer_free( buf );
 
     return scrape;
 }
 
-int
-tr_trackerInfoInit( tr_tracker_info  * info,
-                    const char       * address,
-                    int                address_len )
-{
-    int ret = tr_httpParseURL( address, address_len,
-                               &info->address,
-                               &info->port,
-                               &info->announce );
-    if( !ret )
-        info->scrape = announceToScrape( info->announce );
-
-    return ret;
-}
-
-void
-tr_trackerInfoClear( tr_tracker_info * info )
-{
-    tr_free( info->address );
-    tr_free( info->announce );
-    tr_free( info->scrape );
-    memset( info, '\0', sizeof(tr_tracker_info) );
-}
-
 void
 tr_metainfoRemoveSaved( const tr_handle * handle,
                         const tr_info   * inf )
diff --git a/libtransmission/session.c b/libtransmission/session.c
index 4de4c30e8..0131eafaf 100644
--- a/libtransmission/session.c
+++ b/libtransmission/session.c
@@ -47,6 +47,7 @@
 #include "tracker.h"
 #include "trevent.h"
 #include "utils.h"
+#include "web.h"
 
 /* Generate a peer id : "-TRxyzb-" + 12 random alphanumeric
    characters, where x is the major version number, y is the
@@ -92,22 +93,22 @@ tr_getPeerId( void )
 ***/
 
 tr_encryption_mode
-tr_getEncryptionMode( tr_handle * handle )
+tr_getEncryptionMode( tr_session * session )
 {
-    assert( handle != NULL );
+    assert( session != NULL );
 
-    return handle->encryptionMode;
+    return session->encryptionMode;
 }
 
 void
-tr_setEncryptionMode( tr_handle * handle, tr_encryption_mode mode )
+tr_setEncryptionMode( tr_session * session, tr_encryption_mode mode )
 {
-    assert( handle != NULL );
+    assert( session != NULL );
     assert( mode==TR_ENCRYPTION_PREFERRED
          || mode==TR_ENCRYPTION_REQUIRED
          || mode==TR_PLAINTEXT_PREFERRED );
 
-    handle->encryptionMode = mode;
+    session->encryptionMode = mode;
 }
 
 /***
@@ -191,6 +192,8 @@ tr_initFull( const char * configDir,
 
     tr_statsInit( h );
 
+    h->web = tr_webInit( h );
+
     metainfoLookupRescan( h );
 
     return h;
diff --git a/libtransmission/session.h b/libtransmission/session.h
index 8fc57f828..c053b5962 100644
--- a/libtransmission/session.h
+++ b/libtransmission/session.h
@@ -43,12 +43,6 @@ typedef enum { TR_NET_OK, TR_NET_ERROR, TR_NET_WAIT } tr_tristate_t;
 #define FALSE 0
 #endif
 
-int tr_trackerInfoInit( struct tr_tracker_info  * info,
-                        const char              * address,
-                        int                       address_len );
-
-void tr_trackerInfoClear( struct tr_tracker_info * info );
-
 uint8_t* tr_peerIdNew( void );
 
 const uint8_t* tr_getPeerId( void );
@@ -59,13 +53,6 @@ struct tr_metainfo_lookup
     char * filename;
 };
 
-const char * tr_sessionFindTorrentFile( const tr_handle  * h,
-                                        const char       * hashString );
-
-void tr_sessionSetTorrentFile( tr_handle    * h,
-                               const char   * hashString,
-                               const char   * filename );
-
 struct tr_handle
 {
     unsigned int                 isPortSet        : 1;
@@ -98,6 +85,8 @@ struct tr_handle
 
     struct tr_lock             * lock;
 
+    struct tr_web              * web;
+
     tr_handle_status             stats[2];
     int                          statCur;
 
@@ -108,8 +97,17 @@ struct tr_handle
     int                          metainfoLookupCount;
 };
 
-void tr_globalLock       ( struct tr_handle * );
-void tr_globalUnlock     ( struct tr_handle * );
-int  tr_globalIsLocked   ( const struct tr_handle * );
+typedef struct tr_handle tr_session;
+
+const char * tr_sessionFindTorrentFile( const tr_session * session,
+                                        const char       * hashString );
+
+void tr_sessionSetTorrentFile( tr_session   * session,
+                               const char   * hashString,
+                               const char   * filename );
+
+void tr_globalLock       ( tr_session * );
+void tr_globalUnlock     ( tr_session * );
+int  tr_globalIsLocked   ( const tr_session * );
 
 #endif
diff --git a/libtransmission/tracker.c b/libtransmission/tracker.c
index e7d7ed8df..f31a9b70a 100644
--- a/libtransmission/tracker.c
+++ b/libtransmission/tracker.c
@@ -17,12 +17,10 @@
 #include <libgen.h> /* basename */
 
 #include <event.h>
-#include <evhttp.h>
 
 #include "transmission.h"
 #include "bencode.h"
 #include "completion.h"
-#include "list.h"
 #include "net.h"
 #include "port-forwarding.h"
 #include "publish.h"
@@ -31,9 +29,12 @@
 #include "trcompat.h" /* strlcpy */
 #include "trevent.h"
 #include "utils.h"
+#include "web.h"
 
 enum
 {
+    HTTP_OK = 200,
+
     /* seconds between tracker pulses */
     PULSE_INTERVAL_MSEC = 1000,
 
@@ -62,7 +63,7 @@ enum
     STOP_TIMEOUT_INTERVAL_SEC = 5,
 
     /* the value of the 'numwant' argument passed in tracker requests. */
-    NUMWANT = 200,
+    NUMWANT = 150,
 
     /* the length of the 'key' argument passed in tracker requests */
     KEYLEN = 10
@@ -74,7 +75,11 @@ enum
 
 struct tr_tracker
 {
-    tr_handle * handle;
+    unsigned int isRunning     : 1;
+
+    uint8_t randOffset;
+
+    tr_session * session;
 
     /* these are set from the latest scrape or tracker response */
     int announceIntervalSec;
@@ -82,11 +87,8 @@ struct tr_tracker
     int scrapeIntervalSec;
     int retryScrapeIntervalSec;
 
-    tr_tracker_info * redirect;
-    tr_tracker_info * addresses;
-    int addressIndex;
-    int addressCount;
-    int * tierFronts;
+    /* index into the torrent's tr_info.trackers array */
+    int trackerIndex;
 
     /* sent as the "key" argument in tracker requests
        to verify us if our IP address changes.
@@ -97,7 +99,7 @@ struct tr_tracker
 
     /* torrent hash string */
     uint8_t hash[SHA_DIGEST_LENGTH];
-    char escaped[SHA_DIGEST_LENGTH * 3 + 1];
+    char escaped[SHA_DIGEST_LENGTH*3 + 1];
     char * name;
 
     /* corresponds to the peer_id sent as a tracker request parameter.
@@ -118,14 +120,10 @@ struct tr_tracker
     time_t scrapeAt;
 
     time_t lastScrapeTime;
-    char lastScrapeResponse[512];
+    long lastScrapeResponse;
 
     time_t lastAnnounceTime;
-    char lastAnnounceResponse[512];
-
-    int randOffset;
-
-    unsigned int isRunning     : 1;
+    long lastAnnounceResponse;
 };
 
 /**
@@ -144,7 +142,7 @@ myDebug( const char * file, int line, const tr_tracker * t, const char * fmt, ..
         char * myfile = tr_strdup( file );
 
         evbuffer_add_printf( buf, "[%s] ", tr_getLogTimeStr( timestr, sizeof(timestr) ) );
-        if( t != NULL )
+        if( t )
             evbuffer_add_printf( buf, "%s ", t->name );
         va_start( args, fmt );
         evbuffer_add_vprintf( buf, fmt, args );
@@ -163,57 +161,38 @@ myDebug( const char * file, int line, const tr_tracker * t, const char * fmt, ..
 ****
 ***/
 
+static const tr_tracker_info *
+getCurrentAddressFromTorrent( const tr_tracker * t, const tr_torrent * tor )
+{
+    assert( t->trackerIndex >= 0 );
+    assert( t->trackerIndex < tor->info.trackerCount );
+    return tor->info.trackers + t->trackerIndex;
+}
+    
 static const tr_tracker_info *
 getCurrentAddress( const tr_tracker * t )
 {
-    assert( t->addresses != NULL );
-    assert( t->addressIndex >= 0 );
-    assert( t->addressIndex < t->addressCount );
-
-    return t->redirect ? t->redirect
-                       : t->addresses + t->addressIndex;
+    const tr_torrent * torrent;
+    if(( torrent = tr_torrentFindFromHash( t->session, t->hash )))
+        return getCurrentAddressFromTorrent( t, torrent );
+    return NULL;
 }
 
 static int
-trackerSupportsScrape( const tr_tracker * t )
+trackerSupportsScrape( const tr_tracker * t, const tr_torrent * tor )
 {
-    const tr_tracker_info * info = getCurrentAddress( t );
-
-    return ( info != NULL )
-        && ( info->scrape != NULL )
-        && ( info->scrape[0] != '\0' );
+    const tr_tracker_info * info = getCurrentAddressFromTorrent( t, tor );
+    return info && info->scrape;
 }
 
 /***
 ****
 ***/
 
-struct torrent_hash
-{
-    tr_handle * handle;
-    uint8_t hash[SHA_DIGEST_LENGTH];
-};
-
-static struct torrent_hash*
-torrentHashNew( tr_handle * handle, const tr_tracker * t )
-{
-    struct torrent_hash * data = tr_new( struct torrent_hash, 1 );
-    data->handle = handle;
-    memcpy( data->hash, t->hash, SHA_DIGEST_LENGTH );
-    return data;
-}
-
 tr_tracker *
-findTrackerFromHash( struct torrent_hash * data )
+findTracker( tr_session * session, const uint8_t * hash )
 {
-    tr_torrent * torrent = tr_torrentFindFromHash( data->handle, data->hash );
-    return torrent ? torrent->tracker : NULL;
-}
-
-tr_tracker *
-findTracker( tr_handle * handle, const uint8_t * hash )
-{
-    tr_torrent * torrent = tr_torrentFindFromHash( handle, hash );
+    tr_torrent * torrent = tr_torrentFindFromHash( session, hash );
     return torrent ? torrent->tracker : NULL;
 }
 
@@ -226,7 +205,7 @@ static const tr_tracker_event emptyEvent = { 0, NULL, NULL, NULL, 0, 0 };
 static void
 publishMessage( tr_tracker * t, const char * msg, int type )
 {
-    if( t != NULL )
+    if( t )
     {
         tr_tracker_event event = emptyEvent;
         event.hash = t->hash;
@@ -256,7 +235,8 @@ publishWarning( tr_tracker * t, const char * msg )
 }
 
 static void
-publishNewPeers( tr_tracker * t, int allAreSeeds, void * compact, int compactLen )
+publishNewPeers( tr_tracker * t, int allAreSeeds,
+                 void * compact, int compactLen )
 {
     tr_tracker_event event = emptyEvent;
     event.hash = t->hash;
@@ -272,69 +252,31 @@ publishNewPeers( tr_tracker * t, int allAreSeeds, void * compact, int compactLen
 ****
 ***/
 
-static void onReqDone( tr_handle * handle );
-
-static void
-onStoppedResponse( struct evhttp_request * req UNUSED, void * handle )
-{
-    dbgmsg( NULL, "got a response to some `stop' message" );
-    onReqDone( handle );
-}
-
-static int
-parseBencResponse( struct evhttp_request * req, tr_benc * setme )
-{
-    const unsigned char * body = EVBUFFER_DATA( req->input_buffer );
-    const int bodylen = EVBUFFER_LENGTH( req->input_buffer );
-    return tr_bencLoad( body, bodylen, setme, NULL );
-}
+static void onReqDone( tr_session * session );
 
 static void
-updateAddresses( tr_tracker * t, const struct evhttp_request * req, int * tryAgain )
+updateAddresses( tr_tracker * t, long response_code, int * tryAgain )
 {
     int moveToNextAddress = FALSE;
+    tr_torrent * torrent = tr_torrentFindFromHash( t->session, t->hash );
 
-    if( !req ) /* tracker didn't respond */
+    if( !response_code ) /* tracker didn't respond */
     {
         tr_ninf( t->name, _( "Tracker hasn't responded yet.  Retrying..." ) );
         moveToNextAddress = TRUE;
     }
-    else if( req->response_code == HTTP_OK )
+    else if( response_code == HTTP_OK )
     {
-        if( t->redirect != NULL )
-        {
-            /* multitracker spec: "if a connection with a tracker is
-               successful, it will be moved to the front of the tier." */
-            const int i = t->addressIndex;
-            const int j = t->tierFronts[i];
-            const tr_tracker_info swap = t->addresses[i];
-            t->addresses[i] = t->addresses[j];
-            t->addresses[j] = swap;
-        }
-    }
-    else if(    ( req->response_code == HTTP_MOVEPERM )
-             || ( req->response_code == HTTP_MOVETEMP ) )
-    {
-        const char * loc = evhttp_find_header( req->input_headers, "Location" );
-        tr_tracker_info tmp;
-        if( tr_trackerInfoInit( &tmp, loc, -1 ) ) /* a bad redirect? */
-        {
-            moveToNextAddress = TRUE;
-        }
-        else if( req->response_code == HTTP_MOVEPERM )
-        {
-            tr_tracker_info * cur = &t->addresses[t->addressIndex];
-            tr_trackerInfoClear( cur );
-            *cur = tmp;
-        }
-        else if( req->response_code == HTTP_MOVETEMP )
-        {
-            if( t->redirect == NULL )
-                t->redirect = tr_new0( tr_tracker_info, 1 );
-            else
-                tr_trackerInfoClear( t->redirect );
-            *t->redirect = tmp;
-        }
+#if 0
+/* FIXME */
+        /* multitracker spec: "if a connection with a tracker is
+           successful, it will be moved to the front of the tier." */
+        const int i = t->addressIndex;
+        const int j = t->tierFronts[i];
+        const tr_tracker_info swap = t->addresses[i];
+        t->addresses[i] = t->addresses[j];
+        t->addresses[j] = swap;
+#endif
     }
     else 
     {
@@ -345,15 +287,15 @@ updateAddresses( tr_tracker * t, const struct evhttp_request * req, int * tryAga
 
     if( moveToNextAddress )
     {
-        if ( ++t->addressIndex >= t->addressCount ) /* we've tried them all */
+        if ( ++t->trackerIndex >= torrent->info.trackerCount ) /* we've tried them all */
         {
             *tryAgain = FALSE;
-            t->addressIndex = 0;
+            t->trackerIndex = 0;
         }
         else
         {
-            const tr_tracker_info * n = getCurrentAddress( t );
-            tr_ninf( t->name, _( "Trying next tracker \"%s:%d\"" ), n->address, n->port );
+            const tr_tracker_info * n = getCurrentAddressFromTorrent( t, torrent );
+            tr_ninf( t->name, _( "Trying tracker \"%s\"" ), n->announce );
         }
     }
 }
@@ -372,23 +314,22 @@ parseOldPeers( tr_benc * bePeers, size_t * byteCount )
 
     for( i=0, walk=compact; i<peerCount; ++i )
     {
+        const char * s;
+        int64_t itmp;
         struct in_addr addr;
         tr_port_t port;
-        tr_benc * val;
         tr_benc * peer = &bePeers->val.l.vals[i];
 
-        val = tr_bencDictFind( peer, "ip" );
-        if( !val || val->type!=TYPE_STR || tr_netResolve(val->val.s.s, &addr) )
+        if( !tr_bencDictFindStr( peer, "ip", &s ) || tr_netResolve( s, &addr ) )
             continue;
 
         memcpy( walk, &addr, 4 );
         walk += 4;
 
-        val = tr_bencDictFind( peer, "port" );
-        if( !val || val->type!=TYPE_INT || val->val.i<0 || val->val.i>0xffff )
+        if( !tr_bencDictFindInt( peer, "port", &itmp ) || itmp<0 || itmp>0xffff )
             continue;
 
-        port = htons( val->val.i );
+        port = htons( itmp );
         memcpy( walk, &port, 2 );
         walk += 2;
     }
@@ -398,70 +339,72 @@ parseOldPeers( tr_benc * bePeers, size_t * byteCount )
 }
 
 static void
-onTrackerResponse( struct evhttp_request * req, void * vhash )
+onStoppedResponse( tr_session    * session,
+                   long            responseCode  UNUSED,
+                   const void    * response      UNUSED,
+                   size_t          responseLen   UNUSED,
+                   void          * torrent_hash  UNUSED )
+{
+    dbgmsg( NULL, "got a response to some `stop' message" );
+    onReqDone( session );
+}
+
+static void
+onTrackerResponse( tr_session    * session,
+                   long            responseCode,
+                   const void    * response,
+                   size_t          responseLen,
+                   void          * torrent_hash )
 {
     int tryAgain;
-    int responseCode;
-    struct torrent_hash * torrent_hash = (struct torrent_hash*) vhash;
-    tr_tracker * t = findTrackerFromHash( torrent_hash );
+    tr_tracker * t;
 
-    onReqDone( torrent_hash->handle );
+    onReqDone( session );
+    t = findTracker( session, torrent_hash );
     tr_free( torrent_hash );
-
-    if( t == NULL ) /* tracker has been closed */
+    if( !t ) /* tracker's been closed */
         return;
 
-    dbgmsg( t, "got response from tracker: \"%s\"",
-            ( req && req->response_code_line ) ?  req->response_code_line
-                                               : "(null)" );
+    dbgmsg( t, "tracker response: %d", responseCode );
+    tr_ndbg( t->name, "tracker response: %d", responseCode );
+    t->lastAnnounceResponse = responseCode;
 
-    *t->lastAnnounceResponse = '\0';
-    if( req && req->response_code_line )
-        strlcpy( t->lastAnnounceResponse, req->response_code_line, sizeof( t->lastAnnounceResponse ) );
-
-    tr_ndbg( t->name, "tracker response: %s",
-             ( req ? req->response_code_line : "(null)") );
-
-    if( req && ( req->response_code == HTTP_OK ) )
+    if( responseCode == HTTP_OK )
     {
         tr_benc benc;
-        const int bencLoaded = !parseBencResponse( req, &benc );
-
+        const int bencLoaded = !tr_bencLoad( response, responseLen, &benc, 0 );
         publishErrorClear( t );
-
-        if( bencLoaded && benc.type==TYPE_DICT )
+        if( bencLoaded && tr_bencIsDict( &benc ) )
         {
             tr_benc * tmp;
+            int64_t i;
             int incomplete = -1;
+            const char * str;
 
-            if(( tmp = tr_bencDictFind( &benc, "failure reason" ))) {
-                dbgmsg( t, "got failure message [%s]", tmp->val.s.s );
-                publishErrorMessageAndStop( t, tmp->val.s.s );
-            }
+            if(( tr_bencDictFindStr( &benc, "failure reason", &str )))
+                publishErrorMessageAndStop( t, str );
 
-            if(( tmp = tr_bencDictFind( &benc, "warning message" ))) {
-                dbgmsg( t, "got warning message [%s]", tmp->val.s.s );
-                publishWarning( t, tmp->val.s.s );
-            }
+            if(( tr_bencDictFindStr( &benc, "warning message", &str )))
+                publishWarning( t, str );
 
-            if(( tmp = tr_bencDictFind( &benc, "interval" ))) {
-                dbgmsg( t, "setting interval to %d", tmp->val.i );
-                t->announceIntervalSec = tmp->val.i;
+            if(( tr_bencDictFindInt( &benc, "interval", &i ))) {
+                dbgmsg( t, "setting interval to %d", (int)i );
+                t->announceIntervalSec = i;
             }
 
-            if(( tmp = tr_bencDictFind( &benc, "min interval" ))) {
-                dbgmsg( t, "setting min interval to %d", tmp->val.i );
-                t->announceMinIntervalSec = tmp->val.i;
+            if(( tr_bencDictFindInt( &benc, "min interval", &i ))) {
+                dbgmsg( t, "setting min interval to %d", (int)i );
+                t->announceMinIntervalSec = i;
             }
 
-            if(( tmp = tr_bencDictFind( &benc, "tracker id" )))
-                t->trackerID = tr_strndup( tmp->val.s.s, tmp->val.s.i );
+            if(( tr_bencDictFindStr( &benc, "tracker id", &str )))
+                t->trackerID = tr_strdup( str );
 
-            if(( tmp = tr_bencDictFind( &benc, "complete" )))
-                t->seederCount = tmp->val.i;
+            if(( tr_bencDictFindInt( &benc, "complete", &i )))
+                t->seederCount = i;
 
-            if(( tmp = tr_bencDictFind( &benc, "incomplete" )))
-                t->leecherCount = incomplete = tmp->val.i;
+            if(( tr_bencDictFindInt( &benc, "incomplete", &i )))
+                t->leecherCount = incomplete = i;
 
             if(( tmp = tr_bencDictFind( &benc, "peers" )))
             {
@@ -485,7 +428,7 @@ onTrackerResponse( struct evhttp_request * req, void * vhash )
             tr_bencFree( &benc );
     }
 
-    updateAddresses( t, req, &tryAgain );
+    updateAddresses( t, responseCode, &tryAgain );
 
     /**
     ***
@@ -493,123 +436,101 @@ onTrackerResponse( struct evhttp_request * req, void * vhash )
 
     if( tryAgain )
         responseCode = 300;
-    else if( req )
-        responseCode = req->response_code;
-    else
-        responseCode = 503;
 
     if( 200<=responseCode && responseCode<=299 )
     {
-        dbgmsg( t, "request succeeded. reannouncing in %d seconds",
-                   t->announceIntervalSec );
-        t->reannounceAt = time( NULL ) + t->randOffset + t->announceIntervalSec;
+        const int interval = t->announceIntervalSec + t->randOffset;
+        dbgmsg( t, "request succeeded. reannouncing in %d seconds", interval );
+        t->reannounceAt = time( NULL ) + interval;
         t->manualAnnounceAllowedAt = time( NULL ) + t->announceMinIntervalSec;
     }
     else if( 300<=responseCode && responseCode<=399 )
     {
-        const int interval = 5;
-        dbgmsg( t, "got a redirect. retrying in %d seconds", interval );
-
         /* it's a redirect... updateAddresses() has already
          * parsed the redirect, all that's left is to retry */
+        const int interval = 5;
+        dbgmsg( t, "got a redirect. retrying in %d seconds", interval );
         t->reannounceAt = time( NULL ) + interval;
         t->manualAnnounceAllowedAt = time( NULL ) + t->announceMinIntervalSec;
     }
     else if( 400<=responseCode && responseCode<=499 )
     {
-        const char * err = req && req->response_code_line
-            ? req->response_code_line
-            : "Unspecified 4xx error from tracker.";
-        dbgmsg( t, err );
-
         /* The request could not be understood by the server due to
          * malformed syntax. The client SHOULD NOT repeat the
          * request without modifications. */
-        publishErrorMessageAndStop( t, err );
+        publishErrorMessageAndStop( t, _( "Tracker returned a 4xx message" ) );
         t->manualAnnounceAllowedAt = ~(time_t)0;
         t->reannounceAt = 0;
     }
     else if( 500<=responseCode && responseCode<=599 )
     {
-        dbgmsg( t, "Got a 5xx error... retrying in one minute." );
-
         /* Response status codes beginning with the digit "5" indicate
          * cases in which the server is aware that it has erred or is
          * incapable of performing the request.  So we pause a bit and
          * try again. */
-        if( req && req->response_code_line )
-            publishWarning( t, req->response_code_line );
         t->manualAnnounceAllowedAt = ~(time_t)0;
         t->reannounceAt = time( NULL ) + 60;
     }
     else
     {
-        dbgmsg( t, "Invalid response from tracker... retrying in two minutes." );
-
         /* WTF did we get?? */
-        if( req && req->response_code_line )
-            publishWarning( t, req->response_code_line );
+        dbgmsg( t, "Invalid response from tracker... retrying in two minutes." );
         t->manualAnnounceAllowedAt = ~(time_t)0;
         t->reannounceAt = time( NULL ) + t->randOffset + 120;
     }
 }
 
 static void
-onScrapeResponse( struct evhttp_request * req, void * vhash )
+onScrapeResponse( tr_session   * session,
+                  long           responseCode,
+                  const void   * response,
+                  size_t         responseLen,
+                  void         * torrent_hash )
 {
     int tryAgain;
-    int responseCode;
-    struct torrent_hash * torrent_hash = (struct torrent_hash*) vhash;
-    tr_tracker * t = findTrackerFromHash( torrent_hash );
+    tr_tracker * t;
 
-    onReqDone( torrent_hash->handle );
+    onReqDone( session );
+    t = findTracker( session, torrent_hash );
     tr_free( torrent_hash );
-
-    dbgmsg( t, "Got scrape response for '%s': %s (%d)", (t ? t->name : "(null)"), (req ? req->response_code_line : "(no line)"), (req ? req->response_code : -1) );
-
-    if( t == NULL ) /* tracker's been closed... */
+    if( !t ) /* tracker's been closed... */
         return;
 
-    *t->lastScrapeResponse = '\0';
-    if( req && req->response_code_line )
-        strlcpy( t->lastScrapeResponse, req->response_code_line, sizeof( t->lastScrapeResponse ) );
-
-    tr_ndbg( t->name, "Got scrape response: \"%s\"",
-            ( ( req && req->response_code_line ) ? req->response_code_line : "(null)") );
+    dbgmsg( t, "scrape response: %ld\n", responseCode );
+    tr_ndbg( t->name, "scrape response: %d", responseCode );
+    t->lastScrapeResponse = responseCode;
 
-    if( req && ( req->response_code == HTTP_OK ) )
+    if( responseCode == HTTP_OK )
     {
         tr_benc benc, *files;
-        const int bencLoaded = !parseBencResponse( req, &benc );
-
-        if( bencLoaded
-            && (( files = tr_bencDictFind( &benc, "files" ) ))
-            && ( files->type == TYPE_DICT ) )
+        const int bencLoaded = !tr_bencLoad( response, responseLen, &benc, 0 );
+        if( bencLoaded && tr_bencDictFindDict( &benc, "files", &files ) )
         {
             int i;
             for( i=0; i<files->val.l.count; i+=2 )
             {
+                int64_t itmp;
                 const uint8_t* hash =
-                        (const uint8_t*) files->val.l.vals[i].val.s.s;
-                tr_benc *tmp, *flags;
-                tr_benc *tordict = &files->val.l.vals[i+1];
+                    (const uint8_t*) files->val.l.vals[i].val.s.s;
+                tr_benc * flags;
+                tr_benc * tordict = &files->val.l.vals[i+1];
                 if( memcmp( t->hash, hash, SHA_DIGEST_LENGTH ) )
                     continue;
 
                 publishErrorClear( t );
 
-                if(( tmp = tr_bencDictFind( tordict, "complete" )))
-                    t->seederCount = tmp->val.i;
+                if(( tr_bencDictFindInt( tordict, "complete", &itmp )))
+                    t->seederCount = itmp;
 
-                if(( tmp = tr_bencDictFind( tordict, "incomplete" )))
-                    t->leecherCount = tmp->val.i;
+                if(( tr_bencDictFindInt( tordict, "incomplete", &itmp )))
+                    t->leecherCount = itmp;
 
-                if(( tmp = tr_bencDictFind( tordict, "downloaded" )))
-                    t->timesDownloaded = tmp->val.i;
+                if(( tr_bencDictFindInt( tordict, "downloaded", &itmp )))
+                    t->timesDownloaded = itmp;
 
-                if(( flags = tr_bencDictFind( tordict, "flags" )))
-                    if(( tmp = tr_bencDictFind( flags, "min_request_interval")))
-                        t->scrapeIntervalSec = tmp->val.i;
+                if( tr_bencDictFindDict( tordict, "flags", &flags ))
+                    if(( tr_bencDictFindInt( flags, "min_request_interval", &itmp )))
+                        t->scrapeIntervalSec = i;
 
                 tr_ndbg( t->name, "Scrape successful.  Rescraping in %d seconds.",
                          t->scrapeIntervalSec );
@@ -622,7 +543,7 @@ onScrapeResponse( struct evhttp_request * req, void * vhash )
             tr_bencFree( &benc );
     }
 
-    updateAddresses( t, req, &tryAgain );
+    updateAddresses( t, responseCode, &tryAgain );
 
     /**
     ***
@@ -630,10 +551,6 @@ onScrapeResponse( struct evhttp_request * req, void * vhash )
 
     if( tryAgain )
         responseCode = 300;
-    else if( req )
-        responseCode = req->response_code;
-    else
-        responseCode = 503;
 
     if( 200<=responseCode && responseCode<=299 )
     {
@@ -651,7 +568,8 @@ onScrapeResponse( struct evhttp_request * req, void * vhash )
     else
     {
         const int interval = t->retryScrapeIntervalSec + t->randOffset;
-        dbgmsg( t, "Tracker responded to scrape with %d.  Retrying in %d seconds.", responseCode,  interval );
+        dbgmsg( t, "Tracker responded to scrape with %d.  Retrying in %d seconds.",
+                   responseCode,  interval );
         t->retryScrapeIntervalSec *= 2;
         t->scrapeAt = time( NULL ) + interval;
     }
@@ -673,50 +591,31 @@ enum
 
 struct tr_tracker_request
 {
-    int port;
-    int timeout;
+    char * url;
     int reqtype; /* TR_REQ_* */
-    char * address;
-    char * uri;
-    struct evhttp_request * req;
     uint8_t torrent_hash[SHA_DIGEST_LENGTH];
+    tr_web_done_func * done_func;
+    tr_session * session;
 };
 
 static void
 freeRequest( struct tr_tracker_request * req )
 {
-    tr_free( req->address );
-    tr_free( req->uri );
+    tr_free( req->url );
     tr_free( req );
 }
 
 static void
-addCommonHeaders( const tr_tracker * t,
-                  struct evhttp_request * req )
-{
-    char buf[1024];
-    const tr_tracker_info * address = getCurrentAddress( t );
-    snprintf( buf, sizeof(buf), "%s:%d", address->address, address->port );
-    evhttp_add_header( req->output_headers, "Host", buf );
-    evhttp_add_header( req->output_headers, "Connection", "close" );
-    evhttp_add_header( req->output_headers, "User-Agent",
-                                         TR_NAME "/" LONG_VERSION_STRING );
-}
-
-static char*
 buildTrackerRequestURI( const tr_tracker  * t,
                         const tr_torrent  * torrent,
-                        const char        * eventName )
+                        const char        * eventName,
+                        struct evbuffer   * buf )
 {
     const int isStopping = !strcmp( eventName, "stopped" );
     const int numwant = isStopping ? 0 : NUMWANT;
-    struct evbuffer * buf = evbuffer_new( );
-    char * ret;
-
-    const char * ann = getCurrentAddress(t)->announce;
+    const char * ann = getCurrentAddressFromTorrent(t,torrent)->announce;
     
-    evbuffer_add_printf( buf, "%s"
-                              "%cinfo_hash=%s"
+    evbuffer_add_printf( buf, "%cinfo_hash=%s"
                               "&peer_id=%s"
                               "&port=%d"
                               "&uploaded=%"PRIu64
@@ -726,131 +625,111 @@ buildTrackerRequestURI( const tr_tracker  * t,
                               "&compact=1"
                               "&numwant=%d"
                               "&key=%s"
-                              "&supportcrypto=1"
-                              "&requirecrypto=%d"
                               "%s%s"
                               "%s%s",
-        ann,
         strchr(ann, '?') ? '&' : '?',
         t->escaped,
         t->peer_id,
-        tr_sharedGetPublicPort( t->handle->shared ),
+        tr_sharedGetPublicPort( t->session->shared ),
         torrent->uploadedCur,
         torrent->downloadedCur,
         torrent->corruptCur,
         tr_cpLeftUntilComplete( torrent->completion ),
         numwant,
         t->key_param,
-        ( t->handle->encryptionMode==TR_ENCRYPTION_REQUIRED ? 1 : 0 ),
         ( ( eventName && *eventName ) ? "&event=" : "" ),
         ( ( eventName && *eventName ) ? eventName : "" ),
         ( ( t->trackerID && *t->trackerID ) ? "&trackerid=" : "" ),
         ( ( t->trackerID && *t->trackerID ) ? t->trackerID : "" ) );
-
-    ret = tr_strdup( (char*) EVBUFFER_DATA( buf ) );
-    evbuffer_free( buf );
-    return ret;
 }
 
 static struct tr_tracker_request*
-createRequest( tr_handle * handle, const tr_tracker * tracker, int reqtype )
+createRequest( tr_session * session, const tr_tracker * tracker, int reqtype )
 {
-    static const char* strings[TR_REQ_COUNT] = { "started", "completed", "stopped", "", "err" };
-    const tr_torrent * torrent = tr_torrentFindFromHash( handle, tracker->hash );
-    const tr_tracker_info * address = getCurrentAddress( tracker );
+    static const char* strings[] = { "started", "completed", "stopped", "", "err" };
+    const tr_torrent * torrent = tr_torrentFindFromHash( session, tracker->hash );
+    const tr_tracker_info * address = getCurrentAddressFromTorrent( tracker, torrent );
     const int isStopping = reqtype == TR_REQ_STOPPED;
-    const char * eventName = strings[reqtype];
     struct tr_tracker_request * req;
+    struct evbuffer * url;
+
+    url = evbuffer_new( );
+    evbuffer_add_printf( url, "%s", address->announce );
+    buildTrackerRequestURI( tracker, torrent, strings[reqtype], url );
 
     req = tr_new0( struct tr_tracker_request, 1 );
-    req->address = tr_strdup( address->address );
-    req->port = address->port;
-    req->uri = buildTrackerRequestURI( tracker, torrent, eventName );
-    req->timeout = isStopping ? STOP_TIMEOUT_INTERVAL_SEC : TIMEOUT_INTERVAL_SEC;
+    req->session = session;
     req->reqtype = reqtype;
-    req->req = isStopping
-        ? evhttp_request_new( onStoppedResponse, handle )
-        : evhttp_request_new( onTrackerResponse, torrentHashNew(handle, tracker) );
+    req->done_func =  isStopping ? onStoppedResponse : onTrackerResponse;
+    req->url = tr_strdup( ( char * ) EVBUFFER_DATA( url ) );
     memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH );
-    addCommonHeaders( tracker, req->req );
 
+    evbuffer_free( url );
     return req;
 }
 
 static struct tr_tracker_request*
-createScrape( tr_handle * handle, const tr_tracker * tracker )
+createScrape( tr_session * session, const tr_tracker * tracker )
 {
     const tr_tracker_info * a = getCurrentAddress( tracker );
     struct tr_tracker_request * req;
+    struct evbuffer * url = evbuffer_new( );
+
+    evbuffer_add_printf( url, "%s%cinfo_hash=%s",
+                         a->scrape, strchr(a->scrape,'?')?'&':'?',
+                         tracker->escaped ); 
 
     req = tr_new0( struct tr_tracker_request, 1 );
-    req->address = tr_strdup( a->address );
-    req->port = a->port;
-    req->timeout = TIMEOUT_INTERVAL_SEC;
-    req->req = evhttp_request_new( onScrapeResponse, torrentHashNew( handle, tracker ) );
+    req->session = session;
     req->reqtype = TR_REQ_SCRAPE;
-    tr_asprintf( &req->uri, "%s%cinfo_hash=%s", a->scrape, strchr(a->scrape,'?')?'&':'?', tracker->escaped );
+    req->url = tr_strdup( ( char * ) EVBUFFER_DATA( url ) );
+    req->done_func = onScrapeResponse;
     memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH );
-    addCommonHeaders( tracker, req->req );
 
+    evbuffer_free( url );
     return req;
 }
 
 struct tr_tracker_handle
 {
-    int socketCount;
     unsigned int isShuttingDown : 1;
+    int runningCount;
     tr_timer * pulseTimer;
-    tr_list * requestQueue;
-    tr_list * scrapeQueue;
 };
 
-static int pulse( void * vhandle );
+static int pulse( void * vsession );
 
 static void
-ensureGlobalsExist( tr_handle * handle )
+ensureGlobalsExist( tr_session * session )
 {
-    if( handle->tracker == NULL )
+    if( session->tracker == NULL )
     {
-        handle->tracker = tr_new0( struct tr_tracker_handle, 1 );
-        handle->tracker->pulseTimer = tr_timerNew( handle, pulse, handle, PULSE_INTERVAL_MSEC );
+        session->tracker = tr_new0( struct tr_tracker_handle, 1 );
+        session->tracker->pulseTimer = tr_timerNew( session, pulse, session, PULSE_INTERVAL_MSEC );
         dbgmsg( NULL, "creating tracker timer" );
     }
 }
 
-static void
-freeRequest2( void * req )
-{
-    freeRequest( req );
-}
-
 void
-tr_trackerShuttingDown( tr_handle * handle )
+tr_trackerShuttingDown( tr_session * session )
 {
-    if( handle->tracker )
-    {
-        /* since we're shutting down, we don't need to scrape anymore... */
-        tr_list_free( &handle->tracker->scrapeQueue, freeRequest2 );
-
-        handle->tracker->isShuttingDown = 1;
-    }
+    if( session->tracker )
+        session->tracker->isShuttingDown = 1;
 }
 
 static int
-maybeFreeGlobals( tr_handle * handle )
+maybeFreeGlobals( tr_session * session )
 {
-    int globalsExist = handle->tracker != NULL;
+    int globalsExist = session->tracker != NULL;
 
     if( globalsExist
-        && ( handle->tracker->socketCount < 1 )
-        && ( handle->tracker->requestQueue == NULL )
-        && ( handle->tracker->scrapeQueue == NULL )
-        && ( handle->torrentList== NULL ) )
+        && ( session->tracker->runningCount < 1 )
+        && ( session->torrentList== NULL ) )
     {
         dbgmsg( NULL, "freeing tracker timer" );
-        tr_timerFree( &handle->tracker->pulseTimer );
-        tr_free( handle->tracker );
-        handle->tracker = NULL;
+        tr_timerFree( &session->tracker->pulseTimer );
+        tr_free( session->tracker );
+        session->tracker = NULL;
         globalsExist = FALSE;
     }
 
@@ -861,159 +740,101 @@ maybeFreeGlobals( tr_handle * handle )
 ****
 ***/
 
-static int
-freeConnection( void * evcon )
-{
-    evhttp_connection_free( evcon );
-    return FALSE;
-}
 static void
-connectionClosedCB( struct evhttp_connection * evcon, void * vhandle )
+invokeRequest( void * vreq )
 {
-    tr_handle * handle = vhandle;
-
-    /* libevent references evcon right after calling this function,
-       so we can't free it yet... defer it to after this call chain
-       has played out */
-    tr_timerNew( handle, freeConnection, evcon, 100 );
-}
+    struct tr_tracker_request * req = vreq;
+    uint8_t * hash;
+    tr_tracker * t = findTracker( req->session, req->torrent_hash );
 
-static struct evhttp_connection*
-getConnection( tr_handle * handle, const char * address, int port )
-{
-    struct evhttp_connection * c = evhttp_connection_new( address, port );
-    evhttp_connection_set_closecb( c, connectionClosedCB, handle );
-    return c;
-}
-
-static void
-invokeRequest( tr_handle * handle, const struct tr_tracker_request * req )
-{
-    const time_t now = time( NULL );
-    struct evhttp_connection * evcon = getConnection( handle, req->address, req->port );
-    tr_tracker * t = findTracker( handle, req->torrent_hash );
-    dbgmsg( t, "sending '%s' to tracker %s:%d, timeout is %d", req->uri, req->address, req->port, (int)req->timeout );
-    evhttp_connection_set_timeout( evcon, req->timeout );
-    ++handle->tracker->socketCount;
-
-    if( t != NULL )
+    if( t )
     {
         if( req->reqtype == TR_REQ_SCRAPE )
         {
-            t->lastScrapeTime = now;
+            t->lastScrapeTime = time( NULL );
             t->scrapeAt = 0;
         }
         else
         {
-            t->lastAnnounceTime = now;
+            t->lastAnnounceTime = time( NULL );
             t->reannounceAt = 0;
-            t->manualAnnounceAllowedAt = 0;
+            t->manualAnnounceAllowedAt = ~(time_t)0;
         }
     }
 
-    if( evhttp_make_request( evcon, req->req, EVHTTP_REQ_GET, req->uri ))
-        (*req->req->cb)(req->req, req->req->cb_arg);
-    else
-        dbgmsg( t, "incremented socket count to %d", handle->tracker->socketCount );
-}
+    ++req->session->tracker->runningCount;
 
-static void
-invokeNextInQueue( tr_handle * handle, tr_list ** list )
-{
-    struct tr_tracker_request * req = tr_list_pop_front( list );
-    invokeRequest( handle, req );
-    freeRequest( req );
-}
+    hash = tr_new0( uint8_t, SHA_DIGEST_LENGTH );
+    memcpy( hash, req->torrent_hash, SHA_DIGEST_LENGTH );
+    tr_webRun( req->session, req->url, req->done_func, hash );
 
-static int
-socketIsAvailable( tr_handle * handle )
-{
-    const int max = handle->tracker->isShuttingDown
-                      ? MAX_TRACKER_SOCKETS_DURING_SHUTDOWN
-                      : MAX_TRACKER_SOCKETS;
-    return handle->tracker->socketCount < max;
+    freeRequest( req );
 }
 
-static void ensureGlobalsExist( tr_handle * );
+static void ensureGlobalsExist( tr_session * );
 
 static void
-enqueueScrape( tr_handle * handle, const tr_tracker * tracker )
+enqueueScrape( tr_session * session, const tr_tracker * tracker )
 {
     struct tr_tracker_request * req;
-    ensureGlobalsExist( handle );
-    req = createScrape( handle, tracker );
-    tr_list_append( &handle->tracker->scrapeQueue, req );
+    ensureGlobalsExist( session );
+    req = createScrape( session, tracker );
+    tr_runInEventThread( session, invokeRequest, req );
 }
 
 static void
-enqueueRequest( tr_handle * handle, const tr_tracker * tracker, int reqtype )
+enqueueRequest( tr_session * session, const tr_tracker * tracker, int reqtype )
 {
     struct tr_tracker_request * req;
-    ensureGlobalsExist( handle );
-    req = createRequest( handle, tracker, reqtype );
-    tr_list_append( &handle->tracker->requestQueue, req );
-}
-
-static void
-scrapeSoon( tr_tracker * t )
-{
-    if( trackerSupportsScrape( t ) )
-        t->scrapeAt = time( NULL ) + t->randOffset;
+    ensureGlobalsExist( session );
+    req = createRequest( session, tracker, reqtype );
+    tr_runInEventThread( session, invokeRequest, req );
 }
 
 static int
-pulse( void * vhandle )
+pulse( void * vsession )
 {
-    tr_handle * handle = vhandle;
-    struct tr_tracker_handle * th = handle->tracker;
+    tr_session * session = vsession;
+    struct tr_tracker_handle * th = session->tracker;
     tr_torrent * tor;
     const time_t now = time( NULL );
 
-    if( handle->tracker == NULL )
+    if( !session->tracker )
         return FALSE;
 
-    if( handle->tracker->socketCount || tr_list_size(th->requestQueue) || tr_list_size(th->scrapeQueue) )
-        dbgmsg( NULL, "tracker pulse... %d sockets, %d reqs left, %d scrapes left", handle->tracker->socketCount, tr_list_size(th->requestQueue), tr_list_size(th->scrapeQueue) );
+    if( th->runningCount )
+        dbgmsg( NULL, "tracker pulse... %d running", th->runningCount );
 
     /* upkeep: queue periodic rescrape / reannounce */
-    for( tor=handle->torrentList; tor; tor=tor->next )
+    for( tor=session->torrentList; tor; tor=tor->next )
     {
         tr_tracker * t = tor->tracker;
 
-        if( t->scrapeAt && trackerSupportsScrape( t ) && ( now >= t->scrapeAt ) ) {
+        if( t->scrapeAt && trackerSupportsScrape( t, tor ) && ( now >= t->scrapeAt ) ) {
             t->scrapeAt = 0;
-            enqueueScrape( handle, t );
+            enqueueScrape( session, t );
         }
 
         if( t->reannounceAt && t->isRunning && ( now >= t->reannounceAt ) ) {
             t->reannounceAt = 0;
-            enqueueRequest( handle, t, TR_REQ_REANNOUNCE );
+            enqueueRequest( session, t, TR_REQ_REANNOUNCE );
         }
     }
 
-    if( handle->tracker->socketCount || tr_list_size(th->requestQueue) || tr_list_size(th->scrapeQueue) )
-        dbgmsg( NULL, "tracker pulse after upkeep... %d sockets, %d reqs left, %d scrapes left", handle->tracker->socketCount, tr_list_size(th->requestQueue), tr_list_size(th->scrapeQueue) );
-
-    /* look for things to do... process all the requests, then process all the scrapes */
-    while( th->requestQueue && socketIsAvailable( handle ) )
-        invokeNextInQueue( handle, &th->requestQueue );
-    while( th->scrapeQueue && socketIsAvailable( handle ) )
-        invokeNextInQueue( handle, &th->scrapeQueue );
+    if( th->runningCount )
+        dbgmsg( NULL, "tracker pulse after upkeep... %d running", th->runningCount );
 
-    if( handle->tracker->socketCount || tr_list_size(th->requestQueue) || tr_list_size(th->scrapeQueue) )
-        dbgmsg( NULL, "tracker pulse done... %d sockets, %d reqs left, %d scrapes left", handle->tracker->socketCount, tr_list_size(th->requestQueue), tr_list_size(th->scrapeQueue) );
-
-    return maybeFreeGlobals( handle );
+    return maybeFreeGlobals( session );
 }
 
 static void
-onReqDone( tr_handle * handle )
+onReqDone( tr_session * session )
 {
-    if( handle->tracker )
+    if( session->tracker )
     {
-        pulse( handle );
-        --handle->tracker->socketCount;
-        dbgmsg( NULL, "decrementing socket count to %d", handle->tracker->socketCount );
+        --session->tracker->runningCount;
+        dbgmsg( NULL, "decrementing runningCount to %d", session->tracker->runningCount );
+        pulse( session );
     }
 }
 
@@ -1056,56 +877,29 @@ tr_tracker *
 tr_trackerNew( const tr_torrent * torrent )
 {
     const tr_info * info = &torrent->info;
-    int i, j, sum, *iwalk;
-    tr_tracker_info * nwalk;
     tr_tracker * t;
 
     t = tr_new0( tr_tracker, 1 );
-    t->handle = torrent->handle;
-    t->scrapeIntervalSec       = DEFAULT_SCRAPE_INTERVAL_SEC;
-    t->retryScrapeIntervalSec  = 60;
-    t->announceIntervalSec     = DEFAULT_ANNOUNCE_INTERVAL_SEC;
-    t->announceMinIntervalSec  = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC;
-    generateKeyParam( t->key_param, KEYLEN );
-
     t->publisher = tr_publisherNew( );
-    t->timesDownloaded = -1;
-    t->seederCount = -1;
-    t->leecherCount = -1;
-    t->manualAnnounceAllowedAt = ~(time_t)0;
+    t->session                  = torrent->handle;
+    t->scrapeIntervalSec        = DEFAULT_SCRAPE_INTERVAL_SEC;
+    t->retryScrapeIntervalSec   = 60;
+    t->announceIntervalSec      = DEFAULT_ANNOUNCE_INTERVAL_SEC;
+    t->announceMinIntervalSec   = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC;
+    t->timesDownloaded          = -1;
+    t->seederCount              = -1;
+    t->leecherCount             = -1;
+    t->manualAnnounceAllowedAt  = ~(time_t)0;
     t->name = tr_strdup( info->name );
     t->randOffset = tr_rand( 120 );
     memcpy( t->hash, info->hash, SHA_DIGEST_LENGTH );
     escape( t->escaped, info->hash, SHA_DIGEST_LENGTH );
+    generateKeyParam( t->key_param, KEYLEN );
 
-    for( sum=i=0; i<info->trackerTiers; ++i )
-         sum += info->trackerList[i].count;
-    t->addresses = nwalk = tr_new0( tr_tracker_info, sum );
-    t->addressIndex = 0;
-    t->addressCount = sum;
-    t->tierFronts = iwalk = tr_new0( int, sum );
-
-    for( i=0; i<info->trackerTiers; ++i )
-    {
-        const int tierFront = nwalk - t->addresses;
-
-        for( j=0; j<info->trackerList[i].count; ++j )
-        {
-            const tr_tracker_info * src = &info->trackerList[i].list[j];
-            nwalk->address = tr_strdup( src->address );
-            nwalk->port = src->port;
-            nwalk->announce = tr_strdup( src->announce );
-            nwalk->scrape = tr_strdup( src->scrape );
-            ++nwalk;
-
-            *iwalk++ = tierFront;
-        }
-    }
-
-    assert( nwalk - t->addresses == sum );
-    assert( iwalk - t->tierFronts == sum );
+    t->trackerIndex = 0;
 
-    scrapeSoon( t );
+    if( trackerSupportsScrape( t, torrent ) )
+        t->scrapeAt = time( NULL ) + t->randOffset;
 
     return t;
 }
@@ -1113,7 +907,6 @@ tr_trackerNew( const tr_torrent * torrent )
 static void
 onTrackerFreeNow( void * vt )
 {
-    int i;
     tr_tracker * t = vt;
 
     tr_publisherFree( &t->publisher );
@@ -1121,18 +914,6 @@ onTrackerFreeNow( void * vt )
     tr_free( t->trackerID );
     tr_free( t->peer_id );
 
-    /* addresses... */
-    for( i=0; i<t->addressCount; ++i )
-        tr_trackerInfoClear( &t->addresses[i] );
-    tr_free( t->addresses );
-    tr_free( t->tierFronts );
-
-    /* redirect... */
-    if( t->redirect ) {
-        tr_trackerInfoClear( t->redirect );
-        tr_free( t->redirect );
-    }
-
     tr_free( t );
 }
 
@@ -1144,7 +925,7 @@ void
 tr_trackerFree( tr_tracker * t )
 {
     if( t )
-        tr_runInEventThread( t->handle, onTrackerFreeNow, t );
+        tr_runInEventThread( t->session, onTrackerFreeNow, t );
 }
 
 tr_publisher_tag
@@ -1202,37 +983,36 @@ tr_trackerGetCounts( const tr_tracker  * t,
 void
 tr_trackerStart( tr_tracker * t )
 {
-    if( t )
+    if( t && !t->isRunning )
     {
         tr_free( t->peer_id );
         t->peer_id = tr_peerIdNew( );
 
-        if( t->isRunning == 0 ) {
-            t->isRunning = 1;
-            enqueueRequest( t->handle, t, TR_REQ_STARTED );
-        }
+        t->isRunning = 1;
+        enqueueRequest( t->session, t, TR_REQ_STARTED );
     }
 }
 
 void
 tr_trackerReannounce( tr_tracker * t )
 {
-    enqueueRequest( t->handle, t, TR_REQ_REANNOUNCE );
+    enqueueRequest( t->session, t, TR_REQ_REANNOUNCE );
 }
 
 void
 tr_trackerCompleted( tr_tracker * t )
 {
-    enqueueRequest( t->handle, t, TR_REQ_COMPLETED );
+    enqueueRequest( t->session, t, TR_REQ_COMPLETED );
 }
 
 void
 tr_trackerStop( tr_tracker * t )
 {
+fprintf( stderr, "trackerStop... isRunning %d\n", (int)t->isRunning );
     if( t && t->isRunning ) {
         t->isRunning = 0;
         t->reannounceAt = t->manualAnnounceAllowedAt = 0;
-        enqueueRequest( t->handle, t, TR_REQ_STOPPED );
+        enqueueRequest( t->session, t, TR_REQ_STOPPED );
     }
 }
 
@@ -1244,21 +1024,22 @@ tr_trackerChangeMyPort( tr_tracker * t )
 }
 
 void
-tr_trackerStat( const tr_tracker * t,
+tr_trackerStat( const tr_tracker       * t,
                 struct tr_tracker_stat * setme)
 {
     assert( t != NULL );
     assert( setme != NULL );
 
-    strlcpy( setme->scrapeResponse,
-             t->lastScrapeResponse,
-             sizeof( setme->scrapeResponse ) );
+    snprintf( setme->scrapeResponse,
+              sizeof( setme->scrapeResponse ),
+              "%ld", t->lastScrapeResponse );
     setme->lastScrapeTime = t->lastScrapeTime;
     setme->nextScrapeTime = t->scrapeAt;
 
-    strlcpy( setme->announceResponse,
-             t->lastAnnounceResponse,
-             sizeof( setme->announceResponse ) );
+    snprintf( setme->announceResponse,
+              sizeof( setme->announceResponse ),
+              "%ld", t->lastAnnounceResponse );
+
     setme->lastAnnounceTime = t->lastAnnounceTime;
     setme->nextAnnounceTime = t->reannounceAt;
     setme->nextManualAnnounceTime = t->manualAnnounceAllowedAt;
diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h
index 40a123186..9727cc9cd 100644
--- a/libtransmission/transmission.h
+++ b/libtransmission/transmission.h
@@ -702,8 +702,7 @@ tr_piece;
     
 typedef struct tr_tracker_info
 {
-    char * address;
-    int    port;
+    int    tier;
     char * announce;
     char * scrape;
 }
@@ -723,14 +722,9 @@ struct tr_info
     unsigned int isPrivate : 1;
     unsigned int isMultifile : 1;
 
-    /* Tracker info */
-    struct
-    {
-        tr_tracker_info  * list;
-        int                 count;
-    }                  * trackerList;
-    int                  trackerTiers;
-    char               * primaryAddress;
+    /* these trackers are sorted by tier */
+    tr_tracker_info    * trackers;
+    int                  trackerCount;
 
     /* Torrent info */
     char               * comment;
diff --git a/libtransmission/trevent.c b/libtransmission/trevent.c
index 34333c86a..e88863010 100644
--- a/libtransmission/trevent.c
+++ b/libtransmission/trevent.c
@@ -190,7 +190,7 @@ tr_eventInit( tr_handle * handle )
     eh = tr_new0( tr_event_handle, 1 );
     eh->lock = tr_lockNew( );
     eh->h = handle;
-    eh->pulseInterval = timevalMsec( 100 );
+    eh->pulseInterval = tr_timevalMsec( 100 );
     eh->thread = tr_threadNew( libeventThreadFunc, eh, "libeventThreadFunc" );
 }
 
@@ -289,7 +289,7 @@ tr_timerNew( struct tr_handle * handle,
              uint64_t           timeout_milliseconds )
 {
     tr_timer * timer = tr_new0( tr_timer, 1 );
-    timer->tv = timevalMsec( timeout_milliseconds );
+    timer->tv = tr_timevalMsec( timeout_milliseconds );
     timer->func = func;
     timer->user_data = user_data;
     timer->eh = handle->events;
diff --git a/libtransmission/utils.c b/libtransmission/utils.c
index f605fd008..6f168c105 100644
--- a/libtransmission/utils.c
+++ b/libtransmission/utils.c
@@ -43,8 +43,6 @@
     #include <kernel/OS.h>
 #endif
 
-#include <miniupnp/miniwget.h> /* parseURL */
-
 #include "transmission.h"
 #include "trcompat.h"
 #include "utils.h"
@@ -352,7 +350,7 @@ tr_compareUint32( uint32_t a, uint32_t b )
 **/
 
 struct timeval
-timevalMsec( uint64_t milliseconds )
+tr_timevalMsec( uint64_t milliseconds )
 {
     struct timeval ret;
     const uint64_t microseconds = milliseconds * 1000;
@@ -954,28 +952,67 @@ tr_sha1_to_hex( char * out, const uint8_t * sha1 )
 ****
 ***/
 
+int
+tr_httpIsValidURL( const char * url )
+{
+    return !tr_httpParseURL( url, -1, NULL, NULL, NULL );
+}
+
 int
 tr_httpParseURL( const char * url_in, int len,
                  char ** setme_host,
                  int * setme_port,
                  char ** setme_path )
 {
-    char * url = tr_strndup( url_in, len );
-    char * path;
-    char host[4096+1];
-    unsigned short port;
-    int success;
+    int err;
+    int port = 0;
+    int n;
+    char * tmp;
+    char * pch;
+    const char * protocol = NULL;
+    const char * host = NULL;
+    const char * path = NULL;
+
+    tmp = tr_strndup( url_in, len );
+    if(( pch = strstr( tmp, "://" )))
+    {
+       *pch = '\0';
+       protocol = tmp;
+       pch += 3;
+/*fprintf( stderr, "protocol is [%s]... what's left is [%s]\n", protocol, pch );*/
+       if(( n = strcspn( pch, ":/" )))
+       {
+           const int havePort = pch[n] == ':';
+           host = pch;
+           pch += n;
+           *pch++ = '\0';
+/*fprintf( stderr, "host is [%s]... what's left is [%s]\n", host, pch );*/
+           if( havePort )
+           {
+               char * end;
+               port = strtol( pch, &end, 10 );
+               pch = end;
+/*fprintf( stderr, "port is [%d]... what's left is [%s]\n", port, pch );*/
+           }
+           path = pch;
+/*fprintf( stderr, "path is [%s]\n", path );*/
+       }
+    }
 
-    success = parseURL( url, host, &port, &path );
+    err = !host || !path || !protocol || ( strcmp(protocol,"http") && strcmp(protocol,"https") );
 
-    if( success ) {
-        if( setme_host ) *setme_host = tr_strdup( host );
-        if( setme_port ) *setme_port = port;
-        if( setme_path ) *setme_path = tr_strdup( path );
+    if( !err && !port ) {
+        if( !strcmp(protocol,"http") ) port = 80;
+        if( !strcmp(protocol,"https") ) port = 443;
     }
 
-    tr_free( url );
+    if( !err ) {
+        if( setme_host) { ((char*)host)[-3]=':'; *setme_host = tr_strdup( protocol ); }
+        if( setme_path) { ((char*)path)[-1]='/'; *setme_path = tr_strdup( path-1 ); }
+        if( setme_port) *setme_port = port;
+    }
 
-    return !success;
-}
 
+    tr_free( tmp );
+    return err;
+}
diff --git a/libtransmission/utils.h b/libtransmission/utils.h
index 7ba27a977..78e64f037 100644
--- a/libtransmission/utils.h
+++ b/libtransmission/utils.h
@@ -92,7 +92,7 @@ int tr_asprintf( char **strp, const char *fmt, ...);
 void tr_buildPath( char* buf, size_t buflen,
                    const char * first_element, ... );
 
-struct timeval timevalMsec( uint64_t milliseconds );
+struct timeval tr_timevalMsec( uint64_t milliseconds );
 
 
 int tr_ioErrorFromErrno( int err );
@@ -160,6 +160,8 @@ int tr_compareUint32( uint32_t a, uint32_t b );
 void tr_sha1_to_hex( char * out, const uint8_t * sha1 );
 
 
+int tr_httpIsValidURL( const char * url );
+
 int tr_httpParseURL( const char * url,
                      int          url_len,
                      char     ** setme_host,
diff --git a/libtransmission/web.c b/libtransmission/web.c
new file mode 100644
index 000000000..8038a4739
--- /dev/null
+++ b/libtransmission/web.c
@@ -0,0 +1,256 @@
+/*
+ * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.com>
+ *
+ * This file is licensed by the GPL version 2.  Works owned by the
+ * Transmission project are granted a special exemption to clause 2(b)
+ * so that the bulk of its code can remain under the MIT license. 
+ * This exemption does not extend to derived works not owned by
+ * the Transmission project.
+ *
+ * $Id:$
+ */
+
+#include <event.h>
+#include <curl/curl.h>
+
+#include "transmission.h"
+#include "utils.h"
+#include "web.h"
+
+struct tr_web
+{
+    CURLM * cm;
+    tr_session * session;
+    int remain;
+    struct event timeout;
+};
+
+struct tr_web_task
+{
+    unsigned int tag;
+    struct evbuffer * response;
+    tr_web_done_func * done_func;
+    void * done_func_user_data;
+};
+
+static size_t
+writeFunc( void * ptr, size_t size, size_t nmemb, void * vtask )
+{
+    const size_t byteCount = size * nmemb;
+    struct tr_web_task * task = vtask;
+    evbuffer_add( task->response, ptr, byteCount );
+    return byteCount;
+}
+
+void
+tr_webRun( tr_session         * session,
+           const char         * url,
+           tr_web_done_func   * done_func,
+           void               * done_func_user_data )
+{
+    static unsigned int tag = 0;
+    struct tr_web_task * task;
+    struct tr_web * web = session->web;
+    CURL * ch;
+    CURLMcode rc;
+
+    task = tr_new0( struct tr_web_task, 1 );
+    task->done_func = done_func;
+    task->done_func_user_data = done_func_user_data;
+    task->tag = ++tag;
+    task->response = evbuffer_new( );
+
+fprintf( stderr, "new web tag %u [%s]\n", task->tag, url );
+    ++web->remain;
+
+    ch = curl_easy_init( );
+    curl_easy_setopt( ch, CURLOPT_PRIVATE, task );
+    curl_easy_setopt( ch, CURLOPT_URL, url );
+    curl_easy_setopt( ch, CURLOPT_WRITEFUNCTION, writeFunc );
+    curl_easy_setopt( ch, CURLOPT_WRITEDATA, task );
+    curl_easy_setopt( ch, CURLOPT_USERAGENT, TR_NAME "/" LONG_VERSION_STRING );
+
+    curl_multi_add_handle( web->cm, ch );
+
+    do {
+        int tmp;
+        rc = curl_multi_socket_all( web->cm, &tmp );
+    } while( rc == CURLM_CALL_MULTI_PERFORM );
+}
+
+static void
+responseHandler( tr_web * web )
+{
+    int remaining = 0;
+
+    do {
+        CURLMsg * msg = curl_multi_info_read( web->cm, &remaining );
+        if( msg && ( msg->msg == CURLMSG_DONE ) )
+        {
+            CURL * ch;
+            struct tr_web_task * task;
+            long response_code;
+
+            if( msg->data.result != CURLE_OK )
+                tr_err( "%s", curl_easy_strerror( msg->data.result ) );
+			
+            ch = msg->easy_handle;
+            curl_easy_getinfo( ch, CURLINFO_PRIVATE, &task );
+            curl_easy_getinfo( ch, CURLINFO_RESPONSE_CODE, &response_code );
+
+fprintf( stderr, "web task %u done\n", task->tag );
+            task->done_func( web->session,
+                             response_code,
+                             EVBUFFER_DATA(task->response),
+                             EVBUFFER_LENGTH(task->response),
+                             task->done_func_user_data );
+
+            curl_multi_remove_handle( web->cm, ch );
+            curl_easy_cleanup( ch );
+
+            evbuffer_free( task->response );
+            tr_free( task );
+        }
+    }
+    while( remaining );
+}
+
+/* libevent says that sock is ready to be processed, so wake up libcurl */
+static void
+event_callback( int sock, short action, void * vweb )
+{
+    tr_web * web = vweb;
+    CURLMcode rc;
+    int mask;
+
+#if 0
+    static const char *strings[] = {
+        "NONE","TIMEOUT","READ","TIMEOUT|READ","WRITE","TIMEOUT|WRITE",
+        "READ|WRITE","TIMEOUT|READ|WRITE","SIGNAL" };
+    fprintf( stderr, "Event on socket %d (%s)\n", sock, strings[action] );
+#endif
+
+    switch (action & (EV_READ|EV_WRITE)) {
+        case EV_READ: mask = CURL_CSELECT_IN; break;
+        case EV_WRITE: mask = CURL_CSELECT_OUT; break;
+        case EV_READ|EV_WRITE: mask = CURL_CSELECT_IN|CURL_CSELECT_OUT; break;
+        default: tr_err( "Unknown event %d\n", (int)action ); return;
+    }
+
+    do
+        rc = curl_multi_socket_action( web->cm, sock, mask, &web->remain );
+    while( rc == CURLM_CALL_MULTI_PERFORM );
+
+    if ( rc != CURLM_OK  )
+        tr_err( "%s (%d)", curl_multi_strerror(rc), (int)sock );
+
+    responseHandler( web );
+
+    /* remove timeout if there are no transfers left */
+    if( !web->remain
+        && event_initialized( &web->timeout )
+        && event_pending( &web->timeout, EV_TIMEOUT, NULL ) ) {
+            event_del( &web->timeout );
+            fprintf( stderr, "Removed timeout\n" );
+    }
+}
+
+/* libcurl wants us to tell it when sock is ready to be processed */
+static int
+socket_callback( CURL            * easy UNUSED,
+                 curl_socket_t     sock,
+                 int               action,
+                 void            * vweb,
+                 void            * assigndata )
+{
+    tr_web * web = vweb;
+    int events = EV_PERSIST;
+    struct event * ev = assigndata;
+
+    if( ev )
+        event_del( ev );
+    else {
+        ev = tr_new0( struct event, 1 );
+        curl_multi_assign( web->cm, sock, ev );
+    }
+
+#if 0
+    {
+        static const char *actions[] = {"NONE", "IN", "OUT", "INOUT", "REMOVE"};
+        fprintf( stderr, "Callback on socket %d (%s)\n", (int)sock, actions[action]);
+    }
+#endif
+
+    switch (action) {
+        case CURL_POLL_IN: events |= EV_READ; break;
+        case CURL_POLL_OUT: events |= EV_WRITE; break;
+        case CURL_POLL_INOUT: events |= EV_READ|EV_WRITE; break;
+        case CURL_POLL_REMOVE: tr_free( ev ); /* fallthrough */
+        case CURL_POLL_NONE: return 0;
+        default: tr_err( "Unknown socket action %d", action ); return -1;
+    }
+
+    event_set( ev, sock, events, event_callback, web );
+    event_add( ev, NULL );
+    return 0;
+}
+
+/* libevent says that timeout_ms have passed, so wake up libcurl */
+static void
+timeout_callback( int socket UNUSED, short action UNUSED, void * vweb )
+{
+    CURLMcode rc;
+    tr_web * web = vweb;
+
+    do
+        rc = curl_multi_socket( web->cm, CURL_SOCKET_TIMEOUT, &web->remain );
+    while( rc == CURLM_CALL_MULTI_PERFORM );
+
+    if( rc != CURLM_OK )
+        tr_err( "%s", curl_multi_strerror( rc ) );
+}
+
+/* libcurl wants us to tell it when timeout_ms have passed */
+static void
+timer_callback( CURLM *multi UNUSED, long timeout_ms, void * vweb )
+{
+    tr_web * web = vweb;
+    struct timeval tv = tr_timevalMsec( timeout_ms );
+
+    if( event_initialized( &web->timeout )
+        && event_pending( &web->timeout, EV_TIMEOUT, NULL ) )
+            event_del( &web->timeout );
+
+    event_set( &web->timeout, -1, 0, timeout_callback, vweb );
+    event_add( &web->timeout, &tv );
+}
+
+tr_web*
+tr_webInit( tr_session * session )
+{
+    static int curlInited = FALSE;
+    tr_web * web;
+
+    /* call curl_global_init if we haven't done it already.
+     * try to enable ssl for https support; but if that fails,
+     * try a plain vanilla init */ 
+    if( curlInited == FALSE ) {
+        curlInited = TRUE;
+        if( curl_global_init( CURL_GLOBAL_SSL ) )
+            curl_global_init( 0 );
+    }
+   
+    web = tr_new0( struct tr_web, 1 );
+    web->cm = curl_multi_init( );
+    web->session = session;
+    web->remain = 0;
+
+    curl_multi_setopt( web->cm, CURLMOPT_SOCKETDATA, web );
+    curl_multi_setopt( web->cm, CURLMOPT_SOCKETFUNCTION, socket_callback );
+    curl_multi_setopt( web->cm, CURLMOPT_TIMERDATA, web );
+    curl_multi_setopt( web->cm, CURLMOPT_TIMERFUNCTION, timer_callback );
+    curl_multi_setopt( web->cm, CURLMOPT_MAXCONNECTS, 20 );
+    curl_multi_setopt( web->cm, CURLMOPT_PIPELINING, 1 );
+
+    return web;
+}
diff --git a/libtransmission/web.h b/libtransmission/web.h
new file mode 100644
index 000000000..bb926c9d8
--- /dev/null
+++ b/libtransmission/web.h
@@ -0,0 +1,34 @@
+/*
+ * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.com>
+ *
+ * This file is licensed by the GPL version 2.  Works owned by the
+ * Transmission project are granted a special exemption to clause 2(b)
+ * so that the bulk of its code can remain under the MIT license. 
+ * This exemption does not extend to derived works not owned by
+ * the Transmission project.
+ *
+ * $Id:$
+ */
+
+#ifndef TR_HTTP_H
+#define TR_HTTP_H
+
+struct tr_handle;
+typedef struct tr_web tr_web;
+
+tr_web* tr_webInit( tr_handle * session );
+
+typedef void (tr_web_done_func)( tr_handle    * session,
+                                  long          response_code,
+                                  const void  * response,
+                                  size_t        response_byte_count,
+                                  void        * user_data );
+                               
+void tr_webRun( tr_handle          * session,
+                const char         * url,
+                tr_web_done_func     done_func,
+                void               * done_func_user_data );
+
+
+
+#endif
-- 
2.40.0