/* ======================================================================
The Bodington System Software License, Version 1.0
 
Copyright (c) 2001 The University of Leeds.  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 acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.
 
4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.
 
5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.
 
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
EVENT SHALL THE UNIVERSITY OF LEEDS 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 was originally created by the University of Leeds and may contain voluntary
contributions from others.  For more information on the Bodington Open Source Project, please
see http://bodington.org/
 
====================================================================== */

package org.bodington.servlet;

import org.apache.log4j.Logger;

import java.util.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
//import java.net.URLEncoder;
import java.rmi.RemoteException;
import java.security.Principal;

import org.bodington.database.*;
import org.bodington.server.*;
import org.bodington.server.events.NavigationEvent;
import org.bodington.server.realm.*;
import org.bodington.server.resources.*;
import org.bodington.servlet.facilities.*;
import org.bodington.servlet.template.*;
import org.bodington.util.Base64Decoder;
import org.bodington.util.URLEncoder;

public class Request extends HttpServletRequestWrapper
{
    private static Logger log = Logger.getLogger(Request.class);
    
    /**
     * The largest request we will process in megabytes.
     */
    public static final int MAX_INPUT=20;
    
    public static final String SESSION_ATTRIBUTE_NAME = "org_bodington_servlet_session";
    public static final String SESSION_ID_COOKIE_NAME = "ORG_BODINGTON_SERVLET_SESSION_ID";
    public static final int REQUEST_TYPE_UNKNOWN	= 0;
    public static final int REQUEST_TYPE_NOT_FOUND	= 1;
    public static final int REQUEST_TYPE_TEMPLATE	= 2;
    public static final int REQUEST_TYPE_DOWNLOAD	= 3;
    public static final int REQUEST_TYPE_GENERATED	= 4;
    public static final int REQUEST_TYPE_VIRTUAL	= 5;
    public static final int REQUEST_TYPE_LOGIN_TEMPLATE	= 6;
    public static final int REQUEST_TYPE_NOT_ALLOWED	= 7;
    public static final int REQUEST_TYPE_SPRING = 8;
    
    public static final String RESOURCE_ATTRIBUTE = "org_bodington_servlet_resource";
    public static final String FACILITY_ATTRIBUTE = "org_bodington_servlet_facility";
    public static final String NAVIGATION_ATTRIBUTE = "org_bodington_server_nagivation";
    public static final String BUILDING_ATTRIBUTE = "org_bodington_server_building";
    
    // the wrapper object so this request can call the underlying
    // object at various points
    private HttpServletRequest httpreq;
    
    // config provides information about the servlet in general
    // the request needs some of the configuration information
    private BuildingServletContext buildingServletContext;
    
    // a context onto the server code.  would like to remove all
    // references to BuildingContext in the servlet code in the
    // future but at the moment servlet code still sometimes
    // goes direct to datastores so the context must be here
    private BuildingContext context = null;
    
    // a session through to the server side code.  would like to
    // develop methods here to avoid the need for the servlet to
    // access datastores directly
    private NavigationSession nav_session=null;
    
    // variables relating to the decoded url
    // ====================================================================
    private int request_type=REQUEST_TYPE_UNKNOWN;

    private Template template;
    
    // The extra path information - i.e. the part of the URL after the URL
    // of the servlet.  This is Bodington encoded to escape the 16 bit
    // unicode characters.
    private String url;

    // This is url split into components using slash as delimiter
    private String[] split_url;
    
    // This is part of url refering to a resource
    private String[] split_url_resource;
    
    // This is part of url refering to a file (last element is file)
    private String[] split_url_file;
    
    private String forwarded_file_url;
    
    // This is part of url refering to parameters (last element is template)
    private String[] split_url_parameters;

    private UploadedFileSummary uploaded_file;
    private String uploaded_file_mime_type;
    // ====================================================================
    
    private boolean authenticated=false;
    private boolean anonymous=false;
    

    private String servlet;
    private Map insert_attributes;
    private Hashtable request_properties;
    
    
    // used for conditional output of HTML
    private boolean switchedoff;
    
