package org.owasp.webgoat.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.BitSet;


/**
 *  Copyright (c) 2002 Free Software Foundation developed under the custody of the Open Web
 *  Application Security Project (http://www.owasp.org) This software package org.owasp.webgoat.is published by OWASP
 *  under the GPL. You should read and accept the LICENSE before you use, modify and/or redistribute
 *  this software.
 *
 * @author     Jeff Williams <a href="http://www.aspectsecurity.com">Aspect Security</a>
 * @created    October 28, 2003
 */
public class Exec
{
	/**
	 *  Description of the Method
	 *
	 * @param  command  Description of the Parameter
	 * @param  input    Description of the Parameter
	 * @return          Description of the Return Value
	 */
	public static ExecResults execInput( String command, String input )
	{
		return ( execOptions( command, input, 0, 0, false ) );
	}


	/**
	 *  Description of the Method
	 *
	 * @param  command  Description of the Parameter
	 * @return          Description of the Return Value
	 */
	public static ExecResults execLazy( String command )
	{
		return ( execOptions( command, "", 0, 0, true ) );
	}


	/*
     *  Execute an OS command and capture the output in an ExecResults.
     *  All exceptions are caught and stored in the ExecResults.
     *  @param String command is the OS command to execute
     *  @param String input is piped into the OS command
     *  @param int successCode is the expected return code if the command completes successfully
     *  @param int timeout is the number of milliseconds to wait before interrupting the command
     *  @param boolean quit tells the method to exit when there is no more output waiting
     */
	/**
	 *  Description of the Method
	 *
	 * @param  command      Description of the Parameter
	 * @param  input        Description of the Parameter
	 * @param  successCode  Description of the Parameter
	 * @param  timeout      Description of the Parameter
	 * @param  lazy         Description of the Parameter
	 * @return              Description of the Return Value
	 */
	public static ExecResults execOptions( String[] command, String input, int successCode, int timeout, boolean lazy )
	{
		Process child = null;
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		ByteArrayOutputStream errors = new ByteArrayOutputStream();
		ExecResults results = new ExecResults( command[0], input, successCode, timeout );
		BitSet interrupted = new BitSet( 1 );
		boolean lazyQuit = false;
		ThreadWatcher watcher;

		try
		{
			// start the command
			child = Runtime.getRuntime().exec( command );

			// get the streams in and out of the command
			InputStream processIn = child.getInputStream();
			InputStream processError = child.getErrorStream();
			OutputStream processOut = child.getOutputStream();

			// start the clock running
			if ( timeout > 0 )
			{
				watcher = new ThreadWatcher( child, interrupted, timeout );
				new Thread( watcher ).start();
			}

			// Write to the child process' input stream
			if ( ( input != null ) && !input.equals( "" ) )
			{
				try
				{
					processOut.write( input.getBytes() );
					processOut.flush();
					processOut.close();
				}
				catch ( IOException e1 )
				{
					results.setThrowable( e1 );
				}
			}

			// Read from the child process' output stream
			// The process may get killed by the watcher at any time
			int c = 0;

			try
			{
				while ( true )
				{
					if ( interrupted.get( 0 ) || lazyQuit )
					{
						break;
					}

					// interrupted
					c = processIn.read();

					if ( c == -1 )
					{
						break;
					}

					// end of stream
					output.write( c );

					if ( lazy && ( processIn.available() < 1 ) )
					{
						lazyQuit = true;
					}

					// if lazy and nothing then quit (after at least one read)
				}

				processIn.close();
			}
			catch ( IOException e2 )
			{
				results.setThrowable( e2 );
			}
			finally
			{
				if ( interrupted.get( 0 ) )
				{
					results.setInterrupted();
				}

				results.setOutput( output.toString() );
			}

			// Read from the child process' error stream
			// The process may get killed by the watcher at any time
			try
			{
				while ( true )
				{
					if ( interrupted.get( 0 ) || lazyQuit )
					{
						break;
					}

					// interrupted
					c = processError.read();

					if ( c == -1 )
					{
						break;
					}

					// end of stream
					output.write( c );

					if ( lazy && ( processError.available() < 1 ) )
					{
						lazyQuit = true;
					}

					// if lazy and nothing then quit (after at least one read)
				}

				processError.close();
			}
			catch ( IOException e3 )
			{
				results.setThrowable( e3 );
			}
			finally
			{
				if ( interrupted.get( 0 ) )
				{
					results.setInterrupted();
				}

				results.setErrors( errors.toString() );
			}

			// wait for the return value of the child process.
			if ( !interrupted.get( 0 ) && !lazyQuit )
			{
				int returnCode = child.waitFor();
				results.setReturnCode( returnCode );

				if ( returnCode != successCode )
				{
					results.setError( ExecResults.BADRETURNCODE );
				}
			}
		}
		catch ( InterruptedException i )
		{
			results.setInterrupted();
		}
		catch ( Throwable t )
		{
			results.setThrowable( t );
		}
		finally
		{
			if ( child != null )
			{
				child.destroy();
			}
		}

		return ( results );
	}


