/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.net; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * This class provides a concrete implementation of CookieHandler. It separates * the storage of cookies from the policy which decides to accept or deny * cookies. The constructor can have two arguments: a CookieStore and a * CookiePolicy. The former is in charge of cookie storage and the latter makes * decision on acceptance/rejection. * * CookieHandler is in the center of cookie management. User can make use of * CookieHandler.setDefault to set a CookieManager as the default one used. * * CookieManager.put uses CookiePolicy.shouldAccept to decide whether to put * some cookies into a cookie store. Three built-in CookiePolicy is defined: * ACCEPT_ALL, ACCEPT_NONE and ACCEPT_ORIGINAL_SERVER. Users can also customize * the policy by implementing CookiePolicy. Any accepted HTTP cookie is stored * in CookieStore and users can also have their own implementation. Up to now, * Only add(URI, HttpCookie) and get(URI) are used by CookieManager. Other * methods in this class may probably be used in a more complicated * implementation. * * There are many ways to customize user's own HTTP cookie management: * * First, call CookieHandler.setDefault to set a new CookieHandler * implementation. Second, call CookieHandler.getDefault to use CookieManager. * The CookiePolicy and CookieStore used are customized. Third, use the * customized CookiePolicy and the CookieStore. * * This implementation conforms to <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a> section 3.3. * * @since 1.6 */ public class CookieManager extends CookieHandler { private CookieStore store; private CookiePolicy policy; private static final String VERSION_ZERO_HEADER = "Set-cookie"; private static final String VERSION_ONE_HEADER = "Set-cookie2"; /** * Constructs a new cookie manager. * * The invocation of this constructor is the same as the invocation of * CookieManager(null, null). * */ public CookieManager() { this(null, null); } /** * Constructs a new cookie manager using a specified cookie store and a * cookie policy. * * @param store * a CookieStore to be used by cookie manager. The manager will * use a default one if the arg is null. * @param cookiePolicy * a CookiePolicy to be used by cookie manager * ACCEPT_ORIGINAL_SERVER will be used if the arg is null. */ public CookieManager(CookieStore store, CookiePolicy cookiePolicy) { this.store = store == null ? new CookieStoreImpl() : store; policy = cookiePolicy == null ? CookiePolicy.ACCEPT_ORIGINAL_SERVER : cookiePolicy; } /** * Searches and gets all cookies in the cache by the specified uri in the * request header. * * @param uri * the specified uri to search for * @param requestHeaders * a list of request headers * @return a map that record all such cookies, the map is unchangeable * @throws IOException * if some error of I/O operation occurs */ @Override public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) throws IOException { if (uri == null || requestHeaders == null) { throw new IllegalArgumentException(); } List<HttpCookie> result = new ArrayList<HttpCookie>(); for (HttpCookie cookie : store.get(uri)) { if (HttpCookie.pathMatches(cookie, uri) && HttpCookie.secureMatches(cookie, uri) && HttpCookie.portMatches(cookie, uri)) { result.add(cookie); } } return cookiesToHeaders(result); } private static Map<String, List<String>> cookiesToHeaders(List<HttpCookie> cookies) { if (cookies.isEmpty()) { return Collections.emptyMap(); } StringBuilder result = new StringBuilder(); // If all cookies are version 1, add a version 1 header. No header for version 0 cookies. int minVersion = 1; for (HttpCookie cookie : cookies) { minVersion = Math.min(minVersion, cookie.getVersion()); } if (minVersion == 1) { result.append("$Version=\"1\"; "); } result.append(cookies.get(0).toString()); for (int i = 1; i < cookies.size(); i++) { result.append("; ").append(cookies.get(i).toString()); } return Collections.singletonMap("Cookie", Collections.singletonList(result.toString())); } /** * Sets cookies according to uri and responseHeaders * * @param uri * the specified uri * @param responseHeaders * a list of request headers * @throws IOException * if some error of I/O operation occurs */ @Override public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException { if (uri == null || responseHeaders == null) { throw new IllegalArgumentException(); } // parse and construct cookies according to the map List<HttpCookie> cookies = parseCookie(responseHeaders); for (HttpCookie cookie : cookies) { // if the cookie doesn't have a domain, set one. The policy will do validation. if (cookie.getDomain() == null) { cookie.setDomain(uri.getHost()); } // if the cookie doesn't have a path, set one. If it does, validate it. if (cookie.getPath() == null) { cookie.setPath(pathToCookiePath(uri.getPath())); } else if (!HttpCookie.pathMatches(cookie, uri)) { continue; } // if the cookie has the placeholder port list "", set the port. Otherwise validate it. if ("".equals(cookie.getPortlist())) { cookie.setPortlist(Integer.toString(uri.getEffectivePort())); } else if (cookie.getPortlist() != null && !HttpCookie.portMatches(cookie, uri)) { continue; } // if the cookie conforms to the policy, add it into the store if (policy.shouldAccept(uri, cookie)) { store.add(uri, cookie); } } } /** * Returns a cookie-safe path by truncating everything after the last "/". * When request path like "/foo/bar.html" yields a cookie, that cookie's * default path is "/foo/". */ static String pathToCookiePath(String path) { if (path == null) { return "/"; } int lastSlash = path.lastIndexOf('/'); // -1 yields the empty string return path.substring(0, lastSlash + 1); } private static List<HttpCookie> parseCookie(Map<String, List<String>> responseHeaders) { List<HttpCookie> cookies = new ArrayList<HttpCookie>(); for (Map.Entry<String, List<String>> entry : responseHeaders.entrySet()) { String key = entry.getKey(); // Only "Set-cookie" and "Set-cookie2" pair will be parsed if (key != null && (key.equalsIgnoreCase(VERSION_ZERO_HEADER) || key.equalsIgnoreCase(VERSION_ONE_HEADER))) { // parse list elements one by one for (String cookieStr : entry.getValue()) { try { for (HttpCookie cookie : HttpCookie.parse(cookieStr)) { cookies.add(cookie); } } catch (IllegalArgumentException ignored) { // this string is invalid, jump to the next one. } } } } return cookies; } /** * Sets the cookie policy of this cookie manager. * * ACCEPT_ORIGINAL_SERVER is the default policy for CookieManager. * * @param cookiePolicy * the cookie policy. if null, the original policy will not be * changed. */ public void setCookiePolicy(CookiePolicy cookiePolicy) { if (cookiePolicy != null) { policy = cookiePolicy; } } /** * Gets current cookie store. * * @return the cookie store currently used by cookie manager. */ public CookieStore getCookieStore() { return store; } }