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.*;
/**
*/
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<v.size(); i++) {
+ Cookie c = (Cookie)v.elementAt(i);
+ if (domain.endsWith(c.domain) &&
+ (c.path == null || path.startsWith(c.path)) &&
+ (cookie == null || c.domain.length() > 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 ////////////////////////////////////////////////////////////////////////////////////////
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); } }
* 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;
synchronized(this) {
try {
connect();
- sendRequest(contentType, content);
+ sendRequest(contentType, content, referer, cookies);
} catch (IOException e) {
reset();
throw e;
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();
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)
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 {
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");
+ */
}
}
/** 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;
if (Log.on) Log.info(this, "all PAC results exhausted");
return null;
}
-
+ */
// Everything Else ////////////////////////////////////////////////////////////////////////////
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) {
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) {
} 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");
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")));
// 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
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;
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);
}
if (ret.httpProxyHost == null && ret.socksProxyHost == null) return null;
+ */
return ret;
}
-
+
+ /*
public static JSScope proxyAutoConfigRootScope = new ProxyAutoConfigRootScope();
public static JS getProxyAutoConfigFunction(String url) {
try {
return null;
}
}
-
+ */
// Authorization ///////////////////////////////////////////////////////////////////////////////////
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 {
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;
case "dateRange": return METHOD;
case "timeRange": return METHOD;
case "ProxyConfig": return ProxyConfig;
- //#end
+ // #end
return super.get(name);
}
};
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":
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) {
}
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