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());
 
     }
 


Reply via email to