Cups.java   [plain text]


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>Cups</code> object is used for connecting to servers,
 * reading and writing data, and performing common CUPS operations.
 *
 * @author	TDB
 * @version	1.0.1
 * @since	JDK1.3
 */

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

public class Cups
{

    static final int REQ_STATE_CREATE_HTTP       = 0;
    static final int REQ_STATE_WRITE_HTTP_HEADER = 1;
    static final int REQ_STATE_WRITE_IPP_HEADER  = 2;
    static final int REQ_STATE_WRITE_IPP_ATTRS   = 3;
    static final int REQ_STATE_FINISH_IPP_ATTRS  = 4;
    static final int REQ_STATE_READ_RESPONSE     = 5;
    static final int REQ_STATE_DONE              = 6;
    static final String[] req_state_names =
                          { "Create HTTP", 
                            "Write Http Header", 
                            "Write IPP Header",
                            "Write IPP Attrs", 
                            "Finish IPP Attrs", 
                            "Read Response",
                            "Done" 
                          };


    static final int FILEREQ_STATE_CREATE_HTTP       = 0;
    static final int FILEREQ_STATE_WRITE_HTTP_HEADER = 1;
    static final int FILEREQ_STATE_WRITE_IPP_HEADER  = 2;
    static final int FILEREQ_STATE_WRITE_IPP_ATTRS   = 3;
    static final int FILEREQ_STATE_FINISH_IPP_ATTRS  = 4;
    static final int FILEREQ_STATE_WRITE_FILE_DATA   = 5;
    static final int FILEREQ_STATE_READ_RESPONSE     = 6;
    static final int FILEREQ_STATE_DONE              = 7;
    static final String[] filereq_state_names =
                          { "Create HTTP", 
                            "Write Http Header", 
                            "Write IPP Header",
                            "Write IPP Attrs", 
                            "Finish IPP Attrs", 
                            "Write File Data", 
                            "Read Response",
                            "Done" 
                          };


    IPP		ipp;               //  IPP Request
    IPPHttp     http;              //  Connection to server


    String      protocol;          //  Protocol name
    String      address;           //  address/name of server
    int         port;              //  Port #
    String      path;              //  Path ....
    String      dest;              //  Name of destination printer
    String      instance;          //  Instance of printer

    //
    //  encrypt, user, and passwd are not fully implemented!
    //
    boolean     encrypt;           //  Open encrypted connection.
    String      user;              //  User to login as.
    String      passwd;            //  Password if needed.

    String      site;              //  URL of site.

    int         last_error;        // Last error #
    String      error_text;        // Text for error
    
    /**
     * Void constructor.
     */
    public Cups()
    {
      http     = null;
      ipp      = null;

      protocol = "http";
      address  = "localhost";
      port     = 631;
      path     = "/";
      site     = "http://localhost:631/";
      dest     = "";
      instance = "";
      user     = "";
      passwd   = "";
      encrypt  = false;
    }

    /**
     * Constructor using a <code>URL</code>.
     *
     * @param	<code>p_url</code>	A <code>URL</code> object.
     */
    public Cups( URL p_url )
    {
      http     = null;
      ipp      = null;

      protocol = p_url.getProtocol() + "://";
      address  = p_url.getHost();
      port     = p_url.getPort();
      path     = p_url.getPath();

      site     = protocol + address;
      if (port > 0)
        site = site + ":" + port;

      if (path.length() > 0)
        site = site + path;

      dest     = "";
      instance = "";
      user     = "";
      passwd   = "";
      encrypt  = false;
    }


    /**
     * Set the value of the <code>protocol</code> member.  Valid values
     * are ipp or http.
     *
     * @param	<code>p_protocol</code>		String with protocol.
     */
    public void setProtocol( String p_protocol )
    {
      protocol = p_protocol;
      site     = protocol + "://" + address + ":" + port + path;
    }

    /**
     * Set the value of the <code>server</code> member.  This is an
     * IP address or a hostname.
     *
     * @param	<code>p_server</code>		IP address or hostname.
     */
    public void setServer( String p_server )
    {
      address = p_server;
      site     = protocol + "://" + address + ":" + port + path;
    }


