Showing the changes to java/pkg/java/net/InMemoryCookieStore.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,393 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) 2005, 2008, 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 java.net; | |
| 27 | + | |
| 28 | +import java.net.URI; | |
| 29 | +import java.net.CookieStore; | |
| 30 | +import java.net.HttpCookie; | |
| 31 | +import java.net.URISyntaxException; | |
| 32 | +import java.util.List; | |
| 33 | +import java.util.Map; | |
| 34 | +import java.util.ArrayList; | |
| 35 | +import java.util.HashMap; | |
| 36 | +import java.util.Collections; | |
| 37 | +import java.util.Iterator; | |
| 38 | +import java.util.concurrent.locks.ReentrantLock; | |
| 39 | + | |
| 40 | +/** | |
| 41 | + * A simple in-memory java.net.CookieStore implementation | |
| 42 | + * | |
| 43 | + * @author Edward Wang | |
| 44 | + * @since 1.6 | |
| 45 | + */ | |
| 46 | +class InMemoryCookieStore implements CookieStore { | |
| 47 | + // the in-memory representation of cookies | |
| 48 | + private List<HttpCookie> cookieJar = null; | |
| 49 | + | |
| 50 | + // the cookies are indexed by its domain and associated uri (if present) | |
| 51 | + // CAUTION: when a cookie removed from main data structure (i.e. cookieJar), | |
| 52 | + // it won't be cleared in domainIndex & uriIndex. Double-check the | |
| 53 | + // presence of cookie when retrieve one form index store. | |
| 54 | + private Map<String, List<HttpCookie>> domainIndex = null; | |
| 55 | + private Map<URI, List<HttpCookie>> uriIndex = null; | |
| 56 | + | |
| 57 | + // use ReentrantLock instead of syncronized for scalability | |
| 58 | + private ReentrantLock lock = null; | |
| 59 | + | |
| 60 | + | |
| 61 | + /** | |
| 62 | + * The default ctor | |
| 63 | + */ | |
| 64 | + public InMemoryCookieStore() { | |
| 65 | + cookieJar = new ArrayList<HttpCookie>(); | |
| 66 | + domainIndex = new HashMap<String, List<HttpCookie>>(); | |
| 67 | + uriIndex = new HashMap<URI, List<HttpCookie>>(); | |
| 68 | + | |
| 69 | + lock = new ReentrantLock(false); | |
| 70 | + } | |
| 71 | + | |
| 72 | + /** | |
| 73 | + * Add one cookie into cookie store. | |
| 74 | + */ | |
| 75 | + public void add(URI uri, HttpCookie cookie) { | |
| 76 | + // pre-condition : argument can't be null | |
| 77 | + if (cookie == null) { | |
| 78 | + throw new NullPointerException("cookie is null"); | |
| 79 | + } | |
| 80 | + | |
| 81 | + | |
| 82 | + lock.lock(); | |
| 83 | + try { | |
| 84 | + // remove the ole cookie if there has had one | |
| 85 | + cookieJar.remove(cookie); | |
| 86 | + | |
| 87 | + // add new cookie if it has a non-zero max-age | |
| 88 | + if (cookie.getMaxAge() != 0) { | |
| 89 | + cookieJar.add(cookie); | |
| 90 | + // and add it to domain index | |
| 91 | + if (cookie.getDomain() != null) { | |
| 92 | + addIndex(domainIndex, cookie.getDomain(), cookie); | |
| 93 | + } | |
| 94 | + // add it to uri index, too | |
| 95 | + addIndex(uriIndex, getEffectiveURI(uri), cookie); | |
| 96 | + } | |
| 97 | + } finally { | |
| 98 | + lock.unlock(); | |
| 99 | + } | |
| 100 | + } | |
| 101 | + | |
| 102 | + | |
| 103 | + /** | |
| 104 | + * Get all cookies, which: | |
| 105 | + * 1) given uri domain-matches with, or, associated with | |
| 106 | + * given uri when added to the cookie store. | |
| 107 | + * 3) not expired. | |
| 108 | + * See RFC 2965 sec. 3.3.4 for more detail. | |
| 109 | + */ | |
| 110 | + public List<HttpCookie> get(URI uri) { | |
| 111 | + // argument can't be null | |
| 112 | + if (uri == null) { | |
| 113 | + throw new NullPointerException("uri is null"); | |
| 114 | + } | |
| 115 | + | |
| 116 | + List<HttpCookie> cookies = new ArrayList<HttpCookie>(); | |
| 117 | + boolean secureLink = "https".equalsIgnoreCase(uri.getScheme()); | |
| 118 | + lock.lock(); | |
| 119 | + try { | |
| 120 | + // check domainIndex first | |
| 121 | + getInternal1(cookies, domainIndex, uri.getHost(), secureLink); | |
| 122 | + // check uriIndex then | |
| 123 | + getInternal2(cookies, uriIndex, getEffectiveURI(uri), secureLink); | |
| 124 | + } finally { | |
| 125 | + lock.unlock(); | |
| 126 | + } | |
| 127 | + | |
| 128 | + return cookies; | |
| 129 | + } | |
| 130 | + | |
| 131 | + /** | |
| 132 | + * Get all cookies in cookie store, except those have expired | |
| 133 | + */ | |
| 134 | + public List<HttpCookie> getCookies() { | |
| 135 | + List<HttpCookie> rt; | |
| 136 | + | |
| 137 | + lock.lock(); | |
| 138 | + try { | |
| 139 | + Iterator<HttpCookie> it = cookieJar.iterator(); | |
| 140 | + while (it.hasNext()) { | |
| 141 | + if (it.next().hasExpired()) { | |
| 142 | + it.remove(); | |
| 143 | + } | |
| 144 | + } | |
| 145 | + } finally { | |
| 146 | + rt = Collections.unmodifiableList(cookieJar); | |
| 147 | + lock.unlock(); | |
| 148 | + } | |
| 149 | + | |
| 150 | + return rt; | |
| 151 | + } | |
| 152 | + | |
| 153 | + /** | |
| 154 | + * Get all URIs, which are associated with at least one cookie | |
| 155 | + * of this cookie store. | |
| 156 | + */ | |
| 157 | + public List<URI> getURIs() { | |
| 158 | + List<URI> uris = new ArrayList<URI>(); | |
| 159 | + | |
| 160 | + lock.lock(); | |
| 161 | + try { | |
| 162 | + Iterator<URI> it = uriIndex.keySet().iterator(); | |
| 163 | + while (it.hasNext()) { | |
| 164 | + URI uri = it.next(); | |
| 165 | + List<HttpCookie> cookies = uriIndex.get(uri); | |
| 166 | + if (cookies == null || cookies.size() == 0) { | |
| 167 | + // no cookies list or an empty list associated with | |
| 168 | + // this uri entry, delete it | |
| 169 | + it.remove(); | |
| 170 | + } | |
| 171 | + } | |
| 172 | + } finally { | |
| 173 | + uris.addAll(uriIndex.keySet()); | |
| 174 | + lock.unlock(); | |
| 175 | + } | |
| 176 | + | |
| 177 | + return uris; | |
| 178 | + } | |
| 179 | + | |
| 180 | + | |
| 181 | + /** | |
| 182 | + * Remove a cookie from store | |
| 183 | + */ | |
| 184 | + public boolean remove(URI uri, HttpCookie ck) { | |
| 185 | + // argument can't be null | |
| 186 | + if (ck == null) { | |
| 187 | + throw new NullPointerException("cookie is null"); | |
| 188 | + } | |
| 189 | + | |
| 190 | + boolean modified = false; | |
| 191 | + lock.lock(); | |
| 192 | + try { | |
| 193 | + modified = cookieJar.remove(ck); | |
| 194 | + } finally { | |
| 195 | + lock.unlock(); | |
| 196 | + } | |
| 197 | + | |
| 198 | + return modified; | |
| 199 | + } | |
| 200 | + | |
| 201 | + | |
| 202 | + /** | |
| 203 | + * Remove all cookies in this cookie store. | |
| 204 | + */ | |
| 205 | + public boolean removeAll() { | |
| 206 | + lock.lock(); | |
| 207 | + try { | |
| 208 | + cookieJar.clear(); | |
| 209 | + domainIndex.clear(); | |
| 210 | + uriIndex.clear(); | |
| 211 | + } finally { | |
| 212 | + lock.unlock(); | |
| 213 | + } | |
| 214 | + | |
| 215 | + return true; | |
| 216 | + } | |
| 217 | + | |
| 218 | + | |
| 219 | + /* ---------------- Private operations -------------- */ | |
| 220 | + | |
| 221 | + | |
| 222 | + /* | |
| 223 | + * This is almost the same as HttpCookie.domainMatches except for | |
| 224 | + * one difference: It won't reject cookies when the 'H' part of the | |
| 225 | + * domain contains a dot ('.'). | |
| 226 | + * I.E.: RFC 2965 section 3.3.2 says that if host is x.y.domain.com | |
| 227 | + * and the cookie domain is .domain.com, then it should be rejected. | |
| 228 | + * However that's not how the real world works. Browsers don't reject and | |
| 229 | + * some sites, like yahoo.com do actually expect these cookies to be | |
| 230 | + * passed along. | |
| 231 | + * And should be used for 'old' style cookies (aka Netscape type of cookies) | |
| 232 | + */ | |
| 233 | + private boolean netscapeDomainMatches(String domain, String host) | |
| 234 | + { | |
| 235 | + if (domain == null || host == null) { | |
| 236 | + return false; | |
| 237 | + } | |
| 238 | + | |
| 239 | + // if there's no embedded dot in domain and domain is not .local | |
| 240 | + boolean isLocalDomain = ".local".equalsIgnoreCase(domain); | |
| 241 | + int embeddedDotInDomain = domain.indexOf('.'); | |
| 242 | + if (embeddedDotInDomain == 0) { | |
| 243 | + embeddedDotInDomain = domain.indexOf('.', 1); | |
| 244 | + } | |
| 245 | + if (!isLocalDomain && (embeddedDotInDomain == -1 || embeddedDotInDomain == domain.length() - 1)) { | |
| 246 | + return false; | |
| 247 | + } | |
| 248 | + | |
| 249 | + // if the host name contains no dot and the domain name is .local | |
| 250 | + int firstDotInHost = host.indexOf('.'); | |
| 251 | + if (firstDotInHost == -1 && isLocalDomain) { | |
| 252 | + return true; | |
| 253 | + } | |
| 254 | + | |
| 255 | + int domainLength = domain.length(); | |
| 256 | + int lengthDiff = host.length() - domainLength; | |
| 257 | + if (lengthDiff == 0) { | |
| 258 | + // if the host name and the domain name are just string-compare euqal | |
| 259 | + return host.equalsIgnoreCase(domain); | |
| 260 | + } else if (lengthDiff > 0) { | |
| 261 | + // need to check H & D component | |
| 262 | + String H = host.substring(0, lengthDiff); | |
| 263 | + String D = host.substring(lengthDiff); | |
| 264 | + | |
| 265 | + return (D.equalsIgnoreCase(domain)); | |
| 266 | + } else if (lengthDiff == -1) { | |
| 267 | + // if domain is actually .host | |
| 268 | + return (domain.charAt(0) == '.' && | |
| 269 | + host.equalsIgnoreCase(domain.substring(1))); | |
| 270 | + } | |
| 271 | + | |
| 272 | + return false; | |
| 273 | + } | |
| 274 | + | |
| 275 | + private void getInternal1(List<HttpCookie> cookies, Map<String, List<HttpCookie>> cookieIndex, | |
| 276 | + String host, boolean secureLink) { | |
| 277 | + // Use a separate list to handle cookies that need to be removed so | |
| 278 | + // that there is no conflict with iterators. | |
| 279 | + ArrayList<HttpCookie> toRemove = new ArrayList<HttpCookie>(); | |
| 280 | + for (Map.Entry<String, List<HttpCookie>> entry : cookieIndex.entrySet()) { | |
| 281 | + String domain = entry.getKey(); | |
| 282 | + List<HttpCookie> lst = entry.getValue(); | |
| 283 | + for (HttpCookie c : lst) { | |
| 284 | + if ((c.getVersion() == 0 && netscapeDomainMatches(domain, host)) || | |
| 285 | + (c.getVersion() == 1 && HttpCookie.domainMatches(domain, host))) { | |
| 286 | + if ((cookieJar.indexOf(c) != -1)) { | |
| 287 | + // the cookie still in main cookie store | |
| 288 | + if (!c.hasExpired()) { | |
| 289 | + // don't add twice and make sure it's the proper | |
| 290 | + // security level | |
| 291 | + if ((secureLink || !c.getSecure()) && | |
| 292 | + !cookies.contains(c)) { | |
| 293 | + cookies.add(c); | |
| 294 | + } | |
| 295 | + } else { | |
| 296 | + toRemove.add(c); | |
| 297 | + } | |
| 298 | + } else { | |
| 299 | + // the cookie has beed removed from main store, | |
| 300 | + // so also remove it from domain indexed store | |
| 301 | + toRemove.add(c); | |
| 302 | + } | |
| 303 | + } | |
| 304 | + } | |
| 305 | + // Clear up the cookies that need to be removed | |
| 306 | + for (HttpCookie c : toRemove) { | |
| 307 | + lst.remove(c); | |
| 308 | + cookieJar.remove(c); | |
| 309 | + | |
| 310 | + } | |
| 311 | + toRemove.clear(); | |
| 312 | + } | |
| 313 | + } | |
| 314 | + | |
| 315 | + // @param cookies [OUT] contains the found cookies | |
| 316 | + // @param cookieIndex the index | |
| 317 | + // @param comparator the prediction to decide whether or not | |
| 318 | + // a cookie in index should be returned | |
| 319 | + private <T> void getInternal2(List<HttpCookie> cookies, | |
| 320 | + Map<T, List<HttpCookie>> cookieIndex, | |
| 321 | + Comparable<T> comparator, boolean secureLink) | |
| 322 | + { | |
| 323 | + for (T index : cookieIndex.keySet()) { | |
| 324 | + if (comparator.compareTo(index) == 0) { | |
| 325 | + List<HttpCookie> indexedCookies = cookieIndex.get(index); | |
| 326 | + // check the list of cookies associated with this domain | |
| 327 | + if (indexedCookies != null) { | |
| 328 | + Iterator<HttpCookie> it = indexedCookies.iterator(); | |
| 329 | + while (it.hasNext()) { | |
| 330 | + HttpCookie ck = it.next(); | |
| 331 | + if (cookieJar.indexOf(ck) != -1) { | |
| 332 | + // the cookie still in main cookie store | |
| 333 | + if (!ck.hasExpired()) { | |
| 334 | + // don't add twice | |
| 335 | + if ((secureLink || !ck.getSecure()) && | |
| 336 | + !cookies.contains(ck)) | |
| 337 | + cookies.add(ck); | |
| 338 | + } else { | |
| 339 | + it.remove(); | |
| 340 | + cookieJar.remove(ck); | |
| 341 | + } | |
| 342 | + } else { | |
| 343 | + // the cookie has beed removed from main store, | |
| 344 | + // so also remove it from domain indexed store | |
| 345 | + it.remove(); | |
| 346 | + } | |
| 347 | + } | |
| 348 | + } // end of indexedCookies != null | |
| 349 | + } // end of comparator.compareTo(index) == 0 | |
| 350 | + } // end of cookieIndex iteration | |
| 351 | + } | |
| 352 | + | |
| 353 | + // add 'cookie' indexed by 'index' into 'indexStore' | |
| 354 | + private <T> void addIndex(Map<T, List<HttpCookie>> indexStore, | |
| 355 | + T index, | |
| 356 | + HttpCookie cookie) | |
| 357 | + { | |
| 358 | + if (index != null) { | |
| 359 | + List<HttpCookie> cookies = indexStore.get(index); | |
| 360 | + if (cookies != null) { | |
| 361 | + // there may already have the same cookie, so remove it first | |
| 362 | + cookies.remove(cookie); | |
| 363 | + | |
| 364 | + cookies.add(cookie); | |
| 365 | + } else { | |
| 366 | + cookies = new ArrayList<HttpCookie>(); | |
| 367 | + cookies.add(cookie); | |
| 368 | + indexStore.put(index, cookies); | |
| 369 | + } | |
| 370 | + } | |
| 371 | + } | |
| 372 | + | |
| 373 | + | |
| 374 | + // | |
| 375 | + // for cookie purpose, the effective uri should only be http://host | |
| 376 | + // the path will be taken into account when path-match algorithm applied | |
| 377 | + // | |
| 378 | + private URI getEffectiveURI(URI uri) { | |
| 379 | + URI effectiveURI = null; | |
| 380 | + try { | |
| 381 | + effectiveURI = new URI("http", | |
| 382 | + uri.getHost(), | |
| 383 | + null, // path component | |
| 384 | + null, // query component | |
| 385 | + null // fragment component | |
| 386 | + ); | |
| 387 | + } catch (URISyntaxException ignored) { | |
| 388 | + effectiveURI = uri; | |
| 389 | + } | |
| 390 | + | |
| 391 | + return effectiveURI; | |
| 392 | + } | |
| 393 | +} | |