1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.shiro.web.servlet;
20
21 import org.apache.shiro.util.StringUtils;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse;
27 import java.text.DateFormat;
28 import java.text.SimpleDateFormat;
29 import java.util.Calendar;
30 import java.util.Date;
31 import java.util.Locale;
32 import java.util.TimeZone;
33
34
35
36
37
38
39
40
41
42 public class SimpleCookie implements Cookie {
43
44
45
46
47 public static final int DEFAULT_MAX_AGE = -1;
48
49
50
51
52 public static final int DEFAULT_VERSION = -1;
53
54
55 protected static final String NAME_VALUE_DELIMITER = "=";
56 protected static final String ATTRIBUTE_DELIMITER = "; ";
57 protected static final long DAY_MILLIS = 86400000;
58 protected static final String GMT_TIME_ZONE_ID = "GMT";
59 protected static final String COOKIE_DATE_FORMAT_STRING = "EEE, dd-MMM-yyyy HH:mm:ss z";
60
61 protected static final String COOKIE_HEADER_NAME = "Set-Cookie";
62 protected static final String PATH_ATTRIBUTE_NAME = "Path";
63 protected static final String EXPIRES_ATTRIBUTE_NAME = "Expires";
64 protected static final String MAXAGE_ATTRIBUTE_NAME = "Max-Age";
65 protected static final String DOMAIN_ATTRIBUTE_NAME = "Domain";
66 protected static final String VERSION_ATTRIBUTE_NAME = "Version";
67 protected static final String COMMENT_ATTRIBUTE_NAME = "Comment";
68 protected static final String SECURE_ATTRIBUTE_NAME = "Secure";
69 protected static final String HTTP_ONLY_ATTRIBUTE_NAME = "HttpOnly";
70
71 private static final transient Logger log = LoggerFactory.getLogger(SimpleCookie.class);
72
73 private String name;
74 private String value;
75 private String comment;
76 private String domain;
77 private String path;
78 private int maxAge;
79 private int version;
80 private boolean secure;
81 private boolean httpOnly;
82
83 public SimpleCookie() {
84 this.maxAge = DEFAULT_MAX_AGE;
85 this.version = DEFAULT_VERSION;
86 this.httpOnly = true;
87 }
88
89 public SimpleCookie(String name) {
90 this();
91 this.name = name;
92 }
93
94 public SimpleCookie(Cookie cookie) {
95 this.name = cookie.getName();
96 this.value = cookie.getValue();
97 this.comment = cookie.getComment();
98 this.domain = cookie.getDomain();
99 this.path = cookie.getPath();
100 this.maxAge = Math.max(DEFAULT_MAX_AGE, cookie.getMaxAge());
101 this.version = Math.max(DEFAULT_VERSION, cookie.getVersion());
102 this.secure = cookie.isSecure();
103 this.httpOnly = cookie.isHttpOnly();
104 }
105
106 public String getName() {
107 return name;
108 }
109
110 public void setName(String name) {
111 if (!StringUtils.hasText(name)) {
112 throw new IllegalArgumentException("Name cannot be null/empty.");
113 }
114 this.name = name;
115 }
116
117 public String getValue() {
118 return value;
119 }
120
121 public void setValue(String value) {
122 this.value = value;
123 }
124
125 public String getComment() {
126 return comment;
127 }
128
129 public void setComment(String comment) {
130 this.comment = comment;
131 }
132
133 public String getDomain() {
134 return domain;
135 }
136
137 public void setDomain(String domain) {
138 this.domain = domain;
139 }
140
141 public String getPath() {
142 return path;
143 }
144
145 public void setPath(String path) {
146 this.path = path;
147 }
148
149 public int getMaxAge() {
150 return maxAge;
151 }
152
153 public void setMaxAge(int maxAge) {
154 this.maxAge = Math.max(DEFAULT_MAX_AGE, maxAge);
155 }
156
157 public int getVersion() {
158 return version;
159 }
160
161 public void setVersion(int version) {
162 this.version = Math.max(DEFAULT_VERSION, version);
163 }
164
165 public boolean isSecure() {
166 return secure;
167 }
168
169 public void setSecure(boolean secure) {
170 this.secure = secure;
171 }
172
173 public boolean isHttpOnly() {
174 return httpOnly;
175 }
176
177 public void setHttpOnly(boolean httpOnly) {
178 this.httpOnly = httpOnly;
179 }
180
181
182
183
184
185
186
187
188
189 private String calculatePath(HttpServletRequest request) {
190 String path = StringUtils.clean(getPath());
191 if (!StringUtils.hasText(path)) {
192 path = StringUtils.clean(request.getContextPath());
193 }
194
195
196 if (path == null) {
197 path = ROOT_PATH;
198 }
199 log.trace("calculated path: {}", path);
200 return path;
201 }
202
203 public void saveTo(HttpServletRequest request, HttpServletResponse response) {
204
205 String name = getName();
206 String value = getValue();
207 String comment = getComment();
208 String domain = getDomain();
209 String path = calculatePath(request);
210 int maxAge = getMaxAge();
211 int version = getVersion();
212 boolean secure = isSecure();
213 boolean httpOnly = isHttpOnly();
214
215 addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);
216 }
217
218 private void addCookieHeader(HttpServletResponse response, String name, String value, String comment,
219 String domain, String path, int maxAge, int version,
220 boolean secure, boolean httpOnly) {
221
222 String headerValue = buildHeaderValue(name, value, comment, domain, path, maxAge, version, secure, httpOnly);
223 response.addHeader(COOKIE_HEADER_NAME, headerValue);
224
225 if (log.isDebugEnabled()) {
226 log.debug("Added HttpServletResponse Cookie [{}]", headerValue);
227 }
228 }
229
230
231
232
233
234
235
236
237 protected String buildHeaderValue(String name, String value, String comment,
238 String domain, String path, int maxAge, int version,
239 boolean secure, boolean httpOnly) {
240
241 if (!StringUtils.hasText(name)) {
242 throw new IllegalStateException("Cookie name cannot be null/empty.");
243 }
244
245 StringBuilder sb = new StringBuilder(name).append(NAME_VALUE_DELIMITER);
246
247 if (StringUtils.hasText(value)) {
248 sb.append(value);
249 }
250
251 appendComment(sb, comment);
252 appendDomain(sb, domain);
253 appendPath(sb, path);
254 appendExpires(sb, maxAge);
255 appendVersion(sb, version);
256 appendSecure(sb, secure);
257 appendHttpOnly(sb, httpOnly);
258
259 return sb.toString();
260
261 }
262
263 private void appendComment(StringBuilder sb, String comment) {
264 if (StringUtils.hasText(comment)) {
265 sb.append(ATTRIBUTE_DELIMITER);
266 sb.append(COMMENT_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(comment);
267 }
268 }
269
270 private void appendDomain(StringBuilder sb, String domain) {
271 if (StringUtils.hasText(domain)) {
272 sb.append(ATTRIBUTE_DELIMITER);
273 sb.append(DOMAIN_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(domain);
274 }
275 }
276
277 private void appendPath(StringBuilder sb, String path) {
278 if (StringUtils.hasText(path)) {
279 sb.append(ATTRIBUTE_DELIMITER);
280 sb.append(PATH_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(path);
281 }
282 }
283
284 private void appendExpires(StringBuilder sb, int maxAge) {
285
286
287
288
289
290
291 if (maxAge >= 0) {
292 sb.append(ATTRIBUTE_DELIMITER);
293 sb.append(MAXAGE_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(maxAge);
294 sb.append(ATTRIBUTE_DELIMITER);
295 Date expires;
296 if (maxAge == 0) {
297
298 expires = new Date(System.currentTimeMillis() - DAY_MILLIS);
299 } else {
300
301 Calendar cal = Calendar.getInstance();
302 cal.add(Calendar.SECOND, maxAge);
303 expires = cal.getTime();
304 }
305 String formatted = toCookieDate(expires);
306 sb.append(EXPIRES_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(formatted);
307 }
308 }
309
310 private void appendVersion(StringBuilder sb, int version) {
311 if (version > DEFAULT_VERSION) {
312 sb.append(ATTRIBUTE_DELIMITER);
313 sb.append(VERSION_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(version);
314 }
315 }
316
317 private void appendSecure(StringBuilder sb, boolean secure) {
318 if (secure) {
319 sb.append(ATTRIBUTE_DELIMITER);
320 sb.append(SECURE_ATTRIBUTE_NAME);
321 }
322 }
323
324 private void appendHttpOnly(StringBuilder sb, boolean httpOnly) {
325 if (httpOnly) {
326 sb.append(ATTRIBUTE_DELIMITER);
327 sb.append(HTTP_ONLY_ATTRIBUTE_NAME);
328 }
329 }
330
331
332
333
334
335
336
337
338
339 private boolean pathMatches(String cookiePath, String requestPath) {
340 if (!requestPath.startsWith(cookiePath)) {
341 return false;
342 }
343
344 return requestPath.length() == cookiePath.length()
345 || cookiePath.charAt(cookiePath.length() - 1) == '/'
346 || requestPath.charAt(cookiePath.length()) == '/';
347 }
348
349
350
351
352
353
354
355 private static String toCookieDate(Date date) {
356 TimeZone tz = TimeZone.getTimeZone(GMT_TIME_ZONE_ID);
357 DateFormat fmt = new SimpleDateFormat(COOKIE_DATE_FORMAT_STRING, Locale.US);
358 fmt.setTimeZone(tz);
359 return fmt.format(date);
360 }
361
362 public void removeFrom(HttpServletRequest request, HttpServletResponse response) {
363 String name = getName();
364 String value = DELETED_COOKIE_VALUE;
365 String comment = null;
366 String domain = getDomain();
367 String path = calculatePath(request);
368 int maxAge = 0;
369 int version = getVersion();
370 boolean secure = isSecure();
371 boolean httpOnly = false;
372
373 addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);
374
375 log.trace("Removed '{}' cookie by setting maxAge=0", name);
376 }
377
378 public String readValue(HttpServletRequest request, HttpServletResponse ignored) {
379 String name = getName();
380 String value = null;
381 javax.servlet.http.Cookie cookie = getCookie(request, name);
382 if (cookie != null) {
383
384 String path = StringUtils.clean(getPath());
385 if (path != null && !pathMatches(path, request.getRequestURI())) {
386 log.warn("Found '{}' cookie at path '{}', but should be only used for '{}'", new Object[] { name, request.getRequestURI(), path});
387 } else {
388 value = cookie.getValue();
389 log.debug("Found '{}' cookie value [{}]", name, value);
390 }
391 } else {
392 log.trace("No '{}' cookie value", name);
393 }
394
395 return value;
396 }
397
398
399
400
401
402
403
404
405
406
407 private static javax.servlet.http.Cookie getCookie(HttpServletRequest request, String cookieName) {
408 javax.servlet.http.Cookie cookies[] = request.getCookies();
409 if (cookies != null) {
410 for (javax.servlet.http.Cookie cookie : cookies) {
411 if (cookie.getName().equals(cookieName)) {
412 return cookie;
413 }
414 }
415 }
416 return null;
417 }
418 }