+ char filename[1024], /* Full filter filename */
+ *dirsep; /* Pointer to directory separator */
+ struct stat fileinfo; /* File information */
+
+
+ /*
+ * Parse the filter string; it should be in the following format:
+ *
+ * super/type cost program
+ */
+
+ if (sscanf(filter, "%15[^/]/%31s%d%*[ \t]%1023[^\n]", super, type, &cost,
+ program) != 4)
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR, "%s: invalid filter string \"%s\"!",
+ p->name, filter);
+ return;
+ }
+
+ /*
+ * See if the filter program exists; if not, stop the printer and flag
+ * the error!
+ */
+
+ if (strcmp(program, "-"))
+ {
+ if (program[0] == '/')
+ strlcpy(filename, program, sizeof(filename));
+ else
+ snprintf(filename, sizeof(filename), "%s/filter/%s", ServerBin, program);
+
+ if (stat(filename, &fileinfo))
+ {
+ memset(&fileinfo, 0, sizeof(fileinfo));
+
+ snprintf(p->state_message, sizeof(p->state_message),
+ "Filter \"%s\" for printer \"%s\" not available: %s",
+ filename, p->name, strerror(errno));
+ cupsdSetPrinterReasons(p, "+cups-missing-filter-warning");
+
+ cupsdLogMessage(CUPSD_LOG_ERROR, "%s", p->state_message);
+ }
+
+ /*
+ * When running as root, do additional security checks...
+ */
+
+ if (!RunUser)
+ {
+ /*
+ * Only use filters that are owned by root and do not have group or world
+ * write permissions.
+ */
+
+ if (fileinfo.st_uid ||
+ (fileinfo.st_mode & (S_ISUID | S_IWGRP | S_IWOTH)) != 0)
+ {
+ if (fileinfo.st_uid)
+ snprintf(p->state_message, sizeof(p->state_message),
+ "Filter \"%s\" for printer \"%s\" not owned by root",
+ filename, p->name);
+ else
+ snprintf(p->state_message, sizeof(p->state_message),
+ "Filter \"%s\" for printer \"%s\" has insecure permissions "
+ "(0%o)", filename, p->name, fileinfo.st_mode);
+
+ cupsdSetPrinterReasons(p, "+cups-insecure-filter-warning");
+
+ cupsdLogMessage(CUPSD_LOG_ERROR, "%s", p->state_message);
+ }
+ else if (fileinfo.st_mode)
+ {
+ /*
+ * Similarly, check that the parent directory is also owned by root and
+ * does not have world write permissions.
+ */
+
+ if ((dirsep = strrchr(filename, '/')) != NULL)
+ *dirsep = '\0';
+
+ if (!stat(filename, &fileinfo) &&
+ (fileinfo.st_uid ||
+ (fileinfo.st_mode & (S_ISUID | S_IWGRP | S_IWOTH)) != 0))
+ {
+ if (fileinfo.st_uid)
+ snprintf(p->state_message, sizeof(p->state_message),
+ "Filter directory \"%s\" for printer \"%s\" not owned by "
+ "root", filename, p->name);
+ else
+ snprintf(p->state_message, sizeof(p->state_message),
+ "Filter directory \"%s\" for printer \"%s\" has insecure "
+ "permissions (0%o)", filename, p->name, fileinfo.st_mode);
+
+ cupsdSetPrinterReasons(p, "+cups-insecure-filter-warning");
+
+ cupsdLogMessage(CUPSD_LOG_ERROR, "%s", p->state_message);
+ }
+ }
+ }
+ }
+
+ /*
+ * Add the filter to the MIME database, supporting wildcards as needed...
+ */
+
+ for (temptype = mimeFirstType(MimeDatabase);
+ temptype;
+ temptype = mimeNextType(MimeDatabase))
+ if (((super[0] == '*' && strcasecmp(temptype->super, "printer")) ||
+ !strcasecmp(temptype->super, super)) &&
+ (type[0] == '*' || !strcasecmp(temptype->type, type)))
+ {
+ cupsdLogMessage(CUPSD_LOG_DEBUG2,
+ "add_printer_filter: %s: adding filter %s/%s %s/%s %d %s",
+ p->name, temptype->super, temptype->type,
+ filtertype->super, filtertype->type,
+ cost, program);
+ mimeAddFilter(MimeDatabase, temptype, filtertype, cost, program);
+ }
+}
+
+
+/*
+ * 'add_printer_formats()' - Add document-format-supported values for a printer.
+ */
+
+static void
+add_printer_formats(cupsd_printer_t *p) /* I - Printer */
+{
+ int i; /* Looping var */
+ mime_type_t *type; /* Current MIME type */
+ cups_array_t *filters; /* Filters */
+ ipp_attribute_t *attr; /* document-format-supported attribute */
+ char mimetype[MIME_MAX_SUPER + MIME_MAX_TYPE + 2];
+ /* MIME type name */
+
+
+ /*
+ * Raw (and remote) queues advertise all of the supported MIME
+ * types...
+ */
+
+ cupsArrayDelete(p->filetypes);
+ p->filetypes = NULL;
+
+ if (p->raw)
+ {
+ ippAddStrings(p->attrs, IPP_TAG_PRINTER,
+ (ipp_tag_t)(IPP_TAG_MIMETYPE | IPP_TAG_COPY),
+ "document-format-supported", NumMimeTypes, NULL, MimeTypes);
+ return;
+ }
+
+ /*
+ * Otherwise, loop through the supported MIME types and see if there
+ * are filters for them...
+ */
+
+ cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_printer_formats: %d types, %d filters",
+ mimeNumTypes(MimeDatabase), mimeNumFilters(MimeDatabase));
+
+ p->filetypes = cupsArrayNew(NULL, NULL);
+
+ for (type = mimeFirstType(MimeDatabase);
+ type;
+ type = mimeNextType(MimeDatabase))
+ {
+ snprintf(mimetype, sizeof(mimetype), "%s/%s", type->super, type->type);
+
+ if ((filters = mimeFilter(MimeDatabase, type, p->filetype, NULL)) != NULL)
+ {
+ cupsdLogMessage(CUPSD_LOG_DEBUG2,
+ "add_printer_formats: %s: %s needs %d filters",
+ p->name, mimetype, cupsArrayCount(filters));
+
+ cupsArrayDelete(filters);
+ cupsArrayAdd(p->filetypes, type);
+ }
+ else
+ cupsdLogMessage(CUPSD_LOG_DEBUG2,
+ "add_printer_formats: %s: %s not supported",
+ p->name, mimetype);
+ }
+
+ /*
+ * Add the file formats that can be filtered...
+ */
+
+ if ((type = mimeType(MimeDatabase, "application", "octet-stream")) == NULL ||
+ !cupsArrayFind(p->filetypes, type))
+ i = 1;
+ else
+ i = 0;
+
+ cupsdLogMessage(CUPSD_LOG_DEBUG2,
+ "add_printer_formats: %s: %d supported types",
+ p->name, cupsArrayCount(p->filetypes) + i);
+
+ attr = ippAddStrings(p->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE,
+ "document-format-supported",
+ cupsArrayCount(p->filetypes) + i, NULL, NULL);
+
+ if (i)
+ attr->values[0].string.text = _cupsStrAlloc("application/octet-stream");
+
+ for (type = (mime_type_t *)cupsArrayFirst(p->filetypes);
+ type;
+ i ++, type = (mime_type_t *)cupsArrayNext(p->filetypes))
+ {
+ snprintf(mimetype, sizeof(mimetype), "%s/%s", type->super, type->type);
+
+ attr->values[i].string.text = _cupsStrAlloc(mimetype);
+ }
+
+#ifdef HAVE_DNSSD
+ {
+ char pdl[1024]; /* Buffer to build pdl list */
+ mime_filter_t *filter; /* MIME filter looping var */
+
+
+ /*
+ * We only support raw printing if this is not a Tioga PrintJobMgr based
+ * queue and if application/octet-stream is a known type...
+ */
+
+ for (filter = (mime_filter_t *)cupsArrayFirst(MimeDatabase->filters);
+ filter;
+ filter = (mime_filter_t *)cupsArrayNext(MimeDatabase->filters))
+ {
+ if (filter->dst == p->filetype && filter->filter &&
+ strstr(filter->filter, "PrintJobMgr"))
+ break;
+ }
+
+ pdl[0] = '\0';
+
+ if (!filter && mimeType(MimeDatabase, "application", "octet-stream"))
+ strlcat(pdl, "application/octet-stream,", sizeof(pdl));
+
+ /*
+ * Then list a bunch of formats that are supported by the printer...
+ */
+
+ for (type = (mime_type_t *)cupsArrayFirst(p->filetypes);
+ type;
+ type = (mime_type_t *)cupsArrayNext(p->filetypes))
+ {
+ if (!strcasecmp(type->super, "application"))
+ {
+ if (!strcasecmp(type->type, "pdf"))
+ strlcat(pdl, "application/pdf,", sizeof(pdl));
+ else if (!strcasecmp(type->type, "postscript"))
+ strlcat(pdl, "application/postscript,", sizeof(pdl));
+ }
+ else if (!strcasecmp(type->super, "image"))
+ {
+ if (!strcasecmp(type->type, "jpeg"))
+ strlcat(pdl, "image/jpeg,", sizeof(pdl));
+ else if (!strcasecmp(type->type, "png"))
+ strlcat(pdl, "image/png,", sizeof(pdl));
+ }
+ }
+
+ if (pdl[0])
+ pdl[strlen(pdl) - 1] = '\0'; /* Remove trailing comma */
+
+ cupsdSetString(&p->pdl, pdl);
+ }
+#endif /* HAVE_DNSSD */
+}
+
+
+/*
+ * 'add_string_array()' - Add a string to an array of CUPS strings.
+ */
+
+static void
+add_string_array(cups_array_t **a, /* I - Array */
+ const char *s) /* I - String */
+{
+ if (!*a)
+ *a = cupsArrayNew(NULL, NULL);
+
+ cupsArrayAdd(*a, _cupsStrAlloc(s));
+}
+
+
+/*
+ * 'compare_printers()' - Compare two printers.
+ */
+
+static int /* O - Result of comparison */
+compare_printers(void *first, /* I - First printer */
+ void *second, /* I - Second printer */
+ void *data) /* I - App data (not used) */
+{
+ return (strcasecmp(((cupsd_printer_t *)first)->name,
+ ((cupsd_printer_t *)second)->name));
+}
+
+
+/*
+ * 'delete_printer_filters()' - Delete all MIME filters for a printer.
+ */
+
+static void
+delete_printer_filters(
+ cupsd_printer_t *p) /* I - Printer to remove from */
+{
+ mime_filter_t *filter; /* MIME filter looping var */
+
+
+ /*
+ * Range check input...
+ */
+
+ if (p == NULL)
+ return;
+
+ /*
+ * Remove all filters from the MIME database that have a destination
+ * type == printer...
+ */
+
+ for (filter = mimeFirstFilter(MimeDatabase);
+ filter;
+ filter = mimeNextFilter(MimeDatabase))
+ if (filter->dst == p->filetype || filter->dst == p->prefiltertype)
+ {
+ /*
+ * Delete the current filter...
+ */
+
+ mimeDeleteFilter(MimeDatabase, filter);
+ }
+
+ cupsdSetPrinterReasons(p, "-cups-insecure-filter-warning"
+ ",cups-missing-filter-warning");
+}
+
+
+/*
+ * 'delete_string_array()' - Delete an array of CUPS strings.
+ */
+
+static void
+delete_string_array(cups_array_t **a) /* I - Array */
+{
+ char *ptr; /* Current string */
+
+
+ for (ptr = (char *)cupsArrayFirst(*a);
+ ptr;
+ ptr = (char *)cupsArrayNext(*a))
+ _cupsStrFree(ptr);
+
+ cupsArrayDelete(*a);
+ *a = NULL;
+}
+
+
+/*
+ * 'load_ppd()' - Load a cached PPD file, updating the cache as needed.
+ */
+
+static void
+load_ppd(cupsd_printer_t *p) /* I - Printer */
+{
+ int i, j, k; /* Looping vars */
+ cups_file_t *cache; /* IPP cache file */
+ char cache_name[1024]; /* IPP cache filename */
+ struct stat cache_info; /* IPP cache file info */
+ char pwg_name[1024]; /* PWG cache filename */
+ struct stat pwg_info; /* PWG cache file info */
+ ppd_file_t *ppd; /* PPD file */
+ char ppd_name[1024]; /* PPD filename */
+ struct stat ppd_info; /* PPD file info */
+ int num_media; /* Number of media options */
+ ppd_size_t *size; /* Current PPD size */
+ ppd_option_t *duplex, /* Duplex option */
+ *output_bin, /* OutputBin option */
+ *output_mode, /* OutputMode option */
+ *resolution; /* (Set|JCL|)Resolution option */
+ ppd_choice_t *choice, /* Current PPD choice */
+ *input_slot, /* Current input slot */
+ *media_type; /* Current media type */
+ ppd_attr_t *ppd_attr; /* PPD attribute */
+ int xdpi, /* Horizontal resolution */
+ ydpi; /* Vertical resolution */
+ const char *resptr; /* Pointer into resolution keyword */
+ _pwg_size_t *pwgsize; /* Current PWG size */
+ _pwg_map_t *pwgsource, /* Current PWG source */
+ *pwgtype; /* Current PWG type */
+ ipp_attribute_t *attr; /* Attribute data */
+ ipp_value_t *val; /* Attribute value */
+ int num_finishings, /* Number of finishings */
+ finishings[5]; /* finishings-supported values */
+ int num_qualities, /* Number of print-quality values */
+ qualities[3]; /* print-quality values */
+ int num_margins, /* Number of media-*-margin-supported values */
+ margins[16]; /* media-*-margin-supported values */
+ static const char * const sides[3] = /* sides-supported values */
+ {
+ "one-sided",
+ "two-sided-long-edge",
+ "two-sided-short-edge"
+ };
+ static const char * const standard_commands[] =
+ { /* Standard CUPS commands */
+ "AutoConfigure",
+ "Clean",
+ "PrintSelfTestPage"
+ };
+
+
+ /*
+ * Check to see if the cache is up-to-date...
+ */
+
+ snprintf(cache_name, sizeof(cache_name), "%s/%s.ipp3", CacheDir, p->name);
+ if (stat(cache_name, &cache_info))
+ cache_info.st_mtime = 0;
+
+ snprintf(pwg_name, sizeof(pwg_name), "%s/%s.pwg2", CacheDir, p->name);
+ if (stat(pwg_name, &pwg_info))
+ pwg_info.st_mtime = 0;
+
+ snprintf(ppd_name, sizeof(ppd_name), "%s/ppd/%s.ppd", ServerRoot, p->name);
+ if (stat(ppd_name, &ppd_info))
+ ppd_info.st_mtime = 1;
+
+ ippDelete(p->ppd_attrs);
+ p->ppd_attrs = ippNew();
+
+ _pwgDestroy(p->pwg);
+ p->pwg = NULL;
+
+ if (pwg_info.st_mtime >= ppd_info.st_mtime)
+ p->pwg = _pwgCreateWithFile(pwg_name);
+
+ if (cache_info.st_mtime >= ppd_info.st_mtime && p->pwg &&
+ (cache = cupsFileOpen(cache_name, "r")) != NULL)
+ {
+ /*
+ * Load cached information and return...
+ */
+
+ cupsdLogMessage(CUPSD_LOG_DEBUG, "load_ppd: Loading %s...", cache_name);
+
+ if (ippReadIO(cache, (ipp_iocb_t)cupsFileRead, 1, NULL,
+ p->ppd_attrs) == IPP_DATA)
+ {
+ cupsFileClose(cache);
+ return;
+ }
+
+ cupsFileClose(cache);
+ }
+
+ /*
+ * Reload PPD attributes from disk...
+ */
+
+ cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+
+ _pwgDestroy(p->pwg);
+ p->pwg = NULL;
+
+ cupsdLogMessage(CUPSD_LOG_DEBUG, "load_ppd: Loading %s...", ppd_name);
+
+ delete_string_array(&(p->filters));
+ delete_string_array(&(p->pre_filters));
+
+ p->type &= ~CUPS_PRINTER_OPTIONS;
+ p->type |= CUPS_PRINTER_BW;
+
+ finishings[0] = IPP_FINISHINGS_NONE;
+ num_finishings = 1;
+
+ if ((ppd = ppdOpenFile(ppd_name)) != NULL)
+ {
+ /*
+ * Add make/model and other various attributes...
+ */
+
+ p->pwg = _pwgCreateWithPPD(ppd);
+
+ ppdMarkDefaults(ppd);
+
+ if (ppd->color_device)
+ p->type |= CUPS_PRINTER_COLOR;
+ if (ppd->variable_sizes)
+ p->type |= CUPS_PRINTER_VARIABLE;
+ if (!ppd->manual_copies)
+ p->type |= CUPS_PRINTER_COPIES;
+ if ((ppd_attr = ppdFindAttr(ppd, "cupsFax", NULL)) != NULL)
+ if (ppd_attr->value && !strcasecmp(ppd_attr->value, "true"))
+ p->type |= CUPS_PRINTER_FAX;
+
+ ippAddBoolean(p->ppd_attrs, IPP_TAG_PRINTER, "color-supported",
+ ppd->color_device);
+ if (ppd->throughput)
+ {
+ ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+ "pages-per-minute", ppd->throughput);
+ if (ppd->color_device)
+ ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+ "pages-per-minute-color", ppd->throughput);
+ }
+
+ num_qualities = 0;
+
+ if ((output_mode = ppdFindOption(ppd, "OutputMode")) != NULL)
+ {
+ if (ppdFindChoice(output_mode, "draft") ||
+ ppdFindChoice(output_mode, "fast"))
+ qualities[num_qualities ++] = IPP_QUALITY_DRAFT;
+ if (ppdFindChoice(output_mode, "normal") ||
+ ppdFindChoice(output_mode, "good"))
+ qualities[num_qualities ++] = IPP_QUALITY_NORMAL;
+ if (ppdFindChoice(output_mode, "best") ||
+ ppdFindChoice(output_mode, "high"))
+ qualities[num_qualities ++] = IPP_QUALITY_HIGH;
+ }
+ else if ((ppd_attr = ppdFindAttr(ppd, "APPrinterPreset", NULL)) != NULL)
+ {
+ do
+ {
+ if (strstr(ppd_attr->spec, "draft") ||
+ strstr(ppd_attr->spec, "Draft"))
+ {
+ qualities[num_qualities ++] = IPP_QUALITY_DRAFT;
+ break;
+ }
+ }
+ while ((ppd_attr = ppdFindNextAttr(ppd, "APPrinterPreset",
+ NULL)) != NULL);
+
+ qualities[num_qualities ++] = IPP_QUALITY_NORMAL;
+ qualities[num_qualities ++] = IPP_QUALITY_HIGH;
+ }
+
+ if (num_qualities == 0)
+ qualities[num_qualities ++] = IPP_QUALITY_NORMAL;
+
+ ippAddIntegers(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
+ "print-quality-supported", num_qualities, qualities);
+
+ if (ppd->nickname)
+ {
+ /*
+ * The NickName can be localized in the character set specified
+ * by the LanugageEncoding attribute. However, ppdOpen2() has
+ * already converted the ppd->nickname member to UTF-8 for us
+ * (the original attribute value is available separately)
+ */
+
+ cupsdSetString(&p->make_model, ppd->nickname);
+ }
+ else if (ppd->modelname)
+ {
+ /*
+ * Model name can only contain specific characters...
+ */
+
+ cupsdSetString(&p->make_model, ppd->modelname);
+ }
+ else
+ cupsdSetString(&p->make_model, "Bad PPD File");
+
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
+ "printer-make-and-model", NULL, p->make_model);
+
+ /*
+ * Add media options from the PPD file...
+ */
+
+ if (ppd->num_sizes == 0 || !p->pwg)
+ {
+ if (!ppdFindAttr(ppd, "APScannerOnly", NULL))
+ cupsdLogMessage(CUPSD_LOG_CRIT,
+ "The PPD file for printer %s contains no media "
+ "options and is therefore invalid!", p->name);
+
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "media-default", NULL, "unknown");
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "media-supported", NULL, "unknown");
+ }
+ else
+ {
+ /*
+ * media-default
+ */
+
+ if ((size = ppdPageSize(ppd, NULL)) != NULL)
+ pwgsize = _pwgGetSize(p->pwg, size->name);
+ else
+ pwgsize = NULL;
+
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "media-default", NULL,
+ pwgsize ? pwgsize->map.pwg : "unknown");
+
+ /*
+ * media-col-default
+ */
+
+ if (pwgsize)
+ {
+ ipp_t *col; /* Collection value */
+
+ input_slot = ppdFindMarkedChoice(ppd, "InputSlot");
+ media_type = ppdFindMarkedChoice(ppd, "MediaType");
+ col = new_media_col(pwgsize,
+ input_slot ?
+ _pwgGetSource(p->pwg,
+ input_slot->choice) :
+ NULL,
+ media_type ?
+ _pwgGetType(p->pwg,
+ media_type->choice) :
+ NULL);
+
+ ippAddCollection(p->ppd_attrs, IPP_TAG_PRINTER, "media-col-default",
+ col);
+ ippDelete(col);
+ }
+
+ /*
+ * media-supported
+ */
+
+ num_media = p->pwg->num_sizes;
+ if (p->pwg->custom_min_keyword)
+ num_media += 2;
+
+ if ((attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "media-supported", num_media, NULL,
+ NULL)) != NULL)
+ {
+ val = attr->values;
+
+ for (i = p->pwg->num_sizes, pwgsize = p->pwg->sizes;
+ i > 0;
+ i --, pwgsize ++, val ++)
+ val->string.text = _cupsStrRetain(pwgsize->map.pwg);
+
+ if (p->pwg->custom_min_keyword)
+ {
+ val->string.text = _cupsStrRetain(p->pwg->custom_min_keyword);
+ val ++;
+ val->string.text = _cupsStrRetain(p->pwg->custom_max_keyword);
+ }
+ }
+
+ /*
+ * media-source-supported
+ */
+
+ if (p->pwg->num_sources > 0 &&
+ (attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "media-source-supported", p->pwg->num_sources,
+ NULL, NULL)) != NULL)
+ {
+ for (i = p->pwg->num_sources, pwgsource = p->pwg->sources,
+ val = attr->values;
+ i > 0;
+ i --, pwgsource ++, val ++)
+ val->string.text = _cupsStrRetain(pwgsource->pwg);
+ }
+
+ /*
+ * media-type-supported
+ */
+
+ if (p->pwg->num_types > 0 &&
+ (attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "media-type-supported", p->pwg->num_types,
+ NULL, NULL)) != NULL)
+ {
+ for (i = p->pwg->num_types, pwgtype = p->pwg->types,
+ val = attr->values;
+ i > 0;
+ i --, pwgtype ++, val ++)
+ val->string.text = _cupsStrRetain(pwgtype->pwg);
+ }
+
+ /*
+ * media-*-margin-supported
+ */
+
+ for (i = p->pwg->num_sizes, pwgsize = p->pwg->sizes, num_margins = 0;
+ i > 0 && num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
+ i --, pwgsize ++)
+ {
+ for (j = 0; j < num_margins; j ++)
+ if (pwgsize->bottom == margins[j])
+ break;
+
+ if (j >= num_margins)
+ {
+ margins[num_margins] = pwgsize->bottom;
+ num_margins ++;
+ }
+ }
+
+ if (num_margins > 0)
+ ippAddIntegers(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+ "media-bottom-margin-supported", num_margins, margins);
+ else
+ ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+ "media-bottom-margin-supported", 0);
+
+ for (i = p->pwg->num_sizes, pwgsize = p->pwg->sizes, num_margins = 0;
+ i > 0 && num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
+ i --, pwgsize ++)
+ {
+ for (j = 0; j < num_margins; j ++)
+ if (pwgsize->left == margins[j])
+ break;
+
+ if (j >= num_margins)
+ {
+ margins[num_margins] = pwgsize->left;
+ num_margins ++;
+ }
+ }
+
+ if (num_margins > 0)
+ ippAddIntegers(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+ "media-left-margin-supported", num_margins, margins);
+ else
+ ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+ "media-left-margin-supported", 0);
+
+ for (i = p->pwg->num_sizes, pwgsize = p->pwg->sizes, num_margins = 0;
+ i > 0 && num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
+ i --, pwgsize ++)
+ {
+ for (j = 0; j < num_margins; j ++)
+ if (pwgsize->right == margins[j])
+ break;
+
+ if (j >= num_margins)
+ {
+ margins[num_margins] = pwgsize->right;
+ num_margins ++;
+ }
+ }
+
+ if (num_margins > 0)
+ ippAddIntegers(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+ "media-right-margin-supported", num_margins, margins);
+ else
+ ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+ "media-right-margin-supported", 0);
+
+ for (i = p->pwg->num_sizes, pwgsize = p->pwg->sizes, num_margins = 0;
+ i > 0 && num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
+ i --, pwgsize ++)
+ {
+ for (j = 0; j < num_margins; j ++)
+ if (pwgsize->top == margins[j])
+ break;
+
+ if (j >= num_margins)
+ {
+ margins[num_margins] = pwgsize->top;
+ num_margins ++;
+ }
+ }
+
+ if (num_margins > 0)
+ ippAddIntegers(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+ "media-top-margin-supported", num_margins, margins);
+ else
+ ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+ "media-top-margin-supported", 0);
+
+ /*
+ * media-col-database
+ */
+
+ num_media = p->pwg->num_sizes;
+ if (p->pwg->num_sources)
+ {
+ if (p->pwg->num_types > 0)
+ num_media += p->pwg->num_sizes * p->pwg->num_sources *
+ p->pwg->num_types;
+ else
+ num_media += p->pwg->num_sizes * p->pwg->num_sources;
+ }
+ else if (p->pwg->num_types)
+ num_media += p->pwg->num_sizes * p->pwg->num_types;
+
+ if ((attr = ippAddCollections(p->ppd_attrs, IPP_TAG_PRINTER,
+ "media-col-database", num_media,
+ NULL)) != NULL)
+ {
+ for (i = p->pwg->num_sizes, pwgsize = p->pwg->sizes, val = attr->values;
+ i > 0;
+ i --, pwgsize ++)
+ {
+ /*
+ * Start by adding the page size without source or type...
+ */
+
+ ppdMarkOption(ppd, "PageSize", pwgsize->map.ppd);
+
+ val->collection = new_media_col(pwgsize, NULL, NULL);
+ val ++;
+
+ /*
+ * Then add the specific, supported combinations of size, source, and
+ * type...
+ */
+
+ if (p->pwg->num_sources > 0)
+ {
+ for (j = p->pwg->num_sources, pwgsource = p->pwg->sources;
+ j > 0;
+ j --, pwgsource ++)
+ {
+ ppdMarkOption(ppd, "InputSlot", pwgsource->ppd);
+
+ if (p->pwg->num_types > 0)
+ {
+ for (k = p->pwg->num_types, pwgtype = p->pwg->types;
+ k > 0;
+ k --, pwgtype ++)
+ {
+ if (!ppdMarkOption(ppd, "MediaType", pwgtype->ppd))
+ {
+ val->collection = new_media_col(pwgsize, pwgsource->pwg,
+ pwgtype->pwg);
+ val ++;
+ }
+ }
+ }
+ else if (!ppdConflicts(ppd))
+ {
+ val->collection = new_media_col(pwgsize, pwgsource->pwg, NULL);
+ val ++;
+ }
+ }
+ }
+ else if (p->pwg->num_types > 0)
+ {
+ for (j = p->pwg->num_types, pwgtype = p->pwg->types;
+ j > 0;
+ j --, pwgtype ++)
+ {
+ if (!ppdMarkOption(ppd, "MediaType", pwgtype->ppd))
+ {
+ val->collection = new_media_col(pwgsize, NULL, pwgtype->pwg);
+ val ++;
+ }
+ }
+ }
+ }
+
+ /*
+ * Update the number of media-col-database values...
+ */
+
+ attr->num_values = val - attr->values;
+ }
+ }
+
+ /*
+ * Output bin...
+ */
+
+ if (p->pwg && p->pwg->num_bins > 0)
+ {
+ attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "output-bin-supported", p->pwg->num_bins,
+ NULL, NULL);
+
+ if (attr != NULL)
+ {
+ for (i = 0, val = attr->values;
+ i < p->pwg->num_bins;
+ i ++, val ++)
+ val->string.text = _cupsStrAlloc(p->pwg->bins[i].pwg);
+ }
+
+ if ((output_bin = ppdFindOption(ppd, "OutputBin")) != NULL)
+ {
+ for (i = 0; i < p->pwg->num_bins; i ++)
+ if (!strcmp(p->pwg->bins[i].ppd, output_bin->defchoice))
+ break;
+
+ if (i >= p->pwg->num_bins)
+ i = 0;
+
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "output-bin-default", NULL, p->pwg->bins[i].pwg);
+ }
+ else
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "output-bin-default", NULL, p->pwg->bins[0].pwg);
+ }
+ else if (((ppd_attr = ppdFindAttr(ppd, "DefaultOutputOrder",
+ NULL)) != NULL &&
+ !strcasecmp(ppd_attr->value, "Reverse")) ||
+ (!ppd_attr && ppd->manufacturer && /* EPSON "compatibility heuristic" */
+ !strcasecmp(ppd->manufacturer, "epson")))
+ {
+ /*
+ * Report that this printer has a single output bin that leaves pages face
+ * up.
+ */
+
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "output-bin-supported", NULL, "face-up");
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "output-bin-default", NULL, "face-up");
+ }
+ else
+ {
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "output-bin-supported", NULL, "face-down");
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "output-bin-default", NULL, "face-down");
+ }
+
+ /*
+ * Printer resolutions...
+ */
+
+ if ((resolution = ppdFindOption(ppd, "Resolution")) == NULL)
+ if ((resolution = ppdFindOption(ppd, "JCLResolution")) == NULL)
+ if ((resolution = ppdFindOption(ppd, "SetResolution")) == NULL)
+ resolution = ppdFindOption(ppd, "CNRes_PGP");
+
+ if (resolution)
+ {
+ /*
+ * Report all supported resolutions...
+ */
+
+ attr = ippAddResolutions(p->ppd_attrs, IPP_TAG_PRINTER,
+ "printer-resolution-supported",
+ resolution->num_choices, IPP_RES_PER_INCH,
+ NULL, NULL);
+
+ for (i = 0, choice = resolution->choices;
+ i < resolution->num_choices;
+ i ++, choice ++)
+ {
+ xdpi = ydpi = (int)strtol(choice->choice, (char **)&resptr, 10);
+ if (resptr > choice->choice && xdpi > 0 && *resptr == 'x')
+ ydpi = (int)strtol(resptr + 1, (char **)&resptr, 10);
+
+ if (xdpi <= 0 || ydpi <= 0)
+ {
+ cupsdLogMessage(CUPSD_LOG_WARN,
+ "Bad resolution \"%s\" for printer %s.",
+ choice->choice, p->name);
+ xdpi = ydpi = 72;
+ }
+
+ attr->values[i].resolution.xres = xdpi;
+ attr->values[i].resolution.yres = ydpi;
+ attr->values[i].resolution.units = IPP_RES_PER_INCH;
+
+ if (choice->marked)
+ ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER,
+ "printer-resolution-default", IPP_RES_PER_INCH,
+ xdpi, ydpi);
+ }
+ }
+ else if ((ppd_attr = ppdFindAttr(ppd, "DefaultResolution", NULL)) != NULL &&
+ ppd_attr->value)
+ {
+ /*
+ * Just the DefaultResolution to report...
+ */
+
+ xdpi = ydpi = (int)strtol(ppd_attr->value, (char **)&resptr, 10);
+ if (resptr > ppd_attr->value && xdpi > 0)
+ {
+ if (*resptr == 'x')
+ ydpi = (int)strtol(resptr + 1, (char **)&resptr, 10);
+ else
+ ydpi = xdpi;
+ }
+
+ if (xdpi <= 0 || ydpi <= 0)
+ {
+ cupsdLogMessage(CUPSD_LOG_WARN,
+ "Bad default resolution \"%s\" for printer %s.",
+ ppd_attr->value, p->name);
+ xdpi = ydpi = 72;
+ }
+
+ ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER,
+ "printer-resolution-default", IPP_RES_PER_INCH,
+ xdpi, ydpi);
+ ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER,
+ "printer-resolution-supported", IPP_RES_PER_INCH,
+ xdpi, ydpi);
+ }
+ else
+ {
+ /*
+ * No resolutions in PPD - make one up...
+ */
+
+ ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER,
+ "printer-resolution-default", IPP_RES_PER_INCH,
+ 72, 72);
+ ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER,
+ "printer-resolution-supported", IPP_RES_PER_INCH,
+ 72, 72);
+ }
+
+ /*
+ * Duplexing, etc...
+ */
+
+ if ((duplex = ppdFindOption(ppd, "Duplex")) == NULL)
+ if ((duplex = ppdFindOption(ppd, "EFDuplex")) == NULL)
+ if ((duplex = ppdFindOption(ppd, "EFDuplexing")) == NULL)
+ if ((duplex = ppdFindOption(ppd, "KD03Duplex")) == NULL)
+ duplex = ppdFindOption(ppd, "JCLDuplex");
+
+ if (duplex && duplex->num_choices > 1 &&
+ !ppdInstallableConflict(ppd, duplex->keyword, "DuplexTumble"))
+ {
+ p->type |= CUPS_PRINTER_DUPLEX;
+
+ ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "sides-supported", 3, NULL, sides);
+
+ if (!strcasecmp(duplex->defchoice, "DuplexTumble"))
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "sides-default", NULL, "two-sided-short-edge");
+ else if (!strcasecmp(duplex->defchoice, "DuplexNoTumble"))
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "sides-default", NULL, "two-sided-long-edge");
+ else
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "sides-default", NULL, "one-sided");
+ }
+ else
+ {
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "sides-supported", NULL, "one-sided");
+ ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "sides-default", NULL, "one-sided");
+ }
+
+ if (ppdFindOption(ppd, "Collate") != NULL)
+ p->type |= CUPS_PRINTER_COLLATE;
+
+ if (ppdFindOption(ppd, "StapleLocation") != NULL)
+ {
+ p->type |= CUPS_PRINTER_STAPLE;
+ finishings[num_finishings++] = IPP_FINISHINGS_STAPLE;
+ }
+
+ if (ppdFindOption(ppd, "BindEdge") != NULL)
+ {
+ p->type |= CUPS_PRINTER_BIND;
+ finishings[num_finishings++] = IPP_FINISHINGS_BIND;
+ }
+
+ for (i = 0; i < ppd->num_sizes; i ++)
+ if (ppd->sizes[i].length > 1728)
+ p->type |= CUPS_PRINTER_LARGE;
+ else if (ppd->sizes[i].length > 1008)
+ p->type |= CUPS_PRINTER_MEDIUM;
+ else
+ p->type |= CUPS_PRINTER_SMALL;
+
+ if ((ppd_attr = ppdFindAttr(ppd, "APICADriver", NULL)) != NULL &&
+ ppd_attr->value && !strcasecmp(ppd_attr->value, "true"))
+ {
+ if ((ppd_attr = ppdFindAttr(ppd, "APScannerOnly", NULL)) != NULL &&
+ ppd_attr->value && !strcasecmp(ppd_attr->value, "true"))
+ p->type |= CUPS_PRINTER_SCANNER;
+ else
+ p->type |= CUPS_PRINTER_MFP;
+ }
+
+ /*
+ * Add a filter from application/vnd.cups-raw to printer/name to
+ * handle "raw" printing by users.
+ */
+
+ add_string_array(&(p->filters), "application/vnd.cups-raw 0 -");
+
+ /*
+ * Add any pre-filters in the PPD file...
+ */
+
+ if ((ppd_attr = ppdFindAttr(ppd, "cupsPreFilter", NULL)) != NULL)
+ {
+ for (; ppd_attr; ppd_attr = ppdFindNextAttr(ppd, "cupsPreFilter", NULL))
+ if (ppd_attr->value)
+ add_string_array(&(p->pre_filters), ppd_attr->value);
+ }
+
+ /*
+ * Add any filters in the PPD file...
+ */
+
+ DEBUG_printf(("ppd->num_filters = %d\n", ppd->num_filters));
+ for (i = 0; i < ppd->num_filters; i ++)
+ {
+ DEBUG_printf(("ppd->filters[%d] = \"%s\"\n", i, ppd->filters[i]));
+ add_string_array(&(p->filters), ppd->filters[i]);
+
+ if (!strncasecmp(ppd->filters[i], "application/vnd.cups-command", 28) &&
+ isspace(ppd->filters[i][28] & 255))
+ p->type |= CUPS_PRINTER_COMMANDS;
+ }
+
+ if (ppd->num_filters == 0)
+ {
+ /*
+ * If there are no filters, add PostScript printing filters.
+ */
+
+ add_string_array(&(p->filters),
+ "application/vnd.cups-command 0 commandtops");
+ add_string_array(&(p->filters),
+ "application/vnd.cups-postscript 0 -");
+
+ p->type |= CUPS_PRINTER_COMMANDS;
+ }
+ else if (!(p->type & CUPS_PRINTER_COMMANDS))
+ {
+ /*
+ * See if this is a PostScript device without a command filter...
+ */
+
+ for (i = 0; i < ppd->num_filters; i ++)
+ if (!strncasecmp(ppd->filters[i],
+ "application/vnd.cups-postscript", 31) &&
+ isspace(ppd->filters[i][31] & 255))
+ break;
+
+ if (i < ppd->num_filters)
+ {
+ /*
+ * Add the generic PostScript command filter...
+ */
+
+ add_string_array(&(p->filters),
+ "application/vnd.cups-command 0 commandtops");
+ p->type |= CUPS_PRINTER_COMMANDS;
+ }
+ }
+
+ if (p->type & CUPS_PRINTER_COMMANDS)
+ {
+ char *commands, /* Copy of commands */
+ *start, /* Start of name */
+ *end; /* End of name */
+ int count; /* Number of commands */
+
+
+ if ((ppd_attr = ppdFindAttr(ppd, "cupsCommands", NULL)) != NULL &&
+ ppd_attr->value && ppd_attr->value[0])
+ {
+ for (count = 0, start = ppd_attr->value; *start; count ++)
+ {
+ while (isspace(*start & 255))
+ start ++;
+
+ if (!*start)
+ break;
+
+ while (*start && !isspace(*start & 255))
+ start ++;
+ }
+ }
+ else
+ count = 0;
+
+ if (count > 0)
+ {
+ /*
+ * Make a copy of the commands string and count how many ...
+ */
+
+ attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+ "printer-commands", count, NULL, NULL);
+
+ commands = strdup(ppd_attr->value);
+
+ for (count = 0, start = commands; *start; count ++)
+ {
+ while (isspace(*start & 255))
+ start ++;