diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
index efae9baad00fc11162f3157fba1f4f2e9d1d0781..edcab93cbc7b6b11362628d108b7c8dcab84db07 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
@@ -74,7 +74,7 @@ public class Storage
   /** The default piece size. */
   private static final int DEFAULT_PIECE_SIZE = 256*1024;
   /** bigger than this will be rejected */
-  public static final int MAX_PIECE_SIZE = 4*1024*1024;
+  public static final int MAX_PIECE_SIZE = 8*1024*1024;
   /** The maximum number of pieces in a torrent. */
   public static final int MAX_PIECES = 10*1024;
   public static final long MAX_TOTAL_SIZE = MAX_PIECE_SIZE * (long) MAX_PIECES;
@@ -601,7 +601,6 @@ public class Storage
    * Doesn't really reopen the file descriptors for a restart.
    * Just does an existence check but no length check or data reverification
    *
-   * @param rootDir ignored
    * @throws IOE on fail
    */
   public void reopen() throws IOException
diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java b/apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java
index 3a9c3088e08c359e37258b43ccf333773e254158..aae95b5150341fc004e084ada910a66f47090aac 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java
@@ -56,6 +56,10 @@ class URIUtil
     }
         
     /** Encode a URI path.
+     *
+     *  Somewhat oddly, this encodes all chars >= 0x80 if buf is null, (strict RFC 2396)
+     *  but only the control, space, and special chars if buf is non-null.
+     *
      * @param path The path the encode
      * @param buf StringBuilder to encode path into (or null)
      * @return The StringBuilder or null if no substitutions required.
@@ -83,7 +87,7 @@ class URIUtil
                         buf=new StringBuilder(path.length()*2);
                         break loop;
                     default:
-                        if (c>127)
+                        if (c >= 0x7f || c <= 0x1f)
                         {
                             bytes = DataHelper.getUTF8(path);
                             buf=new StringBuilder(path.length()*2);
@@ -132,12 +136,12 @@ class URIUtil
                       case ' ':
                           buf.append("%20");
                           continue;
+                      case 0x7f:
+                          buf.append("%7F");
+                          continue;
                       default:
-                          if (c<0)
-                          {
-                              buf.append('%');
+                          if (c <= 0x1f) // includes negative
                               toHex(c,buf);
-                          }
                           else
                               buf.append((char)c);
                           continue;
@@ -180,7 +184,10 @@ class URIUtil
                             buf.append("%20");
                             continue;
                         default:
-                            buf.append(c);
+                            if (c <= 0x1f || (c >= 0x7f && c <= 0x9f) || Character.isSpaceChar(c))
+                                toHex(c,buf);
+                            else
+                              buf.append(c);
                             continue;
                     }
                 }
@@ -195,11 +202,27 @@ class URIUtil
      */
     private static void toHex(byte b, StringBuilder buf)
     {
+            buf.append('%');
             int d=0xf&((0xF0&b)>>4);
             buf.append((char)((d>9?('A'-10):'0')+d));
             d=0xf&b;
             buf.append((char)((d>9?('A'-10):'0')+d));
     }
+    
+    /**
+     *  UTF-8
+     */
+    private static void toHex(char c, StringBuilder buf)
+    {
+            if (c > 0x7f) {
+                byte[] b = DataHelper.getUTF8(Character.toString(c));
+                for (int i = 0; i < b.length; i++) {
+                    toHex(b[i], buf);
+                }
+            } else {
+                toHex((byte) c, buf);
+            }
+    }
 }
 
 
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
index 5b06e271b6e5461cf337c350ae183c93b6a1ae03..84e803ff6b8a75674e37396857db13e59305f090 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
@@ -24,6 +24,8 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicLong;
 
+import javax.net.ssl.SSLServerSocketFactory;
+
 import net.i2p.I2PAppContext;
 import net.i2p.I2PException;
 import net.i2p.client.I2PSession;
@@ -86,6 +88,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
     private static int _executorThreadCount;
     private static final Object _executorLock = new Object();
 
