First batch, based on sandbox. The main change is adding the ObjectManager to abstrac...
authorcostin <costin@13f79535-47bb-0310-9956-ffa450edef68>
Sat, 4 Apr 2009 16:24:34 +0000 (16:24 +0000)
committercostin <costin@13f79535-47bb-0310-9956-ffa450edef68>
Sat, 4 Apr 2009 16:24:34 +0000 (16:24 +0000)
and replace some filters with explicit interfaces, as was suggested. This has dependencies on coyote and
tomcat-util, and some optional servlets/filters/plugins to provide various features from the spec.

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@761964 13f79535-47bb-0310-9956-ffa450edef68

71 files changed:
modules/tomcat-lite/java/org/apache/tomcat/addons/UserAuthentication.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/addons/UserSessionManager.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/addons/UserTemplateClassMapper.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/integration/ObjectManager.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/integration/ObjectManagerSpi.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JmxObjectManagerSpi.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/integration/simple/SimpleObjectManager.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/CharsetMapperDefault.properties [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/Connector.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ContextPreinitListener.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/FilterChainImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/FilterConfigImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/Locale2Charset.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ParameterMap.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/RequestDispatcherImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ServletConfigImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ServletContextConfig.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ServletContextImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ServletInputStreamImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ServletOutputStreamImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ServletReaderImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ServletRequestImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ServletRequestWrapperImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ServletResponseImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ServletResponseIncludeWrapper.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/ServletWriterImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/TomcatLite.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/WebappContextMapper.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/WebappFilterMapper.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/WebappServletMapper.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/cli/Main.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/config.properties [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/ClientAbortException.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/CoyoteHttp.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/CoyoteServer.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/CoyoteUtils.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/MapperAdapter.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/MessageReader.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/MessageWriter.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/TomcatLiteWebXmlConfig.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/WebAnnotation.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/WebResourceCollectionData.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/WebXml.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/file/CopyUtils.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/file/DefaultServlet.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/file/Dir2Html.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/file/FileCopyUtils.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/file/MD5Encoder.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/file/URLEncoder.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/file/WebdavServlet.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/file/XMLWriter.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/JasperCompilerTemplateClassMapper.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/JspFileTemplateServlet.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/PreCompileFilter.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/SimpleTemplateClassMapper.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/SingleThreadedProxyServlet.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/WildcardTemplateServlet.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/AccessFilter.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/BasicAuthentication.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/FormAuthentication.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/IPFilter.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/SimpleUserAuthDB.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/UserDB.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/session/HttpSessionImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/session/RandomGenerator.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/session/SimpleSessionManager.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Enumerator.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/util/LocaleParser.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Range.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/util/RequestUtil.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/servlets/util/UrlUtils.java [new file with mode: 0644]

diff --git a/modules/tomcat-lite/java/org/apache/tomcat/addons/UserAuthentication.java b/modules/tomcat-lite/java/org/apache/tomcat/addons/UserAuthentication.java
new file mode 100644 (file)
index 0000000..d565563
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ */
+package org.apache.tomcat.addons;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Plugin for user auth.
+ * 
+ * This interface should support all common forms of auth, 
+ * including Basic, Digest, Form and various other auth 
+ * standards - the plugin has full control over request and
+ * response.  
+ * 
+ * Container will verify the security constraints on URLs and 
+ * call this for all URLs that have constraints. The plugin can
+ * either authenticate and return the principal, or change 
+ * the response - redirect, add headers, send content. 
+ * 
+ * Alternative: a simple Filter can do the same, with some conventions
+ * to support it ( attributes ).
+ * 
+ * @author Costin Manolache
+ */
+public interface UserAuthentication {
+
+    /**
+     * If req has all the info - return the principal.
+     * Otherwise set the challenge in response.
+     * 
+     * @param requestedMethod auth method from web.xml. Spec
+     *  complain plugins must support it. 
+     * @throws IOException 
+     */
+    public Principal authenticate(HttpServletRequest req, 
+                                  HttpServletResponse res, 
+                                  String requestedMethod) throws IOException;
+    
+    
+    public boolean isUserInRole(HttpServletRequest req,
+                                Principal p,
+                                String role);
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/addons/UserSessionManager.java b/modules/tomcat-lite/java/org/apache/tomcat/addons/UserSessionManager.java
new file mode 100644 (file)
index 0000000..5e450e7
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ */
+package org.apache.tomcat.addons;
+
+import java.io.IOException;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+
+/**
+ * Session management plugin. No dependency on tomcat-lite, should
+ * be possible to add this to tomcat-trunk or other containers.
+ * 
+ * The container will:
+ * - extract the session id from request ( via a filter or built-ins )
+ * - call this interface when the user makes the related calls in the 
+ * servlet API.
+ * - provide a context attribute 'context-listeners' with the 
+ * List<EventListener> from web.xml 
+ *
+ * Implementation of this class must provide HttpSession object 
+ * and implement the spec. 
+ * 
+ */
+public interface UserSessionManager {
+
+    
+    
+    HttpSession findSession(String requestedSessionId) throws IOException;
+
+    HttpSession createSession(String requestedSessionId);
+  
+    boolean isValid(HttpSession session);
+
+    void access(HttpSession session);
+  
+    void endAccess(HttpSession session);
+  
+  
+    void setSessionTimeout(int to);
+    
+    void setContext(ServletContext ctx);
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/addons/UserTemplateClassMapper.java b/modules/tomcat-lite/java/org/apache/tomcat/addons/UserTemplateClassMapper.java
new file mode 100644 (file)
index 0000000..9c696a2
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ */
+package org.apache.tomcat.addons;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+/** 
+ * Mapps a templating name ( like a jsp file ) to a class name 
+ * generated by the template compiler.
+ * 
+ * This class is needed at runtime to support *.jsp mappings
+ * or <jsp-file> - can be used to support other formats. 
+ * 
+ * The UserTemplateCompiler is only needed to support run-time
+ * compilation - if ahead-of-time compilation is used 
+ * all generated files can be mapped explicitly with  
+ * <servlet><class-name>.
+ * 
+ * @author Costin Manolache
+ */
+public interface UserTemplateClassMapper {
+
+    /**
+     * Generate and load the proxy corresponding to the template file.
+     *  
+     */
+    public Servlet loadProxy(String jspFile, 
+                             ServletContext ctx, 
+                             ServletConfig config) throws ServletException;
+
+    /**
+     * Quick check if the template ( or deps ) has been modified
+     * and the servlet needs to be regenerated;  
+     */
+    public boolean needsReload(String jspFile, Servlet s);
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/integration/ObjectManager.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/ObjectManager.java
new file mode 100644 (file)
index 0000000..88d4c6a
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ */
+package org.apache.tomcat.integration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tomcat is using JMX heavily for monitoring and config - but other 
+ * apps embedding tomcat may have different preferences. There
+ * is interest to use and better integrate with dependency injection
+ * frameworks, OSGI. 
+ * 
+ * Tomcat will make call to this class when it creates contexts,
+ * servlets, connectors - giving a chance to DI frameworks to inject,
+ * and to JMX to expose the objects. 
+ * 
+ * Tomcat will also call this class when it needs a plugin, allowing
+ * DI or frameworks to locate the dependency.
+ * 
+ * @author Costin Manolache
+ */
+public class ObjectManager {
+    
+    /** 
+     * Attribute used to keep a reference to the object manager 
+     * in the context, for the use of servlets. 
+     */
+    public static final String ATTRIBUTE = "ObjectManager";
+
+    /**
+     * Register a named object with the framework. 
+     * 
+     * For example JMX will expose the object as an MBean.
+     * 
+     * The framework may inject properties - if it supports that.
+     */
+    public void bind(String name, Object o) {
+        for (ObjectManagerSpi p : providers) {
+            p.bind(name, o);
+        }
+    }
+
+    /** 
+     * When an object is no longer in use.
+     */
+    public void unbind(String name) {
+        for (ObjectManagerSpi p : providers) {
+            p.unbind(name);
+        }        
+    }
+
+    /**
+     * Create or get a new object with the given name.
+     */
+    public Object get(String key) {
+        for (ObjectManagerSpi p : providers) {
+            Object o = p.get(key);
+            if (o != null) {
+                return o;
+            }
+        }        
+        return null;
+    }
+
+    /**
+     * Helper for typed get.
+     */
+    public Object get(Class c) {
+        return get(c.getName());
+    }
+
+    /**
+     * ObjectManager delegates to providers. You can have multiple
+     * providers - for example JMX, DI and OSGI at the same time.
+     */
+    protected List<ObjectManagerSpi> providers = 
+        new ArrayList<ObjectManagerSpi>(); 
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/integration/ObjectManagerSpi.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/ObjectManagerSpi.java
new file mode 100644 (file)
index 0000000..3619087
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ */
+package org.apache.tomcat.integration;
+
+/**
+ * Base class for framework-integration plugins.
+ */
+public abstract class ObjectManagerSpi {
+    public abstract void bind(String name, Object o);
+    
+    public abstract void unbind(String name);
+
+    public abstract Object get(String key);
+
+    public void register(ObjectManager om) {
+        om.providers.add(this);
+    }
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JmxObjectManagerSpi.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/jmx/JmxObjectManagerSpi.java
new file mode 100644 (file)
index 0000000..96b1347
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ */
+package org.apache.tomcat.integration.jmx;
+
+import java.util.logging.Logger;
+
+import org.apache.tomcat.integration.ObjectManagerSpi;
+import org.apache.tomcat.util.modeler.Registry;
+
+/**
+ * Plugin for integration with JMX.
+ * 
+ * All objects of interest are registered automatically.
+ */
+public class JmxObjectManagerSpi extends ObjectManagerSpi {
+    Registry registry;
+    Logger log = Logger.getLogger("JmxObjectManager");
+    
+    public JmxObjectManagerSpi() {
+        registry = Registry.getRegistry(null, null);
+    }
+    
+    public void bind(String name, Object o) {
+        try {
+            registry.registerComponent(o, 
+                    ":name=\"" + name + "\"", null);
+        } catch (Exception e) {
+            log.severe("Error registering" + e);
+        }
+    }
+
+    public void unbind(String name) {
+        registry.unregisterComponent(":name=\"" + name + "\"");
+    }
+
+    @Override
+    public Object get(String key) {
+        return null;
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/SimpleObjectManager.java b/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/SimpleObjectManager.java
new file mode 100644 (file)
index 0000000..5b33983
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ */
+package org.apache.tomcat.integration.simple;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.integration.ObjectManager;
+import org.apache.tomcat.integration.ObjectManagerSpi;
+import org.apache.tomcat.util.IntrospectionUtils;
+
+/**
+ * This is a very small 'dependency injection'/registry poor-man substitute, 
+ * based on old tomcat IntrospectionUtils ( which is based on ant ).
+ * Alternative would be to just pick one of spring/guice/etc and use it.
+ * This class is a bit smaller and should be enough for simple use.
+ * 
+ * How it works: 
+ *  - when bound, simple properties are injected in the objects using
+ *  the old IntrospectionUtils, same as in original Tomcat server.xml
+ *   
+ *  - object creation using class name - properties injected as well.
+ *  Similar with how server.xml or ant works.
+ *  
+ *  - it is based on a big Properties file, with command line arguments 
+ *  merged in.
+ *  
+ * Tomcat doesn't require any of the features - they are just used to
+ * allow configuration in 'default' mode, when no other framework is 
+ * used.  
+ * 
+ * See the Spring example for an alternative. I believe most POJO frameworks
+ * can be supported. 
+ * 
+ * @author Costin Manolache
+ */
+public class SimpleObjectManager extends ObjectManagerSpi {
+
+    static Logger log = Logger.getLogger(SimpleObjectManager.class.getName());
+    
+    /** 
+     * Saved CLI arguments. Will be added to the properties.
+     */
+    public static String[] args;
+    
+    protected Properties props = new Properties();
+    protected Map<String, Object> objects = new HashMap();
+    ObjectManager om;
+    
+    public SimpleObjectManager() {
+    }
+
+    public SimpleObjectManager(ObjectManager om) {
+        register(om);
+    }
+
+    public void register(ObjectManager om) {
+        this.om = om;
+        if (args != null) {
+            try {
+                processArgs(args, props);
+            } catch (IOException e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+        super.register(om);
+    }
+    
+    public ObjectManager getObjectManager() {
+        return om;
+    }
+
+    public void load(InputStream is) {
+        try {
+            props.load(is);
+        } catch (IOException e) {
+            throw new RuntimeException("Error loading default config");
+        }
+    }
+    
+    public void loadResource(String res) {
+        InputStream in = this.getClass().getClassLoader()
+            .getResourceAsStream(res);
+        load(in);
+    }
+    
+    public Properties getProperties() {
+        return props;
+    }
+    
+    @Override
+    public void unbind(String name) {
+    }
+
+    @Override
+    public void bind(String name, Object o) {
+        log.info("Bound: " + name + " " + o);
+
+        // TODO: can I make 'inject' public - Guice seems to 
+        // support this.
+        inject(name, o);
+    }
+
+    @Override
+    public Object get(String key) {
+        // Use same syntax as Spring props.
+        String prop = props.getProperty(key + ".(class)");
+        if (prop != null) {
+            Object res = loadClass(prop);
+            inject(key, res);
+            return res;
+        }
+
+        return null;
+    }
+
+    private void inject(String name, Object o) {
+        // Simple injection of primitive types
+        String pref = name + ".";
+        int prefLen = pref.length();
+
+        for (String k: props.stringPropertyNames()) {
+            if (k.startsWith(pref)) {
+                if (k.endsWith(")")) {
+                    continue; // special 
+                }
+                String value = props.getProperty(k);
+                value = IntrospectionUtils.replaceProperties(value, 
+                        props, null);
+                String p = k.substring(prefLen);
+                int idx = p.indexOf(".");
+                if (idx > 0) {
+                    // ignore suffix - indexed properties
+                    p = p.substring(0, idx);
+                }
+                IntrospectionUtils.setProperty(o, p, value);
+                log.info("Setting: " + name + " " + k + " " + value);
+            }
+        }
+        // We could do cooler things - inject objects, etc.
+    }
+
+    private Object loadClass(String className) {
+        try {
+            Class c = Class.forName(className);
+            Object ext = c.newInstance();
+            return ext;
+        } catch (Throwable e) {
+            e.printStackTrace();
+            return null;
+        }        
+    }
+    
+    /**
+     * Populate properties based on CLI:
+     *  -key value
+     *  --key=value
+     *  
+     *  --config=FILE - load a properties file
+     *  
+     * @param args
+     * @param p
+     * @param meta
+     * @return everything after the first non arg not starting with '-'
+     * @throws IOException 
+     */
+    public static String[] processArgs(String[] args, Properties props) 
+            throws IOException {
+
+        for (int i = 0; i < args.length; i++) {
+            String arg = args[i];
+            if (arg.startsWith("--")) {
+                arg = arg.substring(2);
+            } else if (arg.startsWith("-")) {
+                arg = arg.substring(1);
+            } else {
+                String [] res = new String[args.length - i];
+                System.arraycopy(args, i, res, 0, res.length);
+                return res;
+            }
+            
+            String name = arg; 
+            int eq = arg.indexOf("=");
+            String value = null;
+            if (eq > 0) {
+                name = arg.substring(0, eq);
+                value = arg.substring(eq + 1);
+            } else {
+                i++;
+                if (i >= args.length) {
+                    throw new RuntimeException("Missing param " + arg);
+                }
+                value = args[i];
+            }
+
+            if ("config".equals(arg)) {
+                props.load(new FileInputStream(value));
+            } else {
+                props.put(name, value);
+            }
+        }
+        return new String[] {};
+    }
+
+    public static void setArgs(String[] argv) {
+        SimpleObjectManager.args = argv;
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/CharsetMapperDefault.properties b/modules/tomcat-lite/java/org/apache/tomcat/lite/CharsetMapperDefault.properties
new file mode 100644 (file)
index 0000000..c73a8fc
--- /dev/null
@@ -0,0 +1,2 @@
+en=ISO-8859-1
+fr=ISO-8859-1
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/Connector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/Connector.java
new file mode 100644 (file)
index 0000000..d104b68
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ */
+package org.apache.tomcat.lite;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tomcat.integration.ObjectManager;
+
+/**
+ * What we need to plugin a connector.
+ * 
+ * Currently we have lots of deps on coyote Request, but I plan to
+ * change this and allow other HTTP implementations - like MINA, the
+ * experimental async connector, etc. Most important will be 
+ * different support for IO - i.e. a more non-blocking mode.
+ * We'll probably keep MessageBytes as wrappers for request/res
+ * properties. 
+ * 
+ * This interface has no dep on coyote.
+ *  
+ */
+public interface Connector {
+
+    public void setDaemon(boolean b);
+    
+    public void start();
+    
+    public void stop();
+    
+    public void finishResponse(HttpServletResponse res) throws IOException;
+    
+    public void recycle(HttpServletRequest req, HttpServletResponse res);
+
+    void initRequest(HttpServletRequest req, HttpServletResponse res);
+
+    public void setTomcatLite(TomcatLite tomcatLite);
+
+    public void setObjectManager(ObjectManager objectManager);
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ContextPreinitListener.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ContextPreinitListener.java
new file mode 100644 (file)
index 0000000..c1ddaba
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ */
+package org.apache.tomcat.lite;
+
+import javax.servlet.ServletContext;
+
+/**
+ * Tomcat-lite specific interface ( could be moved to addons ).
+ * This class will be called before initialization - implementations
+ * can add servlets, filters, etc. In particular web.xml parsing
+ * is done implementing this interface. 
+ * 
+ * On a small server you could remove web.xml support to reduce 
+ * footprint, and either hardcode this class or use properties.
+ * Same if you already use a framework and you inject settings
+ * or use framework's registry (OSGI).
+ * 
+ * @author Costin Manolache
+ */
+public interface ContextPreinitListener {
+
+    public void preInit(ServletContext ctx);
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/FilterChainImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/FilterChainImpl.java
new file mode 100644 (file)
index 0000000..7d3569b
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2009 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Wraps the list of filters for the current request. One instance 
+ * associated with each RequestImpl, reused. 
+ * 
+ * Populated by the mapper ( WebappFilterMapper for example ), which
+ * determines the filters for the current request.
+ * 
+ * Not thread safe.
+ */
+public final class FilterChainImpl implements FilterChain {
+    private List<FilterConfigImpl> filters =  new ArrayList<FilterConfigImpl>();
+
+
+    /**
+     * The int which is used to maintain the current position 
+     * in the filter chain.
+     */
+    private int pos = 0;
+
+    /**
+     * The servlet instance to be executed by this chain.
+     */
+    private Servlet servlet = null;
+
+
+    private ServletConfigImpl wrapper;
+
+
+    public FilterChainImpl() {
+        super();
+    }
+
+
+    /**
+     * Invoke the next filter in this chain, passing the specified request
+     * and response.  If there are no more filters in this chain, invoke
+     * the <code>service()</code> method of the servlet itself.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet exception occurs
+     */
+    public void doFilter(ServletRequest request, ServletResponse response)
+        throws IOException, ServletException {
+
+
+        // Call the next filter if there is one
+        if (pos < filters.size()) {
+            FilterConfigImpl filterConfig = filters.get(pos++);
+            Filter filter = null;
+            try {
+                filter = filterConfig.getFilter();
+                filter.doFilter(request, response, this);
+            } catch (IOException e) {
+                throw e;
+            } catch (ServletException e) {
+                throw e;
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Throwable e) {
+                e.printStackTrace();
+                throw new ServletException("Throwable", e);
+            }
+            return;
+        }
+
+        // We fell off the end of the chain -- call the servlet instance
+        try {
+            if (servlet != null) 
+                servlet.service(request, response);
+        } catch (IOException e) {
+            throw e;
+        } catch (ServletException e) {
+            throw e;
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new ServletException("Throwable", e);
+        }
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+
+    /**
+     * Add a filter to the set of filters that will be executed in this chain.
+     *
+     * @param filterConfig The FilterConfig for the servlet to be executed
+     */
+    public void addFilter(FilterConfigImpl filterConfig) {
+        filters.add(filterConfig);
+    }
+
+
+    /**
+     * Release references to the filters and wrapper executed by this chain.
+     */
+    public void release() {
+        filters.clear();
+        pos = 0;
+        servlet = null;
+    }
+
+
+    /**
+     * Set the servlet that will be executed at the end of this chain.
+     * Set by the mapper filter 
+     */
+    public void setServlet(ServletConfigImpl wrapper, Servlet servlet) {
+        this.wrapper = wrapper;
+        this.servlet = servlet;
+    }
+
+    // ------ Getters for information ------------ 
+    
+    public int getSize() {
+        return filters.size();
+    }
+    
+    public FilterConfigImpl getFilter(int i) {
+        return filters.get(i);
+    }
+    
+    public Servlet getServlet() {
+        return servlet;
+    }
+    
+    public ServletConfigImpl getServletConfig() {
+        return wrapper;
+    }
+    
+    public int getPos() {
+        return pos;
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/FilterConfigImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/FilterConfigImpl.java
new file mode 100644 (file)
index 0000000..80f1e3f
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.apache.tomcat.servlets.util.Enumerator;
+
+
+/** 
+ * A Filter is configured in web.xml by:
+ *  - name - used in mappings
+ *  - className - used to instantiate the filter
+ *  - init params
+ *  - other things not used in the servlet container ( icon, descr, etc )
+ *  
+ * Alternatively, in API mode you can pass the actual filter.
+ * 
+ * @see ServletConfigImpl
+ */
+public final class FilterConfigImpl implements FilterConfig {
+
+    public FilterConfigImpl(ServletContextImpl context) {
+        this.context = context;
+    }
+    
+    private ServletContextImpl context = null;
+
+    /**
+     * The application Filter we are configured for.
+     */
+    private transient Filter filter = null;
+
+    private String filterName;
+
+    private String filterClass;
+
+    private Map<String, String> initParams;
+
+    public void setData(String filterName, String filterClass,
+                        Map<String, String> params) {
+        this.filterName = filterName;
+        this.filterClass = filterClass;
+        this.initParams = params;
+    }
+    
+    public void setFilter(Filter f) {
+        filter = f;
+    }
+    
+    public String getFilterName() {
+        return filterName;
+    }
+
+    public String getInitParameter(String name) {
+        if (initParams == null) return null;
+        return initParams.get(name);
+    }
+
+    /**
+     * Return an <code>Enumeration</code> of the names of the initialization
+     * parameters for this Filter.
+     */
+    public Enumeration getInitParameterNames() {
+        if (initParams == null)
+            return (new Enumerator(new ArrayList()));
+        else
+            return (new Enumerator(initParams.keySet()));
+    }
+
+
+    /**
+     * Return the ServletContext of our associated web application.
+     */
+    public ServletContext getServletContext() {
+        return context;
+    }
+
+    /**
+     * Return the application Filter we are configured for.
+     */
+    public Filter getFilter() throws ClassCastException, ClassNotFoundException,
+        IllegalAccessException, InstantiationException, ServletException {
+
+        // Return the existing filter instance, if any
+        if (filter != null)
+            return filter;
+
+        ClassLoader classLoader = context.getClassLoader();
+
+        ClassLoader oldCtxClassLoader =
+            Thread.currentThread().getContextClassLoader();
+        if (classLoader != oldCtxClassLoader) {
+            Thread.currentThread().setContextClassLoader(classLoader);
+        }
+        try {
+            Class clazz = classLoader.loadClass(filterClass);
+            this.filter = (Filter) clazz.newInstance();
+        } finally {        
+            if (classLoader != oldCtxClassLoader) {
+                Thread.currentThread().setContextClassLoader(oldCtxClassLoader);
+            }
+        }
+        
+        filter.init(this);
+        return (this.filter);
+    }
+
+
+    /**
+     * Release the Filter instance associated with this FilterConfig,
+     * if there is one.
+     */
+    public void release() {
+        if (this.filter != null){
+            filter.destroy();
+        }
+        this.filter = null;
+     }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/Locale2Charset.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/Locale2Charset.java
new file mode 100644 (file)
index 0000000..0a9d73a
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+
+import java.io.InputStream;
+import java.util.Locale;
+import java.util.Properties;
+
+
+
+/**
+ * One instance per Context. Holds the 
+ * 
+ * Utility class that attempts to map from a Locale to the corresponding
+ * character set to be used for interpreting input text (or generating
+ * output text) when the Content-Type header does not include one.  You
+ * can customize the behavior of this class by modifying the mapping data
+ * it loads, or by subclassing it (to change the algorithm) and then using
+ * your own version for a particular web application.
+ *
+ * @author Craig R. McClanahan
+ */
+public class Locale2Charset {
+
+
+    // ---------------------------------------------------- Manifest Constants
+
+
+    /**
+     * Default properties resource name.
+     */
+    public static final String DEFAULT_RESOURCE =
+      "/org/apache/tomcat/lite/CharsetMapperDefault.properties";
+
+    
+
+    // ---------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new CharsetMapper using the default properties resource.
+     */
+    public Locale2Charset() {
+      String name = DEFAULT_RESOURCE;
+      if (defaultMap == null) { // once !
+        try {
+          defaultMap = new Properties();
+          InputStream stream =
+            this.getClass().getResourceAsStream(name);
+          defaultMap.load(stream);
+          stream.close();
+        } catch (Throwable t) {
+          throw new IllegalArgumentException(t.toString());
+        }
+      }
+      map = defaultMap;
+    }
+
+
+    // ---------------------------------------------------- Instance Variables
+
+
+    private static Properties defaultMap; // shared for all apps
+    
+    /**
+     * The mapping properties that have been initialized from the specified or
+     * default properties resource.
+     */
+    private Properties map;
+
+
+    // ------------------------------------------------------- Public Methods
+
+
+    /**
+     * Calculate the name of a character set to be assumed, given the specified
+     * Locale and the absence of a character set specified as part of the
+     * content type header.
+     *
+     * @param locale The locale for which to calculate a character set
+     */
+    public String getCharset(Locale locale) {
+        // Match full language_country_variant first, then language_country, 
+        // then language only
+        String charset = map.getProperty(locale.toString());
+        if (charset == null) {
+            charset = map.getProperty(locale.getLanguage() + "_" 
+                    + locale.getCountry());
+            if (charset == null) {
+                charset = map.getProperty(locale.getLanguage());
+            }
+        }
+        return (charset);
+    }
+
+    
+    /**
+     * The deployment descriptor can have a
+     * locale-encoding-mapping-list element which describes the
+     * webapp's desired mapping from locale to charset.  This method
+     * gets called when processing the web.xml file for a context
+     *
+     * @param locale The locale for a character set
+     * @param charset The charset to be associated with the locale
+     */
+    public void addCharsetMapping(String locale, String charset) {
+      if (map == defaultMap) { 
+        // new copy, don't modify original
+        map = new Properties(defaultMap);
+      }
+      map.put(locale, charset);
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ParameterMap.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ParameterMap.java
new file mode 100644 (file)
index 0000000..aa64cbb
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Extended implementation of <strong>HashMap</strong> that includes a
+ * <code>locked</code> property.  This class can be used to safely expose
+ * Catalina internal parameter map objects to user classes without having
+ * to clone them in order to avoid modifications.  When first created, a
+ * <code>ParmaeterMap</code> instance is not locked.
+ *
+ * @author Craig R. McClanahan
+ * @version $Revision: 302726 $ $Date: 2004-02-27 06:59:07 -0800 (Fri, 27 Feb 2004) $
+ */
+
+public final class ParameterMap extends HashMap {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new, empty map with the default initial capacity and
+     * load factor.
+     */
+    public ParameterMap() {
+
+        super();
+
+    }
+
+
+    /**
+     * Construct a new, empty map with the specified initial capacity and
+     * default load factor.
+     *
+     * @param initialCapacity The initial capacity of this map
+     */
+    public ParameterMap(int initialCapacity) {
+
+        super(initialCapacity);
+
+    }
+
+
+    /**
+     * Construct a new, empty map with the specified initial capacity and
+     * load factor.
+     *
+     * @param initialCapacity The initial capacity of this map
+     * @param loadFactor The load factor of this map
+     */
+    public ParameterMap(int initialCapacity, float loadFactor) {
+
+        super(initialCapacity, loadFactor);
+
+    }
+
+
+    /**
+     * Construct a new map with the same mappings as the given map.
+     *
+     * @param map Map whose contents are dupliated in the new map
+     */
+    public ParameterMap(Map map) {
+
+        super(map);
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The current lock state of this parameter map.
+     */
+    private boolean locked = false;
+
+
+    /**
+     * Return the locked state of this parameter map.
+     */
+    public boolean isLocked() {
+
+        return (this.locked);
+
+    }
+
+
+    /**
+     * Set the locked state of this parameter map.
+     *
+     * @param locked The new locked state
+     */
+    public void setLocked(boolean locked) {
+
+        this.locked = locked;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+
+    /**
+     * Remove all mappings from this map.
+     *
+     * @exception IllegalStateException if this map is currently locked
+     */
+    public void clear() {
+
+        if (locked)
+            throw new IllegalStateException
+                ("parameterMap.locked");
+        super.clear();
+
+    }
+
+
+    /**
+     * Associate the specified value with the specified key in this map.  If
+     * the map previously contained a mapping for this key, the old value is
+     * replaced.
+     *
+     * @param key Key with which the specified value is to be associated
+     * @param value Value to be associated with the specified key
+     *
+     * @return The previous value associated with the specified key, or
+     *  <code>null</code> if there was no mapping for key
+     *
+     * @exception IllegalStateException if this map is currently locked
+     */
+    public Object put(Object key, Object value) {
+
+        if (locked)
+            throw new IllegalStateException
+                ("parameterMap.locked");
+        return (super.put(key, value));
+
+    }
+
+
+    /**
+     * Copy all of the mappings from the specified map to this one.  These
+     * mappings replace any mappings that this map had for any of the keys
+     * currently in the specified Map.
+     *
+     * @param map Mappings to be stored into this map
+     *
+     * @exception IllegalStateException if this map is currently locked
+     */
+    public void putAll(Map map) {
+
+        if (locked)
+            throw new IllegalStateException
+                ("parameterMap.locked");
+        super.putAll(map);
+
+    }
+
+
+    /**
+     * Remove the mapping for this key from the map if present.
+     *
+     * @param key Key whose mapping is to be removed from the map
+     *
+     * @return The previous value associated with the specified key, or
+     *  <code>null</code> if there was no mapping for that key
+     *
+     * @exception IllegalStateException if this map is currently locked
+     */
+    public Object remove(Object key) {
+
+        if (locked)
+            throw new IllegalStateException
+                ("parameterMap.locked");
+        return (super.remove(key));
+
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/RequestDispatcherImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/RequestDispatcherImpl.java
new file mode 100644 (file)
index 0000000..6ec2397
--- /dev/null
@@ -0,0 +1,853 @@
+/*
+ * 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.tomcat.lite;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.mapper.MappingData;
+
+/**
+ *
+ */
+public final class RequestDispatcherImpl implements RequestDispatcher {
+    /**
+     * The request attribute under which the original servlet path is stored
+     * on an forwarded dispatcher request.
+     */
+    public static final String FORWARD_SERVLET_PATH_ATTR =
+        "javax.servlet.forward.servlet_path";
+
+
+    /**
+     * The request attribute under which the original query string is stored
+     * on an forwarded dispatcher request.
+     */
+    public static final String FORWARD_QUERY_STRING_ATTR =
+        "javax.servlet.forward.query_string";
+
+    /**
+     * The request attribute under which the original request URI is stored
+     * on an forwarded dispatcher request.
+     */
+    public static final String FORWARD_REQUEST_URI_ATTR =
+        "javax.servlet.forward.request_uri";
+    
+    
+    /**
+     * The request attribute under which the original context path is stored
+     * on an forwarded dispatcher request.
+     */
+    public static final String FORWARD_CONTEXT_PATH_ATTR =
+        "javax.servlet.forward.context_path";
+
+
+    /**
+     * The request attribute under which the original path info is stored
+     * on an forwarded dispatcher request.
+     */
+    public static final String FORWARD_PATH_INFO_ATTR =
+        "javax.servlet.forward.path_info";
+
+    /**
+     * The request attribute under which we store the servlet name on a
+     * named dispatcher request.
+     */
+    public static final String NAMED_DISPATCHER_ATTR =
+        "org.apache.catalina.NAMED";
+
+    /**
+     * The request attribute under which the request URI of the included
+     * servlet is stored on an included dispatcher request.
+     */
+    public static final String INCLUDE_REQUEST_URI_ATTR =
+        "javax.servlet.include.request_uri";
+
+
+    /**
+     * The request attribute under which the context path of the included
+     * servlet is stored on an included dispatcher request.
+     */
+    public static final String INCLUDE_CONTEXT_PATH_ATTR =
+        "javax.servlet.include.context_path";
+
+
+    /**
+     * The request attribute under which the path info of the included
+     * servlet is stored on an included dispatcher request.
+     */
+    public static final String INCLUDE_PATH_INFO_ATTR =
+        "javax.servlet.include.path_info";
+
+
+    /**
+     * The request attribute under which the servlet path of the included
+     * servlet is stored on an included dispatcher request.
+     */
+    public static final String INCLUDE_SERVLET_PATH_ATTR =
+        "javax.servlet.include.servlet_path";
+
+
+    /**
+     * The request attribute under which the query string of the included
+     * servlet is stored on an included dispatcher request.
+     */
+    public static final String INCLUDE_QUERY_STRING_ATTR =
+        "javax.servlet.include.query_string";
+
+    /**
+     * The request attribute under which we expose the value of the
+     * <code>&lt;jsp-file&gt;</code> value associated with this servlet,
+     * if any.
+     */
+    public static final String JSP_FILE_ATTR =
+        "org.apache.catalina.jsp_file";
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    private static Logger log = Logger.getLogger(RequestDispatcherImpl.class.getName());
+
+    private ServletContextImpl ctx = null;
+
+    /**
+     * The servlet name for a named dispatcher.
+     */
+    private String name = null;
+
+    // Path for a path dispatcher
+    private String path;
+
+    /**
+     * MappingData object - per thread for buffering.
+     */
+    private transient ThreadLocal localMappingData = new ThreadLocal();
+
+    /*
+      OrigRequest(ServletRequestImpl) -> include/forward * -> this include
+      
+      On the path: user-defined RequestWrapper or our ServletRequestWrapper
+
+      include() is called with a RequestWrapper(->...->origRequest) or origRequest
+      
+      Based on params, etc -> we wrap the req / response in ServletRequestWrapper,
+       call filters+servlet. Inside, the req can be wrapped again in 
+       userReqWrapper, and other include called. 
+       
+      
+     */
+    
+    /**
+     * The outermost request that will be passed on to the invoked servlet.
+     */
+    private ServletRequest outerRequest = null;
+
+    /**
+     * The outermost response that will be passed on to the invoked servlet.
+     */
+    private ServletResponse outerResponse = null;
+
+    /**
+     * The request wrapper we have created and installed (if any).
+     */
+    private ServletRequest wrapRequest = null;
+
+    /**
+     * The response wrapper we have created and installed (if any).
+     */
+    private ServletResponse wrapResponse = null;
+
+    // Parameters used when constructing the dispatcvher 
+    /**
+     * The extra path information for this RequestDispatcher.
+     */
+    private String pathInfo = null;
+    /**
+     * The query string parameters for this RequestDispatcher.
+     */
+    private String queryString = null;
+    /**
+     * The request URI for this RequestDispatcher.
+     */
+    private String requestURI = null;
+    /**
+     * The servlet path for this RequestDispatcher.
+     */
+    private String servletPath = null;
+
+    //
+    private String origServletPath = null;
+    
+    /**
+     * The Wrapper associated with the resource that will be forwarded to
+     * or included.
+     */
+    private ServletConfigImpl wrapper = null;
+    
+    private Servlet servlet; 
+
+    /** Named dispatcher 
+     */
+    public RequestDispatcherImpl(ServletConfigImpl wrapper, String name) {
+        this.wrapper = wrapper;
+        this.name = name;
+        this.ctx = (ServletContextImpl) wrapper.getServletContext();
+
+    }
+    
+    public RequestDispatcherImpl(ServletContextImpl ctx, String path) {
+        this.path = path;
+        this.ctx = ctx;
+    }
+   
+
+
+    /**
+     * Forward this request and response to another resource for processing.
+     * Any runtime exception, IOException, or ServletException thrown by the
+     * called servlet will be propogated to the caller.
+     *
+     * @param request The servlet request to be forwarded
+     * @param response The servlet response to be forwarded
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet exception occurs
+     */
+    public void forward(ServletRequest request, ServletResponse response)
+        throws ServletException, IOException
+    {
+        // Reset any output that has been buffered, but keep headers/cookies
+        if (response.isCommitted()) {
+            throw new IllegalStateException("forward(): response.isComitted()");
+        }
+        
+        try {
+            response.resetBuffer();
+        } catch (IllegalStateException e) {
+            throw e;
+        }
+
+        // Set up to handle the specified request and response
+        setup(request, response, false);
+
+        // Identify the HTTP-specific request and response objects (if any)
+        HttpServletRequest hrequest = (HttpServletRequest) request;
+
+        ServletRequestWrapperImpl wrequest =
+            (ServletRequestWrapperImpl) wrapRequest();
+
+        
+        if (name != null) {
+            wrequest.setRequestURI(hrequest.getRequestURI());
+            wrequest.setContextPath(hrequest.getContextPath());
+            wrequest.setServletPath(hrequest.getServletPath());
+            wrequest.setPathInfo(hrequest.getPathInfo());
+            wrequest.setQueryString(hrequest.getQueryString());
+
+            
+        } else { // path based
+            mapPath();
+            if (wrapper == null) {
+                throw new ServletException("Forward not found " + 
+                        path);
+            }
+            String contextPath = ctx.getContextPath();
+            if (hrequest.getAttribute(FORWARD_REQUEST_URI_ATTR) == null) {
+                wrequest.setAttribute(FORWARD_REQUEST_URI_ATTR,
+                                      hrequest.getRequestURI());
+                wrequest.setAttribute(FORWARD_CONTEXT_PATH_ATTR,
+                                      hrequest.getContextPath());
+                wrequest.setAttribute(FORWARD_SERVLET_PATH_ATTR,
+                                      hrequest.getServletPath());
+                wrequest.setAttribute(FORWARD_PATH_INFO_ATTR,
+                                      hrequest.getPathInfo());
+                wrequest.setAttribute(FORWARD_QUERY_STRING_ATTR,
+                                      hrequest.getQueryString());
+            }
+            wrequest.setContextPath(contextPath);
+            wrequest.setRequestURI(requestURI);
+            wrequest.setServletPath(servletPath);
+            wrequest.setPathInfo(pathInfo);
+            if (queryString != null) {
+                wrequest.setQueryString(queryString);
+                wrequest.setQueryParams(queryString);
+            }
+        }
+        processRequest(outerRequest, outerResponse);
+
+        wrequest.recycle();
+        unwrapRequest();
+
+        // This is not a real close in order to support error processing
+//        if ( log.isDebugEnabled() )
+//            log.debug(" Disabling the response for futher output");
+
+        if  (response instanceof ServletResponseImpl) {
+            ((ServletResponseImpl) response).flushBuffer();
+            ((ServletResponseImpl) response).setSuspended(true);
+        } else {
+            // Servlet SRV.6.2.2. The Resquest/Response may have been wrapped
+            // and may no longer be instance of RequestFacade 
+            if (log.isLoggable(Level.FINE)){
+                log.fine( " The Response is vehiculed using a wrapper: " 
+                           + response.getClass().getName() );
+            }
+
+            // Close anyway
+            try {
+                PrintWriter writer = response.getWriter();
+                writer.close();
+            } catch (IllegalStateException e) {
+                try {
+                    ServletOutputStream stream = response.getOutputStream();
+                    stream.close();
+                } catch (IllegalStateException f) {
+                    ;
+                } catch (IOException f) {
+                    ;
+                }
+            } catch (IOException e) {
+                ;
+            }
+        }
+    }
+
+    
+    
+    /**
+     * Include the response from another resource in the current response.
+     * Any runtime exception, IOException, or ServletException thrown by the
+     * called servlet will be propogated to the caller.
+     *
+     * @param request The servlet request that is including this one
+     * @param response The servlet response to be appended to
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet exception occurs
+     */
+    public void include(ServletRequest request, ServletResponse response)
+        throws ServletException, IOException
+    {
+
+        // Set up to handle the specified request and response
+        setup(request, response, true);
+
+        // Create a wrapped response to use for this request
+        // this actually gets inserted somewhere in the chain - it's not 
+        // the last one, but first non-user response
+        wrapResponse();
+        ServletRequestWrapperImpl wrequest =
+            (ServletRequestWrapperImpl) wrapRequest();
+
+
+        // Handle an HTTP named dispatcher include
+        if (name != null) {
+            wrequest.setAttribute(NAMED_DISPATCHER_ATTR, name);
+            if (servletPath != null) wrequest.setServletPath(servletPath);
+            wrequest.setAttribute(WebappFilterMapper.DISPATCHER_TYPE_ATTR,
+                                  new Integer(WebappFilterMapper.INCLUDE));
+            wrequest.setAttribute(WebappFilterMapper.DISPATCHER_REQUEST_PATH_ATTR, 
+                                  origServletPath);
+        } else {
+            mapPath();
+            String contextPath = ctx.getContextPath();
+            if (requestURI != null)
+                wrequest.setAttribute(INCLUDE_REQUEST_URI_ATTR,
+                                      requestURI);
+            if (contextPath != null)
+                wrequest.setAttribute(INCLUDE_CONTEXT_PATH_ATTR,
+                                      contextPath);
+            if (servletPath != null)
+                wrequest.setAttribute(INCLUDE_SERVLET_PATH_ATTR,
+                                      servletPath);
+            if (pathInfo != null)
+                wrequest.setAttribute(INCLUDE_PATH_INFO_ATTR,
+                                      pathInfo);
+            if (queryString != null) {
+                wrequest.setAttribute(INCLUDE_QUERY_STRING_ATTR,
+                                      queryString);
+                wrequest.setQueryParams(queryString);
+            }
+            
+            wrequest.setAttribute(WebappFilterMapper.DISPATCHER_TYPE_ATTR,
+                                  new Integer(WebappFilterMapper.INCLUDE));
+            wrequest.setAttribute(WebappFilterMapper.DISPATCHER_REQUEST_PATH_ATTR, 
+                                  origServletPath);
+        }
+
+        invoke(outerRequest, outerResponse);
+
+        wrequest.recycle();
+        unwrapRequest();
+        unwrapResponse();
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+    public void mapPath() {
+        if (path == null || servletPath != null) return;
+        
+        // Retrieve the thread local URI, used for mapping
+        // TODO: recycle RequestDispatcher stack and associated objects
+        // instead of this object
+        
+        // Retrieve the thread local mapping data
+        MappingData mappingData = (MappingData) localMappingData.get();
+        if (mappingData == null) {
+            mappingData = new MappingData();
+            localMappingData.set(mappingData);
+        }
+
+        // Get query string
+        int pos = path.indexOf('?');
+        if (pos >= 0) {
+            queryString = path.substring(pos + 1);
+        } else {
+            pos = path.length();
+        }
+        // Map the URI
+        MessageBytes uriMB = MessageBytes.newInstance();
+        CharChunk charBuffer = new CharChunk();
+        //mappingData.localURIBytes;
+        uriMB.recycle();
+        //CharChunk uriCC = uriMB.getCharChunk();
+        try {
+            /*
+             * Ignore any trailing path params (separated by ';') for mapping
+             * purposes.
+             * This is sometimes broken - path params can be on any path 
+             * component, not just last.
+             */
+            int semicolon = path.indexOf(';');
+            if (pos >= 0 && semicolon > pos) {
+                semicolon = -1;
+            }
+            if (ctx.getContextPath().length() > 1 ) {
+                charBuffer.append(ctx.getContextPath());
+            }
+            charBuffer.append(path, 0, 
+                semicolon > 0 ? semicolon : pos);
+
+            // Wrap the buffer 
+            uriMB.setChars(charBuffer.getBuffer(),
+                    charBuffer.getOffset(),
+                    charBuffer.getLength());
+
+            // TODO: make charBuffer part of request or something
+            ctx.getMapper().map(uriMB, mappingData);
+            
+            // at least default wrapper must be returned
+            
+            /*
+             * Append any trailing path params (separated by ';') that were
+             * ignored for mapping purposes, so that they're reflected in the
+             * RequestDispatcher's requestURI
+             */
+            if (semicolon > 0) {
+                // I don't think this will be used in future
+                charBuffer.append(path, 
+                    semicolon, pos - semicolon);
+            }
+        } catch (Exception e) {
+            log.log(Level.SEVERE, "getRequestDispatcher()", e);
+        }
+
+        wrapper = (ServletConfigImpl) mappingData.wrapper;
+        servletPath = mappingData.wrapperPath.toString();
+        pathInfo = mappingData.pathInfo.toString();
+
+        mappingData.recycle();
+
+    }
+    
+
+    /**
+     * Prepare the request based on the filter configuration.
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    private void processRequest(ServletRequest request, 
+                                ServletResponse response)
+            throws IOException, ServletException {
+        Integer disInt = 
+            (Integer) request.getAttribute(WebappFilterMapper.DISPATCHER_TYPE_ATTR);
+        if (disInt != null) {
+            if (disInt.intValue() != WebappFilterMapper.ERROR) {
+                outerRequest.setAttribute
+                    (WebappFilterMapper.DISPATCHER_REQUEST_PATH_ATTR,
+                     origServletPath);
+                outerRequest.setAttribute
+                    (WebappFilterMapper.DISPATCHER_TYPE_ATTR,
+                     new Integer(WebappFilterMapper.FORWARD));
+            }
+            invoke(outerRequest, response);
+        }
+
+    }
+    
+    
+    
+
+    /**
+     * Ask the resource represented by this RequestDispatcher to process
+     * the associated request, and create (or append to) the associated
+     * response.
+     * <p>
+     * <strong>IMPLEMENTATION NOTE</strong>: This implementation assumes
+     * that no filters are applied to a forwarded or included resource,
+     * because they were already done for the original request.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    private void invoke(ServletRequest request, ServletResponse response)
+            throws IOException, ServletException {
+
+        // Checking to see if the context classloader is the current context
+        // classloader. If it's not, we're saving it, and setting the context
+        // classloader to the Context classloader
+        ClassLoader oldCCL = Thread.currentThread().getContextClassLoader();
+        ClassLoader contextClassLoader = ctx.getClassLoader();
+
+        if (oldCCL != contextClassLoader) {
+            Thread.currentThread().setContextClassLoader(contextClassLoader);
+        } else {
+            oldCCL = null;
+        }
+
+        // Initialize local variables we may need
+        HttpServletResponse hresponse = (HttpServletResponse) response;
+        IOException ioException = null;
+        ServletException servletException = null;
+        RuntimeException runtimeException = null;
+        
+        servletException = allocateServlet(hresponse, servletException);
+                
+        // Get the FilterChain Here
+        WebappFilterMapper factory = 
+            ((ServletContextImpl)wrapper.getServletContext()).getFilterMapper();
+        
+        FilterChainImpl filterChain = factory.createFilterChain(request,
+                                                                wrapper,
+                                                                servlet);
+
+        // Call the service() method for the allocated servlet instance
+        try {
+            String jspFile = wrapper.getJspFile();
+            if (jspFile != null)
+                request.setAttribute(JSP_FILE_ATTR, jspFile);
+            else
+                request.removeAttribute(JSP_FILE_ATTR);
+            // for includes/forwards
+            if ((servlet != null) && (filterChain != null)) {
+               filterChain.doFilter(request, response);
+             }
+        } catch (IOException e) {
+            ctx.getLogger().log(Level.WARNING, "RequestDispatcherImpl error " + 
+                    wrapper.getServletName(), e);
+            ioException = e;
+        } catch (UnavailableException e) {
+            ctx.getLogger().log(Level.WARNING, "RequestDispatcherImpl error " + 
+                    wrapper.getServletName(), e);
+            servletException = e;
+            wrapper.unavailable(e);
+        } catch (ServletException e) {
+            servletException = e;
+        } catch (RuntimeException e) {
+            ctx.getLogger().log(Level.WARNING, "RequestDispatcherImpl error " + 
+                    wrapper.getServletName(), e);
+            runtimeException = e;
+        }
+        request.removeAttribute(JSP_FILE_ATTR);
+
+        // Release the filter chain (if any) for this request
+        if (filterChain != null)
+            filterChain.release();
+
+        servletException = servletDealocate(servletException);
+
+        // Reset the old context class loader
+        if (oldCCL != null)
+            Thread.currentThread().setContextClassLoader(oldCCL);
+        
+        // Unwrap request/response if needed
+        unwrapRequest();
+        unwrapResponse();
+
+        // Rethrow an exception if one was thrown by the invoked servlet
+        if (ioException != null)
+            throw ioException;
+        if (servletException != null)
+            throw servletException;
+        if (runtimeException != null)
+            throw runtimeException;
+
+    }
+
+    private ServletException servletDealocate(ServletException servletException)
+    {
+        if (servlet != null) {
+            wrapper.deallocate(servlet);
+        }
+        return servletException;
+    }
+
+    private ServletException allocateServlet(HttpServletResponse hresponse,
+                                             ServletException servletException)
+        throws IOException
+    {
+        boolean unavailable = false;
+
+        // Check for the servlet being marked unavailable
+        if (wrapper.isUnavailable()) {
+            ctx.getLogger().log(Level.WARNING, "isUnavailable() " + wrapper.getServletName());
+            long available = wrapper.getAvailable();
+            if ((available > 0L) && (available < Long.MAX_VALUE))
+                hresponse.setDateHeader("Retry-After", available);
+            hresponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, 
+                    "Unavailable"); // No need to include internal info: wrapper.getServletName();
+            unavailable = true;
+        }
+
+        // Allocate a servlet instance to process this request
+        try {
+            if (!unavailable) {
+                servlet = wrapper.allocate();
+            }
+        } catch (ServletException e) {
+            ctx.getLogger().log(Level.WARNING, "RequestDispatcher: allocate " + 
+                             wrapper.toString());
+            servletException = e;
+            servlet = null;
+        } catch (Throwable e) {
+            ctx.getLogger().log(Level.WARNING, "allocate() error " + wrapper.getServletName(), e);
+            servletException = new ServletException
+                ("Allocate error " + wrapper.getServletName(), e);
+            servlet = null;
+        }
+        return servletException;
+    }
+
+
+    /**
+     * Set up to handle the specified request and response
+     *
+     * @param request The servlet request specified by the caller
+     * @param response The servlet response specified by the caller
+     * @param including Are we performing an include() as opposed to
+     *  a forward()?
+     */
+    private void setup(ServletRequest request, ServletResponse response,
+                       boolean including) {
+
+        this.outerRequest = request;
+        this.outerResponse = response;
+    }
+
+
+    /**
+     * Unwrap the request if we have wrapped it. Not sure how it could end
+     * up in the middle. 
+     */
+    private void unwrapRequest() {
+        if (wrapRequest == null)
+            return;
+
+        ServletRequest previous = null;
+        ServletRequest current = outerRequest;
+        while (current != null) {
+            // If we run into the container request we are done
+            if (current instanceof ServletRequestImpl)
+                break;
+
+            // Remove the current request if it is our wrapper
+            if (current == wrapRequest) {
+                ServletRequest next =
+                  ((ServletRequestWrapper) current).getRequest();
+                if (previous == null)
+                    outerRequest = next;
+                else
+                    ((ServletRequestWrapper) previous).setRequest(next);
+                break;
+            }
+
+            // Advance to the next request in the chain
+            previous = current;
+            current = ((ServletRequestWrapper) current).getRequest();
+        }
+    }
+
+
+    /**
+     * Unwrap the response if we have wrapped it.
+     */
+    private void unwrapResponse() {
+        if (wrapResponse == null)
+            return;
+
+        ServletResponse previous = null;
+        ServletResponse current = outerResponse;
+        while (current != null) {
+            // If we run into the container response we are done
+            if (current instanceof ServletResponseImpl)
+                break;
+
+            // Remove the current response if it is our wrapper
+            if (current == wrapResponse) {
+                ServletResponse next =
+                  ((ServletResponseWrapper) current).getResponse();
+                if (previous == null)
+                    outerResponse = next;
+                else
+                    ((ServletResponseWrapper) previous).setResponse(next);
+                break;
+            }
+            // Advance to the next response in the chain
+            previous = current;
+            current = ((ServletResponseWrapper) current).getResponse();
+        }
+    }
+
+
+    /**
+     * Create and return a request wrapper that has been inserted in the
+     * appropriate spot in the request chain.
+     */
+    private ServletRequest wrapRequest() {
+        // Locate the request we should insert in front of
+        ServletRequest previous = null;
+        ServletRequest current = outerRequest;
+        while (current != null) {
+            if (!(current instanceof ServletRequestWrapper))
+                break;
+            if (current instanceof ServletRequestWrapperImpl)
+                break;
+            if (current instanceof ServletRequestImpl)
+                break;
+            // user-specified
+            previous = current;
+            current = ((ServletRequestWrapper) current).getRequest();
+        }
+        // now previous will be a user-specified wrapper, 
+        // and current one of our own wrappers ( deeper in stack )
+        // ... current USER_previous USER USER
+        // previous is null if the top request is ours.
+
+        // Instantiate a new wrapper at this point and insert it in the chain
+        ServletRequest wrapper = null;
+        
+        // Compute a crossContext flag
+        boolean crossContext = isCrossContext();
+        wrapper = 
+            new ServletRequestWrapperImpl((HttpServletRequest) current, 
+                                          ctx, crossContext);
+
+        if (previous == null) {
+            // outer becomes the wrapper, includes orig wrapper inside
+            outerRequest = wrapper;
+        } else {
+            // outer remains user-specified sersvlet, delegating to 
+            // our wrapper, which delegates to real request or our wrapper.
+            ((ServletRequestWrapper) previous).setRequest(wrapper);
+        }
+        wrapRequest = wrapper;
+        return (wrapper);
+    }
+
+    private boolean isCrossContext() {
+        boolean crossContext = false;
+        if ((outerRequest instanceof ServletRequestWrapperImpl) ||
+                (outerRequest instanceof ServletRequestImpl) ||
+                (outerRequest instanceof HttpServletRequest)) {
+            HttpServletRequest houterRequest = 
+                (HttpServletRequest) outerRequest;
+            Object contextPath = 
+                houterRequest.getAttribute(INCLUDE_CONTEXT_PATH_ATTR);
+            if (contextPath == null) {
+                // Forward
+                contextPath = houterRequest.getContextPath();
+            }
+            crossContext = !(ctx.getContextPath().equals(contextPath));
+        }
+        return crossContext;
+    }
+
+
+    /**
+     * Create and return a response wrapper that has been inserted in the
+     * appropriate spot in the response chain.
+     * 
+     * Side effect: updates outerResponse, wrapResponse.
+     * The chain is updated with a wrapper below lowest user wrapper
+     */
+    private ServletResponse wrapResponse() {
+        // Locate the response we should insert in front of
+        ServletResponse previous = null;
+        ServletResponse current = outerResponse;
+        while (current != null) {
+            if (!(current instanceof ServletResponseWrapper))
+                break;
+            if (current instanceof ServletResponseImpl)
+                break;
+            previous = current;
+            current = ((ServletResponseWrapper) current).getResponse();
+        }
+
+        // Instantiate a new wrapper at this point and insert it in the chain
+        ServletResponse wrapper = 
+             new ServletResponseIncludeWrapper(current);
+            
+        if (previous == null) {
+            // outer is ours, we can wrap on top
+            outerResponse = wrapper;
+        } else {
+            // outer is user-specified, leave it alone. 
+            // we insert ourself below the lowest user-specified response
+            ((ServletResponseWrapper) previous).setResponse(wrapper);
+        }
+        wrapResponse = wrapper;
+        return (wrapper);
+
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletConfigImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletConfigImpl.java
new file mode 100644 (file)
index 0000000..85be60b
--- /dev/null
@@ -0,0 +1,809 @@
+/*
+ * Copyright 1999-2002,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Stack;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.SingleThreadModel;
+import javax.servlet.UnavailableException;
+
+import org.apache.tomcat.addons.UserTemplateClassMapper;
+import org.apache.tomcat.servlets.util.Enumerator;
+import org.apache.tomcat.util.IntrospectionUtils;
+
+/**
+ * Based on Wrapper.
+ * 
+ * Standard implementation of the <b>Wrapper</b> interface that represents
+ * an individual servlet definition.  No child Containers are allowed, and
+ * the parent Container must be a Context.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ */
+public class ServletConfigImpl implements ServletConfig {
+    
+    private static Logger log=
+        Logger.getLogger(ServletConfigImpl.class.getName());
+
+    private static final String[] DEFAULT_SERVLET_METHODS = new String[] {
+                                                    "GET", "HEAD", "POST" };
+
+    // TODO: refactor all 'stm' to separate class (not implemented)
+    //    public static final String SINGLE_THREADED_PROXY =
+    //             "org.apache.tomcat.servlets.jsp.SingleThreadedProxyServlet";
+
+
+    protected Map<String, String> initParams = new HashMap<String, String>();
+    protected String servletName;
+    protected String servletClass;
+    protected String jspFile;
+    protected int loadOnStartup = -1;
+    protected String runAs;
+    protected Map securityRoleRef = new HashMap(); // roleName -> [roleLink]
+    
+    /**
+     * The date and time at which this servlet will become available (in
+     * milliseconds since the epoch), or zero if the servlet is available.
+     * If this value equals Long.MAX_VALUE, the unavailability of this
+     * servlet is considered permanent.
+     */
+    private transient long available = 0L;
+    
+    private ServletContextImpl ctx;
+
+    /**
+     * The (single) initialized instance of this servlet.
+     */
+    private transient Servlet instance = null;
+    
+    /**
+     * Are we unloading our servlet instance at the moment?
+     */
+    private transient boolean unloading = false;
+
+    private Class classClass = null;
+
+    // Support for SingleThreaded
+    /**
+     * The count of allocations that are currently active (even if they
+     * are for the same instance, as will be true on a non-STM servlet).
+     */
+    private transient int countAllocated = 0;
+
+    private transient boolean singleThreadModel = false;
+    /**
+     * Stack containing the STM instances.
+     */
+    private transient Stack instancePool = null;
+
+    
+    // Statistics
+    private transient long loadTime=0;
+    private transient int classLoadTime=0;
+
+    // ------------------------------------------------------------- Properties
+    public ServletConfigImpl(ServletContextImpl ctx, String name, 
+                             String classname) {
+        this.servletName = name;
+        this.servletClass = classname;
+        this.ctx = ctx;
+        ctx.facade.notifyAdd(this);
+    }
+
+    /**
+     * Return the available date/time for this servlet, in milliseconds since
+     * the epoch.  If this date/time is Long.MAX_VALUE, it is considered to mean
+     * that unavailability is permanent and any request for this servlet will return
+     * an SC_NOT_FOUND error.  If this date/time is in the future, any request for
+     * this servlet will return an SC_SERVICE_UNAVAILABLE error.  If it is zero,
+     * the servlet is currently available.
+     */
+    public long getAvailable() {
+        return (this.available);
+    }
+
+
+    /**
+     * Set the available date/time for this servlet, in milliseconds since the
+     * epoch.  If this date/time is Long.MAX_VALUE, it is considered to mean
+     * that unavailability is permanent and any request for this servlet will return
+     * an SC_NOT_FOUND error. If this date/time is in the future, any request for
+     * this servlet will return an SC_SERVICE_UNAVAILABLE error.
+     *
+     * @param available The new available date/time
+     */
+    public void setAvailable(long available) {
+
+        long oldAvailable = this.available;
+        if (available > System.currentTimeMillis())
+            this.available = available;
+        else
+            this.available = 0L;
+
+    }
+
+
+    /**
+     * Return the number of active allocations of this servlet, even if they
+     * are all for the same instance (as will be true for servlets that do
+     * not implement <code>SingleThreadModel</code>.
+     */
+    public int getCountAllocated() {
+        return (this.countAllocated);
+    }
+
+    /**
+     * Return the jsp-file setting for this servlet.
+     */
+    public String getJspFile() {
+        return jspFile;
+    }
+
+    public void setJspFile(String s) {
+      this.jspFile = s;
+    }
+    
+    /**
+     * Return the load-on-startup order value (negative value means
+     * load on first call).
+     */
+    public int getLoadOnStartup() {
+        return loadOnStartup;
+    }
+
+    /**
+     * Return the fully qualified servlet class name for this servlet.
+     */
+    public String getServletClass() {
+        return servletClass;
+    }
+
+    /**
+     * Is this servlet currently unavailable?
+     */
+    public boolean isUnavailable() {
+        if (available == 0L)
+            return (false);
+        else if (available <= System.currentTimeMillis()) {
+            available = 0L;
+            return (false);
+        } else
+            return (true);
+
+    }
+
+
+    /**
+     * Gets the names of the methods supported by the underlying servlet.
+     *
+     * This is the same set of methods included in the Allow response header
+     * in response to an OPTIONS request method processed by the underlying
+     * servlet.
+     *
+     * @return Array of names of the methods supported by the underlying
+     * servlet
+     */
+    public String[] getServletMethods() throws ServletException {
+
+        Class servletClazz = loadServlet().getClass();
+        if (!javax.servlet.http.HttpServlet.class.isAssignableFrom(
+                                                        servletClazz)) {
+            return DEFAULT_SERVLET_METHODS;
+        }
+
+        HashSet allow = new HashSet();
+        allow.add("TRACE");
+        allow.add("OPTIONS");
+       
+        Method[] methods = getAllDeclaredMethods(servletClazz);
+        for (int i=0; methods != null && i<methods.length; i++) {
+            Method m = methods[i];
+           
+            if (m.getName().equals("doGet")) {
+                allow.add("GET");
+                allow.add("HEAD");
+            } else if (m.getName().equals("doPost")) {
+                allow.add("POST");
+            } else if (m.getName().equals("doPut")) {
+                allow.add("PUT");
+            } else if (m.getName().equals("doDelete")) {
+                allow.add("DELETE");
+            }
+        }
+
+        String[] methodNames = new String[allow.size()];
+        return (String[]) allow.toArray(methodNames);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Extract the root cause from a servlet exception.
+     * 
+     * @param e The servlet exception
+     */
+    public static Throwable getRootCause(ServletException e) {
+        Throwable rootCause = e;
+        Throwable rootCauseCheck = null;
+        // Extra aggressive rootCause finding
+        do {
+            try {
+                rootCauseCheck = (Throwable)IntrospectionUtils.getProperty
+                                            (rootCause, "rootCause");
+                if (rootCauseCheck!=null)
+                    rootCause = rootCauseCheck;
+
+            } catch (ClassCastException ex) {
+                rootCauseCheck = null;
+            }
+        } while (rootCauseCheck != null);
+        return rootCause;
+    }
+
+    /**
+     *  MUST be called before service()
+     *  This method should be called to get the servlet. After 
+     *  service(), dealocate should be called. This deals with STM and
+     *  update use counters.
+     *  
+     *  Normally called from RequestDispatcher and TomcatLite.
+     */
+    public Servlet allocate() throws ServletException {
+        // If we are currently unloading this servlet, throw an exception
+        if (unloading) 
+            throw new ServletException
+              ("allocate() while unloading " + getServletName());
+
+        Servlet servlet = null;
+        if (instance == null && !singleThreadModel) {
+            // never loaded.
+            synchronized (this) {
+                if (instance == null && !singleThreadModel) {
+                    try {
+                        servlet = loadServlet();
+                    } catch (ServletException e) {
+                        throw e;
+                    } catch (Throwable e) {
+                        throw new ServletException("loadServlet()", e);
+                    }
+                    if (servlet != null && !singleThreadModel) {
+                        setServlet(servlet);
+                    }
+                }
+            }
+        }
+
+        // If not SingleThreadedModel, return the same instance every time
+        if (instance != null) {
+            countAllocated++;
+            return (instance);
+        }
+        
+        // Simpler policy for ST: unbound number of servlets ( can grow to
+        // one per thread )
+        
+        synchronized (instancePool) {
+            if (instancePool.isEmpty()) {
+                try {
+                    if (servlet != null) {
+                        // this is the first invocation
+                        countAllocated++;
+                        return servlet;
+                    }
+                    countAllocated++;
+                    Servlet newServlet = loadServlet();
+                    log.fine("New STM servet " + newServlet + " " + 
+                            countAllocated);
+                    return newServlet;
+                } catch (ServletException e) {
+                    throw e;
+                } catch (Throwable e) {
+                    throw new ServletException("allocate " + getServletName(),
+                            e);
+                }
+            }
+            log.fine("Get from pool " + instancePool.size() +  " " +
+                    countAllocated);
+            Servlet s = (Servlet) instancePool.pop();
+            countAllocated++;
+            log.fine("After get " + instancePool.size() + " " + s  + 
+                    " " + countAllocated);
+            return s;
+        }
+    }
+
+
+    /**
+     * MUST be called after service().
+     */
+    public void deallocate(Servlet servlet) {
+        // If not SingleThreadModel, no action is required
+        if (!singleThreadModel) {
+            countAllocated--;
+            return;
+        }
+
+        // Unlock and free this instance
+        synchronized (instancePool) {
+            countAllocated--;
+            if (instancePool.contains(servlet)) {
+                System.err.println("Aleady in pool " + servlet + " " 
+                        + instancePool.size()+ " " + countAllocated);
+                return;
+            }
+            System.err.println("return  pool " + servlet +  " " + 
+                    instancePool.size() + " " + countAllocated);
+            instancePool.push(servlet);
+        }
+    }
+
+    private Servlet newInstance() throws ServletException {
+        String actualClass = servletClass;
+
+        if (actualClass == null) {
+            // No explicit name. Try to use the framework
+            if (jspFile != null) {
+                
+                // Named JSPs can be handled by a servlet or by the mapper.
+                Servlet res = (Servlet) ctx.getObjectManager().get("filetemplate-servlet");
+                if (res != null) {
+                    classClass = res.getClass();
+                    actualClass = classClass.getName();
+                    initParams.put("jsp-file", jspFile);
+                    return res;
+                } else {
+                    UserTemplateClassMapper mapper = 
+                        (UserTemplateClassMapper) ctx.getObjectManager().get(
+                                UserTemplateClassMapper.class);
+                    if (mapper != null) {
+                        return mapper.loadProxy(jspFile, ctx, this);
+                    }
+                }
+            }
+            if (actualClass == null) {
+                // Covers 'default-" and "jspwildcard-" servlets as well
+                Servlet res = (Servlet) ctx.getObjectManager().get( servletName +
+                        "-servlet");
+                if (res != null) {
+                    classClass = res.getClass();
+                    actualClass = classClass.getName();
+                    return res;
+                }
+            }
+
+            //ctx.getObjectManager().getObject(c);
+            //ctx.getObjectManager().getObject(servletName);
+        }
+            
+        
+        if (classClass == null) {
+            // set classClass
+            loadClass(actualClass);
+        }
+
+        
+        // jsp-file case. Load the JspProxyServlet instead, with the 
+        // right params. Note the JspProxyServlet is _not_ jasper, 
+        // nor 'jsp' servlet - it is just a proxy with no special 
+        // params. It calls the jsp servlet and jasper to generate the
+        // real class.
+        
+        // this is quite different from catalina, where an ugly kludge was
+        // used to use the same jsp servlet in 2 roles
+        
+        // the jsp proxy is replaced by the web.xml processor
+        
+        if (classClass == null) {
+            unavailable(null);
+            throw new UnavailableException("ClassNotFound: " + actualClass);
+        }
+        
+        // Instantiate and initialize an instance of the servlet class itself
+        try {
+            return (Servlet) classClass.newInstance();
+        } catch (ClassCastException e) {
+            unavailable(null);
+            throw new UnavailableException("ClassCast: (Servlet)" + 
+                    actualClass);
+        } catch (Throwable e) {
+            unavailable(null);
+
+            // Added extra log statement for Bugzilla 36630:
+            // http://issues.apache.org/bugzilla/show_bug.cgi?id=36630
+            if(log.isLoggable(Level.FINE)) {
+                log.log(Level.FINE, "newInstance() error: servlet-name: " + 
+                        getServletName() +
+                        " servlet-class: " + actualClass, e);
+            }
+
+            // Restore the context ClassLoader
+            throw new ServletException("newInstance() error " + getServletName() + 
+                    " " + actualClass, e);
+        }
+    }
+
+    /**
+     * Load and initialize an instance of this servlet, if there is not already
+     * at least one initialized instance.  This can be used, for example, to
+     * load servlets that are marked in the deployment descriptor to be loaded
+     * at server startup time.
+     */
+    public synchronized Servlet loadServlet() throws ServletException {
+        // Nothing to do if we already have an instance or an instance pool
+        if (!singleThreadModel && (instance != null))
+            return instance;
+        
+        long t1=System.currentTimeMillis();
+
+        Servlet servlet = newInstance();
+        
+        classLoadTime=(int) (System.currentTimeMillis() -t1);
+        
+        // Call the initialization method of this servlet
+        try {
+            servlet.init(this);
+        } catch (UnavailableException f) {
+            unavailable(f);
+            throw f;
+        } catch (ServletException f) {
+            throw f;
+        } catch (Throwable f) {
+            getServletContext().log("StandardWrapper.Throwable", f );
+            throw new ServletException("Servlet.init()", f);
+        }
+
+        // Register our newly initialized instance
+        singleThreadModel = servlet instanceof SingleThreadModel;
+        if (singleThreadModel) {
+            if (instancePool == null)
+                instancePool = new Stack();
+        }
+        loadTime=System.currentTimeMillis() -t1;
+        
+        return servlet;
+    }
+
+
+    private void loadClass(String actualClass) throws ServletException {
+        // Complain if no servlet class has been specified
+        if (actualClass == null) {
+            unavailable(null);
+            throw new ServletException("servlet-class missing " +  
+                    getServletName());
+        }
+        
+        ClassLoader classLoader = ctx.getClassLoader(); 
+        if (classLoader == null ) 
+            classLoader = this.getClass().getClassLoader();
+        
+        // Load the specified servlet class from the appropriate class loader
+        try {
+            classClass = classLoader.loadClass(actualClass);
+        } catch (ClassNotFoundException e) {
+            classClass = null;
+        }
+    }
+
+    /**
+     * Return a String representation of this component.
+     */
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        if (ctx != null) {
+            sb.append(ctx.toString());
+            sb.append(".");
+        }
+        sb.append("Servlet[");
+        sb.append(getServletName()).append(" ");
+        sb.append(servletClass);
+        if (jspFile != null) {
+            sb.append(" jsp=").append(jspFile);
+        }
+        sb.append("]");
+        return (sb.toString());
+    }
+
+
+    /**
+     * Process an UnavailableException, marking this servlet as unavailable
+     * for the specified amount of time.
+     *
+     * @param unavailable The exception that occurred, or <code>null</code>
+     *  to mark this servlet as permanently unavailable
+     */
+    public void unavailable(UnavailableException unavailable) {
+        getServletContext().log("UnavailableException:" + getServletName());
+        if (unavailable == null)
+            setAvailable(Long.MAX_VALUE);
+        else if (unavailable.isPermanent())
+            setAvailable(Long.MAX_VALUE);
+        else {
+            int unavailableSeconds = unavailable.getUnavailableSeconds();
+            if (unavailableSeconds <= 0)
+                unavailableSeconds = 60;        // Arbitrary default
+            setAvailable(System.currentTimeMillis() +
+                         (unavailableSeconds * 1000L));
+        }
+
+    }
+
+
+    /**
+     * Unload all initialized instances of this servlet, after calling the
+     * <code>destroy()</code> method for each instance.  This can be used,
+     * for example, prior to shutting down the entire servlet engine, or
+     * prior to reloading all of the classes from the Loader associated with
+     * our Loader's repository.
+     *
+     * @exception ServletException if an exception is thrown by the
+     *  destroy() method
+     */
+    public synchronized void unload() throws ServletException {
+        setAvailable(Long.MAX_VALUE);        
+
+        // Nothing to do if we have never loaded the instance
+        if (!singleThreadModel && (instance == null))
+            return;
+        unloading = true;
+
+        // Loaf a while if the current instance is allocated
+        // (possibly more than once if non-STM)
+        if (countAllocated > 0) {
+            int nRetries = 0;
+            long delay = ctx.getUnloadDelay() / 20;
+            while ((nRetries < 21) && (countAllocated > 0)) {
+                if ((nRetries % 10) == 0) {
+                    log.info("Servlet.unload() timeout " + 
+                            countAllocated);
+                }
+                try {
+                    Thread.sleep(delay);
+                } catch (InterruptedException e) {
+                    ;
+                }
+                nRetries++;
+            }
+        }
+
+        ClassLoader oldCtxClassLoader =
+            Thread.currentThread().getContextClassLoader();
+        if (instance != null) {
+            ClassLoader classLoader = instance.getClass().getClassLoader();
+            
+            PrintStream out = System.out;
+            // Call the servlet destroy() method
+            try {
+                Thread.currentThread().setContextClassLoader(classLoader);
+                instance.destroy();
+            } catch (Throwable t) {
+                instance = null;
+                //instancePool = null;
+                unloading = false;
+                throw new ServletException("Servlet.destroy() " + 
+                        getServletName(), t);
+            } finally {
+                // restore the context ClassLoader
+                Thread.currentThread().setContextClassLoader(oldCtxClassLoader);
+            }
+            
+            // Deregister the destroyed instance
+            instance = null;
+        }
+        if (singleThreadModel && (instancePool != null)) {
+            try {
+                ClassLoader classLoader = ctx.getClassLoader();
+                Thread.currentThread().setContextClassLoader(classLoader);
+                while (!instancePool.isEmpty()) {
+                    ((Servlet) instancePool.pop()).destroy();
+                }
+            } catch (Throwable t) {
+                instancePool = null;
+                unloading = false;
+                throw new ServletException("Servlet.destroy() " + getServletName(), t);
+            } finally {
+                // restore the context ClassLoader
+                Thread.currentThread().setContextClassLoader
+                    (oldCtxClassLoader);
+            }
+            instancePool = null;
+        }
+
+        singleThreadModel = false;
+
+        unloading = false;
+    }
+    
+    
+    /**
+     * Return the initialization parameter value for the specified name,
+     * if any; otherwise return <code>null</code>.
+     *
+     * @param name Name of the initialization parameter to retrieve
+     */
+    public String getInitParameter(String name) {
+
+        return (String)initParams.get(name);
+
+    }
+
+
+    /**
+     * Return the set of initialization parameter names defined for this
+     * servlet.  If none are defined, an empty Enumeration is returned.
+     */
+    public Enumeration getInitParameterNames() {
+
+        synchronized (initParams) {
+            return (new Enumerator(initParams.keySet()));
+        }
+
+    }
+
+
+    /**
+     * Return the servlet context with which this servlet is associated.
+     */
+    public ServletContext getServletContext() {
+        return ctx;
+    }
+
+
+    /**
+     * Return the name of this servlet.
+     */
+    public String getServletName() {
+        return servletName;
+    }
+
+//    public long getProcessingTime() {
+//        return swValve.getProcessingTime();
+//    }
+//
+//    public void setProcessingTime(long processingTime) {
+//        swValve.setProcessingTime(processingTime);
+//    }
+//
+//    public long getMaxTime() {
+//        return swValve.getMaxTime();
+//    }
+//
+//    public void setMaxTime(long maxTime) {
+//        swValve.setMaxTime(maxTime);
+//    }
+//
+//    public long getMinTime() {
+//        return swValve.getMinTime();
+//    }
+//
+//    public void setMinTime(long minTime) {
+//        swValve.setMinTime(minTime);
+//    }
+//
+//    public int getRequestCount() {
+//        return swValve.getRequestCount();
+//    }
+//
+//    public void setRequestCount(int requestCount) {
+//        swValve.setRequestCount(requestCount);
+//    }
+//
+//    public int getErrorCount() {
+//        return swValve.getErrorCount();
+//    }
+//
+//    public void setErrorCount(int errorCount) {
+//           swValve.setErrorCount(errorCount);
+//    }
+//
+//    /**
+//     * Increment the error count used for monitoring.
+//     */
+//    public void incrementErrorCount(){
+//        swValve.setErrorCount(swValve.getErrorCount() + 1);
+//    }
+//
+//    public long getLoadTime() {
+//        return loadTime;
+//    }
+//
+//    public void setLoadTime(long loadTime) {
+//        this.loadTime = loadTime;
+//    }
+//
+//    public int getClassLoadTime() {
+//        return classLoadTime;
+//    }
+
+    // -------------------------------------------------------- Package Methods
+
+
+    // -------------------------------------------------------- Private Methods
+
+    private Method[] getAllDeclaredMethods(Class c) {
+
+        if (c.equals(javax.servlet.http.HttpServlet.class)) {
+            return null;
+        }
+
+        Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
+
+        Method[] thisMethods = c.getDeclaredMethods();
+        if (thisMethods == null) {
+            return parentMethods;
+        }
+
+        if ((parentMethods != null) && (parentMethods.length > 0)) {
+            Method[] allMethods =
+                new Method[parentMethods.length + thisMethods.length];
+           System.arraycopy(parentMethods, 0, allMethods, 0,
+                             parentMethods.length);
+           System.arraycopy(thisMethods, 0, allMethods, parentMethods.length,
+                             thisMethods.length);
+
+           thisMethods = allMethods;
+       }
+
+       return thisMethods;
+    }
+
+    /** Specify the instance. Avoids the class lookup, disables unloading.
+     *  Use for embedded case, or to control the allocation.
+     * 
+     * @param servlet
+     */
+    public void setServlet(Servlet servlet) {
+        instance = servlet;
+        ctx.getObjectManager().bind("Servlet:" +
+                ctx.getContextPath() + ":" + getServletName(), 
+                this);
+    }
+
+    public String getSecurityRoleRef(String role) {
+        return (String)securityRoleRef.get(role);
+    }
+    
+    public void setSecurityRoleRef(Map securityRoles) {
+      this.securityRoleRef = securityRoles;
+    }
+
+    public void setConfig(Map initParams) {
+      this.initParams = initParams;
+    }
+
+    public void setLoadOnStartup(int loadOnStartup) {
+      this.loadOnStartup = loadOnStartup;
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletContextConfig.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletContextConfig.java
new file mode 100644 (file)
index 0000000..bbb43b8
--- /dev/null
@@ -0,0 +1,131 @@
+/**
+ * 
+ */
+package org.apache.tomcat.lite;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * All the data in web.xml should be represented here.
+ * 
+ * Public fields to make it easy to access it. 
+ * Naming should match the web.xml element name.
+ * 
+ * @author Costin Manolache
+ */
+public class ServletContextConfig  implements Serializable {
+    
+    private static final long serialVersionUID = 1728492145981883124L;
+    
+    public String fileName;
+    public long timestamp;
+
+    public boolean full;
+    
+    public String displayName;
+
+    public HashMap<String, String> contextParam = new HashMap<String, String>();
+    
+    public HashMap<String, String> mimeMapping = new HashMap<String, String>(); // extension -> mime-type
+    
+    public ArrayList<String> listenerClass = new ArrayList<String>();
+    
+    public ArrayList<String> welcomeFileList = new ArrayList<String>();
+    
+    // code -> location 
+    public HashMap<String, String> errorPageCode= new HashMap<String, String>(); 
+    
+    // exception -> location
+    public HashMap<String, String> errorPageException= new HashMap<String, String>(); 
+
+    public HashMap<String, String> localeEncodingMapping= new HashMap<String, String>(); // locale -> encoding
+    
+    // public HashMap tagLibs; // uri->location
+    // jsp-property-group
+
+    // securityConstraint
+    public ArrayList<SecurityConstraintData> securityConstraint = new ArrayList<SecurityConstraintData>();
+    
+    // loginConfig
+    public String authMethod;
+    public String realmName;
+    public String formLoginPage;
+    public String formErrorPage;
+    
+    public ArrayList<String> securityRole = new ArrayList<String>();
+    
+    // envEntry
+    public ArrayList<EnvEntryData> envEntry = new ArrayList<EnvEntryData>();
+    
+    // ejbRef
+    // ejbLocalRef
+    // serviceRef
+    // resourceRef
+    // resourceEnvRef
+    // message-destination
+    // message-destinationRef
+    public HashMap<String, FilterData> filters = new HashMap<String, FilterData>();
+    public HashMap<String, ServletData> servlets = new HashMap<String, ServletData>();
+
+    public int sessionTimeout;
+    public boolean distributable;
+    
+    public HashMap<String, String> servletMapping = new HashMap<String, String>(); // url -> servlet
+    public ArrayList<FilterMappingData> filterMappings = new ArrayList<FilterMappingData>();
+    
+
+    public static class FilterData implements Serializable {
+        private static final long serialVersionUID = -535820271746973166L;
+
+        public HashMap<String, String> initParams = new HashMap<String, String>();
+        public String filterClass;
+        public String filterName;
+    }
+    
+    public static class FilterMappingData implements Serializable {
+        private static final long serialVersionUID = -4533568066713041994L;
+        public String filterName;
+        public String urlPattern;
+        public String servletName;
+        public ArrayList<String> dispatcher = new ArrayList<String>();
+    }
+    
+    public static class EnvEntryData  implements Serializable {
+        private static final long serialVersionUID = 7023847615343715257L;
+        public String envEntryName;
+        public String envEntryType;
+        public String envEntryValue;
+    }
+    
+    public static class ServletData implements Serializable {
+        private static final long serialVersionUID = -3216904178501185930L;
+
+        public ServletData() {
+        }
+        public ServletData(String servletName, String servletClass) {
+            this.servletClass = servletClass;
+            this.servletName = servletName;
+        }
+
+        public HashMap<String, String> initParams = new HashMap<String, String>();
+        public String servletName;
+        public String servletClass;
+        public String jspFile;
+        public int loadOnStartup = -1;
+        public String runAs;
+        public HashMap<String, String> securityRoleRef = new HashMap<String, String>(); // roleName -> [roleLink]
+        
+    }
+    
+    public static class SecurityConstraintData  implements Serializable {
+        private static final long serialVersionUID = -4780214921810871769L;
+
+        public ArrayList<String> roleName = new ArrayList<String>(); //   auth-constraint/role
+
+        public ArrayList<String> webResourceCollection = new ArrayList<String>();
+        public String transportGuarantee;
+        
+    }    
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletContextImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletContextImpl.java
new file mode 100644 (file)
index 0000000..0484c07
--- /dev/null
@@ -0,0 +1,1428 @@
+/*
+ * 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.tomcat.lite;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+
+import org.apache.tomcat.addons.UserSessionManager;
+import org.apache.tomcat.integration.ObjectManager;
+import org.apache.tomcat.lite.ServletContextConfig.FilterData;
+import org.apache.tomcat.lite.ServletContextConfig.FilterMappingData;
+import org.apache.tomcat.lite.ServletContextConfig.ServletData;
+import org.apache.tomcat.servlets.util.Enumerator;
+import org.apache.tomcat.servlets.util.RequestUtil;
+import org.apache.tomcat.servlets.util.UrlUtils;
+import org.apache.tomcat.util.http.MimeMap;
+
+
+/**
+ * Context - initialized from web.xml or using APIs.
+ * 
+ * Initialization order:
+ * 
+ *  - add all listeners
+ *  - add all filters
+ *  - add all servlets
+ *  
+ *  - session parameters
+ *  - 
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Revision: 377994 $ $Date: 2006-02-15 04:37:28 -0800 (Wed, 15 Feb 2006) $
+ */
+
+public class ServletContextImpl implements ServletContext {
+    
+    /**
+     * Empty collection to serve as the basis for empty enumerations.
+     */
+    private transient static final ArrayList empty = new ArrayList();
+    
+    transient Logger log;
+    
+    /**
+     * Base path - the directory root of the webapp
+     */
+    protected String basePath = null;
+
+    protected String contextPath;
+
+    // All config from web.xml
+    protected ServletContextConfig contextConfig = new ServletContextConfig();
+
+    MimeMap contentTypes = new MimeMap();
+
+    /**
+     * The context attributes for this context.
+     */
+    protected transient Map<String, Object> attributes = new HashMap<String, Object>();
+
+    /**
+     * List of read only attributes for this context.
+     * In catalina - used to protect workdir att. We trust the app, so no need
+     * for extra complexity.
+     */
+    //protected transient HashMap readOnlyAttributes = new HashMap();
+
+    protected transient ArrayList<EventListener> lifecycleListeners = new ArrayList();
+
+    protected UserSessionManager manager;
+    
+    HashMap<String, FilterConfigImpl> filters = new HashMap<String, FilterConfigImpl>();
+
+    HashMap<String, ServletConfigImpl> servlets = new HashMap<String, ServletConfigImpl>();
+
+    ArrayList<String> securityRoles = new ArrayList<String>();
+
+    /** Mapper for filters.
+     */
+    protected WebappFilterMapper webappFilterMapper;
+    
+    /** Internal mapper for request dispatcher, must have all 
+     *  context mappings. 
+     */ 
+    protected WebappServletMapper mapper;
+    
+    transient Locale2Charset charsetMapper = new Locale2Charset();
+
+    transient TomcatLite facade;
+    
+    ObjectManager om;
+
+    private String hostname;
+
+    // ------------------------------------------------- ServletContext Methods
+    public ServletContextImpl() {
+    }
+
+    public void setTomcat(TomcatLite facade) {
+        this.facade = facade;
+    }
+    
+    /**
+     * Registry/framework interface associated with the context.
+     * Also available as a context attribute.
+     * @return
+     */
+    public ObjectManager getObjectManager() {
+        if (om == null) {
+            om = facade.getObjectManager();
+        }
+        return om;
+    }
+    
+    public void setObjectManager(ObjectManager om) {
+        this.om = om;
+    }
+    
+    public Locale2Charset getCharsetMapper() {
+        return charsetMapper;
+    }
+
+    /**
+     * Set the context path, starting with "/" - "/" for ROOT
+     * @param path
+     */
+    public void setContextPath(String path) {
+        this.contextPath = path;
+        log = Logger.getLogger("webapp" + path.replace('/', '.'));
+    }
+    
+    public void setHostname(String hostname) {
+        this.hostname = hostname;
+    }
+    
+    public String getHostname() {
+        return hostname;
+    }
+    
+    /** The directory where this app is based. May be null.
+     * 
+     * @param basePath
+     */
+    public void setBasePath(String basePath) {
+        this.basePath = basePath;        
+    }
+
+    public ServletContextConfig getContextConfig() {
+        return contextConfig;
+    }
+    
+    /** The directory where this app is based.
+     * 
+     * @param basePath
+     */
+    public String getBasePath() {
+        return basePath;
+    }
+
+    public String getEncodedPath() {
+        return null;
+    }
+
+
+    public boolean getCookies() {
+        return false;
+    }
+
+
+    public ServletContext getServletContext() {
+        return this;
+    }
+
+    public List<EventListener> getListeners() {
+        return lifecycleListeners;
+    }
+    
+    public void addListener(EventListener listener) {
+      lifecycleListeners.add(listener);
+    }
+
+    public void removeListener(EventListener listener) {
+      lifecycleListeners.remove(listener);
+    }
+
+
+
+    public Logger getLogger() {
+        return log;
+    }
+
+    public long getUnloadDelay() {
+        return 0;
+    }
+
+    public ServletConfigImpl getServletConfig(String jsp_servlet_name) {
+        return (ServletConfigImpl)servlets.get(jsp_servlet_name);
+    }
+    
+    public Map getServletConfigs() {
+        return servlets;
+    }
+
+    /**
+     *  Add a servlet to the context.
+     *  Called from processWebAppData()
+     * 
+     * @param servletConfig
+     */
+    public void addServletConfig(ServletConfigImpl servletConfig) {
+        servlets.put(servletConfig.getServletName(), servletConfig);
+    }
+    
+    public boolean getPrivileged() {
+        return false;
+    }
+
+    
+    public Map getFilters() {
+        return filters;
+    }
+    
+
+    protected boolean getCrossContext() {
+        return true;
+    }
+    
+    public void addMimeType(String ext, String type) {
+        contentTypes.addContentType(ext, type);
+    }
+
+    public WebappServletMapper getMapper() {
+        if (mapper == null) {
+            Object customMapper = getObjectManager().get(WebappServletMapper.class);
+            if (customMapper == null) {
+                mapper = new WebappServletMapper();
+            } else {
+                mapper = (WebappServletMapper) customMapper;
+            }
+            mapper.setServletContext(this);
+        }
+
+        return mapper;
+    }
+    
+    public WebappFilterMapper getFilterMapper() {
+        if (webappFilterMapper == null) {
+            Object customMapper = getObjectManager().get(WebappFilterMapper.class);
+            if (customMapper == null) {
+                webappFilterMapper = new WebappFilterMapper();
+            } else {
+                webappFilterMapper = (WebappFilterMapper) customMapper;
+            }
+            webappFilterMapper.setServletContext(this);
+        }
+
+        return webappFilterMapper ;
+    }
+    
+    public FilterConfigImpl getFilter(String name) {
+        return (FilterConfigImpl)filters.get(name);
+    }
+
+    /**
+     * Return the value of the specified context attribute, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param name Name of the context attribute to return
+     */
+    public Object getAttribute(String name) {
+        if ("ObjectManager".equals(name)) {
+            return om;
+        }
+        if ("context-listeners".equals(name)) {
+            return lifecycleListeners;
+        }
+        return (attributes.get(name));
+    }
+
+    /**
+     * Return an enumeration of the names of the context attributes
+     * associated with this context.
+     */
+    public Enumeration getAttributeNames() {
+        return new Enumerator(attributes.keySet(), true);
+    }
+
+
+    public void addSecurityRole(String role) {
+        securityRoles.add(role);
+    }
+    
+    public List getSecurityRoles() {
+        return securityRoles;
+    }
+
+    /**
+     * Return a <code>ServletContext</code> object that corresponds to a
+     * specified URI on the server.  This method allows servlets to gain
+     * access to the context for various parts of the server, and as needed
+     * obtain <code>RequestDispatcher</code> objects or resources from the
+     * context.  The given path must be absolute (beginning with a "/"),
+     * and is interpreted based on our virtual host's document root.
+     *
+     * @param uri Absolute URI of a resource on the server
+     */
+    public ServletContext getContext(String uri) {
+        // TODO: support real uri ( http://host/path )
+        // Validate the format of the specified argument
+        if ((uri == null) || (!uri.startsWith("/")))
+            return (null);
+
+        ServletContextImpl child = null;
+        try {
+            child = facade.getContext(this, uri);
+        } catch (IOException e) {
+        } catch (ServletException e) {
+        }
+
+        if (child == null)
+            return (null);
+
+        if (this.getCrossContext()) {
+            // If crossContext is enabled, can always return the context
+            return child.getServletContext();
+        } else if (child == this) {
+            // Can still return the current context
+            return this.getServletContext();
+        } else {
+            // Nothing to return
+            return (null);
+        }
+    }
+
+    
+    /**
+     * Return the main path associated with this context.
+     */
+    public String getContextPath() {
+        return contextPath;
+    }
+    
+
+    /**
+     * Return the value of the specified initialization parameter, or
+     * <code>null</code> if this parameter does not exist.
+     *
+     * @param name Name of the initialization parameter to retrieve
+     */
+    public String getInitParameter(final String name) {
+        return ((String) contextConfig.contextParam.get(name));
+    }
+
+
+    /**
+     * Return the names of the context's initialization parameters, or an
+     * empty enumeration if the context has no initialization parameters.
+     */
+    public Enumeration getInitParameterNames() {
+        return (new Enumerator(contextConfig.contextParam.keySet()));
+    }
+
+    public void setContextParams(Map newParams) {
+      contextConfig.contextParam = (HashMap) newParams;
+    }
+
+    /**
+     * Return the major version of the Java Servlet API that we implement.
+     */
+    public int getMajorVersion() {
+        return 2;
+    }
+
+
+    /**
+     * Return the minor version of the Java Servlet API that we implement.
+     */
+    public int getMinorVersion() {
+        return 4;
+    }
+
+
+    /**
+     * Return the MIME type of the specified file, or <code>null</code> if
+     * the MIME type cannot be determined.
+     *
+     * @param file Filename for which to identify a MIME type
+     */
+    public String getMimeType(String file) {
+        return contentTypes.getMimeType(file);
+    }
+
+    /**
+     * Return the real path for a given virtual path, if possible; otherwise
+     * return <code>null</code>.
+     *
+     * @param path The path to the desired resource
+     */
+    public String getRealPath(String path) {
+        if (path == null) {
+            return null;
+        }
+
+        File file = new File(basePath, path);
+        return (file.getAbsolutePath());
+    }
+
+    /**
+     * Return a <code>RequestDispatcher</code> object that acts as a
+     * wrapper for the named servlet.
+     *
+     * @param name Name of the servlet for which a dispatcher is requested
+     */
+    public RequestDispatcher getNamedDispatcher(String name) {
+        if (name == null) return null;
+        ServletConfigImpl wrapper = 
+            (ServletConfigImpl) this.getServletConfig(name);
+        if (wrapper == null) return null;
+        
+        return new RequestDispatcherImpl(wrapper, name);
+    }
+
+
+    /**
+     * Return a <code>RequestDispatcher</code> instance that acts as a
+     * wrapper for the resource at the given path.  The path must begin
+     * with a "/" and is interpreted as relative to the current context root.
+     *
+     * @param path The path to the desired resource.
+     */
+    public RequestDispatcher getRequestDispatcher(String path) {
+        if (path == null) return null;
+        
+        if (!path.startsWith("/"))
+            throw new IllegalArgumentException(path);
+
+        path = UrlUtils.normalize(path);
+        if (path == null)  return (null);
+
+        
+        return new RequestDispatcherImpl(this, path);
+    }
+
+    public RequestDispatcher getRequestDispatcher(String path, 
+                                                  int type,
+                                                  String dispatcherPath) {
+        RequestDispatcher dispatcher = getRequestDispatcher(path);
+        //((RequestDispatcherImpl)dispatcher);
+        return dispatcher;
+    }
+
+    ThreadLocal requestDispatcherStack = new ThreadLocal();
+
+    protected ClassLoader classLoader;
+    
+//    protected RequestDispatcherImpl getRequestDispatcher() {
+//        ArrayList/*<RequestDispatcherImpl>*/ list = 
+//            (ArrayList)requestDispatcherStack.get();
+//        if (list == null) {
+//            list = new ArrayList();
+//            requestDispatcherStack.set(list);
+//        }
+//        
+//        
+//        return null;
+//    }
+
+    public void resetDispatcherStack() {
+        
+    }
+    
+    /**
+     * Return the URL to the resource that is mapped to a specified path.
+     * The path must begin with a "/" and is interpreted as relative to the
+     * current context root.
+     *
+     * @param path The path to the desired resource
+     *
+     * @exception MalformedURLException if the path is not given
+     *  in the correct form
+     */
+    public URL getResource(String path)
+        throws MalformedURLException {
+
+        if (path == null || !path.startsWith("/")) {
+            throw new MalformedURLException("getResource() " + path);
+        }
+        
+        path = UrlUtils.normalize(path);
+        if (path == null)
+            return (null);
+
+        String libPath = "/WEB-INF/lib/";
+        if ((path.startsWith(libPath)) && (path.endsWith(".jar"))) {
+            File jarFile = null;
+            jarFile = new File(basePath, path);
+            if (jarFile.exists()) {
+                return jarFile.toURL();
+            } else {
+                return null;
+            }
+        } else {
+            File resFile = new File(basePath + path);
+            if (resFile.exists()) {
+                return resFile.toURL();
+            }
+        }
+
+        return (null);
+
+    }
+
+    /**
+     * Return the requested resource as an <code>InputStream</code>.  The
+     * path must be specified according to the rules described under
+     * <code>getResource</code>.  If no such resource can be identified,
+     * return <code>null</code>.
+     *
+     * @param path The path to the desired resource.
+     */
+    public InputStream getResourceAsStream(String path) {
+
+        path = UrlUtils.normalize(path);
+        if (path == null)
+            return (null);
+
+        File resFile = new File(basePath + path);
+        if (!resFile.exists()) 
+            return null;
+        
+        try {
+            return new FileInputStream(resFile);
+        } catch (FileNotFoundException e) {
+            return null;
+        }
+
+    }
+
+
+    /**
+     * Return a Set containing the resource paths of resources member of the
+     * specified collection. Each path will be a String starting with
+     * a "/" character. The returned set is immutable.
+     *
+     * @param path Collection path
+     */
+    public Set getResourcePaths(String path) {
+
+        // Validate the path argument
+        if (path == null) {
+            return null;
+        }
+        if (!path.startsWith("/")) {
+            throw new IllegalArgumentException("getResourcePaths() " + path);
+        }
+
+        path = UrlUtils.normalize(path);
+        if (path == null)
+            return (null);
+
+        File f = new File(basePath + path);
+        File[] files = f.listFiles();
+        if (files == null) return null;
+        if (!path.endsWith("/")) {
+          path = path + "/";
+        }
+        
+        HashSet result = new HashSet();
+        for (int i=0; i < files.length; i++) {
+            if (files[i].isDirectory() ) {
+                result.add(path + files[i].getName() + "/");
+            } else {
+                result.add(path + files[i].getName());
+            }
+        }
+        return result;
+    }
+
+
+
+    /**
+     * Return the name and version of the servlet container.
+     */
+    public String getServerInfo() {
+        return "Apache Tomcat Lite";
+    }
+
+    /**
+     * @deprecated As of Java Servlet API 2.1, with no direct replacement.
+     */
+    public Servlet getServlet(String name) {
+        return (null);
+    }
+
+
+    /**
+     * Return the display name of this web application.
+     */
+    public String getServletContextName() {
+        return contextConfig.displayName;
+    }
+
+
+    /**
+     * @deprecated As of Java Servlet API 2.1, with no direct replacement.
+     */
+    public Enumeration getServletNames() {
+        return (new Enumerator(empty));
+    }
+
+
+    /**
+     * @deprecated As of Java Servlet API 2.1, with no direct replacement.
+     */
+    public Enumeration getServlets() {
+        return (new Enumerator(empty));
+    }
+
+
+    /**
+     * Writes the specified message to a servlet log file.
+     *
+     * @param message Message to be written
+     */
+    public void log(String message) {
+        this.getLogger().info(message);
+    }
+
+
+    /**
+     * Writes the specified exception and message to a servlet log file.
+     *
+     * @param exception Exception to be reported
+     * @param message Message to be written
+     *
+     * @deprecated As of Java Servlet API 2.1, use
+     *  <code>log(String, Throwable)</code> instead
+     */
+    public void log(Exception exception, String message) {
+        this.getLogger().log(Level.INFO, message, exception);
+    }
+
+
+    /**
+     * Writes the specified message and exception to a servlet log file.
+     *
+     * @param message Message to be written
+     * @param throwable Exception to be reported
+     */
+    public void log(String message, Throwable throwable) {
+        this.getLogger().log(Level.INFO, message, throwable);
+    }
+
+    /**
+     * Remove the context attribute with the specified name, if any.
+     *
+     * @param name Name of the context attribute to be removed
+     */
+    public void removeAttribute(String name) {
+
+        Object value = null;
+        boolean found = false;
+
+        // Remove the specified attribute
+        // Check for read only attribute
+        found = attributes.containsKey(name);
+        if (found) {
+            value = attributes.get(name);
+            attributes.remove(name);
+        } else {
+            return;
+        }
+
+        // Notify interested application event listeners
+        List listeners = this.getListeners();
+        if (listeners.size() == 0)
+            return;
+        ServletContextAttributeEvent event = null;
+        for (int i = 0; i < listeners.size(); i++) {
+            if (!(listeners.get(i) instanceof ServletContextAttributeListener))
+                continue;
+            ServletContextAttributeListener listener =
+                (ServletContextAttributeListener) listeners.get(i);
+            try {
+                if (event == null) {
+                    event = new ServletContextAttributeEvent(this.getServletContext(),
+                            name, value);
+
+                }
+                listener.attributeRemoved(event);
+            } catch (Throwable t) {
+                // FIXME - should we do anything besides log these?
+                log("ServletContextAttributeListener", t);
+            }
+        }
+    }
+
+
+    /**
+     * Bind the specified value with the specified context attribute name,
+     * replacing any existing value for that name.
+     *
+     * @param name Attribute name to be bound
+     * @param value New attribute value to be bound
+     */
+    public void setAttribute(String name, Object value) {
+        // Name cannot be null
+        if (name == null)
+            throw new IllegalArgumentException
+                ("name == null");
+
+        // Null value is the same as removeAttribute()
+        if (value == null) {
+            removeAttribute(name);
+            return;
+        }
+
+        Object oldValue = null;
+        boolean replaced = false;
+
+        // Add or replace the specified attribute
+        synchronized (attributes) {
+            // Check for read only attribute
+            oldValue = attributes.get(name);
+            if (oldValue != null)
+                replaced = true;
+            attributes.put(name, value);
+        }
+
+        // Notify interested application event listeners
+        List listeners = this.getListeners();
+        if (listeners.size() == 0)
+            return;
+        ServletContextAttributeEvent event = null;
+        for (int i = 0; i < listeners.size(); i++) {
+            if (!(listeners.get(i) instanceof ServletContextAttributeListener))
+                continue;
+            ServletContextAttributeListener listener =
+                (ServletContextAttributeListener) listeners.get(i);
+            try {
+                if (event == null) {
+                    if (replaced)
+                        event =
+                            new ServletContextAttributeEvent(this.getServletContext(),
+                                                             name, oldValue);
+                    else
+                        event =
+                            new ServletContextAttributeEvent(this.getServletContext(),
+                                                             name, value);
+                    
+                }
+                if (replaced) {
+                    listener.attributeReplaced(event);
+                } else {
+                    listener.attributeAdded(event);
+                }
+            } catch (Throwable t) {
+                // FIXME - should we do anything besides log these?
+                log("ServletContextAttributeListener error", t);
+            }
+        }
+
+    }
+
+    /**
+     * Clear all application-created attributes.
+     */
+    void clearAttributes() {
+        // Create list of attributes to be removed
+        ArrayList list = new ArrayList();
+        synchronized (attributes) {
+            Iterator iter = attributes.keySet().iterator();
+            while (iter.hasNext()) {
+                list.add(iter.next());
+            }
+        }
+
+        // Remove application originated attributes
+        // (read only attributes will be left in place)
+        Iterator keys = list.iterator();
+        while (keys.hasNext()) {
+            String key = (String) keys.next();
+            removeAttribute(key);
+        }
+    }
+    
+    public void addFilter(String filterName, String filterClass, 
+                          Map params) {
+        FilterConfigImpl fc = new FilterConfigImpl(this);
+        fc.setData(filterName, filterClass, params);
+        filters.put(filterName, fc);
+    }
+    
+    public void initFilters() throws ServletException {
+        Iterator fI = getFilters().values().iterator();
+        while (fI.hasNext()) {
+            FilterConfigImpl fc = (FilterConfigImpl)fI.next();
+            try {
+                fc.getFilter(); // will triger init()
+            } catch (Throwable e) {
+                log.log(Level.WARNING, getContextPath() + " Filter.init() " + 
+                        fc.getFilterName(), e);
+            } 
+            
+        }
+    }
+    public void initServlets() throws ServletException {
+        Iterator fI = getServletConfigs().values().iterator();
+        Map/*<Integer, List<ServletConfigImpl>>*/ onStartup = 
+            new TreeMap/*<Integer, List<ServletConfigImpl>>*/();
+        while (fI.hasNext()) {
+            ServletConfigImpl fc = (ServletConfigImpl)fI.next();
+            if (fc.getLoadOnStartup() > 0 ) {
+                Integer i = new Integer(fc.getLoadOnStartup());
+                List/*<ServletConfigImpl>*/ old = (List)onStartup.get(i);
+                if (old == null) {
+                    old = new ArrayList/*<ServletConfigImpl>*/();
+                    onStartup.put(i, old);
+                }
+                old.add(fc);
+            }
+        }
+        Iterator keys = onStartup.keySet().iterator();
+        while (keys.hasNext()) {
+            Integer key = (Integer)keys.next();
+            List/*<ServletConfigImpl>*/ servlets = (List)onStartup.get(key); 
+            Iterator servletsI = servlets.iterator();
+            while (servletsI.hasNext()) {
+                ServletConfigImpl fc = (ServletConfigImpl) servletsI.next();
+                try {
+                    fc.loadServlet(); 
+                } catch (Throwable e) {
+                    log.log(Level.WARNING, "Error initializing  " + fc.getServletName(), e);
+                } 
+            }
+        }
+    }
+
+    public void initListeners() throws ServletException {
+        Iterator fI = contextConfig.listenerClass.iterator();
+        while (fI.hasNext()) {
+            String listenerClass = (String)fI.next();
+            try {
+                Object l = 
+                    getClassLoader().loadClass(listenerClass).newInstance();
+                lifecycleListeners.add((EventListener) l);
+            } catch (Throwable e) {
+                log.log(Level.WARNING, "Error initializing listener " + listenerClass, e);
+            } 
+        }
+    }
+
+    public ClassLoader getClassLoader() {
+        return classLoader;
+    }
+    
+    public void addMapping(String path, String name) {
+      ServletConfigImpl wrapper = getServletConfig(name);
+      addMapping(path, wrapper);
+    }
+
+    public void addMapping(String path, ServletConfig wrapper) {
+        getMapper().addWrapper(getMapper().contextMapElement, path, wrapper);
+    }
+    
+    public void setWelcomeFiles(String[] name) {
+      getMapper().contextMapElement.welcomeResources = name;
+    }
+
+    public String[] getWelcomeFiles() {
+      return getMapper().contextMapElement.welcomeResources;
+    }
+
+    public void setSessionTimeout(int to) {
+      getManager().setSessionTimeout(to);      
+    }
+    
+    /**
+     * Initialize the context from the parsed config.
+     * 
+     * Note that WebAppData is serializable.
+     */
+    public void processWebAppData(ServletContextConfig d) throws ServletException {
+        this.contextConfig = d;
+        
+        Iterator i1 = d.mimeMapping.entrySet().iterator();
+        while (i1.hasNext()) {
+            Entry k = (Entry)i1.next();
+            addMimeType((String)k.getKey(), (String)k.getValue());
+        }
+        
+        String[] wFiles = (String[])d.welcomeFileList.toArray(new String[0]);
+        if (wFiles.length == 0) {
+            wFiles = new String[] {"index.html" };
+        }
+        if (basePath != null) {
+          getMapper().contextMapElement.resources = new File(getBasePath());
+        }
+        setWelcomeFiles(wFiles);
+        
+        Iterator i2 = d.filters.values().iterator();
+        while (i2.hasNext()) {
+            FilterData fd = (FilterData)i2.next();
+            addFilter(fd.filterName, fd.filterClass, fd.initParams);
+        }
+        
+        Iterator i3 = d.servlets.values().iterator();
+        while (i3.hasNext()) {
+            ServletData sd = (ServletData) i3.next();
+            // jsp-file 
+            if (sd.servletClass == null) {
+                if (sd.jspFile == null) {
+                    log.log(Level.WARNING, "Missing servlet class for " + sd.servletName);
+                    continue;
+                }
+            }
+             
+            ServletConfigImpl sw = 
+              new ServletConfigImpl(this, sd.servletName, sd.servletClass);
+            sw.setConfig(sd.initParams);
+            sw.setJspFile(sd.jspFile);
+            sw.setLoadOnStartup(sd.loadOnStartup);
+            //sw.setRunAs(sd.runAs);
+            sw.setSecurityRoleRef(sd.securityRoleRef);
+            
+            addServletConfig(sw);
+        }
+        
+        Iterator i4 = d.servletMapping.entrySet().iterator();
+        while (i4.hasNext()) {
+            Entry/*<String, String>*/ k = (Entry) i4.next();
+            addMapping((String) k.getKey(), (String) k.getValue());
+        }
+        
+        Iterator i5 = d.filterMappings.iterator();
+        while (i5.hasNext()) {
+            FilterMappingData k = (FilterMappingData) i5.next();  
+            String[] disp = new String[k.dispatcher.size()];
+            if (k.urlPattern != null) {
+              addFilterMapping(k.urlPattern, 
+                  k.filterName, 
+                  (String[])k.dispatcher.toArray(disp));
+            }
+            if (k.servletName != null) {
+              addFilterServletMapping(k.servletName, 
+                  k.filterName, 
+                  (String[])k.dispatcher.toArray(disp));
+            }
+         }
+        
+        for (String n: d.localeEncodingMapping.keySet()) {
+            getCharsetMapper().addCharsetMapping(n, 
+                    d.localeEncodingMapping.get(n));
+        }
+    }
+    
+    public void addServlet(String servletName, String servletClass, 
+                           String jspFile, Map params) {
+      ServletConfigImpl sc = new ServletConfigImpl(this, servletName, 
+          servletClass);
+      sc.setJspFile(jspFile);
+      sc.setConfig(params);
+      addServletConfig(sc);
+    }
+    
+    public void addServlet(String servletName, Servlet servlet) {
+      ServletConfigImpl sc = new ServletConfigImpl(this, servletName, null);
+      sc.setServlet(servlet);
+      addServletConfig(sc);
+    }
+    
+    public void addServletSec(String serlvetName, String runAs, Map roles) {
+      // TODO
+    }
+    
+    
+    
+    public void addFilterMapping(String path, String filterName, 
+                                 String[] dispatcher) {
+      getFilterMapper().addMapping(filterName, 
+          path, null, dispatcher);
+      
+    }
+
+    public void addFilterServletMapping(String servlet, 
+                                        String filterName, 
+                                        String[] dispatcher) {
+      getFilterMapper().addMapping(filterName, 
+          null, servlet, 
+          dispatcher);      
+    }
+    
+    boolean initDone = false;
+    
+    /**
+     * Called from TomcatLite.init(), required before start.
+     * 
+     * Will initialize defaults and load web.xml unless webAppData is 
+     * already set and recent. No other processing is done except reading
+     * the config - you can add or alter it before start() is called.
+     * 
+     * @throws ServletException
+     */
+    public void init() throws ServletException {
+        if (initDone) {
+            return;
+        }
+        initDone = true;
+        // Load global init params from the facade
+        initEngineDefaults();
+        
+        initTempDir();
+        
+
+        // Merge in web.xml - or other config source ( programmatic, etc )
+        ContextPreinitListener cfg = 
+            (ContextPreinitListener) getObjectManager().get( 
+                ContextPreinitListener.class);
+        if (cfg != null) {
+            cfg.preInit(this);
+        }
+
+        processWebAppData(contextConfig);
+        
+        // if not defined yet:
+        addDefaultServlets();
+    }
+    
+    
+    protected void initTempDir() throws ServletException {
+        // We need a base path - at least for temp files, req. by spec 
+        if (basePath == null) {
+            basePath = ("/".equals(contextPath)) ?
+                    facade.getWork().getAbsolutePath() + "/ROOT" :
+                    facade.getWork().getAbsolutePath() + contextPath;
+        }
+        
+        File f = new File(basePath + "/WEB-INF/tmp");
+        f.mkdirs();
+        setAttribute("javax.servlet.context.tempdir", f);
+    }
+    
+    /**
+     * Static file handler ( default )
+     * *.jsp support
+     * 
+     */
+    protected void addDefaultServlets() throws ServletException {
+        if (servlets.get("default") == null) {
+            ServletConfigImpl fileS = new ServletConfigImpl(this, 
+                    "default", null); 
+            addServletConfig(fileS);
+            addMapping("/", fileS);
+        }
+        
+        // *.jsp support
+        if (servlets.get("jspwildcard") == null) {
+            ServletConfigImpl fileS = new ServletConfigImpl(this,
+                        "jspwildcard", null);
+            addServletConfig(fileS);
+            addMapping("*.jsp", fileS);
+        }
+    }
+    
+    protected void initEngineDefaults() throws ServletException {
+        
+        // TODO: make this customizable, avoid loading it on startup
+        // Set the class name as default in the addon support
+        for (String sname: facade.ctxDefaultInitParam.keySet()) {
+            String path = facade.ctxDefaultInitParam.get(sname);
+            contextConfig.contextParam.put(sname, path);
+        }
+
+        for (String sname: facade.preloadServlets.keySet()) {
+            String sclass = facade.preloadServlets.get(sname);
+            ServletConfigImpl fileS = new ServletConfigImpl(this, sname, sclass);
+            addServletConfig(fileS);
+        }
+        
+        for (String sname: facade.preloadMappings.keySet()) {
+            String path = facade.preloadMappings.get(sname);
+            ServletConfigImpl servletConfig = getServletConfig(sname);
+            addMapping(path, servletConfig);
+        }
+    }
+
+        
+    public ArrayList getClasspath(File directory, File classesDir) {
+        ArrayList res = new ArrayList();
+        if (classesDir.isDirectory() && classesDir.exists() &&
+                classesDir.canRead()) {
+            try {
+                URL url = classesDir.toURL();
+                res.add(url);
+            } catch (MalformedURLException e) {
+            }
+        }
+        if (!directory.isDirectory() || !directory.exists()
+                || !directory.canRead()) {
+            return res;
+        }
+        
+        File[] jars = directory.listFiles(new FilenameFilter() {
+            public boolean accept(File dir, String name) {
+              return name.endsWith(".jar");
+            }
+          });
+        
+        for (int j = 0; j < jars.length; j++) {
+            try {
+                URL url = jars[j].toURL();
+                res.add(url);
+            } catch (MalformedURLException e) {
+            }
+        }
+        return res;
+    }
+
+
+    public void start() throws ServletException {
+        String base = getBasePath();
+        
+        ArrayList urls = getClasspath(new File(base + "/WEB-INF/lib"),
+                new File(base + "/WEB-INF/classes"));
+        URL[] urlsA = new URL[urls.size()];
+        urls.toArray(urlsA);
+
+        URLClassLoader parentLoader = 
+            getEngine().getContextParentLoader();
+
+        // create a class loader.
+        // TODO: reimplement special 'deploy' dirs
+
+        /*
+          Repository ctxRepo = new Repository();
+          ctxRepo.setParentClassLoader(parentLoader);
+          ctxRepo.addURL(urlsA);
+          repository = ctxRepo;
+        */
+        
+        classLoader = new URLClassLoader(urlsA, parentLoader);
+        
+        // JMX should know about us ( TODO: is it too early ? )
+        facade.notifyAdd(this);
+
+        initListeners();
+        
+        List listeners = this.getListeners();
+        ServletContextEvent event = null;
+        for (int i = 0; i < listeners.size(); i++) {
+            if (!(listeners.get(i) instanceof ServletContextListener))
+                continue;
+            ServletContextListener listener =
+                (ServletContextListener) listeners.get(i);
+            if (event == null) {
+                event = new ServletContextEvent(this);
+            }
+            try {
+                listener.contextInitialized(event);
+            } catch (Throwable t) {
+                log.log(Level.WARNING, "Context.init() contextInitialized() error:", t);
+            }
+        }
+
+        
+        initFilters();
+        initServlets();
+    }
+
+    public UserSessionManager getManager() {
+      if (manager == null) {
+          manager = (UserSessionManager) getObjectManager().get(
+                  UserSessionManager.class);
+          manager.setContext(this);
+          if (contextConfig.sessionTimeout > 0 ) {
+              manager.setSessionTimeout(contextConfig.sessionTimeout);
+          }
+      }
+      return manager;
+    }
+
+
+    // TODO: configurable ? init-params 
+    public String getSessionCookieName() {
+        return "JSESSIONID";
+    }
+    
+
+    
+    public void destroy() throws ServletException {
+        // destroy filters
+        Iterator fI = filters.values().iterator();
+        while(fI.hasNext()) {
+            FilterConfigImpl fc = (FilterConfigImpl) fI.next();
+            try {
+                fc.getFilter().destroy();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        // destroy servlets
+        fI = servlets.values().iterator();
+        while(fI.hasNext()) {
+            ServletConfigImpl fc = (ServletConfigImpl) fI.next();
+            try {
+                fc.unload();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public TomcatLite getEngine() {
+        return facade;
+    }
+    
+    public String findStatusPage(int status) {
+        if (contextConfig.errorPageCode.size() == 0) {
+            return null;
+        }
+        if (status == 200) {
+            return null;
+        }
+
+        return (String) contextConfig.errorPageCode.get(Integer.toString(status));
+    }
+
+    public void handleStatusPage(ServletRequestImpl req, 
+                                 ServletResponseImpl res, 
+                                 int status, 
+                                 String statusPage) {
+       String message = RequestUtil.filter(res.getMessage());
+       if (message == null)
+           message = "";
+       setErrorAttributes(req, status, message);
+       dispatchError(req, res, statusPage);
+   }
+
+   protected void setErrorAttributes(ServletRequestImpl req,
+                                   int status,
+                                   String message) {
+       req.setAttribute("javax.servlet.error.status_code", 
+               new Integer(status));
+       if (req.getWrapper() != null) {
+           req.setAttribute("javax.servlet.error.servlet_name", 
+                   req.getWrapper().servletName);
+       }
+       req.setAttribute("javax.servlet.error.request_uri", 
+               req.getRequestURI());
+       req.setAttribute("javax.servlet.error.message", 
+               message);
+
+   }
+   
+   public void handleError(ServletRequestImpl req, 
+                           ServletResponseImpl res,
+                           Throwable t) {
+       Throwable realError = t;
+       if (realError instanceof ServletException) {
+           realError = ((ServletException) realError).getRootCause();
+           if (realError == null) {
+               realError = t;
+           }
+       }
+      //if (realError instanceof ClientAbortException ) {
+
+       String errorPage = findErrorPage(t);
+       if ((errorPage == null) && (realError != t)) {
+           errorPage = findErrorPage(realError);
+       }
+
+       if (errorPage != null) {
+           setErrorAttributes(req, 500, t.getMessage());
+           req.setAttribute("javax.servlet.error.exception", realError);
+           req.setAttribute("javax.servlet.error.exception_type",
+                   realError.getClass());
+           dispatchError(req, res, errorPage);
+       } else {
+           log("Unhandled error", t);
+           if (t instanceof ServletException && 
+                   ((ServletException)t).getRootCause() != null) {
+               log("RootCause:", ((ServletException)t).getRootCause());
+           }
+           if (res.getStatus() < 500) {
+               res.setStatus(500);
+           }
+       }
+   }
+
+   protected void dispatchError(ServletRequestImpl req, 
+                              ServletResponseImpl res, 
+                              String errorPage) {
+       RequestDispatcher rd =
+           getRequestDispatcher(errorPage);
+       try {
+           // will clean up the buffer 
+           rd.forward(req, res);
+           return; // handled
+       } catch (ServletException e) {
+           // TODO
+       } catch (IOException e) {
+           // TODO
+       }
+   }
+   
+   protected String findErrorPage(Throwable exception) {
+       if (contextConfig.errorPageException.size() == 0) {
+           return null;
+       }
+       if (exception == null)
+           return (null);
+       Class clazz = exception.getClass();
+       String name = clazz.getName();
+       while (!Object.class.equals(clazz)) {
+           String page = (String)contextConfig.errorPageException.get(name);
+           if (page != null)
+               return (page);
+           clazz = clazz.getSuperclass();
+           if (clazz == null)
+               break;
+           name = clazz.getName();
+       }
+       return (null);
+
+   }
+
+   @Override
+   public void addFilter(String filterName, String description, String className,
+                         Map<String, String> initParameters,
+                         boolean isAsyncSupported) {
+   }
+
+   @Override
+   public void addFilterMappingForServletNames(
+                                               String filterName,
+                                               EnumSet<DispatcherType> dispatcherTypes,
+                                               boolean isMatchAfter,
+                                               String... servletNames) {
+   }
+
+   @Override
+   public void addFilterMappingForUrlPatterns(
+                                              String filterName,
+                                              EnumSet<DispatcherType> dispatcherTypes,
+                                              boolean isMatchAfter,
+                                              String... urlPatterns) {
+   }
+
+   @Override
+   public void addServletMapping(String servletName, String[] urlPatterns) {
+   }
+
+   @Override
+   public EnumSet<SessionTrackingMode> getDefaultSessionTrackingModes() {
+       return null;
+   }
+
+   @Override
+   public EnumSet<SessionTrackingMode> getEffectiveSessionTrackingModes() {
+       return null;
+   }
+
+   @Override
+   public SessionCookieConfig getSessionCookieConfig() {
+       return null;
+   }
+
+   @Override
+   public void setSessionCookieConfig(SessionCookieConfig sessionCookieConfig) {
+   }
+
+   @Override
+   public void setSessionTrackingModes(
+                                       EnumSet<SessionTrackingMode> sessionTrackingModes) {
+   }
+
+}
+
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletInputStreamImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletInputStreamImpl.java
new file mode 100644 (file)
index 0000000..5f5d914
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.servlet.ServletInputStream;
+
+
+/**
+ * Wrapper around MessageReader
+ * 
+ * @author Remy Maucherat
+ * @author Jean-Francois Arcand
+ * @author Costin Manolache
+ */
+public class ServletInputStreamImpl extends ServletInputStream {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    protected InputStream ib;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public ServletInputStreamImpl(InputStream ib) {
+        this.ib = ib;
+    }
+
+    // --------------------------------------------- ServletInputStream Methods
+
+    public long skip(long n)
+        throws IOException {
+        return ib.skip(n);
+    }
+    
+    public void mark(int readAheadLimit)
+    {
+        //try {
+        ib.mark(readAheadLimit);
+//        } catch (IOException e) {
+//            e.printStackTrace();
+//        }
+    }
+
+
+    public void reset()
+        throws IOException {
+        ib.reset();
+    }
+    
+
+
+    public int read()
+        throws IOException {    
+        return ib.read();
+    }
+
+    public int available() throws IOException {
+        return ib.available();
+    }
+
+    public int read(final byte[] b) throws IOException {
+        return ib.read(b, 0, b.length);
+    }
+
+
+    public int read(final byte[] b, final int off, final int len)
+        throws IOException {
+            
+        return ib.read(b, off, len);
+    }
+
+
+    public int readLine(byte[] b, int off, int len) throws IOException {
+        return super.readLine(b, off, len);
+    }
+
+    /** 
+     * Close the stream
+     * Since we re-cycle, we can't allow the call to super.close()
+     * which would permantely disable us.
+     */
+    public void close() throws IOException {
+        ib.close();
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletOutputStreamImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletOutputStreamImpl.java
new file mode 100644 (file)
index 0000000..2cd699c
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+import java.io.IOException;
+
+import javax.servlet.ServletOutputStream;
+
+import org.apache.tomcat.lite.coyote.MessageWriter;
+
+/**
+ * Coyote implementation of the servlet output stream.
+ * 
+ * @author Costin Manolache
+ * @author Remy Maucherat
+ */
+public class ServletOutputStreamImpl 
+    extends ServletOutputStream {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    protected MessageWriter ob;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public ServletOutputStreamImpl(MessageWriter ob) {
+        this.ob = ob;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Prevent cloning the facade.
+     */
+    protected Object clone()
+        throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Clear facade.
+     */
+    void clear() {
+        ob = null;
+    }
+
+
+    // --------------------------------------------------- OutputStream Methods
+
+
+    public void write(int i)
+        throws IOException {
+        ob.writeByte(i);
+    }
+
+
+    public void write(byte[] b)
+        throws IOException {
+        write(b, 0, b.length);
+    }
+
+
+    public void write(byte[] b, int off, int len)
+        throws IOException {
+        ob.write(b, off, len);
+    }
+
+
+    /**
+     * Will send the buffer to the client.
+     */
+    public void flush()
+        throws IOException {
+        ob.flush();
+    }
+
+
+    public void close()
+        throws IOException {
+        ob.close();
+    }
+
+
+    // -------------------------------------------- ServletOutputStream Methods
+
+
+    public void print(String s)
+        throws IOException {
+        ob.write(s);
+    }
+
+
+}
+
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletReaderImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletReaderImpl.java
new file mode 100644 (file)
index 0000000..15119e3
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+
+
+
+/**
+ * Coyote implementation of the buffred reader.
+ * 
+ * @author Remy Maucherat
+ */
+public class ServletReaderImpl
+    extends BufferedReader {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    private static final char[] LINE_SEP = { '\r', '\n' };
+    private static final int MAX_LINE_LENGTH = 4096;
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    protected Reader ib;
+
+
+    protected char[] lineBuffer = null;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public ServletReaderImpl(Reader ib) {
+        super(ib, 1);
+        this.ib = ib;
+    }
+    
+    public void close()
+        throws IOException {
+        ib.close();
+    }
+
+
+    public int read()
+        throws IOException {
+        return ib.read();
+    }
+
+
+    public int read(char[] cbuf)
+        throws IOException {
+        return ib.read(cbuf, 0, cbuf.length);
+    }
+
+
+    public int read(char[] cbuf, int off, int len)
+        throws IOException {
+        return ib.read(cbuf, off, len);
+    }
+
+
+    public long skip(long n)
+        throws IOException {
+        return ib.skip(n);
+    }
+
+
+    public boolean ready()
+        throws IOException {
+        return ib.ready();
+    }
+
+
+    public boolean markSupported() {
+        return true;
+    }
+
+
+    public void mark(int readAheadLimit)
+        throws IOException {
+        ib.mark(readAheadLimit);
+    }
+
+
+    public void reset()
+        throws IOException {
+        ib.reset();
+    }
+
+
+    // TODO: move the readLine functionality to base coyote IO
+    public String readLine()
+        throws IOException {
+
+        if (lineBuffer == null) {
+            lineBuffer = new char[MAX_LINE_LENGTH];
+       }
+
+        String result = null;
+
+        int pos = 0;
+        int end = -1;
+        int skip = -1;
+        StringBuffer aggregator = null;
+        while (end < 0) {
+            mark(MAX_LINE_LENGTH);
+            while ((pos < MAX_LINE_LENGTH) && (end < 0)) {
+                int nRead = read(lineBuffer, pos, MAX_LINE_LENGTH - pos);
+                if (nRead < 0) {
+                    if (pos == 0) {
+                        return null;
+                    }
+                    end = pos;
+                    skip = pos;
+                }
+                for (int i = pos; (i < (pos + nRead)) && (end < 0); i++) {
+                    if (lineBuffer[i] == LINE_SEP[0]) {
+                        end = i;
+                        skip = i + 1;
+                        char nextchar;
+                        if (i == (pos + nRead - 1)) {
+                            nextchar = (char) read();
+                        } else {
+                            nextchar = lineBuffer[i+1];
+                        }
+                        if (nextchar == LINE_SEP[1]) {
+                            skip++;
+                        }
+                    } else if (lineBuffer[i] == LINE_SEP[1]) {
+                        end = i;
+                        skip = i + 1;
+                    }
+                }
+                if (nRead > 0) {
+                    pos += nRead;
+                }
+            }
+            if (end < 0) {
+                if (aggregator == null) {
+                    aggregator = new StringBuffer();
+                }
+                aggregator.append(lineBuffer);
+                pos = 0;
+            } else {
+                reset();
+                skip(skip);
+            }
+        }
+
+        if (aggregator == null) {
+            result = new String(lineBuffer, 0, end);
+        } else {
+            aggregator.append(lineBuffer, 0, end);
+            result = aggregator.toString();
+        }
+
+        return result;
+
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletRequestImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletRequestImpl.java
new file mode 100644 (file)
index 0000000..810b89d
--- /dev/null
@@ -0,0 +1,2510 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.TreeMap;
+import java.util.logging.Level;
+
+import javax.security.auth.Subject;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncListener;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.Request;
+import org.apache.tomcat.addons.UserSessionManager;
+import org.apache.tomcat.lite.coyote.MessageReader;
+import org.apache.tomcat.servlets.util.Enumerator;
+import org.apache.tomcat.servlets.util.LocaleParser;
+import org.apache.tomcat.servlets.util.RequestUtil;
+import org.apache.tomcat.util.buf.B2CConverter;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.buf.UriNormalizer;
+import org.apache.tomcat.util.http.Cookies;
+import org.apache.tomcat.util.http.FastHttpDateFormat;
+import org.apache.tomcat.util.http.Parameters;
+import org.apache.tomcat.util.http.ServerCookie;
+import org.apache.tomcat.util.http.mapper.MappingData;
+
+
+/**
+ * Wrapper object for the Coyote request.
+ *
+ * @author Remy Maucherat
+ * @author Craig R. McClanahan
+ * @version $Revision: 325874 $ $Date: 2005-10-17 03:39:15 -0700 (Mon, 17 Oct 2005) $
+ */
+
+public class ServletRequestImpl implements HttpServletRequest {
+
+    /**
+     * The request attribute under which we store the array of X509Certificate
+     * objects representing the certificate chain presented by our client,
+     * if any.
+     */
+    public static final String CERTIFICATES_ATTR =
+        "javax.servlet.request.X509Certificate";
+
+    /**
+     * The request attribute under which we store the name of the cipher suite
+     * being used on an SSL connection (as an object of type
+     * java.lang.String).
+     */
+    public static final String CIPHER_SUITE_ATTR =
+        "javax.servlet.request.cipher_suite";
+
+    /**
+     * Request dispatcher state.
+     */
+    public static final String DISPATCHER_TYPE_ATTR = 
+        "org.apache.catalina.core.DISPATCHER_TYPE";
+
+    /**
+     * Request dispatcher path.
+     */
+    public static final String DISPATCHER_REQUEST_PATH_ATTR = 
+        "org.apache.catalina.core.DISPATCHER_REQUEST_PATH";
+
+    /**
+     * The servlet context attribute under which we store the class path
+     * for our application class loader (as an object of type String),
+     * delimited with the appropriate path delimiter for this platform.
+     */
+    public static final String CLASS_PATH_ATTR =
+        "org.apache.catalina.jsp_classpath";
+
+
+    /**
+     * The request attribute under which we forward a Java exception
+     * (as an object of type Throwable) to an error page.
+     */
+    public static final String EXCEPTION_ATTR =
+        "javax.servlet.error.exception";
+
+
+    /**
+     * The request attribute under which we forward the request URI
+     * (as an object of type String) of the page on which an error occurred.
+     */
+    public static final String EXCEPTION_PAGE_ATTR =
+        "javax.servlet.error.request_uri";
+
+
+    /**
+     * The request attribute under which we forward a Java exception type
+     * (as an object of type Class) to an error page.
+     */
+    public static final String EXCEPTION_TYPE_ATTR =
+        "javax.servlet.error.exception_type";
+
+
+    /**
+     * The request attribute under which we forward an HTTP status message
+     * (as an object of type STring) to an error page.
+     */
+    public static final String ERROR_MESSAGE_ATTR =
+        "javax.servlet.error.message";
+
+
+    /**
+     * The request attribute under which we expose the value of the
+     * <code>&lt;jsp-file&gt;</code> value associated with this servlet,
+     * if any.
+     */
+    public static final String JSP_FILE_ATTR =
+        "org.apache.catalina.jsp_file";
+
+
+    /**
+     * The request attribute under which we store the key size being used for
+     * this SSL connection (as an object of type java.lang.Integer).
+     */
+    public static final String KEY_SIZE_ATTR =
+        "javax.servlet.request.key_size";
+
+    /**
+     * The request attribute under which we store the session id being used
+     * for this SSL connection (as an object of type java.lang.String).
+     */
+    public static final String SSL_SESSION_ID_ATTR =
+        "javax.servlet.request.ssl_session";
+
+    /**
+     * The request attribute under which we forward a servlet name to
+     * an error page.
+     */
+    public static final String SERVLET_NAME_ATTR =
+        "javax.servlet.error.servlet_name";
+
+    
+    /**
+     * The name of the cookie used to pass the session identifier back
+     * and forth with the client.
+     */
+    public static final String SESSION_COOKIE_NAME = "JSESSIONID";
+
+
+    /**
+     * The name of the path parameter used to pass the session identifier
+     * back and forth with the client.
+     */
+    public static final String SESSION_PARAMETER_NAME = "jsessionid";
+
+
+    /**
+     * The request attribute under which we forward an HTTP status code
+     * (as an object of type Integer) to an error page.
+     */
+    public static final String STATUS_CODE_ATTR =
+        "javax.servlet.error.status_code";
+
+
+    /**
+     * The subject under which the AccessControlContext is running.
+     */
+    public static final String SUBJECT_ATTR =
+        "javax.security.auth.subject";
+
+    
+    /**
+     * The servlet context attribute under which we store a temporary
+     * working directory (as an object of type File) for use by servlets
+     * within this web application.
+     */
+    public static final String WORK_DIR_ATTR =
+        "javax.servlet.context.tempdir";
+
+    protected static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");
+
+
+    /**
+     * The default Locale if none are specified.
+     */
+    protected static Locale defaultLocale = Locale.getDefault();
+
+    // ApplicationFilterFactory. What's the use ???
+    private static Integer REQUEST_INTEGER = new Integer(8);
+
+    /**
+     * The match string for identifying a session ID parameter.
+     */
+    private static final String match = ";" + SESSION_PARAMETER_NAME + "=";
+   
+    /**
+     * The set of cookies associated with this Request.
+     */
+    protected Cookie[] cookies = null;
+
+
+    /**
+     * The set of SimpleDateFormat formats to use in getDateHeader().
+     *
+     * Notice that because SimpleDateFormat is not thread-safe, we can't
+     * declare formats[] as a static variable.
+     */
+    protected SimpleDateFormat formats[] = null;
+    
+
+    /**
+     * The attributes associated with this Request, keyed by attribute name.
+     */
+    protected HashMap attributes = new HashMap();
+
+
+    /**
+     * List of read only attributes for this Request.
+     */
+    //private HashMap readOnlyAttributes = new HashMap();
+
+
+    /**
+     * The preferred Locales assocaited with this Request.
+     */
+    protected ArrayList locales = new ArrayList();
+
+
+    /**
+     * Authentication type.
+     */
+    protected String authType = null;
+
+    /**
+     * User principal.
+     */
+    protected Principal userPrincipal = null;
+
+
+    /**
+     * The Subject associated with the current AccessControllerContext
+     */
+    protected transient Subject subject = null;
+
+
+    /**
+     * The current dispatcher type.
+     */
+    protected Object dispatcherType = null;
+
+
+    /**
+     * The associated input buffer.
+     */
+    protected MessageReader inputBuffer = new MessageReader();
+
+
+    /**
+     * ServletInputStream.
+     */
+    protected ServletInputStreamImpl inputStream = 
+        new ServletInputStreamImpl(inputBuffer.asInputStream());
+
+
+    /**
+     * Reader.
+     */
+    protected BufferedReader reader = new ServletReaderImpl(inputBuffer);
+
+
+    /**
+     * Using stream flag.
+     */
+    protected boolean usingInputStream = false;
+
+
+    /**
+     * Using writer flag.
+     */
+    protected boolean usingReader = false;
+
+
+    /**
+     * Session parsed flag.
+     */
+    protected boolean sessionParsed = false;
+
+
+    /**
+     * Request parameters parsed flag.
+     */
+    protected boolean parametersParsed = false;
+
+
+    /**
+     * Cookies parsed flag.
+     */
+    protected boolean cookiesParsed = false;
+
+
+    /**
+     * Secure flag.
+     */
+    protected boolean secure = false;
+
+
+    /**
+     * Hash map used in the getParametersMap method.
+     */
+    protected ParameterMap parameterMap = new ParameterMap();
+
+
+    /**
+     * The currently active session for this request.
+     */
+    protected HttpSession session = null;
+
+
+    /**
+     * The current request dispatcher path.
+     */
+    protected Object requestDispatcherPath = null;
+
+
+    /**
+     * Was the requested session ID received in a cookie?
+     */
+    protected boolean requestedSessionCookie = false;
+
+
+    /**
+     * The requested session ID (if any) for this request.
+     */
+    protected String requestedSessionId = null;
+
+
+    /**
+     * Was the requested session ID received in a URL?
+     */
+    protected boolean requestedSessionURL = false;
+
+
+    /**
+     * Parse locales.
+     */
+    protected boolean localesParsed = false;
+
+
+    /**
+     * Associated context.
+     */
+    protected ServletContextImpl context = null;
+
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Filter chain associated with the request.
+     */
+    protected FilterChainImpl filterChain = new FilterChainImpl();
+    
+    /**
+     * Mapping data.
+     */
+    protected MappingData mappingData = new MappingData();
+
+
+    // -------------------------------------------------------- Request Methods
+
+    /**
+     * The response with which this request is associated.
+     */
+    protected ServletResponseImpl response = new ServletResponseImpl();
+    
+    /**
+     * URI byte to char converter (not recycled).
+     */
+   // protected B2CConverter URIConverter = null;
+
+    /**
+     * Associated wrapper.
+     */
+    protected ServletConfigImpl wrapper = null;
+
+    /**
+     * Post data buffer.
+     */
+    public final static int CACHED_POST_LEN = 8192;
+    
+    public  byte[] postData = null;
+
+    
+    private Request coyoteRequest;
+    
+    /** New IO/buffer model  
+     */
+    //protected Http11Connection con;
+
+
+    public ServletRequestImpl() {
+        response.setRequest(this);
+    }
+
+
+//    /**
+//     * Return the Host within which this Request is being processed.
+//     */
+//    public Host getHost() {
+//        if (getContext() == null)
+//            return null;
+//        return (Host) getContext().getParent();
+//        //return ((Host) mappingData.host);
+//    }
+//
+//
+//    /**
+//     * Set the Host within which this Request is being processed.  This
+//     * must be called as soon as the appropriate Host is identified, and
+//     * before the Request is passed to a context.
+//     *
+//     * @param host The newly associated Host
+//     */
+//    public void setHost(Host host) {
+//        mappingData.host = host;
+//    }
+
+    /**
+     * Add a Cookie to the set of Cookies associated with this Request.
+     *
+     * @param cookie The new cookie
+     */
+    public void addCookie(Cookie cookie) {
+
+        if (!cookiesParsed)
+            parseCookies();
+
+        int size = 0;
+        if (cookies != null) {
+            size = cookies.length;
+        }
+
+        Cookie[] newCookies = new Cookie[size + 1];
+        for (int i = 0; i < size; i++) {
+            newCookies[i] = cookies[i];
+        }
+        newCookies[size] = cookie;
+
+        cookies = newCookies;
+
+    }
+
+    /**
+     * Add a Header to the set of Headers associated with this Request.
+     *
+     * @param name The new header name
+     * @param value The new header value
+     */
+    public void addHeader(String name, String value) {
+        // Not used
+    }
+
+
+    /**
+     * Add a Locale to the set of preferred Locales for this Request.  The
+     * first added Locale will be the first one returned by getLocales().
+     *
+     * @param locale The new preferred Locale
+     */
+    public void addLocale(Locale locale) {
+        locales.add(locale);
+    }
+
+
+    /**
+     * Add a parameter name and corresponding set of values to this Request.
+     * (This is used when restoring the original request on a form based
+     * login).
+     *
+     * @param name Name of this request parameter
+     * @param values Corresponding values for this request parameter
+     */
+    public void addParameter(String name, String values[]) {
+        coyoteRequest.getParameters().addParameterValues(name, values);
+    }
+
+    /**
+     * Clear the collection of Cookies associated with this Request.
+     */
+    public void clearCookies() {
+        cookiesParsed = true;
+        cookies = null;
+    }
+
+    /**
+     * Clear the collection of Headers associated with this Request.
+     */
+    public void clearHeaders() {
+        // Not used
+    }
+
+    /**
+     * Clear the collection of Locales associated with this Request.
+     */
+    public void clearLocales() {
+        locales.clear();
+    }
+
+    /**
+     * Clear the collection of parameters associated with this Request.
+     */
+    public void clearParameters() {
+        // Not used
+    }
+
+
+    /**
+     * Create and return a ServletInputStream to read the content
+     * associated with this Request.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public ServletInputStream createInputStream() 
+        throws IOException {
+        return inputStream;
+    }
+
+    /**
+     * Perform whatever actions are required to flush and close the input
+     * stream or reader, in a single operation.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public void finishRequest() throws IOException {
+        // The reader and input stream don't need to be closed
+    }
+
+
+    /**
+     * Return the specified request attribute if it exists; otherwise, return
+     * <code>null</code>.
+     *
+     * @param name Name of the request attribute to return
+     */
+    public Object getAttribute(String name) {
+
+        if (name.equals(ServletRequestImpl.DISPATCHER_TYPE_ATTR)) {
+            return (dispatcherType == null) 
+                ? REQUEST_INTEGER
+                : dispatcherType;
+        } else if (name.equals(ServletRequestImpl.DISPATCHER_REQUEST_PATH_ATTR)) {
+            return (requestDispatcherPath == null) 
+                ? getRequestPathMB().toString()
+                : requestDispatcherPath.toString();
+        }
+
+        Object attr=attributes.get(name);
+
+        if(attr!=null)
+            return(attr);
+
+//        attr =  reqB.getAttribute(name);
+//        if(attr != null)
+//            return attr;
+//        if( isSSLAttribute(name) ) {
+//            reqB.action(ActionCode.ACTION_REQ_SSL_ATTRIBUTE, 
+//                                 reqB);
+//            attr = reqB.getAttribute(ServletRequestImpl.CERTIFICATES_ATTR);
+//            if( attr != null) {
+//                attributes.put(ServletRequestImpl.CERTIFICATES_ATTR, attr);
+//            }
+//            attr = reqB.getAttribute(ServletRequestImpl.CIPHER_SUITE_ATTR);
+//            if(attr != null) {
+//                attributes.put(ServletRequestImpl.CIPHER_SUITE_ATTR, attr);
+//            }
+//            attr = reqB.getAttribute(ServletRequestImpl.KEY_SIZE_ATTR);
+//            if(attr != null) {
+//                attributes.put(ServletRequestImpl.KEY_SIZE_ATTR, attr);
+//            }
+//            attr = reqB.getAttribute(ServletRequestImpl.SSL_SESSION_ID_ATTR);
+//            if(attr != null) {
+//                attributes.put(ServletRequestImpl.SSL_SESSION_ID_ATTR, attr);
+//            }
+//            attr = attributes.get(name);
+//        }
+        return attr;
+    }
+
+    /**
+     * Return the names of all request attributes for this Request, or an
+     * empty <code>Enumeration</code> if there are none.
+     */
+    public Enumeration getAttributeNames() {
+        if (isSecure()) {
+            getAttribute(ServletRequestImpl.CERTIFICATES_ATTR);
+        }
+        return new Enumerator(attributes.keySet(), true);
+    }
+
+
+    /**
+     * Return the authentication type used for this Request.
+     */
+    public String getAuthType() {
+        return (authType);
+    }
+
+
+    // ------------------------------------------------- Request Public Methods
+
+
+    /**
+     * Return the character encoding for this Request.
+     */
+    public String getCharacterEncoding() {
+      return (coyoteRequest.getCharacterEncoding());
+    }
+
+
+    /**
+     * Return the content length for this Request.
+     */
+    public int getContentLength() {
+        return (coyoteRequest.getContentLength());
+    }
+
+
+//    /**
+//     * Return the object bound with the specified name to the internal notes
+//     * for this request, or <code>null</code> if no such binding exists.
+//     *
+//     * @param name Name of the note to be returned
+//     */
+//    public Object getNote(String name) {
+//        return (notes.get(name));
+//    }
+//
+//
+//    /**
+//     * Return an Iterator containing the String names of all notes bindings
+//     * that exist for this request.
+//     */
+//    public Iterator getNoteNames() {
+//        return (notes.keySet().iterator());
+//    }
+//
+//
+//    /**
+//     * Remove any object bound to the specified name in the internal notes
+//     * for this request.
+//     *
+//     * @param name Name of the note to be removed
+//     */
+//    public void removeNote(String name) {
+//        notes.remove(name);
+//    }
+//
+//
+//    /**
+//     * Bind an object to a specified name in the internal notes associated
+//     * with this request, replacing any existing binding for this name.
+//     *
+//     * @param name Name to which the object should be bound
+//     * @param value Object to be bound to the specified name
+//     */
+//    public void setNote(String name, Object value) {
+//        notes.put(name, value);
+//    }
+//
+
+    /**
+     * Return the content type for this Request.
+     */
+    public String getContentType() {
+        return (coyoteRequest.getContentType());
+    }
+
+
+    /**
+     * Return the Context within which this Request is being processed.
+     */
+    public ServletContextImpl getContext() {
+        return (this.context);
+    }
+
+
+    /**
+     * Return the portion of the request URI used to select the Context
+     * of the Request.
+     */
+    public String getContextPath() {
+        return (mappingData.contextPath.toString());
+    }
+
+
+    /**
+     * Get the context path.
+     * 
+     * @return the context path
+     */
+    public MessageBytes getContextPathMB() {
+        return (mappingData.contextPath);
+    }
+
+
+    /**
+     * Return the set of Cookies received with this Request.
+     */
+    public Cookie[] getCookies() {
+
+        if (!cookiesParsed)
+            parseCookies();
+
+        return cookies;
+
+    }
+
+
+    /**
+     * Return the value of the specified date header, if any; otherwise
+     * return -1.
+     *
+     * @param name Name of the requested date header
+     *
+     * @exception IllegalArgumentException if the specified header value
+     *  cannot be converted to a date
+     */
+    public long getDateHeader(String name) {
+
+        String value = getHeader(name);
+        if (value == null)
+            return (-1L);
+        if (formats == null) {
+            formats = new SimpleDateFormat[] {
+                new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
+                new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+                new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
+            };
+            formats[0].setTimeZone(GMT_ZONE);
+            formats[1].setTimeZone(GMT_ZONE);
+            formats[2].setTimeZone(GMT_ZONE);
+        }
+        
+        // Attempt to convert the date header in a variety of formats
+        long result = FastHttpDateFormat.parseDate(value, formats);
+        if (result != (-1L)) {
+            return result;
+        }
+        throw new IllegalArgumentException(value);
+
+    }
+
+
+    /**
+     * Get the decoded request URI.
+     * 
+     * @return the URL decoded request URI
+     */
+    public String getDecodedRequestURI() {
+        return (coyoteRequest.decodedURI().toString());
+    }
+
+
+    /**
+     * Get the decoded request URI.
+     * 
+     * @return the URL decoded request URI
+     */
+    public MessageBytes getDecodedRequestURIMB() {
+        return (coyoteRequest.decodedURI());
+    }
+
+
+    /**
+     * Get filter chain associated with the request.
+     */
+    public FilterChainImpl getFilterChain() {
+        return (this.filterChain);
+    }
+
+
+    // ------------------------------------------------- ServletRequest Methods
+
+    /**
+     * Return the first value of the specified header, if any; otherwise,
+     * return <code>null</code>
+     *
+     * @param name Name of the requested header
+     */
+    public String getHeader(String name) {
+        return coyoteRequest.getHeader(name);
+    }
+    
+    /**
+     * Return the names of all headers received with this request.
+     */
+    public Enumeration getHeaderNames() {
+        return coyoteRequest.getMimeHeaders().names();
+    }
+
+
+    /**
+     * Return all of the values of the specified header, if any; otherwise,
+     * return an empty enumeration.
+     *
+     * @param name Name of the requested header
+     */
+    public Enumeration getHeaders(String name) {
+        return coyoteRequest.getMimeHeaders().values(name);
+    }
+
+    /**
+     * Return the servlet input stream for this Request.  The default
+     * implementation returns a servlet input stream created by
+     * <code>createInputStream()</code>.
+     *
+     * @exception IllegalStateException if <code>getReader()</code> has
+     *  already been called for this request
+     * @exception IOException if an input/output error occurs
+     */
+    public ServletInputStream getInputStream() throws IOException {
+
+        if (usingReader)
+            throw new IllegalStateException
+                ("usingReader");
+
+        usingInputStream = true;
+        return inputStream;
+
+    }
+
+
+    /**
+     * Return the value of the specified header as an integer, or -1 if there
+     * is no such header for this request.
+     *
+     * @param name Name of the requested header
+     *
+     * @exception IllegalArgumentException if the specified header value
+     *  cannot be converted to an integer
+     */
+    public int getIntHeader(String name) {
+
+        String value = getHeader(name);
+        if (value == null) {
+            return (-1);
+        } else {
+            return (Integer.parseInt(value));
+        }
+
+    }
+
+
+    /**
+     * Returns the Internet Protocol (IP) address of the interface on
+     * which the request  was received.
+     */       
+    public String getLocalAddr(){
+        return coyoteRequest.localAddr().toString();
+    }
+
+
+    /**
+     * Return the preferred Locale that the client will accept content in,
+     * based on the value for the first <code>Accept-Language</code> header
+     * that was encountered.  If the request did not specify a preferred
+     * language, the server's default Locale is returned.
+     */
+    public Locale getLocale() {
+
+        if (!localesParsed)
+            parseLocales();
+
+        if (locales.size() > 0) {
+            return ((Locale) locales.get(0));
+        } else {
+            return (defaultLocale);
+        }
+
+    }
+
+
+    /**
+     * Return the set of preferred Locales that the client will accept
+     * content in, based on the values for any <code>Accept-Language</code>
+     * headers that were encountered.  If the request did not specify a
+     * preferred language, the server's default Locale is returned.
+     */
+    public Enumeration getLocales() {
+
+        if (!localesParsed)
+            parseLocales();
+
+        if (locales.size() > 0)
+            return (new Enumerator(locales));
+        ArrayList results = new ArrayList();
+        results.add(defaultLocale);
+        return (new Enumerator(results));
+
+    }
+
+
+    /**
+     * Returns the host name of the Internet Protocol (IP) interface on
+     * which the request was received.
+     */
+    public String getLocalName(){
+        return coyoteRequest.localName().toString();
+    }
+
+
+    /**
+     * Returns the Internet Protocol (IP) port number of the interface
+     * on which the request was received.
+     */
+    public int getLocalPort(){
+        return coyoteRequest.getLocalPort();
+    }
+
+
+    /**
+     * Return mapping data.
+     */
+    public MappingData getMappingData() {
+        return (mappingData);
+    }
+
+
+
+    /**
+     * Return the HTTP request method used in this Request.
+     */
+    public String getMethod() {
+        return coyoteRequest.method().toString();
+    }
+
+
+    /**
+     * Return the value of the specified request parameter, if any; otherwise,
+     * return <code>null</code>.  If there is more than one value defined,
+     * return only the first one.
+     *
+     * @param name Name of the desired request parameter
+     */
+    public String getParameter(String name) {
+
+        if (!parametersParsed)
+            parseParameters();
+
+        return coyoteRequest.getParameters().getParameter(name);
+
+    }
+
+
+    /**
+     * Returns a <code>Map</code> of the parameters of this request.
+     * Request parameters are extra information sent with the request.
+     * For HTTP servlets, parameters are contained in the query string
+     * or posted form data.
+     *
+     * @return A <code>Map</code> containing parameter names as keys
+     *  and parameter values as map values.
+     */
+    public Map getParameterMap() {
+
+        if (parameterMap.isLocked())
+            return parameterMap;
+
+        Enumeration enumeration = getParameterNames();
+        while (enumeration.hasMoreElements()) {
+            String name = enumeration.nextElement().toString();
+            String[] values = getParameterValues(name);
+            parameterMap.put(name, values);
+        }
+
+        parameterMap.setLocked(true);
+
+        return parameterMap;
+
+    }
+
+
+    /**
+     * Return the names of all defined request parameters for this request.
+     */
+    public Enumeration getParameterNames() {
+
+        if (!parametersParsed)
+            parseParameters();
+
+        return coyoteRequest.getParameters().getParameterNames();
+
+    }
+
+
+    /**
+     * Return the defined values for the specified request parameter, if any;
+     * otherwise, return <code>null</code>.
+     *
+     * @param name Name of the desired request parameter
+     */
+    public String[] getParameterValues(String name) {
+
+        if (!parametersParsed)
+            parseParameters();
+
+        return coyoteRequest.getParameters().getParameterValues(name);
+
+    }
+
+
+    /**
+     * Return the path information associated with this Request.
+     */
+    public String getPathInfo() {
+        return (mappingData.pathInfo.toString());
+    }
+
+
+    /**
+     * Get the path info.
+     * 
+     * @return the path info
+     */
+    public MessageBytes getPathInfoMB() {
+        return (mappingData.pathInfo);
+    }
+
+
+    /**
+     * Return the extra path information for this request, translated
+     * to a real path.
+     */
+    public String getPathTranslated() {
+
+        if (context == null)
+            return (null);
+
+        if (getPathInfo() == null) {
+            return (null);
+        } else {
+            return (context.getServletContext().getRealPath(getPathInfo()));
+        }
+
+    }
+    
+    /**
+     * Return the principal that has been authenticated for this Request.
+     */
+    public Principal getPrincipal() {
+        return (userPrincipal);
+    }
+     
+    /**
+     * Return the protocol and version used to make this Request.
+     */
+    public String getProtocol() {
+        return coyoteRequest.protocol().toString();
+    }
+
+    /**
+     * Return the query string associated with this request.
+     */
+    public String getQueryString() {
+        String queryString = coyoteRequest.queryString().toString();
+        if (queryString == null || queryString.equals("")) {
+            return (null);
+        } else {
+            return queryString;
+        }
+    }
+
+
+    /**
+     * Read the Reader wrapping the input stream for this Request.  The
+     * default implementation wraps a <code>BufferedReader</code> around the
+     * servlet input stream returned by <code>createInputStream()</code>.
+     *
+     * @exception IllegalStateException if <code>getInputStream()</code>
+     *  has already been called for this request
+     * @exception IOException if an input/output error occurs
+     */
+    public BufferedReader getReader() throws IOException {
+
+        if (usingInputStream)
+            throw new IllegalStateException
+                ("usingInputStream");
+
+        usingReader = true;
+        //inputBuffer.setConverter();// getB2C());
+        return reader;
+
+    }
+
+    /**
+     * Cached list of encoders.
+     */
+    protected HashMap encoders = new HashMap();
+
+    public static final String DEFAULT_CHARACTER_ENCODING="ISO-8859-1";
+
+    /**
+     *  Converter for the encoding associated with the request.
+     *  If encoding is changed - a different encoder will be returned.
+     *  
+     *  Encoders are cached ( per request ) - at least 8K per charset
+     */
+    public B2CConverter getB2C() throws IOException {
+      String enc = getCharacterEncoding();
+      if (enc == null) {
+        enc = DEFAULT_CHARACTER_ENCODING;
+      }
+      B2CConverter conv = 
+        (B2CConverter) encoders.get(enc);
+      if (conv == null) {
+        conv = new B2CConverter(enc);
+        encoders.put(enc, conv);
+      }
+      return conv;
+    }
+    
+    /**
+     * Return the real path of the specified virtual path.
+     *
+     * @param path Path to be translated
+     *
+     * @deprecated As of version 2.1 of the Java Servlet API, use
+     *  <code>ServletContext.getRealPath()</code>.
+     */
+    public String getRealPath(String path) {
+
+        if (context == null)
+            return (null);
+        ServletContext servletContext = context; // .getServletContext();
+        if (servletContext == null)
+            return (null);
+        else {
+            try {
+                return (servletContext.getRealPath(path));
+            } catch (IllegalArgumentException e) {
+                return (null);
+            }
+        }
+
+    }
+
+
+    /**
+     * Return the remote IP address making this Request.
+     */
+    public String getRemoteAddr() {
+      if (coyoteRequest.remoteAddr().isNull()) {
+        coyoteRequest.action(ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE, coyoteRequest);
+      }
+      return coyoteRequest.remoteAddr().toString();
+    }
+
+
+    /**
+     * Return the remote host name making this Request.
+     */
+    public String getRemoteHost() {
+      if (coyoteRequest.remoteHost().isNull()) {
+        coyoteRequest.action(ActionCode.ACTION_REQ_HOST_ATTRIBUTE, coyoteRequest);
+      }
+      return coyoteRequest.remoteHost().toString();
+    }
+
+
+    /**
+     * Returns the Internet Protocol (IP) source port of the client
+     * or last proxy that sent the request.
+     */    
+    public int getRemotePort(){
+        return coyoteRequest.getRemotePort();
+    }
+
+
+    /**
+     * Return the name of the remote user that has been authenticated
+     * for this Request.
+     */
+    public String getRemoteUser() {
+
+        if (userPrincipal != null) {
+            return (userPrincipal.getName());
+        } else {
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * Return the <code>ServletRequest</code> for which this object
+     * is the facade.  This method must be implemented by a subclass.
+     */
+    public HttpServletRequest getRequest() {
+        return this;
+    }
+    
+    public Request getCoyoteRequest() {
+      return coyoteRequest;
+    }
+    
+    public void setCoyoteRequest(Request req) {
+      this.coyoteRequest = req;
+      inputBuffer.setRequest(req);
+    }
+
+
+    /**
+     * Return a RequestDispatcher that wraps the resource at the specified
+     * path, which may be interpreted as relative to the current request path.
+     *
+     * @param path Path of the resource to be wrapped
+     */
+    public RequestDispatcher getRequestDispatcher(String path) {
+
+        if (context == null)
+            return (null);
+
+        // If the path is already context-relative, just pass it through
+        if (path == null)
+            return (null);
+        else if (path.startsWith("/"))
+            return (context.getRequestDispatcher(path));
+
+        // Convert a request-relative path to a context-relative one
+        String servletPath = (String) getAttribute(RequestDispatcherImpl.INCLUDE_SERVLET_PATH_ATTR);
+        if (servletPath == null)
+            servletPath = getServletPath();
+
+        // Add the path info, if there is any
+        String pathInfo = getPathInfo();
+        String requestPath = null;
+
+        if (pathInfo == null) {
+            requestPath = servletPath;
+        } else {
+            requestPath = servletPath + pathInfo;
+        }
+
+        int pos = requestPath.lastIndexOf('/');
+        String relative = null;
+        if (pos >= 0) {
+            relative = RequestUtil.normalize
+                (requestPath.substring(0, pos + 1) + path);
+        } else {
+            relative = RequestUtil.normalize(requestPath + path);
+        }
+
+        return (context.getRequestDispatcher(relative));
+
+    }
+
+
+    /**
+     * Return the session identifier included in this request, if any.
+     */
+    public String getRequestedSessionId() {
+        return (requestedSessionId);
+    }
+
+
+    // ---------------------------------------------------- HttpRequest Methods
+
+
+    /**
+     * Get the request path.
+     * 
+     * @return the request path
+     */
+    public MessageBytes getRequestPathMB() {
+        return (mappingData.requestPath);
+    }
+
+
+    /**
+     * Return the request URI for this request.
+     */
+    public String getRequestURI() {
+        return coyoteRequest.requestURI().toString();
+    }
+    
+    /**
+     */
+    public void setRequestURI(String uri) {
+      coyoteRequest.decodedURI().setString(uri);
+      try {
+        UriNormalizer.decodeRequest(coyoteRequest.decodedURI(), 
+                coyoteRequest.requestURI(), coyoteRequest.getURLDecoder());
+      } catch(IOException ioe) {
+        ioe.printStackTrace();
+        return;
+      }
+    }
+
+
+
+    /**
+     * Reconstructs the URL the client used to make the request.
+     * The returned URL contains a protocol, server name, port
+     * number, and server path, but it does not include query
+     * string parameters.
+     * <p>
+     * Because this method returns a <code>StringBuffer</code>,
+     * not a <code>String</code>, you can modify the URL easily,
+     * for example, to append query parameters.
+     * <p>
+     * This method is useful for creating redirect messages and
+     * for reporting errors.
+     *
+     * @return A <code>StringBuffer</code> object containing the
+     *  reconstructed URL
+     */
+    public StringBuffer getRequestURL() {
+
+        StringBuffer url = new StringBuffer();
+        String scheme = getScheme();
+        int port = getServerPort();
+        if (port < 0)
+            port = 80; // Work around java.net.URL bug
+
+        url.append(scheme);
+        url.append("://");
+        url.append(getServerName());
+        if ((scheme.equals("http") && (port != 80))
+            || (scheme.equals("https") && (port != 443))) {
+            url.append(':');
+            url.append(port);
+        }
+        url.append(getRequestURI());
+
+        return (url);
+
+    }
+
+
+    /**
+     * Return the Response with which this Request is associated.
+     */
+    public ServletResponseImpl getResponse() {
+        return (this.response);
+    }
+
+
+    /**
+     * Return the scheme used to make this Request.
+     */
+    public String getScheme() {
+        String scheme = coyoteRequest.scheme().toString();
+        if (scheme == null) {
+            scheme = (isSecure() ? "https" : "http");
+        }
+        return scheme;
+    }
+
+
+    /**
+     * Return the server name responding to this Request.
+     */
+    public String getServerName() {
+        return (coyoteRequest.serverName().toString());
+    }
+
+
+    /**
+     * Return the server port responding to this Request.
+     */
+    public int getServerPort() {
+        return (coyoteRequest.getServerPort());
+    }
+
+
+    /**
+     * Return the portion of the request URI used to select the servlet
+     * that will process this request.
+     */
+    public String getServletPath() {
+        return (mappingData.wrapperPath.toString());
+    }
+
+
+    /**
+     * Get the servlet path.
+     * 
+     * @return the servlet path
+     */
+    public MessageBytes getServletPathMB() {
+        return (mappingData.wrapperPath);
+    }
+
+
+
+    /**
+     * Return the input stream associated with this Request.
+     */
+    public InputStream getStream() {
+        return inputStream;
+    }
+
+
+    /**
+     * Return the principal that has been authenticated for this Request.
+     */
+    public Principal getUserPrincipal() {
+        return userPrincipal;
+    }
+
+
+    /**
+     * Return the Wrapper within which this Request is being processed.
+     */
+    public ServletConfigImpl getWrapper() {
+        return (this.wrapper);
+    }
+
+
+    /**
+     * Return <code>true</code> if the session identifier included in this
+     * request came from a cookie.
+     */
+    public boolean isRequestedSessionIdFromCookie() {
+
+        if (requestedSessionId != null)
+            return (requestedSessionCookie);
+        else
+            return (false);
+
+    }
+
+
+    /**
+     * Return <code>true</code> if the session identifier included in this
+     * request came from the request URI.
+     *
+     * @deprecated As of Version 2.1 of the Java Servlet API, use
+     *  <code>isRequestedSessionIdFromURL()</code> instead.
+     */
+    public boolean isRequestedSessionIdFromUrl() {
+        return (isRequestedSessionIdFromURL());
+    }
+
+
+    /**
+     * Return <code>true</code> if the session identifier included in this
+     * request came from the request URI.
+     */
+    public boolean isRequestedSessionIdFromURL() {
+
+        if (requestedSessionId != null)
+            return (requestedSessionURL);
+        else
+            return (false);
+
+    }
+
+
+    /**
+     * Return <code>true</code> if the session identifier included in this
+     * request identifies a valid session.
+     */
+    public boolean isRequestedSessionIdValid() {
+
+        if (requestedSessionId == null)
+            return (false);
+        if (context == null)
+            return (false);
+        UserSessionManager manager = context.getManager();
+        if (manager == null)
+            return (false);
+        HttpSession session = null;
+        try {
+            session = manager.findSession(requestedSessionId);
+        } catch (IOException e) {
+            session = null;
+        }
+        if ((session != null) && manager.isValid(session))
+            return (true);
+        else
+            return (false);
+
+    }
+
+    /**
+     * Was this request received on a secure connection?
+     */
+    public boolean isSecure() {
+        return (secure);
+    }
+    
+    
+    /**
+     * Return <code>true</code> if the authenticated user principal
+     * possesses the specified role name.
+     *
+     * @param role Role name to be validated
+     */
+    public boolean isUserInRole(String role) {
+        // Have we got an authenticated principal at all?
+        Principal userPrincipal = getPrincipal();
+        if (userPrincipal == null)
+            return (false);
+
+        // Identify the Realm we will use for checking role assignmenets
+        if (context == null)
+            return (false);
+
+        // Check for a role alias defined in a <security-role-ref> element
+        if (wrapper != null) {
+            String realRole = wrapper.getSecurityRoleRef(role);
+            if (realRole != null) {
+                role = realRole;
+            }
+        }
+
+        if (role.equals(userPrincipal.getName())) {
+            return true;
+        }
+        
+        // TODO: check !!!!
+        // Check for a role defined directly as a <security-role>
+        return false;
+    }
+
+    /**
+     * Release all object references, and initialize instance variables, in
+     * preparation for reuse of this object.
+     */
+    public void recycle() {
+
+        wrapper = null;
+
+        dispatcherType = null;
+        requestDispatcherPath = null;
+
+        authType = null;
+        inputBuffer.recycle();
+        usingInputStream = false;
+        usingReader = false;
+        userPrincipal = null;
+        subject = null;
+        sessionParsed = false;
+        parametersParsed = false;
+        cookiesParsed = false;
+        locales.clear();
+        localesParsed = false;
+        secure = false;
+
+        attributes.clear();
+        //notes.clear();
+        cookies = null;
+
+        if (session != null) {
+            context.getManager().endAccess(session);
+        }
+        context = null;
+        session = null;
+        requestedSessionCookie = false;
+        requestedSessionId = null;
+        requestedSessionURL = false;
+
+        parameterMap.setLocked(false);
+        parameterMap.clear();
+
+        mappingData.recycle();
+
+    }
+
+
+    /**
+     * Remove the specified request attribute if it exists.
+     *
+     * @param name Name of the request attribute to remove
+     */
+    public void removeAttribute(String name) {
+        Object value = null;
+        boolean found = false;
+
+        // Remove the specified attribute
+        // Check for read only attribute
+        // requests are per thread so synchronization unnecessary
+//        if (readOnlyAttributes.containsKey(name)) {
+//            return;
+//        }
+        found = attributes.containsKey(name);
+        if (found) {
+            value = attributes.get(name);
+            attributes.remove(name);
+        } else {
+            return;
+        }
+
+        // Notify interested application event listeners
+        List listeners = context.getListeners();
+        if (listeners.size() == 0)
+            return;
+        ServletRequestAttributeEvent event = null;
+        for (int i = 0; i < listeners.size(); i++) {
+            if (!(listeners.get(i) instanceof ServletRequestAttributeListener))
+                continue;
+            ServletRequestAttributeListener listener =
+                (ServletRequestAttributeListener) listeners.get(i);
+            try {
+                if (event == null) {
+                    event = 
+                        new ServletRequestAttributeEvent(context.getServletContext(),
+                            getRequest(), name, value);
+                }
+                listener.attributeRemoved(event);
+            } catch (Throwable t) {
+                context.getLogger().log(Level.WARNING, "ServletRequestAttributeListner.attributeRemoved()", t);
+                // Error valve will pick this execption up and display it to user
+                attributes.put( ServletRequestImpl.EXCEPTION_ATTR, t );
+            }
+        }
+    }
+
+
+    /**
+     * Set the specified request attribute to the specified value.
+     *
+     * @param name Name of the request attribute to set
+     * @param value The associated value
+     */
+    public void setAttribute(String name, Object value) {
+       
+        // Name cannot be null
+        if (name == null)
+            throw new IllegalArgumentException
+                ("setAttribute() name == null");
+
+        // Null value is the same as removeAttribute()
+        if (value == null) {
+            removeAttribute(name);
+            return;
+        }
+
+        if (name.equals(ServletRequestImpl.DISPATCHER_TYPE_ATTR)) {
+            dispatcherType = value;
+            return;
+        } else if (name.equals(ServletRequestImpl.DISPATCHER_REQUEST_PATH_ATTR)) {
+            requestDispatcherPath = value;
+            return;
+        }
+
+        Object oldValue = null;
+        boolean replaced = false;
+
+        // Add or replace the specified attribute
+        // Check for read only attribute
+        // requests are per thread so synchronization unnecessary
+//        if (readOnlyAttributes.containsKey(name)) {
+//            return;
+//        }
+
+        oldValue = attributes.put(name, value);
+        if (oldValue != null) {
+            replaced = true;
+        }
+
+        // Pass special attributes to the native layer
+//        if (name.startsWith("org.apache.tomcat.")) {
+//            reqB.setAttribute(name, value);
+//        }
+//        
+        // Notify interested application event listeners
+        List listeners = context.getListeners();
+        if (listeners.size() == 0)
+            return;
+        ServletRequestAttributeEvent event = null;
+
+        for (int i = 0; i < listeners.size(); i++) {
+            if (!(listeners.get(i) instanceof ServletRequestAttributeListener))
+                continue;
+            ServletRequestAttributeListener listener =
+                (ServletRequestAttributeListener) listeners.get(i);
+            try {
+                if (event == null) {
+                    if (replaced)
+                        event =
+                            new ServletRequestAttributeEvent(context.getServletContext(),
+                                                             getRequest(), name, oldValue);
+                    else
+                        event =
+                            new ServletRequestAttributeEvent(context.getServletContext(),
+                                                             getRequest(), name, value);
+                }
+                if (replaced) {
+                    listener.attributeReplaced(event);
+                } else {
+                    listener.attributeAdded(event);
+                }
+            } catch (Throwable t) {
+                context.getLogger().log(Level.WARNING, "ServletRequestAttributeListener error", t);
+                // Error valve will pick this execption up and display it to user
+                attributes.put( ServletRequestImpl.EXCEPTION_ATTR, t );
+            }
+        }
+    }
+
+
+    // --------------------------------------------- HttpServletRequest Methods
+
+
+    /**
+     * Set the authentication type used for this request, if any; otherwise
+     * set the type to <code>null</code>.  Typical values are "BASIC",
+     * "DIGEST", or "SSL".
+     *
+     * @param type The authentication type used
+     */
+    public void setAuthType(String type) {
+        this.authType = type;
+    }
+
+
+    /**
+     * Overrides the name of the character encoding used in the body of
+     * this request.  This method must be called prior to reading request
+     * parameters or reading input using <code>getReader()</code>.
+     *
+     * @param enc The character encoding to be used
+     *
+     * @exception UnsupportedEncodingException if the specified encoding
+     *  is not supported
+     *
+     * @since Servlet 2.3
+     */
+    public void setCharacterEncoding(String enc)
+        throws UnsupportedEncodingException {
+
+        // Ensure that the specified encoding is valid
+        byte buffer[] = new byte[1];
+        buffer[0] = (byte) 'a';
+        String dummy = new String(buffer, enc);
+
+        // Save the validated encoding
+        coyoteRequest.setCharacterEncoding(enc);
+
+    }
+
+
+//    public void setConnection(Http11Connection con) {
+//        this.con = con;
+//        //reqB.messageWriter.setConnection(con);
+//        //inputBuffer.setRequest(req);
+//    }
+
+
+    /**
+     * Set the content length associated with this Request.
+     *
+     * @param length The new content length
+     */
+    public void setContentLength(int length) {
+        // Not used
+    }
+
+
+    /**
+     * Set the content type (and optionally the character encoding)
+     * associated with this Request.  For example,
+     * <code>text/html; charset=ISO-8859-4</code>.
+     *
+     * @param type The new content type
+     */
+    public void setContentType(String type) {
+        // Not used
+    }
+
+
+    /**
+     * Set the Context within which this Request is being processed.  This
+     * must be called as soon as the appropriate Context is identified, because
+     * it identifies the value to be returned by <code>getContextPath()</code>,
+     * and thus enables parsing of the request URI.
+     *
+     * @param context The newly associated Context
+     */
+    public void setContext(ServletContextImpl context) {
+        this.context = context;
+    }
+
+
+    /**
+     * Set the context path for this Request.  This will normally be called
+     * when the associated Context is mapping the Request to a particular
+     * Wrapper.
+     *
+     * @param path The context path
+     */
+    public void setContextPath(String path) {
+
+        if (path == null) {
+            mappingData.contextPath.setString("");
+        } else {
+            mappingData.contextPath.setString(path);
+        }
+
+    }
+
+
+    /**
+     * Set the set of cookies recieved with this Request.
+     */
+    public void setCookies(Cookie[] cookies) {
+
+        this.cookies = cookies;
+
+    }
+
+
+    /**
+     * Set the decoded request URI.
+     * 
+     * @param uri The decoded request URI
+     */
+    public void setDecodedRequestURI(String uri) {
+        // Not used
+    }
+
+
+    /**
+     * Set the HTTP request method used for this Request.
+     *
+     * @param method The request method
+     */
+    public void setMethod(String method) {
+      coyoteRequest.method().setString(method);
+    }
+
+
+    /**
+     * Set the path information for this Request.  This will normally be called
+     * when the associated Context is mapping the Request to a particular
+     * Wrapper.
+     *
+     * @param path The path information
+     */
+    public void setPathInfo(String path) {
+        mappingData.pathInfo.setString(path);
+    }
+
+
+    /**
+     * Set the protocol name and version associated with this Request.
+     *
+     * @param protocol Protocol name and version
+     */
+    public void setProtocol(String protocol) {
+        // Not used
+    }
+
+
+    /**
+     * Set the query string for this Request.  This will normally be called
+     * by the HTTP Connector, when it parses the request headers.
+     *
+     * @param query The query string
+     */
+    public void setQueryString(String query) {
+        // Not used
+    }
+
+
+    /**
+     * Set the IP address of the remote client associated with this Request.
+     *
+     * @param remoteAddr The remote IP address
+     */
+    public void setRemoteAddr(String remoteAddr) {
+        // Not used
+    }
+
+
+    /**
+     * Set the fully qualified name of the remote client associated with this
+     * Request.
+     *
+     * @param remoteHost The remote host name
+     */
+    public void setRemoteHost(String remoteHost) {
+        // Not used
+    }
+
+
+    /**
+     * Set a flag indicating whether or not the requested session ID for this
+     * request came in through a cookie.  This is normally called by the
+     * HTTP Connector, when it parses the request headers.
+     *
+     * @param flag The new flag
+     */
+    public void setRequestedSessionCookie(boolean flag) {
+
+        this.requestedSessionCookie = flag;
+
+    }
+
+
+    /**
+     * Set the requested session ID for this request.  This is normally called
+     * by the HTTP Connector, when it parses the request headers.
+     *
+     * @param id The new session id
+     */
+    public void setRequestedSessionId(String id) {
+
+        this.requestedSessionId = id;
+
+    }
+
+
+    /**
+     * Set a flag indicating whether or not the requested session ID for this
+     * request came in through a URL.  This is normally called by the
+     * HTTP Connector, when it parses the request headers.
+     *
+     * @param flag The new flag
+     */
+    public void setRequestedSessionURL(boolean flag) {
+
+        this.requestedSessionURL = flag;
+
+    }
+
+
+    /**
+     * Set the name of the scheme associated with this request.  Typical values
+     * are <code>http</code>, <code>https</code>, and <code>ftp</code>.
+     *
+     * @param scheme The scheme
+     */
+    public void setScheme(String scheme) {
+        // Not used
+    }
+
+
+    /**
+     * Set the value to be returned by <code>isSecure()</code>
+     * for this Request.
+     *
+     * @param secure The new isSecure value
+     */
+    public void setSecure(boolean secure) {
+        this.secure = secure;
+    }
+
+
+    /**
+     * Set the name of the server (virtual host) to process this request.
+     *
+     * @param name The server name
+     */
+    public void setServerName(String name) {
+        coyoteRequest.serverName().setString(name);
+    }
+
+
+    /**
+     * Set the port number of the server to process this request.
+     *
+     * @param port The server port
+     */
+    public void setServerPort(int port) {
+        coyoteRequest.setServerPort(port);
+    }
+
+
+    /**
+     * Set the servlet path for this Request.  This will normally be called
+     * when the associated Context is mapping the Request to a particular
+     * Wrapper.
+     *
+     * @param path The servlet path
+     */
+    public void setServletPath(String path) {
+        if (path != null)
+            mappingData.wrapperPath.setString(path);
+    }
+
+
+    /**
+     * Set the input stream associated with this Request.
+     *
+     * @param stream The new input stream
+     */
+    public void setStream(InputStream stream) {
+        // Ignore
+    }
+
+
+    /**
+     * Set the Principal who has been authenticated for this Request.  This
+     * value is also used to calculate the value to be returned by the
+     * <code>getRemoteUser()</code> method.
+     *
+     * @param principal The user Principal
+     */
+    public void setUserPrincipal(Principal principal) {
+
+        if (System.getSecurityManager() != null){
+            HttpSession session = getSession(false);
+            if ( (subject != null) && 
+                 (!subject.getPrincipals().contains(principal)) ){
+                subject.getPrincipals().add(principal);         
+            } else if (session != null &&
+                        session.getAttribute(ServletRequestImpl.SUBJECT_ATTR) == null) {
+                subject = new Subject();
+                subject.getPrincipals().add(principal);         
+            }
+            if (session != null){
+                session.setAttribute(ServletRequestImpl.SUBJECT_ATTR, subject);
+            }
+        } 
+
+        this.userPrincipal = principal;
+    }
+
+
+    /**
+     * Set the Wrapper within which this Request is being processed.  This
+     * must be called as soon as the appropriate Wrapper is identified, and
+     * before the Request is ultimately passed to an application servlet.
+     * @param wrapper The newly associated Wrapper
+     */
+    public void setWrapper(ServletConfigImpl wrapper) {
+        this.wrapper = wrapper;
+    }
+
+
+    public String toString() {
+        return coyoteRequest.requestURI().toString();
+    }
+
+
+    /**
+     * Configures the given JSESSIONID cookie.
+     *
+     * @param cookie The JSESSIONID cookie to be configured
+     */
+    protected void configureSessionCookie(Cookie cookie) {
+        cookie.setMaxAge(-1);
+        String contextPath = null;
+        if (//!connector.getEmptySessionPath() && 
+                (getContext() != null)) {
+            contextPath = getContext().getEncodedPath();
+        }
+        if ((contextPath != null) && (contextPath.length() > 0)) {
+            cookie.setPath(contextPath);
+        } else {
+            cookie.setPath("/");
+        }
+        if (isSecure()) {
+            cookie.setSecure(true);
+        }
+    }
+
+
+    /**
+     * Return the session associated with this Request, creating one
+     * if necessary.
+     */
+    public HttpSession getSession() {
+        return getSession(true);
+    }
+
+
+    public HttpSession getSession(boolean create) {
+
+        // There cannot be a session if no context has been assigned yet
+        if (context == null)
+            return (null);
+
+
+        // Return the requested session if it exists and is valid
+        UserSessionManager manager = null;
+        if (context != null)
+            manager = context.getManager();
+        if (manager == null)
+            return (null);      // Sessions are not supported
+        
+        // Return the current session if it exists and is valid
+        if ((session != null) && !manager.isValid(session))
+            session = null;
+        if (session != null)
+            return (session);
+        
+        
+        if (requestedSessionId != null) {
+            try {
+                session = manager.findSession(requestedSessionId);
+            } catch (IOException e) {
+                session = null;
+            }
+            if ((session != null) && !manager.isValid(session))
+                session = null;
+            if (session != null) {
+                manager.access(session);
+                return (session);
+            }
+        }
+
+        // Create a new session if requested and the response is not committed
+        if (!create)
+            return (null);
+        if ((context != null) && (response != null) &&
+            context.getCookies() &&
+            getResponse().isCommitted()) {
+            throw new IllegalStateException
+              ("isCommited()");
+        }
+
+        // Attempt to reuse session id if one was submitted in a cookie
+        // Do not reuse the session id if it is from a URL, to prevent possible
+        // phishing attacks
+        if (// connector.getEmptySessionPath() &&
+                isRequestedSessionIdFromCookie()) {
+            session = manager.createSession(getRequestedSessionId());
+        } else {
+            session = manager.createSession(null);
+        }
+
+        // Creating a new session cookie based on that session
+        if ((session != null) && (getContext() != null)
+               && getContext().getCookies()) {
+            Cookie cookie = new Cookie(ServletRequestImpl.SESSION_COOKIE_NAME,
+                                       session.getId());
+            configureSessionCookie(cookie);
+            response.addCookie(cookie);
+        }
+
+        if (session != null) {
+            manager.access(session);
+            return (session);
+        } else {
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * Return the URI converter.
+     */
+//    protected B2CConverter getURIConverter() {
+//        return URIConverter;
+//    }
+//
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Parse cookies.
+     */
+    protected void parseCookies() {
+
+        cookiesParsed = true;
+
+        Cookies serverCookies = coyoteRequest.getCookies();
+        int count = serverCookies.getCookieCount();
+        if (count <= 0)
+            return;
+
+        cookies = new Cookie[count];
+
+        int idx=0;
+        for (int i = 0; i < count; i++) {
+            ServerCookie scookie = serverCookies.getCookie(i);
+            try {
+                Cookie cookie = new Cookie(scookie.getName().toString(),
+                                           scookie.getValue().toString());
+                cookie.setPath(scookie.getPath().toString());
+                cookie.setVersion(scookie.getVersion());
+                String domain = scookie.getDomain().toString();
+                if (domain != null) {
+                    cookie.setDomain(scookie.getDomain().toString());
+                }
+                cookies[idx++] = cookie;
+            } catch(IllegalArgumentException e) {
+                // Ignore bad cookie
+            }
+        }
+        if( idx < count ) {
+            Cookie [] ncookies = new Cookie[idx];
+            System.arraycopy(cookies, 0, ncookies, 0, idx);
+            cookies = ncookies;
+        }
+
+    }
+
+    /**
+     * Parse request locales.
+     */
+    protected void parseLocales() {
+
+        localesParsed = true;
+
+        Enumeration values = getHeaders("accept-language");
+
+        while (values.hasMoreElements()) {
+            String value = values.nextElement().toString();
+            parseLocalesHeader(value);
+        }
+
+    }
+
+    /**
+     * Parse accept-language header value.
+     */
+    protected void parseLocalesHeader(String value) {
+
+      TreeMap locales = new LocaleParser().parseLocale(value);
+      // Process the quality values in highest->lowest order (due to
+      // negating the Double value when creating the key)
+      Iterator keys = locales.keySet().iterator();
+      while (keys.hasNext()) {
+        Double key = (Double) keys.next();
+        ArrayList list = (ArrayList) locales.get(key);
+        Iterator values = list.iterator();
+        while (values.hasNext()) {
+          Locale locale = (Locale) values.next();
+          addLocale(locale);
+        }
+      }
+
+    }
+
+    /**
+     * Parse request parameters.
+     */
+    protected void parseParameters() {
+
+        parametersParsed = true;
+
+        Parameters parameters = coyoteRequest.getParameters();
+
+        // getCharacterEncoding() may have been overridden to search for
+        // hidden form field containing request encoding
+        String enc = getCharacterEncoding();
+
+//        boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
+        if (enc != null) {
+            parameters.setEncoding(enc);
+//            if (useBodyEncodingForURI) {
+//                parameters.setQueryStringEncoding(enc);
+//            }
+        } else {
+            parameters.setEncoding(DEFAULT_CHARACTER_ENCODING);
+//            if (useBodyEncodingForURI) {
+//                parameters.setQueryStringEncoding
+//                    (DEFAULT_CHARACTER_ENCODING);
+//            }
+        }
+
+        parameters.handleQueryParameters();
+
+        if (usingInputStream || usingReader)
+            return;
+
+        if (!getMethod().equalsIgnoreCase("POST"))
+            return;
+
+        String contentType = getContentType();
+        if (contentType == null)
+            contentType = "";
+        int semicolon = contentType.indexOf(';');
+        if (semicolon >= 0) {
+            contentType = contentType.substring(0, semicolon).trim();
+        } else {
+            contentType = contentType.trim();
+        }
+        if (!("application/x-www-form-urlencoded".equals(contentType)))
+            return;
+
+        int len = getContentLength();
+
+        if (len > 0) {
+//            int maxPostSize = connector.getMaxPostSize();
+//            if ((maxPostSize > 0) && (len > maxPostSize)) {
+//                context.getLogger().info
+//                    (sm.getString("reqB.postTooLarge"));
+//                throw new IllegalStateException("Post too large");
+//            }
+            try {
+                byte[] formData = null;
+                if (len < CACHED_POST_LEN) {
+                    if (postData == null)
+                        postData = new byte[CACHED_POST_LEN];
+                    formData = postData;
+                } else {
+                    formData = new byte[len];
+                }
+                int actualLen = readPostBody(formData, len);
+                if (actualLen == len) {
+                    parameters.processParameters(formData, 0, len);
+                }
+            } catch (Throwable t) {
+                ; // Ignore
+            }
+        }
+
+    }
+
+
+    /**
+     * Parse session id in URL. Done in request for performance.
+     * TODO: should be done in manager
+     */
+    protected void parseSessionCookiesId() {
+        String sessionCookieName = context.getSessionCookieName();
+        
+        // Parse session id from cookies
+        Cookies serverCookies = coyoteRequest.getCookies();
+        int count = serverCookies.getCookieCount();
+        if (count <= 0)
+            return;
+
+        for (int i = 0; i < count; i++) {
+            ServerCookie scookie = serverCookies.getCookie(i);
+            if (scookie.getName().equals(sessionCookieName)) {
+                // Override anything requested in the URL
+                if (!isRequestedSessionIdFromCookie()) {
+                    // Accept only the first session id cookie
+                    //scookie.getValue().convertToAscii();
+                    
+                    setRequestedSessionId
+                        (scookie.getValue().toString());
+                    setRequestedSessionCookie(true);
+                    setRequestedSessionURL(false);
+                } else {
+                    if (!isRequestedSessionIdValid()) {
+                        // Replace the session id until one is valid
+                        //scookie.getValue().convertToAscii();
+                        setRequestedSessionId
+                            (scookie.getValue().toString());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Parse session id in URL.
+     */
+    protected void parseSessionId() {
+        Request req = coyoteRequest;
+        ServletRequestImpl request = this;
+        ByteChunk uriBC = req.requestURI().getByteChunk();
+        int semicolon = uriBC.indexOf(match, 0, match.length(), 0);
+
+        if (semicolon > 0) {
+
+            // Parse session ID, and extract it from the decoded request URI
+            int start = uriBC.getStart();
+            int end = uriBC.getEnd();
+
+            int sessionIdStart = semicolon + match.length();
+            int semicolon2 = uriBC.indexOf(';', sessionIdStart);
+            if (semicolon2 >= 0) {
+                request.setRequestedSessionId
+                    (new String(uriBC.getBuffer(), start + sessionIdStart, 
+                            semicolon2 - sessionIdStart));
+                // Extract session ID from request URI
+                byte[] buf = uriBC.getBuffer();
+                for (int i = 0; i < end - start - semicolon2; i++) {
+                    buf[start + semicolon + i] 
+                        = buf[start + i + semicolon2];
+                }
+                uriBC.setBytes(buf, start, end - start - semicolon2 + semicolon);
+            } else {
+                request.setRequestedSessionId
+                    (new String(uriBC.getBuffer(), start + sessionIdStart, 
+                            (end - start) - sessionIdStart));
+                uriBC.setEnd(start + semicolon);
+            }
+            request.setRequestedSessionURL(true);
+
+        } else {
+            request.setRequestedSessionId(null);
+            request.setRequestedSessionURL(false);
+        }
+
+    }
+
+
+    /**
+     * Read post body in an array.
+     */
+    protected int readPostBody(byte body[], int len)
+        throws IOException {
+
+        int offset = 0;
+        do {
+            int inputLen = getStream().read(body, offset, len - offset);
+            if (inputLen <= 0) {
+                return offset;
+            }
+            offset += inputLen;
+        } while ((len - offset) > 0);
+        return len;
+
+    }
+
+
+    /**
+     * Test if a given name is one of the special Servlet-spec SSL attributes.
+     */
+    static boolean isSSLAttribute(String name) {
+        return ServletRequestImpl.CERTIFICATES_ATTR.equals(name) ||
+            ServletRequestImpl.CIPHER_SUITE_ATTR.equals(name) ||
+            ServletRequestImpl.KEY_SIZE_ATTR.equals(name)  ||
+            ServletRequestImpl.SSL_SESSION_ID_ATTR.equals(name);
+    }
+
+
+    @Override
+    public void addAsyncListener(AsyncListener listener) {
+    }
+
+
+    @Override
+    public void addAsyncListener(AsyncListener listener,
+                                 ServletRequest servletRequest,
+                                 ServletResponse servletResponse) {
+    }
+
+
+    @Override
+    public AsyncContext getAsyncContext() {
+        return null;
+    }
+
+
+    @Override
+    public ServletContext getServletContext() {
+        return null;
+    }
+
+
+    @Override
+    public boolean isAsyncStarted() {
+        return false;
+    }
+
+
+    @Override
+    public boolean isAsyncSupported() {
+        return false;
+    }
+
+
+    @Override
+    public void setAsyncTimeout(long timeout) {
+    }
+
+
+    @Override
+    public AsyncContext startAsync() throws IllegalStateException {
+        return null;
+    }
+
+
+    @Override
+    public AsyncContext startAsync(ServletRequest servletRequest,
+                                   ServletResponse servletResponse)
+            throws IllegalStateException {
+        return null;
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletRequestWrapperImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletRequestWrapperImpl.java
new file mode 100644 (file)
index 0000000..af400d8
--- /dev/null
@@ -0,0 +1,943 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.apache.tomcat.addons.UserSessionManager;
+import org.apache.tomcat.servlets.util.Enumerator;
+import org.apache.tomcat.servlets.util.RequestUtil;
+
+
+/**
+ * Wrapper around a <code>javax.servlet.http.HttpServletRequest</code>
+ * that transforms an application request object (which might be the original
+ * one passed to a servlet, or might be based on the 2.3
+ * <code>javax.servlet.http.HttpServletRequestWrapper</code> class)
+ * back into an internal <code>org.apache.catalina.HttpRequest</code>.
+ * <p>
+ * <strong>WARNING</strong>:  Due to Java's lack of support for multiple
+ * inheritance, all of the logic in <code>ApplicationRequest</code> is
+ * duplicated in <code>ApplicationHttpRequest</code>.  Make sure that you
+ * keep these two classes in synchronization when making changes!
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ */
+public class ServletRequestWrapperImpl extends HttpServletRequestWrapper {
+
+
+    // ------------------------------------------------------- Static Variables
+
+
+    /**
+     * The set of attribute names that are special for request dispatchers.
+     */
+    protected static final String specials[] =
+    { RequestDispatcherImpl.INCLUDE_REQUEST_URI_ATTR, 
+      RequestDispatcherImpl.INCLUDE_CONTEXT_PATH_ATTR,
+      RequestDispatcherImpl.INCLUDE_SERVLET_PATH_ATTR, 
+      RequestDispatcherImpl.INCLUDE_PATH_INFO_ATTR,
+      RequestDispatcherImpl.INCLUDE_QUERY_STRING_ATTR, 
+      RequestDispatcherImpl.FORWARD_REQUEST_URI_ATTR, 
+      RequestDispatcherImpl.FORWARD_CONTEXT_PATH_ATTR, 
+      RequestDispatcherImpl.FORWARD_SERVLET_PATH_ATTR, 
+      RequestDispatcherImpl.FORWARD_PATH_INFO_ATTR, 
+      RequestDispatcherImpl.FORWARD_QUERY_STRING_ATTR };
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new wrapped request around the specified servlet request.
+     *
+     * @param request The servlet request being wrapped
+     */
+    public ServletRequestWrapperImpl(HttpServletRequest request, 
+                                     ServletContextImpl context,
+                                     boolean crossContext) {
+
+        super(request);
+        this.context = context;
+        this.crossContext = crossContext;
+        setRequest(request);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The context for this request.
+     */
+    protected ServletContextImpl context = null;
+
+
+    /**
+     * The context path for this request.
+     */
+    protected String contextPath = null;
+
+
+    /**
+     * If this request is cross context, since this changes session accesss
+     * behavior.
+     */
+    protected boolean crossContext = false;
+
+
+    /**
+     * The current dispatcher type.
+     */
+    protected Object dispatcherType = null;
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.core.ApplicationHttpRequest/1.0";
+
+
+    /**
+     * The request parameters for this request.  This is initialized from the
+     * wrapped request, but updates are allowed.
+     */
+    protected Map parameters = null;
+
+
+    /**
+     * Have the parameters for this request already been parsed?
+     */
+    private boolean parsedParams = false;
+
+
+    /**
+     * The path information for this request.
+     */
+    protected String pathInfo = null;
+
+
+    /**
+     * The query parameters for the current request.
+     */
+    private String queryParamString = null;
+
+
+    /**
+     * The query string for this request.
+     */
+    protected String queryString = null;
+
+
+    /**
+     * The current request dispatcher path.
+     */
+    protected Object requestDispatcherPath = null;
+
+
+    /**
+     * The request URI for this request.
+     */
+    protected String requestURI = null;
+
+
+    /**
+     * The servlet path for this request.
+     */
+    protected String servletPath = null;
+
+
+    /**
+     * The currently active session for this request.
+     */
+    protected HttpSession session = null;
+
+
+    /**
+     * Special attributes.
+     */
+    protected Object[] specialAttributes = new Object[specials.length];
+
+
+    // ------------------------------------------------- ServletRequest Methods
+
+
+    /**
+     * Override the <code>getAttribute()</code> method of the wrapped request.
+     *
+     * @param name Name of the attribute to retrieve
+     */
+    public Object getAttribute(String name) {
+
+        if (name.equals(ServletRequestImpl.DISPATCHER_TYPE_ATTR)) {
+            return dispatcherType;
+        } else if (name.equals(ServletRequestImpl.DISPATCHER_REQUEST_PATH_ATTR)) {
+            if ( requestDispatcherPath != null ){
+                return requestDispatcherPath.toString();
+            } else {
+                return null;   
+            }
+        }
+
+        int pos = getSpecial(name);
+        if (pos == -1) {
+            return getRequest().getAttribute(name);
+        } else {
+            if ((specialAttributes[pos] == null) 
+                && (specialAttributes[5] == null) && (pos >= 5)) {
+                // If it's a forward special attribute, and null, it means this
+                // is an include, so we check the wrapped request since 
+                // the request could have been forwarded before the include
+                return getRequest().getAttribute(name);
+            } else {
+                return specialAttributes[pos];
+            }
+        }
+
+    }
+
+
+    /**
+     * Override the <code>getAttributeNames()</code> method of the wrapped
+     * request.
+     */
+    public Enumeration getAttributeNames() {
+        return (new AttributeNamesEnumerator());
+    }
+
+
+    /**
+     * Override the <code>removeAttribute()</code> method of the
+     * wrapped request.
+     *
+     * @param name Name of the attribute to remove
+     */
+    public void removeAttribute(String name) {
+
+        if (!removeSpecial(name))
+            getRequest().removeAttribute(name);
+
+    }
+
+
+    /**
+     * Override the <code>setAttribute()</code> method of the
+     * wrapped request.
+     *
+     * @param name Name of the attribute to set
+     * @param value Value of the attribute to set
+     */
+    public void setAttribute(String name, Object value) {
+
+        if (name.equals(ServletRequestImpl.DISPATCHER_TYPE_ATTR)) {
+            dispatcherType = value;
+            return;
+        } else if (name.equals(ServletRequestImpl.DISPATCHER_REQUEST_PATH_ATTR)) {
+            requestDispatcherPath = value;
+            return;
+        }
+
+        if (!setSpecial(name, value)) {
+            getRequest().setAttribute(name, value);
+        }
+
+    }
+
+
+    /**
+     * Return a RequestDispatcher that wraps the resource at the specified
+     * path, which may be interpreted as relative to the current request path.
+     *
+     * @param path Path of the resource to be wrapped
+     */
+    public RequestDispatcher getRequestDispatcher(String path) {
+
+        if (context == null)
+            return (null);
+
+        // If the path is already context-relative, just pass it through
+        if (path == null)
+            return (null);
+        else if (path.startsWith("/"))
+            return (context.getServletContext().getRequestDispatcher(path));
+
+        // Convert a request-relative path to a context-relative one
+        String servletPath = 
+            (String) getAttribute(RequestDispatcherImpl.INCLUDE_SERVLET_PATH_ATTR);
+        if (servletPath == null)
+            servletPath = getServletPath();
+
+        // Add the path info, if there is any
+        String pathInfo = getPathInfo();
+        String requestPath = null;
+
+        if (pathInfo == null) {
+            requestPath = servletPath;
+        } else {
+            requestPath = servletPath + pathInfo;
+        }
+
+        int pos = requestPath.lastIndexOf('/');
+        String relative = null;
+        if (pos >= 0) {
+            relative = RequestUtil.normalize
+                (requestPath.substring(0, pos + 1) + path);
+        } else {
+            relative = RequestUtil.normalize(requestPath + path);
+        }
+
+        return (context.getServletContext().getRequestDispatcher(relative));
+
+    }
+
+
+    // --------------------------------------------- HttpServletRequest Methods
+
+
+    /**
+     * Override the <code>getContextPath()</code> method of the wrapped
+     * request.
+     */
+    public String getContextPath() {
+
+        return (this.contextPath);
+
+    }
+
+
+    /**
+     * Override the <code>getParameter()</code> method of the wrapped request.
+     *
+     * @param name Name of the requested parameter
+     */
+    public String getParameter(String name) {
+
+       parseParameters();
+
+        Object value = parameters.get(name);
+        if (value == null)
+            return (null);
+        else if (value instanceof String[])
+            return (((String[]) value)[0]);
+        else if (value instanceof String)
+            return ((String) value);
+        else
+            return (value.toString());
+
+    }
+
+
+    /**
+     * Override the <code>getParameterMap()</code> method of the
+     * wrapped request.
+     */
+    public Map getParameterMap() {
+
+       parseParameters();
+        return (parameters);
+
+    }
+
+
+    /**
+     * Override the <code>getParameterNames()</code> method of the
+     * wrapped request.
+     */
+    public Enumeration getParameterNames() {
+
+       parseParameters();
+        return (new Enumerator(parameters.keySet()));
+
+    }
+
+
+    /**
+     * Override the <code>getParameterValues()</code> method of the
+     * wrapped request.
+     *
+     * @param name Name of the requested parameter
+     */
+    public String[] getParameterValues(String name) {
+
+       parseParameters();
+        Object value = parameters.get(name);
+        if (value == null)
+            return ((String[]) null);
+        else if (value instanceof String[])
+            return ((String[]) value);
+        else if (value instanceof String) {
+            String values[] = new String[1];
+            values[0] = (String) value;
+            return (values);
+        } else {
+            String values[] = new String[1];
+            values[0] = value.toString();
+            return (values);
+        }
+
+    }
+
+
+    /**
+     * Override the <code>getPathInfo()</code> method of the wrapped request.
+     */
+    public String getPathInfo() {
+
+        return (this.pathInfo);
+
+    }
+
+
+    /**
+     * Override the <code>getQueryString()</code> method of the wrapped
+     * request.
+     */
+    public String getQueryString() {
+
+        return (this.queryString);
+
+    }
+
+
+    /**
+     * Override the <code>getRequestURI()</code> method of the wrapped
+     * request.
+     */
+    public String getRequestURI() {
+
+        return (this.requestURI);
+
+    }
+
+
+    /**
+     * Override the <code>getRequestURL()</code> method of the wrapped
+     * request.
+     */
+    public StringBuffer getRequestURL() {
+
+        StringBuffer url = new StringBuffer();
+        String scheme = getScheme();
+        int port = getServerPort();
+        if (port < 0)
+            port = 80; // Work around java.net.URL bug
+
+        url.append(scheme);
+        url.append("://");
+        url.append(getServerName());
+        if ((scheme.equals("http") && (port != 80))
+            || (scheme.equals("https") && (port != 443))) {
+            url.append(':');
+            url.append(port);
+        }
+        url.append(getRequestURI());
+
+        return (url);
+
+    }
+
+
+    /**
+     * Override the <code>getServletPath()</code> method of the wrapped
+     * request.
+     */
+    public String getServletPath() {
+
+        return (this.servletPath);
+
+    }
+
+
+    /**
+     * Return the session associated with this Request, creating one
+     * if necessary.
+     */
+    public HttpSession getSession() {
+        return (getSession(true));
+    }
+
+
+    /**
+     * Return the session associated with this Request, creating one
+     * if necessary and requested.
+     *
+     * @param create Create a new session if one does not exist
+     */
+    public HttpSession getSession(boolean create) {
+
+        if (crossContext) {
+            
+            // There cannot be a session if no context has been assigned yet
+            if (context == null)
+                return (null);
+            UserSessionManager manager = context.getManager();
+            // Return the current session if it exists and is valid
+            if (session != null && manager.isValid(session)) {
+                return session;
+            }
+
+            HttpSession other = super.getSession(false);
+            if (create && (other == null)) {
+                // First create a session in the first context: the problem is
+                // that the top level request is the only one which can 
+                // create the cookie safely
+                other = super.getSession(true);
+            }
+            if (other != null) {
+                HttpSession localSession = null;
+                try {
+                    localSession =
+                      manager.findSession(other.getId());
+                } catch (IOException e) {
+                    // Ignore
+                }
+                if (localSession == null && create) {
+                    localSession = 
+                        context.getManager().createSession(other.getId());
+                }
+                if (localSession != null) {
+                    context.getManager().access(localSession);
+                    session = localSession;
+                    return session;
+                }
+            }
+            return null;
+
+        } else {
+            return super.getSession(create);
+        }
+
+    }
+
+
+    /**
+     * Returns true if the request specifies a JSESSIONID that is valid within
+     * the context of this ApplicationHttpRequest, false otherwise.
+     *
+     * @return true if the request specifies a JSESSIONID that is valid within
+     * the context of this ApplicationHttpRequest, false otherwise.
+     */
+    public boolean isRequestedSessionIdValid() {
+
+        if (crossContext) {
+
+            String requestedSessionId = getRequestedSessionId();
+            if (requestedSessionId == null)
+                return (false);
+            if (context == null)
+                return (false);
+            UserSessionManager manager = context.getManager();
+            if (manager == null)
+                return (false);
+            HttpSession session = null;
+            try {
+                session = manager.findSession(requestedSessionId);
+            } catch (IOException e) {
+                session = null;
+            }
+            if ((session != null) && manager.isValid(session)) {
+                return (true);
+            } else {
+                return (false);
+            }
+
+        } else {
+            return super.isRequestedSessionIdValid();
+        }
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Recycle this request
+     */
+    public void recycle() {
+        if (session != null) {
+            context.getManager().endAccess(session);
+        }
+    }
+
+
+    /**
+     * Return descriptive information about this implementation.
+     */
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    /**
+     * Perform a shallow copy of the specified Map, and return the result.
+     *
+     * @param orig Origin Map to be copied
+     */
+    Map copyMap(Map orig) {
+
+        if (orig == null)
+            return (new HashMap());
+        HashMap dest = new HashMap();
+        Iterator keys = orig.keySet().iterator();
+        while (keys.hasNext()) {
+            String key = (String) keys.next();
+            dest.put(key, orig.get(key));
+        }
+        return (dest);
+
+    }
+
+
+    /**
+     * Set the context path for this request.
+     *
+     * @param contextPath The new context path
+     */
+    void setContextPath(String contextPath) {
+
+        this.contextPath = contextPath;
+
+    }
+
+
+    /**
+     * Set the path information for this request.
+     *
+     * @param pathInfo The new path info
+     */
+    void setPathInfo(String pathInfo) {
+
+        this.pathInfo = pathInfo;
+
+    }
+
+
+    /**
+     * Set the query string for this request.
+     *
+     * @param queryString The new query string
+     */
+    void setQueryString(String queryString) {
+
+        this.queryString = queryString;
+
+    }
+
+
+    /**
+     * Set the request that we are wrapping.
+     *
+     * @param request The new wrapped request
+     */
+    void setRequest(HttpServletRequest request) {
+
+        super.setRequest(request);
+
+        // Initialize the attributes for this request
+        dispatcherType = request.getAttribute(ServletRequestImpl.DISPATCHER_TYPE_ATTR);
+        requestDispatcherPath = 
+            request.getAttribute(ServletRequestImpl.DISPATCHER_REQUEST_PATH_ATTR);
+
+        // Initialize the path elements for this request
+        contextPath = request.getContextPath();
+        pathInfo = request.getPathInfo();
+        queryString = request.getQueryString();
+        requestURI = request.getRequestURI();
+        servletPath = request.getServletPath();
+
+    }
+
+
+    /**
+     * Set the request URI for this request.
+     *
+     * @param requestURI The new request URI
+     */
+    void setRequestURI(String requestURI) {
+
+        this.requestURI = requestURI;
+
+    }
+
+
+    /**
+     * Set the servlet path for this request.
+     *
+     * @param servletPath The new servlet path
+     */
+    void setServletPath(String servletPath) {
+
+        this.servletPath = servletPath;
+
+    }
+
+
+    /**
+     * Parses the parameters of this request.
+     *
+     * If parameters are present in both the query string and the request
+     * content, they are merged.
+     */
+    void parseParameters() {
+
+       if (parsedParams) {
+           return;
+       }
+
+        parameters = new HashMap();
+        parameters = copyMap(getRequest().getParameterMap());
+        mergeParameters();
+        parsedParams = true;
+    }
+
+
+    /**
+     * Save query parameters for this request.
+     *
+     * @param queryString The query string containing parameters for this
+     *                    request
+     */
+    void setQueryParams(String queryString) {
+        this.queryParamString = queryString;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Is this attribute name one of the special ones that is added only for
+     * included servlets?
+     *
+     * @param name Attribute name to be tested
+     */
+    protected boolean isSpecial(String name) {
+
+        for (int i = 0; i < specials.length; i++) {
+            if (specials[i].equals(name))
+                return (true);
+        }
+        return (false);
+
+    }
+
+
+    /**
+     * Get a special attribute.
+     *
+     * @return the special attribute pos, or -1 if it is not a special 
+     *         attribute
+     */
+    protected int getSpecial(String name) {
+        for (int i = 0; i < specials.length; i++) {
+            if (specials[i].equals(name)) {
+                return (i);
+            }
+        }
+        return (-1);
+    }
+
+
+    /**
+     * Set a special attribute.
+     * 
+     * @return true if the attribute was a special attribute, false otherwise
+     */
+    protected boolean setSpecial(String name, Object value) {
+        for (int i = 0; i < specials.length; i++) {
+            if (specials[i].equals(name)) {
+                specialAttributes[i] = value;
+                return (true);
+            }
+        }
+        return (false);
+    }
+
+
+    /**
+     * Remove a special attribute.
+     * 
+     * @return true if the attribute was a special attribute, false otherwise
+     */
+    protected boolean removeSpecial(String name) {
+        for (int i = 0; i < specials.length; i++) {
+            if (specials[i].equals(name)) {
+                specialAttributes[i] = null;
+                return (true);
+            }
+        }
+        return (false);
+    }
+
+
+    /**
+     * Merge the two sets of parameter values into a single String array.
+     *
+     * @param values1 First set of values
+     * @param values2 Second set of values
+     */
+    protected String[] mergeValues(Object values1, Object values2) {
+
+        ArrayList results = new ArrayList();
+
+        if (values1 == null)
+            ;
+        else if (values1 instanceof String)
+            results.add(values1);
+        else if (values1 instanceof String[]) {
+            String values[] = (String[]) values1;
+            for (int i = 0; i < values.length; i++)
+                results.add(values[i]);
+        } else
+            results.add(values1.toString());
+
+        if (values2 == null)
+            ;
+        else if (values2 instanceof String)
+            results.add(values2);
+        else if (values2 instanceof String[]) {
+            String values[] = (String[]) values2;
+            for (int i = 0; i < values.length; i++)
+                results.add(values[i]);
+        } else
+            results.add(values2.toString());
+
+        String values[] = new String[results.size()];
+        return ((String[]) results.toArray(values));
+
+    }
+
+
+    // ------------------------------------------------------ Private Methods
+
+
+    /**
+     * Merge the parameters from the saved query parameter string (if any), and
+     * the parameters already present on this request (if any), such that the
+     * parameter values from the query string show up first if there are
+     * duplicate parameter names.
+     */
+    private void mergeParameters() {
+
+        if ((queryParamString == null) || (queryParamString.length() < 1))
+            return;
+
+        HashMap queryParameters = new HashMap();
+        String encoding = getCharacterEncoding();
+        if (encoding == null)
+            encoding = "ISO-8859-1";
+        try {
+            RequestUtil.parseParameters
+                (queryParameters, queryParamString, encoding);
+        } catch (Exception e) {
+            ;
+        }
+        Iterator keys = parameters.keySet().iterator();
+        while (keys.hasNext()) {
+            String key = (String) keys.next();
+            Object value = queryParameters.get(key);
+            if (value == null) {
+                queryParameters.put(key, parameters.get(key));
+                continue;
+            }
+            queryParameters.put
+                (key, mergeValues(value, parameters.get(key)));
+        }
+        parameters = queryParameters;
+
+    }
+
+
+    // ----------------------------------- AttributeNamesEnumerator Inner Class
+
+
+    /**
+     * Utility class used to expose the special attributes as being available
+     * as request attributes.
+     */
+    protected class AttributeNamesEnumerator implements Enumeration {
+
+        protected int pos = -1;
+        protected int last = -1;
+        protected Enumeration parentEnumeration = null;
+        protected String next = null;
+
+        public AttributeNamesEnumerator() {
+            parentEnumeration = getRequest().getAttributeNames();
+            for (int i = 0; i < specialAttributes.length; i++) {
+                if (getAttribute(specials[i]) != null) {
+                    last = i;
+                }
+            }
+        }
+
+        public boolean hasMoreElements() {
+            return ((pos != last) || (next != null) 
+                    || ((next = findNext()) != null));
+        }
+
+        public Object nextElement() {
+            if (pos != last) {
+                for (int i = pos + 1; i <= last; i++) {
+                    if (getAttribute(specials[i]) != null) {
+                        pos = i;
+                        return (specials[i]);
+                    }
+                }
+            }
+            String result = next;
+            if (next != null) {
+                next = findNext();
+            } else {
+                throw new NoSuchElementException();
+            }
+            return result;
+        }
+
+        protected String findNext() {
+            String result = null;
+            while ((result == null) && (parentEnumeration.hasMoreElements())) {
+                String current = (String) parentEnumeration.nextElement();
+                if (!isSpecial(current)) {
+                    result = current;
+                }
+            }
+            return result;
+        }
+
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletResponseImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletResponseImpl.java
new file mode 100644 (file)
index 0000000..42e1802
--- /dev/null
@@ -0,0 +1,1449 @@
+/*
+ * 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.tomcat.lite;
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.Vector;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.coyote.Response;
+import org.apache.tomcat.lite.coyote.MessageWriter;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.buf.UEncoder;
+import org.apache.tomcat.util.http.FastHttpDateFormat;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.http.ServerCookie;
+
+/**
+ * Wrapper object for the Coyote response.
+ *
+ * @author Remy Maucherat
+ * @author Craig R. McClanahan
+ * @version $Revision: 371866 $ $Date: 2006-01-24 00:52:54 -0800 (Tue, 24 Jan 2006) $
+ */
+
+public class ServletResponseImpl
+    implements HttpServletResponse {
+
+    /** 
+     * Format for http response header date field
+     * From DateTool
+     */
+    public static final String HTTP_RESPONSE_DATE_HEADER =
+        "EEE, dd MMM yyyy HH:mm:ss zzz";
+
+    // ----------------------------------------------------------- Constructors
+
+    private Response resB;
+    
+    ServletResponseImpl() {
+        urlEncoder.addSafeCharacter('/');
+    }
+
+
+    // ----------------------------------------------------- Class Variables
+
+
+    /**
+     * Descriptive information about this Response implementation.
+     */
+    protected static final String info =
+        "org.apache.tomcat.lite/1.0";
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The date format we will use for creating date headers.
+     */
+    protected SimpleDateFormat format = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Set the Connector through which this Request was received.
+     *
+     * @param connector The new connector
+     */
+    public void setConnector() {
+        // default size to size of one ajp-packet
+        outputBuffer = MessageWriter.getWriter(req.getCoyoteRequest(), getCoyoteResponse(), 4096);
+        outputStream = new ServletOutputStreamImpl(outputBuffer);
+    }
+
+    /**
+     * Coyote response.
+     */
+    //protected org.apache.coyote.Response coyoteResponse;
+
+
+    /**
+     * The associated output buffer.
+     */
+    protected MessageWriter outputBuffer;
+
+
+    /**
+     * The associated output stream.
+     */
+    protected ServletOutputStreamImpl outputStream;
+
+
+    /**
+     * The associated writer.
+     */
+    protected PrintWriter writer;
+
+
+    /**
+     * The application commit flag.
+     */
+    protected boolean appCommitted = false;
+
+
+    /**
+     * The included flag.
+     */
+    protected boolean included = false;
+
+    
+    /**
+     * The characterEncoding flag
+     */
+    private boolean isCharacterEncodingSet = false;
+    
+    /**
+     * The error flag.
+     */
+    protected boolean error = false;
+
+
+    /**
+     * The set of Cookies associated with this Response.
+     */
+    protected ArrayList cookies = new ArrayList();
+
+
+    /**
+     * Using output stream flag.
+     */
+    protected boolean usingOutputStream = false;
+
+
+    /**
+     * Using writer flag.
+     */
+    protected boolean usingWriter = false;
+
+
+    /**
+     * URL encoder.
+     */
+    protected UEncoder urlEncoder = new UEncoder();
+
+
+    /**
+     * Recyclable buffer to hold the redirect URL.
+     */
+    protected CharChunk redirectURLCC = new CharChunk(1024);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Release all object references, and initialize instance variables, in
+     * preparation for reuse of this object.
+     */
+    public void recycle() {
+
+        outputBuffer.recycle();
+        usingOutputStream = false;
+        usingWriter = false;
+        appCommitted = false;
+        included = false;
+        error = false;
+        isCharacterEncodingSet = false;
+        
+        cookies.clear();
+
+        outputBuffer.recycle();
+
+    }
+
+
+    // ------------------------------------------------------- Response Methods
+
+
+    /**
+     * Return the number of bytes actually written to the output stream.
+     */
+    public int getContentCount() {
+        return outputBuffer.getContentWritten();
+    }
+
+
+    /**
+     * Set the application commit flag.
+     * 
+     * @param appCommitted The new application committed flag value
+     */
+    public void setAppCommitted(boolean appCommitted) {
+        this.appCommitted = appCommitted;
+    }
+
+
+    /**
+     * Application commit flag accessor.
+     */
+    public boolean isAppCommitted() {
+        return (this.appCommitted || isCommitted() || isSuspended()
+                || ((getContentLength() > 0) 
+                    && (getContentCount() >= getContentLength())));
+    }
+
+
+    /**
+     * Return the "processing inside an include" flag.
+     */
+    public boolean getIncluded() {
+        return included;
+    }
+
+
+    /**
+     * Set the "processing inside an include" flag.
+     *
+     * @param included <code>true</code> if we are currently inside a
+     *  RequestDispatcher.include(), else <code>false</code>
+     */
+    public void setIncluded(boolean included) {
+        this.included = included;
+    }
+
+
+    /**
+     * Return descriptive information about this Response implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo() {
+        return (info);
+    }
+
+
+    /**
+     * The request with which this response is associated.
+     */
+    protected ServletRequestImpl req = null;
+
+    /**
+     * Return the Request with which this Response is associated.
+     */
+    public ServletRequestImpl getRequest() {
+        return (this.req);
+    }
+
+    /**
+     * Set the Request with which this Response is associated.
+     *
+     * @param request The new associated request
+     */
+    public void setRequest(ServletRequestImpl request) {
+        this.req = (ServletRequestImpl) request;
+    }
+
+
+    /**
+     * Return the output stream associated with this Response.
+     */
+    public OutputStream getStream() {
+        if (outputStream == null) {
+            outputStream = new ServletOutputStreamImpl(outputBuffer);
+        }
+        return outputStream;
+    }
+
+
+    /**
+     * Set the output stream associated with this Response.
+     *
+     * @param stream The new output stream
+     */
+    public void setStream(OutputStream stream) {
+        // This method is evil
+    }
+
+
+    /**
+     * Set the suspended flag.
+     * 
+     * @param suspended The new suspended flag value
+     */
+    public void setSuspended(boolean suspended) throws IOException {
+        //coyoteResponse.setCommitted(true);
+        flushBuffer();
+        outputBuffer.setSuspended(suspended);
+    }
+
+
+    /**
+     * Suspended flag accessor.
+     */
+    public boolean isSuspended() {
+        return outputBuffer.isSuspended();
+    }
+
+
+    /**
+     * Set the error flag.
+     */
+    public void setError() {
+        error = true;
+    }
+
+
+    /**
+     * Error flag accessor.
+     */
+    public boolean isError() {
+        return error;
+    }
+
+
+    /**
+     * Create and return a ServletOutputStream to write the content
+     * associated with this Response.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public ServletOutputStream createOutputStream() 
+        throws IOException {
+        // Probably useless
+        if (outputStream == null) {
+            outputStream = new ServletOutputStreamImpl(outputBuffer);
+        }
+        return outputStream;
+    }
+
+
+    /**
+     * Return the content length that was set or calculated for this Response.
+     */
+    public int getContentLength() {
+        return getCoyoteResponse().getContentLength();
+    }
+
+
+    /**
+     * Return the content type that was set or calculated for this response,
+     * or <code>null</code> if no content type was set.
+     */
+    public String getContentType() {
+        return getCoyoteResponse().getContentType();
+    }
+
+
+    /**
+     * Return a PrintWriter that can be used to render error messages,
+     * regardless of whether a stream or writer has already been acquired.
+     *
+     * @return Writer which can be used for error reports. If the response is
+     * not an error report returned using sendError or triggered by an
+     * unexpected exception thrown during the servlet processing
+     * (and only in that case), null will be returned if the response stream
+     * has already been used.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public PrintWriter getReporter() throws IOException {
+        if (outputBuffer.isNew()) {
+            outputBuffer.checkConverter();
+            if (writer == null) {
+                writer = new ServletWriterImpl(outputBuffer);
+            }
+            return writer;
+        } else {
+            return null;
+        }
+    }
+
+
+    // ------------------------------------------------ ServletResponse Methods
+
+
+    /**
+     * Flush the buffer and commit this response.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public void flushBuffer() 
+        throws IOException {
+        outputBuffer.flush();
+    }
+
+
+    /**
+     * Return the actual buffer size used for this Response.
+     */
+    public int getBufferSize() {
+        return outputBuffer.getBufferSize();
+    }
+
+
+    /**
+     * Return the character encoding used for this Response.
+     */
+    public String getCharacterEncoding() {
+        return (getCoyoteResponse().getCharacterEncoding());
+    }
+
+
+    /**
+     * Return the servlet output stream associated with this Response.
+     *
+     * @exception IllegalStateException if <code>getWriter</code> has
+     *  already been called for this response
+     * @exception IOException if an input/output error occurs
+     */
+    public ServletOutputStream getOutputStream() 
+        throws IOException {
+
+        if (usingWriter)
+            throw new IllegalStateException
+                ("usingWriter");
+
+        usingOutputStream = true;
+        if (outputStream == null) {
+            outputStream = new ServletOutputStreamImpl(outputBuffer);
+        }
+        return outputStream;
+
+    }
+
+
+    /**
+     * Return the Locale assigned to this response.
+     */
+    public Locale getLocale() {
+        return (getCoyoteResponse().getLocale());
+    }
+
+
+    /**
+     * Return the writer associated with this Response.
+     *
+     * @exception IllegalStateException if <code>getOutputStream</code> has
+     *  already been called for this response
+     * @exception IOException if an input/output error occurs
+     */
+    public PrintWriter getWriter() 
+        throws IOException {
+
+        if (usingOutputStream)
+            throw new IllegalStateException
+                ("usingOutputStream");
+
+        /*
+         * If the response's character encoding has not been specified as
+         * described in <code>getCharacterEncoding</code> (i.e., the method
+         * just returns the default value <code>ISO-8859-1</code>),
+         * <code>getWriter</code> updates it to <code>ISO-8859-1</code>
+         * (with the effect that a subsequent call to getContentType() will
+         * include a charset=ISO-8859-1 component which will also be
+         * reflected in the Content-Type response header, thereby satisfying
+         * the Servlet spec requirement that containers must communicate the
+         * character encoding used for the servlet response's writer to the
+         * client).
+         */
+        setCharacterEncoding(getCharacterEncoding());
+
+        usingWriter = true;
+        outputBuffer.checkConverter();
+        if (writer == null) {
+            writer = new ServletWriterImpl(outputBuffer);
+        }
+        return writer;
+
+    }
+
+
+    /**
+     * Has the output of this response already been committed?
+     */
+    public boolean isCommitted() {
+        return (getCoyoteResponse().isCommitted());
+    }
+
+
+    /**
+     * Clear any content written to the buffer.
+     *
+     * @exception IllegalStateException if this response has already
+     *  been committed
+     */
+    public void reset() {
+
+        if (included)
+            return;     // Ignore any call from an included servlet
+
+        getCoyoteResponse().reset();
+        //req.con.reset();
+        outputBuffer.reset();
+    }
+
+
+    /**
+     * Reset the data buffer but not any status or header information.
+     *
+     * @exception IllegalStateException if the response has already
+     *  been committed
+     */
+    public void resetBuffer() {
+
+        if (isCommitted())
+            throw new IllegalStateException("isCommitted");
+
+        outputBuffer.reset();
+
+    }
+
+
+    /**
+     * Set the buffer size to be used for this Response.
+     *
+     * @param size The new buffer size
+     *
+     * @exception IllegalStateException if this method is called after
+     *  output has been committed for this response
+     */
+    public void setBufferSize(int size) {
+
+        if (isCommitted() || !outputBuffer.isNew())
+            throw new IllegalStateException
+                ("isCommitted || !isNew");
+
+        outputBuffer.setBufferSize(size);
+
+    }
+
+
+    /**
+     * Set the content length (in bytes) for this Response.
+     * Ignored for writers if non-ISO-8859-1 encoding ( we could add more 
+     * encodings that are constant.
+     */
+    public void setContentLength(int length) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+        
+        // writers can use variable-length encoding. 
+        if (usingWriter && !"ISO-8859-1".equals(getCharacterEncoding())) {
+            return;
+        }
+        getCoyoteResponse().setContentLength(length);
+
+    }
+
+
+    /**
+     * Set the content type for this Response.
+     *
+     * @param type The new content type
+     */
+    public void setContentType(String type) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        // Ignore charset if getWriter() has already been called
+        if (usingWriter) {
+            if (type != null) {
+                int index = type.indexOf(";");
+                if (index != -1) {
+                    type = type.substring(0, index);
+                }
+            }
+        }
+
+        getCoyoteResponse().setContentType(type);
+
+        // Check to see if content type contains charset
+        if (type != null) {
+            int index = type.indexOf(";");
+            if (index != -1) {
+                int len = type.length();
+                index++;
+                while (index < len && Character.isSpace(type.charAt(index))) {
+                    index++;
+                }
+                if (index+7 < len
+                        && type.charAt(index) == 'c'
+                        && type.charAt(index+1) == 'h'
+                        && type.charAt(index+2) == 'a'
+                        && type.charAt(index+3) == 'r'
+                        && type.charAt(index+4) == 's'
+                        && type.charAt(index+5) == 'e'
+                        && type.charAt(index+6) == 't'
+                        && type.charAt(index+7) == '=') {
+                    isCharacterEncodingSet = true;
+                }
+            }
+        }
+    }
+
+
+    /*
+     * Overrides the name of the character encoding used in the body
+     * of the request. This method must be called prior to reading
+     * request parameters or reading input using getReader().
+     *
+     * @param charset String containing the name of the chararacter encoding.
+     */
+    public void setCharacterEncoding(String charset) {
+
+        if (isCommitted())
+            return;
+        
+        // Ignore any call from an included servlet
+        if (included)
+            return;     
+        
+        // Ignore any call made after the getWriter has been invoked
+        // The default should be used
+        if (usingWriter)
+            return;
+
+        getCoyoteResponse().setCharacterEncoding(charset);
+        isCharacterEncodingSet = true;
+    }
+
+    
+    
+    /**
+     * Set the Locale that is appropriate for this response, including
+     * setting the appropriate character encoding.
+     *
+     * @param locale The new locale
+     */
+    public void setLocale(Locale locale) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        getCoyoteResponse().setLocale(locale);
+
+        // Ignore any call made after the getWriter has been invoked.
+        // The default should be used
+        if (usingWriter)
+            return;
+
+        if (isCharacterEncodingSet) {
+            return;
+        }
+
+        Locale2Charset cm = req.getContext().getCharsetMapper();
+        String charset = cm.getCharset( locale );
+        if ( charset != null ){
+            getCoyoteResponse().setCharacterEncoding(charset);
+        }
+
+    }
+
+
+    // --------------------------------------------------- HttpResponse Methods
+
+
+    /**
+     * Return an array of all cookies set for this response, or
+     * a zero-length array if no cookies have been set.
+     */
+    public Cookie[] getCookies() {
+        return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()]));
+    }
+
+
+    /**
+     * Return the value for the specified header, or <code>null</code> if this
+     * header has not been set.  If more than one value was added for this
+     * name, only the first is returned; use getHeaderValues() to retrieve all
+     * of them.
+     *
+     * @param name Header name to look up
+     */
+    public String getHeader(String name) {
+        return getCoyoteResponse().getMimeHeaders().getHeader(name);
+    }
+
+
+    /**
+     * Return an array of all the header names set for this response, or
+     * a zero-length array if no headers have been set.
+     */
+    public String[] getHeaderNames() {
+
+        MimeHeaders headers = getCoyoteResponse().getMimeHeaders();
+        int n = headers.size();
+        String[] result = new String[n];
+        for (int i = 0; i < n; i++) {
+            result[i] = headers.getName(i).toString();
+        }
+        return result;
+
+    }
+
+
+    /**
+     * Return an array of all the header values associated with the
+     * specified header name, or an zero-length array if there are no such
+     * header values.
+     *
+     * @param name Header name to look up
+     */
+    public String[] getHeaderValues(String name) {
+
+        Enumeration enumeration = getCoyoteResponse().getMimeHeaders().values(name);
+        Vector result = new Vector();
+        while (enumeration.hasMoreElements()) {
+            result.addElement(enumeration.nextElement());
+        }
+        String[] resultArray = new String[result.size()];
+        result.copyInto(resultArray);
+        return resultArray;
+
+    }
+
+
+    /**
+     * Return the error message that was set with <code>sendError()</code>
+     * for this Response.
+     */
+    public String getMessage() {
+        return getCoyoteResponse().getMessage();
+    }
+
+
+    /**
+     * Return the HTTP status code associated with this Response.
+     */
+    public int getStatus() {
+        return getCoyoteResponse().getStatus();
+    }
+
+
+    /**
+     * Reset this response, and specify the values for the HTTP status code
+     * and corresponding message.
+     *
+     * @exception IllegalStateException if this response has already been
+     *  committed
+     */
+    public void reset(int status, String message) {
+        reset();
+        setStatus(status, message);
+    }
+
+
+    // -------------------------------------------- HttpServletResponse Methods
+
+
+    /**
+     * Add the specified Cookie to those that will be included with
+     * this Response.
+     *
+     * @param cookie Cookie to be added
+     */
+    public void addCookie(final Cookie cookie) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        cookies.add(cookie);
+
+        final StringBuffer sb = new StringBuffer();
+        ServerCookie.appendCookieValue
+        (sb, cookie.getVersion(), cookie.getName(), cookie.getValue(),
+                cookie.getPath(), cookie.getDomain(), cookie.getComment(), 
+                cookie.getMaxAge(), cookie.getSecure(), false);
+
+        // the header name is Set-Cookie for both "old" and v.1 ( RFC2109 )
+        // RFC2965 is not supported by browsers and the Servlet spec
+        // asks for 2109.
+        addHeader("Set-Cookie", sb.toString());
+
+    }
+
+
+    /**
+     * Add the specified date header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Date value to be set
+     */
+    public void addDateHeader(String name, long value) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included) {
+            return;
+        }
+
+        if (format == null) {
+            format = new SimpleDateFormat(HTTP_RESPONSE_DATE_HEADER,
+                                          Locale.US);
+            format.setTimeZone(TimeZone.getTimeZone("GMT"));
+        }
+
+        addHeader(name, FastHttpDateFormat.formatDate(value, format));
+
+    }
+
+
+    /**
+     * Add the specified header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Value to be set
+     */
+    public void addHeader(String name, String value) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        getCoyoteResponse().addHeader(name, value);
+
+    }
+
+
+    /**
+     * Add the specified integer header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Integer value to be set
+     */
+    public void addIntHeader(String name, int value) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        addHeader(name, "" + value);
+
+    }
+
+
+    /**
+     * Has the specified header been set already in this response?
+     *
+     * @param name Name of the header to check
+     */
+    public boolean containsHeader(String name) {
+        // Need special handling for Content-Type and Content-Length due to
+        // special handling of these in coyoteResponse
+        char cc=name.charAt(0);
+        if(cc=='C' || cc=='c') {
+            if(name.equalsIgnoreCase("Content-Type")) {
+                // Will return null if this has not been set
+                return (getCoyoteResponse().getContentType() != null);
+            }
+            if(name.equalsIgnoreCase("Content-Length")) {
+                // -1 means not known and is not sent to client
+                return (getCoyoteResponse().getContentLengthLong() != -1);
+            }
+        }
+
+        return getCoyoteResponse().containsHeader(name);
+    }
+
+
+    /**
+     * Encode the session identifier associated with this response
+     * into the specified redirect URL, if necessary.
+     *
+     * @param url URL to be encoded
+     */
+    public String encodeRedirectURL(String url) {
+
+        if (isEncodeable(toAbsolute(url))) {
+            return (toEncoded(url, req.getSession().getId()));
+        } else {
+            return (url);
+        }
+
+    }
+
+
+    /**
+     * Encode the session identifier associated with this response
+     * into the specified redirect URL, if necessary.
+     *
+     * @param url URL to be encoded
+     *
+     * @deprecated As of Version 2.1 of the Java Servlet API, use
+     *  <code>encodeRedirectURL()</code> instead.
+     */
+    public String encodeRedirectUrl(String url) {
+        return (encodeRedirectURL(url));
+    }
+
+
+    /**
+     * Encode the session identifier associated with this response
+     * into the specified URL, if necessary.
+     *
+     * @param url URL to be encoded
+     */
+    public String encodeURL(String url) {
+        
+        String absolute = toAbsolute(url);
+        if (isEncodeable(absolute)) {
+            // W3c spec clearly said 
+            if (url.equalsIgnoreCase("")){
+                url = absolute;
+            }
+            return (toEncoded(url, req.getSession().getId()));
+        } else {
+            return (url);
+        }
+
+    }
+
+
+    /**
+     * Encode the session identifier associated with this response
+     * into the specified URL, if necessary.
+     *
+     * @param url URL to be encoded
+     *
+     * @deprecated As of Version 2.1 of the Java Servlet API, use
+     *  <code>encodeURL()</code> instead.
+     */
+    public String encodeUrl(String url) {
+        return (encodeURL(url));
+    }
+
+
+    /**
+     * Send an acknowledgment of a request.
+     * 
+     * @exception IOException if an input/output error occurs
+     */
+    public void sendAcknowledgement()
+        throws IOException {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return; 
+
+        getCoyoteResponse().acknowledge();
+
+    }
+
+
+    /**
+     * Send an error response with the specified status and a
+     * default message.
+     *
+     * @param status HTTP status code to send
+     *
+     * @exception IllegalStateException if this response has
+     *  already been committed
+     * @exception IOException if an input/output error occurs
+     */
+    public void sendError(int status) 
+        throws IOException {
+        sendError(status, null);
+    }
+
+
+    /**
+     * Send an error response with the specified status and message.
+     *
+     * @param status HTTP status code to send
+     * @param message Corresponding message to send
+     *
+     * @exception IllegalStateException if this response has
+     *  already been committed
+     * @exception IOException if an input/output error occurs
+     */
+    public void sendError(int status, String message) 
+        throws IOException {
+
+        if (isCommitted())
+            throw new IllegalStateException
+                ("isCommitted");
+
+        // Ignore any call from an included servlet
+        if (included)
+            return; 
+
+        setError();
+
+        getCoyoteResponse().setStatus(status);
+        getCoyoteResponse().setMessage(message);
+
+        // Clear any data content that has been buffered
+        resetBuffer();
+
+        // Cause the response to be finished (from the application perspective)
+        String statusPage = req.getContext().findStatusPage(status);
+
+        if (statusPage != null) {
+            req.getContext().handleStatusPage(req, this, status, statusPage);
+        } else {
+            // Send a default message body.
+            // TODO: maybe other mechanism to customize default.
+            defaultStatusPage(status, message);
+        }
+        setSuspended(true);        
+    }
+
+    /** 
+     * Default handler for status code != 200
+     */
+    void defaultStatusPage(int status, String message)
+            throws IOException {
+        setContentType("text/html");
+        if (status > 400 && status < 600) {
+            if (getOutputBuffer().getBytesWritten() == 0) {
+                getOutputBuffer().write("<html><body><h1>Status: " + 
+                        status + "</h1><h1>Message: " + message + 
+                        "</h1></body></html>");
+                getOutputBuffer().flush();
+            }
+        }
+    }
+
+    
+
+    /**
+     * Send a temporary redirect to the specified redirect location URL.
+     *
+     * @param location Location URL to redirect to
+     *
+     * @exception IllegalStateException if this response has
+     *  already been committed
+     * @exception IOException if an input/output error occurs
+     */
+    public void sendRedirect(String location) 
+        throws IOException {
+
+        if (isCommitted())
+            throw new IllegalStateException
+                ("isCommitted");
+
+        // Ignore any call from an included servlet
+        if (included)
+            return; 
+
+        // Clear any data content that has been buffered
+        resetBuffer();
+
+        // Generate a temporary redirect to the specified location
+        try {
+            String absolute = toAbsolute(location);
+            setStatus(SC_FOUND);
+            setHeader("Location", absolute);
+        } catch (IllegalArgumentException e) {
+            setStatus(SC_NOT_FOUND);
+        }
+
+        // Cause the response to be finished (from the application perspective)
+        setSuspended(true);
+
+    }
+
+
+    /**
+     * Set the specified date header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Date value to be set
+     */
+    public void setDateHeader(String name, long value) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included) {
+            return;
+        }
+
+        if (format == null) {
+            format = new SimpleDateFormat(HTTP_RESPONSE_DATE_HEADER,
+                                          Locale.US);
+            format.setTimeZone(TimeZone.getTimeZone("GMT"));
+        }
+
+        setHeader(name, FastHttpDateFormat.formatDate(value, format));
+
+    }
+
+
+    /**
+     * Set the specified header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Value to be set
+     */
+    public void setHeader(String name, String value) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        getCoyoteResponse().setHeader(name, value);
+
+    }
+
+
+    /**
+     * Set the specified integer header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Integer value to be set
+     */
+    public void setIntHeader(String name, int value) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        setHeader(name, "" + value);
+
+    }
+
+
+    /**
+     * Set the HTTP status to be returned with this response.
+     *
+     * @param status The new HTTP status
+     */
+    public void setStatus(int status) {
+        setStatus(status, null);
+    }
+
+
+    /**
+     * Set the HTTP status and message to be returned with this response.
+     *
+     * @param status The new HTTP status
+     * @param message The associated text message
+     *
+     * @deprecated As of Version 2.1 of the Java Servlet API, this method
+     *  has been deprecated due to the ambiguous meaning of the message
+     *  parameter.
+     */
+    public void setStatus(int status, String message) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        getCoyoteResponse().setStatus(status);
+        getCoyoteResponse().setMessage(message);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return <code>true</code> if the specified URL should be encoded with
+     * a session identifier.  This will be true if all of the following
+     * conditions are met:
+     * <ul>
+     * <li>The request we are responding to asked for a valid session
+     * <li>The requested session ID was not received via a cookie
+     * <li>The specified URL points back to somewhere within the web
+     *     application that is responding to this request
+     * </ul>
+     *
+     * @param location Absolute URL to be validated
+     */
+    protected boolean isEncodeable(final String location) {
+
+        if (location == null)
+            return (false);
+
+        // Is this an intra-document reference?
+        if (location.startsWith("#"))
+            return (false);
+
+        // Are we in a valid session that is not using cookies?
+        final ServletRequestImpl hreq = req;
+        final HttpSession session = hreq.getSession(false);
+        if (session == null)
+            return (false);
+        if (hreq.isRequestedSessionIdFromCookie())
+            return (false);
+        
+        // Is this a valid absolute URL?
+        URL url = null;
+        try {
+            url = new URL(location);
+        } catch (MalformedURLException e) {
+            return (false);
+        }
+
+        // Does this URL match down to (and including) the context path?
+        if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol()))
+            return (false);
+        if (!hreq.getServerName().equalsIgnoreCase(url.getHost()))
+            return (false);
+        int serverPort = hreq.getServerPort();
+        if (serverPort == -1) {
+            if ("https".equals(hreq.getScheme()))
+                serverPort = 443;
+            else
+                serverPort = 80;
+        }
+        int urlPort = url.getPort();
+        if (urlPort == -1) {
+            if ("https".equals(url.getProtocol()))
+                urlPort = 443;
+            else
+                urlPort = 80;
+        }
+        if (serverPort != urlPort)
+            return (false);
+
+        String contextPath = req.getContext().getContextPath();
+        if (contextPath != null) {
+            String file = url.getFile();
+            if ((file == null) || !file.startsWith(contextPath))
+                return (false);
+            if( file.indexOf(";jsessionid=" + session.getId()) >= 0 )
+                return (false);
+        }
+
+        // This URL belongs to our web application, so it is encodeable
+        return (true);
+
+    }
+
+
+    /**
+     * Convert (if necessary) and return the absolute URL that represents the
+     * resource referenced by this possibly relative URL.  If this URL is
+     * already absolute, return it unchanged.
+     *
+     * @param location URL to be (possibly) converted and then returned
+     *
+     * @exception IllegalArgumentException if a MalformedURLException is
+     *  thrown when converting the relative URL to an absolute one
+     */
+    private String toAbsolute(String location) {
+
+        if (location == null)
+            return (location);
+
+        boolean leadingSlash = location.startsWith("/");
+
+        if (leadingSlash || !hasScheme(location)) {
+
+            redirectURLCC.recycle();
+
+            String scheme = req.getScheme();
+            String name = req.getServerName();
+            int port = req.getServerPort();
+
+            try {
+                redirectURLCC.append(scheme, 0, scheme.length());
+                redirectURLCC.append("://", 0, 3);
+                redirectURLCC.append(name, 0, name.length());
+                if ((scheme.equals("http") && port != 80)
+                    || (scheme.equals("https") && port != 443)) {
+                    redirectURLCC.append(':');
+                    String portS = port + "";
+                    redirectURLCC.append(portS, 0, portS.length());
+                }
+                if (!leadingSlash) {
+                    String relativePath = req.getDecodedRequestURI();
+                    int pos = relativePath.lastIndexOf('/');
+                    relativePath = relativePath.substring(0, pos);
+                    
+                    String encodedURI = null;
+                    encodedURI = urlEncoder.encodeURL(relativePath);
+                    redirectURLCC.append(encodedURI, 0, encodedURI.length());
+                    redirectURLCC.append('/');
+                }
+                redirectURLCC.append(location, 0, location.length());
+            } catch (IOException e) {
+                IllegalArgumentException iae =
+                    new IllegalArgumentException(location);
+                iae.initCause(e);
+                throw iae;
+            }
+
+            return redirectURLCC.toString();
+
+        } else {
+
+            return (location);
+
+        }
+
+    }
+
+
+    /**
+     * Determine if a URI string has a <code>scheme</code> component.
+     */
+    private boolean hasScheme(String uri) {
+        int len = uri.length();
+        for(int i=0; i < len ; i++) {
+            char c = uri.charAt(i);
+            if(c == ':') {
+                return i > 0;
+            } else if(!isSchemeChar(c)) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Determine if the character is allowed in the scheme of a URI.
+     * See RFC 2396, Section 3.1
+     */
+    private static boolean isSchemeChar(char c) {
+        return Character.isLetterOrDigit(c) ||
+            c == '+' || c == '-' || c == '.';
+    }
+
+    
+    /**
+     * Return the specified URL with the specified session identifier
+     * suitably encoded.
+     *
+     * @param url URL to be encoded with the session id
+     * @param sessionId Session id to be included in the encoded URL
+     */
+    protected String toEncoded(String url, String sessionId) {
+
+        if ((url == null) || (sessionId == null))
+            return (url);
+
+        String path = url;
+        String query = "";
+        String anchor = "";
+        int question = url.indexOf('?');
+        if (question >= 0) {
+            path = url.substring(0, question);
+            query = url.substring(question);
+        }
+        int pound = path.indexOf('#');
+        if (pound >= 0) {
+            anchor = path.substring(pound);
+            path = path.substring(0, pound);
+        }
+        StringBuffer sb = new StringBuffer(path);
+        if( sb.length() > 0 ) { // jsessionid can't be first.
+            sb.append(";jsessionid=");
+            sb.append(sessionId);
+        }
+        sb.append(anchor);
+        sb.append(query);
+        return (sb.toString());
+
+    }
+
+
+    public int getBytesWritten() {
+      return outputBuffer.getBytesWritten();
+    }
+
+    public MessageWriter getOutputBuffer() {
+      return outputBuffer;
+    }
+
+    public CharSequence getResponseHeader(String name) {
+      MessageBytes v = getCoyoteResponse().getMimeHeaders().getValue(name);
+      return (v == null) ? null : v.toString();
+    }
+
+
+    public Response getCoyoteResponse() {
+      return resB;
+    }
+
+
+    public void setCoyoteResponse(Response resB) {
+        this.resB = resB;
+    }
+
+
+}
+
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletResponseIncludeWrapper.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletResponseIncludeWrapper.java
new file mode 100644 (file)
index 0000000..6f8b260
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+
+/**
+ * Wrapper around the response object received as parameter to 
+ * RequestDispatcher.include().
+ * 
+ * @author Costin Manolache
+ */
+public class ServletResponseIncludeWrapper extends HttpServletResponseWrapper {
+    public ServletResponseIncludeWrapper(ServletResponse current) {
+        super((HttpServletResponse) current);
+    }
+
+    // Not overriden:
+    /*
+    public boolean containsHeader(String name)
+    public String encodeRedirectUrl(String url)
+    public String encodeRedirectURL(String url)
+    public String encodeUrl(String url)
+    public String encodeURL(String url)
+    public void flushBuffer() throws IOException
+    public int getBufferSize()
+    public String getCharacterEncoding()
+    public String getContentType()
+    public Locale getLocale()
+    public ServletOutputStream getOutputStream() throws IOException
+    public ServletResponse getResponse()
+    public PrintWriter getWriter() throws IOException
+    public boolean isCommitted()
+    public void resetBuffer()
+    public void setCharacterEncoding(String charset)
+    public void setResponse(ServletResponse response)
+     */
+    
+    public void reset() {
+        if (getResponse().isCommitted())
+            getResponse().reset(); 
+        else
+            throw new IllegalStateException();
+    }
+
+    public void setContentLength(int len) {
+    }
+
+    public void setContentType(String type) {
+    }
+
+    public void setLocale(Locale loc) {
+    }
+
+    public void setBufferSize(int size) {
+    }
+
+    public void addCookie(Cookie cookie) {
+    }
+
+    public void addDateHeader(String name, long value) {
+    }
+
+    public void addHeader(String name, String value) {
+    }
+
+    public void addIntHeader(String name, int value) {
+    }
+
+    public void sendError(int sc) throws IOException {
+    }
+
+    public void sendError(int sc, String msg) throws IOException {
+    }
+
+    public void sendRedirect(String location) throws IOException {
+    }
+
+    public void setDateHeader(String name, long value) {
+    }
+
+    public void setHeader(String name, String value) {
+    }
+
+    public void setIntHeader(String name, int value) {
+    }
+
+    public void setStatus(int sc) {
+    }
+
+    public void setStatus(int sc, String msg) {
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletWriterImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/ServletWriterImpl.java
new file mode 100644 (file)
index 0000000..a27d693
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+/**
+ * Coyote implementation of the servlet writer.
+ * 
+ * @author Remy Maucherat
+ */
+public class ServletWriterImpl
+    extends PrintWriter {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    private static final char[] LINE_SEP = { '\r', '\n' };
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    protected Writer ob;
+    protected boolean error = false;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public ServletWriterImpl(Writer ob) {
+        super(ob);
+        this.ob = ob;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Prevent cloning the facade.
+     */
+    protected Object clone()
+        throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Clear facade.
+     */
+    void clear() {
+        ob = null;
+    }
+
+
+    /**
+     * Recycle.
+     */
+    void recycle() {
+        error = false;
+    }
+
+
+    // --------------------------------------------------------- Writer Methods
+
+
+    public void flush() {
+
+        if (error)
+            return;
+
+        try {
+            ob.flush();
+        } catch (IOException e) {
+            error = true;
+        }
+
+    }
+
+
+    public void close() {
+
+        // We don't close the PrintWriter - super() is not called,
+        // so the stream can be reused. We close ob.
+        try {
+            ob.close();
+        } catch (IOException ex ) {
+            ;
+        }
+        error = false;
+
+    }
+
+
+    public boolean checkError() {
+        flush();
+        return error;
+    }
+
+
+    public void write(int c) {
+
+        if (error)
+            return;
+
+        try {
+            ob.write(c);
+        } catch (IOException e) {
+            error = true;
+        }
+
+    }
+
+
+    public void write(char buf[], int off, int len) {
+
+        if (error)
+            return;
+
+        try {
+            ob.write(buf, off, len);
+        } catch (IOException e) {
+            error = true;
+        }
+
+    }
+
+
+    public void write(char buf[]) {
+       write(buf, 0, buf.length);
+    }
+
+
+    public void write(String s, int off, int len) {
+
+        if (error)
+            return;
+
+        try {
+            ob.write(s, off, len);
+        } catch (IOException e) {
+            error = true;
+        }
+
+    }
+
+
+    public void write(String s) {
+        write(s, 0, s.length());
+    }
+
+
+    // ---------------------------------------------------- PrintWriter Methods
+
+
+    public void print(boolean b) {
+        if (b) {
+            write("true");
+        } else {
+            write("false");
+        }
+    }
+
+
+    public void print(char c) {
+        write(c);
+    }
+
+
+    public void print(int i) {
+        write(String.valueOf(i));
+    }
+
+
+    public void print(long l) {
+        write(String.valueOf(l));
+    }
+
+
+    public void print(float f) {
+        write(String.valueOf(f));
+    }
+
+
+    public void print(double d) {
+        write(String.valueOf(d));
+    }
+
+
+    public void print(char s[]) {
+        write(s);
+    }
+
+
+    public void print(String s) {
+        if (s == null) {
+            s = "null";
+        }
+        write(s);
+    }
+
+
+    public void print(Object obj) {
+        write(String.valueOf(obj));
+    }
+
+
+    public void println() {
+        write(LINE_SEP);
+    }
+
+
+    public void println(boolean b) {
+        print(b);
+        println();
+    }
+
+
+    public void println(char c) {
+        print(c);
+        println();
+    }
+
+
+    public void println(int i) {
+        print(i);
+        println();
+    }
+
+
+    public void println(long l) {
+        print(l);
+        println();
+    }
+
+
+    public void println(float f) {
+        print(f);
+        println();
+    }
+
+
+    public void println(double d) {
+        print(d);
+        println();
+    }
+
+
+    public void println(char c[]) {
+        print(c);
+        println();
+    }
+
+
+    public void println(String s) {
+        print(s);
+        println();
+    }
+
+
+    public void println(Object o) {
+        print(o);
+        println();
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/TomcatLite.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/TomcatLite.java
new file mode 100644 (file)
index 0000000..5ddd9cd
--- /dev/null
@@ -0,0 +1,642 @@
+/*
+ * 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.tomcat.lite;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tomcat.integration.ObjectManager;
+import org.apache.tomcat.integration.simple.SimpleObjectManager;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.buf.UriNormalizer;
+import org.apache.tomcat.util.http.mapper.MappingData;
+
+/**
+ * Simpler, lower footprint serlvet engine.  
+ * 
+ * Uses ObjectManager to integate with an embedding app 
+ * - the object names it uses:
+ * 
+ * Internal objects created by Tomcat and registered for management 
+ * and injection: 
+ * - Servlet:CONTEXT_PATH:SERVLETNAME - a ServletWrapper
+ * - ServletContext:CONTEXT_PATH
+ * - ProtocolHandler:ep-PORT - coyote ProtocolHandler
+ * - CoyoteServer:CoyoteServer-PORT
+ * - CoyoteAdapter:PATH - for the coyote used Adapter 
+ * - TomcatLite - this object.
+ * - Connector - the connector object
+ * 
+ * Plugins to be constructed by framework ( defaults set in initDefaults ):
+ * - UserSessionManager
+ * - UserTemplateClassMapper
+ * - ContextPreinitListener
+ * - Connector
+ * - WebappServletMapper
+ * - WebappFilterMapper
+ * - default-servlet  
+ * - jspwildcard-servlet
+ * - Foo-servlet - servlet named Foo
+ * 
+ * 
+ * @author Costin Manolache
+ */
+public class TomcatLite implements Runnable {
+
+    private String serverDirName;
+    private File workDir;
+    
+    // all contexts - hostMapper knows about hostnames and how they are mapped.
+    // this shouldn't be needed if we want to delegate ctx management
+    private ArrayList<ServletContextImpl> contexts = new ArrayList();
+
+    URLClassLoader contextParentLoader;
+    
+    // Discovered or default Host/Context mapper
+    Filter hostMapper;
+
+    // Servlets to preload in each context, configurable from CLI or API
+    Map<String,String> preloadServlets = new HashMap();
+    Map<String,String> preloadMappings = new HashMap();
+    
+    Map<String,String> ctxDefaultInitParam = new HashMap();
+        
+    Connector coyoteAdapter;
+    
+    ObjectManager om;
+    
+    static String SERVLETS_PACKAGE = "org.apache.tomcat.servlets";
+    
+    
+    protected boolean daemon = false;
+    
+    public TomcatLite() {
+    }
+
+    public TomcatLite(ObjectManager om) {
+        this.setObjectManager(om);
+    }
+
+    // --------------- start/stop ---------------
+    
+    /**
+     * Return the object manager associated with this tomcat.
+     * If none set, create a minimal one with the default 
+     * values.
+     */
+    public ObjectManager getObjectManager() {
+        if (om == null) {
+            // Defaults.
+            om = new ObjectManager();
+            SimpleObjectManager props = new SimpleObjectManager(om);
+            // Init defaults. If using a custom OM, you should register 
+            // at the default objects as well.
+            props.loadResource("org/apache/tomcat/lite/config.properties");
+        }
+        return om;
+    }
+    
+    public void setObjectManager(ObjectManager om) {
+        this.om = om;
+    }
+    
+    public List/*<ServletContextImpl>*/ getWebapps() {
+        return contexts;
+    }
+    
+    public URLClassLoader getContextParentLoader() {
+        if (contextParentLoader == null) {
+            
+            ClassLoader parent = this.getClass().getClassLoader();
+            contextParentLoader = new URLClassLoader(new URL[] {},
+                    parent);
+            
+            /*if (engineRepo == null) {
+                engineRepo = new Repository();
+                engineRepo.setParentClassLoader(parent);
+            }
+            
+            contextParentLoader = 
+                engineRepo.getClassLoader();
+            */
+        }
+        return contextParentLoader;
+    }
+        
+    public void start() throws IOException {
+        long t0 = System.currentTimeMillis();
+        
+        // start all contexts
+        // init all contexts
+        Iterator i1 = contexts.iterator();
+        while (i1.hasNext()) {
+           ServletContextImpl ctx = (ServletContextImpl) i1.next();
+           try {
+               ctx.start();
+           } catch (Throwable e) {
+               e.printStackTrace();
+           }
+        }
+        long t1 = System.currentTimeMillis();
+        System.err.println("Engine.start() " + (t1-t0));
+    }
+    
+      
+    /**
+     * Add a context - used for IntrospectionUtils.
+     * 
+     * ContextPath:ContextBaseDir
+     */
+    public void setContext(String c) throws ServletException {
+        String[] pathDir = c.split(":", 2);
+        addServletContext("", pathDir[1], pathDir[0]);
+    }
+    
+    public void setServletContexts(List<ServletContext> c) throws ServletException {
+        for (ServletContext ctx: c) {
+            addServletContext((ServletContextImpl) ctx);
+        }
+    }
+    
+    public void setPreload(String servletNameClass) {
+        String[] nv = servletNameClass.split(":");
+        preloadServlets.put(nv[0], nv[1]);
+    }
+    
+    public void addPreload(String servletName, String servletClassName) {
+        preloadServlets.put(servletName, servletClassName);
+    }
+
+    public void setDefaultInitParam(String nameValue) {
+        String[] nv = nameValue.split(":");
+        ctxDefaultInitParam.put(nv[0], nv[1]);
+    }
+    
+    public void addDefaultInitParam(String name, String value) {
+        ctxDefaultInitParam.put(name, value);
+    }
+    
+    public void setPreloadMappings(String servletPath) {
+        String[] nv = servletPath.split(":");
+        preloadMappings.put(nv[0], nv[1]);
+    }
+    
+    public void addPreloadMapping(String servletName, String path) {
+        preloadMappings.put(servletName, path);
+    }
+    
+    public void stop() {
+        Iterator i1 = contexts.iterator();
+        while (i1.hasNext()) {
+           ServletContextImpl ctx = (ServletContextImpl) i1.next();
+            try {
+                ctx.destroy();
+            } catch (Throwable e) {
+                e.printStackTrace();
+            }
+        }
+        stopConnector();
+    }
+
+    // -------------- Context add/remove --------------
+
+    public static String[] DEFAULT_WELCOME = { "index.html" };
+    
+    public void addServletContext(ServletContextImpl ctx) throws ServletException {
+        ctx.setTomcat(this);
+        if (hostMapper == null) { 
+            hostMapper = new WebappContextMapper();
+        }
+
+        ((WebappContextMapper) hostMapper).addHost(ctx.getHostname(), null);
+        ((WebappContextMapper) hostMapper).addContext(ctx.getHostname(), 
+                ctx);
+
+        contexts.add(ctx);
+        
+        getObjectManager().bind("ServletContext:" + ctx.getContextPath(), 
+                ctx);
+
+    }
+    
+    /**
+     * Add a context. 
+     * 
+     * web.xml will be read as part of init, and the initialization will be 
+     * part of start or lazy.
+     * 
+     * @param hostname - ""
+     *            if default host, or string to be matched with Host header
+     * @param basePath = directory where the webapp is installed           
+     * @param path -
+     *            context path, "/" for root, "/examples", etc
+     * @return a servlet context
+     * @throws ServletException
+     */
+    public ServletContext addServletContext(String hostname, 
+                                            String basePath,
+                                            String path)
+        throws ServletException
+    {
+        ServletContextImpl ctx = new ServletContextImpl();
+        ctx.setContextPath(path);
+        ctx.setBasePath(basePath);
+        addServletContext(ctx);
+        return ctx;
+    }
+    
+    public void removeServletContext(ServletContext sctx)
+        throws ServletException
+    {
+        ServletContextImpl ctx = (ServletContextImpl) sctx;
+        // TODO: destroy all servlets and filters
+        // TODO: clean up any other reference to the context or its loader
+        notifyRemove(ctx);
+    }
+    
+    
+    /** 
+     * Required for ServletContext.getContext(uri);
+     * @throws ServletException 
+     * @throws IOException 
+     */
+    public ServletContextImpl getContext(ServletContextImpl impl, String uri) 
+            throws IOException, ServletException {
+        // Create a request - needs to be simplified
+        ServletRequestImpl req = createMessage(impl, impl.contextPath, uri);
+        hostMapper.doFilter(req, null, null);
+        return req.getContext();
+    }
+    
+    public ServletResponseImpl service(ServletRequestImpl req) throws IOException, Exception {
+      ServletResponseImpl res = req.getResponse();
+      service(req, res);
+      endRequest(req, res);
+      return res;
+    }
+    
+    public void service(ServletRequestImpl req, ServletResponseImpl res) 
+            throws Exception, IOException {
+        // parse the session id from URI
+        req.parseSessionId();
+        
+        try {
+          UriNormalizer.decodeRequest(req.getCoyoteRequest().decodedURI(), 
+                  req.getCoyoteRequest().requestURI(),
+                  req.getCoyoteRequest().getURLDecoder());
+        } catch(IOException ioe) {
+            res.setStatus(400);
+            return;
+        }
+        
+        MappingData mapRes = req.getMappingData();
+        try {
+          // TODO: make hostMapper configurable, implement interface,
+          // simple to set on ROOT context
+          hostMapper.doFilter(req, null, null);
+            
+
+          ServletContextImpl ctx = (ServletContextImpl)mapRes.context;
+          if( ctx == null ) {
+            // TODO: 404
+            res.setStatus(404);
+            return;
+          }
+          req.setContext(ctx);
+
+          // bind class loader 
+          Thread.currentThread().setContextClassLoader(ctx.getClassLoader());
+
+          WebappServletMapper mapper = ctx.getMapper();
+          mapper.map(req.getCoyoteRequest().decodedURI(), mapRes);
+
+          // Possible redirect
+          MessageBytes redirectPathMB = mapRes.redirectPath;
+          if (!redirectPathMB.isNull()) {
+              String redirectPath = res.urlEncoder.encodeURL(redirectPathMB.toString());
+              String query = req.getQueryString();
+              if (req.isRequestedSessionIdFromURL()) {
+                  // This is not optimal, but as this is not very common, it
+                  // shouldn't matter
+                  redirectPath = redirectPath + ";" + ServletRequestImpl.SESSION_PARAMETER_NAME + "=" 
+                      + req.getRequestedSessionId();
+              }
+              if (query != null) {
+                  // This is not optimal, but as this is not very common, it
+                  // shouldn't matter
+                  redirectPath = redirectPath + "?" + query;
+              }
+              res.sendRedirect(redirectPath);
+              return;
+          }
+          
+          req.parseSessionCookiesId();
+
+          ServletConfigImpl h=(ServletConfigImpl)mapRes.wrapper;
+          if (h != null) {
+            req.setWrapper((ServletConfigImpl)mapRes.wrapper);
+            serviceServlet(ctx, req, res, h, mapRes );
+            // send the response...
+
+            //res.flushBuffer();
+
+            // Recycle the wrapper request and response
+            //req.recycle();
+            //res.recycle();
+          }
+        } finally {
+            if(mapRes != null ) 
+                mapRes.recycle();
+        }
+    }
+    
+    /** Coyote / mapper adapter. Result of the mapper.
+     *  
+     *  This replaces the valve chain, the path is: 
+     *    1. coyote calls mapper -> result Adapter 
+     *    2. service is called. Additional filters are set on the wrapper. 
+     * @param mapRes 
+     */
+    private void serviceServlet(ServletContextImpl ctx, 
+                               ServletRequestImpl req, 
+                               ServletResponseImpl res,
+                               ServletConfigImpl servletConfig, 
+                               MappingData mapRes) 
+            throws IOException {
+        Servlet servlet = null;
+        try {
+            if (servletConfig.isUnavailable()) {
+                handleUnavailable(res, servletConfig);
+                return;
+            }
+            try {
+                servlet = servletConfig.allocate();
+            } catch(ServletException ex) {
+                handleUnavailable(res, servletConfig);
+            }
+            WebappFilterMapper filterMap = ctx.getFilterMapper();
+            FilterChainImpl chain = 
+                filterMap.createFilterChain(req, servletConfig, servlet);
+            
+            try {
+                if (chain == null) {
+                    if (servlet != null) {
+                        servlet.service(req, res);
+                    } else {
+                        System.err.println("No servlet " + req.getRequestURI());
+                        res.sendError(404);
+                    }
+                } else {
+                    chain.doFilter(req, res);
+                }
+            } catch(UnavailableException ex) {
+                servletConfig.unavailable(ex);
+                handleUnavailable(res, servletConfig);
+                return;
+            }
+            
+            // servlet completed without exception. Check status
+            int status = res.getStatus();
+            if (status != 200 && !res.isCommitted()) {
+                String statusPage = ctx.findStatusPage(status);
+
+                if (statusPage != null) {
+                    ctx.handleStatusPage(req, res, status, statusPage);
+                } else {
+                    // Send a default message body.
+                    // TODO: maybe other mechanism to customize default.
+                    res.defaultStatusPage(status, res.getMessage());
+                }
+            }
+        } catch (Throwable t) {
+            ctx.handleError(req, res, t);
+        } finally {
+            if (servlet != null) {
+                servletConfig.deallocate(servlet);
+            }
+        }
+    }
+
+    private void handleUnavailable(ServletResponseImpl response, 
+                                   ServletConfigImpl servletConfig) 
+            throws IOException {
+        long available = servletConfig.getAvailable();
+        if ((available > 0L) && (available < Long.MAX_VALUE))
+            response.setDateHeader("Retry-After", available);
+        //  TODO: handle via error pages !
+        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, 
+                "Service unavailable");
+    }
+    
+
+    // ------------ Notifications for JMX ----------------
+
+    void notifyAdd(Object o) {
+    }
+
+    void notifyRemove(Object o) {
+    }
+
+    public void setServerDir(String dir) {
+      this.serverDirName = dir;
+    }
+        
+    public File getWork() {
+        if (workDir == null) {
+          if (serverDirName == null) {
+              serverDirName = "./";
+          }
+          File rootDirFile = new File(serverDirName);
+          workDir = new File(rootDirFile, "tomcat-work");
+          if (workDir.exists()) {
+            workDir.mkdirs();
+          }
+        }
+        return workDir;
+    }
+    
+    /** 
+     * Init
+     * 
+     * @throws ServletException
+     * @throws IOException
+     */
+    public void init() throws ServletException, IOException {
+      getObjectManager().bind("TomcatLite", this);
+      if (contexts.size() == 0) {
+        setContext("/:./webapps/ROOT");
+      }
+      getConnector().setObjectManager(getObjectManager());
+      Iterator i1 = contexts.iterator();
+      while (i1.hasNext()) {
+        ServletContextImpl ctx = (ServletContextImpl) i1.next();
+        try {
+          ctx.init();
+        } catch (Throwable e) {
+          e.printStackTrace();
+        }
+      }
+    }
+    
+    /**
+     * Initialize an webapp and add it to the server. 
+     * - load web.xml
+     * - call 
+     * 
+     * @param rootDir
+     * @param path
+     * @param deployServlet
+     */
+    public void init(String rootDir, String path) 
+            throws ServletException, IOException {
+        
+        long t0 = System.currentTimeMillis();
+
+        ServletContextImpl ctx = 
+            (ServletContextImpl)addServletContext(null, 
+                                                  rootDir, 
+                                                  path);
+        ctx.init();
+        
+        long t1 = System.currentTimeMillis();
+        
+        // At this point all config is loaded. Contexts are not yet init()
+        // - this will happen on start.
+        System.err.println("Context.init() " + path + " " + (t1-t0));
+    }
+
+    /** 
+     * Get an empty request/response pair ( response available 
+     * as getResponse() ). Optional set input and output buffers.
+     * 
+     * This can be used for a connector-less interface to tomcat lite.
+     * 
+     * TODO: make it independent of coyote !
+     */
+    
+    public  ServletRequestImpl createMessage() {
+      ServletRequestImpl req = new ServletRequestImpl();
+      ServletResponseImpl res = req.getResponse();
+      
+      getConnector().initRequest(req, res);
+      
+      req.getCoyoteRequest().method().setString("GET");
+      req.getCoyoteRequest().protocol().setString("HTTP/1.1");
+      
+      return req;
+    }
+    
+    /**
+     * Used internally for mapping. 
+     */
+    private ServletRequestImpl createMessage(ServletContextImpl deployCtx,
+                                            String ctxPath,
+                                            String reqPath) {
+      ServletRequestImpl req = createMessage();
+      req.setContextPath(ctxPath);
+      req.setContext(deployCtx);
+      req.setRequestURI(ctxPath + reqPath);
+      return req;
+    }
+    
+
+    /** 
+     * Set a global filter that will be used for context mapping.
+     * 
+     * The filter will get a request, with requestUri and hostname set.
+     *  
+     * It needs to compute the context path and set it as an attribute.
+     * 
+     * Advanced features may include on-demand loading of webapps, large scale
+     * virtual hosting, etc.
+     */
+    public void setContextMapper(Filter hostMapper2) {
+        this.hostMapper = hostMapper2;
+    }
+
+    public void endRequest(ServletRequestImpl req,
+                           ServletResponseImpl res) throws IOException {
+     res.outputBuffer.flush();
+     res.getCoyoteResponse().finish();
+    }
+    
+    public Connector getConnector() {
+        if (coyoteAdapter == null) {
+            coyoteAdapter = (Connector) getObjectManager().get(Connector.class);
+            setConnector(coyoteAdapter);
+        }
+        return coyoteAdapter;
+    }
+
+    public void setConnector(Connector c) {
+        coyoteAdapter = c;
+        coyoteAdapter.setTomcatLite(this);
+        getObjectManager().bind("Connector", coyoteAdapter);
+    }
+
+    
+    public void setDaemon(boolean d) {
+        getConnector();
+        if (coyoteAdapter != null) {
+            coyoteAdapter.setDaemon(d);
+        }
+    }
+
+    public void startConnector() {
+        getConnector();
+        if (coyoteAdapter != null) {
+            coyoteAdapter.start();
+        }
+    }
+
+    public void stopConnector() {
+        getConnector();
+        if (coyoteAdapter != null) {
+            coyoteAdapter.stop();
+        }
+    }
+
+    public void run() {
+        try {
+            execute();
+        } catch (ServletException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    
+    public void execute() throws ServletException, IOException {
+        init();
+        start();
+        startConnector();
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/WebappContextMapper.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/WebappContextMapper.java
new file mode 100644 (file)
index 0000000..7fc2c31
--- /dev/null
@@ -0,0 +1,132 @@
+package org.apache.tomcat.lite;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.mapper.MappingData;
+
+/** 
+ * This handles host and context mapping.
+ * 
+ * The default implementation for tomcat lite is very limitted - 
+ * no support for virtual hosts, no support for contexts deeper than 
+ * 1 level. The intention is to override this with more advanced mappers.
+ * 
+ * With 'ConfigurableHosts' interface it is possible for a smart 
+ * mapper to load/unload virtual hosts at runtime, maybe from a 
+ * database. It should be possible to use databases to store huge number 
+ * of hosts or webapps. 
+ * 
+ */
+public class WebappContextMapper implements Filter {
+
+  ServletContext rootContext;
+  Map<MessageBytes, ServletContext> contexts = new HashMap();
+
+  public WebappContextMapper() {
+  }
+
+  public void addHost(String name, String[] aliases) {
+  }
+  
+  /**
+   * Add a new Context to an existing Host.
+   *
+   * @param hostName Virtual host name this context belongs to
+   * @param contextPath Context path
+   * @param context Context object
+   * @param welcomeResources Welcome files defined for this context
+   * @param resources Static resources of the context
+   */
+  public void addContext(String hostName, 
+                         ServletContext context)
+      throws ServletException
+  {
+    String path = context.getContextPath();
+    if (path.lastIndexOf("/") > 0) {
+      throw new ServletException("Base context mapper supports only one level");
+    }
+    if ("/".equals(path)) {
+      rootContext = context;
+    }
+    MessageBytes mb = MessageBytes.newInstance();
+    mb.setChars(path.toCharArray(), 0, path.length());
+    contexts.put(mb, context);
+  }
+
+
+  /**
+   * Remove a context from an existing host.
+   *
+   * @param hostName Virtual host name this context belongs to
+   * @param path Context path
+   */
+  public void removeContext(String hostName, String path) 
+      throws ServletException {
+    if ("/".equals(path)) {
+      rootContext = null;
+    }
+    contexts.remove(path);  
+  }
+
+  /**
+   * Map the specified URI.
+   */
+  private void mapContext(ServletRequestImpl req)
+      throws IOException, ServletException {
+    MessageBytes uriMB = req.getDecodedRequestURIMB();
+    MappingData mappingData = req.getMappingData(); 
+    uriMB.toChars();
+    CharChunk uri = uriMB.getCharChunk();
+
+    
+    if (uri.length() < 2 || contexts.size() == 0) {
+      mappingData.context = rootContext;
+      if (rootContext != null) {
+        mappingData.contextPath.setString(rootContext.getContextPath());
+      }
+      return;
+    }
+    
+    int nextSlash = uri.indexOf('/', 1);
+    if (nextSlash == -1) {
+      nextSlash = uri.length();
+    }
+    mappingData.contextPath.setChars(uri.getChars(), 0, nextSlash);
+    ServletContext servletContext = contexts.get(mappingData.contextPath);
+
+    if (servletContext != null) {
+      mappingData.context = servletContext;
+    } else {
+      mappingData.context = rootContext;
+      if (rootContext != null) {
+        mappingData.contextPath.setString(rootContext.getContextPath());      
+      }
+    }
+  }
+
+  public void init(FilterConfig filterConfig) throws ServletException {
+  }
+    
+  public void doFilter(ServletRequest request, 
+                       ServletResponse response, 
+                       FilterChain chain) 
+      throws IOException, ServletException {
+    ServletRequestImpl req = (ServletRequestImpl)request;
+    mapContext(req);
+  }
+    
+  public void destroy() {
+  }
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/WebappFilterMapper.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/WebappFilterMapper.java
new file mode 100644 (file)
index 0000000..78847a3
--- /dev/null
@@ -0,0 +1,529 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.lite;
+
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.tomcat.servlets.util.RequestUtil;
+
+/**
+ * First filter after the context and servlet are mapped. It will add 
+ * web.xml-defined filters. 
+ * 
+ * costin: This is another mapping - done in RequestDispatcher or initial 
+ * mapping.
+ * Also: StandardHostValve - sets attribute for error pages,
+ *   StandardWrapperValve - mapping per invocation
+ *
+ * @author Greg Murray
+ * @author Remy Maucherat
+ */
+public class WebappFilterMapper implements Filter {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    public static final int ERROR = 1;
+    public static final Integer ERROR_INTEGER = new Integer(ERROR);
+    public static final int FORWARD = 2;
+    public static final Integer FORWARD_INTEGER = new Integer(FORWARD);
+    public static final int INCLUDE = 4;
+    public static final Integer INCLUDE_INTEGER = new Integer(INCLUDE);
+    public static final int REQUEST = 8;
+    public static final Integer REQUEST_INTEGER = new Integer(REQUEST);
+
+    /**
+     * Request dispatcher state.
+     */
+    public static final String DISPATCHER_TYPE_ATTR = 
+        "org.apache.catalina.core.DISPATCHER_TYPE";
+
+    /**
+     * Request dispatcher path.
+     */
+    public static final String DISPATCHER_REQUEST_PATH_ATTR = 
+        "org.apache.catalina.core.DISPATCHER_REQUEST_PATH";
+
+
+    // ----------------------------------------------------------- Constructors
+    ServletContextImpl servletContext;
+
+    public WebappFilterMapper() {
+    }
+
+    public WebappFilterMapper(ServletContextImpl impl) {
+        servletContext = impl;
+    }
+
+    public void setServletContext(ServletContextImpl sc) {
+        servletContext = sc;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    ArrayList filterMaps = new ArrayList();
+    
+    public void addMapping(String filterName, 
+                           String url, 
+                           String servletName, 
+                           String type[]) {
+        FilterMap map = new FilterMap();
+        map.setURLPattern(url);
+        map.setFilterName(filterName);
+        map.setServletName(servletName);
+        filterMaps.add(map);
+    }
+
+    /**
+     * Construct and return a FilterChain implementation that will wrap the
+     * execution of the specified servlet instance.  If we should not execute
+     * a filter chain at all, return <code>null</code>.
+     *
+     * @param request The servlet request we are processing
+     * @param servlet The servlet instance to be wrapped
+     */
+    public FilterChainImpl createFilterChain(ServletRequest request, 
+                                             ServletConfigImpl wrapper, 
+                                             Servlet servlet) {
+
+        // If there is no servlet to execute, return null
+        if (servlet == null)
+            return (null);
+
+        // get the dispatcher type
+        int dispatcher = -1; 
+        if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) {
+            Integer dispatcherInt = 
+                (Integer) request.getAttribute(DISPATCHER_TYPE_ATTR);
+            dispatcher = dispatcherInt.intValue();
+        }
+        String requestPath = null;
+        Object attribute = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR);
+        
+        if (attribute != null){
+            requestPath = attribute.toString();
+        }
+        
+        HttpServletRequest hreq = null;
+        if (request instanceof HttpServletRequest) 
+            hreq = (HttpServletRequest)request;
+
+        // Create and initialize a filter chain object
+        FilterChainImpl filterChain = null;
+        if ((request instanceof ServletRequestImpl)) {
+            ServletRequestImpl req = (ServletRequestImpl) request;
+            filterChain = (FilterChainImpl) req.getFilterChain();
+            filterChain.release();
+        } else {
+            // Security: Do not recycle
+            filterChain = new FilterChainImpl();
+        }
+
+        filterChain.setServlet(wrapper, servlet);
+
+        // If there are no filter mappings, we are done
+        if ((filterMaps.size() == 0))
+            return (filterChain);
+
+        // Acquire the information we will need to match filter mappings
+        String servletName = wrapper.getServletName();
+
+        int n = 0;
+
+        // TODO(costin): optimize: separate in 2 lists, one for url-mapped, one for
+        // servlet-name. Maybe even separate list for dispatcher and 
+        // non-dispatcher
+        
+        // TODO(costin): optimize: set the FilterConfig in the FilterMap, to 
+        // avoid second hash lookup
+        
+        // Add the relevant path-mapped filters to this filter chain
+        for (int i = 0; i < filterMaps.size(); i++) {
+            FilterMap filterMap = (FilterMap)filterMaps.get(i);
+            if (!matchDispatcher(filterMap ,dispatcher)) {
+                continue;
+            }
+            if (!matchFiltersURL(filterMap, requestPath))
+                continue;
+            FilterConfigImpl filterConfig = 
+                servletContext.getFilter(filterMap.getFilterName());
+            if (filterConfig == null) {
+                // FIXME - log configuration problem
+                continue;
+            }
+            filterChain.addFilter(filterConfig);
+            n++;
+        }
+
+        // Add filters that match on servlet name second
+        for (int i = 0; i < filterMaps.size(); i++) {
+            FilterMap filterMap = (FilterMap)filterMaps.get(i);
+            if (!matchDispatcher(filterMap ,dispatcher)) {
+                continue;
+            }
+            if (!matchFiltersServlet(filterMap, servletName))
+                continue;
+            FilterConfigImpl filterConfig = 
+                servletContext.getFilter(filterMap.getFilterName());
+            if (filterConfig == null) {
+                ;       // FIXME - log configuration problem
+                continue;
+            }
+            filterChain.addFilter(filterConfig);
+            n++;
+        }
+
+        // Return the completed filter chain
+        return (filterChain);
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Return <code>true</code> if the context-relative request path
+     * matches the requirements of the specified filter mapping;
+     * otherwise, return <code>null</code>.
+     *
+     * @param filterMap Filter mapping being checked
+     * @param requestPath Context-relative request path of this request
+     */
+    private boolean matchFiltersURL(FilterMap filterMap, String requestPath) {
+
+        if (requestPath == null)
+            return (false);
+
+        // Match on context relative request path
+        String testPath = filterMap.getURLPattern();
+        if (testPath == null)
+            return (false);
+
+        // Case 1 - Exact Match
+        if (testPath.equals(requestPath))
+            return (true);
+
+        // Case 2 - Path Match ("/.../*")
+        if (testPath.equals("/*"))
+            return (true);
+        if (testPath.endsWith("/*")) {
+            if (testPath.regionMatches(0, requestPath, 0, 
+                                       testPath.length() - 2)) {
+                if (requestPath.length() == (testPath.length() - 2)) {
+                    return (true);
+                } else if ('/' == requestPath.charAt(testPath.length() - 2)) {
+                    return (true);
+                }
+            }
+            return (false);
+        }
+
+        // Case 3 - Extension Match
+        if (testPath.startsWith("*.")) {
+            int slash = requestPath.lastIndexOf('/');
+            int period = requestPath.lastIndexOf('.');
+            if ((slash >= 0) && (period > slash) 
+                && (period != requestPath.length() - 1)
+                && ((requestPath.length() - period) 
+                    == (testPath.length() - 1))) {
+                return (testPath.regionMatches(2, requestPath, period + 1,
+                                               testPath.length() - 2));
+            }
+        }
+
+        // Case 4 - "Default" Match
+        return (false); // NOTE - Not relevant for selecting filters
+
+    }
+
+
+    /**
+     * Return <code>true</code> if the specified servlet name matches
+     * the requirements of the specified filter mapping; otherwise
+     * return <code>false</code>.
+     *
+     * @param filterMap Filter mapping being checked
+     * @param servletName Servlet name being checked
+     */
+    private boolean matchFiltersServlet(FilterMap filterMap, 
+                                        String servletName) {
+
+        if (servletName == null) {
+            return (false);
+        } else {
+            if (servletName.equals(filterMap.getServletName())) {
+                return (true);
+            } else {
+                return false;
+            }
+        }
+
+    }
+
+
+    /**
+     * Convienience method which returns true if  the dispatcher type
+     * matches the dispatcher types specified in the FilterMap
+     */
+    private boolean matchDispatcher(FilterMap filterMap, int dispatcher) {
+        switch (dispatcher) {
+            case FORWARD : {
+                if (filterMap.getDispatcherMapping() == FilterMap.FORWARD ||
+                    filterMap.getDispatcherMapping() == FilterMap.FORWARD_ERROR ||
+                    filterMap.getDispatcherMapping() == FilterMap.INCLUDE_FORWARD ||
+                    filterMap.getDispatcherMapping() == FilterMap.INCLUDE_ERROR_FORWARD ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_FORWARD ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD_INCLUDE ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_FORWARD_INCLUDE) {
+                        return true;
+                }
+                break;
+            }
+            case INCLUDE : {
+                if (filterMap.getDispatcherMapping() == FilterMap.INCLUDE ||
+                    filterMap.getDispatcherMapping() == FilterMap.INCLUDE_ERROR ||
+                    filterMap.getDispatcherMapping() == FilterMap.INCLUDE_FORWARD ||
+                    filterMap.getDispatcherMapping() == FilterMap.INCLUDE_ERROR_FORWARD ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_INCLUDE ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_INCLUDE ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD_INCLUDE ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_FORWARD_INCLUDE) {
+                        return true;
+                }
+                break;
+            }
+            case REQUEST : {
+                if (filterMap.getDispatcherMapping() == FilterMap.REQUEST ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_INCLUDE ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_INCLUDE ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_FORWARD ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_FORWARD_INCLUDE ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD_INCLUDE) {
+                        return true;
+                }
+                break;
+            }
+            case ERROR : {
+                if (filterMap.getDispatcherMapping() == FilterMap.ERROR ||
+                    filterMap.getDispatcherMapping() == FilterMap.FORWARD_ERROR || 
+                    filterMap.getDispatcherMapping() == FilterMap.INCLUDE_ERROR || 
+                    filterMap.getDispatcherMapping() == FilterMap.INCLUDE_ERROR_FORWARD || 
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD_INCLUDE ||
+                    filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_INCLUDE) {
+                        return true;
+                }
+                break;
+            }
+        }
+        return false;
+    }
+
+
+    // -------------------- Map elements -----------------------
+    
+    public static class FilterMap implements Serializable {
+
+
+        // ------------------------------------------------------------- Properties
+
+
+        /**
+         * The name of this filter to be executed when this mapping matches
+         * a particular request.
+         */
+        
+        public static final int ERROR = 1;
+        public static final int FORWARD = 2;
+        public static final int FORWARD_ERROR =3;  
+        public static final int INCLUDE = 4;
+        public static final int INCLUDE_ERROR  = 5;
+        public static final int INCLUDE_ERROR_FORWARD  =6;
+        public static final int INCLUDE_FORWARD  = 7;
+        public static final int REQUEST = 8;
+        public static final int REQUEST_ERROR = 9;
+        public static final int REQUEST_ERROR_FORWARD = 10;
+        public static final int REQUEST_ERROR_FORWARD_INCLUDE = 11;
+        public static final int REQUEST_ERROR_INCLUDE = 12;
+        public static final int REQUEST_FORWARD = 13;
+        public static final int REQUEST_INCLUDE = 14;
+        public static final int REQUEST_FORWARD_INCLUDE= 15;
+        
+        // represents nothing having been set. This will be seen 
+        // as equal to a REQUEST
+        private static final int NOT_SET = -1;
+        
+        private int dispatcherMapping=NOT_SET;
+        
+        private String filterName = null;    
+
+        /**
+         * The URL pattern this mapping matches.
+         */
+        private String urlPattern = null;
+
+        /**
+         * The servlet name this mapping matches.
+         */
+        private String servletName = null;
+
+
+
+        public String getFilterName() {
+            return (this.filterName);
+        }
+
+        public void setFilterName(String filterName) {
+            this.filterName = filterName;
+        }
+
+
+        public String getServletName() {
+            return (this.servletName);
+        }
+
+        public void setServletName(String servletName) {
+            this.servletName = servletName;
+        }
+
+
+        public String getURLPattern() {
+            return (this.urlPattern);
+        }
+
+        public void setURLPattern(String urlPattern) {
+            this.urlPattern = RequestUtil.URLDecode(urlPattern);
+        }
+        
+        /**
+         *
+         * This method will be used to set the current state of the FilterMap
+         * representing the state of when filters should be applied:
+         *
+         *        ERROR
+         *        FORWARD
+         *        FORWARD_ERROR
+         *        INCLUDE
+         *        INCLUDE_ERROR
+         *        INCLUDE_ERROR_FORWARD
+         *        REQUEST
+         *        REQUEST_ERROR
+         *        REQUEST_ERROR_INCLUDE
+         *        REQUEST_ERROR_FORWARD_INCLUDE
+         *        REQUEST_INCLUDE
+         *        REQUEST_FORWARD,
+         *        REQUEST_FORWARD_INCLUDE
+         *
+         */
+        public void setDispatcher(String dispatcherString) {
+            String dispatcher = dispatcherString.toUpperCase();
+            
+            if (dispatcher.equals("FORWARD")) {
+
+                // apply FORWARD to the global dispatcherMapping.
+                switch (dispatcherMapping) {
+                    case NOT_SET  :  dispatcherMapping = FORWARD; break;
+                    case ERROR : dispatcherMapping = FORWARD_ERROR; break;
+                    case INCLUDE  :  dispatcherMapping = INCLUDE_FORWARD; break;
+                    case INCLUDE_ERROR  :  dispatcherMapping = INCLUDE_ERROR_FORWARD; break;
+                    case REQUEST : dispatcherMapping = REQUEST_FORWARD; break;
+                    case REQUEST_ERROR : dispatcherMapping = REQUEST_ERROR_FORWARD; break;
+                    case REQUEST_ERROR_INCLUDE : dispatcherMapping = REQUEST_ERROR_FORWARD_INCLUDE; break;
+                    case REQUEST_INCLUDE : dispatcherMapping = REQUEST_FORWARD_INCLUDE; break;
+                }
+            } else if (dispatcher.equals("INCLUDE")) {
+                // apply INCLUDE to the global dispatcherMapping.
+                switch (dispatcherMapping) {
+                    case NOT_SET  :  dispatcherMapping = INCLUDE; break;
+                    case ERROR : dispatcherMapping = INCLUDE_ERROR; break;
+                    case FORWARD  :  dispatcherMapping = INCLUDE_FORWARD; break;
+                    case FORWARD_ERROR  :  dispatcherMapping = INCLUDE_ERROR_FORWARD; break;
+                    case REQUEST : dispatcherMapping = REQUEST_INCLUDE; break;
+                    case REQUEST_ERROR : dispatcherMapping = REQUEST_ERROR_INCLUDE; break;
+                    case REQUEST_ERROR_FORWARD : dispatcherMapping = REQUEST_ERROR_FORWARD_INCLUDE; break;
+                    case REQUEST_FORWARD : dispatcherMapping = REQUEST_FORWARD_INCLUDE; break;
+                }
+            } else if (dispatcher.equals("REQUEST")) {
+                // apply REQUEST to the global dispatcherMapping.
+                switch (dispatcherMapping) {
+                    case NOT_SET  :  dispatcherMapping = REQUEST; break;
+                    case ERROR : dispatcherMapping = REQUEST_ERROR; break;
+                    case FORWARD  :  dispatcherMapping = REQUEST_FORWARD; break;
+                    case FORWARD_ERROR  :  dispatcherMapping = REQUEST_ERROR_FORWARD; break;
+                    case INCLUDE  :  dispatcherMapping = REQUEST_INCLUDE; break;
+                    case INCLUDE_ERROR  :  dispatcherMapping = REQUEST_ERROR_INCLUDE; break;
+                    case INCLUDE_FORWARD : dispatcherMapping = REQUEST_FORWARD_INCLUDE; break;
+                    case INCLUDE_ERROR_FORWARD : dispatcherMapping = REQUEST_ERROR_FORWARD_INCLUDE; break;
+                }
+            }  else if (dispatcher.equals("ERROR")) {
+                // apply ERROR to the global dispatcherMapping.
+                switch (dispatcherMapping) {
+                    case NOT_SET  :  dispatcherMapping = ERROR; break;
+                    case FORWARD  :  dispatcherMapping = FORWARD_ERROR; break;
+                    case INCLUDE  :  dispatcherMapping = INCLUDE_ERROR; break;
+                    case INCLUDE_FORWARD : dispatcherMapping = INCLUDE_ERROR_FORWARD; break;
+                    case REQUEST : dispatcherMapping = REQUEST_ERROR; break;
+                    case REQUEST_INCLUDE : dispatcherMapping = REQUEST_ERROR_INCLUDE; break;
+                    case REQUEST_FORWARD : dispatcherMapping = REQUEST_ERROR_FORWARD; break;
+                    case REQUEST_FORWARD_INCLUDE : dispatcherMapping = REQUEST_ERROR_FORWARD_INCLUDE; break;
+                }
+            }
+        }
+        
+        public int getDispatcherMapping() {
+            // per the SRV.6.2.5 absence of any dispatcher elements is
+            // equivelant to a REQUEST value
+            if (dispatcherMapping == NOT_SET) return REQUEST;
+            else return dispatcherMapping; 
+        }
+
+    }
+
+
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+
+
+    public void doFilter(ServletRequest request, ServletResponse response, 
+                         FilterChain chain) 
+            throws IOException, ServletException {
+    }
+
+
+    public void destroy() {
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/WebappServletMapper.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/WebappServletMapper.java
new file mode 100644 (file)
index 0000000..a6eb284
--- /dev/null
@@ -0,0 +1,883 @@
+/*
+ *  Copyright 1999-2004 The Apache Software Foundation
+ *
+ *  Licensed 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.tomcat.lite;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.tomcat.util.buf.Ascii;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.mapper.MappingData;
+
+/**
+ * Mapper, which implements the servlet API mapping rules (which are derived
+ * from the HTTP rules).
+ * 
+ * Based on catalina mapper - but simplified. All host and context mappings
+ * is done in HostMapper - this is just dealing with web.xml.
+ * 
+ * For corner cases ( very large number of rules, dynamic rules, etc ) you 
+ * can override the mapper for a context with a class extending this.
+ * 
+ * TODO: remove, use coyote-level mapper or user-space
+ */
+public class WebappServletMapper implements Filter {
+
+    /**
+     * Context associated with this wrapper, used for wrapper mapping.
+     */
+    public ContextMapElement contextMapElement = new ContextMapElement();
+
+
+    // --------------------------------------------------------- Public Methods
+    public WebappServletMapper() {
+    }
+    
+    public void setServletContext(ServletContextImpl impl) {
+        contextMapElement.object = impl;
+        contextMapElement.name = impl.getContextPath();        
+    }
+
+
+   /** Set context, used for wrapper mapping (request dispatcher).
+     *
+     * @param welcomeResources Welcome files defined for this context
+     * @param resources Static resources of the context
+     */
+    public void setContext(String path, String[] welcomeResources,
+                           File resources) {
+        contextMapElement.name = path;
+        contextMapElement.welcomeResources = welcomeResources;
+        contextMapElement.resources = resources;
+    }
+
+
+    /**
+     * Add a wrapper to the context associated with this wrapper.
+     *
+     * @param path Wrapper mapping
+     * @param wrapper The Wrapper object
+     */
+    public void addWrapper(String path, Object wrapper) {
+        addWrapper(contextMapElement, path, wrapper);
+    }
+
+
+    public void addWrapper(String path, Object wrapper, boolean jspWildCard) {
+        addWrapper(contextMapElement, path, wrapper, jspWildCard);
+    }
+
+
+    public void addWrapper(ContextMapElement context, String path, Object wrapper) {
+        addWrapper(context, path, wrapper, false);
+    }
+
+
+    /**
+     * Adds a wrapper to the given context.
+     *
+     * @param context The context to which to add the wrapper
+     * @param path Wrapper mapping
+     * @param wrapper The Wrapper object
+     * @param jspWildCard true if the wrapper corresponds to the JspServlet
+     * and the mapping path contains a wildcard; false otherwise
+     */
+    protected void addWrapper(ContextMapElement context, String path, Object wrapper,
+                              boolean jspWildCard) {
+
+        synchronized (context) {
+            WrapperMapElement newWrapper = new WrapperMapElement();
+            newWrapper.object = wrapper;
+            newWrapper.jspWildCard = jspWildCard;
+            if (path.endsWith("/*")) {
+                // Wildcard wrapper
+                newWrapper.name = path.substring(0, path.length() - 2);
+                WrapperMapElement[] oldWrappers = context.wildcardWrappers;
+                WrapperMapElement[] newWrappers =
+                    new WrapperMapElement[oldWrappers.length + 1];
+                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
+                    context.wildcardWrappers = newWrappers;
+                    int slashCount = slashCount(newWrapper.name);
+                    if (slashCount > context.nesting) {
+                        context.nesting = slashCount;
+                    }
+                }
+            } else if (path.startsWith("*.")) {
+                // Extension wrapper
+                newWrapper.name = path.substring(2);
+                WrapperMapElement[] oldWrappers = context.extensionWrappers;
+                WrapperMapElement[] newWrappers =
+                    new WrapperMapElement[oldWrappers.length + 1];
+                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
+                    context.extensionWrappers = newWrappers;
+                }
+            } else if (path.equals("/")) {
+                // Default wrapper
+                newWrapper.name = "";
+                context.defaultWrapper = newWrapper;
+            } else {
+                // Exact wrapper
+                newWrapper.name = path;
+                WrapperMapElement[] oldWrappers = context.exactWrappers;
+                WrapperMapElement[] newWrappers =
+                    new WrapperMapElement[oldWrappers.length + 1];
+                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
+                    context.exactWrappers = newWrappers;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Remove a wrapper from the context associated with this wrapper.
+     *
+     * @param path Wrapper mapping
+     */
+    public void removeWrapper(String path) {
+        removeWrapper(contextMapElement, path);
+    }
+
+
+    protected void removeWrapper(ContextMapElement context, String path) {
+        synchronized (context) {
+            if (path.endsWith("/*")) {
+                // Wildcard wrapper
+                String name = path.substring(0, path.length() - 2);
+                WrapperMapElement[] oldWrappers = context.wildcardWrappers;
+                WrapperMapElement[] newWrappers =
+                    new WrapperMapElement[oldWrappers.length - 1];
+                if (removeMap(oldWrappers, newWrappers, name)) {
+                    // Recalculate nesting
+                    context.nesting = 0;
+                    for (int i = 0; i < newWrappers.length; i++) {
+                        int slashCount = slashCount(newWrappers[i].name);
+                        if (slashCount > context.nesting) {
+                            context.nesting = slashCount;
+                        }
+                    }
+                    context.wildcardWrappers = newWrappers;
+                }
+            } else if (path.startsWith("*.")) {
+                // Extension wrapper
+                String name = path.substring(2);
+                WrapperMapElement[] oldWrappers = context.extensionWrappers;
+                WrapperMapElement[] newWrappers =
+                    new WrapperMapElement[oldWrappers.length - 1];
+                if (removeMap(oldWrappers, newWrappers, name)) {
+                    context.extensionWrappers = newWrappers;
+                }
+            } else if (path.equals("/")) {
+                // Default wrapper
+                context.defaultWrapper = null;
+            } else {
+                // Exact wrapper
+                String name = path;
+                WrapperMapElement[] oldWrappers = context.exactWrappers;
+                WrapperMapElement[] newWrappers =
+                    new WrapperMapElement[oldWrappers.length - 1];
+                if (removeMap(oldWrappers, newWrappers, name)) {
+                    context.exactWrappers = newWrappers;
+                }
+            }
+        }
+    }
+
+    /**
+     * Map the specified URI relative to the context,
+     * mutating the given mapping data.
+     *
+     * @param uri URI
+     * @param mappingData This structure will contain the result of the mapping
+     *                    operation
+     */
+    public void map(MessageBytes uri, MappingData mappingData)
+        throws Exception {
+
+        uri.toChars();
+        CharChunk uricc = uri.getCharChunk();
+        //uricc.setLimit(-1);
+        internalMapWrapper(contextMapElement, uricc, mappingData);
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Wrapper mapping.
+     */
+    private final void internalMapWrapper(ContextMapElement context,
+                                          CharChunk path,
+                                          MappingData mappingData)
+        throws Exception {
+
+        int pathOffset = path.getOffset();
+        int pathEnd = path.getEnd();
+        int servletPath = pathOffset;
+        boolean noServletPath = false;
+
+        int length = context.name.length();
+        if (length == 1) length--;
+        if (length != (pathEnd - pathOffset)) {
+            servletPath = pathOffset + length;
+        } else {
+            noServletPath = true;
+            // What is this doing ??? 
+            path.append('/');
+            pathOffset = path.getOffset();
+            pathEnd = path.getEnd();
+            servletPath = pathOffset+length;
+        }
+
+        path.setOffset(servletPath);
+
+        // Rule 1 -- Exact Match
+        WrapperMapElement[] exactWrappers = context.exactWrappers;
+        internalMapExactWrapper(exactWrappers, path, mappingData);
+
+        // Rule 2 -- Prefix Match
+        boolean checkJspWelcomeFiles = false;
+        WrapperMapElement[] wildcardWrappers = context.wildcardWrappers;
+        if (mappingData.wrapper == null) {
+            internalMapWildcardWrapper(wildcardWrappers, context.nesting, 
+                                       path, mappingData);
+            if (mappingData.wrapper != null && mappingData.jspWildCard) {
+                char[] buf = path.getBuffer();
+                if (buf[pathEnd - 1] == '/') {
+                    /*
+                     * Path ending in '/' was mapped to JSP servlet based on
+                     * wildcard match (e.g., as specified in url-pattern of a
+                     * jsp-property-group.
+                     * Force the context's welcome files, which are interpreted
+                     * as JSP files (since they match the url-pattern), to be
+                     * considered. See Bugzilla 27664.
+                     */ 
+                    mappingData.wrapper = null;
+                    checkJspWelcomeFiles = true;
+                } else {
+                    // See Bugzilla 27704
+                    mappingData.wrapperPath.setChars(buf, path.getStart(),
+                                                     path.getLength());
+                    mappingData.pathInfo.recycle();
+                }
+            }
+        }
+
+        if(mappingData.wrapper == null && noServletPath) {
+            // The path is empty, redirect to "/"
+            mappingData.redirectPath.setChars
+                (path.getBuffer(), pathOffset, pathEnd);
+            path.setEnd(pathEnd - 1);
+            return;
+        }
+
+        // Rule 3 -- Extension Match
+        WrapperMapElement[] extensionWrappers = context.extensionWrappers;
+        if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
+            internalMapExtensionWrapper(extensionWrappers, path, mappingData);
+        }
+
+        File file = null;
+        // Rule 4 -- Welcome resources processing for servlets
+        if (mappingData.wrapper == null) {
+            boolean checkWelcomeFiles = checkJspWelcomeFiles;
+            if (!checkWelcomeFiles) {
+                char[] buf = path.getBuffer();
+                checkWelcomeFiles = (buf[pathEnd - 1] == '/');
+            }
+            if (checkWelcomeFiles) {
+                for (int i = 0; (i < context.welcomeResources.length)
+                         && (mappingData.wrapper == null); i++) {
+                    path.setOffset(pathOffset);
+                    path.setEnd(pathEnd);
+                    path.append(context.welcomeResources[i], 0,
+                                context.welcomeResources[i].length());
+                    path.setOffset(servletPath);
+
+                    // Rule 4a -- Welcome resources processing for exact macth
+                    internalMapExactWrapper(exactWrappers, path, mappingData);
+
+                    // Rule 4b -- Welcome resources processing for prefix match
+                    if (mappingData.wrapper == null) {
+                        internalMapWildcardWrapper
+                            (wildcardWrappers, context.nesting, 
+                             path, mappingData);
+                    }
+
+                    // Rule 4c -- Welcome resources processing
+                    //            for physical folder
+                    if (mappingData.wrapper == null
+                        && context.resources != null) {
+                        // Default servlet: check if it's file or dir to apply
+                        // welcome files rules. 
+                        // TODO: Save the File in attributes, 
+                        // to avoid duplication in DefaultServlet.
+                        
+                        String pathStr = path.toString();
+                        file = new File(context.resources, pathStr);
+                        if (file.exists() && !(file.isDirectory()) ) {
+                            
+                            internalMapExtensionWrapper(extensionWrappers,
+                                                        path, mappingData);
+                            if (mappingData.wrapper == null
+                                && context.defaultWrapper != null) {
+                                mappingData.wrapper =
+                                    context.defaultWrapper.object;
+                                mappingData.requestPath.setChars
+                                    (path.getBuffer(), path.getStart(), 
+                                     path.getLength());
+                                mappingData.wrapperPath.setChars
+                                    (path.getBuffer(), path.getStart(), 
+                                     path.getLength());
+                                mappingData.requestPath.setString(pathStr);
+                                mappingData.wrapperPath.setString(pathStr);
+                            }
+                        }
+                    }
+                }
+
+                path.setOffset(servletPath);
+                path.setEnd(pathEnd);
+            }
+                                        
+        }
+
+
+        // Rule 7 -- Default servlet
+        if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
+            if (context.defaultWrapper != null) {
+                mappingData.wrapper = context.defaultWrapper.object;
+                mappingData.requestPath.setChars
+                    (path.getBuffer(), path.getStart(), path.getLength());
+                mappingData.wrapperPath.setChars
+                    (path.getBuffer(), path.getStart(), path.getLength());
+            }
+            // Redirection to a folder
+            char[] buf = path.getBuffer();
+            if (context.resources != null && buf[pathEnd -1 ] != '/') {
+                String pathStr = path.toString();
+                file = new File( context.resources, pathStr);
+                if (file.exists() && file.isDirectory()) {
+                    // Note: this mutates the path: do not do any processing 
+                    // after this (since we set the redirectPath, there 
+                    // shouldn't be any)
+                    path.setOffset(pathOffset);
+                    path.append('/');
+                    mappingData.redirectPath.setChars
+                        (path.getBuffer(), path.getStart(), path.getLength());
+                } else {
+                    mappingData.requestPath.setString(pathStr);
+                    mappingData.wrapperPath.setString(pathStr);
+                }
+            }
+        }
+
+        path.setOffset(pathOffset);
+        path.setEnd(pathEnd);
+    }
+
+
+    /**
+     * Exact mapping.
+     */
+    private final void internalMapExactWrapper
+        (WrapperMapElement[] wrappers, CharChunk path, MappingData mappingData) {
+        int pos = find(wrappers, path);
+        if ((pos != -1) && (path.equals(wrappers[pos].name))) {
+            mappingData.requestPath.setString(wrappers[pos].name);
+            mappingData.wrapperPath.setString(wrappers[pos].name);
+            mappingData.wrapper = wrappers[pos].object;
+        }
+    }
+
+
+    /**
+     * Wildcard mapping.
+     */
+    private final void internalMapWildcardWrapper
+        (WrapperMapElement[] wrappers, int nesting, CharChunk path, 
+         MappingData mappingData) {
+
+        int pathEnd = path.getEnd();
+        int pathOffset = path.getOffset();
+
+        int lastSlash = -1;
+        int length = -1;
+        int pos = find(wrappers, path);
+        if (pos != -1) {
+            boolean found = false;
+            while (pos >= 0) {
+                if (path.startsWith(wrappers[pos].name)) {
+                    length = wrappers[pos].name.length();
+                    if (path.getLength() == length) {
+                        found = true;
+                        break;
+                    } else if (path.startsWithIgnoreCase("/", length)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (lastSlash == -1) {
+                    lastSlash = nthSlash(path, nesting + 1);
+                } else {
+                    lastSlash = lastSlash(path);
+                }
+                path.setEnd(lastSlash);
+                pos = find(wrappers, path);
+            }
+            path.setEnd(pathEnd);
+            if (found) {
+                mappingData.wrapperPath.setString(wrappers[pos].name);
+                if (path.getLength() > length) {
+                    mappingData.pathInfo.setChars
+                        (path.getBuffer(),
+                         path.getOffset() + length,
+                         path.getLength() - length);
+                }
+                mappingData.requestPath.setChars
+                    (path.getBuffer(), path.getOffset(), path.getLength());
+                mappingData.wrapper = wrappers[pos].object;
+                mappingData.jspWildCard = wrappers[pos].jspWildCard;
+            }
+        }
+    }
+
+
+    /**
+     * Extension mappings.
+     */
+    private final void internalMapExtensionWrapper
+        (WrapperMapElement[] wrappers, CharChunk path, MappingData mappingData) {
+        char[] buf = path.getBuffer();
+        int pathEnd = path.getEnd();
+        int servletPath = path.getOffset();
+        int slash = -1;
+        for (int i = pathEnd - 1; i >= servletPath; i--) {
+            if (buf[i] == '/') {
+                slash = i;
+                break;
+            }
+        }
+        if (slash == -1 ) slash = 0;
+        if (slash >= 0) {
+            int period = -1;
+            for (int i = pathEnd - 1; i > slash; i--) {
+                if (buf[i] == '.') {
+                    period = i;
+                    break;
+                }
+            }
+            if (period >= 0) {
+                path.setOffset(period + 1);
+                path.setEnd(pathEnd);
+                int pos = find(wrappers, path);
+                if ((pos != -1)
+                    && (path.equals(wrappers[pos].name))) {
+                    mappingData.wrapperPath.setChars
+                        (buf, servletPath, pathEnd - servletPath);
+                    mappingData.requestPath.setChars
+                        (buf, servletPath, pathEnd - servletPath);
+                    mappingData.wrapper = wrappers[pos].object;
+                }
+                path.setOffset(servletPath);
+                path.setEnd(pathEnd);
+            }
+        }
+    }
+
+
+    /**
+     * Find a map elemnt given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    public static final int find(MapElement[] map, CharChunk name) {
+        return find(map, name, name.getStart(), name.getEnd());
+    }
+
+
+    /**
+     * Find a map elemnt given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int find(MapElement[] map, CharChunk name,
+                                  int start, int end) {
+
+        int a = 0;
+        int b = map.length - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        
+        if (compare(name, start, end, map[0].name) < 0 ) {
+            return -1;
+        }         
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) / 2;
+            int result = compare(name, start, end, map[i].name);
+            if (result == 1) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = compare(name, start, end, map[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Find a map elemnt given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int findIgnoreCase(MapElement[] map, CharChunk name) {
+        return findIgnoreCase(map, name, name.getStart(), name.getEnd());
+    }
+
+
+    /**
+     * Find a map elemnt given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int findIgnoreCase(MapElement[] map, CharChunk name,
+                                  int start, int end) {
+
+        int a = 0;
+        int b = map.length - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        if (compareIgnoreCase(name, start, end, map[0].name) < 0 ) {
+            return -1;
+        }         
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) / 2;
+            int result = compareIgnoreCase(name, start, end, map[i].name);
+            if (result == 1) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = compareIgnoreCase(name, start, end, map[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Find a map elemnt given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    public static final int find(MapElement[] map, String name) {
+
+        int a = 0;
+        int b = map.length - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        
+        if (name.compareTo(map[0].name) < 0) {
+            return -1;
+        } 
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) / 2;
+            int result = name.compareTo(map[i].name);
+            if (result > 0) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = name.compareTo(map[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Compare given char chunk with String.
+     * Return -1, 0 or +1 if inferior, equal, or superior to the String.
+     */
+    private static final int compare(CharChunk name, int start, int end,
+                                     String compareTo) {
+        int result = 0;
+        char[] c = name.getBuffer();
+        int len = compareTo.length();
+        if ((end - start) < len) {
+            len = end - start;
+        }
+        for (int i = 0; (i < len) && (result == 0); i++) {
+            if (c[i + start] > compareTo.charAt(i)) {
+                result = 1;
+            } else if (c[i + start] < compareTo.charAt(i)) {
+                result = -1;
+            }
+        }
+        if (result == 0) {
+            if (compareTo.length() > (end - start)) {
+                result = -1;
+            } else if (compareTo.length() < (end - start)) {
+                result = 1;
+            }
+        }
+        return result;
+    }
+
+
+    /**
+     * Compare given char chunk with String ignoring case.
+     * Return -1, 0 or +1 if inferior, equal, or superior to the String.
+     */
+    private static final int compareIgnoreCase(CharChunk name, int start, int end,
+                                     String compareTo) {
+        int result = 0;
+        char[] c = name.getBuffer();
+        int len = compareTo.length();
+        if ((end - start) < len) {
+            len = end - start;
+        }
+        for (int i = 0; (i < len) && (result == 0); i++) {
+            if (Ascii.toLower(c[i + start]) > Ascii.toLower(compareTo.charAt(i))) {
+                result = 1;
+            } else if (Ascii.toLower(c[i + start]) < Ascii.toLower(compareTo.charAt(i))) {
+                result = -1;
+            }
+        }
+        if (result == 0) {
+            if (compareTo.length() > (end - start)) {
+                result = -1;
+            } else if (compareTo.length() < (end - start)) {
+                result = 1;
+            }
+        }
+        return result;
+    }
+
+
+    /**
+     * Find the position of the last slash in the given char chunk.
+     */
+    public static final int lastSlash(CharChunk name) {
+
+        char[] c = name.getBuffer();
+        int end = name.getEnd();
+        int start = name.getStart();
+        int pos = end;
+
+        while (pos > start) {
+            if (c[--pos] == '/') {
+                break;
+            }
+        }
+
+        return (pos);
+
+    }
+
+
+    /**
+     * Find the position of the nth slash, in the given char chunk.
+     */
+    public static final int nthSlash(CharChunk name, int n) {
+
+        char[] c = name.getBuffer();
+        int end = name.getEnd();
+        int start = name.getStart();
+        int pos = start;
+        int count = 0;
+
+        while (pos < end) {
+            if ((c[pos++] == '/') && ((++count) == n)) {
+                pos--;
+                break;
+            }
+        }
+
+        return (pos);
+
+    }
+
+
+    /**
+     * Return the slash count in a given string.
+     */
+    public static final int slashCount(String name) {
+        int pos = -1;
+        int count = 0;
+        while ((pos = name.indexOf('/', pos + 1)) != -1) {
+            count++;
+        }
+        return count;
+    }
+
+
+    /**
+     * Insert into the right place in a sorted MapElement array, and prevent
+     * duplicates.
+     */
+    public static final boolean insertMap
+        (MapElement[] oldMap, MapElement[] newMap, MapElement newElement) {
+        int pos = find(oldMap, newElement.name);
+        if ((pos != -1) && (newElement.name.equals(oldMap[pos].name))) {
+            return false;
+        }
+        System.arraycopy(oldMap, 0, newMap, 0, pos + 1);
+        newMap[pos + 1] = newElement;
+        System.arraycopy
+            (oldMap, pos + 1, newMap, pos + 2, oldMap.length - pos - 1);
+        return true;
+    }
+
+
+    /**
+     * Insert into the right place in a sorted MapElement array.
+     */
+    public static final boolean removeMap
+        (MapElement[] oldMap, MapElement[] newMap, String name) {
+        int pos = find(oldMap, name);
+        if ((pos != -1) && (name.equals(oldMap[pos].name))) {
+            System.arraycopy(oldMap, 0, newMap, 0, pos);
+            System.arraycopy(oldMap, pos + 1, newMap, pos,
+                             oldMap.length - pos - 1);
+            return true;
+        }
+        return false;
+    }
+
+
+    // ------------------------------------------------- MapElement Inner Class
+
+
+    protected static abstract class MapElement {
+        /** hostname or path  
+         */
+        public String name = null;
+        public Object object = null;
+
+        public String toString() {
+            return "MapElement: \"" + name +"\"";
+        }
+    }
+
+
+    // ---------------------------------------------------- Context Inner Class
+
+
+    public static final class ContextMapElement
+        extends MapElement {
+
+        public String[] welcomeResources = new String[0];
+        public File resources = null;
+        public WrapperMapElement defaultWrapper = null;
+        public WrapperMapElement[] exactWrappers = new WrapperMapElement[0];
+        public WrapperMapElement[] wildcardWrappers = new WrapperMapElement[0];
+        public WrapperMapElement[] extensionWrappers = new WrapperMapElement[0];
+        public int nesting = 0;
+
+        public String toString() {
+            return "ContextMapElement {" +
+            "name: \"" + name +
+            "\"\nnesting: \"" + nesting +
+            "\"\n}";
+        }
+    }
+
+
+    // ---------------------------------------------------- Wrapper Inner Class
+
+
+    public static class WrapperMapElement
+        extends MapElement {
+        public boolean jspWildCard = false;
+    }
+
+
+    public void destroy() {
+    }
+
+
+    public void doFilter(ServletRequest request, ServletResponse response,
+                         FilterChain chain) throws IOException,
+        ServletException {
+    }
+
+
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+
+   
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/cli/Main.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/cli/Main.java
new file mode 100644 (file)
index 0000000..3abe1f4
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.tomcat.lite.cli;
+
+import org.apache.tomcat.integration.ObjectManager;
+import org.apache.tomcat.integration.jmx.JmxObjectManagerSpi;
+import org.apache.tomcat.integration.simple.SimpleObjectManager;
+import org.apache.tomcat.lite.TomcatLite;
+import org.apache.tomcat.lite.coyote.CoyoteHttp;
+
+/**
+ * Run tomcat lite, mostly for demo. In normal use you would
+ * embed it in an app, or use a small customized launcher.
+ * 
+ * With no arguments, it'll load only the root context / from 
+ * ./webapps/ROOT
+ *
+ * Most configuration can be done using web.xml files, few settings 
+ * can be set by flags. 
+ * 
+ * @author Costin Manolache
+ */
+public class Main {
+  
+  public static void main(String args[]) 
+          throws Exception {
+      
+      if (args.length == 0) {
+          System.err.println("Please specify at least one webapp.");
+          System.err.println("Example:");
+          System.err.println("-context /:webapps/ROOT -port 9999");
+      }
+
+      // Enable CLI processing
+      SimpleObjectManager.setArgs(args);
+      
+      TomcatLite lite = new TomcatLite();
+      ObjectManager om = lite.getObjectManager();
+
+      // add JMX support
+      new JmxObjectManagerSpi().register(om);
+      
+      lite.run();
+  }    
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/config.properties b/modules/tomcat-lite/java/org/apache/tomcat/lite/config.properties
new file mode 100644 (file)
index 0000000..c6e43e1
--- /dev/null
@@ -0,0 +1,28 @@
+# Support for PropertiesSpi - the 'dummy' framework used for tomcat-lite
+# If tomcat is used with a proper framework, you need to bind and configure 
+# those objects in the framework. 
+
+# --- Class names for required plugin interfaces --- 
+
+org.apache.tomcat.lite.WebappServletMapper.(class)=org.apache.tomcat.lite.WebappServletMapper
+org.apache.tomcat.lite.WebappFilterMapper.(class)=org.apache.tomcat.lite.WebappFilterMapper
+
+# Sessions
+org.apache.tomcat.addons.UserSessionManager.(class)=org.apache.tomcat.servlets.session.SimpleSessionManager
+
+# *.jsp support
+org.apache.tomcat.addons.UserTemplateClassMapper.(class)=org.apache.tomcat.servlets.jsp.JasperCompilerTemplateClassMapper
+
+# Loader for web.xml - you can have your own custom class using a more efficient
+# or hardcoded.
+org.apache.tomcat.lite.ContextPreinitListener.(class)=org.apache.tomcat.lite.webxml.TomcatLiteWebXmlConfig
+
+# Connector class
+org.apache.tomcat.lite.Connector.(class)=org.apache.tomcat.lite.coyote.CoyoteHttp
+
+# --- Other required settings ---
+
+# Customize default and *.jsp mappings
+default-servlet.(class)=org.apache.tomcat.servlets.file.WebdavServlet
+jspwildcard-servlet.(class)=org.apache.tomcat.servlets.jsp.WildcardTemplateServlet
+
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/ClientAbortException.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/ClientAbortException.java
new file mode 100644 (file)
index 0000000..db61fd3
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * 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.tomcat.lite.coyote;
+
+import java.io.IOException;
+
+/**
+ * Wrap an IOException identifying it as being caused by an abort
+ * of a request by a remote client.
+ *
+ * @author Glenn L. Nielsen
+ * @version $Revision: 304063 $ $Date: 2005-08-18 06:25:18 -0700 (Thu, 18 Aug 2005) $
+ */
+
+public final class ClientAbortException extends IOException {
+
+
+    //------------------------------------------------------------ Constructors
+
+
+    /**
+     * Construct a new ClientAbortException with no other information.
+     */
+    public ClientAbortException() {
+
+        this(null, null);
+
+    }
+
+
+    /**
+     * Construct a new ClientAbortException for the specified message.
+     *
+     * @param message Message describing this exception
+     */
+    public ClientAbortException(String message) {
+
+        this(message, null);
+
+    }
+
+
+    /**
+     * Construct a new ClientAbortException for the specified throwable.
+     *
+     * @param throwable Throwable that caused this exception
+     */
+    public ClientAbortException(Throwable throwable) {
+
+        this(null, throwable);
+
+    }
+
+
+    /**
+     * Construct a new ClientAbortException for the specified message
+     * and throwable.
+     *
+     * @param message Message describing this exception
+     * @param throwable Throwable that caused this exception
+     */
+    public ClientAbortException(String message, Throwable throwable) {
+
+        super();
+        this.message = message;
+        this.throwable = throwable;
+
+    }
+
+
+    //------------------------------------------------------ Instance Variables
+
+
+    /**
+     * The error message passed to our constructor (if any)
+     */
+    protected String message = null;
+
+
+    /**
+     * The underlying exception or error passed to our constructor (if any)
+     */
+    protected Throwable throwable = null;
+
+
+    //---------------------------------------------------------- Public Methods
+
+
+    /**
+     * Returns the message associated with this exception, if any.
+     */
+    public String getMessage() {
+
+        return (message);
+
+    }
+
+
+    /**
+     * Returns the cause that caused this exception, if any.
+     */
+    public Throwable getCause() {
+        
+        return (throwable);
+        
+    }
+
+    
+    /**
+     * Return a formatted string that describes this exception.
+     */
+    public String toString() {
+
+        StringBuffer sb = new StringBuffer("ClientAbortException:  ");
+        if (message != null) {
+            sb.append(message);
+            if (throwable != null) {
+                sb.append(":  ");
+            }
+        }
+        if (throwable != null) {
+            sb.append(throwable.toString());
+        }
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/CoyoteHttp.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/CoyoteHttp.java
new file mode 100644 (file)
index 0000000..722eca1
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ */
+package org.apache.tomcat.lite.coyote;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.ActionHook;
+import org.apache.coyote.Adapter;
+import org.apache.coyote.Request;
+import org.apache.coyote.Response;
+import org.apache.tomcat.integration.ObjectManager;
+import org.apache.tomcat.lite.Connector;
+import org.apache.tomcat.lite.ServletRequestImpl;
+import org.apache.tomcat.lite.ServletResponseImpl;
+import org.apache.tomcat.lite.TomcatLite;
+import org.apache.tomcat.util.net.SocketStatus;
+
+public class CoyoteHttp implements Adapter, Connector {
+
+    //private TomcatLite lite;
+    CoyoteServer coyote;
+    private TomcatLite lite;
+
+    public CoyoteHttp() {
+    }
+
+
+    @Override
+    public void finishResponse(HttpServletResponse res) throws IOException {
+        ((ServletResponseImpl) res).getCoyoteResponse().finish();
+    }
+
+
+    public void recycle(HttpServletRequest req, HttpServletResponse res) {
+    
+    }
+    
+    public void setPort(int port) {
+        if (getConnectors() != null) {
+            coyote.setPort(port);
+        }
+    }
+    
+    @Override
+    public void setDaemon(boolean b) {
+        if (getConnectors() != null) {
+            coyote.setDaemon(b);
+        }
+    }
+
+
+    @Override
+    public void start() {
+        if (getConnectors() != null) {
+            try {
+                coyote.init();
+                coyote.start();
+            } catch (Exception e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+    }
+
+
+
+    @Override
+    public void stop() {
+        if (coyote != null) {
+            try {
+                coyote.stop();
+            } catch (Exception e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }        
+    }
+    
+    @Override
+    public void initRequest(HttpServletRequest hreq, HttpServletResponse hres) {
+        ServletRequestImpl req = (ServletRequestImpl) hreq;
+        ServletResponseImpl res = (ServletResponseImpl) hres;
+        
+        Request creq = new Request();
+        res.setCoyoteResponse(new Response());
+        res.getCoyoteResponse().setRequest(creq);
+        res.getCoyoteResponse().setHook(new ActionHook() {
+          public void action(ActionCode actionCode, 
+                             Object param) {
+          }
+        });
+        
+        req.setCoyoteRequest(creq);
+        
+        res.setConnector();
+        
+    }
+      
+    // Coyote-specific hooking.
+    // This could be moved out to a separate class, TomcatLite can 
+    // work without it.
+    public CoyoteServer getConnectors() {
+        if (coyote == null) {
+            coyote = new CoyoteServer();
+            coyote.addAdapter("/", this);        
+        }
+        return coyote;
+    }
+
+    public void setConnectors(CoyoteServer server) {
+        this.coyote = server;
+        coyote.addAdapter("/", this);        
+    }
+
+    @Override
+    public void service(Request req, Response res) throws Exception {
+        // find the facades
+        ServletRequestImpl sreq = (ServletRequestImpl) req.getNote(CoyoteServer.ADAPTER_REQ_NOTE);
+        ServletResponseImpl sres = (ServletResponseImpl) res.getNote(CoyoteServer.ADAPTER_RES_NOTE);
+        if (sreq == null) {
+          sreq = new ServletRequestImpl();
+          sres = sreq.getResponse();
+          
+          sreq.setCoyoteRequest(req);
+          sres.setCoyoteResponse(res);
+          
+          req.setNote(CoyoteServer.ADAPTER_REQ_NOTE, sreq);
+          res.setNote(CoyoteServer.ADAPTER_RES_NOTE, sres);
+          
+          sres.setConnector();
+          
+        }
+        
+        lite.service(sreq, sres);
+        
+        if (res.getNote(CoyoteServer.COMET_RES_NOTE) == null) {
+          if (!sres.isCommitted()) {
+              res.sendHeaders();
+          }
+          sres.getOutputBuffer().flush();
+          res.finish();
+
+          sreq.recycle();
+          sres.recycle();
+        }
+    }
+    
+    // ---- Coyote ---
+    
+    @Override
+    public boolean event(Request req, Response res, SocketStatus status)
+        throws Exception {
+      return false;
+    }
+
+
+    public void setTomcatLite(TomcatLite lite) {
+        this.lite = lite;
+    }
+
+
+    @Override
+    public void setObjectManager(ObjectManager objectManager) {
+        getConnectors();
+        coyote.setObjectManager(objectManager);
+    }
+
+
+    
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/CoyoteServer.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/CoyoteServer.java
new file mode 100644 (file)
index 0000000..17c1e62
--- /dev/null
@@ -0,0 +1,188 @@
+/*  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.tomcat.lite.coyote;
+
+import java.io.IOException;
+
+import org.apache.coyote.Adapter;
+import org.apache.coyote.ProtocolHandler;
+import org.apache.coyote.http11.Http11NioProtocol;
+import org.apache.tomcat.integration.ObjectManager;
+
+
+/** 
+ * Simple example of embeding coyote servlet.
+ * 
+ */
+public class CoyoteServer  {
+  protected int port = 8800;
+  protected boolean daemon = false;
+
+  /**
+   * Note indicating the response is COMET. 
+   */
+  public static final int COMET_RES_NOTE = 2;
+  public static final int COMET_REQ_NOTE = 2;
+  
+  public static final int ADAPTER_RES_NOTE = 1;    
+  public static final int ADAPTER_REQ_NOTE = 1;    
+  
+  protected ProtocolHandler proto;
+
+  protected Adapter adapter = new MapperAdapter();
+  protected int maxThreads = 20;
+  boolean started = false;
+  boolean async = false; // use old nio connector
+  
+  protected ObjectManager om;
+  
+  public CoyoteServer() {  
+  }
+  
+  public void setObjectManager(ObjectManager om) {
+      this.om = om;
+  }
+  
+  /** 
+   * Add an adapter. If more than the 'default' adapter is
+   * added, a MapperAdapter will be inserted.
+   * 
+   * @param path Use "/" for the default.
+   * @param adapter
+   */
+  public void addAdapter(String path, Adapter added) {
+      if ("/".equals(path)) {
+          ((MapperAdapter) adapter).setDefaultAdapter(added);        
+      } else {
+          ((MapperAdapter) adapter).getMapper().addWrapper(path, added);
+      }
+  }
+  
+  /**
+   */
+  public void run() {
+      try {
+          init();
+          start();
+      } catch(IOException ex) {
+          ex.printStackTrace();
+      }
+  }
+
+  public void setDaemon(boolean b) {
+    daemon = b;
+  }
+  
+  public void init() {
+    //JdkLoggerConfig.loadCustom();
+    om.bind("CoyoteServer:" + "CoyoteServer-" + port, 
+            this);
+    om.bind("CoyoteAdapter", adapter);
+  }
+
+  protected void initAdapters() {
+      if (proto == null) {
+          addProtocolHandler(port, daemon);
+      }
+    // adapter = ...
+    // Adapter secondaryadapter = ...
+    //registry.registerComponent(secondaryadapter, ":name=adapter", null);
+  }
+
+  public void stop() throws Exception {
+    if (!started) {
+      return;
+    }
+    proto.destroy();
+    started = false;
+  }
+  
+  /**
+   *  Simple CLI support - arg is a path:className pair.
+   */
+  public void setAdapter(String arg)  {
+    String[] pathClass = arg.split(":", 2);
+    try {
+      Class c = Class.forName(pathClass[1]);
+      Adapter a = (Adapter) c.newInstance();
+      addAdapter(pathClass[0],a);
+    } catch (Throwable e) {
+      e.printStackTrace();
+    }
+  }
+  
+  public void setPort(int port) {
+    if (proto != null) {
+        proto.setAttribute("port", Integer.toString(port));
+    }
+    this.port = port;
+  }
+  
+  public void setConnector(ProtocolHandler h) {
+      this.proto = h;
+      h.setAttribute("port", Integer.toString(port));
+
+      om.bind("ProtocolHandler:" + "ep-" + port, proto);
+  }
+  
+  public void addProtocolHandler(int port, boolean daemon) {
+      Http11NioProtocol proto = new Http11NioProtocol();
+      proto.setCompression("on");
+      proto.setCompressionMinSize(32);
+      proto.setPort(port);
+      proto.getEndpoint().setDaemon(daemon);
+      CoyoteServer server = this;
+      server.setConnector(proto);
+      server.setPort(port);
+      server.setDaemon(daemon);
+  }
+  
+  public void addProtocolHandler(ProtocolHandler proto, 
+                                 int port, boolean daemon) {
+      CoyoteServer server = this;
+      server.setConnector(proto);
+      server.setPort(port);
+      server.setDaemon(daemon);
+  }
+  
+
+  
+  public void start() throws IOException {
+    try {
+      if (started) {
+        return;
+      }
+      initAdapters();
+
+      // not required - should run fine without a connector.
+      if (proto != null) {
+          proto.setAdapter(adapter);
+      
+          proto.init();
+          proto.start();
+      }
+      
+      started = true;
+    } catch (Throwable e) {
+      e.printStackTrace();
+      throw new RuntimeException(e);
+    }
+  }
+  
+  public boolean getStarted() {
+    return started;
+  } 
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/CoyoteUtils.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/CoyoteUtils.java
new file mode 100644 (file)
index 0000000..e97a450
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ */
+package org.apache.tomcat.lite.coyote;
+
+import java.io.IOException;
+
+import org.apache.coyote.Request;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.http.mapper.MappingData;
+
+public class CoyoteUtils {
+    static ByteChunk space = new ByteChunk(1);
+    static ByteChunk col = new ByteChunk(1);
+    static ByteChunk crlf = new ByteChunk(2);
+    static {
+        try {
+            space.append(' ');
+            col.append(':');
+            crlf.append('\r');
+            crlf.append('\n');
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    public static String getContextPath(Request request) {
+        MappingData md = MapperAdapter.getMappingData(request);
+        String ctxPath = (md.contextPath.isNull()) ? "/" : 
+            md.contextPath.toString();
+        return ctxPath;
+    }
+
+    public static String getPathInfo(Request request) {
+        MappingData md = MapperAdapter.getMappingData(request);
+        String ctxPath = (md.pathInfo.isNull()) ? "/" : 
+            md.pathInfo.toString();
+        return ctxPath;
+    }
+    public static String getServletPath(Request request) {
+        MappingData md = MapperAdapter.getMappingData(request);
+        String ctxPath = (md.wrapperPath.isNull()) ? "/" : 
+            md.wrapperPath.toString();
+        return ctxPath;
+    }
+    
+    // TODO: collate all notes in a signle file
+    static int READER_NOTE = 5;
+    
+    public static MessageReader getReader(Request req) {
+        MessageReader r = (MessageReader) req.getNote(READER_NOTE);
+        if (r == null) {
+            r = new MessageReader();
+            r.setRequest(req);
+            req.setNote(READER_NOTE, r);
+        }
+        return r;
+    }
+    
+    /** 
+     * Convert the request to bytes, ready to send.
+     */
+    public static void serializeRequest(Request req, 
+                                        ByteChunk reqBuf) throws IOException {
+        req.method().toBytes();
+        if (!req.unparsedURI().isNull()) {
+            req.unparsedURI().toBytes();
+        }
+        req.protocol().toBytes();
+
+        reqBuf.append(req.method().getByteChunk());
+        reqBuf.append(space);
+        if (req.unparsedURI().isNull()) {
+            req.requestURI().toBytes();
+
+            reqBuf.append(req.requestURI().getByteChunk());      
+        } else {
+            reqBuf.append(req.unparsedURI().getByteChunk());
+        }
+        reqBuf.append(space);
+        reqBuf.append(req.protocol().getByteChunk());
+        reqBuf.append(crlf);
+        // Headers
+        MimeHeaders mimeHeaders = req.getMimeHeaders();
+        boolean hasHost = false;
+        for (int i = 0; i < mimeHeaders.size(); i++) {
+            MessageBytes name = mimeHeaders.getName(i);
+            name.toBytes();
+            reqBuf.append(name.getByteChunk());
+            if (name.equalsIgnoreCase("host")) {
+                hasHost = true;
+            }
+            reqBuf.append(col);
+            mimeHeaders.getValue(i).toBytes();
+            reqBuf.append(mimeHeaders.getValue(i).getByteChunk());
+            reqBuf.append(crlf);
+        }
+        if (!hasHost) {
+            reqBuf.append("Host: localhost\r\n".getBytes(), 0, 17);
+        }
+        reqBuf.append(crlf);
+    }
+
+    
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/MapperAdapter.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/MapperAdapter.java
new file mode 100644 (file)
index 0000000..78ad37f
--- /dev/null
@@ -0,0 +1,139 @@
+/*  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.tomcat.lite.coyote;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import org.apache.coyote.Adapter;
+import org.apache.coyote.Request;
+import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.UriNormalizer;
+import org.apache.tomcat.util.http.mapper.BaseMapper;
+import org.apache.tomcat.util.http.mapper.MappingData;
+import org.apache.tomcat.util.net.SocketStatus;
+
+/**
+ * 
+ */
+public class MapperAdapter implements Adapter {
+
+    private BaseMapper mapper=new BaseMapper();
+    
+    static Logger log = Logger.getLogger("Mapper");
+    static final int MAP_NOTE = 4;
+    
+    public MapperAdapter() {
+        mapper.setDefaultHostName("localhost");
+        mapper.setContext("", new String[] {"index.html"},
+            null);
+    }
+
+    public MapperAdapter(BaseMapper mapper2) {
+        mapper = mapper2;
+    }
+    
+    public static MappingData getMappingData(Request req) {
+        MappingData md = (MappingData) req.getNote(MAP_NOTE);
+        if (md == null) {
+            md = new MappingData();
+            req.setNote(MAP_NOTE, md);
+        }
+        return md;
+    }
+
+    /**
+     * Copy an array of bytes to a different position. Used during 
+     * normalization.
+     */
+    public static void copyBytes(byte[] b, int dest, int src, int len) {
+        for (int pos = 0; pos < len; pos++) {
+            b[pos + dest] = b[pos + src];
+        }
+    }
+
+    
+    public void service(Request req, final Response res)
+            throws Exception {
+        long t0 = System.currentTimeMillis();
+        try {
+          // compute decodedURI - not done by connector
+          UriNormalizer.decodeRequest(req.decodedURI(), req.requestURI(), req.getURLDecoder());
+          MappingData mapRes = getMappingData(req);
+          mapRes.recycle();
+          
+          mapper.map(req.requestURI(), mapRes);
+
+          Adapter h=(Adapter)mapRes.wrapper;
+
+          if (h != null) {
+              log.info(">>>>>>>> START: " + req.method() + " " + 
+                        req.decodedURI() + " " + 
+                        h.getClass().getSimpleName());
+              h.service( req, res );
+          } else {
+              res.setStatus(404);
+          }
+        } catch(IOException ex) {
+            throw ex;
+        } catch( Throwable t ) {
+            t.printStackTrace();
+        } finally {
+            long t1 = System.currentTimeMillis();
+            
+            log.info("<<<<<<<< DONE: " + req.method() + " " + 
+                    req.decodedURI() + " " + 
+                    res.getStatus() + " " + 
+                    (t1 - t0));
+            
+            // Final processing
+            // TODO: only if not commet, this doesn't work with the 
+            // other connectors since we don't have the info
+            // TODO: add this note in the nio/apr connectors
+            // TODO: play nice with TomcatLite, other adapters that flush/close
+            if (res.getNote(CoyoteServer.COMET_RES_NOTE) == null) {
+                MessageWriter mw = MessageWriter.getWriter(req, res, 0);
+                mw.flush();
+                mw.recycle();
+                MessageReader reader = CoyoteUtils.getReader(req);
+                reader.recycle();
+                res.finish();
+
+                req.recycle();
+                res.recycle();
+            }
+        }
+    }
+
+    public BaseMapper getMapper() {
+      return mapper;
+    }
+
+    public void setDefaultAdapter(Adapter adapter) {
+        mapper.addWrapper("/", adapter);
+    }
+    
+    public boolean event(Request req, Response res, boolean error) throws Exception {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    public boolean event(Request req, Response res, SocketStatus status)
+        throws Exception {
+      return false;
+    }
+
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/MessageReader.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/MessageReader.java
new file mode 100644 (file)
index 0000000..5215e5d
--- /dev/null
@@ -0,0 +1,519 @@
+/*
+ * 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.tomcat.lite.coyote;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.HashMap;
+
+import org.apache.coyote.Request;
+import org.apache.tomcat.util.buf.B2CConverter;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.CharChunk;
+
+/**
+ * Refactored from catalina.connector.InputBuffer. Renamed to avoid conflict
+ * with coyote class.
+ * 
+ * TODO: move to coyote package.
+ */
+
+/**
+ * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
+ * OutputBuffer, adapted to handle input instead of output. This allows 
+ * complete recycling of the facade objects (the ServletInputStream and the
+ * BufferedReader).
+ *
+ * @author Remy Maucherat
+ */
+public class MessageReader extends Reader
+    implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel,
+               CharChunk.CharOutputChannel {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    public static final String DEFAULT_ENCODING = 
+        org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
+    public static final int DEFAULT_BUFFER_SIZE = 8*1024;
+
+    // The buffer can be used for byte[] and char[] reading
+    // ( this is needed to support ServletInputStream and BufferedReader )
+    public final int INITIAL_STATE = 0;
+    public final int CHAR_STATE = 1;
+    public final int BYTE_STATE = 2;
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The byte buffer. More data may be added to it while reading.
+     */
+    private ByteChunk bb;
+
+
+    /**
+     * The chunk buffer, will be filled in from the bb.
+     */
+    private CharChunk cb;
+
+
+    /**
+     * State of the output buffer.
+     */
+    private int state = 0;
+
+
+    /**
+     * Number of bytes read.
+     */
+    private int bytesRead = 0;
+
+
+    /**
+     * Number of chars read.
+     */
+    private int charsRead = 0;
+
+
+    /**
+     * Flag which indicates if the input buffer is closed.
+     */
+    private boolean closed = false;
+
+    /**
+     * Encoding to use.
+     */
+    private String enc;
+
+
+    /**
+     * Encoder is set.
+     */
+    private boolean gotEnc = false;
+
+
+    /**
+     * Cached encoders.
+     */
+    protected HashMap<String, B2CConverter> encoders = 
+      new HashMap<String, B2CConverter>();
+
+
+    /**
+     * Current byte to char converter.
+     */
+    protected B2CConverter conv;
+
+
+    /**
+     * Associated Coyote request.
+     */
+    private Request coyoteRequest;
+
+
+    /**
+     * Buffer position.
+     */
+    private int markPos = -1;
+
+
+    /**
+     * Buffer size.
+     */
+    private int size = -1;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Default constructor. Allocate the buffer with the default buffer size.
+     */
+    public MessageReader() {
+        this(DEFAULT_BUFFER_SIZE);
+    }
+
+
+    /**
+     * Alternate constructor which allows specifying the initial buffer size.
+     * 
+     * @param size Buffer size to use
+     */
+    public MessageReader(int size) {
+        this.size = size;
+        bb = new ByteChunk(size);
+        bb.setLimit(size);
+        bb.setByteInputChannel(this);
+        cb = new CharChunk(size);
+        cb.setLimit(size);
+        cb.setOptimizedWrite(false);
+        cb.setCharInputChannel(this);
+        cb.setCharOutputChannel(this);
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Associated Coyote request.
+     * 
+     * @param coyoteRequest Associated Coyote request
+     */
+    public void setRequest(Request coyoteRequest) {
+       this.coyoteRequest = coyoteRequest;
+    }
+
+
+    /**
+     * Get associated Coyote request.
+     * 
+     * @return the associated Coyote request
+     */
+    public Request getRequest() {
+        return this.coyoteRequest;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Recycle the output buffer.
+     */
+    public void recycle() {
+        
+        state = INITIAL_STATE;
+        bytesRead = 0;
+        charsRead = 0;
+        
+        // If usage of mark made the buffer too big, reallocate it
+        if (cb.getChars().length > size) {
+            cb = new CharChunk(size);
+            cb.setLimit(size);
+            cb.setCharInputChannel(this);
+            cb.setCharOutputChannel(this);
+        } else {
+            cb.recycle();
+        }
+        markPos = -1;
+        bb.recycle(); 
+        closed = false;
+        
+        if (conv != null) {
+            conv.recycle();
+        }
+        
+        gotEnc = false;
+        enc = null;
+        
+    }
+
+
+    /**
+     * Close the input buffer.
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    public void close()
+        throws IOException {
+        closed = true;
+    }
+
+
+    public int available()
+        throws IOException {
+        if (state == BYTE_STATE) {
+            return bb.getLength();
+        } else if (state == CHAR_STATE) {
+            return cb.getLength();
+        } else {
+            return 0;
+        }
+    }
+
+
+    // ------------------------------------------------- Bytes Handling Methods
+
+
+    /** 
+     * Reads new bytes in the byte chunk.
+     * 
+     * @param cbuf Byte buffer to be written to the response
+     * @param off Offset
+     * @param len Length
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    public int realReadBytes(byte cbuf[], int off, int len)
+       throws IOException {
+
+        if (closed)
+            return -1;
+        if (coyoteRequest == null)
+            return -1;
+
+        state = BYTE_STATE;
+
+        int result = coyoteRequest.doRead(bb);
+
+        return result;
+
+    }
+
+
+    public int readByte()
+        throws IOException {
+        return bb.substract();
+    }
+
+
+    public int read(byte[] b, int off, int len)
+        throws IOException {
+        return bb.substract(b, off, len);
+    }
+
+
+    // ------------------------------------------------- Chars Handling Methods
+
+
+    /**
+     * Since the converter will use append, it is possible to get chars to
+     * be removed from the buffer for "writing". Since the chars have already
+     * been read before, they are ignored. If a mark was set, then the
+     * mark is lost.
+     */
+    public void realWriteChars(char c[], int off, int len) 
+        throws IOException {
+        markPos = -1;
+    }
+
+
+    public void setEncoding(String s) {
+        enc = s;
+    }
+
+    /** 
+     * Called when a read(char[]) operation is lacking data. It will read
+     * bytes.
+     */
+    public int realReadChars(char cbuf[], int off, int len)
+        throws IOException {
+
+        if (!gotEnc)
+            setConverter();
+
+        if (bb.getLength() <= 0) {
+            int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
+            if (nRead < 0) {
+                return -1;
+            }
+        }
+
+        if (markPos == -1) {
+            cb.setOffset(0);
+            cb.setEnd(0);
+        }
+
+        conv.convert(bb, cb, -1);
+        bb.setOffset(bb.getEnd());
+        state = CHAR_STATE;
+
+        return cb.getLength();
+
+    }
+
+
+    public int read()
+        throws IOException {
+        return cb.substract();
+    }
+
+
+    public int read(char[] cbuf)
+        throws IOException {
+        return read(cbuf, 0, cbuf.length);
+    }
+
+
+    public int read(char[] cbuf, int off, int len)
+        throws IOException {
+        return cb.substract(cbuf, off, len);
+    }
+
+
+    public long skip(long n)
+        throws IOException {
+
+        if (n < 0) {
+            throw new IllegalArgumentException();
+        }
+
+        long nRead = 0;
+        while (nRead < n) {
+            if (cb.getLength() >= n) {
+                cb.setOffset(cb.getStart() + (int) n);
+                nRead = n;
+            } else {
+                nRead += cb.getLength();
+                cb.setOffset(cb.getEnd());
+                int toRead = 0;
+                if (cb.getChars().length < (n - nRead)) {
+                    toRead = cb.getChars().length;
+                } else {
+                    toRead = (int) (n - nRead);
+                }
+                int nb = realReadChars(cb.getChars(), 0, toRead);
+                if (nb < 0)
+                    break;
+            }
+        }
+
+        return nRead;
+
+    }
+
+
+    public boolean ready()
+        throws IOException {
+        return (cb.getLength() > 0);
+    }
+
+
+    public boolean markSupported() {
+        return true;
+    }
+
+
+    public void mark(int readAheadLimit)
+        throws IOException {
+        if (cb.getLength() <= 0) {
+            cb.setOffset(0);
+            cb.setEnd(0);
+        } else {
+            if ((cb.getBuffer().length > (2 * size)) 
+                && (cb.getLength()) < (cb.getStart())) {
+                System.arraycopy(cb.getBuffer(), cb.getStart(), 
+                                 cb.getBuffer(), 0, cb.getLength());
+                cb.setEnd(cb.getLength());
+                cb.setOffset(0);
+            }
+        }
+        int offset = readAheadLimit;
+        if (offset < size) {
+            offset = size;
+        }
+        cb.setLimit(cb.getStart() + offset);
+        markPos = cb.getStart();
+    }
+
+
+    public void reset()
+        throws IOException {
+        if (state == CHAR_STATE) {
+            if (markPos < 0) {
+                cb.recycle();
+                markPos = -1;
+                throw new IOException();
+            } else {
+                cb.setOffset(markPos);
+            }
+        } else {
+            bb.recycle();
+        }
+    }
+
+
+    protected void setConverter()
+        throws IOException {
+        if (coyoteRequest != null)
+            enc = coyoteRequest.getCharacterEncoding();
+
+        gotEnc = true;
+        if (enc == null)
+            enc = DEFAULT_ENCODING;
+        conv = (B2CConverter) encoders.get(enc);
+        if (conv == null) {
+          conv = new B2CConverter(enc);
+          encoders.put(enc, conv);
+        }
+    }
+    
+    public class MRInputStream extends InputStream {
+        public long skip(long n)
+                throws IOException {
+            return MessageReader.this.skip(n);
+        }
+
+        public void mark(int readAheadLimit)
+        {
+            try {
+                MessageReader.this.mark(readAheadLimit);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+
+
+        public void reset()
+        throws IOException {
+            MessageReader.this.reset();
+        }
+
+
+
+        public int read()
+        throws IOException {    
+            return MessageReader.this.readByte();
+        }
+
+        public int available() throws IOException {
+            return MessageReader.this.available();
+        }
+
+        public int read(final byte[] b) throws IOException {
+            return MessageReader.this.read(b, 0, b.length);
+        }
+
+
+        public int read(final byte[] b, final int off, final int len)
+        throws IOException {
+
+            return MessageReader.this.read(b, off, len);
+        }
+
+
+        /** 
+         * Close the stream
+         * Since we re-cycle, we can't allow the call to super.close()
+         * which would permantely disable us.
+         */
+        public void close() throws IOException {
+            MessageReader.this.close();
+        }
+    }
+    
+    MRInputStream is = new MRInputStream();
+    
+    public InputStream asInputStream() {
+        return is;
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/MessageWriter.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/coyote/MessageWriter.java
new file mode 100644 (file)
index 0000000..f53e534
--- /dev/null
@@ -0,0 +1,694 @@
+/*
+ * 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.tomcat.lite.coyote;
+
+
+import java.io.IOException;
+import java.io.Writer;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.Request;
+import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.C2BConverter;
+import org.apache.tomcat.util.buf.CharChunk;
+
+/*
+ * Refactoring: original code in catalina.connector.
+ * - renamed to OutputWriter to avoid confusion with coyote OutputBuffer
+ * - 
+ * TODO: move it to coyote, add Response.getWriter 
+ * 
+ */
+
+/**
+ * The buffer used by Tomcat response. This is a derivative of the Tomcat 3.3
+ * OutputBuffer, with the removal of some of the state handling (which in 
+ * Coyote is mostly the Processor's responsability).
+ *
+ * @author Costin Manolache
+ * @author Remy Maucherat
+ */
+public class MessageWriter extends Writer
+    implements ByteChunk.ByteOutputChannel, CharChunk.CharOutputChannel {
+
+    // used in getWriter, until a method is added to res.
+    private static final int WRITER_NOTE = 3;
+
+    // -------------------------------------------------------------- Constants
+
+
+    public static final String DEFAULT_ENCODING = 
+        org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
+    public static final int DEFAULT_BUFFER_SIZE = 8*1024;
+
+
+    // The buffer can be used for byte[] and char[] writing
+    // ( this is needed to support ServletOutputStream and for
+    // efficient implementations of templating systems )
+    public final int INITIAL_STATE = 0;
+    public final int CHAR_STATE = 1;
+    public final int BYTE_STATE = 2;
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The byte buffer.
+     */
+    private ByteChunk bb;
+
+
+    /**
+     * The chunk buffer.
+     */
+    private CharChunk cb;
+
+
+    /**
+     * State of the output buffer.
+     */
+    private int state = 0;
+
+
+    /**
+     * Number of bytes written.
+     */
+    private int bytesWritten = 0;
+
+
+    /**
+     * Number of chars written.
+     */
+    private int charsWritten = 0;
+
+
+    /**
+     * Flag which indicates if the output buffer is closed.
+     */
+    private boolean closed = false;
+
+
+    /**
+     * Do a flush on the next operation.
+     */
+    private boolean doFlush = false;
+
+
+    /**
+     * Byte chunk used to output bytes. This is just used to wrap the byte[]
+     * to match the coyote OutputBuffer interface
+     */
+    private ByteChunk outputChunk = new ByteChunk();
+
+
+    /**
+     * Encoding to use. 
+     * TODO: isn't it redundant ? enc, gotEnc, conv plus the enc in the bb
+     */
+    private String enc;
+
+
+    /**
+     * Encoder is set.
+     */
+    private boolean gotEnc = false;
+
+
+    /**
+     * List of encoders. The writer is reused - the encoder mapping 
+     * avoids creating expensive objects. In future it'll contain nio.Charsets
+     */
+    protected HashMap encoders = new HashMap();
+
+
+    /**
+     * Current char to byte converter. TODO: replace with Charset
+     */
+    protected C2BConverter conv;
+
+
+    /**
+     * Associated Coyote response.
+     */
+    private Response coyoteResponse;
+
+
+    /**
+     * Suspended flag. All output bytes will be swallowed if this is true.
+     */
+    private boolean suspended = false;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Default constructor. Allocate the buffer with the default buffer size.
+     */
+    public MessageWriter() {
+
+        this(DEFAULT_BUFFER_SIZE);
+
+    }
+
+
+    /**
+     * Alternate constructor which allows specifying the initial buffer size.
+     * 
+     * @param size Buffer size to use
+     */
+    public MessageWriter(int size) {
+
+        bb = new ByteChunk(size);
+        bb.setLimit(size);
+        bb.setByteOutputChannel(this);
+        cb = new CharChunk(size);
+        cb.setCharOutputChannel(this);
+        cb.setLimit(size);
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Associated Coyote response.
+     * 
+     * @param coyoteResponse Associated Coyote response
+     */
+    public void setResponse(Response coyoteResponse) {
+       this.coyoteResponse = coyoteResponse;
+    }
+
+
+    /**
+     * Get associated Coyote response.
+     * 
+     * @return the associated Coyote response
+     */
+    public Response getResponse() {
+        return this.coyoteResponse;
+    }
+
+
+    /**
+     * Is the response output suspended ?
+     * 
+     * @return suspended flag value
+     */
+    public boolean isSuspended() {
+        return this.suspended;
+    }
+
+
+    /**
+     * Set the suspended flag.
+     * 
+     * @param suspended New suspended flag value
+     */
+    public void setSuspended(boolean suspended) {
+        this.suspended = suspended;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Recycle the output buffer.
+     */
+    public void recycle() {
+        
+        state = INITIAL_STATE;
+        bytesWritten = 0;
+        charsWritten = 0;
+        
+        cb.recycle();
+        bb.recycle(); 
+        closed = false;
+        suspended = false;
+        
+        if (conv!= null) {
+            conv.recycle();
+        }
+        
+        gotEnc = false;
+        enc = null;
+        
+    }
+
+
+    /**
+     * Close the output buffer. This tries to calculate the response size if 
+     * the response has not been committed yet.
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    public void close()
+        throws IOException {
+
+        if (closed)
+            return;
+        if (suspended)
+            return;
+
+        if ((!coyoteResponse.isCommitted()) 
+            && (coyoteResponse.getContentLengthLong() == -1)) {
+            // Flushing the char buffer
+            if (state == CHAR_STATE) {
+                cb.flushBuffer();
+                state = BYTE_STATE;
+            }
+            // If this didn't cause a commit of the response, the final content
+            // length can be calculated
+            if (!coyoteResponse.isCommitted()) {
+                coyoteResponse.setContentLength(bb.getLength());
+            }
+        }
+
+        doFlush(false);
+        closed = true;
+
+        coyoteResponse.finish();
+
+    }
+
+
+    /**
+     * Flush bytes or chars contained in the buffer.
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    public void flush()
+        throws IOException {
+        doFlush(true);
+    }
+
+
+    /**
+     * Flush bytes or chars contained in the buffer.
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    protected void doFlush(boolean realFlush)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        doFlush = true;
+        if (state == CHAR_STATE) {
+            cb.flushBuffer();
+            bb.flushBuffer();
+            state = BYTE_STATE;
+        } else if (state == BYTE_STATE) {
+            bb.flushBuffer();
+        } else if (state == INITIAL_STATE) {
+            // If the buffers are empty, commit the response header
+            coyoteResponse.sendHeaders();
+        }
+        doFlush = false;
+
+        if (realFlush) {
+            coyoteResponse.action(ActionCode.ACTION_CLIENT_FLUSH, 
+                                  coyoteResponse);
+            // If some exception occurred earlier, or if some IOE occurred
+            // here, notify the servlet with an IOE
+            if (coyoteResponse.isExceptionPresent()) {
+                throw new ClientAbortException
+                    (coyoteResponse.getErrorException());
+            }
+        }
+
+    }
+
+
+    // ------------------------------------------------- Bytes Handling Methods
+
+
+    /** 
+     * Sends the buffer data to the client output, checking the
+     * state of Response and calling the right interceptors.
+     * 
+     * @param buf Byte buffer to be written to the response
+     * @param off Offset
+     * @param cnt Length
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    public void realWriteBytes(byte buf[], int off, int cnt)
+       throws IOException {
+
+        if (closed)
+            return;
+        if (coyoteResponse == null)
+            return;
+
+        // If we really have something to write
+        if (cnt > 0) {
+            // real write to the adapter
+            outputChunk.setBytes(buf, off, cnt);
+            try {
+                coyoteResponse.doWrite(outputChunk);
+            } catch (IOException e) {
+                // An IOException on a write is almost always due to
+                // the remote client aborting the request.  Wrap this
+                // so that it can be handled better by the error dispatcher.
+                throw new ClientAbortException(e);
+            }
+        }
+
+    }
+
+
+    public void write(byte b[], int off, int len) throws IOException {
+
+        if (suspended)
+            return;
+
+        if (state == CHAR_STATE)
+            cb.flushBuffer();
+        state = BYTE_STATE;
+        writeBytes(b, off, len);
+
+    }
+
+
+    private void writeBytes(byte b[], int off, int len) 
+        throws IOException {
+
+        if (closed)
+            return;
+
+        bb.append(b, off, len);
+        bytesWritten += len;
+
+        // if called from within flush(), then immediately flush
+        // remaining bytes
+        if (doFlush) {
+            bb.flushBuffer();
+        }
+
+    }
+
+
+    public void writeByte(int b)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        if (state == CHAR_STATE)
+            cb.flushBuffer();
+        state = BYTE_STATE;
+
+        bb.append( (byte)b );
+        bytesWritten++;
+
+    }
+
+
+    // ------------------------------------------------- Chars Handling Methods
+
+
+    public void write(int c)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        state = CHAR_STATE;
+
+        cb.append((char) c);
+        charsWritten++;
+
+    }
+
+
+    public void write(char c[])
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        write(c, 0, c.length);
+
+    }
+
+
+    public void write(char c[], int off, int len)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        state = CHAR_STATE;
+
+        cb.append(c, off, len);
+        charsWritten += len;
+
+    }
+
+
+    public void write(StringBuffer sb)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        state = CHAR_STATE;
+
+        int len = sb.length();
+        charsWritten += len;
+        cb.append(sb);
+
+    }
+
+
+    /**
+     * Append a string to the buffer
+     */
+    public void write(String s, int off, int len)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        state=CHAR_STATE;
+
+        charsWritten += len;
+        if (s==null)
+            s="null";
+        cb.append( s, off, len );
+
+    }
+
+
+    public void write(String s)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        state = CHAR_STATE;
+        if (s==null)
+            s="null";
+        write(s, 0, s.length());
+
+    } 
+
+    public void println() throws IOException {
+        write("\n");
+    }
+
+    public void println(String s) throws IOException {
+        write(s);
+        write("\n");
+    }
+
+    public void print(String s) throws IOException {
+        write(s);
+    }
+
+    public void flushChars()
+        throws IOException {
+
+        cb.flushBuffer();
+        state = BYTE_STATE;
+
+    }
+
+
+    public boolean flushCharsNeeded() {
+        return state == CHAR_STATE;
+    }
+
+
+    public void setEncoding(String s) {
+        enc = s;
+    }
+
+
+    public void realWriteChars(char c[], int off, int len) 
+        throws IOException {
+
+        if (!gotEnc)
+            setConverter();
+
+        conv.convert(c, off, len);
+        conv.flushBuffer();    // ???
+
+    }
+
+
+    public void checkConverter() 
+        throws IOException {
+
+        if (!gotEnc)
+            setConverter();
+
+    }
+
+
+    protected void setConverter() 
+        throws IOException {
+
+        if (coyoteResponse != null)
+            enc = coyoteResponse.getCharacterEncoding();
+
+        gotEnc = true;
+        if (enc == null)
+            enc = DEFAULT_ENCODING;
+        conv = (C2BConverter) encoders.get(enc);
+        if (conv == null) {
+            
+            if (System.getSecurityManager() != null){
+                try{
+                    conv = (C2BConverter)AccessController.doPrivileged(
+                            new PrivilegedExceptionAction(){
+
+                                public Object run() throws IOException{
+                                    return new C2BConverter(bb, enc);
+                                }
+
+                            }
+                    );              
+                }catch(PrivilegedActionException ex){
+                    Exception e = ex.getException();
+                    if (e instanceof IOException)
+                        throw (IOException)e; 
+                }
+            } else {
+                conv = new C2BConverter(bb, enc);
+            }
+            
+            encoders.put(enc, conv);
+
+        }
+    }
+
+    
+    // --------------------  BufferedOutputStream compatibility
+
+
+    /**
+     * Real write - this buffer will be sent to the client
+     */
+    public void flushBytes()
+        throws IOException {
+
+        bb.flushBuffer();
+
+    }
+
+
+    public int getBytesWritten() {
+        return bytesWritten;
+    }
+
+
+    public int getCharsWritten() {
+        return charsWritten;
+    }
+
+
+    public int getContentWritten() {
+        return bytesWritten + charsWritten;
+    }
+
+
+    /** 
+     * True if this buffer hasn't been used ( since recycle() ) -
+     * i.e. no chars or bytes have been added to the buffer.  
+     */
+    public boolean isNew() {
+        return (bytesWritten == 0) && (charsWritten == 0);
+    }
+
+
+    public void setBufferSize(int size) {
+        if (size > bb.getLimit()) {// ??????
+           bb.setLimit(size);
+       }
+    }
+
+
+    public void reset() {
+
+        //count=0;
+        bb.recycle();
+        bytesWritten = 0;
+        cb.recycle();
+        charsWritten = 0;
+        gotEnc = false;
+        enc = null;
+        state = INITIAL_STATE;
+    }
+
+
+    public int getBufferSize() {
+       return bb.getLimit();
+    }
+
+
+    public static MessageWriter getWriter(Request req, Response res, int size) 
+    {        
+        MessageWriter out=(MessageWriter)req.getNote(MessageWriter.WRITER_NOTE);
+        if( out == null ) {
+            if( size<=0 ) {
+                out=new MessageWriter();
+            } else {
+                out=new MessageWriter(size);
+            }
+            out.setResponse(res);
+            req.setNote(MessageWriter.WRITER_NOTE, out );
+        }
+        return out;
+    }
+
+    public ByteChunk getByteBuffer() {
+      return outputChunk;
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/TomcatLiteWebXmlConfig.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/TomcatLiteWebXmlConfig.java
new file mode 100644 (file)
index 0000000..dff9e34
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ */
+package org.apache.tomcat.lite.webxml;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.apache.tomcat.lite.ServletContextImpl;
+import org.apache.tomcat.lite.ContextPreinitListener;
+
+/**
+ * Default configurator - parse web.xml, init the context.
+ * 
+ * Will be executed first - if set as the default config addon.
+ * 
+ * Possible extensions: 
+ *   - read from a .ser file instead of web.xml
+ *   - custom code for manual/extra config
+ *   - read from a central repo
+ *  
+ * @author Costin Manolache
+ */
+public class TomcatLiteWebXmlConfig implements ContextPreinitListener {
+
+    protected void readWebXml(ServletContextImpl ctx, 
+                              String base) throws ServletException {
+        // TODO: .ser, reloading, etc
+//        if (contextConfig != null && contextConfig.fileName != null) {
+//            // TODO: this should move to deploy - if not set, there is no point
+//            File f = new File(contextConfig.fileName);
+//            if (f.exists()) {
+//                if (f.lastModified() > contextConfig.timestamp + 1000) {
+//                    log("Reloading web.xml");
+//                    contextConfig = null;
+//                }
+//            } else {
+//                log("Old web.xml");
+//                contextConfig = null;
+//            }
+//        }
+        if (base != null) {
+            WebXml webXml = new WebXml(ctx.getContextConfig());
+            webXml.readWebXml(base);
+        }
+    }
+
+    @Override
+    public void preInit(ServletContext ctx) {
+        ServletContextImpl servletContext =  
+            (ServletContextImpl) ctx;
+        
+        String base = servletContext.getBasePath();
+        if (base == null) {
+            return; // nothing we can do
+        }
+        try {
+            readWebXml(servletContext, base);
+        } catch (ServletException e) {
+            // TODO Auto-generated catch block
+            throw new RuntimeException(e);
+        }
+    }
+    
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/WebAnnotation.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/WebAnnotation.java
new file mode 100644 (file)
index 0000000..cecaf60
--- /dev/null
@@ -0,0 +1,152 @@
+package org.apache.tomcat.lite.webxml;
+
+import java.util.Iterator;
+import java.util.List;
+
+import javax.annotation.security.DeclareRoles;
+import javax.annotation.security.RunAs;
+
+import org.apache.tomcat.lite.ServletContextConfig;
+import org.apache.tomcat.lite.ServletContextConfig.FilterData;
+import org.apache.tomcat.lite.ServletContextConfig.ServletData;
+
+
+
+
+/**
+ * Based on catalina.WebAnnotationSet
+ * 
+ * Supports:
+ *  @DeclaresRoles - on Servlet class - web-app/security-role/role-name
+ *  @RunAs - on Servlet class - web-app/servlet/run-as
+ *  
+ * 
+ * No support for jndi @Resources, @Resource
+ * No @InjectionComplete callback annotation
+ * 
+ * No support for @EJB, @WebServiceRef
+ *  
+ * @author costin
+ * @author Fabien Carrion
+ */
+public class WebAnnotation {
+
+    /**
+     * Process the annotations on a context.
+     */
+    public static void loadApplicationAnnotations(ServletContextConfig context, ClassLoader classLoader) {
+        loadApplicationListenerAnnotations(context, classLoader);
+        loadApplicationFilterAnnotations(context, classLoader);
+        loadApplicationServletAnnotations(context, classLoader);
+    }
+    
+    
+    // -------------------------------------------------------- protected Methods
+    
+    
+    /**
+     * Process the annotations for the listeners.
+     */
+    static void loadApplicationListenerAnnotations(ServletContextConfig context, ClassLoader classLoader) {
+        List applicationListeners = context.listenerClass;
+        for (int i = 0; i < applicationListeners.size(); i++) {
+            loadClassAnnotation(context, (String)applicationListeners.get(i), classLoader);
+        }
+    }
+    
+    
+    /**
+     * Process the annotations for the filters.
+     */
+    static void loadApplicationFilterAnnotations(ServletContextConfig context, ClassLoader classLoader) {
+        Iterator i1 = context.filters.values().iterator();
+        while (i1.hasNext()) {
+            FilterData fc = (FilterData) i1.next();
+            loadClassAnnotation(context, fc.filterClass, classLoader);
+        }
+    }
+    
+    
+    /**
+     * Process the annotations for the servlets.
+     * @param classLoader 
+     */
+    static void loadApplicationServletAnnotations(ServletContextConfig context, ClassLoader classLoader) {
+        Class classClass = null;
+        
+        
+        Iterator i1 = context.servlets.values().iterator();
+        while (i1.hasNext()) {
+            ServletData sd = (ServletData) i1.next();
+            if (sd.servletClass == null) {
+                continue;
+            }
+
+            try {
+                classClass = classLoader.loadClass(sd.servletClass);
+            } catch (ClassNotFoundException e) {
+                // We do nothing
+            } catch (NoClassDefFoundError e) {
+                // We do nothing
+            }
+
+            if (classClass == null) {
+                continue;
+            }
+
+            loadClassAnnotation(context, classClass);
+            /* Process RunAs annotation which can be only on servlets.
+             * Ref JSR 250, equivalent to the run-as element in
+             * the deployment descriptor
+             */
+            if (classClass.isAnnotationPresent(RunAs.class)) {
+                RunAs annotation = (RunAs) 
+                classClass.getAnnotation(RunAs.class);
+                sd.runAs = annotation.value();
+            }
+        }
+    }
+    
+    /**
+     * Process the annotations on a context for a given className.
+     */
+    static void loadClassAnnotation(ServletContextConfig context, 
+                                    String classClass2, ClassLoader classLoader) {
+        
+        Class classClass = null;
+        
+        try {
+            classClass = classLoader.loadClass(classClass2);
+        } catch (ClassNotFoundException e) {
+            // We do nothing
+        } catch (NoClassDefFoundError e) {
+            // We do nothing
+        }
+        
+        if (classClass == null) {
+            return;
+        }
+        loadClassAnnotation(context, classClass);
+    }
+     
+    static void loadClassAnnotation(ServletContextConfig context, 
+                                    Class classClass) {
+        
+        /* Process DeclareRoles annotation.
+         * Ref JSR 250, equivalent to the security-role element in
+         * the deployment descriptor
+         */
+        if (classClass.isAnnotationPresent(DeclareRoles.class)) {
+            DeclareRoles annotation = (DeclareRoles) 
+                classClass.getAnnotation(DeclareRoles.class);
+            for (int i = 0; annotation.value() != null && 
+                            i < annotation.value().length; i++) {
+                context.securityRole.add(annotation.value()[i]);
+            }
+        }
+        
+        
+    }
+
+    
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/WebResourceCollectionData.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/WebResourceCollectionData.java
new file mode 100644 (file)
index 0000000..94fa2a4
--- /dev/null
@@ -0,0 +1,13 @@
+/**
+ * 
+ */
+package org.apache.tomcat.lite.webxml;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+public class WebResourceCollectionData implements Serializable {
+    public String webResourceName;
+    public ArrayList urlPattern = new ArrayList();
+    public ArrayList httpMethod = new ArrayList();
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/WebXml.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/webxml/WebXml.java
new file mode 100644 (file)
index 0000000..65a3fc1
--- /dev/null
@@ -0,0 +1,384 @@
+/*
+ */
+package org.apache.tomcat.lite.webxml;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.servlet.ServletException;
+
+import org.apache.tomcat.lite.ServletContextConfig;
+import org.apache.tomcat.lite.ServletContextConfig.EnvEntryData;
+import org.apache.tomcat.lite.ServletContextConfig.FilterData;
+import org.apache.tomcat.lite.ServletContextConfig.FilterMappingData;
+import org.apache.tomcat.lite.ServletContextConfig.SecurityConstraintData;
+import org.apache.tomcat.lite.ServletContextConfig.ServletData;
+import org.apache.tomcat.util.DomUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+/** 
+ * General-purpose utility to process an web.xml file. Result
+ *  is a tree of objects starting with WebAppData.
+ * 
+ * TODO: allow writting of web.xml, allow modification ( preserving 
+ * comments )
+ * 
+ * @author costin
+ */
+public class WebXml {
+    ServletContextConfig d;
+    
+    public WebXml(ServletContextConfig cfg) {
+        d = cfg;
+    }
+
+    /**
+     * Serialize the data, for caching. 
+     */
+    public void saveWebAppData(String fileName) throws IOException {
+        ObjectOutputStream oos = 
+            new ObjectOutputStream(new FileOutputStream(fileName));
+        oos.writeObject(d);
+        oos.close();
+    }
+    
+    public ServletContextConfig getWebAppData() {
+        return d;
+    }
+    
+    public void readWebXml(String baseDir) throws ServletException {
+        try {
+            File webXmlFile = new File( baseDir + "/WEB-INF/web.xml");
+            if (!webXmlFile.exists()) {
+                return;
+            }
+            d.fileName = webXmlFile.getCanonicalPath();
+            d.timestamp = webXmlFile.lastModified();
+            
+            FileInputStream fileInputStream = new FileInputStream(webXmlFile);
+            readWebXml(fileInputStream);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServletException(e);
+        }
+    }
+    
+     public void readWebXml(InputStream fileInputStream) 
+             throws ServletException {
+         try {
+                    
+            Document document = DomUtil.readXml(fileInputStream);
+            Node webappNode = DomUtil.getChild(document, "web-app");
+
+            String fullS = DomUtil.getAttribute(webappNode, "full");
+            if (fullS != null && fullS.equalsIgnoreCase("true")) {
+                d.full = true;
+            }
+            
+            d.displayName = DomUtil.getAttribute(webappNode, "display-name");
+                        
+            // Process each child of web-app
+            Node confNode = DomUtil.getChild(webappNode, "filter");
+            while (confNode != null ) {
+                processFilter(confNode);
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            confNode = DomUtil.getChild(webappNode, "filter-mapping");
+            while (confNode != null ) {
+                processFilterMapping(confNode);
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            confNode = DomUtil.getChild(webappNode, "context-param");
+            while (confNode != null ) {
+                String n = DomUtil.getChildContent(confNode, "param-name").trim();
+                String v = DomUtil.getChildContent(confNode, "param-value").trim();
+                d.contextParam.put(n, v);
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            confNode = DomUtil.getChild(webappNode, "mime-mapping");
+            while (confNode != null ) {
+                String n = DomUtil.getChildContent(confNode, "extension");
+                String t = DomUtil.getChildContent(confNode, "mime-type");
+                d.mimeMapping.put(n, t);
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            confNode = DomUtil.getChild(webappNode, "error-page");
+            while (confNode != null ) {
+                processErrorPage(confNode);
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            confNode = DomUtil.getChild(webappNode, "jsp-config");
+            while (confNode != null ) {
+                processJspConfig(confNode);
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            confNode = DomUtil.getChild(webappNode, "servlet");
+            while (confNode != null ) {
+                processServlet(confNode);
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            confNode = DomUtil.getChild(webappNode, "servlet-mapping");
+            while (confNode != null ) {
+                processServletMapping(confNode);
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            confNode = DomUtil.getChild(webappNode, "listener");
+            while (confNode != null ) {
+                String lClass = DomUtil.getChildContent(confNode, "listener-class");
+                d.listenerClass.add(lClass);
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            confNode = DomUtil.getChild(webappNode, "security-constraint");
+            while (confNode != null ) {
+                processSecurityConstraint(confNode);
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            confNode = DomUtil.getChild(webappNode, "login-config");
+            while (confNode != null ) {
+                processLoginConfig(confNode);
+                confNode = DomUtil.getNext(confNode);
+                if (confNode != null) 
+                    throw new ServletException("Multiple login-config");
+            }
+
+            confNode = DomUtil.getChild(webappNode, "session-config");
+            while (confNode != null ) {
+                String n = DomUtil.getChildContent(confNode, "session-timeout");
+                int stout = Integer.parseInt(n);
+                d.sessionTimeout = stout;
+                confNode = DomUtil.getNext(confNode);
+                if (confNode != null) 
+                    throw new ServletException("Multiple session-config");
+            }
+
+            confNode = DomUtil.getChild(webappNode, "welcome-file-list");
+            while (confNode != null ) {
+                Node wf = DomUtil.getChild(confNode, "welcome-file");
+                while (wf != null) {
+                    String file = DomUtil.getContent(wf);
+                    d.welcomeFileList.add(file);
+                    wf = DomUtil.getNext(wf);
+                }
+                // more sections ?
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            // Not supported right now - TODO: collect, have jndi plugin
+            confNode = DomUtil.getChild(webappNode, "env-entry");
+            while (confNode != null ) {
+                processEnvEntry(confNode);
+                confNode = DomUtil.getNext(confNode);
+            }
+            
+            confNode = DomUtil.getChild(webappNode, "locale-encoding-mapping-list");
+            while (confNode != null ) {
+                confNode = DomUtil.getNext(confNode);
+                String n = DomUtil.getChildContent(confNode, "locale");
+                String t = DomUtil.getChildContent(confNode, "encoding");
+                d.localeEncodingMapping.put(n, t);
+            }
+
+            confNode = DomUtil.getChild(webappNode, "distributable");
+            while (confNode != null ) {
+                d.distributable = true;
+                confNode = DomUtil.getNext(confNode);
+            }
+
+            confNode = DomUtil.getChild(confNode, "security-role");
+            while (confNode != null ) {
+                String n = DomUtil.getChildContent(confNode, "role-name");
+                d.securityRole.add(n);
+                confNode = DomUtil.getNext(confNode);
+            }
+
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    throw new ServletException(e);
+                }
+    }
+    
+    private void processJspConfig(Node confNode) {
+        Node tagLib = DomUtil.getChild(confNode, "taglib");
+        while (tagLib != null) {
+            String uri = DomUtil.getChildContent(tagLib, "taglib-uri");
+            String l = DomUtil.getChildContent(tagLib, "taglib-location");
+            //d.tagLibs.put(uri, l);
+            tagLib = DomUtil.getNext(tagLib);
+        }
+        
+        tagLib = DomUtil.getChild(confNode, "jsp-property-group");
+        while (tagLib != null) {
+            // That would be the job of the JSP servlet to process.
+            tagLib = DomUtil.getNext(tagLib);
+        }
+    }
+
+    private void processEnvEntry(Node confNode) {
+        EnvEntryData ed = new EnvEntryData();
+        ed.envEntryName = DomUtil.getChildContent(confNode,"env-entry-name");
+        ed.envEntryType = DomUtil.getChildContent(confNode,"env-entry-type");
+        ed.envEntryValue = DomUtil.getChildContent(confNode,"env-entry-value");
+        d.envEntry.add(ed);
+    }
+
+    private void processLoginConfig(Node confNode) {
+        d.authMethod = DomUtil.getChildContent(confNode,"auth-method");
+        d.realmName = DomUtil.getChildContent(confNode,"auth-method");
+        Node formNode = DomUtil.getChild(confNode, "form-login-config");
+        if (formNode != null) {
+            d.formLoginPage = DomUtil.getChildContent(formNode,"form-login-page");
+            d.formErrorPage = DomUtil.getChildContent(formNode,"form-error-page");
+        }
+    }
+
+    private void processSecurityConstraint(Node confNode) {
+        SecurityConstraintData sd = new SecurityConstraintData();
+        Node cn = DomUtil.getChild(confNode, "web-resource-collection");
+        while (cn != null) {
+            WebResourceCollectionData wrd = new WebResourceCollectionData();
+            wrd.webResourceName = DomUtil.getChildContent(cn, "web-resource-name");
+            Node scn = DomUtil.getChild(cn,"url-pattern");
+            while (scn != null) {
+                wrd.urlPattern.add(DomUtil.getContent(scn));
+                scn = DomUtil.getNext(scn);
+            }
+            scn = DomUtil.getChild(cn,"http-method");
+            while (scn != null) {
+                wrd.httpMethod.add(DomUtil.getContent(scn));
+                scn = DomUtil.getNext(scn);
+            }
+            cn = DomUtil.getNext(cn);
+        }
+        d.securityConstraint.add(sd);
+    }
+
+    private void processErrorPage(Node confNode) {
+        String name = DomUtil.getChildContent(confNode,"location");
+        String c = DomUtil.getChildContent(confNode,"error-code");
+        String t = DomUtil.getChildContent(confNode,"exception-type");
+        if (c != null) {
+            d.errorPageCode.put(c, name);
+        }
+        if (t != null) {
+            d.errorPageException.put(t, name);
+        }
+    }
+
+    private void processServlet(Node confNode) throws ServletException {
+        ServletData sd = new ServletData();
+
+        sd.servletName = DomUtil.getChildContent(confNode,"servlet-name");
+        sd.servletClass = DomUtil.getChildContent(confNode,"servlet-class");
+        sd.jspFile = DomUtil.getChildContent(confNode,"jsp-file");
+        
+        processInitParams(confNode, sd.initParams);
+        
+        d.servlets.put( sd.servletName, sd );
+        
+        String los = DomUtil.getChildContent(confNode, "load-on-startup");
+        if (los != null ) { 
+            sd.loadOnStartup = Integer.parseInt(los);
+        }
+        
+        Node sn = DomUtil.getChild(confNode, "security-role-ref");
+        while (sn != null ) {
+            String roleName = DomUtil.getChildContent(sn, "role-name");
+            String roleLink = DomUtil.getChildContent(sn, "role-link");
+            if (roleLink == null) {
+                sd.securityRoleRef.put(roleName, "");
+            } else {
+                sd.securityRoleRef.put(roleName, roleLink);
+            }
+            sn = DomUtil.getNext(sn);
+        }
+    }
+
+    private void processInitParams(Node confNode, HashMap initParams) {
+        Node initN = DomUtil.getChild(confNode, "init-param");
+        while (initN != null ) {
+            String n = DomUtil.getChildContent(initN, "param-name");
+            String v = DomUtil.getChildContent(initN, "param-value");
+            initParams.put(n, v);
+            initN = DomUtil.getNext(initN);
+        }
+    }
+
+    private void processServletMapping(Node confNode) {
+        String name = DomUtil.getChildContent(confNode,"servlet-name");
+        Node dataN = DomUtil.getChild(confNode, "url-pattern");
+        while (dataN != null) {
+            String path = DomUtil.getContent(dataN).trim();
+            dataN = DomUtil.getNext(dataN);
+            
+            if (! (path.startsWith("/") || path.startsWith("*"))) {
+                // backward compat 
+                path = "/" + path;
+            }
+            d.servletMapping.put(path, name);
+        }
+    }
+
+    private void processFilterMapping(Node confNode) {
+      String filterName = DomUtil.getChildContent(confNode,"filter-name");
+      // multiple 
+      ArrayList dispatchers = new ArrayList();
+      Node dataN = DomUtil.getChild(confNode, "dispatcher");
+      while (dataN != null ) {
+          String d = DomUtil.getContent(dataN);
+          dispatchers.add(d);
+          dataN = DomUtil.getNext(dataN);
+      }
+      
+      // Multiple url-pattern and servlet-name in one
+      // mapping rule. Need to be applied in order.
+      dataN = DomUtil.getChild(confNode, "url-pattern");
+      while (dataN != null ) {
+        FilterMappingData fm = new FilterMappingData();
+        fm.filterName = filterName;
+        fm.dispatcher = dispatchers;
+        String path = DomUtil.getContent(dataN);
+        dataN = DomUtil.getNext(dataN);
+        fm.urlPattern = path;
+        d.filterMappings.add(fm);
+      }
+      dataN = DomUtil.getChild(confNode, "servlet-name");
+      while (dataN != null ) {
+        FilterMappingData fm = new FilterMappingData();
+        fm.filterName = filterName;
+        fm.dispatcher = dispatchers;
+        String sn = DomUtil.getContent(dataN);
+        dataN = DomUtil.getNext(dataN);
+        fm.servletName = sn;
+        d.filterMappings.add(fm);
+      }
+    }
+
+    private void processFilter(Node confNode) {
+        String name = DomUtil.getChildContent(confNode,"filter-name");
+        String sclass = DomUtil.getChildContent(confNode,"filter-class");
+        
+        FilterData fd = new FilterData();
+        processInitParams(confNode, fd.initParams);
+        fd.filterName = name;
+        fd.filterClass = sclass;
+        d.filters.put(name, fd);
+    }
+    
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/CopyUtils.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/CopyUtils.java
new file mode 100644 (file)
index 0000000..ebe966d
--- /dev/null
@@ -0,0 +1,257 @@
+package org.apache.tomcat.servlets.file;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+
+import javax.servlet.ServletOutputStream;
+
+public class CopyUtils {
+    protected static int input = 2048;
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param resourceInfo The resource info
+     * @param writer The writer to write to
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public static void copy(InputStream is, 
+                            PrintWriter writer,
+                            String fileEncoding)
+        throws IOException {
+
+        IOException exception = null;
+
+        InputStream resourceInputStream = is;
+
+        Reader reader;
+        if (fileEncoding == null) {
+            reader = new InputStreamReader(resourceInputStream);
+        } else {
+            reader = new InputStreamReader(resourceInputStream,
+                                           fileEncoding);
+        }
+
+        // Copy the input stream to the output stream
+        exception = copyRange(reader, writer);
+
+        // Clean up the reader
+        try {
+            reader.close();
+        } catch (Throwable t) {
+            ;
+        }
+
+        // Rethrow any exception that has occurred
+        if (exception != null)
+            throw exception;
+
+    }
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param resourceInfo The resource information
+     * @param ostream The output stream to write to
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public static void copy(InputStream is, OutputStream ostream)
+            throws IOException {
+
+        IOException exception = null;
+        InputStream resourceInputStream = null;
+
+        resourceInputStream = is;
+
+        InputStream istream = new BufferedInputStream
+            (resourceInputStream, input);
+
+        // Copy the input stream to the output stream
+        exception = CopyUtils.copyRange(istream, ostream);
+
+        // Clean up the input stream
+        try {
+            istream.close();
+        } catch (Throwable t) {
+            ;
+        }
+
+        // Rethrow any exception that has occurred
+        if (exception != null)
+            throw exception;
+    }
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param istream The input stream to read from
+     * @param ostream The output stream to write to
+     * @return Exception which occurred during processing
+     */
+    public static IOException copyRange(InputStream istream,
+                                        OutputStream ostream) {
+
+        // Copy the input stream to the output stream
+        IOException exception = null;
+        byte buffer[] = new byte[input];
+        int len = buffer.length;
+        while (true) {
+            try {
+                len = istream.read(buffer);
+                if (len == -1)
+                    break;
+                ostream.write(buffer, 0, len);
+            } catch (IOException e) {
+                exception = e;
+                len = -1;
+                break;
+            }
+        }
+        return exception;
+
+    }
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param reader The reader to read from
+     * @param writer The writer to write to
+     * @return Exception which occurred during processing
+     */
+    public static IOException copyRange(Reader reader, PrintWriter writer) {
+
+        // Copy the input stream to the output stream
+        IOException exception = null;
+        char buffer[] = new char[input];
+        int len = buffer.length;
+        while (true) {
+            try {
+                len = reader.read(buffer);
+                if (len == -1)
+                    break;
+                writer.write(buffer, 0, len);
+            } catch (IOException e) {
+                exception = e;
+                len = -1;
+                break;
+            }
+        }
+        return exception;
+
+    }
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param istream The input stream to read from
+     * @param ostream The output stream to write to
+     * @param start Start of the range which will be copied
+     * @param end End of the range which will be copied
+     * @return Exception which occurred during processing
+     */
+    public static IOException copyRange(InputStream istream,
+                                  ServletOutputStream ostream,
+                                  long start, long end) {
+
+        try {
+            istream.skip(start);
+        } catch (IOException e) {
+            return e;
+        }
+
+        IOException exception = null;
+        long bytesToRead = end - start + 1;
+
+        byte buffer[] = new byte[input];
+        int len = buffer.length;
+        while ( (bytesToRead > 0) && (len >= buffer.length)) {
+            try {
+                len = istream.read(buffer);
+                if (bytesToRead >= len) {
+                    ostream.write(buffer, 0, len);
+                    bytesToRead -= len;
+                } else {
+                    ostream.write(buffer, 0, (int) bytesToRead);
+                    bytesToRead = 0;
+                }
+            } catch (IOException e) {
+                exception = e;
+                len = -1;
+            }
+            if (len < buffer.length)
+                break;
+        }
+
+        return exception;
+
+    }
+
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param reader The reader to read from
+     * @param writer The writer to write to
+     * @param start Start of the range which will be copied
+     * @param end End of the range which will be copied
+     * @return Exception which occurred during processing
+     */
+    public static IOException copyRange(Reader reader, PrintWriter writer,
+                                  long start, long end) {
+
+        try {
+            reader.skip(start);
+        } catch (IOException e) {
+            return e;
+        }
+
+        IOException exception = null;
+        long bytesToRead = end - start + 1;
+
+        char buffer[] = new char[input];
+        int len = buffer.length;
+        while ( (bytesToRead > 0) && (len >= buffer.length)) {
+            try {
+                len = reader.read(buffer);
+                if (bytesToRead >= len) {
+                    writer.write(buffer, 0, len);
+                    bytesToRead -= len;
+                } else {
+                    writer.write(buffer, 0, (int) bytesToRead);
+                    bytesToRead = 0;
+                }
+            } catch (IOException e) {
+                exception = e;
+                len = -1;
+            }
+            if (len < buffer.length)
+                break;
+        }
+
+        return exception;
+
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/DefaultServlet.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/DefaultServlet.java
new file mode 100644 (file)
index 0000000..d9986b9
--- /dev/null
@@ -0,0 +1,1019 @@
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed 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.tomcat.servlets.file;
+
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tomcat.servlets.util.Range;
+
+/**
+ * The default resource-serving servlet for most web applications,
+ * used to serve static resources such as HTML pages and images.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class DefaultServlet  extends HttpServlet {
+
+
+    // ----------------------------------------------------- Instance Variables
+    
+
+    /**
+     * Should we generate directory listings?
+     */
+    protected boolean listings = true;
+
+    /**
+     * Array containing the safe characters set.
+     */
+    protected static URLEncoder urlEncoder;
+
+    /**
+     * Allow a readme file to be included.
+     */
+    protected String readmeFile = null;
+
+    // TODO: find a better default
+    /**
+     * The input buffer size to use when serving resources.
+     */
+    protected static int input = 2048;
+
+    /**
+     * The output buffer size to use when serving resources.
+     */
+    protected int output = 2048;
+
+    /**
+     * File encoding to be used when reading static files. If none is specified
+     * the platform default is used.
+     */
+    protected String fileEncoding = null;
+
+    static ThreadLocal formatTL = new ThreadLocal();
+        
+    Dir2Html dir2Html = new Dir2Html();
+    /**
+     * Full range marker.
+     */
+    protected static ArrayList FULL = new ArrayList();
+
+    // Context base dir
+    protected File basePath;
+    protected String basePathName;
+    
+    // ----------------------------------------------------- Static Initializer
+
+
+    /**
+     * GMT timezone - all HTTP dates are on GMT
+     */
+    static {
+        urlEncoder = new URLEncoder();
+        urlEncoder.addSafeCharacter('-');
+        urlEncoder.addSafeCharacter('_');
+        urlEncoder.addSafeCharacter('.');
+        urlEncoder.addSafeCharacter('*');
+        urlEncoder.addSafeCharacter('/');
+    }
+
+
+    /**
+     * MIME multipart separation string
+     */
+    protected static final String mimeSeparation = "TOMCAT_MIME_BOUNDARY";
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Finalize this servlet.
+     */
+    public void destroy() {
+    }
+
+
+    /**
+     * Initialize this servlet.
+     */
+    public void init() throws ServletException {
+        String realPath = getServletContext().getRealPath("/");
+        basePath = new File(realPath);
+        try {
+            basePathName = basePath.getCanonicalPath();
+        } catch (IOException e) {
+            basePathName = basePath.getAbsolutePath();
+        }
+        
+        // Set our properties from the initialization parameters
+        String value = null;
+        try {
+            value = getServletConfig().getInitParameter("input");
+            input = Integer.parseInt(value);
+        } catch (Throwable t) {
+            ;
+        }
+        try {
+            value = getServletConfig().getInitParameter("listings");
+            if (value != null )
+                listings = (new Boolean(value)).booleanValue();
+        } catch (Throwable t) {
+            ;
+        }
+        try {
+            value = getServletConfig().getInitParameter("output");
+            output = Integer.parseInt(value);
+        } catch (Throwable t) {
+            ;
+        }
+        fileEncoding = getServletConfig().getInitParameter("fileEncoding");
+        
+        readmeFile = getServletConfig().getInitParameter("readmeFile");
+
+        
+        // Sanity check on the specified buffer sizes
+        if (input < 256)
+            input = 256;
+        if (output < 256)
+            output = 256;
+
+    }
+
+    public void loadDefaultMime() throws IOException {
+        File mimeF = new File("/etc/mime.types");
+        boolean loaded =false;
+        if (!mimeF.exists()) {
+            loaded =true;
+            loadMimeFile( new FileInputStream(mimeF));
+        }
+        mimeF = new File("/etc/httpd/mime.types");
+        if (mimeF.exists()) {
+            loaded =true;
+            loadMimeFile( new FileInputStream(mimeF));
+        }
+        if (!loaded) {
+            throw new IOException("mime.types not found");
+        }
+    }
+
+    public void loadMimeFile(InputStream is) throws IOException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+        String line = null;
+        while((line = reader.readLine()) != null) {
+            line = line.trim();
+            if (line.length() == 0) continue;
+            if (line.startsWith("#")) continue;
+            String[] parts = line.split("\\w+");
+            String type = parts[0];
+            for (int i=1; i < parts.length; i++) {
+                String ext = parts[i];
+                if (!ext.equals("")) {
+                    addMimeType(type, ext);
+                    System.err.println(type + " = " + ext);
+                } else {
+                    System.err.println("XXX " + ext);
+                }
+            }
+        }
+        
+    }
+    
+    private void addMimeType(String type, String ext) {
+        
+    }
+    // ------------------------------------------------------ Protected Methods
+
+    public static final String INCLUDE_REQUEST_URI_ATTR =
+        "javax.servlet.include.request_uri";
+    public static final String INCLUDE_SERVLET_PATH_ATTR =
+        "javax.servlet.include.servlet_path";
+    public static final String INCLUDE_PATH_INFO_ATTR =
+        "javax.servlet.include.path_info";
+    public static final String INCLUDE_CONTEXT_PATH_ATTR =
+        "javax.servlet.include.context_path";
+
+    
+    /**
+     * Return the relative path associated with this servlet. 
+     * Multiple sources are used - include attribute, servlet path, etc
+     *
+     * @param request The servlet request we are processing
+     */
+    public static String getRelativePath(HttpServletRequest request) {
+
+        // Are we being processed by a RequestDispatcher.include()?
+        if (request.getAttribute(INCLUDE_REQUEST_URI_ATTR) != null) {
+            String result = (String) request.getAttribute(
+                                            INCLUDE_PATH_INFO_ATTR);
+            if (result == null)
+                result = (String) request.getAttribute(
+                                            INCLUDE_SERVLET_PATH_ATTR);
+            if ((result == null) || (result.equals("")))
+                result = "/";
+            return (result);
+        }
+
+        // No, extract the desired path directly from the request
+        // For 'default' servlet, the path info contains the path
+        // if this is mapped to serve a subset of the files - we
+        // need both
+        
+        String result = request.getPathInfo();
+        if (result == null) {
+            result = request.getServletPath();
+        } else {
+            result = request.getServletPath() + result;
+        }
+        if ((result == null) || (result.equals(""))) {
+            result = "/";
+        }
+        return (result);
+    }
+
+    protected void doGet(HttpServletRequest request,
+                         HttpServletResponse response)
+        throws IOException, ServletException {
+        serveResource(request, response, true);
+
+    }
+
+
+    protected void doHead(HttpServletRequest request,
+                          HttpServletResponse response)
+        throws IOException, ServletException {
+        serveResource(request, response, false);
+
+    }
+
+
+    protected void doPost(HttpServletRequest request,
+                          HttpServletResponse response)
+        throws IOException, ServletException {
+      // TODO: not allowed ?
+      doGet(request, response);
+    }
+
+
+
+
+    /**
+     * Check if the conditions specified in the optional If headers are
+     * satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceAttributes The resource information
+     * @return boolean true if the resource meets all the specified conditions,
+     * and false if any of the conditions is not satisfied, in which case
+     * request processing is stopped
+     */
+    protected boolean checkIfHeaders(HttpServletRequest request,
+                                     HttpServletResponse response,
+                                     File resourceAttributes)
+        throws IOException {
+
+        return checkIfMatch(request, response, resourceAttributes)
+            && checkIfModifiedSince(request, response, resourceAttributes)
+            && checkIfNoneMatch(request, response, resourceAttributes)
+            && checkIfUnmodifiedSince(request, response, resourceAttributes);
+
+    }
+
+
+    /**
+     * Get the ETag associated with a file.
+     *
+     * @param resourceAttributes The resource information
+     */
+    protected String getETag(File resourceAttributes) {
+        return "W/\"" + resourceAttributes.length() + "-"
+                + resourceAttributes.lastModified() + "\"";
+    }
+
+    LRUFileCache fileCache;
+
+    public void setFileCacheSize(int size) {
+        if (fileCache == null) {
+            fileCache = new LRUFileCache();
+        }
+        fileCache.cacheSize = size;
+    }
+    
+    public int getFileCacheSize() {
+        if (fileCache ==null ) return 0;
+        return fileCache.cacheSize;
+    }
+    
+    static class LRUFileCache extends LinkedHashMap {
+        int cacheSize;
+        public LRUFileCache() {
+        }
+        protected boolean removeEldestEntity(Map.Entry eldest) {
+            return size() > cacheSize;
+        }
+    }
+    
+
+    /**
+     * Serve the specified resource, optionally including the data content.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param content Should the content be included?
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    protected void serveResource(HttpServletRequest request,
+                                 HttpServletResponse response,
+                                 boolean content)
+        throws IOException, ServletException {
+
+        // Identify the requested resource path - checks include attributes
+        String path = getRelativePath(request);
+        
+        // TODO: Check the file cache to avoid a bunch of FS accesses.
+        
+        File resFile = new File(basePath, path);
+
+        if (!resFile.exists()) {
+            send404(request, response);
+            return;
+        }
+
+        boolean isDir = resFile.isDirectory();
+        
+        if (isDir) {
+            getServletContext();
+            // Skip directory listings if we have been configured to
+            // suppress them
+            if (!listings) {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND,
+                                   request.getRequestURI());
+                return;
+            }
+            dir2Html.renderDir(request, response, resFile, fileEncoding, content,
+                    path);
+            
+            return;
+        }
+        
+        // If the resource is not a collection, and the resource path
+        // ends with "/" or "\", return NOT FOUND
+        if (path.endsWith("/") || (path.endsWith("\\"))) {
+            // Check if we're included so we can return the appropriate 
+            // missing resource name in the error
+            String requestUri = (String) request.getAttribute(
+                    INCLUDE_REQUEST_URI_ATTR);
+            if (requestUri == null) {
+                requestUri = request.getRequestURI();
+            }
+            response.sendError(HttpServletResponse.SC_NOT_FOUND,
+                    requestUri);
+            return;
+        }
+
+        // Check if the conditions specified in the optional If headers are
+        // satisfied.
+
+        // Checking If headers. The method will generate the
+        boolean included =
+            (request.getAttribute(INCLUDE_CONTEXT_PATH_ATTR) != null);
+        if (!included
+                && !checkIfHeaders(request, response, resFile)) {
+            return;
+        }
+
+
+        // Find content type.
+        String contentType = getServletContext().getMimeType(resFile.getName());
+
+        long contentLength = -1L;
+
+        // ETag header
+        response.setHeader("ETag", getETag(resFile));
+
+        // TODO: remove the sync, optimize - it's from ResourceAttribute
+        String lastModifiedHttp = lastModifiedHttp(resFile);
+
+        // Last-Modified header
+        response.setHeader("Last-Modified", lastModifiedHttp);
+
+        // Get content length
+        contentLength = resFile.length();
+        // Special case for zero length files, which would cause a
+        // (silent) ISE when setting the output buffer size
+        if (contentLength == 0L) {
+            content = false;
+        }
+
+        ServletOutputStream ostream = null;
+        PrintWriter writer = null;
+
+        if (content) {
+
+            // Trying to retrieve the servlet output stream
+
+            try {
+                ostream = response.getOutputStream();
+            } catch (IllegalStateException e) {
+                // If it fails, we try to get a Writer instead if we're
+                // trying to serve a text file
+                if ( (contentType == null)
+                     || (contentType.startsWith("text")) ) {
+                    writer = response.getWriter();
+                } else {
+                    throw e;
+                }
+            }
+        }
+
+        // Parse range specifier
+
+        ArrayList ranges = parseRange(request, response, resFile);
+
+        if ( ( ((ranges == null) || (ranges.isEmpty()))
+                && (request.getHeader("Range") == null) )
+                || (ranges == FULL) ) {
+
+            processFullFile(response, content, resFile, contentType, 
+                            contentLength, ostream, writer);
+
+        } else {
+
+            if ((ranges == null) || (ranges.isEmpty()))
+                return;
+
+            // Partial content response.
+
+            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+            if (ranges.size() == 1) {
+
+                processSingleRange(response, content, resFile, contentType, 
+                                   ostream, writer, ranges);
+
+            } else {
+
+                processMultiRange(response, content, resFile, contentType, 
+                                  ostream, writer, ranges);
+
+            }
+
+        }
+
+    }
+
+
+    private void processMultiRange(HttpServletResponse response, 
+                                   boolean content, 
+                                   File resFile, 
+                                   String contentType, 
+                                   ServletOutputStream ostream, 
+                                   PrintWriter writer, 
+                                   ArrayList ranges) throws IOException {
+        response.setContentType("multipart/byteranges; boundary="
+                                + mimeSeparation);
+
+        if (content) {
+            try {
+                response.setBufferSize(output);
+            } catch (IllegalStateException e) {
+                // Silent catch
+            }
+            if (ostream != null) {
+                copyRanges(resFile, ostream, ranges.iterator(),
+                           contentType);
+            } else {
+                copyRanges(resFile, writer, ranges.iterator(),
+                           contentType);
+            }
+        }
+    }
+
+
+    private void processSingleRange(HttpServletResponse response, boolean content, File resFile, String contentType, ServletOutputStream ostream, PrintWriter writer, ArrayList ranges) throws IOException {
+        Range range = (Range) ranges.get(0);
+        response.addHeader("Content-Range", "bytes "
+                           + range.start
+                           + "-" + range.end + "/"
+                           + range.length);
+        long length = range.end - range.start + 1;
+        if (length < Integer.MAX_VALUE) {
+            response.setContentLength((int) length);
+        } else {
+            // Set the content-length as String to be able to use a long
+            response.setHeader("content-length", "" + length);
+        }
+
+        if (contentType != null) {
+            response.setContentType(contentType);
+        }
+
+        if (content) {
+            try {
+                response.setBufferSize(output);
+            } catch (IllegalStateException e) {
+                // Silent catch
+            }
+            if (ostream != null) {
+                FileCopyUtils.copy(resFile, ostream, range);
+            } else {
+                FileCopyUtils.copy(resFile, writer, range, fileEncoding);
+            }
+        }
+    }
+
+
+    private void processFullFile(HttpServletResponse response, boolean content, File resFile, String contentType, long contentLength, ServletOutputStream ostream, PrintWriter writer) throws IOException {
+        // Set the appropriate output headers
+        if (contentType != null) {
+            response.setContentType(contentType);
+        }
+        if ((contentLength >= 0)) {
+            if (contentLength < Integer.MAX_VALUE) {
+                response.setContentLength((int) contentLength);
+            } else {
+                // Set the content-length as String to be able to use a long
+                response.setHeader("content-length", "" + contentLength);
+            }
+        }
+
+        // Copy the input stream to our output stream (if requested)
+        if (content) {
+            try {
+                response.setBufferSize(output);
+            } catch (IllegalStateException e) {
+                // Silent catch
+            }
+            if (ostream != null) {
+                FileCopyUtils.copy(resFile, ostream);
+            } else {
+                FileCopyUtils.copy(resFile, writer, fileEncoding);
+            }
+        }
+    }
+
+    public static String lastModifiedHttp(File resFile) {
+        String lastModifiedHttp = null;
+        SimpleDateFormat format = (SimpleDateFormat)formatTL.get();
+        if (format == null) {
+            format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", 
+                    Locale.US);
+            formatTL.set(format);
+        }
+        lastModifiedHttp = format.format(new Date(resFile.lastModified()));
+        return lastModifiedHttp;
+    }
+
+
+    protected static void send404(HttpServletRequest request, HttpServletResponse response) throws IOException {
+        // Check if we're included so we can return the appropriate 
+        // missing resource name in the error
+        String requestUri = (String) request.getAttribute(
+                                        INCLUDE_REQUEST_URI_ATTR);
+        if (requestUri == null) {
+            requestUri = request.getRequestURI();
+        } else {
+            // We're included, and the response.sendError() below is going
+            // to be ignored by the resource that is including us.
+            // Therefore, the only way we can let the including resource
+            // know is by including warning message in response
+            response.getWriter().write("Not found");
+            // skip the URI - just to be safe.
+        }
+
+        response.sendError(HttpServletResponse.SC_NOT_FOUND,
+                           requestUri);
+        return;
+    }
+
+
+
+    /**
+     * Parse the range header.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @return Vector of ranges
+     */
+    protected ArrayList parseRange(HttpServletRequest request,
+                                   HttpServletResponse response,
+                                   File resourceAttributes)
+            throws IOException {
+        // if it has an IfRange and the file is newer, retur FULL 
+        ArrayList result = processIfRange(request, resourceAttributes);
+        if ( result != null ) return result;
+
+        long fileLength = resourceAttributes.length();
+        if (fileLength == 0)
+            return null;
+
+        // Retrieving the range header (if any is specified
+        String rangeHeader = request.getHeader("Range");
+
+        if (rangeHeader == null)
+            return null;
+        // bytes is the only range unit supported (and I don't see the point
+        // of adding new ones).
+        if (!rangeHeader.startsWith("bytes")) {
+            return sendRangeNotSatisfiable(response, fileLength);
+        }
+
+        rangeHeader = rangeHeader.substring(6);
+
+        // Vector which will contain all the ranges which are successfully
+        // parsed.
+        result = Range.parseRanges(fileLength, rangeHeader);
+        if (result == null) {
+            sendRangeNotSatisfiable(response, fileLength);
+        }
+        return result;
+    }
+
+
+    private ArrayList sendRangeNotSatisfiable(HttpServletResponse response, 
+                                              long fileLength) 
+            throws IOException {
+        response.addHeader("Content-Range", "bytes */" + fileLength);
+        response.sendError
+            (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+        return null;
+    }
+
+
+
+    private ArrayList processIfRange(HttpServletRequest request, 
+                                     File resourceAttributes) {
+        // Checking If-Range
+        String headerValue = request.getHeader("If-Range");
+
+        if (headerValue != null) {
+
+            long headerValueTime = (-1L);
+            try {
+                headerValueTime = request.getDateHeader("If-Range");
+            } catch (Exception e) {
+                ;
+            }
+
+            String eTag = getETag(resourceAttributes);
+            long lastModified = resourceAttributes.lastModified();
+
+            if (headerValueTime == (-1L)) {
+
+                // If the ETag the client gave does not match the entity
+                // etag, then the entire entity is returned.
+                if (!eTag.equals(headerValue.trim()))
+                    return FULL;
+
+            } else {
+
+                // If the timestamp of the entity the client got is older than
+                // the last modification date of the entity, the entire entity
+                // is returned.
+                if (lastModified > (headerValueTime + 1000))
+                    return FULL;
+
+            }
+
+        }
+        return null;
+    }
+
+
+    // -------------------------------------------------------- protected Methods
+
+    
+    /**
+     * Check if the if-match condition is satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceInfo File object
+     * @return boolean true if the resource meets the specified condition,
+     * and false if the condition is not satisfied, in which case request
+     * processing is stopped
+     */
+    protected boolean checkIfMatch(HttpServletRequest request,
+                                 HttpServletResponse response,
+                                 File resourceAttributes)
+        throws IOException {
+
+        String eTag = getETag(resourceAttributes);
+        String headerValue = request.getHeader("If-Match");
+        if (headerValue != null) {
+            if (headerValue.indexOf('*') == -1) {
+
+                StringTokenizer commaTokenizer = new StringTokenizer
+                    (headerValue, ",");
+                boolean conditionSatisfied = false;
+
+                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
+                    String currentToken = commaTokenizer.nextToken();
+                    if (currentToken.trim().equals(eTag))
+                        conditionSatisfied = true;
+                }
+
+                // If none of the given ETags match, 412 Precodition failed is
+                // sent back
+                if (!conditionSatisfied) {
+                    response.sendError
+                        (HttpServletResponse.SC_PRECONDITION_FAILED);
+                    return false;
+                }
+
+            }
+        }
+        return true;
+
+    }
+
+
+    /**
+     * Check if the if-modified-since condition is satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceInfo File object
+     * @return boolean true if the resource meets the specified condition,
+     * and false if the condition is not satisfied, in which case request
+     * processing is stopped
+     */
+    protected boolean checkIfModifiedSince(HttpServletRequest request,
+                                         HttpServletResponse response,
+                                         File resourceAttributes)
+        throws IOException {
+        try {
+            long headerValue = request.getDateHeader("If-Modified-Since");
+            long lastModified = resourceAttributes.lastModified();
+            if (headerValue != -1) {
+
+                // If an If-None-Match header has been specified, if modified since
+                // is ignored.
+                if ((request.getHeader("If-None-Match") == null)
+                    && (lastModified <= headerValue + 1000)) {
+                    // The entity has not been modified since the date
+                    // specified by the client. This is not an error case.
+                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                    return false;
+                }
+            }
+        } catch(IllegalArgumentException illegalArgument) {
+            return true;
+        }
+        return true;
+
+    }
+
+
+    /**
+     * Check if the if-none-match condition is satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceInfo File object
+     * @return boolean true if the resource meets the specified condition,
+     * and false if the condition is not satisfied, in which case request
+     * processing is stopped
+     */
+    protected boolean checkIfNoneMatch(HttpServletRequest request,
+                                     HttpServletResponse response,
+                                     File resourceAttributes)
+        throws IOException {
+
+        String eTag = getETag(resourceAttributes);
+        String headerValue = request.getHeader("If-None-Match");
+        if (headerValue != null) {
+
+            boolean conditionSatisfied = false;
+
+            if (!headerValue.equals("*")) {
+
+                StringTokenizer commaTokenizer =
+                    new StringTokenizer(headerValue, ",");
+
+                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
+                    String currentToken = commaTokenizer.nextToken();
+                    if (currentToken.trim().equals(eTag))
+                        conditionSatisfied = true;
+                }
+
+            } else {
+                conditionSatisfied = true;
+            }
+
+            if (conditionSatisfied) {
+
+                // For GET and HEAD, we should respond with
+                // 304 Not Modified.
+                // For every other method, 412 Precondition Failed is sent
+                // back.
+                if ( ("GET".equals(request.getMethod()))
+                     || ("HEAD".equals(request.getMethod())) ) {
+                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                    return false;
+                } else {
+                    response.sendError
+                        (HttpServletResponse.SC_PRECONDITION_FAILED);
+                    return false;
+                }
+            }
+        }
+        return true;
+
+    }
+
+
+    /**
+     * Check if the if-unmodified-since condition is satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceInfo File object
+     * @return boolean true if the resource meets the specified condition,
+     * and false if the condition is not satisfied, in which case request
+     * processing is stopped
+     */
+    protected boolean checkIfUnmodifiedSince(HttpServletRequest request,
+                                           HttpServletResponse response,
+                                           File resourceAttributes)
+        throws IOException {
+        try {
+            long lastModified = resourceAttributes.lastModified();
+            long headerValue = request.getDateHeader("If-Unmodified-Since");
+            if (headerValue != -1) {
+                if ( lastModified > (headerValue + 1000)) {
+                    // The entity has not been modified since the date
+                    // specified by the client. This is not an error case.
+                    response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+                    return false;
+                }
+            }
+        } catch(IllegalArgumentException illegalArgument) {
+            return true;
+        }
+        return true;
+
+    }
+
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param resourceInfo The ResourceInfo object
+     * @param ostream The output stream to write to
+     * @param ranges Enumeration of the ranges the client wanted to retrieve
+     * @param contentType Content type of the resource
+     * @exception IOException if an input/output error occurs
+     */
+    protected void copyRanges(File cacheEntry, ServletOutputStream ostream,
+                              Iterator ranges, String contentType)
+        throws IOException {
+
+        IOException exception = null;
+
+        while ( (exception == null) && (ranges.hasNext()) ) {
+
+            InputStream resourceInputStream = new FileInputStream(cacheEntry);
+            InputStream istream =
+                new BufferedInputStream(resourceInputStream, input);
+
+            Range currentRange = (Range) ranges.next();
+
+            // Writing MIME header.
+            ostream.println();
+            ostream.println("--" + mimeSeparation);
+            if (contentType != null)
+                ostream.println("Content-Type: " + contentType);
+            ostream.println("Content-Range: bytes " + currentRange.start
+                           + "-" + currentRange.end + "/"
+                           + currentRange.length);
+            ostream.println();
+
+            // Printing content
+            exception = CopyUtils.copyRange(istream, ostream, currentRange.start,
+                                            currentRange.end);
+
+            try {
+                istream.close();
+            } catch (Throwable t) {
+                ;
+            }
+
+        }
+
+        ostream.println();
+        ostream.print("--" + mimeSeparation + "--");
+
+        // Rethrow any exception that has occurred
+        if (exception != null)
+            throw exception;
+
+    }
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param resourceInfo The ResourceInfo object
+     * @param writer The writer to write to
+     * @param ranges Enumeration of the ranges the client wanted to retrieve
+     * @param contentType Content type of the resource
+     * @exception IOException if an input/output error occurs
+     */
+    protected void copyRanges(File cacheEntry, PrintWriter writer,
+                              Iterator ranges, String contentType)
+        throws IOException {
+
+        IOException exception = null;
+
+        // quite inefficient - why not sort and open once
+        while ( (exception == null) && (ranges.hasNext()) ) {
+
+            InputStream resourceInputStream = new FileInputStream(cacheEntry);
+            
+            Reader reader;
+            if (fileEncoding == null) {
+                reader = new InputStreamReader(resourceInputStream);
+            } else {
+                reader = new InputStreamReader(resourceInputStream,
+                                               fileEncoding);
+            }
+
+            Range currentRange = (Range) ranges.next();
+
+            // Writing MIME header.
+            writer.println();
+            writer.println("--" + mimeSeparation);
+            if (contentType != null)
+                writer.println("Content-Type: " + contentType);
+            writer.println("Content-Range: bytes " + currentRange.start
+                           + "-" + currentRange.end + "/"
+                           + currentRange.length);
+            writer.println();
+
+            // Printing content
+            exception = CopyUtils.copyRange(reader, writer, currentRange.start,
+                                            currentRange.end);
+
+            try {
+                reader.close();
+            } catch (Throwable t) {
+                ;
+            }
+
+        }
+        writer.println();
+        writer.print("--" + mimeSeparation + "--");
+
+        // Rethrow any exception that has occurred
+        if (exception != null)
+            throw exception;
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/Dir2Html.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/Dir2Html.java
new file mode 100644 (file)
index 0000000..0c8eeb9
--- /dev/null
@@ -0,0 +1,495 @@
+/*
+ * 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.tomcat.servlets.file;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Handles directory listing
+ */
+public class Dir2Html  extends HttpServlet {
+
+    /**
+     * Array containing the safe characters set.
+     */
+    protected static URLEncoder urlEncoder;
+
+    /**
+     * Allow a readme file to be included.
+     */
+    protected String readmeFile = null;
+
+    // TODO: find a better default
+    /**
+     * The input buffer size to use when serving resources.
+     */
+    protected int input = 2048;
+
+    /**
+     * The output buffer size to use when serving resources.
+     */
+    protected int output = 2048;
+
+    /**
+     * File encoding to be used when reading static files. If none is specified
+     * the platform default is used.
+     */
+    protected String fileEncoding = null;
+
+    ThreadLocal formatTL = new ThreadLocal();
+        
+    /**
+     * Full range marker.
+     */
+    protected static ArrayList FULL = new ArrayList();
+
+    // Context base dir
+    protected File basePath;
+    protected String basePathName;
+
+    public static final String TOMCAT_CSS =
+        "H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} " +
+        "H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} " +
+        "H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} " +
+        "BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} " +
+        "B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} " +
+        "P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}" +
+        "A {color : black;}" +
+        "A.name {color : black;}" +
+        "HR {color : #525D76;}";
+
+    // ----------------------------------------------------- Static Initializer
+
+
+    /**
+     * GMT timezone - all HTTP dates are on GMT
+     */
+    static {
+        urlEncoder = new URLEncoder();
+        urlEncoder.addSafeCharacter('-');
+        urlEncoder.addSafeCharacter('_');
+        urlEncoder.addSafeCharacter('.');
+        urlEncoder.addSafeCharacter('*');
+        urlEncoder.addSafeCharacter('/');
+    }
+
+
+    /**
+     * MIME multipart separation string
+     */
+    protected static final String mimeSeparation = "TOMCAT_MIME_BOUNDARY";
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Finalize this servlet.
+     */
+    public void destroy() {
+    }
+
+
+    /**
+     * Initialize this servlet.
+     */
+    public void init() throws ServletException {
+    }
+
+    protected void doGet(HttpServletRequest request,
+                         HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // Serve the requested resource, including the data content
+        //serveResource(request, response, true);
+
+    }
+
+
+    protected void doHead(HttpServletRequest request,
+                          HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // Serve the requested resource, without the data content
+        //serveResource(request, response, false);
+
+    }
+
+
+    protected void doPost(HttpServletRequest request,
+                          HttpServletResponse response)
+        throws IOException, ServletException {
+        doGet(request, response);
+    }
+
+
+
+    
+
+    /**
+     * Serve the specified resource, optionally including the data content.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param content Should the content be included?
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    protected void serveResource(HttpServletRequest request,
+                                 HttpServletResponse response,
+                                 boolean content, 
+                                 String relativePath)
+        throws IOException, ServletException {
+
+        // Identify the requested resource path - checks include attributes
+        String path = DefaultServlet.getRelativePath(request);
+        
+        // TODO: Check the file cache to avoid a bunch of FS accesses.
+        
+        File resFile = new File(basePath, path);
+
+        if (!resFile.exists()) {
+            DefaultServlet.send404(request, response);
+            return;
+        }
+
+        boolean isDir = resFile.isDirectory();
+        
+        if (isDir) {
+            renderDir(request, response, resFile,"UTF=8", content,
+                    relativePath);
+            return;
+        }
+        
+    }
+
+
+    
+    // ----------------- Directory rendering --------------------
+    
+    // Just basic HTML rendering - extend or replace for xslt
+
+    public void renderDir(HttpServletRequest request, 
+            HttpServletResponse response, 
+            File resFile,
+            String fileEncoding,
+            boolean content,
+            String relativePath) throws IOException {
+        
+        String contentType = "text/html;charset=" + fileEncoding;
+
+        ServletOutputStream ostream = null;
+        PrintWriter writer = null;
+        
+        if (content) {
+            // Trying to retrieve the servlet output stream
+            try {
+                ostream = response.getOutputStream();
+            } catch (IllegalStateException e) {
+                // If it fails, we try to get a Writer instead if we're
+                // trying to serve a text file
+                if ( (contentType == null)
+                     || (contentType.startsWith("text")) ) {
+                    writer = response.getWriter();
+                } else {
+                    throw e;
+                }
+            }
+
+        }
+
+        // Set the appropriate output headers
+        response.setContentType(contentType);
+        
+        InputStream renderResult = null;
+
+        if (content) {
+            // Serve the directory browser
+            renderResult =
+                render(request.getContextPath(), resFile, relativePath);
+        }
+
+
+        // Copy the input stream to our output stream (if requested)
+        if (content) {
+            try {
+                response.setBufferSize(output);
+            } catch (IllegalStateException e) {
+                // Silent catch
+            }
+            if (ostream != null) {
+                CopyUtils.copy(renderResult, ostream);
+            } else {
+                CopyUtils.copy(renderResult, writer, fileEncoding);
+            }
+        }
+        
+            
+    }
+    
+
+    /**
+     * URL rewriter.
+     *
+     * @param path Path which has to be rewiten
+     */
+    protected String rewriteUrl(String path) {
+        return urlEncoder.encode( path );
+    }
+
+
+
+    /**
+     *  Decide which way to render. HTML or XML.
+     */
+    protected InputStream render(String contextPath, File cacheEntry,
+                                 String relativePath) {
+        return renderHtml(contextPath, cacheEntry, relativePath);
+    }
+
+
+    /**
+     * Return an InputStream to an HTML representation of the contents
+     * of this directory.
+     *
+     * @param contextPath Context path to which our internal paths are
+     *  relative
+     */
+    protected InputStream renderHtml(String contextPath, File cacheEntry,
+                                     String relativePath) {
+
+        String dirName = cacheEntry.getName();
+
+        // Number of characters to trim from the beginnings of filenames
+//        int trim = relativePath.length();
+//        if (!relativePath.endsWith("/"))
+//            trim += 1;
+//        if (relativePAth.equals("/"))
+//            trim = 1;
+
+        // Prepare a writer to a buffered area
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        OutputStreamWriter osWriter = null;
+        try {
+            osWriter = new OutputStreamWriter(stream, "UTF8");
+        } catch (Exception e) {
+            // Should never happen
+            osWriter = new OutputStreamWriter(stream);
+        }
+        PrintWriter writer = new PrintWriter(osWriter);
+
+        StringBuffer sb = new StringBuffer();
+        
+        // rewriteUrl(contextPath) is expensive. cache result for later reuse
+        String rewrittenContextPath =  rewriteUrl(contextPath);
+
+        // TODO: use a template for localization / customization
+        
+        // Render the page header
+        sb.append("<html>\r\n");
+        sb.append("<head>\r\n");
+        sb.append("<title>");
+        sb.append(dirName);
+        sb.append("</title>\r\n");
+        sb.append("<STYLE><!--");
+        sb.append(TOMCAT_CSS);
+        sb.append("--></STYLE> ");
+        sb.append("</head>\r\n");
+        sb.append("<body>");
+        sb.append("<h1>");
+        sb.append(dirName);
+        sb.append("</h1>");
+
+        sb.append("<HR size=\"1\" noshade=\"noshade\">");
+
+
+        sb.append("<table width=\"100%\" cellspacing=\"0\"" +
+                     " cellpadding=\"5\" align=\"center\">\r\n");
+
+        // Render the column headings
+        sb.append("<tr>\r\n");
+        sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
+        sb.append("Name");
+        sb.append("</strong></font></td>\r\n");
+        sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
+        sb.append("Size");
+        sb.append("</strong></font></td>\r\n");
+        sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
+        sb.append("Last Modified");
+        sb.append("</strong></font></td>\r\n");
+        sb.append("</tr>");
+        boolean shade = false;
+
+        // Render the link to our parent (if required)
+        String parentDirectory = relativePath;
+        if (parentDirectory.endsWith("/")) {
+            parentDirectory =
+                parentDirectory.substring(0, parentDirectory.length() - 1);
+        }
+        int slash = parentDirectory.lastIndexOf('/');
+        if (slash >= 0) {
+            String parent = relativePath.substring(0, slash);
+            sb.append("<tr>\r\n<td align=\"left\">&nbsp;&nbsp;\r\n<a href=\"..");
+//            sb.append(rewrittenContextPath);
+//            if (parent.equals(""))
+//               parent = "/";
+//            sb.append(rewriteUrl(parent));
+//            if (!parent.endsWith("/"))
+//                sb.append("/");
+            sb.append("\">");
+            //sb.append("<b>");
+            sb.append("..");
+            //sb.append("</b>");
+            sb.append("</a></td></tr>");
+            shade = true;
+        }
+
+
+        // Render the directory entries within this directory
+        String[] files = cacheEntry.list();
+        for (int i=0; i<files.length; i++) {
+
+            String resourceName = files[i];
+            String trimmed = resourceName;//.substring(trim);
+            if (trimmed.equalsIgnoreCase("WEB-INF") ||
+                    trimmed.equalsIgnoreCase("META-INF"))
+                continue;
+
+            File childCacheEntry = new File(cacheEntry, resourceName);
+
+            sb.append("<tr");
+            if (shade)
+                sb.append(" bgcolor=\"#eeeeee\"");
+            sb.append(">\r\n");
+            shade = !shade;
+
+            sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
+            sb.append("<a href=\"");
+            if (! relativePath.endsWith("/")) {
+                sb.append(dirName + "/");
+            }
+            //sb.append(rewrittenContextPath);
+//            if (! rewrittenContextPath.endsWith("/")) {
+//                sb.append("/");
+//            }
+//            if ( ! relativePath.equals("")) {
+//                String link = rewriteUrl(relativePath);
+//                sb.append(link).append("/");
+//            }
+            sb.append(resourceName);
+            boolean isDir = childCacheEntry.isDirectory(); 
+            if (isDir)
+                sb.append("/");
+            sb.append("\"><tt>");
+            sb.append(trimmed);
+            if (isDir)
+                sb.append("/");
+            sb.append("</tt></a></td>\r\n");
+
+            sb.append("<td align=\"right\"><tt>");
+            if (isDir)
+                sb.append("&nbsp;");
+            else
+                displaySize(sb,childCacheEntry.length());
+            sb.append("</tt></td>\r\n");
+
+            sb.append("<td align=\"right\"><tt>");
+            sb.append(DefaultServlet.lastModifiedHttp(childCacheEntry));
+            sb.append("</tt></td>\r\n");
+
+            sb.append("</tr>\r\n");
+        }
+
+
+        // Render the page footer
+        sb.append("</table>\r\n");
+
+        sb.append("<HR size=\"1\" noshade=\"noshade\">");
+
+        String readme = getReadme(cacheEntry);
+        if (readme!=null) {
+            sb.append(readme);
+            sb.append("<HR size=\"1\" noshade=\"noshade\">");
+        }
+
+        sb.append("</body>\r\n");
+        sb.append("</html>\r\n");
+
+        // Return an input stream to the underlying bytes
+        writer.write(sb.toString());
+        writer.flush();
+        return (new ByteArrayInputStream(stream.toByteArray()));
+
+    }
+
+    /**
+     * Display the size of a file.
+     */
+    protected void displaySize(StringBuffer buf, long filesize) {
+
+        long leftside = filesize / 1024;
+        long rightside = (filesize % 1024) / 103;  // makes 1 digit
+        if (leftside == 0 && rightside == 0 && filesize != 0)
+            rightside = 1;
+        buf.append(leftside).append(".").append(rightside);
+        buf.append(" KB");
+    }
+
+    /**
+     * Get the readme file as a string.
+     */
+    protected String getReadme(File directory) {
+        if (readmeFile!=null) {
+            try {
+                File rf = new File(directory, readmeFile);
+
+                if (rf.exists()) {
+                    StringWriter buffer = new StringWriter();
+                    InputStream is = new FileInputStream(rf);
+                    CopyUtils.copyRange(new InputStreamReader(is),
+                              new PrintWriter(buffer));
+
+                    return buffer.toString();
+                 }
+             } catch(Throwable e) {
+                 ; /* Should only be IOException or NamingException
+                    * can be ignored
+                    */
+             }
+        }
+        return null;
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/FileCopyUtils.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/FileCopyUtils.java
new file mode 100644 (file)
index 0000000..d3be53a
--- /dev/null
@@ -0,0 +1,112 @@
+package org.apache.tomcat.servlets.file;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+
+import javax.servlet.ServletOutputStream;
+
+import org.apache.tomcat.servlets.util.Range;
+
+
+/** Like CopyUtils, but with File as source.
+ *  
+ *  This has the potential to be optimized with JNI
+ */
+public class FileCopyUtils {
+    protected static int input = 2048;
+
+    public static void copy(File f, OutputStream ostream)
+            throws IOException {
+        CopyUtils.copy(new FileInputStream(f), ostream);
+    }
+
+
+    public static void copy(File cacheEntry, 
+                            PrintWriter writer,
+                            String fileEncoding)
+            throws IOException {
+        CopyUtils.copy(new FileInputStream(cacheEntry), writer, fileEncoding);
+    }
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param resourceInfo The ResourceInfo object
+     * @param ostream The output stream to write to
+     * @param range Range the client wanted to retrieve
+     * @exception IOException if an input/output error occurs
+     */
+    public static void copy(File cacheEntry, ServletOutputStream ostream,
+                      Range range)
+        throws IOException {
+
+        IOException exception = null;
+
+        InputStream resourceInputStream = new FileInputStream(cacheEntry);
+        InputStream istream =
+            new BufferedInputStream(resourceInputStream, input);
+        exception = CopyUtils.copyRange(istream, ostream, range.start, range.end);
+
+        // Clean up the input stream
+        try {
+            istream.close();
+        } catch (Throwable t) {
+            ;
+        }
+
+        // Rethrow any exception that has occurred
+        if (exception != null)
+            throw exception;
+
+    }
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param resourceInfo The ResourceInfo object
+     * @param writer The writer to write to
+     * @param range Range the client wanted to retrieve
+     * @exception IOException if an input/output error occurs
+     */
+    public static void copy(File cacheEntry, PrintWriter writer,
+                            Range range, String fileEncoding)
+        throws IOException {
+
+        IOException exception = null;
+
+        InputStream resourceInputStream = new FileInputStream(cacheEntry);
+
+        Reader reader;
+        if (fileEncoding == null) {
+            reader = new InputStreamReader(resourceInputStream);
+        } else {
+            reader = new InputStreamReader(resourceInputStream,
+                                           fileEncoding);
+        }
+
+        exception = CopyUtils.copyRange(reader, writer, range.start, range.end);
+
+        // Clean up the input stream
+        try {
+            reader.close();
+        } catch (Throwable t) {
+            ;
+        }
+
+        // Rethrow any exception that has occurred
+        if (exception != null)
+            throw exception;
+
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/MD5Encoder.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/MD5Encoder.java
new file mode 100644 (file)
index 0000000..3f544fc
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.servlets.file;
+
+
+/**
+ * Encode an MD5 digest into a String.
+ * <p>
+ * The 128 bit MD5 hash is converted into a 32 character long String.
+ * Each character of the String is the hexadecimal representation of 4 bits
+ * of the digest.
+ *
+ * @author Remy Maucherat
+ */
+
+public final class MD5Encoder {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    private static final char[] hexadecimal =
+    {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+     'a', 'b', 'c', 'd', 'e', 'f'};
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Encodes the 128 bit (16 bytes) MD5 into a 32 character String.
+     *
+     * @param binaryData Array containing the digest
+     * @return Encoded MD5, or null if encoding failed
+     */
+    public String encode( byte[] binaryData ) {
+
+        if (binaryData.length != 16)
+            return null;
+
+        char[] buffer = new char[32];
+
+        for (int i=0; i<16; i++) {
+            int low = (int) (binaryData[i] & 0x0f);
+            int high = (int) ((binaryData[i] & 0xf0) >> 4);
+            buffer[i*2] = hexadecimal[high];
+            buffer[i*2 + 1] = hexadecimal[low];
+        }
+
+        return new String(buffer);
+
+    }
+
+
+}
+
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/URLEncoder.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/URLEncoder.java
new file mode 100644 (file)
index 0000000..64fc962
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.servlets.file;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.BitSet;
+
+/**
+ *
+ * This class is very similar to the java.net.URLEncoder class.
+ *
+ * Unfortunately, with java.net.URLEncoder there is no way to specify to the 
+ * java.net.URLEncoder which characters should NOT be encoded.
+ *
+ * This code was moved from DefaultServlet.java
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ */
+public class URLEncoder {
+    protected static final char[] hexadecimal =
+    {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+     'A', 'B', 'C', 'D', 'E', 'F'};
+
+    //Array containing the safe characters set.
+    protected BitSet safeCharacters = new BitSet(256);
+
+    public URLEncoder() {
+        for (char i = 'a'; i <= 'z'; i++) {
+            addSafeCharacter(i);
+        }
+        for (char i = 'A'; i <= 'Z'; i++) {
+            addSafeCharacter(i);
+        }
+        for (char i = '0'; i <= '9'; i++) {
+            addSafeCharacter(i);
+        }
+    }
+
+    public void addSafeCharacter( char c ) {
+       safeCharacters.set( c );
+    }
+
+    public String encode( String path ) {
+        int maxBytesPerChar = 10;
+        int caseDiff = ('a' - 'A');
+        StringBuffer rewrittenPath = new StringBuffer(path.length());
+        ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
+        OutputStreamWriter writer = null;
+        try {
+            writer = new OutputStreamWriter(buf, "UTF8");
+        } catch (Exception e) {
+            e.printStackTrace();
+            writer = new OutputStreamWriter(buf);
+        }
+
+        for (int i = 0; i < path.length(); i++) {
+            int c = (int) path.charAt(i);
+            if (safeCharacters.get(c)) {
+                rewrittenPath.append((char)c);
+            } else {
+                // convert to external encoding before hex conversion
+                try {
+                    writer.write((char)c);
+                    writer.flush();
+                } catch(IOException e) {
+                    buf.reset();
+                    continue;
+                }
+                byte[] ba = buf.toByteArray();
+                for (int j = 0; j < ba.length; j++) {
+                    // Converting each byte in the buffer
+                    byte toEncode = ba[j];
+                    rewrittenPath.append('%');
+                    int low = (int) (toEncode & 0x0f);
+                    int high = (int) ((toEncode & 0xf0) >> 4);
+                    rewrittenPath.append(hexadecimal[high]);
+                    rewrittenPath.append(hexadecimal[low]);
+                }
+                buf.reset();
+            }
+        }
+        return rewrittenPath.toString();
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/WebdavServlet.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/WebdavServlet.java
new file mode 100644 (file)
index 0000000..93563e6
--- /dev/null
@@ -0,0 +1,1840 @@
+/*
+ * 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.tomcat.servlets.file;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Stack;
+import java.util.TimeZone;
+import java.util.Vector;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.tomcat.servlets.util.Range;
+import org.apache.tomcat.servlets.util.RequestUtil;
+import org.apache.tomcat.servlets.util.UrlUtils;
+import org.apache.tomcat.util.http.FastHttpDateFormat;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+
+
+/**
+ * Servlet which adds support for WebDAV level 1. All the basic HTTP requests
+ * are handled by the DefaultServlet. 
+ * 
+ * Based on Catalina WebdavServlet, with following changes:
+ *  - removed the JNDI abstraction, use File instead
+ *  - removed WebDAV 2 support ( moved to Webdav2Servlet ) - i.e. no locks
+ *    supported
+ *  - simplified and cleaned up the code
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class WebdavServlet extends DefaultServlet {
+
+
+    // -------------------------------------------------------------- Constants
+
+    protected static final String METHOD_PROPFIND = "PROPFIND";
+    protected static final String METHOD_PROPPATCH = "PROPPATCH";
+    protected static final String METHOD_MKCOL = "MKCOL";
+    protected static final String METHOD_COPY = "COPY";
+    protected static final String METHOD_DELETE = "DELETE";
+    protected static final String METHOD_MOVE = "MOVE";
+    protected static final String METHOD_LOCK = "LOCK";
+    protected static final String METHOD_UNLOCK = "UNLOCK";
+
+
+    /**
+     * Default depth is infite.
+     */
+    protected static final int INFINITY = 3; // To limit tree browsing a bit
+
+
+    /**
+     * PROPFIND - Specify a property mask.
+     */
+    protected static final int FIND_BY_PROPERTY = 0;
+
+
+    /**
+     * PROPFIND - Display all properties.
+     */
+    protected static final int FIND_ALL_PROP = 1;
+
+
+    /**
+     * PROPFIND - Return property names.
+     */
+    protected static final int FIND_PROPERTY_NAMES = 2;
+
+    /**
+     * Default namespace.
+     */
+    protected static final String DEFAULT_NAMESPACE = "DAV:";
+
+
+    /**
+     * Simple date format for the creation date ISO representation (partial).
+     * TODO: ThreadLocal
+     */
+    protected static final SimpleDateFormat creationDateFormat =
+        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
+
+    static {
+        creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+    }
+
+    // TODO: replace it with writeRole - who is enabled to write
+    protected boolean readOnly = false;
+
+    /**
+     * Repository of the lock-null resources.
+     * <p>
+     * Key : path of the collection containing the lock-null resource<br>
+     * Value : Vector of lock-null resource which are members of the
+     * collection. Each element of the Vector is the path associated with
+     * the lock-null resource.
+     */
+    protected Hashtable lockNullResources = new Hashtable();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Initialize this servlet.
+     */
+    public void init()
+        throws ServletException {
+
+        super.init();
+
+        try {
+            String value = getServletConfig().getInitParameter("readonly");
+            if (value != null)
+                readOnly = (new Boolean(value)).booleanValue();
+        } catch (Throwable t) {
+            ;
+        }
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+    /**
+     * Handles the special WebDAV methods.
+     */
+    protected void service(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        String method = req.getMethod();
+
+        if (method.equals(METHOD_PROPFIND)) {
+            doPropfind(req, resp);
+        } else if (method.equals(METHOD_PROPPATCH)) {
+            doProppatch(req, resp);
+        } else if (method.equals(METHOD_MKCOL)) {
+            doMkcol(req, resp);
+        } else if (method.equals(METHOD_COPY)) {
+            doCopy(req, resp);
+        } else if (method.equals(METHOD_MOVE)) {
+            doMove(req, resp);
+        } else if (method.equals(METHOD_LOCK)) {
+            doLock(req, resp);
+        } else if (method.equals(METHOD_UNLOCK)) {
+            doUnlock(req, resp);
+        } else if (method.equals(METHOD_DELETE)) {
+            doDelete(req, resp);
+        } else {
+            // DefaultServlet processing - GET, HEAD, POST
+            super.service(req, resp);
+        }
+
+    }
+
+    /**
+     * Check if the conditions specified in the optional If headers are
+     * satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceAttributes The resource information
+     * @return boolean true if the resource meets all the specified conditions,
+     * and false if any of the conditions is not satisfied, in which case
+     * request processing is stopped
+     */
+    protected boolean checkIfHeaders(HttpServletRequest request,
+                                     HttpServletResponse response,
+                                     File resourceAttributes)
+        throws IOException {
+
+        if (!super.checkIfHeaders(request, response, resourceAttributes))
+            return false;
+
+        // TODO : Checking the WebDAV If header
+        return true;
+
+    }
+
+
+    /**
+     * OPTIONS Method.
+     *
+     * @param req The request
+     * @param resp The response
+     * @throws ServletException If an error occurs
+     * @throws IOException If an IO error occurs
+     */
+    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
+            throws ServletException, IOException {
+        resp.addHeader("DAV", "1"); // And not: ,2");
+
+        StringBuffer methodsAllowed = determineMethodsAllowed(basePath,
+                                                              req);
+        resp.addHeader("Allow", methodsAllowed.toString());
+        resp.addHeader("MS-Author-Via", "DAV");
+    }
+
+    // ------------------ PROPFIND --------------------
+
+    /**
+     * PROPFIND Method.
+     */
+    protected void doPropfind(HttpServletRequest req, HttpServletResponse resp)
+            throws ServletException, IOException {
+        String path = getRelativePath(req);
+        if (path.endsWith("/"))
+            path = path.substring(0, path.length() - 1);
+
+        if ((path.toUpperCase().startsWith("/WEB-INF")) ||
+            (path.toUpperCase().startsWith("/META-INF"))) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        // Propfind depth
+        int depth = getDepth(req);
+
+        File object = new File(basePath, path);
+
+        if (!object.exists()) {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
+            return;
+        }
+
+        // Properties which are to be displayed.
+        Vector properties = new Vector();
+        int type = getPropfindType(req, properties);
+
+        resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
+        resp.setContentType("text/xml; charset=UTF-8");
+
+        // Create multistatus object
+        XMLWriter generatedXML = new XMLWriter(resp.getWriter());
+        generatedXML.writeXMLHeader();
+
+        generatedXML.writeElement(null, "multistatus"
+                                  + generateNamespaceDeclarations(),
+                                  XMLWriter.OPENING);
+
+        if (depth == 0) {
+            parseProperties(req, generatedXML, path, type,
+                            properties);
+        } else {
+            propfindRecurse(req, path, depth, properties, 
+                    type, generatedXML);
+        }
+
+        generatedXML.writeElement(null, "multistatus",
+                                  XMLWriter.CLOSING);
+        generatedXML.sendData();
+    }
+
+
+    private void propfindRecurse(HttpServletRequest req, String path, int depth, Vector properties, int type, XMLWriter generatedXML) throws IOException {
+        File object;
+        // The stack always contains the object of the current level
+        Stack stack = new Stack();
+        stack.push(path);
+
+        // Stack of the objects one level below
+        Stack stackBelow = new Stack();
+
+        while ((!stack.isEmpty()) && (depth >= 0)) {
+            String currentPath = (String) stack.pop();
+            parseProperties(req, generatedXML, currentPath,
+                            type, properties);
+
+            object = new File(basePath,currentPath);
+            if (!object.exists()) {
+                continue;
+            }
+
+            if ((object.isDirectory()) && (depth > 0)) {
+
+                File[] files = object.listFiles();
+                for (int i=0; i < files.length; i++) {
+                    String newPath = currentPath;
+                    if (!(newPath.endsWith("/")))
+                        newPath += "/";
+                    newPath += files[i].getName();
+                    stackBelow.push(newPath);
+                }
+            }
+
+            if (stack.isEmpty()) {
+                depth--;
+                stack = stackBelow;
+                stackBelow = new Stack();
+            }
+
+            generatedXML.sendData();
+        }
+    }
+
+
+    private int getPropfindType(HttpServletRequest req, Vector properties) throws ServletException {
+        // Propfind type
+        int type = FIND_ALL_PROP;
+
+        DocumentBuilder documentBuilder = getDocumentBuilder();
+        try {
+            Document document = documentBuilder.parse
+                (new InputSource(req.getInputStream()));
+            // Get the root element of the document
+            Element rootElement = document.getDocumentElement();
+            NodeList childList = rootElement.getChildNodes();
+
+            for (int i=0; i < childList.getLength(); i++) {
+                Node currentNode = childList.item(i);
+                switch (currentNode.getNodeType()) {
+                case Node.TEXT_NODE:
+                    break;
+                case Node.ELEMENT_NODE:
+                    if (currentNode.getNodeName().endsWith("prop")) {
+                        type = FIND_BY_PROPERTY;
+                        Node propNode = currentNode;
+                        NodeList childListPN = propNode.getChildNodes();
+                        for (int iPN=0; iPN < childListPN.getLength(); iPN++) {
+                            Node currentNodePN = childListPN.item(iPN);
+                            switch (currentNodePN.getNodeType()) {
+                            case Node.TEXT_NODE:
+                                break;
+                            case Node.ELEMENT_NODE:
+                                String nodeName = currentNodePN.getNodeName();
+                                String propertyName = null;
+                                if (nodeName.indexOf(':') != -1) {
+                                    propertyName = nodeName.substring
+                                        (nodeName.indexOf(':') + 1);
+                                } else {
+                                    propertyName = nodeName;
+                                }
+                                // href is a live property which is handled differently
+                                properties.addElement(propertyName);
+                                break;
+                            }
+                        }
+                    }
+                    if (currentNode.getNodeName().endsWith("propname")) {
+                        type = FIND_PROPERTY_NAMES;
+                    }
+                    if (currentNode.getNodeName().endsWith("allprop")) {
+                        type = FIND_ALL_PROP;
+                    }
+                    break;
+                }
+            }
+        } catch(Exception e) {
+            // Most likely there was no content : we use the defaults.
+            // TODO : Enhance that !
+        }
+        return type;
+    }
+
+
+    private int getDepth(HttpServletRequest req) {
+        int depth = INFINITY;
+        String depthStr = req.getHeader("Depth");
+
+        if (depthStr == null) {
+            depth = INFINITY;
+        } else {
+            if (depthStr.equals("0")) {
+                depth = 0;
+            } else if (depthStr.equals("1")) {
+                depth = 1;
+            } else if (depthStr.equals("infinity")) {
+                depth = INFINITY;
+            }
+        }
+        return depth;
+    }
+
+
+    /**
+     * URL rewriter.
+     *
+     * @param path Path which has to be rewiten
+     */
+    protected String rewriteUrl(String path) {
+        return urlEncoder.encode( path );
+    }
+
+
+    /**
+     * Propfind helper method.
+     *
+     * @param req The servlet request
+     * @param resources Resources object associated with this context
+     * @param out XML response to the Propfind request
+     * @param path Path of the current resource
+     * @param type Propfind type
+     * @param propertiesVector If the propfind type is find properties by
+     * name, then this Vector contains those properties
+     */
+    protected void parseProperties(HttpServletRequest req,
+                                 XMLWriter out,
+                                 String path, int type,
+                                 Vector propertiesVector) {
+
+        // Exclude any resource in the /WEB-INF and /META-INF subdirectories
+        // (the "toUpperCase()" avoids problems on Windows systems)
+        if (path.toUpperCase().startsWith("/WEB-INF") ||
+            path.toUpperCase().startsWith("/META-INF"))
+            return;
+
+        File cacheEntry = new File(basePath, path);
+
+        out.writeElement(null, "response", XMLWriter.OPENING);
+        String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " "
+                                   + WebdavStatus.getStatusText
+                                   (WebdavStatus.SC_OK));
+
+        // Generating href element
+        out.writeElement(null, "href", XMLWriter.OPENING);
+
+        String href = req.getContextPath();// + req.getServletPath() + 
+        //  req.getPathInfo();
+        
+        // ??? 
+        if ((href.endsWith("/")) && (path.startsWith("/")))
+            href += path.substring(1);
+        else
+            href += path;
+        if ((cacheEntry.isDirectory()) && (!href.endsWith("/")))
+            href += "/";
+
+        out.writeText(rewriteUrl(href));
+
+        out.writeElement(null, "href", XMLWriter.CLOSING);
+
+        String resourceName = path;
+        int lastSlash = path.lastIndexOf('/');
+        if (lastSlash != -1)
+            resourceName = resourceName.substring(lastSlash + 1);
+
+        switch (type) {
+
+        case FIND_ALL_PROP :
+
+            out.writeElement(null, "propstat", XMLWriter.OPENING);
+            out.writeElement(null, "prop", XMLWriter.OPENING);
+
+            out.writeProperty
+                (null, "creationdate",
+                 getISOCreationDate(cacheEntry.lastModified()));
+            out.writeElement(null, "displayname", XMLWriter.OPENING);
+            out.writeData(resourceName);
+            out.writeElement(null, "displayname", XMLWriter.CLOSING);
+            if (!cacheEntry.isDirectory()) {
+                out.writeProperty(null, "getlastmodified", 
+                        FastHttpDateFormat.formatDate(cacheEntry.lastModified(), 
+                                null));
+                out.writeProperty(null, "getcontentlength",  
+                        String.valueOf(cacheEntry.length()));
+                String contentType = 
+                    getServletContext().getMimeType(cacheEntry.getName());
+                if (contentType != null) {
+                    out.writeProperty(null, "getcontenttype",  contentType);
+                }
+                out.writeProperty(null, "getetag",  getETag(cacheEntry));
+                out.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
+            } else {
+                out.writeElement(null, "resourcetype", XMLWriter.OPENING);
+                out.writeElement(null, "collection", XMLWriter.NO_CONTENT);
+                out.writeElement(null, "resourcetype", XMLWriter.CLOSING);
+            }
+
+            out.writeProperty(null, "source", "");
+            out.writeElement(null, "prop", XMLWriter.CLOSING);
+            out.writeElement(null, "status", XMLWriter.OPENING);
+            out.writeText(status);
+            out.writeElement(null, "status", XMLWriter.CLOSING);
+            out.writeElement(null, "propstat", XMLWriter.CLOSING);
+            break;
+
+        case FIND_PROPERTY_NAMES :
+            out.writeElement(null, "propstat", XMLWriter.OPENING);
+            out.writeElement(null, "prop", XMLWriter.OPENING);
+            out.writeElement(null, "creationdate",  XMLWriter.NO_CONTENT);
+            out.writeElement(null, "displayname", XMLWriter.NO_CONTENT);
+            if (! cacheEntry.isDirectory()) {
+                out.writeElement(null, "getcontentlanguage",
+                                          XMLWriter.NO_CONTENT);
+                out.writeElement(null, "getcontentlength",
+                                          XMLWriter.NO_CONTENT);
+                out.writeElement(null, "getcontenttype",
+                                          XMLWriter.NO_CONTENT);
+                out.writeElement(null, "getetag",
+                                          XMLWriter.NO_CONTENT);
+                out.writeElement(null, "getlastmodified",
+                                          XMLWriter.NO_CONTENT);
+            }
+            out.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
+            out.writeElement(null, "source", XMLWriter.NO_CONTENT);
+            out.writeElement(null, "lockdiscovery", XMLWriter.NO_CONTENT);
+
+            out.writeElement(null, "prop", XMLWriter.CLOSING);
+            out.writeElement(null, "status", XMLWriter.OPENING);
+            out.writeText(status);
+            out.writeElement(null, "status", XMLWriter.CLOSING);
+            out.writeElement(null, "propstat", XMLWriter.CLOSING);
+
+            break;
+
+        case FIND_BY_PROPERTY :
+
+            Vector propertiesNotFound = new Vector();
+
+            // Parse the list of properties
+            out.writeElement(null, "propstat", XMLWriter.OPENING);
+            out.writeElement(null, "prop", XMLWriter.OPENING);
+
+            genPropertiesFound(out, propertiesVector, cacheEntry, 
+                    resourceName, propertiesNotFound);
+
+            out.writeElement(null, "prop", XMLWriter.CLOSING);
+            out.writeElement(null, "status", XMLWriter.OPENING);
+            out.writeText(status);
+            out.writeElement(null, "status", XMLWriter.CLOSING);
+            out.writeElement(null, "propstat", XMLWriter.CLOSING);
+
+            Enumeration propertiesNotFoundList = propertiesNotFound.elements();
+
+            if (propertiesNotFoundList.hasMoreElements()) {
+                status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND
+                                    + " " + WebdavStatus.getStatusText
+                                    (WebdavStatus.SC_NOT_FOUND));
+
+                out.writeElement(null, "propstat", XMLWriter.OPENING);
+                out.writeElement(null, "prop", XMLWriter.OPENING);
+                while (propertiesNotFoundList.hasMoreElements()) {
+                    out.writeElement
+                        (null, (String) propertiesNotFoundList.nextElement(),
+                         XMLWriter.NO_CONTENT);
+                }
+                out.writeElement(null, "prop", XMLWriter.CLOSING);
+                out.writeElement(null, "status", XMLWriter.OPENING);
+                out.writeText(status);
+                out.writeElement(null, "status", XMLWriter.CLOSING);
+                out.writeElement(null, "propstat", XMLWriter.CLOSING);
+            }
+            break;
+
+        }
+
+        out.writeElement(null, "response", XMLWriter.CLOSING);
+    }
+
+
+    private void genPropertiesFound(XMLWriter out, Vector propertiesVector, 
+                                    File cacheEntry, String resourceName, 
+                                    Vector propertiesNotFound) {
+        Enumeration properties = propertiesVector.elements();
+
+        while (properties.hasMoreElements()) {
+            String property = (String) properties.nextElement();
+            if (property.equals("creationdate")) {
+                out.writeProperty(null, "creationdate",
+                     getISOCreationDate(cacheEntry.lastModified()));
+            } else if (property.equals("displayname")) {
+                out.writeElement(null, "displayname", XMLWriter.OPENING);
+                out.writeData(resourceName);
+                out.writeElement(null, "displayname", XMLWriter.CLOSING);
+            } else if (property.equals("getcontentlanguage")) {
+                if (cacheEntry.isDirectory()) {
+                    propertiesNotFound.addElement(property);
+                } else {
+                    out.writeElement(null, "getcontentlanguage",
+                            XMLWriter.NO_CONTENT);
+                }
+            } else if (property.equals("getcontentlength")) {
+                if (cacheEntry.isDirectory()) {
+                    propertiesNotFound.addElement(property);
+                } else {
+                    out.writeProperty(null, "getcontentlength",
+                         (String.valueOf(cacheEntry.length())));
+                }
+            } else if (property.equals("getcontenttype")) {
+                if (cacheEntry.isDirectory()) {
+                    propertiesNotFound.addElement(property);
+                } else {
+                    out.writeProperty(null, "getcontenttype",
+                         getServletContext().getMimeType(cacheEntry.getName()));
+                }
+            } else if (property.equals("getetag")) {
+                if (cacheEntry.isDirectory()) {
+                    propertiesNotFound.addElement(property);
+                } else {
+                    out.writeProperty(null, "getetag", getETag(cacheEntry));
+                }
+            } else if (property.equals("getlastmodified")) {
+                if (cacheEntry.isDirectory()) {
+                    propertiesNotFound.addElement(property);
+                } else {
+                    out.writeProperty(null, "getlastmodified", 
+                            FastHttpDateFormat
+                            .formatDate(cacheEntry.lastModified(), null));
+                }
+            } else if (property.equals("resourcetype")) {
+                if (cacheEntry.isDirectory()) {
+                    out.writeElement(null, "resourcetype",
+                                              XMLWriter.OPENING);
+                    out.writeElement(null, "collection",
+                                              XMLWriter.NO_CONTENT);
+                    out.writeElement(null, "resourcetype",
+                                              XMLWriter.CLOSING);
+                } else {
+                    out.writeElement(null, "resourcetype",
+                                              XMLWriter.NO_CONTENT);
+                }
+            } else if (property.equals("source")) {
+                out.writeProperty(null, "source", "");
+            } else if (property.equals("supportedlock")) {
+                // TODO: hook for Webdav2
+                propertiesNotFound.addElement(property);
+            } else if (property.equals("lockdiscovery")) {
+                propertiesNotFound.addElement(property);
+            } else {
+                propertiesNotFound.addElement(property);
+            }
+
+        }
+    }
+
+    // ------------------ PROPPATCH --------------------
+    
+    /**
+     * PROPPATCH Method.
+     */
+    protected void doProppatch(HttpServletRequest req,
+                               HttpServletResponse resp)
+            throws ServletException, IOException {
+        resp.sendError(WebdavStatus.SC_FORBIDDEN);
+    }
+
+    // ------------------ MKCOL --------------------
+
+    /**
+     * MKCOL Method.
+     */
+    protected void doMkcol(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        if (readOnly) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        if (isLocked(req)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return;
+        }
+
+        String path = getRelativePath(req);
+
+        if ((path.toUpperCase().startsWith("/WEB-INF")) ||
+            (path.toUpperCase().startsWith("/META-INF"))) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        File object = new File(basePath,path);
+        
+        // Can't create a collection if a resource already exists at the given
+        // path
+        if (object.exists()) {
+            // Get allowed methods
+            StringBuffer methodsAllowed = determineMethodsAllowed(basePath,
+                                                                  req);
+
+            resp.addHeader("Allow", methodsAllowed.toString());
+
+            resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
+            return;
+        }
+
+        if (req.getInputStream().available() > 0) {
+            DocumentBuilder documentBuilder = getDocumentBuilder();
+            try {
+                Document document = documentBuilder.parse
+                    (new InputSource(req.getInputStream()));
+                // TODO : Process this request body
+                resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
+                return;
+
+            } catch(SAXException saxe) {
+                // Parse error - assume invalid content
+                resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+                return;
+            }
+        }
+
+        boolean result = true;
+        
+        File newDir = new File(basePath, path);
+        result = newDir.mkdir();
+
+        if (!result) {
+            resp.sendError(WebdavStatus.SC_CONFLICT,
+                           WebdavStatus.getStatusText
+                           (WebdavStatus.SC_CONFLICT));
+        } else {
+            resp.setStatus(WebdavStatus.SC_CREATED);
+            // Removing any lock-null resource which would be present
+            lockNullResources.remove(path);
+        }
+
+    }
+
+    /**
+     * DELETE Method.
+     */
+    protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        if (readOnly) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        if (isLocked(req)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return;
+        }
+
+        deleteResource(req, resp);
+
+    }
+
+
+    /**
+     * This is not part of DefaultServlet - all PUT and write operations
+     * should be handled by webdav servlet, which allows more control
+     */
+    protected void doPut(HttpServletRequest req, HttpServletResponse resp)
+            throws ServletException, IOException {
+        if (isLocked(req)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return;
+        }
+
+        if (readOnly) {
+            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+
+        String path = getRelativePath(req);
+
+        if (path.indexOf("..") >= 0) {
+            // not supported, too dangerous
+            // what else to escape ?
+            resp.setStatus(404);
+            return;
+        }
+
+        File resFile = new File(basePath, path);
+        boolean exists = resFile.exists();
+        
+        // extra check
+        if (!resFile.getCanonicalPath().startsWith(basePathName)) {
+            //log.info("File outside basedir " + basePathS + " " + f);
+            resp.setStatus(404);
+            return;
+        }
+
+
+        boolean result = true;
+
+        // Temp. content file used to support partial PUT
+        //File contentFile = null;
+
+        String rangeHeader = req.getHeader("Content-Range");
+        Range range = null;
+        if ( rangeHeader != null ) {
+            // we have a header, but has bad value.
+            // original catalina code has a bug I think
+            range = Range.parseContentRange(rangeHeader);
+            if (range == null) { // can't parse
+                resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+                return;
+            }
+        }
+
+        InputStream resourceInputStream = null;
+
+        // Append data specified in ranges to existing content for this
+        // resource - create a temp. file on the local filesystem to
+        // perform this operation
+        // Assume just one range is specified for now
+        if (range != null) {
+            //contentFile = executePartialPut(req, range, path);
+            //resourceInputStream = new FileInputStream(contentFile);
+            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
+            return;
+        } else {
+            resourceInputStream = req.getInputStream();
+        }
+
+        try {
+            // will override 
+            FileOutputStream fos = new FileOutputStream(resFile);
+            CopyUtils.copy(resourceInputStream, fos);
+        } catch(IOException e) {
+            result = false;
+        }
+
+        if (result) {
+            if (exists) {
+                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
+            } else {
+                resp.setStatus(HttpServletResponse.SC_CREATED);
+            }
+        } else {
+            resp.sendError(HttpServletResponse.SC_CONFLICT);
+        }
+
+        // Removing any lock-null resource which would be present
+        lockNullResources.remove(path);
+    }
+
+
+    /**
+     * Handle a partial PUT.  New content specified in request is appended to
+     * existing content in oldRevisionContent (if present). This code does
+     * not support simultaneous partial updates to the same resource.
+     */
+//    protected File executePartialPut(HttpServletRequest req, Range range,
+//                                     String path)
+//        throws IOException {
+//
+//        // Append data specified in ranges to existing content for this
+//        // resource - create a temp. file on the local filesystem to
+//        // perform this operation
+//        File tempDir = (File) getServletContext().getAttribute
+//            ("javax.servlet.context.tempdir");
+//        // Convert all '/' characters to '.' in resourcePath
+//        String convertedResourcePath = path.replace('/', '.');
+//        File contentFile = new File(tempDir, convertedResourcePath);
+//        if (contentFile.createNewFile()) {
+//            // Clean up contentFile when Tomcat is terminated
+//            contentFile.deleteOnExit();
+//        }
+//
+//        RandomAccessFile randAccessContentFile =
+//            new RandomAccessFile(contentFile, "rw");
+//
+//        Resource oldResource = null;
+//        try {
+//            Object obj = resources.lookup(path);
+//            if (obj instanceof Resource)
+//                oldResource = (Resource) obj;
+//        } catch (NamingException e) {
+//        }
+//
+//        // Copy data in oldRevisionContent to contentFile
+//        if (oldResource != null) {
+//            BufferedInputStream bufOldRevStream =
+//                new BufferedInputStream(oldResource.streamContent(),
+//                                        BUFFER_SIZE);
+//
+//            int numBytesRead;
+//            byte[] copyBuffer = new byte[BUFFER_SIZE];
+//            while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1) {
+//                randAccessContentFile.write(copyBuffer, 0, numBytesRead);
+//            }
+//
+//            bufOldRevStream.close();
+//        }
+//
+//        randAccessContentFile.setLength(range.length);
+//
+//        // Append data in request input stream to contentFile
+//        randAccessContentFile.seek(range.start);
+//        int numBytesRead;
+//        byte[] transferBuffer = new byte[BUFFER_SIZE];
+//        BufferedInputStream requestBufInStream =
+//            new BufferedInputStream(req.getInputStream(), BUFFER_SIZE);
+//        while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1) {
+//            randAccessContentFile.write(transferBuffer, 0, numBytesRead);
+//        }
+//        randAccessContentFile.close();
+//        requestBufInStream.close();
+//
+//        return contentFile;
+//
+//    }
+
+
+    /**
+     * COPY Method.
+     */
+    protected void doCopy(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        if (readOnly) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        copyResource(req, resp);
+    }
+
+
+    /**
+     * MOVE Method.
+     */
+    protected void doMove(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        if (readOnly) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        if (isLocked(req)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return;
+        }
+
+        String path = getRelativePath(req);
+
+        if (copyResource(req, resp)) {
+            deleteResource(path, req, resp, false);
+        }
+
+    }
+
+
+    /**
+     * LOCK Method.
+     */
+    protected void doLock(HttpServletRequest req, HttpServletResponse resp)
+            throws ServletException, IOException {
+        resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
+    }
+
+    /**
+     * UNLOCK Method.
+     */
+    protected void doUnlock(HttpServletRequest req, HttpServletResponse resp)
+            throws ServletException, IOException {
+        resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
+    }
+
+    // -------------------------------------------------------- protected Methods
+
+    /**
+     * Generate the namespace declarations.
+     */
+    protected String generateNamespaceDeclarations() {
+        return " xmlns=\"" + DEFAULT_NAMESPACE + "\"";
+    }
+
+
+    /**
+     * Check to see if a resource is currently write locked. The method
+     * will look at the "If" header to make sure the client
+     * has give the appropriate lock tokens.
+     *
+     * @param req Servlet request
+     * @return boolean true if the resource is locked (and no appropriate
+     * lock token has been found for at least one of the non-shared locks which
+     * are present on the resource).
+     */
+    protected boolean isLocked(HttpServletRequest req) {
+        return false;
+    }
+
+    /**
+     * Check to see if a resource is currently write locked.
+     *
+     * @param path Path of the resource
+     * @param ifHeader "If" HTTP header which was included in the request
+     * @return boolean true if the resource is locked (and no appropriate
+     * lock token has been found for at least one of the non-shared locks which
+     * are present on the resource).
+     */
+    protected boolean isLocked(String path, String ifHeader) {
+        return false;
+    }
+
+
+    /**
+     * Copy a resource.
+     *
+     * @param req Servlet request
+     * @param resp Servlet response
+     * @return boolean true if the copy is successful
+     */
+    protected boolean copyResource(HttpServletRequest req,
+                                 HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        // Parsing destination header
+
+        String destinationPath = req.getHeader("Destination");
+
+        if (destinationPath == null) {
+            resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+            return false;
+        }
+
+        // Remove url encoding from destination
+        destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8");
+
+        destinationPath = removeDestinationPrefix(req, destinationPath);
+
+        // Normalise destination path (remove '.' and '..')
+        destinationPath = UrlUtils.normalize(destinationPath);
+
+        String contextPath = req.getContextPath();
+        if ((contextPath != null) &&
+            (destinationPath.startsWith(contextPath))) {
+            destinationPath = destinationPath.substring(contextPath.length());
+        }
+
+        String pathInfo = req.getPathInfo();
+        if (pathInfo != null) {
+            String servletPath = req.getServletPath();
+            if ((servletPath != null) &&
+                (destinationPath.startsWith(servletPath))) {
+                destinationPath = destinationPath.substring(servletPath.length());
+            }
+        }
+
+
+        if ((destinationPath.toUpperCase().startsWith("/WEB-INF")) ||
+            (destinationPath.toUpperCase().startsWith("/META-INF"))) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return false;
+        }
+
+        String path = getRelativePath(req);
+
+        if ((path.toUpperCase().startsWith("/WEB-INF")) ||
+            (path.toUpperCase().startsWith("/META-INF"))) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return false;
+        }
+
+        if (destinationPath.equals(path)) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return false;
+        }
+
+        // Parsing overwrite header
+
+        boolean overwrite = true;
+        String overwriteHeader = req.getHeader("Overwrite");
+
+        if (overwriteHeader != null) {
+            if (overwriteHeader.equalsIgnoreCase("T")) {
+                overwrite = true;
+            } else {
+                overwrite = false;
+            }
+        }
+
+        // Overwriting the destination
+
+        boolean exists = true;
+        File f1 = new File(basePath, destinationPath);
+        if (!f1.exists()) {
+            exists = false;
+        }
+
+        if (overwrite) {
+            // Delete destination resource, if it exists
+            if (exists) {
+                if (!deleteResource(destinationPath, req, resp, true)) {
+                    return false;
+                }
+            } else {
+                resp.setStatus(WebdavStatus.SC_CREATED);
+            }
+        } else {
+            // If the destination exists, then it's a conflict
+            if (exists) {
+                resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
+                return false;
+            }
+        }
+
+        // Copying source to destination
+
+        Hashtable errorList = new Hashtable();
+
+        boolean result = copyResource(basePath, errorList,
+                                      path, destinationPath);
+
+        if ((!result) || (!errorList.isEmpty())) {
+            sendReport(req, resp, errorList);
+            return false;
+        }
+
+        // Removing any lock-null resource which would be present at
+        // the destination path
+        lockNullResources.remove(destinationPath);
+        return true;
+
+    }
+
+
+    private String removeDestinationPrefix(HttpServletRequest req, 
+                                           String destinationPath) {
+        int protocolIndex = destinationPath.indexOf("://");
+        if (protocolIndex >= 0) {
+            // if the Destination URL contains the protocol, we can safely
+            // trim everything upto the first "/" character after "://"
+            int firstSeparator =
+                destinationPath.indexOf("/", protocolIndex + 4);
+            if (firstSeparator < 0) {
+                destinationPath = "/";
+            } else {
+                destinationPath = destinationPath.substring(firstSeparator);
+            }
+        } else {
+            String hostName = req.getServerName();
+            if ((hostName != null) && (destinationPath.startsWith(hostName))) {
+                destinationPath = destinationPath.substring(hostName.length());
+            }
+
+            int portIndex = destinationPath.indexOf(":");
+            if (portIndex >= 0) {
+                destinationPath = destinationPath.substring(portIndex);
+            }
+
+            if (destinationPath.startsWith(":")) {
+                int firstSeparator = destinationPath.indexOf("/");
+                if (firstSeparator < 0) {
+                    destinationPath = "/";
+                } else {
+                    destinationPath =
+                        destinationPath.substring(firstSeparator);
+                }
+            }
+        }
+        return destinationPath;
+    }
+
+
+    /**
+     * Copy a collection.
+     *
+     * @param resources Resources implementation to be used
+     * @param errorList Hashtable containing the list of errors which occurred
+     * during the copy operation
+     * @param source Path of the resource to be copied
+     * @param dest Destination path
+     */
+    protected boolean copyResource(File resources, Hashtable errorList,
+                                   String source, String dest) {
+
+        File object = new File(basePath, source);
+        File destF = new File(basePath, dest);
+        
+        if (object.isDirectory()) {
+
+            boolean done = destF.mkdirs();
+            if (!done) {
+                errorList.put
+                    (dest, new Integer(WebdavStatus.SC_CONFLICT));
+                return false;
+            }
+
+            File[] enumeration = object.listFiles();
+            for (int i=0; i<enumeration.length; i++) {
+                String childDest = dest;
+                if (!childDest.equals("/"))
+                    childDest += "/";
+                childDest += enumeration[i].getName();
+                String childSrc = source;
+                if (!childSrc.equals("/"))
+                    childSrc += "/";
+                childSrc += enumeration[i].getName();
+                copyResource(resources, errorList, childSrc, childDest);
+            }
+
+        } else {
+
+            try {
+                CopyUtils.copy( new FileInputStream(object), 
+                    new FileOutputStream(dest));
+            } catch(IOException ex ) {
+                errorList.put
+                (source,
+                        new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+                return false;
+            }
+
+        }
+
+        return true;
+
+    }
+
+
+    /**
+     * Delete a resource.
+     *
+     * @param req Servlet request
+     * @param resp Servlet response
+     * @return boolean true if the copy is successful
+     */
+    protected boolean deleteResource(HttpServletRequest req,
+                                   HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        String path = getRelativePath(req);
+
+        return deleteResource(path, req, resp, true);
+
+    }
+
+
+    /**
+     * Delete a resource.
+     *
+     * @param path Path of the resource which is to be deleted
+     * @param req Servlet request
+     * @param resp Servlet response
+     * @param setStatus Should the response status be set on successful
+     *                  completion
+     */
+    protected boolean deleteResource(String path, HttpServletRequest req,
+                                   HttpServletResponse resp, boolean setStatus)
+        throws ServletException, IOException {
+
+        if ((path.toUpperCase().startsWith("/WEB-INF")) ||
+            (path.toUpperCase().startsWith("/META-INF"))) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return false;
+        }
+
+        String ifHeader = req.getHeader("If");
+        if (ifHeader == null)
+            ifHeader = "";
+
+        String lockTokenHeader = req.getHeader("Lock-Token");
+        if (lockTokenHeader == null)
+            lockTokenHeader = "";
+
+        if (isLocked(path, ifHeader + lockTokenHeader)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return false;
+        }
+
+        boolean exists = true;
+        File object = new File(basePath, path);
+        if (!object.exists()) {
+            exists = false;
+        }
+
+        if (!exists) {
+            resp.sendError(WebdavStatus.SC_NOT_FOUND);
+            return false;
+        }
+
+        boolean collection = object.isDirectory();
+
+        if (!collection) {
+            boolean deleted = object.delete();
+            if (!deleted) {
+                resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+                return false;
+            }
+        } else {
+
+            Hashtable errorList = new Hashtable();
+
+            deleteCollection(req, basePath, path, errorList);
+            boolean deleted = object.delete();
+            if (!deleted) {
+                errorList.put(path, new Integer
+                    (WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+            }
+
+            if (!errorList.isEmpty()) {
+
+                sendReport(req, resp, errorList);
+                return false;
+
+            }
+
+        }
+        if (setStatus) {
+            resp.setStatus(WebdavStatus.SC_NO_CONTENT);
+        }
+        return true;
+
+    }
+
+
+    /**
+     * Deletes a collection.
+     *
+     * @param resources Resources implementation associated with the context
+     * @param path Path to the collection to be deleted
+     * @param errorList Contains the list of the errors which occurred
+     */
+    protected void deleteCollection(HttpServletRequest req,
+                                  File resources,
+                                  String path, Hashtable errorList) {
+
+        if ((path.toUpperCase().startsWith("/WEB-INF")) ||
+            (path.toUpperCase().startsWith("/META-INF"))) {
+            errorList.put(path, new Integer(WebdavStatus.SC_FORBIDDEN));
+            return;
+        }
+
+        String ifHeader = req.getHeader("If");
+        if (ifHeader == null)
+            ifHeader = "";
+
+        String lockTokenHeader = req.getHeader("Lock-Token");
+        if (lockTokenHeader == null)
+            lockTokenHeader = "";
+
+        File f = new File(basePath, path);
+        File[] enumeration = f.listFiles();
+
+        for (int i=0; i<enumeration.length; i++) {
+            String childName = path;
+            if (!childName.equals("/"))
+                childName += "/";
+            
+            childName += enumeration[i].getName();
+
+            if (isLocked(childName, ifHeader + lockTokenHeader)) {
+
+                errorList.put(childName, new Integer(WebdavStatus.SC_LOCKED));
+
+            } else {
+                File object = enumeration[i];
+                if (object.isDirectory()) {
+                    deleteCollection(req, resources, childName, errorList);
+                }
+                
+                boolean deleted = object.delete();
+                if (!deleted) {
+                    if (!(object.isDirectory())) {
+                        // If it's not a collection, then it's an unknown
+                        // error
+                        errorList.put
+                        (childName, new Integer
+                                (WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+                    }
+                }
+            }
+
+        }
+
+    }
+
+
+    /**
+     * Send a multistatus element containing a complete error report to the
+     * client.
+     *
+     * @param req Servlet request
+     * @param resp Servlet response
+     * @param errorList List of error to be displayed
+     */
+    protected void sendReport(HttpServletRequest req, HttpServletResponse resp,
+                            Hashtable errorList)
+        throws ServletException, IOException {
+
+        resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
+
+        String absoluteUri = req.getRequestURI();
+        String relativePath = getRelativePath(req);
+
+        XMLWriter generatedXML = new XMLWriter();
+        generatedXML.writeXMLHeader();
+
+        generatedXML.writeElement(null, "multistatus"
+                                  + generateNamespaceDeclarations(),
+                                  XMLWriter.OPENING);
+
+        Enumeration pathList = errorList.keys();
+        while (pathList.hasMoreElements()) {
+
+            String errorPath = (String) pathList.nextElement();
+            int errorCode = ((Integer) errorList.get(errorPath)).intValue();
+
+            generatedXML.writeElement(null, "response", XMLWriter.OPENING);
+
+            generatedXML.writeElement(null, "href", XMLWriter.OPENING);
+            String toAppend = errorPath.substring(relativePath.length());
+            if (!toAppend.startsWith("/"))
+                toAppend = "/" + toAppend;
+            generatedXML.writeText(absoluteUri + toAppend);
+            generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
+            generatedXML.writeElement(null, "status", XMLWriter.OPENING);
+            generatedXML
+                .writeText("HTTP/1.1 " + errorCode + " "
+                           + WebdavStatus.getStatusText(errorCode));
+            generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
+
+            generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
+
+        }
+
+        generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
+
+        Writer writer = resp.getWriter();
+        writer.write(generatedXML.toString());
+        writer.close();
+
+    }
+
+
+
+
+    /**
+     * Return JAXP document builder instance.
+     */
+    protected DocumentBuilder getDocumentBuilder()
+        throws ServletException {
+        DocumentBuilder documentBuilder = null;
+        DocumentBuilderFactory documentBuilderFactory = null;
+        try {
+            documentBuilderFactory = DocumentBuilderFactory.newInstance();
+            documentBuilderFactory.setNamespaceAware(true);
+            documentBuilder = documentBuilderFactory.newDocumentBuilder();
+        } catch(ParserConfigurationException e) {
+            throw new ServletException("Parser error " + e);
+        }
+        return documentBuilder;
+    }
+
+
+
+    /**
+     * Get creation date in ISO format.
+     */
+    protected String getISOCreationDate(long creationDate) {
+        StringBuffer creationDateValue = new StringBuffer
+            (creationDateFormat.format
+             (new Date(creationDate)));
+        /*
+        int offset = Calendar.getInstance().getTimeZone().getRawOffset()
+            / 3600000; // FIXME ?
+        if (offset < 0) {
+            creationDateValue.append("-");
+            offset = -offset;
+        } else if (offset > 0) {
+            creationDateValue.append("+");
+        }
+        if (offset != 0) {
+            if (offset < 10)
+                creationDateValue.append("0");
+            creationDateValue.append(offset + ":00");
+        } else {
+            creationDateValue.append("Z");
+        }
+        */
+        return creationDateValue.toString();
+    }
+
+    /**
+     * Determines the methods normally allowed for the resource.
+     *
+     */
+    protected StringBuffer determineMethodsAllowed(File basePath,
+                                                   HttpServletRequest req) {
+
+        StringBuffer methodsAllowed = new StringBuffer();
+        String path = getRelativePath(req);
+        File object = new File(basePath, path);
+        if (!object.exists()) {
+            methodsAllowed.append("OPTIONS, MKCOL, PUT");
+            return methodsAllowed;
+        }
+        methodsAllowed.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE");
+        methodsAllowed.append(", PROPPATCH, COPY, MOVE, PROPFIND");
+
+        if (object.isDirectory()) {
+            methodsAllowed.append(", PUT");
+        }
+
+        return methodsAllowed;
+    }
+}
+
+
+// --------------------------------------------------------  WebdavStatus Class
+
+
+/**
+ * Wraps the HttpServletResponse class to abstract the
+ * specific protocol used.  To support other protocols
+ * we would only need to modify this class and the
+ * WebDavRetCode classes.
+ *
+ * @author              Marc Eaddy
+ * @version             1.0, 16 Nov 1997
+ */
+class WebdavStatus {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * This Hashtable contains the mapping of HTTP and WebDAV
+     * status codes to descriptive text.  This is a static
+     * variable.
+     */
+    protected static Hashtable mapStatusCodes = new Hashtable();
+
+
+    // ------------------------------------------------------ HTTP Status Codes
+
+
+    /**
+     * Status code (200) indicating the request succeeded normally.
+     */
+    public static final int SC_OK = HttpServletResponse.SC_OK;
+
+
+    /**
+     * Status code (201) indicating the request succeeded and created
+     * a new resource on the server.
+     */
+    public static final int SC_CREATED = HttpServletResponse.SC_CREATED;
+
+
+    /**
+     * Status code (202) indicating that a request was accepted for
+     * processing, but was not completed.
+     */
+    public static final int SC_ACCEPTED = HttpServletResponse.SC_ACCEPTED;
+
+
+    /**
+     * Status code (204) indicating that the request succeeded but that
+     * there was no new information to return.
+     */
+    public static final int SC_NO_CONTENT = HttpServletResponse.SC_NO_CONTENT;
+
+
+    /**
+     * Status code (301) indicating that the resource has permanently
+     * moved to a new location, and that future references should use a
+     * new URI with their requests.
+     */
+    public static final int SC_MOVED_PERMANENTLY =
+        HttpServletResponse.SC_MOVED_PERMANENTLY;
+
+
+    /**
+     * Status code (302) indicating that the resource has temporarily
+     * moved to another location, but that future references should
+     * still use the original URI to access the resource.
+     */
+    public static final int SC_MOVED_TEMPORARILY =
+        HttpServletResponse.SC_MOVED_TEMPORARILY;
+
+
+    /**
+     * Status code (304) indicating that a conditional GET operation
+     * found that the resource was available and not modified.
+     */
+    public static final int SC_NOT_MODIFIED =
+        HttpServletResponse.SC_NOT_MODIFIED;
+
+
+    /**
+     * Status code (400) indicating the request sent by the client was
+     * syntactically incorrect.
+     */
+    public static final int SC_BAD_REQUEST =
+        HttpServletResponse.SC_BAD_REQUEST;
+
+
+    /**
+     * Status code (401) indicating that the request requires HTTP
+     * authentication.
+     */
+    public static final int SC_UNAUTHORIZED =
+        HttpServletResponse.SC_UNAUTHORIZED;
+
+
+    /**
+     * Status code (403) indicating the server understood the request
+     * but refused to fulfill it.
+     */
+    public static final int SC_FORBIDDEN = HttpServletResponse.SC_FORBIDDEN;
+
+
+    /**
+     * Status code (404) indicating that the requested resource is not
+     * available.
+     */
+    public static final int SC_NOT_FOUND = HttpServletResponse.SC_NOT_FOUND;
+
+
+    /**
+     * Status code (500) indicating an error inside the HTTP service
+     * which prevented it from fulfilling the request.
+     */
+    public static final int SC_INTERNAL_SERVER_ERROR =
+        HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+
+
+    /**
+     * Status code (501) indicating the HTTP service does not support
+     * the functionality needed to fulfill the request.
+     */
+    public static final int SC_NOT_IMPLEMENTED =
+        HttpServletResponse.SC_NOT_IMPLEMENTED;
+
+
+    /**
+     * Status code (502) indicating that the HTTP server received an
+     * invalid response from a server it consulted when acting as a
+     * proxy or gateway.
+     */
+    public static final int SC_BAD_GATEWAY =
+        HttpServletResponse.SC_BAD_GATEWAY;
+
+
+    /**
+     * Status code (503) indicating that the HTTP service is
+     * temporarily overloaded, and unable to handle the request.
+     */
+    public static final int SC_SERVICE_UNAVAILABLE =
+        HttpServletResponse.SC_SERVICE_UNAVAILABLE;
+
+
+    /**
+     * Status code (100) indicating the client may continue with
+     * its request.  This interim response is used to inform the
+     * client that the initial part of the request has been
+     * received and has not yet been rejected by the server.
+     */
+    public static final int SC_CONTINUE = 100;
+
+
+    /**
+     * Status code (405) indicating the method specified is not
+     * allowed for the resource.
+     */
+    public static final int SC_METHOD_NOT_ALLOWED = 405;
+
+
+    /**
+     * Status code (409) indicating that the request could not be
+     * completed due to a conflict with the current state of the
+     * resource.
+     */
+    public static final int SC_CONFLICT = 409;
+
+
+    /**
+     * Status code (412) indicating the precondition given in one
+     * or more of the request-header fields evaluated to false
+     * when it was tested on the server.
+     */
+    public static final int SC_PRECONDITION_FAILED = 412;
+
+
+    /**
+     * Status code (413) indicating the server is refusing to
+     * process a request because the request entity is larger
+     * than the server is willing or able to process.
+     */
+    public static final int SC_REQUEST_TOO_LONG = 413;
+
+
+    /**
+     * Status code (415) indicating the server is refusing to service
+     * the request because the entity of the request is in a format
+     * not supported by the requested resource for the requested
+     * method.
+     */
+    public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
+
+
+    // -------------------------------------------- Extended WebDav status code
+
+
+    /**
+     * Status code (207) indicating that the response requires
+     * providing status for multiple independent operations.
+     */
+    public static final int SC_MULTI_STATUS = 207;
+    // This one colides with HTTP 1.1
+    // "207 Parital Update OK"
+
+
+    /**
+     * Status code (418) indicating the entity body submitted with
+     * the PATCH method was not understood by the resource.
+     */
+    public static final int SC_UNPROCESSABLE_ENTITY = 418;
+    // This one colides with HTTP 1.1
+    // "418 Reauthentication Required"
+
+
+    /**
+     * Status code (419) indicating that the resource does not have
+     * sufficient space to record the state of the resource after the
+     * execution of this method.
+     */
+    public static final int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419;
+    // This one colides with HTTP 1.1
+    // "419 Proxy Reauthentication Required"
+
+
+    /**
+     * Status code (420) indicating the method was not executed on
+     * a particular resource within its scope because some part of
+     * the method's execution failed causing the entire method to be
+     * aborted.
+     */
+    public static final int SC_METHOD_FAILURE = 420;
+
+
+    /**
+     * Status code (423) indicating the destination resource of a
+     * method is locked, and either the request did not contain a
+     * valid Lock-Info header, or the Lock-Info header identifies
+     * a lock held by another principal.
+     */
+    public static final int SC_LOCKED = 423;
+
+
+    // ------------------------------------------------------------ Initializer
+
+
+    static {
+        // HTTP 1.0 tatus Code
+        addStatusCodeMap(SC_OK, "OK");
+        addStatusCodeMap(SC_CREATED, "Created");
+        addStatusCodeMap(SC_ACCEPTED, "Accepted");
+        addStatusCodeMap(SC_NO_CONTENT, "No Content");
+        addStatusCodeMap(SC_MOVED_PERMANENTLY, "Moved Permanently");
+        addStatusCodeMap(SC_MOVED_TEMPORARILY, "Moved Temporarily");
+        addStatusCodeMap(SC_NOT_MODIFIED, "Not Modified");
+        addStatusCodeMap(SC_BAD_REQUEST, "Bad Request");
+        addStatusCodeMap(SC_UNAUTHORIZED, "Unauthorized");
+        addStatusCodeMap(SC_FORBIDDEN, "Forbidden");
+        addStatusCodeMap(SC_NOT_FOUND, "Not Found");
+        addStatusCodeMap(SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
+        addStatusCodeMap(SC_NOT_IMPLEMENTED, "Not Implemented");
+        addStatusCodeMap(SC_BAD_GATEWAY, "Bad Gateway");
+        addStatusCodeMap(SC_SERVICE_UNAVAILABLE, "Service Unavailable");
+        addStatusCodeMap(SC_CONTINUE, "Continue");
+        addStatusCodeMap(SC_METHOD_NOT_ALLOWED, "Method Not Allowed");
+        addStatusCodeMap(SC_CONFLICT, "Conflict");
+        addStatusCodeMap(SC_PRECONDITION_FAILED, "Precondition Failed");
+        addStatusCodeMap(SC_REQUEST_TOO_LONG, "Request Too Long");
+        addStatusCodeMap(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type");
+        // WebDav Status Codes
+        addStatusCodeMap(SC_MULTI_STATUS, "Multi-Status");
+        addStatusCodeMap(SC_UNPROCESSABLE_ENTITY, "Unprocessable Entity");
+        addStatusCodeMap(SC_INSUFFICIENT_SPACE_ON_RESOURCE,
+                         "Insufficient Space On Resource");
+        addStatusCodeMap(SC_METHOD_FAILURE, "Method Failure");
+        addStatusCodeMap(SC_LOCKED, "Locked");
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Returns the HTTP status text for the HTTP or WebDav status code
+     * specified by looking it up in the static mapping.  This is a
+     * static function.
+     *
+     * @param   nHttpStatusCode [IN] HTTP or WebDAV status code
+     * @return  A string with a short descriptive phrase for the
+     *                  HTTP status code (e.g., "OK").
+     */
+    public static String getStatusText(int nHttpStatusCode) {
+        Integer intKey = new Integer(nHttpStatusCode);
+
+        if (!mapStatusCodes.containsKey(intKey)) {
+            return "";
+        } else {
+            return (String) mapStatusCodes.get(intKey);
+        }
+    }
+
+
+    // -------------------------------------------------------- protected Methods
+
+
+    /**
+     * Adds a new status code -> status text mapping.  This is a static
+     * method because the mapping is a static variable.
+     *
+     * @param   nKey    [IN] HTTP or WebDAV status code
+     * @param   strVal  [IN] HTTP status text
+     */
+    protected static void addStatusCodeMap(int nKey, String strVal) {
+        mapStatusCodes.put(new Integer(nKey), strVal);
+    }
+
+};
+
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/XMLWriter.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/file/XMLWriter.java
new file mode 100644 (file)
index 0000000..46652dc
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.servlets.file;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * XMLWriter helper class.
+ *
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ */
+public class XMLWriter {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    /**
+     * Opening tag.
+     */
+    public static final int OPENING = 0;
+
+
+    /**
+     * Closing tag.
+     */
+    public static final int CLOSING = 1;
+
+
+    /**
+     * Element with no content.
+     */
+    public static final int NO_CONTENT = 2;
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Buffer.
+     */
+    protected StringBuffer buffer = new StringBuffer();
+
+
+    /**
+     * Writer.
+     */
+    protected Writer writer = null;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructor.
+     */
+    public XMLWriter() {
+    }
+
+
+    /**
+     * Constructor.
+     */
+    public XMLWriter(Writer writer) {
+        this.writer = writer;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Retrieve generated XML.
+     *
+     * @return String containing the generated XML
+     */
+    public String toString() {
+        return buffer.toString();
+    }
+
+
+    /**
+     * Write property to the XML.
+     *
+     * @param namespace Namespace
+     * @param namespaceInfo Namespace info
+     * @param name Property name
+     * @param value Property value
+     */
+    public void writeProperty(String namespace, String namespaceInfo,
+                              String name, String value) {
+        writeElement(namespace, namespaceInfo, name, OPENING);
+        buffer.append(value);
+        writeElement(namespace, namespaceInfo, name, CLOSING);
+
+    }
+
+
+    /**
+     * Write property to the XML.
+     *
+     * @param namespace Namespace
+     * @param name Property name
+     * @param value Property value
+     */
+    public void writeProperty(String namespace, String name, String value) {
+        writeElement(namespace, name, OPENING);
+        buffer.append(value);
+        writeElement(namespace, name, CLOSING);
+    }
+
+
+    /**
+     * Write property to the XML.
+     *
+     * @param namespace Namespace
+     * @param name Property name
+     */
+    public void writeProperty(String namespace, String name) {
+        writeElement(namespace, name, NO_CONTENT);
+    }
+
+
+    /**
+     * Write an element.
+     *
+     * @param name Element name
+     * @param namespace Namespace abbreviation
+     * @param type Element type
+     */
+    public void writeElement(String namespace, String name, int type) {
+        writeElement(namespace, null, name, type);
+    }
+
+
+    /**
+     * Write an element.
+     *
+     * @param namespace Namespace abbreviation
+     * @param namespaceInfo Namespace info
+     * @param name Element name
+     * @param type Element type
+     */
+    public void writeElement(String namespace, String namespaceInfo,
+                             String name, int type) {
+        if ((namespace != null) && (namespace.length() > 0)) {
+            switch (type) {
+            case OPENING:
+                if (namespaceInfo != null) {
+                    buffer.append("<" + namespace + ":" + name + " xmlns:"
+                                  + namespace + "=\""
+                                  + namespaceInfo + "\">");
+                } else {
+                    buffer.append("<" + namespace + ":" + name + ">");
+                }
+                break;
+            case CLOSING:
+                buffer.append("</" + namespace + ":" + name + ">\n");
+                break;
+            case NO_CONTENT:
+            default:
+                if (namespaceInfo != null) {
+                    buffer.append("<" + namespace + ":" + name + " xmlns:"
+                                  + namespace + "=\""
+                                  + namespaceInfo + "\"/>");
+                } else {
+                    buffer.append("<" + namespace + ":" + name + "/>");
+                }
+                break;
+            }
+        } else {
+            switch (type) {
+            case OPENING:
+                buffer.append("<" + name + ">");
+                break;
+            case CLOSING:
+                buffer.append("</" + name + ">\n");
+                break;
+            case NO_CONTENT:
+            default:
+                buffer.append("<" + name + "/>");
+                break;
+            }
+        }
+    }
+
+
+    /**
+     * Write text.
+     *
+     * @param text Text to append
+     */
+    public void writeText(String text) {
+        buffer.append(text);
+    }
+
+
+    /**
+     * Write data.
+     *
+     * @param data Data to append
+     */
+    public void writeData(String data) {
+        buffer.append("<![CDATA[" + data + "]]>");
+    }
+
+
+    /**
+     * Write XML Header.
+     */
+    public void writeXMLHeader() {
+        buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    }
+
+
+    /**
+     * Send data and reinitializes buffer.
+     */
+    public void sendData()
+        throws IOException {
+        if (writer != null) {
+            writer.write(buffer.toString());
+            buffer = new StringBuffer();
+        }
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/JasperCompilerTemplateClassMapper.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/JasperCompilerTemplateClassMapper.java
new file mode 100644 (file)
index 0000000..29c9d8c
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * 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.tomcat.servlets.jsp;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.naming.NamingException;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.apache.InstanceManager;
+import org.apache.jasper.EmbeddedServletOptions;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspC;
+import org.apache.jasper.Options;
+import org.apache.jasper.compiler.JspRuntimeContext;
+import org.apache.jasper.servlet.JspServletWrapper;
+
+/** 
+ * The actual compiler. Maps and compile a jsp-file to a class.
+ */
+public class JasperCompilerTemplateClassMapper 
+        extends SimpleTemplateClassMapper {
+    
+    public void init(ServletConfig config) {
+        this.config = config;
+        ServletContext context = config.getServletContext();
+        context.setAttribute(InstanceManager.class.getName(), 
+                new InstanceManager() {
+
+                    public void destroyInstance(Object arg0)
+                            throws IllegalAccessException,
+                            InvocationTargetException {
+                    }
+
+                    public Object newInstance(String arg0)
+                            throws IllegalAccessException,
+                            InvocationTargetException, NamingException,
+                            InstantiationException, ClassNotFoundException {
+                        return newInstance(arg0, 
+                                this.getClass().getClassLoader());
+                    }
+
+                    public void newInstance(Object o)
+                            throws IllegalAccessException,
+                            InvocationTargetException, NamingException {
+                    }
+
+                    public Object newInstance(String className,
+                                              ClassLoader classLoader)
+                            throws IllegalAccessException,
+                            InvocationTargetException, NamingException,
+                            InstantiationException, ClassNotFoundException {
+                        Class clazz = classLoader.loadClass(className);
+                        return clazz.newInstance();
+                    }
+            
+        });
+        //      Initialize the JSP Runtime Context
+        options = new EmbeddedServletOptions(config, context);
+        
+        rctxt = new JspRuntimeContext(context, options);
+        String basePath = context.getRealPath("/");
+        File f = new File(basePath + "/WEB-INF/classes");
+        f.mkdirs();
+        //fileS.initParams.put("scratchdir",  f.getAbsolutePath());
+        // if load-on-startup: allow other servlets to find us
+
+        
+    }
+    
+    private Options options;
+    private JspRuntimeContext rctxt;
+    private ServletConfig config;
+
+    public boolean needsReload(String jspFile, Servlet s) {
+        JspServletWrapper wrapper =
+            (JspServletWrapper) rctxt.getWrapper(jspFile);
+        // TODO: extract outdate info, compilation date, etc
+        return false;
+    }
+    
+    protected Servlet compileAndInitPage(ServletContext ctx, 
+                                         String jspUri, 
+                                         ServletConfig cfg) 
+    throws ServletException {
+        try {
+            if (config == null) {
+                init(cfg);
+            }
+            JspServletWrapper wrapper =
+                (JspServletWrapper) rctxt.getWrapper(jspUri);
+            if (wrapper == null) {
+                synchronized(this) {
+                    wrapper = (JspServletWrapper) rctxt.getWrapper(jspUri);
+                    if (wrapper == null) {
+                        // Check if the requested JSP page exists, to avoid
+                        // creating unnecessary directories and files.
+                        if (null == ctx.getResource(jspUri)) {
+                            return null;
+                        }
+                        //boolean isErrorPage = exception != null;
+                        wrapper = new JspServletWrapper(cfg, options, jspUri,
+                                false, rctxt);
+                        rctxt.addWrapper(jspUri,wrapper);
+                    }
+                }
+            }
+
+            wrapper.getJspEngineContext().compile();
+            return wrapper.getServlet();
+        } catch (IOException ex) {
+            throw new ServletException(ex);
+        }
+    }
+
+    /**
+     *  
+     * Do the compilation - without JspServletWrapper
+     * 
+     * Options: 
+     *  - jasper.jar in classpath, we do Class.forName for main()
+     *  - TODO: exec jasper.sh ( or any other script set in params ) 
+     *  - TODO: redirect to a different servlet
+     * 
+     * Not used right - see previous method for a safer approach
+     * 
+     * @param ctx
+     * @param jspPath
+     */
+   public void compileJspDirect(ServletContext ctx, String jspPath) {
+        //ServletContextImpl ctx = (ServletContextImpl)sctx;
+        // Params to pass to jspc:
+        // classpath 
+        // webapp base dir
+        String baseDir = ctx.getRealPath("/");
+        // jsp path ( rel. base dir )
+
+        JspC jspc = new JspC();
+        jspc.setUriroot(baseDir);
+        jspc.setTrimSpaces(false);
+        jspc.setPoolingEnabled(true);
+        jspc.setErrorOnUseBeanInvalidClassAttribute(false);
+        jspc.setClassDebugInfo(true);
+        jspc.setCaching(true);
+        jspc.setSmapDumped(true);
+
+        try {
+            jspc.execute();
+        } catch (JasperException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/JspFileTemplateServlet.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/JspFileTemplateServlet.java
new file mode 100644 (file)
index 0000000..85c1cc0
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * 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.tomcat.servlets.jsp;
+
+import java.io.IOException;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tomcat.addons.UserTemplateClassMapper;
+import org.apache.tomcat.integration.ObjectManager;
+
+/**
+ * Support for <servlet><jsp-file> config. 
+ * 
+ * A servlet can be configured with a jsp-file instead of a class. This can
+ * be translated into a regular servlet, with JspFileTemplateServlet class and
+ * the jsp-file as init parameter.  
+ * 
+ * This servlet is not jsp specific - you can put any templating file in the 
+ * jsp-file config ( if you can ignore the jsp in the name of the attribute ).
+ * The file will be converted to a servlet by a custom addon ( jasper in most
+ * cases ) 
+ * 
+ * @author Costin Manolache
+ */
+public class JspFileTemplateServlet extends HttpServlet {
+    
+    String jspFile; 
+    Servlet realJspServlet;
+
+    UserTemplateClassMapper mapper;
+
+    /** 
+     * If called from a <jsp-file> servlet, compile the servlet and init it.
+     */
+    public void init(ServletConfig config) throws ServletException {
+        super.init(config);
+        // Support <servlet><jsp-file>
+        String jspFileParam = config.getInitParameter("jsp-file");
+        // TODO: use extension to find the right UserTemplateClassMapper.
+        ObjectManager om = 
+            (ObjectManager) config.getServletContext().getAttribute(ObjectManager.ATTRIBUTE);
+        mapper = 
+            (UserTemplateClassMapper) om.get(
+                    UserTemplateClassMapper.class);
+        if (mapper == null) {
+            mapper = new SimpleTemplateClassMapper();
+        }
+        
+        if (jspFile == null && jspFileParam != null) {
+            jspFile = jspFileParam;
+        }
+        // exception will be thrown if not set properly
+        realJspServlet = mapper.loadProxy(jspFile, getServletContext(), 
+                config);
+        realJspServlet.init(config);
+    }
+
+    public void setJspFile(String jspFile) {
+        this.jspFile = jspFile;
+    }
+    
+    protected void service(HttpServletRequest req, HttpServletResponse res)
+        throws ServletException, IOException 
+    {
+        // TODO: support reload
+        
+        // realJspServlet will be set - init will fail otherwise.
+        realJspServlet.service(req, res);
+    }
+} 
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/PreCompileFilter.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/PreCompileFilter.java
new file mode 100644 (file)
index 0000000..06497dd
--- /dev/null
@@ -0,0 +1,71 @@
+package org.apache.tomcat.servlets.jsp;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+
+/** 
+ * Filter for JSPs to support 'preCompile' support.
+ * This is a silly feature, can and should be left out in prod.
+ * It needs to be used in the default config to pass the tests.
+ * 
+ * @author Costin Manolache
+ */
+public class PreCompileFilter extends HttpServlet {
+    // Copied from jasper.Constants to avoid compile dep
+    /**
+     * The query parameter that causes the JSP engine to just
+     * pregenerated the servlet but not invoke it. 
+     */
+    public static final String PRECOMPILE = 
+      System.getProperty("org.apache.jasper.Constants.PRECOMPILE", "jsp_precompile");
+    
+    /** 
+     * If called from a <jsp-file> servlet, compile the servlet and init it.
+     */
+    public void init(ServletConfig arg0) throws ServletException {
+        super.init(arg0);
+    }
+    
+    boolean preCompile(HttpServletRequest request) throws ServletException {
+        String queryString = request.getQueryString();
+        if (queryString == null) {
+            return (false);
+        }
+        int start = queryString.indexOf(PRECOMPILE);
+        if (start < 0) {
+            return (false);
+        }
+        queryString =
+            queryString.substring(start + PRECOMPILE.length());
+        if (queryString.length() == 0) {
+            return (true);             // ?jsp_precompile
+        }
+        if (queryString.startsWith("&")) {
+            return (true);             // ?jsp_precompile&foo=bar...
+        }
+        if (!queryString.startsWith("=")) {
+            return (false);            // part of some other name or value
+        }
+        int limit = queryString.length();
+        int ampersand = queryString.indexOf("&");
+        if (ampersand > 0) {
+            limit = ampersand;
+        }
+        String value = queryString.substring(1, limit);
+        if (value.equals("true")) {
+            return (true);             // ?jsp_precompile=true
+        } else if (value.equals("false")) {
+            // Spec says if jsp_precompile=false, the request should not
+            // be delivered to the JSP page; the easiest way to implement
+            // this is to set the flag to true, and precompile the page anyway.
+            // This still conforms to the spec, since it says the
+            // precompilation request can be ignored.
+            return (true);             // ?jsp_precompile=false
+        } else {
+            throw new ServletException("Cannot have request parameter " +
+                        PRECOMPILE + " set to " + value);
+        }
+    }
+} 
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/SimpleTemplateClassMapper.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/SimpleTemplateClassMapper.java
new file mode 100644 (file)
index 0000000..7c01312
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * 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.tomcat.servlets.jsp;
+
+import java.util.Vector;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+
+import org.apache.tomcat.addons.UserTemplateClassMapper;
+
+/** 
+ * Extracted from Jasper - maps a template file ( foo/my.jsp ) to a classname
+ * that is generated by the UserTemplateCompiler.
+ * 
+ * TODO: transform this to an interface, paired with UserTemplateCompiler.
+ * 
+ * @author Costin Manolache
+ */
+public class SimpleTemplateClassMapper implements UserTemplateClassMapper {
+
+    /** 
+     * Load the proxied jsp, if any.
+     * @param config 
+     * @throws ServletException 
+     */
+    public Servlet loadProxy(String jspFile, 
+                             ServletContext ctx, 
+                             ServletConfig config) throws ServletException {
+        String mangledClass = getClassName( jspFile );
+
+        HttpServlet jsp = null;
+        Class jspC = null;
+        
+        // Already created
+        if( jspC == null ) {
+            try {
+                jspC=Class.forName( mangledClass );
+            } catch( Throwable t ) {
+                // Not found - first try 
+            }
+        }
+        
+        if (jspC == null) {
+            // Class not found - needs to be compiled
+            return compileAndInitPage(ctx, jspFile, config);
+        }
+        
+        try {
+            jsp=(HttpServlet)jspC.newInstance();
+        } catch( Throwable t ) {
+            t.printStackTrace();
+        }
+        jsp.init(config);
+        return jsp;
+    }
+    
+    public boolean needsReload(String jspFile, Servlet s) {
+        return false;
+    }
+    
+    protected Servlet compileAndInitPage(ServletContext ctx, 
+                                      String jspUri, 
+                                      ServletConfig cfg) 
+        throws ServletException {
+        throw new ServletException("Pre-compiled page not found, please " +
+                       "add a compiler addon to compile at runtime");
+    }
+    
+    /** Convert an identifier to a class name, using jasper conventions
+     * 
+     * @param jspUri a relative JSP file
+     * @return class name that would be generated by jasper
+     */
+    public String getClassName( String jspUri ) {
+        int iSep = jspUri.lastIndexOf('/') + 1;
+        String className = makeJavaIdentifier(jspUri.substring(iSep));
+        String basePackageName = JSP_PACKAGE_NAME;
+
+        iSep--;
+        String derivedPackageName = (iSep > 0) ?
+                makeJavaPackage(jspUri.substring(1,iSep)) : "";
+        
+        if (derivedPackageName.length() == 0) {
+            return basePackageName + "." + className;
+        }
+        return basePackageName + '.' + derivedPackageName + "." + className;
+    }
+
+    // ------------- Copied from jasper ---------------------------
+
+    private static final String JSP_PACKAGE_NAME = "org.apache.jsp";
+
+    private static final String makeJavaIdentifier(String identifier) {
+        StringBuffer modifiedIdentifier = 
+            new StringBuffer(identifier.length());
+        if (!Character.isJavaIdentifierStart(identifier.charAt(0))) {
+            modifiedIdentifier.append('_');
+        }
+        for (int i = 0; i < identifier.length(); i++) {
+            char ch = identifier.charAt(i);
+            if (Character.isJavaIdentifierPart(ch) && ch != '_') {
+                modifiedIdentifier.append(ch);
+            } else if (ch == '.') {
+                modifiedIdentifier.append('_');
+            } else {
+                modifiedIdentifier.append(mangleChar(ch));
+            }
+        }
+        if (isJavaKeyword(modifiedIdentifier.toString())) {
+            modifiedIdentifier.append('_');
+        }
+        return modifiedIdentifier.toString();
+    }
+
+    private static final String javaKeywords[] = {
+        "abstract", "assert", "boolean", "break", "byte", "case",
+        "catch", "char", "class", "const", "continue",
+        "default", "do", "double", "else", "enum", "extends",
+        "final", "finally", "float", "for", "goto",
+        "if", "implements", "import", "instanceof", "int",
+        "interface", "long", "native", "new", "package",
+        "private", "protected", "public", "return", "short",
+        "static", "strictfp", "super", "switch", "synchronized",
+        "this", "throws", "transient", "try", "void",
+        "volatile", "while" };
+
+    private static final String makeJavaPackage(String path) {
+        String classNameComponents[] = split(path,"/");
+        StringBuffer legalClassNames = new StringBuffer();
+        for (int i = 0; i < classNameComponents.length; i++) {
+            legalClassNames.append(makeJavaIdentifier(classNameComponents[i]));
+            if (i < classNameComponents.length - 1) {
+                legalClassNames.append('.');
+            }
+        }
+        return legalClassNames.toString();
+    }
+
+    private static final String [] split(String path, String pat) {
+        Vector comps = new Vector();
+        int pos = path.indexOf(pat);
+        int start = 0;
+        while( pos >= 0 ) {
+            if(pos > start ) {
+                String comp = path.substring(start,pos);
+                comps.add(comp);
+            }
+            start = pos + pat.length();
+            pos = path.indexOf(pat,start);
+        }
+        if( start < path.length()) {
+            comps.add(path.substring(start));
+        }
+        String [] result = new String[comps.size()];
+        for(int i=0; i < comps.size(); i++) {
+            result[i] = (String)comps.elementAt(i);
+        }
+        return result;
+    }
+            
+
+    /**
+     * Test whether the argument is a Java keyword
+     */
+    private static boolean isJavaKeyword(String key) {
+        int i = 0;
+        int j = javaKeywords.length;
+        while (i < j) {
+            int k = (i+j)/2;
+            int result = javaKeywords[k].compareTo(key);
+            if (result == 0) {
+                return true;
+            }
+            if (result < 0) {
+                i = k+1;
+            } else {
+                j = k;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Mangle the specified character to create a legal Java class name.
+     */
+    private static final String mangleChar(char ch) {
+        char[] result = new char[5];
+        result[0] = '_';
+        result[1] = Character.forDigit((ch >> 12) & 0xf, 16);
+        result[2] = Character.forDigit((ch >> 8) & 0xf, 16);
+        result[3] = Character.forDigit((ch >> 4) & 0xf, 16);
+        result[4] = Character.forDigit(ch & 0xf, 16);
+        return new String(result);
+    }
+    
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/SingleThreadedProxyServlet.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/SingleThreadedProxyServlet.java
new file mode 100644 (file)
index 0000000..02cf9c3
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ */
+package org.apache.tomcat.servlets.jsp;
+
+import java.util.Stack;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/** For SingleThreadedServlet support. 
+ * 
+ * This is container independent.
+ * 
+ * Will maintain a pool of servlets, etc 
+ * 
+ * @author Costin Manolache
+ */
+public class SingleThreadedProxyServlet extends HttpServlet {
+
+    private Class classClass = null;
+    private transient boolean singleThreadModel = false;
+    /**
+     * Stack containing the STM instances.
+     */
+    private transient Stack instancePool = null;
+
+    /**
+     * Extra params: 
+     *   - servlet-class - the class of the single-threaded servlet
+     *   - 
+     * 
+     */
+    public void init() {
+        
+    }
+    
+    public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException {
+        synchronized (instancePool) {
+            if (instancePool.isEmpty()) {
+                try {
+                    Servlet newServlet = null; // loadServlet();
+
+                    // todo: should we init each of them ?
+                    
+                    newServlet.service(req, res);
+                    
+                    
+                } catch (Throwable e) {
+                    throw new ServletException("allocate ",
+                            e);
+                }
+            }
+            Servlet s = (Servlet) instancePool.pop();
+        }
+
+        
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/WildcardTemplateServlet.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/jsp/WildcardTemplateServlet.java
new file mode 100644 (file)
index 0000000..609dbc2
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * 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.tomcat.servlets.jsp;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tomcat.addons.UserTemplateClassMapper;
+import org.apache.tomcat.integration.ObjectManager;
+
+
+/** 
+ *
+ * @author Costin Manolache
+ */
+public class WildcardTemplateServlet extends HttpServlet {
+
+    // for the '*.jsp' case - need to keep track of the jsps
+    HashMap<String, Servlet> jsps=new HashMap<String, Servlet>();
+
+    UserTemplateClassMapper mapper;
+    
+    /** 
+     * If called from a <jsp-file> servlet, compile the servlet and init it.
+     */
+    public void init(ServletConfig config) throws ServletException {
+        super.init(config);
+        ObjectManager om = 
+            (ObjectManager) config.getServletContext().getAttribute(ObjectManager.ATTRIBUTE);
+        mapper = 
+            (UserTemplateClassMapper) om.get(
+                    UserTemplateClassMapper.class);
+        if (mapper == null) {
+            mapper = new SimpleTemplateClassMapper();
+        }
+    }
+
+    // TODO: use context extensions to register the servlet as if it would be 
+    // loaded from web.xml
+    
+    protected void service(HttpServletRequest req, HttpServletResponse res)
+        throws ServletException, IOException 
+    {
+        // This is a *.jsp mapping.
+        String jspPath = null;
+
+        /** 
+         * Magic to get the jsp file from the mappings or container
+         */
+        if (jspPath == null) {
+            jspPath = (String)req.getAttribute("org.apache.catalina.jsp_file");
+        }
+        if (jspPath == null) {
+            // RequestDispatcher.include()
+            jspPath = (String)req.getAttribute("javax.servlet.include.servlet_path");
+            if (jspPath != null) {
+                String pathInfo = (String)req.getAttribute("javax.servlet.include.path_info");
+                if (pathInfo != null) {
+                    jspPath += pathInfo;
+                }
+            } else {
+                jspPath = req.getServletPath();
+                String pathInfo = req.getPathInfo();
+                if (pathInfo != null) {
+                    jspPath += pathInfo;
+                }
+            }
+        }
+        
+        // now we should have jspUri == the path to the jsp.
+        Servlet realJspServlet = jsps.get(jspPath);
+        
+        // TODO: support reload
+        
+        if (realJspServlet == null) {
+            realJspServlet = mapper.loadProxy(jspPath, 
+                    getServletContext(), getServletConfig());
+            if (realJspServlet != null) {
+                jsps.put(jspPath, realJspServlet);
+            } else {
+                throw new ServletException(jspPath + " not found");
+            }
+        }
+        
+        realJspServlet.service(req,  res);
+    }    
+} 
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/AccessFilter.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/AccessFilter.java
new file mode 100644 (file)
index 0000000..5af1e7f
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * Copyright 1999-2001,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.servlets.sec;
+
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tomcat.addons.UserAuthentication;
+
+
+/**
+ * Access control. 
+ * 
+ * In Catalina, the AuthenticatorBase.invoke() will apply the security
+ * filters based on LoginConfig. All constraints are applied in the 
+ * valve. For sessions - the Principal will be cached. The TTL needs to be
+ * indicated by the authenticator.
+ * 
+ * This works differently - it's a regular filter, could be modified and 
+ * set in web.xml explicitely, or set by the container when reading web.xml
+ * 
+ * 
+ * Mappings:
+ * 
+ *  /[FORM]/j_security_check ( with j_password, j_username params )
+ *   -> authentication servlet
+ *   Assert: no other j_security_check mapping
+ *   
+ *  
+ * For each security rule we define one AccessFilter, configured with the 
+ * right init-params.  
+ * 
+ * 1. For each security_constraint, create a (base) filter named: _access_nnn, 
+ * and add init-params for roles 
+ * 
+ * 1.1 For each web-resource-collection, take the method set, sort it. Create
+ * one filter for each set of methods, named _access_nnn_methodlist
+ * 
+ * 2.For each pattern in each web-resource-collection, add a mapping to 
+ * the appropriate filter, at the end of web.xml ( after normal filters
+ * and servlets ).
+ *   
+ * 
+ * @author Costin Manolache
+ */
+public class AccessFilter implements Filter {
+  
+    // web-resource-collection: name -> url+method rules
+    // Since filters don't match method, for each method subset we need a new
+    // filter instance
+    private String[] methods; 
+    
+    // Wildcard on roles - anyone authenticated can access the resource
+    private boolean allRoles = false;
+
+    // if false - no access control needed. 
+    // if true - must check roles - either allRoles or specific list
+    private boolean authConstraint = false;
+
+    // roles to check
+    private String authRoles[] = new String[0];
+
+    /**
+     * The user data constraint for this security constraint.  Must be NONE,
+     * INTEGRAL, or CONFIDENTIAL.
+     */
+    private String userConstraint = "NONE";
+
+    
+    public void destroy() {
+    }
+
+    public static class AuthToken {
+        public String headerValue; // key
+        public long expiry; 
+        public Principal principal;
+    }
+
+    Map cachedTokens = new HashMap();
+
+    UserAuthentication auth;
+    String method;
+    
+    public void doFilter(ServletRequest request, 
+                         ServletResponse servletResponse, 
+                         FilterChain chain) 
+            throws IOException, ServletException {
+        
+        HttpServletResponse res = (HttpServletResponse)servletResponse;
+        HttpServletRequest req = (HttpServletRequest)request;
+
+        String method = req.getMethod();
+        // exclude the context path
+        String uri = req.getServletPath() + req.getPathInfo(); 
+        
+        // no authorization or principal not found. 
+        // Call the auth servlet.
+        // TODO: use URL or RD.include.
+        
+        Principal p = auth.authenticate(req, res, method);
+        // set the principal on req!
+        if (p == null) {
+            // couldn't authenticate - response is set.
+            return;
+        } else {
+            // we are ok - cache the response, forward
+            
+            chain.doFilter(req, res);
+        }
+        
+    }
+
+
+
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/BasicAuthentication.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/BasicAuthentication.java
new file mode 100644 (file)
index 0000000..fa98e5a
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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.tomcat.servlets.sec;
+
+
+import java.io.IOException;
+import java.security.Principal;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.addons.UserAuthentication;
+
+
+/**
+ * Implements Basic authentication.
+ */
+public class BasicAuthentication implements UserAuthentication {
+
+    private static Log log = LogFactory.getLog(BasicAuthentication.class);
+    String realm;
+    
+    @Override
+    public Principal authenticate(HttpServletRequest request,
+                                  HttpServletResponse response,
+                                  String requestedMethod) throws IOException {
+            
+        if (realm == null)
+            realm = request.getServerName() + ":" + request.getServerPort();
+
+        // Validate any credentials already included with this request
+        String authorization = request.getHeader("authorization");
+        if (authorization != null) {
+            Principal principal = null; // processDigestHeader(request, authorization);
+            if (principal != null) {
+                return principal;
+            }
+        }
+
+        String domain = request.getContextPath();
+        String authHeader = getAuthenticateHeader(domain, false);
+        
+        response.setHeader("WWW-Authenticate", authHeader);
+        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+        return null;
+    }
+    
+    
+    /** Generate the auth header
+     */
+    protected String getAuthenticateHeader(String domain,
+                                           boolean stale) {
+
+        long currentTime = System.currentTimeMillis();
+        return "";
+    }
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    public Principal authenticate(final String username, String clientDigest,
+                                  String nOnce, String nc, String cnonce,
+                                  String qop, String realm,
+                                  String md5a2) {
+
+        
+        String serverDigest = null;
+
+        return null;
+    }
+
+
+    @Override
+    public boolean isUserInRole(HttpServletRequest req, Principal p, String role) {
+        return false;
+    }
+    
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/FormAuthentication.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/FormAuthentication.java
new file mode 100644 (file)
index 0000000..d02908f
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * 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.tomcat.servlets.sec;
+
+
+import java.io.IOException;
+import java.security.Principal;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.addons.UserAuthentication;
+
+
+/**
+ * Implements Form authentication.
+ */
+public class FormAuthentication implements UserAuthentication {
+
+    private static Log log = LogFactory.getLog(FormAuthentication.class);
+    String realm;
+    String url;
+    
+    @Override
+    public Principal authenticate(HttpServletRequest request,
+                                  HttpServletResponse response,
+                                  String requestedMethod) throws IOException {
+            
+        if (realm == null)
+            realm = request.getServerName() + ":" + request.getServerPort();
+
+        // Validate any credentials already included with this request
+        String authorization = request.getHeader("authorization");
+        if (authorization != null) {
+            Principal principal = null; // processDigestHeader(request, authorization);
+            if (principal != null) {
+                return principal;
+            }
+        }
+
+        String domain = request.getContextPath();
+        String authHeader = getAuthenticateHeader(domain, false);
+        
+        response.setHeader("WWW-Authenticate", authHeader);
+        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+        return null;
+    }
+    
+    
+    /** Generate the auth header
+     */
+    protected String getAuthenticateHeader(String domain,
+                                           boolean stale) {
+
+        long currentTime = System.currentTimeMillis();
+        return "";
+    }
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    public Principal authenticate(final String username, String clientDigest,
+                                  String nOnce, String nc, String cnonce,
+                                  String qop, String realm,
+                                  String md5a2) {
+
+        
+        String serverDigest = null;
+
+        return null;
+    }
+
+
+    @Override
+    public boolean isUserInRole(HttpServletRequest req, Principal p, String role) {
+        return false;
+    }
+    
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/IPFilter.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/IPFilter.java
new file mode 100644 (file)
index 0000000..fd032a2
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright 1999-2001,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.servlets.sec;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Reimplementation of catalina IP valve.
+ * 
+ */
+public class IPFilter implements Filter {
+    /**
+     * The set of <code>allow</code> regular expressions we will evaluate.
+     */
+    protected Pattern allows[] = new Pattern[0];
+
+
+    /**
+     * The set of <code>deny</code> regular expressions we will evaluate.
+     */
+    protected Pattern denies[] = new Pattern[0];
+
+    // --------------------------------------------------------- Public Methods
+
+    public void setAllows(String pattern) {
+        allows = getPatterns(pattern);
+    }
+    
+    public void setDenies(String pattern) {
+        denies = getPatterns(pattern);
+    }
+    
+    private Pattern[] getPatterns(String pattern) {
+        String[] patSplit = pattern.split(",");
+        ArrayList allowsAL = new ArrayList();
+        for( int i=0; i<patSplit.length; i++) {
+            patSplit[i] = patSplit[i].trim();
+            if (!patSplit[i].equals("")) {
+                allowsAL.add(Pattern.compile(patSplit[i]));
+            }
+        }
+        // TODO
+        Pattern[] result = new Pattern[allowsAL.size()];
+        allowsAL.toArray(result);
+        return result;
+    }
+    
+    public void destroy() {
+    }
+
+
+    public void doFilter(ServletRequest request, 
+                         ServletResponse servletResponse, 
+                         FilterChain chain) 
+            throws IOException, ServletException {
+        
+        HttpServletResponse response = (HttpServletResponse)servletResponse;
+        String property = request.getRemoteAddr();
+        
+        // Check the deny patterns, if any
+        for (int i = 0; i < denies.length; i++) {
+            if (denies[i].matcher(property).matches()) {
+                response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                return;
+            }
+        }
+
+        // Check the allow patterns, if any
+        for (int i = 0; i < allows.length; i++) {
+            if (allows[i].matcher(property).matches()) {
+                chain.doFilter(request, response);
+                return;
+            }
+        }
+
+        // Allow if denies specified but not allows
+        if ((denies.length > 0) && (allows.length == 0)) {
+            chain.doFilter(request, response);
+            return;
+        }
+
+        // Deny this request
+        response.sendError(HttpServletResponse.SC_FORBIDDEN);
+    }
+
+
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/SimpleUserAuthDB.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/SimpleUserAuthDB.java
new file mode 100644 (file)
index 0000000..a216b82
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright 1999-2001,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.servlets.sec;
+
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Properties;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/** 
+ * Load user/passwords from a file.
+ * 
+ * @author Costin Manolache
+ */
+public class SimpleUserAuthDB implements UserDB {
+    
+    private static final String USER_PREFIX = "u.";
+    private static final String ROLE_PREFIX = "r.";
+    
+    HashMap users = new HashMap();
+    HashMap roles = new HashMap();
+    
+    boolean hasMessageDigest = false;
+    String realm = null;
+    
+    public void addUser(String name, String pass) {
+        users.put(name, pass);
+    }
+    
+    public void addRole(String user, String value) {
+        String[] userRoles = value.split(",");
+        roles.put(user, userRoles);
+    }
+    
+    public void setFilename(String fileName) {
+        
+    }
+    
+    public void init(Properties p) throws ServletException {
+    }
+    
+    public void init(ServletConfig servletConfig) throws ServletException {
+        Enumeration names = servletConfig.getInitParameterNames();
+        while (names.hasMoreElements()) {
+            String name = (String)names.nextElement();
+            String value = servletConfig.getInitParameter(name);
+            if (name.startsWith(USER_PREFIX)) {
+                addUser(name.substring(USER_PREFIX.length()), value);
+            }
+            if (name.startsWith(ROLE_PREFIX)) {
+                addRole(name.substring(ROLE_PREFIX.length()), value);
+            }
+        }
+    }
+
+    public void checkAuth(String method, String cookie) {
+        
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/UserDB.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/sec/UserDB.java
new file mode 100644 (file)
index 0000000..19ae39d
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ */
+package org.apache.tomcat.servlets.sec;
+
+/**
+ * Interface to a password/role storage, used by the classes in
+ * this directory. 
+ * 
+ * @author Costin Manolache
+ */
+public interface UserDB {
+
+    //public void auth(String user, String md5);
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/session/HttpSessionImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/session/HttpSessionImpl.java
new file mode 100644 (file)
index 0000000..4c73f52
--- /dev/null
@@ -0,0 +1,784 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.servlets.session;
+
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.apache.tomcat.servlets.util.Enumerator;
+
+/**
+ * Standard implementation of the <b>Session</b> interface.  
+ * 
+ * This is a minimal, non-serializable HttpSession. You can use a different 
+ * session manager, but you should keep in mind that persistent or distributed
+ * sessions are a bad thing. 
+ * 
+ * The session is best for caching data across requests, and tracking the 
+ * user flow. For any data you don't want to lose or is worth preserving - 
+ * use a transaction manager, or any form of storage that provides the 
+ * set of ACID characteristics you need. 
+ * 
+ * Even the most sophisticated sessions managers can't guarantee data integrity
+ * in 100% of cases, and can't notify you of the cases where a replication 
+ * failed. Using such a manager might fool users into making incorrect 
+ * assumptions. Computers and networks do crash at random points, and all
+ * the theory on transactions exists for a good reason.
+ * 
+ * Note: this is a user-space implementation, i.e. this can be used in any
+ * container by using the WebappSessionManager class.
+ *
+ * @author Costin Manolache - removed most of the code
+ * @author Craig R. McClanahan
+ * @author Sean Legassick
+ * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
+ */
+public class HttpSessionImpl  implements HttpSession, Serializable {
+
+    /**
+     * Type array, used as param to toArray()
+     */
+    protected static final String EMPTY_ARRAY[] = new String[0];
+
+    /**
+     * The HTTP session context associated with this session.
+     */
+    protected HttpSessionContext sessionContext = null;
+
+
+    /**
+     * The Manager with which this Session is associated.
+     */
+    protected transient SimpleSessionManager manager = null;
+
+    /**
+     * The session identifier of this Session.
+     */
+    protected String id = null;
+
+    /**
+     * The collection of user data attributes associated with this Session.
+     */
+    protected Map attributes = new HashMap();
+
+    /**
+     * The time this session was created, in milliseconds since midnight,
+     * January 1, 1970 GMT.
+     */
+    protected long creationTime = 0L;
+
+    /**
+     * The last accessed time for this Session.
+     */
+    protected long lastAccessedTime = creationTime;
+
+    /**
+     * The current accessed time for this session.
+     */
+    protected long thisAccessedTime = creationTime;
+
+    /**
+     * The maximum time interval, in seconds, between client requests before
+     * the servlet container may invalidate this session.  A negative time
+     * indicates that the session should never time out.
+     */
+    protected int maxInactiveInterval = -1;
+
+    /**
+     * The access count for this session - how many requests are using this
+     * session ( so we can prevent expiry )
+     */
+    protected transient int accessCount = 0;
+
+    /**
+     * We are currently processing a session expiration, so bypass
+     * certain IllegalStateException tests.  
+     */
+    protected transient boolean expiring = false;
+
+
+    /**
+     * Flag indicating whether this session is new or not.
+     */
+    protected boolean isNew = false;
+
+
+    /**
+     * Flag indicating whether this session is valid or not.
+     */
+    protected boolean isValid = false;
+
+
+    /** Only the manager can create sessions, so it knows about them.
+     */
+    HttpSessionImpl(SimpleSessionManager manager) {
+        this.manager = manager;
+    }
+
+    // ----------   API methods ---------
+
+    /**
+     * Return the session identifier for this session.
+     */
+    public String getId() {
+        return this.id;
+    }
+
+    /**
+     * Return the last time the client sent a request associated with this
+     * session, as the number of milliseconds since midnight, January 1, 1970
+     * GMT.  Actions that your application takes, such as getting or setting
+     * a value associated with the session, do not affect the access time.
+     */
+    public long getLastAccessedTime() {
+        checkValid();
+         return this.lastAccessedTime;
+
+    }
+
+    /**
+     * Return the maximum time interval, in seconds, between client requests
+     * before the servlet container will invalidate the session.  A negative
+     * time indicates that the session should never time out.
+     */
+    public int getMaxInactiveInterval() {
+        return this.maxInactiveInterval;
+    }
+
+
+    /**
+     * Set the maximum time interval, in seconds, between client requests
+     * before the servlet container will invalidate the session.  A negative
+     * time indicates that the session should never time out.
+     *
+     * @param interval The new maximum interval
+     */
+    public void setMaxInactiveInterval(int interval) {
+        this.maxInactiveInterval = interval;
+        if (isValid && interval == 0) {
+            expire();
+        }
+    }
+
+    /**
+     * Return the time when this session was created, in milliseconds since
+     * midnight, January 1, 1970 GMT.
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    public long getCreationTime() {
+        checkValid();
+        return this.creationTime;
+
+    }
+
+    public ServletContext getServletContext() {
+        if (manager == null)
+            return null; // Should never happen
+        ServletContext context = (ServletContext)manager.getContext();
+        return context;
+    }
+
+
+    public HttpSessionContext getSessionContext() {
+        if (sessionContext == null)
+            sessionContext = new StandardSessionContext();
+        return (sessionContext);
+    }
+
+    /**
+     * Return the object bound with the specified name in this session, or
+     * <code>null</code> if no object is bound with that name.
+     *
+     * @param name Name of the attribute to be returned
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    public Object getAttribute(String name) {
+        checkValid();
+        return attributes.get(name);
+    }
+
+
+    /**
+     * Return an <code>Enumeration</code> of <code>String</code> objects
+     * containing the names of the objects bound to this session.
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    public Enumeration getAttributeNames() {
+        checkValid();
+        return new Enumerator(attributes.keySet(), true);
+    }
+
+
+    /**
+     * Return the object bound with the specified name in this session, or
+     * <code>null</code> if no object is bound with that name.
+     *
+     * @param name Name of the value to be returned
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     *
+     * @deprecated As of Version 2.2, this method is replaced by
+     *  <code>getAttribute()</code>
+     */
+    public Object getValue(String name) {
+        return (getAttribute(name));
+    }
+
+
+    /**
+     * Return the set of names of objects bound to this session.  If there
+     * are no such objects, a zero-length array is returned.
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     *
+     * @deprecated As of Version 2.2, this method is replaced by
+     *  <code>getAttributeNames()</code>
+     */
+    public String[] getValueNames() {
+        checkValid();
+        return keys(); // same, but no check for validity
+    }
+
+
+    /**
+     * Invalidates this session and unbinds any objects bound to it.
+     *
+     * @exception IllegalStateException if this method is called on
+     *  an invalidated session
+     */
+    public void invalidate() {
+        checkValid();
+        expire();
+    }
+
+
+    /**
+     * Return <code>true</code> if the client does not yet know about the
+     * session, or if the client chooses not to join the session.  For
+     * example, if the server used only cookie-based sessions, and the client
+     * has disabled the use of cookies, then a session would be new on each
+     * request.
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    public boolean isNew() {
+        checkValid();
+        return (this.isNew);
+    }
+
+    public void putValue(String name, Object value) {
+        setAttribute(name, value);
+    }
+
+    public void removeAttribute(String name) {
+        checkValid();
+        removeAttributeInternal(name, true);
+    }
+
+    public void removeValue(String name) {
+        removeAttribute(name);
+    }
+
+
+    /**
+     * Bind an object to this session, using the specified name.  If an object
+     * of the same name is already bound to this session, the object is
+     * replaced.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>valueBound()</code> on the object.
+     *
+     * @param name Name to which the object is bound, cannot be null
+     * @param value Object to be bound, cannot be null
+     *
+     * @exception IllegalArgumentException if an attempt is made to add a
+     *  non-serializable object in an environment marked distributable.
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    public void setAttribute(String name, Object value) {
+        // Name cannot be null
+        if (name == null) return;
+
+        // Null value is the same as removeAttribute()
+        if (value == null) {
+            removeAttribute(name);
+            return;
+        }
+
+        checkValid();
+        
+        if ((manager != null) && manager.getDistributable() &&
+          !(value instanceof Serializable))
+            throw new IllegalArgumentException("setAttribute() not serializable");
+
+        // Construct an event with the new value
+        HttpSessionBindingEvent event = null;
+
+        // Call the valueBound() method if necessary
+        if (value instanceof HttpSessionBindingListener) {
+            // Don't call any notification if replacing with the same value
+            Object oldValue = attributes.get(name);
+            if (value != oldValue) {
+                event = new HttpSessionBindingEvent(getSession(), name, value);
+                try {
+                    ((HttpSessionBindingListener) value).valueBound(event);
+                } catch (Throwable t){
+                    manager.log.error("Listener valueBound() error", t); 
+                }
+            }
+        }
+
+        // Replace or add this attribute
+        Object unbound = attributes.put(name, value);
+
+        // Call the valueUnbound() method if necessary
+        if ((unbound != null) && (unbound != value) &&
+            (unbound instanceof HttpSessionBindingListener)) {
+            try {
+                ((HttpSessionBindingListener) unbound).valueUnbound
+                    (new HttpSessionBindingEvent(getSession(), name));
+            } catch (Throwable t) {
+                manager.log.error("Listener valueUnbound()", t);
+            }
+        }
+
+        // Notify interested application event listeners
+        ServletContext context = manager.getContext();
+        List listeners = manager.getEventListeners();
+        if (listeners.size() == 0)
+            return;
+        for (int i = 0; i < listeners.size(); i++) {
+            if (!(listeners.get(i) instanceof HttpSessionAttributeListener))
+                continue;
+            HttpSessionAttributeListener listener =
+                (HttpSessionAttributeListener) listeners.get(i);
+            try {
+                if (unbound != null) {
+                    if (event == null) {
+                        event = new HttpSessionBindingEvent
+                            (getSession(), name, unbound);
+                    }
+                    listener.attributeReplaced(event);
+                } else {
+                    if (event == null) {
+                        event = new HttpSessionBindingEvent
+                            (getSession(), name, value);
+                    }
+                    listener.attributeAdded(event);
+                }
+            } catch (Throwable t) {
+                manager.log.error("Listener attibuteAdded/Replaced()", t);
+            }
+        }
+
+    }
+
+    // -------- Implementation - interactions with SessionManager -----
+    
+    /**
+     * Set the creation time for this session.  This method is called by the
+     * Manager when an existing Session instance is reused.
+     */
+    public void setCreationTime(long time) {
+        this.creationTime = time;
+        this.lastAccessedTime = time;
+        this.thisAccessedTime = time;
+    }
+
+    /**
+     * Set the session identifier for this session and notify listeners about
+     * new session
+     *
+     * @param id The new session identifier
+     */
+    public void setId(String id) {
+
+        if ((this.id != null) && (manager != null))
+            manager.remove(this);
+
+        this.id = id;
+
+        if (manager != null)
+            manager.add(this);
+        tellNew();
+    }
+
+
+    /**
+     * Inform the listeners about the new session.
+     */
+    public void tellNew() {
+        // Notify interested application event listeners
+        ServletContext context = manager.getContext();
+        List listeners = manager.getEventListeners();
+        if (listeners.size() > 0) {
+            HttpSessionEvent event =
+                new HttpSessionEvent(getSession());
+            for (int i = 0; i < listeners.size(); i++) {
+                Object listenerObj = listeners.get(i);
+                if (!(listenerObj instanceof HttpSessionListener))
+                    continue;
+                HttpSessionListener listener =
+                    (HttpSessionListener) listenerObj;
+                try {
+                    listener.sessionCreated(event);
+                } catch (Throwable t) {
+                    manager.log.error("listener.sessionCreated()", t);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Return the Manager within which this Session is valid.
+     */
+    public SimpleSessionManager getManager() {
+        return (this.manager);
+    }
+
+
+
+    /**
+     * Set the <code>isNew</code> flag for this session.
+     *
+     * @param isNew The new value for the <code>isNew</code> flag
+     */
+    public void setNew(boolean isNew) {
+        this.isNew = isNew;
+    }
+
+    public HttpSession getSession() {
+        return this;
+    }
+
+    private void checkValid() {
+        if ( !isValid() ) {
+            throw new IllegalStateException("checkValid");
+        }
+    }
+
+    /**
+     * Return the <code>isValid</code> flag for this session.
+     */
+    public boolean isValid() {
+        if (this.expiring) {
+            return true;
+        }
+        if (!this.isValid ) {
+            return false;
+        }
+        if (accessCount > 0) {
+            return true;
+        }
+        if (maxInactiveInterval >= 0) { 
+            long timeNow = System.currentTimeMillis();
+            int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
+            if (timeIdle >= maxInactiveInterval) {
+                expire(true);
+            }
+        }
+        return this.isValid;
+    }
+
+    public void setValid(boolean isValid) {
+        this.isValid = isValid;
+    }
+
+    /**
+     * Update the accessed time information for this session.  This method
+     * should be called by the context when a request comes in for a particular
+     * session, even if the application does not reference it.
+     */
+    public void access() {
+        this.lastAccessedTime = this.thisAccessedTime;
+        this.thisAccessedTime = System.currentTimeMillis();
+
+        evaluateIfValid();
+        accessCount++;
+    }
+
+
+    /**
+     * End the access.
+     */
+    public void endAccess() {
+        isNew = false;
+        accessCount--;
+    }
+
+    /**
+     * Perform the internal processing required to invalidate this session,
+     * without triggering an exception if the session has already expired.
+     */
+    public void expire() {
+        expire(true);
+    }
+
+
+    /**
+     * Perform the internal processing required to invalidate this session,
+     * without triggering an exception if the session has already expired.
+     *
+     * @param notify Should we notify listeners about the demise of
+     *  this session?
+     */
+    public void expire(boolean notify) {
+
+        // Mark this session as "being expired" if needed
+        if (expiring)
+            return;
+
+        synchronized (this) {
+
+            if (manager == null)
+                return;
+
+            expiring = true;
+        
+            // Notify interested application event listeners
+            // FIXME - Assumes we call listeners in reverse order
+            ServletContext context = manager.getContext();
+            List listeners = manager.getEventListeners();
+            if (notify && (listeners.size() > 0)) {
+                HttpSessionEvent event =
+                    new HttpSessionEvent(getSession());
+                for (int i = 0; i < listeners.size(); i++) {
+                    Object listenerObj = listeners.get(i);
+                    int j = (listeners.size() - 1) - i;
+                    if (!(listenerObj instanceof HttpSessionListener))
+                        continue;
+                    HttpSessionListener listener =
+                        (HttpSessionListener) listenerObj;
+                    try {
+                        listener.sessionDestroyed(event);
+                    } catch (Throwable t) {
+                        manager.log.error("listener.sessionDestroyed", t);
+                    }
+                }
+            }
+            accessCount = 0;
+            isValid = false;
+
+            /*
+             * Compute how long this session has been alive, and update
+             * session manager's related properties accordingly
+             */
+            long timeNow = System.currentTimeMillis();
+            int timeAlive = (int) ((timeNow - creationTime)/1000);
+            manager.addExpiredSession(timeAlive);
+
+            // Remove this session from our manager's active sessions
+            manager.remove(this);
+
+            expiring = false;
+
+            // Unbind any objects associated with this session
+            String keys[] = keys();
+            for (int i = 0; i < keys.length; i++)
+                removeAttributeInternal(keys[i], notify);
+        }
+    }
+
+
+    /**
+     * Release all object references, and initialize instance variables, in
+     * preparation for reuse of this object.
+     */
+    public void recycle() {
+
+        // Reset the instance variables associated with this Session
+        attributes.clear();
+        creationTime = 0L;
+        expiring = false;
+        id = null;
+        lastAccessedTime = 0L;
+        maxInactiveInterval = -1;
+        accessCount = 0;
+        isNew = false;
+        isValid = false;
+        manager = null;
+
+    }
+
+    /**
+     * Return a string representation of this object.
+     */
+    public String toString() {
+
+        StringBuffer sb = new StringBuffer();
+        sb.append("StandardSession[");
+        sb.append(id);
+        sb.append("]");
+        return (sb.toString());
+    }
+
+    protected void evaluateIfValid() {
+        /*
+         * If this session has expired or is in the process of expiring or
+         * will never expire, return
+         */
+        if (!this.isValid || expiring || maxInactiveInterval < 0)
+            return;
+
+        isValid();
+
+    }
+              
+    /**
+     * Return the names of all currently defined session attributes
+     * as an array of Strings.  If there are no defined attributes, a
+     * zero-length array is returned.
+     */
+    protected String[] keys() {
+        return ((String[]) attributes.keySet().toArray(EMPTY_ARRAY));
+    }
+
+
+    /**
+     * Remove the object bound with the specified name from this session.  If
+     * the session does not have an object bound with this name, this method
+     * does nothing.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>valueUnbound()</code> on the object.
+     *
+     * @param name Name of the object to remove from this session.
+     * @param notify Should we notify interested listeners that this
+     *  attribute is being removed?
+     */
+    protected void removeAttributeInternal(String name, boolean notify) {
+        // Remove this attribute from our collection
+        Object value = attributes.remove(name);
+
+        // Do we need to do valueUnbound() and attributeRemoved() notification?
+        if (!notify || (value == null)) {
+            return;
+        }
+
+        // Call the valueUnbound() method if necessary
+        HttpSessionBindingEvent event = null;
+        if (value instanceof HttpSessionBindingListener) {
+            event = new HttpSessionBindingEvent(getSession(), name, value);
+            ((HttpSessionBindingListener) value).valueUnbound(event);
+        }
+
+        // Notify interested application event listeners
+        ServletContext context = manager.getContext();
+        List listeners = manager.getEventListeners();
+        if (listeners.size() == 0)
+            return;
+        for (int i = 0; i < listeners.size(); i++) {
+            if (!(listeners.get(i) instanceof HttpSessionAttributeListener))
+                continue;
+            HttpSessionAttributeListener listener =
+                (HttpSessionAttributeListener) listeners.get(i);
+            try {
+                if (event == null) {
+                    event = new HttpSessionBindingEvent
+                        (getSession(), name, value);
+                }
+                listener.attributeRemoved(event);
+            } catch (Throwable t) {
+                manager.log.error("listener.attributeRemoved", t);
+            }
+        }
+
+    }
+    
+}
+
+
+// ------------------------------------------------------------ Protected Class
+
+
+/**
+ * This class is a dummy implementation of the <code>HttpSessionContext</code>
+ * interface, to conform to the requirement that such an object be returned
+ * when <code>HttpSession.getSessionContext()</code> is called.
+ *
+ * @author Craig R. McClanahan
+ *
+ * @deprecated As of Java Servlet API 2.1 with no replacement.  The
+ *  interface will be removed in a future version of this API.
+ */
+
+final class StandardSessionContext implements HttpSessionContext {
+
+
+    protected HashMap dummy = new HashMap();
+
+    /**
+     * Return the session identifiers of all sessions defined
+     * within this context.
+     *
+     * @deprecated As of Java Servlet API 2.1 with no replacement.
+     *  This method must return an empty <code>Enumeration</code>
+     *  and will be removed in a future version of the API.
+     */
+    public Enumeration getIds() {
+
+        return (new Enumerator(dummy));
+
+    }
+
+
+    /**
+     * Return the <code>HttpSession</code> associated with the
+     * specified session identifier.
+     *
+     * @param id Session identifier for which to look up a session
+     *
+     * @deprecated As of Java Servlet API 2.1 with no replacement.
+     *  This method must return null and will be removed in a
+     *  future version of the API.
+     */
+    public HttpSession getSession(String id) {
+
+        return (null);
+
+    }
+
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/session/RandomGenerator.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/session/RandomGenerator.java
new file mode 100644 (file)
index 0000000..a4a6921
--- /dev/null
@@ -0,0 +1,352 @@
+package org.apache.tomcat.servlets.session;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Random;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ *  Generates random IDs, useable as cookies.
+ * 
+ *  Based on code from tomcat session manager - but general purpose.
+ *  Can use /dev/urandom or similar file.
+ *  
+ * 
+ */
+public class RandomGenerator {
+    protected DataInputStream randomIS=null;
+    protected String devRandomSource="/dev/urandom";
+
+    protected static Log log = LogFactory.getLog(RandomGenerator.class);
+
+    /**
+     * The message digest algorithm to be used when generating session
+     * identifiers.  This must be an algorithm supported by the
+     * <code>java.security.MessageDigest</code> class on your platform.
+     */
+    protected String algorithm = "MD5";
+
+    /**
+     * The session id length of Sessions created by this Manager.
+     */
+    protected int sessionIdLength = 16;
+
+    
+    /**
+     * Return the MessageDigest implementation to be used when
+     * creating session identifiers.
+     */
+    protected MessageDigest digest = null;
+
+    public String jvmRoute;
+
+    /**
+     * A String initialization parameter used to increase the entropy of
+     * the initialization of our random number generator.
+     */
+    protected String entropy = null;
+
+    /**
+     * A random number generator to use when generating session identifiers.
+     */
+    protected Random random = null;
+
+    /**
+     * Return the message digest algorithm for this Manager.
+     */
+    public String getAlgorithm() {
+        return (this.algorithm);
+    }
+    
+    public void init() {
+        // Initialize random number generation
+        getRandomBytes(new byte[16]);
+    }
+
+
+    /**
+     * Set the message digest algorithm for this Manager.
+     *
+     * @param algorithm The new message digest algorithm
+     */
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+    
+    /**
+     * Return the MessageDigest object to be used for calculating
+     * session identifiers.  If none has been created yet, initialize
+     * one the first time this method is called.
+     */
+    public synchronized MessageDigest getDigest() {
+
+        if (this.digest == null) {
+            long t1=System.currentTimeMillis();
+            try {
+                this.digest = MessageDigest.getInstance(algorithm);
+            } catch (NoSuchAlgorithmException e) {
+                log.error("Algorithm not found", e);
+                try {
+                    this.digest = MessageDigest.getInstance("MD5");
+                } catch (NoSuchAlgorithmException f) {
+                    log.error("No message digest available", f);
+                    this.digest = null;
+                }
+            }
+            long t2=System.currentTimeMillis();
+            if( log.isDebugEnabled() )
+                log.debug("getDigest() " + (t2-t1));
+        }
+
+        return (this.digest);
+
+    }
+    
+    /**
+     * Generate and return a new session identifier.
+     */
+    public synchronized String generateSessionId() {
+
+        byte random[] = new byte[16];
+        String result = null;
+
+        // Render the result as a String of hexadecimal digits
+        StringBuffer buffer = new StringBuffer();
+        int resultLenBytes = 0;
+
+        while (resultLenBytes < this.sessionIdLength) {
+            getRandomBytes(random);
+            random = getDigest().digest(random);
+            for (int j = 0;
+            j < random.length && resultLenBytes < this.sessionIdLength;
+            j++) {
+                byte b1 = (byte) ((random[j] & 0xf0) >> 4);
+                byte b2 = (byte) (random[j] & 0x0f);
+                if (b1 < 10)
+                    buffer.append((char) ('0' + b1));
+                else
+                    buffer.append((char) ('A' + (b1 - 10)));
+                if (b2 < 10)
+                    buffer.append((char) ('0' + b2));
+                else
+                    buffer.append((char) ('A' + (b2 - 10)));
+                resultLenBytes++;
+            }
+        }
+        if (jvmRoute != null) {
+            buffer.append('.').append(jvmRoute);
+        }
+        result = buffer.toString();
+        return (result);
+
+    }
+    
+    protected void getRandomBytes(byte bytes[]) {
+        // Generate a byte array containing a session identifier
+        if (devRandomSource != null && randomIS == null) {
+            setRandomFile(devRandomSource);
+        }
+        if (randomIS != null) {
+            try {
+                int len = randomIS.read(bytes);
+                if (len == bytes.length) {
+                    return;
+                }
+                if(log.isDebugEnabled())
+                    log.debug("Got " + len + " " + bytes.length );
+            } catch (Exception ex) {
+                // Ignore
+            }
+            devRandomSource = null;
+            
+            try {
+                randomIS.close();
+            } catch (Exception e) {
+                log.warn("Failed to close randomIS.");
+            }
+            
+            randomIS = null;
+        }
+        getRandom().nextBytes(bytes);
+    }
+
+    /**
+     * Return the random number generator instance we should use for
+     * generating session identifiers.  If there is no such generator
+     * currently defined, construct and seed a new one.
+     */
+    public Random getRandom() {
+        if (this.random == null) {
+            // Calculate the new random number generator seed
+            long seed = System.currentTimeMillis();
+            long t1 = seed;
+            char entropy[] = getEntropy().toCharArray();
+            for (int i = 0; i < entropy.length; i++) {
+                long update = ((byte) entropy[i]) << ((i % 8) * 8);
+                seed ^= update;
+            }
+            try {
+                // Construct and seed a new random number generator
+                Class clazz = Class.forName(randomClass);
+                this.random = (Random) clazz.newInstance();
+                this.random.setSeed(seed);
+            } catch (Exception e) {
+                // Fall back to the simple case
+                log.error("Failed to create random " + randomClass, e);
+                this.random = new java.util.Random();
+                this.random.setSeed(seed);
+            }
+            if(log.isDebugEnabled()) {
+                long t2=System.currentTimeMillis();
+                if( (t2-t1) > 100 )
+                    log.debug("Init random: " + " " + (t2-t1));
+            }
+        }
+        
+        return (this.random);
+
+    }
+    
+    /**
+     * Return the entropy increaser value, or compute a semi-useful value
+     * if this String has not yet been set.
+     */
+    public String getEntropy() {
+
+        // Calculate a semi-useful value if this has not been set
+        if (this.entropy == null) {
+            // Use APR to get a crypto secure entropy value
+            byte[] result = new byte[32];
+            boolean apr = false;
+            try {
+                String methodName = "random";
+                Class paramTypes[] = new Class[2];
+                paramTypes[0] = result.getClass();
+                paramTypes[1] = int.class;
+                Object paramValues[] = new Object[2];
+                paramValues[0] = result;
+                paramValues[1] = new Integer(32);
+                Method method = Class.forName("org.apache.tomcat.jni.OS")
+                    .getMethod(methodName, paramTypes);
+                method.invoke(null, paramValues);
+                apr = true;
+            } catch (Throwable t) {
+                // Ignore
+            }
+            if (apr) {
+                setEntropy(new String(result));
+            } else {
+                setEntropy(this.toString());
+            }
+        }
+
+        return (this.entropy);
+
+    }
+
+
+    /**
+     * Set the entropy increaser value.
+     *
+     * @param entropy The new entropy increaser value
+     */
+    public void setEntropy(String entropy) {
+        this.entropy = entropy;
+    }
+
+
+    /**
+     * Return the random number generator class name.
+     */
+    public String getRandomClass() {
+
+        return (this.randomClass);
+
+    }
+
+
+    /**
+     * Set the random number generator class name.
+     *
+     * @param randomClass The new random number generator class name
+     */
+    public void setRandomClass(String randomClass) {
+        this.randomClass = randomClass;
+    }
+    
+    /**
+     * The Java class name of the random number generator class to be used
+     * when generating session identifiers.
+     */
+    protected String randomClass = "java.security.SecureRandom";
+    /** 
+     * Use /dev/random-type special device. This is new code, but may reduce
+     * the big delay in generating the random.
+     *
+     *  You must specify a path to a random generator file. Use /dev/urandom
+     *  for linux ( or similar ) systems. Use /dev/random for maximum security
+     *  ( it may block if not enough "random" exist ). You can also use
+     *  a pipe that generates random.
+     *
+     *  The code will check if the file exists, and default to java Random
+     *  if not found. There is a significant performance difference, very
+     *  visible on the first call to getSession ( like in the first JSP )
+     *  - so use it if available.
+     */
+    public void setRandomFile( String s ) {
+        // as a hack, you can use a static file - and genarate the same
+        // session ids ( good for strange debugging )
+        try{
+            devRandomSource=s;
+            File f=new File( devRandomSource );
+            if( ! f.exists() ) return;
+            randomIS= new DataInputStream( new FileInputStream(f));
+            randomIS.readLong();
+            if( log.isDebugEnabled() )
+                log.debug( "Opening " + devRandomSource );
+        } catch( IOException ex ) {
+            try {
+                randomIS.close();
+            } catch (Exception e) {
+                log.warn("Failed to close randomIS.");
+            }
+            
+            randomIS=null;
+        }
+    }
+
+    public String getRandomFile() {
+        return devRandomSource;
+    }
+
+
+    /**
+     * Gets the session id length (in bytes) of Sessions created by
+     * this Manager.
+     *
+     * @return The session id length
+     */
+    public int getSessionIdLength() {
+
+        return (this.sessionIdLength);
+
+    }
+
+
+    /**
+     * Sets the session id length (in bytes) for Sessions created by this
+     * Manager.
+     *
+     * @param idLength The session id length
+     */
+    public void setSessionIdLength(int idLength) {
+        this.sessionIdLength = idLength;
+    }
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/session/SimpleSessionManager.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/session/SimpleSessionManager.java
new file mode 100644 (file)
index 0000000..72400ea
--- /dev/null
@@ -0,0 +1,545 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.servlets.session;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.addons.UserSessionManager;
+
+// TODO: move 'expiring objects' to a separate utility class
+// TODO: hook the background thread
+
+/**
+ * Minimal implementation of the <b>Manager</b> interface that supports
+ * no session persistence or distributable capabilities.  This class may
+ * be subclassed to create more sophisticated Manager implementations.
+ * 
+ * @author Costin Manolache
+ * @author Craig R. McClanahan
+ */
+public class SimpleSessionManager implements UserSessionManager {
+    protected static Log log = LogFactory.getLog(SimpleSessionManager.class);
+
+    protected RandomGenerator randomG = new RandomGenerator();
+    
+    protected ServletContext context;
+
+
+    /**
+     * The distributable flag for Sessions created by this Manager.  If this
+     * flag is set to <code>true</code>, any user attributes added to a
+     * session controlled by this Manager must be Serializable. 
+     * 
+     * This is for compliance with the spec - tomcat-lite is not intended for
+     * session replication ( use a full version for that )
+     */
+    protected boolean distributable;
+
+    /**
+     * The default maximum inactive interval for Sessions created by
+     * this Manager.
+     */
+    protected int maxInactiveInterval = 60;
+
+    /**
+     * The longest time (in seconds) that an expired session had been alive.
+     */
+    protected int sessionMaxAliveTime;
+
+
+    /**
+     * Average time (in seconds) that expired sessions had been alive.
+     */
+    protected int sessionAverageAliveTime;
+
+
+    /**
+     * Number of sessions that have expired.
+     */
+    protected int expiredSessions = 0;
+
+    static class SessionLRU extends LinkedHashMap {
+        protected boolean removeEldestEntry(Map.Entry eldest) {
+            HttpSessionImpl s = (HttpSessionImpl)eldest.getValue();
+            int size = this.size();
+            
+            // TODO: check if eldest is expired or if we're above the limit.
+            // if eldest is expired, turn a flag to check for more.
+            
+            // Note: this doesn't work well for sessions that set shorter
+            // expiry time, or longer expiry times. 
+            return false;
+        }
+
+    }
+    
+    /**
+     * The set of currently active Sessions for this Manager, keyed by
+     * session identifier.
+     */
+    protected LinkedHashMap sessions = new SessionLRU();
+        
+    // Number of sessions created by this manager
+    protected int sessionCounter=0;
+
+    protected int maxActive=0;
+
+    // number of duplicated session ids - anything >0 means we have problems
+    protected int duplicates=0;
+
+    protected boolean initialized=false;
+    
+    /**
+     * Processing time during session expiration.
+     */
+    protected long processingTime = 0;
+    
+    static List EMPTY_LIST = new ArrayList();
+
+    // One per machine - it has an internal pool, can schedule 
+    // tasks for multiple webapps.
+    static Timer timer = new Timer();
+    boolean active = false;
+    
+    TimerTask task = new TimerTask() {
+        public void run() {
+            processExpires();
+            synchronized (sessions) {
+                // We don't want a timer thread running around if 
+                // there is no activity
+                if (sessions.size() == 0) {
+                    active = false;
+                    this.cancel();
+                }
+            }
+        }
+    };
+
+    public List getEventListeners() {
+        List l = 
+            (List) context.getAttribute("context-listeners");
+        if (l == null) return EMPTY_LIST;
+        return l;
+    }
+    
+    /** 
+     * Total sessions created by this manager.
+     */
+    public int getSessionCounter() {
+        return sessionCounter;
+    }
+
+
+    /** 
+     * Number of duplicated session IDs generated by the random source.
+     * Anything bigger than 0 means problems.
+     *
+     * @return The count of duplicates
+     */
+    public int getDuplicates() {
+        return duplicates;
+    }
+
+
+    /** 
+     * Returns the number of active sessions
+     *
+     * @return number of sessions active
+     */
+    public int getActiveSessions() {
+        return sessions.size();
+    }
+
+
+    /**
+     * Max number of concurrent active sessions
+     *
+     * @return The highest number of concurrent active sessions
+     */
+    public int getMaxActive() {
+        return maxActive;
+    }
+
+
+    public void setMaxActive(int maxActive) {
+        this.maxActive = maxActive;
+    }
+
+
+    /**
+     * Gets the longest time (in seconds) that an expired session had been
+     * alive.
+     *
+     * @return Longest time (in seconds) that an expired session had been
+     * alive.
+     */
+    public int getSessionMaxAliveTime() {
+        return sessionMaxAliveTime;
+    }
+
+    /**
+     * Gets the average time (in seconds) that expired sessions had been
+     * alive.
+     *
+     * @return Average time (in seconds) that expired sessions had been
+     * alive.
+     */
+    public int getSessionAverageAliveTime() {
+        return sessionAverageAliveTime;
+    }
+
+    /**
+     * Return the Container with which this Manager is associated.
+     */
+    public ServletContext getContext() {
+        return (this.context);
+    }
+
+
+    /**
+     * Set the Container with which this Manager is associated.
+     *
+     * @param container The newly associated Container
+     */
+    public void setContext(ServletContext container) {
+        this.context = container;
+    }
+
+    /**
+     * Return the distributable flag for the sessions supported by
+     * this Manager.
+     */
+    public boolean getDistributable() {
+        return (this.distributable);
+    }
+
+    /**
+     * Set the distributable flag for the sessions supported by this
+     * Manager.  If this flag is set, all user data objects added to
+     * sessions associated with this manager must implement Serializable.
+     *
+     * @param distributable The new distributable flag
+     */
+    public void setDistributable(boolean distributable) {
+        this.distributable = distributable;
+    }
+
+    /**
+     * Return the default maximum inactive interval (in seconds)
+     * for Sessions created by this Manager.
+     */
+    public int getSessionTimeout() {
+        return (this.maxInactiveInterval);
+    }
+
+    public void setSessionTimeout(int stout) {
+        maxInactiveInterval = stout;
+    }
+
+    /**
+     * Gets the number of sessions that have expired.
+     *
+     * @return Number of sessions that have expired
+     */
+    public int getExpiredSessions() {
+        return expiredSessions;
+    }
+
+    /**
+     * Called when a session is expired, add the time to statistics
+     */
+    public void addExpiredSession(int timeAlive) {
+        synchronized (this) {
+            this.expiredSessions++; // should be atomic
+            // not sure it's the best solution
+            sessionAverageAliveTime = 
+                ((sessionAverageAliveTime * (expiredSessions-1)) + 
+                        timeAlive)/expiredSessions;
+            if (timeAlive > sessionMaxAliveTime) {
+                sessionMaxAliveTime = timeAlive;
+            }
+        }
+    }
+
+    public long getProcessingTime() {
+        return processingTime;
+    }
+
+    private void enableTimer() {
+        if (active) {
+            return;
+        }
+        active = true;
+        timer.scheduleAtFixedRate(task, getSessionTimeout(), getSessionTimeout());
+    }
+
+    
+    
+    /**
+     * Invalidate all sessions that have expired.
+     */
+    public void processExpires() {
+
+        long timeNow = System.currentTimeMillis();
+        HttpSessionImpl sessions[] = findSessions();
+        int expireHere = 0 ;
+        
+        if(log.isDebugEnabled())
+            log.debug("Start expire sessions "  + " at " + timeNow + " sessioncount " + sessions.length);
+        
+        for (int i = 0; i < sessions.length; i++) {
+            if (!sessions[i].isValid()) {
+                expiredSessions++;
+                expireHere++;
+            }
+        }
+        
+        long timeEnd = System.currentTimeMillis();
+        if(log.isDebugEnabled())
+             log.debug("End expire sessions " + " processingTime " + 
+                       (timeEnd - timeNow) + " expired sessions: " + expireHere);
+        
+        processingTime += ( timeEnd - timeNow );
+
+    }
+
+    public SimpleSessionManager() {
+        randomG.init();        
+    }
+    
+    public void destroy() {
+        initialized=false;
+    }
+
+    /**
+     * Add this Session to the set of active Sessions for this Manager.
+     *
+     * @param session Session to be added
+     */
+    public void add(HttpSessionImpl session) {
+        synchronized (sessions) {
+            sessions.put(session.getId(), session);
+            if( sessions.size() > maxActive ) {
+                maxActive=sessions.size();
+            }
+            
+            // Make sure the timer is set.
+            enableTimer();
+        }
+    }
+
+    
+    /**
+     * Construct and return a new session object, based on the default
+     * settings specified by this Manager's properties.  The session
+     * id specified will be used as the session id.  
+     * If a new session cannot be created for any reason, return 
+     * <code>null</code>.
+     * 
+     * @param sessionId The session id which should be used to create the
+     *  new session; if <code>null</code>, a new session id will be
+     *  generated
+     * @exception IllegalStateException if a new session cannot be
+     *  instantiated for any reason
+     */
+    public HttpSessionImpl createSession(String sessionId) {
+        
+        // Recycle or create a Session instance
+        HttpSessionImpl session = createEmptySession();
+
+        // Initialize the properties of the new session and return it
+        session.setNew(true);
+        session.setValid(true);
+        session.setCreationTime(System.currentTimeMillis());
+        session.setMaxInactiveInterval(this.maxInactiveInterval);
+        if (sessionId == null ) {
+            while (sessionId == null) {
+                sessionId = randomG.generateSessionId();
+                if (sessions.get(sessionId) != null) {
+                    duplicates++;
+                    sessionId = null;    
+                }
+            }
+        }
+/*        } else {
+        // FIXME: Code to be used in case route replacement is needed
+            String jvmRoute = randomG.jvmRoute;
+            if (jvmRoute != null) {
+                String requestJvmRoute = null;
+                int index = sessionId.indexOf(".");
+                if (index > 0) {
+                    requestJvmRoute = sessionId
+                            .substring(index + 1, sessionId.length());
+                }
+                if (requestJvmRoute != null && !requestJvmRoute.equals(jvmRoute)) {
+                    sessionId = sessionId.substring(0, index) + "." + jvmRoute;
+                }
+            }
+*/
+        session.setId(sessionId);
+        sessionCounter++;
+        return (session);
+    }
+    
+    
+    /**
+     * Get a session from the recycled ones or create a new empty one.
+     * The PersistentManager manager does not need to create session data
+     * because it reads it from the Store.
+     */
+    public HttpSessionImpl createEmptySession() {
+        return new HttpSessionImpl(this);
+    }
+
+
+    /**
+     * Return the active Session, associated with this Manager, with the
+     * specified session id (if any); otherwise return <code>null</code>.
+     *
+     * @param id The session id for the session to be returned
+     *
+     * @exception IllegalStateException if a new session cannot be
+     *  instantiated for any reason
+     * @exception IOException if an input/output error occurs while
+     *  processing this request
+     */
+    public HttpSessionImpl findSession(String id) throws IOException {
+
+        if (id == null)
+            return (null);
+        synchronized (sessions) {
+            HttpSessionImpl session = (HttpSessionImpl) sessions.get(id);
+            return (session);
+        }
+
+    }
+
+
+    /**
+     * Return the set of active Sessions associated with this Manager.
+     * If this Manager has no active Sessions, a zero-length array is returned.
+     */
+    public HttpSessionImpl[] findSessions() {
+
+        HttpSessionImpl results[] = null;
+        synchronized (sessions) {
+            results = new HttpSessionImpl[sessions.size()];
+            results = (HttpSessionImpl[]) sessions.values().toArray(results);
+        }
+        return (results);
+
+    }
+
+
+    /**
+     * Remove this Session from the active Sessions for this Manager.
+     *
+     * @param session Session to be removed
+     */
+    public void remove(HttpSessionImpl session) {
+
+        synchronized (sessions) {
+            sessions.remove(session.getId());
+        }
+
+    }
+
+    // ------------------------------------------------------ Protected Methods
+
+    /** JMX and debugging
+     */
+    public void expireSession( String sessionId ) {
+        HttpSessionImpl s=(HttpSessionImpl)sessions.get(sessionId);
+        if( s==null ) {
+            return;
+        }
+        s.expire();
+    }
+
+
+    /** JMX method or debugging
+     */
+    public String getLastAccessedTime( String sessionId ) {
+        HttpSessionImpl s=(HttpSessionImpl)sessions.get(sessionId);
+        if( s==null ) {
+            return "";
+        }
+        return new Date(s.getLastAccessedTime()).toString();
+    }
+
+    ThreadLocal httpSession;
+    
+    public String getSessionCookieName() {
+        return "JSESSIONID";
+    }
+    
+    
+    /** Parse the cookies. Since multiple session cookies could be set ( 
+     * different paths for example ), we need to lookup and find a valid one
+     * for our context. 
+     * 
+     * If none is found - the last (bad) session id is returned.
+     * 
+     * As side effect, an attribute is set on the req with the session ( we 
+     * already looked it up while searching ).
+     */
+    public HttpSession getRequestedSessionId(HttpServletRequest req) {
+        Cookie[] cookies = req.getCookies();
+        String cn = getSessionCookieName();
+        for (int i=0; i<cookies.length; i++) {
+            if (cn.equals(cookies[i].getName())) {
+                String id = cookies[i].getValue();
+                // TODO: mark session from cookie, check validity
+            }
+        }
+        return null;
+    }
+    
+    public String getSessionIdFromUrl(HttpServletRequest req) {
+        
+        return null;
+    }
+    
+    
+    public boolean isValid(HttpSession session) {
+      return ((HttpSessionImpl) session).isValid();
+    }
+
+    public void endAccess(HttpSession session) {
+      ((HttpSessionImpl) session).endAccess();
+    }
+
+    public void access(HttpSession session) {
+      ((HttpSessionImpl) session).access();
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Enumerator.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Enumerator.java
new file mode 100644 (file)
index 0000000..e262547
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * 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.tomcat.servlets.util;
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+
+/**
+ * Adapter class that wraps an <code>Enumeration</code> around a Java2
+ * collection classes object <code>Iterator</code> so that existing APIs
+ * returning Enumerations can easily run on top of the new collections.
+ * Constructors are provided to easliy create such wrappers.
+ *
+ * @author Craig R. McClanahan
+ */
+public final class Enumerator implements Enumeration {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Return an Enumeration over the values of the specified Collection.
+     *
+     * @param collection Collection whose values should be enumerated
+     */
+    public Enumerator(Collection collection) {
+
+        this(collection.iterator());
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values of the specified Collection.
+     *
+     * @param collection Collection whose values should be enumerated
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Collection collection, boolean clone) {
+
+        this(collection.iterator(), clone);
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values returned by the
+     * specified Iterator.
+     *
+     * @param iterator Iterator to be wrapped
+     */
+    public Enumerator(Iterator iterator) {
+
+        super();
+        this.iterator = iterator;
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values returned by the
+     * specified Iterator.
+     *
+     * @param iterator Iterator to be wrapped
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Iterator iterator, boolean clone) {
+
+        super();
+        if (!clone) {
+            this.iterator = iterator;
+        } else {
+            List list = new ArrayList();
+            while (iterator.hasNext()) {
+                list.add(iterator.next());
+            }
+            this.iterator = list.iterator();   
+        }
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values of the specified Map.
+     *
+     * @param map Map whose values should be enumerated
+     */
+    public Enumerator(Map map) {
+
+        this(map.values().iterator());
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values of the specified Map.
+     *
+     * @param map Map whose values should be enumerated
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Map map, boolean clone) {
+
+        this(map.values().iterator(), clone);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The <code>Iterator</code> over which the <code>Enumeration</code>
+     * represented by this class actually operates.
+     */
+    private Iterator iterator = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Tests if this enumeration contains more elements.
+     *
+     * @return <code>true</code> if and only if this enumeration object
+     *  contains at least one more element to provide, <code>false</code>
+     *  otherwise
+     */
+    public boolean hasMoreElements() {
+
+        return (iterator.hasNext());
+
+    }
+
+
+    /**
+     * Returns the next element of this enumeration if this enumeration
+     * has at least one more element to provide.
+     *
+     * @return the next element of this enumeration
+     *
+     * @exception NoSuchElementException if no more elements exist
+     */
+    public Object nextElement() throws NoSuchElementException {
+
+        return (iterator.next());
+
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/LocaleParser.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/LocaleParser.java
new file mode 100644 (file)
index 0000000..b3d167c
--- /dev/null
@@ -0,0 +1,393 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.servlets.util;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.TreeMap;
+
+
+/**
+ * Utility class for string parsing that is higher performance than
+ * StringParser for simple delimited text cases.  Parsing is performed
+ * by setting the string, and then using the <code>findXxxx()</code> and
+ * <code>skipXxxx()</code> families of methods to remember significant
+ * offsets.  To retrieve the parsed substrings, call the <code>extract()</code>
+ * method with the appropriate saved offset values.
+ *
+ * @author Craig R. McClanahan
+ */
+public final class LocaleParser {
+
+    public LocaleParser() {
+        this(null);
+    }
+
+    public LocaleParser(String string) {
+        setString(string);
+    }
+
+    public TreeMap parseLocale(String value) {
+      // Store the accumulated languages that have been requested in
+      // a local collection, sorted by the quality value (so we can
+      // add Locales in descending order).  The values will be ArrayLists
+      // containing the corresponding Locales to be added
+      TreeMap locales = new TreeMap();
+
+      // Preprocess the value to remove all whitespace
+      int white = value.indexOf(' ');
+      if (white < 0)
+          white = value.indexOf('\t');
+      if (white >= 0) {
+          StringBuffer sb = new StringBuffer();
+          int len = value.length();
+          for (int i = 0; i < len; i++) {
+              char ch = value.charAt(i);
+              if ((ch != ' ') && (ch != '\t'))
+                  sb.append(ch);
+          }
+          value = sb.toString();
+      }
+
+      LocaleParser parser = this;
+      // Process each comma-delimited language specification
+      parser.setString(value);        // ASSERT: parser is available to us
+      int length = parser.getLength();
+      while (true) {
+
+          // Extract the next comma-delimited entry
+          int start = parser.getIndex();
+          if (start >= length)
+              break;
+          int end = parser.findChar(',');
+          String entry = parser.extract(start, end).trim();
+          parser.advance();   // For the following entry
+
+          // Extract the quality factor for this entry
+          double quality = 1.0;
+          int semi = entry.indexOf(";q=");
+          if (semi >= 0) {
+              try {
+                  quality = Double.parseDouble(entry.substring(semi + 3));
+              } catch (NumberFormatException e) {
+                  quality = 0.0;
+              }
+              entry = entry.substring(0, semi);
+          }
+
+          // Skip entries we are not going to keep track of
+          if (quality < 0.00005)
+              continue;       // Zero (or effectively zero) quality factors
+          if ("*".equals(entry))
+              continue;       // FIXME - "*" entries are not handled
+
+          // Extract the language and country for this entry
+          String language = null;
+          String country = null;
+          String variant = null;
+          int dash = entry.indexOf('-');
+          if (dash < 0) {
+              language = entry;
+              country = "";
+              variant = "";
+          } else {
+              language = entry.substring(0, dash);
+              country = entry.substring(dash + 1);
+              int vDash = country.indexOf('-');
+              if (vDash > 0) {
+                  String cTemp = country.substring(0, vDash);
+                  variant = country.substring(vDash + 1);
+                  country = cTemp;
+              } else {
+                  variant = "";
+              }
+          }
+
+          // Add a new Locale to the list of Locales for this quality level
+          Locale locale = new Locale(language, country, variant);
+          Double key = new Double(-quality);  // Reverse the order
+          ArrayList values = (ArrayList) locales.get(key);
+          if (values == null) {
+              values = new ArrayList();
+              locales.put(key, values);
+          }
+          values.add(locale);
+
+      }
+
+      return locales;
+    }
+    
+    /**
+     * The characters of the current string, as a character array.  Stored
+     * when the string is first specified to speed up access to characters
+     * being compared during parsing.
+     */
+    private char chars[] = null;
+
+
+    /**
+     * The zero-relative index of the current point at which we are
+     * positioned within the string being parsed.  <strong>NOTE</strong>:
+     * the value of this index can be one larger than the index of the last
+     * character of the string (i.e. equal to the string length) if you
+     * parse off the end of the string.  This value is useful for extracting
+     * substrings that include the end of the string.
+     */
+    private int index = 0;
+
+
+    /**
+     * The length of the String we are currently parsing.  Stored when the
+     * string is first specified to avoid repeated recalculations.
+     */
+    private int length = 0;
+
+
+    /**
+     * The String we are currently parsing.
+     */
+    private String string = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the zero-relative index of our current parsing position
+     * within the string being parsed.
+     */
+    public int getIndex() {
+
+        return (this.index);
+
+    }
+
+
+    /**
+     * Return the length of the string we are parsing.
+     */
+    public int getLength() {
+
+        return (this.length);
+
+    }
+
+
+    /**
+     * Return the String we are currently parsing.
+     */
+    public String getString() {
+
+        return (this.string);
+
+    }
+
+
+    /**
+     * Set the String we are currently parsing.  The parser state is also reset
+     * to begin at the start of this string.
+     *
+     * @param string The string to be parsed.
+     */
+    public void setString(String string) {
+
+        this.string = string;
+        if (string != null) {
+            this.length = string.length();
+            chars = this.string.toCharArray();
+        } else {
+            this.length = 0;
+            chars = new char[0];
+        }
+        reset();
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Advance the current parsing position by one, if we are not already
+     * past the end of the string.
+     */
+    public void advance() {
+
+        if (index < length)
+            index++;
+
+    }
+
+
+    /**
+     * Extract and return a substring that starts at the specified position,
+     * and extends to the end of the string being parsed.  If this is not
+     * possible, a zero-length string is returned.
+     *
+     * @param start Starting index, zero relative, inclusive
+     */
+    public String extract(int start) {
+
+        if ((start < 0) || (start >= length))
+            return ("");
+        else
+            return (string.substring(start));
+
+    }
+
+
+    /**
+     * Extract and return a substring that starts at the specified position,
+     * and ends at the character before the specified position.  If this is
+     * not possible, a zero-length string is returned.
+     *
+     * @param start Starting index, zero relative, inclusive
+     * @param end Ending index, zero relative, exclusive
+     */
+    public String extract(int start, int end) {
+
+        if ((start < 0) || (start >= end) || (end > length))
+            return ("");
+        else
+            return (string.substring(start, end));
+
+    }
+
+
+    /**
+     * Return the index of the next occurrence of the specified character,
+     * or the index of the character after the last position of the string
+     * if no more occurrences of this character are found.  The current
+     * parsing position is updated to the returned value.
+     *
+     * @param ch Character to be found
+     */
+    public int findChar(char ch) {
+
+        while ((index < length) && (ch != chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Return the index of the next occurrence of a non-whitespace character,
+     * or the index of the character after the last position of the string
+     * if no more non-whitespace characters are found.  The current
+     * parsing position is updated to the returned value.
+     */
+    public int findText() {
+
+        while ((index < length) && isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Return the index of the next occurrence of a whitespace character,
+     * or the index of the character after the last position of the string
+     * if no more whitespace characters are found.  The current parsing
+     * position is updated to the returned value.
+     */
+    public int findWhite() {
+
+        while ((index < length) && !isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Reset the current state of the parser to the beginning of the
+     * current string being parsed.
+     */
+    public void reset() {
+
+        index = 0;
+
+    }
+
+
+    /**
+     * Advance the current parsing position while it is pointing at the
+     * specified character, or until it moves past the end of the string.
+     * Return the final value.
+     *
+     * @param ch Character to be skipped
+     */
+    public int skipChar(char ch) {
+
+        while ((index < length) && (ch == chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Advance the current parsing position while it is pointing at a
+     * non-whitespace character, or until it moves past the end of the string.
+     * Return the final value.
+     */
+    public int skipText() {
+
+        while ((index < length) && !isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Advance the current parsing position while it is pointing at a
+     * whitespace character, or until it moves past the end of the string.
+     * Return the final value.
+     */
+    public int skipWhite() {
+
+        while ((index < length) && isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Is the specified character considered to be whitespace?
+     *
+     * @param ch Character to be checked
+     */
+    protected boolean isWhite(char ch) {
+
+        if ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'))
+            return (true);
+        else
+            return (false);
+
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Range.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/Range.java
new file mode 100644 (file)
index 0000000..4cb02ae
--- /dev/null
@@ -0,0 +1,146 @@
+/**
+ * 
+ */
+package org.apache.tomcat.servlets.util;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+/** 
+ * Utils to process HTTP/1.1 ranges. Used by default servlet, could 
+ * be used by any servlet that needs to deal with ranges.
+ * 
+ * It is very good to support ranges if you have large content. In most
+ * cases supporting one range is enough - getting multiple ranges doesn't
+ * seem very common, and it's complex (multipart response).
+ * 
+ * @author Costin Manolache
+ * @author Remy Maucherat 
+ * @author - see DefaultServlet in Catalin for other contributors
+ */
+public class Range {
+
+    public long start;
+    public long end;
+    public long length;
+
+    /**
+     * Validate range.
+     */
+    public boolean validate() {
+        if (end >= length)
+            end = length - 1;
+        return ( (start >= 0) && (end >= 0) && (start <= end)
+                 && (length > 0) );
+    }
+
+    public void recycle() {
+        start = 0;
+        end = 0;
+        length = 0;
+    }
+    
+    /** Parse ranges. 
+     * 
+     * @return null if the range is invalid or can't be parsed
+     */
+    public static ArrayList parseRanges(long fileLength, 
+                                        String rangeHeader) throws IOException {
+        ArrayList result = new ArrayList();
+        StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ",");
+
+        // Parsing the range list
+        while (commaTokenizer.hasMoreTokens()) {
+            String rangeDefinition = commaTokenizer.nextToken().trim();
+
+            Range currentRange = new Range();
+            currentRange.length = fileLength;
+
+            int dashPos = rangeDefinition.indexOf('-');
+
+            if (dashPos == -1) {
+                return null;
+            }
+
+            if (dashPos == 0) {
+                try {
+                    long offset = Long.parseLong(rangeDefinition);
+                    currentRange.start = fileLength + offset;
+                    currentRange.end = fileLength - 1;
+                } catch (NumberFormatException e) {
+                    return null;
+                }
+            } else {
+
+                try {
+                    currentRange.start = Long.parseLong
+                        (rangeDefinition.substring(0, dashPos));
+                    if (dashPos < rangeDefinition.length() - 1)
+                        currentRange.end = Long.parseLong
+                            (rangeDefinition.substring
+                             (dashPos + 1, rangeDefinition.length()));
+                    else
+                        currentRange.end = fileLength - 1;
+                } catch (NumberFormatException e) {
+                    return null;
+                }
+
+            }
+            if (!currentRange.validate()) {
+                return null;
+            }
+            result.add(currentRange);
+        }
+        return result;
+    }
+
+
+    /**
+     * Parse the Content-Range header. Used with PUT or in response.
+     * 
+     * @return Range
+     */
+    public static Range parseContentRange(String rangeHeader)
+            throws IOException {
+        if (rangeHeader == null)
+            return null;
+
+        // bytes is the only range unit supported
+        if (!rangeHeader.startsWith("bytes")) {
+            return null;
+        }
+
+        rangeHeader = rangeHeader.substring(6).trim();
+
+        int dashPos = rangeHeader.indexOf('-');
+        int slashPos = rangeHeader.indexOf('/');
+
+        if (dashPos == -1) {
+            return null;
+        }
+
+        if (slashPos == -1) {
+            return null;
+        }
+
+        Range range = new Range();
+
+        try {
+            range.start = Long.parseLong(rangeHeader.substring(0, dashPos));
+            range.end =
+                Long.parseLong(rangeHeader.substring(dashPos + 1, slashPos));
+            range.length = Long.parseLong
+                (rangeHeader.substring(slashPos + 1, rangeHeader.length()));
+        } catch (NumberFormatException e) {
+            return null;
+        }
+
+        if (!range.validate()) {
+            return null;
+        }
+
+        return range;
+    }
+
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/RequestUtil.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/RequestUtil.java
new file mode 100644 (file)
index 0000000..eb9a543
--- /dev/null
@@ -0,0 +1,509 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.tomcat.servlets.util;
+
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javax.servlet.http.Cookie;
+
+
+/**
+ * General purpose request parsing and encoding utility methods.
+ *
+ * @author Craig R. McClanahan
+ * @author Tim Tye
+ * @version $Revision: 302905 $ $Date: 2004-05-26 18:41:54 +0200 (mer., 26 mai 2004) $
+ */
+
+public final class RequestUtil {
+
+
+    /**
+     * The DateFormat to use for generating readable dates in cookies.
+     */
+    private static SimpleDateFormat format =
+        new SimpleDateFormat(" EEEE, dd-MMM-yy kk:mm:ss zz");
+
+    static {
+        format.setTimeZone(TimeZone.getTimeZone("GMT"));
+    }
+
+
+    /**
+     * Encode a cookie as per RFC 2109.  The resulting string can be used
+     * as the value for a <code>Set-Cookie</code> header.
+     *
+     * @param cookie The cookie to encode.
+     * @return A string following RFC 2109.
+     */
+    public static String encodeCookie(Cookie cookie) {
+
+        StringBuffer buf = new StringBuffer( cookie.getName() );
+        buf.append("=");
+        buf.append(cookie.getValue());
+
+        if (cookie.getComment() != null) {
+            buf.append("; Comment=\"");
+            buf.append(cookie.getComment());
+            buf.append("\"");
+        }
+
+        if (cookie.getDomain() != null) {
+            buf.append("; Domain=\"");
+            buf.append(cookie.getDomain());
+            buf.append("\"");
+        }
+
+        long age = cookie.getMaxAge();
+        if (cookie.getMaxAge() >= 0) {
+            buf.append("; Max-Age=\"");
+            buf.append(cookie.getMaxAge());
+            buf.append("\"");
+        }
+
+        if (cookie.getPath() != null) {
+            buf.append("; Path=\"");
+            buf.append(cookie.getPath());
+            buf.append("\"");
+        }
+
+        if (cookie.getSecure()) {
+            buf.append("; Secure");
+        }
+
+        if (cookie.getVersion() > 0) {
+            buf.append("; Version=\"");
+            buf.append(cookie.getVersion());
+            buf.append("\"");
+        }
+
+        return (buf.toString());
+    }
+
+
+    /**
+     * Filter the specified message string for characters that are sensitive
+     * in HTML.  This avoids potential attacks caused by including JavaScript
+     * codes in the request URL that is often reported in error messages.
+     *
+     * @param message The message string to be filtered
+     */
+    public static String filter(String message) {
+
+        if (message == null)
+            return (null);
+
+        char content[] = new char[message.length()];
+        message.getChars(0, message.length(), content, 0);
+        StringBuffer result = new StringBuffer(content.length + 50);
+        for (int i = 0; i < content.length; i++) {
+            switch (content[i]) {
+            case '<':
+                result.append("&lt;");
+                break;
+            case '>':
+                result.append("&gt;");
+                break;
+            case '&':
+                result.append("&amp;");
+                break;
+            case '"':
+                result.append("&quot;");
+                break;
+            default:
+                result.append(content[i]);
+            }
+        }
+        return (result.toString());
+
+    }
+
+
+    /**
+     * Normalize a relative URI path that may have relative values ("/./",
+     * "/../", and so on ) it it.  <strong>WARNING</strong> - This method is
+     * useful only for normalizing application-generated paths.  It does not
+     * try to perform security checks for malicious input.
+     *
+     * @param path Relative path to be normalized
+     */
+    public static String normalize(String path) {
+
+        if (path == null)
+            return null;
+
+        // Create a place for the normalized path
+        String normalized = path;
+
+        if (normalized.equals("/."))
+            return "/";
+
+        // Add a leading "/" if necessary
+        if (!normalized.startsWith("/"))
+            normalized = "/" + normalized;
+
+        // Resolve occurrences of "//" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("//");
+            if (index < 0)
+                break;
+            normalized = normalized.substring(0, index) +
+                normalized.substring(index + 1);
+        }
+
+        // Resolve occurrences of "/./" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("/./");
+            if (index < 0)
+                break;
+            normalized = normalized.substring(0, index) +
+                normalized.substring(index + 2);
+        }
+
+        // Resolve occurrences of "/../" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("/../");
+            if (index < 0)
+                break;
+            if (index == 0)
+                return (null);  // Trying to go outside our context
+            int index2 = normalized.lastIndexOf('/', index - 1);
+            normalized = normalized.substring(0, index2) +
+                normalized.substring(index + 3);
+        }
+
+        // Return the normalized path that we have completed
+        return (normalized);
+
+    }
+
+
+    /**
+     * Parse the character encoding from the specified content type header.
+     * If the content type is null, or there is no explicit character encoding,
+     * <code>null</code> is returned.
+     *
+     * @param contentType a content type header
+     */
+    public static String parseCharacterEncoding(String contentType) {
+
+        if (contentType == null)
+            return (null);
+        int start = contentType.indexOf("charset=");
+        if (start < 0)
+            return (null);
+        String encoding = contentType.substring(start + 8);
+        int end = encoding.indexOf(';');
+        if (end >= 0)
+            encoding = encoding.substring(0, end);
+        encoding = encoding.trim();
+        if ((encoding.length() > 2) && (encoding.startsWith("\""))
+            && (encoding.endsWith("\"")))
+            encoding = encoding.substring(1, encoding.length() - 1);
+        return (encoding.trim());
+
+    }
+
+
+    /**
+     * Parse a cookie header into an array of cookies according to RFC 2109.
+     *
+     * @param header Value of an HTTP "Cookie" header
+     */
+    public static Cookie[] parseCookieHeader(String header) {
+
+        if ((header == null) || (header.length() < 1))
+            return (new Cookie[0]);
+
+        ArrayList cookies = new ArrayList();
+        while (header.length() > 0) {
+            int semicolon = header.indexOf(';');
+            if (semicolon < 0)
+                semicolon = header.length();
+            if (semicolon == 0)
+                break;
+            String token = header.substring(0, semicolon);
+            if (semicolon < header.length())
+                header = header.substring(semicolon + 1);
+            else
+                header = "";
+            try {
+                int equals = token.indexOf('=');
+                if (equals > 0) {
+                    String name = token.substring(0, equals).trim();
+                    String value = token.substring(equals+1).trim();
+                    cookies.add(new Cookie(name, value));
+                }
+            } catch (Throwable e) {
+                ;
+            }
+        }
+
+        return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()]));
+
+    }
+
+
+    /**
+     * Append request parameters from the specified String to the specified
+     * Map.  It is presumed that the specified Map is not accessed from any
+     * other thread, so no synchronization is performed.
+     * <p>
+     * <strong>IMPLEMENTATION NOTE</strong>:  URL decoding is performed
+     * individually on the parsed name and value elements, rather than on
+     * the entire query string ahead of time, to properly deal with the case
+     * where the name or value includes an encoded "=" or "&" character
+     * that would otherwise be interpreted as a delimiter.
+     *
+     * @param map Map that accumulates the resulting parameters
+     * @param data Input string containing request parameters
+     *
+     * @exception IllegalArgumentException if the data is malformed
+     */
+    public static void parseParameters(Map map, String data, String encoding)
+        throws UnsupportedEncodingException {
+
+        if ((data != null) && (data.length() > 0)) {
+
+            // use the specified encoding to extract bytes out of the
+            // given string so that the encoding is not lost. If an
+            // encoding is not specified, let it use platform default
+            byte[] bytes = null;
+            try {
+                if (encoding == null) {
+                    bytes = data.getBytes();
+                } else {
+                    bytes = data.getBytes(encoding);
+                }
+            } catch (UnsupportedEncodingException uee) {
+            }
+
+            parseParameters(map, bytes, encoding);
+        }
+
+    }
+
+
+    /**
+     * Decode and return the specified URL-encoded String.
+     * When the byte array is converted to a string, the system default
+     * character encoding is used...  This may be different than some other
+     * servers.
+     *
+     * @param str The url-encoded string
+     *
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(String str) {
+
+        return URLDecode(str, null);
+
+    }
+
+
+    /**
+     * Decode and return the specified URL-encoded String.
+     *
+     * @param str The url-encoded string
+     * @param enc The encoding to use; if null, the default encoding is used
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(String str, String enc) {
+
+        if (str == null)
+            return (null);
+
+        // use the specified encoding to extract bytes out of the
+        // given string so that the encoding is not lost. If an
+        // encoding is not specified, let it use platform default
+        byte[] bytes = null;
+        try {
+            if (enc == null) {
+                bytes = str.getBytes();
+            } else {
+                bytes = str.getBytes(enc);
+            }
+        } catch (UnsupportedEncodingException uee) {}
+
+        return URLDecode(bytes, enc);
+
+    }
+
+
+    /**
+     * Decode and return the specified URL-encoded byte array.
+     *
+     * @param bytes The url-encoded byte array
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(byte[] bytes) {
+        return URLDecode(bytes, null);
+    }
+
+
+    /**
+     * Decode and return the specified URL-encoded byte array.
+     *
+     * @param bytes The url-encoded byte array
+     * @param enc The encoding to use; if null, the default encoding is used
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(byte[] bytes, String enc) {
+
+        if (bytes == null)
+            return (null);
+
+        int len = bytes.length;
+        int ix = 0;
+        int ox = 0;
+        while (ix < len) {
+            byte b = bytes[ix++];     // Get byte to test
+            if (b == '+') {
+                b = (byte)' ';
+            } else if (b == '%') {
+                b = (byte) ((convertHexDigit(bytes[ix++]) << 4)
+                            + convertHexDigit(bytes[ix++]));
+            }
+            bytes[ox++] = b;
+        }
+        if (enc != null) {
+            try {
+                return new String(bytes, 0, ox, enc);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return new String(bytes, 0, ox);
+
+    }
+
+
+    /**
+     * Convert a byte character value to hexidecimal digit value.
+     *
+     * @param b the character value byte
+     */
+    private static byte convertHexDigit( byte b ) {
+        if ((b >= '0') && (b <= '9')) return (byte)(b - '0');
+        if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10);
+        if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10);
+        return 0;
+    }
+
+
+    /**
+     * Put name and value pair in map.  When name already exist, add value
+     * to array of values.
+     *
+     * @param map The map to populate
+     * @param name The parameter name
+     * @param value The parameter value
+     */
+    private static void putMapEntry( Map map, String name, String value) {
+        String[] newValues = null;
+        String[] oldValues = (String[]) map.get(name);
+        if (oldValues == null) {
+            newValues = new String[1];
+            newValues[0] = value;
+        } else {
+            newValues = new String[oldValues.length + 1];
+            System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
+            newValues[oldValues.length] = value;
+        }
+        map.put(name, newValues);
+    }
+
+
+    /**
+     * Append request parameters from the specified String to the specified
+     * Map.  It is presumed that the specified Map is not accessed from any
+     * other thread, so no synchronization is performed.
+     * <p>
+     * <strong>IMPLEMENTATION NOTE</strong>:  URL decoding is performed
+     * individually on the parsed name and value elements, rather than on
+     * the entire query string ahead of time, to properly deal with the case
+     * where the name or value includes an encoded "=" or "&" character
+     * that would otherwise be interpreted as a delimiter.
+     *
+     * NOTE: byte array data is modified by this method.  Caller beware.
+     *
+     * @param map Map that accumulates the resulting parameters
+     * @param data Input string containing request parameters
+     * @param encoding Encoding to use for converting hex
+     *
+     * @exception UnsupportedEncodingException if the data is malformed
+     */
+    public static void parseParameters(Map map, byte[] data, String encoding)
+        throws UnsupportedEncodingException {
+
+        if (data != null && data.length > 0) {
+            int    pos = 0;
+            int    ix = 0;
+            int    ox = 0;
+            String key = null;
+            String value = null;
+            while (ix < data.length) {
+                byte c = data[ix++];
+                switch ((char) c) {
+                case '&':
+                    value = new String(data, 0, ox, encoding);
+                    if (key != null) {
+                        putMapEntry(map, key, value);
+                        key = null;
+                    }
+                    ox = 0;
+                    break;
+                case '=':
+                    if (key == null) {
+                        key = new String(data, 0, ox, encoding);
+                        ox = 0;
+                    } else {
+                        data[ox++] = c;
+                    }                   
+                    break;  
+                case '+':
+                    data[ox++] = (byte)' ';
+                    break;
+                case '%':
+                    data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4)
+                                    + convertHexDigit(data[ix++]));
+                    break;
+                default:
+                    data[ox++] = c;
+                }
+            }
+            //The last value does not end in '&'.  So save it now.
+            if (key != null) {
+                value = new String(data, 0, ox, encoding);
+                putMapEntry(map, key, value);
+            }
+        }
+
+    }
+
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/UrlUtils.java b/modules/tomcat-lite/java/org/apache/tomcat/servlets/util/UrlUtils.java
new file mode 100644 (file)
index 0000000..3049de4
--- /dev/null
@@ -0,0 +1,67 @@
+package org.apache.tomcat.servlets.util;
+
+public class UrlUtils {
+
+    /** Used by webdav.
+     * 
+     * Return a context-relative path, beginning with a "/", that represents
+     * the canonical version of the specified path after ".." and "." elements
+     * are resolved out.  If the specified path attempts to go outside the
+     * boundaries of the current context (i.e. too many ".." path elements
+     * are present), return <code>null</code> instead.
+     *
+     * @param path Path to be normalized
+     */
+    public static String normalize(String path) {
+
+        if (path == null)
+            return null;
+
+        // Create a place for the normalized path
+        String normalized = path;
+
+        if (normalized.equals("/."))
+            return "/";
+
+        // Normalize the slashes and add leading slash if necessary
+        if (normalized.indexOf('\\') >= 0)
+            normalized = normalized.replace('\\', '/');
+        
+        if (!normalized.startsWith("/"))
+            normalized = "/" + normalized;
+
+        // Resolve occurrences of "//" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("//");
+            if (index < 0)
+                break;
+            normalized = normalized.substring(0, index) +
+                normalized.substring(index + 1);
+        }
+
+        // Resolve occurrences of "/./" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("/./");
+            if (index < 0)
+                break;
+            normalized = normalized.substring(0, index) +
+                normalized.substring(index + 2);
+        }
+
+        // Resolve occurrences of "/../" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("/../");
+            if (index < 0)
+                break;
+            if (index == 0)
+                return (null);  // Trying to go outside our context
+            int index2 = normalized.lastIndexOf('/', index - 1);
+            normalized = normalized.substring(0, index2) +
+                normalized.substring(index + 3);
+        }
+
+        // Return the normalized path that we have completed
+        return (normalized);
+    }
+
+}