package org.cups;

/**
 * @version 1.00 06-NOV-2003
 * @author  Easy Software Products
 *
 *   Internet Printing Protocol definitions for the Common UNIX Printing
 *   System (CUPS).
 *
 *   Copyright 1997-2003 by Easy Software Products.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Easy Software Products and are protected by Federal
 *   copyright law.  Distribution and use rights are outlined in the file
 *   "LICENSE.txt" which should have been included with this file.  If this
 *   file is missing or damaged please contact Easy Software Products
 *   at:
 *
 *       Attn: CUPS Licensing Information
 *       Easy Software Products
 *       44141 Airport View Drive, Suite 204
 *       Hollywood, Maryland 20636-3111 USA
 *
 *       Voice: (301) 373-9603
 *       EMail: cups-info@cups.org
 *         WWW: http://www.cups.org
 */

/**
 * An <code>IPPHttp</code> object is used for reading/writing to the cups
 * server, and processing responses.
 *
 * @author	TDB
 * @version	1.0
 * @since	JDK1.3
 */

import java.io.*;
import java.util.*;
import java.net.*;
import java.security.*;

public class IPPHttp
{

  /**
   *  Class constants - most not in use yet.
   */
  public static final int HTTP_WAITING = 0x00;
  public static final int HTTP_OPTIONS = 0x01;
  public static final int HTTP_GET = 0x02;
  public static final int HTTP_GET_SEND = 0x03;
  public static final int HTTP_HEAD = 0x04;
  public static final int HTTP_POST = 0x05;
  public static final int HTTP_POST_RECV = 0x06;
  public static final int HTTP_POST_SEND = 0x07;
  public static final int HTTP_PUT = 0x08;
  public static final int HTTP_PUT_RECV = 0x09;
  public static final int HTTP_DELETE = 0x0A;
  public static final int HTTP_TRACE = 0x0B;
  public static final int HTTP_CLOSE = 0x0C;
  public static final int HTTP_STATUS = 0x0D;

  public static final int HTTP_0_9 = 0x09;
  public static final int HTTP_1_0 = 0x64;
  public static final int HTTP_1_1 = 0x65;

  public static final int HTTP_KEEPALIVE_OFF = 0x00;
  public static final int HTTP_KEEPALIVE_ON = 0x01;

  public static final int HTTP_ENCODE_LENGTH = 0x00;
  public static final int HTTP_ENCODE_CHUNKED = 0x01;

  public static final int HTTP_ENCRYPT_IF_REQUESTED = 0x00;
  public static final int HTTP_ENCRYPT_NEVER = 0x01;
  public static final int HTTP_ENCRYPT_REQUIRED = 0x02;
  public static final int HTTP_ENCRYPT_ALWAYS = 0x03;

  public static final int HTTP_AUTH_NONE = 0x00;
  public static final int HTTP_AUTH_BASIC = 0x01;
  public static final int HTTP_AUTH_MD5 = 0x02;
  public static final int HTTP_AUTH_MD5_SESS = 0x03;
  public static final int HTTP_AUTH_MD5_INT = 0x04;
  public static final int HTTP_AUTH_MD5_SESS_INT = 0x05;

  public static final int HTTP_ERROR = 0xFFFFFFFF;
  public static final int HTTP_CONTINUE = 0x64;
  public static final int HTTP_SWITCHING_PROTOCOLS = 0x65;
  public static final int HTTP_OK = 0xC8;
  public static final int HTTP_CREATED = 0xC9;
  public static final int HTTP_ACCEPTED = 0xCA;
  public static final int HTTP_NOT_AUTHORITATIVE = 0xCB;
  public static final int HTTP_NO_CONTENT = 0xCC;
  public static final int HTTP_RESET_CONTENT = 0xCD;
  public static final int HTTP_PARTIAL_CONTENT = 0xCE;
  public static final int HTTP_MULTIPLE_CHOICES = 0x12C;
  public static final int HTTP_MOVED_PERMANENTLY = 0x12D;
  public static final int HTTP_MOVED_TEMPORARILY = 0x12E;
  public static final int HTTP_SEE_OTHER = 0x12F;
  public static final int HTTP_NOT_MODIFIED = 0x130;
  public static final int HTTP_USE_PROXY = 0x131;
  public static final int HTTP_BAD_REQUEST = 0x190;
  public static final int HTTP_UNAUTHORIZED = 0x191;
  public static final int HTTP_PAYMENT_REQUIRED = 0x192;
  public static final int HTTP_FORBIDDEN = 0x193;
  public static final int HTTP_NOT_FOUND = 0x194;
  public static final int HTTP_METHOD_NOT_ALLOWED = 0x195;
  public static final int HTTP_NOT_ACCEPTABLE = 0x196;
  public static final int HTTP_PROXY_AUTHENTICATION = 0x197;
  public static final int HTTP_REQUEST_TIMEOUT = 0x198;
  public static final int HTTP_CONFLICT = 0x199;
  public static final int HTTP_GONE = 0x19A;
  public static final int HTTP_LENGTH_REQUIRED = 0x19B;
  public static final int HTTP_PRECONDITION = 0x19C;
  public static final int HTTP_REQUEST_TOO_LARGE = 0x19D;
  public static final int HTTP_URI_TOO_LONG = 0x19E;
  public static final int HTTP_UNSUPPORTED_MEDIATYPE = 0x19F;
  public static final int HTTP_UPGRADE_REQUIRED = 0x1AA;
  public static final int HTTP_SERVER_ERROR = 0x1F4;
  public static final int HTTP_NOT_IMPLEMENTED = 0x1F5;
  public static final int HTTP_BAD_GATEWAY = 0x1F6;
  public static final int HTTP_SERVICE_UNAVAILABLE = 0x1F7;
  public static final int HTTP_GATEWAY_TIMEOUT = 0x1F8;

