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