	/*
     *  Execute an OS command and capture the output in an ExecResults.
     *  All exceptions are caught and stored in the ExecResults.
     *  @param String command is the OS command to execute
     *  @param String input is piped into the OS command
     *  @param int successCode is the expected return code if the command completes successfully
     *  @param int timeout is the number of milliseconds to wait before interrupting the command
     *  @param boolean quit tells the method to exit when there is no more output waiting
     */
	/**
	 *  Description of the Method
	 *
	 * @param  command      Description of the Parameter
	 * @param  input        Description of the Parameter
	 * @param  successCode  Description of the Parameter
	 * @param  timeout      Description of the Parameter
	 * @param  lazy         Description of the Parameter
	 * @return              Description of the Return Value
	 */
	public static ExecResults execOptions( String command, String input, int successCode, int timeout, boolean lazy )
	{
		Process child = null;
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		ByteArrayOutputStream errors = new ByteArrayOutputStream();
		ExecResults results = new ExecResults( command, input, successCode, timeout );
		BitSet interrupted = new BitSet( 1 );
		boolean lazyQuit = false;
		ThreadWatcher watcher;

		try
		{
			// start the command
			child = Runtime.getRuntime().exec( command );

			// get the streams in and out of the command
			InputStream processIn = child.getInputStream();
			InputStream processError = child.getErrorStream();
			OutputStream processOut = child.getOutputStream();

			// start the clock running
			if ( timeout > 0 )
			{
				watcher = new ThreadWatcher( child, interrupted, timeout );
				new Thread( watcher ).start();
			}

			// Write to the child process' input stream
			if ( ( input != null ) && !input.equals( "" ) )
			{
				try
				{
					processOut.write( input.getBytes() );
					processOut.flush();
					processOut.close();
				}
				catch ( IOException e1 )
				{
					results.setThrowable( e1 );
				}
			}

			// Read from the child process' output stream
			// The process may get killed by the watcher at any time
			int c = 0;

			try
			{
				while ( true )
				{
					if ( interrupted.get( 0 ) || lazyQuit )
					{
						break;
					}

					// interrupted
					c = processIn.read();

					if ( c == -1 )
					{
						break;
					}

					// end of stream
					output.write( c );

					if ( lazy && ( processIn.available() < 1 ) )
					{
						lazyQuit = true;
					}

					// if lazy and nothing then quit (after at least one read)
				}

				processIn.close();
			}
			catch ( IOException e2 )
			{
				results.setThrowable( e2 );
			}
			finally
			{
				if ( interrupted.get( 0 ) )
				{
					results.setInterrupted();
				}

				results.setOutput( output.toString() );
			}

			// Read from the child process' error stream
			// The process may get killed by the watcher at any time
			try
			{
				while ( true )
				{
					if ( interrupted.get( 0 ) || lazyQuit )
					{
						break;
					}

					// interrupted
					c = processError.read();

					if ( c == -1 )
					{
						break;
					}

					// end of stream
					output.write( c );

					if ( lazy && ( processError.available() < 1 ) )
					{
						lazyQuit = true;
					}

					// if lazy and nothing then quit (after at least one read)
				}

				processError.close();
			}
			catch ( IOException e3 )
			{
				results.setThrowable( e3 );
			}
			finally
			{
				if ( interrupted.get( 0 ) )
				{
					results.setInterrupted();
				}

				results.setErrors( errors.toString() );
			}

			// wait for the return value of the child process.
			if ( !interrupted.get( 0 ) && !lazyQuit )
			{
				int returnCode = child.waitFor();
				results.setReturnCode( returnCode );

				if ( returnCode != successCode )
				{
					results.setError( ExecResults.BADRETURNCODE );
				}
			}
		}
		catch ( InterruptedException i )
		{
			results.setInterrupted();
		}
		catch ( Throwable t )
		{
			results.setThrowable( t );
		}
		finally
		{
			if ( child != null )
			{
				child.destroy();
			}
		}

		return ( results );
	}


