-// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
+// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]package org.xwt;
package org.xwt;
import java.net.*;
/** the path (URI) to retrieve on the server */
String path = null;
- /** the socket; created lazily */
+ /** the socket */
Socket sock = null;
/** the socket's inputstream */
/** the content-length of the data being recieved */
int contentLength = 0;
- /** true iff a proxy should be used */
- boolean proxy = false;
-
/** additional headers to be transmitted */
String headers = "";
- public HTTP(String url) throws MalformedURLException, IOException {
- if (url.startsWith("https:")) {
- url = "http" + url.substring(5);
- ssl = true;
- }
- if (!url.startsWith("http:")) throw new IOException("HTTP only supports http/https urls");
+ /** cache for resolveAndCheckIfFirewalled() */
+ static Hashtable resolvedHosts = new Hashtable();
+
+
+ // Constructor ////////////////////////////////////////////////////////////////////////////////////////
+
+ public HTTP(String url) throws MalformedURLException, IOException { this(url, false); }
+ public HTTP(String url, boolean skipResolveCheck) throws MalformedURLException, IOException {
+ if (url.startsWith("https:")) { url = "http" + url.substring(5); ssl = true; }
+ if (!url.startsWith("http:")) throw new HTTPException("HTTP only supports http/https urls");
this.url = new URL(url);
- host = this.url.getHost();
port = this.url.getPort();
path = this.url.getFile();
if (port == -1) port = ssl ? 443 : 80;
+ host = this.url.getHost();
+ if (Log.on) Log.log(this, "creating HTTP object for connection to " + host + ":" + port);
+ addHeader("Host", host); // host header is always sent verbatim
+ if (!skipResolveCheck) host = resolveAndCheckIfFirewalled(host); // might have to use the strict IP if behind a proxy
+
+ ProxyInfo pi = Platform.detectProxy();
+ if (sock == null && pi != null && pi.proxyAutoConfigFunction != null) sock = attemptPAC(pi.proxyAutoConfigFunction);
+ if (sock == null && pi != null && ssl && pi.httpsProxyHost != null) sock = attemptHttpProxy(pi.httpsProxyHost, pi.httpsProxyPort);
+ if (sock == null && pi != null && pi.httpProxyHost != null) sock = attemptHttpProxy(pi.httpProxyHost, pi.httpProxyPort);
+ if (sock == null && pi != null && pi.socksProxyHost != null) sock = attemptSocksProxy(pi.socksProxyHost, pi.socksProxyPort);
+ if (sock == null) sock = attemptDirect();
+ if (sock == null) throw new HTTPException("all socket creation attempts have failed");
+ sock.setTcpNoDelay(true);
+ }
+
+
+ // Safeguarded DNS Resolver ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * resolves the hostname and returns it as a string in the form "x.y.z.w", except for the special case "xmlrpc.xwt.org".
+ * @throws HTTPException if the host falls within a firewalled netblock
+ */
+ private String resolveAndCheckIfFirewalled(String host) throws HTTPException {
+
+ // special case
+ if (host.equals("xmlrpc.xwt.org")) return host;
+
+ // cached
+ if (resolvedHosts.get(host) != null) return (String)resolvedHosts.get(host);
+
+ if (Log.on) Log.log(this, " resolveAndCheckIfFirewalled: resolving " + host);
+
+ // resolve using DNS
try {
InetAddress addr = InetAddress.getByName(host);
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))
- throw new IOException("security violation: " + host + " [" + addr.getHostAddress() + "] is in a firewalled netblock");
- } catch (UnknownHostException uhe) {
- if (Platform.detectProxy() == null) throw new IOException("could not resolve hostname \"" + host + "\" and no proxy configured");
- else if (Log.on) Log.log("could not resolve host " + host + "; assuming that the proxy can resolve it for us");
+ if ((quadbyte[0] == 10 ||
+ (quadbyte[0] == 192 && quadbyte[1] == 168) ||
+ (quadbyte[0] == 172 && (quadbyte[1] & 0xF0) == 16)) && !addr.equals(Main.originAddr))
+ throw new HTTPException("security violation: " + host + " [" + addr.getHostAddress() + "] is in a firewalled netblock");
+ return addr.getHostAddress();
+ } catch (UnknownHostException uhe) { }
+
+ // resolve using xmlrpc.xwt.org
+ if (Platform.detectProxy() == null) throw new HTTPException("could not resolve hostname \"" + host + "\" and no proxy configured");
+ if (Log.on) Log.log(this, " could not resolve host " + host + "; using xmlrpc.xwt.org to ensure security");
+ try {
+ Object ret = new XMLRPC("http://xmlrpc.xwt.org/RPC2/", "dns.resolve").call(new Object[] { host });
+ if (ret == null || !(ret instanceof String)) throw new Exception(" xmlrpc.xwt.org returned non-String: " + ret);
+ resolvedHosts.put(host, ret);
+ return (String)ret;
+ } catch (Throwable e) {
+ throw new HTTPException("exception while attempting to use xmlrpc.xwt.org to resolve " + host + ": " + e);
}
}
- public String getContentType() throws IOException {
- getInputStream();
- return contentType;
- }
- public int getContentLength() throws IOException {
- getInputStream();
- return contentLength;
- }
+ // Methods to attempt socket creation /////////////////////////////////////////////////////////////////
- public void addHeader(String header, String value) throws IOException {
- if (in != null) throw new IOException("attempt to add header after connection has been made");
- headers += header + ": " + value + "\r\n";
+ /** Attempts a direct connection */
+ public Socket attemptDirect() {
+ try {
+ if (Log.on) Log.log(this, "attempting to create unproxied socket to " + host + ":" + port + (ssl ? " [ssl]" : ""));
+ return Platform.getSocket(host, port, ssl, true);
+ } catch (IOException e) {
+ if (Log.on) Log.log(this, "exception in attemptDirect(): " + e);
+ return null;
+ }
}
- private void getSock() throws IOException {
- ProxyInfo pi = Platform.detectProxy();
+ /** Attempts to use an HTTP proxy, employing the CONNECT method if HTTPS is requested */
+ public Socket attemptHttpProxy(String proxyHost, int proxyPort) {
+ try {
+ if (Log.on) Log.log(this, "attempting to create HTTP proxied socket using proxy " + proxyHost + ":" + proxyPort);
+
+ Socket sock = Platform.getSocket(proxyHost, proxyPort, ssl, false);
+ if (!ssl) {
+ path = "http://" + host + ":" + port + path;
+ } else {
+ if (Log.on) Log.log(this, "attempting to create HTTP proxied socket using proxy " + proxyHost + ":" + proxyPort);
+ PrintWriter pw = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
+ BufferedReader br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
+ pw.print("CONNECT " + host + ":" + port + " HTTP/1.0\r\n\r\n");
+ String s = br.readLine();
+ if (s.charAt(9) != '2') throw new HTTPException("proxy refused CONNECT method: \"" + s + "\"");
+ while (br.readLine().length() > 0) { };
+ ((TinySSL)sock).negotiate();
+ }
+ return sock;
- // unproxied
- if (pi == null || (pi.proxyAutoConfigFunction == null && pi.socksProxyHost == null && pi.httpProxyHost == null)) {
- if (Log.on) Log.log(this, "creating unproxied socket to " + host + ":" + port + (ssl ? " [ssl]" : ""));
- sock = Platform.getSocket(host, port, ssl);
- return;
+ } catch (IOException e) {
+ if (Log.on) Log.log(this, "exception in attemptHttpProxy(): " + e);
+ return null;
}
+ }
+
+ /**
+ * Implements SOCKSv4 with v4a DNS extension
+ * @see http://www.socks.nec.com/protocol/socks4.protocol
+ * @see http://www.socks.nec.com/protocol/socks4a.protocol
+ */
+ public Socket attemptSocksProxy(String proxyHost, int proxyPort) {
+
+ // even if host is already a "x.y.z.w" string, we use this to parse it into bytes
+ InetAddress addr = null;
+ try { addr = InetAddress.getByName(host); } catch (Exception e) { }
- // no PAC; simple config
- if (pi.proxyAutoConfigFunction == null) {
- String proxyHost = ssl && pi.httpsProxyHost != null ? pi.httpsProxyHost : pi.httpProxyHost;
- int proxyPort = ssl && pi.httpsProxyHost != null ? pi.httpsProxyPort : pi.httpProxyPort;
- if (Log.on) Log.log(this, "no proxyAutoConfigFunction; using proxy " + proxyHost + ":" + proxyPort);
- sock = Platform.getSocket(proxyHost, proxyPort, ssl);
- proxy = true;
- return;
+ if (Log.on) Log.log(this, "attempting to create SOCKSv4" + (addr == null ? "" : "a") +
+ " proxied socket using proxy " + proxyHost + ":" + proxyPort);
+
+ try {
+ Socket sock = Platform.getSocket(proxyHost, proxyPort, ssl, false);
+
+ DataOutputStream dos = new DataOutputStream(sock.getOutputStream());
+ dos.writeByte(0x04); // SOCKSv4(a)
+ dos.writeByte(0x01); // CONNECT
+ dos.writeShort(port & 0xffff); // port
+ if (addr == null) dos.writeInt(0x00000001); // bogus IP
+ else dos.write(addr.getAddress()); // actual IP
+ dos.writeByte(0x00); // no userid
+ if (addr == null) {
+ PrintWriter pw = new PrintWriter(new OutputStreamWriter(dos));
+ pw.print(host);
+ pw.flush();
+ dos.writeByte(0x00); // hostname null terminator
+ }
+ dos.flush();
+
+ DataInputStream dis = new DataInputStream(sock.getInputStream());
+ dis.readByte(); // reply version
+ byte success = dis.readByte(); // success/fail
+ dis.skip(6); // ip/port
+
+ if ((int)(success & 0xff) == 90) {
+ if (ssl) ((TinySSL)sock).negotiate();
+ return sock;
+ }
+ if (Log.on) Log.log(this, "SOCKS server denied access, code " + (success & 0xff));
+ return null;
+
+ } catch (IOException e) {
+ if (Log.on) Log.log(this, "exception in attemptSocksProxy(): " + e);
+ return null;
}
-
- // PAC
+ }
+
+ /** executes the PAC script and dispatches a call to one of the other attempt methods based on the result */
+ public Socket attemptPAC(Function pacFunc) {
+ if (Log.on) Log.log(this, "evaluating PAC script");
String pac = null;
try {
- Context cx = Context.enter();
- Object obj = pi.proxyAutoConfigFunction.call(cx, ProxyInfo.base, null, new Object[] { url.toString(), host });
- if (Log.on) Log.log(this, "PAC script returned \"" + obj + "\"");
+ Object obj = pacFunc.call(Context.enter(), ProxyInfo.proxyAutoConfigRootScope, null, new Object[] { url.toString(), url.getHost() });
+ if (Log.on) Log.log(this, " PAC script returned \"" + obj + "\"");
pac = obj.toString();
} catch (Throwable e) {
- if (Log.on) Log.log(this, "PAC script threw an exception:");
- if (Log.on) Log.log(this, e);
- throw new IOException("PAC script threw exception " + e);
+ if (Log.on) Log.log(this, "PAC script threw exception " + e);
+ return null;
}
StringTokenizer st = new StringTokenizer(pac, ";", false);
String token = st.nextToken().trim();
if (Log.on) Log.log(this, " trying \"" + token + "\"...");
try {
- if (token.startsWith("DIRECT")) {
- proxy = false;
- sock = Platform.getSocket(host, port, ssl);
- return;
- } else if (token.startsWith("PROXY")) {
- proxy = true;
- sock = Platform.getSocket(token.substring(token.indexOf(' ') + 1, token.indexOf(':')),
- Integer.parseInt(token.substring(token.indexOf(':') + 1)), ssl);
- return;
- } else if (token.startsWith("SOCKS")) {
- // FIXME
- }
+ Socket ret = null;
+ if (token.startsWith("DIRECT"))
+ ret = attemptDirect();
+ else if (token.startsWith("PROXY"))
+ ret = attemptHttpProxy(token.substring(token.indexOf(' ') + 1, token.indexOf(':')),
+ Integer.parseInt(token.substring(token.indexOf(':') + 1)));
+ else if (token.startsWith("SOCKS"))
+ ret = attemptSocksProxy(token.substring(token.indexOf(' ') + 1, token.indexOf(':')),
+ Integer.parseInt(token.substring(token.indexOf(':') + 1)));
+ if (ret != null) return ret;
} catch (Throwable e) {
- if (Log.on) Log.log(this, "attempt at \"" + proxy + "\" failed due to " + e + "; trying next one");
+ if (Log.on) Log.log(this, "attempt at \"" + token + "\" failed due to " + e + "; trying next token");
}
}
- throw new IOException("all proxy options exhausted");
+ if (Log.on) Log.log(this, "all PAC results exhausted");
+ return null;
+ }
+
+
+ // Everything Else ////////////////////////////////////////////////////////////////////////////
+
+ /** returns the content-type of the reply */
+ public String getContentType() throws IOException {
+ getInputStream();
+ return contentType;
+ }
+
+ /** returns the content-length of the reply */
+ public int getContentLength() throws IOException {
+ getInputStream();
+ return contentLength;
+ }
+
+ /** adds a header to the outbound transmission */
+ public void addHeader(String header, String value) throws HTTPException {
+ if (in != null) throw new HTTPException("attempt to add header after connection has been made");
+ headers += header + ": " + value + "\r\n";
}
public OutputStream getOutputStream(int contentLength, String contentType) throws IOException {
if (out != null) return out;
- if (in != null) throw new IOException("attempt to getOutputStream() after getInputStream()");
- getSock();
- sock.setTcpNoDelay(true);
+ if (in != null) throw new HTTPException("attempt to getOutputStream() after getInputStream()");
out = sock.getOutputStream();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
- pw.print("POST " + (proxy ? url.toString() : path) + " HTTP/1.0\r\n");
+ pw.print("POST " + path + " HTTP/1.0\r\n");
pw.print("Host: " + host + "\r\n");
pw.print("User-Agent: XWT\r\n");
pw.print("Content-length: " + contentLength + "\r\n");
if (out != null) {
out.flush();
} else {
- getSock();
- sock.setTcpNoDelay(true);
PrintWriter pw = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
- pw.print("GET " + (proxy ? url.toString() : path) + " HTTP/1.0\r\n");
+ pw.print("GET " + path + " HTTP/1.0\r\n");
pw.print("Host: " + host + "\r\n");
pw.print("User-Agent: XWT\r\n");
pw.print(headers);
int buflen = 0;
while(true) {
int read = in.read();
- if (read == -1) throw new IOException("stream closed while reading headers");
+ if (read == -1) throw new HTTPException("stream closed while reading headers");
buf[buflen++] = (byte)read;
if (buflen >= 4 && buf[buflen - 4] == '\r' && buf[buflen - 3] == '\n' && buf[buflen - 2] == '\r' && buf[buflen - 1] == '\n') break;
if (buflen == buf.length) {
BufferedReader headerReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, buflen)));
String s = headerReader.readLine();
- if (!s.startsWith("HTTP/")) throw new IOException("Expected reply to start with \"HTTP/\"");
+ if (!s.startsWith("HTTP/")) throw new HTTPException("Expected reply to start with \"HTTP/\"");
String reply = s.substring(s.indexOf(' ') + 1);
- if (!reply.startsWith("2")) throw new IOException("HTTP Error: " + reply);
+ if (!reply.startsWith("2")) throw new HTTPException("HTTP Error: " + reply);
while((s = headerReader.readLine()) != null) {
if (s.length() > 15 && s.substring(0, 15).equalsIgnoreCase("content-length: "))
contentLength = Integer.parseInt(s.substring(15));
return in;
}
+
+
+ // HTTPException ///////////////////////////////////////////////////////////////////////////////////
+
+ static class HTTPException extends IOException {
+ public HTTPException(String s) { super(s); }
+ }
+
+
+ // ProxyInfo ///////////////////////////////////////////////////////////////////////////////////
+
public static class ProxyInfo {
public ProxyInfo() { }
public static ProxyInfo detectProxyViaManual() {
try {
- try { InetAddress.getByName("xwt-proxy-httpHost");
- } catch (UnkownHostException unhe) { InetAddress.getByName("xwt-proxy-socksHost"); }
+ // continue iff one of the two resolves
+ try { InetAddress.getByName("xwt-proxy-httpHost"); }
+ catch (UnknownHostException e) { InetAddress.getByName("xwt-proxy-socksHost"); }
if (Log.on) Log.log(Platform.class, "using xwt-proxy-* configuration");
ProxyInfo ret = new ProxyInfo();
} catch (UnknownHostException e) { }
return ret;
} catch (UnknownHostException e) {
- if (Log.on) Log.log(Platform.class, "xwt-proxy-* detection failed due to:");
+ if (Log.on) Log.log(Platform.class, "xwt-proxy-* detection failed due to: " + e);
return null;
}
}
try {
Context cx = Context.enter();
cx.setOptimizationLevel(-1);
- BufferedReader br = new BufferedReader(new InputStreamReader(new HTTP(url).getInputStream()));
+ BufferedReader br = new BufferedReader(new InputStreamReader(new HTTP(url, true).getInputStream()));
String s = null;
String script = "";
while((s = br.readLine()) != null) script += s + "\n";