// PostableFrame.java
// $Id: PostableFrame.java,v 1.4 1998/08/14 11:07:36 bmahe 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.util.*;

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

/**
 * Handle POST.
 */
public class PostableFrame extends HTTPFrame {
    private static HttpTokenList _post_allowed = null; 
    private static HttpTokenList _put_allowed = null; 
    static {
	String post_allowed[] = { "GET", "HEAD", "OPTIONS", "POST", "TRACE" } ;
	_post_allowed = HttpFactory.makeStringList(post_allowed);
	String put_allowed[] = { "GET", "HEAD", "OPTIONS", "PUT",
				 "POST", "TRACE" } ;
	_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("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) ;
    }


    /**
     * 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_PUTABLE) {
	    if (value == Boolean.TRUE)
		allowed = _put_allowed;
	    else 
		allowed = _post_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
    {
	// 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 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:
	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 ;
    }
}
