From 6f053287d58d365fe22324aa311dfe7efefa2433 Mon Sep 17 00:00:00 2001
From: zzz
Date: Wed, 19 Aug 2009 20:20:25 +0000
Subject: [PATCH] * Update: - Fix problems where a requested unsigned
update would actually kick off a signed update - Fix problem
when policy set to notify, and clicking check for update, incorrectly
causing unsigned update download and bad messages - Verify zip
integrity of unsigned updates - Move zip files to router dir, not base
dir - More tweaks and cleanup
---
.../i2p/router/web/ConfigUpdateHandler.java | 2 +-
.../src/net/i2p/router/web/NewsFetcher.java | 4 +-
.../i2p/router/web/UnsignedUpdateHandler.java | 30 +++++++----
.../src/net/i2p/router/web/UpdateHandler.java | 47 ++++++++++-------
apps/routerconsole/jsp/summarynoframe.jsp | 4 +-
core/java/src/net/i2p/util/FileUtil.java | 50 +++++++++++++++++++
6 files changed, 103 insertions(+), 34 deletions(-)
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 256babbe3..d0a3fa92c 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 447273a88..11848ef46 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 0d812d0ec..a89a7a05c 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;
*
*/
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 = "Updating";
+ 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 = "Update downloaded";
- // 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 = "Update downloaded";
+ } else {
+ updFile.delete();
+ _status = "Unsigned update file is corrupt 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 f8890482d..293743bbb 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(":
\n" + (currentWrite + alreadyTransferred));
- buf.append(" transferred");
+ buf.append(":
\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 = "Update downloaded";
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 eae7de252..507934049 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("
" + update.getStatus());
+ out.print("
" + 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("\n");
if (helper.unsignedUpdateAvailable())
- out.print("\n");
+ out.print("\n");
out.print("\n");
}
}
diff --git a/core/java/src/net/i2p/util/FileUtil.java b/core/java/src/net/i2p/util/FileUtil.java
index 38073fd60..ab0341f41 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.