// PostableResource.java
// $Id: PostableResource.java,v 1.10 1997/02/11 13:16:09 abaird Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.jigsaw.forms ;

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

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

/**
 * This class is the root class for  handling forms inside the server.
 * It will decode the form content, and trigger a call to the 
 * <b>handle</b> method of the instance.
 * <p>It inherits from the <b>FileResource</b> class to be able to send the
 * form text, and extends it with an implementation of the <b>post</b> method.
 * @see w3c.jigsaw.resources.FileResource
 */

public class PostableResource extends FileResource {
    private static HttpTokenList _post_allowed = null; 
    private static HttpTokenList _put_allowed = null; 
    static {
	String post_allowed[] = { "GET", "HEAD", "OPTIONS", "POST" } ;
	_post_allowed = HttpFactory.makeStringList(post_allowed);
	String put_allowed[] = { "GET", "HEAD", "OPTIONS", "PUT", "POST" } ;
	_put_allowed = HttpFactory.makeStringList(put_allowed);
    }

    private static MimeType type = MimeType.APPLICATION_X_WWW_FORM_URLENCODED ;
    /**
     * Attribute index - Should we override form values when multiple ?
     */
    protected static int ATTR_OVERIDE = -1 ;
    /**
     * Attribute index - Should we silently convert GET to POST methods ?
     */
    protected static int ATTR_CONVERT_GET = -1 ;

    static {
	Attribute a   = null ;
	Class     cls = null ;
	try {
	    cls = Class.forName("w3c.jigsaw.forms.PostableResource") ;
	} catch (Exception ex) {
	    ex.printStackTrace() ;
	    System.exit(1) ;
	}
	// The override attribute:
	a = new BooleanAttribute("override"
				 , Boolean.FALSE
				 , Attribute.EDITABLE);
	ATTR_OVERIDE = AttributeRegistry.registerAttribute(cls, a) ;
	// The convert get attribute:
	a = new BooleanAttribute("convert-get"
				 , Boolean.TRUE
				 , Attribute.EDITABLE) ;
	ATTR_CONVERT_GET = AttributeRegistry.registerAttribute(cls, a) ;
    }

    /**
     * Get the 'convert GET to POST' flag.
     */

    public boolean getConvertGetFlag() {
	return getBoolean(ATTR_CONVERT_GET, false) ;
    }

    /**
     * Get the 'override multiple form field value' flag.
     */

    public boolean getOverrideFlag() {
	return getBoolean(ATTR_OVERIDE, true) ;
    }

    /**
     * Get this resource body.
     * If we are allowed to convert GET requests to POST, than we first
     * check to see if there is some search string in the request, and continue
     * with normal POST request processing.
     * <p>If there is no search string, or if we are not allowed to convert
     * GETs to POSTs, than we just invoke our <code>super</code> method,
     * which will perform the appropriate job.
     * @param request The request to handle.
     * @exception HTTPException If request couldn't be processed.
     */
     
    public Reply get (Request request) 
	throws HTTPException
    {
	// Check if we should handle it (is it a POST disguised in GET ?)
	if ((! getConvertGetFlag()) || ( ! request.hasState("query")))
	    return super.get (request) ;
	// Get the request entity, and decode it:
	String      query = request.getQueryString() ;
	InputStream in    = new StringBufferInputStream(query) ;
	URLDecoder  d     = new URLDecoder (in, getOverrideFlag()) ;
	try {
	    d.parse () ;
	} catch (URLDecoderException e) {
	    Reply error = request.makeReply(HTTP.BAD_REQUEST) ;
	    error.setContent("Invalid request:unable to decode form data.");
	    throw new HTTPException (error) ;
	} catch (IOException e) {
	    Reply error = request.makeReply(HTTP.BAD_REQUEST) ;
	    error.setContent("Invalid request: unable to read form data.");
	    throw new HTTPException (error) ;
	}
	return handle (request, d) ;
    }

    /**
     * The POST method handling.
     * This method decode the posted data, and triggers a call to the
     * <strong>handle</strong> method.
     * @param request The request to handle.
     * @exception HTTPException If request processing failed.
     */

    public Reply post (Request request) 
	throws HTTPException, ClientException
    {
	// Check that we are dealing with an application/x-www-form-urlencoded:
	if ((! request.hasContentType())
	     || (type.match(request.getContentType()) < 0) ) {
	    Reply error = request.makeReply(HTTP.UNSUPPORTED_MEDIA_TYPE) ;
	    error.setContent("Invalid request content type.");
	    throw new HTTPException (error) ;
	}
	// Get and decode the request entity:
	URLDecoder dec = null;
	try {
	    InputStream in = request.getInputStream() ;
	    // Notify the client that we are willing to continue processing:
	    Client client = request.getClient();
	    if ( client != null ) 
		client.sendContinue();
	    dec = new URLDecoder (in, getOverrideFlag()) ;
	    dec.parse () ;
	} catch (URLDecoderException e) {
	    Reply error = request.makeReply(HTTP.BAD_REQUEST) ;
	    error.setContent("Invalid request: unable to decode form data.") ;
	    throw new HTTPException (error) ;
	} catch (IOException ex) {
	    Reply error = request.makeReply(HTTP.BAD_REQUEST) ;
	    error.setContent("Invalid request: unable to read form data.") ;
	    throw new ClientException(request.getClient(), ex) ;
	}
	// Handle the stuff:
	return handle (request, dec) ;
    }

    /**
     * Handle the form submission, after posted data parsing.
     * <p>This method ought to be abstract, but for reasonable reason, it
     * will just dump (parsed) the form content back to the client, so that it
     * can be used for debugging.
     * @param request The request proper.
     * @param data The parsed data content.
     * @exception HTTPException If form data processing failed.
     * @see w3c.jigsaw.forms.URLDecoder
     */

    public Reply handle (Request request, URLDecoder data)
	throws HTTPException 
    {
	// Now we just dump back the variables we got:
	Enumeration   e = data.keys() ;
	HtmlGenerator g = new HtmlGenerator ("Form decoded values") ;
	g.append ("<p>List of variables and values:</p><ul>") ;
	while ( e.hasMoreElements () ) {
	    String name = (String) e.nextElement() ;
	    g.append ("<li><em>"
		      + name+"</em> = <b>"
		      + data.getValue(name)
		      + "</b></li>");
	}
	g.append ("</ul>") ;
	Reply reply = request.makeReply(HTTP.OK) ;
	reply.setStream (g) ;
	return reply ;
    }

    /**
     * The verify method of a postable resource will always return <strong>
     * false</strong>.
     * Any PostableResource has to be removed manually.
     * @return Always <strong>false</strong>.
     */

    public boolean verify() {
	return false;
    }


    /**
     * Setup the list of allowed methods.
     */

    public void initialize(Object values[]) {
	super.initialize(values);
	// Check for put:
	if ( getPutableFlag() ) 
	    allowed = _put_allowed;
	else
	    allowed = _post_allowed;
    }

}
