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     * &lt;wiki:cookie name="cookiename" var="contextvariable" scope="page" /&gt;
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     * &lt;wiki:cookie name="cookiename" value="encoded_value" /&gt;
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     * &lt;wiki:cookie name="cookiename" item="parameter_name" /&gt;
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     * &lt;wiki:cookie name="cookiename" item="parameter_name" value="value" /&gt;
081     * </pre>
082     * - Sets the value of 'parameter_name' in the named cookie to 'value'.
083     *
084     * <pre>
085     * &lt;wiki:cookie name="cookiename" clear="parameter_name" /&gt;
086     * </pre>
087     * - Removes the named parameter from the cookie.
088     *
089     * <pre>
090     * &lt;wiki:cookie clear="cookiename" /&gt;
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    }