    /**
     * Set the value of the <code>port</code> member.  
     *
     * @param	<code>p_port</code>		Port number.
     */
    public void setPort( int p_port )
    {
      port = p_port;
      site = protocol + "://" + address + ":" + port + path;
    }


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


    /**
     * Set the value of the <code>passwd</code> member.  
     *
     * @param	<code>p_passwd</code>		Password.
     */
    public void setPasswd( String p_passwd )
    {
      passwd = p_passwd;
    }


    /**
     * Set the value of the <code>dest</code> member.  
     *
     * @param	<code>p_dest</code>		Destination.
     */
    public void setDest( String p_dest )
    {
      dest = p_dest;
    }


    /**
     * Set the value of the <code>instance</code> member.  
     *
     * @param	<code>p_instance</code>		Instance.
     */
    public void setInstance( String p_instance)
    {
      instance = p_instance;
    }


    /**
     * Set the value of the <code>encrypt</code> member.  
     *
     * @param	<code>p_enrypt</code>		Yes or no.
     */
    public void setEncrypt( boolean p_encrypt )
    {
      encrypt = p_encrypt;
    }


    /**
     * Get the value of the <code>encrypt</code> member.  
     *
     * @return 	<code>boolean</code>		Encryption on or off.
     */
    public boolean getEncrypt()
    {
      return(encrypt);
    }


    /**
     * Set the value of the <code>path</code> member.  This is the
     * path that will be used in the POST method.
     *
     * @param	<code>p_path</code>		Path on server.
     */
    public void setPath( String p_path )
    {
      path = p_path;
      site = protocol + "://" + address + ":" + port + path;
    }



    public boolean doRequest(String from) throws IOException
    {
      // System.out.println("doRequest From: " + from );
      return(doRequest());
    }



