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