    public Request( HttpServletRequest req, BuildingServletContext c )
    throws BuildingServerException, RequestSizeException
    {
	super( req );
	
	if ( c==null )
	    throw new BuildingServerException( "Unable to access configuration data." );


	buildingServletContext = c;
	httpreq=req;
	switchedoff=false;
	
	if ( httpreq.getContentLength() > (MAX_INPUT*1024*1024) )
	    throw new RequestSizeException( MAX_INPUT*1024*1024);
	
	
	// due to various inadequecies of servlet containers form data is
	// parsed in here instead of relying on the web server.
	String content_type=httpreq.getContentType();
	if ( content_type!=null )
	{
	    if ( content_type.startsWith( "multipart/form-data" ) )
	    {
		try
		{
		    readMultiPart();
		}
		catch ( IOException ioex )
		{
		    throw new BuildingServerException( "Problem reading user input: " + ioex );
		}
	    }
	    else if ( content_type.startsWith( "application/x-www-form-urlencoded" ) &&
	    httpreq.getMethod().equalsIgnoreCase( "POST" ) )
	    {
		try
		{
		    readFormData();
		}
		catch ( IOException ioex )
		{
		    throw new BuildingServerException( "Problem reading user input: " + ioex );
		}
	    }
	}

	try
	{
	    // authenticate user and set up session
	    initSession();

	    // process request path to cope with various encodings
	    initPath();

	    // work out which page of which resource is requested
	    initLocation();

	    // work out if the user can have the requested page
	    // and redirect if necessary
	    accessControl();
	}
	catch ( RemoteException remex )
	{
	    log.error( remex.getMessage(), remex );
	    throw new BuildingServerException( "Technical problem connecting to server engine." );
	}

	
	// fetch the template if required
	
	if ( request_type == REQUEST_TYPE_TEMPLATE ||
		request_type == REQUEST_TYPE_LOGIN_TEMPLATE )
	{
	    template = Template.get( getFacility().facilityname,
				      getResource().getImplicitHttpUIStyle(),
				      getResource().getResourceId(),
				      getPageName()    );
	    if ( template == null )
		request_type = REQUEST_TYPE_NOT_FOUND;
	}

	request_properties = new Hashtable();
    }
    
    
    public void setRequest( ServletRequest r )
    {
        // This is an experiment-
        // previously it was assumed that the request that is wrapped is never
        // changed but in fact the servlet container could put another request
        // object into this wrapper with ServletRequestWrapper.setRequest( ... )
        // and if that happens the variable httpreq here is not the same as the
        // parent object's reference and a call to getServletPath could return
        // the wrong thing.  Really this class should either decide to intercept
        // setRequest and use it to maintain its own reference or it should get
        // rid of the httpreq variable and always fetch a reference from the 
        // parent class.  This is a test of the former.
        super.setRequest( r );
        if ( r instanceof HttpServletRequest )
            httpreq = (HttpServletRequest)r;
        else
            httpreq = null;
    }
    
    
    public ServletContext getServletContext()
    {
        return buildingServletContext;
    }
    
    /**
     * Initialize the session. This method is responsible for initializing a
     * user's <em>session</em>. In practice this encompasses a user's
     * {@link org.bodington.servlet.HttpSession} instance and associated
     * {@link NavigationSession}. The main point of interest that occurs here
     * is authentication of the user. A given instance of the web-application,
     * can be configured to permit multiple authentication routes. Therefore,
     * how the session objects get initialized is entirely dependant on the
     * user's chosen method of authentication. This method now delegates it's
     * main work to a chain of {@link SessionInitializer} instances.
     * @see org.bodington.servlet.HttpSession
     * @see NavigationSession
     * @see SessionInitializer
     * @see SessionInitializerChain
     */
    private void initSession() throws BuildingServerException, RemoteException
    {
        // Can do database look ups on this context but must avoid
        // use of server sessions or resources until the user is
        // properly set up.
        context = BuildingContext.startContext();
        if ( context == null )
            throw new BuildingServerException( "Bodington server isn't ready." );
        context.setPoolOwner( getRemoteAddr() );
        context.setUser( buildingServletContext.getAnonymousUser() );

        // TODO: there's probably a better way for the object to acquire this.
        SessionInitializer requestAuthenticator 
            = (SessionInitializer)getServletContext().getAttribute( "session.initializer.root" );

        setNavigationSession(requestAuthenticator.handleRequest( this ));

        // If the user is now authenticated set things up
        if ( getNavigationSession().isAuthenticated() )
        {
            // would like to get rid of BuildingContext in servlet code
            // in the long run
            User user = getNavigationSession().getAuthenticatedUser();
            context.setUser( user );
            context.setPoolOwner( user );
            authenticated = true;
            anonymous = getNavigationSession().isAnonymous();
        }

    }
    
    private void initPath()
    throws BuildingServerException
    {
	servlet=getContextPath() + getServletPath() + "/";
	
	url=httpreq.getPathInfo();
	if ( url == null || url.length() == 0 )
	    url = "/";
	
	// The web server may or may not have correctly url decoded the path.
	// The setup servlet should determine what happens here.
	
	log.debug( "url=" + url );
	
	// enc1 should be UTF-8, none or destructive
	String enc1 = buildingServletContext.getContainerUrlCharacterEncoding();
	String enc2 = buildingServletContext.getUrlCharacterEncoding();
	
	StringTokenizer tokenizer = new StringTokenizer( url, "/" );
	split_url = new String[tokenizer.countTokens()];
	for ( int t=0; tokenizer.hasMoreTokens(); t++ )
	{
	    try
	    {
		split_url[t] = tokenizer.nextToken();
		if ( enc1.equals( "none" ) )
		{
		    // the web server didn't decode the url so do it now with
		    // our chosen encoding
		    split_url[t] = org.bodington.util.URLDecoder.decode( split_url[t], enc2 );
		}
		else
		{
		    if ( buildingServletContext.isContainerEncodingPlus() )
			// web server decoded the url but forgot to do the pluses
			// so do them now.
			split_url[t] = split_url[t].replace( '+', ' ');
		    
		    // if the decoding was different to what we want encode it
		    // back again and decode it the right way
		    if ( !enc1.equals( enc2 ) )
		    {
			// default means the system default char enxcoding
			if ( enc1.equals( "destructive" ) )
			    // this is going to fail for many URLs but it will work
			    // for some file names so its worth trying
			    split_url[t] = org.bodington.util.URLEncoder.encode( split_url[t], "US-ASCII" );
			else
			    // this is relavant if the sysadmin can identify that the
			    // web server is decoding using some non destructive
			    // encoding
			    split_url[t] = org.bodington.util.URLEncoder.encode( split_url[t], enc1 );
			split_url[t] = org.bodington.util.URLDecoder.decode( split_url[t], enc2 );
		    }
		}
		
	    }
	    catch ( java.io.UnsupportedEncodingException uee )
	    {
		// should never happen!!
		log.error( uee.getMessage(), uee );
		split_url[t] = "?";
		throw new BuildingServerException( "Unexpected error decoding requested page address." );
	    }
	}
    }
    
    
    // New URL coding format!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // urls are like;
    // a)   /servlet/location/location/location/
    // b)   /servlet/location/location/location/bs_template_templatename.html
    // c)   /servlet/location/location/location/param/param/param/bs_template_templatename.html
    // d)	/servlet/location/location/location/folder/folder/folder/
    // e)	/servlet/location/location/location/folder/folder/folder/uploadedfilename.xxx
    // f)   /servlet/location/location/location/param/param/param/bs_generated_name.html
    // g)   /servlet/location/location/location/param/param/param/bs_virtual_name.html
    
