Overview

The CUPS HTTP and IPP APIs provide low-level access to the HTTP and IPP protocols and CUPS scheduler. They are typically used by monitoring and administration programs to perform specific functions not supported by the high-level CUPS API functions.

The HTTP APIs use an opaque structure called http_t to manage connections to a particular HTTP or IPP server. The httpConnectEncrypt function is used to create an instance of this structure for a particular server. The constant CUPS_HTTP_DEFAULT can be used with all of the cups functions to refer to the default CUPS server - the functions create a per-thread http_t as needed.

The IPP APIs use two opaque structures for requests (messages sent to the CUPS scheduler) and responses (messages sent back to your application from the scheduler). The ipp_t type holds a complete request or response and is allocated using the ippNew or ippNewRequest functions and freed using the ippDelete function.

The second opaque structure is called ipp_attribute_t and holds a single IPP attribute which consists of a group tag (ippGetGroupTag), a value type tag (ippGetValueTag), the attribute name (ippGetName), and 1 or more values (ippGetCount, ippGetBoolean, ippGetCollection, ippGetDate, ippGetInteger, ippGetRange, ippGetResolution, and ippGetString). Attributes are added to an ipp_t pointer using one of the ippAdd functions. For example, use ippAddString to add the "printer-uri" and "requesting-user-name" string attributes to a request:

ipp_t *request = ippNewRequest(IPP_GET_JOBS);

ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
             NULL, "ipp://localhost/printers/");
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
             NULL, cupsUser());

Once you have created an IPP request, use the cups functions to send the request to and read the response from the server. For example, the cupsDoRequest function can be used for simple query operations that do not involve files:

#include <cups/cups.h>


ipp_t *get_jobs(void)
{
  ipp_t *request = ippNewRequest(IPP_GET_JOBS);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
               NULL, "ipp://localhost/printers/");
  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
               NULL, cupsUser());

  return (cupsDoRequest(CUPS_HTTP_DEFAULT, request, "/"));
}

The cupsDoRequest function frees the request and returns an IPP response or NULL pointer if the request could not be sent to the server. Once you have a response from the server, you can either use the ippFindAttribute and ippFindNextAttribute functions to find specific attributes, for example:

ipp_t *response;
ipp_attribute_t *attr;

attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM);

You can also walk the list of attributes with a simple for loop like this:

ipp_t *response;
ipp_attribute_t *attr;

for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response))
  if (ippGetName(attr) == NULL)
    puts("--SEPARATOR--");
  else
    puts(ippGetName(attr));

The for loop approach is normally used when collecting attributes for multiple objects (jobs, printers, etc.) in a response. Attributes with NULL names indicate a separator between the attributes of each object. For example, the following code will list the jobs returned from our previous get_jobs example code:

ipp_t *response = get_jobs();

if (response != NULL)
{
  ipp_attribute_t *attr;
  const char *attrname;
  int job_id = 0;
  const char *job_name = NULL;
  const char *job_originating_user_name = NULL;

  puts("Job ID  Owner             Title");
  puts("------  ----------------  ---------------------------------");

  for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response))
  {
   /* Attributes without names are separators between jobs */
    attrname = ippGetName(attr);
    if (attrname == NULL)
    {
      if (job_id > 0)
      {
        if (job_name == NULL)
          job_name = "(withheld)";

        if (job_originating_user_name == NULL)
          job_originating_user_name = "(withheld)";

        printf("%5d  %-16s  %s\n", job_id, job_originating_user_name, job_name);
      }

      job_id = 0;
      job_name = NULL;
      job_originating_user_name = NULL;
      continue;
    }
    else if (!strcmp(attrname, "job-id") && ippGetValueTag(attr) == IPP_TAG_INTEGER)
      job_id = ippGetInteger(attr, 0);
    else if (!strcmp(attrname, "job-name") && ippGetValueTag(attr) == IPP_TAG_NAME)
      job_name = ippGetString(attr, 0, NULL);
    else if (!strcmp(attrname, "job-originating-user-name") &&
             ippGetValueTag(attr) == IPP_TAG_NAME)
      job_originating_user_name = ippGetString(attr, 0, NULL);
  }

  if (job_id > 0)
  {
    if (job_name == NULL)
      job_name = "(withheld)";

    if (job_originating_user_name == NULL)
      job_originating_user_name = "(withheld)";

    printf("%5d  %-16s  %s\n", job_id, job_originating_user_name, job_name);
  }
}

Creating URI Strings

To ensure proper encoding, the httpAssembleURIf function must be used to format a "printer-uri" string for all printer-based requests:

