// ProxyDirectory.java
// $Id: ProxyDirectory.java,v 1.19 1998/01/20 16:33:56 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.jigsaw.proxy ;

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

import w3c.tools.store.*;
import w3c.www.http.*;
import w3c.www.mime.*;
import w3c.jigsaw.http.*;
import w3c.jigsaw.html.*;
import w3c.jigsaw.resources.*;

class FtpTunnel extends HTTPResource {

    public Reply get(Request request) 
	throws HTTPException
    {
	try {
	    MimeType mt;
	    URLConnection c  = request.getURL().openConnection();
	    InputStream   in = c.getInputStream();
	    boolean markUsable = in.markSupported();
	    // We don't know the content length
	    Reply reply = createDefaultReply(request, HTTP.OK);
	    reply.addPragma("no-cache");
	    reply.setNoCache();
	    reply.setContentLength(c.getContentLength());
	    if (markUsable)
		in.mark(Integer.MAX_VALUE);
	    try {
		mt = new MimeType(c.getContentType());
	    } catch (MimeTypeFormatException me) {
		mt = MimeType.TEXT_PLAIN;
	    }
	    reply.setContentType(mt);
	    if (markUsable) // cope with a well known java bug
		in.reset();
	    reply.setStream(in);
	    return reply;
	} catch (Exception ex) {
	    throw new HTTPException("Unable to ftp \""
				    + request.getURL()
				    + ", details \""+ex.getMessage()+"\"");
	}
    }

}

class Stats extends HTTPResource {
    ProxyDirectory proxy     = null;
    Date           startdate = null;
    boolean        hasICP    = true;

    protected String percentage(int part, int tot) {
	double p = ((double) part / ((double) tot)) * ((double) 100);
	return Integer.toString((int) p)+"%";
    }

    public Reply get(Request request) {
	Reply         r = createDefaultReply(request, HTTP.OK);
	HtmlGenerator g = new HtmlGenerator("Proxy statistics");
	int           c = proxy.reqcount+proxy.reqerred;
	
	if ( c == 0 )
	    c = 1;
	g.meta("Refresh", "30");
	g.append("<h1>Proxy statistics</h1>");
	g.append("<p>The proxy was last started at: <em>"
                 + startdate
                 + "</em>");
	g.append("<p><table align=\"center\" border=\"1\"");
	g.append("<tr><th colspan=\"3\">Counter<th>count<th>percentage");
	// The total number of hits to the proxy:
	g.append("<tr><td colspan=\"3\">Total number of handled requests");
	g.append("<td align=center>", Integer.toString(c));
	g.append("<td align=center>", percentage(c, c));
	// The total number of errors:
	g.append("<tr><td width=50><td colspan=\"2\">Erred requests");
	g.append("<td align=center>", Integer.toString(proxy.reqerred));
	g.append("<td align=center>", percentage(proxy.reqerred, c));
	// The total number of ICP redirects:
	g.append("<tr><td width=50><td colspan=\"2\">ICP redirects");
	g.append("<td align=center>", Integer.toString(proxy.cache_icps));
	g.append("<td align=center>", percentage(proxy.cache_icps, c));
	// The total number of no-cache:
	g.append("<tr><td width=50><td colspan=\"2\">Non cacheable");
	g.append("<td align=center>", Integer.toString(proxy.cache_nocache));
	g.append("<td align=center>", percentage(proxy.cache_nocache, c));
	// Cache accesses:
	int cached = (proxy.cache_hits +
		      proxy.cache_misses +
		      proxy.cache_revalidations +
		      proxy.cache_retrievals);
	g.append("<tr><td width=50><td colspan=\"2\">Cache Accesses");
	g.append("<td align=center>", Integer.toString(cached));
	g.append("<td align=center>", percentage(cached, c));
	// Hits (served by cache)
	g.append("<tr><td width=50><td width=50><td>Hits (served by cache)");
	g.append("<td align=center>", Integer.toString(proxy.cache_hits));
	g.append("<td align=center>", percentage(proxy.cache_hits, c));
	// Hits (revalidations)
	g.append("<tr><td width=50><td width=50><td>Hits (revalidations)");
	g.append("<td align=center>"
		 , Integer.toString(proxy.cache_revalidations));
	g.append("<td align=center>"
		 , percentage(proxy.cache_revalidations, c));
	// Misses (no cache entry)
	g.append("<tr><td width=50><td width=50><td>Misses (no entry)");
	g.append("<td align=center>", Integer.toString(proxy.cache_misses));
	g.append("<td align=center>", percentage(proxy.cache_misses, c));
	// Misses (retrievals)
	g.append("<tr><td width=50><td width=50><td>Misses (retrievals)");
	g.append("<td align=center>",Integer.toString(proxy.cache_retrievals));
	g.append("<td align=center>", percentage(proxy.cache_retrievals, c));
	g.append("</table>");
	// Some goodies:
	g.append("<hr>Generated by <i>"
		 , proxy.getServer().getURL().toExternalForm());
	r.setStream(g);
	r.addPragma("no-cache");
	r.setNoCache();
	return r;
    }