  public static final int HTTP_NOT_SUPPORTED = 0x1F9;

  public static final int HTTP_FIELD_UNKNOWN = 0xFFFFFFFF;
  public static final int HTTP_FIELD_ACCEPT_LANGUAGE = 0x00;
  public static final int HTTP_FIELD_ACCEPT_RANGES = 0x01;
  public static final int HTTP_FIELD_AUTHORIZATION = 0x02;
  public static final int HTTP_FIELD_CONNECTION = 0x03;
  public static final int HTTP_FIELD_CONTENT_ENCODING = 0x04;
  public static final int HTTP_FIELD_CONTENT_LANGUAGE = 0x05;
  public static final int HTTP_FIELD_CONTENT_LENGTH = 0x06;
  public static final int HTTP_FIELD_CONTENT_LOCATION = 0x07;
  public static final int HTTP_FIELD_CONTENT_MD5 = 0x08;
  public static final int HTTP_FIELD_CONTENT_RANGE = 0x09;
  public static final int HTTP_FIELD_CONTENT_TYPE = 0x0A;
  public static final int HTTP_FIELD_CONTENT_VERSION = 0x0B;
  public static final int HTTP_FIELD_DATE = 0x0C;
  public static final int HTTP_FIELD_HOST = 0x0D;
  public static final int HTTP_FIELD_IF_MODIFIED_SINCE = 0x0E;
  public static final int HTTP_FIELD_IF_UNMODIFIED_SINCE = 0x0F;
  public static final int HTTP_FIELD_KEEP_ALIVE = 0x10;
  public static final int HTTP_FIELD_LAST_MODIFIED = 0x11;
  public static final int HTTP_FIELD_LINK = 0x12;
  public static final int HTTP_FIELD_LOCATION = 0x13;
  public static final int HTTP_FIELD_RANGE = 0x14;
  public static final int HTTP_FIELD_REFERER = 0x15;
  public static final int HTTP_FIELD_RETRY_AFTER = 0x16;
  public static final int HTTP_FIELD_TRANSFER_ENCODING = 0x17;
  public static final int HTTP_FIELD_UPGRADE = 0x18;
  public static final int HTTP_FIELD_USER_AGENT = 0x19;
  public static final int HTTP_FIELD_WWW_AUTHENTICATE = 0x1A;
  public static final int HTTP_FIELD_MAX = 0x1B;

  public static final String http_fields[] =
                      {
			  "Accept-Language",
			  "Accept-Ranges",
			  "Authorization",
			  "Connection",
			  "Content-Encoding",
			  "Content-Language",
			  "Content-Length",
			  "Content-Location",
			  "Content-MD5",
			  "Content-Range",
			  "Content-Type",
			  "Content-Version",
			  "Date",
			  "Host",
			  "If-Modified-Since",
			  "If-Unmodified-since",
			  "Keep-Alive",
			  "Last-Modified",
			  "Link",
			  "Location",
			  "Range",
			  "Referer",
			  "Retry-After",
			  "Transfer-Encoding",
			  "Upgrade",
			  "User-Agent",
			  "WWW-Authenticate"
			};
  public static final String days[] =
                      {
			  "Sun",
			  "Mon",
			  "Tue",
			  "Wed",
			  "Thu",
			  "Fri",
			  "Sat"
			};
  public static final String months[] =
			{
			  "Jan",
			  "Feb",
			  "Mar",
			  "Apr",
			  "May",
			  "Jun",
		          "Jul",
			  "Aug",
			  "Sep",
			  "Oct",
			  "Nov",
			  "Dec"
			};



  //
  //  Private class members.
  //
  private URL                  url;   // URL of connection.

  public  Socket               conn;       // Connection socket.
  public  boolean              connected;  // True when connected.

  public  BufferedInputStream  is;    //  Input stream.
  public  BufferedReader       br;
  public  BufferedOutputStream os;    //  Output stream.

