| /* |
| * IPP Everywhere printer application for CUPS. |
| * |
| * Copyright © 2010-2019 by Apple Inc. |
| * |
| * Licensed under Apache License v2.0. See the file "LICENSE" for more |
| * information.º |
| * |
| * Note: This program began life as the "ippserver" sample code that first |
| * appeared in CUPS 1.4. The name has been changed in order to distinguish it |
| * from the PWG's much more ambitious "ippserver" program, which supports |
| * different kinds of IPP services and multiple services per instance - the |
| * "ippeveprinter" program exposes a single print service conforming to the |
| * current IPP Everywhere specification, thus the new name. |
| */ |
| |
| /* |
| * Include necessary headers... |
| */ |
| |
| #include <cups/cups-private.h> |
| #include <cups/debug-private.h> |
| #if !CUPS_LITE |
| # include <cups/ppd-private.h> |
| #endif /* !CUPS_LITE */ |
| |
| #include <limits.h> |
| #include <sys/stat.h> |
| |
| #ifdef _WIN32 |
| # include <fcntl.h> |
| # include <io.h> |
| # include <process.h> |
| # define WEXITSTATUS(s) (s) |
| # include <winsock2.h> |
| typedef ULONG nfds_t; |
| # define poll WSAPoll |
| #else |
| extern char **environ; |
| |
| # include <sys/fcntl.h> |
| # include <sys/wait.h> |
| # include <poll.h> |
| #endif /* _WIN32 */ |
| |
| #ifdef HAVE_DNSSD |
| # include <dns_sd.h> |
| #elif defined(HAVE_AVAHI) |
| # include <avahi-client/client.h> |
| # include <avahi-client/publish.h> |
| # include <avahi-common/error.h> |
| # include <avahi-common/thread-watch.h> |
| #endif /* HAVE_DNSSD */ |
| |
| #ifdef HAVE_SYS_MOUNT_H |
| # include <sys/mount.h> |
| #endif /* HAVE_SYS_MOUNT_H */ |
| #ifdef HAVE_SYS_STATFS_H |
| # include <sys/statfs.h> |
| #endif /* HAVE_SYS_STATFS_H */ |
| #ifdef HAVE_SYS_STATVFS_H |
| # include <sys/statvfs.h> |
| #endif /* HAVE_SYS_STATVFS_H */ |
| #ifdef HAVE_SYS_VFS_H |
| # include <sys/vfs.h> |
| #endif /* HAVE_SYS_VFS_H */ |
| |
| #if HAVE_LIBPAM |
| # ifdef HAVE_PAM_PAM_APPL_H |
| # include <pam/pam_appl.h> |
| # else |
| # include <security/pam_appl.h> |
| # endif /* HAVE_PAM_PAM_APPL_H */ |
| #endif /* HAVE_LIBPAM */ |
| |
| #include "printer-png.h" |
| |
| |
| /* |
| * Constants... |
| */ |
| |
| enum ippeve_preason_e /* printer-state-reasons bit values */ |
| { |
| IPPEVE_PREASON_NONE = 0x0000, /* none */ |
| IPPEVE_PREASON_OTHER = 0x0001, /* other */ |
| IPPEVE_PREASON_COVER_OPEN = 0x0002, /* cover-open */ |
| IPPEVE_PREASON_INPUT_TRAY_MISSING = 0x0004, |
| /* input-tray-missing */ |
| IPPEVE_PREASON_MARKER_SUPPLY_EMPTY = 0x0008, |
| /* marker-supply-empty */ |
| IPPEVE_PREASON_MARKER_SUPPLY_LOW = 0x0010, |
| /* marker-supply-low */ |
| IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL = 0x0020, |
| /* marker-waste-almost-full */ |
| IPPEVE_PREASON_MARKER_WASTE_FULL = 0x0040, |
| /* marker-waste-full */ |
| IPPEVE_PREASON_MEDIA_EMPTY = 0x0080, /* media-empty */ |
| IPPEVE_PREASON_MEDIA_JAM = 0x0100, /* media-jam */ |
| IPPEVE_PREASON_MEDIA_LOW = 0x0200, /* media-low */ |
| IPPEVE_PREASON_MEDIA_NEEDED = 0x0400, /* media-needed */ |
| IPPEVE_PREASON_MOVING_TO_PAUSED = 0x0800, |
| /* moving-to-paused */ |
| IPPEVE_PREASON_PAUSED = 0x1000, /* paused */ |
| IPPEVE_PREASON_SPOOL_AREA_FULL = 0x2000,/* spool-area-full */ |
| IPPEVE_PREASON_TONER_EMPTY = 0x4000, /* toner-empty */ |
| IPPEVE_PREASON_TONER_LOW = 0x8000 /* toner-low */ |
| }; |
| typedef unsigned int ippeve_preason_t; /* Bitfield for printer-state-reasons */ |
| static const char * const ippeve_preason_strings[] = |
| { /* Strings for each bit */ |
| /* "none" is implied for no bits set */ |
| "other", |
| "cover-open", |
| "input-tray-missing", |
| "marker-supply-empty", |
| "marker-supply-low", |
| "marker-waste-almost-full", |
| "marker-waste-full", |
| "media-empty", |
| "media-jam", |
| "media-low", |
| "media-needed", |
| "moving-to-paused", |
| "paused", |
| "spool-area-full", |
| "toner-empty", |
| "toner-low" |
| }; |
| |
| |
| /* |
| * URL scheme for web resources... |
| */ |
| |
| #ifdef HAVE_SSL |
| # define WEB_SCHEME "https" |
| #else |
| # define WEB_SCHEME "http" |
| #endif /* HAVE_SSL */ |
| |
| |
| /* |
| * Structures... |
| */ |
| |
| #ifdef HAVE_DNSSD |
| typedef DNSServiceRef ippeve_srv_t; /* Service reference */ |
| typedef TXTRecordRef ippeve_txt_t; /* TXT record */ |
| |
| #elif defined(HAVE_AVAHI) |
| typedef AvahiEntryGroup *ippeve_srv_t; /* Service reference */ |
| typedef AvahiStringList *ippeve_txt_t; /* TXT record */ |
| |
| #else |
| typedef void *ippeve_srv_t; /* Service reference */ |
| typedef void *ippeve_txt_t; /* TXT record */ |
| #endif /* HAVE_DNSSD */ |
| |
| #if HAVE_LIBPAM |
| typedef struct ippeve_authdata_s /* Authentication data */ |
| { |
| char username[HTTP_MAX_VALUE], /* Username string */ |
| *password; /* Password string */ |
| } ippeve_authdata_t; |
| #endif /* HAVE_LIBPAM */ |
| |
| typedef struct ippeve_filter_s /**** Attribute filter ****/ |
| { |
| cups_array_t *ra; /* Requested attributes */ |
| ipp_tag_t group_tag; /* Group to copy */ |
| } ippeve_filter_t; |
| |
| typedef struct ippeve_job_s ippeve_job_t; |
| |
| typedef struct ippeve_printer_s /**** Printer data ****/ |
| { |
| /* TODO: One IPv4 and one IPv6 listener are really not sufficient */ |
| int ipv4, /* IPv4 listener */ |
| ipv6; /* IPv6 listener */ |
| ippeve_srv_t ipp_ref, /* Bonjour IPP service */ |
| ipps_ref, /* Bonjour IPPS service */ |
| http_ref, /* Bonjour HTTP service */ |
| printer_ref; /* Bonjour LPD service */ |
| char *dnssd_name, /* printer-dnssd-name */ |
| *name, /* printer-name */ |
| *icon, /* Icon filename */ |
| *directory, /* Spool directory */ |
| *hostname, /* Hostname */ |
| *uri, /* printer-uri-supported */ |
| *device_uri, /* Device URI (if any) */ |
| *output_format, /* Output format */ |
| #if !CUPS_LITE |
| *ppdfile, /* PPD file (if any) */ |
| #endif /* !CUPS_LITE */ |
| *command; /* Command to run with job file */ |
| int port; /* Port */ |
| int web_forms; /* Enable web interface forms? */ |
| size_t urilen; /* Length of printer URI */ |
| ipp_t *attrs; /* Static attributes */ |
| time_t start_time; /* Startup time */ |
| time_t config_time; /* printer-config-change-time */ |
| ipp_pstate_t state; /* printer-state value */ |
| ippeve_preason_t state_reasons; /* printer-state-reasons values */ |
| time_t state_time; /* printer-state-change-time */ |
| cups_array_t *jobs; /* Jobs */ |
| ippeve_job_t *active_job; /* Current active/pending job */ |
| int next_job_id; /* Next job-id value */ |
| _cups_rwlock_t rwlock; /* Printer lock */ |
| } ippeve_printer_t; |
| |
| struct ippeve_job_s /**** Job data ****/ |
| { |
| int id; /* Job ID */ |
| const char *name, /* job-name */ |
| *username, /* job-originating-user-name */ |
| *format; /* document-format */ |
| ipp_jstate_t state; /* job-state value */ |
| char *message; /* job-state-message value */ |
| int msglevel; /* job-state-message log level (0=error, 1=info) */ |
| time_t created, /* time-at-creation value */ |
| processing, /* time-at-processing value */ |
| completed; /* time-at-completed value */ |
| int impressions, /* job-impressions value */ |
| impcompleted; /* job-impressions-completed value */ |
| ipp_t *attrs; /* Static attributes */ |
| int cancel; /* Non-zero when job canceled */ |
| char *filename; /* Print file name */ |
| int fd; /* Print file descriptor */ |
| ippeve_printer_t *printer; /* Printer */ |
| }; |
| |
| typedef struct ippeve_client_s /**** Client data ****/ |
| { |
| http_t *http; /* HTTP connection */ |
| ipp_t *request, /* IPP request */ |
| *response; /* IPP response */ |
| time_t start; /* Request start time */ |
| http_state_t operation; /* Request operation */ |
| ipp_op_t operation_id; /* IPP operation-id */ |
| char uri[1024], /* Request URI */ |
| *options; /* URI options */ |
| http_addr_t addr; /* Client address */ |
| char hostname[256], /* Client hostname */ |
| username[HTTP_MAX_VALUE]; |
| /* Authenticated username, if any */ |
| ippeve_printer_t *printer; /* Printer */ |
| ippeve_job_t *job; /* Current job, if any */ |
| } ippeve_client_t; |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| static http_status_t authenticate_request(ippeve_client_t *client); |
| static void clean_jobs(ippeve_printer_t *printer); |
| static int compare_jobs(ippeve_job_t *a, ippeve_job_t *b); |
| static void copy_attributes(ipp_t *to, ipp_t *from, cups_array_t *ra, ipp_tag_t group_tag, int quickcopy); |
| static void copy_job_attributes(ippeve_client_t *client, ippeve_job_t *job, cups_array_t *ra); |
| static ippeve_client_t *create_client(ippeve_printer_t *printer, int sock); |
| static ippeve_job_t *create_job(ippeve_client_t *client); |
| static int create_job_file(ippeve_job_t *job, char *fname, size_t fnamesize, const char *dir, const char *ext); |
| static int create_listener(const char *name, int port, int family); |
| static ipp_t *create_media_col(const char *media, const char *source, const char *type, int width, int length, int bottom, int left, int right, int top); |
| static ipp_t *create_media_size(int width, int length); |
| static ippeve_printer_t *create_printer(const char *servername, int serverport, const char *name, const char *location, const char *icon, cups_array_t *docformats, const char *subtypes, const char *directory, const char *command, const char *device_uri, const char *output_format, ipp_t *attrs); |
| static void debug_attributes(const char *title, ipp_t *ipp, int response); |
| static void delete_client(ippeve_client_t *client); |
| static void delete_job(ippeve_job_t *job); |
| static void delete_printer(ippeve_printer_t *printer); |
| #ifdef HAVE_DNSSD |
| static void DNSSD_API dnssd_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, ippeve_printer_t *printer); |
| #elif defined(HAVE_AVAHI) |
| static void dnssd_callback(AvahiEntryGroup *p, AvahiEntryGroupState state, void *context); |
| static void dnssd_client_cb(AvahiClient *c, AvahiClientState state, void *userdata); |
| #endif /* HAVE_DNSSD */ |
| static void dnssd_init(void); |
| static int filter_cb(ippeve_filter_t *filter, ipp_t *dst, ipp_attribute_t *attr); |
| static ippeve_job_t *find_job(ippeve_client_t *client); |
| static void finish_document_data(ippeve_client_t *client, ippeve_job_t *job); |
| static void finish_document_uri(ippeve_client_t *client, ippeve_job_t *job); |
| static void html_escape(ippeve_client_t *client, const char *s, size_t slen); |
| static void html_footer(ippeve_client_t *client); |
| static void html_header(ippeve_client_t *client, const char *title, int refresh); |
| static void html_printf(ippeve_client_t *client, const char *format, ...) _CUPS_FORMAT(2, 3); |
| static void ipp_cancel_job(ippeve_client_t *client); |
| static void ipp_close_job(ippeve_client_t *client); |
| static void ipp_create_job(ippeve_client_t *client); |
| static void ipp_get_job_attributes(ippeve_client_t *client); |
| static void ipp_get_jobs(ippeve_client_t *client); |
| static void ipp_get_printer_attributes(ippeve_client_t *client); |
| static void ipp_identify_printer(ippeve_client_t *client); |
| static void ipp_print_job(ippeve_client_t *client); |
| static void ipp_print_uri(ippeve_client_t *client); |
| static void ipp_send_document(ippeve_client_t *client); |
| static void ipp_send_uri(ippeve_client_t *client); |
| static void ipp_validate_job(ippeve_client_t *client); |
| static ipp_t *load_ippserver_attributes(const char *servername, int serverport, const char *filename, cups_array_t *docformats); |
| static ipp_t *load_legacy_attributes(const char *make, const char *model, int ppm, int ppm_color, int duplex, cups_array_t *docformats); |
| #if !CUPS_LITE |
| static ipp_t *load_ppd_attributes(const char *ppdfile, cups_array_t *docformats); |
| #endif /* !CUPS_LITE */ |
| #if HAVE_LIBPAM |
| static int pam_func(int, const struct pam_message **, struct pam_response **, void *); |
| #endif /* HAVE_LIBPAM */ |
| static int parse_options(ippeve_client_t *client, cups_option_t **options); |
| static void process_attr_message(ippeve_job_t *job, char *message); |
| static void *process_client(ippeve_client_t *client); |
| static int process_http(ippeve_client_t *client); |
| static int process_ipp(ippeve_client_t *client); |
| static void *process_job(ippeve_job_t *job); |
| static void process_state_message(ippeve_job_t *job, char *message); |
| static int register_printer(ippeve_printer_t *printer, const char *subtypes); |
| static int respond_http(ippeve_client_t *client, http_status_t code, const char *content_coding, const char *type, size_t length); |
| static void respond_ipp(ippeve_client_t *client, ipp_status_t status, const char *message, ...) _CUPS_FORMAT(3, 4); |
| static void respond_unsupported(ippeve_client_t *client, ipp_attribute_t *attr); |
| static void run_printer(ippeve_printer_t *printer); |
| static int show_media(ippeve_client_t *client); |
| static int show_status(ippeve_client_t *client); |
| static int show_supplies(ippeve_client_t *client); |
| static char *time_string(time_t tv, char *buffer, size_t bufsize); |
| static void usage(int status) _CUPS_NORETURN; |
| static int valid_doc_attributes(ippeve_client_t *client); |
| static int valid_job_attributes(ippeve_client_t *client); |
| |
| |
| /* |
| * Globals... |
| */ |
| |
| #ifdef HAVE_DNSSD |
| static DNSServiceRef DNSSDMaster = NULL; |
| #elif defined(HAVE_AVAHI) |
| static AvahiThreadedPoll *DNSSDMaster = NULL; |
| static AvahiClient *DNSSDClient = NULL; |
| #endif /* HAVE_DNSSD */ |
| |
| static int KeepFiles = 0, /* Keep spooled job files? */ |
| MaxVersion = 20,/* Maximum IPP version (20 = 2.0, 11 = 1.1, etc.) */ |
| Verbosity = 0; /* Verbosity level */ |
| static const char *PAMService = NULL; |
| /* PAM service */ |
| |
| |
| /* |
| * 'main()' - Main entry to the sample server. |
| */ |
| |
| int /* O - Exit status */ |
| main(int argc, /* I - Number of command-line args */ |
| char *argv[]) /* I - Command-line arguments */ |
| { |
| int i; /* Looping var */ |
| const char *opt, /* Current option character */ |
| *attrfile = NULL, /* ippserver attributes file */ |
| *command = NULL, /* Command to run with job files */ |
| *device_uri = NULL, /* Device URI */ |
| *output_format = NULL, /* Output format */ |
| *icon = NULL, /* Icon file */ |
| #ifdef HAVE_SSL |
| *keypath = NULL, /* Keychain path */ |
| #endif /* HAVE_SSL */ |
| *location = "", /* Location of printer */ |
| *make = "Example", /* Manufacturer */ |
| *model = "Printer", /* Model */ |
| *name = NULL, /* Printer name */ |
| #if !CUPS_LITE |
| *ppdfile = NULL, /* PPD file */ |
| #endif /* !CUPS_LITE */ |
| *subtypes = "_print"; /* DNS-SD service subtype */ |
| int legacy = 0, /* Legacy mode? */ |
| duplex = 0, /* Duplex mode */ |
| ppm = 10, /* Pages per minute for mono */ |
| ppm_color = 0, /* Pages per minute for color */ |
| web_forms = 1; /* Enable web site forms? */ |
| ipp_t *attrs = NULL; /* Printer attributes */ |
| char directory[1024] = ""; /* Spool directory */ |
| cups_array_t *docformats = NULL; /* Supported formats */ |
| const char *servername = NULL; /* Server host name */ |
| int serverport = 0; /* Server port number (0 = auto) */ |
| ippeve_printer_t *printer; /* Printer object */ |
| |
| |
| /* |
| * Parse command-line arguments... |
| */ |
| |
| for (i = 1; i < argc; i ++) |
| { |
| if (!strcmp(argv[i], "--help")) |
| { |
| usage(0); |
| } |
| else if (!strcmp(argv[i], "--no-web-forms")) |
| { |
| web_forms = 0; |
| } |
| else if (!strcmp(argv[i], "--pam-service")) |
| { |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| PAMService = argv[i]; |
| } |
| else if (!strcmp(argv[i], "--version")) |
| { |
| puts(CUPS_SVERSION); |
| return (0); |
| } |
| else if (!strncmp(argv[i], "--", 2)) |
| { |
| _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."), argv[0], argv[i]); |
| usage(1); |
| } |
| else if (argv[i][0] == '-') |
| { |
| for (opt = argv[i] + 1; *opt; opt ++) |
| { |
| switch (*opt) |
| { |
| case '2' : /* -2 (enable 2-sided printing) */ |
| duplex = 1; |
| legacy = 1; |
| break; |
| |
| case 'A' : /* -A (enable authentication) */ |
| if (!PAMService) |
| PAMService = "cups"; |
| break; |
| |
| case 'D' : /* -D device-uri */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| device_uri = argv[i]; |
| break; |
| |
| case 'F' : /* -F output/format */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| output_format = argv[i]; |
| break; |
| |
| #ifdef HAVE_SSL |
| case 'K' : /* -K keypath */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| keypath = argv[i]; |
| break; |
| #endif /* HAVE_SSL */ |
| |
| case 'M' : /* -M manufacturer */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| make = argv[i]; |
| legacy = 1; |
| break; |
| |
| #if !CUPS_LITE |
| case 'P' : /* -P filename.ppd */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| ppdfile = argv[i]; |
| break; |
| #endif /* !CUPS_LITE */ |
| |
| case 'V' : /* -V max-version */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| if (!strcmp(argv[i], "2.0")) |
| MaxVersion = 20; |
| else if (!strcmp(argv[i], "1.1")) |
| MaxVersion = 11; |
| else |
| usage(1); |
| break; |
| |
| case 'a' : /* -a attributes-file */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| attrfile = argv[i]; |
| break; |
| |
| case 'c' : /* -c command */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| command = argv[i]; |
| break; |
| |
| case 'd' : /* -d spool-directory */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| strlcpy(directory, argv[i], sizeof(directory)); |
| break; |
| |
| case 'f' : /* -f type/subtype[,...] */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| docformats = _cupsArrayNewStrings(argv[i], ','); |
| legacy = 1; |
| break; |
| |
| case 'i' : /* -i icon.png */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| icon = argv[i]; |
| break; |
| |
| case 'k' : /* -k (keep files) */ |
| KeepFiles = 1; |
| break; |
| |
| case 'l' : /* -l location */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| location = argv[i]; |
| break; |
| |
| case 'm' : /* -m model */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| model = argv[i]; |
| legacy = 1; |
| break; |
| |
| case 'n' : /* -n hostname */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| servername = argv[i]; |
| break; |
| |
| case 'p' : /* -p port */ |
| i ++; |
| if (i >= argc || !isdigit(argv[i][0] & 255)) |
| usage(1); |
| |
| serverport = atoi(argv[i]); |
| break; |
| |
| case 'r' : /* -r subtype */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| subtypes = argv[i]; |
| break; |
| |
| case 's' : /* -s speed[,color-speed] */ |
| i ++; |
| if (i >= argc) |
| usage(1); |
| |
| if (sscanf(argv[i], "%d,%d", &ppm, &ppm_color) < 1) |
| usage(1); |
| |
| legacy = 1; |
| break; |
| |
| case 'v' : /* -v (be verbose) */ |
| Verbosity ++; |
| break; |
| |
| default : /* Unknown */ |
| _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."), argv[0], *opt); |
| usage(1); |
| } |
| } |
| } |
| else if (!name) |
| { |
| name = argv[i]; |
| } |
| else |
| { |
| _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."), argv[0], argv[i]); |
| usage(1); |
| } |
| } |
| |
| if (!name) |
| usage(1); |
| |
| #if CUPS_LITE |
| if (attrfile != NULL && legacy) |
| usage(1); |
| #else |
| if (((ppdfile != NULL) + (attrfile != NULL) + legacy) > 1) |
| usage(1); |
| #endif /* CUPS_LITE */ |
| |
| /* |
| * Apply defaults as needed... |
| */ |
| |
| if (!serverport) |
| { |
| #ifdef _WIN32 |
| /* |
| * Windows is almost always used as a single user system, so use a default |
| * port number of 8631. |
| */ |
| |
| serverport = 8631; |
| |
| #else |
| /* |
| * Use 8000 + UID mod 1000 for the default port number... |
| */ |
| |
| serverport = 8000 + ((int)getuid() % 1000); |
| #endif /* _WIN32 */ |
| |
| _cupsLangPrintf(stderr, _("Listening on port %d."), serverport); |
| } |
| |
| if (!directory[0]) |
| { |
| const char *tmpdir; /* Temporary directory */ |
| |
| #ifdef _WIN32 |
| if ((tmpdir = getenv("TEMP")) == NULL) |
| tmpdir = "C:/TEMP"; |
| #elif defined(__APPLE__) && TARGET_OS_OSX |
| if ((tmpdir = getenv("TMPDIR")) == NULL) |
| tmpdir = "/private/tmp"; |
| #else |
| if ((tmpdir = getenv("TMPDIR")) == NULL) |
| tmpdir = "/tmp"; |
| #endif /* _WIN32 */ |
| |
| snprintf(directory, sizeof(directory), "%s/ippeveprinter.%d", tmpdir, (int)getpid()); |
| |
| if (mkdir(directory, 0755) && errno != EEXIST) |
| { |
| _cupsLangPrintf(stderr, _("Unable to create spool directory \"%s\": %s"), directory, strerror(errno)); |
| usage(1); |
| } |
| |
| if (Verbosity) |
| _cupsLangPrintf(stderr, _("Using spool directory \"%s\"."), directory); |
| } |
| |
| /* |
| * Initialize DNS-SD... |
| */ |
| |
| dnssd_init(); |
| |
| /* |
| * Create the printer... |
| */ |
| |
| if (!docformats) |
| docformats = _cupsArrayNewStrings(ppm_color > 0 ? "image/jpeg,image/pwg-raster,image/urf": "image/pwg-raster,image/urf", ','); |
| |
| if (attrfile) |
| attrs = load_ippserver_attributes(servername, serverport, attrfile, docformats); |
| #if !CUPS_LITE |
| else if (ppdfile) |
| { |
| attrs = load_ppd_attributes(ppdfile, docformats); |
| |
| if (!command) |
| command = "ippeveps"; |
| |
| if (!output_format) |
| output_format = "application/postscript"; |
| } |
| #endif /* !CUPS_LITE */ |
| else |
| attrs = load_legacy_attributes(make, model, ppm, ppm_color, duplex, docformats); |
| |
| if ((printer = create_printer(servername, serverport, name, location, icon, docformats, subtypes, directory, command, device_uri, output_format, attrs)) == NULL) |
| return (1); |
| |
| printer->web_forms = web_forms; |
| |
| #if !CUPS_LITE |
| if (ppdfile) |
| printer->ppdfile = strdup(ppdfile); |
| #endif /* !CUPS_LITE */ |
| |
| #ifdef HAVE_SSL |
| cupsSetServerCredentials(keypath, printer->hostname, 1); |
| #endif /* HAVE_SSL */ |
| |
| /* |
| * Run the print service... |
| */ |
| |
| run_printer(printer); |
| |
| /* |
| * Destroy the printer and exit... |
| */ |
| |
| delete_printer(printer); |
| |
| return (0); |
| } |
| |
| |
| /* |
| * 'authenticate_request()' - Try to authenticate the request. |
| */ |
| |
| static http_status_t /* O - HTTP_STATUS_CONTINUE to keep going, otherwise status to return */ |
| authenticate_request( |
| ippeve_client_t *client) /* I - Client */ |
| { |
| #if HAVE_LIBPAM |
| /* |
| * If PAM isn't enabled, return 'continue' now... |
| */ |
| |
| const char *authorization; /* Pointer into Authorization string */ |
| int userlen; /* Username:password length */ |
| pam_handle_t *pamh; /* PAM authentication handle */ |
| int pamerr; /* PAM error code */ |
| struct pam_conv pamdata; /* PAM conversation data */ |
| ippeve_authdata_t data; /* Authentication data */ |
| |
| |
| if (!PAMService) |
| return (HTTP_STATUS_CONTINUE); |
| |
| /* |
| * Try authenticating using PAM... |
| */ |
| |
| authorization = httpGetField(client->http, HTTP_FIELD_AUTHORIZATION); |
| |
| if (!*authorization) |
| return (HTTP_STATUS_UNAUTHORIZED); |
| |
| if (strncmp(authorization, "Basic ", 6)) |
| { |
| fputs("Unsupported scheme in Authorization header.\n", stderr); |
| return (HTTP_STATUS_BAD_REQUEST); |
| } |
| |
| authorization += 5; |
| while (isspace(*authorization & 255)) |
| authorization ++; |
| |
| userlen = sizeof(data.username); |
| httpDecode64_2(data.username, &userlen, authorization); |
| |
| if ((data.password = strchr(data.username, ':')) == NULL) |
| { |
| fputs("No password in Authorization header.\n", stderr); |
| return (HTTP_STATUS_BAD_REQUEST); |
| } |
| |
| *(data.password)++ = '\0'; |
| |
| if (!data.username[0]) |
| { |
| fputs("No username in Authorization header.\n", stderr); |
| return (HTTP_STATUS_BAD_REQUEST); |
| } |
| |
| pamdata.conv = pam_func; |
| pamdata.appdata_ptr = &data; |
| |
| if ((pamerr = pam_start(PAMService, data.username, &pamdata, &pamh)) != PAM_SUCCESS) |
| { |
| fprintf(stderr, "pam_start() returned %d (%s)\n", pamerr, pam_strerror(pamh, pamerr)); |
| return (HTTP_STATUS_SERVER_ERROR); |
| } |
| |
| if ((pamerr = pam_authenticate(pamh, PAM_SILENT)) != PAM_SUCCESS) |
| { |
| fprintf(stderr, "pam_authenticate() returned %d (%s)\n", pamerr, pam_strerror(pamh, pamerr)); |
| pam_end(pamh, 0); |
| return (HTTP_STATUS_UNAUTHORIZED); |
| } |
| |
| if ((pamerr = pam_acct_mgmt(pamh, PAM_SILENT)) != PAM_SUCCESS) |
| { |
| fprintf(stderr, "pam_acct_mgmt() returned %d (%s)\n", pamerr, pam_strerror(pamh, pamerr)); |
| pam_end(pamh, 0); |
| return (HTTP_STATUS_SERVER_ERROR); |
| } |
| |
| strlcpy(client->username, data.username, sizeof(client->username)); |
| |
| pam_end(pamh, PAM_SUCCESS); |
| |
| return (HTTP_STATUS_CONTINUE); |
| |
| #else |
| /* |
| * No authentication support built-in, return 'continue'... |
| */ |
| |
| return (HTTP_STATUS_CONTINUE); |
| #endif /* HAVE_LIBPAM */ |
| } |
| |
| |
| /* |
| * 'clean_jobs()' - Clean out old (completed) jobs. |
| */ |
| |
| static void |
| clean_jobs(ippeve_printer_t *printer) /* I - Printer */ |
| { |
| ippeve_job_t *job; /* Current job */ |
| time_t cleantime; /* Clean time */ |
| |
| |
| if (cupsArrayCount(printer->jobs) == 0) |
| return; |
| |
| cleantime = time(NULL) - 60; |
| |
| _cupsRWLockWrite(&(printer->rwlock)); |
| for (job = (ippeve_job_t *)cupsArrayFirst(printer->jobs); |
| job; |
| job = (ippeve_job_t *)cupsArrayNext(printer->jobs)) |
| if (job->completed && job->completed < cleantime) |
| { |
| cupsArrayRemove(printer->jobs, job); |
| delete_job(job); |
| } |
| else |
| break; |
| _cupsRWUnlock(&(printer->rwlock)); |
| } |
| |
| |
| /* |
| * 'compare_jobs()' - Compare two jobs. |
| */ |
| |
| static int /* O - Result of comparison */ |
| compare_jobs(ippeve_job_t *a, /* I - First job */ |
| ippeve_job_t *b) /* I - Second job */ |
| { |
| return (b->id - a->id); |
| } |
| |
| |
| /* |
| * 'copy_attributes()' - Copy attributes from one request to another. |
| */ |
| |
| static void |
| copy_attributes(ipp_t *to, /* I - Destination request */ |
| ipp_t *from, /* I - Source request */ |
| cups_array_t *ra, /* I - Requested attributes */ |
| ipp_tag_t group_tag, /* I - Group to copy */ |
| int quickcopy) /* I - Do a quick copy? */ |
| { |
| ippeve_filter_t filter; /* Filter data */ |
| |
| |
| filter.ra = ra; |
| filter.group_tag = group_tag; |
| |
| ippCopyAttributes(to, from, quickcopy, (ipp_copycb_t)filter_cb, &filter); |
| } |
| |
| |
| /* |
| * 'copy_job_attrs()' - Copy job attributes to the response. |
| */ |
| |
| static void |
| copy_job_attributes( |
| ippeve_client_t *client, /* I - Client */ |
| ippeve_job_t *job, /* I - Job */ |
| cups_array_t *ra) /* I - requested-attributes */ |
| { |
| copy_attributes(client->response, job->attrs, ra, IPP_TAG_JOB, 0); |
| |
| if (!ra || cupsArrayFind(ra, "date-time-at-completed")) |
| { |
| if (job->completed) |
| ippAddDate(client->response, IPP_TAG_JOB, "date-time-at-completed", ippTimeToDate(job->completed)); |
| else |
| ippAddOutOfBand(client->response, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-completed"); |
| } |
| |
| if (!ra || cupsArrayFind(ra, "date-time-at-processing")) |
| { |
| if (job->processing) |
| ippAddDate(client->response, IPP_TAG_JOB, "date-time-at-processing", ippTimeToDate(job->processing)); |
| else |
| ippAddOutOfBand(client->response, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-processing"); |
| } |
| |
| if (!ra || cupsArrayFind(ra, "job-impressions")) |
| ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-impressions", job->impressions); |
| |
| if (!ra || cupsArrayFind(ra, "job-impressions-completed")) |
| ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-impressions-completed", job->impcompleted); |
| |
| if (!ra || cupsArrayFind(ra, "job-printer-up-time")) |
| ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-printer-up-time", (int)(time(NULL) - client->printer->start_time)); |
| |
| if (!ra || cupsArrayFind(ra, "job-state")) |
| ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state", (int)job->state); |
| |
| if (!ra || cupsArrayFind(ra, "job-state-message")) |
| { |
| if (job->message) |
| { |
| ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_TEXT, "job-state-message", NULL, job->message); |
| } |
| else |
| { |
| switch (job->state) |
| { |
| case IPP_JSTATE_PENDING : |
| ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job pending."); |
| break; |
| |
| case IPP_JSTATE_HELD : |
| if (job->fd >= 0) |
| ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job incoming."); |
| else if (ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_ZERO)) |
| ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job held."); |
| else |
| ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job created."); |
| break; |
| |
| case IPP_JSTATE_PROCESSING : |
| if (job->cancel) |
| ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job canceling."); |
| else |
| ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job printing."); |
| break; |
| |
| case IPP_JSTATE_STOPPED : |
| ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job stopped."); |
| break; |
| |
| case IPP_JSTATE_CANCELED : |
| ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job canceled."); |
| break; |
| |
| case IPP_JSTATE_ABORTED : |
| ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job aborted."); |
| break; |
| |
| case IPP_JSTATE_COMPLETED : |
| ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job completed."); |
| break; |
| } |
| } |
| } |
| |
| if (!ra || cupsArrayFind(ra, "job-state-reasons")) |
| { |
| switch (job->state) |
| { |
| case IPP_JSTATE_PENDING : |
| ippAddString(client->response, IPP_TAG_JOB, |
| IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", |
| NULL, "none"); |
| break; |
| |
| case IPP_JSTATE_HELD : |
| if (job->fd >= 0) |
| ippAddString(client->response, IPP_TAG_JOB, |
| IPP_CONST_TAG(IPP_TAG_KEYWORD), |
| "job-state-reasons", NULL, "job-incoming"); |
| else if (ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_ZERO)) |
| ippAddString(client->response, IPP_TAG_JOB, |
| IPP_CONST_TAG(IPP_TAG_KEYWORD), |
| "job-state-reasons", NULL, "job-hold-until-specified"); |
| else |
| ippAddString(client->response, IPP_TAG_JOB, |
| IPP_CONST_TAG(IPP_TAG_KEYWORD), |
| "job-state-reasons", NULL, "job-data-insufficient"); |
| break; |
| |
| case IPP_JSTATE_PROCESSING : |
| if (job->cancel) |
| ippAddString(client->response, IPP_TAG_JOB, |
| IPP_CONST_TAG(IPP_TAG_KEYWORD), |
| "job-state-reasons", NULL, "processing-to-stop-point"); |
| else |
| ippAddString(client->response, IPP_TAG_JOB, |
| IPP_CONST_TAG(IPP_TAG_KEYWORD), |
| "job-state-reasons", NULL, "job-printing"); |
| break; |
| |
| case IPP_JSTATE_STOPPED : |
| ippAddString(client->response, IPP_TAG_JOB, |
| IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", |
| NULL, "job-stopped"); |
| break; |
| |
| case IPP_JSTATE_CANCELED : |
| ippAddString(client->response, IPP_TAG_JOB, |
| IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", |
| NULL, "job-canceled-by-user"); |
| break; |
| |
| case IPP_JSTATE_ABORTED : |
| ippAddString(client->response, IPP_TAG_JOB, |
| IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", |
| NULL, "aborted-by-system"); |
| break; |
| |
| case IPP_JSTATE_COMPLETED : |
| ippAddString(client->response, IPP_TAG_JOB, |
| IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", |
| NULL, "job-completed-successfully"); |
| break; |
| } |
| } |
| |
| if (!ra || cupsArrayFind(ra, "time-at-completed")) |
| ippAddInteger(client->response, IPP_TAG_JOB, |
| job->completed ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE, |
| "time-at-completed", (int)(job->completed - client->printer->start_time)); |
| |
| if (!ra || cupsArrayFind(ra, "time-at-processing")) |
| ippAddInteger(client->response, IPP_TAG_JOB, |
| job->processing ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE, |
| "time-at-processing", (int)(job->processing - client->printer->start_time)); |
| } |
| |
| |
| /* |
| * 'create_client()' - Accept a new network connection and create a client |
| * object. |
| */ |
| |
| static ippeve_client_t * /* O - Client */ |
| create_client(ippeve_printer_t *printer, /* I - Printer */ |
| int sock) /* I - Listen socket */ |
| { |
| ippeve_client_t *client; /* Client */ |
| |
| |
| if ((client = calloc(1, sizeof(ippeve_client_t))) == NULL) |
| { |
| perror("Unable to allocate memory for client"); |
| return (NULL); |
| } |
| |
| client->printer = printer; |
| |
| /* |
| * Accept the client and get the remote address... |
| */ |
| |
| if ((client->http = httpAcceptConnection(sock, 1)) == NULL) |
| { |
| perror("Unable to accept client connection"); |
| |
| free(client); |
| |
| return (NULL); |
| } |
| |
| httpGetHostname(client->http, client->hostname, sizeof(client->hostname)); |
| |
| if (Verbosity) |
| fprintf(stderr, "Accepted connection from %s\n", client->hostname); |
| |
| return (client); |
| } |
| |
| |
| /* |
| * 'create_job()' - Create a new job object from a Print-Job or Create-Job |
| * request. |
| */ |
| |
| static ippeve_job_t * /* O - Job */ |
| create_job(ippeve_client_t *client) /* I - Client */ |
| { |
| ippeve_job_t *job; /* Job */ |
| ipp_attribute_t *attr; /* Job attribute */ |
| char uri[1024], /* job-uri value */ |
| uuid[64]; /* job-uuid value */ |
| |
| |
| _cupsRWLockWrite(&(client->printer->rwlock)); |
| if (client->printer->active_job && |
| client->printer->active_job->state < IPP_JSTATE_CANCELED) |
| { |
| /* |
| * Only accept a single job at a time... |
| */ |
| |
| _cupsRWUnlock(&(client->printer->rwlock)); |
| return (NULL); |
| } |
| |
| /* |
| * Allocate and initialize the job object... |
| */ |
| |
| if ((job = calloc(1, sizeof(ippeve_job_t))) == NULL) |
| { |
| perror("Unable to allocate memory for job"); |
| return (NULL); |
| } |
| |
| job->printer = client->printer; |
| job->attrs = ippNew(); |
| job->state = IPP_JSTATE_HELD; |
| job->fd = -1; |
| |
| /* |
| * Copy all of the job attributes... |
| */ |
| |
| copy_attributes(job->attrs, client->request, NULL, IPP_TAG_JOB, 0); |
| |
| /* |
| * Get the requesting-user-name, document format, and priority... |
| */ |
| |
| if ((attr = ippFindAttribute(client->request, "requesting-user-name", IPP_TAG_NAME)) != NULL) |
| job->username = ippGetString(attr, 0, NULL); |
| else |
| job->username = "anonymous"; |
| |
| ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-originating-user-name", NULL, job->username); |
| |
| if (ippGetOperation(client->request) != IPP_OP_CREATE_JOB) |
| { |
| if ((attr = ippFindAttribute(job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL) |
| job->format = ippGetString(attr, 0, NULL); |
| else if ((attr = ippFindAttribute(job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL) |
| job->format = ippGetString(attr, 0, NULL); |
| else |
| job->format = "application/octet-stream"; |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "job-impressions", IPP_TAG_INTEGER)) != NULL) |
| job->impressions = ippGetInteger(attr, 0); |
| |
| if ((attr = ippFindAttribute(client->request, "job-name", IPP_TAG_NAME)) != NULL) |
| job->name = ippGetString(attr, 0, NULL); |
| |
| /* |
| * Add job description attributes and add to the jobs array... |
| */ |
| |
| job->id = client->printer->next_job_id ++; |
| |
| snprintf(uri, sizeof(uri), "%s/%d", client->printer->uri, job->id); |
| httpAssembleUUID(client->printer->hostname, client->printer->port, client->printer->name, job->id, uuid, sizeof(uuid)); |
| |
| ippAddDate(job->attrs, IPP_TAG_JOB, "date-time-at-creation", ippTimeToDate(time(&job->created))); |
| ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id); |
| ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, uri); |
| ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uuid", NULL, uuid); |
| if ((attr = ippFindAttribute(client->request, "printer-uri", IPP_TAG_URI)) != NULL) |
| ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, ippGetString(attr, 0, NULL)); |
| else |
| ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, client->printer->uri); |
| ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation", (int)(job->created - client->printer->start_time)); |
| |
| cupsArrayAdd(client->printer->jobs, job); |
| client->printer->active_job = job; |
| |
| _cupsRWUnlock(&(client->printer->rwlock)); |
| |
| return (job); |
| } |
| |
| |
| /* |
| * 'create_job_file()' - Create a file for the document in a job. |
| */ |
| |
| static int /* O - File descriptor or -1 on error */ |
| create_job_file( |
| ippeve_job_t *job, /* I - Job */ |
| char *fname, /* I - Filename buffer */ |
| size_t fnamesize, /* I - Size of filename buffer */ |
| const char *directory, /* I - Directory to store in */ |
| const char *ext) /* I - Extension (`NULL` for default) */ |
| { |
| char name[256], /* "Safe" filename */ |
| *nameptr; /* Pointer into filename */ |
| const char *job_name; /* job-name value */ |
| |
| |
| /* |
| * Make a name from the job-name attribute... |
| */ |
| |
| if ((job_name = ippGetString(ippFindAttribute(job->attrs, "job-name", IPP_TAG_NAME), 0, NULL)) == NULL) |
| job_name = "untitled"; |
| |
| for (nameptr = name; *job_name && nameptr < (name + sizeof(name) - 1); job_name ++) |
| { |
| if (isalnum(*job_name & 255) || *job_name == '-') |
| { |
| *nameptr++ = (char)tolower(*job_name & 255); |
| } |
| else |
| { |
| *nameptr++ = '_'; |
| |
| while (job_name[1] && !isalnum(job_name[1] & 255) && job_name[1] != '-') |
| job_name ++; |
| } |
| } |
| |
| *nameptr = '\0'; |
| |
| /* |
| * Figure out the extension... |
| */ |
| |
| if (!ext) |
| { |
| if (!strcasecmp(job->format, "image/jpeg")) |
| ext = "jpg"; |
| else if (!strcasecmp(job->format, "image/png")) |
| ext = "png"; |
| else if (!strcasecmp(job->format, "image/pwg-raster")) |
| ext = "pwg"; |
| else if (!strcasecmp(job->format, "image/urf")) |
| ext = "urf"; |
| else if (!strcasecmp(job->format, "application/pdf")) |
| ext = "pdf"; |
| else if (!strcasecmp(job->format, "application/postscript")) |
| ext = "ps"; |
| else if (!strcasecmp(job->format, "application/vnd.hp-pcl")) |
| ext = "pcl"; |
| else |
| ext = "dat"; |
| } |
| |
| /* |
| * Create a filename with the job-id, job-name, and document-format (extension)... |
| */ |
| |
| snprintf(fname, fnamesize, "%s/%d-%s.%s", directory, job->id, name, ext); |
| |
| return (open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0666)); |
| } |
| |
| |
| /* |
| * 'create_listener()' - Create a listener socket. |
| */ |
| |
| static int /* O - Listener socket or -1 on error */ |
| create_listener(const char *name, /* I - Host name (`NULL` for any address) */ |
| int port, /* I - Port number */ |
| int family) /* I - Address family */ |
| { |
| int sock; /* Listener socket */ |
| http_addrlist_t *addrlist; /* Listen address */ |
| char service[255]; /* Service port */ |
| |
| |
| snprintf(service, sizeof(service), "%d", port); |
| if ((addrlist = httpAddrGetList(name, family, service)) == NULL) |
| return (-1); |
| |
| sock = httpAddrListen(&(addrlist->addr), port); |
| |
| httpAddrFreeList(addrlist); |
| |
| return (sock); |
| } |
| |
| |
| /* |
| * 'create_media_col()' - Create a media-col value. |
| */ |
| |
| static ipp_t * /* O - media-col collection */ |
| create_media_col(const char *media, /* I - Media name */ |
| const char *source, /* I - Media source, if any */ |
| const char *type, /* I - Media type, if any */ |
| int width, /* I - x-dimension in 2540ths */ |
| int length, /* I - y-dimension in 2540ths */ |
| int bottom, /* I - Bottom margin in 2540ths */ |
| int left, /* I - Left margin in 2540ths */ |
| int right, /* I - Right margin in 2540ths */ |
| int top) /* I - Top margin in 2540ths */ |
| { |
| ipp_t *media_col = ippNew(), /* media-col value */ |
| *media_size = create_media_size(width, length); |
| /* media-size value */ |
| char media_key[256]; /* media-key value */ |
| const char *media_key_suffix = ""; /* media-key suffix */ |
| |
| |
| if (bottom == 0 && left == 0 && right == 0 && top == 0) |
| media_key_suffix = "_borderless"; |
| |
| if (type && source) |
| snprintf(media_key, sizeof(media_key), "%s_%s_%s%s", media, source, type, media_key_suffix); |
| else if (type) |
| snprintf(media_key, sizeof(media_key), "%s__%s%s", media, type, media_key_suffix); |
| else if (source) |
| snprintf(media_key, sizeof(media_key), "%s_%s%s", media, source, media_key_suffix); |
| else |
| snprintf(media_key, sizeof(media_key), "%s%s", media, media_key_suffix); |
| |
| ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-key", NULL, media_key); |
| ippAddCollection(media_col, IPP_TAG_PRINTER, "media-size", media_size); |
| ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-size-name", NULL, media); |
| if (bottom >= 0) |
| ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin", bottom); |
| if (left >= 0) |
| ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin", left); |
| if (right >= 0) |
| ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin", right); |
| if (top >= 0) |
| ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin", top); |
| if (source) |
| ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source", NULL, source); |
| if (type) |
| ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type", NULL, type); |
| |
| ippDelete(media_size); |
| |
| return (media_col); |
| } |
| |
| |
| /* |
| * 'create_media_size()' - Create a media-size value. |
| */ |
| |
| static ipp_t * /* O - media-col collection */ |
| create_media_size(int width, /* I - x-dimension in 2540ths */ |
| int length) /* I - y-dimension in 2540ths */ |
| { |
| ipp_t *media_size = ippNew(); /* media-size value */ |
| |
| |
| ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "x-dimension", width); |
| ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "y-dimension", length); |
| |
| return (media_size); |
| } |
| |
| |
| /* |
| * 'create_printer()' - Create, register, and listen for connections to a |
| * printer object. |
| */ |
| |
| static ippeve_printer_t * /* O - Printer */ |
| create_printer( |
| const char *servername, /* I - Server hostname (NULL for default) */ |
| int serverport, /* I - Server port */ |
| const char *name, /* I - printer-name */ |
| const char *location, /* I - printer-location */ |
| const char *icon, /* I - printer-icons */ |
| cups_array_t *docformats, /* I - document-format-supported */ |
| const char *subtypes, /* I - Bonjour service subtype(s) */ |
| const char *directory, /* I - Spool directory */ |
| const char *command, /* I - Command to run on job files, if any */ |
| const char *device_uri, /* I - Output device, if any */ |
| const char *output_format, /* I - Output format, if any */ |
| ipp_t *attrs) /* I - Capability attributes */ |
| { |
| ippeve_printer_t *printer; /* Printer */ |
| int i; /* Looping var */ |
| #ifndef _WIN32 |
| char path[1024]; /* Full path to command */ |
| #endif /* !_WIN32 */ |
| char uri[1024], /* Printer URI */ |
| #ifdef HAVE_SSL |
| securi[1024], /* Secure printer URI */ |
| *uris[2], /* All URIs */ |
| #endif /* HAVE_SSL */ |
| icons[1024], /* printer-icons URI */ |
| adminurl[1024], /* printer-more-info URI */ |
| supplyurl[1024],/* printer-supply-info-uri URI */ |
| uuid[128]; /* printer-uuid */ |
| int k_supported; /* Maximum file size supported */ |
| int num_formats; /* Number of supported document formats */ |
| const char *formats[100], /* Supported document formats */ |
| *format; /* Current format */ |
| int num_sup_attrs; /* Number of supported attributes */ |
| const char *sup_attrs[100];/* Supported attributes */ |
| char xxx_supported[256]; |
| /* Name of -supported attribute */ |
| _cups_globals_t *cg = _cupsGlobals(); |
| /* Global path values */ |
| #ifdef HAVE_STATVFS |
| struct statvfs spoolinfo; /* FS info for spool directory */ |
| double spoolsize; /* FS size */ |
| #elif defined(HAVE_STATFS) |
| struct statfs spoolinfo; /* FS info for spool directory */ |
| double spoolsize; /* FS size */ |
| #endif /* HAVE_STATVFS */ |
| static const char * const versions[] =/* ipp-versions-supported values */ |
| { |
| "1.1", |
| "2.0" |
| }; |
| static const char * const features[] =/* ipp-features-supported values */ |
| { |
| "ipp-everywhere" |
| }; |
| static const int ops[] = /* operations-supported values */ |
| { |
| IPP_OP_PRINT_JOB, |
| IPP_OP_PRINT_URI, |
| IPP_OP_VALIDATE_JOB, |
| IPP_OP_CREATE_JOB, |
| IPP_OP_SEND_DOCUMENT, |
| IPP_OP_SEND_URI, |
| IPP_OP_CANCEL_JOB, |
| IPP_OP_GET_JOB_ATTRIBUTES, |
| IPP_OP_GET_JOBS, |
| IPP_OP_GET_PRINTER_ATTRIBUTES, |
| IPP_OP_CANCEL_MY_JOBS, |
| IPP_OP_CLOSE_JOB, |
| IPP_OP_IDENTIFY_PRINTER |
| }; |
| static const char * const charsets[] =/* charset-supported values */ |
| { |
| "us-ascii", |
| "utf-8" |
| }; |
| static const char * const compressions[] =/* compression-supported values */ |
| { |
| #ifdef HAVE_LIBZ |
| "deflate", |
| "gzip", |
| #endif /* HAVE_LIBZ */ |
| "none" |
| }; |
| static const char * const identify_actions[] = |
| { |
| "display", |
| "sound" |
| }; |
| static const char * const job_creation[] = |
| { /* job-creation-attributes-supported values */ |
| "copies", |
| "document-access", |
| "document-charset", |
| "document-format", |
| "document-message", |
| "document-metadata", |
| "document-name", |
| "document-natural-language", |
| "document-password", |
| "finishings", |
| "finishings-col", |
| "ipp-attribute-fidelity", |
| "job-account-id", |
| "job-account-type", |
| "job-accouunting-sheets", |
| "job-accounting-user-id", |
| "job-authorization-uri", |
| "job-error-action", |
| "job-error-sheet", |
| "job-hold-until", |
| "job-hold-until-time", |
| "job-mandatory-attributes", |
| "job-message-to-operator", |
| "job-name", |
| "job-pages-per-set", |
| "job-password", |
| "job-password-encryption", |
| "job-phone-number", |
| "job-priority", |
| "job-recipient-name", |
| "job-resource-ids", |
| "job-sheet-message", |
| "job-sheets", |
| "job-sheets-col", |
| "media", |
| "media-col", |
| "multiple-document-handling", |
| "number-up", |
| "orientation-requested", |
| "output-bin", |
| "output-device", |
| "overrides", |
| "page-delivery", |
| "page-ranges", |
| "presentation-direction-number-up", |
| "print-color-mode", |
| "print-content-optimize", |
| "print-quality", |
| "print-rendering-intent", |
| "print-scaling", |
| "printer-resolution", |
| "proof-print", |
| "separator-sheets", |
| "sides", |
| "x-image-position", |
| "x-image-shift", |
| "x-side1-image-shift", |
| "x-side2-image-shift", |
| "y-image-position", |
| "y-image-shift", |
| "y-side1-image-shift", |
| "y-side2-image-shift" |
| }; |
| static const char * const media_col_supported[] = |
| { /* media-col-supported values */ |
| "media-bottom-margin", |
| "media-left-margin", |
| "media-right-margin", |
| "media-size", |
| "media-size-name", |
| "media-source", |
| "media-top-margin", |
| "media-type" |
| }; |
| static const char * const multiple_document_handling[] = |
| { /* multiple-document-handling-supported values */ |
| "separate-documents-uncollated-copies", |
| "separate-documents-collated-copies" |
| }; |
| static const char * const reference_uri_schemes_supported[] = |
| { /* reference-uri-schemes-supported */ |
| "file", |
| "ftp", |
| "http" |
| #ifdef HAVE_SSL |
| , "https" |
| #endif /* HAVE_SSL */ |
| }; |
| #ifdef HAVE_SSL |
| static const char * const uri_authentication_supported[] = |
| { /* uri-authentication-supported values */ |
| "none", |
| "none" |
| }; |
| static const char * const uri_authentication_basic[] = |
| { /* uri-authentication-supported values with authentication */ |
| "basic", |
| "basic" |
| }; |
| static const char * const uri_security_supported[] = |
| { /* uri-security-supported values */ |
| "none", |
| "tls" |
| }; |
| #endif /* HAVE_SSL */ |
| static const char * const which_jobs[] = |
| { /* which-jobs-supported values */ |
| "completed", |
| "not-completed", |
| "aborted", |
| "all", |
| "canceled", |
| "pending", |
| "pending-held", |
| "processing", |
| "processing-stopped" |
| }; |
| |
| |
| #ifndef _WIN32 |
| /* |
| * If a command was specified, make sure it exists and is executable... |
| */ |
| |
| if (command) |
| { |
| if (*command == '/' || !strncmp(command, "./", 2)) |
| { |
| if (access(command, X_OK)) |
| { |
| _cupsLangPrintf(stderr, _("Unable to execute command \"%s\": %s"), command, strerror(errno)); |
| return (NULL); |
| } |
| } |
| else |
| { |
| snprintf(path, sizeof(path), "%s/command/%s", cg->cups_serverbin, command); |
| |
| if (access(command, X_OK)) |
| { |
| _cupsLangPrintf(stderr, _("Unable to execute command \"%s\": %s"), command, strerror(errno)); |
| return (NULL); |
| } |
| |
| command = path; |
| } |
| } |
| #endif /* !_WIN32 */ |
| |
| /* |
| * Allocate memory for the printer... |
| */ |
| |
| if ((printer = calloc(1, sizeof(ippeve_printer_t))) == NULL) |
| { |
| _cupsLangPrintError(NULL, _("Unable to allocate memory for printer")); |
| return (NULL); |
| } |
| |
| printer->ipv4 = -1; |
| printer->ipv6 = -1; |
| printer->name = strdup(name); |
| printer->dnssd_name = strdup(name); |
| printer->command = command ? strdup(command) : NULL; |
| printer->device_uri = device_uri ? strdup(device_uri) : NULL; |
| printer->output_format = output_format ? strdup(output_format) : NULL; |
| printer->directory = strdup(directory); |
| printer->icon = icon ? strdup(icon) : NULL; |
| printer->port = serverport; |
| printer->start_time = time(NULL); |
| printer->config_time = printer->start_time; |
| printer->state = IPP_PSTATE_IDLE; |
| printer->state_reasons = IPPEVE_PREASON_NONE; |
| printer->state_time = printer->start_time; |
| printer->jobs = cupsArrayNew((cups_array_func_t)compare_jobs, NULL); |
| printer->next_job_id = 1; |
| |
| if (servername) |
| { |
| printer->hostname = strdup(servername); |
| } |
| else |
| { |
| char temp[1024]; /* Temporary string */ |
| |
| printer->hostname = strdup(httpGetHostname(NULL, temp, sizeof(temp))); |
| } |
| |
| _cupsRWInit(&(printer->rwlock)); |
| |
| /* |
| * Create the listener sockets... |
| */ |
| |
| if ((printer->ipv4 = create_listener(servername, printer->port, AF_INET)) < 0) |
| { |
| perror("Unable to create IPv4 listener"); |
| goto bad_printer; |
| } |
| |
| if ((printer->ipv6 = create_listener(servername, printer->port, AF_INET6)) < 0) |
| { |
| perror("Unable to create IPv6 listener"); |
| goto bad_printer; |
| } |
| |
| /* |
| * Prepare URI values for the printer attributes... |
| */ |
| |
| httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, printer->hostname, printer->port, "/ipp/print"); |
| printer->uri = strdup(uri); |
| printer->urilen = strlen(uri); |
| |
| #ifdef HAVE_SSL |
| httpAssembleURI(HTTP_URI_CODING_ALL, securi, sizeof(securi), "ipps", NULL, printer->hostname, printer->port, "/ipp/print"); |
| #endif /* HAVE_SSL */ |
| |
| httpAssembleURI(HTTP_URI_CODING_ALL, icons, sizeof(icons), WEB_SCHEME, NULL, printer->hostname, printer->port, "/icon.png"); |
| httpAssembleURI(HTTP_URI_CODING_ALL, adminurl, sizeof(adminurl), WEB_SCHEME, NULL, printer->hostname, printer->port, "/"); |
| httpAssembleURI(HTTP_URI_CODING_ALL, supplyurl, sizeof(supplyurl), WEB_SCHEME, NULL, printer->hostname, printer->port, "/supplies"); |
| httpAssembleUUID(printer->hostname, serverport, name, 0, uuid, sizeof(uuid)); |
| |
| if (Verbosity) |
| { |
| fprintf(stderr, "printer-more-info=\"%s\"\n", adminurl); |
| fprintf(stderr, "printer-supply-info-uri=\"%s\"\n", supplyurl); |
| #ifdef HAVE_SSL |
| fprintf(stderr, "printer-uri=\"%s\",\"%s\"\n", uri, securi); |
| #else |
| fprintf(stderr, "printer-uri=\"%s\"\n", uri); |
| #endif /* HAVE_SSL */ |
| } |
| |
| /* |
| * Get the maximum spool size based on the size of the filesystem used for |
| * the spool directory. If the host OS doesn't support the statfs call |
| * or the filesystem is larger than 2TiB, always report INT_MAX. |
| */ |
| |
| #ifdef HAVE_STATVFS |
| if (statvfs(printer->directory, &spoolinfo)) |
| k_supported = INT_MAX; |
| else if ((spoolsize = (double)spoolinfo.f_frsize * |
| spoolinfo.f_blocks / 1024) > INT_MAX) |
| k_supported = INT_MAX; |
| else |
| k_supported = (int)spoolsize; |
| |
| #elif defined(HAVE_STATFS) |
| if (statfs(printer->directory, &spoolinfo)) |
| k_supported = INT_MAX; |
| else if ((spoolsize = (double)spoolinfo.f_bsize * |
| spoolinfo.f_blocks / 1024) > INT_MAX) |
| k_supported = INT_MAX; |
| else |
| k_supported = (int)spoolsize; |
| |
| #else |
| k_supported = INT_MAX; |
| #endif /* HAVE_STATVFS */ |
| |
| /* |
| * Assemble the final list of document formats... |
| */ |
| |
| if (!cupsArrayFind(docformats, (void *)"application/octet-stream")) |
| cupsArrayAdd(docformats, (void *)"application/octet-stream"); |
| |
| for (num_formats = 0, format = (const char *)cupsArrayFirst(docformats); format && num_formats < (int)(sizeof(formats) / sizeof(formats[0])); format = (const char *)cupsArrayNext(docformats)) |
| formats[num_formats ++] = format; |
| |
| /* |
| * Get the list of attributes that can be used when creating a job... |
| */ |
| |
| num_sup_attrs = 0; |
| sup_attrs[num_sup_attrs ++] = "document-access"; |
| sup_attrs[num_sup_attrs ++] = "document-charset"; |
| sup_attrs[num_sup_attrs ++] = "document-format"; |
| sup_attrs[num_sup_attrs ++] = "document-message"; |
| sup_attrs[num_sup_attrs ++] = "document-metadata"; |
| sup_attrs[num_sup_attrs ++] = "document-name"; |
| sup_attrs[num_sup_attrs ++] = "document-natural-language"; |
| sup_attrs[num_sup_attrs ++] = "ipp-attribute-fidelity"; |
| sup_attrs[num_sup_attrs ++] = "job-name"; |
| sup_attrs[num_sup_attrs ++] = "job-priority"; |
| |
| for (i = 0; i < (int)(sizeof(job_creation) / sizeof(job_creation[0])) && num_sup_attrs < (int)(sizeof(sup_attrs) / sizeof(sup_attrs[0])); i ++) |
| { |
| snprintf(xxx_supported, sizeof(xxx_supported), "%s-supported", job_creation[i]); |
| if (ippFindAttribute(attrs, xxx_supported, IPP_TAG_ZERO)) |
| sup_attrs[num_sup_attrs ++] = job_creation[i]; |
| } |
| |
| /* |
| * Fill out the rest of the printer attributes. |
| */ |
| |
| printer->attrs = attrs; |
| |
| /* charset-configured */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_CHARSET), "charset-configured", NULL, "utf-8"); |
| |
| /* charset-supported */ |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_CHARSET), "charset-supported", sizeof(charsets) / sizeof(charsets[0]), NULL, charsets); |
| |
| /* compression-supported */ |
| if (!ippFindAttribute(printer->attrs, "compression-supported", IPP_TAG_ZERO)) |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "compression-supported", (int)(sizeof(compressions) / sizeof(compressions[0])), NULL, compressions); |
| |
| /* document-format-default */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_MIMETYPE), "document-format-default", NULL, "application/octet-stream"); |
| |
| /* document-format-supported */ |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE, "document-format-supported", num_formats, NULL, formats); |
| |
| /* generated-natural-language-supported */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_LANGUAGE), "generated-natural-language-supported", NULL, "en"); |
| |
| /* identify-actions-default */ |
| ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "identify-actions-default", NULL, "sound"); |
| |
| /* identify-actions-supported */ |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "identify-actions-supported", sizeof(identify_actions) / sizeof(identify_actions[0]), NULL, identify_actions); |
| |
| /* ipp-features-supported */ |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-features-supported", sizeof(features) / sizeof(features[0]), NULL, features); |
| |
| /* ipp-versions-supported */ |
| if (MaxVersion == 11) |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-versions-supported", NULL, "1.1"); |
| else |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-versions-supported", (int)(sizeof(versions) / sizeof(versions[0])), NULL, versions); |
| |
| /* job-creation-attributes-supported */ |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-creation-attributes-supported", num_sup_attrs, NULL, sup_attrs); |
| |
| /* job-ids-supported */ |
| ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "job-ids-supported", 1); |
| |
| /* job-k-octets-supported */ |
| ippAddRange(printer->attrs, IPP_TAG_PRINTER, "job-k-octets-supported", 0, k_supported); |
| |
| /* job-priority-default */ |
| ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-priority-default", 50); |
| |
| /* job-priority-supported */ |
| ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-priority-supported", 1); |
| |
| /* job-sheets-default */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-sheets-default", NULL, "none"); |
| |
| /* job-sheets-supported */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-sheets-supported", NULL, "none"); |
| |
| /* media-col-supported */ |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-col-supported", (int)(sizeof(media_col_supported) / sizeof(media_col_supported[0])), NULL, media_col_supported); |
| |
| /* multiple-document-handling-supported */ |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "multiple-document-handling-supported", sizeof(multiple_document_handling) / sizeof(multiple_document_handling[0]), NULL, multiple_document_handling); |
| |
| /* multiple-document-jobs-supported */ |
| ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "multiple-document-jobs-supported", 0); |
| |
| /* multiple-operation-time-out */ |
| ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "multiple-operation-time-out", 60); |
| |
| /* multiple-operation-time-out-action */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "multiple-operation-time-out-action", NULL, "abort-job"); |
| |
| /* natural-language-configured */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_LANGUAGE), "natural-language-configured", NULL, "en"); |
| |
| /* operations-supported */ |
| ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "operations-supported", sizeof(ops) / sizeof(ops[0]), ops); |
| |
| /* pdl-override-supported */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pdl-override-supported", NULL, "attempted"); |
| |
| /* preferred-attributes-supported */ |
| ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "preferred-attributes-supported", 0); |
| |
| /* printer-get-attributes-supported */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "printer-get-attributes-supported", NULL, "document-format"); |
| |
| /* printer-geo-location */ |
| ippAddOutOfBand(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_UNKNOWN, "printer-geo-location"); |
| |
| /* printer-icons */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-icons", NULL, icons); |
| |
| /* printer-is-accepting-jobs */ |
| ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "printer-is-accepting-jobs", 1); |
| |
| /* printer-info */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info", NULL, name); |
| |
| /* printer-location */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-location", NULL, location); |
| |
| /* printer-more-info */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-more-info", NULL, adminurl); |
| |
| /* printer-name */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name", NULL, name); |
| |
| /* printer-organization */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-organization", NULL, ""); |
| |
| /* printer-organizational-unit */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-organizational-unit", NULL, ""); |
| |
| /* printer-supply-info-uri */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-supply-info-uri", NULL, supplyurl); |
| |
| /* printer-uri-supported */ |
| #ifdef HAVE_SSL |
| uris[0] = uri; |
| uris[1] = securi; |
| |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uri-supported", 2, NULL, (const char **)uris); |
| |
| #else |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uri-supported", NULL, uri); |
| #endif /* HAVE_SSL */ |
| |
| /* printer-uuid */ |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uuid", NULL, uuid); |
| |
| /* reference-uri-scheme-supported */ |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_URISCHEME), "reference-uri-schemes-supported", (int)(sizeof(reference_uri_schemes_supported) / sizeof(reference_uri_schemes_supported[0])), NULL, reference_uri_schemes_supported); |
| |
| /* uri-authentication-supported */ |
| #ifdef HAVE_SSL |
| if (PAMService) |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-authentication-supported", 2, NULL, uri_authentication_basic); |
| else |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-authentication-supported", 2, NULL, uri_authentication_supported); |
| #else |
| if (PAMService) |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-authentication-supported", NULL, "basic"); |
| else |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-authentication-supported", NULL, "none"); |
| #endif /* HAVE_SSL */ |
| |
| /* uri-security-supported */ |
| #ifdef HAVE_SSL |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-security-supported", 2, NULL, uri_security_supported); |
| #else |
| ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-security-supported", NULL, "none"); |
| #endif /* HAVE_SSL */ |
| |
| /* which-jobs-supported */ |
| ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "which-jobs-supported", sizeof(which_jobs) / sizeof(which_jobs[0]), NULL, which_jobs); |
| |
| debug_attributes("Printer", printer->attrs, 0); |
| |
| /* |
| * Register the printer with Bonjour... |
| */ |
| |
| if (!register_printer(printer, subtypes)) |
| goto bad_printer; |
| |
| /* |
| * Return it! |
| */ |
| |
| return (printer); |
| |
| |
| /* |
| * If we get here we were unable to create the printer... |
| */ |
| |
| bad_printer: |
| |
| delete_printer(printer); |
| |
| return (NULL); |
| } |
| |
| |
| /* |
| * 'debug_attributes()' - Print attributes in a request or response. |
| */ |
| |
| static void |
| debug_attributes(const char *title, /* I - Title */ |
| ipp_t *ipp, /* I - Request/response */ |
| int type) /* I - 0 = object, 1 = request, 2 = response */ |
| { |
| ipp_tag_t group_tag; /* Current group */ |
| ipp_attribute_t *attr; /* Current attribute */ |
| char buffer[2048]; /* String buffer for value */ |
| int major, minor; /* Version */ |
| |
| |
| if (Verbosity <= 1) |
| return; |
| |
| fprintf(stderr, "%s:\n", title); |
| major = ippGetVersion(ipp, &minor); |
| fprintf(stderr, " version=%d.%d\n", major, minor); |
| if (type == 1) |
| fprintf(stderr, " operation-id=%s(%04x)\n", |
| ippOpString(ippGetOperation(ipp)), ippGetOperation(ipp)); |
| else if (type == 2) |
| fprintf(stderr, " status-code=%s(%04x)\n", |
| ippErrorString(ippGetStatusCode(ipp)), ippGetStatusCode(ipp)); |
| fprintf(stderr, " request-id=%d\n\n", ippGetRequestId(ipp)); |
| |
| for (attr = ippFirstAttribute(ipp), group_tag = IPP_TAG_ZERO; |
| attr; |
| attr = ippNextAttribute(ipp)) |
| { |
| if (ippGetGroupTag(attr) != group_tag) |
| { |
| group_tag = ippGetGroupTag(attr); |
| fprintf(stderr, " %s\n", ippTagString(group_tag)); |
| } |
| |
| if (ippGetName(attr)) |
| { |
| ippAttributeString(attr, buffer, sizeof(buffer)); |
| fprintf(stderr, " %s (%s%s) %s\n", ippGetName(attr), |
| ippGetCount(attr) > 1 ? "1setOf " : "", |
| ippTagString(ippGetValueTag(attr)), buffer); |
| } |
| } |
| } |
| |
| |
| /* |
| * 'delete_client()' - Close the socket and free all memory used by a client |
| * object. |
| */ |
| |
| static void |
| delete_client(ippeve_client_t *client) /* I - Client */ |
| { |
| if (Verbosity) |
| fprintf(stderr, "Closing connection from %s\n", client->hostname); |
| |
| /* |
| * Flush pending writes before closing... |
| */ |
| |
| httpFlushWrite(client->http); |
| |
| /* |
| * Free memory... |
| */ |
| |
| httpClose(client->http); |
| |
| ippDelete(client->request); |
| ippDelete(client->response); |
| |
| free(client); |
| } |
| |
| |
| /* |
| * 'delete_job()' - Remove from the printer and free all memory used by a job |
| * object. |
| */ |
| |
| static void |
| delete_job(ippeve_job_t *job) /* I - Job */ |
| { |
| if (Verbosity) |
| fprintf(stderr, "[Job %d] Removing job from history.\n", job->id); |
| |
| ippDelete(job->attrs); |
| |
| if (job->message) |
| free(job->message); |
| |
| if (job->filename) |
| { |
| if (!KeepFiles) |
| unlink(job->filename); |
| |
| free(job->filename); |
| } |
| |
| free(job); |
| } |
| |
| |
| /* |
| * 'delete_printer()' - Unregister, close listen sockets, and free all memory |
| * used by a printer object. |
| */ |
| |
| static void |
| delete_printer(ippeve_printer_t *printer) /* I - Printer */ |
| { |
| if (printer->ipv4 >= 0) |
| close(printer->ipv4); |
| |
| if (printer->ipv6 >= 0) |
| close(printer->ipv6); |
| |
| #if HAVE_DNSSD |
| if (printer->printer_ref) |
| DNSServiceRefDeallocate(printer->printer_ref); |
| if (printer->ipp_ref) |
| DNSServiceRefDeallocate(printer->ipp_ref); |
| if (printer->ipps_ref) |
| DNSServiceRefDeallocate(printer->ipps_ref); |
| if (printer->http_ref) |
| DNSServiceRefDeallocate(printer->http_ref); |
| #elif defined(HAVE_AVAHI) |
| avahi_threaded_poll_lock(DNSSDMaster); |
| |
| if (printer->printer_ref) |
| avahi_entry_group_free(printer->printer_ref); |
| if (printer->ipp_ref) |
| avahi_entry_group_free(printer->ipp_ref); |
| if (printer->ipps_ref) |
| avahi_entry_group_free(printer->ipps_ref); |
| if (printer->http_ref) |
| avahi_entry_group_free(printer->http_ref); |
| |
| avahi_threaded_poll_unlock(DNSSDMaster); |
| #endif /* HAVE_DNSSD */ |
| |
| if (printer->dnssd_name) |
| free(printer->dnssd_name); |
| if (printer->name) |
| free(printer->name); |
| if (printer->icon) |
| free(printer->icon); |
| if (printer->command) |
| free(printer->command); |
| if (printer->device_uri) |
| free(printer->device_uri); |
| #if !CUPS_LITE |
| if (printer->ppdfile) |
| free(printer->ppdfile); |
| #endif /* !CUPS_LITE */ |
| if (printer->directory) |
| free(printer->directory); |
| if (printer->hostname) |
| free(printer->hostname); |
| if (printer->uri) |
| free(printer->uri); |
| |
| ippDelete(printer->attrs); |
| cupsArrayDelete(printer->jobs); |
| |
| free(printer); |
| } |
| |
| |
| #ifdef HAVE_DNSSD |
| /* |
| * 'dnssd_callback()' - Handle Bonjour registration events. |
| */ |
| |
| static void DNSSD_API |
| dnssd_callback( |
| DNSServiceRef sdRef, /* I - Service reference */ |
| DNSServiceFlags flags, /* I - Status flags */ |
| DNSServiceErrorType errorCode, /* I - Error, if any */ |
| const char *name, /* I - Service name */ |
| const char *regtype, /* I - Service type */ |
| const char *domain, /* I - Domain for service */ |
| ippeve_printer_t *printer) /* I - Printer */ |
| { |
| (void)sdRef; |
| (void)flags; |
| (void)domain; |
| |
| if (errorCode) |
| { |
| fprintf(stderr, "DNSServiceRegister for %s failed with error %d.\n", regtype, (int)errorCode); |
| return; |
| } |
| else if (strcasecmp(name, printer->dnssd_name)) |
| { |
| if (Verbosity) |
| fprintf(stderr, "Now using DNS-SD service name \"%s\".\n", name); |
| |
| /* No lock needed since only the main thread accesses/changes this */ |
| free(printer->dnssd_name); |
| printer->dnssd_name = strdup(name); |
| } |
| } |
| |
| |
| #elif defined(HAVE_AVAHI) |
| /* |
| * 'dnssd_callback()' - Handle Bonjour registration events. |
| */ |
| |
| static void |
| dnssd_callback( |
| AvahiEntryGroup *srv, /* I - Service */ |
| AvahiEntryGroupState state, /* I - Registration state */ |
| void *context) /* I - Printer */ |
| { |
| (void)srv; |
| (void)state; |
| (void)context; |
| } |
| |
| |
| /* |
| * 'dnssd_client_cb()' - Client callback for Avahi. |
| * |
| * Called whenever the client or server state changes... |
| */ |
| |
| static void |
| dnssd_client_cb( |
| AvahiClient *c, /* I - Client */ |
| AvahiClientState state, /* I - Current state */ |
| void *userdata) /* I - User data (unused) */ |
| { |
| (void)userdata; |
| |
| if (!c) |
| return; |
| |
| switch (state) |
| { |
| default : |
| fprintf(stderr, "Ignored Avahi state %d.\n", state); |
| break; |
| |
| case AVAHI_CLIENT_FAILURE: |
| if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) |
| { |
| fputs("Avahi server crashed, exiting.\n", stderr); |
| exit(1); |
| } |
| break; |
| } |
| } |
| #endif /* HAVE_DNSSD */ |
| |
| |
| /* |
| * 'dnssd_init()' - Initialize the DNS-SD service connections... |
| */ |
| |
| static void |
| dnssd_init(void) |
| { |
| #ifdef HAVE_DNSSD |
| if (DNSServiceCreateConnection(&DNSSDMaster) != kDNSServiceErr_NoError) |
| { |
| fputs("Error: Unable to initialize Bonjour.\n", stderr); |
| exit(1); |
| } |
| |
| #elif defined(HAVE_AVAHI) |
| int error; /* Error code, if any */ |
| |
| if ((DNSSDMaster = avahi_threaded_poll_new()) == NULL) |
| { |
| fputs("Error: Unable to initialize Bonjour.\n", stderr); |
| exit(1); |
| } |
| |
| if ((DNSSDClient = avahi_client_new(avahi_threaded_poll_get(DNSSDMaster), AVAHI_CLIENT_NO_FAIL, dnssd_client_cb, NULL, &error)) == NULL) |
| { |
| fputs("Error: Unable to initialize Bonjour.\n", stderr); |
| exit(1); |
| } |
| |
| avahi_threaded_poll_start(DNSSDMaster); |
| #endif /* HAVE_DNSSD */ |
| } |
| |
| |
| /* |
| * 'filter_cb()' - Filter printer attributes based on the requested array. |
| */ |
| |
| static int /* O - 1 to copy, 0 to ignore */ |
| filter_cb(ippeve_filter_t *filter, /* I - Filter parameters */ |
| ipp_t *dst, /* I - Destination (unused) */ |
| ipp_attribute_t *attr) /* I - Source attribute */ |
| { |
| /* |
| * Filter attributes as needed... |
| */ |
| |
| #ifndef _WIN32 /* Avoid MS compiler bug */ |
| (void)dst; |
| #endif /* !_WIN32 */ |
| |
| ipp_tag_t group = ippGetGroupTag(attr); |
| const char *name = ippGetName(attr); |
| |
| if ((filter->group_tag != IPP_TAG_ZERO && group != filter->group_tag && group != IPP_TAG_ZERO) || !name || (!strcmp(name, "media-col-database") && !cupsArrayFind(filter->ra, (void *)name))) |
| return (0); |
| |
| return (!filter->ra || cupsArrayFind(filter->ra, (void *)name) != NULL); |
| } |
| |
| |
| /* |
| * 'find_job()' - Find a job specified in a request. |
| */ |
| |
| static ippeve_job_t * /* O - Job or NULL */ |
| find_job(ippeve_client_t *client) /* I - Client */ |
| { |
| ipp_attribute_t *attr; /* job-id or job-uri attribute */ |
| ippeve_job_t key, /* Job search key */ |
| *job; /* Matching job, if any */ |
| |
| |
| if ((attr = ippFindAttribute(client->request, "job-uri", IPP_TAG_URI)) != NULL) |
| { |
| const char *uri = ippGetString(attr, 0, NULL); |
| |
| if (!strncmp(uri, client->printer->uri, client->printer->urilen) && |
| uri[client->printer->urilen] == '/') |
| key.id = atoi(uri + client->printer->urilen + 1); |
| else |
| return (NULL); |
| } |
| else if ((attr = ippFindAttribute(client->request, "job-id", IPP_TAG_INTEGER)) != NULL) |
| key.id = ippGetInteger(attr, 0); |
| |
| _cupsRWLockRead(&(client->printer->rwlock)); |
| job = (ippeve_job_t *)cupsArrayFind(client->printer->jobs, &key); |
| _cupsRWUnlock(&(client->printer->rwlock)); |
| |
| return (job); |
| } |
| |
| |
| /* |
| * 'finish_document()' - Finish receiving a document file and start processing. |
| */ |
| |
| static void |
| finish_document_data( |
| ippeve_client_t *client, /* I - Client */ |
| ippeve_job_t *job) /* I - Job */ |
| { |
| char filename[1024], /* Filename buffer */ |
| buffer[4096]; /* Copy buffer */ |
| ssize_t bytes; /* Bytes read */ |
| cups_array_t *ra; /* Attributes to send in response */ |
| _cups_thread_t t; /* Thread */ |
| |
| |
| /* |
| * Create a file for the request data... |
| * |
| * TODO: Update code to support piping large raster data to the print command. |
| */ |
| |
| if ((job->fd = create_job_file(job, filename, sizeof(filename), client->printer->directory, NULL)) < 0) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to create print file: %s", strerror(errno)); |
| |
| goto abort_job; |
| } |
| |
| if (Verbosity) |
| fprintf(stderr, "Created job file \"%s\", format \"%s\".\n", filename, job->format); |
| |
| while ((bytes = httpRead2(client->http, buffer, sizeof(buffer))) > 0) |
| { |
| if (write(job->fd, buffer, (size_t)bytes) < bytes) |
| { |
| int error = errno; /* Write error */ |
| |
| close(job->fd); |
| job->fd = -1; |
| |
| unlink(filename); |
| |
| respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror(error)); |
| |
| goto abort_job; |
| } |
| } |
| |
| if (bytes < 0) |
| { |
| /* |
| * Got an error while reading the print data, so abort this job. |
| */ |
| |
| close(job->fd); |
| job->fd = -1; |
| |
| unlink(filename); |
| |
| respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to read print file."); |
| |
| goto abort_job; |
| } |
| |
| if (close(job->fd)) |
| { |
| int error = errno; /* Write error */ |
| |
| job->fd = -1; |
| |
| unlink(filename); |
| |
| respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror(error)); |
| |
| goto abort_job; |
| } |
| |
| job->fd = -1; |
| job->filename = strdup(filename); |
| job->state = IPP_JSTATE_PENDING; |
| |
| /* |
| * Process the job... |
| */ |
| |
| t = _cupsThreadCreate((_cups_thread_func_t)process_job, job); |
| |
| if (t) |
| { |
| _cupsThreadDetach(t); |
| } |
| else |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to process job."); |
| goto abort_job; |
| } |
| |
| /* |
| * Return the job info... |
| */ |
| |
| respond_ipp(client, IPP_STATUS_OK, NULL); |
| |
| ra = cupsArrayNew((cups_array_func_t)strcmp, NULL); |
| cupsArrayAdd(ra, "job-id"); |
| cupsArrayAdd(ra, "job-state"); |
| cupsArrayAdd(ra, "job-state-message"); |
| cupsArrayAdd(ra, "job-state-reasons"); |
| cupsArrayAdd(ra, "job-uri"); |
| |
| copy_job_attributes(client, job, ra); |
| cupsArrayDelete(ra); |
| return; |
| |
| /* |
| * If we get here we had to abort the job... |
| */ |
| |
| abort_job: |
| |
| job->state = IPP_JSTATE_ABORTED; |
| job->completed = time(NULL); |
| |
| ra = cupsArrayNew((cups_array_func_t)strcmp, NULL); |
| cupsArrayAdd(ra, "job-id"); |
| cupsArrayAdd(ra, "job-state"); |
| cupsArrayAdd(ra, "job-state-reasons"); |
| cupsArrayAdd(ra, "job-uri"); |
| |
| copy_job_attributes(client, job, ra); |
| cupsArrayDelete(ra); |
| } |
| |
| |
| /* |
| * 'finish_uri()' - Finish fetching a document URI and start processing. |
| */ |
| |
| static void |
| finish_document_uri( |
| ippeve_client_t *client, /* I - Client */ |
| ippeve_job_t *job) /* I - Job */ |
| { |
| ipp_attribute_t *uri; /* document-uri */ |
| char scheme[256], /* URI scheme */ |
| userpass[256], /* Username and password info */ |
| hostname[256], /* Hostname */ |
| resource[1024]; /* Resource path */ |
| int port; /* Port number */ |
| http_uri_status_t uri_status; /* URI decode status */ |
| http_encryption_t encryption; /* Encryption to use, if any */ |
| http_t *http; /* Connection for http/https URIs */ |
| http_status_t status; /* Access status for http/https URIs */ |
| int infile; /* Input file for local file URIs */ |
| char filename[1024], /* Filename buffer */ |
| buffer[4096]; /* Copy buffer */ |
| ssize_t bytes; /* Bytes read */ |
| ipp_attribute_t *attr; /* Current attribute */ |
| cups_array_t *ra; /* Attributes to send in response */ |
| |
| |
| /* |
| * Do we have a file to print? |
| */ |
| |
| if (httpGetState(client->http) == HTTP_STATE_POST_RECV) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Unexpected document data following request."); |
| |
| goto abort_job; |
| } |
| |
| /* |
| * Do we have a document URI? |
| */ |
| |
| if ((uri = ippFindAttribute(client->request, "document-uri", IPP_TAG_URI)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing document-uri."); |
| |
| goto abort_job; |
| } |
| |
| if (ippGetCount(uri) != 1) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Too many document-uri values."); |
| |
| goto abort_job; |
| } |
| |
| uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, ippGetString(uri, 0, NULL), |
| scheme, sizeof(scheme), userpass, |
| sizeof(userpass), hostname, sizeof(hostname), |
| &port, resource, sizeof(resource)); |
| if (uri_status < HTTP_URI_STATUS_OK) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Bad document-uri: %s", httpURIStatusString(uri_status)); |
| |
| goto abort_job; |
| } |
| |
| if (strcmp(scheme, "file") && |
| #ifdef HAVE_SSL |
| strcmp(scheme, "https") && |
| #endif /* HAVE_SSL */ |
| strcmp(scheme, "http")) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_URI_SCHEME, "URI scheme \"%s\" not supported.", scheme); |
| |
| goto abort_job; |
| } |
| |
| if (!strcmp(scheme, "file") && access(resource, R_OK)) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to access URI: %s", strerror(errno)); |
| |
| goto abort_job; |
| } |
| |
| /* |
| * Get the document format for the job... |
| */ |
| |
| _cupsRWLockWrite(&(client->printer->rwlock)); |
| |
| if ((attr = ippFindAttribute(job->attrs, "document-format", IPP_TAG_MIMETYPE)) != NULL) |
| job->format = ippGetString(attr, 0, NULL); |
| else |
| job->format = "application/octet-stream"; |
| |
| /* |
| * Create a file for the request data... |
| */ |
| |
| if ((job->fd = create_job_file(job, filename, sizeof(filename), client->printer->directory, NULL)) < 0) |
| { |
| _cupsRWUnlock(&(client->printer->rwlock)); |
| |
| respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to create print file: %s", strerror(errno)); |
| |
| goto abort_job; |
| } |
| |
| _cupsRWUnlock(&(client->printer->rwlock)); |
| |
| if (!strcmp(scheme, "file")) |
| { |
| if ((infile = open(resource, O_RDONLY)) < 0) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to access URI: %s", strerror(errno)); |
| |
| goto abort_job; |
| } |
| |
| do |
| { |
| if ((bytes = read(infile, buffer, sizeof(buffer))) < 0 && |
| (errno == EAGAIN || errno == EINTR)) |
| { |
| bytes = 1; |
| } |
| else if (bytes > 0 && write(job->fd, buffer, (size_t)bytes) < bytes) |
| { |
| int error = errno; /* Write error */ |
| |
| close(job->fd); |
| job->fd = -1; |
| |
| unlink(filename); |
| close(infile); |
| |
| respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror(error)); |
| |
| goto abort_job; |
| } |
| } |
| while (bytes > 0); |
| |
| close(infile); |
| } |
| else |
| { |
| #ifdef HAVE_SSL |
| if (port == 443 || !strcmp(scheme, "https")) |
| encryption = HTTP_ENCRYPTION_ALWAYS; |
| else |
| #endif /* HAVE_SSL */ |
| encryption = HTTP_ENCRYPTION_IF_REQUESTED; |
| |
| if ((http = httpConnect2(hostname, port, NULL, AF_UNSPEC, encryption, 1, 30000, NULL)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to connect to %s: %s", hostname, cupsLastErrorString()); |
| |
| close(job->fd); |
| job->fd = -1; |
| |
| unlink(filename); |
| |
| goto abort_job; |
| } |
| |
| httpClearFields(http); |
| httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en"); |
| if (httpGet(http, resource)) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to GET URI: %s", strerror(errno)); |
| |
| close(job->fd); |
| job->fd = -1; |
| |
| unlink(filename); |
| httpClose(http); |
| |
| goto abort_job; |
| } |
| |
| while ((status = httpUpdate(http)) == HTTP_STATUS_CONTINUE); |
| |
| if (status != HTTP_STATUS_OK) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to GET URI: %s", httpStatus(status)); |
| |
| close(job->fd); |
| job->fd = -1; |
| |
| unlink(filename); |
| httpClose(http); |
| |
| goto abort_job; |
| } |
| |
| while ((bytes = httpRead2(http, buffer, sizeof(buffer))) > 0) |
| { |
| if (write(job->fd, buffer, (size_t)bytes) < bytes) |
| { |
| int error = errno; /* Write error */ |
| |
| close(job->fd); |
| job->fd = -1; |
| |
| unlink(filename); |
| httpClose(http); |
| |
| respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, |
| "Unable to write print file: %s", strerror(error)); |
| |
| goto abort_job; |
| } |
| } |
| |
| httpClose(http); |
| } |
| |
| if (close(job->fd)) |
| { |
| int error = errno; /* Write error */ |
| |
| job->fd = -1; |
| |
| unlink(filename); |
| |
| respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror(error)); |
| |
| goto abort_job; |
| } |
| |
| _cupsRWLockWrite(&(client->printer->rwlock)); |
| |
| job->fd = -1; |
| job->filename = strdup(filename); |
| job->state = IPP_JSTATE_PENDING; |
| |
| _cupsRWUnlock(&(client->printer->rwlock)); |
| |
| /* |
| * Process the job... |
| */ |
| |
| process_job(job); |
| |
| /* |
| * Return the job info... |
| */ |
| |
| respond_ipp(client, IPP_STATUS_OK, NULL); |
| |
| ra = cupsArrayNew((cups_array_func_t)strcmp, NULL); |
| cupsArrayAdd(ra, "job-id"); |
| cupsArrayAdd(ra, "job-state"); |
| cupsArrayAdd(ra, "job-state-reasons"); |
| cupsArrayAdd(ra, "job-uri"); |
| |
| copy_job_attributes(client, job, ra); |
| cupsArrayDelete(ra); |
| return; |
| |
| /* |
| * If we get here we had to abort the job... |
| */ |
| |
| abort_job: |
| |
| job->state = IPP_JSTATE_ABORTED; |
| job->completed = time(NULL); |
| |
| ra = cupsArrayNew((cups_array_func_t)strcmp, NULL); |
| cupsArrayAdd(ra, "job-id"); |
| cupsArrayAdd(ra, "job-state"); |
| cupsArrayAdd(ra, "job-state-reasons"); |
| cupsArrayAdd(ra, "job-uri"); |
| |
| copy_job_attributes(client, job, ra); |
| cupsArrayDelete(ra); |
| } |
| |
| |
| /* |
| * 'html_escape()' - Write a HTML-safe string. |
| */ |
| |
| static void |
| html_escape(ippeve_client_t *client, /* I - Client */ |
| const char *s, /* I - String to write */ |
| size_t slen) /* I - Number of characters to write */ |
| { |
| const char *start, /* Start of segment */ |
| *end; /* End of string */ |
| |
| |
| start = s; |
| end = s + (slen > 0 ? slen : strlen(s)); |
| |
| while (*s && s < end) |
| { |
| if (*s == '&' || *s == '<') |
| { |
| if (s > start) |
| httpWrite2(client->http, start, (size_t)(s - start)); |
| |
| if (*s == '&') |
| httpWrite2(client->http, "&", 5); |
| else |
| httpWrite2(client->http, "<", 4); |
| |
| start = s + 1; |
| } |
| |
| s ++; |
| } |
| |
| if (s > start) |
| httpWrite2(client->http, start, (size_t)(s - start)); |
| } |
| |
| |
| /* |
| * 'html_footer()' - Show the web interface footer. |
| * |
| * This function also writes the trailing 0-length chunk. |
| */ |
| |
| static void |
| html_footer(ippeve_client_t *client) /* I - Client */ |
| { |
| html_printf(client, |
| "</div>\n" |
| "</body>\n" |
| "</html>\n"); |
| httpWrite2(client->http, "", 0); |
| } |
| |
| |
| /* |
| * 'html_header()' - Show the web interface header and title. |
| */ |
| |
| static void |
| html_header(ippeve_client_t *client, /* I - Client */ |
| const char *title, /* I - Title */ |
| int refresh) /* I - Refresh timer, if any */ |
| { |
| html_printf(client, |
| "<!doctype html>\n" |
| "<html>\n" |
| "<head>\n" |
| "<title>%s</title>\n" |
| "<link rel=\"shortcut icon\" href=\"/icon.png\" type=\"image/png\">\n" |
| "<link rel=\"apple-touch-icon\" href=\"/icon.png\" type=\"image/png\">\n" |
| "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=9\">\n", title); |
| if (refresh > 0) |
| html_printf(client, "<meta http-equiv=\"refresh\" content=\"%d\">\n", refresh); |
| html_printf(client, |
| "<meta name=\"viewport\" content=\"width=device-width\">\n" |
| "<style>\n" |
| "body { font-family: sans-serif; margin: 0; }\n" |
| "div.body { padding: 0px 10px 10px; }\n" |
| "span.badge { background: #090; border-radius: 5px; color: #fff; padding: 5px 10px; }\n" |
| "span.bar { box-shadow: 0px 1px 5px #333; font-size: 75%%; }\n" |
| "table.form { border-collapse: collapse; margin-left: auto; margin-right: auto; margin-top: 10px; width: auto; }\n" |
| "table.form td, table.form th { padding: 5px 2px; }\n" |
| "table.form td.meter { border-right: solid 1px #ccc; padding: 0px; width: 400px; }\n" |
| "table.form th { text-align: right; }\n" |
| "table.striped { border-bottom: solid thin black; border-collapse: collapse; width: 100%%; }\n" |
| "table.striped tr:nth-child(even) { background: #fcfcfc; }\n" |
| "table.striped tr:nth-child(odd) { background: #f0f0f0; }\n" |
| "table.striped th { background: white; border-bottom: solid thin black; text-align: left; vertical-align: bottom; }\n" |
| "table.striped td { margin: 0; padding: 5px; vertical-align: top; }\n" |
| "table.nav { border-collapse: collapse; width: 100%%; }\n" |
| "table.nav td { margin: 0; text-align: center; }\n" |
| "td.nav a, td.nav a:active, td.nav a:hover, td.nav a:hover:link, td.nav a:hover:link:visited, td.nav a:link, td.nav a:link:visited, td.nav a:visited { background: inherit; color: inherit; font-size: 80%%; text-decoration: none; }\n" |
| "td.nav { background: #333; color: #fff; padding: 4px 8px; width: 33%%; }\n" |
| "td.nav.sel { background: #fff; color: #000; font-weight: bold; }\n" |
| "td.nav:hover { background: #666; color: #fff; }\n" |
| "td.nav:active { background: #000; color: #ff0; }\n" |
| "</style>\n" |
| "</head>\n" |
| "<body>\n" |
| "<table class=\"nav\"><tr>" |
| "<td class=\"nav%s\"><a href=\"/\">Status</a></td>" |
| "<td class=\"nav%s\"><a href=\"/supplies\">Supplies</a></td>" |
| "<td class=\"nav%s\"><a href=\"/media\">Media</a></td>" |
| "</tr></table>\n" |
| "<div class=\"body\">\n", !strcmp(client->uri, "/") ? " sel" : "", !strcmp(client->uri, "/supplies") ? " sel" : "", !strcmp(client->uri, "/media") ? " sel" : ""); |
| } |
| |
| |
| /* |
| * 'html_printf()' - Send formatted text to the client, quoting as needed. |
| */ |
| |
| static void |
| html_printf(ippeve_client_t *client, /* I - Client */ |
| const char *format, /* I - Printf-style format string */ |
| ...) /* I - Additional arguments as needed */ |
| { |
| va_list ap; /* Pointer to arguments */ |
| const char *start; /* Start of string */ |
| char size, /* Size character (h, l, L) */ |
| type; /* Format type character */ |
| int width, /* Width of field */ |
| prec; /* Number of characters of precision */ |
| char tformat[100], /* Temporary format string for sprintf() */ |
| *tptr, /* Pointer into temporary format */ |
| temp[1024]; /* Buffer for formatted numbers */ |
| char *s; /* Pointer to string */ |
| |
| |
| /* |
| * Loop through the format string, formatting as needed... |
| */ |
| |
| va_start(ap, format); |
| start = format; |
| |
| while (*format) |
| { |
| if (*format == '%') |
| { |
| if (format > start) |
| httpWrite2(client->http, start, (size_t)(format - start)); |
| |
| tptr = tformat; |
| *tptr++ = *format++; |
| |
| if (*format == '%') |
| { |
| httpWrite2(client->http, "%", 1); |
| format ++; |
| start = format; |
| continue; |
| } |
| else if (strchr(" -+#\'", *format)) |
| *tptr++ = *format++; |
| |
| if (*format == '*') |
| { |
| /* |
| * Get width from argument... |
| */ |
| |
| format ++; |
| width = va_arg(ap, int); |
| |
| snprintf(tptr, sizeof(tformat) - (size_t)(tptr - tformat), "%d", width); |
| tptr += strlen(tptr); |
| } |
| else |
| { |
| width = 0; |
| |
| while (isdigit(*format & 255)) |
| { |
| if (tptr < (tformat + sizeof(tformat) - 1)) |
| *tptr++ = *format; |
| |
| width = width * 10 + *format++ - '0'; |
| } |
| } |
| |
| if (*format == '.') |
| { |
| if (tptr < (tformat + sizeof(tformat) - 1)) |
| *tptr++ = *format; |
| |
| format ++; |
| |
| if (*format == '*') |
| { |
| /* |
| * Get precision from argument... |
| */ |
| |
| format ++; |
| prec = va_arg(ap, int); |
| |
| snprintf(tptr, sizeof(tformat) - (size_t)(tptr - tformat), "%d", prec); |
| tptr += strlen(tptr); |
| } |
| else |
| { |
| prec = 0; |
| |
| while (isdigit(*format & 255)) |
| { |
| if (tptr < (tformat + sizeof(tformat) - 1)) |
| *tptr++ = *format; |
| |
| prec = prec * 10 + *format++ - '0'; |
| } |
| } |
| } |
| |
| if (*format == 'l' && format[1] == 'l') |
| { |
| size = 'L'; |
| |
| if (tptr < (tformat + sizeof(tformat) - 2)) |
| { |
| *tptr++ = 'l'; |
| *tptr++ = 'l'; |
| } |
| |
| format += 2; |
| } |
| else if (*format == 'h' || *format == 'l' || *format == 'L') |
| { |
| if (tptr < (tformat + sizeof(tformat) - 1)) |
| *tptr++ = *format; |
| |
| size = *format++; |
| } |
| else |
| size = 0; |
| |
| |
| if (!*format) |
| { |
| start = format; |
| break; |
| } |
| |
| if (tptr < (tformat + sizeof(tformat) - 1)) |
| *tptr++ = *format; |
| |
| type = *format++; |
| *tptr = '\0'; |
| start = format; |
| |
| switch (type) |
| { |
| case 'E' : /* Floating point formats */ |
| case 'G' : |
| case 'e' : |
| case 'f' : |
| case 'g' : |
| if ((size_t)(width + 2) > sizeof(temp)) |
| break; |
| |
| sprintf(temp, tformat, va_arg(ap, double)); |
| |
| httpWrite2(client->http, temp, strlen(temp)); |
| break; |
| |
| case 'B' : /* Integer formats */ |
| case 'X' : |
| case 'b' : |
| case 'd' : |
| case 'i' : |
| case 'o' : |
| case 'u' : |
| case 'x' : |
| if ((size_t)(width + 2) > sizeof(temp)) |
| break; |
| |
| # ifdef HAVE_LONG_LONG |
| if (size == 'L') |
| sprintf(temp, tformat, va_arg(ap, long long)); |
| else |
| # endif /* HAVE_LONG_LONG */ |
| if (size == 'l') |
| sprintf(temp, tformat, va_arg(ap, long)); |
| else |
| sprintf(temp, tformat, va_arg(ap, int)); |
| |
| httpWrite2(client->http, temp, strlen(temp)); |
| break; |
| |
| case 'p' : /* Pointer value */ |
| if ((size_t)(width + 2) > sizeof(temp)) |
| break; |
| |
| sprintf(temp, tformat, va_arg(ap, void *)); |
| |
| httpWrite2(client->http, temp, strlen(temp)); |
| break; |
| |
| case 'c' : /* Character or character array */ |
| if (width <= 1) |
| { |
| temp[0] = (char)va_arg(ap, int); |
| temp[1] = '\0'; |
| html_escape(client, temp, 1); |
| } |
| else |
| html_escape(client, va_arg(ap, char *), (size_t)width); |
| break; |
| |
| case 's' : /* String */ |
| if ((s = va_arg(ap, char *)) == NULL) |
| s = "(null)"; |
| |
| html_escape(client, s, strlen(s)); |
| break; |
| } |
| } |
| else |
| format ++; |
| } |
| |
| if (format > start) |
| httpWrite2(client->http, start, (size_t)(format - start)); |
| |
| va_end(ap); |
| } |
| |
| |
| /* |
| * 'ipp_cancel_job()' - Cancel a job. |
| */ |
| |
| static void |
| ipp_cancel_job(ippeve_client_t *client) /* I - Client */ |
| { |
| ippeve_job_t *job; /* Job information */ |
| |
| |
| /* |
| * Get the job... |
| */ |
| |
| if ((job = find_job(client)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist."); |
| return; |
| } |
| |
| /* |
| * See if the job is already completed, canceled, or aborted; if so, |
| * we can't cancel... |
| */ |
| |
| switch (job->state) |
| { |
| case IPP_JSTATE_CANCELED : |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, |
| "Job #%d is already canceled - can\'t cancel.", job->id); |
| break; |
| |
| case IPP_JSTATE_ABORTED : |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, |
| "Job #%d is already aborted - can\'t cancel.", job->id); |
| break; |
| |
| case IPP_JSTATE_COMPLETED : |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, |
| "Job #%d is already completed - can\'t cancel.", job->id); |
| break; |
| |
| default : |
| /* |
| * Cancel the job... |
| */ |
| |
| _cupsRWLockWrite(&(client->printer->rwlock)); |
| |
| if (job->state == IPP_JSTATE_PROCESSING || |
| (job->state == IPP_JSTATE_HELD && job->fd >= 0)) |
| job->cancel = 1; |
| else |
| { |
| job->state = IPP_JSTATE_CANCELED; |
| job->completed = time(NULL); |
| } |
| |
| _cupsRWUnlock(&(client->printer->rwlock)); |
| |
| respond_ipp(client, IPP_STATUS_OK, NULL); |
| break; |
| } |
| } |
| |
| |
| /* |
| * 'ipp_close_job()' - Close an open job. |
| */ |
| |
| static void |
| ipp_close_job(ippeve_client_t *client) /* I - Client */ |
| { |
| ippeve_job_t *job; /* Job information */ |
| |
| |
| /* |
| * Get the job... |
| */ |
| |
| if ((job = find_job(client)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist."); |
| return; |
| } |
| |
| /* |
| * See if the job is already completed, canceled, or aborted; if so, |
| * we can't cancel... |
| */ |
| |
| switch (job->state) |
| { |
| case IPP_JSTATE_CANCELED : |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, |
| "Job #%d is canceled - can\'t close.", job->id); |
| break; |
| |
| case IPP_JSTATE_ABORTED : |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, |
| "Job #%d is aborted - can\'t close.", job->id); |
| break; |
| |
| case IPP_JSTATE_COMPLETED : |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, |
| "Job #%d is completed - can\'t close.", job->id); |
| break; |
| |
| case IPP_JSTATE_PROCESSING : |
| case IPP_JSTATE_STOPPED : |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, |
| "Job #%d is already closed.", job->id); |
| break; |
| |
| default : |
| respond_ipp(client, IPP_STATUS_OK, NULL); |
| break; |
| } |
| } |
| |
| |
| /* |
| * 'ipp_create_job()' - Create a job object. |
| */ |
| |
| static void |
| ipp_create_job(ippeve_client_t *client) /* I - Client */ |
| { |
| ippeve_job_t *job; /* New job */ |
| cups_array_t *ra; /* Attributes to send in response */ |
| |
| |
| /* |
| * Validate print job attributes... |
| */ |
| |
| if (!valid_job_attributes(client)) |
| { |
| httpFlush(client->http); |
| return; |
| } |
| |
| /* |
| * Do we have a file to print? |
| */ |
| |
| if (httpGetState(client->http) == HTTP_STATE_POST_RECV) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, |
| "Unexpected document data following request."); |
| return; |
| } |
| |
| /* |
| * Create the job... |
| */ |
| |
| if ((job = create_job(client)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BUSY, |
| "Currently printing another job."); |
| return; |
| } |
| |
| /* |
| * Return the job info... |
| */ |
| |
| respond_ipp(client, IPP_STATUS_OK, NULL); |
| |
| ra = cupsArrayNew((cups_array_func_t)strcmp, NULL); |
| cupsArrayAdd(ra, "job-id"); |
| cupsArrayAdd(ra, "job-state"); |
| cupsArrayAdd(ra, "job-state-message"); |
| cupsArrayAdd(ra, "job-state-reasons"); |
| cupsArrayAdd(ra, "job-uri"); |
| |
| copy_job_attributes(client, job, ra); |
| cupsArrayDelete(ra); |
| } |
| |
| |
| /* |
| * 'ipp_get_job_attributes()' - Get the attributes for a job object. |
| */ |
| |
| static void |
| ipp_get_job_attributes( |
| ippeve_client_t *client) /* I - Client */ |
| { |
| ippeve_job_t *job; /* Job */ |
| cups_array_t *ra; /* requested-attributes */ |
| |
| |
| if ((job = find_job(client)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job not found."); |
| return; |
| } |
| |
| respond_ipp(client, IPP_STATUS_OK, NULL); |
| |
| ra = ippCreateRequestedArray(client->request); |
| copy_job_attributes(client, job, ra); |
| cupsArrayDelete(ra); |
| } |
| |
| |
| /* |
| * 'ipp_get_jobs()' - Get a list of job objects. |
| */ |
| |
| static void |
| ipp_get_jobs(ippeve_client_t *client) /* I - Client */ |
| { |
| ipp_attribute_t *attr; /* Current attribute */ |
| const char *which_jobs = NULL; |
| /* which-jobs values */ |
| int job_comparison; /* Job comparison */ |
| ipp_jstate_t job_state; /* job-state value */ |
| int first_job_id, /* First job ID */ |
| limit, /* Maximum number of jobs to return */ |
| count; /* Number of jobs that match */ |
| const char *username; /* Username */ |
| ippeve_job_t *job; /* Current job pointer */ |
| cups_array_t *ra; /* Requested attributes array */ |
| |
| |
| /* |
| * See if the "which-jobs" attribute have been specified... |
| */ |
| |
| if ((attr = ippFindAttribute(client->request, "which-jobs", |
| IPP_TAG_KEYWORD)) != NULL) |
| { |
| which_jobs = ippGetString(attr, 0, NULL); |
| fprintf(stderr, "%s Get-Jobs which-jobs=%s", client->hostname, which_jobs); |
| } |
| |
| if (!which_jobs || !strcmp(which_jobs, "not-completed")) |
| { |
| job_comparison = -1; |
| job_state = IPP_JSTATE_STOPPED; |
| } |
| else if (!strcmp(which_jobs, "completed")) |
| { |
| job_comparison = 1; |
| job_state = IPP_JSTATE_CANCELED; |
| } |
| else if (!strcmp(which_jobs, "aborted")) |
| { |
| job_comparison = 0; |
| job_state = IPP_JSTATE_ABORTED; |
| } |
| else if (!strcmp(which_jobs, "all")) |
| { |
| job_comparison = 1; |
| job_state = IPP_JSTATE_PENDING; |
| } |
| else if (!strcmp(which_jobs, "canceled")) |
| { |
| job_comparison = 0; |
| job_state = IPP_JSTATE_CANCELED; |
| } |
| else if (!strcmp(which_jobs, "pending")) |
| { |
| job_comparison = 0; |
| job_state = IPP_JSTATE_PENDING; |
| } |
| else if (!strcmp(which_jobs, "pending-held")) |
| { |
| job_comparison = 0; |
| job_state = IPP_JSTATE_HELD; |
| } |
| else if (!strcmp(which_jobs, "processing")) |
| { |
| job_comparison = 0; |
| job_state = IPP_JSTATE_PROCESSING; |
| } |
| else if (!strcmp(which_jobs, "processing-stopped")) |
| { |
| job_comparison = 0; |
| job_state = IPP_JSTATE_STOPPED; |
| } |
| else |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES, |
| "The which-jobs value \"%s\" is not supported.", which_jobs); |
| ippAddString(client->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD, |
| "which-jobs", NULL, which_jobs); |
| return; |
| } |
| |
| /* |
| * See if they want to limit the number of jobs reported... |
| */ |
| |
| if ((attr = ippFindAttribute(client->request, "limit", |
| IPP_TAG_INTEGER)) != NULL) |
| { |
| limit = ippGetInteger(attr, 0); |
| |
| fprintf(stderr, "%s Get-Jobs limit=%d", client->hostname, limit); |
| } |
| else |
| limit = 0; |
| |
| if ((attr = ippFindAttribute(client->request, "first-job-id", |
| IPP_TAG_INTEGER)) != NULL) |
| { |
| first_job_id = ippGetInteger(attr, 0); |
| |
| fprintf(stderr, "%s Get-Jobs first-job-id=%d", client->hostname, first_job_id); |
| } |
| else |
| first_job_id = 1; |
| |
| /* |
| * See if we only want to see jobs for a specific user... |
| */ |
| |
| username = NULL; |
| |
| if ((attr = ippFindAttribute(client->request, "my-jobs", |
| IPP_TAG_BOOLEAN)) != NULL) |
| { |
| int my_jobs = ippGetBoolean(attr, 0); |
| |
| fprintf(stderr, "%s Get-Jobs my-jobs=%s\n", client->hostname, my_jobs ? "true" : "false"); |
| |
| if (my_jobs) |
| { |
| if ((attr = ippFindAttribute(client->request, "requesting-user-name", |
| IPP_TAG_NAME)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, |
| "Need requesting-user-name with my-jobs."); |
| return; |
| } |
| |
| username = ippGetString(attr, 0, NULL); |
| |
| fprintf(stderr, "%s Get-Jobs requesting-user-name=\"%s\"\n", client->hostname, username); |
| } |
| } |
| |
| /* |
| * OK, build a list of jobs for this printer... |
| */ |
| |
| ra = ippCreateRequestedArray(client->request); |
| |
| respond_ipp(client, IPP_STATUS_OK, NULL); |
| |
| _cupsRWLockRead(&(client->printer->rwlock)); |
| |
| for (count = 0, job = (ippeve_job_t *)cupsArrayFirst(client->printer->jobs); |
| (limit <= 0 || count < limit) && job; |
| job = (ippeve_job_t *)cupsArrayNext(client->printer->jobs)) |
| { |
| /* |
| * Filter out jobs that don't match... |
| */ |
| |
| if ((job_comparison < 0 && job->state > job_state) || |
| (job_comparison == 0 && job->state != job_state) || |
| (job_comparison > 0 && job->state < job_state) || |
| job->id < first_job_id || |
| (username && job->username && |
| strcasecmp(username, job->username))) |
| continue; |
| |
| if (count > 0) |
| ippAddSeparator(client->response); |
| |
| count ++; |
| copy_job_attributes(client, job, ra); |
| } |
| |
| cupsArrayDelete(ra); |
| |
| _cupsRWUnlock(&(client->printer->rwlock)); |
| } |
| |
| |
| /* |
| * 'ipp_get_printer_attributes()' - Get the attributes for a printer object. |
| */ |
| |
| static void |
| ipp_get_printer_attributes( |
| ippeve_client_t *client) /* I - Client */ |
| { |
| cups_array_t *ra; /* Requested attributes array */ |
| ippeve_printer_t *printer; /* Printer */ |
| |
| |
| /* |
| * Send the attributes... |
| */ |
| |
| ra = ippCreateRequestedArray(client->request); |
| printer = client->printer; |
| |
| respond_ipp(client, IPP_STATUS_OK, NULL); |
| |
| _cupsRWLockRead(&(printer->rwlock)); |
| |
| copy_attributes(client->response, printer->attrs, ra, IPP_TAG_ZERO, |
| IPP_TAG_CUPS_CONST); |
| |
| if (!ra || cupsArrayFind(ra, "printer-config-change-date-time")) |
| ippAddDate(client->response, IPP_TAG_PRINTER, "printer-config-change-date-time", ippTimeToDate(printer->config_time)); |
| |
| if (!ra || cupsArrayFind(ra, "printer-config-change-time")) |
| ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-config-change-time", (int)(printer->config_time - printer->start_time)); |
| |
| if (!ra || cupsArrayFind(ra, "printer-current-time")) |
| ippAddDate(client->response, IPP_TAG_PRINTER, "printer-current-time", ippTimeToDate(time(NULL))); |
| |
| |
| if (!ra || cupsArrayFind(ra, "printer-state")) |
| ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_ENUM, "printer-state", (int)printer->state); |
| |
| if (!ra || cupsArrayFind(ra, "printer-state-change-date-time")) |
| ippAddDate(client->response, IPP_TAG_PRINTER, "printer-state-change-date-time", ippTimeToDate(printer->state_time)); |
| |
| if (!ra || cupsArrayFind(ra, "printer-state-change-time")) |
| ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-state-change-time", (int)(printer->state_time - printer->start_time)); |
| |
| if (!ra || cupsArrayFind(ra, "printer-state-message")) |
| { |
| static const char * const messages[] = { "Idle.", "Printing.", "Stopped." }; |
| |
| ippAddString(client->response, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-state-message", NULL, messages[printer->state - IPP_PSTATE_IDLE]); |
| } |
| |
| if (!ra || cupsArrayFind(ra, "printer-state-reasons")) |
| { |
| if (printer->state_reasons == IPPEVE_PREASON_NONE) |
| { |
| ippAddString(client->response, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "printer-state-reasons", NULL, "none"); |
| } |
| else |
| { |
| ipp_attribute_t *attr = NULL; /* printer-state-reasons */ |
| ippeve_preason_t bit; /* Reason bit */ |
| int i; /* Looping var */ |
| char reason[32]; /* Reason string */ |
| |
| for (i = 0, bit = 1; i < (int)(sizeof(ippeve_preason_strings) / sizeof(ippeve_preason_strings[0])); i ++, bit *= 2) |
| { |
| if (printer->state_reasons & bit) |
| { |
| snprintf(reason, sizeof(reason), "%s-%s", ippeve_preason_strings[i], printer->state == IPP_PSTATE_IDLE ? "report" : printer->state == IPP_PSTATE_PROCESSING ? "warning" : "error"); |
| if (attr) |
| ippSetString(client->response, &attr, ippGetCount(attr), reason); |
| else |
| attr = ippAddString(client->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "printer-state-reasons", NULL, reason); |
| } |
| } |
| } |
| } |
| |
| if (!ra || cupsArrayFind(ra, "printer-up-time")) |
| ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-up-time", (int)(time(NULL) - printer->start_time)); |
| |
| if (!ra || cupsArrayFind(ra, "queued-job-count")) |
| ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "queued-job-count", printer->active_job && printer->active_job->state < IPP_JSTATE_CANCELED); |
| |
| _cupsRWUnlock(&(printer->rwlock)); |
| |
| cupsArrayDelete(ra); |
| } |
| |
| |
| /* |
| * 'ipp_identify_printer()' - Beep or display a message. |
| */ |
| |
| static void |
| ipp_identify_printer( |
| ippeve_client_t *client) /* I - Client */ |
| { |
| ipp_attribute_t *actions, /* identify-actions */ |
| *message; /* message */ |
| |
| |
| actions = ippFindAttribute(client->request, "identify-actions", IPP_TAG_KEYWORD); |
| message = ippFindAttribute(client->request, "message", IPP_TAG_TEXT); |
| |
| if (!actions || ippContainsString(actions, "sound")) |
| { |
| putchar(0x07); |
| fflush(stdout); |
| } |
| |
| if (ippContainsString(actions, "display")) |
| printf("IDENTIFY from %s: %s\n", client->hostname, message ? ippGetString(message, 0, NULL) : "No message supplied"); |
| |
| respond_ipp(client, IPP_STATUS_OK, NULL); |
| } |
| |
| |
| /* |
| * 'ipp_print_job()' - Create a job object with an attached document. |
| */ |
| |
| static void |
| ipp_print_job(ippeve_client_t *client) /* I - Client */ |
| { |
| ippeve_job_t *job; /* New job */ |
| |
| |
| /* |
| * Validate print job attributes... |
| */ |
| |
| if (!valid_job_attributes(client)) |
| { |
| httpFlush(client->http); |
| return; |
| } |
| |
| /* |
| * Do we have a file to print? |
| */ |
| |
| if (httpGetState(client->http) == HTTP_STATE_POST_SEND) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "No file in request."); |
| return; |
| } |
| |
| /* |
| * Create the job... |
| */ |
| |
| if ((job = create_job(client)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BUSY, "Currently printing another job."); |
| return; |
| } |
| |
| /* |
| * Then finish getting the document data and process things... |
| */ |
| |
| finish_document_data(client, job); |
| } |
| |
| |
| /* |
| * 'ipp_print_uri()' - Create a job object with a referenced document. |
| */ |
| |
| static void |
| ipp_print_uri(ippeve_client_t *client) /* I - Client */ |
| { |
| ippeve_job_t *job; /* New job */ |
| |
| |
| /* |
| * Validate print job attributes... |
| */ |
| |
| if (!valid_job_attributes(client)) |
| { |
| httpFlush(client->http); |
| return; |
| } |
| |
| /* |
| * Create the job... |
| */ |
| |
| if ((job = create_job(client)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BUSY, "Currently printing another job."); |
| return; |
| } |
| |
| /* |
| * Then finish getting the document data and process things... |
| */ |
| |
| finish_document_uri(client, job); |
| } |
| |
| |
| /* |
| * 'ipp_send_document()' - Add an attached document to a job object created with |
| * Create-Job. |
| */ |
| |
| static void |
| ipp_send_document( |
| ippeve_client_t *client) /* I - Client */ |
| { |
| ippeve_job_t *job; /* Job information */ |
| ipp_attribute_t *attr; /* Current attribute */ |
| |
| |
| /* |
| * Get the job... |
| */ |
| |
| if ((job = find_job(client)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist."); |
| httpFlush(client->http); |
| return; |
| } |
| |
| /* |
| * See if we already have a document for this job or the job has already |
| * in a non-pending state... |
| */ |
| |
| if (job->state > IPP_JSTATE_HELD) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job is not in a pending state."); |
| httpFlush(client->http); |
| return; |
| } |
| else if (job->filename || job->fd >= 0) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED, "Multiple document jobs are not supported."); |
| httpFlush(client->http); |
| return; |
| } |
| |
| /* |
| * Make sure we have the "last-document" operation attribute... |
| */ |
| |
| if ((attr = ippFindAttribute(client->request, "last-document", IPP_TAG_ZERO)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing required last-document attribute."); |
| httpFlush(client->http); |
| return; |
| } |
| else if (ippGetGroupTag(attr) != IPP_TAG_OPERATION) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "The last-document attribute is not in the operation group."); |
| httpFlush(client->http); |
| return; |
| } |
| else if (ippGetValueTag(attr) != IPP_TAG_BOOLEAN || ippGetCount(attr) != 1 || !ippGetBoolean(attr, 0)) |
| { |
| respond_unsupported(client, attr); |
| httpFlush(client->http); |
| return; |
| } |
| |
| /* |
| * Validate document attributes... |
| */ |
| |
| if (!valid_doc_attributes(client)) |
| { |
| httpFlush(client->http); |
| return; |
| } |
| |
| /* |
| * Then finish getting the document data and process things... |
| */ |
| |
| _cupsRWLockWrite(&(client->printer->rwlock)); |
| |
| copy_attributes(job->attrs, client->request, NULL, IPP_TAG_JOB, 0); |
| |
| if ((attr = ippFindAttribute(job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL) |
| job->format = ippGetString(attr, 0, NULL); |
| else if ((attr = ippFindAttribute(job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL) |
| job->format = ippGetString(attr, 0, NULL); |
| else |
| job->format = "application/octet-stream"; |
| |
| _cupsRWUnlock(&(client->printer->rwlock)); |
| |
| finish_document_data(client, job); |
| } |
| |
| |
| /* |
| * 'ipp_send_uri()' - Add a referenced document to a job object created with |
| * Create-Job. |
| */ |
| |
| static void |
| ipp_send_uri(ippeve_client_t *client) /* I - Client */ |
| { |
| ippeve_job_t *job; /* Job information */ |
| ipp_attribute_t *attr; /* Current attribute */ |
| |
| |
| /* |
| * Get the job... |
| */ |
| |
| if ((job = find_job(client)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist."); |
| httpFlush(client->http); |
| return; |
| } |
| |
| /* |
| * See if we already have a document for this job or the job has already |
| * in a non-pending state... |
| */ |
| |
| if (job->state > IPP_JSTATE_HELD) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job is not in a pending state."); |
| httpFlush(client->http); |
| return; |
| } |
| else if (job->filename || job->fd >= 0) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED, "Multiple document jobs are not supported."); |
| httpFlush(client->http); |
| return; |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "last-document", IPP_TAG_ZERO)) == NULL) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing required last-document attribute."); |
| httpFlush(client->http); |
| return; |
| } |
| else if (ippGetGroupTag(attr) != IPP_TAG_OPERATION) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "The last-document attribute is not in the operation group."); |
| httpFlush(client->http); |
| return; |
| } |
| else if (ippGetValueTag(attr) != IPP_TAG_BOOLEAN || ippGetCount(attr) != 1 || !ippGetBoolean(attr, 0)) |
| { |
| respond_unsupported(client, attr); |
| httpFlush(client->http); |
| return; |
| } |
| |
| /* |
| * Validate document attributes... |
| */ |
| |
| if (!valid_doc_attributes(client)) |
| { |
| httpFlush(client->http); |
| return; |
| } |
| |
| /* |
| * Then finish getting the document data and process things... |
| */ |
| |
| _cupsRWLockWrite(&(client->printer->rwlock)); |
| |
| copy_attributes(job->attrs, client->request, NULL, IPP_TAG_JOB, 0); |
| |
| if ((attr = ippFindAttribute(job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL) |
| job->format = ippGetString(attr, 0, NULL); |
| else if ((attr = ippFindAttribute(job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL) |
| job->format = ippGetString(attr, 0, NULL); |
| else |
| job->format = "application/octet-stream"; |
| |
| _cupsRWUnlock(&(client->printer->rwlock)); |
| |
| finish_document_uri(client, job); |
| } |
| |
| |
| /* |
| * 'ipp_validate_job()' - Validate job creation attributes. |
| */ |
| |
| static void |
| ipp_validate_job(ippeve_client_t *client) /* I - Client */ |
| { |
| if (valid_job_attributes(client)) |
| respond_ipp(client, IPP_STATUS_OK, NULL); |
| } |
| |
| |
| /* |
| * 'ippserver_attr_cb()' - Determine whether an attribute should be loaded. |
| */ |
| |
| static int /* O - 1 to use, 0 to ignore */ |
| ippserver_attr_cb( |
| _ipp_file_t *f, /* I - IPP file */ |
| void *user_data, /* I - User data pointer (unused) */ |
| const char *attr) /* I - Attribute name */ |
| { |
| int i, /* Current element */ |
| result; /* Result of comparison */ |
| static const char * const ignored[] = |
| { /* Ignored attributes */ |
| "attributes-charset", |
| "attributes-natural-language", |
| "charset-configured", |
| "charset-supported", |
| "device-service-count", |
| "device-uuid", |
| "document-format-varying-attributes", |
| "generated-natural-language-supported", |
| "identify-actions-default", |
| "identify-actions-supported", |
| "ipp-features-supported", |
| "ipp-versions-supproted", |
| "ippget-event-life", |
| "job-hold-until-supported", |
| "job-hold-until-time-supported", |
| "job-ids-supported", |
| "job-k-octets-supported", |
| "job-settable-attributes-supported", |
| "multiple-document-jobs-supported", |
| "multiple-operation-time-out", |
| "multiple-operation-time-out-action", |
| "natural-language-configured", |
| "notify-attributes-supported", |
| "notify-events-default", |
| "notify-events-supported", |
| "notify-lease-duration-default", |
| "notify-lease-duration-supported", |
| "notify-max-events-supported", |
| "notify-pull-method-supported", |
| "operations-supported", |
| "printer-alert", |
| "printer-alert-description", |
| "printer-camera-image-uri", |
| "printer-charge-info", |
| "printer-charge-info-uri", |
| "printer-config-change-date-time", |
| "printer-config-change-time", |
| "printer-current-time", |
| "printer-detailed-status-messages", |
| "printer-dns-sd-name", |
| "printer-fax-log-uri", |
| "printer-get-attributes-supported", |
| "printer-icons", |
| "printer-id", |
| "printer-info", |
| "printer-is-accepting-jobs", |
| "printer-message-date-time", |
| "printer-message-from-operator", |
| "printer-message-time", |
| "printer-more-info", |
| "printer-service-type", |
| "printer-settable-attributes-supported", |
| "printer-state", |
| "printer-state-message", |
| "printer-state-reasons", |
| "printer-static-resource-directory-uri", |
| "printer-static-resource-k-octets-free", |
| "printer-static-resource-k-octets-supported", |
| "printer-strings-languages-supported", |
| "printer-strings-uri", |
| "printer-supply-info-uri", |
| "printer-up-time", |
| "printer-uri-supported", |
| "printer-xri-supported", |
| "queued-job-count", |
| "reference-uri-scheme-supported", |
| "uri-authentication-supported", |
| "uri-security-supported", |
| "which-jobs-supported", |
| "xri-authentication-supported", |
| "xri-security-supported", |
| "xri-uri-scheme-supported" |
| }; |
| |
| |
| (void)f; |
| (void)user_data; |
| |
| for (i = 0, result = 1; i < (int)(sizeof(ignored) / sizeof(ignored[0])); i ++) |
| { |
| if ((result = strcmp(attr, ignored[i])) <= 0) |
| break; |
| } |
| |
| return (result != 0); |
| } |
| |
| |
| /* |
| * 'ippserver_error_cb()' - Log an error message. |
| */ |
| |
| static int /* O - 1 to continue, 0 to stop */ |
| ippserver_error_cb( |
| _ipp_file_t *f, /* I - IPP file data */ |
| void *user_data, /* I - User data pointer (unused) */ |
| const char *error) /* I - Error message */ |
| { |
| (void)f; |
| (void)user_data; |
| |
| _cupsLangPrintf(stderr, "%s\n", error); |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'ippserver_token_cb()' - Process ippserver-specific config file tokens. |
| */ |
| |
| static int /* O - 1 to continue, 0 to stop */ |
| ippserver_token_cb( |
| _ipp_file_t *f, /* I - IPP file data */ |
| _ipp_vars_t *vars, /* I - IPP variables */ |
| void *user_data, /* I - User data pointer (unused) */ |
| const char *token) /* I - Current token */ |
| { |
| (void)vars; |
| (void)user_data; |
| |
| if (!token) |
| { |
| /* |
| * NULL token means do the initial setup - create an empty IPP message and |
| * return... |
| */ |
| |
| f->attrs = ippNew(); |
| f->group_tag = IPP_TAG_PRINTER; |
| } |
| else |
| { |
| _cupsLangPrintf(stderr, _("Unknown directive \"%s\" on line %d of \"%s\" ignored."), token, f->linenum, f->filename); |
| } |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'load_ippserver_attributes()' - Load IPP attributes from an ippserver file. |
| */ |
| |
| static ipp_t * /* O - IPP attributes or `NULL` on error */ |
| load_ippserver_attributes( |
| const char *servername, /* I - Server name or `NULL` for default */ |
| int serverport, /* I - Server port number */ |
| const char *filename, /* I - ippserver attribute filename */ |
| cups_array_t *docformats) /* I - document-format-supported values */ |
| { |
| ipp_t *attrs; /* IPP attributes */ |
| _ipp_vars_t vars; /* IPP variables */ |
| char temp[256]; /* Temporary string */ |
| |
| |
| (void)docformats; /* for now */ |
| |
| /* |
| * Setup callbacks and variables for the printer configuration file... |
| * |
| * The following additional variables are supported: |
| * |
| * - SERVERNAME: The host name of the server. |
| * - SERVERPORT: The default port of the server. |
| */ |
| |
| _ippVarsInit(&vars, (_ipp_fattr_cb_t)ippserver_attr_cb, (_ipp_ferror_cb_t)ippserver_error_cb, (_ipp_ftoken_cb_t)ippserver_token_cb); |
| |
| if (servername) |
| { |
| _ippVarsSet(&vars, "SERVERNAME", servername); |
| } |
| else |
| { |
| httpGetHostname(NULL, temp, sizeof(temp)); |
| _ippVarsSet(&vars, "SERVERNAME", temp); |
| } |
| |
| snprintf(temp, sizeof(temp), "%d", serverport); |
| _ippVarsSet(&vars, "SERVERPORT", temp); |
| |
| /* |
| * Load attributes and values for the printer... |
| */ |
| |
| attrs = _ippFileParse(&vars, filename, NULL); |
| |
| /* |
| * Free memory and return... |
| */ |
| |
| _ippVarsDeinit(&vars); |
| |
| return (attrs); |
| } |
| |
| |
| /* |
| * 'load_legacy_attributes()' - Load IPP attributes using the old ippserver |
| * options. |
| */ |
| |
| static ipp_t * /* O - IPP attributes or `NULL` on error */ |
| load_legacy_attributes( |
| const char *make, /* I - Manufacturer name */ |
| const char *model, /* I - Model name */ |
| int ppm, /* I - pages-per-minute */ |
| int ppm_color, /* I - pages-per-minute-color */ |
| int duplex, /* I - Duplex support? */ |
| cups_array_t *docformats) /* I - document-format-supported values */ |
| { |
| int i; /* Looping var */ |
| ipp_t *attrs, /* IPP attributes */ |
| *col; /* Collection value */ |
| ipp_attribute_t *attr; /* Current attribute */ |
| char device_id[1024],/* printer-device-id */ |
| *ptr, /* Pointer into device ID */ |
| make_model[128];/* printer-make-and-model */ |
| const char *format, /* Current document format */ |
| *prefix; /* Prefix for device ID */ |
| int num_media; /* Number of media */ |
| const char * const *media; /* List of media */ |
| int num_ready; /* Number of loaded media */ |
| const char * const *ready; /* List of loaded media */ |
| pwg_media_t *pwg; /* PWG media size information */ |
| static const char * const media_supported[] = |
| { /* media-supported values */ |
| "na_letter_8.5x11in", /* Letter */ |
| "na_legal_8.5x14in", /* Legal */ |
| "iso_a4_210x297mm", /* A4 */ |
| "na_number-10_4.125x9.5in", /* #10 Envelope */ |
| "iso_dl_110x220mm" /* DL Envelope */ |
| }; |
| static const char * const media_supported_color[] = |
| { /* media-supported values */ |
| "na_letter_8.5x11in", /* Letter */ |
| "na_legal_8.5x14in", /* Legal */ |
| "iso_a4_210x297mm", /* A4 */ |
| "na_number-10_4.125x9.5in", /* #10 Envelope */ |
| "iso_dl_110x220mm", /* DL Envelope */ |
| "na_index-3x5_3x5in", /* Photo 3x5 */ |
| "oe_photo-l_3.5x5in", /* Photo L */ |
| "na_index-4x6_4x6in", /* Photo 4x6 */ |
| "iso_a6_105x148mm", /* A6 */ |
| "na_5x7_5x7in" /* Photo 5x7 aka 2L */ |
| "iso_a5_148x210mm", /* A5 */ |
| }; |
| static const char * const media_ready[] = |
| { /* media-ready values */ |
| "na_letter_8.5x11in", /* Letter */ |
| "na_number-10_4.125x9.5in" /* #10 */ |
| }; |
| static const char * const media_ready_color[] = |
| { /* media-ready values */ |
| "na_letter_8.5x11in", /* Letter */ |
| "na_index-4x6_4x6in" /* Photo 4x6 */ |
| }; |
| static const char * const media_source_supported[] = |
| { /* media-source-supported values */ |
| "auto", |
| "main", |
| "manual", |
| "by-pass-tray" /* AKA multi-purpose tray */ |
| }; |
| static const char * const media_source_supported_color[] = |
| { /* media-source-supported values */ |
| "auto", |
| "main", |
| "photo" |
| }; |
| static const char * const media_type_supported[] = |
| { /* media-type-supported values */ |
| "auto", |
| "cardstock", |
| "envelope", |
| "labels", |
| "other", |
| "stationery", |
| "stationery-letterhead", |
| "transparency" |
| }; |
| static const char * const media_type_supported_color[] = |
| { /* media-type-supported values */ |
| "auto", |
| "cardstock", |
| "envelope", |
| "labels", |
| "other", |
| "stationery", |
| "stationery-letterhead", |
| "transparency", |
| "photographic-glossy", |
| "photographic-high-gloss", |
| "photographic-matte", |
| "photographic-satin", |
| "photographic-semi-gloss" |
| }; |
| static const int media_bottom_margin_supported[] = |
| { /* media-bottom-margin-supported values */ |
| 635 /* 1/4" */ |
| }; |
| static const int media_bottom_margin_supported_color[] = |
| { /* media-bottom/top-margin-supported values */ |
| 0, /* Borderless */ |
| 1168 /* 0.46" (common HP inkjet bottom margin) */ |
| }; |
| static const int media_lr_margin_supported[] = |
| { /* media-left/right-margin-supported values */ |
| 340, /* 3.4mm (historical HP PCL A4 margin) */ |
| 635 /* 1/4" */ |
| }; |
| static const int media_lr_margin_supported_color[] = |
| { /* media-left/right-margin-supported values */ |
| 0, /* Borderless */ |
| 340, /* 3.4mm (historical HP PCL A4 margin) */ |
| 635 /* 1/4" */ |
| }; |
| static const int media_top_margin_supported[] = |
| { /* media-top-margin-supported values */ |
| 635 /* 1/4" */ |
| }; |
| static const int media_top_margin_supported_color[] = |
| { /* media-top/top-margin-supported values */ |
| 0, /* Borderless */ |
| 102 /* 0.04" (common HP inkjet top margin */ |
| }; |
| static const int orientation_requested_supported[4] = |
| { /* orientation-requested-supported values */ |
| IPP_ORIENT_PORTRAIT, |
| IPP_ORIENT_LANDSCAPE, |
| IPP_ORIENT_REVERSE_LANDSCAPE, |
| IPP_ORIENT_REVERSE_PORTRAIT |
| }; |
| static const char * const overrides_supported[] = |
| { /* overrides-supported values */ |
| "document-numbers", |
| "media", |
| "media-col", |
| "orientation-requested", |
| "pages" |
| }; |
| static const char * const print_color_mode_supported[] = |
| { /* print-color-mode-supported values */ |
| "monochrome" |
| }; |
| static const char * const print_color_mode_supported_color[] = |
| { /* print-color-mode-supported values */ |
| "auto", |
| "color", |
| "monochrome" |
| }; |
| static const int print_quality_supported[] = |
| { /* print-quality-supported values */ |
| IPP_QUALITY_DRAFT, |
| IPP_QUALITY_NORMAL, |
| IPP_QUALITY_HIGH |
| }; |
| static const char * const printer_input_tray[] = |
| { /* printer-input-tray values */ |
| "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto", |
| "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=100;status=0;name=main", |
| "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=manual", |
| "type=sheetFeedAutoNonRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=25;level=-2;status=0;name=by-pass-tray" |
| }; |
| static const char * const printer_input_tray_color[] = |
| { /* printer-input-tray values */ |
| "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto", |
| "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=-2;status=0;name=main", |
| "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=25;level=-2;status=0;name=photo" |
| }; |
| static const char * const printer_supply[] = |
| { /* printer-supply values */ |
| "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;" |
| "maxcapacity=100;level=25;colorantname=unknown;", |
| "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;" |
| "maxcapacity=100;level=75;colorantname=black;" |
| }; |
| static const char * const printer_supply_color[] = |
| { /* printer-supply values */ |
| "index=1;class=receptacleThatIsFilled;type=wasteInk;unit=percent;" |
| "maxcapacity=100;level=25;colorantname=unknown;", |
| "index=2;class=supplyThatIsConsumed;type=ink;unit=percent;" |
| "maxcapacity=100;level=75;colorantname=black;", |
| "index=3;class=supplyThatIsConsumed;type=ink;unit=percent;" |
| "maxcapacity=100;level=50;colorantname=cyan;", |
| "index=4;class=supplyThatIsConsumed;type=ink;unit=percent;" |
| "maxcapacity=100;level=33;colorantname=magenta;", |
| "index=5;class=supplyThatIsConsumed;type=ink;unit=percent;" |
| "maxcapacity=100;level=67;colorantname=yellow;" |
| }; |
| static const char * const printer_supply_description[] = |
| { /* printer-supply-description values */ |
| "Toner Waste Tank", |
| "Black Toner" |
| }; |
| static const char * const printer_supply_description_color[] = |
| { /* printer-supply-description values */ |
| "Ink Waste Tank", |
| "Black Ink", |
| "Cyan Ink", |
| "Magenta Ink", |
| "Yellow Ink" |
| }; |
| static const int pwg_raster_document_resolution_supported[] = |
| { |
| 300, |
| 600 |
| }; |
| static const char * const pwg_raster_document_type_supported[] = |
| { |
| "black_1", |
| "sgray_8" |
| }; |
| static const char * const pwg_raster_document_type_supported_color[] = |
| { |
| "black_1", |
| "sgray_8", |
| "srgb_8", |
| "srgb_16" |
| }; |
| static const char * const sides_supported[] = |
| { /* sides-supported values */ |
| "one-sided", |
| "two-sided-long-edge", |
| "two-sided-short-edge" |
| }; |
| static const char * const urf_supported[] = |
| { /* urf-supported values */ |
| "CP1", |
| "IS1-4-5-19", |
| "MT1-2-3-4-5-6", |
| "RS600", |
| "V1.4", |
| "W8" |
| }; |
| static const char * const urf_supported_color[] = |
| { /* urf-supported values */ |
| "CP1", |
| "IS1-4-5-7-19", |
| "MT1-2-3-4-5-6-8-9-10-11-12-13", |
| "RS600", |
| "SRGB24", |
| "V1.4", |
| "W8" |
| }; |
| static const char * const urf_supported_color_duplex[] = |
| { /* urf-supported values */ |
| "CP1", |
| "IS1-4-5-7-19", |
| "MT1-2-3-4-5-6-8-9-10-11-12-13", |
| "RS600", |
| "SRGB24", |
| "V1.4", |
| "W8", |
| "DM3" |
| }; |
| static const char * const urf_supported_duplex[] = |
| { /* urf-supported values */ |
| "CP1", |
| "IS1-4-5-19", |
| "MT1-2-3-4-5-6", |
| "RS600", |
| "V1.4", |
| "W8", |
| "DM1" |
| }; |
| |
| |
| attrs = ippNew(); |
| |
| if (ppm_color > 0) |
| { |
| num_media = (int)(sizeof(media_supported_color) / sizeof(media_supported_color[0])); |
| media = media_supported_color; |
| num_ready = (int)(sizeof(media_ready_color) / sizeof(media_ready_color[0])); |
| ready = media_ready_color; |
| } |
| else |
| { |
| num_media = (int)(sizeof(media_supported) / sizeof(media_supported[0])); |
| media = media_supported; |
| num_ready = (int)(sizeof(media_ready) / sizeof(media_ready[0])); |
| ready = media_ready; |
| } |
| |
| /* color-supported */ |
| ippAddBoolean(attrs, IPP_TAG_PRINTER, "color-supported", ppm_color > 0); |
| |
| /* copies-default */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default", 1); |
| |
| /* copies-supported */ |
| ippAddRange(attrs, IPP_TAG_PRINTER, "copies-supported", 1, (cupsArrayFind(docformats, (void *)"application/pdf") != NULL || cupsArrayFind(docformats, (void *)"image/jpeg") != NULL) ? 999 : 1); |
| |
| /* document-password-supported */ |
| if (cupsArrayFind(docformats, (void *)"application/pdf")) |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 1023); |
| |
| /* finishings-default */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-default", IPP_FINISHINGS_NONE); |
| |
| /* finishings-supported */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-supported", IPP_FINISHINGS_NONE); |
| |
| /* media-bottom-margin-supported */ |
| if (ppm_color > 0) |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", (int)(sizeof(media_bottom_margin_supported) / sizeof(media_bottom_margin_supported[0])), media_bottom_margin_supported); |
| else |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", (int)(sizeof(media_bottom_margin_supported_color) / sizeof(media_bottom_margin_supported_color[0])), media_bottom_margin_supported_color); |
| |
| /* media-col-database and media-col-default */ |
| attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-col-database", num_media, NULL); |
| for (i = 0; i < num_media; i ++) |
| { |
| int bottom, left, /* media-xxx-margins */ |
| right, top; |
| const char *source; /* media-source, if any */ |
| |
| pwg = pwgMediaForPWG(media[i]); |
| |
| if (pwg->width < 21000 && pwg->length < 21000) |
| { |
| source = "photo"; /* Photo size media from photo tray */ |
| bottom = /* Borderless margins */ |
| left = |
| right = |
| top = 0; |
| } |
| else if (pwg->width < 21000) |
| { |
| source = "by-pass-tray"; /* Envelopes from multi-purpose tray */ |
| bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; |
| left = /* Left/right margins are standard */ |
| right = media_lr_margin_supported[1]; |
| top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; |
| } |
| else if (pwg->width == 21000) |
| { |
| source = NULL; /* A4 from any tray */ |
| bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; |
| left = /* Left/right margins are reduced */ |
| right = media_lr_margin_supported[0]; |
| top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; |
| } |
| else |
| { |
| source = NULL; /* Other size media from any tray */ |
| bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; |
| left = /* Left/right margins are standard */ |
| right = media_lr_margin_supported[1]; |
| top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; |
| } |
| |
| col = create_media_col(media[i], source, NULL, pwg->width, pwg->length, bottom, left, right, top); |
| ippSetCollection(attrs, &attr, i, col); |
| |
| ippDelete(col); |
| } |
| |
| /* media-col-default */ |
| pwg = pwgMediaForPWG(ready[0]); |
| |
| if (pwg->width == 21000) |
| col = create_media_col(ready[0], "main", "stationery", pwg->width, pwg->length, ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0], media_lr_margin_supported[0], media_lr_margin_supported[0], ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]); |
| else |
| col = create_media_col(ready[0], "main", "stationery", pwg->width, pwg->length, ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0], media_lr_margin_supported[1], media_lr_margin_supported[1], ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]); |
| |
| ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-default", col); |
| |
| ippDelete(col); |
| |
| /* media-col-ready */ |
| attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-col-ready", num_ready, NULL); |
| for (i = 0; i < num_ready; i ++) |
| { |
| int bottom, left, /* media-xxx-margins */ |
| right, top; |
| const char *source, /* media-source */ |
| *type; /* media-type */ |
| |
| pwg = pwgMediaForPWG(ready[i]); |
| |
| if (pwg->width < 21000 && pwg->length < 21000) |
| { |
| source = "photo"; /* Photo size media from photo tray */ |
| type = "photographic-glossy"; /* Glossy photo paper */ |
| bottom = /* Borderless margins */ |
| left = |
| right = |
| top = 0; |
| } |
| else if (pwg->width < 21000) |
| { |
| source = "by-pass-tray"; /* Envelopes from multi-purpose tray */ |
| type = "envelope"; /* Envelope */ |
| bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; |
| left = /* Left/right margins are standard */ |
| right = media_lr_margin_supported[1]; |
| top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; |
| } |
| else if (pwg->width == 21000) |
| { |
| source = "main"; /* A4 from main tray */ |
| type = "stationery"; /* Plain paper */ |
| bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; |
| left = /* Left/right margins are reduced */ |
| right = media_lr_margin_supported[0]; |
| top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; |
| } |
| else |
| { |
| source = "main"; /* A4 from main tray */ |
| type = "stationery"; /* Plain paper */ |
| bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; |
| left = /* Left/right margins are standard */ |
| right = media_lr_margin_supported[1]; |
| top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; |
| } |
| |
| col = create_media_col(ready[i], source, type, pwg->width, pwg->length, bottom, left, right, top); |
| ippSetCollection(attrs, &attr, i, col); |
| ippDelete(col); |
| } |
| |
| /* media-default */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-default", NULL, media[0]); |
| |
| /* media-left/right-margin-supported */ |
| if (ppm_color > 0) |
| { |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", (int)(sizeof(media_lr_margin_supported_color) / sizeof(media_lr_margin_supported_color[0])), media_lr_margin_supported_color); |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", (int)(sizeof(media_lr_margin_supported_color) / sizeof(media_lr_margin_supported_color[0])), media_lr_margin_supported_color); |
| } |
| else |
| { |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", (int)(sizeof(media_lr_margin_supported) / sizeof(media_lr_margin_supported[0])), media_lr_margin_supported); |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", (int)(sizeof(media_lr_margin_supported) / sizeof(media_lr_margin_supported[0])), media_lr_margin_supported); |
| } |
| |
| /* media-ready */ |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", num_ready, NULL, ready); |
| |
| /* media-supported */ |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-supported", num_media, NULL, media); |
| |
| /* media-size-supported */ |
| attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-size-supported", num_media, NULL); |
| for (i = 0; i < num_media; i ++) |
| { |
| pwg = pwgMediaForPWG(media[i]); |
| col = create_media_size(pwg->width, pwg->length); |
| |
| ippSetCollection(attrs, &attr, i, col); |
| ippDelete(col); |
| } |
| |
| /* media-source-supported */ |
| if (ppm_color > 0) |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", (int)(sizeof(media_source_supported_color) / sizeof(media_source_supported_color[0])), NULL, media_source_supported_color); |
| else |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", (int)(sizeof(media_source_supported) / sizeof(media_source_supported[0])), NULL, media_source_supported); |
| |
| /* media-top-margin-supported */ |
| if (ppm_color > 0) |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", (int)(sizeof(media_top_margin_supported) / sizeof(media_top_margin_supported[0])), media_top_margin_supported); |
| else |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", (int)(sizeof(media_top_margin_supported_color) / sizeof(media_top_margin_supported_color[0])), media_top_margin_supported_color); |
| |
| /* media-type-supported */ |
| if (ppm_color > 0) |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", (int)(sizeof(media_type_supported_color) / sizeof(media_type_supported_color[0])), NULL, media_type_supported_color); |
| else |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", (int)(sizeof(media_type_supported) / sizeof(media_type_supported[0])), NULL, media_type_supported); |
| |
| /* orientation-requested-default */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-default", IPP_ORIENT_PORTRAIT); |
| |
| /* orientation-requested-supported */ |
| if (cupsArrayFind(docformats, (void *)"application/pdf") || cupsArrayFind(docformats, (void *)"image/jpeg")) |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", (int)(sizeof(orientation_requested_supported) / sizeof(orientation_requested_supported[0])), orientation_requested_supported); |
| else |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", IPP_ORIENT_PORTRAIT); |
| |
| /* output-bin-default */ |
| if (ppm_color > 0) |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-up"); |
| else |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-down"); |
| |
| /* output-bin-supported */ |
| if (ppm_color > 0) |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-up"); |
| else |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-down"); |
| |
| /* overrides-supported */ |
| if (cupsArrayFind(docformats, (void *)"application/pdf")) |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "overrides-supported", (int)(sizeof(overrides_supported) / sizeof(overrides_supported[0])), NULL, overrides_supported); |
| |
| /* page-ranges-supported */ |
| ippAddBoolean(attrs, IPP_TAG_PRINTER, "page-ranges-supported", cupsArrayFind(docformats, (void *)"application/pdf") != NULL); |
| |
| /* pages-per-minute */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute", ppm); |
| |
| /* pages-per-minute-color */ |
| if (ppm_color > 0) |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute-color", ppm_color); |
| |
| /* print-color-mode-default */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-default", NULL, ppm_color > 0 ? "auto" : "monochrome"); |
| |
| /* print-color-mode-supported */ |
| if (ppm_color > 0) |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported_color) / sizeof(print_color_mode_supported_color[0])), NULL, print_color_mode_supported_color); |
| else |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported) / sizeof(print_color_mode_supported[0])), NULL, print_color_mode_supported); |
| |
| /* print-content-optimize-default */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-default", NULL, "auto"); |
| |
| /* print-content-optimize-supported */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-supported", NULL, "auto"); |
| |
| /* print-quality-default */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default", IPP_QUALITY_NORMAL); |
| |
| /* print-quality-supported */ |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-supported", (int)(sizeof(print_quality_supported) / sizeof(print_quality_supported[0])), print_quality_supported); |
| |
| /* print-rendering-intent-default */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-default", NULL, "auto"); |
| |
| /* print-rendering-intent-supported */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-supported", NULL, "auto"); |
| |
| /* printer-device-id */ |
| snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;", make, model); |
| ptr = device_id + strlen(device_id); |
| prefix = "CMD:"; |
| for (format = (const char *)cupsArrayFirst(docformats); format; format = (const char *)cupsArrayNext(docformats)) |
| { |
| if (!strcasecmp(format, "application/pdf")) |
| snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPDF", prefix); |
| else if (!strcasecmp(format, "application/postscript")) |
| snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPS", prefix); |
| else if (!strcasecmp(format, "application/vnd.hp-PCL")) |
| snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPCL", prefix); |
| else if (!strcasecmp(format, "image/jpeg")) |
| snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sJPEG", prefix); |
| else if (!strcasecmp(format, "image/png")) |
| snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPNG", prefix); |
| else if (!strcasecmp(format, "image/pwg-raster")) |
| snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPWG", prefix); |
| else if (!strcasecmp(format, "image/urf")) |
| snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sURF", prefix); |
| else |
| continue; |
| |
| ptr += strlen(ptr); |
| prefix = ","; |
| } |
| if (ptr < (device_id + sizeof(device_id) - 1)) |
| { |
| *ptr++ = ';'; |
| *ptr = '\0'; |
| } |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, device_id); |
| |
| /* printer-input-tray */ |
| if (ppm_color > 0) |
| { |
| attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray_color[0], (int)strlen(printer_input_tray_color[0])); |
| for (i = 1; i < (int)(sizeof(printer_input_tray_color) / sizeof(printer_input_tray_color[0])); i ++) |
| ippSetOctetString(attrs, &attr, i, printer_input_tray_color[i], (int)strlen(printer_input_tray_color[i])); |
| } |
| else |
| { |
| attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray[0], (int)strlen(printer_input_tray[0])); |
| for (i = 1; i < (int)(sizeof(printer_input_tray) / sizeof(printer_input_tray[0])); i ++) |
| ippSetOctetString(attrs, &attr, i, printer_input_tray[i], (int)strlen(printer_input_tray[i])); |
| } |
| |
| /* printer-make-and-model */ |
| snprintf(make_model, sizeof(make_model), "%s %s", make, model); |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model", NULL, make_model); |
| |
| /* printer-resolution-default */ |
| ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, 600, 600); |
| |
| /* printer-resolution-supported */ |
| ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-supported", IPP_RES_PER_INCH, 600, 600); |
| |
| /* printer-supply and printer-supply-description */ |
| if (ppm_color > 0) |
| { |
| attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply_color[0], (int)strlen(printer_supply_color[0])); |
| for (i = 1; i < (int)(sizeof(printer_supply_color) / sizeof(printer_supply_color[0])); i ++) |
| ippSetOctetString(attrs, &attr, i, printer_supply_color[i], (int)strlen(printer_supply_color[i])); |
| |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description_color) / sizeof(printer_supply_description_color[0])), NULL, printer_supply_description_color); |
| } |
| else |
| { |
| attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply[0], (int)strlen(printer_supply[0])); |
| for (i = 1; i < (int)(sizeof(printer_supply) / sizeof(printer_supply[0])); i ++) |
| ippSetOctetString(attrs, &attr, i, printer_supply[i], (int)strlen(printer_supply[i])); |
| |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description) / sizeof(printer_supply_description[0])), NULL, printer_supply_description); |
| } |
| |
| /* pwg-raster-document-xxx-supported */ |
| if (cupsArrayFind(docformats, (void *)"image/pwg-raster")) |
| { |
| ippAddResolutions(attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", (int)(sizeof(pwg_raster_document_resolution_supported) / sizeof(pwg_raster_document_resolution_supported[0])), IPP_RES_PER_INCH, pwg_raster_document_resolution_supported, pwg_raster_document_resolution_supported); |
| |
| if (ppm_color > 0 && duplex) |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back", NULL, "rotated"); |
| else if (duplex) |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back", NULL, "normal"); |
| |
| if (ppm_color > 0) |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported_color) / sizeof(pwg_raster_document_type_supported_color[0])), NULL, pwg_raster_document_type_supported_color); |
| else |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported) / sizeof(pwg_raster_document_type_supported[0])), NULL, pwg_raster_document_type_supported); |
| } |
| |
| /* sides-default */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-default", NULL, "one-sided"); |
| |
| /* sides-supported */ |
| if (duplex) |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", (int)(sizeof(sides_supported) / sizeof(sides_supported[0])), NULL, sides_supported); |
| else |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", NULL, "one-sided"); |
| |
| /* urf-supported */ |
| if (cupsArrayFind(docformats, (void *)"image/urf")) |
| { |
| if (ppm_color > 0) |
| { |
| if (duplex) |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported_color_duplex) / sizeof(urf_supported_color_duplex[0])), NULL, urf_supported_color_duplex); |
| else |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported_color) / sizeof(urf_supported_color[0])), NULL, urf_supported_color); |
| } |
| else if (duplex) |
| { |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported_duplex) / sizeof(urf_supported_duplex[0])), NULL, urf_supported_duplex); |
| } |
| else |
| { |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported) / sizeof(urf_supported[0])), NULL, urf_supported); |
| } |
| } |
| |
| return (attrs); |
| } |
| |
| |
| #if !CUPS_LITE |
| /* |
| * 'load_ppd_attributes()' - Load IPP attributes from a PPD file. |
| */ |
| |
| static ipp_t * /* O - IPP attributes or `NULL` on error */ |
| load_ppd_attributes( |
| const char *ppdfile, /* I - PPD filename */ |
| cups_array_t *docformats) /* I - document-format-supported values */ |
| { |
| int i, j; /* Looping vars */ |
| ipp_t *attrs; /* Attributes */ |
| ipp_attribute_t *attr; /* Current attribute */ |
| ipp_t *col; /* Current collection value */ |
| ppd_file_t *ppd; /* PPD data */ |
| ppd_attr_t *ppd_attr; /* PPD attribute */ |
| ppd_choice_t *ppd_choice; /* PPD choice */ |
| ppd_size_t *ppd_size; /* Default PPD size */ |
| pwg_size_t *pwg_size, /* Current PWG size */ |
| *default_size = NULL; /* Default PWG size */ |
| const char *default_source = NULL, /* Default media source */ |
| *default_type = NULL; /* Default media type */ |
| pwg_map_t *pwg_map; /* Mapping from PWG to PPD keywords */ |
| _ppd_cache_t *pc; /* PPD cache */ |
| _pwg_finishings_t *finishings; /* Current finishings value */ |
| const char *template; /* Current finishings-template value */ |
| int num_margins; /* Number of media-xxx-margin-supported values */ |
| int margins[10]; /* media-xxx-margin-supported values */ |
| int xres, /* Default horizontal resolution */ |
| yres; /* Default vertical resolution */ |
| int num_urf; /* Number of urf-supported values */ |
| const char *urf[10]; /* urf-supported values */ |
| char urf_rs[32]; /* RS value */ |
| static const int orientation_requested_supported[4] = |
| { /* orientation-requested-supported values */ |
| IPP_ORIENT_PORTRAIT, |
| IPP_ORIENT_LANDSCAPE, |
| IPP_ORIENT_REVERSE_LANDSCAPE, |
| IPP_ORIENT_REVERSE_PORTRAIT |
| }; |
| static const char * const overrides_supported[] = |
| { /* overrides-supported */ |
| "document-numbers", |
| "media", |
| "media-col", |
| "orientation-requested", |
| "pages" |
| }; |
| static const char * const print_color_mode_supported[] = |
| { /* print-color-mode-supported values */ |
| "monochrome" |
| }; |
| static const char * const print_color_mode_supported_color[] = |
| { /* print-color-mode-supported values */ |
| "auto", |
| "color", |
| "monochrome" |
| }; |
| static const int print_quality_supported[] = |
| { /* print-quality-supported values */ |
| IPP_QUALITY_DRAFT, |
| IPP_QUALITY_NORMAL, |
| IPP_QUALITY_HIGH |
| }; |
| static const char * const printer_supply[] = |
| { /* printer-supply values */ |
| "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;" |
| "maxcapacity=100;level=25;colorantname=unknown;", |
| "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;" |
| "maxcapacity=100;level=75;colorantname=black;" |
| }; |
| static const char * const printer_supply_color[] = |
| { /* printer-supply values */ |
| "index=1;class=receptacleThatIsFilled;type=wasteInk;unit=percent;" |
| "maxcapacity=100;level=25;colorantname=unknown;", |
| "index=2;class=supplyThatIsConsumed;type=ink;unit=percent;" |
| "maxcapacity=100;level=75;colorantname=black;", |
| "index=3;class=supplyThatIsConsumed;type=ink;unit=percent;" |
| "maxcapacity=100;level=50;colorantname=cyan;", |
| "index=4;class=supplyThatIsConsumed;type=ink;unit=percent;" |
| "maxcapacity=100;level=33;colorantname=magenta;", |
| "index=5;class=supplyThatIsConsumed;type=ink;unit=percent;" |
| "maxcapacity=100;level=67;colorantname=yellow;" |
| }; |
| static const char * const printer_supply_description[] = |
| { /* printer-supply-description values */ |
| "Toner Waste Tank", |
| "Black Toner" |
| }; |
| static const char * const printer_supply_description_color[] = |
| { /* printer-supply-description values */ |
| "Ink Waste Tank", |
| "Black Ink", |
| "Cyan Ink", |
| "Magenta Ink", |
| "Yellow Ink" |
| }; |
| static const char * const pwg_raster_document_type_supported[] = |
| { |
| "black_1", |
| "sgray_8" |
| }; |
| static const char * const pwg_raster_document_type_supported_color[] = |
| { |
| "black_1", |
| "sgray_8", |
| "srgb_8", |
| "srgb_16" |
| }; |
| static const char * const sides_supported[] = |
| { /* sides-supported values */ |
| "one-sided", |
| "two-sided-long-edge", |
| "two-sided-short-edge" |
| }; |
| |
| |
| /* |
| * Open the PPD file... |
| */ |
| |
| if ((ppd = ppdOpenFile(ppdfile)) == NULL) |
| { |
| ppd_status_t status; /* Load error */ |
| |
| status = ppdLastError(&i); |
| _cupsLangPrintf(stderr, _("ippeveprinter: Unable to open \"%s\": %s on line %d."), ppdfile, ppdErrorString(status), i); |
| return (NULL); |
| } |
| |
| ppdMarkDefaults(ppd); |
| |
| pc = _ppdCacheCreateWithPPD(ppd); |
| |
| if ((ppd_size = ppdPageSize(ppd, NULL)) != NULL) |
| { |
| /* |
| * Look up default size... |
| */ |
| |
| for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++) |
| { |
| if (!strcmp(pwg_size->map.ppd, ppd_size->name)) |
| { |
| default_size = pwg_size; |
| break; |
| } |
| } |
| } |
| |
| if (!default_size) |
| { |
| /* |
| * Default to A4 or Letter... |
| */ |
| |
| for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++) |
| { |
| if (!strcmp(pwg_size->map.ppd, "Letter") || !strcmp(pwg_size->map.ppd, "A4")) |
| { |
| default_size = pwg_size; |
| break; |
| } |
| } |
| |
| if (!default_size) |
| default_size = pc->sizes; /* Last resort: first size */ |
| } |
| |
| if ((ppd_choice = ppdFindMarkedChoice(ppd, "InputSlot")) != NULL) |
| default_source = _ppdCacheGetSource(pc, ppd_choice->choice); |
| |
| if ((ppd_choice = ppdFindMarkedChoice(ppd, "MediaType")) != NULL) |
| default_source = _ppdCacheGetType(pc, ppd_choice->choice); |
| |
| if ((ppd_attr = ppdFindAttr(ppd, "DefaultResolution", NULL)) != NULL) |
| { |
| /* |
| * Use the PPD-defined default resolution... |
| */ |
| |
| if ((i = sscanf(ppd_attr->value, "%dx%d", &xres, &yres)) == 1) |
| yres = xres; |
| else if (i < 0) |
| xres = yres = 300; |
| } |
| else |
| { |
| /* |
| * Use default of 300dpi... |
| */ |
| |
| xres = yres = 300; |
| } |
| |
| snprintf(urf_rs, sizeof(urf_rs), "RS%d", yres < xres ? yres : xres); |
| |
| num_urf = 0; |
| urf[num_urf ++] = "V1.4"; |
| urf[num_urf ++] = "CP1"; |
| urf[num_urf ++] = urf_rs; |
| urf[num_urf ++] = "W8"; |
| if (pc->sides_2sided_long) |
| urf[num_urf ++] = "DM1"; |
| if (ppd->color_device) |
| urf[num_urf ++] = "SRGB24"; |
| |
| /* |
| * PostScript printers accept PDF via one of the CUPS PDF to PostScript |
| * filters, along with PostScript (of course) and JPEG... |
| */ |
| |
| cupsArrayAdd(docformats, "application/pdf"); |
| cupsArrayAdd(docformats, "application/postscript"); |
| cupsArrayAdd(docformats, "image/jpeg"); |
| |
| /* |
| * Create the attributes... |
| */ |
| |
| attrs = ippNew(); |
| |
| /* color-supported */ |
| ippAddBoolean(attrs, IPP_TAG_PRINTER, "color-supported", (char)ppd->color_device); |
| |
| /* copies-default */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default", 1); |
| |
| /* copies-supported */ |
| ippAddRange(attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999); |
| |
| /* document-password-supported */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 127); |
| |
| /* finishing-template-supported */ |
| attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template-supported", cupsArrayCount(pc->templates) + 1, NULL, NULL); |
| ippSetString(attrs, &attr, 0, "none"); |
| for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates)) |
| ippSetString(attrs, &attr, i, template); |
| |
| /* finishings-col-database */ |
| attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "finishings-col-database", cupsArrayCount(pc->templates) + 1, NULL); |
| |
| col = ippNew(); |
| ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none"); |
| ippSetCollection(attrs, &attr, 0, col); |
| ippDelete(col); |
| |
| for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates)) |
| { |
| col = ippNew(); |
| ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, template); |
| ippSetCollection(attrs, &attr, i, col); |
| ippDelete(col); |
| } |
| |
| /* finishings-col-default */ |
| col = ippNew(); |
| ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none"); |
| ippAddCollection(attrs, IPP_TAG_PRINTER, "finishings-col-default", col); |
| ippDelete(col); |
| |
| /* finishings-col-ready */ |
| attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "finishings-col-ready", cupsArrayCount(pc->templates) + 1, NULL); |
| |
| col = ippNew(); |
| ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none"); |
| ippSetCollection(attrs, &attr, 0, col); |
| ippDelete(col); |
| |
| for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates)) |
| { |
| col = ippNew(); |
| ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, template); |
| ippSetCollection(attrs, &attr, i, col); |
| ippDelete(col); |
| } |
| |
| /* finishings-col-supported */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishings-col-supported", NULL, "finishing-template"); |
| |
| /* finishings-default */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-default", IPP_FINISHINGS_NONE); |
| |
| /* finishings-ready */ |
| attr = ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-ready", cupsArrayCount(pc->finishings) + 1, NULL); |
| ippSetInteger(attrs, &attr, 0, IPP_FINISHINGS_NONE); |
| for (i = 1, finishings = (_pwg_finishings_t *)cupsArrayFirst(pc->finishings); finishings; i ++, finishings = (_pwg_finishings_t *)cupsArrayNext(pc->finishings)) |
| ippSetInteger(attrs, &attr, i, (int)finishings->value); |
| |
| /* finishings-supported */ |
| attr = ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-supported", cupsArrayCount(pc->finishings) + 1, NULL); |
| ippSetInteger(attrs, &attr, 0, IPP_FINISHINGS_NONE); |
| for (i = 1, finishings = (_pwg_finishings_t *)cupsArrayFirst(pc->finishings); finishings; i ++, finishings = (_pwg_finishings_t *)cupsArrayNext(pc->finishings)) |
| ippSetInteger(attrs, &attr, i, (int)finishings->value); |
| |
| /* media-bottom-margin-supported */ |
| for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++) |
| { |
| for (j = 0; j < num_margins; j ++) |
| { |
| if (margins[j] == pwg_size->bottom) |
| break; |
| } |
| |
| if (j >= num_margins) |
| margins[num_margins ++] = pwg_size->bottom; |
| } |
| |
| for (i = 0; i < (num_margins - 1); i ++) |
| { |
| for (j = i + 1; j < num_margins; j ++) |
| { |
| if (margins[i] > margins[j]) |
| { |
| int mtemp = margins[i]; |
| |
| margins[i] = margins[j]; |
| margins[j] = mtemp; |
| } |
| } |
| } |
| |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", num_margins, margins); |
| |
| /* media-col-database */ |
| attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-col-database", pc->num_sizes, NULL); |
| for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++) |
| { |
| col = create_media_col(pwg_size->map.pwg, NULL, NULL, pwg_size->width, pwg_size->length, pwg_size->bottom, pwg_size->left, pwg_size->right, pwg_size->top); |
| ippSetCollection(attrs, &attr, i, col); |
| ippDelete(col); |
| } |
| |
| /* media-col-default */ |
| col = create_media_col(default_size->map.pwg, default_source, default_type, default_size->width, default_size->length, default_size->bottom, default_size->left, default_size->right, default_size->top); |
| ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-default", col); |
| ippDelete(col); |
| |
| /* media-col-ready */ |
| col = create_media_col(default_size->map.pwg, default_source, default_type, default_size->width, default_size->length, default_size->bottom, default_size->left, default_size->right, default_size->top); |
| ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-ready", col); |
| ippDelete(col); |
| |
| /* media-default */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-default", NULL, default_size->map.pwg); |
| |
| /* media-left-margin-supported */ |
| for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++) |
| { |
| for (j = 0; j < num_margins; j ++) |
| { |
| if (margins[j] == pwg_size->left) |
| break; |
| } |
| |
| if (j >= num_margins) |
| margins[num_margins ++] = pwg_size->left; |
| } |
| |
| for (i = 0; i < (num_margins - 1); i ++) |
| { |
| for (j = i + 1; j < num_margins; j ++) |
| { |
| if (margins[i] > margins[j]) |
| { |
| int mtemp = margins[i]; |
| |
| margins[i] = margins[j]; |
| margins[j] = mtemp; |
| } |
| } |
| } |
| |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", num_margins, margins); |
| |
| /* media-ready */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", NULL, default_size->map.pwg); |
| |
| /* media-right-margin-supported */ |
| for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++) |
| { |
| for (j = 0; j < num_margins; j ++) |
| { |
| if (margins[j] == pwg_size->right) |
| break; |
| } |
| |
| if (j >= num_margins) |
| margins[num_margins ++] = pwg_size->right; |
| } |
| |
| for (i = 0; i < (num_margins - 1); i ++) |
| { |
| for (j = i + 1; j < num_margins; j ++) |
| { |
| if (margins[i] > margins[j]) |
| { |
| int mtemp = margins[i]; |
| |
| margins[i] = margins[j]; |
| margins[j] = mtemp; |
| } |
| } |
| } |
| |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", num_margins, margins); |
| |
| /* media-supported */ |
| attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-supported", pc->num_sizes, NULL, NULL); |
| for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++) |
| ippSetString(attrs, &attr, i, pwg_size->map.pwg); |
| |
| /* media-size-supported */ |
| attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-size-supported", pc->num_sizes, NULL); |
| for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++) |
| { |
| col = create_media_size(pwg_size->width, pwg_size->length); |
| ippSetCollection(attrs, &attr, i, col); |
| ippDelete(col); |
| } |
| |
| /* media-source-supported */ |
| if (pc->num_sources > 0) |
| { |
| attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source-supported", pc->num_sources, NULL, NULL); |
| for (i = 0, pwg_map = pc->sources; i < pc->num_sources; i ++, pwg_map ++) |
| ippSetString(attrs, &attr, i, pwg_map->pwg); |
| } |
| else |
| { |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", NULL, "auto"); |
| } |
| |
| /* media-top-margin-supported */ |
| for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++) |
| { |
| for (j = 0; j < num_margins; j ++) |
| { |
| if (margins[j] == pwg_size->top) |
| break; |
| } |
| |
| if (j >= num_margins) |
| margins[num_margins ++] = pwg_size->top; |
| } |
| |
| for (i = 0; i < (num_margins - 1); i ++) |
| { |
| for (j = i + 1; j < num_margins; j ++) |
| { |
| if (margins[i] > margins[j]) |
| { |
| int mtemp = margins[i]; |
| |
| margins[i] = margins[j]; |
| margins[j] = mtemp; |
| } |
| } |
| } |
| |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", num_margins, margins); |
| |
| /* media-type-supported */ |
| if (pc->num_types > 0) |
| { |
| attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type-supported", pc->num_types, NULL, NULL); |
| for (i = 0, pwg_map = pc->types; i < pc->num_types; i ++, pwg_map ++) |
| ippSetString(attrs, &attr, i, pwg_map->pwg); |
| } |
| else |
| { |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", NULL, "auto"); |
| } |
| |
| /* orientation-requested-default */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-default", IPP_ORIENT_PORTRAIT); |
| |
| /* orientation-requested-supported */ |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", (int)(sizeof(orientation_requested_supported) / sizeof(orientation_requested_supported[0])), orientation_requested_supported); |
| |
| /* output-bin-default */ |
| if (pc->num_bins > 0) |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-default", NULL, pc->bins->pwg); |
| else |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-down"); |
| |
| /* output-bin-supported */ |
| if (pc->num_bins > 0) |
| { |
| attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-supported", pc->num_bins, NULL, NULL); |
| for (i = 0, pwg_map = pc->bins; i < pc->num_bins; i ++, pwg_map ++) |
| ippSetString(attrs, &attr, i, pwg_map->pwg); |
| } |
| else |
| { |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-down"); |
| } |
| |
| /* overrides-supported */ |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "overrides-supported", (int)(sizeof(overrides_supported) / sizeof(overrides_supported[0])), NULL, overrides_supported); |
| |
| /* page-ranges-supported */ |
| ippAddBoolean(attrs, IPP_TAG_PRINTER, "page-ranges-supported", 1); |
| |
| /* pages-per-minute */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute", ppd->throughput); |
| |
| /* pages-per-minute-color */ |
| if (ppd->color_device) |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute-color", ppd->throughput); |
| |
| /* print-color-mode-default */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-default", NULL, ppd->color_device ? "auto" : "monochrome"); |
| |
| /* print-color-mode-supported */ |
| if (ppd->color_device) |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported_color) / sizeof(print_color_mode_supported_color[0])), NULL, print_color_mode_supported_color); |
| else |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported) / sizeof(print_color_mode_supported[0])), NULL, print_color_mode_supported); |
| |
| /* print-content-optimize-default */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-default", NULL, "auto"); |
| |
| /* print-content-optimize-supported */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-supported", NULL, "auto"); |
| |
| /* print-quality-default */ |
| ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default", IPP_QUALITY_NORMAL); |
| |
| /* print-quality-supported */ |
| ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-supported", (int)(sizeof(print_quality_supported) / sizeof(print_quality_supported[0])), print_quality_supported); |
| |
| /* print-rendering-intent-default */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-default", NULL, "auto"); |
| |
| /* print-rendering-intent-supported */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-supported", NULL, "auto"); |
| |
| /* printer-device-id */ |
| if ((ppd_attr = ppdFindAttr(ppd, "1284DeviceId", NULL)) != NULL) |
| { |
| /* |
| * Use the device ID string from the PPD... |
| */ |
| |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, ppd_attr->value); |
| } |
| else |
| { |
| /* |
| * Synthesize a device ID string... |
| */ |
| |
| char device_id[1024]; /* Device ID string */ |
| |
| snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;CMD:PS;", ppd->manufacturer, ppd->modelname); |
| |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, device_id); |
| } |
| |
| /* printer-input-tray */ |
| if (pc->num_sources > 0) |
| { |
| for (i = 0, attr = NULL; i < pc->num_sources; i ++) |
| { |
| char input_tray[1024]; /* printer-input-tray value */ |
| |
| if (!strcmp(pc->sources[i].pwg, "manual") || strstr(pc->sources[i].pwg, "-man") != NULL) |
| snprintf(input_tray, sizeof(input_tray), "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=%s", pc->sources[i].pwg); |
| else |
| snprintf(input_tray, sizeof(input_tray), "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=125;status=0;name=%s", pc->sources[i].pwg); |
| |
| if (attr) |
| ippSetOctetString(attrs, &attr, i, input_tray, (int)strlen(input_tray)); |
| else |
| attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", input_tray, (int)strlen(input_tray)); |
| } |
| } |
| else |
| { |
| static const char *printer_input_tray = "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto"; |
| |
| ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray, (int)strlen(printer_input_tray)); |
| } |
| |
| /* printer-make-and-model */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model", NULL, ppd->nickname); |
| |
| /* printer-resolution-default */ |
| ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, xres, yres); |
| |
| /* printer-resolution-supported */ |
| ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-supported", IPP_RES_PER_INCH, xres, yres); |
| |
| /* printer-supply and printer-supply-description */ |
| if (ppd->color_device) |
| { |
| attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply_color[0], (int)strlen(printer_supply_color[0])); |
| for (i = 1; i < (int)(sizeof(printer_supply_color) / sizeof(printer_supply_color[0])); i ++) |
| ippSetOctetString(attrs, &attr, i, printer_supply_color[i], (int)strlen(printer_supply_color[i])); |
| |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description_color) / sizeof(printer_supply_description_color[0])), NULL, printer_supply_description_color); |
| } |
| else |
| { |
| attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply[0], (int)strlen(printer_supply[0])); |
| for (i = 1; i < (int)(sizeof(printer_supply) / sizeof(printer_supply[0])); i ++) |
| ippSetOctetString(attrs, &attr, i, printer_supply[i], (int)strlen(printer_supply[i])); |
| |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description) / sizeof(printer_supply_description[0])), NULL, printer_supply_description); |
| } |
| |
| /* pwg-raster-document-xxx-supported */ |
| if (cupsArrayFind(docformats, (void *)"image/pwg-raster")) |
| { |
| ippAddResolution(attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", IPP_RES_PER_INCH, xres, yres); |
| |
| if (pc->sides_2sided_long) |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back", NULL, "normal"); |
| |
| if (ppd->color_device) |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported_color) / sizeof(pwg_raster_document_type_supported_color[0])), NULL, pwg_raster_document_type_supported_color); |
| else |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported) / sizeof(pwg_raster_document_type_supported[0])), NULL, pwg_raster_document_type_supported); |
| } |
| |
| /* sides-default */ |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-default", NULL, "one-sided"); |
| |
| /* sides-supported */ |
| if (pc->sides_2sided_long) |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", (int)(sizeof(sides_supported) / sizeof(sides_supported[0])), NULL, sides_supported); |
| else |
| ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", NULL, "one-sided"); |
| |
| /* urf-supported */ |
| if (cupsArrayFind(docformats, (void *)"image/urf")) |
| ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", num_urf, NULL, urf); |
| |
| /* |
| * Free the PPD file and return the attributes... |
| */ |
| |
| _ppdCacheDestroy(pc); |
| |
| ppdClose(ppd); |
| |
| return (attrs); |
| } |
| #endif /* !CUPS_LITE */ |
| |
| |
| #if HAVE_LIBPAM |
| /* |
| * 'pam_func()' - PAM conversation function. |
| */ |
| |
| static int /* O - Success or failure */ |
| pam_func( |
| int num_msg, /* I - Number of messages */ |
| const struct pam_message **msg, /* I - Messages */ |
| struct pam_response **resp, /* O - Responses */ |
| void *appdata_ptr) |
| /* I - Pointer to connection */ |
| { |
| int i; /* Looping var */ |
| struct pam_response *replies; /* Replies */ |
| ippeve_authdata_t *data; /* Pointer to auth data */ |
| |
| |
| /* |
| * Allocate memory for the responses... |
| */ |
| |
| if ((replies = malloc(sizeof(struct pam_response) * (size_t)num_msg)) == NULL) |
| return (PAM_CONV_ERR); |
| |
| /* |
| * Answer all of the messages... |
| */ |
| |
| data = (ippeve_authdata_t *)appdata_ptr; |
| |
| for (i = 0; i < num_msg; i ++) |
| { |
| switch (msg[i]->msg_style) |
| { |
| case PAM_PROMPT_ECHO_ON: |
| replies[i].resp_retcode = PAM_SUCCESS; |
| replies[i].resp = strdup(data->username); |
| break; |
| |
| case PAM_PROMPT_ECHO_OFF: |
| replies[i].resp_retcode = PAM_SUCCESS; |
| replies[i].resp = strdup(data->password); |
| break; |
| |
| case PAM_TEXT_INFO: |
| replies[i].resp_retcode = PAM_SUCCESS; |
| replies[i].resp = NULL; |
| break; |
| |
| case PAM_ERROR_MSG: |
| replies[i].resp_retcode = PAM_SUCCESS; |
| replies[i].resp = NULL; |
| break; |
| |
| default: |
| free(replies); |
| return (PAM_CONV_ERR); |
| } |
| } |
| |
| /* |
| * Return the responses back to PAM... |
| */ |
| |
| *resp = replies; |
| |
| return (PAM_SUCCESS); |
| } |
| #endif /* HAVE_LIBPAM */ |
| |
| |
| /* |
| * 'parse_options()' - Parse URL options into CUPS options. |
| * |
| * The client->options string is destroyed by this function. |
| */ |
| |
| static int /* O - Number of options */ |
| parse_options(ippeve_client_t *client, /* I - Client */ |
| cups_option_t **options)/* O - Options */ |
| { |
| char *name, /* Name */ |
| *value, /* Value */ |
| *next; /* Next name=value pair */ |
| int num_options = 0; /* Number of options */ |
| |
| |
| *options = NULL; |
| |
| for (name = client->options; name && *name; name = next) |
| { |
| if ((value = strchr(name, '=')) == NULL) |
| break; |
| |
| *value++ = '\0'; |
| if ((next = strchr(value, '&')) != NULL) |
| *next++ = '\0'; |
| |
| num_options = cupsAddOption(name, value, num_options, options); |
| } |
| |
| return (num_options); |
| } |
| |
| |
| /* |
| * 'process_attr_message()' - Process an ATTR: message from a command. |
| */ |
| |
| static void |
| process_attr_message( |
| ippeve_job_t *job, /* I - Job */ |
| char *message) /* I - Message */ |
| { |
| int i, /* Looping var */ |
| num_options = 0; /* Number of name=value pairs */ |
| cups_option_t *options = NULL, /* name=value pairs from message */ |
| *option; /* Current option */ |
| ipp_attribute_t *attr; /* Current attribute */ |
| |
| |
| /* |
| * Grab attributes from the message line... |
| */ |
| |
| num_options = cupsParseOptions(message + 5, num_options, &options); |
| |
| /* |
| * Loop through the options and record them in the printer or job objects... |
| */ |
| |
| for (i = num_options, option = options; i > 0; i --, option ++) |
| { |
| if (!strcmp(option->name, "job-impressions")) |
| { |
| /* |
| * Update job-impressions attribute... |
| */ |
| |
| job->impressions = atoi(option->value); |
| } |
| else if (!strcmp(option->name, "job-impressions-completed")) |
| { |
| /* |
| * Update job-impressions-completed attribute... |
| */ |
| |
| job->impcompleted = atoi(option->value); |
| } |
| else if (!strncmp(option->name, "marker-", 7) || !strcmp(option->name, "printer-alert") || !strcmp(option->name, "printer-alert-description") || !strcmp(option->name, "printer-supply") || !strcmp(option->name, "printer-supply-description")) |
| { |
| /* |
| * Update Printer Status attribute... |
| */ |
| |
| _cupsRWLockWrite(&job->printer->rwlock); |
| |
| if ((attr = ippFindAttribute(job->printer->attrs, option->name, IPP_TAG_ZERO)) != NULL) |
| ippDeleteAttribute(job->printer->attrs, attr); |
| |
| cupsEncodeOption(job->printer->attrs, IPP_TAG_PRINTER, option->name, option->value); |
| |
| _cupsRWUnlock(&job->printer->rwlock); |
| } |
| else |
| { |
| /* |
| * Something else that isn't currently supported... |
| */ |
| |
| fprintf(stderr, "[Job %d] Ignoring update of attribute \"%s\" with value \"%s\".\n", job->id, option->name, option->value); |
| } |
| } |
| |
| cupsFreeOptions(num_options, options); |
| } |
| |
| |
| /* |
| * 'process_client()' - Process client requests on a thread. |
| */ |
| |
| static void * /* O - Exit status */ |
| process_client(ippeve_client_t *client) /* I - Client */ |
| { |
| /* |
| * Loop until we are out of requests or timeout (30 seconds)... |
| */ |
| |
| #ifdef HAVE_SSL |
| int first_time = 1; /* First time request? */ |
| #endif /* HAVE_SSL */ |
| |
| while (httpWait(client->http, 30000)) |
| { |
| #ifdef HAVE_SSL |
| if (first_time) |
| { |
| /* |
| * See if we need to negotiate a TLS connection... |
| */ |
| |
| char buf[1]; /* First byte from client */ |
| |
| if (recv(httpGetFd(client->http), buf, 1, MSG_PEEK) == 1 && (!buf[0] || !strchr("DGHOPT", buf[0]))) |
| { |
| fprintf(stderr, "%s Starting HTTPS session.\n", client->hostname); |
| |
| if (httpEncryption(client->http, HTTP_ENCRYPTION_ALWAYS)) |
| { |
| fprintf(stderr, "%s Unable to encrypt connection: %s\n", client->hostname, cupsLastErrorString()); |
| break; |
| } |
| |
| fprintf(stderr, "%s Connection now encrypted.\n", client->hostname); |
| } |
| |
| first_time = 0; |
| } |
| #endif /* HAVE_SSL */ |
| |
| if (!process_http(client)) |
| break; |
| } |
| |
| /* |
| * Close the conection to the client and return... |
| */ |
| |
| delete_client(client); |
| |
| return (NULL); |
| } |
| |
| |
| /* |
| * 'process_http()' - Process a HTTP request. |
| */ |
| |
| int /* O - 1 on success, 0 on failure */ |
| process_http(ippeve_client_t *client) /* I - Client connection */ |
| { |
| char uri[1024]; /* URI */ |
| http_state_t http_state; /* HTTP state */ |
| http_status_t http_status; /* HTTP status */ |
| ipp_state_t ipp_state; /* State of IPP transfer */ |
| char scheme[32], /* Method/scheme */ |
| userpass[128], /* Username:password */ |
| hostname[HTTP_MAX_HOST]; |
| /* Hostname */ |
| int port; /* Port number */ |
| static const char * const http_states[] = |
| { /* Strings for logging HTTP method */ |
| "WAITING", |
| "OPTIONS", |
| "GET", |
| "GET_SEND", |
| "HEAD", |
| "POST", |
| "POST_RECV", |
| "POST_SEND", |
| "PUT", |
| "PUT_RECV", |
| "DELETE", |
| "TRACE", |
| "CONNECT", |
| "STATUS", |
| "UNKNOWN_METHOD", |
| "UNKNOWN_VERSION" |
| }; |
| |
| |
| /* |
| * Clear state variables... |
| */ |
| |
| client->username[0] = '\0'; |
| |
| ippDelete(client->request); |
| ippDelete(client->response); |
| |
| client->request = NULL; |
| client->response = NULL; |
| client->operation = HTTP_STATE_WAITING; |
| |
| /* |
| * Read a request from the connection... |
| */ |
| |
| while ((http_state = httpReadRequest(client->http, uri, |
| sizeof(uri))) == HTTP_STATE_WAITING) |
| usleep(1); |
| |
| /* |
| * Parse the request line... |
| */ |
| |
| if (http_state == HTTP_STATE_ERROR) |
| { |
| if (httpError(client->http) == EPIPE) |
| fprintf(stderr, "%s Client closed connection.\n", client->hostname); |
| else |
| fprintf(stderr, "%s Bad request line (%s).\n", client->hostname, strerror(httpError(client->http))); |
| |
| return (0); |
| } |
| else if (http_state == HTTP_STATE_UNKNOWN_METHOD) |
| { |
| fprintf(stderr, "%s Bad/unknown operation.\n", client->hostname); |
| respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); |
| return (0); |
| } |
| else if (http_state == HTTP_STATE_UNKNOWN_VERSION) |
| { |
| fprintf(stderr, "%s Bad HTTP version.\n", client->hostname); |
| respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); |
| return (0); |
| } |
| |
| fprintf(stderr, "%s %s %s\n", client->hostname, http_states[http_state], uri); |
| |
| /* |
| * Separate the URI into its components... |
| */ |
| |
| if (httpSeparateURI(HTTP_URI_CODING_MOST, uri, scheme, sizeof(scheme), |
| userpass, sizeof(userpass), |
| hostname, sizeof(hostname), &port, |
| client->uri, sizeof(client->uri)) < HTTP_URI_STATUS_OK && |
| (http_state != HTTP_STATE_OPTIONS || strcmp(uri, "*"))) |
| { |
| fprintf(stderr, "%s Bad URI \"%s\".\n", client->hostname, uri); |
| respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); |
| return (0); |
| } |
| |
| if ((client->options = strchr(client->uri, '?')) != NULL) |
| *(client->options)++ = '\0'; |
| |
| /* |
| * Process the request... |
| */ |
| |
| client->start = time(NULL); |
| client->operation = httpGetState(client->http); |
| |
| /* |
| * Parse incoming parameters until the status changes... |
| */ |
| |
| while ((http_status = httpUpdate(client->http)) == HTTP_STATUS_CONTINUE); |
| |
| if (http_status != HTTP_STATUS_OK) |
| { |
| respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); |
| return (0); |
| } |
| |
| if (!httpGetField(client->http, HTTP_FIELD_HOST)[0] && |
| httpGetVersion(client->http) >= HTTP_VERSION_1_1) |
| { |
| /* |
| * HTTP/1.1 and higher require the "Host:" field... |
| */ |
| |
| respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); |
| return (0); |
| } |
| |
| /* |
| * Handle HTTP Upgrade... |
| */ |
| |
| if (!strcasecmp(httpGetField(client->http, HTTP_FIELD_CONNECTION), "Upgrade")) |
| { |
| #ifdef HAVE_SSL |
| if (strstr(httpGetField(client->http, HTTP_FIELD_UPGRADE), "TLS/") != NULL && !httpIsEncrypted(client->http)) |
| { |
| if (!respond_http(client, HTTP_STATUS_SWITCHING_PROTOCOLS, NULL, NULL, 0)) |
| return (0); |
| |
| fprintf(stderr, "%s Upgrading to encrypted connection.\n", client->hostname); |
| |
| if (httpEncryption(client->http, HTTP_ENCRYPTION_REQUIRED)) |
| { |
| fprintf(stderr, "%s Unable to encrypt connection: %s\n", client->hostname, cupsLastErrorString()); |
| return (0); |
| } |
| |
| fprintf(stderr, "%s Connection now encrypted.\n", client->hostname); |
| } |
| else |
| #endif /* HAVE_SSL */ |
| |
| if (!respond_http(client, HTTP_STATUS_NOT_IMPLEMENTED, NULL, NULL, 0)) |
| return (0); |
| } |
| |
| /* |
| * Handle new transfers... |
| */ |
| |
| switch (client->operation) |
| { |
| case HTTP_STATE_OPTIONS : |
| /* |
| * Do OPTIONS command... |
| */ |
| |
| return (respond_http(client, HTTP_STATUS_OK, NULL, NULL, 0)); |
| |
| case HTTP_STATE_HEAD : |
| if (!strcmp(client->uri, "/icon.png")) |
| return (respond_http(client, HTTP_STATUS_OK, NULL, "image/png", 0)); |
| else if (!strcmp(client->uri, "/") || !strcmp(client->uri, "/media") || !strcmp(client->uri, "/supplies")) |
| return (respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0)); |
| else |
| return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0)); |
| |
| case HTTP_STATE_GET : |
| if (!strcmp(client->uri, "/icon.png")) |
| { |
| /* |
| * Send PNG icon file. |
| */ |
| |
| if (client->printer->icon) |
| { |
| int fd; /* Icon file */ |
| struct stat fileinfo; /* Icon file information */ |
| char buffer[4096]; /* Copy buffer */ |
| ssize_t bytes; /* Bytes */ |
| |
| fprintf(stderr, "Icon file is \"%s\".\n", client->printer->icon); |
| |
| if (!stat(client->printer->icon, &fileinfo) && (fd = open(client->printer->icon, O_RDONLY)) >= 0) |
| { |
| if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", (size_t)fileinfo.st_size)) |
| { |
| close(fd); |
| return (0); |
| } |
| |
| while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) |
| httpWrite2(client->http, buffer, (size_t)bytes); |
| |
| httpFlushWrite(client->http); |
| |
| close(fd); |
| } |
| else |
| return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0)); |
| } |
| else |
| { |
| fputs("Icon file is internal printer.png.\n", stderr); |
| |
| if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", sizeof(printer_png))) |
| return (0); |
| |
| httpWrite2(client->http, (const char *)printer_png, sizeof(printer_png)); |
| httpFlushWrite(client->http); |
| } |
| } |
| else |
| { |
| /* |
| * Authenticate if needed... |
| */ |
| |
| if ((http_status = authenticate_request(client)) != HTTP_STATUS_CONTINUE) |
| { |
| return (respond_http(client, http_status, NULL, NULL, 0)); |
| } |
| |
| if (!strcmp(client->uri, "/")) |
| { |
| /* |
| * Show web status page... |
| */ |
| |
| return (show_status(client)); |
| } |
| else if (!strcmp(client->uri, "/media")) |
| { |
| /* |
| * Show web media page... |
| */ |
| |
| return (show_media(client)); |
| } |
| else if (!strcmp(client->uri, "/supplies")) |
| { |
| /* |
| * Show web supplies page... |
| */ |
| |
| return (show_supplies(client)); |
| } |
| else |
| return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0)); |
| } |
| break; |
| |
| case HTTP_STATE_POST : |
| if (strcmp(httpGetField(client->http, HTTP_FIELD_CONTENT_TYPE), |
| "application/ipp")) |
| { |
| /* |
| * Not an IPP request... |
| */ |
| |
| return (respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0)); |
| } |
| |
| /* |
| * Read the IPP request... |
| */ |
| |
| client->request = ippNew(); |
| |
| while ((ipp_state = ippRead(client->http, |
| client->request)) != IPP_STATE_DATA) |
| { |
| if (ipp_state == IPP_STATE_ERROR) |
| { |
| fprintf(stderr, "%s IPP read error (%s).\n", client->hostname, cupsLastErrorString()); |
| respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); |
| return (0); |
| } |
| } |
| |
| /* |
| * Now that we have the IPP request, process the request... |
| */ |
| |
| return (process_ipp(client)); |
| |
| default : |
| break; /* Anti-compiler-warning-code */ |
| } |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'process_ipp()' - Process an IPP request. |
| */ |
| |
| static int /* O - 1 on success, 0 on error */ |
| process_ipp(ippeve_client_t *client) /* I - Client */ |
| { |
| ipp_tag_t group; /* Current group tag */ |
| ipp_attribute_t *attr; /* Current attribute */ |
| ipp_attribute_t *charset; /* Character set attribute */ |
| ipp_attribute_t *language; /* Language attribute */ |
| ipp_attribute_t *uri; /* Printer URI attribute */ |
| int major, minor; /* Version number */ |
| const char *name; /* Name of attribute */ |
| http_status_t status; /* Authentication status */ |
| |
| |
| debug_attributes("Request", client->request, 1); |
| |
| /* |
| * First build an empty response message for this request... |
| */ |
| |
| client->operation_id = ippGetOperation(client->request); |
| client->response = ippNewResponse(client->request); |
| |
| /* |
| * Then validate the request header and required attributes... |
| */ |
| |
| major = ippGetVersion(client->request, &minor); |
| |
| if (major < 1 || major > 2) |
| { |
| /* |
| * Return an error, since we only support IPP 1.x and 2.x. |
| */ |
| |
| respond_ipp(client, IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED, "Bad request version number %d.%d.", major, minor); |
| } |
| else if ((major * 10 + minor) > MaxVersion) |
| { |
| if (httpGetState(client->http) != HTTP_STATE_POST_SEND) |
| httpFlush(client->http); /* Flush trailing (junk) data */ |
| |
| respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); |
| return (0); |
| } |
| else if (ippGetRequestId(client->request) <= 0) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Bad request-id %d.", ippGetRequestId(client->request)); |
| } |
| else if (!ippFirstAttribute(client->request)) |
| { |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "No attributes in request."); |
| } |
| else |
| { |
| /* |
| * Make sure that the attributes are provided in the correct order and |
| * don't repeat groups... |
| */ |
| |
| for (attr = ippFirstAttribute(client->request), |
| group = ippGetGroupTag(attr); |
| attr; |
| attr = ippNextAttribute(client->request)) |
| { |
| if (ippGetGroupTag(attr) < group && ippGetGroupTag(attr) != IPP_TAG_ZERO) |
| { |
| /* |
| * Out of order; return an error... |
| */ |
| |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, |
| "Attribute groups are out of order (%x < %x).", |
| ippGetGroupTag(attr), group); |
| break; |
| } |
| else |
| group = ippGetGroupTag(attr); |
| } |
| |
| if (!attr) |
| { |
| /* |
| * Then make sure that the first three attributes are: |
| * |
| * attributes-charset |
| * attributes-natural-language |
| * printer-uri/job-uri |
| */ |
| |
| attr = ippFirstAttribute(client->request); |
| name = ippGetName(attr); |
| if (attr && name && !strcmp(name, "attributes-charset") && |
| ippGetValueTag(attr) == IPP_TAG_CHARSET) |
| charset = attr; |
| else |
| charset = NULL; |
| |
| attr = ippNextAttribute(client->request); |
| name = ippGetName(attr); |
| |
| if (attr && name && !strcmp(name, "attributes-natural-language") && |
| ippGetValueTag(attr) == IPP_TAG_LANGUAGE) |
| language = attr; |
| else |
| language = NULL; |
| |
| if ((attr = ippFindAttribute(client->request, "printer-uri", |
| IPP_TAG_URI)) != NULL) |
| uri = attr; |
| else if ((attr = ippFindAttribute(client->request, "job-uri", |
| IPP_TAG_URI)) != NULL) |
| uri = attr; |
| else |
| uri = NULL; |
| |
| if (charset && |
| strcasecmp(ippGetString(charset, 0, NULL), "us-ascii") && |
| strcasecmp(ippGetString(charset, 0, NULL), "utf-8")) |
| { |
| /* |
| * Bad character set... |
| */ |
| |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, |
| "Unsupported character set \"%s\".", |
| ippGetString(charset, 0, NULL)); |
| } |
| else if (!charset || !language || !uri) |
| { |
| /* |
| * Return an error, since attributes-charset, |
| * attributes-natural-language, and printer-uri/job-uri are required |
| * for all operations. |
| */ |
| |
| respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, |
| "Missing required attributes."); |
| } |
| else |
| { |
| char scheme[32], /* URI scheme */ |
| userpass[32], /* Username/password in URI */ |
| host[256], /* Host name in URI */ |
| resource[256]; /* Resource path in URI */ |
| int port; /* Port number in URI */ |
| |
| name = ippGetName(uri); |
| |
| if (httpSeparateURI(HTTP_URI_CODING_ALL, ippGetString(uri, 0, NULL), |
| scheme, sizeof(scheme), |
| userpass, sizeof(userpass), |
| host, sizeof(host), &port, |
| resource, sizeof(resource)) < HTTP_URI_STATUS_OK) |
| respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES, |
| "Bad %s value '%s'.", name, ippGetString(uri, 0, NULL)); |
| else if ((!strcmp(name, "job-uri") && |
| strncmp(resource, "/ipp/print/", 11)) || |
| (!strcmp(name, "printer-uri") && |
| strcmp(resource, "/ipp/print"))) |
| respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "%s %s not found.", |
| name, ippGetString(uri, 0, NULL)); |
| else if (client->operation_id != IPP_OP_GET_PRINTER_ATTRIBUTES && (status = authenticate_request(client)) != HTTP_STATUS_CONTINUE) |
| { |
| httpFlush(client->http); |
| |
| return (respond_http(client, status, NULL, NULL, 0)); |
| } |
| else |
| { |
| /* |
| * Handle HTTP Expect... |
| */ |
| |
| if (httpGetExpect(client->http)) |
| { |
| if (httpGetExpect(client->http) == HTTP_STATUS_CONTINUE) |
| { |
| /* |
| * Send 100-continue header... |
| */ |
| |
| if (!respond_http(client, HTTP_STATUS_CONTINUE, NULL, NULL, 0)) |
| return (0); |
| } |
| else |
| { |
| /* |
| * Send 417-expectation-failed header... |
| */ |
| |
| if (!respond_http(client, HTTP_STATUS_EXPECTATION_FAILED, NULL, NULL, 0)) |
| return (0); |
| |
| httpFlush(client->http); |
| return (1); |
| } |
| } |
| |
| /* |
| * Try processing the operation... |
| */ |
| |
| switch (client->operation_id) |
| { |
| case IPP_OP_PRINT_JOB : |
| ipp_print_job(client); |
| break; |
| |
| case IPP_OP_PRINT_URI : |
| ipp_print_uri(client); |
| break; |
| |
| case IPP_OP_VALIDATE_JOB : |
| ipp_validate_job(client); |
| break; |
| |
| case IPP_OP_CREATE_JOB : |
| ipp_create_job(client); |
| break; |
| |
| case IPP_OP_SEND_DOCUMENT : |
| ipp_send_document(client); |
| break; |
| |
| case IPP_OP_SEND_URI : |
| ipp_send_uri(client); |
| break; |
| |
| case IPP_OP_CANCEL_JOB : |
| ipp_cancel_job(client); |
| break; |
| |
| case IPP_OP_GET_JOB_ATTRIBUTES : |
| ipp_get_job_attributes(client); |
| break; |
| |
| case IPP_OP_GET_JOBS : |
| ipp_get_jobs(client); |
| break; |
| |
| case IPP_OP_GET_PRINTER_ATTRIBUTES : |
| ipp_get_printer_attributes(client); |
| break; |
| |
| case IPP_OP_CLOSE_JOB : |
| ipp_close_job(client); |
| break; |
| |
| case IPP_OP_IDENTIFY_PRINTER : |
| ipp_identify_printer(client); |
| break; |
| |
| default : |
| respond_ipp(client, IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED, |
| "Operation not supported."); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| /* |
| * Send the HTTP header and return... |
| */ |
| |
| if (httpGetState(client->http) != HTTP_STATE_POST_SEND) |
| httpFlush(client->http); /* Flush trailing (junk) data */ |
| |
| return (respond_http(client, HTTP_STATUS_OK, NULL, "application/ipp", |
| ippLength(client->response))); |
| } |
| |
| |
| /* |
| * 'process_job()' - Process a print job. |
| */ |
| |
| static void * /* O - Thread exit status */ |
| process_job(ippeve_job_t *job) /* I - Job */ |
| { |
| job->state = IPP_JSTATE_PROCESSING; |
| job->printer->state = IPP_PSTATE_PROCESSING; |
| job->processing = time(NULL); |
| |
| while (job->printer->state_reasons & IPPEVE_PREASON_MEDIA_EMPTY) |
| { |
| job->printer->state_reasons |= IPPEVE_PREASON_MEDIA_NEEDED; |
| |
| sleep(1); |
| } |
| |
| job->printer->state_reasons &= (ippeve_preason_t)~IPPEVE_PREASON_MEDIA_NEEDED; |
| |
| if (job->printer->command) |
| { |
| /* |
| * Execute a command with the job spool file and wait for it to complete... |
| */ |
| |
| int pid, /* Process ID */ |
| status; /* Exit status */ |
| struct timeval start, /* Start time */ |
| end; /* End time */ |
| char *myargv[3], /* Command-line arguments */ |
| *myenvp[400]; /* Environment variables */ |
| int myenvc; /* Number of environment variables */ |
| ipp_attribute_t *attr; /* Job attribute */ |
| char val[1280], /* IPP_NAME=value */ |
| *valptr; /* Pointer into string */ |
| #ifndef _WIN32 |
| int mystdout = -1; /* File for stdout */ |
| int mypipe[2]; /* Pipe for stderr */ |
| char line[2048], /* Line from stderr */ |
| *ptr, /* Pointer into line */ |
| *endptr; /* End of line */ |
| ssize_t bytes; /* Bytes read */ |
| #endif /* !_WIN32 */ |
| |
| fprintf(stderr, "[Job %d] Running command \"%s %s\".\n", job->id, job->printer->command, job->filename); |
| gettimeofday(&start, NULL); |
| |
| /* |
| * Setup the command-line arguments... |
| */ |
| |
| myargv[0] = job->printer->command; |
| myargv[1] = job->filename; |
| myargv[2] = NULL; |
| |
| /* |
| * Copy the current environment, then add environment variables for every |
| * Job attribute and Printer -default attributes... |
| */ |
| |
| for (myenvc = 0; environ[myenvc] && myenvc < (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 1); myenvc ++) |
| myenvp[myenvc] = strdup(environ[myenvc]); |
| |
| if (myenvc > (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 32)) |
| { |
| fprintf(stderr, "[Job %d] Too many environment variables to process job.\n", job->id); |
| job->state = IPP_JSTATE_ABORTED; |
| goto error; |
| } |
| |
| snprintf(val, sizeof(val), "CONTENT_TYPE=%s", job->format); |
| myenvp[myenvc ++] = strdup(val); |
| |
| if (job->printer->device_uri) |
| { |
| snprintf(val, sizeof(val), "DEVICE_URI=%s", job->printer->device_uri); |
| myenvp[myenvc ++] = strdup(val); |
| } |
| |
| if (job->printer->output_format) |
| { |
| snprintf(val, sizeof(val), "OUTPUT_TYPE=%s", job->printer->output_format); |
| myenvp[myenvc ++] = strdup(val); |
| } |
| |
| #if !CUPS_LITE |
| if (job->printer->ppdfile) |
| { |
| snprintf(val, sizeof(val), "PPD=%s", job->printer->ppdfile); |
| myenvp[myenvc++] = strdup(val); |
| } |
| #endif /* !CUPS_LITE */ |
| |
| for (attr = ippFirstAttribute(job->printer->attrs); attr && myenvc < (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 1); attr = ippNextAttribute(job->printer->attrs)) |
| { |
| /* |
| * Convert "attribute-name-default" to "IPP_ATTRIBUTE_NAME_DEFAULT=" and |
| * "pwg-xxx" to "IPP_PWG_XXX", then add the value(s) from the attribute. |
| */ |
| |
| const char *name = ippGetName(attr), |
| /* Attribute name */ |
| *suffix = strstr(name, "-default"); |
| /* Suffix on attribute name */ |
| |
| if (strncmp(name, "pwg-", 4) && (!suffix || suffix[8])) |
| continue; |
| |
| valptr = val; |
| *valptr++ = 'I'; |
| *valptr++ = 'P'; |
| *valptr++ = 'P'; |
| *valptr++ = '_'; |
| while (*name && valptr < (val + sizeof(val) - 2)) |
| { |
| if (*name == '-') |
| *valptr++ = '_'; |
| else |
| *valptr++ = (char)toupper(*name & 255); |
| |
| name ++; |
| } |
| *valptr++ = '='; |
| ippAttributeString(attr, valptr, sizeof(val) - (size_t)(valptr - val)); |
| |
| myenvp[myenvc++] = strdup(val); |
| } |
| |
| for (attr = ippFirstAttribute(job->attrs); attr && myenvc < (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 1); attr = ippNextAttribute(job->attrs)) |
| { |
| /* |
| * Convert "attribute-name" to "IPP_ATTRIBUTE_NAME=" and then add the |
| * value(s) from the attribute. |
| */ |
| |
| const char *name = ippGetName(attr); |
| /* Attribute name */ |
| |
| if (!name) |
| continue; |
| |
| valptr = val; |
| *valptr++ = 'I'; |
| *valptr++ = 'P'; |
| *valptr++ = 'P'; |
| *valptr++ = '_'; |
| while (*name && valptr < (val + sizeof(val) - 2)) |
| { |
| if (*name == '-') |
| *valptr++ = '_'; |
| else |
| *valptr++ = (char)toupper(*name & 255); |
| |
| name ++; |
| } |
| *valptr++ = '='; |
| ippAttributeString(attr, valptr, sizeof(val) - (size_t)(valptr - val)); |
| |
| myenvp[myenvc++] = strdup(val); |
| } |
| |
| if (attr) |
| { |
| fprintf(stderr, "[Job %d] Too many environment variables to process job.\n", job->id); |
| job->state = IPP_JSTATE_ABORTED; |
| goto error; |
| } |
| |
| myenvp[myenvc] = NULL; |
| |
| /* |
| * Now run the program... |
| */ |
| |
| #ifdef _WIN32 |
| status = _spawnvpe(_P_WAIT, job->printer->command, myargv, myenvp); |
| |
| #else |
| if (job->printer->device_uri) |
| { |
| char scheme[32], /* URI scheme */ |
| userpass[256], /* username:password (unused) */ |
| host[256], /* Hostname or IP address */ |
| resource[256]; /* Resource path */ |
| int port; /* Port number */ |
| |
| |
| if (httpSeparateURI(HTTP_URI_CODING_ALL, job->printer->device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK) |
| { |
| fprintf(stderr, "[Job %d] Bad device URI \"%s\".\n", job->id, job->printer->device_uri); |
| } |
| else if (!strcmp(scheme, "file")) |
| { |
| struct stat fileinfo; /* See if this is a file or directory... */ |
| |
| if (stat(resource, &fileinfo)) |
| { |
| if (errno == ENOENT) |
| { |
| if ((mystdout = open(resource, O_WRONLY | O_CREAT | O_TRUNC, 0666)) >= 0) |
| fprintf(stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, resource); |
| else |
| fprintf(stderr, "[Job %d] Unable to create \"%s\": %s\n", job->id, resource, strerror(errno)); |
| } |
| else |
| fprintf(stderr, "[Job %d] Unable to access \"%s\": %s\n", job->id, resource, strerror(errno)); |
| } |
| else if (S_ISDIR(fileinfo.st_mode)) |
| { |
| if ((mystdout = create_job_file(job, line, sizeof(line), resource, "prn")) >= 0) |
| fprintf(stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, line); |
| else |
| fprintf(stderr, "[Job %d] Unable to create \"%s\": %s\n", job->id, line, strerror(errno)); |
| } |
| else if (!S_ISREG(fileinfo.st_mode)) |
| { |
| if ((mystdout = open(resource, O_WRONLY | O_CREAT | O_TRUNC, 0666)) >= 0) |
| fprintf(stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, resource); |
| else |
| fprintf(stderr, "[Job %d] Unable to create \"%s\": %s\n", job->id, resource, strerror(errno)); |
| } |
| else if ((mystdout = open(resource, O_WRONLY)) >= 0) |
| fprintf(stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, resource); |
| else |
| fprintf(stderr, "[Job %d] Unable to open \"%s\": %s\n", job->id, resource, strerror(errno)); |
| } |
| else if (!strcmp(scheme, "socket")) |
| { |
| http_addrlist_t *addrlist; /* List of addresses */ |
| char service[32]; /* Service number */ |
| |
| snprintf(service, sizeof(service), "%d", port); |
| |
| if ((addrlist = httpAddrGetList(host, AF_UNSPEC, service)) == NULL) |
| fprintf(stderr, "[Job %d] Unable to find \"%s\": %s\n", job->id, host, cupsLastErrorString()); |
| else if (!httpAddrConnect2(addrlist, &mystdout, 30000, &(job->cancel))) |
| fprintf(stderr, "[Job %d] Unable to connect to \"%s\": %s\n", job->id, host, cupsLastErrorString()); |
| |
| httpAddrFreeList(addrlist); |
| } |
| else |
| { |
| fprintf(stderr, "[Job %d] Unsupported device URI scheme \"%s\".\n", job->id, scheme); |
| } |
| } |
| else if ((mystdout = create_job_file(job, line, sizeof(line), job->printer->directory, "prn")) >= 0) |
| { |
| fprintf(stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, line); |
| } |
| |
| if (mystdout < 0) |
| mystdout = open("/dev/null", O_WRONLY); |
| |
| if (pipe(mypipe)) |
| { |
| fprintf(stderr, "[Job %d] Unable to create pipe for stderr: %s\n", job->id, strerror(errno)); |
| mypipe[0] = mypipe[1] = -1; |
| } |
| |
| if ((pid = fork()) == 0) |
| { |
| /* |
| * Child comes here... |
| */ |
| |
| close(1); |
| dup2(mystdout, 1); |
| close(mystdout); |
| |
| close(2); |
| dup2(mypipe[1], 2); |
| close(mypipe[0]); |
| close(mypipe[1]); |
| |
| execve(job->printer->command, myargv, myenvp); |
| exit(errno); |
| } |
| else if (pid < 0) |
| { |
| /* |
| * Unable to fork process... |
| */ |
| |
| fprintf(stderr, "[Job %d] Unable to start job processing command: %s\n", job->id, strerror(errno)); |
| status = -1; |
| |
| close(mystdout); |
| close(mypipe[0]); |
| close(mypipe[1]); |
| |
| /* |
| * Free memory used for environment... |
| */ |
| |
| while (myenvc > 0) |
| free(myenvp[-- myenvc]); |
| } |
| else |
| { |
| /* |
| * Free memory used for environment... |
| */ |
| |
| while (myenvc > 0) |
| free(myenvp[-- myenvc]); |
| |
| /* |
| * Close the output file in the parent process... |
| */ |
| |
| close(mystdout); |
| |
| /* |
| * If the pipe exists, read from it until EOF... |
| */ |
| |
| if (mypipe[0] >= 0) |
| { |
| close(mypipe[1]); |
| |
| endptr = line; |
| while ((bytes = read(mypipe[0], endptr, sizeof(line) - (size_t)(endptr - line) - 1)) > 0) |
| { |
| endptr += bytes; |
| *endptr = '\0'; |
| |
| while ((ptr = strchr(line, '\n')) != NULL) |
| { |
| int level = 3; /* Message log level */ |
| |
| *ptr++ = '\0'; |
| |
| if (!strncmp(line, "ATTR:", 5)) |
| { |
| /* |
| * Process job/printer attribute updates. |
| */ |
| |
| process_attr_message(job, line); |
| } |
| else if (!strncmp(line, "DEBUG:", 6)) |
| { |
| /* |
| * Debug message... |
| */ |
| |
| level = 2; |
| } |
| else if (!strncmp(line, "ERROR:", 6)) |
| { |
| /* |
| * Error message... |
| */ |
| |
| level = 0; |
| job->message = strdup(line + 6); |
| job->msglevel = 0; |
| } |
| else if (!strncmp(line, "INFO:", 5)) |
| { |
| /* |
| * Informational/progress message... |
| */ |
| |
| level = 1; |
| if (job->msglevel) |
| { |
| job->message = strdup(line + 5); |
| job->msglevel = 1; |
| } |
| } |
| else if (!strncmp(line, "STATE:", 6)) |
| { |
| /* |
| * Process printer-state-reasons keywords. |
| */ |
| |
| process_state_message(job, line); |
| } |
| |
| if (Verbosity >= level) |
| fprintf(stderr, "[Job %d] Command - %s\n", job->id, line); |
| |
| bytes = ptr - line; |
| if (ptr < endptr) |
| memmove(line, ptr, (size_t)(endptr - ptr)); |
| endptr -= bytes; |
| *endptr = '\0'; |
| } |
| } |
| |
| close(mypipe[0]); |
| } |
| |
| /* |
| * Wait for child to complete... |
| */ |
| |
| # ifdef HAVE_WAITPID |
| while (waitpid(pid, &status, 0) < 0); |
| # else |
| while (wait(&status) < 0); |
| # endif /* HAVE_WAITPID */ |
| } |
| #endif /* _WIN32 */ |
| |
| if (status) |
| { |
| #ifndef _WIN32 |
| if (WIFEXITED(status)) |
| #endif /* !_WIN32 */ |
| fprintf(stderr, "[Job %d] Command \"%s\" exited with status %d.\n", job->id, job->printer->command, WEXITSTATUS(status)); |
| #ifndef _WIN32 |
| else |
| fprintf(stderr, "[Job %d] Command \"%s\" terminated with signal %d.\n", job->id, job->printer->command, WTERMSIG(status)); |
| #endif /* !_WIN32 */ |
| job->state = IPP_JSTATE_ABORTED; |
| } |
| else if (status < 0) |
| job->state = IPP_JSTATE_ABORTED; |
| else |
| fprintf(stderr, "[Job %d] Command \"%s\" completed successfully.\n", job->id, job->printer->command); |
| |
| /* |
| * Report the total processing time... |
| */ |
| |
| gettimeofday(&end, NULL); |
| |
| fprintf(stderr, "[Job %d] Processing time was %.3f seconds.\n", job->id, end.tv_sec - start.tv_sec + 0.000001 * (end.tv_usec - start.tv_usec)); |
| } |
| else |
| { |
| /* |
| * Sleep for a random amount of time to simulate job processing. |
| */ |
| |
| sleep((unsigned)(5 + (CUPS_RAND() % 11))); |
| } |
| |
| if (job->cancel) |
| job->state = IPP_JSTATE_CANCELED; |
| else if (job->state == IPP_JSTATE_PROCESSING) |
| job->state = IPP_JSTATE_COMPLETED; |
| |
| error: |
| |
| job->completed = time(NULL); |
| job->printer->state = IPP_PSTATE_IDLE; |
| job->printer->active_job = NULL; |
| |
| return (NULL); |
| } |
| |
| |
| /* |
| * 'process_state_message()' - Process a STATE: message from a command. |
| */ |
| |
| static void |
| process_state_message( |
| ippeve_job_t *job, /* I - Job */ |
| char *message) /* I - Message */ |
| { |
| int i; /* Looping var */ |
| ippeve_preason_t state_reasons, /* printer-state-reasons values */ |
| bit; /* Current reason bit */ |
| char *ptr, /* Pointer into message */ |
| *next; /* Next keyword in message */ |
| int remove; /* Non-zero if we are removing keywords */ |
| |
| |
| /* |
| * Skip leading "STATE:" and any whitespace... |
| */ |
| |
| for (message += 6; *message; message ++) |
| if (*message != ' ' && *message != '\t') |
| break; |
| |
| /* |
| * Support the following forms of message: |
| * |
| * "keyword[,keyword,...]" to set the printer-state-reasons value(s). |
| * |
| * "-keyword[,keyword,...]" to remove keywords. |
| * |
| * "+keyword[,keyword,...]" to add keywords. |
| * |
| * Keywords may or may not have a suffix (-report, -warning, -error) per |
| * RFC 8011. |
| */ |
| |
| if (*message == '-') |
| { |
| remove = 1; |
| state_reasons = job->printer->state_reasons; |
| message ++; |
| } |
| else if (*message == '+') |
| { |
| remove = 0; |
| state_reasons = job->printer->state_reasons; |
| message ++; |
| } |
| else |
| { |
| remove = 0; |
| state_reasons = IPPEVE_PREASON_NONE; |
| } |
| |
| while (*message) |
| { |
| if ((next = strchr(message, ',')) != NULL) |
| *next++ = '\0'; |
| |
| if ((ptr = strstr(message, "-error")) != NULL) |
| *ptr = '\0'; |
| else if ((ptr = strstr(message, "-report")) != NULL) |
| *ptr = '\0'; |
| else if ((ptr = strstr(message, "-warning")) != NULL) |
| *ptr = '\0'; |
| |
| for (i = 0, bit = 1; i < (int)(sizeof(ippeve_preason_strings) / sizeof(ippeve_preason_strings[0])); i ++, bit *= 2) |
| { |
| if (!strcmp(message, ippeve_preason_strings[i])) |
| { |
| if (remove) |
| state_reasons &= ~bit; |
| else |
| state_reasons |= bit; |
| } |
| } |
| |
| if (next) |
| message = next; |
| else |
| break; |
| } |
| |
| job->printer->state_reasons = state_reasons; |
| } |
| |
| |
| /* |
| * 'register_printer()' - Register a printer object via Bonjour. |
| */ |
| |
| static int /* O - 1 on success, 0 on error */ |
| register_printer( |
| ippeve_printer_t *printer, /* I - Printer */ |
| const char *subtypes) /* I - Service subtype(s) */ |
| { |
| #if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) |
| ippeve_txt_t ipp_txt; /* Bonjour IPP TXT record */ |
| int i, /* Looping var */ |
| count; /* Number of values */ |
| ipp_attribute_t *color_supported, |
| *document_format_supported, |
| *printer_location, |
| *printer_make_and_model, |
| *printer_more_info, |
| *printer_uuid, |
| *sides_supported, |
| *urf_supported; /* Printer attributes */ |
| const char *value; /* Value string */ |
| char formats[252], /* List of supported formats */ |
| urf[252], /* List of supported URF values */ |
| *ptr; /* Pointer into string */ |
| |
| |
| if (!strcmp(subtypes, "off")) |
| return (1); |
| |
| color_supported = ippFindAttribute(printer->attrs, "color-supported", IPP_TAG_BOOLEAN); |
| document_format_supported = ippFindAttribute(printer->attrs, "document-format-supported", IPP_TAG_MIMETYPE); |
| printer_location = ippFindAttribute(printer->attrs, "printer-location", IPP_TAG_TEXT); |
| printer_make_and_model = ippFindAttribute(printer->attrs, "printer-make-and-model", IPP_TAG_TEXT); |
| printer_more_info = ippFindAttribute(printer->attrs, "printer-more-info", IPP_TAG_URI); |
| printer_uuid = ippFindAttribute(printer->attrs, "printer-uuid", IPP_TAG_URI); |
| sides_supported = ippFindAttribute(printer->attrs, "sides-supported", IPP_TAG_KEYWORD); |
| urf_supported = ippFindAttribute(printer->attrs, "urf-supported", IPP_TAG_KEYWORD); |
| |
| for (i = 0, count = ippGetCount(document_format_supported), ptr = formats; i < count; i ++) |
| { |
| value = ippGetString(document_format_supported, i, NULL); |
| |
| if (!strcasecmp(value, "application/octet-stream")) |
| continue; |
| |
| if (ptr > formats && ptr < (formats + sizeof(formats) - 1)) |
| *ptr++ = ','; |
| |
| strlcpy(ptr, value, sizeof(formats) - (size_t)(ptr - formats)); |
| ptr += strlen(ptr); |
| |
| if (ptr >= (formats + sizeof(formats) - 1)) |
| break; |
| } |
| |
| urf[0] = '\0'; |
| for (i = 0, count = ippGetCount(urf_supported), ptr = urf; i < count; i ++) |
| { |
| value = ippGetString(urf_supported, i, NULL); |
| |
| if (ptr > urf && ptr < (urf + sizeof(urf) - 1)) |
| *ptr++ = ','; |
| |
| strlcpy(ptr, value, sizeof(urf) - (size_t)(ptr - urf)); |
| ptr += strlen(ptr); |
| |
| if (ptr >= (urf + sizeof(urf) - 1)) |
| break; |
| } |
| |
| #endif /* HAVE_DNSSD || HAVE_AVAHI */ |
| #ifdef HAVE_DNSSD |
| DNSServiceErrorType error; /* Error from Bonjour */ |
| char regtype[256]; /* Bonjour service type */ |
| uint32_t interface; /* Interface index */ |
| |
| |
| /* |
| * Build the TXT record for IPP... |
| */ |
| |
| TXTRecordCreate(&ipp_txt, 1024, NULL); |
| TXTRecordSetValue(&ipp_txt, "rp", 9, "ipp/print"); |
| if ((value = ippGetString(printer_make_and_model, 0, NULL)) != NULL) |
| TXTRecordSetValue(&ipp_txt, "ty", (uint8_t)strlen(value), value); |
| if ((value = ippGetString(printer_more_info, 0, NULL)) != NULL) |
| TXTRecordSetValue(&ipp_txt, "adminurl", (uint8_t)strlen(value), value); |
| if ((value = ippGetString(printer_location, 0, NULL)) != NULL) |
| TXTRecordSetValue(&ipp_txt, "note", (uint8_t)strlen(value), value); |
| TXTRecordSetValue(&ipp_txt, "pdl", (uint8_t)strlen(formats), formats); |
| TXTRecordSetValue(&ipp_txt, "Color", 1, ippGetBoolean(color_supported, 0) ? "T" : "F"); |
| TXTRecordSetValue(&ipp_txt, "Duplex", 1, ippGetCount(sides_supported) > 1 ? "T" : "F"); |
| if ((value = ippGetString(printer_uuid, 0, NULL)) != NULL) |
| TXTRecordSetValue(&ipp_txt, "UUID", (uint8_t)strlen(value) - 9, value + 9); |
| # ifdef HAVE_SSL |
| TXTRecordSetValue(&ipp_txt, "TLS", 3, "1.2"); |
| # endif /* HAVE_SSL */ |
| if (urf[0]) |
| TXTRecordSetValue(&ipp_txt, "URF", (uint8_t)strlen(urf), urf); |
| TXTRecordSetValue(&ipp_txt, "txtvers", 1, "1"); |
| TXTRecordSetValue(&ipp_txt, "qtotal", 1, "1"); |
| |
| /* |
| * Register the _printer._tcp (LPD) service type with a port number of 0 to |
| * defend our service name but not actually support LPD... |
| */ |
| |
| interface = !strcmp(printer->hostname, "localhost") ? kDNSServiceInterfaceIndexLocalOnly : kDNSServiceInterfaceIndexAny; |
| |
| printer->printer_ref = DNSSDMaster; |
| |
| if ((error = DNSServiceRegister(&(printer->printer_ref), kDNSServiceFlagsShareConnection, interface, printer->dnssd_name, "_printer._tcp", NULL /* domain */, NULL /* host */, 0 /* port */, 0 /* txtLen */, NULL /* txtRecord */, (DNSServiceRegisterReply)dnssd_callback, printer)) != kDNSServiceErr_NoError) |
| { |
| _cupsLangPrintf(stderr, _("Unable to register \"%s.%s\": %d"), printer->dnssd_name, "_printer._tcp", error); |
| return (0); |
| } |
| |
| /* |
| * Then register the _ipp._tcp (IPP) service type with the real port number to |
| * advertise our IPP printer... |
| */ |
| |
| printer->ipp_ref = DNSSDMaster; |
| |
| if (subtypes && *subtypes) |
| snprintf(regtype, sizeof(regtype), "_ipp._tcp,%s", subtypes); |
| else |
| strlcpy(regtype, "_ipp._tcp", sizeof(regtype)); |
| |
| if ((error = DNSServiceRegister(&(printer->ipp_ref), kDNSServiceFlagsShareConnection, interface, printer->dnssd_name, regtype, NULL /* domain */, NULL /* host */, htons(printer->port), TXTRecordGetLength(&ipp_txt), TXTRecordGetBytesPtr(&ipp_txt), (DNSServiceRegisterReply)dnssd_callback, printer)) != kDNSServiceErr_NoError) |
| { |
| _cupsLangPrintf(stderr, _("Unable to register \"%s.%s\": %d"), printer->dnssd_name, regtype, error); |
| return (0); |
| } |
| |
| # ifdef HAVE_SSL |
| /* |
| * Then register the _ipps._tcp (IPP) service type with the real port number to |
| * advertise our IPPS printer... |
| */ |
| |
| printer->ipps_ref = DNSSDMaster; |
| |
| if (subtypes && *subtypes) |
| snprintf(regtype, sizeof(regtype), "_ipps._tcp,%s", subtypes); |
| else |
| strlcpy(regtype, "_ipps._tcp", sizeof(regtype)); |
| |
| if ((error = DNSServiceRegister(&(printer->ipps_ref), kDNSServiceFlagsShareConnection, interface, printer->dnssd_name, regtype, NULL /* domain */, NULL /* host */, htons(printer->port), TXTRecordGetLength(&ipp_txt), TXTRecordGetBytesPtr(&ipp_txt), (DNSServiceRegisterReply)dnssd_callback, printer)) != kDNSServiceErr_NoError) |
| { |
| _cupsLangPrintf(stderr, _("Unable to register \"%s.%s\": %d"), printer->dnssd_name, regtype, error); |
| return (0); |
| } |
| # endif /* HAVE_SSL */ |
| |
| /* |
| * Similarly, register the _http._tcp,_printer (HTTP) service type with the |
| * real port number to advertise our IPP printer... |
| */ |
| |
| printer->http_ref = DNSSDMaster; |
| |
| if ((error = DNSServiceRegister(&(printer->http_ref), kDNSServiceFlagsShareConnection, interface, printer->dnssd_name, "_http._tcp,_printer", NULL /* domain */, NULL /* host */, htons(printer->port), 0 /* txtLen */, NULL /* txtRecord */, (DNSServiceRegisterReply)dnssd_callback, printer)) != kDNSServiceErr_NoError) |
| { |
| _cupsLangPrintf(stderr, _("Unable to register \"%s.%s\": %d"), printer->dnssd_name, "_http._tcp,_printer", error); |
| return (0); |
| } |
| |
| TXTRecordDeallocate(&ipp_txt); |
| |
| #elif defined(HAVE_AVAHI) |
| char temp[256]; /* Subtype service string */ |
| |
| /* |
| * Create the TXT record... |
| */ |
| |
| ipp_txt = NULL; |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "rp=ipp/print"); |
| if ((value = ippGetString(printer_make_and_model, 0, NULL)) != NULL) |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "ty=%s", value); |
| if ((value = ippGetString(printer_more_info, 0, NULL)) != NULL) |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "adminurl=%s", value); |
| if ((value = ippGetString(printer_location, 0, NULL)) != NULL) |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "note=%s", value); |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "pdl=%s", formats); |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "Color=%s", ippGetBoolean(color_supported, 0) ? "T" : "F"); |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "Duplex=%s", ippGetCount(sides_supported) > 1 ? "T" : "F"); |
| if ((value = ippGetString(printer_uuid, 0, NULL)) != NULL) |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "UUID=%s", value + 9); |
| # ifdef HAVE_SSL |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "TLS=1.2"); |
| # endif /* HAVE_SSL */ |
| if (urf[0]) |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "URF=%s", urf); |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "txtvers=1"); |
| ipp_txt = avahi_string_list_add_printf(ipp_txt, "qtotal=1"); |
| |
| /* |
| * Register _printer._tcp (LPD) with port 0 to reserve the service name... |
| */ |
| |
| avahi_threaded_poll_lock(DNSSDMaster); |
| |
| printer->ipp_ref = avahi_entry_group_new(DNSSDClient, dnssd_callback, NULL); |
| |
| avahi_entry_group_add_service_strlst(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_printer._tcp", NULL, NULL, 0, NULL); |
| |
| /* |
| * Then register the _ipp._tcp (IPP)... |
| */ |
| |
| avahi_entry_group_add_service_strlst(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_ipp._tcp", NULL, NULL, printer->port, ipp_txt); |
| if (subtypes && *subtypes) |
| { |
| char *temptypes = strdup(subtypes), *start, *end; |
| |
| for (start = temptypes; *start; start = end) |
| { |
| if ((end = strchr(start, ',')) != NULL) |
| *end++ = '\0'; |
| else |
| end = start + strlen(start); |
| |
| snprintf(temp, sizeof(temp), "%s._sub._ipp._tcp", start); |
| avahi_entry_group_add_service_subtype(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_ipp._tcp", NULL, temp); |
| } |
| |
| free(temptypes); |
| } |
| |
| #ifdef HAVE_SSL |
| /* |
| * _ipps._tcp (IPPS) for secure printing... |
| */ |
| |
| avahi_entry_group_add_service_strlst(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_ipps._tcp", NULL, NULL, printer->port, ipp_txt); |
| if (subtypes && *subtypes) |
| { |
| char *temptypes = strdup(subtypes), *start, *end; |
| |
| for (start = temptypes; *start; start = end) |
| { |
| if ((end = strchr(start, ',')) != NULL) |
| *end++ = '\0'; |
| else |
| end = start + strlen(start); |
| |
| snprintf(temp, sizeof(temp), "%s._sub._ipps._tcp", start); |
| avahi_entry_group_add_service_subtype(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_ipps._tcp", NULL, temp); |
| } |
| |
| free(temptypes); |
| } |
| #endif /* HAVE_SSL */ |
| |
| /* |
| * Finally _http.tcp (HTTP) for the web interface... |
| */ |
| |
| avahi_entry_group_add_service_strlst(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_http._tcp", NULL, NULL, printer->port, NULL); |
| avahi_entry_group_add_service_subtype(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_http._tcp", NULL, "_printer._sub._http._tcp"); |
| |
| /* |
| * Commit it... |
| */ |
| |
| avahi_entry_group_commit(printer->ipp_ref); |
| avahi_threaded_poll_unlock(DNSSDMaster); |
| |
| avahi_string_list_free(ipp_txt); |
| #endif /* HAVE_DNSSD */ |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'respond_http()' - Send a HTTP response. |
| */ |
| |
| int /* O - 1 on success, 0 on failure */ |
| respond_http( |
| ippeve_client_t *client, /* I - Client */ |
| http_status_t code, /* I - HTTP status of response */ |
| const char *content_encoding, /* I - Content-Encoding of response */ |
| const char *type, /* I - MIME media type of response */ |
| size_t length) /* I - Length of response */ |
| { |
| char message[1024]; /* Text message */ |
| |
| |
| fprintf(stderr, "%s %s\n", client->hostname, httpStatus(code)); |
| |
| if (code == HTTP_STATUS_CONTINUE) |
| { |
| /* |
| * 100-continue doesn't send any headers... |
| */ |
| |
| return (httpWriteResponse(client->http, HTTP_STATUS_CONTINUE) == 0); |
| } |
| |
| /* |
| * Format an error message... |
| */ |
| |
| if (!type && !length && code != HTTP_STATUS_OK && code != HTTP_STATUS_SWITCHING_PROTOCOLS) |
| { |
| snprintf(message, sizeof(message), "%d - %s\n", code, httpStatus(code)); |
| |
| type = "text/plain"; |
| length = strlen(message); |
| } |
| else |
| message[0] = '\0'; |
| |
| /* |
| * Send the HTTP response header... |
| */ |
| |
| httpClearFields(client->http); |
| |
| if (code == HTTP_STATUS_METHOD_NOT_ALLOWED || |
| client->operation == HTTP_STATE_OPTIONS) |
| httpSetField(client->http, HTTP_FIELD_ALLOW, "GET, HEAD, OPTIONS, POST"); |
| |
| if (code == HTTP_STATUS_UNAUTHORIZED) |
| { |
| char value[256]; /* WWW-Authenticate value */ |
| |
| snprintf(value, sizeof(value), "Basic realm=\"%s\"", PAMService); |
| httpSetField(client->http, HTTP_FIELD_WWW_AUTHENTICATE, value); |
| } |
| |
| if (type) |
| { |
| if (!strcmp(type, "text/html")) |
| httpSetField(client->http, HTTP_FIELD_CONTENT_TYPE, |
| "text/html; charset=utf-8"); |
| else |
| httpSetField(client->http, HTTP_FIELD_CONTENT_TYPE, type); |
| |
| if (content_encoding) |
| httpSetField(client->http, HTTP_FIELD_CONTENT_ENCODING, content_encoding); |
| } |
| |
| httpSetLength(client->http, length); |
| |
| if (httpWriteResponse(client->http, code) < 0) |
| return (0); |
| |
| /* |
| * Send the response data... |
| */ |
| |
| if (message[0]) |
| { |
| /* |
| * Send a plain text message. |
| */ |
| |
| if (httpPrintf(client->http, "%s", message) < 0) |
| return (0); |
| |
| if (httpWrite2(client->http, "", 0) < 0) |
| return (0); |
| } |
| else if (client->response) |
| { |
| /* |
| * Send an IPP response... |
| */ |
| |
| debug_attributes("Response", client->response, 2); |
| |
| ippSetState(client->response, IPP_STATE_IDLE); |
| |
| if (ippWrite(client->http, client->response) != IPP_STATE_DATA) |
| return (0); |
| } |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'respond_ipp()' - Send an IPP response. |
| */ |
| |
| static void |
| respond_ipp(ippeve_client_t *client, /* I - Client */ |
| ipp_status_t status, /* I - status-code */ |
| const char *message, /* I - printf-style status-message */ |
| ...) /* I - Additional args as needed */ |
| { |
| const char *formatted = NULL; /* Formatted message */ |
| |
| |
| ippSetStatusCode(client->response, status); |
| |
| if (message) |
| { |
| va_list ap; /* Pointer to additional args */ |
| ipp_attribute_t *attr; /* New status-message attribute */ |
| |
| va_start(ap, message); |
| if ((attr = ippFindAttribute(client->response, "status-message", IPP_TAG_TEXT)) != NULL) |
| ippSetStringfv(client->response, &attr, 0, message, ap); |
| else |
| attr = ippAddStringfv(client->response, IPP_TAG_OPERATION, IPP_TAG_TEXT, "status-message", NULL, message, ap); |
| va_end(ap); |
| |
| formatted = ippGetString(attr, 0, NULL); |
| } |
| |
| if (formatted) |
| fprintf(stderr, "%s %s %s (%s)\n", client->hostname, ippOpString(client->operation_id), ippErrorString(status), formatted); |
| else |
| fprintf(stderr, "%s %s %s\n", client->hostname, ippOpString(client->operation_id), ippErrorString(status)); |
| } |
| |
| |
| /* |
| * 'respond_unsupported()' - Respond with an unsupported attribute. |
| */ |
| |
| static void |
| respond_unsupported( |
| ippeve_client_t *client, /* I - Client */ |
| ipp_attribute_t *attr) /* I - Atribute */ |
| { |
| ipp_attribute_t *temp; /* Copy of attribute */ |
| |
| |
| respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES, "Unsupported %s %s%s value.", ippGetName(attr), ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr))); |
| |
| temp = ippCopyAttribute(client->response, attr, 0); |
| ippSetGroupTag(client->response, &temp, IPP_TAG_UNSUPPORTED_GROUP); |
| } |
| |
| |
| /* |
| * 'run_printer()' - Run the printer service. |
| */ |
| |
| static void |
| run_printer(ippeve_printer_t *printer) /* I - Printer */ |
| { |
| int num_fds; /* Number of file descriptors */ |
| struct pollfd polldata[3]; /* poll() data */ |
| int timeout; /* Timeout for poll() */ |
| ippeve_client_t *client; /* New client */ |
| |
| |
| /* |
| * Setup poll() data for the Bonjour service socket and IPv4/6 listeners... |
| */ |
| |
| polldata[0].fd = printer->ipv4; |
| polldata[0].events = POLLIN; |
| |
| polldata[1].fd = printer->ipv6; |
| polldata[1].events = POLLIN; |
| |
| num_fds = 2; |
| |
| #ifdef HAVE_DNSSD |
| polldata[num_fds ].fd = DNSServiceRefSockFD(DNSSDMaster); |
| polldata[num_fds ++].events = POLLIN; |
| #endif /* HAVE_DNSSD */ |
| |
| /* |
| * Loop until we are killed or have a hard error... |
| */ |
| |
| for (;;) |
| { |
| if (cupsArrayCount(printer->jobs)) |
| timeout = 10; |
| else |
| timeout = -1; |
| |
| if (poll(polldata, (nfds_t)num_fds, timeout) < 0 && errno != EINTR) |
| { |
| perror("poll() failed"); |
| break; |
| } |
| |
| if (polldata[0].revents & POLLIN) |
| { |
| if ((client = create_client(printer, printer->ipv4)) != NULL) |
| { |
| _cups_thread_t t = _cupsThreadCreate((_cups_thread_func_t)process_client, client); |
| |
| if (t) |
| { |
| _cupsThreadDetach(t); |
| } |
| else |
| { |
| perror("Unable to create client thread"); |
| delete_client(client); |
| } |
| } |
| } |
| |
| if (polldata[1].revents & POLLIN) |
| { |
| if ((client = create_client(printer, printer->ipv6)) != NULL) |
| { |
| _cups_thread_t t = _cupsThreadCreate((_cups_thread_func_t)process_client, client); |
| |
| if (t) |
| { |
| _cupsThreadDetach(t); |
| } |
| else |
| { |
| perror("Unable to create client thread"); |
| delete_client(client); |
| } |
| } |
| } |
| |
| #ifdef HAVE_DNSSD |
| if (polldata[2].revents & POLLIN) |
| DNSServiceProcessResult(DNSSDMaster); |
| #endif /* HAVE_DNSSD */ |
| |
| /* |
| * Clean out old jobs... |
| */ |
| |
| clean_jobs(printer); |
| } |
| } |
| |
| |
| /* |
| * 'show_media()' - Show media load state. |
| */ |
| |
| static int /* O - 1 on success, 0 on failure */ |
| show_media(ippeve_client_t *client) /* I - Client connection */ |
| { |
| ippeve_printer_t *printer = client->printer; |
| /* Printer */ |
| int i, j, /* Looping vars */ |
| num_ready, /* Number of ready media */ |
| num_sizes, /* Number of media sizes */ |
| num_sources, /* Number of media sources */ |
| num_types; /* Number of media types */ |
| ipp_attribute_t *media_col_ready,/* media-col-ready attribute */ |
| *media_ready, /* media-ready attribute */ |
| *media_sizes, /* media-supported attribute */ |
| *media_sources, /* media-source-supported attribute */ |
| *media_types, /* media-type-supported attribute */ |
| *input_tray; /* printer-input-tray attribute */ |
| ipp_t *media_col; /* media-col value */ |
| const char *media_size, /* media value */ |
| *media_source, /* media-source value */ |
| *media_type, /* media-type value */ |
| *ready_size, /* media-col-ready media-size[-name] value */ |
| *ready_source, /* media-col-ready media-source value */ |
| *ready_tray, /* printer-input-tray value */ |
| *ready_type; /* media-col-ready media-type value */ |
| char tray_str[1024], /* printer-input-tray string value */ |
| *tray_ptr; /* Pointer into value */ |
| int tray_len; /* Length of printer-input-tray value */ |
| int ready_sheets; /* printer-input-tray sheets value */ |
| int num_options = 0;/* Number of form options */ |
| cups_option_t *options = NULL;/* Form options */ |
| static const int sheets[] = /* Number of sheets */ |
| { |
| 250, |
| 125, |
| 50, |
| 25, |
| 5, |
| 0, |
| -2 |
| }; |
| |
| |
| if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0)) |
| return (0); |
| |
| html_header(client, printer->name, 0); |
| |
| if ((media_col_ready = ippFindAttribute(printer->attrs, "media-col-ready", IPP_TAG_BEGIN_COLLECTION)) == NULL) |
| { |
| html_printf(client, "<p>Error: No media-col-ready defined for printer.</p>\n"); |
| html_footer(client); |
| return (1); |
| } |
| |
| media_ready = ippFindAttribute(printer->attrs, "media-ready", IPP_TAG_ZERO); |
| |
| if ((media_sizes = ippFindAttribute(printer->attrs, "media-supported", IPP_TAG_ZERO)) == NULL) |
| { |
| html_printf(client, "<p>Error: No media-supported defined for printer.</p>\n"); |
| html_footer(client); |
| return (1); |
| } |
| |
| if ((media_sources = ippFindAttribute(printer->attrs, "media-source-supported", IPP_TAG_ZERO)) == NULL) |
| { |
| html_printf(client, "<p>Error: No media-source-supported defined for printer.</p>\n"); |
| html_footer(client); |
| return (1); |
| } |
| |
| if ((media_types = ippFindAttribute(printer->attrs, "media-type-supported", IPP_TAG_ZERO)) == NULL) |
| { |
| html_printf(client, "<p>Error: No media-type-supported defined for printer.</p>\n"); |
| html_footer(client); |
| return (1); |
| } |
| |
| if ((input_tray = ippFindAttribute(printer->attrs, "printer-input-tray", IPP_TAG_STRING)) == NULL) |
| { |
| html_printf(client, "<p>Error: No printer-input-tray defined for printer.</p>\n"); |
| html_footer(client); |
| return (1); |
| } |
| |
| num_ready = ippGetCount(media_col_ready); |
| num_sizes = ippGetCount(media_sizes); |
| num_sources = ippGetCount(media_sources); |
| num_types = ippGetCount(media_types); |
| |
| if (num_sources != ippGetCount(input_tray)) |
| { |
| html_printf(client, "<p>Error: Different number of trays in media-source-supported and printer-input-tray defined for printer.</p>\n"); |
| html_footer(client); |
| return (1); |
| } |
| |
| /* |
| * Process form data if present... |
| */ |
| |
| if (printer->web_forms) |
| num_options = parse_options(client, &options); |
| |
| if (num_options > 0) |
| { |
| /* |
| * WARNING: A real printer/server implementation MUST NOT implement |
| * media updates via a GET request - GET requests are supposed to be |
| * idempotent (without side-effects) and we obviously are not |
| * authenticating access here. This form is provided solely to |
| * enable testing and development! |
| */ |
| |
| char name[255]; /* Form name */ |
| const char *val; /* Form value */ |
| pwg_media_t *media; /* Media info */ |
| |
| _cupsRWLockWrite(&printer->rwlock); |
| |
| ippDeleteAttribute(printer->attrs, media_col_ready); |
| media_col_ready = NULL; |
| |
| if (media_ready) |
| { |
| ippDeleteAttribute(printer->attrs, media_ready); |
| media_ready = NULL; |
| } |
| |
| printer->state_reasons &= (ippeve_preason_t)~(IPPEVE_PREASON_MEDIA_LOW | IPPEVE_PREASON_MEDIA_EMPTY | IPPEVE_PREASON_MEDIA_NEEDED); |
| |
| for (i = 0; i < num_sources; i ++) |
| { |
| media_source = ippGetString(media_sources, i, NULL); |
| |
| if (!strcmp(media_source, "auto") || !strcmp(media_source, "manual") || strstr(media_source, "-man") != NULL) |
| continue; |
| |
| snprintf(name, sizeof(name), "size%d", i); |
| if ((media_size = cupsGetOption(name, num_options, options)) != NULL && (media = pwgMediaForPWG(media_size)) != NULL) |
| { |
| snprintf(name, sizeof(name), "type%d", i); |
| if ((media_type = cupsGetOption(name, num_options, options)) != NULL && !*media_type) |
| media_type = NULL; |
| |
| if (media_ready) |
| ippSetString(printer->attrs, &media_ready, ippGetCount(media_ready), media_size); |
| else |
| media_ready = ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", NULL, media_size); |
| |
| media_col = create_media_col(media_size, media_source, media_type, media->width, media->length, -1, -1, -1, -1); |
| |
| if (media_col_ready) |
| ippSetCollection(printer->attrs, &media_col_ready, ippGetCount(media_col_ready), media_col); |
| else |
| media_col_ready = ippAddCollection(printer->attrs, IPP_TAG_PRINTER, "media-col-ready", media_col); |
| ippDelete(media_col); |
| } |
| else |
| media = NULL; |
| |
| snprintf(name, sizeof(name), "level%d", i); |
| if ((val = cupsGetOption(name, num_options, options)) != NULL) |
| ready_sheets = atoi(val); |
| else |
| ready_sheets = 0; |
| |
| snprintf(tray_str, sizeof(tray_str), "type=sheetFeedAuto%sRemovableTray;mediafeed=%d;mediaxfeed=%d;maxcapacity=%d;level=%d;status=0;name=%s;", !strcmp(media_source, "by-pass-tray") ? "Non" : "", media ? media->length : 0, media ? media->width : 0, strcmp(media_source, "by-pass-tray") ? 250 : 25, ready_sheets, media_source); |
| |
| ippSetOctetString(printer->attrs, &input_tray, i, tray_str, (int)strlen(tray_str)); |
| |
| if (ready_sheets == 0) |
| { |
| printer->state_reasons |= IPPEVE_PREASON_MEDIA_EMPTY; |
| if (printer->active_job) |
| printer->state_reasons |= IPPEVE_PREASON_MEDIA_NEEDED; |
| } |
| else if (ready_sheets < 25 && ready_sheets > 0) |
| printer->state_reasons |= IPPEVE_PREASON_MEDIA_LOW; |
| } |
| |
| if (!media_col_ready) |
| media_col_ready = ippAddOutOfBand(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, "media-col-ready"); |
| |
| if (!media_ready) |
| media_ready = ippAddOutOfBand(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, "media-ready"); |
| |
| _cupsRWUnlock(&printer->rwlock); |
| } |
| |
| if (printer->web_forms) |
| html_printf(client, "<form method=\"GET\" action=\"/media\">\n"); |
| |
| html_printf(client, "<table class=\"form\" summary=\"Media\">\n"); |
| for (i = 0; i < num_sources; i ++) |
| { |
| media_source = ippGetString(media_sources, i, NULL); |
| |
| if (!strcmp(media_source, "auto") || !strcmp(media_source, "manual") || strstr(media_source, "-man") != NULL) |
| continue; |
| |
| for (j = 0, ready_size = NULL, ready_type = NULL; j < num_ready; j ++) |
| { |
| media_col = ippGetCollection(media_col_ready, j); |
| ready_size = ippGetString(ippFindAttribute(media_col, "media-size-name", IPP_TAG_ZERO), 0, NULL); |
| ready_source = ippGetString(ippFindAttribute(media_col, "media-source", IPP_TAG_ZERO), 0, NULL); |
| ready_type = ippGetString(ippFindAttribute(media_col, "media-type", IPP_TAG_ZERO), 0, NULL); |
| |
| if (ready_source && !strcmp(ready_source, media_source)) |
| break; |
| |
| ready_source = NULL; |
| ready_size = NULL; |
| ready_type = NULL; |
| } |
| |
| html_printf(client, "<tr><th>%s:</th>", media_source); |
| |
| /* |
| * Media size... |
| */ |
| |
| if (printer->web_forms) |
| { |
| html_printf(client, "<td><select name=\"size%d\"><option value=\"\">None</option>", i); |
| for (j = 0; j < num_sizes; j ++) |
| { |
| media_size = ippGetString(media_sizes, j, NULL); |
| |
| html_printf(client, "<option%s>%s</option>", (ready_size && !strcmp(ready_size, media_size)) ? " selected" : "", media_size); |
| } |
| html_printf(client, "</select>"); |
| } |
| else |
| html_printf(client, "<td>%s", ready_size); |
| |
| /* |
| * Media type... |
| */ |
| |
| if (printer->web_forms) |
| { |
| html_printf(client, " <select name=\"type%d\"><option value=\"\">None</option>", i); |
| for (j = 0; j < num_types; j ++) |
| { |
| media_type = ippGetString(media_types, j, NULL); |
| |
| html_printf(client, "<option%s>%s</option>", (ready_type && !strcmp(ready_type, media_type)) ? " selected" : "", media_type); |
| } |
| html_printf(client, "</select>"); |
| } |
| else if (ready_type) |
| html_printf(client, ", %s", ready_type); |
| |
| /* |
| * Level/sheets loaded... |
| */ |
| |
| if ((ready_tray = ippGetOctetString(input_tray, i, &tray_len)) != NULL) |
| { |
| if (tray_len > (int)(sizeof(tray_str) - 1)) |
| tray_len = (int)sizeof(tray_str) - 1; |
| memcpy(tray_str, ready_tray, (size_t)tray_len); |
| tray_str[tray_len] = '\0'; |
| |
| if ((tray_ptr = strstr(tray_str, "level=")) != NULL) |
| ready_sheets = atoi(tray_ptr + 6); |
| else |
| ready_sheets = 0; |
| } |
| else |
| ready_sheets = 0; |
| |
| if (printer->web_forms) |
| { |
| html_printf(client, " <select name=\"level%d\">", i); |
| for (j = 0; j < (int)(sizeof(sheets) / sizeof(sheets[0])); j ++) |
| { |
| if (!strcmp(media_source, "by-pass-tray") && sheets[j] > 25) |
| continue; |
| |
| if (sheets[j] < 0) |
| html_printf(client, "<option value=\"%d\"%s>Unknown</option>", sheets[j], sheets[j] == ready_sheets ? " selected" : ""); |
| else |
| html_printf(client, "<option value=\"%d\"%s>%d sheets</option>", sheets[j], sheets[j] == ready_sheets ? " selected" : "", sheets[j]); |
| } |
| html_printf(client, "</select></td></tr>\n"); |
| } |
| else if (ready_sheets == 1) |
| html_printf(client, ", 1 sheet</td></tr>\n"); |
| else if (ready_sheets > 0) |
| html_printf(client, ", %d sheets</td></tr>\n", ready_sheets); |
| else |
| html_printf(client, "</td></tr>\n"); |
| } |
| |
| if (printer->web_forms) |
| { |
| html_printf(client, "<tr><td></td><td><input type=\"submit\" value=\"Update Media\">"); |
| if (num_options > 0) |
| html_printf(client, " <span class=\"badge\" id=\"status\">Media updated.</span>\n"); |
| html_printf(client, "</td></tr></table></form>\n"); |
| |
| if (num_options > 0) |
| html_printf(client, "<script>\n" |
| "setTimeout(hide_status, 3000);\n" |
| "function hide_status() {\n" |
| " var status = document.getElementById('status');\n" |
| " status.style.display = 'none';\n" |
| "}\n" |
| "</script>\n"); |
| } |
| else |
| html_printf(client, "</table>\n"); |
| |
| html_footer(client); |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'show_status()' - Show printer/system state. |
| */ |
| |
| static int /* O - 1 on success, 0 on failure */ |
| show_status(ippeve_client_t *client) /* I - Client connection */ |
| { |
| ippeve_printer_t *printer = client->printer; |
| /* Printer */ |
| ippeve_job_t *job; /* Current job */ |
| int i; /* Looping var */ |
| ippeve_preason_t reason; /* Current reason */ |
| static const char * const reasons[] = /* Reason strings */ |
| { |
| "Other", |
| "Cover Open", |
| "Input Tray Missing", |
| "Marker Supply Empty", |
| "Marker Supply Low", |
| "Marker Waste Almost Full", |
| "Marker Waste Full", |
| "Media Empty", |
| "Media Jam", |
| "Media Low", |
| "Media Needed", |
| "Moving to Paused", |
| "Paused", |
| "Spool Area Full", |
| "Toner Empty", |
| "Toner Low" |
| }; |
| static const char * const state_colors[] = |
| { /* State colors */ |
| "#0C0", /* Idle */ |
| "#EE0", /* Processing */ |
| "#C00" /* Stopped */ |
| }; |
| |
| |
| if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0)) |
| return (0); |
| |
| html_header(client, printer->name, printer->state == IPP_PSTATE_PROCESSING ? 5 : 15); |
| html_printf(client, "<h1><img style=\"background: %s; border-radius: 10px; float: left; margin-right: 10px; padding: 10px;\" src=\"/icon.png\" width=\"64\" height=\"64\">%s Jobs</h1>\n", state_colors[printer->state - IPP_PSTATE_IDLE], printer->name); |
| html_printf(client, "<p>%s, %d job(s).", printer->state == IPP_PSTATE_IDLE ? "Idle" : printer->state == IPP_PSTATE_PROCESSING ? "Printing" : "Stopped", cupsArrayCount(printer->jobs)); |
| for (i = 0, reason = 1; i < (int)(sizeof(reasons) / sizeof(reasons[0])); i ++, reason <<= 1) |
| if (printer->state_reasons & reason) |
| html_printf(client, "\n<br> %s", reasons[i]); |
| html_printf(client, "</p>\n"); |
| |
| if (cupsArrayCount(printer->jobs) > 0) |
| { |
| _cupsRWLockRead(&(printer->rwlock)); |
| |
| html_printf(client, "<table class=\"striped\" summary=\"Jobs\"><thead><tr><th>Job #</th><th>Name</th><th>Owner</th><th>Status</th></tr></thead><tbody>\n"); |
| for (job = (ippeve_job_t *)cupsArrayFirst(printer->jobs); job; job = (ippeve_job_t *)cupsArrayNext(printer->jobs)) |
| { |
| char when[256], /* When job queued/started/finished */ |
| hhmmss[64]; /* Time HH:MM:SS */ |
| |
| switch (job->state) |
| { |
| case IPP_JSTATE_PENDING : |
| case IPP_JSTATE_HELD : |
| snprintf(when, sizeof(when), "Queued at %s", time_string(job->created, hhmmss, sizeof(hhmmss))); |
| break; |
| case IPP_JSTATE_PROCESSING : |
| case IPP_JSTATE_STOPPED : |
| snprintf(when, sizeof(when), "Started at %s", time_string(job->processing, hhmmss, sizeof(hhmmss))); |
| break; |
| case IPP_JSTATE_ABORTED : |
| snprintf(when, sizeof(when), "Aborted at %s", time_string(job->completed, hhmmss, sizeof(hhmmss))); |
| break; |
| case IPP_JSTATE_CANCELED : |
| snprintf(when, sizeof(when), "Canceled at %s", time_string(job->completed, hhmmss, sizeof(hhmmss))); |
| break; |
| case IPP_JSTATE_COMPLETED : |
| snprintf(when, sizeof(when), "Completed at %s", time_string(job->completed, hhmmss, sizeof(hhmmss))); |
| break; |
| } |
| |
| html_printf(client, "<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td></tr>\n", job->id, job->name, job->username, when); |
| } |
| html_printf(client, "</tbody></table>\n"); |
| |
| _cupsRWUnlock(&(printer->rwlock)); |
| } |
| |
| html_footer(client); |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'show_supplies()' - Show printer supplies. |
| */ |
| |
| static int /* O - 1 on success, 0 on failure */ |
| show_supplies( |
| ippeve_client_t *client) /* I - Client connection */ |
| { |
| ippeve_printer_t *printer = client->printer; |
| /* Printer */ |
| int i, /* Looping var */ |
| num_supply; /* Number of supplies */ |
| ipp_attribute_t *supply, /* printer-supply attribute */ |
| *supply_desc; /* printer-supply-description attribute */ |
| int num_options = 0; /* Number of form options */ |
| cups_option_t *options = NULL; /* Form options */ |
| int supply_len, /* Length of supply value */ |
| level; /* Supply level */ |
| const char *supply_value; /* Supply value */ |
| char supply_text[1024], /* Supply string */ |
| *supply_ptr; /* Pointer into supply string */ |
| static const char * const printer_supply[] = |
| { /* printer-supply values */ |
| "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;" |
| "maxcapacity=100;level=%d;colorantname=unknown;", |
| "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;" |
| "maxcapacity=100;level=%d;colorantname=black;", |
| "index=3;class=supplyThatIsConsumed;type=toner;unit=percent;" |
| "maxcapacity=100;level=%d;colorantname=cyan;", |
| "index=4;class=supplyThatIsConsumed;type=toner;unit=percent;" |
| "maxcapacity=100;level=%d;colorantname=magenta;", |
| "index=5;class=supplyThatIsConsumed;type=toner;unit=percent;" |
| "maxcapacity=100;level=%d;colorantname=yellow;" |
| }; |
| static const char * const backgrounds[] = |
| { /* Background colors for the supply-level bars */ |
| "#777 linear-gradient(#333,#777)", |
| "#000 linear-gradient(#666,#000)", |
| "#0FF linear-gradient(#6FF,#0FF)", |
| "#F0F linear-gradient(#F6F,#F0F)", |
| "#CC0 linear-gradient(#EE6,#EE0)" |
| }; |
| static const char * const colors[] = /* Text colors for the supply-level bars */ |
| { |
| "#fff", |
| "#fff", |
| "#000", |
| "#000", |
| "#000" |
| }; |
| |
| |
| if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0)) |
| return (0); |
| |
| html_header(client, printer->name, 0); |
| |
| if ((supply = ippFindAttribute(printer->attrs, "printer-supply", IPP_TAG_STRING)) == NULL) |
| { |
| html_printf(client, "<p>Error: No printer-supply defined for printer.</p>\n"); |
| html_footer(client); |
| return (1); |
| } |
| |
| num_supply = ippGetCount(supply); |
| |
| if ((supply_desc = ippFindAttribute(printer->attrs, "printer-supply-description", IPP_TAG_TEXT)) == NULL) |
| { |
| html_printf(client, "<p>Error: No printer-supply-description defined for printer.</p>\n"); |
| html_footer(client); |
| return (1); |
| } |
| |
| if (num_supply != ippGetCount(supply_desc)) |
| { |
| html_printf(client, "<p>Error: Different number of values for printer-supply and printer-supply-description defined for printer.</p>\n"); |
| html_footer(client); |
| return (1); |
| } |
| |
| if (printer->web_forms) |
| num_options = parse_options(client, &options); |
| |
| if (num_options > 0) |
| { |
| /* |
| * WARNING: A real printer/server implementation MUST NOT implement |
| * supply updates via a GET request - GET requests are supposed to be |
| * idempotent (without side-effects) and we obviously are not |
| * authenticating access here. This form is provided solely to |
| * enable testing and development! |
| */ |
| |
| char name[64]; /* Form field */ |
| const char *val; /* Form value */ |
| |
| _cupsRWLockWrite(&printer->rwlock); |
| |
| ippDeleteAttribute(printer->attrs, supply); |
| supply = NULL; |
| |
| printer->state_reasons &= (ippeve_preason_t)~(IPPEVE_PREASON_MARKER_SUPPLY_EMPTY | IPPEVE_PREASON_MARKER_SUPPLY_LOW | IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL | IPPEVE_PREASON_MARKER_WASTE_FULL | IPPEVE_PREASON_TONER_EMPTY | IPPEVE_PREASON_TONER_LOW); |
| |
| for (i = 0; i < num_supply; i ++) |
| { |
| snprintf(name, sizeof(name), "supply%d", i); |
| if ((val = cupsGetOption(name, num_options, options)) != NULL) |
| { |
| level = atoi(val); /* New level */ |
| |
| snprintf(supply_text, sizeof(supply_text), printer_supply[i], level); |
| if (supply) |
| ippSetOctetString(printer->attrs, &supply, ippGetCount(supply), supply_text, (int)strlen(supply_text)); |
| else |
| supply = ippAddOctetString(printer->attrs, IPP_TAG_PRINTER, "printer-supply", supply_text, (int)strlen(supply_text)); |
| |
| if (i == 0) |
| { |
| if (level == 100) |
| printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_FULL; |
| else if (level > 90) |
| printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL; |
| } |
| else |
| { |
| if (level == 0) |
| printer->state_reasons |= IPPEVE_PREASON_TONER_EMPTY; |
| else if (level < 10) |
| printer->state_reasons |= IPPEVE_PREASON_TONER_LOW; |
| } |
| } |
| } |
| |
| _cupsRWUnlock(&printer->rwlock); |
| } |
| |
| if (printer->web_forms) |
| html_printf(client, "<form method=\"GET\" action=\"/supplies\">\n"); |
| |
| html_printf(client, "<table class=\"form\" summary=\"Supplies\">\n"); |
| for (i = 0; i < num_supply; i ++) |
| { |
| supply_value = ippGetOctetString(supply, i, &supply_len); |
| if (supply_len > (int)(sizeof(supply_text) - 1)) |
| supply_len = (int)sizeof(supply_text) - 1; |
| |
| memcpy(supply_text, supply_value, (size_t)supply_len); |
| supply_text[supply_len] = '\0'; |
| |
| if ((supply_ptr = strstr(supply_text, "level=")) != NULL) |
| level = atoi(supply_ptr + 6); |
| else |
| level = 50; |
| |
| if (printer->web_forms) |
| html_printf(client, "<tr><th>%s:</th><td><input name=\"supply%d\" size=\"3\" value=\"%d\"></td>", ippGetString(supply_desc, i, NULL), i, level); |
| else |
| html_printf(client, "<tr><th>%s:</th>", ippGetString(supply_desc, i, NULL)); |
| |
| if (level < 10) |
| html_printf(client, "<td class=\"meter\"><span class=\"bar\" style=\"background: %s; padding: 5px %dpx;\"></span> %d%%</td></tr>\n", backgrounds[i], level * 2, level); |
| else |
| html_printf(client, "<td class=\"meter\"><span class=\"bar\" style=\"background: %s; color: %s; padding: 5px %dpx;\">%d%%</span></td></tr>\n", backgrounds[i], colors[i], level * 2, level); |
| } |
| |
| if (printer->web_forms) |
| { |
| html_printf(client, "<tr><td></td><td colspan=\"2\"><input type=\"submit\" value=\"Update Supplies\">"); |
| if (num_options > 0) |
| html_printf(client, " <span class=\"badge\" id=\"status\">Supplies updated.</span>\n"); |
| html_printf(client, "</td></tr>\n</table>\n</form>\n"); |
| |
| if (num_options > 0) |
| html_printf(client, "<script>\n" |
| "setTimeout(hide_status, 3000);\n" |
| "function hide_status() {\n" |
| " var status = document.getElementById('status');\n" |
| " status.style.display = 'none';\n" |
| "}\n" |
| "</script>\n"); |
| } |
| else |
| html_printf(client, "</table>\n"); |
| |
| html_footer(client); |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'time_string()' - Return the local time in hours, minutes, and seconds. |
| */ |
| |
| static char * |
| time_string(time_t tv, /* I - Time value */ |
| char *buffer, /* I - Buffer */ |
| size_t bufsize) /* I - Size of buffer */ |
| { |
| struct tm date; /* Local time and date */ |
| |
| localtime_r(&tv, &date); |
| |
| strftime(buffer, bufsize, "%X", &date); |
| |
| return (buffer); |
| } |
| |
| |
| /* |
| * 'usage()' - Show program usage. |
| */ |
| |
| static void |
| usage(int status) /* O - Exit status */ |
| { |
| _cupsLangPuts(stdout, _("Usage: ippeveprinter [options] \"name\"")); |
| _cupsLangPuts(stdout, _("Options:")); |
| _cupsLangPuts(stdout, _("--help Show program help")); |
| _cupsLangPuts(stdout, _("--no-web-forms Disable web forms for media and supplies")); |
| _cupsLangPuts(stdout, _("--pam-service service Use the named PAM service")); |
| _cupsLangPuts(stdout, _("--version Show program version")); |
| _cupsLangPuts(stdout, _("-2 Set 2-sided printing support (default=1-sided)")); |
| _cupsLangPuts(stdout, _("-A Enable authentication")); |
| _cupsLangPuts(stdout, _("-D device-uri Set the device URI for the printer")); |
| _cupsLangPuts(stdout, _("-F output-type/subtype Set the output format for the printer")); |
| #ifdef HAVE_SSL |
| _cupsLangPuts(stdout, _("-K keypath Set location of server X.509 certificates and keys.")); |
| #endif /* HAVE_SSL */ |
| _cupsLangPuts(stdout, _("-M manufacturer Set manufacturer name (default=Test)")); |
| _cupsLangPuts(stdout, _("-P filename.ppd Load printer attributes from PPD file")); |
| _cupsLangPuts(stdout, _("-V version Set default IPP version")); |
| _cupsLangPuts(stdout, _("-a filename.conf Load printer attributes from conf file")); |
| _cupsLangPuts(stdout, _("-c command Set print command")); |
| _cupsLangPuts(stdout, _("-d spool-directory Set spool directory")); |
| _cupsLangPuts(stdout, _("-f type/subtype[,...] Set supported file types")); |
| _cupsLangPuts(stdout, _("-i iconfile.png Set icon file")); |
| _cupsLangPuts(stdout, _("-k Keep job spool files")); |
| _cupsLangPuts(stdout, _("-l location Set location of printer")); |
| _cupsLangPuts(stdout, _("-m model Set model name (default=Printer)")); |
| _cupsLangPuts(stdout, _("-n hostname Set hostname for printer")); |
| _cupsLangPuts(stdout, _("-p port Set port number for printer")); |
| _cupsLangPuts(stdout, _("-r subtype,[subtype] Set DNS-SD service subtype")); |
| _cupsLangPuts(stdout, _("-s speed[,color-speed] Set speed in pages per minute")); |
| _cupsLangPuts(stdout, _("-v Be verbose")); |
| |
| exit(status); |
| } |
| |
| |
| /* |
| * 'valid_doc_attributes()' - Determine whether the document attributes are |
| * valid. |
| * |
| * When one or more document attributes are invalid, this function adds a |
| * suitable response and attributes to the unsupported group. |
| */ |
| |
| static int /* O - 1 if valid, 0 if not */ |
| valid_doc_attributes( |
| ippeve_client_t *client) /* I - Client */ |
| { |
| int valid = 1; /* Valid attributes? */ |
| ipp_op_t op = ippGetOperation(client->request); |
| /* IPP operation */ |
| const char *op_name = ippOpString(op); |
| /* IPP operation name */ |
| ipp_attribute_t *attr, /* Current attribute */ |
| *supported; /* xxx-supported attribute */ |
| const char *compression = NULL, |
| /* compression value */ |
| *format = NULL; /* document-format value */ |
| |
| |
| /* |
| * Check operation attributes... |
| */ |
| |
| if ((attr = ippFindAttribute(client->request, "compression", IPP_TAG_ZERO)) != NULL) |
| { |
| /* |
| * If compression is specified, only accept a supported value in a Print-Job |
| * or Send-Document request... |
| */ |
| |
| compression = ippGetString(attr, 0, NULL); |
| supported = ippFindAttribute(client->printer->attrs, |
| "compression-supported", IPP_TAG_KEYWORD); |
| |
| if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD || |
| ippGetGroupTag(attr) != IPP_TAG_OPERATION || |
| (op != IPP_OP_PRINT_JOB && op != IPP_OP_SEND_DOCUMENT && |
| op != IPP_OP_VALIDATE_JOB) || |
| !ippContainsString(supported, compression)) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| else |
| { |
| fprintf(stderr, "%s %s compression=\"%s\"\n", client->hostname, op_name, compression); |
| |
| ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "compression-supplied", NULL, compression); |
| |
| if (strcmp(compression, "none")) |
| { |
| if (Verbosity) |
| fprintf(stderr, "Receiving job file with \"%s\" compression.\n", compression); |
| httpSetField(client->http, HTTP_FIELD_CONTENT_ENCODING, compression); |
| } |
| } |
| } |
| |
| /* |
| * Is it a format we support? |
| */ |
| |
| if ((attr = ippFindAttribute(client->request, "document-format", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_MIMETYPE || |
| ippGetGroupTag(attr) != IPP_TAG_OPERATION) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| else |
| { |
| format = ippGetString(attr, 0, NULL); |
| |
| fprintf(stderr, "%s %s document-format=\"%s\"\n", client->hostname, op_name, format); |
| |
| ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, "document-format-supplied", NULL, format); |
| } |
| } |
| else |
| { |
| format = ippGetString(ippFindAttribute(client->printer->attrs, "document-format-default", IPP_TAG_MIMETYPE), 0, NULL); |
| if (!format) |
| format = "application/octet-stream"; /* Should never happen */ |
| |
| attr = ippAddString(client->request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, format); |
| } |
| |
| if (format && !strcmp(format, "application/octet-stream") && (ippGetOperation(client->request) == IPP_OP_PRINT_JOB || ippGetOperation(client->request) == IPP_OP_SEND_DOCUMENT)) |
| { |
| /* |
| * Auto-type the file using the first 8 bytes of the file... |
| */ |
| |
| unsigned char header[8]; /* First 8 bytes of file */ |
| |
| memset(header, 0, sizeof(header)); |
| httpPeek(client->http, (char *)header, sizeof(header)); |
| |
| if (!memcmp(header, "%PDF", 4)) |
| format = "application/pdf"; |
| else if (!memcmp(header, "%!", 2)) |
| format = "application/postscript"; |
| else if (!memcmp(header, "\377\330\377", 3) && header[3] >= 0xe0 && header[3] <= 0xef) |
| format = "image/jpeg"; |
| else if (!memcmp(header, "\211PNG", 4)) |
| format = "image/png"; |
| else if (!memcmp(header, "RAS2", 4)) |
| format = "image/pwg-raster"; |
| else if (!memcmp(header, "UNIRAST", 8)) |
| format = "image/urf"; |
| else |
| format = NULL; |
| |
| if (format) |
| { |
| fprintf(stderr, "%s %s Auto-typed document-format=\"%s\"\n", client->hostname, op_name, format); |
| |
| ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, "document-format-detected", NULL, format); |
| } |
| } |
| |
| if (op != IPP_OP_CREATE_JOB && (supported = ippFindAttribute(client->printer->attrs, "document-format-supported", IPP_TAG_MIMETYPE)) != NULL && !ippContainsString(supported, format)) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| |
| /* |
| * document-name |
| */ |
| |
| if ((attr = ippFindAttribute(client->request, "document-name", IPP_TAG_NAME)) != NULL) |
| ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_NAME, "document-name-supplied", NULL, ippGetString(attr, 0, NULL)); |
| |
| return (valid); |
| } |
| |
| |
| /* |
| * 'valid_job_attributes()' - Determine whether the job attributes are valid. |
| * |
| * When one or more job attributes are invalid, this function adds a suitable |
| * response and attributes to the unsupported group. |
| */ |
| |
| static int /* O - 1 if valid, 0 if not */ |
| valid_job_attributes( |
| ippeve_client_t *client) /* I - Client */ |
| { |
| int i, /* Looping var */ |
| count, /* Number of values */ |
| valid = 1; /* Valid attributes? */ |
| ipp_attribute_t *attr, /* Current attribute */ |
| *supported; /* xxx-supported attribute */ |
| |
| |
| /* |
| * Check operation attributes... |
| */ |
| |
| valid = valid_doc_attributes(client); |
| |
| /* |
| * Check the various job template attributes... |
| */ |
| |
| if ((attr = ippFindAttribute(client->request, "copies", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_INTEGER || |
| ippGetInteger(attr, 0) < 1 || ippGetInteger(attr, 0) > 999) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "ipp-attribute-fidelity", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_BOOLEAN) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "job-hold-until", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || |
| (ippGetValueTag(attr) != IPP_TAG_NAME && |
| ippGetValueTag(attr) != IPP_TAG_NAMELANG && |
| ippGetValueTag(attr) != IPP_TAG_KEYWORD) || |
| strcmp(ippGetString(attr, 0, NULL), "no-hold")) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "job-impressions", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_INTEGER || ippGetInteger(attr, 0) < 0) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "job-name", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || |
| (ippGetValueTag(attr) != IPP_TAG_NAME && |
| ippGetValueTag(attr) != IPP_TAG_NAMELANG)) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| |
| ippSetGroupTag(client->request, &attr, IPP_TAG_JOB); |
| } |
| else |
| ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, "Untitled"); |
| |
| if ((attr = ippFindAttribute(client->request, "job-priority", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_INTEGER || |
| ippGetInteger(attr, 0) < 1 || ippGetInteger(attr, 0) > 100) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "job-sheets", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || |
| (ippGetValueTag(attr) != IPP_TAG_NAME && |
| ippGetValueTag(attr) != IPP_TAG_NAMELANG && |
| ippGetValueTag(attr) != IPP_TAG_KEYWORD) || |
| strcmp(ippGetString(attr, 0, NULL), "none")) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "media", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || |
| (ippGetValueTag(attr) != IPP_TAG_NAME && |
| ippGetValueTag(attr) != IPP_TAG_NAMELANG && |
| ippGetValueTag(attr) != IPP_TAG_KEYWORD)) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| else |
| { |
| supported = ippFindAttribute(client->printer->attrs, "media-supported", IPP_TAG_KEYWORD); |
| |
| if (!ippContainsString(supported, ippGetString(attr, 0, NULL))) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "media-col", IPP_TAG_ZERO)) != NULL) |
| { |
| ipp_t *col, /* media-col collection */ |
| *size; /* media-size collection */ |
| ipp_attribute_t *member, /* Member attribute */ |
| *x_dim, /* x-dimension */ |
| *y_dim; /* y-dimension */ |
| int x_value, /* y-dimension value */ |
| y_value; /* x-dimension value */ |
| |
| if (ippGetCount(attr) != 1 || |
| ippGetValueTag(attr) != IPP_TAG_BEGIN_COLLECTION) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| |
| col = ippGetCollection(attr, 0); |
| |
| if ((member = ippFindAttribute(col, "media-size-name", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(member) != 1 || |
| (ippGetValueTag(member) != IPP_TAG_NAME && |
| ippGetValueTag(member) != IPP_TAG_NAMELANG && |
| ippGetValueTag(member) != IPP_TAG_KEYWORD)) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| else |
| { |
| supported = ippFindAttribute(client->printer->attrs, "media-supported", IPP_TAG_KEYWORD); |
| |
| if (!ippContainsString(supported, ippGetString(member, 0, NULL))) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| } |
| else if ((member = ippFindAttribute(col, "media-size", IPP_TAG_BEGIN_COLLECTION)) != NULL) |
| { |
| if (ippGetCount(member) != 1) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| else |
| { |
| size = ippGetCollection(member, 0); |
| |
| if ((x_dim = ippFindAttribute(size, "x-dimension", IPP_TAG_INTEGER)) == NULL || ippGetCount(x_dim) != 1 || |
| (y_dim = ippFindAttribute(size, "y-dimension", IPP_TAG_INTEGER)) == NULL || ippGetCount(y_dim) != 1) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| else |
| { |
| x_value = ippGetInteger(x_dim, 0); |
| y_value = ippGetInteger(y_dim, 0); |
| supported = ippFindAttribute(client->printer->attrs, "media-size-supported", IPP_TAG_BEGIN_COLLECTION); |
| count = ippGetCount(supported); |
| |
| for (i = 0; i < count ; i ++) |
| { |
| size = ippGetCollection(supported, i); |
| x_dim = ippFindAttribute(size, "x-dimension", IPP_TAG_ZERO); |
| y_dim = ippFindAttribute(size, "y-dimension", IPP_TAG_ZERO); |
| |
| if (ippContainsInteger(x_dim, x_value) && ippContainsInteger(y_dim, y_value)) |
| break; |
| } |
| |
| if (i >= count) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| } |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "multiple-document-handling", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD || |
| (strcmp(ippGetString(attr, 0, NULL), |
| "separate-documents-uncollated-copies") && |
| strcmp(ippGetString(attr, 0, NULL), |
| "separate-documents-collated-copies"))) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "orientation-requested", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_ENUM || |
| ippGetInteger(attr, 0) < IPP_ORIENT_PORTRAIT || |
| ippGetInteger(attr, 0) > IPP_ORIENT_REVERSE_PORTRAIT) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "page-ranges", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetValueTag(attr) != IPP_TAG_RANGE) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "print-quality", IPP_TAG_ZERO)) != NULL) |
| { |
| if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_ENUM || |
| ippGetInteger(attr, 0) < IPP_QUALITY_DRAFT || |
| ippGetInteger(attr, 0) > IPP_QUALITY_HIGH) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "printer-resolution", IPP_TAG_ZERO)) != NULL) |
| { |
| supported = ippFindAttribute(client->printer->attrs, "printer-resolution-supported", IPP_TAG_RESOLUTION); |
| |
| if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_RESOLUTION || |
| !supported) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| else |
| { |
| int xdpi, /* Horizontal resolution for job template attribute */ |
| ydpi, /* Vertical resolution for job template attribute */ |
| sydpi; /* Vertical resolution for supported value */ |
| ipp_res_t units, /* Units for job template attribute */ |
| sunits; /* Units for supported value */ |
| |
| xdpi = ippGetResolution(attr, 0, &ydpi, &units); |
| count = ippGetCount(supported); |
| |
| for (i = 0; i < count; i ++) |
| { |
| if (xdpi == ippGetResolution(supported, i, &sydpi, &sunits) && ydpi == sydpi && units == sunits) |
| break; |
| } |
| |
| if (i >= count) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| } |
| |
| if ((attr = ippFindAttribute(client->request, "sides", IPP_TAG_ZERO)) != NULL) |
| { |
| const char *sides = ippGetString(attr, 0, NULL); |
| /* "sides" value... */ |
| |
| if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| else if ((supported = ippFindAttribute(client->printer->attrs, "sides-supported", IPP_TAG_KEYWORD)) != NULL) |
| { |
| if (!ippContainsString(supported, sides)) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| else if (strcmp(sides, "one-sided")) |
| { |
| respond_unsupported(client, attr); |
| valid = 0; |
| } |
| } |
| |
| return (valid); |
| } |