\n");
}
diff --git a/apps/routerconsole/jsp/css.jsp b/apps/routerconsole/jsp/css.jsp
index affe83099f..422d1329ce 100644
--- a/apps/routerconsole/jsp/css.jsp
+++ b/apps/routerconsole/jsp/css.jsp
@@ -2,8 +2,14 @@
/*
* This should be included inside ...,
* as it sets the stylesheet.
+ *
+ * This is included almost 30 times, so keep whitespace etc. to a minimum.
*/
+ // http://www.crazysquirrel.com/computing/general/form-encoding.jspx
+ if (request.getCharacterEncoding() == null)
+ request.setCharacterEncoding("UTF-8");
+
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
@@ -15,7 +21,9 @@
session.setAttribute("i2p.contextId", request.getParameter("i2p.contextId"));
}
%>
+
+
" />
console.css" rel="stylesheet" type="text/css">
-
\ No newline at end of file
+
diff --git a/apps/routerconsole/jsp/error.jsp b/apps/routerconsole/jsp/error.jsp
index b900d29a12..8b057046a7 100644
--- a/apps/routerconsole/jsp/error.jsp
+++ b/apps/routerconsole/jsp/error.jsp
@@ -13,7 +13,6 @@
// If it can't find the iframe or viewtheme.jsp I wonder if the whole thing blows up...
%>
I2P Router Console - Page Not Found
-
<%@include file="css.jsp" %>
<%
diff --git a/apps/routerconsole/jsp/flags.jsp b/apps/routerconsole/jsp/flags.jsp
index fc93fcea7b..00ce370fb6 100644
--- a/apps/routerconsole/jsp/flags.jsp
+++ b/apps/routerconsole/jsp/flags.jsp
@@ -25,6 +25,12 @@ if (c != null && c.length() > 0) {
if (rendered)
cout.close();
}
+/*
+ * Send a 403 instead of a 404, because the server sends error.jsp
+ * for 404 errors, complete with the summary bar, which would be
+ * a huge load for a page full of flags if the user didn't have the
+ * flags directory for some reason.
+ */
if (!rendered)
- response.sendError(404, "Not found");
+ response.sendError(403, "Flag not found");
%>
\ No newline at end of file
diff --git a/apps/routerconsole/jsp/index.jsp b/apps/routerconsole/jsp/index.jsp
index 2bef637521..b14f8e3525 100644
--- a/apps/routerconsole/jsp/index.jsp
+++ b/apps/routerconsole/jsp/index.jsp
@@ -5,8 +5,6 @@
<%@include file="css.jsp" %>
I2P Router Console - home
-
-
<%
if (System.getProperty("router.consoleNonce") == null) {
diff --git a/apps/routerconsole/jsp/summaryframe.jsp b/apps/routerconsole/jsp/summaryframe.jsp
index 05ad49ad7d..5d1d9eb2b9 100644
--- a/apps/routerconsole/jsp/summaryframe.jsp
+++ b/apps/routerconsole/jsp/summaryframe.jsp
@@ -10,7 +10,6 @@
<%@include file="css.jsp" %>
Summary Bar
-
<%
// try hard to avoid an error page in the iframe after shutdown
String action = request.getParameter("action");
diff --git a/apps/routerconsole/jsp/viewstat.jsp b/apps/routerconsole/jsp/viewstat.jsp
index aee42b9585..392a37b89c 100644
--- a/apps/routerconsole/jsp/viewstat.jsp
+++ b/apps/routerconsole/jsp/viewstat.jsp
@@ -63,7 +63,12 @@ if ( !rendered && ((rs != null) || fakeBw) ) {
}
} catch (NumberFormatException nfe) {}
}
+/*
+ * Send a 403 instead of a 404, because the server sends error.jsp
+ * for 404 errors, complete with the summary bar, which would be
+ * a huge load for a page full of graphs if there's a problem
+ */
if (!rendered) {
- response.sendError(404, "That stat is not available");
+ response.sendError(403, "That stat is not available");
}
%>
\ No newline at end of file
diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java
index 0f48efef2d..fdd560243f 100644
--- a/core/java/src/net/i2p/data/DataHelper.java
+++ b/core/java/src/net/i2p/data/DataHelper.java
@@ -105,6 +105,19 @@ public class DataHelper {
*/
public static void writeProperties(OutputStream rawStream, Properties props)
throws DataFormatException, IOException {
+ writeProperties(rawStream, props, false);
+ }
+
+ /**
+ * jrandom disabled UTF-8 in mid-2004, for performance reasons,
+ * i.e. slow foo.getBytes("UTF-8")
+ * Re-enable it so we can pass UTF-8 tunnel names through the I2CP SessionConfig.
+ *
+ * Use utf8 = false for RouterAddress (fast, non UTF-8)
+ * Use utf8 = true for SessionConfig (slow, UTF-8)
+ */
+ public static void writeProperties(OutputStream rawStream, Properties props, boolean utf8)
+ throws DataFormatException, IOException {
if (props != null) {
OrderedProperties p = new OrderedProperties();
p.putAll(props);
@@ -112,12 +125,15 @@ public class DataHelper {
for (Iterator iter = p.keySet().iterator(); iter.hasNext();) {
String key = (String) iter.next();
String val = p.getProperty(key);
- // now make sure they're in UTF-8
- //key = new String(key.getBytes(), "UTF-8");
- //val = new String(val.getBytes(), "UTF-8");
- writeString(baos, key);
+ if (utf8)
+ writeStringUTF8(baos, key);
+ else
+ writeString(baos, key);
baos.write(_equalBytes);
- writeString(baos, val);
+ if (utf8)
+ writeStringUTF8(baos, val);
+ else
+ writeString(baos, val);
baos.write(_semicolonBytes);
}
baos.close();
@@ -486,6 +502,7 @@ public class DataHelper {
/** Read in a string from the stream as specified by the I2P data structure spec.
* A string is 1 or more bytes where the first byte is the number of bytes (not characters!)
* in the string and the remaining 0-255 bytes are the non-null terminated UTF-8 encoded character array.
+ *
* @param in stream to read from
* @throws DataFormatException if the stream doesn't contain a validly formatted string
* @throws IOException if there is an IO error reading the string
@@ -496,12 +513,17 @@ public class DataHelper {
byte raw[] = new byte[size];
int read = read(in, raw);
if (read != size) throw new DataFormatException("Not enough bytes to read the string");
- return new String(raw);
+ // the following constructor throws an UnsupportedEncodingException which is an IOException,
+ // but that's only if UTF-8 is not supported. Other encoding errors are not thrown.
+ return new String(raw, "UTF-8");
}
/** Write out a string to the stream as specified by the I2P data structure spec. Note that the max
* size for a string allowed by the spec is 255 bytes.
*
+ * WARNING - this method destroys the encoding, and therefore violates
+ * the data structure spec.
+ *
* @param out stream to write string
* @param string string to write out: null strings are perfectly valid, but strings of excess length will
* cause a DataFormatException to be thrown
@@ -516,13 +538,41 @@ public class DataHelper {
int len = string.length();
if (len > 255)
throw new DataFormatException("The I2P data spec limits strings to 255 bytes or less, but this is "
- + string.length() + " [" + string + "]");
+ + len + " [" + string + "]");
writeLong(out, 1, len);
for (int i = 0; i < len; i++)
out.write((byte)(string.charAt(i) & 0xFF));
}
}
+ /** Write out a string to the stream as specified by the I2P data structure spec. Note that the max
+ * size for a string allowed by the spec is 255 bytes.
+ *
+ * This method correctly uses UTF-8
+ *
+ * @param out stream to write string
+ * @param string UTF-8 string to write out: null strings are perfectly valid, but strings of excess length will
+ * cause a DataFormatException to be thrown
+ * @throws DataFormatException if the string is not valid
+ * @throws IOException if there is an IO error writing the string
+ */
+ private static void writeStringUTF8(OutputStream out, String string)
+ throws DataFormatException, IOException {
+ if (string == null) {
+ writeLong(out, 1, 0);
+ } else {
+ // the following method throws an UnsupportedEncodingException which is an IOException,
+ // but that's only if UTF-8 is not supported. Other encoding errors are not thrown.
+ byte[] raw = string.getBytes("UTF-8");
+ int len = raw.length;
+ if (len > 255)
+ throw new DataFormatException("The I2P data spec limits strings to 255 bytes or less, but this is "
+ + len + " [" + string + "]");
+ writeLong(out, 1, len);
+ out.write(raw);
+ }
+ }
+
/** Read in a boolean as specified by the I2P data structure spec.
* A boolean is 1 byte that is either 0 (false), 1 (true), or 2 (null)
* @param in stream to read from
diff --git a/core/java/src/net/i2p/data/i2cp/SessionConfig.java b/core/java/src/net/i2p/data/i2cp/SessionConfig.java
index 5b1eb6b164..520413620d 100644
--- a/core/java/src/net/i2p/data/i2cp/SessionConfig.java
+++ b/core/java/src/net/i2p/data/i2cp/SessionConfig.java
@@ -173,7 +173,7 @@ public class SessionConfig extends DataStructureImpl {
_log.debug("PubKey size for destination: " + _destination.getPublicKey().getData().length);
_log.debug("SigningKey size for destination: " + _destination.getSigningPublicKey().getData().length);
_destination.writeBytes(out);
- DataHelper.writeProperties(out, _options);
+ DataHelper.writeProperties(out, _options, true); // UTF-8
DataHelper.writeDate(out, _creationDate);
} catch (IOException ioe) {
_log.error("IOError signing", ioe);
@@ -198,7 +198,7 @@ public class SessionConfig extends DataStructureImpl {
if ((_destination == null) || (_options == null) || (_signature == null) || (_creationDate == null))
throw new DataFormatException("Not enough data to create the session config");
_destination.writeBytes(out);
- DataHelper.writeProperties(out, _options);
+ DataHelper.writeProperties(out, _options, true); // UTF-8
DataHelper.writeDate(out, _creationDate);
_signature.writeBytes(out);
}
@@ -232,4 +232,4 @@ public class SessionConfig extends DataStructureImpl {
buf.append("]");
return buf.toString();
}
-}
\ No newline at end of file
+}
diff --git a/installer/resources/wrapper.config b/installer/resources/wrapper.config
index 3f07e7c858..3fbe0375e8 100644
--- a/installer/resources/wrapper.config
+++ b/installer/resources/wrapper.config
@@ -117,8 +117,12 @@ wrapper.logfile=$SYSTEM_java_io_tmpdir/wrapper.log
# no need for a wrapper.java.additional line too.
#wrapper.logfile=$INSTALL_PATH/wrapper.log
-# Format of output for the log file. (See docs for formats)
-wrapper.logfile.format=LPTM
+# Format of output for the log file.
+# The format consists of the tokens 'L' for log level, 'P' for prefix, 'D' for thread,
+# 'T' for time, 'Z' for millisecond time, and 'M' for message
+# Unfortunately the log timezone cannot be changed, see
+# http://www.nabble.com/Log-message-timezone-td23651317.html
+wrapper.logfile.format=TM
# Log Level for log file output. (See docs for log levels)
wrapper.logfile.loglevel=INFO
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index f49a381463..1b20bda9d5 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -1109,7 +1109,11 @@ public class Router {
return;
}
System.out.println("INFO: Update file exists [" + UPDATE_FILE + "] - installing");
- boolean ok = FileUtil.extractZip(updateFile, _context.getBaseDir());
+ // verify the whole thing first
+ // we could remember this fails, and not bother restarting, but who cares...
+ boolean ok = FileUtil.verifyZip(updateFile);
+ if (ok)
+ ok = FileUtil.extractZip(updateFile, _context.getBaseDir());
if (ok)
System.out.println("INFO: Update installed");
else
@@ -1132,6 +1136,7 @@ public class Router {
updateFile.deleteOnExit();
}
}
+ // exit whether ok or not
if (System.getProperty("wrapper.version") != null)
System.out.println("INFO: Restarting after update");
else
diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizerRenderer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizerRenderer.java
index 1e3d71fc87..870e647c9a 100644
--- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizerRenderer.java
+++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizerRenderer.java
@@ -111,10 +111,11 @@ class ProfileOrganizerRenderer {
if (isIntegrated) buf.append(", Integrated");
RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer);
if (info != null) {
- buf.append(" (").append(info.getCapabilities());
+ // prevent HTML injection in the caps and version
+ buf.append(" (").append(DataHelper.stripHTML(info.getCapabilities()));
String v = info.getOption("router.version");
if (v != null)
- buf.append(' ').append(v);
+ buf.append(' ').append(DataHelper.stripHTML(v));
buf.append(')');
}
@@ -153,6 +154,9 @@ class ProfileOrganizerRenderer {
buf.append("