001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.tags; 020 021import java.io.IOException; 022import java.io.UnsupportedEncodingException; 023import java.net.URLDecoder; 024import java.net.URLEncoder; 025import java.util.HashMap; 026import java.util.Iterator; 027import java.util.Map; 028 029import javax.servlet.http.Cookie; 030import javax.servlet.http.HttpServletRequest; 031import javax.servlet.http.HttpServletResponse; 032import javax.servlet.jsp.PageContext; 033import javax.servlet.jsp.tagext.TagSupport; 034 035import org.apache.log4j.Logger; 036 037 038/** 039 * Sets or gets Cookie values. This implementation makes the following 040 * assumptions: 041 * <ul> 042 * <li>The cookie contains any number of name-value pairs 043 * <li>Name-value pairs are separated by "&" in the encoded cookie value string 044 * <li>An encoded name-value pair is compatible with JavaScript's 045 * encodeURIComponent(). Notably, spaces are encoded as "%20". 046 * <li>A decoded name-value pair separates the name and value with a "=" 047 * </ul> 048 * 049 * <p>The value of a cookie carrying values n1="v1" and n2="v2 with space" 050 * would thus be 051 * <pre> 052 * n1%3Dv1&n2%3Dv2%20with%20space 053 * </pre> 054 * 055 * <p>Usage: 056 * 057 * <pre> 058 * <wiki:cookie name="cookiename" var="contextvariable" scope="page" /> 059 * </pre> 060 * - Returns the value of the named cookie, or an empty string if not set. 061 * If 'var' is specified, the value is set into a context variable of this name. 062 * The 'scope' parameter may be added to specify the context: "session", 063 * "page", "request". If var is omitted, the output is placed directly into 064 * the JSP page. 065 * 066 * <pre> 067 * <wiki:cookie name="cookiename" value="encoded_value" /> 068 * </pre> 069 * - Sets the named cookie to the given value. If the value string is empty, 070 * the cookie value is set to empty; otherwise the cookie encoding rules of 071 * this class must be followed for the value. 072 * 073 * <pre> 074 * <wiki:cookie name="cookiename" item="parameter_name" /> 075 * </pre> 076 * - Assumes that the cookie contains URLEncoded name-value pairs, 077 * with name and value separated by an equals sign, and returns the value 078 * of the specified item. 079 * 080 * <wiki:cookie name="cookiename" item="parameter_name" value="value" /> 081 * </pre> 082 * - Sets the value of 'parameter_name' in the named cookie to 'value'. 083 * 084 * <pre> 085 * <wiki:cookie name="cookiename" clear="parameter_name" /> 086 * </pre> 087 * - Removes the named parameter from the cookie. 088 * 089 * <pre> 090 * <wiki:cookie clear="cookiename" /> 091 * </pre> 092 * - Removes the named cookie. Clear may be used at the same time as a value 093 * is retrieved (or set, despite the dubious usefulness of that operation). 094 */ 095public class CookieTag 096 extends TagSupport 097{ 098 private static final long serialVersionUID = 0L; 099 100 private static Logger log = Logger.getLogger( CookieTag.class ); 101 102 /** Name of the cookie value. Required. */ 103 private String m_name; 104 /** Name of the cookie nvp item. Optional. */ 105 private String m_item; 106 /** A value to echo or set. Optional. */ 107 private String m_value; 108 /** Name of a context variable to set result in. Optional, defaults to out.*/ 109 private String m_var; 110 /** Scope of m_var: request, session, page. */ 111 private String m_scope; 112 /** Name of a cookie or a cookie nvp to clear. */ 113 private String m_clear; 114 115 /** 116 * Set the "name" parameter. 117 * 118 * @param s The name. 119 */ 120 public void setName( String s ) 121 { 122 m_name = s; 123 } 124 125 /** 126 * Set the "item" parameter. 127 * 128 * @param s The item. 129 */ 130 public void setItem( String s ) 131 { 132 m_item = s; 133 } 134 135 /** 136 * Set the "value" parameter. 137 * 138 * @param s The value. 139 */ 140 public void setValue( String s ) 141 { 142 m_value = s; 143 } 144 145 /** 146 * Set the "var" parameter. 147 * 148 * @param s The parameter. 149 */ 150 public void setVar( String s ) 151 { 152 m_scope = s; 153 } 154 155 /** 156 * Set the "clear" parameter. 157 * 158 * @param s The parameter. 159 */ 160 public void setClear( String s ) 161 { 162 m_clear = s; 163 } 164 165 /** 166 * Set the "scope" parameter. 167 * 168 * @param s The scope. 169 */ 170 public void setScope( String s ) 171 { 172 m_scope = s; 173 } 174 175 /** 176 * {@inheritDoc} 177 */ 178 public void release() 179 { 180 m_name = m_item = m_var = m_value = m_clear = m_scope = null; 181 super.release(); 182 } 183 184 /** 185 * Examines the parameter and returns the corresponding scope identifier: 186 * "request" maps to PageContext.REQUEST_SCOPE, and so on. 187 * Possible values are "page", "session", "application", and "request", 188 * which is the default return value. 189 */ 190 private int getScope( String s ) 191 { 192 if( s == null ) 193 { 194 return PageContext.REQUEST_SCOPE; 195 } 196 if( "page".equals( m_scope ) ) 197 { 198 return PageContext.PAGE_SCOPE; 199 } 200 if( "session".equals( m_scope ) ) 201 { 202 return PageContext.SESSION_SCOPE; 203 } 204 if( "application".equals( m_scope ) ) 205 { 206 return PageContext.APPLICATION_SCOPE; 207 } 208 209 return PageContext.REQUEST_SCOPE; 210 } 211 212 /** 213 * {@inheritDoc} 214 */ 215 public int doEndTag() 216 { 217 String out = null; 218 Cookie cookie = findCookie( m_name ); 219 boolean changed = false; 220 221 if( m_value != null ) 222 { 223 if( m_item != null ) 224 { 225 setItemValue( cookie, m_item, m_value ); 226 } 227 else 228 { 229 cookie.setValue( m_value ); 230 } 231 changed = true; 232 } 233 else 234 { 235 if( m_item != null ) 236 { 237 out = getItemValue( cookie, m_item ); 238 } 239 else 240 { 241 out = cookie.getValue(); 242 } 243 } 244 245 if( out != null ) 246 { 247 if( m_var != null ) 248 { 249 int scope = getScope( m_scope ); 250 pageContext.setAttribute( m_var, out, scope ); 251 } 252 else 253 { 254 try 255 { 256 pageContext.getOut().print( out ); 257 } 258 catch( IOException ioe ) 259 { 260 log.warn( "Failed to write to JSP page: " + ioe.getMessage(), ioe ); 261 } 262 } 263 } 264 265 Cookie cleared = null; 266 if( m_clear != null ) 267 { 268 cleared = findCookie( m_clear ); 269 if( m_item != null ) 270 { 271 setItemValue( cookie, m_item, null ); 272 } 273 else 274 { 275 cleared.setValue( null ); 276 } 277 } 278 279 HttpServletResponse res = (HttpServletResponse)pageContext.getResponse(); 280 if( changed ) 281 { 282 res.addCookie( cookie ); 283 } 284 if( cleared != null ) 285 { 286 res.addCookie( cleared ); 287 } 288 289 return EVAL_PAGE; 290 } 291 292 /** 293 * Sets a single name-value pair in the given cookie. 294 */ 295 private void setItemValue( Cookie c, String item, String value ) 296 { 297 if( c == null ) 298 { 299 return; 300 } 301 String in = c.getValue(); 302 Map<String, String> values = parseCookieValues( in ); 303 values.put( item, value ); 304 String cv = encodeValues( values ); 305 c.setValue( cv ); 306 } 307 308 /** 309 * Returns the value of the given item in the cookie. 310 */ 311 private String getItemValue( Cookie c, String item ) 312 { 313 if( c == null || item == null ) { 314 return null; 315 } 316 String in = c.getValue(); 317 Map< String, String > values = parseCookieValues( in ); 318 return values.get( item ); 319 } 320 321 322 /** 323 * Parses a cookie value, of format name1%3Fvalue1&name2%3Fvalue2..., 324 * into a Map<String,String>. 325 */ 326 private Map<String, String> parseCookieValues( String s ) 327 { 328 Map< String, String > rval = new HashMap< String, String >(); 329 if( s == null ) { 330 return rval; 331 } 332 String[] nvps = s.split( "&" ); 333 if( nvps.length == 0 ) { 334 return rval; 335 } 336 for( int i = 0; i < nvps.length; i++ ) { 337 String nvp = decode( nvps[i] ); 338 String[] nv = nvp.split( "=" ); 339 if( nv[0] != null && nv[0].trim().length() > 0 ) 340 { 341 rval.put( nv[0], nv[1] ); 342 } 343 } 344 345 return rval; 346 } 347 348 /** 349 * Encodes name-value pairs in the map into a single string, in a format 350 * understood by this class and JavaScript decodeURIComponent(). 351 */ 352 private String encodeValues( Map<String, String> values ) 353 { 354 StringBuilder rval = new StringBuilder(); 355 if( values == null || values.size() == 0 ) { 356 return rval.toString(); 357 } 358 359 Iterator< Map.Entry< String, String > > it = values.entrySet().iterator(); 360 while( it.hasNext() ) { 361 Map.Entry< String, String > e = it.next(); 362 String n = e.getKey(); 363 String v = e.getValue(); 364 if( v != null ) { 365 String nv = n + "=" + v; 366 rval.append( encode( nv ) ); 367 } 368 } 369 370 return rval.toString(); 371 } 372 373 /** 374 * Converts a String to an encoding understood by JavaScript 375 * decodeURIComponent. 376 */ 377 private String encode( String nvp ) 378 { 379 String coded = ""; 380 try 381 { 382 coded = URLEncoder.encode( nvp, "UTF-8" ); 383 } 384 catch( UnsupportedEncodingException e ) 385 { 386 /* never happens */ 387 log.info( "Failed to encode UTF-8", e ); 388 } 389 return coded.replaceAll( "\\+", "%20" ); 390 } 391 392 /** 393 * Converts a cookie value (set by this class, or by a JavaScript 394 * encodeURIComponent call) into a plain string. 395 */ 396 private String decode( String envp ) 397 { 398 String rval; 399 try 400 { 401 rval = URLDecoder.decode( envp , "UTF-8" ); 402 return rval; 403 } 404 catch( UnsupportedEncodingException e ) 405 { 406 log.error( "Failed to decode cookie", e ); 407 return envp; 408 } 409 } 410 411 /** 412 * Locates the named cookie in the request, or creates a new one if it 413 * doesn't exist. 414 */ 415 private Cookie findCookie( String cname ) 416 { 417 HttpServletRequest req = (HttpServletRequest)pageContext.getRequest(); 418 if( req != null ) 419 { 420 Cookie[] cookies = req.getCookies(); 421 if( cookies != null ) 422 { 423 for( int i = 0; i < cookies.length; i++ ) 424 { 425 if( cookies[i].getName().equals( cname ) ) 426 { 427 return cookies[i]; 428 } 429 } 430 } 431 } 432 433 return new Cookie( cname, null ); 434 } 435 436}