const char *name = "Foo";
char uri[1024];
ipp_t *request;

httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, cupsServer(),
                 ippPort(), "/printers/%s", name);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);

Sending Requests with Files

The cupsDoFileRequest and cupsDoIORequest functions are used for requests involving files. The cupsDoFileRequest function attaches the named file to a request and is typically used when sending a print file or changing a printer's PPD file:

const char *filename = "/usr/share/cups/data/testprint.ps";
const char *name = "Foo";
char uri[1024];
char resource[1024];
ipp_t *request = ippNewRequest(IPP_PRINT_JOB);
ipp_t *response;

/* Use httpAssembleURIf for the printer-uri string */
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, cupsServer(),
                 ippPort(), "/printers/%s", name);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
             NULL, cupsUser());
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name",
             NULL, "testprint.ps");

/* Use snprintf for the resource path */
snprintf(resource, sizeof(resource), "/printers/%s", name);

response = cupsDoFileRequest(CUPS_HTTP_DEFAULT, request, resource, filename);

The cupsDoIORequest function optionally attaches a file to the request and optionally saves a file in the response from the server. It is used when using a pipe for the request attachment or when using a request that returns a file, currently only CUPS_GET_DOCUMENT and CUPS_GET_PPD. For example, the following code will download the PPD file for the sample HP LaserJet printer driver:

char tempfile[1024];
int tempfd;
ipp_t *request = ippNewRequest(CUPS_GET_PPD);
ipp_t *response;

ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "ppd-name",
             NULL, "laserjet.ppd");

tempfd = cupsTempFd(tempfile, sizeof(tempfile));

response = cupsDoIORequest(CUPS_HTTP_DEFAULT, request, "/", -1, tempfd);

The example passes -1 for the input file descriptor to specify that no file is to be attached to the request. The PPD file attached to the response is written to the temporary file descriptor we created using the cupsTempFd function.

Asynchronous Request Processing

The cupsSendRequest and cupsGetResponse support asynchronous communications with the server. Unlike the other request functions, the IPP request is not automatically freed, so remember to free your request with the ippDelete function.

File data is attached to the request using the cupsWriteRequestData function, while file data returned from the server is read using the cupsReadResponseData function. We can rewrite the previous CUPS_GET_PPD example to use the asynchronous functions quite easily:

char tempfile[1024];
int tempfd;
ipp_t *request = ippNewRequest(CUPS_GET_PPD);
ipp_t *response;

ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "ppd-name",
             NULL, "laserjet.ppd");

tempfd = cupsTempFd(tempfile, sizeof(tempfile));

if (cupsSendRequest(CUPS_HTTP_DEFAULT, request, "/") == HTTP_CONTINUE)
{
  response = cupsGetResponse(CUPS_HTTP_DEFAULT, "/");

  if (response != NULL)
  {
    ssize_t bytes;
    char buffer[8192];

    while ((bytes = cupsReadResponseData(CUPS_HTTP_DEFAULT, buffer, sizeof(buffer))) > 0)
      write(tempfd, buffer, bytes);
  }
}

/* Free the request! */
ippDelete(request);

The cupsSendRequest function returns the initial HTTP request status, typically either HTTP_CONTINUE or HTTP_UNAUTHORIZED. The latter status is returned when the request requires authentication of some sort. The cupsDoAuthentication function must be called when your see HTTP_UNAUTHORIZED and the request re-sent. We can add authentication support to our example code by using a do ... while loop:

char tempfile[1024];
int tempfd;
ipp_t *request = ippNewRequest(CUPS_GET_PPD);
ipp_t *response;
http_status_t status;

ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "ppd-name",
             NULL, "laserjet.ppd");

tempfd = cupsTempFd(tempfile, sizeof(tempfile));

/* Loop for authentication */
do
{
  status = cupsSendRequest(CUPS_HTTP_DEFAULT, request, "/");

  if (status == HTTP_UNAUTHORIZED)
  {
    /* Try to authenticate, break out of the loop if that fails */
    if (cupsDoAuthentication(CUPS_HTTP_DEFAULT, "POST", "/"))
      break;
  }
}
while (status != HTTP_CONTINUE && status != HTTP_UNAUTHORIZED);

if (status == HTTP_CONTINUE)
{
  response = cupsGetResponse(CUPS_HTTP_DEFAULT, "/");

  if (response != NULL)
  {
    ssize_t bytes;
    char buffer[8192];

    while ((bytes = cupsReadResponseData(CUPS_HTTP_DEFAULT, buffer, sizeof(buffer))) > 0)
      write(tempfd, buffer, bytes);
  }
}

/* Free the request! */
ippDelete(request);