package page.com.jfirewalltest;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.wikiwebserver.core.WareHouse;
import org.wikiwebserver.core.WikiClassLoader;
import org.wikiwebserver.handler.http.HTTPException;
import org.wikiwebserver.handler.http.interfaces.HTTPResponder;
import org.wikiwebserver.html.HTMLHelper;

import page.tools.html.TemplatedPushPage;

import static org.wikiwebserver.html.HTMLHelper.*;

public class PortScanner extends TemplatedPushPage implements HTTPResponder {
    
    private enum Status { UNTESTED, TESTING, OPEN, CLOSED, STEALTH }; 
    
    private static Map<Integer, String> services = null;
    private static Map<Integer, String> trojans = null;
    
    private int connectTimeout = 2000;
    private int socketTimeout = 1000;
    private String host = "localhost";
    private int[] ports = new int[] { 9, 80, 8080, 8081, 8082, 8083, 8084 }; 
    private String test = "GET / HTTP/1.0\\r\\n\\r\\n";
    
    private int portIdx;


    
    protected void generate() throws HTTPException {
        
        addResourceRoot("/templates/com/jfirewalltest/");
        addCSSLink("results.css");    
        
        boolean isPrivate = false;
        host = getHandler().getInetAddress().getHostAddress();

        if (getFormData() != null) {
            
            String hostString = getFormData().getFirst("host");
            if (hostString != null) host = hostString;
            
            String typeString = getFormData().getFirst("type");
            if (typeString != null) {
                isPrivate = typeString.equals("private");
            }
            
            String portsString = getFormData().getFirst("ports");
            if (portsString != null) {
                List<Integer> tempPorts = new ArrayList<Integer>();
                StringTokenizer st = new StringTokenizer(portsString, ",");
                while (st.hasMoreTokens()) {
                    String portString = st.nextToken();
                    if (portString != null && portString.trim().length() > 0) {
                        tempPorts.add(new Integer(portString.trim()));
                    }
                }
                ports = new int[tempPorts.size()];
                for (int i=0; i<tempPorts.size(); i++) {
                    ports[i] = tempPorts.get(i).intValue();
                }
            }
            
            connectTimeout = parseInt(getFormData().getFirst("c_timeout"), connectTimeout);
            socketTimeout = parseInt(getFormData().getFirst("r_timeout"), socketTimeout);

            String testString = getFormData().getFirst("test");
            if (testString != null) {
                if (!test.equals(testString)) {
                    throw new HTTPException(403, "Test request field locked");
                }
                test = testString;
            }
        }
        
        setTitle("External Port Scan - JFirewallTest.com");
        
        append(h(1, "External Port Scan")); 
        if (isPrivate) {
            append(p("The internal port scan highlighted that your computer is on a private network. " +
            		 " Port scanning will continue but the results will be for your router and may not" +
            		 " reflect services or open ports on your local machine.")); 
        }
        append(getGoogleAdsenseBlock("pub-7253309958196609", "7029144047", 728, 90));
        
        append("<table width='700'>"); 
        append("<tr><th width='60'>Port</th><th>Status</td></tr>");
        for (int i=0; i<ports.length; i++) {
            append("<tr>");
            append("<td valign='top'>" + ports[i] + "</td>");
            append("<td>" + div("port_" + ports[i], "Untested") + "</td>");
            append("</tr>");
        }
        append("</table>");
        
        Integer[] times = { 100, 500, 1000, 2000, 5000, 30000 };
        StringBuilder portList = new StringBuilder();
        for (int port : ports) {
            portList.append(String.valueOf(port));
            portList.append(",");
        }
        

        append(form(
                   h(2, "Advanced port scan options") +
                   setting("Host", textfield("host", host)) +
                   setting("Ports", textfield("ports", portList.toString(), "size='60'")) +
                   setting("Connect timeout", select("c_timeout", times, connectTimeout) + " ms") +
                   setting("Socket timeout", select("r_timeout", times, socketTimeout) + " ms") +
                   setting("Test request", textfield("test", test)) +
                   submitbutton("action", "Repeat port scan")
               )
        );
        
        
        setUpdatePeriod(200);
        setPushEnabled(true);
    }
    
    protected void begin() {
        //showStatus(0, Status.TESTING, null, null);
    } 
    
    private int parseInt(String source, int defaultValue) {
        if (source == null) return defaultValue;
        try {
            return Integer.parseInt(source);
        } catch (NumberFormatException ex) {
            return defaultValue;
        }
    }
    
    private String setting(String label, String component) {
        return div(HTMLHelper.ContainerType.CLASS, "label", label) + 
               div(HTMLHelper.ContainerType.CLASS, "component", component) +
               cleardiv();
    }
    
