//
// HTTPWriter.java
//
// Portions of this file are subject to the SNIA Public License Version 1.0
// (the "License"); you may not use this file except in compliance with the
// License. You may obtain a copy of the License at
//
//      http://www.snia.org/resources/openSource.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is HTTPWriter.java
// 
// The Initial Developer of the Original Code is David Simons.
//
// Contributor(s): Jef Poskanzer (see separate license below) 
//                 Bapu Patil
//
// Portions (c)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
//
// This file is based on Serve.java from Acme labs - Please see further license
// restrictions below.
//
//
// Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.com/java/
//
//
//
// This source uses the Solaris WBEM APIs version 2.2. 
// Synchronization will be maintained with the Solaris WBEM APIS,
// which are being standardized through the Java Community Process.
//
//
//
// $Id: HTTPWriter.java,v 1.1 2003/07/17 18:32:55 kumpf Exp $
//
// $Log: HTTPWriter.java,v $
// Revision 1.1  2003/07/17 18:32:55  kumpf
// HP-BAPU: PEP-64 CIM Client and CIM Listener Java Interface
//
// Revision 1.1.1.1  2001/10/31 18:17:20  karl
// import of SNIA 0_7
//
// Revision 1.14  2001/06/23 12:23:43  davidsi
// - Added support for running the CIMOM as a servlet within a webserver.
//
// Revision 1.12  2001/03/19 17:39:19  davidsi
// - Fixed possibility of using connections that have become null.
//
// Revision 1.11  2001/01/07 16:53:30  davidsi
// - Fixed handling of options list which was not getting cleared after
//   requests were sent.
//
// Revision 1.10  2000/11/28 19:26:09  davidsi
// - Fix POST request headers.
//
// Revision 1.9  2000/11/06 19:07:31  davidsi
// - Support for factoring functionality into pluggable modules.
//
// Revision 1.8  2000/08/16 05:59:59  shawn
// merge P_TOMCAT31 branch
//
// Revision 1.7.6.1  2000/08/10 00:03:36  shawn
// head merge
//
// Revision 1.7  2000/07/10 18:45:04  davidsi
// - Removed client side support - this is now a server only class.
//
// Revision 1.6  2000/05/11 10:11:23  davidsi
// - Fixed tabs.
//
// Revision 1.5  2000/05/06 17:20:02  davidsi
// - Brought in line with SUN WBEM APIs 2.2
//
// Revision 1.4  2000/04/09 14:32:17  davidsi
// - Removded utf-8 declaration from XML header - some implementations
//   can't work with this
// - Support for HTTP 1.1 and 1.0. Unified debug messages with text that
//   is sent over the wire.
//
// Revision 1.3  2000/02/13 15:10:04  davidsi
// - Added text to cover re-use of Solaris WBEM APIs.
//
// Revision 1.2  2000/02/06 18:01:08  davidsi
// - BUG FIX. Temporary directory now created if it does not already
//   exist.
//
// Revision 1.1.1.1  2000/02/06 16:41:02  davidsi
// Initial version.
//
//
//

package org.snia.wbemcmd.xml;

import java.io.*;
import java.util.*;
import java.net.*;
import org.snia.wbemcfg.GlobalConfig;
import org.snia.wbemcmd.cimom.CIMOMHandleXML;
import org.snia.wbemcmd.utils.Log;

/*
 * Server side output for http
 */
class HTTPWriter  {
    static public int CIM_HTTP_OP_METHODCALL=1;
    static public int CIM_HTTP_OP_METHODRESPONSE=2;
    static public int CIM_HTTP_OP_OPTIONSCALL=3;
    static public String CIMProtocolVersion="1.0";
    static public String XML_CIM_HEADER1="<?xml version=\"1.0\" ?>";
    static public String url = "/cimom"; // Server url

