diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index 88d6a11be8c31e1eb1b0bb7778fa41412659737a..7cd0b1c121a3764965b752f8e03aa25e397e2fd8 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -1754,7 +1754,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn } return null; } - /**** + +/**** private static String[] tests = { "", "foo", "foo=bar", "&", "&=&", "===", "&&", "i2paddresshelper=foo", @@ -1784,5 +1785,5 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn System.out.println("Test \"" + tests[i] + "\" no match"); } } - ****/ +****/ } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java index 0f7947de2e1f6b79cec24f974ae880ca9729044c..980ba017d7902da22452750d5912f2cade6e65bd 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java @@ -939,7 +939,13 @@ public class GeneralHelper { buf.append(' '); else space = true; - buf.append(e.getKey()).append('=').append(e.getValue()); + buf.append(e.getKey()).append('='); + String v = e.getValue(); + if (v.contains(" ") || v.contains("\t")) { + buf.append('"').append(v).append('"'); + } else { + buf.append(v); + } } return DataHelper.escapeHTML(buf.toString()); } else { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java index 509d55b984342e98f2347a4d194443ccd79bbc5d..01289da930f44a494063cf7104d8df651dfa5133 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java @@ -4,6 +4,7 @@ import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -1121,14 +1122,10 @@ public class TunnelConfig { if (_privKeyFile != null) config.setProperty(TunnelController.PROP_FILE, _privKeyFile); - if (_customOptions != null) { - StringTokenizer tok = new StringTokenizer(_customOptions); - while (tok.hasMoreTokens()) { - String pair = tok.nextToken(); - int eq = pair.indexOf('='); - if ( (eq <= 0) || (eq >= pair.length()) ) - continue; - String key = pair.substring(0, eq); + if (_customOptions != null && _customOptions.length() > 0) { + Map<String, String> custom = parseCustomOptions(_customOptions); + for (Map.Entry<String, String> e : custom.entrySet()) { + String key = e.getKey(); if (_noShowSet.contains(key)) continue; // leave in for HTTP and Connect so it can get migrated to MD5 @@ -1137,7 +1134,7 @@ public class TunnelConfig { (!TunnelController.TYPE_CONNECT.equals(_type)) && _nonProxyNoShowSet.contains(key)) continue; - String val = pair.substring(eq+1); + String val = e.getValue(); config.setProperty(OPT + key, val); } } @@ -1197,4 +1194,119 @@ public class TunnelConfig { config.setProperty("option.outbound.backupQuantity", Integer.toString(_tunnelBackupQuantityOut)); } } + + /** + * Parse the args submitted in the custom options field. + * + * Modified from EepGet.parseAuthArgs() + * Spaces or tabs separate args. + * Args may be single- or double-quoted if they contain spaces or tabs. + * There is no provision for escaping quotes. + * A quoted string may not contain a quote of any kind. + * Double quotes around values are stripped. + * No quotes allowed for keys. + * Keys without values, e.g. key= or key will be returned with "" for the value. + * + * @param args non-null + * @since 0.9.43 + */ + private static Map<String, String> parseCustomOptions(String args) { + Map<String, String> rv = new HashMap<String, String>(8); + StringBuilder buf = new StringBuilder(32); + boolean isQuoted = false; + String key = null; + for (int i = 0; i < args.length(); i++) { + char c = args.charAt(i); + switch (c) { + case '\'': + case '"': + if (isQuoted) { + // keys never quoted + if (key != null) { + rv.put(key, buf.toString().trim()); + key = null; + } + buf.setLength(0); + } + isQuoted = !isQuoted; + break; + + case ' ': + case '\r': + case '\n': + case '\t': + case ',': + // whitespace - if we're in a quoted section, keep this as part of the quote, + // otherwise use it as a delim + if (isQuoted) { + buf.append(c); + } else { + if (key != null) { + if (key.length() > 0) + rv.put(key, buf.toString().trim()); + key = null; + } else { + String k = buf.toString().trim(); + if (k.length() > 0) + rv.put(k, ""); + } + buf.setLength(0); + } + break; + + case '=': + if (isQuoted || key != null) { + buf.append(c); + } else { + key = buf.toString().trim(); + buf.setLength(0); + } + break; + + default: + buf.append(c); + break; + } + } + if (key != null) { + if (key.length() > 0) + rv.put(key, buf.toString().trim()); + } else { + key = buf.toString().trim(); + if (key.length() > 0) + rv.put(key, ""); + } + return rv; + } + +/**** + private static String[] tests = { + "", "foo", "foo=bar", + "f=b x", "x f=b", + " aaa=bbb ccc=ddd ", + "aaa=bbb ccc=ddd x", + "aaa=bbb ccc=ddd x=", + "a=\"w x y z\" b c= d='1 2 3 4'", + "klsjdf owi=\"w\tx y\tz\"", + "z= aaa= ", + "=", " = ", "=foo", " =fpp ", + "a=\"\", b='', c='xxx\" d='aaa'", + "xx=\"missingquote", + "'zxw=123'", + "'zxw=123", + "'zxw=123' a=b c d e", + "x====", "x====x", + "aaa=b=cc====dddddd====", + }; + + public static void main(String[] args) { + for (int i = 0; i < tests.length; i++) { + Map<String, String> m = parseCustomOptions(tests[i]); + System.out.println("\nTest \"" + tests[i] + '"'); + for (Map.Entry<String, String> e : m.entrySet()) { + System.out.println(" \"" + e.getKey() + "\" = \"" + e.getValue() + '"'); + } + } + } +****/ }