1 /* 2 * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.net; 27 28 import java.util.Map; 29 import java.util.List; 30 import java.util.Collections; 31 import java.util.Comparator; 32 import java.io.IOException; 33 34 /** 35 * CookieManager provides a concrete implementation of {@link CookieHandler}, 36 * which separates the storage of cookies from the policy surrounding accepting 37 * and rejecting cookies. A CookieManager is initialized with a {@link CookieStore} 38 * which manages storage, and a {@link CookiePolicy} object, which makes 39 * policy decisions on cookie acceptance/rejection. 40 * 41 * <p> The HTTP cookie management in java.net package looks like: 42 * <blockquote> 43 * <pre> 44 * use 45 * CookieHandler <------- HttpURLConnection 46 * ^ 47 * | impl 48 * | use 49 * CookieManager -------> CookiePolicy 50 * | use 51 * |--------> HttpCookie 52 * | ^ 53 * | | use 54 * | use | 55 * |--------> CookieStore 56 * ^ 57 * | impl 58 * | 59 * Internal in-memory implementation 60 * </pre> 61 * <ul> 62 * <li> 63 * CookieHandler is at the core of cookie management. User can call 64 * CookieHandler.setDefault to set a concrete CookieHanlder implementation 65 * to be used. 66 * </li> 67 * <li> 68 * CookiePolicy.shouldAccept will be called by CookieManager.put to see whether 69 * or not one cookie should be accepted and put into cookie store. User can use 70 * any of three pre-defined CookiePolicy, namely ACCEPT_ALL, ACCEPT_NONE and 71 * ACCEPT_ORIGINAL_SERVER, or user can define his own CookiePolicy implementation 72 * 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 internal 77 * in-memory implementation. Or user can implements one and tell CookieManager 78 * 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 needed 83 * by a more sophisticated CookieStore implementation, e.g. a NetscapeCookieSotre. 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} implementation 92 * <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 session 97 * CookieHandler.setDefault(new CookieManager(new MyCookieStore(), new MyCookiePolicy())); 98 * </pre></blockquote> 99 * <li>Let CookieManager be the default {@link CookieHandler} implementation, but 100 * use customized {@link CookiePolicy}: 101 * <blockquote><pre> 102 * // this should be done at the beginning of an HTTP session 103 * CookieHandler.setDefault(new CookieManager()); 104 * // this can be done at any point of an HTTP session 105 * ((CookieManager)CookieHandler.getDefault()).setCookiePolicy(new MyCookiePolicy()); 106 * </pre></blockquote> 107 * </ul> 108 * </blockquote> 109 * 110 * <p>The implementation conforms to RFC 2965, section 3.3. 111 * 112 * @author Edward Wang 113 * @since 1.6 114 */ 115 public class CookieManager extends CookieHandler 116 { 117 /* ---------------- Fields -------------- */ 118 119 private CookiePolicy policyCallback; 120 121 122 private CookieStore cookieJar = null; 123 124 125 /* ---------------- Ctors -------------- */ 126 127 /** 128 * Create a new cookie manager. 129 * 130 * <p>This constructor will create new cookie manager with default 131 * cookie store and accept policy. The effect is same as 132 * <tt>CookieManager(null, null)</tt>. 133 */ 134 public CookieManager() { 135 this(null, null); 136 } 137 138 139 /** 140 * Create a new cookie manager with specified cookie store and cookie policy. 141 * 142 * @param store a <tt>CookieStore</tt> to be used by cookie manager. 143 * if <tt>null</tt>, cookie manager will use a default one, 144 * which is an in-memory CookieStore implmentation. 145 * @param cookiePolicy a <tt>CookiePolicy</tt> instance 146 * to be used by cookie manager as policy callback. 147 * if <tt>null</tt>, ACCEPT_ORIGINAL_SERVER will 148 * be used. 149 */ 150 public CookieManager(CookieStore store, 151 CookiePolicy cookiePolicy) 152 { 153 // use default cookie policy if not specify one 154 policyCallback = (cookiePolicy == null) ? CookiePolicy.ACCEPT_ORIGINAL_SERVER 155 : cookiePolicy; 156 157 // if not specify CookieStore to use, use default one 158 if (store == null) { 159 cookieJar = new sun.net.www.protocol.http.InMemoryCookieStore(); 160 } else { 161 cookieJar = store; 162 } 163 } 164 165 166 /* ---------------- Public operations -------------- */ 167 168 /** 169 * To set the cookie policy of this cookie manager. 170 * 171 * <p> A instance of <tt>CookieManager</tt> will have 172 * cookie policy ACCEPT_ORIGINAL_SERVER by default. Users always 173 * can call this method to set another cookie policy. 174 * 175 * @param cookiePolicy the cookie policy. Can be <tt>null</tt>, which 176 * has no effects on current cookie policy. 177 */ 178 public void setCookiePolicy(CookiePolicy cookiePolicy) { 179 if (cookiePolicy != null) policyCallback = cookiePolicy; 180 } 181 182 183 /** 184 * To retrieve current cookie store. 185 * 186 * @return the cookie store currently used by cookie manager. 187 */ 188 public CookieStore getCookieStore() { 189 return cookieJar; 190 } 191 192 193 public Map<String, List<String>> 194 get(URI uri, Map<String, List<String>> requestHeaders) 195 throws IOException 196 { 197 // pre-condition check 198 if (uri == null || requestHeaders == null) { 199 throw new IllegalArgumentException("Argument is null"); 200 } 201 202 Map<String, List<String>> cookieMap = 203 new java.util.HashMap<String, List<String>>(); 204 // if there's no default CookieStore, no way for us to get any cookie 205 if (cookieJar == null) 206 return Collections.unmodifiableMap(cookieMap); 207 208 List<HttpCookie> cookies = new java.util.ArrayList<HttpCookie>(); 209 for (HttpCookie cookie : cookieJar.get(uri)) { 210 // apply path-matches rule (RFC 2965 sec. 3.3.4) 211 if (pathMatches(uri.getPath(), cookie.getPath())) { 212 cookies.add(cookie); 213 } 214 } 215 216 // apply sort rule (RFC 2965 sec. 3.3.4) 217 List<String> cookieHeader = sortByPath(cookies); 218 219 cookieMap.put("Cookie", cookieHeader); 220 return Collections.unmodifiableMap(cookieMap); 221 } 222 223 224 public void 225 put(URI uri, Map<String, List<String>> responseHeaders) 226 throws IOException 227 { 228 // pre-condition check 229 if (uri == null || responseHeaders == null) { 230 throw new IllegalArgumentException("Argument is null"); 231 } 232 233 234 // if there's no default CookieStore, no need to remember any cookie 235 if (cookieJar == null) 236 return; 237 238 for (String headerKey : responseHeaders.keySet()) { 239 // RFC 2965 3.2.2, key must be 'Set-Cookie2' 240 // we also accept 'Set-Cookie' here for backward compatibility 241 if (headerKey == null 242 || !(headerKey.equalsIgnoreCase("Set-Cookie2") 243 || headerKey.equalsIgnoreCase("Set-Cookie") 244 ) 245 ) 246 { 247 continue; 248 } 249 250 for (String headerValue : responseHeaders.get(headerKey)) { 251 try { 252 List<HttpCookie> cookies = HttpCookie.parse(headerValue); 253 for (HttpCookie cookie : cookies) { 254 if (shouldAcceptInternal(uri, cookie)) { 255 cookieJar.add(uri, cookie); 256 } 257 } 258 } catch (IllegalArgumentException e) { 259 // invalid set-cookie header string 260 // no-op 261 } 262 } 263 } 264 } 265 266 267 /* ---------------- Private operations -------------- */ 268 269 // to determine whether or not accept this cookie 270 private boolean shouldAcceptInternal(URI uri, HttpCookie cookie) { 271 try { 272 return policyCallback.shouldAccept(uri, cookie); 273 } catch (Exception ignored) { // pretect against malicious callback 274 return false; 275 } 276 } 277 278 279 /* 280 * path-matches algorithm, as defined by RFC 2965 281 */ 282 private boolean pathMatches(String path, String pathToMatchWith) { 283 if (path == pathToMatchWith) 284 return true; 285 if (path == null || pathToMatchWith == null) 286 return false; 287 if (path.startsWith(pathToMatchWith)) 288 return true; 289 290 return false; 291 } 292 293 294 /* 295 * sort cookies with respect to their path: those with more specific Path attributes 296 * precede those with less specific, as defined in RFC 2965 sec. 3.3.4 297 */ 298 private List<String> sortByPath(List<HttpCookie> cookies) { 299 Collections.sort(cookies, new CookiePathComparator()); 300 301 List<String> cookieHeader = new java.util.ArrayList<String>(); 302 for (HttpCookie cookie : cookies) { 303 // Netscape cookie spec and RFC 2965 have different format of Cookie 304 // header; RFC 2965 requires a leading $Version="1" string while Netscape 305 // does not. 306 // The workaround here is to add a $Version="1" string in advance 307 if (cookies.indexOf(cookie) == 0 && cookie.getVersion() > 0) { 308 cookieHeader.add("$Version=\"1\""); 309 } 310 311 cookieHeader.add(cookie.toString()); 312 } 313 return cookieHeader; 314 } 315 316 317 static class CookiePathComparator implements Comparator<HttpCookie> { 318 public int compare(HttpCookie c1, HttpCookie c2) { 319 if (c1 == c2) return 0; 320 if (c1 == null) return -1; 321 if (c2 == null) return 1; 322 323 // path rule only applies to the cookies with same name 324 if (!c1.getName().equals(c2.getName())) return 0; 325 326 // those with more specific Path attributes precede those with less specific 327 if (c1.getPath().startsWith(c2.getPath())) 328 return -1; 329 else if (c2.getPath().startsWith(c1.getPath())) 330 return 1; 331 else 332 return 0; 333 } 334 } 335 }