#include <cups/cups.h>
#include <cups/string.h>
#include <errno.h>
#include <stdlib.h>
#define ERROR_NONE 0
#define ERROR_USAGE 1
#define ERROR_FILE_OPEN 2
#define ERROR_PPD_FORMAT 3
#define ERROR_CONFORMANCE 4
void show_conflicts(ppd_file_t *ppd);
void usage(void);
int
main(int argc,
char *argv[])
{
int i, j, k, m, n;
int len;
char *opt;
const char *ptr;
int files;
int verbose;
int status;
int errors;
int ppdversion;
ppd_status_t error;
int line;
ppd_file_t *ppd;
ppd_attr_t *attr;
ppd_size_t *size;
ppd_group_t *group;
ppd_option_t *option;
ppd_group_t *group2;
ppd_option_t *option2;
ppd_choice_t *choice;
static char *uis[] = { "BOOLEAN", "PICKONE", "PICKMANY" };
static char *sections[] = { "ANY", "DOCUMENT", "EXIT",
"JCL", "PAGE", "PROLOG" };
ppdSetConformance(PPD_CONFORM_STRICT);
verbose = 0;
ppd = NULL;
files = 0;
status = ERROR_NONE;
for (i = 1; i < argc; i ++)
if (argv[i][0] == '-' && argv[i][1])
{
for (opt = argv[i] + 1; *opt; opt ++)
switch (*opt)
{
case 'q' :
if (verbose > 0)
{
fputs("cupstestppd: The -q option is incompatible with the -v option.\n",
stderr);
return (1);
}
verbose --;
break;
case 'r' :
ppdSetConformance(PPD_CONFORM_RELAXED);
break;
case 'v' :
if (verbose < 0)
{
fputs("cupstestppd: The -v option is incompatible with the -q option.\n",
stderr);
return (1);
}
verbose ++;
break;
default :
usage();
break;
}
}
else
{
if (files && verbose >= 0)
putchar('\n');
files ++;
if (argv[i][0] == '-')
{
if (verbose >= 0)
printf("(stdin):");
ppd = ppdOpen(stdin);
}
else if (strlen(argv[i]) > 3 &&
!strcmp(argv[i] + strlen(argv[i]) - 3, ".gz"))
{
char command[1024];
FILE *gunzip;
if (verbose >= 0)
printf("%s:", argv[i]);
snprintf(command, sizeof(command), "gunzip -c \"%s\"", argv[i]);
gunzip = popen(command, "r");
ppd = ppdOpen(gunzip);
if (gunzip != NULL)
pclose(gunzip);
}
else
{
if (verbose >= 0)
printf("%s:", argv[i]);
ppd = ppdOpenFile(argv[i]);
}
if (ppd == NULL)
{
error = ppdLastError(&line);
if (verbose >= 0)
printf(" FAIL\n **FAIL** Unable to open PPD file - ");
if (error <= PPD_ALLOC_ERROR)
{
status = ERROR_FILE_OPEN;
if (verbose >= 0)
puts(strerror(errno));
}
else
{
status = ERROR_PPD_FORMAT;
if (verbose >= 0)
{
printf("%s on line %d.\n", ppdErrorString(error), line);
switch (error)
{
case PPD_MISSING_PPDADOBE4 :
puts(" REF: Page 42, section 5.2.");
break;
case PPD_MISSING_VALUE :
puts(" REF: Page 20, section 3.4.");
break;
case PPD_BAD_OPEN_GROUP :
case PPD_NESTED_OPEN_GROUP :
puts(" REF: Pages 45-46, section 5.2.");
break;
case PPD_BAD_OPEN_UI :
case PPD_NESTED_OPEN_UI :
puts(" REF: Pages 42-45, section 5.2.");
break;
case PPD_BAD_ORDER_DEPENDENCY :
puts(" REF: Pages 48-49, section 5.2.");
break;
case PPD_BAD_UI_CONSTRAINTS :
puts(" REF: Pages 52-54, section 5.2.");
break;
case PPD_MISSING_ASTERISK :
puts(" REF: Page 15, section 3.2.");
break;
case PPD_LINE_TOO_LONG :
puts(" REF: Page 15, section 3.1.");
break;
case PPD_ILLEGAL_CHARACTER :
puts(" REF: Page 15, section 3.1.");
break;
case PPD_ILLEGAL_MAIN_KEYWORD :
puts(" REF: Pages 16-17, section 3.2.");
break;
case PPD_ILLEGAL_OPTION_KEYWORD :
puts(" REF: Page 19, section 3.3.");
break;
case PPD_ILLEGAL_TRANSLATION :
puts(" REF: Page 27, section 3.5.");
break;
default :
break;
}
}
}
continue;
}
errors = 0;
ppdversion = 43;
if (verbose > 0)
puts("\n DETAILED CONFORMANCE TEST RESULTS");
if ((attr = ppdFindAttr(ppd, "FormatVersion", NULL)) != NULL &&
attr->value)
ppdversion = (int)(10 * atof(attr->value) + 0.5);
if (ppdFindAttr(ppd, "DefaultImageableArea", NULL) != NULL)
{
if (verbose > 0)
puts(" PASS DefaultImageableArea");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED DefaultImageableArea");
puts(" REF: Page 102, section 5.15.");
}
errors ++;
}
if (ppdFindAttr(ppd, "DefaultPaperDimension", NULL) != NULL)
{
if (verbose > 0)
puts(" PASS DefaultPaperDimension");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED DefaultPaperDimension");
puts(" REF: Page 103, section 5.15.");
}
errors ++;
}
for (j = 0, group = ppd->groups; j < ppd->num_groups; j ++, group ++)
for (k = 0, option = group->options; k < group->num_options; k ++, option ++)
{
if (option->defchoice[0])
{
if (verbose > 0)
printf(" PASS Default%s\n", option->keyword);
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
printf(" **FAIL** REQUIRED Default%s\n", option->keyword);
puts(" REF: Page 40, section 4.5.");
}
errors ++;
}
}
if (ppdFindAttr(ppd, "FileVersion", NULL) != NULL)
{
if (verbose > 0)
puts(" PASS FileVersion");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED FileVersion");
puts(" REF: Page 56, section 5.3.");
}
errors ++;
}
if (ppdFindAttr(ppd, "FormatVersion", NULL) != NULL)
{
if (verbose > 0)
puts(" PASS FormatVersion");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED FormatVersion");
puts(" REF: Page 56, section 5.3.");
}
errors ++;
}
if (ppd->lang_encoding != NULL)
{
if (verbose > 0)
puts(" PASS LanguageEncoding");
}
else if (ppdversion > 40)
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED LanguageEncoding");
puts(" REF: Pages 56-57, section 5.3.");
}
errors ++;
}
if (ppd->lang_version != NULL)
{
if (verbose > 0)
puts(" PASS LanguageVersion");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED LanguageVersion");
puts(" REF: Pages 57-58, section 5.3.");
}
errors ++;
}
if (ppd->manufacturer != NULL)
{
if (!strncasecmp(ppd->manufacturer, "Hewlett-Packard", 15) ||
!strncasecmp(ppd->manufacturer, "Hewlett Packard", 15))
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** BAD Manufacturer (should be \"HP\")");
puts(" REF: Page 211, table D.1.");
}
errors ++;
}
else if (verbose > 0)
puts(" PASS Manufacturer");
}
else if (ppdversion >= 43)
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED Manufacturer");
puts(" REF: Pages 58-59, section 5.3.");
}
errors ++;
}
if (ppd->modelname != NULL)
{
for (ptr = ppd->modelname; *ptr; ptr ++)
if (!isalnum(*ptr) && !strchr(" ./-+", *ptr))
break;
if (*ptr)
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
printf(" **FAIL** BAD ModelName - \"%c\" not allowed in string.\n",
*ptr);
puts(" REF: Pages 59-60, section 5.3.");
}
errors ++;
}
else if (verbose > 0)
puts(" PASS ModelName");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED ModelName");
puts(" REF: Pages 59-60, section 5.3.");
}
errors ++;
}
if (ppd->nickname != NULL)
{
if (verbose > 0)
puts(" PASS NickName");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED NickName");
puts(" REF: Page 60, section 5.3.");
}
errors ++;
}
if (ppdFindOption(ppd, "PageSize") != NULL)
{
if (verbose > 0)
puts(" PASS PageSize");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED PageSize");
puts(" REF: Pages 99-100, section 5.14.");
}
errors ++;
}
if (ppdFindOption(ppd, "PageRegion") != NULL)
{
if (verbose > 0)
puts(" PASS PageRegion");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED PageRegion");
puts(" REF: Page 100, section 5.14.");
}
errors ++;
}
if (ppd->pcfilename != NULL)
{
if (verbose > 0)
puts(" PASS PCFileName");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED PCFileName");
puts(" REF: Pages 61-62, section 5.3.");
}
errors ++;
}
if (ppd->product != NULL)
{
if (ppd->product[0] != '(' ||
ppd->product[strlen(ppd->product) - 1] != ')')
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** BAD Product - not \"(string)\".");
puts(" REF: Page 62, section 5.3.");
}
errors ++;
}
else if (verbose > 0)
puts(" PASS Product");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED Product");
puts(" REF: Page 62, section 5.3.");
}
errors ++;
}
if ((attr = ppdFindAttr(ppd, "PSVersion", NULL)) != NULL &&
attr->value != NULL)
{
char junkstr[255];
int junkint;
if (sscanf(attr->value, "(%[^)])%d", junkstr, &junkint) != 2)
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** BAD PSVersion - not \"(string) int\".");
puts(" REF: Pages 62-64, section 5.3.");
}
errors ++;
}
else if (verbose > 0)
puts(" PASS PSVersion");
}
else
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED PSVersion");
puts(" REF: Pages 62-64, section 5.3.");
}
errors ++;
}
if (ppd->shortnickname != NULL)
{
if (strlen(ppd->shortnickname) > 31)
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** BAD ShortNickName - longer than 31 chars.");
puts(" REF: Pages 64-65, section 5.3.");
}
errors ++;
}
else if (verbose > 0)
puts(" PASS ShortNickName");
}
else if (ppdversion >= 43)
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** REQUIRED ShortNickName");
puts(" REF: Page 64-65, section 5.3.");
}
errors ++;
}
if (ppd->patches != NULL && strchr(ppd->patches, '\"') &&
strstr(ppd->patches, "*End"))
{
if (verbose >= 0)
{
if (!errors && !verbose)
puts(" FAIL");
puts(" **FAIL** BAD JobPatchFile attribute in file");
puts(" REF: Page 24, section 3.4.");
}
errors ++;
}
if (errors)
status = ERROR_CONFORMANCE;
else if (!verbose)
puts(" PASS");
if (verbose >= 0)
{
ppdMarkDefaults(ppd);
if (ppdConflicts(ppd))
{
puts(" WARN Default choices conflicting!");
show_conflicts(ppd);
}
if (ppdversion < 43)
{
printf(" WARN Obsolete PPD version %.1f!\n",
0.1f * ppdversion);
puts(" REF: Page 42, section 5.2.");
}
if (!ppd->lang_encoding && ppdversion < 41)
{
puts(" WARN LanguageEncoding required by PPD 4.3 spec.");
puts(" REF: Pages 56-57, section 5.3.");
}
if (!ppd->manufacturer && ppdversion < 43)
{
puts(" WARN Manufacturer required by PPD 4.3 spec.");
puts(" REF: Pages 58-59, section 5.3.");
}
if (ppd->pcfilename && strlen(ppd->pcfilename) > 12)
{
puts(" WARN PCFileName longer than 8.3 in violation of PPD spec.");
puts(" REF: Pages 61-62, section 5.3.");
}
if (!ppd->shortnickname && ppdversion < 43)
{
puts(" WARN ShortNickName required by PPD 4.3 spec.");
puts(" REF: Pages 64-65, section 5.3.");
}
for (j = 0, group = ppd->groups; j < ppd->num_groups; j ++, group ++)
for (k = 0, option = group->options; k < group->num_options; k ++, option ++)
{
len = strlen(option->keyword);
for (m = 0, group2 = ppd->groups;
m < ppd->num_groups;
m ++, group2 ++)
for (n = 0, option2 = group2->options;
n < group2->num_options;
n ++, option2 ++)
if (option != option2 &&
len < strlen(option2->keyword) &&
!strncmp(option->keyword, option2->keyword, len))
{
printf(" WARN %s shares a common prefix with %s\n",
option->keyword, option2->keyword);
puts(" REF: Page 15, section 3.2.");
}
}
}
if (verbose > 0)
{
if (errors)
printf(" %d ERROR%s FOUND\n", errors, errors == 1 ? "" : "S");
else
puts(" NO ERRORS FOUND");
}
if (verbose > 1)
{
puts("");
printf(" language_level = %d\n", ppd->language_level);
printf(" color_device = %s\n", ppd->color_device ? "TRUE" : "FALSE");
printf(" variable_sizes = %s\n", ppd->variable_sizes ? "TRUE" : "FALSE");
printf(" landscape = %d\n", ppd->landscape);
switch (ppd->colorspace)
{
case PPD_CS_CMYK :
puts(" colorspace = PPD_CS_CMYK");
break;
case PPD_CS_CMY :
puts(" colorspace = PPD_CS_CMY");
break;
case PPD_CS_GRAY :
puts(" colorspace = PPD_CS_GRAY");
break;
case PPD_CS_RGB :
puts(" colorspace = PPD_CS_RGB");
break;
default :
puts(" colorspace = <unknown>");
break;
}
printf(" num_emulations = %d\n", ppd->num_emulations);
for (j = 0; j < ppd->num_emulations; j ++)
printf(" emulations[%d] = %s\n", j, ppd->emulations[j].name);
printf(" lang_encoding = %s\n", ppd->lang_encoding);
printf(" lang_version = %s\n", ppd->lang_version);
printf(" modelname = %s\n", ppd->modelname);
printf(" ttrasterizer = %s\n",
ppd->ttrasterizer == NULL ? "None" : ppd->ttrasterizer);
printf(" manufacturer = %s\n", ppd->manufacturer);
printf(" product = %s\n", ppd->product);
printf(" nickname = %s\n", ppd->nickname);
printf(" shortnickname = %s\n", ppd->shortnickname);
printf(" patches = %d bytes\n",
ppd->patches == NULL ? 0 : (int)strlen(ppd->patches));
printf(" num_groups = %d\n", ppd->num_groups);
for (j = 0, group = ppd->groups; j < ppd->num_groups; j ++, group ++)
{
printf(" group[%d] = %s\n", j, group->text);
for (k = 0, option = group->options; k < group->num_options; k ++, option ++)
{
printf(" options[%d] = %s (%s) %s %s %.0f (%d choices)\n", k,
option->keyword, option->text, uis[option->ui],
sections[option->section], option->order,
option->num_choices);
if (strcmp(option->keyword, "PageSize") == 0 ||
strcmp(option->keyword, "PageRegion") == 0)
{
for (m = option->num_choices, choice = option->choices;
m > 0;
m --, choice ++)
{
size = ppdPageSize(ppd, choice->choice);
if (size == NULL)
printf(" %s (%s) = ERROR", choice->choice, choice->text);
else
printf(" %s (%s) = %.2fx%.2fin (%.1f,%.1f,%.1f,%.1f)", choice->choice,
choice->text, size->width / 72.0, size->length / 72.0,
size->left / 72.0, size->bottom / 72.0,
size->right / 72.0, size->top / 72.0);
if (strcmp(option->defchoice, choice->choice) == 0)
puts(" *");
else
putchar('\n');
}
}
else
{
for (m = option->num_choices, choice = option->choices;
m > 0;
m --, choice ++)
{
printf(" %s (%s)", choice->choice, choice->text);
if (strcmp(option->defchoice, choice->choice) == 0)
puts(" *");
else
putchar('\n');
}
}
}
}
printf(" num_profiles = %d\n", ppd->num_profiles);
for (j = 0; j < ppd->num_profiles; j ++)
printf(" profiles[%d] = %s/%s %.3f %.3f [ %.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f ]\n",
j, ppd->profiles[j].resolution, ppd->profiles[j].media_type,
ppd->profiles[j].gamma, ppd->profiles[j].density,
ppd->profiles[j].matrix[0][0], ppd->profiles[j].matrix[0][1],
ppd->profiles[j].matrix[0][2], ppd->profiles[j].matrix[1][0],
ppd->profiles[j].matrix[1][1], ppd->profiles[j].matrix[1][2],
ppd->profiles[j].matrix[2][0], ppd->profiles[j].matrix[2][1],
ppd->profiles[j].matrix[2][2]);
printf(" num_fonts = %d\n", ppd->num_fonts);
for (j = 0; j < ppd->num_fonts; j ++)
printf(" fonts[%d] = %s\n", j, ppd->fonts[j]);
printf(" num_attrs = %d\n", ppd->num_attrs);
for (j = 0; j < ppd->num_attrs; j ++)
printf(" attrs[%d] = %s %s%s%s: \"%s\"\n", j,
ppd->attrs[j]->name, ppd->attrs[j]->spec,
ppd->attrs[j]->text[0] ? "/" : "", ppd->attrs[j]->text,
ppd->attrs[j]->value ? ppd->attrs[j]->value : "(null)");
}
ppdClose(ppd);
}
if (!files)
usage();
return (status);
}
void
show_conflicts(ppd_file_t *ppd)
{
int i, j;
ppd_const_t *c;
ppd_option_t *o1, *o2;
ppd_choice_t *c1, *c2;
for (i = ppd->num_consts, c = ppd->consts; i > 0; i --, c ++)
{
o1 = ppdFindOption(ppd, c->option1);
if (o1 == NULL)
continue;
else if (c->choice1[0] != '\0')
{
c1 = ppdFindChoice(o1, c->choice1);
}
else
{
for (j = o1->num_choices, c1 = o1->choices; j > 0; j --, c1 ++)
if (c1->marked)
break;
if (j == 0 ||
strcasecmp(c1->choice, "None") == 0 ||
strcasecmp(c1->choice, "Off") == 0 ||
strcasecmp(c1->choice, "False") == 0)
c1 = NULL;
}
o2 = ppdFindOption(ppd, c->option2);
if (o2 == NULL)
continue;
else if (c->choice2[0] != '\0')
{
c2 = ppdFindChoice(o2, c->choice2);
}
else
{
for (j = o2->num_choices, c2 = o2->choices; j > 0; j --, c2 ++)
if (c2->marked)
break;
if (j == 0 ||
strcasecmp(c2->choice, "None") == 0 ||
strcasecmp(c2->choice, "Off") == 0 ||
strcasecmp(c2->choice, "False") == 0)
c2 = NULL;
}
if (c1 != NULL && c1->marked && c2 != NULL && c2->marked)
printf(" WARN \"%s %s\" conflicts with \"%s %s\"\n"
" (constraint=\"%s %s %s %s\")\n",
o1->keyword, c1->choice, o2->keyword, c2->choice,
c->option1, c->choice1, c->option2, c->choice2);
}
}
void
usage(void)
{
puts("Usage: cupstestppd [-q] [-r] [-v[v]] filename1.ppd[.gz] [... filenameN.ppd[.gz]]");
puts(" program | cupstestppd [-q] [-r] [-v[v]] -");
exit(ERROR_USAGE);
}