1   // ========================================================================
2   // Copyright 2006 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.servlet;
16  
17  import java.io.IOException;
18  import java.util.ArrayList;
19  import java.util.Enumeration;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.concurrent.ConcurrentHashMap;
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  
27  import javax.servlet.Filter;
28  import javax.servlet.FilterChain;
29  import javax.servlet.FilterConfig;
30  import javax.servlet.ServletException;
31  import javax.servlet.ServletRequest;
32  import javax.servlet.ServletResponse;
33  import javax.servlet.http.HttpServletRequest;
34  
35  /* ------------------------------------------------------------ */
36  /** User Agent Filter.
37   * <p>
38   * This filter allows efficient matching of user agent strings for
39   * downstream or extended filters to use for browser specific logic.
40   * </p>
41   * <p>
42   * The filter is configured with the following init parameters:
43   * <dl>
44   * <dt>attribute</dt><dd>If set, then the request attribute of this name is set with the matched user agent string</dd>
45   * <dt>cacheSize</dt><dd>The size of the user-agent cache, used to avoid reparsing of user agent strings. The entire cache is flushed
46   * when this size is reached</dd>
47   * <dt>userAgent</dt><dd>A regex {@link Pattern} to extract the essential elements of the user agent. 
48   * The concatenation of matched pattern groups is used as the user agent name</dd>
49   * <dl> 
50   * An example value for pattern is <code>(?:Mozilla[^\(]*\(compatible;\s*+([^;]*);.*)|(?:.*?([^\s]+/[^\s]+).*)</code>. These two
51   * pattern match the common compatibility user-agent strings and extract the real user agent, failing that, the first
52   * element of the agent string is returned. 
53   * @author gregw
54   *
55   */
56  public class UserAgentFilter implements Filter
57  {
58      private Pattern _pattern;
59      private Map _agentCache = new ConcurrentHashMap();
60      private int _agentCacheSize=1024;
61      private String _attribute;
62  
63      /* ------------------------------------------------------------ */
64      /* (non-Javadoc)
65       * @see javax.servlet.Filter#destroy()
66       */
67      public void destroy()
68      {
69      }
70  
71      /* ------------------------------------------------------------ */
72      /* (non-Javadoc)
73       * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
74       */
75      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
76      {
77          if (_attribute!=null && _pattern!=null)
78          {       
79              String ua=getUserAgent(request);
80              request.setAttribute(_attribute,ua);
81          }
82          chain.doFilter(request,response);
83      }
84  
85      /* ------------------------------------------------------------ */
86      /* (non-Javadoc)
87       * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
88       */
89      public void init(FilterConfig filterConfig) throws ServletException
90      {
91          _attribute=filterConfig.getInitParameter("attribute");
92          
93          String p=filterConfig.getInitParameter("userAgent");
94          if (p!=null)
95              _pattern=Pattern.compile(p);
96          
97          String size=filterConfig.getInitParameter("cacheSize");
98          if (size!=null)
99              _agentCacheSize=Integer.parseInt(size);
100     }
101 
102     /* ------------------------------------------------------------ */
103     public String getUserAgent(ServletRequest request)
104     {
105         String ua=((HttpServletRequest)request).getHeader("User-Agent");
106         return getUserAgent(ua);
107     }
108     
109     /* ------------------------------------------------------------ */
110     /** Get UserAgent.
111      * The configured agent patterns are used to match against the passed user agent string.
112      * If any patterns match, the concatenation of pattern groups is returned as the user agent
113      * string. Match results are cached.
114      * @param ua A user agent string
115      * @return The matched pattern groups or the original user agent string
116      */
117     public String getUserAgent(String ua)
118     {
119         if (ua==null)
120             return null;
121         
122         String tag = (String)_agentCache.get(ua);
123         
124 
125         if (tag==null)
126         {
127             Matcher matcher=_pattern.matcher(ua);
128             if (matcher.matches())
129             {
130                 if(matcher.groupCount()>0)
131                 {
132                     for (int g=1;g<=matcher.groupCount();g++)
133                     {
134                         String group=matcher.group(g);
135                         if (group!=null)
136                             tag=tag==null?group:(tag+group);
137                     }
138                 }
139                 else 
140                     tag=matcher.group();
141             }
142             else
143                 tag=ua;
144 
145             if (_agentCache.size()>=_agentCacheSize)
146                     _agentCache.clear();
147                 _agentCache.put(ua,tag);
148 
149         }
150         return tag;
151     }
152 }