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 }