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 */
019 package org.apache.wiki.tags;
020
021 import java.io.IOException;
022 import java.io.UnsupportedEncodingException;
023 import java.net.URLDecoder;
024 import java.net.URLEncoder;
025 import java.util.HashMap;
026 import java.util.Iterator;
027 import java.util.Map;
028
029 import javax.servlet.http.Cookie;
030 import javax.servlet.http.HttpServletRequest;
031 import javax.servlet.http.HttpServletResponse;
032 import javax.servlet.jsp.PageContext;
033 import javax.servlet.jsp.tagext.TagSupport;
034
035 import 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 */
095 public 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 }