You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by co...@locus.apache.org on 2000/01/30 05:22:48 UTC

cvs commit: jakarta-tomcat/src/share/org/apache/tomcat/servlets WarFileServlet.java DefaultServlet.java

costin      00/01/29 20:22:48

  Modified:    src/etc  server.xml
               src/share/org/apache/tomcat/context DefaultCMSetter.java
                        WebXmlInterceptor.java
               src/share/org/apache/tomcat/core Context.java
                        ContextManager.java RequestInterceptor.java
               src/share/org/apache/tomcat/deployment
                        WebApplicationReader.java
               src/share/org/apache/tomcat/request SessionInterceptor.java
                        SimpleMapper.java
               src/share/org/apache/tomcat/servlets DefaultServlet.java
  Added:       src/share/org/apache/tomcat/context
                        WarWebXmlInterceptor.java
               src/share/org/apache/tomcat/servlets WarFileServlet.java
  Removed:     src/share/org/apache/tomcat/context WarInterceptor.java
               src/share/org/apache/tomcat/request
                        ContextMapperInterceptor.java
                        MapperInterceptor.java
  Log:
  - Started to move "serve-from-war" code in different modules.
  That will make tomcat a bit faster ( since we don't have to check
  if the context is from war for each request ) and probably a bit simpler
  ( for serve-from-war you just need to set the right interceptors and
  default servlet in the context, and the request processing will be the
  same, with war enabled modules doing the processing)
  
  - Removed the "getContextPath()" hack. Now the requestInterceptor has
  2 methods, one will parse the context path and second will parse the rest.
  ( not final - but it's an improvement vs. getContextPath() ).
  
  - moved all code that depends on tomcat.deploy into that package, now
  there is only one method call to read a web.xml into a context.
  
  Other changes:
  - removed adminPort and context comments from server.xml - admin will
  probably have a special tag and section, and we need to reorg. the
  context tag
  
  Revision  Changes    Path
  1.3       +1 -10     jakarta-tomcat/src/etc/server.xml
  
  Index: server.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/etc/server.xml,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- server.xml	2000/01/29 05:52:33	1.2
  +++ server.xml	2000/01/30 04:22:45	1.3
  @@ -1,16 +1,7 @@
   <?xml version="1.0" encoding="ISO-8859-1"?>
   
  -<Server adminPort="-1" workDir="work">
  +<Server>
       <ContextManager port="8080" hostName="" inet="">
  -
  -<!--
  -   Defaults: 
  -            defaultSessionTimeOut="30" 
  -            isWARExpanded="true"
  -            isWARValidated="false" 
  -            isInvokerEnabled="true"
  -            isWorkDirPersistent="false"
  --->
           <Connector className="org.apache.tomcat.service.http.HttpAdapter">
           </Connector>
   
  
  
  
  1.2       +5 -4      jakarta-tomcat/src/share/org/apache/tomcat/context/DefaultCMSetter.java
  
  Index: DefaultCMSetter.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/context/DefaultCMSetter.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- DefaultCMSetter.java	2000/01/29 05:51:28	1.1
  +++ DefaultCMSetter.java	2000/01/30 04:22:45	1.2
  @@ -100,12 +100,13 @@
   	if( ! riE.hasMoreElements() ) {
   	    // nothing set up by starter, add default ones
   	    if(cm.getDebug()>0) cm.log("Setting default interceptors ");
  -	    cm.addRequestInterceptor(new ContextMapperInterceptor( cm ));
  -	    cm.addRequestInterceptor(new SessionInterceptor());
  -	    //	    addRequestInterceptor(new MapperInterceptor());
   
   	    // Use the simplified mapper - revert if too many bugs and
  -	    cm.addRequestInterceptor(new SimpleMapper());
  +	    SimpleMapper smap=new SimpleMapper();
  +	    smap.setContextManager( cm );
  +	    cm.addRequestInterceptor(smap);
  +
  +	    cm.addRequestInterceptor(new SessionInterceptor());
   	}
   
   	return 0;
  
  
  
  1.6       +9 -238    jakarta-tomcat/src/share/org/apache/tomcat/context/WebXmlInterceptor.java
  
  Index: WebXmlInterceptor.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/context/WebXmlInterceptor.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- WebXmlInterceptor.java	2000/01/29 05:51:28	1.5
  +++ WebXmlInterceptor.java	2000/01/30 04:22:45	1.6
  @@ -82,255 +82,26 @@
       }
   	
       public int handleContextInit(Context ctx) {
  +	System.out.println("Context(" + ctx.getPath() + "): " + ctx.getDocBase());
  +	    
   	// process base configuration
  +	WebApplicationReader webXmlReader=new WebApplicationReader();
  +	
  +	// read default web.xml
   	try {
  -	    Class webApplicationDescriptor = Class.forName(
  -	        "org.apache.tomcat.deployment.WebApplicationDescriptor");
  -	    InputStream is =
  -	        webApplicationDescriptor.getResourceAsStream("web.xml");
  -	    String msg = sm.getString("context.getConfig.msg", "default");
  -
  -	    // No message for default  !! System.out.println(msg);
  +	    webXmlReader.processDefaultWebApp( ctx );
   
  -	    processWebApp(ctx, is, true);
  +	    InputStream is= new FileInputStream( ctx.getDocBase() + "/WEB-INF/web.xml");
  +	    webXmlReader.processWebApp(ctx, is);
   	} catch (Exception e) {
  -	    String msg = sm.getString("context.getConfig.e", "default");
  +	    String msg = sm.getString("context.getConfig.e",ctx.getPath() + " " + ctx.getDocBase());
   	    System.out.println(msg);
   	}
  -
  -	// process webApp configuration
  -	String s = ctx.getDocumentBase().toString();
  -	if (ctx.getDocumentBase().getProtocol().equalsIgnoreCase("war")) {
  -	    if (s.endsWith("/")) {
  -	        s = s.substring(0, s.length() - 1);
  -	    }
  -
  -	    s += "!/";
  -	}
  -
  -	URL webURL = null;
  -	try {
  -	    webURL = new URL(s + "/" + Constants.Context.ConfigFile);
  -
  -	    InputStream is = webURL.openConnection().getInputStream();
  -	    // 	    String msg = sm.getString("context.getConfig.msg",
  -	    // 				      webURL.toString());
  -
  -	    System.out.println("Context(" + ctx.getPath() + "): " + webURL.getFile());
  -	    
  -	    processWebApp(ctx, is, false);
  -	} catch (Exception e) {
  -	    String msg = sm.getString("context.getConfig.e",
  -	        (webURL != null) ? webURL.toString() : "not available");
  -
  -            // go silent on this one
  -	    // System.out.println(msg);
  -	}
   	return 0;
       }
   
       public int handleContextShutdown(Context ctx) {
   	return OK;
  -    }
  -
  -    private void processWebApp(Context ctx, InputStream is, boolean internal) {
  -        if (is != null) {
  -	    try {
  -	        WebApplicationDescriptor webDescriptor =
  -		    (new WebApplicationReader()).getDescriptor(is,
  -		        new WebDescriptorFactoryImpl(),
  -			ctx.isWARValidated());
  -
  -		ctx.setDescription( webDescriptor.getDescription());
  -		ctx.setDistributable( webDescriptor.isDistributable());
  -
  -		Enumeration contextParameters=webDescriptor.getContextParameters();
  -		while (contextParameters.hasMoreElements()) {
  -		    ContextParameter contextParameter =
  -			(ContextParameter)contextParameters.nextElement();
  -		    ctx.setInitParameter(contextParameter.getName(),
  -					 contextParameter.getValue());
  -		}
  -		ctx.setSessionTimeOut( webDescriptor.getSessionTimeout());
  -
  -		processServlets(ctx, webDescriptor.getWebComponentDescriptors());
  -		processMimeMaps(ctx, webDescriptor.getMimeMappings());
  -		processWelcomeFiles(ctx, webDescriptor.getWelcomeFiles(),
  -				    internal);
  -		processErrorPages(ctx, webDescriptor.getErrorPageDescriptors());
  -	    } catch (Throwable e) {
  -                String msg = "config parse: " + e.getMessage();
  -
  -                System.out.println(msg);
  -	    }
  -	}
  -    }
  -
  -    private void processServlets(Context ctx, Enumeration servlets) {
  -        // XXX
  -        // oh my ... this has suddenly turned rather ugly
  -        // perhaps the reader should do this normalization work
  -
  -        while (servlets.hasMoreElements()) {
  -	    WebComponentDescriptor webComponentDescriptor =
  -	        (WebComponentDescriptor)servlets.nextElement();
  -	    String name = webComponentDescriptor.getCanonicalName();
  -	    String description = webComponentDescriptor.getDescription();
  -	    String resourceName = null;
  -	    boolean removeResource = false;
  -
  -	    if (webComponentDescriptor instanceof ServletDescriptor) {
  -		resourceName =
  -		    ((ServletDescriptor)webComponentDescriptor).getClassName();
  -
  -		if ( ctx.containsServletByName(name)) {
  -		    String msg = sm.getString("context.dd.dropServlet",
  -					      name + "(" + resourceName + ")" );
  -		    
  -		    System.out.println(msg);
  -		    
  -		    removeResource = true;
  -		    ctx.removeServletByName(name);
  -		}
  -
  -		ctx.addServlet(name, resourceName, description);
  -	    } else if (webComponentDescriptor instanceof JspDescriptor) {
  -		resourceName =
  -		    ((JspDescriptor)webComponentDescriptor).getJspFileName();
  -
  -		if (! resourceName.startsWith("/")) {
  -		    resourceName = "/" + resourceName;
  -		}
  -
  -		if (ctx.containsJSP(resourceName)) {
  -		    String msg = sm.getString("context.dd.dropServlet",
  -					      resourceName);
  -
  -		    System.out.println(msg);
  -		    
  -		    removeResource = true;
  -		    ctx.removeJSP(resourceName);
  -		}
  -
  -		ctx.addJSP(name, resourceName, description);
  -	    }
  -
  -
  -	    // XXX ugly, but outside of context - the whole thing will be
  -	    // rewriten, so don't worry
  -	    
  -	    // if the resource was already defined, override with the new definition
  -	    // XXX I have very little ideea about what it does !
  -	    if (removeResource) {
  -
  -	        Enumeration levels = ctx.getInitLevels();
  -
  -		while (levels.hasMoreElements()) {
  -		    Integer level = (Integer)levels.nextElement();
  -		    Enumeration servletsOnLevel=ctx.getLoadableServlets( level );
  -		    
  -		    Vector buf = new Vector();
  -		    while (servletsOnLevel.hasMoreElements()) {
  -		        String servletName = (String)servletsOnLevel.nextElement();
  -
  -			if (ctx.containsServletByName(servletName)) {
  -			    buf.addElement(servletName);
  -			}
  -		    }
  -		    ctx.setLoadableServlets(level, buf);
  -		}
  -	    }
  -	    
  -	    int loadOnStartUp = webComponentDescriptor.getLoadOnStartUp();
  -
  -            if (loadOnStartUp > Integer.MIN_VALUE) {
  -	        Integer key = new Integer(loadOnStartUp);
  -		ctx.addLoadableServlet( key, name );
  -		
  -	    }
  -
  -	    Enumeration enum =
  -	        webComponentDescriptor.getInitializationParameters();
  -	    Hashtable initializationParameters = new Hashtable();
  -
  -	    while (enum.hasMoreElements()) {
  -	        InitializationParameter initializationParameter =
  -		    (InitializationParameter)enum.nextElement();
  -
  -		initializationParameters.put(
  -		    initializationParameter.getName(),
  -		    initializationParameter.getValue());
  -	    }
  -
  -	    ctx.setServletInitParams( webComponentDescriptor.getCanonicalName(),
  -				 initializationParameters);
  -
  -	    enum = webComponentDescriptor.getUrlPatterns();
  -
  -	    while (enum.hasMoreElements()) {
  -	        String mapping = (String)enum.nextElement();
  -
  -		if (! mapping.startsWith("*.") &&
  -		    ! mapping.startsWith("/")) {
  -		    mapping = "/" + mapping;
  -		}
  -
  -		if (! ctx.containsServlet(mapping) &&
  -		    ! ctx.containsJSP(mapping)) {
  -		    if (ctx.containsMapping(mapping)) {
  -		        String msg = sm.getString("context.dd.dropMapping",
  -			    mapping);
  -
  -			System.out.println(msg);
  -
  -			ctx.removeMapping(mapping);
  -		    }
  -
  -                    ctx.addMapping(name, mapping);
  -		} else {
  -		    String msg = sm.getString("context.dd.ignoreMapping",
  -		        mapping);
  -
  -		    System.out.println(msg);
  -		}
  -	    }
  -	}
  -    }
  -
  -    private void processMimeMaps(Context ctx, Enumeration mimeMaps) {
  -        while (mimeMaps.hasMoreElements()) {
  -	    MimeMapping mimeMapping = (MimeMapping)mimeMaps.nextElement();
  -
  -	    ctx.addContentType(	mimeMapping.getExtension(),
  -				mimeMapping.getMimeType());
  -	}
  -    }
  -
  -    private void processWelcomeFiles(Context ctx, Enumeration welcomeFiles,
  -        boolean internal) {
  -        if (! internal && welcomeFiles.hasMoreElements()) {
  -            ctx.removeWelcomeFiles();
  -        }
  -
  -	while (welcomeFiles.hasMoreElements()) {
  -	    ctx.addWelcomeFile((String)welcomeFiles.nextElement());
  -	}
  -    }
  -
  -    private void processErrorPages(Context ctx, Enumeration errorPages) {
  -        while (errorPages.hasMoreElements()) {
  -	    ErrorPageDescriptor errorPageDescriptor =
  -	        (ErrorPageDescriptor)errorPages.nextElement();
  -	    String key = null;
  -
  -	    if (errorPageDescriptor.getErrorCode() > -1) {
  -	        key = String.valueOf(errorPageDescriptor.getErrorCode());
  -	    } else {
  -	        key = errorPageDescriptor.getExceptionType();
  -	    }
  -
  -	    ctx.addErrorPage(key, errorPageDescriptor.getLocation());
  -	}
       }
   
       
  
  
  
  1.1                  jakarta-tomcat/src/share/org/apache/tomcat/context/WarWebXmlInterceptor.java
  
  Index: WarWebXmlInterceptor.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights 
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer. 
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:  
   *       "This product includes software developed by the 
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written 
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */ 
  
  
  package org.apache.tomcat.context;
  
  import org.apache.tomcat.core.*;
  import org.apache.tomcat.core.Constants;
  import org.apache.tomcat.util.*;
  import org.apache.tomcat.deployment.*;
  import java.io.*;
  import java.net.*;
  import java.util.*;
  import javax.servlet.http.*;
  
  
  /**
   * Will configure the context using the default web.xml
   *
   * This interceptor is used when "serving from WAR" case.
   *
   * @author costin@dnt.ro
   */
  public class WarWebXmlInterceptor implements ContextInterceptor {
      private static StringManager sm =StringManager.getManager("org.apache.tomcat.core");
      
      public WarWebXmlInterceptor() {
      }
  	
      public int handleContextInit(Context ctx) {
  	if (! ctx.getDocumentBase().getProtocol().equalsIgnoreCase("war")) {
  	    return 0;
  	}
  	
  	// process base configuration
  	WebApplicationReader webXmlReader=new WebApplicationReader();
  
  	try {
  	    // read default web.xml
  	    webXmlReader.processDefaultWebApp( ctx );
  	    
  	    // process webApp configuration
  	    String s = ctx.getDocumentBase().toString();
  	    if (s.endsWith("/")) 
  		s = s.substring(0, s.length() - 1);
  	    
  	    URL webURL = null;
  	    webURL = new URL(s + "!/" + Constants.Context.ConfigFile);
  	    
  	    InputStream is = webURL.openConnection().getInputStream();
  	    
  	    System.out.println("Context(" + ctx.getPath() + "): " + webURL.getFile());
  	    
  	    webXmlReader.processWebApp(ctx, is);
  	} catch (Exception e) {
  	    String msg = sm.getString("context.getConfig.e",
  				      ctx.getPath() + " " + ctx.getDocBase() );
  	}
  	return 0;
      }
  
      public int handleContextShutdown(Context ctx) {
  	return OK;
      }
  
      
  }
  
  
  
  1.36      +19 -4     jakarta-tomcat/src/share/org/apache/tomcat/core/Context.java
  
  Index: Context.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/Context.java,v
  retrieving revision 1.35
  retrieving revision 1.36
  diff -u -r1.35 -r1.36
  --- Context.java	2000/01/29 05:51:31	1.35
  +++ Context.java	2000/01/30 04:22:46	1.36
  @@ -180,6 +180,22 @@
   	this.path = path;
       }
   
  +    /* NOTE: if docBase is a URL to a remote location, we should download
  +       the context and unpack it. It is _very_ inefficient to serve
  +       files from a remote location ( at least 2x slower )
  +    */
  +    
  +    /** DocBase points to the web application files.
  +     *
  +     *  There is no restriction on the syntax and content of DocBase,
  +     *  it's up to the various modules to interpret this and use it.
  +     *  For example, to server from a war file you can use war: protocol,
  +     *  and set up War interceptors.
  +     * 
  +     *  "Basic" tomcat treats it is a file ( either absolute or relative to
  +     *  the CM home ). XXX Make it absolute ??
  +     *
  +     */
       public void setDocBase( String docB ) {
   	this.docBase=docB;
       }
  @@ -325,7 +341,9 @@
   	    // if we can't  return a servlet, so it's more probable
   	    // servlets will check for null than IllegalArgument
   	}
  -        return contextM.getContextByPath(path);
  +	Request lr=contextM.createRequest( this, path );
  +	getContextManager().processRequest(lr);
  +        return lr.getContext();
       }
   
       public void log(String msg, Throwable t) {
  @@ -525,9 +543,6 @@
   	// XXX who uses servletBase ???
   	URL servletBase = getDocumentBase();
           this.setServletBase(servletBase);
  -
  -	// expand WAR
  -	new WarInterceptor().handleContextInit( this );
   
   	// Read context's web.xml
   	new WebXmlInterceptor().handleContextInit( this );
  
  
  
  1.29      +4 -9      jakarta-tomcat/src/share/org/apache/tomcat/core/ContextManager.java
  
  Index: ContextManager.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/ContextManager.java,v
  retrieving revision 1.28
  retrieving revision 1.29
  diff -u -r1.28 -r1.29
  --- ContextManager.java	2000/01/29 06:08:48	1.28
  +++ ContextManager.java	2000/01/30 04:22:46	1.29
  @@ -397,6 +397,10 @@
   	if(debug>2) log("");
   
   	for( int i=0; i< requestInterceptors.size(); i++ ) {
  +	    ((RequestInterceptor)requestInterceptors.elementAt(i)).handleRequestContextMap( req );
  +	}
  +
  +	for( int i=0; i< requestInterceptors.size(); i++ ) {
   	    ((RequestInterceptor)requestInterceptors.elementAt(i)).handleRequest( req );
   	}
   
  @@ -405,15 +409,6 @@
   	if(debug>2) log("");
   	return 0;
       }
  -
  -    // XXX XXX hack - we need to create a new request !
  -    ContextMapperInterceptor contextInterceptor=new ContextMapperInterceptor( this );
  -    public Context getContextByPath(String path ) {
  -	// XXX XXX XXX need to create a sub-request !!!!
  -	//
  -	return contextInterceptor.getContextByPath( path );      
  -    }
  -
   
       // -------------------- Sub-Request mechanism --------------------
   
  
  
  
  1.2       +30 -4     jakarta-tomcat/src/share/org/apache/tomcat/core/RequestInterceptor.java
  
  Index: RequestInterceptor.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/RequestInterceptor.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- RequestInterceptor.java	2000/01/07 19:14:11	1.1
  +++ RequestInterceptor.java	2000/01/30 04:22:46	1.2
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/RequestInterceptor.java,v 1.1 2000/01/07 19:14:11 costin Exp $
  - * $Revision: 1.1 $
  - * $Date: 2000/01/07 19:14:11 $
  + * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/RequestInterceptor.java,v 1.2 2000/01/30 04:22:46 costin Exp $
  + * $Revision: 1.2 $
  + * $Date: 2000/01/30 04:22:46 $
    *
    * ====================================================================
    *
  @@ -73,5 +73,31 @@
   public interface RequestInterceptor {
       public static final int OK=0;
       
  -    public int handleRequest(Request request);
  +    /** Will detect the context path for a request
  +     */
  +    //    public int contextMap(Request request);
  +    // XXX name will change!
  +    public int handleRequestContextMap(Request request);
  +
  +    /** Handle mapping inside a context
  +     */
  +    //    public int requestMap(Request request);
  +
  +    /** Security
  +     */
  +    //    public int authentication(Request request);
  +    //    public int authorization(Request request);
  +
  +    /** This handle knows how to guess the session id
  +	from a request ( SSL, cookie, rewriting ).
  +	Note that the request need
  +    */
  +    //    public int sessionId(Request request);
  +
  +    //    public int preService(Request request);
  +    //    public int postService(Request request);
  +
  +    //    public int log(Request request);
  +
  +    public int handleRequest( Request request);
   }
  
  
  
  1.3       +199 -4    jakarta-tomcat/src/share/org/apache/tomcat/deployment/WebApplicationReader.java
  
  Index: WebApplicationReader.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/deployment/WebApplicationReader.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- WebApplicationReader.java	1999/11/13 00:50:27	1.2
  +++ WebApplicationReader.java	2000/01/30 04:22:46	1.3
  @@ -60,10 +60,9 @@
   
   import org.apache.tomcat.util.XMLParser;
   import org.apache.tomcat.util.XMLTree;
  -import java.io.InputStream;
  -import java.util.Enumeration;
  -import java.util.Vector;
  -import java.util.StringTokenizer;
  +import org.apache.tomcat.core.*;
  +import java.io.*;
  +import java.util.*;
   
   /**
    * I am a class that translates an input steram containting an
  @@ -731,4 +730,200 @@
   	return sessionTimeOut.intValue();
       }
   
  +    // -------------------- Context setup--------------------
  +    public void processDefaultWebApp(Context ctx) throws Exception {
  +	Class webApplicationDescriptor = this.getClass();
  +	InputStream is =
  +	    webApplicationDescriptor.getResourceAsStream("web.xml");
  +	processWebApp(ctx, is);
  +    }
  +
  +    public void processWebApp(Context ctx, InputStream is) throws Exception {
  +	WebApplicationDescriptor webDescriptor =getDescriptor(is,
  +							      new WebDescriptorFactoryImpl(),
  +							      ctx.isWARValidated());
  +	
  +	ctx.setDescription( webDescriptor.getDescription());
  +	ctx.setDistributable( webDescriptor.isDistributable());
  +	
  +	Enumeration contextParameters=webDescriptor.getContextParameters();
  +	while (contextParameters.hasMoreElements()) {
  +	    ContextParameter contextParameter =
  +		(ContextParameter)contextParameters.nextElement();
  +	    ctx.setInitParameter(contextParameter.getName(),
  +				 contextParameter.getValue());
  +	}
  +	ctx.setSessionTimeOut( webDescriptor.getSessionTimeout());
  +	
  +	processServlets(ctx, webDescriptor.getWebComponentDescriptors());
  +	processMimeMaps(ctx, webDescriptor.getMimeMappings());
  +	processWelcomeFiles(ctx, webDescriptor.getWelcomeFiles());
  +	processErrorPages(ctx, webDescriptor.getErrorPageDescriptors());
  +    }
  +
  +    private void processServlets(Context ctx, Enumeration servlets) {
  +        // XXX
  +        // oh my ... this has suddenly turned rather ugly
  +        // perhaps the reader should do this normalization work
  +
  +        while (servlets.hasMoreElements()) {
  +	    WebComponentDescriptor webComponentDescriptor =
  +	        (WebComponentDescriptor)servlets.nextElement();
  +	    String name = webComponentDescriptor.getCanonicalName();
  +	    String description = webComponentDescriptor.getDescription();
  +	    String resourceName = null;
  +	    boolean removeResource = false;
  +
  +	    if (webComponentDescriptor instanceof ServletDescriptor) {
  +		resourceName =
  +		    ((ServletDescriptor)webComponentDescriptor).getClassName();
  +
  +		if ( ctx.containsServletByName(name)) {
  +// 		    String msg = sm.getString("context.dd.dropServlet",
  +// 					      name + "(" + resourceName + ")" );
  +		    
  +// 		    System.out.println(msg);
  +		    
  +		    removeResource = true;
  +		    ctx.removeServletByName(name);
  +		}
  +
  +		ctx.addServlet(name, resourceName, description);
  +	    } else if (webComponentDescriptor instanceof JspDescriptor) {
  +		resourceName =
  +		    ((JspDescriptor)webComponentDescriptor).getJspFileName();
  +
  +		if (! resourceName.startsWith("/")) {
  +		    resourceName = "/" + resourceName;
  +		}
  +
  +		if (ctx.containsJSP(resourceName)) {
  +// 		    String msg = sm.getString("context.dd.dropServlet",
  +// 					      resourceName);
  +
  +// 		    System.out.println(msg);
  +		    
  +		    removeResource = true;
  +		    ctx.removeJSP(resourceName);
  +		}
  +
  +		ctx.addJSP(name, resourceName, description);
  +	    }
  +
  +
  +	    // XXX ugly, but outside of context - the whole thing will be
  +	    // rewriten, so don't worry
  +	    
  +	    // if the resource was already defined, override with the new definition
  +	    // XXX I have very little ideea about what it does !
  +	    if (removeResource) {
  +
  +	        Enumeration levels = ctx.getInitLevels();
  +
  +		while (levels.hasMoreElements()) {
  +		    Integer level = (Integer)levels.nextElement();
  +		    Enumeration servletsOnLevel=ctx.getLoadableServlets( level );
  +		    
  +		    Vector buf = new Vector();
  +		    while (servletsOnLevel.hasMoreElements()) {
  +		        String servletName = (String)servletsOnLevel.nextElement();
  +
  +			if (ctx.containsServletByName(servletName)) {
  +			    buf.addElement(servletName);
  +			}
  +		    }
  +		    ctx.setLoadableServlets(level, buf);
  +		}
  +	    }
  +	    
  +	    int loadOnStartUp = webComponentDescriptor.getLoadOnStartUp();
  +
  +            if (loadOnStartUp > Integer.MIN_VALUE) {
  +	        Integer key = new Integer(loadOnStartUp);
  +		ctx.addLoadableServlet( key, name );
  +		
  +	    }
  +
  +	    Enumeration enum =
  +	        webComponentDescriptor.getInitializationParameters();
  +	    Hashtable initializationParameters = new Hashtable();
  +
  +	    while (enum.hasMoreElements()) {
  +	        InitializationParameter initializationParameter =
  +		    (InitializationParameter)enum.nextElement();
  +
  +		initializationParameters.put(
  +		    initializationParameter.getName(),
  +		    initializationParameter.getValue());
  +	    }
  +
  +	    ctx.setServletInitParams( webComponentDescriptor.getCanonicalName(),
  +				 initializationParameters);
  +
  +	    enum = webComponentDescriptor.getUrlPatterns();
  +
  +	    while (enum.hasMoreElements()) {
  +	        String mapping = (String)enum.nextElement();
  +
  +		if (! mapping.startsWith("*.") &&
  +		    ! mapping.startsWith("/")) {
  +		    mapping = "/" + mapping;
  +		}
  +
  +		if (! ctx.containsServlet(mapping) &&
  +		    ! ctx.containsJSP(mapping)) {
  +		    if (ctx.containsMapping(mapping)) {
  +// 		        String msg = sm.getString("context.dd.dropMapping",
  +// 			    mapping);
  +
  +// 			System.out.println(msg);
  +
  +			ctx.removeMapping(mapping);
  +		    }
  +
  +                    ctx.addMapping(name, mapping);
  +		} else {
  +// 		    String msg = sm.getString("context.dd.ignoreMapping",
  +// 		        mapping);
  +
  +// 		    System.out.println(msg);
  +		}
  +	    }
  +	}
  +    }
  +
  +    private void processMimeMaps(Context ctx, Enumeration mimeMaps) {
  +        while (mimeMaps.hasMoreElements()) {
  +	    MimeMapping mimeMapping = (MimeMapping)mimeMaps.nextElement();
  +
  +	    ctx.addContentType(	mimeMapping.getExtension(),
  +				mimeMapping.getMimeType());
  +	}
  +    }
  +
  +    private void processWelcomeFiles(Context ctx, Enumeration welcomeFiles ) {
  +        if ( welcomeFiles.hasMoreElements()) {
  +            ctx.removeWelcomeFiles();
  +        }
  +
  +	while (welcomeFiles.hasMoreElements()) {
  +	    ctx.addWelcomeFile((String)welcomeFiles.nextElement());
  +	}
  +    }
  +
  +    private void processErrorPages(Context ctx, Enumeration errorPages) {
  +        while (errorPages.hasMoreElements()) {
  +	    ErrorPageDescriptor errorPageDescriptor =
  +	        (ErrorPageDescriptor)errorPages.nextElement();
  +	    String key = null;
  +
  +	    if (errorPageDescriptor.getErrorCode() > -1) {
  +	        key = String.valueOf(errorPageDescriptor.getErrorCode());
  +	    } else {
  +	        key = errorPageDescriptor.getExceptionType();
  +	    }
  +
  +	    ctx.addErrorPage(key, errorPageDescriptor.getLocation());
  +	}
  +    }
   }
  
  
  
  1.4       +5 -0      jakarta-tomcat/src/share/org/apache/tomcat/request/SessionInterceptor.java
  
  Index: SessionInterceptor.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/request/SessionInterceptor.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- SessionInterceptor.java	2000/01/09 22:32:43	1.3
  +++ SessionInterceptor.java	2000/01/30 04:22:47	1.4
  @@ -110,4 +110,9 @@
   
   	return 0;
       }
  +
  +    public int handleRequestContextMap( Request rrequest ) {
  +	return 0;
  +    }
  +
   }
  
  
  
  1.2       +82 -1     jakarta-tomcat/src/share/org/apache/tomcat/request/SimpleMapper.java
  
  Index: SimpleMapper.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/request/SimpleMapper.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- SimpleMapper.java	2000/01/15 03:52:58	1.1
  +++ SimpleMapper.java	2000/01/30 04:22:47	1.2
  @@ -65,12 +65,24 @@
   import org.apache.tomcat.util.*;
   import java.util.Hashtable;
   
  +/** Parse request URI and find ContextPath, ServletPath, PathInfo and QueryString
  + *  Use a simple alghoritm - no optimizations or tricks.
  + *  Also, no special features - no VirtualHost, user directories, etc.
  + *
  + *  For "production" environment you should use either an optimized version
  + *  or a real web server parser.
  + */
   public class SimpleMapper  implements  RequestInterceptor {
       int debug=0;
  +    ContextManager cm;
       
       public SimpleMapper() {
       }
   
  +    public void setContextManager( ContextManager cm ) {
  +	this.cm=cm;
  +    }
  +
       public void setDebug( int level ) {
   	debug=level;
       }
  @@ -78,7 +90,32 @@
       void log( String msg ) {
   	System.out.println("SimpleMapper: " + msg );
       }
  -    
  +
  +    /** First step of request porcessing is finding the Context.
  +     *  Advanced mappers will do only one parsing.
  +     */
  +    public int handleRequestContextMap( Request rrequest ) {
  +	// someone else set it up, no need to worry
  +	if( rrequest.getContext() != null )
  +	    return OK;
  +	
  +	// resolve the server that we are for
  +	String path = rrequest.getRequestURI();
  +	
  +	Context ctx= this.getContextByPath(path);
  +	
  +	// final fix on response & request
  +	//		rresponse.setServerHeader(server.getServerHeader());
  +	
  +	String ctxPath = ctx.getPath();
  +	String pathInfo =path.substring(ctxPath.length(),
  +					    path.length());
  +	rrequest.setContext(ctx);
  +	rrequest.updatePaths();
  +	return OK;
  +    }
  +
  +
       public int handleRequest(Request req) {
   	Context context=req.getContext();
   	String path=req.getLookupPath();
  @@ -238,6 +275,50 @@
   
   	return  "/" + path;
       }
  +
  +
  +    // XXX XXX XXX need to fix this - it is used by getContext(String path) (costin)
  +    
  +    /**
  +     * Gets the context that is responsible for requests for a
  +     * particular path.  If no specifically assigned Context can be
  +     * identified, returns the default Context.
  +     *
  +     * @param path The path for which a Context is requested
  +     */
  +    Context getContextByPath(String path) {
  +	String realPath = path;
  +	Context ctx = null;
  +
  +	// XXX
  +	// needs help ... this needs to be optimized out.
  +
  +        lookup:
  +	do {
  +	    ctx = cm.getContext(path);
  +	    if (ctx == null) {
  +	        int i = path.lastIndexOf('/');
  +		if (i > -1 && path.length() > 1) {
  +		    path = path.substring(0, i);
  +		    if (path.length() == 0) {
  +		        path = "/";
  +		    }
  +		} else {
  +		    // path too short
  +		    break lookup;
  +		}
  +	    } else {
  +	    }
  +	} while (ctx == null);
  +
  +	// no map - root context
  +	if (ctx == null) {
  +	    ctx = cm.getContext( "" );
  +	}
  +
  +	return ctx;
  +    }
  +
   
   }
       
  
  
  
  1.4       +40 -145   jakarta-tomcat/src/share/org/apache/tomcat/servlets/DefaultServlet.java
  
  Index: DefaultServlet.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/servlets/DefaultServlet.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- DefaultServlet.java	2000/01/14 19:48:24	1.3
  +++ DefaultServlet.java	2000/01/30 04:22:47	1.4
  @@ -75,30 +75,41 @@
    * @author James Todd [gonzo@eng.sun.com]
    */
   public class DefaultServlet extends HttpServlet {
  -    private ServletContextFacade facade;
  -    private String servletInfo = "DefaultServlet";
  +    private static final String datePattern = "EEE, dd MMM yyyyy HH:mm z";
  +    private static final DateFormat dateFormat = new SimpleDateFormat(datePattern);
  +
  +    ServletContext contextF;
       private Context context;
  -    private MimeMap mimeTypes;
  -    private String datePattern = "EEE, dd MMM yyyyy HH:mm z";
  -    private DateFormat dateFormat = new SimpleDateFormat(datePattern);
  +    String docBase;
  +    int debug=1;
       
       public void init() throws ServletException {
  -	facade = (ServletContextFacade)getServletContext();
  -	context = facade.getRealContext();
  -	mimeTypes = context.getMimeMap();
  +	contextF = getServletContext();
  +	context = ((ServletContextFacade)getServletContext()).getRealContext();
  +
  +	// doesn't change - set it in init!
  +	docBase = context.getDocBase();
  +        if (! docBase.endsWith("/")) {
  +            docBase += "/";
  +        }
  +
  +	// debug 
  +	String dbg=getServletConfig().getInitParameter("debug");
  +	if( dbg!=null) debug=1;
       }
   
       public String getServletInfo() {
  -        return this.servletInfo;
  +        return "DefaultServlet";
       }
   
       public void doGet(HttpServletRequest request,
  -        HttpServletResponse response)
  -    throws ServletException, IOException {
  +		      HttpServletResponse response)
  +	throws ServletException, IOException
  +    {
   	String pathInfo = (String)request.getAttribute(
  -            Constants.Attribute.PathInfo);
  +            "javax.servlet.include.path_info");
   	String requestURI = (String)request.getAttribute(
  -	    Constants.Attribute.RequestURI);
  +            "javax.servlet.include.request_uri");
   
   	if (pathInfo == null) {
   	    pathInfo = request.getPathInfo();
  @@ -107,98 +118,20 @@
   	if (requestURI == null) {
   	    requestURI = request.getRequestURI();
   	}
  -
  -	// XXX XXX BAD BAD BAD - that means another request,
  -	// with the same informations !!!!!!!
  -	// It should use getMappedPath instead !!!
  -        URL url = getServletContext().getResource(pathInfo);
  -
  -	// 	System.out.println("Resource: " + url + " PI: " + pathInfo);
  -	if (url != null) {
  -	    if (url.getProtocol().equals("war") &&
  -	        context.isWARExpanded()) {
  -		String s = context.getWARDir().toString() + pathInfo;
  -
  -		url = URLUtil.resolve(s);
  -	    }
  -
  -	    if (url.getProtocol().equalsIgnoreCase("file")) {
  -		// serve file
  -
  -		File f = new File(url.getFile());
  -
  -                // this takes care of File.getAbsolutePath()
  -                // and File.getName() troubles when running on
  -                // JDK 1.1.x/Windows
  -                //
  -                // BUT IT ALSO ALLOWS THE DREADED ".." AND "jsp." BUGS.
  -                // SO IT'S OUTTA HERE!
  -                //
  -		//f = new File(f.getCanonicalPath());
  -
  -		if (f.exists()) {
  -		    processFile(f, url, request, response);
  -		} else {
  -		    response.sendError(response.SC_NOT_FOUND,
  -                        "File Not Found: " + requestURI);
  -		}
  -	    } else if (url.getProtocol().equalsIgnoreCase("war")) {
  -	        // get content from war
  -
  -	        String documentBase = context.getDocumentBase().toString();
  -
  -		if (documentBase.endsWith("/")) {
  -		    documentBase = documentBase.substring(0,
  -		        documentBase.length() - 1);
  -		}
  - 
  -	        URL mappedURL = new URL(documentBase + "!" + pathInfo);
  -
  -		serveURL(mappedURL, request, response);
  -	    } else {
  -		// get content from url
  -
  -		serveURL(url, request, response);
  -	    }
  -	} else {
  -	    System.out.println("Got null URL: " + url);
  -
  -	    response.sendError(response.SC_NOT_FOUND,
  -                "File Not Found<br>" + requestURI);
  -	}
  -    }
   
  -    private void processFile(File file, URL url,
  -        HttpServletRequest request, HttpServletResponse response)
  -    throws ServletException, IOException {
  -	String requestURI = (String)request.getAttribute(
  -	    Constants.Attribute.RequestURI);
  -
  -	if (requestURI == null) {
  -	    requestURI = request.getRequestURI();
  -	}
  -
  +	// Clean up pathInfo 
  +	File file = new File(docBase + pathInfo);
   	String absPath = file.getAbsolutePath();
  -        String docBase = "";
  -
  -	if (context.getDocumentBase().getProtocol().equalsIgnoreCase("war") &&
  -	    context.isWARExpanded()) {
  -	    String s = context.getWARDir().getAbsolutePath();
  -
  -	    docBase = FileUtil.patch(s);
  -	} else {
  -	    docBase = context.getDocumentBase().getFile();
  -	}
  +	
  +	if( debug > 0 ) contextF.log( "DefaultServlet: "  + absPath);
   
           // take care of File.getAbsolutePath() troubles on
           // jdk1.1.x/win
  +        String patchedPath = FileUtil.patch(absPath);
  +	if( debug > 0 && ! absPath.equals(patchedPath)  )
  +	    contextF.log( "DefaultServlet: patched path" + patchedPath );
  +	absPath=patchedPath;
   
  -        absPath = FileUtil.patch(absPath);
  -
  -        if (! docBase.endsWith("/")) {
  -            docBase += "/";
  -        }
  -
           if (isFileMasked(docBase, absPath)) {
   	    response.sendError(response.SC_NOT_FOUND);
   	    return;
  @@ -206,9 +139,9 @@
   
           if (file.isDirectory()) {
   	    // check for welcome file
  -
   	    String welcomeFile = getWelcomeFile(file);
  -
  +	    if( debug > 0 ) contextF.log( "DefaultServlet: welcome file: "  + welcomeFile);
  +	    
   	    if (welcomeFile != null) {
   	        if (requestURI.endsWith("/")) {
   		    String path = requestURI;
  @@ -229,6 +162,7 @@
   		        path = "/" + path;
   		    }
   
  +		    if( debug > 0 ) contextF.log( "DefaultServlet: forward: "  + path + " " + welcomeFile);
   		    ServletContext context =
   		        getServletContext().getContext(contextPath);
   		    RequestDispatcher rd = context.getRequestDispatcher(
  @@ -247,6 +181,7 @@
   		    // do a redirect so that all relative
   		    // urls work correctly
   
  +		    if( debug > 0 ) contextF.log( "DefaultServlet: redirect: "  + requestURI);
   		    if (! inInclude) {
   			response.sendRedirect(requestURI + "/");
   		    }
  @@ -259,9 +194,9 @@
   	} else {
   	    // serve that file
   	    // check that .jsp/ doesn't slip through on Windows!
  -
  -	    if (! url.getFile().endsWith("/") &&
  -	        ! url.getFile().endsWith("\\")) {
  +	    if( debug > 0 ) contextF.log( "DefaultServlet: serving file: "  + file);
  +	    if (! absPath.endsWith("/") &&
  +	        ! absPath.endsWith("\\")) {
   	        serveFile(file, request, response);
   	    } else {
   	        response.sendError(response.SC_NOT_FOUND,
  @@ -292,46 +227,6 @@
   	return welcomeFile;
       }
   
  -    private void serveURL(URL url, HttpServletRequest request,
  -        HttpServletResponse response)
  -    throws IOException {
  -	try {
  -	    URLConnection con = url.openConnection();
  -	    con.connect();
  -
  -	    // XXX
  -	    // lot of work needed here for when reading from a war
  -
  -            String contentType = con.getContentType();
  -	    int contentLength = con.getContentLength();
  -	    String lastModified = Long.toString(con.getLastModified());
  -
  -	    response.setContentType((contentType != null) ?
  -	        contentType : "text/html");
  -	    response.setContentLength((contentLength >= 0) ?
  -	        contentLength : 0);
  -	    response.setHeader("Last-Modified",
  -	        (lastModified != null) ? lastModified : "");
  -
  -	    InputStream in = con.getInputStream();
  -
  -	    serveStream(in, request, response);
  -	    in.close();
  -	} catch (IOException e) {
  -	    // To do a good error msg, first figure out what we're serving
  -
  -	    String requestURI = (String)request.getAttribute(
  -		Constants.Attribute.RequestURI);
  -
  -   	    if (requestURI == null) {
  -	    	requestURI = request.getRequestURI();
  -	    }
  -
  -	    response.sendError(response.SC_NOT_FOUND,
  -                "File Not Found<br>" + requestURI);
  -	}
  -    }
  -    
       private void serveFile(File file, HttpServletRequest request,
           HttpServletResponse response)
       throws IOException {
  @@ -377,7 +272,7 @@
   		}
   	}
   
  -	String mimeType = mimeTypes.getContentTypeFor(file.getName());
  +	String mimeType = contextF.getMimeType( file.getName() );
   
   	if (mimeType == null) {
   	    mimeType = "text/plain";
  
  
  
  1.1                  jakarta-tomcat/src/share/org/apache/tomcat/servlets/WarFileServlet.java
  
  Index: WarFileServlet.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights 
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer. 
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:  
   *       "This product includes software developed by the 
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written 
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */ 
  package org.apache.tomcat.servlets;
  
  import org.apache.tomcat.core.*;
  import org.apache.tomcat.core.Constants;
  import org.apache.tomcat.util.*;
  import java.io.*;
  import java.net.*;
  import java.text.*;
  import java.util.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  
  /**
   * Serve a file from a WAR.
   * 
   * @author James Duncan Davidson [duncan@eng.sun.com]
   * @author Jason Hunter [jch@eng.sun.com]
   * @author James Todd [gonzo@eng.sun.com]
   */
  public class WarFileServlet extends HttpServlet {
      private ServletContextFacade facade;
      private String servletInfo = "DefaultServlet";
      private Context context;
      private MimeMap mimeTypes;
      private String datePattern = "EEE, dd MMM yyyyy HH:mm z";
      private DateFormat dateFormat = new SimpleDateFormat(datePattern);
      
      public void init() throws ServletException {
  	facade = (ServletContextFacade)getServletContext();
  	context = facade.getRealContext();
  	mimeTypes = context.getMimeMap();
      }
  
      public String getServletInfo() {
          return this.servletInfo;
      }
  
      public void doGet(HttpServletRequest request,
          HttpServletResponse response)
      throws ServletException, IOException {
  	String pathInfo = (String)request.getAttribute(
              Constants.Attribute.PathInfo);
  	String requestURI = (String)request.getAttribute(
  	    Constants.Attribute.RequestURI);
  
  	if (pathInfo == null) {
  	    pathInfo = request.getPathInfo();
  	}
  
  	if (requestURI == null) {
  	    requestURI = request.getRequestURI();
  	}
  
  	// XXX XXX BAD BAD BAD - that means another request,
  	// with the same informations !!!!!!!
  	// It should use getMappedPath instead !!!
          URL url = getServletContext().getResource(pathInfo);
  
  	// 	System.out.println("Resource: " + url + " PI: " + pathInfo);
  	if (url != null) {
  	    if (url.getProtocol().equals("war") &&
  	        context.isWARExpanded()) {
  		String s = context.getWARDir().toString() + pathInfo;
  
  		url = URLUtil.resolve(s);
  	    }
  
  	    if (url.getProtocol().equalsIgnoreCase("file")) {
  		// serve file
  
  		File f = new File(url.getFile());
  
                  // this takes care of File.getAbsolutePath()
                  // and File.getName() troubles when running on
                  // JDK 1.1.x/Windows
                  //
                  // BUT IT ALSO ALLOWS THE DREADED ".." AND "jsp." BUGS.
                  // SO IT'S OUTTA HERE!
                  //
  		//f = new File(f.getCanonicalPath());
  
  		if (f.exists()) {
  		    processFile(f, url, request, response);
  		} else {
  		    response.sendError(response.SC_NOT_FOUND,
                          "File Not Found: " + requestURI);
  		}
  	    } else if (url.getProtocol().equalsIgnoreCase("war")) {
  	        // get content from war
  
  	        String documentBase = context.getDocumentBase().toString();
  
  		if (documentBase.endsWith("/")) {
  		    documentBase = documentBase.substring(0,
  		        documentBase.length() - 1);
  		}
   
  	        URL mappedURL = new URL(documentBase + "!" + pathInfo);
  
  		serveURL(mappedURL, request, response);
  	    } else {
  		// get content from url
  
  		serveURL(url, request, response);
  	    }
  	} else {
  	    System.out.println("Got null URL: " + url);
  
  	    response.sendError(response.SC_NOT_FOUND,
                  "File Not Found<br>" + requestURI);
  	}
      }
  
      private void processFile(File file, URL url,
          HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
  	String requestURI = (String)request.getAttribute(
  	    Constants.Attribute.RequestURI);
  
  	if (requestURI == null) {
  	    requestURI = request.getRequestURI();
  	}
  
  	String absPath = file.getAbsolutePath();
          String docBase = "";
  
  	if (context.getDocumentBase().getProtocol().equalsIgnoreCase("war") &&
  	    context.isWARExpanded()) {
  	    String s = context.getWARDir().getAbsolutePath();
  
  	    docBase = FileUtil.patch(s);
  	} else {
  	    docBase = context.getDocumentBase().getFile();
  	}
  
          // take care of File.getAbsolutePath() troubles on
          // jdk1.1.x/win
  
          absPath = FileUtil.patch(absPath);
  
          if (! docBase.endsWith("/")) {
              docBase += "/";
          }
  
          if (isFileMasked(docBase, absPath)) {
  	    response.sendError(response.SC_NOT_FOUND);
  	    return;
          }
  
          if (file.isDirectory()) {
  	    // check for welcome file
  
  	    String welcomeFile = getWelcomeFile(file);
  
  	    if (welcomeFile != null) {
  	        if (requestURI.endsWith("/")) {
  		    String path = requestURI;
  		    String contextPath = context.getPath();
  
  		    if (contextPath.length() == 0) {
  		        contextPath = "/";
  		    }
  
  		    int index = requestURI.indexOf(contextPath);
  
  		    if (index > -1 ) {
  		        path = requestURI.substring(
  			    index + contextPath.length());
  		    }
  
  		    if (! path.startsWith("/")) {
  		        path = "/" + path;
  		    }
  
  		    ServletContext context =
  		        getServletContext().getContext(contextPath);
  		    RequestDispatcher rd = context.getRequestDispatcher(
  			path + welcomeFile);
  
  		    rd.forward(request, response);
  		} else {
  		    boolean inInclude = false;
  		    Object o = request.getAttribute(
                          Constants.Attribute.Dispatch);
  
  		    if (o != null) {
  		        inInclude = true;
  		    }
  
  		    // do a redirect so that all relative
  		    // urls work correctly
  
  		    if (! inInclude) {
  			response.sendRedirect(requestURI + "/");
  		    }
  		}
  	    } else {
  	        // XXX
  	        // ok, see if it's okay to do this
  	        serveDir(file, request, response);
  	    }
  	} else {
  	    // serve that file
  	    // check that .jsp/ doesn't slip through on Windows!
  
  	    if (! url.getFile().endsWith("/") &&
  	        ! url.getFile().endsWith("\\")) {
  	        serveFile(file, request, response);
  	    } else {
  	        response.sendError(response.SC_NOT_FOUND,
                      "File Not Found<br>" + requestURI);
  	    }
  	}
      }
  
      private String getWelcomeFile(File file) {
          String welcomeFile = null;
          Enumeration enum = context.getWelcomeFiles();
  
  	while (enum.hasMoreElements()) {
  	    String fileName = (String)enum.nextElement();
  
              if (fileName != null &&
                  fileName.trim().length() > 0) {
  	        File f = new File(file, fileName);
  
  	        if (f.exists()) {
  	            welcomeFile = fileName;
  
  		    break;
                  }
  	    }
  	}
  
  	return welcomeFile;
      }
  
      private void serveURL(URL url, HttpServletRequest request,
          HttpServletResponse response)
      throws IOException {
  	try {
  	    URLConnection con = url.openConnection();
  	    con.connect();
  
  	    // XXX
  	    // lot of work needed here for when reading from a war
  
              String contentType = con.getContentType();
  	    int contentLength = con.getContentLength();
  	    String lastModified = Long.toString(con.getLastModified());
  
  	    response.setContentType((contentType != null) ?
  	        contentType : "text/html");
  	    response.setContentLength((contentLength >= 0) ?
  	        contentLength : 0);
  	    response.setHeader("Last-Modified",
  	        (lastModified != null) ? lastModified : "");
  
  	    InputStream in = con.getInputStream();
  
  	    serveStream(in, request, response);
  	    in.close();
  	} catch (IOException e) {
  	    // To do a good error msg, first figure out what we're serving
  
  	    String requestURI = (String)request.getAttribute(
  		Constants.Attribute.RequestURI);
  
     	    if (requestURI == null) {
  	    	requestURI = request.getRequestURI();
  	    }
  
  	    response.sendError(response.SC_NOT_FOUND,
                  "File Not Found<br>" + requestURI);
  	}
      }
      
      private void serveFile(File file, HttpServletRequest request,
          HttpServletResponse response)
      throws IOException {
  
  	String absPath = file.getAbsolutePath();
  	String canPath = file.getCanonicalPath();
  
          // take care of File.getAbsolutePath() troubles on
          // jdk1.1.x/win
  
          absPath = FileUtil.patch(absPath);
  
          // This absPath/canPath comparison plugs security holes...
  	// On Windows, makes "x.jsp.", "x.Jsp", and "x.jsp%20" 
          // return 404 instead of the JSP source
  	// On all platforms, makes sure we don't let ../'s through
          // Unfortunately, on Unix, it prevents symlinks from working
  	// So, a check for File.separatorChar='\\' ..... It hopefully
  	// happens on flavors of Windows.
  	if (File.separatorChar  == '\\') { 
  		// On Windows check ignore case....
  		if(!absPath.equalsIgnoreCase(canPath)) {
  	    	response.sendError(response.SC_NOT_FOUND);
  	    	return;
  		}
  	} else {
  		// The following code on Non Windows disallows ../ 
  		// in the path but also disallows symlinks.... 
  		// 
  		// if(!absPath.equals(canPath)) {
  	    	// response.sendError(response.SC_NOT_FOUND);
  	    	// return;
  		// }
  		// instead lets look for ".." in the absolute path
  		// and disallow only that. 
  		// Why should we loose out on symbolic links?
  		//
  
  		if(absPath.indexOf("..") != -1) {
  			// We have .. in the path...
  	    	response.sendError(response.SC_NOT_FOUND);
  	    	return;
  		}
  	}
  
  	String mimeType = mimeTypes.getContentTypeFor(file.getName());
  
  	if (mimeType == null) {
  	    mimeType = "text/plain";
  	}
  
  	response.setContentType(mimeType);
  	response.setContentLength((int)file.length());
  	response.setDateHeader("Last-Modified", file.lastModified());
  
  	FileInputStream in = new FileInputStream(file);
  
  	try {
  	    serveStream(in, request, response);
  	} catch (FileNotFoundException e) {
  	    // Figure out what we're serving
  
  	    String requestURI = (String)request.getAttribute(
  		Constants.Attribute.RequestURI);
  
     	    if (requestURI == null) {
  	    	requestURI = request.getRequestURI();
  	    }
  
  	    response.sendError(response.SC_NOT_FOUND,
                  "File Not Found<br>" + requestURI);
  	} catch (SocketException e) {
  	    return;  // munch
  	} finally {
  	    if (in != null) {
  		in.close();
  	    }
  	}
      }
  
      private void serveStream(InputStream in, HttpServletRequest request,
          HttpServletResponse response)
      throws IOException {
  	// XXX		
  	// ok, here we are trying to figure out if the response has
  	// already been started with a stream or a writer. We really
  	// need to move these flags into the Request and Response objects
  	// in web.core, but I don't want to suffer that big a hit right
  	// before code freeze.
  	// So, we take the preferred track (stream) first, and fall
  	// back to writer.
  
  	try {
  	    ServletOutputStream out = response.getOutputStream();
  	    serveStreamAsStream(in, out);
  	} catch (IllegalStateException ise) {
  	    PrintWriter out = response.getWriter();
  	    serveStreamAsWriter(in, out);
  	}
      }
  
      private void serveStreamAsStream(InputStream in, OutputStream out)
      throws IOException {
  	byte[] buf = new byte[1024];
  	int read = 0;
  
  	while ((read = in.read(buf)) != -1) {
  	    out.write(buf, 0, read);
  	}
      }
  
      private void serveStreamAsWriter(InputStream in, PrintWriter out)
      throws IOException {
  	InputStreamReader r = new InputStreamReader(in);
  	char[] buf = new char[1024];
  	int read = 0;
  
  	while ((read = r.read(buf)) != -1) {
  	    out.write(buf, 0, read);
  	}
      }
      
      private boolean isFileMasked(String docBase, String requestedFile) {
          for (int i = 0; i < Constants.Context.MASKED_DIR.length; i++) {
              String maskFile = Constants.Context.MASKED_DIR[i];
  
              // case insensitive check
              if (requestedFile.toLowerCase().startsWith(
                      FileUtil.patch(docBase + maskFile).toLowerCase())) {
  	        return true;
  	    }
          }
          return false;
      }
  
      private boolean isDirMasked(String basedir, String subdir) {
          // In the future we could make sure to only mask the special
          // directories if they're rooted in the basedir.  That would
          // allow a WEB-INF dir to be served if it's not *the* WEB-INF for
          // example.
          // But to do that would cause a security breach when one context
          // contained another context, since the subcontext would have its
          // hidden dirs displayed.  So for now all masked dirs are masked.
          //
          for (int i = 0; i < Constants.Context.MASKED_DIR.length; i++) {
              if (subdir.equalsIgnoreCase(Constants.Context.MASKED_DIR[i])) {
                  return true;
              }
          }
          return false;
      }
  
      private void serveDir(File file, HttpServletRequest request,
          HttpServletResponse response)
      throws IOException {
  	// XXX
  	// genericize this! put it into another class! especially
  	// important as we should be able to dive into archives
  	// and get this same kind of information in the furture.
  	
  	boolean shaderow = false;
  
  	// Make sure that we don't let ../'s through
  
  	String absPath = file.getAbsolutePath();
  	String canPath = file.getCanonicalPath();
  
          // take care of File.getAbsolutePath() troubles on
          // jdk1.1.x/win
  
          absPath = FileUtil.patch(absPath);
  
  	if (File.separatorChar  == '\\') { 
  		// On Windows check ignore case....
  		if(!absPath.equalsIgnoreCase(canPath)) {
  		    response.sendError(response.SC_NOT_FOUND);
  		    return;
  		}
  	} else {
  		// The following code on Non Windows disallows ../ 
  		// in the path but also disallows symlinks.... 
  		// 
  		// if(!absPath.equals(canPath)) {
  	    	// response.sendError(response.SC_NOT_FOUND);
  	    	// return;
  		// }
  		// instead lets look for ".." in the absolute path
  		// and disallow only that. 
  		// Why should we loose out on symbolic links?
  		//
  
  		if(absPath.indexOf("..") != -1) {
  		    // We have .. in the path...
  		    response.sendError(response.SC_NOT_FOUND);
  		    return;
  		}
  	}
  
  	Vector dirs = new Vector();
  	Vector files = new Vector();
  	String[] fileNames = file.list();
          String docBase = "";
  
          if (context.getDocumentBase().getProtocol().equalsIgnoreCase("war") &&
  	    context.isWARExpanded()) {
  	    String s = context.getWARDir().getAbsolutePath();
  
  	    docBase = FileUtil.patch(s);
  	} else {
  	    docBase = context.getDocumentBase().getFile();
  	}
  
          if (! docBase.endsWith("/")) {
              docBase += "/";
          }
  
  	for (int i = 0; i < fileNames.length; i++) {
  	    String fileName = fileNames[i];
  
  	    File f = new File(file, fileName);
  
              // Make sure dir isn't masked
              if (f.isDirectory() && isDirMasked(docBase, fileName)) {
                  continue;
              }
  
  	    if (f.isDirectory()) {
                  dirs.addElement(f);
  	    } else {
  		files.addElement(f);
  	    }
  	}
  	
  	// Pre-calculate the request URI for efficiency
  
  	String requestURI = request.getRequestURI();
  
  	// Make another URI that definitely ends with a /
  
  	String slashedRequestURI = null;
  
  	if (requestURI.endsWith("/")) {
  	    slashedRequestURI = requestURI;
  	} else {
  	    slashedRequestURI = requestURI + "/";
  	}
  
  	// see if we are in an include
  
  	boolean inInclude = false;
          Object o = request.getAttribute(Constants.Attribute.Dispatch);
  
  	if (o != null) {
  	    inInclude = true;
  	}
  
  	StringBuffer buf = new StringBuffer();
  
  	if (! inInclude) {
  	    response.setContentType("text/html");
  	    buf.append("<html>\r\n");
  	    buf.append("<head>\r\n");
  
  	    // XXX
  	    // i18n
  
  	    buf.append("<title>Directory Listing for: " + requestURI);
  	    buf.append("</title>\r\n</head><body bgcolor=white>\r\n");
  	}
  
  	buf.append("<table width=90% cellspacing=0 ");
  	buf.append("cellpadding=5 align=center>");
  	buf.append("<tr><td colspan=3><font size=+2><strong>");
  	buf.append("Directory Listing for: " + requestURI);
  	buf.append("</strong></td></tr>\r\n");
  
  	if (! requestURI.equals("/")) {
  	    buf.append("<tr><td colspan=3 bgcolor=#ffffff>");
  	    //buf.append("<a href=\"../\">Up one directory");
  
  	    String toPath = requestURI;
  
  	    if (toPath.endsWith("/")) {
  		toPath = toPath.substring(0, toPath.length() - 1);
  	    }
  
  	    toPath = toPath.substring(0, toPath.lastIndexOf("/"));
  
  	    if (toPath.length() == 0) {
  		toPath = "/";
  	    }
  
  	    buf.append("<a href=\"" + toPath + "\"><tt>Up to: " + toPath);
  	    buf.append("</tt></a></td></tr>\r\n");
  	}
  	
  	if (dirs.size() > 0) {
  	    buf.append("<tr><td colspan=3 bgcolor=#cccccc>");
  	    buf.append("<font size=+2><strong>Subdirectories:</strong>\r\n");
  	    buf.append("</font></td></tr>\r\n");
  
  	    Enumeration e = dirs.elements();
  
              while (e.hasMoreElements()) {
                  File f = (File)e.nextElement();
                  String fileName = f.getName();
  
                  buf.append("<tr");
  
                  if (shaderow) {
                      buf.append(" bgcolor=#eeeeee");
                      shaderow = false;
                  } else {
                      shaderow = true;
                  }
  
                  buf.append("><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
                  buf.append("<tt><a href=\"" + slashedRequestURI +
                      fileName + "\">" + fileName +
                      "/</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" +
                      "</tt>\r\n");
                  buf.append("</td><td><tt>&nbsp;&nbsp;</tt></td>");
                  buf.append("<td align=right><tt>");
                  buf.append(dateFormat.format(new Date(f.lastModified())));
                  buf.append("</tt></td></tr>\r\n");
              }
  
  	    buf.append("\r\n");
  	}
  
  	shaderow = false;
  	buf.append("<tr><td colspan=3 bgcolor=#ffffff>&nbsp;</td></tr>");
  
  	if (files.size() > 0) {
  	    buf.append("<tr><td colspan=4 bgcolor=#cccccc>");
  	    buf.append("<font size=+2><strong>Files:</strong>");
  	    buf.append("</font></td></tr>");
  
  	    Enumeration e = files.elements();
  
  	    while (e.hasMoreElements()) {
  		buf.append("<tr");
  
  		if (shaderow) {
  		    buf.append(" bgcolor=#eeeeee");
  		    shaderow = false;
  		} else {
  		    shaderow = true;
  		}
  
  		buf.append("><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\r\n");
  
  		File f = (File)e.nextElement();
  		String fileName = f.getName();
  
  		buf.append("<tt><a href=\"" + slashedRequestURI +
                      fileName + "\">" + fileName + "</a>");
  		buf.append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<tt>");
  		buf.append("</td></td>\r\n<td align=right><tt>");
  
  		int filesize = (int)f.length();
  		int leftside = filesize / 1024;
  		int rightside = (filesize % 1024) / 103;  // makes 1 digit
  
  		// To avoid 0.0 for non-zero file, we bump to 0.1
  
  		if (leftside == 0 && rightside == 0 && filesize != 0) {
  		    rightside = 1;
  		}
  
  		buf.append(leftside + "." + rightside + " KB");
  		buf.append("</tt></td>");
  		buf.append("<td align=right><tt>");
  		buf.append(dateFormat.format(new Date(f.lastModified())));
  		buf.append("</tt></td></tr>\r\n");
  	    }
  
  	    buf.append("\r\n");
  	}
  
  	buf.append("<tr><td colspan=3 bgcolor=#ffffff>&nbsp;</td></tr>");
  	buf.append("<tr><td colspan=3 bgcolor=#cccccc>");
  	buf.append("<font size=-1>");
  	buf.append(Constants.TOMCAT_NAME);
  	buf.append(" v");
  	buf.append(Constants.TOMCAT_VERSION);
  	buf.append("</font></td></tr></table>");
  
  	if (! inInclude) {
  	    buf.append("</body></html>\r\n");
  	}
  
  	String output = buf.toString();
  
  	byte[] bytes = output.getBytes();
  
  	ByteArrayInputStream in = new ByteArrayInputStream(bytes);
  
  	serveStream(in, request, response);
      }
  }