  private boolean             encrypted;

  public  int                 write_content_length;
  private char                write_buffer[];
  private int                 write_buffer_head;
  private int                 write_buffer_tail;

  public String               read_header_date;
  public String               read_header_server;
  public String               read_header_charset;
  public String               read_header_content_language;
  public String               read_header_content_type;
  public int                  read_header_content_length;

  public char                 read_buffer[];
  private int                 read_buffer_head;
  private int                 read_buffer_tail;

  public int                  status;
  public String               status_text;
  public String               version;
  public int                  error;
  public int                  activity;

  public String               hostname;       // Hostname from URL
  public int                  port;           // Port from URL.
  public String               path;           // Path from URL.
  public String               user;           // User name
  public String               passwd;         // Password

  public String               auth_type;      // none, basic, digest
  public String               realm;          // For digest auth
  public String               opaque;         // For digest auth
  public String               nonce;          // For digest auth
  public String               resource;       // For digest auth
  public String               method;         // For digest auth

  public String               http_request;  
  public int                  http_content_length;



  /**
   *  Constructor using <code>URL</code>.
   *
   *  @param	request_url	<code>URL</code> of server to connect to.
   *  @throw	IOException
   *  @throw	UnknownHostException
   */
  public IPPHttp( String request_url )
        throws IOException, UnknownHostException
  {

    encrypted   = false;
    status      = 0;
    status_text = "";
    version     = "1.0";
    connected   = false;
    user        = "";
    passwd      = "";

    auth_type   = "";
    realm       = "";
    nonce       = "";
    resource    = "";
    method      = "";

    try
    {
      //
      //  Create the URL and split it up.
      //
      url      = new URL(request_url);
      hostname = url.getHost();
      port     = url.getPort();
      path     = url.getPath();


      //
      //  Open the socket and set the options.
      //
      conn     = new Socket(hostname, port);
      conn.setSoTimeout(200);

      //
      //  Create the input and output streams.
      //
      is       = new BufferedInputStream(new DataInputStream(conn.getInputStream()));
      os       = new BufferedOutputStream(new DataOutputStream(conn.getOutputStream()));
      connected = true;
    }
    catch(UnknownHostException unknownhostexception)
    {
            throw unknownhostexception;
    }
    catch(IOException ioexception)
    {
            throw ioexception;
    }
  }



  /**
   *  Constructor using <code>URL, user and pass</code>.
   *
   *  @param	request_url	<code>URL</code> of server to connect to.
   *  @param	p_auth_type     <code>String</code> basic or digest.
   *  @param	p_user		<code>String</code> User name.
   *  @param	p_passwd	<code>String</code> password.
   *  @throw	IOException
   *  @throw	UnknownHostException
   */
  public IPPHttp( String request_url, String p_auth_type,
                  String p_user,      String p_passwd )
        throws IOException, UnknownHostException
  {
    encrypted   = false;
    status      = 0;
    status_text = "";
    version     = "1.0";
    connected   = false;

    user        = p_user;
    passwd      = p_passwd;
    auth_type   = p_auth_type;

    realm       = "";
    nonce       = "";
    resource    = "";
    method      = "";

    try
    {
      //
      //  Create the URL and split it up.
      //
      url      = new URL(request_url);
      hostname = url.getHost();
      port     = url.getPort();
      path     = url.getPath();

      //
      //  Open the socket and set the options.
      //
      conn     = new Socket(hostname, port);
      conn.setSoTimeout(200);

      //
      //  Create the input and output streams.
      //
      is       = new BufferedInputStream(new DataInputStream(conn.getInputStream()));
      os       = new BufferedOutputStream(new DataOutputStream(conn.getOutputStream()));
      connected = true;
    }
    catch(UnknownHostException unknownhostexception)
    {
            throw unknownhostexception;
    }
    catch(IOException ioexception)
    {
            throw ioexception;
    }
  }




  /**
   *  Re-establish a dropped connection.
   *
   *  @return	<code>boolean</code>		True if connected.
   *
   *  @throw	IOException
   */
  public boolean reConnect() throws IOException
  {
    connected   = false;
    status      = 0;
    status_text = "";
    try
    {
      //
      //  Open the socket and set the options.
      //
      conn     = new Socket(hostname, port);
      conn.setSoTimeout(200);

      //
      //  Create the input and output streams.
      //
      is       = new BufferedInputStream(new DataInputStream(conn.getInputStream()));
      os       = new BufferedOutputStream(new DataOutputStream(conn.getOutputStream()));
      connected = true;
      return(connected);

    }
    catch (IOException ioexception)
    {
      connected = false;
      throw(ioexception);
    }
  }



  /**
   * Set the user name.
   *
   * @param	p_user		<code>String</code> - user name.
   */
  public void setUser(String p_user )
  {
    user = p_user;
  }