    // Uses:
    
    // a)	Equivalent to /servlet/location/location/bs_template_index.html
    // b)	To show an HTML template with inserts
    // c)	To show an HTML template with inserts and with parameters passed
    // d)	To return a file listing.
    // e)	To return a file belonging to the resource without processing.
    // f)	To return a file that has been generated by the system and stored on the server.
    // g)	To return a completely synthetic file.
    
   
    // /servlet/location/location/location/param/param/param/
    // doesn't work because it will be interpreted as a folder
    
    // servlet path already removed from path parameter
    private void initLocation()
    throws BuildingServerException
    {
	int page;
	int i;
	boolean in_location;
    
	uploaded_file=null;
	
	uploaded_file_mime_type=null;
	request_type=REQUEST_TYPE_UNKNOWN;
	
	setResource(null);
	setFacility(null);
	BuildingContext context = BuildingContext.getContext();
	if ( context == null )
	    return;
	
	PrimaryKey res_id_final, res_id_current;
	
	// Split up url and work out which part refers to
	// resource, which part to parameters, which part to uploaded file
	// etc.
	res_id_final = getNavigationSession().findResourceId( null );
	split_url_resource = new String[0];

	if ( res_id_final == null )
	{
		request_type=REQUEST_TYPE_NOT_FOUND;
		return;
	}


	// assume in location path until first token that doesn't fit.
	in_location = true;
	for ( int t=0; t<split_url.length; t++ )
	{
		if ( in_location )
		{
			// set up putative split url for resource
			split_url_resource = new String[t+1];
			System.arraycopy( split_url, 0, split_url_resource, 0, t+1 );

			// is it a resource?
			res_id_current = null;
			res_id_current = getNavigationSession().findResourceId( split_url_resource );

			// if not last position must have been the resource
			if ( res_id_current == null )
			{
				in_location = false;
				split_url_resource = new String[t];
				System.arraycopy( split_url, 0, split_url_resource, 0, t );
			}
			else
				// further into tree so move in
				res_id_final = res_id_current;
		}
	}

	// we have the resource so do all necessary initialisation
	setResource(getNavigationSession().findResource( res_id_final ));
	context.setResource( getResource() );
	FacilityList fl = FacilityList.getFacilities();
	setFacility(fl.get( new Integer( getResource().getHttpFacilityNo() ) ));


	// what to do with remaining parts of path?
	// may be parameters + template or
	// folders + file
	// guess the former
	split_url_parameters = new String[split_url.length-split_url_resource.length];
	System.arraycopy( split_url, split_url_resource.length,
	split_url_parameters, 0, split_url_parameters.length );
	
	
	// if there are no more parts to the url this is interpreted as a
	// request for the index.html template page
	if ( split_url_parameters.length == 0 )
	{
	    //looking at root of resource so we can use index template
	    split_url_parameters = new String[1];
	    split_url_parameters [0] = "index.html";
	    request_type=REQUEST_TYPE_TEMPLATE;
	    return;
	}
	
	
	page = split_url_parameters.length-1;
	if (  split_url_parameters[page]!=null && split_url_parameters[page].startsWith( "bs_" ) )
	{
	if ( split_url_parameters[page].startsWith( "bs_template_" ) )
	{
	    // trim off the prefix to get the file name of the template.
	    split_url_parameters[page] = split_url_parameters[page].substring( 12 );
	    request_type=REQUEST_TYPE_TEMPLATE;
	    return;
	}
	
	if ( split_url_parameters[page].startsWith( "bs_generated_" ) )
	{
	    // trim off the prefix to get the file name of the generated file.
	    split_url_parameters[page] = split_url_parameters[page].substring( 13 );
	    if ( !isGeneratedFileNameLegal( split_url_parameters[page] ) )
	    {
		request_type=REQUEST_TYPE_UNKNOWN;
		return;
	    }
	    request_type=REQUEST_TYPE_GENERATED;
	    forwarded_file_url = ResourceUtils.getGeneratedFileAddress(getResource(), this) + split_url_parameters[page];
	    return;
	}
	
	if ( split_url_parameters[page].startsWith( "bs_virtual_" ) )
	{
	    // trim off the prefix to get the file name of the virtual file.
	    split_url_parameters[page] = split_url_parameters[page].substring( 11 );
	    if ( !isGeneratedFileNameLegal( split_url_parameters[page] ) )
	    {
		request_type=REQUEST_TYPE_UNKNOWN;
		return;
	    }
	    request_type=REQUEST_TYPE_VIRTUAL;
	    return;
	}
	if ( split_url_parameters[page].startsWith("bs_spring_") )
	{
	    // trim off the prefix to get the file name of the spring URL
	    split_url_parameters[page] = split_url_parameters[page].substring( 10 );
	    if ( !isGeneratedFileNameLegal( split_url_parameters[page] ) )
	    {
		request_type=REQUEST_TYPE_UNKNOWN;
		return;
	    }
	    request_type=REQUEST_TYPE_SPRING;
	    return;
	}
	
	request_type=REQUEST_TYPE_UNKNOWN;
	return;
	}
	
	// not a template/generated/virtual page so must be upload folder or uploaded file
	
	// not a template so must be upload folder or uploaded file
	split_url_file = split_url_parameters;
	split_url_parameters = null;
	
	// look for the file description...
	uploaded_file = BuildingSessionManagerImpl
				.getSession(getResource()).getUploadedFileSession()
				.getFileSummary(split_url_file);
	
	// could be null if it doesn't exist
	if ( uploaded_file==null || uploaded_file.isDeleted() || uploaded_file.isFolder() )
	{
	request_type=REQUEST_TYPE_NOT_FOUND;
	return;
	}
	
	request_type=REQUEST_TYPE_DOWNLOAD;
	String webpub = ResourceUtils.getWebPublishAddress(getResource(), this);
	String filepub = getBuildingSession().getUploadedFileSession().getRealURL( uploaded_file.getUploadedFileId() );
	// could be null if the file isn't accessible because it is deleted
	// it isn't a file (its a folder) or the file isn't completly uploaded
	if ( filepub == null )
	{
	request_type=REQUEST_TYPE_NOT_FOUND;
	return;
	}
	forwarded_file_url = webpub + filepub;
	uploaded_file_mime_type = uploaded_file.getMimeType();
	
    }

    
    private void resetToLoginTemplate( String page )
    throws BuildingServerException
    {
	// sets the resource to the site root resource
	// and flags the request as a login request
	//try
	//{
	    // let's not change resource to root - this allows different
	    // login templates for different parts of the web site.
	    
	    //PrimaryKey res_id_final = nav_session.findResourceId( null );
	    //resource=nav_session.findResource( res_id_final );
	    split_url = new String[1];
	    split_url[0] = "bs_template_" + page;
	    split_url_resource = new String[0];
	    split_url_parameters = new String[1];
	    split_url_parameters[0] = page;
	    split_url_file = null;
	    request_type=REQUEST_TYPE_LOGIN_TEMPLATE;
	    // nav_session.setResource( res_id_final );
	    // context.setResource( resource );
	    // FacilityList fl = FacilityList.getFacilities();
	    // facility = fl.get( new Integer( resource.getHttpFacilityNo() ) );
	//}
	//catch ( java.rmi.RemoteException rex )
	//{
	//   Logger.getLogger( "org.bodington" ).logp(
	//    Level.SEVERE,
	//    "Request",
	//    "resetToLoginTemplate",
	//    rex.getMessage(),
	//    rex );
	//    throw new BuildingServerException( "Unable to connect through to server system to select page." );
	//}
    }
    
    
    private void accessControl()
    throws BuildingServerException, RemoteException
    {
	// already have these variable set up at class level
        // so don't need local variables
        
	//HttpSession http_session = (HttpSession)getSession( false );
	//NavigationSession nav_session = http_session.getServerNavigationSession();
	
	boolean has_view_access= context.checkPermission( Permission.VIEW );
	boolean attempting_open_access =  !getNavigationSession().isAuthenticated();
	boolean attempting_anonymous_access = getNavigationSession().isAnonymous();
	String page = getPageName();

	
	// unknown page requests are passed through because they
	// only result in error pages anyway.
	if ( request_type == REQUEST_TYPE_UNKNOWN )
	    return;
	
	// template graphics and style sheets are always let through
	if ( request_type == REQUEST_TYPE_TEMPLATE && 
		( page.endsWith( ".gif" ) || 
		  page.endsWith( ".jpg" ) || 
		  page.endsWith( ".css" ) )		)
	    return;
	
	// virtual style sheets are always let through
	if ( request_type == REQUEST_TYPE_VIRTUAL && 
		  page.endsWith( ".css" )		)
	    return;
    
    if ( request_type == REQUEST_TYPE_VIRTUAL && 
                  page.startsWith("logout"))
                return;
    
	// Index and top always go through.
    if ( request_type == REQUEST_TYPE_TEMPLATE && (page.startsWith( "index.html") || page.startsWith( "top.html") || page.startsWith("error403.html")))
        return;
                    
	// requests specifically for login pages are always let through
	// but are always redirected to the root resource
	if ( request_type == REQUEST_TYPE_TEMPLATE && 
		page.startsWith( "login" ) )
	{
	    // signal that this is a template
	    resetToLoginTemplate( page ); 
	    return;
	}

	
	// all other page requests must be authenticated even if it
	// is only anonymous authentication
	if ( !authenticated )
	{
	    // redirect to main login page
	    resetToLoginTemplate( "login.html" ); 
	    return;
	}
	

	// user is authenticated - if they have view access give them
	// what they asked for
	if ( has_view_access )
	    return;
	
	request_type = REQUEST_TYPE_NOT_ALLOWED;
    }
    
    

    
    
