head	1.1;
access;
symbols;
locks; strict;
comment	@# @;


1.1
date	2001.07.17.03.52.41;	author dlr;	state Exp;
branches;
next	;


desc
@@


1.1
log
@Ported from Joist.  JR noticed this was necessary for a clean build (I must not have run the clean target).
@
text
@/* ================================================================
 * Copyright (c) 2000 CollabNet.  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 CollabNet <http://www.collab.net/>."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 * 
 * 5. Products derived from this software may not use the "Tigris" or 
 * "Scarab" names nor may "Tigris" or "Scarab" appear in their names without 
 * prior written permission of CollabNet.
 * 
 * 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 COLLABNET 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 CollabNet.
 */ 
package org.tigris.helm.publish;

// Don't import .* because we are replacing HttpURLConnection.
//
import java.net.URL;
import java.net.MalformedURLException;

// Replacement for HttpURLConnection.
//
import HTTPClient.*;

import java.io.*;
import java.util.*;

// Java Servlet Classes
//
import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.turbine.util.Log;


/**
 * Proxy a request for Noodle.
 *
 * This is the core class for Noodle; it does the actual proxying of
 * the request.
 *
 * TODO: This class looks like it needs to be rewritten so it can be 
 * instantiated.  All methods are unsynchronized statics.  Check.
 * TODO: Read the SERVER_NAME, SERVER_PORT and SCHEME from token.
 *  
 * @@author <a href="mailto:jon@@collab.net">Jon S. Stevens</a>
 * @@author <a href="mailto:stack@@collab.net">St.Ack</a>
 * @@version $Id: ProxyModule.java,v 1.13 2001/06/19 01:57:09 stack Exp $
 */
public class ProxyModule
{
    /** 
     * Debug flag. Set to 'true' to see debug logging.
     */
    private static final boolean DEBUG = false;

    /** 
     * Default server to connect to. 
     */
    private static String SERVER_NAME = "localhost";

    /** 
     * Default port to connect to. 
     */
    private static int SERVER_PORT = 80;

    /** 
     * Default scheme. You can't change it right now. 
     */
    private static final String SCHEME = "http";

    /** 
     * Used when no content type is set on the proxy return. 
     */
    private static final String DEFAULT_CONTENT_TYPE = "text/html";    

    /** 
     * Key to pull what Noodle is to proxy from the query string.
     *
     * Public rather than private so it can be used by other classes.
     */
    public static final String PAGE_VAR = "page";

    /** 
     * Define for the string 'POST'.
     */
    public static final String POST = "POST";

    /** 
     * Define for the string 'GET'.  
     */
    public static final String GET = "GET";

    /** 
     * Define for the string 'PUT'.  
     */
    public static final String PUT = "PUT";

    /** 
     * Define for the string 'HEAD'.  
     */
    public static final String HEAD = "HEAD";
	

	
    /**
     * Proxy a request.
     *
     * This is the meat of everything. It does the proxy of the request 
     * including applying the pre/post filters when necessary.
     *
     * @@param noodleData Noodle Data.  Has what we need to make the request
     *        and its in here that we put the the request results.
     *
     * @@exception Exception 
     *
     * @@see #proxyRequest( NoodleData, String )
     */
    public static final void proxyRequest( NoodleData noodleData )
        throws Exception
    {
		proxyRequest( noodleData, null );
    }