	/**
	 *  Description of the Method
	 *
	 * @param  command  Description of the Parameter
	 * @return          Description of the Return Value
	 */
	public static ExecResults execSimple( String[] command )
	{
		return ( execOptions( command, "", 0, 0, false ) );
	}


	/**
	 *  Description of the Method
	 *
	 * @param  command  Description of the Parameter
	 * @return          Description of the Return Value
	 */
	public static ExecResults execSimple( String command )
	{
		return ( execOptions( command, "", 0, 0, false ) );
	}


	/**
	 *  Description of the Method
	 *
	 * @param  command  Description of the Parameter
	 * @param  args     Description of the Parameter
	 * @return          Description of the Return Value
	 */
	public static ExecResults execSimple( String command, String args )
	{
		return ( execOptions( command, args, 0, 0, false ) );
	}


	/**
	 *  Description of the Method
	 *
	 * @param  command  Description of the Parameter
	 * @param  timeout  Description of the Parameter
	 * @return          Description of the Return Value
	 */
	public static ExecResults execTimeout( String command, int timeout )
	{
		return ( execOptions( command, "", 0, timeout, false ) );
	}


	/**
	 *  The main program for the Exec class
	 *
	 * @param  args  The command line arguments
	 */
	public static void main( String[] args )
	{
		ExecResults results;
		String sep = System.getProperty( "line.separator" );
		System.out.println( "-------------------------------------------" + sep + "TEST 1: execSimple" );
		results = Exec.execSimple( "c:/swarm-2.1.1/bin/whoami.exe" );
		System.out.println( results );
		System.out.println( "-------------------------------------------" + sep + "TEST 2: execSimple (with search)" );
		results = Exec.execSimple( "netstat -r" );
		System.out.println( results );

		if ( results.outputContains( "localhost:1031" ) )
		{
			System.out.println( "ERROR: listening on 1031" );
		}

		System.out.println( "-------------------------------------------" + sep + "TEST 3: execInput" );
		results = Exec.execInput( "find \"cde\"", "abcdefg1\nhijklmnop\nqrstuv\nabcdefg2" );
		System.out.println( results );
		System.out.println( "-------------------------------------------" + sep + "TEST 4:execTimeout" );
		results = Exec.execTimeout( "ping -t 127.0.0.1", 5 * 1000 );
		System.out.println( results );
		System.out.println( "-------------------------------------------" + sep + "TEST 5:execLazy" );
		results = Exec.execLazy( "ping -t 127.0.0.1" );
		System.out.println( results );
		System.out.println( "-------------------------------------------" + sep + "TEST 6:ExecTimeout process never outputs" );
		results = Exec.execTimeout( "c:/swarm-2.1.1/bin/sleep.exe 20", 5 * 1000 );
		System.out.println( results );
		System.out.println( "-------------------------------------------" + sep + "TEST 7:ExecTimeout process waits for input" );
		results = Exec.execTimeout( "c:/swarm-2.1.1/bin/cat", 5 * 1000 );
		System.out.println( results );
	}
}