    Stats(ProxyDirectory proxy) {
	this.proxy     = proxy;
	this.startdate = new Date(System.currentTimeMillis());
    }
}

/**
 * A proxy module for Jigsaw.
 */

public class ProxyDirectory extends ForwardDirectory {
    /**
     * Attribute index - Should we tunnel ftp accesses ?
     */
    protected static int ATTR_HANDLEFTP = -1;

    static {
	Attribute a = null;
	Class     c = null;
	try {
	    c = Class.forName("w3c.jigsaw.proxy.ProxyDirectory");
	} catch (Exception ex) {
	    ex.printStackTrace();
	    System.exit(1);
	}
	// Register the handle-ftp attribute:
	a = new BooleanAttribute("handle-ftp"
				 , Boolean.FALSE
				 , Attribute.EDITABLE);
	ATTR_HANDLEFTP = AttributeRegistry.registerAttribute(c, a);
    }

    URL       url        = null;
    FtpTunnel ftphandler = null;
    Stats     statistics = null;

    /**
     * Trap changes to the handleftp attribute.
     * @param idx The attribute being set.
     * @param value The new value for that attribute.
     */

    public void setValue(int idx, Object value) {
	super.setValue(idx, value);
	if ( idx == ATTR_HANDLEFTP ) {
	    boolean b = ((Boolean) value).booleanValue();
	    if ( b ) {
		ftphandler = new FtpTunnel();
	    } else {
		ftphandler = null;
	    }
	}
    }

    /**
     * Do we handle ftp ?
     * @return A boolean.
     */

    public boolean checkHandleFTP() {
	return getBoolean(ATTR_HANDLEFTP, false);
    }

    /**
     * Lookup for an proxied resource.
     * @param request The request whose URI is to be looked up.
     * @exception HTTPException If something fails.
     */

    public boolean lookup(LookupState ls, LookupResult lr) 
	throws HTTPException
    {
	// Internal lookup:
	if ( ls.isInternal() )
	    return super.lookup(ls, lr);
	// Get the full URL from the request:
	Request request = ls.getRequest();
	URL     requrl  = request.getURL() ;
	if ((url != null)
	    && (requrl.getPort() == url.getPort())
	    && (requrl.getHost().equalsIgnoreCase(url.getHost()))) {
	    // Call super.lookup:
	    super.lookup(ls, lr);
	    if ( ls.hasMoreComponents() ) {
		HTTPResource root = getLocalRootResource();
		if ( root == null ) {
		    lr.setTarget(this);
		    return true;
		}
		return root.lookup(ls, lr);
	    } else {
		lr.setTarget(statistics);
		return true;
	    }
	} else {
	    // Always invoke super lookup, after notification that its a proxy
	    request.setProxy(true);
	    super.lookup(ls, lr);
	    if ( requrl.getProtocol().equals("ftp") ) {
		if (ftphandler != null)
		    lr.setTarget(ftphandler);
		else 
		    lr.setTarget(null);
		return true;
	    } else {
		lr.setTarget(this);
		return true;
	    }
	}
    }

    /**
     * Update the URL in which we are installed.
     * @param values The default attribute values.
     */

    public void initialize(Object values[]) {
	super.initialize(values);
	// Our home url:
	url = getServer().getURL();
	// If we do handle ftp, initialize:
	if ( checkHandleFTP() )
	    ftphandler = new FtpTunnel();
	// Initialize the stats:
	statistics = new Stats(this);
    }
	
}
