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);
}
}
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);
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.
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);