// Small HTTP Server by Al Williams 
// This code is based on the Webster server at
// www.xs4all.nl/~somebody/java/webster.html
// which is covered by the GNU General Public License
// detailed at http://www.gnu.org/copyleft/gpl.html

import java.net.*;
import java.io.*;
import java.util.*;

public class HttpServer implements Runnable {

    private ServerSocket ss;
    boolean simple;  // simple request?
    private Thread runner=null;

    // The server's configuration information is stored in these properties
    protected static Properties props = new Properties();

    // The mime types information is stored in this properties list
    protected static Properties MimeTypes = new Properties();


    HttpServer() {   // main constructor
        runner = new Thread(this);
        runner.start();
    }

    public static void main(String[] args) {
        new HttpServer();
    }

    // override this to provide an action 
    public String action(String filename, Hashtable vars) {
        System.out.println("Serving: "+filename + " " + vars);
        return filename;
    }

   // override this if you want to provide a raw file
   public DataInputStream openFile(String filename, Hashtable vars)
      throws IOException {
         return new DataInputStream(
            new BufferedInputStream (new 
            FileInputStream(fullFileName(filename))));
         }

   public String fullFileName(String filename) {
     return HttpServer.props.getProperty("root") +
       filename;
     }

    public void run() {
        try{
            loadProps();
            loadMimes();
            System.out.println("HttpServer listening on port:" + 
                           props.getProperty("portnumber"));
            // setup serversocket
            ss = new ServerSocket( (new Integer(props.getProperty
                                  ("portnumber"))).intValue() );  
            while(true) {
                Socket s = ss.accept(); // accept incoming requests
                new Thread(new SendFile(this,s)).start();
            }
        } catch(Exception e) {
            System.out.println("Main Serve thread " + e );
        }
    }
    
    // load the properties file
    static void loadProps() throws IOException {
        File f = new File("server.properties");
        if (f.exists()) {
            InputStream is =
                  new BufferedInputStream(new FileInputStream(f));
            props.load(is);
            is.close();
        }
     }  // end of loadProps

    // load the properties file
    static void loadMimes() throws IOException {
        File f = new File("mimetypes.properties");
        if (f.exists()) {
            InputStream is =
                  new BufferedInputStream(new FileInputStream(f));
            MimeTypes.load(is);
            is.close();
        }
    }  // end of loadMimes
    
}  // end of Serve class

class SendFile implements Runnable{
    private Socket client;
    private String fileName,header;
    private String query;
    private DataInputStream requestedFile;
    private int fileLength;
    private HttpServer svr;

    
    SendFile(HttpServer svr,Socket s) {  // constructor
        client = s;
        this.svr=svr;
    }

    public void run() {
        String line;
        try {
            BufferedReader dis = 
                   new BufferedReader(new InputStreamReader
                                              (client.getInputStream()));
            // read request from browser and parse
            while((line=dis.readLine())!=null) { 
                StringTokenizer tokenizer = new StringTokenizer(line," ");
                if (!tokenizer.hasMoreTokens()) break;
                if (tokenizer.nextToken().equals("GET")) {
                    fileName = tokenizer.nextToken();
                    if (fileName.endsWith("/")) {
                        fileName = fileName + 
                             HttpServer.props.getProperty("defaultfile");
                    } else {
                       fileName = fileName.substring(1);
                    }
                String type;
              try {
                  type=tokenizer.nextToken();
                  }
                catch (NoSuchElementException nsee)
                  {
                  type=null;
                  }
                svr.simple=type==null;
                if (svr.simple) break;
                }
            }
            if (fileName.charAt(0)!='/') fileName = "/" + fileName;
            int n=fileName.indexOf('?');
            if (n!=-1) {
                query=fileName.substring(n+1);
                fileName=fileName.substring(0,n);
            }
            else
                query="";
            fileName=URLDecode(fileName);
            // decode query string
            Hashtable qvars = new Hashtable(64);
            int n0,n1;
            do {
                String val,key;
                n0=query.indexOf('&');
                if (n0==-1) n0=query.length();
                if (n0<=0) break;
                String vpart = query.substring(0,n0);
                if (n0==query.length()) query=""; 
                else query=query.substring(n0+1);
                n1=vpart.indexOf('=');
                if (n1==-1) {
                    val=""; 
                    key=vpart;
                } else {
                    val = vpart.substring(n1+1);
                    key = vpart.substring(0,n1);
                }
               qvars.put(URLDecode(key),URLDecode(val));
            } while (!query.equals(""));
            fileName=svr.action(fileName,qvars); 
            try {
                requestedFile=svr.openFile(fileName,qvars);
                fileLength = requestedFile.available();
                constructHeader();
            } catch(IOException e) { // file not found send 404.
                header = "HTTP/1.0 404 File not found\n" + 
                         "Allow: GET\n" +
                         "MIME-Version: 1.0\n"+
                         "Server : HttpServer: a Java Local HTTP Server\n"+
                         "\n\n <H1>404 File not Found</H1>\n";
                fileName = null;
            }
            int i;
            DataOutputStream clientStream = 
                  new DataOutputStream(new BufferedOutputStream(client.
                                       getOutputStream()));
            if (!svr.simple) clientStream.writeBytes(header);
            if (fileName != null) {
                 while((i = requestedFile.read()) != -1) {
                     clientStream.writeByte(i);
                 }
            }
            clientStream.flush();
            clientStream.close();
            dis.close();
            client.close();
            if (requestedFile!=null) requestedFile.close();
        } catch(Exception e) {
            System.out.print("Error closing Socket\n"+e);
        }
    } 

    public String URLDecode(String in)
    {
        StringBuffer out = new StringBuffer(in.length());
        int i = 0;
        int j = 0;
        while (i < in.length())
        {  
            char ch = in.charAt(i);   i++;
            if (ch == '+') ch = ' ';
            else if (ch == '%')
            {    
                ch = (char) Integer.parseInt(
                                  in.substring(i,i+2), 16);
                i+=2;
            }
            out.append(ch);
            j++;
        }
        return new String(out);
    }

    private void constructHeader() {
        String fileType;
        fileType = fileName.substring(fileName.
                      lastIndexOf(".")+1,fileName.length());
        fileType = HttpServer.MimeTypes.getProperty(fileType);
        header = "HTTP/1.0 200 OK\n" + 
                 "Allow: GET\nMIME-Version: 1.0\n"+
                 "Server : HttpServer : a Java Local HTTP Server\n"+
                 "Content-Type: " + fileType + "\n"+
                 "Content-Length: " + fileLength +
                 "\n\n";
        }
    }
