diff options
Diffstat (limited to 'test/ipptest.c')
-rw-r--r-- | test/ipptest.c | 3387 |
1 files changed, 2981 insertions, 406 deletions
diff --git a/test/ipptest.c b/test/ipptest.c index 28404388d..e94f06f36 100644 --- a/test/ipptest.c +++ b/test/ipptest.c @@ -1,9 +1,9 @@ /* - * "$Id: ipptest.c 7847 2008-08-19 04:22:14Z mike $" + * "$Id: ipptest.c 9000 2010-02-24 06:29:38Z mike $" * * IPP test command for the Common UNIX Printing System (CUPS). * - * Copyright 2007-2009 by Apple Inc. + * Copyright 2007-2010 by Apple Inc. * Copyright 1997-2007 by Easy Software Products. * * These coded instructions, statements, and computer programs are the @@ -14,13 +14,28 @@ * * Contents: * - * main() - Parse options and do tests. - * do_tests() - Do tests as specified in the test file. - * expect_matches() - Return true if the tag matches the specification. - * get_token() - Get a token from a file. - * print_attr() - Print an attribute on the screen. - * print_col() - Print a collection attribute on the screen. - * usage() - Show program usage. + * 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. + * print_attr() - Print an attribute on the screen. + * print_col() - Print a collection attribute on the screen. + * print_fatal_error() - Print a fatal error message. + * 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. + * usage() - Show program usage. + * validate_attr() - Determine whether an attribute is valid. + * with_value() - Test a WITH-VALUE predicate. */ /* @@ -29,13 +44,11 @@ #include <stdio.h> #include <stdlib.h> -#include <cups/string.h> #include <errno.h> #include <ctype.h> +#include <regex.h> -#include <cups/cups.h> -#include <cups/language.h> -#include <cups/http-private.h> +#include <cups/globals.h> #ifndef O_BINARY # define O_BINARY 0 #endif /* !O_BINARY */ @@ -45,35 +58,122 @@ * Types... */ +typedef enum /**** 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 struct _cups_expect_s /**** Expected attribute info ****/ { - int 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 */ + 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_undefined, /* Only required if variable is not defined */ + *with_value; /* Attribute must include this value */ + int 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_undefined; /* Only if variable is not defined */ +} _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 ****/ +{ + const char *uri, /* URI for printer */ + *filename; /* Filename */ + char 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? */ + cups_array_t *vars; /* Array of variables */ +} _cups_vars_t; + /* * Globals... */ -int Chunking = 0; /* Use chunked requests */ -int Verbosity = 0; /* Show all attributes? */ +_cups_transfer_t Transfer = _CUPS_TRANSFER_AUTO; + /* How to transfer requests */ +int Verbosity = 0, /* Show all attributes? */ + Version = 11, /* Default IPP version */ + XML = 0, /* Produce XML output? */ + XMLHeader = 0; /* 1 if header is written */ +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 do_tests(const char *uri, const char *testfile); +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 void print_attr(ipp_attribute_t *attr); static void print_col(ipp_t *col); +static void print_fatal_error(const char *s, ...) +#ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 1, 2))) +#endif /* __GNUC__ */ +; +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); 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); /* @@ -84,21 +184,32 @@ 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 */ - const char *uri, /* URI to use */ - *testfile; /* Test file to use */ - int interval; /* Test interval */ + int i; /* Looping var */ + int status; /* Status of tests... */ + char *opt, /* Current option */ + name[1024], /* Name/value buffer */ + *value; /* Pointer to value */ + const char *testfile; /* Test file to use */ + int interval; /* Test interval */ + _cups_vars_t vars; /* Variables */ + http_uri_status_t uri_status; /* URI separation status */ /* + * Initialize the locale and variables... + */ + + _cupsSetLocale(argv); + + memset(&vars, 0, sizeof(vars)); + vars.vars = cupsArrayNew((cups_array_func_t)compare_vars, NULL); + + /* * We need at least: * - * testipp URL testfile + * ipptest URI testfile */ - uri = NULL; testfile = NULL; status = 0; interval = 0; @@ -111,8 +222,58 @@ main(int argc, /* I - Number of command-line args */ { switch (*opt) { + case 'E' : /* Encrypt */ +#ifdef HAVE_SSL + vars.encryption = HTTP_ENCRYPT_REQUIRED; +#else + _cupsLangPrintf(stderr, + _("%s: Sorry, no encryption support compiled in\n"), + argv[0]); +#endif /* HAVE_SSL */ + break; + + case 'V' : /* Set IPP version */ + i ++; + + if (i >= argc) + { + _cupsLangPuts(stderr, + _("ipptest: Missing version for \"-V\".\n")); + 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, + _("ipptest: Bad version %s for \"-V\".\n"), + argv[i]); + usage(); + } + break; + + case 'X' : /* Produce XML output */ + XML = 1; + + if (interval) + { + _cupsLangPuts(stderr, _("ipptest: \"-i\" is incompatible with " + "\"-x\".\n")); + usage(); + } + break; + case 'c' : /* Enable HTTP chunking */ - Chunking = 1; + Transfer = _CUPS_TRANSFER_CHUNKED; break; case 'd' : /* Define a variable */ @@ -120,11 +281,31 @@ main(int argc, /* I - Number of command-line args */ if (i >= argc) { - fputs("ipptest: Missing name=value for \"-d\"!\n", stderr); + _cupsLangPuts(stderr, + _("ipptest: Missing name=value for \"-d\".\n")); usage(); } + + strlcpy(name, argv[i], sizeof(name)); + if ((value = strchr(name, '=')) != NULL) + *value++ = '\0'; else - putenv(argv[i]); + value = name + strlen(name); + + set_variable(&vars, name, value); + break; + + case 'f' : /* Set the default test filename */ + i ++; + + if (i >= argc) + { + _cupsLangPuts(stderr, + _("ipptest: Missing filename for \"-f\".\n")); + usage(); + } + + vars.filename = argv[i]; break; case 'i' : /* Test every N seconds */ @@ -132,19 +313,32 @@ main(int argc, /* I - Number of command-line args */ if (i >= argc) { - fputs("ipptest: Missing seconds for \"-i\"!\n", stderr); + _cupsLangPuts(stderr, + _("ipptest: Missing seconds for \"-i\".\n")); usage(); } else interval = atoi(argv[i]); + + if (XML && interval) + { + _cupsLangPuts(stderr, _("ipptest: \"-i\" is incompatible with " + "\"-x\".\n")); + usage(); + } break; + case 'l' : /* Disable HTTP chunking */ + Transfer = _CUPS_TRANSFER_LENGTH; + break; + case 'v' : /* Be verbose */ Verbosity ++; break; default : - fprintf(stderr, "ipptest: Unknown option \"-%c\"!\n", *opt); + _cupsLangPrintf(stderr, _("ipptest: Unknown option \"-%c\".\n"), + *opt); usage(); break; } @@ -158,15 +352,34 @@ main(int argc, /* I - Number of command-line args */ * Set URI... */ - if (!testfile && uri) + if (vars.uri) { - fputs("ipptest: May only specify a single URI before a test!\n", - stderr); + _cupsLangPuts(stderr, _("ipptest: May only specify a single URI.\n")); usage(); } - uri = argv[i]; - testfile = NULL; + 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, _("ipptest: Bad URI - %s.\n"), + URIStatusStrings[uri_status - HTTP_URI_OVERFLOW]); + return (1); + } + + if (strcmp(vars.scheme, "http") && strcmp(vars.scheme, "https") && + strcmp(vars.scheme, "ipp")) + { + _cupsLangPuts(stderr, _("ipptest: Only http, https, and ipp URIs are " + "supported.")); + return (1); + } } else { @@ -174,26 +387,34 @@ main(int argc, /* I - Number of command-line args */ * Run test... */ + if (!vars.uri) + { + _cupsLangPuts(stderr, _("ipptest: URI required before test file.")); + usage(); + } + testfile = argv[i]; - if (!do_tests(uri, testfile)) - status ++; + if (!do_tests(&vars, testfile)) + status = 1; } } - if (!uri || !testfile) + if (!vars.uri || !testfile) usage(); /* * Loop if the interval is set... */ - if (interval) + if (XML) + print_xml_trailer(!status, NULL); + else if (interval) { for (;;) { sleep(interval); - do_tests(uri, testfile); + do_tests(&vars, testfile); } } @@ -204,50 +425,59 @@ main(int argc, /* I - Number of command-line args */ 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 (strcasecmp(a->name, b->name)); +} + + /* * 'do_tests()' - Do tests as specified in the test file. */ static int /* 1 = success, 0 = failure */ -do_tests(const char *uri, /* I - URI to connect on */ - const char *testfile) /* I - Test file to use */ +do_tests(_cups_vars_t *vars, /* I - Variables */ + const char *testfile) /* I - Test file to use */ { - int i; /* Looping var */ - int linenum; /* Current line number */ - int version; /* IPP version number to use */ - http_t *http; /* HTTP connection to server */ - char scheme[HTTP_MAX_URI], /* URI scheme */ - userpass[HTTP_MAX_URI], /* username:password */ - server[HTTP_MAX_URI], /* Server */ - resource[HTTP_MAX_URI]; /* Resource path */ - int port; /* Port number */ - FILE *fp; /* Test file */ - char token[1024], /* Token from file */ + int i, /* Looping var */ + linenum, /* Current line number */ + pass, /* Did we pass the test? */ + request_id; /* Current request ID */ + 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 */ - *tempptr; /* Pointer into temp string */ - ipp_t *request; /* IPP request */ - ipp_t *response; /* IPP response */ + temp[1024]; /* Temporary string */ + ipp_t *request = NULL; /* IPP request */ + ipp_t *response = NULL; /* IPP response */ + 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 */ - char attr[128]; /* Attribute name */ - int num_statuses; /* Number of valid status codes */ - ipp_status_t statuses[100]; /* Valid status codes */ - int num_expects; /* Number of expected attributes */ - _cups_expect_t expects[100], /* Expected attributes */ + *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 */ + 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; /* Number of displayed attributes */ + int num_displayed = 0; /* Number of displayed attributes */ char *displayed[100]; /* Displayed attributes */ - char name[1024]; /* Name of test */ - char filename[1024]; /* Filename */ - int pass; /* Did we pass the test? */ - int job_id; /* Job ID from last operation */ - int subscription_id; /* Subscription ID from last operation */ /* @@ -256,35 +486,37 @@ do_tests(const char *uri, /* I - URI to connect on */ if ((fp = fopen(testfile, "r")) == NULL) { - printf("Unable to open test file %s - %s\n", testfile, strerror(errno)); - return (0); + print_fatal_error("Unable to open test file %s - %s", testfile, + strerror(errno)); + goto test_error; } /* * Connect to the server... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, - sizeof(userpass), server, sizeof(server), &port, resource, - sizeof(resource)); - if ((http = httpConnect(server, port)) == NULL) + if ((http = httpConnectEncrypt(vars->hostname, vars->port, + vars->encryption)) == NULL) { - printf("Unable to connect to %s on port %d - %s\n", server, port, - strerror(errno)); - fclose(fp); - return (0); + print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname, + vars->port, strerror(errno)); + goto test_error; } /* * Loop on tests... */ - printf("\"%s\":\n", testfile); - pass = 1; - job_id = 0; - subscription_id = 0; - version = 11; - linenum = 1; + if (XML) + print_xml_header(); + else + printf("\"%s\":\n", testfile); + + CUPS_SRAND(time(NULL)); + + pass = 1; + linenum = 1; + request_id = (CUPS_RAND() % 1000) * 137 + 1; while (get_token(fp, token, sizeof(token), &linenum) != NULL) { @@ -292,32 +524,134 @@ do_tests(const char *uri, /* I - URI to connect on */ * Expect an open brace... */ - if (strcmp(token, "{")) + if (!strcmp(token, "DEFINE")) { - printf("Unexpected token %s seen on line %d - aborting test!\n", token, - linenum); - httpClose(http); - return (0); + /* + * 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); + goto test_error; + } + + 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)))) + goto test_error; + } + else + { + print_fatal_error("Missing INCLUDE filename on line %d.", linenum); + goto test_error; + } + + 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); + goto test_error; + } + } + else + { + print_fatal_error("Missing TRANSFER value on line %d.", linenum); + goto test_error; + } + + 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); + goto test_error; + } + } + else + { + print_fatal_error("Missing VERSION number on line %d.", linenum); + goto test_error; + } + + continue; + } + else if (strcmp(token, "{")) + { + print_fatal_error("Unexpected token %s seen on line %d.", token, linenum); + goto test_error; } /* * Initialize things... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, - sizeof(userpass), server, sizeof(server), &port, resource, - sizeof(resource)); + strlcpy(resource, vars->resource, sizeof(resource)); + request_id ++; request = ippNew(); op = (ipp_op_t)0; group = IPP_TAG_ZERO; - num_statuses = 0; - num_expects = 0; - num_displayed = 0; last_expect = NULL; + last_status = NULL; filename[0] = '\0'; + version = Version; + transfer = Transfer; - strcpy(name, testfile); + strlcpy(name, testfile, sizeof(name)); if (strrchr(name, '.') != NULL) *strrchr(name, '.') = '\0'; @@ -327,14 +661,86 @@ do_tests(const char *uri, /* I - URI to connect on */ while (get_token(fp, token, sizeof(token), &linenum) != NULL) { - if (strcasecmp(token, "EXPECT") && + if (strcasecmp(token, "COUNT") && strcasecmp(token, "IF-DEFINED") && + strcasecmp(token, "IF-UNDEFINED") && + strcasecmp(token, "IN-GROUP") && strcasecmp(token, "OF-TYPE") && - strcasecmp(token, "SAME-COUNT-AS")) + strcasecmp(token, "SAME-COUNT-AS") && + strcasecmp(token, "WITH-VALUE")) last_expect = NULL; + if (strcasecmp(token, "IF-DEFINED") && + strcasecmp(token, "IF-UNDEFINED")) + 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); + goto test_error; + } + + 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 + goto test_error; + } + 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); + goto test_error; + } + } else if (!strcasecmp(token, "NAME")) { /* @@ -343,26 +749,87 @@ do_tests(const char *uri, /* I - URI to connect on */ get_token(fp, name, sizeof(name), &linenum); } - else if (!strcasecmp(token, "VERSION")) + else if (!strcmp(token, "REQUEST-ID")) { /* - * IPP version number for test... + * REQUEST-ID # + * REQUEST-ID random */ - int major, minor; /* Major/minor IPP version */ - + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (isdigit(temp[0] & 255)) + request_id = atoi(temp); + else if (!strcasecmp(temp, "random")) + request_id = (CUPS_RAND() % 1000) * 137 + 1; + else + { + print_fatal_error("Bad REQUEST-ID value \"%s\" on line %d.", temp, + linenum); + goto test_error; + } + } + else + { + print_fatal_error("Missing REQUEST-ID value on line %d.", linenum); + goto test_error; + } + } + else if (!strcmp(token, "TRANSFER")) + { + /* + * TRANSFER auto + * TRANSFER chunked + * TRANSFER length + */ - get_token(fp, temp, sizeof(temp), &linenum); - if (sscanf(temp, "%d.%d", &major, &minor) == 2 && - major >= 0 && minor >= 0 && minor < 10) - version = major * 10 + minor; + 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); + goto test_error; + } + } + else + { + print_fatal_error("Missing TRANSFER value on line %d.", linenum); + goto test_error; + } + } + else if (!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); + goto test_error; + } + } else { - printf("Bad version %s seen on line %d - aborting test!\n", token, - linenum); - httpClose(http); - ippDelete(request); - return (0); + print_fatal_error("Missing VERSION number on line %d.", linenum); + goto test_error; } } else if (!strcasecmp(token, "RESOURCE")) @@ -371,7 +838,11 @@ do_tests(const char *uri, /* I - URI to connect on */ * Resource name... */ - get_token(fp, resource, sizeof(resource), &linenum); + if (!get_token(fp, resource, sizeof(resource), &linenum)) + { + print_fatal_error("Missing RESOURCE path on line %d.", linenum); + goto test_error; + } } else if (!strcasecmp(token, "OPERATION")) { @@ -379,8 +850,18 @@ do_tests(const char *uri, /* I - URI to connect on */ * Operation... */ - get_token(fp, token, sizeof(token), &linenum); - op = ippOpValue(token); + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing OPERATION code on line %d.", linenum); + goto test_error; + } + + if ((op = ippOpValue(token)) < 0) + { + print_fatal_error("Bad OPERATION code \"%s\" on line %d.", token, + linenum); + goto test_error; + } } else if (!strcasecmp(token, "GROUP")) { @@ -388,8 +869,17 @@ do_tests(const char *uri, /* I - URI to connect on */ * Attribute group... */ - get_token(fp, token, sizeof(token), &linenum); - value = ippTagValue(token); + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing GROUP tag on line %d.", linenum); + goto test_error; + } + + if ((value = ippTagValue(token)) < 0) + { + print_fatal_error("Bad GROUP tag \"%s\" on line %d.", token, linenum); + goto test_error; + } if (value == group) ippAddSeparator(request); @@ -404,8 +894,19 @@ do_tests(const char *uri, /* I - URI to connect on */ int delay; - get_token(fp, token, sizeof(token), &linenum); - if ((delay = atoi(token)) > 0) + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DELAY value on line %d.", linenum); + goto test_error; + } + + if ((delay = atoi(token)) <= 0) + { + print_fatal_error("Bad DELAY value \"%s\" on line %d.", token, + linenum); + goto test_error; + } + else sleep(delay); } else if (!strcasecmp(token, "ATTR")) @@ -414,102 +915,34 @@ do_tests(const char *uri, /* I - URI to connect on */ * Attribute... */ - get_token(fp, token, sizeof(token), &linenum); - value = ippTagValue(token); - get_token(fp, attr, sizeof(attr), &linenum); - get_token(fp, temp, sizeof(temp), &linenum); - - token[sizeof(token) - 1] = '\0'; + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing ATTR value tag on line %d.", linenum); + goto test_error; + } - for (tempptr = temp, tokenptr = token; - *tempptr && tokenptr < (token + sizeof(token) - 1);) - if (*tempptr == '$') - { - /* - * Substitute a string/number... - */ + if ((value = ippTagValue(token)) < 0) + { + print_fatal_error("Bad ATTR value tag \"%s\" on line %d.", token, + linenum); + goto test_error; + } - if (!strncasecmp(tempptr + 1, "uri", 3)) - { - strlcpy(tokenptr, uri, sizeof(token) - (tokenptr - token)); - tempptr += 4; - } - else if (!strncasecmp(tempptr + 1, "scheme", 6) || - !strncasecmp(tempptr + 1, "method", 6)) - { - strlcpy(tokenptr, scheme, sizeof(token) - (tokenptr - token)); - tempptr += 7; - } - else if (!strncasecmp(tempptr + 1, "username", 8)) - { - strlcpy(tokenptr, userpass, sizeof(token) - (tokenptr - token)); - tempptr += 9; - } - else if (!strncasecmp(tempptr + 1, "hostname", 8)) - { - strlcpy(tokenptr, server, sizeof(token) - (tokenptr - token)); - tempptr += 9; - } - else if (!strncasecmp(tempptr + 1, "port", 4)) - { - snprintf(tokenptr, sizeof(token) - (tokenptr - token), - "%d", port); - tempptr += 5; - } - else if (!strncasecmp(tempptr + 1, "resource", 8)) - { - strlcpy(tokenptr, resource, sizeof(token) - (tokenptr - token)); - tempptr += 9; - } - else if (!strncasecmp(tempptr + 1, "job-id", 6)) - { - snprintf(tokenptr, sizeof(token) - (tokenptr - token), - "%d", job_id); - tempptr += 7; - } - else if (!strncasecmp(tempptr + 1, "notify-subscription-id", 22)) - { - snprintf(tokenptr, sizeof(token) - (tokenptr - token), - "%d", subscription_id); - tempptr += 23; - } - else if (!strncasecmp(tempptr + 1, "user", 4)) - { - strlcpy(tokenptr, cupsUser(), sizeof(token) - (tokenptr - token)); - tempptr += 5; - } - else if (!strncasecmp(tempptr + 1, "ENV[", 4)) - { - char *end; /* End of $ENV[name] */ + printf("%d: value=%s\n", linenum, ippTagString(value)); + if (!get_token(fp, attr, sizeof(attr), &linenum)) + { + print_fatal_error("Missing ATTR name on line %d.", linenum); + goto test_error; + } - if ((end = strchr(tempptr + 5, ']')) != NULL) - { - *end++ = '\0'; - strlcpy(tokenptr, - getenv(tempptr + 5) ? getenv(tempptr + 5) : tempptr + 5, - sizeof(token) - (tokenptr - token)); - tempptr = end; - } - else - { - *tokenptr++ = *tempptr++; - *tokenptr = '\0'; - } - } - else - { - *tokenptr++ = *tempptr++; - *tokenptr = '\0'; - } + if (!get_token(fp, temp, sizeof(temp), &linenum)) + { + print_fatal_error("Missing ATTR value on line %d.", linenum); + goto test_error; + } - tokenptr += strlen(tokenptr); - } - else - { - *tokenptr++ = *tempptr++; - *tokenptr = '\0'; - } + expand_variables(vars, token, temp, sizeof(token)); switch (value) { @@ -526,11 +959,74 @@ do_tests(const char *uri, /* I - URI to connect on */ break; case IPP_TAG_RESOLUTION : - puts(" ERROR: resolution tag not yet supported!"); + { + int xres, /* X resolution */ + yres; /* Y resolution */ + char units[6]; /* Units */ + + if (sscanf(token, "%dx%d%5s", &xres, &yres, units) != 3 || + (strcasecmp(units, "dpi") && strcasecmp(units, "dpc") && + strcasecmp(units, "other"))) + { + print_fatal_error("Bad resolution value \"%s\" on line %d.", + token, linenum); + goto test_error; + } + + if (!strcasecmp(units, "dpi")) + ippAddResolution(request, group, attr, xres, yres, + IPP_RES_PER_INCH); + else if (!strcasecmp(units, "dpc")) + ippAddResolution(request, group, attr, xres, yres, + IPP_RES_PER_CM); + else + ippAddResolution(request, group, attr, xres, yres, + (ipp_res_t)0); + } break; case IPP_TAG_RANGE : - puts(" ERROR: range tag not yet supported!"); + { + 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 test_error; + } + + 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); + else + goto test_error; + } + else + { + print_fatal_error("Bad ATTR collection value on line %d.", + linenum); + goto test_error; + } break; default : @@ -569,18 +1065,45 @@ do_tests(const char *uri, /* I - URI to connect on */ * File... */ - get_token(fp, filename, sizeof(filename), &linenum); + if (!get_token(fp, temp, sizeof(temp), &linenum)) + { + print_fatal_error("Missing FILE filename on line %d.", linenum); + goto test_error; + } + + expand_variables(vars, token, temp, sizeof(token)); + get_filename(testfile, filename, token, sizeof(filename)); } - else if (!strcasecmp(token, "STATUS") && - num_statuses < (int)(sizeof(statuses) / sizeof(statuses[0]))) + else if (!strcasecmp(token, "STATUS")) { /* * Status... */ - get_token(fp, token, sizeof(token), &linenum); - statuses[num_statuses] = ippErrorValue(token); + if (num_statuses >= (int)(sizeof(statuses) / sizeof(statuses[0]))) + { + print_fatal_error("Too many STATUS's on line %d.", linenum); + goto test_error; + } + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing STATUS code on line %d.", linenum); + goto test_error; + } + + if ((statuses[num_statuses].status = ippErrorValue(token)) < 0) + { + print_fatal_error("Bad STATUS code \"%s\" on line %d.", token, + linenum); + goto test_error; + } + + last_status = statuses + num_statuses; num_statuses ++; + + last_status->if_defined = NULL; + last_status->if_undefined = NULL; } else if (!strcasecmp(token, "EXPECT")) { @@ -590,99 +1113,218 @@ do_tests(const char *uri, /* I - URI to connect on */ if (num_expects >= (int)(sizeof(expects) / sizeof(expects[0]))) { - fprintf(stderr, "ipptest: Too many EXPECT's on line %d\n", linenum); - httpClose(http); - ippDelete(request); - return (0); + print_fatal_error("Too many EXPECT's on line %d.", linenum); + goto test_error; } - get_token(fp, token, sizeof(token), &linenum); + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing EXPECT name on line %d.", linenum); + goto test_error; + } 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 + else if (token[0] == '?') { - last_expect->not_expect = 0; - last_expect->name = strdup(token); + last_expect->optional = 1; + last_expect->name = strdup(token + 1); + } + else + last_expect->name = strdup(token); + } + else if (!strcasecmp(token, "COUNT")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing COUNT number on line %d.", linenum); + goto test_error; + } + + if ((i = atoi(token)) <= 0) + { + print_fatal_error("Bad COUNT \"%s\" on line %d.", token, linenum); + goto test_error; } - last_expect->of_type = NULL; - last_expect->same_count_as = NULL; - last_expect->if_defined = NULL; + if (last_expect) + last_expect->count = i; + else + { + print_fatal_error("COUNT without a preceding EXPECT on line %d.", + linenum); + goto test_error; + } } else if (!strcasecmp(token, "OF-TYPE")) { - get_token(fp, token, sizeof(token), &linenum); + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing OF-TYPE value tag(s) on line %d.", + linenum); + goto test_error; + } if (last_expect) last_expect->of_type = strdup(token); else { - fprintf(stderr, - "ipptest: OF-TYPE without a preceding EXPECT on line %d\n", - linenum); - httpClose(http); - ippDelete(request); - return (0); + print_fatal_error("OF-TYPE without a preceding EXPECT on line %d.", + linenum); + goto test_error; + } + } + else if (!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); + goto test_error; + } + + 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); + goto test_error; } } else if (!strcasecmp(token, "SAME-COUNT-AS")) { - get_token(fp, token, sizeof(token), &linenum); + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing SAME-COUNT-AS name on line %d.", linenum); + goto test_error; + } if (last_expect) last_expect->same_count_as = strdup(token); else { - fprintf(stderr, - "ipptest: SAME-COUNT-AS without a preceding EXPECT on line " - "%d\n", linenum); - httpClose(http); - ippDelete(request); - return (0); + print_fatal_error("SAME-COUNT-AS without a preceding EXPECT on line " + "%d.", linenum); + goto test_error; } } else if (!strcasecmp(token, "IF-DEFINED")) { - get_token(fp, token, sizeof(token), &linenum); + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing IF-DEFINED name on line %d.", linenum); + goto test_error; + } if (last_expect) last_expect->if_defined = strdup(token); + else if (last_status) + last_status->if_defined = strdup(token); else { - fprintf(stderr, - "ipptest: IF-DEFINED without a preceding EXPECT on line %d\n", - linenum); - httpClose(http); - ippDelete(request); - return (0); + print_fatal_error("IF-DEFINED without a preceding EXPECT or STATUS " + "on line %d.", linenum); + goto test_error; } } - else if (!strcasecmp(token, "DISPLAY") && - num_displayed < (int)(sizeof(displayed) / sizeof(displayed[0]))) + else if (!strcasecmp(token, "IF-UNDEFINED")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing IF-UNDEFINED name on line %d.", linenum); + goto test_error; + } + + if (last_expect) + last_expect->if_undefined = strdup(token); + else if (last_status) + last_status->if_undefined = strdup(token); + else + { + print_fatal_error("IF-UNDEFINED without a preceding EXPECT or STATUS " + "on line %d.", linenum); + goto test_error; + } + } + else if (!strcasecmp(token, "WITH-VALUE")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing WITH-VALUE value on line %d.", linenum); + goto test_error; + } + + if (last_expect) + { + 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); + goto test_error; + } + } + else if (!strcasecmp(token, "DISPLAY")) { /* * Display attributes... */ - get_token(fp, token, sizeof(token), &linenum); + if (num_displayed >= (int)(sizeof(displayed) / sizeof(displayed[0]))) + { + print_fatal_error("Too many DISPLAY's on line %d", linenum); + goto test_error; + } + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DISPLAY name on line %d.", linenum); + goto test_error; + } + displayed[num_displayed] = strdup(token); num_displayed ++; } else { - fprintf(stderr, - "ipptest: Unexpected token %s seen on line %d - aborting " - "test!\n", token, linenum); - httpClose(http); - ippDelete(request); - return (0); + print_fatal_error("Unexpected token %s seen on line %d.", token, + linenum); + goto test_error; } } @@ -693,21 +1335,42 @@ do_tests(const char *uri, /* I - URI to connect on */ 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 = 1; + request->request.op.request_id = request_id; - if (Verbosity) + if (XML) { - printf(" %s:\n", ippOpString(op)); - + 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("<dict>"); for (attrptr = request->attrs; attrptr; attrptr = attrptr->next) print_attr(attrptr); + puts("</dict>"); } + else + { + if (Verbosity) + { + printf(" %s:\n", ippOpString(op)); - printf(" %-60.60s [", name); - fflush(stdout); + for (attrptr = request->attrs; attrptr; attrptr = attrptr->next) + print_attr(attrptr); + } - if (Chunking) + printf(" %-69.69s [", name); + fflush(stdout); + } + + if (transfer == _CUPS_TRANSFER_CHUNKED || + (transfer == _CUPS_TRANSFER_AUTO && filename[0])) { + /* + * Send request using chunking... + */ + http_status_t status = cupsSendRequest(http, request, resource, 0); if (status == HTTP_CONTINUE && filename[0]) @@ -716,7 +1379,6 @@ do_tests(const char *uri, /* I - URI to connect on */ char buffer[8192]; /* Copy buffer */ ssize_t bytes; /* Bytes read/written */ - if ((fd = open(filename, O_RDONLY | O_BINARY)) >= 0) { while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) @@ -725,7 +1387,12 @@ do_tests(const char *uri, /* I - URI to connect on */ break; } else + { + snprintf(buffer, sizeof(buffer), "%s: %s", filename, strerror(errno)); + _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0); + status = HTTP_ERROR; + } } ippDelete(request); @@ -738,36 +1405,107 @@ do_tests(const char *uri, /* I - URI to connect on */ else if (filename[0]) response = cupsDoFileRequest(http, request, resource, filename); else - response = cupsDoIORequest(http, request, resource, -1, - Verbosity ? 1 : -1); - - if (response == NULL) - { - time_t curtime; + response = cupsDoRequest(http, request, resource); - curtime = time(NULL); + request = NULL; - puts("FAIL]"); - printf(" ERROR %04x (%s) @ %s\n", cupsLastError(), - cupsLastErrorString(), ctime(&curtime)); + if (!response) pass = 0; - } else { if (http->version != HTTP_1_1) pass = 0; + if (response->request.status.version[0] != (version / 10) || + response->request.status.version[1] != (version % 10) || + response->request.status.request_id != request_id) + pass = 0; + if ((attrptr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) != NULL) - job_id = attrptr->values[0].integer; + { + 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) - subscription_id = attrptr->values[0].integer; + { + 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")) + 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")) + 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))) + 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))) + pass = 0; + + for (attrptr = response->attrs, group = attrptr->group_tag; + attrptr; + attrptr = attrptr->next) + { + if (attrptr->group_tag < group && attrptr->group_tag != IPP_TAG_ZERO) + { + pass = 0; + break; + } + + if (!validate_attr(attrptr, 0)) + { + pass = 0; + break; + } + } for (i = 0; i < num_statuses; i ++) - if (response->request.status.status_code == statuses[i]) + { + if (statuses[i].if_defined && + !get_variable(vars, statuses[i].if_defined)) + continue; + + if (statuses[i].if_undefined && + get_variable(vars, statuses[i].if_undefined)) + continue; + + if (response->request.status.status_code == statuses[i].status) break; + } if (i == num_statuses && num_statuses > 0) pass = 0; @@ -775,18 +1513,37 @@ do_tests(const char *uri, /* I - URI to connect on */ { for (i = num_expects, expect = expects; i > 0; i --, expect ++) { - if (expect->if_defined && !getenv(expect->if_defined)) + if (expect->if_defined && !get_variable(vars, expect->if_defined)) + continue; + + if (expect->if_undefined && get_variable(vars, expect->if_undefined)) continue; found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO); - if ((found == NULL) != expect->not_expect || - (found && !expect_matches(expect, found->value_tag))) + 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)) { pass = 0; break; } + if (found && + !with_value(expect->with_value, expect->with_regex, found)) + { + pass = 0; + break; + } + + if (found && expect->count > 0 && found->num_values != expect->count) + { + pass = 0; + break; + } + if (found && expect->same_count_as) { attrptr = ippFindAttribute(response, expect->same_count_as, @@ -800,107 +1557,285 @@ do_tests(const char *uri, /* I - URI to connect on */ } } } + } - if (pass) + if (XML) + { + puts("<key>Successful</key>"); + puts(pass ? "<true />" : "<false />"); + puts("<key>StatusCode</key>"); + print_xml_string("string", ippErrorString(cupsLastError())); + puts("<key>ResponseAttributes</key>"); + puts("<dict>"); + for (attrptr = response ? response->attrs : NULL; + attrptr; + attrptr = attrptr->next) + print_attr(attrptr); + puts("</dict>"); + } + else + { + puts(pass ? "PASS]" : "FAIL]"); + + if (Verbosity && response) { - puts("PASS]"); printf(" RECEIVED: %lu bytes in response\n", (unsigned long)ippLength(response)); + printf(" status-code = %x (%s)\n", cupsLastError(), + ippErrorString(cupsLastError())); - if (Verbosity) - { - for (attrptr = response->attrs; - attrptr != NULL; - attrptr = attrptr->next) - { - print_attr(attrptr); - } - } - else if (num_displayed > 0) + for (attrptr = response->attrs; + attrptr != NULL; + attrptr = attrptr->next) { - for (attrptr = response->attrs; - attrptr != NULL; - attrptr = attrptr->next) - { - if (attrptr->name) + print_attr(attrptr); + } + } + } + + if (pass && !XML && !Verbosity && num_displayed > 0) + { + 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)) { - for (i = 0; i < num_displayed; i ++) - { - if (!strcmp(displayed[i], attrptr->name)) - { - print_attr(attrptr); - break; - } - } + print_attr(attrptr); + break; } - } - } + } + else if (!pass) + { + if (XML) + { + 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 { - puts("FAIL]"); - printf(" RECEIVED: %lu bytes in response\n", - (unsigned long)ippLength(response)); + if (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)); + } + + for (attrptr = response->attrs, group = attrptr->group_tag; + attrptr; + attrptr = attrptr->next) + { + if (attrptr->group_tag < group && attrptr->group_tag != IPP_TAG_ZERO) + print_test_error("Attribute groups out of order (%s < %s)", + ippTagString(attrptr->group_tag), + ippTagString(group)); - if (http->version != HTTP_1_1) - printf(" BAD HTTP VERSION (%d.%d)\n", http->version / 100, - http->version % 100); + validate_attr(attrptr, 1); + } for (i = 0; i < num_statuses; i ++) - if (response->request.status.status_code == statuses[i]) + { + if (statuses[i].if_defined && + !get_variable(vars, statuses[i].if_defined)) + continue; + + if (statuses[i].if_undefined && + get_variable(vars, statuses[i].if_undefined)) + continue; + + if (response->request.status.status_code == statuses[i].status) break; + } if (i == num_statuses && num_statuses > 0) - puts(" BAD STATUS"); + { + print_test_error("Bad status-code (%s)", + ippErrorString(cupsLastError())); + print_test_error("status-message=\"%s\"", cupsLastErrorString()); + } - printf(" status-code = %04x (%s)\n", - cupsLastError(), ippErrorString(cupsLastError())); + for (i = num_expects, expect = expects; i > 0; i --, expect ++) + { + if (expect->if_defined && !get_variable(vars, expect->if_defined)) + continue; + + if (expect->if_undefined && get_variable(vars, expect->if_undefined)) + 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)); - for (i = num_expects, expect = expects; i > 0; i --, expect ++) - { - if (expect->if_defined && !getenv(expect->if_defined)) - continue; + 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)); - found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO); + if (!with_value(expect->with_value, expect->with_regex, found)) + { + 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); + } - if ((found == NULL) != expect->not_expect) - { - if (expect->not_expect) - printf(" NOT EXPECTED: %s\n", expect->name); - else - printf(" EXPECTED: %s\n", expect->name); - } - else if (found) - { - if (!expect_matches(expect, found->value_tag)) - printf(" EXPECTED: %s of type %s but got %s\n", - expect->name, expect->of_type, - ippTagString(found->value_tag)); - else if (expect->same_count_as) + 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) - printf(" EXPECTED: %s (%d values) same count as %s " - "(not returned)\n", - expect->name, found->num_values, expect->same_count_as); + 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) - printf(" EXPECTED: %s (%d values) same count as %s " - "(%d values)\n", - expect->name, found->num_values, expect->same_count_as, - attrptr->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); } } - } - - for (attrptr = response->attrs; attrptr != NULL; attrptr = attrptr->next) - print_attr(attrptr); + } } - ippDelete(response); + if (XML) + puts("</array>"); } + if (XML) + puts("</dict>"); + + 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_undefined) + free(statuses[i].if_undefined); + } + num_statuses = 0; + for (i = num_expects, expect = expects; i > 0; i --, expect ++) { free(expect->name); @@ -910,7 +1845,17 @@ do_tests(const char *uri, /* I - URI to connect on */ free(expect->same_count_as); if (expect->if_defined) free(expect->if_defined); + if (expect->if_undefined) + free(expect->if_undefined); + if (expect->with_value) + free(expect->with_value); } + num_expects = 0; + + for (i = 0; i < num_displayed; i ++) + free(displayed[i]); + num_displayed = 0; + if (!pass) break; } @@ -919,6 +1864,149 @@ do_tests(const char *uri, /* I - URI to connect on */ httpClose(http); return (pass); + + /* + * If we get here there was a fatal test error... + */ + + test_error: + + 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_undefined) + free(statuses[i].if_undefined); + } + + 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_undefined) + free(expect->if_undefined); + if (expect->with_value) + free(expect->with_value); + } + + for (i = 0; i < num_displayed; i ++) + free(displayed[i]); + + return (0); +} + + +/* + * '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'; } @@ -933,7 +2021,8 @@ expect_matches( { int match; /* Match? */ char *of_type, /* Type name to match */ - *next; /* Next name to match */ + *next, /* Next name to match */ + sep; /* Separator character */ /* @@ -945,7 +2034,7 @@ expect_matches( /* * Parse the "of_type" value since the string can contain multiple attribute - * types separated by "|"... + * types separated by "," or "|"... */ for (of_type = expect->of_type, match = 0; !match && of_type; of_type = next) @@ -954,7 +2043,9 @@ expect_matches( * Find the next separator, and set it (temporarily) to nul if present. */ - if ((next = strchr(of_type, '|')) != NULL) + for (next = of_type; *next && *next != '|' && *next != ','; next ++); + + if ((sep = *next) != '\0') *next = '\0'; /* @@ -974,8 +2065,8 @@ expect_matches( * Restore the separator if we have one... */ - if (next) - *next++ = '|'; + if (sep) + *next++ = sep; } return (match); @@ -983,6 +2074,291 @@ expect_matches( /* + * '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 (!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)) < 0) + { + 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 (!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 || + (strcasecmp(units, "dpi") && strcasecmp(units, "dpc") && + strcasecmp(units, "other"))) + { + print_fatal_error("Bad resolution value \"%s\" on line %d.", + token, *linenum); + goto col_error; + } + + if (!strcasecmp(units, "dpi")) + ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres, + IPP_RES_PER_INCH); + else if (!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); + 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/ipptest/filename... + */ + + snprintf(dst, dstsize, "%s/ipptest/%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. */ @@ -1019,7 +2395,7 @@ get_token(FILE *fp, /* I - File to read from */ else if (ch == '\'' || ch == '\"') { /* - * Quoted text... + * Quoted text or regular expression... */ quote = ch; @@ -1027,12 +2403,27 @@ get_token(FILE *fp, /* I - File to read from */ bufend = buf + buflen - 1; while ((ch = getc(fp)) != EOF) - if (ch == quote) + { + 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 == '#') @@ -1066,8 +2457,11 @@ get_token(FILE *fp, /* I - File to read from */ if (ch == '#') ungetc(ch, fp); - + else if (ch == '\n') + (*linenum) ++; + *bufptr = '\0'; + return (buf); } } @@ -1075,59 +2469,142 @@ get_token(FILE *fp, /* I - File to read from */ /* + * '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 */ +{ + unsigned year = (date[0] << 8) + date[1]; + /* Year */ + static char buffer[255]; /* String buffer */ + + + if (date[9] == 0 && date[10] == 0) + snprintf(buffer, sizeof(buffer), "%04u-%02u-%02uT%02u:%02u:%02uZ", + year, date[2], date[3], date[4], date[5], date[6]); + else + snprintf(buffer, sizeof(buffer), "%04u-%02u-%02uT%02u:%02u:%02u%c%02u%02u", + year, date[2], date[3], date[4], date[5], date[6], + date[8], date[9], date[10]); + + return (buffer); +} + + +/* * 'print_attr()' - Print an attribute on the screen. */ static void print_attr(ipp_attribute_t *attr) /* I - Attribute to print */ { - int i; /* Looping var */ + int i; /* Looping var */ + ipp_attribute_t *colattr; /* Collection attribute */ - if (attr->name == NULL) + if (XML) { - puts(" -- separator --"); - return; + if (!attr->name) + { + printf("<key>%s</key>\n<true />\n", ippTagString(attr->group_tag)); + return; + } + + print_xml_string("key", attr->name); + if (attr->num_values > 1) + puts("<array>"); } + else + { + if (!attr->name) + { + puts(" -- separator --"); + return; + } - printf(" %s (%s%s) = ", attr->name, - attr->num_values > 1 ? "1setOf " : "", - ippTagString(attr->value_tag)); + 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); + if (XML) + 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 (attr->values[i].boolean) - printf("true "); + if (XML) + puts(attr->values[i].boolean ? "<true />" : "<false />"); + else if (attr->values[i].boolean) + fputs("true ", stdout); else - printf("false "); - break; - - case IPP_TAG_NOVALUE : - printf("novalue"); + fputs("false ", stdout); 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); + if (XML) + 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 ++) - 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"); + if (XML) + 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 (XML) + 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 : @@ -1137,31 +2614,66 @@ print_attr(ipp_attribute_t *attr) /* I - Attribute to print */ case IPP_TAG_MIMETYPE : case IPP_TAG_LANGUAGE : for (i = 0; i < attr->num_values; i ++) - printf("\"%s\" ", attr->values[i].string.text); + if (XML) + 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 ++) - printf("\"%s\",%s ", attr->values[i].string.text, - attr->values[i].string.charset); + if (XML) + { + 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 (i) - putchar(' '); + if (XML) + { + puts("<dict>"); + for (colattr = attr->values[i].collection->attrs; + colattr; + colattr = colattr->next) + print_attr(colattr); + puts("</dict>"); + } + else + { + if (i) + putchar(' '); - print_col(attr->values[i].collection); + print_col(attr->values[i].collection); + } } break; default : - break; /* anti-compiler-warning-code */ + if (XML) + printf("<string><<%s>></string>\n", + ippTagString(attr->value_tag)); + else + fputs(ippTagString(attr->value_tag), stdout); + break; } - putchar('\n'); + if (XML) + { + if (attr->num_values > 1) + puts("</array>"); + } + else + putchar('\n'); } @@ -1176,10 +2688,10 @@ print_col(ipp_t *col) /* I - Collection attribute to print */ ipp_attribute_t *attr; /* Current attribute in collection */ - putchar('{'); + fputs("{ ", stdout); for (attr = col->attrs; attr; attr = attr->next) { - printf("%s(%s%s)=", attr->name, attr->num_values > 1 ? "1setOf " : "", + printf("%s (%s%s) = ", attr->name, attr->num_values > 1 ? "1setOf " : "", ippTagString(attr->value_tag)); switch (attr->value_tag) @@ -1253,26 +2765,1089 @@ print_col(ipp_t *col) /* I - Collection attribute to print */ /* + * '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 (XML) + { + print_xml_header(); + print_xml_trailer(0, buffer); + } + else + _cupsLangPrintf(stderr, "ipptest: %s\n", 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 (XML) + 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 + 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 */ + + + 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); + } +} + + +/* * 'usage()' - Show program usage. */ static void usage(void) { - fputs("Usage: ipptest [options] URL testfile [ ... testfileN ]\n", stderr); - fputs("Options:\n", stderr); - fputs("\n", stderr); - fputs("-c Send requests using chunking.\n", stderr); - fputs("-d name=value Define variable.\n", stderr); - fputs("-i seconds Repeat the last test file with the given interval.\n", - stderr); - fputs("-v Show all attributes in response, even on success.\n", - stderr); + _cupsLangPuts(stderr, + _("Usage: ipptest [options] URI filename.test [ ... " + "filenameN.test ]\n" + "\n" + "Options:\n" + "\n" + "-E Test with encryption.\n" + "-V version Set default IPP version.\n" + "-X Produce XML instead of plain text.\n" + "-c Send requests using chunking (default)\n" + "-d name=value Define variable.\n" + "-f filename Set default test file.\n" + "-i seconds Repeat the last test file with the given " + "interval.\n" + "-l Send requests using content-length\n" + "-v Show all attributes sent and received.\n")); exit(1); } /* - * End of "$Id: ipptest.c 7847 2008-08-19 04:22:14Z mike $". + * '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 i; /* Looping var */ + char *valptr; /* Pointer into value */ + + + /* + * 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; + if (!strncmp(valptr, "no-value,", 9)) + valptr += 9; + + 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) + return (1); + break; + case '<' : + if (attr->values[i].integer < intvalue) + return (1); + break; + case '>' : + if (attr->values[i].integer > intvalue) + return (1); + break; + } + } + } + break; + + case IPP_TAG_BOOLEAN : + for (i = 0; i < attr->num_values; i ++) + { + if (!strcmp(value, "true") == attr->values[i].boolean) + return (1); + } + break; + + case IPP_TAG_NOVALUE : + return (!strcmp(value, "no-value") || !strncmp(value, "no-value,", 9)); + + 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)) + break; + } + + regfree(&re); + + 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)) + return (1); + } + } + break; + + default : + break; + } + + return (0); +} + + +/* + * End of "$Id: ipptest.c 9000 2010-02-24 06:29:38Z mike $". */ |