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