jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java
changeset 17473 35cd9b3a98ff
parent 16839 d0f2e97b7359
child 17485 feb8cd6a4c7d
--- a/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java	Wed May 15 00:36:03 2013 -0400
+++ b/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java	Wed May 15 15:01:59 2013 +0100
@@ -36,6 +36,7 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.net.SocketTimeoutException;
+import java.net.SocketPermission;
 import java.net.Proxy;
 import java.net.ProxySelector;
 import java.net.URI;
@@ -45,8 +46,13 @@
 import java.net.CacheResponse;
 import java.net.SecureCacheResponse;
 import java.net.CacheRequest;
+import java.net.HttpURLPermission;
 import java.net.Authenticator.RequestorType;
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+import java.security.PrivilegedActionException;
 import java.io.*;
+import java.net.*;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
@@ -303,6 +309,19 @@
      */
     private MessageHeader requests;
 
+    /* The headers actually set by the user are recorded here also
+     */
+    private MessageHeader userHeaders;
+
+    /* Headers and request method cannot be changed
+     * once this flag is set in :-
+     *     - getOutputStream()
+     *     - getInputStream())
+     *     - connect()
+     * Access synchronized on this.
+     */
+    private boolean connecting = false;
+
     /* The following two fields are only used with Digest Authentication */
     String domain;      /* The list of authentication domains */
     DigestAuthentication.Parameters digestparams;
@@ -370,6 +389,9 @@
     private int connectTimeout = NetworkClient.DEFAULT_CONNECT_TIMEOUT;
     private int readTimeout = NetworkClient.DEFAULT_READ_TIMEOUT;
 
+    /* A permission converted from a HttpURLPermission */
+    private SocketPermission socketPermission;
+
     /* Logging support */
     private static final PlatformLogger logger =
             PlatformLogger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
@@ -487,6 +509,14 @@
         }
     }
 
+    public synchronized void setRequestMethod(String method)
+                        throws ProtocolException {
+        if (connecting) {
+            throw new IllegalStateException("connect in progress");
+        }
+        super.setRequestMethod(method);
+    }
+
     /* adds the standard key/val pairs to reqests if necessary & write to
      * given PrintStream
      */
@@ -729,6 +759,7 @@
         super(u);
         requests = new MessageHeader();
         responses = new MessageHeader();
+        userHeaders = new MessageHeader();
         this.handler = handler;
         instProxy = p;
         if (instProxy instanceof sun.net.ApplicationProxy) {
@@ -849,6 +880,9 @@
     // overridden in HTTPS subclass
 
     public void connect() throws IOException {
+        synchronized (this) {
+            connecting = true;
+        }
         plainConnect();
     }
 
@@ -867,10 +901,86 @@
         return false;
     }
 