+    public static final String PROP_USE_SSL = I2PTunnelServer.PROP_USE_SSL;
+
     /**
      *  This constructor always starts the tunnel (ignoring the i2cp.delayOpen option).
      *  It is used to add a client to an existing socket manager.
@@ -599,7 +603,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
                 }
                 return;
             }
-            ss = new ServerSocket(localPort, 0, addr);
+            Properties opts = getTunnel().getClientOptions();
+            boolean useSSL = Boolean.parseBoolean(opts.getProperty(PROP_USE_SSL));
+            if (useSSL) {
+                // was already done in web/IndexBean.java when saving the config
+                boolean wasCreated = SSLClientUtil.verifyKeyStore(opts);
+                if (wasCreated) {
+                    // From here, we can't save the config.
+                    // We shouldn't get here, as SSL isn't the default, so it would
+                    // be enabled via the GUI only.
+                    // If it was done manually, the keys will be regenerated at every startup,
+                    // which is bad.
+                    _log.logAlways(Log.WARN, "Created new i2ptunnel SSL keys but can't save the config, disable and enable via i2ptunnel GUI");
+                }
+                SSLServerSocketFactory fact = SSLClientUtil.initializeFactory(opts);
+                ss = fact.createServerSocket(localPort, 0, addr);
+            } else {
+                ss = new ServerSocket(localPort, 0, addr);
+            }
 
             // If a free port was requested, find out what we got
             if (localPort == 0) {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/SSLClientUtil.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/SSLClientUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..01e7c4791922f8cdd300a107eb678b696cf1892f
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/SSLClientUtil.java
@@ -0,0 +1,203 @@
+package net.i2p.i2ptunnel;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.GeneralSecurityException;
+import java.util.Properties;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLContext;
+
+import net.i2p.I2PAppContext;
+import net.i2p.crypto.KeyStoreUtil;
+import net.i2p.util.Log;
+import net.i2p.util.SecureDirectory;
+
+/**
+ * Utilities for I2PTunnel client SSL server sockets.
+ *
+ * @since 0.9.15 adopted from net.i2p.router.client.SSLClientListenerRunner
+ */
+public class SSLClientUtil {
+
+    private static final String PROP_KEYSTORE_PASSWORD = "keystorePassword";
+    private static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
+    private static final String PROP_KEY_PASSWORD = "keyPassword";
+    private static final String PROP_KEY_ALIAS = "keyAlias";
+    private static final String ASCII_KEYFILE_SUFFIX = ".local.crt";
+    private static final String PROP_KS_NAME = "keystoreFile";
+    private static final String KS_DIR = "keystore";
+    private static final String PREFIX = "i2ptunnel-";
+    private static final String KS_SUFFIX = ".ks";
+    private static final String CERT_DIR = "certificates/i2ptunnel";
+
+    /**
+     *  Create a new selfsigned cert and keystore and pubkey cert if they don't exist.
+     *  May take a while.
+     *
+     *  @param opts in/out, updated if rv is true
+     *  @return false if it already exists; if true, caller must save opts
+     *  @throws IOException on creation fail
+     */
+    public static boolean verifyKeyStore(Properties opts) throws IOException {
+        return verifyKeyStore(opts, "");
+    }
+
+    /**
+     *  Create a new selfsigned cert and keystore and pubkey cert if they don't exist.
+     *  May take a while.
+     *
+     *  @param opts in/out, updated if rv is true
+     *  @param optPfx add this prefix when getting/setting options
+     *  @return false if it already exists; if true, caller must save opts
+     *  @throws IOException on creation fail
+     */
+    public static boolean verifyKeyStore(Properties opts, String optPfx) throws IOException {
+        String name = opts.getProperty(optPfx + PROP_KEY_ALIAS);
+        if (name == null) {
+            name = KeyStoreUtil.randomString();
+            opts.setProperty(optPfx + PROP_KEY_ALIAS, name);
+        }
+        String ksname = opts.getProperty(optPfx + PROP_KS_NAME);
+        if (ksname == null) {
+            ksname = PREFIX + name + KS_SUFFIX;
+            opts.setProperty(optPfx + PROP_KS_NAME, ksname);
+        }
+        File ks = new File(ksname);
+        if (!ks.isAbsolute()) {
+            ks = new File(I2PAppContext.getGlobalContext().getConfigDir(), KS_DIR);
+            ks = new File(ks, ksname);
+        }
+        if (ks.exists())
+            return false;
+        File dir = ks.getParentFile();
+        if (!dir.exists()) {
+            File sdir = new SecureDirectory(dir.getAbsolutePath());
+            if (!sdir.mkdirs())
+                throw new IOException("Unable to create keystore " + ks);
+        }
+        boolean rv = createKeyStore(ks, name, opts, optPfx);
+        if (!rv)
+            throw new IOException("Unable to create keystore " + ks);
+
+        // Now read it back out of the new keystore and save it in ascii form
+        // where the clients can get to it.
+        // Failure of this part is not fatal.
+        exportCert(ks, name, opts, optPfx);
+        return true;
+    }
+
+
+    /**
+     *  Call out to keytool to create a new keystore with a keypair in it.
+     *
+     *  @param name used in CNAME
+     *  @param opts in/out, updated if rv is true, must contain PROP_KEY_ALIAS
+     *  @param optPfx add this prefix when getting/setting options
+     *  @return success, if true, opts will have password properties added to be saved
+     */
+    private static boolean createKeyStore(File ks, String name, Properties opts, String optPfx) {
+        // make a random 48 character password (30 * 8 / 5)
+        String keyPassword = KeyStoreUtil.randomString();
+        // and one for the cname
+        String cname = name + ".i2ptunnel.i2p.net";
+
+        String keyName = opts.getProperty(optPfx + PROP_KEY_ALIAS);
+        boolean success = KeyStoreUtil.createKeys(ks, keyName, cname, "I2PTUNNEL", keyPassword);
+        if (success) {
+            success = ks.exists();
+            if (success) {
+                opts.setProperty(optPfx + PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
+                opts.setProperty(optPfx + PROP_KEY_PASSWORD, keyPassword);
+            }
+        }
+        if (success) {
+            logAlways("Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath() + "\n" +
+                           "The certificate name was generated randomly, and is not associated with your " +
+                           "IP address, host name, router identity, or destination keys.");
+        } else {
+            error("Failed to create I2PTunnel SSL keystore.\n" +
+                       "If you create the keystore manually, you must add " + optPfx + PROP_KEYSTORE_PASSWORD + " and " + optPfx + PROP_KEY_PASSWORD +
+                       " to " + (new File(I2PAppContext.getGlobalContext().getConfigDir(), "i2ptunnel.config")).getAbsolutePath());
+        }
+        return success;
+    }
+
+    /** 
+     *  Pull the cert back OUT of the keystore and save it as ascii
+     *  so the clients can get to it.
+     *
+     *  @param name used to generate output file name
+     *  @param opts must contain optPfx + PROP_KEY_ALIAS
+     *  @param optPfx add this prefix when getting options
+     */
+    private static void exportCert(File ks, String name, Properties opts, String optPfx) {
+        File sdir = new SecureDirectory(I2PAppContext.getGlobalContext().getConfigDir(), CERT_DIR);
+        if (sdir.exists() || sdir.mkdirs()) {
+            String keyAlias = opts.getProperty(optPfx + PROP_KEY_ALIAS);
+            String ksPass = opts.getProperty(optPfx + PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
+            File out = new File(sdir, PREFIX + name + ASCII_KEYFILE_SUFFIX);
+            boolean success = KeyStoreUtil.exportCert(ks, ksPass, keyAlias, out);
+            if (!success)
+                error("Error getting SSL cert to save as ASCII");
+        } else {
+            error("Error saving ASCII SSL keys");
+        }
+    }
+
+    /** 
+     *  Sets up the SSLContext and sets the socket factory.
+     *  No option prefix allowed.
+     *
+     * @throws IOException; GeneralSecurityExceptions are wrapped in IOE for convenience
+     * @return factory, throws on all errors
+     */
+    public static SSLServerSocketFactory initializeFactory(Properties opts) throws IOException {
+        String ksPass = opts.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
+        String keyPass = opts.getProperty(PROP_KEY_PASSWORD);
+        if (keyPass == null) {
+            throw new IOException("No key password, set " + PROP_KEY_PASSWORD + " in " +
+                       (new File(I2PAppContext.getGlobalContext().getConfigDir(), "i2ptunnel.config")).getAbsolutePath());
+        }
+        String ksname = opts.getProperty(PROP_KS_NAME);
+        if (ksname == null) {
+            throw new IOException("No keystore, set " + PROP_KS_NAME + " in " +
+                       (new File(I2PAppContext.getGlobalContext().getConfigDir(), "i2ptunnel.config")).getAbsolutePath());
+        }
+        File ks = new File(ksname);
+        if (!ks.isAbsolute()) {
+            ks = new File(I2PAppContext.getGlobalContext().getConfigDir(), KS_DIR);
+            ks = new File(ks, ksname);
+        }
+
+        InputStream fis = null;
+        try {
+            SSLContext sslc = SSLContext.getInstance("TLS");
+            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+            fis = new FileInputStream(ks);
+            keyStore.load(fis, ksPass.toCharArray());
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            kmf.init(keyStore, keyPass.toCharArray());
+            sslc.init(kmf.getKeyManagers(), null, I2PAppContext.getGlobalContext().random());
+            return sslc.getServerSocketFactory();
+        } catch (GeneralSecurityException gse) {
+            IOException ioe = new IOException("keystore error");
+            ioe.initCause(gse);
+            throw ioe;
+        } finally {
+            if (fis != null) try { fis.close(); } catch (IOException ioe) {}
+        }
+    }
+
+    private static void error(String s) {
+        I2PAppContext.getGlobalContext().logManager().getLog(SSLClientUtil.class).error(s);
+    }
+
+    private static void logAlways(String s) {
+        I2PAppContext.getGlobalContext().logManager().getLog(SSLClientUtil.class).logAlways(Log.INFO, s);
+    }
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java
index dd7f5642d460e27fa04420d135659a3cb8fdab19..b46765287675c4bd2521d46ad511d8472f6b35dc 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java
@@ -1,6 +1,7 @@
 package net.i2p.i2ptunnel.streamr;
 
 import net.i2p.i2ptunnel.udp.*;
+import net.i2p.util.I2PAppThread;
 
 /**
  *
@@ -9,7 +10,7 @@ import net.i2p.i2ptunnel.udp.*;
 public class Pinger implements Source, Runnable {
 
     public Pinger() {
-        this.thread = new Thread(this);
+        this.thread = new I2PAppThread(this);
     }
 
     public void setSink(Sink sink) {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java
index 68a9d90a27f353f3509723b73074f9b530eb7912..2f59418a526c90239ff204244de4823b13f584c3 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSource.java
@@ -6,6 +6,7 @@ import java.util.concurrent.BlockingQueue;
 import net.i2p.client.I2PSession;
 import net.i2p.client.I2PSessionListener;
 import net.i2p.client.datagram.I2PDatagramDissector;
+import net.i2p.util.I2PAppThread;
 
 /**
  *
@@ -33,7 +34,7 @@ public class I2PSource implements Source, Runnable {
         this.sess.setSessionListener(new Listener());
         
         // create thread
-        this.thread = new Thread(this);
+        this.thread = new I2PAppThread(this);
     }
     
     public void setSink(Sink sink) {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java
index 6f1a20e5ffd813c63c6bcafee78caa08f8ed1984..23d4b8048bc49fc6b6bf8749a513b0fe2ebefe2b 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/UDPSource.java
@@ -3,6 +3,8 @@ package net.i2p.i2ptunnel.udp;
 import java.net.DatagramSocket;
 import java.net.DatagramPacket;
 
+import net.i2p.util.I2PAppThread;
+
 /**
  *
  * @author welterde
@@ -19,13 +21,13 @@ public class UDPSource implements Source, Runnable {
         }
         
         // create thread
-        this.thread = new Thread(this);
+        this.thread = new I2PAppThread(this);
     }
 
     /** use socket from UDPSink */
     public UDPSource(DatagramSocket sock) {
         this.sock = sock;
-        this.thread = new Thread(this);
+        this.thread = new I2PAppThread(this);
     }
     
     public void setSink(Sink sink) {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
index 06558d78e572e23c69bbe9694eab0adf54e00cfe..95c5bea682743f786e337c0a091ccbc0019acb74 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
@@ -26,15 +26,18 @@ import net.i2p.app.ClientAppManager;
 import net.i2p.app.Outproxy;
 import net.i2p.client.I2PClient;
 import net.i2p.data.Certificate;
+import net.i2p.data.DataHelper;
 import net.i2p.data.Destination;
 import net.i2p.data.PrivateKeyFile;
 import net.i2p.data.SessionKey;
+import net.i2p.i2ptunnel.I2PTunnelClientBase;
 import net.i2p.i2ptunnel.I2PTunnelConnectClient;
 import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
 import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
 import net.i2p.i2ptunnel.I2PTunnelHTTPServer;
 import net.i2p.i2ptunnel.I2PTunnelIRCClient;
 import net.i2p.i2ptunnel.I2PTunnelServer;
+import net.i2p.i2ptunnel.SSLClientUtil;
 import net.i2p.i2ptunnel.TunnelController;
 import net.i2p.i2ptunnel.TunnelControllerGroup;
 import net.i2p.util.Addresses;
@@ -255,7 +258,7 @@ public class IndexBean {
         // give the messages a chance to make it to the window
         try { Thread.sleep(1000); } catch (InterruptedException ie) {}
         // and give them something to look at in any case
-        return _("Starting tunnel") + ' ' + getTunnelName(_tunnel) + "&hellip;";
+        return _("Starting tunnel") + ' ' + getTunnelName(_tunnel) + "...";
     }
     
     private String stop() {
@@ -268,7 +271,7 @@ public class IndexBean {
         // give the messages a chance to make it to the window
         try { Thread.sleep(1000); } catch (InterruptedException ie) {}
         // and give them something to look at in any case
-        return _("Stopping tunnel") + ' ' + getTunnelName(_tunnel) + "&hellip;";
+        return _("Stopping tunnel") + ' ' + getTunnelName(_tunnel) + "...";
     }
     
     private String saveChanges() {
@@ -276,7 +279,27 @@ public class IndexBean {
         TunnelController cur = getController(_tunnel);
         
         Properties config = getConfig();
-        
+
+        String ksMsg = null;
+        String type = config.getProperty(TunnelController.PROP_TYPE);
+        if (TunnelController.TYPE_STD_CLIENT.equals(type) || TunnelController.TYPE_IRC_CLIENT.equals(type)) {
+            //
+            // If we switch to SSL, create the keystore here, so we can store the new properties.
+            // Down in I2PTunnelClientBase it's very hard to save the config.
+            //
+            if (Boolean.parseBoolean(config.getProperty(OPT + I2PTunnelClientBase.PROP_USE_SSL))) {
+                try {
+                    boolean created = SSLClientUtil.verifyKeyStore(config, OPT);
+                    if (created) {
+                        // config now contains new keystore props
+                        ksMsg = "Created new self-signed certificate for tunnel " + getTunnelName(_tunnel);
+                    }        
+                } catch (IOException ioe) {       
+                    ksMsg = "Failed to create new self-signed certificate for tunnel " +
+                            getTunnelName(_tunnel) + ", check logs: " + ioe;
+                }        
+            }        
+        }        
         if (cur == null) {
             // creating new
             cur = new TunnelController(config, "", true);
@@ -327,6 +350,8 @@ public class IndexBean {
         }
         
         List<String> msgs = doSave();
+        if (ksMsg != null)
+            msgs.add(ksMsg);
         return getMessages(msgs);
     }
 
@@ -397,6 +422,7 @@ public class IndexBean {
      * Executes any action requested (start/stop/etc) and dump out the 
      * messages.
      *
+     * @return HTML escaped
      */
     public String getMessages() {
         if (_group == null)
@@ -405,13 +431,14 @@ public class IndexBean {
         StringBuilder buf = new StringBuilder(512);
         if (_action != null) {
             try {
-                buf.append(processAction()).append("\n");
+                buf.append(processAction()).append('\n');
             } catch (Exception e) {
                 _log.log(Log.CRIT, "Error processing " + _action, e);
+                buf.append("Error: ").append(e.toString()).append('\n');
             }
         }
         getMessages(_group.clearAllMessages(), buf);
-        return buf.toString();
+        return DataHelper.escapeHTML(buf.toString());
     }
     
     ////
@@ -1293,7 +1320,8 @@ public class IndexBean {
         I2PTunnelIRCClient.PROP_DCC
         };
     private static final String _booleanClientOpts[] = {
-        "i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen"
+        "i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen",
+        I2PTunnelClientBase.PROP_USE_SSL,
         };
     private static final String _booleanProxyOpts[] = {
         I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH,
diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp
index cbfc8b713033e156f1c9e6a053b474e777685e9f..05c091b197814638d698df6b3f7abdb14b73c31c 100644
--- a/apps/i2ptunnel/jsp/editClient.jsp
+++ b/apps/i2ptunnel/jsp/editClient.jsp
@@ -140,6 +140,14 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
                 </select>                
          <% } /* streamrclient */ %>
             </div> 
+         <% if ("client".equals(tunnelType) || "ircclient".equals(tunnelType)) {
+          %><div id="portField" class="rowItem">
+                <label>
+                    <%=intl._("Use SSL?")%>
+                </label>
+                <input value="1" type="checkbox" id="startOnLoad" name="useSSL" title="Clients use SSL to connect" <%=(editBean.isSSLEnabled(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />                
+            </div>
+         <% } /* tunnel types */ %>
 
             <div class="subdivider">
                 <hr />
diff --git a/apps/i2ptunnel/jsp/index.jsp b/apps/i2ptunnel/jsp/index.jsp
index f1117a272a64938d1ce5d711ba137fb93986e956..0b63e98f31257f070bc8c43817b7b86811c7aa08 100644
--- a/apps/i2ptunnel/jsp/index.jsp
+++ b/apps/i2ptunnel/jsp/index.jsp
@@ -244,6 +244,8 @@
          <%
                String cPort= indexBean.getClientPort2(curClient);
                out.write(cPort);
+               if (indexBean.isSSLEnabled(curClient))
+                   out.write(" SSL");
           %>
             </span>
         </div>
diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java
index fbeb0e58676bbe63cfe527344f13dede9dae5da1..e943ff6df154c5135affe57a786f8b2896921a8e 100644
--- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java
+++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java
@@ -25,7 +25,15 @@ import net.i2p.util.Log;
  */
 public class I2PSocketManagerFactory {
 
+    /**
+     *  Ignored since 0.9.12, cannot be changed via properties.
+     *  @deprecated
+     */
     public static final String PROP_MANAGER = "i2p.streaming.manager";
+
+    /**
+     *  The one and only manager.
+     */
     public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.impl.I2PSocketManagerFull";
 
     /**
diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java
index a962230b97125e4bbd03cf14c1b35b11f73acaa4..f4cb8ead123f62afc84d4806c825e4cbb926981c 100644
--- a/core/java/src/net/i2p/data/DataHelper.java
+++ b/core/java/src/net/i2p/data/DataHelper.java
@@ -1629,8 +1629,8 @@ public class DataHelper {
         return rv;
     }
 
-    private static final String escapeChars[] = {"&", "\"", "<", ">", "\"", "'"};
-    private static final String escapeCodes[] = {"&amp;", "&quot;", "&lt;", "&gt;", "&quot;", "&apos;"};
+    private static final String escapeChars[] = {"&", "\"", "<", ">", "'"};
+    private static final String escapeCodes[] = {"&amp;", "&quot;", "&lt;", "&gt;", "&apos;"};
 
     /**
      * Escape a string for inclusion in HTML
diff --git a/core/java/src/net/i2p/util/Clock.java b/core/java/src/net/i2p/util/Clock.java
index 294d3ffa6d51ff56974f3f0e54d5842fc3be3a7b..25600259c6b786f60f8ba33932b156b1c4830a8d 100644
--- a/core/java/src/net/i2p/util/Clock.java
+++ b/core/java/src/net/i2p/util/Clock.java
@@ -97,7 +97,7 @@ public class Clock implements Timestamper.UpdateListener {
                 getLog().info("Updating clock offset to " + offsetMs + "ms from " + _offset + "ms");
             
             if (!_statCreated) {
-                _context.statManager().createRequiredRateStat("clock.skew", "Clock step adjustment (ms)", "Clock", new long[] { 10*60*1000, 3*60*60*1000, 24*60*60*60 });
+                _context.statManager().createRequiredRateStat("clock.skew", "Clock step adjustment (ms)", "Clock", new long[] { 10*60*1000, 3*60*60*1000, 24*60*60*1000 });
                 _statCreated = true;
             }
             _context.statManager().addRateData("clock.skew", delta, 0);
diff --git a/history.txt b/history.txt
index 68e84e941144cb99920831d73a90434f4932c549..3b9c6fdcaf74666ca6dc21a85e40ec2bbe3397b2 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,9 @@
+2014-08-21 zzz
+ * i2psnark:
+   - Escape control chars in encodePath()
+   - Increase max piece size to 8 MB (ticket #1347)
+ * i2ptunnel: Add local SSL support for std. and IRC client tunnels (ticket #1107)
+
 2014-08-19 zzz
  * i2psnark:
    - Don't filter create torrent form, and
diff --git a/router/java/src/net/i2p/router/RouterClock.java b/router/java/src/net/i2p/router/RouterClock.java
index f3778dc56ebc0467e41a41b86f6e052c68509517..051d282cdacc5414caf416fe75e812aac411637f 100644
--- a/router/java/src/net/i2p/router/RouterClock.java
+++ b/router/java/src/net/i2p/router/RouterClock.java
@@ -161,7 +161,7 @@ public class RouterClock extends Clock {
                 getLog().info("Updating target clock offset to " + offsetMs + "ms from " + _offset + "ms, Stratum " + stratum);
             
             if (!_statCreated) {
-                _context.statManager().createRequiredRateStat("clock.skew", "Clock step adjustment (ms)", "Clock", new long[] { 10*60*1000, 3*60*60*1000, 24*60*60*60 });
+                _context.statManager().createRequiredRateStat("clock.skew", "Clock step adjustment (ms)", "Clock", new long[] { 10*60*1000, 3*60*60*1000, 24*60*60*1000 });
                 _statCreated = true;
             }
             _context.statManager().addRateData("clock.skew", delta);
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index fecba78d6973d87e9930d631de787e05fec2a510..a6204817e3d9e640632604f1432a96320e1992ce 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
     /** deprecated */
     public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 7;
+    public final static long BUILD = 8;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java b/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java
index 2e49098742a7e5d569870b3c546eb574643e0766..d363d7a3f540cda01e194c7d836aeac93c6d8c5b 100644
--- a/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java
+++ b/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java
@@ -48,6 +48,8 @@ class BatchedPreprocessor extends TrivialPreprocessor {
     private long _pendingSince;
     private final String _name;
     
+    private static final boolean DEBUG = false;
+
     public BatchedPreprocessor(RouterContext ctx, String name) {
         super(ctx);
         _name = name;
@@ -175,7 +177,8 @@ class BatchedPreprocessor extends TrivialPreprocessor {
                                                                + " len=" + cur.getData().length + " alloc=" + allocated);
                         if (timingBuf != null)
                             timingBuf.append(" sent " + cur);
-                        notePreprocessing(cur.getMessageId(), cur.getFragmentNumber(), cur.getData().length, cur.getMessageIds(), "flushed allocated");
+                        if (DEBUG)
+                            notePreprocessing(cur.getMessageId(), cur.getFragmentNumber(), cur.getData().length, cur.getMessageIds(), "flushed allocated");
                         _context.statManager().addRateData("tunnel.batchFragmentation", cur.getFragmentNumber() + 1);
                         _context.statManager().addRateData("tunnel.writeDelay", cur.getLifetime(), cur.getData().length);
                     }
@@ -184,7 +187,8 @@ class BatchedPreprocessor extends TrivialPreprocessor {
                         PendingGatewayMessage cur = pending.remove(0);
                         if (timingBuf != null)
                             timingBuf.append(" sent perfect fit " + cur).append(".");
-                        notePreprocessing(cur.getMessageId(), cur.getFragmentNumber(), msg.getData().length, msg.getMessageIds(), "flushed tail, remaining: " + pending);
+                        if (DEBUG)
+                            notePreprocessing(cur.getMessageId(), cur.getFragmentNumber(), msg.getData().length, msg.getMessageIds(), "flushed tail, remaining: " + pending);
                         _context.statManager().addRateData("tunnel.batchFragmentation", cur.getFragmentNumber() + 1);
                         _context.statManager().addRateData("tunnel.writeDelay", cur.getLifetime(), cur.getData().length);
                     }
@@ -234,7 +238,8 @@ class BatchedPreprocessor extends TrivialPreprocessor {
                         if (cur.getOffset() < cur.getData().length)
                             break;
                         pending.remove(0);
-                        notePreprocessing(cur.getMessageId(), cur.getFragmentNumber(), cur.getData().length, cur.getMessageIds(), "flushed remaining");
+                        if (DEBUG)
+                            notePreprocessing(cur.getMessageId(), cur.getFragmentNumber(), cur.getData().length, cur.getMessageIds(), "flushed remaining");
                         _context.statManager().addRateData("tunnel.batchFragmentation", cur.getFragmentNumber() + 1);
                         _context.statManager().addRateData("tunnel.writeDelay", cur.getLifetime(), cur.getData().length);
                     }
@@ -383,9 +388,12 @@ class BatchedPreprocessor extends TrivialPreprocessor {
         }
 
         long msgId = sender.sendPreprocessed(preprocessed, rec);
-        for (int i = 0; i < pending.size(); i++) {
-            PendingGatewayMessage cur = pending.get(i);
-            cur.addMessageId(msgId);
+        if (DEBUG) {
+            // creates a list in PGM
+            for (int i = 0; i < pending.size(); i++) {
+                PendingGatewayMessage cur = pending.get(i);
+                cur.addMessageId(msgId);
+            }
         }
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Sent " + startAt + ":" + sendThrough + " out of " + pending + " in message " + msgId);
diff --git a/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java b/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java
index 33f446893f99a5b521dc5486bee641c5cf7ebc69..3d62ded4bd777d320aafc9e2eb876d1829b5ebb8 100644
--- a/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java
+++ b/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java
@@ -11,8 +11,8 @@ import net.i2p.router.RouterContext;
  *
  */
 class BatchedRouterPreprocessor extends BatchedPreprocessor {
-    private TunnelCreatorConfig _config;
-    protected HopConfig _hopConfig;
+    private final TunnelCreatorConfig _config;
+    protected final HopConfig _hopConfig;
     private final long _sendDelay;
     
     /** 
@@ -34,12 +34,14 @@ class BatchedRouterPreprocessor extends BatchedPreprocessor {
     public BatchedRouterPreprocessor(RouterContext ctx, TunnelCreatorConfig cfg) {
         super(ctx, getName(cfg));
         _config = cfg;
+        _hopConfig = null;
         _sendDelay = initialSendDelay();
     }
 
     /** for IBGWs */
     public BatchedRouterPreprocessor(RouterContext ctx, HopConfig cfg) {
         super(ctx, getName(cfg));
+        _config = null;
         _hopConfig = cfg;
         _sendDelay = initialSendDelay();
     }
diff --git a/router/java/src/net/i2p/router/tunnel/OutboundSender.java b/router/java/src/net/i2p/router/tunnel/OutboundSender.java
index 961c29031f610d2bfe0c9f24fc0fa3d4fb9194ca..6125bf48eda35bde3cb98b8ffe74e172522a4c03 100644
--- a/router/java/src/net/i2p/router/tunnel/OutboundSender.java
+++ b/router/java/src/net/i2p/router/tunnel/OutboundSender.java
@@ -10,30 +10,30 @@ import net.i2p.util.Log;
  *
  */
 class OutboundSender implements TunnelGateway.Sender {
-    private final I2PAppContext _context;
-    private final Log _log;
+    //private final I2PAppContext _context;
+    //private final Log _log;
     private final TunnelCreatorConfig _config;
     private final OutboundGatewayProcessor _processor;
     
     //static final boolean USE_ENCRYPTION = HopProcessor.USE_ENCRYPTION;
     
     public OutboundSender(I2PAppContext ctx, TunnelCreatorConfig config) {
-        _context = ctx;
-        _log = ctx.logManager().getLog(OutboundSender.class);
+        //_context = ctx;
+        //_log = ctx.logManager().getLog(OutboundSender.class);
         _config = config;
-        _processor = new OutboundGatewayProcessor(_context, config);
+        _processor = new OutboundGatewayProcessor(ctx, config);
     }
     
     public long sendPreprocessed(byte[] preprocessed, TunnelGateway.Receiver receiver) {
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("preprocessed data going out " + _config + ": " + Base64.encode(preprocessed));
+        //if (_log.shouldLog(Log.DEBUG))
+        //    _log.debug("preprocessed data going out " + _config + ": " + Base64.encode(preprocessed));
         //if (USE_ENCRYPTION)
             _processor.process(preprocessed, 0, preprocessed.length);
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("after wrapping up the preprocessed data on " + _config);
+        //if (_log.shouldLog(Log.DEBUG))
+        //    _log.debug("after wrapping up the preprocessed data on " + _config);
         long rv = receiver.receiveEncrypted(preprocessed);
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("after receiving on " + _config + ": receiver = " + receiver);
+        //if (_log.shouldLog(Log.DEBUG))
+        //    _log.debug("after receiving on " + _config + ": receiver = " + receiver);
         return rv;
     }
 }
diff --git a/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java b/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java
index 0d8475ece717d50a1d1b27540ecf39da39e6e7ac..19e84dc04d3cf7390ae1f4322de9cba0b23a1e0f 100644
--- a/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java
+++ b/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java
@@ -17,6 +17,9 @@ import net.i2p.util.SimpleByteCache;
  * optimal throughput.
  *
  * See FragmentHandler Javadoc for tunnel message fragment format
+ *
+ * Not instantiated directly except in unit tests; see BatchedPreprocessor
+ *
  */
 class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
     protected final RouterContext _context;
@@ -48,7 +51,7 @@ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
      * NOTE: Unused here, see BatchedPreprocessor override, super is not called.
      */
     public boolean preprocessQueue(List<PendingGatewayMessage> pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
-        throw new IllegalArgumentException("unused, right?");
+        throw new UnsupportedOperationException("unused, right?");
     }
     
     protected void notePreprocessing(long messageId, int numFragments, int totalLength, List<Long> messageIds, String msg) {}
@@ -265,7 +268,7 @@ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
      *  Does NOT include 4 for the message ID if the message will be fragmented;
      *  call getInstructionAugmentationSize() for that.
      */
-    protected int getInstructionsSize(PendingGatewayMessage msg) {
+    protected static int getInstructionsSize(PendingGatewayMessage msg) {
         if (msg.getFragmentNumber() > 0) 
             return 7;
         // control byte
@@ -283,7 +286,7 @@ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
     }
     
     /** @return 0 or 4 */
-    protected int getInstructionAugmentationSize(PendingGatewayMessage msg, int offset, int instructionsSize) {
+    protected static int getInstructionAugmentationSize(PendingGatewayMessage msg, int offset, int instructionsSize) {
         int payloadLength = msg.getData().length - msg.getOffset();
         if (offset + payloadLength + instructionsSize + IV_SIZE + 1 + 4 > PREPROCESSED_SIZE) {
             // requires fragmentation, so include the messageId
diff --git a/router/java/src/net/i2p/router/tunnel/TunnelGateway.java b/router/java/src/net/i2p/router/tunnel/TunnelGateway.java
index 242b3dc036747b58d7d3bfbd533f33b105f3d07c..eb1632224d6e316d9b1dc6b95ed3edc1d0850166 100644
--- a/router/java/src/net/i2p/router/tunnel/TunnelGateway.java
+++ b/router/java/src/net/i2p/router/tunnel/TunnelGateway.java
@@ -33,7 +33,7 @@ import net.i2p.util.SimpleTimer2;
  *
  * Unused directly - see PumpedTunnelGateway, ThrottledPumpedTunnelGateway, and TunnelGatewayZeroHop overrides.
  */
-class TunnelGateway {
+abstract class TunnelGateway {
     protected final RouterContext _context;
     protected final Log _log;
     protected final List<PendingGatewayMessage> _queue;
diff --git a/router/java/test/junit/net/i2p/router/tunnel/FragmentTest.java b/router/java/test/junit/net/i2p/router/tunnel/FragmentTest.java
index 2c0f5e1b401f401e7a69476c9b34fd34d06e26be..ac6b961072cae3fde4f4cd725213f7906fa24d91 100644
--- a/router/java/test/junit/net/i2p/router/tunnel/FragmentTest.java
+++ b/router/java/test/junit/net/i2p/router/tunnel/FragmentTest.java
@@ -66,8 +66,8 @@ public class FragmentTest {
 
         try {
             pre.preprocessQueue(messages, new SenderImpl(), receiver);
-            fail("should have thrown IAE");
-        } catch (IllegalArgumentException expected){}
+            fail("should have thrown UOE");
+        } catch (UnsupportedOperationException expected){}
     }
     
     /**
@@ -89,8 +89,8 @@ public class FragmentTest {
             
         try {
             pre.preprocessQueue(messages, new SenderImpl(), receiver);
-            fail("should have thrown IAE");
-        } catch (IllegalArgumentException expected){}
+            fail("should have thrown UOE");
+        } catch (UnsupportedOperationException expected){}
     }
     
     /**