From 54f0cae2ff3ba36b8c9a7eb1f6ec795948a52ba7 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 24 Feb 2012 17:58:54 +0000 Subject: [PATCH] refactor proxy.i2p server to its own class --- .../i2p/i2ptunnel/I2PTunnelHTTPClient.java | 205 +-------------- .../localServer/LocalHTTPServer.java | 248 ++++++++++++++++++ 2 files changed, 255 insertions(+), 198 deletions(-) create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index b3bce9cbcd..4980425c69 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -36,8 +36,8 @@ import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; +import net.i2p.i2ptunnel.localServer.LocalHTTPServer; import net.i2p.util.EventDispatcher; -import net.i2p.util.FileUtil; import net.i2p.util.Log; import net.i2p.util.PortMapper; import net.i2p.util.Translate; @@ -309,7 +309,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn return rv; } - private static final String LOCAL_SERVER = "proxy.i2p"; + public static final String LOCAL_SERVER = "proxy.i2p"; private static final boolean DEFAULT_GZIP = true; /** all default to false */ public static final String PROP_REFERER = "i2ptunnel.httpclient.sendReferer"; @@ -798,7 +798,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) { out.write(ERR_HELPER_DISABLED); } else { - serveLocalFile(out, method, targetRequest, _proxyNonce); + LocalHTTPServer.serveLocalFile(out, method, targetRequest, _proxyNonce); } s.close(); return; @@ -1046,7 +1046,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn // we won't ever get here } - private static void writeFooter(OutputStream out) throws IOException { + /** + * Public only for LocalHTTPServer, not for general use + */ + public 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()); @@ -1196,20 +1199,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn return protocol.toLowerCase(Locale.US).equals("http://"); } - private final static byte[] ERR_404 = - ("HTTP/1.1 404 Not Found\r\n"+ - "Content-Type: text/plain\r\n"+ - "\r\n"+ - "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"+ @@ -1217,186 +1206,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn "Address helpers disabled") .getBytes(); - /** - * Very simple web server. - * - * Serve local files in the docs/ directory, for CSS and images in - * error pages, using the reserved address proxy.i2p - * (similar to p.p in privoxy). - * This solves the problems with including links to the router console, - * as assuming the router console is at 127.0.0.1 leads to broken - * links if it isn't. - * - * Ignore all request headers (If-Modified-Since, etc.) - * - * There is basic protection here - - * FileUtil.readFile() prevents traversal above the base directory - - * but inproxy/gateway ops would be wise to block proxy.i2p to prevent - * exposing the docs/ directory or perhaps other issues through - * uncaught vulnerabilities. - * Restrict to the /themes/ directory for now. - * - * @param targetRequest "proxy.i2p/themes/foo.png HTTP/1.1" - */ - 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(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(); - } catch (IOException ioe) {} - return; - } - if ((method.equals("GET") || method.equals("HEAD")) && - targetRequest.startsWith(LOCAL_SERVER + "/themes/") && - !targetRequest.contains("..")) { - int space = targetRequest.indexOf(' '); - String filename = null; - try { - filename = targetRequest.substring(LOCAL_SERVER.length() + 8, space); // "/themes/".length - } catch (IndexOutOfBoundsException ioobe) { - return; - } - // theme hack - if (filename.startsWith("console/default/")) - filename = filename.replaceFirst("default", I2PAppContext.getGlobalContext().getProperty("routerconsole.theme", "light")); - File themesDir = new File(_errorDir, "themes"); - File file = new File(themesDir, filename); - if (file.exists() && !file.isDirectory()) { - String type; - if (filename.endsWith(".css")) - type = "text/css"; - else if (filename.endsWith(".ico")) - type = "image/x-icon"; - else if (filename.endsWith(".png")) - type = "image/png"; - else if (filename.endsWith(".jpg")) - type = "image/jpeg"; - else type = "text/html"; - try { - out.write("HTTP/1.1 200 OK\r\nContent-Type: ".getBytes()); - 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 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); - out.flush(); - } 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"+ - ""+ - "" + _("Redirecting to {0}", host) + "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "

" + - "
\n" + - "

" + - (success ? - _("Saved {0} to the {1} addressbook, redirecting now.", host, book) : - _("Failed to save {0} to the {1} addressbook, redirecting now.", host, book)) + - "

\n

" + - _("Click here if you are not redirected automatically.") + - "

