Author: craigmcc
Date: Thu Nov 30 14:55:59 2006
New Revision: 481099
URL: http://svn.apache.org/viewvc?view=rev&rev=481099
Log:
Add support for configuring (via pattern matching) the set of resource ids
that a particular processor will be allowed to provide, with disallowed ones
getting an HTTP 404 response back. For the classloader and webapp resources,
the default configuration has these patterns:
Excluded: *.class,*.jsp,*.properties
Included: *.css,*.gif,*.html,*.jpg,*.js,*.png,*.xml
(As before, the webapp resource processor disallows access inside WEB-INF,
no matter whether the file matches an included pattern or not).
Defaults for the dynamic processor (the ones that map a resource id to a
method on a managed bean) are still set to allow all patterns. This deserves
more thought; there does not appear to be a set of sensible defaults that
is likely to work for a majority of applications using this feature.
SHALE-344
Added:
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/FilteringProcessor.java
(with props)
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/faces/
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/faces/MappingsHelperTestCase.java
(with props)
Modified:
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/Constants.java
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/faces/MappingsHelper.java
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/faces/RemotingPhaseListener.java
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/AbstractResourceProcessor.java
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/MethodBindingProcessor.java
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/impl/ClassResourceProcessorTestCase.java
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/impl/MethodBindingProcessorTestCase.java
Modified:
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/Constants.java
URL:
http://svn.apache.org/viewvc/shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/Constants.java?view=diff&rev=481099&r1=481098&r2=481099
==============================================================================
---
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/Constants.java
(original)
+++
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/Constants.java
Thu Nov 30 14:55:59 2006
@@ -18,7 +18,7 @@
package org.apache.shale.remoting;
/**
- * <p>Manifest constants related to Shale remoting support.</p>
+ * <p>Manifest constants related to Shale Remoting support.</p>
*/
public final class Constants {
@@ -36,6 +36,50 @@
/**
+ * <p>Context initialization parameter containing a comma-delimited list
+ * of URL matching patterns for resource identifiers that will be
+ * explicitly excluded. If not specified, the value of constant
+ * CLASS_RESOURCES_EXCLUDES_DEFAULT will be used.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String CLASS_RESOURCES_EXCLUDES =
+ "org.apache.shale.remoting.CLASS_RESOURCES_EXCLUDES";
+
+
+ /**
+ * <p>Default value for the CLASS_RESOURCES_EXCLUDES context initialization
+ * parameter if no explicit value is specified.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String CLASS_RESOURCES_EXCLUDES_DEFAULT =
+ "*.class,*.jsp,*.properties";
+
+
+ /**
+ * <p>Context initialization parameter containing a comma-delimited list
+ * of URL matching patterns for resource identifiers that will be
+ * explicitly included. If not specified, the value of constant
+ * CLASS_RESOURCES_INCLUDES_DEFAULT will be used.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String CLASS_RESOURCES_INCLUDES =
+ "org.apache.shale.remoting.CLASS_RESOURCES_INCLUDES";
+
+
+ /**
+ * <p>Default value for the CLASS_RESOURCES_INCLUDES context initialization
+ * parameter if no explicit value is specified.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String CLASS_RESOURCES_INCLUDES_DEFAULT =
+ "*.css,*.gif,*.html,*.jpg,*.js,*.png,*.xml";
+
+
+ /**
* <p>Context initialization parameter containing a comma-delimited list of
* colon-delimited pairs, with each pair representing a URL matching
pattern
* (such as <code>/foo/*</code> or <code>*.foo</code>) and the fully
qualified
@@ -50,6 +94,50 @@
/**
+ * <p>Context initialization parameter containing a comma-delimited list
+ * of URL matching patterns for resource identifiers that will be
+ * explicitly excluded. If not specified, the value of constant
+ * DYNAMIC_RESOURCES_EXCLUDES_DEFAULT will be used.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String DYNAMIC_RESOURCES_EXCLUDES =
+ "org.apache.shale.remoting.DYNAMIC_RESOURCES_EXCLUDES";
+
+
+ /**
+ * <p>Default value for the DYNAMIC_RESOURCES_EXCLUDES context
initialization
+ * parameter if no explicit value is specified.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String DYNAMIC_RESOURCES_EXCLUDES_DEFAULT =
+ null;
+
+
+ /**
+ * <p>Context initialization parameter containing a comma-delimited list
+ * of URL matching patterns for resource identifiers that will be
+ * explicitly included. If not specified, the value of constant
+ * DYNAMIC_RESOURCES_INCLUDES_DEFAULT will be used.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String DYNAMIC_RESOURCES_INCLUDES =
+ "org.apache.shale.remoting.DYNAMIC_RESOURCES_INCLUDES";
+
+
+ /**
+ * <p>Default value for the DYNAMIC_RESOURCES_INCLUDES context
initialization
+ * parameter if no explicit value is specified.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String DYNAMIC_RESOURCES_INCLUDES_DEFAULT =
+ null;
+
+
+ /**
* <p>Context initialization parameter containing a comma-delimited list of
* colon-delimited pairs, with each pair representing a URL matching
pattern
* (such as <code>/foo/*</code> or <code>*.foo</code>) and the fully
qualified
@@ -102,6 +190,50 @@
/**
+ * <p>Context initialization parameter containing a comma-delimited list
+ * of URL matching patterns for resource identifiers that will be
+ * explicitly excluded. If not specified, the value of constant
+ * OTHER_RESOURCES_EXCLUDES_DEFAULT will be used.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String OTHER_RESOURCES_EXCLUDES =
+ "org.apache.shale.remoting.OTHER_RESOURCES_EXCLUDES";
+
+
+ /**
+ * <p>Default value for the OTHER_RESOURCES_EXCLUDES context initialization
+ * parameter if no explicit value is specified.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String OTHER_RESOURCES_EXCLUDES_DEFAULT =
+ "*.class,*.jsp,*.properties";
+
+
+ /**
+ * <p>Context initialization parameter containing a comma-delimited list
+ * of URL matching patterns for resource identifiers that will be
+ * explicitly included. If not specified, the value of constant
+ * OTHER_RESOURCES_INCLUDES_DEFAULT will be used.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String OTHER_RESOURCES_INCLUDES =
+ "org.apache.shale.remoting.OTHER_RESOURCES_INCLUDES";
+
+
+ /**
+ * <p>Default value for the OTHER_RESOURCES_INCLUDES context initialization
+ * parameter if no explicit value is specified.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String OTHER_RESOURCES_INCLUDES_DEFAULT =
+ "*.css,*.gif,*.html,*.jpg,*.js,*.png,*.xml";
+
+
+ /**
* <p>Context initialization parameter containing a comma-delimited list of
* colon-delimited pairs, with each pair representing a URL matching
pattern
* (such as <code>/foo/*</code> or <code>*.foo</code>) and the fully
qualified
@@ -112,6 +244,50 @@
*/
public static final String OTHER_RESOURCES_PARAM =
"org.apache.shale.remoting.OTHER_RESOURCES";
+
+
+ /**
+ * <p>Context initialization parameter containing a comma-delimited list
+ * of URL matching patterns for resource identifiers that will be
+ * explicitly excluded. If not specified, the value of constant
+ * WEB_RESOURCES_EXCLUDES_DEFAULT will be used.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String WEBAPP_RESOURCES_EXCLUDES =
+ "org.apache.shale.remoting.WEB_RESOURCES_EXCLUDES";
+
+
+ /**
+ * <p>Default value for the WEB_RESOURCES_EXCLUDES context initialization
+ * parameter if no explicit value is specified.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String WEBAPP_RESOURCES_EXCLUDES_DEFAULT =
+ "*.class,*.jsp,*.properties";
+
+
+ /**
+ * <p>Context initialization parameter containing a comma-delimited list
+ * of URL matching patterns for resource identifiers that will be
+ * explicitly included. If not specified, the value of constant
+ * WEB_RESOURCES_INCLUDES_DEFAULT will be used.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String WEBAPP_RESOURCES_INCLUDES =
+ "org.apache.shale.remoting.WEB_RESOURCES_INCLUDES";
+
+
+ /**
+ * <p>Default value for the WEB_RESOURCES_INCLUDES context initialization
+ * parameter if no explicit value is specified.</p>
+ *
+ * @since 1.0.4
+ */
+ public static final String WEBAPP_RESOURCES_INCLUDES_DEFAULT =
+ "*.css,*.gif,*.html,*.jpg,*.js,*.png,*.xml";
/**
Modified:
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/faces/MappingsHelper.java
URL:
http://svn.apache.org/viewvc/shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/faces/MappingsHelper.java?view=diff&rev=481099&r1=481098&r2=481099
==============================================================================
---
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/faces/MappingsHelper.java
(original)
+++
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/faces/MappingsHelper.java
Thu Nov 30 14:55:59 2006
@@ -35,6 +35,7 @@
import org.apache.shale.remoting.Mappings;
import org.apache.shale.remoting.Mechanism;
import org.apache.shale.remoting.Processor;
+import org.apache.shale.remoting.impl.FilteringProcessor;
import org.apache.shale.remoting.impl.MappingImpl;
import org.apache.shale.remoting.impl.MappingsImpl;
import org.w3c.dom.Document;
@@ -100,6 +101,12 @@
* @param context <code>FacesContext</code> for the current request
* @param mappings [EMAIL PROTECTED] Mappings} instance being configured
* @param paramName Context initialization parameter name to process
+ * @param excludesName Context initialization parameter containing our
+ * exclude patterns
+ * @param excludesDefault Default exclude patterns if none are configured
+ * @param includesName Context initialization parameter containing our
+ * include patterns
+ * @param includesDefault Default include patterns if none are configured
* @param mechanism [EMAIL PROTECTED] Mechanism} to configure on created
instances
* @param defaultValue Default value (if any) if not specified
*
@@ -107,8 +114,10 @@
* or configured
*/
private void configureMappings(FacesContext context, Mappings mappings,
- String paramName, Mechanism mechanism,
- String defaultValue) {
+ String paramName,
+ String excludesName, String excludesDefault,
+ String includesName, String includesDefault,
+ Mechanism mechanism, String defaultValue) {
// Identify the Mapping implementation class to be used
Class clazz = MappingImpl.class;
@@ -167,8 +176,25 @@
mapping.setMappings(mappings);
mapping.setMechanism(mechanism);
mapping.setPattern(pattern);
- mapping.setProcessor((Processor) processorClazz.newInstance());
+ Processor processor = (Processor) processorClazz.newInstance();
+ if (processor instanceof FilteringProcessor) {
+ String excludesPatterns =
+
context.getExternalContext().getInitParameter(excludesName);
+ if (excludesPatterns == null) {
+ excludesPatterns = excludesDefault;
+ }
+ ((FilteringProcessor)
processor).setExcludes(excludesPatterns);
+ String includesPatterns =
+
context.getExternalContext().getInitParameter(includesName);
+ if (includesPatterns == null) {
+ includesPatterns = includesDefault;
+ }
+ ((FilteringProcessor)
processor).setIncludes(includesPatterns);
+ }
+ mapping.setProcessor(processor);
mappings.addMapping(mapping);
+ } catch (RuntimeException e) {
+ throw e;
} catch (Exception e) {
throw new FacesException(e);
}
@@ -214,15 +240,31 @@
// Configure the Mapping instances for this Mappings instance
configureMappings(context, mappings, Constants.CLASS_RESOURCES_PARAM,
+ Constants.CLASS_RESOURCES_EXCLUDES,
+ Constants.CLASS_RESOURCES_EXCLUDES_DEFAULT,
+ Constants.CLASS_RESOURCES_INCLUDES,
+ Constants.CLASS_RESOURCES_INCLUDES_DEFAULT,
Mechanism.CLASS_RESOURCE,
"/static/*:org.apache.shale.remoting.impl.ClassResourceProcessor");
configureMappings(context, mappings, Constants.DYNAMIC_RESOURCES_PARAM,
+ Constants.DYNAMIC_RESOURCES_EXCLUDES,
+ Constants.DYNAMIC_RESOURCES_EXCLUDES_DEFAULT,
+ Constants.DYNAMIC_RESOURCES_INCLUDES,
+ Constants.DYNAMIC_RESOURCES_INCLUDES_DEFAULT,
Mechanism.DYNAMIC_RESOURCE,
"/dynamic/*:org.apache.shale.remoting.impl.MethodBindingProcessor");
configureMappings(context, mappings, Constants.OTHER_RESOURCES_PARAM,
+ Constants.OTHER_RESOURCES_EXCLUDES,
+ Constants.OTHER_RESOURCES_EXCLUDES_DEFAULT,
+ Constants.OTHER_RESOURCES_INCLUDES,
+ Constants.OTHER_RESOURCES_INCLUDES_DEFAULT,
Mechanism.OTHER_RESOURCE,
null);
configureMappings(context, mappings, Constants.WEBAPP_RESOURCES_PARAM,
+ Constants.WEBAPP_RESOURCES_EXCLUDES,
+ Constants.WEBAPP_RESOURCES_EXCLUDES_DEFAULT,
+ Constants.WEBAPP_RESOURCES_INCLUDES,
+ Constants.WEBAPP_RESOURCES_INCLUDES_DEFAULT,
Mechanism.WEBAPP_RESOURCE,
"/webapp/*:org.apache.shale.remoting.impl.WebResourceProcessor");
Modified:
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/faces/RemotingPhaseListener.java
URL:
http://svn.apache.org/viewvc/shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/faces/RemotingPhaseListener.java?view=diff&rev=481099&r1=481098&r2=481099
==============================================================================
---
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/faces/RemotingPhaseListener.java
(original)
+++
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/faces/RemotingPhaseListener.java
Thu Nov 30 14:55:59 2006
@@ -31,6 +31,7 @@
import org.apache.commons.logging.LogFactory;
import org.apache.shale.remoting.Mapping;
import org.apache.shale.remoting.Mappings;
+import org.apache.shale.remoting.Processor;
/**
* <p>A JavaServer Faces <code>PhaseListener</code> that provides support for
@@ -109,7 +110,8 @@
+ "' with resource id '" + resourceId + "'");
}
try {
- mapping.getProcessor().process(context, resourceId);
+ Processor processor = mapping.getProcessor();
+ processor.process(context, resourceId);
} catch (IOException e) {
throw new FacesException(e);
}
Modified:
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/AbstractResourceProcessor.java
URL:
http://svn.apache.org/viewvc/shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/AbstractResourceProcessor.java?view=diff&rev=481099&r1=481098&r2=481099
==============================================================================
---
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/AbstractResourceProcessor.java
(original)
+++
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/AbstractResourceProcessor.java
Thu Nov 30 14:55:59 2006
@@ -24,9 +24,11 @@
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
@@ -36,6 +38,7 @@
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.apache.shale.remoting.impl.FilteringProcessor;
import org.apache.shale.remoting.Processor;
import org.apache.shale.remoting.faces.ResponseFactory;
@@ -43,7 +46,7 @@
* <p>Convenience abstract base class for [EMAIL PROTECTED] Processor}
implementations
* that serve up static resources.</p>
*/
-public abstract class AbstractResourceProcessor implements Processor {
+public abstract class AbstractResourceProcessor extends FilteringProcessor {
// ------------------------------------------------------ Instance
Variables
@@ -79,6 +82,19 @@
}
if (!resourceId.startsWith("/")) {
throw new IllegalArgumentException(resourceId);
+ }
+
+ // Filter based on our includes and excludes patterns
+ if (!accept(resourceId)) {
+ if (log().isTraceEnabled()) {
+ log().trace("Resource id '" + resourceId
+ + "' rejected by include/exclude rules");
+ }
+ // Send an HTTP "not found" response to avoid giving the client
+ // any information about a resource that exists and was refused,
+ // versus a resource that does not exist
+ sendNotFound(context, resourceId);
+ context.responseComplete();
}
// Acquire a URL to the specified resource, if it exists
Added:
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/FilteringProcessor.java
URL:
http://svn.apache.org/viewvc/shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/FilteringProcessor.java?view=auto&rev=481099
==============================================================================
---
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/FilteringProcessor.java
(added)
+++
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/FilteringProcessor.java
Thu Nov 30 14:55:59 2006
@@ -0,0 +1,266 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shale.remoting.impl;
+
+import java.io.IOException;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.shale.remoting.Processor;
+
+/**
+ * <p>Abstract base class for [EMAIL PROTECTED] Processor} implementations
that filter
+ * requests based on matching the resource identifier against a set of
+ * <code>includes</code> and <code>excludes</code> regular expressions.</p>
+ *
+ * @since 1.0.4
+ */
+public abstract class FilteringProcessor implements Processor {
+
+
+ // ------------------------------------------------------ Instance
Variables
+
+
+ /**
+ * <p>Comma-delimited regular expression patterns to exclude remote host
+ * names that match.</p>
+ */
+ private String excludes = null;
+
+
+ /**
+ * <p>Array of regular expression patterns for the excludes list.</p>
+ */
+ private String excludesPatterns[] = new String[0];
+
+
+ /**
+ * <p>Comma-delimited regular expression patterns to include remote host
+ * names that match.</p>
+ */
+ private String includes = null;
+
+
+ /**
+ * <p>Array of regular expression patterns for the includes list.</p>
+ */
+ private String includesPatterns[] = new String[0];
+
+
+ // --------------------------------------------------------------
Properties
+
+
+ /**
+ * <p>Return the comma-delimited regular expresson patterns to exclude
+ * remote host names that match, if any; otherwise, return
+ * <code>null</code>.</p>
+ */
+ public String getExcludes() { return this.excludes; }
+
+
+ /**
+ * <p>Set the comma-delimited regular expression patterns to exclude
+ * remote host names that match, if any; or <code>null</code> for no
+ * restrictions.</p>
+ *
+ * @param excludes New exclude pattern(s)
+ */
+ public void setExcludes(String excludes) {
+ this.excludes = excludes;
+ this.excludesPatterns = precompile(excludes);
+ }
+
+
+ /**
+ * <p>Return the comma-delimited regular expresson patterns to include
+ * remote host names that match, if any; otherwise, return
+ * <code>null</code>.</p>
+ */
+ public String getIncludes() { return this.includes; }
+
+
+ /**
+ * <p>Set the comma-delimited regular expression patterns to include
+ * remote host names that match, if any; or <code>null</code> for no
+ * restrictions.</p>
+ *
+ * @param includes New include pattern(s)
+ */
+ public void setIncludes(String includes) {
+ this.includes = includes;
+ this.includesPatterns = precompile(includes);
+ }
+
+
+ // ------------------------------------------------------- Protected
Methods
+
+
+ /**
+ * <p>Return <code>true</code> if we should accept a request for the
+ * specified resource identifier, based upon our configured includes
+ * and excludes patterns (if any).</p>
+ *
+ * @param resourceId Resource identifier to validate
+ */
+ protected boolean accept(String resourceId) {
+
+ // Check for a match on the excluded list
+ if (matches(resourceId, excludesPatterns, false)) {
+ return false;
+ }
+
+ // Check for a match on the included list
+ if (matches(resourceId, includesPatterns, true)) {
+ return true;
+ }
+
+ // If there was at least one include pattern,
+ // unconditionally reject this request
+ if ((includesPatterns != null) && (includesPatterns.length > 0)) {
+ return false;
+ }
+
+ // Unconditionally accept this request
+ return true;
+
+ }
+
+
+ // --------------------------------------------------------- Private
Methods
+
+
+ /**
+ * <p>Match the specified expression against the specified precompiled
+ * patterns. If there are no patterns, return the specified unrestricted
+ * return value; otherwise, return <code>true</code> if the expression
+ * matches one of the patterns, or <code>false</code> otherwise.</p>
+ *
+ * @param expr Expression to be tested
+ * @param patterns Array of <code>Pattern</code> to be tested against
+ * @param unrestricted Result to be returned if there are no matches
+ */
+ private boolean matches(String expr, String patterns[],
+ boolean unrestricted) {
+
+ // Check for the unrestricted case
+ if ((patterns == null) || (patterns.length == 0)) {
+ return unrestricted;
+ }
+
+ // Compare each pattern in turn for a match
+ for (int i = 0; i < patterns.length; i++) {
+ if (patterns[i].startsWith("*")) {
+ return expr.endsWith(patterns[i].substring(1));
+ } else if (patterns[i].endsWith("*")) {
+ return
expr.startsWith(patterns[i].substring(0,patterns[i].length() - 1));
+ } else {
+ return patterns[i].equals(expr);
+ }
+ }
+
+ // No match found, so return false
+ return false;
+
+ }
+
+
+ /**
+ * <p>Parse the specified string of comma-delimited URL pattern
+ * matching expressions into an array of patterns that can be processed
+ * at runtime more quickly. Valid patterns are the same as those
+ * supported for matching a request URI to a Processor instance:</p>
+ * <ul>
+ * <li>Must not be null or zero-length string</li>
+ * <li>EITHER must start with "/" and end with "/*"</li>
+ * <li>OR must start with "*." and not have any other period</li>
+ * </ul>
+ *
+ * @param expr Comma-delimited URL pattern matching expressions
+ *
+ * @exception IllegalArgumentException if an invalid pattern is encountered
+ */
+ private String[] precompile(String expr) {
+
+ if (expr == null) {
+ return new String[0];
+ }
+
+ // Set up to parse the specified expression
+ String pattern = null;
+ StreamTokenizer st =
+ new StreamTokenizer(new StringReader(expr));
+ st.eolIsSignificant(false);
+ st.lowerCaseMode(false);
+ st.slashSlashComments(false);
+ st.slashStarComments(false);
+ st.wordChars(0x00, 0xff);
+ st.quoteChar('\'');
+ st.quoteChar('"');
+ st.whitespaceChars(0, ' ');
+ st.whitespaceChars(',', ',');
+ List list = new ArrayList();
+ int type = 0;
+
+ // Parse and validate each included pattern
+ while (true) {
+
+ // Parse the next pattern
+ try {
+ type = st.nextToken();
+ } catch (IOException e) {
+ ; // Can not happen
+ }
+ if (type == StreamTokenizer.TT_EOF) {
+ break;
+ } else if (type == StreamTokenizer.TT_NUMBER) {
+ pattern = "" + st.nval;
+ } else if (type == StreamTokenizer.TT_WORD) {
+ pattern = st.sval.trim();
+ } else {
+ throw new IllegalArgumentException(expr);
+ }
+
+ // Validate this pattern
+ if (pattern.length() < 1) {
+ throw new IllegalArgumentException(pattern);
+ }
+ if (pattern.startsWith("/")) {
+ if (!pattern.endsWith("/*")) {
+ throw new IllegalArgumentException(pattern);
+ }
+ } else if (pattern.startsWith("*.")) {
+ if (pattern.substring(2).indexOf('.') > 0) {
+ throw new IllegalArgumentException(pattern);
+ }
+ } else {
+ throw new IllegalArgumentException(pattern);
+ }
+
+ // Add this pattern to our list
+ list.add(pattern);
+
+ }
+
+ // Return the precompiled patterns as an array
+ return (String[]) list.toArray(new String[list.size()]);
+
+ }
+
+
+}
Propchange:
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/FilteringProcessor.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/FilteringProcessor.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Modified:
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/MethodBindingProcessor.java
URL:
http://svn.apache.org/viewvc/shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/MethodBindingProcessor.java?view=diff&rev=481099&r1=481098&r2=481099
==============================================================================
---
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/MethodBindingProcessor.java
(original)
+++
shale/framework/trunk/shale-remoting/src/main/java/org/apache/shale/remoting/impl/MethodBindingProcessor.java
Thu Nov 30 14:55:59 2006
@@ -20,8 +20,11 @@
import java.io.IOException;
import javax.faces.context.FacesContext;
import javax.faces.el.MethodBinding;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.apache.shale.remoting.impl.FilteringProcessor;
import org.apache.shale.remoting.Processor;
/**
@@ -31,7 +34,7 @@
* resource identifier gets mapped are encapsulated in the
<code>mapResourceId</code>
* method, which may be specialized as desired in a subclass.</p>
*/
-public class MethodBindingProcessor implements Processor {
+public class MethodBindingProcessor extends FilteringProcessor {
// ------------------------------------------------------------
Constructors
@@ -68,6 +71,19 @@
*/
public void process(FacesContext context, String resourceId) throws
IOException {
+ // Filter based on our includes and excludes patterns
+ if (!accept(resourceId)) {
+ if (log().isTraceEnabled()) {
+ log().trace("Resource id '" + resourceId
+ + "' rejected by include/exclude rules");
+ }
+ // Send an HTTP "not found" response to avoid giving the client
+ // any information about a resource that exists and was refused,
+ // versus a resource that does not exist
+ sendNotFound(context, resourceId);
+ context.responseComplete();
+ }
+
// Create and execute a method binding based on this resource
identifier
MethodBinding mb = mapResourceId(context, resourceId);
if (log().isDebugEnabled()) {
@@ -137,6 +153,46 @@
log = LogFactory.getLog(MethodBindingProcessor.class);
}
return log;
+
+ }
+
+
+ /**
+ * <p>Send a "not found" HTTP response, if possible. Otherwise, throw an
+ * <code>IllegalArgumentException</code> that will ripple out.</p>
+ *
+ * @param context <code>FacesContext</code> for the current request
+ * @param resourceId Resource identifier of the resource that was not found
+ *
+ * @exception IllegalArgumentException if we cannot send an HTTP response
+ * @exception IOException if an input/output error occurs
+ *
+ * @since 1.0.4
+ */
+ private void sendNotFound(FacesContext context, String resourceId) throws
IOException {
+
+ if (servletRequest(context)) {
+ HttpServletResponse response = (HttpServletResponse)
+ context.getExternalContext().getResponse();
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, resourceId);
+ } else {
+ throw new IllegalArgumentException(resourceId);
+ }
+
+ }
+
+
+ /**
+ * <p>Return <code>true</code> if we are processing a servlet request (as
+ * opposed to a portlet request).</p>
+ *
+ * @param context <code>FacesContext</code> for the current request
+ *
+ * @since 1.0.4
+ */
+ private boolean servletRequest(FacesContext context) {
+
+ return context.getExternalContext().getContext() instanceof
ServletContext;
}
Added:
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/faces/MappingsHelperTestCase.java
URL:
http://svn.apache.org/viewvc/shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/faces/MappingsHelperTestCase.java?view=auto&rev=481099
==============================================================================
---
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/faces/MappingsHelperTestCase.java
(added)
+++
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/faces/MappingsHelperTestCase.java
Thu Nov 30 14:55:59 2006
@@ -0,0 +1,219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shale.remoting.faces;
+
+import javax.faces.application.ViewHandler;
+import javax.faces.component.UIViewRoot;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.shale.remoting.Constants;
+import org.apache.shale.remoting.Mapping;
+import org.apache.shale.remoting.Mappings;
+import org.apache.shale.remoting.Mechanism;
+import org.apache.shale.remoting.Processor;
+import org.apache.shale.remoting.impl.ClassResourceProcessor;
+import org.apache.shale.remoting.impl.FilteringProcessor;
+import org.apache.shale.remoting.impl.MappingImpl;
+import org.apache.shale.remoting.impl.MappingsImpl;
+import org.apache.shale.remoting.impl.MethodBindingProcessor;
+import org.apache.shale.remoting.impl.WebResourceProcessor;
+import org.apache.shale.test.base.AbstractJsfTestCase;
+
+/**
+ * <p>Test case for <code>org.apache.shale.remoting.MappingsHelper</code>.
+ * These tests focus on correct configuration, not on executable
+ * functionality.</p>
+ */
+public class MappingsHelperTestCase extends AbstractJsfTestCase {
+
+
+ // ------------------------------------------------------------
Constructors
+
+
+ // Construct a new instance of this test case.
+ public MappingsHelperTestCase(String name) {
+ super(name);
+ }
+
+
+ // ----------------------------------------------------------- Setup
Methods
+
+
+ // Set up instance variables for this test case.
+ protected void setUp() throws Exception {
+
+ super.setUp();
+ helper = new MappingsHelper();
+ // mappings instance set after configuration, not here
+
+ }
+
+
+ // Return the tests included in this test case.
+ public static Test suite() {
+
+ return (new TestSuite(MappingsHelperTestCase.class));
+
+ }
+
+
+ // Tear down instance variables for this test case.
+ protected void tearDown() throws Exception {
+
+ mappings = null;
+ helper = null;
+ super.tearDown();
+
+ }
+
+
+ // ------------------------------------------------------ Instance
Variables
+
+
+ // The instance to be tested
+ private MappingsHelper helper = null;
+
+
+ // An individual Mapping instance to be validated
+ private Mapping mapping = null;
+
+
+ // The mappings instance retrieved after configuration
+ private Mappings mappings = null;
+
+
+ // An individual Processor instance to be validated
+ private Processor processor = null;
+
+
+ // ------------------------------------------------------------ Test
Methods
+
+
+ // Test an invalid exclude pattern
+ public void testInvalidExclude() {
+
+ servletContext.addInitParameter(Constants.CLASS_RESOURCES_EXCLUDES,
+ "*.class,/foo");
+ try {
+ mappings = helper.getMappings(facesContext);
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ ; // Expected result
+ }
+
+ }
+
+
+ // Test an invalid include pattern
+ public void testInvalidInclude() {
+
+ servletContext.addInitParameter(Constants.DYNAMIC_RESOURCES_INCLUDES,
+ "/bar/*,*.x.y");
+ try {
+ mappings = helper.getMappings(facesContext);
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ ; // Expected result
+ }
+
+ }
+
+
+ // Test an invalid mechanism pattern
+ public void testInvalidMechanism() {
+
+ servletContext.addInitParameter(Constants.WEBAPP_RESOURCES_PARAM,
+
"*/*:org.apache.shale.remoting.impl.MethodBindingProcessor");
+ try {
+ mappings = helper.getMappings(facesContext);
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ ; // Expected result
+ }
+
+ }
+
+
+ // Test a pristine instance with default configuration
+ public void testPristine() {
+
+ assertNotNull(helper);
+
+ // Acquire the configured mappings instance
+ mappings = helper.getMappings(facesContext);
+ assertNotNull(mappings);
+
+ // Validate the characteristics of the Mappings instance itself
+ assertTrue(mappings instanceof MappingsImpl);
+ assertEquals(ViewHandler.DEFAULT_SUFFIX, mappings.getExtension());
+ assertEquals(3, mappings.getMappings().size());
+ assertEquals(0, mappings.getPatterns().length);
+
+ // Validate the "/static/*" Mapping instance for valid configuration
+ mapping = mappings.getMapping("/static/*");
+ assertNotNull(mapping);
+ assertTrue(mapping instanceof MappingImpl);
+ assertTrue(mappings == mapping.getMappings());
+ assertEquals(Mechanism.CLASS_RESOURCE, mapping.getMechanism());
+ assertEquals("/static/*", mapping.getPattern());
+ processor = mapping.getProcessor();
+ assertNotNull(processor);
+ assertTrue(processor instanceof ClassResourceProcessor);
+ assertEquals(Constants.CLASS_RESOURCES_EXCLUDES_DEFAULT,
+ ((FilteringProcessor) processor).getExcludes());
+ assertEquals(Constants.CLASS_RESOURCES_INCLUDES_DEFAULT,
+ ((FilteringProcessor) processor).getIncludes());
+
+ // Validate the "/dynamic/*" Mapping instance for valid configuration
+ mapping = mappings.getMapping("/dynamic/*");
+ assertNotNull(mapping);
+ assertTrue(mapping instanceof MappingImpl);
+ assertTrue(mappings == mapping.getMappings());
+ assertEquals(Mechanism.DYNAMIC_RESOURCE, mapping.getMechanism());
+ assertEquals("/dynamic/*", mapping.getPattern());
+ processor = mapping.getProcessor();
+ assertNotNull(processor);
+ assertTrue(processor instanceof MethodBindingProcessor);
+ assertEquals(Constants.DYNAMIC_RESOURCES_EXCLUDES_DEFAULT,
+ ((FilteringProcessor) processor).getExcludes());
+ assertEquals(Constants.DYNAMIC_RESOURCES_INCLUDES_DEFAULT,
+ ((FilteringProcessor) processor).getIncludes());
+
+ // Validate the "/webapp/*" Mapping instance for valid configuration
+ mapping = mappings.getMapping("/webapp/*");
+ assertNotNull(mapping);
+ assertTrue(mapping instanceof MappingImpl);
+ assertTrue(mappings == mapping.getMappings());
+ assertEquals(Mechanism.WEBAPP_RESOURCE, mapping.getMechanism());
+ assertEquals("/webapp/*", mapping.getPattern());
+ processor = mapping.getProcessor();
+ assertNotNull(processor);
+ assertTrue(processor instanceof WebResourceProcessor);
+ assertEquals(Constants.WEBAPP_RESOURCES_EXCLUDES_DEFAULT,
+ ((FilteringProcessor) processor).getExcludes());
+ assertEquals(Constants.WEBAPP_RESOURCES_INCLUDES_DEFAULT,
+ ((FilteringProcessor) processor).getIncludes());
+
+ }
+
+
+ // --------------------------------------------------------- Support
Methods
+
+
+
+}
Propchange:
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/faces/MappingsHelperTestCase.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/faces/MappingsHelperTestCase.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Modified:
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/impl/ClassResourceProcessorTestCase.java
URL:
http://svn.apache.org/viewvc/shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/impl/ClassResourceProcessorTestCase.java?view=diff&rev=481099&r1=481098&r2=481099
==============================================================================
---
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/impl/ClassResourceProcessorTestCase.java
(original)
+++
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/impl/ClassResourceProcessorTestCase.java
Thu Nov 30 14:55:59 2006
@@ -21,6 +21,7 @@
import junit.framework.Test;
import junit.framework.TestSuite;
+import org.apache.shale.remoting.Constants;
import org.apache.shale.test.base.AbstractJsfTestCase;
import org.apache.shale.test.mock.MockServletOutputStream;
@@ -46,6 +47,9 @@
private static final String INVALID_RESOURCE_ID =
"/org/apache/shale/remoting/impl/MissingData.text";
+ private static final String SENSITIVE_RESOURCE_ID =
+ "/org/apache/shale/remoting/Bundle.properties";
+
private static final String VALID_RESOURCE_ID =
"/org/apache/shale/remoting/impl/TestData.text";
@@ -63,6 +67,8 @@
super.setUp();
servletContext.addMimeType("text", "text/x-plain");
processor = new ClassResourceProcessor();
+ processor.setExcludes(Constants.CLASS_RESOURCES_EXCLUDES_DEFAULT);
+ processor.setIncludes(Constants.CLASS_RESOURCES_INCLUDES_DEFAULT);
}
@@ -114,6 +120,48 @@
assertNotNull(processor.getResourceURL(facesContext,
VALID_RESOURCE_ID));
assertNull(processor.getResourceURL(facesContext,
INVALID_RESOURCE_ID));
+
+ }
+
+
+ // Test attempt to execute an expression for an excluded pattern
+ public void testPatternExcluded() throws Exception {
+
+ processor.setExcludes("*.text");
+ processor.process(facesContext, VALID_RESOURCE_ID);
+ assertEquals(404, response.getStatus());
+
+ }
+
+ // Test attempt to execute an expression for an included pattern
+ public void testPatternIncluded() throws Exception {
+
+ processor.setExcludes(null);
+ processor.setIncludes("*.properties");
+ processor.process(facesContext, SENSITIVE_RESOURCE_ID);
+ assertEquals(200, response.getStatus());
+
+ }
+
+ // Test attempt to execute an expression for a mixed exclude/include case
+ public void testPatternMixed() throws Exception {
+
+ processor.setExcludes("*.properties");
+ processor.setIncludes("*.text");
+ processor.process(facesContext, VALID_RESOURCE_ID);
+ assertEquals(200, response.getStatus());
+
+ }
+
+
+ // Test attempt to access an existing sensitive resource that should be
+ // blocked by the default configuration
+ public void testPatternSensitive() throws Exception {
+
+ assertEquals(Constants.CLASS_RESOURCES_EXCLUDES_DEFAULT,
processor.getExcludes());
+ assertEquals(Constants.CLASS_RESOURCES_INCLUDES_DEFAULT,
processor.getIncludes());
+ processor.process(facesContext, SENSITIVE_RESOURCE_ID);
+ assertEquals(404, response.getStatus());
}
Modified:
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/impl/MethodBindingProcessorTestCase.java
URL:
http://svn.apache.org/viewvc/shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/impl/MethodBindingProcessorTestCase.java?view=diff&rev=481099&r1=481098&r2=481099
==============================================================================
---
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/impl/MethodBindingProcessorTestCase.java
(original)
+++
shale/framework/trunk/shale-remoting/src/test/java/org/apache/shale/remoting/impl/MethodBindingProcessorTestCase.java
Thu Nov 30 14:55:59 2006
@@ -21,6 +21,7 @@
import javax.faces.el.MethodNotFoundException;
import junit.framework.Test;
import junit.framework.TestSuite;
+import org.apache.shale.remoting.Constants;
import org.apache.shale.test.base.AbstractJsfTestCase;
import org.apache.shale.test.mock.MockPrintWriter;
import org.apache.shale.test.mock.MockServletOutputStream;
@@ -48,6 +49,8 @@
super.setUp();
processor = new MethodBindingProcessor();
+ processor.setExcludes(Constants.DYNAMIC_RESOURCES_EXCLUDES_DEFAULT);
+ processor.setIncludes(Constants.DYNAMIC_RESOURCES_INCLUDES_DEFAULT);
servletContext.setAttribute("business", new
MethodBindingProcessorBusinessObject());
}
@@ -118,6 +121,37 @@
processor.mapResourceId(facesContext,
"/business/indirectStream").getExpressionString());
assertEquals("#{business.indirectWriter}",
processor.mapResourceId(facesContext,
"/business/indirectWriter").getExpressionString());
+
+ }
+
+
+ // Test attempt to execute an expression for an excluded pattern
+ public void testPatternExcluded() throws Exception {
+
+ processor.setExcludes("/business/*");
+ processor.process(facesContext, "/business/directWriter");
+ assertEquals(404, response.getStatus());
+
+ }
+
+
+ // Test attempt to execute an expression for an included pattern
+ public void testPatternIncluded() throws Exception {
+
+ processor.setIncludes("/business/*");
+ processor.process(facesContext, "/business/directWriter");
+ assertEquals(200, response.getStatus());
+
+ }
+
+
+ // Test attempt to execute an expression for a mixed exclude/include case
+ public void testPatternMixed() throws Exception {
+
+ processor.setExcludes("/bar/*");
+ processor.setIncludes("/business/*");
+ processor.process(facesContext, "/business/directWriter");
+ assertEquals(200, response.getStatus());
}