6980004: limit HTTP request cookie headers in HttpURLConnection
authormichaelm
Thu, 16 Sep 2010 09:22:51 -0700
changeset 6876 13fdbd146659
parent 6875 81d6ec3397e5
child 6877 80aa31f1b638
6980004: limit HTTP request cookie headers in HttpURLConnection 6961084: limit setting of some request headers in HttpURLConnection Reviewed-by: chegar
jdk/src/share/classes/sun/net/www/MessageHeader.java
jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java
--- a/jdk/src/share/classes/sun/net/www/MessageHeader.java	Thu Sep 16 08:08:06 2010 -0700
+++ b/jdk/src/share/classes/sun/net/www/MessageHeader.java	Thu Sep 16 09:22:51 2010 -0700
@@ -196,6 +196,10 @@
     }
 
     public synchronized Map<String, List<String>> getHeaders(String[] excludeList) {
+        return filterAndAddHeaders(excludeList, null);
+    }
+
+    public synchronized Map<String, List<String>> filterAndAddHeaders(String[] excludeList, Map<String, List<String>>  include) {
         boolean skipIt = false;
         Map<String, List<String>> m = new HashMap<String, List<String>>();
         for (int i = nkeys; --i >= 0;) {
@@ -223,6 +227,19 @@
             }
         }
 
+        if (include != null) {
+            Iterator entries = include.entrySet().iterator();
+            while (entries.hasNext()) {
+                Map.Entry entry = (Map.Entry)entries.next();
+                List l = (List)m.get(entry.getKey());
+                if (l == null) {
+                    l = new ArrayList();
+                    m.put((String)entry.getKey(), l);
+                }
+                l.add(entry.getValue());
+            }
+        }
+
         for (String key : m.keySet()) {
             m.put(key, Collections.unmodifiableList(m.get(key)));
         }
--- a/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java	Thu Sep 16 08:08:06 2010 -0700
+++ b/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java	Thu Sep 16 09:22:51 2010 -0700
@@ -51,6 +51,9 @@
 import java.util.Locale;
 import java.util.StringTokenizer;
 import java.util.Iterator;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Set;
 import sun.net.*;
 import sun.net.www.*;
 import sun.net.www.http.HttpClient;
@@ -140,6 +143,54 @@
     */
     private static int bufSize4ES = 0;
 
+    /*
+     * Restrict setting of request headers through the public api
+     * consistent with JavaScript XMLHttpRequest2 with a few
+     * exceptions. Disallowed headers are silently ignored for
+     * backwards compatibility reasons rather than throwing a
+     * SecurityException. For example, some applets set the
+     * Host header since old JREs did not implement HTTP 1.1.
+     * Additionally, any header starting with Sec- is
+     * disallowed.
+     *
+     * The following headers are allowed for historical reasons:
+     *
+     * Accept-Charset, Accept-Encoding, Cookie, Cookie2, Date,
+     * Referer, TE, User-Agent, headers beginning with Proxy-.
+     *
+     * The following headers are allowed in a limited form:
+     *
+     * Connection: close
+     *
+     * See http://www.w3.org/TR/XMLHttpRequest2.
+     */
+    private static final boolean allowRestrictedHeaders;
+    private static final Set<String> restrictedHeaderSet;
+    private static final String[] restrictedHeaders = {
+        /* Restricted by XMLHttpRequest2 */
+        //"Accept-Charset",
+        //"Accept-Encoding",
+        "Access-Control-Request-Headers",
+        "Access-Control-Request-Method",
+        "Connection", /* close is allowed */
+        "Content-Length",
+        //"Cookie",
+        //"Cookie2",
+        "Content-Transfer-Encoding",
+        //"Date",
+        //"Expect",
+        "Host",
+        "Keep-Alive",
+        "Origin",
+        // "Referer",
+        // "TE",
+        "Trailer",
+        "Transfer-Encoding",
+        "Upgrade",
+        //"User-Agent",
+        "Via"
+    };
+
     static {
         maxRedirects = java.security.AccessController.doPrivileged(
             new sun.security.action.GetIntegerAction(
@@ -178,7 +229,17 @@
             bufSize4ES = 4096; // use the default
         }
 
-
+        allowRestrictedHeaders = ((Boolean)java.security.AccessController.doPrivileged(
+                new sun.security.action.GetBooleanAction(
+                    "sun.net.http.allowRestrictedHeaders"))).booleanValue();
+        if (!allowRestrictedHeaders) {
+            restrictedHeaderSet = new HashSet<String>(restrictedHeaders.length);
+            for (int i=0; i < restrictedHeaders.length; i++) {
+                restrictedHeaderSet.add(restrictedHeaders[i].toLowerCase());
+            }
+        } else {
+            restrictedHeaderSet = null;
+        }
     }
 
     static final String httpVersion = "HTTP/1.1";
@@ -191,6 +252,15 @@
             "Proxy-Authorization",
             "Authorization"
     };
+
+    // also exclude system cookies when any might be set
+    private static final String[] EXCLUDE_HEADERS2= {
+            "Proxy-Authorization",
+            "Authorization",
+            "Cookie",
+            "Cookie2"
+    };
+
     protected HttpClient http;
     protected Handler handler;
     protected Proxy instProxy;