").getBytes("UTF-8")); - writeFooter(out); - out.flush(); - } - - /** - * Decode %xx encoding - * @since 0.8.7 - */ - 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 */ diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java new file mode 100644 index 0000000000..0140b609cd --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java @@ -0,0 +1,248 @@ +/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java) + * (c) 2003 - 2004 mihi + */ +package net.i2p.i2ptunnel.localServer; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import net.i2p.I2PAppContext; +import net.i2p.client.naming.NamingService; +import net.i2p.data.DataFormatException; +import net.i2p.data.Destination; +import net.i2p.i2ptunnel.I2PTunnelHTTPClient; +import net.i2p.util.FileUtil; +import net.i2p.util.Log; +import net.i2p.util.Translate; + +/** + * Very simple web server. + * + * Serve local files in the docs/ directory, for CSS and images in + * error pages, using the reserved address proxy.i2p + * (similar to p.p in privoxy). + * This solves the problems with including links to the router console, + * as assuming the router console is at 127.0.0.1 leads to broken + * links if it isn't. + * + * @since 0.7.6, moved from I2PTunnelHTTPClient in 0.9 + */ +public abstract class LocalHTTPServer { + + private final static byte[] ERR_404 = + ("HTTP/1.1 404 Not Found\r\n"+ + "Content-Type: text/plain\r\n"+ + "\r\n"+ + "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(); + + /** + * Very simple web server. + * + * Serve local files in the docs/ directory, for CSS and images in + * error pages, using the reserved address proxy.i2p + * (similar to p.p in privoxy). + * This solves the problems with including links to the router console, + * as assuming the router console is at 127.0.0.1 leads to broken + * links if it isn't. + * + * Ignore all request headers (If-Modified-Since, etc.) + * + * There is basic protection here - + * FileUtil.readFile() prevents traversal above the base directory - + * but inproxy/gateway ops would be wise to block proxy.i2p to prevent + * exposing the docs/ directory or perhaps other issues through + * uncaught vulnerabilities. + * Restrict to the /themes/ directory for now. + * + * @param targetRequest "proxy.i2p/themes/foo.png HTTP/1.1" + */ + public 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(I2PTunnelHTTPClient.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(); + } catch (IOException ioe) {} + return; + } + if ((method.equals("GET") || method.equals("HEAD")) && + targetRequest.startsWith(I2PTunnelHTTPClient.LOCAL_SERVER + "/themes/") && + !targetRequest.contains("..")) { + int space = targetRequest.indexOf(' '); + String filename = null; + try { + filename = targetRequest.substring(I2PTunnelHTTPClient.LOCAL_SERVER.length() + 8, space); // "/themes/".length + } catch (IndexOutOfBoundsException ioobe) { + return; + } + // theme hack + if (filename.startsWith("console/default/")) + filename = filename.replaceFirst("default", I2PAppContext.getGlobalContext().getProperty("routerconsole.theme", "light")); + File themesDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs/themes"); + File file = new File(themesDir, filename); + if (file.exists() && !file.isDirectory()) { + String type; + if (filename.endsWith(".css")) + type = "text/css"; + else if (filename.endsWith(".ico")) + type = "image/x-icon"; + else if (filename.endsWith(".png")) + type = "image/png"; + else if (filename.endsWith(".jpg")) + type = "image/jpeg"; + else type = "text/html"; + try { + out.write("HTTP/1.1 200 OK\r\nContent-Type: ".getBytes()); + 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(I2PTunnelHTTPClient.LOCAL_SERVER + "/add?")) { + int spc = targetRequest.indexOf(' '); + String query = targetRequest.substring(I2PTunnelHTTPClient.LOCAL_SERVER.length() + 5, spc); // "/add?".length() + Map 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); + out.flush(); + } 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"+ + ""+ + "" + _("Redirecting to {0}", host) + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "" + + "
\n" + + "

" + + (success ? + _("Saved {0} to the {1} addressbook, redirecting now.", host, book) : + _("Failed to save {0} to the {1} addressbook, redirecting now.", host, book)) + + "

\n

" + + _("Click here if you are not redirected automatically.") + + "

").getBytes("UTF-8")); + I2PTunnelHTTPClient.writeFooter(out); + out.flush(); + } + + /** + * Decode %xx encoding + * @since 0.8.7 + */ + 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 */ + 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} */ + protected static String _(String key, Object o, Object o2) { + return Translate.getString(key, o, o2, I2PAppContext.getGlobalContext(), BUNDLE_NAME); + } + +}