package servlet;

import proof.*;
import sdsi.*;
import sdsi.sexp.*;
import Tools.*;

import com.mortbay.HTTP.*;

import java.io.*;
import java.net.*;
import java.util.*;
import java.security.*;
import javax.servlet.*;
import javax.servlet.http.*;

/**
 * FileServlet serves up a tree of files using Snowflake security
 * implemented by ProtectedServlet.
 *
 * Individual requests are handled by instantiating an inner class,
 * FileServlet.PMHandler, that holds the state of the request while its
 * methods chew away on it.
 */
public class FileServlet extends ProtectedServlet
{
	File webRoot;
	HashMap fileToProofMap;
	boolean signFiles = true;
	boolean cacheSigns = true;

	/**
	 * Standard servlet initialization from a configuration object.
	 * This method creates a Prover that gathers initial delegations
	 * from the hard-coded directory <code>certs-server</code>.
	 * <br>The config parameter <code>root</code> defines the top of the
	 * Unix file tree to serve.
	 */
	public void init(ServletConfig config) {
		try {
			webRoot = new File(config.getInitParameter("root"));
			super.init(config);

			prover = new Prover2("certs-server");
			fileToProofMap = new HashMap();
			System.out.println("FileServlet.init() complete");
		} catch (Exception ex) {
			System.err.println("Exception in initializer");
			ex.printStackTrace();
		}
	}

	PSHandler getHandler(HttpServletRequest request,
		HttpServletResponse response) {
		return new Handler(request, response);
	}

	class Handler extends PSHandler {

		Handler(HttpServletRequest request, HttpServletResponse response) {
			super(this$0, request, response);
		}

		/**
		 * A service-specific tag definition. Return a tag that
		 * describes this request fairly specifically, so that
		 * delegations may be choosy about how to restrict delegations.
		 */
		Tag getResourceTag() {
			SexpList methodSexp = 
				new SexpList().append("method").append(request.getMethod());
			SexpList serviceSexp = 
				new SexpList().append("service")
					.append("Jon's Protected Service");
			SexpList resourcePathSexp = 
				new SexpList().append("resourcePath").append(getResourcePath());
			SexpList webSexp = 
				new SexpList().append("web").append(methodSexp)
					.append(serviceSexp).append(resourcePathSexp);
			SexpList tagSexp =
				new SexpList().append("tag").append(webSexp);
			try {
				return new Tag(tagSexp);
			} catch (SexpParseException ex) {
				ex.printStackTrace();
				return null;
			}
		}

		public void servePage()
			throws ServletException, IOException {
	
			String rp = getResourcePath();

			File requestedFile = new File(webRoot, rp);
	
			Vector v = new Vector();
			File parent = requestedFile;
			File current;
			do {
				current = parent;
				v.insertElementAt(current, 0);
				parent = current.getParentFile();
			} while (parent!=null && !parent.equals(current));
	
			// walk down from root, ensuring not to follow '..' or a symlink
			// TODO Don't know how test for symlinks in Java.
			for (int i=0; i<v.size(); i++) {
				File component = (File) v.elementAt(i);
				if (component.getName().equals("..")) {
					response.sendError(HttpServletResponse.SC_FORBIDDEN,
						"Path contains ..");
				}
			}
	
			if (!requestedFile.exists()) {
				response.sendError(HttpServletResponse.SC_NOT_FOUND,
					"Path not found on server");
			} else if (requestedFile.isDirectory()) {
				String resourcePath = getResourcePath();
				if (!resourcePath.equals("")
					&& !resourcePath.endsWith("/")) {
					response.setHeader("Location", getResourcePath()+"/");
					response.setStatus(response.SC_MOVED_PERMANENTLY,
						"You forgot the /, bonehead");
					// apparently you don't actually have to do anything to
					// the response to have it written...
				} else {
					displayDirectory(requestedFile);
				}
			} else {
				// Have the server name the file.
				if (signFiles) {
					nameFile(requestedFile, rp);
				}
				displayFile(requestedFile);
			}
		}
	
		
		// special out-of-band control channel for reconfiguring
		// the server.
		boolean checkExperimentReconfigure() {
			if (request.getHeader("X-Experiment-Control")!=null) {
				// re-init() everything we know about the servlet,
				// hoping to make these creepy heisenbugs hide for
				// a while.
				parent.init(parent.saveConfig);
				System.gc();	// more hocus-pocus. Anything for low cv. :v)

				String signFilesStr = request.getHeader("X-SignFiles");
				signFiles =  (signFilesStr != null)
							&& signFilesStr.startsWith("t");
				String cacheSignsStr = request.getHeader("X-CacheSigns");
				cacheSigns =  (cacheSignsStr != null)
							&& cacheSignsStr.startsWith("t");
				String debugStr = request.getHeader("X-Debug");
				debug =  (debugStr != null)
							&& debugStr.startsWith("t");
				System.out.println("signFiles: "+signFiles);
				System.out.println("cacheSigns: "+cacheSigns);
				System.out.println("debug: "+debug);
				try {
					Writer w = response.getWriter();
					w.write("nuthin'.");
				} catch (IOException ex) {
				}
				return true;
			}
			return false;
		}

		public void displayDirectory(File dir)
			throws ServletException, IOException {
	
			response.setContentType("text/html");
			Writer w = response.getWriter();
			w.write("<h3>Directory listing of "+dir.getName()+"</h3><ul>\n");
			w.write("<li><a href=\"..\">.. (up)</a><p>\n");
			File[] fileList = dir.listFiles();
			for (int i=0; i<fileList.length; i++) {
				w.write("<li><a href=\""
					+URLEncoder.encode(fileList[i].getName())
					+(fileList[i].isDirectory()
							? "/"
							: ""
					 	 )
					+"\">"
					+fileList[i].getName()+"</a>\n");
			}
			w.write("</ul>\n");
		}
	
		public void displayFile(File file)
			throws ServletException, IOException {
	
			response.setContentType("text/plain");
			CopyStream.copyStream(new FileInputStream(file),
				response.getOutputStream());
		}

		public void nameFile(File file, String uri)
			throws ServletException, IOException {
			FileProof fs = (FileProof) fileToProofMap.get(file);
			if (!cacheSigns
				|| fs==null
				|| fs.lastModified != file.lastModified()) {
				fs = new FileProof();
				ObjectHash fileHash =
					KeyTools.hashStream(new FileInputStream(file));
				sdsi.Name namePrincipal;
				try {
					namePrincipal = new sdsi.Name(
						prover.getIdentityPublicKey(), new String[] {uri});
				} catch (SDSIException ex) {
					// can't do anything here. Bummer.
					// Should log the failure.
					ex.printStackTrace();
					return;
				}
				fs.proof = prover.createAuth(fileHash,
					namePrincipal, prover.getIdentityPublicKey());
				fs.lastModified = file.lastModified();
				fileToProofMap.put(file, fs);
			}
			response.setHeader(DOCFORSERVERNAME, fs.proof.toReadableString(2000));
		}
	}

	static class FileProof {
		long lastModified;
		Proof proof;
	}
}
