View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit;
16  
17  import java.io.Serializable;
18  import java.util.Collections;
19  import java.util.Date;
20  import java.util.Iterator;
21  import java.util.LinkedHashSet;
22  import java.util.Objects;
23  import java.util.Set;
24  
25  import org.htmlunit.http.Cookie;
26  
27  /**
28   * Manages cookies for a {@link WebClient}.
29   *
30   * <p>This class is thread-safe: all mutator and accessor methods are
31   * synchronized on the {@code CookieManager} instance.
32   * </p>
33   *
34   * <p>Cookie support can be turned off via {@link #setCookiesEnabled(boolean)}.
35   * While disabled, this manager ignores all cookie operations: additions and
36   * removals become no-ops, and accessors behave as if the cookie store were
37   * empty. Subclasses that override these methods must preserve this contract.
38   * </p>
39   *
40   * @author Daniel Gredler
41   * @author Ahmed Ashour
42   * @author Nicolas Belisle
43   * @author Ronald Brill
44   */
45  public class CookieManager implements Serializable {
46  
47      /** Whether or not cookies are enabled. */
48      private boolean cookiesEnabled_;
49  
50      /** The cookies added to this cookie manager. */
51      private final Set<Cookie> cookies_ = new LinkedHashSet<>();
52  
53      /**
54       * Creates a new instance.
55       */
56      public CookieManager() {
57          cookiesEnabled_ = true;
58      }
59  
60      /**
61       * Enables or disables cookie support. Cookies are enabled by default.
62       * Disabling does not clear existing cookies; it only suppresses all
63       * cookie operations until re-enabled.
64       *
65       * @param enabled {@code true} to enable cookie support, {@code false} to disable it
66       */
67      public synchronized void setCookiesEnabled(final boolean enabled) {
68          cookiesEnabled_ = enabled;
69      }
70  
71      /**
72       * Returns {@code true} if cookies are enabled. Cookies are enabled by default.
73       *
74       * @return {@code true} if cookies are enabled, {@code false} otherwise
75       */
76      public synchronized boolean isCookiesEnabled() {
77          return cookiesEnabled_;
78      }
79  
80      /**
81       * Returns a snapshot of the currently configured cookies as an
82       * unmodifiable set. The returned set is a copy and will not reflect
83       * later changes to this manager's cookie store.
84       *
85       * @return the currently configured cookies, in an unmodifiable set;
86       *         empty if cookie support is disabled
87       */
88      public synchronized Set<Cookie> getCookies() {
89          if (!isCookiesEnabled()) {
90              return Collections.emptySet();
91          }
92  
93          final Set<Cookie> copy = new LinkedHashSet<>(cookies_);
94          return Collections.unmodifiableSet(copy);
95      }
96  
97      /**
98       * Removes all cookies whose expiration date is strictly before the
99       * given date. A cookie expiring at exactly {@code date} is not
100      * considered expired by this comparison (consistent with RFC 6265's
101      * treatment of cookie expiry).
102      *
103      * @param date the date to compare against; if {@code null}, this method
104      *             does nothing and returns {@code false}
105      * @return {@code true} if one or more cookies were found expired and removed;
106      *         {@code false} otherwise, or if cookie support is disabled
107      */
108     public synchronized boolean clearExpired(final Date date) {
109         if (!isCookiesEnabled()) {
110             return false;
111         }
112 
113         if (date == null) {
114             return false;
115         }
116 
117         boolean foundExpired = false;
118         for (final Iterator<Cookie> iter = cookies_.iterator(); iter.hasNext();) {
119             final Cookie cookie = iter.next();
120             if (cookie.getExpires() != null && date.after(cookie.getExpires())) {
121                 iter.remove();
122                 foundExpired = true;
123             }
124         }
125         return foundExpired;
126     }
127 
128     /**
129      * Returns the currently configured cookie with the specified name, or
130      * {@code null} if none exists. If multiple stored cookies happen to
131      * share a name (e.g., differing by domain or path), the first match
132      * encountered in iteration order is returned.
133      *
134      * @param name the cookie name to look up; may be {@code null}
135      * @return the matching cookie, or {@code null} if none exists or cookie
136      *         support is disabled
137      */
138     public synchronized Cookie getCookie(final String name) {
139         if (!isCookiesEnabled()) {
140             return null;
141         }
142 
143         for (final Cookie cookie : cookies_) {
144             if (Objects.equals(cookie.getName(), name)) {
145                 return cookie;
146             }
147         }
148         return null;
149     }
150 
151     /**
152      * Adds the specified cookie, replacing any existing cookie considered
153      * equal to it. If the cookie is already expired relative to the current
154      * time, it replaces the old entry (if any) but is not itself re-added —
155      * this is the mechanism by which a cookie can be deleted by supplying a
156      * past expiration date, per RFC 6265.
157      *
158      * @param cookie the cookie to add
159      */
160     public synchronized void addCookie(final Cookie cookie) {
161         if (!isCookiesEnabled()) {
162             return;
163         }
164 
165         cookies_.remove(cookie);
166 
167         // don't add expired cookie
168         if (cookie.getExpires() == null || cookie.getExpires().after(new Date())) {
169             cookies_.add(cookie);
170         }
171     }
172 
173     /**
174      * Removes the specified cookie, if present. Does nothing if cookie support
175      * is disabled.
176      *
177      * @param cookie the cookie to remove; may be {@code null}, in which case
178      *               this method does nothing
179      */
180     public synchronized void removeCookie(final Cookie cookie) {
181         if (!isCookiesEnabled()) {
182             return;
183         }
184 
185         cookies_.remove(cookie);
186     }
187 
188     /**
189      * Removes all cookies from this manager. Does nothing if cookie support
190      * is disabled.
191      */
192     public synchronized void clearCookies() {
193         if (!isCookiesEnabled()) {
194             return;
195         }
196 
197         cookies_.clear();
198     }
199 }