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