diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
index 256babbe3c6dbe6a1ea83ba4e8db8127a64c1914..d0a3fa92c361f657c073796dacf5c5e62cd007e2 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
@@ -54,7 +54,7 @@ public class ConfigUpdateHandler extends FormHandler {
             NewsFetcher fetcher = NewsFetcher.getInstance(I2PAppContext.getGlobalContext());
             fetcher.fetchNews();
             if (fetcher.shouldFetchUnsigned())
-                fetcher.fetchUnsigned();
+                fetcher.fetchUnsignedHead();
             if (fetcher.updateAvailable() || fetcher.unsignedUpdateAvailable()) {
                 if ( (_updatePolicy == null) || (!_updatePolicy.equals("notify")) )
                     addFormNotice("Update available, attempting to download now");
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
index 447273a8870875218079a7e4a5b31e77ad6ed010..11848ef46a2e8e691da0cc86781e87390fca3eab 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
@@ -188,8 +188,8 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
                     if (ms <= 0) return;
                     if (modtime > ms) {
                         _unsignedUpdateAvailable = true;
-                        // '07-Jul 21:09' with month name in the system locale
-                        _unsignedUpdateVersion = (new SimpleDateFormat("dd-MMM HH:mm")).format(new Date(modtime));
+                        // '07-Jul 21:09 UTC' with month name in the system locale
+                        _unsignedUpdateVersion = (new SimpleDateFormat("dd-MMM HH:mm")).format(new Date(modtime)) + " UTC";
                         if (shouldInstall())
                             fetchUnsigned();
                     }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UnsignedUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UnsignedUpdateHandler.java
index 0d812d0ec0e225f3750af41d1c69f1a075fed9c0..a89a7a05c7e7f7e60b1e1ec2532e9c2eb75a0ce9 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/UnsignedUpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/UnsignedUpdateHandler.java
@@ -21,6 +21,7 @@ import net.i2p.util.Log;
  * </p>
  */
 public class UnsignedUpdateHandler extends UpdateHandler {
+    private static UnsignedUpdateRunner _unsignedUpdateRunner;
     private String _zipURL;
     private String _zipVersion;
 
@@ -34,19 +35,19 @@ public class UnsignedUpdateHandler extends UpdateHandler {
     @Override
     public void update() {
         // don't block waiting for the other one to finish
-        if ("true".equals(System.getProperty(PROP_UPDATE_IN_PROGRESS, "false"))) {
+        if ("true".equals(System.getProperty(PROP_UPDATE_IN_PROGRESS))) {
             _log.error("Update already running");
             return;
         }
         synchronized (UpdateHandler.class) {
-            if (_updateRunner == null) {
-                _updateRunner = new UnsignedUpdateRunner();
+            if (_unsignedUpdateRunner == null) {
+                _unsignedUpdateRunner = new UnsignedUpdateRunner();
             }
-            if (_updateRunner.isRunning()) {
+            if (_unsignedUpdateRunner.isRunning()) {
                 return;
             } else {
                 System.setProperty(PROP_UPDATE_IN_PROGRESS, "true");
-                I2PAppThread update = new I2PAppThread(_updateRunner, "Update");
+                I2PAppThread update = new I2PAppThread(_unsignedUpdateRunner, "UnsignedUpdate");
                 update.start();
             }
         }
@@ -64,6 +65,8 @@ public class UnsignedUpdateHandler extends UpdateHandler {
         @Override
         protected void update() {
             _status = "<b>Updating</b>";
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Starting unsigned update URL: " + _zipURL);
             // always proxy for now
             //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);
@@ -74,19 +77,26 @@ public class UnsignedUpdateHandler extends UpdateHandler {
                 _get.addStatusListener(UnsignedUpdateRunner.this);
                 _get.fetch();
             } catch (Throwable t) {
-                _context.logManager().getLog(UpdateHandler.class).error("Error updating", t);
+                _log.error("Error updating", t);
             }
         }
         
         /** eepget listener callback Overrides */
         @Override
         public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
-            _status = "<b>Update downloaded</b>";
-            // we should really verify zipfile integrity here but there's no easy way to do it
-            String to = (new File(_context.getBaseDir(), Router.UPDATE_FILE)).getAbsolutePath();
+            File updFile = new File(_updateFile);
+            if (FileUtil.verifyZip(updFile)) {
+                _status = "<b>Update downloaded</b>";
+            } else {
+                updFile.delete();
+                _status = "<b>Unsigned update file is corrupt</b> from " + url;
+                _log.log(Log.CRIT, "Corrupt zip file from " + url);
+                return;
+            }
+            String to = (new File(_context.getRouterDir(), Router.UPDATE_FILE)).getAbsolutePath();
             boolean copied = FileUtil.copy(_updateFile, to, true);
             if (copied) {
-                (new File(_updateFile)).delete();
+                updFile.delete();
                 String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
                 this.done = true;
                 String lastmod = _get.getLastModified();
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
index f8890482db0b19a8ba1962af8d4da2c70bcd24b9..293743bbbc6c71b0713dd619be8ad2d576021676 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
@@ -8,11 +8,12 @@ import java.util.StringTokenizer;
 
 import net.i2p.I2PAppContext;
 import net.i2p.crypto.TrustedUpdate;
+import net.i2p.data.DataHelper;
 import net.i2p.router.Router;
 import net.i2p.router.RouterContext;
 import net.i2p.router.RouterVersion;
 import net.i2p.util.EepGet;
-import net.i2p.util.I2PThread;
+import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
 
 /**
@@ -31,7 +32,9 @@ public class UpdateHandler {
     protected RouterContext _context;
     protected Log _log;
     protected String _updateFile;
+    protected static String _status = "";
     private String _action;
+    private String _nonce;
     
     protected static final String SIGNED_UPDATE_FILE = "i2pupdate.sud";
     protected static final String PROP_UPDATE_IN_PROGRESS = "net.i2p.router.web.UpdateHandler.updateInProgress";
@@ -61,13 +64,22 @@ public class UpdateHandler {
         }
     }
     
-    public void setUpdateAction(String val) { _action = val; }
+    /** these two can be set in either order, so call checkUpdateAction() twice */
+    public void setUpdateAction(String val) {
+        _action = val;
+        checkUpdateAction();
+    }
     
     public void setUpdateNonce(String nonce) { 
-        if (nonce == null) return;
-        if (nonce.equals(System.getProperty("net.i2p.router.web.UpdateHandler.nonce")) ||
-            nonce.equals(System.getProperty("net.i2p.router.web.UpdateHandler.noncePrev"))) {
-            if (_action != null && _action.contains("Unsigned")) {
+        _nonce = nonce;
+        checkUpdateAction();
+    }
+
+    private void checkUpdateAction() { 
+        if (_nonce == null || _action == null) return;
+        if (_nonce.equals(System.getProperty("net.i2p.router.web.UpdateHandler.nonce")) ||
+            _nonce.equals(System.getProperty("net.i2p.router.web.UpdateHandler.noncePrev"))) {
+            if (_action.contains("Unsigned")) {
                 // Not us, have NewsFetcher instantiate the correct class.
                 NewsFetcher fetcher = NewsFetcher.getInstance(_context);
                 fetcher.fetchUnsigned();
@@ -90,16 +102,14 @@ public class UpdateHandler {
                 return;
             } else {
                 System.setProperty(PROP_UPDATE_IN_PROGRESS, "true");
-                I2PThread update = new I2PThread(_updateRunner, "Update");
+                I2PAppThread update = new I2PAppThread(_updateRunner, "SignedUpdate");
                 update.start();
             }
         }
     }
     
-    public String getStatus() {
-        if (_updateRunner == null)
-            return "";
-        return _updateRunner.getStatus();
+    public static String getStatus() {
+        return _status;
     }
     
     public boolean isDone() {
@@ -113,7 +123,6 @@ public class UpdateHandler {
     public class UpdateRunner implements Runnable, EepGet.StatusListener {
         protected boolean _isRunning;
         protected boolean done;
-        protected String _status;
         protected EepGet _get;
         private final DecimalFormat _pct = new DecimalFormat("0.0%");
 
@@ -126,7 +135,6 @@ public class UpdateHandler {
         public boolean isDone() {
             return this.done;
         }
-        public String getStatus() { return _status; }
         public void run() {
             _isRunning = true;
             update();
@@ -150,7 +158,7 @@ public class UpdateHandler {
                 _get.addStatusListener(UpdateRunner.this);
                 _get.fetch();
             } catch (Throwable t) {
-                _context.logManager().getLog(UpdateHandler.class).error("Error updating", t);
+                _log.error("Error updating", t);
             }
         }
         
@@ -167,15 +175,16 @@ public class UpdateHandler {
             synchronized (_pct) {
                 buf.append(_pct.format(pct));
             }
-            buf.append(":<br />\n" + (currentWrite + alreadyTransferred));
-            buf.append(" transferred");
+            buf.append(":<br>\n");
+            buf.append(DataHelper.formatSize(currentWrite + alreadyTransferred));
+            buf.append("B transferred");
             _status = buf.toString();
         }
         public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
             _status = "<b>Update downloaded</b>";
             TrustedUpdate up = new TrustedUpdate(_context);
             File f = new File(_updateFile);
-            File to = new File(_context.getBaseDir(), Router.UPDATE_FILE);
+            File to = new File(_context.getRouterDir(), Router.UPDATE_FILE);
             String err = up.migrateVerified(RouterVersion.VERSION, f, to);
             f.delete();
             if (err == null) {
@@ -233,9 +242,9 @@ public class UpdateHandler {
         while (tok.hasMoreTokens())
             URLList.add(tok.nextToken().trim());
         int size = URLList.size();
-        _log.log(Log.DEBUG, "Picking update source from " + size + " candidates.");
+        //_log.log(Log.DEBUG, "Picking update source from " + size + " candidates.");
         if (size <= 0) {
-            _log.log(Log.WARN, "Update list is empty - no update available");
+            _log.log(Log.CRIT, "Update source list is empty - cannot download update");
             return null;
         }
         int index = I2PAppContext.getGlobalContext().random().nextInt(size);
diff --git a/apps/routerconsole/jsp/summarynoframe.jsp b/apps/routerconsole/jsp/summarynoframe.jsp
index eae7de252db6ee698facbfd25e7e740f7e85ad93..507934049b00bb1b00982c453ca8f0de18860599 100644
--- a/apps/routerconsole/jsp/summarynoframe.jsp
+++ b/apps/routerconsole/jsp/summarynoframe.jsp
@@ -60,7 +60,7 @@
 <%
     if (helper.updateAvailable() || helper.unsignedUpdateAvailable()) {
         // display all the time so we display the final failure message
-        out.print("<br />" + update.getStatus());
+        out.print("<br>" + net.i2p.router.web.UpdateHandler.getStatus());
         if ("true".equals(System.getProperty("net.i2p.router.web.UpdateHandler.updateInProgress"))) {
         } else if((!update.isDone()) &&
                   request.getParameter("action") == null &&
@@ -76,7 +76,7 @@
             if (helper.updateAvailable())
                 out.print("<button type=\"submit\" name=\"updateAction\" value=\"signed\" >Download " + helper.getUpdateVersion() + " Update</button>\n");
             if (helper.unsignedUpdateAvailable())
-                out.print("<button type=\"submit\" name=\"updateAction\" value=\"Unsigned\" >Download Unsigned<br />" + helper.getUnsignedUpdateVersion() + " Update</button>\n");
+                out.print("<button type=\"submit\" name=\"updateAction\" value=\"Unsigned\" >Download Unsigned<br>Update " + helper.getUnsignedUpdateVersion() + "</button>\n");
             out.print("</form></p>\n");
         }
     }
diff --git a/core/java/src/net/i2p/util/FileUtil.java b/core/java/src/net/i2p/util/FileUtil.java
index 38073fd6054a5a20de2ded809ff8cefa27ab9b4d..ab0341f411de0d9219bf057bf704927b82b7ce50 100644
--- a/core/java/src/net/i2p/util/FileUtil.java
+++ b/core/java/src/net/i2p/util/FileUtil.java
@@ -137,6 +137,56 @@ public class FileUtil {
         }
     }
     
+    /**
+     * Verify the integrity of a zipfile.
+     * There doesn't seem to be any library function to do this,
+     * so we basically go through all the motions of extractZip() above,
+     * unzipping everything but throwing away the data.
+     *
+     * @return true if ok
+     */
+    public static boolean verifyZip(File zipfile) {
+        ZipFile zip = null;
+        try {
+            byte buf[] = new byte[16*1024];
+            zip = new ZipFile(zipfile);
+            Enumeration entries = zip.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = (ZipEntry)entries.nextElement();
+                if (entry.getName().indexOf("..") != -1) {
+                    //System.err.println("ERROR: Refusing to extract a zip entry with '..' in it [" + entry.getName() + "]");
+                    return false;
+                }
+                if (entry.isDirectory()) {
+                    // noop
+                } else {
+                    try {
+                        InputStream in = zip.getInputStream(entry);
+                        int read = 0;
+                        while ( (read = in.read(buf)) != -1) {
+                            // throw the data away
+                        }
+                        //System.err.println("INFO: File [" + entry.getName() + "] extracted");
+                        in.close();
+                    } catch (IOException ioe) {
+                        //System.err.println("ERROR: Error extracting the zip entry (" + entry.getName() + "]");
+                        //ioe.printStackTrace();
+                        return false;
+                    }
+                }
+            }
+            return true;
+        } catch (IOException ioe) {
+            //System.err.println("ERROR: Unable to extract the zip file");
+            //ioe.printStackTrace();
+            return false;
+        } finally {
+            if (zip != null) {
+                try { zip.close(); } catch (IOException ioe) {}
+            }
+        }
+    }
+    
     /**
      * Read in the last few lines of a (newline delimited) textfile, or null if
      * the file doesn't exist.