1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 package java.net; 28 29 import java.util.Map; 30 import java.util.List; 31 import java.util.Collections; 32 import java.util.Comparator; 33 import java.io.IOException; 34 import sun.util.logging.PlatformLogger; 35 36 /** 37 * CookieManager provides a concrete implementation of {@link CookieHandler}, 38 * which separates the storage of cookies from the policy surrounding accepting 39 * and rejecting cookies. A CookieManager is initialized with a {@link CookieStore} 40 * which manages storage, and a {@link CookiePolicy} object, which makes 41 * policy decisions on cookie acceptance/rejection. 42 * 43 * <p> The HTTP cookie management in java.net package looks like: 44 * <blockquote> 45 * <pre>{@code 46 * use 47 * CookieHandler <------- HttpURLConnection 48 * ^ 49 * | impl 50 * | use 51 * CookieManager -------> CookiePolicy 52 * | use 53 * |--------> HttpCookie 54 * | ^ 55 * | | use 56 * | use | 57 * |--------> CookieStore 58 * ^ 59 * | impl 60 * | 61 * Internal in-memory implementation 62 * }</pre> 63 * <ul> 64 * <li> 65 * CookieHandler is at the core of cookie management. User can call 66 * CookieHandler.setDefault to set a concrete CookieHanlder implementation 67 * to be used. 68 * </li> 69 * <li> 70 * CookiePolicy.shouldAccept will be called by CookieManager.put to see whether 71 * or not one cookie should be accepted and put into cookie store. User can use 72 * any of three pre-defined CookiePolicy, namely ACCEPT_ALL, ACCEPT_NONE and 73 * ACCEPT_ORIGINAL_SERVER, or user can define his own CookiePolicy implementation 74 * and tell CookieManager to use it. 75 * </li> 76 * <li> 77 * CookieStore is the place where any accepted HTTP cookie is stored in. 78 * If not specified when created, a CookieManager instance will use an internal 79 * in-memory implementation. Or user can implements one and tell CookieManager 80 * to use it. 81 * </li> 82 * <li> 83 * Currently, only CookieStore.add(URI, HttpCookie) and CookieStore.get(URI) 84 * are used by CookieManager. Others are for completeness and might be needed 85 * by a more sophisticated CookieStore implementation, e.g. a NetscapeCookieSotre. 86 * </li> 87 * </ul> 88 * </blockquote> 89 * 90 * <p>There're various ways user can hook up his own HTTP cookie management behavior, e.g. 91 * <blockquote> 92 * <ul> 93 * <li>Use CookieHandler.setDefault to set a brand new {@link CookieHandler} implementation 94 * <li>Let CookieManager be the default {@link CookieHandler} implementation, 95 * but implement user's own {@link CookieStore} and {@link CookiePolicy} 96 * and tell default CookieManager to use them: 97 * <blockquote><pre> 98 * // this should be done at the beginning of an HTTP session 99 * CookieHandler.setDefault(new CookieManager(new MyCookieStore(), new MyCookiePolicy())); 100 * </pre></blockquote> 101 * <li>Let CookieManager be the default {@link CookieHandler} implementation, but 102 * use customized {@link CookiePolicy}: 103 * <blockquote><pre> 104 * // this should be done at the beginning of an HTTP session 105 * CookieHandler.setDefault(new CookieManager()); 106 * // this can be done at any point of an HTTP session 107 * ((CookieManager)CookieHandler.getDefault()).setCookiePolicy(new MyCookiePolicy()); 108 * </pre></blockquote> 109 * </ul> 110 * </blockquote> 111 * 112 * <p>The implementation conforms to <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a>, section 3.3. 113 * 114 * @see CookiePolicy 115 * @author Edward Wang 116 * @since 1.6 117 */ 118 public class CookieManager extends CookieHandler 119 { 120 /* ---------------- Fields -------------- */ 121 122 private CookiePolicy policyCallback; 123 124 125 private CookieStore cookieJar = null; 126 127 128 /* ---------------- Ctors -------------- */ 129 130 /** 131 * Create a new cookie manager. 132 * 133 * <p>This constructor will create new cookie manager with default 134 * cookie store and accept policy. The effect is same as 135 * {@code CookieManager(null, null)}. 136 */ CookieManager()137 public CookieManager() { 138 this(null, null); 139 } 140 141 142 /** 143 * Create a new cookie manager with specified cookie store and cookie policy. 144 * 145 * @param store a {@code CookieStore} to be used by cookie manager. 146 * if {@code null}, cookie manager will use a default one, 147 * which is an in-memory CookieStore implementation. 148 * @param cookiePolicy a {@code CookiePolicy} instance 149 * to be used by cookie manager as policy callback. 150 * if {@code null}, ACCEPT_ORIGINAL_SERVER will 151 * be used. 152 */ CookieManager(CookieStore store, CookiePolicy cookiePolicy)153 public CookieManager(CookieStore store, 154 CookiePolicy cookiePolicy) 155 { 156 // use default cookie policy if not specify one 157 policyCallback = (cookiePolicy == null) ? CookiePolicy.ACCEPT_ORIGINAL_SERVER 158 : cookiePolicy; 159 160 // if not specify CookieStore to use, use default one 161 if (store == null) { 162 cookieJar = new InMemoryCookieStore(); 163 } else { 164 cookieJar = store; 165 } 166 } 167 168 169 /* ---------------- Public operations -------------- */ 170 171 /** 172 * To set the cookie policy of this cookie manager. 173 * 174 * <p> A instance of {@code CookieManager} will have 175 * cookie policy ACCEPT_ORIGINAL_SERVER by default. Users always 176 * can call this method to set another cookie policy. 177 * 178 * @param cookiePolicy the cookie policy. Can be {@code null}, which 179 * has no effects on current cookie policy. 180 */ setCookiePolicy(CookiePolicy cookiePolicy)181 public void setCookiePolicy(CookiePolicy cookiePolicy) { 182 if (cookiePolicy != null) policyCallback = cookiePolicy; 183 } 184 185 186 /** 187 * To retrieve current cookie store. 188 * 189 * @return the cookie store currently used by cookie manager. 190 */ getCookieStore()191 public CookieStore getCookieStore() { 192 return cookieJar; 193 } 194 195 196 public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders)197 get(URI uri, Map<String, List<String>> requestHeaders) 198 throws IOException 199 { 200 // pre-condition check 201 if (uri == null || requestHeaders == null) { 202 throw new IllegalArgumentException("Argument is null"); 203 } 204 205 Map<String, List<String>> cookieMap = 206 new java.util.HashMap<String, List<String>>(); 207 // if there's no default CookieStore, no way for us to get any cookie 208 if (cookieJar == null) 209 return Collections.unmodifiableMap(cookieMap); 210 211 boolean secureLink = "https".equalsIgnoreCase(uri.getScheme()); 212 List<HttpCookie> cookies = new java.util.ArrayList<HttpCookie>(); 213 // BEGIN Android-removed: The logic of converting null path is moved into pathMatches. 214 /* 215 String path = uri.getPath(); 216 if (path == null || path.isEmpty()) { 217 path = "/"; 218 } 219 */ 220 // END Android-removed: The logic of converting null path is moved into pathMatches. 221 for (HttpCookie cookie : cookieJar.get(uri)) { 222 // apply path-matches rule (RFC 2965 sec. 3.3.4) 223 // and check for the possible "secure" tag (i.e. don't send 224 // 'secure' cookies over unsecure links) 225 if (pathMatches(uri, cookie) && 226 (secureLink || !cookie.getSecure())) { 227 // BEGIN Android-removed: App compat: b/25897688 InMemoryCookieStore ignores scheme. 228 /* 229 if (cookie.isHttpOnly()) { 230 String s = uri.getScheme(); 231 if (!"http".equalsIgnoreCase(s) && !"https".equalsIgnoreCase(s)) { 232 continue; 233 } 234 } 235 */ 236 // END Android-removed: App compat: b/25897688 InMemoryCookieStore ignores scheme. 237 238 // Let's check the authorize port list if it exists 239 String ports = cookie.getPortlist(); 240 if (ports != null && !ports.isEmpty()) { 241 int port = uri.getPort(); 242 if (port == -1) { 243 port = "https".equals(uri.getScheme()) ? 443 : 80; 244 } 245 if (isInPortList(ports, port)) { 246 cookies.add(cookie); 247 } 248 } else { 249 cookies.add(cookie); 250 } 251 } 252 } 253 // Android-added: b/25897688 A fix to return empty map if cookies list is empty 254 if (cookies.isEmpty()) { 255 return Collections.emptyMap(); 256 } 257 258 // apply sort rule (RFC 2965 sec. 3.3.4) 259 List<String> cookieHeader = sortByPath(cookies); 260 261 cookieMap.put("Cookie", cookieHeader); 262 return Collections.unmodifiableMap(cookieMap); 263 } 264 265 public void put(URI uri, Map<String, List<String>> responseHeaders)266 put(URI uri, Map<String, List<String>> responseHeaders) 267 throws IOException 268 { 269 // pre-condition check 270 if (uri == null || responseHeaders == null) { 271 throw new IllegalArgumentException("Argument is null"); 272 } 273 274 275 // if there's no default CookieStore, no need to remember any cookie 276 if (cookieJar == null) 277 return; 278 279 PlatformLogger logger = PlatformLogger.getLogger("java.net.CookieManager"); 280 for (String headerKey : responseHeaders.keySet()) { 281 // RFC 2965 3.2.2, key must be 'Set-Cookie2' 282 // we also accept 'Set-Cookie' here for backward compatibility 283 if (headerKey == null 284 || !(headerKey.equalsIgnoreCase("Set-Cookie2") 285 || headerKey.equalsIgnoreCase("Set-Cookie") 286 ) 287 ) 288 { 289 continue; 290 } 291 292 for (String headerValue : responseHeaders.get(headerKey)) { 293 try { 294 List<HttpCookie> cookies; 295 try { 296 cookies = HttpCookie.parse(headerValue); 297 } catch (IllegalArgumentException e) { 298 // Bogus header, make an empty list and log the error 299 cookies = java.util.Collections.emptyList(); 300 if (logger.isLoggable(PlatformLogger.Level.SEVERE)) { 301 logger.severe("Invalid cookie for " + uri + ": " + headerValue); 302 } 303 } 304 for (HttpCookie cookie : cookies) { 305 if (cookie.getPath() == null) { 306 // If no path is specified, then by default 307 // the path is the directory of the page/doc 308 String path = uri.getPath(); 309 if (!path.endsWith("/")) { 310 int i = path.lastIndexOf("/"); 311 if (i > 0) { 312 path = path.substring(0, i + 1); 313 } else { 314 path = "/"; 315 } 316 } 317 cookie.setPath(path); 318 // Android-added: b/25763487 A fix to verify cookie URI before removal 319 } else { 320 // Validate existing path 321 if (!pathMatches(uri, cookie)) { 322 continue; 323 } 324 } 325 326 // As per RFC 2965, section 3.3.1: 327 // Domain Defaults to the effective request-host. (Note that because 328 // there is no dot at the beginning of effective request-host, 329 // the default Domain can only domain-match itself.) 330 if (cookie.getDomain() == null) { 331 String host = uri.getHost(); 332 if (host != null && !host.contains(".")) 333 host += ".local"; 334 cookie.setDomain(host); 335 } 336 String ports = cookie.getPortlist(); 337 if (ports != null) { 338 int port = uri.getPort(); 339 if (port == -1) { 340 port = "https".equals(uri.getScheme()) ? 443 : 80; 341 } 342 if (ports.isEmpty()) { 343 // Empty port list means this should be restricted 344 // to the incoming URI port 345 cookie.setPortlist("" + port ); 346 if (shouldAcceptInternal(uri, cookie)) { 347 cookieJar.add(uri, cookie); 348 } 349 } else { 350 // Only store cookies with a port list 351 // IF the URI port is in that list, as per 352 // RFC 2965 section 3.3.2 353 if (isInPortList(ports, port) && 354 shouldAcceptInternal(uri, cookie)) { 355 cookieJar.add(uri, cookie); 356 } 357 } 358 } else { 359 if (shouldAcceptInternal(uri, cookie)) { 360 cookieJar.add(uri, cookie); 361 } 362 } 363 } 364 } catch (IllegalArgumentException e) { 365 // invalid set-cookie header string 366 // no-op 367 } 368 } 369 } 370 } 371 372 373 /* ---------------- Private operations -------------- */ 374 375 // to determine whether or not accept this cookie shouldAcceptInternal(URI uri, HttpCookie cookie)376 private boolean shouldAcceptInternal(URI uri, HttpCookie cookie) { 377 try { 378 return policyCallback.shouldAccept(uri, cookie); 379 } catch (Exception ignored) { // pretect against malicious callback 380 return false; 381 } 382 } 383 384 isInPortList(String lst, int port)385 static private boolean isInPortList(String lst, int port) { 386 int i = lst.indexOf(","); 387 int val = -1; 388 while (i > 0) { 389 try { 390 val = Integer.parseInt(lst.substring(0, i)); 391 if (val == port) { 392 return true; 393 } 394 } catch (NumberFormatException numberFormatException) { 395 } 396 lst = lst.substring(i+1); 397 i = lst.indexOf(","); 398 } 399 if (!lst.isEmpty()) { 400 try { 401 val = Integer.parseInt(lst); 402 if (val == port) { 403 return true; 404 } 405 } catch (NumberFormatException numberFormatException) { 406 } 407 } 408 return false; 409 } 410 411 // Android-changed: b/25763487 Cookie path matching logic in OpenJDK was wrong 412 /** 413 * Return true iff. the path from {@code cookie} matches the path from {@code uri}. 414 */ pathMatches(URI uri, HttpCookie cookie)415 private static boolean pathMatches(URI uri, HttpCookie cookie) { 416 return normalizePath(uri.getPath()).startsWith(normalizePath(cookie.getPath())); 417 } 418 normalizePath(String path)419 private static String normalizePath(String path) { 420 if (path == null) { 421 path = ""; 422 } 423 424 if (!path.endsWith("/")) { 425 path = path + "/"; 426 } 427 428 return path; 429 } 430 431 432 /* 433 * sort cookies with respect to their path: those with more specific Path attributes 434 * precede those with less specific, as defined in RFC 2965 sec. 3.3.4 435 */ sortByPath(List<HttpCookie> cookies)436 private List<String> sortByPath(List<HttpCookie> cookies) { 437 Collections.sort(cookies, new CookiePathComparator()); 438 439 // BEGIN Android-changed: Netscape cookie spec and RFC 2965 have different format 440 // of Cookie header; RFC 2965 requires a leading $Version="1" string while Netscape does not 441 // The workaround here is to add a $Version="1" string in advance 442 final StringBuilder result = new StringBuilder(); 443 int minVersion = 1; 444 for (HttpCookie cookie : cookies) { 445 if (cookie.getVersion() < minVersion) { 446 minVersion = cookie.getVersion(); 447 } 448 } 449 450 if (minVersion == 1) { 451 result.append("$Version=\"1\"; "); 452 } 453 454 for (int i = 0; i < cookies.size(); ++i) { 455 if (i != 0) { 456 result.append("; "); 457 } 458 459 result.append(cookies.get(i).toString()); 460 } 461 462 List<String> cookieHeader = new java.util.ArrayList<String>(); 463 cookieHeader.add(result.toString()); 464 // END Android-changed: Netscape cookie spec and RFC 2965 have different format 465 return cookieHeader; 466 } 467 468 469 static class CookiePathComparator implements Comparator<HttpCookie> { compare(HttpCookie c1, HttpCookie c2)470 public int compare(HttpCookie c1, HttpCookie c2) { 471 if (c1 == c2) return 0; 472 if (c1 == null) return -1; 473 if (c2 == null) return 1; 474 475 // path rule only applies to the cookies with same name 476 if (!c1.getName().equals(c2.getName())) return 0; 477 478 // Android-changed: normalize before comparison 479 final String c1Path = normalizePath(c1.getPath()); 480 final String c2Path = normalizePath(c2.getPath()); 481 482 // those with more specific Path attributes precede those with less specific 483 if (c1Path.startsWith(c2Path)) 484 return -1; 485 else if (c2Path.startsWith(c1Path)) 486 return 1; 487 else 488 return 0; 489 } 490 } 491 } 492