diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java
index cf4543d8f7ec48f4ee4d0fab97bd76c1980d51fb..2bfd8ccf12cfbdda1be685df762a2cc48be790b6 100644
--- a/core/java/src/net/i2p/util/EepGet.java
+++ b/core/java/src/net/i2p/util/EepGet.java
@@ -44,21 +44,21 @@ public class EepGet {
     protected String _actualURL;
     private String _postData;
     private boolean _allowCaching;
-    protected final List _listeners;
+    protected final List<StatusListener> _listeners;
     
-    private boolean _keepFetching;
-    private Socket _proxy;
+    protected boolean _keepFetching;
+    protected Socket _proxy;
     protected OutputStream _proxyOut;
     protected InputStream _proxyIn;
     protected OutputStream _out;
     protected long _alreadyTransferred;
-    private long _bytesTransferred;
+    protected long _bytesTransferred;
     protected long _bytesRemaining;
     protected int _currentAttempt;
     private String _etag;
     private String _lastModified;
-    private boolean _encodingChunked;
-    private boolean _notModified;
+    protected boolean _encodingChunked;
+    protected boolean _notModified;
     private String _contentType;
     protected boolean _transferFailed;
     protected boolean _headersRead;
@@ -441,7 +441,7 @@ public class EepGet {
             timeout.setTotalTimeoutPeriod(_fetchEndTime);
             try {
                 for (int i = 0; i < _listeners.size(); i++) 
-                    ((StatusListener)_listeners.get(i)).attempting(_url);
+                    _listeners.get(i).attempting(_url);
                 sendRequest(timeout);
                 timeout.resetTimer();
                 doFetch(timeout);
@@ -452,7 +452,7 @@ public class EepGet {
             } catch (IOException ioe) {
                 timeout.cancel();
                 for (int i = 0; i < _listeners.size(); i++) 
-                    ((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe);
+                    _listeners.get(i).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe);
                 if (_log.shouldLog(Log.WARN))
                     _log.warn("ERR: doFetch failed " +  ioe);
             } finally {
@@ -480,13 +480,13 @@ public class EepGet {
         }
 
         for (int i = 0; i < _listeners.size(); i++) 
-            ((StatusListener)_listeners.get(i)).transferFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt);
+            _listeners.get(i).transferFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt);
         if (_log.shouldLog(Log.WARN))
             _log.warn("All attempts failed for " + _url);
         return false;
     }
 
-    /** return true if the URL was completely retrieved */
+    /** single fetch */
     protected void doFetch(SocketTimeout timeout) throws IOException {
         _headersRead = false;
         _aborted = false;
@@ -586,7 +586,7 @@ public class EepGet {
                 _bytesRemaining -= read;
             if (read > 0) {
                 for (int i = 0; i < _listeners.size(); i++) 
-                    ((StatusListener)_listeners.get(i)).bytesTransferred(
+                    _listeners.get(i).bytesTransferred(
                             _alreadyTransferred, 
                             read, 
                             _bytesTransferred, 
@@ -615,12 +615,12 @@ public class EepGet {
         if (_transferFailed) {
             // 404, etc - transferFailed is called after all attempts fail, by fetch() above
             for (int i = 0; i < _listeners.size(); i++) 
-                ((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, new Exception("Attempt failed"));
+                _listeners.get(i).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, new Exception("Attempt failed"));
         } else if ((_minSize > 0) && (_alreadyTransferred < _minSize)) {
             throw new IOException("Bytes transferred " + _alreadyTransferred + " violates minimum of " + _minSize + " bytes");
         } else if ( (_bytesRemaining == -1) || (remaining == 0) ) {
             for (int i = 0; i < _listeners.size(); i++) 
-                ((StatusListener)_listeners.get(i)).transferComplete(
+                _listeners.get(i).transferComplete(
                         _alreadyTransferred, 
                         _bytesTransferred, 
                         _encodingChunked?-1:_bytesRemaining, 
@@ -744,7 +744,7 @@ public class EepGet {
         }
     }
     
-    private long readChunkLength() throws IOException {
+    protected long readChunkLength() throws IOException {
         StringBuilder buf = new StringBuilder(8);
         int nl = 0;
         while (true) {
@@ -808,7 +808,7 @@ public class EepGet {
 
     private void handle(String key, String val) {
         for (int i = 0; i < _listeners.size(); i++) 
-            ((StatusListener)_listeners.get(i)).headerReceived(_url, _currentAttempt, key.trim(), val.trim());
+            _listeners.get(i).headerReceived(_url, _currentAttempt, key.trim(), val.trim());
         
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Header line: [" + key + "] = [" + val + "]");
diff --git a/core/java/src/net/i2p/util/SSLEepGet.java b/core/java/src/net/i2p/util/SSLEepGet.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4b58999087e36ba9352902fa95cfa0b4e027f68
--- /dev/null
+++ b/core/java/src/net/i2p/util/SSLEepGet.java
@@ -0,0 +1,317 @@
+package net.i2p.util;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import javax.net.ssl.SSLSocketFactory;
+
+// all part of the CA experiment below
+//import java.io.FileInputStream;
+//import java.io.InputStream;
+//import java.util.Enumeration;
+//import java.security.KeyStore;
+//import java.security.GeneralSecurityException;
+//import java.security.cert.CertificateExpiredException;
+//import java.security.cert.CertificateNotYetValidException;
+//import java.security.cert.CertificateFactory;
+//import java.security.cert.X509Certificate;
+//import javax.net.ssl.KeyManagerFactory;
+//import javax.net.ssl.SSLContext;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
+
+/**
+ * HTTPS only, non-proxied only, no retries, no min and max size options, no timeout option
+ * Fails on 301 or 302 (doesn't follow redirect)
+ * Fails on self-signed certs (must have a valid cert chain)
+ *
+ * @author zzz
+ * @since 0.7.10
+ */
+public class SSLEepGet extends EepGet {
+    //private static SSLContext _sslContext;
+
+    public SSLEepGet(I2PAppContext ctx, OutputStream outputStream, String url) {
+        // we're using this constructor:
+        // public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize, String outputFile, OutputStream outputStream, String url, boolean allowCaching, String etag, String postData) {
+        super(ctx, false, null, -1, 0, -1, -1, null, outputStream, url, true, null, null);
+    }
+   
+    /**
+     * SSLEepGet url
+     * no command line options supported
+     */ 
+    public static void main(String args[]) {
+        String url = null;
+        try {
+            for (int i = 0; i < args.length; i++) {
+                if (args[i].startsWith("-")) {
+                    usage();
+                    return;
+                } else {
+                    url = args[i];
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            usage();
+            return;
+        }
+        
+        if (url == null) {
+            usage();
+            return;
+        }
+
+        String saveAs = suggestName(url);
+        OutputStream out;
+        try {
+            // resume from a previous eepget won't work right doing it this way
+            out = new FileOutputStream(saveAs);
+        } catch (IOException ioe) {
+            System.err.println("Failed to create output file " + saveAs);
+            return;
+        }
+
+        /******
+         *  This is all an experiment to add a CA cert loaded from a file so we can use
+         *  selfsigned certs on our servers.
+         *  But it's failing.
+         *  Run as java -Djava.security.debug=certpath -Djavax.net.debug=trustmanager -cp $I2P/lib/i2p.jar net.i2p.util.SSLEepGet "$@"
+         *  to see the problems. It isn't including the added cert in the Trust Anchor list.
+         ******/
+
+        /******
+        String foo = System.getProperty("javax.net.ssl.keyStore");
+        if (foo == null) {
+            File cacerts = new File(System.getProperty("java.home"), "lib/security/cacerts");
+            foo = cacerts.getAbsolutePath();
+        }
+        System.err.println("Location is: " + foo);
+        try {
+            KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+            try {
+                InputStream fis = new FileInputStream(foo);
+                ks.load(fis, "changeit".toCharArray());
+                fis.close();
+            } catch (GeneralSecurityException gse) {
+                System.err.println("KS error, no default keys: " + gse);
+                ks.load(null, "changeit".toCharArray());
+            } catch (IOException ioe) {
+                System.err.println("IO error, no default keys: " + ioe);
+                ks.load(null, "changeit".toCharArray());
+            }
+
+            addCert(ks, "cacert");
+
+            for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) {
+                String alias = e.nextElement();
+                System.err.println("Aliases: " + alias + " isCert? " + ks.isCertificateEntry(alias));
+            }
+
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            kmf.init(ks, "".toCharArray());
+            SSLContext sslc = SSLContext.getInstance("SSL");
+            sslc.init(kmf.getKeyManagers(), null, null);
+            _sslContext = sslc;
+        } catch (GeneralSecurityException gse) {
+            System.err.println("KS error: " + gse);
+            return;
+        } catch (IOException ioe) {
+            System.err.println("IO error: " + ioe);
+            return;
+        }
+        *******/
+
+        EepGet get = new SSLEepGet(I2PAppContext.getGlobalContext(), out, url);
+        get.addStatusListener(get.new CLIStatusListener(1024, 40));
+        get.fetch(45*1000, -1, 60*1000);
+    }
+    
+    private static void usage() {
+        System.err.println("SSLEepGet url");
+    }
+
+/******
+    private static boolean addCert(KeyStore ks, String file) {
+
+            try {
+                InputStream fis = new FileInputStream(file);
+                CertificateFactory cf = CertificateFactory.getInstance("X.509");
+                X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
+                fis.close();
+                System.err.println("Adding cert, Issuer: " + cert.getIssuerX500Principal());
+                try {
+                    cert.checkValidity();
+                } catch (CertificateExpiredException cee) {
+                    System.err.println("Warning - expired cert: " + cee);
+                } catch (CertificateNotYetValidException cnyve) {
+                    System.err.println("Warning - not yet valid cert: " + cnyve);
+                }
+                // use file name as alias
+                ks.setCertificateEntry(file, cert);
+            } catch (GeneralSecurityException gse) {
+                System.err.println("Read cert error: " + gse);
+                return false;
+            } catch (IOException ioe) {
+                System.err.println("Read cert error: " + ioe);
+                return false;
+            }
+        return true;
+    }
+*******/
+    
+    @Override
+    protected void doFetch(SocketTimeout timeout) throws IOException {
+        _headersRead = false;
+        _aborted = false;
+        try {
+            readHeaders();
+        } finally {
+            _headersRead = true;
+        }
+        if (_aborted)
+            throw new IOException("Timed out reading the HTTP headers");
+        
+        timeout.resetTimer();
+        if (_fetchInactivityTimeout > 0)
+            timeout.setInactivityTimeout(_fetchInactivityTimeout);
+        else
+            timeout.setInactivityTimeout(60*1000);
+        
+        if (_redirectLocation != null) {
+            throw new IOException("Server redirect to " + _redirectLocation + " not allowed");
+        }
+        
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("Headers read completely, reading " + _bytesRemaining);
+        
+        boolean strictSize = (_bytesRemaining >= 0);
+
+        int remaining = (int)_bytesRemaining;
+        byte buf[] = new byte[1024];
+        while (_keepFetching && ( (remaining > 0) || !strictSize ) && !_aborted) {
+            int toRead = buf.length;
+            if (strictSize && toRead > remaining)
+                toRead = remaining;
+            int read = _proxyIn.read(buf, 0, toRead);
+            if (read == -1)
+                break;
+            timeout.resetTimer();
+            _out.write(buf, 0, read);
+            _bytesTransferred += read;
+
+            remaining -= read;
+            if (remaining==0 && _encodingChunked) {
+                int char1 = _proxyIn.read();
+                if (char1 == '\r') {
+                    int char2 = _proxyIn.read();
+                    if (char2 == '\n') {
+                        remaining = (int) readChunkLength();
+                    } else {
+                        _out.write(char1);
+                        _out.write(char2);
+                        _bytesTransferred += 2;
+                        remaining -= 2;
+                        read += 2;
+                    }
+                } else {
+                    _out.write(char1);
+                    _bytesTransferred++;
+                    remaining--;
+                    read++;
+                }
+            }
+            timeout.resetTimer();
+            if (_bytesRemaining >= read) // else chunked?
+                _bytesRemaining -= read;
+            if (read > 0) {
+                for (int i = 0; i < _listeners.size(); i++) 
+                    _listeners.get(i).bytesTransferred(
+                            _alreadyTransferred, 
+                            read, 
+                            _bytesTransferred, 
+                            _encodingChunked?-1:_bytesRemaining, 
+                            _url);
+                // This seems necessary to properly resume a partial download into a stream,
+                // as nothing else increments _alreadyTransferred, and there's no file length to check.
+                // Do this after calling the listeners to keep the total correct
+                _alreadyTransferred += read;
+            }
+        }
+            
+        if (_out != null)
+            _out.close();
+        _out = null;
+        
+        if (_aborted)
+            throw new IOException("Timed out reading the HTTP data");
+        
+        timeout.cancel();
+        
+        if (_transferFailed) {
+            // 404, etc - transferFailed is called after all attempts fail, by fetch() above
+            for (int i = 0; i < _listeners.size(); i++) 
+                _listeners.get(i).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, new Exception("Attempt failed"));
+        } else if ( (_bytesRemaining == -1) || (remaining == 0) ) {
+            for (int i = 0; i < _listeners.size(); i++) 
+                _listeners.get(i).transferComplete(
+                        _alreadyTransferred, 
+                        _bytesTransferred, 
+                        _encodingChunked?-1:_bytesRemaining, 
+                        _url, 
+                        _outputFile, 
+                        _notModified);
+        } else {
+            throw new IOException("Disconnection on attempt " + _currentAttempt + " after " + _bytesTransferred);
+        }
+    }
+
+    @Override
+    protected void sendRequest(SocketTimeout timeout) throws IOException {
+        if (_outputStream != null) {
+            // We are reading into a stream supplied by a caller,
+            // for which we cannot easily determine how much we've written.
+            // Assume that _alreadyTransferred holds the right value
+            // (we should never be restarted to work on an old stream).
+	} else {
+            File outFile = new File(_outputFile);
+            if (outFile.exists())
+                _alreadyTransferred = outFile.length();
+        }
+
+        String req = getRequest();
+
+        try {
+            URL url = new URL(_actualURL);
+            if ("https".equals(url.getProtocol())) {
+                String host = url.getHost();
+                int port = url.getPort();
+                if (port == -1)
+                    port = 443;
+                // part of the experiment above
+                //if (_sslContext != null)
+                //    _proxy = _sslContext.getSocketFactory().createSocket(host, port);
+                //else
+                    _proxy = SSLSocketFactory.getDefault().createSocket(host, port);
+            } else {
+                throw new IOException("Only https supported: " + _actualURL);
+            }
+        } catch (MalformedURLException mue) {
+            throw new IOException("Request URL is invalid");
+        }
+
+        _proxyIn = _proxy.getInputStream();
+        _proxyOut = _proxy.getOutputStream();
+        
+        _proxyOut.write(DataHelper.getUTF8(req));
+        _proxyOut.flush();
+        
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("Request flushed");
+    }
+}