@@ -213,6 +283,7 @@
     /* User set Cookies */
     private boolean setUserCookies = true;
     private String userCookies = null;
+    private String userCookies2 = null;
 
     /* We only have a single static authenticator for now.
      * REMIND:  backwards compatibility with JDK 1.1.  Should be
@@ -329,6 +400,41 @@
             });
     }
 
+    private boolean isRestrictedHeader(String key, String value) {
+        if (allowRestrictedHeaders) {
+            return false;
+        }
+
+        key = key.toLowerCase();
+        if (restrictedHeaderSet.contains(key)) {
+            /*
+             * Exceptions to restricted headers:
+             *
+             * Allow "Connection: close".
+             */
+            if (key.equals("connection") && value.equalsIgnoreCase("close")) {
+                return false;
+            }
+            return true;
+        } else if (key.startsWith("sec-")) {
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * Checks the validity of http message header and whether the header
+     * is restricted and throws IllegalArgumentException if invalid or
+     * restricted.
+     */
+    private boolean isExternalMessageHeaderAllowed(String key, String value) {
+        checkMessageHeader(key, value);
+        if (!isRestrictedHeader(key, value)) {
+            return true;
+        }
+        return false;
+    }
+
     /* Logging support */
     public static PlatformLogger getHttpLogger() {
         return logger;
@@ -1047,15 +1153,21 @@
             // we only want to capture the user defined Cookies once, as
             // they cannot be changed by user code after we are connected,
             // only internally.
-            if (setUserCookies) {
-                int k = requests.getKey("Cookie");
-                if ( k != -1)
-                    userCookies = requests.getValue(k);
-                setUserCookies = false;
+            synchronized (this) {
+                if (setUserCookies) {
+                    int k = requests.getKey("Cookie");
+                    if ( k != -1)
+                        userCookies = requests.getValue(k);
+                    k = requests.getKey("Cookie2");
+                    if ( k != -1)
+                        userCookies2 = requests.getValue(k);
+                    setUserCookies = false;
+                }
             }
 
             // remove old Cookie header before setting new one.
             requests.remove("Cookie");
+            requests.remove("Cookie2");
 
             URI uri = ParseUtil.toURI(url);
             if (uri != null) {
@@ -1101,6 +1213,13 @@
                 else
                     requests.set("Cookie", userCookies);
             }
+            if (userCookies2 != null) {
+                int k;
+                if ((k = requests.getKey("Cookie")) != -1)
+                    requests.set("Cookie2", requests.getValue(k) + ";" + userCookies2);
+                else
+                    requests.set("Cookie2", userCookies2);
+            }
 
         } // end of getting cookies
     }
@@ -2539,8 +2658,9 @@
         if (key == null)
             throw new NullPointerException ("key is null");
 
-        checkMessageHeader(key, value);
-        requests.set(key, value);
+        if (isExternalMessageHeaderAllowed(key, value)) {
+            requests.set(key, value);
+        }
     }
 
     /**
@@ -2561,8 +2681,9 @@
         if (key == null)
             throw new NullPointerException ("key is null");
 
-        checkMessageHeader(key, value);
-        requests.add(key, value);
+        if (isExternalMessageHeaderAllowed(key, value)) {
+            requests.add(key, value);
+        }
     }
 
     //
@@ -2575,13 +2696,23 @@
     }
 
     @Override
-    public String getRequestProperty (String key) {
+    public synchronized String getRequestProperty (String key) {
+        if (key == null) {
+            return null;
+        }
+
         // don't return headers containing security sensitive information
-        if (key != null) {
-            for (int i=0; i < EXCLUDE_HEADERS.length; i++) {
-                if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
-                    return null;
-                }
+        for (int i=0; i < EXCLUDE_HEADERS.length; i++) {
+            if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
+                return null;
+            }
+        }
+        if (!setUserCookies) {
+            if (key.equalsIgnoreCase("Cookie")) {
+                return userCookies;
+            }
+            if (key.equalsIgnoreCase("Cookie2")) {
+                return userCookies2;
             }
         }
         return requests.findValue(key);
@@ -2600,12 +2731,29 @@
      * @since 1.4
      */
     @Override
-    public Map<String, List<String>> getRequestProperties() {
+    public synchronized Map<String, List<String>> getRequestProperties() {
         if (connected)
             throw new IllegalStateException("Already connected");
 
         // exclude headers containing security-sensitive info
-        return requests.getHeaders(EXCLUDE_HEADERS);
+        if (setUserCookies) {
+            return requests.getHeaders(EXCLUDE_HEADERS);
+        }
+        /*
+         * The cookies in the requests message headers may have
+         * been modified. Use the saved user cookies instead.
+         */
+        Map userCookiesMap = null;
+        if (userCookies != null || userCookies2 != null) {
+            userCookiesMap = new HashMap();
+            if (userCookies != null) {
+                userCookiesMap.put("Cookie", userCookies);
+            }
+            if (userCookies2 != null) {
+                userCookiesMap.put("Cookie2", userCookies2);
+            }
+        }
+        return requests.filterAndAddHeaders(EXCLUDE_HEADERS2, userCookiesMap);
     }
 
     @Override