    /**
     * Proxy a request.
     *
     * This is the meat of everything. It does the proxy of the request 
     * including applying the pre/post filters when necessary.
     * 
     * @@param noodleData Noodle Data.  Has what we need to make the request
     *        and its in here that we put the the request results.
     * @@param inSavedOffQuery req.getQueryString can return a null query 
     *        string in circumstance where not logged in to Helm. If the 
     *        caller has squirreled away the original query, pass it here.  
     *        May be null.
     * 
     * @@exception Exception
     */
    public static final void proxyRequest( NoodleData noodleData,
	        String inSavedOffQuery )
        throws Exception
    {
        HttpServletRequest req = noodleData.getRequest( );
        HttpServletResponse res = noodleData.getResponse( );

        String query = getQueryString( req, inSavedOffQuery );
        String queryMinusPAGE_VAR = stripQueryString( query, PAGE_VAR );
        noodleData.setQueryData( queryMinusPAGE_VAR );        

        String page 
            = noodleData.postProcessPageName( getPageToProxy( req, query ) );
        URL url = getURL( page );
        noodleData.setURL( url );

        // Make up a URI that is the page to get plus the query string
        // stripped of PAGE_VAR item.
        //
        StringBuffer proxyURI = new StringBuffer( url.getFile( ) );
        if( queryMinusPAGE_VAR.length( ) > 0 )
        {
            proxyURI.append("?");
            proxyURI.append( queryMinusPAGE_VAR );
        }

        // EXECUTE PRE-FILTERS
        // 
        NoodleFilterLoader.getInstance().doExecutePreFilters(noodleData);

        // Proxy.
        // 
        HTTPResponse httpResponse = proxyRequestMethod( req, url, 
            proxyURI.toString( ), noodleData.getHeadersToSendAsNVPair( ), 
            noodleData.getPostData( ) );
        noodleData.setHTTPResponse( httpResponse );

        byte[] data = null;
        String ct = null;
        try
        {
            data = readFully( 
                new BufferedInputStream( httpResponse.getInputStream()));
            // I think the above line is more efficient than what 
            // HTTPClient.Response is trying to do....ie:
            // data = httpResponse.getData();

            ct = httpResponse.getHeader("Content-Type");
            if (ct == null || ct.length() == 0)
                ct = DEFAULT_CONTENT_TYPE;
        }

        catch (Exception e)
        {
            Log.error( "Error occurred while accessing content:"
                + url.toString( ), e );
            Vector v = noodleData.getHeadersToSend( );
            StringBuffer strbuf = new StringBuffer( );
            for( int i = 0; i < v.size( ); i++ )
            {
                strbuf.append( ( ( NVPair )v.get( i ) ).getName( ) );
                strbuf.append( ":" );
                strbuf.append( ( ( NVPair )v.get( i ) ).getValue( ) );
                strbuf.append( "===" );
            }
            Log.error( "Request Headers: " + strbuf.toString( ) );
            data = "Error occurred while accessing content.".getBytes();
            ct = DEFAULT_CONTENT_TYPE;
        }

        // Set the content type for the Servlet response.
        //
        res.setContentType(ct);

        // EXECUTE POST FILTERS!
        // Set the response data into the noodleData for filter processing.
        //
        noodleData.setResponseData(data);
        NoodleFilterLoader.getInstance().doExecutePostFilters(noodleData);
    }

    /** 
     * Do the actual proxy w/ appropriate method (GET, POST, etc.).
     *
     * @@param inRequest The servlet request object.
     * @@param inURL Basic url w/ host and port information.  Used to 
     *        set up the HTTPClient connection.
     * @@param inProxyURI File we're to request along w/ whats left of
     *        query string after stripping page to request (If any).
     * @@param inHeaders2Send HTTP headers to send on the proxy request.
     * @@param inPostData Data to use w/ POST requests, if thats what
     *        we're doing here.
     *
     * @@return A HTTPClient HTTPResponse object.
     *
     * @@exception IOException
     * @@exception ModuleException
     * @@exception java.net.ProtocolException If we get a method request
     *            that we do not support.
     *
     */ 
    private static HTTPResponse proxyRequestMethod( 
        HttpServletRequest inRequest, URL inURL, String inProxyURI, 
            NVPair[] inHeaders2Send, byte[] inPostData )
        throws java.net.ProtocolException, IOException, ModuleException
    {
        HTTPResponse httpResponse = null;
        String emptyStr = new String( );

        HTTPConnection conn = new HTTPConnection( inURL );

        if( inRequest.getMethod( ).equalsIgnoreCase( GET ) )
        {
            httpResponse = conn.Get( inProxyURI, emptyStr, inHeaders2Send );
        }
        else if( inRequest.getMethod( ).equalsIgnoreCase( POST ) )
        {
            httpResponse = conn.Post( inProxyURI, inPostData, inHeaders2Send );
        }
        else if( inRequest.getMethod( ).equalsIgnoreCase( HEAD ) )
        {
            httpResponse = conn.Head( inProxyURI, emptyStr, inHeaders2Send );
        }
        else if( inRequest.getMethod( ).equalsIgnoreCase( PUT ) )
        {
            httpResponse = conn.Put( inProxyURI, emptyStr, inHeaders2Send );
        }
        else
        {
            throw new java.net.ProtocolException( "UNSUPPORTED HTTP method: "
                + inRequest.getMethod( ) );
        }

        return httpResponse;
    }

    /** 
     * Get the query string.
     *
     * inRequest.getQueryString( ) can return null.  Put in check for null and 
     * use alternate query string source, if one supplied.
     *
     * @@param inRequest Our incoming request.
     * @@parm inSavedOffQuery Last chance query string.  May be null.
     *
     * @@return The query string.
     *
     * @@exception NullPointerException Throw NPE if we can't get query string.
     */
    private static String getQueryString( HttpServletRequest inRequest,
            String inSavedOffQuery ) 
        throws NullPointerException
    {
        String query = inRequest.getQueryString();

        if( query == null )
        {
            query = inSavedOffQuery;
            if( query == null )
            {
                throw new NullPointerException( 
                    "query [req.getQueryString( )] is null" );
            }
        }

        if( DEBUG )
        {
            Log.debug( "QUERY: " + query );
        }

        return query.trim( );
    }

