diff -Nru cups-filters-1.0.52/debian/changelog cups-filters-1.0.52/debian/changelog --- cups-filters-1.0.52/debian/changelog 2014-06-04 13:26:23.000000000 +0200 +++ cups-filters-1.0.52/debian/changelog 2014-10-27 16:21:30.000000000 +0100 @@ -1,3 +1,26 @@ +cups-filters (1.0.52-0ubuntu1.3) trusty-proposed; urgency=low + + * Added full support for the IPP Everywhere standard for driverless + printing (LP: #1386241): + - Added ippusbxd (Support for IPP-over-USB printers) generating the + new binary package cups-filters-ippusbxd. + - Added rastertopdf filter. This filter allows PWG Raster as input + format for a CUPS queue. This is needed to make shared CUPS printers + fully emulating IPP Everywhere printers (all other requirements are + fulfilled by CUPS itself). + - rastertopdf-mime-convs.patch: Added conversion rule for rastertopdf + filter. + - add-ipp-everywhere-ppd.patch: Added PPD file for a generic IPP + Everywhere printer (on-the-fly generation via cupsfilters.drv). + - support-for-pwgraster-output-with-ppd.patch: Support for PWG-Raster + output selected via keyword in the PPD file. + - pdftoraster-cspace-18-19-20.patch: pdftoraster: Support for output + in the color spaces 18 (CUPS_CSPACE_SW, sGray), 19 (CUPS_CSPACE_SRGB, + sRGB), and 20 (CUPS_CSPACE_ADOBERGB, Adobe RGB). No color management + appropriate to these color spaces is added yet. + + -- Till Kamppeter Mon, 27 Oct 2014 16:21:06 +0100 + cups-filters (1.0.52-0ubuntu1.2) trusty-proposed; urgency=low * Re-added ./configure option "--with-pdftops=hybrid" to activate diff -Nru cups-filters-1.0.52/debian/control cups-filters-1.0.52/debian/control --- cups-filters-1.0.52/debian/control 2014-05-05 19:12:36.000000000 +0200 +++ cups-filters-1.0.52/debian/control 2014-10-23 13:17:17.000000000 +0200 @@ -38,7 +38,10 @@ libavahi-glib-dev, librsvg2-bin, # Needed for tests - fonts-dejavu-core + fonts-dejavu-core, +# Needed for ippusbxd + libudev-dev, + libusb-1.0-0-dev Homepage: http://www.openprinting.org/ Vcs-Git: https://alioth.debian.org/anonscm/git/printing/cups-filters.git Vcs-Browser: http://anonscm.debian.org/gitweb/?p=printing/cups-filters.git @@ -175,3 +178,20 @@ #Description: OpenPrinting CUPS Filters - Debugging symbols # This package provides the debugging symbols for OpenPrinting # CUPS Filters. + +Package: cups-filters-ippusbxd +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends} +Recommends: system-config-printer-udev +Enhances: cups +Description: ippusbxd - Daemon for IPP-over-USB printer support + This package provides ippusbxd, a daemon which lets IPP-over-USB + printers appear as regular network IPP printers and so one can prints + on them with CUPS ("ipp" backend) and access the configuration web + interface with any browser. + . + We have added ippusbxd as a binary package to cups-filters only + temporarily to get ippusbxd into Utopic before Feature Freeze. Later + on, an own source package for ippusbxd will get introduced in Debian + and synced to Ubuntu, its binary package replacing this package. diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/behaviour cups-filters-1.0.52/debian/local/ippusbxd/behaviour --- cups-filters-1.0.52/debian/local/ippusbxd/behaviour 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/behaviour 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,2 @@ +- Must use CRLF for transfer: chunked messages +- Rotates USB interfaces between TCP connections diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/http.c cups-filters-1.0.52/debian/local/ippusbxd/http.c --- cups-filters-1.0.52/debian/local/ippusbxd/http.c 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/http.c 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,594 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include + +#include "http.h" +#include "logging.h" + +#define BUFFER_STEP (1 << 12) + +struct http_message_t *http_message_new() +{ + struct http_message_t *msg = calloc(1, sizeof(*msg)); + if (msg == NULL) { + ERR("failed to alloc space for http message"); + return NULL; + } + + msg->spare_capacity = 0; + msg->spare_filled = 0; + msg->spare_buffer = NULL; + + return msg; +} + +void message_free(struct http_message_t *msg) +{ + free(msg->spare_buffer); + free(msg); +} + +static void packet_check_completion(struct http_packet_t *pkt) +{ + struct http_message_t *msg = pkt->parent_message; + // Msg full + if (msg->claimed_size && msg->received_size >= msg->claimed_size) { + msg->is_completed = 1; + + // Sanity check + if (msg->spare_filled > 0) + ERR_AND_EXIT("Msg spare not empty upon completion"); + } + + // Pkt full + if (pkt->expected_size && pkt->filled_size >= pkt->expected_size) + pkt->is_completed = 1; + + // Pkt at capacity + if (pkt->filled_size == pkt->buffer_capacity) { + pkt->is_completed = 1; + msg->is_completed = 1; + } else if (pkt->filled_size > pkt->buffer_capacity) { + // Santiy check + ERR_AND_EXIT("Overflowed packet buffer"); + } +} + +static int doesMatch(const char *matcher, size_t matcher_len, + const uint8_t *key, size_t key_len) +{ + for (size_t i = 0; i < matcher_len; i++) + if (i >= key_len || matcher[i] != key[i]) + return 0; + return 1; +} + +static int inspect_header_field(struct http_packet_t *pkt, size_t header_size, + char *key, size_t key_size) +{ + // Find key + uint8_t *pos = memmem(pkt->buffer, header_size, key, key_size); + if (pos == NULL) + return -1; + + // Find first digit + size_t number_pos = (size_t) (pos - pkt->buffer) + key_size; + while (number_pos < pkt->filled_size && !isdigit(pkt->buffer[number_pos])) + ++number_pos; + + // Find next non-digit + size_t number_end = number_pos; + while (number_end < pkt->filled_size && isdigit(pkt->buffer[number_end])) + ++number_end; + + // Failed to find next non-digit + // header field might be broken + if (number_end >= pkt->filled_size) + return -1; + + // Temporary stringification of buffer for atoi() + uint8_t original_char = pkt->buffer[number_end]; + pkt->buffer[number_end] = '\0'; + int val = atoi((const char *)(pkt->buffer + number_pos)); + + // Restore buffer + pkt->buffer[number_end] = original_char; + return val; +} + +static void packet_store_excess(struct http_packet_t *pkt) +{ + struct http_message_t *msg = pkt->parent_message; + if (msg->spare_buffer != NULL) + ERR_AND_EXIT("Do not store excess to non-empty packet"); + + if (pkt->expected_size >= pkt->filled_size) + ERR_AND_EXIT("Do not call packet_store_excess() unless needed"); + + size_t spare_size = pkt->filled_size - pkt->expected_size; + size_t non_spare = pkt->expected_size; + NOTE("HTTP: Storing %d bytes of excess", spare_size); + + // Align to BUFFER_STEP + size_t needed_size = 0; + needed_size += spare_size / BUFFER_STEP; + needed_size += (spare_size % BUFFER_STEP) > 0 ? BUFFER_STEP : 0; + + if (msg->spare_buffer == NULL) { + uint8_t *buffer = calloc(1, needed_size); + if (buffer == NULL) + ERR_AND_EXIT("Failed to alloc msg spare buffer"); + + msg->spare_buffer = buffer; + } + + memcpy(msg->spare_buffer, pkt->buffer + non_spare, spare_size); + pkt->filled_size = non_spare; + + msg->spare_capacity = needed_size; + msg->spare_filled = spare_size; +} + +static void packet_take_spare(struct http_packet_t *pkt) +{ + struct http_message_t *msg = pkt->parent_message; + if (msg->spare_filled == 0) + return; + + if (msg->spare_buffer == NULL) + return; + + if (pkt->filled_size > 0) + ERR_AND_EXIT("pkt should be empty when loading msg spare"); + + // Take message's buffer + size_t msg_size = msg->spare_capacity; + size_t msg_filled = msg->spare_filled; + uint8_t *msg_buffer = msg->spare_buffer; + + pkt->buffer_capacity = msg_size; + pkt->filled_size = msg_filled; + pkt->buffer = msg_buffer; + + msg->spare_capacity = 0; + msg->spare_filled = 0; + msg->spare_buffer = NULL; +} + +static ssize_t packet_find_chunked_size(struct http_packet_t *pkt) +{ + // NOTE: + // chunks can have trailers which are + // tacked on http header fields. + // NOTE: + // chunks may also have extensions. + // No one uses or supports them. + + // Find end of size string + if (pkt->filled_size >= SSIZE_MAX) + ERR_AND_EXIT("Buffer beyond sane size"); + + ssize_t max = (ssize_t) pkt->filled_size; + ssize_t size_end = -1; + ssize_t miniheader_end = -1; + ssize_t delimiter_start = -1; + for (ssize_t i = 0; i < max; i++) { + + uint8_t *buf = pkt->buffer; + if (size_end < 0) { + // No extension + if (i + 1 < max && ( + buf[i] == '\r' && // CR + buf[i + 1] == '\n')// LF + ) { + size_end = i + 1; + miniheader_end = size_end; + delimiter_start = i; + break; + } + + // No extension + if (buf[i] == '\n') // LF + { + size_end = i; + miniheader_end = size_end; + delimiter_start = i; + break; + } + + // Has extensions + if (buf[i] == ';') + { + size_end = i; + continue; + } + } + if (miniheader_end < 0) { + if (i + 1 < max && ( + buf[i] == '\r' && // CR + buf[i + 1] == '\n')// LF + ) { + miniheader_end = i + 1; + delimiter_start = i; + break; + } + + if (buf[i] == '\n') // LF + { + miniheader_end = i; + delimiter_start = i; + break; + } + } + } + + if (miniheader_end < 0) { + // NOTE: knowing just the size field + // is not enough since the extensions + // are not included in the size + NOTE("failed to find chunk mini-header so far"); + return -1; + } + + // Temporary stringification for strtol() + uint8_t original_char = *(pkt->buffer + size_end); + *(pkt->buffer + size_end) = '\0'; + size_t size = strtoul((char *)pkt->buffer, NULL, 16); + NOTE("Chunk size raw: %s", pkt->buffer); + *(pkt->buffer + size_end) = original_char; + if (size > SSIZE_MAX) + ERR_AND_EXIT("chunk size is insane"); + + if (size > 0) { + // Regular chunk + ssize_t chunk_size = (ssize_t) size; // Chunk body + chunk_size += miniheader_end + 1; // Mini-header + chunk_size += 2; // Trailing CRLF + NOTE("HTTP: Chunk size: %lu", chunk_size); + return (ssize_t) chunk_size; + } + + // Terminator chunk + // May have trailers in body + ssize_t full_size = -1; + for (ssize_t i = delimiter_start; i < max; i++) { + uint8_t *buf = pkt->buffer; + if (i + 3 < max && ( + buf[i] == '\r' && // CR + buf[i + 1] == '\n' && // LF + buf[i + 2] == '\r' && // CR + buf[i + 3] == '\n') // LF + ) { + full_size = i + 4; + break; + } + + if (i + 1 < max && + buf[i] == '\n' && // LF + buf[i + 1] == '\n') // LF + { + full_size = i + 2; + break; + } + } + + if (full_size < 0) { + NOTE("Chunk miniheader present but body incomplete"); + return -1; + } + + NOTE("Found end chunked packet"); + pkt->parent_message->is_completed = 1; + pkt->is_completed = 1; + return full_size; +} + +static ssize_t packet_get_header_size(struct http_packet_t *pkt) +{ + if (pkt->header_size != 0) + goto found; + + /* RFC2616 recomends we match newline on \n despite full + * complience requires the message to use only \r\n + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3 + */ + + // Find header + for (size_t i = 0; i < pkt->filled_size && i < SSIZE_MAX; i++) { + // two \r\n pairs + if ((i + 3) < pkt->filled_size && + '\r' == pkt->buffer[i] && + '\n' == pkt->buffer[i + 1] && + '\r' == pkt->buffer[i + 2] && + '\n' == pkt->buffer[i + 3]) { + pkt->header_size = i + 4; + goto found; + } + + // two \n pairs + if ((i + 1) < pkt->filled_size && + '\n' == pkt->buffer[i] && + '\n' == pkt->buffer[i + 1]) { + pkt->header_size = i + 2; + goto found; + } + } + + return -1; + +found: + return (ssize_t) pkt->header_size; +} + +enum http_request_t packet_find_type(struct http_packet_t *pkt) +{ + enum http_request_t type = HTTP_UNSET; + size_t size = 0; + /* Valid methods for determining http request + * size are defined by W3 in RFC2616 section 4.4 + * link: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 + */ + + /* This function attempts to find what method this + * packet would use. This is only possible in specific case: + * 1. if the request uses method 1 we can check the http + * request type. We must be called on a packet which + * has the full header. + * 2. if the request uses method 2 we need the full header + * but a simple network-byte-order-aware string search + * works. This function does not work if called with + * a chunked transport's sub-packet. + * 3. if the request uses method 3 we again perform the + * string search. + * + * All cases require the packat to contain the full header. + */ + + ssize_t header_size_raw = packet_get_header_size(pkt); + if (header_size_raw < 0) { + // We don't have the header yet + goto do_ret; + } + size_t header_size = (size_t) header_size_raw; + + // Try Transfer-Encoding Chunked + char xfer_encode_str[] = "Transfer-Encoding: chunked"; + size_t xfer_encode_str_size = sizeof(xfer_encode_str) - 1; + uint8_t *xfer_encode_pos = memmem(pkt->buffer, header_size, + xfer_encode_str, + xfer_encode_str_size); + if (xfer_encode_pos != NULL) { + size = 0; + type = HTTP_CHUNKED; + goto do_ret; + } + + // Try Content-Length + char content_length_str[] = "Content-Length: "; + ssize_t contlen_size = inspect_header_field(pkt, header_size, + content_length_str, sizeof(content_length_str) - 1); + if (contlen_size >= 0) { + size = (size_t) contlen_size + header_size; + type = HTTP_CONTENT_LENGTH; + goto do_ret; + } + + // Get requests + if (doesMatch("GET", 3, pkt->buffer, pkt->filled_size)) { + size = header_size; + type = HTTP_HEADER_ONLY; + goto do_ret; + } + + // No size was detectable yet header was found + type = HTTP_UNKNOWN; + size = 0; + +do_ret: + pkt->parent_message->claimed_size = size; + pkt->parent_message->type = type; + return type; +} + +size_t packet_pending_bytes(struct http_packet_t *pkt) +{ + struct http_message_t *msg = pkt->parent_message; + + // Check Cache + if (pkt->expected_size > 0) + goto pending_known; + + if (HTTP_UNSET == msg->type) { + msg->type = packet_find_type(pkt); + + if (HTTP_CHUNKED == msg->type) { + // Note: this was the packet with the + // header of our chunked message. + + // Save any non-header data we got + ssize_t header_size = packet_get_header_size(pkt); + + // Sanity check + if (header_size < 0 || + (size_t)header_size > pkt->filled_size) + ERR_AND_EXIT("HTTP: Could not find header twice"); + + NOTE("HTTP: Chunked header size is %ld bytes", + header_size); + pkt->expected_size = (size_t) header_size; + msg->claimed_size = 0; + goto pending_known; + } + } + + + if (HTTP_CHUNKED == msg->type) { + if (pkt->filled_size == 0) { + // Grab chunk's mini-header + goto pending_known; + } + + if (pkt->expected_size == 0) { + // Check chunk's mini-header + ssize_t size = packet_find_chunked_size(pkt); + if (size <= 0) { + ERR("============================================="); + ERR("Malformed chunk-transport http header receivd"); + ERR("Missing chunk's mini-headers in first data"); + ERR("Have %d bytes", pkt->filled_size); + printf("%.*s\n", (int)pkt->filled_size, pkt->buffer); + ERR("Malformed chunk-transport http header receivd"); + ERR("============================================="); + goto pending_known; + } + + pkt->expected_size = (size_t) size; + msg->claimed_size = 0; + } + + goto pending_known; + } + if (HTTP_HEADER_ONLY == msg->type) { + // Note: we can only know it is header only + // when the buffer already contains the header. + // So this next call cannot fail. + pkt->expected_size = (size_t) packet_get_header_size(pkt); + msg->claimed_size = pkt->expected_size; + goto pending_known; + } + if (HTTP_CONTENT_LENGTH == msg->type) { + // Note: find_header() has + // filled msg's claimed_size + msg->claimed_size = msg->claimed_size; + pkt->expected_size = msg->claimed_size; + goto pending_known; + } + +pending_known: + + // Save excess data + if (pkt->expected_size && pkt->filled_size > pkt->expected_size) + packet_store_excess(pkt); + + size_t expected = pkt->expected_size; + if (expected == 0) + expected = msg->claimed_size; + if (expected == 0) + expected = pkt->buffer_capacity; + + // Sanity check + if (expected < pkt->filled_size) + ERR_AND_EXIT("Expected cannot be larger than filled"); + + size_t pending = expected - pkt->filled_size; + + packet_check_completion(pkt); + + // Expand buffer as needed + while (pending + pkt->filled_size > pkt->buffer_capacity) { + ssize_t size_added = packet_expand(pkt); + if (size_added < 0) { + WARN("packet at max allowed size"); + return 0; + } + if (size_added == 0) { + ERR("Failed to expand packet"); + return 0; + } + } + + return pending; +} + +void packet_mark_received(struct http_packet_t *pkt, size_t received) +{ + struct http_message_t *msg = pkt->parent_message; + msg->received_size += received; + + pkt->filled_size += received; + NOTE("HTTP: got %lu bytes so: pkt has %lu bytes, msg has %lu bytes", + received, pkt->filled_size, msg->received_size); + + packet_check_completion(pkt); + + if (pkt->filled_size > pkt->buffer_capacity) + ERR_AND_EXIT("Overflowed packet's buffer"); + + if (pkt->expected_size && pkt->filled_size > pkt->expected_size) { + // Store excess data + packet_store_excess(pkt); + } +} + +struct http_packet_t *packet_new(struct http_message_t *parent_msg) +{ + struct http_packet_t *pkt = NULL; + uint8_t *buf = NULL; + size_t const capacity = BUFFER_STEP; + + assert(parent_msg != NULL); + pkt = calloc(1, sizeof(*pkt)); + if (pkt == NULL) { + ERR("failed to alloc packet"); + return NULL; + } + pkt->parent_message = parent_msg; + pkt->expected_size = 0; + + // Claim any spare data from prior packets + packet_take_spare(pkt); + + if (pkt->buffer == NULL) { + buf = calloc(capacity, sizeof(*buf)); + if (buf == NULL) { + ERR("failed to alloc space for packet's buffer or space for packet"); + free(pkt); + return NULL; + } + + // Assemble packet + pkt->buffer = buf; + pkt->buffer_capacity = capacity; + pkt->filled_size = 0; + } + + return pkt; +} + +void packet_free(struct http_packet_t *pkt) +{ + free(pkt->buffer); + free(pkt); +} + +#define MAX_PACKET_SIZE (1 << 26) // 64MiB +ssize_t packet_expand(struct http_packet_t *pkt) +{ + size_t cur_size = pkt->buffer_capacity; + size_t new_size = cur_size * 2; + if (new_size > MAX_PACKET_SIZE) { + WARN("HTTP: cannot expand packet beyond limit"); + return -1; + } + NOTE("HTTP: doubling packet buffer to %lu", new_size); + + uint8_t *new_buf = realloc(pkt->buffer, new_size); + if (new_buf == NULL) { + // If realloc fails the original buffer is still valid + WARN("Failed to expand packet"); + return 0; + } + pkt->buffer = new_buf; + pkt->buffer_capacity = new_size; + + size_t diff = new_size - cur_size; + if (diff > SSIZE_MAX) + ERR_AND_EXIT("Buffer expanded beyond sane limit"); + return (ssize_t) diff; +} diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/http.h cups-filters-1.0.52/debian/local/ippusbxd/http.h --- cups-filters-1.0.52/debian/local/ippusbxd/http.h 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/http.h 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,52 @@ +#pragma once +#include +#include + +enum http_request_t { + HTTP_UNSET, + HTTP_UNKNOWN, + HTTP_CHUNKED, + HTTP_CONTENT_LENGTH, + HTTP_HEADER_ONLY +}; + +struct http_message_t { + enum http_request_t type; + + size_t spare_filled; + size_t spare_capacity; + uint8_t *spare_buffer; + + size_t unreceived_size; + uint8_t is_completed; + + // Detected from child packets + size_t claimed_size; + size_t received_size; +}; + +struct http_packet_t { + // Cache + size_t header_size; + + size_t filled_size; + size_t expected_size; + + size_t buffer_capacity; + uint8_t *buffer; + + struct http_message_t *parent_message; + + uint8_t is_completed; +}; + +struct http_message_t *http_message_new(void); +void message_free(struct http_message_t *); + +enum http_request_t packet_find_type(struct http_packet_t *pkt); +size_t packet_pending_bytes(struct http_packet_t *); +void packet_mark_received(struct http_packet_t *, size_t); + +struct http_packet_t *packet_new(struct http_message_t *); +void packet_free(struct http_packet_t *); +ssize_t packet_expand(struct http_packet_t *); diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/ippusbxd.c cups-filters-1.0.52/debian/local/ippusbxd/ippusbxd.c --- cups-filters-1.0.52/debian/local/ippusbxd/ippusbxd.c 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/ippusbxd.c 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,262 @@ +#include +#include +#include + +#include +#include +#include + +#include "options.h" +#include "logging.h" +#include "http.h" +#include "tcp.h" +#include "usb.h" + +struct service_thread_param { + struct tcp_conn_t *tcp; + struct usb_sock_t *usb_sock; + pthread_t thread_handle; +}; +static void *service_connection(void *arg_void) +{ + struct service_thread_param *arg = + (struct service_thread_param *)arg_void; + + // clasify priority + while (!arg->tcp->is_closed) { + struct usb_conn_t *usb = NULL; + struct http_message_t *server_msg = NULL; + struct http_message_t *client_msg = NULL; + + // Client's request + NOTE("Client msg starting"); + client_msg = http_message_new(); + if (client_msg == NULL) { + ERR("Failed to create message"); + break; + } + + while (!client_msg->is_completed) { + struct http_packet_t *pkt; + pkt = tcp_packet_get(arg->tcp, client_msg); + if (pkt == NULL) { + if (arg->tcp->is_closed) { + NOTE("Client closed connection\n"); + goto cleanup_subconn; + } + ERR_AND_EXIT("Got null packet from tcp"); + } + if (usb == NULL) { + usb = usb_conn_aquire(arg->usb_sock, 1); + if (usb == NULL) { + ERR("Failed to aquire usb interface"); + packet_free(pkt); + goto cleanup_subconn; + } + NOTE("Interface #%d: aquired usb conn", + usb->interface_index); + } + + NOTE("Pkt from tcp\n===\n%.*s\n===", (int)pkt->filled_size, pkt->buffer); + usb_conn_packet_send(usb, pkt); + packet_free(pkt); + } + message_free(client_msg); + client_msg = NULL; + NOTE("Interface #%d: Client msg completed\n", + usb->interface_index); + + + // Server's response + NOTE("Interface #%d: Server msg starting", + usb->interface_index); + server_msg = http_message_new(); + if (server_msg == NULL) { + ERR("Failed to create message"); + goto cleanup_subconn; + } + while (!server_msg->is_completed) { + struct http_packet_t *pkt; + pkt = usb_conn_packet_get(usb, server_msg); + if (pkt == NULL) + break; + + NOTE("Pkt from usb\n===\n%.*s\n===", + (int)pkt->filled_size, pkt->buffer); + tcp_packet_send(arg->tcp, pkt); + packet_free(pkt); + NOTE("Interface #%d: Server pkt done", + usb->interface_index); + } + NOTE("Interface #%d: Server msg completed\n", + usb->interface_index); + +cleanup_subconn: + if (client_msg != NULL) + message_free(client_msg); + if (server_msg != NULL) + message_free(server_msg); + if (usb != NULL) + usb_conn_release(usb); + } + + + + tcp_conn_close(arg->tcp); + free(arg); + return NULL; +} + +static void start_daemon() +{ + // Capture USB device + struct usb_sock_t *usb_sock = usb_open(); + if (usb_sock == NULL) + goto cleanup_usb; + + // Capture a socket + uint16_t desired_port = g_options.desired_port; + struct tcp_sock_t *tcp_socket = tcp_open(desired_port); + if (tcp_socket == NULL) + goto cleanup_tcp; + + uint16_t real_port = tcp_port_number_get(tcp_socket); + if (desired_port != 0 && desired_port != real_port) { + ERR("Received port number did not match requested port number." + " The requested port number may be too high."); + goto cleanup_tcp; + } + printf("%u|", real_port); + fflush(stdout); + + // Lose connection to caller + if (!g_options.nofork_mode && fork() > 0) + exit(0); + + + // Register for unplug event + if (usb_can_callback(usb_sock)) + usb_register_callback(usb_sock); + + for (;;) { + struct service_thread_param *args = calloc(1, sizeof(*args)); + if (args == NULL) { + ERR("Failed to alloc space for thread args"); + goto cleanup_thread; + } + + args->usb_sock = usb_sock; + args->tcp = tcp_conn_accept(tcp_socket); + if (args->tcp == NULL) { + ERR("Failed to open tcp connection"); + goto cleanup_thread; + } + + int status = pthread_create(&args->thread_handle, NULL, + &service_connection, args); + if (status) { + ERR("Failed to spawn thread, error %d", status); + goto cleanup_thread; + } + + continue; + + cleanup_thread: + if (args != NULL) { + if (args->tcp != NULL) + tcp_conn_close(args->tcp); + free(args); + } + break; + } + +cleanup_tcp: + if (tcp_socket!= NULL) + tcp_close(tcp_socket); +cleanup_usb: + if (usb_sock != NULL) + usb_close(usb_sock); + return; +} + +static uint16_t strto16(const char *str) +{ + unsigned long val = strtoul(str, NULL, 16); + if (val > UINT16_MAX) + exit(1); + return (uint16_t)val; +} + +int main(int argc, char *argv[]) +{ + int c; + g_options.log_destination = LOGGING_STDERR; + + while ((c = getopt(argc, argv, "qnhdp:s:lv:m:")) != -1) { + switch (c) { + case '?': + case 'h': + g_options.help_mode = 1; + break; + case 'p': + { + long long port = 0; + // Request specific port + port = atoi(optarg); + if (port < 0) { + ERR("Port number must be non-negative"); + return 1; + } + if (port > UINT16_MAX) { + ERR("Port number must be %u or less, " + "but not negative", UINT16_MAX); + return 2; + } + g_options.desired_port = (uint16_t)port; + break; + } + case 'l': + g_options.log_destination = LOGGING_SYSLOG; + break; + case 'd': + g_options.nofork_mode = 1; + g_options.verbose_mode = 1; + break; + case 'q': + g_options.verbose_mode = 1; + break; + case 'n': + g_options.nofork_mode = 1; + break; + case 'v': + g_options.vendor_id = strto16(optarg); + break; + case 'm': + g_options.product_id = strto16(optarg); + break; + case 's': + g_options.serial_num = (unsigned char *)optarg; + break; + } + } + + if (g_options.help_mode) { + printf( + "Usage: %s -v -m -p \n" + "Options:\n" + " -h Show this help message\n" + " -v Vendor ID of desired printer\n" + " -m Product ID of desired printer\n" + " -s Serial number of desired printer\n" + " -p Port number to bind against\n" + " -l Redirect logging to syslog\n" + " -q Enable verbose tracing\n" + " -d Debug mode for verbose output and no fork\n" + " -n No fork mode\n" + , argv[0]); + return 0; + } + + start_daemon(); + return 0; +} diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/license.md cups-filters-1.0.52/debian/local/ippusbxd/license.md --- cups-filters-1.0.52/debian/local/ippusbxd/license.md 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/license.md 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,196 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/logging.c cups-filters-1.0.52/debian/local/ippusbxd/logging.c --- cups-filters-1.0.52/debian/local/ippusbxd/logging.c 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/logging.c 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,21 @@ +#include +#include + +#include + +#include "logging.h" +#include "options.h" + +void BASE_LOG(enum log_level level, const char *fmt, ...) +{ + if (!g_options.verbose_mode && level != LOGGING_ERROR) + return; + + va_list arg; + va_start(arg, fmt); + if (g_options.log_destination == LOGGING_STDERR) + vfprintf(stderr, fmt, arg); + else if (g_options.log_destination == LOGGING_SYSLOG) + syslog(LOG_ERR, fmt, arg); + va_end(arg); +} diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/logging.h cups-filters-1.0.52/debian/local/ippusbxd/logging.h --- cups-filters-1.0.52/debian/local/ippusbxd/logging.h 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/logging.h 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,39 @@ +#pragma once +#include // For pthread_self() +#define TID() (pthread_self()) + +enum log_level { + LOGGING_ERROR, + LOGGING_WARNING, + LOGGING_NOTICE, + LOGGING_CONFORMANCE, +}; + +#define PP_CAT(x, y) PP_CAT_2(x, y) +#define PP_CAT_2(x, y) x##y + +# define LOG_ARITY(...) LOG_ARITY_2(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0) +# define LOG_ARITY_2(_15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0, ...) _0 + +#define LOG_OVERLOAD(Name, ...) PP_CAT(Name, LOG_ARITY(__VA_ARGS__))(__VA_ARGS__) + +#define ERR(...) LOG_OVERLOAD(ERR_, __VA_ARGS__) +#define ERR_1(msg) BASE_LOG(LOGGING_ERROR, "<%d>Error: " msg "\n", TID()) +#define ERR_2(msg, ...) BASE_LOG(LOGGING_ERROR, "<%d>Error: " msg "\n", TID(), __VA_ARGS__) + +#define WARN(...) LOG_OVERLOAD(WARN_, __VA_ARGS__) +#define WARN_1(msg) BASE_LOG(LOGGING_WARNING, "<%d>Warning: " msg "\n", TID()) +#define WARN_2(msg, ...) BASE_LOG(LOGGING_WARNING, "<%d>Warning: " msg "\n", TID(), __VA_ARGS__) + +#define NOTE(...) LOG_OVERLOAD(NOTE_, __VA_ARGS__) +#define NOTE_1(msg) BASE_LOG(LOGGING_NOTICE, "<%d>Note: " msg "\n", TID()) +#define NOTE_2(msg, ...) BASE_LOG(LOGGING_NOTICE, "<%d>Note: " msg "\n", TID(), __VA_ARGS__) + +#define CONF(...) LOG_OVERLOAD(CONF_, __VA_ARGS__) +#define CONF_1(msg) BASE_LOG(LOGGING_CONFORMANCE, "<%d>Standard Conformance Failure: " msg "\n", TID()) +#define CONF_2(msg, ...) BASE_LOG(LOGGING_CONFORMANCE, "<%d>Standard Conformance Failure: " msg "\n", TID(), __VA_ARGS__) + +#define ERR_AND_EXIT(...) do { ERR(__VA_ARGS__); exit(-1);} while (0) + +void BASE_LOG(enum log_level, const char *, ...); + diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/options.c cups-filters-1.0.52/debian/local/ippusbxd/options.c --- cups-filters-1.0.52/debian/local/ippusbxd/options.c 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/options.c 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,4 @@ +#include "options.h" + +struct options g_options; + diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/options.h cups-filters-1.0.52/debian/local/ippusbxd/options.h --- cups-filters-1.0.52/debian/local/ippusbxd/options.h 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/options.h 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,25 @@ +#pragma once +#include + +enum log_target { + LOGGING_STDERR, + LOGGING_SYSLOG +}; + +struct options { + // Runtime configuration + uint16_t desired_port; + enum log_target log_destination; + + // Behavior + int help_mode; + int verbose_mode; + int nofork_mode; + + // Printer indentity + unsigned char *serial_num; + int vendor_id; + int product_id; +}; + +extern struct options g_options; diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/readme.md cups-filters-1.0.52/debian/local/ippusbxd/readme.md --- cups-filters-1.0.52/debian/local/ippusbxd/readme.md 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/readme.md 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,78 @@ +# IPPUSBXD [![Coverity analysis status](https://scan.coverity.com/projects/2634/badge.svg)](https://scan.coverity.com/projects/2634) + +Version 1.21 + +About +======= +IPPUSBXD is a userland driver for ipp over usb class usb devices. It has been +designed for Linux but uses a cross platform usb library allowing eventual +porting to Windows and other non-posixs platforms. + +The ipp over usb standard was ratified by the usb forum in 2012. As of 2014 Mac +OSX implemented this standard and with the addition of ippusbxd soon linux shall +as well. + +IPPUSBXD depends on posixs threads, posixs networking, and libusb as developed +by the community at libusb.info + +IPPUSBXD has the following advantages; + +1. At runtime links only with libc, pthreads, and libusb. On a typical system +these libraries will already be in RAM. This gives ippusbxd a minimal ram +footprint. +2. Requires no read access to any files. +3. Ships with a strict apparmor profile. +3. Runs warning & leak free in valgrind +4. Compiles warning free in clang +5. Analyzed warning free in Coverity +6. Can be installed anywhere +7. Near zero cpu usage while idle +8. Low cpu usage while working + +Building +======= + +To build ippusbxd you must have the libusb 1.0 development headers installed along +with cmake. + +Under Ubuntu and Debian this is acomplished by running: + sudo apt-get install libusb-1.0-0-dev cmake + +Once the dependencies are installed simply run: + make + +That will run a makefile which will inturn run cmake. This makefile also +supports several GNU-style make commands such as clean, and redep. + +Presentation on IPPUSBXD +======= +On August 2014 at the Fall Printer Working Group meeting I gave a presentation +on ippusbxd and the ipp over usb protocol. Slides from this presentation can be +found in the docs folder. + +IPPUSBXD, the name +======= +The original name for this project was ippusbd. Part way through development it +came to my attention that ippusbd was the name of the ipp over usb implemented +used by Mac OSX. + +This prompted a rename and Ira of the OpenPrinting group and PWG suggested +IPPUSBXD. + +Either all-caps IPPUSBXD or all-lower-case ippusbxd are valid names. + +License +======= +Copyright 2014 Daniel Dressler + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/tcp.c cups-filters-1.0.52/debian/local/ippusbxd/tcp.c --- cups-filters-1.0.52/debian/local/ippusbxd/tcp.c 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/tcp.c 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,192 @@ +#define _GNU_SOURCE +#include +#include +#include +#include + +#include +#include +#include + +#include "logging.h" +#include "tcp.h" + + +struct tcp_sock_t *tcp_open(uint16_t port) +{ + struct tcp_sock_t *this = calloc(1, sizeof *this); + if (this == NULL) { + ERR("callocing this failed"); + goto error; + } + + // Open [S]ocket [D]escriptor + this->sd = -1; + this->sd = socket(AF_INET6, SOCK_STREAM, 0); + if (this->sd < 0) { + ERR("sockect open failed"); + goto error; + } + + // Configure socket params + struct sockaddr_in6 addr; + memset(&addr, 0, sizeof addr); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(port); + addr.sin6_addr = in6addr_any; + + // Bind to localhost + if (bind(this->sd, + (struct sockaddr *)&addr, + sizeof addr) < 0) { + ERR("Bind on port failed. " + "Requested port may be taken or require root permissions."); + goto error; + } + + // Let kernel over-accept max number of connections + if (listen(this->sd, HTTP_MAX_PENDING_CONNS) < 0) { + ERR("listen failed on socket"); + goto error; + } + + return this; + +error: + if (this != NULL) { + if (this->sd != -1) { + close(this->sd); + } + free(this); + } + return NULL; +} + +void tcp_close(struct tcp_sock_t *this) +{ + close(this->sd); + free(this); +} + +uint16_t tcp_port_number_get(struct tcp_sock_t *sock) +{ + sock->info_size = sizeof sock->info; + int query_status = getsockname( + sock->sd, + (struct sockaddr *) &(sock->info), + &(sock->info_size)); + if (query_status == -1) { + ERR("query on socket port number failed"); + goto error; + } + + return ntohs(sock->info.sin6_port); + +error: + return 0; +} + +struct http_packet_t *tcp_packet_get(struct tcp_conn_t *tcp, + struct http_message_t *msg) +{ + // Alloc packet ==---------------------------------------------------== + struct http_packet_t *pkt = packet_new(msg); + if (pkt == NULL) { + ERR("failed to create packet for incoming tcp message"); + goto error; + } + + size_t want_size = packet_pending_bytes(pkt); + if (want_size == 0) { + NOTE("TCP: Got %lu from spare buffer", pkt->filled_size); + return pkt; + } + + while (want_size != 0 && !msg->is_completed) { + NOTE("TCP: Getting %d bytes", want_size); + uint8_t *subbuffer = pkt->buffer + pkt->filled_size; + ssize_t gotten_size = recv(tcp->sd, subbuffer, want_size, 0); + if (gotten_size < 0) { + int errno_saved = errno; + ERR("recv failed with err %d:%s", errno_saved, + strerror(errno_saved)); + goto error; + } + NOTE("TCP: Got %d bytes", gotten_size); + if (gotten_size == 0) { + tcp->is_closed = 1; + if (pkt->filled_size == 0) { + // Client closed TCP conn + goto error; + } else { + break; + } + } + + packet_mark_received(pkt, (unsigned) gotten_size); + want_size = packet_pending_bytes(pkt); + } + + NOTE("TCP: Received %lu bytes", pkt->filled_size); + return pkt; + +error: + if (pkt != NULL) + packet_free(pkt); + return NULL; +} + +void tcp_packet_send(struct tcp_conn_t *conn, struct http_packet_t *pkt) +{ + size_t remaining = pkt->filled_size; + size_t total = 0; + while (remaining > 0) { + ssize_t sent = send(conn->sd, pkt->buffer + total, + remaining, MSG_NOSIGNAL); + if (sent < 0) { + if (errno == EPIPE) { + conn->is_closed = 1; + return; + } + ERR_AND_EXIT("Failed to sent data over TCP"); + } + + size_t sent_ulong = (unsigned) sent; + total += sent_ulong; + if (sent_ulong >= remaining) + remaining = 0; + else + remaining -= sent_ulong; + } + NOTE("TCP: sent %lu bytes", total); +} + + +struct tcp_conn_t *tcp_conn_accept(struct tcp_sock_t *sock) +{ + struct tcp_conn_t *conn = calloc(1, sizeof *conn); + if (conn == NULL) { + ERR("Calloc for connection struct failed"); + goto error; + } + + conn->sd = accept(sock->sd, NULL, NULL); + if (conn->sd < 0) { + ERR("accept failed"); + goto error; + } + + return conn; + +error: + if (conn != NULL) + free(conn); + return NULL; +} + +void tcp_conn_close(struct tcp_conn_t *conn) +{ + close(conn->sd); + free(conn); +} + diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/tcp.h cups-filters-1.0.52/debian/local/ippusbxd/tcp.h --- cups-filters-1.0.52/debian/local/ippusbxd/tcp.h 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/tcp.h 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,37 @@ +#pragma once +#include + +#include +#include +#include +#include + +#include "http.h" + +#define HTTP_MAX_PENDING_CONNS 0 +#define BUFFER_STEP (1 << 13) +#define BUFFER_STEP_RATIO (2) +#define BUFFER_INIT_RATIO (1) +#define BUFFER_MAX (1 << 20) + +struct tcp_sock_t { + int sd; + struct sockaddr_in6 info; + socklen_t info_size; +}; + +struct tcp_conn_t { + int sd; + int is_closed; +}; + +struct tcp_sock_t *tcp_open(uint16_t); +void tcp_close(struct tcp_sock_t *); +uint16_t tcp_port_number_get(struct tcp_sock_t *); + +struct tcp_conn_t *tcp_conn_accept(struct tcp_sock_t *); +void tcp_conn_close(struct tcp_conn_t *); + +struct http_packet_t *tcp_packet_get(struct tcp_conn_t *, + struct http_message_t *); +void tcp_packet_send(struct tcp_conn_t *, struct http_packet_t *); diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/usb.c cups-filters-1.0.52/debian/local/ippusbxd/usb.c --- cups-filters-1.0.52/debian/local/ippusbxd/usb.c 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/usb.c 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,679 @@ +#define _XOPEN_SOURCE 600 +#include +#include +#include +#include +#include + +#include + +#include "options.h" +#include "logging.h" +#include "http.h" +#include "usb.h" + +#define IGNORE(x) (void)(x) + +static int is_ippusb_interface(const struct libusb_interface_descriptor *interf) +{ + return interf->bInterfaceClass == 0x07 && + interf->bInterfaceSubClass == 0x01 && + interf->bInterfaceProtocol == 0x04; +} + +static int count_ippoverusb_interfaces(struct libusb_config_descriptor *config) +{ + int ippusb_interface_count = 0; + + for (uint8_t interface_num = 0; + interface_num < config->bNumInterfaces; + interface_num++) { + + const struct libusb_interface *interface = NULL; + interface = &config->interface[interface_num]; + + for (int alt_num = 0; + alt_num < interface->num_altsetting; + alt_num++) { + + const struct libusb_interface_descriptor *alt = NULL; + alt = &interface->altsetting[alt_num]; + + // Check for IPP over USB interfaces + if (!is_ippusb_interface(alt)) + continue; + + ippusb_interface_count++; + break; + } + } + + return ippusb_interface_count; +} + +static int is_our_device(libusb_device *dev, + struct libusb_device_descriptor desc) +{ + static const int SERIAL_MAX = 1024; + unsigned char serial[1024]; + if ((g_options.vendor_id && desc.idVendor != g_options.vendor_id) && + (g_options.product_id && desc.idProduct != g_options.product_id)) + return 0; + + if (g_options.serial_num == NULL) + return 1; + + libusb_device_handle *handle = NULL; + int status = libusb_open(dev, &handle); + if (status != 0) + return 0; + + status = libusb_get_string_descriptor_ascii(handle, + desc.iSerialNumber, serial, SERIAL_MAX); + libusb_close(handle); + + if (status <= 0) { + WARN("Failed to get serial from device"); + return 0; + } + + return strcmp((char *)serial, (char *)g_options.serial_num) == 0; +} + +struct usb_sock_t *usb_open() +{ + struct usb_sock_t *usb = calloc(1, sizeof *usb); + int status = 1; + status = libusb_init(&usb->context); + if (status < 0) { + ERR("libusb init failed with error: %s", + libusb_error_name(status)); + goto error_usbinit; + } + + libusb_device **device_list = NULL; + ssize_t device_count = libusb_get_device_list(usb->context, &device_list); + if (device_count < 0) { + ERR("failed to get list of usb devices"); + goto error; + } + + + // Discover device and count interfaces ==---------------------------== + int selected_config = -1; + unsigned int selected_ipp_interface_count = 0; + int auto_pick = !(g_options.vendor_id || + g_options.product_id || + g_options.serial_num); + + libusb_device *printer_device = NULL; + for (ssize_t i = 0; i < device_count; i++) { + libusb_device *candidate = device_list[i]; + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(candidate, &desc); + + if (!is_our_device(candidate, desc)) + continue; + + for (uint8_t config_num = 0; + config_num < desc.bNumConfigurations; + config_num++) { + struct libusb_config_descriptor *config = NULL; + status = libusb_get_config_descriptor(candidate, + config_num, + &config); + if (status < 0) + ERR_AND_EXIT("USB: didn't get config desc %s", + libusb_error_name(status)); + + int interface_count = count_ippoverusb_interfaces(config); + libusb_free_config_descriptor(config); + if (interface_count >= 2) { + selected_config = config_num; + selected_ipp_interface_count = (unsigned) interface_count; + printer_device = candidate; + goto found_device; + } + + // CONFTEST: Two or more interfaces are required + if (interface_count == 1) { + CONF("usb device has only one ipp interface " + "in violation of standard"); + goto error; + } + + if (!auto_pick) { + ERR_AND_EXIT("No ipp-usb interfaces found"); + } + } + } +found_device: + + if (printer_device == NULL) { + if (!auto_pick) { + ERR("No printer found by that vid, pid, serial"); + } else { + ERR("No IPP over USB printer found"); + } + goto error; + } + + + // Open the printer ==-----------------------------------------------== + status = libusb_open(printer_device, &usb->printer); + if (status != 0) { + ERR("failed to open device"); + goto error; + } + + + // Open every IPP-USB interface ==-----------------------------------== + usb->num_interfaces = selected_ipp_interface_count; + usb->interfaces = calloc(usb->num_interfaces, + sizeof(*usb->interfaces)); + if (usb->interfaces == NULL) { + ERR("Failed to alloc interfaces"); + goto error; + } + + struct libusb_config_descriptor *config = NULL; + status = libusb_get_config_descriptor(printer_device, + (uint8_t)selected_config, + &config); + if (status != 0 || config == NULL) { + ERR("Failed to aquire config descriptor"); + goto error; + } + + unsigned int interfs = selected_ipp_interface_count; + for (uint8_t interf_num = 0; + interf_num < config->bNumInterfaces; + interf_num++) { + + const struct libusb_interface *interf = NULL; + interf = &config->interface[interf_num]; + for (int alt_num = 0; + alt_num < interf->num_altsetting; + alt_num++) { + + const struct libusb_interface_descriptor *alt = NULL; + alt = &interf->altsetting[alt_num]; + + // Skip non-IPP-USB interfaces + if (!is_ippusb_interface(alt)) + continue; + + interfs--; + + struct usb_interface *uf = usb->interfaces + interfs; + uf->interface_number = interf_num; + uf->libusb_interface_index = alt->bInterfaceNumber; + uf->interface_alt = alt_num; + + // Store interface's two endpoints + for (int end_i = 0; end_i < alt->bNumEndpoints; + end_i++) { + const struct libusb_endpoint_descriptor *end; + end = &alt->endpoint[end_i]; + + usb->max_packet_size = end->wMaxPacketSize; + + // High bit set means endpoint + // is an INPUT or IN endpoint. + uint8_t address = end->bEndpointAddress; + if (address & 0x80) + uf->endpoint_in = address; + else + uf->endpoint_out = address; + } + + break; + } + } + libusb_free_config_descriptor(config); + libusb_free_device_list(device_list, 1); + + + // Pour interfaces into pool ==--------------------------------------== + usb->num_avail = usb->num_interfaces; + usb->interface_pool = calloc(usb->num_avail, + sizeof(*usb->interface_pool)); + if (usb->interface_pool == NULL) { + ERR("Failed to alloc interface pool"); + goto error; + } + for (uint32_t i = 0; i < usb->num_avail; i++) { + usb->interface_pool[i] = i; + } + + + // Stale lock + int status_lock = sem_init(&usb->num_staled_lock, 0, 1); + if (status_lock != 0) { + ERR("Failed to create num_staled lock"); + goto error; + } + + // Pool management lock + status_lock = sem_init(&usb->pool_manage_lock, 0, 1); + if (status_lock != 0) { + ERR("Failed to create high priority pool lock"); + goto error; + } + // High priority lock + status_lock = sem_init(&usb->pool_high_priority_lock, 0, 1); + if (status_lock != 0) { + ERR("Failed to create low priority pool lock"); + goto error; + } + // Low priority lock + status_lock = sem_init(&usb->pool_low_priority_lock, + 0, usb->num_avail - 1); + if (status_lock != 0) { + ERR("Failed to create high priority pool lock"); + goto error; + } + + return usb; + +error: + if (device_list != NULL) + libusb_free_device_list(device_list, 1); +error_usbinit: + if (usb != NULL) { + if (usb->context != NULL) + libusb_exit(usb->context); + if (usb->interfaces != NULL) + free(usb->interfaces); + if (usb->interface_pool != NULL) + free(usb->interface_pool); + free(usb); + } + return NULL; +} + +void usb_close(struct usb_sock_t *usb) +{ + // Release interfaces + for (uint32_t i = 0; i < usb->num_interfaces; i++) { + int number = usb->interfaces[i].interface_number; + libusb_release_interface(usb->printer, number); + } + + libusb_close(usb->printer); + libusb_exit(usb->context); + + sem_destroy(&usb->pool_high_priority_lock); + sem_destroy(&usb->pool_low_priority_lock); + sem_destroy(&usb->num_staled_lock); + free(usb->interfaces); + free(usb->interface_pool); + free(usb); + return; +} + +int usb_can_callback(struct usb_sock_t *usb) +{ + IGNORE(usb); + + if (!g_options.vendor_id || + !g_options.product_id) + { + NOTE("Exit-on-unplug requires vid & pid"); + return 0; + } + + int works = !!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG); + if (!works) + WARN("Libusb cannot tell us when to disconnect"); + return works; +} + +static int LIBUSB_CALL usb_exit_on_unplug(libusb_context *context, + libusb_device *device, + libusb_hotplug_event event, + void *call_data) +{ + IGNORE(context); + IGNORE(event); + IGNORE(call_data); + + NOTE("Received unplug callback"); + + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(device, &desc); + + if (is_our_device(device, desc)) + exit(0); + + return 0; +} + +static void *usb_pump_events(void *user_data) +{ + IGNORE(user_data); + + for (;;) { + // NOTE: This is a blocking call so + // no need for sleep() + libusb_handle_events_completed(NULL, NULL); + } +} + +void usb_register_callback(struct usb_sock_t *usb) +{ + IGNORE(usb); + + int status = libusb_hotplug_register_callback( + NULL, + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + // Note: libusb's enum has no default value + // a bug has been filled with libusb. + // Please switch the below line to 0 + // once the issue has been fixed in + // deployed versions of libusb + // https://github.com/libusb/libusb/issues/35 + // 0, + LIBUSB_HOTPLUG_ENUMERATE, + g_options.vendor_id, + g_options.product_id, + LIBUSB_HOTPLUG_MATCH_ANY, + &usb_exit_on_unplug, + NULL, + NULL); + if (status == LIBUSB_SUCCESS) { + pthread_t thread_handle; + pthread_create(&thread_handle, NULL, &usb_pump_events, NULL); + NOTE("Registered unplug callback"); + } else + ERR("Failed to register unplug callback"); +} + +static void usb_conn_mark_staled(struct usb_conn_t *conn) +{ + if (conn->is_staled) + return; + + struct usb_sock_t *usb = conn->parent; + + sem_wait(&usb->num_staled_lock); + { + usb->num_staled++; + } + sem_post(&usb->num_staled_lock); + + conn->is_staled = 1; +} + +static void usb_conn_mark_moving(struct usb_conn_t *conn) +{ + if (!conn->is_staled) + return; + + struct usb_sock_t *usb = conn->parent; + + sem_wait(&usb->num_staled_lock); + { + usb->num_staled--; + } + sem_post(&usb->num_staled_lock); + + conn->is_staled = 0; +} + +static int usb_all_conns_staled(struct usb_sock_t *usb) +{ + int staled; + + sem_wait(&usb->num_staled_lock); + { + sem_wait(&usb->pool_manage_lock); + { + staled = usb->num_staled == usb->num_taken; + } + sem_post(&usb->pool_manage_lock); + } + sem_post(&usb->num_staled_lock); + + return staled; +} + +struct usb_conn_t *usb_conn_aquire(struct usb_sock_t *usb, + int is_high_priority) +{ + int used_high_priority = 0; + if (is_high_priority) { + // We first take the high priority lock. + sem_wait(&usb->pool_high_priority_lock); + used_high_priority = 1; + + // We can then check if a low priority + // interface is available. + if (!sem_trywait(&usb->pool_low_priority_lock)) { + // If a low priority is avail we take that one + // then release the high priority interface. + // Otherwise we use the high priority interface. + used_high_priority = 0; + sem_post(&usb->pool_high_priority_lock); + } + } else { + sem_wait(&usb->pool_low_priority_lock); + } + + struct usb_conn_t *conn = calloc(1, sizeof(*conn)); + if (conn == NULL) { + ERR("failed to aloc space for usb connection"); + return NULL; + } + + sem_wait(&usb->pool_manage_lock); + { + conn->parent = usb; + conn->is_high_priority = used_high_priority; + + usb->num_taken++; + uint32_t slot = --usb->num_avail; + + conn->interface_index = usb->interface_pool[slot]; + conn->interface = usb->interfaces + conn->interface_index; + struct usb_interface *uf = conn->interface; + + // Make kernel release interface + if (libusb_kernel_driver_active(usb->printer, + uf->libusb_interface_index) == 1) { + // Only linux supports this + // other platforms will fail + // thus we ignore the error code + // it either works or it does not + libusb_detach_kernel_driver(usb->printer, + uf->libusb_interface_index); + } + + // Claim the whole interface + int status = 0; + do { + // Spinlock-like + // Libusb does not offer a blocking call + // so we're left with a spinlock + status = libusb_claim_interface( + usb->printer, uf->libusb_interface_index); + switch (status) { + case LIBUSB_ERROR_NOT_FOUND: + ERR_AND_EXIT("USB Interface did not exist"); + case LIBUSB_ERROR_NO_DEVICE: + ERR_AND_EXIT("Printer was removed"); + default: + break; + } + } while (status != 0); + + // Select the IPP-USB alt setting of the interface + libusb_set_interface_alt_setting(usb->printer, + uf->libusb_interface_index, + uf->interface_alt); + } + sem_post(&usb->pool_manage_lock); + return conn; +} + +void usb_conn_release(struct usb_conn_t *conn) +{ + struct usb_sock_t *usb = conn->parent; + sem_wait(&usb->pool_manage_lock); + { + libusb_release_interface(usb->printer, + conn->interface->libusb_interface_index); + + // Return usb interface to pool + usb->num_taken--; + uint32_t slot = usb->num_avail++; + usb->interface_pool[slot] = conn->interface_index; + + // Release our interface lock + if (conn->is_high_priority) + sem_post(&usb->pool_high_priority_lock); + else + sem_post(&usb->pool_low_priority_lock); + + free(conn); + } + sem_post(&usb->pool_manage_lock); +} + +void usb_conn_packet_send(struct usb_conn_t *conn, struct http_packet_t *pkt) +{ + int size_sent = 0; + const int timeout = 6 * 60 * 60 * 1000; // 6 hours in milliseconds + int num_timeouts = 0; + size_t sent = 0; + size_t pending = pkt->filled_size; + while (pending > 0) { + int to_send = (int)pending; + + NOTE("USB: want to send %d bytes", to_send); + int status = libusb_bulk_transfer(conn->parent->printer, + conn->interface->endpoint_out, + pkt->buffer + sent, to_send, + &size_sent, timeout); + if (status == LIBUSB_ERROR_TIMEOUT) { + NOTE("USB: send timed out, retrying"); + + if (num_timeouts++ > PRINTER_CRASH_TIMEOUT) + ERR_AND_EXIT("Usb send fully timed out"); + + // Sleep for tenth of a second + struct timespec sleep_dur; + sleep_dur.tv_sec = 0; + sleep_dur.tv_nsec = 100000000; + nanosleep(&sleep_dur, NULL); + continue; + } + + if (status == LIBUSB_ERROR_NO_DEVICE) + ERR_AND_EXIT("Printer has been disconnected"); + if (status < 0) + ERR_AND_EXIT("USB: send failed with status %s", + libusb_error_name(status)); + if (size_sent < 0) + ERR_AND_EXIT("Unexpected negative size_sent"); + + pending -= (size_t) size_sent; + sent += (size_t) size_sent; + NOTE("USB: sent %d bytes", size_sent); + } + NOTE("USB: sent %d bytes in total", sent); +} + +struct http_packet_t *usb_conn_packet_get(struct usb_conn_t *conn, struct http_message_t *msg) +{ + if (msg->is_completed) + return NULL; + + struct http_packet_t *pkt = packet_new(msg); + if (pkt == NULL) { + ERR("failed to create packet for incoming usb message"); + goto cleanup; + } + + // File packet + const int timeout = 6 * 60 * 60 * 1000; // 6 hours in milliseconds + size_t read_size_ulong = packet_pending_bytes(pkt); + if (read_size_ulong == 0) + return pkt; + + int times_staled = 0; + while (read_size_ulong > 0 && !msg->is_completed) { + if (read_size_ulong >= INT_MAX) + goto cleanup; + int read_size = (int)read_size_ulong; + + // Ensure read_size is multiple of usb packets + read_size += (512 - (read_size % 512)) % 512; + + // Expand buffer if needed + if (pkt->buffer_capacity < pkt->filled_size + read_size_ulong) + if (packet_expand(pkt) < 0) + ERR_AND_EXIT("Failed to ensure room for usb pkt"); + + NOTE("USB: Getting %d bytes of %d", read_size, pkt->expected_size); + int gotten_size = 0; + int status = libusb_bulk_transfer( + conn->parent->printer, + conn->interface->endpoint_in, + pkt->buffer + pkt->filled_size, + read_size, + &gotten_size, timeout); + + if (status == LIBUSB_ERROR_NO_DEVICE) + ERR_AND_EXIT("Printer has been disconnected"); + + if (status != 0 && status != LIBUSB_ERROR_TIMEOUT) { + ERR("bulk xfer failed with error code %d", status); + ERR("tried reading %d bytes", read_size); + goto cleanup; + } + + if (gotten_size < 0) + ERR_AND_EXIT("Negative read size unexpected"); + + if (gotten_size > 0) { + times_staled = 0; + usb_conn_mark_moving(conn); + } else { + times_staled++; + if (times_staled > CONN_STALE_THRESHHOLD) { + usb_conn_mark_staled(conn); + + if (usb_all_conns_staled(conn->parent) && + times_staled > PRINTER_CRASH_TIMEOUT) { + ERR("USB timedout, dropping data"); + goto cleanup; + } + + if (pkt->filled_size > 0) + NOTE("Packet so far \n%.*s\n", + pkt->filled_size, + pkt->buffer); + } + + // Sleep for tenth of a second + struct timespec sleep_dur; + sleep_dur.tv_sec = 0; + sleep_dur.tv_nsec = 100000000; + nanosleep(&sleep_dur, NULL); + } + + NOTE("USB: Got %d bytes", gotten_size); + packet_mark_received(pkt, (size_t)gotten_size); + read_size_ulong = packet_pending_bytes(pkt); + } + NOTE("USB: Received %d bytes of %d with type %d", + pkt->filled_size, pkt->expected_size, msg->type); + + return pkt; + +cleanup: + if (pkt != NULL) + packet_free(pkt); + return NULL; +} diff -Nru cups-filters-1.0.52/debian/local/ippusbxd/usb.h cups-filters-1.0.52/debian/local/ippusbxd/usb.h --- cups-filters-1.0.52/debian/local/ippusbxd/usb.h 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/ippusbxd/usb.h 2014-10-22 19:49:01.000000000 +0200 @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +static const int PRINTER_CRASH_TIMEOUT = 60; +static const int CONN_STALE_THRESHHOLD = 6; + +struct usb_interface { + uint8_t interface_number; + uint8_t libusb_interface_index; + int interface_alt; + uint8_t endpoint_in; + uint8_t endpoint_out; +}; + +struct usb_sock_t { + libusb_context *context; + libusb_device_handle *printer; + int max_packet_size; + + uint32_t num_interfaces; + struct usb_interface *interfaces; + + uint32_t num_staled; + sem_t num_staled_lock; + + sem_t pool_manage_lock; + uint32_t num_avail; + uint32_t num_taken; + + uint32_t *interface_pool; + sem_t pool_low_priority_lock; + sem_t pool_high_priority_lock; + +}; + +struct usb_conn_t { + struct usb_sock_t *parent; + struct usb_interface *interface; + uint32_t interface_index; + int is_high_priority; + int is_staled; +}; + +struct usb_sock_t *usb_open(void); +void usb_close(struct usb_sock_t *); + +int usb_can_callback(struct usb_sock_t *); +void usb_register_callback(struct usb_sock_t *); + +struct usb_conn_t *usb_conn_aquire(struct usb_sock_t *, int); +void usb_conn_release(struct usb_conn_t *); + +void usb_conn_packet_send(struct usb_conn_t *, struct http_packet_t *); +struct http_packet_t *usb_conn_packet_get(struct usb_conn_t *, struct http_message_t *); + diff -Nru cups-filters-1.0.52/debian/local/rastertopdf/colormanager.c cups-filters-1.0.52/debian/local/rastertopdf/colormanager.c --- cups-filters-1.0.52/debian/local/rastertopdf/colormanager.c 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/rastertopdf/colormanager.c 2014-10-22 19:59:47.000000000 +0200 @@ -0,0 +1,354 @@ +/* +Copyright (c) 2011-2013, Richard Hughes +Copyright (c) 2014, Joseph Simon + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +MIT Open Source License - http://www.opensource.org/ + +*/ + + +#include "colormanager.h" +#include +//#include + + +#define CM_MAX_FILE_LENGTH 1024 + + +/* Private function prototypes */ +static int _get_colord_printer_cm_status (const char *printer_name); +static char *_get_colord_printer_id (const char *printer_name); +static int _get_colord_profile (const char *printer_name, + char **profile, + ppd_file_t *ppd); +static char *_get_ppd_icc_fallback (ppd_file_t *ppd, + char **qualifier); + + + +/* Commonly-used calibration numbers */ + +double adobergb_wp[3] = {0.95045471, 1.0, 1.08905029}; +double sgray_wp[3] = {0.9505, 1, 1.0890}; +double adobergb_gamma[3] = {2.2, 2.2, 2.2}; +double sgray_gamma[1] = {2.2}; +double adobergb_matrix[9] = {0.60974121, 0.20527649, 0.14918518, + 0.31111145, 0.62567139, 0.06321716, + 0.01947021, 0.06086731, 0.74456787}; +double blackpoint_default[3] = {0.0, 0.0, 0.0}; + + + + +/* + * Public functions + */ + + +/* Get printer color management status from the system's color manager */ +int +cmIsPrinterCmDisabled(const char *printer_name) /* dest name */ +{ + + int is_cm_off = 0; /* color management status flag */ + + + /* Request color management status from colord */ + is_cm_off = _get_colord_printer_cm_status(printer_name); + + if (is_cm_off) + fprintf(stderr,"DEBUG: Color Manager: Color management disabled by OS.\n"); + + return is_cm_off; + +} + + +/* Get printer ICC profile from the system's color manager */ +int +cmGetPrinterIccProfile(const char *printer_name, /* Printer name (usually "dest" name) */ + char **icc_profile, /* ICC Profile Path */ + ppd_file_t *ppd) /* Optional PPD file for fallback profile */ +{ + + int profile_set = 0; /* 'is profile found' flag */ + + + /* Request a profile from colord */ + profile_set = _get_colord_profile(printer_name, icc_profile, ppd); + + fprintf(stderr, "DEBUG: Color Manager: ICC Profile: %s\n", *icc_profile ? + *icc_profile : "None"); + + return profile_set; + +} + + +/* Find the "cm-calibration" CUPS option */ +cm_calibration_t +cmGetCupsColorCalibrateMode(cups_option_t *options, /* Options from CUPS */ + int num_options) /* Options from CUPS */ +{ + + cm_calibration_t status; /* color management status */ + + + /* Find the string in CUPS options and */ + if (cupsGetOption(CM_CALIBRATION_STRING, num_options, options) != NULL) + status = CM_CALIBRATION_ENABLED; + else + status = CM_CALIBRATION_DISABLED; + + fprintf(stderr, "DEBUG: Color Manager: %s\n", status ? + "Calibration Mode/Enabled" : "Calibration Mode/Off"); + + return status; + +} + + +/* Functions to return specific calibration data */ + + +/* Gamma values */ + +double *cmGammaAdobeRgb(void) +{ + return adobergb_gamma; +} +double *cmGammaSGray(void) +{ + return sgray_gamma; +} + + +/* Whitepoint values */ + +double *cmWhitePointAdobeRgb(void) +{ + return adobergb_wp; +} +double *cmWhitePointSGray(void) +{ + return sgray_wp; +} + + +/* Adapted primaries matrix */ + +double *cmMatrixAdobeRgb(void) +{ + return adobergb_matrix; +} + + +/* Blackpoint value */ + +double *cmBlackPointDefault(void) +{ + return blackpoint_default; +} + + + + +/* + * Private functions + */ + + +char * +_get_colord_printer_id(const char *printer_name) /* Dest name */ +{ + + if (printer_name == NULL) { + fprintf(stderr, "DEBUG: Color Manager: Invalid printer name.\n"); + return 0; + } + + /* Create printer id string for colord */ + char* printer_id = (char*)malloc(CM_MAX_FILE_LENGTH); + snprintf (printer_id, CM_MAX_FILE_LENGTH, "cups-%s", printer_name); + + return printer_id; + +} + + +int +_get_colord_printer_cm_status(const char *printer_name) /* Dest name */ +{ + + /* If invalid input, we leave color management alone */ + if (printer_name == NULL) { + fprintf(stderr, "DEBUG: Color Manager: Invalid printer name.\n"); + return 0; + } else if (!strcmp(printer_name, "(null)")) + return 0; + + int is_printer_cm_disabled = 0; /* color management status flag */ + char *printer_id = 0; /* colord printer id string */ + + + /* Check if device is inhibited/disabled in colord */ + printer_id = _get_colord_printer_id(printer_name); + is_printer_cm_disabled = colord_get_inhibit_for_device_id (printer_id); + + if (printer_id != NULL) + free(printer_id); + + return is_printer_cm_disabled; + +} + +int +_get_colord_profile(const char *printer_name, /* Dest name */ + char **profile, /* Requested icc profile path */ + ppd_file_t *ppd) /* PPD file */ +{ + + if (printer_name == NULL || profile == 0) { + fprintf(stderr, "DEBUG: Color Manager: Invalid input - Unable to find profile.\n"); + return -1; + } + + int is_profile_set = 0; /* profile-is-found flag */ + char **qualifier = NULL; /* color qualifier strings */ + char *icc_profile = NULL; /* icc profile path */ + char *printer_id = NULL; /* colord printer id */ + + + /* Get color qualifier triple */ + qualifier = colord_get_qualifier_for_ppd(ppd); + + if (qualifier != NULL) { + printer_id = _get_colord_printer_id(printer_name); + /* Get profile from colord using qualifiers */ + icc_profile = colord_get_profile_for_device_id (printer_id, qualifier); + } + + if (icc_profile) + is_profile_set = 1; + else if (ppd) { + /* Get optional fallback PPD profile */ + icc_profile = _get_ppd_icc_fallback(ppd, qualifier); + if (icc_profile) + is_profile_set = 1; + } + + /* If a profile is found, we give it to the caller */ + if (is_profile_set) + *profile = strdup(icc_profile); + else + *profile = 0; + + if (printer_id != NULL) + free(printer_id); + + if (qualifier != NULL) { + for (int i=0; qualifier[i] != NULL; i++) + free(qualifier[i]); + free(qualifier); + } + + return is_profile_set; + +} + + +#ifndef CUPSDATA +#define CUPSDATA "/usr/share/cups" +#endif + +/* From gstoraster */ +char * +_get_ppd_icc_fallback (ppd_file_t *ppd, char **qualifier) +{ + char full_path[1024]; + char *icc_profile = NULL; + char qualifer_tmp[1024]; + const char *profile_key; + ppd_attr_t *attr; + + /* get profile attr, falling back to CUPS */ + profile_key = "APTiogaProfile"; + attr = ppdFindAttr(ppd, profile_key, NULL); + if (attr == NULL) { + profile_key = "cupsICCProfile"; + attr = ppdFindAttr(ppd, profile_key, NULL); + } + + /* create a string for a quick comparion */ + snprintf(qualifer_tmp, sizeof(qualifer_tmp), + "%s.%s.%s", + qualifier[0], + qualifier[1], + qualifier[2]); + + /* neither */ + if (attr == NULL) { + fprintf(stderr, "INFO: Color Manager: no profiles specified in PPD\n"); + goto out; + } + + /* try to find a profile that matches the qualifier exactly */ + for (;attr != NULL; attr = ppdFindNextAttr(ppd, profile_key, NULL)) { + fprintf(stderr, "INFO: Color Manager: found profile %s in PPD with qualifier '%s'\n", + attr->value, attr->spec); + + /* invalid entry */ + if (attr->spec == NULL || attr->value == NULL) + continue; + + /* expand to a full path if not already specified */ + if (attr->value[0] != '/') + snprintf(full_path, sizeof(full_path), + "%s/profiles/%s", CUPSDATA, attr->value); + else + strncpy(full_path, attr->value, sizeof(full_path)); + + /* check the file exists */ + if (access(full_path, 0)) { + fprintf(stderr, "INFO: Color Manager: found profile %s in PPD that does not exist\n", + full_path); + continue; + } + + /* matches the qualifier */ + if (strcmp(qualifer_tmp, attr->spec) == 0) { + icc_profile = strdup(full_path); + goto out; + } + } + + /* no match */ + if (attr == NULL) { + fprintf(stderr, "INFO: Color Manager: no profiles in PPD for qualifier '%s'\n", + qualifer_tmp); + goto out; + } + +out: + return icc_profile; +} + diff -Nru cups-filters-1.0.52/debian/local/rastertopdf/colormanager.h cups-filters-1.0.52/debian/local/rastertopdf/colormanager.h --- cups-filters-1.0.52/debian/local/rastertopdf/colormanager.h 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/rastertopdf/colormanager.h 2014-10-22 19:59:47.000000000 +0200 @@ -0,0 +1,86 @@ +/* +Copyright (c) 2014, Joseph Simon + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +MIT Open Source License - http://www.opensource.org/ + +*/ + + +#ifndef _CUPS_FILTERS_COLORMANAGER_H_ +# define _CUPS_FILTERS_COLORMANAGER_H_ + + +/* "Color Manager" -- Color management interface for cups-filters */ + + +# ifdef __cplusplus +extern "C" { +# endif /* __cplusplus */ + + +#include + + + +#define CM_CALIBRATION_STRING "cm-calibration" /* String for "Color Calibration Mode" */ + + +/* Enum for status of CUPS color calibration */ +typedef enum cm_calibration_e +{ + CM_CALIBRATION_DISABLED = 0, /* "cm-calibration" option not found */ + CM_CALIBRATION_ENABLED = 1 /* "cm-calibration" found */ +} cm_calibration_t; + + + +/* + * Prototypes + */ + + +extern +cm_calibration_t cmGetCupsColorCalibrateMode (cups_option_t *options, + int num_options); + +extern int cmGetPrinterIccProfile (const char *printer_id, + char **icc_profile, + ppd_file_t *ppd); + +extern int cmIsPrinterCmDisabled (const char *printer_id); + +extern double* cmGammaAdobeRgb (void); +extern double* cmGammaSGray (void); + +extern double* cmWhitePointAdobeRgb (void); +extern double* cmWhitePointSGray (void); + +extern double* cmMatrixAdobeRgb (void); +extern double* cmBlackPointDefault (void); + + + +# ifdef __cplusplus +} +# endif /* __cplusplus */ + +#endif /* !_CUPS_FILTERS_COLORMANAGER_H_ */ diff -Nru cups-filters-1.0.52/debian/local/rastertopdf/rastertopdf.cpp cups-filters-1.0.52/debian/local/rastertopdf/rastertopdf.cpp --- cups-filters-1.0.52/debian/local/rastertopdf/rastertopdf.cpp 1970-01-01 01:00:00.000000000 +0100 +++ cups-filters-1.0.52/debian/local/rastertopdf/rastertopdf.cpp 2014-10-23 12:45:31.000000000 +0200 @@ -0,0 +1,1100 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * @brief Convert PWG Raster to a PDF file + * @file rastertopdf.cpp + * @author Neil 'Superna' Armstrong (C) 2010 + * @author Tobias Hoffmann (c) 2012 + * @author Till Kamppeter (c) 2014 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // ntohl + +#include +#include +#include +#include + +#include +#include + +#ifdef USE_LCMS1 +#include +#define cmsColorSpaceSignature icColorSpaceSignature +#define cmsSetLogErrorHandler cmsSetErrorHandler +#define cmsSigXYZData icSigXYZData +#define cmsSigLuvData icSigLuvData +#define cmsSigLabData icSigLabData +#define cmsSigYCbCrData icSigYCbCrData +#define cmsSigYxyData icSigYxyData +#define cmsSigRgbData icSigRgbData +#define cmsSigHsvData icSigHsvData +#define cmsSigHlsData icSigHlsData +#define cmsSigCmyData icSigCmyData +#define cmsSig3colorData icSig3colorData +#define cmsSigGrayData icSigGrayData +#define cmsSigCmykData icSigCmykData +#define cmsSig4colorData icSig4colorData +#define cmsSig2colorData icSig2colorData +#define cmsSig5colorData icSig5colorData +#define cmsSig6colorData icSig6colorData +#define cmsSig7colorData icSig7colorData +#define cmsSig8colorData icSig8colorData +#define cmsSig9colorData icSig9colorData +#define cmsSig10colorData icSig10colorData +#define cmsSig11colorData icSig11colorData +#define cmsSig12colorData icSig12colorData +#define cmsSig13colorData icSig13colorData +#define cmsSig14colorData icSig14colorData +#define cmsSig15colorData icSig15colorData +#define cmsSaveProfileToMem _cmsSaveProfileToMem +#else +#include +#endif + +#define DEFAULT_PDF_UNIT 72 // 1/72 inch + +#define PROGRAM "rastertopdf" + +#define dprintf(format, ...) fprintf(stderr, "DEBUG2: (" PROGRAM ") " format, __VA_ARGS__) + +#define iprintf(format, ...) fprintf(stderr, "INFO: (" PROGRAM ") " format, __VA_ARGS__) + + +// Color conversion function +typedef unsigned char *(*convertFunction)(unsigned char *src, + unsigned char *dst, unsigned int pixels); + +// Bit conversion function +typedef unsigned char *(*bitFunction)(unsigned char *src, + unsigned char *dst, unsigned int pixels); + +// PDF color conversion function +typedef void (*pdfConvertFunction)(struct pdf_info * info); + +cmsHPROFILE colorProfile = NULL; // ICC Profile to be applied to PDF +int cm_disabled = 0; // Flag rasied if color management is disabled +cm_calibration_t cm_calibrate; // Status of CUPS color management ("on" or "off") +convertFunction conversion_function; // Raster color conversion function +bitFunction bit_function; // Raster bit function + + +#ifdef USE_LCMS1 +static int lcmsErrorHandler(int ErrorCode, const char *ErrorText) +{ + fprintf(stderr, "ERROR: %s\n",ErrorText); + return 1; +} +#else +static void lcmsErrorHandler(cmsContext contextId, cmsUInt32Number ErrorCode, + const char *ErrorText) +{ + fprintf(stderr, "ERROR: %s\n",ErrorText); +} +#endif + + + +// Bit conversion functions + +unsigned char *invertBits(unsigned char *src, unsigned char *dst, unsigned int pixels) +{ + unsigned int i; + + // Invert black to grayscale... + for (i = pixels, dst = src; i > 0; i --, dst ++) + *dst = ~*dst; + + return dst; +} + +unsigned char *noBitConversion(unsigned char *src, unsigned char *dst, unsigned int pixels) +{ + return src; +} + +// Color conversion functions + +unsigned char *rgbToCmyk(unsigned char *src, unsigned char *dst, unsigned int pixels) +{ + cupsImageRGBToCMYK(src,dst,pixels); + return dst; +} +unsigned char *whiteToCmyk(unsigned char *src, unsigned char *dst, unsigned int pixels) +{ + cupsImageWhiteToCMYK(src,dst,pixels); + return dst; +} + +unsigned char *cmykToRgb(unsigned char *src, unsigned char *dst, unsigned int pixels) +{ + cupsImageCMYKToRGB(src,dst,pixels); + return dst; +} + +unsigned char *whiteToRgb(unsigned char *src, unsigned char *dst, unsigned int pixels) +{ + cupsImageWhiteToRGB(src,dst,pixels); + return dst; +} + +unsigned char *rgbToWhite(unsigned char *src, unsigned char *dst, unsigned int pixels) +{ + cupsImageRGBToWhite(src,dst,pixels); + return dst; +} + +unsigned char *cmykToWhite(unsigned char *src, unsigned char *dst, unsigned int pixels) +{ + cupsImageCMYKToWhite(src,dst,pixels); + return dst; +} + +unsigned char *noColorConversion(unsigned char *src, + unsigned char *dst, unsigned int pixels) +{ + return src; +} + + + + +void die(const char * str) +{ + fprintf(stderr, "ERROR: (" PROGRAM ") %s\n", str); + exit(1); +} + + +//------------- PDF --------------- + +struct pdf_info +{ + pdf_info() + : pagecount(0), + width(0),height(0), + line_bytes(0), + bpp(0), bpc(0), render_intent(""), + color_space(CUPS_CSPACE_K), + page_width(0),page_height(0) + { + } + + QPDF pdf; + QPDFObjectHandle page; + unsigned pagecount; + unsigned width; + unsigned height; + unsigned line_bytes; + unsigned bpp; + unsigned bpc; + std::string render_intent; + cups_cspace_t color_space; + PointerHolder page_data; + double page_width,page_height; +}; + +int create_pdf_file(struct pdf_info * info) +{ + try { + info->pdf.emptyPDF(); + } catch (...) { + return 1; + } + return 0; +} + +QPDFObjectHandle makeBox(double x1, double y1, double x2, double y2) +{ + QPDFObjectHandle ret=QPDFObjectHandle::newArray(); + ret.appendItem(QPDFObjectHandle::newReal(x1)); + ret.appendItem(QPDFObjectHandle::newReal(y1)); + ret.appendItem(QPDFObjectHandle::newReal(x2)); + ret.appendItem(QPDFObjectHandle::newReal(y2)); + return ret; +} + + + + + +// PDF color conversion functons... + +void modify_pdf_color(struct pdf_info * info, int bpp, int bpc, convertFunction fn) +{ + unsigned old_bpp = info->bpp; + unsigned old_bpc = info->bpc; + double old_ncolor = old_bpp/old_bpc; + + unsigned old_line_bytes = info->line_bytes; + + double new_ncolor = (bpp/bpc); + + info->line_bytes = (unsigned)old_line_bytes*(new_ncolor/old_ncolor); + info->bpp = bpp; + info->bpc = bpc; + conversion_function = fn; + + return; +} + +void convertPdf_NoConversion(struct pdf_info * info) +{ + conversion_function = noColorConversion; + bit_function = noBitConversion; +} + +void convertPdf_Cmyk8ToWhite8(struct pdf_info * info) +{ + modify_pdf_color(info, 8, 8, cmykToWhite); + bit_function = noBitConversion; +} + +void convertPdf_Rgb8ToWhite8(struct pdf_info * info) +{ + modify_pdf_color(info, 8, 8, rgbToWhite); + bit_function = noBitConversion; +} + +void convertPdf_Cmyk8ToRgb8(struct pdf_info * info) +{ + modify_pdf_color(info, 24, 8, cmykToRgb); + bit_function = noBitConversion; +} + +void convertPdf_White8ToRgb8(struct pdf_info * info) +{ + modify_pdf_color(info, 24, 8, whiteToRgb); + bit_function = invertBits; +} + +void convertPdf_Rgb8ToCmyk8(struct pdf_info * info) +{ + modify_pdf_color(info, 32, 8, rgbToCmyk); + bit_function = noBitConversion; +} + +void convertPdf_White8ToCmyk8(struct pdf_info * info) +{ + modify_pdf_color(info, 32, 8, whiteToCmyk); + bit_function = invertBits; +} + +void convertPdf_InvertColors(struct pdf_info * info) +{ + conversion_function = noColorConversion; + bit_function = invertBits; +} + + + +#define PRE_COMPRESS + +// Create an '/ICCBased' array and embed a previously +// set ICC Profile in the PDF +QPDFObjectHandle embedIccProfile(QPDF &pdf) +{ + if (colorProfile == NULL) { + return QPDFObjectHandle::newNull(); + } + + // Return handler + QPDFObjectHandle ret; + // ICCBased array + QPDFObjectHandle array = QPDFObjectHandle::newArray(); + // Profile stream dictionary + QPDFObjectHandle iccstream; + + std::map dict; + std::map streamdict; + std::string n_value = ""; + std::string alternate_cs = ""; + PointerHolderph; + +#ifdef USE_LCMS1 + size_t profile_size; +#else + unsigned int profile_size; +#endif + + cmsColorSpaceSignature css = cmsGetColorSpace(colorProfile); + + // Write color component # for /ICCBased array in stream dictionary + switch(css){ + case cmsSigGrayData: + n_value = "1"; + alternate_cs = "/DeviceGray"; + break; + case cmsSigRgbData: + n_value = "3"; + alternate_cs = "/DeviceRGB"; + break; + case cmsSigCmykData: + n_value = "4"; + alternate_cs = "/DeviceCMYK"; + break; + default: + fputs("DEBUG: Failed to embed ICC Profile.\n", stderr); + return QPDFObjectHandle::newNull(); + } + + streamdict["/Alternate"]=QPDFObjectHandle::newName(alternate_cs); + streamdict["/N"]=QPDFObjectHandle::newName(n_value); + + // Read profile into memory + cmsSaveProfileToMem(colorProfile, NULL, &profile_size); + unsigned char buff[profile_size]; + cmsSaveProfileToMem(colorProfile, buff, &profile_size); + + // Write ICC profile buffer into PDF + ph = new Buffer(buff, profile_size); + iccstream = QPDFObjectHandle::newStream(&pdf, ph); + iccstream.replaceDict(QPDFObjectHandle::newDictionary(streamdict)); + + array.appendItem(QPDFObjectHandle::newName("/ICCBased")); + array.appendItem(iccstream); + + // Return a PDF object reference to an '/ICCBased' array + ret = pdf.makeIndirectObject(array); + + fputs("DEBUG: ICC Profile embedded in PDF.\n", stderr); + + return ret; +} + +QPDFObjectHandle embedSrgbProfile(QPDF &pdf) +{ + QPDFObjectHandle iccbased_reference; + + // Create an sRGB profile from lcms + colorProfile = cmsCreate_sRGBProfile(); + // Embed it into the profile + iccbased_reference = embedIccProfile(pdf); + + return iccbased_reference; +} + +/* +Calibration function for non-Lab PDF color spaces +Requires white point data, and if available, gamma or matrix numbers. + +Output: + [/'color_space' + << /Gamma ['gamma[0]'...'gamma[n]'] + /WhitePoint ['wp[0]' 'wp[1]' 'wp[2]'] + /Matrix ['matrix[0]'...'matrix[n*n]'] + >> + ] +*/ +QPDFObjectHandle getCalibrationArray(const char * color_space, double wp[], + double gamma[], double matrix[], double bp[]) +{ + // Check for invalid input + if ((!strcmp("/CalGray", color_space) && matrix != NULL) || + wp == NULL) + return QPDFObjectHandle(); + + QPDFObjectHandle ret; + std::string csString = color_space; + std::string colorSpaceArrayString = ""; + + char gamma_str[128]; + char bp_str[256]; + char wp_str[256]; + char matrix_str[512]; + + + // Convert numbers into string data for /Gamma, /WhitePoint, and/or /Matrix + + + // WhitePoint + snprintf(wp_str, sizeof(wp_str), "/WhitePoint [%g %g %g]", + wp[0], wp[1], wp[2]); + + + // Gamma + if (!strcmp("/CalGray", color_space) && gamma != NULL) + snprintf(gamma_str, sizeof(gamma_str), "/Gamma %g", + gamma[0]); + else if (!strcmp("/CalRGB", color_space) && gamma != NULL) + snprintf(gamma_str, sizeof(gamma_str), "/Gamma [%g %g %g]", + gamma[0], gamma[1], gamma[2]); + else + gamma_str[0] = '\0'; + + + // BlackPoint + if (bp != NULL) + snprintf(bp_str, sizeof(bp_str), "/BlackPoint [%g %g %g]", + bp[0], bp[1], bp[2]); + else + bp_str[0] = '\0'; + + + // Matrix + if (!strcmp("/CalRGB", color_space) && matrix != NULL) { + snprintf(matrix_str, sizeof(matrix_str), "/Matrix [%g %g %g %g %g %g %g %g %g]", + matrix[0], matrix[1], matrix[2], + matrix[3], matrix[4], matrix[5], + matrix[6], matrix[7], matrix[8]); + } else + matrix_str[0] = '\0'; + + + // Write array string... + colorSpaceArrayString = "[" + csString + " <<" + + gamma_str + " " + wp_str + " " + matrix_str + " " + bp_str + + " >>]"; + + ret = QPDFObjectHandle::parse(colorSpaceArrayString); + + return ret; +} + +QPDFObjectHandle getCalRGBArray(double wp[3], double gamma[3], double matrix[9], double bp[3]) +{ + QPDFObjectHandle ret = getCalibrationArray("/CalRGB", wp, gamma, matrix, bp); + return ret; +} + +QPDFObjectHandle getCalGrayArray(double wp[3], double gamma[1], double bp[3]) +{ + QPDFObjectHandle ret = getCalibrationArray("/CalGray", wp, gamma, 0, bp); + return ret; +} + +QPDFObjectHandle makeImage(QPDF &pdf, PointerHolder page_data, unsigned width, + unsigned height, std::string render_intent, cups_cspace_t cs, unsigned bpc) +{ + QPDFObjectHandle ret = QPDFObjectHandle::newStream(&pdf); + + QPDFObjectHandle icc_ref; + + int use_blackpoint = 0; + std::map dict; + + dict["/Type"]=QPDFObjectHandle::newName("/XObject"); + dict["/Subtype"]=QPDFObjectHandle::newName("/Image"); + dict["/Width"]=QPDFObjectHandle::newInteger(width); + dict["/Height"]=QPDFObjectHandle::newInteger(height); + dict["/BitsPerComponent"]=QPDFObjectHandle::newInteger(bpc); + + if (!cm_disabled) { + // Write rendering intent into the PDF based on raster settings + if (render_intent == "Perceptual") { + dict["/Intent"]=QPDFObjectHandle::newName("/Perceptual"); + } else if (render_intent == "Absolute") { + dict["/Intent"]=QPDFObjectHandle::newName("/AbsoluteColorimetric"); + } else if (render_intent == "Relative") { + dict["/Intent"]=QPDFObjectHandle::newName("/RelativeColorimetric"); + } else if (render_intent == "Saturation") { + dict["/Intent"]=QPDFObjectHandle::newName("/Saturation"); + } else if (render_intent == "RelativeBpc") { + /* Enable blackpoint compensation */ + dict["/Intent"]=QPDFObjectHandle::newName("/RelativeColorimetric"); + use_blackpoint = 1; + } + } + + /* Write "/ColorSpace" dictionary based on raster input */ + if (colorProfile != NULL && !cm_disabled) { + icc_ref = embedIccProfile(pdf); + + if (!icc_ref.isNull()) + dict["/ColorSpace"]=icc_ref; + } else if (!cm_disabled) { + switch (cs) { + case CUPS_CSPACE_DEVICE1: + case CUPS_CSPACE_DEVICE2: + case CUPS_CSPACE_DEVICE3: + case CUPS_CSPACE_DEVICE4: + case CUPS_CSPACE_DEVICE5: + case CUPS_CSPACE_DEVICE6: + case CUPS_CSPACE_DEVICE7: + case CUPS_CSPACE_DEVICE8: + case CUPS_CSPACE_DEVICE9: + case CUPS_CSPACE_DEVICEA: + case CUPS_CSPACE_DEVICEB: + case CUPS_CSPACE_DEVICEC: + case CUPS_CSPACE_DEVICED: + case CUPS_CSPACE_DEVICEE: + case CUPS_CSPACE_DEVICEF: + // For right now, DeviceN will use /DeviceCMYK in the PDF + dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK"); + break; + case CUPS_CSPACE_K: + dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceGray"); + break; + case CUPS_CSPACE_SW: + if (use_blackpoint) + dict["/ColorSpace"]=getCalGrayArray(cmWhitePointSGray(), cmGammaSGray(), + cmBlackPointDefault()); + else + dict["/ColorSpace"]=getCalGrayArray(cmWhitePointSGray(), cmGammaSGray(), 0); + break; + case CUPS_CSPACE_CMYK: + dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK"); + break; + case CUPS_CSPACE_RGB: + dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB"); + break; + case CUPS_CSPACE_SRGB: + icc_ref = embedSrgbProfile(pdf); + if (!icc_ref.isNull()) + dict["/ColorSpace"]=icc_ref; + else + dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB"); + break; + case CUPS_CSPACE_ADOBERGB: + if (use_blackpoint) + dict["/ColorSpace"]=getCalRGBArray(cmWhitePointAdobeRgb(), cmGammaAdobeRgb(), + cmMatrixAdobeRgb(), cmBlackPointDefault()); + else + dict["/ColorSpace"]=getCalRGBArray(cmWhitePointAdobeRgb(), + cmGammaAdobeRgb(), cmMatrixAdobeRgb(), 0); + break; + default: + fputs("DEBUG: Color space not supported.\n", stderr); + return QPDFObjectHandle(); + } + } else if (cm_disabled) { + switch(cs) { + case CUPS_CSPACE_K: + case CUPS_CSPACE_SW: + dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceGray"); + break; + case CUPS_CSPACE_RGB: + case CUPS_CSPACE_SRGB: + case CUPS_CSPACE_ADOBERGB: + dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB"); + break; + case CUPS_CSPACE_DEVICE1: + case CUPS_CSPACE_DEVICE2: + case CUPS_CSPACE_DEVICE3: + case CUPS_CSPACE_DEVICE4: + case CUPS_CSPACE_DEVICE5: + case CUPS_CSPACE_DEVICE6: + case CUPS_CSPACE_DEVICE7: + case CUPS_CSPACE_DEVICE8: + case CUPS_CSPACE_DEVICE9: + case CUPS_CSPACE_DEVICEA: + case CUPS_CSPACE_DEVICEB: + case CUPS_CSPACE_DEVICEC: + case CUPS_CSPACE_DEVICED: + case CUPS_CSPACE_DEVICEE: + case CUPS_CSPACE_DEVICEF: + case CUPS_CSPACE_CMYK: + dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK"); + break; + default: + fputs("DEBUG: Color space not supported.\n", stderr); + return QPDFObjectHandle(); + } + } else + return QPDFObjectHandle(); + + ret.replaceDict(QPDFObjectHandle::newDictionary(dict)); + +#ifdef PRE_COMPRESS + // we deliver already compressed content (instead of letting QPDFWriter do it), to avoid using excessive memory + Pl_Buffer psink("psink"); + Pl_Flate pflate("pflate",&psink,Pl_Flate::a_deflate); + + pflate.write(page_data->getBuffer(),page_data->getSize()); + pflate.finish(); + + ret.replaceStreamData(PointerHolder(psink.getBuffer()), + QPDFObjectHandle::newName("/FlateDecode"),QPDFObjectHandle::newNull()); +#else + ret.replaceStreamData(page_data,QPDFObjectHandle::newNull(),QPDFObjectHandle::newNull()); +#endif + + return ret; +} + +void finish_page(struct pdf_info * info) +{ + //Finish previous Page + if(!info->page_data.getPointer()) + return; + + QPDFObjectHandle image = makeImage(info->pdf, info->page_data, info->width, info->height, info->render_intent, info->color_space, info->bpc); + if(!image.isInitialized()) die("Unable to load image data"); + + // add it + info->page.getKey("/Resources").getKey("/XObject").replaceKey("/I",image); + + // draw it + std::string content; + content.append(QUtil::double_to_string(info->page_width) + " 0 0 " + + QUtil::double_to_string(info->page_height) + " 0 0 cm\n"); + content.append("/I Do\n"); + info->page.getKey("/Contents").replaceStreamData(content,QPDFObjectHandle::newNull(),QPDFObjectHandle::newNull()); + + // bookkeeping + info->page_data = PointerHolder(); +} + + +/* Perform modifications to PDF if color space conversions are needed */ +int prepare_pdf_page(struct pdf_info * info, int width, int height, int bpl, + int bpp, int bpc, std::string render_intent, cups_cspace_t color_space) +{ +#define IMAGE_CMYK_8 (bpp == 32 && bpc == 8) +#define IMAGE_CMYK_16 (bpp == 64 && bpc == 16) +#define IMAGE_RGB_8 (bpp == 24 && bpc == 8) +#define IMAGE_RGB_16 (bpp == 48 && bpc == 16) +#define IMAGE_WHITE_1 (bpp == 1 && bpc == 1) +#define IMAGE_WHITE_8 (bpp == 8 && bpc == 8) +#define IMAGE_WHITE_16 (bpp == 16 && bpc == 16) + + int error = 0; + pdfConvertFunction fn = convertPdf_NoConversion; + cmsColorSpaceSignature css; + + /* Register available raster information into the PDF */ + info->width = width; + info->height = height; + info->line_bytes = bpl; + info->bpp = bpp; + info->bpc = bpc; + info->render_intent = render_intent; + info->color_space = color_space; + + /* Invert grayscale by default */ + if (color_space == CUPS_CSPACE_K) + fn = convertPdf_InvertColors; + + if (colorProfile != NULL) { + css = cmsGetColorSpace(colorProfile); + + // Convert image and PDF color space to an embedded ICC Profile color space + switch(css) { + // Convert PDF to Grayscale when using a gray profile + case cmsSigGrayData: + if (color_space == CUPS_CSPACE_CMYK) + fn = convertPdf_Cmyk8ToWhite8; + else if (color_space == CUPS_CSPACE_RGB) + fn = convertPdf_Rgb8ToWhite8; + else + fn = convertPdf_InvertColors; + info->color_space = CUPS_CSPACE_K; + break; + // Convert PDF to RGB when using an RGB profile + case cmsSigRgbData: + if (color_space == CUPS_CSPACE_CMYK) + fn = convertPdf_Cmyk8ToRgb8; + else if (color_space == CUPS_CSPACE_K) + fn = convertPdf_White8ToRgb8; + info->color_space = CUPS_CSPACE_RGB; + break; + // Convert PDF to CMYK when using an RGB profile + case cmsSigCmykData: + if (color_space == CUPS_CSPACE_RGB) + fn = convertPdf_Rgb8ToCmyk8; + else if (color_space == CUPS_CSPACE_K) + fn = convertPdf_White8ToCmyk8; + info->color_space = CUPS_CSPACE_CMYK; + break; + default: + fputs("DEBUG: Unable to convert PDF from profile.\n", stderr); + colorProfile = NULL; + error = 1; + } + // Perform conversion of an image color space + } else if (!cm_disabled) { + switch (color_space) { + // Convert image to CMYK + case CUPS_CSPACE_CMYK: + if (IMAGE_RGB_8) + fn = convertPdf_Rgb8ToCmyk8; + else if (IMAGE_RGB_16) + fn = convertPdf_NoConversion; + else if (IMAGE_WHITE_8) + fn = convertPdf_White8ToCmyk8; + else if (IMAGE_WHITE_16) + fn = convertPdf_NoConversion; + break; + // Convert image to RGB + case CUPS_CSPACE_ADOBERGB: + case CUPS_CSPACE_RGB: + case CUPS_CSPACE_SRGB: + if (IMAGE_CMYK_8) + fn = convertPdf_Cmyk8ToRgb8; + else if (IMAGE_CMYK_16) + fn = convertPdf_NoConversion; + else if (IMAGE_WHITE_8) + fn = convertPdf_White8ToRgb8; + else if (IMAGE_WHITE_16) + fn = convertPdf_NoConversion; + break; + // Convert image to Grayscale + case CUPS_CSPACE_SW: + case CUPS_CSPACE_K: + if (IMAGE_CMYK_8) + fn = convertPdf_Cmyk8ToWhite8; + else if (IMAGE_CMYK_16) + fn = convertPdf_NoConversion; + else if (IMAGE_RGB_8) + fn = convertPdf_Rgb8ToWhite8; + else if (IMAGE_RGB_16) + fn = convertPdf_NoConversion; + break; + case CUPS_CSPACE_DEVICE1: + case CUPS_CSPACE_DEVICE2: + case CUPS_CSPACE_DEVICE3: + case CUPS_CSPACE_DEVICE4: + case CUPS_CSPACE_DEVICE5: + case CUPS_CSPACE_DEVICE6: + case CUPS_CSPACE_DEVICE7: + case CUPS_CSPACE_DEVICE8: + case CUPS_CSPACE_DEVICE9: + case CUPS_CSPACE_DEVICEA: + case CUPS_CSPACE_DEVICEB: + case CUPS_CSPACE_DEVICEC: + case CUPS_CSPACE_DEVICED: + case CUPS_CSPACE_DEVICEE: + case CUPS_CSPACE_DEVICEF: + // No conversion for right now + fn = convertPdf_NoConversion; + break; + default: + fputs("DEBUG: Color space not supported.\n", stderr); + error = 1; + break; + } + } + + if (!error) + fn(info); + + return error; +} + +int add_pdf_page(struct pdf_info * info, int pagen, unsigned width, + unsigned height, int bpp, int bpc, int bpl, std::string render_intent, + cups_cspace_t color_space, unsigned xdpi, unsigned ydpi) +{ + try { + finish_page(info); // any active + + prepare_pdf_page(info, width, height, bpl, bpp, + bpc, render_intent, color_space); + + if (info->height > (std::numeric_limits::max() / info->line_bytes)) { + die("Page too big"); + } + info->page_data = PointerHolder(new Buffer(info->line_bytes*info->height)); + + QPDFObjectHandle page = QPDFObjectHandle::parse( + "<<" + " /Type /Page" + " /Resources <<" + " /XObject << >> " + " >>" + " /MediaBox null " + " /Contents null " + ">>"); + page.replaceKey("/Contents",QPDFObjectHandle::newStream(&info->pdf)); // data will be provided later + + // Convert to pdf units + info->page_width=((double)info->width/xdpi)*DEFAULT_PDF_UNIT; + info->page_height=((double)info->height/ydpi)*DEFAULT_PDF_UNIT; + page.replaceKey("/MediaBox",makeBox(0,0,info->page_width,info->page_height)); + + info->page = info->pdf.makeIndirectObject(page); // we want to keep a reference + info->pdf.addPage(info->page, false); + } catch (std::bad_alloc &ex) { + die("Unable to allocate page data"); + } catch (...) { + return 1; + } + + return 0; +} + +int close_pdf_file(struct pdf_info * info) +{ + try { + finish_page(info); // any active + + QPDFWriter output(info->pdf,NULL); +// output.setMinimumPDFVersion("1.4"); + output.write(); + } catch (...) { + return 1; + } + + return 0; +} + +void pdf_set_line(struct pdf_info * info, unsigned line_n, unsigned char *line) +{ + //dprintf("pdf_set_line(%d)\n", line_n); + + if(line_n > info->height) + { + dprintf("Bad line %d\n", line_n); + return; + } + + memcpy((info->page_data->getBuffer()+(line_n*info->line_bytes)), line, info->line_bytes); +} + +int convert_raster(cups_raster_t *ras, unsigned width, unsigned height, + int bpp, int bpl, struct pdf_info * info) +{ + // We should be at raster start + int i; + unsigned cur_line = 0; + unsigned char *PixelBuffer, *ptr, *buff; + + PixelBuffer = (unsigned char *)malloc(bpl); + buff = (unsigned char *)malloc(info->line_bytes); + + do + { + // Read raster data... + cupsRasterReadPixels(ras, PixelBuffer, bpl); + +#if !ARCH_IS_BIG_ENDIAN + + if (info->bpc == 16) + { + // Swap byte pairs for endianess (cupsRasterReadPixels() switches + // from Big Endian back to the system's Endian) + for (i = bpl, ptr = PixelBuffer; i > 0; i -= 2, ptr += 2) + { + unsigned char swap = *ptr; + *ptr = *(ptr + 1); + *(ptr + 1) = swap; + } + } +#endif /* !ARCH_IS_BIG_ENDIAN */ + + // perform bit operations if necessary + bit_function(PixelBuffer, ptr, bpl); + + // write lines and color convert when necessary + pdf_set_line(info, cur_line, conversion_function(PixelBuffer, buff, width)); + ++cur_line; + } + while(cur_line < height); + + free(buff); + free(PixelBuffer); + + return 0; +} + +int setProfile(const char * path) +{ + if (path != NULL) + colorProfile = cmsOpenProfileFromFile(path,"r"); + + if (colorProfile != NULL) { + fputs("DEBUG: Load profile successful.\n", stderr); + return 0; + } + else { + fputs("DEBUG: Unable to load profile.\n", stderr); + return 1; + } +} + +/* Obtain a source profile name using color qualifiers from raster file */ +const char * getIPPColorProfileName(const char * media_type, cups_cspace_t cs, unsigned dpi) +{ + std::string mediaType = ""; + std::string resolution = ""; + std::string colorModel = ""; + + std::string iccProfile = ""; + + // ColorModel + switch (cs) { + case CUPS_CSPACE_RGB: + colorModel = "rgb"; + break; + case CUPS_CSPACE_SRGB: + colorModel = "srgb"; + break; + case CUPS_CSPACE_ADOBERGB: + colorModel = "adobergb"; + break; + case CUPS_CSPACE_K: + colorModel = "gray"; + break; + case CUPS_CSPACE_CMYK: + colorModel = "cmyk"; + break; + default: + colorModel = ""; + break; + } + + if (media_type != NULL) + mediaType = media_type; + if (dpi > 0) + resolution = dpi; + + // Requires color space and media type qualifiers + if (resolution != "" || colorModel != "") + return 0; + + // profile-uri reference: "http://www.server.com/colorModel-Resolution-mediaType.icc + if (mediaType != "") + iccProfile = colorModel + "-" + resolution + ".icc"; + else + iccProfile = colorModel + "-" + resolution + "-" + mediaType + ".icc"; + + return iccProfile.c_str(); +} + +int main(int argc, char **argv) +{ + int fd, Page; + struct pdf_info pdf; + FILE * input = NULL; + cups_raster_t *ras; /* Raster stream for printing */ + cups_page_header2_t header; /* Page header from file */ + ppd_file_t *ppd; /* PPD file */ + int num_options; /* Number of options */ + const char* profile_name; /* IPP Profile Name */ + cups_option_t *options; /* Options */ + + // Make sure status messages are not buffered... + setbuf(stderr, NULL); + + cmsSetLogErrorHandler(lcmsErrorHandler); + + if (argc < 6 || argc > 7) + { + fprintf(stderr, "Usage: %s