diff --git a/LICENSE.txt b/LICENSE.txt
index d91b95fe795772203ae9e9b650aafe7774404cba..b769f516e82b49ac587c082e0821e7add6c8eee9 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -238,8 +238,8 @@ Applications:
       Bundles systray4j-2.4.1:
       See licenses/LICENSE-LGPLv2.1.txt
 
-   Tomcat 6.0.35:
-   Copyright 1999-2011 The Apache Software Foundation
+   Tomcat 6.0.36:
+   Copyright 1999-2012 The Apache Software Foundation
    See licenses/LICENSE-Apache2.0.txt
    See licenses/NOTICE-Tomcat.txt
 
diff --git a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
index a9b70041c8b74af4343a1768daaa261a3a17dee0..810a10db08e7c66031eeba934261d9fd8c5edf9c 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
@@ -61,6 +61,7 @@ public class MetaInfo
   private final byte[] piece_hashes;
   private final long length;
   private final boolean privateTorrent;
+  private final List<List<String>> announce_list;
   private Map<String, BEValue> infoMap;
 
   /**
@@ -69,9 +70,11 @@ public class MetaInfo
    *  @param announce may be null
    *  @param files null for single-file torrent
    *  @param lengths null for single-file torrent
+   *  @param announce_list may be null
    */
   MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
-           int piece_length, byte[] piece_hashes, long length, boolean privateTorrent)
+           int piece_length, byte[] piece_hashes, long length, boolean privateTorrent,
+           List<List<String>> announce_list)
   {
     this.announce = announce;
     this.name = name;
@@ -83,6 +86,7 @@ public class MetaInfo
     this.piece_hashes = piece_hashes;
     this.length = length;
     this.privateTorrent = privateTorrent;
+    this.announce_list = announce_list;
 
     // TODO if we add a parameter for other keys
     //if (other != null) {
@@ -141,6 +145,23 @@ public class MetaInfo
         this.announce = val.getString();
     }
 
+    // BEP 12
+    val = m.get("announce-list");
+    if (val == null) {
+        this.announce_list = null;
+    } else {
+        this.announce_list = new ArrayList();
+        List<BEValue> bl1 = val.getList();
+        for (BEValue bev : bl1) {
+            List<BEValue> bl2 = bev.getList();
+            List<String> sl2 = new ArrayList();           
+            for (BEValue bev2 : bl2) {
+                sl2.add(bev2.getString());
+            }
+            this.announce_list.add(sl2);
+        }
+    }
+
     val = m.get("info");
     if (val == null)
         throw new InvalidBEncodingException("Missing info map");
@@ -296,6 +317,15 @@ public class MetaInfo
     return announce;
   }
 
+  /**
+   * Returns a list of lists of urls.
+   *
+   * @since 0.9.5
+   */
+  public List<List<String>> getAnnounceList() {
+    return announce_list;
+  }
+
   /**
    * Returns the original 20 byte SHA1 hash over the bencoded info map.
    */