  /**
   * Set the password.
   *
   * @param	p_passwd	<code>String</code> - password.
   */
  public void setPassword(String p_passwd )
  {
    passwd = p_passwd;
  }




  /**
   * Write the request header bytes to the server.
   *
   * @param	request		<code>String</code> - the request.
   * @param	content_length	<code>int</code> - size of the total request.
   * @throw	IOException
   */
  public int writeHeader(String request, int content_length )
        throws IOException
  {

    http_request        = request;
    http_content_length = content_length;

    try
    {
      String s1 = "POST " + request + " HTTP/1.0\r\n";
      os.write(s1.getBytes(), 0, s1.length());

      s1 = "Content-type: application/ipp\r\n";
      os.write(s1.getBytes(), 0, s1.length());


      //
      //  Do basic style authorization if needed.
      //
      if (auth_type.compareTo("basic") == 0)
      {
        s1 = user + ":" + passwd;
        IPPBase64Encoder encoder = new IPPBase64Encoder();
        String auth_string = encoder.encode(s1.getBytes());
        s1 = "Authorization: Basic " + auth_string + "\r\n";
        os.write(s1.getBytes(), 0, s1.length());
      }
      else if (auth_type.compareTo("digest") == 0)
      {
        try
        {
            IPPMD5 md5 = IPPMD5.getInstance();
            String auth_string = md5.MD5Digest(user, passwd, realm,
                                             "POST", path, nonce );
            s1 = "Authorization: Digest " + "username=\"" + user + "\", " +
                 "realm=\"" + realm + "\", " +
                 "nonce=\"" + nonce + "\", " +
                 "response=\"" + auth_string + "\"\r\n";

            os.write(s1.getBytes(), 0, s1.length());
        }
        catch(NoSuchAlgorithmException e)
        {
            System.out.println("No such algorithm: MD5.");
        }
      }

      s1 = "Content-length: " + content_length + "\r\n\r\n";
      os.write(s1.getBytes(), 0, s1.length());
      os.flush();
    }
    catch(IOException ioexception)
    {
      error = HTTP_ERROR;
      throw ioexception;
    }


    try
    {
      int          local_status = 0;

      //
      //  Check for any response.
      //
      if (is.available() > 0)
      {
        StringBuffer http_version = new StringBuffer(32);
        StringBuffer http_status  = new StringBuffer(32);
        StringBuffer http_text    = new StringBuffer(256);

        String read_buffer;
        status = 0;
        is.mark(8192);
        while (is.available() > 0)
        {
          read_buffer = read_line();

          if (read_buffer.startsWith("HTTP/"))
          {
            int i,n;
            String s2 = read_buffer.substring(5);


            for (i=0;(i < s2.length() && s2.charAt(i) != ' '); i++)
            {
              http_version.append(s2.charAt(i));
            }
            while (i < s2.length() && s2.charAt(i) == ' ')
              i++;
            for (;(i < s2.length() && s2.charAt(i) != '\n' && 
                 s2.charAt(i) != '\r' && s2.charAt(i) != ' '); i++)
            {
              http_status.append(s2.charAt(i));
            }

            while (i < s2.length() && s2.charAt(i) == ' ')
              i++;
            for (n=0;(n < 256 && i < s2.length() && s2.charAt(i) != '\n' && 
                 s2.charAt(i) != '\r' && s2.charAt(i) != ' '); i++)
            {
              http_text.append(s2.charAt(i));
            }
            local_status      = Integer.parseInt(http_status.toString(), 10);
          }
        }
        is.reset();
      }

      //
      //  See if we need to reconnect and send authorization.
      //
      switch( local_status )
      {
        //
        //  Not authorized.
        //
        case HTTP_UNAUTHORIZED: read_header();
                  return( local_status );
      }
    }
    catch(IOException ioexception)
    {
      error = HTTP_ERROR;
      throw ioexception;
    }
    return(0);
  }






  public int checkForResponse()
  {
    //
    //  Check for any response.
    //
    try
    {
      if (is.available() > 0)
      {
        StringBuffer http_version = new StringBuffer(32);
        StringBuffer http_status  = new StringBuffer(32);
        StringBuffer http_text    = new StringBuffer(256);
        int          local_status = 0;
        String       read_buffer;

        status = 0;
        is.mark(8192);
        while (is.available() > 0)
        {
          read_buffer = read_line();
          if (read_buffer.startsWith("HTTP/"))
          {
            int i,n;
            String s2 = read_buffer.substring(5);
            for (i=0;(i < s2.length() && s2.charAt(i) != ' '); i++)
            {
              http_version.append(s2.charAt(i));
            }
            while (i < s2.length() && s2.charAt(i) == ' ')
              i++;
            for (;(i < s2.length() && s2.charAt(i) != '\n' && 
                 s2.charAt(i) != '\r' && s2.charAt(i) != ' '); i++)
            {
              http_status.append(s2.charAt(i));
            }

            while (i < s2.length() && s2.charAt(i) == ' ')
              i++;
            for (n=0;(n < 256 && i < s2.length() && s2.charAt(i) != '\n' && 
                 s2.charAt(i) != '\r' && s2.charAt(i) != ' '); i++)
            {
              http_text.append(s2.charAt(i));
            }
            local_status      = Integer.parseInt(http_status.toString(), 10);
            status = local_status;
          }
        }
        is.reset();

        //
        //  See if we need to reconnect and send authorization.
        //
        switch( local_status )
        {
          //
          //  Not authorized.
          //
          case HTTP_UNAUTHORIZED: read_header();
                    return( local_status );
        }
      } 
    }
    catch (IOException e)
    {
      return(HTTP_ERROR);
    }
    return(0);
  }