    /**
     * Do a CUPS request to the server.
     *
     * @param	<code>p_dest</code>		Destination name.
     * @return  <code>boolean</code>            True on success, false otherwise
     * @throw   <code>IOException</code>
     */
    public boolean doRequest() throws IOException
    {
      IPPAttribute attr;
      int state  = REQ_STATE_CREATE_HTTP;
      int errors = 0;

      while (true)
      {
        switch( state )
        {

          case REQ_STATE_CREATE_HTTP:
            String url_str = site + dest;

            try
            {
              if (user.length() > 0 && passwd.length() > 0)
                http = new IPPHttp(url_str, "", user, passwd );
              else
                http = new IPPHttp(url_str);
              state++;
            }
            catch (IOException e)
            {
              throw(e);
            }
            break;



          case REQ_STATE_WRITE_HTTP_HEADER:
            //
            //  Send the HTTP header.
            //
            switch( http.writeHeader( http.path, ipp.sizeInBytes() ))
            {
              case IPPHttp.HTTP_FORBIDDEN:
              case IPPHttp.HTTP_NOT_FOUND:
              case IPPHttp.HTTP_BAD_REQUEST:
              case IPPHttp.HTTP_METHOD_NOT_ALLOWED:
              case IPPHttp.HTTP_PAYMENT_REQUIRED:
              case IPPHttp.HTTP_UPGRADE_REQUIRED:
              case IPPHttp.HTTP_ERROR:
              case IPPHttp.HTTP_UNAUTHORIZED:
                     errors++;
                     if (errors < 5)
                     {
                       if (!http.reConnect())
                       {
                         System.out.println("Could not reConnect(0)!");
                         return(false);
                       }
                     }
                     else
                     {
                       return(false);
                     }
                     break;

              default: state++;
            }
            break;



          case REQ_STATE_WRITE_IPP_HEADER:
            //
            //  Send the request header.
            //
            byte[] header = new byte[8];
            header[0] = (byte)1; 
            header[1] = (byte)1; 
            header[2] = (byte)((ipp.request.operation_id & 0xff00) >> 8);
            header[3] = (byte)(ipp.request.operation_id & 0xff);
            header[4] = (byte)((ipp.request.request_id & 0xff000000) >> 24);
            header[5] = (byte)((ipp.request.request_id & 0xff0000) >> 16);
            header[6] = (byte)((ipp.request.request_id & 0xff00) >> 8);
            header[7] = (byte)(ipp.request.request_id & 0xff);
            http.write( header );
            if (http.checkForResponse() >= IPPHttp.HTTP_BAD_REQUEST)
            {
               errors++;
               if (errors < 5)
               {
                 if (http.reConnect())
                   state = REQ_STATE_WRITE_HTTP_HEADER;
                 else
                 {
                   System.out.println("Could not reConnect(1)\n");
                   return(false);
                 }
               }
               else
               {
                 return(false);
               }
            }
            else state++;
            break;


          case REQ_STATE_WRITE_IPP_ATTRS:
            //
            //  Send the attributes list.
            //
            byte[]  bytes;
            int     sz;
            int     last_group = -1;
            boolean auth_error = false;
            for (int i=0; i < ipp.attrs.size() && !auth_error; i++)
            {
              attr = (IPPAttribute)ipp.attrs.get(i);
              sz    = attr.sizeInBytes(last_group);
              bytes = attr.getBytes(sz,last_group);
              last_group = attr.group_tag;
              http.write(bytes);

              //
              //  Check for server response between each attribute.
              //
              if (http.checkForResponse() >= IPPHttp.HTTP_BAD_REQUEST)
              {
                 errors++;
                 if (errors < 5)
                 {
                   if (!http.reConnect())
                   {
                     System.out.println("Could not reConnect(2)");
                     return(false);
                   }
                   state = REQ_STATE_WRITE_HTTP_HEADER;
                   auth_error = true;
                 }
                 else
                 {
                   return(false);
                 }
              }

            }
            if (!auth_error)
              state++;
            break;



          case REQ_STATE_FINISH_IPP_ATTRS:
            //
            //  Send the end of attributes tag.
            //
            byte[] footer = new byte[1];
            footer[0] = (byte)IPPDefs.TAG_END; 
            http.write( footer );

            //
            //  Keep checking .....
            //
            if (http.checkForResponse() >= IPPHttp.HTTP_BAD_REQUEST)
            {
              errors++;
              if (errors < 5)
              {
                   if (!http.reConnect())
                   {
                     System.out.println("Could not reConnect(3)");
                     return(false);
                   }
                   state = REQ_STATE_WRITE_HTTP_HEADER;
              }
              else
              {
                return(false);
              }
            }
            else state++;
            break;



          case REQ_STATE_READ_RESPONSE:
            //
            //   Now read back response
            //
            int read_length;
            read_length = http.read_header();
            switch( http.status )
            {
               case IPPHttp.HTTP_OK: 
                 break;

               case IPPHttp.HTTP_UNAUTHORIZED: 
                    http.reConnect();
                    state = REQ_STATE_WRITE_HTTP_HEADER;
                    errors = 0;
                 break;

               default:
                  errors++;
                  if (errors < 5)
                  {
                    if (!http.reConnect())
                    {
                      System.out.println("Could not reConnect(4)");
                      return(false);
                    }
                    state = REQ_STATE_WRITE_HTTP_HEADER;
                  }
                  else 
                  {
                    System.out.println("Too many errors: " + errors );
                    return(false);
                  }
                  break;
            }

            if ((read_length > 0) && (state == REQ_STATE_READ_RESPONSE))
            {
              http.read_buffer = http.read(read_length);
              ipp = http.processResponse();
              state++;
            }
            break;

          case REQ_STATE_DONE:
            //
            //  success.
            //
            http.conn.close();
            http = null;
            return(true);
        }
      }

    }  // End of doRequest



