Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractRegExpFilter |
|
| 3.4545454545454546;3.455 |
1 | /* | |
2 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
3 | * contributor license agreements. See the NOTICE file distributed with | |
4 | * this work for additional information regarding copyright ownership. | |
5 | * The ASF licenses this file to you under the Apache License, Version 2.0 | |
6 | * (the "License"); you may not use this file except in compliance with | |
7 | * the License. You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software | |
12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | * See the License for the specific language governing permissions and | |
15 | * limitations under the License. | |
16 | */ | |
17 | ||
18 | package org.apache.shale.application; | |
19 | ||
20 | import java.io.IOException; | |
21 | import java.io.StreamTokenizer; | |
22 | import java.io.StringReader; | |
23 | import java.util.ArrayList; | |
24 | import java.util.List; | |
25 | import java.util.regex.Pattern; | |
26 | ||
27 | import javax.servlet.http.HttpServletResponse; | |
28 | ||
29 | import org.apache.commons.chain.Command; | |
30 | import org.apache.commons.chain.Context; | |
31 | import org.apache.commons.logging.Log; | |
32 | import org.apache.commons.logging.LogFactory; | |
33 | import org.apache.shale.application.faces.ShaleWebContext; | |
34 | ||
35 | /** | |
36 | * <p>Convenience base class for <code>Command</code> implementations that | |
37 | * perform regular expression matching against a set of zero or more | |
38 | * patterns. The default <code>Command</code> implementation will perform | |
39 | * the following algorithm.</p> | |
40 | * <ul> | |
41 | * <li>Retrieve the value to be compared by calling <code>value()</code>.</li> | |
42 | * <li>If the specified value is <code>null</code>, call <code>reject()</code> | |
43 | * and return <code>true</code> to indicate that request procesing is | |
44 | * complete.</li> | |
45 | * <li>If there are any include patterns, and the value matches one of | |
46 | * these patterns, call <code>accept()</code> and return | |
47 | * <code>false</code> to indicate request processing should continue.</li> | |
48 | * <li>If there are any exclude patterns, and the value matches one of | |
49 | * these patterns, call <code>reject()</code> and return | |
50 | * <code>true</code> to indicate that request processing is complete.</li> | |
51 | * <li>If there are any include patterns, and the value does not match one of | |
52 | * these patterns, call <code>reject()</code> and return | |
53 | * <code>true</code> to indicate that request processing is complete.</li> | |
54 | * <li>Call <code>accept()</code> and return <code>false</code> to indicate | |
55 | * that request processing should continue.</li> | |
56 | * </ul> | |
57 | * | |
58 | * <p><strong>USAGE NOTE:</strong> - See the class JavaDocs for | |
59 | * <code>java.util.regex.Pattern</code> for the valid syntax for regular | |
60 | * expression patterns supported by this command.</p> | |
61 | * | |
62 | * <p><strong>USAGE NOTE:</strong> - Commands based on this class will only | |
63 | * be effective if used before the regular filter chain is processed. In | |
64 | * other words, you should invoke it as part of a <code>preprocess</code> | |
65 | * chain in the <code>shale</code> catalog.</p> | |
66 | * | |
67 | * $Id: AbstractRegExpFilter.java 464373 2006-10-16 04:21:54Z rahul $ | |
68 | */ | |
69 | ||
70 | 0 | public abstract class AbstractRegExpFilter implements Command { |
71 | ||
72 | ||
73 | ||
74 | // -------------------------------------------------------- Static Variables | |
75 | ||
76 | ||
77 | /** | |
78 | * <p>Log instance for this class.</p> | |
79 | */ | |
80 | 0 | private static final Log log = LogFactory.getLog(AbstractRegExpFilter.class); |
81 | ||
82 | ||
83 | // ------------------------------------------------------ Instance Variables | |
84 | ||
85 | ||
86 | /** | |
87 | * <p>Comma-delimited regular expression patterns to exclude remote host | |
88 | * names that match.</p> | |
89 | */ | |
90 | 0 | private String excludes = null; |
91 | ||
92 | ||
93 | /** | |
94 | * <p>Array of regular expression patterns for the excludes list.</p> | |
95 | */ | |
96 | 0 | private Pattern excludesPatterns[] = new Pattern[0]; |
97 | ||
98 | ||
99 | /** | |
100 | * <p>Comma-delimited regular expression patterns to include remote host | |
101 | * names that match.</p> | |
102 | */ | |
103 | 0 | private String includes = null; |
104 | ||
105 | ||
106 | /** | |
107 | * <p>Array of regular expression patterns for the includes list.</p> | |
108 | */ | |
109 | 0 | private Pattern includesPatterns[] = new Pattern[0]; |
110 | ||
111 | /** | |
112 | * <p>Returns an array of regular expression patterns for the includes list.</p> | |
113 | */ | |
114 | 0 | protected Pattern[] getIncludesPatterns() { return includesPatterns; } |
115 | ||
116 | ||
117 | // -------------------------------------------------------------- Properties | |
118 | ||
119 | ||
120 | /** | |
121 | * <p>Return the comma-delimited regular expresson patterns to exclude | |
122 | * remote host names that match, if any; otherwise, return | |
123 | * <code>null</code>.</p> | |
124 | */ | |
125 | 0 | public String getExcludes() { return this.excludes; } |
126 | ||
127 | ||
128 | /** | |
129 | * <p>Set the comma-delimited regular expression patterns to exclude | |
130 | * remote host names that match, if any; or <code>null</code> for no | |
131 | * restrictions.</p> | |
132 | * | |
133 | * @param excludes New exclude pattern(s) | |
134 | */ | |
135 | public void setExcludes(String excludes) { | |
136 | 0 | this.excludes = excludes; |
137 | 0 | this.excludesPatterns = precompile(excludes); |
138 | 0 | } |
139 | ||
140 | ||
141 | /** | |
142 | * <p>Return the comma-delimited regular expresson patterns to include | |
143 | * remote host names that match, if any; otherwise, return | |
144 | * <code>null</code>.</p> | |
145 | */ | |
146 | 0 | public String getIncludes() { return this.includes; } |
147 | ||
148 | ||
149 | /** | |
150 | * <p>Set the comma-delimited regular expression patterns to include | |
151 | * remote host names that match, if any; or <code>null</code> for no | |
152 | * restrictions.</p> | |
153 | * | |
154 | * @param includes New include pattern(s) | |
155 | */ | |
156 | public void setIncludes(String includes) { | |
157 | 0 | this.includes = includes; |
158 | 0 | this.includesPatterns = precompile(includes); |
159 | 0 | } |
160 | ||
161 | ||
162 | // --------------------------------------------------------- Command Methods | |
163 | ||
164 | ||
165 | /** | |
166 | * <p>Perform the matching algorithm described in our class Javadocs | |
167 | * against the value returned by the <code>value()</code> method.</p> | |
168 | * | |
169 | * @param context <code>ShaleWebContext</code> for this request | |
170 | * | |
171 | * @exception Exception if an error occurs | |
172 | */ | |
173 | public boolean execute(Context context) throws Exception { | |
174 | ||
175 | // Acquire the value to be tested | |
176 | 0 | ShaleWebContext webContext = (ShaleWebContext) context; |
177 | 0 | String value = value(webContext); |
178 | 0 | if (log.isDebugEnabled()) { |
179 | 0 | log.debug("execute(" + value + ")"); |
180 | } | |
181 | 0 | if (value == null) { |
182 | 0 | if (log.isTraceEnabled()) { |
183 | 0 | log.trace(" reject(null)"); |
184 | } | |
185 | 0 | reject(webContext); |
186 | 0 | return true; |
187 | } | |
188 | ||
189 | // Check for a match on the included list | |
190 | 0 | if (matches(value, includesPatterns, true)) { |
191 | 0 | if (log.isTraceEnabled()) { |
192 | 0 | log.trace(" accept(include)"); |
193 | } | |
194 | 0 | accept(webContext); |
195 | 0 | return false; |
196 | } | |
197 | ||
198 | // Check for a match on the excluded list | |
199 | 0 | if (matches(value, excludesPatterns, false)) { |
200 | 0 | if (log.isTraceEnabled()) { |
201 | 0 | log.trace(" reject(exclude)"); |
202 | } | |
203 | 0 | reject(webContext); |
204 | 0 | return true; |
205 | } | |
206 | ||
207 | // If there was at least one include pattern, | |
208 | // unconditionally reject this request | |
209 | 0 | if ((includesPatterns != null) && (includesPatterns.length > 0)) { |
210 | 0 | if (log.isTraceEnabled()) { |
211 | 0 | log.trace(" reject(not include)"); |
212 | } | |
213 | 0 | reject(webContext); |
214 | 0 | return true; |
215 | } | |
216 | ||
217 | // Unconditionally accept this request | |
218 | 0 | if (log.isTraceEnabled()) { |
219 | 0 | log.trace(" accept(default)"); |
220 | } | |
221 | 0 | accept(webContext); |
222 | 0 | return false; |
223 | ||
224 | } | |
225 | ||
226 | ||
227 | // ------------------------------------------------------- Protected Methods | |
228 | ||
229 | ||
230 | /** | |
231 | * <p>Perform whatever processing is necessary to mark this request as | |
232 | * being accepted. The default implementation does nothing.</p> | |
233 | * | |
234 | * @param context <code>Context</code> for the current request | |
235 | * | |
236 | * @exception Exception if an error occurs | |
237 | */ | |
238 | protected void accept(ShaleWebContext context) throws Exception { | |
239 | ||
240 | // No action required | |
241 | ||
242 | 0 | } |
243 | ||
244 | ||
245 | /** | |
246 | * <p>Perform whatever processing is necessary to mark this request as | |
247 | * being rejected. The default implementation returns a status code | |
248 | * of <code>HttpServletResponse.SC_FORBIDDEN</code>.</p> | |
249 | * | |
250 | * @param context <code>Context</code> for the current request | |
251 | * | |
252 | * @exception Exception if an error occurs | |
253 | */ | |
254 | protected void reject(ShaleWebContext context) throws Exception { | |
255 | ||
256 | 0 | HttpServletResponse response = context.getResponse(); |
257 | 0 | response.sendError(HttpServletResponse.SC_FORBIDDEN); |
258 | ||
259 | 0 | } |
260 | ||
261 | ||
262 | /** | |
263 | * <p>Return the value, from the specified context, that should be used | |
264 | * to match against the configured exclude and include patterns. This | |
265 | * method must be implemented by a concrete subclass.</p> | |
266 | * | |
267 | * @param context <code>Context</code> for the current request | |
268 | */ | |
269 | protected abstract String value(ShaleWebContext context); | |
270 | ||
271 | ||
272 | // --------------------------------------------------------- Private Methods | |
273 | ||
274 | ||
275 | /** | |
276 | * <p>Match the specified expression against the specified precompiled | |
277 | * patterns. If there are no patterns, return the specified unrestricted | |
278 | * return value; otherwise, return <code>true</code> if the expression | |
279 | * matches one of the patterns, or <code>false</code> otherwise.</p> | |
280 | * | |
281 | * @param expr Expression to be tested | |
282 | * @param patterns Array of <code>Pattern</code> to be tested against | |
283 | * @param unrestricted Result to be returned if there are no matches | |
284 | */ | |
285 | protected boolean matches(String expr, Pattern patterns[], | |
286 | boolean unrestricted) { | |
287 | ||
288 | // Check for the unrestricted case | |
289 | 0 | if ((patterns == null) || (patterns.length == 0)) { |
290 | 0 | return unrestricted; |
291 | } | |
292 | ||
293 | // Compare each pattern in turn for a match | |
294 | 0 | for (int i = 0; i < patterns.length; i++) { |
295 | 0 | if (patterns[i].matcher(expr).matches()) { |
296 | 0 | return true; |
297 | } | |
298 | } | |
299 | ||
300 | // No match found, so return false | |
301 | 0 | return false; |
302 | ||
303 | } | |
304 | ||
305 | ||
306 | /** | |
307 | * <p>Parse the specified string of comma-delimited (and optionally quoted, | |
308 | * if an embedded comma is required) regular expressions into an array | |
309 | * of precompiled <code>Pattern</code> instances that represent these | |
310 | * expressons.</p> | |
311 | * | |
312 | * @param expr Comma-delimited regular expressions | |
313 | */ | |
314 | private Pattern[] precompile(String expr) { | |
315 | ||
316 | 0 | if (expr == null) { |
317 | 0 | return new Pattern[0]; |
318 | } | |
319 | ||
320 | // Set up to parse the specified expression | |
321 | 0 | StreamTokenizer st = |
322 | new StreamTokenizer(new StringReader(expr)); | |
323 | 0 | st.eolIsSignificant(false); |
324 | 0 | st.lowerCaseMode(false); |
325 | 0 | st.slashSlashComments(false); |
326 | 0 | st.slashStarComments(false); |
327 | 0 | st.wordChars(0x00, 0xff); |
328 | 0 | st.quoteChar('\''); |
329 | 0 | st.quoteChar('"'); |
330 | 0 | st.whitespaceChars(0, ' '); |
331 | 0 | st.whitespaceChars(',', ','); |
332 | 0 | List list = new ArrayList(); |
333 | 0 | int type = 0; |
334 | ||
335 | // Parse each included expression | |
336 | while (true) { | |
337 | try { | |
338 | 0 | type = st.nextToken(); |
339 | 0 | } catch (IOException e) { |
340 | ; // Can not happen | |
341 | 0 | } |
342 | 0 | if (type == StreamTokenizer.TT_EOF) { |
343 | 0 | break; |
344 | 0 | } else if (type == StreamTokenizer.TT_NUMBER) { |
345 | 0 | list.add(Pattern.compile("" + st.nval)); |
346 | 0 | } else if (type == StreamTokenizer.TT_WORD) { |
347 | 0 | list.add(Pattern.compile(st.sval.trim())); |
348 | 0 | } else { |
349 | 0 | throw new IllegalArgumentException(expr); |
350 | } | |
351 | } | |
352 | ||
353 | // Return the precompiled patterns as an array | |
354 | 0 | return (Pattern[]) list.toArray(new Pattern[list.size()]); |
355 | ||
356 | } | |
357 | ||
358 | ||
359 | } |