Path: blob/master/src/java.base/share/classes/java/net/CookieManager.java
41152 views
/*1* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.net;2627import java.util.Map;28import java.util.List;29import java.util.Collections;30import java.util.Comparator;31import java.io.IOException;32import sun.util.logging.PlatformLogger;3334/**35* CookieManager provides a concrete implementation of {@link CookieHandler},36* which separates the storage of cookies from the policy surrounding accepting37* and rejecting cookies. A CookieManager is initialized with a {@link CookieStore}38* which manages storage, and a {@link CookiePolicy} object, which makes39* policy decisions on cookie acceptance/rejection.40*41* <p> The HTTP cookie management in java.net package looks like:42* <blockquote>43* <pre>{@code44* use45* CookieHandler <------- HttpURLConnection46* ^47* | impl48* | use49* CookieManager -------> CookiePolicy50* | use51* |--------> HttpCookie52* | ^53* | | use54* | use |55* |--------> CookieStore56* ^57* | impl58* |59* Internal in-memory implementation60* }</pre>61* <ul>62* <li>63* CookieHandler is at the core of cookie management. User can call64* CookieHandler.setDefault to set a concrete CookieHandler implementation65* to be used.66* </li>67* <li>68* CookiePolicy.shouldAccept will be called by CookieManager.put to see whether69* or not one cookie should be accepted and put into cookie store. User can use70* any of three pre-defined CookiePolicy, namely ACCEPT_ALL, ACCEPT_NONE and71* ACCEPT_ORIGINAL_SERVER, or user can define his own CookiePolicy implementation72* and tell CookieManager to use it.73* </li>74* <li>75* CookieStore is the place where any accepted HTTP cookie is stored in.76* If not specified when created, a CookieManager instance will use an internal77* in-memory implementation. Or user can implements one and tell CookieManager78* to use it.79* </li>80* <li>81* Currently, only CookieStore.add(URI, HttpCookie) and CookieStore.get(URI)82* are used by CookieManager. Others are for completeness and might be needed83* by a more sophisticated CookieStore implementation, e.g. a NetscapeCookieStore.84* </li>85* </ul>86* </blockquote>87*88* <p>There're various ways user can hook up his own HTTP cookie management behavior, e.g.89* <blockquote>90* <ul>91* <li>Use CookieHandler.setDefault to set a brand new {@link CookieHandler} implementation92* <li>Let CookieManager be the default {@link CookieHandler} implementation,93* but implement user's own {@link CookieStore} and {@link CookiePolicy}94* and tell default CookieManager to use them:95* <blockquote><pre>96* // this should be done at the beginning of an HTTP session97* CookieHandler.setDefault(new CookieManager(new MyCookieStore(), new MyCookiePolicy()));98* </pre></blockquote>99* <li>Let CookieManager be the default {@link CookieHandler} implementation, but100* use customized {@link CookiePolicy}:101* <blockquote><pre>102* // this should be done at the beginning of an HTTP session103* CookieHandler.setDefault(new CookieManager());104* // this can be done at any point of an HTTP session105* ((CookieManager)CookieHandler.getDefault()).setCookiePolicy(new MyCookiePolicy());106* </pre></blockquote>107* </ul>108* </blockquote>109*110* <p>The implementation conforms to <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a>, section 3.3.111*112* @see CookiePolicy113* @author Edward Wang114* @since 1.6115*/116public class CookieManager extends CookieHandler117{118/* ---------------- Fields -------------- */119120private CookiePolicy policyCallback;121122123private CookieStore cookieJar = null;124125126/* ---------------- Ctors -------------- */127128/**129* Create a new cookie manager.130*131* <p>This constructor will create new cookie manager with default132* cookie store and accept policy. The effect is same as133* {@code CookieManager(null, null)}.134*/135public CookieManager() {136this(null, null);137}138139140/**141* Create a new cookie manager with specified cookie store and cookie policy.142*143* @param store a {@code CookieStore} to be used by cookie manager.144* if {@code null}, cookie manager will use a default one,145* which is an in-memory CookieStore implementation.146* @param cookiePolicy a {@code CookiePolicy} instance147* to be used by cookie manager as policy callback.148* if {@code null}, ACCEPT_ORIGINAL_SERVER will149* be used.150*/151public CookieManager(CookieStore store,152CookiePolicy cookiePolicy)153{154// use default cookie policy if not specify one155policyCallback = (cookiePolicy == null) ? CookiePolicy.ACCEPT_ORIGINAL_SERVER156: cookiePolicy;157158// if not specify CookieStore to use, use default one159if (store == null) {160cookieJar = new InMemoryCookieStore();161} else {162cookieJar = store;163}164}165166167/* ---------------- Public operations -------------- */168169/**170* To set the cookie policy of this cookie manager.171*172* <p> A instance of {@code CookieManager} will have173* cookie policy ACCEPT_ORIGINAL_SERVER by default. Users always174* can call this method to set another cookie policy.175*176* @param cookiePolicy the cookie policy. Can be {@code null}, which177* has no effects on current cookie policy.178*/179public void setCookiePolicy(CookiePolicy cookiePolicy) {180if (cookiePolicy != null) policyCallback = cookiePolicy;181}182183184/**185* To retrieve current cookie store.186*187* @return the cookie store currently used by cookie manager.188*/189public CookieStore getCookieStore() {190return cookieJar;191}192193194public Map<String, List<String>>195get(URI uri, Map<String, List<String>> requestHeaders)196throws IOException197{198// pre-condition check199if (uri == null || requestHeaders == null) {200throw new IllegalArgumentException("Argument is null");201}202203// if there's no default CookieStore, no way for us to get any cookie204if (cookieJar == null)205return Map.of();206207boolean secureLink = "https".equalsIgnoreCase(uri.getScheme());208List<HttpCookie> cookies = new java.util.ArrayList<>();209String path = uri.getPath();210if (path == null || path.isEmpty()) {211path = "/";212}213for (HttpCookie cookie : cookieJar.get(uri)) {214// apply path-matches rule (RFC 2965 sec. 3.3.4)215// and check for the possible "secure" tag (i.e. don't send216// 'secure' cookies over unsecure links)217if (pathMatches(path, cookie.getPath()) &&218(secureLink || !cookie.getSecure())) {219// Enforce httponly attribute220if (cookie.isHttpOnly()) {221String s = uri.getScheme();222if (!"http".equalsIgnoreCase(s) && !"https".equalsIgnoreCase(s)) {223continue;224}225}226// Let's check the authorize port list if it exists227String ports = cookie.getPortlist();228if (ports != null && !ports.isEmpty()) {229int port = uri.getPort();230if (port == -1) {231port = "https".equals(uri.getScheme()) ? 443 : 80;232}233if (isInPortList(ports, port)) {234cookies.add(cookie);235}236} else {237cookies.add(cookie);238}239}240}241242// apply sort rule (RFC 2965 sec. 3.3.4)243List<String> cookieHeader = sortByPathAndAge(cookies);244245return Map.of("Cookie", cookieHeader);246}247248public void249put(URI uri, Map<String, List<String>> responseHeaders)250throws IOException251{252// pre-condition check253if (uri == null || responseHeaders == null) {254throw new IllegalArgumentException("Argument is null");255}256257258// if there's no default CookieStore, no need to remember any cookie259if (cookieJar == null)260return;261262PlatformLogger logger = PlatformLogger.getLogger("java.net.CookieManager");263for (String headerKey : responseHeaders.keySet()) {264// RFC 2965 3.2.2, key must be 'Set-Cookie2'265// we also accept 'Set-Cookie' here for backward compatibility266if (headerKey == null267|| !(headerKey.equalsIgnoreCase("Set-Cookie2")268|| headerKey.equalsIgnoreCase("Set-Cookie")269)270)271{272continue;273}274275for (String headerValue : responseHeaders.get(headerKey)) {276try {277List<HttpCookie> cookies;278try {279cookies = HttpCookie.parse(headerValue);280} catch (IllegalArgumentException e) {281// Bogus header, make an empty list and log the error282cookies = java.util.Collections.emptyList();283if (logger.isLoggable(PlatformLogger.Level.SEVERE)) {284logger.severe("Invalid cookie for " + uri + ": " + headerValue);285}286}287for (HttpCookie cookie : cookies) {288if (cookie.getPath() == null) {289// If no path is specified, then by default290// the path is the directory of the page/doc291String path = uri.getPath();292if (!path.endsWith("/")) {293int i = path.lastIndexOf('/');294if (i > 0) {295path = path.substring(0, i + 1);296} else {297path = "/";298}299}300cookie.setPath(path);301}302303// As per RFC 2965, section 3.3.1:304// Domain Defaults to the effective request-host. (Note that because305// there is no dot at the beginning of effective request-host,306// the default Domain can only domain-match itself.)307if (cookie.getDomain() == null) {308String host = uri.getHost();309if (host != null && !host.contains("."))310host += ".local";311cookie.setDomain(host);312}313String ports = cookie.getPortlist();314if (ports != null) {315int port = uri.getPort();316if (port == -1) {317port = "https".equals(uri.getScheme()) ? 443 : 80;318}319if (ports.isEmpty()) {320// Empty port list means this should be restricted321// to the incoming URI port322cookie.setPortlist("" + port );323if (shouldAcceptInternal(uri, cookie)) {324cookieJar.add(uri, cookie);325}326} else {327// Only store cookies with a port list328// IF the URI port is in that list, as per329// RFC 2965 section 3.3.2330if (isInPortList(ports, port) &&331shouldAcceptInternal(uri, cookie)) {332cookieJar.add(uri, cookie);333}334}335} else {336if (shouldAcceptInternal(uri, cookie)) {337cookieJar.add(uri, cookie);338}339}340}341} catch (IllegalArgumentException e) {342// invalid set-cookie header string343// no-op344}345}346}347}348349350/* ---------------- Private operations -------------- */351352// to determine whether or not accept this cookie353private boolean shouldAcceptInternal(URI uri, HttpCookie cookie) {354try {355return policyCallback.shouldAccept(uri, cookie);356} catch (Exception ignored) { // protect against malicious callback357return false;358}359}360361362private static boolean isInPortList(String lst, int port) {363int i = lst.indexOf(',');364int val = -1;365while (i > 0) {366try {367val = Integer.parseInt(lst, 0, i, 10);368if (val == port) {369return true;370}371} catch (NumberFormatException numberFormatException) {372}373lst = lst.substring(i+1);374i = lst.indexOf(',');375}376if (!lst.isEmpty()) {377try {378val = Integer.parseInt(lst);379if (val == port) {380return true;381}382} catch (NumberFormatException numberFormatException) {383}384}385return false;386}387388/*389* path-matches algorithm, as defined by RFC 2965390*/391private boolean pathMatches(String path, String pathToMatchWith) {392if (path == pathToMatchWith)393return true;394if (path == null || pathToMatchWith == null)395return false;396if (path.startsWith(pathToMatchWith))397return true;398399return false;400}401402403/*404* sort cookies with respect to their path and age: those with more longer Path attributes405* precede those with shorter, as defined in RFC 6265. Cookies with the same length406* path are distinguished by creation time (older first). Method made PP to enable testing.407*/408static List<String> sortByPathAndAge(List<HttpCookie> cookies) {409Collections.sort(cookies, new CookieComparator());410411List<String> cookieHeader = new java.util.ArrayList<>();412for (HttpCookie cookie : cookies) {413// Netscape cookie spec and RFC 2965 have different format of Cookie414// header; RFC 2965 requires a leading $Version="1" string while Netscape415// does not.416// The workaround here is to add a $Version="1" string in advance417if (cookies.indexOf(cookie) == 0 && cookie.getVersion() > 0) {418cookieHeader.add("$Version=\"1\"");419}420421cookieHeader.add(cookie.toString());422}423return cookieHeader;424}425426427// Comparator compares the length of the path. Longer paths should precede shorter ones.428// As per rfc6265 cookies with equal path lengths sort on creation time.429430static class CookieComparator implements Comparator<HttpCookie> {431public int compare(HttpCookie c1, HttpCookie c2) {432if (c1 == c2) return 0;433if (c1 == null) return -1;434if (c2 == null) return 1;435436String p1 = c1.getPath();437String p2 = c2.getPath();438p1 = (p1 == null) ? "" : p1;439p2 = (p2 == null) ? "" : p2;440int len1 = p1.length();441int len2 = p2.length();442if (len1 > len2)443return -1;444if (len2 > len1)445return 1;446447// Check creation time. Sort older first448long creation1 = c1.getCreationTime();449long creation2 = c2.getCreationTime();450if (creation1 < creation2) {451return -1;452}453if (creation1 > creation2) {454return 1;455}456return 0;457}458}459}460461462