    //
    // Some WBEM implementations don't appreciate being told that the
    // character encoding is utf-8, so omit it...
    //
    //  static private String XML_CIM_HEADER1="<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
    static public String XML_CIM_HEADER2="<CIM CIMVERSION=\"2.0\" DTDVERSION=\"2.0\">";

    
     // Status codes.
    public static final int SC_CONTINUE = 100;
    public static final int SC_SWITCHING_PROTOCOLS = 101;
    public static final int SC_OK = 200;
    public static final int SC_CREATED = 201;
    public static final int SC_ACCEPTED = 202;
    public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203;
    public static final int SC_NO_CONTENT = 204;
    public static final int SC_RESET_CONTENT = 205;
    public static final int SC_PARTIAL_CONTENT = 206;
    public static final int SC_MULTIPLE_CHOICES = 300;
    public static final int SC_MOVED_PERMANENTLY = 301;
    public static final int SC_MOVED_TEMPORARILY = 302;
    public static final int SC_SEE_OTHER = 303;
    public static final int SC_NOT_MODIFIED = 304;
    public static final int SC_USE_PROXY = 305;
    public static final int SC_BAD_REQUEST = 400;
    public static final int SC_UNAUTHORIZED = 401;
    public static final int SC_PAYMENT_REQUIRED = 402;
    public static final int SC_FORBIDDEN = 403;
    public static final int SC_NOT_FOUND = 404;
    public static final int SC_METHOD_NOT_ALLOWED = 405;
    public static final int SC_NOT_ACCEPTABLE = 406;
    public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
    public static final int SC_REQUEST_TIMEOUT = 408;
    public static final int SC_CONFLICT = 409;
    public static final int SC_GONE = 410;
    public static final int SC_LENGTH_REQUIRED = 411;
    public static final int SC_PRECONDITION_FAILED = 412;
    public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413;
    public static final int SC_REQUEST_URI_TOO_LONG = 414;
    public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
    public static final int SC_INTERNAL_SERVER_ERROR = 500;
    public static final int SC_NOT_IMPLEMENTED = 501;
    public static final int SC_BAD_GATEWAY = 502;
    public static final int SC_SERVICE_UNAVAILABLE = 503;
    public static final int SC_GATEWAY_TIMEOUT = 504;
    public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505;

    // Attn: 08/7/2002 ( Bapu )
    // As per the spec header-prefix is a dynamically generated string
    // and should only be 2 DIGITS
    //
    public static HTTPCounter globalHeaderNumber=new HTTPCounter(99);
    
    BufferedOutputStream pw;
    Vector linesToOutput=new Vector();
    Vector optionsList=new Vector();
    int size=0;
    InetAddress localHost;
    String hostName;
    String methodName=null;
    String objectPath=null;
    String auth = null;
    boolean useHTTP11ForResponse=true;
    int operationType=CIM_HTTP_OP_METHODRESPONSE;
    private WBEMResponse wbemResponse = null;
    

    // XML RMI is disabled by default
    boolean xmlRMIEnabled = false;


    HTTPWriter(OutputStream os,String rname) {
        pw=new BufferedOutputStream(os,8192);
                            
    }

    /**
     * This is the constructor for initializing <code>HTTPWriter</code>
     * for outputing to a stream for use by <code>XMLRMIInput</code>.
     *
     * @param os The output stream to write to.
     * @param wbemResponse Response object to be returned to the RMI client.
     */
    HTTPWriter(OutputStream os,WBEMResponse xmlRMIResponse) {
        pw=new BufferedOutputStream(os,8192);
        wbemResponse = xmlRMIResponse;              
    }

    HTTPWriter(OutputStream os,int mode,String rname) {
        if (mode!=CIM_HTTP_OP_METHODCALL && 
            mode!=CIM_HTTP_OP_METHODRESPONSE &&
            mode!=CIM_HTTP_OP_OPTIONSCALL)
            throw new ExceptionInInitializerError("Bad mode for operation");
        pw=new BufferedOutputStream(os,8192);
        this.hostName=rname;
        this.operationType=mode;
    }



    public void setMethodName(String name) {
        methodName=name;
    }

    public void setObjectPath(String name) {
        objectPath=name;
    }

    public void setUseHTTP11ForResponse(boolean flag) {
        useHTTP11ForResponse=flag;
    }
    

    public void setXMLRMIEnabled(boolean flag) {
        xmlRMIEnabled=flag;
    }

    public void close() {
        try {
          if (pw!=null) {
            pw.close();
          }
        } catch (Exception e) {}
    }
    

