diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index a9b1a82878597bcbfe4121d210768ccef001e2c3..46deb635caae5814f7bab78066b08c4dfcf07e0c 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -18,16 +18,20 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; import net.i2p.I2PException; +import net.i2p.client.naming.NamingService; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketManager; import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.data.Base32; import net.i2p.data.Base64; +import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.util.EventDispatcher; @@ -62,7 +66,16 @@ import net.i2p.util.Translate; */ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runnable { - private HashMap addressHelpers = new HashMap(); + /** + * Map of host name to base64 destination for destinations collected + * via address helper links + */ + private final ConcurrentHashMap<String, String> addressHelpers = new ConcurrentHashMap(8); + + /** + * Used to protect actions via http://proxy.i2p/ + */ + private final String _proxyNonce; /** * These are backups if the xxx.ht error page is missing. @@ -130,6 +143,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn "or naming one of them differently.<p>") .getBytes(); + private final static byte[] ERR_AHELPER_NEW = + ("HTTP/1.1 409 New Address\r\n"+ + "Content-Type: text/html; charset=iso-8859-1\r\n"+ + "Cache-control: no-cache\r\n"+ + "\r\n"+ + "<html><body><H1>New Host Name with Address Helper</H1>"+ + "The address helper link you followed is for a new host name that is not in your address book. " + + "You may either save the destination for this host name to your address book, or remember it only until your router restarts. " + + "If you save it to your address book, you will not see this message again. " + + "If you do not wish to visit this host, click the \"back\" button on your browser.") + .getBytes(); + private final static byte[] ERR_BAD_PROTOCOL = ("HTTP/1.1 403 Bad Protocol\r\n"+ "Content-Type: text/html; charset=iso-8859-1\r\n"+ @@ -162,6 +187,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn public I2PTunnelHTTPClient(int localPort, Logging l, I2PSocketManager sockMgr, I2PTunnel tunnel, EventDispatcher notifyThis, long clientId) { super(localPort, l, sockMgr, tunnel, notifyThis, clientId); + _proxyNonce = Long.toString(_context.random().nextLong()); // proxyList = new ArrayList(); setName("HTTP Proxy on " + getTunnel().listenHost + ':' + localPort); @@ -177,6 +203,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn String wwwProxy, EventDispatcher notifyThis, I2PTunnel tunnel) throws IllegalArgumentException { super(localPort, ownDest, l, notifyThis, "HTTP Proxy on " + tunnel.listenHost + ':' + localPort + " #" + (++__clientId), tunnel); + _proxyNonce = Long.toString(_context.random().nextLong()); //proxyList = new ArrayList(); // We won't use outside of i2p if (waitEventValue("openBaseClientResult").equals("error")) { @@ -258,12 +285,14 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn return rv; } + private static final String LOCAL_SERVER = "proxy.i2p"; private static final boolean DEFAULT_GZIP = true; - // all default to false + /** all default to false */ public static final String PROP_REFERER = "i2ptunnel.httpclient.sendReferer"; public static final String PROP_USER_AGENT = "i2ptunnel.httpclient.sendUserAgent"; public static final String PROP_VIA = "i2ptunnel.httpclient.sendVia"; public static final String PROP_JUMP_SERVERS = "i2ptunnel.httpclient.jumpServers"; + public static final String PROP_DISABLE_HELPER = "i2ptunnel.httpclient.disableAddressHelper"; protected void clientConnectionRun(Socket s) { InputStream in = null; @@ -278,7 +307,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn InputReader reader = new InputReader(s.getInputStream()); String line, method = null, protocol = null, host = null, destination = null; StringBuilder newRequest = new StringBuilder(); - int ahelper = 0; + boolean ahelperPresent = false; + boolean ahelperNew = false; + String ahelperKey = null; + String userAgent = null; String authorization = null; while ((line = reader.readLine(method)) != null) { line = line.trim(); @@ -334,6 +366,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn protocol = request.substring(0, pos + 2); request = request.substring(pos + 2); + // "foo.i2p/bar/baz HTTP/1.1", with any i2paddresshelper parameter removed targetRequest = request; // pos is the start of the path @@ -380,21 +413,20 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn destination = host; host = getHostName(destination); line = method + ' ' + request.substring(pos); - } else if (host.toLowerCase().equals("proxy.i2p")) { + } else if (host.toLowerCase().equals(LOCAL_SERVER)) { // so we don't do any naming service lookups destination = host; usingInternalServer = true; } else if (host.toLowerCase().endsWith(".i2p")) { // Destination gets the host name destination = host; - // Host becomes the destination key + // Host becomes the destination's "{b32}.b32.i2p" string, or "i2p" on lookup failure host = getHostName(destination); int pos2; if ((pos2 = request.indexOf("?")) != -1) { // Try to find an address helper in the fragments // and split the request into it's component parts for rebuilding later - String ahelperKey = null; boolean ahelperConflict = false; String fragments = request.substring(pos2 + 1); @@ -404,6 +436,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn String urlEncoding = ""; fragments = fragments.substring(0, pos2); String initialFragments = fragments; + // FIXME split on ';' also fragments = fragments + "&"; String fragment; while(fragments.length() > 0) { @@ -412,35 +445,43 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn fragments = fragments.substring(pos2 + 1); // Fragment looks like addresshelper key - if (fragment.startsWith("i2paddresshelper=")) { + if (fragment.startsWith("i2paddresshelper=") && + !Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) { pos2 = fragment.indexOf("="); ahelperKey = fragment.substring(pos2 + 1); // Key contains data, lets not ignore it if (ahelperKey != null) { + ahelperPresent = true; // ahelperKey will be validated later - - // Host resolvable only with addresshelper - if ( (host == null) || ("i2p".equals(host)) ) - { - // Cannot check, use addresshelper key - addressHelpers.put(destination,ahelperKey); - } else { - // Host resolvable from database, verify addresshelper key - // Silently bypass correct keys, otherwise alert - String destB64 = null; - Destination _dest = _context.namingService().lookup(host); - if (_dest != null) - destB64 = _dest.toBase64(); - if (destB64 != null && !destB64.equals(ahelperKey)) - { + if (host == null || "i2p".equals(host)) { + // Host lookup failed - resolvable only with addresshelper + // Store in local HashMap unless there is conflict + String old = addressHelpers.putIfAbsent(destination.toLowerCase(), ahelperKey); + ahelperNew = old == null; + if ((!ahelperNew) && !old.equals(ahelperKey)) { // Conflict: handle when URL reconstruction done ahelperConflict = true; if (_log.shouldLog(Log.WARN)) - _log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + destB64 + "], specified key [" + ahelperKey + "]."); + _log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + + "], trusted key [" + old + "], specified key [" + ahelperKey + "]."); + } + } else { + // If the host is resolvable from database, verify addresshelper key + // Silently bypass correct keys, otherwise alert + Destination hostDest = _context.namingService().lookup(destination); + if (hostDest != null) { + String destB64 = hostDest.toBase64(); + if (destB64 != null && !destB64.equals(ahelperKey)) { + // Conflict: handle when URL reconstruction done + ahelperConflict = true; + if (_log.shouldLog(Log.WARN)) + _log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + + "], trusted key [" + destB64 + "], specified key [" + ahelperKey + "]."); + } } } - } + } // ahelperKey } else { // Other fragments, just pass along // Append each fragment to urlEncoding @@ -453,11 +494,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn } // Reconstruct the request minus the i2paddresshelper GET var request = uriPath + urlEncoding + " " + protocolVersion; + targetRequest = request; // Did addresshelper key conflict? - if (ahelperConflict) - { - + if (ahelperConflict) { if (out != null) { // convert ahelperKey to b32 String alias = getHostName(ahelperKey); @@ -479,16 +519,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn s.close(); return; } - } + } // end query processing - String addressHelper = (String) addressHelpers.get(destination); - if (addressHelper != null) { - destination = addressHelper; - host = getHostName(destination); - ahelper = 1; - } + String addressHelper = addressHelpers.get(destination); + if (addressHelper != null) + host = getHostName(addressHelper); line = method + " " + request.substring(pos); + // end of (host endsWith(".i2p")) + } else if (host.toLowerCase().equals("localhost") || host.equals("127.0.0.1") || host.startsWith("192.168.")) { // if somebody is trying to get to 192.168.example.com, oh well @@ -584,10 +623,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn line = "Host: " + host; if (_log.shouldLog(Log.INFO)) _log.info(getPrefix(requestId) + "Setting host = " + host); - } else if (lowercaseLine.startsWith("user-agent: ") && - !Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue()) { - line = null; - continue; + } else if (lowercaseLine.startsWith("user-agent: ")) { + // save for deciding whether to offer address book form + userAgent = lowercaseLine.substring(12); + if (!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue()) { + line = null; + continue; + } } else if (lowercaseLine.startsWith("accept")) { // strip the accept-blah headers, as they vary dramatically from // browser to browser @@ -687,15 +729,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix(requestId) + "Destination: " + destination); - // Serve local proxy files (images, css linked from error pages) - // Ignore all the headers - // Allow without authorization - if (usingInternalServer) { - serveLocalFile(out, method, targetRequest); - s.close(); - return; - } - // Authorization if (!authorize(s, requestId, authorization)) { if (_log.shouldLog(Log.WARN)) { @@ -705,19 +738,41 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn _log.warn(getPrefix(requestId) + "Auth required, sending 407"); } out.write(getErrorPage("auth", ERR_AUTH)); + writeFooter(out); s.close(); return; } + // Serve local proxy files (images, css linked from error pages) + // Ignore all the headers + if (usingInternalServer) { + // disable the add form if address helper is disabled + if (targetRequest.startsWith(LOCAL_SERVER + "/add?") && + Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) { + out.write(ERR_HELPER_DISABLED); + } else { + serveLocalFile(out, method, targetRequest, _proxyNonce); + } + s.close(); + return; + } + + // LOOKUP // If the host is "i2p", the getHostName() lookup failed, don't try to // look it up again as the naming service does not do negative caching // so it will be slow. - Destination clientDest; - if ("i2p".equals(host)) + String addressHelper = addressHelpers.get(destination.toLowerCase()); + if (addressHelper != null) { + clientDest = _context.namingService().lookup(addressHelper); + // remove bad entries + if (clientDest == null) + addressHelpers.remove(destination.toLowerCase()); + } else if ("i2p".equals(host)) { clientDest = null; - else + } else { clientDest = _context.namingService().lookup(destination); + } if (clientDest == null) { //l.log("Could not resolve " + destination + "."); @@ -727,7 +782,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn String jumpServers = null; if (usingWWWProxy) header = getErrorPage("dnfp", ERR_DESTINATION_UNKNOWN); - else if(ahelper != 0) + else if (ahelperPresent) header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN); else if (destination.length() == 60 && destination.endsWith(".b32.i2p")) header = getErrorPage("dnf", ERR_DESTINATION_UNKNOWN); @@ -741,7 +796,34 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn s.close(); return; } - String remoteID; + + // Address helper response form + // This will only load once - the second time it won't be "new" + // Don't do this for eepget, which uses a user-agent of "Wget" + if (ahelperNew && "GET".equals(method) && + (userAgent == null || !userAgent.startsWith("Wget")) && + !Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) { + writeHelperSaveForm(out, destination, ahelperKey, protocol + targetRequest); + s.close(); + return; + } + + // Redirect to non-addresshelper URL to not clog the browser address bar + // and not pass the parameter to the eepsite. + // This also prevents the not-found error page from looking bad + if (ahelperPresent) { + String uri = protocol + targetRequest; + int spc = uri.indexOf(" "); + if (spc >= 0) + uri = uri.substring(0, spc); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Auto redirecting to " + uri); + out.write(("HTTP/1.1 301 Address Helper Accepted\r\n"+ + "Location: " + uri + "\r\n"+ + "\r\n").getBytes("UTF-8")); + s.close(); + return; + } Properties opts = new Properties(); //opts.setProperty("i2p.streaming.inactivityTimeout", ""+120*1000); @@ -779,6 +861,38 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn } } + /** @since 0.8.7 */ + private void writeHelperSaveForm(OutputStream out, String destination, String ahelperKey, String targetRequest) throws IOException { + if (out == null) + return; + // strip HTTP/1.1 + int protopos = targetRequest.indexOf(" "); + if (protopos >= 0) + targetRequest = targetRequest.substring(0, protopos); + byte[] header = getErrorPage("ahelper-new", ERR_AHELPER_NEW); + out.write(header); + out.write(("<table><tr><td class=\"mediumtags\" align=\"right\">" + _("Host") + "</td><td class=\"mediumtags\">" + destination + "</td></tr>\n" + + "<tr><td class=\"mediumtags\" align=\"right\">" + _("Destination") + "</td><td>" + + "<textarea rows=\"1\" style=\"height: 4em; min-width: 0; min-height: 0;\" cols=\"70\" wrap=\"off\" readonly=\"readonly\" >" + + ahelperKey + "</textarea></td></tr></table>\n" + + "<hr><div class=\"formaction\">"+ + "<form method=\"GET\" action=\"" + targetRequest + "\">" + + "<button type=\"submit\">" + _("Continue to {0} without saving", destination) + "</button>" + + "</form>\n<form method=\"GET\" action=\"http://" + LOCAL_SERVER + "/add\">" + + "<input type=\"hidden\" name=\"host\" value=\"" + destination + "\">\n" + + "<input type=\"hidden\" name=\"dest\" value=\"" + ahelperKey + "\">\n" + + "<input type=\"hidden\" name=\"nonce\" value=\"" + _proxyNonce + "\">\n" + + "<button type=\"submit\" name=\"router\" value=\"router\">" + _("Save {0} to router address book and continue to eepsite", destination) + "</button><br>\n").getBytes("UTF-8")); + if (_context.namingService().getName().equals("BlockfileNamingService")) { + // only blockfile supports multiple books + out.write(("<button type=\"submit\" name=\"master\" value=\"master\">" + _("Save {0} to master address book and continue to eepsite", destination) + "</button><br>\n").getBytes("UTF-8")); + out.write(("<button type=\"submit\" name=\"private\" value=\"private\">" + _("Save {0} to private address book and continue to eepsite", destination) + "</button>\n").getBytes("UTF-8")); + } + out.write(("<input type=\"hidden\" name=\"url\" value=\"" + targetRequest + "\">\n" + + "</form></div></div>").getBytes()); + writeFooter(out); + } + /** * Read the first line unbuffered. * After that, switch to a BufferedReader, unless the method is "POST". @@ -915,7 +1029,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn out.write(errMessage); if (targetRequest != null) { int protopos = targetRequest.indexOf(" "); - String uri = null; + String uri; if (protopos >= 0) uri = targetRequest.substring(0, protopos); else @@ -1022,6 +1136,20 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn "HTTP Proxy local file not found") .getBytes(); + private final static byte[] ERR_ADD = + ("HTTP/1.1 409 Bad\r\n"+ + "Content-Type: text/plain\r\n"+ + "\r\n"+ + "Add to addressbook failed - bad parameters") + .getBytes(); + + private final static byte[] ERR_HELPER_DISABLED = + ("HTTP/1.1 403 Disabled\r\n"+ + "Content-Type: text/plain\r\n"+ + "\r\n"+ + "Address helpers disabled") + .getBytes(); + /** * Very simple web server. * @@ -1043,9 +1171,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn * * @param targetRequest "proxy.i2p/themes/foo.png HTTP/1.1" */ - private static void serveLocalFile(OutputStream out, String method, String targetRequest) { + private static void serveLocalFile(OutputStream out, String method, String targetRequest, String proxyNonce) { + //System.err.println("targetRequest: \"" + targetRequest + "\""); // a home page message for the curious... - if (targetRequest.startsWith("proxy.i2p/ ")) { + if (targetRequest.startsWith(LOCAL_SERVER + "/ ")) { try { out.write(("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nCache-Control: max-age=86400\r\n\r\nI2P HTTP proxy OK").getBytes()); out.flush(); @@ -1053,12 +1182,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn return; } if ((method.equals("GET") || method.equals("HEAD")) && - targetRequest.startsWith("proxy.i2p/themes/") && + targetRequest.startsWith(LOCAL_SERVER + "/themes/") && !targetRequest.contains("..")) { int space = targetRequest.indexOf(' '); String filename = null; try { - filename = targetRequest.substring(17, space); // "proxy.i2p/themes/".length + filename = targetRequest.substring(LOCAL_SERVER.length() + 8, space); // "/themes/".length } catch (IndexOutOfBoundsException ioobe) {} // theme hack if (filename.startsWith("console/default/")) @@ -1081,9 +1210,65 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn out.write(type.getBytes()); out.write("\r\nCache-Control: max-age=86400\r\n\r\n".getBytes()); FileUtil.readFile(filename, themesDir.getAbsolutePath(), out); + } catch (IOException ioe) {} + return; + } + } + + // Add to addressbook (form submit) + // Parameters are url, host, dest, nonce, and master | router | private. + // Do the add and redirect. + if (targetRequest.startsWith(LOCAL_SERVER + "/add?")) { + int spc = targetRequest.indexOf(' '); + String query = targetRequest.substring(LOCAL_SERVER.length() + 5, spc); // "/add?".length() + Map<String, String> opts = new HashMap(8); + StringTokenizer tok = new StringTokenizer(query, "=&;"); + while (tok.hasMoreTokens()) { + String k = tok.nextToken(); + if (!tok.hasMoreTokens()) + break; + String v = tok.nextToken(); + opts.put(decode(k), decode(v)); + } + + String url = opts.get("url"); + String host = opts.get("host"); + String b64Dest = opts.get("dest"); + String nonce = opts.get("nonce"); + String book = "privatehosts.txt"; + if (opts.get("master") != null) + book = "userhosts.txt"; + else if (opts.get("router") != null) + book = "hosts.txt"; + Destination dest = null; + if (b64Dest != null) { + try { + dest = new Destination(b64Dest); + } catch (DataFormatException dfe) { + System.err.println("Bad dest to save?" + b64Dest); + } + } + //System.err.println("url : \"" + url + "\""); + //System.err.println("host : \"" + host + "\""); + //System.err.println("b64dest : \"" + b64Dest + "\""); + //System.err.println("book : \"" + book + "\""); + //System.err.println("nonce : \"" + nonce + "\""); + if (proxyNonce.equals(nonce) && url != null && host != null && dest != null) { + try { + NamingService ns = I2PAppContext.getGlobalContext().namingService(); + Properties nsOptions = new Properties(); + nsOptions.setProperty("list", book); + nsOptions.setProperty("s", _("Added via address helper")); + boolean success = ns.put(host, dest, nsOptions); + writeRedirectPage(out, success, host, book, url); return; } catch (IOException ioe) {} } + try { + out.write(ERR_ADD); + out.flush(); + } catch (IOException ioe) {} + return; } try { out.write(ERR_404); @@ -1091,16 +1276,68 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn } catch (IOException ioe) {} } + /** @since 0.8.7 */ + private static void writeRedirectPage(OutputStream out, boolean success, String host, String book, String url) throws IOException { + out.write(("HTTP/1.1 200 OK\r\n"+ + "Content-Type: text/html; charset=UTF-8\r\n"+ + "\r\n"+ + "<html><head>"+ + "<title>" + _("Redirecting to {0}", host) + "</title>\n" + + "<link rel=\"shortcut icon\" href=\"http://proxy.i2p/themes/console/images/favicon.ico\" >\n" + + "<link href=\"http://proxy.i2p/themes/console/default/console.css\" rel=\"stylesheet\" type=\"text/css\" >\n" + + "<meta http-equiv=\"Refresh\" content=\"1; url=" + url + "\">\n" + + "</head><body>\n" + + "<div class=logo>\n" + + "<a href=\"http://127.0.0.1:7657/\" title=\"" + _("Router Console") + "\"><img src=\"http://proxy.i2p/themes/console/images/i2plogo.png\" alt=\"I2P Router Console\" border=\"0\"></a><hr>\n" + + "<a href=\"http://127.0.0.1:7657/config\">" + _("Configuration") + "</a> <a href=\"http://127.0.0.1:7657/help.jsp\">" + _("Help") + "</a> <a href=\"http://127.0.0.1:7657/susidns/index.jsp\">" + _("Addressbook") + "</a>\n" + + "</div>" + + "<div class=warning id=warning>\n" + + "<h3>" + + (success ? + _("Saved {0} to the {1} addressbook, redirecting now.", host, book) : + _("Failed to save {0} to the {1} addressbook, redirecting now.", host, book)) + + "</h3>\n<p><a href=\"" + url + "\">" + + _("Click here if you are not redirected automatically.") + + "</a></p></div>").getBytes("UTF-8")); + writeFooter(out); + out.flush(); + } + + private static String decode(String s) { + if (!s.contains("%")) + return s; + StringBuilder buf = new StringBuilder(s.length()); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c != '%') { + buf.append(c); + } else { + try { + buf.append((char) Integer.parseInt(s.substring(++i, (++i) + 1), 16)); + } catch (IndexOutOfBoundsException ioobe) { + break; + } catch (NumberFormatException nfe) { + break; + } + } + } + return buf.toString(); + } private static final String BUNDLE_NAME = "net.i2p.i2ptunnel.web.messages"; /** lang in routerconsole.lang property, else current locale */ - public static String _(String key) { + protected static String _(String key) { return Translate.getString(key, I2PAppContext.getGlobalContext(), BUNDLE_NAME); } + /** {0} */ + protected static String _(String key, Object o) { + return Translate.getString(key, o, I2PAppContext.getGlobalContext(), BUNDLE_NAME); + } + /** {0} and {1} */ - public static String _(String key, Object o, Object o2) { + protected static String _(String key, Object o, Object o2) { return Translate.getString(key, o, o2, I2PAppContext.getGlobalContext(), BUNDLE_NAME); } diff --git a/history.txt b/history.txt index fd12cab05646d72bbb4466baaa16c30f27799b4f..7fdc3d250bbfd76bd2338bc3c319cf725466c983 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,9 @@ +2011-05-25 zzz + * CPUID: Load 64-bit libcpuid if available + * HTTP Proxy: Address helper refactoring, address book add form + * Naming: B32 fixes + * NetDB: Increase floodfills again + 2011-05-23 zzz * Console: - Disable zh translation in graphs on windows due to font issues diff --git a/installer/resources/proxy/ahelper-new-header.ht b/installer/resources/proxy/ahelper-new-header.ht new file mode 100644 index 0000000000000000000000000000000000000000..b8a1f9500f40f65b1b6eb1d44042f9851cf8d8bc --- /dev/null +++ b/installer/resources/proxy/ahelper-new-header.ht @@ -0,0 +1,25 @@ +HTTP/1.1 409 New Address +Content-Type: text/html; charset=UTF-8 +Cache-control: no-cache +Connection: close +Proxy-Connection: close + +<html><head> +<title>I2P Information: New Host Name</title> +<link rel="shortcut icon" href="http://proxy.i2p/themes/console/images/favicon.ico" > +<link href="http://proxy.i2p/themes/console/default/console.css" rel="stylesheet" type="text/css" > +</head> +<body> +<div class=logo> + <a href="http://127.0.0.1:7657/index.jsp" title="Router Console"><img src="http://proxy.i2p/themes/console/images/i2plogo.png" alt="I2P Router Console" border="0"></a><hr> + <a href="http://127.0.0.1:7657/config.jsp">Configuration</a> <a href="http://127.0.0.1:7657/help.jsp">Help</a> <a href="http://127.0.0.1:7657/susidns/">Addressbook</a> +</div> +<div class=warning id=warning> +<h3>Information: New Host Name with Address Helper</h3> +<p> +The address helper link you followed is for a new host name that is not in your address book. +You may save this host name to your local address book. +If you save it to your address book, you will not see this message again. +If you do not save it, the host name will be forgotten after the next router restart. +If you do not wish to visit this host, click the "back" button on your browser. +</p> diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 4e28a2c8d2676ff98ee569a93ae883ef407d9461..c10128fe6223a59f4fa2320e38c3309487438a87 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 5; + public final static long BUILD = 6; /** for example "-test" */ public final static String EXTRA = "";