@@ -470,12 +500,13 @@ public class MetaInfo
   /**
    * Creates a copy of this MetaInfo that shares everything except the
    * announce URL.
+   * Drops any announce-list.
    */
   public MetaInfo reannounce(String announce)
   {
     return new MetaInfo(announce, name, name_utf8, files,
                         lengths, piece_length,
-                        piece_hashes, length, privateTorrent);
+                        piece_hashes, length, privateTorrent, null);
   }
 
   /**
@@ -486,6 +517,8 @@ public class MetaInfo
         Map m = new HashMap();
         if (announce != null)
             m.put("announce", announce);
+        if (announce_list != null)
+            m.put("announce-list", announce_list);
         Map info = createInfoMap();
         m.put("info", info);
         // don't save this locally, we should only do this once
diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
index eef6679ce2d74b2227ed1f03bb932c39ca4873ee..08e2ae67bd7bbc0dce8045aadf11855c30a44b5f 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
@@ -886,7 +886,9 @@ public class SnarkManager implements CompleteListener {
                         }
                     }
                 } catch (IOException ioe) {
-                    addMessage(_("Torrent in \"{0}\" is invalid", sfile.getName()) + ": " + ioe.getMessage());
+                    String err = _("Torrent in \"{0}\" is invalid", sfile.getName()) + ": " + ioe.getMessage();
+                    addMessage(err);
+                    _log.error(err, ioe);
                     if (sfile.exists())
                         sfile.delete();
                     return;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
index a42001ba8c607613804fa34d2c1952b637d64ed0..036e0f39b3e45f88eeba0f227743ace581f63c55 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
@@ -122,6 +122,7 @@ public class Storage
    * @throws IOException when creating and/or checking files fails.
    */
   public Storage(I2PSnarkUtil util, File baseFile, String announce,
+                 List<List<String>> announce_list,
                  boolean privateTorrent, StorageListener listener)
     throws IOException
   {
@@ -182,7 +183,8 @@ public class Storage
     // TODO thread this so we can return and show something on the UI
     byte[] piece_hashes = fast_digestCreate();
     metainfo = new MetaInfo(announce, baseFile.getName(), null, files,
-                            lengthsList, piece_size, piece_hashes, total, privateTorrent);
+                            lengthsList, piece_size, piece_hashes, total, privateTorrent,
+                            announce_list);
 
   }
 
