View Javadoc

1   /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.intercept.web;
17  
18  import java.beans.PropertyEditorSupport;
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.StringReader;
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  import org.acegisecurity.util.StringSplitUtils;
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.springframework.util.StringUtils;
29  
30  
31  /**
32   * Property editor to assist with the setup of a {@link FilterInvocationDefinitionSource}.<p>The class creates and
33   * populates a {@link RegExpBasedFilterInvocationDefinitionMap} or {@link PathBasedFilterInvocationDefinitionMap}
34   * (depending on the type of patterns presented).</p>
35   *  <p>By default the class treats presented patterns as regular expressions. If the keyword
36   * <code>PATTERN_TYPE_APACHE_ANT</code> is present (case sensitive), patterns will be treated as Apache Ant paths
37   * rather than regular expressions.</p>
38   *
39   * @author Ben Alex
40   * @version $Id: FilterInvocationDefinitionSourceEditor.java 1784 2007-02-24 21:00:24Z luke_t $
41   */
42  public class FilterInvocationDefinitionSourceEditor extends PropertyEditorSupport {
43      //~ Static fields/initializers =====================================================================================
44  
45      private static final Log logger = LogFactory.getLog(FilterInvocationDefinitionSourceEditor.class);
46      public static final String DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON =
47              "CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON";
48      public static final String DIRECTIVE_PATTERN_TYPE_APACHE_ANT = "PATTERN_TYPE_APACHE_ANT";
49  
50      //~ Methods ========================================================================================================
51  
52      public void setAsText(String s) throws IllegalArgumentException {
53          FilterInvocationDefinitionDecorator source = new FilterInvocationDefinitionDecorator();
54  
55          if ((s == null) || "".equals(s)) {
56              // Leave target object empty
57              source.setDecorated(new RegExpBasedFilterInvocationDefinitionMap());
58          } else {
59              // Check if we need to override the default definition map
60              if (s.lastIndexOf(DIRECTIVE_PATTERN_TYPE_APACHE_ANT) != -1) {
61                  source.setDecorated(new PathBasedFilterInvocationDefinitionMap());
62  
63                  if (logger.isDebugEnabled()) {
64                      logger.debug(("Detected " + DIRECTIVE_PATTERN_TYPE_APACHE_ANT
65                          + " directive; using Apache Ant style path expressions"));
66                  }
67              } else {
68                  source.setDecorated(new RegExpBasedFilterInvocationDefinitionMap());
69              }
70  
71              if (s.lastIndexOf(DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON) != -1) {
72                  if (logger.isDebugEnabled()) {
73                      logger.debug("Detected " + DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
74                          + " directive; Instructing mapper to convert URLs to lowercase before comparison");
75                  }
76  
77                  source.setConvertUrlToLowercaseBeforeComparison(true);
78              }
79  
80              BufferedReader br = new BufferedReader(new StringReader(s));
81              int counter = 0;
82              String line;
83  
84              List mappings = new ArrayList();
85  
86              while (true) {
87                  counter++;
88  
89                  try {
90                      line = br.readLine();
91                  } catch (IOException ioe) {
92                      throw new IllegalArgumentException(ioe.getMessage());
93                  }
94  
95                  if (line == null) {
96                      break;
97                  }
98  
99                  line = line.trim();
100 
101                 if (logger.isDebugEnabled()) {
102                     logger.debug("Line " + counter + ": " + line);
103                 }
104 
105                 if (line.startsWith("//")) {
106                     continue;
107                 }
108 
109                 // Attempt to detect malformed lines (as per SEC-204)
110                 if (line.lastIndexOf(DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON) != -1) {
111                     // Directive found; check for second directive or name=value
112                     if ((line.lastIndexOf(DIRECTIVE_PATTERN_TYPE_APACHE_ANT) != -1) || (line.lastIndexOf("=") != -1)) {
113                         throw new IllegalArgumentException("Line appears to be malformed: " + line);
114                     }
115                 }
116 
117                 // Attempt to detect malformed lines (as per SEC-204)
118                 if (line.lastIndexOf(DIRECTIVE_PATTERN_TYPE_APACHE_ANT) != -1) {
119                     // Directive found; check for second directive or name=value
120                     if ((line.lastIndexOf(DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON) != -1)
121                         || (line.lastIndexOf("=") != -1)) {
122                         throw new IllegalArgumentException("Line appears to be malformed: " + line);
123                     }
124                 }
125 
126                 // Skip lines that are not directives
127                 if (line.lastIndexOf('=') == -1) {
128                     continue;
129                 }
130 
131                 if (line.lastIndexOf("==") != -1) {
132                     throw new IllegalArgumentException("Only single equals should be used in line " + line);
133                 }
134 
135                 // Tokenize the line into its name/value tokens
136                 // As per SEC-219, use the LAST equals as the delimiter between LHS and RHS
137                 String name = StringSplitUtils.substringBeforeLast(line, "=");
138                 String value = StringSplitUtils.substringAfterLast(line, "=");
139 
140                 if (!StringUtils.hasText(name) || !StringUtils.hasText(value)) {
141                     throw new IllegalArgumentException("Failed to parse a valid name/value pair from " + line);
142                 }
143 
144                 // Attempt to detect malformed lines (as per SEC-204)
145                 if (source.isConvertUrlToLowercaseBeforeComparison()
146                     && source.getDecorated() instanceof PathBasedFilterInvocationDefinitionMap) {
147                     // Should all be lowercase; check each character
148                     // We only do this for Ant (regexp have control chars)
149                     for (int i = 0; i < name.length(); i++) {
150                         String character = name.substring(i, i + 1);
151 
152                         if (!character.toLowerCase().equals(character)) {
153                             throw new IllegalArgumentException("You are using the "
154                                 + DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
155                                 + " with Ant Paths, yet you have specified an uppercase character in line: " + line
156                                 + " (character '" + character + "')");
157                         }
158                     }
159                 }
160 
161                 FilterInvocationDefinitionSourceMapping mapping = new FilterInvocationDefinitionSourceMapping();
162                 mapping.setUrl(name);
163 
164                 String[] tokens = org.springframework.util.StringUtils
165                         .commaDelimitedListToStringArray(value);
166 
167                 for (int i = 0; i < tokens.length; i++) {
168                     mapping.addConfigAttribute(tokens[i].trim());
169                 }
170 
171                 mappings.add(mapping);
172             }
173             source.setMappings(mappings);
174         }
175 
176         setValue(source.getDecorated());
177     }
178 }