tools/ipptool.c
Rama master · réplica de lectura del repositorio original.
//
// ipptool command for CUPS.
//
// Copyright © 2020-2026 by OpenPrinting.
// Copyright © 2020 by The Printer Working Group.
// Copyright © 2007-2021 by Apple Inc.
// Copyright © 1997-2007 by Easy Software Products.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
//
#include <cups/cups-private.h>
#include <cups/raster-testpage.h>
#include <regex.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef _WIN32
# include <windows.h>
# ifndef R_OK
# define R_OK 0
# endif // !R_OK
#else
# include <signal.h>
# include <termios.h>
#endif // _WIN32
#ifndef O_BINARY
# define O_BINARY 0
#endif // !O_BINARY
//
// Limits...
//
#define MAX_EXPECT 1000 // Maximum number of EXPECT directives
#define MAX_DISPLAY 200 // Maximum number of DISPLAY directives
#define MAX_MONITOR 10 // Maximum number of MONITOR-PRINTER-STATE EXPECT directives
//
// Types...
//
typedef enum ipptool_content_e // Content Validation
{
IPPTOOL_CONTENT_NONE, // No content validation
IPPTOOL_CONTENT_AVAILABLE, // Accessible resource
IPPTOOL_CONTENT_VALID, // Valid resource
IPPTOOL_CONTENT_VALID_ICON // Valid icon resource
} ipptool_content_t;
typedef enum ipptool_output_e // Output mode
{
IPPTOOL_OUTPUT_QUIET, // No output
IPPTOOL_OUTPUT_TEST, // Traditional CUPS test output
IPPTOOL_OUTPUT_PLIST, // XML plist test output
IPPTOOL_OUTPUT_IPPFILE, // ippserver attribute file output
IPPTOOL_OUTPUT_LIST, // Tabular list output
IPPTOOL_OUTPUT_CSV, // Comma-separated values output
IPPTOOL_OUTPUT_JSON // JSON output
} ipptool_output_t;
typedef enum ipptool_transfer_e // How to send request data
{
IPPTOOL_TRANSFER_AUTO, // Chunk for files, length for static
IPPTOOL_TRANSFER_CHUNKED, // Chunk always
IPPTOOL_TRANSFER_LENGTH // Length always
} ipptool_transfer_t;
typedef enum ipptool_with_e // WITH flags
{
IPPTOOL_WITH_LITERAL = 0, // Match string is a literal value
IPPTOOL_WITH_ALL = 1, // Must match all values
IPPTOOL_WITH_REGEX = 2, // Match string is a regular expression
IPPTOOL_WITH_HOSTNAME = 4, // Match string is a URI hostname
IPPTOOL_WITH_RESOURCE = 8, // Match string is a URI resource
IPPTOOL_WITH_SCHEME = 16 // Match string is a URI scheme
} ipptool_with_t;
typedef struct ipptool_expect_s // Expected attribute info
{
bool optional, // Optional attribute?
not_expect, // Don't expect attribute?
expect_all; // Expect all attributes to match/not match
char *name, // Attribute name
*of_type, // Type name
*same_count_as; // Parallel attribute name
cups_array_t *if_defined, // Only required if variable(s) defined
*if_not_defined; // Only required if variable(s) are not defined
char *with_value, // Attribute must include this value
*with_value_from, // Attribute must have one of the values in this attribute
*define_match, // Variable to define on match
*define_no_match, // Variable to define on no-match
*define_value, // Variable to define with value
*display_match; // Message to display on a match
ipptool_content_t with_content; // WITH-*-CONTENT value
cups_array_t *with_mime_types; // WITH-*-MIME-TYPES value(s)
char *save_filespec; // SAVE-*-CONTENT filespec
int repeat_limit; // Maximum number of times to repeat
bool repeat_match, // Repeat test on match
repeat_no_match, // Repeat test on no match
with_distinct; // WITH-DISTINCT-VALUES?
int with_flags; // WITH flags
int count; // Expected count if > 0
ipp_tag_t in_group; // IN-GROUP value
} ipptool_expect_t;
typedef struct ipptool_generate_s //// GENERATE-FILE parameters
{
char media[128], // Media size name
type[128]; // Raster type/color mode
int xdpi, // Horizontal resolution
ydpi; // Vertical resolution
ipp_orient_t orientation; // Orientation
char sides[128]; // Duplex mode
int num_copies, // Number of copies
num_pages; // Number of pages
char format[128]; // Document format
char sheet_back[128]; // "pwg-raster-document-sheet-back" value
} ipptool_generate_t;
typedef struct ipptool_status_s // Status info
{
ipp_status_t status; // Expected status code
cups_array_t *if_defined, // Only required if variable(s) defined
*if_not_defined; // Only required if variable(s) are not defined
char *define_match, // Variable to define on match
*define_no_match, // Variable to define on no-match
*define_value; // Variable to define with value
int repeat_limit; // Maximum number of times to repeat
bool repeat_match, // Repeat the test when it does not match
repeat_no_match; // Repeat the test when it matches
} ipptool_status_t;
typedef struct ipptool_test_s // Test Data
{
// Global Options
ipp_file_t *parent; // Parent IPP data file values
int password_tries; // Number of password attempts
http_encryption_t encryption; // Encryption for connection
int family; // Address family
ipptool_output_t output; // Output mode
bool repeat_on_busy; // Repeat tests on server-error-busy
bool stop_after_include_error;
// Stop after include errors?
double timeout; // Timeout for connection
bool validate_headers; // Validate HTTP headers in response?
int verbosity; // Show all attributes?
char *bearer_token, // HTTP Bearer token
*client_name, // TLS client certificate name
*user_agent; // HTTP User-Agent value, if any
// Test Defaults
bool def_ignore_errors; // Default IGNORE-ERRORS value
ipptool_transfer_t def_transfer; // Default TRANSFER value
int def_version; // Default IPP version
// Global State
http_t *http; // HTTP connection to printer/server
cups_file_t *outfile; // Output file
bool show_header, // Show the test header?
xml_header, // `true` if XML plist header was written
pass; // Have we passed all tests?
int test_count, // Number of tests (total)
pass_count, // Number of tests that passed
fail_count, // Number of tests that failed
skip_count; // Number of tests that were skipped
// Per-Test State
ipp_op_t op; // Operation code
cups_array_t *errors; // Errors array
bool prev_pass, // Result of previous test
skip_previous; // Skip on previous test failure?
char compression[16]; // COMPRESSION value
useconds_t delay; // Initial delay
int num_displayed; // Number of displayed attributes
char *displayed[MAX_DISPLAY];// Displayed attributes
int num_expects; // Number of expected attributes
ipptool_expect_t expects[MAX_EXPECT], // Expected attributes
*expect, // Current expected attribute
*last_expect; // Last EXPECT (for predicates)
char file[1024], // Data filename
file_id[1024]; // File identifier
bool ignore_errors; // Ignore test failures?
char name[1024]; // Test name
char pause[1024]; // PAUSE value
useconds_t repeat_interval; // Repeat interval (delay)
int request_id; // Current request ID
char resource[512]; // Resource for request
bool pass_test, // Pass this test?
skip_test; // Skip this test?
int num_statuses; // Number of valid status codes
ipptool_status_t statuses[100], // Valid status codes
*last_status; // Last STATUS (for predicates)
char test_id[1024]; // Test identifier
ipptool_transfer_t transfer; // To chunk or not to chunk
int version; // IPP version number to use
cups_thread_t monitor_thread; // Monitoring thread ID
bool monitor_done; // Set to `true` to stop monitor thread
char *monitor_uri; // MONITOR-PRINTER-STATE URI
useconds_t monitor_delay, // MONITOR-PRINTER-STATE DELAY value, if any
monitor_interval; // MONITOR-PRINTER-STATE DELAY interval
int num_monitor_expects; // Number MONITOR-PRINTER-STATE EXPECTs
ipptool_expect_t monitor_expects[MAX_MONITOR];
// MONITOR-PRINTER-STATE EXPECTs
ipptool_generate_t *generate_params; // GENERATE-FILE parameters
char buffer[1024*1024]; // Output buffer
} ipptool_test_t;
//
// Globals...
//
static bool Cancel = false; // Cancel test?
//
// Local functions...
//
static void add_stringf(cups_array_t *a, const char *s, ...) _CUPS_FORMAT(2, 3);
static ipptool_test_t *alloc_data(void);
static bool check_vars(ipp_file_t *f, cups_array_t *vars, bool need_all);
static void clear_data(ipptool_test_t *data);
static int compare_uris(const char *a, const char *b);
static http_t *connect_printer(ipptool_test_t *data);
static void copy_hex_string(char *buffer, unsigned char *data, int datalen, size_t bufsize);
static int create_file(const char *filespec, const char *resource, int idx, char *filename, size_t filenamesize);
static void *do_monitor_printer_state(ipptool_test_t *data);
static bool do_test(ipp_file_t *file, ipptool_test_t *data);
static bool do_tests(const char *testfile, ipptool_test_t *data);
static bool error_cb(ipp_file_t *f, ipptool_test_t *data, const char *error);
static bool expect_matches(ipptool_expect_t *expect, ipp_attribute_t *attr);
static void free_data(ipptool_test_t *data);
static http_status_t generate_file(http_t *http, ipptool_generate_t *params);
static char *get_filename(const char *testfile, char *dst, const char *src, size_t dstsize);
static const char *get_string(ipp_attribute_t *attr, int element, int flags, char *buffer, size_t bufsize);
static char *iso_date(const ipp_uchar_t *date);
static bool parse_generate_file(ipp_file_t *f, ipptool_test_t *data);
static bool parse_monitor_printer_state(ipp_file_t *f, ipptool_test_t *data);
static const char *password_cb(const char *prompt, http_t *http, const char *method, const char *resource, void *user_data);
static void pause_message(const char *message);
static void print_attr(cups_file_t *outfile, ipptool_output_t output, ipp_attribute_t *attr, ipp_tag_t *group);
static ipp_attribute_t *print_csv(ipptool_test_t *data, ipp_t *ipp, ipp_attribute_t *attr, int num_displayed, char **displayed, int *widths);
static void print_fatal_error(ipptool_test_t *data, const char *s, ...) _CUPS_FORMAT(2, 3);
static void print_ippserver_attr(ipptool_test_t *data, ipp_attribute_t *attr, int indent);
static void print_ippserver_string(ipptool_test_t *data, const char *s, size_t len);
static void print_json_attr(ipptool_test_t *data, ipp_attribute_t *attr, int indent);
static void print_json_string(ipptool_test_t *data, const char *s, size_t len);
static ipp_attribute_t *print_line(ipptool_test_t *data, ipp_t *ipp, ipp_attribute_t *attr, int num_displayed, char **displayed, int *widths);
static void print_xml_header(ipptool_test_t *data);
static void print_xml_string(cups_file_t *outfile, const char *element, const char *s);
static void print_xml_trailer(ipptool_test_t *data, int success, const char *message);
static void set_client_certificate(ipptool_test_t *data);
#ifndef _WIN32
static void sigterm_handler(int sig);
#endif // _WIN32
static int timeout_cb(http_t *http, void *user_data);
static bool token_cb(ipp_file_t *f, ipptool_test_t *data, const char *token);
static int usage(FILE *out);
static bool valid_image(const char *filename, int *width, int *height, int *depth);
static bool with_content(cups_array_t *errors, ipp_attribute_t *attr, ipptool_content_t content, cups_array_t *mime_types, const char *filespec);
static bool with_distinct_values(cups_array_t *errors, ipp_attribute_t *attr);
static const char *with_flags_string(int flags);
static bool with_value(ipptool_test_t *data, cups_array_t *errors, char *value, int flags, ipp_attribute_t *attr, char *matchbuf, size_t matchlen);
static bool with_value_from(cups_array_t *errors, ipp_attribute_t *fromattr, ipp_attribute_t *attr, char *matchbuf, size_t matchlen);
//
// 'main()' - Parse options and do tests.
//
int // O - Exit status
main(int argc, // I - Number of command-line args
char *argv[]) // I - Command-line arguments
{
int i; // Looping var
int status; // Status of tests...
char *opt, // Current option
name[1024], // Name/value buffer
*value, // Pointer to value
filename[1024], // Real filename
testname[1024]; // Real test filename
const char *base, // Base filename
*ext, // Extension on filename
*testfile; // Test file to use
int interval, // Test interval in microseconds
repeat; // Repeat count
ipptool_test_t *data; // Test data
_cups_globals_t *cg = _cupsGlobals();
// Global data
#ifndef _WIN32
// Catch SIGINT and SIGTERM...
signal(SIGINT, sigterm_handler);
signal(SIGTERM, sigterm_handler);
#endif // !_WIN32
// Initialize the locale and variables...
_cupsSetLocale(argv);
data = alloc_data();
// We need at least:
//
// ipptool URI testfile
interval = 0;
repeat = 0;
status = 0;
testfile = NULL;
for (i = 1; i < argc; i ++)
{
if (!strcmp(argv[i], "--bearer-token"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("%s: Missing token after '--bearer-token'."), "ipptool");
free_data(data);
return (usage(stderr));
}
data->bearer_token = argv[i];
}
else if (!strcmp(argv[i], "--client-name"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("%s: Missing client name after '--client-name'."), "ipptool");
free_data(data);
return (usage(stderr));
}
data->client_name = argv[i];
}
else if (!strcmp(argv[i], "--help"))
{
free_data(data);
return (usage(stdout));
}
else if (!strcmp(argv[i], "--ippfile") || !strcmp(argv[i], "--ippserver"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("ipptool: Missing filename for \"%s\"."), argv[i - 1]);
free_data(data);
return (usage(stderr));
}
if (data->outfile != cupsFileStdout())
return (usage(stderr));
if (interval || repeat)
{
_cupsLangPrintf(stderr, _("%s: '-i' and '-n' are incompatible with '--ippfile', '-P', and '-X'."), "ipptool");
return (usage(stderr));
}
if ((data->outfile = cupsFileOpen(argv[i], "w")) == NULL)
{
_cupsLangPrintf(stderr, _("%s: Unable to open \"%s\": %s"), "ipptool", argv[i], strerror(errno));
free_data(data);
return (1);
}
data->output = IPPTOOL_OUTPUT_IPPFILE;
}
else if (!strcmp(argv[i], "--stop-after-include-error"))
{
data->stop_after_include_error = true;
}
else if (!strcmp(argv[i], "--user-agent"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("%s: Missing user agent after '--user-agent'."), "ipptool");
free_data(data);
return (usage(stderr));
}
data->user_agent = argv[i];
}
else if (!strcmp(argv[i], "--version"))
{
puts(CUPS_SVERSION);
free_data(data);
return (0);
}
else if (!strncmp(argv[i], "--", 2))
{
_cupsLangPrintf(stderr, _("%s: Unknown option '%s'."), "ipptool", argv[i]);
return (usage(stderr));
}
else if (argv[i][0] == '-')
{
for (opt = argv[i] + 1; *opt; opt ++)
{
switch (*opt)
{
case '4' : // Connect using IPv4 only
data->family = AF_INET;
break;
#ifdef AF_INET6
case '6' : // Connect using IPv6 only
data->family = AF_INET6;
break;
#endif // AF_INET6
case 'c' : // CSV output
data->output = IPPTOOL_OUTPUT_CSV;
break;
case 'C' : // Enable HTTP chunking
data->def_transfer = IPPTOOL_TRANSFER_CHUNKED;
break;
case 'd' : // Define a variable
i ++;
if (i >= argc)
{
_cupsLangPuts(stderr, _("ipptool: Missing name=value for \"-d\"."));
return (usage(stderr));
}
cupsCopyString(name, argv[i], sizeof(name));
if ((value = strchr(name, '=')) != NULL)
*value++ = '\0';
else
value = name + strlen(name);
ippFileSetVar(data->parent, name, value);
break;
case 'E' : // Encrypt with TLS
data->encryption = HTTP_ENCRYPTION_REQUIRED;
break;
case 'f' : // Set the default test filename
i ++;
if (i >= argc)
{
_cupsLangPuts(stderr, _("ipptool: Missing filename for \"-f\"."));
return (usage(stderr));
}
if (access(argv[i], 0))
{
// Try filename.gz...
snprintf(filename, sizeof(filename), "%s.gz", argv[i]);
if (access(filename, 0) && filename[0] != '/'
#ifdef _WIN32
&& (!_cups_isalpha(filename[0]) || filename[1] != ':')
#endif // _WIN32
)
{
snprintf(filename, sizeof(filename), "%s/ipptool/%s", cg->cups_datadir, argv[i]);
if (access(filename, 0))
{
snprintf(filename, sizeof(filename), "%s/ipptool/%s.gz", cg->cups_datadir, argv[i]);
if (access(filename, 0))
cupsCopyString(filename, argv[i], sizeof(filename));
}
}
}
else
{
cupsCopyString(filename, argv[i], sizeof(filename));
}
ippFileSetVar(data->parent, "filename", filename);
if ((base = strrchr(filename, '/')) != NULL)
ippFileSetVar(data->parent, "basename", base + 1);
else
ippFileSetVar(data->parent, "basename", filename);
if ((ext = strrchr(filename, '.')) != NULL)
{
// Guess the MIME media type based on the extension...
if (!_cups_strcasecmp(ext, ".gif"))
ippFileSetVar(data->parent, "filetype", "image/gif");
else if (!_cups_strcasecmp(ext, ".htm") || !_cups_strcasecmp(ext, ".htm.gz") || !_cups_strcasecmp(ext, ".html") || !_cups_strcasecmp(ext, ".html.gz"))
ippFileSetVar(data->parent, "filetype", "text/html");
else if (!_cups_strcasecmp(ext, ".jpg") || !_cups_strcasecmp(ext, ".jpeg"))
ippFileSetVar(data->parent, "filetype", "image/jpeg");
else if (!_cups_strcasecmp(ext, ".pcl") || !_cups_strcasecmp(ext, ".pcl.gz"))
ippFileSetVar(data->parent, "filetype", "application/vnd.hp-PCL");
else if (!_cups_strcasecmp(ext, ".pdf"))
ippFileSetVar(data->parent, "filetype", "application/pdf");
else if (!_cups_strcasecmp(ext, ".png"))
ippFileSetVar(data->parent, "filetype", "image/png");
else if (!_cups_strcasecmp(ext, ".ps") || !_cups_strcasecmp(ext, ".ps.gz"))
ippFileSetVar(data->parent, "filetype", "application/postscript");
else if (!_cups_strcasecmp(ext, ".pwg") || !_cups_strcasecmp(ext, ".pwg.gz") || !_cups_strcasecmp(ext, ".ras") || !_cups_strcasecmp(ext, ".ras.gz"))
ippFileSetVar(data->parent, "filetype", "image/pwg-raster");
else if (!_cups_strcasecmp(ext, ".pxl") || !_cups_strcasecmp(ext, ".pxl.gz"))
ippFileSetVar(data->parent, "filetype", "application/vnd.hp-PCLXL");
else if (!_cups_strcasecmp(ext, ".tif") || !_cups_strcasecmp(ext, ".tiff"))
ippFileSetVar(data->parent, "filetype", "image/tiff");
else if (!_cups_strcasecmp(ext, ".txt") || !_cups_strcasecmp(ext, ".txt.gz"))
ippFileSetVar(data->parent, "filetype", "text/plain");
else if (!_cups_strcasecmp(ext, ".urf") || !_cups_strcasecmp(ext, ".urf.gz"))
ippFileSetVar(data->parent, "filetype", "image/urf");
else if (!_cups_strcasecmp(ext, ".xps"))
ippFileSetVar(data->parent, "filetype", "application/openxps");
else
ippFileSetVar(data->parent, "filetype", "application/octet-stream");
}
else
{
// Use the "auto-type" MIME media type...
ippFileSetVar(data->parent, "filetype", "application/octet-stream");
}
break;
case 'h' : // Validate response headers
data->validate_headers = true;
break;
case 'i' : // Test every N seconds
i ++;
if (i >= argc)
{
_cupsLangPuts(stderr, _("ipptool: Missing seconds for \"-i\"."));
return (usage(stderr));
}
else
{
interval = (int)(_cupsStrScand(argv[i], NULL, localeconv()) * 1000000.0);
if (interval <= 0)
{
_cupsLangPuts(stderr, _("ipptool: Invalid seconds for \"-i\"."));
return (usage(stderr));
}
}
if ((data->output == IPPTOOL_OUTPUT_PLIST || data->output == IPPTOOL_OUTPUT_IPPFILE) && interval)
{
_cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"--ippserver\", \"-P\", and \"-X\"."));
return (usage(stderr));
}
break;
case 'I' : // Ignore errors
data->def_ignore_errors = true;
break;
case 'j' : // JSON output
data->output = IPPTOOL_OUTPUT_JSON;
break;
case 'l' : // List as a table
data->output = IPPTOOL_OUTPUT_LIST;
break;
case 'L' : // Disable HTTP chunking
data->def_transfer = IPPTOOL_TRANSFER_LENGTH;
break;
case 'n' : // Repeat count
i ++;
if (i >= argc)
{
_cupsLangPuts(stderr, _("ipptool: Missing count for \"-n\"."));
return (usage(stderr));
}
else
{
repeat = atoi(argv[i]);
}
if ((data->output == IPPTOOL_OUTPUT_PLIST || data->output == IPPTOOL_OUTPUT_IPPFILE) && repeat)
{
_cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"--ippserver\", \"-P\", and \"-X\"."));
return (usage(stderr));
}
break;
case 'P' : // Output to plist file
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("%s: Missing filename for \"-P\"."), "ipptool");
return (usage(stderr));
}
if (data->outfile != cupsFileStdout())
return (usage(stderr));
if ((data->outfile = cupsFileOpen(argv[i], "w")) == NULL)
{
_cupsLangPrintf(stderr, _("%s: Unable to open \"%s\": %s"), "ipptool", argv[i], strerror(errno));
exit(1);
}
data->output = IPPTOOL_OUTPUT_PLIST;
if (interval || repeat)
{
_cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"-P\" and \"-X\"."));
return (usage(stderr));
}
break;
case 'q' : // Be quiet
data->output = IPPTOOL_OUTPUT_QUIET;
break;
case 'R' : // Repeat on server-error-busy
data->repeat_on_busy = true;
break;
case 'S' : // Encrypt with SSL
data->encryption = HTTP_ENCRYPTION_ALWAYS;
break;
case 't' : // CUPS test output
data->output = IPPTOOL_OUTPUT_TEST;
break;
case 'T' : // Set timeout
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("%s: Missing timeout for \"-T\"."), "ipptool");
return (usage(stderr));
}
data->timeout = _cupsStrScand(argv[i], NULL, localeconv());
break;
case 'v' : // Be verbose
data->verbosity ++;
break;
case 'V' : // Set IPP version
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("%s: Missing version for \"-V\"."), "ipptool");
return (usage(stderr));
}
if (!strcmp(argv[i], "1.0"))
{
data->def_version = 10;
}
else if (!strcmp(argv[i], "1.1"))
{
data->def_version = 11;
}
else if (!strcmp(argv[i], "2.0"))
{
data->def_version = 20;
}
else if (!strcmp(argv[i], "2.1"))
{
data->def_version = 21;
}
else if (!strcmp(argv[i], "2.2"))
{
data->def_version = 22;
}
else
{
_cupsLangPrintf(stderr, _("%s: Bad version %s for \"-V\"."), "ipptool", argv[i]);
return (usage(stderr));
}
break;
case 'X' : // Produce XML output
data->output = IPPTOOL_OUTPUT_PLIST;
if (interval || repeat)
{
_cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"-P\" and \"-X\"."));
return (usage(stderr));
}
break;
default :
_cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."), "ipptool", *opt);
free_data(data);
return (usage(stderr));
}
}
}
else if (!strncmp(argv[i], "ipp://", 6) || !strncmp(argv[i], "http://", 7) || !strncmp(argv[i], "ipps://", 7) || !strncmp(argv[i], "https://", 8))
{
// Set URI...
if (ippFileGetVar(data->parent, "uri"))
{
_cupsLangPuts(stderr, _("ipptool: May only specify a single URI."));
free_data(data);
return (usage(stderr));
}
if (!strncmp(argv[i], "ipps://", 7) || !strncmp(argv[i], "https://", 8))
data->encryption = HTTP_ENCRYPTION_ALWAYS;
if (!ippFileSetVar(data->parent, "uri", argv[i]))
{
_cupsLangPrintf(stderr, _("ipptool: Bad URI \"%s\"."), argv[i]);
free_data(data);
return (1);
}
if (ippFileGetVar(data->parent, "uriuser") && ippFileGetVar(data->parent, "uripassword"))
cupsSetPasswordCB2(password_cb, data->parent);
}
else
{
// Run test...
if (!ippFileGetVar(data->parent, "uri"))
{
_cupsLangPuts(stderr, _("ipptool: URI required before test file."));
_cupsLangPuts(stderr, argv[i]);
free_data(data);
return (usage(stderr));
}
if (access(argv[i], 0) && argv[i][0] != '/'
#ifdef _WIN32
&& (!_cups_isalpha(argv[i][0]) || argv[i][1] != ':')
#endif // _WIN32
)
{
snprintf(testname, sizeof(testname), "%s/ipptool/%s", cg->cups_datadir, argv[i]);
if (access(testname, 0))
testfile = argv[i];
else
testfile = testname;
}
else
{
testfile = argv[i];
}
if (access(testfile, 0))
{
_cupsLangPrintf(stderr, _("%s: Unable to open \"%s\": %s"), "ipptool", testfile, strerror(errno));
status = 1;
}
else if (!do_tests(testfile, data))
{
status = 1;
}
}
}
if (!ippFileGetVar(data->parent, "uri") || !testfile)
{
free_data(data);
return (usage(stderr));
}
// Loop if the interval is set...
if (data->output == IPPTOOL_OUTPUT_PLIST)
{
print_xml_trailer(data, !status, NULL);
}
else if (interval > 0 && repeat > 0)
{
while (repeat > 1)
{
usleep((useconds_t)interval);
do_tests(testfile, data);
repeat --;
}
}
else if (interval > 0)
{
for (;;)
{
usleep((useconds_t)interval);
do_tests(testfile, data);
}
}
if ((data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile)) && data->test_count > 1)
{
// Show a summary report if there were multiple tests...
cupsFilePrintf(cupsFileStdout(), "\nSummary: %d tests, %d passed, %d failed, %d skipped\nScore: %d%%\n", data->test_count, data->pass_count, data->fail_count, data->skip_count, 100 * (data->pass_count + data->skip_count) / data->test_count);
}
cupsFileClose(data->outfile);
free_data(data);
// Exit...
return (status);
}
//
// 'add_stringf()' - Add a formatted string to an array.
//
static void
add_stringf(cups_array_t *a, // I - Array
const char *s, // I - Printf-style format string
...) // I - Additional args as needed
{
char buffer[10240]; // Format buffer
va_list ap; // Argument pointer
// Don't bother is the array is NULL...
if (!a)
return;
// Format the message...
va_start(ap, s);
vsnprintf(buffer, sizeof(buffer), s, ap);
va_end(ap);
// Add it to the array...
cupsArrayAdd(a, buffer);
}
//
// 'alloc_data()' - Initialize and allocate test data.
//
static ipptool_test_t * // O - Test data
alloc_data(void)
{
ipptool_test_t *data; // Test data
if ((data = calloc(1, sizeof(ipptool_test_t))) == NULL)
{
_cupsLangPrintf(stderr, _("ipptool: Unable to allocate memory: %s"), strerror(errno));
exit(1);
}
data->parent = ippFileNew(/*parent*/NULL, /*attr_cb*/NULL, (ipp_ferror_cb_t)error_cb, data);
data->output = IPPTOOL_OUTPUT_LIST;
data->outfile = cupsFileStdout();
data->family = AF_UNSPEC;
data->def_transfer = IPPTOOL_TRANSFER_AUTO;
data->def_version = 20;
data->errors = cupsArrayNew3(NULL, NULL, NULL, 0, _cupsArrayStrdup, _cupsArrayFree);
data->pass = true;
data->prev_pass = true;
data->request_id = (cupsGetRand() % 1000) * 137 + 1;
data->show_header = true;
data->monitor_thread = CUPS_THREAD_INVALID;
ippFileSetVar(data->parent, "date-start", iso_date(ippTimeToDate(time(NULL))));
return (data);
}
//
// 'check_vars()' - Check for the existence of the listed variables.
//
static bool // O - `true` if vars are defined, `false` otherwise
check_vars(ipp_file_t *f, // I - IPP file
cups_array_t *vars, // I - Variables to look for
bool need_all) // I - Do we need all of them to exist?
{
bool ret = false; // Return value
const char *var; // Current variable
if (!vars)
return (true);
for (var = (const char *)cupsArrayGetFirst(vars); var; var = (const char *)cupsArrayGetNext(vars))
{
if (ippFileGetVar(f, var))
{
if (getenv("IPPTOOL_DEBUG"))
fprintf(stderr, "ipptool: %s is present\n", var);
ret = true;
}
else if (need_all)
return (false);
}
return (ret);
}
//
// 'clear_data()' - Clear per-test data...
//
static void
clear_data(ipptool_test_t *data) // I - Test data
{
int i; // Looping var
ipptool_expect_t *expect; // Current EXPECT
cupsArrayClear(data->errors);
for (i = 0; i < data->num_displayed; i ++)
free(data->displayed[i]);
data->num_displayed = 0;
for (i = data->num_expects, expect = data->expects; i > 0; i --, expect ++)
{
free(expect->name);
free(expect->of_type);
free(expect->same_count_as);
cupsArrayDelete(expect->if_defined);
cupsArrayDelete(expect->if_not_defined);
free(expect->with_value);
free(expect->define_match);
free(expect->define_no_match);
free(expect->define_value);
free(expect->display_match);
cupsArrayDelete(expect->with_mime_types);
free(expect->save_filespec);
}
data->num_expects = 0;
for (i = 0; i < data->num_statuses; i ++)
{
cupsArrayDelete(data->statuses[i].if_defined);
cupsArrayDelete(data->statuses[i].if_not_defined);
free(data->statuses[i].define_match);
free(data->statuses[i].define_no_match);
free(data->statuses[i].define_value);
}
data->num_statuses = 0;
free(data->monitor_uri);
data->monitor_uri = NULL;
for (i = data->num_monitor_expects, expect = data->monitor_expects; i > 0; i --, expect ++)
{
free(expect->name);
free(expect->of_type);
free(expect->same_count_as);
cupsArrayDelete(expect->if_defined);
cupsArrayDelete(expect->if_not_defined);
free(expect->with_value);
free(expect->define_match);
free(expect->define_no_match);
free(expect->define_value);
free(expect->display_match);
}
data->num_monitor_expects = 0;
free(data->generate_params);
data->generate_params = NULL;
}
//
// 'compare_uris()' - Compare two URIs...
//
static int // O - Result of comparison
compare_uris(const char *a, // I - First URI
const char *b) // I - Second URI
{
char ascheme[32], // Components of first URI
auserpass[256],
ahost[256],
aresource[256];
int aport;
char bscheme[32], // Components of second URI
buserpass[256],
bhost[256],
bresource[256];
int bport;
char *ptr; // Pointer into string
int result; // Result of comparison
// Separate the URIs into their components...
if (httpSeparateURI(HTTP_URI_CODING_ALL, a, ascheme, sizeof(ascheme), auserpass, sizeof(auserpass), ahost, sizeof(ahost), &aport, aresource, sizeof(aresource)) < HTTP_URI_STATUS_OK)
return (-1);
if (httpSeparateURI(HTTP_URI_CODING_ALL, b, bscheme, sizeof(bscheme), buserpass, sizeof(buserpass), bhost, sizeof(bhost), &bport, bresource, sizeof(bresource)) < HTTP_URI_STATUS_OK)
return (-1);
// Strip trailing dots from the host components, if present...
if ((ptr = ahost + strlen(ahost) - 1) > ahost && *ptr == '.')
*ptr = '\0';
if ((ptr = bhost + strlen(bhost) - 1) > bhost && *ptr == '.')
*ptr = '\0';
// Compare each component...
if ((result = _cups_strcasecmp(ascheme, bscheme)) != 0)
return (result);
if ((result = strcmp(auserpass, buserpass)) != 0)
return (result);
if ((result = _cups_strcasecmp(ahost, bhost)) != 0)
return (result);
if (aport != bport)
return (aport - bport);
if (!_cups_strcasecmp(ascheme, "mailto") || !_cups_strcasecmp(ascheme, "urn"))
return (_cups_strcasecmp(aresource, bresource));
else
return (strcmp(aresource, bresource));
}
//
// 'connect_printer()' - Connect to the printer.
//
static http_t * // O - HTTP connection or `NULL` on error
connect_printer(ipptool_test_t *data) // I - Test data
{
const char *scheme = ippFileGetVar(data->parent, "scheme"),
*hostname = ippFileGetVar(data->parent, "hostname"),
*port = ippFileGetVar(data->parent, "port");
// URI fields
http_encryption_t encryption; // Encryption mode
http_t *http; // HTTP connection
if (!scheme || !hostname || !port)
{
// This should never happen, but just in case...
print_fatal_error(data, "Missing printer/system URI.");
return (NULL);
}
if (data->client_name)
set_client_certificate(data);
if (!_cups_strcasecmp(scheme, "https") || !_cups_strcasecmp(scheme, "ipps") || atoi(port) == 443)
encryption = HTTP_ENCRYPTION_ALWAYS;
else
encryption = data->encryption;
if ((http = httpConnect2(hostname, atoi(port), /*addrlist*/NULL, data->family, encryption, /*blocking*/1, /*msec*/30000, /*cancel*/NULL)) == NULL)
{
print_fatal_error(data, "Unable to connect to '%s' on port %s: %s", hostname, port, cupsGetErrorString());
return (NULL);
}
if (data->bearer_token)
httpSetAuthString(http, "Bearer", data->bearer_token);
httpSetDefaultField(http, HTTP_FIELD_ACCEPT_ENCODING, "deflate, gzip, identity");
if (data->user_agent)
httpSetDefaultField(http, HTTP_FIELD_USER_AGENT, data->user_agent);
if (data->timeout > 0.0)
httpSetTimeout(http, data->timeout, timeout_cb, NULL);
return (http);
}
//
// 'copy_hex_string()' - Copy an octetString to a C string and encode as hex if
// needed.
//
static void
copy_hex_string(char *buffer, // I - String buffer
unsigned char *data, // I - octetString data
int datalen, // I - octetString length
size_t bufsize) // I - Size of string buffer
{
char *bufptr, // Pointer into string buffer
*bufend = buffer + bufsize - 2;
// End of string buffer
unsigned char *dataptr, // Pointer into octetString data
*dataend = data + datalen;
// End of octetString data
static const char *hexdigits = "0123456789ABCDEF";
// Hex digits
// First see if there are any non-ASCII bytes in the octetString...
for (dataptr = data; dataptr < dataend; dataptr ++)
{
if (_cups_iscntrl(*dataptr))
break;
}
if (dataptr < dataend)
{
// Yes, encode as hex...
*buffer = '<';
for (bufptr = buffer + 1, dataptr = data; bufptr < bufend && dataptr < dataend; dataptr ++)
{
*bufptr++ = hexdigits[*dataptr >> 4];
*bufptr++ = hexdigits[*dataptr & 15];
}
if (bufptr < bufend)
*bufptr++ = '>';
*bufptr = '\0';
}
else
{
// No, copy as a string...
if ((size_t)datalen >= bufsize)
datalen = (int)bufsize - 1;
memcpy(buffer, data, (size_t)datalen);
buffer[datalen] = '\0';
}
}
//
// 'create_file()' - Create a file for content checks.
//
static int // O - File descriptor or -1 on error
create_file(const char *filespec, // I - Filespec string or NULL
const char *resource, // I - Resource name
int idx, // I - Value index
char *filename, // I - Filename buffer
size_t filenamesize) // I - Filename buffer size
{
char *ptr, // Pointer into filename
*end, // End of filename buffer
base_resource[256], // Base name for resource
*base_ext; // Extension for resource
// If there is no filespec, just create a temporary file...
if (!filespec)
return (cupsCreateTempFd(NULL, NULL, filename, filenamesize));
// Convert resource path to base name...
if ((ptr = strrchr(resource, '/')) != NULL)
cupsCopyString(base_resource, ptr + 1, sizeof(base_resource));
else
cupsCopyString(base_resource, resource, sizeof(base_resource));
if ((base_ext = strrchr(base_resource, '.')) != NULL)
*base_ext++ = '\0';
else
base_ext = base_resource + strlen(base_resource);
// Format the filename...
for (ptr = filename, end = filename + filenamesize - 1; *filespec && ptr < end;)
{
if (!strncmp(filespec, "%basename%", 10))
{
cupsCopyString(ptr, base_resource, (size_t)(end - ptr + 1));
ptr += strlen(ptr);
filespec += 10;
}
else if (!strncmp(filespec, "%ext%", 5))
{
cupsCopyString(ptr, base_ext, (size_t)(end - ptr + 1));
ptr += strlen(ptr);
filespec += 5;
}
else if (!strncmp(filespec, "%index%", 7))
{
snprintf(ptr, (size_t)(end - ptr + 1), "%u", (unsigned)idx);
ptr += strlen(ptr);
filespec += 7;
}
else if (*filespec == '%')
{
filespec ++;
if (*filespec == '%')
*ptr++ = '%';
while (*filespec != '%')
filespec ++;
if (*filespec)
filespec ++;
}
else
{
// Copy literal character...
*ptr++ = *filespec++;
}
}
*ptr = '\0';
// Try creating the file...
return (open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666));
}
//
// 'do_monitor_printer_state()' - Do the MONITOR-PRINTER-STATE tests in the background.
//
static void * // O - Thread exit status
do_monitor_printer_state(
ipptool_test_t *data) // I - Test data
{
int i, j; // Looping vars
char scheme[32], // URI scheme
userpass[32], // URI username:password
host[256], // URI hostname/IP address
resource[256]; // URI resource path
int port; // URI port number
http_encryption_t encryption; // Encryption to use
http_t *http; // Connection to printer
ipp_t *request, // IPP request
*response = NULL; // IPP response
http_status_t status; // Request status
ipp_attribute_t *found; // Found attribute
ipptool_expect_t *expect; // Current EXPECT test
char buffer[131072]; // Copy buffer
int num_pattrs; // Number of printer attributes
const char *pattrs[100]; // Printer attributes we care about
if (getenv("IPPTOOL_DEBUG"))
fprintf(stderr, "ipptool: Monitoring printer '%s' in the background.\n", data->monitor_uri);
// Connect to the printer...
if (httpSeparateURI(HTTP_URI_CODING_ALL, data->monitor_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
{
print_fatal_error(data, "Bad printer URI \"%s\".", data->monitor_uri);
return (NULL);
}
if (!_cups_strcasecmp(scheme, "https") || !_cups_strcasecmp(scheme, "ipps") || port == 443)
encryption = HTTP_ENCRYPTION_ALWAYS;
else
encryption = data->encryption;
if ((http = httpConnect2(host, port, /*addrlist*/NULL, data->family, encryption, /*blocking*/1, /*msec*/30000, /*cancel*/NULL)) == NULL)
{
print_fatal_error(data, "Unable to connect to \"%s\" on port %d - %s", host, port, cupsGetErrorString());
return (0);
}
httpSetDefaultField(http, HTTP_FIELD_ACCEPT_ENCODING, "deflate, gzip, identity");
if (data->timeout > 0.0)
httpSetTimeout(http, data->timeout, timeout_cb, NULL);
// Wait for the initial delay as needed...
if (data->monitor_delay)
usleep(data->monitor_delay);
// Create a query request that we'll reuse...
request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
ippSetRequestId(request, data->request_id * 100 - 1);
ippSetVersion(request, data->version / 10, data->version % 10);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, data->monitor_uri);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsGetUser());
for (i = data->num_monitor_expects, expect = data->monitor_expects, num_pattrs = 0; i > 0; i --, expect ++)
{
// Add EXPECT attribute names...
for (j = 0; j < num_pattrs; j ++)
{
if (!strcmp(expect->name, pattrs[j]))
break;
}
if (j >= num_pattrs && num_pattrs < (int)(sizeof(pattrs) / sizeof(pattrs[0])))
pattrs[num_pattrs ++] = expect->name;
}
if (num_pattrs > 0)
ippAddStrings(request, IPP_TAG_OPERATION, IPP_CONST_TAG(IPP_TAG_KEYWORD), "requested-attributes", num_pattrs, NULL, pattrs);
// Loop until we need to stop...
while (!data->monitor_done && !Cancel)
{
// Poll the printer state...
ippSetRequestId(request, ippGetRequestId(request) + 1);
if ((status = cupsSendRequest(http, request, resource, ippGetLength(request))) != HTTP_STATUS_ERROR)
{
response = cupsGetResponse(http, resource);
status = httpGetStatus(http);
}
if (!data->monitor_done && !Cancel && status == HTTP_STATUS_ERROR && httpGetError(data->http) != EINVAL &&
#ifdef _WIN32
httpGetError(data->http) != WSAETIMEDOUT)
#else
httpGetError(data->http) != ETIMEDOUT)
#endif // _WIN32
{
if (!httpConnectAgain(http, /*msec*/30000, /*cancel*/NULL))
break;
}
else if (status == HTTP_STATUS_ERROR || status == HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED)
{
break;
}
else if (status != HTTP_STATUS_OK)
{
httpFlush(http);
if (status == HTTP_STATUS_UNAUTHORIZED)
continue;
break;
}
for (i = data->num_monitor_expects, expect = data->monitor_expects; i > 0; i --, expect ++)
{
if (expect->if_defined && !check_vars(data->parent, expect->if_defined, /*need_all*/true))
continue;
if (expect->if_not_defined && check_vars(data->parent, expect->if_not_defined, /*need_all*/false))
continue;
found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
if ((found && expect->not_expect) ||
(!found && !(expect->not_expect || expect->optional)) ||
(found && !expect_matches(expect, found)) ||
(expect->in_group && ippGetGroupTag(found) != expect->in_group) ||
(expect->with_distinct && !with_distinct_values(NULL, found)))
{
if (expect->define_no_match)
{
ippFileSetVar(data->parent, expect->define_no_match, "1");
data->monitor_done = true;
}
break;
}
if (found)
ippAttributeString(found, buffer, sizeof(buffer));
if (found && !with_value(data, NULL, expect->with_value, expect->with_flags, found, buffer, sizeof(buffer)))
{
if (expect->define_no_match)
{
ippFileSetVar(data->parent, expect->define_no_match, "1");
data->monitor_done = true;
}
break;
}
if (found && expect->count > 0 && ippGetCount(found) != expect->count)
{
if (expect->define_no_match)
{
ippFileSetVar(data->parent, expect->define_no_match, "1");
data->monitor_done = true;
}
break;
}
if (found && expect->display_match && (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout())))
cupsFilePrintf(cupsFileStdout(), "CONT]\n\n%s\n\n %-68.68s [", expect->display_match, data->name);
if (found && expect->define_match)
{
ippFileSetVar(data->parent, expect->define_match, "1");
data->monitor_done = true;
}
if (found && expect->define_value)
{
if (!expect->with_value)
{
int last = ippGetCount(found) - 1;
// Last element in attribute
switch (ippGetValueTag(found))
{
case IPP_TAG_ENUM :
case IPP_TAG_INTEGER :
snprintf(buffer, sizeof(buffer), "%d", ippGetInteger(found, last));
break;
case IPP_TAG_BOOLEAN :
if (ippGetBoolean(found, last))
cupsCopyString(buffer, "true", sizeof(buffer));
else
cupsCopyString(buffer, "false", sizeof(buffer));
break;
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 :
cupsCopyString(buffer, ippGetString(found, last, NULL), sizeof(buffer));
break;
default :
ippAttributeString(found, buffer, sizeof(buffer));
break;
}
}
ippFileSetVar(data->parent, expect->define_value, buffer);
data->monitor_done = true;
}
}
if (i == 0)
data->monitor_done = true; // All tests passed
ippDelete(response);
response = NULL;
// Sleep between requests...
if (data->monitor_done || Cancel)
break;
usleep(data->monitor_interval);
}
// Close the connection to the printer and return...
httpClose(http);
ippDelete(request);
ippDelete(response);
return (NULL);
}
//
// 'do_test()' - Do a single test from the test file.
//
static bool // O - `true` on success, `false` on failure
do_test(ipp_file_t *f, // I - IPP data file
ipptool_test_t *data) // I - Test data
{
int i; // Looping var
bool status_ok; // Did we get a matching status?
int repeat_count = 0; // Repeat count
bool repeat_test; // Repeat the test?
ipptool_expect_t *expect; // Current expected attribute
ipp_t *request, // IPP request
*response; // IPP response
size_t length; // Length of IPP request
http_status_t status; // HTTP status
cups_array_t *a; // Duplicate attribute array
ipp_tag_t group; // Current group
ipp_attribute_t *attrptr, // Attribute pointer
*found; // Found attribute
char temp[1024]; // Temporary string
cups_file_t *reqfile; // File to send
ssize_t bytes; // Bytes read/written
int widths[200]; // Width of columns
const char *error; // Current error
if (Cancel)
return (false);
if (getenv("IPPTOOL_DEBUG"))
fprintf(stderr, "ipptool: Doing test '%s', num_expects=%u, num_statuses=%u.\n", data->name, (unsigned)data->num_expects, (unsigned)data->num_statuses);
// Show any PAUSE message, as needed...
if (data->pause[0])
{
if (!data->skip_test && !data->pass_test)
pause_message(data->pause);
data->pause[0] = '\0';
}
// Start the background thread as needed...
if (data->monitor_uri)
{
data->monitor_done = false;
data->monitor_thread = cupsThreadCreate((cups_thread_func_t)do_monitor_printer_state, data);
}
// Take over control of the attributes in the request...
request = ippFileGetAttributes(f);
ippFileSetAttributes(f, NULL);
// Submit the IPP request...
data->test_count ++;
ippSetOperation(request, data->op);
ippSetVersion(request, data->version / 10, data->version % 10);
ippSetRequestId(request, data->request_id);
if (data->output == IPPTOOL_OUTPUT_PLIST)
{
cupsFilePuts(data->outfile, "<dict>\n");
cupsFilePuts(data->outfile, "<key>Name</key>\n");
print_xml_string(data->outfile, "string", data->name);
if (data->file_id[0])
{
cupsFilePuts(data->outfile, "<key>FileId</key>\n");
print_xml_string(data->outfile, "string", data->file_id);
}
if (data->test_id[0])
{
cupsFilePuts(data->outfile, "<key>TestId</key>\n");
print_xml_string(data->outfile, "string", data->test_id);
}
cupsFilePuts(data->outfile, "<key>Version</key>\n");
cupsFilePrintf(data->outfile, "<string>%d.%d</string>\n", data->version / 10, data->version % 10);
cupsFilePuts(data->outfile, "<key>Operation</key>\n");
print_xml_string(data->outfile, "string", ippOpString(ippGetOperation(request)));
cupsFilePuts(data->outfile, "<key>RequestId</key>\n");
cupsFilePrintf(data->outfile, "<integer>%d</integer>\n", data->request_id);
cupsFilePuts(data->outfile, "<key>RequestAttributes</key>\n");
cupsFilePuts(data->outfile, "<array>\n");
if (ippGetFirstAttribute(request))
{
cupsFilePuts(data->outfile, "<dict>\n");
for (attrptr = ippGetFirstAttribute(request), group = ippGetGroupTag(attrptr); attrptr; attrptr = ippGetNextAttribute(request))
print_attr(data->outfile, data->output, attrptr, &group);
cupsFilePuts(data->outfile, "</dict>\n");
}
cupsFilePuts(data->outfile, "</array>\n");
}
if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
{
if (data->verbosity)
{
cupsFilePrintf(cupsFileStdout(), " %s:\n", ippOpString(ippGetOperation(request)));
for (attrptr = ippGetFirstAttribute(request); attrptr; attrptr = ippGetNextAttribute(request))
print_attr(cupsFileStdout(), IPPTOOL_OUTPUT_TEST, attrptr, NULL);
}
cupsFilePrintf(cupsFileStdout(), " %-68.68s [", data->name);
}
if ((data->skip_previous && !data->prev_pass) || data->skip_test || data->pass_test)
{
if (!data->pass_test)
data->skip_count ++;
ippDelete(request);
request = NULL;
response = NULL;
if (data->output == IPPTOOL_OUTPUT_PLIST)
{
cupsFilePuts(data->outfile, "<key>Successful</key>\n");
cupsFilePuts(data->outfile, "<true />\n");
cupsFilePuts(data->outfile, "<key>Skipped</key>\n");
if (data->pass_test)
cupsFilePuts(data->outfile, "<false />\n");
else
cupsFilePuts(data->outfile, "<true />\n");
cupsFilePuts(data->outfile, "<key>StatusCode</key>\n");
if (data->pass_test)
print_xml_string(data->outfile, "string", "pass");
else
print_xml_string(data->outfile, "string", "skip");
cupsFilePuts(data->outfile, "<key>ResponseAttributes</key>\n");
cupsFilePuts(data->outfile, "<dict />\n");
}
if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
{
if (data->pass_test)
cupsFilePuts(cupsFileStdout(), "PASS]\n");
else
cupsFilePuts(cupsFileStdout(), "SKIP]\n");
}
goto skip_error;
}
data->password_tries = 0;
do
{
if (data->delay > 0)
usleep(data->delay);
if (getenv("IPPTOOL_DEBUG"))
fprintf(stderr, "ipptool: Sending %s request to '%s'.\n", ippOpString(ippGetOperation(request)), data->resource);
data->delay = data->repeat_interval;
repeat_count ++;
status = HTTP_STATUS_OK;
if (data->transfer == IPPTOOL_TRANSFER_CHUNKED || (data->transfer == IPPTOOL_TRANSFER_AUTO && (data->file[0] || data->generate_params)))
{
// Send request using chunking - a 0 length means "chunk".
length = 0;
}
else
{
// Send request using content length...
length = ippGetLength(request);
if (data->file[0] && (reqfile = cupsFileOpen(data->file, "r")) != NULL)
{
// Read the file to get the uncompressed file size...
while ((bytes = cupsFileRead(reqfile, data->buffer, sizeof(data->buffer))) > 0)
length += (size_t)bytes;
cupsFileClose(reqfile);
}
}
// Send the request...
data->prev_pass = true;
repeat_test = false;
response = NULL;
if (status != HTTP_STATUS_ERROR)
{
while (!response && !Cancel && data->prev_pass)
{
ippSetRequestId(request, ++ data->request_id);
status = cupsSendRequest(data->http, request, data->resource, length);
if (data->compression[0])
httpSetField(data->http, HTTP_FIELD_CONTENT_ENCODING, data->compression);
if (!Cancel && status == HTTP_STATUS_CONTINUE && ippGetState(request) == IPP_STATE_DATA && data->file[0])
{
// Send attached file...
if ((reqfile = cupsFileOpen(data->file, "r")) != NULL)
{
while (!Cancel && (bytes = cupsFileRead(reqfile, data->buffer, sizeof(data->buffer))) > 0)
{
if ((status = cupsWriteRequestData(data->http, data->buffer, (size_t)bytes)) != HTTP_STATUS_CONTINUE)
break;
}
cupsFileClose(reqfile);
}
else
{
snprintf(data->buffer, sizeof(data->buffer), "%s: %s", data->file, strerror(errno));
_cupsSetError(IPP_STATUS_ERROR_INTERNAL, data->buffer, 0);
status = HTTP_STATUS_ERROR;
}
}
else if (!Cancel && status == HTTP_STATUS_CONTINUE && ippGetState(request) == IPP_STATE_DATA && data->generate_params)
{
// Generate attached file...
status = generate_file(data->http, data->generate_params);
}
// Get the server's response...
if (!Cancel && status != HTTP_STATUS_ERROR)
{
response = cupsGetResponse(data->http, data->resource);
status = httpGetStatus(data->http);
}
if (!Cancel && status == HTTP_STATUS_ERROR && httpGetError(data->http) != EINVAL &&
#ifdef _WIN32
httpGetError(data->http) != WSAETIMEDOUT)
#else
httpGetError(data->http) != ETIMEDOUT)
#endif // _WIN32
{
if (!httpConnectAgain(data->http, /*msec*/30000, /*cancel*/NULL))
data->prev_pass = false;
}
else if (status == HTTP_STATUS_ERROR || status == HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED)
{
data->prev_pass = false;
break;
}
else if (status != HTTP_STATUS_OK)
{
httpFlush(data->http);
if (status == HTTP_STATUS_UNAUTHORIZED)
continue;
break;
}
}
}
if (!Cancel && status == HTTP_STATUS_ERROR && httpGetError(data->http) != EINVAL &&
#ifdef _WIN32
httpGetError(data->http) != WSAETIMEDOUT)
#else
httpGetError(data->http) != ETIMEDOUT)
#endif // _WIN32
{
if (!httpConnectAgain(data->http, /*msec*/30000, /*cancel*/NULL))
data->prev_pass = false;
}
else if (status == HTTP_STATUS_ERROR)
{
if (!Cancel)
httpConnectAgain(data->http, /*msec*/30000, /*cancel*/NULL);
data->prev_pass = false;
}
else if (status != HTTP_STATUS_OK)
{
httpFlush(data->http);
data->prev_pass = false;
}
// Check results of request...
cupsArrayClear(data->errors);
if (httpGetVersion(data->http) != HTTP_VERSION_1_1)
{
int version = (int)httpGetVersion(data->http);
add_stringf(data->errors, "Bad HTTP version (%d.%d)", version / 100, version % 100);
}
if (data->validate_headers)
{
const char *header; // HTTP header value
if ((header = httpGetField(data->http, HTTP_FIELD_CONTENT_TYPE)) == NULL || _cups_strcasecmp(header, "application/ipp"))
add_stringf(data->errors, "Bad HTTP Content-Type in response (%s)", header && *header ? header : "<missing>");
if ((header = httpGetField(data->http, HTTP_FIELD_DATE)) != NULL && *header && httpGetDateTime(header) == 0)
add_stringf(data->errors, "Bad HTTP Date in response (%s)", header);
}
if (!response)
{
// No response, log error...
add_stringf(data->errors, "IPP request failed with status %s (%s)", ippErrorString(cupsGetError()), cupsGetErrorString());
}
else
{
// Collect common attribute values...
if ((attrptr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) != NULL)
{
snprintf(temp, sizeof(temp), "%d", ippGetInteger(attrptr, 0));
ippFileSetVar(data->parent, "job-id", temp);
}
if ((attrptr = ippFindAttribute(response, "job-uri", IPP_TAG_URI)) != NULL)
ippFileSetVar(data->parent, "job-uri", ippGetString(attrptr, 0, NULL));
if ((attrptr = ippFindAttribute(response, "notify-subscription-id", IPP_TAG_INTEGER)) != NULL)
{
snprintf(temp, sizeof(temp), "%d", ippGetInteger(attrptr, 0));
ippFileSetVar(data->parent, "notify-subscription-id", temp);
}
// Check response, validating groups and attributes and logging errors as needed...
if (ippGetState(response) != IPP_STATE_DATA)
add_stringf(data->errors, "Missing end-of-attributes-tag in response (RFC 2910 section 3.5.1)");
if (data->version)
{
int major, minor; // IPP version
major = ippGetVersion(response, &minor);
if (major != (data->version / 10) || minor != (data->version % 10))
add_stringf(data->errors, "Bad version %d.%d in response - expected %d.%d (RFC 8011 section 4.1.8).", major, minor, data->version / 10, data->version % 10);
}
if (ippGetRequestId(response) != data->request_id)
add_stringf(data->errors, "Bad request ID %d in response - expected %d (RFC 8011 section 4.1.1)", ippGetRequestId(response), data->request_id);
attrptr = ippGetFirstAttribute(response);
if (!attrptr)
{
add_stringf(data->errors, "Missing first attribute \"attributes-charset (charset)\" in group operation-attributes-tag (RFC 8011 section 4.1.4).");
}
else
{
if (!ippGetName(attrptr) || ippGetValueTag(attrptr) != IPP_TAG_CHARSET || ippGetGroupTag(attrptr) != IPP_TAG_OPERATION || ippGetCount(attrptr) != 1 ||strcmp(ippGetName(attrptr), "attributes-charset"))
add_stringf(data->errors, "Bad first attribute \"%s (%s%s)\" in group %s, expected \"attributes-charset (charset)\" in group operation-attributes-tag (RFC 8011 section 4.1.4).", ippGetName(attrptr) ? ippGetName(attrptr) : "(null)", ippGetCount(attrptr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attrptr)), ippTagString(ippGetGroupTag(attrptr)));
attrptr = ippGetNextAttribute(response);
if (!attrptr)
add_stringf(data->errors, "Missing second attribute \"attributes-natural-language (naturalLanguage)\" in group operation-attributes-tag (RFC 8011 section 4.1.4).");
else if (!ippGetName(attrptr) || ippGetValueTag(attrptr) != IPP_TAG_LANGUAGE || ippGetGroupTag(attrptr) != IPP_TAG_OPERATION || ippGetCount(attrptr) != 1 || strcmp(ippGetName(attrptr), "attributes-natural-language"))
add_stringf(data->errors, "Bad first attribute \"%s (%s%s)\" in group %s, expected \"attributes-natural-language (naturalLanguage)\" in group operation-attributes-tag (RFC 8011 section 4.1.4).", ippGetName(attrptr) ? ippGetName(attrptr) : "(null)", ippGetCount(attrptr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attrptr)), ippTagString(ippGetGroupTag(attrptr)));
}
if ((attrptr = ippFindAttribute(response, "status-message", IPP_TAG_ZERO)) != NULL)
{
const char *status_message = ippGetString(attrptr, 0, NULL);
// String value
if (ippGetValueTag(attrptr) != IPP_TAG_TEXT)
add_stringf(data->errors, "status-message (text(255)) has wrong value tag %s (RFC 8011 section 4.1.6.2).", ippTagString(ippGetValueTag(attrptr)));
if (ippGetGroupTag(attrptr) != IPP_TAG_OPERATION)
add_stringf(data->errors, "status-message (text(255)) has wrong group tag %s (RFC 8011 section 4.1.6.2).", ippTagString(ippGetGroupTag(attrptr)));
if (ippGetCount(attrptr) != 1)
add_stringf(data->errors, "status-message (text(255)) has %u values (RFC 8011 section 4.1.6.2).", (unsigned)ippGetCount(attrptr));
if (status_message && strlen(status_message) > 255)
add_stringf(data->errors, "status-message (text(255)) has bad length %u (RFC 8011 section 4.1.6.2).", (unsigned)strlen(status_message));
}
if ((attrptr = ippFindAttribute(response, "detailed-status-message",
IPP_TAG_ZERO)) != NULL)
{
const char *detailed_status_message = ippGetString(attrptr, 0, NULL);
// String value
if (ippGetValueTag(attrptr) != IPP_TAG_TEXT)
add_stringf(data->errors, "detailed-status-message (text(MAX)) has wrong value tag %s (RFC 8011 section 4.1.6.3).", ippTagString(ippGetValueTag(attrptr)));
if (ippGetGroupTag(attrptr) != IPP_TAG_OPERATION)
add_stringf(data->errors, "detailed-status-message (text(MAX)) has wrong group tag %s (RFC 8011 section 4.1.6.3).", ippTagString(ippGetGroupTag(attrptr)));
if (ippGetCount(attrptr) != 1)
add_stringf(data->errors, "detailed-status-message (text(MAX)) has %u values (RFC 8011 section 4.1.6.3).", (unsigned)ippGetCount(attrptr));
if (detailed_status_message && strlen(detailed_status_message) > 1023)
add_stringf(data->errors, "detailed-status-message (text(MAX)) has bad length %u (RFC 8011 section 4.1.6.3).", (unsigned)strlen(detailed_status_message));
}
a = cupsArrayNew3((cups_array_cb_t)_cupsArrayStrcmp, NULL, NULL, 0, NULL, NULL);
for (attrptr = ippGetFirstAttribute(response), group = ippGetGroupTag(attrptr);
attrptr;
attrptr = ippGetNextAttribute(response))
{
if (ippGetGroupTag(attrptr) != group)
{
bool out_of_order = false; // Are attribute groups out-of-order?
cupsArrayClear(a);
switch (ippGetGroupTag(attrptr))
{
case IPP_TAG_ZERO :
break;
case IPP_TAG_OPERATION :
out_of_order = true;
break;
case IPP_TAG_UNSUPPORTED_GROUP :
if (group != IPP_TAG_OPERATION)
out_of_order = true;
break;
case IPP_TAG_JOB :
case IPP_TAG_PRINTER :
if (group != IPP_TAG_OPERATION && group != IPP_TAG_UNSUPPORTED_GROUP)
out_of_order = true;
break;
case IPP_TAG_SUBSCRIPTION :
if (group > ippGetGroupTag(attrptr) && group != IPP_TAG_DOCUMENT)
out_of_order = true;
break;
default :
if (group > ippGetGroupTag(attrptr))
out_of_order = true;
break;
}
if (out_of_order)
add_stringf(data->errors, "Attribute groups out of order (%s < %s)", ippTagString(ippGetGroupTag(attrptr)), ippTagString(group));
if (ippGetGroupTag(attrptr) != IPP_TAG_ZERO)
group = ippGetGroupTag(attrptr);
}
if (!ippValidateAttribute(attrptr))
cupsArrayAdd(data->errors, (void *)cupsGetErrorString());
if (ippGetName(attrptr))
{
if (cupsArrayFind(a, (void *)ippGetName(attrptr)) && data->output < IPPTOOL_OUTPUT_LIST)
add_stringf(data->errors, "Duplicate \"%s\" attribute in %s group", ippGetName(attrptr), ippTagString(group));
cupsArrayAdd(a, (void *)ippGetName(attrptr));
}
}
cupsArrayDelete(a);
// Now check the test-defined expected status-code and attribute values...
if (ippGetStatusCode(response) == IPP_STATUS_ERROR_BUSY && data->repeat_on_busy)
{
// Repeat on a server-error-busy status code...
status_ok = true;
repeat_test = true;
}
else
{
for (i = 0, status_ok = false; i < data->num_statuses; i ++)
{
if (data->statuses[i].if_defined && !check_vars(data->parent, data->statuses[i].if_defined, /*need_all*/true))
continue;
if (data->statuses[i].if_not_defined && check_vars(data->parent, data->statuses[i].if_not_defined, /*need_all*/false))
continue;
if (ippGetStatusCode(response) == data->statuses[i].status)
{
status_ok = true;
if (data->statuses[i].repeat_match && repeat_count < data->statuses[i].repeat_limit)
repeat_test = true;
if (data->statuses[i].define_match)
ippFileSetVar(data->parent, data->statuses[i].define_match, "1");
}
else
{
if (data->statuses[i].repeat_no_match && repeat_count < data->statuses[i].repeat_limit)
repeat_test = true;
if (data->statuses[i].define_no_match)
{
ippFileSetVar(data->parent, data->statuses[i].define_no_match, "1");
status_ok = true;
}
}
}
}
if (!status_ok && data->num_statuses > 0)
{
for (i = 0; i < data->num_statuses; i ++)
{
if (data->statuses[i].if_defined && !check_vars(data->parent, data->statuses[i].if_defined, /*need_all*/true))
continue;
if (data->statuses[i].if_not_defined && check_vars(data->parent, data->statuses[i].if_not_defined, /*need_all*/false))
continue;
if (!data->statuses[i].repeat_match || repeat_count >= data->statuses[i].repeat_limit)
add_stringf(data->errors, "EXPECTED: STATUS %s (got %s)", ippErrorString(data->statuses[i].status), ippErrorString(cupsGetError()));
}
if ((attrptr = ippFindAttribute(response, "status-message", IPP_TAG_TEXT)) != NULL)
add_stringf(data->errors, "status-message=\"%s\"", ippGetString(attrptr, 0, NULL));
}
for (i = data->num_expects, expect = data->expects; i > 0; i --, expect ++)
{
cups_array_t *exp_errors; // Temporary list of errors
bool exp_member, // Expect for member attribute?
exp_pass; // Did this expect pass?
ipp_attribute_t *group_found; // Found parent attribute for group tests
if (expect->if_defined && !check_vars(data->parent, expect->if_defined, /*need_all*/true))
continue;
if (expect->if_not_defined && check_vars(data->parent, expect->if_not_defined, /*need_all*/false))
continue;
if ((found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL && expect->in_group && expect->in_group != ippGetGroupTag(found))
{
while ((found = ippFindNextAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL)
if (expect->in_group == ippGetGroupTag(found))
break;
}
exp_errors = cupsArrayNew3(NULL, NULL, NULL, 0, _cupsArrayStrdup, _cupsArrayFree);
exp_member = strchr(expect->name, '/') != NULL;
exp_pass = false;
do
{
group_found = found;
ippSave(response);
if (expect->in_group && strchr(expect->name, '/'))
{
char group_name[256],// Parent attribute name
*group_ptr; // Pointer into parent attribute name
cupsCopyString(group_name, expect->name, sizeof(group_name));
if ((group_ptr = strchr(group_name, '/')) != NULL)
*group_ptr = '\0';
group_found = ippFindAttribute(response, group_name, IPP_TAG_ZERO);
}
if ((found && expect->not_expect) ||
(!found && !(expect->not_expect || expect->optional)) ||
(found && !expect_matches(expect, found)) ||
(group_found && expect->in_group && ippGetGroupTag(group_found) != expect->in_group) ||
(expect->with_distinct && !with_distinct_values(NULL, found)))
{
if (expect->define_no_match)
{
ippFileSetVar(data->parent, expect->define_no_match, "1");
exp_pass = true;
}
else if (!expect->define_match && !expect->define_value)
{
if (found && expect->not_expect && !expect->with_value && !expect->with_value_from)
{
add_stringf(exp_errors, "NOT EXPECTED: %s", expect->name);
}
else if (!found && !(expect->not_expect || expect->optional))
{
add_stringf(exp_errors, "EXPECTED: %s", expect->name);
}
else if (found)
{
if (!expect_matches(expect, found))
add_stringf(exp_errors, "EXPECTED: %s OF-TYPE %s (got %s)",
expect->name, expect->of_type,
ippTagString(ippGetValueTag(found)));
if (expect->in_group && ippGetGroupTag(group_found) != expect->in_group)
add_stringf(exp_errors, "EXPECTED: %s IN-GROUP %s (got %s).",
expect->name, ippTagString(expect->in_group),
ippTagString(ippGetGroupTag(group_found)));
if (expect->with_distinct)
with_distinct_values(exp_errors, found);
}
}
if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
repeat_test = true;
ippRestore(response);
break;
}
if (found)
ippAttributeString(found, data->buffer, sizeof(data->buffer));
if (found && (expect->with_content || expect->with_mime_types || expect->save_filespec) && !with_content(exp_errors, found, expect->with_content, expect->with_mime_types, expect->save_filespec))
{
if (expect->define_no_match)
{
ippFileSetVar(data->parent, expect->define_no_match, "1");
exp_pass = true;
}
}
if (found && expect->with_value_from && !with_value_from(NULL, ippFindAttribute(response, expect->with_value_from, IPP_TAG_ZERO), found, data->buffer, sizeof(data->buffer)))
{
if (expect->define_no_match)
{
ippFileSetVar(data->parent, expect->define_no_match, "1");
exp_pass = true;
}
else if (!expect->define_match && !expect->define_value && ((!expect->repeat_match && !expect->repeat_no_match) || repeat_count >= expect->repeat_limit))
{
add_stringf(exp_errors, "EXPECTED: %s WITH-VALUES-FROM %s", expect->name, expect->with_value_from);
with_value_from(exp_errors, ippFindAttribute(response, expect->with_value_from, IPP_TAG_ZERO), found, data->buffer, sizeof(data->buffer));
}
if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
repeat_test = true;
ippRestore(response);
break;
}
else if (found && !with_value(data, NULL, expect->with_value, expect->with_flags, found, data->buffer, sizeof(data->buffer)))
{
if (expect->define_no_match)
{
ippFileSetVar(data->parent, expect->define_no_match, "1");
exp_pass = true;
}
else if (!expect->define_match && !expect->define_value &&
!expect->repeat_match && (!expect->repeat_no_match || repeat_count >= expect->repeat_limit))
{
if (expect->with_flags & IPPTOOL_WITH_REGEX)
add_stringf(exp_errors, "EXPECTED: %s %s /%s/", expect->name, with_flags_string(expect->with_flags), expect->with_value);
else
add_stringf(exp_errors, "EXPECTED: %s %s \"%s\"", expect->name, with_flags_string(expect->with_flags), expect->with_value);
with_value(data, exp_errors, expect->with_value, expect->with_flags, found, data->buffer, sizeof(data->buffer));
}
if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
repeat_test = true;
ippRestore(response);
break;
}
else if (expect->with_value)
{
exp_pass = true;
}
if (found && expect->count > 0 && ippGetCount(found) != expect->count)
{
if (expect->define_no_match)
{
ippFileSetVar(data->parent, expect->define_no_match, "1");
exp_pass = true;
}
else if (!expect->define_match && !expect->define_value)
{
add_stringf(exp_errors, "EXPECTED: %s COUNT %u (got %u)", expect->name, (unsigned)expect->count, (unsigned)ippGetCount(found));
}
if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
repeat_test = true;
ippRestore(response);
break;
}
if (found && expect->same_count_as)
{
attrptr = ippFindAttribute(response, expect->same_count_as,
IPP_TAG_ZERO);
if (!attrptr || ippGetCount(attrptr) != ippGetCount(found))
{
if (expect->define_no_match)
{
ippFileSetVar(data->parent, expect->define_no_match, "1");
exp_pass = true;
}
else if (!expect->define_match && !expect->define_value)
{
if (!attrptr)
add_stringf(exp_errors, "EXPECTED: %s (%u values) SAME-COUNT-AS %s (not returned)", expect->name, (unsigned)ippGetCount(found), expect->same_count_as);
else if (ippGetCount(attrptr) != ippGetCount(found))
add_stringf(exp_errors, "EXPECTED: %s (%u values) SAME-COUNT-AS %s (%u values)", expect->name, (unsigned)ippGetCount(found), expect->same_count_as, (unsigned)ippGetCount(attrptr));
}
if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
repeat_test = true;
ippRestore(response);
break;
}
}
if (found && expect->display_match && (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout())))
cupsFilePrintf(cupsFileStdout(), "\n%s\n\n", expect->display_match);
if (found && expect->define_match)
{
if (getenv("IPPTOOL_DEBUG"))
fprintf(stderr, "ipptool: %s=1\n", expect->define_match);
ippFileSetVar(data->parent, expect->define_match, "1");
exp_pass = true;
}
if (found && expect->define_value)
{
exp_pass = true;
if (!expect->with_value)
{
int last = ippGetCount(found) - 1;
// Last element in attribute
switch (ippGetValueTag(found))
{
case IPP_TAG_ENUM :
case IPP_TAG_INTEGER :
snprintf(data->buffer, sizeof(data->buffer), "%d", ippGetInteger(found, last));
break;
case IPP_TAG_BOOLEAN :
if (ippGetBoolean(found, last))
cupsCopyString(data->buffer, "true", sizeof(data->buffer));
else
cupsCopyString(data->buffer, "false", sizeof(data->buffer));
break;
case IPP_TAG_RESOLUTION :
{
int xres, // Horizontal resolution
yres; // Vertical resolution
ipp_res_t units; // Resolution units
xres = ippGetResolution(found, last, &yres, &units);
if (xres == yres)
snprintf(data->buffer, sizeof(data->buffer), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
else
snprintf(data->buffer, sizeof(data->buffer), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
}
break;
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 :
cupsCopyString(data->buffer, ippGetString(found, last, NULL), sizeof(data->buffer));
break;
default :
ippAttributeString(found, data->buffer, sizeof(data->buffer));
break;
}
}
if (getenv("IPPTOOL_DEBUG"))
fprintf(stderr, "ipptool: %s=%s\n", expect->define_value, data->buffer);
ippFileSetVar(data->parent, expect->define_value, data->buffer);
}
if (found && expect->repeat_match && repeat_count < expect->repeat_limit)
repeat_test = 1;
ippRestore(response);
}
while ((expect->expect_all || !exp_member) && (found = ippFindNextAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL);
// Handle results of the EXPECT checks...
if (!exp_pass)
{
// Copy errors...
char *e; // Current error
for (e = (char *)cupsArrayGetFirst(exp_errors); e; e = (char *)cupsArrayGetNext(exp_errors))
cupsArrayAdd(data->errors, e);
}
cupsArrayDelete(exp_errors);
}
}
// If we are going to repeat this test, display intermediate results...
if (repeat_test)
{
if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
{
cupsFilePrintf(cupsFileStdout(), "%04d]\n", repeat_count);
\
if (data->num_displayed > 0)
{
for (attrptr = ippGetFirstAttribute(response); attrptr; attrptr = ippGetNextAttribute(response))
{
const char *attrname = ippGetName(attrptr);
if (attrname)
{
for (i = 0; i < data->num_displayed; i ++)
{
if (!strcmp(data->displayed[i], attrname))
{
print_attr(cupsFileStdout(), IPPTOOL_OUTPUT_TEST, attrptr, NULL);
break;
}
}
}
}
}
}
if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
{
cupsFilePrintf(cupsFileStdout(), " %-68.68s [", data->name);
}
ippDelete(response);
response = NULL;
}
}
while (repeat_test);
ippDelete(request);
request = NULL;
if (cupsArrayGetCount(data->errors) > 0)
data->prev_pass = data->pass = false;
if (data->prev_pass)
data->pass_count ++;
else
data->fail_count ++;
if (data->output == IPPTOOL_OUTPUT_PLIST)
{
cupsFilePuts(data->outfile, "<key>Successful</key>\n");
cupsFilePuts(data->outfile, data->prev_pass ? "<true />\n" : "<false />\n");
cupsFilePuts(data->outfile, "<key>StatusCode</key>\n");
print_xml_string(data->outfile, "string", ippErrorString(cupsGetError()));
cupsFilePuts(data->outfile, "<key>ResponseAttributes</key>\n");
cupsFilePuts(data->outfile, "<array>\n");
cupsFilePuts(data->outfile, "<dict>\n");
for (attrptr = ippGetFirstAttribute(response), group = ippGetGroupTag(attrptr);
attrptr;
attrptr = ippGetNextAttribute(response))
print_attr(data->outfile, data->output, attrptr, &group);
cupsFilePuts(data->outfile, "</dict>\n");
cupsFilePuts(data->outfile, "</array>\n");
}
else if (data->output == IPPTOOL_OUTPUT_IPPFILE && response)
{
for (attrptr = ippGetFirstAttribute(response); attrptr; attrptr = ippGetNextAttribute(response))
{
if (!ippGetName(attrptr) || ippGetGroupTag(attrptr) != IPP_TAG_PRINTER)
continue;
print_ippserver_attr(data, attrptr, 0);
}
}
else if (data->output == IPPTOOL_OUTPUT_JSON && response)
{
ipp_tag_t cur_tag = IPP_TAG_ZERO, // Current group tag
group_tag; // Attribute's group tag
cupsFilePuts(data->outfile, "[\n");
attrptr = ippGetFirstAttribute(response);
while (attrptr)
{
group_tag = ippGetGroupTag(attrptr);
if (group_tag && ippGetName(attrptr))
{
if (group_tag != cur_tag)
{
if (cur_tag)
cupsFilePuts(data->outfile, " },\n");
cupsFilePrintf(data->outfile, " {\n \"group-tag\": \"%s\",\n", ippTagString(group_tag));
cur_tag = group_tag;
}
print_json_attr(data, attrptr, 8);
attrptr = ippGetNextAttribute(response);
cupsFilePuts(data->outfile, ippGetName(attrptr) && ippGetGroupTag(attrptr) == cur_tag ? ",\n" : "\n");
}
else
{
attrptr = ippGetNextAttribute(response);
}
}
if (cur_tag)
cupsFilePuts(data->outfile, " }\n");
cupsFilePuts(data->outfile, "]\n");
}
if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
{
cupsFilePuts(cupsFileStdout(), data->prev_pass ? "PASS]\n" : "FAIL]\n");
if (!data->prev_pass || (data->verbosity && response))
{
cupsFilePrintf(cupsFileStdout(), " RECEIVED: %lu bytes in response\n", (unsigned long)ippGetLength(response));
cupsFilePrintf(cupsFileStdout(), " status-code = %s (%s)\n", ippErrorString(cupsGetError()), cupsGetErrorString());
if (data->verbosity && response)
{
for (attrptr = ippGetFirstAttribute(response); attrptr; attrptr = ippGetNextAttribute(response))
print_attr(cupsFileStdout(), IPPTOOL_OUTPUT_TEST, attrptr, NULL);
}
}
}
else if (!data->prev_pass && data->output != IPPTOOL_OUTPUT_QUIET)
fprintf(stderr, "%s\n", cupsGetErrorString());
if (data->prev_pass && data->output >= IPPTOOL_OUTPUT_LIST && !data->verbosity && data->num_displayed > 0)
{
int width; // Length of value
for (i = 0; i < data->num_displayed; i ++)
{
widths[i] = (int)strlen(data->displayed[i]);
for (attrptr = ippFindAttribute(response, data->displayed[i], IPP_TAG_ZERO);
attrptr;
attrptr = ippFindNextAttribute(response, data->displayed[i], IPP_TAG_ZERO))
{
width = (int)ippAttributeString(attrptr, NULL, 0);
if (width > widths[i])
widths[i] = width;
}
}
if (data->output == IPPTOOL_OUTPUT_CSV)
print_csv(data, NULL, NULL, data->num_displayed, data->displayed, widths);
else
print_line(data, NULL, NULL, data->num_displayed, data->displayed, widths);
attrptr = ippGetFirstAttribute(response);
while (attrptr)
{
while (attrptr && ippGetGroupTag(attrptr) <= IPP_TAG_OPERATION)
attrptr = ippGetNextAttribute(response);
if (attrptr)
{
if (data->output == IPPTOOL_OUTPUT_CSV)
attrptr = print_csv(data, response, attrptr, data->num_displayed, data->displayed, widths);
else
attrptr = print_line(data, response, attrptr, data->num_displayed, data->displayed, widths);
while (attrptr && ippGetGroupTag(attrptr) > IPP_TAG_OPERATION)
attrptr = ippGetNextAttribute(response);
}
}
}
else if (!data->prev_pass)
{
if (data->output == IPPTOOL_OUTPUT_PLIST)
{
cupsFilePuts(data->outfile, "<key>Errors</key>\n");
cupsFilePuts(data->outfile, "<array>\n");
for (error = (char *)cupsArrayGetFirst(data->errors);
error;
error = (char *)cupsArrayGetNext(data->errors))
print_xml_string(data->outfile, "string", error);
cupsFilePuts(data->outfile, "</array>\n");
}
if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
{
for (error = (char *)cupsArrayGetFirst(data->errors);
error;
error = (char *)cupsArrayGetNext(data->errors))
cupsFilePrintf(cupsFileStdout(), " %s\n", error);
}
}
if (data->num_displayed > 0 && !data->verbosity && response && (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout())))
{
for (attrptr = ippGetFirstAttribute(response); attrptr; attrptr = ippGetNextAttribute(response))
{
if (ippGetName(attrptr))
{
for (i = 0; i < data->num_displayed; i ++)
{
if (!strcmp(data->displayed[i], ippGetName(attrptr)))
{
print_attr(data->outfile, data->output, attrptr, NULL);
break;
}
}
}
}
}
skip_error:
if (data->monitor_thread != CUPS_THREAD_INVALID)
{
data->monitor_done = true;
cupsThreadWait(data->monitor_thread);
}
if (data->output == IPPTOOL_OUTPUT_PLIST)
cupsFilePuts(data->outfile, "</dict>\n");
ippDelete(response);
response = NULL;
clear_data(data);
return (data->ignore_errors || data->prev_pass);
}
//
// 'do_tests()' - Do tests as specified in the test file.
//
static bool // O - `true` on success, `false` on failure
do_tests(const char *testfile, // I - Test file to use
ipptool_test_t *data) // I - Test data
{
ipp_file_t *file; // IPP data file
// Connect to the printer/server...
data->http = connect_printer(data);
// Run tests...
if ((file = ippFileNew(data->parent, NULL, (ipp_ferror_cb_t)error_cb, data)) == NULL)
{
print_fatal_error(data, "Unable to create test file parser: %s", cupsGetErrorString());
return (false);
}
if (!ippFileOpen(file, testfile, "r"))
{
print_fatal_error(data, "Unable to open '%s': %s", testfile, cupsGetErrorString());
return (false);
}
ippFileRead(file, (ipp_ftoken_cb_t)token_cb, true);
ippFileDelete(file);
// Close connection and return...
httpClose(data->http);
data->http = NULL;
return (data->pass);
}
//
// 'error_cb()' - Print/add an error message.
//
static bool // O - `true` to continue, `false` to stop
error_cb(ipp_file_t *f, // I - IPP file data (not used)
ipptool_test_t *data, // I - Test data
const char *error) // I - Error message
{
(void)f;
print_fatal_error(data, "%s", error);
return (true);
}
//
// 'expect_matches()' - Return true if the tag matches the specification.
//
static bool // O - `true` on match, `false` on non-match
expect_matches(
ipptool_expect_t *expect, // I - Expected attribute
ipp_attribute_t *attr) // I - Attribute
{
int i, // Looping var
count; // Number of values
bool match; // Match?
char *of_type, // Type name to match
*paren, // Pointer to opening parenthesis
*next, // Next name to match
sep; // Separator character
ipp_tag_t value_tag; // Syntax/value tag
int lower, upper; // Lower and upper bounds for syntax
// If we don't expect a particular type, return immediately...
if (!expect->of_type)
return (true);
// Parse the "of_type" value since the string can contain multiple attribute
// types separated by "," or "|"...
value_tag = ippGetValueTag(attr);
count = ippGetCount(attr);
for (of_type = expect->of_type, match = false; !match && *of_type; of_type = next)
{
// Find the next separator, and set it (temporarily) to nul if present.
for (next = of_type; *next && *next != '|' && *next != ','; next ++);
if ((sep = *next) != '\0')
*next = '\0';
// Support some meta-types to make it easier to write the test file.
if ((paren = strchr(of_type, '(')) != NULL)
{
char *ptr; // Pointer into syntax string
*paren = '\0';
if (!strncmp(paren + 1, "MIN:", 4))
{
lower = INT_MIN;
ptr = paren + 5;
}
else if ((ptr = strchr(paren + 1, ':')) != NULL)
{
lower = atoi(paren + 1);
ptr ++;
}
else
{
lower = 0;
ptr = paren + 1;
}
if (!strcmp(ptr, "MAX)"))
upper = INT_MAX;
else
upper = atoi(ptr);
}
else
{
lower = INT_MIN;
upper = INT_MAX;
}
if (!strcmp(of_type, "text"))
{
if (upper == INT_MAX)
upper = 1023;
if (value_tag == IPP_TAG_TEXTLANG || value_tag == IPP_TAG_TEXT)
{
for (i = 0; i < count; i ++)
{
if (strlen(ippGetString(attr, i, NULL)) > (size_t)upper)
break;
}
match = (i == count);
}
}
else if (!strcmp(of_type, "name"))
{
if (upper == INT_MAX)
upper = 255;
if (value_tag == IPP_TAG_NAMELANG || value_tag == IPP_TAG_NAME)
{
for (i = 0; i < count; i ++)
{
if (strlen(ippGetString(attr, i, NULL)) > (size_t)upper)
break;
}
match = (i == count);
}
}
else if (!strcmp(of_type, "collection"))
{
match = value_tag == IPP_TAG_BEGIN_COLLECTION;
}
else if (value_tag == ippTagValue(of_type))
{
switch (value_tag)
{
case IPP_TAG_KEYWORD :
case IPP_TAG_URI :
if (upper == INT_MAX)
{
if (value_tag == IPP_TAG_KEYWORD)
upper = 255;
else
upper = 1023;
}
for (i = 0; i < count; i ++)
{
if (strlen(ippGetString(attr, i, NULL)) > (size_t)upper)
break;
}
match = (i == count);
break;
case IPP_TAG_STRING :
if (upper == INT_MAX)
upper = 1023;
for (i = 0; i < count; i ++)
{
int datalen; // Length of octetString value
ippGetOctetString(attr, i, &datalen);
if (datalen > upper)
break;
}
match = (i == count);
break;
case IPP_TAG_INTEGER :
for (i = 0; i < count; i ++)
{
int value = ippGetInteger(attr, i);
// Integer value
if (value < lower || value > upper)
break;
}
match = (i == count);
break;
case IPP_TAG_RANGE :
for (i = 0; i < count; i ++)
{
int vupper, vlower = ippGetRange(attr, i, &vupper);
// Range value
if (vlower < lower || vlower > upper || vupper < lower || vupper > upper)
break;
}
match = (i == count);
break;
default :
// No other constraints, so this is a match
match = true;
break;
}
}
// Restore the separators if we have them...
if (paren)
*paren = '(';
if (sep)
*next++ = sep;
}
return (match);
}
//
// 'free_data()' - Free test data.
//
static void
free_data(ipptool_test_t *data) // I - Test data
{
clear_data(data);
ippFileDelete(data->parent);
cupsArrayDelete(data->errors);
free(data);
}
//
// 'generate_file()' - Generate a print file.
//
static http_status_t // O - HTTP status
generate_file(
http_t *http, // I - HTTP connection
ipptool_generate_t *params) // I - GENERATE-FILE parameters
{
cups_raster_mode_t mode; // Raster output mode
cups_raster_t *ras; // Raster stream
cups_page_header2_t header; // Raster page header (front side)
cups_page_header2_t back_header; // Raster page header (back side)
pwg_media_t *pwg; // PWG media information
cups_media_t media; // CUPS media information
// Set the output mode...
if (!strcmp(params->format, "image/pwg-raster"))
mode = CUPS_RASTER_WRITE_PWG;
else if (!strcmp(params->format, "image/urf"))
mode = CUPS_RASTER_WRITE_APPLE;
else
mode = CUPS_RASTER_WRITE_COMPRESSED;
// Create the raster header...
if ((pwg = pwgMediaForPWG(params->media)) == NULL)
{
fprintf(stderr, "ipptool: Unable to parse media size '%s'.\n", params->media);
return (HTTP_STATUS_SERVER_ERROR);
}
memset(&media, 0, sizeof(media));
cupsCopyString(media.media, pwg->pwg, sizeof(media.media));
media.width = pwg->width;
media.length = pwg->length;
cupsRasterInitHeader(&header, &media, /*optimize*/NULL, IPP_QUALITY_NORMAL, /*intent*/NULL, params->orientation, params->sides, params->type, params->xdpi, params->ydpi, /*sheet_back*/NULL);
cupsRasterInitHeader(&back_header, &media, /*optimize*/NULL, IPP_QUALITY_NORMAL, /*intent*/NULL, params->orientation, params->sides, params->type, params->xdpi, params->ydpi, params->sheet_back);
#if 0
fprintf(stderr, "ipptool: media='%s'\n", params->media);
fprintf(stderr, "ipptool: type='%s'\n", params->type);
fprintf(stderr, "ipptool: resolution=%dx%d\n", params->xdpi, params->ydpi);
fprintf(stderr, "ipptool: orientation=%d\n", params->orientation);
fprintf(stderr, "ipptool: sides='%s'\n", params->sides);
fprintf(stderr, "ipptool: num_copies=%d\n", params->num_copies);
fprintf(stderr, "ipptool: num_pages=%d\n", params->num_pages);
fprintf(stderr, "ipptool: format='%s'\n", params->format);
fprintf(stderr, "ipptool: sheet_back='%s'\n", params->sheet_back);
#endif // 0
// Create the raster stream...
if ((ras = cupsRasterOpenIO((cups_raster_cb_t)httpWrite, http, mode)) == NULL)
return (HTTP_STATUS_SERVER_ERROR);
// Write it...
if (!cupsRasterWriteTest(ras, &header, &back_header, params->sheet_back, params->orientation, params->num_copies, params->num_pages))
return (HTTP_STATUS_SERVER_ERROR);
cupsRasterClose(ras);
return (HTTP_STATUS_CONTINUE);
}
//
// 'get_filename()' - Get a filename based on the current test file.
//
static char * // O - Filename
get_filename(const char *testfile, // I - Current test file
char *dst, // I - Destination filename
const char *src, // I - Source filename
size_t dstsize) // I - Size of destination buffer
{
char *dstptr; // Pointer into destination
_cups_globals_t *cg = _cupsGlobals();
// Global data
if (*src == '<' && src[strlen(src) - 1] == '>')
{
// Map <filename> to CUPS_DATADIR/ipptool/filename...
snprintf(dst, dstsize, "%s/ipptool/%s", cg->cups_datadir, src + 1);
dstptr = dst + strlen(dst) - 1;
if (*dstptr == '>')
*dstptr = '\0';
}
else if (!access(src, R_OK) || *src == '/'
#ifdef _WIN32
|| (_cups_isalpha(*src) && src[1] == ':')
#endif // _WIN32
)
{
// Use the path as-is...
cupsCopyString(dst, src, dstsize);
}
else
{
// Make path relative to testfile...
cupsCopyString(dst, testfile, dstsize);
if ((dstptr = strrchr(dst, '/')) != NULL)
dstptr ++;
else
dstptr = dst; // Should never happen
cupsCopyString(dstptr, src, dstsize - (size_t)(dstptr - dst));
#if _WIN32
if (_access(dst, 0))
{
// Not available relative to the testfile, see if it can be found on the desktop...
const char *userprofile = getenv("USERPROFILE");
// User home directory
if (userprofile)
snprintf(dst, dstsize, "%s/Desktop/%s", userprofile, src);
}
#endif // _WIN32
}
return (dst);
}
//
// 'get_string()' - Get a pointer to a string value or the portion of interest.
//
static const char * // O - Pointer to string
get_string(ipp_attribute_t *attr, // I - IPP attribute
int element, // I - Element to fetch
int flags, // I - Value ("with") flags
char *buffer, // I - Temporary buffer
size_t bufsize) // I - Size of temporary buffer
{
const char *value; // Value
char *ptr, // Pointer into value
scheme[256], // URI scheme
userpass[256], // Username/password
hostname[256], // Hostname
resource[1024]; // Resource
int port; // Port number
value = ippGetString(attr, element, NULL);
if (flags & IPPTOOL_WITH_HOSTNAME)
{
if (httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), buffer, bufsize, &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
buffer[0] = '\0';
ptr = buffer + strlen(buffer) - 1;
if (ptr >= buffer && *ptr == '.')
*ptr = '\0'; // Drop trailing "."
return (buffer);
}
else if (flags & IPPTOOL_WITH_RESOURCE)
{
if (httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, buffer, bufsize) < HTTP_URI_STATUS_OK)
buffer[0] = '\0';
return (buffer);
}
else if (flags & IPPTOOL_WITH_SCHEME)
{
if (httpSeparateURI(HTTP_URI_CODING_ALL, value, buffer, bufsize, userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
buffer[0] = '\0';
return (buffer);
}
else if (ippGetValueTag(attr) == IPP_TAG_URI && (!strncmp(value, "ipp://", 6) || !strncmp(value, "http://", 7) || !strncmp(value, "ipps://", 7) || !strncmp(value, "https://", 8)))
{
http_uri_status_t status = httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource));
if (status < HTTP_URI_STATUS_OK)
{
// Bad URI...
buffer[0] = '\0';
}
else
{
// Normalize URI with no trailing dot...
if ((ptr = hostname + strlen(hostname) - 1) >= hostname && *ptr == '.')
*ptr = '\0';
httpAssembleURI(HTTP_URI_CODING_ALL, buffer, bufsize, scheme, userpass, hostname, port, resource);
}
return (buffer);
}
else
{
return (value);
}
}
//
// '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(const ipp_uchar_t *date) // I - IPP (RFC 1903) date/time value
{
time_t utctime; // UTC time since 1970
struct tm utcdate; // UTC date/time
static char buffer[255]; // String buffer
utctime = ippDateToTime(date);
gmtime_r(&utctime, &utcdate);
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02dZ",
utcdate.tm_year + 1900, utcdate.tm_mon + 1, utcdate.tm_mday,
utcdate.tm_hour, utcdate.tm_min, utcdate.tm_sec);
return (buffer);
}
//
// 'parse_generate_file()' - Parse the GENERATE-FILE directive.
//
// GENERATE-FILE {
// MEDIA "media size name, default, ready"
// COLORSPACE "colorspace_bits, auto, color, monochrome, bi-level"
// RESOLUTION "resolution, min, max, default"
// ORIENTATION "portrait, landscape, reverse-landscape, reverse-portrait"
// SIDES "one-sided, two-sided-long-edge, two-sided-short-edge"
// NUM-COPIES "copies"
// NUM-PAGES "pages, min"
// FORMAT "image/pwg-raster, image/urf"
// }
//
static bool // O - `true` to continue, `false` to stop
parse_generate_file(
ipp_file_t *f, // I - IPP file data
ipptool_test_t *data) // I - Test data
{
int i; // Looping var
ipptool_generate_t *params = NULL; // Generation parameters
http_t *http; // Connection to printer
ipp_t *request, // Get-Printer-Attributes request
*response = NULL;// Get-Printer-Attributes response
ipp_attribute_t *attr; // Current attribute
const char *keyword; // Keyword value
char token[256], // Token string
temp[1024], // Temporary string
value[1024], // Value string
*ptr; // Pointer into value
static const char *autos[][2] = // Automatic color/monochrome keywords
{
{ "SRGB24", "srgb_8" },
{ "ADOBERGB24", "adobe-rgb_8" },
{ "DEVRGB24", "rgb_8" },
{ "DEVCMYK32", "cmyk_8" },
{ "ADOBERGB48", "adobe-rgb_16" },
{ "DEVRGB48", "rgb_16" },
{ "DEVCMYK64", "cmyk_16" },
{ "W8", "sgray_8" },
{ NULL, "black_8" },
{ "W16", "sgray_16" },
{ NULL, "black_16" },
{ NULL, "sgray_1" },
{ NULL, "black_1" }
};
static const char *bi_levels[][2] = // Bi-level keywords
{
{ NULL, "sgray_1" },
{ NULL, "black_1" }
};
static const char *colors[][2] = // Color keywords
{
{ "SRGB24", "srgb_8" },
{ "ADOBERGB24", "adobe-rgb_8" },
{ "DEVRGB24", "rgb_8" },
{ "DEVCMYK32", "cmyk_8" },
{ "ADOBERGB48", "adobe-rgb_16" },
{ "DEVRGB48", "rgb_16" },
{ "DEVCMYK64", "cmyk_16" }
};
static const char *monochromes[][2] = // Monochrome keywords
{
{ "W8", "sgray_8" },
{ NULL, "black_8" },
{ "W16", "sgray_16" },
{ NULL, "black_16" },
{ NULL, "sgray_1" },
{ NULL, "black_1" }
};
// Make sure we have an open brace after the GENERATE-FILE...
if (!ippFileReadToken(f, token, sizeof(token)) || strcmp(token, "{"))
{
print_fatal_error(data, "Missing open brace on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
// Get printer attributes...
if ((http = connect_printer(data)) == NULL)
{
print_fatal_error(data, "GENERATE-FILE connection failure on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, ippFileGetVar(data->parent, "uri"));
response = cupsDoRequest(http, request, ippFileGetVar(data->parent, "resource"));
httpClose(http);
if (cupsGetError() >= IPP_STATUS_ERROR_BAD_REQUEST)
{
print_fatal_error(data, "GENERATE-FILE query failure on line %d of '%s': %s", ippFileGetLineNumber(f), ippFileGetFilename(f), cupsGetErrorString());
ippDelete(response);
return (false);
}
// Allocate parameters...
if ((params = calloc(1, sizeof(ipptool_generate_t))) == NULL)
{
print_fatal_error(data, "GENERATE-FILE memory allocation failure on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
// Loop until we get a closing brace...
while (ippFileReadToken(f, token, sizeof(token)))
{
if (!strcmp(token, "}"))
{
// Update the raster type as needed...
if (!params->type[0])
{
// Get request/printer default value for print-color-mode, default to "auto"...
if ((attr = ippFileGetAttribute(f, "print-color-mode", IPP_TAG_KEYWORD)) == NULL)
attr = ippFindAttribute(response, "print-color-mode-default", IPP_TAG_KEYWORD);
if (attr)
cupsCopyString(params->type, ippGetString(attr, 0, NULL), sizeof(params->type));
else
cupsCopyString(params->type, "auto", sizeof(params->type));
}
if (!strcmp(params->type, "auto"))
{
// Find auto keyword...
params->type[0] = '\0';
if ((attr = ippFindAttribute(response, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD)) == NULL)
attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD);
for (i = 0; i < (int)(sizeof(autos) / sizeof(autos[0])); i ++)
{
if (ippContainsString(attr, autos[i][0]) || ippContainsString(attr, autos[i][1]))
{
cupsCopyString(params->type, autos[i][1], sizeof(params->type));
break;
}
}
if (!params->type[0])
{
print_fatal_error(data, "Printer does not support COLORSPACE \"auto\" on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
else if (!strcmp(params->type, "bi-level"))
{
// Find bi-level keyword...
params->type[0] = '\0';
attr = ippFindAttribute(response, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD);
for (i = 0; i < (int)(sizeof(bi_levels) / sizeof(bi_levels[0])); i ++)
{
if (ippContainsString(attr, bi_levels[i][1]))
{
cupsCopyString(params->type, bi_levels[i][1], sizeof(params->type));
break;
}
}
if (!params->type[0])
{
print_fatal_error(data, "Printer does not support COLORSPACE \"bi-level\" on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
else if (!strcmp(params->type, "color"))
{
// Find color keyword...
params->type[0] = '\0';
if ((attr = ippFindAttribute(response, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD)) != NULL)
attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD);
for (i = 0; i < (int)(sizeof(colors) / sizeof(colors[0])); i ++)
{
if (ippContainsString(attr, colors[i][0]) || ippContainsString(attr, colors[i][1]))
{
cupsCopyString(params->type, colors[i][1], sizeof(params->type));
break;
}
}
if (!params->type[0])
{
print_fatal_error(data, "Printer does not support COLORSPACE \"color\" on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
else if (!strcmp(params->type, "monochrome"))
{
// Find grayscale keyword...
params->type[0] = '\0';
if ((attr = ippFindAttribute(response, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD)) == NULL)
attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD);
for (i = 0; i < (int)(sizeof(monochromes) / sizeof(monochromes[0])); i ++)
{
if (ippContainsString(attr, monochromes[i][0]) || ippContainsString(attr, monochromes[i][1]))
{
cupsCopyString(params->type, monochromes[i][1], sizeof(params->type));
break;
}
}
if (!params->type[0])
{
print_fatal_error(data, "Printer does not support COLORSPACE \"monochrome\" on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
// Make sure we have an output format...
if (!params->format[0])
{
// Check the supported formats and choose a suitable one...
if ((keyword = ippGetString(ippFileGetAttribute(f, "document-format", IPP_TAG_MIMETYPE), 0, NULL)) != NULL)
{
if (strcmp(keyword, "image/pwg-raster") && strcmp(keyword, "image/urf"))
{
print_fatal_error(data, "Unsupported \"document-format\" value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
cupsCopyString(params->format, keyword, sizeof(params->format));
}
else if ((attr = ippFindAttribute(response, "document-format-supported", IPP_TAG_MIMETYPE)) != NULL)
{
// Default to Apple Raster unless sending bitmaps, which are only
// supported by PWG Raster...
if (ippContainsString(attr, "image/urf") && strncmp(params->type, "black_", 6) && strcmp(params->type, "srgb_1"))
cupsCopyString(params->format, "image/urf", sizeof(params->format));
else if (ippContainsString(attr, "image/pwg-raster"))
cupsCopyString(params->format, "image/pwg-raster", sizeof(params->format));
}
if (!params->format[0])
{
print_fatal_error(data, "Printer does not support a compatible FORMAT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
// Get default/ready media...
if (!params->media[0] || !strcmp(params->media, "default"))
{
// Use job ticket or default media...
if (!params->media[0] && (keyword = ippGetString(ippFileGetAttribute(f, "media", IPP_TAG_ZERO), 0, NULL)) != NULL)
{
cupsCopyString(params->media, keyword, sizeof(params->media));
}
else if ((keyword = ippGetString(ippFindAttribute(response, "media-default", IPP_TAG_ZERO), 0, NULL)) != NULL)
{
cupsCopyString(params->media, keyword, sizeof(params->media));
}
else
{
print_fatal_error(data, "Printer does not report a default MEDIA size name on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
else if (!strcmp(params->media, "ready"))
{
// Use ready media
if ((keyword = ippGetString(ippFindAttribute(response, "media-ready", IPP_TAG_ZERO), 0, NULL)) != NULL)
{
cupsCopyString(params->media, keyword, sizeof(params->media));
}
else
{
print_fatal_error(data, "Printer does not report a ready MEDIA size name on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
// Default resolution
if (!params->xdpi || !params->ydpi)
{
if ((attr = ippFindAttribute(response, "pwg-raster-document-resolution-supported", IPP_TAG_RESOLUTION)) != NULL)
{
ipp_res_t units; // Resolution units
// Use the middle resolution in the list...
params->xdpi = ippGetResolution(attr, ippGetCount(attr) / 2, ¶ms->ydpi, &units);
if (units == IPP_RES_PER_CM)
{
params->xdpi = (int)(params->xdpi * 2.54);
params->ydpi = (int)(params->ydpi * 2.54);
}
}
else if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) != NULL)
{
int count = ippGetCount(attr); // Number of values
for (i = 0; i < count; i ++)
{
keyword = ippGetString(attr, i, NULL);
if (!strncmp(keyword, "RS", 2))
{
// Use the first resolution in the list...
params->xdpi = params->ydpi = atoi(keyword + 2);
break;
}
}
}
if (params->xdpi <= 0 || params->xdpi > 9600 || params->ydpi <= 0 || params->ydpi > 9600)
{
print_fatal_error(data, "Printer does not report a supported RESOLUTION on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
// Default duplex/sides
if (!params->sides[0])
{
if ((keyword = ippGetString(ippFileGetAttribute(f, "sides", IPP_TAG_ZERO), 0, NULL)) != NULL)
{
// Use the setting from the job ticket...
cupsCopyString(params->sides, keyword, sizeof(params->sides));
}
else if (params->num_pages != 1 && (attr = ippFindAttribute(response, "sides-supported", IPP_TAG_KEYWORD)) != NULL && ippGetCount(attr) > 1)
{
// Default to two-sided for capable printers...
if (params->orientation == IPP_ORIENT_LANDSCAPE || params->orientation == IPP_ORIENT_REVERSE_LANDSCAPE)
cupsCopyString(params->sides, "two-sided-short-edge", sizeof(params->sides));
else
cupsCopyString(params->sides, "two-sided-long-edge", sizeof(params->sides));
}
else
{
// Fall back to 1-sided output...
cupsCopyString(params->sides, "one-sided", sizeof(params->sides));
}
}
// Default orientation
if (!params->orientation)
{
// Use the job ticket value, otherwise use landscape for short-edge duplex
if ((attr = ippFileGetAttribute(f, "orientation-requested", IPP_TAG_ENUM)) != NULL)
params->orientation = (ipp_orient_t)ippGetInteger(attr, 0);
else
params->orientation = !strcmp(params->sides, "two-sided-short-edge") ? IPP_ORIENT_LANDSCAPE : IPP_ORIENT_PORTRAIT;
}
// Default number of copies and pages...
if (!params->num_copies)
params->num_copies = 1;
if (!params->num_pages)
params->num_pages = !strncmp(params->sides, "two-sided-", 10) ? 2 : 1;
// Back side transform, if any
if (!params->sheet_back[0])
{
if ((attr = ippFindAttribute(response, "pwg-raster-document-sheet-back", IPP_TAG_KEYWORD)) != NULL)
{
cupsCopyString(params->sheet_back, ippGetString(attr, 0, NULL), sizeof(params->sheet_back));
}
else if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) != NULL)
{
if (ippContainsString(attr, "DM2"))
cupsCopyString(params->sheet_back, "flip", sizeof(params->sheet_back));
else if (ippContainsString(attr, "DM3"))
cupsCopyString(params->sheet_back, "rotated", sizeof(params->sheet_back));
else if (ippContainsString(attr, "DM4"))
cupsCopyString(params->sheet_back, "manual-tumble", sizeof(params->sheet_back));
else
cupsCopyString(params->sheet_back, "normal", sizeof(params->sheet_back));
}
else
{
cupsCopyString(params->sheet_back, "normal", sizeof(params->sheet_back));
}
}
// Everything is good, save the parameters and return...
data->generate_params = params;
ippDelete(response);
return (1);
}
else if (!_cups_strcasecmp(token, "COLORSPACE"))
{
if (params->type[0])
{
print_fatal_error(data, "Unexpected extra COLORSPACE on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing COLORSPACE value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
ippFileExpandVars(f, value, temp, sizeof(value));
if (!strcmp(value, "auto") || !strcmp(value, "bi-level") || !strcmp(value, "color") || !strcmp(value, "monochrome") || !strcmp(value, "adobe-rgb_8") || !strcmp(value, "adobe-rgb_16") || !strcmp(value, "black_1") || !strcmp(value, "black_8") || !strcmp(value, "black_16") || !strcmp(value, "cmyk_8") || !strcmp(value, "cmyk_16") || !strcmp(value, "rgb_8") || !strcmp(value, "rgb_16") || !strcmp(value, "sgray_1") || !strcmp(value, "sgray_8") || !strcmp(value, "sgray_16") || !strcmp(value, "srgb_8") || !strcmp(value, "srgb_16"))
{
// Use "print-color-mode" or "pwg-raster-document-type-supported" keyword...
cupsCopyString(params->type, value, sizeof(params->type));
}
else
{
print_fatal_error(data, "Bad COLORSPACE \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
else if (!_cups_strcasecmp(token, "FORMAT"))
{
if (params->format[0])
{
print_fatal_error(data, "Unexpected extra FORMAT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing FORMAT MIME media type on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
ippFileExpandVars(f, value, temp, sizeof(value));
if (!strcmp(value, "image/pwg-raster") || !strcmp(value, "image/urf"))
{
cupsCopyString(params->format, value, sizeof(params->format));
}
else
{
print_fatal_error(data, "Bad FORMAT \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
else if (!_cups_strcasecmp(token, "MEDIA"))
{
if (params->media[0])
{
print_fatal_error(data, "Unexpected extra MEDIA on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing MEDIA size name on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
ippFileExpandVars(f, value, temp, sizeof(value));
if (!strcmp(value, "default") || !strcmp(value, "ready") || pwgMediaForPWG(value) != NULL)
{
cupsCopyString(params->media, value, sizeof(params->media));
}
else
{
print_fatal_error(data, "Bad MEDIA \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
else if (!_cups_strcasecmp(token, "NUM-COPIES"))
{
int intvalue; // Number of copies value
if (params->num_copies)
{
print_fatal_error(data, "Unexpected extra NUM-COPIES on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing NUM-COPIES number on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
ippFileExpandVars(f, value, temp, sizeof(value));
if ((intvalue = strtol(value, NULL, 10)) > INT_MAX || intvalue < 1)
{
print_fatal_error(data, "Bad NUM-COPIES \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
params->num_copies = intvalue;
}
else if (!_cups_strcasecmp(token, "NUM-PAGES"))
{
int intvalue; // Number of pages value
if (params->num_pages)
{
print_fatal_error(data, "Unexpected extra NUM-PAGES on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing NUM-PAGES number on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
ippFileExpandVars(f, value, temp, sizeof(value));
if ((intvalue = strtol(value, NULL, 10)) > INT_MAX || intvalue < 1)
{
print_fatal_error(data, "Bad NUM-PAGES \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
params->num_pages = (int)intvalue;
}
else if (!_cups_strcasecmp(token, "ORIENTATION"))
{
if (params->orientation)
{
print_fatal_error(data, "Unexpected extra ORIENTATION on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing ORIENTATION on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
ippFileExpandVars(f, value, temp, sizeof(value));
if (!strcmp(value, "portrait"))
params->orientation = IPP_ORIENT_PORTRAIT;
else if (!strcmp(value, "landscape"))
params->orientation = IPP_ORIENT_LANDSCAPE;
else if (!strcmp(value, "reverse-landscape"))
params->orientation = IPP_ORIENT_REVERSE_LANDSCAPE;
else if (!strcmp(value, "reverse-portrait"))
params->orientation = IPP_ORIENT_REVERSE_PORTRAIT;
else
{
print_fatal_error(data, "Bad ORIENTATION \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
else if (!_cups_strcasecmp(token, "RESOLUTION"))
{
if (params->xdpi || params->ydpi)
{
print_fatal_error(data, "Unexpected extra RESOLUTION on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing RESOLUTION on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
ippFileExpandVars(f, value, temp, sizeof(value));
if (!strcmp(value, "min") || !strcmp(value, "max"))
{
if ((attr = ippFindAttribute(response, "pwg-raster-document-resolution-supported", IPP_TAG_RESOLUTION)) != NULL)
{
ipp_res_t units; // Resolution units
// Use the first or last resolution in the list...
params->xdpi = ippGetResolution(attr, !strcmp(value, "min") ? 0 : ippGetCount(attr) - 1, ¶ms->ydpi, &units);
if (units == IPP_RES_PER_CM)
{
params->xdpi = (int)(params->xdpi * 2.54);
params->ydpi = (int)(params->ydpi * 2.54);
}
}
else if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) != NULL)
{
int count = ippGetCount(attr); // Number of values
for (i = 0; i < count; i ++)
{
keyword = ippGetString(attr, i, NULL);
if (!strncmp(keyword, "RS", 2))
{
if (!strcmp(value, "min"))
{
// Use the first resolution in the list...
params->xdpi = params->ydpi = atoi(keyword + 2);
}
else
{
// Use the last resolution in the list...
params->xdpi = params->ydpi = (int)strtol(keyword + 2, &ptr, 10);
while (ptr && *ptr && *ptr == '-')
params->xdpi = params->ydpi = (int)strtol(ptr + 1, &ptr, 10);
}
break;
}
}
}
}
else if (strcmp(value, "default"))
{
char units[8] = ""; // Resolution units (dpi or dpcm)
if (sscanf(value, "%dx%d%7s", ¶ms->xdpi, ¶ms->ydpi, units) == 1)
{
sscanf(value, "%d%7s", ¶ms->xdpi, units);
params->ydpi = params->xdpi;
}
if (!strcmp(units, "dpcm"))
{
params->xdpi = (int)(params->xdpi * 2.54);
params->ydpi = (int)(params->ydpi * 2.54);
}
else if (strcmp(units, "dpi"))
params->xdpi = params->ydpi = 0;
}
if (strcmp(value, "default") && (params->xdpi <= 0 || params->xdpi > 9600 || params->ydpi <= 0 || params->ydpi > 9600))
{
print_fatal_error(data, "Bad RESOLUTION \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
else if (!_cups_strcasecmp(token, "SIDES"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing SIDES on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
ippFileExpandVars(f, value, temp, sizeof(value));
if (!strcmp(value, "one-sided") || !strcmp(value, "two-sided-long-edge") || !strcmp(value, "two-sided-short-edge"))
{
cupsCopyString(params->sides, value, sizeof(params->sides));
}
else
{
print_fatal_error(data, "Bad SIDES \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
else
{
print_fatal_error(data, "Unknown %s on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
goto fail;
}
}
print_fatal_error(data, "Missing closing brace on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
fail:
free(params);
ippDelete(response);
return (0);
}
//
// 'parse_monitor_printer_state()' - Parse the MONITOR-PRINTER-STATE directive.
//
// MONITOR-PRINTER-STATE [printer-uri] {
// DELAY nnn
// EXPECT attribute-name ...
// }
//
static bool // O - `true` to continue, `false` to stop
parse_monitor_printer_state(
ipp_file_t *f, // I - IPP file data
ipptool_test_t *data) // I - Test data
{
char token[256], // Token string
name[1024], // Name string
temp[1024], // Temporary string
value[1024], // Value string
*ptr; // Pointer into value
const char *uri; // Printer URI
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing printer URI on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if (strcmp(temp, "{"))
{
// Got a printer URI so copy it...
ippFileExpandVars(f, value, temp, sizeof(value));
data->monitor_uri = strdup(value);
// Then see if we have an opening brace...
if (!ippFileReadToken(f, temp, sizeof(temp)) || strcmp(temp, "{"))
{
print_fatal_error(data, "Missing opening brace on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
else if ((uri = ippFileGetVar(data->parent, "uri")) != NULL)
{
// Use the default printer URI...
data->monitor_uri = strdup(uri);
}
// Loop until we get a closing brace...
while (ippFileReadToken(f, token, sizeof(token)))
{
if (_cups_strcasecmp(token, "COUNT") &&
_cups_strcasecmp(token, "DEFINE-MATCH") &&
_cups_strcasecmp(token, "DEFINE-NO-MATCH") &&
_cups_strcasecmp(token, "DEFINE-VALUE") &&
_cups_strcasecmp(token, "DISPLAY-MATCH") &&
_cups_strcasecmp(token, "IF-DEFINED") &&
_cups_strcasecmp(token, "IF-NOT-DEFINED") &&
_cups_strcasecmp(token, "IN-GROUP") &&
_cups_strcasecmp(token, "OF-TYPE") &&
_cups_strcasecmp(token, "WITH-DISTINCT-VALUES") &&
_cups_strcasecmp(token, "WITH-VALUE"))
data->last_expect = NULL;
if (!strcmp(token, "}"))
return (1);
else if (!_cups_strcasecmp(token, "EXPECT"))
{
// Expected attributes...
if (data->num_monitor_expects >= (int)(sizeof(data->monitor_expects) / sizeof(data->monitor_expects[0])))
{
print_fatal_error(data, "Too many EXPECT's on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if (!ippFileReadToken(f, name, sizeof(name)))
{
print_fatal_error(data, "Missing EXPECT name on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
data->last_expect = data->monitor_expects + data->num_monitor_expects;
data->num_monitor_expects ++;
memset(data->last_expect, 0, sizeof(ipptool_expect_t));
data->last_expect->repeat_limit = 1000;
if (name[0] == '!')
{
data->last_expect->not_expect = 1;
data->last_expect->name = strdup(name + 1);
}
else if (name[0] == '?')
{
data->last_expect->optional = 1;
data->last_expect->name = strdup(name + 1);
}
else
data->last_expect->name = strdup(name);
}
else if (!_cups_strcasecmp(token, "COUNT"))
{
int count; // Count value
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing COUNT number on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if ((count = strtol(temp, NULL, 10)) > INT_MAX)
{
print_fatal_error(data, "Bad COUNT \"%s\" on line %d of '%s'.", temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if (data->last_expect)
{
data->last_expect->count = count;
}
else
{
print_fatal_error(data, "COUNT without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
else if (!_cups_strcasecmp(token, "DEFINE-MATCH"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing DEFINE-MATCH variable on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if (data->last_expect)
{
data->last_expect->define_match = strdup(temp);
}
else
{
print_fatal_error(data, "DEFINE-MATCH without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
else if (!_cups_strcasecmp(token, "DEFINE-NO-MATCH"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing DEFINE-NO-MATCH variable on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if (data->last_expect)
{
data->last_expect->define_no_match = strdup(temp);
}
else
{
print_fatal_error(data, "DEFINE-NO-MATCH without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
else if (!_cups_strcasecmp(token, "DEFINE-VALUE"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing DEFINE-VALUE variable on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if (data->last_expect)
{
data->last_expect->define_value = strdup(temp);
}
else
{
print_fatal_error(data, "DEFINE-VALUE without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
else if (!_cups_strcasecmp(token, "DISPLAY-MATCH"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing DISPLAY-MATCH message on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if (data->last_expect)
{
data->last_expect->display_match = strdup(temp);
}
else
{
print_fatal_error(data, "DISPLAY-MATCH without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
else if (!_cups_strcasecmp(token, "DELAY"))
{
// Delay before operation...
double dval; // Delay value
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing DELAY value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
ippFileExpandVars(f, value, temp, sizeof(value));
if ((dval = _cupsStrScand(value, &ptr, localeconv())) < 0.0 || (*ptr && *ptr != ','))
{
print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
data->monitor_delay = (useconds_t)(1000000.0 * dval);
if (*ptr == ',')
{
if ((dval = _cupsStrScand(ptr + 1, &ptr, localeconv())) <= 0.0 || *ptr)
{
print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
data->monitor_interval = (useconds_t)(1000000.0 * dval);
}
else
data->monitor_interval = data->monitor_delay;
}
else if (!_cups_strcasecmp(token, "OF-TYPE"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing OF-TYPE value tag(s) on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if (data->last_expect)
{
data->last_expect->of_type = strdup(temp);
}
else
{
print_fatal_error(data, "OF-TYPE without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
else if (!_cups_strcasecmp(token, "IN-GROUP"))
{
ipp_tag_t in_group; // IN-GROUP value
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing IN-GROUP group tag on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if ((in_group = ippTagValue(temp)) == IPP_TAG_ZERO || in_group >= IPP_TAG_UNSUPPORTED_VALUE)
{
print_fatal_error(data, "Bad IN-GROUP group tag \"%s\" on line %d of '%s'.", temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
else if (data->last_expect)
{
data->last_expect->in_group = in_group;
}
else
{
print_fatal_error(data, "IN-GROUP without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
else if (!_cups_strcasecmp(token, "IF-DEFINED"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing IF-DEFINED name on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if (data->last_expect)
{
if (data->last_expect->if_defined)
cupsArrayAddStrings(data->last_expect->if_defined, temp, ',');
else
data->last_expect->if_defined = cupsArrayNewStrings(temp, ',');
}
else
{
print_fatal_error(data, "IF-DEFINED without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
else if (!_cups_strcasecmp(token, "IF-NOT-DEFINED"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing IF-NOT-DEFINED name on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
if (data->last_expect)
{
if (data->last_expect->if_not_defined)
cupsArrayAddStrings(data->last_expect->if_not_defined, temp, ',');
else
data->last_expect->if_not_defined = cupsArrayNewStrings(temp, ',');
}
else
{
print_fatal_error(data, "IF-NOT-DEFINED without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
else if (!_cups_strcasecmp(token, "WITH-DISTINCT-VALUES"))
{
if (data->last_expect)
{
data->last_expect->with_distinct = 1;
}
else
{
print_fatal_error(data, "%s without a preceding EXPECT on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
else if (!_cups_strcasecmp(token, "WITH-VALUE"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing %s value on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
// Read additional comma-delimited values - needed since legacy test files
// will have unquoted WITH-VALUE values with commas...
ptr = temp + strlen(temp);
for (;;)
{
ippFileSavePosition(f);
ptr += strlen(ptr);
if (!ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
break;
if (!strcmp(ptr, ","))
{
// Append a value...
ptr += strlen(ptr);
if (!ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
break;
}
else
{
// Not another value, stop here...
ippFileRestorePosition(f);
*ptr = '\0';
break;
}
}
if (data->last_expect)
{
// Expand any variables in the value and then save it.
ippFileExpandVars(f, value, temp, sizeof(value));
ptr = value + strlen(value) - 1;
if (value[0] == '/' && ptr > value && *ptr == '/')
{
// WITH-VALUE is a POSIX extended regular expression.
data->last_expect->with_value = calloc(1, (size_t)(ptr - value));
data->last_expect->with_flags |= IPPTOOL_WITH_REGEX;
if (data->last_expect->with_value)
memcpy(data->last_expect->with_value, value + 1, (size_t)(ptr - value - 1));
}
else
{
// WITH-VALUE is a literal value...
for (ptr = value; *ptr; ptr ++)
{
if (*ptr == '\\' && ptr[1])
{
// Remove \ from \foo...
_cups_strcpy(ptr, ptr + 1);
}
}
data->last_expect->with_value = strdup(value);
data->last_expect->with_flags |= IPPTOOL_WITH_LITERAL;
}
}
else
{
print_fatal_error(data, "%s without a preceding EXPECT on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
}
}
print_fatal_error(data, "Missing closing brace on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (0);
}
//
// 'password_cb()' - Password callback using the IPP variables.
//
const char * // O - Password string or @code NULL@
password_cb(
const char *prompt, // I - Prompt string (not used)
http_t *http, // I - HTTP connection (not used)
const char *method, // I - HTTP method (not used)
const char *resource, // I - Resource path (not used)
void *user_data) // I - IPP test data
{
ipptool_test_t *test = (ipptool_test_t *)user_data;
// IPP test data
const char *uriuser = ippFileGetVar(test->parent, "uriuser"),
// Username
*uripassword = ippFileGetVar(test->parent, "uripassword");
// Password
(void)prompt;
(void)http;
(void)method;
(void)resource;
if (uriuser && uripassword && test->password_tries < 3)
{
test->password_tries ++;
cupsSetUser(uriuser);
return (uripassword);
}
else
{
return (NULL);
}
}
//
// 'pause_message()' - Display the message and pause until the user presses a key.
//
static void
pause_message(const char *message) // I - Message
{
#ifdef _WIN32
HANDLE tty; // Console handle
DWORD mode; // Console mode
char key; // Key press
DWORD bytes; // Bytes read for key press
// Disable input echo and set raw input...
if ((tty = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE)
return;
if (!GetConsoleMode(tty, &mode))
return;
if (!SetConsoleMode(tty, 0))
return;
#else
int tty; // /dev/tty - never read from stdin
struct termios original, // Original input mode
noecho; // No echo input mode
char key; // Current key press
// Disable input echo and set raw input...
if ((tty = open("/dev/tty", O_RDONLY)) < 0)
return;
if (tcgetattr(tty, &original))
{
close(tty);
return;
}
noecho = original;
noecho.c_lflag &= (tcflag_t)~(ICANON | ECHO | ECHOE | ISIG);
if (tcsetattr(tty, TCSAFLUSH, &noecho))
{
close(tty);
return;
}
#endif // _WIN32
// Display the prompt...
cupsFilePrintf(cupsFileStdout(), "\n%s\n\n---- PRESS ANY KEY ----", message);
#ifdef _WIN32
// Read a key...
ReadFile(tty, &key, 1, &bytes, NULL);
// Cleanup...
SetConsoleMode(tty, mode);
#else
// Read a key...
read(tty, &key, 1);
// Cleanup...
tcsetattr(tty, TCSAFLUSH, &original);
close(tty);
#endif // _WIN32
// Erase the "press any key" prompt...
cupsFilePuts(cupsFileStdout(), "\r \r");
}
//
// 'print_attr()' - Print an attribute on the screen.
//
static void
print_attr(cups_file_t *outfile, // I - Output file
ipptool_output_t output, // I - Output format
ipp_attribute_t *attr, // I - Attribute to print
ipp_tag_t *group) // IO - Current group
{
int i, // Looping var
count; // Number of values
ipp_attribute_t *colattr; // Collection attribute
if (output == IPPTOOL_OUTPUT_PLIST)
{
if (!ippGetName(attr) || (group && *group != ippGetGroupTag(attr)))
{
if (ippGetGroupTag(attr) != IPP_TAG_ZERO)
{
cupsFilePuts(outfile, "</dict>\n");
cupsFilePuts(outfile, "<dict>\n");
}
if (group)
*group = ippGetGroupTag(attr);
}
if (!ippGetName(attr))
return;
print_xml_string(outfile, "key", ippGetName(attr));
if ((count = ippGetCount(attr)) > 1)
cupsFilePuts(outfile, "<array>\n");
switch (ippGetValueTag(attr))
{
case IPP_TAG_INTEGER :
case IPP_TAG_ENUM :
for (i = 0; i < count; i ++)
cupsFilePrintf(outfile, "<integer>%d</integer>\n", ippGetInteger(attr, i));
break;
case IPP_TAG_BOOLEAN :
for (i = 0; i < count; i ++)
cupsFilePuts(outfile, ippGetBoolean(attr, i) ? "<true />\n" : "<false />\n");
break;
case IPP_TAG_RANGE :
for (i = 0; i < count; i ++)
{
int lower, upper; // Lower and upper ranges
lower = ippGetRange(attr, i, &upper);
cupsFilePrintf(outfile, "<dict><key>lower</key><integer>%d</integer><key>upper</key><integer>%d</integer></dict>\n", lower, upper);
}
break;
case IPP_TAG_RESOLUTION :
for (i = 0; i < count; i ++)
{
int xres, yres; // Resolution values
ipp_res_t units; // Resolution units
xres = ippGetResolution(attr, i, &yres, &units);
cupsFilePrintf(outfile, "<dict><key>xres</key><integer>%d</integer><key>yres</key><integer>%d</integer><key>units</key><string>%s</string></dict>\n", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
}
break;
case IPP_TAG_DATE :
for (i = 0; i < count; i ++)
cupsFilePrintf(outfile, "<date>%s</date>\n", iso_date(ippGetDate(attr, i)));
break;
case IPP_TAG_STRING :
for (i = 0; i < count; i ++)
{
int datalen; // Length of data
void *data = ippGetOctetString(attr, i, &datalen);
// Data
char buffer[IPP_MAX_LENGTH * 5 / 4 + 1];
// Base64 output buffer
cupsFilePrintf(outfile, "<data>%s</data>\n", httpEncode64_3(buffer, sizeof(buffer), data, (size_t)datalen, false));
}
break;
case IPP_TAG_TEXT :
case IPP_TAG_NAME :
case IPP_TAG_KEYWORD :
case IPP_TAG_URI :
case IPP_TAG_URISCHEME :
case IPP_TAG_CHARSET :
case IPP_TAG_LANGUAGE :
case IPP_TAG_MIMETYPE :
for (i = 0; i < count; i ++)
print_xml_string(outfile, "string", ippGetString(attr, i, NULL));
break;
case IPP_TAG_TEXTLANG :
case IPP_TAG_NAMELANG :
for (i = 0; i < count; i ++)
{
const char *s, // String
*lang; // Language
s = ippGetString(attr, i, &lang);
cupsFilePuts(outfile, "<dict><key>language</key><string>");
print_xml_string(outfile, NULL, lang);
cupsFilePuts(outfile, "</string><key>string</key><string>");
print_xml_string(outfile, NULL, s);
cupsFilePuts(outfile, "</string></dict>\n");
}
break;
case IPP_TAG_BEGIN_COLLECTION :
for (i = 0; i < count; i ++)
{
ipp_t *col = ippGetCollection(attr, i);
// Collection value
cupsFilePuts(outfile, "<dict>\n");
for (colattr = ippGetFirstAttribute(col); colattr; colattr = ippGetNextAttribute(col))
print_attr(outfile, output, colattr, NULL);
cupsFilePuts(outfile, "</dict>\n");
}
break;
default :
cupsFilePrintf(outfile, "<string><<%s>></string>\n", ippTagString(ippGetValueTag(attr)));
break;
}
if (count > 1)
cupsFilePuts(outfile, "</array>\n");
}
else
{
size_t attrsize; // Size of current attribute
static char *buffer = NULL; // Value buffer
static size_t bufsize = 0; // Current size of value buffer
if (!buffer)
{
bufsize = 65536;
buffer = malloc(bufsize);
}
if (output == IPPTOOL_OUTPUT_TEST)
{
if (!ippGetName(attr))
{
cupsFilePuts(outfile, " -- separator --\n");
return;
}
cupsFilePrintf(outfile, " %s (%s%s) = ", ippGetName(attr), ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)));
}
if ((attrsize = ippAttributeString(attr, buffer, bufsize)) >= bufsize)
{
// Expand attribute value buffer...
char *temp = realloc(buffer, attrsize + 1);
// New buffer pointer
if (temp)
{
buffer = temp;
bufsize = attrsize + 1;
ippAttributeString(attr, buffer, bufsize);
}
}
cupsFilePrintf(outfile, "%s\n", buffer);
}
}
//
// 'print_csv()' - Print a line of CSV text.
//
static ipp_attribute_t * // O - Next attribute
print_csv(
ipptool_test_t *data, // I - Test data
ipp_t *ipp, // I - Response message
ipp_attribute_t *attr, // I - First attribute for line
int num_displayed, // I - Number of attributes to display
char **displayed, // I - Attributes to display
int *widths) // I - Column widths
{
int i; // Looping var
int maxlength; // Max length of all columns
ipp_attribute_t *current = attr; // Current attribute
char *values[MAX_DISPLAY], // Strings to display
*valptr; // Pointer into value
// Get the maximum string length we have to show and allocate...
for (i = 1, maxlength = widths[0]; i < num_displayed; i ++)
{
if (widths[i] > maxlength)
maxlength = widths[i];
}
maxlength += 2;
// Loop through the attributes to display...
if (attr)
{
// Collect the values...
memset(values, 0, sizeof(values));
for (; current; current = ippGetNextAttribute(ipp))
{
if (!ippGetName(current))
break;
for (i = 0; i < num_displayed; i ++)
{
if (!strcmp(ippGetName(current), displayed[i]))
{
if ((values[i] = (char *)calloc(1, (size_t)maxlength)) != NULL)
ippAttributeString(current, values[i], (size_t)maxlength);
break;
}
}
}
// Output the line...
for (i = 0; i < num_displayed; i ++)
{
if (i)
cupsFilePutChar(data->outfile, ',');
if (!values[i])
continue;
if (strchr(values[i], ',') != NULL || strchr(values[i], '\"') != NULL || strchr(values[i], '\\') != NULL)
{
// Quoted value...
cupsFilePutChar(data->outfile, '\"');
for (valptr = values[i]; *valptr; valptr ++)
{
if (*valptr == '\\' || *valptr == '\"')
cupsFilePutChar(data->outfile, '\\');
cupsFilePutChar(data->outfile, *valptr);
}
cupsFilePutChar(data->outfile, '\"');
}
else
{
// Unquoted value...
cupsFilePuts(data->outfile, values[i]);
}
free(values[i]);
}
cupsFilePutChar(data->outfile, '\n');
}
else
{
// Show column headings...
for (i = 0; i < num_displayed; i ++)
{
if (i)
cupsFilePutChar(data->outfile, ',');
cupsFilePuts(data->outfile, displayed[i]);
}
cupsFilePutChar(data->outfile, '\n');
}
return (current);
}
//
// 'print_fatal_error()' - Print a fatal error message.
//
static void
print_fatal_error(
ipptool_test_t *data, // I - Test data
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 (data->output == IPPTOOL_OUTPUT_PLIST)
{
print_xml_header(data);
print_xml_trailer(data, 0, buffer);
}
_cupsLangPrintf(stderr, "ipptool: %s", buffer);
}
//
// 'print_ippserver_attr()' - Print a attribute suitable for use by ippserver.
//
static void
print_ippserver_attr(
ipptool_test_t *data, // I - Test data
ipp_attribute_t *attr, // I - Attribute to print
int indent) // I - Indentation level
{
int i, // Looping var
count = ippGetCount(attr);
// Number of values
ipp_attribute_t *colattr; // Collection attribute
if (indent == 0)
cupsFilePrintf(data->outfile, "ATTR %s %s", ippTagString(ippGetValueTag(attr)), ippGetName(attr));
else
cupsFilePrintf(data->outfile, "%*sMEMBER %s %s", indent, "", ippTagString(ippGetValueTag(attr)), ippGetName(attr));
switch (ippGetValueTag(attr))
{
case IPP_TAG_INTEGER :
case IPP_TAG_ENUM :
for (i = 0; i < count; i ++)
cupsFilePrintf(data->outfile, "%s%d", i ? "," : " ", ippGetInteger(attr, i));
break;
case IPP_TAG_BOOLEAN :
cupsFilePuts(data->outfile, ippGetBoolean(attr, 0) ? " true" : " false");
for (i = 1; i < count; i ++)
cupsFilePuts(data->outfile, ippGetBoolean(attr, 1) ? ",true" : ",false");
break;
case IPP_TAG_RANGE :
for (i = 0; i < count; i ++)
{
int upper, lower = ippGetRange(attr, i, &upper);
cupsFilePrintf(data->outfile, "%s%d-%d", i ? "," : " ", lower, upper);
}
break;
case IPP_TAG_RESOLUTION :
for (i = 0; i < count; i ++)
{
ipp_res_t units;
int yres, xres = ippGetResolution(attr, i, &yres, &units);
cupsFilePrintf(data->outfile, "%s%dx%d%s", i ? "," : " ", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
}
break;
case IPP_TAG_DATE :
for (i = 0; i < count; i ++)
cupsFilePrintf(data->outfile, "%s%s", i ? "," : " ", iso_date(ippGetDate(attr, i)));
break;
case IPP_TAG_STRING :
for (i = 0; i < count; i ++)
{
int len;
const char *s = (const char *)ippGetOctetString(attr, i, &len);
cupsFilePuts(data->outfile, i ? "," : " ");
print_ippserver_string(data, s, (size_t)len);
}
break;
case IPP_TAG_TEXT :
case IPP_TAG_TEXTLANG :
case IPP_TAG_NAME :
case IPP_TAG_NAMELANG :
case IPP_TAG_KEYWORD :
case IPP_TAG_URI :
case IPP_TAG_URISCHEME :
case IPP_TAG_CHARSET :
case IPP_TAG_LANGUAGE :
case IPP_TAG_MIMETYPE :
for (i = 0; i < count; i ++)
{
const char *s = ippGetString(attr, i, NULL);
cupsFilePuts(data->outfile, i ? "," : " ");
print_ippserver_string(data, s, strlen(s));
}
break;
case IPP_TAG_BEGIN_COLLECTION :
for (i = 0; i < count; i ++)
{
ipp_t *col = ippGetCollection(attr, i);
cupsFilePuts(data->outfile, i ? ",{\n" : " {\n");
for (colattr = ippGetFirstAttribute(col); colattr; colattr = ippGetNextAttribute(col))
print_ippserver_attr(data, colattr, indent + 4);
cupsFilePrintf(data->outfile, "%*s}", indent, "");
}
break;
default :
// Out-of-band value
break;
}
cupsFilePuts(data->outfile, "\n");
}
//
// 'print_ippserver_string()' - Print a string suitable for use by ippserver.
//
static void
print_ippserver_string(
ipptool_test_t *data, // I - Test data
const char *s, // I - String to print
size_t len) // I - Length of string
{
cupsFilePutChar(data->outfile, '\"');
while (len > 0)
{
if (*s == '\"' || *s == '\\')
cupsFilePutChar(data->outfile, '\\');
cupsFilePutChar(data->outfile, *s);
s ++;
len --;
}
cupsFilePutChar(data->outfile, '\"');
}
//
// 'print_json_attr()' - Print an attribute in JSON format.
//
static void
print_json_attr(
ipptool_test_t *data, // I - Test data
ipp_attribute_t *attr, // I - IPP attribute
int indent) // I - Indentation
{
const char *name = ippGetName(attr);
// Name of attribute
int i, // Looping var
count = ippGetCount(attr);
// Number of values
ipp_attribute_t *colattr; // Collection attribute
cupsFilePrintf(data->outfile, "%*s", indent, "");
print_json_string(data, name, strlen(name));
switch (ippGetValueTag(attr))
{
case IPP_TAG_INTEGER :
case IPP_TAG_ENUM :
if (count == 1)
{
cupsFilePrintf(data->outfile, ": %d", ippGetInteger(attr, 0));
}
else
{
cupsFilePuts(data->outfile, ": [\n");
for (i = 0; i < count; i ++)
cupsFilePrintf(data->outfile, "%*s%d%s", indent + 4, "", ippGetInteger(attr, i), (i + 1) < count ? ",\n" : "\n");
cupsFilePrintf(data->outfile, "%*s]", indent, "");
}
break;
case IPP_TAG_BOOLEAN :
if (count == 1)
{
cupsFilePrintf(data->outfile, ": %s", ippGetBoolean(attr, 0) ? "true" : "false");
}
else
{
cupsFilePuts(data->outfile, ": [\n");
for (i = 0; i < count; i ++)
cupsFilePrintf(data->outfile, "%*s%s%s", indent + 4, "", ippGetBoolean(attr, i) ? "true" : "false", (i + 1) < count ? ",\n" : "\n");
cupsFilePrintf(data->outfile, "%*s]", indent, "");
}
break;
case IPP_TAG_RANGE :
if (count == 1)
{
int upper, lower = ippGetRange(attr, 0, &upper);
cupsFilePrintf(data->outfile, ": {\n%*s\"lower\": %d,\n%*s\"upper\": %d\n%*s}", indent + 4, "", lower, indent + 4, "", upper, indent, "");
}
else
{
cupsFilePuts(data->outfile, ": [\n");
for (i = 0; i < count; i ++)
{
int upper, lower = ippGetRange(attr, i, &upper);
cupsFilePrintf(data->outfile, "%*s{\n%*s\"lower\": %d,\n%*s\"upper\": %d\n%*s},\n", indent + 4, "", indent + 8, "", lower, indent + 8, "", upper, indent + 4, "");
}
cupsFilePrintf(data->outfile, "%*s]", indent, "");
}
break;
case IPP_TAG_RESOLUTION :
if (count == 1)
{
ipp_res_t units;
int yres, xres = ippGetResolution(attr, 0, &yres, &units);
cupsFilePrintf(data->outfile, ": {\n%*s\"units\": \"%s\",\n%*s\"xres\": %d,\n%*s\"yres\": %d\n%*s}", indent + 4, "", units == IPP_RES_PER_INCH ? "dpi" : "dpcm", indent + 4, "", xres, indent + 4, "", yres, indent, "");
}
else
{
cupsFilePuts(data->outfile, ": [\n");
for (i = 0; i < count; i ++)
{
ipp_res_t units;
int yres, xres = ippGetResolution(attr, i, &yres, &units);
cupsFilePrintf(data->outfile, "%*s{\n%*s\"units\": \"%s\",\n%*s\"xres\": %d,\n%*s\"yres\": %d\n%*s},\n", indent + 4, "", indent + 8, "", units == IPP_RES_PER_INCH ? "dpi" : "dpcm", indent + 8, "", xres, indent + 8, "", yres, indent + 4, "");
}
cupsFilePrintf(data->outfile, "%*s]", indent, "");
}
break;
case IPP_TAG_DATE :
if (count == 1)
{
cupsFilePrintf(data->outfile, ": \"%s\"", iso_date(ippGetDate(attr, 0)));
}
else
{
cupsFilePuts(data->outfile, ": [\n");
for (i = 0; i < count; i ++)
cupsFilePrintf(data->outfile, "%*s\"%s\"%s", indent + 4, "", iso_date(ippGetDate(attr, i)), (i + 1) < count ? ",\n" : "\n");
cupsFilePrintf(data->outfile, "%*s]", indent, "");
}
break;
case IPP_TAG_STRING :
if (count == 1)
{
int len;
const char *s = (const char *)ippGetOctetString(attr, 0, &len);
cupsFilePuts(data->outfile, ": \"");
while (len > 0)
{
cupsFilePrintf(data->outfile, "%02X", *s++ & 255);
len --;
}
cupsFilePuts(data->outfile, "\"");
}
else
{
cupsFilePuts(data->outfile, ": [\n");
for (i = 0; i < count; i ++)
{
int len;
const char *s = (const char *)ippGetOctetString(attr, i, &len);
cupsFilePrintf(data->outfile, "%*s\"", indent + 4, "");
while (len > 0)
{
cupsFilePrintf(data->outfile, "%02X", *s++ & 255);
len --;
}
cupsFilePuts(data->outfile, (i + 1) < count ? "\",\n" : "\"\n");
}
cupsFilePrintf(data->outfile, "%*s]", indent, "");
}
break;
case IPP_TAG_TEXT :
case IPP_TAG_TEXTLANG :
case IPP_TAG_NAME :
case IPP_TAG_NAMELANG :
case IPP_TAG_KEYWORD :
case IPP_TAG_URI :
case IPP_TAG_URISCHEME :
case IPP_TAG_CHARSET :
case IPP_TAG_LANGUAGE :
case IPP_TAG_MIMETYPE :
if (count == 1)
{
const char *s = ippGetString(attr, 0, NULL);
cupsFilePuts(data->outfile, ": ");
print_json_string(data, s, strlen(s));
}
else
{
cupsFilePuts(data->outfile, ": [\n");
for (i = 0; i < count; i ++)
{
const char *s = ippGetString(attr, i, NULL);
cupsFilePrintf(data->outfile, "%*s", indent + 4, "");
print_json_string(data, s, strlen(s));
cupsFilePuts(data->outfile, (i + 1) < count ? ",\n" : "\n");
}
cupsFilePrintf(data->outfile, "%*s]", indent, "");
}
break;
case IPP_TAG_BEGIN_COLLECTION :
if (count == 1)
{
ipp_t *col = ippGetCollection(attr, 0);
cupsFilePuts(data->outfile, ": {\n");
colattr = ippGetFirstAttribute(col);
while (colattr)
{
print_json_attr(data, colattr, indent + 4);
colattr = ippGetNextAttribute(col);
cupsFilePuts(data->outfile, colattr ? ",\n" : "\n");
}
cupsFilePrintf(data->outfile, "%*s}", indent, "");
}
else
{
cupsFilePuts(data->outfile, ": [\n");
for (i = 0; i < count; i ++)
{
ipp_t *col = ippGetCollection(attr, i);
cupsFilePrintf(data->outfile, "%*s{\n", indent + 4, "");
colattr = ippGetFirstAttribute(col);
while (colattr)
{
print_json_attr(data, colattr, indent + 8);
colattr = ippGetNextAttribute(col);
cupsFilePuts(data->outfile, colattr ? ",\n" : "\n");
}
cupsFilePrintf(data->outfile, "%*s}%s", indent + 4, "", (i + 1) < count ? ",\n" : "\n");
}
cupsFilePrintf(data->outfile, "%*s]", indent, "");
}
break;
default :
// Out-of-band value
cupsFilePrintf(data->outfile, ": null");
break;
}
}
//
// 'print_json_string()' - Print a string in JSON format.
//
static void
print_json_string(
ipptool_test_t *data, // I - Test data
const char *s, // I - String to print
size_t len) // I - Length of string
{
cupsFilePutChar(data->outfile, '\"');
while (len > 0)
{
switch (*s)
{
case '\"' :
case '\\' :
cupsFilePutChar(data->outfile, '\\');
cupsFilePutChar(data->outfile, *s);
break;
case '\n' :
cupsFilePuts(data->outfile, "\\n");
break;
case '\r' :
cupsFilePuts(data->outfile, "\\r");
break;
case '\t' :
cupsFilePuts(data->outfile, "\\t");
break;
default :
if (_cups_iscntrl(*s))
cupsFilePrintf(data->outfile, "\\%03o", *s);
else
cupsFilePutChar(data->outfile, *s);
break;
}
s ++;
len --;
}
cupsFilePutChar(data->outfile, '\"');
}
//
// 'print_line()' - Print a line of formatted or CSV text.
//
static ipp_attribute_t * // O - Next attribute
print_line(
ipptool_test_t *data, // I - Test data
ipp_t *ipp, // I - Response message
ipp_attribute_t *attr, // I - First attribute for line
int num_displayed, // I - Number of attributes to display
char **displayed, // I - Attributes to display
int *widths) // I - Column widths
{
int i; // Looping var
int maxlength; // Max length of all columns
ipp_attribute_t *current = attr; // Current attribute
char *values[MAX_DISPLAY]; // Strings to display
// Get the maximum string length we have to show and allocate...
for (i = 1, maxlength = widths[0]; i < num_displayed; i ++)
{
if (widths[i] > maxlength)
maxlength = widths[i];
}
maxlength += 2;
// Loop through the attributes to display...
if (attr)
{
// Collect the values...
memset(values, 0, sizeof(values));
for (; current; current = ippGetNextAttribute(ipp))
{
if (!ippGetName(current))
break;
for (i = 0; i < num_displayed; i ++)
{
if (!strcmp(ippGetName(current), displayed[i]))
{
if ((values[i] = (char *)calloc(1, (size_t)maxlength)) != NULL)
ippAttributeString(current, values[i], (size_t)maxlength);
break;
}
}
}
// Output the line...
for (i = 0; i < num_displayed; i ++)
{
if (i)
cupsFilePutChar(data->outfile, ' ');
cupsFilePrintf(data->outfile, "%*s", (int)-widths[i], values[i] ? values[i] : "");
free(values[i]);
}
cupsFilePutChar(data->outfile, '\n');
}
else
{
// Show column headings...
char *buffer = (char *)malloc((size_t)maxlength);
// Buffer for separator lines
if (!buffer)
return (current);
for (i = 0; i < num_displayed; i ++)
{
if (i)
cupsFilePutChar(data->outfile, ' ');
cupsFilePrintf(data->outfile, "%*s", (int)-widths[i], displayed[i]);
}
cupsFilePutChar(data->outfile, '\n');
for (i = 0; i < num_displayed; i ++)
{
if (i)
cupsFilePutChar(data->outfile, ' ');
memset(buffer, '-', widths[i]);
buffer[widths[i]] = '\0';
cupsFilePuts(data->outfile, buffer);
}
cupsFilePutChar(data->outfile, '\n');
free(buffer);
}
return (current);
}
//
// 'print_xml_header()' - Print a standard XML plist header.
//
static void
print_xml_header(ipptool_test_t *data) // I - Test data
{
if (!data->xml_header)
{
cupsFilePuts(data->outfile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
cupsFilePuts(data->outfile, "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n");
cupsFilePuts(data->outfile, "<plist version=\"1.0\">\n");
cupsFilePuts(data->outfile, "<dict>\n");
cupsFilePuts(data->outfile, "<key>ipptoolVersion</key>\n");
cupsFilePuts(data->outfile, "<string>" CUPS_SVERSION "</string>\n");
cupsFilePuts(data->outfile, "<key>Transfer</key>\n");
cupsFilePrintf(data->outfile, "<string>%s</string>\n", data->transfer == IPPTOOL_TRANSFER_AUTO ? "auto" : data->transfer == IPPTOOL_TRANSFER_CHUNKED ? "chunked" : "length");
cupsFilePuts(data->outfile, "<key>Tests</key>\n");
cupsFilePuts(data->outfile, "<array>\n");
data->xml_header = 1;
}
}
//
// 'print_xml_string()' - Print an XML string with escaping.
//
static void
print_xml_string(cups_file_t *outfile, // I - Test data
const char *element, // I - Element name or NULL
const char *s) // I - String to print
{
if (element)
cupsFilePrintf(outfile, "<%s>", element);
while (*s)
{
if (*s == '&')
{
cupsFilePuts(outfile, "&");
}
else if (*s == '<')
{
cupsFilePuts(outfile, "<");
}
else if (*s == '>')
{
cupsFilePuts(outfile, ">");
}
else if ((*s & 0xe0) == 0xc0)
{
// Validate UTF-8 two-byte sequence...
if ((s[1] & 0xc0) != 0x80)
{
cupsFilePutChar(outfile, '?');
s ++;
}
else
{
cupsFilePutChar(outfile, *s++);
cupsFilePutChar(outfile, *s);
}
}
else if ((*s & 0xf0) == 0xe0)
{
// Validate UTF-8 three-byte sequence...
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80)
{
cupsFilePutChar(outfile, '?');
s += 2;
}
else
{
cupsFilePutChar(outfile, *s++);
cupsFilePutChar(outfile, *s++);
cupsFilePutChar(outfile, *s);
}
}
else if ((*s & 0xf8) == 0xf0)
{
// Validate UTF-8 four-byte sequence...
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || (s[3] & 0xc0) != 0x80)
{
cupsFilePutChar(outfile, '?');
s += 3;
}
else
{
cupsFilePutChar(outfile, *s++);
cupsFilePutChar(outfile, *s++);
cupsFilePutChar(outfile, *s++);
cupsFilePutChar(outfile, *s);
}
}
else if ((*s & 0x80) || (_cups_iscntrl(*s) && !_cups_isspace(*s)))
{
// Invalid control character...
cupsFilePutChar(outfile, '?');
}
else
{
cupsFilePutChar(outfile, *s);
}
s ++;
}
if (element)
cupsFilePrintf(outfile, "</%s>\n", element);
}
//
// 'print_xml_trailer()' - Print the XML trailer with success/fail value.
//
static void
print_xml_trailer(
ipptool_test_t *data, // I - Test data
int success, // I - 1 on success, 0 on failure
const char *message) // I - Error message or NULL
{
if (data->xml_header)
{
cupsFilePuts(data->outfile, "</array>\n");
cupsFilePuts(data->outfile, "<key>Successful</key>\n");
cupsFilePuts(data->outfile, success ? "<true />\n" : "<false />\n");
if (message)
{
cupsFilePuts(data->outfile, "<key>ErrorMessage</key>\n");
print_xml_string(data->outfile, "string", message);
}
cupsFilePuts(data->outfile, "</dict>\n");
cupsFilePuts(data->outfile, "</plist>\n");
data->xml_header = 0;
}
}
//
// 'set_client_certificate()' - Set the client certificate and private key.
//
static void
set_client_certificate(
ipptool_test_t *data) // I - Test data
{
char *creds, // Public key/certificate
*key; // Private key
// Copy and set the client credentials for the given name...
creds = cupsCopyCredentials(/*path*/NULL, data->client_name);
key = cupsCopyCredentials(/*path*/NULL, data->client_name);
cupsSetClientCredentials(creds, key);
free(creds);
free(key);
}
#ifndef _WIN32
//
// 'sigterm_handler()' - Handle SIGINT and SIGTERM.
//
static void
sigterm_handler(int sig) // I - Signal number (unused)
{
(void)sig;
Cancel = true;
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
}
#endif // !_WIN32
//
// 'timeout_cb()' - Handle HTTP timeouts.
//
static int // O - 1 to continue, 0 to cancel
timeout_cb(http_t *http, // I - Connection to server
void *user_data) // I - User data (unused)
{
int buffered = 0; // Bytes buffered but not yet sent
(void)user_data;
// If the socket still have data waiting to be sent to the printer (as can
// happen if the printer runs out of paper), continue to wait until the output
// buffer is empty...
#ifdef SO_NWRITE // macOS and some versions of Linux
socklen_t len = sizeof(buffered); // Size of return value
if (getsockopt(httpGetFd(http), SOL_SOCKET, SO_NWRITE, &buffered, &len))
buffered = 0;
#elif defined(SIOCOUTQ) // Others except Windows
if (ioctl(httpGetFd(http), SIOCOUTQ, &buffered))
buffered = 0;
#else // Windows (not possible)
(void)http;
#endif // SO_NWRITE
return (buffered > 0);
}
//
// 'token_cb()' - Parse test file-specific tokens and run tests.
//
static bool // O - `true` to continue, `false` to stop
token_cb(ipp_file_t *f, // I - IPP file data
ipptool_test_t *data, // I - Test data
const char *token) // I - Current token
{
char name[1024], // Name string
temp[1024], // Temporary string
value[1024], // Value string
*ptr; // Pointer into value
if (getenv("IPPTOOL_DEBUG"))
fprintf(stderr, "ipptool: token='%s'\n", token);
if (ippFileGetAttributes(f))
{
// Parse until we see a close brace...
if (_cups_strcasecmp(token, "COUNT") &&
_cups_strcasecmp(token, "DEFINE-MATCH") &&
_cups_strcasecmp(token, "DEFINE-NO-MATCH") &&
_cups_strcasecmp(token, "DEFINE-VALUE") &&
_cups_strcasecmp(token, "DISPLAY-MATCH") &&
_cups_strcasecmp(token, "IF-DEFINED") &&
_cups_strcasecmp(token, "IF-NOT-DEFINED") &&
_cups_strcasecmp(token, "IN-GROUP") &&
_cups_strcasecmp(token, "OF-TYPE") &&
_cups_strcasecmp(token, "REPEAT-LIMIT") &&
_cups_strcasecmp(token, "REPEAT-MATCH") &&
_cups_strcasecmp(token, "REPEAT-NO-MATCH") &&
_cups_strcasecmp(token, "SAME-COUNT-AS") &&
_cups_strcasecmp(token, "SAVE-ALL-CONTENT") &&
_cups_strcasecmp(token, "SAVE-CONTENT") &&
_cups_strcasecmp(token, "WITH-ALL-CONTENT") &&
_cups_strcasecmp(token, "WITH-ALL-MIME-TYPES") &&
_cups_strcasecmp(token, "WITH-ALL-VALUES") &&
_cups_strcasecmp(token, "WITH-ALL-VALUES-FROM") &&
_cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") &&
_cups_strcasecmp(token, "WITH-ALL-RESOURCES") &&
_cups_strcasecmp(token, "WITH-ALL-SCHEMES") &&
_cups_strcasecmp(token, "WITH-CONTENT") &&
_cups_strcasecmp(token, "WITH-DISTINCT-VALUES") &&
_cups_strcasecmp(token, "WITH-HOSTNAME") &&
_cups_strcasecmp(token, "WITH-MIME-TYPES") &&
_cups_strcasecmp(token, "WITH-RESOURCE") &&
_cups_strcasecmp(token, "WITH-SCHEME") &&
_cups_strcasecmp(token, "WITH-VALUE") &&
_cups_strcasecmp(token, "WITH-VALUE-FROM"))
data->last_expect = NULL;
if (_cups_strcasecmp(token, "DEFINE-MATCH") &&
_cups_strcasecmp(token, "DEFINE-NO-MATCH") &&
_cups_strcasecmp(token, "IF-DEFINED") &&
_cups_strcasecmp(token, "IF-NOT-DEFINED") &&
_cups_strcasecmp(token, "REPEAT-LIMIT") &&
_cups_strcasecmp(token, "REPEAT-MATCH") &&
_cups_strcasecmp(token, "REPEAT-NO-MATCH"))
data->last_status = NULL;
if (!strcmp(token, "}"))
{
return (do_test(f, data));
}
else if (!strcmp(token, "GENERATE-FILE"))
{
if (data->generate_params)
{
print_fatal_error(data, "Extra GENERATE-FILE seen on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
else if (data->file[0])
{
print_fatal_error(data, "Cannot use GENERATE-FILE on line %d of '%s' with FILE.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
return (parse_generate_file(f, data));
}
else if (!strcmp(token, "MONITOR-PRINTER-STATE"))
{
if (data->monitor_uri)
{
print_fatal_error(data, "Extra MONITOR-PRINTER-STATE seen on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
return (parse_monitor_printer_state(f, data));
}
else if (!strcmp(token, "COMPRESSION"))
{
// COMPRESSION none
// COMPRESSION deflate
// COMPRESSION gzip
if (ippFileReadToken(f, temp, sizeof(temp)))
{
ippFileExpandVars(f, data->compression, temp, sizeof(data->compression));
if (strcmp(data->compression, "none") && strcmp(data->compression, "deflate") && strcmp(data->compression, "gzip"))
{
print_fatal_error(data, "Unsupported COMPRESSION value \"%s\" on line %d of '%s'.", data->compression, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (!strcmp(data->compression, "none"))
data->compression[0] = '\0';
}
else
{
print_fatal_error(data, "Missing COMPRESSION value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "DEFINE"))
{
// DEFINE name value
if (ippFileReadToken(f, name, sizeof(name)) && ippFileReadToken(f, temp, sizeof(temp)))
{
ippFileExpandVars(f, value, temp, sizeof(value));
ippFileSetVar(f, name, value);
}
else
{
print_fatal_error(data, "Missing DEFINE name and/or value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "IGNORE-ERRORS"))
{
// IGNORE-ERRORS yes
// IGNORE-ERRORS no
if (ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
{
data->ignore_errors = !_cups_strcasecmp(temp, "yes");
}
else
{
print_fatal_error(data, "Missing IGNORE-ERRORS value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "NAME"))
{
// Name of test...
if (ippFileReadToken(f, temp, sizeof(temp)))
{
ippFileExpandVars(f, data->name, temp, sizeof(data->name));
}
else
{
print_fatal_error(data, "Missing NAME string on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "PAUSE"))
{
// Pause with a message...
if (ippFileReadToken(f, temp, sizeof(temp)))
{
cupsCopyString(data->pause, temp, sizeof(data->pause));
}
else
{
print_fatal_error(data, "Missing PAUSE message on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "REQUEST-ID"))
{
// REQUEST-ID #
// REQUEST-ID random
if (ippFileReadToken(f, temp, sizeof(temp)))
{
if (_cups_isdigit(temp[0]))
{
data->request_id = atoi(temp) - 1;
}
else if (!_cups_strcasecmp(temp, "random"))
{
data->request_id = (cupsGetRand() % 1000) * 137;
}
else
{
print_fatal_error(data, "Bad REQUEST-ID value \"%s\" on line %d of '%s'.", temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else
{
print_fatal_error(data, "Missing REQUEST-ID value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "PASS-IF-DEFINED"))
{
// PASS-IF-DEFINED variable
if (ippFileReadToken(f, name, sizeof(name)))
{
if (ippFileGetVar(f, name))
data->pass_test = 1;
}
else
{
print_fatal_error(data, "Missing PASS-IF-DEFINED value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "PASS-IF-NOT-DEFINED"))
{
// PASS-IF-NOT-DEFINED variable
if (ippFileReadToken(f, name, sizeof(name)))
{
if (!ippFileGetVar(f, name))
data->pass_test = 1;
}
else
{
print_fatal_error(data, "Missing PASS-IF-NOT-DEFINED value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "SKIP-IF-DEFINED"))
{
// SKIP-IF-DEFINED variable
if (ippFileReadToken(f, name, sizeof(name)))
{
if (ippFileGetVar(f, name) || getenv(name))
data->skip_test = true;
}
else
{
print_fatal_error(data, "Missing SKIP-IF-DEFINED value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "SKIP-IF-MISSING"))
{
// SKIP-IF-MISSING filename
if (ippFileReadToken(f, temp, sizeof(temp)))
{
char filename[1024]; // Filename
ippFileExpandVars(f, value, temp, sizeof(value));
get_filename(ippFileGetFilename(f), filename, temp, sizeof(filename));
if (access(filename, R_OK))
data->skip_test = true;
}
else
{
print_fatal_error(data, "Missing SKIP-IF-MISSING filename on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "SKIP-IF-NOT-DEFINED"))
{
// SKIP-IF-NOT-DEFINED variable
if (ippFileReadToken(f, name, sizeof(name)))
{
if (!ippFileGetVar(f, name) && !getenv(name))
data->skip_test = true;
}
else
{
print_fatal_error(data, "Missing SKIP-IF-NOT-DEFINED value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "SKIP-PREVIOUS-ERROR"))
{
// SKIP-PREVIOUS-ERROR yes
// SKIP-PREVIOUS-ERROR no
if (ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
{
data->skip_previous = !_cups_strcasecmp(temp, "yes");
}
else
{
print_fatal_error(data, "Missing SKIP-PREVIOUS-ERROR value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "TEST-ID"))
{
// TEST-ID "string"
if (ippFileReadToken(f, temp, sizeof(temp)))
{
ippFileExpandVars(f, data->test_id, temp, sizeof(data->test_id));
}
else
{
print_fatal_error(data, "Missing TEST-ID value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "TRANSFER"))
{
// TRANSFER auto
// TRANSFER chunked
// TRANSFER length
if (ippFileReadToken(f, temp, sizeof(temp)))
{
if (!strcmp(temp, "auto"))
{
data->transfer = IPPTOOL_TRANSFER_AUTO;
}
else if (!strcmp(temp, "chunked"))
{
data->transfer = IPPTOOL_TRANSFER_CHUNKED;
}
else if (!strcmp(temp, "length"))
{
data->transfer = IPPTOOL_TRANSFER_LENGTH;
}
else
{
print_fatal_error(data, "Bad TRANSFER value \"%s\" on line %d of '%s'.", temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else
{
print_fatal_error(data, "Missing TRANSFER value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "VERSION"))
{
if (ippFileReadToken(f, temp, sizeof(temp)))
{
if (!strcmp(temp, "0.0"))
{
data->version = 0;
}
else if (!strcmp(temp, "1.0"))
{
data->version = 10;
}
else if (!strcmp(temp, "1.1"))
{
data->version = 11;
}
else if (!strcmp(temp, "2.0"))
{
data->version = 20;
}
else if (!strcmp(temp, "2.1"))
{
data->version = 21;
}
else if (!strcmp(temp, "2.2"))
{
data->version = 22;
}
else
{
print_fatal_error(data, "Bad VERSION \"%s\" on line %d of '%s'.", temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else
{
print_fatal_error(data, "Missing VERSION number on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "RESOURCE"))
{
// Resource name...
if (!ippFileReadToken(f, data->resource, sizeof(data->resource)))
{
print_fatal_error(data, "Missing RESOURCE path on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "OPERATION"))
{
// Operation...
ipp_op_t op; // Operation code
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing OPERATION code on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
ippFileExpandVars(f, value, temp, sizeof(value));
if ((op = ippOpValue(value)) == (ipp_op_t)-1 && (op = (ipp_op_t)strtol(value, NULL, 0)) == 0)
{
print_fatal_error(data, "Bad OPERATION code \"%s\" on line %d of '%s'.", temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
data->op = op;
}
else if (!_cups_strcasecmp(token, "DELAY"))
{
// Delay before operation...
double dval; // Delay value
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing DELAY value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
ippFileExpandVars(f, value, temp, sizeof(value));
if ((dval = _cupsStrScand(value, &ptr, localeconv())) < 0.0 || (*ptr && *ptr != ','))
{
print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
data->delay = (useconds_t)(1000000.0 * dval);
if (*ptr == ',')
{
if ((dval = _cupsStrScand(ptr + 1, &ptr, localeconv())) <= 0.0 || *ptr)
{
print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of '%s'.", value, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
data->repeat_interval = (useconds_t)(1000000.0 * dval);
}
else
{
data->repeat_interval = data->delay;
}
}
else if (!_cups_strcasecmp(token, "FILE"))
{
// File...
if (data->file[0])
{
print_fatal_error(data, "Extra FILE seen on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
else if (data->generate_params)
{
print_fatal_error(data, "Cannot use FILE on line %d of '%s' with GENERATE-FILE.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing FILE filename on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
ippFileExpandVars(f, value, temp, sizeof(value));
get_filename(ippFileGetFilename(f), data->file, value, sizeof(data->file));
if (access(data->file, R_OK))
{
print_fatal_error(data, "Filename \"%s\" (mapped to \"%s\") on line %d of '%s' cannot be read.", value, data->file, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "STATUS"))
{
// Status...
if (data->num_statuses >= (int)(sizeof(data->statuses) / sizeof(data->statuses[0])))
{
print_fatal_error(data, "Too many STATUS's on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing STATUS code on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if ((data->statuses[data->num_statuses].status = ippErrorValue(temp)) == (ipp_status_t)-1 && (data->statuses[data->num_statuses].status = (ipp_status_t)strtol(temp, NULL, 0)) == 0)
{
print_fatal_error(data, "Bad STATUS code \"%s\" on line %d of '%s'.", temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
data->last_status = data->statuses + data->num_statuses;
data->num_statuses ++;
data->last_status->define_match = NULL;
data->last_status->define_no_match = NULL;
data->last_status->if_defined = NULL;
data->last_status->if_not_defined = NULL;
data->last_status->repeat_limit = 1000;
data->last_status->repeat_match = 0;
data->last_status->repeat_no_match = 0;
}
else if (!_cups_strcasecmp(token, "EXPECT") || !_cups_strcasecmp(token, "EXPECT-ALL"))
{
// Expected attributes...
int expect_all = !_cups_strcasecmp(token, "EXPECT-ALL");
if (data->num_expects >= (int)(sizeof(data->expects) / sizeof(data->expects[0])))
{
print_fatal_error(data, "Too many EXPECT's on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (!ippFileReadToken(f, name, sizeof(name)))
{
print_fatal_error(data, "Missing EXPECT name on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
data->last_expect = data->expects + data->num_expects;
data->num_expects ++;
memset(data->last_expect, 0, sizeof(ipptool_expect_t));
data->last_expect->repeat_limit = 1000;
data->last_expect->expect_all = expect_all;
if (name[0] == '!')
{
data->last_expect->not_expect = 1;
data->last_expect->name = strdup(name + 1);
}
else if (name[0] == '?')
{
data->last_expect->optional = 1;
data->last_expect->name = strdup(name + 1);
}
else
data->last_expect->name = strdup(name);
}
else if (!_cups_strcasecmp(token, "COUNT"))
{
int count; // Count value
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing COUNT number on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if ((count = strtol(temp, NULL, 10)) > INT_MAX)
{
print_fatal_error(data, "Bad COUNT \"%s\" on line %d of '%s'.", temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
data->last_expect->count = count;
}
else
{
print_fatal_error(data, "COUNT without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "DEFINE-MATCH"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing DEFINE-MATCH variable on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
data->last_expect->define_match = strdup(temp);
}
else if (data->last_status)
{
data->last_status->define_match = strdup(temp);
}
else
{
print_fatal_error(data, "DEFINE-MATCH without a preceding EXPECT or STATUS on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "DEFINE-NO-MATCH"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing DEFINE-NO-MATCH variable on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
data->last_expect->define_no_match = strdup(temp);
}
else if (data->last_status)
{
data->last_status->define_no_match = strdup(temp);
}
else
{
print_fatal_error(data, "DEFINE-NO-MATCH without a preceding EXPECT or STATUS on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "DEFINE-VALUE"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing DEFINE-VALUE variable on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
data->last_expect->define_value = strdup(temp);
}
else
{
print_fatal_error(data, "DEFINE-VALUE without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "DISPLAY-MATCH"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing DISPLAY-MATCH mesaage on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
data->last_expect->display_match = strdup(temp);
}
else
{
print_fatal_error(data, "DISPLAY-MATCH without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "OF-TYPE"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing OF-TYPE value tag(s) on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
data->last_expect->of_type = strdup(temp);
}
else
{
print_fatal_error(data, "OF-TYPE without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "IN-GROUP"))
{
ipp_tag_t in_group; // IN-GROUP value
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing IN-GROUP group tag on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if ((in_group = ippTagValue(temp)) == IPP_TAG_ZERO || in_group >= IPP_TAG_UNSUPPORTED_VALUE)
{
print_fatal_error(data, "Bad IN-GROUP group tag \"%s\" on line %d of '%s'.", temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
else if (data->last_expect)
{
data->last_expect->in_group = in_group;
}
else
{
print_fatal_error(data, "IN-GROUP without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "REPEAT-LIMIT"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing REPEAT-LIMIT value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
else if (atoi(temp) <= 0)
{
print_fatal_error(data, "Bad REPEAT-LIMIT value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_status)
{
data->last_status->repeat_limit = atoi(temp);
}
else if (data->last_expect)
{
data->last_expect->repeat_limit = atoi(temp);
}
else
{
print_fatal_error(data, "REPEAT-LIMIT without a preceding EXPECT or STATUS on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "REPEAT-MATCH"))
{
if (data->last_status)
{
data->last_status->repeat_match = 1;
}
else if (data->last_expect)
{
data->last_expect->repeat_match = 1;
}
else
{
print_fatal_error(data, "REPEAT-MATCH without a preceding EXPECT or STATUS on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "REPEAT-NO-MATCH"))
{
if (data->last_status)
{
data->last_status->repeat_no_match = 1;
}
else if (data->last_expect)
{
data->last_expect->repeat_no_match = 1;
}
else
{
print_fatal_error(data, "REPEAT-NO-MATCH without a preceding EXPECT or STATUS on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "SAME-COUNT-AS"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing SAME-COUNT-AS name on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
data->last_expect->same_count_as = strdup(temp);
}
else
{
print_fatal_error(data, "SAME-COUNT-AS without a preceding EXPECT on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "SAVE-ALL-CONTENT") || !_cups_strcasecmp(token, "SAVE-CONTENT"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing %s filespec on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
data->last_expect->save_filespec = strdup(temp);
}
else
{
print_fatal_error(data, "%s without a preceding EXPECT on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "IF-DEFINED"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing IF-DEFINED name on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
if (data->last_expect->if_defined)
cupsArrayAddStrings(data->last_expect->if_defined, temp, ',');
else
data->last_expect->if_defined = cupsArrayNewStrings(temp, ',');
}
else if (data->last_status)
{
if (data->last_status->if_defined)
cupsArrayAddStrings(data->last_status->if_defined, temp, ',');
else
data->last_status->if_defined = cupsArrayNewStrings(temp, ',');
}
else
{
print_fatal_error(data, "IF-DEFINED without a preceding EXPECT or STATUS on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "IF-NOT-DEFINED"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing IF-NOT-DEFINED name on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
if (data->last_expect->if_not_defined)
cupsArrayAddStrings(data->last_expect->if_not_defined, temp, ',');
else
data->last_expect->if_not_defined = cupsArrayNewStrings(temp, ',');
}
else if (data->last_status)
{
if (data->last_status->if_not_defined)
cupsArrayAddStrings(data->last_status->if_not_defined, temp, ',');
else
data->last_status->if_not_defined = cupsArrayNewStrings(temp, ',');
}
else
{
print_fatal_error(data, "IF-NOT-DEFINED without a preceding EXPECT or STATUS on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "WITH-ALL-CONTENT") || !_cups_strcasecmp(token, "WITH-CONTENT"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing %s condition on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
if (!_cups_strcasecmp(temp, "available"))
{
data->last_expect->with_content = IPPTOOL_CONTENT_AVAILABLE;
}
else if (!_cups_strcasecmp(temp, "valid"))
{
data->last_expect->with_content = IPPTOOL_CONTENT_VALID;
}
else if (!_cups_strcasecmp(temp, "valid-icon"))
{
data->last_expect->with_content = IPPTOOL_CONTENT_VALID_ICON;
}
else
{
print_fatal_error(data, "Unsupported %s %s on line %d of '%s'.", token, temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else
{
print_fatal_error(data, "%s without a preceding EXPECT on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "WITH-ALL-MIME-TYPES") || !_cups_strcasecmp(token, "WITH-MIME-TYPES"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing %s MIME media type(s) on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
data->last_expect->with_mime_types = cupsArrayNewStrings(temp, ',');
}
else
{
print_fatal_error(data, "%s without a preceding EXPECT on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "WITH-DISTINCT-VALUES"))
{
if (data->last_expect)
{
data->last_expect->with_distinct = 1;
}
else
{
print_fatal_error(data, "%s without a preceding EXPECT on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "WITH-ALL-VALUES") ||
!_cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") ||
!_cups_strcasecmp(token, "WITH-ALL-RESOURCES") ||
!_cups_strcasecmp(token, "WITH-ALL-SCHEMES") ||
!_cups_strcasecmp(token, "WITH-HOSTNAME") ||
!_cups_strcasecmp(token, "WITH-RESOURCE") ||
!_cups_strcasecmp(token, "WITH-SCHEME") ||
!_cups_strcasecmp(token, "WITH-VALUE"))
{
if (data->last_expect)
{
if (!_cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") || !_cups_strcasecmp(token, "WITH-HOSTNAME"))
data->last_expect->with_flags = IPPTOOL_WITH_HOSTNAME;
else if (!_cups_strcasecmp(token, "WITH-ALL-RESOURCES") || !_cups_strcasecmp(token, "WITH-RESOURCE"))
data->last_expect->with_flags = IPPTOOL_WITH_RESOURCE;
else if (!_cups_strcasecmp(token, "WITH-ALL-SCHEMES") || !_cups_strcasecmp(token, "WITH-SCHEME"))
data->last_expect->with_flags = IPPTOOL_WITH_SCHEME;
if (!_cups_strncasecmp(token, "WITH-ALL-", 9))
data->last_expect->with_flags |= IPPTOOL_WITH_ALL;
}
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing %s value on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
// Read additional comma-delimited values - needed since legacy test files
// will have unquoted WITH-VALUE values with commas...
ptr = temp + strlen(temp);
for (;;)
{
ippFileSavePosition(f);
ptr += strlen(ptr);
if (!ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
break;
if (!strcmp(ptr, ","))
{
// Append a value...
ptr += strlen(ptr);
if (!ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
break;
}
else
{
// Not another value, stop here...
ippFileRestorePosition(f);
*ptr = '\0';
break;
}
}
if (data->last_expect)
{
// Expand any variables in the value and then save it.
ippFileExpandVars(f, value, temp, sizeof(value));
ptr = value + strlen(value) - 1;
if (value[0] == '/' && ptr > value && *ptr == '/')
{
// WITH-VALUE is a POSIX extended regular expression.
data->last_expect->with_value = calloc(1, (size_t)(ptr - value));
data->last_expect->with_flags |= IPPTOOL_WITH_REGEX;
if (data->last_expect->with_value)
memcpy(data->last_expect->with_value, value + 1, (size_t)(ptr - value - 1));
}
else
{
// WITH-VALUE is a literal value...
for (ptr = value; *ptr; ptr ++)
{
if (*ptr == '\\' && ptr[1])
{
// Remove \ from \foo...
_cups_strcpy(ptr, ptr + 1);
}
}
data->last_expect->with_value = strdup(value);
data->last_expect->with_flags |= IPPTOOL_WITH_LITERAL;
}
}
else
{
print_fatal_error(data, "%s without a preceding EXPECT on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "WITH-ALL-VALUES-FROM") ||
!_cups_strcasecmp(token, "WITH-VALUE-FROM"))
{
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing %s value on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (data->last_expect)
{
// Expand any variables in the value and then save it.
ippFileExpandVars(f, value, temp, sizeof(value));
data->last_expect->with_value_from = strdup(value);
data->last_expect->with_flags = IPPTOOL_WITH_LITERAL;
if (!_cups_strncasecmp(token, "WITH-ALL-", 9))
data->last_expect->with_flags |= IPPTOOL_WITH_ALL;
}
else
{
print_fatal_error(data, "%s without a preceding EXPECT on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!_cups_strcasecmp(token, "DISPLAY"))
{
// Display attributes...
if (data->num_displayed >= (int)(sizeof(data->displayed) / sizeof(data->displayed[0])))
{
print_fatal_error(data, "Too many DISPLAY's on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
if (!ippFileReadToken(f, temp, sizeof(temp)))
{
print_fatal_error(data, "Missing DISPLAY name on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
data->displayed[data->num_displayed] = strdup(temp);
data->num_displayed ++;
}
else
{
print_fatal_error(data, "Unexpected token %s seen on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else
{
// Scan for the start of a test (open brace)...
if (!strcmp(token, "{"))
{
// Start new test...
const char *resource; // Resource path
if (data->show_header)
{
if (data->output == IPPTOOL_OUTPUT_PLIST)
print_xml_header(data);
if (data->output == IPPTOOL_OUTPUT_TEST || (data->output == IPPTOOL_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
cupsFilePrintf(cupsFileStdout(), "\"%s\":\n", ippFileGetFilename(f));
data->show_header = false;
}
if ((resource = ippFileGetVar(data->parent, "resource")) == NULL)
resource = "/ipp/print";
data->compression[0] = '\0';
data->delay = 0;
data->num_expects = 0;
data->last_expect = NULL;
data->file[0] = '\0';
data->ignore_errors = data->def_ignore_errors;
cupsCopyString(data->name, ippFileGetFilename(f), sizeof(data->name));
if ((ptr = strrchr(data->name, '.')) != NULL)
*ptr = '\0';
data->repeat_interval = 5000000;
cupsCopyString(data->resource, resource, sizeof(data->resource));
data->skip_previous = false;
data->pass_test = false;
data->skip_test = false;
data->num_statuses = 0;
data->last_status = NULL;
data->test_id[0] = '\0';
data->transfer = data->def_transfer;
data->version = data->def_version;
free(data->monitor_uri);
data->monitor_uri = NULL;
data->monitor_delay = 0;
data->monitor_interval = 5000000;
data->num_monitor_expects = 0;
ippFileSetAttributes(f, ippNew());
ippFileSetVar(f, "date-current", iso_date(ippTimeToDate(time(NULL))));
}
else if (!strcmp(token, "DEFINE"))
{
// DEFINE name value
if (ippFileReadToken(f, name, sizeof(name)) && ippFileReadToken(f, temp, sizeof(temp)))
{
ippFileSetVar(f, "date-current", iso_date(ippTimeToDate(time(NULL))));
ippFileExpandVars(f, value, temp, sizeof(value));
ippFileSetVar(f, name, value);
}
else
{
print_fatal_error(data, "Missing DEFINE name and/or value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "DEFINE-DEFAULT"))
{
// DEFINE-DEFAULT name value
if (ippFileReadToken(f, name, sizeof(name)) && ippFileReadToken(f, temp, sizeof(temp)))
{
if (!ippFileGetVar(f, name))
{
ippFileSetVar(f, "date-current", iso_date(ippTimeToDate(time(NULL))));
ippFileExpandVars(f, value, temp, sizeof(value));
ippFileSetVar(f, name, value);
}
}
else
{
print_fatal_error(data, "Missing DEFINE-DEFAULT name and/or value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "FILE-ID"))
{
// FILE-ID "string"
if (ippFileReadToken(f, temp, sizeof(temp)))
{
ippFileSetVar(f, "date-current", iso_date(ippTimeToDate(time(NULL))));
ippFileExpandVars(f, data->file_id, temp, sizeof(data->file_id));
}
else
{
print_fatal_error(data, "Missing FILE-ID value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "IGNORE-ERRORS"))
{
// IGNORE-ERRORS yes
// IGNORE-ERRORS no
if (ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
{
data->def_ignore_errors = !_cups_strcasecmp(temp, "yes");
}
else
{
print_fatal_error(data, "Missing IGNORE-ERRORS value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "INCLUDE"))
{
// INCLUDE "filename"
// INCLUDE <filename>
if (ippFileReadToken(f, temp, sizeof(temp)))
{
// Map the filename to and then run the tests...
ipptool_test_t inc_data; // Data for included file
bool inc_pass; // Include file passed?
char filename[1024]; // Mapped filename
memcpy(&inc_data, data, sizeof(inc_data));
inc_data.monitor_thread = CUPS_THREAD_INVALID;
inc_data.test_count = 0;
inc_data.pass_count = 0;
inc_data.fail_count = 0;
inc_data.skip_count = 0;
inc_data.http = NULL;
inc_data.pass = true;
inc_data.prev_pass = true;
inc_data.show_header = true;
inc_pass = do_tests(get_filename(ippFileGetFilename(f), filename, temp, sizeof(filename)), &inc_data);
data->test_count += inc_data.test_count;
data->pass_count += inc_data.pass_count;
data->fail_count += inc_data.fail_count;
data->skip_count += inc_data.skip_count;
if (!inc_pass && data->stop_after_include_error)
{
data->pass = data->prev_pass = false;
return (false);
}
}
else
{
print_fatal_error(data, "Missing INCLUDE filename on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
data->show_header = true;
}
else if (!strcmp(token, "INCLUDE-IF-DEFINED"))
{
// INCLUDE-IF-DEFINED name "filename"
// INCLUDE-IF-DEFINED name <filename>
if (ippFileReadToken(f, name, sizeof(name)) && ippFileReadToken(f, temp, sizeof(temp)))
{
// Map the filename to and then run the tests...
ipptool_test_t inc_data; // Data for included file
bool inc_pass; // Include file passed?
char filename[1024]; // Mapped filename
memcpy(&inc_data, data, sizeof(inc_data));
inc_data.monitor_thread = CUPS_THREAD_INVALID;
inc_data.test_count = 0;
inc_data.pass_count = 0;
inc_data.fail_count = 0;
inc_data.skip_count = 0;
inc_data.http = NULL;
inc_data.pass = true;
inc_data.prev_pass = true;
inc_data.show_header = true;
inc_pass = do_tests(get_filename(ippFileGetFilename(f), filename, temp, sizeof(filename)), &inc_data);
data->test_count += inc_data.test_count;
data->pass_count += inc_data.pass_count;
data->fail_count += inc_data.fail_count;
data->skip_count += inc_data.skip_count;
if (!inc_pass && data->stop_after_include_error)
{
data->pass = data->prev_pass = false;
return (false);
}
}
else
{
print_fatal_error(data, "Missing INCLUDE-IF-DEFINED name or filename on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
data->show_header = true;
}
else if (!strcmp(token, "INCLUDE-IF-NOT-DEFINED"))
{
// INCLUDE-IF-NOT-DEFINED name "filename"
// INCLUDE-IF-NOT-DEFINED name <filename>
if (ippFileReadToken(f, name, sizeof(name)) && ippFileReadToken(f, temp, sizeof(temp)))
{
// Map the filename to and then run the tests...
ipptool_test_t inc_data; // Data for included file
bool inc_pass; // Include file passed?
char filename[1024]; // Mapped filename
memcpy(&inc_data, data, sizeof(inc_data));
inc_data.monitor_thread = CUPS_THREAD_INVALID;
inc_data.test_count = 0;
inc_data.pass_count = 0;
inc_data.fail_count = 0;
inc_data.skip_count = 0;
inc_data.http = NULL;
inc_data.pass = true;
inc_data.prev_pass = true;
inc_data.show_header = true;
inc_pass = do_tests(get_filename(ippFileGetFilename(f), filename, temp, sizeof(filename)), &inc_data);
data->test_count += inc_data.test_count;
data->pass_count += inc_data.pass_count;
data->fail_count += inc_data.fail_count;
data->skip_count += inc_data.skip_count;
if (!inc_pass && data->stop_after_include_error)
{
data->pass = data->prev_pass = false;
return (false);
}
}
else
{
print_fatal_error(data, "Missing INCLUDE-IF-NOT-DEFINED name or filename on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
data->show_header = true;
}
else if (!strcmp(token, "SKIP-IF-DEFINED"))
{
// SKIP-IF-DEFINED variable
if (ippFileReadToken(f, name, sizeof(name)))
{
if (ippFileGetVar(f, name) || getenv(name))
data->skip_test = true;
}
else
{
print_fatal_error(data, "Missing SKIP-IF-DEFINED variable on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "SKIP-IF-NOT-DEFINED"))
{
// SKIP-IF-NOT-DEFINED variable
if (ippFileReadToken(f, name, sizeof(name)))
{
if (!ippFileGetVar(f, name) && !getenv(name))
data->skip_test = true;
}
else
{
print_fatal_error(data, "Missing SKIP-IF-NOT-DEFINED variable on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "STOP-AFTER-INCLUDE-ERROR"))
{
// STOP-AFTER-INCLUDE-ERROR yes
// STOP-AFTER-INCLUDE-ERROR no
if (ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
{
data->stop_after_include_error = !_cups_strcasecmp(temp, "yes");
}
else
{
print_fatal_error(data, "Missing STOP-AFTER-INCLUDE-ERROR value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "TRANSFER"))
{
// TRANSFER auto
// TRANSFER chunked
// TRANSFER length
if (ippFileReadToken(f, temp, sizeof(temp)))
{
if (!strcmp(temp, "auto"))
data->def_transfer = IPPTOOL_TRANSFER_AUTO;
else if (!strcmp(temp, "chunked"))
data->def_transfer = IPPTOOL_TRANSFER_CHUNKED;
else if (!strcmp(temp, "length"))
data->def_transfer = IPPTOOL_TRANSFER_LENGTH;
else
{
print_fatal_error(data, "Bad TRANSFER value \"%s\" on line %d of '%s'.", temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else
{
print_fatal_error(data, "Missing TRANSFER value on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else if (!strcmp(token, "VERSION"))
{
if (ippFileReadToken(f, temp, sizeof(temp)))
{
if (!strcmp(temp, "1.0"))
data->def_version = 10;
else if (!strcmp(temp, "1.1"))
data->def_version = 11;
else if (!strcmp(temp, "2.0"))
data->def_version = 20;
else if (!strcmp(temp, "2.1"))
data->def_version = 21;
else if (!strcmp(temp, "2.2"))
data->def_version = 22;
else
{
print_fatal_error(data, "Bad VERSION \"%s\" on line %d of '%s'.", temp, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else
{
print_fatal_error(data, "Missing VERSION number on line %d of '%s'.", ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
else
{
print_fatal_error(data, "Unexpected token %s seen on line %d of '%s'.", token, ippFileGetLineNumber(f), ippFileGetFilename(f));
return (false);
}
}
return (true);
}
//
// 'usage()' - Show program usage.
//
static int // O - Exit status
usage(FILE *out) // I - Output file
{
_cupsLangPuts(out, _("Usage: ipptool [options] URI filename [ ... filenameN ]"));
_cupsLangPuts(out, _("Options:"));
_cupsLangPuts(out, _("--bearer-token BEARER-TOKEN\n"
" Set the OAuth Bearer token for authentication"));
_cupsLangPuts(out, _("--client-name CLIENT-NAME\n"
" Set the TLS client certificate name"));
_cupsLangPuts(out, _("--help Show this help"));
_cupsLangPuts(out, _("--ippserver filename Produce ippserver attribute file"));
_cupsLangPuts(out, _("--stop-after-include-error\n"
" Stop tests after a failed INCLUDE"));
_cupsLangPuts(out, _("--user-agent USER-AGENT Set the HTTP User-Agent string"));
_cupsLangPuts(out, _("--version Show version"));
_cupsLangPuts(out, _("-4 Connect using IPv4"));
_cupsLangPuts(out, _("-6 Connect using IPv6"));
_cupsLangPuts(out, _("-C Send requests using chunking (default)"));
_cupsLangPuts(out, _("-E Test with encryption using HTTP Upgrade to TLS"));
_cupsLangPuts(out, _("-I Ignore errors"));
_cupsLangPuts(out, _("-L Send requests using content-length"));
_cupsLangPuts(out, _("-P filename.plist Produce XML plist to a file and test report to standard output"));
_cupsLangPuts(out, _("-R Repeat tests on server-error-busy"));
_cupsLangPuts(out, _("-S Test with encryption using HTTPS"));
_cupsLangPuts(out, _("-T seconds Set the receive/send timeout in seconds"));
_cupsLangPuts(out, _("-V version Set default IPP version"));
_cupsLangPuts(out, _("-X Produce XML plist instead of plain text"));
_cupsLangPuts(out, _("-c Produce CSV output"));
_cupsLangPuts(out, _("-d name=value Set named variable to value"));
_cupsLangPuts(out, _("-f filename Set default request filename"));
_cupsLangPuts(out, _("-h Validate HTTP response headers"));
_cupsLangPuts(out, _("-i seconds Repeat the last file with the given time interval"));
_cupsLangPuts(out, _("-l Produce plain text output"));
_cupsLangPuts(out, _("-n count Repeat the last file the given number of times"));
_cupsLangPuts(out, _("-q Run silently"));
_cupsLangPuts(out, _("-t Produce a test report"));
_cupsLangPuts(out, _("-v Be verbose"));
return (out == stdout ? 0 : 1);
}
//
// 'valid_image()' - Validate an image.
//
// Supports JPEG and PNG images.
//
static bool // O - `true` if valid, `false` if not
valid_image(const char *filename, // I - Image filename
int *width, // O - Width in columns
int *height, // O - Height in lines
int *depth) // O - Number of color planes
{
bool ret = true; // Return value
int fd; // File descriptor
unsigned char buffer[16384], // Read buffer
*bufptr, // Pointer into buffer
*bufend; // End of buffer
ssize_t bytes; // Number of bytes read
// Initialize things...
*width = *height = *depth = 0;
// Try opening the file and reading from it...
if ((fd = open(filename, O_RDONLY | O_BINARY)) < 0)
{
// Unable to open...
ret = false;
}
else if ((bytes = read(fd, buffer, sizeof(buffer))) < 16)
{
// Unable to read...
ret = false;
}
else if (!memcmp(buffer, "\211PNG\015\012\032\012\000\000\000\015IHDR", 16) && bytes > 25)
{
// PNG image...
*width = (int)((buffer[16] << 24) | (buffer[17] << 16) | (buffer[18] << 8) | buffer[19]);
*height = (int)((buffer[20] << 24) | (buffer[21] << 16) | (buffer[22] << 8) | buffer[23]);
*depth = ((buffer[25] & 3) == 0 ? 1 : 3) + ((buffer[25] & 4) ? 1 : 0);
}
else if (!memcmp(buffer, "\377\330\377", 3))
{
// JPEG image...
size_t length; // Length of chunk
for (bufptr = buffer + 2, bufend = buffer + bytes; bufptr < bufend;)
{
if (*bufptr == 0xff)
{
bufptr ++;
if (bufptr >= bufend)
{
// If we are at the end of the current buffer, re-fill and continue...
if ((bytes = read(fd, buffer, sizeof(buffer))) <= 0)
break;
bufptr = buffer;
bufend = buffer + bytes;
}
if (*bufptr == 0xff)
continue;
if ((bufptr + 16) >= bufend)
{
// Read more of the marker...
bytes = bufend - bufptr;
memmove(buffer, bufptr, (size_t)bytes);
bufptr = buffer;
bufend = buffer + bytes;
if ((bytes = read(fd, bufend, sizeof(buffer) - (size_t)bytes)) <= 0)
break;
bufend += bytes;
}
length = (size_t)((bufptr[1] << 8) | bufptr[2]);
if ((*bufptr >= 0xc0 && *bufptr <= 0xc3) || (*bufptr >= 0xc5 && *bufptr <= 0xc7) || (*bufptr >= 0xc9 && *bufptr <= 0xcb) || (*bufptr >= 0xcd && *bufptr <= 0xcf))
{
// SOFn marker, look for dimensions...
if (bufptr[3] != 8)
{
ret = false;
break;
}
*width = (int)((bufptr[6] << 8) | bufptr[7]);
*height = (int)((bufptr[4] << 8) | bufptr[5]);
*depth = (int)bufptr[8];
break;
}
// Skip past this marker...
bufptr ++;
bytes = bufend - bufptr;
while (length >= (size_t)bytes)
{
length -= (size_t)bytes;
if ((bytes = read(fd, buffer, sizeof(buffer))) <= 0)
break;
bufptr = buffer;
bufend = buffer + bytes;
}
if (length > (size_t)bytes)
break;
bufptr += length;
}
}
if (*width == 0 || *height == 0 || (*depth != 1 && *depth != 3))
ret = false;
}
else
{
// Something we don't recognize...
ret = false;
}
if (fd >= 0)
close(fd);
return (ret);
}
//
// 'with_content()' - Verify that URIs meet content/MIME media type requirements
// and save as needed.
//
static bool // O - `true` if valid, `false` otherwise
with_content(
cups_array_t *errors, // I - Array of errors
ipp_attribute_t *attr, // I - Attribute
ipptool_content_t content, // I - Content validation rule
cups_array_t *mime_types, // I - Comma-delimited list of MIME media types
const char *filespec) // I - Output filename specification
{
bool ret = true; // Return value
int i, // Looping var
count; // Number of values
const char *uri; // URI value
char scheme[256], // Scheme
userpass[256], // Username:password (not used)
host[256], // Hostname
resource[256], // Resource path
*resptr; // Pointer into resource
int port; // Port number
http_encryption_t encryption; // Encryption mode
http_uri_status_t uri_status; // URI decoding status
http_t *http; // Connection to server
http_status_t status; // Request status
const char *content_type; // Content-Type header
for (i = 0, count = ippGetCount(attr); i < count; i ++)
{
uri = ippGetString(attr, i, NULL);
if ((uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource))) < HTTP_URI_STATUS_OK)
{
add_stringf(errors, "Bad URI value '%s': %s", uri, httpURIStatusString(uri_status));
ret = false;
continue;
}
if ((resptr = strchr(resource, '#')) != NULL)
*resptr = '\0'; // Strip HTML target ("...#target")
if (strcmp(scheme, "http") && strcmp(scheme, "https") && strcmp(scheme, "ipp") && strcmp(scheme, "ipps"))
{
add_stringf(errors, "Unsupported URI scheme for '%s'.", uri);
ret = false;
continue;
}
encryption = (!strcmp(scheme, "https") || !strcmp(scheme, "ipps") || port == 443) ? HTTP_ENCRYPTION_ALWAYS : HTTP_ENCRYPTION_IF_REQUESTED;
if ((http = httpConnect2(host, port, /*addrlist*/NULL, AF_UNSPEC, encryption, /*blocking*/1, /*msec*/30000, /*cancel*/NULL)) == NULL)
{
add_stringf(errors, "Unable to connect to '%s' on port %d: %s", host, port, cupsGetErrorString());
ret = false;
continue;
}
if (content == IPPTOOL_CONTENT_AVAILABLE)
{
if (!httpWriteRequest(http, "HEAD", resource))
{
add_stringf(errors, "Unable to send HEAD request to '%s': %s", uri, cupsGetErrorString());
ret = false;
goto http_done;
}
while ((status = httpUpdate(http)) == HTTP_STATUS_CONTINUE)
{
// Do nothing
}
if (status != HTTP_STATUS_OK)
{
add_stringf(errors, "Got unexpected status %d for HEAD request to '%s'.", (int)status, uri);
ret = false;
goto http_done;
}
content_type = httpGetField(http, HTTP_FIELD_CONTENT_TYPE);
if ((!strcmp(scheme, "ipp") || !strcmp(scheme, "ipps")) != !_cups_strcasecmp(content_type, "application/ipp") || (mime_types && !cupsArrayFind(mime_types, (void *)content_type)))
{
add_stringf(errors, "Got unexpected Content-Type '%s' for HEAD request to '%s'.", content_type, uri);
ret = false;
goto http_done;
}
}
else if (!strcmp(scheme, "http") || !strcmp(scheme, "https"))
{
// Check HTTP resource with a GET...
char filename[1024]; // Temporary filename
int fd; // Temporary file
struct stat fileinfo; // Temporary file information
int width, // Image width
height, // Image height
depth; // Image color depth
if ((fd = create_file(filespec, resource, i + 1, filename, sizeof(filename))) < 0)
{
add_stringf(errors, "Unable to create temporary file for WITH-CONTENT: %s", strerror(errno));
ret = false;
goto http_done;
}
status = cupsGetFd(http, resource, fd);
if (fstat(fd, &fileinfo))
memset(&fileinfo, 0, sizeof(fileinfo));
close(fd);
if (status != HTTP_STATUS_OK)
{
add_stringf(errors, "Got unexpected status %d for GET request to '%s'.", (int)status, uri);
ret = false;
goto get_done;
}
content_type = httpGetField(http, HTTP_FIELD_CONTENT_TYPE);
if (mime_types && !cupsArrayFind(mime_types, (void *)content_type))
{
add_stringf(errors, "Got unexpected Content-Type '%s' for GET request to '%s'.", content_type, uri);
ret = false;
goto get_done;
}
if (content == IPPTOOL_CONTENT_VALID_ICON)
{
if (_cups_strcasecmp(content_type, "image/png"))
{
add_stringf(errors, "Got unexpected Content-Type '%s' for GET request to '%s'.", content_type, uri);
ret = false;
goto get_done;
}
if (!valid_image(filename, &width, &height, &depth))
{
add_stringf(errors, "Unable to load image '%s'.", uri);
ret = false;
goto get_done;
}
else if (width != height || (width != 48 && width != 128 && width != 512))
{
add_stringf(errors, "Image '%s' has bad dimensions %dx%d.", uri, width, height);
ret = false;
goto get_done;
}
else if (depth & 1)
{
add_stringf(errors, "Image '%s' doesn't have transparency information.", uri);
ret = false;
goto get_done;
}
}
else if (!_cups_strcasecmp(content_type, "image/jpeg") || !_cups_strcasecmp(content_type, "image/png"))
{
// Validate image content
if (!valid_image(filename, &width, &height, &depth))
{
add_stringf(errors, "Unable to open image '%s'.", uri);
ret = false;
goto get_done;
}
}
else if (!_cups_strcasecmp(content_type, "application/ipp"))
{
ipp_t *ipp = ippNew(); // IPP message
if ((fd = open(filename, O_RDONLY | O_BINARY)) < 0)
{
add_stringf(errors, "Unable to open '%s': %s", uri, strerror(errno));
ippDelete(ipp);
ret = false;
goto get_done;
}
else if (ippReadFile(fd, ipp) != IPP_STATE_DATA)
{
add_stringf(errors, "Unable to read '%s': %s", uri, cupsGetErrorString());
ippDelete(ipp);
close(fd);
ret = false;
goto get_done;
}
ippDelete(ipp);
close(fd);
}
else if (!_cups_strcasecmp(content_type, "application/pdf") || !_cups_strcasecmp(content_type, "application/vnd.iccprofile") || !_cups_strcasecmp(content_type, "text/css") || !_cups_strcasecmp(content_type, "text/html") || !_cups_strncasecmp(content_type, "text/html;", 10) || !_cups_strcasecmp(content_type, "text/strings"))
{
// Just require these files to be non-empty for now, might add more checks in the future...
if (fileinfo.st_size == 0)
{
add_stringf(errors, "Empty resource '%s'.", uri);
ret = false;
goto get_done;
}
}
else
{
add_stringf(errors, "Got unexpected Content-Type '%s' for GET request to '%s'.", content_type, uri);
ret = false;
goto get_done;
}
get_done:
if (!filespec)
unlink(filename);
}
else
{
// Check IPP resource...
ipp_t *request; // IPP Get-Printer-Attributes request
request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
ippDelete(cupsDoRequest(http, request, resource));
if (cupsGetError() > IPP_STATUS_OK_EVENTS_COMPLETE)
{
add_stringf(errors, "Got unexpected status-code '%s' (%s) for Get-Printer-Attributes request to '%s'.", ippErrorString(cupsGetError()), cupsGetErrorString(), uri);
ret = false;
}
}
http_done:
httpClose(http);
}
return (ret);
}
//
// 'with_distinct_values()' - Verify that an attribute contains unique values.
//
static bool // O - `true` if distinct, `false` if duplicate
with_distinct_values(
cups_array_t *errors, // I - Array of errors
ipp_attribute_t *attr) // I - Attribute to test
{
bool ret; // Return value
int i, // Looping var
count; // Number of values
ipp_tag_t value_tag; // Value syntax
const char *value; // Current value
char buffer[131072]; // Temporary buffer
cups_array_t *values; // Array of values as strings
// If there is only 1 value, it must be distinct
if ((count = ippGetCount(attr)) == 1)
return (true);
// Only check integers, enums, rangeOfInteger, resolution, and nul-terminated
// strings...
switch (value_tag = ippGetValueTag(attr))
{
case IPP_TAG_INTEGER :
case IPP_TAG_ENUM :
case IPP_TAG_RANGE :
case IPP_TAG_RESOLUTION :
case IPP_TAG_KEYWORD :
case IPP_TAG_URISCHEME :
case IPP_TAG_CHARSET :
case IPP_TAG_LANGUAGE :
case IPP_TAG_MIMETYPE :
case IPP_TAG_BEGIN_COLLECTION :
break;
default :
add_stringf(errors, "WITH-DISTINCT-VALUES %s not supported for 1setOf %s", ippGetName(attr), ippTagString(value_tag));
return (false);
}
// Collect values and determine they are all unique...
values = cupsArrayNew3((cups_array_cb_t)_cupsArrayStrcmp, NULL, NULL, 0, _cupsArrayStrdup, _cupsArrayFree);
for (i = 0; i < count; i ++)
{
switch (value_tag)
{
case IPP_TAG_INTEGER :
case IPP_TAG_ENUM :
snprintf(buffer, sizeof(buffer), "%d", ippGetInteger(attr, i));
value = buffer;
break;
case IPP_TAG_RANGE :
{
int upper, lower = ippGetRange(attr, i, &upper);
// Range values
snprintf(buffer, sizeof(buffer), "%d-%d", lower, upper);
value = buffer;
}
break;
case IPP_TAG_RESOLUTION :
{
ipp_res_t units; // Resolution units
int yres, xres = ippGetResolution(attr, i, &yres, &units);
// Resolution values
if (xres == yres)
snprintf(buffer, sizeof(buffer), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
else
snprintf(buffer, sizeof(buffer), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
value = buffer;
}
break;
case IPP_TAG_KEYWORD :
case IPP_TAG_URISCHEME :
case IPP_TAG_CHARSET :
case IPP_TAG_LANGUAGE :
case IPP_TAG_MIMETYPE :
value = ippGetString(attr, i, NULL);
break;
case IPP_TAG_BEGIN_COLLECTION :
{
ipp_t *col = ippGetCollection(attr, i);
// Collection value
ipp_attribute_t *member; // Member attribute
char *bufptr, // Pointer into buffer
*bufend, // End of buffer
prefix; // Prefix character
for (prefix = '{', bufptr = buffer, bufend = buffer + sizeof(buffer) - 2, member = ippGetFirstAttribute(col); member && bufptr < bufend; member = ippGetNextAttribute(col))
{
*bufptr++ = prefix;
prefix = ' ';
ippAttributeString(member, bufptr, (size_t)(bufend - bufptr));
bufptr += strlen(bufptr);
}
*bufptr++ = '}';
*bufptr = '\0';
value = buffer;
}
break;
default : // Should never happen
value = "unsupported";
break;
}
if (cupsArrayFind(values, (void *)value))
add_stringf(errors, "DUPLICATE: %s=%s", ippGetName(attr), value);
else
cupsArrayAdd(values, (void *)value);
}
// Cleanup...
ret = cupsArrayGetCount(values) == count;
cupsArrayDelete(values);
return (ret);
}
//
// 'with_flags_string()' - Return the "WITH-xxx" predicate that corresponds to
// the flags.
//
static const char * // O - WITH-xxx string
with_flags_string(int flags) // I - WITH flags
{
if (flags & IPPTOOL_WITH_ALL)
{
if (flags & IPPTOOL_WITH_HOSTNAME)
return ("WITH-ALL-HOSTNAMES");
else if (flags & IPPTOOL_WITH_RESOURCE)
return ("WITH-ALL-RESOURCES");
else if (flags & IPPTOOL_WITH_SCHEME)
return ("WITH-ALL-SCHEMES");
else
return ("WITH-ALL-VALUES");
}
else if (flags & IPPTOOL_WITH_HOSTNAME)
return ("WITH-HOSTNAME");
else if (flags & IPPTOOL_WITH_RESOURCE)
return ("WITH-RESOURCE");
else if (flags & IPPTOOL_WITH_SCHEME)
return ("WITH-SCHEME");
else
return ("WITH-VALUE");
}
//
// 'with_value()' - Test a WITH-VALUE predicate.
//
static bool // O - `true` on match, `false` on non-match
with_value(ipptool_test_t *data, // I - Test data
cups_array_t *errors, // I - Errors array
char *value, // I - Value string
int flags, // I - Flags for match
ipp_attribute_t *attr, // I - Attribute to compare
char *matchbuf, // I - Buffer to hold matching value
size_t matchlen) // I - Length of match buffer
{
int i, // Looping var
count; // Number of values
bool match; // Match?
char temp[1024], // Temporary value string
*valptr; // Pointer into value
const char *name; // Attribute name
*matchbuf = '\0';
match = (flags & IPPTOOL_WITH_ALL) ? true : false;
// NULL matches everything.
if (!value || !*value)
return (true);
// Compare the value string to the attribute value.
name = ippGetName(attr);
count = ippGetCount(attr);
switch (ippGetValueTag(attr))
{
case IPP_TAG_INTEGER :
case IPP_TAG_ENUM :
for (i = 0; i < count; i ++)
{
char op, // Comparison operator
*nextptr; // Next pointer
int intvalue, // Integer value
attrvalue = ippGetInteger(attr, i);
// Attribute value
bool valmatch = false; // Does the current value match?
valptr = value;
while (_cups_isspace(*valptr) || _cups_isdigit(*valptr) ||
*valptr == '-' || *valptr == ',' || *valptr == '<' ||
*valptr == '=' || *valptr == '>' || *valptr == '!')
{
op = '=';
while (*valptr && !_cups_isdigit(*valptr) && *valptr != '-')
{
if (*valptr == '<' || *valptr == '>' || *valptr == '=' || *valptr == '!')
op = *valptr;
valptr ++;
}
if (!*valptr)
break;
intvalue = (int)strtol(valptr, &nextptr, 0);
if (nextptr == valptr)
break;
valptr = nextptr;
if ((op == '=' && attrvalue == intvalue) ||
(op == '!' && attrvalue != intvalue) ||
(op == '<' && attrvalue < intvalue) ||
(op == '>' && attrvalue > intvalue))
{
if (!matchbuf[0])
snprintf(matchbuf, matchlen, "%d", attrvalue);
valmatch = true;
break;
}
}
if (flags & IPPTOOL_WITH_ALL)
{
if (!valmatch)
{
match = false;
break;
}
}
else if (valmatch)
{
match = true;
break;
}
}
if (!match && errors)
{
for (i = 0; i < count; i ++)
add_stringf(data->errors, "GOT: %s=%d", name, ippGetInteger(attr, i));
}
break;
case IPP_TAG_RANGE :
for (i = 0; i < count; i ++)
{
char op, // Comparison operator
*nextptr; // Next pointer
int intvalue, // Integer value
lower, // Lower range
upper; // Upper range
bool valmatch = false; // Does the current value match?
lower = ippGetRange(attr, i, &upper);
valptr = value;
while (_cups_isspace(*valptr) || _cups_isdigit(*valptr) ||
*valptr == '-' || *valptr == ',' || *valptr == '!' || *valptr == '<' ||
*valptr == '=' || *valptr == '>')
{
op = '=';
while (*valptr && !_cups_isdigit(*valptr) && *valptr != '-')
{
if (*valptr == '<' || *valptr == '>' || *valptr == '=' || *valptr == '!')
op = *valptr;
valptr ++;
}
if (!*valptr)
break;
intvalue = (int)strtol(valptr, &nextptr, 0);
if (nextptr == valptr)
break;
valptr = nextptr;
if ((op == '=' && lower <= intvalue && upper >= intvalue) ||
(op == '!' && (lower > intvalue || upper < intvalue)) ||
(op == '<' && upper < intvalue) ||
(op == '>' && upper > intvalue))
{
if (!matchbuf[0])
snprintf(matchbuf, matchlen, "%d-%d", lower, upper);
valmatch = true;
break;
}
}
if (flags & IPPTOOL_WITH_ALL)
{
if (!valmatch)
{
match = false;
break;
}
}
else if (valmatch)
{
match = true;
break;
}
}
if (!match && errors)
{
for (i = 0; i < count; i ++)
{
int lower, upper; // Range values
lower = ippGetRange(attr, i, &upper);
add_stringf(data->errors, "GOT: %s=%d-%d", name, lower, upper);
}
}
break;
case IPP_TAG_BOOLEAN :
for (i = 0; i < count; i ++)
{
if ((!strcmp(value, "true") || !strcmp(value, "1")) == ippGetBoolean(attr, i))
{
if (!matchbuf[0])
cupsCopyString(matchbuf, value, matchlen);
if (!(flags & IPPTOOL_WITH_ALL))
{
match = true;
break;
}
}
else if (flags & IPPTOOL_WITH_ALL)
{
match = false;
break;
}
}
if (!match && errors)
{
for (i = 0; i < count; i ++)
add_stringf(data->errors, "GOT: %s=%s", name, ippGetBoolean(attr, i) ? "true" : "false");
}
break;
case IPP_TAG_RESOLUTION :
for (i = 0; i < count; i ++)
{
int xres, yres; // Resolution values
ipp_res_t units; // Resolution units
xres = ippGetResolution(attr, i, &yres, &units);
if (xres == yres)
snprintf(temp, sizeof(temp), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
else
snprintf(temp, sizeof(temp), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
if (!strcmp(value, temp))
{
if (!matchbuf[0])
cupsCopyString(matchbuf, value, matchlen);
if (!(flags & IPPTOOL_WITH_ALL))
{
match = true;
break;
}
}
else if (flags & IPPTOOL_WITH_ALL)
{
match = false;
break;
}
}
if (!match && errors)
{
for (i = 0; i < count; i ++)
{
int xres, yres; // Resolution values
ipp_res_t units; // Resolution units
xres = ippGetResolution(attr, i, &yres, &units);
if (xres == yres)
snprintf(temp, sizeof(temp), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
else
snprintf(temp, sizeof(temp), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
if (strcmp(value, temp))
add_stringf(data->errors, "GOT: %s=%s", name, temp);
}
}
break;
case IPP_TAG_NOVALUE :
case IPP_TAG_UNKNOWN :
return (true);
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 (flags & IPPTOOL_WITH_REGEX)
{
// Value is an extended, case-sensitive POSIX regular expression...
int r; // Error, if any
regex_t re; // Regular expression
if ((r = regcomp(&re, value, REG_EXTENDED | REG_NOSUB)) != 0)
{
regerror(r, &re, temp, sizeof(temp));
print_fatal_error(data, "Unable to compile WITH-VALUE regular expression \"%s\" - %s", value, temp);
return (false);
}
// See if ALL of the values match the given regular expression.
for (i = 0; i < count; i ++)
{
if (!regexec(&re, get_string(attr, i, flags, temp, sizeof(temp)),
0, NULL, 0))
{
if (!matchbuf[0])
cupsCopyString(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
if (!(flags & IPPTOOL_WITH_ALL))
{
match = true;
break;
}
}
else if (flags & IPPTOOL_WITH_ALL)
{
match = false;
break;
}
}
regfree(&re);
}
else if (ippGetValueTag(attr) == IPP_TAG_URI && !(flags & (IPPTOOL_WITH_SCHEME | IPPTOOL_WITH_HOSTNAME | IPPTOOL_WITH_RESOURCE)))
{
// Value is a literal URI string, see if the value(s) match...
for (i = 0; i < count; i ++)
{
if (!compare_uris(value, get_string(attr, i, flags, temp, sizeof(temp))))
{
if (!matchbuf[0])
cupsCopyString(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
if (!(flags & IPPTOOL_WITH_ALL))
{
match = true;
break;
}
}
else if (flags & IPPTOOL_WITH_ALL)
{
match = false;
break;
}
}
}
else
{
// Value is a literal string, see if the value(s) match...
for (i = 0; i < count; i ++)
{
int result;
switch (ippGetValueTag(attr))
{
case IPP_TAG_URI :
// Some URI components are case-sensitive, some not...
if (flags & (IPPTOOL_WITH_SCHEME | IPPTOOL_WITH_HOSTNAME))
result = _cups_strcasecmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
else
result = strcmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
break;
case IPP_TAG_MIMETYPE :
case IPP_TAG_NAME :
case IPP_TAG_NAMELANG :
case IPP_TAG_TEXT :
case IPP_TAG_TEXTLANG :
// mimeMediaType, nameWithoutLanguage, nameWithLanguage,
// textWithoutLanguage, and textWithLanguage are defined to
// be case-insensitive strings...
result = _cups_strcasecmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
break;
default :
// Other string syntaxes are defined as lowercased so we use
// case-sensitive comparisons to catch problems...
result = strcmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
break;
}
if (!result)
{
if (!matchbuf[0])
cupsCopyString(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
if (!(flags & IPPTOOL_WITH_ALL))
{
match = true;
break;
}
}
else if (flags & IPPTOOL_WITH_ALL)
{
match = false;
break;
}
}
}
if (!match && errors)
{
for (i = 0; i < count; i ++)
add_stringf(data->errors, "GOT: %s=\"%s\"", name, ippGetString(attr, i, NULL));
}
break;
case IPP_TAG_STRING :
if (flags & IPPTOOL_WITH_REGEX)
{
// Value is an extended, case-sensitive POSIX regular expression...
void *adata; // Pointer to octetString data
int adatalen; // Length of octetString
int r; // Error, if any
regex_t re; // Regular expression
if ((r = regcomp(&re, value, REG_EXTENDED | REG_NOSUB)) != 0)
{
regerror(r, &re, temp, sizeof(temp));
print_fatal_error(data, "Unable to compile WITH-VALUE regular expression \"%s\" - %s", value, temp);
return (false);
}
// See if ALL of the values match the given regular expression.
for (i = 0; i < count; i ++)
{
if ((adata = ippGetOctetString(attr, i, &adatalen)) == NULL || adatalen >= (int)sizeof(temp))
{
match = false;
break;
}
memcpy(temp, adata, (size_t)adatalen);
temp[adatalen] = '\0';
if (!regexec(&re, temp, 0, NULL, 0))
{
if (!matchbuf[0])
cupsCopyString(matchbuf, temp, matchlen);
if (!(flags & IPPTOOL_WITH_ALL))
{
match = true;
break;
}
}
else if (flags & IPPTOOL_WITH_ALL)
{
match = false;
break;
}
}
regfree(&re);
if (!match && errors)
{
for (i = 0; i < count; i ++)
{
adata = ippGetOctetString(attr, i, &adatalen);
copy_hex_string(temp, adata, adatalen, sizeof(temp));
add_stringf(data->errors, "GOT: %s=\"%s\"", name, temp);
}
}
}
else
{
// Value is a literal or hex-encoded string...
unsigned char withdata[1023], // WITH-VALUE data
*adata; // Pointer to octetString data
int withlen, // Length of WITH-VALUE data
adatalen; // Length of octetString
if (*value == '<')
{
// Grab hex-encoded value...
if ((withlen = (int)strlen(value)) & 1 || withlen > (int)(2 * (sizeof(withdata) + 1)))
{
print_fatal_error(data, "Bad WITH-VALUE hex value.");
return (false);
}
withlen = withlen / 2 - 1;
for (valptr = value + 1, adata = withdata; *valptr; valptr += 2)
{
int x1, x2; // Current hex digits
if ((x1 = _cups_xdigit(valptr[0])) < 0 || (x2 = _cups_xdigit(valptr[1])) < 0)
break;
*adata++ = (unsigned char)((x1 << 4) | x2);
}
if (*valptr)
{
print_fatal_error(data, "Bad WITH-VALUE hex value.");
return (false);
}
}
else
{
// Copy literal string value...
withlen = strlen(value);
memcpy(withdata, value, (size_t)withlen);
}
for (i = 0; i < count; i ++)
{
adata = ippGetOctetString(attr, i, &adatalen);
if (withlen == adatalen && !memcmp(withdata, adata, (size_t)withlen))
{
if (!matchbuf[0])
copy_hex_string(matchbuf, adata, adatalen, matchlen);
if (!(flags & IPPTOOL_WITH_ALL))
{
match = true;
break;
}
}
else if (flags & IPPTOOL_WITH_ALL)
{
match = false;
break;
}
}
if (!match && errors)
{
for (i = 0; i < count; i ++)
{
adata = ippGetOctetString(attr, i, &adatalen);
copy_hex_string(temp, adata, adatalen, sizeof(temp));
add_stringf(data->errors, "GOT: %s=\"%s\"", name, temp);
}
}
}
break;
default :
break;
}
return (match);
}
//
// 'with_value_from()' - Test a WITH-VALUE-FROM predicate.
//
static bool // O - `true` on match, `false` on non-match
with_value_from(
cups_array_t *errors, // I - Errors array
ipp_attribute_t *fromattr, // I - "From" attribute
ipp_attribute_t *attr, // I - Attribute to compare
char *matchbuf, // I - Buffer to hold matching value
size_t matchlen) // I - Length of match buffer
{
int i, j, // Looping vars
count = ippGetCount(attr);
// Number of attribute values
bool match = true; // Match?
*matchbuf = '\0';
// Compare the from value(s) to the attribute value(s)...
switch (ippGetValueTag(attr))
{
case IPP_TAG_INTEGER :
if (ippGetValueTag(fromattr) != IPP_TAG_INTEGER && ippGetValueTag(fromattr) != IPP_TAG_RANGE)
goto wrong_value_tag;
for (i = 0; i < count; i ++)
{
int value = ippGetInteger(attr, i);
// Current integer value
if (ippContainsInteger(fromattr, value))
{
if (!matchbuf[0])
snprintf(matchbuf, matchlen, "%d", value);
}
else
{
add_stringf(errors, "GOT: %s=%d", ippGetName(attr), value);
match = false;
}
}
break;
case IPP_TAG_ENUM :
if (ippGetValueTag(fromattr) != IPP_TAG_ENUM)
goto wrong_value_tag;
for (i = 0; i < count; i ++)
{
int value = ippGetInteger(attr, i);
// Current integer value
if (ippContainsInteger(fromattr, value))
{
if (!matchbuf[0])
snprintf(matchbuf, matchlen, "%d", value);
}
else
{
add_stringf(errors, "GOT: %s=%d", ippGetName(attr), value);
match = false;
}
}
break;
case IPP_TAG_RESOLUTION :
if (ippGetValueTag(fromattr) != IPP_TAG_RESOLUTION)
goto wrong_value_tag;
for (i = 0; i < count; i ++)
{
int xres, yres; // Current X,Y resolution
ipp_res_t units; // Current units
int fromcount = ippGetCount(fromattr);
// From count
int fromxres, fromyres;
// From X,Y resolution
ipp_res_t fromunits; // From units
xres = ippGetResolution(attr, i, &yres, &units);
for (j = 0; j < fromcount; j ++)
{
fromxres = ippGetResolution(fromattr, j, &fromyres, &fromunits);
if (fromxres == xres && fromyres == yres && fromunits == units)
break;
}
if (j < fromcount)
{
if (!matchbuf[0])
{
if (xres == yres)
snprintf(matchbuf, matchlen, "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
else
snprintf(matchbuf, matchlen, "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
}
}
else
{
if (xres == yres)
add_stringf(errors, "GOT: %s=%d%s", ippGetName(attr), xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
else
add_stringf(errors, "GOT: %s=%dx%d%s", ippGetName(attr), xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
match = false;
}
}
break;
case IPP_TAG_NOVALUE :
case IPP_TAG_UNKNOWN :
return (true);
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_URISCHEME :
for (i = 0; i < count; i ++)
{
const char *value = ippGetString(attr, i, NULL);
// Current string value
if (ippContainsString(fromattr, value))
{
if (!matchbuf[0])
cupsCopyString(matchbuf, value, matchlen);
}
else
{
add_stringf(errors, "GOT: %s='%s'", ippGetName(attr), value);
match = false;
}
}
break;
case IPP_TAG_URI :
for (i = 0; i < count; i ++)
{
const char *value = ippGetString(attr, i, NULL);
// Current string value
int fromcount = ippGetCount(fromattr);
// From count
for (j = 0; j < fromcount; j ++)
{
if (!compare_uris(value, ippGetString(fromattr, j, NULL)))
{
if (!matchbuf[0])
cupsCopyString(matchbuf, value, matchlen);
break;
}
}
if (j >= fromcount)
{
add_stringf(errors, "GOT: %s='%s'", ippGetName(attr), value);
match = false;
}
}
break;
default :
match = false;
break;
}
return (match);
// value tag mismatch between fromattr and attr...
wrong_value_tag :
add_stringf(errors, "GOT: %s OF-TYPE %s", ippGetName(attr), ippTagString(ippGetValueTag(attr)));
return (false);
}