/*
 *
 * ====================================================================
 *
 * 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]
 *
 */


/*
  Based on Ajp11ConnectionHandler and Ajp12 implementation of JServ
*/
package org.apache.tomcat.modules.server;

import java.io.*;
import java.net.*;
import java.util.*;
import org.apache.tomcat.core.*;
import org.apache.tomcat.util.net.*;
import org.apache.tomcat.util.*;
import org.apache.tomcat.util.aaa.SimplePrincipal;

class Ajp12 {
    Socket socket;
    InputStream sin;
    BufferedInputStream ajpin;
    private boolean tomcatAuthentication=true;
    boolean shutdown=false;
    boolean isPing=false;
    boolean doLog;
    String secret=null;

    public Ajp12() {
    }

    // Debug only - use only to debug this component
    void d( String s ) {
	System.out.print("Ajp12: ");
	System.out.println( s );
    }
    
    public int doRead() throws IOException {
	return ajpin.read();
    }

    public  int doRead( byte b[], int off, int len ) throws IOException {
	return ajpin.read( b,off,len);
    }

    public void setSocket( Socket s ) throws IOException {
	this.socket = s;
	sin = s.getInputStream();
	ajpin = new BufferedInputStream(sin);
    }

    public void setSecret( String s ) {
	secret=s;
    }
    
    public void readNextRequest(Request req) throws IOException {
	String dummy,token1,token2;
	int marker;
	int signal;

	try {
	    boolean more=true;
            while (more) {
		marker = ajpin.read();
		switch (marker) {
		case 0:       //NOP marker useful for testing if stream is OK
		    break;
		    
		case 1: //beginning of request
		    req.method().setString(  readString(ajpin, null));
		     
		    //Zone
		    String contextPath = readString(ajpin, null);

		    // GS, the following commented line causes the Apache +
		    // Jserv + Tomcat combination to hang with a 404!!!
		    // if("ROOT".equals( contextPath ) ) contextPath="";
		    if("ROOT".equalsIgnoreCase( contextPath ) ) contextPath=null;

		    // XXX Do not set the context - we need extra info.
		    // this have to be rewritten !!!
		    //		    if( contextPath!= null )
		    //	req.setContext( contextM.getContext( contextPath ));
		    
		    //Servlet - not used
		    dummy=readString(ajpin, null);
		    
		    //Server hostname
		    req.serverName().setString(readString(ajpin, null) );
		    //System.out.println("XXX hostname: " + req.serverName());
		    
		    //Apache document root
		    dummy = readString(ajpin, null);               
		    req.pathInfo().setString( readString(ajpin, null));               		    //Apache parsed path-translated XXX Bug in mod_jserv !!!!!
		    dummy = readString(ajpin, null);
		    req.queryString().setString( readString(ajpin, null));  
		    req.remoteAddr().setString(readString(ajpin, ""));
		    req.remoteHost().setString( readString(ajpin, ""));
                    if (isTomcatAuthentication())
                        dummy=readString(ajpin, null);
                    else {
                        req.setRemoteUser( readString(ajpin, null));
                        // Note that roles are not integrated with apache
                        req.setUserPrincipal( new SimplePrincipal( req.getRemoteUser() ));
                    }
		    req.setAuthType(readString(ajpin, null));
		    //remote port
		    dummy = readString(ajpin, null);
		    //		    System.out.println("XXX rport " + dummy );

		    req.method().setString( readString(ajpin, null));
		    req.requestURI().setString( readString(ajpin, ""));

		    // XXX don't set lookup path - problems with URL rewriting.
		    // need to be fixed.
		    //if(contextPath!=null && contextPath.length() >0 )
		    //lookupPath=requestURI.substring( contextPath.length()+1 );

		    //script filename
		    dummy = readString(ajpin, null);
		    //script name
		    dummy = readString(ajpin, null);                   
		    req.serverName().setString( readString(ajpin, ""));       

		    int serverPort=80;
		    try {
			String p=readString(ajpin, null);
			//System.out.println("XXX p " + p);
			if(p==null ) p="80";
			serverPort= Integer.parseInt(p);
		    } catch (Exception any) {
			any.printStackTrace();
		    }
		    req.setServerPort( serverPort );

                    /* Quick and dirty patch to set https scheme in ajp12
                     * but I recommand using ajp13 instead 
                     */
                    if (serverPort == 443)
                       req.scheme().setString("https");

		    // System.out.println("XXX port: " + req.getServerPort());

		    //server protocol
		    String proto=readString(ajpin,null);
		    if( proto==null ) proto="HTTP/1.0";
		    req.protocol().setString (proto);  
		    //server signature
		    dummy = readString(ajpin, "");
		    //server software
		    dummy = readString(ajpin, "");
		    req.setJvmRoute( readString(ajpin, null));                   

		    dummy = readString(ajpin, "");
		    dummy = readString(ajpin, "");
		    break;


                    /**
                     * Marker = 5 will be used by mod_jserv to send environment
		     * vars as key+value (dynamically configurable).
                     * can be considered as "reserved", and safely ignored by
		     * other connectors.
		     * env_vars is (above in this  code) commented out for
		     * performance issues. so theses env vars are simply
		     * ignored. (just here for compatibility)
                     * but it is where mod_jserv would place SSL_* env vars
		     * (by exemple)
                     * See the new parameter for mod_jserv (version > 1.1):
                     * ApJServEnvVar localname remotename
                     *                                            - jluc
                     */
                case 5: // Environment vars
                    token1 = readString(ajpin, null);
                    token2 = readString(ajpin, "");
                    /*
                     * Env variables should go into the request attributes
                     * table. 
		     *
		     * Also, if one of the request attributes is HTTPS=on
                     * assume that there is an SSL connection.
		     */
                    req.setAttribute(token1, token2);
                    if(token1.equals("HTTPS") && token2.equals("on")) {
                        req.scheme().setString("https");
                    }
                    break;

		case 3: // Header
		    token1 = readString(ajpin, null);
		    token2 = readString(ajpin, "");
// 		    if( "Host".equalsIgnoreCase( token1 )) {
// 			System.out.println("XXX Host: " + token2);
// 		    }
		    req.getMimeHeaders().addValue(token1).setString(token2);
		    break;

		case 254: // Signal
		    signal = ajpin.read();

		    if (signal == 0) { // PING implemented as signal
			try {
			    // close the socket connection after we send reply
			    socket.getOutputStream().write(0); // PING reply
			    sin.close();
			} catch (IOException ignored) {
			    req.getContextManager().log("Exception closing, ignored",  ignored);
			}
                        isPing = true;
                        return;
		    } else {
			try {
			    // close the socket connection before handling any
			    // signal but get the addresses first so they are
			    // not corrupted
			    InetAddress serverAddr = socket.getLocalAddress();
			    InetAddress clientAddr = socket.getInetAddress();
			    if ( (signal== 15) &&
				 isSameAddress(serverAddr, clientAddr) ) {
				if( secret!=null ) {
				    String stopMsg=readString(ajpin, "");
				    if( ! secret.equals( stopMsg ) ) {
					req.getContextManager().log("Attempt to stop with the wrong secret");
					return;
				    }
				}
                                ContextManager cm=req.getContextManager();
				cm.shutdown();
				cm.log("Exiting" );
				// same behavior as in past, because it seems
				// that stopping everything doesn't work -
				// need to figure
				// out what happens with the threads ( XXX )
                                // until we remove the System.exit() call we
                                // also need to close the socket to allow the
                                // underlying layer to shutdown the connection
                                // properly.  Setting soLinger small will
                                // speed this up.
                                socket.setSoLinger(true, 0);
                                sin.close();
				System.exit(0);
				shutdown=true;
				return;
			    }
			    sin.close();
			} catch (Exception ignored) {
			    req.getContextManager().log("Ignored exception " +
						      "processing signal " +
						      signal, ignored);
			}
		    }
		    return;
		    
		case 4:
		case 255:
		    more=false;
		    break;
		    
		case -1:
		    throw new java.io.IOException("Stream closed prematurely");
		    
		    
		default:
		    throw new java.io.IOException("Stream broken");
		} // switch
            } // while
	} catch (IOException ioe) {
	    throw ioe;
        } catch (Exception e) {
	    req.getContextManager().log("Uncaught exception handling request", e);
        }
	
	// REQUEST_URI may includes query string
	String requestURI=req.requestURI().toString();
	int indexQ=requestURI.indexOf("?");
	int rLen=requestURI.length();
	if ( (indexQ >-1) && ( indexQ  < rLen) ) {
	    req.queryString().
		setString( requestURI.substring(indexQ + 1,
						requestURI.length()));
	    req.requestURI().setString( requestURI.substring(0, indexQ));
	} 
    }

