/*
 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.webkit.network;

import com.sun.javafx.logging.PlatformLogger;
import com.sun.javafx.logging.PlatformLogger.Level;

import java.util.LinkedHashMap;
import java.util.Comparator;
import java.util.Collections;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;

A cookie store.
/** * A cookie store. */
final class CookieStore { private static final PlatformLogger logger = PlatformLogger.getLogger(CookieStore.class.getName()); private static final int MAX_BUCKET_SIZE = 50; private static final int TOTAL_COUNT_LOWER_THRESHOLD = 3000; private static final int TOTAL_COUNT_UPPER_THRESHOLD = 4000;
The mapping from domain names to cookie buckets. Each cookie bucket stores the cookies associated with the corresponding domain. Each cookie bucket is represented by a Map to facilitate retrieval of a cookie by another cookie with the same name, domain, and path.
/** * The mapping from domain names to cookie buckets. * Each cookie bucket stores the cookies associated with the * corresponding domain. Each cookie bucket is represented * by a Map<Cookie,Cookie> to facilitate retrieval of a cookie * by another cookie with the same name, domain, and path. */
private final Map<String,Map<Cookie,Cookie>> buckets = new HashMap<String,Map<Cookie,Cookie>>();
The total number of cookies currently in the store.
/** * The total number of cookies currently in the store. */
private int totalCount = 0;
Creates a new CookieStore.
/** * Creates a new {@code CookieStore}. */
CookieStore() { }
Returns the currently stored cookie with the same name, domain, and path as the given cookie.
/** * Returns the currently stored cookie with the same name, domain, and * path as the given cookie. */
Cookie get(Cookie cookie) { Map<Cookie,Cookie> bucket = buckets.get(cookie.getDomain()); if (bucket == null) { return null; } Cookie storedCookie = bucket.get(cookie); if (storedCookie == null) { return null; } if (storedCookie.hasExpired()) { bucket.remove(storedCookie); totalCount--; log("Expired cookie removed by get", storedCookie, bucket); return null; } return storedCookie; }
Returns all the currently stored cookies that match the given query.
/** * Returns all the currently stored cookies that match the given query. */
List<Cookie> get(String hostname, String path, boolean secureProtocol, boolean httpApi) { if (logger.isLoggable(Level.FINEST)) { logger.finest("hostname: [{0}], path: [{1}], " + "secureProtocol: [{2}], httpApi: [{3}]", new Object[] { hostname, path, secureProtocol, httpApi}); } ArrayList<Cookie> result = new ArrayList<Cookie>(); String domain = hostname; while (domain.length() > 0) { Map<Cookie,Cookie> bucket = buckets.get(domain); if (bucket != null) { find(result, bucket, hostname, path, secureProtocol, httpApi); } int nextPoint = domain.indexOf('.'); if (nextPoint != -1) { domain = domain.substring(nextPoint + 1); } else { break; } } Collections.sort(result, new GetComparator()); long currentTime = System.currentTimeMillis(); for (Cookie cookie : result) { cookie.setLastAccessTime(currentTime); } logger.finest("result: {0}", result); return result; }
Finds all the cookies that are stored in the given bucket and match the given query.
/** * Finds all the cookies that are stored in the given bucket and * match the given query. */
private void find(List<Cookie> list, Map<Cookie,Cookie> bucket, String hostname, String path, boolean secureProtocol, boolean httpApi) { Iterator<Cookie> it = bucket.values().iterator(); while (it.hasNext()) { Cookie cookie = it.next(); if (cookie.hasExpired()) { it.remove(); totalCount--; log("Expired cookie removed by find", cookie, bucket); continue; } if (cookie.getHostOnly()) { if (!hostname.equalsIgnoreCase(cookie.getDomain())) { continue; } } else { if (!Cookie.domainMatches(hostname, cookie.getDomain())) { continue; } } if (!Cookie.pathMatches(path, cookie.getPath())) { continue; } if (cookie.getSecureOnly() && !secureProtocol) { continue; } if (cookie.getHttpOnly() && !httpApi) { continue; } list.add(cookie); } } private static final class GetComparator implements Comparator<Cookie> { @Override public int compare(Cookie c1, Cookie c2) { int d = c2.getPath().length() - c1.getPath().length(); if (d != 0) { return d; } return c1.getCreationTime().compareTo(c2.getCreationTime()); } }
Stores the given cookie.
/** * Stores the given cookie. */
void put(Cookie cookie) { Map<Cookie,Cookie> bucket = buckets.get(cookie.getDomain()); if (bucket == null) { bucket = new LinkedHashMap<Cookie,Cookie>(20); buckets.put(cookie.getDomain(), bucket); } if (cookie.hasExpired()) { log("Cookie expired", cookie, bucket); if (bucket.remove(cookie) != null) { totalCount--; log("Expired cookie removed by put", cookie, bucket); } } else { if (bucket.put(cookie, cookie) == null) { totalCount++; log("Cookie added", cookie, bucket); if (bucket.size() > MAX_BUCKET_SIZE) { purge(bucket); } if (totalCount > TOTAL_COUNT_UPPER_THRESHOLD) { purge(); } } else { log("Cookie updated", cookie, bucket); } } }
Removes excess cookies from a given bucket.
/** * Removes excess cookies from a given bucket. */
private void purge(Map<Cookie,Cookie> bucket) { logger.finest("Purging bucket: {0}", bucket.values()); Cookie earliestCookie = null; Iterator<Cookie> it = bucket.values().iterator(); while (it.hasNext()) { Cookie cookie = it.next(); if (cookie.hasExpired()) { it.remove(); totalCount--; log("Expired cookie removed", cookie, bucket); } else { if (earliestCookie == null || cookie.getLastAccessTime() < earliestCookie.getLastAccessTime()) { earliestCookie = cookie; } } } if (bucket.size() > MAX_BUCKET_SIZE) { bucket.remove(earliestCookie); totalCount--; log("Excess cookie removed", earliestCookie, bucket); } }
Removes excess cookies globally.
/** * Removes excess cookies globally. */
private void purge() { logger.finest("Purging store"); Queue<Cookie> removalQueue = new PriorityQueue<Cookie>(totalCount / 2, new RemovalComparator()); for (Map.Entry<String,Map<Cookie,Cookie>> entry : buckets.entrySet()) { Map<Cookie,Cookie> bucket = entry.getValue(); Iterator<Cookie> it = bucket.values().iterator(); while (it.hasNext()) { Cookie cookie = it.next(); if (cookie.hasExpired()) { it.remove(); totalCount--; log("Expired cookie removed", cookie, bucket); } else { removalQueue.add(cookie); } } } while (totalCount > TOTAL_COUNT_LOWER_THRESHOLD) { Cookie cookie = removalQueue.remove(); Map<Cookie,Cookie> bucket = buckets.get(cookie.getDomain()); if (bucket != null) { bucket.remove(cookie); totalCount--; log("Excess cookie removed", cookie, bucket); } } } private static final class RemovalComparator implements Comparator<Cookie> { @Override public int compare(Cookie c1, Cookie c2) { return (int) (c1.getLastAccessTime() - c2.getLastAccessTime()); } }
Logs a cookie event.
/** * Logs a cookie event. */
private void log(String message, Cookie cookie, Map<Cookie,Cookie> bucket) { if (logger.isLoggable(Level.FINEST)) { logger.finest("{0}: {1}, bucket size: {2}, total count: {3}", new Object[] {message, cookie, bucket.size(), totalCount}); } } }