Path: blob/master/src/java.base/share/classes/java/net/InMemoryCookieStore.java
41152 views
/*1* Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.net;2627import java.net.URI;28import java.net.CookieStore;29import java.net.HttpCookie;30import java.net.URISyntaxException;31import java.util.List;32import java.util.Map;33import java.util.ArrayList;34import java.util.HashMap;35import java.util.Collections;36import java.util.Iterator;37import java.util.concurrent.locks.ReentrantLock;3839/**40* A simple in-memory java.net.CookieStore implementation41*42* @author Edward Wang43* @since 1.644*/45class InMemoryCookieStore implements CookieStore {46// the in-memory representation of cookies47private List<HttpCookie> cookieJar = null;4849// the cookies are indexed by its domain and associated uri (if present)50// CAUTION: when a cookie removed from main data structure (i.e. cookieJar),51// it won't be cleared in domainIndex & uriIndex. Double-check the52// presence of cookie when retrieve one form index store.53private Map<String, List<HttpCookie>> domainIndex = null;54private Map<URI, List<HttpCookie>> uriIndex = null;5556// use ReentrantLock instead of synchronized for scalability57private ReentrantLock lock = null;585960/**61* The default ctor62*/63public InMemoryCookieStore() {64cookieJar = new ArrayList<>();65domainIndex = new HashMap<>();66uriIndex = new HashMap<>();6768lock = new ReentrantLock(false);69}7071/**72* Add one cookie into cookie store.73*/74public void add(URI uri, HttpCookie cookie) {75// pre-condition : argument can't be null76if (cookie == null) {77throw new NullPointerException("cookie is null");78}798081lock.lock();82try {83// remove the ole cookie if there has had one84cookieJar.remove(cookie);8586// add new cookie if it has a non-zero max-age87if (cookie.getMaxAge() != 0) {88cookieJar.add(cookie);89// and add it to domain index90if (cookie.getDomain() != null) {91addIndex(domainIndex, cookie.getDomain(), cookie);92}93if (uri != null) {94// add it to uri index, too95addIndex(uriIndex, getEffectiveURI(uri), cookie);96}97}98} finally {99lock.unlock();100}101}102103104/**105* Get all cookies, which:106* 1) given uri domain-matches with, or, associated with107* given uri when added to the cookie store.108* 3) not expired.109* See RFC 2965 sec. 3.3.4 for more detail.110*/111public List<HttpCookie> get(URI uri) {112// argument can't be null113if (uri == null) {114throw new NullPointerException("uri is null");115}116117List<HttpCookie> cookies = new ArrayList<>();118boolean secureLink = "https".equalsIgnoreCase(uri.getScheme());119lock.lock();120try {121// check domainIndex first122getInternal1(cookies, domainIndex, uri.getHost(), secureLink);123// check uriIndex then124getInternal2(cookies, uriIndex, getEffectiveURI(uri), secureLink);125} finally {126lock.unlock();127}128129return cookies;130}131132/**133* Get all cookies in cookie store, except those have expired134*/135public List<HttpCookie> getCookies() {136List<HttpCookie> rt;137138lock.lock();139try {140Iterator<HttpCookie> it = cookieJar.iterator();141while (it.hasNext()) {142if (it.next().hasExpired()) {143it.remove();144}145}146} finally {147rt = Collections.unmodifiableList(cookieJar);148lock.unlock();149}150151return rt;152}153154/**155* Get all URIs, which are associated with at least one cookie156* of this cookie store.157*/158public List<URI> getURIs() {159List<URI> uris = new ArrayList<>();160161lock.lock();162try {163Iterator<URI> it = uriIndex.keySet().iterator();164while (it.hasNext()) {165URI uri = it.next();166List<HttpCookie> cookies = uriIndex.get(uri);167if (cookies == null || cookies.size() == 0) {168// no cookies list or an empty list associated with169// this uri entry, delete it170it.remove();171}172}173} finally {174uris.addAll(uriIndex.keySet());175lock.unlock();176}177178return uris;179}180181182/**183* Remove a cookie from store184*/185public boolean remove(URI uri, HttpCookie ck) {186// argument can't be null187if (ck == null) {188throw new NullPointerException("cookie is null");189}190191boolean modified = false;192lock.lock();193try {194modified = cookieJar.remove(ck);195} finally {196lock.unlock();197}198199return modified;200}201202203/**204* Remove all cookies in this cookie store.205*/206public boolean removeAll() {207lock.lock();208try {209if (cookieJar.isEmpty()) {210return false;211}212cookieJar.clear();213domainIndex.clear();214uriIndex.clear();215} finally {216lock.unlock();217}218219return true;220}221222223/* ---------------- Private operations -------------- */224225226/*227* This is almost the same as HttpCookie.domainMatches except for228* one difference: It won't reject cookies when the 'H' part of the229* domain contains a dot ('.').230* I.E.: RFC 2965 section 3.3.2 says that if host is x.y.domain.com231* and the cookie domain is .domain.com, then it should be rejected.232* However that's not how the real world works. Browsers don't reject and233* some sites, like yahoo.com do actually expect these cookies to be234* passed along.235* And should be used for 'old' style cookies (aka Netscape type of cookies)236*/237private boolean netscapeDomainMatches(String domain, String host)238{239if (domain == null || host == null) {240return false;241}242243// if there's no embedded dot in domain and domain is not .local244boolean isLocalDomain = ".local".equalsIgnoreCase(domain);245int embeddedDotInDomain = domain.indexOf('.');246if (embeddedDotInDomain == 0) {247embeddedDotInDomain = domain.indexOf('.', 1);248}249if (!isLocalDomain && (embeddedDotInDomain == -1 || embeddedDotInDomain == domain.length() - 1)) {250return false;251}252253// if the host name contains no dot and the domain name is .local254int firstDotInHost = host.indexOf('.');255if (firstDotInHost == -1 && isLocalDomain) {256return true;257}258259int domainLength = domain.length();260int lengthDiff = host.length() - domainLength;261if (lengthDiff == 0) {262// if the host name and the domain name are just string-compare equal263return host.equalsIgnoreCase(domain);264} else if (lengthDiff > 0) {265// need to check H & D component266String H = host.substring(0, lengthDiff);267String D = host.substring(lengthDiff);268269return (D.equalsIgnoreCase(domain));270} else if (lengthDiff == -1) {271// if domain is actually .host272return (domain.charAt(0) == '.' &&273host.equalsIgnoreCase(domain.substring(1)));274}275276return false;277}278279private void getInternal1(List<HttpCookie> cookies, Map<String, List<HttpCookie>> cookieIndex,280String host, boolean secureLink) {281// Use a separate list to handle cookies that need to be removed so282// that there is no conflict with iterators.283ArrayList<HttpCookie> toRemove = new ArrayList<>();284for (Map.Entry<String, List<HttpCookie>> entry : cookieIndex.entrySet()) {285String domain = entry.getKey();286List<HttpCookie> lst = entry.getValue();287for (HttpCookie c : lst) {288if ((c.getVersion() == 0 && netscapeDomainMatches(domain, host)) ||289(c.getVersion() == 1 && HttpCookie.domainMatches(domain, host))) {290if ((cookieJar.indexOf(c) != -1)) {291// the cookie still in main cookie store292if (!c.hasExpired()) {293// don't add twice and make sure it's the proper294// security level295if ((secureLink || !c.getSecure()) &&296!cookies.contains(c)) {297cookies.add(c);298}299} else {300toRemove.add(c);301}302} else {303// the cookie has been removed from main store,304// so also remove it from domain indexed store305toRemove.add(c);306}307}308}309// Clear up the cookies that need to be removed310for (HttpCookie c : toRemove) {311lst.remove(c);312cookieJar.remove(c);313314}315toRemove.clear();316}317}318319// @param cookies [OUT] contains the found cookies320// @param cookieIndex the index321// @param comparator the prediction to decide whether or not322// a cookie in index should be returned323private <T> void getInternal2(List<HttpCookie> cookies,324Map<T, List<HttpCookie>> cookieIndex,325Comparable<T> comparator, boolean secureLink)326{327for (T index : cookieIndex.keySet()) {328if (comparator.compareTo(index) == 0) {329List<HttpCookie> indexedCookies = cookieIndex.get(index);330// check the list of cookies associated with this domain331if (indexedCookies != null) {332Iterator<HttpCookie> it = indexedCookies.iterator();333while (it.hasNext()) {334HttpCookie ck = it.next();335if (cookieJar.indexOf(ck) != -1) {336// the cookie still in main cookie store337if (!ck.hasExpired()) {338// don't add twice339if ((secureLink || !ck.getSecure()) &&340!cookies.contains(ck))341cookies.add(ck);342} else {343it.remove();344cookieJar.remove(ck);345}346} else {347// the cookie has been removed from main store,348// so also remove it from domain indexed store349it.remove();350}351}352} // end of indexedCookies != null353} // end of comparator.compareTo(index) == 0354} // end of cookieIndex iteration355}356357// add 'cookie' indexed by 'index' into 'indexStore'358private <T> void addIndex(Map<T, List<HttpCookie>> indexStore,359T index,360HttpCookie cookie)361{362if (index != null) {363List<HttpCookie> cookies = indexStore.get(index);364if (cookies != null) {365// there may already have the same cookie, so remove it first366cookies.remove(cookie);367368cookies.add(cookie);369} else {370cookies = new ArrayList<>();371cookies.add(cookie);372indexStore.put(index, cookies);373}374}375}376377378//379// for cookie purpose, the effective uri should only be http://host380// the path will be taken into account when path-match algorithm applied381//382private URI getEffectiveURI(URI uri) {383URI effectiveURI = null;384try {385effectiveURI = new URI("http",386uri.getHost(),387null, // path component388null, // query component389null // fragment component390);391} catch (URISyntaxException ignored) {392effectiveURI = uri;393}394395return effectiveURI;396}397}398399400