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