/*
 * $Id: HttpServerProc.java,v 1.4 2008/02/13 03:47:26 nishi Exp $
 * based on http://xxx.upken.jp/report/honey/http/
 */

package galatea.httpserver;


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Hashtable;


class HttpServerProc implements Runnable {
	
	private static final String SERVER_NAME = "Galatea HTTP Server";
	
	private static final int BUFFER = 1024;
	private static final int timeout = 10000; // milli second
	
	private static final int HTTP_METHOD_UNSET = -1;
	private static final int HTTP_METHOD_GET = 0;
	private static final int HTTP_METHOD_POST = 1;
	private static final int HTTP_METHOD_HEAD = 2;
	private static final int HTTP_METHOD_UNKNOWN = 99;
	private static final String METHODS[] = {"GET","POST","HEAD"};
	
	private static final int HTTP_ATTRIB_USERAGENT = 0;
	private static final int HTTP_ATTRIB_HOST = 1;
	private static final int HTTP_ATTRIB_CONTENTLENGTH = 2;
	private static final int HTTP_ATTRIB_CONTENTTYPE = 3;
	private static final String ATTRIBS[] = {"User-Agent","Host","Content-Length","Content-Type"};
	private static final String STR_FORM_URLENC = "application/x-www-form-urlencoded";
	private static final String STR_FORM_MULTI = "multipart/form-data";
	private static final String STR_BOUNDARY = "boundary";
	
	private Socket sock;
	private InputStream in;
	private OutputStream out;
	private String remote_host;
	private int threadnumber;
	private IHttpServerResponse response;
	
	public HttpServerProc(Socket cl, int c, IHttpServerResponse r){
		sock = cl;
		threadnumber = c;
		response = r;
		try {
			in = cl.getInputStream();
			out = cl.getOutputStream();
		}catch(IOException e){
			HttpServer.addLogPrintLn(e.getMessage());
		}
		remote_host = sock.getInetAddress().getHostAddress();
		HttpServer.addLogPrintLn("Connect from: " + remote_host);
	}
	
