diff --git a/apps/i2ptunnel/jsp/edit.jsp b/apps/i2ptunnel/jsp/edit.jsp index b58798b20..2da356e9c 100644 --- a/apps/i2ptunnel/jsp/edit.jsp +++ b/apps/i2ptunnel/jsp/edit.jsp @@ -1,3 +1,4 @@ +<%@page pageEncoding="UTF-8"%> <%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean" %><% String tun = request.getParameter("tunnel"); if (tun != null) { diff --git a/apps/i2ptunnel/jsp/index.jsp b/apps/i2ptunnel/jsp/index.jsp index 45a3da56a..87a006f6d 100644 --- a/apps/i2ptunnel/jsp/index.jsp +++ b/apps/i2ptunnel/jsp/index.jsp @@ -1,3 +1,9 @@ +<% + // http://www.crazysquirrel.com/computing/general/form-encoding.jspx + if (request.getCharacterEncoding() == null) + request.setCharacterEncoding("UTF-8"); +%> +<%@page pageEncoding="UTF-8"%> <%@page contentType="text/html" import="net.i2p.i2ptunnel.web.IndexBean"%> @@ -6,13 +12,12 @@ I2P Tunnel Manager - List - + + <% if (indexBean.allowCSS()) { - %> - + %> <% } %> diff --git a/apps/routerconsole/jsp/css.jsp b/apps/routerconsole/jsp/css.jsp index 209cab2b6..422d1329c 100644 --- a/apps/routerconsole/jsp/css.jsp +++ b/apps/routerconsole/jsp/css.jsp @@ -6,6 +6,10 @@ * 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); diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index 0f48efef2..f97811d37 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,16 @@ 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 + * * @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 @@ -523,6 +544,34 @@ public class DataHelper { } } + /** 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 " + + string.length() + " [" + 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 5b1eb6b16..520413620 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 +}