/**
 * @author Abeer Alhuzali
 * For more information, please read "NAVEX: Precise and Scalable Exploit Generation for Dynamic Web Applications"
 *
 */
package navex;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.htmlparser.tags.FormTag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import edu.uci.ics.crawler4j.crawler.CrawlConfig;
import edu.uci.ics.crawler4j.crawler.Page;
import edu.uci.ics.crawler4j.fetcher.PageFetcher;
import edu.uci.ics.crawler4j.util.IO;
import navex.formula.Trace;
import navex.formula.TraceAnalysis;
import navex.graphDatabase.NeoGraphDatabase;
import navex.solver.DynamicSolver;
import navex.solver.Solver;
import navex.solver.SolverModel;
import edu.uci.ics.crawler4j.parser.HtmlParseData;


public class Main {
	private static final Logger logger = LoggerFactory.getLogger(Main.class);



	public static void main(String[] args) {
		long startTime = System.currentTimeMillis();
		CrawlConfig config = null;

		NeoGraphDatabase graph = new NeoGraphDatabase();

		try {
			config =BasicCrawlController.crawlerMain(args, graph);
		} catch (Exception e) {
			e.printStackTrace();
		}
		//reinitiat the authentication before analyzing forms.
		PageFetcher pageFetcher = new PageFetcher(config);

		startFormAnalyzer(pageFetcher, graph);

		//shutDown the pagefetcher
		pageFetcher.shutDown();
		long analysisEndTime = System.currentTimeMillis();
		long analysisDiffTime = (analysisEndTime - startTime);

		System.out.println("=====TOTAL Crawling+ processing time is====="+analysisDiffTime);

	}


