001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.server.dispatch;
031:
032: import com.caucho.config.ConfigException;
033: import com.caucho.server.util.CauchoSystem;
034: import com.caucho.util.CharBuffer;
035: import com.caucho.util.L10N;
036:
037: import javax.annotation.PostConstruct;
038: import javax.servlet.ServletException;
039: import java.util.ArrayList;
040: import java.util.HashSet;
041: import java.util.regex.Pattern;
042:
043: /**
044: * Configuration for a filter.
045: */
046: public class FilterMapping extends FilterConfigImpl {
047: static L10N L = new L10N(FilterMapping.class);
048:
049: private String _urlPattern;
050: private final ArrayList<String> _servletNames = new ArrayList<String>();
051:
052: // The match expressions
053: private final ArrayList<Match> _matchList = new ArrayList<Match>();
054:
055: private HashSet<String> _dispatcher;
056:
057: /**
058: * Creates a new filter mapping object.
059: */
060: public FilterMapping() {
061: }
062:
063: /**
064: * Sets the url pattern
065: */
066: public URLPattern createUrlPattern() throws ServletException {
067: return new URLPattern();
068: }
069:
070: /**
071: * Gets the url pattern
072: */
073: public String getURLPattern() {
074: return _urlPattern;
075: }
076:
077: /**
078: * Sets the url regexp
079: */
080: public void setURLRegexp(String pattern) throws ServletException {
081: Pattern regexp;
082:
083: if (CauchoSystem.isCaseInsensitive())
084: regexp = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
085: else
086: regexp = Pattern.compile(pattern, 0);
087:
088: _matchList.add(Match.createInclude(regexp));
089: }
090:
091: /**
092: * Sets the servlet name
093: */
094: public void addServletName(String servletName) {
095: if (servletName == null)
096: throw new NullPointerException();
097:
098: _servletNames.add(servletName);
099: }
100:
101: /**
102: * Adds a dispatcher.
103: */
104: public void addDispatcher(String dispatcher) throws ConfigException {
105: if (_dispatcher == null)
106: _dispatcher = new HashSet<String>();
107:
108: if (dispatcher == null
109: || (!dispatcher.equals("REQUEST")
110: && !dispatcher.equals("FORWARD")
111: && !dispatcher.equals("INCLUDE") && !dispatcher
112: .equals("ERROR"))) {
113: throw new ConfigException(
114: L
115: .l(
116: "'{0}' is an unknown value for <dispatcher> 'REQUEST', 'FORWARD', 'INCLUDE', and 'ERROR' are the valid values.",
117: dispatcher));
118: }
119:
120: _dispatcher.add(dispatcher);
121: }
122:
123: /**
124: * True if the dispatcher is for REQUEST.
125: */
126: public boolean isRequest() {
127: return _dispatcher == null || _dispatcher.contains("REQUEST");
128: }
129:
130: /**
131: * True if the dispatcher is for INCLUDE.
132: */
133: public boolean isInclude() {
134: return _dispatcher != null && _dispatcher.contains("INCLUDE");
135: }
136:
137: /**
138: * True if the dispatcher is for FORWARD.
139: */
140: public boolean isForward() {
141: return _dispatcher != null && _dispatcher.contains("FORWARD");
142: }
143:
144: /**
145: * True if the dispatcher is for ERROR.
146: */
147: public boolean isError() {
148: return _dispatcher != null && _dispatcher.contains("ERROR");
149: }
150:
151: /**
152: * Returns true if the filter map matches the invocation URL.
153: *
154: * @param invocation the request's invocation
155: * @param servletName the servlet name to match
156: */
157: boolean isMatch(String servletName) {
158: for (int i = 0; i < _servletNames.size(); i++) {
159: String matchName = _servletNames.get(i);
160:
161: if (matchName.equals(servletName) || "*".equals(matchName))
162: return true;
163: }
164:
165: return false;
166: }
167:
168: /**
169: * Returns true if the filter map matches the invocation URL.
170: *
171: * @param invocation the request's invocation
172: */
173: boolean isMatch(ServletInvocation invocation) {
174: return isMatch(invocation.getServletPath(), invocation
175: .getPathInfo());
176: }
177:
178: /**
179: * Returns true if the filter map matches the servlet path and path info.
180: * */
181: public boolean isMatch(String servletPath, String pathInfo) {
182: String uri;
183:
184: if (pathInfo == null)
185: uri = servletPath;
186: else if (servletPath == null)
187: uri = pathInfo;
188: else
189: uri = servletPath + pathInfo;
190:
191: int size = _matchList.size();
192:
193: for (int i = 0; i < size; i++) {
194: Match match = _matchList.get(i);
195:
196: int value = match.match(uri);
197:
198: switch (value) {
199: case Match.INCLUDE:
200: return true;
201: case Match.EXCLUDE:
202: return false;
203: }
204: }
205:
206: return false;
207: }
208:
209: /**
210: * Converts a url-pattern to a regular expression
211: *
212: * @param pattern the url-pattern to convert
213: * @param flags the regexp flags, e.g. "i" for case insensitive
214: *
215: * @return the equivalent regular expression
216: */
217: private Pattern urlPatternToRegexp(String pattern, int flags)
218: throws ServletException {
219: if (pattern.length() == 0 || pattern.length() == 1
220: && pattern.charAt(0) == '/') {
221: try {
222: return Pattern.compile("^/$", flags);
223: } catch (Exception e) {
224: throw new ServletException(e);
225: }
226: }
227:
228: int length = pattern.length();
229: boolean isExact = true;
230:
231: if (pattern.charAt(0) != '/' && pattern.charAt(0) != '*') {
232: pattern = "/" + pattern;
233: length++;
234: }
235:
236: int prefixLength = -1;
237: boolean isShort = false;
238: CharBuffer cb = new CharBuffer();
239: cb.append("^");
240: for (int i = 0; i < length; i++) {
241: char ch = pattern.charAt(i);
242:
243: if (ch == '*' && i + 1 == length && i > 0) {
244: isExact = false;
245:
246: if (pattern.charAt(i - 1) == '/') {
247: cb.setLength(cb.length() - 1);
248:
249: if (prefixLength < 0)
250: prefixLength = i - 1;
251:
252: } else if (prefixLength < 0)
253: prefixLength = i;
254:
255: if (prefixLength == 0)
256: prefixLength = 1;
257: } else if (ch == '*') {
258: isExact = false;
259: cb.append(".*");
260: if (prefixLength < 0)
261: prefixLength = i;
262:
263: if (i == 0)
264: isShort = true;
265: } else if (ch == '.' || ch == '[' || ch == '^' || ch == '$'
266: || ch == '{' || ch == '}' || ch == '|' || ch == '('
267: || ch == ')' || ch == '?') {
268: cb.append('\\');
269: cb.append(ch);
270: } else
271: cb.append(ch);
272: }
273:
274: if (isExact)
275: cb.append('$');
276: else
277: cb.append("(?=/)|" + cb.toString() + "$");
278:
279: try {
280: return Pattern.compile(cb.close(), flags);
281: } catch (Exception e) {
282: throw new ServletException(e);
283: }
284: }
285:
286: /**
287: * Returns a printable representation of the filter config object.
288: */
289: public String toString() {
290: return "FilterMapping[pattern=" + _urlPattern + ",name="
291: + getFilterName() + "]";
292: }
293:
294: public class URLPattern {
295: boolean _hasInclude = false;
296:
297: /**
298: * Sets the singleton url-pattern.
299: */
300: public URLPattern addText(String pattern)
301: throws ServletException {
302: pattern = pattern.trim();
303:
304: _urlPattern = pattern;
305:
306: Pattern regexp;
307:
308: if (CauchoSystem.isCaseInsensitive())
309: regexp = urlPatternToRegexp(pattern,
310: Pattern.CASE_INSENSITIVE);
311: else
312: regexp = urlPatternToRegexp(pattern, 0);
313:
314: _hasInclude = true;
315:
316: _matchList.add(Match.createInclude(regexp));
317:
318: return this ;
319: }
320:
321: /**
322: * Adds an include pattern.
323: */
324: public void addIncludePattern(String pattern)
325: throws ServletException {
326: pattern = pattern.trim();
327:
328: Pattern regexp;
329:
330: if (CauchoSystem.isCaseInsensitive())
331: regexp = urlPatternToRegexp(pattern,
332: Pattern.CASE_INSENSITIVE);
333: else
334: regexp = urlPatternToRegexp(pattern, 0);
335:
336: _hasInclude = true;
337:
338: _matchList.add(Match.createInclude(regexp));
339: }
340:
341: /**
342: * Adds an exclude pattern.
343: */
344: public void addExcludePattern(String pattern)
345: throws ServletException {
346: pattern = pattern.trim();
347:
348: Pattern regexp;
349:
350: if (CauchoSystem.isCaseInsensitive())
351: regexp = urlPatternToRegexp(pattern,
352: Pattern.CASE_INSENSITIVE);
353: else
354: regexp = urlPatternToRegexp(pattern, 0);
355:
356: _matchList.add(Match.createExclude(regexp));
357: }
358:
359: /**
360: * Adds an include regexp.
361: */
362: public void addIncludeRegexp(String pattern) {
363: pattern = pattern.trim();
364:
365: Pattern regexp;
366:
367: if (CauchoSystem.isCaseInsensitive())
368: regexp = Pattern.compile(pattern,
369: Pattern.CASE_INSENSITIVE);
370: else
371: regexp = Pattern.compile(pattern, 0);
372:
373: _hasInclude = true;
374:
375: _matchList.add(Match.createInclude(regexp));
376: }
377:
378: /**
379: * Adds an exclude regexp.
380: */
381: public void addExcludeRegexp(String pattern) {
382: pattern = pattern.trim();
383:
384: Pattern regexp;
385:
386: if (CauchoSystem.isCaseInsensitive())
387: regexp = Pattern.compile(pattern,
388: Pattern.CASE_INSENSITIVE);
389: else
390: regexp = Pattern.compile(pattern, 0);
391:
392: _matchList.add(Match.createExclude(regexp));
393: }
394:
395: /**
396: * Initialize, adding the all-match for exclude patterns.
397: */
398: @PostConstruct
399: public void init() throws Exception {
400: if (_matchList.size() > 0 && !_hasInclude) {
401: Pattern regexp = Pattern.compile("");
402: _matchList.add(Match.createInclude(regexp));
403: }
404: }
405: }
406:
407: static class Match {
408: static final int INCLUDE = 1;
409: static final int EXCLUDE = -1;
410: static final int NO_MATCH = 0;
411:
412: private final Pattern _regexp;
413: private final int _value;
414:
415: private Match(Pattern regexp, int value) {
416: _regexp = regexp;
417: _value = value;
418: }
419:
420: /**
421: * Creates an include pattern.
422: */
423: static Match createInclude(Pattern regexp) {
424: return new Match(regexp, INCLUDE);
425: }
426:
427: /**
428: * Creates an exclude pattern.
429: */
430: static Match createExclude(Pattern regexp) {
431: return new Match(regexp, EXCLUDE);
432: }
433:
434: /**
435: * Returns the match value.
436: */
437: int match(String uri) {
438: if (_regexp.matcher(uri).find())
439: return _value;
440: else
441: return NO_MATCH;
442: }
443: }
444: }
|