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