/*
* Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package java.net;
import java.net.
URI;
import java.net.
CookieStore;
import java.net.
HttpCookie;
import java.net.
URISyntaxException;
import java.util.
List;
import java.util.
Map;
import java.util.
ArrayList;
import java.util.
HashMap;
import java.util.
Collections;
import java.util.
Iterator;
import java.util.concurrent.locks.
ReentrantLock;
/**
* A simple in-memory java.net.CookieStore implementation
*
* @author Edward Wang
* @since 1.6
*/
class
InMemoryCookieStore implements
CookieStore {
// the in-memory representation of cookies
private
List<
HttpCookie>
cookieJar = null;
// the cookies are indexed by its domain and associated uri (if present)
// CAUTION: when a cookie removed from main data structure (i.e. cookieJar),
// it won't be cleared in domainIndex & uriIndex. Double-check the
// presence of cookie when retrieve one form index store.
private
Map<
String,
List<
HttpCookie>>
domainIndex = null;
private
Map<
URI,
List<
HttpCookie>>
uriIndex = null;
// use ReentrantLock instead of syncronized for scalability
private
ReentrantLock lock = null;
/**
* The default ctor
*/
public
InMemoryCookieStore() {
cookieJar = new
ArrayList<
HttpCookie>();
domainIndex = new
HashMap<
String,
List<
HttpCookie>>();
uriIndex = new
HashMap<
URI,
List<
HttpCookie>>();
lock = new
ReentrantLock(false);
}
/**
* Add one cookie into cookie store.
*/
public void
add(
URI uri,
HttpCookie cookie) {
// pre-condition : argument can't be null
if (
cookie == null) {
throw new
NullPointerException("cookie is null");
}
lock.
lock();
try {
// remove the ole cookie if there has had one
cookieJar.
remove(
cookie);
// add new cookie if it has a non-zero max-age
if (
cookie.
getMaxAge() != 0) {
cookieJar.
add(
cookie);
// and add it to domain index
if (
cookie.
getDomain() != null) {
addIndex(
domainIndex,
cookie.
getDomain(),
cookie);
}
if (
uri != null) {
// add it to uri index, too
addIndex(
uriIndex,
getEffectiveURI(
uri),
cookie);
}
}
} finally {
lock.
unlock();
}
}
/**
* Get all cookies, which:
* 1) given uri domain-matches with, or, associated with
* given uri when added to the cookie store.
* 3) not expired.
* See RFC 2965 sec. 3.3.4 for more detail.
*/
public
List<
HttpCookie>
get(
URI uri) {
// argument can't be null
if (
uri == null) {
throw new
NullPointerException("uri is null");
}
List<
HttpCookie>
cookies = new
ArrayList<
HttpCookie>();
boolean
secureLink = "https".
equalsIgnoreCase(
uri.
getScheme());
lock.
lock();
try {
// check domainIndex first
getInternal1(
cookies,
domainIndex,
uri.
getHost(),
secureLink);
// check uriIndex then
getInternal2(
cookies,
uriIndex,
getEffectiveURI(
uri),
secureLink);
} finally {
lock.
unlock();
}
return
cookies;
}
/**
* Get all cookies in cookie store, except those have expired
*/
public
List<
HttpCookie>
getCookies() {
List<
HttpCookie>
rt;
lock.
lock();
try {
Iterator<
HttpCookie>
it =
cookieJar.
iterator();
while (
it.
hasNext()) {
if (
it.
next().
hasExpired()) {
it.
remove();
}
}
} finally {
rt =
Collections.
unmodifiableList(
cookieJar);
lock.
unlock();
}
return
rt;
}
/**
* Get all URIs, which are associated with at least one cookie
* of this cookie store.
*/
public
List<
URI>
getURIs() {
List<
URI>
uris = new
ArrayList<
URI>();
lock.
lock();
try {
Iterator<
URI>
it =
uriIndex.
keySet().
iterator();
while (
it.
hasNext()) {
URI uri =
it.
next();
List<
HttpCookie>
cookies =
uriIndex.
get(
uri);
if (
cookies == null ||
cookies.
size() == 0) {
// no cookies list or an empty list associated with
// this uri entry, delete it
it.
remove();
}
}
} finally {
uris.
addAll(
uriIndex.
keySet());
lock.
unlock();
}
return
uris;
}
/**
* Remove a cookie from store
*/
public boolean
remove(
URI uri,
HttpCookie ck) {
// argument can't be null
if (
ck == null) {
throw new
NullPointerException("cookie is null");
}
boolean
modified = false;
lock.
lock();
try {
modified =
cookieJar.
remove(
ck);
} finally {
lock.
unlock();
}
return
modified;
}
/**
* Remove all cookies in this cookie store.
*/
public boolean
removeAll() {
lock.
lock();
try {
if (
cookieJar.
isEmpty()) {
return false;
}
cookieJar.
clear();
domainIndex.
clear();
uriIndex.
clear();
} finally {
lock.
unlock();
}
return true;
}
/* ---------------- Private operations -------------- */
/*
* This is almost the same as HttpCookie.domainMatches except for
* one difference: It won't reject cookies when the 'H' part of the
* domain contains a dot ('.').
* I.E.: RFC 2965 section 3.3.2 says that if host is x.y.domain.com
* and the cookie domain is .domain.com, then it should be rejected.
* However that's not how the real world works. Browsers don't reject and
* some sites, like yahoo.com do actually expect these cookies to be
* passed along.
* And should be used for 'old' style cookies (aka Netscape type of cookies)
*/
private boolean
netscapeDomainMatches(
String domain,
String host)
{
if (
domain == null ||
host == null) {
return false;
}
// if there's no embedded dot in domain and domain is not .local
boolean
isLocalDomain = ".local".
equalsIgnoreCase(
domain);
int
embeddedDotInDomain =
domain.
indexOf('.');
if (
embeddedDotInDomain == 0) {
embeddedDotInDomain =
domain.
indexOf('.', 1);
}
if (!
isLocalDomain && (
embeddedDotInDomain == -1 ||
embeddedDotInDomain ==
domain.
length() - 1)) {
return false;
}
// if the host name contains no dot and the domain name is .local
int
firstDotInHost =
host.
indexOf('.');
if (
firstDotInHost == -1 &&
isLocalDomain) {
return true;
}
int
domainLength =
domain.
length();
int
lengthDiff =
host.
length() -
domainLength;
if (
lengthDiff == 0) {
// if the host name and the domain name are just string-compare euqal
return
host.
equalsIgnoreCase(
domain);
} else if (
lengthDiff > 0) {
// need to check H & D component
String H =
host.
substring(0,
lengthDiff);
String D =
host.
substring(
lengthDiff);
return (
D.
equalsIgnoreCase(
domain));
} else if (
lengthDiff == -1) {
// if domain is actually .host
return (
domain.
charAt(0) == '.' &&
host.
equalsIgnoreCase(
domain.
substring(1)));
}
return false;
}
private void
getInternal1(
List<
HttpCookie>
cookies,
Map<
String,
List<
HttpCookie>>
cookieIndex,
String host, boolean
secureLink) {
// Use a separate list to handle cookies that need to be removed so
// that there is no conflict with iterators.
ArrayList<
HttpCookie>
toRemove = new
ArrayList<
HttpCookie>();
for (
Map.
Entry<
String,
List<
HttpCookie>>
entry :
cookieIndex.
entrySet()) {
String domain =
entry.
getKey();
List<
HttpCookie>
lst =
entry.
getValue();
for (
HttpCookie c :
lst) {
if ((
c.
getVersion() == 0 &&
netscapeDomainMatches(
domain,
host)) ||
(
c.
getVersion() == 1 &&
HttpCookie.
domainMatches(
domain,
host))) {
if ((
cookieJar.
indexOf(
c) != -1)) {
// the cookie still in main cookie store
if (!
c.
hasExpired()) {
// don't add twice and make sure it's the proper
// security level
if ((
secureLink || !
c.
getSecure()) &&
!
cookies.
contains(
c)) {
cookies.
add(
c);
}
} else {
toRemove.
add(
c);
}
} else {
// the cookie has beed removed from main store,
// so also remove it from domain indexed store
toRemove.
add(
c);
}
}
}
// Clear up the cookies that need to be removed
for (
HttpCookie c :
toRemove) {
lst.
remove(
c);
cookieJar.
remove(
c);
}
toRemove.
clear();
}
}
// @param cookies [OUT] contains the found cookies
// @param cookieIndex the index
// @param comparator the prediction to decide whether or not
// a cookie in index should be returned
private <T> void
getInternal2(
List<
HttpCookie>
cookies,
Map<T,
List<
HttpCookie>>
cookieIndex,
Comparable<T>
comparator, boolean
secureLink)
{
for (T
index :
cookieIndex.
keySet()) {
if (
comparator.
compareTo(
index) == 0) {
List<
HttpCookie>
indexedCookies =
cookieIndex.
get(
index);
// check the list of cookies associated with this domain
if (
indexedCookies != null) {
Iterator<
HttpCookie>
it =
indexedCookies.
iterator();
while (
it.
hasNext()) {
HttpCookie ck =
it.
next();
if (
cookieJar.
indexOf(
ck) != -1) {
// the cookie still in main cookie store
if (!
ck.
hasExpired()) {
// don't add twice
if ((
secureLink || !
ck.
getSecure()) &&
!
cookies.
contains(
ck))
cookies.
add(
ck);
} else {
it.
remove();
cookieJar.
remove(
ck);
}
} else {
// the cookie has beed removed from main store,
// so also remove it from domain indexed store
it.
remove();
}
}
} // end of indexedCookies != null
} // end of comparator.compareTo(index) == 0
} // end of cookieIndex iteration
}
// add 'cookie' indexed by 'index' into 'indexStore'
private <T> void
addIndex(
Map<T,
List<
HttpCookie>>
indexStore,
T
index,
HttpCookie cookie)
{
if (
index != null) {
List<
HttpCookie>
cookies =
indexStore.
get(
index);
if (
cookies != null) {
// there may already have the same cookie, so remove it first
cookies.
remove(
cookie);
cookies.
add(
cookie);
} else {
cookies = new
ArrayList<
HttpCookie>();
cookies.
add(
cookie);
indexStore.
put(
index,
cookies);
}
}
}
//
// for cookie purpose, the effective uri should only be http://host
// the path will be taken into account when path-match algorithm applied
//
private
URI getEffectiveURI(
URI uri) {
URI effectiveURI = null;
try {
effectiveURI = new
URI("http",
uri.
getHost(),
null, // path component
null, // query component
null // fragment component
);
} catch (
URISyntaxException ignored) {
effectiveURI =
uri;
}
return
effectiveURI;
}
}