diff options
author | Didier Raboud <odyx@debian.org> | 2012-10-25 20:57:13 +0200 |
---|---|---|
committer | Didier Raboud <odyx@debian.org> | 2012-10-25 20:57:13 +0200 |
commit | 49a2853988b074d087e82c51aec4f9fc052a057d (patch) | |
tree | c38ece96005bc33bd4e133fd0037f3428fdc039d /test/ippserver.c | |
parent | a312f7e1ac68fb22275719f6208b670d9edd45b5 (diff) | |
download | cups-49a2853988b074d087e82c51aec4f9fc052a057d.tar.gz |
Imported Upstream version 1.5.0upstream/1.5.0
Diffstat (limited to 'test/ippserver.c')
-rw-r--r-- | test/ippserver.c | 4380 |
1 files changed, 4380 insertions, 0 deletions
diff --git a/test/ippserver.c b/test/ippserver.c new file mode 100644 index 00000000..fc10d52b --- /dev/null +++ b/test/ippserver.c @@ -0,0 +1,4380 @@ +/* + * "$Id: ippserver.c 9793 2011-05-20 03:49:49Z mike $" + * + * Sample IPP/2.0 server for CUPS. + * + * Copyright 2010-2011 by Apple Inc. + * + * These coded instructions, statements, and computer programs are the + * property of Apple Inc. and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "LICENSE.txt" + * which should have been included with this file. If this file is + * file is missing or damaged, see the license at "http://www.cups.org/". + * + * This file is subject to the Apple OS-Developed Software exception. + * + * Contents: + * + * main() - Main entry to the sample server. + * clean_jobs() - Clean out old (completed) jobs. + * compare_jobs() - Compare two jobs. + * copy_attribute() - Copy a single attribute. + * copy_attributes() - Copy attributes from one request to another. + * copy_job_attrs() - Copy job attributes to the response. + * create_client() - Accept a new network connection and create a + * client object. + * create_job() - Create a new job object from a Print-Job or + * Create-Job request. + * create_listener() - Create a listener socket. + * create_media_col() - Create a media-col value. + * create_printer() - Create, register, and listen for connections + * to a printer object. + * create_requested_array() - Create an array for requested-attributes. + * debug_attributes() - Print attributes in a request or response. + * delete_client() - Close the socket and free all memory used by + * a client object. + * delete_job() - Remove from the printer and free all memory + * used by a job object. + * delete_printer() - Unregister, close listen sockets, and free + * all memory used by a printer object. + * dnssd_callback() - Handle Bonjour registration events. + * find_job() - Find a job specified in a request. + * html_escape() - Write a HTML-safe string. + * html_printf() - Send formatted text to the client, quoting + * as needed. + * ipp_cancel_job() - Cancel a job. + * ipp_create_job() - Create a job object. + * ipp_get_job_attributes() - Get the attributes for a job object. + * ipp_get_jobs() - Get a list of job objects. + * ipp_get_printer_attributes() - Get the attributes for a printer object. + * ipp_print_job() - Create a job object with an attached + * document. + * ipp_send_document() - Add an attached document to a job object + * created with Create-Job. + * ipp_validate_job() - Validate job creation attributes. + * process_client() - Process client requests on a thread. + * process_http() - Process a HTTP request. + * process_ipp() - Process an IPP request. + * process_job() - Process a print job. + * register_printer() - Register a printer object via Bonjour. + * respond_http() - Send a HTTP response. + * respond_ipp() - Send an IPP response. + * run_printer() - Run the printer service. + * usage() - Show program usage. + * valid_job_attributes() - Determine whether the job attributes are + * valid. + */ + +/* + * Include necessary headers... + */ + +#include <cups/cups-private.h> +#ifdef HAVE_DNSSD +# include <dns_sd.h> +#endif /* HAVE_DNSSD */ +#include <sys/stat.h> +#include <poll.h> +#ifdef HAVE_SYS_MOUNT_H +# include <sys/mount.h> +#endif /* HAVE_SYS_MOUNT_H */ +#ifdef HAVE_SYS_STATFS_H +# include <sys/statfs.h> +#endif /* HAVE_SYS_STATFS_H */ +#ifdef HAVE_SYS_STATVFS_H +# include <sys/statvfs.h> +#endif /* HAVE_SYS_STATVFS_H */ +#ifdef HAVE_SYS_VFS_H +# include <sys/vfs.h> +#endif /* HAVE_SYS_VFS_H */ + + +/* + * Constants... + */ + +enum _ipp_preasons_e /* printer-state-reasons bit values */ +{ + _IPP_PRINTER_NONE = 0x0000, /* none */ + _IPP_PRINTER_OTHER = 0x0001, /* other */ + _IPP_PRINTER_COVER_OPEN = 0x0002, /* cover-open */ + _IPP_PRINTER_INPUT_TRAY_MISSING = 0x0004, + /* input-tray-missing */ + _IPP_PRINTER_MARKER_SUPPLY_EMPTY = 0x0008, + /* marker-supply-empty */ + _IPP_PRINTER_MARKER_SUPPLY_LOW = 0x0010, + /* marker-suply-low */ + _IPP_PRINTER_MARKER_WASTE_ALMOST_FULL = 0x0020, + /* marker-waste-almost-full */ + _IPP_PRINTER_MARKER_WASTE_FULL = 0x0040, + /* marker-waste-full */ + _IPP_PRINTER_MEDIA_EMPTY = 0x0080, /* media-empty */ + _IPP_PRINTER_MEDIA_JAM = 0x0100, /* media-jam */ + _IPP_PRINTER_MEDIA_LOW = 0x0200, /* media-low */ + _IPP_PRINTER_MEDIA_NEEDED = 0x0400, /* media-needed */ + _IPP_PRINTER_MOVING_TO_PAUSED = 0x0800, + /* moving-to-paused */ + _IPP_PRINTER_PAUSED = 0x1000, /* paused */ + _IPP_PRINTER_SPOOL_AREA_FULL = 0x2000,/* spool-area-full */ + _IPP_PRINTER_TONER_EMPTY = 0x4000, /* toner-empty */ + _IPP_PRINTER_TONER_LOW = 0x8000 /* toner-low */ +}; +typedef unsigned int _ipp_preasons_t; /* Bitfield for printer-state-reasons */ + +typedef enum _ipp_media_class_e +{ + _IPP_GENERAL, /* General-purpose size */ + _IPP_PHOTO_ONLY, /* Photo-only size */ + _IPP_ENV_ONLY /* Envelope-only size */ +} _ipp_media_class_t; + +static const char * const media_supported[] = +{ /* media-supported values */ + "iso_a4_210x297mm", /* A4 */ + "iso_a5_148x210mm", /* A5 */ + "iso_a6_105x148mm", /* A6 */ + "iso_dl_110x220mm", /* DL */ + "na_legal_8.5x14in", /* Legal */ + "na_letter_8.5x11in", /* Letter */ + "na_number-10_4.125x9.5in", /* #10 */ + "na_index-3x5_3x5in", /* 3x5 */ + "oe_photo-l_3.5x5in", /* L */ + "na_index-4x6_4x6in", /* 4x6 */ + "na_5x7_5x7in" /* 5x7 */ +}; +static const int media_col_sizes[][3] = +{ /* media-col-database sizes */ + { 21000, 29700, _IPP_GENERAL }, /* A4 */ + { 14800, 21000, _IPP_PHOTO_ONLY }, /* A5 */ + { 10500, 14800, _IPP_PHOTO_ONLY }, /* A6 */ + { 11000, 22000, _IPP_ENV_ONLY }, /* DL */ + { 21590, 35560, _IPP_GENERAL }, /* Legal */ + { 21590, 27940, _IPP_GENERAL }, /* Letter */ + { 10477, 24130, _IPP_ENV_ONLY }, /* #10 */ + { 7630, 12700, _IPP_PHOTO_ONLY }, /* 3x5 */ + { 8890, 12700, _IPP_PHOTO_ONLY }, /* L */ + { 10160, 15240, _IPP_PHOTO_ONLY }, /* 4x6 */ + { 12700, 17780, _IPP_PHOTO_ONLY } /* 5x7 */ +}; +static const char * const media_type_supported[] = + /* media-type-supported values */ +{ + "auto", + "cardstock", + "envelope", + "labels", + "other", + "photographic-glossy", + "photographic-high-gloss", + "photographic-matte", + "photographic-satin", + "photographic-semi-gloss", + "stationery", + "stationery-letterhead", + "transparency" +}; + + +/* + * Structures... + */ + +typedef struct _ipp_job_s _ipp_job_t; + +typedef struct _ipp_printer_s /**** Printer data ****/ +{ + int ipv4, /* IPv4 listener */ + ipv6; /* IPv6 listener */ +#ifdef HAVE_DNSSD + DNSServiceRef common_ref, /* Shared service connection */ + ipp_ref, /* Bonjour IPP service */ + http_ref, /* Bonjour HTTP service */ + printer_ref; /* Bonjour LPD service */ + TXTRecordRef ipp_txt; /* Bonjour IPP TXT record */ + char *dnssd_name; /* printer-dnssd-name */ +#endif /* HAVE_DNSSD */ + char *name, /* printer-name */ + *icon, /* Icon filename */ + *directory, /* Spool directory */ + *hostname, /* Hostname */ + *uri; /* printer-uri-supported */ + int port; /* Port */ + size_t urilen; /* Length of printer URI */ + ipp_t *attrs; /* Static attributes */ + ipp_pstate_t state; /* printer-state value */ + _ipp_preasons_t state_reasons; /* printer-state-reasons values */ + cups_array_t *jobs; /* Jobs */ + _ipp_job_t *active_job; /* Current active/pending job */ + int next_job_id; /* Next job-id value */ + _cups_rwlock_t rwlock; /* Printer lock */ +} _ipp_printer_t; + +struct _ipp_job_s /**** Job data ****/ +{ + int id; /* Job ID */ + char *name, /* job-name */ + *username, /* job-originating-user-name */ + *format; /* document-format */ + ipp_jstate_t state; /* job-state value */ + time_t processing, /* time-at-processing value */ + completed; /* time-at-completed value */ + ipp_t *attrs; /* Static attributes */ + int cancel; /* Non-zero when job canceled */ + char *filename; /* Print file name */ + int fd; /* Print file descriptor */ + _ipp_printer_t *printer; /* Printer */ +}; + +typedef struct _ipp_client_s /**** Client data ****/ +{ + http_t http; /* HTTP connection */ + ipp_t *request, /* IPP request */ + *response; /* IPP response */ + time_t start; /* Request start time */ + http_state_t operation; /* Request operation */ + ipp_op_t operation_id; /* IPP operation-id */ + char uri[1024]; /* Request URI */ + http_addr_t addr; /* Client address */ + _ipp_printer_t *printer; /* Printer */ + _ipp_job_t *job; /* Current job, if any */ +} _ipp_client_t; + + +/* + * Local functions... + */ + +static void clean_jobs(_ipp_printer_t *printer); +static int compare_jobs(_ipp_job_t *a, _ipp_job_t *b); +static ipp_attribute_t *copy_attribute(ipp_t *to, ipp_attribute_t *attr, + ipp_tag_t group_tag, int quickcopy); +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_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 /* HAVE_DNSSD */ + const char *directory); +static cups_array_t *create_requested_array(_ipp_client_t *client); +static void debug_attributes(const char *title, ipp_t *ipp); +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 /* HAVE_DNSSD */ +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, + ...) +# ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 2, 3))) +# endif /* __GNUC__ */ +; +static void ipp_cancel_job(_ipp_client_t *client); +#if 0 +static void ipp_create_job(_ipp_client_t *client); +#endif /* 0 */ +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); +#if 0 +static void ipp_send_document(_ipp_client_t *client); +#endif /* 0 */ +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 /* HAVE_DNSSD */ +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, ...) +#ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 3, 4))) +#endif /* __GNUC__ */ +; +static void run_printer(_ipp_printer_t *printer); +static void usage(int status); +static int valid_job_attributes(_ipp_client_t *client); + + +/* + * Globals... + */ + +static int KeepFiles = 0, + Verbosity = 0; + + +/* + * 'main()' - Main entry to the sample server. + */ + +int /* O - Exit status */ +main(int argc, /* I - Number of command-line args */ + char *argv[]) /* I - Command-line arguments */ +{ + int i; /* Looping var */ + const char *opt, /* Current option character */ + *servername = NULL, /* Server host name */ + *name = NULL, /* Printer name */ + *location = "", /* Location of printer */ + *make = "Test", /* Manufacturer */ + *model = "Printer", /* Model */ + *icon = "printer.png", /* Icon file */ + *formats = "application/pdf,image/jpeg"; + /* Supported formats */ +#ifdef HAVE_DNSSD + const char *regtype = "_ipp._tcp"; /* Bonjour service type */ +#endif /* HAVE_DNSSD */ + int port = 8631, /* Port number (0 = auto) TODO: FIX */ + duplex = 0, /* Duplex mode */ + ppm = 10, /* Pages per minute for mono */ + ppm_color = 0; /* Pages per minute for color */ + char directory[1024] = ""; /* Spool directory */ + _ipp_printer_t *printer; /* Printer object */ + + + /* + * Parse command-line arguments... + */ + + for (i = 1; i < argc; i ++) + if (argv[i][0] == '-') + { + for (opt = argv[i] + 1; *opt; opt ++) + switch (*opt) + { + case '2' : /* -2 (enable 2-sided printing) */ + duplex = 1; + break; + + case 'M' : /* -M manufacturer */ + i ++; + if (i >= argc) + usage(1); + make = argv[i]; + break; + + case 'd' : /* -d spool-directory */ + i ++; + if (i >= argc) + usage(1); + strlcpy(directory, argv[i], sizeof(directory)); + break; + + case 'f' : /* -f type/subtype[,...] */ + i ++; + if (i >= argc) + usage(1); + formats = argv[i]; + break; + + case 'h' : /* -h (show help) */ + usage(0); + break; + + case 'i' : /* -i icon.png */ + i ++; + if (i >= argc) + usage(1); + icon = argv[i]; + break; + + case 'k' : /* -k (keep files) */ + KeepFiles = 1; + break; + + case 'l' : /* -l location */ + i ++; + if (i >= argc) + usage(1); + location = argv[i]; + break; + + case 'm' : /* -m model */ + i ++; + if (i >= argc) + usage(1); + model = argv[i]; + break; + + case 'n' : /* -n hostname */ + i ++; + if (i >= argc) + usage(1); + servername = argv[i]; + break; + + case 'p' : /* -p port */ + i ++; + if (i >= argc || !isdigit(argv[i][0] & 255)) + usage(1); + port = atoi(argv[i]); + break; + +#ifdef HAVE_DNSSD + case 'r' : /* -r regtype */ + i ++; + if (i >= argc) + usage(1); + regtype = argv[i]; + break; +#endif /* HAVE_DNSSD */ + + case 's' : /* -s speed[,color-speed] */ + i ++; + if (i >= argc) + usage(1); + if (sscanf(argv[i], "%d,%d", &ppm, &ppm_color) < 1) + usage(1); + break; + + case 'v' : /* -v (be verbose) */ + Verbosity ++; + break; + + default : /* Unknown */ + 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); + + /* + * Apply defaults as needed... + */ + + 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); + } + + /* + * Create the printer... + */ + + if ((printer = create_printer(servername, name, location, make, model, icon, + formats, ppm, ppm_color, duplex, port, +#ifdef HAVE_DNSSD + regtype, +#endif /* HAVE_DNSSD */ + directory)) == NULL) + return (1); + + /* + * Run the print service... + */ + + run_printer(printer); + + /* + * Destroy the printer and exit... + */ + + delete_printer(printer); + + return (0); +} + + +/* + * 'clean_jobs()' - Clean out old (completed) jobs. + */ + +static void +clean_jobs(_ipp_printer_t *printer) /* I - Printer */ +{ + _ipp_job_t *job; /* Current job */ + time_t cleantime; /* Clean time */ + + + 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)); +} + + +/* + * 'compare_jobs()' - Compare two jobs. + */ + +static int /* O - Result of comparison */ +compare_jobs(_ipp_job_t *a, /* I - First job */ + _ipp_job_t *b) /* I - Second job */ +{ + return (b->id - a->id); +} + + +/* + * 'copy_attribute()' - Copy a single attribute. + */ + +static ipp_attribute_t * /* O - New attribute */ +copy_attribute( + ipp_t *to, /* O - Destination request/response */ + ipp_attribute_t *attr, /* I - Attribute to copy */ + ipp_tag_t group_tag, /* I - Group to put the copy in */ + int quickcopy) /* I - Do a quick copy? */ +{ + int i; /* Looping var */ + ipp_attribute_t *toattr; /* Destination attribute */ + + + if (Verbosity && attr->name) + { + char buffer[2048]; /* Attribute value */ + + _ippAttrString(attr, buffer, sizeof(buffer)); + + fprintf(stderr, "Copying %s (%s%s) %s\n", attr->name, + attr->num_values > 1 ? "1setOf " : "", + ippTagString(attr->value_tag & ~IPP_TAG_COPY), buffer); + } + + switch (attr->value_tag & ~IPP_TAG_COPY) + { + case IPP_TAG_ZERO : + toattr = ippAddSeparator(to); + break; + + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + toattr = ippAddIntegers(to, group_tag, attr->value_tag, + attr->name, attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].integer = attr->values[i].integer; + break; + + case IPP_TAG_BOOLEAN : + toattr = ippAddBooleans(to, group_tag, attr->name, + attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].boolean = attr->values[i].boolean; + break; + + case IPP_TAG_TEXT : + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + case IPP_TAG_URI : + case IPP_TAG_URISCHEME : + case IPP_TAG_CHARSET : + case IPP_TAG_LANGUAGE : + case IPP_TAG_MIMETYPE : + toattr = ippAddStrings(to, group_tag, + (ipp_tag_t)(attr->value_tag | quickcopy), + attr->name, attr->num_values, NULL, NULL); + + if (quickcopy) + { + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].string.text = attr->values[i].string.text; + } + else + { + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].string.text = + _cupsStrAlloc(attr->values[i].string.text); + } + break; + + case IPP_TAG_DATE : + toattr = ippAddDate(to, group_tag, attr->name, + attr->values[0].date); + break; + + case IPP_TAG_RESOLUTION : + toattr = ippAddResolutions(to, group_tag, attr->name, + attr->num_values, IPP_RES_PER_INCH, + NULL, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].resolution.xres = attr->values[i].resolution.xres; + toattr->values[i].resolution.yres = attr->values[i].resolution.yres; + toattr->values[i].resolution.units = attr->values[i].resolution.units; + } + break; + + case IPP_TAG_RANGE : + toattr = ippAddRanges(to, group_tag, attr->name, + attr->num_values, NULL, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].range.lower = attr->values[i].range.lower; + toattr->values[i].range.upper = attr->values[i].range.upper; + } + break; + + case IPP_TAG_TEXTLANG : + case IPP_TAG_NAMELANG : + toattr = ippAddStrings(to, group_tag, + (ipp_tag_t)(attr->value_tag | quickcopy), + attr->name, attr->num_values, NULL, NULL); + + if (quickcopy) + { + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].string.charset = attr->values[i].string.charset; + toattr->values[i].string.text = attr->values[i].string.text; + } + } + else + { + for (i = 0; i < attr->num_values; i ++) + { + if (!i) + toattr->values[i].string.charset = + _cupsStrAlloc(attr->values[i].string.charset); + else + toattr->values[i].string.charset = + toattr->values[0].string.charset; + + toattr->values[i].string.text = + _cupsStrAlloc(attr->values[i].string.text); + } + } + break; + + case IPP_TAG_BEGIN_COLLECTION : + toattr = ippAddCollections(to, group_tag, attr->name, + attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].collection = attr->values[i].collection; + attr->values[i].collection->use ++; + } + break; + + case IPP_TAG_STRING : + if (quickcopy) + { + toattr = ippAddOctetString(to, group_tag, attr->name, NULL, 0); + toattr->value_tag |= quickcopy; + toattr->values[0].unknown.data = attr->values[0].unknown.data; + toattr->values[0].unknown.length = attr->values[0].unknown.length; + } + else + toattr = ippAddOctetString(to, attr->group_tag, attr->name, + attr->values[0].unknown.data, + attr->values[0].unknown.length); + break; + + default : + toattr = ippAddIntegers(to, group_tag, attr->value_tag, + attr->name, attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].unknown.length = attr->values[i].unknown.length; + + if (toattr->values[i].unknown.length > 0) + { + if ((toattr->values[i].unknown.data = + malloc(toattr->values[i].unknown.length)) == NULL) + toattr->values[i].unknown.length = 0; + else + memcpy(toattr->values[i].unknown.data, + attr->values[i].unknown.data, + toattr->values[i].unknown.length); + } + } + break; /* anti-compiler-warning-code */ + } + + return (toattr); +} + + +/* + * 'copy_attributes()' - Copy attributes from one request to another. + */ + +static void +copy_attributes(ipp_t *to, /* I - Destination request */ + ipp_t *from, /* I - Source request */ + cups_array_t *ra, /* I - Requested attributes */ + ipp_tag_t group_tag, /* I - Group to copy */ + int quickcopy) /* I - Do a quick copy? */ +{ + ipp_attribute_t *fromattr; /* Source attribute */ + + + if (!to || !from) + return; + + for (fromattr = from->attrs; fromattr; fromattr = fromattr->next) + { + /* + * Filter attributes as needed... + */ + + 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)) + copy_attribute(to, fromattr, fromattr->group_tag, quickcopy); + } +} + + +/* + * 'copy_job_attrs()' - Copy job attributes to the response. + */ + +static void +copy_job_attributes( + _ipp_client_t *client, /* I - Client */ + _ipp_job_t *job, /* I - Job */ + cups_array_t *ra) /* I - requested-attributes */ +{ + copy_attributes(client->response, job->attrs, ra, IPP_TAG_ZERO, 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"); + 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); +} + + +/* + * 'create_client()' - Accept a new network connection and create a client + * object. + */ + +static _ipp_client_t * /* O - Client */ +create_client(_ipp_printer_t *printer, /* I - Printer */ + int sock) /* I - Listen socket */ +{ + _ipp_client_t *client; /* Client */ + int val; /* Parameter value */ + socklen_t addrlen; /* Length of address */ + + + 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; + + /* + * Accept the client and get the remote address... + */ + + 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"); + + /* + * Using TCP_NODELAY improves responsiveness, especially on systems + * with a slow loopback interface. Since we write large buffers + * when sending print files and requests, there shouldn't be any + * performance penalty for this... + */ + + val = 1; + setsockopt(client->http.fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, + sizeof(val)); + + return (client); +} + + +/* + * 'create_job()' - Create a new job object from a Print-Job or Create-Job + * request. + */ + +static _ipp_job_t * /* O - Job */ +create_job(_ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* Job */ + ipp_attribute_t *attr; /* Job attribute */ + char uri[1024]; /* job-uri value */ + + + _cupsRWLockWrite(&(client->printer->rwlock)); + if (client->printer->active_job && + client->printer->active_job->state < IPP_JOB_CANCELED) + { + /* + * Only accept a single job at a time... + */ + + _cupsRWLockWrite(&(client->printer->rwlock)); + return (NULL); + } + + /* + * Allocate and initialize the job object... + */ + + 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; + + /* + * Set all but the first two attributes to the job attributes group... + */ + + for (attr = job->attrs->attrs->next->next; attr; attr = attr->next) + attr->group_tag = IPP_TAG_JOB; + + /* + * Get the requesting-user-name, document format, and priority... + */ + + 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"; + + /* + * Add job description attributes and add to the jobs array... + */ + + 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); +} + + +/* + * 'create_listener()' - Create a listener socket. + */ + +static int /* O - Listener socket or -1 on error */ +create_listener(int family, /* I - Address family */ + int *port) /* IO - Port number */ +{ + int sock, /* Listener socket */ + val; /* Socket value */ + http_addr_t address; /* Listen address */ + socklen_t addrlen; /* Length of listen address */ + + + 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 /* IPV6_V6ONLY */ + + if (!*port) + { + /* + * Get the auto-assigned port number for the IPv4 socket... + */ + + /* TODO: This code does not appear to work - port is always 0... */ + 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); +} + + +/* + * 'create_media_col()' - Create a media-col value. + */ + +static ipp_t * /* O - media-col collection */ +create_media_col(const char *media, /* I - Media name */ + const char *type, /* I - Nedua type */ + int width, /* I - x-dimension in 2540ths */ + int length, /* I - y-dimension in 2540ths */ + int margins) /* I - Value for margins */ +{ + ipp_t *media_col = ippNew(), /* media-col value */ + *media_size = ippNew(); /* media-size value */ + char media_key[256]; /* media-key value */ + + + ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "x-dimension", + width); + ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "y-dimension", + length); + + 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); +} + + +/* + * 'create_printer()' - Create, register, and listen for connections to a + * printer object. + */ + +static _ipp_printer_t * /* O - Printer */ +create_printer(const char *servername, /* I - Server hostname (NULL for default) */ + const char *name, /* I - printer-name */ + const char *location, /* I - printer-location */ + const char *make, /* I - printer-make-and-model */ + const char *model, /* I - printer-make-and-model */ + const char *icon, /* I - printer-icons */ + const char *docformats, /* I - document-format-supported */ + int ppm, /* I - Pages per minute in grayscale */ + int ppm_color, /* I - Pages per minute in color (0 for gray) */ + int duplex, /* I - 1 = duplex, 0 = simplex */ + int port, /* I - Port for listeners or 0 for auto */ +#ifdef HAVE_DNSSD + const char *regtype, /* I - Bonjour service type */ +#endif /* HAVE_DNSSD */ + const char *directory) /* I - Spool directory */ +{ + int i, j; /* Looping vars */ + _ipp_printer_t *printer; /* Printer */ + char hostname[256], /* Hostname */ + uri[1024], /* Printer URI */ + icons[1024], /* printer-icons URI */ + adminurl[1024], /* printer-more-info URI */ + device_id[1024],/* printer-device-id */ + make_model[128];/* printer-make-and-model */ + int num_formats; /* Number of document-format-supported values */ + char *defformat, /* document-format-default value */ + *formats[100], /* document-format-supported values */ + *ptr; /* Pointer into string */ + const char *prefix; /* Prefix string */ + int num_database; /* Number of database values */ + ipp_attribute_t *media_col_database; + /* media-col-database value */ + ipp_t *media_col_default; + /* media-col-default value */ + ipp_value_t *media_col_value; + /* Current media-col-database value */ + int k_supported; /* Maximum file size supported */ +#ifdef HAVE_STATVFS + struct statvfs spoolinfo; /* FS info for spool directory */ + double spoolsize; /* FS size */ +#elif defined(HAVE_STATFS) + struct statfs spoolinfo; /* FS info for spool directory */ + double spoolsize; /* FS size */ +#endif /* HAVE_STATVFS */ + static const int orients[4] = /* orientation-requested-supported values */ + { + IPP_PORTRAIT, + IPP_LANDSCAPE, + IPP_REVERSE_LANDSCAPE, + IPP_REVERSE_PORTRAIT + }; + static const char * const versions[] =/* ipp-versions-supported values */ + { + "1.0", + "1.1", + "2.0" + }; + static const int ops[] = /* operations-supported values */ + { + IPP_PRINT_JOB, + IPP_VALIDATE_JOB, + IPP_CREATE_JOB, + IPP_SEND_DOCUMENT, + IPP_CANCEL_JOB, + IPP_GET_JOB_ATTRIBUTES, + IPP_GET_JOBS, + IPP_GET_PRINTER_ATTRIBUTES + }; + static const char * const charsets[] =/* charset-supported values */ + { + "us-ascii", + "utf-8" + }; + static const char * const job_creation[] = + { /* job-creation-attributes-supported values */ + "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-col-supported values */ + "media-bottom-margin", + "media-left-margin", + "media-right-margin", + "media-size", + "media-top-margin", + "media-type" + }; + static const int media_xxx_margin_supported[] = + { /* media-xxx-margin-supported values */ + 0, + 635 + }; + static const char * const multiple_document_handling[] = + { /* multiple-document-handling-supported values */ + "separate-documents-uncollated-copies", + "separate-documents-collated-copies" + }; + static const int print_quality_supported[] = + { /* print-quality-supported values */ + IPP_QUALITY_DRAFT, + IPP_QUALITY_NORMAL, + IPP_QUALITY_HIGH + }; + static const char * const sides_supported[] = + { /* sides-supported values */ + "one-sided", + "two-sided-long-edge", + "two-sided-short-edge" + }; + static const char * const which_jobs[] = + { /* which-jobs-supported values */ + "completed", + "not-completed", + "aborted", + "all", + "canceled", + "pending", + "pending-held", + "processing", + "processing-stopped" + }; + + + /* + * Allocate memory for the printer... + */ + + 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 /* HAVE_DNSSD */ + 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)); + + /* + * Create the listener sockets... + */ + + 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; + } + + /* + * Prepare values for the printer attributes... + */ + + 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)); + + /* + * Get the maximum spool size based on the size of the filesystem used for + * the spool directory. If the host OS doesn't support the statfs call + * or the filesystem is larger than 2TiB, always report INT_MAX. + */ + +#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 /* HAVE_STATVFS */ + + /* + * Create the printer attributes. This list of attributes is sorted to improve + * performance when the client provides a requested-attributes attribute... + */ + + printer->attrs = ippNew(); + + /* charset-configured */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY, + "charset-configured", NULL, "utf-8"); + + /* charset-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY, + "charset-supported", sizeof(charsets) / sizeof(charsets[0]), + NULL, charsets); + + /* color-supported */ + ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "color-supported", + ppm_color > 0); + + /* compression-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "compression-supported", NULL, "none"); + + /* copies-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "copies-default", 1); + + /* copies-supported */ + ippAddRange(printer->attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999); + + /* document-format-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE, + "document-format-default", NULL, defformat); + + /* document-format-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE, + "document-format-supported", num_formats, NULL, + (const char * const *)formats); + + /* finishings-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "finishings-default", IPP_FINISHINGS_NONE); + + /* finishings-supported */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "finishings-supported", IPP_FINISHINGS_NONE); + + /* generated-natural-language-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY, + "generated-natural-language-supported", NULL, "en"); + + /* ipp-versions-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "ipp-versions-supported", + sizeof(versions) / sizeof(versions[0]), NULL, versions); + + /* job-creation-attributes-supported */ + 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); + + /* job-k-octets-supported */ + ippAddRange(printer->attrs, IPP_TAG_PRINTER, "job-k-octets-supported", 0, + k_supported); + + /* job-priority-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "job-priority-default", 50); + + /* job-priority-supported */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "job-priority-supported", 100); + + /* job-sheets-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY, + "job-sheets-default", NULL, "none"); + + /* job-sheets-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY, + "job-sheets-supported", NULL, "none"); + + /* media-bottom-margin-supported */ + 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); + + /* media-col-database */ + 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; /* auto + envelope */ + else if (media_col_sizes[i][2] == _IPP_PHOTO_ONLY) + num_database += 12; /* auto + photographic-* + borderless */ + else + num_database += (int)(sizeof(media_type_supported) / + sizeof(media_type_supported[0])) + 6; + /* All types + borderless */ + } + + 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))) + { + /* + * Add borderless version for this combination... + */ + + 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 */ + 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); + + /* media-col-supported */ + 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); + + /* media-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "media-default", NULL, media_supported[0]); + + /* media-left-margin-supported */ + 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); + + /* media-right-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); + + /* media-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-top-margin-supported */ + 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); + + /* multiple-document-handling-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); + + /* multiple-document-jobs-supported */ + ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, + "multiple-document-jobs-supported", 0); + + /* natural-language-configured */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY, + "natural-language-configured", NULL, "en"); + + /* number-up-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "number-up-default", 1); + + /* number-up-supported */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "number-up-supported", 1); + + /* operations-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "operations-supported", sizeof(ops) / sizeof(ops[0]), ops); + + /* orientation-requested-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, + "orientation-requested-default", 0); + + /* orientation-requested-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "orientation-requested-supported", 4, orients); + + /* output-bin-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "output-bin-default", NULL, "face-down"); + + /* output-bin-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "output-bin-supported", NULL, "face-down"); + + /* pages-per-minute */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "pages-per-minute", ppm); + + /* pages-per-minute-color */ + if (ppm_color > 0) + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "pages-per-minute-color", ppm_color); + + /* pdl-override-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "pdl-override-supported", NULL, "attempted"); + + /* print-quality-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "print-quality-default", IPP_QUALITY_NORMAL); + + /* print-quality-supported */ + 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); + + /* printer-device-id */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, + "printer-device-id", NULL, device_id); + + /* printer-icons */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, + "printer-icons", NULL, icons); + + /* printer-is-accepting-jobs */ + ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "printer-is-accepting-jobs", + 1); + + /* printer-info */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info", + NULL, name); + + /* printer-location */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, + "printer-location", NULL, location); + + /* printer-make-and-model */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, + "printer-make-and-model", NULL, make_model); + + /* printer-more-info */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, + "printer-more-info", NULL, adminurl); + + /* printer-name */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name", + NULL, name); + + /* printer-resolution-default */ + ippAddResolution(printer->attrs, IPP_TAG_PRINTER, + "printer-resolution-default", IPP_RES_PER_INCH, 600, 600); + + /* printer-resolution-supported */ + ippAddResolution(printer->attrs, IPP_TAG_PRINTER, + "printer-resolution-supported", IPP_RES_PER_INCH, 600, 600); + + /* printer-uri-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, + "printer-uri-supported", NULL, uri); + + /* sides-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "sides-default", NULL, "one-sided"); + + /* sides-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "sides-supported", duplex ? 3 : 1, NULL, sides_supported); + + /* uri-authentication-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "uri-authentication-supported", NULL, "none"); + + /* uri-security-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "uri-security-supported", NULL, "none"); + + /* which-jobs-supported */ + 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); + +#ifdef HAVE_DNSSD + /* + * Register the printer with Bonjour... + */ + + if (!register_printer(printer, location, make, model, docformats, adminurl, + ppm_color > 0, duplex, regtype)) + goto bad_printer; +#endif /* HAVE_DNSSD */ + + /* + * Return it! + */ + + return (printer); + + + /* + * If we get here we were unable to create the printer... + */ + + bad_printer: + + delete_printer(printer); + return (NULL); +} + + +/* + * 'create_requested_array()' - Create an array for requested-attributes. + */ + +static cups_array_t * /* O - requested-attributes array */ +create_requested_array( + _ipp_client_t *client) /* I - Client */ +{ + int i; /* Looping var */ + ipp_attribute_t *requested; /* requested-attributes attribute */ + cups_array_t *ra; /* Requested attributes array */ + char *value; /* Current value */ + + + /* + * Get the requested-attributes attribute, and return NULL if we don't + * have one... + */ + + if ((requested = ippFindAttribute(client->request, "requested-attributes", + IPP_TAG_KEYWORD)) == NULL) + return (NULL); + + /* + * If the attribute contains a single "all" keyword, return NULL... + */ + + if (requested->num_values == 1 && + !strcmp(requested->values[0].string.text, "all")) + return (NULL); + + /* + * Create an array using "strcmp" as the comparison function... + */ + + 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); +} + + +/* + * 'debug_attributes()' - Print attributes in a request or response. + */ + +static void +debug_attributes(const char *title, /* I - Title */ + ipp_t *ipp) /* I - Request/response */ +{ + ipp_tag_t group_tag; /* Current group */ + ipp_attribute_t *attr; /* Current attribute */ + char buffer[2048]; /* String buffer for value */ + + + if (Verbosity <= 1) + return; + + fprintf(stderr, "%s:\n", title); + 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) + { + _ippAttrString(attr, buffer, sizeof(buffer)); + fprintf(stderr, " %s (%s%s) %s\n", attr->name, + attr->num_values > 1 ? "1setOf " : "", + ippTagString(attr->value_tag), buffer); + } + } +} + + +/* + * 'delete_client()' - Close the socket and free all memory used by a client + * object. + */ + +static void +delete_client(_ipp_client_t *client) /* I - Client */ +{ + if (Verbosity) + fprintf(stderr, "Closing connection from %s (%s)\n", client->http.hostname, + client->http.hostaddr->addr.sa_family == AF_INET ? "IPv4" : "IPv6"); + + /* + * Flush pending writes before closing... + */ + + httpFlushWrite(&(client->http)); + + if (client->http.fd >= 0) + close(client->http.fd); + + /* + * Free memory... + */ + + httpClearCookie(&(client->http)); + httpClearFields(&(client->http)); + + ippDelete(client->request); + + ippDelete(client->response); + + free(client); +} + + +/* + * 'delete_job()' - Remove from the printer and free all memory used by a job + * object. + */ + +static void +delete_job(_ipp_job_t *job) /* I - 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); +} + + +/* + * 'delete_printer()' - Unregister, close listen sockets, and free all memory + * used by a printer object. + */ + +static void +delete_printer(_ipp_printer_t *printer) /* I - 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 /* HAVE_DNSSD */ + + 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 +/* + * 'dnssd_callback()' - Handle Bonjour registration events. + */ + +static void +dnssd_callback( + DNSServiceRef sdRef, /* I - Service reference */ + DNSServiceFlags flags, /* I - Status flags */ + DNSServiceErrorType errorCode, /* I - Error, if any */ + const char *name, /* I - Service name */ + const char *regtype, /* I - Service type */ + const char *domain, /* I - Domain for service */ + _ipp_printer_t *printer) /* I - 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); + + /* No lock needed since only the main thread accesses/changes this */ + _cupsStrFree(printer->dnssd_name); + printer->dnssd_name = _cupsStrAlloc(name); + } +} +#endif /* HAVE_DNSSD */ + + +/* + * 'find_job()' - Find a job specified in a request. + */ + +static _ipp_job_t * /* O - Job or NULL */ +find_job(_ipp_client_t *client) /* I - Client */ +{ + ipp_attribute_t *attr; /* job-id or job-uri attribute */ + _ipp_job_t key, /* Job search key */ + *job; /* Matching job, if any */ + + + 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); +} + + +/* + * 'html_escape()' - Write a HTML-safe string. + */ + +static void +html_escape(_ipp_client_t *client, /* I - Client */ + const char *s, /* I - String to write */ + size_t slen) /* I - Number of characters to write */ +{ + const char *start, /* Start of segment */ + *end; /* End of string */ + + + 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); +} + + +/* + * 'html_printf()' - Send formatted text to the client, quoting as needed. + */ + +static void +html_printf(_ipp_client_t *client, /* I - Client */ + const char *format, /* I - Printf-style format string */ + ...) /* I - Additional arguments as needed */ +{ + va_list ap; /* Pointer to arguments */ + const char *start; /* Start of string */ + char size, /* Size character (h, l, L) */ + type; /* Format type character */ + int width, /* Width of field */ + prec; /* Number of characters of precision */ + char tformat[100], /* Temporary format string for sprintf() */ + *tptr, /* Pointer into temporary format */ + temp[1024]; /* Buffer for formatted numbers */ + char *s; /* Pointer to string */ + + + /* + * Loop through the format string, formatting as needed... + */ + + 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 == '*') + { + /* + * Get width from argument... + */ + + 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 == '*') + { + /* + * Get precision from argument... + */ + + 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' : /* Floating point formats */ + 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' : /* Integer formats */ + 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 /* HAVE_LONG_LONG */ + 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' : /* Pointer value */ + if ((width + 2) > sizeof(temp)) + break; + + sprintf(temp, tformat, va_arg(ap, void *)); + + httpWrite2(&(client->http), temp, strlen(temp)); + break; + + case 'c' : /* Character or character array */ + 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' : /* String */ + 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); +} + + +/* + * 'ipp_cancel_job()' - Cancel a job. + */ + +static void +ipp_cancel_job(_ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* Job information */ + + + /* + * Get the job... + */ + + if ((job = find_job(client)) == NULL) + return; + + /* + * See if the job is already completed, canceled, or aborted; if so, + * we can't cancel... + */ + + 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 : + /* + * Cancel the job... + */ + + _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; + } +} + + +#if 0 +/* + * 'ipp_create_job()' - Create a job object. + */ + +static void +ipp_create_job(_ipp_client_t *client) /* I - Client */ +{ +} +#endif /* 0 */ + + +/* + * 'ipp_get_job_attributes()' - Get the attributes for a job object. + */ + +static void +ipp_get_job_attributes( + _ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* Job */ + cups_array_t *ra; /* requested-attributes */ + + + 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); +} + + +/* + * 'ipp_get_jobs()' - Get a list of job objects. + */ + +static void +ipp_get_jobs(_ipp_client_t *client) /* I - Client */ +{ + ipp_attribute_t *attr; /* Current attribute */ + int job_comparison; /* Job comparison */ + ipp_jstate_t job_state; /* job-state value */ + int first_job_id, /* First job ID */ + limit, /* Maximum number of jobs to return */ + count; /* Number of jobs that match */ + const char *username; /* Username */ + _ipp_job_t *job; /* Current job pointer */ + cups_array_t *ra; /* Requested attributes array */ + + + /* + * See if the "which-jobs" attribute have been specified... + */ + + 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; + } + + /* + * See if they want to limit the number of jobs reported... + */ + + 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; + + /* + * See if we only want to see jobs for a specific user... + */ + + 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); + } + } + + /* + * OK, build a list of jobs for this printer... + */ + + if ((ra = create_requested_array(client)) == NULL && + !ippFindAttribute(client->request, "requested-attributes", + IPP_TAG_KEYWORD)) + { + /* + * IPP conformance - Get-Jobs has a default requested-attributes value of + * "job-id" and "job-uri". + */ + + 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)) + { + /* + * Filter out jobs that don't match... + */ + + 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)); +} + + +/* + * 'ipp_get_printer_attributes()' - Get the attributes for a printer object. + */ + +static void +ipp_get_printer_attributes( + _ipp_client_t *client) /* I - Client */ +{ + cups_array_t *ra; /* Requested attributes array */ + _ipp_printer_t *printer; /* Printer */ + + + /* + * Send the attributes... + */ + + 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;/* Number of reasons */ + const char *reasons[32]; /* Reason strings */ + + 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); +} + + +/* + * 'ipp_print_job()' - Create a job object with an attached document. + */ + +static void +ipp_print_job(_ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* New job */ + char filename[1024], /* Filename buffer */ + buffer[4096]; /* Copy buffer */ + ssize_t bytes; /* Bytes read */ + cups_array_t *ra; /* Attributes to send in response */ + + + /* + * Validate print job attributes... + */ + + if (!valid_job_attributes(client)) + { + httpFlush(&(client->http)); + return; + } + + /* + * Do we have a file to print? + */ + + if (client->http.state == HTTP_POST_SEND) + { + respond_ipp(client, IPP_BAD_REQUEST, "No file in request."); + return; + } + + /* + * Print the job... + */ + + if ((job = create_job(client)) == NULL) + { + respond_ipp(client, IPP_PRINTER_BUSY, "Currently printing another job."); + return; + } + + /* + * Create a file for the request data... + */ + + 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; /* Write error */ + + 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) + { + /* + * Got an error while reading the print data, so abort this job. + */ + + 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; /* Write error */ + + 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; + + /* + * Process the job... + */ + + 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; + } + + /* + * Return the job info... + */ + + 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); +} + + +#if 0 +/* + * 'ipp_send_document()' - Add an attached document to a job object created with + * Create-Job. + */ + +static void +ipp_send_document(_ipp_client_t *client)/* I - Client */ +{ +} +#endif /* 0 */ + + +/* + * 'ipp_validate_job()' - Validate job creation attributes. + */ + +static void +ipp_validate_job(_ipp_client_t *client) /* I - Client */ +{ +} + + +/* + * 'process_client()' - Process client requests on a thread. + */ + +static void * /* O - Exit status */ +process_client(_ipp_client_t *client) /* I - Client */ +{ + /* + * Loop until we are out of requests or timeout (30 seconds)... + */ + + while (httpWait(&(client->http), 30000)) + if (!process_http(client)) + break; + + /* + * Close the conection to the client and return... + */ + + delete_client(client); + + return (NULL); +} + + +/* + * 'process_http()' - Process a HTTP request. + */ + +int /* O - 1 on success, 0 on failure */ +process_http(_ipp_client_t *client) /* I - Client connection */ +{ + char line[4096], /* Line from client... */ + operation[64], /* Operation code from socket */ + uri[1024], /* URI */ + version[64], /* HTTP version number string */ + *ptr; /* Pointer into strings */ + int major, minor; /* HTTP version numbers */ + http_status_t status; /* Transfer status */ + ipp_state_t state; /* State of IPP transfer */ + + + /* + * Abort if we have an error on the connection... + */ + + if (client->http.error) + return (0); + + /* + * Clear state variables... + */ + + 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; + + /* + * Read a request from the connection... + */ + + while ((ptr = httpGets(line, sizeof(line) - 1, &(client->http))) != NULL) + if (*ptr) + break; + + if (!ptr) + return (0); + + /* + * Parse the request line... + */ + + 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; + } + + /* + * Handle full URLs in the request line... + */ + + if (!strncmp(client->uri, "http:", 5) || !strncmp(client->uri, "ipp:", 4)) + { + char scheme[32], /* Method/scheme */ + userpass[128], /* Username:password */ + hostname[HTTP_MAX_HOST];/* Hostname */ + int port; /* Port number */ + + /* + * Separate the URI into its components... + */ + + 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 + { + /* + * Decode URI + */ + + 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); + } + } + + /* + * Process the request... + */ + + 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; + + /* + * Parse incoming parameters until the status changes... + */ + + 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) + { + /* + * HTTP/1.1 and higher require the "Host:" field... + */ + + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + + /* + * Handle HTTP Upgrade... + */ + + if (!_cups_strcasecmp(client->http.fields[HTTP_FIELD_CONNECTION], "Upgrade")) + { + if (!respond_http(client, HTTP_NOT_IMPLEMENTED, NULL, 0)) + return (0); + } + + /* + * Handle HTTP Expect... + */ + + if (client->http.expect && + (client->operation == HTTP_POST || client->operation == HTTP_PUT)) + { + if (client->http.expect == HTTP_CONTINUE) + { + /* + * Send 100-continue header... + */ + + if (!respond_http(client, HTTP_CONTINUE, NULL, 0)) + return (0); + } + else + { + /* + * Send 417-expectation-failed header... + */ + + 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; + } + } + + /* + * Handle new transfers... + */ + + switch (client->operation) + { + case HTTP_OPTIONS : + /* + * Do HEAD/OPTIONS command... + */ + + 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")) + { + /* + * Send PNG icon file. + */ + + int fd; /* Icon file */ + struct stat fileinfo; /* Icon file information */ + char buffer[4096]; /* Copy buffer */ + ssize_t bytes; /* 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, "/")) + { + /* + * Show web status page... + */ + + 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)) + { + /* + * Negative content lengths are invalid... + */ + + return (respond_http(client, HTTP_BAD_REQUEST, NULL, 0)); + } + + if (strcmp(client->http.fields[HTTP_FIELD_CONTENT_TYPE], + "application/ipp")) + { + /* + * Not an IPP request... + */ + + return (respond_http(client, HTTP_BAD_REQUEST, NULL, 0)); + } + + /* + * Read the IPP request... + */ + + 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, + ippOpString(client->request->request.op.operation_id)); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + + /* + * Now that we have the IPP request, process the request... + */ + + return (process_ipp(client)); + + default : + break; /* Anti-compiler-warning-code */ + } + + return (1); +} + + +/* + * 'process_ipp()' - Process an IPP request. + */ + +static int /* O - 1 on success, 0 on error */ +process_ipp(_ipp_client_t *client) /* I - Client */ +{ + ipp_tag_t group; /* Current group tag */ + ipp_attribute_t *attr; /* Current attribute */ + ipp_attribute_t *charset; /* Character set attribute */ + ipp_attribute_t *language; /* Language attribute */ + ipp_attribute_t *uri; /* Printer URI attribute */ + + + debug_attributes("Request", client->request); + + /* + * First build an empty response message for this request... + */ + + 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; + + /* + * Then validate the request header and required attributes... + */ + + if (client->request->request.any.version[0] < 1 || + client->request->request.any.version[0] > 2) + { + /* + * Return an error, since we only support IPP 1.x and 2.x. + */ + + 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 + { + /* + * Make sure that the attributes are provided in the correct order and + * don't repeat groups... + */ + + for (attr = client->request->attrs, group = attr->group_tag; + attr; + attr = attr->next) + if (attr->group_tag < group && attr->group_tag != IPP_TAG_ZERO) + { + /* + * Out of order; return an error... + */ + + 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) + { + /* + * Then make sure that the first three attributes are: + * + * attributes-charset + * attributes-natural-language + * printer-uri/job-uri + */ + + 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")) + { + /* + * Bad character set... + */ + + respond_ipp(client, IPP_BAD_REQUEST, + "Unsupported character set \"%s\".", + charset->values[0].string.text); + } + else if (!charset || !language || !uri) + { + /* + * Return an error, since attributes-charset, + * attributes-natural-language, and printer-uri/job-uri are required + * for all operations. + */ + + 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 + { + /* + * Try processing the operation... + */ + + if (client->http.expect == HTTP_CONTINUE) + { + /* + * Send 100-continue header... + */ + + 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_VALIDATE_JOB : + ipp_validate_job(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; + } + } + } + } + + /* + * Send the HTTP header and return... + */ + + if (client->http.state != HTTP_POST_SEND) + httpFlush(&(client->http)); /* Flush trailing (junk) data */ + + return (respond_http(client, HTTP_OK, "application/ipp", + ippLength(client->response))); +} + + +/* + * 'process_job()' - Process a print job. + */ + +static void * /* O - Thread exit status */ +process_job(_ipp_job_t *job) /* I - 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 +/* + * 'register_printer()' - Register a printer object via Bonjour. + */ + +static int /* O - 1 on success, 0 on error */ +register_printer( + _ipp_printer_t *printer, /* I - Printer */ + const char *location, /* I - Location */ + const char *make, /* I - Manufacturer */ + const char *model, /* I - Model name */ + const char *formats, /* I - Supported formats */ + const char *adminurl, /* I - Web interface URL */ + int color, /* I - 1 = color, 0 = monochrome */ + int duplex, /* I - 1 = duplex, 0 = simplex */ + const char *regtype) /* I - Service type */ +{ + DNSServiceErrorType error; /* Error from Bonjour */ + char make_model[256],/* Make and model together */ + product[256]; /* Product string */ + + + /* + * Build the TXT record for IPP... + */ + + 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"); + + /* + * Create a shared service reference for Bonjour... + */ + + if ((error = DNSServiceCreateConnection(&(printer->common_ref))) + != kDNSServiceErr_NoError) + { + fprintf(stderr, "Unable to create mDNSResponder connection: %d\n", error); + return (0); + } + + /* + * Register the _printer._tcp (LPD) service type with a port number of 0 to + * defend our service name but not actually support LPD... + */ + + printer->printer_ref = printer->common_ref; + + if ((error = DNSServiceRegister(&(printer->printer_ref), + kDNSServiceFlagsShareConnection, + 0 /* interfaceIndex */, printer->dnssd_name, + "_printer._tcp", NULL /* domain */, + NULL /* host */, 0 /* port */, 0 /* txtLen */, + NULL /* txtRecord */, + (DNSServiceRegisterReply)dnssd_callback, + printer)) != kDNSServiceErr_NoError) + { + fprintf(stderr, "Unable to register \"%s._printer._tcp\": %d\n", + printer->dnssd_name, error); + return (0); + } + + /* + * Then register the _ipp._tcp (IPP) service type with the real port number to + * advertise our IPP printer... + */ + + printer->ipp_ref = printer->common_ref; + + if ((error = DNSServiceRegister(&(printer->ipp_ref), + kDNSServiceFlagsShareConnection, + 0 /* interfaceIndex */, printer->dnssd_name, + regtype, NULL /* domain */, + NULL /* host */, 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); + } + + /* + * Similarly, register the _http._tcp,_printer (HTTP) service type with the + * real port number to advertise our IPP printer... + */ + + printer->http_ref = printer->common_ref; + + if ((error = DNSServiceRegister(&(printer->http_ref), + kDNSServiceFlagsShareConnection, + 0 /* interfaceIndex */, printer->dnssd_name, + "_http._tcp,_printer", NULL /* domain */, + NULL /* host */, htons(printer->port), + 0 /* txtLen */, NULL, /* txtRecord */ + (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 /* HAVE_DNSSD */ + + +/* + * 'respond_http()' - Send a HTTP response. + */ + +int /* O - 1 on success, 0 on failure */ +respond_http(_ipp_client_t *client, /* I - Client */ + http_status_t code, /* I - HTTP status of response */ + const char *type, /* I - MIME type of response */ + size_t length) /* I - Length of response */ +{ + char message[1024]; /* Text message */ + + + fprintf(stderr, "%s %s\n", client->http.hostname, httpStatus(code)); + + if (code == HTTP_CONTINUE) + { + /* + * 100-continue doesn't send any headers... + */ + + return (httpPrintf(&(client->http), "HTTP/%d.%d 100 Continue\r\n\r\n", + client->http.version / 100, + client->http.version % 100) > 0); + } + + /* + * Format an error message... + */ + + 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'; + + /* + * Send the HTTP status header... + */ + + 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); + + /* + * Follow the header with the response fields... + */ + + 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); + + /* + * Send the response data... + */ + + if (message[0]) + { + /* + * Send a plain text message. + */ + + if (httpPrintf(&(client->http), "%s", message) < 0) + return (0); + } + else if (client->response) + { + /* + * Send an IPP response... + */ + + debug_attributes("Response", client->response); + + 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; + + /* + * Flush the data and return... + */ + + return (httpFlushWrite(&(client->http)) >= 0); +} + + +/* + * 'respond_ipp()' - Send an IPP response. + */ + +static void +respond_ipp(_ipp_client_t *client, /* I - Client */ + ipp_status_t status, /* I - status-code */ + const char *message, /* I - printf-style status-message */ + ...) /* I - Additional args as needed */ +{ + va_list ap; /* Pointer to additional args */ + char formatted[1024]; /* Formatted errror message */ + + + 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); +} + + +/* + * 'run_printer()' - Run the printer service. + */ + +static void +run_printer(_ipp_printer_t *printer) /* I - Printer */ +{ + int num_fds; /* Number of file descriptors */ + struct pollfd polldata[3]; /* poll() data */ + int timeout; /* Timeout for poll() */ + _ipp_client_t *client; /* New client */ + + + /* + * Setup poll() data for the Bonjour service socket and IPv4/6 listeners... + */ + + 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 /* HAVE_DNSSD */ + + /* + * Loop until we are killed or have a hard error... + */ + + 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 /* HAVE_DNSSD */ + + /* + * Clean out old jobs... + */ + + clean_jobs(printer); + } +} + + +/* + * 'usage()' - Show program usage. + */ + +static void +usage(int status) /* O - Exit 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); +} + + +/* + * 'valid_job_attributes()' - Determine whether the job attributes are valid. + * + * When one or more job attributes are invalid, this function adds a suitable + * response and attributes to the unsupported group. + */ + +static int /* O - 1 if valid, 0 if not */ +valid_job_attributes( + _ipp_client_t *client) /* I - Client */ +{ + int i; /* Looping var */ + ipp_attribute_t *attr, /* Current attribute */ + *supported; /* document-format-supported */ + const char *format = NULL; /* document-format value */ + int valid = 1; /* Valid attributes? */ + + + /* + * Check operation attributes... + */ + +#define respond_unsupported(client, attr) \ + if (valid) \ + { \ + respond_ipp(client, IPP_ATTRIBUTES, "Unsupported %s %s%s value.", \ + attr->name, attr->num_values > 1 ? "1setOf " : "", \ + ippTagString(attr->value_tag)); \ + valid = 0; \ + } \ + copy_attribute(client->response, attr, IPP_TAG_UNSUPPORTED_GROUP, 0) + + if ((attr = ippFindAttribute(client->request, "compression", + IPP_TAG_ZERO)) != NULL) + { + /* + * If compression is specified, only accept "none"... + */ + + 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 Print-Job compression=\"%s\"\n", + client->http.hostname, attr->values[0].string.text); + } + + /* + * Is it a format we support? + */ + + 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_VALIDATE_JOB) + { + /* + * Auto-type the file using the first 4 bytes of the file... + */ + + unsigned char header[4]; /* First 4 bytes of file */ + + 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) + 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 ((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) + { + respond_unsupported(client, attr); + } + } + + /* + * Check the various job template attributes... + */ + + 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); + } + /* TODO: check for valid media-col */ + } + + 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 (valid); +} + + +/* + * End of "$Id: ippserver.c 9793 2011-05-20 03:49:49Z mike $". + */ |