1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package java.net; 18 19import java.io.IOException; 20import java.util.ArrayList; 21import java.util.Collections; 22import java.util.List; 23import java.util.Map; 24 25/** 26 * This class provides a concrete implementation of CookieHandler. It separates 27 * the storage of cookies from the policy which decides to accept or deny 28 * cookies. The constructor can have two arguments: a CookieStore and a 29 * CookiePolicy. The former is in charge of cookie storage and the latter makes 30 * decision on acceptance/rejection. 31 * 32 * CookieHandler is in the center of cookie management. User can make use of 33 * CookieHandler.setDefault to set a CookieManager as the default one used. 34 * 35 * CookieManager.put uses CookiePolicy.shouldAccept to decide whether to put 36 * some cookies into a cookie store. Three built-in CookiePolicy is defined: 37 * ACCEPT_ALL, ACCEPT_NONE and ACCEPT_ORIGINAL_SERVER. Users can also customize 38 * the policy by implementing CookiePolicy. Any accepted HTTP cookie is stored 39 * in CookieStore and users can also have their own implementation. Up to now, 40 * Only add(URI, HttpCookie) and get(URI) are used by CookieManager. Other 41 * methods in this class may probably be used in a more complicated 42 * implementation. 43 * 44 * There are many ways to customize user's own HTTP cookie management: 45 * 46 * First, call CookieHandler.setDefault to set a new CookieHandler 47 * implementation. Second, call CookieHandler.getDefault to use CookieManager. 48 * The CookiePolicy and CookieStore used are customized. Third, use the 49 * customized CookiePolicy and the CookieStore. 50 * 51 * This implementation conforms to <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a> section 3.3. 52 * 53 * @since 1.6 54 */ 55public class CookieManager extends CookieHandler { 56 private CookieStore store; 57 58 private CookiePolicy policy; 59 60 private static final String VERSION_ZERO_HEADER = "Set-cookie"; 61 62 private static final String VERSION_ONE_HEADER = "Set-cookie2"; 63 64 /** 65 * Constructs a new cookie manager. 66 * 67 * The invocation of this constructor is the same as the invocation of 68 * CookieManager(null, null). 69 * 70 */ 71 public CookieManager() { 72 this(null, null); 73 } 74 75 /** 76 * Constructs a new cookie manager using a specified cookie store and a 77 * cookie policy. 78 * 79 * @param store 80 * a CookieStore to be used by cookie manager. The manager will 81 * use a default one if the arg is null. 82 * @param cookiePolicy 83 * a CookiePolicy to be used by cookie manager 84 * ACCEPT_ORIGINAL_SERVER will be used if the arg is null. 85 */ 86 public CookieManager(CookieStore store, CookiePolicy cookiePolicy) { 87 this.store = store == null ? new CookieStoreImpl() : store; 88 policy = cookiePolicy == null ? CookiePolicy.ACCEPT_ORIGINAL_SERVER 89 : cookiePolicy; 90 } 91 92 /** 93 * Searches and gets all cookies in the cache by the specified uri in the 94 * request header. 95 * 96 * @param uri 97 * the specified uri to search for 98 * @param requestHeaders 99 * a list of request headers 100 * @return a map that record all such cookies, the map is unchangeable 101 * @throws IOException 102 * if some error of I/O operation occurs 103 */ 104 @Override 105 public Map<String, List<String>> get(URI uri, 106 Map<String, List<String>> requestHeaders) throws IOException { 107 if (uri == null || requestHeaders == null) { 108 throw new IllegalArgumentException(); 109 } 110 111 List<HttpCookie> result = new ArrayList<HttpCookie>(); 112 for (HttpCookie cookie : store.get(uri)) { 113 if (HttpCookie.pathMatches(cookie, uri) 114 && HttpCookie.secureMatches(cookie, uri) 115 && HttpCookie.portMatches(cookie, uri)) { 116 result.add(cookie); 117 } 118 } 119 120 return cookiesToHeaders(result); 121 } 122 123 private static Map<String, List<String>> cookiesToHeaders(List<HttpCookie> cookies) { 124 if (cookies.isEmpty()) { 125 return Collections.emptyMap(); 126 } 127 128 StringBuilder result = new StringBuilder(); 129 130 // If all cookies are version 1, add a version 1 header. No header for version 0 cookies. 131 int minVersion = 1; 132 for (HttpCookie cookie : cookies) { 133 minVersion = Math.min(minVersion, cookie.getVersion()); 134 } 135 if (minVersion == 1) { 136 result.append("$Version=\"1\"; "); 137 } 138 139 result.append(cookies.get(0).toString()); 140 for (int i = 1; i < cookies.size(); i++) { 141 result.append("; ").append(cookies.get(i).toString()); 142 } 143 144 return Collections.singletonMap("Cookie", Collections.singletonList(result.toString())); 145 } 146 147 /** 148 * Sets cookies according to uri and responseHeaders 149 * 150 * @param uri 151 * the specified uri 152 * @param responseHeaders 153 * a list of request headers 154 * @throws IOException 155 * if some error of I/O operation occurs 156 */ 157 @Override 158 public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException { 159 if (uri == null || responseHeaders == null) { 160 throw new IllegalArgumentException(); 161 } 162 163 // parse and construct cookies according to the map 164 List<HttpCookie> cookies = parseCookie(responseHeaders); 165 for (HttpCookie cookie : cookies) { 166 167 // if the cookie doesn't have a domain, set one. The policy will do validation. 168 if (cookie.getDomain() == null) { 169 cookie.setDomain(uri.getHost()); 170 } 171 172 // if the cookie doesn't have a path, set one. If it does, validate it. 173 if (cookie.getPath() == null) { 174 cookie.setPath(pathToCookiePath(uri.getPath())); 175 } else if (!HttpCookie.pathMatches(cookie, uri)) { 176 continue; 177 } 178 179 // if the cookie has the placeholder port list "", set the port. Otherwise validate it. 180 if ("".equals(cookie.getPortlist())) { 181 cookie.setPortlist(Integer.toString(uri.getEffectivePort())); 182 } else if (cookie.getPortlist() != null && !HttpCookie.portMatches(cookie, uri)) { 183 continue; 184 } 185 186 // if the cookie conforms to the policy, add it into the store 187 if (policy.shouldAccept(uri, cookie)) { 188 store.add(uri, cookie); 189 } 190 } 191 } 192 193 /** 194 * Returns a cookie-safe path by truncating everything after the last "/". 195 * When request path like "/foo/bar.html" yields a cookie, that cookie's 196 * default path is "/foo/". 197 */ 198 static String pathToCookiePath(String path) { 199 if (path == null) { 200 return "/"; 201 } 202 int lastSlash = path.lastIndexOf('/'); // -1 yields the empty string 203 return path.substring(0, lastSlash + 1); 204 } 205 206 private static List<HttpCookie> parseCookie(Map<String, List<String>> responseHeaders) { 207 List<HttpCookie> cookies = new ArrayList<HttpCookie>(); 208 for (Map.Entry<String, List<String>> entry : responseHeaders.entrySet()) { 209 String key = entry.getKey(); 210 // Only "Set-cookie" and "Set-cookie2" pair will be parsed 211 if (key != null && (key.equalsIgnoreCase(VERSION_ZERO_HEADER) 212 || key.equalsIgnoreCase(VERSION_ONE_HEADER))) { 213 // parse list elements one by one 214 for (String cookieStr : entry.getValue()) { 215 try { 216 for (HttpCookie cookie : HttpCookie.parse(cookieStr)) { 217 cookies.add(cookie); 218 } 219 } catch (IllegalArgumentException ignored) { 220 // this string is invalid, jump to the next one. 221 } 222 } 223 } 224 } 225 return cookies; 226 } 227 228 /** 229 * Sets the cookie policy of this cookie manager. 230 * 231 * ACCEPT_ORIGINAL_SERVER is the default policy for CookieManager. 232 * 233 * @param cookiePolicy 234 * the cookie policy. if null, the original policy will not be 235 * changed. 236 */ 237 public void setCookiePolicy(CookiePolicy cookiePolicy) { 238 if (cookiePolicy != null) { 239 policy = cookiePolicy; 240 } 241 } 242 243 /** 244 * Gets current cookie store. 245 * 246 * @return the cookie store currently used by cookie manager. 247 */ 248 public CookieStore getCookieStore() { 249 return store; 250 } 251} 252