  /**
   *  Write bytes to the output stream.
   *
   *  @param	bytes		Array of bytes to write to the stream.
   *  @throw	IOException
   */
  public void write(byte bytes[])
        throws IOException
  {
    try
    {
      os.write(bytes, 0, bytes.length);
      os.flush();
    }
    catch(IOException ioexception)
    {
      error = HTTP_ERROR;
      throw ioexception;
    }
  }


  /**
   *  Write bytes to the output stream.
   *
   *  @param	bytes		Array of bytes to write to the stream.
   *  @param	length		Number of bytes to write to the stream.
   *  @throw	IOException
   */
  public void write(byte bytes[], int length )
        throws IOException
  {
    try
    {
      os.write(bytes, 0, length);
      os.flush();
    }
    catch(IOException ioexception)
    {
      error = HTTP_ERROR;
      throw ioexception;
    }
  }










  /**
   *  Read the HTTP header from the input stream.
   *
   *  @return	<code>int</code>	Content length of response.
   *  @return	0			Return zero on error.
   *  @throw	IOException
   */	
  public int read_header()
        throws IOException
  {
    boolean done = false;
    read_header_content_length = 0;

    String read_buffer; 
    while (!done)
    {
        read_buffer = read_line();
        if (read_buffer.startsWith("HTTP/"))
        {
          int i,n;
          String s2 = read_buffer.substring(5);

          StringBuffer http_version = new StringBuffer(32);
          StringBuffer http_status  = new StringBuffer(32);
          StringBuffer http_text    = new StringBuffer(256);

          for (i=0;(i < s2.length() && s2.charAt(i) != ' '); i++)
          {
            http_version.append(s2.charAt(i));
          }
          while (i < s2.length() && s2.charAt(i) == ' ')
            i++;
          for (;(i < s2.length() && s2.charAt(i) != '\n' && 
                 s2.charAt(i) != '\r' && s2.charAt(i) != ' '); i++)
          {
            http_status.append(s2.charAt(i));
          }

          while (i < s2.length() && s2.charAt(i) == ' ')
            i++;
          for (n=0;(n < 256 && i < s2.length() && s2.charAt(i) != '\n' && 
                 s2.charAt(i) != '\r' && s2.charAt(i) != ' '); i++)
          {
            http_text.append(s2.charAt(i));
          }
          version     = http_version.toString();
          status      = Integer.parseInt(http_status.toString(), 10);
          status_text = http_text.toString();
        }
        else if (read_buffer.startsWith("WWW-Authenticate: Basic"))
        {
          String s2=read_buffer.substring("WWW-Authenticate: Basic".length());
          auth_type = "basic";
        }
        else if (read_buffer.startsWith("WWW-Authenticate: Digest"))
        {
          String s2=read_buffer.substring("WWW-Authenticate: Digest".length());
          auth_type = "digest";
          parseAuthenticate(s2);
        }
        else if (read_buffer.startsWith("Content-Length:"))
        {
          String s2 = read_buffer.substring(15);
          read_header_content_length = Integer.parseInt(s2.trim(), 10);
        }
        else if (read_buffer.startsWith("Content-Language:"))
        {
          String s3 = read_buffer.substring(17);
          read_header_content_language = s3.trim();
        } 
        else if (read_buffer.startsWith("Server:"))
        {
          String s4 = read_buffer.substring(7);
          read_header_server = s4.trim();
        }
        else if (read_buffer.startsWith("Date:"))
        {
          String s5 = read_buffer.substring(5);
          read_header_date = s5.trim();
        } 
        else if (read_buffer.length() == 0)
        {
          done = true;
          return( read_header_content_length );
        }
    }
    return( 0 );
  }



  /**
   *  Read a line from the input stream.
   *
   * @return	<code>String</code>	Line read.
   * @throw	<code>IOException</code>
   */
  public String read_line()
        throws IOException
  {
    StringBuffer sb = new StringBuffer();
    int          c = 0;

    try
    {
      while ((c != -1) && (c != 10))
      {
        c = is.read();
        switch( c )
        {
          case -1:
          case 10:
          case 13:
                   break;

          default: sb.append((char)c);
        }
      }
    }
    catch (IOException e)
    {
      throw(e);
    }
    return(sb.toString());
  }





