diff -r fd16c54261b3 -r 90ce3da70b43 jdk/src/share/classes/java/net/CookieManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/java/net/CookieManager.java Sat Dec 01 00:00:00 2007 +0000 @@ -0,0 +1,335 @@ +/* + * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package java.net; + +import java.util.Map; +import java.util.List; +import java.util.Collections; +import java.util.Comparator; +import java.io.IOException; + +/** + * CookieManager provides a concrete implementation of {@link CookieHandler}, + * which separates the storage of cookies from the policy surrounding accepting + * and rejecting cookies. A CookieManager is initialized with a {@link CookieStore} + * which manages storage, and a {@link CookiePolicy} object, which makes + * policy decisions on cookie acceptance/rejection. + * + *

The HTTP cookie management in java.net package looks like: + *

+ *
+ *                  use
+ * CookieHandler <------- HttpURLConnection
+ *       ^
+ *       | impl
+ *       |         use
+ * CookieManager -------> CookiePolicy
+ *             |   use
+ *             |--------> HttpCookie
+ *             |              ^
+ *             |              | use
+ *             |   use        |
+ *             |--------> CookieStore
+ *                            ^
+ *                            | impl
+ *                            |
+ *                  Internal in-memory implementation
+ * 
+ * + *
+ * + *

There're various ways user can hook up his own HTTP cookie management behavior, e.g. + *

+ * + *
+ * + *

The implementation conforms to RFC 2965, section 3.3. + * + * @author Edward Wang + * @since 1.6 + */ +public class CookieManager extends CookieHandler +{ + /* ---------------- Fields -------------- */ + + private CookiePolicy policyCallback; + + + private CookieStore cookieJar = null; + + + /* ---------------- Ctors -------------- */ + + /** + * Create a new cookie manager. + * + *

This constructor will create new cookie manager with default + * cookie store and accept policy. The effect is same as + * CookieManager(null, null). + */ + public CookieManager() { + this(null, null); + } + + + /** + * Create a new cookie manager with specified cookie store and cookie policy. + * + * @param store a CookieStore to be used by cookie manager. + * if null, cookie manager will use a default one, + * which is an in-memory CookieStore implmentation. + * @param cookiePolicy a CookiePolicy instance + * to be used by cookie manager as policy callback. + * if null, ACCEPT_ORIGINAL_SERVER will + * be used. + */ + public CookieManager(CookieStore store, + CookiePolicy cookiePolicy) + { + // use default cookie policy if not specify one + policyCallback = (cookiePolicy == null) ? CookiePolicy.ACCEPT_ORIGINAL_SERVER + : cookiePolicy; + + // if not specify CookieStore to use, use default one + if (store == null) { + cookieJar = new sun.net.www.protocol.http.InMemoryCookieStore(); + } else { + cookieJar = store; + } + } + + + /* ---------------- Public operations -------------- */ + + /** + * To set the cookie policy of this cookie manager. + * + *

A instance of CookieManager will have + * cookie policy ACCEPT_ORIGINAL_SERVER by default. Users always + * can call this method to set another cookie policy. + * + * @param cookiePolicy the cookie policy. Can be null, which + * has no effects on current cookie policy. + */ + public void setCookiePolicy(CookiePolicy cookiePolicy) { + if (cookiePolicy != null) policyCallback = cookiePolicy; + } + + + /** + * To retrieve current cookie store. + * + * @return the cookie store currently used by cookie manager. + */ + public CookieStore getCookieStore() { + return cookieJar; + } + + + public Map> + get(URI uri, Map> requestHeaders) + throws IOException + { + // pre-condition check + if (uri == null || requestHeaders == null) { + throw new IllegalArgumentException("Argument is null"); + } + + Map> cookieMap = + new java.util.HashMap>(); + // if there's no default CookieStore, no way for us to get any cookie + if (cookieJar == null) + return Collections.unmodifiableMap(cookieMap); + + List cookies = new java.util.ArrayList(); + for (HttpCookie cookie : cookieJar.get(uri)) { + // apply path-matches rule (RFC 2965 sec. 3.3.4) + if (pathMatches(uri.getPath(), cookie.getPath())) { + cookies.add(cookie); + } + } + + // apply sort rule (RFC 2965 sec. 3.3.4) + List cookieHeader = sortByPath(cookies); + + cookieMap.put("Cookie", cookieHeader); + return Collections.unmodifiableMap(cookieMap); + } + + + public void + put(URI uri, Map> responseHeaders) + throws IOException + { + // pre-condition check + if (uri == null || responseHeaders == null) { + throw new IllegalArgumentException("Argument is null"); + } + + + // if there's no default CookieStore, no need to remember any cookie + if (cookieJar == null) + return; + + for (String headerKey : responseHeaders.keySet()) { + // RFC 2965 3.2.2, key must be 'Set-Cookie2' + // we also accept 'Set-Cookie' here for backward compatibility + if (headerKey == null + || !(headerKey.equalsIgnoreCase("Set-Cookie2") + || headerKey.equalsIgnoreCase("Set-Cookie") + ) + ) + { + continue; + } + + for (String headerValue : responseHeaders.get(headerKey)) { + try { + List cookies = HttpCookie.parse(headerValue); + for (HttpCookie cookie : cookies) { + if (shouldAcceptInternal(uri, cookie)) { + cookieJar.add(uri, cookie); + } + } + } catch (IllegalArgumentException e) { + // invalid set-cookie header string + // no-op + } + } + } + } + + + /* ---------------- Private operations -------------- */ + + // to determine whether or not accept this cookie + private boolean shouldAcceptInternal(URI uri, HttpCookie cookie) { + try { + return policyCallback.shouldAccept(uri, cookie); + } catch (Exception ignored) { // pretect against malicious callback + return false; + } + } + + + /* + * path-matches algorithm, as defined by RFC 2965 + */ + private boolean pathMatches(String path, String pathToMatchWith) { + if (path == pathToMatchWith) + return true; + if (path == null || pathToMatchWith == null) + return false; + if (path.startsWith(pathToMatchWith)) + return true; + + return false; + } + + + /* + * sort cookies with respect to their path: those with more specific Path attributes + * precede those with less specific, as defined in RFC 2965 sec. 3.3.4 + */ + private List sortByPath(List cookies) { + Collections.sort(cookies, new CookiePathComparator()); + + List cookieHeader = new java.util.ArrayList(); + for (HttpCookie cookie : cookies) { + // Netscape cookie spec and RFC 2965 have different format of Cookie + // header; RFC 2965 requires a leading $Version="1" string while Netscape + // does not. + // The workaround here is to add a $Version="1" string in advance + if (cookies.indexOf(cookie) == 0 && cookie.getVersion() > 0) { + cookieHeader.add("$Version=\"1\""); + } + + cookieHeader.add(cookie.toString()); + } + return cookieHeader; + } + + + static class CookiePathComparator implements Comparator { + public int compare(HttpCookie c1, HttpCookie c2) { + if (c1 == c2) return 0; + if (c1 == null) return -1; + if (c2 == null) return 1; + + // path rule only applies to the cookies with same name + if (!c1.getName().equals(c2.getName())) return 0; + + // those with more specific Path attributes precede those with less specific + if (c1.getPath().startsWith(c2.getPath())) + return -1; + else if (c2.getPath().startsWith(c1.getPath())) + return 1; + else + return 0; + } + } +}