    // PostableFrame.java
    // $Id: PostableFrame.html,v 1.2 1998/07/09 10:48:24 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.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.
         */
        public Reply get (Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	// 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) ;
        }

        public Reply post (Request request)
    	throws ProtocolException, NotAProtocolException
        {
    	// 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 ;
        }
    }