  /**
   *  Read up to <code>count</code> bytes from the input stream.
   *
   * @param	<code>count</code>	Number of bytes to read.
   * @return	<code>char[]</code>	Character array of data read.
   * @throw	<code>IOException</code>
   */
  public char[] read(int count)
        throws IOException
  {
    char ac[] = new char[count];
    int j = 0;
    try
    {
      for (int k = is.read(); k != -1 && j < count; k = is.read())
      {
        ac[j++] = (char)k;
      }
    }
    catch(IOException ioexception)
    {
      throw ioexception;
    }
    return(ac);
  }


  /**
   *  Process the HTTP response from the server.
   *
   * @return	<code>IPP</code>	IPP object containing response data.
   * @see	<code>IPP</code>
   * @see	<code>IPPRequest</code>
   * @see	<code>IPPAttribute</code>
   * @see	<code>IPPValue</code>
   * @see	<code>IPPDefs</code>
   */
  public IPP processResponse()
  {
    IPP              ipp;
    IPPAttribute     attr;       // temp attribute 
    IPPValue         val;        // temp value

    short            vtag;       //  Current value tag 
    short            gtag;       //  Current group tag

    char[]           buffer;

    ipp = new IPP();
    ipp.request = new IPPRequest();

    int read_buffer_bytes     = read_buffer.length;
    int read_buffer_remaining = read_buffer_bytes;
    int bufferidx             = 0;
    int n;


    ipp.current = -1;   // current attritue??
    ipp.last    = -1;   //  last attr?
    attr        = null;
    buffer      = read_buffer;
    gtag        = -1;
    vtag        = -1;


    //  ---------------------------------------------------------------
    //  State machine to process response.
    //
    ipp.state  = IPPDefs.IDLE;
    while ((ipp.state != IPPDefs.TAG_END) &&
           (read_buffer_remaining > 0))
    {
      switch (ipp.state)
      {
        case IPPDefs.IDLE :
            ipp.state++;         /* Avoid common problem... */

        //
        // Get the request header...
        //
        case IPPDefs.HEADER :
          if (read_buffer_remaining < 8)
	  {
	    return (null);
	  }

          //
          //  Verify the major version number...
	  //
	  if (buffer[0] != (char)1)
	  {
	    return (null);
	  }

          //
          //  Then copy the request header over...
  	  //
          ipp.request.version[0]  = buffer[bufferidx++];
          ipp.request.version[1]  = buffer[bufferidx++];
          ipp.request.op_status   = (short)((short)buffer[bufferidx] << 8) | 
                                             (short)buffer[bufferidx+1];
          bufferidx += 2;
      
          //
          //  Get the text version of the request status ....
          //
          ipp.status = new IPPStatus(ipp.request.op_status);

          ipp.request.request_id  = (int)((int)buffer[bufferidx] << 24) | 
                                         ((int)buffer[bufferidx+1] << 16) |
	                                 ((int)buffer[bufferidx+2] << 8) | 
                                         ((int)buffer[bufferidx+3]);
          bufferidx += 4;
          read_buffer_remaining -= 8;

          ipp.state   = IPPDefs.ATTRIBUTE;
	  ipp.current = -1;
	  ipp.current_tag  = IPPDefs.TAG_ZERO;
          break;

      case IPPDefs.ATTRIBUTE :
          while (read_buffer_remaining > 0)
          {
	    //
	    //  Read the value tag first.
	    //
            vtag = (short)buffer[bufferidx++];
            read_buffer_remaining--;
	    if (vtag == IPPDefs.TAG_END)
	    {
	      //
	      //  No more attributes left...
	      //
	      ipp.state = IPPDefs.DATA;
              if (attr != null)
              {
                ipp.addAttribute(attr);
                attr = null;
              }
	      break;
	    }
            else if (vtag < IPPDefs.TAG_UNSUPPORTED_VALUE)
	    {
              if (attr != null)
              {
                ipp.addAttribute(attr);
              }

	      //
	      //  Group tag...  Set the current group and continue...
	      //
              gtag = vtag;

              // If still the same group ....
              if (ipp.current_tag == gtag)
              {
                //
                //  Add a separator
                //
                attr = new IPPAttribute(IPPDefs.TAG_ZERO,IPPDefs.TAG_ZERO,"");
                ipp.addAttribute(attr);
                attr = null;
              }


	      ipp.current_tag  = gtag;
	      ipp.current = -1;
	      continue;
	    }

            //
	    // Get the name...
	    //
            n = ((int)buffer[bufferidx] << 8) | (int)buffer[bufferidx+1];
            bufferidx += 2;
            read_buffer_remaining -= 2;

            if (n == 0)
	    {
              //
              //  Parse Error, can't add additional values to null attr
              //
              if (attr == null)
	        return (null);

	      //
	      //  More values for current attribute...
	      //

	      //
	      // Make sure we aren't adding a new value of a different
	      // type...
	      //

	      if (attr.value_tag == IPPDefs.TAG_STRING ||
                  (attr.value_tag >= IPPDefs.TAG_TEXTLANG &&
		   attr.value_tag <= IPPDefs.TAG_MIMETYPE))
              {
	        //
	        // String values can sometimes come across in different
	        // forms; accept sets of differing values...
	        //
	        if (vtag != IPPDefs.TAG_STRING &&
    	           (vtag < IPPDefs.TAG_TEXTLANG || vtag > IPPDefs.TAG_MIMETYPE))
	          return (null);
              }
	      else if (attr.value_tag != vtag)
	        return (null);
	    }
	    else
	    {
              if (attr != null)
              {
                ipp.addAttribute(attr);
                attr = null;
              }

              //
              // New Attribute
              //
              StringBuffer s = new StringBuffer();
              for (int i=0; i < n; i++)
              {
                s.append((char)buffer[bufferidx++]);
                read_buffer_remaining--;
              }
              attr     = new IPPAttribute( gtag, vtag, s.toString() );
	    }
	    n = ((short)buffer[bufferidx] << 8) | (short)buffer[bufferidx+1];
            bufferidx += 2;
            read_buffer_remaining -= 2;

	    switch (vtag)
	    {
	      case IPPDefs.TAG_INTEGER :
	      case IPPDefs.TAG_ENUM :
	  	n = (int)(((int)buffer[bufferidx] << 24) |
                          ((int)buffer[bufferidx+1] << 16) |
                          ((int)buffer[bufferidx+2] << 8) |
                          ((int)buffer[bufferidx+3]));
                bufferidx += 4;
                read_buffer_remaining -= 4;
                attr.addInteger( n );
	        break;

	      case IPPDefs.TAG_BOOLEAN :
                if ((byte)buffer[bufferidx++] > 0)
                  attr.addBoolean(true);
                else
                  attr.addBoolean(false);
                read_buffer_remaining --;
	        break;

	      case IPPDefs.TAG_TEXT :
	      case IPPDefs.TAG_NAME :
	      case IPPDefs.TAG_KEYWORD :
	      case IPPDefs.TAG_STRING :
	      case IPPDefs.TAG_URI :
	      case IPPDefs.TAG_URISCHEME :
	      case IPPDefs.TAG_CHARSET :
	      case IPPDefs.TAG_LANGUAGE :
	      case IPPDefs.TAG_MIMETYPE :
                StringBuffer s = new StringBuffer();
                for (int i=0; i < n; i++ )
                {
                  s.append( (char)buffer[bufferidx++] );
                  read_buffer_remaining --;
                }
                attr.addString( "", s.toString() );
	        break;


	      case IPPDefs.TAG_DATE :
                char db[] = new char[11];
                for (int i=0; i < 11; i++ )
                {
                  db[i] = (char)buffer[bufferidx++];
                  read_buffer_remaining --;
                }
                attr.addDate( db );
	        break;


	      case IPPDefs.TAG_RESOLUTION :
                if (read_buffer_remaining < 9)
                  return( null );

                int  x, y;
                byte u;
                x = (int)(((int)buffer[bufferidx] & 0xff000000) << 24) |
                         (((int)buffer[bufferidx+1] & 0x00ff0000) << 16) |
                         (((int)buffer[bufferidx+2] & 0x0000ff00) << 8) |
                         (((int)buffer[bufferidx+3] & 0x000000ff));
                bufferidx += 4;
                read_buffer_remaining -= 4;

                y = (int)(((int)buffer[bufferidx] & 0xff000000) << 24) |
                         (((int)buffer[bufferidx+1] & 0x00ff0000) << 16) |
                         (((int)buffer[bufferidx+2] & 0x0000ff00) << 8) |
                         (((int)buffer[bufferidx+3] & 0x000000ff));
                bufferidx += 4;
                read_buffer_remaining -= 4;

                u = (byte)buffer[bufferidx++];
                read_buffer_remaining--;
                attr.addResolution( u, x, y );         
	        break;

	      case IPPDefs.TAG_RANGE :
	        if (read_buffer_remaining < 8)
		  return (null);

                int lower, upper; 
                lower = (int)(((int)buffer[bufferidx] & 0xff000000) << 24) |
                         (((int)buffer[bufferidx+1] & 0x00ff0000) << 16) |
                         (((int)buffer[bufferidx+2] & 0x0000ff00) << 8) |
                         (((int)buffer[bufferidx+3] & 0x000000ff));
                bufferidx += 4;
                read_buffer_remaining -= 4;

                upper = (int)(((int)buffer[bufferidx] & 0xff000000) << 24) |
                         (((int)buffer[bufferidx+1] & 0x00ff0000) << 16) |
                         (((int)buffer[bufferidx+2] & 0x0000ff00) << 8) |
                         (((int)buffer[bufferidx+3] & 0x000000ff));
                bufferidx += 4;
                read_buffer_remaining -= 4;

                attr.addRange( (short)lower, (short)upper );         
	        break;

	      case IPPDefs.TAG_TEXTLANG :
	      case IPPDefs.TAG_NAMELANG :
	       //
	       // text-with-language and name-with-language are composite
	       // values:
	       //
	       // charset-length
	       // charset
	       // text-length
	       // text
	       //

		n = ((int)buffer[bufferidx] << 8) | (int)buffer[bufferidx+1];
                bufferidx += 2;

                StringBuffer cs = new StringBuffer();
                for (int i=0; i < n; i++ )
                {
                  cs.append( (char)buffer[bufferidx++] );
                  read_buffer_remaining --;
                }

		n = ((int)buffer[bufferidx] << 8) | (int)buffer[bufferidx+1];
                bufferidx += 2;

                StringBuffer tx = new StringBuffer();
                for (int i=0; i < n; i++ )
                {
                  tx.append( (char)buffer[bufferidx++] );
                  read_buffer_remaining --;
                }

                attr.addString( cs.toString(), tx.toString() );
	        break;


              default : /* Other unsupported values */
	        if (n > 0)
		{
                  bufferidx += n;
                  read_buffer_remaining -= n;
		}
	        break;
	    }
	  }  // End of while

          if (attr != null)
          {
            ipp.addAttribute(attr);
            attr = null;
          }
          break;

      case IPPDefs.DATA :
        break;

      default :
        break; /* anti-compiler-warning-code */
    }
  }
  return (ipp);
}



//
//  Parse a WWW-Authenticate: Digest request
//
public void parseAuthenticate( String p_auth )
{
  String        tmp;
  StringBuffer	val;
  int           i,n;

  tmp = p_auth;
  while (tmp.length() > 0)
  {
    i = 0;
    while (tmp.length() > 0 && (tmp.charAt(i) == ' ' || tmp.charAt(i) == '"'))
      tmp = tmp.substring(1); 
    i = 0;

    if (tmp.startsWith("realm="))
    {
      i = "realm=".length();
      tmp = tmp.substring(i);
      i = 0;
      while ((i < tmp.length()) && 
             (tmp.charAt(i) == ' ' || 
              tmp.charAt(i) == '"' || 
              tmp.charAt(i) == '='))
      {
        i++;
      }
      val = new StringBuffer(1024);
      while (i < tmp.length() && tmp.charAt(i) != '"')
        val.append(tmp.charAt(i++));
      realm = val.toString();
      tmp = tmp.substring(i);
    }
    else if (tmp.startsWith("nonce="))
    {
      i = "nonce=".length();
      tmp = tmp.substring(i);
      i = 0;
      while ((i < tmp.length()) && 
             (tmp.charAt(i) == ' ' || 
              tmp.charAt(i) == '"' || 
              tmp.charAt(i) == '='))
      {
        i++;
      }
      val = new StringBuffer(1024);
      while (i < tmp.length() && tmp.charAt(i) != '"')
        val.append(tmp.charAt(i++));
      nonce = val.toString();
      tmp = tmp.substring(i);
    }
    else if (tmp.startsWith("opaque="))
    {
      i = "opaque=".length();
      tmp = tmp.substring(i);
      i = 0;
      while ((i < tmp.length()) && 
             (tmp.charAt(i) == ' ' || 
              tmp.charAt(i) == '"' || 
              tmp.charAt(i) == '='))
      {
        i++;
      }
      val = new StringBuffer(1024);
      while (i < tmp.length() && tmp.charAt(i) != '"')
        val.append(tmp.charAt(i++));
      opaque = val.toString();
      tmp = tmp.substring(i);
    }
    else 
    {
      StringBuffer name = new StringBuffer(256);
      while ((i < tmp.length()) && 
             (tmp.charAt(i) != ' ' || 
              tmp.charAt(i) != '"' || 
              tmp.charAt(i) != '='))
      {
        name.append(tmp.charAt(i++));
      }
     
      i = name.toString().length();
      tmp = tmp.substring(i);
      i = 0;
      while ((i < tmp.length()) && 
             (tmp.charAt(i) == ' ' || 
              tmp.charAt(i) == '"' || 
              tmp.charAt(i) == '='))
      {
        i++;
      }
      val = new StringBuffer(1024);
      while (i < tmp.length() && tmp.charAt(i) != '"')
        val.append(tmp.charAt(i++));
      //junk = val.toString();
      tmp = tmp.substring(i);
    }
  }
}





}  // End of IPPHttp class