    /**
     * Return <code>true</code> if the specified client and server addresses
     * are the same.  This method works around a bug in the IBM 1.1.8 JVM on
     * Linux, where the address bytes are returned reversed in some
     * circumstances.
     *
     * @param server The server's InetAddress
     * @param client The client's InetAddress
     */
    public static boolean isSameAddress(InetAddress server, InetAddress client) {
	// Compare the byte array versions of the two addresses
	byte serverAddr[] = server.getAddress();
	byte clientAddr[] = client.getAddress();
	if (serverAddr.length != clientAddr.length)
	    return (false);
	boolean match = true;
	for (int i = 0; i < serverAddr.length; i++) {
	    if (serverAddr[i] != clientAddr[i]) {
		match = false;
		break;
	    }
	}
	if (match)
	    return (true);

	// Compare the reversed form of the two addresses
	for (int i = 0; i < serverAddr.length; i++) {
	    if (serverAddr[i] != clientAddr[(serverAddr.length-1)-i])
		return (false);
	}
	return (true);
    }

    
    public static int readWord(InputStream in ) throws java.io.IOException {
        int b1 = in.read();
        if( b1 == -1)
            return -1;

        int b2 = in.read();
        if ( b2==-1)
            return -1;

        return ((int)((b1 << 8) | b2)) & 0xffff;
    }

    // UTF8 is a strict superset of ASCII.
    final static String CHARSET = "UTF8";

    public static String readString(InputStream in, String def)
	throws java.io.IOException {
        int len = readWord(in);

        if( len == 0xffff)
            return def;

        if( len == -1)
            throw new java.io.IOException("Stream broken");

        byte[] b = new byte[len];
        int p = 0;
        int r;
        while(p<len) {
            r = in.read(b,p, len - p);
            if( r< 0) {
                throw new IOException("Stream broken, couldn't " +
				      "demarshal string :"+len+":"+p);
            }
            p = p+r;
        }
        return new String(b, CHARSET);
    }

    public boolean isTomcatAuthentication() {
        return tomcatAuthentication;
    }

    public void setTomcatAuthentication(boolean newTomcatAuthentication) {
        tomcatAuthentication = newTomcatAuthentication;
    }

    
}