    private boolean isGeneratedFileNameLegal( String name )
    {
	char c;
	
	for ( int i=0; i<name.length(); i++ )
	{
	    c = name.charAt( i );
	    if ( Character.isLetterOrDigit( c ) )
		continue;
	    if ( c == '_' || c == '.' )
		continue;
	    return false;
	}
	
	return true;
    }
    
    
    
    
    
    public String absoluteURL()
    {
	String temp=servlet;
	
	for ( int i=0; i<split_url_resource.length; i++ )
	{
	    temp+=split_url_resource[i];
	    temp+="/";
	}
	return temp;
    }
    
    
    public int getTemplateParameterCount()
    {
	if ( split_url_parameters == null )
	    return 0;
	if ( split_url_parameters.length<2 )
	    return 0;
	return split_url_parameters.length-1;
    }
    
    public String getTemplateParameter( int i )
    {
	if ( split_url_parameters == null )
	    return null;
	if ( i<0 || i>=(split_url_parameters.length-1) )
	    return null;
	return split_url_parameters[i];
    }
    
    public String getPageName()
    {
	if ( split_url_parameters == null )
	    return null;
	if ( split_url_parameters.length < 1 )
	    return null;
	
	return split_url_parameters[split_url_parameters.length-1];
    }
    
    public String getForwardedFileURL()
    {
	return forwarded_file_url;
    }
    
