From 8a0c3f10f4f307ee445d9b6976b4888a6623446f Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Fri, 4 Oct 2013 19:06:39 +0000
Subject: [PATCH]  Update:  - Stub out support for clearnet su3 updating  -
 PartialEepGet and SSLEepGet tweaks to support clearnet update  - Remove
 proxy, key, and url config from /configupdate  - More URI checks in
 UpdateRunner  - Add su3 mime type  - Move advanced setting to HelperBase

---
 apps/i2psnark/mime.properties                 |  1 +
 .../router/update/ConsoleUpdateManager.java   |  5 ++
 .../net/i2p/router/update/NewsFetcher.java    | 37 ++++++-----
 .../net/i2p/router/update/UpdateHandler.java  |  9 ++-
 .../net/i2p/router/update/UpdateRunner.java   | 64 +++++++++++++++++--
 .../i2p/router/web/ConfigTunnelsHelper.java   |  4 +-
 .../src/net/i2p/router/web/HelperBase.java    |  7 ++
 .../net/i2p/router/web/TunnelRenderer.java    |  2 +-
 apps/routerconsole/jsp/configupdate.jsp       | 12 ++--
 core/java/src/net/i2p/util/PartialEepGet.java | 14 +++-
 core/java/src/net/i2p/util/SSLEepGet.java     | 32 +++++++++-
 11 files changed, 152 insertions(+), 35 deletions(-)

diff --git a/apps/i2psnark/mime.properties b/apps/i2psnark/mime.properties
index 12eea9b1f8..4c0a7a2570 100644
--- a/apps/i2psnark/mime.properties
+++ b/apps/i2psnark/mime.properties
@@ -18,6 +18,7 @@ ogv	= video/ogg
 oga	= audio/ogg
 rar	= application/x-rar-compressed
 su2	= application/zip
