X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Forg%2Fibex%2Fnet%2FHTTP.java;h=69f54cd159577b7e93581c4eb55ae9f1a8ea9133;hb=3d9ee8107aed4978ccd3bd48addea79b420af48b;hp=15ba89928f3ed499d85984de9172215a418094a1;hpb=3e60b07ef3168f08e9429462f419a98be5637714;p=org.ibex.net.git diff --git a/src/org/ibex/net/HTTP.java b/src/org/ibex/net/HTTP.java index 15ba899..69f54cd 100644 --- a/src/org/ibex/net/HTTP.java +++ b/src/org/ibex/net/HTTP.java @@ -4,10 +4,7 @@ package org.ibex.net; import java.net.*; import java.io.*; import java.util.*; -import org.ibex.js.*; import org.ibex.util.*; -import org.ibex.plat.*; -import org.ibex.core.*; import org.ibex.crypto.*; /** @@ -16,6 +13,90 @@ import org.ibex.crypto.*; */ public class HTTP { + public static InetAddress originAddr = null; + public static String originHost = null; + + // FIXME: HACK: these shouldn't be set globally + public static String userAgent = "Ibex"; + public static boolean allowRedirects = true; + + // Cookies ////////////////////////////////////////////////////////////////////////////// + + public static class Cookie { + public final String name; + public final String value; + public final String domain; + public final String path; + public final Date expires; + public final boolean secure; + public Cookie(String name, String value, String domain, String path, Date expires, boolean secure) { + this.name = name; + this.value = value; + this.domain = domain; + this.path = path; + this.expires = expires; + this.secure = secure; + } + + // FIXME: this could be much more efficient + // FIXME currently only implements http://wp.netscape.com/newsref/std/cookie_spec.html + public static class Jar { + private Hash h = new Hash(); + public String getCookieHeader(String domain, String path, boolean secure) { + StringBuffer ret = new StringBuffer("Cookie: "); + Enumeration e = h.keys(); + while (e.hasMoreElements()) { + Vec v = (Vec)h.get(e.nextElement()); + Cookie cookie = null; + for(int i=0; i cookie.domain.length())) + cookie = c; + } + if (cookie != null) { + ret.append(cookie.name); + ret.append("="); + ret.append(cookie.value); + ret.append("; "); + } + } + //ret.setLength(ret.length() - 2); + return ret.toString(); + } + public void setCookie(String header, String defaultDomain) { + String name = null; + String value = null; + String domain = defaultDomain; + String path = "/"; + Date expires = null; + boolean secure = false; + StringTokenizer st = new StringTokenizer(header, ";"); + while(st.hasMoreTokens()) { + String s = st.nextToken(); + if (s.indexOf('=') == -1) { + if (s.equals("secure")) secure = true; + continue; + } + String start = s.substring(0, s.indexOf('=')); + String end = s.substring(s.indexOf('=')+1); + if (name == null) { + name = start; + value = end; + continue; + } + //#switch(start.toLowerCase()) + case "domain": domain = end; + case "path": path = end; + case "expires": expires = new Date(end); + //#end + } + if (h.get(name) == null) h.put(name, new Vec()); + ((Vec)h.get(name)).addElement(new Cookie(name, value, domain, path, expires, secure)); + } + } + } // Public Methods //////////////////////////////////////////////////////////////////////////////////////// @@ -23,10 +104,12 @@ public class HTTP { public HTTP(String url, boolean skipResolveCheck) { originalUrl = url; this.skipResolveCheck = skipResolveCheck; } /** Performs an HTTP GET request */ - public InputStream GET() throws IOException { return makeRequest(null, null); } - + public InputStream GET(String referer, Cookie.Jar cookies) throws IOException { + return makeRequest(null, null, referer, cookies); } + /** Performs an HTTP POST request; content is additional headers, blank line, and body */ - public InputStream POST(String contentType, String content) throws IOException { return makeRequest(contentType, content); } + public InputStream POST(String contentType, String content, String referer, Cookie.Jar cookies) throws IOException { + return makeRequest(contentType, content, referer, cookies); } public static class HTTPException extends IOException { public HTTPException(String s) { super(s); } } @@ -64,7 +147,7 @@ public class HTTP { * This method isn't synchronized; however, only one thread can be in the inner synchronized block at a time, and the rest of * the method is protected by in-order one-at-a-time semaphore lock-steps */ - private InputStream makeRequest(String contentType, String content) throws IOException { + private InputStream makeRequest(String contentType, String content, String referer, Cookie.Jar cookies) throws IOException { // Step 1: send the request and establish a semaphore to stop any requests that pipeline after us Semaphore blockOn = null; @@ -72,7 +155,7 @@ public class HTTP { synchronized(this) { try { connect(); - sendRequest(contentType, content); + sendRequest(contentType, content, referer, cookies); } catch (IOException e) { reset(); throw e; @@ -92,13 +175,13 @@ public class HTTP { if (in == null) throw new HTTPException("a previous pipelined call messed up the socket"); - Hashtable h = in == null ? null : parseHeaders(in); + Hashtable h = in == null ? null : parseHeaders(in, cookies); if (h == null) { if (firstRequest) throw new HTTPException("server closed the socket with no response"); // sometimes the server chooses to close the stream between requests reset(); releaseMe.release(); - return makeRequest(contentType, content); + return makeRequest(contentType, content, referer, cookies); } String reply = h.get("STATUSLINE").toString(); @@ -116,7 +199,16 @@ public class HTTP { new HTTPInputStream(in, cl, releaseMe).close(); } releaseMe.release(); - return makeRequest(contentType, content); + return makeRequest(contentType, content, referer, cookies); + + } else if (reply.startsWith("3") && allowRedirects) { + String location = (String)h.get("location"); + if (location == null) throw new HTTPException("Got HTTP " + reply.substring(0, 3) + " but no Location header"); + Log.info(HTTP.class, "redirecting to " + location); + if (content != null) + return new HTTP(location).POST(contentType, content, url, cookies); + else + return new HTTP(location).GET(url, cookies); } else if (reply.startsWith("2")) { if (h.get("HTTP").equals("1.0") && h.get("content-length") == null) @@ -150,7 +242,7 @@ public class HTTP { if (resolvedHosts.get(host) != null) return; // if all scripts are trustworthy (local FS), continue - if (Main.originAddr == null) return; + if (originAddr == null) return; // resolve using DNS try { @@ -158,14 +250,16 @@ public class HTTP { byte[] quadbyte = addr.getAddress(); if ((quadbyte[0] == 10 || (quadbyte[0] == 192 && quadbyte[1] == 168) || - (quadbyte[0] == 172 && (quadbyte[1] & 0xF0) == 16)) && !addr.equals(Main.originAddr)) + (quadbyte[0] == 172 && (quadbyte[1] & 0xF0) == 16)) && !addr.equals(originAddr)) throw new HTTPException("security violation: " + host + " [" + addr.getHostAddress() + "] is in a firewalled netblock"); return; } catch (UnknownHostException uhe) { } + /* if (Platform.detectProxy() == null) throw new HTTPException("could not resolve hostname \"" + host + "\" and no proxy configured"); + */ } @@ -267,6 +361,7 @@ public class HTTP { } /** executes the PAC script and dispatches a call to one of the other attempt methods based on the result */ + /* private Socket attemptPAC(org.ibex.js.JS pacFunc) { if (Log.verbose) Log.info(this, "evaluating PAC script"); String pac = null; @@ -301,7 +396,7 @@ public class HTTP { if (Log.on) Log.info(this, "all PAC results exhausted"); return null; } - + */ // Everything Else //////////////////////////////////////////////////////////////////////////// @@ -341,6 +436,7 @@ public class HTTP { host = temphost; if (Log.verbose) Log.info(this, "creating HTTP object for connection to " + host + ":" + port); + /* Proxy pi = Platform.detectProxy(); OUTER: do { if (pi != null) { @@ -351,13 +447,14 @@ public class HTTP { if (sock == null && pi.socksProxyHost != null) sock = attemptSocksProxy(pi.socksProxyHost, pi.socksProxyPort); } } while (false); + */ proxied = sock != null; if (sock == null) sock = attemptDirect(); if (sock == null) throw new HTTPException("unable to contact host " + host); if (in == null) in = new BufferedInputStream(sock.getInputStream()); } - private void sendRequest(String contentType, String content) throws IOException { + private void sendRequest(String contentType, String content, String referer, Cookie.Jar cookies) throws IOException { PrintWriter pw = new PrintWriter(new OutputStreamWriter(originalUrl.equals("stdio:") ? System.out : sock.getOutputStream())); if (content != null) { @@ -370,11 +467,12 @@ public class HTTP { } else { pw.print("GET " + path + " HTTP/1.1\r\n"); } - - pw.print("User-Agent: Ibex\r\n"); + + if (cookies != null) pw.print(cookies.getCookieHeader(host, path, ssl)); + pw.print("User-Agent: " + userAgent + "\r\n"); pw.print("Accept-encoding: gzip\r\n"); pw.print("Host: " + (host + (port == 80 ? "" : (":" + port))) + "\r\n"); - if (proxied) pw.print("X-RequestOrigin: " + Main.originHost + "\r\n"); + if (proxied) pw.print("X-RequestOrigin: " + originHost + "\r\n"); if (Proxy.Authorization.authorization != null) pw.print("Proxy-Authorization: "+Proxy.Authorization.authorization2+"\r\n"); if (authCache.get(originalUrl) != null) pw.print("Authorization: " + authCache.get(originalUrl) + "\r\n"); @@ -430,11 +528,11 @@ public class HTTP { Proxy.Authorization.authorization2 = "NTLM " + Base64.encode(Proxy.NTLM.type1); return; } - + /* if (!realm.equals("Digest") || Proxy.Authorization.authorization2 == null || !"true".equals(h.get("stale"))) Proxy.Authorization.getPassword(realm, style, sock.getInetAddress().getHostAddress(), Proxy.Authorization.authorization); - + */ if (style.equals("Basic")) { Proxy.Authorization.authorization2 = "Basic " + new String(Base64.encode(Proxy.Authorization.authorization.getBytes("UTF8"))); @@ -572,7 +670,7 @@ public class HTTP { // Misc Helpers /////////////////////////////////////////////////////////////////////////////////// /** reads a set of HTTP headers off of the input stream, returning null if the stream is already at its end */ - private Hashtable parseHeaders(InputStream in) throws IOException { + private Hashtable parseHeaders(InputStream in, Cookie.Jar cookies) throws IOException { Hashtable ret = new Hashtable(); // we can't use a BufferedReader directly on the input stream, since it will buffer past the end of the headers @@ -606,6 +704,7 @@ public class HTTP { String back = s.substring(s.indexOf(':') + 1).trim(); // ugly hack: we never replace a Digest-auth with a Basic-auth (proxy + www) if (front.endsWith("-authenticate") && ret.get(front) != null && !back.equals("Digest")) continue; + if (front.equals("set-cookie")) cookies.setCookie(back, host); ret.put(front, back); } return ret; @@ -664,11 +763,14 @@ public class HTTP { public String socksProxyHost = null; ///< the SOCKS Proxy Host to use public int socksProxyPort = -1; ///< the SOCKS Proxy Port to use public String[] excluded = new String[] { }; ///< hosts to be excluded from proxy use; wildcards permitted - public JS proxyAutoConfigFunction = null; ///< the PAC script + + // ** temporarily disabled so HTTP does not depend on org.ibex.js ** + //public JS proxyAutoConfigFunction = null; ///< the PAC script + public Object proxyAutoConfigFunction = null; ///< the PAC script public static Proxy detectProxyViaManual() { Proxy ret = new Proxy(); - + /* ret.httpProxyHost = Platform.getEnv("http_proxy"); if (ret.httpProxyHost != null) { if (ret.httpProxyHost.startsWith("http://")) ret.httpProxyHost = ret.httpProxyHost.substring(7); @@ -716,9 +818,11 @@ public class HTTP { } if (ret.httpProxyHost == null && ret.socksProxyHost == null) return null; + */ return ret; } - + + /* public static JSScope proxyAutoConfigRootScope = new ProxyAutoConfigRootScope(); public static JS getProxyAutoConfigFunction(String url) { try { @@ -765,7 +869,7 @@ public class HTTP { return null; } } - + */ // Authorization /////////////////////////////////////////////////////////////////////////////////// @@ -775,6 +879,8 @@ public class HTTP { static public String authorization2 = null; static public Semaphore waitingForUser = new Semaphore(); + // FIXME: temporarily disabled so we can use HTTP outside the core + /* public static synchronized void getPassword(final String realm, final String style, final String proxyIP, String oldAuth) throws IOException { @@ -799,17 +905,18 @@ public class HTTP { waitingForUser.block(); if (Log.on) Log.info(Authorization.class, "got proxy authorization info; re-attempting connection"); } + */ } // ProxyAutoConfigRootJSScope //////////////////////////////////////////////////////////////////// - + /* public static class ProxyAutoConfigRootScope extends JSScope.Global { public ProxyAutoConfigRootScope() { super(); } public Object get(Object name) throws JSExn { - //#switch(name) + // #switch(name) case "isPlainHostName": return METHOD; case "dnsDomainIs": return METHOD; case "localHostOrDomainIs": return METHOD; @@ -823,7 +930,7 @@ public class HTTP { case "dateRange": return METHOD; case "timeRange": return METHOD; case "ProxyConfig": return ProxyConfig; - //#end + // #end return super.get(name); } @@ -836,7 +943,7 @@ public class HTTP { }; public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { - //#switch(method) + // #switch(method) case "isPlainHostName": return (a0.toString().indexOf('.') == -1) ? Boolean.TRUE : Boolean.FALSE; case "dnsDomainIs": return (a0.toString().endsWith(a1.toString())) ? Boolean.TRUE : Boolean.FALSE; case "localHostOrDomainIs": @@ -904,7 +1011,7 @@ public class HTTP { case "dateRange": throw new JSExn("Ibex does not support dateRange() in PAC scripts"); case "timeRange": throw new JSExn("Ibex does not support timeRange() in PAC scripts"); - //#end + // #end return super.callMethod(method, a0, a1, a2, rest, nargs); } private static boolean match(String[] arr, String s, int index) { @@ -917,7 +1024,7 @@ public class HTTP { } public static String[] days = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; } - + */ /** * An implementation of Microsoft's proprietary NTLM authentication protocol. This code was derived from Eric