    Hashtable data, filenames, formdata;
    DataInputStream input;
    String delimitor;
    String parameter_name, filename;
    static int inc=10000;
    
    /**
     * Split up the request into files.
     * Checks that we havn't exceeded the upload limit as the HTTP
     * header may be false.
     * @throws BuildingServerException
     * @throws IOException
     */
    private void readMultiPart()
    throws RequestSizeException, IOException
    {
	data = new Hashtable();
	filenames = new Hashtable();
	input = new DataInputStream( new BufferedInputStream( httpreq.getInputStream() ) );
	//first line of multipart is the delimitor
	delimitor    = input.readLine();
	File file;
	long total=0;
	
	while ( getNextPart() )
	{
	    if ( filename==null )
	    {
		data.put( parameter_name, readPart() );
	    }
	    else
	    {
		//file = new File( "c:\\temp\\", Integer.toString( inc++ ) + ".bin" );
		file = BuildingContext.createTempFile( "form", ".bin" );
		FileOutputStream fout = new FileOutputStream( file );
		total+=readPartToOutput( fout );
		fout.close();
		
		data.put( parameter_name, file.getPath() );
		filenames.put( parameter_name, filename );
		
		if ( total> (MAX_INPUT*1024*1024) )
		    throw new RequestSizeException(MAX_INPUT*1024*1024);
		
	    }
	}
	Enumeration enumeration = data.keys();
	String name, fname;
	while ( enumeration.hasMoreElements() )
	{
	    name = (String) enumeration.nextElement();
	    log.debug( "Multipart HTTP Part Name: " );
	    log.debug( name );
	    log.debug( "Multipart HTTP Part Value: " );
	    log.debug( (String)data.get( name ) );
	    fname = (String)filenames.get( name );
	    if ( fname!=null )
	    {
		log.debug( "Multipart HTTP Part File: " );
		log.debug( fname );
	    }
	}
    }
    
    
    private void readFormData()
    throws BuildingServerException, IOException
    {
	InputStream in = new BufferedInputStream( httpreq.getInputStream() );
	int n = httpreq.getContentLength();
	int offset, i;
	boolean in_hex=false;
	int hexed=0;
	int datum, digit, digits=0;
	String name, value;
	
	formdata = new Hashtable();
	
	// read the whole thing into a byte array
	// treating url escaped codes as "byte" codes not
	// character codes
	
	// buffer is more than big enough because escaped
	// bytes are sent as 3 bytes but stored as one and only one
	// field at a time is put in it.
	byte[] raw = new byte[n];
	
	int index_to_assignment=0;
	
	offset = 0;
	datum=0;
	while ( datum >= 0 )
	{
	    datum=in.read();
	    
	    if ( in_hex )
	    {
		digit = Character.digit( (char)datum, 16 );
		if ( digit == -1 )
		    throw new IllegalArgumentException( "Illegal escaped character code in form data." );
		
		hexed = (hexed*16) + digit;
		
		if ( digits == 1 )
		{
		    raw[offset++] = (byte)hexed;
		    in_hex=false;
		}
		else
		    digits++;
	    }
	    else
	    {
		if ( datum == '%' )
		{
		    in_hex = true;
		    hexed=0;
		    digits=0;
		}
		else if ( datum == '+' )
		    raw[offset++] = (byte)' ';
		else if ( datum == '&' || datum == -1 )
		{
		    //reached end of field
		    
		    // if field was not empty parse it
		    if ( offset>0 )
		    {
			//if there is data but no field name bomb out
			if ( index_to_assignment == 0 )
			    throw new IllegalArgumentException( "Illegal form data." );
			
			
			name = new String( raw, 0, index_to_assignment, "UTF8");
			value = new String( raw, index_to_assignment + 1, offset-(index_to_assignment + 1), "UTF8");
			
			
			addFormField( formdata, name, value );
		    }
		    
		    //reset counters for next field
		    offset=0;
		    index_to_assignment=0;
		}
		else if ( datum == '=' )
		{
		    index_to_assignment = offset;
		    raw[offset++] = (byte)datum;
		}
		else
		    raw[offset++] = (byte)datum;
	    }
	}
    }
    
