2003/10/01 03:08:31
[org.ibex.core.git] / src / org / xwt / HTTP.java
index a5c7114..ec46105 100644 (file)
@@ -51,12 +51,12 @@ public class HTTP {
      */
     Semaphore okToRecieve = null;
 
+    /** true iff this is the first request to be made on this socket */
+    boolean firstRequest = true;
+
     /** cache for resolveAndCheckIfFirewalled() */
     static Hashtable resolvedHosts = new Hashtable();
 
-    /** if any request encounters an IOException, the entire HTTP connection is invalidated */
-    boolean invalid = false;
-
     /** true iff we are allowed to skip the resolve check (only allowed when we're downloading the PAC script) */
     boolean skipResolveCheck = false;
 
@@ -73,27 +73,26 @@ public class HTTP {
     }
 
     /** Performs an HTTP GET request */
-    public HTTPInputStream GET() throws IOException { return makeRequest(null, null); }
+    public InputStream GET() throws IOException { return makeRequest(null, null); }
 
     /** Performs an HTTP POST request; content is appended to the headers (so it should include a blank line to delimit the beginning of the body) */
-    public HTTPInputStream POST(String contentType, String content) throws IOException { return makeRequest(contentType, content); }
+    public InputStream POST(String contentType, String content) throws IOException { return makeRequest(contentType, content); }
 
     /**
      *  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 HTTPInputStream makeRequest(String contentType, String content) throws IOException {
+    private InputStream makeRequest(String contentType, String content) throws IOException {
 
         // Step 1: send the request and establish a semaphore to stop any requests that pipeline after us
         Semaphore blockOn = null;
         Semaphore releaseMe = null;
         synchronized(this) {
-            if (invalid) throw new HTTPException("connection failed on a previous pipelined call");
             try {
                 connect();
                 sendRequest(contentType, content);
             } catch (IOException e) {
-                invalid = true;
+                reset();
                 throw e;
             }
             blockOn = okToRecieve;
@@ -104,12 +103,18 @@ public class HTTP {
         boolean doRelease = true;
         try {
             if (blockOn != null) blockOn.block();
-            if (invalid) throw new HTTPException("connection failed on a previous pipelined call");
+            
+            // previous call wrecked the socket connection, but we already sent our request, so we can't just retry --
+            // this could cause the server to receive the request twice, which could be bad (think of the case where the
+            // server call causes Amazon.com to ship you an item with one-click purchasing).
+            if (sock == null)
+                throw new HTTPException("a previous pipelined call messed up the socket");
             
             Hashtable h = in == null ? null : parseHeaders(in);
             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
-                in = null; sock = null;
+                reset();
                 releaseMe.release();
                 return makeRequest(contentType, content);
             }
@@ -123,7 +128,7 @@ public class HTTP {
                 
                 if (h.get("HTTP").equals("1.0") && h.get("content-length") == null) {
                     if (Log.on) Log.log(this, "proxy returned an HTTP/1.0 reply with no content-length...");
-                    in = null; sock = null;
+                    reset();
                 } else {
                     int cl = h.get("content-length") == null ? -1 : Integer.parseInt(h.get("content-length").toString());
                     new HTTPInputStream(in, cl, releaseMe).close();
@@ -135,7 +140,8 @@ public class HTTP {
                 if (h.get("HTTP").equals("1.0") && h.get("content-length") == null)
                     throw new HTTPException("XWT does not support HTTP/1.0 servers which fail to return the Content-Length header");
                 int cl = h.get("content-length") == null ? -1 : Integer.parseInt(h.get("content-length").toString());
-                HTTPInputStream ret = new HTTPInputStream(in, cl, releaseMe);
+                InputStream ret = new HTTPInputStream(in, cl, releaseMe);
+                if ("gzip".equals(h.get("content-encoding"))) ret = new java.util.zip.GZIPInputStream(ret);
                 doRelease = false;
                 return ret;
                 
@@ -144,7 +150,7 @@ public class HTTP {
                 
             }
             
-        } catch (IOException e) { invalid = true; throw e;
+        } catch (IOException e) { reset(); throw e;
         } finally { if (doRelease) releaseMe.release();
         }
     }
@@ -176,6 +182,17 @@ public class HTTP {
         } catch (UnknownHostException uhe) { }
 
         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 {
+            JS.Array args = new JS.Array();
+            args.addElement(host);
+            Object ret = new XMLRPC("http://xmlrpc.xwt.org/RPC2/", "dns.resolve").call(args);
+            if (ret == null || !(ret instanceof String)) throw new Exception("    xmlrpc.xwt.org returned non-String: " + ret);
+            resolvedHosts.put(host, ret);
+            return;
+        } catch (Throwable e) {
+            throw new HTTPException("exception while attempting to use xmlrpc.xwt.org to resolve " + host + ": " + e);
+        }
     }
 
 
@@ -311,6 +328,10 @@ public class HTTP {
     // Everything Else ////////////////////////////////////////////////////////////////////////////
 
     private synchronized void connect() throws IOException {
+        if (originalUrl.equals("stdio:")) {
+            in = new BufferedInputStream(System.in);
+            return;
+        }
         if (sock != null) {
             if (in == null) in = new BufferedInputStream(sock.getInputStream());
             return;
@@ -342,10 +363,15 @@ public class HTTP {
         if (Log.verbose) Log.log(this, "creating HTTP object for connection to " + host + ":" + port);
 
         Proxy 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);
+        OUTER: do {
+            if (pi != null) {
+                for(int i=0; i<pi.excluded.length; i++) if (host.equals(pi.excluded[i])) break OUTER;
+                if (sock == null && pi.proxyAutoConfigFunction != null) sock = attemptPAC(pi.proxyAutoConfigFunction);
+                if (sock == null && ssl && pi.httpsProxyHost != null) sock = attemptHttpProxy(pi.httpsProxyHost, pi.httpsProxyPort);
+                if (sock == null && pi.httpProxyHost != null) sock = attemptHttpProxy(pi.httpProxyHost, pi.httpProxyPort);
+                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);
@@ -354,7 +380,7 @@ public class HTTP {
 
     public void sendRequest(String contentType, String content) throws IOException {
 
-        PrintWriter pw = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
+        PrintWriter pw = new PrintWriter(new OutputStreamWriter(originalUrl.equals("stdio:") ? System.out : sock.getOutputStream()));
         if (content != null) {
             pw.print("POST " + path + " HTTP/1.1\r\n");
             int contentLength = content.substring(0, 2).equals("\r\n") ?
@@ -367,6 +393,7 @@ public class HTTP {
         }
         
         pw.print("User-Agent: XWT\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");
 
@@ -467,7 +494,7 @@ public class HTTP {
         private int contentLength = 0;
         public int getContentLength() { return contentLength; }
 
-        HTTPInputStream(InputStream in, int length, Semaphore releaseMe) {
+        HTTPInputStream(InputStream in, int length, Semaphore releaseMe) throws IOException {
             super(in);
             this.releaseMe = releaseMe;
             this.contentLength = length;
@@ -497,7 +524,7 @@ public class HTTP {
                 int i = super.read();
                 if (i == -1) throw new HTTPException("encountered end of stream while reading chunk length");
 
-                // FIXME: handle chunking extensions
+                // FEATURE: handle chunking extensions
                 if (i == '\r') {
                     super.read();    // LF
                     break;
@@ -526,7 +553,7 @@ public class HTTP {
                 }
                 return ret;
             } finally {
-                if (!good) invalid = true;
+                if (!good) reset();
             }
         }
 
@@ -544,6 +571,12 @@ public class HTTP {
         }
     }
 
+    void reset() {
+        firstRequest = true;
+        in = null;
+        sock = null;
+    }
+
 
     // Misc Helpers ///////////////////////////////////////////////////////////////////////////////////
 
@@ -742,8 +775,6 @@ public class HTTP {
                         try {
                             org.xwt.js.JS.Array arr = new org.xwt.js.JS.Array();
                             arr.addElement(((JS.Exn)e).getObject());
-                            // FIXME
-                            //XWT.recursivePrintObject.call();
                         } catch (Exception e2) {
                             Log.log(Platform.class, e);
                         }
@@ -771,10 +802,11 @@ public class HTTP {
 
                 if (authorization != oldAuth) return;
                 if (Log.on) Log.log(Authorization.class, "displaying proxy authorization dialog");
-                MessageQueue.add(new Message() {
+                Message.Q.add(new Message() {
                         public void perform() {
                             Box b = new Box();
-                            Template.getTemplate("org.xwt.builtin.proxy_authorization", null).apply(b, null, null, null, 0, 0, null);
+                            Template t = Template.getTemplate((Res)Main.builtin.get("org/xwt/builtin/proxy_authorization.xwt"));
+                            t.apply(b, null, null);
                             b.put("realm", realm);
                             b.put("proxyIP", proxyIP);
                         }
@@ -789,12 +821,10 @@ public class HTTP {
 
         // ProxyAutoConfigRootScope ////////////////////////////////////////////////////////////////////
 
-        public static class ProxyAutoConfigRootScope extends JS.Scope {
+        public static class ProxyAutoConfigRootScope extends JS.GlobalScope {
 
             public ProxyAutoConfigRootScope() { super(null); }
         
-            // FIXME: needs "standard objects"
-
             public Object get(Object name) {
                 if (name.equals("isPlainHostName")) return isPlainHostName;
                 else if (name.equals("dnsDomainIs")) return dnsDomainIs;