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