    public static void addFormField( Hashtable hashtable, String name, String value )
    {
	String[] valuelist;
	String[] newlist;
	
	if ( hashtable.containsKey( name ) )
	{
	    valuelist = (String[])hashtable.get( name );
	    newlist = new String[valuelist.length + 1];
	    for( int j = 0; j < valuelist.length; j++)
		newlist[j] = valuelist[j];
	    
	    newlist[valuelist.length] = value;
	}
	else
	{
	    newlist = new String[1];
	    newlist[0] = value;
	}
	
	hashtable.put( name, newlist );
    }
    
    
    //reads headers and lines up on data section of part
    private boolean getNextPart()
    {
	String paramName = null;
	try
	{
	    String lineIn = null;
	    
	    parameter_name=null;
	    filename=null;
	    //keep looping on header lines until empty line or end of file.
	    while ( ( lineIn=input.readLine() ) != null && lineIn.length()>0 )
	    {
		if ( lineIn.indexOf( "name=" ) != -1 )
		{
		    //- The next line contains the parameter name
		    int s = lineIn.indexOf( "name=" );
		    int e = lineIn.indexOf( "\"", s+6 );
		    parameter_name = lineIn.substring( s+6, e );
		    
		    if ( lineIn.indexOf( "filename=") != -1 )
		    {
			s = lineIn.indexOf( "filename=" );
			e = lineIn.indexOf( "\"", s+10 );
			filename = lineIn.substring( s+10, e );
			if ( filename.lastIndexOf( "\\" ) != -1 )
			    filename = filename.substring( filename.lastIndexOf( "\\" )+1 );
			
			if ( filename.length() == 0 )
			    filename = null;
			else
			    filename = new String( filename.toString().getBytes( "ISO8859_1" ), "UTF8" );
		    }
		}
	    }
	}
	catch( Exception E )
	{}
	
	if ( parameter_name!=null )
	    return true;
	
	return false;
    }
    
    int readPartToOutput( OutputStream _Out )
    {
	byte buffer[] = new byte[1024];
	byte tbuffer[] = new byte[delimitor.length()+3];
	int x = 0;
	int length = 0;
	byte charIn;
	
	try
	{
	    while ( true )
	    {
		charIn = input.readByte();
		//may be start of delimitor
		//loop on short lines
		if ( charIn == '\r' )
		{
		    while ( charIn == '\r' )
		    {
			//flush through any outstanding bytes
			_Out.write( buffer, 0, x );
			length+=x;
			x = 0;
			
			//- Check for delimitor
			int y=0;
			do
			{
			    tbuffer[y] = charIn;
			    y++;
			    charIn    = input.readByte();
			}
			while ( charIn != '\r' && y < delimitor.length()+1 );
			
			//if we got a line feed too early
			//output the line (without the line feed)
			//and go round again
			if ( charIn =='\r' )
			{
			    _Out.write( tbuffer, 0, y );
			    length+=y;
			    continue;
			}
			
			//add the last byte (which wasn't a line feed)
			tbuffer[y] = charIn;
			y++;
			
			//we have a buffer which is the right length
			//and can test it against the delimitor
			//(we avoid the line feed at the start of the buffer).
			String temp = new String( tbuffer, 0, 2, delimitor.length() );
			if ( temp.indexOf( delimitor ) != -1 )
			{
			    //Logger.getLogger("org.bodington").fine( "normal ending" );
			    //if we reached the delimitor line the end of the line wasn't read
			    //so now read to end of delimitor line so ready to read next part
			    input.readLine();
			    
			    return length;
			}
			
			//if it wasn't the delimitor after all, just output it all.
			_Out.write( tbuffer, 0, y );
			length+=y;
		    }
		}
		else
		{
		    buffer[x] = charIn;
		    x++;
		    if ( x == 1023 )
		    {
			_Out.write( buffer, 0, x );
			length+=x;
			x = 0;
		    }
		}
	    }
	    
	}
	catch ( EOFException eofe)
	{
	    log.info("EOF reading from input. Probably client disconnected or parse error.");
	}
	catch( IOException e )
	{
	    log.error(e);
	}

	return -1;
    }
    
    
    String readPart()
    {
	String line;
	StringBuffer value = new StringBuffer();
	int i;
	try
	{
	    for ( i=0; ( line=input.readLine() ) != null; i++ )
	    {
		if ( line.startsWith( delimitor ) )
		    return new String( value.toString().getBytes( "ISO8859_1" ), "UTF8" );
		if ( i>0 )
		    value.append( "\n" );
		value.append( line );
	    }
	}
	catch( IOException E )
	{}
	
	return null;
    }
    
    
    /**
     * Returns the parameter map. Doesn't do any of the bodington botching and probably
     * doesn't work in all cases. Use at your own risk.
     */
    public Map getParameterMap()
    {
	return formdata;
    }
    
