package freenet.clients.http;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.util.Date;

import freenet.client.DefaultMIMETypes;
import freenet.l10n.NodeL10n;
import freenet.support.api.Bucket;
import freenet.support.api.HTTPRequest;
import freenet.support.io.FileBucket;

/**
 * Static Toadlet.
 * Serve up static files
 */
public class StaticToadlet extends Toadlet {
	StaticToadlet() {
		super(null);
	}
	
	public static final String ROOT_URL = "/static/";
	public static final String ROOT_PATH = "staticfiles/";
	public static final String OVERRIDE = "override/";
	public static final String OVERRIDE_URL = ROOT_URL + OVERRIDE;
	
	public void handleMethodGET(URI uri, HTTPRequest request, ToadletContext ctx) throws ToadletContextClosedException, IOException {
		String path = uri.getPath();
		
		if (!path.startsWith(ROOT_URL)) {
			// we should never get any other path anyway
			return;
		}
		try {
			path = path.substring(ROOT_URL.length());
		} catch (IndexOutOfBoundsException ioobe) {
			this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathNotFound"));
			return;
		}
		
		// be very strict about what characters we allow in the path, since
		if (!path.matches("^[A-Za-z0-9\\._\\/\\-]*$") || path.contains("..")) {
			this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathInvalidChars"));
			return;
		}
		
		if(path.startsWith(OVERRIDE)) {
			File f = this.container.getOverrideFile();
			if(f == null || (!f.exists()) || (f.isDirectory()) || (!f.isFile())) {
				this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathInvalidChars"));
				return;
			}
			f = f.getAbsoluteFile();
			if(f == null || (!f.exists()) || (f.isDirectory()) || (!f.isFile())) {
				this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathInvalidChars"));
				return;
			}
			File parent = f.getParentFile();
			// Basic sanity check.
			// Prevents user from specifying root dir.
			// They can still shoot themselves in the foot, but only when developing themes/using custom themes.
			// Because of the .. check above, any malicious thing cannot break out of the dir anyway.
			if(parent.getParentFile() == null) {
				this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathInvalidChars"));
				return;
			}
			File from = new File(parent, path.substring(OVERRIDE.length()));
			if((!from.exists()) && (!from.isFile())) {
				this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathInvalidChars"));
				return;
			}
			try {
				FileBucket fb = new FileBucket(from, true, false, false, false);
				ctx.sendReplyHeadersStatic(200, "OK", null, DefaultMIMETypes.guessMIMEType(path, false), fb.size(), new Date(System.currentTimeMillis() - 1000)); // Already expired, we want it to reload it.
				ctx.writeData(fb);
				return;
			} catch (IOException e) {
				// Not strictly accurate but close enough
				this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathNotFound"));
				return;
			}
		}
		
		InputStream strm = getClass().getResourceAsStream(ROOT_PATH+path);
		if (strm == null) {
			this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathNotFound"));
			return;
		}
		Bucket data = ctx.getBucketFactory().makeBucket(strm.available());
		OutputStream os = data.getOutputStream();
		try {
		byte[] cbuf = new byte[4096];
		while(true) {
			int r = strm.read(cbuf);
			if(r == -1) break;
			os.write(cbuf, 0, r);
		}
		} finally {
			strm.close();
			os.close();
		}
		
		URL url = getClass().getResource(ROOT_PATH+path);
		Date mTime = getUrlMTime(url);
		
		ctx.sendReplyHeadersStatic(200, "OK", null, DefaultMIMETypes.guessMIMEType(path, false), data.size(), mTime);

		ctx.writeData(data);
	}
	
	/**
	 * Try to find the modification time for a URL, or return null if not possible
	 * We usually load our resources from the JAR, or possibly from a file in some setups, so we check the modification time of
	 * the JAR for resources in a jar and the mtime for files.
	 */
	private Date getUrlMTime(URL url) {
		if (url.getProtocol().equals("jar")) {
			File f = new File(url.getPath().substring(0, url.getPath().indexOf('!')));
			return new Date(f.lastModified());
		} else if (url.getProtocol().equals("file")) {
			File f = new File(url.getPath());
			return new Date(f.lastModified());
		} else {
			return null;
		}
	}
	
	private String l10n(String key) {
		return NodeL10n.getBase().getString("StaticToadlet."+key);
	}

	@Override
	public String path() {
		return ROOT_URL;
	}

	/** Do we have a specific static file? Note that override files are not 
	 * supported here as it is a static method.
	 * @param The path to the file, relative to the staticfiles directory.
	 */
	public static boolean haveFile(String path) {
		URL url = StaticToadlet.class.getResource(ROOT_PATH+path);
		return url != null;
	}

}