    /** 
     * Get page we're to proxy.
     *
     * Pull from the HttpServletRequest or from query string.
     *
     * @@param inRequest The servlet request object.
     * @@parm inQueryString Request query string.
     *
     * @@return Page we're to proxy.
     * 
     * @@exception NullPointerException If we can't get a page to proxy.
     *
     */
    private static String getPageToProxy( HttpServletRequest inRequest,
            String inQueryString )
        throws NullPointerException
    {
        String page = null;

        try
        {
            page = inRequest.getParameter(PAGE_VAR);
        }

        finally
        {
            if( page == null )
            {
                page = getValueFromQueryString( PAGE_VAR, inQueryString );
            }
        }

        return page;
    }

    /** 
     * Get value from the passed in query string.
     *
     * @@param inKey Key we use pulling value from inQueryStr.
     * @@param inQueryStr Query string to pull value on the end of inKey from.
     *
     * @@exception NullPointerException If we can't find inKey value in passed
     *            string.
     */
    private static String getValueFromQueryString( String inKey, 
            String inQueryStr )
        throws NullPointerException
    {
        String strReturn = null; // PAGE_VAR argument to return
        int indexStart = -1;
        int indexEnd = -1;
        
        if( inQueryStr == null )
            throw new NullPointerException( "inQueryStr is null" );
        indexStart = inQueryStr.indexOf( inKey );
        if( indexStart < 0 ) // A bit of misuse of NullPointerException
            throw new NullPointerException( inKey + " not in inQueryStr" );
        indexStart += inKey.length( ); // Put index past inKey
        indexStart += 1; // Put index past the '='
        // End of inKey is at end of string or at first first occurance past 
        // current index of a '&' -- the query args seperator.
        //
        indexEnd = inQueryStr.indexOf( indexStart, '&' );
        if( indexEnd > 0 )
            strReturn = inQueryStr.substring( indexStart, indexEnd );
        else
            strReturn = inQueryStr.substring( indexStart ); 
        
        return strReturn;
    }

    /**
     * Strip key and its value from the querystring.
     *
     * @@param inQueryString Query string.
     *
     * @@return New query string absent 'page=VALUE'.
     */
    public static final String stripQueryString( 
            String inQueryString, String inKey )
    {
        StringBuffer sb = new StringBuffer( inQueryString );
        int start = inQueryString.indexOf( inKey + "=" );
        int end = inQueryString.indexOf( "&", start );
        if( end == -1 )
            end = inQueryString.length();
        if( start > 0 )
            start = start - 1;
        else if( start == 0 && ( ( end + 1 ) < inQueryString.length( ) ) )
            end = end + 1;
        return inQueryString.substring( 0, start ) +
            inQueryString.substring( end, inQueryString.length( ) );
    }

    /**
     * Parse the protocol.
     *
     * Not used yet, but here for future reference
     *
     * @@param value Protocol to parse.
     *
     * @@return 
     *
     */
    private static String parseProtocol(String value)
    {
        return value.substring(0,value.indexOf("/"));
    }

    /** 
     * Read all data from stream even if total length is unknown.
     *
     * Reads 4096 bytes at a time.
     *
     * @@param is Stream to read from.
     *
     * @@return Byte array of all read from stream.
     *
     * @@exception IOException
     */
    public static byte[] readFully ( InputStream is )
        throws IOException
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] block = new byte[4096];
        int count = 0;
        while ((count = is.read(block)) != -1)
        {
            baos.write(block, 0, count);
        }
        baos.close();
        return baos.toByteArray();
    }

    /**
     * Override the default server name ("localhost").        
     *
     * @@param name Server name
     */
    public static void setServerName(String name)
    {
        SERVER_NAME = name;
    }

    /**
     * Override the default server port (80).  
     * 
     * @@param port Port to use.
     */ 
    public static void setServerPort(int port) 
    { 
        SERVER_PORT = port; 
    } 
    
    /** 
     * URL for making the connection to the remote location.
     * 
     * @@param page The page param must be an URLEncoded string.
     *
     * @@return Returns page as an url.
     *
     * @@exception MalformedURLException
     */
    public static URL getURL(String page)
        throws MalformedURLException
    {
        if (page == null)
        {
            throw new MalformedURLException( "Noodle page variable not found");
        }

        return new URL(SCHEME, SERVER_NAME, SERVER_PORT, page);
    }
}    
@
