diff options
author | Anas Nashif <anas.nashif@intel.com> | 2012-10-30 15:47:44 -0700 |
---|---|---|
committer | Anas Nashif <anas.nashif@intel.com> | 2012-10-30 15:47:44 -0700 |
commit | 4c8dd44ec57d63e728bda99034c043b8941419df (patch) | |
tree | 5531f93b7c293790552944e6981a1745baaf7226 /test/ipptool.c | |
download | cups-4c8dd44ec57d63e728bda99034c043b8941419df.tar.gz cups-4c8dd44ec57d63e728bda99034c043b8941419df.tar.bz2 cups-4c8dd44ec57d63e728bda99034c043b8941419df.zip |
Imported Upstream version 1.5.3upstream/1.5.3
Diffstat (limited to 'test/ipptool.c')
-rw-r--r-- | test/ipptool.c | 5416 |
1 files changed, 5416 insertions, 0 deletions
diff --git a/test/ipptool.c b/test/ipptool.c new file mode 100644 index 0000000..23270ee --- /dev/null +++ b/test/ipptool.c @@ -0,0 +1,5416 @@ +/* + * "$Id: ipptool.c 10090 2011-10-25 22:39:56Z mike $" + * + * ipptool command for CUPS. + * + * Copyright 2007-2011 by Apple Inc. + * Copyright 1997-2007 by Easy Software Products. + * + * These coded instructions, statements, and computer programs are the + * property of Apple Inc. and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "LICENSE.txt" + * which should have been included with this file. If this file is + * file is missing or damaged, see the license at "http://www.cups.org/". + * + * This file is subject to the Apple OS-Developed Software exception. + * + * Contents: + * + * main() - Parse options and do tests. + * compare_vars() - Compare two variables. + * do_tests() - Do tests as specified in the test file. + * expand_variables() - Expand variables in a string. + * expect_matches() - Return true if the tag matches the specification. + * get_collection() - Get a collection value from the current test file. + * get_filename() - Get a filename based on the current test file. + * get_token() - Get a token from a file. + * get_variable() - Get the value of a variable. + * iso_date() - Return an ISO 8601 date/time string for the given IPP + * dateTime value. + * password_cb() - Password callback for authenticated tests. + * print_attr() - Print an attribute on the screen. + * print_col() - Print a collection attribute on the screen. + * print_csv() - Print a line of CSV text. + * print_fatal_error() - Print a fatal error message. + * print_line() - Print a line of formatted or CSV text. + * print_test_error() - Print a test error message. + * print_xml_header() - Print a standard XML plist header. + * print_xml_string() - Print an XML string with escaping. + * print_xml_trailer() - Print the XML trailer with success/fail value. + * set_variable() - Set a variable value. + * sigterm_handler() - Handle SIGINT and SIGTERM. + * timeout_cb() - Handle HTTP timeouts. + * usage() - Show program usage. + * validate_attr() - Determine whether an attribute is valid. + * with_value() - Test a WITH-VALUE predicate. + */ + +/* + * Include necessary headers... + */ + +#include <cups/cups-private.h> +#include <cups/file-private.h> +#include <regex.h> +#include <sys/stat.h> +#ifndef WIN32 +# include <signal.h> +#endif /* WIN32 */ +#ifndef O_BINARY +# define O_BINARY 0 +#endif /* !O_BINARY */ + + +/* + * Types... + */ + +typedef enum _cups_transfer_e /**** How to send request data ****/ +{ + _CUPS_TRANSFER_AUTO, /* Chunk for files, length for static */ + _CUPS_TRANSFER_CHUNKED, /* Chunk always */ + _CUPS_TRANSFER_LENGTH /* Length always */ +} _cups_transfer_t; + +typedef enum _cups_output_e /**** Output mode ****/ +{ + _CUPS_OUTPUT_QUIET, /* No output */ + _CUPS_OUTPUT_TEST, /* Traditional CUPS test output */ + _CUPS_OUTPUT_PLIST, /* XML plist test output */ + _CUPS_OUTPUT_LIST, /* Tabular list output */ + _CUPS_OUTPUT_CSV /* Comma-separated values output */ +} _cups_output_t; + +typedef struct _cups_expect_s /**** Expected attribute info ****/ +{ + int optional, /* Optional attribute? */ + not_expect; /* Don't expect attribute? */ + char *name, /* Attribute name */ + *of_type, /* Type name */ + *same_count_as, /* Parallel attribute name */ + *if_defined, /* Only required if variable defined */ + *if_not_defined, /* Only required if variable is not defined */ + *with_value, /* Attribute must include this value */ + *define_match, /* Variable to define on match */ + *define_no_match, /* Variable to define on no-match */ + *define_value; /* Variable to define with value */ + int repeat_match, /* Repeat test on match */ + repeat_no_match, /* Repeat test on no match */ + with_regex, /* WITH-VALUE is a regular expression */ + count; /* Expected count if > 0 */ + ipp_tag_t in_group; /* IN-GROUP value */ +} _cups_expect_t; + +typedef struct _cups_status_s /**** Status info ****/ +{ + ipp_status_t status; /* Expected status code */ + char *if_defined, /* Only if variable is defined */ + *if_not_defined; /* Only if variable is not defined */ + int repeat_match, /* Repeat the test when it does not match */ + repeat_no_match; /* Repeat the test when it matches */ +} _cups_status_t; + +typedef struct _cups_var_s /**** Variable ****/ +{ + char *name, /* Name of variable */ + *value; /* Value of variable */ +} _cups_var_t; + +typedef struct _cups_vars_s /**** Set of variables ****/ +{ + char *uri, /* URI for printer */ + *filename, /* Filename */ + scheme[64], /* Scheme from URI */ + userpass[256], /* Username/password from URI */ + hostname[256], /* Hostname from URI */ + resource[1024]; /* Resource path from URI */ + int port; /* Port number from URI */ + http_encryption_t encryption; /* Encryption for connection? */ + double timeout; /* Timeout for connection */ + int family; /* Address family */ + cups_array_t *vars; /* Array of variables */ +} _cups_vars_t; + + +/* + * Globals... + */ + +_cups_transfer_t Transfer = _CUPS_TRANSFER_AUTO; + /* How to transfer requests */ +_cups_output_t Output = _CUPS_OUTPUT_LIST; + /* Output mode */ +int Cancel = 0, /* Cancel test? */ + IgnoreErrors = 0, /* Ignore errors? */ + Verbosity = 0, /* Show all attributes? */ + Version = 11, /* Default IPP version */ + XMLHeader = 0, /* 1 if header is written */ + TestCount = 0, /* Number of tests run */ + PassCount = 0, /* Number of passing tests */ + FailCount = 0, /* Number of failing tests */ + SkipCount = 0; /* Number of skipped tests */ +char *Password = NULL; /* Password from URI */ +const char * const URIStatusStrings[] = /* URI status strings */ +{ + "URI too large", + "Bad arguments to function", + "Bad resource in URI", + "Bad port number in URI", + "Bad hostname/address in URI", + "Bad username in URI", + "Bad scheme in URI", + "Bad/empty URI", + "OK", + "Missing scheme in URI", + "Unknown scheme in URI", + "Missing resource in URI" +}; + + +/* + * Local functions... + */ + +static int compare_vars(_cups_var_t *a, _cups_var_t *b); +static int do_tests(_cups_vars_t *vars, const char *testfile); +static void expand_variables(_cups_vars_t *vars, char *dst, const char *src, + size_t dstsize) +#ifdef __GNUC__ +__attribute((nonnull(1,2,3))) +#endif /* __GNUC__ */ +; +static int expect_matches(_cups_expect_t *expect, ipp_tag_t value_tag); +static ipp_t *get_collection(_cups_vars_t *vars, FILE *fp, int *linenum); +static char *get_filename(const char *testfile, char *dst, const char *src, + size_t dstsize); +static char *get_token(FILE *fp, char *buf, int buflen, + int *linenum); +static char *get_variable(_cups_vars_t *vars, const char *name); +static char *iso_date(ipp_uchar_t *date); +static const char *password_cb(const char *prompt); +static void print_attr(ipp_attribute_t *attr, ipp_tag_t *group); +static void print_col(ipp_t *col); +static void print_csv(ipp_attribute_t *attr, int num_displayed, + char **displayed, size_t *widths); +static void print_fatal_error(const char *s, ...) +#ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 1, 2))) +#endif /* __GNUC__ */ +; +static void print_line(ipp_attribute_t *attr, int num_displayed, + char **displayed, size_t *widths); +static void print_test_error(const char *s, ...) +#ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 1, 2))) +#endif /* __GNUC__ */ +; +static void print_xml_header(void); +static void print_xml_string(const char *element, const char *s); +static void print_xml_trailer(int success, const char *message); +static void set_variable(_cups_vars_t *vars, const char *name, + const char *value); +#ifndef WIN32 +static void sigterm_handler(int sig); +#endif /* WIN32 */ +static int timeout_cb(http_t *http, void *user_data); +static void usage(void); +static int validate_attr(ipp_attribute_t *attr, int print); +static int with_value(char *value, int regex, ipp_attribute_t *attr, + int report, char *matchbuf, size_t matchlen); + + +/* + * 'main()' - Parse options and do tests. + */ + +int /* O - Exit status */ +main(int argc, /* I - Number of command-line args */ + char *argv[]) /* I - Command-line arguments */ +{ + int i; /* Looping var */ + int status; /* Status of tests... */ + char *opt, /* Current option */ + name[1024], /* Name/value buffer */ + *value, /* Pointer to value */ + filename[1024], /* Real filename */ + testname[1024], /* Real test filename */ + uri[1024]; /* Copy of printer URI */ + const char *ext, /* Extension on filename */ + *testfile; /* Test file to use */ + int interval, /* Test interval in microseconds */ + repeat; /* Repeat count */ + _cups_vars_t vars; /* Variables */ + http_uri_status_t uri_status; /* URI separation status */ + _cups_globals_t *cg = _cupsGlobals(); + /* Global data */ + + +#ifndef WIN32 + /* + * Catch SIGINT and SIGTERM... + */ + + signal(SIGINT, sigterm_handler); + signal(SIGTERM, sigterm_handler); +#endif /* !WIN32 */ + + /* + * Initialize the locale and variables... + */ + + _cupsSetLocale(argv); + + memset(&vars, 0, sizeof(vars)); + vars.family = AF_UNSPEC; + vars.vars = cupsArrayNew((cups_array_func_t)compare_vars, NULL); + + /* + * We need at least: + * + * ipptool URI testfile + */ + + interval = 0; + repeat = 0; + status = 0; + testfile = NULL; + + for (i = 1; i < argc; i ++) + { + if (argv[i][0] == '-') + { + for (opt = argv[i] + 1; *opt; opt ++) + { + switch (*opt) + { + case '4' : /* Connect using IPv4 only */ + vars.family = AF_INET; + break; + +#ifdef AF_INET6 + case '6' : /* Connect using IPv6 only */ + vars.family = AF_INET6; + break; +#endif /* AF_INET6 */ + + case 'C' : /* Enable HTTP chunking */ + Transfer = _CUPS_TRANSFER_CHUNKED; + break; + + case 'E' : /* Encrypt with TLS */ +#ifdef HAVE_SSL + vars.encryption = HTTP_ENCRYPT_REQUIRED; +#else + _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."), + argv[0]); +#endif /* HAVE_SSL */ + break; + + case 'I' : /* Ignore errors */ + IgnoreErrors = 1; + break; + + case 'L' : /* Disable HTTP chunking */ + Transfer = _CUPS_TRANSFER_LENGTH; + break; + + case 'S' : /* Encrypt with SSL */ +#ifdef HAVE_SSL + vars.encryption = HTTP_ENCRYPT_ALWAYS; +#else + _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."), + argv[0]); +#endif /* HAVE_SSL */ + break; + + case 'T' : /* Set timeout */ + i ++; + + if (i >= argc) + { + _cupsLangPuts(stderr, + _("ipptool: Missing timeout for \"-T\".")); + usage(); + } + + vars.timeout = _cupsStrScand(argv[i], NULL, localeconv()); + break; + + case 'V' : /* Set IPP version */ + i ++; + + if (i >= argc) + { + _cupsLangPuts(stderr, + _("ipptool: Missing version for \"-V\".")); + usage(); + } + + if (!strcmp(argv[i], "1.0")) + Version = 10; + else if (!strcmp(argv[i], "1.1")) + Version = 11; + else if (!strcmp(argv[i], "2.0")) + Version = 20; + else if (!strcmp(argv[i], "2.1")) + Version = 21; + else if (!strcmp(argv[i], "2.2")) + Version = 22; + else + { + _cupsLangPrintf(stderr, + _("ipptool: Bad version %s for \"-V\"."), + argv[i]); + usage(); + } + break; + + case 'X' : /* Produce XML output */ + Output = _CUPS_OUTPUT_PLIST; + + if (interval || repeat) + { + _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are " + "incompatible with -X\".")); + usage(); + } + break; + + case 'c' : /* CSV output */ + Output = _CUPS_OUTPUT_CSV; + break; + + case 'd' : /* Define a variable */ + i ++; + + if (i >= argc) + { + _cupsLangPuts(stderr, + _("ipptool: Missing name=value for \"-d\".")); + usage(); + } + + strlcpy(name, argv[i], sizeof(name)); + if ((value = strchr(name, '=')) != NULL) + *value++ = '\0'; + else + value = name + strlen(name); + + set_variable(&vars, name, value); + break; + + case 'f' : /* Set the default test filename */ + i ++; + + if (i >= argc) + { + _cupsLangPuts(stderr, + _("ipptool: Missing filename for \"-f\".")); + usage(); + } + + if (vars.filename) + free(vars.filename); + + if (access(argv[i], 0)) + { + /* + * Try filename.gz... + */ + + snprintf(filename, sizeof(filename), "%s.gz", argv[i]); + if (access(filename, 0) && filename[0] != '/') + { + snprintf(filename, sizeof(filename), "%s/ipptool/%s", + cg->cups_datadir, argv[i]); + if (access(filename, 0)) + { + snprintf(filename, sizeof(filename), "%s/ipptool/%s.gz", + cg->cups_datadir, argv[i]); + if (access(filename, 0)) + vars.filename = strdup(argv[i]); + } + else + vars.filename = strdup(filename); + } + else + vars.filename = strdup(filename); + } + else + vars.filename = strdup(argv[i]); + + if ((ext = strrchr(vars.filename, '.')) != NULL) + { + /* + * Guess the MIME media type based on the extension... + */ + + if (!_cups_strcasecmp(ext, ".gif")) + set_variable(&vars, "filetype", "image/gif"); + else if (!_cups_strcasecmp(ext, ".htm") || + !_cups_strcasecmp(ext, ".htm.gz") || + !_cups_strcasecmp(ext, ".html") || + !_cups_strcasecmp(ext, ".html.gz")) + set_variable(&vars, "filetype", "text/html"); + else if (!_cups_strcasecmp(ext, ".jpg")) + set_variable(&vars, "filetype", "image/jpeg"); + else if (!_cups_strcasecmp(ext, ".pdf")) + set_variable(&vars, "filetype", "application/pdf"); + else if (!_cups_strcasecmp(ext, ".png")) + set_variable(&vars, "filetype", "image/png"); + else if (!_cups_strcasecmp(ext, ".ps") || + !_cups_strcasecmp(ext, ".ps.gz")) + set_variable(&vars, "filetype", "application/postscript"); + else if (!_cups_strcasecmp(ext, ".ras") || + !_cups_strcasecmp(ext, ".ras.gz")) + set_variable(&vars, "filetype", "image/pwg-raster"); + else if (!_cups_strcasecmp(ext, ".txt") || + !_cups_strcasecmp(ext, ".txt.gz")) + set_variable(&vars, "filetype", "text/plain"); + else if (!_cups_strcasecmp(ext, ".xps")) + set_variable(&vars, "filetype", "application/openxps"); + else + set_variable(&vars, "filetype", "application/octet-stream"); + } + else + { + /* + * Use the "auto-type" MIME media type... + */ + + set_variable(&vars, "filetype", "application/octet-stream"); + } + break; + + case 'i' : /* Test every N seconds */ + i ++; + + if (i >= argc) + { + _cupsLangPuts(stderr, + _("ipptool: Missing seconds for \"-i\".")); + usage(); + } + else + { + interval = (int)(_cupsStrScand(argv[i], NULL, localeconv()) * + 1000000.0); + if (interval <= 0) + { + _cupsLangPuts(stderr, + _("ipptool: Invalid seconds for \"-i\".")); + usage(); + } + } + + if (Output == _CUPS_OUTPUT_PLIST && interval) + { + _cupsLangPuts(stderr, _("ipptool: \"-i\" is incompatible with " + "\"-X\".")); + usage(); + } + break; + + case 'l' : /* List as a table */ + Output = _CUPS_OUTPUT_LIST; + break; + + case 'n' : /* Repeat count */ + i ++; + + if (i >= argc) + { + _cupsLangPuts(stderr, + _("ipptool: Missing count for \"-n\".")); + usage(); + } + else + repeat = atoi(argv[i]); + + if (Output == _CUPS_OUTPUT_PLIST && repeat) + { + _cupsLangPuts(stderr, _("ipptool: \"-n\" is incompatible with " + "\"-X\".")); + usage(); + } + break; + + case 'q' : /* Be quiet */ + Output = _CUPS_OUTPUT_QUIET; + break; + + case 't' : /* CUPS test output */ + Output = _CUPS_OUTPUT_TEST; + break; + + case 'v' : /* Be verbose */ + Verbosity ++; + break; + + default : + _cupsLangPrintf(stderr, _("ipptool: Unknown option \"-%c\"."), + *opt); + usage(); + break; + } + } + } + else if (!strncmp(argv[i], "ipp://", 6) || !strncmp(argv[i], "http://", 7) +#ifdef HAVE_SSL + || !strncmp(argv[i], "ipps://", 7) + || !strncmp(argv[i], "https://", 8) +#endif /* HAVE_SSL */ + ) + { + /* + * Set URI... + */ + + if (vars.uri) + { + _cupsLangPuts(stderr, _("ipptool: May only specify a single URI.")); + usage(); + } + +#ifdef HAVE_SSL + if (!strncmp(argv[i], "ipps://", 7) || !strncmp(argv[i], "https://", 8)) + vars.encryption = HTTP_ENCRYPT_ALWAYS; +#endif /* HAVE_SSL */ + + vars.uri = argv[i]; + uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, vars.uri, + vars.scheme, sizeof(vars.scheme), + vars.userpass, sizeof(vars.userpass), + vars.hostname, sizeof(vars.hostname), + &(vars.port), + vars.resource, sizeof(vars.resource)); + + if (uri_status != HTTP_URI_OK) + { + _cupsLangPrintf(stderr, _("ipptool: Bad URI - %s."), + URIStatusStrings[uri_status - HTTP_URI_OVERFLOW]); + return (1); + } + + if (vars.userpass[0]) + { + if ((Password = strchr(vars.userpass, ':')) != NULL) + *Password++ = '\0'; + + cupsSetUser(vars.userpass); + cupsSetPasswordCB(password_cb); + set_variable(&vars, "uriuser", vars.userpass); + } + + httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), vars.scheme, NULL, + vars.hostname, vars.port, vars.resource); + vars.uri = uri; + } + else + { + /* + * Run test... + */ + + if (!vars.uri) + { + _cupsLangPuts(stderr, _("ipptool: URI required before test file.")); + usage(); + } + + if (access(argv[i], 0) && argv[i][0] != '/') + { + snprintf(testname, sizeof(testname), "%s/ipptool/%s", cg->cups_datadir, + argv[i]); + if (access(testname, 0)) + testfile = argv[i]; + else + testfile = testname; + } + else + testfile = argv[i]; + + if (!do_tests(&vars, testfile)) + status = 1; + } + } + + if (!vars.uri || !testfile) + usage(); + + /* + * Loop if the interval is set... + */ + + if (Output == _CUPS_OUTPUT_PLIST) + print_xml_trailer(!status, NULL); + else if (interval > 0 && repeat > 0) + { + while (repeat > 1) + { + usleep(interval); + do_tests(&vars, testfile); + repeat --; + } + } + else if (interval > 0) + { + for (;;) + { + usleep(interval); + do_tests(&vars, testfile); + } + } + else if (Output == _CUPS_OUTPUT_TEST && TestCount > 1) + { + /* + * Show a summary report if there were multiple tests... + */ + + printf("\nSummary: %d tests, %d passed, %d failed, %d skipped\n" + "Score: %d%%\n", TestCount, PassCount, FailCount, SkipCount, + 100 * (PassCount + SkipCount) / TestCount); + } + + /* + * Exit... + */ + + return (status); +} + + +/* + * 'compare_vars()' - Compare two variables. + */ + +static int /* O - Result of comparison */ +compare_vars(_cups_var_t *a, /* I - First variable */ + _cups_var_t *b) /* I - Second variable */ +{ + return (_cups_strcasecmp(a->name, b->name)); +} + + +/* + * 'do_tests()' - Do tests as specified in the test file. + */ + +static int /* 1 = success, 0 = failure */ +do_tests(_cups_vars_t *vars, /* I - Variables */ + const char *testfile) /* I - Test file to use */ +{ + int i, /* Looping var */ + linenum, /* Current line number */ + pass, /* Did we pass the test? */ + prev_pass = 1, /* Did we pass the previous test? */ + request_id, /* Current request ID */ + show_header = 1, /* Show the test header? */ + ignore_errors, /* Ignore test failures? */ + skip_previous = 0, /* Skip on previous test failure? */ + repeat_test; /* Repeat a test? */ + http_t *http = NULL; /* HTTP connection to server */ + FILE *fp = NULL; /* Test file */ + char resource[512], /* Resource for request */ + token[1024], /* Token from file */ + *tokenptr, /* Pointer into token */ + temp[1024], /* Temporary string */ + buffer[8192]; /* Copy buffer */ + ipp_t *request = NULL, /* IPP request */ + *response = NULL; /* IPP response */ + size_t length; /* Length of IPP request */ + http_status_t status; /* HTTP status */ + cups_file_t *reqfile; /* File to send */ + ssize_t bytes; /* Bytes read/written */ + char attr[128]; /* Attribute name */ + ipp_op_t op; /* Operation */ + ipp_tag_t group; /* Current group */ + ipp_tag_t value; /* Current value type */ + ipp_attribute_t *attrptr, /* Attribute pointer */ + *found, /* Found attribute */ + *lastcol = NULL; /* Last collection attribute */ + char name[1024]; /* Name of test */ + char filename[1024]; /* Filename */ + _cups_transfer_t transfer; /* To chunk or not to chunk */ + int version, /* IPP version number to use */ + skip_test; /* Skip this test? */ + int num_statuses = 0; /* Number of valid status codes */ + _cups_status_t statuses[100], /* Valid status codes */ + *last_status; /* Last STATUS (for predicates) */ + int num_expects = 0; /* Number of expected attributes */ + _cups_expect_t expects[200], /* Expected attributes */ + *expect, /* Current expected attribute */ + *last_expect; /* Last EXPECT (for predicates) */ + int num_displayed = 0; /* Number of displayed attributes */ + char *displayed[200]; /* Displayed attributes */ + size_t widths[200]; /* Width of columns */ + cups_array_t *a; /* Duplicate attribute array */ + + + /* + * Open the test file... + */ + + if ((fp = fopen(testfile, "r")) == NULL) + { + print_fatal_error("Unable to open test file %s - %s", testfile, + strerror(errno)); + pass = 0; + goto test_exit; + } + + /* + * Connect to the server... + */ + + if ((http = _httpCreate(vars->hostname, vars->port, NULL, vars->encryption, + vars->family)) == NULL) + { + print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname, + vars->port, cupsLastErrorString()); + pass = 0; + goto test_exit; + } + + if (httpReconnect(http)) + { + print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname, + vars->port, cupsLastErrorString()); + pass = 0; + goto test_exit; + } + + if (vars->timeout > 0.0) + httpSetTimeout(http, vars->timeout, timeout_cb, NULL); + + /* + * Loop on tests... + */ + + CUPS_SRAND(time(NULL)); + + pass = 1; + linenum = 1; + request_id = (CUPS_RAND() % 1000) * 137 + 1; + + while (!Cancel && get_token(fp, token, sizeof(token), &linenum) != NULL) + { + /* + * Expect an open brace... + */ + + if (!strcmp(token, "DEFINE")) + { + /* + * DEFINE name value + */ + + if (get_token(fp, attr, sizeof(attr), &linenum) && + get_token(fp, temp, sizeof(temp), &linenum)) + { + expand_variables(vars, token, temp, sizeof(token)); + set_variable(vars, attr, token); + } + else + { + print_fatal_error("Missing DEFINE name and/or value on line %d.", + linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!strcmp(token, "DEFINE-DEFAULT")) + { + /* + * DEFINE-DEFAULT name value + */ + + if (get_token(fp, attr, sizeof(attr), &linenum) && + get_token(fp, temp, sizeof(temp), &linenum)) + { + expand_variables(vars, token, temp, sizeof(token)); + if (!get_variable(vars, attr)) + set_variable(vars, attr, token); + } + else + { + print_fatal_error("Missing DEFINE-DEFAULT name and/or value on line " + "%d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!strcmp(token, "IGNORE-ERRORS")) + { + /* + * IGNORE-ERRORS yes + * IGNORE-ERRORS no + */ + + if (get_token(fp, temp, sizeof(temp), &linenum) && + (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no"))) + { + IgnoreErrors = !_cups_strcasecmp(temp, "yes"); + } + else + { + print_fatal_error("Missing IGNORE-ERRORS value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!strcmp(token, "INCLUDE")) + { + /* + * INCLUDE "filename" + * INCLUDE <filename> + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + /* + * Map the filename to and then run the tests... + */ + + if (!do_tests(vars, get_filename(testfile, filename, temp, + sizeof(filename)))) + { + pass = 0; + + if (!IgnoreErrors) + goto test_exit; + } + } + else + { + print_fatal_error("Missing INCLUDE filename on line %d.", linenum); + pass = 0; + goto test_exit; + } + + show_header = 1; + continue; + } + else if (!strcmp(token, "INCLUDE-IF-DEFINED")) + { + /* + * INCLUDE-IF-DEFINED name "filename" + * INCLUDE-IF-DEFINED name <filename> + */ + + if (get_token(fp, attr, sizeof(attr), &linenum) && + get_token(fp, temp, sizeof(temp), &linenum)) + { + /* + * Map the filename to and then run the tests... + */ + + if (get_variable(vars, attr) && + !do_tests(vars, get_filename(testfile, filename, temp, + sizeof(filename)))) + { + pass = 0; + + if (!IgnoreErrors) + goto test_exit; + } + } + else + { + print_fatal_error("Missing INCLUDE-IF-DEFINED name or filename on line " + "%d.", linenum); + pass = 0; + goto test_exit; + } + + show_header = 1; + continue; + } + else if (!strcmp(token, "INCLUDE-IF-NOT-DEFINED")) + { + /* + * INCLUDE-IF-NOT-DEFINED name "filename" + * INCLUDE-IF-NOT-DEFINED name <filename> + */ + + if (get_token(fp, attr, sizeof(attr), &linenum) && + get_token(fp, temp, sizeof(temp), &linenum)) + { + /* + * Map the filename to and then run the tests... + */ + + if (!get_variable(vars, attr) && + !do_tests(vars, get_filename(testfile, filename, temp, + sizeof(filename)))) + { + pass = 0; + + if (!IgnoreErrors) + goto test_exit; + } + } + else + { + print_fatal_error("Missing INCLUDE-IF-NOT-DEFINED name or filename on " + "line %d.", linenum); + pass = 0; + goto test_exit; + } + + show_header = 1; + continue; + } + else if (!strcmp(token, "SKIP-IF-DEFINED")) + { + /* + * SKIP-IF-DEFINED variable + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (get_variable(vars, temp)) + goto test_exit; + } + else + { + print_fatal_error("Missing SKIP-IF-DEFINED variable on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "SKIP-IF-NOT-DEFINED")) + { + /* + * SKIP-IF-NOT-DEFINED variable + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!get_variable(vars, temp)) + goto test_exit; + } + else + { + print_fatal_error("Missing SKIP-IF-NOT-DEFINED variable on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "TRANSFER")) + { + /* + * TRANSFER auto + * TRANSFER chunked + * TRANSFER length + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!strcmp(temp, "auto")) + Transfer = _CUPS_TRANSFER_AUTO; + else if (!strcmp(temp, "chunked")) + Transfer = _CUPS_TRANSFER_CHUNKED; + else if (!strcmp(temp, "length")) + Transfer = _CUPS_TRANSFER_LENGTH; + else + { + print_fatal_error("Bad TRANSFER value \"%s\" on line %d.", temp, + linenum); + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Missing TRANSFER value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!strcmp(token, "VERSION")) + { + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!strcmp(temp, "1.0")) + Version = 10; + else if (!strcmp(temp, "1.1")) + Version = 11; + else if (!strcmp(temp, "2.0")) + Version = 20; + else if (!strcmp(temp, "2.1")) + Version = 21; + else if (!strcmp(temp, "2.2")) + Version = 22; + else + { + print_fatal_error("Bad VERSION \"%s\" on line %d.", temp, linenum); + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Missing VERSION number on line %d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (strcmp(token, "{")) + { + print_fatal_error("Unexpected token %s seen on line %d.", token, linenum); + pass = 0; + goto test_exit; + } + + /* + * Initialize things... + */ + + if (show_header) + { + if (Output == _CUPS_OUTPUT_PLIST) + print_xml_header(); + else if (Output == _CUPS_OUTPUT_TEST) + printf("\"%s\":\n", testfile); + + show_header = 0; + } + + strlcpy(resource, vars->resource, sizeof(resource)); + + request_id ++; + request = ippNew(); + op = (ipp_op_t)0; + group = IPP_TAG_ZERO; + ignore_errors = IgnoreErrors; + last_expect = NULL; + last_status = NULL; + filename[0] = '\0'; + skip_previous = 0; + skip_test = 0; + version = Version; + transfer = Transfer; + + strlcpy(name, testfile, sizeof(name)); + if (strrchr(name, '.') != NULL) + *strrchr(name, '.') = '\0'; + + /* + * Parse until we see a close brace... + */ + + while (get_token(fp, token, sizeof(token), &linenum) != NULL) + { + if (_cups_strcasecmp(token, "COUNT") && + _cups_strcasecmp(token, "DEFINE-MATCH") && + _cups_strcasecmp(token, "DEFINE-NO-MATCH") && + _cups_strcasecmp(token, "DEFINE-VALUE") && + _cups_strcasecmp(token, "IF-DEFINED") && + _cups_strcasecmp(token, "IF-NOT-DEFINED") && + _cups_strcasecmp(token, "IN-GROUP") && + _cups_strcasecmp(token, "OF-TYPE") && + _cups_strcasecmp(token, "REPEAT-MATCH") && + _cups_strcasecmp(token, "REPEAT-NO-MATCH") && + _cups_strcasecmp(token, "SAME-COUNT-AS") && + _cups_strcasecmp(token, "WITH-VALUE")) + last_expect = NULL; + + if (_cups_strcasecmp(token, "IF-DEFINED") && + _cups_strcasecmp(token, "IF-NOT-DEFINED") && + _cups_strcasecmp(token, "REPEAT-MATCH") && + _cups_strcasecmp(token, "REPEAT-NO-MATCH")) + last_status = NULL; + + if (!strcmp(token, "}")) + break; + else if (!strcmp(token, "{") && lastcol) + { + /* + * Another collection value + */ + + ipp_t *col = get_collection(vars, fp, &linenum); + /* Collection value */ + + if (col) + { + ipp_attribute_t *tempcol; /* Pointer to new buffer */ + + + /* + * Reallocate memory... + */ + + if ((tempcol = realloc(lastcol, sizeof(ipp_attribute_t) + + (lastcol->num_values + 1) * + sizeof(ipp_value_t))) == NULL) + { + print_fatal_error("Unable to allocate memory on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (tempcol != lastcol) + { + /* + * Reset pointers in the list... + */ + + if (request->prev) + request->prev->next = tempcol; + else + request->attrs = tempcol; + + lastcol = request->current = request->last = tempcol; + } + + lastcol->values[lastcol->num_values].collection = col; + lastcol->num_values ++; + } + else + { + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "DEFINE")) + { + /* + * DEFINE name value + */ + + if (get_token(fp, attr, sizeof(attr), &linenum) && + get_token(fp, temp, sizeof(temp), &linenum)) + { + expand_variables(vars, token, temp, sizeof(token)); + set_variable(vars, attr, token); + } + else + { + print_fatal_error("Missing DEFINE name and/or value on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "IGNORE-ERRORS")) + { + /* + * IGNORE-ERRORS yes + * IGNORE-ERRORS no + */ + + if (get_token(fp, temp, sizeof(temp), &linenum) && + (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no"))) + { + ignore_errors = !_cups_strcasecmp(temp, "yes"); + } + else + { + print_fatal_error("Missing IGNORE-ERRORS value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!_cups_strcasecmp(token, "NAME")) + { + /* + * Name of test... + */ + + get_token(fp, name, sizeof(name), &linenum); + } + else if (!strcmp(token, "REQUEST-ID")) + { + /* + * REQUEST-ID # + * REQUEST-ID random + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (isdigit(temp[0] & 255)) + request_id = atoi(temp); + else if (!_cups_strcasecmp(temp, "random")) + request_id = (CUPS_RAND() % 1000) * 137 + 1; + else + { + print_fatal_error("Bad REQUEST-ID value \"%s\" on line %d.", temp, + linenum); + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Missing REQUEST-ID value on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "SKIP-IF-DEFINED")) + { + /* + * SKIP-IF-DEFINED variable + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (get_variable(vars, temp)) + skip_test = 1; + } + else + { + print_fatal_error("Missing SKIP-IF-DEFINED value on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "SKIP-IF-NOT-DEFINED")) + { + /* + * SKIP-IF-NOT-DEFINED variable + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!get_variable(vars, temp)) + skip_test = 1; + } + else + { + print_fatal_error("Missing SKIP-IF-NOT-DEFINED value on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "SKIP-PREVIOUS-ERROR")) + { + /* + * SKIP-PREVIOUS-ERROR yes + * SKIP-PREVIOUS-ERROR no + */ + + if (get_token(fp, temp, sizeof(temp), &linenum) && + (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no"))) + { + skip_previous = !_cups_strcasecmp(temp, "yes"); + } + else + { + print_fatal_error("Missing SKIP-PREVIOUS-ERROR value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!strcmp(token, "TRANSFER")) + { + /* + * TRANSFER auto + * TRANSFER chunked + * TRANSFER length + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!strcmp(temp, "auto")) + transfer = _CUPS_TRANSFER_AUTO; + else if (!strcmp(temp, "chunked")) + transfer = _CUPS_TRANSFER_CHUNKED; + else if (!strcmp(temp, "length")) + transfer = _CUPS_TRANSFER_LENGTH; + else + { + print_fatal_error("Bad TRANSFER value \"%s\" on line %d.", temp, + linenum); + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Missing TRANSFER value on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "VERSION")) + { + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!strcmp(temp, "0.0")) + version = 0; + else if (!strcmp(temp, "1.0")) + version = 10; + else if (!strcmp(temp, "1.1")) + version = 11; + else if (!strcmp(temp, "2.0")) + version = 20; + else if (!strcmp(temp, "2.1")) + version = 21; + else if (!strcmp(temp, "2.2")) + version = 22; + else + { + print_fatal_error("Bad VERSION \"%s\" on line %d.", temp, linenum); + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Missing VERSION number on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "RESOURCE")) + { + /* + * Resource name... + */ + + if (!get_token(fp, resource, sizeof(resource), &linenum)) + { + print_fatal_error("Missing RESOURCE path on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "OPERATION")) + { + /* + * Operation... + */ + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing OPERATION code on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((op = ippOpValue(token)) == (ipp_op_t)-1 && + (op = strtol(token, NULL, 0)) == 0) + { + print_fatal_error("Bad OPERATION code \"%s\" on line %d.", token, + linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "GROUP")) + { + /* + * Attribute group... + */ + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing GROUP tag on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((value = ippTagValue(token)) < 0) + { + print_fatal_error("Bad GROUP tag \"%s\" on line %d.", token, linenum); + pass = 0; + goto test_exit; + } + + if (value == group) + ippAddSeparator(request); + + group = value; + } + else if (!_cups_strcasecmp(token, "DELAY")) + { + /* + * Delay before operation... + */ + + double delay; + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DELAY value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((delay = _cupsStrScand(token, NULL, localeconv())) <= 0.0) + { + print_fatal_error("Bad DELAY value \"%s\" on line %d.", token, + linenum); + pass = 0; + goto test_exit; + } + else + { + if (Output == _CUPS_OUTPUT_TEST) + printf(" [%g second delay]\n", delay); + + usleep((int)(1000000.0 * delay)); + } + } + else if (!_cups_strcasecmp(token, "ATTR")) + { + /* + * Attribute... + */ + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing ATTR value tag on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((value = ippTagValue(token)) == IPP_TAG_ZERO) + { + print_fatal_error("Bad ATTR value tag \"%s\" on line %d.", token, + linenum); + pass = 0; + goto test_exit; + } + + if (!get_token(fp, attr, sizeof(attr), &linenum)) + { + print_fatal_error("Missing ATTR name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (!get_token(fp, temp, sizeof(temp), &linenum)) + { + print_fatal_error("Missing ATTR value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + expand_variables(vars, token, temp, sizeof(token)); + + switch (value) + { + case IPP_TAG_BOOLEAN : + if (!_cups_strcasecmp(token, "true")) + ippAddBoolean(request, group, attr, 1); + else + ippAddBoolean(request, group, attr, atoi(token)); + break; + + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + if (!strchr(token, ',')) + ippAddInteger(request, group, value, attr, + strtol(token, &tokenptr, 0)); + else + { + int values[100], /* Values */ + num_values = 1; /* Number of values */ + + values[0] = strtol(token, &tokenptr, 10); + while (tokenptr && *tokenptr && + num_values < (int)(sizeof(values) / sizeof(values[0]))) + { + if (*tokenptr == ',') + tokenptr ++; + else if (!isdigit(*tokenptr & 255) && *tokenptr != '-') + break; + + values[num_values] = strtol(tokenptr, &tokenptr, 0); + num_values ++; + } + + ippAddIntegers(request, group, value, attr, num_values, values); + } + + if (!tokenptr || *tokenptr) + { + print_fatal_error("Bad %s value \"%s\" on line %d.", + ippTagString(value), token, linenum); + pass = 0; + goto test_exit; + } + break; + + case IPP_TAG_RESOLUTION : + { + int xres, /* X resolution */ + yres; /* Y resolution */ + char *ptr; /* Pointer into value */ + + xres = yres = strtol(token, (char **)&ptr, 10); + if (ptr > token && xres > 0) + { + if (*ptr == 'x') + yres = strtol(ptr + 1, (char **)&ptr, 10); + } + + if (ptr <= token || xres <= 0 || yres <= 0 || !ptr || + (_cups_strcasecmp(ptr, "dpi") && _cups_strcasecmp(ptr, "dpc") && + _cups_strcasecmp(ptr, "other"))) + { + print_fatal_error("Bad resolution value \"%s\" on line %d.", + token, linenum); + pass = 0; + goto test_exit; + } + + if (!_cups_strcasecmp(ptr, "dpi")) + ippAddResolution(request, group, attr, IPP_RES_PER_INCH, + xres, yres); + else if (!_cups_strcasecmp(ptr, "dpc")) + ippAddResolution(request, group, attr, IPP_RES_PER_CM, + xres, yres); + else + ippAddResolution(request, group, attr, (ipp_res_t)0, + xres, yres); + } + break; + + case IPP_TAG_RANGE : + { + int lowers[4], /* Lower value */ + uppers[4], /* Upper values */ + num_vals; /* Number of values */ + + + num_vals = sscanf(token, "%d-%d,%d-%d,%d-%d,%d-%d", + lowers + 0, uppers + 0, + lowers + 1, uppers + 1, + lowers + 2, uppers + 2, + lowers + 3, uppers + 3); + + if ((num_vals & 1) || num_vals == 0) + { + print_fatal_error("Bad rangeOfInteger value \"%s\" on line " + "%d.", token, linenum); + pass = 0; + goto test_exit; + } + + ippAddRanges(request, group, attr, num_vals / 2, lowers, + uppers); + } + break; + + case IPP_TAG_BEGIN_COLLECTION : + if (!strcmp(token, "{")) + { + ipp_t *col = get_collection(vars, fp, &linenum); + /* Collection value */ + + if (col) + { + lastcol = ippAddCollection(request, group, attr, col); + ippDelete(col); + } + else + { + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Bad ATTR collection value on line %d.", + linenum); + pass = 0; + goto test_exit; + } + break; + + default : + print_fatal_error("Unsupported ATTR value tag %s on line %d.", + ippTagString(value), linenum); + pass = 0; + goto test_exit; + + case IPP_TAG_TEXTLANG : + case IPP_TAG_NAMELANG : + case IPP_TAG_TEXT : + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + case IPP_TAG_URI : + case IPP_TAG_URISCHEME : + case IPP_TAG_CHARSET : + case IPP_TAG_LANGUAGE : + case IPP_TAG_MIMETYPE : + if (!strchr(token, ',')) + ippAddString(request, group, value, attr, NULL, token); + else + { + /* + * Multiple string values... + */ + + int num_values; /* Number of values */ + char *values[100], /* Values */ + *ptr; /* Pointer to next value */ + + + values[0] = token; + num_values = 1; + + for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ',')) + { + *ptr++ = '\0'; + values[num_values] = ptr; + num_values ++; + } + + ippAddStrings(request, group, value, attr, num_values, + NULL, (const char **)values); + } + break; + } + } + else if (!_cups_strcasecmp(token, "FILE")) + { + /* + * File... + */ + + if (!get_token(fp, temp, sizeof(temp), &linenum)) + { + print_fatal_error("Missing FILE filename on line %d.", linenum); + pass = 0; + goto test_exit; + } + + expand_variables(vars, token, temp, sizeof(token)); + get_filename(testfile, filename, token, sizeof(filename)); + } + else if (!_cups_strcasecmp(token, "STATUS")) + { + /* + * Status... + */ + + if (num_statuses >= (int)(sizeof(statuses) / sizeof(statuses[0]))) + { + print_fatal_error("Too many STATUS's on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing STATUS code on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((statuses[num_statuses].status = ippErrorValue(token)) + == (ipp_status_t)-1 && + (statuses[num_statuses].status = strtol(token, NULL, 0)) == 0) + { + print_fatal_error("Bad STATUS code \"%s\" on line %d.", token, + linenum); + pass = 0; + goto test_exit; + } + + last_status = statuses + num_statuses; + num_statuses ++; + + last_status->if_defined = NULL; + last_status->if_not_defined = NULL; + last_status->repeat_match = 0; + last_status->repeat_no_match = 0; + } + else if (!_cups_strcasecmp(token, "EXPECT")) + { + /* + * Expected attributes... + */ + + if (num_expects >= (int)(sizeof(expects) / sizeof(expects[0]))) + { + print_fatal_error("Too many EXPECT's on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing EXPECT name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + last_expect = expects + num_expects; + num_expects ++; + + memset(last_expect, 0, sizeof(_cups_expect_t)); + + if (token[0] == '!') + { + last_expect->not_expect = 1; + last_expect->name = strdup(token + 1); + } + else if (token[0] == '?') + { + last_expect->optional = 1; + last_expect->name = strdup(token + 1); + } + else + last_expect->name = strdup(token); + } + else if (!_cups_strcasecmp(token, "COUNT")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing COUNT number on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((i = atoi(token)) <= 0) + { + print_fatal_error("Bad COUNT \"%s\" on line %d.", token, linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->count = i; + else + { + print_fatal_error("COUNT without a preceding EXPECT on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "DEFINE-MATCH")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DEFINE-MATCH variable on line %d.", + linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->define_match = strdup(token); + else + { + print_fatal_error("DEFINE-MATCH without a preceding EXPECT on line " + "%d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "DEFINE-NO-MATCH")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DEFINE-NO-MATCH variable on line %d.", + linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->define_no_match = strdup(token); + else + { + print_fatal_error("DEFINE-NO-MATCH without a preceding EXPECT on " + "line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "DEFINE-VALUE")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DEFINE-VALUE variable on line %d.", + linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->define_value = strdup(token); + else + { + print_fatal_error("DEFINE-VALUE without a preceding EXPECT on line " + "%d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "OF-TYPE")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing OF-TYPE value tag(s) on line %d.", + linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->of_type = strdup(token); + else + { + print_fatal_error("OF-TYPE without a preceding EXPECT on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "IN-GROUP")) + { + ipp_tag_t in_group; /* IN-GROUP value */ + + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing IN-GROUP group tag on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((in_group = ippTagValue(token)) == (ipp_tag_t)-1) + { + } + else if (last_expect) + last_expect->in_group = in_group; + else + { + print_fatal_error("IN-GROUP without a preceding EXPECT on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "REPEAT-MATCH")) + { + if (last_status) + last_status->repeat_match = 1; + else if (last_expect) + last_expect->repeat_match = 1; + else + { + print_fatal_error("REPEAT-MATCH without a preceding EXPECT or STATUS " + "on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "REPEAT-NO-MATCH")) + { + if (last_status) + last_status->repeat_no_match = 1; + else if (last_expect) + last_expect->repeat_no_match = 1; + else + { + print_fatal_error("REPEAT-NO-MATCH without a preceding EXPECT or " + "STATUS on ine %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "SAME-COUNT-AS")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing SAME-COUNT-AS name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->same_count_as = strdup(token); + else + { + print_fatal_error("SAME-COUNT-AS without a preceding EXPECT on line " + "%d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "IF-DEFINED")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing IF-DEFINED name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->if_defined = strdup(token); + else if (last_status) + last_status->if_defined = strdup(token); + else + { + print_fatal_error("IF-DEFINED without a preceding EXPECT or STATUS " + "on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "IF-NOT-DEFINED")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing IF-NOT-DEFINED name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->if_not_defined = strdup(token); + else if (last_status) + last_status->if_not_defined = strdup(token); + else + { + print_fatal_error("IF-NOT-DEFINED without a preceding EXPECT or STATUS " + "on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "WITH-VALUE")) + { + if (!get_token(fp, temp, sizeof(temp), &linenum)) + { + print_fatal_error("Missing WITH-VALUE value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + { + /* + * Expand any variables in the value and then save it. + */ + + expand_variables(vars, token, temp, sizeof(token)); + + tokenptr = token + strlen(token) - 1; + + if (token[0] == '/' && tokenptr > token && *tokenptr == '/') + { + /* + * WITH-VALUE is a POSIX extended regular expression. + */ + + last_expect->with_value = calloc(1, tokenptr - token); + last_expect->with_regex = 1; + + if (last_expect->with_value) + memcpy(last_expect->with_value, token + 1, tokenptr - token - 1); + } + else + { + /* + * WITH-VALUE is a literal value... + */ + + last_expect->with_value = strdup(token); + } + } + else + { + print_fatal_error("WITH-VALUE without a preceding EXPECT on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "DISPLAY")) + { + /* + * Display attributes... + */ + + if (num_displayed >= (int)(sizeof(displayed) / sizeof(displayed[0]))) + { + print_fatal_error("Too many DISPLAY's on line %d", linenum); + pass = 0; + goto test_exit; + } + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DISPLAY name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + displayed[num_displayed] = strdup(token); + num_displayed ++; + } + else + { + print_fatal_error("Unexpected token %s seen on line %d.", token, + linenum); + pass = 0; + goto test_exit; + } + } + + /* + * Submit the IPP request... + */ + + TestCount ++; + + request->request.op.version[0] = version / 10; + request->request.op.version[1] = version % 10; + request->request.op.operation_id = op; + request->request.op.request_id = request_id; + + if (Output == _CUPS_OUTPUT_PLIST) + { + puts("<dict>"); + puts("<key>Name</key>"); + print_xml_string("string", name); + puts("<key>Operation</key>"); + print_xml_string("string", ippOpString(op)); + puts("<key>RequestAttributes</key>"); + puts("<array>"); + if (request->attrs) + { + puts("<dict>"); + for (attrptr = request->attrs, group = attrptr->group_tag; + attrptr; + attrptr = attrptr->next) + print_attr(attrptr, &group); + puts("</dict>"); + } + puts("</array>"); + } + else if (Output == _CUPS_OUTPUT_TEST) + { + if (Verbosity) + { + printf(" %s:\n", ippOpString(op)); + + for (attrptr = request->attrs; attrptr; attrptr = attrptr->next) + print_attr(attrptr, NULL); + } + + printf(" %-68.68s [", name); + fflush(stdout); + } + + if ((skip_previous && !prev_pass) || skip_test) + { + SkipCount ++; + + ippDelete(request); + request = NULL; + + if (Output == _CUPS_OUTPUT_PLIST) + { + puts("<key>Successful</key>"); + puts("<true />"); + puts("<key>StatusCode</key>"); + print_xml_string("string", "skip"); + puts("<key>ResponseAttributes</key>"); + puts("<dict>"); + puts("</dict>"); + } + else if (Output == _CUPS_OUTPUT_TEST) + puts("SKIP]"); + + goto skip_error; + } + + do + { + status = HTTP_OK; + + if (transfer == _CUPS_TRANSFER_CHUNKED || + (transfer == _CUPS_TRANSFER_AUTO && filename[0])) + { + /* + * Send request using chunking - a 0 length means "chunk". + */ + + length = 0; + } + else + { + /* + * Send request using content length... + */ + + length = ippLength(request); + + if (filename[0] && (reqfile = cupsFileOpen(filename, "r")) != NULL) + { + /* + * Read the file to get the uncompressed file size... + */ + + while ((bytes = cupsFileRead(reqfile, buffer, sizeof(buffer))) > 0) + length += bytes; + + cupsFileClose(reqfile); + } + } + + /* + * Send the request... + */ + + response = NULL; + repeat_test = 0; + prev_pass = 1; + + if (status != HTTP_ERROR) + { + while (!response && !Cancel && prev_pass) + { + status = cupsSendRequest(http, request, resource, length); + + if (!Cancel && status == HTTP_CONTINUE && + request->state == IPP_DATA && filename[0]) + { + if ((reqfile = cupsFileOpen(filename, "r")) != NULL) + { + while (!Cancel && + (bytes = cupsFileRead(reqfile, buffer, + sizeof(buffer))) > 0) + if ((status = cupsWriteRequestData(http, buffer, + bytes)) != HTTP_CONTINUE) + break; + + cupsFileClose(reqfile); + } + else + { + snprintf(buffer, sizeof(buffer), "%s: %s", filename, + strerror(errno)); + _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0); + + status = HTTP_ERROR; + } + } + + /* + * Get the server's response... + */ + + if (!Cancel && status != HTTP_ERROR) + { + response = cupsGetResponse(http, resource); + status = httpGetStatus(http); + } + + if (!Cancel && status == HTTP_ERROR && +#ifdef WIN32 + http->error != WSAETIMEDOUT) +#else + http->error != ETIMEDOUT) +#endif /* WIN32 */ + { + if (httpReconnect(http)) + prev_pass = 0; + } + else if (status == HTTP_ERROR) + { + if (!Cancel) + httpReconnect(http); + + prev_pass = 0; + } + } + } + + /* + * Check results of request... + */ + + if (!response) + prev_pass = pass = 0; + else + { + if (http->version != HTTP_1_1) + prev_pass = pass = 0; + + if (response->state != IPP_DATA) + prev_pass = pass = 0; + + if (response->request.status.request_id != request_id) + prev_pass = pass = 0; + + if (version && + (response->request.status.version[0] != (version / 10) || + response->request.status.version[1] != (version % 10))) + prev_pass = pass = 0; + + if ((attrptr = ippFindAttribute(response, "job-id", + IPP_TAG_INTEGER)) != NULL) + { + snprintf(temp, sizeof(temp), "%d", attrptr->values[0].integer); + set_variable(vars, "job-id", temp); + } + + if ((attrptr = ippFindAttribute(response, "job-uri", + IPP_TAG_URI)) != NULL) + set_variable(vars, "job-uri", attrptr->values[0].string.text); + + if ((attrptr = ippFindAttribute(response, "notify-subscription-id", + IPP_TAG_INTEGER)) != NULL) + { + snprintf(temp, sizeof(temp), "%d", attrptr->values[0].integer); + set_variable(vars, "notify-subscription-id", temp); + } + + attrptr = response->attrs; + if (!attrptr || !attrptr->name || + attrptr->value_tag != IPP_TAG_CHARSET || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + strcmp(attrptr->name, "attributes-charset")) + prev_pass = pass = 0; + + if (attrptr) + { + attrptr = attrptr->next; + if (!attrptr || !attrptr->name || + attrptr->value_tag != IPP_TAG_LANGUAGE || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + strcmp(attrptr->name, "attributes-natural-language")) + prev_pass = pass = 0; + } + + if ((attrptr = ippFindAttribute(response, "status-message", + IPP_TAG_ZERO)) != NULL && + (attrptr->value_tag != IPP_TAG_TEXT || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + (attrptr->value_tag == IPP_TAG_TEXT && + strlen(attrptr->values[0].string.text) > 255))) + prev_pass = pass = 0; + + if ((attrptr = ippFindAttribute(response, "detailed-status-message", + IPP_TAG_ZERO)) != NULL && + (attrptr->value_tag != IPP_TAG_TEXT || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + (attrptr->value_tag == IPP_TAG_TEXT && + strlen(attrptr->values[0].string.text) > 1023))) + prev_pass = pass = 0; + + a = cupsArrayNew((cups_array_func_t)strcmp, NULL); + + for (attrptr = response->attrs, group = attrptr->group_tag; + attrptr; + attrptr = attrptr->next) + { + if (attrptr->group_tag != group) + { + cupsArrayClear(a); + + switch (attrptr->group_tag) + { + case IPP_TAG_ZERO : + break; + + case IPP_TAG_OPERATION : + prev_pass = pass = 0; + break; + + case IPP_TAG_UNSUPPORTED_GROUP : + if (group != IPP_TAG_OPERATION) + prev_pass = pass = 0; + break; + + case IPP_TAG_JOB : + case IPP_TAG_PRINTER : + if (group != IPP_TAG_OPERATION && + group != IPP_TAG_UNSUPPORTED_GROUP) + prev_pass = pass = 0; + break; + + case IPP_TAG_SUBSCRIPTION : + if (group > attrptr->group_tag && + group != IPP_TAG_DOCUMENT) + prev_pass = pass = 0; + break; + + default : + if (group > attrptr->group_tag) + prev_pass = pass = 0; + break; + } + + if (!pass) + break; + + if (attrptr->group_tag != IPP_TAG_ZERO) + group = attrptr->group_tag; + } + + if (!validate_attr(attrptr, 0)) + { + prev_pass = pass = 0; + break; + } + + if (attrptr->name) + { + if (cupsArrayFind(a, attrptr->name)) + { + prev_pass = pass = 0; + break; + } + + cupsArrayAdd(a, attrptr->name); + } + } + + cupsArrayDelete(a); + + for (i = 0; i < num_statuses; i ++) + { + if (statuses[i].if_defined && + !get_variable(vars, statuses[i].if_defined)) + continue; + + if (statuses[i].if_not_defined && + get_variable(vars, statuses[i].if_not_defined)) + continue; + + if (response->request.status.status_code == statuses[i].status) + { + if (statuses[i].repeat_match) + repeat_test = 1; + + break; + } + else if (statuses[i].repeat_no_match) + repeat_test = 1; + } + + if (i == num_statuses && num_statuses > 0) + prev_pass = pass = 0; + + for (i = num_expects, expect = expects; i > 0; i --, expect ++) + { + if (expect->if_defined && !get_variable(vars, expect->if_defined)) + continue; + + if (expect->if_not_defined && + get_variable(vars, expect->if_not_defined)) + continue; + + found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO); + + if ((found && expect->not_expect) || + (!found && !(expect->not_expect || expect->optional)) || + (found && !expect_matches(expect, found->value_tag)) || + (found && expect->in_group && + found->group_tag != expect->in_group)) + { + if (expect->define_no_match) + set_variable(vars, expect->define_no_match, "1"); + else if (!expect->define_match && !expect->define_value) + prev_pass = pass = 0; + + if (expect->repeat_no_match) + repeat_test = 1; + + continue; + } + + if (found) + _ippAttrString(found, buffer, sizeof(buffer)); + + if (found && + !with_value(expect->with_value, expect->with_regex, found, 0, + buffer, sizeof(buffer))) + { + if (expect->define_no_match) + set_variable(vars, expect->define_no_match, "1"); + else if (!expect->define_match && !expect->define_value) + prev_pass = pass = 0; + + if (expect->repeat_no_match) + repeat_test = 1; + + continue; + } + + if (found && expect->count > 0 && + found->num_values != expect->count) + { + if (expect->define_no_match) + set_variable(vars, expect->define_no_match, "1"); + else if (!expect->define_match && !expect->define_value) + prev_pass = pass = 0; + + if (expect->repeat_no_match) + repeat_test = 1; + + continue; + } + + if (found && expect->same_count_as) + { + attrptr = ippFindAttribute(response, expect->same_count_as, + IPP_TAG_ZERO); + + if (!attrptr || attrptr->num_values != found->num_values) + { + if (expect->define_no_match) + set_variable(vars, expect->define_no_match, "1"); + else if (!expect->define_match && !expect->define_value) + prev_pass = pass = 0; + + if (expect->repeat_no_match) + repeat_test = 1; + + continue; + } + } + + if (found && expect->define_match) + set_variable(vars, expect->define_match, "1"); + + if (found && expect->define_value) + set_variable(vars, expect->define_value, buffer); + + if (found && expect->repeat_match) + repeat_test = 1; + } + } + + /* + * If we are going to repeat this test, sleep 1 second so we don't flood + * the printer with requests... + */ + + if (repeat_test) + sleep(1); + } + while (repeat_test); + + ippDelete(request); + + request = NULL; + + if (prev_pass) + PassCount ++; + else + FailCount ++; + + if (Output == _CUPS_OUTPUT_PLIST) + { + puts("<key>Successful</key>"); + puts(prev_pass ? "<true />" : "<false />"); + puts("<key>StatusCode</key>"); + print_xml_string("string", ippErrorString(cupsLastError())); + puts("<key>ResponseAttributes</key>"); + puts("<array>"); + puts("<dict>"); + for (attrptr = response ? response->attrs : NULL, + group = attrptr ? attrptr->group_tag : IPP_TAG_ZERO; + attrptr; + attrptr = attrptr->next) + print_attr(attrptr, &group); + puts("</dict>"); + puts("</array>"); + } + else if (Output == _CUPS_OUTPUT_TEST) + { + puts(prev_pass ? "PASS]" : "FAIL]"); + + if (!prev_pass || (Verbosity && response)) + { + printf(" RECEIVED: %lu bytes in response\n", + (unsigned long)ippLength(response)); + printf(" status-code = %s (%s)\n", ippErrorString(cupsLastError()), + cupsLastErrorString()); + + if (response) + { + for (attrptr = response->attrs; + attrptr != NULL; + attrptr = attrptr->next) + print_attr(attrptr, NULL); + } + } + } + else if (!prev_pass) + fprintf(stderr, "%s\n", cupsLastErrorString()); + + if (prev_pass && Output >= _CUPS_OUTPUT_LIST && !Verbosity && + num_displayed > 0) + { + size_t width; /* Length of value */ + + for (i = 0; i < num_displayed; i ++) + { + widths[i] = strlen(displayed[i]); + + for (attrptr = ippFindAttribute(response, displayed[i], IPP_TAG_ZERO); + attrptr; + attrptr = ippFindNextAttribute(response, displayed[i], + IPP_TAG_ZERO)) + { + width = _ippAttrString(attrptr, NULL, 0); + if (width > widths[i]) + widths[i] = width; + } + } + + if (Output == _CUPS_OUTPUT_CSV) + print_csv(NULL, num_displayed, displayed, widths); + else + print_line(NULL, num_displayed, displayed, widths); + + attrptr = response->attrs; + + while (attrptr) + { + while (attrptr && attrptr->group_tag <= IPP_TAG_OPERATION) + attrptr = attrptr->next; + + if (attrptr) + { + if (Output == _CUPS_OUTPUT_CSV) + print_csv(attrptr, num_displayed, displayed, widths); + else + print_line(attrptr, num_displayed, displayed, widths); + + while (attrptr && attrptr->group_tag > IPP_TAG_OPERATION) + attrptr = attrptr->next; + } + } + } + else if (!prev_pass) + { + if (Output == _CUPS_OUTPUT_PLIST) + { + puts("<key>Errors</key>"); + puts("<array>"); + } + + if (http->version != HTTP_1_1) + print_test_error("Bad HTTP version (%d.%d)", http->version / 100, + http->version % 100); + + if (!response) + print_test_error("IPP request failed with status %s (%s)", + ippErrorString(cupsLastError()), + cupsLastErrorString()); + else + { + if (response->state != IPP_DATA) + print_test_error("Missing end-of-attributes-tag in response " + "(RFC 2910 section 3.5.1)"); + + if (version && + (response->request.status.version[0] != (version / 10) || + response->request.status.version[1] != (version % 10))) + print_test_error("Bad version %d.%d in response - expected %d.%d " + "(RFC 2911 section 3.1.8).", + response->request.status.version[0], + response->request.status.version[1], + version / 10, version % 10); + + if (response->request.status.request_id != request_id) + print_test_error("Bad request ID %d in response - expected %d " + "(RFC 2911 section 3.1.1)", + response->request.status.request_id, request_id); + + attrptr = response->attrs; + if (!attrptr) + print_test_error("Missing first attribute \"attributes-charset " + "(charset)\" in group operation-attributes-tag " + "(RFC 2911 section 3.1.4)."); + else + { + if (!attrptr->name || + attrptr->value_tag != IPP_TAG_CHARSET || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + strcmp(attrptr->name, "attributes-charset")) + print_test_error("Bad first attribute \"%s (%s%s)\" in group %s, " + "expected \"attributes-charset (charset)\" in " + "group operation-attributes-tag (RFC 2911 section " + "3.1.4).", + attrptr->name ? attrptr->name : "(null)", + attrptr->num_values > 1 ? "1setOf " : "", + ippTagString(attrptr->value_tag), + ippTagString(attrptr->group_tag)); + + attrptr = attrptr->next; + if (!attrptr) + print_test_error("Missing second attribute \"attributes-natural-" + "language (naturalLanguage)\" in group " + "operation-attributes-tag (RFC 2911 section " + "3.1.4)."); + else if (!attrptr->name || + attrptr->value_tag != IPP_TAG_LANGUAGE || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + strcmp(attrptr->name, "attributes-natural-language")) + print_test_error("Bad first attribute \"%s (%s%s)\" in group %s, " + "expected \"attributes-natural-language " + "(naturalLanguage)\" in group " + "operation-attributes-tag (RFC 2911 section " + "3.1.4).", + attrptr->name ? attrptr->name : "(null)", + attrptr->num_values > 1 ? "1setOf " : "", + ippTagString(attrptr->value_tag), + ippTagString(attrptr->group_tag)); + } + + if ((attrptr = ippFindAttribute(response, "status-message", + IPP_TAG_ZERO)) != NULL) + { + if (attrptr->value_tag != IPP_TAG_TEXT) + print_test_error("status-message (text(255)) has wrong value tag " + "%s (RFC 2911 section 3.1.6.2).", + ippTagString(attrptr->value_tag)); + if (attrptr->group_tag != IPP_TAG_OPERATION) + print_test_error("status-message (text(255)) has wrong group tag " + "%s (RFC 2911 section 3.1.6.2).", + ippTagString(attrptr->group_tag)); + if (attrptr->num_values != 1) + print_test_error("status-message (text(255)) has %d values " + "(RFC 2911 section 3.1.6.2).", + attrptr->num_values); + if (attrptr->value_tag == IPP_TAG_TEXT && + strlen(attrptr->values[0].string.text) > 255) + print_test_error("status-message (text(255)) has bad length %d" + " (RFC 2911 section 3.1.6.2).", + (int)strlen(attrptr->values[0].string.text)); + } + + if ((attrptr = ippFindAttribute(response, "detailed-status-message", + IPP_TAG_ZERO)) != NULL) + { + if (attrptr->value_tag != IPP_TAG_TEXT) + print_test_error("detailed-status-message (text(MAX)) has wrong " + "value tag %s (RFC 2911 section 3.1.6.3).", + ippTagString(attrptr->value_tag)); + if (attrptr->group_tag != IPP_TAG_OPERATION) + print_test_error("detailed-status-message (text(MAX)) has wrong " + "group tag %s (RFC 2911 section 3.1.6.3).", + ippTagString(attrptr->group_tag)); + if (attrptr->num_values != 1) + print_test_error("detailed-status-message (text(MAX)) has %d values" + " (RFC 2911 section 3.1.6.3).", + attrptr->num_values); + if (attrptr->value_tag == IPP_TAG_TEXT && + strlen(attrptr->values[0].string.text) > 1023) + print_test_error("detailed-status-message (text(MAX)) has bad " + "length %d (RFC 2911 section 3.1.6.3).", + (int)strlen(attrptr->values[0].string.text)); + } + + a = cupsArrayNew((cups_array_func_t)strcmp, NULL); + + for (attrptr = response->attrs, group = attrptr->group_tag; + attrptr; + attrptr = attrptr->next) + { + if (attrptr->group_tag != group) + { + cupsArrayClear(a); + + switch (attrptr->group_tag) + { + case IPP_TAG_ZERO : + break; + + case IPP_TAG_OPERATION : + prev_pass = pass = 0; + break; + + case IPP_TAG_UNSUPPORTED_GROUP : + if (group != IPP_TAG_OPERATION) + print_test_error("Attribute groups out of order (%s < %s)", + ippTagString(attrptr->group_tag), + ippTagString(group)); + break; + + case IPP_TAG_JOB : + case IPP_TAG_PRINTER : + if (group != IPP_TAG_OPERATION && + group != IPP_TAG_UNSUPPORTED_GROUP) + print_test_error("Attribute groups out of order (%s < %s)", + ippTagString(attrptr->group_tag), + ippTagString(group)); + break; + + case IPP_TAG_SUBSCRIPTION : + if (group > attrptr->group_tag && + group != IPP_TAG_DOCUMENT) + print_test_error("Attribute groups out of order (%s < %s)", + ippTagString(attrptr->group_tag), + ippTagString(group)); + break; + + default : + if (group > attrptr->group_tag) + print_test_error("Attribute groups out of order (%s < %s)", + ippTagString(attrptr->group_tag), + ippTagString(group)); + break; + } + + if (attrptr->group_tag != IPP_TAG_ZERO) + group = attrptr->group_tag; + } + + validate_attr(attrptr, 1); + + if (attrptr->name) + { + if (cupsArrayFind(a, attrptr->name)) + print_test_error("Duplicate \"%s\" attribute in %s group", + attrptr->name, ippTagString(group)); + + cupsArrayAdd(a, attrptr->name); + } + } + + cupsArrayDelete(a); + + for (i = 0; i < num_statuses; i ++) + { + if (statuses[i].if_defined && + !get_variable(vars, statuses[i].if_defined)) + continue; + + if (statuses[i].if_not_defined && + get_variable(vars, statuses[i].if_not_defined)) + continue; + + if (response->request.status.status_code == statuses[i].status) + break; + } + + if (i == num_statuses && num_statuses > 0) + { + for (i = 0; i < num_statuses; i ++) + { + if (statuses[i].if_defined && + !get_variable(vars, statuses[i].if_defined)) + continue; + + if (statuses[i].if_not_defined && + get_variable(vars, statuses[i].if_not_defined)) + continue; + + print_test_error("EXPECTED: STATUS %s (got %s)", + ippErrorString(statuses[i].status), + ippErrorString(cupsLastError())); + } + + if ((attrptr = ippFindAttribute(response, "status-message", + IPP_TAG_TEXT)) != NULL) + print_test_error("status-message=\"%s\"", + attrptr->values[0].string.text); + } + + for (i = num_expects, expect = expects; i > 0; i --, expect ++) + { + if (expect->define_match || expect->define_no_match || + expect->define_value) + continue; + + if (expect->if_defined && !get_variable(vars, expect->if_defined)) + continue; + + if (expect->if_not_defined && + get_variable(vars, expect->if_not_defined)) + continue; + + found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO); + + if (found && expect->not_expect) + print_test_error("NOT EXPECTED: %s", expect->name); + else if (!found && !(expect->not_expect || expect->optional)) + print_test_error("EXPECTED: %s", expect->name); + else if (found) + { + if (!expect_matches(expect, found->value_tag)) + print_test_error("EXPECTED: %s OF-TYPE %s (got %s)", + expect->name, expect->of_type, + ippTagString(found->value_tag)); + + if (expect->in_group && found->group_tag != expect->in_group) + print_test_error("EXPECTED: %s IN-GROUP %s (got %s).", + expect->name, ippTagString(expect->in_group), + ippTagString(found->group_tag)); + + if (!with_value(expect->with_value, expect->with_regex, found, 0, + buffer, sizeof(buffer))) + { + if (expect->with_regex) + print_test_error("EXPECTED: %s WITH-VALUE /%s/", + expect->name, expect->with_value); + else + print_test_error("EXPECTED: %s WITH-VALUE \"%s\"", + expect->name, expect->with_value); + + with_value(expect->with_value, expect->with_regex, found, 1, + buffer, sizeof(buffer)); + } + + if (expect->count > 0 && found->num_values != expect->count) + { + print_test_error("EXPECTED: %s COUNT %d (got %d)", expect->name, + expect->count, found->num_values); + } + + if (expect->same_count_as) + { + attrptr = ippFindAttribute(response, expect->same_count_as, + IPP_TAG_ZERO); + + if (!attrptr) + print_test_error("EXPECTED: %s (%d values) SAME-COUNT-AS %s " + "(not returned)", expect->name, + found->num_values, expect->same_count_as); + else if (attrptr->num_values != found->num_values) + print_test_error("EXPECTED: %s (%d values) SAME-COUNT-AS %s " + "(%d values)", expect->name, found->num_values, + expect->same_count_as, attrptr->num_values); + } + } + } + } + + if (Output == _CUPS_OUTPUT_PLIST) + puts("</array>"); + } + + if (num_displayed > 0 && !Verbosity && + (Output == _CUPS_OUTPUT_TEST || Output == _CUPS_OUTPUT_PLIST)) + { + for (attrptr = response->attrs; + attrptr != NULL; + attrptr = attrptr->next) + { + if (attrptr->name) + { + for (i = 0; i < num_displayed; i ++) + { + if (!strcmp(displayed[i], attrptr->name)) + { + print_attr(attrptr, NULL); + break; + } + } + } + } + } + + skip_error: + + if (Output == _CUPS_OUTPUT_PLIST) + puts("</dict>"); + + fflush(stdout); + + ippDelete(response); + response = NULL; + + for (i = 0; i < num_statuses; i ++) + { + if (statuses[i].if_defined) + free(statuses[i].if_defined); + if (statuses[i].if_not_defined) + free(statuses[i].if_not_defined); + } + num_statuses = 0; + + for (i = num_expects, expect = expects; i > 0; i --, expect ++) + { + free(expect->name); + if (expect->of_type) + free(expect->of_type); + if (expect->same_count_as) + free(expect->same_count_as); + if (expect->if_defined) + free(expect->if_defined); + if (expect->if_not_defined) + free(expect->if_not_defined); + if (expect->with_value) + free(expect->with_value); + if (expect->define_match) + free(expect->define_match); + if (expect->define_no_match) + free(expect->define_no_match); + if (expect->define_value) + free(expect->define_value); + } + num_expects = 0; + + for (i = 0; i < num_displayed; i ++) + free(displayed[i]); + num_displayed = 0; + + if (!ignore_errors && !prev_pass) + break; + } + + test_exit: + + if (fp) + fclose(fp); + + httpClose(http); + ippDelete(request); + ippDelete(response); + + for (i = 0; i < num_statuses; i ++) + { + if (statuses[i].if_defined) + free(statuses[i].if_defined); + if (statuses[i].if_not_defined) + free(statuses[i].if_not_defined); + } + + for (i = num_expects, expect = expects; i > 0; i --, expect ++) + { + free(expect->name); + if (expect->of_type) + free(expect->of_type); + if (expect->same_count_as) + free(expect->same_count_as); + if (expect->if_defined) + free(expect->if_defined); + if (expect->if_not_defined) + free(expect->if_not_defined); + if (expect->with_value) + free(expect->with_value); + if (expect->define_match) + free(expect->define_match); + if (expect->define_no_match) + free(expect->define_no_match); + if (expect->define_value) + free(expect->define_value); + } + + for (i = 0; i < num_displayed; i ++) + free(displayed[i]); + + return (pass); +} + + +/* + * 'expand_variables()' - Expand variables in a string. + */ + +static void +expand_variables(_cups_vars_t *vars, /* I - Variables */ + char *dst, /* I - Destination string buffer */ + const char *src, /* I - Source string */ + size_t dstsize) /* I - Size of destination buffer */ +{ + char *dstptr, /* Pointer into destination */ + *dstend, /* End of destination */ + temp[256], /* Temporary string */ + *tempptr; /* Pointer into temporary string */ + const char *value; /* Value to substitute */ + + + dstptr = dst; + dstend = dst + dstsize - 1; + + while (*src && dstptr < dstend) + { + if (*src == '$') + { + /* + * Substitute a string/number... + */ + + if (!strncmp(src, "$$", 2)) + { + value = "$"; + src += 2; + } + else if (!strncmp(src, "$ENV[", 5)) + { + strlcpy(temp, src + 5, sizeof(temp)); + + for (tempptr = temp; *tempptr; tempptr ++) + if (*tempptr == ']') + break; + + if (*tempptr) + *tempptr++ = '\0'; + + value = getenv(temp); + src += tempptr - temp + 5; + } + else if (vars) + { + strlcpy(temp, src + 1, sizeof(temp)); + + for (tempptr = temp; *tempptr; tempptr ++) + if (!isalnum(*tempptr & 255) && *tempptr != '-' && *tempptr != '_') + break; + + if (*tempptr) + *tempptr = '\0'; + + if (!strcmp(temp, "uri")) + value = vars->uri; + else if (!strcmp(temp, "filename")) + value = vars->filename; + else if (!strcmp(temp, "scheme") || !strcmp(temp, "method")) + value = vars->scheme; + else if (!strcmp(temp, "username")) + value = vars->userpass; + else if (!strcmp(temp, "hostname")) + value = vars->hostname; + else if (!strcmp(temp, "port")) + { + snprintf(temp, sizeof(temp), "%d", vars->port); + value = temp; + } + else if (!strcmp(temp, "resource")) + value = vars->resource; + else if (!strcmp(temp, "user")) + value = cupsUser(); + else + value = get_variable(vars, temp); + + src += tempptr - temp + 1; + } + else + { + value = "$"; + src ++; + } + + if (value) + { + strlcpy(dstptr, value, dstend - dstptr + 1); + dstptr += strlen(dstptr); + } + } + else + *dstptr++ = *src++; + } + + *dstptr = '\0'; +} + + +/* + * 'expect_matches()' - Return true if the tag matches the specification. + */ + +static int /* O - 1 if matches, 0 otherwise */ +expect_matches( + _cups_expect_t *expect, /* I - Expected attribute */ + ipp_tag_t value_tag) /* I - Value tag for attribute */ +{ + int match; /* Match? */ + char *of_type, /* Type name to match */ + *next, /* Next name to match */ + sep; /* Separator character */ + + + /* + * If we don't expect a particular type, return immediately... + */ + + if (!expect->of_type) + return (1); + + /* + * Parse the "of_type" value since the string can contain multiple attribute + * types separated by "," or "|"... + */ + + for (of_type = expect->of_type, match = 0; !match && *of_type; of_type = next) + { + /* + * Find the next separator, and set it (temporarily) to nul if present. + */ + + for (next = of_type; *next && *next != '|' && *next != ','; next ++); + + if ((sep = *next) != '\0') + *next = '\0'; + + /* + * Support some meta-types to make it easier to write the test file. + */ + + if (!strcmp(of_type, "text")) + match = value_tag == IPP_TAG_TEXTLANG || value_tag == IPP_TAG_TEXT; + else if (!strcmp(of_type, "name")) + match = value_tag == IPP_TAG_NAMELANG || value_tag == IPP_TAG_NAME; + else if (!strcmp(of_type, "collection")) + match = value_tag == IPP_TAG_BEGIN_COLLECTION; + else + match = value_tag == ippTagValue(of_type); + + /* + * Restore the separator if we have one... + */ + + if (sep) + *next++ = sep; + } + + return (match); +} + + +/* + * 'get_collection()' - Get a collection value from the current test file. + */ + +static ipp_t * /* O - Collection value */ +get_collection(_cups_vars_t *vars, /* I - Variables */ + FILE *fp, /* I - File to read from */ + int *linenum) /* IO - Line number */ +{ + char token[1024], /* Token from file */ + temp[1024], /* Temporary string */ + attr[128]; /* Attribute name */ + ipp_tag_t value; /* Current value type */ + ipp_t *col = ippNew(); /* Collection value */ + ipp_attribute_t *lastcol = NULL; /* Last collection attribute */ + + + while (get_token(fp, token, sizeof(token), linenum) != NULL) + { + if (!strcmp(token, "}")) + break; + else if (!strcmp(token, "{") && lastcol) + { + /* + * Another collection value + */ + + ipp_t *subcol = get_collection(vars, fp, linenum); + /* Collection value */ + + if (subcol) + { + ipp_attribute_t *tempcol; /* Pointer to new buffer */ + + + /* + * Reallocate memory... + */ + + if ((tempcol = realloc(lastcol, sizeof(ipp_attribute_t) + + (lastcol->num_values + 1) * + sizeof(ipp_value_t))) == NULL) + { + print_fatal_error("Unable to allocate memory on line %d.", *linenum); + goto col_error; + } + + if (tempcol != lastcol) + { + /* + * Reset pointers in the list... + */ + + if (col->prev) + col->prev->next = tempcol; + else + col->attrs = tempcol; + + lastcol = col->current = col->last = tempcol; + } + + lastcol->values[lastcol->num_values].collection = subcol; + lastcol->num_values ++; + } + else + goto col_error; + } + else if (!_cups_strcasecmp(token, "MEMBER")) + { + /* + * Attribute... + */ + + lastcol = NULL; + + if (!get_token(fp, token, sizeof(token), linenum)) + { + print_fatal_error("Missing MEMBER value tag on line %d.", *linenum); + goto col_error; + } + + if ((value = ippTagValue(token)) == IPP_TAG_ZERO) + { + print_fatal_error("Bad MEMBER value tag \"%s\" on line %d.", token, + *linenum); + goto col_error; + } + + if (!get_token(fp, attr, sizeof(attr), linenum)) + { + print_fatal_error("Missing MEMBER name on line %d.", *linenum); + goto col_error; + } + + if (!get_token(fp, temp, sizeof(temp), linenum)) + { + print_fatal_error("Missing MEMBER value on line %d.", *linenum); + goto col_error; + } + + expand_variables(vars, token, temp, sizeof(token)); + + switch (value) + { + case IPP_TAG_BOOLEAN : + if (!_cups_strcasecmp(token, "true")) + ippAddBoolean(col, IPP_TAG_ZERO, attr, 1); + else + ippAddBoolean(col, IPP_TAG_ZERO, attr, atoi(token)); + break; + + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + ippAddInteger(col, IPP_TAG_ZERO, value, attr, atoi(token)); + break; + + case IPP_TAG_RESOLUTION : + { + int xres, /* X resolution */ + yres; /* Y resolution */ + char units[6]; /* Units */ + + if (sscanf(token, "%dx%d%5s", &xres, &yres, units) != 3 || + (_cups_strcasecmp(units, "dpi") && _cups_strcasecmp(units, "dpc") && + _cups_strcasecmp(units, "other"))) + { + print_fatal_error("Bad resolution value \"%s\" on line %d.", + token, *linenum); + goto col_error; + } + + if (!_cups_strcasecmp(units, "dpi")) + ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres, + IPP_RES_PER_INCH); + else if (!_cups_strcasecmp(units, "dpc")) + ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres, + IPP_RES_PER_CM); + else + ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres, + (ipp_res_t)0); + } + break; + + case IPP_TAG_RANGE : + { + int lowers[4], /* Lower value */ + uppers[4], /* Upper values */ + num_vals; /* Number of values */ + + + num_vals = sscanf(token, "%d-%d,%d-%d,%d-%d,%d-%d", + lowers + 0, uppers + 0, + lowers + 1, uppers + 1, + lowers + 2, uppers + 2, + lowers + 3, uppers + 3); + + if ((num_vals & 1) || num_vals == 0) + { + print_fatal_error("Bad rangeOfInteger value \"%s\" on line %d.", + token, *linenum); + goto col_error; + } + + ippAddRanges(col, IPP_TAG_ZERO, attr, num_vals / 2, lowers, + uppers); + } + break; + + case IPP_TAG_BEGIN_COLLECTION : + if (!strcmp(token, "{")) + { + ipp_t *subcol = get_collection(vars, fp, linenum); + /* Collection value */ + + if (subcol) + { + lastcol = ippAddCollection(col, IPP_TAG_ZERO, attr, subcol); + ippDelete(subcol); + } + else + goto col_error; + } + else + { + print_fatal_error("Bad collection value on line %d.", *linenum); + goto col_error; + } + break; + + default : + if (!strchr(token, ',')) + ippAddString(col, IPP_TAG_ZERO, value, attr, NULL, token); + else + { + /* + * Multiple string values... + */ + + int num_values; /* Number of values */ + char *values[100], /* Values */ + *ptr; /* Pointer to next value */ + + + values[0] = token; + num_values = 1; + + for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ',')) + { + *ptr++ = '\0'; + values[num_values] = ptr; + num_values ++; + } + + ippAddStrings(col, IPP_TAG_ZERO, value, attr, num_values, + NULL, (const char **)values); + } + break; + } + } + } + + return (col); + + /* + * If we get here there was a parse error; free memory and return. + */ + + col_error: + + ippDelete(col); + + return (NULL); +} + + +/* + * 'get_filename()' - Get a filename based on the current test file. + */ + +static char * /* O - Filename */ +get_filename(const char *testfile, /* I - Current test file */ + char *dst, /* I - Destination filename */ + const char *src, /* I - Source filename */ + size_t dstsize) /* I - Size of destination buffer */ +{ + char *dstptr; /* Pointer into destination */ + _cups_globals_t *cg = _cupsGlobals(); + /* Global data */ + + + if (*src == '<' && src[strlen(src) - 1] == '>') + { + /* + * Map <filename> to CUPS_DATADIR/ipptool/filename... + */ + + snprintf(dst, dstsize, "%s/ipptool/%s", cg->cups_datadir, src + 1); + dstptr = dst + strlen(dst) - 1; + if (*dstptr == '>') + *dstptr = '\0'; + } + else if (*src == '/' || !strchr(testfile, '/')) + { + /* + * Use the path as-is... + */ + + strlcpy(dst, src, dstsize); + } + else + { + /* + * Make path relative to testfile... + */ + + strlcpy(dst, testfile, dstsize); + if ((dstptr = strrchr(dst, '/')) != NULL) + dstptr ++; + else + dstptr = dst; /* Should never happen */ + + strlcpy(dstptr, src, dstsize - (dstptr - dst)); + } + + return (dst); +} + + +/* + * 'get_token()' - Get a token from a file. + */ + +static char * /* O - Token from file or NULL on EOF */ +get_token(FILE *fp, /* I - File to read from */ + char *buf, /* I - Buffer to read into */ + int buflen, /* I - Length of buffer */ + int *linenum) /* IO - Current line number */ +{ + int ch, /* Character from file */ + quote; /* Quoting character */ + char *bufptr, /* Pointer into buffer */ + *bufend; /* End of buffer */ + + + for (;;) + { + /* + * Skip whitespace... + */ + + while (isspace(ch = getc(fp))) + { + if (ch == '\n') + (*linenum) ++; + } + + /* + * Read a token... + */ + + if (ch == EOF) + return (NULL); + else if (ch == '\'' || ch == '\"') + { + /* + * Quoted text or regular expression... + */ + + quote = ch; + bufptr = buf; + bufend = buf + buflen - 1; + + while ((ch = getc(fp)) != EOF) + { + if (ch == '\\') + { + /* + * Escape next character... + */ + + if (bufptr < bufend) + *bufptr++ = ch; + + if ((ch = getc(fp)) != EOF && bufptr < bufend) + *bufptr++ = ch; + } + else if (ch == quote) + break; + else if (bufptr < bufend) + *bufptr++ = ch; + } + + *bufptr = '\0'; + + return (buf); + } + else if (ch == '#') + { + /* + * Comment... + */ + + while ((ch = getc(fp)) != EOF) + if (ch == '\n') + break; + + (*linenum) ++; + } + else + { + /* + * Whitespace delimited text... + */ + + ungetc(ch, fp); + + bufptr = buf; + bufend = buf + buflen - 1; + + while ((ch = getc(fp)) != EOF) + if (isspace(ch) || ch == '#') + break; + else if (bufptr < bufend) + *bufptr++ = ch; + + if (ch == '#') + ungetc(ch, fp); + else if (ch == '\n') + (*linenum) ++; + + *bufptr = '\0'; + + return (buf); + } + } +} + + +/* + * 'get_variable()' - Get the value of a variable. + */ + +static char * /* O - Value or NULL */ +get_variable(_cups_vars_t *vars, /* I - Variables */ + const char *name) /* I - Variable name */ +{ + _cups_var_t key, /* Search key */ + *match; /* Matching variable, if any */ + + + key.name = (char *)name; + match = cupsArrayFind(vars->vars, &key); + + return (match ? match->value : NULL); +} + + +/* + * 'iso_date()' - Return an ISO 8601 date/time string for the given IPP dateTime + * value. + */ + +static char * /* O - ISO 8601 date/time string */ +iso_date(ipp_uchar_t *date) /* I - IPP (RFC 1903) date/time value */ +{ + time_t utctime; /* UTC time since 1970 */ + struct tm *utcdate; /* UTC date/time */ + static char buffer[255]; /* String buffer */ + + + utctime = ippDateToTime(date); + utcdate = gmtime(&utctime); + + snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02dZ", + utcdate->tm_year + 1900, utcdate->tm_mon + 1, utcdate->tm_mday, + utcdate->tm_hour, utcdate->tm_min, utcdate->tm_sec); + + return (buffer); +} + + +/* + * 'password_cb()' - Password callback for authenticated tests. + */ + +static const char * /* O - Password */ +password_cb(const char *prompt) /* I - Prompt (unused) */ +{ + (void)prompt; + + return (Password); +} + + +/* + * 'print_attr()' - Print an attribute on the screen. + */ + +static void +print_attr(ipp_attribute_t *attr, /* I - Attribute to print */ + ipp_tag_t *group) /* IO - Current group */ +{ + int i; /* Looping var */ + ipp_attribute_t *colattr; /* Collection attribute */ + + + if (Output == _CUPS_OUTPUT_PLIST) + { + if (!attr->name || (group && *group != attr->group_tag)) + { + puts("</dict>"); + puts("<dict>"); + + if (group) + *group = attr->group_tag; + } + + if (!attr->name) + return; + + print_xml_string("key", attr->name); + if (attr->num_values > 1) + puts("<array>"); + + switch (attr->value_tag) + { + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + printf("<integer>%d</integer>\n", attr->values[i].integer); + else + printf("%d ", attr->values[i].integer); + break; + + case IPP_TAG_BOOLEAN : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + puts(attr->values[i].boolean ? "<true />" : "<false />"); + else if (attr->values[i].boolean) + fputs("true ", stdout); + else + fputs("false ", stdout); + break; + + case IPP_TAG_RANGE : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + printf("<dict><key>lower</key><integer>%d</integer>" + "<key>upper</key><integer>%d</integer></dict>\n", + attr->values[i].range.lower, attr->values[i].range.upper); + else + printf("%d-%d ", attr->values[i].range.lower, + attr->values[i].range.upper); + break; + + case IPP_TAG_RESOLUTION : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + printf("<dict><key>xres</key><integer>%d</integer>" + "<key>yres</key><integer>%d</integer>" + "<key>units</key><string>%s</string></dict>\n", + attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == IPP_RES_PER_INCH ? + "dpi" : "dpc"); + else + printf("%dx%d%s ", attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == IPP_RES_PER_INCH ? + "dpi" : "dpc"); + break; + + case IPP_TAG_DATE : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + printf("<date>%s</date>\n", iso_date(attr->values[i].date)); + else + printf("%s ", iso_date(attr->values[i].date)); + break; + + case IPP_TAG_STRING : + case IPP_TAG_TEXT : + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + case IPP_TAG_CHARSET : + case IPP_TAG_URI : + case IPP_TAG_MIMETYPE : + case IPP_TAG_LANGUAGE : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + print_xml_string("string", attr->values[i].string.text); + else + printf("\"%s\" ", attr->values[i].string.text); + break; + + case IPP_TAG_TEXTLANG : + case IPP_TAG_NAMELANG : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + { + fputs("<dict><key>language</key><string>", stdout); + print_xml_string(NULL, attr->values[i].string.charset); + fputs("</string><key>string</key><string>", stdout); + print_xml_string(NULL, attr->values[i].string.text); + puts("</string></dict>"); + } + else + printf("\"%s\"(%s) ", attr->values[i].string.text, + attr->values[i].string.charset); + break; + + case IPP_TAG_BEGIN_COLLECTION : + for (i = 0; i < attr->num_values; i ++) + { + if (Output == _CUPS_OUTPUT_PLIST) + { + puts("<dict>"); + for (colattr = attr->values[i].collection->attrs; + colattr; + colattr = colattr->next) + print_attr(colattr, NULL); + puts("</dict>"); + } + else + { + if (i) + putchar(' '); + + print_col(attr->values[i].collection); + } + } + break; + + default : + if (Output == _CUPS_OUTPUT_PLIST) + printf("<string><<%s>></string>\n", + ippTagString(attr->value_tag)); + else + fputs(ippTagString(attr->value_tag), stdout); + break; + } + + if (attr->num_values > 1) + puts("</array>"); + } + else + { + char buffer[8192]; /* Value buffer */ + + if (Output == _CUPS_OUTPUT_TEST) + { + if (!attr->name) + { + puts(" -- separator --"); + return; + } + + printf(" %s (%s%s) = ", attr->name, + attr->num_values > 1 ? "1setOf " : "", + ippTagString(attr->value_tag)); + } + + _ippAttrString(attr, buffer, sizeof(buffer)); + puts(buffer); + } +} + + +/* + * 'print_col()' - Print a collection attribute on the screen. + */ + +static void +print_col(ipp_t *col) /* I - Collection attribute to print */ +{ + int i; /* Looping var */ + ipp_attribute_t *attr; /* Current attribute in collection */ + + + fputs("{ ", stdout); + for (attr = col->attrs; attr; attr = attr->next) + { + printf("%s (%s%s) = ", attr->name, attr->num_values > 1 ? "1setOf " : "", + ippTagString(attr->value_tag)); + + switch (attr->value_tag) + { + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + for (i = 0; i < attr->num_values; i ++) + printf("%d ", attr->values[i].integer); + break; + + case IPP_TAG_BOOLEAN : + for (i = 0; i < attr->num_values; i ++) + if (attr->values[i].boolean) + printf("true "); + else + printf("false "); + break; + + case IPP_TAG_NOVALUE : + printf("novalue"); + break; + + case IPP_TAG_RANGE : + for (i = 0; i < attr->num_values; i ++) + printf("%d-%d ", attr->values[i].range.lower, + attr->values[i].range.upper); + break; + + case IPP_TAG_RESOLUTION : + for (i = 0; i < attr->num_values; i ++) + printf("%dx%d%s ", attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == IPP_RES_PER_INCH ? + "dpi" : "dpc"); + break; + + case IPP_TAG_STRING : + case IPP_TAG_TEXT : + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + case IPP_TAG_CHARSET : + case IPP_TAG_URI : + case IPP_TAG_MIMETYPE : + case IPP_TAG_LANGUAGE : + for (i = 0; i < attr->num_values; i ++) + printf("\"%s\" ", attr->values[i].string.text); + break; + + case IPP_TAG_TEXTLANG : + case IPP_TAG_NAMELANG : + for (i = 0; i < attr->num_values; i ++) + printf("\"%s\",%s ", attr->values[i].string.text, + attr->values[i].string.charset); + break; + + case IPP_TAG_BEGIN_COLLECTION : + for (i = 0; i < attr->num_values; i ++) + { + print_col(attr->values[i].collection); + putchar(' '); + } + break; + + default : + break; /* anti-compiler-warning-code */ + } + } + + putchar('}'); +} + + +/* + * 'print_csv()' - Print a line of CSV text. + */ + +static void +print_csv( + ipp_attribute_t *attr, /* I - First attribute for line */ + int num_displayed, /* I - Number of attributes to display */ + char **displayed, /* I - Attributes to display */ + size_t *widths) /* I - Column widths */ +{ + int i; /* Looping var */ + size_t maxlength; /* Max length of all columns */ + char *buffer, /* String buffer */ + *bufptr; /* Pointer into buffer */ + ipp_attribute_t *current; /* Current attribute */ + + + /* + * Get the maximum string length we have to show and allocate... + */ + + for (i = 1, maxlength = widths[0]; i < num_displayed; i ++) + if (widths[i] > maxlength) + maxlength = widths[i]; + + maxlength += 2; + + if ((buffer = malloc(maxlength)) == NULL) + return; + + /* + * Loop through the attributes to display... + */ + + if (attr) + { + for (i = 0; i < num_displayed; i ++) + { + if (i) + putchar(','); + + buffer[0] = '\0'; + + for (current = attr; current; current = current->next) + { + if (!current->name) + break; + else if (!strcmp(current->name, displayed[i])) + { + _ippAttrString(current, buffer, maxlength); + break; + } + } + + if (strchr(buffer, ',') != NULL || strchr(buffer, '\"') != NULL || + strchr(buffer, '\\') != NULL) + { + putchar('\"'); + for (bufptr = buffer; *bufptr; bufptr ++) + { + if (*bufptr == '\\' || *bufptr == '\"') + putchar('\\'); + putchar(*bufptr); + } + putchar('\"'); + } + else + fputs(buffer, stdout); + } + putchar('\n'); + } + else + { + for (i = 0; i < num_displayed; i ++) + { + if (i) + putchar(','); + + fputs(displayed[i], stdout); + } + putchar('\n'); + } + + free(buffer); +} + + +/* + * 'print_fatal_error()' - Print a fatal error message. + */ + +static void +print_fatal_error(const char *s, /* I - Printf-style format string */ + ...) /* I - Additional arguments as needed */ +{ + char buffer[10240]; /* Format buffer */ + va_list ap; /* Pointer to arguments */ + + + /* + * Format the error message... + */ + + va_start(ap, s); + vsnprintf(buffer, sizeof(buffer), s, ap); + va_end(ap); + + /* + * Then output it... + */ + + if (Output == _CUPS_OUTPUT_PLIST) + { + print_xml_header(); + print_xml_trailer(0, buffer); + } + else + _cupsLangPrintf(stderr, "ipptool: %s", buffer); +} + + +/* + * 'print_line()' - Print a line of formatted or CSV text. + */ + +static void +print_line( + ipp_attribute_t *attr, /* I - First attribute for line */ + int num_displayed, /* I - Number of attributes to display */ + char **displayed, /* I - Attributes to display */ + size_t *widths) /* I - Column widths */ +{ + int i; /* Looping var */ + size_t maxlength; /* Max length of all columns */ + char *buffer; /* String buffer */ + ipp_attribute_t *current; /* Current attribute */ + + + /* + * Get the maximum string length we have to show and allocate... + */ + + for (i = 1, maxlength = widths[0]; i < num_displayed; i ++) + if (widths[i] > maxlength) + maxlength = widths[i]; + + maxlength += 2; + + if ((buffer = malloc(maxlength)) == NULL) + return; + + /* + * Loop through the attributes to display... + */ + + if (attr) + { + for (i = 0; i < num_displayed; i ++) + { + if (i) + putchar(' '); + + buffer[0] = '\0'; + + for (current = attr; current; current = current->next) + { + if (!current->name) + break; + else if (!strcmp(current->name, displayed[i])) + { + _ippAttrString(current, buffer, maxlength); + break; + } + } + + printf("%*s", (int)-widths[i], buffer); + } + putchar('\n'); + } + else + { + for (i = 0; i < num_displayed; i ++) + { + if (i) + putchar(' '); + + printf("%*s", (int)-widths[i], displayed[i]); + } + putchar('\n'); + + for (i = 0; i < num_displayed; i ++) + { + if (i) + putchar(' '); + + memset(buffer, '-', widths[i]); + buffer[widths[i]] = '\0'; + fputs(buffer, stdout); + } + putchar('\n'); + } + + free(buffer); +} + + +/* + * 'print_test_error()' - Print a test error message. + */ + +static void +print_test_error(const char *s, /* I - Printf-style format string */ + ...) /* I - Additional arguments as needed */ +{ + char buffer[10240]; /* Format buffer */ + va_list ap; /* Pointer to arguments */ + + + /* + * Format the error message... + */ + + va_start(ap, s); + vsnprintf(buffer, sizeof(buffer), s, ap); + va_end(ap); + + /* + * Then output it... + */ + + if (Output == _CUPS_OUTPUT_PLIST) + print_xml_string("string", buffer); + else + printf(" %s\n", buffer); +} + + +/* + * 'print_xml_header()' - Print a standard XML plist header. + */ + +static void +print_xml_header(void) +{ + if (!XMLHeader) + { + puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + puts("<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" " + "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"); + puts("<plist version=\"1.0\">"); + puts("<dict>"); + puts("<key>Transfer</key>"); + printf("<string>%s</string>\n", + Transfer == _CUPS_TRANSFER_AUTO ? "auto" : + Transfer == _CUPS_TRANSFER_CHUNKED ? "chunked" : "length"); + puts("<key>Tests</key>"); + puts("<array>"); + + XMLHeader = 1; + } +} + + +/* + * 'print_xml_string()' - Print an XML string with escaping. + */ + +static void +print_xml_string(const char *element, /* I - Element name or NULL */ + const char *s) /* I - String to print */ +{ + if (element) + printf("<%s>", element); + + while (*s) + { + if (*s == '&') + fputs("&", stdout); + else if (*s == '<') + fputs("<", stdout); + else if (*s == '>') + fputs(">", stdout); + else if ((*s & 0xe0) == 0xc0) + { + /* + * Validate UTF-8 two-byte sequence... + */ + + if ((s[1] & 0xc0) != 0x80) + { + putchar('?'); + s ++; + } + else + { + putchar(*s++); + putchar(*s); + } + } + else if ((*s & 0xf0) == 0xe0) + { + /* + * Validate UTF-8 three-byte sequence... + */ + + if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80) + { + putchar('?'); + s += 2; + } + else + { + putchar(*s++); + putchar(*s++); + putchar(*s); + } + } + else if ((*s & 0xf8) == 0xf0) + { + /* + * Validate UTF-8 four-byte sequence... + */ + + if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || + (s[3] & 0xc0) != 0x80) + { + putchar('?'); + s += 3; + } + else + { + putchar(*s++); + putchar(*s++); + putchar(*s++); + putchar(*s); + } + } + else if ((*s & 0x80) || (*s < ' ' && !isspace(*s & 255))) + { + /* + * Invalid control character... + */ + + putchar('?'); + } + else + putchar(*s); + + s ++; + } + + if (element) + printf("</%s>\n", element); +} + + +/* + * 'print_xml_trailer()' - Print the XML trailer with success/fail value. + */ + +static void +print_xml_trailer(int success, /* I - 1 on success, 0 on failure */ + const char *message) /* I - Error message or NULL */ +{ + if (XMLHeader) + { + puts("</array>"); + puts("<key>Successful</key>"); + puts(success ? "<true />" : "<false />"); + if (message) + { + puts("<key>ErrorMessage</key>"); + print_xml_string("string", message); + } + puts("</dict>"); + puts("</plist>"); + + XMLHeader = 0; + } +} + + +/* + * 'set_variable()' - Set a variable value. + */ + +static void +set_variable(_cups_vars_t *vars, /* I - Variables */ + const char *name, /* I - Variable name */ + const char *value) /* I - Value string */ +{ + _cups_var_t key, /* Search key */ + *var; /* New variable */ + + + if (!_cups_strcasecmp(name, "filename")) + { + if (vars->filename) + free(vars->filename); + + vars->filename = strdup(value); + return; + } + + key.name = (char *)name; + if ((var = cupsArrayFind(vars->vars, &key)) != NULL) + { + free(var->value); + var->value = strdup(value); + } + else if ((var = malloc(sizeof(_cups_var_t))) == NULL) + { + print_fatal_error("Unable to allocate memory for variable \"%s\".", name); + exit(1); + } + else + { + var->name = strdup(name); + var->value = strdup(value); + + cupsArrayAdd(vars->vars, var); + } +} + + +#ifndef WIN32 +/* + * 'sigterm_handler()' - Handle SIGINT and SIGTERM. + */ + +static void +sigterm_handler(int sig) /* I - Signal number (unused) */ +{ + (void)sig; + + Cancel = 1; + + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); +} +#endif /* !WIN32 */ + + +/* + * 'timeout_cb()' - Handle HTTP timeouts. + */ + +static int /* O - 1 to continue, 0 to cancel */ +timeout_cb(http_t *http, /* I - Connection to server (unused) */ + void *user_data) /* I - User data (unused) */ +{ + (void)http; + (void)user_data; + + /* Always cancel on timeout */ + return (0); +} + + +/* + * 'usage()' - Show program usage. + */ + +static void +usage(void) +{ + _cupsLangPuts(stderr, _("Usage: ipptool [options] URI filename [ ... " + "filenameN ]")); + _cupsLangPuts(stderr, _("Options:")); + _cupsLangPuts(stderr, _(" -4 Connect using IPv4.")); + _cupsLangPuts(stderr, _(" -6 Connect using IPv6.")); + _cupsLangPuts(stderr, _(" -C Send requests using " + "chunking (default).")); + _cupsLangPuts(stderr, _(" -E Test with TLS " + "encryption.")); + _cupsLangPuts(stderr, _(" -I Ignore errors.")); + _cupsLangPuts(stderr, _(" -L Send requests using " + "content-length.")); + _cupsLangPuts(stderr, _(" -S Test with SSL " + "encryption.")); + _cupsLangPuts(stderr, _(" -T Set the receive/send " + "timeout in seconds.")); + _cupsLangPuts(stderr, _(" -V version Set default IPP " + "version.")); + _cupsLangPuts(stderr, _(" -X Produce XML plist instead " + "of plain text.")); + _cupsLangPuts(stderr, _(" -d name=value Set named variable to " + "value.")); + _cupsLangPuts(stderr, _(" -f filename Set default request " + "filename.")); + _cupsLangPuts(stderr, _(" -i seconds Repeat the last file with " + "the given time interval.")); + _cupsLangPuts(stderr, _(" -n count Repeat the last file the " + "given number of times.")); + _cupsLangPuts(stderr, _(" -q Be quiet - no output " + "except errors.")); + _cupsLangPuts(stderr, _(" -t Produce a test report.")); + _cupsLangPuts(stderr, _(" -v Show all attributes sent " + "and received.")); + + exit(1); +} + + +/* + * 'validate_attr()' - Determine whether an attribute is valid. + */ + +static int /* O - 1 if valid, 0 otherwise */ +validate_attr(ipp_attribute_t *attr, /* I - Attribute to validate */ + int print) /* I - 1 = report issues to stdout */ +{ + int i; /* Looping var */ + char scheme[64], /* Scheme from URI */ + userpass[256], /* Username/password from URI */ + hostname[256], /* Hostname from URI */ + resource[1024]; /* Resource from URI */ + int port, /* Port number from URI */ + uri_status, /* URI separation status */ + valid = 1; /* Is the attribute valid? */ + const char *ptr; /* Pointer into string */ + ipp_attribute_t *colattr; /* Collection attribute */ + regex_t re; /* Regular expression */ + ipp_uchar_t *date; /* Current date value */ + + + /* + * Skip separators. + */ + + if (!attr->name) + return (1); + + /* + * Validate the attribute name. + */ + + for (ptr = attr->name; *ptr; ptr ++) + if (!isalnum(*ptr & 255) && *ptr != '-' && *ptr != '.' && *ptr != '_') + break; + + if (*ptr || ptr == attr->name) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad attribute name - invalid character (RFC " + "2911 section 4.1.3).", attr->name); + } + + if ((ptr - attr->name) > 255) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad attribute name - bad length (RFC 2911 " + "section 4.1.3).", attr->name); + } + + switch (attr->value_tag) + { + case IPP_TAG_INTEGER : + break; + + case IPP_TAG_BOOLEAN : + for (i = 0; i < attr->num_values; i ++) + { + if (attr->values[i].boolean != 0 && + attr->values[i].boolean != 1) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad boolen value %d (RFC 2911 section " + "4.1.10).", attr->name, attr->values[i].boolean); + else + break; + } + } + break; + + case IPP_TAG_ENUM : + for (i = 0; i < attr->num_values; i ++) + { + if (attr->values[i].integer < 1) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad enum value %d - out of range " + "(RFC 2911 section 4.1.4).", attr->name, + attr->values[i].integer); + else + break; + } + } + break; + + case IPP_TAG_STRING : + for (i = 0; i < attr->num_values; i ++) + { + if (attr->values[i].unknown.length > 1023) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad octetString value - bad length %d " + "(RFC 2911 section 4.1.10).", attr->name, + attr->values[i].unknown.length); + else + break; + } + } + break; + + case IPP_TAG_DATE : + for (i = 0; i < attr->num_values; i ++) + { + date = attr->values[i].date; + + if (date[2] < 1 || date[2] > 12) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime month %u (RFC 2911 " + "section 4.1.13).", attr->name, date[2]); + else + break; + } + + if (date[3] < 1 || date[3] > 31) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime day %u (RFC 2911 " + "section 4.1.13).", attr->name, date[3]); + else + break; + } + + if (date[4] > 23) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime hours %u (RFC 2911 " + "section 4.1.13).", attr->name, date[4]); + else + break; + } + + if (date[5] > 59) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime minutes %u (RFC 2911 " + "section 4.1.13).", attr->name, date[5]); + else + break; + } + + if (date[6] > 60) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime seconds %u (RFC 2911 " + "section 4.1.13).", attr->name, date[6]); + else + break; + } + + if (date[7] > 9) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime deciseconds %u (RFC 2911 " + "section 4.1.13).", attr->name, date[7]); + else + break; + } + + if (date[8] != '-' && date[8] != '+') + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime UTC sign '%c' (RFC 2911 " + "section 4.1.13).", attr->name, date[8]); + else + break; + } + + if (date[9] > 11) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime UTC hours %u (RFC 2911 " + "section 4.1.13).", attr->name, date[9]); + else + break; + } + + if (date[10] > 59) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime UTC minutes %u (RFC 2911 " + "section 4.1.13).", attr->name, date[10]); + else + break; + } + } + break; + + case IPP_TAG_RESOLUTION : + for (i = 0; i < attr->num_values; i ++) + { + if (attr->values[i].resolution.xres <= 0) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad resolution value %dx%d%s - cross " + "feed resolution must be positive (RFC 2911 " + "section 4.1.13).", attr->name, + attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == + IPP_RES_PER_INCH ? "dpi" : + attr->values[i].resolution.units == + IPP_RES_PER_CM ? "dpc" : "unknown"); + else + break; + } + + if (attr->values[i].resolution.yres <= 0) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad resolution value %dx%d%s - feed " + "resolution must be positive (RFC 2911 section " + "4.1.13).", attr->name, + attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == + IPP_RES_PER_INCH ? "dpi" : + attr->values[i].resolution.units == + IPP_RES_PER_CM ? "dpc" : "unknown"); + else + break; + } + + if (attr->values[i].resolution.units != IPP_RES_PER_INCH && + attr->values[i].resolution.units != IPP_RES_PER_CM) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad resolution value %dx%d%s - bad " + "units value (RFC 2911 section 4.1.13).", + attr->name, attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == + IPP_RES_PER_INCH ? "dpi" : + attr->values[i].resolution.units == + IPP_RES_PER_CM ? "dpc" : "unknown"); + else + break; + } + } + break; + + case IPP_TAG_RANGE : + for (i = 0; i < attr->num_values; i ++) + { + if (attr->values[i].range.lower > attr->values[i].range.upper) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad rangeOfInteger value %d-%d - lower " + "greater than upper (RFC 2911 section 4.1.13).", + attr->name, attr->values[i].range.lower, + attr->values[i].range.upper); + else + break; + } + } + break; + + case IPP_TAG_BEGIN_COLLECTION : + for (i = 0; i < attr->num_values; i ++) + { + for (colattr = attr->values[i].collection->attrs; + colattr; + colattr = colattr->next) + { + if (!validate_attr(colattr, 0)) + { + valid = 0; + break; + } + } + + if (colattr && print) + { + print_test_error("\"%s\": Bad collection value.", attr->name); + + while (colattr) + { + validate_attr(colattr, print); + colattr = colattr->next; + } + } + } + break; + + case IPP_TAG_TEXT : + case IPP_TAG_TEXTLANG : + for (i = 0; i < attr->num_values; i ++) + { + for (ptr = attr->values[i].string.text; *ptr; ptr ++) + { + if ((*ptr & 0xe0) == 0xc0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if ((*ptr & 0xf0) == 0xe0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if ((*ptr & 0xf8) == 0xf0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if (*ptr & 0x80) + break; + } + + if (*ptr) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad text value \"%s\" - bad UTF-8 " + "sequence (RFC 2911 section 4.1.1).", attr->name, + attr->values[i].string.text); + else + break; + } + + if ((ptr - attr->values[i].string.text) > 1023) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad text value \"%s\" - bad length %d " + "(RFC 2911 section 4.1.1).", attr->name, + attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_NAME : + case IPP_TAG_NAMELANG : + for (i = 0; i < attr->num_values; i ++) + { + for (ptr = attr->values[i].string.text; *ptr; ptr ++) + { + if ((*ptr & 0xe0) == 0xc0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if ((*ptr & 0xf0) == 0xe0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if ((*ptr & 0xf8) == 0xf0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if (*ptr & 0x80) + break; + } + + if (*ptr) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad name value \"%s\" - bad UTF-8 " + "sequence (RFC 2911 section 4.1.2).", attr->name, + attr->values[i].string.text); + else + break; + } + + if ((ptr - attr->values[i].string.text) > 1023) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad name value \"%s\" - bad length %d " + "(RFC 2911 section 4.1.2).", attr->name, + attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_KEYWORD : + for (i = 0; i < attr->num_values; i ++) + { + for (ptr = attr->values[i].string.text; *ptr; ptr ++) + if (!isalnum(*ptr & 255) && *ptr != '-' && *ptr != '.' && + *ptr != '_') + break; + + if (*ptr || ptr == attr->values[i].string.text) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad keyword value \"%s\" - invalid " + "character (RFC 2911 section 4.1.3).", + attr->name, attr->values[i].string.text); + else + break; + } + + if ((ptr - attr->values[i].string.text) > 255) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad keyword value \"%s\" - bad " + "length %d (RFC 2911 section 4.1.3).", + attr->name, attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_URI : + for (i = 0; i < attr->num_values; i ++) + { + uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, + attr->values[i].string.text, + scheme, sizeof(scheme), + userpass, sizeof(userpass), + hostname, sizeof(hostname), + &port, resource, sizeof(resource)); + + if (uri_status < HTTP_URI_OK) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad URI value \"%s\" - %s " + "(RFC 2911 section 4.1.5).", attr->name, + attr->values[i].string.text, + URIStatusStrings[uri_status - + HTTP_URI_OVERFLOW]); + else + break; + } + + if (strlen(attr->values[i].string.text) > 1023) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad URI value \"%s\" - bad length %d " + "(RFC 2911 section 4.1.5).", attr->name, + attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_URISCHEME : + for (i = 0; i < attr->num_values; i ++) + { + ptr = attr->values[i].string.text; + if (islower(*ptr & 255)) + { + for (ptr ++; *ptr; ptr ++) + if (!islower(*ptr & 255) && !isdigit(*ptr & 255) && + *ptr != '+' && *ptr != '-' && *ptr != '.') + break; + } + + if (*ptr || ptr == attr->values[i].string.text) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad uriScheme value \"%s\" - bad " + "characters (RFC 2911 section 4.1.6).", + attr->name, attr->values[i].string.text); + else + break; + } + + if ((ptr - attr->values[i].string.text) > 63) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad uriScheme value \"%s\" - bad " + "length %d (RFC 2911 section 4.1.6).", + attr->name, attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_CHARSET : + for (i = 0; i < attr->num_values; i ++) + { + for (ptr = attr->values[i].string.text; *ptr; ptr ++) + if (!isprint(*ptr & 255) || isupper(*ptr & 255) || + isspace(*ptr & 255)) + break; + + if (*ptr || ptr == attr->values[i].string.text) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad charset value \"%s\" - bad " + "characters (RFC 2911 section 4.1.7).", + attr->name, attr->values[i].string.text); + else + break; + } + + if ((ptr - attr->values[i].string.text) > 40) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad charset value \"%s\" - bad " + "length %d (RFC 2911 section 4.1.7).", + attr->name, attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_LANGUAGE : + /* + * The following regular expression is derived from the ABNF for + * language tags in RFC 4646. All I can say is that this is the + * easiest way to check the values... + */ + + if ((i = regcomp(&re, + "^(" + "(([a-z]{2,3}(-[a-z][a-z][a-z]){0,3})|[a-z]{4,8})" + /* language */ + "(-[a-z][a-z][a-z][a-z]){0,1}" /* script */ + "(-([a-z][a-z]|[0-9][0-9][0-9])){0,1}" /* region */ + "(-([a-z]{5,8}|[0-9][0-9][0-9]))*" /* variant */ + "(-[a-wy-z](-[a-z0-9]{2,8})+)*" /* extension */ + "(-x(-[a-z0-9]{1,8})+)*" /* privateuse */ + "|" + "x(-[a-z0-9]{1,8})+" /* privateuse */ + "|" + "[a-z]{1,3}(-[a-z][0-9]{2,8}){1,2}" /* grandfathered */ + ")$", + REG_NOSUB | REG_EXTENDED)) != 0) + { + char temp[256]; /* Temporary error string */ + + regerror(i, &re, temp, sizeof(temp)); + print_fatal_error("Unable to compile naturalLanguage regular " + "expression: %s.", temp); + } + + for (i = 0; i < attr->num_values; i ++) + { + if (regexec(&re, attr->values[i].string.text, 0, NULL, 0)) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad naturalLanguage value \"%s\" - bad " + "characters (RFC 2911 section 4.1.8).", + attr->name, attr->values[i].string.text); + else + break; + } + + if (strlen(attr->values[i].string.text) > 63) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad naturalLanguage value \"%s\" - bad " + "length %d (RFC 2911 section 4.1.8).", + attr->name, attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + + regfree(&re); + break; + + case IPP_TAG_MIMETYPE : + /* + * The following regular expression is derived from the ABNF for + * language tags in RFC 2045 and 4288. All I can say is that this is + * the easiest way to check the values... + */ + + if ((i = regcomp(&re, + "^" + "[-a-zA-Z0-9!#$&.+^_]{1,127}" /* type-name */ + "/" + "[-a-zA-Z0-9!#$&.+^_]{1,127}" /* subtype-name */ + "(;[-a-zA-Z0-9!#$&.+^_]{1,127}=" /* parameter= */ + "([-a-zA-Z0-9!#$&.+^_]{1,127}|\"[^\"]*\"))*" + /* value */ + "$", + REG_NOSUB | REG_EXTENDED)) != 0) + { + char temp[256]; /* Temporary error string */ + + regerror(i, &re, temp, sizeof(temp)); + print_fatal_error("Unable to compile mimeMediaType regular " + "expression: %s.", temp); + } + + for (i = 0; i < attr->num_values; i ++) + { + if (regexec(&re, attr->values[i].string.text, 0, NULL, 0)) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad mimeMediaType value \"%s\" - bad " + "characters (RFC 2911 section 4.1.9).", + attr->name, attr->values[i].string.text); + else + break; + } + + if (strlen(attr->values[i].string.text) > 255) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad mimeMediaType value \"%s\" - bad " + "length %d (RFC 2911 section 4.1.9).", + attr->name, attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + default : + break; + } + + return (valid); +} + + +/* + * 'with_value()' - Test a WITH-VALUE predicate. + */ + +static int /* O - 1 on match, 0 on non-match */ +with_value(char *value, /* I - Value string */ + int regex, /* I - Value is a regular expression */ + ipp_attribute_t *attr, /* I - Attribute to compare */ + int report, /* I - 1 = report failures */ + char *matchbuf, /* I - Buffer to hold matching value */ + size_t matchlen) /* I - Length of match buffer */ +{ + int i; /* Looping var */ + char *valptr; /* Pointer into value */ + + + *matchbuf = '\0'; + + /* + * NULL matches everything. + */ + + if (!value || !*value) + return (1); + + /* + * Compare the value string to the attribute value. + */ + + switch (attr->value_tag) + { + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + for (i = 0; i < attr->num_values; i ++) + { + char op, /* Comparison operator */ + *nextptr; /* Next pointer */ + int intvalue; /* Integer value */ + + + valptr = value; + + while (isspace(*valptr & 255) || isdigit(*valptr & 255) || + *valptr == '-' || *valptr == ',' || *valptr == '<' || + *valptr == '=' || *valptr == '>') + { + op = '='; + while (*valptr && !isdigit(*valptr & 255) && *valptr != '-') + { + if (*valptr == '<' || *valptr == '>' || *valptr == '=') + op = *valptr; + valptr ++; + } + + if (!*valptr) + break; + + intvalue = strtol(valptr, &nextptr, 0); + if (nextptr == valptr) + break; + valptr = nextptr; + + switch (op) + { + case '=' : + if (attr->values[i].integer == intvalue) + { + snprintf(matchbuf, matchlen, "%d", attr->values[i].integer); + return (1); + } + break; + case '<' : + if (attr->values[i].integer < intvalue) + { + snprintf(matchbuf, matchlen, "%d", attr->values[i].integer); + return (1); + } + break; + case '>' : + if (attr->values[i].integer > intvalue) + { + snprintf(matchbuf, matchlen, "%d", attr->values[i].integer); + return (1); + } + break; + } + } + } + + if (report) + { + for (i = 0; i < attr->num_values; i ++) + print_test_error("GOT: %s=%d", attr->name, attr->values[i].integer); + } + break; + + case IPP_TAG_RANGE : + for (i = 0; i < attr->num_values; i ++) + { + char op, /* Comparison operator */ + *nextptr; /* Next pointer */ + int intvalue; /* Integer value */ + + + valptr = value; + + while (isspace(*valptr & 255) || isdigit(*valptr & 255) || + *valptr == '-' || *valptr == ',' || *valptr == '<' || + *valptr == '=' || *valptr == '>') + { + op = '='; + while (*valptr && !isdigit(*valptr & 255) && *valptr != '-') + { + if (*valptr == '<' || *valptr == '>' || *valptr == '=') + op = *valptr; + valptr ++; + } + + if (!*valptr) + break; + + intvalue = strtol(valptr, &nextptr, 0); + if (nextptr == valptr) + break; + valptr = nextptr; + + switch (op) + { + case '=' : + if (attr->values[i].range.lower == intvalue || + attr->values[i].range.upper == intvalue) + { + snprintf(matchbuf, matchlen, "%d-%d", + attr->values[i].range.lower, + attr->values[i].range.upper); + return (1); + } + break; + case '<' : + if (attr->values[i].range.upper < intvalue) + { + snprintf(matchbuf, matchlen, "%d-%d", + attr->values[i].range.lower, + attr->values[i].range.upper); + return (1); + } + break; + case '>' : + if (attr->values[i].range.upper > intvalue) + { + snprintf(matchbuf, matchlen, "%d-%d", + attr->values[i].range.lower, + attr->values[i].range.upper); + return (1); + } + break; + } + } + } + + if (report) + { + for (i = 0; i < attr->num_values; i ++) + print_test_error("GOT: %s=%d-%d", attr->name, + attr->values[i].range.lower, + attr->values[i].range.upper); + } + break; + + case IPP_TAG_BOOLEAN : + for (i = 0; i < attr->num_values; i ++) + { + if (!strcmp(value, "true") == attr->values[i].boolean) + { + strlcpy(matchbuf, value, matchlen); + return (1); + } + } + + if (report) + { + for (i = 0; i < attr->num_values; i ++) + print_test_error("GOT: %s=%s", attr->name, + attr->values[i].boolean ? "true" : "false"); + } + break; + + case IPP_TAG_NOVALUE : + case IPP_TAG_UNKNOWN : + return (1); + + case IPP_TAG_CHARSET : + case IPP_TAG_KEYWORD : + case IPP_TAG_LANGUAGE : + case IPP_TAG_MIMETYPE : + case IPP_TAG_NAME : + case IPP_TAG_NAMELANG : + case IPP_TAG_TEXT : + case IPP_TAG_TEXTLANG : + case IPP_TAG_URI : + case IPP_TAG_URISCHEME : + if (regex) + { + /* + * Value is an extended, case-sensitive POSIX regular expression... + */ + + regex_t re; /* Regular expression */ + + if ((i = regcomp(&re, value, REG_EXTENDED | REG_NOSUB)) != 0) + { + char temp[256]; /* Temporary string */ + + regerror(i, &re, temp, sizeof(temp)); + + print_fatal_error("Unable to compile WITH-VALUE regular expression " + "\"%s\" - %s", value, temp); + return (0); + } + + /* + * See if ALL of the values match the given regular expression. + */ + + for (i = 0; i < attr->num_values; i ++) + { + if (regexec(&re, attr->values[i].string.text, 0, NULL, 0)) + { + if (report) + print_test_error("GOT: %s=\"%s\"", attr->name, + attr->values[i].string.text); + else + break; + } + } + + regfree(&re); + + if (i == attr->num_values) + strlcpy(matchbuf, attr->values[0].string.text, matchlen); + + return (i == attr->num_values); + } + else + { + /* + * Value is a literal string, see if at least one value matches the + * literal string... + */ + + for (i = 0; i < attr->num_values; i ++) + { + if (!strcmp(value, attr->values[i].string.text)) + { + strlcpy(matchbuf, attr->values[i].string.text, matchlen); + return (1); + } + } + + if (report) + { + for (i = 0; i < attr->num_values; i ++) + print_test_error("GOT: %s=\"%s\"", attr->name, + attr->values[i].string.text); + } + } + break; + + default : + break; + } + + return (0); +} + + +/* + * End of "$Id: ipptool.c 10090 2011-10-25 22:39:56Z mike $". + */ |