    public void makeHeader(String id) {
        println(XML_CIM_HEADER1);
        println(XML_CIM_HEADER2);
        println("<MESSAGE ID=\""+id+"\" PROTOCOLVERSION=\"1.0\">");
    }

    /**
     * Construct a response to the HTTP OPTIONS request
     *
     * @param code the standard HTTP error
     */
    static String status2String( int code ) {
        switch ( code )   {
            case SC_CONTINUE: return( "Continue" );
            case SC_SWITCHING_PROTOCOLS: return(  "Switching protocols" ); 
            case SC_OK: return( "Ok" ); 
            case SC_CREATED: return( "Created" ); 
            case SC_ACCEPTED: return( "Accepted" ); 
            case SC_NON_AUTHORITATIVE_INFORMATION: return( "Non-authoritative" ); 
            case SC_NO_CONTENT: return( "No content" ); 
            case SC_RESET_CONTENT: return( "Reset content" ); 
            case SC_PARTIAL_CONTENT:return( "Partial content" ); 
            case SC_MULTIPLE_CHOICES:return( "Multiple choices" ); 
            case SC_MOVED_PERMANENTLY:return( "Moved permanentently" ); 
            case SC_MOVED_TEMPORARILY:return( "Moved temporarily" ); 
            case SC_SEE_OTHER: return( "See other" ); 
            case SC_NOT_MODIFIED: return( "Not modified" ); 
            case SC_USE_PROXY: return( "Use proxy" ); 
            case SC_BAD_REQUEST: return( "Bad request" ); 
            case SC_UNAUTHORIZED: return( "Unauthorized" );
            case SC_PAYMENT_REQUIRED:  return( "Payment required" );
            case SC_FORBIDDEN: return( "Forbidden" );
            case SC_NOT_FOUND: return( "Not found" );
            case SC_METHOD_NOT_ALLOWED:return( "Method not allowed" );
            case SC_NOT_ACCEPTABLE:return( "Not acceptable" );
            case SC_PROXY_AUTHENTICATION_REQUIRED:return( "Proxy auth required" );
            case SC_REQUEST_TIMEOUT:return( "Request timeout" );
            case SC_CONFLICT: return( "Conflict" );
            case SC_GONE: return( "Gone" );
            case SC_LENGTH_REQUIRED:return( "Length required" );
            case SC_PRECONDITION_FAILED:return( "Precondition failed" );
            case SC_REQUEST_ENTITY_TOO_LARGE:return( "Request entity too large" );
            case SC_REQUEST_URI_TOO_LONG:return( "Request URI too large" );
            case SC_UNSUPPORTED_MEDIA_TYPE:return( "Unsupported media type" );
            case SC_INTERNAL_SERVER_ERROR:return( "Internal server error" );
            case SC_NOT_IMPLEMENTED: return( "Not implemented" ); 
            case SC_BAD_GATEWAY: return( "Bad gateway" );
            case SC_SERVICE_UNAVAILABLE: return( "Service unavailable" ); 
            case SC_GATEWAY_TIMEOUT: return( "Gateway timeout" ); 
            case SC_HTTP_VERSION_NOT_SUPPORTED:return( "HTTP version not supported" ); 
            default: return( "" );
        }
    }

    
    private int getSize() {
        return(size);
    }
    

    
    
    static byte[] lineterm={13,10};
    
    private void writeStringln(String str) {
        try {
            pw.write(str.getBytes());
            pw.write(lineterm);
            if (GlobalConfig.DEBUG_XML)
                Log.printlnTransaction("OUT:"+str);
        } catch (Exception e) {
            if (GlobalConfig.DEBUG_XML)
                Log.println("HTTPWriter: Write string failed: " + e.toString());
        }
    }   
    
    private void writeStringln() {
        try {
            pw.write(lineterm);
            if (GlobalConfig.DEBUG_XML)
                Log.printlnTransaction("OUT:");
        } catch (Exception e) {
            if (GlobalConfig.DEBUG_XML)
                Log.println("HTTPWriter: Write string failed: " + e.toString());
        }
    }
    
    
    //
    // addOption
    //
    // Adds a supported CIM option to those that will be reported
    // by the server
    //  
    public void addOption(String option) {
        optionsList.addElement(option);
    }
    
