2 * Sample IPP Everywhere server for CUPS.
4 * Copyright © 2010-2018 by Apple Inc.
6 * Licensed under Apache License v2.0. See the file "LICENSE" for more
11 * Disable deprecated stuff so we can verify that the public API is sufficient
12 * to implement a server.
15 #define _CUPS_NO_DEPRECATED 1 /* Disable deprecated stuff */
19 * Include necessary headers...
22 #include <config.h> /* CUPS configuration header */
23 #include <cups/cups.h> /* Public API */
24 #include <cups/string-private.h> /* CUPS string functions */
25 #include <cups/thread-private.h> /* For multithreading functions */
38 # define WEXITSTATUS(s) (s)
39 # include <winsock2.h>
43 extern char **environ
;
45 # include <sys/fcntl.h>
46 # include <sys/wait.h>
52 #elif defined(HAVE_AVAHI)
53 # include <avahi-client/client.h>
54 # include <avahi-client/publish.h>
55 # include <avahi-common/error.h>
56 # include <avahi-common/thread-watch.h>
57 #endif /* HAVE_DNSSD */
58 #ifdef HAVE_SYS_MOUNT_H
59 # include <sys/mount.h>
60 #endif /* HAVE_SYS_MOUNT_H */
61 #ifdef HAVE_SYS_STATFS_H
62 # include <sys/statfs.h>
63 #endif /* HAVE_SYS_STATFS_H */
64 #ifdef HAVE_SYS_STATVFS_H
65 # include <sys/statvfs.h>
66 #endif /* HAVE_SYS_STATVFS_H */
69 #endif /* HAVE_SYS_VFS_H */
76 enum _ipp_preason_e
/* printer-state-reasons bit values */
78 _IPP_PREASON_NONE
= 0x0000, /* none */
79 _IPP_PREASON_OTHER
= 0x0001, /* other */
80 _IPP_PREASON_COVER_OPEN
= 0x0002, /* cover-open */
81 _IPP_PREASON_INPUT_TRAY_MISSING
= 0x0004,
82 /* input-tray-missing */
83 _IPP_PREASON_MARKER_SUPPLY_EMPTY
= 0x0008,
84 /* marker-supply-empty */
85 _IPP_PREASON_MARKER_SUPPLY_LOW
= 0x0010,
86 /* marker-supply-low */
87 _IPP_PREASON_MARKER_WASTE_ALMOST_FULL
= 0x0020,
88 /* marker-waste-almost-full */
89 _IPP_PREASON_MARKER_WASTE_FULL
= 0x0040,
90 /* marker-waste-full */
91 _IPP_PREASON_MEDIA_EMPTY
= 0x0080, /* media-empty */
92 _IPP_PREASON_MEDIA_JAM
= 0x0100, /* media-jam */
93 _IPP_PREASON_MEDIA_LOW
= 0x0200, /* media-low */
94 _IPP_PREASON_MEDIA_NEEDED
= 0x0400, /* media-needed */
95 _IPP_PREASON_MOVING_TO_PAUSED
= 0x0800,
96 /* moving-to-paused */
97 _IPP_PREASON_PAUSED
= 0x1000, /* paused */
98 _IPP_PREASON_SPOOL_AREA_FULL
= 0x2000,/* spool-area-full */
99 _IPP_PREASON_TONER_EMPTY
= 0x4000, /* toner-empty */
100 _IPP_PREASON_TONER_LOW
= 0x8000 /* toner-low */
102 typedef unsigned int _ipp_preason_t
; /* Bitfield for printer-state-reasons */
103 static const char * const _ipp_preason_strings
[] =
104 { /* Strings for each bit */
105 /* "none" is implied for no bits set */
108 "input-tray-missing",
109 "marker-supply-empty",
111 "marker-waste-almost-full",
124 typedef enum _ipp_media_class_e
126 _IPP_GENERAL
, /* General-purpose size */
127 _IPP_PHOTO_ONLY
, /* Photo-only size */
128 _IPP_ENV_ONLY
/* Envelope-only size */
129 } _ipp_media_class_t
;
131 typedef enum _ipp_media_size_e
133 _IPP_MEDIA_SIZE_NONE
= -1,
138 _IPP_MEDIA_SIZE_LEGAL
,
139 _IPP_MEDIA_SIZE_LETTER
,
140 _IPP_MEDIA_SIZE_COM10
,
146 static const char * const media_supported
[] =
147 { /* media-supported values */
148 "iso_a4_210x297mm", /* A4 */
149 "iso_a5_148x210mm", /* A5 */
150 "iso_a6_105x148mm", /* A6 */
151 "iso_dl_110x220mm", /* DL */
152 "na_legal_8.5x14in", /* Legal */
153 "na_letter_8.5x11in", /* Letter */
154 "na_number-10_4.125x9.5in", /* #10 */
155 "na_index-3x5_3x5in", /* 3x5 */
156 "oe_photo-l_3.5x5in", /* L */
157 "na_index-4x6_4x6in", /* 4x6 */
158 "na_5x7_5x7in" /* 5x7 aka 2L */
160 static const int media_col_sizes
[][3] =
161 { /* media-col-database sizes */
162 { 21000, 29700, _IPP_GENERAL
}, /* A4 */
163 { 14800, 21000, _IPP_PHOTO_ONLY
}, /* A5 */
164 { 10500, 14800, _IPP_PHOTO_ONLY
}, /* A6 */
165 { 11000, 22000, _IPP_ENV_ONLY
}, /* DL */
166 { 21590, 35560, _IPP_GENERAL
}, /* Legal */
167 { 21590, 27940, _IPP_GENERAL
}, /* Letter */
168 { 10477, 24130, _IPP_ENV_ONLY
}, /* #10 */
169 { 7630, 12700, _IPP_PHOTO_ONLY
}, /* 3x5 */
170 { 8890, 12700, _IPP_PHOTO_ONLY
}, /* L */
171 { 10160, 15240, _IPP_PHOTO_ONLY
}, /* 4x6 */
172 { 12700, 17780, _IPP_PHOTO_ONLY
} /* 5x7 aka 2L */
175 typedef enum _ipp_media_source_e
177 _IPP_MEDIA_SOURCE_NONE
= -1,
178 _IPP_MEDIA_SOURCE_AUTO
,
179 _IPP_MEDIA_SOURCE_MAIN
,
180 _IPP_MEDIA_SOURCE_MANUAL
,
181 _IPP_MEDIA_SOURCE_ENVELOPE
,
182 _IPP_MEDIA_SOURCE_PHOTO
183 } _ipp_media_source_t
;
184 static const char * const media_source_supported
[] =
185 /* media-source-supported values */
194 typedef enum _ipp_media_type_e
196 _IPP_MEDIA_TYPE_NONE
= -1,
197 _IPP_MEDIA_TYPE_AUTO
,
198 _IPP_MEDIA_TYPE_CARDSTOCK
,
199 _IPP_MEDIA_TYPE_ENVELOPE
,
200 _IPP_MEDIA_TYPE_LABELS
,
201 _IPP_MEDIA_TYPE_OTHER
,
202 _IPP_MEDIA_TYPE_GLOSSY
,
203 _IPP_MEDIA_TYPE_HIGH_GLOSS
,
204 _IPP_MEDIA_TYPE_MATTE
,
205 _IPP_MEDIA_TYPE_SATIN
,
206 _IPP_MEDIA_TYPE_SEMI_GLOSS
,
207 _IPP_MEDIA_TYPE_STATIONERY
,
208 _IPP_MEDIA_TYPE_LETTERHEAD
,
209 _IPP_MEDIA_TYPE_TRANSPARENCY
211 static const char * const media_type_supported
[] =
212 /* media-type-supported values */
219 "photographic-glossy",
220 "photographic-high-gloss",
221 "photographic-matte",
222 "photographic-satin",
223 "photographic-semi-gloss",
225 "stationery-letterhead",
229 typedef enum _ipp_supply_e
231 _IPP_SUPPLY_CYAN
, /* Cyan Toner */
232 _IPP_SUPPLY_MAGENTA
, /* Magenta Toner */
233 _IPP_SUPPLY_YELLOW
, /* Yellow Toner */
234 _IPP_SUPPLY_BLACK
, /* Black Toner */
235 _IPP_SUPPLY_WASTE
/* Waste Toner */
237 static const char * const printer_supplies
[] =
238 { /* printer-supply-description values */
247 * URL scheme for web resources...
251 # define WEB_SCHEME "https"
253 # define WEB_SCHEME "http"
254 #endif /* HAVE_SSL */
262 typedef DNSServiceRef _ipp_srv_t
; /* Service reference */
263 typedef TXTRecordRef _ipp_txt_t
; /* TXT record */
265 #elif defined(HAVE_AVAHI)
266 typedef AvahiEntryGroup
*_ipp_srv_t
; /* Service reference */
267 typedef AvahiStringList
*_ipp_txt_t
; /* TXT record */
270 typedef void *_ipp_srv_t
; /* Service reference */
271 typedef void *_ipp_txt_t
; /* TXT record */
272 #endif /* HAVE_DNSSD */
274 typedef struct _ipp_filter_s
/**** Attribute filter ****/
276 cups_array_t
*ra
; /* Requested attributes */
277 ipp_tag_t group_tag
; /* Group to copy */
280 typedef struct _ipp_job_s _ipp_job_t
;
282 typedef struct _ipp_printer_s
/**** Printer data ****/
284 int ipv4
, /* IPv4 listener */
285 ipv6
; /* IPv6 listener */
286 _ipp_srv_t ipp_ref
, /* Bonjour IPP service */
287 ipps_ref
, /* Bonjour IPPS service */
288 http_ref
, /* Bonjour HTTP service */
289 printer_ref
; /* Bonjour LPD service */
290 char *dnssd_name
, /* printer-dnssd-name */
291 *name
, /* printer-name */
292 *icon
, /* Icon filename */
293 *directory
, /* Spool directory */
294 *hostname
, /* Hostname */
295 *uri
, /* printer-uri-supported */
296 *command
; /* Command to run with job file */
298 size_t urilen
; /* Length of printer URI */
299 ipp_t
*attrs
; /* Static attributes */
300 time_t start_time
; /* Startup time */
301 time_t config_time
; /* printer-config-change-time */
302 ipp_pstate_t state
; /* printer-state value */
303 _ipp_preason_t state_reasons
; /* printer-state-reasons values */
304 time_t state_time
; /* printer-state-change-time */
305 cups_array_t
*jobs
; /* Jobs */
306 _ipp_job_t
*active_job
; /* Current active/pending job */
307 int next_job_id
; /* Next job-id value */
308 _cups_rwlock_t rwlock
; /* Printer lock */
309 _ipp_media_size_t main_size
; /* Ready media */
310 _ipp_media_type_t main_type
;
312 _ipp_media_size_t envelope_size
;
314 _ipp_media_size_t photo_size
;
315 _ipp_media_type_t photo_type
;
317 int supplies
[5]; /* Supply levels (0-100) */
320 struct _ipp_job_s
/**** Job data ****/
323 const char *name
, /* job-name */
324 *username
, /* job-originating-user-name */
325 *format
; /* document-format */
326 ipp_jstate_t state
; /* job-state value */
327 time_t created
, /* time-at-creation value */
328 processing
, /* time-at-processing value */
329 completed
; /* time-at-completed value */
330 int impressions
, /* job-impressions value */
331 impcompleted
; /* job-impressions-completed value */
332 ipp_t
*attrs
; /* Static attributes */
333 int cancel
; /* Non-zero when job canceled */
334 char *filename
; /* Print file name */
335 int fd
; /* Print file descriptor */
336 _ipp_printer_t
*printer
; /* Printer */
339 typedef struct _ipp_client_s
/**** Client data ****/
341 http_t
*http
; /* HTTP connection */
342 ipp_t
*request
, /* IPP request */
343 *response
; /* IPP response */
344 time_t start
; /* Request start time */
345 http_state_t operation
; /* Request operation */
346 ipp_op_t operation_id
; /* IPP operation-id */
347 char uri
[1024], /* Request URI */
348 *options
; /* URI options */
349 http_addr_t addr
; /* Client address */
350 char hostname
[256]; /* Client hostname */
351 _ipp_printer_t
*printer
; /* Printer */
352 _ipp_job_t
*job
; /* Current job, if any */
360 static void clean_jobs(_ipp_printer_t
*printer
);
361 static int compare_jobs(_ipp_job_t
*a
, _ipp_job_t
*b
);
362 static void copy_attributes(ipp_t
*to
, ipp_t
*from
, cups_array_t
*ra
,
363 ipp_tag_t group_tag
, int quickcopy
);
364 static void copy_job_attributes(_ipp_client_t
*client
,
365 _ipp_job_t
*job
, cups_array_t
*ra
);
366 static _ipp_client_t
*create_client(_ipp_printer_t
*printer
, int sock
);
367 static _ipp_job_t
*create_job(_ipp_client_t
*client
);
368 static int create_listener(int family
, int port
);
369 static ipp_t
*create_media_col(const char *media
, const char *source
, const char *type
, int width
, int length
, int margins
);
370 static ipp_t
*create_media_size(int width
, int length
);
371 static _ipp_printer_t
*create_printer(const char *servername
,
372 const char *name
, const char *location
,
373 const char *make
, const char *model
,
375 const char *docformats
, int ppm
,
376 int ppm_color
, int duplex
, int port
,
377 int pin
, const char *subtype
,
378 const char *directory
,
380 const char *attrfile
);
381 static void debug_attributes(const char *title
, ipp_t
*ipp
,
383 static void delete_client(_ipp_client_t
*client
);
384 static void delete_job(_ipp_job_t
*job
);
385 static void delete_printer(_ipp_printer_t
*printer
);
387 static void DNSSD_API
dnssd_callback(DNSServiceRef sdRef
,
388 DNSServiceFlags flags
,
389 DNSServiceErrorType errorCode
,
393 _ipp_printer_t
*printer
);
394 #elif defined(HAVE_AVAHI)
395 static void dnssd_callback(AvahiEntryGroup
*p
, AvahiEntryGroupState state
, void *context
);
396 static void dnssd_client_cb(AvahiClient
*c
, AvahiClientState state
, void *userdata
);
397 #endif /* HAVE_DNSSD */
398 static void dnssd_init(void);
399 static int filter_cb(_ipp_filter_t
*filter
, ipp_t
*dst
, ipp_attribute_t
*attr
);
400 static _ipp_job_t
*find_job(_ipp_client_t
*client
);
401 static ipp_t
*get_collection(FILE *fp
, const char *filename
, int *linenum
);
402 static char *get_token(FILE *fp
, char *buf
, int buflen
, int *linenum
);
403 static void html_escape(_ipp_client_t
*client
, const char *s
,
405 static void html_footer(_ipp_client_t
*client
);
406 static void html_header(_ipp_client_t
*client
, const char *title
);
407 static void html_printf(_ipp_client_t
*client
, const char *format
,
408 ...) __attribute__((__format__(__printf__
,
410 static void ipp_cancel_job(_ipp_client_t
*client
);
411 static void ipp_close_job(_ipp_client_t
*client
);
412 static void ipp_create_job(_ipp_client_t
*client
);
413 static void ipp_get_job_attributes(_ipp_client_t
*client
);
414 static void ipp_get_jobs(_ipp_client_t
*client
);
415 static void ipp_get_printer_attributes(_ipp_client_t
*client
);
416 static void ipp_identify_printer(_ipp_client_t
*client
);
417 static void ipp_print_job(_ipp_client_t
*client
);
418 static void ipp_print_uri(_ipp_client_t
*client
);
419 static void ipp_send_document(_ipp_client_t
*client
);
420 static void ipp_send_uri(_ipp_client_t
*client
);
421 static void ipp_validate_job(_ipp_client_t
*client
);
422 static void load_attributes(const char *filename
, ipp_t
*attrs
);
423 static int parse_options(_ipp_client_t
*client
, cups_option_t
**options
);
424 static void process_attr_message(_ipp_job_t
*job
, char *message
);
425 static void *process_client(_ipp_client_t
*client
);
426 static int process_http(_ipp_client_t
*client
);
427 static int process_ipp(_ipp_client_t
*client
);
428 static void *process_job(_ipp_job_t
*job
);
429 static void process_state_message(_ipp_job_t
*job
, char *message
);
430 static int register_printer(_ipp_printer_t
*printer
, const char *location
, const char *make
, const char *model
, const char *formats
, const char *adminurl
, const char *uuid
, int color
, int duplex
, const char *regtype
);
431 static int respond_http(_ipp_client_t
*client
, http_status_t code
,
432 const char *content_coding
,
433 const char *type
, size_t length
);
434 static void respond_ipp(_ipp_client_t
*client
, ipp_status_t status
,
435 const char *message
, ...)
436 __attribute__ ((__format__ (__printf__
, 3, 4)));
437 static void respond_unsupported(_ipp_client_t
*client
,
438 ipp_attribute_t
*attr
);
439 static void run_printer(_ipp_printer_t
*printer
);
440 static char *time_string(time_t tv
, char *buffer
, size_t bufsize
);
441 static void usage(int status
) __attribute__((noreturn
));
442 static int valid_doc_attributes(_ipp_client_t
*client
);
443 static int valid_job_attributes(_ipp_client_t
*client
);
451 static DNSServiceRef DNSSDMaster
= NULL
;
452 #elif defined(HAVE_AVAHI)
453 static AvahiThreadedPoll
*DNSSDMaster
= NULL
;
454 static AvahiClient
*DNSSDClient
= NULL
;
455 #endif /* HAVE_DNSSD */
457 static int KeepFiles
= 0,
463 * 'main()' - Main entry to the sample server.
466 int /* O - Exit status */
467 main(int argc
, /* I - Number of command-line args */
468 char *argv
[]) /* I - Command-line arguments */
470 int i
; /* Looping var */
471 const char *opt
, /* Current option character */
472 *attrfile
= NULL
, /* Attributes file */
473 *command
= NULL
, /* Command to run with job files */
474 *servername
= NULL
, /* Server host name */
475 *name
= NULL
, /* Printer name */
476 *location
= "", /* Location of printer */
477 *make
= "Test", /* Manufacturer */
478 *model
= "Printer", /* Model */
479 *icon
= "printer.png", /* Icon file */
480 *formats
= "application/pdf,image/jpeg,image/pwg-raster";
481 /* Supported formats */
483 const char *keypath
= NULL
; /* Keychain path */
484 #endif /* HAVE_SSL */
485 const char *subtype
= "_print"; /* Bonjour service subtype */
486 int port
= 0, /* Port number (0 = auto) */
487 duplex
= 0, /* Duplex mode */
488 ppm
= 10, /* Pages per minute for mono */
489 ppm_color
= 0, /* Pages per minute for color */
490 pin
= 0; /* PIN printing mode? */
491 char directory
[1024] = "", /* Spool directory */
492 hostname
[1024]; /* Auto-detected hostname */
493 _ipp_printer_t
*printer
; /* Printer object */
497 * Parse command-line arguments...
500 for (i
= 1; i
< argc
; i
++)
501 if (argv
[i
][0] == '-')
503 for (opt
= argv
[i
] + 1; *opt
; opt
++)
507 case '2' : /* -2 (enable 2-sided printing) */
512 case 'K' : /* -K keypath */
518 #endif /* HAVE_SSL */
520 case 'M' : /* -M manufacturer */
527 case 'P' : /* -P (PIN printing mode) */
531 case 'V' : /* -V max-version */
536 if (!strcmp(argv
[i
], "2.2"))
538 else if (!strcmp(argv
[i
], "2.1"))
540 else if (!strcmp(argv
[i
], "2.0"))
542 else if (!strcmp(argv
[i
], "1.1"))
548 case 'a' : /* -a attributes-file */
556 case 'c' : /* -c command */
564 case 'd' : /* -d spool-directory */
568 strlcpy(directory
, argv
[i
], sizeof(directory
));
571 case 'f' : /* -f type/subtype[,...] */
578 case 'h' : /* -h (show help) */
581 case 'i' : /* -i icon.png */
588 case 'k' : /* -k (keep files) */
592 case 'l' : /* -l location */
599 case 'm' : /* -m model */
606 case 'n' : /* -n hostname */
610 servername
= argv
[i
];
613 case 'p' : /* -p port */
615 if (i
>= argc
|| !isdigit(argv
[i
][0] & 255))
617 port
= atoi(argv
[i
]);
620 case 'r' : /* -r subtype */
627 case 's' : /* -s speed[,color-speed] */
631 if (sscanf(argv
[i
], "%d,%d", &ppm
, &ppm_color
) < 1)
635 case 'v' : /* -v (be verbose) */
639 default : /* Unknown */
640 fprintf(stderr
, "Unknown option \"-%c\".\n", *opt
);
651 fprintf(stderr
, "Unexpected command-line argument \"%s\"\n", argv
[i
]);
659 * Apply defaults as needed...
663 servername
= httpGetHostname(NULL
, hostname
, sizeof(hostname
));
669 * Windows is almost always used as a single user system, so use a default
670 * port number of 8631.
677 * Use 8000 + UID mod 1000 for the default port number...
680 port
= 8000 + ((int)getuid() % 1000);
683 fprintf(stderr
, "Listening on port %d.\n", port
);
688 const char *tmpdir
; /* Temporary directory */
691 if ((tmpdir
= getenv("TEMP")) == NULL
)
693 #elif defined(__APPLE__) && !TARGET_OS_IOS
694 if ((tmpdir
= getenv("TMPDIR")) == NULL
)
695 tmpdir
= "/private/tmp";
697 if ((tmpdir
= getenv("TMPDIR")) == NULL
)
701 snprintf(directory
, sizeof(directory
), "%s/ippserver.%d", tmpdir
, (int)getpid());
703 if (mkdir(directory
, 0755) && errno
!= EEXIST
)
705 fprintf(stderr
, "Unable to create spool directory \"%s\": %s\n",
706 directory
, strerror(errno
));
711 fprintf(stderr
, "Using spool directory \"%s\".\n", directory
);
715 cupsSetServerCredentials(keypath
, servername
, 1);
716 #endif /* HAVE_SSL */
719 * Initialize Bonjour...
725 * Create the printer...
728 if ((printer
= create_printer(servername
, name
, location
, make
, model
, icon
,
729 formats
, ppm
, ppm_color
, duplex
, port
, pin
,
730 subtype
, directory
, command
, attrfile
)) == NULL
)
734 * Run the print service...
737 run_printer(printer
);
740 * Destroy the printer and exit...
743 delete_printer(printer
);
750 * 'clean_jobs()' - Clean out old (completed) jobs.
754 clean_jobs(_ipp_printer_t
*printer
) /* I - Printer */
756 _ipp_job_t
*job
; /* Current job */
757 time_t cleantime
; /* Clean time */
760 if (cupsArrayCount(printer
->jobs
) == 0)
763 cleantime
= time(NULL
) - 60;
765 _cupsRWLockWrite(&(printer
->rwlock
));
766 for (job
= (_ipp_job_t
*)cupsArrayFirst(printer
->jobs
);
768 job
= (_ipp_job_t
*)cupsArrayNext(printer
->jobs
))
769 if (job
->completed
&& job
->completed
< cleantime
)
771 cupsArrayRemove(printer
->jobs
, job
);
776 _cupsRWUnlock(&(printer
->rwlock
));
781 * 'compare_jobs()' - Compare two jobs.
784 static int /* O - Result of comparison */
785 compare_jobs(_ipp_job_t
*a
, /* I - First job */
786 _ipp_job_t
*b
) /* I - Second job */
788 return (b
->id
- a
->id
);
793 * 'copy_attributes()' - Copy attributes from one request to another.
797 copy_attributes(ipp_t
*to
, /* I - Destination request */
798 ipp_t
*from
, /* I - Source request */
799 cups_array_t
*ra
, /* I - Requested attributes */
800 ipp_tag_t group_tag
, /* I - Group to copy */
801 int quickcopy
) /* I - Do a quick copy? */
803 _ipp_filter_t filter
; /* Filter data */
807 filter
.group_tag
= group_tag
;
809 ippCopyAttributes(to
, from
, quickcopy
, (ipp_copycb_t
)filter_cb
, &filter
);
814 * 'copy_job_attrs()' - Copy job attributes to the response.
819 _ipp_client_t
*client
, /* I - Client */
820 _ipp_job_t
*job
, /* I - Job */
821 cups_array_t
*ra
) /* I - requested-attributes */
823 copy_attributes(client
->response
, job
->attrs
, ra
, IPP_TAG_JOB
, 0);
825 if (!ra
|| cupsArrayFind(ra
, "date-time-at-completed"))
828 ippAddDate(client
->response
, IPP_TAG_JOB
, "date-time-at-completed", ippTimeToDate(job
->completed
));
830 ippAddOutOfBand(client
->response
, IPP_TAG_JOB
, IPP_TAG_NOVALUE
, "date-time-at-completed");
833 if (!ra
|| cupsArrayFind(ra
, "date-time-at-processing"))
836 ippAddDate(client
->response
, IPP_TAG_JOB
, "date-time-at-processing", ippTimeToDate(job
->processing
));
838 ippAddOutOfBand(client
->response
, IPP_TAG_JOB
, IPP_TAG_NOVALUE
, "date-time-at-processing");
841 if (!ra
|| cupsArrayFind(ra
, "job-impressions"))
842 ippAddInteger(client
->response
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "job-impressions", job
->impressions
);
844 if (!ra
|| cupsArrayFind(ra
, "job-impressions-completed"))
845 ippAddInteger(client
->response
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "job-impressions-completed", job
->impcompleted
);
847 if (!ra
|| cupsArrayFind(ra
, "job-printer-up-time"))
848 ippAddInteger(client
->response
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "job-printer-up-time", (int)(time(NULL
) - client
->printer
->start_time
));
850 if (!ra
|| cupsArrayFind(ra
, "job-state"))
851 ippAddInteger(client
->response
, IPP_TAG_JOB
, IPP_TAG_ENUM
,
852 "job-state", job
->state
);
854 if (!ra
|| cupsArrayFind(ra
, "job-state-message"))
858 case IPP_JSTATE_PENDING
:
859 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job pending.");
862 case IPP_JSTATE_HELD
:
864 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job incoming.");
865 else if (ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_ZERO
))
866 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job held.");
868 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job created.");
871 case IPP_JSTATE_PROCESSING
:
873 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job canceling.");
875 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job printing.");
878 case IPP_JSTATE_STOPPED
:
879 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job stopped.");
882 case IPP_JSTATE_CANCELED
:
883 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job canceled.");
886 case IPP_JSTATE_ABORTED
:
887 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job aborted.");
890 case IPP_JSTATE_COMPLETED
:
891 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job completed.");
896 if (!ra
|| cupsArrayFind(ra
, "job-state-reasons"))
900 case IPP_JSTATE_PENDING
:
901 ippAddString(client
->response
, IPP_TAG_JOB
,
902 IPP_CONST_TAG(IPP_TAG_KEYWORD
), "job-state-reasons",
906 case IPP_JSTATE_HELD
:
908 ippAddString(client
->response
, IPP_TAG_JOB
,
909 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
910 "job-state-reasons", NULL
, "job-incoming");
911 else if (ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_ZERO
))
912 ippAddString(client
->response
, IPP_TAG_JOB
,
913 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
914 "job-state-reasons", NULL
, "job-hold-until-specified");
916 ippAddString(client
->response
, IPP_TAG_JOB
,
917 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
918 "job-state-reasons", NULL
, "job-data-insufficient");
921 case IPP_JSTATE_PROCESSING
:
923 ippAddString(client
->response
, IPP_TAG_JOB
,
924 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
925 "job-state-reasons", NULL
, "processing-to-stop-point");
927 ippAddString(client
->response
, IPP_TAG_JOB
,
928 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
929 "job-state-reasons", NULL
, "job-printing");
932 case IPP_JSTATE_STOPPED
:
933 ippAddString(client
->response
, IPP_TAG_JOB
,
934 IPP_CONST_TAG(IPP_TAG_KEYWORD
), "job-state-reasons",
935 NULL
, "job-stopped");
938 case IPP_JSTATE_CANCELED
:
939 ippAddString(client
->response
, IPP_TAG_JOB
,
940 IPP_CONST_TAG(IPP_TAG_KEYWORD
), "job-state-reasons",
941 NULL
, "job-canceled-by-user");
944 case IPP_JSTATE_ABORTED
:
945 ippAddString(client
->response
, IPP_TAG_JOB
,
946 IPP_CONST_TAG(IPP_TAG_KEYWORD
), "job-state-reasons",
947 NULL
, "aborted-by-system");
950 case IPP_JSTATE_COMPLETED
:
951 ippAddString(client
->response
, IPP_TAG_JOB
,
952 IPP_CONST_TAG(IPP_TAG_KEYWORD
), "job-state-reasons",
953 NULL
, "job-completed-successfully");
958 if (!ra
|| cupsArrayFind(ra
, "time-at-completed"))
959 ippAddInteger(client
->response
, IPP_TAG_JOB
,
960 job
->completed
? IPP_TAG_INTEGER
: IPP_TAG_NOVALUE
,
961 "time-at-completed", (int)(job
->completed
- client
->printer
->start_time
));
963 if (!ra
|| cupsArrayFind(ra
, "time-at-processing"))
964 ippAddInteger(client
->response
, IPP_TAG_JOB
,
965 job
->processing
? IPP_TAG_INTEGER
: IPP_TAG_NOVALUE
,
966 "time-at-processing", (int)(job
->processing
- client
->printer
->start_time
));
971 * 'create_client()' - Accept a new network connection and create a client
975 static _ipp_client_t
* /* O - Client */
976 create_client(_ipp_printer_t
*printer
, /* I - Printer */
977 int sock
) /* I - Listen socket */
979 _ipp_client_t
*client
; /* Client */
982 if ((client
= calloc(1, sizeof(_ipp_client_t
))) == NULL
)
984 perror("Unable to allocate memory for client");
988 client
->printer
= printer
;
991 * Accept the client and get the remote address...
994 if ((client
->http
= httpAcceptConnection(sock
, 1)) == NULL
)
996 perror("Unable to accept client connection");
1003 httpGetHostname(client
->http
, client
->hostname
, sizeof(client
->hostname
));
1006 fprintf(stderr
, "Accepted connection from %s\n", client
->hostname
);
1013 * 'create_job()' - Create a new job object from a Print-Job or Create-Job
1017 static _ipp_job_t
* /* O - Job */
1018 create_job(_ipp_client_t
*client
) /* I - Client */
1020 _ipp_job_t
*job
; /* Job */
1021 ipp_attribute_t
*attr
; /* Job attribute */
1022 char uri
[1024], /* job-uri value */
1023 uuid
[64]; /* job-uuid value */
1026 _cupsRWLockWrite(&(client
->printer
->rwlock
));
1027 if (client
->printer
->active_job
&&
1028 client
->printer
->active_job
->state
< IPP_JSTATE_CANCELED
)
1031 * Only accept a single job at a time...
1034 _cupsRWUnlock(&(client
->printer
->rwlock
));
1039 * Allocate and initialize the job object...
1042 if ((job
= calloc(1, sizeof(_ipp_job_t
))) == NULL
)
1044 perror("Unable to allocate memory for job");
1048 job
->printer
= client
->printer
;
1049 job
->attrs
= ippNew();
1050 job
->state
= IPP_JSTATE_HELD
;
1054 * Copy all of the job attributes...
1057 copy_attributes(job
->attrs
, client
->request
, NULL
, IPP_TAG_JOB
, 0);
1060 * Get the requesting-user-name, document format, and priority...
1063 if ((attr
= ippFindAttribute(client
->request
, "requesting-user-name", IPP_TAG_NAME
)) != NULL
)
1064 job
->username
= ippGetString(attr
, 0, NULL
);
1066 job
->username
= "anonymous";
1068 ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_NAME
, "job-originating-user-name", NULL
, job
->username
);
1070 if (ippGetOperation(client
->request
) != IPP_OP_CREATE_JOB
)
1072 if ((attr
= ippFindAttribute(job
->attrs
, "document-format-detected", IPP_TAG_MIMETYPE
)) != NULL
)
1073 job
->format
= ippGetString(attr
, 0, NULL
);
1074 else if ((attr
= ippFindAttribute(job
->attrs
, "document-format-supplied", IPP_TAG_MIMETYPE
)) != NULL
)
1075 job
->format
= ippGetString(attr
, 0, NULL
);
1077 job
->format
= "application/octet-stream";
1080 if ((attr
= ippFindAttribute(client
->request
, "job-impressions", IPP_TAG_INTEGER
)) != NULL
)
1081 job
->impressions
= ippGetInteger(attr
, 0);
1083 if ((attr
= ippFindAttribute(client
->request
, "job-name", IPP_TAG_NAME
)) != NULL
)
1084 job
->name
= ippGetString(attr
, 0, NULL
);
1087 * Add job description attributes and add to the jobs array...
1090 job
->id
= client
->printer
->next_job_id
++;
1092 snprintf(uri
, sizeof(uri
), "%s/%d", client
->printer
->uri
, job
->id
);
1093 httpAssembleUUID(client
->printer
->hostname
, client
->printer
->port
, client
->printer
->name
, job
->id
, uuid
, sizeof(uuid
));
1095 ippAddDate(job
->attrs
, IPP_TAG_JOB
, "date-time-at-creation", ippTimeToDate(time(&job
->created
)));
1096 ippAddInteger(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "job-id", job
->id
);
1097 ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_URI
, "job-uri", NULL
, uri
);
1098 ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_URI
, "job-uuid", NULL
, uuid
);
1099 if ((attr
= ippFindAttribute(client
->request
, "printer-uri", IPP_TAG_URI
)) != NULL
)
1100 ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_URI
, "job-printer-uri", NULL
, ippGetString(attr
, 0, NULL
));
1102 ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_URI
, "job-printer-uri", NULL
, client
->printer
->uri
);
1103 ippAddInteger(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "time-at-creation", (int)(job
->created
- client
->printer
->start_time
));
1105 cupsArrayAdd(client
->printer
->jobs
, job
);
1106 client
->printer
->active_job
= job
;
1108 _cupsRWUnlock(&(client
->printer
->rwlock
));
1115 * 'create_job_filename()' - Create the filename for a document in a job.
1118 static void create_job_filename(
1119 _ipp_printer_t
*printer
, /* I - Printer */
1120 _ipp_job_t
*job
, /* I - Job */
1121 char *fname
, /* I - Filename buffer */
1122 size_t fnamesize
) /* I - Size of filename buffer */
1124 char name
[256], /* "Safe" filename */
1125 *nameptr
; /* Pointer into filename */
1126 const char *ext
, /* Filename extension */
1127 *job_name
; /* job-name value */
1128 ipp_attribute_t
*job_name_attr
; /* job-name attribute */
1132 * Make a name from the job-name attribute...
1135 if ((job_name_attr
= ippFindAttribute(job
->attrs
, "job-name", IPP_TAG_NAME
)) != NULL
)
1136 job_name
= ippGetString(job_name_attr
, 0, NULL
);
1138 job_name
= "untitled";
1140 for (nameptr
= name
; *job_name
&& nameptr
< (name
+ sizeof(name
) - 1); job_name
++)
1141 if (isalnum(*job_name
& 255) || *job_name
== '-')
1142 *nameptr
++ = (char)tolower(*job_name
& 255);
1149 * Figure out the extension...
1152 if (!strcasecmp(job
->format
, "image/jpeg"))
1154 else if (!strcasecmp(job
->format
, "image/png"))
1156 else if (!strcasecmp(job
->format
, "image/pwg-raster"))
1158 else if (!strcasecmp(job
->format
, "image/urf"))
1160 else if (!strcasecmp(job
->format
, "application/pdf"))
1162 else if (!strcasecmp(job
->format
, "application/postscript"))
1168 * Create a filename with the job-id, job-name, and document-format (extension)...
1171 snprintf(fname
, fnamesize
, "%s/%d-%s.%s", printer
->directory
, job
->id
, name
, ext
);
1176 * 'create_listener()' - Create a listener socket.
1179 static int /* O - Listener socket or -1 on error */
1180 create_listener(int family
, /* I - Address family */
1181 int port
) /* I - Port number */
1183 int sock
; /* Listener socket */
1184 http_addrlist_t
*addrlist
; /* Listen address */
1185 char service
[255]; /* Service port */
1188 snprintf(service
, sizeof(service
), "%d", port
);
1189 if ((addrlist
= httpAddrGetList(NULL
, family
, service
)) == NULL
)
1192 sock
= httpAddrListen(&(addrlist
->addr
), port
);
1194 httpAddrFreeList(addrlist
);
1201 * 'create_media_col()' - Create a media-col value.
1204 static ipp_t
* /* O - media-col collection */
1205 create_media_col(const char *media
, /* I - Media name */
1206 const char *source
, /* I - Media source */
1207 const char *type
, /* I - Media type */
1208 int width
, /* I - x-dimension in 2540ths */
1209 int length
, /* I - y-dimension in 2540ths */
1210 int margins
) /* I - Value for margins */
1212 ipp_t
*media_col
= ippNew(), /* media-col value */
1213 *media_size
= create_media_size(width
, length
);
1214 /* media-size value */
1215 char media_key
[256]; /* media-key value */
1219 snprintf(media_key
, sizeof(media_key
), "%s_%s_%s%s", media
, source
, type
, margins
== 0 ? "_borderless" : "");
1221 snprintf(media_key
, sizeof(media_key
), "%s__%s%s", media
, type
, margins
== 0 ? "_borderless" : "");
1223 snprintf(media_key
, sizeof(media_key
), "%s_%s%s", media
, source
, margins
== 0 ? "_borderless" : "");
1225 snprintf(media_key
, sizeof(media_key
), "%s%s", media
, margins
== 0 ? "_borderless" : "");
1227 ippAddString(media_col
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "media-key", NULL
,
1229 ippAddCollection(media_col
, IPP_TAG_PRINTER
, "media-size", media_size
);
1230 ippAddString(media_col
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "media-size-name", NULL
, media
);
1231 ippAddInteger(media_col
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1232 "media-bottom-margin", margins
);
1233 ippAddInteger(media_col
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1234 "media-left-margin", margins
);
1235 ippAddInteger(media_col
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1236 "media-right-margin", margins
);
1237 ippAddInteger(media_col
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1238 "media-top-margin", margins
);
1240 ippAddString(media_col
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "media-source", NULL
, source
);
1242 ippAddString(media_col
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "media-type", NULL
, type
);
1244 ippDelete(media_size
);
1251 * 'create_media_size()' - Create a media-size value.
1254 static ipp_t
* /* O - media-col collection */
1255 create_media_size(int width
, /* I - x-dimension in 2540ths */
1256 int length
) /* I - y-dimension in 2540ths */
1258 ipp_t
*media_size
= ippNew(); /* media-size value */
1261 ippAddInteger(media_size
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "x-dimension",
1263 ippAddInteger(media_size
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "y-dimension",
1266 return (media_size
);
1271 * 'create_printer()' - Create, register, and listen for connections to a
1275 static _ipp_printer_t
* /* O - Printer */
1276 create_printer(const char *servername
, /* I - Server hostname (NULL for default) */
1277 const char *name
, /* I - printer-name */
1278 const char *location
, /* I - printer-location */
1279 const char *make
, /* I - printer-make-and-model */
1280 const char *model
, /* I - printer-make-and-model */
1281 const char *icon
, /* I - printer-icons */
1282 const char *docformats
, /* I - document-format-supported */
1283 int ppm
, /* I - Pages per minute in grayscale */
1284 int ppm_color
, /* I - Pages per minute in color (0 for gray) */
1285 int duplex
, /* I - 1 = duplex, 0 = simplex */
1286 int port
, /* I - Port for listeners or 0 for auto */
1287 int pin
, /* I - Require PIN printing */
1288 const char *subtype
, /* I - Bonjour service subtype */
1289 const char *directory
, /* I - Spool directory */
1290 const char *command
, /* I - Command to run on job files */
1291 const char *attrfile
) /* I - Attributes file */
1293 int i
, j
; /* Looping vars */
1294 _ipp_printer_t
*printer
; /* Printer */
1296 char path
[1024]; /* Full path to command */
1298 char uri
[1024], /* Printer URI */
1300 securi
[1024], /* Secure printer URI */
1301 *uris
[2], /* All URIs */
1302 #endif /* HAVE_SSL */
1303 icons
[1024], /* printer-icons URI */
1304 adminurl
[1024], /* printer-more-info URI */
1305 supplyurl
[1024],/* printer-supply-info-uri URI */
1306 device_id
[1024],/* printer-device-id */
1307 make_model
[128],/* printer-make-and-model */
1308 uuid
[128]; /* printer-uuid */
1309 int num_formats
; /* Number of document-format-supported values */
1310 char *defformat
, /* document-format-default value */
1311 *formats
[100], /* document-format-supported values */
1312 *ptr
; /* Pointer into string */
1313 const char *prefix
; /* Prefix string */
1314 int num_database
; /* Number of database values */
1315 ipp_attribute_t
*media_col_database
,
1316 /* media-col-database value */
1317 *media_size_supported
;
1318 /* media-size-supported value */
1319 ipp_t
*media_col_default
;
1320 /* media-col-default value */
1321 int media_col_index
;/* Current media-col-database value */
1322 int k_supported
; /* Maximum file size supported */
1324 struct statvfs spoolinfo
; /* FS info for spool directory */
1325 double spoolsize
; /* FS size */
1326 #elif defined(HAVE_STATFS)
1327 struct statfs spoolinfo
; /* FS info for spool directory */
1328 double spoolsize
; /* FS size */
1329 #endif /* HAVE_STATVFS */
1330 static const int orients
[4] = /* orientation-requested-supported values */
1332 IPP_ORIENT_PORTRAIT
,
1333 IPP_ORIENT_LANDSCAPE
,
1334 IPP_ORIENT_REVERSE_LANDSCAPE
,
1335 IPP_ORIENT_REVERSE_PORTRAIT
1337 static const char * const versions
[] =/* ipp-versions-supported values */
1344 static const char * const features
[] =/* ipp-features-supported values */
1348 static const int ops
[] = /* operations-supported values */
1352 IPP_OP_VALIDATE_JOB
,
1354 IPP_OP_SEND_DOCUMENT
,
1357 IPP_OP_GET_JOB_ATTRIBUTES
,
1359 IPP_OP_GET_PRINTER_ATTRIBUTES
,
1360 IPP_OP_CANCEL_MY_JOBS
,
1362 IPP_OP_IDENTIFY_PRINTER
1364 static const char * const charsets
[] =/* charset-supported values */
1369 static const char * const compressions
[] =/* compression-supported values */
1374 #endif /* HAVE_LIBZ */
1377 static const char * const identify_actions
[] =
1382 static const char * const job_creation
[] =
1383 { /* job-creation-attributes-supported values */
1385 "ipp-attribute-fidelity",
1387 "job-accounting-user-id",
1393 "multiple-document-handling",
1394 "orientation-requested",
1398 static const char * const media_col_supported
[] =
1399 { /* media-col-supported values */
1400 "media-bottom-margin",
1401 "media-left-margin",
1402 "media-right-margin",
1408 static const int media_xxx_margin_supported
[] =
1409 { /* media-xxx-margin-supported values */
1413 static const char * const multiple_document_handling
[] =
1414 { /* multiple-document-handling-supported values */
1415 "separate-documents-uncollated-copies",
1416 "separate-documents-collated-copies"
1418 static const char * const overrides
[] =
1419 { /* overrides-supported */
1423 static const char * const print_color_mode_supported
[] =
1424 { /* print-color-mode-supported values */
1429 static const int print_quality_supported
[] =
1430 { /* print-quality-supported values */
1435 static const int pwg_raster_document_resolution_supported
[] =
1440 static const char * const pwg_raster_document_type_supported
[] =
1448 static const char * const reference_uri_schemes_supported
[] =
1449 { /* reference-uri-schemes-supported */
1455 #endif /* HAVE_SSL */
1457 static const char * const sides_supported
[] =
1458 { /* sides-supported values */
1460 "two-sided-long-edge",
1461 "two-sided-short-edge"
1463 static const char * const urf_supported
[] =
1464 { /* urf-supported values */
1467 "MT1-2-3-4-5-6-8-9-10-11-12-13",
1475 static const char * const uri_authentication_supported
[] =
1476 { /* uri-authentication-supported values */
1480 static const char * const uri_security_supported
[] =
1481 { /* uri-security-supported values */
1485 #endif /* HAVE_SSL */
1486 static const char * const which_jobs
[] =
1487 { /* which-jobs-supported values */
1496 "processing-stopped"
1502 * If a command was specified, make sure it exists and is executable...
1507 if (*command
== '/' || !strncmp(command
, "./", 2))
1509 if (access(command
, X_OK
))
1511 fprintf(stderr
, "ippserver: Unable to execute command \"%s\": %s\n", command
, strerror(errno
));
1517 if (!cupsFileFind(command
, getenv("PATH"), 1, path
, sizeof(path
)))
1519 fprintf(stderr
, "ippserver: Unable to find command \"%s\".\n", command
);
1529 * Allocate memory for the printer...
1532 if ((printer
= calloc(1, sizeof(_ipp_printer_t
))) == NULL
)
1534 perror("ippserver: Unable to allocate memory for printer");
1540 printer
->name
= strdup(name
);
1541 printer
->dnssd_name
= strdup(printer
->name
);
1542 printer
->command
= command
? strdup(command
) : NULL
;
1543 printer
->directory
= strdup(directory
);
1544 printer
->hostname
= strdup(servername
);
1545 printer
->port
= port
;
1546 printer
->start_time
= time(NULL
);
1547 printer
->config_time
= printer
->start_time
;
1548 printer
->state
= IPP_PSTATE_IDLE
;
1549 printer
->state_reasons
= _IPP_PREASON_NONE
;
1550 printer
->state_time
= printer
->start_time
;
1551 printer
->jobs
= cupsArrayNew((cups_array_func_t
)compare_jobs
, NULL
);
1552 printer
->next_job_id
= 1;
1554 httpAssembleURI(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), "ipp", NULL
, printer
->hostname
, printer
->port
, "/ipp/print");
1555 printer
->uri
= strdup(uri
);
1556 printer
->urilen
= strlen(uri
);
1559 httpAssembleURI(HTTP_URI_CODING_ALL
, securi
, sizeof(securi
), "ipps", NULL
, printer
->hostname
, printer
->port
, "/ipp/print");
1560 #endif /* HAVE_SSL */
1563 printer
->icon
= strdup(icon
);
1565 printer
->main_size
= _IPP_MEDIA_SIZE_A4
;
1566 printer
->main_type
= _IPP_MEDIA_TYPE_STATIONERY
;
1567 printer
->main_level
= 500;
1569 printer
->envelope_size
= _IPP_MEDIA_SIZE_NONE
;
1570 printer
->envelope_level
= 0;
1572 printer
->photo_size
= _IPP_MEDIA_SIZE_NONE
;
1573 printer
->photo_type
= _IPP_MEDIA_TYPE_NONE
;
1574 printer
->photo_level
= 0;
1576 printer
->supplies
[_IPP_SUPPLY_CYAN
] = 100;
1577 printer
->supplies
[_IPP_SUPPLY_MAGENTA
] = 100;
1578 printer
->supplies
[_IPP_SUPPLY_YELLOW
] = 100;
1579 printer
->supplies
[_IPP_SUPPLY_BLACK
] = 100;
1580 printer
->supplies
[_IPP_SUPPLY_WASTE
] = 0;
1582 _cupsRWInit(&(printer
->rwlock
));
1585 * Create the listener sockets...
1588 if ((printer
->ipv4
= create_listener(AF_INET
, printer
->port
)) < 0)
1590 perror("Unable to create IPv4 listener");
1594 if ((printer
->ipv6
= create_listener(AF_INET6
, printer
->port
)) < 0)
1596 perror("Unable to create IPv6 listener");
1601 * Prepare values for the printer attributes...
1604 httpAssembleURI(HTTP_URI_CODING_ALL
, icons
, sizeof(icons
), WEB_SCHEME
, NULL
, printer
->hostname
, printer
->port
, "/icon.png");
1605 httpAssembleURI(HTTP_URI_CODING_ALL
, adminurl
, sizeof(adminurl
), WEB_SCHEME
, NULL
, printer
->hostname
, printer
->port
, "/");
1606 httpAssembleURI(HTTP_URI_CODING_ALL
, supplyurl
, sizeof(supplyurl
), WEB_SCHEME
, NULL
, printer
->hostname
, printer
->port
, "/supplies");
1610 fprintf(stderr
, "printer-more-info=\"%s\"\n", adminurl
);
1611 fprintf(stderr
, "printer-supply-info-uri=\"%s\"\n", supplyurl
);
1612 fprintf(stderr
, "printer-uri=\"%s\"\n", uri
);
1615 snprintf(make_model
, sizeof(make_model
), "%s %s", make
, model
);
1618 formats
[0] = strdup(docformats
);
1619 defformat
= formats
[0];
1620 for (ptr
= strchr(formats
[0], ','); ptr
; ptr
= strchr(ptr
, ','))
1623 formats
[num_formats
++] = ptr
;
1625 if (!strcasecmp(ptr
, "application/octet-stream"))
1629 snprintf(device_id
, sizeof(device_id
), "MFG:%s;MDL:%s;", make
, model
);
1630 ptr
= device_id
+ strlen(device_id
);
1632 for (i
= 0; i
< num_formats
; i
++)
1634 if (!strcasecmp(formats
[i
], "application/pdf"))
1635 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%sPDF", prefix
);
1636 else if (!strcasecmp(formats
[i
], "application/postscript"))
1637 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%sPS", prefix
);
1638 else if (!strcasecmp(formats
[i
], "application/vnd.hp-PCL"))
1639 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%sPCL", prefix
);
1640 else if (!strcasecmp(formats
[i
], "image/jpeg"))
1641 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%sJPEG", prefix
);
1642 else if (!strcasecmp(formats
[i
], "image/png"))
1643 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%sPNG", prefix
);
1644 else if (strcasecmp(formats
[i
], "application/octet-stream"))
1645 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%s%s", prefix
, formats
[i
]);
1650 if (ptr
< (device_id
+ sizeof(device_id
) - 1))
1657 * Get the maximum spool size based on the size of the filesystem used for
1658 * the spool directory. If the host OS doesn't support the statfs call
1659 * or the filesystem is larger than 2TiB, always report INT_MAX.
1663 if (statvfs(printer
->directory
, &spoolinfo
))
1664 k_supported
= INT_MAX
;
1665 else if ((spoolsize
= (double)spoolinfo
.f_frsize
*
1666 spoolinfo
.f_blocks
/ 1024) > INT_MAX
)
1667 k_supported
= INT_MAX
;
1669 k_supported
= (int)spoolsize
;
1671 #elif defined(HAVE_STATFS)
1672 if (statfs(printer
->directory
, &spoolinfo
))
1673 k_supported
= INT_MAX
;
1674 else if ((spoolsize
= (double)spoolinfo
.f_bsize
*
1675 spoolinfo
.f_blocks
/ 1024) > INT_MAX
)
1676 k_supported
= INT_MAX
;
1678 k_supported
= (int)spoolsize
;
1681 k_supported
= INT_MAX
;
1682 #endif /* HAVE_STATVFS */
1685 * Create the printer attributes. This list of attributes is sorted to improve
1686 * performance when the client provides a requested-attributes attribute...
1689 printer
->attrs
= ippNew();
1692 load_attributes(attrfile
, printer
->attrs
);
1694 /* charset-configured */
1695 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_CHARSET
), "charset-configured", NULL
, "utf-8");
1697 /* charset-supported */
1698 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_CHARSET
), "charset-supported", sizeof(charsets
) / sizeof(charsets
[0]), NULL
, charsets
);
1700 /* color-supported */
1701 if (!ippFindAttribute(printer
->attrs
, "color-supported", IPP_TAG_ZERO
))
1702 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "color-supported", ppm_color
> 0);
1704 /* compression-supported */
1705 if (!ippFindAttribute(printer
->attrs
, "compression-supported", IPP_TAG_ZERO
))
1706 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "compression-supported", (int)(sizeof(compressions
) / sizeof(compressions
[0])), NULL
, compressions
);
1708 /* copies-default */
1709 if (!ippFindAttribute(printer
->attrs
, "copies-default", IPP_TAG_ZERO
))
1710 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "copies-default", 1);
1712 /* copies-supported */
1713 if (!ippFindAttribute(printer
->attrs
, "copies-supported", IPP_TAG_ZERO
))
1714 ippAddRange(printer
->attrs
, IPP_TAG_PRINTER
, "copies-supported", 1, 999);
1716 /* document-format-default */
1717 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_MIMETYPE
,
1718 "document-format-default", NULL
, defformat
);
1720 /* document-format-supported */
1721 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_MIMETYPE
,
1722 "document-format-supported", num_formats
, NULL
,
1723 (const char * const *)formats
);
1725 /* document-password-supported */
1726 if (!ippFindAttribute(printer
->attrs
, "document-password-supported", IPP_TAG_ZERO
))
1727 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "document-password-supported", 127);
1729 /* finishings-default */
1730 if (!ippFindAttribute(printer
->attrs
, "finishings-default", IPP_TAG_ZERO
))
1731 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
, "finishings-default", IPP_FINISHINGS_NONE
);
1733 /* finishings-supported */
1734 if (!ippFindAttribute(printer
->attrs
, "finishings-supported", IPP_TAG_ZERO
))
1735 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
, "finishings-supported", IPP_FINISHINGS_NONE
);
1737 /* generated-natural-language-supported */
1738 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_LANGUAGE
), "generated-natural-language-supported", NULL
, "en");
1740 /* identify-actions-default */
1741 if (!ippFindAttribute(printer
->attrs
, "identify-actions-default", IPP_TAG_ZERO
))
1742 ippAddString (printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "identify-actions-default", NULL
, "sound");
1744 /* identify-actions-supported */
1745 if (!ippFindAttribute(printer
->attrs
, "identify-actions-supported", IPP_TAG_ZERO
))
1746 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
);
1748 /* ipp-features-supported */
1749 if (!ippFindAttribute(printer
->attrs
, "ipp-features-supported", IPP_TAG_ZERO
))
1750 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "ipp-features-supported", sizeof(features
) / sizeof(features
[0]), NULL
, features
);
1752 /* ipp-versions-supported */
1753 if (!ippFindAttribute(printer
->attrs
, "ipp-versions-supported", IPP_TAG_ZERO
))
1755 int num_versions
= MaxVersion
== 11 ? 1 : MaxVersion
== 20 ? 2 : MaxVersion
== 21 ? 3 : 4;
1756 /* Number of supported versions */
1758 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "ipp-versions-supported", num_versions
, NULL
, versions
);
1761 /* job-account-id-default */
1762 if (!ippFindAttribute(printer
->attrs
, "job-account-id-default", IPP_TAG_ZERO
))
1763 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_NAME
), "job-account-id-default", NULL
, "");
1765 /* job-account-id-supported */
1766 if (!ippFindAttribute(printer
->attrs
, "job-account-id-supported", IPP_TAG_ZERO
))
1767 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "job-account-id-supported", 1);
1769 /* job-accounting-user-id-default */
1770 if (!ippFindAttribute(printer
->attrs
, "job-accounting-user-id-default", IPP_TAG_ZERO
))
1771 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_NAME
), "job-accounting-user-id-default", NULL
, "");
1773 /* job-accounting-user-id-supported */
1774 if (!ippFindAttribute(printer
->attrs
, "job-accounting-user-id-supported", IPP_TAG_ZERO
))
1775 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "job-accounting-user-id-supported", 1);
1777 /* job-creation-attributes-supported */
1778 if (!ippFindAttribute(printer
->attrs
, "job-creation-attributes-supported", IPP_TAG_ZERO
))
1779 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "job-creation-attributes-supported", sizeof(job_creation
) / sizeof(job_creation
[0]), NULL
, job_creation
);
1781 /* job-ids-supported */
1782 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "job-ids-supported", 1);
1784 /* job-k-octets-supported */
1785 ippAddRange(printer
->attrs
, IPP_TAG_PRINTER
, "job-k-octets-supported", 0,
1788 /* job-password-supported */
1789 if (!ippFindAttribute(printer
->attrs
, "job-password-supported", IPP_TAG_ZERO
))
1790 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "job-password-supported", 4);
1792 /* job-priority-default */
1793 if (!ippFindAttribute(printer
->attrs
, "job-priority-default", IPP_TAG_ZERO
))
1794 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "job-priority-default", 50);
1796 /* job-priority-supported */
1797 if (!ippFindAttribute(printer
->attrs
, "job-priority-supported", IPP_TAG_ZERO
))
1798 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "job-priority-supported", 100);
1800 /* job-sheets-default */
1801 if (!ippFindAttribute(printer
->attrs
, "job-sheets-default", IPP_TAG_ZERO
))
1802 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_NAME
), "job-sheets-default", NULL
, "none");
1804 /* job-sheets-supported */
1805 if (!ippFindAttribute(printer
->attrs
, "job-sheets-supported", IPP_TAG_ZERO
))
1806 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_NAME
), "job-sheets-supported", NULL
, "none");
1808 /* media-bottom-margin-supported */
1809 if (!ippFindAttribute(printer
->attrs
, "media-bottom-margin-supported", IPP_TAG_ZERO
))
1810 ippAddIntegers(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "media-bottom-margin-supported", (int)(sizeof(media_xxx_margin_supported
) / sizeof(media_xxx_margin_supported
[0])), media_xxx_margin_supported
);
1812 /* media-col-database */
1813 if (!ippFindAttribute(printer
->attrs
, "media-col-database", IPP_TAG_ZERO
))
1815 for (num_database
= 0, i
= 0;
1816 i
< (int)(sizeof(media_col_sizes
) / sizeof(media_col_sizes
[0]));
1819 if (media_col_sizes
[i
][2] == _IPP_ENV_ONLY
)
1820 num_database
+= 3; /* auto + manual + envelope */
1821 else if (media_col_sizes
[i
][2] == _IPP_PHOTO_ONLY
)
1822 num_database
+= 6 * 3; /* auto + photographic-* from auto, manual, and photo */
1824 num_database
+= 2; /* Regular + borderless */
1827 media_col_database
= ippAddCollections(printer
->attrs
, IPP_TAG_PRINTER
, "media-col-database", num_database
, NULL
);
1828 for (media_col_index
= 0, i
= 0;
1829 i
< (int)(sizeof(media_col_sizes
) / sizeof(media_col_sizes
[0]));
1832 switch (media_col_sizes
[i
][2])
1836 * Regular + borderless for the general class; no source/type
1840 ippSetCollection(printer
->attrs
, &media_col_database
, media_col_index
++, create_media_col(media_supported
[i
], NULL
, NULL
, media_col_sizes
[i
][0], media_col_sizes
[i
][1], media_xxx_margin_supported
[1]));
1841 ippSetCollection(printer
->attrs
, &media_col_database
, media_col_index
++, create_media_col(media_supported
[i
], NULL
, NULL
, media_col_sizes
[i
][0], media_col_sizes
[i
][1], media_xxx_margin_supported
[0]));
1844 case _IPP_ENV_ONLY
:
1846 * Regular margins for "auto", "manual", and "envelope" sources.
1849 ippSetCollection(printer
->attrs
, &media_col_database
, media_col_index
++, create_media_col(media_supported
[i
], "auto", "envelope", media_col_sizes
[i
][0], media_col_sizes
[i
][1], media_xxx_margin_supported
[1]));
1850 ippSetCollection(printer
->attrs
, &media_col_database
, media_col_index
++, create_media_col(media_supported
[i
], "manual", "envelope", media_col_sizes
[i
][0], media_col_sizes
[i
][1], media_xxx_margin_supported
[1]));
1851 ippSetCollection(printer
->attrs
, &media_col_database
, media_col_index
++, create_media_col(media_supported
[i
], "envelope", "envelope", media_col_sizes
[i
][0], media_col_sizes
[i
][1], media_xxx_margin_supported
[1]));
1853 case _IPP_PHOTO_ONLY
:
1855 * Photos have specific media types and can only be printed via
1856 * the auto, manual, and photo sources...
1860 j
< (int)(sizeof(media_type_supported
) /
1861 sizeof(media_type_supported
[0]));
1864 if (strcmp(media_type_supported
[j
], "auto") && strncmp(media_type_supported
[j
], "photographic-", 13))
1867 ippSetCollection(printer
->attrs
, &media_col_database
, media_col_index
++, create_media_col(media_supported
[i
], "auto", media_type_supported
[j
], media_col_sizes
[i
][0], media_col_sizes
[i
][1], media_xxx_margin_supported
[0]));
1868 ippSetCollection(printer
->attrs
, &media_col_database
, media_col_index
++, create_media_col(media_supported
[i
], "manual", media_type_supported
[j
], media_col_sizes
[i
][0], media_col_sizes
[i
][1], media_xxx_margin_supported
[0]));
1869 ippSetCollection(printer
->attrs
, &media_col_database
, media_col_index
++, create_media_col(media_supported
[i
], "photo", media_type_supported
[j
], media_col_sizes
[i
][0], media_col_sizes
[i
][1], media_xxx_margin_supported
[0]));
1876 /* media-col-default */
1877 if (!ippFindAttribute(printer
->attrs
, "media-col-default", IPP_TAG_ZERO
))
1879 media_col_default
= create_media_col(media_supported
[0], media_source_supported
[0], media_type_supported
[0], media_col_sizes
[0][0], media_col_sizes
[0][1],media_xxx_margin_supported
[1]);
1881 ippAddCollection(printer
->attrs
, IPP_TAG_PRINTER
, "media-col-default",
1883 ippDelete(media_col_default
);
1886 /* media-col-supported */
1887 if (!ippFindAttribute(printer
->attrs
, "media-col-supported", IPP_TAG_ZERO
))
1888 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
);
1891 if (!ippFindAttribute(printer
->attrs
, "media-default", IPP_TAG_ZERO
))
1892 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "media-default", NULL
, media_supported
[0]);
1894 /* media-left-margin-supported */
1895 if (!ippFindAttribute(printer
->attrs
, "media-left-margin-supported", IPP_TAG_ZERO
))
1896 ippAddIntegers(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "media-left-margin-supported", (int)(sizeof(media_xxx_margin_supported
) / sizeof(media_xxx_margin_supported
[0])), media_xxx_margin_supported
);
1898 /* media-right-margin-supported */
1899 if (!ippFindAttribute(printer
->attrs
, "media-right-margin-supported", IPP_TAG_ZERO
))
1900 ippAddIntegers(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "media-right-margin-supported", (int)(sizeof(media_xxx_margin_supported
) / sizeof(media_xxx_margin_supported
[0])), media_xxx_margin_supported
);
1902 /* media-supported */
1903 if (!ippFindAttribute(printer
->attrs
, "media-supported", IPP_TAG_ZERO
))
1904 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "media-supported", (int)(sizeof(media_supported
) / sizeof(media_supported
[0])), NULL
, media_supported
);
1906 /* media-size-supported */
1907 if (!ippFindAttribute(printer
->attrs
, "media-size-supported", IPP_TAG_ZERO
))
1909 media_size_supported
= ippAddCollections(printer
->attrs
, IPP_TAG_PRINTER
, "media-size-supported", (int)(sizeof(media_col_sizes
) / sizeof(media_col_sizes
[0])), NULL
);
1912 i
< (int)(sizeof(media_col_sizes
) / sizeof(media_col_sizes
[0]));
1915 ipp_t
*size
= create_media_size(media_col_sizes
[i
][0], media_col_sizes
[i
][1]);
1917 ippSetCollection(printer
->attrs
, &media_size_supported
, i
, size
);
1922 /* media-source-supported */
1923 if (!ippFindAttribute(printer
->attrs
, "media-source-supported", IPP_TAG_ZERO
))
1924 ippAddStrings(printer
->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
);
1926 /* media-top-margin-supported */
1927 if (!ippFindAttribute(printer
->attrs
, "media-top-margin-supported", IPP_TAG_ZERO
))
1928 ippAddIntegers(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "media-top-margin-supported", (int)(sizeof(media_xxx_margin_supported
) / sizeof(media_xxx_margin_supported
[0])), media_xxx_margin_supported
);
1930 /* media-type-supported */
1931 if (!ippFindAttribute(printer
->attrs
, "media-type-supported", IPP_TAG_ZERO
))
1932 ippAddStrings(printer
->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
);
1934 /* multiple-document-handling-supported */
1935 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
);
1937 /* multiple-document-jobs-supported */
1938 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "multiple-document-jobs-supported", 0);
1940 /* multiple-operation-time-out */
1941 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "multiple-operation-time-out", 60);
1943 /* multiple-operation-time-out-action */
1944 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "multiple-operation-time-out-action", NULL
, "abort-job");
1946 /* natural-language-configured */
1947 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
,
1948 IPP_CONST_TAG(IPP_TAG_LANGUAGE
),
1949 "natural-language-configured", NULL
, "en");
1951 /* number-up-default */
1952 if (!ippFindAttribute(printer
->attrs
, "number-up-default", IPP_TAG_ZERO
))
1953 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "number-up-default", 1);
1955 /* number-up-supported */
1956 if (!ippFindAttribute(printer
->attrs
, "number-up-supported", IPP_TAG_ZERO
))
1957 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "number-up-supported", 1);
1959 /* operations-supported */
1960 ippAddIntegers(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
, "operations-supported", sizeof(ops
) / sizeof(ops
[0]), ops
);
1962 /* orientation-requested-default */
1963 if (!ippFindAttribute(printer
->attrs
, "orientation-requested-default", IPP_TAG_ZERO
))
1964 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_NOVALUE
, "orientation-requested-default", 0);
1966 /* orientation-requested-supported */
1967 if (!ippFindAttribute(printer
->attrs
, "orientation-requested-supported", IPP_TAG_ZERO
))
1968 ippAddIntegers(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
, "orientation-requested-supported", 4, orients
);
1970 /* output-bin-default */
1971 if (!ippFindAttribute(printer
->attrs
, "output-bin-default", IPP_TAG_ZERO
))
1972 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "output-bin-default", NULL
, "face-down");
1974 /* output-bin-supported */
1975 if (!ippFindAttribute(printer
->attrs
, "output-bin-supported", IPP_TAG_ZERO
))
1976 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "output-bin-supported", NULL
, "face-down");
1978 /* overrides-supported */
1979 if (!ippFindAttribute(printer
->attrs
, "overrides-supported", IPP_TAG_ZERO
))
1980 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "overrides-supported", (int)(sizeof(overrides
) / sizeof(overrides
[0])), NULL
, overrides
);
1982 /* page-ranges-supported */
1983 if (!ippFindAttribute(printer
->attrs
, "page-ranges-supported", IPP_TAG_ZERO
))
1984 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "page-ranges-supported", 1);
1986 /* pages-per-minute */
1987 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1988 "pages-per-minute", ppm
);
1990 /* pages-per-minute-color */
1992 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1993 "pages-per-minute-color", ppm_color
);
1995 /* pdl-override-supported */
1996 if (!ippFindAttribute(printer
->attrs
, "pdl-override-supported", IPP_TAG_ZERO
))
1997 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "pdl-override-supported", NULL
, "attempted");
1999 /* preferred-attributes-supported */
2000 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "preferred-attributes-supported", 0);
2002 /* print-color-mode-default */
2003 if (!ippFindAttribute(printer
->attrs
, "print-color-mode-default", IPP_TAG_ZERO
))
2004 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "print-color-mode-default", NULL
, "auto");
2006 /* print-color-mode-supported */
2007 if (!ippFindAttribute(printer
->attrs
, "print-color-mode-supported", IPP_TAG_ZERO
))
2008 ippAddStrings(printer
->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
);
2010 /* print-content-optimize-default */
2011 if (!ippFindAttribute(printer
->attrs
, "print-content-optimize-default", IPP_TAG_ZERO
))
2012 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "print-content-optimize-default", NULL
, "auto");
2014 /* print-content-optimize-supported */
2015 if (!ippFindAttribute(printer
->attrs
, "print-content-optimize-supported", IPP_TAG_ZERO
))
2016 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "print-content-optimize-supported", NULL
, "auto");
2018 /* print-rendering-intent-default */
2019 if (!ippFindAttribute(printer
->attrs
, "print-rendering-intent-default", IPP_TAG_ZERO
))
2020 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "print-rendering-intent-default", NULL
, "auto");
2022 /* print-rendering-intent-supported */
2023 if (!ippFindAttribute(printer
->attrs
, "print-rendering-intent-supported", IPP_TAG_ZERO
))
2024 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "print-rendering-intent-supported", NULL
, "auto");
2026 /* print-quality-default */
2027 if (!ippFindAttribute(printer
->attrs
, "print-quality-default", IPP_TAG_ZERO
))
2028 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
, "print-quality-default", IPP_QUALITY_NORMAL
);
2030 /* print-quality-supported */
2031 if (!ippFindAttribute(printer
->attrs
, "print-quality-supported", IPP_TAG_ZERO
))
2032 ippAddIntegers(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
, "print-quality-supported", (int)(sizeof(print_quality_supported
) / sizeof(print_quality_supported
[0])), print_quality_supported
);
2034 /* printer-device-id */
2035 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
,
2036 "printer-device-id", NULL
, device_id
);
2038 /* printer-get-attributes-supported */
2039 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "printer-get-attributes-supported", NULL
, "document-format");
2041 /* printer-geo-location */
2042 if (!ippFindAttribute(printer
->attrs
, "printer-geo-location", IPP_TAG_ZERO
))
2043 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_UNKNOWN
, "printer-geo-location", 0);
2046 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
,
2047 "printer-icons", NULL
, icons
);
2049 /* printer-is-accepting-jobs */
2050 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "printer-is-accepting-jobs", 1);
2053 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
, "printer-info", NULL
, name
);
2055 /* printer-location */
2056 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
,
2057 "printer-location", NULL
, location
);
2059 /* printer-make-and-model */
2060 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
,
2061 "printer-make-and-model", NULL
, make_model
);
2063 /* printer-mandatory-job-attributes */
2064 if (pin
&& !ippFindAttribute(printer
->attrs
, "", IPP_TAG_ZERO
))
2066 static const char * const names
[] =
2069 "job-accounting-user-id",
2073 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
,
2074 "printer-mandatory-job-attributes",
2075 (int)(sizeof(names
) / sizeof(names
[0])), NULL
, names
);
2078 /* printer-more-info */
2079 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
, "printer-more-info", NULL
, adminurl
);
2082 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_NAME
, "printer-name", NULL
, name
);
2084 /* printer-organization */
2085 if (!ippFindAttribute(printer
->attrs
, "printer-organization", IPP_TAG_ZERO
))
2086 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_TEXT
), "printer-organization", NULL
, "Apple Inc.");
2088 /* printer-organizational-unit */
2089 if (!ippFindAttribute(printer
->attrs
, "printer-organizational-unit", IPP_TAG_ZERO
))
2090 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_TEXT
), "printer-organizational-unit", NULL
, "Printing Engineering");
2092 /* printer-resolution-default */
2093 if (!ippFindAttribute(printer
->attrs
, "printer-resolution-default", IPP_TAG_ZERO
))
2094 ippAddResolution(printer
->attrs
, IPP_TAG_PRINTER
, "printer-resolution-default", IPP_RES_PER_INCH
, 600, 600);
2096 /* printer-resolution-supported */
2097 if (!ippFindAttribute(printer
->attrs
, "printer-resolutions-supported", IPP_TAG_ZERO
))
2098 ippAddResolution(printer
->attrs
, IPP_TAG_PRINTER
, "printer-resolution-supported", IPP_RES_PER_INCH
, 600, 600);
2100 /* printer-supply-description */
2101 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_TEXT
), "printer-supply-description", (int)(sizeof(printer_supplies
) / sizeof(printer_supplies
[0])), NULL
, printer_supplies
);
2103 /* printer-supply-info-uri */
2104 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
, "printer-supply-info-uri", NULL
, supplyurl
);
2106 /* printer-uri-supported */
2111 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
, "printer-uri-supported", 2, NULL
, (const char **)uris
);
2114 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
, "printer-uri-supported", NULL
, uri
);
2115 #endif /* HAVE_SSL */
2118 httpAssembleUUID(printer
->hostname
, port
, name
, 0, uuid
, sizeof(uuid
));
2119 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
, "printer-uuid", NULL
, uuid
);
2121 /* pwg-raster-document-xxx-supported */
2122 for (i
= 0; i
< num_formats
; i
++)
2123 if (!strcasecmp(formats
[i
], "image/pwg-raster"))
2126 if (i
< num_formats
)
2128 if (!ippFindAttribute(printer
->attrs
, "pwg-raster-document-resolution-supported", IPP_TAG_ZERO
))
2129 ippAddResolutions(printer
->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
);
2130 if (!ippFindAttribute(printer
->attrs
, "pwg-raster-document-sheet-back", IPP_TAG_ZERO
))
2131 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "pwg-raster-document-sheet-back", NULL
, "normal");
2132 if (!ippFindAttribute(printer
->attrs
, "pwg-raster-document-type-supported", IPP_TAG_ZERO
))
2133 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, 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
);
2136 /* reference-uri-scheme-supported */
2137 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
);
2140 if (!ippFindAttribute(printer
->attrs
, "sides-default", IPP_TAG_ZERO
))
2141 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "sides-default", NULL
, "one-sided");
2143 /* sides-supported */
2144 if (!ippFindAttribute(printer
->attrs
, "sides-supported", IPP_TAG_ZERO
))
2145 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "sides-supported", duplex
? 3 : 1, NULL
, sides_supported
);
2148 for (i
= 0; i
< num_formats
; i
++)
2149 if (!strcasecmp(formats
[i
], "image/urf"))
2152 if (i
< num_formats
&& !ippFindAttribute(printer
->attrs
, "urf-supported", IPP_TAG_ZERO
))
2153 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "urf-supported", (int)(sizeof(urf_supported
) / sizeof(urf_supported
[0])) - !duplex
, NULL
, urf_supported
);
2155 /* uri-authentication-supported */
2157 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "uri-authentication-supported", 2, NULL
, uri_authentication_supported
);
2159 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "uri-authentication-supported", NULL
, "none");
2160 #endif /* HAVE_SSL */
2162 /* uri-security-supported */
2164 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "uri-security-supported", 2, NULL
, uri_security_supported
);
2166 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "uri-security-supported", NULL
, "none");
2167 #endif /* HAVE_SSL */
2169 /* which-jobs-supported */
2170 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
);
2174 debug_attributes("Printer", printer
->attrs
, 0);
2177 * Register the printer with Bonjour...
2180 if (!register_printer(printer
, location
, make
, model
, docformats
, adminurl
, uuid
+ 9, ppm_color
> 0, duplex
, subtype
))
2191 * If we get here we were unable to create the printer...
2196 delete_printer(printer
);
2202 * 'debug_attributes()' - Print attributes in a request or response.
2206 debug_attributes(const char *title
, /* I - Title */
2207 ipp_t
*ipp
, /* I - Request/response */
2208 int type
) /* I - 0 = object, 1 = request, 2 = response */
2210 ipp_tag_t group_tag
; /* Current group */
2211 ipp_attribute_t
*attr
; /* Current attribute */
2212 char buffer
[2048]; /* String buffer for value */
2213 int major
, minor
; /* Version */
2219 fprintf(stderr
, "%s:\n", title
);
2220 major
= ippGetVersion(ipp
, &minor
);
2221 fprintf(stderr
, " version=%d.%d\n", major
, minor
);
2223 fprintf(stderr
, " operation-id=%s(%04x)\n",
2224 ippOpString(ippGetOperation(ipp
)), ippGetOperation(ipp
));
2226 fprintf(stderr
, " status-code=%s(%04x)\n",
2227 ippErrorString(ippGetStatusCode(ipp
)), ippGetStatusCode(ipp
));
2228 fprintf(stderr
, " request-id=%d\n\n", ippGetRequestId(ipp
));
2230 for (attr
= ippFirstAttribute(ipp
), group_tag
= IPP_TAG_ZERO
;
2232 attr
= ippNextAttribute(ipp
))
2234 if (ippGetGroupTag(attr
) != group_tag
)
2236 group_tag
= ippGetGroupTag(attr
);
2237 fprintf(stderr
, " %s\n", ippTagString(group_tag
));
2240 if (ippGetName(attr
))
2242 ippAttributeString(attr
, buffer
, sizeof(buffer
));
2243 fprintf(stderr
, " %s (%s%s) %s\n", ippGetName(attr
),
2244 ippGetCount(attr
) > 1 ? "1setOf " : "",
2245 ippTagString(ippGetValueTag(attr
)), buffer
);
2252 * 'delete_client()' - Close the socket and free all memory used by a client
2257 delete_client(_ipp_client_t
*client
) /* I - Client */
2260 fprintf(stderr
, "Closing connection from %s\n", client
->hostname
);
2263 * Flush pending writes before closing...
2266 httpFlushWrite(client
->http
);
2272 httpClose(client
->http
);
2274 ippDelete(client
->request
);
2275 ippDelete(client
->response
);
2282 * 'delete_job()' - Remove from the printer and free all memory used by a job
2287 delete_job(_ipp_job_t
*job
) /* I - Job */
2290 fprintf(stderr
, "Removing job #%d from history.\n", job
->id
);
2292 ippDelete(job
->attrs
);
2297 unlink(job
->filename
);
2299 free(job
->filename
);
2307 * 'delete_printer()' - Unregister, close listen sockets, and free all memory
2308 * used by a printer object.
2312 delete_printer(_ipp_printer_t
*printer
) /* I - Printer */
2314 if (printer
->ipv4
>= 0)
2315 close(printer
->ipv4
);
2317 if (printer
->ipv6
>= 0)
2318 close(printer
->ipv6
);
2321 if (printer
->printer_ref
)
2322 DNSServiceRefDeallocate(printer
->printer_ref
);
2323 if (printer
->ipp_ref
)
2324 DNSServiceRefDeallocate(printer
->ipp_ref
);
2325 if (printer
->ipps_ref
)
2326 DNSServiceRefDeallocate(printer
->ipps_ref
);
2327 if (printer
->http_ref
)
2328 DNSServiceRefDeallocate(printer
->http_ref
);
2329 #elif defined(HAVE_AVAHI)
2330 avahi_threaded_poll_lock(DNSSDMaster
);
2332 if (printer
->printer_ref
)
2333 avahi_entry_group_free(printer
->printer_ref
);
2334 if (printer
->ipp_ref
)
2335 avahi_entry_group_free(printer
->ipp_ref
);
2336 if (printer
->ipps_ref
)
2337 avahi_entry_group_free(printer
->ipps_ref
);
2338 if (printer
->http_ref
)
2339 avahi_entry_group_free(printer
->http_ref
);
2341 avahi_threaded_poll_unlock(DNSSDMaster
);
2342 #endif /* HAVE_DNSSD */
2344 if (printer
->dnssd_name
)
2345 free(printer
->dnssd_name
);
2347 free(printer
->name
);
2349 free(printer
->icon
);
2350 if (printer
->command
)
2351 free(printer
->command
);
2352 if (printer
->directory
)
2353 free(printer
->directory
);
2354 if (printer
->hostname
)
2355 free(printer
->hostname
);
2359 ippDelete(printer
->attrs
);
2360 cupsArrayDelete(printer
->jobs
);
2368 * 'dnssd_callback()' - Handle Bonjour registration events.
2371 static void DNSSD_API
2373 DNSServiceRef sdRef
, /* I - Service reference */
2374 DNSServiceFlags flags
, /* I - Status flags */
2375 DNSServiceErrorType errorCode
, /* I - Error, if any */
2376 const char *name
, /* I - Service name */
2377 const char *regtype
, /* I - Service type */
2378 const char *domain
, /* I - Domain for service */
2379 _ipp_printer_t
*printer
) /* I - Printer */
2387 fprintf(stderr
, "DNSServiceRegister for %s failed with error %d.\n",
2388 regtype
, (int)errorCode
);
2391 else if (strcasecmp(name
, printer
->dnssd_name
))
2394 fprintf(stderr
, "Now using DNS-SD service name \"%s\".\n", name
);
2396 /* No lock needed since only the main thread accesses/changes this */
2397 free(printer
->dnssd_name
);
2398 printer
->dnssd_name
= strdup(name
);
2403 #elif defined(HAVE_AVAHI)
2405 * 'dnssd_callback()' - Handle Bonjour registration events.
2410 AvahiEntryGroup
*srv
, /* I - Service */
2411 AvahiEntryGroupState state
, /* I - Registration state */
2412 void *context
) /* I - Printer */
2421 * 'dnssd_client_cb()' - Client callback for Avahi.
2423 * Called whenever the client or server state changes...
2428 AvahiClient
*c
, /* I - Client */
2429 AvahiClientState state
, /* I - Current state */
2430 void *userdata
) /* I - User data (unused) */
2440 fprintf(stderr
, "Ignore Avahi state %d.\n", state
);
2443 case AVAHI_CLIENT_FAILURE
:
2444 if (avahi_client_errno(c
) == AVAHI_ERR_DISCONNECTED
)
2446 fputs("Avahi server crashed, exiting.\n", stderr
);
2452 #endif /* HAVE_DNSSD */
2456 * 'dnssd_init()' - Initialize the DNS-SD service connections...
2463 if (DNSServiceCreateConnection(&DNSSDMaster
) != kDNSServiceErr_NoError
)
2465 fputs("Error: Unable to initialize Bonjour.\n", stderr
);
2469 #elif defined(HAVE_AVAHI)
2470 int error
; /* Error code, if any */
2472 if ((DNSSDMaster
= avahi_threaded_poll_new()) == NULL
)
2474 fputs("Error: Unable to initialize Bonjour.\n", stderr
);
2478 if ((DNSSDClient
= avahi_client_new(avahi_threaded_poll_get(DNSSDMaster
), AVAHI_CLIENT_NO_FAIL
, dnssd_client_cb
, NULL
, &error
)) == NULL
)
2480 fputs("Error: Unable to initialize Bonjour.\n", stderr
);
2484 avahi_threaded_poll_start(DNSSDMaster
);
2485 #endif /* HAVE_DNSSD */
2490 * 'filter_cb()' - Filter printer attributes based on the requested array.
2493 static int /* O - 1 to copy, 0 to ignore */
2494 filter_cb(_ipp_filter_t
*filter
, /* I - Filter parameters */
2495 ipp_t
*dst
, /* I - Destination (unused) */
2496 ipp_attribute_t
*attr
) /* I - Source attribute */
2499 * Filter attributes as needed...
2502 #ifndef WIN32 /* Avoid MS compiler bug */
2506 ipp_tag_t group
= ippGetGroupTag(attr
);
2507 const char *name
= ippGetName(attr
);
2509 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
)))
2512 return (!filter
->ra
|| cupsArrayFind(filter
->ra
, (void *)name
) != NULL
);
2517 * 'find_job()' - Find a job specified in a request.
2520 static _ipp_job_t
* /* O - Job or NULL */
2521 find_job(_ipp_client_t
*client
) /* I - Client */
2523 ipp_attribute_t
*attr
; /* job-id or job-uri attribute */
2524 _ipp_job_t key
, /* Job search key */
2525 *job
; /* Matching job, if any */
2528 if ((attr
= ippFindAttribute(client
->request
, "job-uri", IPP_TAG_URI
)) != NULL
)
2530 const char *uri
= ippGetString(attr
, 0, NULL
);
2532 if (!strncmp(uri
, client
->printer
->uri
, client
->printer
->urilen
) &&
2533 uri
[client
->printer
->urilen
] == '/')
2534 key
.id
= atoi(uri
+ client
->printer
->urilen
+ 1);
2538 else if ((attr
= ippFindAttribute(client
->request
, "job-id", IPP_TAG_INTEGER
)) != NULL
)
2539 key
.id
= ippGetInteger(attr
, 0);
2541 _cupsRWLockRead(&(client
->printer
->rwlock
));
2542 job
= (_ipp_job_t
*)cupsArrayFind(client
->printer
->jobs
, &key
);
2543 _cupsRWUnlock(&(client
->printer
->rwlock
));
2550 * 'get_collection()' - Get a collection value from a file.
2553 static ipp_t
* /* O - Collection value */
2554 get_collection(FILE *fp
, /* I - File to read from */
2555 const char *filename
, /* I - Attributes filename */
2556 int *linenum
) /* IO - Line number */
2558 char token
[1024], /* Token from file */
2559 attr
[128]; /* Attribute name */
2560 ipp_tag_t value
; /* Current value type */
2561 ipp_t
*col
= ippNew(); /* Collection value */
2562 ipp_attribute_t
*lastcol
= NULL
; /* Last collection attribute */
2565 while (get_token(fp
, token
, sizeof(token
), linenum
) != NULL
)
2567 if (!strcmp(token
, "}"))
2569 else if (!strcmp(token
, "{") && lastcol
)
2572 * Another collection value
2575 ipp_t
*subcol
= get_collection(fp
, filename
, linenum
);
2576 /* Collection value */
2579 ippSetCollection(col
, &lastcol
, ippGetCount(lastcol
), subcol
);
2583 else if (!_cups_strcasecmp(token
, "MEMBER"))
2591 if (!get_token(fp
, token
, sizeof(token
), linenum
))
2593 fprintf(stderr
, "ippserver: Missing MEMBER value tag on line %d of \"%s\".\n", *linenum
, filename
);
2597 if ((value
= ippTagValue(token
)) == IPP_TAG_ZERO
)
2599 fprintf(stderr
, "ippserver: Bad MEMBER value tag \"%s\" on line %d of \"%s\".\n", token
, *linenum
, filename
);
2603 if (!get_token(fp
, attr
, sizeof(attr
), linenum
))
2605 fprintf(stderr
, "ippserver: Missing MEMBER name on line %d of \"%s\".\n", *linenum
, filename
);
2609 if (!get_token(fp
, token
, sizeof(token
), linenum
))
2611 fprintf(stderr
, "ippserver: Missing MEMBER value on line %d of \"%s\".\n", *linenum
, filename
);
2617 case IPP_TAG_BOOLEAN
:
2618 if (!_cups_strcasecmp(token
, "true"))
2619 ippAddBoolean(col
, IPP_TAG_ZERO
, attr
, 1);
2621 ippAddBoolean(col
, IPP_TAG_ZERO
, attr
, (char)atoi(token
));
2624 case IPP_TAG_INTEGER
:
2626 ippAddInteger(col
, IPP_TAG_ZERO
, value
, attr
, atoi(token
));
2629 case IPP_TAG_RESOLUTION
:
2631 int xres
, /* X resolution */
2632 yres
; /* Y resolution */
2633 char units
[6]; /* Units */
2635 if (sscanf(token
, "%dx%d%5s", &xres
, &yres
, units
) != 3 ||
2636 (_cups_strcasecmp(units
, "dpi") &&
2637 _cups_strcasecmp(units
, "dpc") &&
2638 _cups_strcasecmp(units
, "dpcm") &&
2639 _cups_strcasecmp(units
, "other")))
2641 fprintf(stderr
, "ippserver: Bad resolution value \"%s\" on line %d of \"%s\".\n", token
, *linenum
, filename
);
2645 if (!_cups_strcasecmp(units
, "dpi"))
2646 ippAddResolution(col
, IPP_TAG_ZERO
, attr
, IPP_RES_PER_INCH
, xres
, yres
);
2647 else if (!_cups_strcasecmp(units
, "dpc") ||
2648 !_cups_strcasecmp(units
, "dpcm"))
2649 ippAddResolution(col
, IPP_TAG_ZERO
, attr
, IPP_RES_PER_CM
, xres
, yres
);
2651 ippAddResolution(col
, IPP_TAG_ZERO
, attr
, (ipp_res_t
)0, xres
, yres
);
2655 case IPP_TAG_RANGE
:
2657 int lowers
[4], /* Lower value */
2658 uppers
[4], /* Upper values */
2659 num_vals
; /* Number of values */
2662 num_vals
= sscanf(token
, "%d-%d,%d-%d,%d-%d,%d-%d",
2663 lowers
+ 0, uppers
+ 0,
2664 lowers
+ 1, uppers
+ 1,
2665 lowers
+ 2, uppers
+ 2,
2666 lowers
+ 3, uppers
+ 3);
2668 if ((num_vals
& 1) || num_vals
== 0)
2670 fprintf(stderr
, "ippserver: Bad rangeOfInteger value \"%s\" on line %d of \"%s\".\n", token
, *linenum
, filename
);
2674 ippAddRanges(col
, IPP_TAG_ZERO
, attr
, num_vals
/ 2, lowers
,
2679 case IPP_TAG_BEGIN_COLLECTION
:
2680 if (!strcmp(token
, "{"))
2682 ipp_t
*subcol
= get_collection(fp
, filename
, linenum
);
2683 /* Collection value */
2687 lastcol
= ippAddCollection(col
, IPP_TAG_ZERO
, attr
, subcol
);
2695 fprintf(stderr
, "ippserver: Bad collection value on line %d of \"%s\".\n", *linenum
, filename
);
2699 case IPP_TAG_STRING
:
2700 ippAddOctetString(col
, IPP_TAG_ZERO
, attr
, token
, (int)strlen(token
));
2704 if (!strchr(token
, ','))
2705 ippAddString(col
, IPP_TAG_ZERO
, value
, attr
, NULL
, token
);
2709 * Multiple string values...
2712 int num_values
; /* Number of values */
2713 char *values
[100], /* Values */
2714 *ptr
; /* Pointer to next value */
2720 for (ptr
= strchr(token
, ','); ptr
; ptr
= strchr(ptr
, ','))
2723 values
[num_values
] = ptr
;
2725 if (num_values
>= (int)(sizeof(values
) / sizeof(values
[0])))
2729 ippAddStrings(col
, IPP_TAG_ZERO
, value
, attr
, num_values
,
2730 NULL
, (const char **)values
);
2740 * If we get here there was a parse error; free memory and return.
2752 * 'get_token()' - Get a token from a file.
2755 static char * /* O - Token from file or NULL on EOF */
2756 get_token(FILE *fp
, /* I - File to read from */
2757 char *buf
, /* I - Buffer to read into */
2758 int buflen
, /* I - Length of buffer */
2759 int *linenum
) /* IO - Current line number */
2761 int ch
, /* Character from file */
2762 quote
; /* Quoting character */
2763 char *bufptr
, /* Pointer into buffer */
2764 *bufend
; /* End of buffer */
2770 * Skip whitespace...
2773 while (isspace(ch
= getc(fp
)))
2785 else if (ch
== '\'' || ch
== '\"')
2788 * Quoted text or regular expression...
2793 bufend
= buf
+ buflen
- 1;
2795 while ((ch
= getc(fp
)) != EOF
)
2800 * Escape next character...
2803 if (bufptr
< bufend
)
2804 *bufptr
++ = (char)ch
;
2806 if ((ch
= getc(fp
)) != EOF
&& bufptr
< bufend
)
2807 *bufptr
++ = (char)ch
;
2809 else if (ch
== quote
)
2811 else if (bufptr
< bufend
)
2812 *bufptr
++ = (char)ch
;
2825 while ((ch
= getc(fp
)) != EOF
)
2831 else if (ch
== '{' || ch
== '}' || ch
== ',')
2841 * Whitespace delimited text...
2847 bufend
= buf
+ buflen
- 1;
2849 while ((ch
= getc(fp
)) != EOF
)
2850 if (isspace(ch
) || ch
== '#')
2852 else if (bufptr
< bufend
)
2853 *bufptr
++ = (char)ch
;
2857 else if (ch
== '\n')
2869 * 'html_escape()' - Write a HTML-safe string.
2873 html_escape(_ipp_client_t
*client
, /* I - Client */
2874 const char *s
, /* I - String to write */
2875 size_t slen
) /* I - Number of characters to write */
2877 const char *start
, /* Start of segment */
2878 *end
; /* End of string */
2882 end
= s
+ (slen
> 0 ? slen
: strlen(s
));
2884 while (*s
&& s
< end
)
2886 if (*s
== '&' || *s
== '<')
2889 httpWrite2(client
->http
, start
, (size_t)(s
- start
));
2892 httpWrite2(client
->http
, "&", 5);
2894 httpWrite2(client
->http
, "<", 4);
2903 httpWrite2(client
->http
, start
, (size_t)(s
- start
));
2908 * 'html_footer()' - Show the web interface footer.
2910 * This function also writes the trailing 0-length chunk.
2914 html_footer(_ipp_client_t
*client
) /* I - Client */
2920 httpWrite2(client
->http
, "", 0);
2925 * 'html_header()' - Show the web interface header and title.
2929 html_header(_ipp_client_t
*client
, /* I - Client */
2930 const char *title
) /* I - Title */
2936 "<title>%s</title>\n"
2937 "<link rel=\"shortcut icon\" href=\"/icon.png\" type=\"image/png\">\n"
2938 "<link rel=\"apple-touch-icon\" href=\"/icon.png\" type=\"image/png\">\n"
2939 "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=9\">\n"
2940 "<meta name=\"viewport\" content=\"width=device-width\">\n"
2942 "body { font-family: sans-serif; margin: 0; }\n"
2943 "div.body { padding: 0px 10px 10px; }\n"
2944 "blockquote { background: #dfd; border-radius: 5px; color: #006; padding: 10px; }\n"
2945 "table.form { border-collapse: collapse; margin-top: 10px; width: 100%%; }\n"
2946 "table.form td, table.form th { padding: 5px 2px; width: 50%%; }\n"
2947 "table.form th { text-align: right; }\n"
2948 "table.striped { border-bottom: solid thin black; border-collapse: collapse; width: 100%%; }\n"
2949 "table.striped tr:nth-child(even) { background: #fcfcfc; }\n"
2950 "table.striped tr:nth-child(odd) { background: #f0f0f0; }\n"
2951 "table.striped th { background: white; border-bottom: solid thin black; text-align: left; vertical-align: bottom; }\n"
2952 "table.striped td { margin: 0; padding: 5px; vertical-align: top; }\n"
2953 "table.nav { border-collapse: collapse; width: 100%%; }\n"
2954 "table.nav td { margin: 0; text-align: center; }\n"
2955 "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"
2956 "td.nav { background: #333; color: #fff; padding: 4px 8px; width: 33%%; }\n"
2957 "td.nav.sel { background: #fff; color: #000; font-weight: bold; }\n"
2958 "td.nav:hover { background: #666; color: #fff; }\n"
2959 "td.nav:active { background: #000; color: #ff0; }\n"
2963 "<table class=\"nav\"><tr>"
2964 "<td class=\"nav%s\"><a href=\"/\">Status</a></td>"
2965 "<td class=\"nav%s\"><a href=\"/supplies\">Supplies</a></td>"
2966 "<td class=\"nav%s\"><a href=\"/media\">Media</a></td>"
2968 "<div class=\"body\">\n", title
, !strcmp(client
->uri
, "/") ? " sel" : "", !strcmp(client
->uri
, "/supplies") ? " sel" : "", !strcmp(client
->uri
, "/media") ? " sel" : "");
2973 * 'html_printf()' - Send formatted text to the client, quoting as needed.
2977 html_printf(_ipp_client_t
*client
, /* I - Client */
2978 const char *format
, /* I - Printf-style format string */
2979 ...) /* I - Additional arguments as needed */
2981 va_list ap
; /* Pointer to arguments */
2982 const char *start
; /* Start of string */
2983 char size
, /* Size character (h, l, L) */
2984 type
; /* Format type character */
2985 int width
, /* Width of field */
2986 prec
; /* Number of characters of precision */
2987 char tformat
[100], /* Temporary format string for sprintf() */
2988 *tptr
, /* Pointer into temporary format */
2989 temp
[1024]; /* Buffer for formatted numbers */
2990 char *s
; /* Pointer to string */
2994 * Loop through the format string, formatting as needed...
2997 va_start(ap
, format
);
3005 httpWrite2(client
->http
, start
, (size_t)(format
- start
));
3008 *tptr
++ = *format
++;
3012 httpWrite2(client
->http
, "%", 1);
3017 else if (strchr(" -+#\'", *format
))
3018 *tptr
++ = *format
++;
3023 * Get width from argument...
3027 width
= va_arg(ap
, int);
3029 snprintf(tptr
, sizeof(tformat
) - (size_t)(tptr
- tformat
), "%d", width
);
3030 tptr
+= strlen(tptr
);
3036 while (isdigit(*format
& 255))
3038 if (tptr
< (tformat
+ sizeof(tformat
) - 1))
3041 width
= width
* 10 + *format
++ - '0';
3047 if (tptr
< (tformat
+ sizeof(tformat
) - 1))
3055 * Get precision from argument...
3059 prec
= va_arg(ap
, int);
3061 snprintf(tptr
, sizeof(tformat
) - (size_t)(tptr
- tformat
), "%d", prec
);
3062 tptr
+= strlen(tptr
);
3068 while (isdigit(*format
& 255))
3070 if (tptr
< (tformat
+ sizeof(tformat
) - 1))
3073 prec
= prec
* 10 + *format
++ - '0';
3078 if (*format
== 'l' && format
[1] == 'l')
3082 if (tptr
< (tformat
+ sizeof(tformat
) - 2))
3090 else if (*format
== 'h' || *format
== 'l' || *format
== 'L')
3092 if (tptr
< (tformat
+ sizeof(tformat
) - 1))
3107 if (tptr
< (tformat
+ sizeof(tformat
) - 1))
3116 case 'E' : /* Floating point formats */
3121 if ((size_t)(width
+ 2) > sizeof(temp
))
3124 sprintf(temp
, tformat
, va_arg(ap
, double));
3126 httpWrite2(client
->http
, temp
, strlen(temp
));
3129 case 'B' : /* Integer formats */
3137 if ((size_t)(width
+ 2) > sizeof(temp
))
3140 # ifdef HAVE_LONG_LONG
3142 sprintf(temp
, tformat
, va_arg(ap
, long long));
3144 # endif /* HAVE_LONG_LONG */
3146 sprintf(temp
, tformat
, va_arg(ap
, long));
3148 sprintf(temp
, tformat
, va_arg(ap
, int));
3150 httpWrite2(client
->http
, temp
, strlen(temp
));
3153 case 'p' : /* Pointer value */
3154 if ((size_t)(width
+ 2) > sizeof(temp
))
3157 sprintf(temp
, tformat
, va_arg(ap
, void *));
3159 httpWrite2(client
->http
, temp
, strlen(temp
));
3162 case 'c' : /* Character or character array */
3165 temp
[0] = (char)va_arg(ap
, int);
3167 html_escape(client
, temp
, 1);
3170 html_escape(client
, va_arg(ap
, char *), (size_t)width
);
3173 case 's' : /* String */
3174 if ((s
= va_arg(ap
, char *)) == NULL
)
3177 html_escape(client
, s
, strlen(s
));
3186 httpWrite2(client
->http
, start
, (size_t)(format
- start
));
3193 * 'ipp_cancel_job()' - Cancel a job.
3197 ipp_cancel_job(_ipp_client_t
*client
) /* I - Client */
3199 _ipp_job_t
*job
; /* Job information */
3206 if ((job
= find_job(client
)) == NULL
)
3208 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "Job does not exist.");
3213 * See if the job is already completed, canceled, or aborted; if so,
3214 * we can't cancel...
3219 case IPP_JSTATE_CANCELED
:
3220 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3221 "Job #%d is already canceled - can\'t cancel.", job
->id
);
3224 case IPP_JSTATE_ABORTED
:
3225 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3226 "Job #%d is already aborted - can\'t cancel.", job
->id
);
3229 case IPP_JSTATE_COMPLETED
:
3230 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3231 "Job #%d is already completed - can\'t cancel.", job
->id
);
3239 _cupsRWLockWrite(&(client
->printer
->rwlock
));
3241 if (job
->state
== IPP_JSTATE_PROCESSING
||
3242 (job
->state
== IPP_JSTATE_HELD
&& job
->fd
>= 0))
3246 job
->state
= IPP_JSTATE_CANCELED
;
3247 job
->completed
= time(NULL
);
3250 _cupsRWUnlock(&(client
->printer
->rwlock
));
3252 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3259 * 'ipp_close_job()' - Close an open job.
3263 ipp_close_job(_ipp_client_t
*client
) /* I - Client */
3265 _ipp_job_t
*job
; /* Job information */
3272 if ((job
= find_job(client
)) == NULL
)
3274 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "Job does not exist.");
3279 * See if the job is already completed, canceled, or aborted; if so,
3280 * we can't cancel...
3285 case IPP_JSTATE_CANCELED
:
3286 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3287 "Job #%d is canceled - can\'t close.", job
->id
);
3290 case IPP_JSTATE_ABORTED
:
3291 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3292 "Job #%d is aborted - can\'t close.", job
->id
);
3295 case IPP_JSTATE_COMPLETED
:
3296 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3297 "Job #%d is completed - can\'t close.", job
->id
);
3300 case IPP_JSTATE_PROCESSING
:
3301 case IPP_JSTATE_STOPPED
:
3302 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3303 "Job #%d is already closed.", job
->id
);
3307 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3314 * 'ipp_create_job()' - Create a job object.
3318 ipp_create_job(_ipp_client_t
*client
) /* I - Client */
3320 _ipp_job_t
*job
; /* New job */
3321 cups_array_t
*ra
; /* Attributes to send in response */
3325 * Validate print job attributes...
3328 if (!valid_job_attributes(client
))
3330 httpFlush(client
->http
);
3335 * Do we have a file to print?
3338 if (httpGetState(client
->http
) == HTTP_STATE_POST_RECV
)
3340 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
3341 "Unexpected document data following request.");
3349 if ((job
= create_job(client
)) == NULL
)
3351 respond_ipp(client
, IPP_STATUS_ERROR_BUSY
,
3352 "Currently printing another job.");
3357 * Return the job info...
3360 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3362 ra
= cupsArrayNew((cups_array_func_t
)strcmp
, NULL
);
3363 cupsArrayAdd(ra
, "job-id");
3364 cupsArrayAdd(ra
, "job-state");
3365 cupsArrayAdd(ra
, "job-state-message");
3366 cupsArrayAdd(ra
, "job-state-reasons");
3367 cupsArrayAdd(ra
, "job-uri");
3369 copy_job_attributes(client
, job
, ra
);
3370 cupsArrayDelete(ra
);
3375 * 'ipp_get_job_attributes()' - Get the attributes for a job object.
3379 ipp_get_job_attributes(
3380 _ipp_client_t
*client
) /* I - Client */
3382 _ipp_job_t
*job
; /* Job */
3383 cups_array_t
*ra
; /* requested-attributes */
3386 if ((job
= find_job(client
)) == NULL
)
3388 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "Job not found.");
3392 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3394 ra
= ippCreateRequestedArray(client
->request
);
3395 copy_job_attributes(client
, job
, ra
);
3396 cupsArrayDelete(ra
);
3401 * 'ipp_get_jobs()' - Get a list of job objects.
3405 ipp_get_jobs(_ipp_client_t
*client
) /* I - Client */
3407 ipp_attribute_t
*attr
; /* Current attribute */
3408 const char *which_jobs
= NULL
;
3409 /* which-jobs values */
3410 int job_comparison
; /* Job comparison */
3411 ipp_jstate_t job_state
; /* job-state value */
3412 int first_job_id
, /* First job ID */
3413 limit
, /* Maximum number of jobs to return */
3414 count
; /* Number of jobs that match */
3415 const char *username
; /* Username */
3416 _ipp_job_t
*job
; /* Current job pointer */
3417 cups_array_t
*ra
; /* Requested attributes array */
3421 * See if the "which-jobs" attribute have been specified...
3424 if ((attr
= ippFindAttribute(client
->request
, "which-jobs",
3425 IPP_TAG_KEYWORD
)) != NULL
)
3427 which_jobs
= ippGetString(attr
, 0, NULL
);
3428 fprintf(stderr
, "%s Get-Jobs which-jobs=%s", client
->hostname
, which_jobs
);
3431 if (!which_jobs
|| !strcmp(which_jobs
, "not-completed"))
3433 job_comparison
= -1;
3434 job_state
= IPP_JSTATE_STOPPED
;
3436 else if (!strcmp(which_jobs
, "completed"))
3439 job_state
= IPP_JSTATE_CANCELED
;
3441 else if (!strcmp(which_jobs
, "aborted"))
3444 job_state
= IPP_JSTATE_ABORTED
;
3446 else if (!strcmp(which_jobs
, "all"))
3449 job_state
= IPP_JSTATE_PENDING
;
3451 else if (!strcmp(which_jobs
, "canceled"))
3454 job_state
= IPP_JSTATE_CANCELED
;
3456 else if (!strcmp(which_jobs
, "pending"))
3459 job_state
= IPP_JSTATE_PENDING
;
3461 else if (!strcmp(which_jobs
, "pending-held"))
3464 job_state
= IPP_JSTATE_HELD
;
3466 else if (!strcmp(which_jobs
, "processing"))
3469 job_state
= IPP_JSTATE_PROCESSING
;
3471 else if (!strcmp(which_jobs
, "processing-stopped"))
3474 job_state
= IPP_JSTATE_STOPPED
;
3478 respond_ipp(client
, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES
,
3479 "The which-jobs value \"%s\" is not supported.", which_jobs
);
3480 ippAddString(client
->response
, IPP_TAG_UNSUPPORTED_GROUP
, IPP_TAG_KEYWORD
,
3481 "which-jobs", NULL
, which_jobs
);
3486 * See if they want to limit the number of jobs reported...
3489 if ((attr
= ippFindAttribute(client
->request
, "limit",
3490 IPP_TAG_INTEGER
)) != NULL
)
3492 limit
= ippGetInteger(attr
, 0);
3494 fprintf(stderr
, "%s Get-Jobs limit=%d", client
->hostname
, limit
);
3499 if ((attr
= ippFindAttribute(client
->request
, "first-job-id",
3500 IPP_TAG_INTEGER
)) != NULL
)
3502 first_job_id
= ippGetInteger(attr
, 0);
3504 fprintf(stderr
, "%s Get-Jobs first-job-id=%d", client
->hostname
,
3511 * See if we only want to see jobs for a specific user...
3516 if ((attr
= ippFindAttribute(client
->request
, "my-jobs",
3517 IPP_TAG_BOOLEAN
)) != NULL
)
3519 int my_jobs
= ippGetBoolean(attr
, 0);
3521 fprintf(stderr
, "%s Get-Jobs my-jobs=%s\n", client
->hostname
,
3522 my_jobs
? "true" : "false");
3526 if ((attr
= ippFindAttribute(client
->request
, "requesting-user-name",
3527 IPP_TAG_NAME
)) == NULL
)
3529 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
3530 "Need requesting-user-name with my-jobs.");
3534 username
= ippGetString(attr
, 0, NULL
);
3536 fprintf(stderr
, "%s Get-Jobs requesting-user-name=\"%s\"\n",
3537 client
->hostname
, username
);
3542 * OK, build a list of jobs for this printer...
3545 ra
= ippCreateRequestedArray(client
->request
);
3547 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3549 _cupsRWLockRead(&(client
->printer
->rwlock
));
3551 for (count
= 0, job
= (_ipp_job_t
*)cupsArrayFirst(client
->printer
->jobs
);
3552 (limit
<= 0 || count
< limit
) && job
;
3553 job
= (_ipp_job_t
*)cupsArrayNext(client
->printer
->jobs
))
3556 * Filter out jobs that don't match...
3559 if ((job_comparison
< 0 && job
->state
> job_state
) ||
3560 (job_comparison
== 0 && job
->state
!= job_state
) ||
3561 (job_comparison
> 0 && job
->state
< job_state
) ||
3562 job
->id
< first_job_id
||
3563 (username
&& job
->username
&&
3564 strcasecmp(username
, job
->username
)))
3568 ippAddSeparator(client
->response
);
3571 copy_job_attributes(client
, job
, ra
);
3574 cupsArrayDelete(ra
);
3576 _cupsRWUnlock(&(client
->printer
->rwlock
));
3581 * 'ipp_get_printer_attributes()' - Get the attributes for a printer object.
3585 ipp_get_printer_attributes(
3586 _ipp_client_t
*client
) /* I - Client */
3588 cups_array_t
*ra
; /* Requested attributes array */
3589 _ipp_printer_t
*printer
; /* Printer */
3593 * Send the attributes...
3596 ra
= ippCreateRequestedArray(client
->request
);
3597 printer
= client
->printer
;
3599 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3601 _cupsRWLockRead(&(printer
->rwlock
));
3603 copy_attributes(client
->response
, printer
->attrs
, ra
, IPP_TAG_ZERO
,
3604 IPP_TAG_CUPS_CONST
);
3606 if (!ra
|| cupsArrayFind(ra
, "media-col-ready"))
3608 int i
, /* Looping var */
3609 num_ready
= 0; /* Number of ready media */
3610 ipp_t
*ready
[3]; /* Ready media */
3612 if (printer
->main_size
!= _IPP_MEDIA_SIZE_NONE
)
3614 if (printer
->main_type
!= _IPP_MEDIA_TYPE_NONE
)
3615 ready
[num_ready
++] = create_media_col(media_supported
[printer
->main_size
], "main", media_type_supported
[printer
->main_type
], media_col_sizes
[printer
->main_size
][0], media_col_sizes
[printer
->main_size
][1], 635);
3617 ready
[num_ready
++] = create_media_col(media_supported
[printer
->main_size
], "main", NULL
, media_col_sizes
[printer
->main_size
][0], media_col_sizes
[printer
->main_size
][1], 635);
3619 if (printer
->envelope_size
!= _IPP_MEDIA_SIZE_NONE
)
3620 ready
[num_ready
++] = create_media_col(media_supported
[printer
->envelope_size
], "envelope", NULL
, media_col_sizes
[printer
->envelope_size
][0], media_col_sizes
[printer
->envelope_size
][1], 635);
3621 if (printer
->photo_size
!= _IPP_MEDIA_SIZE_NONE
)
3623 if (printer
->photo_type
!= _IPP_MEDIA_TYPE_NONE
)
3624 ready
[num_ready
++] = create_media_col(media_supported
[printer
->photo_size
], "photo", media_type_supported
[printer
->photo_type
], media_col_sizes
[printer
->photo_size
][0], media_col_sizes
[printer
->photo_size
][1], 0);
3626 ready
[num_ready
++] = create_media_col(media_supported
[printer
->photo_size
], "photo", NULL
, media_col_sizes
[printer
->photo_size
][0], media_col_sizes
[printer
->photo_size
][1], 0);
3631 ippAddCollections(client
->response
, IPP_TAG_PRINTER
, "media-col-ready", num_ready
, (const ipp_t
**)ready
);
3632 for (i
= 0; i
< num_ready
; i
++)
3633 ippDelete(ready
[i
]);
3636 ippAddOutOfBand(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_NOVALUE
, "media-col-ready");
3639 if (!ra
|| cupsArrayFind(ra
, "media-ready"))
3641 int num_ready
= 0; /* Number of ready media */
3642 const char *ready
[3]; /* Ready media */
3644 if (printer
->main_size
!= _IPP_MEDIA_SIZE_NONE
)
3645 ready
[num_ready
++] = media_supported
[printer
->main_size
];
3647 if (printer
->envelope_size
!= _IPP_MEDIA_SIZE_NONE
)
3648 ready
[num_ready
++] = media_supported
[printer
->envelope_size
];
3650 if (printer
->photo_size
!= _IPP_MEDIA_SIZE_NONE
)
3651 ready
[num_ready
++] = media_supported
[printer
->photo_size
];
3654 ippAddStrings(client
->response
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "media-ready", num_ready
, NULL
, ready
);
3656 ippAddOutOfBand(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_NOVALUE
, "media-ready");
3659 if (!ra
|| cupsArrayFind(ra
, "printer-config-change-date-time"))
3660 ippAddDate(client
->response
, IPP_TAG_PRINTER
, "printer-config-change-date-time", ippTimeToDate(printer
->config_time
));
3662 if (!ra
|| cupsArrayFind(ra
, "printer-config-change-time"))
3663 ippAddInteger(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "printer-config-change-time", (int)(printer
->config_time
- printer
->start_time
));
3665 if (!ra
|| cupsArrayFind(ra
, "printer-current-time"))
3666 ippAddDate(client
->response
, IPP_TAG_PRINTER
, "printer-current-time", ippTimeToDate(time(NULL
)));
3669 if (!ra
|| cupsArrayFind(ra
, "printer-state"))
3670 ippAddInteger(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
,
3671 "printer-state", printer
->state
);
3673 if (!ra
|| cupsArrayFind(ra
, "printer-state-change-date-time"))
3674 ippAddDate(client
->response
, IPP_TAG_PRINTER
, "printer-state-change-date-time", ippTimeToDate(printer
->state_time
));
3676 if (!ra
|| cupsArrayFind(ra
, "printer-state-change-time"))
3677 ippAddInteger(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "printer-state-change-time", (int)(printer
->state_time
- printer
->start_time
));
3679 if (!ra
|| cupsArrayFind(ra
, "printer-state-message"))
3681 static const char * const messages
[] = { "Idle.", "Printing.", "Stopped." };
3683 ippAddString(client
->response
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_TEXT
), "printer-state-message", NULL
, messages
[printer
->state
- IPP_PSTATE_IDLE
]);
3686 if (!ra
|| cupsArrayFind(ra
, "printer-state-reasons"))
3688 if (printer
->state_reasons
== _IPP_PREASON_NONE
)
3689 ippAddString(client
->response
, IPP_TAG_PRINTER
,
3690 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
3691 "printer-state-reasons", NULL
, "none");
3694 ipp_attribute_t
*attr
= NULL
; /* printer-state-reasons */
3695 _ipp_preason_t bit
; /* Reason bit */
3696 int i
; /* Looping var */
3697 char reason
[32]; /* Reason string */
3699 for (i
= 0, bit
= 1; i
< (int)(sizeof(_ipp_preason_strings
) / sizeof(_ipp_preason_strings
[0])); i
++, bit
*= 2)
3701 if (printer
->state_reasons
& bit
)
3703 snprintf(reason
, sizeof(reason
), "%s-%s", _ipp_preason_strings
[i
], printer
->state
== IPP_PSTATE_IDLE
? "report" : printer
->state
== IPP_PSTATE_PROCESSING
? "warning" : "error");
3705 ippSetString(client
->response
, &attr
, ippGetCount(attr
), reason
);
3707 attr
= ippAddString(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "printer-state-reasons", NULL
, reason
);
3713 if (!ra
|| cupsArrayFind(ra
, "printer-supply"))
3715 int i
; /* Looping var */
3716 char buffer
[256]; /* Supply value buffer */
3717 ipp_attribute_t
*attr
= NULL
; /* Attribute */
3718 static const char * const colorants
[] = { "cyan", "magenta", "yellow", "black", "unknown" };
3720 for (i
= 0; i
< 5; i
++)
3722 snprintf(buffer
, sizeof(buffer
), "index=%d;class=%s;type=%s;unit=percent;maxcapacity=100;level=%d;colorantname=%s;", i
+ 1, i
< 4 ? "supplyThatIsConsumed" : "receptacleThatIsFilled", i
< 4 ? "toner" : "wasteToner", printer
->supplies
[i
], colorants
[i
]);
3725 attr
= ippAddOctetString(client
->response
, IPP_TAG_PRINTER
, "printer-supply", buffer
, (int)strlen(buffer
));
3727 ippSetOctetString(client
->response
, &attr
, i
, buffer
, (int)strlen(buffer
));
3731 if (!ra
|| cupsArrayFind(ra
, "printer-up-time"))
3732 ippAddInteger(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "printer-up-time", (int)(time(NULL
) - printer
->start_time
));
3734 if (!ra
|| cupsArrayFind(ra
, "queued-job-count"))
3735 ippAddInteger(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
3736 "queued-job-count", printer
->active_job
&& printer
->active_job
->state
< IPP_JSTATE_CANCELED
);
3738 _cupsRWUnlock(&(printer
->rwlock
));
3740 cupsArrayDelete(ra
);
3745 * 'ipp_identify_printer()' - Beep or display a message.
3749 ipp_identify_printer(
3750 _ipp_client_t
*client
) /* I - Client */
3752 ipp_attribute_t
*actions
, /* identify-actions */
3753 *message
; /* message */
3756 actions
= ippFindAttribute(client
->request
, "identify-actions", IPP_TAG_KEYWORD
);
3757 message
= ippFindAttribute(client
->request
, "message", IPP_TAG_TEXT
);
3759 if (!actions
|| ippContainsString(actions
, "sound"))
3765 if (ippContainsString(actions
, "display"))
3766 printf("IDENTIFY from %s: %s\n", client
->hostname
, message
? ippGetString(message
, 0, NULL
) : "No message supplied");
3768 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3773 * 'ipp_print_job()' - Create a job object with an attached document.
3777 ipp_print_job(_ipp_client_t
*client
) /* I - Client */
3779 _ipp_job_t
*job
; /* New job */
3780 char filename
[1024], /* Filename buffer */
3781 buffer
[4096]; /* Copy buffer */
3782 ssize_t bytes
; /* Bytes read */
3783 cups_array_t
*ra
; /* Attributes to send in response */
3784 _cups_thread_t t
; /* Thread */
3788 * Validate print job attributes...
3791 if (!valid_job_attributes(client
))
3793 httpFlush(client
->http
);
3798 * Do we have a file to print?
3801 if (httpGetState(client
->http
) == HTTP_STATE_POST_SEND
)
3803 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "No file in request.");
3811 if ((job
= create_job(client
)) == NULL
)
3813 respond_ipp(client
, IPP_STATUS_ERROR_BUSY
,
3814 "Currently printing another job.");
3819 * Create a file for the request data...
3822 create_job_filename(client
->printer
, job
, filename
, sizeof(filename
));
3825 fprintf(stderr
, "Creating job file \"%s\", format \"%s\".\n", filename
, job
->format
);
3827 if ((job
->fd
= open(filename
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600)) < 0)
3829 job
->state
= IPP_JSTATE_ABORTED
;
3831 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
3832 "Unable to create print file: %s", strerror(errno
));
3836 while ((bytes
= httpRead2(client
->http
, buffer
, sizeof(buffer
))) > 0)
3838 if (write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
3840 int error
= errno
; /* Write error */
3842 job
->state
= IPP_JSTATE_ABORTED
;
3849 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
3850 "Unable to write print file: %s", strerror(error
));
3858 * Got an error while reading the print data, so abort this job.
3861 job
->state
= IPP_JSTATE_ABORTED
;
3868 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
3869 "Unable to read print file.");
3875 int error
= errno
; /* Write error */
3877 job
->state
= IPP_JSTATE_ABORTED
;
3882 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
3883 "Unable to write print file: %s", strerror(error
));
3888 job
->filename
= strdup(filename
);
3889 job
->state
= IPP_JSTATE_PENDING
;
3892 * Process the job...
3895 t
= _cupsThreadCreate((_cups_thread_func_t
)process_job
, job
);
3899 _cupsThreadDetach(t
);
3903 job
->state
= IPP_JSTATE_ABORTED
;
3904 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
, "Unable to process job.");
3909 * Return the job info...
3912 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3914 ra
= cupsArrayNew((cups_array_func_t
)strcmp
, NULL
);
3915 cupsArrayAdd(ra
, "job-id");
3916 cupsArrayAdd(ra
, "job-state");
3917 cupsArrayAdd(ra
, "job-state-message");
3918 cupsArrayAdd(ra
, "job-state-reasons");
3919 cupsArrayAdd(ra
, "job-uri");
3921 copy_job_attributes(client
, job
, ra
);
3922 cupsArrayDelete(ra
);
3927 * 'ipp_print_uri()' - Create a job object with a referenced document.
3931 ipp_print_uri(_ipp_client_t
*client
) /* I - Client */
3933 _ipp_job_t
*job
; /* New job */
3934 ipp_attribute_t
*uri
; /* document-uri */
3935 char scheme
[256], /* URI scheme */
3936 userpass
[256], /* Username and password info */
3937 hostname
[256], /* Hostname */
3938 resource
[1024]; /* Resource path */
3939 int port
; /* Port number */
3940 http_uri_status_t uri_status
; /* URI decode status */
3941 http_encryption_t encryption
; /* Encryption to use, if any */
3942 http_t
*http
; /* Connection for http/https URIs */
3943 http_status_t status
; /* Access status for http/https URIs */
3944 int infile
; /* Input file for local file URIs */
3945 char filename
[1024], /* Filename buffer */
3946 buffer
[4096]; /* Copy buffer */
3947 ssize_t bytes
; /* Bytes read */
3948 cups_array_t
*ra
; /* Attributes to send in response */
3949 static const char * const uri_status_strings
[] =
3950 { /* URI decode errors */
3952 "Bad arguments to function.",
3953 "Bad resource in URI.",
3954 "Bad port number in URI.",
3955 "Bad hostname in URI.",
3956 "Bad username in URI.",
3957 "Bad scheme in URI.",
3963 * Validate print job attributes...
3966 if (!valid_job_attributes(client
))
3968 httpFlush(client
->http
);
3973 * Do we have a file to print?
3976 if (httpGetState(client
->http
) == HTTP_STATE_POST_RECV
)
3978 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
3979 "Unexpected document data following request.");
3984 * Do we have a document URI?
3987 if ((uri
= ippFindAttribute(client
->request
, "document-uri",
3988 IPP_TAG_URI
)) == NULL
)
3990 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "Missing document-uri.");
3994 if (ippGetCount(uri
) != 1)
3996 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
3997 "Too many document-uri values.");
4001 uri_status
= httpSeparateURI(HTTP_URI_CODING_ALL
, ippGetString(uri
, 0, NULL
),
4002 scheme
, sizeof(scheme
), userpass
,
4003 sizeof(userpass
), hostname
, sizeof(hostname
),
4004 &port
, resource
, sizeof(resource
));
4005 if (uri_status
< HTTP_URI_STATUS_OK
)
4007 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "Bad document-uri: %s",
4008 uri_status_strings
[uri_status
- HTTP_URI_STATUS_OVERFLOW
]);
4012 if (strcmp(scheme
, "file") &&
4014 strcmp(scheme
, "https") &&
4015 #endif /* HAVE_SSL */
4016 strcmp(scheme
, "http"))
4018 respond_ipp(client
, IPP_STATUS_ERROR_URI_SCHEME
,
4019 "URI scheme \"%s\" not supported.", scheme
);
4023 if (!strcmp(scheme
, "file") && access(resource
, R_OK
))
4025 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4026 "Unable to access URI: %s", strerror(errno
));
4034 if ((job
= create_job(client
)) == NULL
)
4036 respond_ipp(client
, IPP_STATUS_ERROR_BUSY
,
4037 "Currently printing another job.");
4042 * Create a file for the request data...
4045 if (!strcasecmp(job
->format
, "image/jpeg"))
4046 snprintf(filename
, sizeof(filename
), "%s/%d.jpg",
4047 client
->printer
->directory
, job
->id
);
4048 else if (!strcasecmp(job
->format
, "image/png"))
4049 snprintf(filename
, sizeof(filename
), "%s/%d.png",
4050 client
->printer
->directory
, job
->id
);
4051 else if (!strcasecmp(job
->format
, "application/pdf"))
4052 snprintf(filename
, sizeof(filename
), "%s/%d.pdf",
4053 client
->printer
->directory
, job
->id
);
4054 else if (!strcasecmp(job
->format
, "application/postscript"))
4055 snprintf(filename
, sizeof(filename
), "%s/%d.ps",
4056 client
->printer
->directory
, job
->id
);
4058 snprintf(filename
, sizeof(filename
), "%s/%d.prn",
4059 client
->printer
->directory
, job
->id
);
4061 if ((job
->fd
= open(filename
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600)) < 0)
4063 job
->state
= IPP_JSTATE_ABORTED
;
4065 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4066 "Unable to create print file: %s", strerror(errno
));
4070 if (!strcmp(scheme
, "file"))
4072 if ((infile
= open(resource
, O_RDONLY
)) < 0)
4074 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4075 "Unable to access URI: %s", strerror(errno
));
4081 if ((bytes
= read(infile
, buffer
, sizeof(buffer
))) < 0 &&
4082 (errno
== EAGAIN
|| errno
== EINTR
))
4084 else if (bytes
> 0 && write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
4086 int error
= errno
; /* Write error */
4088 job
->state
= IPP_JSTATE_ABORTED
;
4096 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4097 "Unable to write print file: %s", strerror(error
));
4108 if (port
== 443 || !strcmp(scheme
, "https"))
4109 encryption
= HTTP_ENCRYPTION_ALWAYS
;
4111 #endif /* HAVE_SSL */
4112 encryption
= HTTP_ENCRYPTION_IF_REQUESTED
;
4114 if ((http
= httpConnect2(hostname
, port
, NULL
, AF_UNSPEC
, encryption
,
4115 1, 30000, NULL
)) == NULL
)
4117 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4118 "Unable to connect to %s: %s", hostname
,
4119 cupsLastErrorString());
4120 job
->state
= IPP_JSTATE_ABORTED
;
4129 httpClearFields(http
);
4130 httpSetField(http
, HTTP_FIELD_ACCEPT_LANGUAGE
, "en");
4131 if (httpGet(http
, resource
))
4133 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4134 "Unable to GET URI: %s", strerror(errno
));
4136 job
->state
= IPP_JSTATE_ABORTED
;
4146 while ((status
= httpUpdate(http
)) == HTTP_STATUS_CONTINUE
);
4148 if (status
!= HTTP_STATUS_OK
)
4150 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4151 "Unable to GET URI: %s", httpStatus(status
));
4153 job
->state
= IPP_JSTATE_ABORTED
;
4163 while ((bytes
= httpRead2(http
, buffer
, sizeof(buffer
))) > 0)
4165 if (write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
4167 int error
= errno
; /* Write error */
4169 job
->state
= IPP_JSTATE_ABORTED
;
4177 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4178 "Unable to write print file: %s", strerror(error
));
4188 int error
= errno
; /* Write error */
4190 job
->state
= IPP_JSTATE_ABORTED
;
4195 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4196 "Unable to write print file: %s", strerror(error
));
4201 job
->filename
= strdup(filename
);
4202 job
->state
= IPP_JSTATE_PENDING
;
4205 * Process the job...
4211 * Return the job info...
4214 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
4216 ra
= cupsArrayNew((cups_array_func_t
)strcmp
, NULL
);
4217 cupsArrayAdd(ra
, "job-id");
4218 cupsArrayAdd(ra
, "job-state");
4219 cupsArrayAdd(ra
, "job-state-reasons");
4220 cupsArrayAdd(ra
, "job-uri");
4222 copy_job_attributes(client
, job
, ra
);
4223 cupsArrayDelete(ra
);
4228 * 'ipp_send_document()' - Add an attached document to a job object created with
4233 ipp_send_document(_ipp_client_t
*client
)/* I - Client */
4235 _ipp_job_t
*job
; /* Job information */
4236 char filename
[1024], /* Filename buffer */
4237 buffer
[4096]; /* Copy buffer */
4238 ssize_t bytes
; /* Bytes read */
4239 ipp_attribute_t
*attr
; /* Current attribute */
4240 cups_array_t
*ra
; /* Attributes to send in response */
4247 if ((job
= find_job(client
)) == NULL
)
4249 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "Job does not exist.");
4250 httpFlush(client
->http
);
4255 * See if we already have a document for this job or the job has already
4256 * in a non-pending state...
4259 if (job
->state
> IPP_JSTATE_HELD
)
4261 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
4262 "Job is not in a pending state.");
4263 httpFlush(client
->http
);
4266 else if (job
->filename
|| job
->fd
>= 0)
4268 respond_ipp(client
, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED
,
4269 "Multiple document jobs are not supported.");
4270 httpFlush(client
->http
);
4274 if ((attr
= ippFindAttribute(client
->request
, "last-document",
4275 IPP_TAG_ZERO
)) == NULL
)
4277 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
4278 "Missing required last-document attribute.");
4279 httpFlush(client
->http
);
4282 else if (ippGetValueTag(attr
) != IPP_TAG_BOOLEAN
|| ippGetCount(attr
) != 1 ||
4283 !ippGetBoolean(attr
, 0))
4285 respond_unsupported(client
, attr
);
4286 httpFlush(client
->http
);
4291 * Validate document attributes...
4294 if (!valid_doc_attributes(client
))
4296 httpFlush(client
->http
);
4300 copy_attributes(job
->attrs
, client
->request
, NULL
, IPP_TAG_JOB
, 0);
4303 * Get the document format for the job...
4306 _cupsRWLockWrite(&(client
->printer
->rwlock
));
4308 if ((attr
= ippFindAttribute(job
->attrs
, "document-format-detected", IPP_TAG_MIMETYPE
)) != NULL
)
4309 job
->format
= ippGetString(attr
, 0, NULL
);
4310 else if ((attr
= ippFindAttribute(job
->attrs
, "document-format-supplied", IPP_TAG_MIMETYPE
)) != NULL
)
4311 job
->format
= ippGetString(attr
, 0, NULL
);
4313 job
->format
= "application/octet-stream";
4316 * Create a file for the request data...
4319 create_job_filename(client
->printer
, job
, filename
, sizeof(filename
));
4322 fprintf(stderr
, "Creating job file \"%s\", format \"%s\".\n", filename
, job
->format
);
4324 job
->fd
= open(filename
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600);
4326 _cupsRWUnlock(&(client
->printer
->rwlock
));
4330 job
->state
= IPP_JSTATE_ABORTED
;
4332 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4333 "Unable to create print file: %s", strerror(errno
));
4337 while ((bytes
= httpRead2(client
->http
, buffer
, sizeof(buffer
))) > 0)
4339 if (write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
4341 int error
= errno
; /* Write error */
4343 job
->state
= IPP_JSTATE_ABORTED
;
4350 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4351 "Unable to write print file: %s", strerror(error
));
4359 * Got an error while reading the print data, so abort this job.
4362 job
->state
= IPP_JSTATE_ABORTED
;
4369 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4370 "Unable to read print file.");
4376 int error
= errno
; /* Write error */
4378 job
->state
= IPP_JSTATE_ABORTED
;
4383 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4384 "Unable to write print file: %s", strerror(error
));
4388 _cupsRWLockWrite(&(client
->printer
->rwlock
));
4391 job
->filename
= strdup(filename
);
4392 job
->state
= IPP_JSTATE_PENDING
;
4394 _cupsRWUnlock(&(client
->printer
->rwlock
));
4397 * Process the job...
4403 * Return the job info...
4406 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
4408 ra
= cupsArrayNew((cups_array_func_t
)strcmp
, NULL
);
4409 cupsArrayAdd(ra
, "job-id");
4410 cupsArrayAdd(ra
, "job-state");
4411 cupsArrayAdd(ra
, "job-state-reasons");
4412 cupsArrayAdd(ra
, "job-uri");
4414 copy_job_attributes(client
, job
, ra
);
4415 cupsArrayDelete(ra
);
4420 * 'ipp_send_uri()' - Add a referenced document to a job object created with
4425 ipp_send_uri(_ipp_client_t
*client
) /* I - Client */
4427 _ipp_job_t
*job
; /* Job information */
4428 ipp_attribute_t
*uri
; /* document-uri */
4429 char scheme
[256], /* URI scheme */
4430 userpass
[256], /* Username and password info */
4431 hostname
[256], /* Hostname */
4432 resource
[1024]; /* Resource path */
4433 int port
; /* Port number */
4434 http_uri_status_t uri_status
; /* URI decode status */
4435 http_encryption_t encryption
; /* Encryption to use, if any */
4436 http_t
*http
; /* Connection for http/https URIs */
4437 http_status_t status
; /* Access status for http/https URIs */
4438 int infile
; /* Input file for local file URIs */
4439 char filename
[1024], /* Filename buffer */
4440 buffer
[4096]; /* Copy buffer */
4441 ssize_t bytes
; /* Bytes read */
4442 ipp_attribute_t
*attr
; /* Current attribute */
4443 cups_array_t
*ra
; /* Attributes to send in response */
4444 static const char * const uri_status_strings
[] =
4445 { /* URI decode errors */
4447 "Bad arguments to function.",
4448 "Bad resource in URI.",
4449 "Bad port number in URI.",
4450 "Bad hostname in URI.",
4451 "Bad username in URI.",
4452 "Bad scheme in URI.",
4461 if ((job
= find_job(client
)) == NULL
)
4463 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "Job does not exist.");
4464 httpFlush(client
->http
);
4469 * See if we already have a document for this job or the job has already
4470 * in a non-pending state...
4473 if (job
->state
> IPP_JSTATE_HELD
)
4475 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
4476 "Job is not in a pending state.");
4477 httpFlush(client
->http
);
4480 else if (job
->filename
|| job
->fd
>= 0)
4482 respond_ipp(client
, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED
,
4483 "Multiple document jobs are not supported.");
4484 httpFlush(client
->http
);
4488 if ((attr
= ippFindAttribute(client
->request
, "last-document",
4489 IPP_TAG_ZERO
)) == NULL
)
4491 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
4492 "Missing required last-document attribute.");
4493 httpFlush(client
->http
);
4496 else if (ippGetValueTag(attr
) != IPP_TAG_BOOLEAN
|| ippGetCount(attr
) != 1 ||
4497 !ippGetBoolean(attr
, 0))
4499 respond_unsupported(client
, attr
);
4500 httpFlush(client
->http
);
4505 * Validate document attributes...
4508 if (!valid_doc_attributes(client
))
4510 httpFlush(client
->http
);
4515 * Do we have a file to print?
4518 if (httpGetState(client
->http
) == HTTP_STATE_POST_RECV
)
4520 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
4521 "Unexpected document data following request.");
4526 * Do we have a document URI?
4529 if ((uri
= ippFindAttribute(client
->request
, "document-uri",
4530 IPP_TAG_URI
)) == NULL
)
4532 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "Missing document-uri.");
4536 if (ippGetCount(uri
) != 1)
4538 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
4539 "Too many document-uri values.");
4543 uri_status
= httpSeparateURI(HTTP_URI_CODING_ALL
, ippGetString(uri
, 0, NULL
),
4544 scheme
, sizeof(scheme
), userpass
,
4545 sizeof(userpass
), hostname
, sizeof(hostname
),
4546 &port
, resource
, sizeof(resource
));
4547 if (uri_status
< HTTP_URI_STATUS_OK
)
4549 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "Bad document-uri: %s",
4550 uri_status_strings
[uri_status
- HTTP_URI_STATUS_OVERFLOW
]);
4554 if (strcmp(scheme
, "file") &&
4556 strcmp(scheme
, "https") &&
4557 #endif /* HAVE_SSL */
4558 strcmp(scheme
, "http"))
4560 respond_ipp(client
, IPP_STATUS_ERROR_URI_SCHEME
,
4561 "URI scheme \"%s\" not supported.", scheme
);
4565 if (!strcmp(scheme
, "file") && access(resource
, R_OK
))
4567 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4568 "Unable to access URI: %s", strerror(errno
));
4573 * Get the document format for the job...
4576 _cupsRWLockWrite(&(client
->printer
->rwlock
));
4578 if ((attr
= ippFindAttribute(job
->attrs
, "document-format",
4579 IPP_TAG_MIMETYPE
)) != NULL
)
4580 job
->format
= ippGetString(attr
, 0, NULL
);
4582 job
->format
= "application/octet-stream";
4585 * Create a file for the request data...
4588 if (!strcasecmp(job
->format
, "image/jpeg"))
4589 snprintf(filename
, sizeof(filename
), "%s/%d.jpg",
4590 client
->printer
->directory
, job
->id
);
4591 else if (!strcasecmp(job
->format
, "image/png"))
4592 snprintf(filename
, sizeof(filename
), "%s/%d.png",
4593 client
->printer
->directory
, job
->id
);
4594 else if (!strcasecmp(job
->format
, "application/pdf"))
4595 snprintf(filename
, sizeof(filename
), "%s/%d.pdf",
4596 client
->printer
->directory
, job
->id
);
4597 else if (!strcasecmp(job
->format
, "application/postscript"))
4598 snprintf(filename
, sizeof(filename
), "%s/%d.ps",
4599 client
->printer
->directory
, job
->id
);
4601 snprintf(filename
, sizeof(filename
), "%s/%d.prn",
4602 client
->printer
->directory
, job
->id
);
4604 job
->fd
= open(filename
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600);
4606 _cupsRWUnlock(&(client
->printer
->rwlock
));
4610 job
->state
= IPP_JSTATE_ABORTED
;
4612 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4613 "Unable to create print file: %s", strerror(errno
));
4617 if (!strcmp(scheme
, "file"))
4619 if ((infile
= open(resource
, O_RDONLY
)) < 0)
4621 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4622 "Unable to access URI: %s", strerror(errno
));
4628 if ((bytes
= read(infile
, buffer
, sizeof(buffer
))) < 0 &&
4629 (errno
== EAGAIN
|| errno
== EINTR
))
4631 else if (bytes
> 0 && write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
4633 int error
= errno
; /* Write error */
4635 job
->state
= IPP_JSTATE_ABORTED
;
4643 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4644 "Unable to write print file: %s", strerror(error
));
4655 if (port
== 443 || !strcmp(scheme
, "https"))
4656 encryption
= HTTP_ENCRYPTION_ALWAYS
;
4658 #endif /* HAVE_SSL */
4659 encryption
= HTTP_ENCRYPTION_IF_REQUESTED
;
4661 if ((http
= httpConnect2(hostname
, port
, NULL
, AF_UNSPEC
, encryption
,
4662 1, 30000, NULL
)) == NULL
)
4664 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4665 "Unable to connect to %s: %s", hostname
,
4666 cupsLastErrorString());
4667 job
->state
= IPP_JSTATE_ABORTED
;
4676 httpClearFields(http
);
4677 httpSetField(http
, HTTP_FIELD_ACCEPT_LANGUAGE
, "en");
4678 if (httpGet(http
, resource
))
4680 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4681 "Unable to GET URI: %s", strerror(errno
));
4683 job
->state
= IPP_JSTATE_ABORTED
;
4693 while ((status
= httpUpdate(http
)) == HTTP_STATUS_CONTINUE
);
4695 if (status
!= HTTP_STATUS_OK
)
4697 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4698 "Unable to GET URI: %s", httpStatus(status
));
4700 job
->state
= IPP_JSTATE_ABORTED
;
4710 while ((bytes
= httpRead2(http
, buffer
, sizeof(buffer
))) > 0)
4712 if (write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
4714 int error
= errno
; /* Write error */
4716 job
->state
= IPP_JSTATE_ABORTED
;
4724 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4725 "Unable to write print file: %s", strerror(error
));
4735 int error
= errno
; /* Write error */
4737 job
->state
= IPP_JSTATE_ABORTED
;
4742 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4743 "Unable to write print file: %s", strerror(error
));
4747 _cupsRWLockWrite(&(client
->printer
->rwlock
));
4750 job
->filename
= strdup(filename
);
4751 job
->state
= IPP_JSTATE_PENDING
;
4753 _cupsRWUnlock(&(client
->printer
->rwlock
));
4756 * Process the job...
4762 * Return the job info...
4765 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
4767 ra
= cupsArrayNew((cups_array_func_t
)strcmp
, NULL
);
4768 cupsArrayAdd(ra
, "job-id");
4769 cupsArrayAdd(ra
, "job-state");
4770 cupsArrayAdd(ra
, "job-state-reasons");
4771 cupsArrayAdd(ra
, "job-uri");
4773 copy_job_attributes(client
, job
, ra
);
4774 cupsArrayDelete(ra
);
4779 * 'ipp_validate_job()' - Validate job creation attributes.
4783 ipp_validate_job(_ipp_client_t
*client
) /* I - Client */
4785 if (valid_job_attributes(client
))
4786 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
4791 * 'load_attributes()' - Load printer attributes from a file.
4793 * Syntax is based on ipptool format:
4795 * ATTR value-tag name value
4799 load_attributes(const char *filename
, /* I - File to load */
4800 ipp_t
*attrs
) /* I - Printer attributes */
4802 int linenum
= 0; /* Current line number */
4803 FILE *fp
= NULL
; /* Test file */
4804 char attr
[128], /* Attribute name */
4805 token
[1024], /* Token from file */
4806 *tokenptr
; /* Pointer into token */
4807 ipp_tag_t value
; /* Current value type */
4808 ipp_attribute_t
*attrptr
, /* Attribute pointer */
4809 *lastcol
= NULL
; /* Last collection attribute */
4812 if ((fp
= fopen(filename
, "r")) == NULL
)
4814 fprintf(stderr
, "ippserver: Unable to open \"%s\": %s\n", filename
, strerror(errno
));
4818 while (get_token(fp
, token
, sizeof(token
), &linenum
) != NULL
)
4820 if (!_cups_strcasecmp(token
, "ATTR"))
4826 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
4828 fprintf(stderr
, "ippserver: Missing ATTR value tag on line %d of \"%s\".\n", linenum
, filename
);
4832 if ((value
= ippTagValue(token
)) == IPP_TAG_ZERO
)
4834 fprintf(stderr
, "ippserver: Bad ATTR value tag \"%s\" on line %d of \"%s\".\n", token
, linenum
, filename
);
4838 if (!get_token(fp
, attr
, sizeof(attr
), &linenum
))
4840 fprintf(stderr
, "ippserver: Missing ATTR name on line %d of \"%s\".\n", linenum
, filename
);
4844 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
4846 fprintf(stderr
, "ippserver: Missing ATTR value on line %d of \"%s\".\n", linenum
, filename
);
4854 case IPP_TAG_BOOLEAN
:
4855 if (!_cups_strcasecmp(token
, "true"))
4856 attrptr
= ippAddBoolean(attrs
, IPP_TAG_PRINTER
, attr
, 1);
4858 attrptr
= ippAddBoolean(attrs
, IPP_TAG_PRINTER
, attr
, (char)atoi(token
));
4861 case IPP_TAG_INTEGER
:
4863 if (!strchr(token
, ','))
4864 attrptr
= ippAddInteger(attrs
, IPP_TAG_PRINTER
, value
, attr
, (int)strtol(token
, &tokenptr
, 0));
4867 int values
[100], /* Values */
4868 num_values
= 1; /* Number of values */
4870 values
[0] = (int)strtol(token
, &tokenptr
, 10);
4871 while (tokenptr
&& *tokenptr
&&
4872 num_values
< (int)(sizeof(values
) / sizeof(values
[0])))
4874 if (*tokenptr
== ',')
4876 else if (!isdigit(*tokenptr
& 255) && *tokenptr
!= '-')
4879 values
[num_values
] = (int)strtol(tokenptr
, &tokenptr
, 0);
4883 attrptr
= ippAddIntegers(attrs
, IPP_TAG_PRINTER
, value
, attr
, num_values
, values
);
4886 if (!tokenptr
|| *tokenptr
)
4888 fprintf(stderr
, "ippserver: Bad %s value \"%s\" on line %d of \"%s\".\n", ippTagString(value
), token
, linenum
, filename
);
4893 case IPP_TAG_RESOLUTION
:
4895 int xres
, /* X resolution */
4896 yres
; /* Y resolution */
4897 ipp_res_t units
; /* Units */
4898 char *start
, /* Start of value */
4899 *ptr
, /* Pointer into value */
4900 *next
= NULL
; /* Next value */
4902 for (start
= token
; start
; start
= next
)
4904 xres
= yres
= (int)strtol(start
, (char **)&ptr
, 10);
4905 if (ptr
> start
&& xres
> 0)
4908 yres
= (int)strtol(ptr
+ 1, (char **)&ptr
, 10);
4911 if (ptr
&& (next
= strchr(ptr
, ',')) != NULL
)
4914 if (ptr
<= start
|| xres
<= 0 || yres
<= 0 || !ptr
||
4915 (_cups_strcasecmp(ptr
, "dpi") &&
4916 _cups_strcasecmp(ptr
, "dpc") &&
4917 _cups_strcasecmp(ptr
, "dpcm") &&
4918 _cups_strcasecmp(ptr
, "other")))
4920 fprintf(stderr
, "ippserver: Bad resolution value \"%s\" on line %d of \"%s\".\n", token
, linenum
, filename
);
4924 if (!_cups_strcasecmp(ptr
, "dpc") || !_cups_strcasecmp(ptr
, "dpcm"))
4925 units
= IPP_RES_PER_CM
;
4927 units
= IPP_RES_PER_INCH
;
4930 ippSetResolution(attrs
, &attrptr
, ippGetCount(attrptr
), units
, xres
, yres
);
4932 attrptr
= ippAddResolution(attrs
, IPP_TAG_PRINTER
, attr
, units
, xres
, yres
);
4937 case IPP_TAG_RANGE
:
4939 int lowers
[4], /* Lower value */
4940 uppers
[4], /* Upper values */
4941 num_vals
; /* Number of values */
4944 num_vals
= sscanf(token
, "%d-%d,%d-%d,%d-%d,%d-%d",
4945 lowers
+ 0, uppers
+ 0,
4946 lowers
+ 1, uppers
+ 1,
4947 lowers
+ 2, uppers
+ 2,
4948 lowers
+ 3, uppers
+ 3);
4950 if ((num_vals
& 1) || num_vals
== 0)
4952 fprintf(stderr
, "ippserver: Bad rangeOfInteger value \"%s\" on line %d of \"%s\".", token
, linenum
, filename
);
4956 attrptr
= ippAddRanges(attrs
, IPP_TAG_PRINTER
, attr
, num_vals
/ 2, lowers
,
4961 case IPP_TAG_BEGIN_COLLECTION
:
4962 if (!strcmp(token
, "{"))
4964 ipp_t
*col
= get_collection(fp
, filename
, &linenum
);
4965 /* Collection value */
4969 attrptr
= lastcol
= ippAddCollection(attrs
, IPP_TAG_PRINTER
, attr
, col
);
4977 fprintf(stderr
, "ippserver: Bad ATTR collection value on line %d of \"%s\".\n", linenum
, filename
);
4983 ipp_t
*col
; /* Collection value */
4984 long pos
= ftell(fp
); /* Save position of file */
4986 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
4989 if (strcmp(token
, ","))
4991 fseek(fp
, pos
, SEEK_SET
);
4995 if (!get_token(fp
, token
, sizeof(token
), &linenum
) || strcmp(token
, "{"))
4997 fprintf(stderr
, "ippserver: Unexpected \"%s\" on line %d of \"%s\".\n", token
, linenum
, filename
);
5001 if ((col
= get_collection(fp
, filename
, &linenum
)) == NULL
)
5004 ippSetCollection(attrs
, &attrptr
, ippGetCount(attrptr
), col
);
5006 while (!strcmp(token
, "{"));
5009 case IPP_TAG_STRING
:
5010 attrptr
= ippAddOctetString(attrs
, IPP_TAG_PRINTER
, attr
, token
, (int)strlen(token
));
5014 fprintf(stderr
, "ippserver: Unsupported ATTR value tag %s on line %d of \"%s\".\n", ippTagString(value
), linenum
, filename
);
5017 case IPP_TAG_TEXTLANG
:
5018 case IPP_TAG_NAMELANG
:
5021 case IPP_TAG_KEYWORD
:
5023 case IPP_TAG_URISCHEME
:
5024 case IPP_TAG_CHARSET
:
5025 case IPP_TAG_LANGUAGE
:
5026 case IPP_TAG_MIMETYPE
:
5027 if (!strchr(token
, ','))
5028 attrptr
= ippAddString(attrs
, IPP_TAG_PRINTER
, value
, attr
, NULL
, token
);
5032 * Multiple string values...
5035 int num_values
; /* Number of values */
5036 char *values
[100], /* Values */
5037 *ptr
; /* Pointer to next value */
5043 for (ptr
= strchr(token
, ','); ptr
; ptr
= strchr(ptr
, ','))
5045 if (ptr
> token
&& ptr
[-1] == '\\')
5046 _cups_strcpy(ptr
- 1, ptr
);
5050 values
[num_values
] = ptr
;
5052 if (num_values
>= (int)(sizeof(values
) / sizeof(values
[0])))
5057 attrptr
= ippAddStrings(attrs
, IPP_TAG_PRINTER
, value
, attr
, num_values
, NULL
, (const char **)values
);
5064 fprintf(stderr
, "ippserver: Unable to add attribute on line %d of \"%s\": %s\n", linenum
, filename
, cupsLastErrorString());
5070 fprintf(stderr
, "ippserver: Unknown directive \"%s\" on line %d of \"%s\".\n", token
, linenum
, filename
);
5080 * 'parse_options()' - Parse URL options into CUPS options.
5082 * The client->options string is destroyed by this function.
5085 static int /* O - Number of options */
5086 parse_options(_ipp_client_t
*client
, /* I - Client */
5087 cups_option_t
**options
) /* O - Options */
5089 char *name
, /* Name */
5091 *next
; /* Next name=value pair */
5092 int num_options
= 0; /* Number of options */
5097 for (name
= client
->options
; name
&& *name
; name
= next
)
5099 if ((value
= strchr(name
, '=')) == NULL
)
5103 if ((next
= strchr(value
, '&')) != NULL
)
5106 num_options
= cupsAddOption(name
, value
, num_options
, options
);
5109 return (num_options
);
5114 * 'process_attr_message()' - Process an ATTR: message from a command.
5118 process_attr_message(
5119 _ipp_job_t
*job
, /* I - Job */
5120 char *message
) /* I - Message */
5128 * 'process_client()' - Process client requests on a thread.
5131 static void * /* O - Exit status */
5132 process_client(_ipp_client_t
*client
) /* I - Client */
5135 * Loop until we are out of requests or timeout (30 seconds)...
5139 int first_time
= 1; /* First time request? */
5140 #endif /* HAVE_SSL */
5142 while (httpWait(client
->http
, 30000))
5148 * See if we need to negotiate a TLS connection...
5151 char buf
[1]; /* First byte from client */
5153 if (recv(httpGetFd(client
->http
), buf
, 1, MSG_PEEK
) == 1 && (!buf
[0] || !strchr("DGHOPT", buf
[0])))
5155 fprintf(stderr
, "%s Starting HTTPS session.\n", client
->hostname
);
5157 if (httpEncryption(client
->http
, HTTP_ENCRYPTION_ALWAYS
))
5159 fprintf(stderr
, "%s Unable to encrypt connection: %s\n", client
->hostname
, cupsLastErrorString());
5163 fprintf(stderr
, "%s Connection now encrypted.\n", client
->hostname
);
5168 #endif /* HAVE_SSL */
5170 if (!process_http(client
))
5175 * Close the conection to the client and return...
5178 delete_client(client
);
5185 * 'process_http()' - Process a HTTP request.
5188 int /* O - 1 on success, 0 on failure */
5189 process_http(_ipp_client_t
*client
) /* I - Client connection */
5191 char uri
[1024]; /* URI */
5192 http_state_t http_state
; /* HTTP state */
5193 http_status_t http_status
; /* HTTP status */
5194 ipp_state_t ipp_state
; /* State of IPP transfer */
5195 char scheme
[32], /* Method/scheme */
5196 userpass
[128], /* Username:password */
5197 hostname
[HTTP_MAX_HOST
];
5199 int port
; /* Port number */
5200 const char *encoding
; /* Content-Encoding value */
5201 static const char * const http_states
[] =
5202 { /* Strings for logging HTTP method */
5223 * Clear state variables...
5226 ippDelete(client
->request
);
5227 ippDelete(client
->response
);
5229 client
->request
= NULL
;
5230 client
->response
= NULL
;
5231 client
->operation
= HTTP_STATE_WAITING
;
5234 * Read a request from the connection...
5237 while ((http_state
= httpReadRequest(client
->http
, uri
,
5238 sizeof(uri
))) == HTTP_STATE_WAITING
)
5242 * Parse the request line...
5245 if (http_state
== HTTP_STATE_ERROR
)
5247 if (httpError(client
->http
) == EPIPE
)
5248 fprintf(stderr
, "%s Client closed connection.\n", client
->hostname
);
5250 fprintf(stderr
, "%s Bad request line (%s).\n", client
->hostname
,
5251 strerror(httpError(client
->http
)));
5255 else if (http_state
== HTTP_STATE_UNKNOWN_METHOD
)
5257 fprintf(stderr
, "%s Bad/unknown operation.\n", client
->hostname
);
5258 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5261 else if (http_state
== HTTP_STATE_UNKNOWN_VERSION
)
5263 fprintf(stderr
, "%s Bad HTTP version.\n", client
->hostname
);
5264 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5268 fprintf(stderr
, "%s %s %s\n", client
->hostname
, http_states
[http_state
],
5272 * Separate the URI into its components...
5275 if (httpSeparateURI(HTTP_URI_CODING_MOST
, uri
, scheme
, sizeof(scheme
),
5276 userpass
, sizeof(userpass
),
5277 hostname
, sizeof(hostname
), &port
,
5278 client
->uri
, sizeof(client
->uri
)) < HTTP_URI_STATUS_OK
&&
5279 (http_state
!= HTTP_STATE_OPTIONS
|| strcmp(uri
, "*")))
5281 fprintf(stderr
, "%s Bad URI \"%s\".\n", client
->hostname
, uri
);
5282 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5286 if ((client
->options
= strchr(client
->uri
, '?')) != NULL
)
5287 *(client
->options
)++ = '\0';
5290 * Process the request...
5293 client
->start
= time(NULL
);
5294 client
->operation
= httpGetState(client
->http
);
5297 * Parse incoming parameters until the status changes...
5300 while ((http_status
= httpUpdate(client
->http
)) == HTTP_STATUS_CONTINUE
);
5302 if (http_status
!= HTTP_STATUS_OK
)
5304 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5308 if (!httpGetField(client
->http
, HTTP_FIELD_HOST
)[0] &&
5309 httpGetVersion(client
->http
) >= HTTP_VERSION_1_1
)
5312 * HTTP/1.1 and higher require the "Host:" field...
5315 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5320 * Handle HTTP Upgrade...
5323 if (!strcasecmp(httpGetField(client
->http
, HTTP_FIELD_CONNECTION
),
5327 if (strstr(httpGetField(client
->http
, HTTP_FIELD_UPGRADE
), "TLS/") != NULL
&& !httpIsEncrypted(client
->http
))
5329 if (!respond_http(client
, HTTP_STATUS_SWITCHING_PROTOCOLS
, NULL
, NULL
, 0))
5332 fprintf(stderr
, "%s Upgrading to encrypted connection.\n", client
->hostname
);
5334 if (httpEncryption(client
->http
, HTTP_ENCRYPTION_REQUIRED
))
5336 fprintf(stderr
, "%s Unable to encrypt connection: %s\n", client
->hostname
, cupsLastErrorString());
5340 fprintf(stderr
, "%s Connection now encrypted.\n", client
->hostname
);
5343 #endif /* HAVE_SSL */
5345 if (!respond_http(client
, HTTP_STATUS_NOT_IMPLEMENTED
, NULL
, NULL
, 0))
5350 * Handle HTTP Expect...
5353 if (httpGetExpect(client
->http
) &&
5354 (client
->operation
== HTTP_STATE_POST
||
5355 client
->operation
== HTTP_STATE_PUT
))
5357 if (httpGetExpect(client
->http
) == HTTP_STATUS_CONTINUE
)
5360 * Send 100-continue header...
5363 if (!respond_http(client
, HTTP_STATUS_CONTINUE
, NULL
, NULL
, 0))
5369 * Send 417-expectation-failed header...
5372 if (!respond_http(client
, HTTP_STATUS_EXPECTATION_FAILED
, NULL
, NULL
, 0))
5378 * Handle new transfers...
5381 encoding
= httpGetContentEncoding(client
->http
);
5383 switch (client
->operation
)
5385 case HTTP_STATE_OPTIONS
:
5387 * Do OPTIONS command...
5390 return (respond_http(client
, HTTP_STATUS_OK
, NULL
, NULL
, 0));
5392 case HTTP_STATE_HEAD
:
5393 if (!strcmp(client
->uri
, "/icon.png"))
5394 return (respond_http(client
, HTTP_STATUS_OK
, NULL
, "image/png", 0));
5395 else if (!strcmp(client
->uri
, "/") || !strcmp(client
->uri
, "/media") || !strcmp(client
->uri
, "/supplies"))
5396 return (respond_http(client
, HTTP_STATUS_OK
, NULL
, "text/html", 0));
5398 return (respond_http(client
, HTTP_STATUS_NOT_FOUND
, NULL
, NULL
, 0));
5400 case HTTP_STATE_GET
:
5401 if (!strcmp(client
->uri
, "/icon.png"))
5404 * Send PNG icon file.
5407 int fd
; /* Icon file */
5408 struct stat fileinfo
; /* Icon file information */
5409 char buffer
[4096]; /* Copy buffer */
5410 ssize_t bytes
; /* Bytes */
5412 fprintf(stderr
, "Icon file is \"%s\".\n", client
->printer
->icon
);
5414 if (!stat(client
->printer
->icon
, &fileinfo
) &&
5415 (fd
= open(client
->printer
->icon
, O_RDONLY
)) >= 0)
5417 if (!respond_http(client
, HTTP_STATUS_OK
, NULL
, "image/png",
5418 (size_t)fileinfo
.st_size
))
5424 while ((bytes
= read(fd
, buffer
, sizeof(buffer
))) > 0)
5425 httpWrite2(client
->http
, buffer
, (size_t)bytes
);
5427 httpFlushWrite(client
->http
);
5432 return (respond_http(client
, HTTP_STATUS_NOT_FOUND
, NULL
, NULL
, 0));
5434 else if (!strcmp(client
->uri
, "/"))
5437 * Show web status page...
5440 _ipp_job_t
*job
; /* Current job */
5441 int i
; /* Looping var */
5442 _ipp_preason_t reason
; /* Current reason */
5443 static const char * const reasons
[] =
5444 { /* Reason strings */
5447 "Input Tray Missing",
5448 "Marker Supply Empty",
5449 "Marker Supply Low",
5450 "Marker Waste Almost Full",
5451 "Marker Waste Full",
5463 if (!respond_http(client
, HTTP_STATUS_OK
, encoding
, "text/html", 0))
5466 html_header(client
, client
->printer
->name
);
5468 "<p><img align=\"right\" src=\"/icon.png\" width=\"64\" height=\"64\"><b>ippserver (" CUPS_SVERSION
")</b></p>\n"
5469 "<p>%s, %d job(s).", client
->printer
->state
== IPP_PSTATE_IDLE
? "Idle" : client
->printer
->state
== IPP_PSTATE_PROCESSING
? "Printing" : "Stopped", cupsArrayCount(client
->printer
->jobs
));
5470 for (i
= 0, reason
= 1; i
< (int)(sizeof(reasons
) / sizeof(reasons
[0])); i
++, reason
<<= 1)
5471 if (client
->printer
->state_reasons
& reason
)
5472 html_printf(client
, "\n<br> %s", reasons
[i
]);
5473 html_printf(client
, "</p>\n");
5475 if (cupsArrayCount(client
->printer
->jobs
) > 0)
5477 _cupsRWLockRead(&(client
->printer
->rwlock
));
5479 html_printf(client
, "<table class=\"striped\" summary=\"Jobs\"><thead><tr><th>Job #</th><th>Name</th><th>Owner</th><th>When</th></tr></thead><tbody>\n");
5480 for (job
= (_ipp_job_t
*)cupsArrayFirst(client
->printer
->jobs
); job
; job
= (_ipp_job_t
*)cupsArrayNext(client
->printer
->jobs
))
5482 char when
[256], /* When job queued/started/finished */
5483 hhmmss
[64]; /* Time HH:MM:SS */
5487 case IPP_JSTATE_PENDING
:
5488 case IPP_JSTATE_HELD
:
5489 snprintf(when
, sizeof(when
), "Queued at %s", time_string(job
->created
, hhmmss
, sizeof(hhmmss
)));
5491 case IPP_JSTATE_PROCESSING
:
5492 case IPP_JSTATE_STOPPED
:
5493 snprintf(when
, sizeof(when
), "Started at %s", time_string(job
->processing
, hhmmss
, sizeof(hhmmss
)));
5495 case IPP_JSTATE_ABORTED
:
5496 snprintf(when
, sizeof(when
), "Aborted at %s", time_string(job
->completed
, hhmmss
, sizeof(hhmmss
)));
5498 case IPP_JSTATE_CANCELED
:
5499 snprintf(when
, sizeof(when
), "Canceled at %s", time_string(job
->completed
, hhmmss
, sizeof(hhmmss
)));
5501 case IPP_JSTATE_COMPLETED
:
5502 snprintf(when
, sizeof(when
), "Completed at %s", time_string(job
->completed
, hhmmss
, sizeof(hhmmss
)));
5506 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
);
5508 html_printf(client
, "</tbody></table>\n");
5510 _cupsRWUnlock(&(client
->printer
->rwlock
));
5512 html_footer(client
);
5516 else if (!strcmp(client
->uri
, "/media"))
5519 * Show web media page...
5522 int i
, /* Looping var */
5523 num_options
; /* Number of form options */
5524 cups_option_t
*options
; /* Form options */
5525 static const char * const sizes
[] =
5526 { /* Size strings */
5539 static const char * const types
[] =
5556 static const int sheets
[] = /* Number of sheets */
5565 if (!respond_http(client
, HTTP_STATUS_OK
, encoding
, "text/html", 0))
5568 html_header(client
, client
->printer
->name
);
5570 if ((num_options
= parse_options(client
, &options
)) > 0)
5573 * WARNING: A real printer/server implementation MUST NOT implement
5574 * media updates via a GET request - GET requests are supposed to be
5575 * idempotent (without side-effects) and we obviously are not
5576 * authenticating access here. This form is provided solely to
5577 * enable testing and development!
5580 const char *val
; /* Form value */
5582 if ((val
= cupsGetOption("main_size", num_options
, options
)) != NULL
)
5583 client
->printer
->main_size
= atoi(val
);
5584 if ((val
= cupsGetOption("main_type", num_options
, options
)) != NULL
)
5585 client
->printer
->main_type
= atoi(val
);
5586 if ((val
= cupsGetOption("main_level", num_options
, options
)) != NULL
)
5587 client
->printer
->main_level
= atoi(val
);
5589 if ((val
= cupsGetOption("envelope_size", num_options
, options
)) != NULL
)
5590 client
->printer
->envelope_size
= atoi(val
);
5591 if ((val
= cupsGetOption("envelope_level", num_options
, options
)) != NULL
)
5592 client
->printer
->envelope_level
= atoi(val
);
5594 if ((val
= cupsGetOption("photo_size", num_options
, options
)) != NULL
)
5595 client
->printer
->photo_size
= atoi(val
);
5596 if ((val
= cupsGetOption("photo_type", num_options
, options
)) != NULL
)
5597 client
->printer
->photo_type
= atoi(val
);
5598 if ((val
= cupsGetOption("photo_level", num_options
, options
)) != NULL
)
5599 client
->printer
->photo_level
= atoi(val
);
5601 if ((client
->printer
->main_level
< 100 && client
->printer
->main_level
> 0) || (client
->printer
->envelope_level
< 25 && client
->printer
->envelope_level
> 0) || (client
->printer
->photo_level
< 25 && client
->printer
->photo_level
> 0))
5602 client
->printer
->state_reasons
|= _IPP_PREASON_MEDIA_LOW
;
5604 client
->printer
->state_reasons
&= (_ipp_preason_t
)~_IPP_PREASON_MEDIA_LOW
;
5606 if ((client
->printer
->main_level
== 0 && client
->printer
->main_size
> _IPP_MEDIA_SIZE_NONE
) || (client
->printer
->envelope_level
== 0 && client
->printer
->envelope_size
> _IPP_MEDIA_SIZE_NONE
) || (client
->printer
->photo_level
== 0 && client
->printer
->photo_size
> _IPP_MEDIA_SIZE_NONE
))
5608 client
->printer
->state_reasons
|= _IPP_PREASON_MEDIA_EMPTY
;
5609 if (client
->printer
->active_job
)
5610 client
->printer
->state_reasons
|= _IPP_PREASON_MEDIA_NEEDED
;
5613 client
->printer
->state_reasons
&= (_ipp_preason_t
)~(_IPP_PREASON_MEDIA_EMPTY
| _IPP_PREASON_MEDIA_NEEDED
);
5615 html_printf(client
, "<blockquote>Media updated.</blockquote>\n");
5618 html_printf(client
, "<form method=\"GET\" action=\"/media\">\n");
5620 html_printf(client
, "<table class=\"form\" summary=\"Media\">\n");
5621 html_printf(client
, "<tr><th>Main Tray:</th><td><select name=\"main_size\"><option value=\"-1\">None</option>");
5622 for (i
= 0; i
< (int)(sizeof(sizes
) / sizeof(sizes
[0])); i
++)
5623 if (!strstr(sizes
[i
], "Envelope") && !strstr(sizes
[i
], "Photo"))
5624 html_printf(client
, "<option value=\"%d\"%s>%s</option>", i
, i
== client
->printer
->main_size
? " selected" : "", sizes
[i
]);
5625 html_printf(client
, "</select> <select name=\"main_type\"><option value=\"-1\">None</option>");
5626 for (i
= 0; i
< (int)(sizeof(types
) / sizeof(types
[0])); i
++)
5627 if (!strstr(types
[i
], "Photo"))
5628 html_printf(client
, "<option value=\"%d\"%s>%s</option>", i
, i
== client
->printer
->main_type
? " selected" : "", types
[i
]);
5629 html_printf(client
, "</select> <select name=\"main_level\">");
5630 for (i
= 0; i
< (int)(sizeof(sheets
) / sizeof(sheets
[0])); i
++)
5631 html_printf(client
, "<option value=\"%d\"%s>%d sheets</option>", sheets
[i
], sheets
[i
] == client
->printer
->main_level
? " selected" : "", sheets
[i
]);
5632 html_printf(client
, "</select></td></tr>\n");
5635 "<tr><th>Envelope Feeder:</th><td><select name=\"envelope_size\"><option value=\"-1\">None</option>");
5636 for (i
= 0; i
< (int)(sizeof(sizes
) / sizeof(sizes
[0])); i
++)
5637 if (strstr(sizes
[i
], "Envelope"))
5638 html_printf(client
, "<option value=\"%d\"%s>%s</option>", i
, i
== client
->printer
->envelope_size
? " selected" : "", sizes
[i
]);
5639 html_printf(client
, "</select> <select name=\"envelope_level\">");
5640 for (i
= 0; i
< (int)(sizeof(sheets
) / sizeof(sheets
[0])); i
++)
5641 html_printf(client
, "<option value=\"%d\"%s>%d sheets</option>", sheets
[i
], sheets
[i
] == client
->printer
->envelope_level
? " selected" : "", sheets
[i
]);
5642 html_printf(client
, "</select></td></tr>\n");
5645 "<tr><th>Photo Tray:</th><td><select name=\"photo_size\"><option value=\"-1\">None</option>");
5646 for (i
= 0; i
< (int)(sizeof(sizes
) / sizeof(sizes
[0])); i
++)
5647 if (strstr(sizes
[i
], "Photo"))
5648 html_printf(client
, "<option value=\"%d\"%s>%s</option>", i
, i
== client
->printer
->photo_size
? " selected" : "", sizes
[i
]);
5649 html_printf(client
, "</select> <select name=\"photo_type\"><option value=\"-1\">None</option>");
5650 for (i
= 0; i
< (int)(sizeof(types
) / sizeof(types
[0])); i
++)
5651 if (strstr(types
[i
], "Photo"))
5652 html_printf(client
, "<option value=\"%d\"%s>%s</option>", i
, i
== client
->printer
->photo_type
? " selected" : "", types
[i
]);
5653 html_printf(client
, "</select> <select name=\"photo_level\">");
5654 for (i
= 0; i
< (int)(sizeof(sheets
) / sizeof(sheets
[0])); i
++)
5655 html_printf(client
, "<option value=\"%d\"%s>%d sheets</option>", sheets
[i
], sheets
[i
] == client
->printer
->photo_level
? " selected" : "", sheets
[i
]);
5656 html_printf(client
, "</select></td></tr>\n");
5658 html_printf(client
, "<tr><td></td><td><input type=\"submit\" value=\"Update Media\"></td></tr></table></form>\n");
5659 html_footer(client
);
5663 else if (!strcmp(client
->uri
, "/supplies"))
5666 * Show web supplies page...
5669 int i
, j
, /* Looping vars */
5670 num_options
; /* Number of form options */
5671 cups_option_t
*options
; /* Form options */
5672 static const int levels
[] = { 0, 5, 10, 25, 50, 75, 90, 95, 100 };
5674 if (!respond_http(client
, HTTP_STATUS_OK
, encoding
, "text/html", 0))
5677 html_header(client
, client
->printer
->name
);
5679 if ((num_options
= parse_options(client
, &options
)) > 0)
5682 * WARNING: A real printer/server implementation MUST NOT implement
5683 * supply updates via a GET request - GET requests are supposed to be
5684 * idempotent (without side-effects) and we obviously are not
5685 * authenticating access here. This form is provided solely to
5686 * enable testing and development!
5689 char name
[64]; /* Form field */
5690 const char *val
; /* Form value */
5692 client
->printer
->state_reasons
&= (_ipp_preason_t
)~(_IPP_PREASON_MARKER_SUPPLY_EMPTY
| _IPP_PREASON_MARKER_SUPPLY_LOW
| _IPP_PREASON_MARKER_WASTE_ALMOST_FULL
| _IPP_PREASON_MARKER_WASTE_FULL
| _IPP_PREASON_TONER_EMPTY
| _IPP_PREASON_TONER_LOW
);
5694 for (i
= 0; i
< (int)(sizeof(printer_supplies
) / sizeof(printer_supplies
[0])); i
++)
5696 snprintf(name
, sizeof(name
), "supply_%d", i
);
5697 if ((val
= cupsGetOption(name
, num_options
, options
)) != NULL
)
5699 int level
= client
->printer
->supplies
[i
] = atoi(val
);
5705 client
->printer
->state_reasons
|= _IPP_PREASON_TONER_EMPTY
;
5706 else if (level
< 10)
5707 client
->printer
->state_reasons
|= _IPP_PREASON_TONER_LOW
;
5712 client
->printer
->state_reasons
|= _IPP_PREASON_MARKER_WASTE_FULL
;
5713 else if (level
> 90)
5714 client
->printer
->state_reasons
|= _IPP_PREASON_MARKER_WASTE_ALMOST_FULL
;
5719 html_printf(client
, "<blockquote>Supplies updated.</blockquote>\n");
5722 html_printf(client
, "<form method=\"GET\" action=\"/supplies\">\n");
5724 html_printf(client
, "<table class=\"form\" summary=\"Supplies\">\n");
5725 for (i
= 0; i
< (int)(sizeof(printer_supplies
) / sizeof(printer_supplies
[0])); i
++)
5727 html_printf(client
, "<tr><th>%s:</th><td><select name=\"supply_%d\">", printer_supplies
[i
], i
);
5728 for (j
= 0; j
< (int)(sizeof(levels
) / sizeof(levels
[0])); j
++)
5729 html_printf(client
, "<option value=\"%d\"%s>%d%%</option>", levels
[j
], levels
[j
] == client
->printer
->supplies
[i
] ? " selected" : "", levels
[j
]);
5730 html_printf(client
, "</select></td></tr>\n");
5732 html_printf(client
, "<tr><td></td><td><input type=\"submit\" value=\"Update Supplies\"></td></tr>\n</table>\n</form>\n");
5733 html_footer(client
);
5738 return (respond_http(client
, HTTP_STATUS_NOT_FOUND
, NULL
, NULL
, 0));
5741 case HTTP_STATE_POST
:
5742 if (strcmp(httpGetField(client
->http
, HTTP_FIELD_CONTENT_TYPE
),
5746 * Not an IPP request...
5749 return (respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0));
5753 * Read the IPP request...
5756 client
->request
= ippNew();
5758 while ((ipp_state
= ippRead(client
->http
,
5759 client
->request
)) != IPP_STATE_DATA
)
5761 if (ipp_state
== IPP_STATE_ERROR
)
5763 fprintf(stderr
, "%s IPP read error (%s).\n", client
->hostname
,
5764 cupsLastErrorString());
5765 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5771 * Now that we have the IPP request, process the request...
5774 return (process_ipp(client
));
5777 break; /* Anti-compiler-warning-code */
5785 * 'process_ipp()' - Process an IPP request.
5788 static int /* O - 1 on success, 0 on error */
5789 process_ipp(_ipp_client_t
*client
) /* I - Client */
5791 ipp_tag_t group
; /* Current group tag */
5792 ipp_attribute_t
*attr
; /* Current attribute */
5793 ipp_attribute_t
*charset
; /* Character set attribute */
5794 ipp_attribute_t
*language
; /* Language attribute */
5795 ipp_attribute_t
*uri
; /* Printer URI attribute */
5796 int major
, minor
; /* Version number */
5797 const char *name
; /* Name of attribute */
5800 debug_attributes("Request", client
->request
, 1);
5803 * First build an empty response message for this request...
5806 client
->operation_id
= ippGetOperation(client
->request
);
5807 client
->response
= ippNewResponse(client
->request
);
5810 * Then validate the request header and required attributes...
5813 major
= ippGetVersion(client
->request
, &minor
);
5815 if (major
< 1 || major
> 2)
5818 * Return an error, since we only support IPP 1.x and 2.x.
5821 respond_ipp(client
, IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED
, "Bad request version number %d.%d.", major
, minor
);
5823 else if ((major
* 10 + minor
) > MaxVersion
)
5825 if (httpGetState(client
->http
) != HTTP_STATE_POST_SEND
)
5826 httpFlush(client
->http
); /* Flush trailing (junk) data */
5828 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5831 else if (ippGetRequestId(client
->request
) <= 0)
5833 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "Bad request-id %d.", ippGetRequestId(client
->request
));
5835 else if (!ippFirstAttribute(client
->request
))
5837 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "No attributes in request.");
5842 * Make sure that the attributes are provided in the correct order and
5843 * don't repeat groups...
5846 for (attr
= ippFirstAttribute(client
->request
),
5847 group
= ippGetGroupTag(attr
);
5849 attr
= ippNextAttribute(client
->request
))
5851 if (ippGetGroupTag(attr
) < group
&& ippGetGroupTag(attr
) != IPP_TAG_ZERO
)
5854 * Out of order; return an error...
5857 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
5858 "Attribute groups are out of order (%x < %x).",
5859 ippGetGroupTag(attr
), group
);
5863 group
= ippGetGroupTag(attr
);
5869 * Then make sure that the first three attributes are:
5871 * attributes-charset
5872 * attributes-natural-language
5873 * printer-uri/job-uri
5876 attr
= ippFirstAttribute(client
->request
);
5877 name
= ippGetName(attr
);
5878 if (attr
&& name
&& !strcmp(name
, "attributes-charset") &&
5879 ippGetValueTag(attr
) == IPP_TAG_CHARSET
)
5884 attr
= ippNextAttribute(client
->request
);
5885 name
= ippGetName(attr
);
5887 if (attr
&& name
&& !strcmp(name
, "attributes-natural-language") &&
5888 ippGetValueTag(attr
) == IPP_TAG_LANGUAGE
)
5893 if ((attr
= ippFindAttribute(client
->request
, "printer-uri",
5894 IPP_TAG_URI
)) != NULL
)
5896 else if ((attr
= ippFindAttribute(client
->request
, "job-uri",
5897 IPP_TAG_URI
)) != NULL
)
5903 strcasecmp(ippGetString(charset
, 0, NULL
), "us-ascii") &&
5904 strcasecmp(ippGetString(charset
, 0, NULL
), "utf-8"))
5907 * Bad character set...
5910 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
5911 "Unsupported character set \"%s\".",
5912 ippGetString(charset
, 0, NULL
));
5914 else if (!charset
|| !language
|| !uri
)
5917 * Return an error, since attributes-charset,
5918 * attributes-natural-language, and printer-uri/job-uri are required
5919 * for all operations.
5922 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
5923 "Missing required attributes.");
5927 char scheme
[32], /* URI scheme */
5928 userpass
[32], /* Username/password in URI */
5929 host
[256], /* Host name in URI */
5930 resource
[256]; /* Resource path in URI */
5931 int port
; /* Port number in URI */
5933 name
= ippGetName(uri
);
5935 if (httpSeparateURI(HTTP_URI_CODING_ALL
, ippGetString(uri
, 0, NULL
),
5936 scheme
, sizeof(scheme
),
5937 userpass
, sizeof(userpass
),
5938 host
, sizeof(host
), &port
,
5939 resource
, sizeof(resource
)) < HTTP_URI_STATUS_OK
)
5940 respond_ipp(client
, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES
,
5941 "Bad %s value '%s'.", name
, ippGetString(uri
, 0, NULL
));
5942 else if ((!strcmp(name
, "job-uri") &&
5943 strncmp(resource
, "/ipp/print/", 11)) ||
5944 (!strcmp(name
, "printer-uri") &&
5945 strcmp(resource
, "/ipp/print")))
5946 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "%s %s not found.",
5947 name
, ippGetString(uri
, 0, NULL
));
5951 * Try processing the operation...
5954 switch (ippGetOperation(client
->request
))
5956 case IPP_OP_PRINT_JOB
:
5957 ipp_print_job(client
);
5960 case IPP_OP_PRINT_URI
:
5961 ipp_print_uri(client
);
5964 case IPP_OP_VALIDATE_JOB
:
5965 ipp_validate_job(client
);
5968 case IPP_OP_CREATE_JOB
:
5969 ipp_create_job(client
);
5972 case IPP_OP_SEND_DOCUMENT
:
5973 ipp_send_document(client
);
5976 case IPP_OP_SEND_URI
:
5977 ipp_send_uri(client
);
5980 case IPP_OP_CANCEL_JOB
:
5981 ipp_cancel_job(client
);
5984 case IPP_OP_GET_JOB_ATTRIBUTES
:
5985 ipp_get_job_attributes(client
);
5988 case IPP_OP_GET_JOBS
:
5989 ipp_get_jobs(client
);
5992 case IPP_OP_GET_PRINTER_ATTRIBUTES
:
5993 ipp_get_printer_attributes(client
);
5996 case IPP_OP_CLOSE_JOB
:
5997 ipp_close_job(client
);
6000 case IPP_OP_IDENTIFY_PRINTER
:
6001 ipp_identify_printer(client
);
6005 respond_ipp(client
, IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED
,
6006 "Operation not supported.");
6015 * Send the HTTP header and return...
6018 if (httpGetState(client
->http
) != HTTP_STATE_POST_SEND
)
6019 httpFlush(client
->http
); /* Flush trailing (junk) data */
6021 return (respond_http(client
, HTTP_STATUS_OK
, NULL
, "application/ipp",
6022 ippLength(client
->response
)));
6027 * 'process_job()' - Process a print job.
6030 static void * /* O - Thread exit status */
6031 process_job(_ipp_job_t
*job
) /* I - Job */
6033 job
->state
= IPP_JSTATE_PROCESSING
;
6034 job
->printer
->state
= IPP_PSTATE_PROCESSING
;
6035 job
->processing
= time(NULL
);
6037 while (job
->printer
->state_reasons
& _IPP_PREASON_MEDIA_EMPTY
)
6039 job
->printer
->state_reasons
|= _IPP_PREASON_MEDIA_NEEDED
;
6044 job
->printer
->state_reasons
&= (_ipp_preason_t
)~_IPP_PREASON_MEDIA_NEEDED
;
6046 if (job
->printer
->command
)
6049 * Execute a command with the job spool file and wait for it to complete...
6052 int pid
, /* Process ID */
6053 status
; /* Exit status */
6054 time_t start
, /* Start time */
6056 char *myargv
[3], /* Command-line arguments */
6057 *myenvp
[200]; /* Environment variables */
6058 int myenvc
; /* Number of environment variables */
6059 ipp_attribute_t
*attr
; /* Job attribute */
6060 char val
[1280], /* IPP_NAME=value */
6061 *valptr
; /* Pointer into string */
6063 int mypipe
[2]; /* Pipe for stderr */
6064 char line
[2048], /* Line from stderr */
6065 *ptr
, /* Pointer into line */
6066 *endptr
; /* End of line */
6067 ssize_t bytes
; /* Bytes read */
6070 fprintf(stderr
, "Running command \"%s %s\".\n", job
->printer
->command
,
6075 * Setup the command-line arguments...
6078 myargv
[0] = job
->printer
->command
;
6079 myargv
[1] = job
->filename
;
6083 * Copy the current environment, then add ENV variables for every Job
6087 for (myenvc
= 0; environ
[myenvc
] && myenvc
< (int)(sizeof(myenvp
) / sizeof(myenvp
[0]) - 1); myenvc
++)
6088 myenvp
[myenvc
] = strdup(environ
[myenvc
]);
6090 for (attr
= ippFirstAttribute(job
->attrs
); attr
&& myenvc
< (int)(sizeof(myenvp
) / sizeof(myenvp
[0]) - 1); attr
= ippNextAttribute(job
->attrs
))
6093 * Convert "attribute-name" to "IPP_ATTRIBUTE_NAME=" and then add the
6094 * value(s) from the attribute.
6097 const char *name
= ippGetName(attr
);
6106 while (*name
&& valptr
< (val
+ sizeof(val
) - 2))
6111 *valptr
++ = (char)toupper(*name
& 255);
6116 ippAttributeString(attr
, valptr
, sizeof(val
) - (size_t)(valptr
- val
));
6118 myenvp
[myenvc
++] = strdup(val
);
6120 myenvp
[myenvc
] = NULL
;
6123 * Now run the program...
6127 status
= _spawnvpe(_P_WAIT
, job
->printer
->command
, myargv
, myenvp
);
6132 perror("Unable to create pipe for stderr");
6133 mypipe
[0] = mypipe
[1] = -1;
6136 if ((pid
= fork()) == 0)
6139 * Child comes here...
6147 execve(job
->printer
->command
, myargv
, myenvp
);
6153 * Unable to fork process...
6156 perror("Unable to start job processing command");
6163 * Free memory used for environment...
6167 free(myenvp
[-- myenvc
]);
6172 * Free memory used for environment...
6176 free(myenvp
[-- myenvc
]);
6179 * If the pipe exists, read from it until EOF...
6187 while ((bytes
= read(mypipe
[0], endptr
, sizeof(line
) - (size_t)(endptr
- line
) - 1)) > 0)
6192 while ((ptr
= strchr(line
, '\n')) != NULL
)
6196 if (!strncmp(line
, "STATE:", 6))
6199 * Process printer-state-reasons keywords.
6202 process_state_message(job
, line
);
6204 else if (!strncmp(line
, "ATTR:", 5))
6207 * Process printer attribute update.
6210 process_attr_message(job
, line
);
6212 else if (Verbosity
> 1)
6213 fprintf(stderr
, "%s: %s\n", job
->printer
->command
, line
);
6217 memmove(line
, ptr
, (size_t)(endptr
- ptr
));
6227 * Wait for child to complete...
6230 # ifdef HAVE_WAITPID
6231 while (waitpid(pid
, &status
, 0) < 0);
6233 while (wait(&status
) < 0);
6234 # endif /* HAVE_WAITPID */
6241 if (WIFEXITED(status
))
6243 fprintf(stderr
, "Command \"%s\" exited with status %d.\n",
6244 job
->printer
->command
, WEXITSTATUS(status
));
6247 fprintf(stderr
, "Command \"%s\" terminated with signal %d.\n",
6248 job
->printer
->command
, WTERMSIG(status
));
6250 job
->state
= IPP_JSTATE_ABORTED
;
6252 else if (status
< 0)
6253 job
->state
= IPP_JSTATE_ABORTED
;
6255 fprintf(stderr
, "Command \"%s\" completed successfully.\n",
6256 job
->printer
->command
);
6259 * Make sure processing takes at least 5 seconds...
6263 if ((end
- start
) < 5)
6269 * Sleep for a random amount of time to simulate job processing.
6272 sleep((unsigned)(5 + (rand() % 11)));
6276 job
->state
= IPP_JSTATE_CANCELED
;
6277 else if (job
->state
== IPP_JSTATE_PROCESSING
)
6278 job
->state
= IPP_JSTATE_COMPLETED
;
6280 job
->completed
= time(NULL
);
6281 job
->printer
->state
= IPP_PSTATE_IDLE
;
6282 job
->printer
->active_job
= NULL
;
6289 * 'process_state_message()' - Process a STATE: message from a command.
6293 process_state_message(
6294 _ipp_job_t
*job
, /* I - Job */
6295 char *message
) /* I - Message */
6297 int i
; /* Looping var */
6298 _ipp_preason_t state_reasons
, /* printer-state-reasons values */
6299 bit
; /* Current reason bit */
6300 char *ptr
, /* Pointer into message */
6301 *next
; /* Next keyword in message */
6302 int remove
; /* Non-zero if we are removing keywords */
6306 * Skip leading "STATE:" and any whitespace...
6309 for (message
+= 6; *message
; message
++)
6310 if (*message
!= ' ' && *message
!= '\t')
6314 * Support the following forms of message:
6316 * "keyword[,keyword,...]" to set the printer-state-reasons value(s).
6318 * "-keyword[,keyword,...]" to remove keywords.
6320 * "+keyword[,keyword,...]" to add keywords.
6322 * Keywords may or may not have a suffix (-report, -warning, -error) per
6326 if (*message
== '-')
6329 state_reasons
= job
->printer
->state_reasons
;
6332 else if (*message
== '+')
6335 state_reasons
= job
->printer
->state_reasons
;
6341 state_reasons
= _IPP_PREASON_NONE
;
6346 if ((next
= strchr(message
, ',')) != NULL
)
6349 if ((ptr
= strstr(message
, "-error")) != NULL
)
6351 else if ((ptr
= strstr(message
, "-report")) != NULL
)
6353 else if ((ptr
= strstr(message
, "-warning")) != NULL
)
6356 for (i
= 0, bit
= 1; i
< (int)(sizeof(_ipp_preason_strings
) / sizeof(_ipp_preason_strings
[0])); i
++, bit
*= 2)
6358 if (!strcmp(message
, _ipp_preason_strings
[i
]))
6361 state_reasons
&= ~bit
;
6363 state_reasons
|= bit
;
6373 job
->printer
->state_reasons
= state_reasons
;
6378 * 'register_printer()' - Register a printer object via Bonjour.
6381 static int /* O - 1 on success, 0 on error */
6383 _ipp_printer_t
*printer
, /* I - Printer */
6384 const char *location
, /* I - Location */
6385 const char *make
, /* I - Manufacturer */
6386 const char *model
, /* I - Model name */
6387 const char *formats
, /* I - Supported formats */
6388 const char *adminurl
, /* I - Web interface URL */
6389 const char *uuid
, /* I - Printer UUID */
6390 int color
, /* I - 1 = color, 0 = monochrome */
6391 int duplex
, /* I - 1 = duplex, 0 = simplex */
6392 const char *subtype
) /* I - Service subtype */
6394 #if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
6395 _ipp_txt_t ipp_txt
; /* Bonjour IPP TXT record */
6396 #endif /* HAVE_DNSSD || HAVE_AVAHI */
6398 DNSServiceErrorType error
; /* Error from Bonjour */
6399 char make_model
[256],/* Make and model together */
6400 product
[256], /* Product string */
6401 regtype
[256]; /* Bonjour service type */
6405 * Build the TXT record for IPP...
6408 snprintf(make_model
, sizeof(make_model
), "%s %s", make
, model
);
6409 snprintf(product
, sizeof(product
), "(%s)", model
);
6411 TXTRecordCreate(&ipp_txt
, 1024, NULL
);
6412 TXTRecordSetValue(&ipp_txt
, "rp", 9, "ipp/print");
6413 TXTRecordSetValue(&ipp_txt
, "ty", (uint8_t)strlen(make_model
),
6415 TXTRecordSetValue(&ipp_txt
, "adminurl", (uint8_t)strlen(adminurl
),
6418 TXTRecordSetValue(&ipp_txt
, "note", (uint8_t)strlen(location
),
6420 TXTRecordSetValue(&ipp_txt
, "product", (uint8_t)strlen(product
),
6422 TXTRecordSetValue(&ipp_txt
, "pdl", (uint8_t)strlen(formats
),
6424 TXTRecordSetValue(&ipp_txt
, "Color", 1, color
? "T" : "F");
6425 TXTRecordSetValue(&ipp_txt
, "Duplex", 1, duplex
? "T" : "F");
6426 TXTRecordSetValue(&ipp_txt
, "usb_MFG", (uint8_t)strlen(make
),
6428 TXTRecordSetValue(&ipp_txt
, "usb_MDL", (uint8_t)strlen(model
),
6430 TXTRecordSetValue(&ipp_txt
, "UUID", (uint8_t)strlen(uuid
), uuid
);
6432 TXTRecordSetValue(&ipp_txt
, "TLS", 3, "1.2");
6433 # endif /* HAVE_SSL */
6434 if (strstr(formats
, "image/urf"))
6435 TXTRecordSetValue(&ipp_txt
, "URF", 66, "CP1,IS1-5-7,MT1-2-3-4-5-6-8-9-10-11-12-13,RS300,SRGB24,V1.4,W8,DM1");
6437 TXTRecordSetValue(&ipp_txt
, "txtvers", 1, "1");
6438 TXTRecordSetValue(&ipp_txt
, "qtotal", 1, "1");
6441 * Register the _printer._tcp (LPD) service type with a port number of 0 to
6442 * defend our service name but not actually support LPD...
6445 printer
->printer_ref
= DNSSDMaster
;
6447 if ((error
= DNSServiceRegister(&(printer
->printer_ref
),
6448 kDNSServiceFlagsShareConnection
,
6449 0 /* interfaceIndex */, printer
->dnssd_name
,
6450 "_printer._tcp", NULL
/* domain */,
6451 NULL
/* host */, 0 /* port */, 0 /* txtLen */,
6452 NULL
/* txtRecord */,
6453 (DNSServiceRegisterReply
)dnssd_callback
,
6454 printer
)) != kDNSServiceErr_NoError
)
6456 fprintf(stderr
, "Unable to register \"%s._printer._tcp\": %d\n",
6457 printer
->dnssd_name
, error
);
6462 * Then register the _ipp._tcp (IPP) service type with the real port number to
6463 * advertise our IPP printer...
6466 printer
->ipp_ref
= DNSSDMaster
;
6468 if (subtype
&& *subtype
)
6469 snprintf(regtype
, sizeof(regtype
), "_ipp._tcp,%s", subtype
);
6471 strlcpy(regtype
, "_ipp._tcp", sizeof(regtype
));
6473 if ((error
= DNSServiceRegister(&(printer
->ipp_ref
),
6474 kDNSServiceFlagsShareConnection
,
6475 0 /* interfaceIndex */, printer
->dnssd_name
,
6476 regtype
, NULL
/* domain */,
6477 NULL
/* host */, htons(printer
->port
),
6478 TXTRecordGetLength(&ipp_txt
),
6479 TXTRecordGetBytesPtr(&ipp_txt
),
6480 (DNSServiceRegisterReply
)dnssd_callback
,
6481 printer
)) != kDNSServiceErr_NoError
)
6483 fprintf(stderr
, "Unable to register \"%s.%s\": %d\n",
6484 printer
->dnssd_name
, regtype
, error
);
6490 * Then register the _ipps._tcp (IPP) service type with the real port number to
6491 * advertise our IPPS printer...
6494 printer
->ipps_ref
= DNSSDMaster
;
6496 if (subtype
&& *subtype
)
6497 snprintf(regtype
, sizeof(regtype
), "_ipps._tcp,%s", subtype
);
6499 strlcpy(regtype
, "_ipps._tcp", sizeof(regtype
));
6501 if ((error
= DNSServiceRegister(&(printer
->ipps_ref
),
6502 kDNSServiceFlagsShareConnection
,
6503 0 /* interfaceIndex */, printer
->dnssd_name
,
6504 regtype
, NULL
/* domain */,
6505 NULL
/* host */, htons(printer
->port
),
6506 TXTRecordGetLength(&ipp_txt
),
6507 TXTRecordGetBytesPtr(&ipp_txt
),
6508 (DNSServiceRegisterReply
)dnssd_callback
,
6509 printer
)) != kDNSServiceErr_NoError
)
6511 fprintf(stderr
, "Unable to register \"%s.%s\": %d\n",
6512 printer
->dnssd_name
, regtype
, error
);
6515 # endif /* HAVE_SSL */
6518 * Similarly, register the _http._tcp,_printer (HTTP) service type with the
6519 * real port number to advertise our IPP printer...
6522 printer
->http_ref
= DNSSDMaster
;
6524 if ((error
= DNSServiceRegister(&(printer
->http_ref
),
6525 kDNSServiceFlagsShareConnection
,
6526 0 /* interfaceIndex */, printer
->dnssd_name
,
6527 "_http._tcp,_printer", NULL
/* domain */,
6528 NULL
/* host */, htons(printer
->port
),
6529 0 /* txtLen */, NULL
, /* txtRecord */
6530 (DNSServiceRegisterReply
)dnssd_callback
,
6531 printer
)) != kDNSServiceErr_NoError
)
6533 fprintf(stderr
, "Unable to register \"%s.%s\": %d\n",
6534 printer
->dnssd_name
, regtype
, error
);
6538 TXTRecordDeallocate(&ipp_txt
);
6540 #elif defined(HAVE_AVAHI)
6541 char temp
[256]; /* Subtype service string */
6544 * Create the TXT record...
6548 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "rp=ipp/print");
6549 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "ty=%s %s", make
, model
);
6550 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "adminurl=%s", adminurl
);
6552 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "note=%s", location
);
6553 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "product=(%s)", model
);
6554 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "pdl=%s", formats
);
6555 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "Color=%s", color
? "T" : "F");
6556 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "Duplex=%s", duplex
? "T" : "F");
6557 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "usb_MFG=%s", make
);
6558 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "usb_MDL=%s", model
);
6559 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "UUID=%s", uuid
);
6561 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "TLS=1.2");
6562 # endif /* HAVE_SSL */
6565 * Register _printer._tcp (LPD) with port 0 to reserve the service name...
6568 avahi_threaded_poll_lock(DNSSDMaster
);
6570 printer
->ipp_ref
= avahi_entry_group_new(DNSSDClient
, dnssd_callback
, NULL
);
6572 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
);
6575 * Then register the _ipp._tcp (IPP)...
6578 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
);
6579 if (subtype
&& *subtype
)
6581 snprintf(temp
, sizeof(temp
), "%s._sub._ipp._tcp", subtype
);
6582 avahi_entry_group_add_service_subtype(printer
->ipp_ref
, AVAHI_IF_UNSPEC
, AVAHI_PROTO_UNSPEC
, 0, printer
->dnssd_name
, "_ipp._tcp", NULL
, temp
);
6587 * _ipps._tcp (IPPS) for secure printing...
6590 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
);
6591 if (subtype
&& *subtype
)
6593 snprintf(temp
, sizeof(temp
), "%s._sub._ipps._tcp", subtype
);
6594 avahi_entry_group_add_service_subtype(printer
->ipp_ref
, AVAHI_IF_UNSPEC
, AVAHI_PROTO_UNSPEC
, 0, printer
->dnssd_name
, "_ipps._tcp", NULL
, temp
);
6596 #endif /* HAVE_SSL */
6599 * Finally _http.tcp (HTTP) for the web interface...
6602 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
);
6603 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");
6609 avahi_entry_group_commit(printer
->ipp_ref
);
6610 avahi_threaded_poll_unlock(DNSSDMaster
);
6612 avahi_string_list_free(ipp_txt
);
6613 #endif /* HAVE_DNSSD */
6620 * 'respond_http()' - Send a HTTP response.
6623 int /* O - 1 on success, 0 on failure */
6625 _ipp_client_t
*client
, /* I - Client */
6626 http_status_t code
, /* I - HTTP status of response */
6627 const char *content_encoding
, /* I - Content-Encoding of response */
6628 const char *type
, /* I - MIME media type of response */
6629 size_t length
) /* I - Length of response */
6631 char message
[1024]; /* Text message */
6634 fprintf(stderr
, "%s %s\n", client
->hostname
, httpStatus(code
));
6636 if (code
== HTTP_STATUS_CONTINUE
)
6639 * 100-continue doesn't send any headers...
6642 return (httpWriteResponse(client
->http
, HTTP_STATUS_CONTINUE
) == 0);
6646 * Format an error message...
6649 if (!type
&& !length
&& code
!= HTTP_STATUS_OK
&& code
!= HTTP_STATUS_SWITCHING_PROTOCOLS
)
6651 snprintf(message
, sizeof(message
), "%d - %s\n", code
, httpStatus(code
));
6653 type
= "text/plain";
6654 length
= strlen(message
);
6660 * Send the HTTP response header...
6663 httpClearFields(client
->http
);
6665 if (code
== HTTP_STATUS_METHOD_NOT_ALLOWED
||
6666 client
->operation
== HTTP_STATE_OPTIONS
)
6667 httpSetField(client
->http
, HTTP_FIELD_ALLOW
, "GET, HEAD, OPTIONS, POST");
6671 if (!strcmp(type
, "text/html"))
6672 httpSetField(client
->http
, HTTP_FIELD_CONTENT_TYPE
,
6673 "text/html; charset=utf-8");
6675 httpSetField(client
->http
, HTTP_FIELD_CONTENT_TYPE
, type
);
6677 if (content_encoding
)
6678 httpSetField(client
->http
, HTTP_FIELD_CONTENT_ENCODING
, content_encoding
);
6681 httpSetLength(client
->http
, length
);
6683 if (httpWriteResponse(client
->http
, code
) < 0)
6687 * Send the response data...
6693 * Send a plain text message.
6696 if (httpPrintf(client
->http
, "%s", message
) < 0)
6699 if (httpWrite2(client
->http
, "", 0) < 0)
6702 else if (client
->response
)
6705 * Send an IPP response...
6708 debug_attributes("Response", client
->response
, 2);
6710 ippSetState(client
->response
, IPP_STATE_IDLE
);
6712 if (ippWrite(client
->http
, client
->response
) != IPP_STATE_DATA
)
6721 * 'respond_ipp()' - Send an IPP response.
6725 respond_ipp(_ipp_client_t
*client
, /* I - Client */
6726 ipp_status_t status
, /* I - status-code */
6727 const char *message
, /* I - printf-style status-message */
6728 ...) /* I - Additional args as needed */
6730 const char *formatted
= NULL
; /* Formatted message */
6733 ippSetStatusCode(client
->response
, status
);
6737 va_list ap
; /* Pointer to additional args */
6738 ipp_attribute_t
*attr
; /* New status-message attribute */
6740 va_start(ap
, message
);
6741 if ((attr
= ippFindAttribute(client
->response
, "status-message",
6742 IPP_TAG_TEXT
)) != NULL
)
6743 ippSetStringfv(client
->response
, &attr
, 0, message
, ap
);
6745 attr
= ippAddStringfv(client
->response
, IPP_TAG_OPERATION
, IPP_TAG_TEXT
,
6746 "status-message", NULL
, message
, ap
);
6749 formatted
= ippGetString(attr
, 0, NULL
);
6753 fprintf(stderr
, "%s %s %s (%s)\n", client
->hostname
,
6754 ippOpString(client
->operation_id
), ippErrorString(status
),
6757 fprintf(stderr
, "%s %s %s\n", client
->hostname
,
6758 ippOpString(client
->operation_id
), ippErrorString(status
));
6763 * 'respond_unsupported()' - Respond with an unsupported attribute.
6767 respond_unsupported(
6768 _ipp_client_t
*client
, /* I - Client */
6769 ipp_attribute_t
*attr
) /* I - Atribute */
6771 ipp_attribute_t
*temp
; /* Copy of attribute */
6774 respond_ipp(client
, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES
,
6775 "Unsupported %s %s%s value.", ippGetName(attr
),
6776 ippGetCount(attr
) > 1 ? "1setOf " : "",
6777 ippTagString(ippGetValueTag(attr
)));
6779 temp
= ippCopyAttribute(client
->response
, attr
, 0);
6780 ippSetGroupTag(client
->response
, &temp
, IPP_TAG_UNSUPPORTED_GROUP
);
6785 * 'run_printer()' - Run the printer service.
6789 run_printer(_ipp_printer_t
*printer
) /* I - Printer */
6791 int num_fds
; /* Number of file descriptors */
6792 struct pollfd polldata
[3]; /* poll() data */
6793 int timeout
; /* Timeout for poll() */
6794 _ipp_client_t
*client
; /* New client */
6798 * Setup poll() data for the Bonjour service socket and IPv4/6 listeners...
6801 polldata
[0].fd
= printer
->ipv4
;
6802 polldata
[0].events
= POLLIN
;
6804 polldata
[1].fd
= printer
->ipv6
;
6805 polldata
[1].events
= POLLIN
;
6810 polldata
[num_fds
].fd
= DNSServiceRefSockFD(DNSSDMaster
);
6811 polldata
[num_fds
++].events
= POLLIN
;
6812 #endif /* HAVE_DNSSD */
6815 * Loop until we are killed or have a hard error...
6820 if (cupsArrayCount(printer
->jobs
))
6825 if (poll(polldata
, (nfds_t
)num_fds
, timeout
) < 0 && errno
!= EINTR
)
6827 perror("poll() failed");
6831 if (polldata
[0].revents
& POLLIN
)
6833 if ((client
= create_client(printer
, printer
->ipv4
)) != NULL
)
6835 _cups_thread_t t
= _cupsThreadCreate((_cups_thread_func_t
)process_client
, client
);
6839 _cupsThreadDetach(t
);
6843 perror("Unable to create client thread");
6844 delete_client(client
);
6849 if (polldata
[1].revents
& POLLIN
)
6851 if ((client
= create_client(printer
, printer
->ipv6
)) != NULL
)
6853 _cups_thread_t t
= _cupsThreadCreate((_cups_thread_func_t
)process_client
, client
);
6857 _cupsThreadDetach(t
);
6861 perror("Unable to create client thread");
6862 delete_client(client
);
6868 if (polldata
[2].revents
& POLLIN
)
6869 DNSServiceProcessResult(DNSSDMaster
);
6870 #endif /* HAVE_DNSSD */
6873 * Clean out old jobs...
6876 clean_jobs(printer
);
6882 * 'time_string()' - Return the local time in hours, minutes, and seconds.
6886 time_string(time_t tv
, /* I - Time value */
6887 char *buffer
, /* I - Buffer */
6888 size_t bufsize
) /* I - Size of buffer */
6890 struct tm
*curtime
= localtime(&tv
);
6893 strftime(buffer
, bufsize
, "%X", curtime
);
6899 * 'usage()' - Show program usage.
6903 usage(int status
) /* O - Exit status */
6907 puts(CUPS_SVERSION
" - Copyright (c) 2010-2018 by Apple Inc. All rights reserved.");
6911 puts("Usage: ippserver [options] \"name\"");
6914 puts("-2 Supports 2-sided printing (default=1-sided)");
6915 puts("-M manufacturer Manufacturer name (default=Test)");
6916 puts("-P PIN printing mode");
6917 puts("-V max-version Set maximum supported IPP version");
6918 puts("-a attributes-file Load printer attributes from file");
6919 puts("-c command Run command for every print job");
6920 printf("-d spool-directory Spool directory "
6921 "(default=/tmp/ippserver.%d)\n", (int)getpid());
6922 puts("-f type/subtype[,...] List of supported types "
6923 "(default=application/pdf,image/jpeg)");
6924 puts("-h Show program help");
6925 puts("-i iconfile.png PNG icon file (default=printer.png)");
6926 puts("-k Keep job spool files");
6927 puts("-l location Location of printer (default=empty string)");
6928 puts("-m model Model name (default=Printer)");
6929 puts("-n hostname Hostname for printer");
6930 puts("-p port Port number (default=auto)");
6931 puts("-r subtype Bonjour service subtype (default=_print)");
6932 puts("-s speed[,color-speed] Speed in pages per minute (default=10,0)");
6933 puts("-v[vvv] Be (very) verbose");
6940 * 'valid_doc_attributes()' - Determine whether the document attributes are
6943 * When one or more document attributes are invalid, this function adds a
6944 * suitable response and attributes to the unsupported group.
6947 static int /* O - 1 if valid, 0 if not */
6948 valid_doc_attributes(
6949 _ipp_client_t
*client
) /* I - Client */
6951 int valid
= 1; /* Valid attributes? */
6952 ipp_op_t op
= ippGetOperation(client
->request
);
6954 const char *op_name
= ippOpString(op
);
6955 /* IPP operation name */
6956 ipp_attribute_t
*attr
, /* Current attribute */
6957 *supported
; /* xxx-supported attribute */
6958 const char *compression
= NULL
,
6959 /* compression value */
6960 *format
= NULL
; /* document-format value */
6964 * Check operation attributes...
6967 if ((attr
= ippFindAttribute(client
->request
, "compression", IPP_TAG_ZERO
)) != NULL
)
6970 * If compression is specified, only accept a supported value in a Print-Job
6971 * or Send-Document request...
6974 compression
= ippGetString(attr
, 0, NULL
);
6975 supported
= ippFindAttribute(client
->printer
->attrs
,
6976 "compression-supported", IPP_TAG_KEYWORD
);
6978 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_KEYWORD
||
6979 ippGetGroupTag(attr
) != IPP_TAG_OPERATION
||
6980 (op
!= IPP_OP_PRINT_JOB
&& op
!= IPP_OP_SEND_DOCUMENT
&&
6981 op
!= IPP_OP_VALIDATE_JOB
) ||
6982 !ippContainsString(supported
, compression
))
6984 respond_unsupported(client
, attr
);
6989 fprintf(stderr
, "%s %s compression=\"%s\"\n", client
->hostname
, op_name
, compression
);
6991 ippAddString(client
->request
, IPP_TAG_JOB
, IPP_TAG_KEYWORD
, "compression-supplied", NULL
, compression
);
6993 if (strcmp(compression
, "none"))
6996 fprintf(stderr
, "Receiving job file with \"%s\" compression.\n", compression
);
6997 httpSetField(client
->http
, HTTP_FIELD_CONTENT_ENCODING
, compression
);
7003 * Is it a format we support?
7006 if ((attr
= ippFindAttribute(client
->request
, "document-format", IPP_TAG_ZERO
)) != NULL
)
7008 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_MIMETYPE
||
7009 ippGetGroupTag(attr
) != IPP_TAG_OPERATION
)
7011 respond_unsupported(client
, attr
);
7016 format
= ippGetString(attr
, 0, NULL
);
7018 fprintf(stderr
, "%s %s document-format=\"%s\"\n",
7019 client
->hostname
, op_name
, format
);
7021 ippAddString(client
->request
, IPP_TAG_JOB
, IPP_TAG_MIMETYPE
, "document-format-supplied", NULL
, format
);
7026 format
= ippGetString(ippFindAttribute(client
->printer
->attrs
, "document-format-default", IPP_TAG_MIMETYPE
), 0, NULL
);
7028 format
= "application/octet-stream"; /* Should never happen */
7030 attr
= ippAddString(client
->request
, IPP_TAG_OPERATION
, IPP_TAG_MIMETYPE
, "document-format", NULL
, format
);
7033 if (format
&& !strcmp(format
, "application/octet-stream") && (ippGetOperation(client
->request
) == IPP_OP_PRINT_JOB
|| ippGetOperation(client
->request
) == IPP_OP_SEND_DOCUMENT
))
7036 * Auto-type the file using the first 8 bytes of the file...
7039 unsigned char header
[8]; /* First 8 bytes of file */
7041 memset(header
, 0, sizeof(header
));
7042 httpPeek(client
->http
, (char *)header
, sizeof(header
));
7044 if (!memcmp(header
, "%PDF", 4))
7045 format
= "application/pdf";
7046 else if (!memcmp(header
, "%!", 2))
7047 format
= "application/postscript";
7048 else if (!memcmp(header
, "\377\330\377", 3) && header
[3] >= 0xe0 && header
[3] <= 0xef)
7049 format
= "image/jpeg";
7050 else if (!memcmp(header
, "\211PNG", 4))
7051 format
= "image/png";
7052 else if (!memcmp(header
, "RAS2", 4))
7053 format
= "image/pwg-raster";
7054 else if (!memcmp(header
, "UNIRAST", 8))
7055 format
= "image/urf";
7061 fprintf(stderr
, "%s %s Auto-typed document-format=\"%s\"\n",
7062 client
->hostname
, op_name
, format
);
7064 ippAddString(client
->request
, IPP_TAG_JOB
, IPP_TAG_MIMETYPE
, "document-format-detected", NULL
, format
);
7068 if (op
!= IPP_OP_CREATE_JOB
&& (supported
= ippFindAttribute(client
->printer
->attrs
, "document-format-supported", IPP_TAG_MIMETYPE
)) != NULL
&& !ippContainsString(supported
, format
))
7070 respond_unsupported(client
, attr
);
7078 if ((attr
= ippFindAttribute(client
->request
, "document-name", IPP_TAG_NAME
)) != NULL
)
7079 ippAddString(client
->request
, IPP_TAG_JOB
, IPP_TAG_NAME
, "document-name-supplied", NULL
, ippGetString(attr
, 0, NULL
));
7086 * 'valid_job_attributes()' - Determine whether the job attributes are valid.
7088 * When one or more job attributes are invalid, this function adds a suitable
7089 * response and attributes to the unsupported group.
7092 static int /* O - 1 if valid, 0 if not */
7093 valid_job_attributes(
7094 _ipp_client_t
*client
) /* I - Client */
7096 int i
, /* Looping var */
7097 count
, /* Number of values */
7098 valid
= 1; /* Valid attributes? */
7099 ipp_attribute_t
*attr
, /* Current attribute */
7100 *supported
; /* xxx-supported attribute */
7104 * Check operation attributes...
7107 valid
= valid_doc_attributes(client
);
7110 * Check the various job template attributes...
7113 if ((attr
= ippFindAttribute(client
->request
, "copies", IPP_TAG_ZERO
)) != NULL
)
7115 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_INTEGER
||
7116 ippGetInteger(attr
, 0) < 1 || ippGetInteger(attr
, 0) > 999)
7118 respond_unsupported(client
, attr
);
7123 if ((attr
= ippFindAttribute(client
->request
, "ipp-attribute-fidelity", IPP_TAG_ZERO
)) != NULL
)
7125 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_BOOLEAN
)
7127 respond_unsupported(client
, attr
);
7132 if ((attr
= ippFindAttribute(client
->request
, "job-hold-until", IPP_TAG_ZERO
)) != NULL
)
7134 if (ippGetCount(attr
) != 1 ||
7135 (ippGetValueTag(attr
) != IPP_TAG_NAME
&&
7136 ippGetValueTag(attr
) != IPP_TAG_NAMELANG
&&
7137 ippGetValueTag(attr
) != IPP_TAG_KEYWORD
) ||
7138 strcmp(ippGetString(attr
, 0, NULL
), "no-hold"))
7140 respond_unsupported(client
, attr
);
7145 if ((attr
= ippFindAttribute(client
->request
, "job-impressions", IPP_TAG_ZERO
)) != NULL
)
7147 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_INTEGER
|| ippGetInteger(attr
, 0) < 0)
7149 respond_unsupported(client
, attr
);
7154 if ((attr
= ippFindAttribute(client
->request
, "job-name", IPP_TAG_ZERO
)) != NULL
)
7156 if (ippGetCount(attr
) != 1 ||
7157 (ippGetValueTag(attr
) != IPP_TAG_NAME
&&
7158 ippGetValueTag(attr
) != IPP_TAG_NAMELANG
))
7160 respond_unsupported(client
, attr
);
7164 ippSetGroupTag(client
->request
, &attr
, IPP_TAG_JOB
);
7167 ippAddString(client
->request
, IPP_TAG_JOB
, IPP_TAG_NAME
, "job-name", NULL
, "Untitled");
7169 if ((attr
= ippFindAttribute(client
->request
, "job-priority", IPP_TAG_ZERO
)) != NULL
)
7171 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_INTEGER
||
7172 ippGetInteger(attr
, 0) < 1 || ippGetInteger(attr
, 0) > 100)
7174 respond_unsupported(client
, attr
);
7179 if ((attr
= ippFindAttribute(client
->request
, "job-sheets", IPP_TAG_ZERO
)) != NULL
)
7181 if (ippGetCount(attr
) != 1 ||
7182 (ippGetValueTag(attr
) != IPP_TAG_NAME
&&
7183 ippGetValueTag(attr
) != IPP_TAG_NAMELANG
&&
7184 ippGetValueTag(attr
) != IPP_TAG_KEYWORD
) ||
7185 strcmp(ippGetString(attr
, 0, NULL
), "none"))
7187 respond_unsupported(client
, attr
);
7192 if ((attr
= ippFindAttribute(client
->request
, "media", IPP_TAG_ZERO
)) != NULL
)
7194 if (ippGetCount(attr
) != 1 ||
7195 (ippGetValueTag(attr
) != IPP_TAG_NAME
&&
7196 ippGetValueTag(attr
) != IPP_TAG_NAMELANG
&&
7197 ippGetValueTag(attr
) != IPP_TAG_KEYWORD
))
7199 respond_unsupported(client
, attr
);
7204 supported
= ippFindAttribute(client
->printer
->attrs
, "media-supported", IPP_TAG_KEYWORD
);
7206 if (!ippContainsString(supported
, ippGetString(attr
, 0, NULL
)))
7208 respond_unsupported(client
, attr
);
7214 if ((attr
= ippFindAttribute(client
->request
, "media-col", IPP_TAG_ZERO
)) != NULL
)
7216 ipp_t
*col
, /* media-col collection */
7217 *size
; /* media-size collection */
7218 ipp_attribute_t
*member
, /* Member attribute */
7219 *x_dim
, /* x-dimension */
7220 *y_dim
; /* y-dimension */
7221 int x_value
, /* y-dimension value */
7222 y_value
; /* x-dimension value */
7224 if (ippGetCount(attr
) != 1 ||
7225 ippGetValueTag(attr
) != IPP_TAG_BEGIN_COLLECTION
)
7227 respond_unsupported(client
, attr
);
7231 col
= ippGetCollection(attr
, 0);
7233 if ((member
= ippFindAttribute(col
, "media-size-name", IPP_TAG_ZERO
)) != NULL
)
7235 if (ippGetCount(member
) != 1 ||
7236 (ippGetValueTag(member
) != IPP_TAG_NAME
&&
7237 ippGetValueTag(member
) != IPP_TAG_NAMELANG
&&
7238 ippGetValueTag(member
) != IPP_TAG_KEYWORD
))
7240 respond_unsupported(client
, attr
);
7245 supported
= ippFindAttribute(client
->printer
->attrs
, "media-supported", IPP_TAG_KEYWORD
);
7247 if (!ippContainsString(supported
, ippGetString(member
, 0, NULL
)))
7249 respond_unsupported(client
, attr
);
7254 else if ((member
= ippFindAttribute(col
, "media-size", IPP_TAG_BEGIN_COLLECTION
)) != NULL
)
7256 if (ippGetCount(member
) != 1)
7258 respond_unsupported(client
, attr
);
7263 size
= ippGetCollection(member
, 0);
7265 if ((x_dim
= ippFindAttribute(size
, "x-dimension", IPP_TAG_INTEGER
)) == NULL
|| ippGetCount(x_dim
) != 1 ||
7266 (y_dim
= ippFindAttribute(size
, "y-dimension", IPP_TAG_INTEGER
)) == NULL
|| ippGetCount(y_dim
) != 1)
7268 respond_unsupported(client
, attr
);
7273 x_value
= ippGetInteger(x_dim
, 0);
7274 y_value
= ippGetInteger(y_dim
, 0);
7275 supported
= ippFindAttribute(client
->printer
->attrs
, "media-size-supported", IPP_TAG_BEGIN_COLLECTION
);
7276 count
= ippGetCount(supported
);
7278 for (i
= 0; i
< count
; i
++)
7280 size
= ippGetCollection(supported
, i
);
7281 x_dim
= ippFindAttribute(size
, "x-dimension", IPP_TAG_ZERO
);
7282 y_dim
= ippFindAttribute(size
, "y-dimension", IPP_TAG_ZERO
);
7284 if (ippContainsInteger(x_dim
, x_value
) && ippContainsInteger(y_dim
, y_value
))
7290 respond_unsupported(client
, attr
);
7298 if ((attr
= ippFindAttribute(client
->request
, "multiple-document-handling", IPP_TAG_ZERO
)) != NULL
)
7300 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_KEYWORD
||
7301 (strcmp(ippGetString(attr
, 0, NULL
),
7302 "separate-documents-uncollated-copies") &&
7303 strcmp(ippGetString(attr
, 0, NULL
),
7304 "separate-documents-collated-copies")))
7306 respond_unsupported(client
, attr
);
7311 if ((attr
= ippFindAttribute(client
->request
, "orientation-requested", IPP_TAG_ZERO
)) != NULL
)
7313 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_ENUM
||
7314 ippGetInteger(attr
, 0) < IPP_ORIENT_PORTRAIT
||
7315 ippGetInteger(attr
, 0) > IPP_ORIENT_REVERSE_PORTRAIT
)
7317 respond_unsupported(client
, attr
);
7322 if ((attr
= ippFindAttribute(client
->request
, "page-ranges", IPP_TAG_ZERO
)) != NULL
)
7324 if (ippGetValueTag(attr
) != IPP_TAG_RANGE
)
7326 respond_unsupported(client
, attr
);
7331 if ((attr
= ippFindAttribute(client
->request
, "print-quality", IPP_TAG_ZERO
)) != NULL
)
7333 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_ENUM
||
7334 ippGetInteger(attr
, 0) < IPP_QUALITY_DRAFT
||
7335 ippGetInteger(attr
, 0) > IPP_QUALITY_HIGH
)
7337 respond_unsupported(client
, attr
);
7342 if ((attr
= ippFindAttribute(client
->request
, "printer-resolution", IPP_TAG_ZERO
)) != NULL
)
7344 supported
= ippFindAttribute(client
->printer
->attrs
, "printer-resolution-supported", IPP_TAG_RESOLUTION
);
7346 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_RESOLUTION
||
7349 respond_unsupported(client
, attr
);
7354 int xdpi
, /* Horizontal resolution for job template attribute */
7355 ydpi
, /* Vertical resolution for job template attribute */
7356 sydpi
; /* Vertical resolution for supported value */
7357 ipp_res_t units
, /* Units for job template attribute */
7358 sunits
; /* Units for supported value */
7360 xdpi
= ippGetResolution(attr
, 0, &ydpi
, &units
);
7361 count
= ippGetCount(supported
);
7363 for (i
= 0; i
< count
; i
++)
7365 if (xdpi
== ippGetResolution(supported
, i
, &sydpi
, &sunits
) && ydpi
== sydpi
&& units
== sunits
)
7371 respond_unsupported(client
, attr
);
7377 if ((attr
= ippFindAttribute(client
->request
, "sides", IPP_TAG_ZERO
)) != NULL
)
7379 const char *sides
= ippGetString(attr
, 0, NULL
);
7380 /* "sides" value... */
7382 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_KEYWORD
)
7384 respond_unsupported(client
, attr
);
7387 else if ((supported
= ippFindAttribute(client
->printer
->attrs
, "sides-supported", IPP_TAG_KEYWORD
)) != NULL
)
7389 if (!ippContainsString(supported
, sides
))
7391 respond_unsupported(client
, attr
);
7395 else if (strcmp(sides
, "one-sided"))
7397 respond_unsupported(client
, attr
);