	/*
	 * This method performs the following: 
	 * 1- call solver. The solver generates a model
	 * 2- process model.
	 * 3- generate an http request from the model.
	 * 4- send the http request to the application. The app generates a trace. 
	 * 5- use Joern (enhanced CPG) to analyze the Trace.
	 * 6- traverse the trace to check : 
	 * 			1- failed trace : collect constraints + repeat from (1)
	 * 			2- success trace : add the Http request to seed pool + create Neoj4
	 * 				nodes.
	 * 
	 */
	public static void startFormAnalyzer(PageFetcher pageFetcher, 
			NeoGraphDatabase graph){

		logger.debug("Analyzing Forms");

		if (CrawlerFilter.getAllPages() != null){
			logger.debug("  ....Starting to process Forms in the app....");
			for (Page page : CrawlerFilter.getAllPages()){
				System.out.println("analyzing forms in "+page.getWebURL().getURL());

				int counter = 0;

				if (page.getParseData() instanceof HtmlParseData )
				{ HtmlParseData ff = ((HtmlParseData)page.getParseData());

				for (HTMLForm form:ff.getForms() ){
					List <NameValuePair> params = null;
					String method = ((FormTag)form.getForm()).getFormMethod();
					String action = ((FormTag)form.getForm()).getFormLocation(); 

					System.out.println(" form located in : "+ form.getUrl());
					System.out.println(" form action is:" + action); 
					//if it it a login form --> continue;
					boolean flag = false ;
					for (String p : Options.getLoginFile()){
						if (action.equalsIgnoreCase(p)){
							flag = true;
							break;
						}
					}
					if (flag)
						continue;

					// input: form's solver spec file , form method{get/post}
					//and action i.e. url

					Solver solve = new Solver(form.getZ3FormFormulas());
					String spec = solve.prepareSolver();
					String temp = form.getUrl().replace("http://", "");
					String cFile = "formsSpec/"+temp+"__"+(counter);
					//Z3-str3 does not accept file name that has = ? or &
					cFile= cFile.replace("?", "_").replace("&", "_").replace("=", "_");

					IO.writeToFile(cFile, spec, false);
					//js support 
					String js=  form.getHelperFuncs()
							+ form.domRepresentation + " \n " + form.windowRepresentation
							+ "\n" + form.commonJS + " \n " + form.jsValidation;

					IO.writeToFile(cFile+".scripts", js, false);

					extractJSConstraints(cFile+".scripts");



					invokeSolver(cFile);


					if (action.isEmpty())
						action= form.getUrl();
					if (action.startsWith("./") && !action.equals("./")){
						int start=0;
						String localhost= "localhost";
						//The following is specific to Navex's evaluation. Edit as needed.
						if (cFile.indexOf("localhost/") > 0)
						{
							start = cFile.indexOf("localhost/")+10;

						}
						else if (cFile.indexOf("192.168.0.123/") > 0)
						{
							start = cFile.indexOf("192.168.0.123/")+14;
							localhost = "192.168.0.123";
						}
						//End 

						String appName= cFile.substring(start, cFile.indexOf("/", start)+1);

						action = "http://"+localhost+"/".concat(appName).concat(action.replace("./", ""));
					}
					else if (action.startsWith("./") && action.equals("./")){
						action=form.getUrl();
					}
					else if (!action.startsWith("http://") && (action.startsWith("localhost") || 
							action.startsWith("192.168.0.123")   ))
						action= "http://".concat(action);
					else if (!action.startsWith("http://localhost/")  || !action.startsWith("192.168.0.123") ){

						int start = 0; String localhost = "localhost";

						if (cFile.indexOf("localhost/") > 0)
						{
							start = cFile.indexOf("localhost/")+10;

						}
						else if (cFile.indexOf("192.168.0.123/") > 0)
						{
							start = cFile.indexOf("192.168.0.123/")+14;
							localhost = "192.168.0.123";
						}



						String appName= cFile.substring(start, cFile.indexOf("/", start)+1);

						action = "http://"+localhost+"/".concat(appName).concat(action);
					}
					//update the form action
					((FormTag)form.getForm()).setFormLocation(action);
					;
					String[] mArgs = {cFile+".model", method, action, "form"};
					SolverModel model = new SolverModel();
					try {
						params = model.main(mArgs, pageFetcher);
					} catch (ClientProtocolException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (URISyntaxException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					//we have to invoke the traversal ONLY if its a failed trace
					//Again. This is specific to Navex's evaluation. Please edit as needed.
					if (!IO.IsSuccTrace("/home/user/log/trace.xt")){	
						invokeTraversal();
						//loop until we have a successful trace
						Trace t = new Trace(form.getZ3FormFormulas(),
								DynamicSolver.getServerFormula());
						String combinedSpec = TraceAnalysis.start(t, form);
						String tt = form.getUrl().replace("http://", "");
						String comFile = "CombinedSpec/"+tt+"__"+(counter);
						//Z3-str3 does not accept a file name that has: =, ? , &
						comFile= comFile.replace("?", "_").replace("&", "_").replace("=", "_");

						IO.writeToFile(comFile, combinedSpec, false);

						//invoke the solver again for the combined contraraits Fc+Fs(trace)
						invokeSolver(comFile);

						String[] mArgsTrace = {comFile+".model", method, action, "trace"};
						model = new SolverModel();
						try {
							params = model.main(mArgsTrace, pageFetcher);
						} catch (ClientProtocolException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						} catch (URISyntaxException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}


					}
					//successful trace 
					//add nodes to the navigation graph
					else {
						graph.StartNeoDb(page, form ,"form",  params, method);
					}

					counter++;
				}
				}
			}
		}
		else 
			logger.debug("There are NO forms to process ..\n End of the analysis");
	}
	// Edit paths to main.py 
	private static void invokeTraversal() {
		System.out.println( "    --------------------------------------------------------------\n");
		System.out.println( "   Invoking the gremlin Traversal script (main.py) ...\n");
		System.out.println( "    --------------------------------------------------------------\n");

		String cm = "cd /home/user/python-joern/ ;source .env/bin/activate;";
		cm += "python /home/user/python-joern/main.py";
		System.out.println( cm+ "\n");
		String[] cmd = { "/bin/sh", "-c", cm };

		try
		{
			Process p = Runtime.getRuntime().exec( cmd );
			p.waitFor();
		} catch( IOException e )
		{
			System.err.println( "Error invoking the traversal script: " + cm );
		} catch( InterruptedException e )
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}


	}
	// Edit paths to Z3-str.py  
	public static void invokeSolver(String cFile) {

		System.out.println( "    --------------------------------------------------------------\n");
		System.out.println( "    Generating Inputs ...\n");
		System.out.println( "    --------------------------------------------------------------\n");

		System.out.println( " Step 1 : Invokign the solver \n  ");
		String cm = "./../../Z3-str2/Z3-str.py -f "+cFile+" > "+cFile+".model";
		System.out.println( cm+ "\n");
		String[] cmd = { "/bin/sh", "-c", cm };

		try
		{   
		Process proc = Runtime.getRuntime().exec( cmd );

		// any error???
		//this is very important
		boolean exitVal = proc.waitFor(5, TimeUnit.SECONDS);
		System.out.println("ExitValue: " + exitVal); 
		proc.destroy();
		} catch (Throwable t)
		{
			t.printStackTrace();
		}
	}

	//Edit path to narcissus engin
	private static void extractJSConstraints(String srcFile) {

		// if the HTML constraints did not have any meaningful JS, do 
		//  not process them.
		//  HTML constraint generator places the DO_NOT_PROCESS_NOTAMPER
		//  flag in the scripts that need not be processed. 
		System.out.println( "--------------------------------------------------------------\n");
		System.out.println( "Extracting JavaScript constraints...\n");
		System.out.println( "--------------------------------------------------------------\n");


		//Edit path to narcissus engin
		String narc="/home/user/repos/navex/js-1.8.5/js/narcissus";


		boolean dontProcess = IO.grep(srcFile, "DO_NOT_PROCESS_NOTAMPER");

		if ( dontProcess ){
			System.out.println( "Processing "+srcFile+"...skipping\n");
		}        
		else{    
			System.out.println( "Processing "+srcFile+"...\n");
			try
			{
				IO.copyFile(srcFile, narc+"/test.js");
			} catch( IOException e )
			{
				System.err.println( "Error invoking cp  : " );
			}
			String newDir= System.getProperty("user.dir") +"/"+narc;
			System.out.println("Executing - "+narc+"/../src/dist/bin/js js.js  > "+srcFile+".log\n");

			String cm= narc+"/../src/dist/bin/js js.js  >  "+srcFile+".log";
			String[] cmd2 = { "/bin/sh", "-c", cm };


			try
			{
				Process p = Runtime.getRuntime().exec( cmd2 );
				p.waitFor();
			} catch( IOException e )
			{
				System.err.println( "Error invoking narc : " + cm );
			} catch( InterruptedException e )
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			String jscFile =srcFile.replace(".scripts.log", ".constraints_JS");

			String toRemove="JavaScript evaluator generated these constraints NTBEGIN";

			IO.copyFile(srcFile, jscFile, toRemove, "");             

			System.out.println( "  Generated JS constraints in "+jscFile+" \n");
		}

		System.out.println( "DONE extracing JS constraints\n");

	}


}