+    private String getHostAndPort(URL url) {
+        String host = url.getHost();
+        int port = url.getPort();
+        if (port == -1) {
+            String scheme = url.getProtocol();
+            if ("http".equals(scheme)) {
+                return host + ":80";
+            } else { // scheme must be https
+                return host + ":443";
+            }
+        }
+        return host + ":" + Integer.toString(port);
+    }
+
     protected void plainConnect()  throws IOException {
-        if (connected) {
-            return;
+        synchronized (this) {
+            if (connected) {
+                return;
+            }
+        }
+        SocketPermission p = URLtoSocketPermission(this.url);
+        if (p != null) {
+            try {
+                AccessController.doPrivileged(
+                    new PrivilegedExceptionAction<Void>() {
+                        public Void run() throws IOException {
+                            plainConnect0();
+                            return null;
+                        }
+                    }
+//                    }, null, p -- replace line above, when limited doPriv ready
+                );
+            } catch (PrivilegedActionException e) {
+                    throw (IOException) e.getException();
+            }
+        } else {
+            // run without additional permission
+            plainConnect0();
         }
+    }
+
+    /**
+     *  if the caller has a HttpURLPermission for connecting to the
+     *  given URL, then return a SocketPermission which permits
+     *  access to that destination. Return null otherwise. The permission
+     *  is cached in a field (which can only be changed by redirects)
+     */
+    SocketPermission URLtoSocketPermission(URL url) throws IOException {
+
+        if (socketPermission != null) {
+            return socketPermission;
+        }
+
+        SecurityManager sm = System.getSecurityManager();
+
+        if (sm == null) {
+            return null;
+        }
+
+        // the permission, which we might grant
+
+        SocketPermission newPerm = new SocketPermission(
+            getHostAndPort(url), "connect"
+        );
+
+        String actions = getRequestMethod()+":" +
+                getUserSetHeaders().getHeaderNamesInList();
+
+        HttpURLPermission p = new HttpURLPermission(url.toString(), actions);
+        try {
+            sm.checkPermission(p);
+            socketPermission = newPerm;
+            return socketPermission;
+        } catch (SecurityException e) {
+            // fall thru
+        }
+        return null;
+    }
+
+    protected void plainConnect0()  throws IOException {
         // try to see if request can be served from local cache
         if (cacheHandler != null && getUseCaches()) {
             try {
@@ -1068,7 +1178,28 @@
 
     @Override
     public synchronized OutputStream getOutputStream() throws IOException {
-
+        connecting = true;
+        SocketPermission p = URLtoSocketPermission(this.url);
+
+        if (p != null) {
+            try {
+                return AccessController.doPrivileged(
+                    new PrivilegedExceptionAction<OutputStream>() {
+                        public OutputStream run() throws IOException {
+                            return getOutputStream0();
+                        }
+                    }
+//                    }, null, p -- replace line above, when limited doPriv ready
+                );
+            } catch (PrivilegedActionException e) {
+                throw (IOException) e.getException();
+            }
+        } else {
+            return getOutputStream0();
+        }
+    }
+
+    private synchronized OutputStream getOutputStream0() throws IOException {
         try {
             if (!doOutput) {
                 throw new ProtocolException("cannot write to a URLConnection"
@@ -1231,8 +1362,30 @@
     }
 
     @Override
+    public synchronized InputStream getInputStream() throws IOException {
+        connecting = true;
+        SocketPermission p = URLtoSocketPermission(this.url);
+
+        if (p != null) {
+            try {
+                return AccessController.doPrivileged(
+                    new PrivilegedExceptionAction<InputStream>() {
+                        public InputStream run() throws IOException {
+                            return getInputStream0();
+                        }
+                    }
+//                    }, null, p -- replace line above, when limited doPriv ready
+                );
+            } catch (PrivilegedActionException e) {
+                throw (IOException) e.getException();
+            }
+        } else {
+            return getInputStream0();
+        }
+    }
+
     @SuppressWarnings("empty-statement")
-    public synchronized InputStream getInputStream() throws IOException {
+    private synchronized InputStream getInputStream0() throws IOException {
 
         if (!doInput) {
             throw new ProtocolException("Cannot read from URLConnection"
@@ -2319,18 +2472,19 @@
             return false;
         }
 
-        int stat = getResponseCode();
+        final int stat = getResponseCode();
         if (stat < 300 || stat > 307 || stat == 306
                                 || stat == HTTP_NOT_MODIFIED) {
             return false;
         }
-        String loc = getHeaderField("Location");
+        final String loc = getHeaderField("Location");
         if (loc == null) {
             /* this should be present - if not, we have no choice
              * but to go forward w/ the response we got
              */
             return false;
         }
+
         URL locUrl;
         try {
             locUrl = new URL(loc);
@@ -2342,6 +2496,38 @@
           // treat loc as a relative URI to conform to popular browsers
           locUrl = new URL(url, loc);
         }
+
+        final URL locUrl0 = locUrl;
+        socketPermission = null; // force recalculation
+        SocketPermission p = URLtoSocketPermission(locUrl);
+
+        if (p != null) {
+            try {
+                return AccessController.doPrivileged(
+                    new PrivilegedExceptionAction<Boolean>() {
+                        public Boolean run() throws IOException {
+                            return followRedirect0(loc, stat, locUrl0);
+                        }
+                    }
+//                    }, null, p -- replace line above, when limited doPriv ready
+                );
+            } catch (PrivilegedActionException e) {
+                throw (IOException) e.getException();
+            }
+        } else {
+            // run without additional permission
+            return followRedirect0(loc, stat, locUrl);
+        }
+    }
+
+    /* Tells us whether to follow a redirect.  If so, it
+     * closes the connection (break any keep-alive) and
+     * resets the url, re-connects, and resets the request
+     * property.
+     */
+    private boolean followRedirect0(String loc, int stat, URL locUrl)
+        throws IOException
+    {
         disconnectInternal();
         if (streaming()) {
             throw new HttpRetryException (RETRY_MSG3, stat, loc);
@@ -2753,17 +2939,24 @@
      * @param value the value to be set
      */
     @Override
-    public void setRequestProperty(String key, String value) {
-        if (connected)
+    public synchronized void setRequestProperty(String key, String value) {
+        if (connected || connecting)
             throw new IllegalStateException("Already connected");
         if (key == null)
             throw new NullPointerException ("key is null");
 
         if (isExternalMessageHeaderAllowed(key, value)) {
             requests.set(key, value);
+            if (!key.equalsIgnoreCase("Content-Type")) {
+                userHeaders.set(key, value);
+            }
         }
     }
 
+    MessageHeader getUserSetHeaders() {
+        return userHeaders;
+    }
+
     /**
      * Adds a general request property specified by a
      * key-value pair.  This method will not overwrite
@@ -2776,14 +2969,17 @@
      * @since 1.4
      */
     @Override
-    public void addRequestProperty(String key, String value) {
-        if (connected)
+    public synchronized void addRequestProperty(String key, String value) {
+        if (connected || connecting)
             throw new IllegalStateException("Already connected");
         if (key == null)
             throw new NullPointerException ("key is null");
 
         if (isExternalMessageHeaderAllowed(key, value)) {
             requests.add(key, value);
+            if (!key.equalsIgnoreCase("Content-Type")) {
+                    userHeaders.add(key, value);
+            }
         }
     }