// PostableFrame.java
// $Id: PostableFrame.java,v 1.7 2002/07/03 16:04:03 ylafon 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.IOException;
import java.io.InputStream;
import java.io.StringBufferInputStream;
import java.util.Enumeration;
import org.w3c.tools.resources.Attribute;
import org.w3c.tools.resources.AttributeRegistry;
import org.w3c.tools.resources.BooleanAttribute;
import org.w3c.tools.resources.ProtocolException;
import org.w3c.tools.resources.ResourceException;
import org.w3c.jigsaw.forms.URLDecoder;
import org.w3c.jigsaw.forms.URLDecoderException;
import org.w3c.www.mime.MimeType;
import org.w3c.www.http.HTTP;
import org.w3c.www.http.HttpFactory;
import org.w3c.www.http.HttpTokenList;
import org.w3c.jigsaw.http.Client;
import org.w3c.jigsaw.http.ClientException;
import org.w3c.jigsaw.http.HTTPException;
import org.w3c.jigsaw.http.Reply;
import org.w3c.jigsaw.http.Request;

import org.w3c.jigsaw.html.HtmlGenerator ;

/**
 * Handle POST.
 */
public class PostableFrame extends HTTPFrame {

    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("org.w3c.jigsaw.frames.PostableFrame") ;
	} 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 the Allowed methods for this resource
     * @return an HttpTokenList
     */
    protected HttpTokenList getAllow() {
	if (allowed != null) {
	    return allowed;
	}
	int size = 5; // the default HEAD GET OPTIONS POST TRACE
	if (getPutableFlag()) {
	    size++;
	}
	if (getAllowDeleteFlag()) {
	    size++;
	}
	String allow_str[] = new String[size];
	int i = 0;
	if (getAllowDeleteFlag()) {
	    allow_str[i++] = "DELETE";
	}
	allow_str[i++] = "HEAD";
	allow_str[i++] = "GET";
	allow_str[i++] = "OPTIONS";
	allow_str[i++] = "POST";
	if (getPutableFlag()) {
	    allow_str[i++] = "PUT";
	}
	allow_str[i] = "TRACE";
	allowed = HttpFactory.makeStringList(allow_str);
	return allowed;
    }

    /**
     * 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 ProtocolException If request couldn't be processed.
     * @exception ResourceException If the resource got a fatal error.
     */
    public Reply get (Request request) 
	throws ProtocolException, ResourceException
    {
	// 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) ;
    }

    /**
     * Perform the post method.
     * @param request The request to handle.
     * @exception ProtocolException If request couldn't be processed.
     * @exception ResourceException If the resource got a fatal error.
     */
    public Reply post (Request request)
	throws ProtocolException, ResourceException
    {
	boolean mustURLDecode = false;
	// Check that we are dealing with an application/x-www-form-urlencoded:
	if (request.hasContentType()) {
	    mustURLDecode = (type.match(request.getContentType()) ==
					MimeType.MATCH_SPECIFIC_SUBTYPE);
	}
	// Get and decode the request entity:
	URLDecoder dec = null;
	if (mustURLDecode) {
	    try {
		InputStream in = request.getInputStream() ;
		// Notify the client we are willing to continue processing:
		Client client = request.getClient();
		// FIXME check expect
		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 ProtocolException If form data processing failed.
     * @see org.w3c.jigsaw.forms.URLDecoder
     */

    public Reply handle (Request request, URLDecoder data)
	throws ProtocolException 
    {
	// Now we just dump back the variables we got:
	HtmlGenerator g = new HtmlGenerator ("Form decoded values") ;
	if (data != null) {
	    Enumeration   e = data.keys() ;
	    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>") ;
	} else {
	    g.append ("<p>Not with the application/x-www-form-urlencoded"
		      +" mime type");
	}
	Reply reply = request.makeReply(HTTP.OK) ;
	reply.setStream (g) ;
	return reply ;
    }
}