    public void println(String str) {
        linesToOutput.addElement(str);
        size += str.length()+2;
    }
        
    public String getOutput() {
        StringBuffer buf=new StringBuffer();
        for(int i=0;i<linesToOutput.size();i++) {
            buf.append((String)(linesToOutput.elementAt(i)));
        }
        linesToOutput.removeAllElements();
        size=0;
        return(buf.toString());
    }

  //
  // This simply calls flush(boolean)
  //
  public void flush() throws HTTPException 
  {
    flush(false);
  }
  
  public void flush(boolean tryHTTP10) throws HTTPException 
  {
    int headerNumber1;
    int headerNumber2;

    //
    // Bump header number
    //
    int val=globalHeaderNumber.getCounter();
    headerNumber1=val%10;
    headerNumber2=val/10;       
    String headerNumber=headerNumber2+""+headerNumber1;
        
    if (GlobalConfig.DEBUG_XML) 
    {
      Log.startTransaction();
    }
    /*
      Either XML RMI or HTTP input but not both can be enabled in
      CIMServer at the same time. If XML RMI is enabled, the corresponding
      output and headers are set in the WBEMResponse object. For the HTTP
      input case the output and headers are written to the HTTP socket.
    */
    if (operationType==CIM_HTTP_OP_METHODCALL) 
    {
      if (methodName==null) 
      {
        purge();
        throw new HTTPException(
                                "Operation request requires MethodName");
      }
      if (objectPath==null)       
      {
        purge();
        throw new HTTPException(
                                "Operation request requires Object Path");
      }

      //
      // changed from GlobalConfig.useHTTP11 to tryHTTP10 flag
      //
      if ( tryHTTP10 != true ) 
      {
        writeStringln("M-POST "+ url +" HTTP/1.1");
        writeStringln("HOST: "+hostName);
        writeStringln("Content-type: application/xml; charset=\"utf-8\"");
        writeStringln("Content-length: "+getSize());
	if (auth != null)
	     writeStringln("Authorization: Basic " + auth);
        writeStringln("Man: http://www.dmtf.org/cim/mapping/http/v1.0;ns="+headerNumber);
        writeStringln(headerNumber+"-CIMOperation: MethodCall");
        writeStringln(headerNumber+"-CIMProtocolVersion: "+CIMProtocolVersion);
        writeStringln(headerNumber+"-CIMMethod: "+methodName);
        methodName=null;                //
	//
        // TODO -needs encoding!
        //
        writeStringln(headerNumber+"-CIMObject: "+objectPath);
      } 
      else 
      {
        //
        // Write a 1.0 HTTP request
        //
        writeStringln("POST "+ url +" HTTP/1.0");
        writeStringln("HOST: "+hostName);
        writeStringln("Content-type: application/xml; charset=\"utf-8\"");
        writeStringln("Content-length: "+getSize());

        // Added by Bapu ( 07/22/02) 
        // for persistent connections
        writeStringln("Connection: keep-alive");

	if (auth != null)
	     writeStringln("Authorization: Basic " + auth);
        writeStringln("CIMOperation: MethodCall");
        writeStringln("CIMProtocolVersion: "+CIMProtocolVersion);
        writeStringln("CIMMethod: "+methodName);
        writeStringln("CIMObject: "+objectPath);
        methodName=null;
      }
    } 
    else 
    {
      if( xmlRMIEnabled == true )
        {
          // Set WBEMResponse for XML RMI
          wbemResponse.setResCode( SC_OK );
          wbemResponse.setResMessage( status2String( SC_OK ) );
          wbemResponse.setCIMOperation( "MethodResponse" );
          wbemResponse.setCIMProtocolVersion( CIMProtocolVersion );
          int contentLength = getSize();
          wbemResponse.setContentLength( contentLength );
        } else if (useHTTP11ForResponse) {
          // Write HTTP headers
          writeStringln("HTTP/1.1 200 OK");
          writeStringln("Content-type: application/xml; charset=\"utf-8\"");
          if (GlobalConfig.useContentLength)
	     writeStringln("Content-length: "+getSize());
          writeStringln("Ext:");
          if (GlobalConfig.disconnectAfterTransfer || !GlobalConfig.useContentLength )
            writeStringln("Connection: close");
          writeStringln("Cache-Control: no-cache");
          if (optionsList.size()>0)
            writeStringln("Opt: http://www.dmtf.org/cim/mapping/http/v1.0;ns="+headerNumber);
          else
            writeStringln("Man: http://www.dmtf.org/cim/mapping/http/v1.0;ns="+headerNumber);
          writeStringln(headerNumber+"-CIMOperation: MethodResponse");
          writeStringln(headerNumber+"-CIMProtocolVersion: "+CIMProtocolVersion);
          for(int i=0;i<optionsList.size();i++) {
            writeStringln(headerNumber+"-"+optionsList.elementAt(i));
          }
          optionsList.removeAllElements();
        } else {
          writeStringln("HTTP/1.0 200 OK");
          writeStringln("Content-type: application/xml; charset=\"utf8\"");
          if (GlobalConfig.useContentLength)
            writeStringln("Content-length: "+getSize());
          writeStringln("Ext:");
          if (GlobalConfig.disconnectAfterTransfer|| !GlobalConfig.useContentLength )
            writeStringln("Connection: close");
          writeStringln("Cache-Control: no-cache");
          writeStringln("CIMOperation: MethodResponse");
          writeStringln("CIMProtocolVersion: "+CIMProtocolVersion);
          for(int i=0;i<optionsList.size();i++) {
            writeStringln((String)(optionsList.elementAt(i)));
          }
          optionsList.removeAllElements();
        }
    }
    // Don't write a blank line out for XMLRMI input.
    if( xmlRMIEnabled == false )
      writeStringln();
    for(int i=0;i<linesToOutput.size();i++) {
      writeStringln((String)(linesToOutput.elementAt(i)));
    }
          
    linesToOutput.removeAllElements();
          
    try 
    {
      pw.flush();
    } 
    catch (java.net.SocketTimeoutException se) 
    {
        if (GlobalConfig.DEBUG_XML)
            Log.println("HTTPWriter: read time out- " + se.toString());

        throw new HTTPException(se.toString(), HTTPWriter.SC_REQUEST_TIMEOUT);
    }
    catch (Exception e) 
    {
        if (GlobalConfig.DEBUG_XML)
            Log.println("HTTPWriter: Flush failed, Internal server error - " 
                + e.toString());

        throw new HTTPException(e.toString(), 
				HTTPWriter.SC_INTERNAL_SERVER_ERROR);
    }
    size=0;
  }
    