	public void run(){
		byte getbuff[] = new byte[BUFFER];
		byte procbuff[] = new byte[BUFFER + BUFFER];
		int getpointer, procpointer;
		int method = HTTP_METHOD_UNSET;
		String request_file = null;
		String httpversion;
		Hashtable<String,String> attrib = new Hashtable<String,String>();
		//Hashtable attrib = new Hashtable();
		
		try {
			sock.setSoTimeout(timeout);
			
			// Header Check
			procpointer = 0;
			int checkpoint = -1;
			
			HEADCHECK: while(true){
				getpointer = in.read(getbuff);
				if(getpointer < 0){
					HttpServer.addLogPrintLn("Detect getpointer < 0 : break header check.");
					break HEADCHECK;
				}
				if(getpointer > 0){
					HttpServer.addLogPrint(new String(getbuff, 0, getpointer));
				}
				for(int i=0; i<getpointer; i++){
					procbuff[procpointer] = getbuff[i];
					procpointer++;
					if(procpointer >= BUFFER + BUFFER){
						HttpServer.addLogPrintLn("Buffer over flow : break header check.");
						break HEADCHECK;
					}
				}
				int startpoint = 0;
				//HEADDATACHECK: 
				for(int i=0; i<procpointer; i++){
					if(procbuff[i] == 13){
						if(i == startpoint){
							HttpServer.addLogPrintLn("Detect: \\r\\n\\r\\n");
							checkpoint = i;
							if(i+1 < procpointer){
								if(procbuff[i+1] == 10){
									checkpoint++;
								}
							}
							break HEADCHECK;
						}
						checkpoint = i;
						
						if(method == HTTP_METHOD_UNSET){
							METHODCHECK: for(int j=startpoint; j<checkpoint; j++){
								if(procbuff[j] == 32){
									String strTmp = new String(procbuff, startpoint, j-startpoint);
									for(int k=0; k<METHODS.length; k++){
										if(strTmp.compareTo(METHODS[k]) == 0){
											method = k;
											HttpServer.addLogPrintLn("Detect: Method = " + METHODS[method]);
										}
									}
									if(method == HTTP_METHOD_UNSET){
										method = HTTP_METHOD_UNKNOWN;
										HttpServer.addLogPrintLn("Detect: Method=UNKNOWN");
									}
									startpoint = j;
									break METHODCHECK;
								}
							}
						FILECHECK: for(int j=checkpoint-1; j>startpoint; j--){
							if(procbuff[j] == 32){
								if(j-startpoint > 1){
									request_file = new String(procbuff, startpoint+1, j-startpoint-1);
								}else{
									request_file = null;
								}
								startpoint = j + 1;
								HttpServer.addLogPrintLn("Detect: Request File = " + request_file);
								break FILECHECK;
							}
						}
							if(checkpoint > startpoint){
								httpversion = new String(procbuff, startpoint, checkpoint-startpoint);
							}else{
								httpversion = null;
							}
							HttpServer.addLogPrintLn("Detect: HTTP Version = " + httpversion);
						}else{
							String strK = null, strV = null;
							COLONCHECK: for(int j=startpoint; j<checkpoint; j++){
								if(procbuff[j] == 58){
									strK = new String(procbuff, startpoint, j-startpoint);
									if(j+2 < checkpoint){
										if(procbuff[j+1] == 32){
											strV = new String(procbuff, j+2, checkpoint-j-2);
										}else{
											strV = new String(procbuff, j+1, checkpoint-j-1);
										}
									}else{
										strV = null;
									}
								}
								if(strK != null && strV != null){
									attrib.put(strK, strV);
									break COLONCHECK;
								}
							}
						}
						
						if(i+1 < procpointer){
							if(procbuff[i+1] == 10){
								i++;
								checkpoint = i;
							}
						}
						startpoint = checkpoint + 1;
					}
				}
				
				if(checkpoint >= 0){
					int j = 0;
					for(int i=checkpoint+1; i<procpointer; i++, j++){
						procbuff[j] = procbuff[i];
					}
					procpointer = j;
				}
				checkpoint = -1;
			}
			
			if(checkpoint >= 0){
				int j = 0;
				for(int i=checkpoint+1; i<procpointer; i++, j++){
					procbuff[j] = procbuff[i];
				}
				procpointer = j;
			}
			
			if(method == HTTP_METHOD_POST){
				long contentlength = 0;
				long contentlast = 0;
				Long objL = new Long((String) attrib.get(ATTRIBS[HTTP_ATTRIB_CONTENTLENGTH]));
				if(objL != null){
					contentlength = objL.longValue();
					contentlast = contentlength;
					HttpServer.addLogPrintLn("Detect: Content-Length: " + objL.toString());
				}
				
				BODYCHECK: while(true){
					checkpoint = -1;
					if(procpointer > 0){
						// HTTPServer.addLogln("contentlast=" + contentlast + ",procpointer=" + procpointer);
						contentlast -= procpointer;
						if(contentlast < 1){
							HttpServer.addLogPrintLn("Content is complete.");
							break BODYCHECK;
						}
						procpointer = 0;
					}
					getpointer = in.read(getbuff);
					if(getpointer < 0){
						HttpServer.addLogPrintLn("Detect getpointer < 0 : break body check.");
						break BODYCHECK;
					}
					if(getpointer > 0){
						HttpServer.addLogPrint(new String(getbuff, 0, getpointer));
					}
					for(int i=0; i<getpointer; i++){
						procbuff[procpointer] = getbuff[i];
						procpointer++;
						if(procpointer >= BUFFER + BUFFER){
							HttpServer.addLogPrintLn("Buffer over flow : break body check.");
							break BODYCHECK;
						}
					}
				}
			}
			
			String answer = response.getResponse(request_file);
			if (answer != null) {
				String strret = "HTTP/1.0 200 OK\r\n";
				strret += "Server: " + SERVER_NAME + "\r\n";
				strret += "Connection: close\r\n";
				// strret += "Content-Length: " + answer.getBytes().length + "\r\n";
				strret += "Cache-Control: no-cache\r\n";
				strret += "Content-Type: text/html\r\n";
				strret += "\r\n";
				out.write(strret.getBytes());
				out.write(answer.getBytes("UTF-8"));
				out.flush();
			} else {
				String strret = "HTTP/1.0 403 Forbidden\r\n";
				strret += "Server: " + SERVER_NAME + "\r\n";
				strret += "Connection: close\r\n";
				strret += "Content-Length: 11\r\n";
				strret += "Content-Type: text/html\r\n";
				strret += "\r\n";
				strret += "Forbidden\r\n";
				out.write(strret.getBytes());
				out.flush();
			}
		} catch(IOException e) {
			HttpServer.addLogPrintLn(toString() + e.getMessage());
		} finally {
			try {
				in.close();
				out.close();
				sock.close();
				HttpServer.addLogPrintLn("Connection Close.");
			} catch(IOException e){}
		}
	}
	
	public String toString(){
		return "Remote Host [" + remote_host + "] Thread Number [" + threadnumber + "]";
	}
}