+su3	= application/zip
 sud	= application/zip
 tbz	= application/x-bzip2
 txt	= text/plain
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java b/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
index 6f5704ce60..17b33a0591 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java
@@ -135,6 +135,11 @@ public class ConsoleUpdateManager implements UpdateManager {
         if (ConfigUpdateHandler.USE_SU3_UPDATE) {
             register(c, ROUTER_SIGNED_SU3, HTTP, 0);
             register(u, ROUTER_SIGNED_SU3, HTTP, 0);
+            // todo
+            //register(c, ROUTER_SIGNED_SU3, HTTPS_CLEARNET, 0);
+            //register(u, ROUTER_SIGNED_SU3, HTTPS_CLEARNET, -10);
+            //register(c, ROUTER_SIGNED_SU3, HTTP_CLEARNET, 0);
+            //register(u, ROUTER_SIGNED_SU3, HTTP_CLEARNET, -20);
         }
         // TODO see NewsFetcher
         //register(u, ROUTER_SIGNED, HTTPS_CLEARNET, -5);
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
index e208d024d8..edd3ddbae0 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
@@ -114,6 +114,8 @@ class NewsFetcher extends UpdateRunner {
     private static final String SU3_KEY = "su3torrent";
     private static final String CLEARNET_SUD_KEY = "sudclearnet";
     private static final String CLEARNET_SU2_KEY = "su2clearnet";
+    private static final String CLEARNET_HTTP_SU3_KEY = "su3clearnet";
+    private static final String CLEARNET_HTTPS_SU3_KEY = "su3ssl";
     private static final String I2P_SUD_KEY = "sudi2p";
     private static final String I2P_SU2_KEY = "su2i2p";
 
@@ -150,14 +152,9 @@ class NewsFetcher extends UpdateRunner {
                             // Must do su3 first
                             if (ConfigUpdateHandler.USE_SU3_UPDATE) {
                                 sourceMap.put(HTTP, _mgr.getUpdateURLs(ROUTER_SIGNED_SU3, "", HTTP));
-                                String murl = args.get(SU3_KEY);
-                                if (murl != null) {
-                                    List<URI> uris = tokenize(murl);
-                                    if (!uris.isEmpty()) {
-                                        Collections.shuffle(uris, _context.random());
-                                        sourceMap.put(TORRENT, uris);
-                                    }
-                                }
+                                addMethod(TORRENT, args.get(SU3_KEY), sourceMap);
+                                addMethod(HTTP_CLEARNET, args.get(CLEARNET_HTTP_SU3_KEY), sourceMap);
+                                addMethod(HTTPS_CLEARNET, args.get(CLEARNET_HTTPS_SU3_KEY), sourceMap);
                                 // notify about all sources at once
                                 _mgr.notifyVersionAvailable(this, _currentURI, ROUTER_SIGNED_SU3,
                                                             "", sourceMap, ver, "");
@@ -166,14 +163,7 @@ class NewsFetcher extends UpdateRunner {
                             // now do sud/su2
                             sourceMap.put(HTTP, _mgr.getUpdateURLs(ROUTER_SIGNED, "", HTTP));
                             String key = FileUtil.isPack200Supported() ? SU2_KEY : SUD_KEY;
-                            String murl = args.get(key);
-                            if (murl != null) {
-                                List<URI> uris = tokenize(murl);
-                                if (!uris.isEmpty()) {
-                                    Collections.shuffle(uris, _context.random());
-                                    sourceMap.put(TORRENT, uris);
-                                }
-                            }
+                            addMethod(TORRENT, args.get(key), sourceMap);
                             // notify about all sources at once
                             _mgr.notifyVersionAvailable(this, _currentURI, ROUTER_SIGNED,
                                                         "", sourceMap, ver, "");
@@ -280,6 +270,21 @@ class NewsFetcher extends UpdateRunner {
         return rv;
     }
 
+    /**
+     *  Parse URLs and add to the map
+     *  @param urls may be null
+     *  @since 0.9.9
+     */
+    private void addMethod(UpdateMethod method, String urls, Map<UpdateMethod, List<URI>> map) {
+        if (urls != null) {
+            List<URI> uris = tokenize(urls);
+            if (!uris.isEmpty()) {
+                Collections.shuffle(uris, _context.random());
+                map.put(method, uris);
+            }
+        }
+    }
+
     /** override to prevent status update */
     @Override
     public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/update/UpdateHandler.java
index af3f0e39cc..949fa372d8 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UpdateHandler.java
@@ -5,6 +5,8 @@ import java.util.List;
 
 import net.i2p.router.RouterContext;
 import net.i2p.update.*;
+import static net.i2p.update.UpdateType.*;
+import static net.i2p.update.UpdateMethod.*;
 
 /**
  * <p>Handles the request to update the router by firing one or more
@@ -38,10 +40,11 @@ class UpdateHandler implements Updater {
      */
     public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
                              String id, String newVersion, long maxTime) {
-        if ((type != UpdateType.ROUTER_SIGNED && type != UpdateType.ROUTER_SIGNED_SU3) ||
-            method != UpdateMethod.HTTP || updateSources.isEmpty())
+        if ((type != ROUTER_SIGNED && type != ROUTER_SIGNED_SU3) ||
+           ( method != HTTP && method != HTTP_CLEARNET && method != HTTPS_CLEARNET) ||
+            updateSources.isEmpty())
             return null;
-        UpdateRunner update = new UpdateRunner(_context, _mgr, type, updateSources);
+        UpdateRunner update = new UpdateRunner(_context, _mgr, type, method, updateSources);
         // set status before thread to ensure UI feedback
         _mgr.notifyProgress(update, "<b>" + _mgr._("Updating") + "</b>");
         return update;
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/UpdateRunner.java b/apps/routerconsole/java/src/net/i2p/router/update/UpdateRunner.java
index f048752d03..c3b388db2a 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UpdateRunner.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UpdateRunner.java
@@ -5,6 +5,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.net.URI;
 import java.util.List;
+import java.util.Locale;
 import java.util.StringTokenizer;
 
 import net.i2p.crypto.TrustedUpdate;
@@ -13,10 +14,12 @@ import net.i2p.router.RouterContext;
 import net.i2p.router.RouterVersion;
 import net.i2p.router.web.ConfigUpdateHandler;
 import net.i2p.update.*;
+import static net.i2p.update.UpdateMethod.*;
 import net.i2p.util.EepGet;
 import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
 import net.i2p.util.PartialEepGet;
+import net.i2p.util.SSLEepGet;
 import net.i2p.util.VersionComparator;
 
 /**
@@ -31,6 +34,7 @@ class UpdateRunner extends I2PAppThread implements UpdateTask, EepGet.StatusList
     protected final Log _log;
     protected final ConsoleUpdateManager _mgr;
     protected final UpdateType _type;
+    protected final UpdateMethod _method;
     protected final List<URI> _urls;
     protected final String _updateFile;
     protected volatile boolean _isRunning;
@@ -58,18 +62,38 @@ class UpdateRunner extends I2PAppThread implements UpdateTask, EepGet.StatusList
         this(ctx, mgr, type, uris, RouterVersion.VERSION);
     }
 
+    /**
+     *  Uses router version for partial checks
+     *  @since 0.9.9
+     */
+    public UpdateRunner(RouterContext ctx, ConsoleUpdateManager mgr, UpdateType type,
+                        UpdateMethod method, List<URI> uris) {
+        this(ctx, mgr, type, method, uris, RouterVersion.VERSION);
+    }
+
     /**
      *  @param currentVersion used for partial checks
      *  @since 0.9.7
      */
     public UpdateRunner(RouterContext ctx, ConsoleUpdateManager mgr, UpdateType type,
                         List<URI> uris, String currentVersion) { 
+        this(ctx, mgr, type, HTTP, uris, currentVersion);
+    }
+
+    /**
+     *  @param method HTTP, HTTP_CLEARNET, or HTTPS_CLEARNET
+     *  @param currentVersion used for partial checks
+     *  @since 0.9.9
+     */
+    public UpdateRunner(RouterContext ctx, ConsoleUpdateManager mgr, UpdateType type,
+                        UpdateMethod method, List<URI> uris, String currentVersion) { 
         super("Update Runner");
         setDaemon(true);
         _context = ctx;
         _log = ctx.logManager().getLog(getClass());
         _mgr = mgr;
         _type = type;
+        _method = method;
         _urls = uris;
         _baos = new ByteArrayOutputStream(TrustedUpdate.HEADER_BYTES);
         _updateFile = (new File(ctx.getTempDir(), "update" + ctx.random().nextInt() + ".tmp")).getAbsolutePath();
@@ -87,7 +111,7 @@ class UpdateRunner extends I2PAppThread implements UpdateTask, EepGet.StatusList
 
     public UpdateType getType() { return _type; }
 
-    public UpdateMethod getMethod() { return UpdateMethod.HTTP; }
+    public UpdateMethod getMethod() { return _method; }
 
     public URI getURI() { return _currentURI; }
 
@@ -120,9 +144,26 @@ class UpdateRunner extends I2PAppThread implements UpdateTask, EepGet.StatusList
         // Alternative: In bytesTransferred(), Check the data in the output file after
         // we've received at least 56 bytes. Need a cancel() method in EepGet ?
 
-        boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
-        String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
-        int proxyPort = ConfigUpdateHandler.proxyPort(_context);
+        boolean shouldProxy;
+        String proxyHost;
+        int proxyPort;
+        boolean isSSL = false;
+        if (_method == HTTP) {
+            shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
+            proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
+            proxyPort = ConfigUpdateHandler.proxyPort(_context);
+        } else if (_method == HTTP_CLEARNET) {
+            shouldProxy = false;
+            proxyHost = null;
+            proxyPort = 0;
+        } else if (_method == HTTPS_CLEARNET) {
+            shouldProxy = false;
+            proxyHost = null;
+            proxyPort = 0;
+            isSSL = true;
+        } else {
+            throw new IllegalArgumentException();
+        }
 
         if (_urls.isEmpty()) {
             // not likely, don't bother translating
@@ -135,12 +176,23 @@ class UpdateRunner extends I2PAppThread implements UpdateTask, EepGet.StatusList
         for (URI uri : _urls) {
             _currentURI = uri;
             String updateURL = uri.toString();
+            if ((_method == HTTP && !"http".equals(uri.getScheme())) ||
+                (_method == HTTP_CLEARNET && !"http".equals(uri.getScheme())) ||
+                (_method == HTTPS_CLEARNET && !"https".equals(uri.getScheme())) ||
+                uri.getHost() == null ||
+                (_method != HTTP && uri.getHost().toLowerCase(Locale.US).endsWith(".i2p"))) {
+                if (_log.shouldLog(Log.WARN))
+                    _log.warn("Bad update URI " + uri + " for method " + _method);
+                continue;
+            }
+
             updateStatus("<b>" + _("Updating from {0}", linkify(updateURL)) + "</b>");
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug("Selected update URL: " + updateURL);
 
             // Check the first 56 bytes for the version
-            if (shouldProxy) {
+            // PartialEepGet works with clearnet but not with SSL
+            if (!isSSL) {
                 _isPartial = true;
                 _baos.reset();
                 try {
@@ -160,6 +212,8 @@ class UpdateRunner extends I2PAppThread implements UpdateTask, EepGet.StatusList
                 if (shouldProxy)
                     // 40 retries!!
                     _get = new EepGet(_context, proxyHost, proxyPort, 40, _updateFile, updateURL, false);
+                else if (isSSL)
+                    _get = new SSLEepGet(_context, _updateFile, updateURL);
                 else
                     _get = new EepGet(_context, 1, _updateFile, updateURL, false);
                 _get.addStatusListener(UpdateRunner.this);
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java
index 85cfd6066e..5146d7b0d9 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java
@@ -14,8 +14,6 @@ public class ConfigTunnelsHelper extends HelperBase {
     private static final String HOPS = ngettext("1 hop", "{0} hops");
     private static final String TUNNELS = ngettext("1 tunnel", "{0} tunnels");
 
-    static final String PROP_ADVANCED = "routerconsole.advanced";
-
     public String getForm() {
         StringBuilder buf = new StringBuilder(1024);
         // HTML: <input> cannot be inside a <table>
@@ -69,7 +67,7 @@ public class ConfigTunnelsHelper extends HelperBase {
 
     private void renderForm(StringBuilder buf, int index, String prefix, String name, TunnelPoolSettings in, TunnelPoolSettings out) {
 
-        boolean advanced = _context.getBooleanProperty(PROP_ADVANCED);
+        boolean advanced = isAdvanced();
 
         buf.append("<tr><th colspan=\"3\"><a name=\"").append(prefix).append("\">");
         buf.append(name).append("</a></th></tr>\n");
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java b/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java
index 2bee07833b..ff0dc80592 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java
@@ -11,6 +11,8 @@ public abstract class HelperBase {
     protected RouterContext _context;
     protected Writer _out;
 
+    static final String PROP_ADVANCED = "routerconsole.advanced";
+
     /**
      * Configure this bean to query a particular router context
      *
@@ -25,6 +27,11 @@ public abstract class HelperBase {
         }
     }
 
+    /** @since 0.9.9 */
+    public boolean isAdvanced() {
+        return _context.getBooleanProperty(PROP_ADVANCED);
+    }
+
     /** might be useful in the jsp's */
     //public RouterContext getContext() { return _context; }
 
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java
index 6be7466a1e..073ddad1b9 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java
@@ -39,7 +39,7 @@ public class TunnelRenderer {
         Map<Hash, TunnelPool> clientInboundPools = _context.tunnelManager().getInboundClientPools();
         Map<Hash, TunnelPool> clientOutboundPools = _context.tunnelManager().getOutboundClientPools();
         destinations = new ArrayList(clientInboundPools.keySet());
-        boolean debug = _context.getBooleanProperty(ConfigTunnelsHelper.PROP_ADVANCED);
+        boolean debug = _context.getBooleanProperty(HelperBase.PROP_ADVANCED);
         for (int i = 0; i < destinations.size(); i++) {
             Hash client = destinations.get(i);
             boolean isLocal = _context.clientManager().isLocal(client);
diff --git a/apps/routerconsole/jsp/configupdate.jsp b/apps/routerconsole/jsp/configupdate.jsp
index 2d2e54d257..2fd5e65360 100644
--- a/apps/routerconsole/jsp/configupdate.jsp
+++ b/apps/routerconsole/jsp/configupdate.jsp
@@ -49,17 +49,21 @@
           <td><jsp:getProperty name="updatehelper" property="updatePolicySelectBox" /></td></tr>
     <% }   // if canInstall %>
         <tr><td class="mediumtags" align="right"><b><%=intl._("Update through the eepProxy?")%></b></td>
-          <td><jsp:getProperty name="updatehelper" property="updateThroughProxy" /></td>
-        </tr><tr><td class="mediumtags" align="right"><b><%=intl._("eepProxy host")%>:</b></td>
+          <td><jsp:getProperty name="updatehelper" property="updateThroughProxy" /></td></tr>
+      <% if (updatehelper.isAdvanced()) { %>
+        <tr><td class="mediumtags" align="right"><b><%=intl._("eepProxy host")%>:</b></td>
           <td><input type="text" size="10" name="proxyHost" value="<jsp:getProperty name="updatehelper" property="proxyHost" />" /></td>
         </tr><tr><td class="mediumtags" align="right"><b><%=intl._("eepProxy port")%>:</b></td>
           <td><input type="text" size="10" name="proxyPort" value="<jsp:getProperty name="updatehelper" property="proxyPort" />" /></td></tr>
+      <% }   // if isAdvanced %>
     <% if (updatehelper.canInstall()) { %>
+      <% if (updatehelper.isAdvanced()) { %>
         <tr><td class="mediumtags" align="right"><b><%=intl._("Update URLs")%>:</b></td>
           <td><textarea cols="60" rows="6" name="updateURL" wrap="off" spellcheck="false"><jsp:getProperty name="updatehelper" property="updateURL" /></textarea></td>
         </tr><tr><td class="mediumtags" align="right"><b><%=intl._("Trusted keys")%>:</b></td>
-          <td><textarea cols="60" rows="6" name="trustedKeys" wrap="off" spellcheck="false"><jsp:getProperty name="updatehelper" property="trustedKeys" /></textarea></td>
-        </tr><tr><td id="unsignedbuild" class="mediumtags" align="right"><b><%=intl._("Update with unsigned development builds?")%></b></td>
+          <td><textarea cols="60" rows="6" name="trustedKeys" wrap="off" spellcheck="false"><jsp:getProperty name="updatehelper" property="trustedKeys" /></textarea></td></tr>
+      <% }   // if isAdvanced %>
+        <tr><td id="unsignedbuild" class="mediumtags" align="right"><b><%=intl._("Update with unsigned development builds?")%></b></td>
           <td><jsp:getProperty name="updatehelper" property="updateUnsigned" /></td>
         </tr><tr><td class="mediumtags" align="right"><b><%=intl._("Unsigned Build URL")%>:</b></td>
           <td><input type="text" size="60" name="zipURL" value="<jsp:getProperty name="updatehelper" property="zipURL" />"></td></tr>
diff --git a/core/java/src/net/i2p/util/PartialEepGet.java b/core/java/src/net/i2p/util/PartialEepGet.java
index d557137307..7fe625b66c 100644
--- a/core/java/src/net/i2p/util/PartialEepGet.java
+++ b/core/java/src/net/i2p/util/PartialEepGet.java
@@ -11,6 +11,9 @@ import net.i2p.I2PAppContext;
  * Fetch exactly the first 'size' bytes into a stream
  * Anything less or more will throw an IOException
  * No retries, no min and max size options, no timeout option
+ * If the server does not return a Content-Length header of the correct size,
+ * the fetch will fail.
+ *
  * Useful for checking .sud versions
  *
  * @since 0.7.12
@@ -19,7 +22,13 @@ import net.i2p.I2PAppContext;
 public class PartialEepGet extends EepGet {
     long _fetchSize;
 
-    /** @param size fetch exactly this many bytes */
+    /**
+     * Instantiate an EepGet that will fetch exactly size bytes when fetch() is called.
+     *
+     * @param proxyHost use null or "" for no proxy
+     * @param proxyPort use 0 for no proxy
+     * @param size fetch exactly this many bytes
+     */
     public PartialEepGet(I2PAppContext ctx, String proxyHost, int proxyPort,
                          OutputStream outputStream,  String url, long size) {
         // we're using this constructor:
@@ -88,7 +97,8 @@ public class PartialEepGet extends EepGet {
     }
     
     private static void usage() {
-        System.err.println("PartialEepGet [-p 127.0.0.1:4444] [-l #bytes] url");
+        System.err.println("PartialEepGet [-p 127.0.0.1:4444] [-l #bytes] url\n" +
+                           "              (use -p :0 for no proxy)");
     }
     
     @Override
diff --git a/core/java/src/net/i2p/util/SSLEepGet.java b/core/java/src/net/i2p/util/SSLEepGet.java
index e7a060230f..3866f3d1ab 100644
--- a/core/java/src/net/i2p/util/SSLEepGet.java
+++ b/core/java/src/net/i2p/util/SSLEepGet.java
@@ -103,9 +103,39 @@ public class SSLEepGet extends EepGet {
      *  @since 0.8.2
      */
     public SSLEepGet(I2PAppContext ctx, OutputStream outputStream, String url, SSLState state) {
+        this(ctx, null, outputStream, url, null);
+    }
+
+    /**
+     *  A new SSLEepGet with a new SSLState
+     *  @since 0.9.9
+     */
+    public SSLEepGet(I2PAppContext ctx, String outputFile, String url) {
+        this(ctx, outputFile, url, null);
+    }
+
+    /**
+     *  @param state an SSLState retrieved from a previous SSLEepGet with getSSLState(), or null.
+     *               This makes repeated fetches from the same host MUCH faster,
+     *               and prevents repeated key store loads even for different hosts.
+     *  @since 0.9.9
+     */
+    public SSLEepGet(I2PAppContext ctx, String outputFile, String url, SSLState state) {
+        this(ctx, outputFile, null, url, null);
+    }
+
+    /**
+     *  outputFile, outputStream: One null, one non-null
+     *
+     *  @param state an SSLState retrieved from a previous SSLEepGet with getSSLState(), or null.
+     *               This makes repeated fetches from the same host MUCH faster,
+     *               and prevents repeated key store loads even for different hosts.
+     *  @since 0.9.9
+     */
+    private SSLEepGet(I2PAppContext ctx, String outputFile, OutputStream outputStream, String url, SSLState state) {
         // 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);
+        super(ctx, false, null, -1, 0, -1, -1, outputFile, outputStream, url, true, null, null);
         if (state != null && state.context != null)
             _sslContext = state.context;
         else
-- 
GitLab