    private void showStatus(int portIdx, Status status, String initialResponse, 
                            String testString, String responseToTest) {
        
        String id = "port_" + ports[portIdx];
        String value = status.toString();
        StringBuilder results = new StringBuilder();
        results.append(div(HTMLHelper.ContainerType.CLASS, "statusHead", "Status")); 
        results.append(div(HTMLHelper.ContainerType.CLASS, "status",
                           div(HTMLHelper.ContainerType.CLASS, value, value))); 
        
        String serviceName = getServiceName(ports[portIdx]);
        if (serviceName != null) {
            results.append(div(HTMLHelper.ContainerType.CLASS, "serviceNameHead", "Service name")); 
            results.append(div(HTMLHelper.ContainerType.CLASS, "serviceName", serviceName)); 
        }
        
        String trojanName = getTrojanName(ports[portIdx]);
        if (trojanName != null) { 
            results.append(div(HTMLHelper.ContainerType.CLASS, "trojanNameHead", "Possible trojan")); 
            results.append(div(HTMLHelper.ContainerType.CLASS, "trojanName", trojanName)); 
        }
        
        if (status == Status.OPEN) {
            results.append(div(HTMLHelper.ContainerType.CLASS, "initialResponseHead", "Initial response"));
            if (initialResponse == null) initialResponse = "No initial response";
            results.append(div(HTMLHelper.ContainerType.CLASS, "initialResponse", initialResponse)); 
            results.append(div(HTMLHelper.ContainerType.CLASS, "testStringHead", "Test string")); 
            results.append(div(HTMLHelper.ContainerType.CLASS, "testString", testString)); 
            responseToTest = responseToTest.replace("\n", br());            
            results.append(div(HTMLHelper.ContainerType.CLASS, "responseToTestHead", "Response to test")); 
            if (responseToTest == null || responseToTest.length() == 0) {
                responseToTest = "No response";
            }
            results.append(div(HTMLHelper.ContainerType.CLASS, "responseToTest", responseToTest)); 
        }
        String html = WareHouse.escapeStringForJavaScript(results.toString());
        append(javaScript("$('#" + id + "').slideUp(200, function() {" +
        		          "  $('#" + id + "').html('" + html + "');" +
        		          "  $('#" + id + "').slideDown(500);" +
        		          "});"));
    }

    protected void update() throws IOException {
        
        Socket socket = null;
        Status status = Status.TESTING;
        try {
            InetSocketAddress addr = new InetSocketAddress(host, ports[portIdx]);
            socket = new Socket();
            socket.bind(null);
            socket.connect(addr, connectTimeout);
            status = Status.OPEN;

        } catch (SocketTimeoutException ex) {
            socket = null;
            status = Status.STEALTH;                
        } catch (IOException ex) {
            socket = null;
            status = Status.CLOSED;
        }
            
        String initialResponse = null;
        StringBuilder responseToTest = new StringBuilder();
        if (socket != null) {
            BufferedReader reader = null;
            BufferedWriter writer = null;
            try {
                socket.setSoTimeout(socketTimeout);   
                reader = new BufferedReader(new InputStreamReader(
                        socket.getInputStream(), "US-ASCII")); 
                writer = new BufferedWriter(new OutputStreamWriter(
                        socket.getOutputStream(), "US-ASCII"));
                
                try {
                    initialResponse = reader.readLine();
                } catch (SocketTimeoutException ex) {}
                
                if (initialResponse == null) {
                    String unescaped = test.replace("\\n", "\n");
                    unescaped = unescaped.replace("\\r", "\r");
                    unescaped = unescaped.replace("\\t", "\t");                    
                    writer.write(unescaped);
                    writer.flush();
                }

                String line = reader.readLine();
                while (line != null && line.length() > 0) {
                    responseToTest.append(WareHouse.escapeHTMLEntities(line) + LF);
                    line = reader.readLine();
                }
                

            } catch (IOException ex) {
                // ex.printStackTrace();
            } finally {
                if (reader != null) reader.close();
                if (writer != null) writer.close();
                socket = null;
            }

        }
        

        showStatus(portIdx, status, initialResponse, test, responseToTest.toString());
        
        // Next port for testing
        portIdx ++;
        
        if (portIdx >= ports.length) {
            // We are done!
            this.setPushEnabled(false);
        }
        
    }
    

    private synchronized static String getServiceName(int port) {
        if (services == null) {
            services = importPortMap("templates/com/jfirewalltest/services.txt");
        }
        return services.get(new Integer(port));
    }
    
    private synchronized static String getTrojanName(int port) {
        if (trojans == null) {
            trojans = importPortMap("templates/com/jfirewalltest/trojans.txt");
        }
        return trojans.get(new Integer(port));
    }    

    private static Map<Integer, String> importPortMap(String path) {
        
        Map<Integer, String> portMap = new HashMap<Integer, String>();
        InputStream in = null;
        try {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            if (cl instanceof WikiClassLoader) {
                File file = ((WikiClassLoader)cl).getResourceFile(path);
                if (file != null) {
                    in = new FileInputStream(file);
                }
            }
            else {
                URL url = cl.getResource(path);
                if (url != null) {
                    in = url.openStream();
                }
            }
            if (in != null) {
                BufferedReader reader = new BufferedReader(new InputStreamReader(in, "utf8"));
                String line = reader.readLine();
                while (line != null) {
                    int split = line.indexOf(':');
                    if (split > 0 && split < line.length()-1) {
                        portMap.put(new Integer(line.substring(0, split)), line.substring(split+1));
                    }
                    line = reader.readLine();
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try { in.close(); } catch (Exception ex) {}
        }
        
        return portMap;
    }    
}

