From c393e70ca9793d36631d80034e606c1e6865e355 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 14 Nov 2009 15:05:44 +0000 Subject: [PATCH] * HTTP Proxy: - Add support for error page translations - Add support for external pages for all errors - Fix lack of \r in error page headers - HTML transitional fixes - Cleanups --- .../i2p/i2ptunnel/I2PTunnelHTTPClient.java | 150 +++++++++++------- build.xml | 4 +- core/java/src/net/i2p/util/FileUtil.java | 5 + history.txt | 10 ++ .../{ => proxy}/ahelper-conflict-header.ht | 12 +- installer/resources/proxy/denied-header.ht | 20 +++ installer/resources/{ => proxy}/dnf-header.ht | 8 +- .../resources/{ => proxy}/dnfb-header.ht | 8 +- .../resources/{ => proxy}/dnfh-header.ht | 8 +- .../resources/{ => proxy}/dnfp-header.ht | 8 +- installer/resources/proxy/localhost-header.ht | 23 +++ installer/resources/proxy/noproxy-header.ht | 21 +++ installer/resources/proxy/protocol-header.ht | 21 +++ .../src/net/i2p/router/RouterVersion.java | 2 +- 14 files changed, 219 insertions(+), 81 deletions(-) rename installer/resources/{ => proxy}/ahelper-conflict-header.ht (85%) create mode 100644 installer/resources/proxy/denied-header.ht rename installer/resources/{ => proxy}/dnf-header.ht (84%) rename installer/resources/{ => proxy}/dnfb-header.ht (85%) rename installer/resources/{ => proxy}/dnfh-header.ht (88%) rename installer/resources/{ => proxy}/dnfp-header.ht (87%) create mode 100644 installer/resources/proxy/localhost-header.ht create mode 100644 installer/resources/proxy/noproxy-header.ht create mode 100644 installer/resources/proxy/protocol-header.ht diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index 1916b415a..b729b659b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -3,8 +3,10 @@ */ package net.i2p.i2ptunnel; +import java.io.ByteArrayOutputStream; import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -15,6 +17,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Properties; import java.util.StringTokenizer; @@ -55,6 +58,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable private HashMap addressHelpers = new HashMap(); + /** + * These are backups if the xxx.ht error page is missing. + */ + private final static byte[] ERR_REQUEST_DENIED = ("HTTP/1.1 403 Access Denied\r\n"+ "Content-Type: text/html; charset=iso-8859-1\r\n"+ @@ -77,6 +84,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable "Could not find the following Destination:

") .getBytes(); +/***** private final static byte[] ERR_TIMEOUT = ("HTTP/1.1 504 Gateway Timeout\r\n"+ "Content-Type: text/html; charset=iso-8859-1\r\n"+ @@ -88,6 +96,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable "destination may have issues. Could not get a response from "+ "the following Destination:

") .getBytes(); +*****/ private final static byte[] ERR_NO_OUTPROXY = ("HTTP/1.1 503 Service Unavailable\r\n"+ @@ -108,11 +117,11 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable "The addresshelper link you followed specifies a different destination key "+ "than a host entry in your host database. "+ "Someone could be trying to impersonate another eepsite, "+ - "or people have given two eepsites identical names.

"+ + "or people have given two eepsites identical names.

"+ "You can resolve the conflict by considering which key you trust, "+ "and either discarding the addresshelper link, "+ "discarding the host entry from your host database, "+ - "or naming one of them differently.

") + "or naming one of them differently.

") .getBytes(); private final static byte[] ERR_BAD_PROTOCOL = @@ -376,22 +385,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable // Did addresshelper key conflict? if (ahelperConflict) { - String str; - byte[] header; - str = FileUtil.readTextFile((new File(_errorDir, "ahelper-conflict-header.ht")).getAbsolutePath(), 100, true); - if (str != null) header = str.getBytes(); - else header = ERR_AHELPER_CONFLICT; if (out != null) { + // Fixme untranslated long alias = I2PAppContext.getGlobalContext().random().nextLong(); String trustedURL = protocol + uriPath + urlEncoding; String conflictURL = protocol + alias + ".i2p/?" + initialFragments; + byte[] header = getErrorPage("ahelper-conflict", ERR_AHELPER_CONFLICT); out.write(header); - out.write(("To visit the destination in your host database, click here. To visit the conflicting addresshelper link by temporarily giving it a random alias, click here.

").getBytes()); - out.write("

I2P HTTP Proxy Server
Generated on: ".getBytes()); - out.write(new Date().toString().getBytes()); - out.write("

\n".getBytes()); - out.flush(); + out.write(("To visit the destination in your host database, click here. To visit the conflicting addresshelper link by temporarily giving it a random alias, click here.

").getBytes()); + writeFooter(out); } s.close(); return; @@ -408,11 +411,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable line = method + " " + request.substring(pos); } else if (host.toLowerCase().equals("localhost") || host.equals("127.0.0.1")) { if (out != null) { - out.write(ERR_LOCALHOST); - out.write("

Generated on: ".getBytes()); - out.write(new Date().toString().getBytes()); - out.write("\n".getBytes()); - out.flush(); + out.write(getErrorPage("localhost", ERR_LOCALHOST)); + writeFooter(out); } s.close(); return; @@ -430,11 +430,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable _log.warn(getPrefix(requestId) + "Host wants to be outproxied, but we dont have any!"); l.log("No HTTP outproxy found for the request."); if (out != null) { - out.write(ERR_NO_OUTPROXY); - out.write("

Generated on: ".getBytes()); - out.write(new Date().toString().getBytes()); - out.write("\n".getBytes()); - out.flush(); + out.write(getErrorPage("noproxy", ERR_NO_OUTPROXY)); + writeFooter(out); } s.close(); return; @@ -449,11 +446,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable if (pos < 0) { l.log("Invalid request url [" + request + "]"); if (out != null) { - out.write(ERR_REQUEST_DENIED); - out.write("

Generated on: ".getBytes()); - out.write(new Date().toString().getBytes()); - out.write("\n".getBytes()); - out.flush(); + out.write(getErrorPage("denied", ERR_REQUEST_DENIED)); + writeFooter(out); } s.close(); return; @@ -540,13 +534,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable l.log("No HTTP method found in the request."); if (out != null) { if ("http://".equalsIgnoreCase(protocol)) - out.write(ERR_REQUEST_DENIED); + out.write(getErrorPage("denied", ERR_REQUEST_DENIED)); else - out.write(ERR_BAD_PROTOCOL); - out.write("

Generated on: ".getBytes()); - out.write(new Date().toString().getBytes()); - out.write("\n".getBytes()); - out.flush(); + out.write(getErrorPage("protocol", ERR_BAD_PROTOCOL)); + writeFooter(out); } s.close(); return; @@ -568,23 +559,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable //l.log("Could not resolve " + destination + "."); if (_log.shouldLog(Log.WARN)) _log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest); - String str; byte[] header; boolean showAddrHelper = false; if (usingWWWProxy) - str = FileUtil.readTextFile((new File(_errorDir, "dnfp-header.ht")).getAbsolutePath(), 100, true); + header = getErrorPage("dnfp", ERR_DESTINATION_UNKNOWN); else if(ahelper != 0) - str = FileUtil.readTextFile((new File(_errorDir, "dnfb-header.ht")).getAbsolutePath(), 100, true); + header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN); else if (destination.length() == 60 && destination.endsWith(".b32.i2p")) - str = FileUtil.readTextFile((new File(_errorDir, "dnf-header.ht")).getAbsolutePath(), 100, true); + header = getErrorPage("dnf", ERR_DESTINATION_UNKNOWN); else { - str = FileUtil.readTextFile((new File(_errorDir, "dnfh-header.ht")).getAbsolutePath(), 100, true); + header = getErrorPage("dnfh", ERR_DESTINATION_UNKNOWN); showAddrHelper = true; } - if (str != null) - header = str.getBytes(); - else - header = ERR_DESTINATION_UNKNOWN; writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination, showAddrHelper); s.close(); return; @@ -658,6 +644,64 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable } } + /** + * foo => errordir/foo-header_xx.ht for lang xx, or errordir/foo-header.ht, + * or the backup byte array on fail. + * + * .ht files must be UTF-8 encoded and use \r\n terminators so the + * HTTP headers are conformant. + * We can't use FileUtil.readFile() because it strips \r + * + * @return non-null + */ + private byte[] getErrorPage(String base, byte[] backup) { + return getErrorPage(getTunnel().getContext(), base, backup); + } + + private static byte[] getErrorPage(I2PAppContext ctx, String base, byte[] backup) { + File errorDir = new File(ctx.getBaseDir(), "docs"); + String lang = ctx.getProperty("routerconsole.lang", Locale.getDefault().getLanguage()); + if (lang != null && lang.length() > 0 && !lang.equals("en")) { + File file = new File(errorDir, base + "-header_" + lang + ".ht"); + try { + return readFile(file); + } catch (IOException ioe) { + // try the english version now + } + } + File file = new File(errorDir, base + "-header.ht"); + try { + return readFile(file); + } catch (IOException ioe) { + return backup; + } + } + + private static byte[] readFile(File file) throws IOException { + FileInputStream fis = null; + byte[] buf = new byte[512]; + ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); + try { + int len = 0; + fis = new FileInputStream(file); + while ((len = fis.read(buf)) > 0) { + baos.write(buf, 0, len); + } + return baos.toByteArray(); + } finally { + try { if (fis != null) fis.close(); } catch (IOException foo) {} + } + // we won't ever get here + } + + private static void writeFooter(OutputStream out) throws IOException { + // the css is hiding this div for now, but we'll keep it here anyway + out.write("

I2P HTTP Proxy Server
Generated on: ".getBytes()); + out.write(new Date().toString().getBytes()); + out.write("

\n".getBytes()); + out.flush(); + } + private static class OnTimeout implements Runnable { private Socket _socket; private OutputStream _out; @@ -705,9 +749,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable out.write("\">http://".getBytes()); out.write(uri.getBytes()); out.write("".getBytes()); - if (usingWWWProxy) out.write(("
WWW proxy: " + wwwProxy).getBytes()); + if (usingWWWProxy) out.write(("
WWW proxy: " + wwwProxy).getBytes()); if (showAddrHelper) { - out.write("

Click a link below to look for an address helper by using a \"jump\" service:
".getBytes()); + // Fixme untranslated + out.write("

Click a link below to look for an address helper by using a \"jump\" service:
".getBytes()); for (int i = 0; i < jumpServers.length; i++) { // Skip jump servers we don't know String jumphost = jumpServers[i].substring(7); // "http://" @@ -719,7 +764,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable continue; } - out.write("
".getBytes()); @@ -729,10 +774,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable } } } - out.write("

I2P HTTP Proxy Server
Generated on: ".getBytes()); - out.write(new Date().toString().getBytes()); - out.write("

\n".getBytes()); - out.flush(); + out.write("".getBytes()); + writeFooter(out); } } @@ -744,16 +787,11 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable // _log.warn(getPrefix(requestId) + "Error sending to " + wwwProxy + " (proxy? " + usingWWWProxy + ", request: " + targetRequest, ex); if (out != null) { try { - String str; byte[] header; if (usingWWWProxy) - str = FileUtil.readTextFile((new File(_errorDir, "dnfp-header.ht")).getAbsolutePath(), 100, true); + header = getErrorPage(I2PAppContext.getGlobalContext(), "dnfp", ERR_DESTINATION_UNKNOWN); else - str = FileUtil.readTextFile((new File(_errorDir, "dnf-header.ht")).getAbsolutePath(), 100, true); - if (str != null) - header = str.getBytes(); - else - header = ERR_DESTINATION_UNKNOWN; + header = getErrorPage(I2PAppContext.getGlobalContext(), "dnf", ERR_DESTINATION_UNKNOWN); writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy, false); } catch (IOException ioe) { // static diff --git a/build.xml b/build.xml index d6af47fdd..804bb8326 100644 --- a/build.xml +++ b/build.xml @@ -333,7 +333,7 @@ --> - + @@ -372,7 +372,7 @@ - + diff --git a/core/java/src/net/i2p/util/FileUtil.java b/core/java/src/net/i2p/util/FileUtil.java index f35898139..fda506641 100644 --- a/core/java/src/net/i2p/util/FileUtil.java +++ b/core/java/src/net/i2p/util/FileUtil.java @@ -196,9 +196,14 @@ public class FileUtil { * Read in the last few lines of a (newline delimited) textfile, or null if * the file doesn't exist. * + * Warning - this inefficiently allocates a StringBuilder of size maxNumLines*80, + * so don't make it too big. + * Warning - converts \r\n to \n + * * @param startAtBeginning if true, read the first maxNumLines, otherwise read * the last maxNumLines * @param maxNumLines max number of lines (or -1 for unlimited) + * @return string or null; does not throw IOException. * */ public static String readTextFile(String filename, int maxNumLines, boolean startAtBeginning) { diff --git a/history.txt b/history.txt index 57955b704..63565eb79 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,13 @@ +2009-11-14 zzz + * HTTP Proxy: + - Add support for error page translations + - Add support for external pages for all errors + - Fix lack of \r in error page headers + - HTML transitional fixes + - Cleanups + * UDP PeerTestManager: Throw in some synchronization to + try to fix stuck tests + 2009-11-11 zzz * Console: Some colon cleansing * FloodfillPeerSelector: Adjustments diff --git a/installer/resources/ahelper-conflict-header.ht b/installer/resources/proxy/ahelper-conflict-header.ht similarity index 85% rename from installer/resources/ahelper-conflict-header.ht rename to installer/resources/proxy/ahelper-conflict-header.ht index 41ae9ac47..670a64693 100644 --- a/installer/resources/ahelper-conflict-header.ht +++ b/installer/resources/proxy/ahelper-conflict-header.ht @@ -1,17 +1,17 @@ HTTP/1.1 409 Conflict -Content-Type: text/html; charset=iso-8859-1 +Content-Type: text/html; charset=UTF-8 Cache-control: no-cache Connection: close Proxy-Connection: close I2P Warning: Destination key conflict - - + +
@@ -20,9 +20,9 @@ The addresshelper link you followed specifies a different destination key than a host entry in your host database. Someone could be trying to impersonate another eepsite, or people have given two eepsites identical names. -

+

You can resolve the conflict by considering which key you trust, and either discarding the addresshelper link, discarding the host entry from your host database, or naming one of them differently. -

+

diff --git a/installer/resources/proxy/denied-header.ht b/installer/resources/proxy/denied-header.ht new file mode 100644 index 000000000..65c042e80 --- /dev/null +++ b/installer/resources/proxy/denied-header.ht @@ -0,0 +1,20 @@ +HTTP/1.1 403 Request Denied +Content-Type: text/html; charset=UTF-8 +Cache-control: no-cache +Connection: close +Proxy-Connection: close + + +I2P Warning: Request Denied + + + + +

+
+

Warning: Request Denied

+You attempted to connect to a non-I2P website or location. +
diff --git a/installer/resources/dnf-header.ht b/installer/resources/proxy/dnf-header.ht similarity index 84% rename from installer/resources/dnf-header.ht rename to installer/resources/proxy/dnf-header.ht index d3adc8261..bb00373a0 100644 --- a/installer/resources/dnf-header.ht +++ b/installer/resources/proxy/dnf-header.ht @@ -1,17 +1,17 @@ HTTP/1.1 504 Gateway Timeout -Content-Type: text/html; charset=iso-8859-1 +Content-Type: text/html; charset=UTF-8 Cache-control: no-cache Connection: close Proxy-Connection: close I2P Warning: Eepsite not reachable - - + +
diff --git a/installer/resources/dnfb-header.ht b/installer/resources/proxy/dnfb-header.ht similarity index 85% rename from installer/resources/dnfb-header.ht rename to installer/resources/proxy/dnfb-header.ht index ec17ebfa3..d16ab75bd 100644 --- a/installer/resources/dnfb-header.ht +++ b/installer/resources/proxy/dnfb-header.ht @@ -1,17 +1,17 @@ HTTP/1.1 400 Destination Not Found -Content-Type: text/html; charset=iso-8859-1 +Content-Type: text/html; charset=UTF-8 Cache-control: no-cache Connection: close Proxy-Connection: close I2P Warning: Invalid eepsite destination - - + +
diff --git a/installer/resources/dnfh-header.ht b/installer/resources/proxy/dnfh-header.ht similarity index 88% rename from installer/resources/dnfh-header.ht rename to installer/resources/proxy/dnfh-header.ht index b387727af..7ff802b57 100644 --- a/installer/resources/dnfh-header.ht +++ b/installer/resources/proxy/dnfh-header.ht @@ -1,17 +1,17 @@ HTTP/1.1 404 Domain Not Found -Content-Type: text/html; charset=iso-8859-1 +Content-Type: text/html; charset=UTF-8 Cache-control: no-cache Connection: close Proxy-Connection: close I2P Warning: Eepsite unknown - - + +
diff --git a/installer/resources/dnfp-header.ht b/installer/resources/proxy/dnfp-header.ht similarity index 87% rename from installer/resources/dnfp-header.ht rename to installer/resources/proxy/dnfp-header.ht index 1cea2df69..9f627bb8d 100644 --- a/installer/resources/dnfp-header.ht +++ b/installer/resources/proxy/dnfp-header.ht @@ -1,17 +1,17 @@ HTTP/1.1 504 Gateway Timeout -Content-Type: text/html; charset=iso-8859-1 +Content-Type: text/html; charset=UTF-8 Cache-control: no-cache Connection: close Proxy-Connection: close I2P Warning: Outproxy Not Found - - + +
diff --git a/installer/resources/proxy/localhost-header.ht b/installer/resources/proxy/localhost-header.ht new file mode 100644 index 000000000..cbb2715cb --- /dev/null +++ b/installer/resources/proxy/localhost-header.ht @@ -0,0 +1,23 @@ +HTTP/1.1 403 Access Denied +Content-Type: text/html; charset=UTF-8 +Cache-control: no-cache +Connection: close +Proxy-Connection: close + + +I2P Warning: Request Denied + + + + + +
+

Warning: Localhost Access

+Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations. +
diff --git a/installer/resources/proxy/noproxy-header.ht b/installer/resources/proxy/noproxy-header.ht new file mode 100644 index 000000000..0459c2844 --- /dev/null +++ b/installer/resources/proxy/noproxy-header.ht @@ -0,0 +1,21 @@ +HTTP/1.1 503 Service Unavailable +Content-Type: text/html; charset=UTF-8 +Cache-control: no-cache +Connection: close +Proxy-Connection: close + + +I2P Warning: No outproxy configured + + + + + +
+

Warning: No Outproxy Configured

+Your request was for a site outside of I2P, but you have no +HTTP outproxy configured. Please configure an outproxy in I2PTunnel. +
diff --git a/installer/resources/proxy/protocol-header.ht b/installer/resources/proxy/protocol-header.ht new file mode 100644 index 000000000..12474eb49 --- /dev/null +++ b/installer/resources/proxy/protocol-header.ht @@ -0,0 +1,21 @@ +HTTP/1.1 403 Bad Protocol +Content-Type: text/html; charset=UTF-8 +Cache-control: no-cache +Connection: close +Proxy-Connection: close + + +I2P Warning: Non-HTTP Protocol + + + + + +
+

Warning: Non-HTTP Protocol

+The request uses a bad protocol. +The I2P HTTP Proxy supports http:// requests ONLY. Other protocols such as https:// and ftp:// are not allowed. +
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index ac1036698..32a2083be 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 = 16; + public final static long BUILD = 17; /** for example "-test" */ public final static String EXTRA = ""; public final static String FULL_VERSION = VERSION + "-" + BUILD + EXTRA;