    //
    // Removes all buffered output without writing to the
    // output channel
    //
    public void purge() 
    {
        size=0;
        linesToOutput.removeAllElements();
    }

    /**
     *  Writes an error response using the specified status code and a default
     *  message.
     *  @param resCode the status code
     *  @exception IOException if an I/O error has occurred
     */
    public void sendError( int resCode ) throws IOException
    {
        String resMessage=status2String(resCode);
        String reqProtocol;
        
    if (useHTTP11ForResponse)
            reqProtocol="HTTP/1.1";
        else
            reqProtocol="HTTP/1.0";
              
        if( xmlRMIEnabled == true )
          {
            // Set WBEMResponse for XML RMI
            wbemResponse.setResCode( resCode );
            wbemResponse.setResMessage( resMessage );
            int contentLength = getSize();
            wbemResponse.setContentLength( contentLength );
          } 
        else
            {
              writeStringln( reqProtocol + " " + resCode + " " + resMessage );    
              
              for(int i=0;i<optionsList.size();i++) {
                writeStringln((String)(optionsList.elementAt(i)));
              }
              writeStringln();
              writeStringln( "<HTML><HEAD>" );
              writeStringln( "<TITLE>" + resCode + " " + resMessage + "</TITLE>" );
              writeStringln( "</HEAD><BODY BGCOLOR=\"#99cc99\">" );
              writeStringln( "<H2>TEST" + resCode + " " + resMessage + "</H2>" );
              writeStringln( "<HR>" );
              writeStringln( "</BODY></HTML>" );
              
            }      
        pw.flush();
        linesToOutput.removeAllElements();
        size=0;
    }

    public void setAuth(String authToken) {
	auth=authToken;
    }
}
