001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.util.cookies; 018 019import java.time.Instant; 020import java.time.LocalDateTime; 021import java.time.ZoneId; 022 023import javax.servlet.http.Cookie; 024import org.apache.wicket.markup.html.form.FormComponent; 025import org.apache.wicket.protocol.http.WebApplication; 026import org.apache.wicket.protocol.http.servlet.ServletWebRequest; 027import org.apache.wicket.request.Response; 028import org.apache.wicket.request.cycle.RequestCycle; 029import org.apache.wicket.request.http.WebRequest; 030import org.apache.wicket.request.http.WebResponse; 031import org.apache.wicket.util.string.Strings; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035 036/** 037 * Helper class to simplify Cookie handling. 038 * 039 * @author Juergen Donnerstag 040 * @author Jonathan Locke 041 */ 042public class CookieUtils 043{ 044 private final static Logger log = LoggerFactory.getLogger(CookieUtils.class); 045 046 public static final String DEFAULT_SESSIONID_COOKIE_NAME = "JSESSIONID"; 047 048 private final CookieDefaults settings; 049 050 /** 051 * Construct. 052 */ 053 public CookieUtils() 054 { 055 settings = new CookieDefaults(); 056 } 057 058 /** 059 * Construct. 060 * 061 * @param settings 062 * the default settings for the saved cookies 063 */ 064 public CookieUtils(final CookieDefaults settings) 065 { 066 this.settings = settings; 067 } 068 069 /** 070 * @return Gets the settings for these utils 071 */ 072 public final CookieDefaults getSettings() 073 { 074 return settings; 075 } 076 077 /** 078 * Remove the cookie identified by the key 079 * 080 * @param key 081 * The cookie name 082 */ 083 public final void remove(final String key) 084 { 085 final Cookie cookie = getCookie(key); 086 if (cookie != null) 087 { 088 remove(cookie); 089 } 090 } 091 092 /** 093 * Remove the cookie identified by the form component 094 * 095 * @param formComponent 096 */ 097 public final void remove(final FormComponent<?> formComponent) 098 { 099 remove(getKey(formComponent)); 100 } 101 102 /** 103 * This method gets used when a cookie key needs to be derived from a form component. By default 104 * the component's page relative path is used. 105 * 106 * @param component 107 * @return cookie key 108 */ 109 protected String getKey(final FormComponent<?> component) 110 { 111 return getSaveKey(component.getPageRelativePath()); 112 } 113 114 /** 115 * Retrieve the cookie value by means of its key. 116 * 117 * @param key 118 * The cookie name 119 * @return The cookie value associated with the key 120 */ 121 public final String load(final String key) 122 { 123 final Cookie cookie = getCookie(key); 124 if (cookie != null) 125 { 126 return cookie.getValue(); 127 } 128 return null; 129 } 130 131 /** 132 * Retrieve the cookie value associated with the formComponent and load the model object with 133 * the cookie value. 134 * 135 * @param formComponent 136 * @return The Cookie value which has also been used to set the component's model value 137 */ 138 public final String load(final FormComponent<?> formComponent) 139 { 140 String value = load(getKey(formComponent)); 141 if (value != null) 142 { 143 // Assign the retrieved/persisted value to the component 144 formComponent.setModelValue(new String[] {value}); 145 } 146 return value; 147 } 148 149 /** 150 * Create a Cookie with key and value and save it in the browser with the next response 151 * 152 * @param name 153 * The cookie name 154 * @param value 155 * The cookie value 156 */ 157 public final void save(String name, final String value) 158 { 159 Cookie cookie = getCookie(name); 160 if (cookie == null) 161 { 162 cookie = new Cookie(name, value); 163 } 164 else 165 { 166 cookie.setValue(value); 167 } 168 save(cookie); 169 } 170 171 /** 172 * Save the form components model value in a cookie 173 * 174 * @param formComponent 175 */ 176 public final void save(final FormComponent<?> formComponent) 177 { 178 save(getKey(formComponent), formComponent.getValue()); 179 } 180 181 /** 182 * Make sure the 'key' does not contain any illegal chars. E.g. for cookies ':' is not allowed. 183 * 184 * @param key 185 * The key to be validated 186 * @return The save key 187 */ 188 protected String getSaveKey(String key) 189 { 190 if (Strings.isEmpty(key)) 191 { 192 throw new IllegalArgumentException("A Cookie name can not be null or empty"); 193 } 194 195 // cookie names cannot contain ':', 196 // we replace ':' with '.' but first we have to encode '.' as '..' 197 key = Strings.replaceAll(key, ".", "..").toString(); 198 key = key.replace(':', '.'); 199 return key; 200 } 201 202 /** 203 * Convenience method for deleting a cookie by name. Delete the cookie by setting its maximum 204 * age to zero. 205 * 206 * @param cookie 207 * The cookie to delete 208 */ 209 private void remove(final Cookie cookie) 210 { 211 if (cookie != null) 212 { 213 save(cookie); 214 215 // Delete the cookie by setting its maximum age to zero 216 cookie.setMaxAge(0); 217 cookie.setValue(null); 218 219 if (log.isDebugEnabled()) 220 { 221 log.debug("Removed Cookie: " + cookie.getName()); 222 } 223 } 224 } 225 226 /** 227 * Gets the cookie with 'name' attached to the latest WebRequest. 228 * 229 * @param name 230 * The name of the cookie to be looked up 231 * 232 * @return Any cookies for this request 233 */ 234 public Cookie getCookie(final String name) 235 { 236 try 237 { 238 WebRequest webRequest = getWebRequest(); 239 Cookie cookie = webRequest.getCookie(name); 240 if (log.isDebugEnabled()) 241 { 242 if (cookie != null) 243 { 244 log.debug("Found Cookie with name=" + name + " and request URI=" + 245 webRequest.getUrl().toString()); 246 } 247 else 248 { 249 log.debug("Unable to find Cookie with name=" + name + " and request URI=" + 250 webRequest.getUrl().toString()); 251 } 252 } 253 254 return cookie; 255 } 256 catch (NullPointerException ex) 257 { 258 // Ignore any app server problem here 259 } 260 261 return null; 262 } 263 264 265 /** 266 * Gets the name of the cookie where the session id is stored. 267 * 268 * @param application 269 * The current we application holding the {@link javax.servlet.ServletContext}. 270 * 271 * @return The name set in {@link javax.servlet.SessionCookieConfig} or the default value 'JSESSIONID' if not set 272 */ 273 public String getSessionIdCookieName(WebApplication application) 274 { 275 String jsessionCookieName = application.getServletContext().getSessionCookieConfig().getName(); 276 277 return jsessionCookieName == null ? DEFAULT_SESSIONID_COOKIE_NAME : jsessionCookieName; 278 } 279 280 /** 281 * Persist/save the data using Cookies. 282 * 283 * @param cookie 284 * The Cookie to be persisted. 285 * @return The cookie provided 286 */ 287 private Cookie save(final Cookie cookie) 288 { 289 if (cookie == null) 290 { 291 return null; 292 } 293 294 initializeCookie(cookie); 295 296 getWebResponse().addCookie(cookie); 297 298 if (log.isDebugEnabled()) 299 { 300 log.debug("Cookie saved: " + cookieToDebugString(cookie) + "; request URI=" + 301 getWebRequest().getUrl().toString()); 302 } 303 304 return cookie; 305 } 306 307 /** 308 * Is called before the Cookie is saved. May be subclassed for different (dynamic) Cookie 309 * parameters. Static parameters can also be changed via {@link CookieDefaults}. 310 * 311 * @param cookie 312 */ 313 protected void initializeCookie(final Cookie cookie) 314 { 315 final String comment = settings.getComment(); 316 if (comment != null) 317 { 318 cookie.setComment(comment); 319 } 320 321 final String domain = settings.getDomain(); 322 if (domain != null) 323 { 324 cookie.setDomain(domain); 325 } 326 327 ServletWebRequest request = (ServletWebRequest)getWebRequest(); 328 String path = request.getContainerRequest().getContextPath() + "/" + 329 request.getFilterPrefix(); 330 331 cookie.setPath(path); 332 cookie.setVersion(settings.getVersion()); 333 cookie.setSecure(settings.getSecure()); 334 cookie.setMaxAge(settings.getMaxAge()); 335 cookie.setHttpOnly(settings.isHttpOnly()); 336 } 337 338 /** 339 * Convenience method to get the http request. 340 * 341 * @return WebRequest related to the RequestCycle 342 */ 343 private WebRequest getWebRequest() 344 { 345 return (WebRequest)RequestCycle.get().getRequest(); 346 } 347 348 /** 349 * Convenience method to get the http response. 350 * 351 * @return WebResponse related to the RequestCycle 352 */ 353 private WebResponse getWebResponse() 354 { 355 RequestCycle cycle = RequestCycle.get(); 356 Response response = cycle.getResponse(); 357 if (!(response instanceof WebResponse)) 358 { 359 response = cycle.getOriginalResponse(); 360 } 361 return (WebResponse)response; 362 } 363 364 /** 365 * Gets debug info as a string for the given cookie. 366 * 367 * @param cookie 368 * the cookie to debug. 369 * @return a string that represents the internals of the cookie. 370 */ 371 private String cookieToDebugString(final Cookie cookie) 372 { 373 final LocalDateTime localDateTime = Instant.ofEpochMilli(cookie.getMaxAge()).atZone(ZoneId.systemDefault()).toLocalDateTime(); 374 return "[Cookie " + " name = " + cookie.getName() + ", value = " + cookie.getValue() + 375 ", domain = " + cookie.getDomain() + ", path = " + cookie.getPath() + ", maxAge = " + 376 localDateTime + "(" + cookie.getMaxAge() + ")" + "]"; 377 } 378}