1 /* 2 * Copyright (c) 2011, 2014, 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 com.sun.webkit.network; 27 28 import com.sun.javafx.logging.PlatformLogger; 29 import com.sun.javafx.logging.PlatformLogger.Level; 30 31 import java.util.LinkedHashMap; 32 import java.util.Comparator; 33 import java.util.Collections; 34 import java.util.ArrayList; 35 import java.util.HashMap; 36 import java.util.Iterator; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.PriorityQueue; 40 import java.util.Queue; 41 42 /** 43 * A cookie store. 44 */ 45 final class CookieStore { 46 47 private static final PlatformLogger logger = 48 PlatformLogger.getLogger(CookieStore.class.getName()); 49 50 private static final int MAX_BUCKET_SIZE = 50; 51 private static final int TOTAL_COUNT_LOWER_THRESHOLD = 3000; 52 private static final int TOTAL_COUNT_UPPER_THRESHOLD = 4000; 53 54 55 /** 56 * The mapping from domain names to cookie buckets. 57 * Each cookie bucket stores the cookies associated with the 58 * corresponding domain. Each cookie bucket is represented 59 * by a Map<Cookie,Cookie> to facilitate retrieval of a cookie 60 * by another cookie with the same name, domain, and path. 61 */ 62 private final Map<String,Map<Cookie,Cookie>> buckets = 63 new HashMap<String,Map<Cookie,Cookie>>(); 64 65 /** 66 * The total number of cookies currently in the store. 67 */ 68 private int totalCount = 0; 69 70 71 /** 72 * Creates a new {@code CookieStore}. 73 */ 74 CookieStore() { 75 } 76 77 78 /** 79 * Returns the currently stored cookie with the same name, domain, and 80 * path as the given cookie. 81 */ 82 Cookie get(Cookie cookie) { 83 Map<Cookie,Cookie> bucket = buckets.get(cookie.getDomain()); 84 if (bucket == null) { 85 return null; 86 } 87 Cookie storedCookie = bucket.get(cookie); 88 if (storedCookie == null) { 89 return null; 90 } 91 if (storedCookie.hasExpired()) { 92 bucket.remove(storedCookie); 93 totalCount--; 94 log("Expired cookie removed by get", storedCookie, bucket); 95 return null; 96 } 97 return storedCookie; 98 } 99 100 101 /** 102 * Returns all the currently stored cookies that match the given query. 103 */ 104 List<Cookie> get(String hostname, String path, boolean secureProtocol, 105 boolean httpApi) 106 { 107 if (logger.isLoggable(Level.FINEST)) { 108 logger.finest("hostname: [{0}], path: [{1}], " 109 + "secureProtocol: [{2}], httpApi: [{3}]", new Object[] { 110 hostname, path, secureProtocol, httpApi}); 111 } 112 113 ArrayList<Cookie> result = new ArrayList<Cookie>(); 114 115 String domain = hostname; 116 while (domain.length() > 0) { 117 Map<Cookie,Cookie> bucket = buckets.get(domain); 118 if (bucket != null) { 119 find(result, bucket, hostname, path, secureProtocol, httpApi); 120 } 121 int nextPoint = domain.indexOf('.'); 122 if (nextPoint != -1) { 123 domain = domain.substring(nextPoint + 1); 124 } else { 125 break; 126 } 127 } 128 129 Collections.sort(result, new GetComparator()); 130 131 long currentTime = System.currentTimeMillis(); 132 for (Cookie cookie : result) { 133 cookie.setLastAccessTime(currentTime); 134 } 135 136 logger.finest("result: {0}", result); 137 return result; 138 } 139 140 /** 141 * Finds all the cookies that are stored in the given bucket and 142 * match the given query. 143 */ 144 private void find(List<Cookie> list, Map<Cookie,Cookie> bucket, 145 String hostname, String path, boolean secureProtocol, 146 boolean httpApi) 147 { 148 Iterator<Cookie> it = bucket.values().iterator(); 149 while (it.hasNext()) { 150 Cookie cookie = it.next(); 151 if (cookie.hasExpired()) { 152 it.remove(); 153 totalCount--; 154 log("Expired cookie removed by find", cookie, bucket); 155 continue; 156 } 157 158 if (cookie.getHostOnly()) { 159 if (!hostname.equalsIgnoreCase(cookie.getDomain())) { 160 continue; 161 } 162 } else { 163 if (!Cookie.domainMatches(hostname, cookie.getDomain())) { 164 continue; 165 } 166 } 167 168 if (!Cookie.pathMatches(path, cookie.getPath())) { 169 continue; 170 } 171 172 if (cookie.getSecureOnly() && !secureProtocol) { 173 continue; 174 } 175 176 if (cookie.getHttpOnly() && !httpApi) { 177 continue; 178 } 179 180 list.add(cookie); 181 } 182 } 183 184 private static final class GetComparator implements Comparator<Cookie> { 185 @Override 186 public int compare(Cookie c1, Cookie c2) { 187 int d = c2.getPath().length() - c1.getPath().length(); 188 if (d != 0) { 189 return d; 190 } 191 return c1.getCreationTime().compareTo(c2.getCreationTime()); 192 } 193 } 194 195 /** 196 * Stores the given cookie. 197 */ 198 void put(Cookie cookie) { 199 Map<Cookie,Cookie> bucket = buckets.get(cookie.getDomain()); 200 if (bucket == null) { 201 bucket = new LinkedHashMap<Cookie,Cookie>(20); 202 buckets.put(cookie.getDomain(), bucket); 203 } 204 if (cookie.hasExpired()) { 205 log("Cookie expired", cookie, bucket); 206 if (bucket.remove(cookie) != null) { 207 totalCount--; 208 log("Expired cookie removed by put", cookie, bucket); 209 } 210 } else { 211 if (bucket.put(cookie, cookie) == null) { 212 totalCount++; 213 log("Cookie added", cookie, bucket); 214 if (bucket.size() > MAX_BUCKET_SIZE) { 215 purge(bucket); 216 } 217 if (totalCount > TOTAL_COUNT_UPPER_THRESHOLD) { 218 purge(); 219 } 220 } else { 221 log("Cookie updated", cookie, bucket); 222 } 223 } 224 } 225 226 /** 227 * Removes excess cookies from a given bucket. 228 */ 229 private void purge(Map<Cookie,Cookie> bucket) { 230 logger.finest("Purging bucket: {0}", bucket.values()); 231 232 Cookie earliestCookie = null; 233 Iterator<Cookie> it = bucket.values().iterator(); 234 while (it.hasNext()) { 235 Cookie cookie = it.next(); 236 if (cookie.hasExpired()) { 237 it.remove(); 238 totalCount--; 239 log("Expired cookie removed", cookie, bucket); 240 } else { 241 if (earliestCookie == null || cookie.getLastAccessTime() 242 < earliestCookie.getLastAccessTime()) 243 { 244 earliestCookie = cookie; 245 } 246 } 247 } 248 if (bucket.size() > MAX_BUCKET_SIZE) { 249 bucket.remove(earliestCookie); 250 totalCount--; 251 log("Excess cookie removed", earliestCookie, bucket); 252 } 253 } 254 255 /** 256 * Removes excess cookies globally. 257 */ 258 private void purge() { 259 logger.finest("Purging store"); 260 261 Queue<Cookie> removalQueue = new PriorityQueue<Cookie>(totalCount / 2, 262 new RemovalComparator()); 263 264 for (Map.Entry<String,Map<Cookie,Cookie>> entry : buckets.entrySet()) { 265 Map<Cookie,Cookie> bucket = entry.getValue(); 266 Iterator<Cookie> it = bucket.values().iterator(); 267 while (it.hasNext()) { 268 Cookie cookie = it.next(); 269 if (cookie.hasExpired()) { 270 it.remove(); 271 totalCount--; 272 log("Expired cookie removed", cookie, bucket); 273 } else { 274 removalQueue.add(cookie); 275 } 276 } 277 } 278 279 while (totalCount > TOTAL_COUNT_LOWER_THRESHOLD) { 280 Cookie cookie = removalQueue.remove(); 281 Map<Cookie,Cookie> bucket = buckets.get(cookie.getDomain()); 282 if (bucket != null) { 283 bucket.remove(cookie); 284 totalCount--; 285 log("Excess cookie removed", cookie, bucket); 286 } 287 } 288 } 289 290 private static final class RemovalComparator implements Comparator<Cookie> { 291 @Override 292 public int compare(Cookie c1, Cookie c2) { 293 return (int) (c1.getLastAccessTime() - c2.getLastAccessTime()); 294 } 295 } 296 297 /** 298 * Logs a cookie event. 299 */ 300 private void log(String message, Cookie cookie, 301 Map<Cookie,Cookie> bucket) 302 { 303 if (logger.isLoggable(Level.FINEST)) { 304 logger.finest("{0}: {1}, bucket size: {2}, total count: {3}", 305 new Object[] {message, cookie, bucket.size(), totalCount}); 306 } 307 } 308 }