    public Enumeration getParameterNames()
    {
	if ( formdata==null && data==null )
	    return httpreq.getParameterNames();
	if ( data==null )
	    return formdata.keys();
	return data.keys();
    }
    
    public String[] getParameterValues( java.lang.String name )
    {
	if ( formdata==null && data==null )
	{
	    String[] list = httpreq.getParameterValues( name );
	    if ( list == null )
		return null;
	    try
	    {
		for ( int i=0; i<list.length; i++ )
		    list[i] = new String( list[i].getBytes( "ISO8859_1" ), "UTF8" );
		return list;
	    }
	    catch ( UnsupportedEncodingException ueex )
	    {
		// should never happen!
	    }
	    
	    return null;
	}
	
	if ( data==null )
	{
	    return (String[])formdata.get( name );
	}
	
	String[] v = new String[1];
	v[0] = (String)data.get( name );
	if ( v[0]==null )
	    return null;
	return v;
    }
    
    public String getParameter( java.lang.String name )
    {
	int i;
	
	if ( data==null && formdata == null)
	{
	    byte[] raw;
	    String unmangled;
	    String mangled = httpreq.getParameter( name );
	    if ( mangled == null )
		return null;
	    
	    
	    try
	    {
		raw = mangled.getBytes( "ISO8859_1" );
		log.debug( "raw data size " + raw.length );
		
		
		unmangled = new String( raw, "UTF8" );
		log.debug( "unmangled parameter " + unmangled.length() );
		log.debug( unmangled );
		return unmangled;
	    }
	    catch ( UnsupportedEncodingException ueex )
	    {
	        // should never happen!
	        log.error( ueex.getMessage(), ueex );
	    }
	    
	    return null;
	}
	
	if ( data==null )
	{
	    String[] plist = (String[])formdata.get( name );
	    if ( plist!=null )
		return plist[0];
	    return null;
	}
	
	return (String)data.get( name );
    }
    
    public String getParameterFileName(  String name )
    {
	if ( filenames == null )
	    return null;
	return (String)filenames.get( name );
    }
    

    /**
     * Set the attributes used in an insert call which is typically
     * an old style template building tag. This is used to pass the
     * attributes from the template processor to the facility.
     * @see LegacyTemplateProcessor
     * @param a A Hashtable of the attributes.
     */
    public void setInsertAttributes( Map a )
    {
	insert_attributes = a;
    }
    
    /**
     * Gets one of the attributes from an insert.
     * This is used to get attributes from a building tag set by
     * the legacy template code in the facility methods.
     * @see LegacyTemplateProcessor
     * @param name The attribute to get.
     * @param def The default value to return.
     * @return The value of the attribute, which should never be
     * null unless the default is.
     */
    public String getInsertAttribute( String name, String def )
    {
	if ( insert_attributes == null )
	    return def;
	Object a = insert_attributes.get( name );
	if ( a==null )
	    return def;
	if ( a instanceof String )
	    return (String)a;
	return a.toString();
    }
    
    /**
     * Get the insert comand that was called.
     * This is the command attribute from a building command.
     * Eg: &lt;building command=mycommand name=myname&gt;
     * would have an insertcommand of mycommand.
     * @see LegacyTemplateProcessor
     * @return The command attribute from a building tag.
     */
    public String getInsertCommand()
    {
	return getInsertAttribute( "command", null );
    }
    
    /**
     * Gets the insert name.
     * This is the name attribute from a building command.
     * Eg: &lt;building command=mycommand name=myname&gt;
     * would have an insertname of myname.
     * @see LegacyTemplateProcessor
     * @return The name attribute or null if we didn't have one.
     */
    public String getInsertName()
    {
	return getInsertAttribute( "name", null );
    }
    
    
    public Object getRequestProperty( String name )
    {
	return request_properties.get( name );
    }
    public Object setRequestProperty( String name, Object thing )
    {
	return request_properties.put( name, thing );
    }
    
 
    
    public String getContextPath()
    {
	String p = httpreq.getContextPath();
	
	// chop off trailing slash if there is one
	if ( p.endsWith( "/" ) )
	    return p.substring( 0, p.length()-1 );
	
	return p;
    }
    
    public String getServletPath()
    {
	String p = httpreq.getServletPath();
	
	// chop off trailing slash if there is one
	if ( p.endsWith( "/" ) )
	    return p.substring( 0, p.length()-1 );
	
	return p;
    }
    