    /**
     * Send a FILE to the CUPS server.
     *
     * @param	<code>file</code>		File to send.
     * @return  <code>boolean</code>            True on success, false otherwise
     * @throw   <code>IOException</code>
     */
    public boolean doRequest(File file) throws IOException
    {
      IPPAttribute     attr;
      int              state  = FILEREQ_STATE_CREATE_HTTP;
      int              errors = 0;
      FileInputStream  fis    = null;

      while (true)
      {
        switch( state )
        {

          case FILEREQ_STATE_CREATE_HTTP:
            String url_str = site + dest;
            try
            {
              if (user.length() > 0 && passwd.length() > 0)
                http = new IPPHttp(url_str, "", user, passwd );
              else
                http = new IPPHttp(url_str);
              state++;
            }
            catch (IOException e)
            {
              throw(e);
            }
            break;



          case FILEREQ_STATE_WRITE_HTTP_HEADER:

            if (fis != null)
            {
              fis.close();
            }

            //
            //  Open an input stream to the file.
            //
            try
            {
              fis = new FileInputStream(file);
            }
            catch (IOException e)
            {
              last_error = -1;
              error_text = "Error opening file input stream.";
              throw(e);
            }

            //
            //  Send the HTTP header.
            //
            int ippSz = ipp.sizeInBytes() + (int)file.length();
            switch( http.writeHeader( http.path, ippSz ))
            {
              case IPPHttp.HTTP_FORBIDDEN:
              case IPPHttp.HTTP_NOT_FOUND:
              case IPPHttp.HTTP_BAD_REQUEST:
              case IPPHttp.HTTP_METHOD_NOT_ALLOWED:
              case IPPHttp.HTTP_PAYMENT_REQUIRED:
              case IPPHttp.HTTP_UPGRADE_REQUIRED:
              case IPPHttp.HTTP_ERROR:
              case IPPHttp.HTTP_UNAUTHORIZED:
                     errors++;
                     if (errors < 5)
                     {
                       http.reConnect();
                     }
                     else
                       return(false);
                     break;

              default: state++;
            }
            break;



          case FILEREQ_STATE_WRITE_IPP_HEADER:
            //
            //  Send the request header.
            //
            byte[] header = new byte[8];
            header[0] = (byte)1; 
            header[1] = (byte)1; 
            header[2] = (byte)((ipp.request.operation_id & 0xff00) >> 8);
            header[3] = (byte)(ipp.request.operation_id & 0xff);
            header[4] = (byte)((ipp.request.request_id & 0xff000000) >> 24);
            header[5] = (byte)((ipp.request.request_id & 0xff0000) >> 16);
            header[6] = (byte)((ipp.request.request_id & 0xff00) >> 8);
            header[7] = (byte)(ipp.request.request_id & 0xff);
            http.write( header );
            if (http.checkForResponse() >= IPPHttp.HTTP_BAD_REQUEST)
            {
               errors++;
               if (errors < 5)
               {
                 http.reConnect();
                 state = FILEREQ_STATE_WRITE_HTTP_HEADER;
               }
               else
                 return(false);
            }
            else state++;
            break;


          case FILEREQ_STATE_WRITE_IPP_ATTRS:
            //
            //  Send the attributes list.
            //
            byte[]  bytes;
            int     sz;
            int     last_group = -1;
            boolean auth_error = false;
            for (int i=0; i < ipp.attrs.size() && !auth_error; i++)
            {
              attr = (IPPAttribute)ipp.attrs.get(i);
              sz    = attr.sizeInBytes(last_group);
              bytes = attr.getBytes(sz,last_group);
              last_group = attr.group_tag;
              http.write(bytes);

              //
              //  Check for server response between each attribute.
              //
              if (http.checkForResponse() >= IPPHttp.HTTP_BAD_REQUEST)
              {
                 errors++;
                 if (errors < 5)
                 {
                   http.reConnect();
                   state = FILEREQ_STATE_WRITE_HTTP_HEADER;
                   auth_error = true;
                 }
                 else
                   return(false);
              }

            }
            if (!auth_error)
              state++;
            break;



          case FILEREQ_STATE_FINISH_IPP_ATTRS:
            //
            //  Send the end of attributes tag.
            //
            byte[] footer = new byte[1];
            footer[0] = (byte)IPPDefs.TAG_END; 
            http.write( footer );

            //
            //  Keep checking .....
            //
            if (http.checkForResponse() >= IPPHttp.HTTP_BAD_REQUEST)
            {
              errors++;
              if (errors < 5)
              {
                   http.reConnect();
                   state = FILEREQ_STATE_WRITE_HTTP_HEADER;
              }
              else
                return(false);
            }
            else state++;
            break;



          case FILEREQ_STATE_WRITE_FILE_DATA:
            //
            //  Send the file data - this could be improved on ALOT.
            //
            int    count; 
            byte[] b      = new byte[1024];
            while ((state == FILEREQ_STATE_WRITE_FILE_DATA) &&
                   ((count = fis.read(b)) != -1))
            {
                //
                //  Keep checking .....
                //
                if (http.checkForResponse() >= IPPHttp.HTTP_BAD_REQUEST)
                {
                  errors++;
                  if (errors < 5)
                  {
                    http.reConnect();
                    state = FILEREQ_STATE_WRITE_HTTP_HEADER;
                  }
                  else
                  {
                    return(false);
                  }
                }
                else
                {
                  if (count > 0)
                    http.write( b, count );
                }

            } // while 

            if (state == FILEREQ_STATE_WRITE_FILE_DATA)
            {
                fis.close();
                fis = null;
                state++;
            }
            break;



          case FILEREQ_STATE_READ_RESPONSE:
            //
            //   Now read back response
            //
            int read_length;
            read_length = http.read_header();
            switch( http.status )
            {
               case IPPHttp.HTTP_OK: 
                 break;

               case IPPHttp.HTTP_UNAUTHORIZED: 
                    http.reConnect();
                    state = FILEREQ_STATE_WRITE_HTTP_HEADER;
                    errors = 0;
                 break;

               default:
                  errors++;
                  if (errors < 5)
                  {
                    http.reConnect();
                    state = FILEREQ_STATE_WRITE_HTTP_HEADER;
                  }
                  else 
                  {
                    return(false);
                  }
                  break;
            }

            if ((read_length > 0) && (state == FILEREQ_STATE_READ_RESPONSE))
            {
              http.read_buffer = http.read(read_length);
              ipp = http.processResponse();
              state++;
            }
            break;

          case FILEREQ_STATE_DONE:
            //
            //  success.
            //
            http.conn.close();
            http = null;
            return(true);
        }
      }

    }  // End of doRequest(file)







