#include <cups/cups-private.h>
#ifdef HAVE_DNSSD
# include <dns_sd.h>
#endif
#include <sys/stat.h>
#include <poll.h>
#ifdef HAVE_SYS_MOUNT_H
# include <sys/mount.h>
#endif
#ifdef HAVE_SYS_STATFS_H
# include <sys/statfs.h>
#endif
#ifdef HAVE_SYS_STATVFS_H
# include <sys/statvfs.h>
#endif
#ifdef HAVE_SYS_VFS_H
# include <sys/vfs.h>
#endif
enum _ipp_preasons_e
{
_IPP_PRINTER_NONE = 0x0000,
_IPP_PRINTER_OTHER = 0x0001,
_IPP_PRINTER_COVER_OPEN = 0x0002,
_IPP_PRINTER_INPUT_TRAY_MISSING = 0x0004,
_IPP_PRINTER_MARKER_SUPPLY_EMPTY = 0x0008,
_IPP_PRINTER_MARKER_SUPPLY_LOW = 0x0010,
_IPP_PRINTER_MARKER_WASTE_ALMOST_FULL = 0x0020,
_IPP_PRINTER_MARKER_WASTE_FULL = 0x0040,
_IPP_PRINTER_MEDIA_EMPTY = 0x0080,
_IPP_PRINTER_MEDIA_JAM = 0x0100,
_IPP_PRINTER_MEDIA_LOW = 0x0200,
_IPP_PRINTER_MEDIA_NEEDED = 0x0400,
_IPP_PRINTER_MOVING_TO_PAUSED = 0x0800,
_IPP_PRINTER_PAUSED = 0x1000,
_IPP_PRINTER_SPOOL_AREA_FULL = 0x2000,
_IPP_PRINTER_TONER_EMPTY = 0x4000,
_IPP_PRINTER_TONER_LOW = 0x8000
};
typedef unsigned int _ipp_preasons_t;
typedef enum _ipp_media_class_e
{
_IPP_GENERAL,
_IPP_PHOTO_ONLY,
_IPP_ENV_ONLY
} _ipp_media_class_t;
static const char * const media_supported[] =
{
"iso_a4_210x297mm",
"iso_a5_148x210mm",
"iso_a6_105x148mm",
"iso_dl_110x220mm",
"na_legal_8.5x14in",
"na_letter_8.5x11in",
"na_number-10_4.125x9.5in",
"na_index-3x5_3x5in",
"oe_photo-l_3.5x5in",
"na_index-4x6_4x6in",
"na_5x7_5x7in"
};
static const int media_col_sizes[][3] =
{
{ 21000, 29700, _IPP_GENERAL },
{ 14800, 21000, _IPP_PHOTO_ONLY },
{ 10500, 14800, _IPP_PHOTO_ONLY },
{ 11000, 22000, _IPP_ENV_ONLY },
{ 21590, 35560, _IPP_GENERAL },
{ 21590, 27940, _IPP_GENERAL },
{ 10477, 24130, _IPP_ENV_ONLY },
{ 7630, 12700, _IPP_PHOTO_ONLY },
{ 8890, 12700, _IPP_PHOTO_ONLY },
{ 10160, 15240, _IPP_PHOTO_ONLY },
{ 12700, 17780, _IPP_PHOTO_ONLY }
};
static const char * const media_type_supported[] =
{
"auto",
"cardstock",
"envelope",
"labels",
"other",
"photographic-glossy",
"photographic-high-gloss",
"photographic-matte",
"photographic-satin",
"photographic-semi-gloss",
"stationery",
"stationery-letterhead",
"transparency"
};
typedef struct _ipp_job_s _ipp_job_t;
typedef struct _ipp_printer_s
{
int ipv4,
ipv6;
#ifdef HAVE_DNSSD
DNSServiceRef common_ref,
ipp_ref,
http_ref,
printer_ref;
TXTRecordRef ipp_txt;
char *dnssd_name;
#endif
char *name,
*icon,
*directory,
*hostname,
*uri;
int port;
size_t urilen;
ipp_t *attrs;
ipp_pstate_t state;
_ipp_preasons_t state_reasons;
cups_array_t *jobs;
_ipp_job_t *active_job;
int next_job_id;
_cups_rwlock_t rwlock;
} _ipp_printer_t;
struct _ipp_job_s
{
int id;
char *name,
*username,
*format;
ipp_jstate_t state;
time_t processing,
completed;
ipp_t *attrs;
int cancel;
char *filename;
int fd;
_ipp_printer_t *printer;
};
typedef struct _ipp_client_s
{
http_t http;
ipp_t *request,
*response;
time_t start;
http_state_t operation;
ipp_op_t operation_id;
char uri[1024];
http_addr_t addr;
_ipp_printer_t *printer;
_ipp_job_t *job;
} _ipp_client_t;
static void clean_jobs(_ipp_printer_t *printer);
static int compare_jobs(_ipp_job_t *a, _ipp_job_t *b);
static void copy_attributes(ipp_t *to, ipp_t *from, cups_array_t *ra,
ipp_tag_t group_tag, int quickcopy);
static void copy_job_attributes(_ipp_client_t *client,
_ipp_job_t *job, cups_array_t *ra);
static _ipp_client_t *create_client(_ipp_printer_t *printer, int sock);
static _ipp_job_t *create_job(_ipp_client_t *client);
static int create_listener(int family, int *port);
static ipp_t *create_media_col(const char *media, const char *type,
int width, int length, int margins);
static ipp_t *create_media_size(int width, int length);
static _ipp_printer_t *create_printer(const char *servername,
const char *name, const char *location,
const char *make, const char *model,
const char *icon,
const char *docformats, int ppm,
int ppm_color, int duplex, int port,
#ifdef HAVE_DNSSD
const char *regtype,
#endif
const char *directory);
static cups_array_t *create_requested_array(_ipp_client_t *client);
static void debug_attributes(const char *title, ipp_t *ipp,
int response);
static void delete_client(_ipp_client_t *client);
static void delete_job(_ipp_job_t *job);
static void delete_printer(_ipp_printer_t *printer);
#ifdef HAVE_DNSSD
static void dnssd_callback(DNSServiceRef sdRef,
DNSServiceFlags flags,
DNSServiceErrorType errorCode,
const char *name,
const char *regtype,
const char *domain,
_ipp_printer_t *printer);
#endif
static _ipp_job_t *find_job(_ipp_client_t *client);
static void html_escape(_ipp_client_t *client, const char *s,
size_t slen);
static void html_printf(_ipp_client_t *client, const char *format,
...) __attribute__((__format__(__printf__,
2, 3)));
static void ipp_cancel_job(_ipp_client_t *client);
static void ipp_create_job(_ipp_client_t *client);
static void ipp_get_job_attributes(_ipp_client_t *client);
static void ipp_get_jobs(_ipp_client_t *client);
static void ipp_get_printer_attributes(_ipp_client_t *client);
static void ipp_print_job(_ipp_client_t *client);
static void ipp_print_uri(_ipp_client_t *client);
static void ipp_send_document(_ipp_client_t *client);
static void ipp_send_uri(_ipp_client_t *client);
static void ipp_validate_job(_ipp_client_t *client);
static void *process_client(_ipp_client_t *client);
static int process_http(_ipp_client_t *client);
static int process_ipp(_ipp_client_t *client);
static void *process_job(_ipp_job_t *job);
#ifdef HAVE_DNSSD
static int register_printer(_ipp_printer_t *printer,
const char *location, const char *make,
const char *model, const char *formats,
const char *adminurl, int color,
int duplex, const char *regtype);
#endif
static int respond_http(_ipp_client_t *client, http_status_t code,
const char *type, size_t length);
static void respond_ipp(_ipp_client_t *client, ipp_status_t status,
const char *message, ...)
__attribute__ ((__format__ (__printf__, 3, 4)));
static void respond_unsupported(_ipp_client_t *client,
ipp_attribute_t *attr);
static void run_printer(_ipp_printer_t *printer);
static void usage(int status) __attribute__((noreturn));
static int valid_doc_attributes(_ipp_client_t *client);
static int valid_job_attributes(_ipp_client_t *client);
static int KeepFiles = 0,
Verbosity = 0;
int
main(int argc,
char *argv[])
{
int i;
const char *opt,
*servername = NULL,
*name = NULL,
*location = "",
*make = "Test",
*model = "Printer",
*icon = "printer.png",
*formats = "application/pdf,image/jpeg";
#ifdef HAVE_DNSSD
const char *regtype = "_ipp._tcp";
#endif
int port = 8631,
duplex = 0,
ppm = 10,
ppm_color = 0;
char directory[1024] = "";
_ipp_printer_t *printer;
for (i = 1; i < argc; i ++)
if (argv[i][0] == '-')
{
for (opt = argv[i] + 1; *opt; opt ++)
switch (*opt)
{
case '2' :
duplex = 1;
break;
case 'M' :
i ++;
if (i >= argc)
usage(1);
make = argv[i];
break;
case 'd' :
i ++;
if (i >= argc)
usage(1);
strlcpy(directory, argv[i], sizeof(directory));
break;
case 'f' :
i ++;
if (i >= argc)
usage(1);
formats = argv[i];
break;
case 'h' :
usage(0);
break;
case 'i' :
i ++;
if (i >= argc)
usage(1);
icon = argv[i];
break;
case 'k' :
KeepFiles = 1;
break;
case 'l' :
i ++;
if (i >= argc)
usage(1);
location = argv[i];
break;
case 'm' :
i ++;
if (i >= argc)
usage(1);
model = argv[i];
break;
case 'n' :
i ++;
if (i >= argc)
usage(1);
servername = argv[i];
break;
case 'p' :
i ++;
if (i >= argc || !isdigit(argv[i][0] & 255))
usage(1);
port = atoi(argv[i]);
break;
#ifdef HAVE_DNSSD
case 'r' :
i ++;
if (i >= argc)
usage(1);
regtype = argv[i];
break;
#endif
case 's' :
i ++;
if (i >= argc)
usage(1);
if (sscanf(argv[i], "%d,%d", &ppm, &ppm_color) < 1)
usage(1);
break;
case 'v' :
Verbosity ++;
break;
default :
fprintf(stderr, "Unknown option \"-%c\".\n", *opt);
usage(1);
break;
}
}
else if (!name)
{
name = argv[i];
}
else
{
fprintf(stderr, "Unexpected command-line argument \"%s\"\n", argv[i]);
usage(1);
}
if (!name)
usage(1);
if (!directory[0])
{
snprintf(directory, sizeof(directory), "/tmp/ippserver.%d", (int)getpid());
if (mkdir(directory, 0777) && errno != EEXIST)
{
fprintf(stderr, "Unable to create spool directory \"%s\": %s\n",
directory, strerror(errno));
usage(1);
}
if (Verbosity)
fprintf(stderr, "Using spool directory \"%s\".\n", directory);
}
if ((printer = create_printer(servername, name, location, make, model, icon,
formats, ppm, ppm_color, duplex, port,
#ifdef HAVE_DNSSD
regtype,
#endif
directory)) == NULL)
return (1);
run_printer(printer);
delete_printer(printer);
return (0);
}
static void
clean_jobs(_ipp_printer_t *printer)
{
_ipp_job_t *job;
time_t cleantime;
if (cupsArrayCount(printer->jobs) == 0)
return;
cleantime = time(NULL) - 60;
_cupsRWLockWrite(&(printer->rwlock));
for (job = (_ipp_job_t *)cupsArrayFirst(printer->jobs);
job;
job = (_ipp_job_t *)cupsArrayNext(printer->jobs))
if (job->completed && job->completed < cleantime)
{
cupsArrayRemove(printer->jobs, job);
delete_job(job);
}
else
break;
_cupsRWUnlock(&(printer->rwlock));
}
static int
compare_jobs(_ipp_job_t *a,
_ipp_job_t *b)
{
return (b->id - a->id);
}
static void
copy_attributes(ipp_t *to,
ipp_t *from,
cups_array_t *ra,
ipp_tag_t group_tag,
int quickcopy)
{
ipp_attribute_t *fromattr;
if (!to || !from)
return;
for (fromattr = from->attrs; fromattr; fromattr = fromattr->next)
{
if ((group_tag != IPP_TAG_ZERO && fromattr->group_tag != group_tag &&
fromattr->group_tag != IPP_TAG_ZERO) || !fromattr->name)
continue;
if (!ra || cupsArrayFind(ra, fromattr->name))
ippCopyAttribute(to, fromattr, quickcopy);
}
}
static void
copy_job_attributes(
_ipp_client_t *client,
_ipp_job_t *job,
cups_array_t *ra)
{
copy_attributes(client->response, job->attrs, ra, IPP_TAG_JOB, 0);
if (!ra || cupsArrayFind(ra, "job-printer-up-time"))
ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER,
"job-printer-up-time", (int)time(NULL));
if (!ra || cupsArrayFind(ra, "job-state"))
ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_ENUM,
"job-state", job->state);
if (!ra || cupsArrayFind(ra, "job-state-reasons"))
{
switch (job->state)
{
case IPP_JOB_PENDING :
ippAddString(client->response, IPP_TAG_JOB,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
NULL, "none");
break;
case IPP_JOB_HELD :
if (job->fd >= 0)
ippAddString(client->response, IPP_TAG_JOB,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
NULL, "job-incoming");
else if (ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_ZERO))
ippAddString(client->response, IPP_TAG_JOB,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
NULL, "job-hold-until-specified");
else
ippAddString(client->response, IPP_TAG_JOB,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
NULL, "job-data-insufficient");
break;
case IPP_JOB_PROCESSING :
if (job->cancel)
ippAddString(client->response, IPP_TAG_JOB,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
NULL, "processing-to-stop-point");
else
ippAddString(client->response, IPP_TAG_JOB,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
NULL, "job-printing");
break;
case IPP_JOB_STOPPED :
ippAddString(client->response, IPP_TAG_JOB,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
NULL, "job-stopped");
break;
case IPP_JOB_CANCELED :
ippAddString(client->response, IPP_TAG_JOB,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
NULL, "job-canceled-by-user");
break;
case IPP_JOB_ABORTED :
ippAddString(client->response, IPP_TAG_JOB,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
NULL, "aborted-by-system");
break;
case IPP_JOB_COMPLETED :
ippAddString(client->response, IPP_TAG_JOB,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
NULL, "job-completed-successfully");
break;
}
}
if (!ra || cupsArrayFind(ra, "time-at-completed"))
ippAddInteger(client->response, IPP_TAG_JOB,
job->completed ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE,
"time-at-completed", job->completed);
if (!ra || cupsArrayFind(ra, "time-at-processing"))
ippAddInteger(client->response, IPP_TAG_JOB,
job->processing ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE,
"time-at-processing", job->processing);
}
static _ipp_client_t *
create_client(_ipp_printer_t *printer,
int sock)
{
_ipp_client_t *client;
int val;
socklen_t addrlen;
if ((client = calloc(1, sizeof(_ipp_client_t))) == NULL)
{
perror("Unable to allocate memory for client");
return (NULL);
}
client->printer = printer;
client->http.activity = time(NULL);
client->http.hostaddr = &(client->addr);
client->http.blocking = 1;
client->http.wait_value = 60000;
addrlen = sizeof(http_addr_t);
if ((client->http.fd = accept(sock, (struct sockaddr *)&(client->addr),
&addrlen)) < 0)
{
perror("Unable to accept client connection");
free(client);
return (NULL);
}
httpAddrString(&(client->addr), client->http.hostname,
sizeof(client->http.hostname));
if (Verbosity)
fprintf(stderr, "Accepted connection from %s (%s)\n", client->http.hostname,
client->http.hostaddr->addr.sa_family == AF_INET ? "IPv4" : "IPv6");
val = 1;
setsockopt(client->http.fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val,
sizeof(val));
return (client);
}
static _ipp_job_t *
create_job(_ipp_client_t *client)
{
_ipp_job_t *job;
ipp_attribute_t *attr;
char uri[1024];
_cupsRWLockWrite(&(client->printer->rwlock));
if (client->printer->active_job &&
client->printer->active_job->state < IPP_JOB_CANCELED)
{
_cupsRWLockWrite(&(client->printer->rwlock));
return (NULL);
}
if ((job = calloc(1, sizeof(_ipp_job_t))) == NULL)
{
perror("Unable to allocate memory for job");
return (NULL);
}
job->printer = client->printer;
job->attrs = client->request;
job->state = IPP_JOB_HELD;
job->fd = -1;
client->request = NULL;
for (attr = job->attrs->attrs->next->next; attr; attr = attr->next)
attr->group_tag = IPP_TAG_JOB;
if ((attr = ippFindAttribute(job->attrs, "requesting-user-name",
IPP_TAG_NAME)) != NULL)
{
_cupsStrFree(attr->name);
attr->name = _cupsStrAlloc("job-originating-user-name");
}
else
attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME | IPP_TAG_COPY,
"job-originating-user-name", NULL, "anonymous");
if (attr)
job->username = attr->values[0].string.text;
else
job->username = "anonymous";
if ((attr = ippFindAttribute(job->attrs, "document-format",
IPP_TAG_MIMETYPE)) != NULL)
job->format = attr->values[0].string.text;
else
job->format = "application/octet-stream";
job->id = client->printer->next_job_id ++;
snprintf(uri, sizeof(uri), "%s/%d", client->printer->uri, job->id);
ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);
ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, uri);
ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL,
client->printer->uri);
ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation",
(int)time(NULL));
cupsArrayAdd(client->printer->jobs, job);
client->printer->active_job = job;
_cupsRWUnlock(&(client->printer->rwlock));
return (job);
}
static int
create_listener(int family,
int *port)
{
int sock,
val;
http_addr_t address;
socklen_t addrlen;
if ((sock = socket(family, SOCK_STREAM, 0)) < 0)
return (-1);
val = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
#ifdef IPV6_V6ONLY
if (family == AF_INET6)
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
#endif
if (!*port)
{
addrlen = sizeof(address);
if (getsockname(sock, (struct sockaddr *)&address, &addrlen))
{
perror("getsockname() failed");
*port = 8631;
}
else
*port = _httpAddrPort(&address);
fprintf(stderr, "Listening on port %d.\n", *port);
}
memset(&address, 0, sizeof(address));
address.addr.sa_family = family;
_httpAddrSetPort(&address, *port);
if (bind(sock, (struct sockaddr *)&address, httpAddrLength(&address)))
{
close(sock);
return (-1);
}
if (listen(sock, 5))
{
close(sock);
return (-1);
}
return (sock);
}
static ipp_t *
create_media_col(const char *media,
const char *type,
int width,
int length,
int margins)
{
ipp_t *media_col = ippNew(),
*media_size = create_media_size(width, length);
char media_key[256];
snprintf(media_key, sizeof(media_key), "%s_%s%s", media, type,
margins == 0 ? "_borderless" : "");
ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-key", NULL,
media_key);
ippAddCollection(media_col, IPP_TAG_PRINTER, "media-size", media_size);
ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"media-bottom-margin", margins);
ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"media-left-margin", margins);
ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"media-right-margin", margins);
ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"media-top-margin", margins);
ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type",
NULL, type);
ippDelete(media_size);
return (media_col);
}
static ipp_t *
create_media_size(int width,
int length)
{
ipp_t *media_size = ippNew();
ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "x-dimension",
width);
ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "y-dimension",
length);
return (media_size);
}
static _ipp_printer_t *
create_printer(const char *servername,
const char *name,
const char *location,
const char *make,
const char *model,
const char *icon,
const char *docformats,
int ppm,
int ppm_color,
int duplex,
int port,
#ifdef HAVE_DNSSD
const char *regtype,
#endif
const char *directory)
{
int i, j;
_ipp_printer_t *printer;
char hostname[256],
uri[1024],
icons[1024],
adminurl[1024],
device_id[1024],
make_model[128];
int num_formats;
char *defformat,
*formats[100],
*ptr;
const char *prefix;
int num_database;
ipp_attribute_t *media_col_database,
*media_size_supported;
ipp_t *media_col_default;
_ipp_value_t *media_col_value;
int k_supported;
#ifdef HAVE_STATVFS
struct statvfs spoolinfo;
double spoolsize;
#elif defined(HAVE_STATFS)
struct statfs spoolinfo;
double spoolsize;
#endif
static const int orients[4] =
{
IPP_PORTRAIT,
IPP_LANDSCAPE,
IPP_REVERSE_LANDSCAPE,
IPP_REVERSE_PORTRAIT
};
static const char * const versions[] =
{
"1.0",
"1.1",
"2.0"
};
static const int ops[] =
{
IPP_PRINT_JOB,
IPP_PRINT_URI,
IPP_VALIDATE_JOB,
IPP_CREATE_JOB,
IPP_SEND_DOCUMENT,
IPP_SEND_URI,
IPP_CANCEL_JOB,
IPP_GET_JOB_ATTRIBUTES,
IPP_GET_JOBS,
IPP_GET_PRINTER_ATTRIBUTES
};
static const char * const charsets[] =
{
"us-ascii",
"utf-8"
};
static const char * const job_creation[] =
{
"copies",
"ipp-attribute-fidelity",
"job-name",
"job-priority",
"media",
"media-col",
"multiple-document-handling",
"orientation-requested",
"print-quality",
"sides"
};
static const char * const media_col_supported[] =
{
"media-bottom-margin",
"media-left-margin",
"media-right-margin",
"media-size",
"media-top-margin",
"media-type"
};
static const int media_xxx_margin_supported[] =
{
0,
635
};
static const char * const multiple_document_handling[] =
{
"separate-documents-uncollated-copies",
"separate-documents-collated-copies"
};
static const int print_quality_supported[] =
{
IPP_QUALITY_DRAFT,
IPP_QUALITY_NORMAL,
IPP_QUALITY_HIGH
};
static const char * const reference_uri_schemes_supported[] =
{
"file",
"ftp",
"http"
#ifdef HAVE_SSL
, "https"
#endif
};
static const char * const sides_supported[] =
{
"one-sided",
"two-sided-long-edge",
"two-sided-short-edge"
};
static const char * const which_jobs[] =
{
"completed",
"not-completed",
"aborted",
"all",
"canceled",
"pending",
"pending-held",
"processing",
"processing-stopped"
};
if ((printer = calloc(1, sizeof(_ipp_printer_t))) == NULL)
{
perror("Unable to allocate memory for printer");
return (NULL);
}
printer->ipv4 = -1;
printer->ipv6 = -1;
printer->name = _cupsStrAlloc(name);
#ifdef HAVE_DNSSD
printer->dnssd_name = _cupsStrRetain(printer->name);
#endif
printer->directory = _cupsStrAlloc(directory);
printer->hostname = _cupsStrAlloc(servername ? servername :
httpGetHostname(NULL, hostname,
sizeof(hostname)));
printer->port = port;
printer->state = IPP_PRINTER_IDLE;
printer->state_reasons = _IPP_PRINTER_NONE;
printer->jobs = cupsArrayNew((cups_array_func_t)compare_jobs, NULL);
printer->next_job_id = 1;
httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
printer->hostname, printer->port, "/ipp");
printer->uri = _cupsStrAlloc(uri);
printer->urilen = strlen(uri);
_cupsRWInit(&(printer->rwlock));
if ((printer->ipv4 = create_listener(AF_INET, &(printer->port))) < 0)
{
perror("Unable to create IPv4 listener");
goto bad_printer;
}
if ((printer->ipv6 = create_listener(AF_INET6, &(printer->port))) < 0)
{
perror("Unable to create IPv6 listener");
goto bad_printer;
}
httpAssembleURI(HTTP_URI_CODING_ALL, icons, sizeof(icons), "http", NULL,
printer->hostname, printer->port, "/icon.png");
httpAssembleURI(HTTP_URI_CODING_ALL, adminurl, sizeof(adminurl), "http", NULL,
printer->hostname, printer->port, "/");
if (Verbosity)
{
fprintf(stderr, "printer-more-info=\"%s\"\n", adminurl);
fprintf(stderr, "printer-uri=\"%s\"\n", uri);
}
snprintf(make_model, sizeof(make_model), "%s %s", make, model);
num_formats = 1;
formats[0] = strdup(docformats);
defformat = formats[0];
for (ptr = strchr(formats[0], ','); ptr; ptr = strchr(ptr, ','))
{
*ptr++ = '\0';
formats[num_formats++] = ptr;
if (!_cups_strcasecmp(ptr, "application/octet-stream"))
defformat = ptr;
}
snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;", make, model);
ptr = device_id + strlen(device_id);
prefix = "CMD:";
for (i = 0; i < num_formats; i ++)
{
if (!_cups_strcasecmp(formats[i], "application/pdf"))
snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPDF", prefix);
else if (!_cups_strcasecmp(formats[i], "application/postscript"))
snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPS", prefix);
else if (!_cups_strcasecmp(formats[i], "application/vnd.hp-PCL"))
snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPCL", prefix);
else if (!_cups_strcasecmp(formats[i], "image/jpeg"))
snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sJPEG", prefix);
else if (!_cups_strcasecmp(formats[i], "image/png"))
snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPNG", prefix);
else if (_cups_strcasecmp(formats[i], "application/octet-stream"))
snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%s%s", prefix,
formats[i]);
ptr += strlen(ptr);
prefix = ",";
}
strlcat(device_id, ";", sizeof(device_id));
#ifdef HAVE_STATVFS
if (statvfs(printer->directory, &spoolinfo))
k_supported = INT_MAX;
else if ((spoolsize = (double)spoolinfo.f_frsize *
spoolinfo.f_blocks / 1024) > INT_MAX)
k_supported = INT_MAX;
else
k_supported = (int)spoolsize;
#elif defined(HAVE_STATFS)
if (statfs(printer->directory, &spoolinfo))
k_supported = INT_MAX;
else if ((spoolsize = (double)spoolinfo.f_bsize *
spoolinfo.f_blocks / 1024) > INT_MAX)
k_supported = INT_MAX;
else
k_supported = (int)spoolsize;
#else
k_supported = INT_MAX;
#endif
printer->attrs = ippNew();
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY,
"charset-configured", NULL, "utf-8");
ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY,
"charset-supported", sizeof(charsets) / sizeof(charsets[0]),
NULL, charsets);
ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "color-supported",
ppm_color > 0);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"compression-supported", NULL, "none");
ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"copies-default", 1);
ippAddRange(printer->attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE,
"document-format-default", NULL, defformat);
ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE,
"document-format-supported", num_formats, NULL,
(const char * const *)formats);
ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
"finishings-default", IPP_FINISHINGS_NONE);
ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
"finishings-supported", IPP_FINISHINGS_NONE);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY,
"generated-natural-language-supported", NULL, "en");
ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"ipp-versions-supported",
sizeof(versions) / sizeof(versions[0]), NULL, versions);
ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"job-creation-attributes-supported",
sizeof(job_creation) / sizeof(job_creation[0]),
NULL, job_creation);
ippAddRange(printer->attrs, IPP_TAG_PRINTER, "job-k-octets-supported", 0,
k_supported);
ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"job-priority-default", 50);
ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"job-priority-supported", 100);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY,
"job-sheets-default", NULL, "none");
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY,
"job-sheets-supported", NULL, "none");
ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"media-bottom-margin-supported",
(int)(sizeof(media_xxx_margin_supported) /
sizeof(media_xxx_margin_supported[0])),
media_xxx_margin_supported);
for (num_database = 0, i = 0;
i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
i ++)
{
if (media_col_sizes[i][2] == _IPP_ENV_ONLY)
num_database += 2;
else if (media_col_sizes[i][2] == _IPP_PHOTO_ONLY)
num_database += 12;
else
num_database += (int)(sizeof(media_type_supported) /
sizeof(media_type_supported[0])) + 6;
}
media_col_database = ippAddCollections(printer->attrs, IPP_TAG_PRINTER,
"media-col-database", num_database,
NULL);
for (media_col_value = media_col_database->values, i = 0;
i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
i ++)
{
for (j = 0;
j < (int)(sizeof(media_type_supported) /
sizeof(media_type_supported[0]));
j ++)
{
if (media_col_sizes[i][2] == _IPP_ENV_ONLY &&
strcmp(media_type_supported[j], "auto") &&
strcmp(media_type_supported[j], "envelope"))
continue;
else if (media_col_sizes[i][2] == _IPP_PHOTO_ONLY &&
strcmp(media_type_supported[j], "auto") &&
strncmp(media_type_supported[j], "photographic-", 13))
continue;
media_col_value->collection =
create_media_col(media_supported[i], media_type_supported[j],
media_col_sizes[i][0], media_col_sizes[i][1],
media_xxx_margin_supported[1]);
media_col_value ++;
if (media_col_sizes[i][2] != _IPP_ENV_ONLY &&
(!strcmp(media_type_supported[j], "auto") ||
!strncmp(media_type_supported[j], "photographic-", 13)))
{
media_col_value->collection =
create_media_col(media_supported[i], media_type_supported[j],
media_col_sizes[i][0], media_col_sizes[i][1],
media_xxx_margin_supported[0]);
media_col_value ++;
}
}
}
media_col_default = create_media_col(media_supported[0],
media_type_supported[0],
media_col_sizes[0][0],
media_col_sizes[0][1],
media_xxx_margin_supported[1]);
ippAddCollection(printer->attrs, IPP_TAG_PRINTER, "media-col-default",
media_col_default);
ippDelete(media_col_default);
ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"media-col-supported",
(int)(sizeof(media_col_supported) /
sizeof(media_col_supported[0])), NULL,
media_col_supported);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"media-default", NULL, media_supported[0]);
ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"media-left-margin-supported",
(int)(sizeof(media_xxx_margin_supported) /
sizeof(media_xxx_margin_supported[0])),
media_xxx_margin_supported);
ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"media-right-margin-supported",
(int)(sizeof(media_xxx_margin_supported) /
sizeof(media_xxx_margin_supported[0])),
media_xxx_margin_supported);
ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"media-supported",
(int)(sizeof(media_supported) / sizeof(media_supported[0])),
NULL, media_supported);
media_size_supported = ippAddCollections(printer->attrs, IPP_TAG_PRINTER,
"media-size-supported",
(int)(sizeof(media_col_sizes) /
sizeof(media_col_sizes[0])),
NULL);
for (i = 0;
i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
i ++)
media_size_supported->values[i].collection =
create_media_size(media_col_sizes[i][0], media_col_sizes[i][1]);
ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"media-top-margin-supported",
(int)(sizeof(media_xxx_margin_supported) /
sizeof(media_xxx_margin_supported[0])),
media_xxx_margin_supported);
ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"media-type-supported",
(int)(sizeof(media_type_supported) /
sizeof(media_type_supported[0])),
NULL, media_type_supported);
ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"multiple-document-handling-supported",
sizeof(multiple_document_handling) /
sizeof(multiple_document_handling[0]), NULL,
multiple_document_handling);
ippAddBoolean(printer->attrs, IPP_TAG_PRINTER,
"multiple-document-jobs-supported", 0);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY,
"natural-language-configured", NULL, "en");
ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"number-up-default", 1);
ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"number-up-supported", 1);
ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
"operations-supported", sizeof(ops) / sizeof(ops[0]), ops);
ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE,
"orientation-requested-default", 0);
ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
"orientation-requested-supported", 4, orients);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"output-bin-default", NULL, "face-down");
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"output-bin-supported", NULL, "face-down");
ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"pages-per-minute", ppm);
if (ppm_color > 0)
ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"pages-per-minute-color", ppm_color);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"pdl-override-supported", NULL, "attempted");
ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
"print-quality-default", IPP_QUALITY_NORMAL);
ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
"print-quality-supported",
(int)(sizeof(print_quality_supported) /
sizeof(print_quality_supported[0])),
print_quality_supported);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
"printer-device-id", NULL, device_id);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI,
"printer-icons", NULL, icons);
ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "printer-is-accepting-jobs",
1);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info",
NULL, name);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
"printer-location", NULL, location);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
"printer-make-and-model", NULL, make_model);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI,
"printer-more-info", NULL, adminurl);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name",
NULL, name);
ippAddResolution(printer->attrs, IPP_TAG_PRINTER,
"printer-resolution-default", IPP_RES_PER_INCH, 600, 600);
ippAddResolution(printer->attrs, IPP_TAG_PRINTER,
"printer-resolution-supported", IPP_RES_PER_INCH, 600, 600);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI,
"printer-uri-supported", NULL, uri);
ippAddStrings(printer->attrs, IPP_TAG_PRINTER,
IPP_TAG_URISCHEME | IPP_TAG_COPY,
"reference-uri-schemes-supported",
(int)(sizeof(reference_uri_schemes_supported) /
sizeof(reference_uri_schemes_supported[0])),
NULL, reference_uri_schemes_supported);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"sides-default", NULL, "one-sided");
ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"sides-supported", duplex ? 3 : 1, NULL, sides_supported);
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"uri-authentication-supported", NULL, "none");
ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"uri-security-supported", NULL, "none");
ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
"which-jobs-supported",
sizeof(which_jobs) / sizeof(which_jobs[0]), NULL, which_jobs);
free(formats[0]);
debug_attributes("Printer", printer->attrs, 0);
#ifdef HAVE_DNSSD
if (!register_printer(printer, location, make, model, docformats, adminurl,
ppm_color > 0, duplex, regtype))
goto bad_printer;
#endif
return (printer);
bad_printer:
delete_printer(printer);
return (NULL);
}
static cups_array_t *
create_requested_array(
_ipp_client_t *client)
{
int i;
ipp_attribute_t *requested;
cups_array_t *ra;
char *value;
if ((requested = ippFindAttribute(client->request, "requested-attributes",
IPP_TAG_KEYWORD)) == NULL)
return (NULL);
if (requested->num_values == 1 &&
!strcmp(requested->values[0].string.text, "all"))
return (NULL);
ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
for (i = 0; i < requested->num_values; i ++)
{
value = requested->values[i].string.text;
if (!strcmp(value, "job-template"))
{
cupsArrayAdd(ra, "copies");
cupsArrayAdd(ra, "copies-default");
cupsArrayAdd(ra, "copies-supported");
cupsArrayAdd(ra, "finishings");
cupsArrayAdd(ra, "finishings-default");
cupsArrayAdd(ra, "finishings-supported");
cupsArrayAdd(ra, "job-hold-until");
cupsArrayAdd(ra, "job-hold-until-default");
cupsArrayAdd(ra, "job-hold-until-supported");
cupsArrayAdd(ra, "job-priority");
cupsArrayAdd(ra, "job-priority-default");
cupsArrayAdd(ra, "job-priority-supported");
cupsArrayAdd(ra, "job-sheets");
cupsArrayAdd(ra, "job-sheets-default");
cupsArrayAdd(ra, "job-sheets-supported");
cupsArrayAdd(ra, "media");
cupsArrayAdd(ra, "media-col");
cupsArrayAdd(ra, "media-col-default");
cupsArrayAdd(ra, "media-col-supported");
cupsArrayAdd(ra, "media-default");
cupsArrayAdd(ra, "media-source-supported");
cupsArrayAdd(ra, "media-supported");
cupsArrayAdd(ra, "media-type-supported");
cupsArrayAdd(ra, "multiple-document-handling");
cupsArrayAdd(ra, "multiple-document-handling-default");
cupsArrayAdd(ra, "multiple-document-handling-supported");
cupsArrayAdd(ra, "number-up");
cupsArrayAdd(ra, "number-up-default");
cupsArrayAdd(ra, "number-up-supported");
cupsArrayAdd(ra, "orientation-requested");
cupsArrayAdd(ra, "orientation-requested-default");
cupsArrayAdd(ra, "orientation-requested-supported");
cupsArrayAdd(ra, "page-ranges");
cupsArrayAdd(ra, "page-ranges-supported");
cupsArrayAdd(ra, "printer-resolution");
cupsArrayAdd(ra, "printer-resolution-default");
cupsArrayAdd(ra, "printer-resolution-supported");
cupsArrayAdd(ra, "print-quality");
cupsArrayAdd(ra, "print-quality-default");
cupsArrayAdd(ra, "print-quality-supported");
cupsArrayAdd(ra, "sides");
cupsArrayAdd(ra, "sides-default");
cupsArrayAdd(ra, "sides-supported");
}
else if (!strcmp(value, "job-description"))
{
cupsArrayAdd(ra, "date-time-at-completed");
cupsArrayAdd(ra, "date-time-at-creation");
cupsArrayAdd(ra, "date-time-at-processing");
cupsArrayAdd(ra, "job-detailed-status-message");
cupsArrayAdd(ra, "job-document-access-errors");
cupsArrayAdd(ra, "job-id");
cupsArrayAdd(ra, "job-impressions");
cupsArrayAdd(ra, "job-impressions-completed");
cupsArrayAdd(ra, "job-k-octets");
cupsArrayAdd(ra, "job-k-octets-processed");
cupsArrayAdd(ra, "job-media-sheets");
cupsArrayAdd(ra, "job-media-sheets-completed");
cupsArrayAdd(ra, "job-message-from-operator");
cupsArrayAdd(ra, "job-more-info");
cupsArrayAdd(ra, "job-name");
cupsArrayAdd(ra, "job-originating-user-name");
cupsArrayAdd(ra, "job-printer-up-time");
cupsArrayAdd(ra, "job-printer-uri");
cupsArrayAdd(ra, "job-state");
cupsArrayAdd(ra, "job-state-message");
cupsArrayAdd(ra, "job-state-reasons");
cupsArrayAdd(ra, "job-uri");
cupsArrayAdd(ra, "number-of-documents");
cupsArrayAdd(ra, "number-of-intervening-jobs");
cupsArrayAdd(ra, "output-device-assigned");
cupsArrayAdd(ra, "time-at-completed");
cupsArrayAdd(ra, "time-at-creation");
cupsArrayAdd(ra, "time-at-processing");
}
else if (!strcmp(value, "printer-description"))
{
cupsArrayAdd(ra, "charset-configured");
cupsArrayAdd(ra, "charset-supported");
cupsArrayAdd(ra, "color-supported");
cupsArrayAdd(ra, "compression-supported");
cupsArrayAdd(ra, "document-format-default");
cupsArrayAdd(ra, "document-format-supported");
cupsArrayAdd(ra, "generated-natural-language-supported");
cupsArrayAdd(ra, "ipp-versions-supported");
cupsArrayAdd(ra, "job-impressions-supported");
cupsArrayAdd(ra, "job-k-octets-supported");
cupsArrayAdd(ra, "job-media-sheets-supported");
cupsArrayAdd(ra, "multiple-document-jobs-supported");
cupsArrayAdd(ra, "multiple-operation-time-out");
cupsArrayAdd(ra, "natural-language-configured");
cupsArrayAdd(ra, "notify-attributes-supported");
cupsArrayAdd(ra, "notify-lease-duration-default");
cupsArrayAdd(ra, "notify-lease-duration-supported");
cupsArrayAdd(ra, "notify-max-events-supported");
cupsArrayAdd(ra, "notify-events-default");
cupsArrayAdd(ra, "notify-events-supported");
cupsArrayAdd(ra, "notify-pull-method-supported");
cupsArrayAdd(ra, "notify-schemes-supported");
cupsArrayAdd(ra, "operations-supported");
cupsArrayAdd(ra, "pages-per-minute");
cupsArrayAdd(ra, "pages-per-minute-color");
cupsArrayAdd(ra, "pdl-override-supported");
cupsArrayAdd(ra, "printer-alert");
cupsArrayAdd(ra, "printer-alert-description");
cupsArrayAdd(ra, "printer-current-time");
cupsArrayAdd(ra, "printer-driver-installer");
cupsArrayAdd(ra, "printer-info");
cupsArrayAdd(ra, "printer-is-accepting-jobs");
cupsArrayAdd(ra, "printer-location");
cupsArrayAdd(ra, "printer-make-and-model");
cupsArrayAdd(ra, "printer-message-from-operator");
cupsArrayAdd(ra, "printer-more-info");
cupsArrayAdd(ra, "printer-more-info-manufacturer");
cupsArrayAdd(ra, "printer-name");
cupsArrayAdd(ra, "printer-state");
cupsArrayAdd(ra, "printer-state-message");
cupsArrayAdd(ra, "printer-state-reasons");
cupsArrayAdd(ra, "printer-up-time");
cupsArrayAdd(ra, "printer-uri-supported");
cupsArrayAdd(ra, "queued-job-count");
cupsArrayAdd(ra, "reference-uri-schemes-supported");
cupsArrayAdd(ra, "uri-authentication-supported");
cupsArrayAdd(ra, "uri-security-supported");
}
else if (!strcmp(value, "printer-defaults"))
{
cupsArrayAdd(ra, "copies-default");
cupsArrayAdd(ra, "document-format-default");
cupsArrayAdd(ra, "finishings-default");
cupsArrayAdd(ra, "job-hold-until-default");
cupsArrayAdd(ra, "job-priority-default");
cupsArrayAdd(ra, "job-sheets-default");
cupsArrayAdd(ra, "media-default");
cupsArrayAdd(ra, "media-col-default");
cupsArrayAdd(ra, "number-up-default");
cupsArrayAdd(ra, "orientation-requested-default");
cupsArrayAdd(ra, "sides-default");
}
else if (!strcmp(value, "subscription-template"))
{
cupsArrayAdd(ra, "notify-attributes");
cupsArrayAdd(ra, "notify-charset");
cupsArrayAdd(ra, "notify-events");
cupsArrayAdd(ra, "notify-lease-duration");
cupsArrayAdd(ra, "notify-natural-language");
cupsArrayAdd(ra, "notify-pull-method");
cupsArrayAdd(ra, "notify-recipient-uri");
cupsArrayAdd(ra, "notify-time-interval");
cupsArrayAdd(ra, "notify-user-data");
}
else
cupsArrayAdd(ra, value);
}
return (ra);
}
static void
debug_attributes(const char *title,
ipp_t *ipp,
int type)
{
ipp_tag_t group_tag;
ipp_attribute_t *attr;
char buffer[2048];
if (Verbosity <= 1)
return;
fprintf(stderr, "%s:\n", title);
fprintf(stderr, " version=%d.%d\n", ipp->request.any.version[0],
ipp->request.any.version[1]);
if (type == 1)
fprintf(stderr, " operation-id=%s(%04x)\n",
ippOpString(ipp->request.op.operation_id),
ipp->request.op.operation_id);
else if (type == 2)
fprintf(stderr, " status-code=%s(%04x)\n",
ippErrorString(ipp->request.status.status_code),
ipp->request.status.status_code);
fprintf(stderr, " request-id=%d\n\n", ipp->request.any.request_id);
for (attr = ipp->attrs, group_tag = IPP_TAG_ZERO; attr; attr = attr->next)
{
if (attr->group_tag != group_tag)
{
group_tag = attr->group_tag;
fprintf(stderr, " %s\n", ippTagString(group_tag));
}
if (attr->name)
{
ippAttributeString(attr, buffer, sizeof(buffer));
fprintf(stderr, " %s (%s%s) %s\n", attr->name,
attr->num_values > 1 ? "1setOf " : "",
ippTagString(attr->value_tag), buffer);
}
}
}
static void
delete_client(_ipp_client_t *client)
{
if (Verbosity)
fprintf(stderr, "Closing connection from %s (%s)\n", client->http.hostname,
client->http.hostaddr->addr.sa_family == AF_INET ? "IPv4" : "IPv6");
httpFlushWrite(&(client->http));
if (client->http.fd >= 0)
close(client->http.fd);
httpClearCookie(&(client->http));
httpClearFields(&(client->http));
ippDelete(client->request);
ippDelete(client->response);
free(client);
}
static void
delete_job(_ipp_job_t *job)
{
if (Verbosity)
fprintf(stderr, "Removing job #%d from history.\n", job->id);
ippDelete(job->attrs);
if (job->filename)
{
if (!KeepFiles)
unlink(job->filename);
free(job->filename);
}
free(job);
}
static void
delete_printer(_ipp_printer_t *printer)
{
if (printer->ipv4 >= 0)
close(printer->ipv4);
if (printer->ipv6 >= 0)
close(printer->ipv6);
#if HAVE_DNSSD
if (printer->printer_ref)
DNSServiceRefDeallocate(printer->printer_ref);
if (printer->ipp_ref)
DNSServiceRefDeallocate(printer->ipp_ref);
if (printer->http_ref)
DNSServiceRefDeallocate(printer->http_ref);
if (printer->common_ref)
DNSServiceRefDeallocate(printer->common_ref);
TXTRecordDeallocate(&(printer->ipp_txt));
if (printer->dnssd_name)
_cupsStrFree(printer->dnssd_name);
#endif
if (printer->name)
_cupsStrFree(printer->name);
if (printer->icon)
_cupsStrFree(printer->icon);
if (printer->directory)
_cupsStrFree(printer->directory);
if (printer->hostname)
_cupsStrFree(printer->hostname);
if (printer->uri)
_cupsStrFree(printer->uri);
ippDelete(printer->attrs);
cupsArrayDelete(printer->jobs);
free(printer);
}
#ifdef HAVE_DNSSD
static void
dnssd_callback(
DNSServiceRef sdRef,
DNSServiceFlags flags,
DNSServiceErrorType errorCode,
const char *name,
const char *regtype,
const char *domain,
_ipp_printer_t *printer)
{
if (errorCode)
{
fprintf(stderr, "DNSServiceRegister for %s failed with error %d.\n",
regtype, (int)errorCode);
return;
}
else if (_cups_strcasecmp(name, printer->dnssd_name))
{
if (Verbosity)
fprintf(stderr, "Now using DNS-SD service name \"%s\".\n", name);
_cupsStrFree(printer->dnssd_name);
printer->dnssd_name = _cupsStrAlloc(name);
}
}
#endif
static _ipp_job_t *
find_job(_ipp_client_t *client)
{
ipp_attribute_t *attr;
_ipp_job_t key,
*job;
key.id = 0;
if ((attr = ippFindAttribute(client->request, "job-uri",
IPP_TAG_URI)) != NULL)
{
if (!strncmp(attr->values[0].string.text, client->printer->uri,
client->printer->urilen) &&
attr->values[0].string.text[client->printer->urilen] == '/')
key.id = atoi(attr->values[0].string.text + client->printer->urilen + 1);
}
else if ((attr = ippFindAttribute(client->request, "job-id",
IPP_TAG_INTEGER)) != NULL)
key.id = attr->values[0].integer;
_cupsRWLockRead(&(client->printer->rwlock));
job = (_ipp_job_t *)cupsArrayFind(client->printer->jobs, &key);
_cupsRWUnlock(&(client->printer->rwlock));
return (job);
}
static void
html_escape(_ipp_client_t *client,
const char *s,
size_t slen)
{
const char *start,
*end;
start = s;
end = s + (slen > 0 ? slen : strlen(s));
while (*s && s < end)
{
if (*s == '&' || *s == '<')
{
if (s > start)
httpWrite2(&(client->http), start, s - start);
if (*s == '&')
httpWrite2(&(client->http), "&", 5);
else
httpWrite2(&(client->http), "<", 4);
start = s + 1;
}
s ++;
}
if (s > start)
httpWrite2(&(client->http), start, s - start);
}
static void
html_printf(_ipp_client_t *client,
const char *format,
...)
{
va_list ap;
const char *start;
char size,
type;
int width,
prec;
char tformat[100],
*tptr,
temp[1024];
char *s;
va_start(ap, format);
start = format;
while (*format)
{
if (*format == '%')
{
if (format > start)
httpWrite2(&(client->http), start, format - start);
tptr = tformat;
*tptr++ = *format++;
if (*format == '%')
{
httpWrite2(&(client->http), "%", 1);
format ++;
continue;
}
else if (strchr(" -+#\'", *format))
*tptr++ = *format++;
if (*format == '*')
{
format ++;
width = va_arg(ap, int);
snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", width);
tptr += strlen(tptr);
}
else
{
width = 0;
while (isdigit(*format & 255))
{
if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format;
width = width * 10 + *format++ - '0';
}
}
if (*format == '.')
{
if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format;
format ++;
if (*format == '*')
{
format ++;
prec = va_arg(ap, int);
snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", prec);
tptr += strlen(tptr);
}
else
{
prec = 0;
while (isdigit(*format & 255))
{
if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format;
prec = prec * 10 + *format++ - '0';
}
}
}
if (*format == 'l' && format[1] == 'l')
{
size = 'L';
if (tptr < (tformat + sizeof(tformat) - 2))
{
*tptr++ = 'l';
*tptr++ = 'l';
}
format += 2;
}
else if (*format == 'h' || *format == 'l' || *format == 'L')
{
if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format;
size = *format++;
}
else
size = 0;
if (!*format)
{
start = format;
break;
}
if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format;
type = *format++;
*tptr = '\0';
start = format;
switch (type)
{
case 'E' :
case 'G' :
case 'e' :
case 'f' :
case 'g' :
if ((width + 2) > sizeof(temp))
break;
sprintf(temp, tformat, va_arg(ap, double));
httpWrite2(&(client->http), temp, strlen(temp));
break;
case 'B' :
case 'X' :
case 'b' :
case 'd' :
case 'i' :
case 'o' :
case 'u' :
case 'x' :
if ((width + 2) > sizeof(temp))
break;
# ifdef HAVE_LONG_LONG
if (size == 'L')
sprintf(temp, tformat, va_arg(ap, long long));
else
# endif
if (size == 'l')
sprintf(temp, tformat, va_arg(ap, long));
else
sprintf(temp, tformat, va_arg(ap, int));
httpWrite2(&(client->http), temp, strlen(temp));
break;
case 'p' :
if ((width + 2) > sizeof(temp))
break;
sprintf(temp, tformat, va_arg(ap, void *));
httpWrite2(&(client->http), temp, strlen(temp));
break;
case 'c' :
if (width <= 1)
{
temp[0] = va_arg(ap, int);
temp[1] = '\0';
html_escape(client, temp, 1);
}
else
html_escape(client, va_arg(ap, char *), (size_t)width);
break;
case 's' :
if ((s = va_arg(ap, char *)) == NULL)
s = "(null)";
html_escape(client, s, strlen(s));
break;
}
}
else
format ++;
}
if (format > start)
httpWrite2(&(client->http), start, format - start);
va_end(ap);
}
static void
ipp_cancel_job(_ipp_client_t *client)
{
_ipp_job_t *job;
if ((job = find_job(client)) == NULL)
{
respond_ipp(client, IPP_NOT_FOUND, "Job does not exist.");
return;
}
switch (job->state)
{
case IPP_JOB_CANCELED :
respond_ipp(client, IPP_NOT_POSSIBLE,
"Job #%d is already canceled - can\'t cancel.", job->id);
break;
case IPP_JOB_ABORTED :
respond_ipp(client, IPP_NOT_POSSIBLE,
"Job #%d is already aborted - can\'t cancel.", job->id);
break;
case IPP_JOB_COMPLETED :
respond_ipp(client, IPP_NOT_POSSIBLE,
"Job #%d is already completed - can\'t cancel.", job->id);
break;
default :
_cupsRWLockWrite(&(client->printer->rwlock));
if (job->state == IPP_JOB_PROCESSING ||
(job->state == IPP_JOB_HELD && job->fd >= 0))
job->cancel = 1;
else
{
job->state = IPP_JOB_CANCELED;
job->completed = time(NULL);
}
_cupsRWUnlock(&(client->printer->rwlock));
respond_ipp(client, IPP_OK, NULL);
break;
}
}
static void
ipp_create_job(_ipp_client_t *client)
{
_ipp_job_t *job;
cups_array_t *ra;
if (!valid_job_attributes(client))
{
httpFlush(&(client->http));
return;
}
if (client->http.state == HTTP_POST_RECV)
{
respond_ipp(client, IPP_BAD_REQUEST,
"Unexpected document data following request.");
return;
}
if ((job = create_job(client)) == NULL)
{
respond_ipp(client, IPP_PRINTER_BUSY, "Currently printing another job.");
return;
}
respond_ipp(client, IPP_OK, NULL);
ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
cupsArrayAdd(ra, "job-id");
cupsArrayAdd(ra, "job-state");
cupsArrayAdd(ra, "job-state-reasons");
cupsArrayAdd(ra, "job-uri");
copy_job_attributes(client, job, ra);
cupsArrayDelete(ra);
}
static void
ipp_get_job_attributes(
_ipp_client_t *client)
{
_ipp_job_t *job;
cups_array_t *ra;
if ((job = find_job(client)) == NULL)
{
respond_ipp(client, IPP_NOT_FOUND, "Job not found.");
return;
}
respond_ipp(client, IPP_OK, NULL);
ra = create_requested_array(client);
copy_job_attributes(client, job, ra);
cupsArrayDelete(ra);
}
static void
ipp_get_jobs(_ipp_client_t *client)
{
ipp_attribute_t *attr;
int job_comparison;
ipp_jstate_t job_state;
int first_job_id,
limit,
count;
const char *username;
_ipp_job_t *job;
cups_array_t *ra;
if ((attr = ippFindAttribute(client->request, "which-jobs",
IPP_TAG_KEYWORD)) != NULL)
fprintf(stderr, "%s Get-Jobs which-jobs=%s", client->http.hostname,
attr->values[0].string.text);
if (!attr || !strcmp(attr->values[0].string.text, "not-completed"))
{
job_comparison = -1;
job_state = IPP_JOB_STOPPED;
}
else if (!strcmp(attr->values[0].string.text, "completed"))
{
job_comparison = 1;
job_state = IPP_JOB_CANCELED;
}
else if (!strcmp(attr->values[0].string.text, "aborted"))
{
job_comparison = 0;
job_state = IPP_JOB_ABORTED;
}
else if (!strcmp(attr->values[0].string.text, "all"))
{
job_comparison = 1;
job_state = IPP_JOB_PENDING;
}
else if (!strcmp(attr->values[0].string.text, "canceled"))
{
job_comparison = 0;
job_state = IPP_JOB_CANCELED;
}
else if (!strcmp(attr->values[0].string.text, "pending"))
{
job_comparison = 0;
job_state = IPP_JOB_PENDING;
}
else if (!strcmp(attr->values[0].string.text, "pending-held"))
{
job_comparison = 0;
job_state = IPP_JOB_HELD;
}
else if (!strcmp(attr->values[0].string.text, "processing"))
{
job_comparison = 0;
job_state = IPP_JOB_PROCESSING;
}
else if (!strcmp(attr->values[0].string.text, "processing-stopped"))
{
job_comparison = 0;
job_state = IPP_JOB_STOPPED;
}
else
{
respond_ipp(client, IPP_ATTRIBUTES,
"The which-jobs value \"%s\" is not supported.",
attr->values[0].string.text);
ippAddString(client->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
"which-jobs", NULL, attr->values[0].string.text);
return;
}
if ((attr = ippFindAttribute(client->request, "limit",
IPP_TAG_INTEGER)) != NULL)
{
limit = attr->values[0].integer;
fprintf(stderr, "%s Get-Jobs limit=%d", client->http.hostname, limit);
}
else
limit = 0;
if ((attr = ippFindAttribute(client->request, "first-job-id",
IPP_TAG_INTEGER)) != NULL)
{
first_job_id = attr->values[0].integer;
fprintf(stderr, "%s Get-Jobs first-job-id=%d", client->http.hostname,
first_job_id);
}
else
first_job_id = 1;
username = NULL;
if ((attr = ippFindAttribute(client->request, "my-jobs",
IPP_TAG_BOOLEAN)) != NULL)
{
fprintf(stderr, "%s Get-Jobs my-jobs=%s\n", client->http.hostname,
attr->values[0].boolean ? "true" : "false");
if (attr->values[0].boolean)
{
if ((attr = ippFindAttribute(client->request, "requesting-user-name",
IPP_TAG_NAME)) == NULL)
{
respond_ipp(client, IPP_BAD_REQUEST,
"Need requesting-user-name with my-jobs.");
return;
}
username = attr->values[0].string.text;
fprintf(stderr, "%s Get-Jobs requesting-user-name=\"%s\"\n",
client->http.hostname, username);
}
}
if ((ra = create_requested_array(client)) == NULL &&
!ippFindAttribute(client->request, "requested-attributes",
IPP_TAG_KEYWORD))
{
ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
cupsArrayAdd(ra, "job-id");
cupsArrayAdd(ra, "job-uri");
}
respond_ipp(client, IPP_OK, NULL);
_cupsRWLockRead(&(client->printer->rwlock));
for (count = 0, job = (_ipp_job_t *)cupsArrayFirst(client->printer->jobs);
(limit <= 0 || count < limit) && job;
job = (_ipp_job_t *)cupsArrayNext(client->printer->jobs))
{
if ((job_comparison < 0 && job->state > job_state) ||
(job_comparison == 0 && job->state != job_state) ||
(job_comparison > 0 && job->state < job_state) ||
job->id < first_job_id ||
(username && job->username && _cups_strcasecmp(username, job->username)))
continue;
if (count > 0)
ippAddSeparator(client->response);
count ++;
copy_job_attributes(client, job, ra);
}
cupsArrayDelete(ra);
_cupsRWUnlock(&(client->printer->rwlock));
}
static void
ipp_get_printer_attributes(
_ipp_client_t *client)
{
cups_array_t *ra;
_ipp_printer_t *printer;
ra = create_requested_array(client);
printer = client->printer;
respond_ipp(client, IPP_OK, NULL);
_cupsRWLockRead(&(printer->rwlock));
copy_attributes(client->response, printer->attrs, ra, IPP_TAG_ZERO,
IPP_TAG_COPY);
if (!ra || cupsArrayFind(ra, "printer-state"))
ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_ENUM,
"printer-state", printer->state);
if (!ra || cupsArrayFind(ra, "printer-state-reasons"))
{
if (printer->state_reasons == _IPP_PRINTER_NONE)
ippAddString(client->response, IPP_TAG_PRINTER,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "printer-state-reasons",
NULL, "none");
else
{
int num_reasons = 0;
const char *reasons[32];
if (printer->state_reasons & _IPP_PRINTER_OTHER)
reasons[num_reasons ++] = "other";
if (printer->state_reasons & _IPP_PRINTER_COVER_OPEN)
reasons[num_reasons ++] = "cover-open";
if (printer->state_reasons & _IPP_PRINTER_INPUT_TRAY_MISSING)
reasons[num_reasons ++] = "input-tray-missing";
if (printer->state_reasons & _IPP_PRINTER_MARKER_SUPPLY_EMPTY)
reasons[num_reasons ++] = "marker-supply-empty-warning";
if (printer->state_reasons & _IPP_PRINTER_MARKER_SUPPLY_LOW)
reasons[num_reasons ++] = "marker-supply-low-report";
if (printer->state_reasons & _IPP_PRINTER_MARKER_WASTE_ALMOST_FULL)
reasons[num_reasons ++] = "marker-waste-almost-full-report";
if (printer->state_reasons & _IPP_PRINTER_MARKER_WASTE_FULL)
reasons[num_reasons ++] = "marker-waste-full-warning";
if (printer->state_reasons & _IPP_PRINTER_MEDIA_EMPTY)
reasons[num_reasons ++] = "media-empty-warning";
if (printer->state_reasons & _IPP_PRINTER_MEDIA_JAM)
reasons[num_reasons ++] = "media-jam-warning";
if (printer->state_reasons & _IPP_PRINTER_MEDIA_LOW)
reasons[num_reasons ++] = "media-low-report";
if (printer->state_reasons & _IPP_PRINTER_MEDIA_NEEDED)
reasons[num_reasons ++] = "media-needed-report";
if (printer->state_reasons & _IPP_PRINTER_MOVING_TO_PAUSED)
reasons[num_reasons ++] = "moving-to-paused";
if (printer->state_reasons & _IPP_PRINTER_PAUSED)
reasons[num_reasons ++] = "paused";
if (printer->state_reasons & _IPP_PRINTER_SPOOL_AREA_FULL)
reasons[num_reasons ++] = "spool-area-full";
if (printer->state_reasons & _IPP_PRINTER_TONER_EMPTY)
reasons[num_reasons ++] = "toner-empty-warning";
if (printer->state_reasons & _IPP_PRINTER_TONER_LOW)
reasons[num_reasons ++] = "toner-low-report";
ippAddStrings(client->response, IPP_TAG_PRINTER,
IPP_TAG_KEYWORD | IPP_TAG_COPY, "printer-state-reasons",
num_reasons, NULL, reasons);
}
}
if (!ra || cupsArrayFind(ra, "printer-up-time"))
ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"printer-up-time", (int)time(NULL));
if (!ra || cupsArrayFind(ra, "queued-job-count"))
ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
"queued-job-count",
printer->active_job &&
printer->active_job->state < IPP_JOB_CANCELED);
_cupsRWUnlock(&(printer->rwlock));
cupsArrayDelete(ra);
}
static void
ipp_print_job(_ipp_client_t *client)
{
_ipp_job_t *job;
char filename[1024],
buffer[4096];
ssize_t bytes;
cups_array_t *ra;
if (!valid_job_attributes(client))
{
httpFlush(&(client->http));
return;
}
if (client->http.state == HTTP_POST_SEND)
{
respond_ipp(client, IPP_BAD_REQUEST, "No file in request.");
return;
}
if ((job = create_job(client)) == NULL)
{
respond_ipp(client, IPP_PRINTER_BUSY, "Currently printing another job.");
return;
}
if (!_cups_strcasecmp(job->format, "image/jpeg"))
snprintf(filename, sizeof(filename), "%s/%d.jpg",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "image/png"))
snprintf(filename, sizeof(filename), "%s/%d.png",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "application/pdf"))
snprintf(filename, sizeof(filename), "%s/%d.pdf",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "application/postscript"))
snprintf(filename, sizeof(filename), "%s/%d.ps",
client->printer->directory, job->id);
else
snprintf(filename, sizeof(filename), "%s/%d.prn",
client->printer->directory, job->id);
if ((job->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0)
{
job->state = IPP_JOB_ABORTED;
respond_ipp(client, IPP_INTERNAL_ERROR,
"Unable to create print file: %s", strerror(errno));
return;
}
while ((bytes = httpRead2(&(client->http), buffer, sizeof(buffer))) > 0)
{
if (write(job->fd, buffer, bytes) < bytes)
{
int error = errno;
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
respond_ipp(client, IPP_INTERNAL_ERROR,
"Unable to write print file: %s", strerror(error));
return;
}
}
if (bytes < 0)
{
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to read print file.");
return;
}
if (close(job->fd))
{
int error = errno;
job->state = IPP_JOB_ABORTED;
job->fd = -1;
unlink(filename);
respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to write print file: %s",
strerror(error));
return;
}
job->fd = -1;
job->filename = strdup(filename);
job->state = IPP_JOB_PENDING;
#if 0
if (!_cupsThreadCreate((_cups_thread_func_t)process_job, job))
{
job->state = IPP_JOB_ABORTED;
respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to process job.");
return;
}
#else
process_job(job);
#endif
respond_ipp(client, IPP_OK, NULL);
ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
cupsArrayAdd(ra, "job-id");
cupsArrayAdd(ra, "job-state");
cupsArrayAdd(ra, "job-state-reasons");
cupsArrayAdd(ra, "job-uri");
copy_job_attributes(client, job, ra);
cupsArrayDelete(ra);
}
static void
ipp_print_uri(_ipp_client_t *client)
{
_ipp_job_t *job;
ipp_attribute_t *uri;
char scheme[256],
userpass[256],
hostname[256],
resource[1024];
int port;
http_uri_status_t uri_status;
http_encryption_t encryption;
http_t *http;
http_status_t status;
int infile;
char filename[1024],
buffer[4096];
ssize_t bytes;
cups_array_t *ra;
static const char * const uri_status_strings[] =
{
"URI too large.",
"Bad arguments to function.",
"Bad resource in URI.",
"Bad port number in URI.",
"Bad hostname in URI.",
"Bad username in URI.",
"Bad scheme in URI.",
"Bad/empty URI."
};
if (!valid_job_attributes(client))
{
httpFlush(&(client->http));
return;
}
if (client->http.state == HTTP_POST_RECV)
{
respond_ipp(client, IPP_BAD_REQUEST,
"Unexpected document data following request.");
return;
}
if ((uri = ippFindAttribute(client->request, "document-uri",
IPP_TAG_URI)) == NULL)
{
respond_ipp(client, IPP_BAD_REQUEST, "Missing document-uri.");
return;
}
if (uri->num_values != 1)
{
respond_ipp(client, IPP_BAD_REQUEST, "Too many document-uri values.");
return;
}
uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text,
scheme, sizeof(scheme), userpass,
sizeof(userpass), hostname, sizeof(hostname),
&port, resource, sizeof(resource));
if (uri_status < HTTP_URI_OK)
{
respond_ipp(client, IPP_BAD_REQUEST, "Bad document-uri: %s",
uri_status_strings[uri_status - HTTP_URI_OVERFLOW]);
return;
}
if (strcmp(scheme, "file") &&
#ifdef HAVE_SSL
strcmp(scheme, "https") &&
#endif
strcmp(scheme, "http"))
{
respond_ipp(client, IPP_URI_SCHEME, "URI scheme \"%s\" not supported.",
scheme);
return;
}
if (!strcmp(scheme, "file") && access(resource, R_OK))
{
respond_ipp(client, IPP_DOCUMENT_ACCESS_ERROR, "Unable to access URI: %s",
strerror(errno));
return;
}
if ((job = create_job(client)) == NULL)
{
respond_ipp(client, IPP_PRINTER_BUSY, "Currently printing another job.");
return;
}
if (!_cups_strcasecmp(job->format, "image/jpeg"))
snprintf(filename, sizeof(filename), "%s/%d.jpg",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "image/png"))
snprintf(filename, sizeof(filename), "%s/%d.png",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "application/pdf"))
snprintf(filename, sizeof(filename), "%s/%d.pdf",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "application/postscript"))
snprintf(filename, sizeof(filename), "%s/%d.ps",
client->printer->directory, job->id);
else
snprintf(filename, sizeof(filename), "%s/%d.prn",
client->printer->directory, job->id);
if ((job->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0)
{
job->state = IPP_JOB_ABORTED;
respond_ipp(client, IPP_INTERNAL_ERROR,
"Unable to create print file: %s", strerror(errno));
return;
}
if (!strcmp(scheme, "file"))
{
if ((infile = open(resource, O_RDONLY)) < 0)
{
respond_ipp(client, IPP_DOCUMENT_ACCESS_ERROR, "Unable to access URI: %s",
strerror(errno));
return;
}
do
{
if ((bytes = read(infile, buffer, sizeof(buffer))) < 0 &&
(errno == EAGAIN || errno == EINTR))
bytes = 1;
else if (bytes > 0 && write(job->fd, buffer, bytes) < bytes)
{
int error = errno;
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
close(infile);
respond_ipp(client, IPP_INTERNAL_ERROR,
"Unable to write print file: %s", strerror(error));
return;
}
}
while (bytes > 0);
close(infile);
}
else
{
#ifdef HAVE_SSL
if (port == 443 || !strcmp(scheme, "https"))
encryption = HTTP_ENCRYPT_ALWAYS;
else
#endif
encryption = HTTP_ENCRYPT_IF_REQUESTED;
if ((http = httpConnectEncrypt(hostname, port, encryption)) == NULL)
{
respond_ipp(client, IPP_DOCUMENT_ACCESS_ERROR,
"Unable to connect to %s: %s", hostname,
cupsLastErrorString());
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
return;
}
httpClearFields(http);
httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en");
if (httpGet(http, resource))
{
respond_ipp(client, IPP_DOCUMENT_ACCESS_ERROR, "Unable to GET URI: %s",
strerror(errno));
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
httpClose(http);
return;
}
while ((status = httpUpdate(http)) == HTTP_CONTINUE);
if (status != HTTP_OK)
{
respond_ipp(client, IPP_DOCUMENT_ACCESS_ERROR, "Unable to GET URI: %s",
httpStatus(status));
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
httpClose(http);
return;
}
while ((bytes = httpRead2(http, buffer, sizeof(buffer))) > 0)
{
if (write(job->fd, buffer, bytes) < bytes)
{
int error = errno;
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
httpClose(http);
respond_ipp(client, IPP_INTERNAL_ERROR,
"Unable to write print file: %s", strerror(error));
return;
}
}
httpClose(http);
}
if (close(job->fd))
{
int error = errno;
job->state = IPP_JOB_ABORTED;
job->fd = -1;
unlink(filename);
respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to write print file: %s",
strerror(error));
return;
}
job->fd = -1;
job->filename = strdup(filename);
job->state = IPP_JOB_PENDING;
#if 0
if (!_cupsThreadCreate((_cups_thread_func_t)process_job, job))
{
job->state = IPP_JOB_ABORTED;
respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to process job.");
return;
}
#else
process_job(job);
#endif
respond_ipp(client, IPP_OK, NULL);
ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
cupsArrayAdd(ra, "job-id");
cupsArrayAdd(ra, "job-state");
cupsArrayAdd(ra, "job-state-reasons");
cupsArrayAdd(ra, "job-uri");
copy_job_attributes(client, job, ra);
cupsArrayDelete(ra);
}
static void
ipp_send_document(_ipp_client_t *client)
{
_ipp_job_t *job;
char filename[1024],
buffer[4096];
ssize_t bytes;
ipp_attribute_t *attr;
cups_array_t *ra;
if ((job = find_job(client)) == NULL)
{
respond_ipp(client, IPP_NOT_FOUND, "Job does not exist.");
httpFlush(&(client->http));
return;
}
if (job->state > IPP_JOB_HELD)
{
respond_ipp(client, IPP_NOT_POSSIBLE, "Job is not in a pending state.");
httpFlush(&(client->http));
return;
}
else if (job->filename || job->fd >= 0)
{
respond_ipp(client, IPP_MULTIPLE_JOBS_NOT_SUPPORTED,
"Multiple document jobs are not supported.");
httpFlush(&(client->http));
return;
}
if ((attr = ippFindAttribute(client->request, "last-document",
IPP_TAG_ZERO)) == NULL)
{
respond_ipp(client, IPP_BAD_REQUEST,
"Missing required last-document attribute.");
httpFlush(&(client->http));
return;
}
else if (attr->value_tag != IPP_TAG_BOOLEAN || attr->num_values != 1 ||
!attr->values[0].boolean)
{
respond_unsupported(client, attr);
httpFlush(&(client->http));
return;
}
if (!valid_doc_attributes(client))
{
httpFlush(&(client->http));
return;
}
_cupsRWLockWrite(&(client->printer->rwlock));
if ((attr = ippFindAttribute(job->attrs, "document-format",
IPP_TAG_MIMETYPE)) != NULL)
job->format = attr->values[0].string.text;
else
job->format = "application/octet-stream";
if (!_cups_strcasecmp(job->format, "image/jpeg"))
snprintf(filename, sizeof(filename), "%s/%d.jpg",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "image/png"))
snprintf(filename, sizeof(filename), "%s/%d.png",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "application/pdf"))
snprintf(filename, sizeof(filename), "%s/%d.pdf",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "application/postscript"))
snprintf(filename, sizeof(filename), "%s/%d.ps",
client->printer->directory, job->id);
else
snprintf(filename, sizeof(filename), "%s/%d.prn",
client->printer->directory, job->id);
job->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600);
_cupsRWUnlock(&(client->printer->rwlock));
if (job->fd < 0)
{
job->state = IPP_JOB_ABORTED;
respond_ipp(client, IPP_INTERNAL_ERROR,
"Unable to create print file: %s", strerror(errno));
return;
}
while ((bytes = httpRead2(&(client->http), buffer, sizeof(buffer))) > 0)
{
if (write(job->fd, buffer, bytes) < bytes)
{
int error = errno;
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
respond_ipp(client, IPP_INTERNAL_ERROR,
"Unable to write print file: %s", strerror(error));
return;
}
}
if (bytes < 0)
{
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to read print file.");
return;
}
if (close(job->fd))
{
int error = errno;
job->state = IPP_JOB_ABORTED;
job->fd = -1;
unlink(filename);
respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to write print file: %s",
strerror(error));
return;
}
_cupsRWLockWrite(&(client->printer->rwlock));
job->fd = -1;
job->filename = strdup(filename);
job->state = IPP_JOB_PENDING;
_cupsRWUnlock(&(client->printer->rwlock));
#if 0
if (!_cupsThreadCreate((_cups_thread_func_t)process_job, job))
{
job->state = IPP_JOB_ABORTED;
respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to process job.");
return;
}
#else
process_job(job);
#endif
respond_ipp(client, IPP_OK, NULL);
ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
cupsArrayAdd(ra, "job-id");
cupsArrayAdd(ra, "job-state");
cupsArrayAdd(ra, "job-state-reasons");
cupsArrayAdd(ra, "job-uri");
copy_job_attributes(client, job, ra);
cupsArrayDelete(ra);
}
static void
ipp_send_uri(_ipp_client_t *client)
{
_ipp_job_t *job;
ipp_attribute_t *uri;
char scheme[256],
userpass[256],
hostname[256],
resource[1024];
int port;
http_uri_status_t uri_status;
http_encryption_t encryption;
http_t *http;
http_status_t status;
int infile;
char filename[1024],
buffer[4096];
ssize_t bytes;
ipp_attribute_t *attr;
cups_array_t *ra;
static const char * const uri_status_strings[] =
{
"URI too large.",
"Bad arguments to function.",
"Bad resource in URI.",
"Bad port number in URI.",
"Bad hostname in URI.",
"Bad username in URI.",
"Bad scheme in URI.",
"Bad/empty URI."
};
if ((job = find_job(client)) == NULL)
{
respond_ipp(client, IPP_NOT_FOUND, "Job does not exist.");
httpFlush(&(client->http));
return;
}
if (job->state > IPP_JOB_HELD)
{
respond_ipp(client, IPP_NOT_POSSIBLE, "Job is not in a pending state.");
httpFlush(&(client->http));
return;
}
else if (job->filename || job->fd >= 0)
{
respond_ipp(client, IPP_MULTIPLE_JOBS_NOT_SUPPORTED,
"Multiple document jobs are not supported.");
httpFlush(&(client->http));
return;
}
if ((attr = ippFindAttribute(client->request, "last-document",
IPP_TAG_ZERO)) == NULL)
{
respond_ipp(client, IPP_BAD_REQUEST,
"Missing required last-document attribute.");
httpFlush(&(client->http));
return;
}
else if (attr->value_tag != IPP_TAG_BOOLEAN || attr->num_values != 1 ||
!attr->values[0].boolean)
{
respond_unsupported(client, attr);
httpFlush(&(client->http));
return;
}
if (!valid_doc_attributes(client))
{
httpFlush(&(client->http));
return;
}
if (client->http.state == HTTP_POST_RECV)
{
respond_ipp(client, IPP_BAD_REQUEST,
"Unexpected document data following request.");
return;
}
if ((uri = ippFindAttribute(client->request, "document-uri",
IPP_TAG_URI)) == NULL)
{
respond_ipp(client, IPP_BAD_REQUEST, "Missing document-uri.");
return;
}
if (uri->num_values != 1)
{
respond_ipp(client, IPP_BAD_REQUEST, "Too many document-uri values.");
return;
}
uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text,
scheme, sizeof(scheme), userpass,
sizeof(userpass), hostname, sizeof(hostname),
&port, resource, sizeof(resource));
if (uri_status < HTTP_URI_OK)
{
respond_ipp(client, IPP_BAD_REQUEST, "Bad document-uri: %s",
uri_status_strings[uri_status - HTTP_URI_OVERFLOW]);
return;
}
if (strcmp(scheme, "file") &&
#ifdef HAVE_SSL
strcmp(scheme, "https") &&
#endif
strcmp(scheme, "http"))
{
respond_ipp(client, IPP_URI_SCHEME, "URI scheme \"%s\" not supported.",
scheme);
return;
}
if (!strcmp(scheme, "file") && access(resource, R_OK))
{
respond_ipp(client, IPP_DOCUMENT_ACCESS_ERROR, "Unable to access URI: %s",
strerror(errno));
return;
}
_cupsRWLockWrite(&(client->printer->rwlock));
if ((attr = ippFindAttribute(job->attrs, "document-format",
IPP_TAG_MIMETYPE)) != NULL)
job->format = attr->values[0].string.text;
else
job->format = "application/octet-stream";
if (!_cups_strcasecmp(job->format, "image/jpeg"))
snprintf(filename, sizeof(filename), "%s/%d.jpg",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "image/png"))
snprintf(filename, sizeof(filename), "%s/%d.png",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "application/pdf"))
snprintf(filename, sizeof(filename), "%s/%d.pdf",
client->printer->directory, job->id);
else if (!_cups_strcasecmp(job->format, "application/postscript"))
snprintf(filename, sizeof(filename), "%s/%d.ps",
client->printer->directory, job->id);
else
snprintf(filename, sizeof(filename), "%s/%d.prn",
client->printer->directory, job->id);
job->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600);
_cupsRWUnlock(&(client->printer->rwlock));
if (job->fd < 0)
{
job->state = IPP_JOB_ABORTED;
respond_ipp(client, IPP_INTERNAL_ERROR,
"Unable to create print file: %s", strerror(errno));
return;
}
if (!strcmp(scheme, "file"))
{
if ((infile = open(resource, O_RDONLY)) < 0)
{
respond_ipp(client, IPP_DOCUMENT_ACCESS_ERROR, "Unable to access URI: %s",
strerror(errno));
return;
}
do
{
if ((bytes = read(infile, buffer, sizeof(buffer))) < 0 &&
(errno == EAGAIN || errno == EINTR))
bytes = 1;
else if (bytes > 0 && write(job->fd, buffer, bytes) < bytes)
{
int error = errno;
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
close(infile);
respond_ipp(client, IPP_INTERNAL_ERROR,
"Unable to write print file: %s", strerror(error));
return;
}
}
while (bytes > 0);
close(infile);
}
else
{
#ifdef HAVE_SSL
if (port == 443 || !strcmp(scheme, "https"))
encryption = HTTP_ENCRYPT_ALWAYS;
else
#endif
encryption = HTTP_ENCRYPT_IF_REQUESTED;
if ((http = httpConnectEncrypt(hostname, port, encryption)) == NULL)
{
respond_ipp(client, IPP_DOCUMENT_ACCESS_ERROR,
"Unable to connect to %s: %s", hostname,
cupsLastErrorString());
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
return;
}
httpClearFields(http);
httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en");
if (httpGet(http, resource))
{
respond_ipp(client, IPP_DOCUMENT_ACCESS_ERROR, "Unable to GET URI: %s",
strerror(errno));
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
httpClose(http);
return;
}
while ((status = httpUpdate(http)) == HTTP_CONTINUE);
if (status != HTTP_OK)
{
respond_ipp(client, IPP_DOCUMENT_ACCESS_ERROR, "Unable to GET URI: %s",
httpStatus(status));
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
httpClose(http);
return;
}
while ((bytes = httpRead2(http, buffer, sizeof(buffer))) > 0)
{
if (write(job->fd, buffer, bytes) < bytes)
{
int error = errno;
job->state = IPP_JOB_ABORTED;
close(job->fd);
job->fd = -1;
unlink(filename);
httpClose(http);
respond_ipp(client, IPP_INTERNAL_ERROR,
"Unable to write print file: %s", strerror(error));
return;
}
}
httpClose(http);
}
if (close(job->fd))
{
int error = errno;
job->state = IPP_JOB_ABORTED;
job->fd = -1;
unlink(filename);
respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to write print file: %s",
strerror(error));
return;
}
_cupsRWLockWrite(&(client->printer->rwlock));
job->fd = -1;
job->filename = strdup(filename);
job->state = IPP_JOB_PENDING;
_cupsRWUnlock(&(client->printer->rwlock));
#if 0
if (!_cupsThreadCreate((_cups_thread_func_t)process_job, job))
{
job->state = IPP_JOB_ABORTED;
respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to process job.");
return;
}
#else
process_job(job);
#endif
respond_ipp(client, IPP_OK, NULL);
ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
cupsArrayAdd(ra, "job-id");
cupsArrayAdd(ra, "job-state");
cupsArrayAdd(ra, "job-state-reasons");
cupsArrayAdd(ra, "job-uri");
copy_job_attributes(client, job, ra);
cupsArrayDelete(ra);
}
static void
ipp_validate_job(_ipp_client_t *client)
{
if (valid_job_attributes(client))
respond_ipp(client, IPP_OK, NULL);
}
static void *
process_client(_ipp_client_t *client)
{
while (httpWait(&(client->http), 30000))
if (!process_http(client))
break;
delete_client(client);
return (NULL);
}
int
process_http(_ipp_client_t *client)
{
char line[4096],
operation[64],
uri[1024],
version[64],
*ptr;
int major, minor;
http_status_t status;
ipp_state_t state;
if (client->http.error)
return (0);
httpClearFields(&(client->http));
ippDelete(client->request);
ippDelete(client->response);
client->http.activity = time(NULL);
client->http.version = HTTP_1_1;
client->http.keep_alive = HTTP_KEEPALIVE_OFF;
client->http.data_encoding = HTTP_ENCODE_LENGTH;
client->http.data_remaining = 0;
client->request = NULL;
client->response = NULL;
client->operation = HTTP_WAITING;
while ((ptr = httpGets(line, sizeof(line) - 1, &(client->http))) != NULL)
if (*ptr)
break;
if (!ptr)
return (0);
fprintf(stderr, "%s %s\n", client->http.hostname, line);
switch (sscanf(line, "%63s%1023s%63s", operation, uri, version))
{
case 1 :
fprintf(stderr, "%s Bad request line.\n", client->http.hostname);
respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
return (0);
case 2 :
client->http.version = HTTP_0_9;
break;
case 3 :
if (sscanf(version, "HTTP/%d.%d", &major, &minor) != 2)
{
fprintf(stderr, "%s Bad HTTP version.\n", client->http.hostname);
respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
return (0);
}
if (major < 2)
{
client->http.version = (http_version_t)(major * 100 + minor);
if (client->http.version == HTTP_1_1)
client->http.keep_alive = HTTP_KEEPALIVE_ON;
else
client->http.keep_alive = HTTP_KEEPALIVE_OFF;
}
else
{
respond_http(client, HTTP_NOT_SUPPORTED, NULL, 0);
return (0);
}
break;
}
if (!strncmp(client->uri, "http:", 5) || !strncmp(client->uri, "ipp:", 4))
{
char scheme[32],
userpass[128],
hostname[HTTP_MAX_HOST];
int port;
if (httpSeparateURI(HTTP_URI_CODING_MOST, uri, scheme, sizeof(scheme),
userpass, sizeof(userpass),
hostname, sizeof(hostname), &port,
client->uri, sizeof(client->uri)) < HTTP_URI_OK)
{
fprintf(stderr, "%s Bad URI \"%s\".\n", client->http.hostname, uri);
respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
return (0);
}
}
else
{
if (!_httpDecodeURI(client->uri, uri, sizeof(client->uri)))
{
fprintf(stderr, "%s Bad URI \"%s\".\n", client->http.hostname, uri);
respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
return (0);
}
}
if (!strcmp(operation, "GET"))
client->http.state = HTTP_GET;
else if (!strcmp(operation, "POST"))
client->http.state = HTTP_POST;
else if (!strcmp(operation, "OPTIONS"))
client->http.state = HTTP_OPTIONS;
else if (!strcmp(operation, "HEAD"))
client->http.state = HTTP_HEAD;
else
{
fprintf(stderr, "%s Bad operation \"%s\".\n", client->http.hostname,
operation);
respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
return (0);
}
client->start = time(NULL);
client->operation = client->http.state;
client->http.status = HTTP_OK;
while ((status = httpUpdate(&(client->http))) == HTTP_CONTINUE);
if (status != HTTP_OK)
{
respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
return (0);
}
if (!client->http.fields[HTTP_FIELD_HOST][0] &&
client->http.version >= HTTP_1_1)
{
respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
return (0);
}
if (!_cups_strcasecmp(client->http.fields[HTTP_FIELD_CONNECTION], "Upgrade"))
{
if (!respond_http(client, HTTP_NOT_IMPLEMENTED, NULL, 0))
return (0);
}
if (client->http.expect &&
(client->operation == HTTP_POST || client->operation == HTTP_PUT))
{
if (client->http.expect == HTTP_CONTINUE)
{
if (!respond_http(client, HTTP_CONTINUE, NULL, 0))
return (0);
}
else
{
if (!respond_http(client, HTTP_EXPECTATION_FAILED, NULL, 0))
return (0);
httpPrintf(&(client->http), "Content-Length: 0\r\n");
httpPrintf(&(client->http), "\r\n");
httpFlushWrite(&(client->http));
client->http.data_encoding = HTTP_ENCODE_LENGTH;
}
}
switch (client->operation)
{
case HTTP_OPTIONS :
return (respond_http(client, HTTP_OK, NULL, 0));
case HTTP_HEAD :
if (!strcmp(client->uri, "/icon.png"))
return (respond_http(client, HTTP_OK, "image/png", 0));
else if (!strcmp(client->uri, "/"))
return (respond_http(client, HTTP_OK, "text/html", 0));
else
return (respond_http(client, HTTP_NOT_FOUND, NULL, 0));
break;
case HTTP_GET :
if (!strcmp(client->uri, "/icon.png"))
{
int fd;
struct stat fileinfo;
char buffer[4096];
ssize_t bytes;
if (!stat(client->printer->icon, &fileinfo) &&
(fd = open(client->printer->icon, O_RDONLY)) >= 0)
{
if (!respond_http(client, HTTP_OK, "image/png", fileinfo.st_size))
{
close(fd);
return (0);
}
while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
httpWrite2(&(client->http), buffer, bytes);
httpFlushWrite(&(client->http));
close(fd);
}
else
return (respond_http(client, HTTP_NOT_FOUND, NULL, 0));
}
else if (!strcmp(client->uri, "/"))
{
if (!respond_http(client, HTTP_OK, "text/html", 0))
return (0);
html_printf(client,
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" "
"\"http://www.w3.org/TR/html4/strict.dtd\">\n"
"<html>\n"
"<head>\n"
"<title>%s</title>\n"
"<link rel=\"SHORTCUT ICON\" href=\"/icon.png\" "
"type=\"image/png\">\n"
"</head>\n"
"<body>\n"
"</body>\n"
"<h1>%s</h1>\n"
"<p>%s, %d job(s).</p>\n"
"</body>\n"
"</html>\n",
client->printer->name, client->printer->name,
client->printer->state == IPP_PRINTER_IDLE ? "Idle" :
client->printer->state == IPP_PRINTER_PROCESSING ?
"Printing" : "Stopped",
cupsArrayCount(client->printer->jobs));
httpWrite2(&(client->http), "", 0);
return (1);
}
else
return (respond_http(client, HTTP_NOT_FOUND, NULL, 0));
break;
case HTTP_POST :
if (client->http.data_remaining < 0 ||
(!client->http.fields[HTTP_FIELD_CONTENT_LENGTH][0] &&
client->http.data_encoding == HTTP_ENCODE_LENGTH))
{
return (respond_http(client, HTTP_BAD_REQUEST, NULL, 0));
}
if (strcmp(client->http.fields[HTTP_FIELD_CONTENT_TYPE],
"application/ipp"))
{
return (respond_http(client, HTTP_BAD_REQUEST, NULL, 0));
}
client->request = ippNew();
while ((state = ippRead(&(client->http), client->request)) != IPP_DATA)
if (state == IPP_ERROR)
{
fprintf(stderr, "%s IPP read error (%s).\n", client->http.hostname,
cupsLastErrorString());
respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
return (0);
}
return (process_ipp(client));
default :
break;
}
return (1);
}
static int
process_ipp(_ipp_client_t *client)
{
ipp_tag_t group;
ipp_attribute_t *attr;
ipp_attribute_t *charset;
ipp_attribute_t *language;
ipp_attribute_t *uri;
debug_attributes("Request", client->request, 1);
client->operation_id = client->request->request.op.operation_id;
client->response = ippNew();
client->response->request.status.version[0] =
client->request->request.op.version[0];
client->response->request.status.version[1] =
client->request->request.op.version[1];
client->response->request.status.request_id =
client->request->request.op.request_id;
if (client->request->request.any.version[0] < 1 ||
client->request->request.any.version[0] > 2)
{
respond_ipp(client, IPP_VERSION_NOT_SUPPORTED,
"Bad request version number %d.%d.",
client->request->request.any.version[0],
client->request->request.any.version[1]);
}
else if (client->request->request.any.request_id <= 0)
respond_ipp(client, IPP_BAD_REQUEST, "Bad request-id %d.",
client->request->request.any.request_id);
else if (!client->request->attrs)
respond_ipp(client, IPP_BAD_REQUEST, "No attributes in request.");
else
{
for (attr = client->request->attrs, group = attr->group_tag;
attr;
attr = attr->next)
if (attr->group_tag < group && attr->group_tag != IPP_TAG_ZERO)
{
respond_ipp(client, IPP_BAD_REQUEST,
"Attribute groups are out of order (%x < %x).",
attr->group_tag, group);
break;
}
else
group = attr->group_tag;
if (!attr)
{
attr = client->request->attrs;
if (attr && attr->name &&
!strcmp(attr->name, "attributes-charset") &&
(attr->value_tag & IPP_TAG_MASK) == IPP_TAG_CHARSET)
charset = attr;
else
charset = NULL;
if (attr)
attr = attr->next;
if (attr && attr->name &&
!strcmp(attr->name, "attributes-natural-language") &&
(attr->value_tag & IPP_TAG_MASK) == IPP_TAG_LANGUAGE)
language = attr;
else
language = NULL;
if ((attr = ippFindAttribute(client->request, "printer-uri",
IPP_TAG_URI)) != NULL)
uri = attr;
else if ((attr = ippFindAttribute(client->request, "job-uri",
IPP_TAG_URI)) != NULL)
uri = attr;
else
uri = NULL;
ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
"attributes-charset", NULL,
charset ? charset->values[0].string.text : "utf-8");
ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
"attributes-natural-language", NULL,
language ? language->values[0].string.text : "en");
if (charset &&
_cups_strcasecmp(charset->values[0].string.text, "us-ascii") &&
_cups_strcasecmp(charset->values[0].string.text, "utf-8"))
{
respond_ipp(client, IPP_BAD_REQUEST,
"Unsupported character set \"%s\".",
charset->values[0].string.text);
}
else if (!charset || !language || !uri)
{
respond_ipp(client, IPP_BAD_REQUEST, "Missing required attributes.");
}
else if (strcmp(uri->values[0].string.text, client->printer->uri) &&
strncmp(uri->values[0].string.text, client->printer->uri,
client->printer->urilen))
{
respond_ipp(client, IPP_NOT_FOUND, "%s %s not found.", uri->name,
uri->values[0].string.text);
}
else
{
if (client->http.expect == HTTP_CONTINUE)
{
if (!respond_http(client, HTTP_CONTINUE, NULL, 0))
return (0);
}
switch (client->request->request.op.operation_id)
{
case IPP_PRINT_JOB :
ipp_print_job(client);
break;
case IPP_PRINT_URI :
ipp_print_uri(client);
break;
case IPP_VALIDATE_JOB :
ipp_validate_job(client);
break;
case IPP_CREATE_JOB :
ipp_create_job(client);
break;
case IPP_SEND_DOCUMENT :
ipp_send_document(client);
break;
case IPP_SEND_URI :
ipp_send_uri(client);
break;
case IPP_CANCEL_JOB :
ipp_cancel_job(client);
break;
case IPP_GET_JOB_ATTRIBUTES :
ipp_get_job_attributes(client);
break;
case IPP_GET_JOBS :
ipp_get_jobs(client);
break;
case IPP_GET_PRINTER_ATTRIBUTES :
ipp_get_printer_attributes(client);
break;
default :
respond_ipp(client, IPP_OPERATION_NOT_SUPPORTED,
"Operation not supported.");
break;
}
}
}
}
if (client->http.state != HTTP_POST_SEND)
httpFlush(&(client->http));
return (respond_http(client, HTTP_OK, "application/ipp",
ippLength(client->response)));
}
static void *
process_job(_ipp_job_t *job)
{
job->state = IPP_JOB_PROCESSING;
job->printer->state = IPP_PRINTER_PROCESSING;
sleep(5);
if (job->cancel)
job->state = IPP_JOB_CANCELED;
else
job->state = IPP_JOB_COMPLETED;
job->completed = time(NULL);
job->printer->state = IPP_PRINTER_IDLE;
job->printer->active_job = NULL;
return (NULL);
}
#ifdef HAVE_DNSSD
static int
register_printer(
_ipp_printer_t *printer,
const char *location,
const char *make,
const char *model,
const char *formats,
const char *adminurl,
int color,
int duplex,
const char *regtype)
{
DNSServiceErrorType error;
char make_model[256],
product[256];
snprintf(make_model, sizeof(make_model), "%s %s", make, model);
snprintf(product, sizeof(product), "(%s)", model);
TXTRecordCreate(&(printer->ipp_txt), 1024, NULL);
TXTRecordSetValue(&(printer->ipp_txt), "txtvers", 1, "1");
TXTRecordSetValue(&(printer->ipp_txt), "qtotal", 1, "1");
TXTRecordSetValue(&(printer->ipp_txt), "rp", 3, "ipp");
TXTRecordSetValue(&(printer->ipp_txt), "ty", (uint8_t)strlen(make_model),
make_model);
TXTRecordSetValue(&(printer->ipp_txt), "adminurl", (uint8_t)strlen(adminurl),
adminurl);
TXTRecordSetValue(&(printer->ipp_txt), "note", (uint8_t)strlen(location),
location);
TXTRecordSetValue(&(printer->ipp_txt), "priority", 1, "0");
TXTRecordSetValue(&(printer->ipp_txt), "product", (uint8_t)strlen(product),
product);
TXTRecordSetValue(&(printer->ipp_txt), "pdl", (uint8_t)strlen(formats),
formats);
TXTRecordSetValue(&(printer->ipp_txt), "Color", 1, color ? "T" : "F");
TXTRecordSetValue(&(printer->ipp_txt), "Duplex", 1, duplex ? "T" : "F");
TXTRecordSetValue(&(printer->ipp_txt), "usb_MFG", (uint8_t)strlen(make),
make);
TXTRecordSetValue(&(printer->ipp_txt), "usb_MDL", (uint8_t)strlen(model),
model);
TXTRecordSetValue(&(printer->ipp_txt), "air", 4, "none");
if ((error = DNSServiceCreateConnection(&(printer->common_ref)))
!= kDNSServiceErr_NoError)
{
fprintf(stderr, "Unable to create mDNSResponder connection: %d\n", error);
return (0);
}
printer->printer_ref = printer->common_ref;
if ((error = DNSServiceRegister(&(printer->printer_ref),
kDNSServiceFlagsShareConnection,
0 , printer->dnssd_name,
"_printer._tcp", NULL ,
NULL , 0 , 0 ,
NULL ,
(DNSServiceRegisterReply)dnssd_callback,
printer)) != kDNSServiceErr_NoError)
{
fprintf(stderr, "Unable to register \"%s._printer._tcp\": %d\n",
printer->dnssd_name, error);
return (0);
}
printer->ipp_ref = printer->common_ref;
if ((error = DNSServiceRegister(&(printer->ipp_ref),
kDNSServiceFlagsShareConnection,
0 , printer->dnssd_name,
regtype, NULL ,
NULL , htons(printer->port),
TXTRecordGetLength(&(printer->ipp_txt)),
TXTRecordGetBytesPtr(&(printer->ipp_txt)),
(DNSServiceRegisterReply)dnssd_callback,
printer)) != kDNSServiceErr_NoError)
{
fprintf(stderr, "Unable to register \"%s.%s\": %d\n",
printer->dnssd_name, regtype, error);
return (0);
}
printer->http_ref = printer->common_ref;
if ((error = DNSServiceRegister(&(printer->http_ref),
kDNSServiceFlagsShareConnection,
0 , printer->dnssd_name,
"_http._tcp,_printer", NULL ,
NULL , htons(printer->port),
0 , NULL,
(DNSServiceRegisterReply)dnssd_callback,
printer)) != kDNSServiceErr_NoError)
{
fprintf(stderr, "Unable to register \"%s.%s\": %d\n",
printer->dnssd_name, regtype, error);
return (0);
}
return (1);
}
#endif
int
respond_http(_ipp_client_t *client,
http_status_t code,
const char *type,
size_t length)
{
char message[1024];
fprintf(stderr, "%s %s\n", client->http.hostname, httpStatus(code));
if (code == HTTP_CONTINUE)
{
return (httpPrintf(&(client->http), "HTTP/%d.%d 100 Continue\r\n\r\n",
client->http.version / 100,
client->http.version % 100) > 0);
}
if (!type && !length && code != HTTP_OK)
{
snprintf(message, sizeof(message), "%d - %s\n", code, httpStatus(code));
type = "text/plain";
length = strlen(message);
}
else
message[0] = '\0';
httpFlushWrite(&(client->http));
client->http.data_encoding = HTTP_ENCODE_FIELDS;
if (httpPrintf(&(client->http), "HTTP/%d.%d %d %s\r\n", client->http.version / 100,
client->http.version % 100, code, httpStatus(code)) < 0)
return (0);
if (httpPrintf(&(client->http), "Date: %s\r\n", httpGetDateString(time(NULL))) < 0)
return (0);
if (client->http.keep_alive && client->http.version >= HTTP_1_0)
{
if (httpPrintf(&(client->http),
"Connection: Keep-Alive\r\n"
"Keep-Alive: timeout=10\r\n") < 0)
return (0);
}
if (code == HTTP_METHOD_NOT_ALLOWED || client->operation == HTTP_OPTIONS)
{
if (httpPrintf(&(client->http), "Allow: GET, HEAD, OPTIONS, POST\r\n") < 0)
return (0);
}
if (type)
{
if (!strcmp(type, "text/html"))
{
if (httpPrintf(&(client->http),
"Content-Type: text/html; charset=utf-8\r\n") < 0)
return (0);
}
else if (httpPrintf(&(client->http), "Content-Type: %s\r\n", type) < 0)
return (0);
}
if (length == 0 && !message[0])
{
if (httpPrintf(&(client->http), "Transfer-Encoding: chunked\r\n\r\n") < 0)
return (0);
}
else if (httpPrintf(&(client->http), "Content-Length: " CUPS_LLFMT "\r\n\r\n",
CUPS_LLCAST length) < 0)
return (0);
if (httpFlushWrite(&(client->http)) < 0)
return (0);
if (message[0])
{
if (httpPrintf(&(client->http), "%s", message) < 0)
return (0);
}
else if (client->response)
{
debug_attributes("Response", client->response, 2);
client->http.data_encoding = HTTP_ENCODE_LENGTH;
client->http.data_remaining = (off_t)ippLength(client->response);
client->response->state = IPP_IDLE;
if (ippWrite(&(client->http), client->response) != IPP_DATA)
return (0);
}
else
client->http.data_encoding = HTTP_ENCODE_CHUNKED;
return (httpFlushWrite(&(client->http)) >= 0);
}
static void
respond_ipp(_ipp_client_t *client,
ipp_status_t status,
const char *message,
...)
{
va_list ap;
char formatted[1024];
client->response->request.status.status_code = status;
if (!client->response->attrs)
{
ippAddString(client->response, IPP_TAG_OPERATION,
IPP_TAG_CHARSET | IPP_TAG_COPY, "attributes-charset", NULL,
"utf-8");
ippAddString(client->response, IPP_TAG_OPERATION,
IPP_TAG_LANGUAGE | IPP_TAG_COPY, "attributes-natural-language",
NULL, "en-us");
}
if (message)
{
va_start(ap, message);
vsnprintf(formatted, sizeof(formatted), message, ap);
va_end(ap);
ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_TEXT,
"status-message", NULL, formatted);
}
else
formatted[0] = '\0';
fprintf(stderr, "%s %s %s (%s)\n", client->http.hostname,
ippOpString(client->operation_id), ippErrorString(status), formatted);
}
static void
respond_unsupported(
_ipp_client_t *client,
ipp_attribute_t *attr)
{
ipp_attribute_t *temp;
if (!client->response->attrs)
respond_ipp(client, IPP_ATTRIBUTES, "Unsupported %s %s%s value.",
attr->name, attr->num_values > 1 ? "1setOf " : "",
ippTagString(attr->value_tag));
else
ippSetStatusCode(client->response, IPP_ATTRIBUTES);
temp = ippCopyAttribute(client->response, attr, 0);
ippSetGroupTag(client->response, &temp, IPP_TAG_UNSUPPORTED_GROUP);
}
static void
run_printer(_ipp_printer_t *printer)
{
int num_fds;
struct pollfd polldata[3];
int timeout;
_ipp_client_t *client;
polldata[0].fd = printer->ipv4;
polldata[0].events = POLLIN;
polldata[1].fd = printer->ipv6;
polldata[1].events = POLLIN;
num_fds = 2;
#ifdef HAVE_DNSSD
polldata[num_fds ].fd = DNSServiceRefSockFD(printer->common_ref);
polldata[num_fds ++].events = POLLIN;
#endif
for (;;)
{
if (cupsArrayCount(printer->jobs))
timeout = 10;
else
timeout = -1;
if (poll(polldata, num_fds, timeout) < 0 && errno != EINTR)
{
perror("poll() failed");
break;
}
if (polldata[0].revents & POLLIN)
{
if ((client = create_client(printer, printer->ipv4)) != NULL)
{
if (!_cupsThreadCreate((_cups_thread_func_t)process_client, client))
{
perror("Unable to create client thread");
delete_client(client);
}
}
}
if (polldata[1].revents & POLLIN)
{
if ((client = create_client(printer, printer->ipv6)) != NULL)
{
if (!_cupsThreadCreate((_cups_thread_func_t)process_client, client))
{
perror("Unable to create client thread");
delete_client(client);
}
}
}
#ifdef HAVE_DNSSD
if (polldata[2].revents & POLLIN)
DNSServiceProcessResult(printer->common_ref);
#endif
clean_jobs(printer);
}
}
static void
usage(int status)
{
if (!status)
{
puts(CUPS_SVERSION " - Copyright 2010 by Apple Inc. All rights reserved.");
puts("");
}
puts("Usage: ippserver [options] \"name\"");
puts("");
puts("Options:");
puts("-2 Supports 2-sided printing (default=1-sided)");
puts("-M manufacturer Manufacturer name (default=Test)");
printf("-d spool-directory Spool directory "
"(default=/tmp/ippserver.%d)\n", (int)getpid());
puts("-f type/subtype[,...] List of supported types "
"(default=application/pdf,image/jpeg)");
puts("-h Show program help");
puts("-i iconfile.png PNG icon file (default=printer.png)");
puts("-l location Location of printer (default=empty string)");
puts("-m model Model name (default=Printer)");
puts("-n hostname Hostname for printer");
puts("-p port Port number (default=auto)");
puts("-r regtype Bonjour service type (default=_ipp._tcp)");
puts("-s speed[,color-speed] Speed in pages per minute (default=10,0)");
puts("-v[vvv] Be (very) verbose");
exit(status);
}
static int
valid_doc_attributes(
_ipp_client_t *client)
{
int i;
ipp_attribute_t *attr,
*supported;
const char *format = NULL;
if ((attr = ippFindAttribute(client->request, "compression",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD ||
strcmp(attr->values[0].string.text, "none"))
respond_unsupported(client, attr);
else
fprintf(stderr, "%s %s compression=\"%s\"\n",
client->http.hostname,
ippOpString(client->request->request.op.operation_id),
attr->values[0].string.text);
}
if ((attr = ippFindAttribute(client->request, "document-format",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 || attr->value_tag != IPP_TAG_MIMETYPE)
respond_unsupported(client, attr);
else
{
format = attr->values[0].string.text;
fprintf(stderr, "%s %s document-format=\"%s\"\n",
client->http.hostname,
ippOpString(client->request->request.op.operation_id), format);
}
}
else
format = "application/octet-stream";
if (!strcmp(format, "application/octet-stream") &&
(client->request->request.op.operation_id == IPP_PRINT_JOB ||
client->request->request.op.operation_id == IPP_SEND_DOCUMENT))
{
unsigned char header[4];
memset(header, 0, sizeof(header));
_httpPeek(&(client->http), (char *)header, sizeof(header));
if (!memcmp(header, "%PDF", 4))
format = "application/pdf";
else if (!memcmp(header, "%!", 2))
format = "application/postscript";
else if (!memcmp(header, "\377\330\377", 3) &&
header[3] >= 0xe0 && header[3] <= 0xef)
format = "image/jpeg";
else if (!memcmp(header, "\211PNG", 4))
format = "image/png";
if (format)
fprintf(stderr, "%s %s Auto-typed document-format=\"%s\"\n",
client->http.hostname,
ippOpString(client->request->request.op.operation_id), format);
if (!attr)
attr = ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE,
"document-format", NULL, format);
else
{
_cupsStrFree(attr->values[0].string.text);
attr->values[0].string.text = _cupsStrAlloc(format);
}
}
if (client->request->request.op.operation_id != IPP_CREATE_JOB &&
(supported = ippFindAttribute(client->printer->attrs,
"document-format-supported",
IPP_TAG_MIMETYPE)) != NULL)
{
for (i = 0; i < supported->num_values; i ++)
if (!_cups_strcasecmp(format, supported->values[i].string.text))
break;
if (i >= supported->num_values && attr)
respond_unsupported(client, attr);
}
return (!client->response->attrs ||
!client->response->attrs->next ||
!client->response->attrs->next->next);
}
static int
valid_job_attributes(
_ipp_client_t *client)
{
int i;
ipp_attribute_t *attr,
*supported;
valid_doc_attributes(client);
if ((attr = ippFindAttribute(client->request, "copies",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 || attr->value_tag != IPP_TAG_INTEGER ||
attr->values[0].integer < 1 || attr->values[0].integer > 999)
{
respond_unsupported(client, attr);
}
}
if ((attr = ippFindAttribute(client->request, "ipp-attribute-fidelity",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 || attr->value_tag != IPP_TAG_BOOLEAN)
{
respond_unsupported(client, attr);
}
}
if ((attr = ippFindAttribute(client->request, "job-hold-until",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 ||
(attr->value_tag != IPP_TAG_NAME &&
attr->value_tag != IPP_TAG_NAMELANG &&
attr->value_tag != IPP_TAG_KEYWORD) ||
strcmp(attr->values[0].string.text, "no-hold"))
{
respond_unsupported(client, attr);
}
}
if ((attr = ippFindAttribute(client->request, "job-name",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 ||
(attr->value_tag != IPP_TAG_NAME &&
attr->value_tag != IPP_TAG_NAMELANG))
{
respond_unsupported(client, attr);
}
}
if ((attr = ippFindAttribute(client->request, "job-priority",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 || attr->value_tag != IPP_TAG_INTEGER ||
attr->values[0].integer < 1 || attr->values[0].integer > 100)
{
respond_unsupported(client, attr);
}
}
if ((attr = ippFindAttribute(client->request, "job-sheets",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 ||
(attr->value_tag != IPP_TAG_NAME &&
attr->value_tag != IPP_TAG_NAMELANG &&
attr->value_tag != IPP_TAG_KEYWORD) ||
strcmp(attr->values[0].string.text, "none"))
{
respond_unsupported(client, attr);
}
}
if ((attr = ippFindAttribute(client->request, "media",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 ||
(attr->value_tag != IPP_TAG_NAME &&
attr->value_tag != IPP_TAG_NAMELANG &&
attr->value_tag != IPP_TAG_KEYWORD))
{
respond_unsupported(client, attr);
}
else
{
for (i = 0;
i < (int)(sizeof(media_supported) / sizeof(media_supported[0]));
i ++)
if (!strcmp(attr->values[0].string.text, media_supported[i]))
break;
if (i >= (int)(sizeof(media_supported) / sizeof(media_supported[0])))
{
respond_unsupported(client, attr);
}
}
}
if ((attr = ippFindAttribute(client->request, "media-col",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 || attr->value_tag != IPP_TAG_BEGIN_COLLECTION)
{
respond_unsupported(client, attr);
}
}
if ((attr = ippFindAttribute(client->request, "multiple-document-handling",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD ||
(strcmp(attr->values[0].string.text,
"separate-documents-uncollated-copies") &&
strcmp(attr->values[0].string.text,
"separate-documents-collated-copies")))
{
respond_unsupported(client, attr);
}
}
if ((attr = ippFindAttribute(client->request, "orientation-requested",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 || attr->value_tag != IPP_TAG_ENUM ||
attr->values[0].integer < IPP_PORTRAIT ||
attr->values[0].integer > IPP_REVERSE_PORTRAIT)
{
respond_unsupported(client, attr);
}
}
if ((attr = ippFindAttribute(client->request, "page-ranges",
IPP_TAG_ZERO)) != NULL)
{
respond_unsupported(client, attr);
}
if ((attr = ippFindAttribute(client->request, "print-quality",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 || attr->value_tag != IPP_TAG_ENUM ||
attr->values[0].integer < IPP_QUALITY_DRAFT ||
attr->values[0].integer > IPP_QUALITY_HIGH)
{
respond_unsupported(client, attr);
}
}
if ((attr = ippFindAttribute(client->request, "printer-resolution",
IPP_TAG_ZERO)) != NULL)
{
respond_unsupported(client, attr);
}
if ((attr = ippFindAttribute(client->request, "sides",
IPP_TAG_ZERO)) != NULL)
{
if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD)
{
respond_unsupported(client, attr);
}
if ((supported = ippFindAttribute(client->printer->attrs, "sides",
IPP_TAG_KEYWORD)) != NULL)
{
for (i = 0; i < supported->num_values; i ++)
if (!strcmp(attr->values[0].string.text,
supported->values[i].string.text))
break;
if (i >= supported->num_values)
{
respond_unsupported(client, attr);
}
}
else
{
respond_unsupported(client, attr);
}
}
return (!client->response->attrs ||
!client->response->attrs->next ||
!client->response->attrs->next->next);
}