xref: /aosp_15_r20/external/libcups/tools/ipptool.c (revision 5e7646d21f1134fb0638875d812ef646c12ab91e)
1 /*
2  * ipptool command for CUPS.
3  *
4  * Copyright © 2021 by OpenPrinting.
5  * Copyright © 2020 by The Printer Working Group.
6  * Copyright © 2007-2021 by Apple Inc.
7  * Copyright © 1997-2007 by Easy Software Products.
8  *
9  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
10  * information.
11  */
12 
13 /*
14  * Include necessary headers...
15  */
16 
17 #include <cups/cups-private.h>
18 #include <regex.h>
19 #include <sys/stat.h>
20 #ifdef _WIN32
21 #  include <windows.h>
22 #  ifndef R_OK
23 #    define R_OK 0
24 #  endif /* !R_OK */
25 #else
26 #  include <signal.h>
27 #  include <termios.h>
28 #endif /* _WIN32 */
29 #ifndef O_BINARY
30 #  define O_BINARY 0
31 #endif /* !O_BINARY */
32 
33 
34 /*
35  * Limits...
36  */
37 
38 #define MAX_EXPECT	200		// Maximum number of EXPECT directives
39 #define MAX_DISPLAY	200		// Maximum number of DISPLAY directives
40 #define MAX_MONITOR	10		// Maximum number of MONITOR-PRINTER-STATE EXPECT directives
41 
42 
43 /*
44  * Types...
45  */
46 
47 typedef enum ipptool_transfer_e		/**** How to send request data ****/
48 {
49   IPPTOOL_TRANSFER_AUTO,		/* Chunk for files, length for static */
50   IPPTOOL_TRANSFER_CHUNKED,		/* Chunk always */
51   IPPTOOL_TRANSFER_LENGTH		/* Length always */
52 } ipptool_transfer_t;
53 
54 typedef enum ipptool_output_e		/**** Output mode ****/
55 {
56   IPPTOOL_OUTPUT_QUIET,			/* No output */
57   IPPTOOL_OUTPUT_TEST,			/* Traditional CUPS test output */
58   IPPTOOL_OUTPUT_PLIST,			/* XML plist test output */
59   IPPTOOL_OUTPUT_IPPSERVER,		/* ippserver attribute file output */
60   IPPTOOL_OUTPUT_LIST,			/* Tabular list output */
61   IPPTOOL_OUTPUT_CSV			/* Comma-separated values output */
62 } ipptool_output_t;
63 
64 typedef enum ipptool_with_e		/**** WITH flags ****/
65 {
66   IPPTOOL_WITH_LITERAL = 0,		/* Match string is a literal value */
67   IPPTOOL_WITH_ALL = 1,			/* Must match all values */
68   IPPTOOL_WITH_REGEX = 2,		/* Match string is a regular expression */
69   IPPTOOL_WITH_HOSTNAME = 4,		/* Match string is a URI hostname */
70   IPPTOOL_WITH_RESOURCE = 8,		/* Match string is a URI resource */
71   IPPTOOL_WITH_SCHEME = 16		/* Match string is a URI scheme */
72 } ipptool_with_t;
73 
74 typedef struct ipptool_expect_s		/**** Expected attribute info ****/
75 {
76   int		optional,		/* Optional attribute? */
77 		not_expect,		/* Don't expect attribute? */
78 		expect_all;		/* Expect all attributes to match/not match */
79   char		*name,			/* Attribute name */
80 		*of_type,		/* Type name */
81 		*same_count_as,		/* Parallel attribute name */
82 		*if_defined,		/* Only required if variable defined */
83 		*if_not_defined,	/* Only required if variable is not defined */
84 		*with_value,		/* Attribute must include this value */
85 		*with_value_from,	/* Attribute must have one of the values in this attribute */
86 		*define_match,		/* Variable to define on match */
87 		*define_no_match,	/* Variable to define on no-match */
88 		*define_value,		/* Variable to define with value */
89 		*display_match;		/* Message to display on a match */
90   int		repeat_limit,		/* Maximum number of times to repeat */
91 		repeat_match,		/* Repeat test on match */
92 		repeat_no_match,	/* Repeat test on no match */
93 		with_distinct,		/* WITH-DISTINCT-VALUES? */
94 		with_flags,		/* WITH flags */
95 		count;			/* Expected count if > 0 */
96   ipp_tag_t	in_group;		/* IN-GROUP value */
97 } ipptool_expect_t;
98 
99 typedef struct ipptool_status_s		/**** Status info ****/
100 {
101   ipp_status_t	status;			/* Expected status code */
102   char		*if_defined,		/* Only if variable is defined */
103 		*if_not_defined,	/* Only if variable is not defined */
104 		*define_match,		/* Variable to define on match */
105 		*define_no_match,	/* Variable to define on no-match */
106 		*define_value;		/* Variable to define with value */
107   int		repeat_limit,		/* Maximum number of times to repeat */
108 		repeat_match,		/* Repeat the test when it does not match */
109 		repeat_no_match;	/* Repeat the test when it matches */
110 } ipptool_status_t;
111 
112 typedef struct ipptool_test_s		/**** Test Data ****/
113 {
114   /* Global Options */
115   _ipp_vars_t	*vars;			/* Variables */
116   http_encryption_t encryption;		/* Encryption for connection */
117   int		family;			/* Address family */
118   ipptool_output_t output;		/* Output mode */
119   int		repeat_on_busy;		/* Repeat tests on server-error-busy */
120   int		stop_after_include_error;
121 					/* Stop after include errors? */
122   double	timeout;		/* Timeout for connection */
123   int		validate_headers,	/* Validate HTTP headers in response? */
124                 verbosity;		/* Show all attributes? */
125 
126   /* Test Defaults */
127   int		def_ignore_errors;	/* Default IGNORE-ERRORS value */
128   ipptool_transfer_t def_transfer;	/* Default TRANSFER value */
129   int		def_version;		/* Default IPP version */
130 
131   /* Global State */
132   http_t	*http;			/* HTTP connection to printer/server */
133   cups_file_t	*outfile;		/* Output file */
134   int		show_header,		/* Show the test header? */
135 		xml_header;		/* 1 if XML plist header was written */
136   int		pass,			/* Have we passed all tests? */
137 		test_count,		/* Number of tests (total) */
138 		pass_count,		/* Number of tests that passed */
139 		fail_count,		/* Number of tests that failed */
140 		skip_count;		/* Number of tests that were skipped */
141 
142   /* Per-Test State */
143   cups_array_t	*errors;		/* Errors array */
144   int		prev_pass,		/* Result of previous test */
145 		skip_previous;		/* Skip on previous test failure? */
146   char		compression[16];	/* COMPRESSION value */
147   useconds_t	delay;                  /* Initial delay */
148   int		num_displayed;		/* Number of displayed attributes */
149   char		*displayed[MAX_DISPLAY];/* Displayed attributes */
150   int		num_expects;		/* Number of expected attributes */
151   ipptool_expect_t expects[MAX_EXPECT],	/* Expected attributes */
152 		*expect,		/* Current expected attribute */
153 		*last_expect;		/* Last EXPECT (for predicates) */
154   char		file[1024],		/* Data filename */
155 		file_id[1024];		/* File identifier */
156   int		ignore_errors;		/* Ignore test failures? */
157   char		name[1024];		/* Test name */
158   char		pause[1024];		/* PAUSE value */
159   useconds_t	repeat_interval;	/* Repeat interval (delay) */
160   int		request_id;		/* Current request ID */
161   char		resource[512];		/* Resource for request */
162   int		pass_test,		/* Pass this test? */
163 		skip_test,		/* Skip this test? */
164 		num_statuses;		/* Number of valid status codes */
165   ipptool_status_t statuses[100],	/* Valid status codes */
166 		*last_status;		/* Last STATUS (for predicates) */
167   char		test_id[1024];		/* Test identifier */
168   ipptool_transfer_t transfer;		/* To chunk or not to chunk */
169   int		version;		/* IPP version number to use */
170   _cups_thread_t monitor_thread;	/* Monitoring thread ID */
171   int		monitor_done;		/* Set to 1 to stop monitor thread */
172   char		*monitor_uri;		/* MONITOR-PRINTER-STATE URI */
173   useconds_t	monitor_delay,		/* MONITOR-PRINTER-STATE DELAY value, if any */
174 		monitor_interval;	/* MONITOR-PRINTER-STATE DELAY interval */
175   int		num_monitor_expects;	/* Number MONITOR-PRINTER-STATE EXPECTs */
176   ipptool_expect_t monitor_expects[MAX_MONITOR];
177 					/* MONITOR-PRINTER-STATE EXPECTs */
178 } ipptool_test_t;
179 
180 
181 /*
182  * Globals...
183  */
184 
185 static int	Cancel = 0;		/* Cancel test? */
186 
187 
188 /*
189  * Local functions...
190  */
191 
192 static void	add_stringf(cups_array_t *a, const char *s, ...) _CUPS_FORMAT(2, 3);
193 static int      compare_uris(const char *a, const char *b);
194 static void	copy_hex_string(char *buffer, unsigned char *data, int datalen, size_t bufsize);
195 static void	*do_monitor_printer_state(ipptool_test_t *data);
196 static int	do_test(_ipp_file_t *f, ipptool_test_t *data);
197 static int	do_tests(const char *testfile, ipptool_test_t *data);
198 static int	error_cb(_ipp_file_t *f, ipptool_test_t *data, const char *error);
199 static int      expect_matches(ipptool_expect_t *expect, ipp_attribute_t *attr);
200 static char	*get_filename(const char *testfile, char *dst, const char *src, size_t dstsize);
201 static const char *get_string(ipp_attribute_t *attr, int element, int flags, char *buffer, size_t bufsize);
202 static void	init_data(ipptool_test_t *data);
203 static char	*iso_date(const ipp_uchar_t *date);
204 static int	parse_monitor_printer_state(_ipp_file_t *f, ipptool_test_t *data);
205 static void	pause_message(const char *message);
206 static void	print_attr(cups_file_t *outfile, ipptool_output_t output, ipp_attribute_t *attr, ipp_tag_t *group);
207 static ipp_attribute_t *print_csv(ipptool_test_t *data, ipp_t *ipp, ipp_attribute_t *attr, int num_displayed, char **displayed, size_t *widths);
208 static void	print_fatal_error(ipptool_test_t *data, const char *s, ...) _CUPS_FORMAT(2, 3);
209 static void	print_ippserver_attr(ipptool_test_t *data, ipp_attribute_t *attr, int indent);
210 static void	print_ippserver_string(ipptool_test_t *data, const char *s, size_t len);
211 static ipp_attribute_t *print_line(ipptool_test_t *data, ipp_t *ipp, ipp_attribute_t *attr, int num_displayed, char **displayed, size_t *widths);
212 static void	print_xml_header(ipptool_test_t *data);
213 static void	print_xml_string(cups_file_t *outfile, const char *element, const char *s);
214 static void	print_xml_trailer(ipptool_test_t *data, int success, const char *message);
215 #ifndef _WIN32
216 static void	sigterm_handler(int sig);
217 #endif /* _WIN32 */
218 static int	timeout_cb(http_t *http, void *user_data);
219 static int	token_cb(_ipp_file_t *f, _ipp_vars_t *vars, ipptool_test_t *data, const char *token);
220 static void	usage(void) _CUPS_NORETURN;
221 static int	with_distinct_values(cups_array_t *errors, ipp_attribute_t *attr);
222 static const char *with_flags_string(int flags);
223 static int      with_value(ipptool_test_t *data, cups_array_t *errors, char *value, int flags, ipp_attribute_t *attr, char *matchbuf, size_t matchlen);
224 static int      with_value_from(cups_array_t *errors, ipp_attribute_t *fromattr, ipp_attribute_t *attr, char *matchbuf, size_t matchlen);
225 
226 
227 /*
228  * 'main()' - Parse options and do tests.
229  */
230 
231 int					/* O - Exit status */
main(int argc,char * argv[])232 main(int  argc,				/* I - Number of command-line args */
233      char *argv[])			/* I - Command-line arguments */
234 {
235   int			i;		/* Looping var */
236   int			status;		/* Status of tests... */
237   char			*opt,		/* Current option */
238 			name[1024],	/* Name/value buffer */
239 			*value,		/* Pointer to value */
240 			filename[1024],	/* Real filename */
241 			testname[1024];	/* Real test filename */
242   const char		*ext,		/* Extension on filename */
243 			*testfile;	/* Test file to use */
244   int			interval,	/* Test interval in microseconds */
245 			repeat;		/* Repeat count */
246   _ipp_vars_t		vars;		/* Variables */
247   ipptool_test_t	data;		/* Test data */
248   _cups_globals_t	*cg = _cupsGlobals();
249 					/* Global data */
250 
251 
252 #ifndef _WIN32
253  /*
254   * Catch SIGINT and SIGTERM...
255   */
256 
257   signal(SIGINT, sigterm_handler);
258   signal(SIGTERM, sigterm_handler);
259 #endif /* !_WIN32 */
260 
261  /*
262   * Initialize the locale and variables...
263   */
264 
265   _cupsSetLocale(argv);
266 
267   init_data(&data);
268 
269   _ippVarsInit(&vars, NULL, (_ipp_ferror_cb_t)error_cb, (_ipp_ftoken_cb_t)token_cb);
270   data.vars = &vars;
271 
272   _ippVarsSet(data.vars, "date-start", iso_date(ippTimeToDate(time(NULL))));
273 
274  /*
275   * We need at least:
276   *
277   *     ipptool URI testfile
278   */
279 
280   interval = 0;
281   repeat   = 0;
282   status   = 0;
283   testfile = NULL;
284 
285   for (i = 1; i < argc; i ++)
286   {
287     if (!strcmp(argv[i], "--help"))
288     {
289       usage();
290     }
291     else if (!strcmp(argv[i], "--ippserver"))
292     {
293       i ++;
294 
295       if (i >= argc)
296       {
297 	_cupsLangPuts(stderr, _("ipptool: Missing filename for \"--ippserver\"."));
298 	usage();
299       }
300 
301       if (data.outfile != cupsFileStdout())
302 	usage();
303 
304       if ((data.outfile = cupsFileOpen(argv[i], "w")) == NULL)
305       {
306 	_cupsLangPrintf(stderr, _("%s: Unable to open \"%s\": %s"), "ipptool", argv[i], strerror(errno));
307 	exit(1);
308       }
309 
310       data.output = IPPTOOL_OUTPUT_IPPSERVER;
311     }
312     else if (!strcmp(argv[i], "--stop-after-include-error"))
313     {
314       data.stop_after_include_error = 1;
315     }
316     else if (!strcmp(argv[i], "--version"))
317     {
318       puts(CUPS_SVERSION);
319       return (0);
320     }
321     else if (argv[i][0] == '-')
322     {
323       for (opt = argv[i] + 1; *opt; opt ++)
324       {
325         switch (*opt)
326         {
327 	  case '4' : /* Connect using IPv4 only */
328 	      data.family = AF_INET;
329 	      break;
330 
331 #ifdef AF_INET6
332 	  case '6' : /* Connect using IPv6 only */
333 	      data.family = AF_INET6;
334 	      break;
335 #endif /* AF_INET6 */
336 
337           case 'C' : /* Enable HTTP chunking */
338               data.def_transfer = IPPTOOL_TRANSFER_CHUNKED;
339               break;
340 
341 	  case 'E' : /* Encrypt with TLS */
342 #ifdef HAVE_TLS
343 	      data.encryption = HTTP_ENCRYPT_REQUIRED;
344 #else
345 	      _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."),
346 			      argv[0]);
347 #endif /* HAVE_TLS */
348 	      break;
349 
350           case 'I' : /* Ignore errors */
351 	      data.def_ignore_errors = 1;
352 	      break;
353 
354           case 'L' : /* Disable HTTP chunking */
355               data.def_transfer = IPPTOOL_TRANSFER_LENGTH;
356               break;
357 
358           case 'P' : /* Output to plist file */
359 	      i ++;
360 
361 	      if (i >= argc)
362 	      {
363 		_cupsLangPrintf(stderr, _("%s: Missing filename for \"-P\"."), "ipptool");
364 		usage();
365               }
366 
367               if (data.outfile != cupsFileStdout())
368                 usage();
369 
370               if ((data.outfile = cupsFileOpen(argv[i], "w")) == NULL)
371               {
372                 _cupsLangPrintf(stderr, _("%s: Unable to open \"%s\": %s"), "ipptool", argv[i], strerror(errno));
373                 exit(1);
374               }
375 
376 	      data.output = IPPTOOL_OUTPUT_PLIST;
377 
378               if (interval || repeat)
379 	      {
380 	        _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"-P\" and \"-X\"."));
381 		usage();
382 	      }
383               break;
384 
385           case 'R' : /* Repeat on server-error-busy */
386               data.repeat_on_busy = 1;
387               break;
388 
389 	  case 'S' : /* Encrypt with SSL */
390 #ifdef HAVE_TLS
391 	      data.encryption = HTTP_ENCRYPT_ALWAYS;
392 #else
393 	      _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."), "ipptool");
394 #endif /* HAVE_TLS */
395 	      break;
396 
397 	  case 'T' : /* Set timeout */
398 	      i ++;
399 
400 	      if (i >= argc)
401 	      {
402 		_cupsLangPrintf(stderr, _("%s: Missing timeout for \"-T\"."), "ipptool");
403 		usage();
404               }
405 
406 	      data.timeout = _cupsStrScand(argv[i], NULL, localeconv());
407 	      break;
408 
409 	  case 'V' : /* Set IPP version */
410 	      i ++;
411 
412 	      if (i >= argc)
413 	      {
414 		_cupsLangPrintf(stderr, _("%s: Missing version for \"-V\"."), "ipptool");
415 		usage();
416               }
417 
418 	      if (!strcmp(argv[i], "1.0"))
419 	      {
420 	        data.def_version = 10;
421 	      }
422 	      else if (!strcmp(argv[i], "1.1"))
423 	      {
424 	        data.def_version = 11;
425 	      }
426 	      else if (!strcmp(argv[i], "2.0"))
427 	      {
428 	        data.def_version = 20;
429 	      }
430 	      else if (!strcmp(argv[i], "2.1"))
431 	      {
432 	        data.def_version = 21;
433 	      }
434 	      else if (!strcmp(argv[i], "2.2"))
435 	      {
436 	        data.def_version = 22;
437 	      }
438 	      else
439 	      {
440 		_cupsLangPrintf(stderr, _("%s: Bad version %s for \"-V\"."), "ipptool", argv[i]);
441 		usage();
442 	      }
443 	      break;
444 
445           case 'X' : /* Produce XML output */
446 	      data.output = IPPTOOL_OUTPUT_PLIST;
447 
448               if (interval || repeat)
449 	      {
450 	        _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"-P\" and \"-X\"."));
451 		usage();
452 	      }
453 	      break;
454 
455           case 'c' : /* CSV output */
456               data.output = IPPTOOL_OUTPUT_CSV;
457               break;
458 
459           case 'd' : /* Define a variable */
460 	      i ++;
461 
462 	      if (i >= argc)
463 	      {
464 		_cupsLangPuts(stderr, _("ipptool: Missing name=value for \"-d\"."));
465 		usage();
466               }
467 
468               strlcpy(name, argv[i], sizeof(name));
469 	      if ((value = strchr(name, '=')) != NULL)
470 	        *value++ = '\0';
471 	      else
472 	        value = name + strlen(name);
473 
474 	      _ippVarsSet(data.vars, name, value);
475 	      break;
476 
477           case 'f' : /* Set the default test filename */
478 	      i ++;
479 
480 	      if (i >= argc)
481 	      {
482 		_cupsLangPuts(stderr, _("ipptool: Missing filename for \"-f\"."));
483 		usage();
484               }
485 
486               if (access(argv[i], 0))
487               {
488                /*
489                 * Try filename.gz...
490                 */
491 
492 		snprintf(filename, sizeof(filename), "%s.gz", argv[i]);
493                 if (access(filename, 0) && filename[0] != '/'
494 #ifdef _WIN32
495                     && (!isalpha(filename[0] & 255) || filename[1] != ':')
496 #endif /* _WIN32 */
497                     )
498 		{
499 		  snprintf(filename, sizeof(filename), "%s/ipptool/%s", cg->cups_datadir, argv[i]);
500 		  if (access(filename, 0))
501 		  {
502 		    snprintf(filename, sizeof(filename), "%s/ipptool/%s.gz", cg->cups_datadir, argv[i]);
503 		    if (access(filename, 0))
504 		      strlcpy(filename, argv[i], sizeof(filename));
505 		  }
506 		}
507 	      }
508               else
509 		strlcpy(filename, argv[i], sizeof(filename));
510 
511 	      _ippVarsSet(data.vars, "filename", filename);
512 
513               if ((ext = strrchr(filename, '.')) != NULL)
514               {
515                /*
516                 * Guess the MIME media type based on the extension...
517                 */
518 
519                 if (!_cups_strcasecmp(ext, ".gif"))
520                   _ippVarsSet(data.vars, "filetype", "image/gif");
521                 else if (!_cups_strcasecmp(ext, ".htm") ||
522                          !_cups_strcasecmp(ext, ".htm.gz") ||
523                          !_cups_strcasecmp(ext, ".html") ||
524                          !_cups_strcasecmp(ext, ".html.gz"))
525                   _ippVarsSet(data.vars, "filetype", "text/html");
526                 else if (!_cups_strcasecmp(ext, ".jpg") ||
527                          !_cups_strcasecmp(ext, ".jpeg"))
528                   _ippVarsSet(data.vars, "filetype", "image/jpeg");
529                 else if (!_cups_strcasecmp(ext, ".pcl") ||
530                          !_cups_strcasecmp(ext, ".pcl.gz"))
531                   _ippVarsSet(data.vars, "filetype", "application/vnd.hp-PCL");
532                 else if (!_cups_strcasecmp(ext, ".pdf"))
533                   _ippVarsSet(data.vars, "filetype", "application/pdf");
534                 else if (!_cups_strcasecmp(ext, ".png"))
535                   _ippVarsSet(data.vars, "filetype", "image/png");
536                 else if (!_cups_strcasecmp(ext, ".ps") ||
537                          !_cups_strcasecmp(ext, ".ps.gz"))
538                   _ippVarsSet(data.vars, "filetype", "application/postscript");
539                 else if (!_cups_strcasecmp(ext, ".pwg") ||
540                          !_cups_strcasecmp(ext, ".pwg.gz") ||
541                          !_cups_strcasecmp(ext, ".ras") ||
542                          !_cups_strcasecmp(ext, ".ras.gz"))
543                   _ippVarsSet(data.vars, "filetype", "image/pwg-raster");
544                 else if (!_cups_strcasecmp(ext, ".tif") ||
545                          !_cups_strcasecmp(ext, ".tiff"))
546                   _ippVarsSet(data.vars, "filetype", "image/tiff");
547                 else if (!_cups_strcasecmp(ext, ".txt") ||
548                          !_cups_strcasecmp(ext, ".txt.gz"))
549                   _ippVarsSet(data.vars, "filetype", "text/plain");
550                 else if (!_cups_strcasecmp(ext, ".urf") ||
551                          !_cups_strcasecmp(ext, ".urf.gz"))
552                   _ippVarsSet(data.vars, "filetype", "image/urf");
553                 else if (!_cups_strcasecmp(ext, ".xps"))
554                   _ippVarsSet(data.vars, "filetype", "application/openxps");
555                 else
556 		  _ippVarsSet(data.vars, "filetype", "application/octet-stream");
557               }
558               else
559               {
560                /*
561                 * Use the "auto-type" MIME media type...
562                 */
563 
564 		_ippVarsSet(data.vars, "filetype", "application/octet-stream");
565               }
566 	      break;
567 
568           case 'h' : /* Validate response headers */
569               data.validate_headers = 1;
570               break;
571 
572           case 'i' : /* Test every N seconds */
573 	      i ++;
574 
575 	      if (i >= argc)
576 	      {
577 		_cupsLangPuts(stderr, _("ipptool: Missing seconds for \"-i\"."));
578 		usage();
579               }
580 	      else
581 	      {
582 		interval = (int)(_cupsStrScand(argv[i], NULL, localeconv()) * 1000000.0);
583 		if (interval <= 0)
584 		{
585 		  _cupsLangPuts(stderr, _("ipptool: Invalid seconds for \"-i\"."));
586 		  usage();
587 		}
588               }
589 
590               if ((data.output == IPPTOOL_OUTPUT_PLIST || data.output == IPPTOOL_OUTPUT_IPPSERVER) && interval)
591 	      {
592 	        _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"--ippserver\", \"-P\", and \"-X\"."));
593 		usage();
594 	      }
595 	      break;
596 
597           case 'l' : /* List as a table */
598               data.output = IPPTOOL_OUTPUT_LIST;
599               break;
600 
601           case 'n' : /* Repeat count */
602               i ++;
603 
604 	      if (i >= argc)
605 	      {
606 		_cupsLangPuts(stderr, _("ipptool: Missing count for \"-n\"."));
607 		usage();
608               }
609 	      else
610 		repeat = atoi(argv[i]);
611 
612               if ((data.output == IPPTOOL_OUTPUT_PLIST || data.output == IPPTOOL_OUTPUT_IPPSERVER) && repeat)
613 	      {
614 	        _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"--ippserver\", \"-P\", and \"-X\"."));
615 		usage();
616 	      }
617 	      break;
618 
619           case 'q' : /* Be quiet */
620               data.output = IPPTOOL_OUTPUT_QUIET;
621               break;
622 
623           case 't' : /* CUPS test output */
624               data.output = IPPTOOL_OUTPUT_TEST;
625               break;
626 
627           case 'v' : /* Be verbose */
628 	      data.verbosity ++;
629 	      break;
630 
631 	  default :
632 	      _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."), "ipptool", *opt);
633 	      usage();
634 	}
635       }
636     }
637     else if (!strncmp(argv[i], "ipp://", 6) || !strncmp(argv[i], "http://", 7)
638 #ifdef HAVE_TLS
639 	     || !strncmp(argv[i], "ipps://", 7) || !strncmp(argv[i], "https://", 8)
640 #endif /* HAVE_TLS */
641 	     )
642     {
643      /*
644       * Set URI...
645       */
646 
647       if (data.vars->uri)
648       {
649         _cupsLangPuts(stderr, _("ipptool: May only specify a single URI."));
650         usage();
651       }
652 
653 #ifdef HAVE_TLS
654       if (!strncmp(argv[i], "ipps://", 7) || !strncmp(argv[i], "https://", 8))
655         data.encryption = HTTP_ENCRYPT_ALWAYS;
656 #endif /* HAVE_TLS */
657 
658       if (!_ippVarsSet(data.vars, "uri", argv[i]))
659       {
660         _cupsLangPrintf(stderr, _("ipptool: Bad URI \"%s\"."), argv[i]);
661         return (1);
662       }
663 
664       if (data.vars->username[0] && data.vars->password)
665 	cupsSetPasswordCB2(_ippVarsPasswordCB, data.vars);
666     }
667     else
668     {
669      /*
670       * Run test...
671       */
672 
673       if (!data.vars->uri)
674       {
675         _cupsLangPuts(stderr, _("ipptool: URI required before test file."));
676         _cupsLangPuts(stderr, argv[i]);
677 	usage();
678       }
679 
680       if (access(argv[i], 0) && argv[i][0] != '/'
681 #ifdef _WIN32
682           && (!isalpha(argv[i][0] & 255) || argv[i][1] != ':')
683 #endif /* _WIN32 */
684           )
685       {
686         snprintf(testname, sizeof(testname), "%s/ipptool/%s", cg->cups_datadir, argv[i]);
687         if (access(testname, 0))
688           testfile = argv[i];
689         else
690           testfile = testname;
691       }
692       else
693         testfile = argv[i];
694 
695       if (access(testfile, 0))
696       {
697         _cupsLangPrintf(stderr, _("%s: Unable to open \"%s\": %s"), "ipptool", testfile, strerror(errno));
698         status = 1;
699       }
700       else if (!do_tests(testfile, &data))
701         status = 1;
702     }
703   }
704 
705   if (!data.vars->uri || !testfile)
706     usage();
707 
708  /*
709   * Loop if the interval is set...
710   */
711 
712   if (data.output == IPPTOOL_OUTPUT_PLIST)
713     print_xml_trailer(&data, !status, NULL);
714   else if (interval > 0 && repeat > 0)
715   {
716     while (repeat > 1)
717     {
718       usleep((useconds_t)interval);
719       do_tests(testfile, &data);
720       repeat --;
721     }
722   }
723   else if (interval > 0)
724   {
725     for (;;)
726     {
727       usleep((useconds_t)interval);
728       do_tests(testfile, &data);
729     }
730   }
731 
732   if ((data.output == IPPTOOL_OUTPUT_TEST || (data.output == IPPTOOL_OUTPUT_PLIST && data.outfile)) && data.test_count > 1)
733   {
734    /*
735     * Show a summary report if there were multiple tests...
736     */
737 
738     cupsFilePrintf(cupsFileStdout(), "\nSummary: %d tests, %d passed, %d failed, %d skipped\nScore: %d%%\n", data.test_count, data.pass_count, data.fail_count, data.skip_count, 100 * (data.pass_count + data.skip_count) / data.test_count);
739   }
740 
741   cupsFileClose(data.outfile);
742 
743  /*
744   * Exit...
745   */
746 
747   return (status);
748 }
749 
750 
751 /*
752  * 'add_stringf()' - Add a formatted string to an array.
753  */
754 
755 static void
add_stringf(cups_array_t * a,const char * s,...)756 add_stringf(cups_array_t *a,		/* I - Array */
757             const char   *s,		/* I - Printf-style format string */
758             ...)			/* I - Additional args as needed */
759 {
760   char		buffer[10240];		/* Format buffer */
761   va_list	ap;			/* Argument pointer */
762 
763 
764  /*
765   * Don't bother is the array is NULL...
766   */
767 
768   if (!a)
769     return;
770 
771  /*
772   * Format the message...
773   */
774 
775   va_start(ap, s);
776   vsnprintf(buffer, sizeof(buffer), s, ap);
777   va_end(ap);
778 
779  /*
780   * Add it to the array...
781   */
782 
783   cupsArrayAdd(a, buffer);
784 }
785 
786 
787 /*
788  * 'compare_uris()' - Compare two URIs...
789  */
790 
791 static int                              /* O - Result of comparison */
compare_uris(const char * a,const char * b)792 compare_uris(const char *a,             /* I - First URI */
793              const char *b)             /* I - Second URI */
794 {
795   char  ascheme[32],                    /* Components of first URI */
796         auserpass[256],
797         ahost[256],
798         aresource[256];
799   int   aport;
800   char  bscheme[32],                    /* Components of second URI */
801         buserpass[256],
802         bhost[256],
803         bresource[256];
804   int   bport;
805   char  *ptr;                           /* Pointer into string */
806   int   result;                         /* Result of comparison */
807 
808 
809  /*
810   * Separate the URIs into their components...
811   */
812 
813   if (httpSeparateURI(HTTP_URI_CODING_ALL, a, ascheme, sizeof(ascheme), auserpass, sizeof(auserpass), ahost, sizeof(ahost), &aport, aresource, sizeof(aresource)) < HTTP_URI_STATUS_OK)
814     return (-1);
815 
816   if (httpSeparateURI(HTTP_URI_CODING_ALL, b, bscheme, sizeof(bscheme), buserpass, sizeof(buserpass), bhost, sizeof(bhost), &bport, bresource, sizeof(bresource)) < HTTP_URI_STATUS_OK)
817     return (-1);
818 
819  /*
820   * Strip trailing dots from the host components, if present...
821   */
822 
823   if ((ptr = ahost + strlen(ahost) - 1) > ahost && *ptr == '.')
824     *ptr = '\0';
825 
826   if ((ptr = bhost + strlen(bhost) - 1) > bhost && *ptr == '.')
827     *ptr = '\0';
828 
829  /*
830   * Compare each component...
831   */
832 
833   if ((result = _cups_strcasecmp(ascheme, bscheme)) != 0)
834     return (result);
835 
836   if ((result = strcmp(auserpass, buserpass)) != 0)
837     return (result);
838 
839   if ((result = _cups_strcasecmp(ahost, bhost)) != 0)
840     return (result);
841 
842   if (aport != bport)
843     return (aport - bport);
844 
845   if (!_cups_strcasecmp(ascheme, "mailto") || !_cups_strcasecmp(ascheme, "urn"))
846     return (_cups_strcasecmp(aresource, bresource));
847   else
848     return (strcmp(aresource, bresource));
849 }
850 
851 
852 /*
853  * 'copy_hex_string()' - Copy an octetString to a C string and encode as hex if
854  *                       needed.
855  */
856 
857 static void
copy_hex_string(char * buffer,unsigned char * data,int datalen,size_t bufsize)858 copy_hex_string(char          *buffer,	/* I - String buffer */
859 		unsigned char *data,	/* I - octetString data */
860 		int           datalen,	/* I - octetString length */
861 		size_t        bufsize)	/* I - Size of string buffer */
862 {
863   char		*bufptr,		/* Pointer into string buffer */
864 		*bufend = buffer + bufsize - 2;
865 					/* End of string buffer */
866   unsigned char	*dataptr,		/* Pointer into octetString data */
867 		*dataend = data + datalen;
868 					/* End of octetString data */
869   static const char *hexdigits = "0123456789ABCDEF";
870 					/* Hex digits */
871 
872 
873  /*
874   * First see if there are any non-ASCII bytes in the octetString...
875   */
876 
877   for (dataptr = data; dataptr < dataend; dataptr ++)
878     if (*dataptr < 0x20 || *dataptr >= 0x7f)
879       break;
880 
881   if (dataptr < dataend)
882   {
883    /*
884     * Yes, encode as hex...
885     */
886 
887     *buffer = '<';
888 
889     for (bufptr = buffer + 1, dataptr = data; bufptr < bufend && dataptr < dataend; dataptr ++)
890     {
891       *bufptr++ = hexdigits[*dataptr >> 4];
892       *bufptr++ = hexdigits[*dataptr & 15];
893     }
894 
895     if (bufptr < bufend)
896       *bufptr++ = '>';
897 
898     *bufptr = '\0';
899   }
900   else
901   {
902    /*
903     * No, copy as a string...
904     */
905 
906     if ((size_t)datalen > bufsize)
907       datalen = (int)bufsize - 1;
908 
909     memcpy(buffer, data, (size_t)datalen);
910     buffer[datalen] = '\0';
911   }
912 }
913 
914 
915 /*
916  * 'do_monitor_printer_state()' - Do the MONITOR-PRINTER-STATE tests in the background.
917  */
918 
919 static void *				// O - Thread exit status
do_monitor_printer_state(ipptool_test_t * data)920 do_monitor_printer_state(
921     ipptool_test_t *data)		// I - Test data
922 {
923   int		i, j;			// Looping vars
924   char		scheme[32],		// URI scheme
925 		userpass[32],		// URI username:password
926 		host[256],		// URI hostname/IP address
927 		resource[256];		// URI resource path
928   int		port;			// URI port number
929   http_encryption_t encryption;		// Encryption to use
930   http_t	*http;			// Connection to printer
931   ipp_t		*request,		// IPP request
932 		*response = NULL;	// IPP response
933   http_status_t	status;			// Request status
934   ipp_attribute_t *found;		// Found attribute
935   ipptool_expect_t *expect;		// Current EXPECT test
936   char		buffer[131072];		// Copy buffer
937   int		num_pattrs;		// Number of printer attributes
938   const char	*pattrs[100];		// Printer attributes we care about
939 
940 
941   // Connect to the printer...
942   if (httpSeparateURI(HTTP_URI_CODING_ALL, data->monitor_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
943   {
944     print_fatal_error(data, "Bad printer URI \"%s\".", data->monitor_uri);
945     return (NULL);
946   }
947 
948   if (!_cups_strcasecmp(scheme, "https") || !_cups_strcasecmp(scheme, "ipps") || port == 443)
949     encryption = HTTP_ENCRYPTION_ALWAYS;
950   else
951     encryption = data->encryption;
952 
953   if ((http = httpConnect2(host, port, NULL, data->family, encryption, 1, 30000, NULL)) == NULL)
954   {
955     print_fatal_error(data, "Unable to connect to \"%s\" on port %d - %s", host, port, cupsLastErrorString());
956     return (0);
957   }
958 
959 #ifdef HAVE_LIBZ
960   httpSetDefaultField(http, HTTP_FIELD_ACCEPT_ENCODING, "deflate, gzip, identity");
961 #else
962   httpSetDefaultField(http, HTTP_FIELD_ACCEPT_ENCODING, "identity");
963 #endif /* HAVE_LIBZ */
964 
965   if (data->timeout > 0.0)
966     httpSetTimeout(http, data->timeout, timeout_cb, NULL);
967 
968   // Wait for the initial delay as needed...
969   if (data->monitor_delay)
970     usleep(data->monitor_delay);
971 
972   // Create a query request that we'll reuse...
973   request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
974   ippSetRequestId(request, data->request_id * 100 - 1);
975   ippSetVersion(request, data->version / 10, data->version % 10);
976   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, data->monitor_uri);
977   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
978 
979   for (i = data->num_monitor_expects, expect = data->monitor_expects, num_pattrs = 0; i > 0; i --, expect ++)
980   {
981     // Add EXPECT attribute names...
982     for (j = 0; j < num_pattrs; j ++)
983     {
984       if (!strcmp(expect->name, pattrs[j]))
985         break;
986     }
987 
988     if (j >= num_pattrs && num_pattrs < (int)(sizeof(pattrs) / sizeof(pattrs[0])))
989       pattrs[num_pattrs ++] = expect->name;
990   }
991 
992   if (num_pattrs > 0)
993     ippAddStrings(request, IPP_TAG_OPERATION, IPP_CONST_TAG(IPP_TAG_KEYWORD), "requested-attributes", num_pattrs, NULL, pattrs);
994 
995   // Loop until we need to stop...
996   while (!data->monitor_done && !Cancel)
997   {
998     // Poll the printer state...
999     ippSetRequestId(request, ippGetRequestId(request) + 1);
1000 
1001     if ((status = cupsSendRequest(http, request, resource, ippLength(request))) != HTTP_STATUS_ERROR)
1002     {
1003       response = cupsGetResponse(http, resource);
1004       status   = httpGetStatus(http);
1005     }
1006 
1007     if (!data->monitor_done && !Cancel && status == HTTP_STATUS_ERROR && httpError(data->http) != EINVAL &&
1008 #ifdef _WIN32
1009 	httpError(data->http) != WSAETIMEDOUT)
1010 #else
1011 	httpError(data->http) != ETIMEDOUT)
1012 #endif // _WIN32
1013     {
1014       if (httpReconnect2(http, 30000, NULL))
1015 	break;
1016     }
1017     else if (status == HTTP_STATUS_ERROR || status == HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED)
1018     {
1019       break;
1020     }
1021     else if (status != HTTP_STATUS_OK)
1022     {
1023       httpFlush(http);
1024 
1025       if (status == HTTP_STATUS_UNAUTHORIZED)
1026 	continue;
1027 
1028       break;
1029     }
1030 
1031     for (i = data->num_monitor_expects, expect = data->monitor_expects; i > 0; i --, expect ++)
1032     {
1033       if (expect->if_defined && !_ippVarsGet(data->vars, expect->if_defined))
1034 	continue;
1035 
1036       if (expect->if_not_defined && _ippVarsGet(data->vars, expect->if_not_defined))
1037 	continue;
1038 
1039       found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
1040 
1041       if ((found && expect->not_expect) ||
1042 	  (!found && !(expect->not_expect || expect->optional)) ||
1043 	  (found && !expect_matches(expect, found)) ||
1044 	  (expect->in_group && ippGetGroupTag(found) != expect->in_group) ||
1045 	  (expect->with_distinct && !with_distinct_values(NULL, found)))
1046       {
1047 	if (expect->define_no_match)
1048 	{
1049 	  _ippVarsSet(data->vars, expect->define_no_match, "1");
1050 	  data->monitor_done = 1;
1051 	}
1052 	break;
1053       }
1054 
1055       if (found)
1056 	ippAttributeString(found, buffer, sizeof(buffer));
1057 
1058       if (found && !with_value(data, NULL, expect->with_value, expect->with_flags, found, buffer, sizeof(buffer)))
1059       {
1060 	if (expect->define_no_match)
1061 	{
1062 	  _ippVarsSet(data->vars, expect->define_no_match, "1");
1063 	  data->monitor_done = 1;
1064 	}
1065 	break;
1066       }
1067 
1068       if (found && expect->count > 0 && ippGetCount(found) != expect->count)
1069       {
1070 	if (expect->define_no_match)
1071 	{
1072 	  _ippVarsSet(data->vars, expect->define_no_match, "1");
1073 	  data->monitor_done = 1;
1074 	}
1075 	break;
1076       }
1077 
1078       if (found && expect->display_match && (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout())))
1079 	cupsFilePrintf(cupsFileStdout(), "CONT]\n\n%s\n\n    %-68.68s [", expect->display_match, data->name);
1080 
1081       if (found && expect->define_match)
1082       {
1083 	_ippVarsSet(data->vars, expect->define_match, "1");
1084 	data->monitor_done = 1;
1085       }
1086 
1087       if (found && expect->define_value)
1088       {
1089 	if (!expect->with_value)
1090 	{
1091 	  int last = ippGetCount(found) - 1;
1092 					// Last element in attribute
1093 
1094 	  switch (ippGetValueTag(found))
1095 	  {
1096 	    case IPP_TAG_ENUM :
1097 	    case IPP_TAG_INTEGER :
1098 		snprintf(buffer, sizeof(buffer), "%d", ippGetInteger(found, last));
1099 		break;
1100 
1101 	    case IPP_TAG_BOOLEAN :
1102 		if (ippGetBoolean(found, last))
1103 		  strlcpy(buffer, "true", sizeof(buffer));
1104 		else
1105 		  strlcpy(buffer, "false", sizeof(buffer));
1106 		break;
1107 
1108 	    case IPP_TAG_CHARSET :
1109 	    case IPP_TAG_KEYWORD :
1110 	    case IPP_TAG_LANGUAGE :
1111 	    case IPP_TAG_MIMETYPE :
1112 	    case IPP_TAG_NAME :
1113 	    case IPP_TAG_NAMELANG :
1114 	    case IPP_TAG_TEXT :
1115 	    case IPP_TAG_TEXTLANG :
1116 	    case IPP_TAG_URI :
1117 	    case IPP_TAG_URISCHEME :
1118 		strlcpy(buffer, ippGetString(found, last, NULL), sizeof(buffer));
1119 		break;
1120 
1121 	    default :
1122 		ippAttributeString(found, buffer, sizeof(buffer));
1123 		break;
1124 	  }
1125 	}
1126 
1127 	_ippVarsSet(data->vars, expect->define_value, buffer);
1128 	data->monitor_done = 1;
1129       }
1130     }
1131 
1132     if (i == 0)
1133       data->monitor_done = 1;		// All tests passed
1134 
1135     ippDelete(response);
1136     response = NULL;
1137 
1138     // Sleep between requests...
1139     if (data->monitor_done || Cancel)
1140       break;
1141 
1142     usleep(data->monitor_interval);
1143   }
1144 
1145   // Close the connection to the printer and return...
1146   httpClose(http);
1147   ippDelete(request);
1148   ippDelete(response);
1149 
1150   return (NULL);
1151 }
1152 
1153 
1154 /*
1155  * 'do_test()' - Do a single test from the test file.
1156  */
1157 
1158 static int				/* O - 1 on success, 0 on failure */
do_test(_ipp_file_t * f,ipptool_test_t * data)1159 do_test(_ipp_file_t    *f,		/* I - IPP data file */
1160         ipptool_test_t *data)		/* I - Test data */
1161 
1162 {
1163   int	        i,			/* Looping var */
1164 		status_ok,		/* Did we get a matching status? */
1165 		repeat_count = 0,	/* Repeat count */
1166 		repeat_test;		/* Repeat the test? */
1167   ipptool_expect_t *expect;		/* Current expected attribute */
1168   ipp_t		*request,		/* IPP request */
1169 		*response;		/* IPP response */
1170   size_t	length;			/* Length of IPP request */
1171   http_status_t	status;			/* HTTP status */
1172   cups_array_t	*a;			/* Duplicate attribute array */
1173   ipp_tag_t	group;			/* Current group */
1174   ipp_attribute_t *attrptr,		/* Attribute pointer */
1175 		*found;			/* Found attribute */
1176   char		temp[1024];		/* Temporary string */
1177   cups_file_t	*reqfile;		/* File to send */
1178   ssize_t	bytes;			/* Bytes read/written */
1179   char		buffer[131072];		/* Copy buffer */
1180   size_t	widths[200];		/* Width of columns */
1181   const char	*error;			/* Current error */
1182 
1183 
1184   if (Cancel)
1185     return (0);
1186 
1187  /*
1188   * Show any PAUSE message, as needed...
1189   */
1190 
1191   if (data->pause[0])
1192   {
1193     if (!data->skip_test && !data->pass_test)
1194       pause_message(data->pause);
1195 
1196     data->pause[0] = '\0';
1197   }
1198 
1199  /*
1200   * Start the background thread as needed...
1201   */
1202 
1203   if (data->monitor_uri)
1204   {
1205     data->monitor_done   = 0;
1206     data->monitor_thread = _cupsThreadCreate((_cups_thread_func_t)do_monitor_printer_state, data);
1207   }
1208 
1209  /*
1210   * Take over control of the attributes in the request...
1211   */
1212 
1213   request  = f->attrs;
1214   f->attrs = NULL;
1215 
1216  /*
1217   * Submit the IPP request...
1218   */
1219 
1220   data->test_count ++;
1221 
1222   ippSetVersion(request, data->version / 10, data->version % 10);
1223   ippSetRequestId(request, data->request_id);
1224 
1225   if (data->output == IPPTOOL_OUTPUT_PLIST)
1226   {
1227     cupsFilePuts(data->outfile, "<dict>\n");
1228     cupsFilePuts(data->outfile, "<key>Name</key>\n");
1229     print_xml_string(data->outfile, "string", data->name);
1230     if (data->file_id[0])
1231     {
1232       cupsFilePuts(data->outfile, "<key>FileId</key>\n");
1233       print_xml_string(data->outfile, "string", data->file_id);
1234     }
1235     if (data->test_id[0])
1236     {
1237       cupsFilePuts(data->outfile, "<key>TestId</key>\n");
1238       print_xml_string(data->outfile, "string", data->test_id);
1239     }
1240     cupsFilePuts(data->outfile, "<key>Version</key>\n");
1241     cupsFilePrintf(data->outfile, "<string>%d.%d</string>\n", data->version / 10, data->version % 10);
1242     cupsFilePuts(data->outfile, "<key>Operation</key>\n");
1243     print_xml_string(data->outfile, "string", ippOpString(ippGetOperation(request)));
1244     cupsFilePuts(data->outfile, "<key>RequestId</key>\n");
1245     cupsFilePrintf(data->outfile, "<integer>%d</integer>\n", data->request_id);
1246     cupsFilePuts(data->outfile, "<key>RequestAttributes</key>\n");
1247     cupsFilePuts(data->outfile, "<array>\n");
1248     if (ippFirstAttribute(request))
1249     {
1250       cupsFilePuts(data->outfile, "<dict>\n");
1251       for (attrptr = ippFirstAttribute(request), group = ippGetGroupTag(attrptr); attrptr; attrptr = ippNextAttribute(request))
1252 	print_attr(data->outfile, data->output, attrptr, &group);
1253       cupsFilePuts(data->outfile, "</dict>\n");
1254     }
1255     cupsFilePuts(data->outfile, "</array>\n");
1256   }
1257 
1258   if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
1259   {
1260     if (data->verbosity)
1261     {
1262       cupsFilePrintf(cupsFileStdout(), "    %s:\n", ippOpString(ippGetOperation(request)));
1263 
1264       for (attrptr = ippFirstAttribute(request); attrptr; attrptr = ippNextAttribute(request))
1265 	print_attr(cupsFileStdout(), IPPTOOL_OUTPUT_TEST, attrptr, NULL);
1266     }
1267 
1268     cupsFilePrintf(cupsFileStdout(), "    %-68.68s [", data->name);
1269   }
1270 
1271   if ((data->skip_previous && !data->prev_pass) || data->skip_test || data->pass_test)
1272   {
1273     if (!data->pass_test)
1274       data->skip_count ++;
1275 
1276     ippDelete(request);
1277     request  = NULL;
1278     response = NULL;
1279 
1280     if (data->output == IPPTOOL_OUTPUT_PLIST)
1281     {
1282       cupsFilePuts(data->outfile, "<key>Successful</key>\n");
1283       cupsFilePuts(data->outfile, "<true />\n");
1284       cupsFilePuts(data->outfile, "<key>Skipped</key>\n");
1285       if (data->pass_test)
1286 	cupsFilePuts(data->outfile, "<false />\n");
1287       else
1288 	cupsFilePuts(data->outfile, "<true />\n");
1289       cupsFilePuts(data->outfile, "<key>StatusCode</key>\n");
1290       if (data->pass_test)
1291 	print_xml_string(data->outfile, "string", "pass");
1292       else
1293 	print_xml_string(data->outfile, "string", "skip");
1294       cupsFilePuts(data->outfile, "<key>ResponseAttributes</key>\n");
1295       cupsFilePuts(data->outfile, "<dict />\n");
1296     }
1297 
1298     if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
1299     {
1300       if (data->pass_test)
1301 	cupsFilePuts(cupsFileStdout(), "PASS]\n");
1302       else
1303 	cupsFilePuts(cupsFileStdout(), "SKIP]\n");
1304     }
1305 
1306     goto skip_error;
1307   }
1308 
1309   data->vars->password_tries = 0;
1310 
1311   do
1312   {
1313     if (data->delay > 0)
1314       usleep(data->delay);
1315 
1316     data->delay = data->repeat_interval;
1317     repeat_count ++;
1318 
1319     status = HTTP_STATUS_OK;
1320 
1321     if (data->transfer == IPPTOOL_TRANSFER_CHUNKED || (data->transfer == IPPTOOL_TRANSFER_AUTO && data->file[0]))
1322     {
1323      /*
1324       * Send request using chunking - a 0 length means "chunk".
1325       */
1326 
1327       length = 0;
1328     }
1329     else
1330     {
1331      /*
1332       * Send request using content length...
1333       */
1334 
1335       length = ippLength(request);
1336 
1337       if (data->file[0] && (reqfile = cupsFileOpen(data->file, "r")) != NULL)
1338       {
1339        /*
1340 	* Read the file to get the uncompressed file size...
1341 	*/
1342 
1343 	while ((bytes = cupsFileRead(reqfile, buffer, sizeof(buffer))) > 0)
1344 	  length += (size_t)bytes;
1345 
1346 	cupsFileClose(reqfile);
1347       }
1348     }
1349 
1350    /*
1351     * Send the request...
1352     */
1353 
1354     data->prev_pass = 1;
1355     repeat_test     = 0;
1356     response        = NULL;
1357 
1358     if (status != HTTP_STATUS_ERROR)
1359     {
1360       while (!response && !Cancel && data->prev_pass)
1361       {
1362         ippSetRequestId(request, ++ data->request_id);
1363 
1364 	status = cupsSendRequest(data->http, request, data->resource, length);
1365 
1366 #ifdef HAVE_LIBZ
1367 	if (data->compression[0])
1368 	  httpSetField(data->http, HTTP_FIELD_CONTENT_ENCODING, data->compression);
1369 #endif /* HAVE_LIBZ */
1370 
1371 	if (!Cancel && status == HTTP_STATUS_CONTINUE && ippGetState(request) == IPP_DATA && data->file[0])
1372 	{
1373 	  if ((reqfile = cupsFileOpen(data->file, "r")) != NULL)
1374 	  {
1375 	    while (!Cancel && (bytes = cupsFileRead(reqfile, buffer, sizeof(buffer))) > 0)
1376 	    {
1377 	      if ((status = cupsWriteRequestData(data->http, buffer, (size_t)bytes)) != HTTP_STATUS_CONTINUE)
1378 		break;
1379             }
1380 
1381 	    cupsFileClose(reqfile);
1382 	  }
1383 	  else
1384 	  {
1385 	    snprintf(buffer, sizeof(buffer), "%s: %s", data->file, strerror(errno));
1386 	    _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0);
1387 
1388 	    status = HTTP_STATUS_ERROR;
1389 	  }
1390 	}
1391 
1392        /*
1393 	* Get the server's response...
1394 	*/
1395 
1396 	if (!Cancel && status != HTTP_STATUS_ERROR)
1397 	{
1398 	  response = cupsGetResponse(data->http, data->resource);
1399 	  status   = httpGetStatus(data->http);
1400 	}
1401 
1402 	if (!Cancel && status == HTTP_STATUS_ERROR && httpError(data->http) != EINVAL &&
1403 #ifdef _WIN32
1404 	    httpError(data->http) != WSAETIMEDOUT)
1405 #else
1406 	    httpError(data->http) != ETIMEDOUT)
1407 #endif /* _WIN32 */
1408 	{
1409 	  if (httpReconnect2(data->http, 30000, NULL))
1410 	    data->prev_pass = 0;
1411 	}
1412 	else if (status == HTTP_STATUS_ERROR || status == HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED)
1413 	{
1414 	  data->prev_pass = 0;
1415 	  break;
1416 	}
1417 	else if (status != HTTP_STATUS_OK)
1418 	{
1419 	  httpFlush(data->http);
1420 
1421 	  if (status == HTTP_STATUS_UNAUTHORIZED)
1422 	    continue;
1423 
1424 	  break;
1425 	}
1426       }
1427     }
1428 
1429     if (!Cancel && status == HTTP_STATUS_ERROR && httpError(data->http) != EINVAL &&
1430 #ifdef _WIN32
1431 	httpError(data->http) != WSAETIMEDOUT)
1432 #else
1433 	httpError(data->http) != ETIMEDOUT)
1434 #endif /* _WIN32 */
1435     {
1436       if (httpReconnect2(data->http, 30000, NULL))
1437 	data->prev_pass = 0;
1438     }
1439     else if (status == HTTP_STATUS_ERROR)
1440     {
1441       if (!Cancel)
1442 	httpReconnect2(data->http, 30000, NULL);
1443 
1444       data->prev_pass = 0;
1445     }
1446     else if (status != HTTP_STATUS_OK)
1447     {
1448       httpFlush(data->http);
1449       data->prev_pass = 0;
1450     }
1451 
1452    /*
1453     * Check results of request...
1454     */
1455 
1456     cupsArrayClear(data->errors);
1457 
1458     if (httpGetVersion(data->http) != HTTP_1_1)
1459     {
1460       int version = (int)httpGetVersion(data->http);
1461 
1462       add_stringf(data->errors, "Bad HTTP version (%d.%d)", version / 100, version % 100);
1463     }
1464 
1465     if (data->validate_headers)
1466     {
1467       const char *header;               /* HTTP header value */
1468 
1469       if ((header = httpGetField(data->http, HTTP_FIELD_CONTENT_TYPE)) == NULL || _cups_strcasecmp(header, "application/ipp"))
1470 	add_stringf(data->errors, "Bad HTTP Content-Type in response (%s)", header && *header ? header : "<missing>");
1471 
1472       if ((header = httpGetField(data->http, HTTP_FIELD_DATE)) != NULL && *header && httpGetDateTime(header) == 0)
1473 	add_stringf(data->errors, "Bad HTTP Date in response (%s)", header);
1474     }
1475 
1476     if (!response)
1477     {
1478      /*
1479       * No response, log error...
1480       */
1481 
1482       add_stringf(data->errors, "IPP request failed with status %s (%s)", ippErrorString(cupsLastError()), cupsLastErrorString());
1483     }
1484     else
1485     {
1486      /*
1487       * Collect common attribute values...
1488       */
1489 
1490       if ((attrptr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) != NULL)
1491       {
1492 	snprintf(temp, sizeof(temp), "%d", ippGetInteger(attrptr, 0));
1493 	_ippVarsSet(data->vars, "job-id", temp);
1494       }
1495 
1496       if ((attrptr = ippFindAttribute(response, "job-uri", IPP_TAG_URI)) != NULL)
1497 	_ippVarsSet(data->vars, "job-uri", ippGetString(attrptr, 0, NULL));
1498 
1499       if ((attrptr = ippFindAttribute(response, "notify-subscription-id", IPP_TAG_INTEGER)) != NULL)
1500       {
1501 	snprintf(temp, sizeof(temp), "%d", ippGetInteger(attrptr, 0));
1502 	_ippVarsSet(data->vars, "notify-subscription-id", temp);
1503       }
1504 
1505      /*
1506       * Check response, validating groups and attributes and logging errors
1507       * as needed...
1508       */
1509 
1510       if (ippGetState(response) != IPP_DATA)
1511 	add_stringf(data->errors, "Missing end-of-attributes-tag in response (RFC 2910 section 3.5.1)");
1512 
1513       if (data->version)
1514       {
1515         int major, minor;		/* IPP version */
1516 
1517         major = ippGetVersion(response, &minor);
1518 
1519         if (major != (data->version / 10) || minor != (data->version % 10))
1520 	  add_stringf(data->errors, "Bad version %d.%d in response - expected %d.%d (RFC 8011 section 4.1.8).", major, minor, data->version / 10, data->version % 10);
1521       }
1522 
1523       if (ippGetRequestId(response) != data->request_id)
1524 	add_stringf(data->errors, "Bad request ID %d in response - expected %d (RFC 8011 section 4.1.1)", ippGetRequestId(response), data->request_id);
1525 
1526       attrptr = ippFirstAttribute(response);
1527       if (!attrptr)
1528       {
1529 	add_stringf(data->errors, "Missing first attribute \"attributes-charset (charset)\" in group operation-attributes-tag (RFC 8011 section 4.1.4).");
1530       }
1531       else
1532       {
1533 	if (!ippGetName(attrptr) || ippGetValueTag(attrptr) != IPP_TAG_CHARSET || ippGetGroupTag(attrptr) != IPP_TAG_OPERATION || ippGetCount(attrptr) != 1 ||strcmp(ippGetName(attrptr), "attributes-charset"))
1534 	  add_stringf(data->errors, "Bad first attribute \"%s (%s%s)\" in group %s, expected \"attributes-charset (charset)\" in group operation-attributes-tag (RFC 8011 section 4.1.4).", ippGetName(attrptr) ? ippGetName(attrptr) : "(null)", ippGetCount(attrptr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attrptr)), ippTagString(ippGetGroupTag(attrptr)));
1535 
1536 	attrptr = ippNextAttribute(response);
1537 	if (!attrptr)
1538 	  add_stringf(data->errors, "Missing second attribute \"attributes-natural-language (naturalLanguage)\" in group operation-attributes-tag (RFC 8011 section 4.1.4).");
1539 	else if (!ippGetName(attrptr) || ippGetValueTag(attrptr) != IPP_TAG_LANGUAGE || ippGetGroupTag(attrptr) != IPP_TAG_OPERATION || ippGetCount(attrptr) != 1 || strcmp(ippGetName(attrptr), "attributes-natural-language"))
1540 	  add_stringf(data->errors, "Bad first attribute \"%s (%s%s)\" in group %s, expected \"attributes-natural-language (naturalLanguage)\" in group operation-attributes-tag (RFC 8011 section 4.1.4).", ippGetName(attrptr) ? ippGetName(attrptr) : "(null)", ippGetCount(attrptr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attrptr)), ippTagString(ippGetGroupTag(attrptr)));
1541       }
1542 
1543       if ((attrptr = ippFindAttribute(response, "status-message", IPP_TAG_ZERO)) != NULL)
1544       {
1545         const char *status_message = ippGetString(attrptr, 0, NULL);
1546 						/* String value */
1547 
1548 	if (ippGetValueTag(attrptr) != IPP_TAG_TEXT)
1549 	  add_stringf(data->errors, "status-message (text(255)) has wrong value tag %s (RFC 8011 section 4.1.6.2).", ippTagString(ippGetValueTag(attrptr)));
1550 	if (ippGetGroupTag(attrptr) != IPP_TAG_OPERATION)
1551 	  add_stringf(data->errors, "status-message (text(255)) has wrong group tag %s (RFC 8011 section 4.1.6.2).", ippTagString(ippGetGroupTag(attrptr)));
1552 	if (ippGetCount(attrptr) != 1)
1553 	  add_stringf(data->errors, "status-message (text(255)) has %d values (RFC 8011 section 4.1.6.2).", ippGetCount(attrptr));
1554 	if (status_message && strlen(status_message) > 255)
1555 	  add_stringf(data->errors, "status-message (text(255)) has bad length %d (RFC 8011 section 4.1.6.2).", (int)strlen(status_message));
1556       }
1557 
1558       if ((attrptr = ippFindAttribute(response, "detailed-status-message",
1559 				       IPP_TAG_ZERO)) != NULL)
1560       {
1561         const char *detailed_status_message = ippGetString(attrptr, 0, NULL);
1562 						/* String value */
1563 
1564 	if (ippGetValueTag(attrptr) != IPP_TAG_TEXT)
1565 	  add_stringf(data->errors, "detailed-status-message (text(MAX)) has wrong value tag %s (RFC 8011 section 4.1.6.3).", ippTagString(ippGetValueTag(attrptr)));
1566 	if (ippGetGroupTag(attrptr) != IPP_TAG_OPERATION)
1567 	  add_stringf(data->errors, "detailed-status-message (text(MAX)) has wrong group tag %s (RFC 8011 section 4.1.6.3).", ippTagString(ippGetGroupTag(attrptr)));
1568 	if (ippGetCount(attrptr) != 1)
1569 	  add_stringf(data->errors, "detailed-status-message (text(MAX)) has %d values (RFC 8011 section 4.1.6.3).", ippGetCount(attrptr));
1570 	if (detailed_status_message && strlen(detailed_status_message) > 1023)
1571 	  add_stringf(data->errors, "detailed-status-message (text(MAX)) has bad length %d (RFC 8011 section 4.1.6.3).", (int)strlen(detailed_status_message));
1572       }
1573 
1574       a = cupsArrayNew((cups_array_func_t)strcmp, NULL);
1575 
1576       for (attrptr = ippFirstAttribute(response), group = ippGetGroupTag(attrptr);
1577 	   attrptr;
1578 	   attrptr = ippNextAttribute(response))
1579       {
1580 	if (ippGetGroupTag(attrptr) != group)
1581 	{
1582 	  int out_of_order = 0;	/* Are attribute groups out-of-order? */
1583 	  cupsArrayClear(a);
1584 
1585 	  switch (ippGetGroupTag(attrptr))
1586 	  {
1587 	    case IPP_TAG_ZERO :
1588 		break;
1589 
1590 	    case IPP_TAG_OPERATION :
1591 		out_of_order = 1;
1592 		break;
1593 
1594 	    case IPP_TAG_UNSUPPORTED_GROUP :
1595 		if (group != IPP_TAG_OPERATION)
1596 		  out_of_order = 1;
1597 		break;
1598 
1599 	    case IPP_TAG_JOB :
1600 	    case IPP_TAG_PRINTER :
1601 		if (group != IPP_TAG_OPERATION && group != IPP_TAG_UNSUPPORTED_GROUP)
1602 		  out_of_order = 1;
1603 		break;
1604 
1605 	    case IPP_TAG_SUBSCRIPTION :
1606 		if (group > ippGetGroupTag(attrptr) && group != IPP_TAG_DOCUMENT)
1607 		  out_of_order = 1;
1608 		break;
1609 
1610 	    default :
1611 		if (group > ippGetGroupTag(attrptr))
1612 		  out_of_order = 1;
1613 		break;
1614 	  }
1615 
1616 	  if (out_of_order)
1617 	    add_stringf(data->errors, "Attribute groups out of order (%s < %s)", ippTagString(ippGetGroupTag(attrptr)), ippTagString(group));
1618 
1619 	  if (ippGetGroupTag(attrptr) != IPP_TAG_ZERO)
1620 	    group = ippGetGroupTag(attrptr);
1621 	}
1622 
1623 	if (!ippValidateAttribute(attrptr))
1624 	  cupsArrayAdd(data->errors, (void *)cupsLastErrorString());
1625 
1626 	if (ippGetName(attrptr))
1627 	{
1628 	  if (cupsArrayFind(a, (void *)ippGetName(attrptr)) && data->output < IPPTOOL_OUTPUT_LIST)
1629 	    add_stringf(data->errors, "Duplicate \"%s\" attribute in %s group", ippGetName(attrptr), ippTagString(group));
1630 
1631 	  cupsArrayAdd(a, (void *)ippGetName(attrptr));
1632 	}
1633       }
1634 
1635       cupsArrayDelete(a);
1636 
1637      /*
1638       * Now check the test-defined expected status-code and attribute
1639       * values...
1640       */
1641 
1642       if (ippGetStatusCode(response) == IPP_STATUS_ERROR_BUSY && data->repeat_on_busy)
1643       {
1644         // Repeat on a server-error-busy status code...
1645         status_ok   = 1;
1646         repeat_test = 1;
1647       }
1648 
1649       for (i = 0, status_ok = 0; i < data->num_statuses; i ++)
1650       {
1651 	if (data->statuses[i].if_defined &&
1652 	    !_ippVarsGet(data->vars, data->statuses[i].if_defined))
1653 	  continue;
1654 
1655 	if (data->statuses[i].if_not_defined &&
1656 	    _ippVarsGet(data->vars, data->statuses[i].if_not_defined))
1657 	  continue;
1658 
1659 	if (ippGetStatusCode(response) == data->statuses[i].status)
1660 	{
1661 	  status_ok = 1;
1662 
1663 	  if (data->statuses[i].repeat_match && repeat_count < data->statuses[i].repeat_limit)
1664 	    repeat_test = 1;
1665 
1666 	  if (data->statuses[i].define_match)
1667 	    _ippVarsSet(data->vars, data->statuses[i].define_match, "1");
1668 	}
1669 	else
1670 	{
1671 	  if (data->statuses[i].repeat_no_match && repeat_count < data->statuses[i].repeat_limit)
1672 	    repeat_test = 1;
1673 
1674 	  if (data->statuses[i].define_no_match)
1675 	  {
1676 	    _ippVarsSet(data->vars, data->statuses[i].define_no_match, "1");
1677 	    status_ok = 1;
1678 	  }
1679 	}
1680       }
1681 
1682       if (!status_ok && data->num_statuses > 0)
1683       {
1684 	for (i = 0; i < data->num_statuses; i ++)
1685 	{
1686 	  if (data->statuses[i].if_defined &&
1687 	      !_ippVarsGet(data->vars, data->statuses[i].if_defined))
1688 	    continue;
1689 
1690 	  if (data->statuses[i].if_not_defined &&
1691 	      _ippVarsGet(data->vars, data->statuses[i].if_not_defined))
1692 	    continue;
1693 
1694 	  if (!data->statuses[i].repeat_match || repeat_count >= data->statuses[i].repeat_limit)
1695 	    add_stringf(data->errors, "EXPECTED: STATUS %s (got %s)", ippErrorString(data->statuses[i].status), ippErrorString(cupsLastError()));
1696 	}
1697 
1698 	if ((attrptr = ippFindAttribute(response, "status-message", IPP_TAG_TEXT)) != NULL)
1699 	  add_stringf(data->errors, "status-message=\"%s\"", ippGetString(attrptr, 0, NULL));
1700       }
1701 
1702       for (i = data->num_expects, expect = data->expects; i > 0; i --, expect ++)
1703       {
1704 	ipp_attribute_t *group_found;	/* Found parent attribute for group tests */
1705 
1706 	if (expect->if_defined && !_ippVarsGet(data->vars, expect->if_defined))
1707 	  continue;
1708 
1709 	if (expect->if_not_defined &&
1710 	    _ippVarsGet(data->vars, expect->if_not_defined))
1711 	  continue;
1712 
1713 	if ((found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL && expect->in_group && expect->in_group != ippGetGroupTag(found))
1714 	{
1715 	  while ((found = ippFindNextAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL)
1716 	    if (expect->in_group == ippGetGroupTag(found))
1717 	      break;
1718 	}
1719 
1720 	do
1721 	{
1722 	  group_found = found;
1723 
1724           if (expect->in_group && strchr(expect->name, '/'))
1725           {
1726             char	group_name[256],/* Parent attribute name */
1727 			*group_ptr;	/* Pointer into parent attribute name */
1728 
1729 	    strlcpy(group_name, expect->name, sizeof(group_name));
1730 	    if ((group_ptr = strchr(group_name, '/')) != NULL)
1731 	      *group_ptr = '\0';
1732 
1733 	    group_found = ippFindAttribute(response, group_name, IPP_TAG_ZERO);
1734 	  }
1735 
1736 	  if ((found && expect->not_expect) ||
1737 	      (!found && !(expect->not_expect || expect->optional)) ||
1738 	      (found && !expect_matches(expect, found)) ||
1739 	      (group_found && expect->in_group && ippGetGroupTag(group_found) != expect->in_group) ||
1740 	      (expect->with_distinct && !with_distinct_values(NULL, found)))
1741 	  {
1742 	    if (expect->define_no_match)
1743 	      _ippVarsSet(data->vars, expect->define_no_match, "1");
1744 	    else if (!expect->define_match && !expect->define_value)
1745 	    {
1746 	      if (found && expect->not_expect && !expect->with_value && !expect->with_value_from)
1747 		add_stringf(data->errors, "NOT EXPECTED: %s", expect->name);
1748 	      else if (!found && !(expect->not_expect || expect->optional))
1749 		add_stringf(data->errors, "EXPECTED: %s", expect->name);
1750 	      else if (found)
1751 	      {
1752 		if (!expect_matches(expect, found))
1753 		  add_stringf(data->errors, "EXPECTED: %s OF-TYPE %s (got %s)",
1754 			      expect->name, expect->of_type,
1755 			      ippTagString(ippGetValueTag(found)));
1756 
1757 		if (expect->in_group && ippGetGroupTag(group_found) != expect->in_group)
1758 		  add_stringf(data->errors, "EXPECTED: %s IN-GROUP %s (got %s).",
1759 			      expect->name, ippTagString(expect->in_group),
1760 			      ippTagString(ippGetGroupTag(group_found)));
1761 
1762                 if (expect->with_distinct)
1763                   with_distinct_values(data->errors, found);
1764 	      }
1765 	    }
1766 
1767 	    if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
1768 	      repeat_test = 1;
1769 	    break;
1770 	  }
1771 
1772 	  if (found)
1773 	    ippAttributeString(found, buffer, sizeof(buffer));
1774 
1775 	  if (found && expect->with_value_from && !with_value_from(NULL, ippFindAttribute(response, expect->with_value_from, IPP_TAG_ZERO), found, buffer, sizeof(buffer)))
1776 	  {
1777 	    if (expect->define_no_match)
1778 	      _ippVarsSet(data->vars, expect->define_no_match, "1");
1779 	    else if (!expect->define_match && !expect->define_value && ((!expect->repeat_match && !expect->repeat_no_match) || repeat_count >= expect->repeat_limit))
1780 	    {
1781 	      add_stringf(data->errors, "EXPECTED: %s WITH-VALUES-FROM %s", expect->name, expect->with_value_from);
1782 
1783 	      with_value_from(data->errors, ippFindAttribute(response, expect->with_value_from, IPP_TAG_ZERO), found, buffer, sizeof(buffer));
1784 	    }
1785 
1786 	    if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
1787 	      repeat_test = 1;
1788 
1789 	    break;
1790 	  }
1791 	  else if (found && !with_value(data, NULL, expect->with_value, expect->with_flags, found, buffer, sizeof(buffer)))
1792 	  {
1793 	    if (expect->define_no_match)
1794 	      _ippVarsSet(data->vars, expect->define_no_match, "1");
1795 	    else if (!expect->define_match && !expect->define_value &&
1796 		     !expect->repeat_match && (!expect->repeat_no_match || repeat_count >= expect->repeat_limit))
1797 	    {
1798 	      if (expect->with_flags & IPPTOOL_WITH_REGEX)
1799 		add_stringf(data->errors, "EXPECTED: %s %s /%s/", expect->name, with_flags_string(expect->with_flags), expect->with_value);
1800 	      else
1801 		add_stringf(data->errors, "EXPECTED: %s %s \"%s\"", expect->name, with_flags_string(expect->with_flags), expect->with_value);
1802 
1803 	      with_value(data, data->errors, expect->with_value, expect->with_flags, found, buffer, sizeof(buffer));
1804 	    }
1805 
1806 	    if (expect->repeat_no_match &&
1807 		repeat_count < expect->repeat_limit)
1808 	      repeat_test = 1;
1809 
1810 	    break;
1811 	  }
1812 
1813 	  if (found && expect->count > 0 && ippGetCount(found) != expect->count)
1814 	  {
1815 	    if (expect->define_no_match)
1816 	      _ippVarsSet(data->vars, expect->define_no_match, "1");
1817 	    else if (!expect->define_match && !expect->define_value)
1818 	    {
1819 	      add_stringf(data->errors, "EXPECTED: %s COUNT %d (got %d)", expect->name, expect->count, ippGetCount(found));
1820 	    }
1821 
1822 	    if (expect->repeat_no_match &&
1823 		repeat_count < expect->repeat_limit)
1824 	      repeat_test = 1;
1825 
1826 	    break;
1827 	  }
1828 
1829 	  if (found && expect->same_count_as)
1830 	  {
1831 	    attrptr = ippFindAttribute(response, expect->same_count_as,
1832 				       IPP_TAG_ZERO);
1833 
1834 	    if (!attrptr || ippGetCount(attrptr) != ippGetCount(found))
1835 	    {
1836 	      if (expect->define_no_match)
1837 		_ippVarsSet(data->vars, expect->define_no_match, "1");
1838 	      else if (!expect->define_match && !expect->define_value)
1839 	      {
1840 		if (!attrptr)
1841 		  add_stringf(data->errors, "EXPECTED: %s (%d values) SAME-COUNT-AS %s (not returned)", expect->name, ippGetCount(found), expect->same_count_as);
1842 		else if (ippGetCount(attrptr) != ippGetCount(found))
1843 		  add_stringf(data->errors, "EXPECTED: %s (%d values) SAME-COUNT-AS %s (%d values)", expect->name, ippGetCount(found), expect->same_count_as, ippGetCount(attrptr));
1844 	      }
1845 
1846 	      if (expect->repeat_no_match &&
1847 		  repeat_count < expect->repeat_limit)
1848 		repeat_test = 1;
1849 
1850 	      break;
1851 	    }
1852 	  }
1853 
1854 	  if (found && expect->display_match && (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout())))
1855 	    cupsFilePrintf(cupsFileStdout(), "\n%s\n\n", expect->display_match);
1856 
1857 	  if (found && expect->define_match)
1858 	    _ippVarsSet(data->vars, expect->define_match, "1");
1859 
1860 	  if (found && expect->define_value)
1861 	  {
1862 	    if (!expect->with_value)
1863 	    {
1864 	      int last = ippGetCount(found) - 1;
1865 					/* Last element in attribute */
1866 
1867 	      switch (ippGetValueTag(found))
1868 	      {
1869 		case IPP_TAG_ENUM :
1870 		case IPP_TAG_INTEGER :
1871 		    snprintf(buffer, sizeof(buffer), "%d", ippGetInteger(found, last));
1872 		    break;
1873 
1874 		case IPP_TAG_BOOLEAN :
1875 		    if (ippGetBoolean(found, last))
1876 		      strlcpy(buffer, "true", sizeof(buffer));
1877 		    else
1878 		      strlcpy(buffer, "false", sizeof(buffer));
1879 		    break;
1880 
1881 		case IPP_TAG_RESOLUTION :
1882 		    {
1883 		      int	xres,	/* Horizontal resolution */
1884 				yres;	/* Vertical resolution */
1885 		      ipp_res_t	units;	/* Resolution units */
1886 
1887 		      xres = ippGetResolution(found, last, &yres, &units);
1888 
1889 		      if (xres == yres)
1890 			snprintf(buffer, sizeof(buffer), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
1891 		      else
1892 			snprintf(buffer, sizeof(buffer), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
1893 		    }
1894 		    break;
1895 
1896 		case IPP_TAG_CHARSET :
1897 		case IPP_TAG_KEYWORD :
1898 		case IPP_TAG_LANGUAGE :
1899 		case IPP_TAG_MIMETYPE :
1900 		case IPP_TAG_NAME :
1901 		case IPP_TAG_NAMELANG :
1902 		case IPP_TAG_TEXT :
1903 		case IPP_TAG_TEXTLANG :
1904 		case IPP_TAG_URI :
1905 		case IPP_TAG_URISCHEME :
1906 		    strlcpy(buffer, ippGetString(found, last, NULL), sizeof(buffer));
1907 		    break;
1908 
1909 		default :
1910 		    ippAttributeString(found, buffer, sizeof(buffer));
1911 		    break;
1912 	      }
1913 	    }
1914 
1915 	    _ippVarsSet(data->vars, expect->define_value, buffer);
1916 	  }
1917 
1918 	  if (found && expect->repeat_match &&
1919 	      repeat_count < expect->repeat_limit)
1920 	    repeat_test = 1;
1921 	}
1922 	while (expect->expect_all && (found = ippFindNextAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL);
1923       }
1924     }
1925 
1926    /*
1927     * If we are going to repeat this test, display intermediate results...
1928     */
1929 
1930     if (repeat_test)
1931     {
1932       if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
1933       {
1934 	cupsFilePrintf(cupsFileStdout(), "%04d]\n", repeat_count);
1935 \
1936 	if (data->num_displayed > 0)
1937 	{
1938 	  for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
1939 	  {
1940 	    const char *attrname = ippGetName(attrptr);
1941 	    if (attrname)
1942 	    {
1943 	      for (i = 0; i < data->num_displayed; i ++)
1944 	      {
1945 		if (!strcmp(data->displayed[i], attrname))
1946 		{
1947 		  print_attr(cupsFileStdout(), IPPTOOL_OUTPUT_TEST, attrptr, NULL);
1948 		  break;
1949 		}
1950 	      }
1951 	    }
1952 	  }
1953 	}
1954       }
1955 
1956       if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
1957       {
1958 	cupsFilePrintf(cupsFileStdout(), "    %-68.68s [", data->name);
1959       }
1960 
1961       ippDelete(response);
1962       response = NULL;
1963     }
1964   }
1965   while (repeat_test);
1966 
1967   ippDelete(request);
1968 
1969   request = NULL;
1970 
1971   if (cupsArrayCount(data->errors) > 0)
1972     data->prev_pass = data->pass = 0;
1973 
1974   if (data->prev_pass)
1975     data->pass_count ++;
1976   else
1977     data->fail_count ++;
1978 
1979   if (data->output == IPPTOOL_OUTPUT_PLIST)
1980   {
1981     cupsFilePuts(data->outfile, "<key>Successful</key>\n");
1982     cupsFilePuts(data->outfile, data->prev_pass ? "<true />\n" : "<false />\n");
1983     cupsFilePuts(data->outfile, "<key>StatusCode</key>\n");
1984     print_xml_string(data->outfile, "string", ippErrorString(cupsLastError()));
1985     cupsFilePuts(data->outfile, "<key>ResponseAttributes</key>\n");
1986     cupsFilePuts(data->outfile, "<array>\n");
1987     cupsFilePuts(data->outfile, "<dict>\n");
1988     for (attrptr = ippFirstAttribute(response), group = ippGetGroupTag(attrptr);
1989 	 attrptr;
1990 	 attrptr = ippNextAttribute(response))
1991       print_attr(data->outfile, data->output, attrptr, &group);
1992     cupsFilePuts(data->outfile, "</dict>\n");
1993     cupsFilePuts(data->outfile, "</array>\n");
1994   }
1995   else if (data->output == IPPTOOL_OUTPUT_IPPSERVER && response)
1996   {
1997     for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
1998     {
1999       if (!ippGetName(attrptr) || ippGetGroupTag(attrptr) != IPP_TAG_PRINTER)
2000 	continue;
2001 
2002       print_ippserver_attr(data, attrptr, 0);
2003     }
2004   }
2005 
2006   if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
2007   {
2008     cupsFilePuts(cupsFileStdout(), data->prev_pass ? "PASS]\n" : "FAIL]\n");
2009 
2010     if (!data->prev_pass || (data->verbosity && response))
2011     {
2012       cupsFilePrintf(cupsFileStdout(), "        RECEIVED: %lu bytes in response\n", (unsigned long)ippLength(response));
2013       cupsFilePrintf(cupsFileStdout(), "        status-code = %s (%s)\n", ippErrorString(cupsLastError()), cupsLastErrorString());
2014 
2015       if (data->verbosity && response)
2016       {
2017 	for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
2018 	  print_attr(cupsFileStdout(), IPPTOOL_OUTPUT_TEST, attrptr, NULL);
2019       }
2020     }
2021   }
2022   else if (!data->prev_pass && data->output != IPPTOOL_OUTPUT_QUIET)
2023     fprintf(stderr, "%s\n", cupsLastErrorString());
2024 
2025   if (data->prev_pass && data->output >= IPPTOOL_OUTPUT_LIST && !data->verbosity && data->num_displayed > 0)
2026   {
2027     size_t	width;			/* Length of value */
2028 
2029     for (i = 0; i < data->num_displayed; i ++)
2030     {
2031       widths[i] = strlen(data->displayed[i]);
2032 
2033       for (attrptr = ippFindAttribute(response, data->displayed[i], IPP_TAG_ZERO);
2034 	   attrptr;
2035 	   attrptr = ippFindNextAttribute(response, data->displayed[i], IPP_TAG_ZERO))
2036       {
2037 	width = ippAttributeString(attrptr, NULL, 0);
2038 	if (width > widths[i])
2039 	  widths[i] = width;
2040       }
2041     }
2042 
2043     if (data->output == IPPTOOL_OUTPUT_CSV)
2044       print_csv(data, NULL, NULL, data->num_displayed, data->displayed, widths);
2045     else
2046       print_line(data, NULL, NULL, data->num_displayed, data->displayed, widths);
2047 
2048     attrptr = ippFirstAttribute(response);
2049 
2050     while (attrptr)
2051     {
2052       while (attrptr && ippGetGroupTag(attrptr) <= IPP_TAG_OPERATION)
2053 	attrptr = ippNextAttribute(response);
2054 
2055       if (attrptr)
2056       {
2057 	if (data->output == IPPTOOL_OUTPUT_CSV)
2058 	  attrptr = print_csv(data, response, attrptr, data->num_displayed, data->displayed, widths);
2059 	else
2060 	  attrptr = print_line(data, response, attrptr, data->num_displayed, data->displayed, widths);
2061 
2062 	while (attrptr && ippGetGroupTag(attrptr) > IPP_TAG_OPERATION)
2063 	  attrptr = ippNextAttribute(response);
2064       }
2065     }
2066   }
2067   else if (!data->prev_pass)
2068   {
2069     if (data->output == IPPTOOL_OUTPUT_PLIST)
2070     {
2071       cupsFilePuts(data->outfile, "<key>Errors</key>\n");
2072       cupsFilePuts(data->outfile, "<array>\n");
2073 
2074       for (error = (char *)cupsArrayFirst(data->errors);
2075 	   error;
2076 	   error = (char *)cupsArrayNext(data->errors))
2077 	print_xml_string(data->outfile, "string", error);
2078 
2079       cupsFilePuts(data->outfile, "</array>\n");
2080     }
2081 
2082     if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
2083     {
2084       for (error = (char *)cupsArrayFirst(data->errors);
2085 	   error;
2086 	   error = (char *)cupsArrayNext(data->errors))
2087 	cupsFilePrintf(cupsFileStdout(), "        %s\n", error);
2088     }
2089   }
2090 
2091   if (data->num_displayed > 0 && !data->verbosity && response && (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout())))
2092   {
2093     for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
2094     {
2095       if (ippGetName(attrptr))
2096       {
2097 	for (i = 0; i < data->num_displayed; i ++)
2098 	{
2099 	  if (!strcmp(data->displayed[i], ippGetName(attrptr)))
2100 	  {
2101 	    print_attr(data->outfile, data->output, attrptr, NULL);
2102 	    break;
2103 	  }
2104 	}
2105       }
2106     }
2107   }
2108 
2109   skip_error:
2110 
2111   if (data->monitor_thread)
2112   {
2113     data->monitor_done = 1;
2114     _cupsThreadWait(data->monitor_thread);
2115   }
2116 
2117   if (data->output == IPPTOOL_OUTPUT_PLIST)
2118     cupsFilePuts(data->outfile, "</dict>\n");
2119 
2120   ippDelete(response);
2121   response = NULL;
2122 
2123   for (i = 0; i < data->num_statuses; i ++)
2124   {
2125     free(data->statuses[i].if_defined);
2126     free(data->statuses[i].if_not_defined);
2127     free(data->statuses[i].define_match);
2128     free(data->statuses[i].define_no_match);
2129   }
2130   data->num_statuses = 0;
2131 
2132   for (i = data->num_expects, expect = data->expects; i > 0; i --, expect ++)
2133   {
2134     free(expect->name);
2135     free(expect->of_type);
2136     free(expect->same_count_as);
2137     free(expect->if_defined);
2138     free(expect->if_not_defined);
2139     free(expect->with_value);
2140     free(expect->define_match);
2141     free(expect->define_no_match);
2142     free(expect->define_value);
2143     free(expect->display_match);
2144   }
2145   data->num_expects = 0;
2146 
2147   for (i = 0; i < data->num_displayed; i ++)
2148     free(data->displayed[i]);
2149   data->num_displayed = 0;
2150 
2151   free(data->monitor_uri);
2152   data->monitor_uri = NULL;
2153 
2154   for (i = data->num_monitor_expects, expect = data->monitor_expects; i > 0; i --, expect ++)
2155   {
2156     free(expect->name);
2157     free(expect->of_type);
2158     free(expect->same_count_as);
2159     free(expect->if_defined);
2160     free(expect->if_not_defined);
2161     free(expect->with_value);
2162     free(expect->define_match);
2163     free(expect->define_no_match);
2164     free(expect->define_value);
2165     free(expect->display_match);
2166   }
2167   data->num_monitor_expects = 0;
2168 
2169   return (data->ignore_errors || data->prev_pass);
2170 }
2171 
2172 
2173 /*
2174  * 'do_tests()' - Do tests as specified in the test file.
2175  */
2176 
2177 static int				/* O - 1 on success, 0 on failure */
do_tests(const char * testfile,ipptool_test_t * data)2178 do_tests(const char     *testfile,	/* I - Test file to use */
2179          ipptool_test_t *data)		/* I - Test data */
2180 {
2181   http_encryption_t encryption;		/* Encryption mode */
2182 
2183 
2184  /*
2185   * Connect to the printer/server...
2186   */
2187 
2188   if (!_cups_strcasecmp(data->vars->scheme, "https") || !_cups_strcasecmp(data->vars->scheme, "ipps") || data->vars->port == 443)
2189     encryption = HTTP_ENCRYPTION_ALWAYS;
2190   else
2191     encryption = data->encryption;
2192 
2193   if ((data->http = httpConnect2(data->vars->host, data->vars->port, NULL, data->family, encryption, 1, 30000, NULL)) == NULL)
2194   {
2195     print_fatal_error(data, "Unable to connect to \"%s\" on port %d - %s", data->vars->host, data->vars->port, cupsLastErrorString());
2196     return (0);
2197   }
2198 
2199 #ifdef HAVE_LIBZ
2200   httpSetDefaultField(data->http, HTTP_FIELD_ACCEPT_ENCODING, "deflate, gzip, identity");
2201 #else
2202   httpSetDefaultField(data->http, HTTP_FIELD_ACCEPT_ENCODING, "identity");
2203 #endif /* HAVE_LIBZ */
2204 
2205   if (data->timeout > 0.0)
2206     httpSetTimeout(data->http, data->timeout, timeout_cb, NULL);
2207 
2208  /*
2209   * Run tests...
2210   */
2211 
2212   _ippFileParse(data->vars, testfile, (void *)data);
2213 
2214  /*
2215   * Close connection and return...
2216   */
2217 
2218   httpClose(data->http);
2219   data->http = NULL;
2220 
2221   return (data->pass);
2222 }
2223 
2224 
2225 /*
2226  * 'error_cb()' - Print/add an error message.
2227  */
2228 
2229 static int				/* O - 1 to continue, 0 to stop */
error_cb(_ipp_file_t * f,ipptool_test_t * data,const char * error)2230 error_cb(_ipp_file_t      *f,		/* I - IPP file data */
2231          ipptool_test_t *data,	/* I - Test data */
2232          const char       *error)	/* I - Error message */
2233 {
2234   (void)f;
2235 
2236   print_fatal_error(data, "%s", error);
2237 
2238   return (1);
2239 }
2240 
2241 
2242 /*
2243  * 'expect_matches()' - Return true if the tag matches the specification.
2244  */
2245 
2246 static int				/* O - 1 if matches, 0 otherwise */
expect_matches(ipptool_expect_t * expect,ipp_attribute_t * attr)2247 expect_matches(
2248     ipptool_expect_t *expect,		/* I - Expected attribute */
2249     ipp_attribute_t  *attr)		/* I - Attribute */
2250 {
2251   int		i,			/* Looping var */
2252 		count,			/* Number of values */
2253 		match;			/* Match? */
2254   char		*of_type,		/* Type name to match */
2255 		*paren,			/* Pointer to opening parenthesis */
2256 		*next,			/* Next name to match */
2257 		sep;			/* Separator character */
2258   ipp_tag_t	value_tag;		/* Syntax/value tag */
2259   int		lower, upper;		/* Lower and upper bounds for syntax */
2260 
2261 
2262  /*
2263   * If we don't expect a particular type, return immediately...
2264   */
2265 
2266   if (!expect->of_type)
2267     return (1);
2268 
2269  /*
2270   * Parse the "of_type" value since the string can contain multiple attribute
2271   * types separated by "," or "|"...
2272   */
2273 
2274   value_tag = ippGetValueTag(attr);
2275   count     = ippGetCount(attr);
2276 
2277   for (of_type = expect->of_type, match = 0; !match && *of_type; of_type = next)
2278   {
2279    /*
2280     * Find the next separator, and set it (temporarily) to nul if present.
2281     */
2282 
2283     for (next = of_type; *next && *next != '|' && *next != ','; next ++);
2284 
2285     if ((sep = *next) != '\0')
2286       *next = '\0';
2287 
2288    /*
2289     * Support some meta-types to make it easier to write the test file.
2290     */
2291 
2292     if ((paren = strchr(of_type, '(')) != NULL)
2293     {
2294       char *ptr;			// Pointer into syntax string
2295 
2296       *paren = '\0';
2297 
2298       if (!strncmp(paren + 1, "MIN:", 4))
2299       {
2300         lower = INT_MIN;
2301         ptr   = paren + 5;
2302       }
2303       else if ((ptr = strchr(paren + 1, ':')) != NULL)
2304       {
2305         lower = atoi(paren + 1);
2306       }
2307       else
2308       {
2309         lower = 0;
2310         ptr   = paren + 1;
2311       }
2312 
2313       if (!strcmp(ptr, "MAX)"))
2314         upper = INT_MAX;
2315       else
2316         upper = atoi(ptr);
2317     }
2318     else
2319     {
2320       lower = INT_MIN;
2321       upper = INT_MAX;
2322     }
2323 
2324     if (!strcmp(of_type, "text"))
2325     {
2326       if (upper == INT_MAX)
2327         upper = 1023;
2328 
2329       if (value_tag == IPP_TAG_TEXTLANG || value_tag == IPP_TAG_TEXT)
2330       {
2331         for (i = 0; i < count; i ++)
2332 	{
2333 	  if (strlen(ippGetString(attr, i, NULL)) > (size_t)upper)
2334 	    break;
2335 	}
2336 
2337 	match = (i == count);
2338       }
2339     }
2340     else if (!strcmp(of_type, "name"))
2341     {
2342       if (upper == INT_MAX)
2343         upper = 255;
2344 
2345       if (value_tag == IPP_TAG_NAMELANG || value_tag == IPP_TAG_NAME)
2346       {
2347         for (i = 0; i < count; i ++)
2348 	{
2349 	  if (strlen(ippGetString(attr, i, NULL)) > (size_t)upper)
2350 	    break;
2351 	}
2352 
2353 	match = (i == count);
2354       }
2355     }
2356     else if (!strcmp(of_type, "collection"))
2357     {
2358       match = value_tag == IPP_TAG_BEGIN_COLLECTION;
2359     }
2360     else if (value_tag == ippTagValue(of_type))
2361     {
2362       switch (value_tag)
2363       {
2364         case IPP_TAG_KEYWORD :
2365         case IPP_TAG_URI :
2366             if (upper == INT_MAX)
2367             {
2368               if (value_tag == IPP_TAG_KEYWORD)
2369 		upper = 255;
2370 	      else
2371 	        upper = 1023;
2372 	    }
2373 
2374 	    for (i = 0; i < count; i ++)
2375 	    {
2376 	      if (strlen(ippGetString(attr, i, NULL)) > (size_t)upper)
2377 		break;
2378 	    }
2379 
2380 	    match = (i == count);
2381 	    break;
2382 
2383         case IPP_TAG_STRING :
2384             if (upper == INT_MAX)
2385 	      upper = 1023;
2386 
2387 	    for (i = 0; i < count; i ++)
2388 	    {
2389 	      int	datalen;	// Length of octetString value
2390 
2391 	      ippGetOctetString(attr, i, &datalen);
2392 
2393 	      if (datalen > upper)
2394 		break;
2395 	    }
2396 
2397 	    match = (i == count);
2398 	    break;
2399 
2400 	case IPP_TAG_INTEGER :
2401 	    for (i = 0; i < count; i ++)
2402 	    {
2403 	      int value = ippGetInteger(attr, i);
2404 					// Integer value
2405 
2406 	      if (value < lower || value > upper)
2407 		break;
2408 	    }
2409 
2410 	    match = (i == count);
2411 	    break;
2412 
2413 	case IPP_TAG_RANGE :
2414 	    for (i = 0; i < count; i ++)
2415 	    {
2416 	      int vupper, vlower = ippGetRange(attr, i, &vupper);
2417 					// Range value
2418 
2419 	      if (vlower < lower || vlower > upper || vupper < lower || vupper > upper)
2420 		break;
2421 	    }
2422 
2423 	    match = (i == count);
2424 	    break;
2425 
2426 	default :
2427 	    // No other constraints, so this is a match
2428 	    match = 1;
2429 	    break;
2430       }
2431     }
2432 
2433    /*
2434     * Restore the separators if we have them...
2435     */
2436 
2437     if (paren)
2438       *paren = '(';
2439 
2440     if (sep)
2441       *next++ = sep;
2442   }
2443 
2444   return (match);
2445 }
2446 
2447 
2448 /*
2449  * 'get_filename()' - Get a filename based on the current test file.
2450  */
2451 
2452 static char *				/* O - Filename */
get_filename(const char * testfile,char * dst,const char * src,size_t dstsize)2453 get_filename(const char *testfile,	/* I - Current test file */
2454              char       *dst,		/* I - Destination filename */
2455 	     const char *src,		/* I - Source filename */
2456              size_t     dstsize)	/* I - Size of destination buffer */
2457 {
2458   char			*dstptr;	/* Pointer into destination */
2459   _cups_globals_t	*cg = _cupsGlobals();
2460 					/* Global data */
2461 
2462 
2463   if (*src == '<' && src[strlen(src) - 1] == '>')
2464   {
2465    /*
2466     * Map <filename> to CUPS_DATADIR/ipptool/filename...
2467     */
2468 
2469     snprintf(dst, dstsize, "%s/ipptool/%s", cg->cups_datadir, src + 1);
2470     dstptr = dst + strlen(dst) - 1;
2471     if (*dstptr == '>')
2472       *dstptr = '\0';
2473   }
2474   else if (!access(src, R_OK) || *src == '/'
2475 #ifdef _WIN32
2476            || (isalpha(*src & 255) && src[1] == ':')
2477 #endif /* _WIN32 */
2478            )
2479   {
2480    /*
2481     * Use the path as-is...
2482     */
2483 
2484     strlcpy(dst, src, dstsize);
2485   }
2486   else
2487   {
2488    /*
2489     * Make path relative to testfile...
2490     */
2491 
2492     strlcpy(dst, testfile, dstsize);
2493     if ((dstptr = strrchr(dst, '/')) != NULL)
2494       dstptr ++;
2495     else
2496       dstptr = dst; /* Should never happen */
2497 
2498     strlcpy(dstptr, src, dstsize - (size_t)(dstptr - dst));
2499 
2500 #if _WIN32
2501     if (_access(dst, 0))
2502     {
2503      /*
2504       * Not available relative to the testfile, see if it can be found on the
2505       * desktop...
2506       */
2507       const char *userprofile = getenv("USERPROFILE");
2508 					/* User home directory */
2509 
2510       if (userprofile)
2511         snprintf(dst, dstsize, "%s/Desktop/%s", userprofile, src);
2512     }
2513 #endif /* _WIN32 */
2514   }
2515 
2516   return (dst);
2517 }
2518 
2519 
2520 /*
2521  * 'get_string()' - Get a pointer to a string value or the portion of interest.
2522  */
2523 
2524 static const char *			/* O - Pointer to string */
get_string(ipp_attribute_t * attr,int element,int flags,char * buffer,size_t bufsize)2525 get_string(ipp_attribute_t *attr,	/* I - IPP attribute */
2526            int             element,	/* I - Element to fetch */
2527            int             flags,	/* I - Value ("with") flags */
2528            char            *buffer,	/* I - Temporary buffer */
2529 	   size_t          bufsize)	/* I - Size of temporary buffer */
2530 {
2531   const char	*value;			/* Value */
2532   char		*ptr,			/* Pointer into value */
2533 		scheme[256],		/* URI scheme */
2534 		userpass[256],		/* Username/password */
2535 		hostname[256],		/* Hostname */
2536 		resource[1024];		/* Resource */
2537   int		port;			/* Port number */
2538 
2539 
2540   value = ippGetString(attr, element, NULL);
2541 
2542   if (flags & IPPTOOL_WITH_HOSTNAME)
2543   {
2544     if (httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), buffer, (int)bufsize, &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
2545       buffer[0] = '\0';
2546 
2547     ptr = buffer + strlen(buffer) - 1;
2548     if (ptr >= buffer && *ptr == '.')
2549       *ptr = '\0';			/* Drop trailing "." */
2550 
2551     return (buffer);
2552   }
2553   else if (flags & IPPTOOL_WITH_RESOURCE)
2554   {
2555     if (httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, buffer, (int)bufsize) < HTTP_URI_STATUS_OK)
2556       buffer[0] = '\0';
2557 
2558     return (buffer);
2559   }
2560   else if (flags & IPPTOOL_WITH_SCHEME)
2561   {
2562     if (httpSeparateURI(HTTP_URI_CODING_ALL, value, buffer, (int)bufsize, userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
2563       buffer[0] = '\0';
2564 
2565     return (buffer);
2566   }
2567   else if (ippGetValueTag(attr) == IPP_TAG_URI && (!strncmp(value, "ipp://", 6) || !strncmp(value, "http://", 7) || !strncmp(value, "ipps://", 7) || !strncmp(value, "https://", 8)))
2568   {
2569     http_uri_status_t status = httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource));
2570 
2571     if (status < HTTP_URI_STATUS_OK)
2572     {
2573      /*
2574       * Bad URI...
2575       */
2576 
2577       buffer[0] = '\0';
2578     }
2579     else
2580     {
2581      /*
2582       * Normalize URI with no trailing dot...
2583       */
2584 
2585       if ((ptr = hostname + strlen(hostname) - 1) >= hostname && *ptr == '.')
2586 	*ptr = '\0';
2587 
2588       httpAssembleURI(HTTP_URI_CODING_ALL, buffer, (int)bufsize, scheme, userpass, hostname, port, resource);
2589     }
2590 
2591     return (buffer);
2592   }
2593   else
2594     return (value);
2595 }
2596 
2597 
2598 /*
2599  * 'init_data()' - Initialize test data.
2600  */
2601 
2602 static void
init_data(ipptool_test_t * data)2603 init_data(ipptool_test_t *data)	/* I - Data */
2604 {
2605   memset(data, 0, sizeof(ipptool_test_t));
2606 
2607   data->output       = IPPTOOL_OUTPUT_LIST;
2608   data->outfile      = cupsFileStdout();
2609   data->family       = AF_UNSPEC;
2610   data->def_transfer = IPPTOOL_TRANSFER_AUTO;
2611   data->def_version  = 11;
2612   data->errors       = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
2613   data->pass         = 1;
2614   data->prev_pass    = 1;
2615   data->request_id   = (CUPS_RAND() % 1000) * 137;
2616   data->show_header  = 1;
2617 }
2618 
2619 
2620 /*
2621  * 'iso_date()' - Return an ISO 8601 date/time string for the given IPP dateTime
2622  *                value.
2623  */
2624 
2625 static char *				/* O - ISO 8601 date/time string */
iso_date(const ipp_uchar_t * date)2626 iso_date(const ipp_uchar_t *date)	/* I - IPP (RFC 1903) date/time value */
2627 {
2628   time_t	utctime;		/* UTC time since 1970 */
2629   struct tm	utcdate;		/* UTC date/time */
2630   static char	buffer[255];		/* String buffer */
2631 
2632 
2633   utctime = ippDateToTime(date);
2634   gmtime_r(&utctime, &utcdate);
2635 
2636   snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02dZ",
2637 	   utcdate.tm_year + 1900, utcdate.tm_mon + 1, utcdate.tm_mday,
2638 	   utcdate.tm_hour, utcdate.tm_min, utcdate.tm_sec);
2639 
2640   return (buffer);
2641 }
2642 
2643 
2644 /*
2645  * 'parse_monitor_printer_state()' - Parse the MONITOR-PRINTER-STATE directive.
2646  *
2647  * MONITOR-PRINTER-STATE [printer-uri] {
2648  *     DELAY nnn
2649  *     EXPECT attribute-name ...
2650  * }
2651  */
2652 
2653 static int				/* O - 1 to continue, 0 to stop */
parse_monitor_printer_state(_ipp_file_t * f,ipptool_test_t * data)2654 parse_monitor_printer_state(
2655     _ipp_file_t    *f,			/* I - IPP file data */
2656     ipptool_test_t *data)		/* I - Test data */
2657 {
2658   char	token[256],			/* Token string */
2659 	name[1024],			/* Name string */
2660 	temp[1024],			/* Temporary string */
2661 	value[1024],			/* Value string */
2662 	*ptr;				/* Pointer into value */
2663 
2664 
2665   if (!_ippFileReadToken(f, temp, sizeof(temp)))
2666   {
2667     print_fatal_error(data, "Missing printer URI on line %d of \"%s\".", f->linenum, f->filename);
2668     return (0);
2669   }
2670 
2671   if (strcmp(temp, "{"))
2672   {
2673     // Got a printer URI so copy it...
2674     _ippVarsExpand(data->vars, value, temp, sizeof(value));
2675     data->monitor_uri = strdup(value);
2676 
2677     // Then see if we have an opening brace...
2678     if (!_ippFileReadToken(f, temp, sizeof(temp)) || strcmp(temp, "{"))
2679     {
2680       print_fatal_error(data, "Missing opening brace on line %d of \"%s\".", f->linenum, f->filename);
2681       return (0);
2682     }
2683   }
2684   else
2685   {
2686     // Use the default printer URI...
2687     data->monitor_uri = strdup(data->vars->uri);
2688   }
2689 
2690   // Loop until we get a closing brace...
2691   while (_ippFileReadToken(f, token, sizeof(token)))
2692   {
2693     if (_cups_strcasecmp(token, "COUNT") &&
2694 	_cups_strcasecmp(token, "DEFINE-MATCH") &&
2695 	_cups_strcasecmp(token, "DEFINE-NO-MATCH") &&
2696 	_cups_strcasecmp(token, "DEFINE-VALUE") &&
2697 	_cups_strcasecmp(token, "DISPLAY-MATCH") &&
2698 	_cups_strcasecmp(token, "IF-DEFINED") &&
2699 	_cups_strcasecmp(token, "IF-NOT-DEFINED") &&
2700 	_cups_strcasecmp(token, "IN-GROUP") &&
2701 	_cups_strcasecmp(token, "OF-TYPE") &&
2702 	_cups_strcasecmp(token, "WITH-DISTINCT-VALUES") &&
2703 	_cups_strcasecmp(token, "WITH-VALUE"))
2704       data->last_expect = NULL;
2705 
2706     if (!strcmp(token, "}"))
2707       return (1);
2708     else if (!_cups_strcasecmp(token, "EXPECT"))
2709     {
2710      /*
2711       * Expected attributes...
2712       */
2713 
2714       if (data->num_monitor_expects >= (int)(sizeof(data->monitor_expects) / sizeof(data->monitor_expects[0])))
2715       {
2716 	print_fatal_error(data, "Too many EXPECT's on line %d of \"%s\".", f->linenum, f->filename);
2717 	return (0);
2718       }
2719 
2720       if (!_ippFileReadToken(f, name, sizeof(name)))
2721       {
2722 	print_fatal_error(data, "Missing EXPECT name on line %d of \"%s\".", f->linenum, f->filename);
2723 	return (0);
2724       }
2725 
2726       data->last_expect = data->monitor_expects + data->num_monitor_expects;
2727       data->num_monitor_expects ++;
2728 
2729       memset(data->last_expect, 0, sizeof(ipptool_expect_t));
2730       data->last_expect->repeat_limit = 1000;
2731 
2732       if (name[0] == '!')
2733       {
2734 	data->last_expect->not_expect = 1;
2735 	data->last_expect->name       = strdup(name + 1);
2736       }
2737       else if (name[0] == '?')
2738       {
2739 	data->last_expect->optional = 1;
2740 	data->last_expect->name     = strdup(name + 1);
2741       }
2742       else
2743 	data->last_expect->name = strdup(name);
2744     }
2745     else if (!_cups_strcasecmp(token, "COUNT"))
2746     {
2747       int	count;			/* Count value */
2748 
2749       if (!_ippFileReadToken(f, temp, sizeof(temp)))
2750       {
2751 	print_fatal_error(data, "Missing COUNT number on line %d of \"%s\".", f->linenum, f->filename);
2752 	return (0);
2753       }
2754 
2755       if ((count = atoi(temp)) <= 0)
2756       {
2757 	print_fatal_error(data, "Bad COUNT \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
2758 	return (0);
2759       }
2760 
2761       if (data->last_expect)
2762       {
2763 	data->last_expect->count = count;
2764       }
2765       else
2766       {
2767 	print_fatal_error(data, "COUNT without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
2768 	return (0);
2769       }
2770     }
2771     else if (!_cups_strcasecmp(token, "DEFINE-MATCH"))
2772     {
2773       if (!_ippFileReadToken(f, temp, sizeof(temp)))
2774       {
2775 	print_fatal_error(data, "Missing DEFINE-MATCH variable on line %d of \"%s\".", f->linenum, f->filename);
2776 	return (0);
2777       }
2778 
2779       if (data->last_expect)
2780       {
2781 	data->last_expect->define_match = strdup(temp);
2782       }
2783       else
2784       {
2785 	print_fatal_error(data, "DEFINE-MATCH without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
2786 	return (0);
2787       }
2788     }
2789     else if (!_cups_strcasecmp(token, "DEFINE-NO-MATCH"))
2790     {
2791       if (!_ippFileReadToken(f, temp, sizeof(temp)))
2792       {
2793 	print_fatal_error(data, "Missing DEFINE-NO-MATCH variable on line %d of \"%s\".", f->linenum, f->filename);
2794 	return (0);
2795       }
2796 
2797       if (data->last_expect)
2798       {
2799 	data->last_expect->define_no_match = strdup(temp);
2800       }
2801       else
2802       {
2803 	print_fatal_error(data, "DEFINE-NO-MATCH without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
2804 	return (0);
2805       }
2806     }
2807     else if (!_cups_strcasecmp(token, "DEFINE-VALUE"))
2808     {
2809       if (!_ippFileReadToken(f, temp, sizeof(temp)))
2810       {
2811 	print_fatal_error(data, "Missing DEFINE-VALUE variable on line %d of \"%s\".", f->linenum, f->filename);
2812 	return (0);
2813       }
2814 
2815       if (data->last_expect)
2816       {
2817 	data->last_expect->define_value = strdup(temp);
2818       }
2819       else
2820       {
2821 	print_fatal_error(data, "DEFINE-VALUE without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
2822 	return (0);
2823       }
2824     }
2825     else if (!_cups_strcasecmp(token, "DISPLAY-MATCH"))
2826     {
2827       if (!_ippFileReadToken(f, temp, sizeof(temp)))
2828       {
2829 	print_fatal_error(data, "Missing DISPLAY-MATCH message on line %d of \"%s\".", f->linenum, f->filename);
2830 	return (0);
2831       }
2832 
2833       if (data->last_expect)
2834       {
2835 	data->last_expect->display_match = strdup(temp);
2836       }
2837       else
2838       {
2839 	print_fatal_error(data, "DISPLAY-MATCH without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
2840 	return (0);
2841       }
2842     }
2843     else if (!_cups_strcasecmp(token, "DELAY"))
2844     {
2845      /*
2846       * Delay before operation...
2847       */
2848 
2849       double dval;                    /* Delay value */
2850 
2851       if (!_ippFileReadToken(f, temp, sizeof(temp)))
2852       {
2853 	print_fatal_error(data, "Missing DELAY value on line %d of \"%s\".", f->linenum, f->filename);
2854 	return (0);
2855       }
2856 
2857       _ippVarsExpand(data->vars, value, temp, sizeof(value));
2858 
2859       if ((dval = _cupsStrScand(value, &ptr, localeconv())) < 0.0 || (*ptr && *ptr != ','))
2860       {
2861 	print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
2862 	return (0);
2863       }
2864 
2865       data->monitor_delay = (useconds_t)(1000000.0 * dval);
2866 
2867       if (*ptr == ',')
2868       {
2869 	if ((dval = _cupsStrScand(ptr + 1, &ptr, localeconv())) <= 0.0 || *ptr)
2870 	{
2871 	  print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
2872 	  return (0);
2873 	}
2874 
2875 	data->monitor_interval = (useconds_t)(1000000.0 * dval);
2876       }
2877       else
2878 	data->monitor_interval = data->monitor_delay;
2879     }
2880     else if (!_cups_strcasecmp(token, "OF-TYPE"))
2881     {
2882       if (!_ippFileReadToken(f, temp, sizeof(temp)))
2883       {
2884 	print_fatal_error(data, "Missing OF-TYPE value tag(s) on line %d of \"%s\".", f->linenum, f->filename);
2885 	return (0);
2886       }
2887 
2888       if (data->last_expect)
2889       {
2890 	data->last_expect->of_type = strdup(temp);
2891       }
2892       else
2893       {
2894 	print_fatal_error(data, "OF-TYPE without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
2895 	return (0);
2896       }
2897     }
2898     else if (!_cups_strcasecmp(token, "IN-GROUP"))
2899     {
2900       ipp_tag_t	in_group;		/* IN-GROUP value */
2901 
2902       if (!_ippFileReadToken(f, temp, sizeof(temp)))
2903       {
2904 	print_fatal_error(data, "Missing IN-GROUP group tag on line %d of \"%s\".", f->linenum, f->filename);
2905 	return (0);
2906       }
2907 
2908       if ((in_group = ippTagValue(temp)) == IPP_TAG_ZERO || in_group >= IPP_TAG_UNSUPPORTED_VALUE)
2909       {
2910 	print_fatal_error(data, "Bad IN-GROUP group tag \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
2911 	return (0);
2912       }
2913       else if (data->last_expect)
2914       {
2915 	data->last_expect->in_group = in_group;
2916       }
2917       else
2918       {
2919 	print_fatal_error(data, "IN-GROUP without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
2920 	return (0);
2921       }
2922     }
2923     else if (!_cups_strcasecmp(token, "IF-DEFINED"))
2924     {
2925       if (!_ippFileReadToken(f, temp, sizeof(temp)))
2926       {
2927 	print_fatal_error(data, "Missing IF-DEFINED name on line %d of \"%s\".", f->linenum, f->filename);
2928 	return (0);
2929       }
2930 
2931       if (data->last_expect)
2932       {
2933 	data->last_expect->if_defined = strdup(temp);
2934       }
2935       else
2936       {
2937 	print_fatal_error(data, "IF-DEFINED without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
2938 	return (0);
2939       }
2940     }
2941     else if (!_cups_strcasecmp(token, "IF-NOT-DEFINED"))
2942     {
2943       if (!_ippFileReadToken(f, temp, sizeof(temp)))
2944       {
2945 	print_fatal_error(data, "Missing IF-NOT-DEFINED name on line %d of \"%s\".", f->linenum, f->filename);
2946 	return (0);
2947       }
2948 
2949       if (data->last_expect)
2950       {
2951 	data->last_expect->if_not_defined = strdup(temp);
2952       }
2953       else
2954       {
2955 	print_fatal_error(data, "IF-NOT-DEFINED without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
2956 	return (0);
2957       }
2958     }
2959     else if (!_cups_strcasecmp(token, "WITH-DISTINCT-VALUES"))
2960     {
2961       if (data->last_expect)
2962       {
2963         data->last_expect->with_distinct = 1;
2964       }
2965       else
2966       {
2967 	print_fatal_error(data, "%s without a preceding EXPECT on line %d of \"%s\".", token, f->linenum, f->filename);
2968 	return (0);
2969       }
2970     }
2971     else if (!_cups_strcasecmp(token, "WITH-VALUE"))
2972     {
2973       off_t	lastpos;		/* Last file position */
2974       int	lastline;		/* Last line number */
2975 
2976       if (!_ippFileReadToken(f, temp, sizeof(temp)))
2977       {
2978 	print_fatal_error(data, "Missing %s value on line %d of \"%s\".", token, f->linenum, f->filename);
2979 	return (0);
2980       }
2981 
2982      /*
2983       * Read additional comma-delimited values - needed since legacy test files
2984       * will have unquoted WITH-VALUE values with commas...
2985       */
2986 
2987       ptr = temp + strlen(temp);
2988 
2989       for (;;)
2990       {
2991         lastpos  = cupsFileTell(f->fp);
2992         lastline = f->linenum;
2993         ptr      += strlen(ptr);
2994 
2995 	if (!_ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
2996 	  break;
2997 
2998         if (!strcmp(ptr, ","))
2999         {
3000          /*
3001           * Append a value...
3002           */
3003 
3004 	  ptr += strlen(ptr);
3005 
3006 	  if (!_ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
3007 	    break;
3008         }
3009         else
3010         {
3011          /*
3012           * Not another value, stop here...
3013           */
3014 
3015           cupsFileSeek(f->fp, lastpos);
3016           f->linenum = lastline;
3017           *ptr = '\0';
3018           break;
3019 	}
3020       }
3021 
3022       if (data->last_expect)
3023       {
3024        /*
3025 	* Expand any variables in the value and then save it.
3026 	*/
3027 
3028 	_ippVarsExpand(data->vars, value, temp, sizeof(value));
3029 
3030 	ptr = value + strlen(value) - 1;
3031 
3032 	if (value[0] == '/' && ptr > value && *ptr == '/')
3033 	{
3034 	 /*
3035 	  * WITH-VALUE is a POSIX extended regular expression.
3036 	  */
3037 
3038 	  data->last_expect->with_value = calloc(1, (size_t)(ptr - value));
3039 	  data->last_expect->with_flags |= IPPTOOL_WITH_REGEX;
3040 
3041 	  if (data->last_expect->with_value)
3042 	    memcpy(data->last_expect->with_value, value + 1, (size_t)(ptr - value - 1));
3043 	}
3044 	else
3045 	{
3046 	 /*
3047 	  * WITH-VALUE is a literal value...
3048 	  */
3049 
3050 	  for (ptr = value; *ptr; ptr ++)
3051 	  {
3052 	    if (*ptr == '\\' && ptr[1])
3053 	    {
3054 	     /*
3055 	      * Remove \ from \foo...
3056 	      */
3057 
3058 	      _cups_strcpy(ptr, ptr + 1);
3059 	    }
3060 	  }
3061 
3062 	  data->last_expect->with_value = strdup(value);
3063 	  data->last_expect->with_flags |= IPPTOOL_WITH_LITERAL;
3064 	}
3065       }
3066       else
3067       {
3068 	print_fatal_error(data, "%s without a preceding EXPECT on line %d of \"%s\".", token, f->linenum, f->filename);
3069 	return (0);
3070       }
3071     }
3072   }
3073 
3074   print_fatal_error(data, "Missing closing brace on line %d of \"%s\".", f->linenum, f->filename);
3075 
3076   return (0);
3077 }
3078 
3079 
3080 /*
3081  * 'pause_message()' - Display the message and pause until the user presses a key.
3082  */
3083 
3084 static void
pause_message(const char * message)3085 pause_message(const char *message)	/* I - Message */
3086 {
3087 #ifdef _WIN32
3088   HANDLE	tty;			/* Console handle */
3089   DWORD		mode;			/* Console mode */
3090   char		key;			/* Key press */
3091   DWORD		bytes;			/* Bytes read for key press */
3092 
3093 
3094  /*
3095   * Disable input echo and set raw input...
3096   */
3097 
3098   if ((tty = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE)
3099     return;
3100 
3101   if (!GetConsoleMode(tty, &mode))
3102     return;
3103 
3104   if (!SetConsoleMode(tty, 0))
3105     return;
3106 
3107 #else
3108   int			tty;		/* /dev/tty - never read from stdin */
3109   struct termios	original,	/* Original input mode */
3110 			noecho;		/* No echo input mode */
3111   char			key;		/* Current key press */
3112 
3113 
3114  /*
3115   * Disable input echo and set raw input...
3116   */
3117 
3118   if ((tty = open("/dev/tty", O_RDONLY)) < 0)
3119     return;
3120 
3121   if (tcgetattr(tty, &original))
3122   {
3123     close(tty);
3124     return;
3125   }
3126 
3127   noecho = original;
3128   noecho.c_lflag &= (tcflag_t)~(ICANON | ECHO | ECHOE | ISIG);
3129 
3130   if (tcsetattr(tty, TCSAFLUSH, &noecho))
3131   {
3132     close(tty);
3133     return;
3134   }
3135 #endif /* _WIN32 */
3136 
3137  /*
3138   * Display the prompt...
3139   */
3140 
3141   cupsFilePrintf(cupsFileStdout(), "\n%s\n\n---- PRESS ANY KEY ----", message);
3142 
3143 #ifdef _WIN32
3144  /*
3145   * Read a key...
3146   */
3147 
3148   ReadFile(tty, &key, 1, &bytes, NULL);
3149 
3150  /*
3151   * Cleanup...
3152   */
3153 
3154   SetConsoleMode(tty, mode);
3155 
3156 #else
3157  /*
3158   * Read a key...
3159   */
3160 
3161   read(tty, &key, 1);
3162 
3163  /*
3164   * Cleanup...
3165   */
3166 
3167   tcsetattr(tty, TCSAFLUSH, &original);
3168   close(tty);
3169 #endif /* _WIN32 */
3170 
3171  /*
3172   * Erase the "press any key" prompt...
3173   */
3174 
3175   cupsFilePuts(cupsFileStdout(), "\r                       \r");
3176 }
3177 
3178 
3179 /*
3180  * 'print_attr()' - Print an attribute on the screen.
3181  */
3182 
3183 static void
print_attr(cups_file_t * outfile,ipptool_output_t output,ipp_attribute_t * attr,ipp_tag_t * group)3184 print_attr(cups_file_t      *outfile,	/* I  - Output file */
3185            ipptool_output_t output,	/* I  - Output format */
3186            ipp_attribute_t  *attr,	/* I  - Attribute to print */
3187            ipp_tag_t        *group)	/* IO - Current group */
3188 {
3189   int			i,		/* Looping var */
3190 			count;		/* Number of values */
3191   ipp_attribute_t	*colattr;	/* Collection attribute */
3192 
3193 
3194   if (output == IPPTOOL_OUTPUT_PLIST)
3195   {
3196     if (!ippGetName(attr) || (group && *group != ippGetGroupTag(attr)))
3197     {
3198       if (ippGetGroupTag(attr) != IPP_TAG_ZERO)
3199       {
3200 	cupsFilePuts(outfile, "</dict>\n");
3201 	cupsFilePuts(outfile, "<dict>\n");
3202       }
3203 
3204       if (group)
3205         *group = ippGetGroupTag(attr);
3206     }
3207 
3208     if (!ippGetName(attr))
3209       return;
3210 
3211     print_xml_string(outfile, "key", ippGetName(attr));
3212     if ((count = ippGetCount(attr)) > 1)
3213       cupsFilePuts(outfile, "<array>\n");
3214 
3215     switch (ippGetValueTag(attr))
3216     {
3217       case IPP_TAG_INTEGER :
3218       case IPP_TAG_ENUM :
3219 	  for (i = 0; i < count; i ++)
3220 	    cupsFilePrintf(outfile, "<integer>%d</integer>\n", ippGetInteger(attr, i));
3221 	  break;
3222 
3223       case IPP_TAG_BOOLEAN :
3224 	  for (i = 0; i < count; i ++)
3225 	    cupsFilePuts(outfile, ippGetBoolean(attr, i) ? "<true />\n" : "<false />\n");
3226 	  break;
3227 
3228       case IPP_TAG_RANGE :
3229 	  for (i = 0; i < count; i ++)
3230 	  {
3231 	    int lower, upper;		/* Lower and upper ranges */
3232 
3233 	    lower = ippGetRange(attr, i, &upper);
3234 	    cupsFilePrintf(outfile, "<dict><key>lower</key><integer>%d</integer><key>upper</key><integer>%d</integer></dict>\n", lower, upper);
3235 	  }
3236 	  break;
3237 
3238       case IPP_TAG_RESOLUTION :
3239 	  for (i = 0; i < count; i ++)
3240 	  {
3241 	    int		xres, yres;	/* Resolution values */
3242 	    ipp_res_t	units;		/* Resolution units */
3243 
3244             xres = ippGetResolution(attr, i, &yres, &units);
3245 	    cupsFilePrintf(outfile, "<dict><key>xres</key><integer>%d</integer><key>yres</key><integer>%d</integer><key>units</key><string>%s</string></dict>\n", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
3246 	  }
3247 	  break;
3248 
3249       case IPP_TAG_DATE :
3250 	  for (i = 0; i < count; i ++)
3251 	    cupsFilePrintf(outfile, "<date>%s</date>\n", iso_date(ippGetDate(attr, i)));
3252 	  break;
3253 
3254       case IPP_TAG_STRING :
3255           for (i = 0; i < count; i ++)
3256           {
3257             int		datalen;	/* Length of data */
3258             void	*data = ippGetOctetString(attr, i, &datalen);
3259 					/* Data */
3260 	    char	buffer[IPP_MAX_LENGTH * 5 / 4 + 1];
3261 					/* Base64 output buffer */
3262 
3263 	    cupsFilePrintf(outfile, "<data>%s</data>\n", httpEncode64_2(buffer, sizeof(buffer), data, datalen));
3264           }
3265           break;
3266 
3267       case IPP_TAG_TEXT :
3268       case IPP_TAG_NAME :
3269       case IPP_TAG_KEYWORD :
3270       case IPP_TAG_URI :
3271       case IPP_TAG_URISCHEME :
3272       case IPP_TAG_CHARSET :
3273       case IPP_TAG_LANGUAGE :
3274       case IPP_TAG_MIMETYPE :
3275 	  for (i = 0; i < count; i ++)
3276 	    print_xml_string(outfile, "string", ippGetString(attr, i, NULL));
3277 	  break;
3278 
3279       case IPP_TAG_TEXTLANG :
3280       case IPP_TAG_NAMELANG :
3281 	  for (i = 0; i < count; i ++)
3282 	  {
3283 	    const char *s,		/* String */
3284 			*lang;		/* Language */
3285 
3286             s = ippGetString(attr, i, &lang);
3287 	    cupsFilePuts(outfile, "<dict><key>language</key><string>");
3288 	    print_xml_string(outfile, NULL, lang);
3289 	    cupsFilePuts(outfile, "</string><key>string</key><string>");
3290 	    print_xml_string(outfile, NULL, s);
3291 	    cupsFilePuts(outfile, "</string></dict>\n");
3292 	  }
3293 	  break;
3294 
3295       case IPP_TAG_BEGIN_COLLECTION :
3296 	  for (i = 0; i < count; i ++)
3297 	  {
3298 	    ipp_t *col = ippGetCollection(attr, i);
3299 					/* Collection value */
3300 
3301 	    cupsFilePuts(outfile, "<dict>\n");
3302 	    for (colattr = ippFirstAttribute(col); colattr; colattr = ippNextAttribute(col))
3303 	      print_attr(outfile, output, colattr, NULL);
3304 	    cupsFilePuts(outfile, "</dict>\n");
3305 	  }
3306 	  break;
3307 
3308       default :
3309 	  cupsFilePrintf(outfile, "<string>&lt;&lt;%s&gt;&gt;</string>\n", ippTagString(ippGetValueTag(attr)));
3310 	  break;
3311     }
3312 
3313     if (count > 1)
3314       cupsFilePuts(outfile, "</array>\n");
3315   }
3316   else
3317   {
3318     char	buffer[131072];		/* Value buffer */
3319 
3320     if (output == IPPTOOL_OUTPUT_TEST)
3321     {
3322       if (!ippGetName(attr))
3323       {
3324         cupsFilePuts(outfile, "        -- separator --\n");
3325         return;
3326       }
3327 
3328       cupsFilePrintf(outfile, "        %s (%s%s) = ", ippGetName(attr), ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)));
3329     }
3330 
3331     ippAttributeString(attr, buffer, sizeof(buffer));
3332     cupsFilePrintf(outfile, "%s\n", buffer);
3333   }
3334 }
3335 
3336 
3337 /*
3338  * 'print_csv()' - Print a line of CSV text.
3339  */
3340 
3341 static ipp_attribute_t *		/* O - Next attribute */
print_csv(ipptool_test_t * data,ipp_t * ipp,ipp_attribute_t * attr,int num_displayed,char ** displayed,size_t * widths)3342 print_csv(
3343     ipptool_test_t  *data,		/* I - Test data */
3344     ipp_t           *ipp,		/* I - Response message */
3345     ipp_attribute_t *attr,		/* I - First attribute for line */
3346     int             num_displayed,	/* I - Number of attributes to display */
3347     char            **displayed,	/* I - Attributes to display */
3348     size_t          *widths)		/* I - Column widths */
3349 {
3350   int		i;			/* Looping var */
3351   size_t	maxlength;		/* Max length of all columns */
3352   ipp_attribute_t *current = attr;	/* Current attribute */
3353   char		*values[MAX_DISPLAY],	/* Strings to display */
3354 		*valptr;		/* Pointer into value */
3355 
3356  /*
3357   * Get the maximum string length we have to show and allocate...
3358   */
3359 
3360   for (i = 1, maxlength = widths[0]; i < num_displayed; i ++)
3361     if (widths[i] > maxlength)
3362       maxlength = widths[i];
3363 
3364   maxlength += 2;
3365 
3366  /*
3367   * Loop through the attributes to display...
3368   */
3369 
3370   if (attr)
3371   {
3372     // Collect the values...
3373     memset(values, 0, sizeof(values));
3374 
3375     for (; current; current = ippNextAttribute(ipp))
3376     {
3377       if (!ippGetName(current))
3378 	break;
3379 
3380       for (i = 0; i < num_displayed; i ++)
3381       {
3382         if (!strcmp(ippGetName(current), displayed[i]))
3383         {
3384           if ((values[i] = (char *)calloc(1, maxlength)) != NULL)
3385 	    ippAttributeString(current, values[i], maxlength);
3386           break;
3387 	}
3388       }
3389     }
3390 
3391     // Output the line...
3392     for (i = 0; i < num_displayed; i ++)
3393     {
3394       if (i)
3395         cupsFilePutChar(data->outfile, ',');
3396 
3397       if (!values[i])
3398         continue;
3399 
3400       if (strchr(values[i], ',') != NULL || strchr(values[i], '\"') != NULL || strchr(values[i], '\\') != NULL)
3401       {
3402         // Quoted value...
3403         cupsFilePutChar(data->outfile, '\"');
3404         for (valptr = values[i]; *valptr; valptr ++)
3405         {
3406           if (*valptr == '\\' || *valptr == '\"')
3407             cupsFilePutChar(data->outfile, '\\');
3408           cupsFilePutChar(data->outfile, *valptr);
3409         }
3410         cupsFilePutChar(data->outfile, '\"');
3411       }
3412       else
3413       {
3414         // Unquoted value...
3415         cupsFilePuts(data->outfile, values[i]);
3416       }
3417 
3418       free(values[i]);
3419     }
3420     cupsFilePutChar(data->outfile, '\n');
3421   }
3422   else
3423   {
3424     // Show column headings...
3425     for (i = 0; i < num_displayed; i ++)
3426     {
3427       if (i)
3428         cupsFilePutChar(data->outfile, ',');
3429 
3430       cupsFilePuts(data->outfile, displayed[i]);
3431     }
3432     cupsFilePutChar(data->outfile, '\n');
3433   }
3434 
3435   return (current);
3436 }
3437 
3438 
3439 /*
3440  * 'print_fatal_error()' - Print a fatal error message.
3441  */
3442 
3443 static void
print_fatal_error(ipptool_test_t * data,const char * s,...)3444 print_fatal_error(
3445     ipptool_test_t *data,		/* I - Test data */
3446     const char       *s,		/* I - Printf-style format string */
3447     ...)				/* I - Additional arguments as needed */
3448 {
3449   char		buffer[10240];		/* Format buffer */
3450   va_list	ap;			/* Pointer to arguments */
3451 
3452 
3453  /*
3454   * Format the error message...
3455   */
3456 
3457   va_start(ap, s);
3458   vsnprintf(buffer, sizeof(buffer), s, ap);
3459   va_end(ap);
3460 
3461  /*
3462   * Then output it...
3463   */
3464 
3465   if (data->output == IPPTOOL_OUTPUT_PLIST)
3466   {
3467     print_xml_header(data);
3468     print_xml_trailer(data, 0, buffer);
3469   }
3470 
3471   _cupsLangPrintf(stderr, "ipptool: %s", buffer);
3472 }
3473 
3474 
3475 /*
3476  * 'print_ippserver_attr()' - Print a attribute suitable for use by ippserver.
3477  */
3478 
3479 static void
print_ippserver_attr(ipptool_test_t * data,ipp_attribute_t * attr,int indent)3480 print_ippserver_attr(
3481     ipptool_test_t *data,		/* I - Test data */
3482     ipp_attribute_t  *attr,		/* I - Attribute to print */
3483     int              indent)		/* I - Indentation level */
3484 {
3485   int			i,		/* Looping var */
3486 			count = ippGetCount(attr);
3487 					/* Number of values */
3488   ipp_attribute_t	*colattr;	/* Collection attribute */
3489 
3490 
3491   if (indent == 0)
3492     cupsFilePrintf(data->outfile, "ATTR %s %s", ippTagString(ippGetValueTag(attr)), ippGetName(attr));
3493   else
3494     cupsFilePrintf(data->outfile, "%*sMEMBER %s %s", indent, "", ippTagString(ippGetValueTag(attr)), ippGetName(attr));
3495 
3496   switch (ippGetValueTag(attr))
3497   {
3498     case IPP_TAG_INTEGER :
3499     case IPP_TAG_ENUM :
3500 	for (i = 0; i < count; i ++)
3501 	  cupsFilePrintf(data->outfile, "%s%d", i ? "," : " ", ippGetInteger(attr, i));
3502 	break;
3503 
3504     case IPP_TAG_BOOLEAN :
3505 	cupsFilePuts(data->outfile, ippGetBoolean(attr, 0) ? " true" : " false");
3506 
3507 	for (i = 1; i < count; i ++)
3508 	  cupsFilePuts(data->outfile, ippGetBoolean(attr, 1) ? ",true" : ",false");
3509 	break;
3510 
3511     case IPP_TAG_RANGE :
3512 	for (i = 0; i < count; i ++)
3513 	{
3514 	  int upper, lower = ippGetRange(attr, i, &upper);
3515 
3516 	  cupsFilePrintf(data->outfile, "%s%d-%d", i ? "," : " ", lower, upper);
3517 	}
3518 	break;
3519 
3520     case IPP_TAG_RESOLUTION :
3521 	for (i = 0; i < count; i ++)
3522 	{
3523 	  ipp_res_t units;
3524 	  int yres, xres = ippGetResolution(attr, i, &yres, &units);
3525 
3526 	  cupsFilePrintf(data->outfile, "%s%dx%d%s", i ? "," : " ", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
3527 	}
3528 	break;
3529 
3530     case IPP_TAG_DATE :
3531 	for (i = 0; i < count; i ++)
3532 	  cupsFilePrintf(data->outfile, "%s%s", i ? "," : " ", iso_date(ippGetDate(attr, i)));
3533 	break;
3534 
3535     case IPP_TAG_STRING :
3536 	for (i = 0; i < count; i ++)
3537 	{
3538 	  int len;
3539 	  const char *s = (const char *)ippGetOctetString(attr, i, &len);
3540 
3541 	  cupsFilePuts(data->outfile, i ? "," : " ");
3542 	  print_ippserver_string(data, s, (size_t)len);
3543 	}
3544 	break;
3545 
3546     case IPP_TAG_TEXT :
3547     case IPP_TAG_TEXTLANG :
3548     case IPP_TAG_NAME :
3549     case IPP_TAG_NAMELANG :
3550     case IPP_TAG_KEYWORD :
3551     case IPP_TAG_URI :
3552     case IPP_TAG_URISCHEME :
3553     case IPP_TAG_CHARSET :
3554     case IPP_TAG_LANGUAGE :
3555     case IPP_TAG_MIMETYPE :
3556 	for (i = 0; i < count; i ++)
3557 	{
3558 	  const char *s = ippGetString(attr, i, NULL);
3559 
3560 	  cupsFilePuts(data->outfile, i ? "," : " ");
3561 	  print_ippserver_string(data, s, strlen(s));
3562 	}
3563 	break;
3564 
3565     case IPP_TAG_BEGIN_COLLECTION :
3566 	for (i = 0; i < count; i ++)
3567 	{
3568 	  ipp_t *col = ippGetCollection(attr, i);
3569 
3570 	  cupsFilePuts(data->outfile, i ? ",{\n" : " {\n");
3571 	  for (colattr = ippFirstAttribute(col); colattr; colattr = ippNextAttribute(col))
3572 	    print_ippserver_attr(data, colattr, indent + 4);
3573 	  cupsFilePrintf(data->outfile, "%*s}", indent, "");
3574 	}
3575 	break;
3576 
3577     default :
3578         /* Out-of-band value */
3579 	break;
3580   }
3581 
3582   cupsFilePuts(data->outfile, "\n");
3583 }
3584 
3585 
3586 /*
3587  * 'print_ippserver_string()' - Print a string suitable for use by ippserver.
3588  */
3589 
3590 static void
print_ippserver_string(ipptool_test_t * data,const char * s,size_t len)3591 print_ippserver_string(
3592     ipptool_test_t *data,		/* I - Test data */
3593     const char       *s,		/* I - String to print */
3594     size_t           len)		/* I - Length of string */
3595 {
3596   cupsFilePutChar(data->outfile, '\"');
3597   while (len > 0)
3598   {
3599     if (*s == '\"')
3600       cupsFilePutChar(data->outfile, '\\');
3601     cupsFilePutChar(data->outfile, *s);
3602 
3603     s ++;
3604     len --;
3605   }
3606   cupsFilePutChar(data->outfile, '\"');
3607 }
3608 
3609 
3610 /*
3611  * 'print_line()' - Print a line of formatted or CSV text.
3612  */
3613 
3614 static ipp_attribute_t *		/* O - Next attribute */
print_line(ipptool_test_t * data,ipp_t * ipp,ipp_attribute_t * attr,int num_displayed,char ** displayed,size_t * widths)3615 print_line(
3616     ipptool_test_t *data,		/* I - Test data */
3617     ipp_t            *ipp,		/* I - Response message */
3618     ipp_attribute_t  *attr,		/* I - First attribute for line */
3619     int              num_displayed,	/* I - Number of attributes to display */
3620     char             **displayed,	/* I - Attributes to display */
3621     size_t           *widths)		/* I - Column widths */
3622 {
3623   int		i;			/* Looping var */
3624   size_t	maxlength;		/* Max length of all columns */
3625   ipp_attribute_t *current = attr;	/* Current attribute */
3626   char		*values[MAX_DISPLAY];	/* Strings to display */
3627 
3628 
3629  /*
3630   * Get the maximum string length we have to show and allocate...
3631   */
3632 
3633   for (i = 1, maxlength = widths[0]; i < num_displayed; i ++)
3634     if (widths[i] > maxlength)
3635       maxlength = widths[i];
3636 
3637   maxlength += 2;
3638 
3639  /*
3640   * Loop through the attributes to display...
3641   */
3642 
3643   if (attr)
3644   {
3645     // Collect the values...
3646     memset(values, 0, sizeof(values));
3647 
3648     for (; current; current = ippNextAttribute(ipp))
3649     {
3650       if (!ippGetName(current))
3651 	break;
3652 
3653       for (i = 0; i < num_displayed; i ++)
3654       {
3655         if (!strcmp(ippGetName(current), displayed[i]))
3656         {
3657           if ((values[i] = (char *)calloc(1, maxlength)) != NULL)
3658 	    ippAttributeString(current, values[i], maxlength);
3659           break;
3660 	}
3661       }
3662     }
3663 
3664     // Output the line...
3665     for (i = 0; i < num_displayed; i ++)
3666     {
3667       if (i)
3668         cupsFilePutChar(data->outfile, ' ');
3669 
3670       cupsFilePrintf(data->outfile, "%*s", (int)-widths[i], values[i] ? values[i] : "");
3671       free(values[i]);
3672     }
3673     cupsFilePutChar(data->outfile, '\n');
3674   }
3675   else
3676   {
3677     // Show column headings...
3678     char *buffer = (char *)malloc(maxlength);
3679 					// Buffer for separator lines
3680 
3681     if (!buffer)
3682       return (current);
3683 
3684     for (i = 0; i < num_displayed; i ++)
3685     {
3686       if (i)
3687         cupsFilePutChar(data->outfile, ' ');
3688 
3689       cupsFilePrintf(data->outfile, "%*s", (int)-widths[i], displayed[i]);
3690     }
3691     cupsFilePutChar(data->outfile, '\n');
3692 
3693     for (i = 0; i < num_displayed; i ++)
3694     {
3695       if (i)
3696 	cupsFilePutChar(data->outfile, ' ');
3697 
3698       memset(buffer, '-', widths[i]);
3699       buffer[widths[i]] = '\0';
3700       cupsFilePuts(data->outfile, buffer);
3701     }
3702     cupsFilePutChar(data->outfile, '\n');
3703     free(buffer);
3704   }
3705 
3706   return (current);
3707 }
3708 
3709 
3710 /*
3711  * 'print_xml_header()' - Print a standard XML plist header.
3712  */
3713 
3714 static void
print_xml_header(ipptool_test_t * data)3715 print_xml_header(ipptool_test_t *data)/* I - Test data */
3716 {
3717   if (!data->xml_header)
3718   {
3719     cupsFilePuts(data->outfile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
3720     cupsFilePuts(data->outfile, "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n");
3721     cupsFilePuts(data->outfile, "<plist version=\"1.0\">\n");
3722     cupsFilePuts(data->outfile, "<dict>\n");
3723     cupsFilePuts(data->outfile, "<key>ipptoolVersion</key>\n");
3724     cupsFilePuts(data->outfile, "<string>" CUPS_SVERSION "</string>\n");
3725     cupsFilePuts(data->outfile, "<key>Transfer</key>\n");
3726     cupsFilePrintf(data->outfile, "<string>%s</string>\n", data->transfer == IPPTOOL_TRANSFER_AUTO ? "auto" : data->transfer == IPPTOOL_TRANSFER_CHUNKED ? "chunked" : "length");
3727     cupsFilePuts(data->outfile, "<key>Tests</key>\n");
3728     cupsFilePuts(data->outfile, "<array>\n");
3729 
3730     data->xml_header = 1;
3731   }
3732 }
3733 
3734 
3735 /*
3736  * 'print_xml_string()' - Print an XML string with escaping.
3737  */
3738 
3739 static void
print_xml_string(cups_file_t * outfile,const char * element,const char * s)3740 print_xml_string(cups_file_t *outfile,	/* I - Test data */
3741 		 const char  *element,	/* I - Element name or NULL */
3742 		 const char  *s)	/* I - String to print */
3743 {
3744   if (element)
3745     cupsFilePrintf(outfile, "<%s>", element);
3746 
3747   while (*s)
3748   {
3749     if (*s == '&')
3750       cupsFilePuts(outfile, "&amp;");
3751     else if (*s == '<')
3752       cupsFilePuts(outfile, "&lt;");
3753     else if (*s == '>')
3754       cupsFilePuts(outfile, "&gt;");
3755     else if ((*s & 0xe0) == 0xc0)
3756     {
3757      /*
3758       * Validate UTF-8 two-byte sequence...
3759       */
3760 
3761       if ((s[1] & 0xc0) != 0x80)
3762       {
3763         cupsFilePutChar(outfile, '?');
3764         s ++;
3765       }
3766       else
3767       {
3768         cupsFilePutChar(outfile, *s++);
3769         cupsFilePutChar(outfile, *s);
3770       }
3771     }
3772     else if ((*s & 0xf0) == 0xe0)
3773     {
3774      /*
3775       * Validate UTF-8 three-byte sequence...
3776       */
3777 
3778       if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80)
3779       {
3780         cupsFilePutChar(outfile, '?');
3781         s += 2;
3782       }
3783       else
3784       {
3785         cupsFilePutChar(outfile, *s++);
3786         cupsFilePutChar(outfile, *s++);
3787         cupsFilePutChar(outfile, *s);
3788       }
3789     }
3790     else if ((*s & 0xf8) == 0xf0)
3791     {
3792      /*
3793       * Validate UTF-8 four-byte sequence...
3794       */
3795 
3796       if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
3797           (s[3] & 0xc0) != 0x80)
3798       {
3799         cupsFilePutChar(outfile, '?');
3800         s += 3;
3801       }
3802       else
3803       {
3804         cupsFilePutChar(outfile, *s++);
3805         cupsFilePutChar(outfile, *s++);
3806         cupsFilePutChar(outfile, *s++);
3807         cupsFilePutChar(outfile, *s);
3808       }
3809     }
3810     else if ((*s & 0x80) || (*s < ' ' && !isspace(*s & 255)))
3811     {
3812      /*
3813       * Invalid control character...
3814       */
3815 
3816       cupsFilePutChar(outfile, '?');
3817     }
3818     else
3819       cupsFilePutChar(outfile, *s);
3820 
3821     s ++;
3822   }
3823 
3824   if (element)
3825     cupsFilePrintf(outfile, "</%s>\n", element);
3826 }
3827 
3828 
3829 /*
3830  * 'print_xml_trailer()' - Print the XML trailer with success/fail value.
3831  */
3832 
3833 static void
print_xml_trailer(ipptool_test_t * data,int success,const char * message)3834 print_xml_trailer(
3835     ipptool_test_t *data,		/* I - Test data */
3836     int              success,		/* I - 1 on success, 0 on failure */
3837     const char       *message)		/* I - Error message or NULL */
3838 {
3839   if (data->xml_header)
3840   {
3841     cupsFilePuts(data->outfile, "</array>\n");
3842     cupsFilePuts(data->outfile, "<key>Successful</key>\n");
3843     cupsFilePuts(data->outfile, success ? "<true />\n" : "<false />\n");
3844     if (message)
3845     {
3846       cupsFilePuts(data->outfile, "<key>ErrorMessage</key>\n");
3847       print_xml_string(data->outfile, "string", message);
3848     }
3849     cupsFilePuts(data->outfile, "</dict>\n");
3850     cupsFilePuts(data->outfile, "</plist>\n");
3851 
3852     data->xml_header = 0;
3853   }
3854 }
3855 
3856 
3857 #ifndef _WIN32
3858 /*
3859  * 'sigterm_handler()' - Handle SIGINT and SIGTERM.
3860  */
3861 
3862 static void
sigterm_handler(int sig)3863 sigterm_handler(int sig)		/* I - Signal number (unused) */
3864 {
3865   (void)sig;
3866 
3867   Cancel = 1;
3868 
3869   signal(SIGINT, SIG_DFL);
3870   signal(SIGTERM, SIG_DFL);
3871 }
3872 #endif /* !_WIN32 */
3873 
3874 
3875 /*
3876  * 'timeout_cb()' - Handle HTTP timeouts.
3877  */
3878 
3879 static int				/* O - 1 to continue, 0 to cancel */
timeout_cb(http_t * http,void * user_data)3880 timeout_cb(http_t *http,		/* I - Connection to server */
3881            void   *user_data)		/* I - User data (unused) */
3882 {
3883   int		buffered = 0;		/* Bytes buffered but not yet sent */
3884 
3885 
3886   (void)user_data;
3887 
3888  /*
3889   * If the socket still have data waiting to be sent to the printer (as can
3890   * happen if the printer runs out of paper), continue to wait until the output
3891   * buffer is empty...
3892   */
3893 
3894 #ifdef SO_NWRITE			/* macOS and some versions of Linux */
3895   socklen_t len = sizeof(buffered);	/* Size of return value */
3896 
3897   if (getsockopt(httpGetFd(http), SOL_SOCKET, SO_NWRITE, &buffered, &len))
3898     buffered = 0;
3899 
3900 #elif defined(SIOCOUTQ)			/* Others except Windows */
3901   if (ioctl(httpGetFd(http), SIOCOUTQ, &buffered))
3902     buffered = 0;
3903 
3904 #else					/* Windows (not possible) */
3905   (void)http;
3906 #endif /* SO_NWRITE */
3907 
3908   return (buffered > 0);
3909 }
3910 
3911 
3912 /*
3913  * 'token_cb()' - Parse test file-specific tokens and run tests.
3914  */
3915 
3916 static int				/* O - 1 to continue, 0 to stop */
token_cb(_ipp_file_t * f,_ipp_vars_t * vars,ipptool_test_t * data,const char * token)3917 token_cb(_ipp_file_t    *f,		/* I - IPP file data */
3918          _ipp_vars_t    *vars,		/* I - IPP variables */
3919          ipptool_test_t *data,		/* I - Test data */
3920          const char     *token)		/* I - Current token */
3921 {
3922   char	name[1024],			/* Name string */
3923 	temp[1024],			/* Temporary string */
3924 	value[1024],			/* Value string */
3925 	*ptr;				/* Pointer into value */
3926 
3927 
3928   if (!token)
3929   {
3930    /*
3931     * Initialize state as needed (nothing for now...)
3932     */
3933 
3934     return (1);
3935   }
3936   else if (f->attrs)
3937   {
3938    /*
3939     * Parse until we see a close brace...
3940     */
3941 
3942     if (_cups_strcasecmp(token, "COUNT") &&
3943 	_cups_strcasecmp(token, "DEFINE-MATCH") &&
3944 	_cups_strcasecmp(token, "DEFINE-NO-MATCH") &&
3945 	_cups_strcasecmp(token, "DEFINE-VALUE") &&
3946 	_cups_strcasecmp(token, "DISPLAY-MATCH") &&
3947 	_cups_strcasecmp(token, "IF-DEFINED") &&
3948 	_cups_strcasecmp(token, "IF-NOT-DEFINED") &&
3949 	_cups_strcasecmp(token, "IN-GROUP") &&
3950 	_cups_strcasecmp(token, "OF-TYPE") &&
3951 	_cups_strcasecmp(token, "REPEAT-LIMIT") &&
3952 	_cups_strcasecmp(token, "REPEAT-MATCH") &&
3953 	_cups_strcasecmp(token, "REPEAT-NO-MATCH") &&
3954 	_cups_strcasecmp(token, "SAME-COUNT-AS") &&
3955 	_cups_strcasecmp(token, "WITH-ALL-VALUES") &&
3956 	_cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") &&
3957 	_cups_strcasecmp(token, "WITH-ALL-RESOURCES") &&
3958 	_cups_strcasecmp(token, "WITH-ALL-SCHEMES") &&
3959 	_cups_strcasecmp(token, "WITH-DISTINCT-VALUES") &&
3960 	_cups_strcasecmp(token, "WITH-HOSTNAME") &&
3961 	_cups_strcasecmp(token, "WITH-RESOURCE") &&
3962 	_cups_strcasecmp(token, "WITH-SCHEME") &&
3963 	_cups_strcasecmp(token, "WITH-VALUE") &&
3964 	_cups_strcasecmp(token, "WITH-VALUE-FROM"))
3965       data->last_expect = NULL;
3966 
3967     if (_cups_strcasecmp(token, "DEFINE-MATCH") &&
3968 	_cups_strcasecmp(token, "DEFINE-NO-MATCH") &&
3969 	_cups_strcasecmp(token, "IF-DEFINED") &&
3970 	_cups_strcasecmp(token, "IF-NOT-DEFINED") &&
3971 	_cups_strcasecmp(token, "REPEAT-LIMIT") &&
3972 	_cups_strcasecmp(token, "REPEAT-MATCH") &&
3973 	_cups_strcasecmp(token, "REPEAT-NO-MATCH"))
3974       data->last_status = NULL;
3975 
3976     if (!strcmp(token, "}"))
3977     {
3978       return (do_test(f, data));
3979     }
3980     else if (!strcmp(token, "MONITOR-PRINTER-STATE"))
3981     {
3982       if (data->monitor_uri)
3983       {
3984 	print_fatal_error(data, "Extra MONITOR-PRINTER-STATE seen on line %d of \"%s\".", f->linenum, f->filename);
3985 	return (0);
3986       }
3987 
3988       return (parse_monitor_printer_state(f, data));
3989     }
3990     else if (!strcmp(token, "COMPRESSION"))
3991     {
3992      /*
3993       * COMPRESSION none
3994       * COMPRESSION deflate
3995       * COMPRESSION gzip
3996       */
3997 
3998       if (_ippFileReadToken(f, temp, sizeof(temp)))
3999       {
4000 	_ippVarsExpand(vars, data->compression, temp, sizeof(data->compression));
4001 #ifdef HAVE_LIBZ
4002 	if (strcmp(data->compression, "none") && strcmp(data->compression, "deflate") &&
4003 	    strcmp(data->compression, "gzip"))
4004 #else
4005 	if (strcmp(data->compression, "none"))
4006 #endif /* HAVE_LIBZ */
4007 	{
4008 	  print_fatal_error(data, "Unsupported COMPRESSION value \"%s\" on line %d of \"%s\".", data->compression, f->linenum, f->filename);
4009 	  return (0);
4010 	}
4011 
4012 	if (!strcmp(data->compression, "none"))
4013 	  data->compression[0] = '\0';
4014       }
4015       else
4016       {
4017 	print_fatal_error(data, "Missing COMPRESSION value on line %d of \"%s\".", f->linenum, f->filename);
4018 	return (0);
4019       }
4020     }
4021     else if (!strcmp(token, "DEFINE"))
4022     {
4023      /*
4024       * DEFINE name value
4025       */
4026 
4027       if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
4028       {
4029 	_ippVarsExpand(vars, value, temp, sizeof(value));
4030 	_ippVarsSet(vars, name, value);
4031       }
4032       else
4033       {
4034 	print_fatal_error(data, "Missing DEFINE name and/or value on line %d of \"%s\".", f->linenum, f->filename);
4035 	return (0);
4036       }
4037     }
4038     else if (!strcmp(token, "IGNORE-ERRORS"))
4039     {
4040      /*
4041       * IGNORE-ERRORS yes
4042       * IGNORE-ERRORS no
4043       */
4044 
4045       if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
4046       {
4047 	data->ignore_errors = !_cups_strcasecmp(temp, "yes");
4048       }
4049       else
4050       {
4051 	print_fatal_error(data, "Missing IGNORE-ERRORS value on line %d of \"%s\".", f->linenum, f->filename);
4052 	return (0);
4053       }
4054     }
4055     else if (!_cups_strcasecmp(token, "NAME"))
4056     {
4057      /*
4058       * Name of test...
4059       */
4060 
4061       _ippFileReadToken(f, temp, sizeof(temp));
4062       _ippVarsExpand(vars, data->name, temp, sizeof(data->name));
4063     }
4064     else if (!_cups_strcasecmp(token, "PAUSE"))
4065     {
4066      /*
4067       * Pause with a message...
4068       */
4069 
4070       if (_ippFileReadToken(f, temp, sizeof(temp)))
4071       {
4072         strlcpy(data->pause, temp, sizeof(data->pause));
4073       }
4074       else
4075       {
4076 	print_fatal_error(data, "Missing PAUSE message on line %d of \"%s\".", f->linenum, f->filename);
4077 	return (0);
4078       }
4079     }
4080     else if (!strcmp(token, "REQUEST-ID"))
4081     {
4082      /*
4083       * REQUEST-ID #
4084       * REQUEST-ID random
4085       */
4086 
4087       if (_ippFileReadToken(f, temp, sizeof(temp)))
4088       {
4089 	if (isdigit(temp[0] & 255))
4090 	{
4091 	  data->request_id = atoi(temp) - 1;
4092 	}
4093 	else if (!_cups_strcasecmp(temp, "random"))
4094 	{
4095 	  data->request_id = (CUPS_RAND() % 1000) * 137;
4096 	}
4097 	else
4098 	{
4099 	  print_fatal_error(data, "Bad REQUEST-ID value \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
4100 	  return (0);
4101 	}
4102       }
4103       else
4104       {
4105 	print_fatal_error(data, "Missing REQUEST-ID value on line %d of \"%s\".", f->linenum, f->filename);
4106 	return (0);
4107       }
4108     }
4109     else if (!strcmp(token, "PASS-IF-DEFINED"))
4110     {
4111      /*
4112       * PASS-IF-DEFINED variable
4113       */
4114 
4115       if (_ippFileReadToken(f, name, sizeof(name)))
4116       {
4117 	if (_ippVarsGet(vars, name))
4118 	  data->pass_test = 1;
4119       }
4120       else
4121       {
4122 	print_fatal_error(data, "Missing PASS-IF-DEFINED value on line %d of \"%s\".", f->linenum, f->filename);
4123 	return (0);
4124       }
4125     }
4126     else if (!strcmp(token, "PASS-IF-NOT-DEFINED"))
4127     {
4128      /*
4129       * PASS-IF-NOT-DEFINED variable
4130       */
4131 
4132       if (_ippFileReadToken(f, name, sizeof(name)))
4133       {
4134 	if (!_ippVarsGet(vars, name))
4135 	  data->pass_test = 1;
4136       }
4137       else
4138       {
4139 	print_fatal_error(data, "Missing PASS-IF-NOT-DEFINED value on line %d of \"%s\".", f->linenum, f->filename);
4140 	return (0);
4141       }
4142     }
4143     else if (!strcmp(token, "SKIP-IF-DEFINED"))
4144     {
4145      /*
4146       * SKIP-IF-DEFINED variable
4147       */
4148 
4149       if (_ippFileReadToken(f, name, sizeof(name)))
4150       {
4151 	if (_ippVarsGet(vars, name))
4152 	  data->skip_test = 1;
4153       }
4154       else
4155       {
4156 	print_fatal_error(data, "Missing SKIP-IF-DEFINED value on line %d of \"%s\".", f->linenum, f->filename);
4157 	return (0);
4158       }
4159     }
4160     else if (!strcmp(token, "SKIP-IF-MISSING"))
4161     {
4162      /*
4163       * SKIP-IF-MISSING filename
4164       */
4165 
4166       if (_ippFileReadToken(f, temp, sizeof(temp)))
4167       {
4168         char filename[1024];		/* Filename */
4169 
4170 	_ippVarsExpand(vars, value, temp, sizeof(value));
4171 	get_filename(f->filename, filename, temp, sizeof(filename));
4172 
4173 	if (access(filename, R_OK))
4174 	  data->skip_test = 1;
4175       }
4176       else
4177       {
4178 	print_fatal_error(data, "Missing SKIP-IF-MISSING filename on line %d of \"%s\".", f->linenum, f->filename);
4179 	return (0);
4180       }
4181     }
4182     else if (!strcmp(token, "SKIP-IF-NOT-DEFINED"))
4183     {
4184      /*
4185       * SKIP-IF-NOT-DEFINED variable
4186       */
4187 
4188       if (_ippFileReadToken(f, name, sizeof(name)))
4189       {
4190 	if (!_ippVarsGet(vars, name))
4191 	  data->skip_test = 1;
4192       }
4193       else
4194       {
4195 	print_fatal_error(data, "Missing SKIP-IF-NOT-DEFINED value on line %d of \"%s\".", f->linenum, f->filename);
4196 	return (0);
4197       }
4198     }
4199     else if (!strcmp(token, "SKIP-PREVIOUS-ERROR"))
4200     {
4201      /*
4202       * SKIP-PREVIOUS-ERROR yes
4203       * SKIP-PREVIOUS-ERROR no
4204       */
4205 
4206       if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
4207       {
4208 	data->skip_previous = !_cups_strcasecmp(temp, "yes");
4209       }
4210       else
4211       {
4212 	print_fatal_error(data, "Missing SKIP-PREVIOUS-ERROR value on line %d of \"%s\".", f->linenum, f->filename);
4213 	return (0);
4214       }
4215     }
4216     else if (!strcmp(token, "TEST-ID"))
4217     {
4218      /*
4219       * TEST-ID "string"
4220       */
4221 
4222       if (_ippFileReadToken(f, temp, sizeof(temp)))
4223       {
4224 	_ippVarsExpand(vars, data->test_id, temp, sizeof(data->test_id));
4225       }
4226       else
4227       {
4228 	print_fatal_error(data, "Missing TEST-ID value on line %d of \"%s\".", f->linenum, f->filename);
4229 	return (0);
4230       }
4231     }
4232     else if (!strcmp(token, "TRANSFER"))
4233     {
4234      /*
4235       * TRANSFER auto
4236       * TRANSFER chunked
4237       * TRANSFER length
4238       */
4239 
4240       if (_ippFileReadToken(f, temp, sizeof(temp)))
4241       {
4242 	if (!strcmp(temp, "auto"))
4243 	{
4244 	  data->transfer = IPPTOOL_TRANSFER_AUTO;
4245 	}
4246 	else if (!strcmp(temp, "chunked"))
4247 	{
4248 	  data->transfer = IPPTOOL_TRANSFER_CHUNKED;
4249 	}
4250 	else if (!strcmp(temp, "length"))
4251 	{
4252 	  data->transfer = IPPTOOL_TRANSFER_LENGTH;
4253 	}
4254 	else
4255 	{
4256 	  print_fatal_error(data, "Bad TRANSFER value \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
4257 	  return (0);
4258 	}
4259       }
4260       else
4261       {
4262 	print_fatal_error(data, "Missing TRANSFER value on line %d of \"%s\".", f->linenum, f->filename);
4263 	return (0);
4264       }
4265     }
4266     else if (!_cups_strcasecmp(token, "VERSION"))
4267     {
4268       if (_ippFileReadToken(f, temp, sizeof(temp)))
4269       {
4270 	if (!strcmp(temp, "0.0"))
4271 	{
4272 	  data->version = 0;
4273 	}
4274 	else if (!strcmp(temp, "1.0"))
4275 	{
4276 	  data->version = 10;
4277 	}
4278 	else if (!strcmp(temp, "1.1"))
4279 	{
4280 	  data->version = 11;
4281 	}
4282 	else if (!strcmp(temp, "2.0"))
4283 	{
4284 	  data->version = 20;
4285 	}
4286 	else if (!strcmp(temp, "2.1"))
4287 	{
4288 	  data->version = 21;
4289 	}
4290 	else if (!strcmp(temp, "2.2"))
4291 	{
4292 	  data->version = 22;
4293 	}
4294 	else
4295 	{
4296 	  print_fatal_error(data, "Bad VERSION \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
4297 	  return (0);
4298 	}
4299       }
4300       else
4301       {
4302 	print_fatal_error(data, "Missing VERSION number on line %d of \"%s\".", f->linenum, f->filename);
4303 	return (0);
4304       }
4305     }
4306     else if (!_cups_strcasecmp(token, "RESOURCE"))
4307     {
4308      /*
4309       * Resource name...
4310       */
4311 
4312       if (!_ippFileReadToken(f, data->resource, sizeof(data->resource)))
4313       {
4314 	print_fatal_error(data, "Missing RESOURCE path on line %d of \"%s\".", f->linenum, f->filename);
4315 	return (0);
4316       }
4317     }
4318     else if (!_cups_strcasecmp(token, "OPERATION"))
4319     {
4320      /*
4321       * Operation...
4322       */
4323 
4324       ipp_op_t	op;			/* Operation code */
4325 
4326       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4327       {
4328 	print_fatal_error(data, "Missing OPERATION code on line %d of \"%s\".", f->linenum, f->filename);
4329 	return (0);
4330       }
4331 
4332       _ippVarsExpand(vars, value, temp, sizeof(value));
4333 
4334       if ((op = ippOpValue(value)) == (ipp_op_t)-1 && (op = (ipp_op_t)strtol(value, NULL, 0)) == 0)
4335       {
4336 	print_fatal_error(data, "Bad OPERATION code \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
4337 	return (0);
4338       }
4339 
4340       ippSetOperation(f->attrs, op);
4341     }
4342     else if (!_cups_strcasecmp(token, "GROUP"))
4343     {
4344      /*
4345       * Attribute group...
4346       */
4347 
4348       ipp_tag_t	group_tag;		/* Group tag */
4349 
4350       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4351       {
4352 	print_fatal_error(data, "Missing GROUP tag on line %d of \"%s\".", f->linenum, f->filename);
4353 	return (0);
4354       }
4355 
4356       if ((group_tag = ippTagValue(temp)) == IPP_TAG_ZERO || group_tag >= IPP_TAG_UNSUPPORTED_VALUE)
4357       {
4358 	print_fatal_error(data, "Bad GROUP tag \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
4359 	return (0);
4360       }
4361 
4362       if (group_tag == f->group_tag)
4363 	ippAddSeparator(f->attrs);
4364 
4365       f->group_tag = group_tag;
4366     }
4367     else if (!_cups_strcasecmp(token, "DELAY"))
4368     {
4369      /*
4370       * Delay before operation...
4371       */
4372 
4373       double dval;                    /* Delay value */
4374 
4375       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4376       {
4377 	print_fatal_error(data, "Missing DELAY value on line %d of \"%s\".", f->linenum, f->filename);
4378 	return (0);
4379       }
4380 
4381       _ippVarsExpand(vars, value, temp, sizeof(value));
4382 
4383       if ((dval = _cupsStrScand(value, &ptr, localeconv())) < 0.0 || (*ptr && *ptr != ','))
4384       {
4385 	print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
4386 	return (0);
4387       }
4388 
4389       data->delay = (useconds_t)(1000000.0 * dval);
4390 
4391       if (*ptr == ',')
4392       {
4393 	if ((dval = _cupsStrScand(ptr + 1, &ptr, localeconv())) <= 0.0 || *ptr)
4394 	{
4395 	  print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
4396 	  return (0);
4397 	}
4398 
4399 	data->repeat_interval = (useconds_t)(1000000.0 * dval);
4400       }
4401       else
4402 	data->repeat_interval = data->delay;
4403     }
4404     else if (!_cups_strcasecmp(token, "FILE"))
4405     {
4406      /*
4407       * File...
4408       */
4409 
4410       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4411       {
4412 	print_fatal_error(data, "Missing FILE filename on line %d of \"%s\".", f->linenum, f->filename);
4413 	return (0);
4414       }
4415 
4416       _ippVarsExpand(vars, value, temp, sizeof(value));
4417       get_filename(f->filename, data->file, value, sizeof(data->file));
4418 
4419       if (access(data->file, R_OK))
4420       {
4421 	print_fatal_error(data, "Filename \"%s\" (mapped to \"%s\") on line %d of \"%s\" cannot be read.", value, data->file, f->linenum, f->filename);
4422 	return (0);
4423       }
4424     }
4425     else if (!_cups_strcasecmp(token, "STATUS"))
4426     {
4427      /*
4428       * Status...
4429       */
4430 
4431       if (data->num_statuses >= (int)(sizeof(data->statuses) / sizeof(data->statuses[0])))
4432       {
4433 	print_fatal_error(data, "Too many STATUS's on line %d of \"%s\".", f->linenum, f->filename);
4434 	return (0);
4435       }
4436 
4437       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4438       {
4439 	print_fatal_error(data, "Missing STATUS code on line %d of \"%s\".", f->linenum, f->filename);
4440 	return (0);
4441       }
4442 
4443       if ((data->statuses[data->num_statuses].status = ippErrorValue(temp)) == (ipp_status_t)-1 && (data->statuses[data->num_statuses].status = (ipp_status_t)strtol(temp, NULL, 0)) == 0)
4444       {
4445 	print_fatal_error(data, "Bad STATUS code \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
4446 	return (0);
4447       }
4448 
4449       data->last_status = data->statuses + data->num_statuses;
4450       data->num_statuses ++;
4451 
4452       data->last_status->define_match    = NULL;
4453       data->last_status->define_no_match = NULL;
4454       data->last_status->if_defined      = NULL;
4455       data->last_status->if_not_defined  = NULL;
4456       data->last_status->repeat_limit    = 1000;
4457       data->last_status->repeat_match    = 0;
4458       data->last_status->repeat_no_match = 0;
4459     }
4460     else if (!_cups_strcasecmp(token, "EXPECT") || !_cups_strcasecmp(token, "EXPECT-ALL"))
4461     {
4462      /*
4463       * Expected attributes...
4464       */
4465 
4466       int expect_all = !_cups_strcasecmp(token, "EXPECT-ALL");
4467 
4468       if (data->num_expects >= (int)(sizeof(data->expects) / sizeof(data->expects[0])))
4469       {
4470 	print_fatal_error(data, "Too many EXPECT's on line %d of \"%s\".", f->linenum, f->filename);
4471 	return (0);
4472       }
4473 
4474       if (!_ippFileReadToken(f, name, sizeof(name)))
4475       {
4476 	print_fatal_error(data, "Missing EXPECT name on line %d of \"%s\".", f->linenum, f->filename);
4477 	return (0);
4478       }
4479 
4480       data->last_expect = data->expects + data->num_expects;
4481       data->num_expects ++;
4482 
4483       memset(data->last_expect, 0, sizeof(ipptool_expect_t));
4484       data->last_expect->repeat_limit = 1000;
4485       data->last_expect->expect_all   = expect_all;
4486 
4487       if (name[0] == '!')
4488       {
4489 	data->last_expect->not_expect = 1;
4490 	data->last_expect->name       = strdup(name + 1);
4491       }
4492       else if (name[0] == '?')
4493       {
4494 	data->last_expect->optional = 1;
4495 	data->last_expect->name     = strdup(name + 1);
4496       }
4497       else
4498 	data->last_expect->name = strdup(name);
4499     }
4500     else if (!_cups_strcasecmp(token, "COUNT"))
4501     {
4502       int	count;			/* Count value */
4503 
4504       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4505       {
4506 	print_fatal_error(data, "Missing COUNT number on line %d of \"%s\".", f->linenum, f->filename);
4507 	return (0);
4508       }
4509 
4510       if ((count = atoi(temp)) <= 0)
4511       {
4512 	print_fatal_error(data, "Bad COUNT \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
4513 	return (0);
4514       }
4515 
4516       if (data->last_expect)
4517       {
4518 	data->last_expect->count = count;
4519       }
4520       else
4521       {
4522 	print_fatal_error(data, "COUNT without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
4523 	return (0);
4524       }
4525     }
4526     else if (!_cups_strcasecmp(token, "DEFINE-MATCH"))
4527     {
4528       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4529       {
4530 	print_fatal_error(data, "Missing DEFINE-MATCH variable on line %d of \"%s\".", f->linenum, f->filename);
4531 	return (0);
4532       }
4533 
4534       if (data->last_expect)
4535       {
4536 	data->last_expect->define_match = strdup(temp);
4537       }
4538       else if (data->last_status)
4539       {
4540 	data->last_status->define_match = strdup(temp);
4541       }
4542       else
4543       {
4544 	print_fatal_error(data, "DEFINE-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
4545 	return (0);
4546       }
4547     }
4548     else if (!_cups_strcasecmp(token, "DEFINE-NO-MATCH"))
4549     {
4550       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4551       {
4552 	print_fatal_error(data, "Missing DEFINE-NO-MATCH variable on line %d of \"%s\".", f->linenum, f->filename);
4553 	return (0);
4554       }
4555 
4556       if (data->last_expect)
4557       {
4558 	data->last_expect->define_no_match = strdup(temp);
4559       }
4560       else if (data->last_status)
4561       {
4562 	data->last_status->define_no_match = strdup(temp);
4563       }
4564       else
4565       {
4566 	print_fatal_error(data, "DEFINE-NO-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
4567 	return (0);
4568       }
4569     }
4570     else if (!_cups_strcasecmp(token, "DEFINE-VALUE"))
4571     {
4572       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4573       {
4574 	print_fatal_error(data, "Missing DEFINE-VALUE variable on line %d of \"%s\".", f->linenum, f->filename);
4575 	return (0);
4576       }
4577 
4578       if (data->last_expect)
4579       {
4580 	data->last_expect->define_value = strdup(temp);
4581       }
4582       else
4583       {
4584 	print_fatal_error(data, "DEFINE-VALUE without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
4585 	return (0);
4586       }
4587     }
4588     else if (!_cups_strcasecmp(token, "DISPLAY-MATCH"))
4589     {
4590       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4591       {
4592 	print_fatal_error(data, "Missing DISPLAY-MATCH mesaage on line %d of \"%s\".", f->linenum, f->filename);
4593 	return (0);
4594       }
4595 
4596       if (data->last_expect)
4597       {
4598 	data->last_expect->display_match = strdup(temp);
4599       }
4600       else
4601       {
4602 	print_fatal_error(data, "DISPLAY-MATCH without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
4603 	return (0);
4604       }
4605     }
4606     else if (!_cups_strcasecmp(token, "OF-TYPE"))
4607     {
4608       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4609       {
4610 	print_fatal_error(data, "Missing OF-TYPE value tag(s) on line %d of \"%s\".", f->linenum, f->filename);
4611 	return (0);
4612       }
4613 
4614       if (data->last_expect)
4615       {
4616 	data->last_expect->of_type = strdup(temp);
4617       }
4618       else
4619       {
4620 	print_fatal_error(data, "OF-TYPE without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
4621 	return (0);
4622       }
4623     }
4624     else if (!_cups_strcasecmp(token, "IN-GROUP"))
4625     {
4626       ipp_tag_t	in_group;		/* IN-GROUP value */
4627 
4628       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4629       {
4630 	print_fatal_error(data, "Missing IN-GROUP group tag on line %d of \"%s\".", f->linenum, f->filename);
4631 	return (0);
4632       }
4633 
4634       if ((in_group = ippTagValue(temp)) == IPP_TAG_ZERO || in_group >= IPP_TAG_UNSUPPORTED_VALUE)
4635       {
4636 	print_fatal_error(data, "Bad IN-GROUP group tag \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
4637 	return (0);
4638       }
4639       else if (data->last_expect)
4640       {
4641 	data->last_expect->in_group = in_group;
4642       }
4643       else
4644       {
4645 	print_fatal_error(data, "IN-GROUP without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
4646 	return (0);
4647       }
4648     }
4649     else if (!_cups_strcasecmp(token, "REPEAT-LIMIT"))
4650     {
4651       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4652       {
4653 	print_fatal_error(data, "Missing REPEAT-LIMIT value on line %d of \"%s\".", f->linenum, f->filename);
4654 	return (0);
4655       }
4656       else if (atoi(temp) <= 0)
4657       {
4658 	print_fatal_error(data, "Bad REPEAT-LIMIT value on line %d of \"%s\".", f->linenum, f->filename);
4659 	return (0);
4660       }
4661 
4662       if (data->last_status)
4663       {
4664 	data->last_status->repeat_limit = atoi(temp);
4665       }
4666       else if (data->last_expect)
4667       {
4668 	data->last_expect->repeat_limit = atoi(temp);
4669       }
4670       else
4671       {
4672 	print_fatal_error(data, "REPEAT-LIMIT without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
4673 	return (0);
4674       }
4675     }
4676     else if (!_cups_strcasecmp(token, "REPEAT-MATCH"))
4677     {
4678       if (data->last_status)
4679       {
4680 	data->last_status->repeat_match = 1;
4681       }
4682       else if (data->last_expect)
4683       {
4684 	data->last_expect->repeat_match = 1;
4685       }
4686       else
4687       {
4688 	print_fatal_error(data, "REPEAT-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
4689 	return (0);
4690       }
4691     }
4692     else if (!_cups_strcasecmp(token, "REPEAT-NO-MATCH"))
4693     {
4694       if (data->last_status)
4695       {
4696 	data->last_status->repeat_no_match = 1;
4697       }
4698       else if (data->last_expect)
4699       {
4700 	data->last_expect->repeat_no_match = 1;
4701       }
4702       else
4703       {
4704 	print_fatal_error(data, "REPEAT-NO-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
4705 	return (0);
4706       }
4707     }
4708     else if (!_cups_strcasecmp(token, "SAME-COUNT-AS"))
4709     {
4710       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4711       {
4712 	print_fatal_error(data, "Missing SAME-COUNT-AS name on line %d of \"%s\".", f->linenum, f->filename);
4713 	return (0);
4714       }
4715 
4716       if (data->last_expect)
4717       {
4718 	data->last_expect->same_count_as = strdup(temp);
4719       }
4720       else
4721       {
4722 	print_fatal_error(data, "SAME-COUNT-AS without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
4723 	return (0);
4724       }
4725     }
4726     else if (!_cups_strcasecmp(token, "IF-DEFINED"))
4727     {
4728       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4729       {
4730 	print_fatal_error(data, "Missing IF-DEFINED name on line %d of \"%s\".", f->linenum, f->filename);
4731 	return (0);
4732       }
4733 
4734       if (data->last_expect)
4735       {
4736 	data->last_expect->if_defined = strdup(temp);
4737       }
4738       else if (data->last_status)
4739       {
4740 	data->last_status->if_defined = strdup(temp);
4741       }
4742       else
4743       {
4744 	print_fatal_error(data, "IF-DEFINED without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
4745 	return (0);
4746       }
4747     }
4748     else if (!_cups_strcasecmp(token, "IF-NOT-DEFINED"))
4749     {
4750       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4751       {
4752 	print_fatal_error(data, "Missing IF-NOT-DEFINED name on line %d of \"%s\".", f->linenum, f->filename);
4753 	return (0);
4754       }
4755 
4756       if (data->last_expect)
4757       {
4758 	data->last_expect->if_not_defined = strdup(temp);
4759       }
4760       else if (data->last_status)
4761       {
4762 	data->last_status->if_not_defined = strdup(temp);
4763       }
4764       else
4765       {
4766 	print_fatal_error(data, "IF-NOT-DEFINED without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
4767 	return (0);
4768       }
4769     }
4770     else if (!_cups_strcasecmp(token, "WITH-DISTINCT-VALUES"))
4771     {
4772       if (data->last_expect)
4773       {
4774         data->last_expect->with_distinct = 1;
4775       }
4776       else
4777       {
4778 	print_fatal_error(data, "%s without a preceding EXPECT on line %d of \"%s\".", token, f->linenum, f->filename);
4779 	return (0);
4780       }
4781     }
4782     else if (!_cups_strcasecmp(token, "WITH-ALL-VALUES") ||
4783 	     !_cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") ||
4784 	     !_cups_strcasecmp(token, "WITH-ALL-RESOURCES") ||
4785 	     !_cups_strcasecmp(token, "WITH-ALL-SCHEMES") ||
4786 	     !_cups_strcasecmp(token, "WITH-HOSTNAME") ||
4787 	     !_cups_strcasecmp(token, "WITH-RESOURCE") ||
4788 	     !_cups_strcasecmp(token, "WITH-SCHEME") ||
4789 	     !_cups_strcasecmp(token, "WITH-VALUE"))
4790     {
4791       off_t	lastpos;		/* Last file position */
4792       int	lastline;		/* Last line number */
4793 
4794       if (data->last_expect)
4795       {
4796 	if (!_cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") || !_cups_strcasecmp(token, "WITH-HOSTNAME"))
4797 	  data->last_expect->with_flags = IPPTOOL_WITH_HOSTNAME;
4798 	else if (!_cups_strcasecmp(token, "WITH-ALL-RESOURCES") || !_cups_strcasecmp(token, "WITH-RESOURCE"))
4799 	  data->last_expect->with_flags = IPPTOOL_WITH_RESOURCE;
4800 	else if (!_cups_strcasecmp(token, "WITH-ALL-SCHEMES") || !_cups_strcasecmp(token, "WITH-SCHEME"))
4801 	  data->last_expect->with_flags = IPPTOOL_WITH_SCHEME;
4802 
4803 	if (!_cups_strncasecmp(token, "WITH-ALL-", 9))
4804 	  data->last_expect->with_flags |= IPPTOOL_WITH_ALL;
4805       }
4806 
4807       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4808       {
4809 	print_fatal_error(data, "Missing %s value on line %d of \"%s\".", token, f->linenum, f->filename);
4810 	return (0);
4811       }
4812 
4813      /*
4814       * Read additional comma-delimited values - needed since legacy test files
4815       * will have unquoted WITH-VALUE values with commas...
4816       */
4817 
4818       ptr = temp + strlen(temp);
4819 
4820       for (;;)
4821       {
4822         lastpos  = cupsFileTell(f->fp);
4823         lastline = f->linenum;
4824         ptr      += strlen(ptr);
4825 
4826 	if (!_ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
4827 	  break;
4828 
4829         if (!strcmp(ptr, ","))
4830         {
4831          /*
4832           * Append a value...
4833           */
4834 
4835 	  ptr += strlen(ptr);
4836 
4837 	  if (!_ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
4838 	    break;
4839         }
4840         else
4841         {
4842          /*
4843           * Not another value, stop here...
4844           */
4845 
4846           cupsFileSeek(f->fp, lastpos);
4847           f->linenum = lastline;
4848           *ptr = '\0';
4849           break;
4850 	}
4851       }
4852 
4853       if (data->last_expect)
4854       {
4855        /*
4856 	* Expand any variables in the value and then save it.
4857 	*/
4858 
4859 	_ippVarsExpand(vars, value, temp, sizeof(value));
4860 
4861 	ptr = value + strlen(value) - 1;
4862 
4863 	if (value[0] == '/' && ptr > value && *ptr == '/')
4864 	{
4865 	 /*
4866 	  * WITH-VALUE is a POSIX extended regular expression.
4867 	  */
4868 
4869 	  data->last_expect->with_value = calloc(1, (size_t)(ptr - value));
4870 	  data->last_expect->with_flags |= IPPTOOL_WITH_REGEX;
4871 
4872 	  if (data->last_expect->with_value)
4873 	    memcpy(data->last_expect->with_value, value + 1, (size_t)(ptr - value - 1));
4874 	}
4875 	else
4876 	{
4877 	 /*
4878 	  * WITH-VALUE is a literal value...
4879 	  */
4880 
4881 	  for (ptr = value; *ptr; ptr ++)
4882 	  {
4883 	    if (*ptr == '\\' && ptr[1])
4884 	    {
4885 	     /*
4886 	      * Remove \ from \foo...
4887 	      */
4888 
4889 	      _cups_strcpy(ptr, ptr + 1);
4890 	    }
4891 	  }
4892 
4893 	  data->last_expect->with_value = strdup(value);
4894 	  data->last_expect->with_flags |= IPPTOOL_WITH_LITERAL;
4895 	}
4896       }
4897       else
4898       {
4899 	print_fatal_error(data, "%s without a preceding EXPECT on line %d of \"%s\".", token, f->linenum, f->filename);
4900 	return (0);
4901       }
4902     }
4903     else if (!_cups_strcasecmp(token, "WITH-VALUE-FROM"))
4904     {
4905       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4906       {
4907 	print_fatal_error(data, "Missing %s value on line %d of \"%s\".", token, f->linenum, f->filename);
4908 	return (0);
4909       }
4910 
4911       if (data->last_expect)
4912       {
4913        /*
4914 	* Expand any variables in the value and then save it.
4915 	*/
4916 
4917 	_ippVarsExpand(vars, value, temp, sizeof(value));
4918 
4919 	data->last_expect->with_value_from = strdup(value);
4920 	data->last_expect->with_flags      = IPPTOOL_WITH_LITERAL;
4921       }
4922       else
4923       {
4924 	print_fatal_error(data, "%s without a preceding EXPECT on line %d of \"%s\".", token, f->linenum, f->filename);
4925 	return (0);
4926       }
4927     }
4928     else if (!_cups_strcasecmp(token, "DISPLAY"))
4929     {
4930      /*
4931       * Display attributes...
4932       */
4933 
4934       if (data->num_displayed >= (int)(sizeof(data->displayed) / sizeof(data->displayed[0])))
4935       {
4936 	print_fatal_error(data, "Too many DISPLAY's on line %d of \"%s\".", f->linenum, f->filename);
4937 	return (0);
4938       }
4939 
4940       if (!_ippFileReadToken(f, temp, sizeof(temp)))
4941       {
4942 	print_fatal_error(data, "Missing DISPLAY name on line %d of \"%s\".", f->linenum, f->filename);
4943 	return (0);
4944       }
4945 
4946       data->displayed[data->num_displayed] = strdup(temp);
4947       data->num_displayed ++;
4948     }
4949     else
4950     {
4951       print_fatal_error(data, "Unexpected token %s seen on line %d of \"%s\".", token, f->linenum, f->filename);
4952       return (0);
4953     }
4954   }
4955   else
4956   {
4957    /*
4958     * Scan for the start of a test (open brace)...
4959     */
4960 
4961     if (!strcmp(token, "{"))
4962     {
4963      /*
4964       * Start new test...
4965       */
4966 
4967       if (data->show_header)
4968       {
4969 	if (data->output == IPPTOOL_OUTPUT_PLIST)
4970 	  print_xml_header(data);
4971 
4972 	if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
4973 	  cupsFilePrintf(cupsFileStdout(), "\"%s\":\n", f->filename);
4974 
4975 	data->show_header = 0;
4976       }
4977 
4978       data->compression[0] = '\0';
4979       data->delay          = 0;
4980       data->num_expects    = 0;
4981       data->last_expect    = NULL;
4982       data->file[0]        = '\0';
4983       data->ignore_errors  = data->def_ignore_errors;
4984       strlcpy(data->name, f->filename, sizeof(data->name));
4985       if ((ptr = strrchr(data->name, '.')) != NULL)
4986         *ptr = '\0';
4987       data->repeat_interval = 5000000;
4988       strlcpy(data->resource, data->vars->resource, sizeof(data->resource));
4989       data->skip_previous = 0;
4990       data->pass_test     = 0;
4991       data->skip_test     = 0;
4992       data->num_statuses  = 0;
4993       data->last_status   = NULL;
4994       data->test_id[0]    = '\0';
4995       data->transfer      = data->def_transfer;
4996       data->version       = data->def_version;
4997 
4998       free(data->monitor_uri);
4999       data->monitor_uri         = NULL;
5000       data->monitor_delay       = 0;
5001       data->monitor_interval    = 5000000;
5002       data->num_monitor_expects = 0;
5003 
5004       _ippVarsSet(vars, "date-current", iso_date(ippTimeToDate(time(NULL))));
5005 
5006       f->attrs     = ippNew();
5007       f->group_tag = IPP_TAG_ZERO;
5008     }
5009     else if (!strcmp(token, "DEFINE"))
5010     {
5011      /*
5012       * DEFINE name value
5013       */
5014 
5015       if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
5016       {
5017         _ippVarsSet(vars, "date-current", iso_date(ippTimeToDate(time(NULL))));
5018         _ippVarsExpand(vars, value, temp, sizeof(value));
5019 	_ippVarsSet(vars, name, value);
5020       }
5021       else
5022       {
5023         print_fatal_error(data, "Missing DEFINE name and/or value on line %d of \"%s\".", f->linenum, f->filename);
5024 	return (0);
5025       }
5026     }
5027     else if (!strcmp(token, "DEFINE-DEFAULT"))
5028     {
5029      /*
5030       * DEFINE-DEFAULT name value
5031       */
5032 
5033       if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
5034       {
5035         if (!_ippVarsGet(vars, name))
5036         {
5037           _ippVarsSet(vars, "date-current", iso_date(ippTimeToDate(time(NULL))));
5038 	  _ippVarsExpand(vars, value, temp, sizeof(value));
5039 	  _ippVarsSet(vars, name, value);
5040 	}
5041       }
5042       else
5043       {
5044         print_fatal_error(data, "Missing DEFINE-DEFAULT name and/or value on line %d of \"%s\".", f->linenum, f->filename);
5045 	return (0);
5046       }
5047     }
5048     else if (!strcmp(token, "FILE-ID"))
5049     {
5050      /*
5051       * FILE-ID "string"
5052       */
5053 
5054       if (_ippFileReadToken(f, temp, sizeof(temp)))
5055       {
5056         _ippVarsSet(vars, "date-current", iso_date(ippTimeToDate(time(NULL))));
5057         _ippVarsExpand(vars, data->file_id, temp, sizeof(data->file_id));
5058       }
5059       else
5060       {
5061         print_fatal_error(data, "Missing FILE-ID value on line %d of \"%s\".", f->linenum, f->filename);
5062         return (0);
5063       }
5064     }
5065     else if (!strcmp(token, "IGNORE-ERRORS"))
5066     {
5067      /*
5068       * IGNORE-ERRORS yes
5069       * IGNORE-ERRORS no
5070       */
5071 
5072       if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
5073       {
5074         data->def_ignore_errors = !_cups_strcasecmp(temp, "yes");
5075       }
5076       else
5077       {
5078         print_fatal_error(data, "Missing IGNORE-ERRORS value on line %d of \"%s\".", f->linenum, f->filename);
5079         return (0);
5080       }
5081     }
5082     else if (!strcmp(token, "INCLUDE"))
5083     {
5084      /*
5085       * INCLUDE "filename"
5086       * INCLUDE <filename>
5087       */
5088 
5089       if (_ippFileReadToken(f, temp, sizeof(temp)))
5090       {
5091        /*
5092         * Map the filename to and then run the tests...
5093 	*/
5094 
5095         ipptool_test_t	inc_data;	/* Data for included file */
5096         char		filename[1024];	/* Mapped filename */
5097 
5098         memcpy(&inc_data, data, sizeof(inc_data));
5099         inc_data.http        = NULL;
5100 	inc_data.pass        = 1;
5101 	inc_data.prev_pass   = 1;
5102 	inc_data.show_header = 1;
5103 
5104         if (!do_tests(get_filename(f->filename, filename, temp, sizeof(filename)), &inc_data) && data->stop_after_include_error)
5105         {
5106           data->pass = data->prev_pass = 0;
5107           return (0);
5108 	}
5109       }
5110       else
5111       {
5112         print_fatal_error(data, "Missing INCLUDE filename on line %d of \"%s\".", f->linenum, f->filename);
5113         return (0);
5114       }
5115 
5116       data->show_header = 1;
5117     }
5118     else if (!strcmp(token, "INCLUDE-IF-DEFINED"))
5119     {
5120      /*
5121       * INCLUDE-IF-DEFINED name "filename"
5122       * INCLUDE-IF-DEFINED name <filename>
5123       */
5124 
5125       if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
5126       {
5127        /*
5128         * Map the filename to and then run the tests...
5129 	*/
5130 
5131         ipptool_test_t inc_data;	/* Data for included file */
5132         char		filename[1024];	/* Mapped filename */
5133 
5134         memcpy(&inc_data, data, sizeof(inc_data));
5135         inc_data.http        = NULL;
5136 	inc_data.pass        = 1;
5137 	inc_data.prev_pass   = 1;
5138 	inc_data.show_header = 1;
5139 
5140         if (!do_tests(get_filename(f->filename, filename, temp, sizeof(filename)), &inc_data) && data->stop_after_include_error)
5141         {
5142           data->pass = data->prev_pass = 0;
5143           return (0);
5144 	}
5145       }
5146       else
5147       {
5148         print_fatal_error(data, "Missing INCLUDE-IF-DEFINED name or filename on line %d of \"%s\".", f->linenum, f->filename);
5149         return (0);
5150       }
5151 
5152       data->show_header = 1;
5153     }
5154     else if (!strcmp(token, "INCLUDE-IF-NOT-DEFINED"))
5155     {
5156      /*
5157       * INCLUDE-IF-NOT-DEFINED name "filename"
5158       * INCLUDE-IF-NOT-DEFINED name <filename>
5159       */
5160 
5161       if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
5162       {
5163        /*
5164         * Map the filename to and then run the tests...
5165 	*/
5166 
5167         ipptool_test_t inc_data;	/* Data for included file */
5168         char		filename[1024];	/* Mapped filename */
5169 
5170         memcpy(&inc_data, data, sizeof(inc_data));
5171         inc_data.http        = NULL;
5172 	inc_data.pass        = 1;
5173 	inc_data.prev_pass   = 1;
5174 	inc_data.show_header = 1;
5175 
5176         if (!do_tests(get_filename(f->filename, filename, temp, sizeof(filename)), &inc_data) && data->stop_after_include_error)
5177         {
5178           data->pass = data->prev_pass = 0;
5179           return (0);
5180 	}
5181       }
5182       else
5183       {
5184         print_fatal_error(data, "Missing INCLUDE-IF-NOT-DEFINED name or filename on line %d of \"%s\".", f->linenum, f->filename);
5185         return (0);
5186       }
5187 
5188       data->show_header = 1;
5189     }
5190     else if (!strcmp(token, "SKIP-IF-DEFINED"))
5191     {
5192      /*
5193       * SKIP-IF-DEFINED variable
5194       */
5195 
5196       if (_ippFileReadToken(f, name, sizeof(name)))
5197       {
5198         if (_ippVarsGet(vars, name))
5199           data->skip_test = 1;
5200       }
5201       else
5202       {
5203         print_fatal_error(data, "Missing SKIP-IF-DEFINED variable on line %d of \"%s\".", f->linenum, f->filename);
5204         return (0);
5205       }
5206     }
5207     else if (!strcmp(token, "SKIP-IF-NOT-DEFINED"))
5208     {
5209      /*
5210       * SKIP-IF-NOT-DEFINED variable
5211       */
5212 
5213       if (_ippFileReadToken(f, name, sizeof(name)))
5214       {
5215         if (!_ippVarsGet(vars, name))
5216           data->skip_test = 1;
5217       }
5218       else
5219       {
5220         print_fatal_error(data, "Missing SKIP-IF-NOT-DEFINED variable on line %d of \"%s\".", f->linenum, f->filename);
5221         return (0);
5222       }
5223     }
5224     else if (!strcmp(token, "STOP-AFTER-INCLUDE-ERROR"))
5225     {
5226      /*
5227       * STOP-AFTER-INCLUDE-ERROR yes
5228       * STOP-AFTER-INCLUDE-ERROR no
5229       */
5230 
5231       if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
5232       {
5233         data->stop_after_include_error = !_cups_strcasecmp(temp, "yes");
5234       }
5235       else
5236       {
5237         print_fatal_error(data, "Missing STOP-AFTER-INCLUDE-ERROR value on line %d of \"%s\".", f->linenum, f->filename);
5238         return (0);
5239       }
5240     }
5241     else if (!strcmp(token, "TRANSFER"))
5242     {
5243      /*
5244       * TRANSFER auto
5245       * TRANSFER chunked
5246       * TRANSFER length
5247       */
5248 
5249       if (_ippFileReadToken(f, temp, sizeof(temp)))
5250       {
5251         if (!strcmp(temp, "auto"))
5252 	  data->def_transfer = IPPTOOL_TRANSFER_AUTO;
5253 	else if (!strcmp(temp, "chunked"))
5254 	  data->def_transfer = IPPTOOL_TRANSFER_CHUNKED;
5255 	else if (!strcmp(temp, "length"))
5256 	  data->def_transfer = IPPTOOL_TRANSFER_LENGTH;
5257 	else
5258 	{
5259 	  print_fatal_error(data, "Bad TRANSFER value \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
5260 	  return (0);
5261 	}
5262       }
5263       else
5264       {
5265         print_fatal_error(data, "Missing TRANSFER value on line %d of \"%s\".", f->linenum, f->filename);
5266 	return (0);
5267       }
5268     }
5269     else if (!strcmp(token, "VERSION"))
5270     {
5271       if (_ippFileReadToken(f, temp, sizeof(temp)))
5272       {
5273         if (!strcmp(temp, "1.0"))
5274 	  data->def_version = 10;
5275 	else if (!strcmp(temp, "1.1"))
5276 	  data->def_version = 11;
5277 	else if (!strcmp(temp, "2.0"))
5278 	  data->def_version = 20;
5279 	else if (!strcmp(temp, "2.1"))
5280 	  data->def_version = 21;
5281 	else if (!strcmp(temp, "2.2"))
5282 	  data->def_version = 22;
5283 	else
5284 	{
5285 	  print_fatal_error(data, "Bad VERSION \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
5286 	  return (0);
5287 	}
5288       }
5289       else
5290       {
5291         print_fatal_error(data, "Missing VERSION number on line %d of \"%s\".", f->linenum, f->filename);
5292         return (0);
5293       }
5294     }
5295     else
5296     {
5297       print_fatal_error(data, "Unexpected token %s seen on line %d of \"%s\".", token, f->linenum, f->filename);
5298       return (0);
5299     }
5300   }
5301 
5302   return (1);
5303 }
5304 
5305 
5306 /*
5307  * 'usage()' - Show program usage.
5308  */
5309 
5310 static void
usage(void)5311 usage(void)
5312 {
5313   _cupsLangPuts(stderr, _("Usage: ipptool [options] URI filename [ ... filenameN ]"));
5314   _cupsLangPuts(stderr, _("Options:"));
5315   _cupsLangPuts(stderr, _("--ippserver filename    Produce ippserver attribute file"));
5316   _cupsLangPuts(stderr, _("--stop-after-include-error\n"
5317                           "                        Stop tests after a failed INCLUDE"));
5318   _cupsLangPuts(stderr, _("--version               Show version"));
5319   _cupsLangPuts(stderr, _("-4                      Connect using IPv4"));
5320   _cupsLangPuts(stderr, _("-6                      Connect using IPv6"));
5321   _cupsLangPuts(stderr, _("-C                      Send requests using chunking (default)"));
5322   _cupsLangPuts(stderr, _("-E                      Test with encryption using HTTP Upgrade to TLS"));
5323   _cupsLangPuts(stderr, _("-I                      Ignore errors"));
5324   _cupsLangPuts(stderr, _("-L                      Send requests using content-length"));
5325   _cupsLangPuts(stderr, _("-P filename.plist       Produce XML plist to a file and test report to standard output"));
5326   _cupsLangPuts(stderr, _("-R                      Repeat tests on server-error-busy"));
5327   _cupsLangPuts(stderr, _("-S                      Test with encryption using HTTPS"));
5328   _cupsLangPuts(stderr, _("-T seconds              Set the receive/send timeout in seconds"));
5329   _cupsLangPuts(stderr, _("-V version              Set default IPP version"));
5330   _cupsLangPuts(stderr, _("-X                      Produce XML plist instead of plain text"));
5331   _cupsLangPuts(stderr, _("-c                      Produce CSV output"));
5332   _cupsLangPuts(stderr, _("-d name=value           Set named variable to value"));
5333   _cupsLangPuts(stderr, _("-f filename             Set default request filename"));
5334   _cupsLangPuts(stderr, _("-h                      Validate HTTP response headers"));
5335   _cupsLangPuts(stderr, _("-i seconds              Repeat the last file with the given time interval"));
5336   _cupsLangPuts(stderr, _("-l                      Produce plain text output"));
5337   _cupsLangPuts(stderr, _("-n count                Repeat the last file the given number of times"));
5338   _cupsLangPuts(stderr, _("-q                      Run silently"));
5339   _cupsLangPuts(stderr, _("-t                      Produce a test report"));
5340   _cupsLangPuts(stderr, _("-v                      Be verbose"));
5341 
5342   exit(1);
5343 }
5344 
5345 
5346 /*
5347  * 'with_distinct_values()' - Verify that an attribute contains unique values.
5348  */
5349 
5350 static int				// O - 1 if distinct, 0 if duplicate
with_distinct_values(cups_array_t * errors,ipp_attribute_t * attr)5351 with_distinct_values(
5352     cups_array_t    *errors,		// I - Array of errors
5353     ipp_attribute_t *attr)		// I - Attribute to test
5354 {
5355   int		i,			// Looping var
5356 		count;			// Number of values
5357   ipp_tag_t	value_tag;		// Value syntax
5358   const char	*value;			// Current value
5359   char		buffer[8192];		// Temporary buffer
5360   cups_array_t	*values;		// Array of values as strings
5361 
5362 
5363   // If there is only 1 value, it must be distinct
5364   if ((count = ippGetCount(attr)) == 1)
5365     return (1);
5366 
5367   // Only check integers, enums, rangeOfInteger, resolution, and nul-terminated
5368   // strings...
5369   switch (value_tag = ippGetValueTag(attr))
5370   {
5371     case IPP_TAG_INTEGER :
5372     case IPP_TAG_ENUM :
5373     case IPP_TAG_RANGE :
5374     case IPP_TAG_RESOLUTION :
5375     case IPP_TAG_KEYWORD :
5376     case IPP_TAG_URISCHEME :
5377     case IPP_TAG_CHARSET :
5378     case IPP_TAG_LANGUAGE :
5379     case IPP_TAG_MIMETYPE :
5380     case IPP_TAG_BEGIN_COLLECTION :
5381         break;
5382 
5383     default :
5384         add_stringf(errors, "WITH-DISTINCT-VALUES %s not supported for 1setOf %s", ippGetName(attr), ippTagString(value_tag));
5385         return (0);
5386   }
5387 
5388   // Collect values and determine they are all unique...
5389   values = cupsArrayNew3((cups_array_func_t)strcmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
5390 
5391   for (i = 0; i < count; i ++)
5392   {
5393     switch (value_tag)
5394     {
5395       case IPP_TAG_INTEGER :
5396       case IPP_TAG_ENUM :
5397           snprintf(buffer, sizeof(buffer), "%d", ippGetInteger(attr, i));
5398           value = buffer;
5399           break;
5400       case IPP_TAG_RANGE :
5401           {
5402             int upper, lower = ippGetRange(attr, i, &upper);
5403 					// Range values
5404 
5405             snprintf(buffer, sizeof(buffer), "%d-%d", lower, upper);
5406             value = buffer;
5407 	  }
5408           break;
5409       case IPP_TAG_RESOLUTION :
5410           {
5411             ipp_res_t units;		// Resolution units
5412             int yres, xres = ippGetResolution(attr, i, &yres, &units);
5413 					// Resolution values
5414 
5415             if (xres == yres)
5416               snprintf(buffer, sizeof(buffer), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
5417 	    else
5418               snprintf(buffer, sizeof(buffer), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
5419             value = buffer;
5420 	  }
5421           break;
5422       case IPP_TAG_KEYWORD :
5423       case IPP_TAG_URISCHEME :
5424       case IPP_TAG_CHARSET :
5425       case IPP_TAG_LANGUAGE :
5426       case IPP_TAG_MIMETYPE :
5427           value = ippGetString(attr, i, NULL);
5428           break;
5429       case IPP_TAG_BEGIN_COLLECTION :
5430           {
5431             ipp_t	*col = ippGetCollection(attr, i);
5432 					// Collection value
5433             ipp_attribute_t *member;	// Member attribute
5434             char	*bufptr,	// Pointer into buffer
5435 			*bufend,	// End of buffer
5436 			prefix;		// Prefix character
5437 
5438             for (prefix = '{', bufptr = buffer, bufend = buffer + sizeof(buffer) - 2, member = ippFirstAttribute(col); member && bufptr < bufend; member = ippNextAttribute(col))
5439             {
5440               *bufptr++ = prefix;
5441               prefix    = ' ';
5442 
5443               ippAttributeString(member, bufptr, (size_t)(bufend - bufptr));
5444               bufptr += strlen(bufptr);
5445             }
5446 
5447             *bufptr++ = '}';
5448             *bufptr   = '\0';
5449             value     = buffer;
5450           }
5451           break;
5452       default : // Should never happen
5453           value = "unsupported";
5454           break;
5455     }
5456 
5457     if (cupsArrayFind(values, (void *)value))
5458       add_stringf(errors, "DUPLICATE: %s=%s", ippGetName(attr), value);
5459     else
5460       cupsArrayAdd(values, (void *)value);
5461   }
5462 
5463   // Cleanup...
5464   i = cupsArrayCount(values) == count;
5465   cupsArrayDelete(values);
5466 
5467   return (i);
5468 }
5469 
5470 
5471 /*
5472  * 'with_flags_string()' - Return the "WITH-xxx" predicate that corresponds to
5473  *                         the flags.
5474  */
5475 
5476 static const char *                     /* O - WITH-xxx string */
with_flags_string(int flags)5477 with_flags_string(int flags)            /* I - WITH flags */
5478 {
5479   if (flags & IPPTOOL_WITH_ALL)
5480   {
5481     if (flags & IPPTOOL_WITH_HOSTNAME)
5482       return ("WITH-ALL-HOSTNAMES");
5483     else if (flags & IPPTOOL_WITH_RESOURCE)
5484       return ("WITH-ALL-RESOURCES");
5485     else if (flags & IPPTOOL_WITH_SCHEME)
5486       return ("WITH-ALL-SCHEMES");
5487     else
5488       return ("WITH-ALL-VALUES");
5489   }
5490   else if (flags & IPPTOOL_WITH_HOSTNAME)
5491     return ("WITH-HOSTNAME");
5492   else if (flags & IPPTOOL_WITH_RESOURCE)
5493     return ("WITH-RESOURCE");
5494   else if (flags & IPPTOOL_WITH_SCHEME)
5495     return ("WITH-SCHEME");
5496   else
5497     return ("WITH-VALUE");
5498 }
5499 
5500 
5501 /*
5502  * 'with_value()' - Test a WITH-VALUE predicate.
5503  */
5504 
5505 static int				/* O - 1 on match, 0 on non-match */
with_value(ipptool_test_t * data,cups_array_t * errors,char * value,int flags,ipp_attribute_t * attr,char * matchbuf,size_t matchlen)5506 with_value(ipptool_test_t *data,	/* I - Test data */
5507            cups_array_t     *errors,	/* I - Errors array */
5508            char             *value,	/* I - Value string */
5509            int              flags,	/* I - Flags for match */
5510            ipp_attribute_t  *attr,	/* I - Attribute to compare */
5511 	   char             *matchbuf,	/* I - Buffer to hold matching value */
5512 	   size_t           matchlen)	/* I - Length of match buffer */
5513 {
5514   int		i,			/* Looping var */
5515     		count,			/* Number of values */
5516 		match;			/* Match? */
5517   char		temp[1024],		/* Temporary value string */
5518 		*valptr;		/* Pointer into value */
5519   const char	*name;			/* Attribute name */
5520 
5521 
5522   *matchbuf = '\0';
5523   match     = (flags & IPPTOOL_WITH_ALL) ? 1 : 0;
5524 
5525  /*
5526   * NULL matches everything.
5527   */
5528 
5529   if (!value || !*value)
5530     return (1);
5531 
5532  /*
5533   * Compare the value string to the attribute value.
5534   */
5535 
5536   name  = ippGetName(attr);
5537   count = ippGetCount(attr);
5538 
5539   switch (ippGetValueTag(attr))
5540   {
5541     case IPP_TAG_INTEGER :
5542     case IPP_TAG_ENUM :
5543         for (i = 0; i < count; i ++)
5544         {
5545 	  char	op,			/* Comparison operator */
5546 	  	*nextptr;		/* Next pointer */
5547 	  int	intvalue,		/* Integer value */
5548 		attrvalue = ippGetInteger(attr, i),
5549 					/* Attribute value */
5550 	  	valmatch = 0;		/* Does the current value match? */
5551 
5552           valptr = value;
5553 
5554 	  while (isspace(*valptr & 255) || isdigit(*valptr & 255) ||
5555 		 *valptr == '-' || *valptr == ',' || *valptr == '<' ||
5556 		 *valptr == '=' || *valptr == '>')
5557 	  {
5558 	    op = '=';
5559 	    while (*valptr && !isdigit(*valptr & 255) && *valptr != '-')
5560 	    {
5561 	      if (*valptr == '<' || *valptr == '>' || *valptr == '=')
5562 		op = *valptr;
5563 	      valptr ++;
5564 	    }
5565 
5566             if (!*valptr)
5567 	      break;
5568 
5569 	    intvalue = (int)strtol(valptr, &nextptr, 0);
5570 	    if (nextptr == valptr)
5571 	      break;
5572 	    valptr = nextptr;
5573 
5574             if ((op == '=' && attrvalue == intvalue) ||
5575                 (op == '<' && attrvalue < intvalue) ||
5576                 (op == '>' && attrvalue > intvalue))
5577 	    {
5578 	      if (!matchbuf[0])
5579 		snprintf(matchbuf, matchlen, "%d", attrvalue);
5580 
5581 	      valmatch = 1;
5582 	      break;
5583 	    }
5584 	  }
5585 
5586           if (flags & IPPTOOL_WITH_ALL)
5587           {
5588             if (!valmatch)
5589             {
5590               match = 0;
5591               break;
5592             }
5593           }
5594           else if (valmatch)
5595           {
5596             match = 1;
5597             break;
5598           }
5599         }
5600 
5601         if (!match && errors)
5602 	{
5603 	  for (i = 0; i < count; i ++)
5604 	    add_stringf(data->errors, "GOT: %s=%d", name, ippGetInteger(attr, i));
5605 	}
5606 	break;
5607 
5608     case IPP_TAG_RANGE :
5609         for (i = 0; i < count; i ++)
5610         {
5611 	  char	op,			/* Comparison operator */
5612 	  	*nextptr;		/* Next pointer */
5613 	  int	intvalue,		/* Integer value */
5614 	        lower,			/* Lower range */
5615 	        upper,			/* Upper range */
5616 	  	valmatch = 0;		/* Does the current value match? */
5617 
5618 	  lower = ippGetRange(attr, i, &upper);
5619           valptr = value;
5620 
5621 	  while (isspace(*valptr & 255) || isdigit(*valptr & 255) ||
5622 		 *valptr == '-' || *valptr == ',' || *valptr == '<' ||
5623 		 *valptr == '=' || *valptr == '>')
5624 	  {
5625 	    op = '=';
5626 	    while (*valptr && !isdigit(*valptr & 255) && *valptr != '-')
5627 	    {
5628 	      if (*valptr == '<' || *valptr == '>' || *valptr == '=')
5629 		op = *valptr;
5630 	      valptr ++;
5631 	    }
5632 
5633             if (!*valptr)
5634 	      break;
5635 
5636 	    intvalue = (int)strtol(valptr, &nextptr, 0);
5637 	    if (nextptr == valptr)
5638 	      break;
5639 	    valptr = nextptr;
5640 
5641             if ((op == '=' && (lower == intvalue || upper == intvalue)) ||
5642 		(op == '<' && upper < intvalue) ||
5643 		(op == '>' && upper > intvalue))
5644 	    {
5645 	      if (!matchbuf[0])
5646 		snprintf(matchbuf, matchlen, "%d-%d", lower, upper);
5647 
5648 	      valmatch = 1;
5649 	      break;
5650 	    }
5651 	  }
5652 
5653           if (flags & IPPTOOL_WITH_ALL)
5654           {
5655             if (!valmatch)
5656             {
5657               match = 0;
5658               break;
5659             }
5660           }
5661           else if (valmatch)
5662           {
5663             match = 1;
5664             break;
5665           }
5666         }
5667 
5668         if (!match && errors)
5669 	{
5670 	  for (i = 0; i < count; i ++)
5671 	  {
5672 	    int lower, upper;		/* Range values */
5673 
5674 	    lower = ippGetRange(attr, i, &upper);
5675 	    add_stringf(data->errors, "GOT: %s=%d-%d", name, lower, upper);
5676 	  }
5677 	}
5678 	break;
5679 
5680     case IPP_TAG_BOOLEAN :
5681 	for (i = 0; i < count; i ++)
5682 	{
5683           if ((!strcmp(value, "true") || !strcmp(value, "1")) == ippGetBoolean(attr, i))
5684           {
5685             if (!matchbuf[0])
5686 	      strlcpy(matchbuf, value, matchlen);
5687 
5688 	    if (!(flags & IPPTOOL_WITH_ALL))
5689 	    {
5690 	      match = 1;
5691 	      break;
5692 	    }
5693 	  }
5694 	  else if (flags & IPPTOOL_WITH_ALL)
5695 	  {
5696 	    match = 0;
5697 	    break;
5698 	  }
5699 	}
5700 
5701 	if (!match && errors)
5702 	{
5703 	  for (i = 0; i < count; i ++)
5704 	    add_stringf(data->errors, "GOT: %s=%s", name, ippGetBoolean(attr, i) ? "true" : "false");
5705 	}
5706 	break;
5707 
5708     case IPP_TAG_RESOLUTION :
5709 	for (i = 0; i < count; i ++)
5710 	{
5711 	  int		xres, yres;	/* Resolution values */
5712 	  ipp_res_t	units;		/* Resolution units */
5713 
5714 	  xres = ippGetResolution(attr, i, &yres, &units);
5715 	  if (xres == yres)
5716 	    snprintf(temp, sizeof(temp), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
5717 	  else
5718 	    snprintf(temp, sizeof(temp), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
5719 
5720           if (!strcmp(value, temp))
5721           {
5722             if (!matchbuf[0])
5723 	      strlcpy(matchbuf, value, matchlen);
5724 
5725 	    if (!(flags & IPPTOOL_WITH_ALL))
5726 	    {
5727 	      match = 1;
5728 	      break;
5729 	    }
5730 	  }
5731 	  else if (flags & IPPTOOL_WITH_ALL)
5732 	  {
5733 	    match = 0;
5734 	    break;
5735 	  }
5736 	}
5737 
5738 	if (!match && errors)
5739 	{
5740 	  for (i = 0; i < count; i ++)
5741 	  {
5742 	    int		xres, yres;	/* Resolution values */
5743 	    ipp_res_t	units;		/* Resolution units */
5744 
5745 	    xres = ippGetResolution(attr, i, &yres, &units);
5746 	    if (xres == yres)
5747 	      snprintf(temp, sizeof(temp), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
5748 	    else
5749 	      snprintf(temp, sizeof(temp), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
5750 
5751             if (strcmp(value, temp))
5752 	      add_stringf(data->errors, "GOT: %s=%s", name, temp);
5753 	  }
5754 	}
5755 	break;
5756 
5757     case IPP_TAG_NOVALUE :
5758     case IPP_TAG_UNKNOWN :
5759 	return (1);
5760 
5761     case IPP_TAG_CHARSET :
5762     case IPP_TAG_KEYWORD :
5763     case IPP_TAG_LANGUAGE :
5764     case IPP_TAG_MIMETYPE :
5765     case IPP_TAG_NAME :
5766     case IPP_TAG_NAMELANG :
5767     case IPP_TAG_TEXT :
5768     case IPP_TAG_TEXTLANG :
5769     case IPP_TAG_URI :
5770     case IPP_TAG_URISCHEME :
5771         if (flags & IPPTOOL_WITH_REGEX)
5772 	{
5773 	 /*
5774 	  * Value is an extended, case-sensitive POSIX regular expression...
5775 	  */
5776 
5777 	  regex_t	re;		/* Regular expression */
5778 
5779           if ((i = regcomp(&re, value, REG_EXTENDED | REG_NOSUB)) != 0)
5780 	  {
5781             regerror(i, &re, temp, sizeof(temp));
5782 
5783 	    print_fatal_error(data, "Unable to compile WITH-VALUE regular expression \"%s\" - %s", value, temp);
5784 	    return (0);
5785 	  }
5786 
5787          /*
5788 	  * See if ALL of the values match the given regular expression.
5789 	  */
5790 
5791 	  for (i = 0; i < count; i ++)
5792 	  {
5793 	    if (!regexec(&re, get_string(attr, i, flags, temp, sizeof(temp)),
5794 	                 0, NULL, 0))
5795 	    {
5796 	      if (!matchbuf[0])
5797 		strlcpy(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
5798 
5799 	      if (!(flags & IPPTOOL_WITH_ALL))
5800 	      {
5801 	        match = 1;
5802 	        break;
5803 	      }
5804 	    }
5805 	    else if (flags & IPPTOOL_WITH_ALL)
5806 	    {
5807 	      match = 0;
5808 	      break;
5809 	    }
5810 	  }
5811 
5812 	  regfree(&re);
5813 	}
5814 	else if (ippGetValueTag(attr) == IPP_TAG_URI && !(flags & (IPPTOOL_WITH_SCHEME | IPPTOOL_WITH_HOSTNAME | IPPTOOL_WITH_RESOURCE)))
5815 	{
5816 	 /*
5817 	  * Value is a literal URI string, see if the value(s) match...
5818 	  */
5819 
5820 	  for (i = 0; i < count; i ++)
5821 	  {
5822 	    if (!compare_uris(value, get_string(attr, i, flags, temp, sizeof(temp))))
5823 	    {
5824 	      if (!matchbuf[0])
5825 		strlcpy(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
5826 
5827 	      if (!(flags & IPPTOOL_WITH_ALL))
5828 	      {
5829 	        match = 1;
5830 	        break;
5831 	      }
5832 	    }
5833 	    else if (flags & IPPTOOL_WITH_ALL)
5834 	    {
5835 	      match = 0;
5836 	      break;
5837 	    }
5838 	  }
5839 	}
5840 	else
5841 	{
5842 	 /*
5843 	  * Value is a literal string, see if the value(s) match...
5844 	  */
5845 
5846 	  for (i = 0; i < count; i ++)
5847 	  {
5848 	    int result;
5849 
5850             switch (ippGetValueTag(attr))
5851             {
5852               case IPP_TAG_URI :
5853                  /*
5854                   * Some URI components are case-sensitive, some not...
5855                   */
5856 
5857                   if (flags & (IPPTOOL_WITH_SCHEME | IPPTOOL_WITH_HOSTNAME))
5858                     result = _cups_strcasecmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
5859                   else
5860                     result = strcmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
5861                   break;
5862 
5863               case IPP_TAG_MIMETYPE :
5864               case IPP_TAG_NAME :
5865               case IPP_TAG_NAMELANG :
5866               case IPP_TAG_TEXT :
5867               case IPP_TAG_TEXTLANG :
5868                  /*
5869                   * mimeMediaType, nameWithoutLanguage, nameWithLanguage,
5870                   * textWithoutLanguage, and textWithLanguage are defined to
5871                   * be case-insensitive strings...
5872                   */
5873 
5874                   result = _cups_strcasecmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
5875                   break;
5876 
5877               default :
5878                  /*
5879                   * Other string syntaxes are defined as lowercased so we use
5880                   * case-sensitive comparisons to catch problems...
5881                   */
5882 
5883                   result = strcmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
5884                   break;
5885             }
5886 
5887             if (!result)
5888 	    {
5889 	      if (!matchbuf[0])
5890 		strlcpy(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
5891 
5892 	      if (!(flags & IPPTOOL_WITH_ALL))
5893 	      {
5894 	        match = 1;
5895 	        break;
5896 	      }
5897 	    }
5898 	    else if (flags & IPPTOOL_WITH_ALL)
5899 	    {
5900 	      match = 0;
5901 	      break;
5902 	    }
5903 	  }
5904 	}
5905 
5906         if (!match && errors)
5907         {
5908 	  for (i = 0; i < count; i ++)
5909 	    add_stringf(data->errors, "GOT: %s=\"%s\"", name, ippGetString(attr, i, NULL));
5910         }
5911 	break;
5912 
5913     case IPP_TAG_STRING :
5914         if (flags & IPPTOOL_WITH_REGEX)
5915 	{
5916 	 /*
5917 	  * Value is an extended, case-sensitive POSIX regular expression...
5918 	  */
5919 
5920 	  void		*adata;		/* Pointer to octetString data */
5921 	  int		adatalen;	/* Length of octetString */
5922 	  regex_t	re;		/* Regular expression */
5923 
5924           if ((i = regcomp(&re, value, REG_EXTENDED | REG_NOSUB)) != 0)
5925 	  {
5926             regerror(i, &re, temp, sizeof(temp));
5927 
5928 	    print_fatal_error(data, "Unable to compile WITH-VALUE regular expression \"%s\" - %s", value, temp);
5929 	    return (0);
5930 	  }
5931 
5932          /*
5933 	  * See if ALL of the values match the given regular expression.
5934 	  */
5935 
5936 	  for (i = 0; i < count; i ++)
5937 	  {
5938             if ((adata = ippGetOctetString(attr, i, &adatalen)) == NULL || adatalen >= (int)sizeof(temp))
5939             {
5940               match = 0;
5941               break;
5942             }
5943             memcpy(temp, adata, (size_t)adatalen);
5944             temp[adatalen] = '\0';
5945 
5946 	    if (!regexec(&re, temp, 0, NULL, 0))
5947 	    {
5948 	      if (!matchbuf[0])
5949 		strlcpy(matchbuf, temp, matchlen);
5950 
5951 	      if (!(flags & IPPTOOL_WITH_ALL))
5952 	      {
5953 	        match = 1;
5954 	        break;
5955 	      }
5956 	    }
5957 	    else if (flags & IPPTOOL_WITH_ALL)
5958 	    {
5959 	      match = 0;
5960 	      break;
5961 	    }
5962 	  }
5963 
5964 	  regfree(&re);
5965 
5966 	  if (!match && errors)
5967 	  {
5968 	    for (i = 0; i < count; i ++)
5969 	    {
5970 	      adata = ippGetOctetString(attr, i, &adatalen);
5971 	      copy_hex_string(temp, adata, adatalen, sizeof(temp));
5972 	      add_stringf(data->errors, "GOT: %s=\"%s\"", name, temp);
5973 	    }
5974 	  }
5975 	}
5976 	else
5977         {
5978          /*
5979           * Value is a literal or hex-encoded string...
5980           */
5981 
5982           unsigned char	withdata[1023],	/* WITH-VALUE data */
5983 			*adata;		/* Pointer to octetString data */
5984 	  int		withlen,	/* Length of WITH-VALUE data */
5985 			adatalen;	/* Length of octetString */
5986 
5987           if (*value == '<')
5988           {
5989            /*
5990             * Grab hex-encoded value...
5991             */
5992 
5993             if ((withlen = (int)strlen(value)) & 1 || withlen > (int)(2 * (sizeof(withdata) + 1)))
5994             {
5995 	      print_fatal_error(data, "Bad WITH-VALUE hex value.");
5996               return (0);
5997 	    }
5998 
5999 	    withlen = withlen / 2 - 1;
6000 
6001             for (valptr = value + 1, adata = withdata; *valptr; valptr += 2)
6002             {
6003               int ch;			/* Current character/byte */
6004 
6005 	      if (isdigit(valptr[0]))
6006 	        ch = (valptr[0] - '0') << 4;
6007 	      else if (isalpha(valptr[0]))
6008 	        ch = (tolower(valptr[0]) - 'a' + 10) << 4;
6009 	      else
6010 	        break;
6011 
6012 	      if (isdigit(valptr[1]))
6013 	        ch |= valptr[1] - '0';
6014 	      else if (isalpha(valptr[1]))
6015 	        ch |= tolower(valptr[1]) - 'a' + 10;
6016 	      else
6017 	        break;
6018 
6019 	      *adata++ = (unsigned char)ch;
6020 	    }
6021 
6022 	    if (*valptr)
6023 	    {
6024 	      print_fatal_error(data, "Bad WITH-VALUE hex value.");
6025               return (0);
6026 	    }
6027           }
6028           else
6029           {
6030            /*
6031             * Copy literal string value...
6032             */
6033 
6034             withlen = (int)strlen(value);
6035 
6036             memcpy(withdata, value, (size_t)withlen);
6037 	  }
6038 
6039 	  for (i = 0; i < count; i ++)
6040 	  {
6041 	    adata = ippGetOctetString(attr, i, &adatalen);
6042 
6043 	    if (withlen == adatalen && !memcmp(withdata, adata, (size_t)withlen))
6044 	    {
6045 	      if (!matchbuf[0])
6046                 copy_hex_string(matchbuf, adata, adatalen, matchlen);
6047 
6048 	      if (!(flags & IPPTOOL_WITH_ALL))
6049 	      {
6050 	        match = 1;
6051 	        break;
6052 	      }
6053 	    }
6054 	    else if (flags & IPPTOOL_WITH_ALL)
6055 	    {
6056 	      match = 0;
6057 	      break;
6058 	    }
6059 	  }
6060 
6061 	  if (!match && errors)
6062 	  {
6063 	    for (i = 0; i < count; i ++)
6064 	    {
6065 	      adata = ippGetOctetString(attr, i, &adatalen);
6066 	      copy_hex_string(temp, adata, adatalen, sizeof(temp));
6067 	      add_stringf(data->errors, "GOT: %s=\"%s\"", name, temp);
6068 	    }
6069 	  }
6070         }
6071         break;
6072 
6073     default :
6074         break;
6075   }
6076 
6077   return (match);
6078 }
6079 
6080 
6081 /*
6082  * 'with_value_from()' - Test a WITH-VALUE-FROM predicate.
6083  */
6084 
6085 static int				/* O - 1 on match, 0 on non-match */
with_value_from(cups_array_t * errors,ipp_attribute_t * fromattr,ipp_attribute_t * attr,char * matchbuf,size_t matchlen)6086 with_value_from(
6087     cups_array_t    *errors,		/* I - Errors array */
6088     ipp_attribute_t *fromattr,		/* I - "From" attribute */
6089     ipp_attribute_t *attr,		/* I - Attribute to compare */
6090     char            *matchbuf,		/* I - Buffer to hold matching value */
6091     size_t          matchlen)		/* I - Length of match buffer */
6092 {
6093   int	i, j,				/* Looping vars */
6094 	count = ippGetCount(attr),	/* Number of attribute values */
6095 	match = 1;			/* Match? */
6096 
6097 
6098   *matchbuf = '\0';
6099 
6100  /*
6101   * Compare the from value(s) to the attribute value(s)...
6102   */
6103 
6104   switch (ippGetValueTag(attr))
6105   {
6106     case IPP_TAG_INTEGER :
6107         if (ippGetValueTag(fromattr) != IPP_TAG_INTEGER && ippGetValueTag(fromattr) != IPP_TAG_RANGE)
6108 	  goto wrong_value_tag;
6109 
6110 	for (i = 0; i < count; i ++)
6111 	{
6112 	  int value = ippGetInteger(attr, i);
6113 					/* Current integer value */
6114 
6115 	  if (ippContainsInteger(fromattr, value))
6116 	  {
6117 	    if (!matchbuf[0])
6118 	      snprintf(matchbuf, matchlen, "%d", value);
6119 	  }
6120 	  else
6121 	  {
6122 	    add_stringf(errors, "GOT: %s=%d", ippGetName(attr), value);
6123 	    match = 0;
6124 	  }
6125 	}
6126 	break;
6127 
6128     case IPP_TAG_ENUM :
6129         if (ippGetValueTag(fromattr) != IPP_TAG_ENUM)
6130 	  goto wrong_value_tag;
6131 
6132 	for (i = 0; i < count; i ++)
6133 	{
6134 	  int value = ippGetInteger(attr, i);
6135 					/* Current integer value */
6136 
6137 	  if (ippContainsInteger(fromattr, value))
6138 	  {
6139 	    if (!matchbuf[0])
6140 	      snprintf(matchbuf, matchlen, "%d", value);
6141 	  }
6142 	  else
6143 	  {
6144 	    add_stringf(errors, "GOT: %s=%d", ippGetName(attr), value);
6145 	    match = 0;
6146 	  }
6147 	}
6148 	break;
6149 
6150     case IPP_TAG_RESOLUTION :
6151         if (ippGetValueTag(fromattr) != IPP_TAG_RESOLUTION)
6152 	  goto wrong_value_tag;
6153 
6154 	for (i = 0; i < count; i ++)
6155 	{
6156 	  int xres, yres;
6157 	  ipp_res_t units;
6158           int fromcount = ippGetCount(fromattr);
6159 	  int fromxres, fromyres;
6160 	  ipp_res_t fromunits;
6161 
6162 	  xres = ippGetResolution(attr, i, &yres, &units);
6163 
6164           for (j = 0; j < fromcount; j ++)
6165 	  {
6166 	    fromxres = ippGetResolution(fromattr, j, &fromyres, &fromunits);
6167 	    if (fromxres == xres && fromyres == yres && fromunits == units)
6168 	      break;
6169 	  }
6170 
6171 	  if (j < fromcount)
6172 	  {
6173 	    if (!matchbuf[0])
6174 	    {
6175 	      if (xres == yres)
6176 	        snprintf(matchbuf, matchlen, "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
6177 	      else
6178 	        snprintf(matchbuf, matchlen, "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
6179 	    }
6180 	  }
6181 	  else
6182 	  {
6183 	    if (xres == yres)
6184 	      add_stringf(errors, "GOT: %s=%d%s", ippGetName(attr), xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
6185 	    else
6186 	      add_stringf(errors, "GOT: %s=%dx%d%s", ippGetName(attr), xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
6187 
6188 	    match = 0;
6189 	  }
6190 	}
6191 	break;
6192 
6193     case IPP_TAG_NOVALUE :
6194     case IPP_TAG_UNKNOWN :
6195 	return (1);
6196 
6197     case IPP_TAG_CHARSET :
6198     case IPP_TAG_KEYWORD :
6199     case IPP_TAG_LANGUAGE :
6200     case IPP_TAG_MIMETYPE :
6201     case IPP_TAG_NAME :
6202     case IPP_TAG_NAMELANG :
6203     case IPP_TAG_TEXT :
6204     case IPP_TAG_TEXTLANG :
6205     case IPP_TAG_URISCHEME :
6206 	for (i = 0; i < count; i ++)
6207 	{
6208 	  const char *value = ippGetString(attr, i, NULL);
6209 					/* Current string value */
6210 
6211 	  if (ippContainsString(fromattr, value))
6212 	  {
6213 	    if (!matchbuf[0])
6214 	      strlcpy(matchbuf, value, matchlen);
6215 	  }
6216 	  else
6217 	  {
6218 	    add_stringf(errors, "GOT: %s='%s'", ippGetName(attr), value);
6219 	    match = 0;
6220 	  }
6221 	}
6222 	break;
6223 
6224     case IPP_TAG_URI :
6225 	for (i = 0; i < count; i ++)
6226 	{
6227 	  const char *value = ippGetString(attr, i, NULL);
6228 					/* Current string value */
6229           int fromcount = ippGetCount(fromattr);
6230 
6231           for (j = 0; j < fromcount; j ++)
6232           {
6233             if (!compare_uris(value, ippGetString(fromattr, j, NULL)))
6234             {
6235               if (!matchbuf[0])
6236                 strlcpy(matchbuf, value, matchlen);
6237               break;
6238             }
6239           }
6240 
6241 	  if (j >= fromcount)
6242 	  {
6243 	    add_stringf(errors, "GOT: %s='%s'", ippGetName(attr), value);
6244 	    match = 0;
6245 	  }
6246 	}
6247 	break;
6248 
6249     default :
6250         match = 0;
6251         break;
6252   }
6253 
6254   return (match);
6255 
6256   /* value tag mismatch between fromattr and attr */
6257   wrong_value_tag :
6258 
6259   add_stringf(errors, "GOT: %s OF-TYPE %s", ippGetName(attr), ippTagString(ippGetValueTag(attr)));
6260 
6261   return (0);
6262 }
6263