# HG changeset patch # User michaelm # Date 1284654171 25200 # Node ID 13fdbd146659df2dfbc3565df8fdcb89c90fc0a2 # Parent 81d6ec3397e5a1c3a81b12588f75794e8e46fbcc 6980004: limit HTTP request cookie headers in HttpURLConnection 6961084: limit setting of some request headers in HttpURLConnection Reviewed-by: chegar diff -r 81d6ec3397e5 -r 13fdbd146659 jdk/src/share/classes/sun/net/www/MessageHeader.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> getHeaders(String[] excludeList) { + return filterAndAddHeaders(excludeList, null); + } + + public synchronized Map> filterAndAddHeaders(String[] excludeList, Map> include) { boolean skipIt = false; Map> m = new HashMap>(); 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))); } diff -r 81d6ec3397e5 -r 13fdbd146659 jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java --- 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 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(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> getRequestProperties() { + public synchronized Map> 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