    /**
     * This gets the method through which the authentication was done
     * internally to Bodington.
     */
    public String getAuthType()
    {
	return (String)getSession().getAttribute( "org.bodington.servlet.auth_type" );
    }
    
    /**
     * Returns the authenticated Bodington user.
     */
    public Principal getUserPrincipal()
    {
        try
        {
            return getNavigationSession().getAuthenticatedUser();
        }
        catch (Exception e)
        {
            return null;
        }
    }
  
  
    public synchronized javax.servlet.http.HttpSession getSession()
    {
	return getSession( true );
    }
    
    public synchronized javax.servlet.http.HttpSession getSession( boolean create )
    {
	Object att = getAttribute( SESSION_ATTRIBUTE_NAME );
	if ( att!=null )
	{
	    if ( att instanceof javax.servlet.http.HttpSession )
		return (javax.servlet.http.HttpSession)att;
	}
	
	if ( !create )
	    return null;
	
	org.bodington.servlet.HttpSession session = new org.bodington.servlet.HttpSession();
    session.setServletContext( getServletContext() );
    // this is a bodington specific initialisation.  It give the session a
    // chance to find things out about the user agent once only at start of
    // session, for example is the user using a PDA or handheld.
    session.init( this );
	setAttribute( SESSION_ATTRIBUTE_NAME, session );

	return session;
    }

    public NavigationSession getServerNavigationSession()
    {
        return getNavigationSession();
    }
    
    public UploadedFileSummary getUploadedFile()
    {
	return uploaded_file;
    }
    
    public String getUploadedFileMimeType()
    {
	return uploaded_file_mime_type;
    }
    
    public int getRequestType()
    {
	return request_type;
    }
    
    public boolean isAuthenticated()
    {
	return authenticated;
    }

    public String getAuthenticationError()
    {
	try
	{
	    String s = getNavigationSession().getAuthenticationError();
	    if ( s == null ) return "";
	    return s;
	}
	catch ( Exception e )
	{
	    log.error( e.getMessage(), e );
	    return "";
	}
    }

    public boolean isAnonymous()
    {
	return anonymous;
    }

    /**
     * Get the User ID for the request.
     * Any exceptions thrown get ignored.
     * @see NavigationSession#getAuthenticatedUserId()
     * @return PrimaryKey of the user.
     */
    public PrimaryKey getUserId()
    {
        try
        {
            return getServerNavigationSession().getAuthenticatedUserId();
        }
        catch (Exception e) {}
        return null;
    }


    // This method added so Bodington can forward or include foreign servlets and those
    // servlets can be given an identifier for the user logged into Bodington.
    // The user_id is sent because this is the only universal identifier that every
    // user has - it's possible for a user NOT to have a user_name.
    // Specifically, this has been added for interoperability with Shibboleth origin servlets.
    public String getRemoteUser()
    {
        if ( !this.isAuthenticated() ) return null;
        if ( this.isAnonymous() ) return null;
        if ( getUserId() == null ) return null;
        return getUserId().toString();
    }
    
    public Template getTemplate()
    {
	return template;
    }
    
    public Facility getFacility()
    {
	return (Facility)getAttribute(FACILITY_ATTRIBUTE);
    }
    
    protected void setFacility(Facility facility)
    {
        setAttribute(FACILITY_ATTRIBUTE, facility);
    }
    
    public Resource getResource()
    {
	return (Resource)getAttribute(RESOURCE_ATTRIBUTE);
    }
    
    protected void setResource(Resource resource)
    {
        setAttribute(RESOURCE_ATTRIBUTE, resource);
    }

    
    public boolean isSwitchedOff()
    {
	return switchedoff;
    }
    
    public void setSwitchedOff( boolean b )
    {
	switchedoff = b;
    }
    
    
    public void finalize()
    throws Throwable
    {
	if ( filenames != null && data !=null )
	{
	    Enumeration enumeration = data.keys();
	    String name, fname;
	    File file;
	    while ( enumeration.hasMoreElements() )
	    {
		name = (String)enumeration.nextElement();
		fname = (String)data.get( name );
		if ( fname == null )
		    continue;
		file = new File( fname );
		if ( file.exists() )
		    file.delete();
	    }
	    filenames.clear();
	    data.clear();
	}
    }

    public BuildingSession getBuildingSession()
    {
    	BuildingSession session = (BuildingSession)getAttribute(BUILDING_ATTRIBUTE);
    	if (session == null)
    	{
    		try
    		{
				session = BuildingSessionManagerImpl.getSession(getResource());
				setAttribute(BUILDING_ATTRIBUTE, session);
			}
    		catch (BuildingServerException e)
    		{
				log.error("Can't get BuildingSession for: "+ getResource(), e);
			}
    	}
    	return session;
    }

    /**
     * @param nav_session The nav_session to set.
     */
    private void setNavigationSession(NavigationSession nav_session)
    {
        setAttribute(NAVIGATION_ATTRIBUTE, nav_session);
    }


    /**
     * @return Returns the nav_session.
     */
    private NavigationSession getNavigationSession()
    {
        return (NavigationSession)getAttribute(NAVIGATION_ATTRIBUTE);
    }
}
