4 * ipptool command for CUPS.
6 * Copyright 2007-2013 by Apple Inc.
7 * Copyright 1997-2007 by Easy Software Products.
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/".
15 * This file is subject to the Apple OS-Developed Software exception.
19 * main() - Parse options and do tests.
20 * add_stringf() - Add a formatted string to an array.
21 * compare_vars() - Compare two variables.
22 * do_tests() - Do tests as specified in the test file.
23 * expand_variables() - Expand variables in a string.
24 * expect_matches() - Return true if the tag matches the specification.
25 * get_collection() - Get a collection value from the current test file.
26 * get_filename() - Get a filename based on the current test file.
27 * get_token() - Get a token from a file.
28 * get_variable() - Get the value of a variable.
29 * iso_date() - Return an ISO 8601 date/time string for the given IPP
31 * password_cb() - Password callback for authenticated tests.
32 * print_attr() - Print an attribute on the screen.
33 * print_col() - Print a collection attribute on the screen.
34 * print_csv() - Print a line of CSV text.
35 * print_fatal_error() - Print a fatal error message.
36 * print_line() - Print a line of formatted or CSV text.
37 * print_xml_header() - Print a standard XML plist header.
38 * print_xml_string() - Print an XML string with escaping.
39 * print_xml_trailer() - Print the XML trailer with success/fail value.
40 * set_variable() - Set a variable value.
41 * sigterm_handler() - Handle SIGINT and SIGTERM.
42 * timeout_cb() - Handle HTTP timeouts.
43 * usage() - Show program usage.
44 * validate_attr() - Determine whether an attribute is valid.
45 * with_value() - Test a WITH-VALUE predicate.
49 * Include necessary headers...
52 #include <cups/cups-private.h>
53 #include <cups/file-private.h>
63 #endif /* !O_BINARY */
70 typedef enum _cups_transfer_e
/**** How to send request data ****/
72 _CUPS_TRANSFER_AUTO
, /* Chunk for files, length for static */
73 _CUPS_TRANSFER_CHUNKED
, /* Chunk always */
74 _CUPS_TRANSFER_LENGTH
/* Length always */
77 typedef enum _cups_output_e
/**** Output mode ****/
79 _CUPS_OUTPUT_QUIET
, /* No output */
80 _CUPS_OUTPUT_TEST
, /* Traditional CUPS test output */
81 _CUPS_OUTPUT_PLIST
, /* XML plist test output */
82 _CUPS_OUTPUT_LIST
, /* Tabular list output */
83 _CUPS_OUTPUT_CSV
/* Comma-separated values output */
86 typedef enum _cups_with_e
/**** WITH flags ****/
88 _CUPS_WITH_LITERAL
= 0, /* Match string is a literal value */
89 _CUPS_WITH_ALL
= 1, /* Must match all values */
90 _CUPS_WITH_REGEX
= 2, /* Match string is a regular expression */
91 _CUPS_WITH_HOSTNAME
= 4, /* Match string is a URI hostname */
92 _CUPS_WITH_RESOURCE
= 8, /* Match string is a URI resource */
93 _CUPS_WITH_SCHEME
= 16 /* Match string is a URI scheme */
96 typedef struct _cups_expect_s
/**** Expected attribute info ****/
98 int optional
, /* Optional attribute? */
99 not_expect
; /* Don't expect attribute? */
100 char *name
, /* Attribute name */
101 *of_type
, /* Type name */
102 *same_count_as
, /* Parallel attribute name */
103 *if_defined
, /* Only required if variable defined */
104 *if_not_defined
, /* Only required if variable is not defined */
105 *with_value
, /* Attribute must include this value */
106 *define_match
, /* Variable to define on match */
107 *define_no_match
, /* Variable to define on no-match */
108 *define_value
; /* Variable to define with value */
109 int repeat_limit
, /* Maximum number of times to repeat */
110 repeat_match
, /* Repeat test on match */
111 repeat_no_match
, /* Repeat test on no match */
112 with_flags
, /* WITH flags */
113 count
; /* Expected count if > 0 */
114 ipp_tag_t in_group
; /* IN-GROUP value */
117 typedef struct _cups_status_s
/**** Status info ****/
119 ipp_status_t status
; /* Expected status code */
120 char *if_defined
, /* Only if variable is defined */
121 *if_not_defined
, /* Only if variable is not defined */
122 *define_match
, /* Variable to define on match */
123 *define_no_match
, /* Variable to define on no-match */
124 *define_value
; /* Variable to define with value */
125 int repeat_limit
, /* Maximum number of times to repeat */
126 repeat_match
, /* Repeat the test when it does not match */
127 repeat_no_match
; /* Repeat the test when it matches */
130 typedef struct _cups_var_s
/**** Variable ****/
132 char *name
, /* Name of variable */
133 *value
; /* Value of variable */
136 typedef struct _cups_vars_s
/**** Set of variables ****/
138 char *uri
, /* URI for printer */
139 *filename
, /* Filename */
140 scheme
[64], /* Scheme from URI */
141 userpass
[256], /* Username/password from URI */
142 hostname
[256], /* Hostname from URI */
143 resource
[1024]; /* Resource path from URI */
144 int port
; /* Port number from URI */
145 http_encryption_t encryption
; /* Encryption for connection? */
146 double timeout
; /* Timeout for connection */
147 int family
; /* Address family */
148 cups_array_t
*vars
; /* Array of variables */
156 _cups_transfer_t Transfer
= _CUPS_TRANSFER_AUTO
;
157 /* How to transfer requests */
158 _cups_output_t Output
= _CUPS_OUTPUT_LIST
;
160 int Cancel
= 0, /* Cancel test? */
161 IgnoreErrors
= 0, /* Ignore errors? */
162 StopAfterIncludeError
= 0,
163 /* Stop after include errors? */
164 Verbosity
= 0, /* Show all attributes? */
165 Version
= 11, /* Default IPP version */
166 XMLHeader
= 0, /* 1 if header is written */
167 TestCount
= 0, /* Number of tests run */
168 PassCount
= 0, /* Number of passing tests */
169 FailCount
= 0, /* Number of failing tests */
170 SkipCount
= 0; /* Number of skipped tests */
171 char *Password
= NULL
; /* Password from URI */
172 const char * const URIStatusStrings
[] = /* URI status strings */
175 "Bad arguments to function",
176 "Bad resource in URI",
177 "Bad port number in URI",
178 "Bad hostname/address in URI",
179 "Bad username in URI",
183 "Missing scheme in URI",
184 "Unknown scheme in URI",
185 "Missing resource in URI"
193 static void add_stringf(cups_array_t
*a
, const char *s
, ...)
194 __attribute__ ((__format__ (__printf__
, 2, 3)));
195 static int compare_vars(_cups_var_t
*a
, _cups_var_t
*b
);
196 static int do_tests(_cups_vars_t
*vars
, const char *testfile
);
197 static void expand_variables(_cups_vars_t
*vars
, char *dst
, const char *src
,
198 size_t dstsize
) __attribute__((nonnull(1,2,3)));
199 static int expect_matches(_cups_expect_t
*expect
, ipp_tag_t value_tag
);
200 static ipp_t
*get_collection(_cups_vars_t
*vars
, FILE *fp
, int *linenum
);
201 static char *get_filename(const char *testfile
, char *dst
, const char *src
,
203 static char *get_string(ipp_attribute_t
*attr
, int element
, int flags
,
204 char *buffer
, size_t bufsize
);
205 static char *get_token(FILE *fp
, char *buf
, int buflen
,
207 static char *get_variable(_cups_vars_t
*vars
, const char *name
);
208 static char *iso_date(ipp_uchar_t
*date
);
209 static const char *password_cb(const char *prompt
);
210 static void print_attr(ipp_attribute_t
*attr
, ipp_tag_t
*group
);
211 static void print_col(ipp_t
*col
);
212 static void print_csv(ipp_attribute_t
*attr
, int num_displayed
,
213 char **displayed
, size_t *widths
);
214 static void print_fatal_error(const char *s
, ...)
215 __attribute__ ((__format__ (__printf__
, 1, 2)));
216 static void print_line(ipp_attribute_t
*attr
, int num_displayed
,
217 char **displayed
, size_t *widths
);
218 static void print_xml_header(void);
219 static void print_xml_string(const char *element
, const char *s
);
220 static void print_xml_trailer(int success
, const char *message
);
221 static void set_variable(_cups_vars_t
*vars
, const char *name
,
224 static void sigterm_handler(int sig
);
226 static int timeout_cb(http_t
*http
, void *user_data
);
227 static void usage(void) __attribute__((noreturn
));
228 static int validate_attr(cups_array_t
*errors
, ipp_attribute_t
*attr
);
229 static int with_value(cups_array_t
*errors
, char *value
, int flags
,
230 ipp_attribute_t
*attr
, char *matchbuf
,
235 * 'main()' - Parse options and do tests.
238 int /* O - Exit status */
239 main(int argc
, /* I - Number of command-line args */
240 char *argv
[]) /* I - Command-line arguments */
242 int i
; /* Looping var */
243 int status
; /* Status of tests... */
244 char *opt
, /* Current option */
245 name
[1024], /* Name/value buffer */
246 *value
, /* Pointer to value */
247 filename
[1024], /* Real filename */
248 testname
[1024], /* Real test filename */
249 uri
[1024]; /* Copy of printer URI */
250 const char *ext
, /* Extension on filename */
251 *testfile
; /* Test file to use */
252 int interval
, /* Test interval in microseconds */
253 repeat
; /* Repeat count */
254 _cups_vars_t vars
; /* Variables */
255 http_uri_status_t uri_status
; /* URI separation status */
256 _cups_globals_t
*cg
= _cupsGlobals();
262 * Catch SIGINT and SIGTERM...
265 signal(SIGINT
, sigterm_handler
);
266 signal(SIGTERM
, sigterm_handler
);
270 * Initialize the locale and variables...
273 _cupsSetLocale(argv
);
275 memset(&vars
, 0, sizeof(vars
));
276 vars
.family
= AF_UNSPEC
;
277 vars
.vars
= cupsArrayNew((cups_array_func_t
)compare_vars
, NULL
);
282 * ipptool URI testfile
290 for (i
= 1; i
< argc
; i
++)
292 if (!strcmp(argv
[i
], "--help"))
296 else if (!strcmp(argv
[i
], "--stop-after-include-error"))
298 StopAfterIncludeError
= 1;
300 else if (!strcmp(argv
[i
], "--version"))
305 else if (argv
[i
][0] == '-')
307 for (opt
= argv
[i
] + 1; *opt
; opt
++)
311 case '4' : /* Connect using IPv4 only */
312 vars
.family
= AF_INET
;
316 case '6' : /* Connect using IPv6 only */
317 vars
.family
= AF_INET6
;
319 #endif /* AF_INET6 */
321 case 'C' : /* Enable HTTP chunking */
322 Transfer
= _CUPS_TRANSFER_CHUNKED
;
325 case 'E' : /* Encrypt with TLS */
327 vars
.encryption
= HTTP_ENCRYPT_REQUIRED
;
329 _cupsLangPrintf(stderr
, _("%s: Sorry, no encryption support."),
331 #endif /* HAVE_SSL */
334 case 'I' : /* Ignore errors */
338 case 'L' : /* Disable HTTP chunking */
339 Transfer
= _CUPS_TRANSFER_LENGTH
;
342 case 'S' : /* Encrypt with SSL */
344 vars
.encryption
= HTTP_ENCRYPT_ALWAYS
;
346 _cupsLangPrintf(stderr
, _("%s: Sorry, no encryption support."),
348 #endif /* HAVE_SSL */
351 case 'T' : /* Set timeout */
356 _cupsLangPuts(stderr
,
357 _("ipptool: Missing timeout for \"-T\"."));
361 vars
.timeout
= _cupsStrScand(argv
[i
], NULL
, localeconv());
364 case 'V' : /* Set IPP version */
369 _cupsLangPuts(stderr
,
370 _("ipptool: Missing version for \"-V\"."));
374 if (!strcmp(argv
[i
], "1.0"))
376 else if (!strcmp(argv
[i
], "1.1"))
378 else if (!strcmp(argv
[i
], "2.0"))
380 else if (!strcmp(argv
[i
], "2.1"))
382 else if (!strcmp(argv
[i
], "2.2"))
386 _cupsLangPrintf(stderr
,
387 _("ipptool: Bad version %s for \"-V\"."),
393 case 'X' : /* Produce XML output */
394 Output
= _CUPS_OUTPUT_PLIST
;
396 if (interval
|| repeat
)
398 _cupsLangPuts(stderr
, _("ipptool: \"-i\" and \"-n\" are "
399 "incompatible with -X\"."));
404 case 'c' : /* CSV output */
405 Output
= _CUPS_OUTPUT_CSV
;
408 case 'd' : /* Define a variable */
413 _cupsLangPuts(stderr
,
414 _("ipptool: Missing name=value for \"-d\"."));
418 strlcpy(name
, argv
[i
], sizeof(name
));
419 if ((value
= strchr(name
, '=')) != NULL
)
422 value
= name
+ strlen(name
);
424 set_variable(&vars
, name
, value
);
427 case 'f' : /* Set the default test filename */
432 _cupsLangPuts(stderr
,
433 _("ipptool: Missing filename for \"-f\"."));
440 vars
.filename
= NULL
;
443 if (access(argv
[i
], 0))
449 snprintf(filename
, sizeof(filename
), "%s.gz", argv
[i
]);
450 if (access(filename
, 0) && filename
[0] != '/'
452 && (!isalpha(filename
[0] & 255) || filename
[1] != ':')
456 snprintf(filename
, sizeof(filename
), "%s/ipptool/%s",
457 cg
->cups_datadir
, argv
[i
]);
458 if (access(filename
, 0))
460 snprintf(filename
, sizeof(filename
), "%s/ipptool/%s.gz",
461 cg
->cups_datadir
, argv
[i
]);
462 if (access(filename
, 0))
463 vars
.filename
= strdup(argv
[i
]);
465 vars
.filename
= strdup(filename
);
468 vars
.filename
= strdup(filename
);
471 vars
.filename
= strdup(filename
);
474 vars
.filename
= strdup(argv
[i
]);
476 if ((ext
= strrchr(vars
.filename
, '.')) != NULL
)
479 * Guess the MIME media type based on the extension...
482 if (!_cups_strcasecmp(ext
, ".gif"))
483 set_variable(&vars
, "filetype", "image/gif");
484 else if (!_cups_strcasecmp(ext
, ".htm") ||
485 !_cups_strcasecmp(ext
, ".htm.gz") ||
486 !_cups_strcasecmp(ext
, ".html") ||
487 !_cups_strcasecmp(ext
, ".html.gz"))
488 set_variable(&vars
, "filetype", "text/html");
489 else if (!_cups_strcasecmp(ext
, ".jpg"))
490 set_variable(&vars
, "filetype", "image/jpeg");
491 else if (!_cups_strcasecmp(ext
, ".pcl") ||
492 !_cups_strcasecmp(ext
, ".pcl.gz"))
493 set_variable(&vars
, "filetype", "application/vnd.hp-PCL");
494 else if (!_cups_strcasecmp(ext
, ".pdf"))
495 set_variable(&vars
, "filetype", "application/pdf");
496 else if (!_cups_strcasecmp(ext
, ".png"))
497 set_variable(&vars
, "filetype", "image/png");
498 else if (!_cups_strcasecmp(ext
, ".ps") ||
499 !_cups_strcasecmp(ext
, ".ps.gz"))
500 set_variable(&vars
, "filetype", "application/postscript");
501 else if (!_cups_strcasecmp(ext
, ".pwg") ||
502 !_cups_strcasecmp(ext
, ".pwg.gz") ||
503 !_cups_strcasecmp(ext
, ".ras") ||
504 !_cups_strcasecmp(ext
, ".ras.gz"))
505 set_variable(&vars
, "filetype", "image/pwg-raster");
506 else if (!_cups_strcasecmp(ext
, ".txt") ||
507 !_cups_strcasecmp(ext
, ".txt.gz"))
508 set_variable(&vars
, "filetype", "text/plain");
509 else if (!_cups_strcasecmp(ext
, ".xps"))
510 set_variable(&vars
, "filetype", "application/openxps");
512 set_variable(&vars
, "filetype", "application/octet-stream");
517 * Use the "auto-type" MIME media type...
520 set_variable(&vars
, "filetype", "application/octet-stream");
524 case 'i' : /* Test every N seconds */
529 _cupsLangPuts(stderr
,
530 _("ipptool: Missing seconds for \"-i\"."));
535 interval
= (int)(_cupsStrScand(argv
[i
], NULL
, localeconv()) *
539 _cupsLangPuts(stderr
,
540 _("ipptool: Invalid seconds for \"-i\"."));
545 if (Output
== _CUPS_OUTPUT_PLIST
&& interval
)
547 _cupsLangPuts(stderr
, _("ipptool: \"-i\" and \"-n\" are "
548 "incompatible with -X\"."));
553 case 'l' : /* List as a table */
554 Output
= _CUPS_OUTPUT_LIST
;
557 case 'n' : /* Repeat count */
562 _cupsLangPuts(stderr
,
563 _("ipptool: Missing count for \"-n\"."));
567 repeat
= atoi(argv
[i
]);
569 if (Output
== _CUPS_OUTPUT_PLIST
&& repeat
)
571 _cupsLangPuts(stderr
, _("ipptool: \"-i\" and \"-n\" are "
572 "incompatible with -X\"."));
577 case 'q' : /* Be quiet */
578 Output
= _CUPS_OUTPUT_QUIET
;
581 case 't' : /* CUPS test output */
582 Output
= _CUPS_OUTPUT_TEST
;
585 case 'v' : /* Be verbose */
590 _cupsLangPrintf(stderr
, _("ipptool: Unknown option \"-%c\"."),
597 else if (!strncmp(argv
[i
], "ipp://", 6) || !strncmp(argv
[i
], "http://", 7)
599 || !strncmp(argv
[i
], "ipps://", 7)
600 || !strncmp(argv
[i
], "https://", 8)
601 #endif /* HAVE_SSL */
610 _cupsLangPuts(stderr
, _("ipptool: May only specify a single URI."));
615 if (!strncmp(argv
[i
], "ipps://", 7) || !strncmp(argv
[i
], "https://", 8))
616 vars
.encryption
= HTTP_ENCRYPT_ALWAYS
;
617 #endif /* HAVE_SSL */
620 uri_status
= httpSeparateURI(HTTP_URI_CODING_ALL
, vars
.uri
,
621 vars
.scheme
, sizeof(vars
.scheme
),
622 vars
.userpass
, sizeof(vars
.userpass
),
623 vars
.hostname
, sizeof(vars
.hostname
),
625 vars
.resource
, sizeof(vars
.resource
));
627 if (uri_status
!= HTTP_URI_OK
)
629 _cupsLangPrintf(stderr
, _("ipptool: Bad URI - %s."),
630 URIStatusStrings
[uri_status
- HTTP_URI_OVERFLOW
]);
634 if (vars
.userpass
[0])
636 if ((Password
= strchr(vars
.userpass
, ':')) != NULL
)
639 cupsSetUser(vars
.userpass
);
640 cupsSetPasswordCB(password_cb
);
641 set_variable(&vars
, "uriuser", vars
.userpass
);
644 httpAssembleURI(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), vars
.scheme
, NULL
,
645 vars
.hostname
, vars
.port
, vars
.resource
);
656 _cupsLangPuts(stderr
, _("ipptool: URI required before test file."));
660 if (access(argv
[i
], 0) && argv
[i
][0] != '/'
662 && (!isalpha(argv
[i
][0] & 255) || argv
[i
][1] != ':')
666 snprintf(testname
, sizeof(testname
), "%s/ipptool/%s", cg
->cups_datadir
,
668 if (access(testname
, 0))
676 if (!do_tests(&vars
, testfile
))
681 if (!vars
.uri
|| !testfile
)
685 * Loop if the interval is set...
688 if (Output
== _CUPS_OUTPUT_PLIST
)
689 print_xml_trailer(!status
, NULL
);
690 else if (interval
> 0 && repeat
> 0)
695 do_tests(&vars
, testfile
);
699 else if (interval
> 0)
704 do_tests(&vars
, testfile
);
707 else if (Output
== _CUPS_OUTPUT_TEST
&& TestCount
> 1)
710 * Show a summary report if there were multiple tests...
713 printf("\nSummary: %d tests, %d passed, %d failed, %d skipped\n"
714 "Score: %d%%\n", TestCount
, PassCount
, FailCount
, SkipCount
,
715 100 * (PassCount
+ SkipCount
) / TestCount
);
727 * 'add_stringf()' - Add a formatted string to an array.
731 add_stringf(cups_array_t
*a
, /* I - Array */
732 const char *s
, /* I - Printf-style format string */
733 ...) /* I - Additional args as needed */
735 char buffer
[10240]; /* Format buffer */
736 va_list ap
; /* Argument pointer */
740 * Don't bother is the array is NULL...
747 * Format the message...
751 vsnprintf(buffer
, sizeof(buffer
), s
, ap
);
755 * Add it to the array...
758 cupsArrayAdd(a
, buffer
);
763 * 'compare_vars()' - Compare two variables.
766 static int /* O - Result of comparison */
767 compare_vars(_cups_var_t
*a
, /* I - First variable */
768 _cups_var_t
*b
) /* I - Second variable */
770 return (_cups_strcasecmp(a
->name
, b
->name
));
775 * 'do_tests()' - Do tests as specified in the test file.
778 static int /* 1 = success, 0 = failure */
779 do_tests(_cups_vars_t
*vars
, /* I - Variables */
780 const char *testfile
) /* I - Test file to use */
782 int i
, /* Looping var */
783 linenum
, /* Current line number */
784 pass
, /* Did we pass the test? */
785 prev_pass
= 1, /* Did we pass the previous test? */
786 request_id
, /* Current request ID */
787 show_header
= 1, /* Show the test header? */
788 ignore_errors
, /* Ignore test failures? */
789 skip_previous
= 0, /* Skip on previous test failure? */
790 repeat_count
, /* Repeat count */
791 repeat_interval
, /* Repeat interval */
792 repeat_prev
, /* Previous repeat interval */
793 repeat_test
; /* Repeat a test? */
794 http_t
*http
= NULL
; /* HTTP connection to server */
795 FILE *fp
= NULL
; /* Test file */
796 char resource
[512], /* Resource for request */
797 token
[1024], /* Token from file */
798 *tokenptr
, /* Pointer into token */
799 temp
[1024], /* Temporary string */
800 buffer
[8192], /* Copy buffer */
801 compression
[16]; /* COMPRESSION value */
802 ipp_t
*request
= NULL
, /* IPP request */
803 *response
= NULL
; /* IPP response */
804 size_t length
; /* Length of IPP request */
805 http_status_t status
; /* HTTP status */
806 cups_file_t
*reqfile
; /* File to send */
807 ssize_t bytes
; /* Bytes read/written */
808 char attr
[128]; /* Attribute name */
809 ipp_op_t op
; /* Operation */
810 ipp_tag_t group
; /* Current group */
811 ipp_tag_t value
; /* Current value type */
812 ipp_attribute_t
*attrptr
, /* Attribute pointer */
813 *found
, /* Found attribute */
814 *lastcol
= NULL
; /* Last collection attribute */
815 char name
[1024], /* Name of test */
816 file_id
[1024], /* File identifier */
817 test_id
[1024]; /* Test identifier */
818 char filename
[1024]; /* Filename */
819 _cups_transfer_t transfer
; /* To chunk or not to chunk */
820 int version
, /* IPP version number to use */
821 skip_test
; /* Skip this test? */
822 int num_statuses
= 0; /* Number of valid status codes */
823 _cups_status_t statuses
[100], /* Valid status codes */
824 *last_status
; /* Last STATUS (for predicates) */
825 int num_expects
= 0; /* Number of expected attributes */
826 _cups_expect_t expects
[200], /* Expected attributes */
827 *expect
, /* Current expected attribute */
828 *last_expect
; /* Last EXPECT (for predicates) */
829 int num_displayed
= 0; /* Number of displayed attributes */
830 char *displayed
[200]; /* Displayed attributes */
831 size_t widths
[200]; /* Width of columns */
832 cups_array_t
*a
, /* Duplicate attribute array */
833 *errors
= NULL
; /* Errors array */
834 const char *error
; /* Current error */
838 * Open the test file...
841 if ((fp
= fopen(testfile
, "r")) == NULL
)
843 print_fatal_error("Unable to open test file %s - %s", testfile
,
850 * Connect to the server...
853 if ((http
= httpConnect2(vars
->hostname
, vars
->port
, NULL
, vars
->family
,
854 vars
->encryption
, 1, 30000, NULL
)) == NULL
)
856 print_fatal_error("Unable to connect to %s on port %d - %s", vars
->hostname
,
857 vars
->port
, cupsLastErrorString());
863 httpSetDefaultField(http
, HTTP_FIELD_ACCEPT_ENCODING
,
864 "deflate, gzip, identity");
866 httpSetDefaultField(http
, HTTP_FIELD_ACCEPT_ENCODING
, "identity");
867 #endif /* HAVE_LIBZ */
869 if (vars
->timeout
> 0.0)
870 httpSetTimeout(http
, vars
->timeout
, timeout_cb
, NULL
);
876 CUPS_SRAND(time(NULL
));
878 errors
= cupsArrayNew3(NULL
, NULL
, NULL
, 0, (cups_acopy_func_t
)strdup
,
879 (cups_afree_func_t
)free
);
883 request_id
= (CUPS_RAND() % 1000) * 137 + 1;
885 while (!Cancel
&& get_token(fp
, token
, sizeof(token
), &linenum
) != NULL
)
888 * Expect an open brace...
891 if (!strcmp(token
, "DEFINE"))
897 if (get_token(fp
, attr
, sizeof(attr
), &linenum
) &&
898 get_token(fp
, temp
, sizeof(temp
), &linenum
))
900 expand_variables(vars
, token
, temp
, sizeof(token
));
901 set_variable(vars
, attr
, token
);
905 print_fatal_error("Missing DEFINE name and/or value on line %d.",
913 else if (!strcmp(token
, "DEFINE-DEFAULT"))
916 * DEFINE-DEFAULT name value
919 if (get_token(fp
, attr
, sizeof(attr
), &linenum
) &&
920 get_token(fp
, temp
, sizeof(temp
), &linenum
))
922 expand_variables(vars
, token
, temp
, sizeof(token
));
923 if (!get_variable(vars
, attr
))
924 set_variable(vars
, attr
, token
);
928 print_fatal_error("Missing DEFINE-DEFAULT name and/or value on line "
936 else if (!strcmp(token
, "FILE-ID"))
942 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
944 expand_variables(vars
, file_id
, temp
, sizeof(file_id
));
948 print_fatal_error("Missing FILE-ID value on line %d.", linenum
);
955 else if (!strcmp(token
, "IGNORE-ERRORS"))
962 if (get_token(fp
, temp
, sizeof(temp
), &linenum
) &&
963 (!_cups_strcasecmp(temp
, "yes") || !_cups_strcasecmp(temp
, "no")))
965 IgnoreErrors
= !_cups_strcasecmp(temp
, "yes");
969 print_fatal_error("Missing IGNORE-ERRORS value on line %d.", linenum
);
976 else if (!strcmp(token
, "INCLUDE"))
983 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
986 * Map the filename to and then run the tests...
989 if (!do_tests(vars
, get_filename(testfile
, filename
, temp
,
994 if (StopAfterIncludeError
)
1000 print_fatal_error("Missing INCLUDE filename on line %d.", linenum
);
1008 else if (!strcmp(token
, "INCLUDE-IF-DEFINED"))
1011 * INCLUDE-IF-DEFINED name "filename"
1012 * INCLUDE-IF-DEFINED name <filename>
1015 if (get_token(fp
, attr
, sizeof(attr
), &linenum
) &&
1016 get_token(fp
, temp
, sizeof(temp
), &linenum
))
1019 * Map the filename to and then run the tests...
1022 if (get_variable(vars
, attr
) &&
1023 !do_tests(vars
, get_filename(testfile
, filename
, temp
,
1028 if (StopAfterIncludeError
)
1034 print_fatal_error("Missing INCLUDE-IF-DEFINED name or filename on line "
1043 else if (!strcmp(token
, "INCLUDE-IF-NOT-DEFINED"))
1046 * INCLUDE-IF-NOT-DEFINED name "filename"
1047 * INCLUDE-IF-NOT-DEFINED name <filename>
1050 if (get_token(fp
, attr
, sizeof(attr
), &linenum
) &&
1051 get_token(fp
, temp
, sizeof(temp
), &linenum
))
1054 * Map the filename to and then run the tests...
1057 if (!get_variable(vars
, attr
) &&
1058 !do_tests(vars
, get_filename(testfile
, filename
, temp
,
1063 if (StopAfterIncludeError
)
1069 print_fatal_error("Missing INCLUDE-IF-NOT-DEFINED name or filename on "
1070 "line %d.", linenum
);
1078 else if (!strcmp(token
, "SKIP-IF-DEFINED"))
1081 * SKIP-IF-DEFINED variable
1084 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1086 if (get_variable(vars
, temp
))
1091 print_fatal_error("Missing SKIP-IF-DEFINED variable on line %d.",
1097 else if (!strcmp(token
, "SKIP-IF-NOT-DEFINED"))
1100 * SKIP-IF-NOT-DEFINED variable
1103 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1105 if (!get_variable(vars
, temp
))
1110 print_fatal_error("Missing SKIP-IF-NOT-DEFINED variable on line %d.",
1116 else if (!strcmp(token
, "STOP-AFTER-INCLUDE-ERROR"))
1119 * STOP-AFTER-INCLUDE-ERROR yes
1120 * STOP-AFTER-INCLUDE-ERROR no
1123 if (get_token(fp
, temp
, sizeof(temp
), &linenum
) &&
1124 (!_cups_strcasecmp(temp
, "yes") || !_cups_strcasecmp(temp
, "no")))
1126 StopAfterIncludeError
= !_cups_strcasecmp(temp
, "yes");
1130 print_fatal_error("Missing STOP-AFTER-INCLUDE-ERROR value on line %d.",
1138 else if (!strcmp(token
, "TRANSFER"))
1146 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1148 if (!strcmp(temp
, "auto"))
1149 Transfer
= _CUPS_TRANSFER_AUTO
;
1150 else if (!strcmp(temp
, "chunked"))
1151 Transfer
= _CUPS_TRANSFER_CHUNKED
;
1152 else if (!strcmp(temp
, "length"))
1153 Transfer
= _CUPS_TRANSFER_LENGTH
;
1156 print_fatal_error("Bad TRANSFER value \"%s\" on line %d.", temp
,
1164 print_fatal_error("Missing TRANSFER value on line %d.", linenum
);
1171 else if (!strcmp(token
, "VERSION"))
1173 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1175 if (!strcmp(temp
, "1.0"))
1177 else if (!strcmp(temp
, "1.1"))
1179 else if (!strcmp(temp
, "2.0"))
1181 else if (!strcmp(temp
, "2.1"))
1183 else if (!strcmp(temp
, "2.2"))
1187 print_fatal_error("Bad VERSION \"%s\" on line %d.", temp
, linenum
);
1194 print_fatal_error("Missing VERSION number on line %d.", linenum
);
1201 else if (strcmp(token
, "{"))
1203 print_fatal_error("Unexpected token %s seen on line %d.", token
, linenum
);
1209 * Initialize things...
1214 if (Output
== _CUPS_OUTPUT_PLIST
)
1216 else if (Output
== _CUPS_OUTPUT_TEST
)
1217 printf("\"%s\":\n", testfile
);
1222 strlcpy(resource
, vars
->resource
, sizeof(resource
));
1227 group
= IPP_TAG_ZERO
;
1228 ignore_errors
= IgnoreErrors
;
1236 transfer
= Transfer
;
1237 compression
[0] = '\0';
1239 strlcpy(name
, testfile
, sizeof(name
));
1240 if (strrchr(name
, '.') != NULL
)
1241 *strrchr(name
, '.') = '\0';
1244 * Parse until we see a close brace...
1247 while (get_token(fp
, token
, sizeof(token
), &linenum
) != NULL
)
1249 if (_cups_strcasecmp(token
, "COUNT") &&
1250 _cups_strcasecmp(token
, "DEFINE-MATCH") &&
1251 _cups_strcasecmp(token
, "DEFINE-NO-MATCH") &&
1252 _cups_strcasecmp(token
, "DEFINE-VALUE") &&
1253 _cups_strcasecmp(token
, "IF-DEFINED") &&
1254 _cups_strcasecmp(token
, "IF-NOT-DEFINED") &&
1255 _cups_strcasecmp(token
, "IN-GROUP") &&
1256 _cups_strcasecmp(token
, "OF-TYPE") &&
1257 _cups_strcasecmp(token
, "REPEAT-LIMIT") &&
1258 _cups_strcasecmp(token
, "REPEAT-MATCH") &&
1259 _cups_strcasecmp(token
, "REPEAT-NO-MATCH") &&
1260 _cups_strcasecmp(token
, "SAME-COUNT-AS") &&
1261 _cups_strcasecmp(token
, "WITH-ALL-VALUES") &&
1262 _cups_strcasecmp(token
, "WITH-ALL-HOSTNAMES") &&
1263 _cups_strcasecmp(token
, "WITH-ALL-RESOURCES") &&
1264 _cups_strcasecmp(token
, "WITH-ALL-SCHEMES") &&
1265 _cups_strcasecmp(token
, "WITH-HOSTNAME") &&
1266 _cups_strcasecmp(token
, "WITH-RESOURCE") &&
1267 _cups_strcasecmp(token
, "WITH-SCHEME") &&
1268 _cups_strcasecmp(token
, "WITH-VALUE"))
1271 if (_cups_strcasecmp(token
, "DEFINE-MATCH") &&
1272 _cups_strcasecmp(token
, "DEFINE-NO-MATCH") &&
1273 _cups_strcasecmp(token
, "IF-DEFINED") &&
1274 _cups_strcasecmp(token
, "IF-NOT-DEFINED") &&
1275 _cups_strcasecmp(token
, "REPEAT-LIMIT") &&
1276 _cups_strcasecmp(token
, "REPEAT-MATCH") &&
1277 _cups_strcasecmp(token
, "REPEAT-NO-MATCH"))
1280 if (!strcmp(token
, "}"))
1282 else if (!strcmp(token
, "{") && lastcol
)
1285 * Another collection value
1288 ipp_t
*col
= get_collection(vars
, fp
, &linenum
);
1289 /* Collection value */
1293 ipp_attribute_t
*tempcol
; /* Pointer to new buffer */
1297 * Reallocate memory...
1300 if ((tempcol
= realloc(lastcol
, sizeof(ipp_attribute_t
) +
1301 (lastcol
->num_values
+ 1) *
1302 sizeof(_ipp_value_t
))) == NULL
)
1304 print_fatal_error("Unable to allocate memory on line %d.", linenum
);
1309 if (tempcol
!= lastcol
)
1312 * Reset pointers in the list...
1316 request
->prev
->next
= tempcol
;
1318 request
->attrs
= tempcol
;
1320 lastcol
= request
->current
= request
->last
= tempcol
;
1323 lastcol
->values
[lastcol
->num_values
].collection
= col
;
1324 lastcol
->num_values
++;
1332 else if (!strcmp(token
, "COMPRESSION"))
1336 * COMPRESSION deflate
1340 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1342 expand_variables(vars
, compression
, temp
, sizeof(compression
));
1344 if (strcmp(compression
, "none") && strcmp(compression
, "deflate") &&
1345 strcmp(compression
, "gzip"))
1347 if (strcmp(compression
, "none"))
1348 #endif /* HAVE_LIBZ */
1350 print_fatal_error("Unsupported COMPRESSION value '%s' on line %d.",
1351 compression
, linenum
);
1356 if (!strcmp(compression
, "none"))
1357 compression
[0] = '\0';
1361 print_fatal_error("Missing COMPRESSION value on line %d.", linenum
);
1366 else if (!strcmp(token
, "DEFINE"))
1372 if (get_token(fp
, attr
, sizeof(attr
), &linenum
) &&
1373 get_token(fp
, temp
, sizeof(temp
), &linenum
))
1375 expand_variables(vars
, token
, temp
, sizeof(token
));
1376 set_variable(vars
, attr
, token
);
1380 print_fatal_error("Missing DEFINE name and/or value on line %d.",
1386 else if (!strcmp(token
, "IGNORE-ERRORS"))
1393 if (get_token(fp
, temp
, sizeof(temp
), &linenum
) &&
1394 (!_cups_strcasecmp(temp
, "yes") || !_cups_strcasecmp(temp
, "no")))
1396 ignore_errors
= !_cups_strcasecmp(temp
, "yes");
1400 print_fatal_error("Missing IGNORE-ERRORS value on line %d.", linenum
);
1407 else if (!_cups_strcasecmp(token
, "NAME"))
1413 get_token(fp
, name
, sizeof(name
), &linenum
);
1415 else if (!strcmp(token
, "REQUEST-ID"))
1422 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1424 if (isdigit(temp
[0] & 255))
1425 request_id
= atoi(temp
);
1426 else if (!_cups_strcasecmp(temp
, "random"))
1427 request_id
= (CUPS_RAND() % 1000) * 137 + 1;
1430 print_fatal_error("Bad REQUEST-ID value \"%s\" on line %d.", temp
,
1438 print_fatal_error("Missing REQUEST-ID value on line %d.", linenum
);
1443 else if (!strcmp(token
, "SKIP-IF-DEFINED"))
1446 * SKIP-IF-DEFINED variable
1449 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1451 if (get_variable(vars
, temp
))
1456 print_fatal_error("Missing SKIP-IF-DEFINED value on line %d.",
1462 else if (!strcmp(token
, "SKIP-IF-MISSING"))
1465 * SKIP-IF-MISSING filename
1468 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1470 expand_variables(vars
, token
, temp
, sizeof(token
));
1471 get_filename(testfile
, filename
, token
, sizeof(filename
));
1473 if (access(filename
, R_OK
))
1478 print_fatal_error("Missing SKIP-IF-MISSING filename on line %d.",
1484 else if (!strcmp(token
, "SKIP-IF-NOT-DEFINED"))
1487 * SKIP-IF-NOT-DEFINED variable
1490 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1492 if (!get_variable(vars
, temp
))
1497 print_fatal_error("Missing SKIP-IF-NOT-DEFINED value on line %d.",
1503 else if (!strcmp(token
, "SKIP-PREVIOUS-ERROR"))
1506 * SKIP-PREVIOUS-ERROR yes
1507 * SKIP-PREVIOUS-ERROR no
1510 if (get_token(fp
, temp
, sizeof(temp
), &linenum
) &&
1511 (!_cups_strcasecmp(temp
, "yes") || !_cups_strcasecmp(temp
, "no")))
1513 skip_previous
= !_cups_strcasecmp(temp
, "yes");
1517 print_fatal_error("Missing SKIP-PREVIOUS-ERROR value on line %d.", linenum
);
1524 else if (!strcmp(token
, "TEST-ID"))
1530 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1532 expand_variables(vars
, test_id
, temp
, sizeof(test_id
));
1536 print_fatal_error("Missing TEST-ID value on line %d.", linenum
);
1543 else if (!strcmp(token
, "TRANSFER"))
1551 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1553 if (!strcmp(temp
, "auto"))
1554 transfer
= _CUPS_TRANSFER_AUTO
;
1555 else if (!strcmp(temp
, "chunked"))
1556 transfer
= _CUPS_TRANSFER_CHUNKED
;
1557 else if (!strcmp(temp
, "length"))
1558 transfer
= _CUPS_TRANSFER_LENGTH
;
1561 print_fatal_error("Bad TRANSFER value \"%s\" on line %d.", temp
,
1569 print_fatal_error("Missing TRANSFER value on line %d.", linenum
);
1574 else if (!_cups_strcasecmp(token
, "VERSION"))
1576 if (get_token(fp
, temp
, sizeof(temp
), &linenum
))
1578 if (!strcmp(temp
, "0.0"))
1580 else if (!strcmp(temp
, "1.0"))
1582 else if (!strcmp(temp
, "1.1"))
1584 else if (!strcmp(temp
, "2.0"))
1586 else if (!strcmp(temp
, "2.1"))
1588 else if (!strcmp(temp
, "2.2"))
1592 print_fatal_error("Bad VERSION \"%s\" on line %d.", temp
, linenum
);
1599 print_fatal_error("Missing VERSION number on line %d.", linenum
);
1604 else if (!_cups_strcasecmp(token
, "RESOURCE"))
1610 if (!get_token(fp
, resource
, sizeof(resource
), &linenum
))
1612 print_fatal_error("Missing RESOURCE path on line %d.", linenum
);
1617 else if (!_cups_strcasecmp(token
, "OPERATION"))
1623 if (!get_token(fp
, temp
, sizeof(temp
), &linenum
))
1625 print_fatal_error("Missing OPERATION code on line %d.", linenum
);
1630 expand_variables(vars
, token
, temp
, sizeof(token
));
1632 if ((op
= ippOpValue(token
)) == (ipp_op_t
)-1 &&
1633 (op
= strtol(token
, NULL
, 0)) == 0)
1635 print_fatal_error("Bad OPERATION code \"%s\" on line %d.", token
,
1641 else if (!_cups_strcasecmp(token
, "GROUP"))
1644 * Attribute group...
1647 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
1649 print_fatal_error("Missing GROUP tag on line %d.", linenum
);
1654 if ((value
= ippTagValue(token
)) < 0)
1656 print_fatal_error("Bad GROUP tag \"%s\" on line %d.", token
, linenum
);
1662 ippAddSeparator(request
);
1666 else if (!_cups_strcasecmp(token
, "DELAY"))
1669 * Delay before operation...
1674 if (!get_token(fp
, temp
, sizeof(temp
), &linenum
))
1676 print_fatal_error("Missing DELAY value on line %d.", linenum
);
1681 expand_variables(vars
, token
, temp
, sizeof(token
));
1683 if ((delay
= _cupsStrScand(token
, NULL
, localeconv())) <= 0.0)
1685 print_fatal_error("Bad DELAY value \"%s\" on line %d.", token
,
1692 if (Output
== _CUPS_OUTPUT_TEST
)
1693 printf(" [%g second delay]\n", delay
);
1695 usleep((int)(1000000.0 * delay
));
1698 else if (!_cups_strcasecmp(token
, "ATTR"))
1704 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
1706 print_fatal_error("Missing ATTR value tag on line %d.", linenum
);
1711 if ((value
= ippTagValue(token
)) == IPP_TAG_ZERO
)
1713 print_fatal_error("Bad ATTR value tag \"%s\" on line %d.", token
,
1719 if (!get_token(fp
, attr
, sizeof(attr
), &linenum
))
1721 print_fatal_error("Missing ATTR name on line %d.", linenum
);
1726 if (!get_token(fp
, temp
, sizeof(temp
), &linenum
))
1728 print_fatal_error("Missing ATTR value on line %d.", linenum
);
1733 expand_variables(vars
, token
, temp
, sizeof(token
));
1738 case IPP_TAG_BOOLEAN
:
1739 if (!_cups_strcasecmp(token
, "true"))
1740 attrptr
= ippAddBoolean(request
, group
, attr
, 1);
1742 attrptr
= ippAddBoolean(request
, group
, attr
, atoi(token
));
1745 case IPP_TAG_INTEGER
:
1747 if (!strchr(token
, ','))
1748 attrptr
= ippAddInteger(request
, group
, value
, attr
,
1749 strtol(token
, &tokenptr
, 0));
1752 int values
[100], /* Values */
1753 num_values
= 1; /* Number of values */
1755 values
[0] = strtol(token
, &tokenptr
, 10);
1756 while (tokenptr
&& *tokenptr
&&
1757 num_values
< (int)(sizeof(values
) / sizeof(values
[0])))
1759 if (*tokenptr
== ',')
1761 else if (!isdigit(*tokenptr
& 255) && *tokenptr
!= '-')
1764 values
[num_values
] = strtol(tokenptr
, &tokenptr
, 0);
1768 attrptr
= ippAddIntegers(request
, group
, value
, attr
, num_values
, values
);
1771 if (!tokenptr
|| *tokenptr
)
1773 print_fatal_error("Bad %s value \"%s\" on line %d.",
1774 ippTagString(value
), token
, linenum
);
1780 case IPP_TAG_RESOLUTION
:
1782 int xres
, /* X resolution */
1783 yres
; /* Y resolution */
1784 char *ptr
; /* Pointer into value */
1786 xres
= yres
= strtol(token
, (char **)&ptr
, 10);
1787 if (ptr
> token
&& xres
> 0)
1790 yres
= strtol(ptr
+ 1, (char **)&ptr
, 10);
1793 if (ptr
<= token
|| xres
<= 0 || yres
<= 0 || !ptr
||
1794 (_cups_strcasecmp(ptr
, "dpi") &&
1795 _cups_strcasecmp(ptr
, "dpc") &&
1796 _cups_strcasecmp(ptr
, "dpcm") &&
1797 _cups_strcasecmp(ptr
, "other")))
1799 print_fatal_error("Bad resolution value \"%s\" on line %d.",
1805 if (!_cups_strcasecmp(ptr
, "dpi"))
1806 attrptr
= ippAddResolution(request
, group
, attr
, IPP_RES_PER_INCH
,
1808 else if (!_cups_strcasecmp(ptr
, "dpc") ||
1809 !_cups_strcasecmp(ptr
, "dpcm"))
1810 attrptr
= ippAddResolution(request
, group
, attr
, IPP_RES_PER_CM
,
1813 attrptr
= ippAddResolution(request
, group
, attr
, (ipp_res_t
)0,
1818 case IPP_TAG_RANGE
:
1820 int lowers
[4], /* Lower value */
1821 uppers
[4], /* Upper values */
1822 num_vals
; /* Number of values */
1825 num_vals
= sscanf(token
, "%d-%d,%d-%d,%d-%d,%d-%d",
1826 lowers
+ 0, uppers
+ 0,
1827 lowers
+ 1, uppers
+ 1,
1828 lowers
+ 2, uppers
+ 2,
1829 lowers
+ 3, uppers
+ 3);
1831 if ((num_vals
& 1) || num_vals
== 0)
1833 print_fatal_error("Bad rangeOfInteger value \"%s\" on line "
1834 "%d.", token
, linenum
);
1839 attrptr
= ippAddRanges(request
, group
, attr
, num_vals
/ 2, lowers
,
1844 case IPP_TAG_BEGIN_COLLECTION
:
1845 if (!strcmp(token
, "{"))
1847 ipp_t
*col
= get_collection(vars
, fp
, &linenum
);
1848 /* Collection value */
1852 attrptr
= lastcol
= ippAddCollection(request
, group
, attr
, col
);
1863 print_fatal_error("Bad ATTR collection value on line %d.",
1870 case IPP_TAG_STRING
:
1871 attrptr
= ippAddOctetString(request
, group
, attr
, token
,
1876 print_fatal_error("Unsupported ATTR value tag %s on line %d.",
1877 ippTagString(value
), linenum
);
1881 case IPP_TAG_TEXTLANG
:
1882 case IPP_TAG_NAMELANG
:
1885 case IPP_TAG_KEYWORD
:
1887 case IPP_TAG_URISCHEME
:
1888 case IPP_TAG_CHARSET
:
1889 case IPP_TAG_LANGUAGE
:
1890 case IPP_TAG_MIMETYPE
:
1891 if (!strchr(token
, ','))
1892 attrptr
= ippAddString(request
, group
, value
, attr
, NULL
, token
);
1896 * Multiple string values...
1899 int num_values
; /* Number of values */
1900 char *values
[100], /* Values */
1901 *ptr
; /* Pointer to next value */
1907 for (ptr
= strchr(token
, ','); ptr
; ptr
= strchr(ptr
, ','))
1909 if (ptr
> token
&& ptr
[-1] == '\\')
1910 _cups_strcpy(ptr
- 1, ptr
);
1914 values
[num_values
] = ptr
;
1919 attrptr
= ippAddStrings(request
, group
, value
, attr
, num_values
,
1920 NULL
, (const char **)values
);
1927 print_fatal_error("Unable to add attribute on line %d: %s", linenum
,
1928 cupsLastErrorString());
1933 else if (!_cups_strcasecmp(token
, "FILE"))
1939 if (!get_token(fp
, temp
, sizeof(temp
), &linenum
))
1941 print_fatal_error("Missing FILE filename on line %d.", linenum
);
1946 expand_variables(vars
, token
, temp
, sizeof(token
));
1947 get_filename(testfile
, filename
, token
, sizeof(filename
));
1949 if (access(filename
, R_OK
))
1951 print_fatal_error("Filename \"%s\" on line %d cannot be read.",
1953 print_fatal_error("Filename mapped to \"%s\".", filename
);
1958 else if (!_cups_strcasecmp(token
, "STATUS"))
1964 if (num_statuses
>= (int)(sizeof(statuses
) / sizeof(statuses
[0])))
1966 print_fatal_error("Too many STATUS's on line %d.", linenum
);
1971 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
1973 print_fatal_error("Missing STATUS code on line %d.", linenum
);
1978 if ((statuses
[num_statuses
].status
= ippErrorValue(token
))
1979 == (ipp_status_t
)-1 &&
1980 (statuses
[num_statuses
].status
= strtol(token
, NULL
, 0)) == 0)
1982 print_fatal_error("Bad STATUS code \"%s\" on line %d.", token
,
1988 last_status
= statuses
+ num_statuses
;
1991 last_status
->define_match
= NULL
;
1992 last_status
->define_no_match
= NULL
;
1993 last_status
->if_defined
= NULL
;
1994 last_status
->if_not_defined
= NULL
;
1995 last_status
->repeat_limit
= 1000;
1996 last_status
->repeat_match
= 0;
1997 last_status
->repeat_no_match
= 0;
1999 else if (!_cups_strcasecmp(token
, "EXPECT"))
2002 * Expected attributes...
2005 if (num_expects
>= (int)(sizeof(expects
) / sizeof(expects
[0])))
2007 print_fatal_error("Too many EXPECT's on line %d.", linenum
);
2012 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2014 print_fatal_error("Missing EXPECT name on line %d.", linenum
);
2019 last_expect
= expects
+ num_expects
;
2022 memset(last_expect
, 0, sizeof(_cups_expect_t
));
2023 last_expect
->repeat_limit
= 1000;
2025 if (token
[0] == '!')
2027 last_expect
->not_expect
= 1;
2028 last_expect
->name
= strdup(token
+ 1);
2030 else if (token
[0] == '?')
2032 last_expect
->optional
= 1;
2033 last_expect
->name
= strdup(token
+ 1);
2036 last_expect
->name
= strdup(token
);
2038 else if (!_cups_strcasecmp(token
, "COUNT"))
2040 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2042 print_fatal_error("Missing COUNT number on line %d.", linenum
);
2047 if ((i
= atoi(token
)) <= 0)
2049 print_fatal_error("Bad COUNT \"%s\" on line %d.", token
, linenum
);
2055 last_expect
->count
= i
;
2058 print_fatal_error("COUNT without a preceding EXPECT on line %d.",
2064 else if (!_cups_strcasecmp(token
, "DEFINE-MATCH"))
2066 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2068 print_fatal_error("Missing DEFINE-MATCH variable on line %d.",
2075 last_expect
->define_match
= strdup(token
);
2076 else if (last_status
)
2077 last_status
->define_match
= strdup(token
);
2080 print_fatal_error("DEFINE-MATCH without a preceding EXPECT or STATUS "
2081 "on line %d.", linenum
);
2086 else if (!_cups_strcasecmp(token
, "DEFINE-NO-MATCH"))
2088 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2090 print_fatal_error("Missing DEFINE-NO-MATCH variable on line %d.",
2097 last_expect
->define_no_match
= strdup(token
);
2098 else if (last_status
)
2099 last_status
->define_no_match
= strdup(token
);
2102 print_fatal_error("DEFINE-NO-MATCH without a preceding EXPECT or "
2103 "STATUS on line %d.", linenum
);
2108 else if (!_cups_strcasecmp(token
, "DEFINE-VALUE"))
2110 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2112 print_fatal_error("Missing DEFINE-VALUE variable on line %d.",
2119 last_expect
->define_value
= strdup(token
);
2122 print_fatal_error("DEFINE-VALUE without a preceding EXPECT on "
2123 "line %d.", linenum
);
2128 else if (!_cups_strcasecmp(token
, "OF-TYPE"))
2130 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2132 print_fatal_error("Missing OF-TYPE value tag(s) on line %d.",
2139 last_expect
->of_type
= strdup(token
);
2142 print_fatal_error("OF-TYPE without a preceding EXPECT on line %d.",
2148 else if (!_cups_strcasecmp(token
, "IN-GROUP"))
2150 ipp_tag_t in_group
; /* IN-GROUP value */
2153 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2155 print_fatal_error("Missing IN-GROUP group tag on line %d.", linenum
);
2160 if ((in_group
= ippTagValue(token
)) == (ipp_tag_t
)-1)
2163 else if (last_expect
)
2164 last_expect
->in_group
= in_group
;
2167 print_fatal_error("IN-GROUP without a preceding EXPECT on line %d.",
2173 else if (!_cups_strcasecmp(token
, "REPEAT-LIMIT"))
2175 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2177 print_fatal_error("Missing REPEAT-LIMIT value on line %d.", linenum
);
2181 else if (atoi(token
) <= 0)
2183 print_fatal_error("Bad REPEAT-LIMIT value on line %d.", linenum
);
2189 last_status
->repeat_limit
= atoi(token
);
2190 else if (last_expect
)
2191 last_expect
->repeat_limit
= atoi(token
);
2194 print_fatal_error("REPEAT-LIMIT without a preceding EXPECT or STATUS "
2195 "on line %d.", linenum
);
2200 else if (!_cups_strcasecmp(token
, "REPEAT-MATCH"))
2203 last_status
->repeat_match
= 1;
2204 else if (last_expect
)
2205 last_expect
->repeat_match
= 1;
2208 print_fatal_error("REPEAT-MATCH without a preceding EXPECT or STATUS "
2209 "on line %d.", linenum
);
2214 else if (!_cups_strcasecmp(token
, "REPEAT-NO-MATCH"))
2217 last_status
->repeat_no_match
= 1;
2218 else if (last_expect
)
2219 last_expect
->repeat_no_match
= 1;
2222 print_fatal_error("REPEAT-NO-MATCH without a preceding EXPECT or "
2223 "STATUS on ine %d.", linenum
);
2228 else if (!_cups_strcasecmp(token
, "SAME-COUNT-AS"))
2230 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2232 print_fatal_error("Missing SAME-COUNT-AS name on line %d.", linenum
);
2238 last_expect
->same_count_as
= strdup(token
);
2241 print_fatal_error("SAME-COUNT-AS without a preceding EXPECT on line "
2247 else if (!_cups_strcasecmp(token
, "IF-DEFINED"))
2249 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2251 print_fatal_error("Missing IF-DEFINED name on line %d.", linenum
);
2257 last_expect
->if_defined
= strdup(token
);
2258 else if (last_status
)
2259 last_status
->if_defined
= strdup(token
);
2262 print_fatal_error("IF-DEFINED without a preceding EXPECT or STATUS "
2263 "on line %d.", linenum
);
2268 else if (!_cups_strcasecmp(token
, "IF-NOT-DEFINED"))
2270 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2272 print_fatal_error("Missing IF-NOT-DEFINED name on line %d.", linenum
);
2278 last_expect
->if_not_defined
= strdup(token
);
2279 else if (last_status
)
2280 last_status
->if_not_defined
= strdup(token
);
2283 print_fatal_error("IF-NOT-DEFINED without a preceding EXPECT or STATUS "
2284 "on line %d.", linenum
);
2289 else if (!_cups_strcasecmp(token
, "WITH-ALL-VALUES") ||
2290 !_cups_strcasecmp(token
, "WITH-ALL-HOSTNAMES") ||
2291 !_cups_strcasecmp(token
, "WITH-ALL-RESOURCES") ||
2292 !_cups_strcasecmp(token
, "WITH-ALL-SCHEMES") ||
2293 !_cups_strcasecmp(token
, "WITH-HOSTNAME") ||
2294 !_cups_strcasecmp(token
, "WITH-RESOURCE") ||
2295 !_cups_strcasecmp(token
, "WITH-SCHEME") ||
2296 !_cups_strcasecmp(token
, "WITH-VALUE"))
2300 if (!_cups_strcasecmp(token
, "WITH-ALL-HOSTNAMES") ||
2301 !_cups_strcasecmp(token
, "WITH-HOSTNAME"))
2302 last_expect
->with_flags
= _CUPS_WITH_HOSTNAME
;
2303 else if (!_cups_strcasecmp(token
, "WITH-ALL-RESOURCES") ||
2304 !_cups_strcasecmp(token
, "WITH-RESOURCE"))
2305 last_expect
->with_flags
= _CUPS_WITH_RESOURCE
;
2306 else if (!_cups_strcasecmp(token
, "WITH-ALL-SCHEMES") ||
2307 !_cups_strcasecmp(token
, "WITH-SCHEME"))
2308 last_expect
->with_flags
= _CUPS_WITH_SCHEME
;
2310 if (!_cups_strncasecmp(token
, "WITH-ALL-", 9))
2311 last_expect
->with_flags
|= _CUPS_WITH_ALL
;
2314 if (!get_token(fp
, temp
, sizeof(temp
), &linenum
))
2316 print_fatal_error("Missing %s value on line %d.", token
, linenum
);
2324 * Expand any variables in the value and then save it.
2327 expand_variables(vars
, token
, temp
, sizeof(token
));
2329 tokenptr
= token
+ strlen(token
) - 1;
2331 if (token
[0] == '/' && tokenptr
> token
&& *tokenptr
== '/')
2334 * WITH-VALUE is a POSIX extended regular expression.
2337 last_expect
->with_value
= calloc(1, tokenptr
- token
);
2338 last_expect
->with_flags
|= _CUPS_WITH_REGEX
;
2340 if (last_expect
->with_value
)
2341 memcpy(last_expect
->with_value
, token
+ 1, tokenptr
- token
- 1);
2346 * WITH-VALUE is a literal value...
2349 char *ptr
; /* Pointer into value */
2351 for (ptr
= token
; *ptr
; ptr
++)
2353 if (*ptr
== '\\' && ptr
[1])
2356 * Remove \ from \foo...
2359 _cups_strcpy(ptr
, ptr
+ 1);
2363 last_expect
->with_value
= strdup(token
);
2364 last_expect
->with_flags
|= _CUPS_WITH_LITERAL
;
2369 print_fatal_error("%s without a preceding EXPECT on line %d.", token
,
2375 else if (!_cups_strcasecmp(token
, "DISPLAY"))
2378 * Display attributes...
2381 if (num_displayed
>= (int)(sizeof(displayed
) / sizeof(displayed
[0])))
2383 print_fatal_error("Too many DISPLAY's on line %d", linenum
);
2388 if (!get_token(fp
, token
, sizeof(token
), &linenum
))
2390 print_fatal_error("Missing DISPLAY name on line %d.", linenum
);
2395 displayed
[num_displayed
] = strdup(token
);
2400 print_fatal_error("Unexpected token %s seen on line %d.", token
,
2408 * Submit the IPP request...
2413 request
->request
.op
.version
[0] = version
/ 10;
2414 request
->request
.op
.version
[1] = version
% 10;
2415 request
->request
.op
.operation_id
= op
;
2416 request
->request
.op
.request_id
= request_id
;
2418 if (Output
== _CUPS_OUTPUT_PLIST
)
2421 puts("<key>Name</key>");
2422 print_xml_string("string", name
);
2425 puts("<key>FileId</key>");
2426 print_xml_string("string", file_id
);
2430 puts("<key>TestId</key>");
2431 print_xml_string("string", test_id
);
2433 puts("<key>Version</key>");
2434 printf("<string>%d.%d</string>\n", version
/ 10, version
% 10);
2435 puts("<key>Operation</key>");
2436 print_xml_string("string", ippOpString(op
));
2437 puts("<key>RequestId</key>");
2438 printf("<integer>%d</integer>\n", request_id
);
2439 puts("<key>RequestAttributes</key>");
2444 for (attrptr
= request
->attrs
,
2445 group
= attrptr
? attrptr
->group_tag
: IPP_TAG_ZERO
;
2447 attrptr
= attrptr
->next
)
2448 print_attr(attrptr
, &group
);
2453 else if (Output
== _CUPS_OUTPUT_TEST
)
2457 printf(" %s:\n", ippOpString(op
));
2459 for (attrptr
= request
->attrs
; attrptr
; attrptr
= attrptr
->next
)
2460 print_attr(attrptr
, NULL
);
2463 printf(" %-68.68s [", name
);
2467 if ((skip_previous
&& !prev_pass
) || skip_test
)
2474 if (Output
== _CUPS_OUTPUT_PLIST
)
2476 puts("<key>Successful</key>");
2478 puts("<key>StatusCode</key>");
2479 print_xml_string("string", "skip");
2480 puts("<key>ResponseAttributes</key>");
2483 else if (Output
== _CUPS_OUTPUT_TEST
)
2490 repeat_interval
= 1;
2499 if (transfer
== _CUPS_TRANSFER_CHUNKED
||
2500 (transfer
== _CUPS_TRANSFER_AUTO
&& filename
[0]))
2503 * Send request using chunking - a 0 length means "chunk".
2511 * Send request using content length...
2514 length
= ippLength(request
);
2516 if (filename
[0] && (reqfile
= cupsFileOpen(filename
, "r")) != NULL
)
2519 * Read the file to get the uncompressed file size...
2522 while ((bytes
= cupsFileRead(reqfile
, buffer
, sizeof(buffer
))) > 0)
2525 cupsFileClose(reqfile
);
2530 * Send the request...
2537 if (status
!= HTTP_ERROR
)
2539 while (!response
&& !Cancel
&& prev_pass
)
2541 status
= cupsSendRequest(http
, request
, resource
, length
);
2545 httpSetField(http
, HTTP_FIELD_CONTENT_ENCODING
, compression
);
2546 #endif /* HAVE_LIBZ */
2548 if (!Cancel
&& status
== HTTP_CONTINUE
&&
2549 request
->state
== IPP_DATA
&& filename
[0])
2551 if ((reqfile
= cupsFileOpen(filename
, "r")) != NULL
)
2554 (bytes
= cupsFileRead(reqfile
, buffer
,
2555 sizeof(buffer
))) > 0)
2556 if ((status
= cupsWriteRequestData(http
, buffer
,
2557 bytes
)) != HTTP_CONTINUE
)
2560 cupsFileClose(reqfile
);
2564 snprintf(buffer
, sizeof(buffer
), "%s: %s", filename
,
2566 _cupsSetError(IPP_INTERNAL_ERROR
, buffer
, 0);
2568 status
= HTTP_ERROR
;
2573 * Get the server's response...
2576 if (!Cancel
&& status
!= HTTP_ERROR
)
2578 response
= cupsGetResponse(http
, resource
);
2579 status
= httpGetStatus(http
);
2582 if (!Cancel
&& status
== HTTP_ERROR
&& http
->error
!= EINVAL
&&
2584 http
->error
!= WSAETIMEDOUT
)
2586 http
->error
!= ETIMEDOUT
)
2589 if (httpReconnect(http
))
2592 else if (status
== HTTP_ERROR
)
2597 else if (status
!= HTTP_OK
)
2601 if (status
== HTTP_STATUS_UNAUTHORIZED
)
2609 if (!Cancel
&& status
== HTTP_ERROR
&& http
->error
!= EINVAL
&&
2611 http
->error
!= WSAETIMEDOUT
)
2613 http
->error
!= ETIMEDOUT
)
2616 if (httpReconnect(http
))
2619 else if (status
== HTTP_ERROR
)
2622 httpReconnect(http
);
2626 else if (status
!= HTTP_OK
)
2633 * Check results of request...
2636 cupsArrayClear(errors
);
2638 if (http
->version
!= HTTP_1_1
)
2639 add_stringf(errors
, "Bad HTTP version (%d.%d)", http
->version
/ 100,
2640 http
->version
% 100);
2645 * No response, log error...
2648 add_stringf(errors
, "IPP request failed with status %s (%s)",
2649 ippErrorString(cupsLastError()),
2650 cupsLastErrorString());
2655 * Collect common attribute values...
2658 if ((attrptr
= ippFindAttribute(response
, "job-id",
2659 IPP_TAG_INTEGER
)) != NULL
)
2661 snprintf(temp
, sizeof(temp
), "%d", attrptr
->values
[0].integer
);
2662 set_variable(vars
, "job-id", temp
);
2665 if ((attrptr
= ippFindAttribute(response
, "job-uri",
2666 IPP_TAG_URI
)) != NULL
)
2667 set_variable(vars
, "job-uri", attrptr
->values
[0].string
.text
);
2669 if ((attrptr
= ippFindAttribute(response
, "notify-subscription-id",
2670 IPP_TAG_INTEGER
)) != NULL
)
2672 snprintf(temp
, sizeof(temp
), "%d", attrptr
->values
[0].integer
);
2673 set_variable(vars
, "notify-subscription-id", temp
);
2677 * Check response, validating groups and attributes and logging errors
2681 if (response
->state
!= IPP_DATA
)
2683 "Missing end-of-attributes-tag in response "
2684 "(RFC 2910 section 3.5.1)");
2687 (response
->request
.status
.version
[0] != (version
/ 10) ||
2688 response
->request
.status
.version
[1] != (version
% 10)))
2690 "Bad version %d.%d in response - expected %d.%d "
2691 "(RFC 2911 section 3.1.8).",
2692 response
->request
.status
.version
[0],
2693 response
->request
.status
.version
[1],
2694 version
/ 10, version
% 10);
2696 if (response
->request
.status
.request_id
!= request_id
)
2698 "Bad request ID %d in response - expected %d "
2699 "(RFC 2911 section 3.1.1)",
2700 response
->request
.status
.request_id
, request_id
);
2702 attrptr
= response
->attrs
;
2705 "Missing first attribute \"attributes-charset "
2706 "(charset)\" in group operation-attributes-tag "
2707 "(RFC 2911 section 3.1.4).");
2710 if (!attrptr
->name
||
2711 attrptr
->value_tag
!= IPP_TAG_CHARSET
||
2712 attrptr
->group_tag
!= IPP_TAG_OPERATION
||
2713 attrptr
->num_values
!= 1 ||
2714 strcmp(attrptr
->name
, "attributes-charset"))
2716 "Bad first attribute \"%s (%s%s)\" in group %s, "
2717 "expected \"attributes-charset (charset)\" in "
2718 "group operation-attributes-tag (RFC 2911 section "
2720 attrptr
->name
? attrptr
->name
: "(null)",
2721 attrptr
->num_values
> 1 ? "1setOf " : "",
2722 ippTagString(attrptr
->value_tag
),
2723 ippTagString(attrptr
->group_tag
));
2725 attrptr
= attrptr
->next
;
2728 "Missing second attribute \"attributes-natural-"
2729 "language (naturalLanguage)\" in group "
2730 "operation-attributes-tag (RFC 2911 section "
2732 else if (!attrptr
->name
||
2733 attrptr
->value_tag
!= IPP_TAG_LANGUAGE
||
2734 attrptr
->group_tag
!= IPP_TAG_OPERATION
||
2735 attrptr
->num_values
!= 1 ||
2736 strcmp(attrptr
->name
, "attributes-natural-language"))
2738 "Bad first attribute \"%s (%s%s)\" in group %s, "
2739 "expected \"attributes-natural-language "
2740 "(naturalLanguage)\" in group "
2741 "operation-attributes-tag (RFC 2911 section "
2743 attrptr
->name
? attrptr
->name
: "(null)",
2744 attrptr
->num_values
> 1 ? "1setOf " : "",
2745 ippTagString(attrptr
->value_tag
),
2746 ippTagString(attrptr
->group_tag
));
2749 if ((attrptr
= ippFindAttribute(response
, "status-message",
2750 IPP_TAG_ZERO
)) != NULL
)
2752 if (attrptr
->value_tag
!= IPP_TAG_TEXT
)
2754 "status-message (text(255)) has wrong value tag "
2755 "%s (RFC 2911 section 3.1.6.2).",
2756 ippTagString(attrptr
->value_tag
));
2757 if (attrptr
->group_tag
!= IPP_TAG_OPERATION
)
2759 "status-message (text(255)) has wrong group tag "
2760 "%s (RFC 2911 section 3.1.6.2).",
2761 ippTagString(attrptr
->group_tag
));
2762 if (attrptr
->num_values
!= 1)
2764 "status-message (text(255)) has %d values "
2765 "(RFC 2911 section 3.1.6.2).",
2766 attrptr
->num_values
);
2767 if (attrptr
->value_tag
== IPP_TAG_TEXT
&&
2768 strlen(attrptr
->values
[0].string
.text
) > 255)
2770 "status-message (text(255)) has bad length %d"
2771 " (RFC 2911 section 3.1.6.2).",
2772 (int)strlen(attrptr
->values
[0].string
.text
));
2775 if ((attrptr
= ippFindAttribute(response
, "detailed-status-message",
2776 IPP_TAG_ZERO
)) != NULL
)
2778 if (attrptr
->value_tag
!= IPP_TAG_TEXT
)
2780 "detailed-status-message (text(MAX)) has wrong "
2781 "value tag %s (RFC 2911 section 3.1.6.3).",
2782 ippTagString(attrptr
->value_tag
));
2783 if (attrptr
->group_tag
!= IPP_TAG_OPERATION
)
2785 "detailed-status-message (text(MAX)) has wrong "
2786 "group tag %s (RFC 2911 section 3.1.6.3).",
2787 ippTagString(attrptr
->group_tag
));
2788 if (attrptr
->num_values
!= 1)
2790 "detailed-status-message (text(MAX)) has %d values"
2791 " (RFC 2911 section 3.1.6.3).",
2792 attrptr
->num_values
);
2793 if (attrptr
->value_tag
== IPP_TAG_TEXT
&&
2794 strlen(attrptr
->values
[0].string
.text
) > 1023)
2796 "detailed-status-message (text(MAX)) has bad "
2797 "length %d (RFC 2911 section 3.1.6.3).",
2798 (int)strlen(attrptr
->values
[0].string
.text
));
2801 a
= cupsArrayNew((cups_array_func_t
)strcmp
, NULL
);
2803 for (attrptr
= response
->attrs
,
2804 group
= attrptr
? attrptr
->group_tag
: IPP_TAG_ZERO
;
2806 attrptr
= attrptr
->next
)
2808 if (attrptr
->group_tag
!= group
)
2810 int out_of_order
= 0; /* Are attribute groups out-of-order? */
2813 switch (attrptr
->group_tag
)
2818 case IPP_TAG_OPERATION
:
2822 case IPP_TAG_UNSUPPORTED_GROUP
:
2823 if (group
!= IPP_TAG_OPERATION
)
2828 case IPP_TAG_PRINTER
:
2829 if (group
!= IPP_TAG_OPERATION
&&
2830 group
!= IPP_TAG_UNSUPPORTED_GROUP
)
2834 case IPP_TAG_SUBSCRIPTION
:
2835 if (group
> attrptr
->group_tag
&&
2836 group
!= IPP_TAG_DOCUMENT
)
2841 if (group
> attrptr
->group_tag
)
2847 add_stringf(errors
, "Attribute groups out of order (%s < %s)",
2848 ippTagString(attrptr
->group_tag
),
2849 ippTagString(group
));
2851 if (attrptr
->group_tag
!= IPP_TAG_ZERO
)
2852 group
= attrptr
->group_tag
;
2855 validate_attr(errors
, attrptr
);
2859 if (cupsArrayFind(a
, attrptr
->name
))
2860 add_stringf(errors
, "Duplicate \"%s\" attribute in %s group",
2861 attrptr
->name
, ippTagString(group
));
2863 cupsArrayAdd(a
, attrptr
->name
);
2870 * Now check the test-defined expected status-code and attribute
2874 for (i
= 0; i
< num_statuses
; i
++)
2876 if (statuses
[i
].if_defined
&&
2877 !get_variable(vars
, statuses
[i
].if_defined
))
2880 if (statuses
[i
].if_not_defined
&&
2881 get_variable(vars
, statuses
[i
].if_not_defined
))
2884 if (response
->request
.status
.status_code
== statuses
[i
].status
)
2886 if (statuses
[i
].repeat_match
&&
2887 repeat_count
< statuses
[i
].repeat_limit
)
2890 if (statuses
[i
].define_match
)
2891 set_variable(vars
, statuses
[i
].define_match
, "1");
2897 if (statuses
[i
].repeat_no_match
&&
2898 repeat_count
< statuses
[i
].repeat_limit
)
2901 if (statuses
[i
].define_no_match
)
2903 set_variable(vars
, statuses
[i
].define_no_match
, "1");
2909 if (i
== num_statuses
&& num_statuses
> 0)
2911 for (i
= 0; i
< num_statuses
; i
++)
2913 if (statuses
[i
].if_defined
&&
2914 !get_variable(vars
, statuses
[i
].if_defined
))
2917 if (statuses
[i
].if_not_defined
&&
2918 get_variable(vars
, statuses
[i
].if_not_defined
))
2921 if (!statuses
[i
].repeat_match
)
2922 add_stringf(errors
, "EXPECTED: STATUS %s (got %s)",
2923 ippErrorString(statuses
[i
].status
),
2924 ippErrorString(cupsLastError()));
2927 if ((attrptr
= ippFindAttribute(response
, "status-message",
2928 IPP_TAG_TEXT
)) != NULL
)
2929 add_stringf(errors
, "status-message=\"%s\"",
2930 attrptr
->values
[0].string
.text
);
2933 for (i
= num_expects
, expect
= expects
; i
> 0; i
--, expect
++)
2935 if (expect
->if_defined
&& !get_variable(vars
, expect
->if_defined
))
2938 if (expect
->if_not_defined
&&
2939 get_variable(vars
, expect
->if_not_defined
))
2942 found
= ippFindAttribute(response
, expect
->name
, IPP_TAG_ZERO
);
2944 if ((found
&& expect
->not_expect
) ||
2945 (!found
&& !(expect
->not_expect
|| expect
->optional
)) ||
2946 (found
&& !expect_matches(expect
, found
->value_tag
)) ||
2947 (found
&& expect
->in_group
&&
2948 found
->group_tag
!= expect
->in_group
))
2950 if (expect
->define_no_match
)
2951 set_variable(vars
, expect
->define_no_match
, "1");
2952 else if (!expect
->define_match
&& !expect
->define_value
)
2954 if (found
&& expect
->not_expect
)
2955 add_stringf(errors
, "NOT EXPECTED: %s", expect
->name
);
2956 else if (!found
&& !(expect
->not_expect
|| expect
->optional
))
2957 add_stringf(errors
, "EXPECTED: %s", expect
->name
);
2960 if (!expect_matches(expect
, found
->value_tag
))
2961 add_stringf(errors
, "EXPECTED: %s OF-TYPE %s (got %s)",
2962 expect
->name
, expect
->of_type
,
2963 ippTagString(found
->value_tag
));
2965 if (expect
->in_group
&& found
->group_tag
!= expect
->in_group
)
2966 add_stringf(errors
, "EXPECTED: %s IN-GROUP %s (got %s).",
2967 expect
->name
, ippTagString(expect
->in_group
),
2968 ippTagString(found
->group_tag
));
2972 if (expect
->repeat_no_match
&&
2973 repeat_count
< expect
->repeat_limit
)
2980 ippAttributeString(found
, buffer
, sizeof(buffer
));
2983 !with_value(NULL
, expect
->with_value
, expect
->with_flags
, found
,
2984 buffer
, sizeof(buffer
)))
2986 if (expect
->define_no_match
)
2987 set_variable(vars
, expect
->define_no_match
, "1");
2988 else if (!expect
->define_match
&& !expect
->define_value
&&
2989 !expect
->repeat_match
&& !expect
->repeat_no_match
)
2991 if (expect
->with_flags
& _CUPS_WITH_REGEX
)
2992 add_stringf(errors
, "EXPECTED: %s %s /%s/",
2994 (expect
->with_flags
& _CUPS_WITH_ALL
) ?
2995 "WITH-ALL-VALUES" : "WITH-VALUE",
2996 expect
->with_value
);
2998 add_stringf(errors
, "EXPECTED: %s %s \"%s\"",
3000 (expect
->with_flags
& _CUPS_WITH_ALL
) ?
3001 "WITH-ALL-VALUES" : "WITH-VALUE",
3002 expect
->with_value
);
3004 with_value(errors
, expect
->with_value
, expect
->with_flags
, found
,
3005 buffer
, sizeof(buffer
));
3008 if (expect
->repeat_no_match
&&
3009 repeat_count
< expect
->repeat_limit
)
3015 if (found
&& expect
->count
> 0 &&
3016 found
->num_values
!= expect
->count
)
3018 if (expect
->define_no_match
)
3019 set_variable(vars
, expect
->define_no_match
, "1");
3020 else if (!expect
->define_match
&& !expect
->define_value
)
3022 add_stringf(errors
, "EXPECTED: %s COUNT %d (got %d)", expect
->name
,
3023 expect
->count
, found
->num_values
);
3026 if (expect
->repeat_no_match
&&
3027 repeat_count
< expect
->repeat_limit
)
3033 if (found
&& expect
->same_count_as
)
3035 attrptr
= ippFindAttribute(response
, expect
->same_count_as
,
3038 if (!attrptr
|| attrptr
->num_values
!= found
->num_values
)
3040 if (expect
->define_no_match
)
3041 set_variable(vars
, expect
->define_no_match
, "1");
3042 else if (!expect
->define_match
&& !expect
->define_value
)
3046 "EXPECTED: %s (%d values) SAME-COUNT-AS %s "
3047 "(not returned)", expect
->name
,
3048 found
->num_values
, expect
->same_count_as
);
3049 else if (attrptr
->num_values
!= found
->num_values
)
3051 "EXPECTED: %s (%d values) SAME-COUNT-AS %s "
3052 "(%d values)", expect
->name
, found
->num_values
,
3053 expect
->same_count_as
, attrptr
->num_values
);
3056 if (expect
->repeat_no_match
&&
3057 repeat_count
< expect
->repeat_limit
)
3064 if (found
&& expect
->define_match
)
3065 set_variable(vars
, expect
->define_match
, "1");
3067 if (found
&& expect
->define_value
)
3068 set_variable(vars
, expect
->define_value
, buffer
);
3070 if (found
&& expect
->repeat_match
&&
3071 repeat_count
< expect
->repeat_limit
)
3077 * If we are going to repeat this test, sleep 1 second so we don't flood
3078 * the printer with requests...
3083 if (Output
== _CUPS_OUTPUT_TEST
)
3085 printf("%04d]\n", repeat_count
);
3089 sleep(repeat_interval
);
3090 repeat_interval
= _cupsNextDelay(repeat_interval
, &repeat_prev
);
3092 if (Output
== _CUPS_OUTPUT_TEST
)
3094 printf(" %-68.68s [", name
);
3099 while (repeat_test
);
3105 if (cupsArrayCount(errors
) > 0)
3106 prev_pass
= pass
= 0;
3113 if (Output
== _CUPS_OUTPUT_PLIST
)
3115 puts("<key>Successful</key>");
3116 puts(prev_pass
? "<true />" : "<false />");
3117 puts("<key>StatusCode</key>");
3118 print_xml_string("string", ippErrorString(cupsLastError()));
3119 puts("<key>ResponseAttributes</key>");
3122 for (attrptr
= response
? response
->attrs
: NULL
,
3123 group
= attrptr
? attrptr
->group_tag
: IPP_TAG_ZERO
;
3125 attrptr
= attrptr
->next
)
3126 print_attr(attrptr
, &group
);
3130 else if (Output
== _CUPS_OUTPUT_TEST
)
3132 puts(prev_pass
? "PASS]" : "FAIL]");
3134 if (!prev_pass
|| (Verbosity
&& response
))
3136 printf(" RECEIVED: %lu bytes in response\n",
3137 (unsigned long)ippLength(response
));
3138 printf(" status-code = %s (%s)\n", ippErrorString(cupsLastError()),
3139 cupsLastErrorString());
3143 for (attrptr
= response
->attrs
;
3145 attrptr
= attrptr
->next
)
3146 print_attr(attrptr
, NULL
);
3150 else if (!prev_pass
)
3151 fprintf(stderr
, "%s\n", cupsLastErrorString());
3153 if (prev_pass
&& Output
>= _CUPS_OUTPUT_LIST
&& !Verbosity
&&
3156 size_t width
; /* Length of value */
3158 for (i
= 0; i
< num_displayed
; i
++)
3160 widths
[i
] = strlen(displayed
[i
]);
3162 for (attrptr
= ippFindAttribute(response
, displayed
[i
], IPP_TAG_ZERO
);
3164 attrptr
= ippFindNextAttribute(response
, displayed
[i
],
3167 width
= ippAttributeString(attrptr
, NULL
, 0);
3168 if (width
> widths
[i
])
3173 if (Output
== _CUPS_OUTPUT_CSV
)
3174 print_csv(NULL
, num_displayed
, displayed
, widths
);
3176 print_line(NULL
, num_displayed
, displayed
, widths
);
3178 attrptr
= response
->attrs
;
3182 while (attrptr
&& attrptr
->group_tag
<= IPP_TAG_OPERATION
)
3183 attrptr
= attrptr
->next
;
3187 if (Output
== _CUPS_OUTPUT_CSV
)
3188 print_csv(attrptr
, num_displayed
, displayed
, widths
);
3190 print_line(attrptr
, num_displayed
, displayed
, widths
);
3192 while (attrptr
&& attrptr
->group_tag
> IPP_TAG_OPERATION
)
3193 attrptr
= attrptr
->next
;
3197 else if (!prev_pass
)
3199 if (Output
== _CUPS_OUTPUT_PLIST
)
3201 puts("<key>Errors</key>");
3204 for (error
= (char *)cupsArrayFirst(errors
);
3206 error
= (char *)cupsArrayNext(errors
))
3207 print_xml_string("string", error
);
3213 for (error
= (char *)cupsArrayFirst(errors
);
3215 error
= (char *)cupsArrayNext(errors
))
3216 printf(" %s\n", error
);
3220 if (num_displayed
> 0 && !Verbosity
&& response
&&
3221 Output
== _CUPS_OUTPUT_TEST
)
3223 for (attrptr
= response
->attrs
;
3225 attrptr
= attrptr
->next
)
3229 for (i
= 0; i
< num_displayed
; i
++)
3231 if (!strcmp(displayed
[i
], attrptr
->name
))
3233 print_attr(attrptr
, NULL
);
3243 if (Output
== _CUPS_OUTPUT_PLIST
)
3248 ippDelete(response
);
3251 for (i
= 0; i
< num_statuses
; i
++)
3253 if (statuses
[i
].if_defined
)
3254 free(statuses
[i
].if_defined
);
3255 if (statuses
[i
].if_not_defined
)
3256 free(statuses
[i
].if_not_defined
);
3257 if (statuses
[i
].define_match
)
3258 free(statuses
[i
].define_match
);
3259 if (statuses
[i
].define_no_match
)
3260 free(statuses
[i
].define_no_match
);
3264 for (i
= num_expects
, expect
= expects
; i
> 0; i
--, expect
++)
3267 if (expect
->of_type
)
3268 free(expect
->of_type
);
3269 if (expect
->same_count_as
)
3270 free(expect
->same_count_as
);
3271 if (expect
->if_defined
)
3272 free(expect
->if_defined
);
3273 if (expect
->if_not_defined
)
3274 free(expect
->if_not_defined
);
3275 if (expect
->with_value
)
3276 free(expect
->with_value
);
3277 if (expect
->define_match
)
3278 free(expect
->define_match
);
3279 if (expect
->define_no_match
)
3280 free(expect
->define_no_match
);
3281 if (expect
->define_value
)
3282 free(expect
->define_value
);
3286 for (i
= 0; i
< num_displayed
; i
++)
3290 if (!ignore_errors
&& !prev_pass
)
3296 cupsArrayDelete(errors
);
3303 ippDelete(response
);
3305 for (i
= 0; i
< num_statuses
; i
++)
3307 if (statuses
[i
].if_defined
)
3308 free(statuses
[i
].if_defined
);
3309 if (statuses
[i
].if_not_defined
)
3310 free(statuses
[i
].if_not_defined
);
3311 if (statuses
[i
].define_match
)
3312 free(statuses
[i
].define_match
);
3313 if (statuses
[i
].define_no_match
)
3314 free(statuses
[i
].define_no_match
);
3317 for (i
= num_expects
, expect
= expects
; i
> 0; i
--, expect
++)
3320 if (expect
->of_type
)
3321 free(expect
->of_type
);
3322 if (expect
->same_count_as
)
3323 free(expect
->same_count_as
);
3324 if (expect
->if_defined
)
3325 free(expect
->if_defined
);
3326 if (expect
->if_not_defined
)
3327 free(expect
->if_not_defined
);
3328 if (expect
->with_value
)
3329 free(expect
->with_value
);
3330 if (expect
->define_match
)
3331 free(expect
->define_match
);
3332 if (expect
->define_no_match
)
3333 free(expect
->define_no_match
);
3334 if (expect
->define_value
)
3335 free(expect
->define_value
);
3338 for (i
= 0; i
< num_displayed
; i
++)
3346 * 'expand_variables()' - Expand variables in a string.
3350 expand_variables(_cups_vars_t
*vars
, /* I - Variables */
3351 char *dst
, /* I - Destination string buffer */
3352 const char *src
, /* I - Source string */
3353 size_t dstsize
) /* I - Size of destination buffer */
3355 char *dstptr
, /* Pointer into destination */
3356 *dstend
, /* End of destination */
3357 temp
[256], /* Temporary string */
3358 *tempptr
; /* Pointer into temporary string */
3359 const char *value
; /* Value to substitute */
3363 dstend
= dst
+ dstsize
- 1;
3365 while (*src
&& dstptr
< dstend
)
3370 * Substitute a string/number...
3373 if (!strncmp(src
, "$$", 2))
3378 else if (!strncmp(src
, "$ENV[", 5))
3380 strlcpy(temp
, src
+ 5, sizeof(temp
));
3382 for (tempptr
= temp
; *tempptr
; tempptr
++)
3383 if (*tempptr
== ']')
3389 value
= getenv(temp
);
3390 src
+= tempptr
- temp
+ 5;
3394 strlcpy(temp
, src
+ 1, sizeof(temp
));
3396 for (tempptr
= temp
; *tempptr
; tempptr
++)
3397 if (!isalnum(*tempptr
& 255) && *tempptr
!= '-' && *tempptr
!= '_')
3403 if (!strcmp(temp
, "uri"))
3405 else if (!strcmp(temp
, "filename"))
3406 value
= vars
->filename
;
3407 else if (!strcmp(temp
, "scheme") || !strcmp(temp
, "method"))
3408 value
= vars
->scheme
;
3409 else if (!strcmp(temp
, "username"))
3410 value
= vars
->userpass
;
3411 else if (!strcmp(temp
, "hostname"))
3412 value
= vars
->hostname
;
3413 else if (!strcmp(temp
, "port"))
3415 snprintf(temp
, sizeof(temp
), "%d", vars
->port
);
3418 else if (!strcmp(temp
, "resource"))
3419 value
= vars
->resource
;
3420 else if (!strcmp(temp
, "user"))
3423 value
= get_variable(vars
, temp
);
3425 src
+= tempptr
- temp
+ 1;
3435 strlcpy(dstptr
, value
, dstend
- dstptr
+ 1);
3436 dstptr
+= strlen(dstptr
);
3448 * 'expect_matches()' - Return true if the tag matches the specification.
3451 static int /* O - 1 if matches, 0 otherwise */
3453 _cups_expect_t
*expect
, /* I - Expected attribute */
3454 ipp_tag_t value_tag
) /* I - Value tag for attribute */
3456 int match
; /* Match? */
3457 char *of_type
, /* Type name to match */
3458 *next
, /* Next name to match */
3459 sep
; /* Separator character */
3463 * If we don't expect a particular type, return immediately...
3466 if (!expect
->of_type
)
3470 * Parse the "of_type" value since the string can contain multiple attribute
3471 * types separated by "," or "|"...
3474 for (of_type
= expect
->of_type
, match
= 0; !match
&& *of_type
; of_type
= next
)
3477 * Find the next separator, and set it (temporarily) to nul if present.
3480 for (next
= of_type
; *next
&& *next
!= '|' && *next
!= ','; next
++);
3482 if ((sep
= *next
) != '\0')
3486 * Support some meta-types to make it easier to write the test file.
3489 if (!strcmp(of_type
, "text"))
3490 match
= value_tag
== IPP_TAG_TEXTLANG
|| value_tag
== IPP_TAG_TEXT
;
3491 else if (!strcmp(of_type
, "name"))
3492 match
= value_tag
== IPP_TAG_NAMELANG
|| value_tag
== IPP_TAG_NAME
;
3493 else if (!strcmp(of_type
, "collection"))
3494 match
= value_tag
== IPP_TAG_BEGIN_COLLECTION
;
3496 match
= value_tag
== ippTagValue(of_type
);
3499 * Restore the separator if we have one...
3511 * 'get_collection()' - Get a collection value from the current test file.
3514 static ipp_t
* /* O - Collection value */
3515 get_collection(_cups_vars_t
*vars
, /* I - Variables */
3516 FILE *fp
, /* I - File to read from */
3517 int *linenum
) /* IO - Line number */
3519 char token
[1024], /* Token from file */
3520 temp
[1024], /* Temporary string */
3521 attr
[128]; /* Attribute name */
3522 ipp_tag_t value
; /* Current value type */
3523 ipp_t
*col
= ippNew(); /* Collection value */
3524 ipp_attribute_t
*lastcol
= NULL
; /* Last collection attribute */
3527 while (get_token(fp
, token
, sizeof(token
), linenum
) != NULL
)
3529 if (!strcmp(token
, "}"))
3531 else if (!strcmp(token
, "{") && lastcol
)
3534 * Another collection value
3537 ipp_t
*subcol
= get_collection(vars
, fp
, linenum
);
3538 /* Collection value */
3542 ipp_attribute_t
*tempcol
; /* Pointer to new buffer */
3546 * Reallocate memory...
3549 if ((tempcol
= realloc(lastcol
, sizeof(ipp_attribute_t
) +
3550 (lastcol
->num_values
+ 1) *
3551 sizeof(_ipp_value_t
))) == NULL
)
3553 print_fatal_error("Unable to allocate memory on line %d.", *linenum
);
3557 if (tempcol
!= lastcol
)
3560 * Reset pointers in the list...
3564 col
->prev
->next
= tempcol
;
3566 col
->attrs
= tempcol
;
3568 lastcol
= col
->current
= col
->last
= tempcol
;
3571 lastcol
->values
[lastcol
->num_values
].collection
= subcol
;
3572 lastcol
->num_values
++;
3577 else if (!_cups_strcasecmp(token
, "MEMBER"))
3585 if (!get_token(fp
, token
, sizeof(token
), linenum
))
3587 print_fatal_error("Missing MEMBER value tag on line %d.", *linenum
);
3591 if ((value
= ippTagValue(token
)) == IPP_TAG_ZERO
)
3593 print_fatal_error("Bad MEMBER value tag \"%s\" on line %d.", token
,
3598 if (!get_token(fp
, attr
, sizeof(attr
), linenum
))
3600 print_fatal_error("Missing MEMBER name on line %d.", *linenum
);
3604 if (!get_token(fp
, temp
, sizeof(temp
), linenum
))
3606 print_fatal_error("Missing MEMBER value on line %d.", *linenum
);
3610 expand_variables(vars
, token
, temp
, sizeof(token
));
3614 case IPP_TAG_BOOLEAN
:
3615 if (!_cups_strcasecmp(token
, "true"))
3616 ippAddBoolean(col
, IPP_TAG_ZERO
, attr
, 1);
3618 ippAddBoolean(col
, IPP_TAG_ZERO
, attr
, atoi(token
));
3621 case IPP_TAG_INTEGER
:
3623 ippAddInteger(col
, IPP_TAG_ZERO
, value
, attr
, atoi(token
));
3626 case IPP_TAG_RESOLUTION
:
3628 int xres
, /* X resolution */
3629 yres
; /* Y resolution */
3630 char units
[6]; /* Units */
3632 if (sscanf(token
, "%dx%d%5s", &xres
, &yres
, units
) != 3 ||
3633 (_cups_strcasecmp(units
, "dpi") &&
3634 _cups_strcasecmp(units
, "dpc") &&
3635 _cups_strcasecmp(units
, "dpcm") &&
3636 _cups_strcasecmp(units
, "other")))
3638 print_fatal_error("Bad resolution value \"%s\" on line %d.",
3643 if (!_cups_strcasecmp(units
, "dpi"))
3644 ippAddResolution(col
, IPP_TAG_ZERO
, attr
, xres
, yres
,
3646 else if (!_cups_strcasecmp(units
, "dpc") ||
3647 !_cups_strcasecmp(units
, "dpcm"))
3648 ippAddResolution(col
, IPP_TAG_ZERO
, attr
, xres
, yres
,
3651 ippAddResolution(col
, IPP_TAG_ZERO
, attr
, xres
, yres
,
3656 case IPP_TAG_RANGE
:
3658 int lowers
[4], /* Lower value */
3659 uppers
[4], /* Upper values */
3660 num_vals
; /* Number of values */
3663 num_vals
= sscanf(token
, "%d-%d,%d-%d,%d-%d,%d-%d",
3664 lowers
+ 0, uppers
+ 0,
3665 lowers
+ 1, uppers
+ 1,
3666 lowers
+ 2, uppers
+ 2,
3667 lowers
+ 3, uppers
+ 3);
3669 if ((num_vals
& 1) || num_vals
== 0)
3671 print_fatal_error("Bad rangeOfInteger value \"%s\" on line %d.",
3676 ippAddRanges(col
, IPP_TAG_ZERO
, attr
, num_vals
/ 2, lowers
,
3681 case IPP_TAG_BEGIN_COLLECTION
:
3682 if (!strcmp(token
, "{"))
3684 ipp_t
*subcol
= get_collection(vars
, fp
, linenum
);
3685 /* Collection value */
3689 lastcol
= ippAddCollection(col
, IPP_TAG_ZERO
, attr
, subcol
);
3697 print_fatal_error("Bad collection value on line %d.", *linenum
);
3701 case IPP_TAG_STRING
:
3702 ippAddOctetString(col
, IPP_TAG_ZERO
, attr
, token
, strlen(token
));
3706 if (!strchr(token
, ','))
3707 ippAddString(col
, IPP_TAG_ZERO
, value
, attr
, NULL
, token
);
3711 * Multiple string values...
3714 int num_values
; /* Number of values */
3715 char *values
[100], /* Values */
3716 *ptr
; /* Pointer to next value */
3722 for (ptr
= strchr(token
, ','); ptr
; ptr
= strchr(ptr
, ','))
3725 values
[num_values
] = ptr
;
3729 ippAddStrings(col
, IPP_TAG_ZERO
, value
, attr
, num_values
,
3730 NULL
, (const char **)values
);
3740 * If we get here there was a parse error; free memory and return.
3752 * 'get_filename()' - Get a filename based on the current test file.
3755 static char * /* O - Filename */
3756 get_filename(const char *testfile
, /* I - Current test file */
3757 char *dst
, /* I - Destination filename */
3758 const char *src
, /* I - Source filename */
3759 size_t dstsize
) /* I - Size of destination buffer */
3761 char *dstptr
; /* Pointer into destination */
3762 _cups_globals_t
*cg
= _cupsGlobals();
3766 if (*src
== '<' && src
[strlen(src
) - 1] == '>')
3769 * Map <filename> to CUPS_DATADIR/ipptool/filename...
3772 snprintf(dst
, dstsize
, "%s/ipptool/%s", cg
->cups_datadir
, src
+ 1);
3773 dstptr
= dst
+ strlen(dst
) - 1;
3777 else if (*src
== '/' || !strchr(testfile
, '/')
3779 || (isalpha(*src
& 255) && src
[1] == ':')
3784 * Use the path as-is...
3787 strlcpy(dst
, src
, dstsize
);
3792 * Make path relative to testfile...
3795 strlcpy(dst
, testfile
, dstsize
);
3796 if ((dstptr
= strrchr(dst
, '/')) != NULL
)
3799 dstptr
= dst
; /* Should never happen */
3801 strlcpy(dstptr
, src
, dstsize
- (dstptr
- dst
));
3809 * 'get_string()' - Get a pointer to a string value or the portion of interest.
3812 static char * /* O - Pointer to string */
3813 get_string(ipp_attribute_t
*attr
, /* I - IPP attribute */
3814 int element
, /* I - Element to fetch */
3815 int flags
, /* I - Value ("with") flags */
3816 char *buffer
, /* I - Temporary buffer */
3817 size_t bufsize
) /* I - Size of temporary buffer */
3819 char *ptr
, /* Value */
3820 scheme
[256], /* URI scheme */
3821 userpass
[256], /* Username/password */
3822 hostname
[256], /* Hostname */
3823 resource
[1024]; /* Resource */
3824 int port
; /* Port number */
3827 ptr
= attr
->values
[element
].string
.text
;
3829 if (flags
& _CUPS_WITH_HOSTNAME
)
3831 if (httpSeparateURI(HTTP_URI_CODING_ALL
, ptr
, scheme
, sizeof(scheme
),
3832 userpass
, sizeof(userpass
), buffer
, bufsize
, &port
,
3833 resource
, sizeof(resource
)) < HTTP_URI_STATUS_OK
)
3838 else if (flags
& _CUPS_WITH_RESOURCE
)
3840 if (httpSeparateURI(HTTP_URI_CODING_ALL
, ptr
, scheme
, sizeof(scheme
),
3841 userpass
, sizeof(userpass
), hostname
, sizeof(hostname
),
3842 &port
, buffer
, bufsize
) < HTTP_URI_STATUS_OK
)
3847 else if (flags
& _CUPS_WITH_SCHEME
)
3849 if (httpSeparateURI(HTTP_URI_CODING_ALL
, ptr
, buffer
, bufsize
,
3850 userpass
, sizeof(userpass
), hostname
, sizeof(hostname
),
3851 &port
, resource
, sizeof(resource
)) < HTTP_URI_STATUS_OK
)
3862 * 'get_token()' - Get a token from a file.
3865 static char * /* O - Token from file or NULL on EOF */
3866 get_token(FILE *fp
, /* I - File to read from */
3867 char *buf
, /* I - Buffer to read into */
3868 int buflen
, /* I - Length of buffer */
3869 int *linenum
) /* IO - Current line number */
3871 int ch
, /* Character from file */
3872 quote
; /* Quoting character */
3873 char *bufptr
, /* Pointer into buffer */
3874 *bufend
; /* End of buffer */
3880 * Skip whitespace...
3883 while (isspace(ch
= getc(fp
)))
3895 else if (ch
== '\'' || ch
== '\"')
3898 * Quoted text or regular expression...
3903 bufend
= buf
+ buflen
- 1;
3905 while ((ch
= getc(fp
)) != EOF
)
3910 * Escape next character...
3913 if (bufptr
< bufend
)
3916 if ((ch
= getc(fp
)) != EOF
&& bufptr
< bufend
)
3919 else if (ch
== quote
)
3921 else if (bufptr
< bufend
)
3935 while ((ch
= getc(fp
)) != EOF
)
3944 * Whitespace delimited text...
3950 bufend
= buf
+ buflen
- 1;
3952 while ((ch
= getc(fp
)) != EOF
)
3953 if (isspace(ch
) || ch
== '#')
3955 else if (bufptr
< bufend
)
3960 else if (ch
== '\n')
3972 * 'get_variable()' - Get the value of a variable.
3975 static char * /* O - Value or NULL */
3976 get_variable(_cups_vars_t
*vars
, /* I - Variables */
3977 const char *name
) /* I - Variable name */
3979 _cups_var_t key
, /* Search key */
3980 *match
; /* Matching variable, if any */
3983 key
.name
= (char *)name
;
3984 match
= cupsArrayFind(vars
->vars
, &key
);
3986 return (match
? match
->value
: NULL
);
3991 * 'iso_date()' - Return an ISO 8601 date/time string for the given IPP dateTime
3995 static char * /* O - ISO 8601 date/time string */
3996 iso_date(ipp_uchar_t
*date
) /* I - IPP (RFC 1903) date/time value */
3998 time_t utctime
; /* UTC time since 1970 */
3999 struct tm
*utcdate
; /* UTC date/time */
4000 static char buffer
[255]; /* String buffer */
4003 utctime
= ippDateToTime(date
);
4004 utcdate
= gmtime(&utctime
);
4006 snprintf(buffer
, sizeof(buffer
), "%04d-%02d-%02dT%02d:%02d:%02dZ",
4007 utcdate
->tm_year
+ 1900, utcdate
->tm_mon
+ 1, utcdate
->tm_mday
,
4008 utcdate
->tm_hour
, utcdate
->tm_min
, utcdate
->tm_sec
);
4015 * 'password_cb()' - Password callback for authenticated tests.
4018 static const char * /* O - Password */
4019 password_cb(const char *prompt
) /* I - Prompt (unused) */
4028 * 'print_attr()' - Print an attribute on the screen.
4032 print_attr(ipp_attribute_t
*attr
, /* I - Attribute to print */
4033 ipp_tag_t
*group
) /* IO - Current group */
4035 int i
; /* Looping var */
4036 ipp_attribute_t
*colattr
; /* Collection attribute */
4039 if (Output
== _CUPS_OUTPUT_PLIST
)
4041 if (!attr
->name
|| (group
&& *group
!= attr
->group_tag
))
4043 if (attr
->group_tag
!= IPP_TAG_ZERO
)
4050 *group
= attr
->group_tag
;
4056 print_xml_string("key", attr
->name
);
4057 if (attr
->num_values
> 1)
4060 switch (attr
->value_tag
)
4062 case IPP_TAG_INTEGER
:
4064 for (i
= 0; i
< attr
->num_values
; i
++)
4065 if (Output
== _CUPS_OUTPUT_PLIST
)
4066 printf("<integer>%d</integer>\n", attr
->values
[i
].integer
);
4068 printf("%d ", attr
->values
[i
].integer
);
4071 case IPP_TAG_BOOLEAN
:
4072 for (i
= 0; i
< attr
->num_values
; i
++)
4073 if (Output
== _CUPS_OUTPUT_PLIST
)
4074 puts(attr
->values
[i
].boolean
? "<true />" : "<false />");
4075 else if (attr
->values
[i
].boolean
)
4076 fputs("true ", stdout
);
4078 fputs("false ", stdout
);
4081 case IPP_TAG_RANGE
:
4082 for (i
= 0; i
< attr
->num_values
; i
++)
4083 if (Output
== _CUPS_OUTPUT_PLIST
)
4084 printf("<dict><key>lower</key><integer>%d</integer>"
4085 "<key>upper</key><integer>%d</integer></dict>\n",
4086 attr
->values
[i
].range
.lower
, attr
->values
[i
].range
.upper
);
4088 printf("%d-%d ", attr
->values
[i
].range
.lower
,
4089 attr
->values
[i
].range
.upper
);
4092 case IPP_TAG_RESOLUTION
:
4093 for (i
= 0; i
< attr
->num_values
; i
++)
4094 if (Output
== _CUPS_OUTPUT_PLIST
)
4095 printf("<dict><key>xres</key><integer>%d</integer>"
4096 "<key>yres</key><integer>%d</integer>"
4097 "<key>units</key><string>%s</string></dict>\n",
4098 attr
->values
[i
].resolution
.xres
,
4099 attr
->values
[i
].resolution
.yres
,
4100 attr
->values
[i
].resolution
.units
== IPP_RES_PER_INCH
?
4103 printf("%dx%d%s ", attr
->values
[i
].resolution
.xres
,
4104 attr
->values
[i
].resolution
.yres
,
4105 attr
->values
[i
].resolution
.units
== IPP_RES_PER_INCH
?
4110 for (i
= 0; i
< attr
->num_values
; i
++)
4111 if (Output
== _CUPS_OUTPUT_PLIST
)
4112 printf("<date>%s</date>\n", iso_date(attr
->values
[i
].date
));
4114 printf("%s ", iso_date(attr
->values
[i
].date
));
4117 case IPP_TAG_STRING
:
4118 for (i
= 0; i
< attr
->num_values
; i
++)
4120 if (Output
== _CUPS_OUTPUT_PLIST
)
4122 char buffer
[IPP_MAX_LENGTH
* 5 / 4 + 1];
4125 printf("<data>%s</data>\n",
4126 httpEncode64_2(buffer
, sizeof(buffer
),
4127 attr
->values
[i
].unknown
.data
,
4128 attr
->values
[i
].unknown
.length
));
4132 char *ptr
, /* Pointer into data */
4133 *end
; /* End of data */
4136 for (ptr
= attr
->values
[i
].unknown
.data
,
4137 end
= ptr
+ attr
->values
[i
].unknown
.length
;
4141 if (*ptr
== '\\' || *ptr
== '\"')
4142 printf("\\%c", *ptr
);
4143 else if (!isprint(*ptr
& 255))
4144 printf("\\%03o", *ptr
& 255);
4155 case IPP_TAG_KEYWORD
:
4156 case IPP_TAG_CHARSET
:
4158 case IPP_TAG_MIMETYPE
:
4159 case IPP_TAG_LANGUAGE
:
4160 for (i
= 0; i
< attr
->num_values
; i
++)
4161 if (Output
== _CUPS_OUTPUT_PLIST
)
4162 print_xml_string("string", attr
->values
[i
].string
.text
);
4164 printf("\"%s\" ", attr
->values
[i
].string
.text
);
4167 case IPP_TAG_TEXTLANG
:
4168 case IPP_TAG_NAMELANG
:
4169 for (i
= 0; i
< attr
->num_values
; i
++)
4170 if (Output
== _CUPS_OUTPUT_PLIST
)
4172 fputs("<dict><key>language</key><string>", stdout
);
4173 print_xml_string(NULL
, attr
->values
[i
].string
.language
);
4174 fputs("</string><key>string</key><string>", stdout
);
4175 print_xml_string(NULL
, attr
->values
[i
].string
.text
);
4176 puts("</string></dict>");
4179 printf("\"%s\"[%s] ", attr
->values
[i
].string
.text
,
4180 attr
->values
[i
].string
.language
);
4183 case IPP_TAG_BEGIN_COLLECTION
:
4184 for (i
= 0; i
< attr
->num_values
; i
++)
4186 if (Output
== _CUPS_OUTPUT_PLIST
)
4189 for (colattr
= attr
->values
[i
].collection
->attrs
;
4191 colattr
= colattr
->next
)
4192 print_attr(colattr
, NULL
);
4200 print_col(attr
->values
[i
].collection
);
4206 if (Output
== _CUPS_OUTPUT_PLIST
)
4207 printf("<string><<%s>></string>\n",
4208 ippTagString(attr
->value_tag
));
4210 fputs(ippTagString(attr
->value_tag
), stdout
);
4214 if (attr
->num_values
> 1)
4219 char buffer
[8192]; /* Value buffer */
4221 if (Output
== _CUPS_OUTPUT_TEST
)
4225 puts(" -- separator --");
4229 printf(" %s (%s%s) = ", attr
->name
,
4230 attr
->num_values
> 1 ? "1setOf " : "",
4231 ippTagString(attr
->value_tag
));
4234 ippAttributeString(attr
, buffer
, sizeof(buffer
));
4241 * 'print_col()' - Print a collection attribute on the screen.
4245 print_col(ipp_t
*col
) /* I - Collection attribute to print */
4247 int i
; /* Looping var */
4248 ipp_attribute_t
*attr
; /* Current attribute in collection */
4251 fputs("{ ", stdout
);
4252 for (attr
= col
->attrs
; attr
; attr
= attr
->next
)
4254 printf("%s (%s%s) = ", attr
->name
, attr
->num_values
> 1 ? "1setOf " : "",
4255 ippTagString(attr
->value_tag
));
4257 switch (attr
->value_tag
)
4259 case IPP_TAG_INTEGER
:
4261 for (i
= 0; i
< attr
->num_values
; i
++)
4262 printf("%d ", attr
->values
[i
].integer
);
4265 case IPP_TAG_BOOLEAN
:
4266 for (i
= 0; i
< attr
->num_values
; i
++)
4267 if (attr
->values
[i
].boolean
)
4273 case IPP_TAG_NOVALUE
:
4277 case IPP_TAG_RANGE
:
4278 for (i
= 0; i
< attr
->num_values
; i
++)
4279 printf("%d-%d ", attr
->values
[i
].range
.lower
,
4280 attr
->values
[i
].range
.upper
);
4283 case IPP_TAG_RESOLUTION
:
4284 for (i
= 0; i
< attr
->num_values
; i
++)
4285 printf("%dx%d%s ", attr
->values
[i
].resolution
.xres
,
4286 attr
->values
[i
].resolution
.yres
,
4287 attr
->values
[i
].resolution
.units
== IPP_RES_PER_INCH
?
4291 case IPP_TAG_STRING
:
4294 case IPP_TAG_KEYWORD
:
4295 case IPP_TAG_CHARSET
:
4297 case IPP_TAG_MIMETYPE
:
4298 case IPP_TAG_LANGUAGE
:
4299 for (i
= 0; i
< attr
->num_values
; i
++)
4300 printf("\"%s\" ", attr
->values
[i
].string
.text
);
4303 case IPP_TAG_TEXTLANG
:
4304 case IPP_TAG_NAMELANG
:
4305 for (i
= 0; i
< attr
->num_values
; i
++)
4306 printf("\"%s\"[%s] ", attr
->values
[i
].string
.text
,
4307 attr
->values
[i
].string
.language
);
4310 case IPP_TAG_BEGIN_COLLECTION
:
4311 for (i
= 0; i
< attr
->num_values
; i
++)
4313 print_col(attr
->values
[i
].collection
);
4319 break; /* anti-compiler-warning-code */
4328 * 'print_csv()' - Print a line of CSV text.
4333 ipp_attribute_t
*attr
, /* I - First attribute for line */
4334 int num_displayed
, /* I - Number of attributes to display */
4335 char **displayed
, /* I - Attributes to display */
4336 size_t *widths
) /* I - Column widths */
4338 int i
; /* Looping var */
4339 size_t maxlength
; /* Max length of all columns */
4340 char *buffer
, /* String buffer */
4341 *bufptr
; /* Pointer into buffer */
4342 ipp_attribute_t
*current
; /* Current attribute */
4346 * Get the maximum string length we have to show and allocate...
4349 for (i
= 1, maxlength
= widths
[0]; i
< num_displayed
; i
++)
4350 if (widths
[i
] > maxlength
)
4351 maxlength
= widths
[i
];
4355 if ((buffer
= malloc(maxlength
)) == NULL
)
4359 * Loop through the attributes to display...
4364 for (i
= 0; i
< num_displayed
; i
++)
4371 for (current
= attr
; current
; current
= current
->next
)
4375 else if (!strcmp(current
->name
, displayed
[i
]))
4377 ippAttributeString(current
, buffer
, maxlength
);
4382 if (strchr(buffer
, ',') != NULL
|| strchr(buffer
, '\"') != NULL
||
4383 strchr(buffer
, '\\') != NULL
)
4386 for (bufptr
= buffer
; *bufptr
; bufptr
++)
4388 if (*bufptr
== '\\' || *bufptr
== '\"')
4395 fputs(buffer
, stdout
);
4401 for (i
= 0; i
< num_displayed
; i
++)
4406 fputs(displayed
[i
], stdout
);
4416 * 'print_fatal_error()' - Print a fatal error message.
4420 print_fatal_error(const char *s
, /* I - Printf-style format string */
4421 ...) /* I - Additional arguments as needed */
4423 char buffer
[10240]; /* Format buffer */
4424 va_list ap
; /* Pointer to arguments */
4428 * Format the error message...
4432 vsnprintf(buffer
, sizeof(buffer
), s
, ap
);
4439 if (Output
== _CUPS_OUTPUT_PLIST
)
4442 print_xml_trailer(0, buffer
);
4445 _cupsLangPrintf(stderr
, "ipptool: %s", buffer
);
4450 * 'print_line()' - Print a line of formatted or CSV text.
4455 ipp_attribute_t
*attr
, /* I - First attribute for line */
4456 int num_displayed
, /* I - Number of attributes to display */
4457 char **displayed
, /* I - Attributes to display */
4458 size_t *widths
) /* I - Column widths */
4460 int i
; /* Looping var */
4461 size_t maxlength
; /* Max length of all columns */
4462 char *buffer
; /* String buffer */
4463 ipp_attribute_t
*current
; /* Current attribute */
4467 * Get the maximum string length we have to show and allocate...
4470 for (i
= 1, maxlength
= widths
[0]; i
< num_displayed
; i
++)
4471 if (widths
[i
] > maxlength
)
4472 maxlength
= widths
[i
];
4476 if ((buffer
= malloc(maxlength
)) == NULL
)
4480 * Loop through the attributes to display...
4485 for (i
= 0; i
< num_displayed
; i
++)
4492 for (current
= attr
; current
; current
= current
->next
)
4496 else if (!strcmp(current
->name
, displayed
[i
]))
4498 ippAttributeString(current
, buffer
, maxlength
);
4503 printf("%*s", (int)-widths
[i
], buffer
);
4509 for (i
= 0; i
< num_displayed
; i
++)
4514 printf("%*s", (int)-widths
[i
], displayed
[i
]);
4518 for (i
= 0; i
< num_displayed
; i
++)
4523 memset(buffer
, '-', widths
[i
]);
4524 buffer
[widths
[i
]] = '\0';
4525 fputs(buffer
, stdout
);
4535 * 'print_xml_header()' - Print a standard XML plist header.
4539 print_xml_header(void)
4543 puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
4544 puts("<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
4545 "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
4546 puts("<plist version=\"1.0\">");
4548 puts("<key>ipptoolVersion</key>");
4549 puts("<string>" CUPS_SVERSION
"</string>");
4550 puts("<key>Transfer</key>");
4551 printf("<string>%s</string>\n",
4552 Transfer
== _CUPS_TRANSFER_AUTO
? "auto" :
4553 Transfer
== _CUPS_TRANSFER_CHUNKED
? "chunked" : "length");
4554 puts("<key>Tests</key>");
4563 * 'print_xml_string()' - Print an XML string with escaping.
4567 print_xml_string(const char *element
, /* I - Element name or NULL */
4568 const char *s
) /* I - String to print */
4571 printf("<%s>", element
);
4576 fputs("&", stdout
);
4578 fputs("<", stdout
);
4580 fputs(">", stdout
);
4581 else if ((*s
& 0xe0) == 0xc0)
4584 * Validate UTF-8 two-byte sequence...
4587 if ((s
[1] & 0xc0) != 0x80)
4598 else if ((*s
& 0xf0) == 0xe0)
4601 * Validate UTF-8 three-byte sequence...
4604 if ((s
[1] & 0xc0) != 0x80 || (s
[2] & 0xc0) != 0x80)
4616 else if ((*s
& 0xf8) == 0xf0)
4619 * Validate UTF-8 four-byte sequence...
4622 if ((s
[1] & 0xc0) != 0x80 || (s
[2] & 0xc0) != 0x80 ||
4623 (s
[3] & 0xc0) != 0x80)
4636 else if ((*s
& 0x80) || (*s
< ' ' && !isspace(*s
& 255)))
4639 * Invalid control character...
4651 printf("</%s>\n", element
);
4656 * 'print_xml_trailer()' - Print the XML trailer with success/fail value.
4660 print_xml_trailer(int success
, /* I - 1 on success, 0 on failure */
4661 const char *message
) /* I - Error message or NULL */
4666 puts("<key>Successful</key>");
4667 puts(success
? "<true />" : "<false />");
4670 puts("<key>ErrorMessage</key>");
4671 print_xml_string("string", message
);
4682 * 'set_variable()' - Set a variable value.
4686 set_variable(_cups_vars_t
*vars
, /* I - Variables */
4687 const char *name
, /* I - Variable name */
4688 const char *value
) /* I - Value string */
4690 _cups_var_t key
, /* Search key */
4691 *var
; /* New variable */
4694 if (!_cups_strcasecmp(name
, "filename"))
4697 free(vars
->filename
);
4699 vars
->filename
= strdup(value
);
4703 key
.name
= (char *)name
;
4704 if ((var
= cupsArrayFind(vars
->vars
, &key
)) != NULL
)
4707 var
->value
= strdup(value
);
4709 else if ((var
= malloc(sizeof(_cups_var_t
))) == NULL
)
4711 print_fatal_error("Unable to allocate memory for variable \"%s\".", name
);
4716 var
->name
= strdup(name
);
4717 var
->value
= strdup(value
);
4719 cupsArrayAdd(vars
->vars
, var
);
4726 * 'sigterm_handler()' - Handle SIGINT and SIGTERM.
4730 sigterm_handler(int sig
) /* I - Signal number (unused) */
4736 signal(SIGINT
, SIG_DFL
);
4737 signal(SIGTERM
, SIG_DFL
);
4743 * 'timeout_cb()' - Handle HTTP timeouts.
4746 static int /* O - 1 to continue, 0 to cancel */
4747 timeout_cb(http_t
*http
, /* I - Connection to server */
4748 void *user_data
) /* I - User data (unused) */
4750 int buffered
= 0; /* Bytes buffered but not yet sent */
4754 * If the socket still have data waiting to be sent to the printer (as can
4755 * happen if the printer runs out of paper), continue to wait until the output
4756 * buffer is empty...
4759 #ifdef SO_NWRITE /* OS X and some versions of Linux */
4760 socklen_t len
= sizeof(buffered
); /* Size of return value */
4762 if (getsockopt(httpGetFd(http
), SOL_SOCKET
, SO_NWRITE
, &buffered
, &len
))
4765 #elif defined(SIOCOUTQ) /* Others except Windows */
4766 if (ioctl(httpGetFd(http
), SIOCOUTQ
, &buffered
))
4769 #else /* Windows (not possible) */
4772 #endif /* SO_NWRITE */
4774 return (buffered
> 0);
4779 * 'usage()' - Show program usage.
4785 _cupsLangPuts(stderr
, _("Usage: ipptool [options] URI filename [ ... "
4787 _cupsLangPuts(stderr
, _("Options:"));
4788 _cupsLangPuts(stderr
, _(" -4 Connect using IPv4."));
4789 _cupsLangPuts(stderr
, _(" -6 Connect using IPv6."));
4790 _cupsLangPuts(stderr
, _(" -C Send requests using "
4791 "chunking (default)."));
4792 _cupsLangPuts(stdout
, _(" -E Test with HTTP Upgrade to "
4794 _cupsLangPuts(stderr
, _(" -I Ignore errors."));
4795 _cupsLangPuts(stderr
, _(" -L Send requests using "
4796 "content-length."));
4797 _cupsLangPuts(stderr
, _(" -S Test with SSL "
4799 _cupsLangPuts(stderr
, _(" -T seconds Set the receive/send "
4800 "timeout in seconds."));
4801 _cupsLangPuts(stderr
, _(" -V version Set default IPP "
4803 _cupsLangPuts(stderr
, _(" -X Produce XML plist instead "
4805 _cupsLangPuts(stderr
, _(" -d name=value Set named variable to "
4807 _cupsLangPuts(stderr
, _(" -f filename Set default request "
4809 _cupsLangPuts(stderr
, _(" -i seconds Repeat the last file with "
4810 "the given time interval."));
4811 _cupsLangPuts(stderr
, _(" -n count Repeat the last file the "
4812 "given number of times."));
4813 _cupsLangPuts(stderr
, _(" -q Run silently."));
4814 _cupsLangPuts(stderr
, _(" -t Produce a test report."));
4815 _cupsLangPuts(stderr
, _(" -v Be verbose."));
4822 * 'validate_attr()' - Determine whether an attribute is valid.
4825 static int /* O - 1 if valid, 0 otherwise */
4826 validate_attr(cups_array_t
*errors
, /* I - Errors array */
4827 ipp_attribute_t
*attr
) /* I - Attribute to validate */
4829 int i
; /* Looping var */
4830 char scheme
[64], /* Scheme from URI */
4831 userpass
[256], /* Username/password from URI */
4832 hostname
[256], /* Hostname from URI */
4833 resource
[1024]; /* Resource from URI */
4834 int port
, /* Port number from URI */
4835 uri_status
, /* URI separation status */
4836 valid
= 1; /* Is the attribute valid? */
4837 const char *ptr
; /* Pointer into string */
4838 ipp_attribute_t
*colattr
; /* Collection attribute */
4839 regex_t re
; /* Regular expression */
4840 ipp_uchar_t
*date
; /* Current date value */
4851 * Validate the attribute name.
4854 for (ptr
= attr
->name
; *ptr
; ptr
++)
4855 if (!isalnum(*ptr
& 255) && *ptr
!= '-' && *ptr
!= '.' && *ptr
!= '_')
4858 if (*ptr
|| ptr
== attr
->name
)
4863 "\"%s\": Bad attribute name - invalid character "
4864 "(RFC 2911 section 4.1.3).", attr
->name
);
4867 if ((ptr
- attr
->name
) > 255)
4872 "\"%s\": Bad attribute name - bad length "
4873 "(RFC 2911 section 4.1.3).", attr
->name
);
4876 switch (attr
->value_tag
)
4878 case IPP_TAG_INTEGER
:
4881 case IPP_TAG_BOOLEAN
:
4882 for (i
= 0; i
< attr
->num_values
; i
++)
4884 if (attr
->values
[i
].boolean
!= 0 &&
4885 attr
->values
[i
].boolean
!= 1)
4890 "\"%s\": Bad boolen value %d "
4891 "(RFC 2911 section 4.1.11).", attr
->name
,
4892 attr
->values
[i
].boolean
);
4898 for (i
= 0; i
< attr
->num_values
; i
++)
4900 if (attr
->values
[i
].integer
< 1)
4905 "\"%s\": Bad enum value %d - out of range "
4906 "(RFC 2911 section 4.1.4).", attr
->name
,
4907 attr
->values
[i
].integer
);
4912 case IPP_TAG_STRING
:
4913 for (i
= 0; i
< attr
->num_values
; i
++)
4915 if (attr
->values
[i
].unknown
.length
> IPP_MAX_OCTETSTRING
)
4920 "\"%s\": Bad octetString value - bad length %d "
4921 "(RFC 2911 section 4.1.10).", attr
->name
,
4922 attr
->values
[i
].unknown
.length
);
4928 for (i
= 0; i
< attr
->num_values
; i
++)
4930 date
= attr
->values
[i
].date
;
4932 if (date
[2] < 1 || date
[2] > 12)
4937 "\"%s\": Bad dateTime month %u "
4938 "(RFC 2911 section 4.1.14).", attr
->name
, date
[2]);
4941 if (date
[3] < 1 || date
[3] > 31)
4946 "\"%s\": Bad dateTime day %u "
4947 "(RFC 2911 section 4.1.14).", attr
->name
, date
[3]);
4955 "\"%s\": Bad dateTime hours %u "
4956 "(RFC 2911 section 4.1.14).", attr
->name
, date
[4]);
4964 "\"%s\": Bad dateTime minutes %u "
4965 "(RFC 2911 section 4.1.14).", attr
->name
, date
[5]);
4973 "\"%s\": Bad dateTime seconds %u "
4974 "(RFC 2911 section 4.1.14).", attr
->name
, date
[6]);
4982 "\"%s\": Bad dateTime deciseconds %u "
4983 "(RFC 2911 section 4.1.14).", attr
->name
, date
[7]);
4986 if (date
[8] != '-' && date
[8] != '+')
4991 "\"%s\": Bad dateTime UTC sign '%c' "
4992 "(RFC 2911 section 4.1.14).", attr
->name
, date
[8]);
5000 "\"%s\": Bad dateTime UTC hours %u "
5001 "(RFC 2911 section 4.1.14).", attr
->name
, date
[9]);
5009 "\"%s\": Bad dateTime UTC minutes %u "
5010 "(RFC 2911 section 4.1.14).", attr
->name
, date
[10]);
5015 case IPP_TAG_RESOLUTION
:
5016 for (i
= 0; i
< attr
->num_values
; i
++)
5018 if (attr
->values
[i
].resolution
.xres
<= 0)
5023 "\"%s\": Bad resolution value %dx%d%s - cross "
5024 "feed resolution must be positive "
5025 "(RFC 2911 section 4.1.15).", attr
->name
,
5026 attr
->values
[i
].resolution
.xres
,
5027 attr
->values
[i
].resolution
.yres
,
5028 attr
->values
[i
].resolution
.units
==
5029 IPP_RES_PER_INCH
? "dpi" :
5030 attr
->values
[i
].resolution
.units
==
5031 IPP_RES_PER_CM
? "dpcm" : "unknown");
5034 if (attr
->values
[i
].resolution
.yres
<= 0)
5039 "\"%s\": Bad resolution value %dx%d%s - feed "
5040 "resolution must be positive "
5041 "(RFC 2911 section 4.1.15).", attr
->name
,
5042 attr
->values
[i
].resolution
.xres
,
5043 attr
->values
[i
].resolution
.yres
,
5044 attr
->values
[i
].resolution
.units
==
5045 IPP_RES_PER_INCH
? "dpi" :
5046 attr
->values
[i
].resolution
.units
==
5047 IPP_RES_PER_CM
? "dpcm" : "unknown");
5050 if (attr
->values
[i
].resolution
.units
!= IPP_RES_PER_INCH
&&
5051 attr
->values
[i
].resolution
.units
!= IPP_RES_PER_CM
)
5056 "\"%s\": Bad resolution value %dx%d%s - bad "
5057 "units value (RFC 2911 section 4.1.15).",
5058 attr
->name
, attr
->values
[i
].resolution
.xres
,
5059 attr
->values
[i
].resolution
.yres
,
5060 attr
->values
[i
].resolution
.units
==
5061 IPP_RES_PER_INCH
? "dpi" :
5062 attr
->values
[i
].resolution
.units
==
5063 IPP_RES_PER_CM
? "dpcm" : "unknown");
5068 case IPP_TAG_RANGE
:
5069 for (i
= 0; i
< attr
->num_values
; i
++)
5071 if (attr
->values
[i
].range
.lower
> attr
->values
[i
].range
.upper
)
5076 "\"%s\": Bad rangeOfInteger value %d-%d - lower "
5077 "greater than upper (RFC 2911 section 4.1.13).",
5078 attr
->name
, attr
->values
[i
].range
.lower
,
5079 attr
->values
[i
].range
.upper
);
5084 case IPP_TAG_BEGIN_COLLECTION
:
5085 for (i
= 0; i
< attr
->num_values
; i
++)
5087 for (colattr
= attr
->values
[i
].collection
->attrs
;
5089 colattr
= colattr
->next
)
5091 if (!validate_attr(NULL
, colattr
))
5098 if (colattr
&& errors
)
5100 add_stringf(errors
, "\"%s\": Bad collection value.", attr
->name
);
5104 validate_attr(errors
, colattr
);
5105 colattr
= colattr
->next
;
5112 case IPP_TAG_TEXTLANG
:
5113 for (i
= 0; i
< attr
->num_values
; i
++)
5115 for (ptr
= attr
->values
[i
].string
.text
; *ptr
; ptr
++)
5117 if ((*ptr
& 0xe0) == 0xc0)
5120 if ((*ptr
& 0xc0) != 0x80)
5123 else if ((*ptr
& 0xf0) == 0xe0)
5126 if ((*ptr
& 0xc0) != 0x80)
5129 if ((*ptr
& 0xc0) != 0x80)
5132 else if ((*ptr
& 0xf8) == 0xf0)
5135 if ((*ptr
& 0xc0) != 0x80)
5138 if ((*ptr
& 0xc0) != 0x80)
5141 if ((*ptr
& 0xc0) != 0x80)
5144 else if (*ptr
& 0x80)
5153 "\"%s\": Bad text value \"%s\" - bad UTF-8 "
5154 "sequence (RFC 2911 section 4.1.1).", attr
->name
,
5155 attr
->values
[i
].string
.text
);
5158 if ((ptr
- attr
->values
[i
].string
.text
) > (IPP_MAX_TEXT
- 1))
5163 "\"%s\": Bad text value \"%s\" - bad length %d "
5164 "(RFC 2911 section 4.1.1).", attr
->name
,
5165 attr
->values
[i
].string
.text
,
5166 (int)strlen(attr
->values
[i
].string
.text
));
5172 case IPP_TAG_NAMELANG
:
5173 for (i
= 0; i
< attr
->num_values
; i
++)
5175 for (ptr
= attr
->values
[i
].string
.text
; *ptr
; ptr
++)
5177 if ((*ptr
& 0xe0) == 0xc0)
5180 if ((*ptr
& 0xc0) != 0x80)
5183 else if ((*ptr
& 0xf0) == 0xe0)
5186 if ((*ptr
& 0xc0) != 0x80)
5189 if ((*ptr
& 0xc0) != 0x80)
5192 else if ((*ptr
& 0xf8) == 0xf0)
5195 if ((*ptr
& 0xc0) != 0x80)
5198 if ((*ptr
& 0xc0) != 0x80)
5201 if ((*ptr
& 0xc0) != 0x80)
5204 else if (*ptr
& 0x80)
5213 "\"%s\": Bad name value \"%s\" - bad UTF-8 "
5214 "sequence (RFC 2911 section 4.1.2).", attr
->name
,
5215 attr
->values
[i
].string
.text
);
5218 if ((ptr
- attr
->values
[i
].string
.text
) > (IPP_MAX_NAME
- 1))
5223 "\"%s\": Bad name value \"%s\" - bad length %d "
5224 "(RFC 2911 section 4.1.2).", attr
->name
,
5225 attr
->values
[i
].string
.text
,
5226 (int)strlen(attr
->values
[i
].string
.text
));
5231 case IPP_TAG_KEYWORD
:
5232 for (i
= 0; i
< attr
->num_values
; i
++)
5234 for (ptr
= attr
->values
[i
].string
.text
; *ptr
; ptr
++)
5235 if (!isalnum(*ptr
& 255) && *ptr
!= '-' && *ptr
!= '.' &&
5239 if (*ptr
|| ptr
== attr
->values
[i
].string
.text
)
5244 "\"%s\": Bad keyword value \"%s\" - invalid "
5245 "character (RFC 2911 section 4.1.3).",
5246 attr
->name
, attr
->values
[i
].string
.text
);
5249 if ((ptr
- attr
->values
[i
].string
.text
) > (IPP_MAX_KEYWORD
- 1))
5254 "\"%s\": Bad keyword value \"%s\" - bad "
5255 "length %d (RFC 2911 section 4.1.3).",
5256 attr
->name
, attr
->values
[i
].string
.text
,
5257 (int)strlen(attr
->values
[i
].string
.text
));
5263 for (i
= 0; i
< attr
->num_values
; i
++)
5265 uri_status
= httpSeparateURI(HTTP_URI_CODING_ALL
,
5266 attr
->values
[i
].string
.text
,
5267 scheme
, sizeof(scheme
),
5268 userpass
, sizeof(userpass
),
5269 hostname
, sizeof(hostname
),
5270 &port
, resource
, sizeof(resource
));
5272 if (uri_status
< HTTP_URI_OK
)
5277 "\"%s\": Bad URI value \"%s\" - %s "
5278 "(RFC 2911 section 4.1.5).", attr
->name
,
5279 attr
->values
[i
].string
.text
,
5280 URIStatusStrings
[uri_status
-
5281 HTTP_URI_OVERFLOW
]);
5284 if (strlen(attr
->values
[i
].string
.text
) > (IPP_MAX_URI
- 1))
5289 "\"%s\": Bad URI value \"%s\" - bad length %d "
5290 "(RFC 2911 section 4.1.5).", attr
->name
,
5291 attr
->values
[i
].string
.text
,
5292 (int)strlen(attr
->values
[i
].string
.text
));
5297 case IPP_TAG_URISCHEME
:
5298 for (i
= 0; i
< attr
->num_values
; i
++)
5300 ptr
= attr
->values
[i
].string
.text
;
5301 if (islower(*ptr
& 255))
5303 for (ptr
++; *ptr
; ptr
++)
5304 if (!islower(*ptr
& 255) && !isdigit(*ptr
& 255) &&
5305 *ptr
!= '+' && *ptr
!= '-' && *ptr
!= '.')
5309 if (*ptr
|| ptr
== attr
->values
[i
].string
.text
)
5314 "\"%s\": Bad uriScheme value \"%s\" - bad "
5315 "characters (RFC 2911 section 4.1.6).",
5316 attr
->name
, attr
->values
[i
].string
.text
);
5319 if ((ptr
- attr
->values
[i
].string
.text
) > (IPP_MAX_URISCHEME
- 1))
5324 "\"%s\": Bad uriScheme value \"%s\" - bad "
5325 "length %d (RFC 2911 section 4.1.6).",
5326 attr
->name
, attr
->values
[i
].string
.text
,
5327 (int)strlen(attr
->values
[i
].string
.text
));
5332 case IPP_TAG_CHARSET
:
5333 for (i
= 0; i
< attr
->num_values
; i
++)
5335 for (ptr
= attr
->values
[i
].string
.text
; *ptr
; ptr
++)
5336 if (!isprint(*ptr
& 255) || isupper(*ptr
& 255) ||
5337 isspace(*ptr
& 255))
5340 if (*ptr
|| ptr
== attr
->values
[i
].string
.text
)
5345 "\"%s\": Bad charset value \"%s\" - bad "
5346 "characters (RFC 2911 section 4.1.7).",
5347 attr
->name
, attr
->values
[i
].string
.text
);
5350 if ((ptr
- attr
->values
[i
].string
.text
) > (IPP_MAX_CHARSET
- 1))
5355 "\"%s\": Bad charset value \"%s\" - bad "
5356 "length %d (RFC 2911 section 4.1.7).",
5357 attr
->name
, attr
->values
[i
].string
.text
,
5358 (int)strlen(attr
->values
[i
].string
.text
));
5363 case IPP_TAG_LANGUAGE
:
5365 * The following regular expression is derived from the ABNF for
5366 * language tags in RFC 4646. All I can say is that this is the
5367 * easiest way to check the values...
5370 if ((i
= regcomp(&re
,
5372 "(([a-z]{2,3}(-[a-z][a-z][a-z]){0,3})|[a-z]{4,8})"
5374 "(-[a-z][a-z][a-z][a-z]){0,1}" /* script */
5375 "(-([a-z][a-z]|[0-9][0-9][0-9])){0,1}" /* region */
5376 "(-([a-z]{5,8}|[0-9][0-9][0-9]))*" /* variant */
5377 "(-[a-wy-z](-[a-z0-9]{2,8})+)*" /* extension */
5378 "(-x(-[a-z0-9]{1,8})+)*" /* privateuse */
5380 "x(-[a-z0-9]{1,8})+" /* privateuse */
5382 "[a-z]{1,3}(-[a-z][0-9]{2,8}){1,2}" /* grandfathered */
5384 REG_NOSUB
| REG_EXTENDED
)) != 0)
5386 char temp
[256]; /* Temporary error string */
5388 regerror(i
, &re
, temp
, sizeof(temp
));
5389 print_fatal_error("Unable to compile naturalLanguage regular "
5390 "expression: %s.", temp
);
5394 for (i
= 0; i
< attr
->num_values
; i
++)
5396 if (regexec(&re
, attr
->values
[i
].string
.text
, 0, NULL
, 0))
5401 "\"%s\": Bad naturalLanguage value \"%s\" - bad "
5402 "characters (RFC 2911 section 4.1.8).",
5403 attr
->name
, attr
->values
[i
].string
.text
);
5406 if (strlen(attr
->values
[i
].string
.text
) > (IPP_MAX_LANGUAGE
- 1))
5411 "\"%s\": Bad naturalLanguage value \"%s\" - bad "
5412 "length %d (RFC 2911 section 4.1.8).",
5413 attr
->name
, attr
->values
[i
].string
.text
,
5414 (int)strlen(attr
->values
[i
].string
.text
));
5421 case IPP_TAG_MIMETYPE
:
5423 * The following regular expression is derived from the ABNF for
5424 * language tags in RFC 2045 and 4288. All I can say is that this is
5425 * the easiest way to check the values...
5428 if ((i
= regcomp(&re
,
5430 "[-a-zA-Z0-9!#$&.+^_]{1,127}" /* type-name */
5432 "[-a-zA-Z0-9!#$&.+^_]{1,127}" /* subtype-name */
5433 "(;[-a-zA-Z0-9!#$&.+^_]{1,127}=" /* parameter= */
5434 "([-a-zA-Z0-9!#$&.+^_]{1,127}|\"[^\"]*\"))*"
5437 REG_NOSUB
| REG_EXTENDED
)) != 0)
5439 char temp
[256]; /* Temporary error string */
5441 regerror(i
, &re
, temp
, sizeof(temp
));
5442 print_fatal_error("Unable to compile mimeMediaType regular "
5443 "expression: %s.", temp
);
5447 for (i
= 0; i
< attr
->num_values
; i
++)
5449 if (regexec(&re
, attr
->values
[i
].string
.text
, 0, NULL
, 0))
5454 "\"%s\": Bad mimeMediaType value \"%s\" - bad "
5455 "characters (RFC 2911 section 4.1.9).",
5456 attr
->name
, attr
->values
[i
].string
.text
);
5459 if (strlen(attr
->values
[i
].string
.text
) > (IPP_MAX_MIMETYPE
- 1))
5464 "\"%s\": Bad mimeMediaType value \"%s\" - bad "
5465 "length %d (RFC 2911 section 4.1.9).",
5466 attr
->name
, attr
->values
[i
].string
.text
,
5467 (int)strlen(attr
->values
[i
].string
.text
));
5483 * 'with_value()' - Test a WITH-VALUE predicate.
5486 static int /* O - 1 on match, 0 on non-match */
5487 with_value(cups_array_t
*errors
, /* I - Errors array */
5488 char *value
, /* I - Value string */
5489 int flags
, /* I - Flags for match */
5490 ipp_attribute_t
*attr
, /* I - Attribute to compare */
5491 char *matchbuf
, /* I - Buffer to hold matching value */
5492 size_t matchlen
) /* I - Length of match buffer */
5494 int i
, /* Looping var */
5496 char temp
[1024], /* Temporary value string */
5497 *valptr
; /* Pointer into value */
5501 match
= (flags
& _CUPS_WITH_ALL
) ? 1 : 0;
5504 * NULL matches everything.
5507 if (!value
|| !*value
)
5511 * Compare the value string to the attribute value.
5514 switch (attr
->value_tag
)
5516 case IPP_TAG_INTEGER
:
5518 for (i
= 0; i
< attr
->num_values
; i
++)
5520 char op
, /* Comparison operator */
5521 *nextptr
; /* Next pointer */
5522 int intvalue
, /* Integer value */
5523 valmatch
= 0; /* Does the current value match? */
5527 while (isspace(*valptr
& 255) || isdigit(*valptr
& 255) ||
5528 *valptr
== '-' || *valptr
== ',' || *valptr
== '<' ||
5529 *valptr
== '=' || *valptr
== '>')
5532 while (*valptr
&& !isdigit(*valptr
& 255) && *valptr
!= '-')
5534 if (*valptr
== '<' || *valptr
== '>' || *valptr
== '=')
5542 intvalue
= strtol(valptr
, &nextptr
, 0);
5543 if (nextptr
== valptr
)
5547 if ((op
== '=' && attr
->values
[i
].integer
== intvalue
) ||
5548 (op
== '<' && attr
->values
[i
].integer
< intvalue
) ||
5549 (op
== '>' && attr
->values
[i
].integer
> intvalue
))
5552 snprintf(matchbuf
, matchlen
, "%d",
5553 attr
->values
[i
].integer
);
5560 if (flags
& _CUPS_WITH_ALL
)
5575 if (!match
&& errors
)
5577 for (i
= 0; i
< attr
->num_values
; i
++)
5578 add_stringf(errors
, "GOT: %s=%d", attr
->name
,
5579 attr
->values
[i
].integer
);
5583 case IPP_TAG_RANGE
:
5584 for (i
= 0; i
< attr
->num_values
; i
++)
5586 char op
, /* Comparison operator */
5587 *nextptr
; /* Next pointer */
5588 int intvalue
, /* Integer value */
5589 valmatch
= 0; /* Does the current value match? */
5593 while (isspace(*valptr
& 255) || isdigit(*valptr
& 255) ||
5594 *valptr
== '-' || *valptr
== ',' || *valptr
== '<' ||
5595 *valptr
== '=' || *valptr
== '>')
5598 while (*valptr
&& !isdigit(*valptr
& 255) && *valptr
!= '-')
5600 if (*valptr
== '<' || *valptr
== '>' || *valptr
== '=')
5608 intvalue
= strtol(valptr
, &nextptr
, 0);
5609 if (nextptr
== valptr
)
5613 if ((op
== '=' && (attr
->values
[i
].range
.lower
== intvalue
||
5614 attr
->values
[i
].range
.upper
== intvalue
)) ||
5615 (op
== '<' && attr
->values
[i
].range
.upper
< intvalue
) ||
5616 (op
== '>' && attr
->values
[i
].range
.upper
> intvalue
))
5619 snprintf(matchbuf
, matchlen
, "%d-%d",
5620 attr
->values
[0].range
.lower
,
5621 attr
->values
[0].range
.upper
);
5628 if (flags
& _CUPS_WITH_ALL
)
5643 if (!match
&& errors
)
5645 for (i
= 0; i
< attr
->num_values
; i
++)
5646 add_stringf(errors
, "GOT: %s=%d-%d", attr
->name
,
5647 attr
->values
[i
].range
.lower
,
5648 attr
->values
[i
].range
.upper
);
5652 case IPP_TAG_BOOLEAN
:
5653 for (i
= 0; i
< attr
->num_values
; i
++)
5655 if (!strcmp(value
, "true") == attr
->values
[i
].boolean
)
5658 strlcpy(matchbuf
, value
, matchlen
);
5660 if (!(flags
& _CUPS_WITH_ALL
))
5666 else if (flags
& _CUPS_WITH_ALL
)
5673 if (!match
&& errors
)
5675 for (i
= 0; i
< attr
->num_values
; i
++)
5676 add_stringf(errors
, "GOT: %s=%s", attr
->name
,
5677 attr
->values
[i
].boolean
? "true" : "false");
5681 case IPP_TAG_RESOLUTION
:
5682 for (i
= 0; i
< attr
->num_values
; i
++)
5684 if (attr
->values
[i
].resolution
.xres
==
5685 attr
->values
[i
].resolution
.yres
)
5686 snprintf(temp
, sizeof(temp
), "%d%s",
5687 attr
->values
[i
].resolution
.xres
,
5688 attr
->values
[i
].resolution
.units
== IPP_RES_PER_INCH
?
5691 snprintf(temp
, sizeof(temp
), "%dx%d%s",
5692 attr
->values
[i
].resolution
.xres
,
5693 attr
->values
[i
].resolution
.yres
,
5694 attr
->values
[i
].resolution
.units
== IPP_RES_PER_INCH
?
5697 if (!strcmp(value
, temp
))
5700 strlcpy(matchbuf
, value
, matchlen
);
5702 if (!(flags
& _CUPS_WITH_ALL
))
5708 else if (flags
& _CUPS_WITH_ALL
)
5715 if (!match
&& errors
)
5717 for (i
= 0; i
< attr
->num_values
; i
++)
5719 if (attr
->values
[i
].resolution
.xres
==
5720 attr
->values
[i
].resolution
.yres
)
5721 snprintf(temp
, sizeof(temp
), "%d%s",
5722 attr
->values
[i
].resolution
.xres
,
5723 attr
->values
[i
].resolution
.units
== IPP_RES_PER_INCH
?
5726 snprintf(temp
, sizeof(temp
), "%dx%d%s",
5727 attr
->values
[i
].resolution
.xres
,
5728 attr
->values
[i
].resolution
.yres
,
5729 attr
->values
[i
].resolution
.units
== IPP_RES_PER_INCH
?
5732 if (strcmp(value
, temp
))
5733 add_stringf(errors
, "GOT: %s=%s", attr
->name
, temp
);
5738 case IPP_TAG_NOVALUE
:
5739 case IPP_TAG_UNKNOWN
:
5742 case IPP_TAG_CHARSET
:
5743 case IPP_TAG_KEYWORD
:
5744 case IPP_TAG_LANGUAGE
:
5745 case IPP_TAG_MIMETYPE
:
5747 case IPP_TAG_NAMELANG
:
5749 case IPP_TAG_TEXTLANG
:
5751 case IPP_TAG_URISCHEME
:
5752 if (flags
& _CUPS_WITH_REGEX
)
5755 * Value is an extended, case-sensitive POSIX regular expression...
5758 regex_t re
; /* Regular expression */
5760 if ((i
= regcomp(&re
, value
, REG_EXTENDED
| REG_NOSUB
)) != 0)
5762 regerror(i
, &re
, temp
, sizeof(temp
));
5764 print_fatal_error("Unable to compile WITH-VALUE regular expression "
5765 "\"%s\" - %s", value
, temp
);
5770 * See if ALL of the values match the given regular expression.
5773 for (i
= 0; i
< attr
->num_values
; i
++)
5775 if (!regexec(&re
, get_string(attr
, i
, flags
, temp
, sizeof(temp
)),
5780 get_string(attr
, i
, flags
, temp
, sizeof(temp
)),
5783 if (!(flags
& _CUPS_WITH_ALL
))
5789 else if (flags
& _CUPS_WITH_ALL
)
5801 * Value is a literal string, see if the value(s) match...
5804 for (i
= 0; i
< attr
->num_values
; i
++)
5806 if (!strcmp(value
, get_string(attr
, i
, flags
, temp
, sizeof(temp
))))
5810 get_string(attr
, i
, flags
, temp
, sizeof(temp
)),
5813 if (!(flags
& _CUPS_WITH_ALL
))
5819 else if (flags
& _CUPS_WITH_ALL
)
5827 if (!match
&& errors
)
5829 for (i
= 0; i
< attr
->num_values
; i
++)
5830 add_stringf(errors
, "GOT: %s=\"%s\"", attr
->name
,
5831 attr
->values
[i
].string
.text
);