--- /dev/null
+/*
+ */
+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);
+}
--- /dev/null
+/*
+ */
+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);
+
+
+}
--- /dev/null
+/*
+ */
+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);
+}
--- /dev/null
+/*
+ */
+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>();
+}
--- /dev/null
+/*
+ */
+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
--- /dev/null
+/*
+ */
+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;
+ }
+
+}
--- /dev/null
+/*
+ */
+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;
+ }
+}
--- /dev/null
+en=ISO-8859-1
+fr=ISO-8859-1
--- /dev/null
+/*
+ */
+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);
+}
--- /dev/null
+/*
+ */
+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);
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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));
+
+ }
+
+
+}
--- /dev/null
+/*
+ * 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><jsp-file></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);
+
+ }
+
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+
+}
--- /dev/null
+/**
+ *
+ */
+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
--- /dev/null
+/*
+ * 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) {
+ }
+
+}
+
--- /dev/null
+/*
+ * 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();
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+
+}
+
--- /dev/null
+/*
+ * 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;
+
+ }
+}
--- /dev/null
+/*
+ * 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><jsp-file></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;
+ }
+
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+ }
+
+
+}
--- /dev/null
+/*
+ * 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><description>/<version></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;
+ }
+
+
+}
+
--- /dev/null
+/*
+ * 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) {
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+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
--- /dev/null
+/*
+ * 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() {
+ }
+
+}
--- /dev/null
+/*
+ * 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 {
+ }
+
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+# 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
+
--- /dev/null
+/*
+ * 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());
+
+ }
+
+
+}
--- /dev/null
+/*
+ */
+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);
+ }
+
+
+
+}
--- /dev/null
+/* 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
--- /dev/null
+/*
+ */
+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);
+ }
+
+
+}
--- /dev/null
+/* 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
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ */
+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);
+ }
+ }
+
+}
--- /dev/null
+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]);
+ }
+ }
+
+
+ }
+
+
+}
--- /dev/null
+/**
+ *
+ */
+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
--- /dev/null
+/*
+ */
+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);
+ }
+
+}
--- /dev/null
+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;
+
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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\"> \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\"> \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(" ");
+ 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;
+ }
+
+}
--- /dev/null
+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;
+
+ }
+}
--- /dev/null
+/*
+ * 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);
+
+ }
+
+
+}
+
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+};
+
--- /dev/null
+/*
+ * 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();
+ }
+ }
+
+
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ */
+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();
+ }
+
+
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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 {
+ }
+
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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 {
+ }
+
+
+}
--- /dev/null
+/*
+ * 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) {
+
+ }
+
+
+}
--- /dev/null
+/*
+ */
+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);
+}
--- /dev/null
+/*
+ * 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);
+
+ }
+
+
+
+}
--- /dev/null
+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
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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());
+
+ }
+
+
+}
--- /dev/null
+/*
+ * 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);
+
+ }
+
+
+}
--- /dev/null
+/**
+ *
+ */
+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
--- /dev/null
+/*
+ * 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("<");
+ break;
+ case '>':
+ result.append(">");
+ break;
+ case '&':
+ result.append("&");
+ break;
+ case '"':
+ result.append(""");
+ 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);
+ }
+ }
+
+ }
+
+
+
+}
--- /dev/null
+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);
+ }
+
+}