diff --git a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java index b19a211db..ee698b96e 100644 --- a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java +++ b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java @@ -681,7 +681,11 @@ public class BlockfileNamingService extends DummyNamingService { private static class PropertiesSerializer implements Serializer { public byte[] getBytes(Object o) { Properties p = (Properties) o; - return DataHelper.toProperties(p); + try { + return DataHelper.toProperties(p); + } catch (DataFormatException dfe) { + return null; + } } public Object construct(byte[] b) { diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index 1aa5e9040..eea636286 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -35,6 +35,7 @@ import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.TreeMap; import java.util.zip.Deflater; @@ -102,6 +103,7 @@ public class DataHelper { /** * Write a mapping to the stream, as defined by the I2P data structure spec, * and store it into a Properties object. See readProperties for the format. + * Output is sorted by property name. * Property keys and values must not contain '=' or ';', this is not checked and they are not escaped * Keys and values must be 255 bytes or less, * Formatted length must not exceed 65535 bytes @@ -131,16 +133,46 @@ public class DataHelper { * * Use utf8 = false for RouterAddress (fast, non UTF-8) * Use utf8 = true for SessionConfig (slow, UTF-8) + * @param props source may be null */ public static void writeProperties(OutputStream rawStream, Properties props, boolean utf8) throws DataFormatException, IOException { + writeProperties(rawStream, props, utf8, props != null && !(props instanceof OrderedProperties)); + } + + /** + * Writes the props to the stream, sorted by property name if sort == true or + * if props is an OrderedProperties. + * See readProperties() for the format. + * Property keys and values must not contain '=' or ';', this is not checked and they are not escaped + * Keys and values must be 255 bytes or less, + * Formatted length must not exceed 65535 bytes + * @throws DataFormatException if either is too long. + * + * 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) + * @param props source may be null + * @param sort should we sort the properties? (set to false if already sorted, e.g. OrderedProperties) + * @since 0.8.6 + */ + public static void writeProperties(OutputStream rawStream, Properties props, boolean utf8, boolean sort) + throws DataFormatException, IOException { if (props != null) { - OrderedProperties p = new OrderedProperties(); - p.putAll(props); + Properties p; + if (sort) { + p = new OrderedProperties(); + p.putAll(props); + } else { + p = props; + } ByteArrayOutputStream baos = new ByteArrayOutputStream(p.size() * 64); - for (Iterator iter = p.keySet().iterator(); iter.hasNext();) { - String key = (String) iter.next(); - String val = p.getProperty(key); + for (Map.Entry entry : p.entrySet()) { + String key = (String) entry.getKey(); + String val = (String) entry.getValue(); if (utf8) writeStringUTF8(baos, key); else @@ -168,36 +200,36 @@ public class DataHelper { * See readProperties() for the format. * Property keys and values must not contain '=' or ';', this is not checked and they are not escaped * Keys and values must be 255 bytes or less, - * @throws DataFormatException if too long. * Formatted length must not exceed 65535 bytes - * Should throw DataFormatException if too long but it doesn't. + * Strings will be UTF-8 encoded in the byte array. * Warning - confusing method name, Properties is the source. * * @deprecated unused * * @param target returned array as specified in data structure spec - * @param props source + * @param props source may be null * @return new offset + * @throws DataFormatException if any string is over 255 bytes long, or if the total length + * is greater than 65535 bytes. */ @Deprecated public static int toProperties(byte target[], int offset, Properties props) throws DataFormatException, IOException { if (props != null) { OrderedProperties p = new OrderedProperties(); p.putAll(props); - ByteArrayOutputStream baos = new ByteArrayOutputStream(32); - 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); + ByteArrayOutputStream baos = new ByteArrayOutputStream(p.size() * 64); + for (Map.Entry entry : p.entrySet()) { + String key = (String) entry.getKey(); + String val = (String) entry.getValue(); + writeStringUTF8(baos, key); baos.write(EQUAL_BYTES); - writeString(baos, val); + writeStringUTF8(baos, val); baos.write(SEMICOLON_BYTES); } baos.close(); byte propBytes[] = baos.toByteArray(); + if (propBytes.length > 65535) + throw new DataFormatException("Properties too big (65535 max): " + propBytes.length); toLong(target, offset, 2, propBytes.length); offset += 2; System.arraycopy(propBytes, 0, target, offset, propBytes.length); @@ -213,14 +245,12 @@ public class DataHelper { * Reads the props from the byte array and puts them in the Properties target * See readProperties() for the format. * Warning - confusing method name, Properties is the target. - * - * @deprecated unused + * Strings must be UTF-8 encoded in the byte array. * * @param source source * @param target returned Properties * @return new offset */ - @Deprecated public static int fromProperties(byte source[], int offset, Properties target) throws DataFormatException, IOException { int size = (int)fromLong(source, offset, 2); offset += 2; @@ -231,12 +261,12 @@ public class DataHelper { String key = readString(in); int read = read(in, eqBuf); if ((read != eqBuf.length) || (!eq(eqBuf, EQUAL_BYTES))) { - break; + throw new DataFormatException("Bad key"); } String val = readString(in); read = read(in, semiBuf); if ((read != semiBuf.length) || (!eq(semiBuf, SEMICOLON_BYTES))) { - break; + throw new DataFormatException("Bad value"); } target.put(key, val); } @@ -246,24 +276,20 @@ public class DataHelper { /** * Writes the props to returned byte array, not sorted * (unless the opts param is an OrderedProperties) + * Strings will be UTF-8 encoded in the byte array. * See readProperties() for the format. * Property keys and values must not contain '=' or ';', this is not checked and they are not escaped * Keys and values must be 255 bytes or less, * Formatted length must not exceed 65535 bytes * Warning - confusing method name, Properties is the source. * - * @deprecated unused - * - * @throws RuntimeException if either is too long. + * @throws DataFormatException if key, value, or total is too long */ - @Deprecated - public static byte[] toProperties(Properties opts) { + public static byte[] toProperties(Properties opts) throws DataFormatException { try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(2); - writeProperties(baos, opts); + ByteArrayOutputStream baos = new ByteArrayOutputStream(2 + (32 * opts.size())); + writeProperties(baos, opts, true, false); return baos.toByteArray(); - } catch (DataFormatException dfe) { - throw new RuntimeException("Format error writing to memory?! " + dfe.getMessage()); } catch (IOException ioe) { throw new RuntimeException("IO error writing to memory?! " + ioe.getMessage()); } @@ -276,9 +302,9 @@ public class DataHelper { public static String toString(Properties options) { StringBuilder buf = new StringBuilder(); if (options != null) { - for (Iterator iter = options.keySet().iterator(); iter.hasNext();) { - String key = (String) iter.next(); - String val = options.getProperty(key); + for (Map.Entry entry : options.entrySet()) { + String key = (String) entry.getKey(); + String val = (String) entry.getValue(); buf.append("[").append(key).append("] = [").append(val).append("]"); } } else { @@ -353,9 +379,9 @@ public class DataHelper { try { out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8"))); out.println("# NOTE: This I2P config file must use UTF-8 encoding"); - for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) { - String name = (String)iter.next(); - String val = props.getProperty(name); + for (Map.Entry entry : props.entrySet()) { + String name = (String) entry.getKey(); + String val = (String) entry.getValue(); out.println(name + "=" + val); } out.flush(); @@ -720,10 +746,8 @@ public class DataHelper { } /** deprecated - used only in DatabaseLookupMessage */ - @Deprecated public static final byte BOOLEAN_TRUE = 0x1; /** deprecated - used only in DatabaseLookupMessage */ - @Deprecated public static final byte BOOLEAN_FALSE = 0x0; /** @deprecated unused */ @Deprecated diff --git a/core/java/src/net/i2p/util/OrderedProperties.java b/core/java/src/net/i2p/util/OrderedProperties.java index 731f27a85..56323f267 100644 --- a/core/java/src/net/i2p/util/OrderedProperties.java +++ b/core/java/src/net/i2p/util/OrderedProperties.java @@ -40,15 +40,15 @@ public class OrderedProperties extends Properties { } @Override - public Set entrySet() { - TreeSet rv = new TreeSet(new EntryComparator()); + public Set> entrySet() { + TreeSet> rv = new TreeSet(new EntryComparator()); rv.addAll(super.entrySet()); return Collections.unmodifiableSortedSet(rv); } - private static class EntryComparator implements Comparator { - public int compare(Object l, Object r) { - return ((String)((Map.Entry)l).getKey()).compareTo(((String)((Map.Entry)r).getKey())); + private static class EntryComparator implements Comparator { + public int compare(Map.Entry l, Map.Entry r) { + return ((String)l.getKey()).compareTo(((String)r.getKey())); } } }