+
+ /*
+ * Check that the groups do not have any duplicate names...
+ */
+
+ for (i = ppd->num_groups, groupa = ppd->groups; i > 1; i --, groupa ++)
+ for (j = i - 1, groupb = groupa + 1; j > 0; j --, groupb ++)
+ if (!strcasecmp(groupa->name, groupb->name))
+ {
+ if (!errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" **FAIL** Group names %s and %s differ only "
+ "by case\n"),
+ groupa->name, groupb->name);
+
+ errors ++;
+ }
+
+ /*
+ * Check that the options do not have any duplicate names...
+ */
+
+ for (optiona = ppdFirstOption(ppd); optiona; optiona = ppdNextOption(ppd))
+ {
+ cupsArraySave(ppd->options);
+ for (optionb = ppdNextOption(ppd); optionb; optionb = ppdNextOption(ppd))
+ if (!strcasecmp(optiona->keyword, optionb->keyword))
+ {
+ if (!errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" **FAIL** Option names %s and %s differ only "
+ "by case\n"),
+ optiona->keyword, optionb->keyword);
+
+ errors ++;
+ }
+ cupsArrayRestore(ppd->options);
+
+ /*
+ * Then the choices...
+ */
+
+ for (i = optiona->num_choices, choicea = optiona->choices;
+ i > 1;
+ i --, choicea ++)
+ for (j = i - 1, choiceb = choicea + 1; j > 0; j --, choiceb ++)
+ if (!strcmp(choicea->choice, choiceb->choice))
+ {
+ if (!errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" **FAIL** Multiple occurrences of %s "
+ "choice name %s\n"),
+ optiona->keyword, choicea->choice);
+
+ errors ++;
+
+ choicea ++;
+ i --;
+ break;
+ }
+ else if (!strcasecmp(choicea->choice, choiceb->choice))
+ {
+ if (!errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" **FAIL** %s choice names %s and %s "
+ "differ only by case\n"),
+ optiona->keyword, choicea->choice, choiceb->choice);
+
+ errors ++;
+ }
+ }
+
+ /*
+ * Return the number of errors found...
+ */
+
+ return (errors);
+}
+
+
+/*
+ * 'check_defaults()' - Check default option keywords in the PPD file.
+ */
+
+static int /* O - Errors found */
+check_defaults(ppd_file_t *ppd, /* I - PPD file */
+ int errors, /* I - Errors found */
+ int verbose, /* I - Verbosity level */
+ int warn) /* I - Warnings only? */
+{
+ int j, k; /* Looping vars */
+ ppd_attr_t *attr; /* PPD attribute */
+ ppd_option_t *option; /* Standard UI option */
+ const char *prefix; /* WARN/FAIL prefix */
+
+
+ prefix = warn ? " WARN " : "**FAIL**";
+
+ for (j = 0; j < ppd->num_attrs; j ++)
+ {
+ attr = ppd->attrs[j];
+
+ if (!strcmp(attr->name, "DefaultColorSpace") ||
+ !strcmp(attr->name, "DefaultFont") ||
+ !strcmp(attr->name, "DefaultHalftoneType") ||
+ !strcmp(attr->name, "DefaultImageableArea") ||
+ !strcmp(attr->name, "DefaultLeadingEdge") ||
+ !strcmp(attr->name, "DefaultOutputOrder") ||
+ !strcmp(attr->name, "DefaultPaperDimension") ||
+ !strcmp(attr->name, "DefaultResolution") ||
+ !strcmp(attr->name, "DefaultTransfer"))
+ continue;
+
+ if (!strncmp(attr->name, "Default", 7))
+ {
+ if ((option = ppdFindOption(ppd, attr->name + 7)) != NULL &&
+ strcmp(attr->value, "Unknown"))
+ {
+ /*
+ * Check that the default option value matches a choice...
+ */
+
+ for (k = 0; k < option->num_choices; k ++)
+ if (!strcmp(option->choices[k].choice, attr->value))
+ break;
+
+ if (k >= option->num_choices)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s %s %s does not exist\n"),
+ prefix, attr->name, attr->value);
+
+ if (!warn)
+ errors ++;
+ }
+ }
+ }
+ }
+
+ return (errors);
+}
+
+
+/*
+ * 'check_duplex()' - Check duplex keywords in the PPD file.
+ */
+
+static int /* O - Errors found */
+check_duplex(ppd_file_t *ppd, /* I - PPD file */
+ int errors, /* I - Error found */
+ int verbose, /* I - Verbosity level */
+ int warn) /* I - Warnings only? */
+{
+ int i; /* Looping var */
+ ppd_option_t *option; /* PPD option */
+ ppd_choice_t *choice; /* Current choice */
+ const char *prefix; /* Message prefix */
+
+
+ prefix = warn ? " WARN " : "**FAIL**";
+
+ /*
+ * Check for a duplex option, and for standard values...
+ */
+
+ if ((option = ppdFindOption(ppd, "Duplex")) != NULL)
+ {
+ if (!ppdFindChoice(option, "None"))
+ {
+ if (verbose >= 0)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ _cupsLangPrintf(stdout,
+ _(" %s REQUIRED %s does not define "
+ "choice None\n"
+ " REF: Page 122, section 5.17\n"),
+ prefix, option->keyword);
+ }
+
+ if (!warn)
+ errors ++;
+ }
+
+ for (i = option->num_choices, choice = option->choices;
+ i > 0;
+ i --, choice ++)
+ if (strcmp(choice->choice, "None") &&
+ strcmp(choice->choice, "DuplexNoTumble") &&
+ strcmp(choice->choice, "DuplexTumble") &&
+ strcmp(choice->choice, "SimplexTumble"))
+ {
+ if (verbose >= 0)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ _cupsLangPrintf(stdout,
+ _(" %s Bad %s choice %s\n"
+ " REF: Page 122, section 5.17\n"),
+ prefix, option->keyword, choice->choice);
+ }
+
+ if (!warn)
+ errors ++;
+ }
+ }
+
+ return (errors);
+}
+
+
+/*
+ * 'check_filters()' - Check filters in the PPD file.
+ */
+
+static int /* O - Errors found */
+check_filters(ppd_file_t *ppd, /* I - PPD file */
+ const char *root, /* I - Root directory */
+ int errors, /* I - Errors found */
+ int verbose, /* I - Verbosity level */
+ int warn) /* I - Warnings only? */
+{
+ int i; /* Looping var */
+ ppd_attr_t *attr; /* PPD attribute */
+ const char *ptr; /* Pointer into string */
+ char super[16], /* Super-type for filter */
+ type[256], /* Type for filter */
+ program[1024], /* Program/filter name */
+ pathprog[1024]; /* Complete path to program/filter */
+ int cost; /* Cost of filter */
+ const char *prefix; /* WARN/FAIL prefix */
+ struct stat fileinfo; /* File information */
+
+
+ prefix = warn ? " WARN " : "**FAIL**";
+
+ /*
+ * cupsFilter
+ */
+
+ for (i = 0; i < ppd->num_filters; i ++)
+ {
+ if (sscanf(ppd->filters[i], "%15[^/]/%255s%d%*[ \t]%1023[^\n]", super, type,
+ &cost, program) != 4)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad cupsFilter value \"%s\"\n"),
+ prefix, ppd->filters[i]);
+
+ if (!warn)
+ errors ++;
+ }
+ else if (strcmp(program, "-"))
+ {
+ if (program[0] == '/')
+ snprintf(pathprog, sizeof(pathprog), "%s%s", root, program);
+ else
+ {
+ if ((ptr = getenv("CUPS_SERVERBIN")) == NULL)
+ ptr = CUPS_SERVERBIN;
+
+ if (*ptr == '/' || !*root)
+ snprintf(pathprog, sizeof(pathprog), "%s%s/filter/%s", root, ptr,
+ program);
+ else
+ snprintf(pathprog, sizeof(pathprog), "%s/%s/filter/%s", root, ptr,
+ program);
+ }
+
+ if (stat(pathprog, &fileinfo))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Missing cupsFilter "
+ "file \"%s\"\n"), prefix, pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else if (fileinfo.st_uid != 0 ||
+ (fileinfo.st_mode & MODE_WRITE) ||
+ (fileinfo.st_mode & MODE_MASK) != MODE_PROGRAM)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Bad permissions on cupsFilter "
+ "file \"%s\"\n"), prefix, pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else
+ errors = valid_path("cupsFilter", pathprog, errors, verbose, warn);
+ }
+ }
+
+ /*
+ * cupsPreFilter
+ */
+
+ for (attr = ppdFindAttr(ppd, "cupsPreFilter", NULL);
+ attr;
+ attr = ppdFindNextAttr(ppd, "cupsPreFilter", NULL))
+ {
+ if (strcmp(attr->name, "cupsPreFilter"))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad spelling of %s - should be %s\n"),
+ prefix, attr->name, "cupsPreFilter");
+
+ if (!warn)
+ errors ++;
+ }
+
+ if (!attr->value ||
+ sscanf(attr->value, "%15[^/]/%255s%d%*[ \t]%1023[^\n]", super, type,
+ &cost, program) != 4)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad cupsPreFilter value \"%s\"\n"),
+ prefix, attr->value ? attr->value : "");
+
+ if (!warn)
+ errors ++;
+ }
+ else if (strcmp(program, "-"))
+ {
+ if (program[0] == '/')
+ snprintf(pathprog, sizeof(pathprog), "%s%s", root, program);
+ else
+ {
+ if ((ptr = getenv("CUPS_SERVERBIN")) == NULL)
+ ptr = CUPS_SERVERBIN;
+
+ if (*ptr == '/' || !*root)
+ snprintf(pathprog, sizeof(pathprog), "%s%s/filter/%s", root, ptr,
+ program);
+ else
+ snprintf(pathprog, sizeof(pathprog), "%s/%s/filter/%s", root, ptr,
+ program);
+ }
+
+ if (stat(pathprog, &fileinfo))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Missing cupsPreFilter "
+ "file \"%s\"\n"), prefix, pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else if (fileinfo.st_uid != 0 ||
+ (fileinfo.st_mode & MODE_WRITE) ||
+ (fileinfo.st_mode & MODE_MASK) != MODE_PROGRAM)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Bad permissions on "
+ "cupsPreFilter file \"%s\"\n"), prefix,
+ pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else
+ errors = valid_path("cupsPreFilter", pathprog, errors, verbose, warn);
+ }
+ }
+
+#ifdef __APPLE__
+ /*
+ * APDialogExtension
+ */
+
+ for (attr = ppdFindAttr(ppd, "APDialogExtension", NULL);
+ attr != NULL;
+ attr = ppdFindNextAttr(ppd, "APDialogExtension", NULL))
+ {
+ if (strcmp(attr->name, "APDialogExtension"))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad spelling of %s - should be %s\n"),
+ prefix, attr->name, "APDialogExtension");
+
+ if (!warn)
+ errors ++;
+ }
+
+ snprintf(pathprog, sizeof(pathprog), "%s%s", root,
+ attr->value ? attr->value : "(null)");
+
+ if (!attr->value || stat(pathprog, &fileinfo))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Missing "
+ "APDialogExtension file \"%s\"\n"),
+ prefix, pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else if (fileinfo.st_uid != 0 ||
+ (fileinfo.st_mode & MODE_WRITE) ||
+ (fileinfo.st_mode & MODE_MASK) != MODE_DIRECTORY)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Bad permissions on "
+ "APDialogExtension file \"%s\"\n"), prefix,
+ pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else
+ errors = valid_path("APDialogExtension", pathprog, errors, verbose,
+ warn);
+ }
+
+ /*
+ * APPrinterIconPath
+ */
+
+ if ((attr = ppdFindAttr(ppd, "APPrinterIconPath", NULL)) != NULL)
+ {
+ if (strcmp(attr->name, "APPrinterIconPath"))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad spelling of %s - should be %s\n"),
+ prefix, attr->name, "APPrinterIconPath");
+
+ if (!warn)
+ errors ++;
+ }
+
+ snprintf(pathprog, sizeof(pathprog), "%s%s", root,
+ attr->value ? attr->value : "(null)");
+
+ if (!attr->value || stat(pathprog, &fileinfo))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Missing "
+ "APPrinterIconPath file \"%s\"\n"),
+ prefix, pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else if (fileinfo.st_uid != 0 ||
+ (fileinfo.st_mode & MODE_WRITE) ||
+ (fileinfo.st_mode & MODE_MASK) != MODE_DATAFILE)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Bad permissions on "
+ "APPrinterIconPath file \"%s\"\n"), prefix,
+ pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else
+ errors = valid_path("APPrinterIconPath", pathprog, errors, verbose,
+ warn);
+ }
+
+ /*
+ * APPrinterLowInkTool
+ */
+
+ if ((attr = ppdFindAttr(ppd, "APPrinterLowInkTool", NULL)) != NULL)
+ {
+ if (strcmp(attr->name, "APPrinterLowInkTool"))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad spelling of %s - should be %s\n"),
+ prefix, attr->name, "APPrinterLowInkTool");
+
+ if (!warn)
+ errors ++;
+ }
+
+ snprintf(pathprog, sizeof(pathprog), "%s%s", root,
+ attr->value ? attr->value : "(null)");
+
+ if (!attr->value || stat(pathprog, &fileinfo))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Missing "
+ "APPrinterLowInkTool file \"%s\"\n"),
+ prefix, pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else if (fileinfo.st_uid != 0 ||
+ (fileinfo.st_mode & MODE_WRITE) ||
+ (fileinfo.st_mode & MODE_MASK) != MODE_DIRECTORY)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Bad permissions on "
+ "APPrinterLowInkTool file \"%s\"\n"), prefix,
+ pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else
+ errors = valid_path("APPrinterLowInkTool", pathprog, errors, verbose,
+ warn);
+ }
+
+ /*
+ * APPrinterUtilityPath
+ */
+
+ if ((attr = ppdFindAttr(ppd, "APPrinterUtilityPath", NULL)) != NULL)
+ {
+ if (strcmp(attr->name, "APPrinterUtilityPath"))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad spelling of %s - should be %s\n"),
+ prefix, attr->name, "APPrinterUtilityPath");
+
+ if (!warn)
+ errors ++;
+ }
+
+ snprintf(pathprog, sizeof(pathprog), "%s%s", root,
+ attr->value ? attr->value : "(null)");
+
+ if (!attr->value || stat(pathprog, &fileinfo))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Missing "
+ "APPrinterUtilityPath file \"%s\"\n"),
+ prefix, pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else if (fileinfo.st_uid != 0 ||
+ (fileinfo.st_mode & MODE_WRITE) ||
+ (fileinfo.st_mode & MODE_MASK) != MODE_DIRECTORY)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Bad permissions on "
+ "APPrinterUtilityPath file \"%s\"\n"), prefix,
+ pathprog);
+
+ if (!warn)
+ errors ++;
+ }
+ else
+ errors = valid_path("APPrinterUtilityPath", pathprog, errors, verbose,
+ warn);
+ }
+
+ /*
+ * APScanAppBundleID and APScanAppPath
+ */
+
+ if ((attr = ppdFindAttr(ppd, "APScanAppPath", NULL)) != NULL)
+ {
+ if (strcmp(attr->name, "APScanAppPath"))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad spelling of %s - should be %s\n"),
+ prefix, attr->name, "APScanAppPath");
+
+ if (!warn)
+ errors ++;
+ }
+
+ if (!attr->value || stat(attr->value, &fileinfo))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Missing "
+ "APScanAppPath file \"%s\"\n"),
+ prefix, attr->value ? attr->value : "<NULL>");
+
+ if (!warn)
+ errors ++;
+ }
+ else if (fileinfo.st_uid != 0 ||
+ (fileinfo.st_mode & MODE_WRITE) ||
+ (fileinfo.st_mode & MODE_MASK) != MODE_DIRECTORY)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Bad permissions on "
+ "APScanAppPath file \"%s\"\n"), prefix,
+ attr->value);
+
+ if (!warn)
+ errors ++;
+ }
+ else
+ errors = valid_path("APScanAppPath", attr->value, errors, verbose,
+ warn);
+
+ if (ppdFindAttr(ppd, "APScanAppBundleID", NULL))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Cannot provide both "
+ "APScanAppPath and APScanAppBundleID\n"),
+ prefix);
+
+ if (!warn)
+ errors ++;
+ }
+ }
+#endif /* __APPLE__ */
+
+ return (errors);
+}
+
+
+/*
+ * 'check_profiles()' - Check ICC color profiles in the PPD file.
+ */
+
+static int /* O - Errors found */
+check_profiles(ppd_file_t *ppd, /* I - PPD file */
+ const char *root, /* I - Root directory */
+ int errors, /* I - Errors found */
+ int verbose, /* I - Verbosity level */
+ int warn) /* I - Warnings only? */
+{
+ int i; /* Looping var */
+ ppd_attr_t *attr; /* PPD attribute */
+ const char *ptr; /* Pointer into string */
+ const char *prefix; /* WARN/FAIL prefix */
+ char filename[1024]; /* Profile filename */
+ struct stat fileinfo; /* File information */
+ int num_profiles = 0; /* Number of profiles */
+ unsigned hash, /* Current hash value */
+ hashes[1000]; /* Hash values of profile names */
+ const char *specs[1000]; /* Specifiers for profiles */
+
+
+ prefix = warn ? " WARN " : "**FAIL**";
+
+ for (attr = ppdFindAttr(ppd, "cupsICCProfile", NULL);
+ attr;
+ attr = ppdFindNextAttr(ppd, "cupsICCProfile", NULL))
+ {
+ /*
+ * Check for valid selector...
+ */
+
+ for (i = 0, ptr = strchr(attr->spec, '.'); ptr; ptr = strchr(ptr + 1, '.'))
+ i ++;
+
+ if (!attr->value || i < 2)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad cupsICCProfile %s\n"),
+ prefix, attr->spec);
+
+ if (!warn)
+ errors ++;
+
+ continue;
+ }
+
+ /*
+ * Check for valid profile filename...
+ */
+
+ if (attr->value[0] == '/')
+ snprintf(filename, sizeof(filename), "%s%s", root, attr->value);
+ else
+ {
+ if ((ptr = getenv("CUPS_DATADIR")) == NULL)
+ ptr = CUPS_DATADIR;
+
+ if (*ptr == '/' || !*root)
+ snprintf(filename, sizeof(filename), "%s%s/profiles/%s", root, ptr,
+ attr->value);
+ else
+ snprintf(filename, sizeof(filename), "%s/%s/profiles/%s", root, ptr,
+ attr->value);
+ }
+
+ if (stat(filename, &fileinfo))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Missing cupsICCProfile "
+ "file \"%s\"\n"), prefix, filename);
+
+ if (!warn)
+ errors ++;
+ }
+ else if (fileinfo.st_uid != 0 ||
+ (fileinfo.st_mode & MODE_WRITE) ||
+ (fileinfo.st_mode & MODE_MASK) != MODE_DATAFILE)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout, _(" %s Bad permissions on "
+ "cupsICCProfile file \"%s\"\n"), prefix,
+ filename);
+
+ if (!warn)
+ errors ++;
+ }
+ else
+ errors = valid_path("cupsICCProfile", filename, errors, verbose, warn);
+
+ /*
+ * Check for hash collisions...
+ */
+
+ hash = _ppdHashName(attr->spec);
+
+ if (num_profiles > 0)
+ {
+ for (i = 0; i < num_profiles; i ++)
+ if (hashes[i] == hash)
+ break;
+
+ if (i < num_profiles)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s cupsICCProfile %s hash value "
+ "collides with %s\n"), prefix, attr->spec,
+ specs[i]);
+
+ if (!warn)
+ errors ++;
+ }
+ }
+
+ /*
+ * Remember up to 1000 profiles...
+ */
+
+ if (num_profiles < 1000)
+ {
+ hashes[num_profiles] = hash;
+ specs[num_profiles] = attr->spec;
+ num_profiles ++;
+ }
+ }
+
+ return (errors);
+}
+
+
+/*
+ * 'check_sizes()' - Check media sizes in the PPD file.
+ */
+
+static int /* O - Errors found */
+check_sizes(ppd_file_t *ppd, /* I - PPD file */
+ int errors, /* I - Errors found */
+ int verbose, /* I - Verbosity level */
+ int warn) /* I - Warnings only? */
+{
+ int i; /* Looping vars */
+ ppd_size_t *size; /* Current size */
+ int width, /* Custom width */
+ length; /* Custom length */
+ char name[PPD_MAX_NAME], /* Size name without dot suffix */
+ *nameptr; /* Pointer into name */
+ const char *prefix; /* WARN/FAIL prefix */
+ ppd_option_t *page_size, /* PageSize option */
+ *page_region; /* PageRegion option */
+
+
+ prefix = warn ? " WARN " : "**FAIL**";
+
+ if ((page_size = ppdFindOption(ppd, "PageSize")) == NULL && warn != 2)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Missing REQUIRED PageSize option\n"
+ " REF: Page 99, section 5.14.\n"),
+ prefix);
+
+ if (!warn)
+ errors ++;
+ }
+
+ if ((page_region = ppdFindOption(ppd, "PageRegion")) == NULL && warn != 2)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Missing REQUIRED PageRegion option\n"
+ " REF: Page 100, section 5.14.\n"),
+ prefix);
+
+ if (!warn)
+ errors ++;
+ }
+
+ for (i = ppd->num_sizes, size = ppd->sizes; i > 0; i --, size ++)
+ {
+ /*
+ * Check that the size name is standard...
+ */
+
+ if (!strcmp(size->name, "Custom"))
+ {
+ /*
+ * Skip custom page size...
+ */
+
+ continue;
+ }
+ else if (warn != 2 && size->name[0] == 'w' &&
+ sscanf(size->name, "w%dh%d", &width, &length) == 2)
+ {
+ /*
+ * Validate device-specific size wNNNhNNN should have proper width and
+ * length...
+ */
+
+ if (fabs(width - size->width) >= 1.0 ||
+ fabs(length - size->length) >= 1.0)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Size \"%s\" has unexpected dimensions "
+ "(%gx%g)\n"),
+ prefix, size->name, size->width, size->length);
+
+ if (!warn)
+ errors ++;
+ }
+ }
+ else if (warn && verbose >= 0)
+ {
+ /*
+ * Lookup the size name in the standard size table...
+ */
+
+ strlcpy(name, size->name, sizeof(name));
+ if ((nameptr = strchr(name, '.')) != NULL)
+ *nameptr = '\0';
+
+ if (!bsearch(name, adobe_size_names,
+ sizeof(adobe_size_names) /
+ sizeof(adobe_size_names[0]),
+ sizeof(adobe_size_names[0]),
+ (int (*)(const void *, const void *))strcmp))
+ {
+ _cupsLangPrintf(stdout,
+ _(" %s Non-standard size name \"%s\"\n"
+ " REF: Page 187, section B.2.\n"),
+ prefix, size->name);
+ }
+ }
+
+ /*
+ * Verify that the size is defined for both PageSize and PageRegion...
+ */
+
+ if (warn != 2 && !ppdFindChoice(page_size, size->name))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Size \"%s\" defined for %s but not for "
+ "%s\n"),
+ prefix, size->name, "PageRegion", "PageSize");
+
+ if (!warn)
+ errors ++;
+ }
+ else if (warn != 2 && !ppdFindChoice(page_region, size->name))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Size \"%s\" defined for %s but not for "
+ "%s\n"),
+ prefix, size->name, "PageSize", "PageRegion");
+
+ if (!warn)
+ errors ++;
+ }
+ }
+
+ return (errors);
+}
+
+
+/*
+ * 'check_translations()' - Check translations in the PPD file.
+ */
+
+static int /* O - Errors found */
+check_translations(ppd_file_t *ppd, /* I - PPD file */
+ int errors, /* I - Errors found */
+ int verbose, /* I - Verbosity level */
+ int warn) /* I - Warnings only? */
+{
+ int j; /* Looping var */
+ ppd_attr_t *attr; /* PPD attribute */
+ cups_array_t *languages; /* Array of languages */
+ int langlen; /* Length of language */
+ char *language, /* Current language */
+ keyword[PPD_MAX_NAME], /* Localization keyword (full) */
+ llkeyword[PPD_MAX_NAME],/* Localization keyword (base) */
+ ckeyword[PPD_MAX_NAME], /* Custom option keyword (full) */
+ cllkeyword[PPD_MAX_NAME];
+ /* Custom option keyword (base) */
+ ppd_option_t *option; /* Standard UI option */
+ ppd_coption_t *coption; /* Custom option */
+ ppd_cparam_t *cparam; /* Custom parameter */
+ char ll[3]; /* Base language */
+ const char *prefix; /* WARN/FAIL prefix */
+ const char *text; /* Pointer into UI text */
+
+
+ prefix = warn ? " WARN " : "**FAIL**";
+
+ if ((languages = _ppdGetLanguages(ppd)) != NULL)
+ {
+ /*
+ * This file contains localizations, check them...
+ */
+
+ for (language = (char *)cupsArrayFirst(languages);
+ language;
+ language = (char *)cupsArrayNext(languages))
+ {
+ langlen = (int)strlen(language);
+ if (langlen != 2 && langlen != 5)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad language \"%s\"\n"),
+ prefix, language);
+
+ if (!warn)
+ errors ++;
+
+ continue;
+ }
+
+ if (!strcmp(language, "en"))
+ continue;
+
+ strlcpy(ll, language, sizeof(ll));
+
+ /*
+ * Loop through all options and choices...
+ */
+
+ for (option = ppdFirstOption(ppd);
+ option;
+ option = ppdNextOption(ppd))
+ {
+ if (!strcmp(option->keyword, "PageRegion"))
+ continue;
+
+ snprintf(keyword, sizeof(keyword), "%s.Translation", language);
+ snprintf(llkeyword, sizeof(llkeyword), "%s.Translation", ll);
+
+ if ((attr = ppdFindAttr(ppd, keyword, option->keyword)) == NULL &&
+ (attr = ppdFindAttr(ppd, llkeyword, option->keyword)) == NULL)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Missing \"%s\" translation "
+ "string for option %s\n"),
+ prefix, language, option->keyword);
+
+ if (!warn)
+ errors ++;
+ }
+ else if (!valid_utf8(attr->text))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad UTF-8 \"%s\" translation "
+ "string for option %s\n"),
+ prefix, language, option->keyword);
+
+ if (!warn)
+ errors ++;
+ }
+
+ snprintf(keyword, sizeof(keyword), "%s.%s", language,
+ option->keyword);
+ snprintf(llkeyword, sizeof(llkeyword), "%s.%s", ll,
+ option->keyword);
+
+ for (j = 0; j < option->num_choices; j ++)
+ {
+ /*
+ * First see if this choice is a number; if so, don't require
+ * translation...
+ */
+
+ for (text = option->choices[j].text; *text; text ++)
+ if (!strchr("0123456789-+.", *text))
+ break;
+
+ if (!*text)
+ continue;
+
+ /*
+ * Check custom choices differently...
+ */
+
+ if (!strcasecmp(option->choices[j].choice, "Custom") &&
+ (coption = ppdFindCustomOption(ppd,
+ option->keyword)) != NULL)
+ {
+ snprintf(ckeyword, sizeof(ckeyword), "%s.Custom%s",
+ language, option->keyword);
+
+ if ((attr = ppdFindAttr(ppd, ckeyword, "True")) != NULL &&
+ !valid_utf8(attr->text))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad UTF-8 \"%s\" "
+ "translation string for option %s, "
+ "choice %s\n"),
+ prefix, language,
+ ckeyword + 1 + strlen(language),
+ "True");
+
+ if (!warn)
+ errors ++;
+ }
+
+ if (strcasecmp(option->keyword, "PageSize"))
+ {
+ for (cparam = (ppd_cparam_t *)cupsArrayFirst(coption->params);
+ cparam;
+ cparam = (ppd_cparam_t *)cupsArrayNext(coption->params))
+ {
+ snprintf(ckeyword, sizeof(ckeyword), "%s.ParamCustom%s",
+ language, option->keyword);
+ snprintf(cllkeyword, sizeof(cllkeyword), "%s.ParamCustom%s",
+ ll, option->keyword);
+
+ if ((attr = ppdFindAttr(ppd, ckeyword,
+ cparam->name)) == NULL &&
+ (attr = ppdFindAttr(ppd, cllkeyword,
+ cparam->name)) == NULL)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Missing \"%s\" "
+ "translation string for option %s, "
+ "choice %s\n"),
+ prefix, language,
+ ckeyword + 1 + strlen(language),
+ cparam->name);
+
+ if (!warn)
+ errors ++;
+ }
+ else if (!valid_utf8(attr->text))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad UTF-8 \"%s\" "
+ "translation string for option %s, "
+ "choice %s\n"),
+ prefix, language,
+ ckeyword + 1 + strlen(language),
+ cparam->name);
+
+ if (!warn)
+ errors ++;
+ }
+ }
+ }
+ }
+ else if ((attr = ppdFindAttr(ppd, keyword,
+ option->choices[j].choice)) == NULL &&
+ (attr = ppdFindAttr(ppd, llkeyword,
+ option->choices[j].choice)) == NULL)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Missing \"%s\" "
+ "translation string for option %s, "
+ "choice %s\n"),
+ prefix, language, option->keyword,
+ option->choices[j].choice);
+
+ if (!warn)
+ errors ++;
+ }
+ else if (!valid_utf8(attr->text))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s Bad UTF-8 \"%s\" "
+ "translation string for option %s, "
+ "choice %s\n"),
+ prefix, language, option->keyword,
+ option->choices[j].choice);
+
+ if (!warn)
+ errors ++;
+ }
+ }
+ }
+ }
+
+ /*
+ * Verify that we have the base language for each localized one...
+ */
+
+ for (language = (char *)cupsArrayFirst(languages);
+ language;
+ language = (char *)cupsArrayNext(languages))
+ if (language[2])
+ {
+ /*
+ * Lookup the base language...
+ */
+
+ cupsArraySave(languages);
+
+ strlcpy(ll, language, sizeof(ll));
+
+ if (!cupsArrayFind(languages, ll) &&
+ strcmp(ll, "zh") && strcmp(ll, "en"))
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s No base translation \"%s\" "
+ "is included in file\n"), prefix, ll);
+
+ if (!warn)
+ errors ++;
+ }
+
+ cupsArrayRestore(languages);
+ }
+
+ /*
+ * Free memory used for the languages...
+ */
+
+ _ppdFreeLanguages(languages);
+ }
+
+ return (errors);
+}
+
+
+/*
+ * 'show_conflicts()' - Show option conflicts in a PPD file.
+ */
+
+static void
+show_conflicts(ppd_file_t *ppd) /* I - PPD to check */
+{
+ int i, j; /* Looping variables */
+ ppd_const_t *c; /* Current constraint */
+ ppd_option_t *o1, *o2; /* Options */
+ ppd_choice_t *c1, *c2; /* Choices */
+
+
+ /*
+ * Loop through all of the UI constraints and report any options
+ * that conflict...
+ */
+
+ for (i = ppd->num_consts, c = ppd->consts; i > 0; i --, c ++)
+ {
+ /*
+ * Grab pointers to the first option...
+ */
+
+ o1 = ppdFindOption(ppd, c->option1);
+
+ if (o1 == NULL)
+ continue;
+ else if (c->choice1[0] != '\0')
+ {
+ /*
+ * This constraint maps to a specific choice.
+ */
+
+ c1 = ppdFindChoice(o1, c->choice1);
+ }
+ else
+ {
+ /*
+ * This constraint applies to any choice for this option.
+ */
+
+ for (j = o1->num_choices, c1 = o1->choices; j > 0; j --, c1 ++)
+ if (c1->marked)
+ break;
+
+ if (j == 0 ||
+ !strcasecmp(c1->choice, "None") ||
+ !strcasecmp(c1->choice, "Off") ||
+ !strcasecmp(c1->choice, "False"))
+ c1 = NULL;
+ }
+
+ /*
+ * Grab pointers to the second option...
+ */
+
+ o2 = ppdFindOption(ppd, c->option2);
+
+ if (o2 == NULL)
+ continue;
+ else if (c->choice2[0] != '\0')
+ {
+ /*
+ * This constraint maps to a specific choice.
+ */
+
+ c2 = ppdFindChoice(o2, c->choice2);
+ }
+ else
+ {
+ /*
+ * This constraint applies to any choice for this option.
+ */
+
+ for (j = o2->num_choices, c2 = o2->choices; j > 0; j --, c2 ++)
+ if (c2->marked)
+ break;
+
+ if (j == 0 ||
+ !strcasecmp(c2->choice, "None") ||
+ !strcasecmp(c2->choice, "Off") ||
+ !strcasecmp(c2->choice, "False"))
+ c2 = NULL;
+ }
+
+ /*
+ * If both options are marked then there is a conflict...
+ */
+
+ if (c1 != NULL && c1->marked && c2 != NULL && c2->marked)
+ _cupsLangPrintf(stdout,
+ _(" 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);
+ }
+}
+
+
+/*
+ * 'test_raster()' - Test PostScript commands for raster printers.
+ */
+
+static int /* O - 1 on success, 0 on failure */
+test_raster(ppd_file_t *ppd, /* I - PPD file */
+ int verbose) /* I - Verbosity */
+{
+ cups_page_header2_t header; /* Page header */
+
+
+ ppdMarkDefaults(ppd);
+ if (cupsRasterInterpretPPD(&header, ppd, 0, NULL, 0))
+ {
+ if (!verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" **FAIL** Default option code cannot be "
+ "interpreted: %s\n"), cupsRasterErrorString());
+
+ return (0);
+ }
+
+ /*
+ * Try a test of custom page size code, if available...
+ */
+
+ if (!ppdPageSize(ppd, "Custom.612x792"))
+ return (1);
+
+ ppdMarkOption(ppd, "PageSize", "Custom.612x792");
+
+ if (cupsRasterInterpretPPD(&header, ppd, 0, NULL, 0))
+ {
+ if (!verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" **FAIL** Default option code cannot be "
+ "interpreted: %s\n"), cupsRasterErrorString());
+
+ return (0);
+ }
+
+ return (1);
+}
+
+
+/*
+ * 'usage()' - Show program usage...
+ */
+
+static void
+usage(void)
+{
+ _cupsLangPuts(stdout,
+ _("Usage: cupstestppd [options] filename1.ppd[.gz] "
+ "[... filenameN.ppd[.gz]]\n"
+ " program | cupstestppd [options] -\n"
+ "\n"
+ "Options:\n"
+ "\n"
+ " -I {filters,profiles}\n"
+ " Ignore missing files\n"
+ " -R root-directory Set alternate root\n"
+ " -W {all,none,constraints,defaults,duplex,filters,"
+ "profiles,sizes,translations}\n"
+ " Issue warnings instead of errors\n"
+ " -q Run silently\n"
+ " -r Use 'relaxed' open mode\n"
+ " -v Be slightly verbose\n"
+ " -vv Be very verbose\n"));
+
+ exit(ERROR_USAGE);
+}
+
+
+/*
+ * 'valid_path()' - Check whether a path has the correct capitalization.
+ */
+
+static int /* O - Errors found */
+valid_path(const char *keyword, /* I - Keyword using path */
+ const char *path, /* I - Path to check */
+ int errors, /* I - Errors found */
+ int verbose, /* I - Verbosity level */
+ int warn) /* I - Warnings only? */
+{
+ cups_dir_t *dir; /* Current directory */
+ cups_dentry_t *dentry; /* Current directory entry */
+ char temp[1024], /* Temporary path */
+ *ptr; /* Pointer into temporary path */
+ const char *prefix; /* WARN/FAIL prefix */
+
+
+ prefix = warn ? " WARN " : "**FAIL**";
+
+ /*
+ * Loop over the components of the path, checking that the entry exists with
+ * the same capitalization...
+ */
+
+ strlcpy(temp, path, sizeof(temp));
+
+ while ((ptr = strrchr(temp, '/')) != NULL)
+ {
+ /*
+ * Chop off the trailing component so temp == dirname and ptr == basename.
+ */
+
+ *ptr++ = '\0';
+
+ /*
+ * Try opening the directory containing the base name...
+ */
+
+ if (temp[0])
+ dir = cupsDirOpen(temp);
+ else
+ dir = cupsDirOpen("/");
+
+ if (!dir)
+ dentry = NULL;
+ else
+ {
+ while ((dentry = cupsDirRead(dir)) != NULL)
+ {
+ if (!strcmp(dentry->filename, ptr))
+ break;
+ }
+
+ cupsDirClose(dir);
+ }
+
+ /*
+ * Display an error if the filename doesn't exist with the same
+ * capitalization...
+ */
+
+ if (!dentry)
+ {
+ if (!warn && !errors && !verbose)
+ _cupsLangPuts(stdout, _(" FAIL\n"));
+
+ if (verbose >= 0)
+ _cupsLangPrintf(stdout,
+ _(" %s %s file \"%s\" has the wrong "
+ "capitalization\n"), prefix, keyword, path);
+
+ if (!warn)
+ errors ++;
+
+ break;
+ }
+ }
+
+ return (errors);
+}
+
+
+/*
+ * 'valid_utf8()' - Check whether a string contains valid UTF-8 text.
+ */
+
+static int /* O - 1 if valid, 0 if not */
+valid_utf8(const char *s) /* I - String to check */
+{
+ while (*s)
+ {
+ if (*s & 0x80)
+ {
+ /*
+ * Check for valid UTF-8 sequence...
+ */
+
+ if ((*s & 0xc0) == 0x80)
+ return (0); /* Illegal suffix byte */
+ else if ((*s & 0xe0) == 0xc0)
+ {
+ /*
+ * 2-byte sequence...
+ */
+
+ s ++;
+
+ if ((*s & 0xc0) != 0x80)
+ return (0); /* Missing suffix byte */
+ }
+ else if ((*s & 0xf0) == 0xe0)
+ {
+ /*
+ * 3-byte sequence...
+ */
+
+ s ++;
+
+ if ((*s & 0xc0) != 0x80)
+ return (0); /* Missing suffix byte */
+
+ s ++;
+
+ if ((*s & 0xc0) != 0x80)
+ return (0); /* Missing suffix byte */
+ }
+ else if ((*s & 0xf8) == 0xf0)
+ {
+ /*
+ * 4-byte sequence...
+ */
+
+ s ++;
+
+ if ((*s & 0xc0) != 0x80)
+ return (0); /* Missing suffix byte */
+
+ s ++;
+
+ if ((*s & 0xc0) != 0x80)
+ return (0); /* Missing suffix byte */
+
+ s ++;
+
+ if ((*s & 0xc0) != 0x80)
+ return (0); /* Missing suffix byte */
+ }
+ else
+ return (0); /* Bad sequence */
+ }
+
+ s ++;
+ }
+
+ return (1);