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