001 /*
002 * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.net;
027
028 import java.util.Map;
029 import java.util.List;
030 import java.util.Collections;
031 import java.util.Comparator;
032 import java.io.IOException;
033
034 /**
035 * CookieManager provides a concrete implementation of {@link CookieHandler},
036 * which separates the storage of cookies from the policy surrounding accepting
037 * and rejecting cookies. A CookieManager is initialized with a {@link CookieStore}
038 * which manages storage, and a {@link CookiePolicy} object, which makes
039 * policy decisions on cookie acceptance/rejection.
040 *
041 * <p> The HTTP cookie management in java.net package looks like:
042 * <blockquote>
043 * <pre>
044 * use
045 * CookieHandler <------- HttpURLConnection
046 * ^
047 * | impl
048 * | use
049 * CookieManager -------> CookiePolicy
050 * | use
051 * |--------> HttpCookie
052 * | ^
053 * | | use
054 * | use |
055 * |--------> CookieStore
056 * ^
057 * | impl
058 * |
059 * Internal in-memory implementation
060 * </pre>
061 * <ul>
062 * <li>
063 * CookieHandler is at the core of cookie management. User can call
064 * CookieHandler.setDefault to set a concrete CookieHanlder implementation
065 * to be used.
066 * </li>
067 * <li>
068 * CookiePolicy.shouldAccept will be called by CookieManager.put to see whether
069 * or not one cookie should be accepted and put into cookie store. User can use
070 * any of three pre-defined CookiePolicy, namely ACCEPT_ALL, ACCEPT_NONE and
071 * ACCEPT_ORIGINAL_SERVER, or user can define his own CookiePolicy implementation
072 * and tell CookieManager to use it.
073 * </li>
074 * <li>
075 * CookieStore is the place where any accepted HTTP cookie is stored in.
076 * If not specified when created, a CookieManager instance will use an internal
077 * in-memory implementation. Or user can implements one and tell CookieManager
078 * to use it.
079 * </li>
080 * <li>
081 * Currently, only CookieStore.add(URI, HttpCookie) and CookieStore.get(URI)
082 * are used by CookieManager. Others are for completeness and might be needed
083 * by a more sophisticated CookieStore implementation, e.g. a NetscapeCookieSotre.
084 * </li>
085 * </ul>
086 * </blockquote>
087 *
088 * <p>There're various ways user can hook up his own HTTP cookie management behavior, e.g.
089 * <blockquote>
090 * <ul>
091 * <li>Use CookieHandler.setDefault to set a brand new {@link CookieHandler} implementation
092 * <li>Let CookieManager be the default {@link CookieHandler} implementation,
093 * but implement user's own {@link CookieStore} and {@link CookiePolicy}
094 * and tell default CookieManager to use them:
095 * <blockquote><pre>
096 * // this should be done at the beginning of an HTTP session
097 * CookieHandler.setDefault(new CookieManager(new MyCookieStore(), new MyCookiePolicy()));
098 * </pre></blockquote>
099 * <li>Let CookieManager be the default {@link CookieHandler} implementation, but
100 * use customized {@link CookiePolicy}:
101 * <blockquote><pre>
102 * // this should be done at the beginning of an HTTP session
103 * CookieHandler.setDefault(new CookieManager());
104 * // this can be done at any point of an HTTP session
105 * ((CookieManager)CookieHandler.getDefault()).setCookiePolicy(new MyCookiePolicy());
106 * </pre></blockquote>
107 * </ul>
108 * </blockquote>
109 *
110 * <p>The implementation conforms to RFC 2965, section 3.3.
111 *
112 * @version %I%, %E%
113 * @author Edward Wang
114 * @since 1.6
115 */
116 public class CookieManager extends CookieHandler {
117 /* ---------------- Fields -------------- */
118
119 private CookiePolicy policyCallback;
120
121 private CookieStore cookieJar = null;
122
123 /* ---------------- Ctors -------------- */
124
125 /**
126 * Create a new cookie manager.
127 *
128 * <p>This constructor will create new cookie manager with default
129 * cookie store and accept policy. The effect is same as
130 * <tt>CookieManager(null, null)</tt>.
131 */
132 public CookieManager() {
133 this (null, null);
134 }
135
136 /**
137 * Create a new cookie manager with specified cookie store and cookie policy.
138 *
139 * @param store a <tt>CookieStore</tt> to be used by cookie manager.
140 * if <tt>null</tt>, cookie manager will use a default one,
141 * which is an in-memory CookieStore implmentation.
142 * @param cookiePolicy a <tt>CookiePolicy</tt> instance
143 * to be used by cookie manager as policy callback.
144 * if <tt>null</tt>, ACCEPT_ORIGINAL_SERVER will
145 * be used.
146 */
147 public CookieManager(CookieStore store, CookiePolicy cookiePolicy) {
148 // use default cookie policy if not specify one
149 policyCallback = (cookiePolicy == null) ? CookiePolicy.ACCEPT_ORIGINAL_SERVER
150 : cookiePolicy;
151
152 // if not specify CookieStore to use, use default one
153 if (store == null) {
154 cookieJar = new sun.net.www.protocol.http.InMemoryCookieStore();
155 } else {
156 cookieJar = store;
157 }
158 }
159
160 /* ---------------- Public operations -------------- */
161
162 /**
163 * To set the cookie policy of this cookie manager.
164 *
165 * <p> A instance of <tt>CookieManager</tt> will have
166 * cookie policy ACCEPT_ORIGINAL_SERVER by default. Users always
167 * can call this method to set another cookie policy.
168 *
169 * @param cookiePolicy the cookie policy. Can be <tt>null</tt>, which
170 * has no effects on current cookie policy.
171 */
172 public void setCookiePolicy(CookiePolicy cookiePolicy) {
173 if (cookiePolicy != null)
174 policyCallback = cookiePolicy;
175 }
176
177 /**
178 * To retrieve current cookie store.
179 *
180 * @return the cookie store currently used by cookie manager.
181 */
182 public CookieStore getCookieStore() {
183 return cookieJar;
184 }
185
186 public Map<String, List<String>> get(URI uri,
187 Map<String, List<String>> requestHeaders)
188 throws IOException {
189 // pre-condition check
190 if (uri == null || requestHeaders == null) {
191 throw new IllegalArgumentException("Argument is null");
192 }
193
194 Map<String, List<String>> cookieMap = new java.util.HashMap<String, List<String>>();
195 // if there's no default CookieStore, no way for us to get any cookie
196 if (cookieJar == null)
197 return Collections.unmodifiableMap(cookieMap);
198
199 List<HttpCookie> cookies = new java.util.ArrayList<HttpCookie>();
200 for (HttpCookie cookie : cookieJar.get(uri)) {
201 // apply path-matches rule (RFC 2965 sec. 3.3.4)
202 if (pathMatches(uri.getPath(), cookie.getPath())) {
203 cookies.add(cookie);
204 }
205 }
206
207 // apply sort rule (RFC 2965 sec. 3.3.4)
208 List<String> cookieHeader = sortByPath(cookies);
209
210 cookieMap.put("Cookie", cookieHeader);
211 return Collections.unmodifiableMap(cookieMap);
212 }
213
214 public void put(URI uri, Map<String, List<String>> responseHeaders)
215 throws IOException {
216 // pre-condition check
217 if (uri == null || responseHeaders == null) {
218 throw new IllegalArgumentException("Argument is null");
219 }
220
221 // if there's no default CookieStore, no need to remember any cookie
222 if (cookieJar == null)
223 return;
224
225 for (String headerKey : responseHeaders.keySet()) {
226 // RFC 2965 3.2.2, key must be 'Set-Cookie2'
227 // we also accept 'Set-Cookie' here for backward compatibility
228 if (headerKey == null
229 || !(headerKey.equalsIgnoreCase("Set-Cookie2") || headerKey
230 .equalsIgnoreCase("Set-Cookie"))) {
231 continue;
232 }
233
234 for (String headerValue : responseHeaders.get(headerKey)) {
235 try {
236 List<HttpCookie> cookies = HttpCookie
237 .parse(headerValue);
238 for (HttpCookie cookie : cookies) {
239 if (shouldAcceptInternal(uri, cookie)) {
240 cookieJar.add(uri, cookie);
241 }
242 }
243 } catch (IllegalArgumentException e) {
244 // invalid set-cookie header string
245 // no-op
246 }
247 }
248 }
249 }
250
251 /* ---------------- Private operations -------------- */
252
253 // to determine whether or not accept this cookie
254 private boolean shouldAcceptInternal(URI uri, HttpCookie cookie) {
255 try {
256 return policyCallback.shouldAccept(uri, cookie);
257 } catch (Exception ignored) { // pretect against malicious callback
258 return false;
259 }
260 }
261
262 /*
263 * path-matches algorithm, as defined by RFC 2965
264 */
265 private boolean pathMatches(String path, String pathToMatchWith) {
266 if (path == pathToMatchWith)
267 return true;
268 if (path == null || pathToMatchWith == null)
269 return false;
270 if (path.startsWith(pathToMatchWith))
271 return true;
272
273 return false;
274 }
275
276 /*
277 * sort cookies with respect to their path: those with more specific Path attributes
278 * precede those with less specific, as defined in RFC 2965 sec. 3.3.4
279 */
280 private List<String> sortByPath(List<HttpCookie> cookies) {
281 Collections.sort(cookies, new CookiePathComparator());
282
283 List<String> cookieHeader = new java.util.ArrayList<String>();
284 for (HttpCookie cookie : cookies) {
285 // Netscape cookie spec and RFC 2965 have different format of Cookie
286 // header; RFC 2965 requires a leading $Version="1" string while Netscape
287 // does not.
288 // The workaround here is to add a $Version="1" string in advance
289 if (cookies.indexOf(cookie) == 0 && cookie.getVersion() > 0) {
290 cookieHeader.add("$Version=\"1\"");
291 }
292
293 cookieHeader.add(cookie.toString());
294 }
295 return cookieHeader;
296 }
297
298 static class CookiePathComparator implements Comparator<HttpCookie> {
299 public int compare(HttpCookie c1, HttpCookie c2) {
300 if (c1 == c2)
301 return 0;
302 if (c1 == null)
303 return -1;
304 if (c2 == null)
305 return 1;
306
307 // path rule only applies to the cookies with same name
308 if (!c1.getName().equals(c2.getName()))
309 return 0;
310
311 // those with more specific Path attributes precede those with less specific
312 if (c1.getPath().startsWith(c2.getPath()))
313 return -1;
314 else if (c2.getPath().startsWith(c1.getPath()))
315 return 1;
316 else
317 return 0;
318 }
319 }
320 }
|