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.LinkedHashSet;
21  import java.util.Objects;
22  import java.util.Set;
23  import java.util.concurrent.locks.ReentrantReadWriteLock;
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 operations are
31   * protected via a {@link ReentrantReadWriteLock}.
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      private final ReentrantReadWriteLock lock_ = new ReentrantReadWriteLock();
48  
49      /** Whether or not cookies are enabled. */
50      private boolean cookiesEnabled_;
51  
52      /** The cookies added to this cookie manager. */
53      private final Set<Cookie> cookies_ = new LinkedHashSet<>();
54  
55      /**
56       * Creates a new instance.
57       */
58      public CookieManager() {
59          cookiesEnabled_ = true;
60      }
61  
62      /**
63       * Enables or disables cookie support. Cookies are enabled by default.
64       * Disabling does not clear existing cookies; it only suppresses all
65       * cookie operations until re-enabled.
66       *
67       * @param enabled {@code true} to enable cookie support, {@code false} to disable it
68       */
69      public void setCookiesEnabled(final boolean enabled) {
70          lock_.writeLock().lock();
71          try {
72              cookiesEnabled_ = enabled;
73          }
74          finally {
75              lock_.writeLock().unlock();
76          }
77      }
78  
79      /**
80       * Returns {@code true} if cookies are enabled. Cookies are enabled by default.
81       *
82       * @return {@code true} if cookies are enabled, {@code false} otherwise
83       */
84      public boolean isCookiesEnabled() {
85          lock_.readLock().lock();
86          try {
87              return cookiesEnabled_;
88          }
89          finally {
90              lock_.readLock().unlock();
91          }
92      }
93  
94      /**
95       * Returns a snapshot of the currently configured cookies as an
96       * unmodifiable set. The returned set is a copy and will not reflect
97       * later changes to this manager's cookie store.
98       *
99       * @return the currently configured cookies, in an unmodifiable set;
100      *         empty if cookie support is disabled
101      */
102     public Set<Cookie> getCookies() {
103         lock_.readLock().lock();
104         try {
105             if (!cookiesEnabled_) {
106                 return Collections.emptySet();
107             }
108 
109             final Set<Cookie> copy = new LinkedHashSet<>(cookies_);
110             return Collections.unmodifiableSet(copy);
111         }
112         finally {
113             lock_.readLock().unlock();
114         }
115     }
116 
117     /**
118      * Removes all cookies whose expiration date is strictly before the
119      * given date. A cookie expiring at exactly {@code date} is not
120      * considered expired by this comparison (consistent with RFC 6265's
121      * treatment of cookie expiry).
122      *
123      * @param date the date to compare against; if {@code null}, this method
124      *             does nothing and returns {@code false}
125      * @return {@code true} if one or more cookies were found expired and removed;
126      *         {@code false} otherwise, or if cookie support is disabled
127      */
128     public boolean clearExpired(final Date date) {
129         lock_.writeLock().lock();
130         try {
131             if (!cookiesEnabled_ || date == null) {
132                 return false;
133             }
134 
135             return cookies_.removeIf(cookie ->
136                 cookie.getExpires() != null && date.after(cookie.getExpires()));
137         }
138         finally {
139             lock_.writeLock().unlock();
140         }
141     }
142 
143     /**
144      * Returns the currently configured cookie with the specified name, or
145      * {@code null} if none exists. If multiple stored cookies happen to
146      * share a name (e.g., differing by domain or path), the first match
147      * encountered in iteration order is returned.
148      *
149      * @param name the cookie name to look up; may be {@code null}
150      * @return the matching cookie, or {@code null} if none exists or cookie
151      *         support is disabled
152      */
153     public Cookie getCookie(final String name) {
154         lock_.readLock().lock();
155         try {
156             if (!cookiesEnabled_) {
157                 return null;
158             }
159 
160             for (final Cookie cookie : cookies_) {
161                 if (Objects.equals(cookie.getName(), name)) {
162                     return cookie;
163                 }
164             }
165             return null;
166         }
167         finally {
168             lock_.readLock().unlock();
169         }
170     }
171 
172     /**
173      * Adds the specified cookie, replacing any existing cookie considered
174      * equal to it. If the cookie is already expired relative to the current
175      * time, it replaces the old entry (if any) but is not itself re-added —
176      * this is the mechanism by which a cookie can be deleted by supplying a
177      * past expiration date, per RFC 6265.
178      *
179      * @param cookie the cookie to add
180      */
181     public void addCookie(final Cookie cookie) {
182         lock_.writeLock().lock();
183         try {
184             if (!cookiesEnabled_) {
185                 return;
186             }
187 
188             cookies_.remove(cookie);
189 
190             // don't add expired cookie
191             if (cookie.getExpires() == null || cookie.getExpires().after(new Date())) {
192                 cookies_.add(cookie);
193             }
194         }
195         finally {
196             lock_.writeLock().unlock();
197         }
198     }
199 
200     /**
201      * Removes the specified cookie, if present. Does nothing if cookie support
202      * is disabled.
203      *
204      * @param cookie the cookie to remove; may be {@code null}, in which case
205      *               this method does nothing
206      */
207     public void removeCookie(final Cookie cookie) {
208         lock_.writeLock().lock();
209         try {
210             if (!cookiesEnabled_) {
211                 return;
212             }
213 
214             cookies_.remove(cookie);
215         }
216         finally {
217             lock_.writeLock().unlock();
218         }
219     }
220 
221     /**
222      * Removes all cookies from this manager. Does nothing if cookie support
223      * is disabled.
224      */
225     public void clearCookies() {
226         lock_.writeLock().lock();
227         try {
228             if (!cookiesEnabled_) {
229                 return;
230             }
231 
232             cookies_.clear();
233         }
234         finally {
235             lock_.writeLock().unlock();
236         }
237     }
238 }