    /**
     * Get a list of jobs.
     * 
     * @param	<code>showMyJobs</code>		Show only jobs for user.
     * @param   <code>showCompleted</code>      Show completed OR active jobs.
     *
     * @return	<code>CupsJob[]</code>		Array of job objects, or null.
     * @throw	<code>IOException</code>
     */
    public CupsJob[] cupsGetJobs( boolean showMyJobs, boolean showCompleted ) 
           throws IOException
    {

      IPPAttribute a;

      String req_attrs[] = /* Requested attributes */
	     {
		  "job-id",
		  "job-priority",
		  "job-k-octets",
		  "job-state",
		  "time-at-completed",
		  "time-at-creation",
		  "time-at-processing",
		  "job-printer-uri",
		  "document-format",
		  "job-name",
		  "job-originating-user-name"
	     };


      ipp = new IPP();
      ipp.request = new IPPRequest( 1, (short)IPPDefs.GET_JOBS );

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_CHARSET,
                            "attributes-charset" );
      a.addString( "", "iso-8859-1" );  
      ipp.addAttribute(a);
            

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_LANGUAGE,
                            "attributes-natural-language" );
      a.addString( "", "en" );  
      ipp.addAttribute(a);
            

      //
      //  Add the printer uri
      //
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_URI,
                            "printer-uri" );

      if (site != null)
        a.addString( "", site );  
      else
        a.addString( "", "ipp://localhost/jobs" );  // Default ...
      // a.dump_values();
      ipp.addAttribute(a);
            
      
      //
      //  Add the requesting user name
      //  **FIX** This should be fixed to use the user member.
      //
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_NAME,
                            "requesting-user-name" );
      a.addString( "", "root" );
      ipp.addAttribute(a);

      //
      //  Show only my jobs?
      //
      if (showMyJobs)
      {
        a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_BOOLEAN,
                              "my-jobs" );
        a.addBoolean( true );
        ipp.addAttribute(a);
      }
  
      //
      //  Show completed jobs?
      //
      if (showCompleted)
      {
        a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_KEYWORD,
                              "which-jobs" );
        a.addString( "", "completed" );
        ipp.addAttribute(a);
      }

      //
      //  Get ALL attributes - to get only listed ones,
      //  uncomment this and fill in req_attrs.
      //
      // a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_KEYWORD,
      //                      "requested-attributes" );
      // a.addStrings( "", req_attrs );
      // ipp.addAttribute(a);
      
      //
      //  Do the request and process the response.
      //
      if (doRequest("cupsGetJobs"))
      {


        //
        //   First we have to figure out how many jobs there are
        //   so we can create the array.
        //

        //
        //  Skip past leading attributes
        //
        int i = 0;
        int group_tag = -1;
        while ((i < ipp.attrs.size()) && (group_tag != IPPDefs.TAG_JOB))
        {
          a = (IPPAttribute)ipp.attrs.get(i);
          group_tag = a.group_tag;
          if (group_tag != IPPDefs.TAG_JOB)
            i++;
        }

        int num_jobs = 0;
        group_tag = IPPDefs.TAG_JOB;
        while ((i < ipp.attrs.size()) && (group_tag == IPPDefs.TAG_JOB))
        { 
          a = (IPPAttribute)ipp.attrs.get(i++);
          if ((a != null) && (a.name.compareTo("job-id") == 0))
            num_jobs++;
          // a.dump_values();
        }

        if (num_jobs < 1)
          return(null);


        //
        //  Now create the array of the proper size.
        //
        int n = 0;
        CupsJob[] jobs = new CupsJob[num_jobs];
        for (n=0; n < num_jobs; n++)
        {
          jobs[n] = new CupsJob();
        }




        //
        //  Skip past leading attributes
        //
        group_tag = -1;
        i = 0;
        while ((i < ipp.attrs.size()) && (group_tag != IPPDefs.TAG_JOB))
        {
          a = (IPPAttribute)ipp.attrs.get(i);
          group_tag = a.group_tag;
          if (group_tag != IPPDefs.TAG_JOB)
            i++;
        }

        //
        //  Now we actually fill the array with the job data.
        //
        n = 0;
        for (;i < ipp.attrs.size(); i++)
        {
          a = (IPPAttribute)ipp.attrs.get(i);

          if (a.group_tag == IPPDefs.TAG_ZERO) 
          {
             n++;
             continue;
          }
          else 
          {
            try
            {
              jobs[n].updateAttribute( a );
            }
            catch (ArrayIndexOutOfBoundsException e)
            {
              return(jobs);
            }
          }
        }
        return( jobs );
      }
      return(null);

    }  //  End of cupsGetJobs



    /**
     * Get a list of printers.
     *
     * @return	<code>String[]</code>		Array of printers, or null.
     * @throw	<code>IOException</code>
     */
    public String[] cupsGetPrinters() 
           throws IOException
    {

      IPPAttribute a;

      ipp = new IPP();

      //
      //  Fill in the required attributes
      //
      ipp.request = new IPPRequest( 1, (short)IPPDefs.CUPS_GET_PRINTERS );

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_CHARSET,
                            "attributes-charset" );
      a.addString( "", "iso-8859-1" );  
      ipp.addAttribute(a);
            

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_LANGUAGE,
                            "attributes-natural-language" );
      a.addString( "", "en" );  
      ipp.addAttribute(a);
            

      if (doRequest("cupsGetPrinters"))
      {
        int num_printers = 0;
        for (int i=0; i < ipp.attrs.size(); i++)
        {
          a = (IPPAttribute)ipp.attrs.get(i);
          if ((a.name.compareTo("printer-name") == 0) &&
              (a.value_tag == IPPDefs.TAG_NAME))
          {
            num_printers++;
          }
        }
        if (num_printers < 1)
          return(null);

        String[] printers = new String[num_printers];
        IPPValue val;
        int     n = 0;
        for (int i=0; i < ipp.attrs.size(); i++)
        {
          a = (IPPAttribute)ipp.attrs.get(i);
          if (a.group_tag < 2)
            continue;

          if ((a.name.compareTo("printer-name") == 0) &&
              (a.value_tag == IPPDefs.TAG_NAME))
          {
            val = (IPPValue)a.values.get(0);
            if (val != null)
            {
              printers[n] = val.text;
              n++;
            }
          }
        }
        return( printers );

      }  // if doRequest ...

      return(null);

    }  // End of cupsGetPrinters




    /** 
     * Get default destination.
     *
     * @return	<code>String</code>	Name of default printer, or null.
     * @throw	<code>IOException</code>
     */
    public String cupsGetDefault() 
           throws IOException
    {

      IPPAttribute a;


      ipp = new IPP();
      //
      //  Fill in the required attributes
      //
      ipp.request = new IPPRequest( 1, (short)IPPDefs.CUPS_GET_DEFAULT);

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_CHARSET,
                            "attributes-charset" );
      a.addString( "", "iso-8859-1" );  
      ipp.addAttribute(a);
            

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_LANGUAGE,
                            "attributes-natural-language" );
      a.addString( "", "en" );  
      ipp.addAttribute(a);
            

      if (doRequest("cupsGetDefault"))
      {
        if ((ipp == null) || (ipp.attrs == null))
          return(null);

        int num_printers = 0;
        for (int i=0; i < ipp.attrs.size(); i++)
        {
          a = (IPPAttribute)ipp.attrs.get(i);
          if ((a.name.compareTo("printer-name") == 0) &&
              (a.value_tag == IPPDefs.TAG_NAME))
          {
            IPPValue val = (IPPValue)a.values.get(0);
            if (val != null)
            {
              return( val.text );
            }
          }
        }
      }  // if doRequest ...

      return(null);

    }  // End of cupsGetDefault






    /** 
     *  Get printer attributes
     *
     * @param	<code>printer_name</code>	Name of printer to get info for.
     * @return	<code>List</code>	        List of attributes.
     * @throw	<code>IOException</code>
     *
     * @see	<code>CupsPrinter</code>
     */
    public List cupsGetPrinterAttributes( String printer_name ) 
           throws IOException
    {

      IPPAttribute a;

      ipp = new IPP();

      //
      //  Fill in the required attributes
      //
      ipp.request = new IPPRequest( 1, (short)IPPDefs.GET_PRINTER_ATTRIBUTES );

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_CHARSET,
                            "attributes-charset" );
      a.addString( "", "iso-8859-1" );  
      ipp.addAttribute(a);
            

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_LANGUAGE,
                            "attributes-natural-language" );
      a.addString( "", "en" );  
      ipp.addAttribute(a);
            
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_URI,
                            "printer-uri" );
      a.addString( "", site + "/printers/" + printer_name );  
      ipp.addAttribute(a);

      if (doRequest("cupsGetPrinterAttributes"))
      {
        return(ipp.attrs);
      }  

      return(null);

    }  // End of cupsGetPrinterAttributes



    
    /** 
     *  Print a file.
     *
     *  @param	<code>p_filename</code>		Path of file to print.
     *	@param	<code>p_attrs[]</code>		Array of print job attributes.
     *
     *  @return	<code>CupsJob</code>		Object with job info.
     *
     *  @throw	<code>IOException</code>
     *
     *  @see	<code>CupsJob</code>
     */
    public CupsJob cupsPrintFile( String p_filename,
                                  IPPAttribute p_attrs[] )
           throws IOException
    {

      CupsJob      job;
      IPPAttribute a;
      File         file;


      file = new File(p_filename);
      if (!file.exists())
      {
        last_error = -1;
        error_text = "File does not exist.";
        return(null);
      }

      if (!file.canRead())
      {
        last_error = -1;
        error_text = "File cannot be read.";
        return(null);
      }


      ipp = new IPP();
      //
      //  Fill in the required attributes
      //
      ipp.request = new IPPRequest( 1, (short)IPPDefs.PRINT_JOB );

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_CHARSET,
                            "attributes-charset" );
      a.addString( "", "iso-8859-1" );  
      ipp.addAttribute(a);

      // ------------
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_LANGUAGE,
                            "attributes-natural-language" );
      a.addString( "", "en" );  
      ipp.addAttribute(a);
            
      // ------------
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_URI,
                            "printer-uri" );
      a.addString( "", site + dest );  
      ipp.addAttribute(a);

      // ------------
      // **FIX**  Fix this later.