@@ -1225,7 +1227,7 @@ public class Storage
       File file = null;
       FileOutputStream out = null;
       try {
-          Storage storage = new Storage(util, base, announce, false, null);
+          Storage storage = new Storage(util, base, announce, null, false, null);
           MetaInfo meta = storage.getMetaInfo();
           file = new File(storage.getBaseName() + ".torrent");
           out = new FileOutputStream(file);
diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
index d6cf2fe7cadf931c1c98ecdc9124d1b2d2211ce0..65dc6d505f68074dc53adbaffb6a60e90fa1cebb 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
@@ -31,6 +31,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
@@ -40,6 +41,7 @@ import java.util.Set;
 import net.i2p.I2PAppContext;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Hash;
+import net.i2p.util.ConvertToHash;
 import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
 import net.i2p.util.SimpleTimer2;
@@ -109,8 +111,8 @@ public class TrackerClient implements Runnable {
   private boolean completed;
   private volatile boolean _fastUnannounce;
   private long lastDHTAnnounce;
-  private final List<Tracker> trackers;
-  private final List<Tracker> backupTrackers;
+  private final List<TCTracker> trackers;
+  private final List<TCTracker> backupTrackers;
 
   /**
    * Call start() to start it.
@@ -270,9 +272,12 @@ public class TrackerClient implements Runnable {
         primary = meta.getAnnounce();
     else if (additionalTrackerURL != null)
         primary = additionalTrackerURL;
+    Set<Hash> trackerHashes = new HashSet(8);
+
+    // primary tracker
     if (primary != null) {
-        if (isValidAnnounce(primary)) {
-            trackers.add(new Tracker(primary, true));
+        if (isNewValidTracker(trackerHashes, primary)) {
+            trackers.add(new TCTracker(primary, true));
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug("Announce: [" + primary + "] infoHash: " + infoHash);
         } else {
@@ -281,36 +286,35 @@ public class TrackerClient implements Runnable {
         }
     } else {
         _log.warn("No primary announce");
-        primary = "";
     }
+
+    // announce list
+    if (meta != null && !meta.isPrivate()) {
+        List<List<String>> list = meta.getAnnounceList();
+        if (list != null) {
+            for (List<String> llist : list) {
+                for (String url : llist) {
+                    if (!isNewValidTracker(trackerHashes, url))
+                        continue;
+                    trackers.add(new TCTracker(url, trackers.isEmpty()));
+                    if (_log.shouldLog(Log.DEBUG))
+                        _log.debug("Additional announce (list): [" + url + "] for infoHash: " + infoHash);
+                }
+            }
+        }
+    }
+
+    // configured open trackers
     if (meta == null || !meta.isPrivate()) {
         List<String> tlist = _util.getOpenTrackers();
         for (int i = 0; i < tlist.size(); i++) {
-             String url = tlist.get(i);
-             if (!isValidAnnounce(url)) {
-                _log.error("Bad announce URL: [" + url + "]");
-                continue;
-             }
-             int slash = url.indexOf('/', 7);
-             if (slash <= 7) {
-                _log.error("Bad announce URL: [" + url + "]");
+            String url = tlist.get(i);
+            if (!isNewValidTracker(trackerHashes, url))
                 continue;
-             }
-             if (primary.startsWith(url.substring(0, slash)))
-                continue;
-             String dest = _util.lookup(url.substring(7, slash));
-             if (dest == null) {
-                _log.error("Announce host unknown: [" + url.substring(7, slash) + "]");
-                continue;
-             }
-             if (primary.startsWith("http://" + dest))
-                continue;
-             if (primary.startsWith("http://i2p/" + dest))
-                continue;
-             // opentrackers are primary if we don't have primary
-             trackers.add(new Tracker(url, primary.equals("")));
-             if (_log.shouldLog(Log.DEBUG))
-                 _log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
+            // opentrackers are primary if we don't have primary
+            trackers.add(new TCTracker(url, trackers.isEmpty()));
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
         }
     }
 
@@ -318,31 +322,40 @@ public class TrackerClient implements Runnable {
     if (trackers.isEmpty() && (meta == null || !meta.isPrivate())) {
         List<String> tlist = _util.getBackupTrackers();
         for (int i = 0; i < tlist.size(); i++) {
-             String url = tlist.get(i);
-             if (!isValidAnnounce(url)) {
-                _log.error("Bad announce URL: [" + url + "]");
-                continue;
-             }
-             int slash = url.indexOf('/', 7);
-             if (slash <= 7) {
-                _log.error("Bad announce URL: [" + url + "]");
+            String url = tlist.get(i);
+            if (!isNewValidTracker(trackerHashes, url))
                 continue;
-             }
-             String dest = _util.lookup(url.substring(7, slash));
-             if (dest == null) {
-                _log.error("Announce host unknown: [" + url.substring(7, slash) + "]");
-                continue;
-             }
-             backupTrackers.add(new Tracker(url, false));
-             if (_log.shouldLog(Log.DEBUG))
-                 _log.debug("Backup announce: [" + url + "] for infoHash: " + infoHash);
+            backupTrackers.add(new TCTracker(url, false));
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Backup announce: [" + url + "] for infoHash: " + infoHash);
+        }
+        if (backupTrackers.isEmpty()) {
+            backupTrackers.add(new TCTracker(DEFAULT_BACKUP_TRACKER, false));
         }
-        if (backupTrackers.isEmpty())
-            backupTrackers.add(new Tracker(DEFAULT_BACKUP_TRACKER, false));
     }
     this.completed = coordinator.getLeft() == 0;
   }
 
+  /**
+   *  @param existing the ones we already know about
+   *  @param ann an announce URL non-null
+   *  @return true if ann is valid and new; adds to existing if returns true
+   *  @since 0.9.5
+   */
+  private boolean isNewValidTracker(Set<Hash> existing, String ann) {
+      Hash h = getHostHash(ann);
+      if (h == null) {
+         _log.error("Bad announce URL: [" + ann + ']');
+         return false;
+      }
+      boolean rv = existing.add(h);
+      if (!rv) {
+          if (_log.shouldLog(Log.INFO))
+             _log.info("Dup announce URL: [" + ann + ']');
+      }
+      return rv;
+  }
+
   /**
    *  Announce to all the trackers, get peers from PEX and DHT, then queue up a SimpleTimer2 event.
    *  This will take several seconds to several minutes.
@@ -425,7 +438,7 @@ public class TrackerClient implements Runnable {
   /**
    *  @return max peers seen
    */
-  private int getPeersFromTrackers(List<Tracker> trckrs) {
+  private int getPeersFromTrackers(List<TCTracker> trckrs) {
             long uploaded = coordinator.getUploaded();
             long downloaded = coordinator.getDownloaded();
             long left = coordinator.getLeft();   // -1 in magnet mode
@@ -442,7 +455,7 @@ public class TrackerClient implements Runnable {
 
             // *** loop once for each tracker
             int maxSeenPeers = 0;
-            for (Tracker tr : trckrs) {
+            for (TCTracker tr : trckrs) {
               if ((!stop) && (!tr.stop) &&
                   (completed || coordinator.needOutboundPeers() || !tr.started) &&
                   (event.equals(COMPLETED_EVENT) || System.currentTimeMillis() > tr.lastRequestTime + tr.interval))
@@ -639,7 +652,7 @@ public class TrackerClient implements Runnable {
       if (dht != null)
           dht.unannounce(snark.getInfoHash());
       int i = 0;
-      for (Tracker tr : trackers) {
+      for (TCTracker tr : trackers) {
           if (_util.connected() &&
               tr.started && (!tr.stop) && tr.trackerProblems == null) {
               try {
@@ -659,9 +672,9 @@ public class TrackerClient implements Runnable {
    *  @since 0.9.1
    */
   private class Unannouncer implements Runnable {
-     private final Tracker tr;
+     private final TCTracker tr;
 
-     public Unannouncer(Tracker tr) {
+     public Unannouncer(TCTracker tr) {
          this.tr = tr;
      }
 
@@ -685,7 +698,7 @@ public class TrackerClient implements Runnable {
      }
   }
   
-  private TrackerInfo doRequest(Tracker tr, String infoHash,
+  private TrackerInfo doRequest(TCTracker tr, String infoHash,
                                 String peerID, long uploaded,
                                 long downloaded, long left, String event)
     throws IOException
@@ -775,6 +788,7 @@ public class TrackerClient implements Runnable {
   }
 
   /**
+   *  @param ann an announce URL
    *  @return true for i2p hosts only
    *  @since 0.7.12
    */
@@ -790,10 +804,38 @@ public class TrackerClient implements Runnable {
            url.getPort() < 0;
   }
 
-  private static class Tracker
+  /**
+   *  @param ann an announce URL non-null
+   *  @return a Hash for i2p hosts only, null otherwise
+   *  @since 0.9.5
+   */
+  private static Hash getHostHash(String ann) {
+    URL url;
+    try {
+        url = new URL(ann);
+    } catch (MalformedURLException mue) {
+        return null;
+    }
+    if (url.getPort() >= 0 || !url.getProtocol().equals("http"))
+        return null;
+    String host = url.getHost();
+    if (host.endsWith(".i2p"))
+        return ConvertToHash.getHash(host);
+    if (host.equals("i2p")) {
+        String path = url.getPath();
+        if (path == null || path.length() < 517 ||
+            !path.startsWith("/"))
+            return null;
+        String[] parts = path.substring(1).split("/?&;", 2);
+        return ConvertToHash.getHash(parts[0]);
+    }
+    return null;
+  }
+
+  private static class TCTracker
   {
-      String announce;
-      boolean isPrimary;
+      final String announce;
+      final boolean isPrimary;
       long interval;
       long lastRequestTime;
       String trackerProblems;
@@ -803,7 +845,7 @@ public class TrackerClient implements Runnable {
       int consecutiveFails;
       int seenPeers;
 
-      public Tracker(String a, boolean p)
+      public TCTracker(String a, boolean p)
       {
           announce = a;
           isPrimary = p;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
index ea5d751e554cbc2062acdf1343b72d11374c4c18..2765d587310cbc7fbf419b66e496875fb009a3c1 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -61,7 +61,7 @@ public class I2PSnarkServlet extends DefaultServlet {
     private Resource _resourceBase;
     private String _themePath;
     private String _imgPath;
-    private String _lastAnnounceURL = "";
+    private String _lastAnnounceURL;
     
     public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
  
@@ -730,18 +730,54 @@ public class I2PSnarkServlet extends DefaultServlet {
                 //if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) )
                 //    announceURL = announceURLOther;
 
-                if (announceURL == null || announceURL.length() <= 0)
-                    _manager.addMessage(_("Error creating torrent - you must select a tracker"));
-                else if (baseFile.exists()) {
-                    _lastAnnounceURL = announceURL;
+                if (baseFile.exists()) {
                     if (announceURL.equals("none"))
                         announceURL = null;
+                    _lastAnnounceURL = announceURL;
+                    List<String> backupURLs = new ArrayList();
+                    Enumeration e = req.getParameterNames();
+                    while (e.hasMoreElements()) {
+                         Object o = e.nextElement();
+                         if (!(o instanceof String))
+                             continue;
+                         String k = (String) o;
+                        if (k.startsWith("backup_")) {
+                            String url = k.substring(7);
+                            if (!url.equals(announceURL))
+                                backupURLs.add(url);
+                        }
+                    }
+                    List<List<String>> announceList = null;
+                    if (!backupURLs.isEmpty()) {
+                        // BEP 12 - Put primary first, then the others, each as the sole entry in their own list
+                        if (announceURL == null) {
+                            _manager.addMessage(_("Error - Cannot include alternate trackers without a primary tracker"));
+                            return;
+                        }
+                        backupURLs.add(0, announceURL);
+                        boolean hasPrivate = false;
+                        boolean hasPublic = false;
+                        for (String url : backupURLs) {
+                            if (_manager.getPrivateTrackers().contains(announceURL))
+                                hasPrivate = true;
+                            else
+                                hasPublic = true;
+                        }
+                        if (hasPrivate && hasPublic) {
+                            _manager.addMessage(_("Error - Cannot mix private and public trackers in a torrent"));
+                            return;
+                        }
+                        announceList = new ArrayList(backupURLs.size());
+                        for (String url : backupURLs) {
+                            announceList.add(Collections.singletonList(url));
+                        }
+                    }
                     try {
                         // This may take a long time to check the storage, but since it already exists,
                         // it shouldn't be THAT bad, so keep it in this thread.
                         // TODO thread it for big torrents, perhaps a la FetchAndAdd
                         boolean isPrivate = _manager.getPrivateTrackers().contains(announceURL);
-                        Storage s = new Storage(_manager.util(), baseFile, announceURL, isPrivate, null);
+                        Storage s = new Storage(_manager.util(), baseFile, announceURL, announceList, isPrivate, null);
                         s.close(); // close the files... maybe need a way to pass this Storage to addTorrent rather than starting over
                         MetaInfo info = s.getMetaInfo();
                         File torrentFile = new File(_manager.getDataDir(), s.getBaseName() + ".torrent");
@@ -1372,6 +1408,7 @@ public class I2PSnarkServlet extends DefaultServlet {
     }
 
     /**
+     *  Start of anchor only, caller must add anchor text or img and close anchor
      *  @return string or null
      *  @since 0.8.4
      */
@@ -1399,6 +1436,7 @@ public class I2PSnarkServlet extends DefaultServlet {
     }
 
     /**
+     *  Full anchor with img
      *  @return string or null
      *  @since 0.8.4
      */
@@ -1414,6 +1452,29 @@ public class I2PSnarkServlet extends DefaultServlet {
         return null;
     }
 
+    /**
+     *  Full anchor with shortened URL as anchor text
+     *  @return string, non-null
+     *  @since 0.9.5
+     */
+    private String getShortTrackerLink(String announce, byte[] infohash) {
+        StringBuilder buf = new StringBuilder(128);
+        String trackerLinkUrl = getTrackerLinkUrl(announce, infohash);
+        if (trackerLinkUrl != null)
+            buf.append(trackerLinkUrl);
+        if (announce.startsWith("http://"))
+            announce = announce.substring(7);
+        int slsh = announce.indexOf('/');
+        if (slsh > 0)
+            announce = announce.substring(0, slsh);
+        if (announce.length() > 67)
+            announce = announce.substring(0, 40) + "&hellip;" + announce.substring(announce.length() - 8);
+        buf.append(announce);
+        if (trackerLinkUrl != null)
+            buf.append("</a>");
+        return buf.toString();
+    }
+
     private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
         // display incoming parameter if a GET so links will work
         String newURL = req.getParameter("newURL");
@@ -1482,23 +1543,32 @@ public class I2PSnarkServlet extends DefaultServlet {
                   + "\" title=\"");
         out.write(_("File or directory to seed (must be within the specified path)"));
         out.write("\" ><tr><td>\n");
-        out.write(_("Tracker"));
-        out.write(":<td><select name=\"announceURL\"><option value=\"\">");
-        out.write(_("Select a tracker"));
-        out.write("</option>\n");
-        // todo remember this one with _lastAnnounceURL also
-        out.write("<option value=\"none\">");
-        //out.write(_("Open trackers and DHT only"));
-        out.write(_("Open trackers only"));
-        out.write("</option>\n");
+        out.write(_("Trackers"));
+        out.write(":<td><table style=\"width: 20%;\"><tr><td></td><td align=\"center\">");
+        out.write(_("Primary"));
+        out.write("</td><td align=\"center\">");
+        out.write(_("Alternates"));
+        out.write("</td></tr>\n");
         for (Tracker t : sortedTrackers) {
             String name = t.name;
             String announceURL = t.announceURL.replace("&#61;", "=");
+            out.write("<tr><td>");
+            out.write(name);
+            out.write("</td><td align=\"center\"><input type=\"radio\" name=\"announceURL\" value=\"");
+            out.write(announceURL);
+            out.write("\"");
             if (announceURL.equals(_lastAnnounceURL))
-                announceURL += "\" selected=\"selected";
-            out.write("\t<option value=\"" + announceURL + "\">" + name + "</option>\n");
+                out.write(" checked");
+            out.write("></td><td align=\"center\"><input type=\"checkbox\" name=\"backup_");
+            out.write(announceURL);
+            out.write("\" value=\"foo\"></td></tr>\n");
         }
-        out.write("</select>\n");
+        out.write("<tr><td>");
+        out.write(_("none"));
+        out.write("</td><td align=\"center\"><input type=\"radio\" name=\"announceURL\" value=\"none\"");
+        if (_lastAnnounceURL == null)
+            out.write(" checked");
+        out.write("></td><td></td></tr></table>\n");
         // make the user add a tracker on the config form now
         //out.write(_("or"));
         //out.write("&nbsp;<input type=\"text\" name=\"announceURLOther\" size=\"57\" value=\"http://\" " +
@@ -1998,20 +2068,26 @@ public class I2PSnarkServlet extends DefaultServlet {
                     String trackerLink = getTrackerLink(announce, snark.getInfoHash());
                     if (trackerLink != null)
                         buf.append(trackerLink).append(' ');
-                    buf.append("<b>").append(_("Tracker")).append(":</b> ");
-                    String trackerLinkUrl = getTrackerLinkUrl(announce, snark.getInfoHash());
-                    if (trackerLinkUrl != null)
-                        buf.append(trackerLinkUrl);
-                    if (announce.startsWith("http://"))
-                        announce = announce.substring(7);
-                    int slsh = announce.indexOf('/');
-                    if (slsh > 0)
-                        announce = announce.substring(0, slsh);
-                    if (announce.length() > 67)
-                        announce = announce.substring(0, 40) + "&hellip;" + announce.substring(announce.length() - 8);
-                    buf.append(announce);
-                    if (trackerLinkUrl != null)
-                        buf.append("</a>");
+                    buf.append("<b>").append(_("Primary Tracker")).append(":</b> ");
+                    buf.append(getShortTrackerLink(announce, snark.getInfoHash()));
+                    buf.append("</td></tr>");
+                }
+                List<List<String>> alist = meta.getAnnounceList();
+                if (alist != null) {
+                    buf.append("<tr><td><b>");
+                    buf.append(_("Tracker List")).append(":</b> ");
+                    for (List<String> alist2 : alist) {
+                        buf.append('[');
+                        boolean more = false;
+                        for (String s : alist2) {
+                            if (more)
+                                buf.append(' ');
+                            else
+                                more = true;
+                            buf.append(getShortTrackerLink(s, snark.getInfoHash()));
+                        }
+                        buf.append("] ");
+                    }
                     buf.append("</td></tr>");
                 }
             }
diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp
index 6ee0ae540ef9fcf1ffda0c87c264193b071630ab..0ee00159b9622994f587765c70fcb6cd73d7f134 100644
--- a/apps/i2ptunnel/jsp/editClient.jsp
+++ b/apps/i2ptunnel/jsp/editClient.jsp
@@ -248,7 +248,7 @@
                     <option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>><%=intl._("2 inbound, 2 outbound tunnels (standard bandwidth usage, standard reliability)")%></option>
                     <option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>><%=intl._("3 inbound, 3 outbound tunnels (higher bandwidth usage, higher reliability)")%></option>
                 <% if (tunnelQuantity > 3) {
-                %>    <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> <%=intl._("tunnels")%></option>
+                %>    <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%>&nbsp;<%=intl._("tunnels")%></option>
                 <% }
               %></select>                
             </div>
diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp
index 9447fc36873025f04ffe74a285927afd3a9e3266..7b0deaff8e0dd14dcf879edd3c4f21a6c1a8bab4 100644
--- a/apps/i2ptunnel/jsp/editServer.jsp
+++ b/apps/i2ptunnel/jsp/editServer.jsp
@@ -264,8 +264,11 @@
                   %><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>><%=intl._("1 inbound, 1 outbound tunnel  (low bandwidth usage, less reliability)")%></option>
                     <option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>><%=intl._("2 inbound, 2 outbound tunnels (standard bandwidth usage, standard reliability)")%></option>
                     <option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>><%=intl._("3 inbound, 3 outbound tunnels (higher bandwidth usage, higher reliability)")%></option>
-                <% if (tunnelQuantity > 3) {
-                %>    <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> <%=intl._("tunnels")%></option>
+                    <option value="4"<%=(tunnelQuantity == 4 ? " selected=\"selected\"" : "") %>><%=intl._("4 in, 4 out (high traffic server)")%></option>
+                    <option value="5"<%=(tunnelQuantity == 5 ? " selected=\"selected\"" : "") %>><%=intl._("5 in, 5 out (high traffic server)")%></option>
+                    <option value="6"<%=(tunnelQuantity == 6 ? " selected=\"selected\"" : "") %>><%=intl._("6 in, 6 out (high traffic server)")%></option>
+                <% if (tunnelQuantity > 6) {
+                %>    <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%>&nbsp;<%=intl._("tunnels")%></option>
                 <% }
               %></select>                
             </div>
diff --git a/apps/jetty/apache-tomcat-deployer/NOTICE b/apps/jetty/apache-tomcat-deployer/NOTICE
index c44c35de8848e6dbe18ff5477c67694b74b19042..aaa19b6a8c8382fc1542021af67fa7b40ba5756e 100644
--- a/apps/jetty/apache-tomcat-deployer/NOTICE
+++ b/apps/jetty/apache-tomcat-deployer/NOTICE
@@ -1,5 +1,5 @@
 Apache Tomcat
-Copyright 1999-2011 The Apache Software Foundation
+Copyright 1999-2012 The Apache Software Foundation
 
 This product includes software developed by
 The Apache Software Foundation (http://www.apache.org/).
diff --git a/apps/jetty/apache-tomcat-deployer/README-i2p.txt b/apps/jetty/apache-tomcat-deployer/README-i2p.txt
index dd398113861368996037f52c137ce55db6445a44..5a0568444a76b401ca449db17ce9f08318456c01 100644
--- a/apps/jetty/apache-tomcat-deployer/README-i2p.txt
+++ b/apps/jetty/apache-tomcat-deployer/README-i2p.txt
@@ -2,7 +2,7 @@ This is Apache Tomcat 6.x, supporting Servlet 2.5 and JSP 2.1.
 The Glassfish JSP 2.1 bundled in Jetty 6 is way too old.
 
 Retrieved from the file
-	apache-tomcat-6.0.35-deployer.tar.gz
+	apache-tomcat-6.0.36-deployer.tar.gz
 
 minus the following files and directores:
 
diff --git a/apps/jetty/apache-tomcat-deployer/lib/el-api.jar b/apps/jetty/apache-tomcat-deployer/lib/el-api.jar
index 3518f7d76b3b83fc0ae8f660079439d7c68a27e0..7503cdabddd5f10328f7f80fed35d5c091c12009 100644
Binary files a/apps/jetty/apache-tomcat-deployer/lib/el-api.jar and b/apps/jetty/apache-tomcat-deployer/lib/el-api.jar differ
diff --git a/apps/jetty/apache-tomcat-deployer/lib/jasper-el.jar b/apps/jetty/apache-tomcat-deployer/lib/jasper-el.jar
index 23876732eeb471c2d620ab485745086e5b9e1269..c51e275e2f1a1731d3ff71f662671a10397ad2c7 100644
Binary files a/apps/jetty/apache-tomcat-deployer/lib/jasper-el.jar and b/apps/jetty/apache-tomcat-deployer/lib/jasper-el.jar differ
diff --git a/apps/jetty/apache-tomcat-deployer/lib/jasper.jar b/apps/jetty/apache-tomcat-deployer/lib/jasper.jar
index e64284f4f625353e50e1f2956261e1fb515de529..47284070c277533f3cdab32ca0b2fd63800a3a0d 100644
Binary files a/apps/jetty/apache-tomcat-deployer/lib/jasper.jar and b/apps/jetty/apache-tomcat-deployer/lib/jasper.jar differ
diff --git a/apps/jetty/apache-tomcat-deployer/lib/jsp-api.jar b/apps/jetty/apache-tomcat-deployer/lib/jsp-api.jar
index 6ef6574cea37f17321800b1921f18e6ab63d47fd..3030459542bceaa1c7705a015c840b215eceef23 100644
Binary files a/apps/jetty/apache-tomcat-deployer/lib/jsp-api.jar and b/apps/jetty/apache-tomcat-deployer/lib/jsp-api.jar differ
diff --git a/apps/jetty/apache-tomcat-deployer/lib/servlet-api.jar b/apps/jetty/apache-tomcat-deployer/lib/servlet-api.jar
index 1bf50af6e504fff15c5a3ad31b773b12aedee16d..44f490c6af297ea457d6126864327ae1f58cfaa9 100644
Binary files a/apps/jetty/apache-tomcat-deployer/lib/servlet-api.jar and b/apps/jetty/apache-tomcat-deployer/lib/servlet-api.jar differ
diff --git a/apps/jetty/apache-tomcat-deployer/lib/tomcat-juli.jar b/apps/jetty/apache-tomcat-deployer/lib/tomcat-juli.jar
index 07571768cafb3948f98eb0b1d3ac5fa01393875d..2fcbafd9de994260a4ca36dbc356121e370f31ef 100644
Binary files a/apps/jetty/apache-tomcat-deployer/lib/tomcat-juli.jar and b/apps/jetty/apache-tomcat-deployer/lib/tomcat-juli.jar differ