    // HTTPFrame.java
    // $Id: HTTPFrame.html,v 1.2 1998/03/26 09:52:17 benoit Exp $
    // (c) COPYRIGHT MIT and INRIA, 1996.
    // Please first read the full copyright statement in file COPYRIGHT.html

    package org.w3c.jigsaw.frames;

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

    import org.w3c.tools.sorter.*;
    import org.w3c.tools.resources.*;
    import org.w3c.tools.resources.event.*;
    import org.w3c.jigsaw.http.* ;
    import org.w3c.jigsaw.html.* ;
    import org.w3c.www.mime.*;
    import org.w3c.www.http.*;

    import org.w3c.tools.resources.ProtocolException;
    import org.w3c.tools.resources.NotAProtocolException;

    /**
     * Default class to handle the HTTP protocol, manage FileResource and
     * DirectoryResource.
     */
    public class HTTPFrame extends ProtocolFrame {

        /**
         * The special class of filter.
         */
        protected static Class filterClass = null;

        /**
         * Condition check return code - Condition existed but failed.
         */
        public static final int COND_FAILED = 1;
        /**
         * Condition check return code - Condition existed and succeeded.
         */
        public static final int COND_OK = 2;

        // Methods allowed by that class in general:
        protected static HttpTokenList _allowed        = null;
        private   static HttpTokenList _put_allowed    = null;
        private   static HttpTokenList _browse_allowed = null;
        private   static HttpTokenList _accept_ranges  = null;

        static {
    	// allowed shares _allowed value, that's a feature.
    	String str_allowed[] = { "GET", "HEAD", "OPTIONS", "TRACE"};
    	_allowed = HttpFactory.makeStringList(str_allowed);
    	String str_allow[] = { "HEAD" , "GET" , "PUT" , "OPTIONS", "TRACE" };
    	_put_allowed = HttpFactory.makeStringList(str_allow);
    	String str_all[] = { "HEAD" , "GET" , "BROWSE" , "OPTIONS","TRACE" };
    	_browse_allowed = HttpFactory.makeStringList(str_allow);
    	String accept_ranges[] = { "bytes" };
    	_accept_ranges = HttpFactory.makeStringList(accept_ranges);
        }
        // Methods allowed by instances of that class in particular:
        protected        HttpTokenList  allowed = _allowed;

        /**
         * Attributes index - The index for the quality attribute.
         */
        protected static int ATTR_QUALITY = -1 ;
        /**
         * Attribute index - The index for the title attribute.
         */
        protected static int ATTR_TITLE = -1 ;
        /**
         * Attribute index - The index for the content languages attribute.
         */
        protected static int ATTR_CONTENT_LANGUAGE = -1 ;
        /**
         * Attribute index - The index for the content encodings attribute.
         */
        protected static int ATTR_CONTENT_ENCODING = -1 ;
        /**
         * Attribute index - The index for the content type attribute.
         */
        protected static int ATTR_CONTENT_TYPE = -1 ;
        /**
         * Attribute index - The index for the content length attribute.
         */
        protected static int ATTR_CONTENT_LENGTH = -1 ;
        /**
         * Attribute index - The icon (if any) associated to the resource.
         */
        protected static int ATTR_ICON = -1 ;
        /**
         * Attribute index - Max age: the maximum drift allowed from reality.
         */
        protected static int ATTR_MAXAGE = -1 ;
        //
        // Attribute relative to FileResource
        //

        /**
         * Attribute index - Do we allow PUT method on this file.
         */
        protected static int ATTR_PUTABLE = -1 ;

        //
        // Attribute relative to DirectoryResource
        //

        /**
         * Attribute index - The index for our relocate attribute.
         */
        protected static int ATTR_RELOCATE = -1 ;
        /**
         * Attribute index - our index resource name.
         */
        protected static int ATTR_INDEX = -1 ;
        /**
         * Attribute index - The icon directory to use in dir listing.
         */
        protected static int ATTR_ICONDIR = -1 ;
        /**
         * Attribute index - Allow the GNN browse method.
         */
        protected static int ATTR_BROWSABLE = -1 ;
        /**
         * Attribute index - Style sheet for directory listing
         */
        protected static int ATTR_STYLE_LINK = -1 ;

        static {
    	Attribute a   = null ;
    	Class     cls = null ;

    	try {
    	    filterClass =
    		Class.forName("org.w3c.tools.resources.ResourceFilter");
    	} catch (Exception ex) {
    	    throw new RuntimeException("No ResourceFilter class found.");
    	}

    	// Get a pointer to our class:
    	try {
    	    cls = Class.forName("org.w3c.jigsaw.frames.HTTPFrame") ;
    	} catch (Exception ex) {
    	    ex.printStackTrace() ;
    	    System.exit(1) ;
    	}
    	// The quality attribute:
    	a = new DoubleAttribute("quality"
    				, new Double(1.0)
    				, Attribute.EDITABLE);
    	ATTR_QUALITY = AttributeRegistry.registerAttribute(cls, a) ;
    	// The title attribute:
    	a = new StringAttribute("title"
    				, null
    				, Attribute.EDITABLE) ;
    	ATTR_TITLE = AttributeRegistry.registerAttribute(cls, a) ;
    	// The content language attribute:
    	a = new LanguageAttribute("content-language"
    				  , null
    				  , Attribute.EDITABLE) ;
    	ATTR_CONTENT_LANGUAGE = AttributeRegistry.registerAttribute(cls,a);
    	// The content encoding attribute:
    	a = new EncodingAttribute("content-encoding"
    				  , null
    				  , Attribute.EDITABLE) ;
    	ATTR_CONTENT_ENCODING = AttributeRegistry.registerAttribute(cls,a);
    	// The content type attribute:
    	a = new MimeTypeAttribute("content-type"
    				  , MimeType.TEXT_PLAIN
    				  , Attribute.EDITABLE) ;
    	ATTR_CONTENT_TYPE = AttributeRegistry.registerAttribute(cls,a);
    	// The content length attribute:
    	a = new IntegerAttribute("content-length"
    				 , null
    				 , Attribute.COMPUTED);
    	ATTR_CONTENT_LENGTH = AttributeRegistry.registerAttribute(cls,a);
    	// The icon attribute:
    	a = new StringAttribute("icon"
    				, null
    				, Attribute.EDITABLE) ;
    	ATTR_ICON = AttributeRegistry.registerAttribute(cls, a) ;
    	// The max age attribute (in ms)
    	a = new LongAttribute("maxage"
    			      , null
    			      , Attribute.EDITABLE) ;
    	ATTR_MAXAGE = AttributeRegistry.registerAttribute(cls, a) ;

    	//
    	// Attribute relative to a FileResource
    	//

    	// The putable flag:
    	a = new BooleanAttribute("putable"
    				 , Boolean.FALSE
    				 , Attribute.EDITABLE) ;
    	ATTR_PUTABLE = AttributeRegistry.registerAttribute(cls, a) ;

    	//
    	// Attribute relative to a DirectoryResource
    	//

    	//Should we relocate invalid request to this directory ?
    	a = new BooleanAttribute("relocate"
    				 , Boolean.TRUE
    				 , Attribute.EDITABLE);
    	ATTR_RELOCATE = AttributeRegistry.registerAttribute(cls, a) ;
    	// Our index resource name (optional).
    	a = new StringAttribute("index"
    				, null
    				, Attribute.EDITABLE) ;
    	ATTR_INDEX = AttributeRegistry.registerAttribute(cls, a) ;
    	// Our icon directory.
    	a = new StringAttribute("icondir"
    				, null
    				, Attribute.EDITABLE) ;
    	ATTR_ICONDIR = AttributeRegistry.registerAttribute(cls,a);
    	// The browsable flag:
    	a = new BooleanAttribute("browsable"
    				 , Boolean.FALSE
    				 , Attribute.EDITABLE) ;
    	ATTR_BROWSABLE = AttributeRegistry.registerAttribute(cls, a) ;
    	// The style sheet attribute:
    	a = new StringAttribute("style-sheet-link"
    				, null
    				, Attribute.EDITABLE) ;
    	ATTR_STYLE_LINK = AttributeRegistry.registerAttribute(cls, a) ;
        }

        protected DirectoryResource dresource = null;
        protected FileResource      fresource = null;

        public void registerResource(FramedResource resource) {
    	super.registerResource(resource);
    	if (resource instanceof FileResource)
    	    fresource = (FileResource) resource;
    	else if (resource instanceof DirectoryResource)
    	    dresource = (DirectoryResource) resource;
        }

        public FileResource getFileResource() {
    	return fresource;
        }

        public DirectoryResource getDirectoryResource() {
    	return dresource;
        }

        /**
         * use this one instead of registerResource if the resource type
         * doesn't matter or if this is not a file or a directory resource.
         * In subclasses you should have to do that:
         * <pre>
         *  public void registerResource(FramedResource resource) {
         *   super.registerOtherResource(resource);
         *  }
         * </pre>
         * @param the resource to register.
         */
        public void registerOtherResource(FramedResource resource) {
    	super.registerResource(resource);
    	dresource = null;
    	fresource = null;
        }

        // The HTTPResource keeps a cache of ready to use Http values. This
        // allows to save converting to/from wire rep these objects. Not
        // much CPU time, but also memory is spared.
        HttpMimeType  contenttype     = null;
        HttpInteger   contentlength   = null;
        HttpDate      lastmodified    = null;
        HttpTokenList contentencoding = null;
        HttpTokenList contentlanguage = null;

        // The Http entity tag for this resource (for FileResource only)
        HttpEntityTag etag   = null;

        /**
         * Get this resource's help url.
         * @return An URL, encoded as a String, or <strong>null</strong> if not
         * available.
         */

        public String getHelpURL() {
    	httpd server = (httpd) getServer();
    	if ( server == null )
    	    return null;
    	String docurl = server.getDocumentationURL();
    	if ( docurl == null )
    	    return null;
    	return docurl + "/" + getClass().getName() + ".html";
        }

        /**
         * Get the help URL for that resource's attribute.
         * @param topic The topic (can be an attribute name, or a property, etc).
         * @return A String encoded URL, or <strong>null</strong>.
         */

        public String getHelpURL(String topic) {
    	httpd server = (httpd) getServer();
    	if ( server == null )
    	    return null;
    	String docurl = server.getDocumentationURL();
    	if ( docurl == null )
    	    return null;
    	Class defines = AttributeRegistry.getAttributeClass(getClass(), topic);
    	if ( defines != null )
    	    return docurl + "/" + defines.getName() + ".html";
    	return null;
        }

        /**
         * Listen its resource.
         */
        public void attributeChanged(AttributeChangedEvent evt) {
    	super.attributeChanged(evt);
    	String name = evt.getAttribute().getName();
    	if (evt.getSource() instanceof FileResource) {
    	    if (name.equals("file-stamp")) {
    		etag = null;
    		lastmodified = null;
    	    } else if (name.equals("file-length")) {
    		setValue(ATTR_CONTENT_LENGTH, evt.getNewValue());
    	    }
    	}
    	if (name.equals("last-modified")) {
    	    setValue(ATTR_LAST_MODIFIED, evt.getNewValue());
    	} else {
    	    lastmodified = null;
    	}
        }

        /**
         * Catch setValue, to maintain cached header values correctness.
         * @param idx The index of the attribute to be set.
         * @param value The new value for the attribute.
         */

        public synchronized void setValue(int idx, Object value) {
    	super.setValue(idx, value);
    	if (idx == ATTR_CONTENT_TYPE)
    	    contenttype = null;
    	if (idx == ATTR_CONTENT_LENGTH)
    	    contentlength = null;
    	if ( idx == ATTR_CONTENT_ENCODING )
    	    contentencoding = null;
    	if ( idx == ATTR_CONTENT_LANGUAGE )
    	    contentlanguage = null;
    	if ( idx == ATTR_PUTABLE) {
    	    if (fresource != null) {
    		if (value == Boolean.TRUE)
    		    allowed = _put_allowed;
    		else
    		    allowed = _allowed;
    	    }
    	}
    	// Any attribute setting modifies the last modified time:
    	lastmodified = null;
        }

        /**
         * Get the full URL for that resource.
         * @return An URL instance.
         */
        public URL getURL(Request request) {
    	try {
    	    return new URL(request.getURL(), resource.getURLPath());
    	} catch (MalformedURLException ex) {
    	    throw new RuntimeException("unable to build "+
    				       getURLPath()+
    				       " full URL, from server "+
    				       getServer().getURL());
    	}
        }

        /**
         * Get this resource quality.
         * @return The resource quality, or some negative value if not defined.
         */

        public double getQuality() {
    	return getDouble(ATTR_QUALITY, -1.0) ;
        }

        /**
         * Get this resource title.
         * @return This resource's title, or <strong>null</strong> if not
         *    defined.
         */

        public String getTitle() {
    	return getString(ATTR_TITLE, null) ;
        }

        /**
         * Get this resource content language.
         * Language are stored as a comma separated String of tokens.
         * @return A comma separated string of language tokens, or
         *    <strong>null</strong> if undefined.
         */

        public String getContentLanguage() {
    	return (String) getValue(ATTR_CONTENT_LANGUAGE, null) ;
        }

        /**
         * Get this resource content encoding.
         * The content encoding of a resource is stored as a comma separated
         * list of tokens (as decribed in the Content_encoding header of the
         * HTTP specification, and in the order they should appear in the header).
         * @return A string of comma separated encoding tokens, or
         *    <strong>null</strong> if not defined.
         */

        public String getContentEncoding() {
    	String def = (String) attributes[ATTR_CONTENT_ENCODING].getDefault();
    	return (String) getString (ATTR_CONTENT_ENCODING, def) ;
        }

        /**
         * Get this resource content type.
         * @return An instance of MIMEType, or <strong>null</strong> if not
         *    defined.
         */

        public MimeType getContentType() {
    	return (MimeType) getValue(ATTR_CONTENT_TYPE, null);
        }

        /**
         * Get this resource content length.
         * @return The resource content length, or <strong>-1</strong> if not
         *    defined.
         */

        public int getContentLength() {
    	return getInt(ATTR_CONTENT_LENGTH, -1) ;
        }

        /**
         * Get this resource's icon.
         */

        public String getIcon() {
    	return getString(ATTR_ICON, null) ;
        }

        /**
         * Get this resource's max age.
         * The max age of a resource indicates how much drift is allowed between
         * the physicall version of the resource, and any in-memory cached version
         * of it.
         * <p>The max age attribute is a long number giving the number of
         * milliseconds of allowed drift.
         */

        public long getMaxAge() {
    	return getLong(ATTR_MAXAGE, (long) -1) ;
        }

        //
        // Relative to FileResource ...
        //

        /**
         * Does this resource support byte ranges.
         */
        protected boolean acceptRanges = false;

        /**
         * Get the PUT'able flag (are we allow to PUT to the resource ?)
         */
        public boolean getPutableFlag() {
    	return getBoolean(ATTR_PUTABLE, false) ;
        }

        public Reply handleRangeRequest(Request request, HttpRange r)
    	throws ProtocolException
        {
    	// Should we check against a IfRange header ?
    	HttpEntityTag t = request.getIfRange();

    	if ( t != null ) {
    	    if (t.isWeak() || ! t.getTag().equals(etag.getTag()))
    		return null;
    	}
    	// Check the range:
    	int cl = getContentLength();
    	int fb = r.getFirstPosition();
    	int lb = r.getLastPosition();
    	if ((fb < 0) && (lb >= 0)) {
    	    fb = cl - 1 - lb;
    	    lb = cl;
    	} else if (lb < 0) {
    	    lb = cl;
    	}
    	if ((fb < 0) || (lb < 0) || (fb <= lb)) {
    	    HttpContentRange cr = null;
    	    fb = (fb < 0) ? 0 : fb;
    	    lb = ((lb > cl) || (lb < 0)) ? cl : lb;
    	    cr = HttpFactory.makeContentRange("bytes", fb, lb, cl);
    	    // Emit reply:
    	    Reply rr = createDefaultReply(request, HTTP.PARTIAL_CONTENT);
    	    try {
    		rr.setContentLength(lb-fb);
    		rr.setHeaderValue(rr.H_CONTENT_RANGE, cr);
    		rr.setStream(new ByteRangeOutputStream(fresource.getFile(),
    						       fb,
    						       lb+1));
    		return rr;
    	    } catch (IOException ex) {
    	    }
    	}
    	return null;
        }

        //
        // Relative to DirectoryResource ...
        //

        /**
         * Get this class browsable flag.
         */
        public boolean getBrowsableFlag() {
    	return getBoolean (ATTR_BROWSABLE, false) ;
        }

        /**
         * Get this frame style sheet link
         */
        public String getStyleSheetURL() {
    	return getString (ATTR_STYLE_LINK, null);
        }

        /**
         * Our current (cached) directory listing.
         */
        protected HtmlGenerator listing = null ;
        /**
         * The time at which we generated the directory index.
         */
        protected long listing_stamp = -1 ;

        private String getUnextendedName(String name) {
    	int strlen = name.length() ;
    	for (int i = 0 ; i < strlen ; i++) {
    	    // FIXME: Should use the system props to get the right sep
    	    if ( name.charAt(i) == '.' ) {
    		if ( i == 0 )
    		    return null ;
    		return name.substring(0, i) ;
    	    }
    	}
    	return null ;
        }

        /**
         * Get the optional icon directory.
         */
        public String getIconDirectory() {
    	return getString(ATTR_ICONDIR, "/icons") ;
        }

        /**
         * Should we relocate invalid requests to this directory.
         * @return A boolean <strong>true</strong> if we should relocate.
         */
        public boolean getRelocateFlag() {
    	return getBoolean(ATTR_RELOCATE, true) ;
        }

        /**
         * Get the optinal index name for this directory listing.
         * @return The name of the resource responsible to list that container.
         */
        public String getIndex() {
    	return (String) getValue(ATTR_INDEX, null) ;
        }

        /**
         * Add our own Style Sheet to the HtmlGenerator.
         * @param g The HtmlGenerator.
         */
        protected void addStyleSheet(HtmlGenerator g) {
    	// Add style link
    	String css_url = getStyleSheetURL();
    	if (css_url != null) {
    	    g.addLink( new HtmlLink("STYLESHEET",
    				    css_url,
    				    MimeType.TEXT_CSS));
    	}
        }

        /**
         * Reply with an HTML doc listing the resources of this directory.
         * This function takes special care not to regenerate a directory listing
         * when one is available. It also caches the date of the directory
         * listing, so that it can win big with NOT_MODIFIED.
         * <p>Using a modem, I know that each place I can reply with an
         * NOT_MODIFIED, <strong>is</strong> a big win.
         * @param request The request to handle.
         * @exception ProtocolException If processsing the request failed.
         */

        public synchronized Reply getDirectoryListing(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	if (dresource == null)
    	    throw new NotAProtocolException("this frame is not attached to a "+
    					    "DirectoryResource. ("+
    					    resource.getIdentifier()+")");
    	// delete us if the directory was deleted
    	if (! dresource.getDirectory().exists()) {
    	    //delete us and emit an error
    	    String msg = dresource.getIdentifier()+
    		": deleted, removing the DirectoryResource";
    	    getServer().errlog(dresource, msg);
    	    try {
    		dresource.delete();
    	    } catch (MultipleLockException ex) {
    	    }
    	    // Emit an error back:
    	    Reply error = request.makeReply(HTTP.NOT_FOUND) ;
    	    error.setContent ("<h1>Document not found</h1>"+
    			      "<p>The document "+
    			      request.getURL()+
    			      " is indexed but not available."+
    			      "<p>The server is misconfigured.") ;
    	    throw new HTTPException (error) ;
    	}
    	// Have we already an up-to-date computed a listing ?
    	if ((listing == null)
    	    || (dresource.getDirectory().lastModified() > listing_stamp)
    	    || (dresource.getLastModified() > listing_stamp)
    	    || (getLastModified() > listing_stamp)) {

    	    Class http_class = null;
    	    try {
    		http_class = Class.forName("org.w3c.jigsaw.frames.HTTPFrame");
    	    } catch (ClassNotFoundException ex) {
    		http_class = null;
    	    }

    	    Enumeration   enum      =
    		dresource.enumerateResourceIdentifiers() ;
    	    Vector        resources = Sorter.sortStringEnumeration(enum) ;
    	    HtmlGenerator g         =
    		new HtmlGenerator("Directory of "+
    				  dresource.getIdentifier());
    	    // Add style link
    	    addStyleSheet(g);
    	    g.append("<h1>"+dresource.getIdentifier()+"</h1>");
    	    // Link to the parent, when possible:
    	    if ( dresource.getParent() != null )
    		g.append("<p><a href=\"..\">Parent</a><br>");
    	    // List the children:
    	    for (int i = 0 ; i < resources.size() ; i++) {
    		String       name     = (String) resources.elementAt(i);
    		ResourceReference rr = null;
    		rr = dresource.lookup(name);
    		FramedResource resource = null;
    		try {
    		    resource = (FramedResource) rr.lock();
    		    HTTPFrame itsframe = null;
    		    if (http_class != null)
    			itsframe = (HTTPFrame) resource.getFrame(http_class);
    		    if (itsframe != null) {
    			// Icon first, if available
    			String icon = itsframe.getIcon() ;
    			if ( icon != null )
    			    g.append("<img src=\""+
    				     getIconDirectory() +"/" + icon+
    				     "\">");
    			// Resource's name with link:
    			g.append("<a href=\""
    				 , resource.getURLPath()
    				 , "\">"+name+"</a>");
    			// resource's title, if any:
    			String title = itsframe.getTitle();
    			if ( title != null )
    			    g.append(" "+title);
    			g.append("<br>");
    		    } else {
    			// Resource's name with link:
    			g.append("<a href=\""
    				 , resource.getURLPath()
    				 , "\">"+name+"</a>"+" No HTTP Frame!");
    			g.append("<br>");
    		    }
    		} catch (InvalidResourceException ex) {
    		    g.append(name+" cannot be loaded (server misconfigured)");
    		    g.append("<br>");
    		    continue;
    		} finally {
    		    rr.unlock();
    		}
    	    }
    	    g.close() ;
    	    listing_stamp = getLastModified() ;
    	    listing       = g ;
    	} else if ( checkIfModifiedSince(request) == COND_FAILED ) {
    	    // Is it an IMS request ?
    	    return createDefaultReply(request, HTTP.NOT_MODIFIED) ;
    	}
    	// New content or need update:
    	Reply reply = createDefaultReply(request, HTTP.OK) ;
    	reply.setLastModified(listing_stamp) ;
    	reply.setStream(listing) ;
    	return reply ;
        }

        //
        // Commom part.
        //

        /**
         * Update the cached headers value.
         * Each resource maintains a set of cached values for headers, this
         * allows for a nice sped-up in headers marshalling, which - as the
         * complexity of the protocol increases - becomes a bottleneck.
         */

        protected void updateCachedHeaders() {
    	// Precompute a set of header values to keep by:
    	if ( contenttype == null )
    	    contenttype = HttpFactory.makeMimeType(getContentType());
    	if (contentlength == null) {
    	    int cl = getContentLength();
    	    if ( cl >= 0 )
    		contentlength = HttpFactory.makeInteger(getContentLength());
    	}
    	if ( lastmodified == null ) {
    	    long lm = getLastModified();
    	    if ( lm > 0 )
    		lastmodified = HttpFactory.makeDate(getLastModified());
    	}
    	if (definesAttribute(ATTR_CONTENT_ENCODING) &&(contentencoding==null))
    	    contentencoding = HttpFactory.makeStringList(getContentEncoding());
    	if (definesAttribute(ATTR_CONTENT_LANGUAGE) &&(contentlanguage==null))
    	    contentlanguage = HttpFactory.makeStringList(getContentLanguage());

    	if (fresource != null) {
    	    // We only take car eof etag here:
    	    if ( etag == null ) {
    		long lstamp = fresource.getFileStamp();
    		if ( lstamp >= 0L ) {
    		    String soid  = Integer.toString(getOid(), 32);
    		    String stamp = Long.toString(lstamp, 32);
    		    etag = HttpFactory.makeETag(false, soid+":"+stamp);
    		}
    	    }
    	}
        }

        /**
         * Create a reply to answer to request on this file.
         * This method will create a suitable reply (matching the given request)
         * and will set all its default header values to the appropriate
         * values.
         * @param request The request to make a reply for.
         * @return An instance of Reply, suited to answer this request.
         */

        public Reply createDefaultReply(Request request, int status) {
    	Reply reply = request.makeReply(status);
    	updateCachedHeaders();
    	if ( status != HTTP.NOT_MODIFIED ) {
    	    if ( contentlength != null )
    		reply.setHeaderValue(Reply.H_CONTENT_LENGTH, contentlength);
    	    if ( contenttype != null )
    		reply.setHeaderValue(Reply.H_CONTENT_TYPE, contenttype);
    	    if ( lastmodified != null )
    		reply.setHeaderValue(Reply.H_LAST_MODIFIED, lastmodified);
    	    if ( contentencoding != null )
    		reply.setHeaderValue(Reply.H_CONTENT_ENCODING,contentencoding);
    	    if ( contentlanguage != null )
    		reply.setHeaderValue(Reply.H_CONTENT_LANGUAGE,contentlanguage);

    	}
    	long maxage = getMaxAge();
    	if ( maxage >= 0 ) {
    	    if (reply.getMajorVersion() >= 1 ) {
    		if (reply.getMinorVersion() >= 1) {
    		    reply.setMaxAge((int) (maxage / 1000));
    		}
    		// If max-age is zero, say what you mean:
    		long expires = (System.currentTimeMillis()
    				+ ((maxage == 0) ? -1000 : maxage));
    		reply.setExpires(expires);
    	    }
    	}
    	// Set the date of the reply (round it to secs):
    	reply.setDate((System.currentTimeMillis() / 1000L) * 1000L);

    	if (fresource != null) {
    	    // Set the entity tag:
    	    if ( etag != null )
    		reply.setHeaderValue(reply.H_ETAG, etag);
    	    if ( acceptRanges )
    		reply.setHeaderValue(reply.H_ACCEPT_RANGES, _accept_ranges);
    	}

    	return reply;
        }

        /**
         * Check the <code>If-Match</code> condition of that request.
         * @param request The request to check.
         * @return An integer, either <code>COND_FAILED</cond> if condition
         * was checked, but failed, <code>COND_OK</code> if condition was checked
         * and succeeded, or <strong>0</strong> if the condition was not checked
         * at all (eg because the resource or the request didn't support it).
         */

        public int checkIfMatch(Request request) {
    	if (fresource != null) {
    	    HttpEntityTag tags[] = request.getIfMatch();
    	    if ( tags != null ) {
    		// Good, real validators in use:
    		if ( etag != null ) {
    		    // Note: if etag is null this means that the resource has
    		    // changed and has not been even emited since then...
    		    for (int i = 0 ; i < tags.length ; i++) {
    			HttpEntityTag t = tags[i];
    			if ((!t.isWeak()) && t.getTag().equals(etag.getTag()))
    			    return COND_OK;
    		    }
    		}
    		return COND_FAILED;
    	    }
    	}
    	return 0;
        }

        /**
         * Check the <code>If-None-Match</code> condition of that request.
         * @param request The request to check.
         * @return An integer, either <code>COND_FAILED</cond> if condition
         * was checked, but failed, <code>COND_OK</code> if condition was checked
         * and succeeded, or <strong>0</strong> if the condition was not checked
         * at all (eg because the resource or the request didn't support it).
         */

        public int checkIfNoneMatch(Request request) {
    	if (fresource != null) {
    	    // Check for an If-None-Match conditional:
    	    HttpEntityTag tags[] = request.getIfNoneMatch();
    	    if ( tags != null ) {
    		if ( etag == null )
    		    return COND_OK;
    		for (int i = 0 ; i < tags.length ; i++) {
    		    HttpEntityTag t = tags[i];
    		    if (( ! t.isWeak()) && t.getTag().equals(etag.getTag()))
    			return COND_FAILED;
    		}
    		return COND_OK;
    	    }
    	}
    	return 0;
        }

        /**
         * Check the <code>If-Modified-Since</code> condition of that request.
         * @param request The request to check.
         * @return An integer, either <code>COND_FAILED</cond> if condition
         * was checked, but failed, <code>COND_OK</code> if condition was checked
         * and succeeded, or <strong>0</strong> if the condition was not checked
         * at all (eg because the resource or the request didn't support it).
         */

        public int checkIfModifiedSince(Request request) {
    	// Check for an If-Modified-Since conditional:
    	long ims = request.getIfModifiedSince();
    	if (dresource != null) {
    	    if (ims >= 0)
    		return (((listing_stamp > 0) && (listing_stamp - 1000 <= ims))
    			? COND_FAILED
    			: COND_OK);
    	} else if (fresource != null) {
    	    long cmt = getLastModified();
    	    if ( ims >= 0 )
    		return ((cmt > 0) && (cmt - 1000 <= ims))
    		    ? COND_FAILED : COND_OK;
    	}
    	return 0;
        }

        /**
         * Check the <code>If-Unmodified-Since</code> condition of that request.
         * @param request The request to check.
         * @return An integer, either <code>COND_FAILED</cond> if condition
         * was checked, but failed, <code>COND_OK</code> if condition was checked
         * and succeeded, or <strong>0</strong> if the condition was not checked
         * at all (eg because the resource or the request didn't support it).
         */

        public int checkIfUnmodifiedSince(Request request) {
    	if (fresource != null) {
    	    // Check for an If-Unmodified-Since conditional:
    	    long iums = request.getIfUnmodifiedSince();
    	    long cmt = getLastModified();
    	    if ( iums >= 0 )
    		return ((cmt > 0) && (cmt - 1000) >= iums)
    		    ? COND_FAILED : COND_OK;
    	}
    	return 0;
        }

        public boolean lookup(LookupState ls, LookupResult lr)
    	throws ProtocolException
        {
    	RequestInterface req = ls.getRequest();
    	if (! checkRequest(req))
    	    return false;
    	if (lookupFilters(ls,lr))
    	    return true;
    	return lookupResource(ls,lr);
        }

        protected boolean lookupResource(LookupState ls, LookupResult lr)
    	throws ProtocolException
        {
    	if (fresource != null) {
    	    return lookupFile(ls,lr);
    	} else if (dresource != null) {
    	    return lookupDirectory(ls,lr);
    	} else {
    	    return lookupOther(ls,lr);
    	}
        }

        protected boolean lookupFilters(LookupState ls, LookupResult lr)
    	throws ProtocolException
        {
    	ResourceFilter filters[] = getFilters();
    	if ( filters != null ) {
    	    // Mark filters, for them to be called at outgoing time:
    	    lr.addFilters(filters);
    	    // Some clever filter around ?
    	    for (int i = 0 ; i < filters.length ; i++) {
    		if ( filters[i] == null )
    		    continue;
    		if ( filters[i].lookup(ls, lr) )
    		    return true;
    	    }
    	}
    	return false;
        }

        protected boolean lookupDirectory(LookupState ls, LookupResult lr)
    	throws ProtocolException
        {
    	// Give a chance to our super-class to run its own lookup scheme:
    	// do we have to create a resource (PUT) ?
    	if ((ls.hasMoreComponents()) && getPutableFlag()) {
    	    Request request = (Request) ls.getRequest() ;
    	    if ((request == null) || request.getMethod().equals("PUT")) {
    		// We might well want to create a resource:
    		String            name = ls.peekNextComponent() ;
    		ResourceReference rr   = dresource.lookup(name);
    		if ((rr == null) && (dresource.getExtensibleFlag())) {
    		    if (ls.countRemainingComponents() == 1)
    			rr = dresource.createResource(name);
    		    else
    			rr = dresource.createDirectoryResource(name);
    		    if (rr == null) {
    			Reply error = request.makeReply(HTTP.BAD_REQUEST) ;
    			error.setContent(
    				      "Failed to create resource "+
    				      name +" : "+
    				      "Unable to create the appropriate file:"+
    				      request.getURLPath()+
    				      " or the indexer wasn't able to "+
    				      "deduce a resource for it.");
    			throw new HTTPException (error);
    		    }
    		} else if (rr == null) {
    		    Reply error = request.makeReply(HTTP.BAD_REQUEST) ;
    		    error.setContent("Failed to create resource "+
    				     name +" : "+
    				     dresource.getIdentifier()+
    				     " is not extensible.");
    		    throw new HTTPException (error);
    		}
    	    }
    	}
    	if ( super.lookup(ls, lr) ) {
    	    if ( ! ls.isDirectory() && ! ls.isInternal() ) {
    		// The directory lookup URL doesn't end with a slash:
    		Request request = (Request)ls.getRequest() ;
    		if ( request == null ) {
    		    lr.setTarget(null);
    		    return true;
    		}
    		URL url = null;
    		try {
    		    url = (ls.hasRequest()
    			   ? getURL(request)
    			   : new URL(getServer().getURL(),
    				     resource.getURLPath()));
    		} catch (MalformedURLException ex) {
    		    getServer().errlog(this, "unable to build full URL.");
    		    throw new HTTPException("Internal server error");
    		}
    		String msg = "Invalid requested URL: the directory resource "+
    		    " you are trying to reach is available only through "+
    		    " its full URL: <a href=\""+
    		    url + "\">" + url + "</a>.";
    		if ( getRelocateFlag() ) {
    		    // Emit an error (with reloc if allowed)
    		    Reply reloc = request.makeReply(HTTP.MOVED_TEMPORARILY);
    		    reloc.setContent(msg) ;
    		    reloc.setLocation(url);
    		    lr.setTarget(null);
    		    lr.setReply(reloc);
    		    return true;
    		} else {
    		    Reply error = request.makeReply(HTTP.NOT_FOUND) ;
    		    error.setContent(msg) ;
    		    lr.setTarget(null);
    		    lr.setReply(error);
    		    return true;
    		}
    	    } else if ( ! ls.isInternal() ) {
    		// return the index file.
    		String index = getIndex();
    		if ( index != null && index.length() > 0) {
    		    DirectoryResource dir = (DirectoryResource) resource;
    		    ResourceReference rr = dir.lookup(index);
    		    if (rr != null) {
    			try {
    			    FramedResource rindex = (FramedResource) rr.lock();
    			    return rindex.lookup(ls,lr);
    			} catch (InvalidResourceException ex) {
    			} finally {
    			    rr.unlock();
    			}
    		    }
    		}
    	    }
    	    return true;
    	}
    	return false;
        }

        protected boolean lookupFile(LookupState ls, LookupResult lr)
    	throws ProtocolException
        {
    	return super.lookup(ls,lr);
        }

        protected boolean lookupOther(LookupState ls, LookupResult lr)
    	throws ProtocolException
        {
    	return super.lookup(ls,lr);
        }

        public boolean checkRequest(RequestInterface request) {
    	return ((request == null)
    		? true
    		:(request instanceof org.w3c.jigsaw.http.Request));
        }

        protected ReplyInterface performFrames(RequestInterface request)
    	throws ProtocolException, NotAProtocolException
        {
    	return super.performFrames(request);
        }

        public ReplyInterface perform(RequestInterface req)
    	throws ProtocolException, NotAProtocolException
        {
    	ReplyInterface repi = super.perform(req);
    	if (repi != null)
    	    return repi;

    	if (! checkRequest(req))
    	    return null;

    	Reply  reply  = null;
    	Request request = (Request) req;
    	String method = request.getMethod () ;
    	// Perform the request:
    	if ( method.equals("GET") ) {
    	    reply = get(request) ;
    	} else if ( method.equals("HEAD") ) {
    	    reply = head(request) ;
    	} else if ( method.equals("POST") ) {
    	    reply = post(request) ;
    	} else if ( method.equals("PUT") ) {
    	    reply = put(request) ;
    	} else if ( method.equals("OPTIONS") ) {
    	    reply = options(request);
    	} else if ( method.equals("DELETE") ) {
    	    reply = delete(request) ;
    	} else if ( method.equals("LINK") ) {
    	    reply = link(request) ;
    	} else if ( method.equals("UNLINK") ) {
    	    reply = unlink(request) ;
    	} else if ( method.equals("TRACE") ) {
    	    reply = trace(request) ;
    	} else {
    	    reply = extended(request) ;
    	}
    	return reply;
        }

        /**
         * The default GET method.
         * @param request The request to handle.
         * @exception ProtocolException if processing the request failed.
         * @exception NotAProtocolException If the client instance controling the
         * request processing got a fatal error.
         */

        public Reply get(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	if (dresource != null) {
    	    // we manage a DirectoryResource
    	    return getDirectoryResource(request) ;
    	} else if (fresource != null) {
    	    // we manage a FileResource
    	    return getFileResource(request);
    	} else {
    	    return getOtherResource(request);
    	}
        }

        protected Reply getOtherResource(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	// we don't manage this kind of resource
    	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
    	error.setContent("Method GET not implemented.") ;
    	throw new HTTPException (error) ;
        }

        protected Reply getFileResource(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	if (fresource == null)
    	    throw new NotAProtocolException("this frame is not attached to a "+
    					    "FileResource. ("+
    					    resource.getIdentifier()+")");
    	Reply reply = null;
    	File file = fresource.getFile() ;
    	fresource.checkContent();
    	updateCachedHeaders();
    	// Check validators:
    	if ( checkIfMatch(request) == COND_FAILED ) {
    	    Reply r = request.makeReply(HTTP.PRECONDITION_FAILED);
    	    r.setContent("Pre-conditions failed.");
    	    return r;
    	}
    	if ( checkIfNoneMatch(request) == COND_FAILED )
    	    return createDefaultReply(request, HTTP.NOT_MODIFIED);
    	if ( checkIfModifiedSince(request) == COND_FAILED )
    	    return createDefaultReply(request, HTTP.NOT_MODIFIED);
    	if ( checkIfUnmodifiedSince(request) == COND_FAILED ) {
    	    Reply r = request.makeReply(HTTP.PRECONDITION_FAILED);
    	    r.setContent("Pre-conditions failed.");
    	    return r;
    	}
    	// Does this file really exists, if so send it back
    	if ( file.exists() ) {
    	    reply = null;
    	    // Check for a range request:
    	    HttpRange ranges[] = request.getRange();
    	    if ((ranges != null) && (ranges.length == 1)) {
    		Reply rangereply = handleRangeRequest(request, ranges[0]);
    		if ( rangereply != null )
    		    return rangereply;
    	    }
    	    // Default to full reply:
    	    reply = createDefaultReply(request, HTTP.OK) ;
    	    try {
    		reply.setStream(new FileInputStream(file));
    	    } catch (IOException ex) {
    		// I hate to have to loose time in tries
    	    }
    	    return reply ;
    	} else {
    	    // Delete the resource if parent is extensible:
    	    boolean extensible = false;
    	    ResourceReference rr = fresource.getParent();
    	    ResourceReference rrtemp = null;
    	    Resource p = null;
    	    while ( true ) {
    		try {
    		    if (rr == null)
    			break;
    		    p = rr.lock();
    		    if (p instanceof DirectoryResource) {
    			extensible =
    			    ((DirectoryResource)p).getExtensibleFlag();
    			break;
    		    }
    		    rrtemp = p.getParent();
    		} catch (InvalidResourceException ex) {
    		    break;
    		} finally {
    		    if (rr != null)
    			rr.unlock();
    		}
    		rr = rrtemp;
    	    }
    	    if (extensible) {
    		// The resource is indexed but has no file, emit an error
    		String msg = fresource.getFile()+
    		    ": deleted, removing the FileResource.";
    		getServer().errlog(fresource, msg);
    		try {
    		    fresource.delete();
    		} catch (MultipleLockException ex) {
    		    Reply error = request.makeReply(HTTP.NOT_FOUND) ;
    		    error.setContent ("<h1>Document not found</h1>"+
    				      "<p>The document "+
    				      request.getURL()+
    				      " is indexed but not available."+
    				      "<p>"+ex.getMessage()+
    				      "<p>The server is misconfigured.") ;
    		    throw new HTTPException (error) ;
    		}
    	    }
    	    // Emit an error back:
    	    Reply error = request.makeReply(HTTP.NOT_FOUND) ;
    	    error.setContent ("<h1>Document not found</h1>"+
    			      "<p>The document "+
    			      request.getURL()+
    			      " is indexed but not available."+
    			      "<p>The server is misconfigured.") ;
    	    throw new HTTPException (error) ;
    	}
        }

        protected Reply getDirectoryResource(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	return getDirectoryListing(request) ;
        }

        /**
         * The default HEAD method replies does a GET and removes entity.
         * @param request The request to handle.
         * @exception ProtocolException Always thrown, to return a NOT_IMPLEMENTED
         *    error.
         * @exception NotAProtocolException If the client instance controling the
         * request processing got a fatal error.
         */

        public Reply head(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	if (dresource != null) {
    	    return headDirectoryResource(request);
    	} else if (fresource != null) {
    	    return headFileResource(request);
    	} else {
    	    return headOtherResource(request);
    	}
        }

        protected Reply headOtherResource(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	Reply reply = null;
    	reply = getOtherResource(request) ;
    	reply.setStream((InputStream) null);
    	return reply;
        }

        protected Reply headDirectoryResource(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	Reply reply = null;
    	reply = getDirectoryResource(request) ;
    	reply.setStream((InputStream) null);
    	return reply;
        }

        protected Reply headFileResource(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	if (fresource == null)
    	    throw new NotAProtocolException("this frame is not attached to a "+
    					    "FileResource. ("+
    					    resource.getIdentifier()+")");
    	Reply reply = null;
    	fresource.checkContent();
    	updateCachedHeaders();
    	// Conditional check:
    	if ( checkIfMatch(request) == COND_FAILED ) {
    	    Reply r = request.makeReply(HTTP.PRECONDITION_FAILED);
    	    r.setContent("Pre-conditions failed.");
    	    return r;
    	}
    	if ( checkIfNoneMatch(request) == COND_FAILED )
    	    return createDefaultReply(request, HTTP.NOT_MODIFIED);
    	if ( checkIfModifiedSince(request) == COND_FAILED )
    	    return createDefaultReply(request, HTTP.NOT_MODIFIED);
    	if ( checkIfUnmodifiedSince(request) == COND_FAILED ) {
    	    Reply r = request.makeReply(HTTP.PRECONDITION_FAILED);
    	    r.setContent("Pre-conditions failed.");
    	    return r;
    	}
    	if (! fresource.getFile().exists()) {
    	    // Delete the resource if parent is extensible:
    	    boolean extensible = false;
    	    ResourceReference rr = fresource.getParent();
    	    ResourceReference rrtemp = null;
    	    Resource p = null;
    	    while ( true ) {
    		try {
    		    if (rr == null)
    			break;
    		    p = rr.lock();
    		    if (p instanceof DirectoryResource)
    			extensible =
    			    ((DirectoryResource)p).getExtensibleFlag();
    		    rrtemp = p.getParent();
    		} catch (InvalidResourceException ex) {
    		    break;
    		} finally {
    		    if (rr != null)
    			rr.unlock();
    		}
    		rr = rrtemp;
    	    }
    	    if (extensible) {
    		// The resource is indexed but has no file, emit an error
    		String msg = fresource.getFile()+
    		    ": deleted, removing the FileResource.";
    		getServer().errlog(fresource, msg);
    		try {
    		    fresource.delete();
    		} catch (MultipleLockException ex) {
    		    Reply error = request.makeReply(HTTP.NOT_FOUND) ;
    		    error.setContent ("<h1>Document not found</h1>"+
    				      "<p>The document "+
    				      request.getURL()+
    				      " is indexed but not available."+
    				      "<p>"+ex.getMessage()+
    				      "<p>The server is misconfigured.") ;
    		    throw new HTTPException (error) ;
    		}
    	    }
    	    // Emit an error back:
    	    Reply error = request.makeReply(HTTP.NOT_FOUND) ;
    	    error.setContent ("<h1>Document not found</h1>"+
    			      "<p>The document "+
    			      request.getURL()+
    			      " is indexed but not available."+
    			      "<p>The server is misconfigured.") ;
    	    throw new HTTPException (error) ;
    	}
    	return createDefaultReply(request, HTTP.OK);
        }

        /**
         * The default POST method replies with a not implemented.
         * @param request The request to handle.
         * @exception ProtocolException Always thrown, to return a NOT_IMPLEMENTED
         *    error.
         * @exception NotAProtocolException If the client instance controling the
         * request processing got a fatal error.
         */

        public Reply post(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	Reply error = request.makeReply(HTTP.NOT_ALLOWED) ;
    	if ( allowed != null )
    	    error.setHeaderValue(Reply.H_ALLOW, allowed);
    	error.setContent("Method POST not allowed on this resource.") ;
    	throw new HTTPException (error) ;
        }

        /**
         * The default PUT method replies with a not implemented.
         * @param request The request to handle.
         * @exception ProtocolException Always thrown, to return a NOT_IMPLEMENTED
         *    error.
         * @exception NotAProtocolException If the client instance controling the
         * request processing got a fatal error.
         */

        public Reply put(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	if (fresource != null) {
    	    return putFileResource(request);
    	} else {
    	    return putOtherResource(request);
    	}
        }

        protected Reply putOtherResource(Request request)
    	throws ProtocolException
        {
    	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
    	error.setContent("Method PUT not implemented.") ;
    	throw new HTTPException (error) ;
        }

        protected Reply putFileResource(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	Reply reply = null;
    	int status = HTTP.OK;
    	fresource.checkContent();
    	updateCachedHeaders();
    	// Is this resource writable ?
    	if ( ! getPutableFlag() ) {
    	    Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
    	    error.setContent("Method PUT not implemented.") ;
    	    throw new HTTPException (error) ;
    	}
    	// Check validators:
    	if ((checkIfMatch(request) == COND_FAILED)
    	    || (checkIfNoneMatch(request) == COND_FAILED)
    	    || (checkIfModifiedSince(request) == COND_FAILED)
    	    || (checkIfUnmodifiedSince(request) == COND_FAILED)) {
    	    Reply r = request.makeReply(HTTP.PRECONDITION_FAILED);
    	    r.setContent("Pre-condition failed.");
    	    return r;
    	}
    	// Check the request:
    	InputStream in = null;
    	try {
    	    in = request.getInputStream();
    	    if ( in == null ) {
    		Reply error = request.makeReply(HTTP.BAD_REQUEST) ;
    		error.setContent ("<p>Request doesn't have a valid content.");
    		throw new HTTPException (error) ;
    	    }
    	} catch (IOException ex) {
    	    throw new ClientException(request.getClient(), ex);
    	}
    	// We do not support (for the time being) put with ranges:
    	if ( request.hasContentRange() ) {
    	    Reply error = request.makeReply(HTTP.BAD_REQUEST);
    	    error.setContent("partial PUT not supported.");
    	    throw new HTTPException(error);
    	}
    	// Check that if some type is provided it doesn't conflict:
    	if ( request.hasContentType() ) {
    	    MimeType rtype = request.getContentType() ;
    	    MimeType type  = getContentType() ;
    	    if ( type == null ) {
    		setValue (ATTR_CONTENT_TYPE, rtype) ;
    	    } else if ( rtype.match (type) < 0 ) {
    		Reply error = request.makeReply(HTTP.UNSUPPORTED_MEDIA_TYPE) ;
    		error.setContent ("<p>Invalid content type: "+type.toString());
    		throw new HTTPException (error) ;
    	    }
    	}
    	// Write the body back to the file:
    	try {
    	    // We are about to accept the put, notify client before continuing
    	    Client client = request.getClient();
    	    if ( client != null  && request.getExpect() != null ) {
    		client.sendContinue();
    	    }
    	    if ( fresource.newContent(request.getInputStream()) )
    		status = HTTP.CREATED;
    	    else
    		status = HTTP.NO_CONTENT;
    	} catch (IOException ex) {
    	    throw new ClientException(request.getClient(), ex);
    	}
    	if ( status == HTTP.CREATED ) {
    	    reply = request.makeReply(status);
    	    reply.setLocation(getURL(request));
    	    reply.setContent ("<p>Entity body saved succesfully !") ;
    	} else {
    	    reply = createDefaultReply(request, status);
    	}
    	return reply ;
        }

        /**
         * The default OPTIONS method replies with a not implemented.
         * @param request The request to handle.
         * @exception ProtocolException In case of errors.
         * @exception NotAProtocolException If the client instance controling the
         * request processing got a fatal error.
         */

        public Reply options(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	Reply reply = createDefaultReply(request, HTTP.OK);
    	// Removed unused headers:
    	reply.setContentLength(-1);
    	reply.setContentType(null);
    	// Add the allow header:
    	if ( allowed != null )
    	    reply.setHeaderValue(Reply.H_ALLOW, allowed);
    	return reply;
        }

        /**
         * The default DELETE method replies with a not implemented.
         * @param request The request to handle.
         * @exception ProtocolException Always thrown, to return a NOT_IMPLEMENTED
         *    error.
         * @exception NotAProtocolException If the client instance controling the
         * request processing got a fatal error.
         */

        public Reply delete(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
    	error.setContent("Method DELETE not implemented.") ;
    	throw new HTTPException (error) ;
        }

        /**
         * The default LINK method replies with a not implemented.
         * @param request The request to handle.
         * @exception ProtocolException Always thrown, to return a NOT_IMPLEMENTED
         *    error.
         * @exception NotAProtocolException If the client instance controling the
         * request processing got a fatal error.
         */

        public Reply link(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
    	error.setContent("Method LINK not implemented.") ;
    	throw new HTTPException (error) ;
        }

        /**
         * The default UNLINK method replies with a not implemented.
         * @param request The request to handle.
         * @exception ProtocolException Always thrown, to return a NOT_IMPLEMENTED
         *    error.
         * @exception NotAProtocolException If the client instance controling the
         * request processing got a fatal error.
         */

        public Reply unlink(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
    	error.setContent("Method UNLINK not implemented.") ;
    	throw new HTTPException (error) ;
        }

        /**
         * The default TRACE method replies with a not implemented
         * @param request The request to handle.
         * @exception HTTPException In case of errors.
         * @exception ClientException If the client instance controling the
         * request processing got a fatal error.
         */

        public Reply trace(Request request)
            throws HTTPException, ClientException
        {
            Reply reply = createDefaultReply(request, HTTP.OK);
    	reply.setNoCache(); // don't cache this
            reply.setMaxAge(-1); //
            // Dump the request as the body
            // Removed unused headers:
    	// FIXME should be something else for chuncked stream
    	ByteArrayOutputStream ba = new ByteArrayOutputStream();
    	try {
    	    reply.setContentType(new MimeType("message/http"));
    	    request.dump(ba);
    	    reply.setContentLength(ba.size());
    	} catch (Exception ex) {
    	    ex.printStackTrace();
    	}
    	reply.setStream(new ByteArrayInputStream(ba.toByteArray()));
    	return reply;
        }

        /**
         * The handler for unknown method replies with a not implemented.
         * @param request The request to handle.
         * @exception ProtocolException Always thrown, to return a NOT_IMPLEMENTED
         *    error.
         * @exception NotAProtocolException If the client instance controling the
         * request processing got a fatal error.
         */

        public Reply extended(Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	String method = request.getMethod() ;
    	if ((method != null) && method.equals("BROWSE") && getBrowsableFlag())
    	    return browse(request) ;

    	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
    	error.setContent("Method "+method+" not implemented.") ;
    	throw new HTTPException (error) ;
        }

        /**
         * Handle the browse method.
         * @param request The request to handle.
         */

        protected static MimeType  browsetype = null;

        protected synchronized MimeType getBrowseType() {
    	if ( browsetype == null ) {
    	    try {
    		browsetype = new MimeType("application/x-navibrowse");
    	    } catch (Exception ex) {
    		ex.printStackTrace();
    	    }
    	}
    	return browsetype;
        }

        /**
         * A present to GNNPress users !
         * This method implements the <code>BROWSE</code> method that
         * AOL press (or GNN press, or whatever its last name is) expects.
         * @param request The request to process.
         * @exception ProtocolException If some error occurs.
         * @return A Reply instance.
         */
        public Reply browse (Request request)
    	throws ProtocolException
        {
    	if (dresource == null) {
    	    Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
    	    error.setContent("Method "+request.getMethod()+
    			     " not implemented.") ;
    	    throw new HTTPException (error) ;
    	}

    	Enumeration  enum      = dresource.enumerateResourceIdentifiers() ;
    	Vector       resources = Sorter.sortStringEnumeration(enum) ;
    	int          rsize     = ((resources == null) ? 0 : resources.size()) ;
    	StringBuffer sb        = new StringBuffer() ;

    	// As we have enumerated all resources, just looking the store is ok
    	for (int i = 0 ; i < rsize ; i++) {
    	    String            rname = (String) resources.elementAt(i) ;
    	    ResourceReference rr    = null;
    	    Resource          r     = null;
    	    try {
    		rr = dresource.lookup(rname) ;
    		r = rr.lock();
    		// may throw InvalidResourceException

    		if ( r instanceof DirectoryResource ) {
    		    sb.append("application/x-navidir "+
    			      rname+
    			      "\r\n") ;
    		} else {
    		    HTTPFrame itsframe =
    			(HTTPFrame) resource.getFrame(getClass());
    		    if (itsframe != null) {
    			sb.append(itsframe.getContentType().toString()+
    				  " "+
    				  rname+
    				  "\r\n") ;
    		    }
    		}
    	    } catch (InvalidResourceException ex) {
    		continue;
    	    } finally {
    		rr.unlock();
    	    }
    	}
    	Reply reply = request.makeReply(HTTP.OK) ;
    	reply.addPragma("no-cache");
    	reply.setNoCache();
    	reply.setContent(sb.toString()) ;
    	reply.setContentType(getBrowseType());
    	return reply ;
        }

        //
        // Filtered part
        //

        /**
         * Get our whole list of filters.
         */

        public synchronized ResourceFilter[] getFilters() {
    	ResourceFrame frames[] = collectFrames(filterClass);
    	if ( frames != null ) {
    	    // FIXME Normally a simple cast should suffice (?)
    	    ResourceFilter f[] = new ResourceFilter[frames.length];
    	    for (int i = 0 ; i < frames.length ; i++)
    		f[i] = (ResourceFilter) frames[i];
    	    return f;
    	}
    	return null;
        }

        /**
         * Get the list of filters of this class.
         * @param cls The class of filters requested.
         * @return An array of filters, which are instances of the given class.
         */

        public synchronized ResourceFilter[] getFilters(Class cls) {
    	ResourceFrame frames[] = collectFrames(cls);
    	if ( frames != null ) {
    	    // FIXME Normally a simple cast should suffice (?)
    	    ResourceFilter f[] = new ResourceFilter[frames.length];
    	    for (int i = 0 ; i < frames.length ; i++)
    		f[i] = (ResourceFilter) frames[i];
    	    return f;
    	}
    	return null;
        }

    }