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 private static String toCookieDate(Date date) {
338 TimeZone tz = TimeZone.getTimeZone(GMT_TIME_ZONE_ID);
339 DateFormat fmt = new SimpleDateFormat(COOKIE_DATE_FORMAT_STRING, Locale.US);
340 fmt.setTimeZone(tz);
341 return fmt.format(date);
342 }
343
344 public void removeFrom(HttpServletRequest request, HttpServletResponse response) {
345 String name = getName();
346 String value = DELETED_COOKIE_VALUE;
347 String comment = null;
348 String domain = getDomain();
349 String path = calculatePath(request);
350 int maxAge = 0;
351 int version = getVersion();
352 boolean secure = isSecure();
353 boolean httpOnly = false;
354
355 addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);
356
357 log.trace("Removed '{}' cookie by setting maxAge=0", name);
358 }
359
360 public String readValue(HttpServletRequest request, HttpServletResponse ignored) {
361 String name = getName();
362 String value = null;
363 javax.servlet.http.Cookie cookie = getCookie(request, name);
364 if (cookie != null) {
365 value = cookie.getValue();
366 log.debug("Found '{}' cookie value [{}]", name, value);
367 } else {
368 log.trace("No '{}' cookie value", name);
369 }
370
371 return value;
372 }
373
374
375
376
377
378
379
380
381
382
383 private static javax.servlet.http.Cookie getCookie(HttpServletRequest request, String cookieName) {
384 javax.servlet.http.Cookie cookies[] = request.getCookies();
385 if (cookies != null) {
386 for (javax.servlet.http.Cookie cookie : cookies) {
387 if (cookie.getName().equals(cookieName)) {
388 return cookie;
389 }
390 }
391 }
392 return null;
393 }
394 }