1 /*
2 * Copyright (c) 2002-2025 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.util;
16
17 import java.io.Serializable;
18 import java.util.Date;
19
20 import org.apache.commons.lang3.builder.EqualsBuilder;
21 import org.apache.commons.lang3.builder.HashCodeBuilder;
22 import org.apache.http.cookie.ClientCookie;
23 import org.apache.http.impl.cookie.BasicClientCookie;
24
25 /**
26 * A cookie. This class is immutable.
27 *
28 * @author Daniel Gredler
29 * @author Nicolas Belisle
30 * @author Ahmed Ashour
31 * @author Ronald Brill
32 */
33 public class Cookie implements Serializable {
34
35 private final ClientCookie httpClientCookie_;
36
37 /**
38 * Creates a new cookie with the specified name and value which applies to the specified domain.
39 * The new cookie applies to all paths, never expires and is not secure.
40 * @param domain the domain to which this cookie applies
41 * @param name the cookie name
42 * @param value the cookie name
43 */
44 public Cookie(final String domain, final String name, final String value) {
45 this(domain, name, value, null, null, false);
46 }
47
48 /**
49 * Creates a new cookie with the specified name and value which applies to the specified domain,
50 * the specified path, and expires on the specified date.
51 * @param domain the domain to which this cookie applies
52 * @param name the cookie name
53 * @param value the cookie name
54 * @param path the path to which this cookie applies
55 * @param expires the date on which this cookie expires
56 * @param secure whether or not this cookie is secure (i.e. HTTPS vs HTTP)
57 */
58 public Cookie(final String domain, final String name, final String value, final String path, final Date expires,
59 final boolean secure) {
60 this(domain, name, value, path, expires, secure, false, null);
61 }
62
63 /**
64 * Creates a new cookie with the specified name and value which applies to the specified domain,
65 * the specified path, and expires on the specified date.
66 * @param domain the domain to which this cookie applies
67 * @param name the cookie name
68 * @param value the cookie name
69 * @param path the path to which this cookie applies
70 * @param expires the date on which this cookie expires
71 * @param secure whether or not this cookie is secure (i.e. HTTPS vs HTTP)
72 * @param httpOnly whether or not this cookie should be only used for HTTP(S) headers
73 */
74 public Cookie(final String domain, final String name, final String value, final String path, final Date expires,
75 final boolean secure, final boolean httpOnly) {
76 this(domain, name, value, path, expires, secure, httpOnly, null);
77 }
78
79 /**
80 * Creates a new cookie with the specified name and value which applies to the specified domain,
81 * the specified path, and expires on the specified date.
82 * @param domain the domain to which this cookie applies
83 * @param name the cookie name
84 * @param value the cookie name
85 * @param path the path to which this cookie applies
86 * @param expires the date on which this cookie expires
87 * @param secure whether or not this cookie is secure (i.e. HTTPS vs HTTP)
88 * @param httpOnly whether or not this cookie should be only used for HTTP(S) headers
89 * @param sameSite the sameSite attribute
90 */
91 public Cookie(final String domain, final String name, final String value, final String path, final Date expires,
92 final boolean secure, final boolean httpOnly, final String sameSite) {
93 if (domain == null) {
94 throw new IllegalArgumentException("Cookie domain must be specified");
95 }
96
97 final BasicClientCookie cookie = new BasicClientCookie(name, value == null ? "" : value);
98
99 cookie.setDomain(domain);
100 // BasicDomainHandler.match(Cookie, CookieOrigin) checks the attib also (see #333)
101 cookie.setAttribute(ClientCookie.DOMAIN_ATTR, domain);
102
103 cookie.setPath(path);
104 if (expires != null) {
105 cookie.setExpiryDate(expires);
106 }
107 cookie.setSecure(secure);
108 if (httpOnly) {
109 cookie.setAttribute("httponly", "true");
110 }
111
112 if (sameSite != null) {
113 cookie.setAttribute("samesite", sameSite);
114 }
115
116 httpClientCookie_ = cookie;
117 }
118
119 /**
120 * Creates a new HtmlUnit cookie from the HttpClient cookie provided.
121 * @param clientCookie the HttpClient cookie
122 */
123 public Cookie(final ClientCookie clientCookie) {
124 httpClientCookie_ = clientCookie;
125 }
126
127 /**
128 * Creates a new cookie with the specified name and value which applies to the specified domain,
129 * the specified path, and expires after the specified amount of time.
130 * @param domain the domain to which this cookie applies
131 * @param name the cookie name
132 * @param value the cookie name
133 * @param path the path to which this cookie applies
134 * @param maxAge the number of seconds for which this cookie is valid; <code>-1</code> indicates that the
135 * cookie should never expire; other negative numbers are not allowed
136 * @param secure whether or not this cookie is secure (i.e. HTTPS vs HTTP)
137 */
138 public Cookie(final String domain, final String name, final String value, final String path, final int maxAge,
139 final boolean secure) {
140 this(domain, name, value, path, convertToExpiryDate(maxAge), secure);
141 }
142
143 private static Date convertToExpiryDate(final int maxAge) {
144 if (maxAge < -1) {
145 throw new IllegalArgumentException("invalid max age: " + maxAge);
146 }
147
148 if (maxAge >= 0) {
149 return new Date(System.currentTimeMillis() + (maxAge * 1000L));
150 }
151
152 return null;
153 }
154
155 /**
156 * Returns the cookie name.
157 * @return the cookie name
158 */
159 public String getName() {
160 return httpClientCookie_.getName();
161 }
162
163 /**
164 * Returns the cookie value.
165 * @return the cookie value
166 */
167 public String getValue() {
168 return httpClientCookie_.getValue();
169 }
170
171 /**
172 * Returns the domain to which this cookie applies ({@code null} for all domains).
173 * @return the domain to which this cookie applies ({@code null} for all domains)
174 */
175 public String getDomain() {
176 return httpClientCookie_.getDomain();
177 }
178
179 /**
180 * Returns the path to which this cookie applies ({@code null} for all paths).
181 * @return the path to which this cookie applies ({@code null} for all paths)
182 */
183 public String getPath() {
184 return httpClientCookie_.getPath();
185 }
186
187 /**
188 * Returns the date on which this cookie expires ({@code null} if it never expires).
189 * @return the date on which this cookie expires ({@code null} if it never expires)
190 */
191 public Date getExpires() {
192 return httpClientCookie_.getExpiryDate();
193 }
194
195 /**
196 * Returns whether or not this cookie is secure (i.e. HTTPS vs HTTP).
197 * @return whether or not this cookie is secure (i.e. HTTPS vs HTTP)
198 */
199 public boolean isSecure() {
200 return httpClientCookie_.isSecure();
201 }
202
203 /**
204 * Returns whether or not this cookie is HttpOnly (i.e. not available in JS).
205 * @see <a href="http://en.wikipedia.org/wiki/HTTP_cookie#Secure_and_HttpOnly">Wikipedia</a>
206 * @return whether or not this cookie is HttpOnly (i.e. not available in JS).
207 */
208 public boolean isHttpOnly() {
209 return httpClientCookie_.getAttribute("httponly") != null;
210 }
211
212 /**
213 * @return the SameSite value or {@code null} if not set.
214 */
215 public String getSameSite() {
216 return httpClientCookie_.getAttribute("samesite");
217 }
218
219 /**
220 * {@inheritDoc}
221 */
222 @Override
223 public String toString() {
224 return getName() + "=" + getValue()
225 + (getDomain() == null ? "" : ";domain=" + getDomain())
226 + (getPath() == null ? "" : ";path=" + getPath())
227 + (getExpires() == null ? "" : ";expires=" + getExpires())
228 + (isSecure() ? ";secure" : "")
229 + (isHttpOnly() ? ";httpOnly" : "")
230 + (getSameSite() == null ? "" : ";sameSite=" + getSameSite());
231 }
232
233 /**
234 * {@inheritDoc}
235 */
236 @Override
237 public boolean equals(final Object o) {
238 if (!(o instanceof Cookie)) {
239 return false;
240 }
241 final Cookie other = (Cookie) o;
242 final String path = getPath() == null ? "/" : getPath();
243 final String otherPath = other.getPath() == null ? "/" : other.getPath();
244 return new EqualsBuilder()
245 .append(getName(), other.getName())
246 .append(getDomain(), other.getDomain())
247 .append(path, otherPath)
248 .isEquals();
249 }
250
251 /**
252 * {@inheritDoc}
253 */
254 @Override
255 public int hashCode() {
256 final String path = getPath() == null ? "/" : getPath();
257 return new HashCodeBuilder()
258 .append(getName())
259 .append(getDomain())
260 .append(path)
261 .toHashCode();
262 }
263
264 /**
265 * Converts this cookie to an HttpClient cookie.
266 * @return an HttpClient version of this cookie
267 */
268 public org.apache.http.cookie.Cookie toHttpClient() {
269 return httpClientCookie_;
270 }
271 }