/*
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_NAME,
                            "requesting-user-name" );
      // a.addString( "", p_username );  
      a.addString( "", "root");  
      ipp.addAttribute(a);
*/

      // ------------
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_NAME,
                            "job-name" );
      a.addString( "", file.getName() );  
      ipp.addAttribute(a);

      if (p_attrs != null)
      {
        for (int i=0; i < p_attrs.length; i++)
        {
          a = p_attrs[i];
          ipp.addAttribute(a);
        }
      }

      if (doRequest(file))
      {
        job = new CupsJob();
        for (int i=0; i < ipp.attrs.size(); i++)
        {
           a = (IPPAttribute)ipp.attrs.get(i);
           job.updateAttribute(a);
        }
        return(job);

      }  // if doRequest ...

      return(null);

    }  // End of cupsPrintFile





    /**
     *  Cancel a job - send a job cancel request to the server.
     *
     * @param	<code>printer_name</code>	Destination.
     * @param	<code>p_job_id</code>		ID of job.
     * @param	<code>p_user_name</code>	Requesting user name.		
     *
     * @throw	<code>IOException</code>
     */
    public int cupsCancelJob( String printer_name,
                              int    p_job_id,
                              String p_user_name ) 
           throws IOException
    {

      IPPAttribute a;

      ipp = new IPP();

      //
      //  Fill in the required attributes
      //
      ipp.request = new IPPRequest( 1, (short)IPPDefs.CANCEL_JOB );

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_CHARSET,
                            "attributes-charset" );
      a.addString( "", "iso-8859-1" );  
      ipp.addAttribute(a);

      // ------------
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_LANGUAGE,
                            "attributes-natural-language" );
      a.addString( "", "en" );  
      ipp.addAttribute(a);
            
      // ------------
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_URI,
                            "printer-uri" );
      a.addString( "", site + dest );  
      ipp.addAttribute(a);
            
      // ------------
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_INTEGER,
                            "job-id" );
      a.addInteger( p_job_id );  
      ipp.addAttribute(a);
            
      // ------------
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_NAME,
                            "requesting-user-name" );
      a.addString( "", p_user_name );  
      ipp.addAttribute(a);

      if (doRequest("cupsCancelJob"))
      {
        for (int i=0; i < ipp.attrs.size(); i++)
        {
          a = (IPPAttribute)ipp.attrs.get(i);
          a.dump_values();
        }
        return(0);

      }  // if doRequest ...

      return(0);

    }  // End of cupsCancelJob




  public List cupsGetPrinterStatus(String printer_name) 
	throws IOException
  {
      IPPAttribute a;
      String       p_uri;

      ipp = new IPP();

      //
      //  Fill in the required attributes
      //
      ipp.request = new IPPRequest(1,(short)IPPDefs.GET_PRINTER_ATTRIBUTES);

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_CHARSET,
                            "attributes-charset" );
      a.addString( "", "iso-8859-1" );  
      ipp.addAttribute(a);
            

      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_LANGUAGE,
                            "attributes-natural-language" );
      a.addString( "", "en" );  
      ipp.addAttribute(a);
            
      a = new IPPAttribute( IPPDefs.TAG_OPERATION, IPPDefs.TAG_URI,
                            "printer-uri" );
      p_uri = "ipp://" + address + ":" + 
              port + "/printers/" + printer_name;  
      a.addString( "", p_uri );
      ipp.addAttribute(a);
            
      if (doRequest("cupsGetPrinterStatus"))
      {
        return(ipp.attrs);
      } 
      return(null);
  }





}  // End of Cups class