Home | History | Annotate | Download | only in net
      1 /* Licensed to the Apache Software Foundation (ASF) under one or more
      2  * contributor license agreements.  See the NOTICE file distributed with
      3  * this work for additional information regarding copyright ownership.
      4  * The ASF licenses this file to You under the Apache License, Version 2.0
      5  * (the "License"); you may not use this file except in compliance with
      6  * the License.  You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package java.net;
     18 
     19 import java.io.IOException;
     20 import java.util.ArrayList;
     21 import java.util.Collections;
     22 import java.util.List;
     23 import java.util.Map;
     24 
     25 /**
     26  * This class provides a concrete implementation of CookieHandler. It separates
     27  * the storage of cookies from the policy which decides to accept or deny
     28  * cookies. The constructor can have two arguments: a CookieStore and a
     29  * CookiePolicy. The former is in charge of cookie storage and the latter makes
     30  * decision on acceptance/rejection.
     31  *
     32  * CookieHandler is in the center of cookie management. User can make use of
     33  * CookieHandler.setDefault to set a CookieManager as the default one used.
     34  *
     35  * CookieManager.put uses CookiePolicy.shouldAccept to decide whether to put
     36  * some cookies into a cookie store. Three built-in CookiePolicy is defined:
     37  * ACCEPT_ALL, ACCEPT_NONE and ACCEPT_ORIGINAL_SERVER. Users can also customize
     38  * the policy by implementing CookiePolicy. Any accepted HTTP cookie is stored
     39  * in CookieStore and users can also have their own implementation. Up to now,
     40  * Only add(URI, HttpCookie) and get(URI) are used by CookieManager. Other
     41  * methods in this class may probably be used in a more complicated
     42  * implementation.
     43  *
     44  * There are many ways to customize user's own HTTP cookie management:
     45  *
     46  * First, call CookieHandler.setDefault to set a new CookieHandler
     47  * implementation. Second, call CookieHandler.getDefault to use CookieManager.
     48  * The CookiePolicy and CookieStore used are customized. Third, use the
     49  * customized CookiePolicy and the CookieStore.
     50  *
     51  * This implementation conforms to <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a> section 3.3.
     52  *
     53  * @since 1.6
     54  */
     55 public class CookieManager extends CookieHandler {
     56     private CookieStore store;
     57 
     58     private CookiePolicy policy;
     59 
     60     private static final String VERSION_ZERO_HEADER = "Set-cookie";
     61 
     62     private static final String VERSION_ONE_HEADER = "Set-cookie2";
     63 
     64     /**
     65      * Constructs a new cookie manager.
     66      *
     67      * The invocation of this constructor is the same as the invocation of
     68      * CookieManager(null, null).
     69      *
     70      */
     71     public CookieManager() {
     72         this(null, null);
     73     }
     74 
     75     /**
     76      * Constructs a new cookie manager using a specified cookie store and a
     77      * cookie policy.
     78      *
     79      * @param store
     80      *            a CookieStore to be used by cookie manager. The manager will
     81      *            use a default one if the arg is null.
     82      * @param cookiePolicy
     83      *            a CookiePolicy to be used by cookie manager
     84      *            ACCEPT_ORIGINAL_SERVER will be used if the arg is null.
     85      */
     86     public CookieManager(CookieStore store, CookiePolicy cookiePolicy) {
     87         this.store = store == null ? new CookieStoreImpl() : store;
     88         policy = cookiePolicy == null ? CookiePolicy.ACCEPT_ORIGINAL_SERVER
     89                 : cookiePolicy;
     90     }
     91 
     92     /**
     93      * Searches and gets all cookies in the cache by the specified uri in the
     94      * request header.
     95      *
     96      * @param uri
     97      *            the specified uri to search for
     98      * @param requestHeaders
     99      *            a list of request headers
    100      * @return a map that record all such cookies, the map is unchangeable
    101      * @throws IOException
    102      *             if some error of I/O operation occurs
    103      */
    104     @Override
    105     public Map<String, List<String>> get(URI uri,
    106             Map<String, List<String>> requestHeaders) throws IOException {
    107         if (uri == null || requestHeaders == null) {
    108             throw new IllegalArgumentException();
    109         }
    110 
    111         List<HttpCookie> result = new ArrayList<HttpCookie>();
    112         for (HttpCookie cookie : store.get(uri)) {
    113             if (HttpCookie.pathMatches(cookie, uri)
    114                     && HttpCookie.secureMatches(cookie, uri)
    115                     && HttpCookie.portMatches(cookie, uri)) {
    116                 result.add(cookie);
    117             }
    118         }
    119 
    120         return cookiesToHeaders(result);
    121     }
    122 
    123     private static Map<String, List<String>> cookiesToHeaders(List<HttpCookie> cookies) {
    124         if (cookies.isEmpty()) {
    125             return Collections.emptyMap();
    126         }
    127 
    128         StringBuilder result = new StringBuilder();
    129 
    130         // If all cookies are version 1, add a version 1 header. No header for version 0 cookies.
    131         int minVersion = 1;
    132         for (HttpCookie cookie : cookies) {
    133             minVersion = Math.min(minVersion, cookie.getVersion());
    134         }
    135         if (minVersion == 1) {
    136             result.append("$Version=\"1\"; ");
    137         }
    138 
    139         result.append(cookies.get(0).toString());
    140         for (int i = 1; i < cookies.size(); i++) {
    141             result.append("; ").append(cookies.get(i).toString());
    142         }
    143 
    144         return Collections.singletonMap("Cookie", Collections.singletonList(result.toString()));
    145     }
    146 
    147     /**
    148      * Sets cookies according to uri and responseHeaders
    149      *
    150      * @param uri
    151      *            the specified uri
    152      * @param responseHeaders
    153      *            a list of request headers
    154      * @throws IOException
    155      *             if some error of I/O operation occurs
    156      */
    157     @Override
    158     public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException {
    159         if (uri == null || responseHeaders == null) {
    160             throw new IllegalArgumentException();
    161         }
    162 
    163         // parse and construct cookies according to the map
    164         List<HttpCookie> cookies = parseCookie(responseHeaders);
    165         for (HttpCookie cookie : cookies) {
    166 
    167             // if the cookie doesn't have a domain, set one. The policy will do validation.
    168             if (cookie.getDomain() == null) {
    169                 cookie.setDomain(uri.getHost());
    170             }
    171 
    172             // if the cookie doesn't have a path, set one. If it does, validate it.
    173             if (cookie.getPath() == null) {
    174                 cookie.setPath(pathToCookiePath(uri.getPath()));
    175             } else if (!HttpCookie.pathMatches(cookie, uri)) {
    176                 continue;
    177             }
    178 
    179             // if the cookie has the placeholder port list "", set the port. Otherwise validate it.
    180             if ("".equals(cookie.getPortlist())) {
    181                 cookie.setPortlist(Integer.toString(uri.getEffectivePort()));
    182             } else if (cookie.getPortlist() != null && !HttpCookie.portMatches(cookie, uri)) {
    183                 continue;
    184             }
    185 
    186             // if the cookie conforms to the policy, add it into the store
    187             if (policy.shouldAccept(uri, cookie)) {
    188                 store.add(uri, cookie);
    189             }
    190         }
    191     }
    192 
    193     /**
    194      * Returns a cookie-safe path by truncating everything after the last "/".
    195      * When request path like "/foo/bar.html" yields a cookie, that cookie's
    196      * default path is "/foo/".
    197      */
    198     static String pathToCookiePath(String path) {
    199         if (path == null) {
    200             return "/";
    201         }
    202         int lastSlash = path.lastIndexOf('/'); // -1 yields the empty string
    203         return path.substring(0, lastSlash + 1);
    204     }
    205 
    206     private static List<HttpCookie> parseCookie(Map<String, List<String>> responseHeaders) {
    207         List<HttpCookie> cookies = new ArrayList<HttpCookie>();
    208         for (Map.Entry<String, List<String>> entry : responseHeaders.entrySet()) {
    209             String key = entry.getKey();
    210             // Only "Set-cookie" and "Set-cookie2" pair will be parsed
    211             if (key != null && (key.equalsIgnoreCase(VERSION_ZERO_HEADER)
    212                     || key.equalsIgnoreCase(VERSION_ONE_HEADER))) {
    213                 // parse list elements one by one
    214                 for (String cookieStr : entry.getValue()) {
    215                     try {
    216                         for (HttpCookie cookie : HttpCookie.parse(cookieStr)) {
    217                             cookies.add(cookie);
    218                         }
    219                     } catch (IllegalArgumentException ignored) {
    220                         // this string is invalid, jump to the next one.
    221                     }
    222                 }
    223             }
    224         }
    225         return cookies;
    226     }
    227 
    228     /**
    229      * Sets the cookie policy of this cookie manager.
    230      *
    231      * ACCEPT_ORIGINAL_SERVER is the default policy for CookieManager.
    232      *
    233      * @param cookiePolicy
    234      *            the cookie policy. if null, the original policy will not be
    235      *            changed.
    236      */
    237     public void setCookiePolicy(CookiePolicy cookiePolicy) {
    238         if (cookiePolicy != null) {
    239             policy = cookiePolicy;
    240         }
    241     }
    242 
    243     /**
    244      * Gets current cookie store.
    245      *
    246      * @return the cookie store currently used by cookie manager.
    247      */
    248     public CookieStore getCookieStore() {
    249         return store;
    250     }
    251 }
    252