2 * Sample IPP Everywhere server for CUPS.
4 * Copyright 2010-2015 by Apple Inc.
6 * These coded instructions, statements, and computer programs are the
7 * property of Apple Inc. and are protected by Federal copyright
8 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
9 * which should have been included with this file. If this file is
10 * file is missing or damaged, see the license at "http://www.cups.org/".
12 * This file is subject to the Apple OS-Developed Software exception.
16 * Disable private and deprecated stuff so we can verify that the public API
17 * is sufficient to implement a server.
20 #define _IPP_PRIVATE_STRUCTURES 0 /* Disable private IPP stuff */
21 #define _CUPS_NO_DEPRECATED 1 /* Disable deprecated stuff */
25 * Include necessary headers...
28 #include <config.h> /* CUPS configuration header */
29 #include <cups/cups.h> /* Public API */
30 #include <cups/string-private.h> /* CUPS string functions */
31 #include <cups/thread-private.h> /* For multithreading functions */
44 # define WEXITSTATUS(s) (s)
45 # include <winsock2.h>
49 extern char **environ
;
51 # include <sys/fcntl.h>
52 # include <sys/wait.h>
58 #elif defined(HAVE_AVAHI)
59 # include <avahi-client/client.h>
60 # include <avahi-client/publish.h>
61 # include <avahi-common/error.h>
62 # include <avahi-common/thread-watch.h>
63 #endif /* HAVE_DNSSD */
64 #ifdef HAVE_SYS_MOUNT_H
65 # include <sys/mount.h>
66 #endif /* HAVE_SYS_MOUNT_H */
67 #ifdef HAVE_SYS_STATFS_H
68 # include <sys/statfs.h>
69 #endif /* HAVE_SYS_STATFS_H */
70 #ifdef HAVE_SYS_STATVFS_H
71 # include <sys/statvfs.h>
72 #endif /* HAVE_SYS_STATVFS_H */
75 #endif /* HAVE_SYS_VFS_H */
82 enum _ipp_preason_e
/* printer-state-reasons bit values */
84 _IPP_PREASON_NONE
= 0x0000, /* none */
85 _IPP_PREASON_OTHER
= 0x0001, /* other */
86 _IPP_PREASON_COVER_OPEN
= 0x0002, /* cover-open */
87 _IPP_PREASON_INPUT_TRAY_MISSING
= 0x0004,
88 /* input-tray-missing */
89 _IPP_PREASON_MARKER_SUPPLY_EMPTY
= 0x0008,
90 /* marker-supply-empty */
91 _IPP_PREASON_MARKER_SUPPLY_LOW
= 0x0010,
92 /* marker-supply-low */
93 _IPP_PREASON_MARKER_WASTE_ALMOST_FULL
= 0x0020,
94 /* marker-waste-almost-full */
95 _IPP_PREASON_MARKER_WASTE_FULL
= 0x0040,
96 /* marker-waste-full */
97 _IPP_PREASON_MEDIA_EMPTY
= 0x0080, /* media-empty */
98 _IPP_PREASON_MEDIA_JAM
= 0x0100, /* media-jam */
99 _IPP_PREASON_MEDIA_LOW
= 0x0200, /* media-low */
100 _IPP_PREASON_MEDIA_NEEDED
= 0x0400, /* media-needed */
101 _IPP_PREASON_MOVING_TO_PAUSED
= 0x0800,
102 /* moving-to-paused */
103 _IPP_PREASON_PAUSED
= 0x1000, /* paused */
104 _IPP_PREASON_SPOOL_AREA_FULL
= 0x2000,/* spool-area-full */
105 _IPP_PREASON_TONER_EMPTY
= 0x4000, /* toner-empty */
106 _IPP_PREASON_TONER_LOW
= 0x8000 /* toner-low */
108 typedef unsigned int _ipp_preason_t
; /* Bitfield for printer-state-reasons */
109 static const char * const _ipp_preason_strings
[] =
110 { /* Strings for each bit */
111 /* "none" is implied for no bits set */
114 "input-tray-missing",
115 "marker-supply-empty",
117 "marker-waste-almost-full",
130 typedef enum _ipp_media_class_e
132 _IPP_GENERAL
, /* General-purpose size */
133 _IPP_PHOTO_ONLY
, /* Photo-only size */
134 _IPP_ENV_ONLY
/* Envelope-only size */
135 } _ipp_media_class_t
;
137 typedef enum _ipp_media_size_e
139 _IPP_MEDIA_SIZE_NONE
= -1,
144 _IPP_MEDIA_SIZE_LEGAL
,
145 _IPP_MEDIA_SIZE_LETTER
,
146 _IPP_MEDIA_SIZE_COM10
,
152 static const char * const media_supported
[] =
153 { /* media-supported values */
154 "iso_a4_210x297mm", /* A4 */
155 "iso_a5_148x210mm", /* A5 */
156 "iso_a6_105x148mm", /* A6 */
157 "iso_dl_110x220mm", /* DL */
158 "na_legal_8.5x14in", /* Legal */
159 "na_letter_8.5x11in", /* Letter */
160 "na_number-10_4.125x9.5in", /* #10 */
161 "na_index-3x5_3x5in", /* 3x5 */
162 "oe_photo-l_3.5x5in", /* L */
163 "na_index-4x6_4x6in", /* 4x6 */
164 "na_5x7_5x7in" /* 5x7 aka 2L */
166 static const int media_col_sizes
[][3] =
167 { /* media-col-database sizes */
168 { 21000, 29700, _IPP_GENERAL
}, /* A4 */
169 { 14800, 21000, _IPP_PHOTO_ONLY
}, /* A5 */
170 { 10500, 14800, _IPP_PHOTO_ONLY
}, /* A6 */
171 { 11000, 22000, _IPP_ENV_ONLY
}, /* DL */
172 { 21590, 35560, _IPP_GENERAL
}, /* Legal */
173 { 21590, 27940, _IPP_GENERAL
}, /* Letter */
174 { 10477, 24130, _IPP_ENV_ONLY
}, /* #10 */
175 { 7630, 12700, _IPP_PHOTO_ONLY
}, /* 3x5 */
176 { 8890, 12700, _IPP_PHOTO_ONLY
}, /* L */
177 { 10160, 15240, _IPP_PHOTO_ONLY
}, /* 4x6 */
178 { 12700, 17780, _IPP_PHOTO_ONLY
} /* 5x7 aka 2L */
181 typedef enum _ipp_media_source_e
183 _IPP_MEDIA_SOURCE_NONE
= -1,
184 _IPP_MEDIA_SOURCE_AUTO
,
185 _IPP_MEDIA_SOURCE_MAIN
,
186 _IPP_MEDIA_SOURCE_MANUAL
,
187 _IPP_MEDIA_SOURCE_ENVELOPE
,
188 _IPP_MEDIA_SOURCE_PHOTO
189 } _ipp_media_source_t
;
190 static const char * const media_source_supported
[] =
191 /* media-source-supported values */
200 typedef enum _ipp_media_type_e
202 _IPP_MEDIA_TYPE_NONE
= -1,
203 _IPP_MEDIA_TYPE_AUTO
,
204 _IPP_MEDIA_TYPE_CARDSTOCK
,
205 _IPP_MEDIA_TYPE_ENVELOPE
,
206 _IPP_MEDIA_TYPE_LABELS
,
207 _IPP_MEDIA_TYPE_OTHER
,
208 _IPP_MEDIA_TYPE_GLOSSY
,
209 _IPP_MEDIA_TYPE_HIGH_GLOSS
,
210 _IPP_MEDIA_TYPE_MATTE
,
211 _IPP_MEDIA_TYPE_SATIN
,
212 _IPP_MEDIA_TYPE_SEMI_GLOSS
,
213 _IPP_MEDIA_TYPE_STATIONERY
,
214 _IPP_MEDIA_TYPE_LETTERHEAD
,
215 _IPP_MEDIA_TYPE_TRANSPARENCY
217 static const char * const media_type_supported
[] =
218 /* media-type-supported values */
225 "photographic-glossy",
226 "photographic-high-gloss",
227 "photographic-matte",
228 "photographic-satin",
229 "photographic-semi-gloss",
231 "stationery-letterhead",
235 typedef enum _ipp_supply_e
237 _IPP_SUPPLY_CYAN
, /* Cyan Toner */
238 _IPP_SUPPLY_MAGENTA
, /* Magenta Toner */
239 _IPP_SUPPLY_YELLOW
, /* Yellow Toner */
240 _IPP_SUPPLY_BLACK
, /* Black Toner */
241 _IPP_SUPPLY_WASTE
/* Waste Toner */
243 static const char * const printer_supplies
[] =
244 { /* printer-supply-description values */
253 * URL scheme for web resources...
257 # define WEB_SCHEME "https"
259 # define WEB_SCHEME "http"
260 #endif /* HAVE_SSL */
268 typedef DNSServiceRef _ipp_srv_t
; /* Service reference */
269 typedef TXTRecordRef _ipp_txt_t
; /* TXT record */
271 #elif defined(HAVE_AVAHI)
272 typedef AvahiEntryGroup
*_ipp_srv_t
; /* Service reference */
273 typedef AvahiStringList
*_ipp_txt_t
; /* TXT record */
276 typedef void *_ipp_srv_t
; /* Service reference */
277 typedef void *_ipp_txt_t
; /* TXT record */
278 #endif /* HAVE_DNSSD */
280 typedef struct _ipp_filter_s
/**** Attribute filter ****/
282 cups_array_t
*ra
; /* Requested attributes */
283 ipp_tag_t group_tag
; /* Group to copy */
286 typedef struct _ipp_job_s _ipp_job_t
;
288 typedef struct _ipp_printer_s
/**** Printer data ****/
290 int ipv4
, /* IPv4 listener */
291 ipv6
; /* IPv6 listener */
292 _ipp_srv_t ipp_ref
, /* Bonjour IPP service */
293 ipps_ref
, /* Bonjour IPPS service */
294 http_ref
, /* Bonjour HTTP service */
295 printer_ref
; /* Bonjour LPD service */
296 char *dnssd_name
, /* printer-dnssd-name */
297 *name
, /* printer-name */
298 *icon
, /* Icon filename */
299 *directory
, /* Spool directory */
300 *hostname
, /* Hostname */
301 *uri
, /* printer-uri-supported */
302 *command
; /* Command to run with job file */
304 size_t urilen
; /* Length of printer URI */
305 ipp_t
*attrs
; /* Static attributes */
306 time_t start_time
; /* Startup time */
307 time_t config_time
; /* printer-config-change-time */
308 ipp_pstate_t state
; /* printer-state value */
309 _ipp_preason_t state_reasons
; /* printer-state-reasons values */
310 time_t state_time
; /* printer-state-change-time */
311 cups_array_t
*jobs
; /* Jobs */
312 _ipp_job_t
*active_job
; /* Current active/pending job */
313 int next_job_id
; /* Next job-id value */
314 _cups_rwlock_t rwlock
; /* Printer lock */
315 _ipp_media_size_t main_size
; /* Ready media */
316 _ipp_media_type_t main_type
;
318 _ipp_media_size_t envelope_size
;
320 _ipp_media_size_t photo_size
;
321 _ipp_media_type_t photo_type
;
323 int supplies
[5]; /* Supply levels (0-100) */
326 struct _ipp_job_s
/**** Job data ****/
329 const char *name
, /* job-name */
330 *username
, /* job-originating-user-name */
331 *format
; /* document-format */
332 ipp_jstate_t state
; /* job-state value */
333 time_t created
, /* time-at-creation value */
334 processing
, /* time-at-processing value */
335 completed
; /* time-at-completed value */
336 int impressions
, /* job-impressions value */
337 impcompleted
; /* job-impressions-completed value */
338 ipp_t
*attrs
; /* Static attributes */
339 int cancel
; /* Non-zero when job canceled */
340 char *filename
; /* Print file name */
341 int fd
; /* Print file descriptor */
342 _ipp_printer_t
*printer
; /* Printer */
345 typedef struct _ipp_client_s
/**** Client data ****/
347 http_t
*http
; /* HTTP connection */
348 ipp_t
*request
, /* IPP request */
349 *response
; /* IPP response */
350 time_t start
; /* Request start time */
351 http_state_t operation
; /* Request operation */
352 ipp_op_t operation_id
; /* IPP operation-id */
353 char uri
[1024], /* Request URI */
354 *options
; /* URI options */
355 http_addr_t addr
; /* Client address */
356 char hostname
[256]; /* Client hostname */
357 _ipp_printer_t
*printer
; /* Printer */
358 _ipp_job_t
*job
; /* Current job, if any */
366 static void clean_jobs(_ipp_printer_t
*printer
);
367 static int compare_jobs(_ipp_job_t
*a
, _ipp_job_t
*b
);
368 static void copy_attributes(ipp_t
*to
, ipp_t
*from
, cups_array_t
*ra
,
369 ipp_tag_t group_tag
, int quickcopy
);
370 static void copy_job_attributes(_ipp_client_t
*client
,
371 _ipp_job_t
*job
, cups_array_t
*ra
);
372 static _ipp_client_t
*create_client(_ipp_printer_t
*printer
, int sock
);
373 static _ipp_job_t
*create_job(_ipp_client_t
*client
);
374 static int create_listener(int family
, int port
);
375 static ipp_t
*create_media_col(const char *media
, const char *source
, const char *type
, int width
, int length
, int margins
);
376 static ipp_t
*create_media_size(int width
, int length
);
377 static _ipp_printer_t
*create_printer(const char *servername
,
378 const char *name
, const char *location
,
379 const char *make
, const char *model
,
381 const char *docformats
, int ppm
,
382 int ppm_color
, int duplex
, int port
,
383 int pin
, const char *subtype
,
384 const char *directory
,
386 const char *attrfile
);
387 static void debug_attributes(const char *title
, ipp_t
*ipp
,
389 static void delete_client(_ipp_client_t
*client
);
390 static void delete_job(_ipp_job_t
*job
);
391 static void delete_printer(_ipp_printer_t
*printer
);
393 static void DNSSD_API
dnssd_callback(DNSServiceRef sdRef
,
394 DNSServiceFlags flags
,
395 DNSServiceErrorType errorCode
,
399 _ipp_printer_t
*printer
);
400 #elif defined(HAVE_AVAHI)
401 static void dnssd_callback(AvahiEntryGroup
*p
, AvahiEntryGroupState state
, void *context
);
402 static void dnssd_client_cb(AvahiClient
*c
, AvahiClientState state
, void *userdata
);
403 #endif /* HAVE_DNSSD */
404 static void dnssd_init(void);
405 static int filter_cb(_ipp_filter_t
*filter
, ipp_t
*dst
, ipp_attribute_t
*attr
);
406 static _ipp_job_t
*find_job(_ipp_client_t
*client
);
407 static ipp_t
*get_collection(FILE *fp
, const char *filename
, int *linenum
);
408 static char *get_token(FILE *fp
, char *buf
, int buflen
, int *linenum
);
409 static void html_escape(_ipp_client_t
*client
, const char *s
,
411 static void html_footer(_ipp_client_t
*client
);
412 static void html_header(_ipp_client_t
*client
, const char *title
);
413 static void html_printf(_ipp_client_t
*client
, const char *format
,
414 ...) __attribute__((__format__(__printf__
,
416 static void ipp_cancel_job(_ipp_client_t
*client
);
417 static void ipp_close_job(_ipp_client_t
*client
);
418 static void ipp_create_job(_ipp_client_t
*client
);
419 static void ipp_get_job_attributes(_ipp_client_t
*client
);
420 static void ipp_get_jobs(_ipp_client_t
*client
);
421 static void ipp_get_printer_attributes(_ipp_client_t
*client
);
422 static void ipp_identify_printer(_ipp_client_t
*client
);
423 static void ipp_print_job(_ipp_client_t
*client
);
424 static void ipp_print_uri(_ipp_client_t
*client
);
425 static void ipp_send_document(_ipp_client_t
*client
);
426 static void ipp_send_uri(_ipp_client_t
*client
);
427 static void ipp_validate_job(_ipp_client_t
*client
);
428 static void load_attributes(const char *filename
, ipp_t
*attrs
);
429 static int parse_options(_ipp_client_t
*client
, cups_option_t
**options
);
430 static void process_attr_message(_ipp_job_t
*job
, char *message
);
431 static void *process_client(_ipp_client_t
*client
);
432 static int process_http(_ipp_client_t
*client
);
433 static int process_ipp(_ipp_client_t
*client
);
434 static void *process_job(_ipp_job_t
*job
);
435 static void process_state_message(_ipp_job_t
*job
, char *message
);
436 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
);
437 static int respond_http(_ipp_client_t
*client
, http_status_t code
,
438 const char *content_coding
,
439 const char *type
, size_t length
);
440 static void respond_ipp(_ipp_client_t
*client
, ipp_status_t status
,
441 const char *message
, ...)
442 __attribute__ ((__format__ (__printf__
, 3, 4)));
443 static void respond_unsupported(_ipp_client_t
*client
,
444 ipp_attribute_t
*attr
);
445 static void run_printer(_ipp_printer_t
*printer
);
446 static char *time_string(time_t tv
, char *buffer
, size_t bufsize
);
447 static void usage(int status
) __attribute__((noreturn
));
448 static int valid_doc_attributes(_ipp_client_t
*client
);
449 static int valid_job_attributes(_ipp_client_t
*client
);
457 static DNSServiceRef DNSSDMaster
= NULL
;
458 #elif defined(HAVE_AVAHI)
459 static AvahiThreadedPoll
*DNSSDMaster
= NULL
;
460 static AvahiClient
*DNSSDClient
= NULL
;
461 #endif /* HAVE_DNSSD */
463 static int KeepFiles
= 0,
468 * 'main()' - Main entry to the sample server.
471 int /* O - Exit status */
472 main(int argc
, /* I - Number of command-line args */
473 char *argv
[]) /* I - Command-line arguments */
475 int i
; /* Looping var */
476 const char *opt
, /* Current option character */
477 *attrfile
= NULL
, /* Attributes file */
478 *command
= NULL
, /* Command to run with job files */
479 *servername
= NULL
, /* Server host name */
480 *name
= NULL
, /* Printer name */
481 *location
= "", /* Location of printer */
482 *make
= "Test", /* Manufacturer */
483 *model
= "Printer", /* Model */
484 *icon
= "printer.png", /* Icon file */
485 *formats
= "application/pdf,image/jpeg,image/pwg-raster";
486 /* Supported formats */
488 const char *keypath
= NULL
; /* Keychain path */
489 #endif /* HAVE_SSL */
490 const char *subtype
= "_print"; /* Bonjour service subtype */
491 int port
= 0, /* Port number (0 = auto) */
492 duplex
= 0, /* Duplex mode */
493 ppm
= 10, /* Pages per minute for mono */
494 ppm_color
= 0, /* Pages per minute for color */
495 pin
= 0; /* PIN printing mode? */
496 char directory
[1024] = "", /* Spool directory */
497 hostname
[1024]; /* Auto-detected hostname */
498 _ipp_printer_t
*printer
; /* Printer object */
502 * Parse command-line arguments...
505 for (i
= 1; i
< argc
; i
++)
506 if (argv
[i
][0] == '-')
508 for (opt
= argv
[i
] + 1; *opt
; opt
++)
512 case '2' : /* -2 (enable 2-sided printing) */
517 case 'K' : /* -K keypath */
523 #endif /* HAVE_SSL */
525 case 'M' : /* -M manufacturer */
532 case 'P' : /* -P (PIN printing mode) */
536 case 'a' : /* -a attributes-file */
544 case 'c' : /* -c command */
552 case 'd' : /* -d spool-directory */
556 strlcpy(directory
, argv
[i
], sizeof(directory
));
559 case 'f' : /* -f type/subtype[,...] */
566 case 'h' : /* -h (show help) */
569 case 'i' : /* -i icon.png */
576 case 'k' : /* -k (keep files) */
580 case 'l' : /* -l location */
587 case 'm' : /* -m model */
594 case 'n' : /* -n hostname */
598 servername
= argv
[i
];
601 case 'p' : /* -p port */
603 if (i
>= argc
|| !isdigit(argv
[i
][0] & 255))
605 port
= atoi(argv
[i
]);
608 case 'r' : /* -r subtype */
615 case 's' : /* -s speed[,color-speed] */
619 if (sscanf(argv
[i
], "%d,%d", &ppm
, &ppm_color
) < 1)
623 case 'v' : /* -v (be verbose) */
627 default : /* Unknown */
628 fprintf(stderr
, "Unknown option \"-%c\".\n", *opt
);
639 fprintf(stderr
, "Unexpected command-line argument \"%s\"\n", argv
[i
]);
647 * Apply defaults as needed...
651 servername
= httpGetHostname(NULL
, hostname
, sizeof(hostname
));
657 * Windows is almost always used as a single user system, so use a default
658 * port number of 8631.
665 * Use 8000 + UID mod 1000 for the default port number...
668 port
= 8000 + ((int)getuid() % 1000);
671 fprintf(stderr
, "Listening on port %d.\n", port
);
676 const char *tmpdir
; /* Temporary directory */
679 if ((tmpdir
= getenv("TEMP")) == NULL
)
681 #elif defined(__APPLE__) && !TARGET_OS_IOS
682 if ((tmpdir
= getenv("TMPDIR")) == NULL
)
683 tmpdir
= "/private/tmp";
685 if ((tmpdir
= getenv("TMPDIR")) == NULL
)
689 snprintf(directory
, sizeof(directory
), "%s/ippserver.%d", tmpdir
, (int)getpid());
691 if (mkdir(directory
, 0755) && errno
!= EEXIST
)
693 fprintf(stderr
, "Unable to create spool directory \"%s\": %s\n",
694 directory
, strerror(errno
));
699 fprintf(stderr
, "Using spool directory \"%s\".\n", directory
);
703 cupsSetServerCredentials(keypath
, servername
, 1);
704 #endif /* HAVE_SSL */
707 * Initialize Bonjour...
713 * Create the printer...
716 if ((printer
= create_printer(servername
, name
, location
, make
, model
, icon
,
717 formats
, ppm
, ppm_color
, duplex
, port
, pin
,
718 subtype
, directory
, command
, attrfile
)) == NULL
)
722 * Run the print service...
725 run_printer(printer
);
728 * Destroy the printer and exit...
731 delete_printer(printer
);
738 * 'clean_jobs()' - Clean out old (completed) jobs.
742 clean_jobs(_ipp_printer_t
*printer
) /* I - Printer */
744 _ipp_job_t
*job
; /* Current job */
745 time_t cleantime
; /* Clean time */
748 if (cupsArrayCount(printer
->jobs
) == 0)
751 cleantime
= time(NULL
) - 60;
753 _cupsRWLockWrite(&(printer
->rwlock
));
754 for (job
= (_ipp_job_t
*)cupsArrayFirst(printer
->jobs
);
756 job
= (_ipp_job_t
*)cupsArrayNext(printer
->jobs
))
757 if (job
->completed
&& job
->completed
< cleantime
)
759 cupsArrayRemove(printer
->jobs
, job
);
764 _cupsRWUnlock(&(printer
->rwlock
));
769 * 'compare_jobs()' - Compare two jobs.
772 static int /* O - Result of comparison */
773 compare_jobs(_ipp_job_t
*a
, /* I - First job */
774 _ipp_job_t
*b
) /* I - Second job */
776 return (b
->id
- a
->id
);
781 * 'copy_attributes()' - Copy attributes from one request to another.
785 copy_attributes(ipp_t
*to
, /* I - Destination request */
786 ipp_t
*from
, /* I - Source request */
787 cups_array_t
*ra
, /* I - Requested attributes */
788 ipp_tag_t group_tag
, /* I - Group to copy */
789 int quickcopy
) /* I - Do a quick copy? */
791 _ipp_filter_t filter
; /* Filter data */
795 filter
.group_tag
= group_tag
;
797 ippCopyAttributes(to
, from
, quickcopy
, (ipp_copycb_t
)filter_cb
, &filter
);
802 * 'copy_job_attrs()' - Copy job attributes to the response.
807 _ipp_client_t
*client
, /* I - Client */
808 _ipp_job_t
*job
, /* I - Job */
809 cups_array_t
*ra
) /* I - requested-attributes */
811 copy_attributes(client
->response
, job
->attrs
, ra
, IPP_TAG_JOB
, 0);
813 if (!ra
|| cupsArrayFind(ra
, "date-time-at-completed"))
816 ippAddDate(client
->response
, IPP_TAG_JOB
, "date-time-at-completed", ippTimeToDate(job
->completed
));
818 ippAddOutOfBand(client
->response
, IPP_TAG_JOB
, IPP_TAG_NOVALUE
, "date-time-at-completed");
821 if (!ra
|| cupsArrayFind(ra
, "date-time-at-processing"))
824 ippAddDate(client
->response
, IPP_TAG_JOB
, "date-time-at-processing", ippTimeToDate(job
->processing
));
826 ippAddOutOfBand(client
->response
, IPP_TAG_JOB
, IPP_TAG_NOVALUE
, "date-time-at-processing");
829 if (!ra
|| cupsArrayFind(ra
, "job-impressions"))
830 ippAddInteger(client
->response
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "job-impressions", job
->impressions
);
832 if (!ra
|| cupsArrayFind(ra
, "job-impressions-completed"))
833 ippAddInteger(client
->response
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "job-impressions-completed", job
->impcompleted
);
835 if (!ra
|| cupsArrayFind(ra
, "job-printer-up-time"))
836 ippAddInteger(client
->response
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "job-printer-up-time", (int)(time(NULL
) - client
->printer
->start_time
));
838 if (!ra
|| cupsArrayFind(ra
, "job-state"))
839 ippAddInteger(client
->response
, IPP_TAG_JOB
, IPP_TAG_ENUM
,
840 "job-state", job
->state
);
842 if (!ra
|| cupsArrayFind(ra
, "job-state-message"))
846 case IPP_JSTATE_PENDING
:
847 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job pending.");
850 case IPP_JSTATE_HELD
:
852 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job incoming.");
853 else if (ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_ZERO
))
854 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job held.");
856 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job created.");
859 case IPP_JSTATE_PROCESSING
:
861 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job canceling.");
863 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job printing.");
866 case IPP_JSTATE_STOPPED
:
867 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job stopped.");
870 case IPP_JSTATE_CANCELED
:
871 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job canceled.");
874 case IPP_JSTATE_ABORTED
:
875 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job aborted.");
878 case IPP_JSTATE_COMPLETED
:
879 ippAddString(client
->response
, IPP_TAG_JOB
, IPP_CONST_TAG(IPP_TAG_TEXT
), "job-state-message", NULL
, "Job completed.");
884 if (!ra
|| cupsArrayFind(ra
, "job-state-reasons"))
888 case IPP_JSTATE_PENDING
:
889 ippAddString(client
->response
, IPP_TAG_JOB
,
890 IPP_CONST_TAG(IPP_TAG_KEYWORD
), "job-state-reasons",
894 case IPP_JSTATE_HELD
:
896 ippAddString(client
->response
, IPP_TAG_JOB
,
897 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
898 "job-state-reasons", NULL
, "job-incoming");
899 else if (ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_ZERO
))
900 ippAddString(client
->response
, IPP_TAG_JOB
,
901 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
902 "job-state-reasons", NULL
, "job-hold-until-specified");
904 ippAddString(client
->response
, IPP_TAG_JOB
,
905 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
906 "job-state-reasons", NULL
, "job-data-insufficient");
909 case IPP_JSTATE_PROCESSING
:
911 ippAddString(client
->response
, IPP_TAG_JOB
,
912 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
913 "job-state-reasons", NULL
, "processing-to-stop-point");
915 ippAddString(client
->response
, IPP_TAG_JOB
,
916 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
917 "job-state-reasons", NULL
, "job-printing");
920 case IPP_JSTATE_STOPPED
:
921 ippAddString(client
->response
, IPP_TAG_JOB
,
922 IPP_CONST_TAG(IPP_TAG_KEYWORD
), "job-state-reasons",
923 NULL
, "job-stopped");
926 case IPP_JSTATE_CANCELED
:
927 ippAddString(client
->response
, IPP_TAG_JOB
,
928 IPP_CONST_TAG(IPP_TAG_KEYWORD
), "job-state-reasons",
929 NULL
, "job-canceled-by-user");
932 case IPP_JSTATE_ABORTED
:
933 ippAddString(client
->response
, IPP_TAG_JOB
,
934 IPP_CONST_TAG(IPP_TAG_KEYWORD
), "job-state-reasons",
935 NULL
, "aborted-by-system");
938 case IPP_JSTATE_COMPLETED
:
939 ippAddString(client
->response
, IPP_TAG_JOB
,
940 IPP_CONST_TAG(IPP_TAG_KEYWORD
), "job-state-reasons",
941 NULL
, "job-completed-successfully");
946 if (!ra
|| cupsArrayFind(ra
, "time-at-completed"))
947 ippAddInteger(client
->response
, IPP_TAG_JOB
,
948 job
->completed
? IPP_TAG_INTEGER
: IPP_TAG_NOVALUE
,
949 "time-at-completed", (int)(job
->completed
- client
->printer
->start_time
));
951 if (!ra
|| cupsArrayFind(ra
, "time-at-processing"))
952 ippAddInteger(client
->response
, IPP_TAG_JOB
,
953 job
->processing
? IPP_TAG_INTEGER
: IPP_TAG_NOVALUE
,
954 "time-at-processing", (int)(job
->processing
- client
->printer
->start_time
));
959 * 'create_client()' - Accept a new network connection and create a client
963 static _ipp_client_t
* /* O - Client */
964 create_client(_ipp_printer_t
*printer
, /* I - Printer */
965 int sock
) /* I - Listen socket */
967 _ipp_client_t
*client
; /* Client */
970 if ((client
= calloc(1, sizeof(_ipp_client_t
))) == NULL
)
972 perror("Unable to allocate memory for client");
976 client
->printer
= printer
;
979 * Accept the client and get the remote address...
982 if ((client
->http
= httpAcceptConnection(sock
, 1)) == NULL
)
984 perror("Unable to accept client connection");
991 httpGetHostname(client
->http
, client
->hostname
, sizeof(client
->hostname
));
994 fprintf(stderr
, "Accepted connection from %s\n", client
->hostname
);
1001 * 'create_job()' - Create a new job object from a Print-Job or Create-Job
1005 static _ipp_job_t
* /* O - Job */
1006 create_job(_ipp_client_t
*client
) /* I - Client */
1008 _ipp_job_t
*job
; /* Job */
1009 ipp_attribute_t
*attr
; /* Job attribute */
1010 char uri
[1024], /* job-uri value */
1011 uuid
[64]; /* job-uuid value */
1014 _cupsRWLockWrite(&(client
->printer
->rwlock
));
1015 if (client
->printer
->active_job
&&
1016 client
->printer
->active_job
->state
< IPP_JSTATE_CANCELED
)
1019 * Only accept a single job at a time...
1022 _cupsRWLockWrite(&(client
->printer
->rwlock
));
1027 * Allocate and initialize the job object...
1030 if ((job
= calloc(1, sizeof(_ipp_job_t
))) == NULL
)
1032 perror("Unable to allocate memory for job");
1036 job
->printer
= client
->printer
;
1037 job
->attrs
= ippNew();
1038 job
->state
= IPP_JSTATE_HELD
;
1042 * Copy all of the job attributes...
1045 copy_attributes(job
->attrs
, client
->request
, NULL
, IPP_TAG_JOB
, 0);
1048 * Get the requesting-user-name, document format, and priority...
1051 if ((attr
= ippFindAttribute(client
->request
, "requesting-user-name", IPP_TAG_NAME
)) != NULL
)
1052 job
->username
= ippGetString(attr
, 0, NULL
);
1054 job
->username
= "anonymous";
1056 ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_NAME
, "job-originating-user-name", NULL
, job
->username
);
1058 if (ippGetOperation(client
->request
) != IPP_OP_CREATE_JOB
)
1060 if ((attr
= ippFindAttribute(job
->attrs
, "document-format-detected", IPP_TAG_MIMETYPE
)) != NULL
)
1061 job
->format
= ippGetString(attr
, 0, NULL
);
1062 else if ((attr
= ippFindAttribute(job
->attrs
, "document-format-supplied", IPP_TAG_MIMETYPE
)) != NULL
)
1063 job
->format
= ippGetString(attr
, 0, NULL
);
1065 job
->format
= "application/octet-stream";
1068 if ((attr
= ippFindAttribute(client
->request
, "job-impressions", IPP_TAG_INTEGER
)) != NULL
)
1069 job
->impressions
= ippGetInteger(attr
, 0);
1071 if ((attr
= ippFindAttribute(client
->request
, "job-name", IPP_TAG_NAME
)) != NULL
)
1072 job
->name
= ippGetString(attr
, 0, NULL
);
1075 * Add job description attributes and add to the jobs array...
1078 job
->id
= client
->printer
->next_job_id
++;
1080 snprintf(uri
, sizeof(uri
), "%s/%d", client
->printer
->uri
, job
->id
);
1081 httpAssembleUUID(client
->printer
->hostname
, client
->printer
->port
, client
->printer
->name
, job
->id
, uuid
, sizeof(uuid
));
1083 ippAddDate(job
->attrs
, IPP_TAG_JOB
, "date-time-at-creation", ippTimeToDate(time(&job
->created
)));
1084 ippAddInteger(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "job-id", job
->id
);
1085 ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_URI
, "job-uri", NULL
, uri
);
1086 ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_URI
, "job-uuid", NULL
, uuid
);
1087 if ((attr
= ippFindAttribute(client
->request
, "printer-uri", IPP_TAG_URI
)) != NULL
)
1088 ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_URI
, "job-printer-uri", NULL
, ippGetString(attr
, 0, NULL
));
1090 ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_URI
, "job-printer-uri", NULL
, client
->printer
->uri
);
1091 ippAddInteger(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "time-at-creation", (int)(job
->created
- client
->printer
->start_time
));
1093 cupsArrayAdd(client
->printer
->jobs
, job
);
1094 client
->printer
->active_job
= job
;
1096 _cupsRWUnlock(&(client
->printer
->rwlock
));
1103 * 'create_job_filename()' - Create the filename for a document in a job.
1106 static void create_job_filename(
1107 _ipp_printer_t
*printer
, /* I - Printer */
1108 _ipp_job_t
*job
, /* I - Job */
1109 char *fname
, /* I - Filename buffer */
1110 size_t fnamesize
) /* I - Size of filename buffer */
1112 char name
[256], /* "Safe" filename */
1113 *nameptr
; /* Pointer into filename */
1114 const char *ext
, /* Filename extension */
1115 *job_name
; /* job-name value */
1116 ipp_attribute_t
*job_name_attr
; /* job-name attribute */
1120 * Make a name from the job-name attribute...
1123 if ((job_name_attr
= ippFindAttribute(job
->attrs
, "job-name", IPP_TAG_NAME
)) != NULL
)
1124 job_name
= ippGetString(job_name_attr
, 0, NULL
);
1126 job_name
= "untitled";
1128 for (nameptr
= name
; *job_name
&& nameptr
< (name
+ sizeof(name
) - 1); job_name
++)
1129 if (isalnum(*job_name
& 255) || *job_name
== '-')
1130 *nameptr
++ = (char)tolower(*job_name
& 255);
1137 * Figure out the extension...
1140 if (!strcasecmp(job
->format
, "image/jpeg"))
1142 else if (!strcasecmp(job
->format
, "image/png"))
1144 else if (!strcasecmp(job
->format
, "image/pwg-raster"))
1146 else if (!strcasecmp(job
->format
, "image/urf"))
1148 else if (!strcasecmp(job
->format
, "application/pdf"))
1150 else if (!strcasecmp(job
->format
, "application/postscript"))
1156 * Create a filename with the job-id, job-name, and document-format (extension)...
1159 snprintf(fname
, fnamesize
, "%s/%d-%s.%s", printer
->directory
, job
->id
, name
, ext
);
1164 * 'create_listener()' - Create a listener socket.
1167 static int /* O - Listener socket or -1 on error */
1168 create_listener(int family
, /* I - Address family */
1169 int port
) /* I - Port number */
1171 int sock
; /* Listener socket */
1172 http_addrlist_t
*addrlist
; /* Listen address */
1173 char service
[255]; /* Service port */
1176 snprintf(service
, sizeof(service
), "%d", port
);
1177 if ((addrlist
= httpAddrGetList(NULL
, family
, service
)) == NULL
)
1180 sock
= httpAddrListen(&(addrlist
->addr
), port
);
1182 httpAddrFreeList(addrlist
);
1189 * 'create_media_col()' - Create a media-col value.
1192 static ipp_t
* /* O - media-col collection */
1193 create_media_col(const char *media
, /* I - Media name */
1194 const char *source
, /* I - Media source */
1195 const char *type
, /* I - Media type */
1196 int width
, /* I - x-dimension in 2540ths */
1197 int length
, /* I - y-dimension in 2540ths */
1198 int margins
) /* I - Value for margins */
1200 ipp_t
*media_col
= ippNew(), /* media-col value */
1201 *media_size
= create_media_size(width
, length
);
1202 /* media-size value */
1203 char media_key
[256]; /* media-key value */
1207 snprintf(media_key
, sizeof(media_key
), "%s_%s_%s%s", media
, source
, type
, margins
== 0 ? "_borderless" : "");
1209 snprintf(media_key
, sizeof(media_key
), "%s__%s%s", media
, type
, margins
== 0 ? "_borderless" : "");
1211 snprintf(media_key
, sizeof(media_key
), "%s_%s%s", media
, source
, margins
== 0 ? "_borderless" : "");
1213 snprintf(media_key
, sizeof(media_key
), "%s%s", media
, margins
== 0 ? "_borderless" : "");
1215 ippAddString(media_col
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "media-key", NULL
,
1217 ippAddCollection(media_col
, IPP_TAG_PRINTER
, "media-size", media_size
);
1218 ippAddString(media_col
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "media-size-name", NULL
, media
);
1219 ippAddInteger(media_col
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1220 "media-bottom-margin", margins
);
1221 ippAddInteger(media_col
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1222 "media-left-margin", margins
);
1223 ippAddInteger(media_col
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1224 "media-right-margin", margins
);
1225 ippAddInteger(media_col
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1226 "media-top-margin", margins
);
1228 ippAddString(media_col
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "media-source", NULL
, source
);
1230 ippAddString(media_col
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "media-type", NULL
, type
);
1232 ippDelete(media_size
);
1239 * 'create_media_size()' - Create a media-size value.
1242 static ipp_t
* /* O - media-col collection */
1243 create_media_size(int width
, /* I - x-dimension in 2540ths */
1244 int length
) /* I - y-dimension in 2540ths */
1246 ipp_t
*media_size
= ippNew(); /* media-size value */
1249 ippAddInteger(media_size
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "x-dimension",
1251 ippAddInteger(media_size
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "y-dimension",
1254 return (media_size
);
1259 * 'create_printer()' - Create, register, and listen for connections to a
1263 static _ipp_printer_t
* /* O - Printer */
1264 create_printer(const char *servername
, /* I - Server hostname (NULL for default) */
1265 const char *name
, /* I - printer-name */
1266 const char *location
, /* I - printer-location */
1267 const char *make
, /* I - printer-make-and-model */
1268 const char *model
, /* I - printer-make-and-model */
1269 const char *icon
, /* I - printer-icons */
1270 const char *docformats
, /* I - document-format-supported */
1271 int ppm
, /* I - Pages per minute in grayscale */
1272 int ppm_color
, /* I - Pages per minute in color (0 for gray) */
1273 int duplex
, /* I - 1 = duplex, 0 = simplex */
1274 int port
, /* I - Port for listeners or 0 for auto */
1275 int pin
, /* I - Require PIN printing */
1276 const char *subtype
, /* I - Bonjour service subtype */
1277 const char *directory
, /* I - Spool directory */
1278 const char *command
, /* I - Command to run on job files */
1279 const char *attrfile
) /* I - Attributes file */
1281 int i
, j
; /* Looping vars */
1282 _ipp_printer_t
*printer
; /* Printer */
1284 char path
[1024]; /* Full path to command */
1286 char uri
[1024], /* Printer URI */
1288 securi
[1024], /* Secure printer URI */
1289 *uris
[2], /* All URIs */
1290 #endif /* HAVE_SSL */
1291 icons
[1024], /* printer-icons URI */
1292 adminurl
[1024], /* printer-more-info URI */
1293 supplyurl
[1024],/* printer-supply-info-uri URI */
1294 device_id
[1024],/* printer-device-id */
1295 make_model
[128],/* printer-make-and-model */
1296 uuid
[128]; /* printer-uuid */
1297 int num_formats
; /* Number of document-format-supported values */
1298 char *defformat
, /* document-format-default value */
1299 *formats
[100], /* document-format-supported values */
1300 *ptr
; /* Pointer into string */
1301 const char *prefix
; /* Prefix string */
1302 int num_database
; /* Number of database values */
1303 ipp_attribute_t
*media_col_database
,
1304 /* media-col-database value */
1305 *media_size_supported
;
1306 /* media-size-supported value */
1307 ipp_t
*media_col_default
;
1308 /* media-col-default value */
1309 int media_col_index
;/* Current media-col-database value */
1310 int k_supported
; /* Maximum file size supported */
1312 struct statvfs spoolinfo
; /* FS info for spool directory */
1313 double spoolsize
; /* FS size */
1314 #elif defined(HAVE_STATFS)
1315 struct statfs spoolinfo
; /* FS info for spool directory */
1316 double spoolsize
; /* FS size */
1317 #endif /* HAVE_STATVFS */
1318 static const int orients
[4] = /* orientation-requested-supported values */
1320 IPP_ORIENT_PORTRAIT
,
1321 IPP_ORIENT_LANDSCAPE
,
1322 IPP_ORIENT_REVERSE_LANDSCAPE
,
1323 IPP_ORIENT_REVERSE_PORTRAIT
1325 static const char * const versions
[] =/* ipp-versions-supported values */
1331 static const char * const features
[] =/* ipp-features-supported values */
1335 static const int ops
[] = /* operations-supported values */
1339 IPP_OP_VALIDATE_JOB
,
1341 IPP_OP_SEND_DOCUMENT
,
1344 IPP_OP_GET_JOB_ATTRIBUTES
,
1346 IPP_OP_GET_PRINTER_ATTRIBUTES
,
1347 IPP_OP_CANCEL_MY_JOBS
,
1349 IPP_OP_IDENTIFY_PRINTER
1351 static const char * const charsets
[] =/* charset-supported values */
1356 static const char * const compressions
[] =/* compression-supported values */
1361 #endif /* HAVE_LIBZ */
1364 static const char * const identify_actions
[] =
1369 static const char * const job_creation
[] =
1370 { /* job-creation-attributes-supported values */
1372 "ipp-attribute-fidelity",
1374 "job-accounting-user-id",
1380 "multiple-document-handling",
1381 "orientation-requested",
1385 static const char * const media_col_supported
[] =
1386 { /* media-col-supported values */
1387 "media-bottom-margin",
1388 "media-left-margin",
1389 "media-right-margin",
1395 static const int media_xxx_margin_supported
[] =
1396 { /* media-xxx-margin-supported values */
1400 static const char * const multiple_document_handling
[] =
1401 { /* multiple-document-handling-supported values */
1402 "separate-documents-uncollated-copies",
1403 "separate-documents-collated-copies"
1405 static const char * const overrides
[] =
1406 { /* overrides-supported */
1410 static const char * const print_color_mode_supported
[] =
1411 { /* print-color-mode-supported values */
1416 static const int print_quality_supported
[] =
1417 { /* print-quality-supported values */
1422 static const int pwg_raster_document_resolution_supported
[] =
1427 static const char * const pwg_raster_document_type_supported
[] =
1435 static const char * const reference_uri_schemes_supported
[] =
1436 { /* reference-uri-schemes-supported */
1442 #endif /* HAVE_SSL */
1444 static const char * const sides_supported
[] =
1445 { /* sides-supported values */
1447 "two-sided-long-edge",
1448 "two-sided-short-edge"
1450 static const char * const urf_supported
[] =
1451 { /* urf-supported values */
1454 "MT1-2-3-4-5-6-8-9-10-11-12-13",
1462 static const char * const uri_authentication_supported
[] =
1463 { /* uri-authentication-supported values */
1467 static const char * const uri_security_supported
[] =
1468 { /* uri-security-supported values */
1472 #endif /* HAVE_SSL */
1473 static const char * const which_jobs
[] =
1474 { /* which-jobs-supported values */
1483 "processing-stopped"
1489 * If a command was specified, make sure it exists and is executable...
1494 if (*command
== '/' || !strncmp(command
, "./", 2))
1496 if (access(command
, X_OK
))
1498 fprintf(stderr
, "ippserver: Unable to execute command \"%s\": %s\n", command
, strerror(errno
));
1504 if (!cupsFileFind(command
, getenv("PATH"), 1, path
, sizeof(path
)))
1506 fprintf(stderr
, "ippserver: Unable to find command \"%s\".\n", command
);
1516 * Allocate memory for the printer...
1519 if ((printer
= calloc(1, sizeof(_ipp_printer_t
))) == NULL
)
1521 perror("ippserver: Unable to allocate memory for printer");
1527 printer
->name
= strdup(name
);
1528 printer
->dnssd_name
= strdup(printer
->name
);
1529 printer
->command
= command
? strdup(command
) : NULL
;
1530 printer
->directory
= strdup(directory
);
1531 printer
->hostname
= strdup(servername
);
1532 printer
->port
= port
;
1533 printer
->start_time
= time(NULL
);
1534 printer
->config_time
= printer
->start_time
;
1535 printer
->state
= IPP_PSTATE_IDLE
;
1536 printer
->state_reasons
= _IPP_PREASON_NONE
;
1537 printer
->state_time
= printer
->start_time
;
1538 printer
->jobs
= cupsArrayNew((cups_array_func_t
)compare_jobs
, NULL
);
1539 printer
->next_job_id
= 1;
1541 httpAssembleURI(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), "ipp", NULL
, printer
->hostname
, printer
->port
, "/ipp/print");
1542 printer
->uri
= strdup(uri
);
1543 printer
->urilen
= strlen(uri
);
1546 httpAssembleURI(HTTP_URI_CODING_ALL
, securi
, sizeof(securi
), "ipps", NULL
, printer
->hostname
, printer
->port
, "/ipp/print");
1547 #endif /* HAVE_SSL */
1550 printer
->icon
= strdup(icon
);
1552 printer
->main_size
= _IPP_MEDIA_SIZE_A4
;
1553 printer
->main_type
= _IPP_MEDIA_TYPE_STATIONERY
;
1554 printer
->main_level
= 500;
1556 printer
->envelope_size
= _IPP_MEDIA_SIZE_NONE
;
1557 printer
->envelope_level
= 0;
1559 printer
->photo_size
= _IPP_MEDIA_SIZE_NONE
;
1560 printer
->photo_type
= _IPP_MEDIA_TYPE_NONE
;
1561 printer
->photo_level
= 0;
1563 printer
->supplies
[_IPP_SUPPLY_CYAN
] = 100;
1564 printer
->supplies
[_IPP_SUPPLY_MAGENTA
] = 100;
1565 printer
->supplies
[_IPP_SUPPLY_YELLOW
] = 100;
1566 printer
->supplies
[_IPP_SUPPLY_BLACK
] = 100;
1567 printer
->supplies
[_IPP_SUPPLY_WASTE
] = 0;
1569 _cupsRWInit(&(printer
->rwlock
));
1572 * Create the listener sockets...
1575 if ((printer
->ipv4
= create_listener(AF_INET
, printer
->port
)) < 0)
1577 perror("Unable to create IPv4 listener");
1581 if ((printer
->ipv6
= create_listener(AF_INET6
, printer
->port
)) < 0)
1583 perror("Unable to create IPv6 listener");
1588 * Prepare values for the printer attributes...
1591 httpAssembleURI(HTTP_URI_CODING_ALL
, icons
, sizeof(icons
), WEB_SCHEME
, NULL
, printer
->hostname
, printer
->port
, "/icon.png");
1592 httpAssembleURI(HTTP_URI_CODING_ALL
, adminurl
, sizeof(adminurl
), WEB_SCHEME
, NULL
, printer
->hostname
, printer
->port
, "/");
1593 httpAssembleURI(HTTP_URI_CODING_ALL
, supplyurl
, sizeof(supplyurl
), WEB_SCHEME
, NULL
, printer
->hostname
, printer
->port
, "/supplies");
1597 fprintf(stderr
, "printer-more-info=\"%s\"\n", adminurl
);
1598 fprintf(stderr
, "printer-supply-info-uri=\"%s\"\n", supplyurl
);
1599 fprintf(stderr
, "printer-uri=\"%s\"\n", uri
);
1602 snprintf(make_model
, sizeof(make_model
), "%s %s", make
, model
);
1605 formats
[0] = strdup(docformats
);
1606 defformat
= formats
[0];
1607 for (ptr
= strchr(formats
[0], ','); ptr
; ptr
= strchr(ptr
, ','))
1610 formats
[num_formats
++] = ptr
;
1612 if (!strcasecmp(ptr
, "application/octet-stream"))
1616 snprintf(device_id
, sizeof(device_id
), "MFG:%s;MDL:%s;", make
, model
);
1617 ptr
= device_id
+ strlen(device_id
);
1619 for (i
= 0; i
< num_formats
; i
++)
1621 if (!strcasecmp(formats
[i
], "application/pdf"))
1622 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%sPDF", prefix
);
1623 else if (!strcasecmp(formats
[i
], "application/postscript"))
1624 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%sPS", prefix
);
1625 else if (!strcasecmp(formats
[i
], "application/vnd.hp-PCL"))
1626 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%sPCL", prefix
);
1627 else if (!strcasecmp(formats
[i
], "image/jpeg"))
1628 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%sJPEG", prefix
);
1629 else if (!strcasecmp(formats
[i
], "image/png"))
1630 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%sPNG", prefix
);
1631 else if (strcasecmp(formats
[i
], "application/octet-stream"))
1632 snprintf(ptr
, sizeof(device_id
) - (size_t)(ptr
- device_id
), "%s%s", prefix
, formats
[i
]);
1637 if (ptr
< (device_id
+ sizeof(device_id
) - 1))
1644 * Get the maximum spool size based on the size of the filesystem used for
1645 * the spool directory. If the host OS doesn't support the statfs call
1646 * or the filesystem is larger than 2TiB, always report INT_MAX.
1650 if (statvfs(printer
->directory
, &spoolinfo
))
1651 k_supported
= INT_MAX
;
1652 else if ((spoolsize
= (double)spoolinfo
.f_frsize
*
1653 spoolinfo
.f_blocks
/ 1024) > INT_MAX
)
1654 k_supported
= INT_MAX
;
1656 k_supported
= (int)spoolsize
;
1658 #elif defined(HAVE_STATFS)
1659 if (statfs(printer
->directory
, &spoolinfo
))
1660 k_supported
= INT_MAX
;
1661 else if ((spoolsize
= (double)spoolinfo
.f_bsize
*
1662 spoolinfo
.f_blocks
/ 1024) > INT_MAX
)
1663 k_supported
= INT_MAX
;
1665 k_supported
= (int)spoolsize
;
1668 k_supported
= INT_MAX
;
1669 #endif /* HAVE_STATVFS */
1672 * Create the printer attributes. This list of attributes is sorted to improve
1673 * performance when the client provides a requested-attributes attribute...
1676 printer
->attrs
= ippNew();
1679 load_attributes(attrfile
, printer
->attrs
);
1681 /* charset-configured */
1682 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_CHARSET
), "charset-configured", NULL
, "utf-8");
1684 /* charset-supported */
1685 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_CHARSET
), "charset-supported", sizeof(charsets
) / sizeof(charsets
[0]), NULL
, charsets
);
1687 /* color-supported */
1688 if (!ippFindAttribute(printer
->attrs
, "color-supported", IPP_TAG_ZERO
))
1689 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "color-supported", ppm_color
> 0);
1691 /* compression-supported */
1692 if (!ippFindAttribute(printer
->attrs
, "compression-supported", IPP_TAG_ZERO
))
1693 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "compression-supported", (int)(sizeof(compressions
) / sizeof(compressions
[0])), NULL
, compressions
);
1695 /* copies-default */
1696 if (!ippFindAttribute(printer
->attrs
, "copies-default", IPP_TAG_ZERO
))
1697 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "copies-default", 1);
1699 /* copies-supported */
1700 if (!ippFindAttribute(printer
->attrs
, "copies-supported", IPP_TAG_ZERO
))
1701 ippAddRange(printer
->attrs
, IPP_TAG_PRINTER
, "copies-supported", 1, 999);
1703 /* document-format-default */
1704 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_MIMETYPE
,
1705 "document-format-default", NULL
, defformat
);
1707 /* document-format-supported */
1708 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_MIMETYPE
,
1709 "document-format-supported", num_formats
, NULL
,
1710 (const char * const *)formats
);
1712 /* document-password-supported */
1713 if (!ippFindAttribute(printer
->attrs
, "document-password-supported", IPP_TAG_ZERO
))
1714 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "document-password-supported", 127);
1716 /* finishings-default */
1717 if (!ippFindAttribute(printer
->attrs
, "finishings-default", IPP_TAG_ZERO
))
1718 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
, "finishings-default", IPP_FINISHINGS_NONE
);
1720 /* finishings-supported */
1721 if (!ippFindAttribute(printer
->attrs
, "finishings-supported", IPP_TAG_ZERO
))
1722 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
, "finishings-supported", IPP_FINISHINGS_NONE
);
1724 /* generated-natural-language-supported */
1725 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_LANGUAGE
), "generated-natural-language-supported", NULL
, "en");
1727 /* identify-actions-default */
1728 if (!ippFindAttribute(printer
->attrs
, "identify-actions-default", IPP_TAG_ZERO
))
1729 ippAddString (printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "identify-actions-default", NULL
, "sound");
1731 /* identify-actions-supported */
1732 if (!ippFindAttribute(printer
->attrs
, "identify-actions-supported", IPP_TAG_ZERO
))
1733 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
);
1735 /* ipp-features-supported */
1736 if (!ippFindAttribute(printer
->attrs
, "ipp-features-supported", IPP_TAG_ZERO
))
1737 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "ipp-features-supported", sizeof(features
) / sizeof(features
[0]), NULL
, features
);
1739 /* ipp-versions-supported */
1740 if (!ippFindAttribute(printer
->attrs
, "ipp-versions-supported", IPP_TAG_ZERO
))
1741 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "ipp-versions-supported", sizeof(versions
) / sizeof(versions
[0]), NULL
, versions
);
1743 /* job-account-id-default */
1744 if (!ippFindAttribute(printer
->attrs
, "job-account-id-default", IPP_TAG_ZERO
))
1745 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_NAME
), "job-account-id-default", NULL
, "");
1747 /* job-account-id-supported */
1748 if (!ippFindAttribute(printer
->attrs
, "job-account-id-supported", IPP_TAG_ZERO
))
1749 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "job-account-id-supported", 1);
1751 /* job-accounting-user-id-default */
1752 if (!ippFindAttribute(printer
->attrs
, "job-accounting-user-id-default", IPP_TAG_ZERO
))
1753 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_NAME
), "job-accounting-user-id-default", NULL
, "");
1755 /* job-accounting-user-id-supported */
1756 if (!ippFindAttribute(printer
->attrs
, "job-accounting-user-id-supported", IPP_TAG_ZERO
))
1757 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "job-accounting-user-id-supported", 1);
1759 /* job-creation-attributes-supported */
1760 if (!ippFindAttribute(printer
->attrs
, "job-creation-attributes-supported", IPP_TAG_ZERO
))
1761 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
);
1763 /* job-ids-supported */
1764 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "job-ids-supported", 1);
1766 /* job-k-octets-supported */
1767 ippAddRange(printer
->attrs
, IPP_TAG_PRINTER
, "job-k-octets-supported", 0,
1770 /* job-password-supported */
1771 if (!ippFindAttribute(printer
->attrs
, "job-password-supported", IPP_TAG_ZERO
))
1772 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "job-password-supported", 4);
1774 /* job-priority-default */
1775 if (!ippFindAttribute(printer
->attrs
, "job-priority-default", IPP_TAG_ZERO
))
1776 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "job-priority-default", 50);
1778 /* job-priority-supported */
1779 if (!ippFindAttribute(printer
->attrs
, "job-priority-supported", IPP_TAG_ZERO
))
1780 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "job-priority-supported", 100);
1782 /* job-sheets-default */
1783 if (!ippFindAttribute(printer
->attrs
, "job-sheets-default", IPP_TAG_ZERO
))
1784 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_NAME
), "job-sheets-default", NULL
, "none");
1786 /* job-sheets-supported */
1787 if (!ippFindAttribute(printer
->attrs
, "job-sheets-supported", IPP_TAG_ZERO
))
1788 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_NAME
), "job-sheets-supported", NULL
, "none");
1790 /* media-bottom-margin-supported */
1791 if (!ippFindAttribute(printer
->attrs
, "media-bottom-margin-supported", IPP_TAG_ZERO
))
1792 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
);
1794 /* media-col-database */
1795 if (!ippFindAttribute(printer
->attrs
, "media-col-database", IPP_TAG_ZERO
))
1797 for (num_database
= 0, i
= 0;
1798 i
< (int)(sizeof(media_col_sizes
) / sizeof(media_col_sizes
[0]));
1801 if (media_col_sizes
[i
][2] == _IPP_ENV_ONLY
)
1802 num_database
+= 3; /* auto + manual + envelope */
1803 else if (media_col_sizes
[i
][2] == _IPP_PHOTO_ONLY
)
1804 num_database
+= 6 * 3; /* auto + photographic-* from auto, manual, and photo */
1806 num_database
+= 2; /* Regular + borderless */
1809 media_col_database
= ippAddCollections(printer
->attrs
, IPP_TAG_PRINTER
, "media-col-database", num_database
, NULL
);
1810 for (media_col_index
= 0, i
= 0;
1811 i
< (int)(sizeof(media_col_sizes
) / sizeof(media_col_sizes
[0]));
1814 switch (media_col_sizes
[i
][2])
1818 * Regular + borderless for the general class; no source/type
1822 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]));
1823 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]));
1826 case _IPP_ENV_ONLY
:
1828 * Regular margins for "auto", "manual", and "envelope" sources.
1831 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]));
1832 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]));
1833 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]));
1835 case _IPP_PHOTO_ONLY
:
1837 * Photos have specific media types and can only be printed via
1838 * the auto, manual, and photo sources...
1842 j
< (int)(sizeof(media_type_supported
) /
1843 sizeof(media_type_supported
[0]));
1846 if (strcmp(media_type_supported
[j
], "auto") && strncmp(media_type_supported
[j
], "photographic-", 13))
1849 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]));
1850 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]));
1851 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]));
1858 /* media-col-default */
1859 if (!ippFindAttribute(printer
->attrs
, "media-col-default", IPP_TAG_ZERO
))
1861 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]);
1863 ippAddCollection(printer
->attrs
, IPP_TAG_PRINTER
, "media-col-default",
1865 ippDelete(media_col_default
);
1868 /* media-col-supported */
1869 if (!ippFindAttribute(printer
->attrs
, "media-col-supported", IPP_TAG_ZERO
))
1870 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
);
1873 if (!ippFindAttribute(printer
->attrs
, "media-default", IPP_TAG_ZERO
))
1874 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "media-default", NULL
, media_supported
[0]);
1876 /* media-left-margin-supported */
1877 if (!ippFindAttribute(printer
->attrs
, "media-left-margin-supported", IPP_TAG_ZERO
))
1878 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
);
1880 /* media-right-margin-supported */
1881 if (!ippFindAttribute(printer
->attrs
, "media-right-margin-supported", IPP_TAG_ZERO
))
1882 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
);
1884 /* media-supported */
1885 if (!ippFindAttribute(printer
->attrs
, "media-supported", IPP_TAG_ZERO
))
1886 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
);
1888 /* media-size-supported */
1889 if (!ippFindAttribute(printer
->attrs
, "media-size-supported", IPP_TAG_ZERO
))
1891 media_size_supported
= ippAddCollections(printer
->attrs
, IPP_TAG_PRINTER
, "media-size-supported", (int)(sizeof(media_col_sizes
) / sizeof(media_col_sizes
[0])), NULL
);
1894 i
< (int)(sizeof(media_col_sizes
) / sizeof(media_col_sizes
[0]));
1897 ipp_t
*size
= create_media_size(media_col_sizes
[i
][0], media_col_sizes
[i
][1]);
1899 ippSetCollection(printer
->attrs
, &media_size_supported
, i
, size
);
1904 /* media-source-supported */
1905 if (!ippFindAttribute(printer
->attrs
, "media-source-supported", IPP_TAG_ZERO
))
1906 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
);
1908 /* media-top-margin-supported */
1909 if (!ippFindAttribute(printer
->attrs
, "media-top-margin-supported", IPP_TAG_ZERO
))
1910 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
);
1912 /* media-type-supported */
1913 if (!ippFindAttribute(printer
->attrs
, "media-type-supported", IPP_TAG_ZERO
))
1914 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
);
1916 /* multiple-document-handling-supported */
1917 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
);
1919 /* multiple-document-jobs-supported */
1920 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "multiple-document-jobs-supported", 0);
1922 /* multiple-operation-time-out */
1923 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "multiple-operation-time-out", 60);
1925 /* multiple-operation-time-out-action */
1926 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "multiple-operation-time-out-action", NULL
, "abort-job");
1928 /* natural-language-configured */
1929 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
,
1930 IPP_CONST_TAG(IPP_TAG_LANGUAGE
),
1931 "natural-language-configured", NULL
, "en");
1933 /* number-up-default */
1934 if (!ippFindAttribute(printer
->attrs
, "number-up-default", IPP_TAG_ZERO
))
1935 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "number-up-default", 1);
1937 /* number-up-supported */
1938 if (!ippFindAttribute(printer
->attrs
, "number-up-supported", IPP_TAG_ZERO
))
1939 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "number-up-supported", 1);
1941 /* operations-supported */
1942 ippAddIntegers(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
, "operations-supported", sizeof(ops
) / sizeof(ops
[0]), ops
);
1944 /* orientation-requested-default */
1945 if (!ippFindAttribute(printer
->attrs
, "orientation-requested-default", IPP_TAG_ZERO
))
1946 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_NOVALUE
, "orientation-requested-default", 0);
1948 /* orientation-requested-supported */
1949 if (!ippFindAttribute(printer
->attrs
, "orientation-requested-supported", IPP_TAG_ZERO
))
1950 ippAddIntegers(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
, "orientation-requested-supported", 4, orients
);
1952 /* output-bin-default */
1953 if (!ippFindAttribute(printer
->attrs
, "output-bin-default", IPP_TAG_ZERO
))
1954 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "output-bin-default", NULL
, "face-down");
1956 /* output-bin-supported */
1957 if (!ippFindAttribute(printer
->attrs
, "output-bin-supported", IPP_TAG_ZERO
))
1958 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "output-bin-supported", NULL
, "face-down");
1960 /* overrides-supported */
1961 if (!ippFindAttribute(printer
->attrs
, "overrides-supported", IPP_TAG_ZERO
))
1962 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "overrides-supported", (int)(sizeof(overrides
) / sizeof(overrides
[0])), NULL
, overrides
);
1964 /* page-ranges-supported */
1965 if (!ippFindAttribute(printer
->attrs
, "page-ranges-supported", IPP_TAG_ZERO
))
1966 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "page-ranges-supported", 1);
1968 /* pages-per-minute */
1969 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1970 "pages-per-minute", ppm
);
1972 /* pages-per-minute-color */
1974 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
1975 "pages-per-minute-color", ppm_color
);
1977 /* pdl-override-supported */
1978 if (!ippFindAttribute(printer
->attrs
, "pdl-override-supported", IPP_TAG_ZERO
))
1979 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "pdl-override-supported", NULL
, "attempted");
1981 /* preferred-attributes-supported */
1982 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "preferred-attributes-supported", 0);
1984 /* print-color-mode-default */
1985 if (!ippFindAttribute(printer
->attrs
, "print-color-mode-default", IPP_TAG_ZERO
))
1986 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "print-color-mode-default", NULL
, "auto");
1988 /* print-color-mode-supported */
1989 if (!ippFindAttribute(printer
->attrs
, "print-color-mode-supported", IPP_TAG_ZERO
))
1990 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
);
1992 /* print-content-optimize-default */
1993 if (!ippFindAttribute(printer
->attrs
, "print-content-optimize-default", IPP_TAG_ZERO
))
1994 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "print-content-optimize-default", NULL
, "auto");
1996 /* print-content-optimize-supported */
1997 if (!ippFindAttribute(printer
->attrs
, "print-content-optimize-supported", IPP_TAG_ZERO
))
1998 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "print-content-optimize-supported", NULL
, "auto");
2000 /* print-rendering-intent-default */
2001 if (!ippFindAttribute(printer
->attrs
, "print-rendering-intent-default", IPP_TAG_ZERO
))
2002 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "print-rendering-intent-default", NULL
, "auto");
2004 /* print-rendering-intent-supported */
2005 if (!ippFindAttribute(printer
->attrs
, "print-rendering-intent-supported", IPP_TAG_ZERO
))
2006 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "print-rendering-intent-supported", NULL
, "auto");
2008 /* print-quality-default */
2009 if (!ippFindAttribute(printer
->attrs
, "print-quality-default", IPP_TAG_ZERO
))
2010 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
, "print-quality-default", IPP_QUALITY_NORMAL
);
2012 /* print-quality-supported */
2013 if (!ippFindAttribute(printer
->attrs
, "print-quality-supported", IPP_TAG_ZERO
))
2014 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
);
2016 /* printer-device-id */
2017 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
,
2018 "printer-device-id", NULL
, device_id
);
2020 /* printer-get-attributes-supported */
2021 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "printer-get-attributes-supported", NULL
, "document-format");
2023 /* printer-geo-location */
2024 if (!ippFindAttribute(printer
->attrs
, "printer-geo-location", IPP_TAG_ZERO
))
2025 ippAddInteger(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_UNKNOWN
, "printer-geo-location", 0);
2028 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
,
2029 "printer-icons", NULL
, icons
);
2031 /* printer-is-accepting-jobs */
2032 ippAddBoolean(printer
->attrs
, IPP_TAG_PRINTER
, "printer-is-accepting-jobs", 1);
2035 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
, "printer-info", NULL
, name
);
2037 /* printer-location */
2038 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
,
2039 "printer-location", NULL
, location
);
2041 /* printer-make-and-model */
2042 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
,
2043 "printer-make-and-model", NULL
, make_model
);
2045 /* printer-mandatory-job-attributes */
2046 if (pin
&& !ippFindAttribute(printer
->attrs
, "", IPP_TAG_ZERO
))
2048 static const char * const names
[] =
2051 "job-accounting-user-id",
2055 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
,
2056 "printer-mandatory-job-attributes",
2057 (int)(sizeof(names
) / sizeof(names
[0])), NULL
, names
);
2060 /* printer-more-info */
2061 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
, "printer-more-info", NULL
, adminurl
);
2064 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_NAME
, "printer-name", NULL
, name
);
2066 /* printer-organization */
2067 if (!ippFindAttribute(printer
->attrs
, "printer-organization", IPP_TAG_ZERO
))
2068 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_TEXT
), "printer-organization", NULL
, "Apple Inc.");
2070 /* printer-organizational-unit */
2071 if (!ippFindAttribute(printer
->attrs
, "printer-organizational-unit", IPP_TAG_ZERO
))
2072 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_TEXT
), "printer-organizational-unit", NULL
, "Printing Engineering");
2074 /* printer-resolution-default */
2075 if (!ippFindAttribute(printer
->attrs
, "printer-resolution-default", IPP_TAG_ZERO
))
2076 ippAddResolution(printer
->attrs
, IPP_TAG_PRINTER
, "printer-resolution-default", IPP_RES_PER_INCH
, 600, 600);
2078 /* printer-resolution-supported */
2079 if (!ippFindAttribute(printer
->attrs
, "printer-resolutions-supported", IPP_TAG_ZERO
))
2080 ippAddResolution(printer
->attrs
, IPP_TAG_PRINTER
, "printer-resolution-supported", IPP_RES_PER_INCH
, 600, 600);
2082 /* printer-supply-description */
2083 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
);
2085 /* printer-supply-info-uri */
2086 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
, "printer-supply-info-uri", NULL
, supplyurl
);
2088 /* printer-uri-supported */
2093 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
, "printer-uri-supported", 2, NULL
, (const char **)uris
);
2096 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
, "printer-uri-supported", NULL
, uri
);
2097 #endif /* HAVE_SSL */
2100 httpAssembleUUID(printer
->hostname
, port
, name
, 0, uuid
, sizeof(uuid
));
2101 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_URI
, "printer-uuid", NULL
, uuid
);
2103 /* pwg-raster-document-xxx-supported */
2104 for (i
= 0; i
< num_formats
; i
++)
2105 if (!strcasecmp(formats
[i
], "image/pwg-raster"))
2108 if (i
< num_formats
)
2110 if (!ippFindAttribute(printer
->attrs
, "pwg-raster-document-resolution-supported", IPP_TAG_ZERO
))
2111 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
);
2112 if (!ippFindAttribute(printer
->attrs
, "pwg-raster-document-sheet-back", IPP_TAG_ZERO
))
2113 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "pwg-raster-document-sheet-back", NULL
, "normal");
2114 if (!ippFindAttribute(printer
->attrs
, "pwg-raster-document-type-supported", IPP_TAG_ZERO
))
2115 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
);
2118 /* reference-uri-scheme-supported */
2119 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
);
2122 if (!ippFindAttribute(printer
->attrs
, "sides-default", IPP_TAG_ZERO
))
2123 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "sides-default", NULL
, "one-sided");
2125 /* sides-supported */
2126 if (!ippFindAttribute(printer
->attrs
, "sides-supported", IPP_TAG_ZERO
))
2127 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "sides-supported", duplex
? 3 : 1, NULL
, sides_supported
);
2130 for (i
= 0; i
< num_formats
; i
++)
2131 if (!strcasecmp(formats
[i
], "image/urf"))
2134 if (i
< num_formats
&& !ippFindAttribute(printer
->attrs
, "urf-supported", IPP_TAG_ZERO
))
2135 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "urf-supported", (int)(sizeof(urf_supported
) / sizeof(urf_supported
[0])) - !duplex
, NULL
, urf_supported
);
2137 /* uri-authentication-supported */
2139 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "uri-authentication-supported", 2, NULL
, uri_authentication_supported
);
2141 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "uri-authentication-supported", NULL
, "none");
2142 #endif /* HAVE_SSL */
2144 /* uri-security-supported */
2146 ippAddStrings(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "uri-security-supported", 2, NULL
, uri_security_supported
);
2148 ippAddString(printer
->attrs
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "uri-security-supported", NULL
, "none");
2149 #endif /* HAVE_SSL */
2151 /* which-jobs-supported */
2152 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
);
2156 debug_attributes("Printer", printer
->attrs
, 0);
2159 * Register the printer with Bonjour...
2162 if (!register_printer(printer
, location
, make
, model
, docformats
, adminurl
, uuid
+ 9, ppm_color
> 0, duplex
, subtype
))
2173 * If we get here we were unable to create the printer...
2178 delete_printer(printer
);
2184 * 'debug_attributes()' - Print attributes in a request or response.
2188 debug_attributes(const char *title
, /* I - Title */
2189 ipp_t
*ipp
, /* I - Request/response */
2190 int type
) /* I - 0 = object, 1 = request, 2 = response */
2192 ipp_tag_t group_tag
; /* Current group */
2193 ipp_attribute_t
*attr
; /* Current attribute */
2194 char buffer
[2048]; /* String buffer for value */
2195 int major
, minor
; /* Version */
2201 fprintf(stderr
, "%s:\n", title
);
2202 major
= ippGetVersion(ipp
, &minor
);
2203 fprintf(stderr
, " version=%d.%d\n", major
, minor
);
2205 fprintf(stderr
, " operation-id=%s(%04x)\n",
2206 ippOpString(ippGetOperation(ipp
)), ippGetOperation(ipp
));
2208 fprintf(stderr
, " status-code=%s(%04x)\n",
2209 ippErrorString(ippGetStatusCode(ipp
)), ippGetStatusCode(ipp
));
2210 fprintf(stderr
, " request-id=%d\n\n", ippGetRequestId(ipp
));
2212 for (attr
= ippFirstAttribute(ipp
), group_tag
= IPP_TAG_ZERO
;
2214 attr
= ippNextAttribute(ipp
))
2216 if (ippGetGroupTag(attr
) != group_tag
)
2218 group_tag
= ippGetGroupTag(attr
);
2219 fprintf(stderr
, " %s\n", ippTagString(group_tag
));
2222 if (ippGetName(attr
))
2224 ippAttributeString(attr
, buffer
, sizeof(buffer
));
2225 fprintf(stderr
, " %s (%s%s) %s\n", ippGetName(attr
),
2226 ippGetCount(attr
) > 1 ? "1setOf " : "",
2227 ippTagString(ippGetValueTag(attr
)), buffer
);
2234 * 'delete_client()' - Close the socket and free all memory used by a client
2239 delete_client(_ipp_client_t
*client
) /* I - Client */
2242 fprintf(stderr
, "Closing connection from %s\n", client
->hostname
);
2245 * Flush pending writes before closing...
2248 httpFlushWrite(client
->http
);
2254 httpClose(client
->http
);
2256 ippDelete(client
->request
);
2257 ippDelete(client
->response
);
2264 * 'delete_job()' - Remove from the printer and free all memory used by a job
2269 delete_job(_ipp_job_t
*job
) /* I - Job */
2272 fprintf(stderr
, "Removing job #%d from history.\n", job
->id
);
2274 ippDelete(job
->attrs
);
2279 unlink(job
->filename
);
2281 free(job
->filename
);
2289 * 'delete_printer()' - Unregister, close listen sockets, and free all memory
2290 * used by a printer object.
2294 delete_printer(_ipp_printer_t
*printer
) /* I - Printer */
2296 if (printer
->ipv4
>= 0)
2297 close(printer
->ipv4
);
2299 if (printer
->ipv6
>= 0)
2300 close(printer
->ipv6
);
2303 if (printer
->printer_ref
)
2304 DNSServiceRefDeallocate(printer
->printer_ref
);
2305 if (printer
->ipp_ref
)
2306 DNSServiceRefDeallocate(printer
->ipp_ref
);
2307 if (printer
->ipps_ref
)
2308 DNSServiceRefDeallocate(printer
->ipps_ref
);
2309 if (printer
->http_ref
)
2310 DNSServiceRefDeallocate(printer
->http_ref
);
2311 #elif defined(HAVE_AVAHI)
2312 avahi_threaded_poll_lock(DNSSDMaster
);
2314 if (printer
->printer_ref
)
2315 avahi_entry_group_free(printer
->printer_ref
);
2316 if (printer
->ipp_ref
)
2317 avahi_entry_group_free(printer
->ipp_ref
);
2318 if (printer
->ipps_ref
)
2319 avahi_entry_group_free(printer
->ipps_ref
);
2320 if (printer
->http_ref
)
2321 avahi_entry_group_free(printer
->http_ref
);
2323 avahi_threaded_poll_unlock(DNSSDMaster
);
2324 #endif /* HAVE_DNSSD */
2326 if (printer
->dnssd_name
)
2327 free(printer
->dnssd_name
);
2329 free(printer
->name
);
2331 free(printer
->icon
);
2332 if (printer
->command
)
2333 free(printer
->command
);
2334 if (printer
->directory
)
2335 free(printer
->directory
);
2336 if (printer
->hostname
)
2337 free(printer
->hostname
);
2341 ippDelete(printer
->attrs
);
2342 cupsArrayDelete(printer
->jobs
);
2350 * 'dnssd_callback()' - Handle Bonjour registration events.
2353 static void DNSSD_API
2355 DNSServiceRef sdRef
, /* I - Service reference */
2356 DNSServiceFlags flags
, /* I - Status flags */
2357 DNSServiceErrorType errorCode
, /* I - Error, if any */
2358 const char *name
, /* I - Service name */
2359 const char *regtype
, /* I - Service type */
2360 const char *domain
, /* I - Domain for service */
2361 _ipp_printer_t
*printer
) /* I - Printer */
2369 fprintf(stderr
, "DNSServiceRegister for %s failed with error %d.\n",
2370 regtype
, (int)errorCode
);
2373 else if (strcasecmp(name
, printer
->dnssd_name
))
2376 fprintf(stderr
, "Now using DNS-SD service name \"%s\".\n", name
);
2378 /* No lock needed since only the main thread accesses/changes this */
2379 free(printer
->dnssd_name
);
2380 printer
->dnssd_name
= strdup(name
);
2385 #elif defined(HAVE_AVAHI)
2387 * 'dnssd_callback()' - Handle Bonjour registration events.
2392 AvahiEntryGroup
*srv
, /* I - Service */
2393 AvahiEntryGroupState state
, /* I - Registration state */
2394 void *context
) /* I - Printer */
2403 * 'dnssd_client_cb()' - Client callback for Avahi.
2405 * Called whenever the client or server state changes...
2410 AvahiClient
*c
, /* I - Client */
2411 AvahiClientState state
, /* I - Current state */
2412 void *userdata
) /* I - User data (unused) */
2422 fprintf(stderr
, "Ignore Avahi state %d.\n", state
);
2425 case AVAHI_CLIENT_FAILURE
:
2426 if (avahi_client_errno(c
) == AVAHI_ERR_DISCONNECTED
)
2428 fputs("Avahi server crashed, exiting.\n", stderr
);
2434 #endif /* HAVE_DNSSD */
2438 * 'dnssd_init()' - Initialize the DNS-SD service connections...
2445 if (DNSServiceCreateConnection(&DNSSDMaster
) != kDNSServiceErr_NoError
)
2447 fputs("Error: Unable to initialize Bonjour.\n", stderr
);
2451 #elif defined(HAVE_AVAHI)
2452 int error
; /* Error code, if any */
2454 if ((DNSSDMaster
= avahi_threaded_poll_new()) == NULL
)
2456 fputs("Error: Unable to initialize Bonjour.\n", stderr
);
2460 if ((DNSSDClient
= avahi_client_new(avahi_threaded_poll_get(DNSSDMaster
), AVAHI_CLIENT_NO_FAIL
, dnssd_client_cb
, NULL
, &error
)) == NULL
)
2462 fputs("Error: Unable to initialize Bonjour.\n", stderr
);
2466 avahi_threaded_poll_start(DNSSDMaster
);
2467 #endif /* HAVE_DNSSD */
2472 * 'filter_cb()' - Filter printer attributes based on the requested array.
2475 static int /* O - 1 to copy, 0 to ignore */
2476 filter_cb(_ipp_filter_t
*filter
, /* I - Filter parameters */
2477 ipp_t
*dst
, /* I - Destination (unused) */
2478 ipp_attribute_t
*attr
) /* I - Source attribute */
2481 * Filter attributes as needed...
2484 #ifndef WIN32 /* Avoid MS compiler bug */
2488 ipp_tag_t group
= ippGetGroupTag(attr
);
2489 const char *name
= ippGetName(attr
);
2491 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
)))
2494 return (!filter
->ra
|| cupsArrayFind(filter
->ra
, (void *)name
) != NULL
);
2499 * 'find_job()' - Find a job specified in a request.
2502 static _ipp_job_t
* /* O - Job or NULL */
2503 find_job(_ipp_client_t
*client
) /* I - Client */
2505 ipp_attribute_t
*attr
; /* job-id or job-uri attribute */
2506 _ipp_job_t key
, /* Job search key */
2507 *job
; /* Matching job, if any */
2510 if ((attr
= ippFindAttribute(client
->request
, "job-uri", IPP_TAG_URI
)) != NULL
)
2512 const char *uri
= ippGetString(attr
, 0, NULL
);
2514 if (!strncmp(uri
, client
->printer
->uri
, client
->printer
->urilen
) &&
2515 uri
[client
->printer
->urilen
] == '/')
2516 key
.id
= atoi(uri
+ client
->printer
->urilen
+ 1);
2520 else if ((attr
= ippFindAttribute(client
->request
, "job-id", IPP_TAG_INTEGER
)) != NULL
)
2521 key
.id
= ippGetInteger(attr
, 0);
2523 _cupsRWLockRead(&(client
->printer
->rwlock
));
2524 job
= (_ipp_job_t
*)cupsArrayFind(client
->printer
->jobs
, &key
);
2525 _cupsRWUnlock(&(client
->printer
->rwlock
));
2532 * 'get_collection()' - Get a collection value from a file.
2535 static ipp_t
* /* O - Collection value */
2536 get_collection(FILE *fp
, /* I - File to read from */
2537 const char *filename
, /* I - Attributes filename */
2538 int *linenum
) /* IO - Line number */
2540 char token
[1024], /* Token from file */
2541 attr
[128]; /* Attribute name */
2542 ipp_tag_t value
; /* Current value type */
2543 ipp_t
*col
= ippNew(); /* Collection value */
2544 ipp_attribute_t
*lastcol
= NULL
; /* Last collection attribute */
2547 while (get_token(fp
, token
, sizeof(token
), linenum
) != NULL
)
2549 if (!strcmp(token
, "}"))
2551 else if (!strcmp(token
, "{") && lastcol
)
2554 * Another collection value
2557 ipp_t
*subcol
= get_collection(fp
, filename
, linenum
);
2558 /* Collection value */
2561 ippSetCollection(col
, &lastcol
, ippGetCount(lastcol
), subcol
);
2565 else if (!_cups_strcasecmp(token
, "MEMBER"))
2573 if (!get_token(fp
, token
, sizeof(token
), linenum
))
2575 fprintf(stderr
, "ippserver: Missing MEMBER value tag on line %d of \"%s\".\n", *linenum
, filename
);
2579 if ((value
= ippTagValue(token
)) == IPP_TAG_ZERO
)
2581 fprintf(stderr
, "ippserver: Bad MEMBER value tag \"%s\" on line %d of \"%s\".\n", token
, *linenum
, filename
);
2585 if (!get_token(fp
, attr
, sizeof(attr
), linenum
))
2587 fprintf(stderr
, "ippserver: Missing MEMBER name on line %d of \"%s\".\n", *linenum
, filename
);
2591 if (!get_token(fp
, token
, sizeof(token
), linenum
))
2593 fprintf(stderr
, "ippserver: Missing MEMBER value on line %d of \"%s\".\n", *linenum
, filename
);
2599 case IPP_TAG_BOOLEAN
:
2600 if (!_cups_strcasecmp(token
, "true"))
2601 ippAddBoolean(col
, IPP_TAG_ZERO
, attr
, 1);
2603 ippAddBoolean(col
, IPP_TAG_ZERO
, attr
, (char)atoi(token
));
2606 case IPP_TAG_INTEGER
:
2608 ippAddInteger(col
, IPP_TAG_ZERO
, value
, attr
, atoi(token
));
2611 case IPP_TAG_RESOLUTION
:
2613 int xres
, /* X resolution */
2614 yres
; /* Y resolution */
2615 char units
[6]; /* Units */
2617 if (sscanf(token
, "%dx%d%5s", &xres
, &yres
, units
) != 3 ||
2618 (_cups_strcasecmp(units
, "dpi") &&
2619 _cups_strcasecmp(units
, "dpc") &&
2620 _cups_strcasecmp(units
, "dpcm") &&
2621 _cups_strcasecmp(units
, "other")))
2623 fprintf(stderr
, "ippserver: Bad resolution value \"%s\" on line %d of \"%s\".\n", token
, *linenum
, filename
);
2627 if (!_cups_strcasecmp(units
, "dpi"))
2628 ippAddResolution(col
, IPP_TAG_ZERO
, attr
, IPP_RES_PER_INCH
, xres
, yres
);
2629 else if (!_cups_strcasecmp(units
, "dpc") ||
2630 !_cups_strcasecmp(units
, "dpcm"))
2631 ippAddResolution(col
, IPP_TAG_ZERO
, attr
, IPP_RES_PER_CM
, xres
, yres
);
2633 ippAddResolution(col
, IPP_TAG_ZERO
, attr
, (ipp_res_t
)0, xres
, yres
);
2637 case IPP_TAG_RANGE
:
2639 int lowers
[4], /* Lower value */
2640 uppers
[4], /* Upper values */
2641 num_vals
; /* Number of values */
2644 num_vals
= sscanf(token
, "%d-%d,%d-%d,%d-%d,%d-%d",
2645 lowers
+ 0, uppers
+ 0,
2646 lowers
+ 1, uppers
+ 1,
2647 lowers
+ 2, uppers
+ 2,
2648 lowers
+ 3, uppers
+ 3);
2650 if ((num_vals
& 1) || num_vals
== 0)
2652 fprintf(stderr
, "ippserver: Bad rangeOfInteger value \"%s\" on line %d of \"%s\".\n", token
, *linenum
, filename
);
2656 ippAddRanges(col
, IPP_TAG_ZERO
, attr
, num_vals
/ 2, lowers
,
2661 case IPP_TAG_BEGIN_COLLECTION
:
2662 if (!strcmp(token
, "{"))
2664 ipp_t
*subcol
= get_collection(fp
, filename
, linenum
);
2665 /* Collection value */
2669 lastcol
= ippAddCollection(col
, IPP_TAG_ZERO
, attr
, subcol
);
2677 fprintf(stderr
, "ippserver: Bad collection value on line %d of \"%s\".\n", *linenum
, filename
);
2681 case IPP_TAG_STRING
:
2682 ippAddOctetString(col
, IPP_TAG_ZERO
, attr
, token
, (int)strlen(token
));
2686 if (!strchr(token
, ','))
2687 ippAddString(col
, IPP_TAG_ZERO
, value
, attr
, NULL
, token
);
2691 * Multiple string values...
2694 int num_values
; /* Number of values */
2695 char *values
[100], /* Values */
2696 *ptr
; /* Pointer to next value */
2702 for (ptr
= strchr(token
, ','); ptr
; ptr
= strchr(ptr
, ','))
2705 values
[num_values
] = ptr
;
2707 if (num_values
>= (int)(sizeof(values
) / sizeof(values
[0])))
2711 ippAddStrings(col
, IPP_TAG_ZERO
, value
, attr
, num_values
,
2712 NULL
, (const char **)values
);
2722 * If we get here there was a parse error; free memory and return.
2734 * 'get_token()' - Get a token from a file.
2737 static char * /* O - Token from file or NULL on EOF */
2738 get_token(FILE *fp
, /* I - File to read from */
2739 char *buf
, /* I - Buffer to read into */
2740 int buflen
, /* I - Length of buffer */
2741 int *linenum
) /* IO - Current line number */
2743 int ch
, /* Character from file */
2744 quote
; /* Quoting character */
2745 char *bufptr
, /* Pointer into buffer */
2746 *bufend
; /* End of buffer */
2752 * Skip whitespace...
2755 while (isspace(ch
= getc(fp
)))
2767 else if (ch
== '\'' || ch
== '\"')
2770 * Quoted text or regular expression...
2775 bufend
= buf
+ buflen
- 1;
2777 while ((ch
= getc(fp
)) != EOF
)
2782 * Escape next character...
2785 if (bufptr
< bufend
)
2786 *bufptr
++ = (char)ch
;
2788 if ((ch
= getc(fp
)) != EOF
&& bufptr
< bufend
)
2789 *bufptr
++ = (char)ch
;
2791 else if (ch
== quote
)
2793 else if (bufptr
< bufend
)
2794 *bufptr
++ = (char)ch
;
2807 while ((ch
= getc(fp
)) != EOF
)
2813 else if (ch
== '{' || ch
== '}' || ch
== ',')
2823 * Whitespace delimited text...
2829 bufend
= buf
+ buflen
- 1;
2831 while ((ch
= getc(fp
)) != EOF
)
2832 if (isspace(ch
) || ch
== '#')
2834 else if (bufptr
< bufend
)
2835 *bufptr
++ = (char)ch
;
2839 else if (ch
== '\n')
2851 * 'html_escape()' - Write a HTML-safe string.
2855 html_escape(_ipp_client_t
*client
, /* I - Client */
2856 const char *s
, /* I - String to write */
2857 size_t slen
) /* I - Number of characters to write */
2859 const char *start
, /* Start of segment */
2860 *end
; /* End of string */
2864 end
= s
+ (slen
> 0 ? slen
: strlen(s
));
2866 while (*s
&& s
< end
)
2868 if (*s
== '&' || *s
== '<')
2871 httpWrite2(client
->http
, start
, (size_t)(s
- start
));
2874 httpWrite2(client
->http
, "&", 5);
2876 httpWrite2(client
->http
, "<", 4);
2885 httpWrite2(client
->http
, start
, (size_t)(s
- start
));
2890 * 'html_footer()' - Show the web interface footer.
2892 * This function also writes the trailing 0-length chunk.
2896 html_footer(_ipp_client_t
*client
) /* I - Client */
2902 httpWrite2(client
->http
, "", 0);
2907 * 'html_header()' - Show the web interface header and title.
2911 html_header(_ipp_client_t
*client
, /* I - Client */
2912 const char *title
) /* I - Title */
2918 "<title>%s</title>\n"
2919 "<link rel=\"shortcut icon\" href=\"/icon.png\" type=\"image/png\">\n"
2920 "<link rel=\"apple-touch-icon\" href=\"/icon.png\" type=\"image/png\">\n"
2921 "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=9\">\n"
2922 "<meta name=\"viewport\" content=\"width=device-width\">\n"
2924 "body { font-family: sans-serif; margin: 0; }\n"
2925 "div.body { padding: 0px 10px 10px; }\n"
2926 "blockquote { background: #dfd; border-radius: 5px; color: #006; padding: 10px; }\n"
2927 "table.form { border-collapse: collapse; margin-top: 10px; width: 100%%; }\n"
2928 "table.form td, table.form th { padding: 5px 2px; width: 50%%; }\n"
2929 "table.form th { text-align: right; }\n"
2930 "table.striped { border-bottom: solid thin black; border-collapse: collapse; width: 100%%; }\n"
2931 "table.striped tr:nth-child(even) { background: #fcfcfc; }\n"
2932 "table.striped tr:nth-child(odd) { background: #f0f0f0; }\n"
2933 "table.striped th { background: white; border-bottom: solid thin black; text-align: left; vertical-align: bottom; }\n"
2934 "table.striped td { margin: 0; padding: 5px; vertical-align: top; }\n"
2935 "table.nav { border-collapse: collapse; width: 100%%; }\n"
2936 "table.nav td { margin: 0; text-align: center; }\n"
2937 "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"
2938 "td.nav { background: #333; color: #fff; padding: 4px 8px; width: 33%%; }\n"
2939 "td.nav.sel { background: #fff; color: #000; font-weight: bold; }\n"
2940 "td.nav:hover { background: #666; color: #fff; }\n"
2941 "td.nav:active { background: #000; color: #ff0; }\n"
2945 "<table class=\"nav\"><tr>"
2946 "<td class=\"nav%s\"><a href=\"/\">Status</a></td>"
2947 "<td class=\"nav%s\"><a href=\"/supplies\">Supplies</a></td>"
2948 "<td class=\"nav%s\"><a href=\"/media\">Media</a></td>"
2950 "<div class=\"body\">\n", title
, !strcmp(client
->uri
, "/") ? " sel" : "", !strcmp(client
->uri
, "/supplies") ? " sel" : "", !strcmp(client
->uri
, "/media") ? " sel" : "");
2955 * 'html_printf()' - Send formatted text to the client, quoting as needed.
2959 html_printf(_ipp_client_t
*client
, /* I - Client */
2960 const char *format
, /* I - Printf-style format string */
2961 ...) /* I - Additional arguments as needed */
2963 va_list ap
; /* Pointer to arguments */
2964 const char *start
; /* Start of string */
2965 char size
, /* Size character (h, l, L) */
2966 type
; /* Format type character */
2967 int width
, /* Width of field */
2968 prec
; /* Number of characters of precision */
2969 char tformat
[100], /* Temporary format string for sprintf() */
2970 *tptr
, /* Pointer into temporary format */
2971 temp
[1024]; /* Buffer for formatted numbers */
2972 char *s
; /* Pointer to string */
2976 * Loop through the format string, formatting as needed...
2979 va_start(ap
, format
);
2987 httpWrite2(client
->http
, start
, (size_t)(format
- start
));
2990 *tptr
++ = *format
++;
2994 httpWrite2(client
->http
, "%", 1);
2999 else if (strchr(" -+#\'", *format
))
3000 *tptr
++ = *format
++;
3005 * Get width from argument...
3009 width
= va_arg(ap
, int);
3011 snprintf(tptr
, sizeof(tformat
) - (size_t)(tptr
- tformat
), "%d", width
);
3012 tptr
+= strlen(tptr
);
3018 while (isdigit(*format
& 255))
3020 if (tptr
< (tformat
+ sizeof(tformat
) - 1))
3023 width
= width
* 10 + *format
++ - '0';
3029 if (tptr
< (tformat
+ sizeof(tformat
) - 1))
3037 * Get precision from argument...
3041 prec
= va_arg(ap
, int);
3043 snprintf(tptr
, sizeof(tformat
) - (size_t)(tptr
- tformat
), "%d", prec
);
3044 tptr
+= strlen(tptr
);
3050 while (isdigit(*format
& 255))
3052 if (tptr
< (tformat
+ sizeof(tformat
) - 1))
3055 prec
= prec
* 10 + *format
++ - '0';
3060 if (*format
== 'l' && format
[1] == 'l')
3064 if (tptr
< (tformat
+ sizeof(tformat
) - 2))
3072 else if (*format
== 'h' || *format
== 'l' || *format
== 'L')
3074 if (tptr
< (tformat
+ sizeof(tformat
) - 1))
3089 if (tptr
< (tformat
+ sizeof(tformat
) - 1))
3098 case 'E' : /* Floating point formats */
3103 if ((size_t)(width
+ 2) > sizeof(temp
))
3106 sprintf(temp
, tformat
, va_arg(ap
, double));
3108 httpWrite2(client
->http
, temp
, strlen(temp
));
3111 case 'B' : /* Integer formats */
3119 if ((size_t)(width
+ 2) > sizeof(temp
))
3122 # ifdef HAVE_LONG_LONG
3124 sprintf(temp
, tformat
, va_arg(ap
, long long));
3126 # endif /* HAVE_LONG_LONG */
3128 sprintf(temp
, tformat
, va_arg(ap
, long));
3130 sprintf(temp
, tformat
, va_arg(ap
, int));
3132 httpWrite2(client
->http
, temp
, strlen(temp
));
3135 case 'p' : /* Pointer value */
3136 if ((size_t)(width
+ 2) > sizeof(temp
))
3139 sprintf(temp
, tformat
, va_arg(ap
, void *));
3141 httpWrite2(client
->http
, temp
, strlen(temp
));
3144 case 'c' : /* Character or character array */
3147 temp
[0] = (char)va_arg(ap
, int);
3149 html_escape(client
, temp
, 1);
3152 html_escape(client
, va_arg(ap
, char *), (size_t)width
);
3155 case 's' : /* String */
3156 if ((s
= va_arg(ap
, char *)) == NULL
)
3159 html_escape(client
, s
, strlen(s
));
3168 httpWrite2(client
->http
, start
, (size_t)(format
- start
));
3175 * 'ipp_cancel_job()' - Cancel a job.
3179 ipp_cancel_job(_ipp_client_t
*client
) /* I - Client */
3181 _ipp_job_t
*job
; /* Job information */
3188 if ((job
= find_job(client
)) == NULL
)
3190 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "Job does not exist.");
3195 * See if the job is already completed, canceled, or aborted; if so,
3196 * we can't cancel...
3201 case IPP_JSTATE_CANCELED
:
3202 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3203 "Job #%d is already canceled - can\'t cancel.", job
->id
);
3206 case IPP_JSTATE_ABORTED
:
3207 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3208 "Job #%d is already aborted - can\'t cancel.", job
->id
);
3211 case IPP_JSTATE_COMPLETED
:
3212 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3213 "Job #%d is already completed - can\'t cancel.", job
->id
);
3221 _cupsRWLockWrite(&(client
->printer
->rwlock
));
3223 if (job
->state
== IPP_JSTATE_PROCESSING
||
3224 (job
->state
== IPP_JSTATE_HELD
&& job
->fd
>= 0))
3228 job
->state
= IPP_JSTATE_CANCELED
;
3229 job
->completed
= time(NULL
);
3232 _cupsRWUnlock(&(client
->printer
->rwlock
));
3234 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3241 * 'ipp_close_job()' - Close an open job.
3245 ipp_close_job(_ipp_client_t
*client
) /* I - Client */
3247 _ipp_job_t
*job
; /* Job information */
3254 if ((job
= find_job(client
)) == NULL
)
3256 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "Job does not exist.");
3261 * See if the job is already completed, canceled, or aborted; if so,
3262 * we can't cancel...
3267 case IPP_JSTATE_CANCELED
:
3268 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3269 "Job #%d is canceled - can\'t close.", job
->id
);
3272 case IPP_JSTATE_ABORTED
:
3273 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3274 "Job #%d is aborted - can\'t close.", job
->id
);
3277 case IPP_JSTATE_COMPLETED
:
3278 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3279 "Job #%d is completed - can\'t close.", job
->id
);
3282 case IPP_JSTATE_PROCESSING
:
3283 case IPP_JSTATE_STOPPED
:
3284 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
3285 "Job #%d is already closed.", job
->id
);
3289 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3296 * 'ipp_create_job()' - Create a job object.
3300 ipp_create_job(_ipp_client_t
*client
) /* I - Client */
3302 _ipp_job_t
*job
; /* New job */
3303 cups_array_t
*ra
; /* Attributes to send in response */
3307 * Validate print job attributes...
3310 if (!valid_job_attributes(client
))
3312 httpFlush(client
->http
);
3317 * Do we have a file to print?
3320 if (httpGetState(client
->http
) == HTTP_STATE_POST_RECV
)
3322 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
3323 "Unexpected document data following request.");
3331 if ((job
= create_job(client
)) == NULL
)
3333 respond_ipp(client
, IPP_STATUS_ERROR_BUSY
,
3334 "Currently printing another job.");
3339 * Return the job info...
3342 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3344 ra
= cupsArrayNew((cups_array_func_t
)strcmp
, NULL
);
3345 cupsArrayAdd(ra
, "job-id");
3346 cupsArrayAdd(ra
, "job-state");
3347 cupsArrayAdd(ra
, "job-state-message");
3348 cupsArrayAdd(ra
, "job-state-reasons");
3349 cupsArrayAdd(ra
, "job-uri");
3351 copy_job_attributes(client
, job
, ra
);
3352 cupsArrayDelete(ra
);
3357 * 'ipp_get_job_attributes()' - Get the attributes for a job object.
3361 ipp_get_job_attributes(
3362 _ipp_client_t
*client
) /* I - Client */
3364 _ipp_job_t
*job
; /* Job */
3365 cups_array_t
*ra
; /* requested-attributes */
3368 if ((job
= find_job(client
)) == NULL
)
3370 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "Job not found.");
3374 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3376 ra
= ippCreateRequestedArray(client
->request
);
3377 copy_job_attributes(client
, job
, ra
);
3378 cupsArrayDelete(ra
);
3383 * 'ipp_get_jobs()' - Get a list of job objects.
3387 ipp_get_jobs(_ipp_client_t
*client
) /* I - Client */
3389 ipp_attribute_t
*attr
; /* Current attribute */
3390 const char *which_jobs
= NULL
;
3391 /* which-jobs values */
3392 int job_comparison
; /* Job comparison */
3393 ipp_jstate_t job_state
; /* job-state value */
3394 int first_job_id
, /* First job ID */
3395 limit
, /* Maximum number of jobs to return */
3396 count
; /* Number of jobs that match */
3397 const char *username
; /* Username */
3398 _ipp_job_t
*job
; /* Current job pointer */
3399 cups_array_t
*ra
; /* Requested attributes array */
3403 * See if the "which-jobs" attribute have been specified...
3406 if ((attr
= ippFindAttribute(client
->request
, "which-jobs",
3407 IPP_TAG_KEYWORD
)) != NULL
)
3409 which_jobs
= ippGetString(attr
, 0, NULL
);
3410 fprintf(stderr
, "%s Get-Jobs which-jobs=%s", client
->hostname
, which_jobs
);
3413 if (!which_jobs
|| !strcmp(which_jobs
, "not-completed"))
3415 job_comparison
= -1;
3416 job_state
= IPP_JSTATE_STOPPED
;
3418 else if (!strcmp(which_jobs
, "completed"))
3421 job_state
= IPP_JSTATE_CANCELED
;
3423 else if (!strcmp(which_jobs
, "aborted"))
3426 job_state
= IPP_JSTATE_ABORTED
;
3428 else if (!strcmp(which_jobs
, "all"))
3431 job_state
= IPP_JSTATE_PENDING
;
3433 else if (!strcmp(which_jobs
, "canceled"))
3436 job_state
= IPP_JSTATE_CANCELED
;
3438 else if (!strcmp(which_jobs
, "pending"))
3441 job_state
= IPP_JSTATE_PENDING
;
3443 else if (!strcmp(which_jobs
, "pending-held"))
3446 job_state
= IPP_JSTATE_HELD
;
3448 else if (!strcmp(which_jobs
, "processing"))
3451 job_state
= IPP_JSTATE_PROCESSING
;
3453 else if (!strcmp(which_jobs
, "processing-stopped"))
3456 job_state
= IPP_JSTATE_STOPPED
;
3460 respond_ipp(client
, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES
,
3461 "The which-jobs value \"%s\" is not supported.", which_jobs
);
3462 ippAddString(client
->response
, IPP_TAG_UNSUPPORTED_GROUP
, IPP_TAG_KEYWORD
,
3463 "which-jobs", NULL
, which_jobs
);
3468 * See if they want to limit the number of jobs reported...
3471 if ((attr
= ippFindAttribute(client
->request
, "limit",
3472 IPP_TAG_INTEGER
)) != NULL
)
3474 limit
= ippGetInteger(attr
, 0);
3476 fprintf(stderr
, "%s Get-Jobs limit=%d", client
->hostname
, limit
);
3481 if ((attr
= ippFindAttribute(client
->request
, "first-job-id",
3482 IPP_TAG_INTEGER
)) != NULL
)
3484 first_job_id
= ippGetInteger(attr
, 0);
3486 fprintf(stderr
, "%s Get-Jobs first-job-id=%d", client
->hostname
,
3493 * See if we only want to see jobs for a specific user...
3498 if ((attr
= ippFindAttribute(client
->request
, "my-jobs",
3499 IPP_TAG_BOOLEAN
)) != NULL
)
3501 int my_jobs
= ippGetBoolean(attr
, 0);
3503 fprintf(stderr
, "%s Get-Jobs my-jobs=%s\n", client
->hostname
,
3504 my_jobs
? "true" : "false");
3508 if ((attr
= ippFindAttribute(client
->request
, "requesting-user-name",
3509 IPP_TAG_NAME
)) == NULL
)
3511 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
3512 "Need requesting-user-name with my-jobs.");
3516 username
= ippGetString(attr
, 0, NULL
);
3518 fprintf(stderr
, "%s Get-Jobs requesting-user-name=\"%s\"\n",
3519 client
->hostname
, username
);
3524 * OK, build a list of jobs for this printer...
3527 ra
= ippCreateRequestedArray(client
->request
);
3529 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3531 _cupsRWLockRead(&(client
->printer
->rwlock
));
3533 for (count
= 0, job
= (_ipp_job_t
*)cupsArrayFirst(client
->printer
->jobs
);
3534 (limit
<= 0 || count
< limit
) && job
;
3535 job
= (_ipp_job_t
*)cupsArrayNext(client
->printer
->jobs
))
3538 * Filter out jobs that don't match...
3541 if ((job_comparison
< 0 && job
->state
> job_state
) ||
3542 (job_comparison
== 0 && job
->state
!= job_state
) ||
3543 (job_comparison
> 0 && job
->state
< job_state
) ||
3544 job
->id
< first_job_id
||
3545 (username
&& job
->username
&&
3546 strcasecmp(username
, job
->username
)))
3550 ippAddSeparator(client
->response
);
3553 copy_job_attributes(client
, job
, ra
);
3556 cupsArrayDelete(ra
);
3558 _cupsRWUnlock(&(client
->printer
->rwlock
));
3563 * 'ipp_get_printer_attributes()' - Get the attributes for a printer object.
3567 ipp_get_printer_attributes(
3568 _ipp_client_t
*client
) /* I - Client */
3570 cups_array_t
*ra
; /* Requested attributes array */
3571 _ipp_printer_t
*printer
; /* Printer */
3575 * Send the attributes...
3578 ra
= ippCreateRequestedArray(client
->request
);
3579 printer
= client
->printer
;
3581 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3583 _cupsRWLockRead(&(printer
->rwlock
));
3585 copy_attributes(client
->response
, printer
->attrs
, ra
, IPP_TAG_ZERO
,
3586 IPP_TAG_CUPS_CONST
);
3588 if (!ra
|| cupsArrayFind(ra
, "media-col-ready"))
3590 int i
, /* Looping var */
3591 num_ready
= 0; /* Number of ready media */
3592 ipp_t
*ready
[3]; /* Ready media */
3594 if (printer
->main_size
!= _IPP_MEDIA_SIZE_NONE
)
3596 if (printer
->main_type
!= _IPP_MEDIA_TYPE_NONE
)
3597 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);
3599 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);
3601 if (printer
->envelope_size
!= _IPP_MEDIA_SIZE_NONE
)
3602 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);
3603 if (printer
->photo_size
!= _IPP_MEDIA_SIZE_NONE
)
3605 if (printer
->photo_type
!= _IPP_MEDIA_TYPE_NONE
)
3606 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);
3608 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);
3613 ippAddCollections(client
->response
, IPP_TAG_PRINTER
, "media-col-ready", num_ready
, (const ipp_t
**)ready
);
3614 for (i
= 0; i
< num_ready
; i
++)
3615 ippDelete(ready
[i
]);
3618 ippAddOutOfBand(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_NOVALUE
, "media-col-ready");
3621 if (!ra
|| cupsArrayFind(ra
, "media-ready"))
3623 int num_ready
= 0; /* Number of ready media */
3624 const char *ready
[3]; /* Ready media */
3626 if (printer
->main_size
!= _IPP_MEDIA_SIZE_NONE
)
3627 ready
[num_ready
++] = media_supported
[printer
->main_size
];
3629 if (printer
->envelope_size
!= _IPP_MEDIA_SIZE_NONE
)
3630 ready
[num_ready
++] = media_supported
[printer
->envelope_size
];
3632 if (printer
->photo_size
!= _IPP_MEDIA_SIZE_NONE
)
3633 ready
[num_ready
++] = media_supported
[printer
->photo_size
];
3636 ippAddStrings(client
->response
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_KEYWORD
), "media-ready", num_ready
, NULL
, ready
);
3638 ippAddOutOfBand(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_NOVALUE
, "media-ready");
3641 if (!ra
|| cupsArrayFind(ra
, "printer-config-change-date-time"))
3642 ippAddDate(client
->response
, IPP_TAG_PRINTER
, "printer-config-change-date-time", ippTimeToDate(printer
->config_time
));
3644 if (!ra
|| cupsArrayFind(ra
, "printer-config-change-time"))
3645 ippAddInteger(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "printer-config-change-time", (int)(printer
->config_time
- printer
->start_time
));
3647 if (!ra
|| cupsArrayFind(ra
, "printer-current-time"))
3648 ippAddDate(client
->response
, IPP_TAG_PRINTER
, "printer-current-time", ippTimeToDate(time(NULL
)));
3651 if (!ra
|| cupsArrayFind(ra
, "printer-state"))
3652 ippAddInteger(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_ENUM
,
3653 "printer-state", printer
->state
);
3655 if (!ra
|| cupsArrayFind(ra
, "printer-state-change-date-time"))
3656 ippAddDate(client
->response
, IPP_TAG_PRINTER
, "printer-state-change-date-time", ippTimeToDate(printer
->state_time
));
3658 if (!ra
|| cupsArrayFind(ra
, "printer-state-change-time"))
3659 ippAddInteger(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "printer-state-change-time", (int)(printer
->state_time
- printer
->start_time
));
3661 if (!ra
|| cupsArrayFind(ra
, "printer-state-message"))
3663 static const char * const messages
[] = { "Idle.", "Printing.", "Stopped." };
3665 ippAddString(client
->response
, IPP_TAG_PRINTER
, IPP_CONST_TAG(IPP_TAG_TEXT
), "printer-state-message", NULL
, messages
[printer
->state
- IPP_PSTATE_IDLE
]);
3668 if (!ra
|| cupsArrayFind(ra
, "printer-state-reasons"))
3670 if (printer
->state_reasons
== _IPP_PREASON_NONE
)
3671 ippAddString(client
->response
, IPP_TAG_PRINTER
,
3672 IPP_CONST_TAG(IPP_TAG_KEYWORD
),
3673 "printer-state-reasons", NULL
, "none");
3676 ipp_attribute_t
*attr
= NULL
; /* printer-state-reasons */
3677 _ipp_preason_t bit
; /* Reason bit */
3678 int i
; /* Looping var */
3679 char reason
[32]; /* Reason string */
3681 for (i
= 0, bit
= 1; i
< (int)(sizeof(_ipp_preason_strings
) / sizeof(_ipp_preason_strings
[0])); i
++, bit
*= 2)
3683 if (printer
->state_reasons
& bit
)
3685 snprintf(reason
, sizeof(reason
), "%s-%s", _ipp_preason_strings
[i
], printer
->state
== IPP_PSTATE_IDLE
? "report" : printer
->state
== IPP_PSTATE_PROCESSING
? "warning" : "error");
3687 ippSetString(client
->response
, &attr
, ippGetCount(attr
), reason
);
3689 attr
= ippAddString(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_KEYWORD
, "printer-state-reasons", NULL
, reason
);
3695 if (!ra
|| cupsArrayFind(ra
, "printer-supply"))
3697 int i
; /* Looping var */
3698 char buffer
[256]; /* Supply value buffer */
3699 ipp_attribute_t
*attr
= NULL
; /* Attribute */
3700 static const char * const colorants
[] = { "cyan", "magenta", "yellow", "black", "unknown" };
3702 for (i
= 0; i
< 5; i
++)
3704 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
]);
3707 attr
= ippAddOctetString(client
->response
, IPP_TAG_PRINTER
, "printer-supply", buffer
, (int)strlen(buffer
));
3709 ippSetOctetString(client
->response
, &attr
, i
, buffer
, (int)strlen(buffer
));
3713 if (!ra
|| cupsArrayFind(ra
, "printer-up-time"))
3714 ippAddInteger(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
, "printer-up-time", (int)(time(NULL
) - printer
->start_time
));
3716 if (!ra
|| cupsArrayFind(ra
, "queued-job-count"))
3717 ippAddInteger(client
->response
, IPP_TAG_PRINTER
, IPP_TAG_INTEGER
,
3718 "queued-job-count", printer
->active_job
&& printer
->active_job
->state
< IPP_JSTATE_CANCELED
);
3720 _cupsRWUnlock(&(printer
->rwlock
));
3722 cupsArrayDelete(ra
);
3727 * 'ipp_identify_printer()' - Beep or display a message.
3731 ipp_identify_printer(
3732 _ipp_client_t
*client
) /* I - Client */
3734 ipp_attribute_t
*actions
, /* identify-actions */
3735 *message
; /* message */
3738 actions
= ippFindAttribute(client
->request
, "identify-actions", IPP_TAG_KEYWORD
);
3739 message
= ippFindAttribute(client
->request
, "message", IPP_TAG_TEXT
);
3741 if (!actions
|| ippContainsString(actions
, "sound"))
3747 if (ippContainsString(actions
, "display"))
3748 printf("IDENTIFY from %s: %s\n", client
->hostname
, message
? ippGetString(message
, 0, NULL
) : "No message supplied");
3750 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3755 * 'ipp_print_job()' - Create a job object with an attached document.
3759 ipp_print_job(_ipp_client_t
*client
) /* I - Client */
3761 _ipp_job_t
*job
; /* New job */
3762 char filename
[1024], /* Filename buffer */
3763 buffer
[4096]; /* Copy buffer */
3764 ssize_t bytes
; /* Bytes read */
3765 cups_array_t
*ra
; /* Attributes to send in response */
3769 * Validate print job attributes...
3772 if (!valid_job_attributes(client
))
3774 httpFlush(client
->http
);
3779 * Do we have a file to print?
3782 if (httpGetState(client
->http
) == HTTP_STATE_POST_SEND
)
3784 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "No file in request.");
3792 if ((job
= create_job(client
)) == NULL
)
3794 respond_ipp(client
, IPP_STATUS_ERROR_BUSY
,
3795 "Currently printing another job.");
3800 * Create a file for the request data...
3803 create_job_filename(client
->printer
, job
, filename
, sizeof(filename
));
3806 fprintf(stderr
, "Creating job file \"%s\", format \"%s\".\n", filename
, job
->format
);
3808 if ((job
->fd
= open(filename
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600)) < 0)
3810 job
->state
= IPP_JSTATE_ABORTED
;
3812 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
3813 "Unable to create print file: %s", strerror(errno
));
3817 while ((bytes
= httpRead2(client
->http
, buffer
, sizeof(buffer
))) > 0)
3819 if (write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
3821 int error
= errno
; /* Write error */
3823 job
->state
= IPP_JSTATE_ABORTED
;
3830 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
3831 "Unable to write print file: %s", strerror(error
));
3839 * Got an error while reading the print data, so abort this job.
3842 job
->state
= IPP_JSTATE_ABORTED
;
3849 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
3850 "Unable to read print file.");
3856 int error
= errno
; /* Write error */
3858 job
->state
= IPP_JSTATE_ABORTED
;
3863 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
3864 "Unable to write print file: %s", strerror(error
));
3869 job
->filename
= strdup(filename
);
3870 job
->state
= IPP_JSTATE_PENDING
;
3873 * Process the job...
3876 if (!_cupsThreadCreate((_cups_thread_func_t
)process_job
, job
))
3878 job
->state
= IPP_JSTATE_ABORTED
;
3879 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
, "Unable to process job.");
3884 * Return the job info...
3887 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
3889 ra
= cupsArrayNew((cups_array_func_t
)strcmp
, NULL
);
3890 cupsArrayAdd(ra
, "job-id");
3891 cupsArrayAdd(ra
, "job-state");
3892 cupsArrayAdd(ra
, "job-state-message");
3893 cupsArrayAdd(ra
, "job-state-reasons");
3894 cupsArrayAdd(ra
, "job-uri");
3896 copy_job_attributes(client
, job
, ra
);
3897 cupsArrayDelete(ra
);
3902 * 'ipp_print_uri()' - Create a job object with a referenced document.
3906 ipp_print_uri(_ipp_client_t
*client
) /* I - Client */
3908 _ipp_job_t
*job
; /* New job */
3909 ipp_attribute_t
*uri
; /* document-uri */
3910 char scheme
[256], /* URI scheme */
3911 userpass
[256], /* Username and password info */
3912 hostname
[256], /* Hostname */
3913 resource
[1024]; /* Resource path */
3914 int port
; /* Port number */
3915 http_uri_status_t uri_status
; /* URI decode status */
3916 http_encryption_t encryption
; /* Encryption to use, if any */
3917 http_t
*http
; /* Connection for http/https URIs */
3918 http_status_t status
; /* Access status for http/https URIs */
3919 int infile
; /* Input file for local file URIs */
3920 char filename
[1024], /* Filename buffer */
3921 buffer
[4096]; /* Copy buffer */
3922 ssize_t bytes
; /* Bytes read */
3923 cups_array_t
*ra
; /* Attributes to send in response */
3924 static const char * const uri_status_strings
[] =
3925 { /* URI decode errors */
3927 "Bad arguments to function.",
3928 "Bad resource in URI.",
3929 "Bad port number in URI.",
3930 "Bad hostname in URI.",
3931 "Bad username in URI.",
3932 "Bad scheme in URI.",
3938 * Validate print job attributes...
3941 if (!valid_job_attributes(client
))
3943 httpFlush(client
->http
);
3948 * Do we have a file to print?
3951 if (httpGetState(client
->http
) == HTTP_STATE_POST_RECV
)
3953 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
3954 "Unexpected document data following request.");
3959 * Do we have a document URI?
3962 if ((uri
= ippFindAttribute(client
->request
, "document-uri",
3963 IPP_TAG_URI
)) == NULL
)
3965 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "Missing document-uri.");
3969 if (ippGetCount(uri
) != 1)
3971 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
3972 "Too many document-uri values.");
3976 uri_status
= httpSeparateURI(HTTP_URI_CODING_ALL
, ippGetString(uri
, 0, NULL
),
3977 scheme
, sizeof(scheme
), userpass
,
3978 sizeof(userpass
), hostname
, sizeof(hostname
),
3979 &port
, resource
, sizeof(resource
));
3980 if (uri_status
< HTTP_URI_STATUS_OK
)
3982 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "Bad document-uri: %s",
3983 uri_status_strings
[uri_status
- HTTP_URI_STATUS_OVERFLOW
]);
3987 if (strcmp(scheme
, "file") &&
3989 strcmp(scheme
, "https") &&
3990 #endif /* HAVE_SSL */
3991 strcmp(scheme
, "http"))
3993 respond_ipp(client
, IPP_STATUS_ERROR_URI_SCHEME
,
3994 "URI scheme \"%s\" not supported.", scheme
);
3998 if (!strcmp(scheme
, "file") && access(resource
, R_OK
))
4000 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4001 "Unable to access URI: %s", strerror(errno
));
4009 if ((job
= create_job(client
)) == NULL
)
4011 respond_ipp(client
, IPP_STATUS_ERROR_BUSY
,
4012 "Currently printing another job.");
4017 * Create a file for the request data...
4020 if (!strcasecmp(job
->format
, "image/jpeg"))
4021 snprintf(filename
, sizeof(filename
), "%s/%d.jpg",
4022 client
->printer
->directory
, job
->id
);
4023 else if (!strcasecmp(job
->format
, "image/png"))
4024 snprintf(filename
, sizeof(filename
), "%s/%d.png",
4025 client
->printer
->directory
, job
->id
);
4026 else if (!strcasecmp(job
->format
, "application/pdf"))
4027 snprintf(filename
, sizeof(filename
), "%s/%d.pdf",
4028 client
->printer
->directory
, job
->id
);
4029 else if (!strcasecmp(job
->format
, "application/postscript"))
4030 snprintf(filename
, sizeof(filename
), "%s/%d.ps",
4031 client
->printer
->directory
, job
->id
);
4033 snprintf(filename
, sizeof(filename
), "%s/%d.prn",
4034 client
->printer
->directory
, job
->id
);
4036 if ((job
->fd
= open(filename
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600)) < 0)
4038 job
->state
= IPP_JSTATE_ABORTED
;
4040 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4041 "Unable to create print file: %s", strerror(errno
));
4045 if (!strcmp(scheme
, "file"))
4047 if ((infile
= open(resource
, O_RDONLY
)) < 0)
4049 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4050 "Unable to access URI: %s", strerror(errno
));
4056 if ((bytes
= read(infile
, buffer
, sizeof(buffer
))) < 0 &&
4057 (errno
== EAGAIN
|| errno
== EINTR
))
4059 else if (bytes
> 0 && write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
4061 int error
= errno
; /* Write error */
4063 job
->state
= IPP_JSTATE_ABORTED
;
4071 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4072 "Unable to write print file: %s", strerror(error
));
4083 if (port
== 443 || !strcmp(scheme
, "https"))
4084 encryption
= HTTP_ENCRYPTION_ALWAYS
;
4086 #endif /* HAVE_SSL */
4087 encryption
= HTTP_ENCRYPTION_IF_REQUESTED
;
4089 if ((http
= httpConnect2(hostname
, port
, NULL
, AF_UNSPEC
, encryption
,
4090 1, 30000, NULL
)) == NULL
)
4092 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4093 "Unable to connect to %s: %s", hostname
,
4094 cupsLastErrorString());
4095 job
->state
= IPP_JSTATE_ABORTED
;
4104 httpClearFields(http
);
4105 httpSetField(http
, HTTP_FIELD_ACCEPT_LANGUAGE
, "en");
4106 if (httpGet(http
, resource
))
4108 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4109 "Unable to GET URI: %s", strerror(errno
));
4111 job
->state
= IPP_JSTATE_ABORTED
;
4121 while ((status
= httpUpdate(http
)) == HTTP_STATUS_CONTINUE
);
4123 if (status
!= HTTP_STATUS_OK
)
4125 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4126 "Unable to GET URI: %s", httpStatus(status
));
4128 job
->state
= IPP_JSTATE_ABORTED
;
4138 while ((bytes
= httpRead2(http
, buffer
, sizeof(buffer
))) > 0)
4140 if (write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
4142 int error
= errno
; /* Write error */
4144 job
->state
= IPP_JSTATE_ABORTED
;
4152 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4153 "Unable to write print file: %s", strerror(error
));
4163 int error
= errno
; /* Write error */
4165 job
->state
= IPP_JSTATE_ABORTED
;
4170 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4171 "Unable to write print file: %s", strerror(error
));
4176 job
->filename
= strdup(filename
);
4177 job
->state
= IPP_JSTATE_PENDING
;
4180 * Process the job...
4184 if (!_cupsThreadCreate((_cups_thread_func_t
)process_job
, job
))
4186 job
->state
= IPP_JSTATE_ABORTED
;
4187 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
, "Unable to process job.");
4196 * Return the job info...
4199 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
4201 ra
= cupsArrayNew((cups_array_func_t
)strcmp
, NULL
);
4202 cupsArrayAdd(ra
, "job-id");
4203 cupsArrayAdd(ra
, "job-state");
4204 cupsArrayAdd(ra
, "job-state-reasons");
4205 cupsArrayAdd(ra
, "job-uri");
4207 copy_job_attributes(client
, job
, ra
);
4208 cupsArrayDelete(ra
);
4213 * 'ipp_send_document()' - Add an attached document to a job object created with
4218 ipp_send_document(_ipp_client_t
*client
)/* I - Client */
4220 _ipp_job_t
*job
; /* Job information */
4221 char filename
[1024], /* Filename buffer */
4222 buffer
[4096]; /* Copy buffer */
4223 ssize_t bytes
; /* Bytes read */
4224 ipp_attribute_t
*attr
; /* Current attribute */
4225 cups_array_t
*ra
; /* Attributes to send in response */
4232 if ((job
= find_job(client
)) == NULL
)
4234 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "Job does not exist.");
4235 httpFlush(client
->http
);
4240 * See if we already have a document for this job or the job has already
4241 * in a non-pending state...
4244 if (job
->state
> IPP_JSTATE_HELD
)
4246 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
4247 "Job is not in a pending state.");
4248 httpFlush(client
->http
);
4251 else if (job
->filename
|| job
->fd
>= 0)
4253 respond_ipp(client
, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED
,
4254 "Multiple document jobs are not supported.");
4255 httpFlush(client
->http
);
4259 if ((attr
= ippFindAttribute(client
->request
, "last-document",
4260 IPP_TAG_ZERO
)) == NULL
)
4262 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
4263 "Missing required last-document attribute.");
4264 httpFlush(client
->http
);
4267 else if (ippGetValueTag(attr
) != IPP_TAG_BOOLEAN
|| ippGetCount(attr
) != 1 ||
4268 !ippGetBoolean(attr
, 0))
4270 respond_unsupported(client
, attr
);
4271 httpFlush(client
->http
);
4276 * Validate document attributes...
4279 if (!valid_doc_attributes(client
))
4281 httpFlush(client
->http
);
4285 copy_attributes(job
->attrs
, client
->request
, NULL
, IPP_TAG_JOB
, 0);
4288 * Get the document format for the job...
4291 _cupsRWLockWrite(&(client
->printer
->rwlock
));
4293 if ((attr
= ippFindAttribute(job
->attrs
, "document-format-detected", IPP_TAG_MIMETYPE
)) != NULL
)
4294 job
->format
= ippGetString(attr
, 0, NULL
);
4295 else if ((attr
= ippFindAttribute(job
->attrs
, "document-format-supplied", IPP_TAG_MIMETYPE
)) != NULL
)
4296 job
->format
= ippGetString(attr
, 0, NULL
);
4298 job
->format
= "application/octet-stream";
4301 * Create a file for the request data...
4304 create_job_filename(client
->printer
, job
, filename
, sizeof(filename
));
4307 fprintf(stderr
, "Creating job file \"%s\", format \"%s\".\n", filename
, job
->format
);
4309 job
->fd
= open(filename
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600);
4311 _cupsRWUnlock(&(client
->printer
->rwlock
));
4315 job
->state
= IPP_JSTATE_ABORTED
;
4317 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4318 "Unable to create print file: %s", strerror(errno
));
4322 while ((bytes
= httpRead2(client
->http
, buffer
, sizeof(buffer
))) > 0)
4324 if (write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
4326 int error
= errno
; /* Write error */
4328 job
->state
= IPP_JSTATE_ABORTED
;
4335 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4336 "Unable to write print file: %s", strerror(error
));
4344 * Got an error while reading the print data, so abort this job.
4347 job
->state
= IPP_JSTATE_ABORTED
;
4354 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4355 "Unable to read print file.");
4361 int error
= errno
; /* Write error */
4363 job
->state
= IPP_JSTATE_ABORTED
;
4368 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4369 "Unable to write print file: %s", strerror(error
));
4373 _cupsRWLockWrite(&(client
->printer
->rwlock
));
4376 job
->filename
= strdup(filename
);
4377 job
->state
= IPP_JSTATE_PENDING
;
4379 _cupsRWUnlock(&(client
->printer
->rwlock
));
4382 * Process the job...
4386 if (!_cupsThreadCreate((_cups_thread_func_t
)process_job
, job
))
4388 job
->state
= IPP_JSTATE_ABORTED
;
4389 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
, "Unable to process job.");
4398 * Return the job info...
4401 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
4403 ra
= cupsArrayNew((cups_array_func_t
)strcmp
, NULL
);
4404 cupsArrayAdd(ra
, "job-id");
4405 cupsArrayAdd(ra
, "job-state");
4406 cupsArrayAdd(ra
, "job-state-reasons");
4407 cupsArrayAdd(ra
, "job-uri");
4409 copy_job_attributes(client
, job
, ra
);
4410 cupsArrayDelete(ra
);
4415 * 'ipp_send_uri()' - Add a referenced document to a job object created with
4420 ipp_send_uri(_ipp_client_t
*client
) /* I - Client */
4422 _ipp_job_t
*job
; /* Job information */
4423 ipp_attribute_t
*uri
; /* document-uri */
4424 char scheme
[256], /* URI scheme */
4425 userpass
[256], /* Username and password info */
4426 hostname
[256], /* Hostname */
4427 resource
[1024]; /* Resource path */
4428 int port
; /* Port number */
4429 http_uri_status_t uri_status
; /* URI decode status */
4430 http_encryption_t encryption
; /* Encryption to use, if any */
4431 http_t
*http
; /* Connection for http/https URIs */
4432 http_status_t status
; /* Access status for http/https URIs */
4433 int infile
; /* Input file for local file URIs */
4434 char filename
[1024], /* Filename buffer */
4435 buffer
[4096]; /* Copy buffer */
4436 ssize_t bytes
; /* Bytes read */
4437 ipp_attribute_t
*attr
; /* Current attribute */
4438 cups_array_t
*ra
; /* Attributes to send in response */
4439 static const char * const uri_status_strings
[] =
4440 { /* URI decode errors */
4442 "Bad arguments to function.",
4443 "Bad resource in URI.",
4444 "Bad port number in URI.",
4445 "Bad hostname in URI.",
4446 "Bad username in URI.",
4447 "Bad scheme in URI.",
4456 if ((job
= find_job(client
)) == NULL
)
4458 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "Job does not exist.");
4459 httpFlush(client
->http
);
4464 * See if we already have a document for this job or the job has already
4465 * in a non-pending state...
4468 if (job
->state
> IPP_JSTATE_HELD
)
4470 respond_ipp(client
, IPP_STATUS_ERROR_NOT_POSSIBLE
,
4471 "Job is not in a pending state.");
4472 httpFlush(client
->http
);
4475 else if (job
->filename
|| job
->fd
>= 0)
4477 respond_ipp(client
, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED
,
4478 "Multiple document jobs are not supported.");
4479 httpFlush(client
->http
);
4483 if ((attr
= ippFindAttribute(client
->request
, "last-document",
4484 IPP_TAG_ZERO
)) == NULL
)
4486 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
4487 "Missing required last-document attribute.");
4488 httpFlush(client
->http
);
4491 else if (ippGetValueTag(attr
) != IPP_TAG_BOOLEAN
|| ippGetCount(attr
) != 1 ||
4492 !ippGetBoolean(attr
, 0))
4494 respond_unsupported(client
, attr
);
4495 httpFlush(client
->http
);
4500 * Validate document attributes...
4503 if (!valid_doc_attributes(client
))
4505 httpFlush(client
->http
);
4510 * Do we have a file to print?
4513 if (httpGetState(client
->http
) == HTTP_STATE_POST_RECV
)
4515 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
4516 "Unexpected document data following request.");
4521 * Do we have a document URI?
4524 if ((uri
= ippFindAttribute(client
->request
, "document-uri",
4525 IPP_TAG_URI
)) == NULL
)
4527 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "Missing document-uri.");
4531 if (ippGetCount(uri
) != 1)
4533 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
4534 "Too many document-uri values.");
4538 uri_status
= httpSeparateURI(HTTP_URI_CODING_ALL
, ippGetString(uri
, 0, NULL
),
4539 scheme
, sizeof(scheme
), userpass
,
4540 sizeof(userpass
), hostname
, sizeof(hostname
),
4541 &port
, resource
, sizeof(resource
));
4542 if (uri_status
< HTTP_URI_STATUS_OK
)
4544 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "Bad document-uri: %s",
4545 uri_status_strings
[uri_status
- HTTP_URI_STATUS_OVERFLOW
]);
4549 if (strcmp(scheme
, "file") &&
4551 strcmp(scheme
, "https") &&
4552 #endif /* HAVE_SSL */
4553 strcmp(scheme
, "http"))
4555 respond_ipp(client
, IPP_STATUS_ERROR_URI_SCHEME
,
4556 "URI scheme \"%s\" not supported.", scheme
);
4560 if (!strcmp(scheme
, "file") && access(resource
, R_OK
))
4562 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4563 "Unable to access URI: %s", strerror(errno
));
4568 * Get the document format for the job...
4571 _cupsRWLockWrite(&(client
->printer
->rwlock
));
4573 if ((attr
= ippFindAttribute(job
->attrs
, "document-format",
4574 IPP_TAG_MIMETYPE
)) != NULL
)
4575 job
->format
= ippGetString(attr
, 0, NULL
);
4577 job
->format
= "application/octet-stream";
4580 * Create a file for the request data...
4583 if (!strcasecmp(job
->format
, "image/jpeg"))
4584 snprintf(filename
, sizeof(filename
), "%s/%d.jpg",
4585 client
->printer
->directory
, job
->id
);
4586 else if (!strcasecmp(job
->format
, "image/png"))
4587 snprintf(filename
, sizeof(filename
), "%s/%d.png",
4588 client
->printer
->directory
, job
->id
);
4589 else if (!strcasecmp(job
->format
, "application/pdf"))
4590 snprintf(filename
, sizeof(filename
), "%s/%d.pdf",
4591 client
->printer
->directory
, job
->id
);
4592 else if (!strcasecmp(job
->format
, "application/postscript"))
4593 snprintf(filename
, sizeof(filename
), "%s/%d.ps",
4594 client
->printer
->directory
, job
->id
);
4596 snprintf(filename
, sizeof(filename
), "%s/%d.prn",
4597 client
->printer
->directory
, job
->id
);
4599 job
->fd
= open(filename
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600);
4601 _cupsRWUnlock(&(client
->printer
->rwlock
));
4605 job
->state
= IPP_JSTATE_ABORTED
;
4607 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4608 "Unable to create print file: %s", strerror(errno
));
4612 if (!strcmp(scheme
, "file"))
4614 if ((infile
= open(resource
, O_RDONLY
)) < 0)
4616 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4617 "Unable to access URI: %s", strerror(errno
));
4623 if ((bytes
= read(infile
, buffer
, sizeof(buffer
))) < 0 &&
4624 (errno
== EAGAIN
|| errno
== EINTR
))
4626 else if (bytes
> 0 && write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
4628 int error
= errno
; /* Write error */
4630 job
->state
= IPP_JSTATE_ABORTED
;
4638 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4639 "Unable to write print file: %s", strerror(error
));
4650 if (port
== 443 || !strcmp(scheme
, "https"))
4651 encryption
= HTTP_ENCRYPTION_ALWAYS
;
4653 #endif /* HAVE_SSL */
4654 encryption
= HTTP_ENCRYPTION_IF_REQUESTED
;
4656 if ((http
= httpConnect2(hostname
, port
, NULL
, AF_UNSPEC
, encryption
,
4657 1, 30000, NULL
)) == NULL
)
4659 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4660 "Unable to connect to %s: %s", hostname
,
4661 cupsLastErrorString());
4662 job
->state
= IPP_JSTATE_ABORTED
;
4671 httpClearFields(http
);
4672 httpSetField(http
, HTTP_FIELD_ACCEPT_LANGUAGE
, "en");
4673 if (httpGet(http
, resource
))
4675 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4676 "Unable to GET URI: %s", strerror(errno
));
4678 job
->state
= IPP_JSTATE_ABORTED
;
4688 while ((status
= httpUpdate(http
)) == HTTP_STATUS_CONTINUE
);
4690 if (status
!= HTTP_STATUS_OK
)
4692 respond_ipp(client
, IPP_STATUS_ERROR_DOCUMENT_ACCESS
,
4693 "Unable to GET URI: %s", httpStatus(status
));
4695 job
->state
= IPP_JSTATE_ABORTED
;
4705 while ((bytes
= httpRead2(http
, buffer
, sizeof(buffer
))) > 0)
4707 if (write(job
->fd
, buffer
, (size_t)bytes
) < bytes
)
4709 int error
= errno
; /* Write error */
4711 job
->state
= IPP_JSTATE_ABORTED
;
4719 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4720 "Unable to write print file: %s", strerror(error
));
4730 int error
= errno
; /* Write error */
4732 job
->state
= IPP_JSTATE_ABORTED
;
4737 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
,
4738 "Unable to write print file: %s", strerror(error
));
4742 _cupsRWLockWrite(&(client
->printer
->rwlock
));
4745 job
->filename
= strdup(filename
);
4746 job
->state
= IPP_JSTATE_PENDING
;
4748 _cupsRWUnlock(&(client
->printer
->rwlock
));
4751 * Process the job...
4755 if (!_cupsThreadCreate((_cups_thread_func_t
)process_job
, job
))
4757 job
->state
= IPP_JSTATE_ABORTED
;
4758 respond_ipp(client
, IPP_STATUS_ERROR_INTERNAL
, "Unable to process job.");
4767 * Return the job info...
4770 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
4772 ra
= cupsArrayNew((cups_array_func_t
)strcmp
, NULL
);
4773 cupsArrayAdd(ra
, "job-id");
4774 cupsArrayAdd(ra
, "job-state");
4775 cupsArrayAdd(ra
, "job-state-reasons");
4776 cupsArrayAdd(ra
, "job-uri");
4778 copy_job_attributes(client
, job
, ra
);
4779 cupsArrayDelete(ra
);
4784 * 'ipp_validate_job()' - Validate job creation attributes.
4788 ipp_validate_job(_ipp_client_t
*client
) /* I - Client */
4790 if (valid_job_attributes(client
))
4791 respond_ipp(client
, IPP_STATUS_OK
, NULL
);
4796 * 'load_attributes()' - Load printer attributes from a file.
4798 * Syntax is based on ipptool format:
4800 * ATTR value-tag name value
4804 load_attributes(const char *filename
, /* I - File to load */
4805 ipp_t
*attrs
) /* I - Printer attributes */
4807 int linenum
= 0; /* Current line number */
4808 FILE *fp
= NULL
; /* Test file */
4809 char attr
[128], /* Attribute name */
4810 token
[1024], /* Token from file */
4811 *tokenptr
; /* Pointer into token */
4812 ipp_tag_t value
; /* Current value type */
4813 ipp_attribute_t
*attrptr
, /* Attribute pointer */
4814 *lastcol
= NULL
; /* Last collection attribute */
4817 if ((fp
= fopen(filename
, "r")) == NULL
)
4819 fprintf(stderr
, "ippserver: Unable to open \"%s\": %s\n", filename
, strerror(errno
));
4823 while (get_token(fp
, token
, sizeof(token
), &linenum
) != NULL
)
4825 if (!_cups_strcasecmp(token
, "ATTR"))
4831 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
4833 fprintf(stderr
, "ippserver: Missing ATTR value tag on line %d of \"%s\".\n", linenum
, filename
);
4837 if ((value
= ippTagValue(token
)) == IPP_TAG_ZERO
)
4839 fprintf(stderr
, "ippserver: Bad ATTR value tag \"%s\" on line %d of \"%s\".\n", token
, linenum
, filename
);
4843 if (!get_token(fp
, attr
, sizeof(attr
), &linenum
))
4845 fprintf(stderr
, "ippserver: Missing ATTR name on line %d of \"%s\".\n", linenum
, filename
);
4849 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
4851 fprintf(stderr
, "ippserver: Missing ATTR value on line %d of \"%s\".\n", linenum
, filename
);
4859 case IPP_TAG_BOOLEAN
:
4860 if (!_cups_strcasecmp(token
, "true"))
4861 attrptr
= ippAddBoolean(attrs
, IPP_TAG_PRINTER
, attr
, 1);
4863 attrptr
= ippAddBoolean(attrs
, IPP_TAG_PRINTER
, attr
, (char)atoi(token
));
4866 case IPP_TAG_INTEGER
:
4868 if (!strchr(token
, ','))
4869 attrptr
= ippAddInteger(attrs
, IPP_TAG_PRINTER
, value
, attr
, (int)strtol(token
, &tokenptr
, 0));
4872 int values
[100], /* Values */
4873 num_values
= 1; /* Number of values */
4875 values
[0] = (int)strtol(token
, &tokenptr
, 10);
4876 while (tokenptr
&& *tokenptr
&&
4877 num_values
< (int)(sizeof(values
) / sizeof(values
[0])))
4879 if (*tokenptr
== ',')
4881 else if (!isdigit(*tokenptr
& 255) && *tokenptr
!= '-')
4884 values
[num_values
] = (int)strtol(tokenptr
, &tokenptr
, 0);
4888 attrptr
= ippAddIntegers(attrs
, IPP_TAG_PRINTER
, value
, attr
, num_values
, values
);
4891 if (!tokenptr
|| *tokenptr
)
4893 fprintf(stderr
, "ippserver: Bad %s value \"%s\" on line %d of \"%s\".\n", ippTagString(value
), token
, linenum
, filename
);
4898 case IPP_TAG_RESOLUTION
:
4900 int xres
, /* X resolution */
4901 yres
; /* Y resolution */
4902 ipp_res_t units
; /* Units */
4903 char *start
, /* Start of value */
4904 *ptr
, /* Pointer into value */
4905 *next
= NULL
; /* Next value */
4907 for (start
= token
; start
; start
= next
)
4909 xres
= yres
= (int)strtol(start
, (char **)&ptr
, 10);
4910 if (ptr
> start
&& xres
> 0)
4913 yres
= (int)strtol(ptr
+ 1, (char **)&ptr
, 10);
4916 if (ptr
&& (next
= strchr(ptr
, ',')) != NULL
)
4919 if (ptr
<= start
|| xres
<= 0 || yres
<= 0 || !ptr
||
4920 (_cups_strcasecmp(ptr
, "dpi") &&
4921 _cups_strcasecmp(ptr
, "dpc") &&
4922 _cups_strcasecmp(ptr
, "dpcm") &&
4923 _cups_strcasecmp(ptr
, "other")))
4925 fprintf(stderr
, "ippserver: Bad resolution value \"%s\" on line %d of \"%s\".\n", token
, linenum
, filename
);
4929 if (!_cups_strcasecmp(ptr
, "dpc") || !_cups_strcasecmp(ptr
, "dpcm"))
4930 units
= IPP_RES_PER_CM
;
4932 units
= IPP_RES_PER_INCH
;
4935 ippSetResolution(attrs
, &attrptr
, ippGetCount(attrptr
), units
, xres
, yres
);
4937 attrptr
= ippAddResolution(attrs
, IPP_TAG_PRINTER
, attr
, units
, xres
, yres
);
4942 case IPP_TAG_RANGE
:
4944 int lowers
[4], /* Lower value */
4945 uppers
[4], /* Upper values */
4946 num_vals
; /* Number of values */
4949 num_vals
= sscanf(token
, "%d-%d,%d-%d,%d-%d,%d-%d",
4950 lowers
+ 0, uppers
+ 0,
4951 lowers
+ 1, uppers
+ 1,
4952 lowers
+ 2, uppers
+ 2,
4953 lowers
+ 3, uppers
+ 3);
4955 if ((num_vals
& 1) || num_vals
== 0)
4957 fprintf(stderr
, "ippserver: Bad rangeOfInteger value \"%s\" on line %d of \"%s\".", token
, linenum
, filename
);
4961 attrptr
= ippAddRanges(attrs
, IPP_TAG_PRINTER
, attr
, num_vals
/ 2, lowers
,
4966 case IPP_TAG_BEGIN_COLLECTION
:
4967 if (!strcmp(token
, "{"))
4969 ipp_t
*col
= get_collection(fp
, filename
, &linenum
);
4970 /* Collection value */
4974 attrptr
= lastcol
= ippAddCollection(attrs
, IPP_TAG_PRINTER
, attr
, col
);
4982 fprintf(stderr
, "ippserver: Bad ATTR collection value on line %d of \"%s\".\n", linenum
, filename
);
4988 ipp_t
*col
; /* Collection value */
4989 long pos
= ftell(fp
); /* Save position of file */
4991 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
4994 if (strcmp(token
, ","))
4996 fseek(fp
, pos
, SEEK_SET
);
5000 if (!get_token(fp
, token
, sizeof(token
), &linenum
) || strcmp(token
, "{"))
5002 fprintf(stderr
, "ippserver: Unexpected \"%s\" on line %d of \"%s\".\n", token
, linenum
, filename
);
5006 if ((col
= get_collection(fp
, filename
, &linenum
)) == NULL
)
5009 ippSetCollection(attrs
, &attrptr
, ippGetCount(attrptr
), col
);
5012 while (!strcmp(token
, "{"));
5015 case IPP_TAG_STRING
:
5016 attrptr
= ippAddOctetString(attrs
, IPP_TAG_PRINTER
, attr
, token
, (int)strlen(token
));
5020 fprintf(stderr
, "ippserver: Unsupported ATTR value tag %s on line %d of \"%s\".\n", ippTagString(value
), linenum
, filename
);
5023 case IPP_TAG_TEXTLANG
:
5024 case IPP_TAG_NAMELANG
:
5027 case IPP_TAG_KEYWORD
:
5029 case IPP_TAG_URISCHEME
:
5030 case IPP_TAG_CHARSET
:
5031 case IPP_TAG_LANGUAGE
:
5032 case IPP_TAG_MIMETYPE
:
5033 if (!strchr(token
, ','))
5034 attrptr
= ippAddString(attrs
, IPP_TAG_PRINTER
, value
, attr
, NULL
, token
);
5038 * Multiple string values...
5041 int num_values
; /* Number of values */
5042 char *values
[100], /* Values */
5043 *ptr
; /* Pointer to next value */
5049 for (ptr
= strchr(token
, ','); ptr
; ptr
= strchr(ptr
, ','))
5051 if (ptr
> token
&& ptr
[-1] == '\\')
5052 _cups_strcpy(ptr
- 1, ptr
);
5056 values
[num_values
] = ptr
;
5058 if (num_values
>= (int)(sizeof(values
) / sizeof(values
[0])))
5063 attrptr
= ippAddStrings(attrs
, IPP_TAG_PRINTER
, value
, attr
, num_values
, NULL
, (const char **)values
);
5070 fprintf(stderr
, "ippserver: Unable to add attribute on line %d of \"%s\": %s\n", linenum
, filename
, cupsLastErrorString());
5076 fprintf(stderr
, "ippserver: Unknown directive \"%s\" on line %d of \"%s\".\n", token
, linenum
, filename
);
5086 * 'parse_options()' - Parse URL options into CUPS options.
5088 * The client->options string is destroyed by this function.
5091 static int /* O - Number of options */
5092 parse_options(_ipp_client_t
*client
, /* I - Client */
5093 cups_option_t
**options
) /* O - Options */
5095 char *name
, /* Name */
5097 *next
; /* Next name=value pair */
5098 int num_options
= 0; /* Number of options */
5103 for (name
= client
->options
; name
&& *name
; name
= next
)
5105 if ((value
= strchr(name
, '=')) == NULL
)
5109 if ((next
= strchr(value
, '&')) != NULL
)
5112 num_options
= cupsAddOption(name
, value
, num_options
, options
);
5115 return (num_options
);
5120 * 'process_attr_message()' - Process an ATTR: message from a command.
5124 process_attr_message(
5125 _ipp_job_t
*job
, /* I - Job */
5126 char *message
) /* I - Message */
5134 * 'process_client()' - Process client requests on a thread.
5137 static void * /* O - Exit status */
5138 process_client(_ipp_client_t
*client
) /* I - Client */
5141 * Loop until we are out of requests or timeout (30 seconds)...
5145 int first_time
= 1; /* First time request? */
5146 #endif /* HAVE_SSL */
5148 while (httpWait(client
->http
, 30000))
5154 * See if we need to negotiate a TLS connection...
5157 char buf
[1]; /* First byte from client */
5159 if (recv(httpGetFd(client
->http
), buf
, 1, MSG_PEEK
) == 1 && (!buf
[0] || !strchr("DGHOPT", buf
[0])))
5161 fprintf(stderr
, "%s Starting HTTPS session.\n", client
->hostname
);
5163 if (httpEncryption(client
->http
, HTTP_ENCRYPTION_ALWAYS
))
5165 fprintf(stderr
, "%s Unable to encrypt connection: %s\n", client
->hostname
, cupsLastErrorString());
5169 fprintf(stderr
, "%s Connection now encrypted.\n", client
->hostname
);
5174 #endif /* HAVE_SSL */
5176 if (!process_http(client
))
5181 * Close the conection to the client and return...
5184 delete_client(client
);
5191 * 'process_http()' - Process a HTTP request.
5194 int /* O - 1 on success, 0 on failure */
5195 process_http(_ipp_client_t
*client
) /* I - Client connection */
5197 char uri
[1024]; /* URI */
5198 http_state_t http_state
; /* HTTP state */
5199 http_status_t http_status
; /* HTTP status */
5200 ipp_state_t ipp_state
; /* State of IPP transfer */
5201 char scheme
[32], /* Method/scheme */
5202 userpass
[128], /* Username:password */
5203 hostname
[HTTP_MAX_HOST
];
5205 int port
; /* Port number */
5206 const char *encoding
; /* Content-Encoding value */
5207 static const char * const http_states
[] =
5208 { /* Strings for logging HTTP method */
5229 * Clear state variables...
5232 ippDelete(client
->request
);
5233 ippDelete(client
->response
);
5235 client
->request
= NULL
;
5236 client
->response
= NULL
;
5237 client
->operation
= HTTP_STATE_WAITING
;
5240 * Read a request from the connection...
5243 while ((http_state
= httpReadRequest(client
->http
, uri
,
5244 sizeof(uri
))) == HTTP_STATE_WAITING
)
5248 * Parse the request line...
5251 if (http_state
== HTTP_STATE_ERROR
)
5253 if (httpError(client
->http
) == EPIPE
)
5254 fprintf(stderr
, "%s Client closed connection.\n", client
->hostname
);
5256 fprintf(stderr
, "%s Bad request line (%s).\n", client
->hostname
,
5257 strerror(httpError(client
->http
)));
5261 else if (http_state
== HTTP_STATE_UNKNOWN_METHOD
)
5263 fprintf(stderr
, "%s Bad/unknown operation.\n", client
->hostname
);
5264 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5267 else if (http_state
== HTTP_STATE_UNKNOWN_VERSION
)
5269 fprintf(stderr
, "%s Bad HTTP version.\n", client
->hostname
);
5270 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5274 fprintf(stderr
, "%s %s %s\n", client
->hostname
, http_states
[http_state
],
5278 * Separate the URI into its components...
5281 if (httpSeparateURI(HTTP_URI_CODING_MOST
, uri
, scheme
, sizeof(scheme
),
5282 userpass
, sizeof(userpass
),
5283 hostname
, sizeof(hostname
), &port
,
5284 client
->uri
, sizeof(client
->uri
)) < HTTP_URI_STATUS_OK
&&
5285 (http_state
!= HTTP_STATE_OPTIONS
|| strcmp(uri
, "*")))
5287 fprintf(stderr
, "%s Bad URI \"%s\".\n", client
->hostname
, uri
);
5288 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5292 if ((client
->options
= strchr(client
->uri
, '?')) != NULL
)
5293 *(client
->options
)++ = '\0';
5296 * Process the request...
5299 client
->start
= time(NULL
);
5300 client
->operation
= httpGetState(client
->http
);
5303 * Parse incoming parameters until the status changes...
5306 while ((http_status
= httpUpdate(client
->http
)) == HTTP_STATUS_CONTINUE
);
5308 if (http_status
!= HTTP_STATUS_OK
)
5310 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5314 if (!httpGetField(client
->http
, HTTP_FIELD_HOST
)[0] &&
5315 httpGetVersion(client
->http
) >= HTTP_VERSION_1_1
)
5318 * HTTP/1.1 and higher require the "Host:" field...
5321 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5326 * Handle HTTP Upgrade...
5329 if (!strcasecmp(httpGetField(client
->http
, HTTP_FIELD_CONNECTION
),
5333 if (strstr(httpGetField(client
->http
, HTTP_FIELD_UPGRADE
), "TLS/") != NULL
&& !httpIsEncrypted(client
->http
))
5335 if (!respond_http(client
, HTTP_STATUS_SWITCHING_PROTOCOLS
, NULL
, NULL
, 0))
5338 fprintf(stderr
, "%s Upgrading to encrypted connection.\n", client
->hostname
);
5340 if (httpEncryption(client
->http
, HTTP_ENCRYPTION_REQUIRED
))
5342 fprintf(stderr
, "%s Unable to encrypt connection: %s\n", client
->hostname
, cupsLastErrorString());
5346 fprintf(stderr
, "%s Connection now encrypted.\n", client
->hostname
);
5349 #endif /* HAVE_SSL */
5351 if (!respond_http(client
, HTTP_STATUS_NOT_IMPLEMENTED
, NULL
, NULL
, 0))
5356 * Handle HTTP Expect...
5359 if (httpGetExpect(client
->http
) &&
5360 (client
->operation
== HTTP_STATE_POST
||
5361 client
->operation
== HTTP_STATE_PUT
))
5363 if (httpGetExpect(client
->http
) == HTTP_STATUS_CONTINUE
)
5366 * Send 100-continue header...
5369 if (!respond_http(client
, HTTP_STATUS_CONTINUE
, NULL
, NULL
, 0))
5375 * Send 417-expectation-failed header...
5378 if (!respond_http(client
, HTTP_STATUS_EXPECTATION_FAILED
, NULL
, NULL
, 0))
5384 * Handle new transfers...
5387 encoding
= httpGetContentEncoding(client
->http
);
5389 switch (client
->operation
)
5391 case HTTP_STATE_OPTIONS
:
5393 * Do OPTIONS command...
5396 return (respond_http(client
, HTTP_STATUS_OK
, NULL
, NULL
, 0));
5398 case HTTP_STATE_HEAD
:
5399 if (!strcmp(client
->uri
, "/icon.png"))
5400 return (respond_http(client
, HTTP_STATUS_OK
, NULL
, "image/png", 0));
5401 else if (!strcmp(client
->uri
, "/") || !strcmp(client
->uri
, "/media") || !strcmp(client
->uri
, "/supplies"))
5402 return (respond_http(client
, HTTP_STATUS_OK
, NULL
, "text/html", 0));
5404 return (respond_http(client
, HTTP_STATUS_NOT_FOUND
, NULL
, NULL
, 0));
5406 case HTTP_STATE_GET
:
5407 if (!strcmp(client
->uri
, "/icon.png"))
5410 * Send PNG icon file.
5413 int fd
; /* Icon file */
5414 struct stat fileinfo
; /* Icon file information */
5415 char buffer
[4096]; /* Copy buffer */
5416 ssize_t bytes
; /* Bytes */
5418 fprintf(stderr
, "Icon file is \"%s\".\n", client
->printer
->icon
);
5420 if (!stat(client
->printer
->icon
, &fileinfo
) &&
5421 (fd
= open(client
->printer
->icon
, O_RDONLY
)) >= 0)
5423 if (!respond_http(client
, HTTP_STATUS_OK
, NULL
, "image/png",
5424 (size_t)fileinfo
.st_size
))
5430 while ((bytes
= read(fd
, buffer
, sizeof(buffer
))) > 0)
5431 httpWrite2(client
->http
, buffer
, (size_t)bytes
);
5433 httpFlushWrite(client
->http
);
5438 return (respond_http(client
, HTTP_STATUS_NOT_FOUND
, NULL
, NULL
, 0));
5440 else if (!strcmp(client
->uri
, "/"))
5443 * Show web status page...
5446 _ipp_job_t
*job
; /* Current job */
5447 int i
; /* Looping var */
5448 _ipp_preason_t reason
; /* Current reason */
5449 static const char * const reasons
[] =
5450 { /* Reason strings */
5453 "Input Tray Missing",
5454 "Marker Supply Empty",
5455 "Marker Supply Low",
5456 "Marker Waste Almost Full",
5457 "Marker Waste Full",
5469 if (!respond_http(client
, HTTP_STATUS_OK
, encoding
, "text/html", 0))
5472 html_header(client
, client
->printer
->name
);
5474 "<p><img align=\"right\" src=\"/icon.png\" width=\"64\" height=\"64\"><b>ippserver (" CUPS_SVERSION
")</b></p>\n"
5475 "<p>%s, %d job(s).", client
->printer
->state
== IPP_PSTATE_IDLE
? "Idle" : client
->printer
->state
== IPP_PSTATE_PROCESSING
? "Printing" : "Stopped", cupsArrayCount(client
->printer
->jobs
));
5476 for (i
= 0, reason
= 1; i
< (int)(sizeof(reasons
) / sizeof(reasons
[0])); i
++, reason
<<= 1)
5477 if (client
->printer
->state_reasons
& reason
)
5478 html_printf(client
, "\n<br> %s", reasons
[i
]);
5479 html_printf(client
, "</p>\n");
5481 if (cupsArrayCount(client
->printer
->jobs
) > 0)
5483 _cupsRWLockRead(&(client
->printer
->rwlock
));
5485 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");
5486 for (job
= (_ipp_job_t
*)cupsArrayFirst(client
->printer
->jobs
); job
; job
= (_ipp_job_t
*)cupsArrayNext(client
->printer
->jobs
))
5488 char when
[256], /* When job queued/started/finished */
5489 hhmmss
[64]; /* Time HH:MM:SS */
5493 case IPP_JSTATE_PENDING
:
5494 case IPP_JSTATE_HELD
:
5495 snprintf(when
, sizeof(when
), "Queued at %s", time_string(job
->created
, hhmmss
, sizeof(hhmmss
)));
5497 case IPP_JSTATE_PROCESSING
:
5498 case IPP_JSTATE_STOPPED
:
5499 snprintf(when
, sizeof(when
), "Started at %s", time_string(job
->processing
, hhmmss
, sizeof(hhmmss
)));
5501 case IPP_JSTATE_ABORTED
:
5502 snprintf(when
, sizeof(when
), "Aborted at %s", time_string(job
->completed
, hhmmss
, sizeof(hhmmss
)));
5504 case IPP_JSTATE_CANCELED
:
5505 snprintf(when
, sizeof(when
), "Canceled at %s", time_string(job
->completed
, hhmmss
, sizeof(hhmmss
)));
5507 case IPP_JSTATE_COMPLETED
:
5508 snprintf(when
, sizeof(when
), "Completed at %s", time_string(job
->completed
, hhmmss
, sizeof(hhmmss
)));
5512 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
);
5514 html_printf(client
, "</tbody></table>\n");
5516 _cupsRWUnlock(&(client
->printer
->rwlock
));
5518 html_footer(client
);
5522 else if (!strcmp(client
->uri
, "/media"))
5525 * Show web media page...
5528 int i
, /* Looping var */
5529 num_options
; /* Number of form options */
5530 cups_option_t
*options
; /* Form options */
5531 static const char * const sizes
[] =
5532 { /* Size strings */
5545 static const char * const types
[] =
5562 static const int sheets
[] = /* Number of sheets */
5571 if (!respond_http(client
, HTTP_STATUS_OK
, encoding
, "text/html", 0))
5574 html_header(client
, client
->printer
->name
);
5576 if ((num_options
= parse_options(client
, &options
)) > 0)
5579 * WARNING: A real printer/server implementation MUST NOT implement
5580 * media updates via a GET request - GET requests are supposed to be
5581 * idempotent (without side-effects) and we obviously are not
5582 * authenticating access here. This form is provided solely to
5583 * enable testing and development!
5586 const char *val
; /* Form value */
5588 if ((val
= cupsGetOption("main_size", num_options
, options
)) != NULL
)
5589 client
->printer
->main_size
= atoi(val
);
5590 if ((val
= cupsGetOption("main_type", num_options
, options
)) != NULL
)
5591 client
->printer
->main_type
= atoi(val
);
5592 if ((val
= cupsGetOption("main_level", num_options
, options
)) != NULL
)
5593 client
->printer
->main_level
= atoi(val
);
5595 if ((val
= cupsGetOption("envelope_size", num_options
, options
)) != NULL
)
5596 client
->printer
->envelope_size
= atoi(val
);
5597 if ((val
= cupsGetOption("envelope_level", num_options
, options
)) != NULL
)
5598 client
->printer
->envelope_level
= atoi(val
);
5600 if ((val
= cupsGetOption("photo_size", num_options
, options
)) != NULL
)
5601 client
->printer
->photo_size
= atoi(val
);
5602 if ((val
= cupsGetOption("photo_type", num_options
, options
)) != NULL
)
5603 client
->printer
->photo_type
= atoi(val
);
5604 if ((val
= cupsGetOption("photo_level", num_options
, options
)) != NULL
)
5605 client
->printer
->photo_level
= atoi(val
);
5607 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))
5608 client
->printer
->state_reasons
|= _IPP_PREASON_MEDIA_LOW
;
5610 client
->printer
->state_reasons
&= (_ipp_preason_t
)~_IPP_PREASON_MEDIA_LOW
;
5612 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
))
5614 client
->printer
->state_reasons
|= _IPP_PREASON_MEDIA_EMPTY
;
5615 if (client
->printer
->active_job
)
5616 client
->printer
->state_reasons
|= _IPP_PREASON_MEDIA_NEEDED
;
5619 client
->printer
->state_reasons
&= (_ipp_preason_t
)~(_IPP_PREASON_MEDIA_EMPTY
| _IPP_PREASON_MEDIA_NEEDED
);
5621 html_printf(client
, "<blockquote>Media updated.</blockquote>\n");
5624 html_printf(client
, "<form method=\"GET\" action=\"/media\">\n");
5626 html_printf(client
, "<table class=\"form\" summary=\"Media\">\n");
5627 html_printf(client
, "<tr><th>Main Tray:</th><td><select name=\"main_size\"><option value=\"-1\">None</option>");
5628 for (i
= 0; i
< (int)(sizeof(sizes
) / sizeof(sizes
[0])); i
++)
5629 if (!strstr(sizes
[i
], "Envelope") && !strstr(sizes
[i
], "Photo"))
5630 html_printf(client
, "<option value=\"%d\"%s>%s</option>", i
, i
== client
->printer
->main_size
? " selected" : "", sizes
[i
]);
5631 html_printf(client
, "</select> <select name=\"main_type\"><option value=\"-1\">None</option>");
5632 for (i
= 0; i
< (int)(sizeof(types
) / sizeof(types
[0])); i
++)
5633 if (!strstr(types
[i
], "Photo"))
5634 html_printf(client
, "<option value=\"%d\"%s>%s</option>", i
, i
== client
->printer
->main_type
? " selected" : "", types
[i
]);
5635 html_printf(client
, "</select> <select name=\"main_level\">");
5636 for (i
= 0; i
< (int)(sizeof(sheets
) / sizeof(sheets
[0])); i
++)
5637 html_printf(client
, "<option value=\"%d\"%s>%d sheets</option>", sheets
[i
], sheets
[i
] == client
->printer
->main_level
? " selected" : "", sheets
[i
]);
5638 html_printf(client
, "</select></td></tr>\n");
5641 "<tr><th>Envelope Feeder:</th><td><select name=\"envelope_size\"><option value=\"-1\">None</option>");
5642 for (i
= 0; i
< (int)(sizeof(sizes
) / sizeof(sizes
[0])); i
++)
5643 if (strstr(sizes
[i
], "Envelope"))
5644 html_printf(client
, "<option value=\"%d\"%s>%s</option>", i
, i
== client
->printer
->envelope_size
? " selected" : "", sizes
[i
]);
5645 html_printf(client
, "</select> <select name=\"envelope_level\">");
5646 for (i
= 0; i
< (int)(sizeof(sheets
) / sizeof(sheets
[0])); i
++)
5647 html_printf(client
, "<option value=\"%d\"%s>%d sheets</option>", sheets
[i
], sheets
[i
] == client
->printer
->envelope_level
? " selected" : "", sheets
[i
]);
5648 html_printf(client
, "</select></td></tr>\n");
5651 "<tr><th>Photo Tray:</th><td><select name=\"photo_size\"><option value=\"-1\">None</option>");
5652 for (i
= 0; i
< (int)(sizeof(sizes
) / sizeof(sizes
[0])); i
++)
5653 if (strstr(sizes
[i
], "Photo"))
5654 html_printf(client
, "<option value=\"%d\"%s>%s</option>", i
, i
== client
->printer
->photo_size
? " selected" : "", sizes
[i
]);
5655 html_printf(client
, "</select> <select name=\"photo_type\"><option value=\"-1\">None</option>");
5656 for (i
= 0; i
< (int)(sizeof(types
) / sizeof(types
[0])); i
++)
5657 if (strstr(types
[i
], "Photo"))
5658 html_printf(client
, "<option value=\"%d\"%s>%s</option>", i
, i
== client
->printer
->photo_type
? " selected" : "", types
[i
]);
5659 html_printf(client
, "</select> <select name=\"photo_level\">");
5660 for (i
= 0; i
< (int)(sizeof(sheets
) / sizeof(sheets
[0])); i
++)
5661 html_printf(client
, "<option value=\"%d\"%s>%d sheets</option>", sheets
[i
], sheets
[i
] == client
->printer
->photo_level
? " selected" : "", sheets
[i
]);
5662 html_printf(client
, "</select></td></tr>\n");
5664 html_printf(client
, "<tr><td></td><td><input type=\"submit\" value=\"Update Media\"></td></tr></table></form>\n");
5665 html_footer(client
);
5669 else if (!strcmp(client
->uri
, "/supplies"))
5672 * Show web supplies page...
5675 int i
, j
, /* Looping vars */
5676 num_options
; /* Number of form options */
5677 cups_option_t
*options
; /* Form options */
5678 static const int levels
[] = { 0, 5, 10, 25, 50, 75, 90, 95, 100 };
5680 if (!respond_http(client
, HTTP_STATUS_OK
, encoding
, "text/html", 0))
5683 html_header(client
, client
->printer
->name
);
5685 if ((num_options
= parse_options(client
, &options
)) > 0)
5688 * WARNING: A real printer/server implementation MUST NOT implement
5689 * supply updates via a GET request - GET requests are supposed to be
5690 * idempotent (without side-effects) and we obviously are not
5691 * authenticating access here. This form is provided solely to
5692 * enable testing and development!
5695 char name
[64]; /* Form field */
5696 const char *val
; /* Form value */
5698 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
);
5700 for (i
= 0; i
< (int)(sizeof(printer_supplies
) / sizeof(printer_supplies
[0])); i
++)
5702 snprintf(name
, sizeof(name
), "supply_%d", i
);
5703 if ((val
= cupsGetOption(name
, num_options
, options
)) != NULL
)
5705 int level
= client
->printer
->supplies
[i
] = atoi(val
);
5711 client
->printer
->state_reasons
|= _IPP_PREASON_TONER_EMPTY
;
5712 else if (level
< 10)
5713 client
->printer
->state_reasons
|= _IPP_PREASON_TONER_LOW
;
5718 client
->printer
->state_reasons
|= _IPP_PREASON_MARKER_WASTE_FULL
;
5719 else if (level
> 90)
5720 client
->printer
->state_reasons
|= _IPP_PREASON_MARKER_WASTE_ALMOST_FULL
;
5725 html_printf(client
, "<blockquote>Supplies updated.</blockquote>\n");
5728 html_printf(client
, "<form method=\"GET\" action=\"/supplies\">\n");
5730 html_printf(client
, "<table class=\"form\" summary=\"Supplies\">\n");
5731 for (i
= 0; i
< (int)(sizeof(printer_supplies
) / sizeof(printer_supplies
[0])); i
++)
5733 html_printf(client
, "<tr><th>%s:</th><td><select name=\"supply_%d\">", printer_supplies
[i
], i
);
5734 for (j
= 0; j
< (int)(sizeof(levels
) / sizeof(levels
[0])); j
++)
5735 html_printf(client
, "<option value=\"%d\"%s>%d%%</option>", levels
[j
], levels
[j
] == client
->printer
->supplies
[i
] ? " selected" : "", levels
[j
]);
5736 html_printf(client
, "</select></td></tr>\n");
5738 html_printf(client
, "<tr><td></td><td><input type=\"submit\" value=\"Update Supplies\"></td></tr>\n</table>\n</form>\n");
5739 html_footer(client
);
5744 return (respond_http(client
, HTTP_STATUS_NOT_FOUND
, NULL
, NULL
, 0));
5747 case HTTP_STATE_POST
:
5748 if (strcmp(httpGetField(client
->http
, HTTP_FIELD_CONTENT_TYPE
),
5752 * Not an IPP request...
5755 return (respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0));
5759 * Read the IPP request...
5762 client
->request
= ippNew();
5764 while ((ipp_state
= ippRead(client
->http
,
5765 client
->request
)) != IPP_STATE_DATA
)
5767 if (ipp_state
== IPP_STATE_ERROR
)
5769 fprintf(stderr
, "%s IPP read error (%s).\n", client
->hostname
,
5770 cupsLastErrorString());
5771 respond_http(client
, HTTP_STATUS_BAD_REQUEST
, NULL
, NULL
, 0);
5777 * Now that we have the IPP request, process the request...
5780 return (process_ipp(client
));
5783 break; /* Anti-compiler-warning-code */
5791 * 'process_ipp()' - Process an IPP request.
5794 static int /* O - 1 on success, 0 on error */
5795 process_ipp(_ipp_client_t
*client
) /* I - Client */
5797 ipp_tag_t group
; /* Current group tag */
5798 ipp_attribute_t
*attr
; /* Current attribute */
5799 ipp_attribute_t
*charset
; /* Character set attribute */
5800 ipp_attribute_t
*language
; /* Language attribute */
5801 ipp_attribute_t
*uri
; /* Printer URI attribute */
5802 int major
, minor
; /* Version number */
5803 const char *name
; /* Name of attribute */
5806 debug_attributes("Request", client
->request
, 1);
5809 * First build an empty response message for this request...
5812 client
->operation_id
= ippGetOperation(client
->request
);
5813 client
->response
= ippNewResponse(client
->request
);
5816 * Then validate the request header and required attributes...
5819 major
= ippGetVersion(client
->request
, &minor
);
5821 if (major
< 1 || major
> 2)
5824 * Return an error, since we only support IPP 1.x and 2.x.
5827 respond_ipp(client
, IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED
,
5828 "Bad request version number %d.%d.", major
, minor
);
5830 else if (ippGetRequestId(client
->request
) <= 0)
5831 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
, "Bad request-id %d.",
5832 ippGetRequestId(client
->request
));
5833 else if (!ippFirstAttribute(client
->request
))
5834 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
5835 "No attributes in request.");
5839 * Make sure that the attributes are provided in the correct order and
5840 * don't repeat groups...
5843 for (attr
= ippFirstAttribute(client
->request
),
5844 group
= ippGetGroupTag(attr
);
5846 attr
= ippNextAttribute(client
->request
))
5848 if (ippGetGroupTag(attr
) < group
&& ippGetGroupTag(attr
) != IPP_TAG_ZERO
)
5851 * Out of order; return an error...
5854 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
5855 "Attribute groups are out of order (%x < %x).",
5856 ippGetGroupTag(attr
), group
);
5860 group
= ippGetGroupTag(attr
);
5866 * Then make sure that the first three attributes are:
5868 * attributes-charset
5869 * attributes-natural-language
5870 * printer-uri/job-uri
5873 attr
= ippFirstAttribute(client
->request
);
5874 name
= ippGetName(attr
);
5875 if (attr
&& name
&& !strcmp(name
, "attributes-charset") &&
5876 ippGetValueTag(attr
) == IPP_TAG_CHARSET
)
5881 attr
= ippNextAttribute(client
->request
);
5882 name
= ippGetName(attr
);
5884 if (attr
&& name
&& !strcmp(name
, "attributes-natural-language") &&
5885 ippGetValueTag(attr
) == IPP_TAG_LANGUAGE
)
5890 if ((attr
= ippFindAttribute(client
->request
, "printer-uri",
5891 IPP_TAG_URI
)) != NULL
)
5893 else if ((attr
= ippFindAttribute(client
->request
, "job-uri",
5894 IPP_TAG_URI
)) != NULL
)
5900 strcasecmp(ippGetString(charset
, 0, NULL
), "us-ascii") &&
5901 strcasecmp(ippGetString(charset
, 0, NULL
), "utf-8"))
5904 * Bad character set...
5907 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
5908 "Unsupported character set \"%s\".",
5909 ippGetString(charset
, 0, NULL
));
5911 else if (!charset
|| !language
|| !uri
)
5914 * Return an error, since attributes-charset,
5915 * attributes-natural-language, and printer-uri/job-uri are required
5916 * for all operations.
5919 respond_ipp(client
, IPP_STATUS_ERROR_BAD_REQUEST
,
5920 "Missing required attributes.");
5924 char scheme
[32], /* URI scheme */
5925 userpass
[32], /* Username/password in URI */
5926 host
[256], /* Host name in URI */
5927 resource
[256]; /* Resource path in URI */
5928 int port
; /* Port number in URI */
5930 name
= ippGetName(uri
);
5932 if (httpSeparateURI(HTTP_URI_CODING_ALL
, ippGetString(uri
, 0, NULL
),
5933 scheme
, sizeof(scheme
),
5934 userpass
, sizeof(userpass
),
5935 host
, sizeof(host
), &port
,
5936 resource
, sizeof(resource
)) < HTTP_URI_STATUS_OK
)
5937 respond_ipp(client
, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES
,
5938 "Bad %s value '%s'.", name
, ippGetString(uri
, 0, NULL
));
5939 else if ((!strcmp(name
, "job-uri") &&
5940 strncmp(resource
, "/ipp/print/", 11)) ||
5941 (!strcmp(name
, "printer-uri") &&
5942 strcmp(resource
, "/ipp/print")))
5943 respond_ipp(client
, IPP_STATUS_ERROR_NOT_FOUND
, "%s %s not found.",
5944 name
, ippGetString(uri
, 0, NULL
));
5948 * Try processing the operation...
5951 switch (ippGetOperation(client
->request
))
5953 case IPP_OP_PRINT_JOB
:
5954 ipp_print_job(client
);
5957 case IPP_OP_PRINT_URI
:
5958 ipp_print_uri(client
);
5961 case IPP_OP_VALIDATE_JOB
:
5962 ipp_validate_job(client
);
5965 case IPP_OP_CREATE_JOB
:
5966 ipp_create_job(client
);
5969 case IPP_OP_SEND_DOCUMENT
:
5970 ipp_send_document(client
);
5973 case IPP_OP_SEND_URI
:
5974 ipp_send_uri(client
);
5977 case IPP_OP_CANCEL_JOB
:
5978 ipp_cancel_job(client
);
5981 case IPP_OP_GET_JOB_ATTRIBUTES
:
5982 ipp_get_job_attributes(client
);
5985 case IPP_OP_GET_JOBS
:
5986 ipp_get_jobs(client
);
5989 case IPP_OP_GET_PRINTER_ATTRIBUTES
:
5990 ipp_get_printer_attributes(client
);
5993 case IPP_OP_CLOSE_JOB
:
5994 ipp_close_job(client
);
5997 case IPP_OP_IDENTIFY_PRINTER
:
5998 ipp_identify_printer(client
);
6002 respond_ipp(client
, IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED
,
6003 "Operation not supported.");
6012 * Send the HTTP header and return...
6015 if (httpGetState(client
->http
) != HTTP_STATE_POST_SEND
)
6016 httpFlush(client
->http
); /* Flush trailing (junk) data */
6018 return (respond_http(client
, HTTP_STATUS_OK
, NULL
, "application/ipp",
6019 ippLength(client
->response
)));
6024 * 'process_job()' - Process a print job.
6027 static void * /* O - Thread exit status */
6028 process_job(_ipp_job_t
*job
) /* I - Job */
6030 job
->state
= IPP_JSTATE_PROCESSING
;
6031 job
->printer
->state
= IPP_PSTATE_PROCESSING
;
6032 job
->processing
= time(NULL
);
6034 while (job
->printer
->state_reasons
& _IPP_PREASON_MEDIA_EMPTY
)
6036 job
->printer
->state_reasons
|= _IPP_PREASON_MEDIA_NEEDED
;
6041 job
->printer
->state_reasons
&= (_ipp_preason_t
)~_IPP_PREASON_MEDIA_NEEDED
;
6043 if (job
->printer
->command
)
6046 * Execute a command with the job spool file and wait for it to complete...
6049 int pid
, /* Process ID */
6050 status
; /* Exit status */
6051 time_t start
, /* Start time */
6053 char *myargv
[3], /* Command-line arguments */
6054 *myenvp
[200]; /* Environment variables */
6055 int myenvc
; /* Number of environment variables */
6056 ipp_attribute_t
*attr
; /* Job attribute */
6057 char val
[1280], /* IPP_NAME=value */
6058 *valptr
; /* Pointer into string */
6060 int mypipe
[2]; /* Pipe for stderr */
6061 char line
[2048], /* Line from stderr */
6062 *ptr
, /* Pointer into line */
6063 *endptr
; /* End of line */
6064 ssize_t bytes
; /* Bytes read */
6067 fprintf(stderr
, "Running command \"%s %s\".\n", job
->printer
->command
,
6072 * Setup the command-line arguments...
6075 myargv
[0] = job
->printer
->command
;
6076 myargv
[1] = job
->filename
;
6080 * Copy the current environment, then add ENV variables for every Job
6084 for (myenvc
= 0; environ
[myenvc
] && myenvc
< (int)(sizeof(myenvp
) / sizeof(myenvp
[0]) - 1); myenvc
++)
6085 myenvp
[myenvc
] = strdup(environ
[myenvc
]);
6087 for (attr
= ippFirstAttribute(job
->attrs
); attr
&& myenvc
< (int)(sizeof(myenvp
) / sizeof(myenvp
[0]) - 1); attr
= ippNextAttribute(job
->attrs
))
6090 * Convert "attribute-name" to "IPP_ATTRIBUTE_NAME=" and then add the
6091 * value(s) from the attribute.
6094 const char *name
= ippGetName(attr
);
6103 while (*name
&& valptr
< (val
+ sizeof(val
) - 2))
6108 *valptr
++ = (char)toupper(*name
& 255);
6113 ippAttributeString(attr
, valptr
, sizeof(val
) - (size_t)(valptr
- val
));
6115 myenvp
[myenvc
++] = strdup(val
);
6117 myenvp
[myenvc
] = NULL
;
6120 * Now run the program...
6124 status
= _spawnvpe(_P_WAIT
, job
->printer
->command
, myargv
, myenvp
);
6129 perror("Unable to create pipe for stderr");
6130 mypipe
[0] = mypipe
[1] = -1;
6133 if ((pid
= fork()) == 0)
6136 * Child comes here...
6144 execve(job
->printer
->command
, myargv
, myenvp
);
6150 * Unable to fork process...
6153 perror("Unable to start job processing command");
6160 * Free memory used for environment...
6164 free(myenvp
[-- myenvc
]);
6169 * Free memory used for environment...
6173 free(myenvp
[-- myenvc
]);
6176 * If the pipe exists, read from it until EOF...
6184 while ((bytes
= read(mypipe
[0], endptr
, sizeof(line
) - (size_t)(endptr
- line
) - 1)) > 0)
6189 while ((ptr
= strchr(line
, '\n')) != NULL
)
6193 if (!strncmp(line
, "STATE:", 6))
6196 * Process printer-state-reasons keywords.
6199 process_state_message(job
, line
);
6201 else if (!strncmp(line
, "ATTR:", 5))
6204 * Process printer attribute update.
6207 process_attr_message(job
, line
);
6209 else if (Verbosity
> 1)
6210 fprintf(stderr
, "%s: %s\n", job
->printer
->command
, line
);
6214 memmove(line
, ptr
, (size_t)(endptr
- ptr
));
6224 * Wait for child to complete...
6227 # ifdef HAVE_WAITPID
6228 while (waitpid(pid
, &status
, 0) < 0);
6230 while (wait(&status
) < 0);
6231 # endif /* HAVE_WAITPID */
6238 if (WIFEXITED(status
))
6240 fprintf(stderr
, "Command \"%s\" exited with status %d.\n",
6241 job
->printer
->command
, WEXITSTATUS(status
));
6244 fprintf(stderr
, "Command \"%s\" terminated with signal %d.\n",
6245 job
->printer
->command
, WTERMSIG(status
));
6247 job
->state
= IPP_JSTATE_ABORTED
;
6249 else if (status
< 0)
6250 job
->state
= IPP_JSTATE_ABORTED
;
6252 fprintf(stderr
, "Command \"%s\" completed successfully.\n",
6253 job
->printer
->command
);
6256 * Make sure processing takes at least 5 seconds...
6260 if ((end
- start
) < 5)
6266 * Sleep for a random amount of time to simulate job processing.
6269 sleep((unsigned)(5 + (rand() % 11)));
6273 job
->state
= IPP_JSTATE_CANCELED
;
6274 else if (job
->state
== IPP_JSTATE_PROCESSING
)
6275 job
->state
= IPP_JSTATE_COMPLETED
;
6277 job
->completed
= time(NULL
);
6278 job
->printer
->state
= IPP_PSTATE_IDLE
;
6279 job
->printer
->active_job
= NULL
;
6286 * 'process_state_message()' - Process a STATE: message from a command.
6290 process_state_message(
6291 _ipp_job_t
*job
, /* I - Job */
6292 char *message
) /* I - Message */
6294 int i
; /* Looping var */
6295 _ipp_preason_t state_reasons
, /* printer-state-reasons values */
6296 bit
; /* Current reason bit */
6297 char *ptr
, /* Pointer into message */
6298 *next
; /* Next keyword in message */
6299 int remove
; /* Non-zero if we are removing keywords */
6303 * Skip leading "STATE:" and any whitespace...
6306 for (message
+= 6; *message
; message
++)
6307 if (*message
!= ' ' && *message
!= '\t')
6311 * Support the following forms of message:
6313 * "keyword[,keyword,...]" to set the printer-state-reasons value(s).
6315 * "-keyword[,keyword,...]" to remove keywords.
6317 * "+keyword[,keyword,...]" to add keywords.
6319 * Keywords may or may not have a suffix (-report, -warning, -error) per
6323 if (*message
== '-')
6326 state_reasons
= job
->printer
->state_reasons
;
6329 else if (*message
== '+')
6332 state_reasons
= job
->printer
->state_reasons
;
6338 state_reasons
= _IPP_PREASON_NONE
;
6343 if ((next
= strchr(message
, ',')) != NULL
)
6346 if ((ptr
= strstr(message
, "-error")) != NULL
)
6348 else if ((ptr
= strstr(message
, "-report")) != NULL
)
6350 else if ((ptr
= strstr(message
, "-warning")) != NULL
)
6353 for (i
= 0, bit
= 1; i
< (int)(sizeof(_ipp_preason_strings
) / sizeof(_ipp_preason_strings
[0])); i
++, bit
*= 2)
6355 if (!strcmp(message
, _ipp_preason_strings
[i
]))
6358 state_reasons
&= ~bit
;
6360 state_reasons
|= bit
;
6370 job
->printer
->state_reasons
= state_reasons
;
6375 * 'register_printer()' - Register a printer object via Bonjour.
6378 static int /* O - 1 on success, 0 on error */
6380 _ipp_printer_t
*printer
, /* I - Printer */
6381 const char *location
, /* I - Location */
6382 const char *make
, /* I - Manufacturer */
6383 const char *model
, /* I - Model name */
6384 const char *formats
, /* I - Supported formats */
6385 const char *adminurl
, /* I - Web interface URL */
6386 const char *uuid
, /* I - Printer UUID */
6387 int color
, /* I - 1 = color, 0 = monochrome */
6388 int duplex
, /* I - 1 = duplex, 0 = simplex */
6389 const char *subtype
) /* I - Service subtype */
6391 #if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
6392 _ipp_txt_t ipp_txt
; /* Bonjour IPP TXT record */
6393 #endif /* HAVE_DNSSD || HAVE_AVAHI */
6395 DNSServiceErrorType error
; /* Error from Bonjour */
6396 char make_model
[256],/* Make and model together */
6397 product
[256], /* Product string */
6398 regtype
[256]; /* Bonjour service type */
6402 * Build the TXT record for IPP...
6405 snprintf(make_model
, sizeof(make_model
), "%s %s", make
, model
);
6406 snprintf(product
, sizeof(product
), "(%s)", model
);
6408 TXTRecordCreate(&ipp_txt
, 1024, NULL
);
6409 TXTRecordSetValue(&ipp_txt
, "rp", 9, "ipp/print");
6410 TXTRecordSetValue(&ipp_txt
, "ty", (uint8_t)strlen(make_model
),
6412 TXTRecordSetValue(&ipp_txt
, "adminurl", (uint8_t)strlen(adminurl
),
6415 TXTRecordSetValue(&ipp_txt
, "note", (uint8_t)strlen(location
),
6417 TXTRecordSetValue(&ipp_txt
, "product", (uint8_t)strlen(product
),
6419 TXTRecordSetValue(&ipp_txt
, "pdl", (uint8_t)strlen(formats
),
6421 TXTRecordSetValue(&ipp_txt
, "Color", 1, color
? "T" : "F");
6422 TXTRecordSetValue(&ipp_txt
, "Duplex", 1, duplex
? "T" : "F");
6423 TXTRecordSetValue(&ipp_txt
, "usb_MFG", (uint8_t)strlen(make
),
6425 TXTRecordSetValue(&ipp_txt
, "usb_MDL", (uint8_t)strlen(model
),
6427 TXTRecordSetValue(&ipp_txt
, "UUID", (uint8_t)strlen(uuid
), uuid
);
6429 TXTRecordSetValue(&ipp_txt
, "TLS", 3, "1.2");
6430 # endif /* HAVE_SSL */
6431 if (strstr(formats
, "image/urf"))
6432 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");
6434 TXTRecordSetValue(&ipp_txt
, "txtvers", 1, "1");
6435 TXTRecordSetValue(&ipp_txt
, "qtotal", 1, "1");
6438 * Register the _printer._tcp (LPD) service type with a port number of 0 to
6439 * defend our service name but not actually support LPD...
6442 printer
->printer_ref
= DNSSDMaster
;
6444 if ((error
= DNSServiceRegister(&(printer
->printer_ref
),
6445 kDNSServiceFlagsShareConnection
,
6446 0 /* interfaceIndex */, printer
->dnssd_name
,
6447 "_printer._tcp", NULL
/* domain */,
6448 NULL
/* host */, 0 /* port */, 0 /* txtLen */,
6449 NULL
/* txtRecord */,
6450 (DNSServiceRegisterReply
)dnssd_callback
,
6451 printer
)) != kDNSServiceErr_NoError
)
6453 fprintf(stderr
, "Unable to register \"%s._printer._tcp\": %d\n",
6454 printer
->dnssd_name
, error
);
6459 * Then register the _ipp._tcp (IPP) service type with the real port number to
6460 * advertise our IPP printer...
6463 printer
->ipp_ref
= DNSSDMaster
;
6465 if (subtype
&& *subtype
)
6466 snprintf(regtype
, sizeof(regtype
), "_ipp._tcp,%s", subtype
);
6468 strlcpy(regtype
, "_ipp._tcp", sizeof(regtype
));
6470 if ((error
= DNSServiceRegister(&(printer
->ipp_ref
),
6471 kDNSServiceFlagsShareConnection
,
6472 0 /* interfaceIndex */, printer
->dnssd_name
,
6473 regtype
, NULL
/* domain */,
6474 NULL
/* host */, htons(printer
->port
),
6475 TXTRecordGetLength(&ipp_txt
),
6476 TXTRecordGetBytesPtr(&ipp_txt
),
6477 (DNSServiceRegisterReply
)dnssd_callback
,
6478 printer
)) != kDNSServiceErr_NoError
)
6480 fprintf(stderr
, "Unable to register \"%s.%s\": %d\n",
6481 printer
->dnssd_name
, regtype
, error
);
6487 * Then register the _ipps._tcp (IPP) service type with the real port number to
6488 * advertise our IPPS printer...
6491 printer
->ipps_ref
= DNSSDMaster
;
6493 if (subtype
&& *subtype
)
6494 snprintf(regtype
, sizeof(regtype
), "_ipps._tcp,%s", subtype
);
6496 strlcpy(regtype
, "_ipps._tcp", sizeof(regtype
));
6498 if ((error
= DNSServiceRegister(&(printer
->ipps_ref
),
6499 kDNSServiceFlagsShareConnection
,
6500 0 /* interfaceIndex */, printer
->dnssd_name
,
6501 regtype
, NULL
/* domain */,
6502 NULL
/* host */, htons(printer
->port
),
6503 TXTRecordGetLength(&ipp_txt
),
6504 TXTRecordGetBytesPtr(&ipp_txt
),
6505 (DNSServiceRegisterReply
)dnssd_callback
,
6506 printer
)) != kDNSServiceErr_NoError
)
6508 fprintf(stderr
, "Unable to register \"%s.%s\": %d\n",
6509 printer
->dnssd_name
, regtype
, error
);
6512 # endif /* HAVE_SSL */
6515 * Similarly, register the _http._tcp,_printer (HTTP) service type with the
6516 * real port number to advertise our IPP printer...
6519 printer
->http_ref
= DNSSDMaster
;
6521 if ((error
= DNSServiceRegister(&(printer
->http_ref
),
6522 kDNSServiceFlagsShareConnection
,
6523 0 /* interfaceIndex */, printer
->dnssd_name
,
6524 "_http._tcp,_printer", NULL
/* domain */,
6525 NULL
/* host */, htons(printer
->port
),
6526 0 /* txtLen */, NULL
, /* txtRecord */
6527 (DNSServiceRegisterReply
)dnssd_callback
,
6528 printer
)) != kDNSServiceErr_NoError
)
6530 fprintf(stderr
, "Unable to register \"%s.%s\": %d\n",
6531 printer
->dnssd_name
, regtype
, error
);
6535 TXTRecordDeallocate(&ipp_txt
);
6537 #elif defined(HAVE_AVAHI)
6538 char temp
[256]; /* Subtype service string */
6541 * Create the TXT record...
6545 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "rp=ipp/print");
6546 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "ty=%s %s", make
, model
);
6547 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "adminurl=%s", adminurl
);
6549 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "note=%s", location
);
6550 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "product=(%s)", model
);
6551 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "pdl=%s", formats
);
6552 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "Color=%s", color
? "T" : "F");
6553 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "Duplex=%s", duplex
? "T" : "F");
6554 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "usb_MFG=%s", make
);
6555 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "usb_MDL=%s", model
);
6556 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "UUID=%s", uuid
);
6558 ipp_txt
= avahi_string_list_add_printf(ipp_txt
, "TLS=1.2");
6559 # endif /* HAVE_SSL */
6562 * Register _printer._tcp (LPD) with port 0 to reserve the service name...
6565 avahi_threaded_poll_lock(DNSSDMaster
);
6567 printer
->ipp_ref
= avahi_entry_group_new(DNSSDClient
, dnssd_callback
, NULL
);
6569 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
);
6572 * Then register the _ipp._tcp (IPP)...
6575 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
);
6576 if (subtype
&& *subtype
)
6578 snprintf(temp
, sizeof(temp
), "%s._sub._ipp._tcp", subtype
);
6579 avahi_entry_group_add_service_subtype(printer
->ipp_ref
, AVAHI_IF_UNSPEC
, AVAHI_PROTO_UNSPEC
, 0, printer
->dnssd_name
, "_ipp._tcp", NULL
, temp
);
6584 * _ipps._tcp (IPPS) for secure printing...
6587 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
);
6588 if (subtype
&& *subtype
)
6590 snprintf(temp
, sizeof(temp
), "%s._sub._ipps._tcp", subtype
);
6591 avahi_entry_group_add_service_subtype(printer
->ipp_ref
, AVAHI_IF_UNSPEC
, AVAHI_PROTO_UNSPEC
, 0, printer
->dnssd_name
, "_ipps._tcp", NULL
, temp
);
6593 #endif /* HAVE_SSL */
6596 * Finally _http.tcp (HTTP) for the web interface...
6599 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
);
6600 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");
6606 avahi_entry_group_commit(printer
->ipp_ref
);
6607 avahi_threaded_poll_unlock(DNSSDMaster
);
6609 avahi_string_list_free(ipp_txt
);
6610 #endif /* HAVE_DNSSD */
6617 * 'respond_http()' - Send a HTTP response.
6620 int /* O - 1 on success, 0 on failure */
6622 _ipp_client_t
*client
, /* I - Client */
6623 http_status_t code
, /* I - HTTP status of response */
6624 const char *content_encoding
, /* I - Content-Encoding of response */
6625 const char *type
, /* I - MIME media type of response */
6626 size_t length
) /* I - Length of response */
6628 char message
[1024]; /* Text message */
6631 fprintf(stderr
, "%s %s\n", client
->hostname
, httpStatus(code
));
6633 if (code
== HTTP_STATUS_CONTINUE
)
6636 * 100-continue doesn't send any headers...
6639 return (httpWriteResponse(client
->http
, HTTP_STATUS_CONTINUE
) == 0);
6643 * Format an error message...
6646 if (!type
&& !length
&& code
!= HTTP_STATUS_OK
&& code
!= HTTP_STATUS_SWITCHING_PROTOCOLS
)
6648 snprintf(message
, sizeof(message
), "%d - %s\n", code
, httpStatus(code
));
6650 type
= "text/plain";
6651 length
= strlen(message
);
6657 * Send the HTTP response header...
6660 httpClearFields(client
->http
);
6662 if (code
== HTTP_STATUS_METHOD_NOT_ALLOWED
||
6663 client
->operation
== HTTP_STATE_OPTIONS
)
6664 httpSetField(client
->http
, HTTP_FIELD_ALLOW
, "GET, HEAD, OPTIONS, POST");
6668 if (!strcmp(type
, "text/html"))
6669 httpSetField(client
->http
, HTTP_FIELD_CONTENT_TYPE
,
6670 "text/html; charset=utf-8");
6672 httpSetField(client
->http
, HTTP_FIELD_CONTENT_TYPE
, type
);
6674 if (content_encoding
)
6675 httpSetField(client
->http
, HTTP_FIELD_CONTENT_ENCODING
, content_encoding
);
6678 httpSetLength(client
->http
, length
);
6680 if (httpWriteResponse(client
->http
, code
) < 0)
6684 * Send the response data...
6690 * Send a plain text message.
6693 if (httpPrintf(client
->http
, "%s", message
) < 0)
6696 if (httpWrite2(client
->http
, "", 0) < 0)
6699 else if (client
->response
)
6702 * Send an IPP response...
6705 debug_attributes("Response", client
->response
, 2);
6707 ippSetState(client
->response
, IPP_STATE_IDLE
);
6709 if (ippWrite(client
->http
, client
->response
) != IPP_STATE_DATA
)
6718 * 'respond_ipp()' - Send an IPP response.
6722 respond_ipp(_ipp_client_t
*client
, /* I - Client */
6723 ipp_status_t status
, /* I - status-code */
6724 const char *message
, /* I - printf-style status-message */
6725 ...) /* I - Additional args as needed */
6727 const char *formatted
= NULL
; /* Formatted message */
6730 ippSetStatusCode(client
->response
, status
);
6734 va_list ap
; /* Pointer to additional args */
6735 ipp_attribute_t
*attr
; /* New status-message attribute */
6737 va_start(ap
, message
);
6738 if ((attr
= ippFindAttribute(client
->response
, "status-message",
6739 IPP_TAG_TEXT
)) != NULL
)
6740 ippSetStringfv(client
->response
, &attr
, 0, message
, ap
);
6742 attr
= ippAddStringfv(client
->response
, IPP_TAG_OPERATION
, IPP_TAG_TEXT
,
6743 "status-message", NULL
, message
, ap
);
6746 formatted
= ippGetString(attr
, 0, NULL
);
6750 fprintf(stderr
, "%s %s %s (%s)\n", client
->hostname
,
6751 ippOpString(client
->operation_id
), ippErrorString(status
),
6754 fprintf(stderr
, "%s %s %s\n", client
->hostname
,
6755 ippOpString(client
->operation_id
), ippErrorString(status
));
6760 * 'respond_unsupported()' - Respond with an unsupported attribute.
6764 respond_unsupported(
6765 _ipp_client_t
*client
, /* I - Client */
6766 ipp_attribute_t
*attr
) /* I - Atribute */
6768 ipp_attribute_t
*temp
; /* Copy of attribute */
6771 respond_ipp(client
, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES
,
6772 "Unsupported %s %s%s value.", ippGetName(attr
),
6773 ippGetCount(attr
) > 1 ? "1setOf " : "",
6774 ippTagString(ippGetValueTag(attr
)));
6776 temp
= ippCopyAttribute(client
->response
, attr
, 0);
6777 ippSetGroupTag(client
->response
, &temp
, IPP_TAG_UNSUPPORTED_GROUP
);
6782 * 'run_printer()' - Run the printer service.
6786 run_printer(_ipp_printer_t
*printer
) /* I - Printer */
6788 int num_fds
; /* Number of file descriptors */
6789 struct pollfd polldata
[3]; /* poll() data */
6790 int timeout
; /* Timeout for poll() */
6791 _ipp_client_t
*client
; /* New client */
6795 * Setup poll() data for the Bonjour service socket and IPv4/6 listeners...
6798 polldata
[0].fd
= printer
->ipv4
;
6799 polldata
[0].events
= POLLIN
;
6801 polldata
[1].fd
= printer
->ipv6
;
6802 polldata
[1].events
= POLLIN
;
6807 polldata
[num_fds
].fd
= DNSServiceRefSockFD(DNSSDMaster
);
6808 polldata
[num_fds
++].events
= POLLIN
;
6809 #endif /* HAVE_DNSSD */
6812 * Loop until we are killed or have a hard error...
6817 if (cupsArrayCount(printer
->jobs
))
6822 if (poll(polldata
, (nfds_t
)num_fds
, timeout
) < 0 && errno
!= EINTR
)
6824 perror("poll() failed");
6828 if (polldata
[0].revents
& POLLIN
)
6830 if ((client
= create_client(printer
, printer
->ipv4
)) != NULL
)
6832 if (!_cupsThreadCreate((_cups_thread_func_t
)process_client
, client
))
6834 perror("Unable to create client thread");
6835 delete_client(client
);
6840 if (polldata
[1].revents
& POLLIN
)
6842 if ((client
= create_client(printer
, printer
->ipv6
)) != NULL
)
6844 if (!_cupsThreadCreate((_cups_thread_func_t
)process_client
, client
))
6846 perror("Unable to create client thread");
6847 delete_client(client
);
6853 if (polldata
[2].revents
& POLLIN
)
6854 DNSServiceProcessResult(DNSSDMaster
);
6855 #endif /* HAVE_DNSSD */
6858 * Clean out old jobs...
6861 clean_jobs(printer
);
6867 * 'time_string()' - Return the local time in hours, minutes, and seconds.
6871 time_string(time_t tv
, /* I - Time value */
6872 char *buffer
, /* I - Buffer */
6873 size_t bufsize
) /* I - Size of buffer */
6875 struct tm
*curtime
= localtime(&tv
);
6878 strftime(buffer
, bufsize
, "%X", curtime
);
6884 * 'usage()' - Show program usage.
6888 usage(int status
) /* O - Exit status */
6892 puts(CUPS_SVERSION
" - Copyright 2010-2015 by Apple Inc. All rights "
6897 puts("Usage: ippserver [options] \"name\"");
6900 puts("-2 Supports 2-sided printing (default=1-sided)");
6901 puts("-M manufacturer Manufacturer name (default=Test)");
6902 puts("-P PIN printing mode");
6903 puts("-a attributes-file Load printer attributes from file");
6904 puts("-c command Run command for every print job");
6905 printf("-d spool-directory Spool directory "
6906 "(default=/tmp/ippserver.%d)\n", (int)getpid());
6907 puts("-f type/subtype[,...] List of supported types "
6908 "(default=application/pdf,image/jpeg)");
6909 puts("-h Show program help");
6910 puts("-i iconfile.png PNG icon file (default=printer.png)");
6911 puts("-k Keep job spool files");
6912 puts("-l location Location of printer (default=empty string)");
6913 puts("-m model Model name (default=Printer)");
6914 puts("-n hostname Hostname for printer");
6915 puts("-p port Port number (default=auto)");
6916 puts("-r subtype Bonjour service subtype (default=_print)");
6917 puts("-s speed[,color-speed] Speed in pages per minute (default=10,0)");
6918 puts("-v[vvv] Be (very) verbose");
6925 * 'valid_doc_attributes()' - Determine whether the document attributes are
6928 * When one or more document attributes are invalid, this function adds a
6929 * suitable response and attributes to the unsupported group.
6932 static int /* O - 1 if valid, 0 if not */
6933 valid_doc_attributes(
6934 _ipp_client_t
*client
) /* I - Client */
6936 int valid
= 1; /* Valid attributes? */
6937 ipp_op_t op
= ippGetOperation(client
->request
);
6939 const char *op_name
= ippOpString(op
);
6940 /* IPP operation name */
6941 ipp_attribute_t
*attr
, /* Current attribute */
6942 *supported
; /* xxx-supported attribute */
6943 const char *compression
= NULL
,
6944 /* compression value */
6945 *format
= NULL
; /* document-format value */
6949 * Check operation attributes...
6952 if ((attr
= ippFindAttribute(client
->request
, "compression", IPP_TAG_ZERO
)) != NULL
)
6955 * If compression is specified, only accept a supported value in a Print-Job
6956 * or Send-Document request...
6959 compression
= ippGetString(attr
, 0, NULL
);
6960 supported
= ippFindAttribute(client
->printer
->attrs
,
6961 "compression-supported", IPP_TAG_KEYWORD
);
6963 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_KEYWORD
||
6964 ippGetGroupTag(attr
) != IPP_TAG_OPERATION
||
6965 (op
!= IPP_OP_PRINT_JOB
&& op
!= IPP_OP_SEND_DOCUMENT
&&
6966 op
!= IPP_OP_VALIDATE_JOB
) ||
6967 !ippContainsString(supported
, compression
))
6969 respond_unsupported(client
, attr
);
6974 fprintf(stderr
, "%s %s compression=\"%s\"\n", client
->hostname
, op_name
, compression
);
6976 ippAddString(client
->request
, IPP_TAG_JOB
, IPP_TAG_KEYWORD
, "compression-supplied", NULL
, compression
);
6978 if (strcmp(compression
, "none"))
6981 fprintf(stderr
, "Receiving job file with \"%s\" compression.\n", compression
);
6982 httpSetField(client
->http
, HTTP_FIELD_CONTENT_ENCODING
, compression
);
6988 * Is it a format we support?
6991 if ((attr
= ippFindAttribute(client
->request
, "document-format", IPP_TAG_ZERO
)) != NULL
)
6993 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_MIMETYPE
||
6994 ippGetGroupTag(attr
) != IPP_TAG_OPERATION
)
6996 respond_unsupported(client
, attr
);
7001 format
= ippGetString(attr
, 0, NULL
);
7003 fprintf(stderr
, "%s %s document-format=\"%s\"\n",
7004 client
->hostname
, op_name
, format
);
7006 ippAddString(client
->request
, IPP_TAG_JOB
, IPP_TAG_MIMETYPE
, "document-format-supplied", NULL
, format
);
7011 format
= ippGetString(ippFindAttribute(client
->printer
->attrs
, "document-format-default", IPP_TAG_MIMETYPE
), 0, NULL
);
7013 format
= "application/octet-stream"; /* Should never happen */
7015 attr
= ippAddString(client
->request
, IPP_TAG_OPERATION
, IPP_TAG_MIMETYPE
, "document-format", NULL
, format
);
7018 if (!strcmp(format
, "application/octet-stream") && (ippGetOperation(client
->request
) == IPP_OP_PRINT_JOB
|| ippGetOperation(client
->request
) == IPP_OP_SEND_DOCUMENT
))
7021 * Auto-type the file using the first 8 bytes of the file...
7024 unsigned char header
[8]; /* First 8 bytes of file */
7026 memset(header
, 0, sizeof(header
));
7027 httpPeek(client
->http
, (char *)header
, sizeof(header
));
7029 if (!memcmp(header
, "%PDF", 4))
7030 format
= "application/pdf";
7031 else if (!memcmp(header
, "%!", 2))
7032 format
= "application/postscript";
7033 else if (!memcmp(header
, "\377\330\377", 3) && header
[3] >= 0xe0 && header
[3] <= 0xef)
7034 format
= "image/jpeg";
7035 else if (!memcmp(header
, "\211PNG", 4))
7036 format
= "image/png";
7037 else if (!memcmp(header
, "RAS2", 4))
7038 format
= "image/pwg-raster";
7039 else if (!memcmp(header
, "UNIRAST", 8))
7040 format
= "image/urf";
7046 fprintf(stderr
, "%s %s Auto-typed document-format=\"%s\"\n",
7047 client
->hostname
, op_name
, format
);
7049 ippAddString(client
->request
, IPP_TAG_JOB
, IPP_TAG_MIMETYPE
, "document-format-detected", NULL
, format
);
7053 if (op
!= IPP_OP_CREATE_JOB
&& (supported
= ippFindAttribute(client
->printer
->attrs
, "document-format-supported", IPP_TAG_MIMETYPE
)) != NULL
&& !ippContainsString(supported
, format
))
7055 respond_unsupported(client
, attr
);
7063 if ((attr
= ippFindAttribute(client
->request
, "document-name", IPP_TAG_NAME
)) != NULL
)
7064 ippAddString(client
->request
, IPP_TAG_JOB
, IPP_TAG_NAME
, "document-name-supplied", NULL
, ippGetString(attr
, 0, NULL
));
7071 * 'valid_job_attributes()' - Determine whether the job attributes are valid.
7073 * When one or more job attributes are invalid, this function adds a suitable
7074 * response and attributes to the unsupported group.
7077 static int /* O - 1 if valid, 0 if not */
7078 valid_job_attributes(
7079 _ipp_client_t
*client
) /* I - Client */
7081 int i
, /* Looping var */
7082 count
, /* Number of values */
7083 valid
= 1; /* Valid attributes? */
7084 ipp_attribute_t
*attr
, /* Current attribute */
7085 *supported
; /* xxx-supported attribute */
7089 * Check operation attributes...
7092 valid
= valid_doc_attributes(client
);
7095 * Check the various job template attributes...
7098 if ((attr
= ippFindAttribute(client
->request
, "copies", IPP_TAG_ZERO
)) != NULL
)
7100 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_INTEGER
||
7101 ippGetInteger(attr
, 0) < 1 || ippGetInteger(attr
, 0) > 999)
7103 respond_unsupported(client
, attr
);
7108 if ((attr
= ippFindAttribute(client
->request
, "ipp-attribute-fidelity", IPP_TAG_ZERO
)) != NULL
)
7110 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_BOOLEAN
)
7112 respond_unsupported(client
, attr
);
7117 if ((attr
= ippFindAttribute(client
->request
, "job-hold-until", IPP_TAG_ZERO
)) != NULL
)
7119 if (ippGetCount(attr
) != 1 ||
7120 (ippGetValueTag(attr
) != IPP_TAG_NAME
&&
7121 ippGetValueTag(attr
) != IPP_TAG_NAMELANG
&&
7122 ippGetValueTag(attr
) != IPP_TAG_KEYWORD
) ||
7123 strcmp(ippGetString(attr
, 0, NULL
), "no-hold"))
7125 respond_unsupported(client
, attr
);
7130 if ((attr
= ippFindAttribute(client
->request
, "job-impressions", IPP_TAG_ZERO
)) != NULL
)
7132 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_INTEGER
|| ippGetInteger(attr
, 0) < 0)
7134 respond_unsupported(client
, attr
);
7139 if ((attr
= ippFindAttribute(client
->request
, "job-name", IPP_TAG_ZERO
)) != NULL
)
7141 if (ippGetCount(attr
) != 1 ||
7142 (ippGetValueTag(attr
) != IPP_TAG_NAME
&&
7143 ippGetValueTag(attr
) != IPP_TAG_NAMELANG
))
7145 respond_unsupported(client
, attr
);
7149 ippSetGroupTag(client
->request
, &attr
, IPP_TAG_JOB
);
7152 ippAddString(client
->request
, IPP_TAG_JOB
, IPP_TAG_NAME
, "job-name", NULL
, "Untitled");
7154 if ((attr
= ippFindAttribute(client
->request
, "job-priority", IPP_TAG_ZERO
)) != NULL
)
7156 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_INTEGER
||
7157 ippGetInteger(attr
, 0) < 1 || ippGetInteger(attr
, 0) > 100)
7159 respond_unsupported(client
, attr
);
7164 if ((attr
= ippFindAttribute(client
->request
, "job-sheets", IPP_TAG_ZERO
)) != NULL
)
7166 if (ippGetCount(attr
) != 1 ||
7167 (ippGetValueTag(attr
) != IPP_TAG_NAME
&&
7168 ippGetValueTag(attr
) != IPP_TAG_NAMELANG
&&
7169 ippGetValueTag(attr
) != IPP_TAG_KEYWORD
) ||
7170 strcmp(ippGetString(attr
, 0, NULL
), "none"))
7172 respond_unsupported(client
, attr
);
7177 if ((attr
= ippFindAttribute(client
->request
, "media", IPP_TAG_ZERO
)) != NULL
)
7179 if (ippGetCount(attr
) != 1 ||
7180 (ippGetValueTag(attr
) != IPP_TAG_NAME
&&
7181 ippGetValueTag(attr
) != IPP_TAG_NAMELANG
&&
7182 ippGetValueTag(attr
) != IPP_TAG_KEYWORD
))
7184 respond_unsupported(client
, attr
);
7189 supported
= ippFindAttribute(client
->printer
->attrs
, "media-supported", IPP_TAG_KEYWORD
);
7191 if (!ippContainsString(supported
, ippGetString(attr
, 0, NULL
)))
7193 respond_unsupported(client
, attr
);
7199 if ((attr
= ippFindAttribute(client
->request
, "media-col", IPP_TAG_ZERO
)) != NULL
)
7201 ipp_t
*col
, /* media-col collection */
7202 *size
; /* media-size collection */
7203 ipp_attribute_t
*member
, /* Member attribute */
7204 *x_dim
, /* x-dimension */
7205 *y_dim
; /* y-dimension */
7206 int x_value
, /* y-dimension value */
7207 y_value
; /* x-dimension value */
7209 if (ippGetCount(attr
) != 1 ||
7210 ippGetValueTag(attr
) != IPP_TAG_BEGIN_COLLECTION
)
7212 respond_unsupported(client
, attr
);
7216 col
= ippGetCollection(attr
, 0);
7218 if ((member
= ippFindAttribute(col
, "media-size-name", IPP_TAG_ZERO
)) != NULL
)
7220 if (ippGetCount(member
) != 1 ||
7221 (ippGetValueTag(member
) != IPP_TAG_NAME
&&
7222 ippGetValueTag(member
) != IPP_TAG_NAMELANG
&&
7223 ippGetValueTag(member
) != IPP_TAG_KEYWORD
))
7225 respond_unsupported(client
, attr
);
7230 supported
= ippFindAttribute(client
->printer
->attrs
, "media-supported", IPP_TAG_KEYWORD
);
7232 if (!ippContainsString(supported
, ippGetString(member
, 0, NULL
)))
7234 respond_unsupported(client
, attr
);
7239 else if ((member
= ippFindAttribute(col
, "media-size", IPP_TAG_BEGIN_COLLECTION
)) != NULL
)
7241 if (ippGetCount(member
) != 1)
7243 respond_unsupported(client
, attr
);
7248 size
= ippGetCollection(member
, 0);
7250 if ((x_dim
= ippFindAttribute(size
, "x-dimension", IPP_TAG_INTEGER
)) == NULL
|| ippGetCount(x_dim
) != 1 ||
7251 (y_dim
= ippFindAttribute(size
, "y-dimension", IPP_TAG_INTEGER
)) == NULL
|| ippGetCount(y_dim
) != 1)
7253 respond_unsupported(client
, attr
);
7258 x_value
= ippGetInteger(x_dim
, 0);
7259 y_value
= ippGetInteger(y_dim
, 0);
7260 supported
= ippFindAttribute(client
->printer
->attrs
, "media-size-supported", IPP_TAG_BEGIN_COLLECTION
);
7261 count
= ippGetCount(supported
);
7263 for (i
= 0; i
< count
; i
++)
7265 size
= ippGetCollection(supported
, i
);
7266 x_dim
= ippFindAttribute(size
, "x-dimension", IPP_TAG_ZERO
);
7267 y_dim
= ippFindAttribute(size
, "y-dimension", IPP_TAG_ZERO
);
7269 if (ippContainsInteger(x_dim
, x_value
) && ippContainsInteger(y_dim
, y_value
))
7275 respond_unsupported(client
, attr
);
7283 if ((attr
= ippFindAttribute(client
->request
, "multiple-document-handling", IPP_TAG_ZERO
)) != NULL
)
7285 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_KEYWORD
||
7286 (strcmp(ippGetString(attr
, 0, NULL
),
7287 "separate-documents-uncollated-copies") &&
7288 strcmp(ippGetString(attr
, 0, NULL
),
7289 "separate-documents-collated-copies")))
7291 respond_unsupported(client
, attr
);
7296 if ((attr
= ippFindAttribute(client
->request
, "orientation-requested", IPP_TAG_ZERO
)) != NULL
)
7298 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_ENUM
||
7299 ippGetInteger(attr
, 0) < IPP_ORIENT_PORTRAIT
||
7300 ippGetInteger(attr
, 0) > IPP_ORIENT_REVERSE_PORTRAIT
)
7302 respond_unsupported(client
, attr
);
7307 if ((attr
= ippFindAttribute(client
->request
, "page-ranges", IPP_TAG_ZERO
)) != NULL
)
7309 if (ippGetValueTag(attr
) != IPP_TAG_RANGE
)
7311 respond_unsupported(client
, attr
);
7316 if ((attr
= ippFindAttribute(client
->request
, "print-quality", IPP_TAG_ZERO
)) != NULL
)
7318 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_ENUM
||
7319 ippGetInteger(attr
, 0) < IPP_QUALITY_DRAFT
||
7320 ippGetInteger(attr
, 0) > IPP_QUALITY_HIGH
)
7322 respond_unsupported(client
, attr
);
7327 if ((attr
= ippFindAttribute(client
->request
, "printer-resolution", IPP_TAG_ZERO
)) != NULL
)
7329 supported
= ippFindAttribute(client
->printer
->attrs
, "printer-resolution-supported", IPP_TAG_RESOLUTION
);
7331 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_RESOLUTION
||
7334 respond_unsupported(client
, attr
);
7339 int xdpi
, /* Horizontal resolution for job template attribute */
7340 ydpi
, /* Vertical resolution for job template attribute */
7341 sydpi
; /* Vertical resolution for supported value */
7342 ipp_res_t units
, /* Units for job template attribute */
7343 sunits
; /* Units for supported value */
7345 xdpi
= ippGetResolution(attr
, 0, &ydpi
, &units
);
7346 count
= ippGetCount(supported
);
7348 for (i
= 0; i
< count
; i
++)
7350 if (xdpi
== ippGetResolution(supported
, i
, &sydpi
, &sunits
) && ydpi
== sydpi
&& units
== sunits
)
7356 respond_unsupported(client
, attr
);
7362 if ((attr
= ippFindAttribute(client
->request
, "sides", IPP_TAG_ZERO
)) != NULL
)
7364 const char *sides
= ippGetString(attr
, 0, NULL
);
7365 /* "sides" value... */
7367 if (ippGetCount(attr
) != 1 || ippGetValueTag(attr
) != IPP_TAG_KEYWORD
)
7369 respond_unsupported(client
, attr
);
7372 else if ((supported
= ippFindAttribute(client
->printer
->attrs
, "sides-supported", IPP_TAG_KEYWORD
)) != NULL
)
7374 if (!ippContainsString(supported
, sides
))
7376 respond_unsupported(client
, attr
);
7380 else if (strcmp(sides
, "one-sided"))
7382 respond_unsupported(client
, attr
);