#include <stdio.h>
#include <stdlib.h>
#include <cups/string.h>
#include <errno.h>
#include <ctype.h>
#include <cups/cups.h>
#include <cups/language.h>
#include <cups/http-private.h>
#ifndef O_BINARY
# define O_BINARY 0
#endif
typedef struct _cups_expect_s
{
int not_expect;
char *name,
*of_type,
*same_count_as,
*if_defined;
} _cups_expect_t;
int Chunking = 0;
int Verbosity = 0;
static int do_tests(const char *uri, const char *testfile);
static int expect_matches(_cups_expect_t *expect, ipp_tag_t value_tag);
static char *get_token(FILE *fp, char *buf, int buflen,
int *linenum);
static void print_attr(ipp_attribute_t *attr);
static void print_col(ipp_t *col);
static void usage(void);
int
main(int argc,
char *argv[])
{
int i;
int status;
char *opt;
const char *uri,
*testfile;
int interval;
uri = NULL;
testfile = NULL;
status = 0;
interval = 0;
for (i = 1; i < argc; i ++)
{
if (argv[i][0] == '-')
{
for (opt = argv[i] + 1; *opt; opt ++)
{
switch (*opt)
{
case 'c' :
Chunking = 1;
break;
case 'd' :
i ++;
if (i >= argc)
{
fputs("ipptest: Missing name=value for \"-d\"!\n", stderr);
usage();
}
else
putenv(argv[i]);
break;
case 'i' :
i++;
if (i >= argc)
{
fputs("ipptest: Missing seconds for \"-i\"!\n", stderr);
usage();
}
else
interval = atoi(argv[i]);
break;
case 'v' :
Verbosity ++;
break;
default :
fprintf(stderr, "ipptest: Unknown option \"-%c\"!\n", *opt);
usage();
break;
}
}
}
else if (!strncmp(argv[i], "ipp://", 6) ||
!strncmp(argv[i], "http://", 7) ||
!strncmp(argv[i], "https://", 8))
{
if (!testfile && uri)
{
fputs("ipptest: May only specify a single URI before a test!\n",
stderr);
usage();
}
uri = argv[i];
testfile = NULL;
}
else
{
testfile = argv[i];
if (!do_tests(uri, testfile))
status ++;
}
}
if (!uri || !testfile)
usage();
if (interval)
{
for (;;)
{
sleep(interval);
do_tests(uri, testfile);
}
}
return (status);
}
static int
do_tests(const char *uri,
const char *testfile)
{
int i;
int linenum;
int version;
http_t *http;
char scheme[HTTP_MAX_URI],
userpass[HTTP_MAX_URI],
server[HTTP_MAX_URI],
resource[HTTP_MAX_URI];
int port;
FILE *fp;
char token[1024],
*tokenptr,
temp[1024],
*tempptr;
ipp_t *request;
ipp_t *response;
ipp_op_t op;
ipp_tag_t group;
ipp_tag_t value;
ipp_attribute_t *attrptr,
*found;
char attr[128];
int num_statuses;
ipp_status_t statuses[100];
int num_expects;
_cups_expect_t expects[100],
*expect,
*last_expect;
int num_displayed;
char *displayed[100];
char name[1024];
char filename[1024];
int pass;
int job_id;
int subscription_id;
if ((fp = fopen(testfile, "r")) == NULL)
{
printf("Unable to open test file %s - %s\n", testfile, strerror(errno));
return (0);
}
httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass,
sizeof(userpass), server, sizeof(server), &port, resource,
sizeof(resource));
if ((http = httpConnect(server, port)) == NULL)
{
printf("Unable to connect to %s on port %d - %s\n", server, port,
strerror(errno));
fclose(fp);
return (0);
}
printf("\"%s\":\n", testfile);
pass = 1;
job_id = 0;
subscription_id = 0;
version = 11;
linenum = 1;
while (get_token(fp, token, sizeof(token), &linenum) != NULL)
{
if (strcmp(token, "{"))
{
printf("Unexpected token %s seen on line %d - aborting test!\n", token,
linenum);
httpClose(http);
return (0);
}
httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass,
sizeof(userpass), server, sizeof(server), &port, resource,
sizeof(resource));
request = ippNew();
op = (ipp_op_t)0;
group = IPP_TAG_ZERO;
num_statuses = 0;
num_expects = 0;
num_displayed = 0;
last_expect = NULL;
filename[0] = '\0';
strcpy(name, testfile);
if (strrchr(name, '.') != NULL)
*strrchr(name, '.') = '\0';
while (get_token(fp, token, sizeof(token), &linenum) != NULL)
{
if (strcasecmp(token, "EXPECT") &&
strcasecmp(token, "IF-DEFINED") &&
strcasecmp(token, "OF-TYPE") &&
strcasecmp(token, "SAME-COUNT-AS"))
last_expect = NULL;
if (!strcmp(token, "}"))
break;
else if (!strcasecmp(token, "NAME"))
{
get_token(fp, name, sizeof(name), &linenum);
}
else if (!strcasecmp(token, "VERSION"))
{
int major, minor;
get_token(fp, temp, sizeof(temp), &linenum);
if (sscanf(temp, "%d.%d", &major, &minor) == 2 &&
major >= 0 && minor >= 0 && minor < 10)
version = major * 10 + minor;
else
{
printf("Bad version %s seen on line %d - aborting test!\n", token,
linenum);
httpClose(http);
ippDelete(request);
return (0);
}
}
else if (!strcasecmp(token, "RESOURCE"))
{
get_token(fp, resource, sizeof(resource), &linenum);
}
else if (!strcasecmp(token, "OPERATION"))
{
get_token(fp, token, sizeof(token), &linenum);
op = ippOpValue(token);
}
else if (!strcasecmp(token, "GROUP"))
{
get_token(fp, token, sizeof(token), &linenum);
value = ippTagValue(token);
if (value == group)
ippAddSeparator(request);
group = value;
}
else if (!strcasecmp(token, "DELAY"))
{
int delay;
get_token(fp, token, sizeof(token), &linenum);
if ((delay = atoi(token)) > 0)
sleep(delay);
}
else if (!strcasecmp(token, "ATTR"))
{
get_token(fp, token, sizeof(token), &linenum);
value = ippTagValue(token);
get_token(fp, attr, sizeof(attr), &linenum);
get_token(fp, temp, sizeof(temp), &linenum);
token[sizeof(token) - 1] = '\0';
for (tempptr = temp, tokenptr = token;
*tempptr && tokenptr < (token + sizeof(token) - 1);)
if (*tempptr == '$')
{
if (!strncasecmp(tempptr + 1, "uri", 3))
{
strlcpy(tokenptr, uri, sizeof(token) - (tokenptr - token));
tempptr += 4;
}
else if (!strncasecmp(tempptr + 1, "scheme", 6) ||
!strncasecmp(tempptr + 1, "method", 6))
{
strlcpy(tokenptr, scheme, sizeof(token) - (tokenptr - token));
tempptr += 7;
}
else if (!strncasecmp(tempptr + 1, "username", 8))
{
strlcpy(tokenptr, userpass, sizeof(token) - (tokenptr - token));
tempptr += 9;
}
else if (!strncasecmp(tempptr + 1, "hostname", 8))
{
strlcpy(tokenptr, server, sizeof(token) - (tokenptr - token));
tempptr += 9;
}
else if (!strncasecmp(tempptr + 1, "port", 4))
{
snprintf(tokenptr, sizeof(token) - (tokenptr - token),
"%d", port);
tempptr += 5;
}
else if (!strncasecmp(tempptr + 1, "resource", 8))
{
strlcpy(tokenptr, resource, sizeof(token) - (tokenptr - token));
tempptr += 9;
}
else if (!strncasecmp(tempptr + 1, "job-id", 6))
{
snprintf(tokenptr, sizeof(token) - (tokenptr - token),
"%d", job_id);
tempptr += 7;
}
else if (!strncasecmp(tempptr + 1, "notify-subscription-id", 22))
{
snprintf(tokenptr, sizeof(token) - (tokenptr - token),
"%d", subscription_id);
tempptr += 23;
}
else if (!strncasecmp(tempptr + 1, "user", 4))
{
strlcpy(tokenptr, cupsUser(), sizeof(token) - (tokenptr - token));
tempptr += 5;
}
else if (!strncasecmp(tempptr + 1, "ENV[", 4))
{
char *end;
if ((end = strchr(tempptr + 5, ']')) != NULL)
{
*end++ = '\0';
strlcpy(tokenptr,
getenv(tempptr + 5) ? getenv(tempptr + 5) : tempptr + 5,
sizeof(token) - (tokenptr - token));
tempptr = end;
}
else
{
*tokenptr++ = *tempptr++;
*tokenptr = '\0';
}
}
else
{
*tokenptr++ = *tempptr++;
*tokenptr = '\0';
}
tokenptr += strlen(tokenptr);
}
else
{
*tokenptr++ = *tempptr++;
*tokenptr = '\0';
}
switch (value)
{
case IPP_TAG_BOOLEAN :
if (!strcasecmp(token, "true"))
ippAddBoolean(request, group, attr, 1);
else
ippAddBoolean(request, group, attr, atoi(token));
break;
case IPP_TAG_INTEGER :
case IPP_TAG_ENUM :
ippAddInteger(request, group, value, attr, atoi(token));
break;
case IPP_TAG_RESOLUTION :
puts(" ERROR: resolution tag not yet supported!");
break;
case IPP_TAG_RANGE :
puts(" ERROR: range tag not yet supported!");
break;
default :
if (!strchr(token, ','))
ippAddString(request, group, value, attr, NULL, token);
else
{
int num_values;
char *values[100],
*ptr;
values[0] = token;
num_values = 1;
for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ','))
{
*ptr++ = '\0';
values[num_values] = ptr;
num_values ++;
}
ippAddStrings(request, group, value, attr, num_values,
NULL, (const char **)values);
}
break;
}
}
else if (!strcasecmp(token, "FILE"))
{
get_token(fp, filename, sizeof(filename), &linenum);
}
else if (!strcasecmp(token, "STATUS") &&
num_statuses < (int)(sizeof(statuses) / sizeof(statuses[0])))
{
get_token(fp, token, sizeof(token), &linenum);
statuses[num_statuses] = ippErrorValue(token);
num_statuses ++;
}
else if (!strcasecmp(token, "EXPECT"))
{
if (num_expects >= (int)(sizeof(expects) / sizeof(expects[0])))
{
fprintf(stderr, "ipptest: Too many EXPECT's on line %d\n", linenum);
httpClose(http);
ippDelete(request);
return (0);
}
get_token(fp, token, sizeof(token), &linenum);
last_expect = expects + num_expects;
num_expects ++;
if (token[0] == '!')
{
last_expect->not_expect = 1;
last_expect->name = strdup(token + 1);
}
else
{
last_expect->not_expect = 0;
last_expect->name = strdup(token);
}
last_expect->of_type = NULL;
last_expect->same_count_as = NULL;
last_expect->if_defined = NULL;
}
else if (!strcasecmp(token, "OF-TYPE"))
{
get_token(fp, token, sizeof(token), &linenum);
if (last_expect)
last_expect->of_type = strdup(token);
else
{
fprintf(stderr,
"ipptest: OF-TYPE without a preceding EXPECT on line %d\n",
linenum);
httpClose(http);
ippDelete(request);
return (0);
}
}
else if (!strcasecmp(token, "SAME-COUNT-AS"))
{
get_token(fp, token, sizeof(token), &linenum);
if (last_expect)
last_expect->same_count_as = strdup(token);
else
{
fprintf(stderr,
"ipptest: SAME-COUNT-AS without a preceding EXPECT on line "
"%d\n", linenum);
httpClose(http);
ippDelete(request);
return (0);
}
}
else if (!strcasecmp(token, "IF-DEFINED"))
{
get_token(fp, token, sizeof(token), &linenum);
if (last_expect)
last_expect->if_defined = strdup(token);
else
{
fprintf(stderr,
"ipptest: IF-DEFINED without a preceding EXPECT on line %d\n",
linenum);
httpClose(http);
ippDelete(request);
return (0);
}
}
else if (!strcasecmp(token, "DISPLAY") &&
num_displayed < (int)(sizeof(displayed) / sizeof(displayed[0])))
{
get_token(fp, token, sizeof(token), &linenum);
displayed[num_displayed] = strdup(token);
num_displayed ++;
}
else
{
fprintf(stderr,
"ipptest: Unexpected token %s seen on line %d - aborting "
"test!\n", token, linenum);
httpClose(http);
ippDelete(request);
return (0);
}
}
request->request.op.version[0] = version / 10;
request->request.op.version[1] = version % 10;
request->request.op.operation_id = op;
request->request.op.request_id = 1;
if (Verbosity)
{
printf(" %s:\n", ippOpString(op));
for (attrptr = request->attrs; attrptr; attrptr = attrptr->next)
print_attr(attrptr);
}
printf(" %-60.60s [", name);
fflush(stdout);
if (Chunking)
{
http_status_t status = cupsSendRequest(http, request, resource, 0);
if (status == HTTP_CONTINUE && filename[0])
{
int fd;
char buffer[8192];
ssize_t bytes;
if ((fd = open(filename, O_RDONLY | O_BINARY)) >= 0)
{
while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
if ((status = cupsWriteRequestData(http, buffer,
bytes)) != HTTP_CONTINUE)
break;
}
else
status = HTTP_ERROR;
}
ippDelete(request);
if (status == HTTP_CONTINUE)
response = cupsGetResponse(http, resource);
else
response = NULL;
}
else if (filename[0])
response = cupsDoFileRequest(http, request, resource, filename);
else
response = cupsDoIORequest(http, request, resource, -1,
Verbosity ? 1 : -1);
if (response == NULL)
{
time_t curtime;
curtime = time(NULL);
puts("FAIL]");
printf(" ERROR %04x (%s) @ %s\n", cupsLastError(),
cupsLastErrorString(), ctime(&curtime));
pass = 0;
}
else
{
if (http->version != HTTP_1_1)
pass = 0;
if ((attrptr = ippFindAttribute(response, "job-id",
IPP_TAG_INTEGER)) != NULL)
job_id = attrptr->values[0].integer;
if ((attrptr = ippFindAttribute(response, "notify-subscription-id",
IPP_TAG_INTEGER)) != NULL)
subscription_id = attrptr->values[0].integer;
for (i = 0; i < num_statuses; i ++)
if (response->request.status.status_code == statuses[i])
break;
if (i == num_statuses && num_statuses > 0)
pass = 0;
else
{
for (i = num_expects, expect = expects; i > 0; i --, expect ++)
{
if (expect->if_defined && !getenv(expect->if_defined))
continue;
found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
if ((found == NULL) != expect->not_expect ||
(found && !expect_matches(expect, found->value_tag)))
{
pass = 0;
break;
}
if (found && expect->same_count_as)
{
attrptr = ippFindAttribute(response, expect->same_count_as,
IPP_TAG_ZERO);
if (!attrptr || attrptr->num_values != found->num_values)
{
pass = 0;
break;
}
}
}
}
if (pass)
{
puts("PASS]");
printf(" RECEIVED: %lu bytes in response\n",
(unsigned long)ippLength(response));
if (Verbosity)
{
for (attrptr = response->attrs;
attrptr != NULL;
attrptr = attrptr->next)
{
print_attr(attrptr);
}
}
else if (num_displayed > 0)
{
for (attrptr = response->attrs;
attrptr != NULL;
attrptr = attrptr->next)
{
if (attrptr->name)
{
for (i = 0; i < num_displayed; i ++)
{
if (!strcmp(displayed[i], attrptr->name))
{
print_attr(attrptr);
break;
}
}
}
}
}
}
else
{
puts("FAIL]");
printf(" RECEIVED: %lu bytes in response\n",
(unsigned long)ippLength(response));
if (http->version != HTTP_1_1)
printf(" BAD HTTP VERSION (%d.%d)\n", http->version / 100,
http->version % 100);
for (i = 0; i < num_statuses; i ++)
if (response->request.status.status_code == statuses[i])
break;
if (i == num_statuses && num_statuses > 0)
puts(" BAD STATUS");
printf(" status-code = %04x (%s)\n",
cupsLastError(), ippErrorString(cupsLastError()));
for (i = num_expects, expect = expects; i > 0; i --, expect ++)
{
if (expect->if_defined && !getenv(expect->if_defined))
continue;
found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
if ((found == NULL) != expect->not_expect)
{
if (expect->not_expect)
printf(" NOT EXPECTED: %s\n", expect->name);
else
printf(" EXPECTED: %s\n", expect->name);
}
else if (found)
{
if (!expect_matches(expect, found->value_tag))
printf(" EXPECTED: %s of type %s but got %s\n",
expect->name, expect->of_type,
ippTagString(found->value_tag));
else if (expect->same_count_as)
{
attrptr = ippFindAttribute(response, expect->same_count_as,
IPP_TAG_ZERO);
if (!attrptr)
printf(" EXPECTED: %s (%d values) same count as %s "
"(not returned)\n",
expect->name, found->num_values, expect->same_count_as);
else if (attrptr->num_values != found->num_values)
printf(" EXPECTED: %s (%d values) same count as %s "
"(%d values)\n",
expect->name, found->num_values, expect->same_count_as,
attrptr->num_values);
}
}
}
for (attrptr = response->attrs; attrptr != NULL; attrptr = attrptr->next)
print_attr(attrptr);
}
ippDelete(response);
}
for (i = num_expects, expect = expects; i > 0; i --, expect ++)
{
free(expect->name);
if (expect->of_type)
free(expect->of_type);
if (expect->same_count_as)
free(expect->same_count_as);
if (expect->if_defined)
free(expect->if_defined);
}
if (!pass)
break;
}
fclose(fp);
httpClose(http);
return (pass);
}
static int
expect_matches(
_cups_expect_t *expect,
ipp_tag_t value_tag)
{
int match;
char *of_type,
*next;
if (!expect->of_type)
return (1);
for (of_type = expect->of_type, match = 0; !match && of_type; of_type = next)
{
if ((next = strchr(of_type, '|')) != NULL)
*next = '\0';
if (!strcmp(of_type, "text"))
match = value_tag == IPP_TAG_TEXTLANG || value_tag == IPP_TAG_TEXT;
else if (!strcmp(of_type, "name"))
match = value_tag == IPP_TAG_NAMELANG || value_tag == IPP_TAG_NAME;
else if (!strcmp(of_type, "collection"))
match = value_tag == IPP_TAG_BEGIN_COLLECTION;
else
match = value_tag == ippTagValue(of_type);
if (next)
*next++ = '|';
}
return (match);
}
static char *
get_token(FILE *fp,
char *buf,
int buflen,
int *linenum)
{
int ch,
quote;
char *bufptr,
*bufend;
for (;;)
{
while (isspace(ch = getc(fp)))
{
if (ch == '\n')
(*linenum) ++;
}
if (ch == EOF)
return (NULL);
else if (ch == '\'' || ch == '\"')
{
quote = ch;
bufptr = buf;
bufend = buf + buflen - 1;
while ((ch = getc(fp)) != EOF)
if (ch == quote)
break;
else if (bufptr < bufend)
*bufptr++ = ch;
*bufptr = '\0';
return (buf);
}
else if (ch == '#')
{
while ((ch = getc(fp)) != EOF)
if (ch == '\n')
break;
(*linenum) ++;
}
else
{
ungetc(ch, fp);
bufptr = buf;
bufend = buf + buflen - 1;
while ((ch = getc(fp)) != EOF)
if (isspace(ch) || ch == '#')
break;
else if (bufptr < bufend)
*bufptr++ = ch;
if (ch == '#')
ungetc(ch, fp);
*bufptr = '\0';
return (buf);
}
}
}
static void
print_attr(ipp_attribute_t *attr)
{
int i;
if (attr->name == NULL)
{
puts(" -- separator --");
return;
}
printf(" %s (%s%s) = ", attr->name,
attr->num_values > 1 ? "1setOf " : "",
ippTagString(attr->value_tag));
switch (attr->value_tag)
{
case IPP_TAG_INTEGER :
case IPP_TAG_ENUM :
for (i = 0; i < attr->num_values; i ++)
printf("%d ", attr->values[i].integer);
break;
case IPP_TAG_BOOLEAN :
for (i = 0; i < attr->num_values; i ++)
if (attr->values[i].boolean)
printf("true ");
else
printf("false ");
break;
case IPP_TAG_NOVALUE :
printf("novalue");
break;
case IPP_TAG_RANGE :
for (i = 0; i < attr->num_values; i ++)
printf("%d-%d ", attr->values[i].range.lower,
attr->values[i].range.upper);
break;
case IPP_TAG_RESOLUTION :
for (i = 0; i < attr->num_values; i ++)
printf("%dx%d%s ", attr->values[i].resolution.xres,
attr->values[i].resolution.yres,
attr->values[i].resolution.units == IPP_RES_PER_INCH ?
"dpi" : "dpc");
break;
case IPP_TAG_STRING :
case IPP_TAG_TEXT :
case IPP_TAG_NAME :
case IPP_TAG_KEYWORD :
case IPP_TAG_CHARSET :
case IPP_TAG_URI :
case IPP_TAG_MIMETYPE :
case IPP_TAG_LANGUAGE :
for (i = 0; i < attr->num_values; i ++)
printf("\"%s\" ", attr->values[i].string.text);
break;
case IPP_TAG_TEXTLANG :
case IPP_TAG_NAMELANG :
for (i = 0; i < attr->num_values; i ++)
printf("\"%s\",%s ", attr->values[i].string.text,
attr->values[i].string.charset);
break;
case IPP_TAG_BEGIN_COLLECTION :
for (i = 0; i < attr->num_values; i ++)
{
if (i)
putchar(' ');
print_col(attr->values[i].collection);
}
break;
default :
break;
}
putchar('\n');
}
static void
print_col(ipp_t *col)
{
int i;
ipp_attribute_t *attr;
putchar('{');
for (attr = col->attrs; attr; attr = attr->next)
{
printf("%s(%s%s)=", attr->name, attr->num_values > 1 ? "1setOf " : "",
ippTagString(attr->value_tag));
switch (attr->value_tag)
{
case IPP_TAG_INTEGER :
case IPP_TAG_ENUM :
for (i = 0; i < attr->num_values; i ++)
printf("%d ", attr->values[i].integer);
break;
case IPP_TAG_BOOLEAN :
for (i = 0; i < attr->num_values; i ++)
if (attr->values[i].boolean)
printf("true ");
else
printf("false ");
break;
case IPP_TAG_NOVALUE :
printf("novalue");
break;
case IPP_TAG_RANGE :
for (i = 0; i < attr->num_values; i ++)
printf("%d-%d ", attr->values[i].range.lower,
attr->values[i].range.upper);
break;
case IPP_TAG_RESOLUTION :
for (i = 0; i < attr->num_values; i ++)
printf("%dx%d%s ", attr->values[i].resolution.xres,
attr->values[i].resolution.yres,
attr->values[i].resolution.units == IPP_RES_PER_INCH ?
"dpi" : "dpc");
break;
case IPP_TAG_STRING :
case IPP_TAG_TEXT :
case IPP_TAG_NAME :
case IPP_TAG_KEYWORD :
case IPP_TAG_CHARSET :
case IPP_TAG_URI :
case IPP_TAG_MIMETYPE :
case IPP_TAG_LANGUAGE :
for (i = 0; i < attr->num_values; i ++)
printf("\"%s\" ", attr->values[i].string.text);
break;
case IPP_TAG_TEXTLANG :
case IPP_TAG_NAMELANG :
for (i = 0; i < attr->num_values; i ++)
printf("\"%s\",%s ", attr->values[i].string.text,
attr->values[i].string.charset);
break;
case IPP_TAG_BEGIN_COLLECTION :
for (i = 0; i < attr->num_values; i ++)
{
print_col(attr->values[i].collection);
putchar(' ');
}
break;
default :
break;
}
}
putchar('}');
}
static void
usage(void)
{
fputs("Usage: ipptest [options] URL testfile [ ... testfileN ]\n", stderr);
fputs("Options:\n", stderr);
fputs("\n", stderr);
fputs("-c Send requests using chunking.\n", stderr);
fputs("-d name=value Define variable.\n", stderr);
fputs("-i seconds Repeat the last test file with the given interval.\n",
stderr);
fputs("-v Show all attributes in response, even on success.\n",
stderr);
exit(1);
}