Add PPD Pin support for Ricoh Printers
Adds ability for complex remapping of IPP->PPD values.
BUG=chromium:1082308
TEST=tast run printer.PinPrint*
Change-Id: Ieeca1c13dac5f7f951b99c3de01979c525099891
diff --git a/cups/ppd-cache.c b/cups/ppd-cache.c
index 194cf91..9b57363 100644
--- a/cups/ppd-cache.c
+++ b/cups/ppd-cache.c
@@ -60,6 +60,8 @@
static int pwg_find_size_tracker(const char *media_name,
const struct pwg_media_size_tracker_s *trackers,
int trackers_length);
+static char * _ppdGetAttributeValue(ipp_attribute_t *attr);
+static char * _ppdTransformValue(ipp_t *collection, char *value_buf);
/*
@@ -4416,6 +4418,234 @@
/*
+ * '_ppdGetAttributeValue()' retrieves the value from attr and converts it to a
+ * string. Caller is responsible for freeing the returned string when done.
+ */
+static char * /* O - allocated buffer containing the value */
+_ppdGetAttributeValue(
+ ipp_attribute_t *attr) /* I - attribute whose value should be retrieved */
+{
+ char *value_buf = NULL;
+ int value_buf_size;
+
+
+ switch (ippGetValueTag(attr))
+ {
+ case IPP_TAG_INTEGER:
+ {
+ int value = ippGetInteger(attr, 0);
+ value_buf_size = snprintf(NULL, 0, "%d", value);
+ if ((value_buf = (char *)calloc((size_t)++value_buf_size,
+ sizeof(char))) == NULL)
+ break;
+ snprintf(value_buf, (size_t)value_buf_size, "%d", value);
+ }
+ break;
+
+ case IPP_TAG_BOOLEAN:
+ {
+ const char *value = ippGetBoolean(attr, 0) ? "True" : "False";
+ value_buf_size = strlen(value);
+ if ((value_buf = (char *)calloc((size_t)++value_buf_size,
+ sizeof(char))) == NULL)
+ break;
+ strlcpy(value_buf, value, (size_t)value_buf_size);
+ }
+ break;
+
+ case IPP_TAG_STRING:
+ {
+ char *ptr;
+ if ((ptr = ippGetOctetString(attr, 0, &value_buf_size)) != NULL)
+ {
+ if ((value_buf = (char *)calloc((size_t)(value_buf_size + 1),
+ sizeof(char))) != NULL)
+ {
+ strncat(value_buf, ptr, (size_t)value_buf_size);
+ }
+ }
+ }
+ break;
+
+ default:
+ {
+ // See if the value can be decoded as a string. If not, it is not
+ // supported..
+ const char* string;
+ if ((string = ippGetString(attr, 0, NULL)) != NULL)
+ {
+ value_buf_size = strlen(string);
+ if ((value_buf = (char *)calloc((size_t)++value_buf_size,
+ sizeof(char))) == NULL)
+ break;
+ strlcpy(value_buf, string, (size_t)value_buf_size);
+ }
+ else
+ {
+ DEBUG_printf((
+ "1_ppdGetAttributeValue: Unsupported attr: name=%s, value_tag=%d",
+ ippGetName(attr), ippGetValueTag(attr)));
+ }
+ }
+ break;
+ }
+
+ return value_buf;
+}
+
+/*
+ * '_ppdTransformValue()' - Applies any necessary transformations to the IPP
+ * value to make it acceptable to the PPD. Any attribute in |collection| that
+ * begins with '_' is considered to be a transformation attribute, the name
+ * indicating the type of transformation. This function will take ownership of
+ * |value_buf|, but the caller is responsible for the lifecycle of the return
+ * value.
+ */
+static char * /* O - allocated buffer containing the value */
+_ppdTransformValue(
+ ipp_t *collection, /* I - transformations to perform */
+ char *value_buf) /* I - buffer to be transformed. Not valid
+ * after call completes. */
+{
+ ipp_attribute_t *option;
+
+
+ for (option = ippFirstAttribute(collection);
+ option;
+ option = ippNextAttribute(collection))
+ {
+ const char *option_name = ippGetName(option);
+
+
+ // Skip the actual option attributes.
+ if (option_name[0] != '_')
+ continue;
+
+ if (!strcmp(option_name, "_delimiters"))
+ {
+ const char *delimiters = ippGetString(option, 0, NULL);
+ char *p = strpbrk(value_buf, delimiters);
+
+ if (p)
+ *p = '\0';
+ }
+ else if (!strcmp(option_name, "_constraint"))
+ {
+ int max,
+ min = ippGetRange(option, 0, &max);
+
+
+ if (strlen(value_buf) < min)
+ {
+ // If the supplied value is too small, then we can't convert this value,
+ // so don't pass it to the filters.
+ DEBUG_printf(("1_ppdTransformValue: %s is shorter than %d", value_buf,
+ min));
+ free (value_buf);
+ return NULL;
+ }
+ else if (strlen(value_buf) > max)
+ {
+ // Truncate the value at the length allowed.
+ value_buf[max] = '\0';
+ }
+ }
+ else if (!strcmp(option_name, "_valid-chars"))
+ {
+ ipp_attribute_t *replacement_attr;
+ const char *replacement_char;
+
+
+ if (((replacement_attr = ippFindAttribute(collection,
+ "_replacement-char",
+ IPP_TAG_KEYWORD)) != NULL) &&
+ ((replacement_char = ippGetString(replacement_attr, 0,
+ NULL)) != NULL) &&
+ (strlen(replacement_char) == 1))
+ {
+ const char *valid_chars = ippGetString(option, 0, NULL);
+ int i,
+ len = strlen(value_buf);
+
+
+ for (i = 0; i < len; ++i)
+ {
+ if (strchr(valid_chars, value_buf[i]) == NULL)
+ {
+ // Invalid character. Replace it with the replacement char.
+ value_buf[i] = replacement_char[0];
+ }
+ }
+ }
+ else
+ {
+ DEBUG_printf(
+ ("1_ppdTransformValue: Can't apply _valid_chars attribute because "
+ "no _replacement-char found"));
+ free(value_buf);
+ return NULL;
+ }
+ }
+ else if (!strcmp(option_name, "_replacement-char"))
+ {
+ // Just skip this one - it's handled in _valid-chars above.
+ continue;
+ }
+ else if (!strcmp(option_name, "_mapping"))
+ {
+ /*
+ * Collection of the IPP -> PPD value mappings.
+ */
+ ipp_t *enum_collection = ippGetCollection(option, 0);
+ if (enum_collection)
+ {
+ const char *option_val;
+ ipp_attribute_t *option_attr;
+
+
+ if (((option_attr = ippFindAttribute(enum_collection, value_buf,
+ IPP_TAG_KEYWORD)) != NULL) &&
+ ((option_val = ippGetString(option_attr, 0, NULL)) != NULL)) {
+ /*
+ * Replace the IPP string with the PPD string.
+ */
+ size_t new_size = (strlen(option_val) + 1) * sizeof(char);
+ if ((value_buf = (char *)realloc(value_buf, new_size)) == NULL)
+ {
+ free(value_buf);
+ return NULL;
+ }
+ strcpy(value_buf, option_val);
+ }
+ else
+ {
+ ipp_attribute_t *enum_attr;
+ DEBUG_printf(("1_ppdTransformValue: No mapping found for %s",
+ value_buf));
+
+ free(value_buf);
+ return NULL;
+ }
+ } else {
+ DEBUG_printf(
+ ("1_ppdTransformValue: No collection found in option: name=%s, "
+ "value_tag=%d", ippGetName(option), ippGetValueTag(option)));
+ free(value_buf);
+ return NULL;
+ }
+ }
+ else
+ {
+ DEBUG_printf(
+ ("1_ppdTransformValue: unknown transformation. attr: name=%s, "
+ "value_tag=%d", ippGetName(option), ippGetValueTag(option)));
+ }
+ }
+
+ return value_buf;
+}
+
+/*
* '_ppdConvertOptions()' - Converts the relevant ipp attributes found in
* |job_attrs| to PPD options suitable for sending to a filter, using
* |option_mappings| to determine which attribute should be converted, and to
@@ -4455,84 +4685,29 @@
ippGetName(attr),
IPP_TAG_BEGIN_COLLECTION)) != NULL)
{
- char *value_buf = NULL;
- int value_buf_size;
-
-
/*
* Since a mapping was found for this IPP attribute, convert the value
* of the attribute to a string. This is so it can be added to the mapping
* option values below. Do this IPP value -> string conversion only once,
* though, since the mapping may contain several OEM-specific options.
*/
+ char *value_buf;
+ ipp_attribute_t *option;
+ ipp_t *collection = ippGetCollection(mapping, 0);
+
+
+ if ((value_buf = _ppdGetAttributeValue(attr)) == NULL)
+ continue;
+
+ DEBUG_printf(("3_ppdConvertOptions: attr(%s)=%s",ippGetName(attr),
+ value_buf));
+
+ if ((value_buf = _ppdTransformValue(collection, value_buf)) == NULL)
+ continue;
+
DEBUG_printf(
- ("3_ppdConvertOptions: Found mapping for attr: name=%s, value_tag=%d",
- ippGetName(attr), ippGetValueTag(attr)));
- switch (ippGetValueTag(attr))
- {
- case IPP_TAG_INTEGER:
- {
- int value = ippGetInteger(attr, 0);
- value_buf_size = snprintf(NULL, 0, "%d", value);
- if ((value_buf = (char *)calloc((size_t)++value_buf_size,
- sizeof(char))) == NULL)
- continue;
- snprintf(value_buf, (size_t)value_buf_size, "%d", value);
- }
- break;
-
- case IPP_TAG_BOOLEAN:
- {
- const char *value = ippGetBoolean(attr, 0) ? "True" : "False";
- value_buf_size = strlen(value);
- if ((value_buf = (char *)calloc((size_t)++value_buf_size,
- sizeof(char))) == NULL)
- continue;
- strlcpy(value_buf, value, (size_t)value_buf_size);
- }
- break;
-
- case IPP_TAG_STRING:
- {
- char *ptr;
- if ((ptr = ippGetOctetString(attr, 0, &value_buf_size)) != NULL) {
- if ((value_buf = (char *)calloc((size_t)(value_buf_size + 1),
- sizeof(char))) != NULL) {
- strncat(value_buf, ptr, (size_t)value_buf_size);
- }
- }
- }
- break;
-
- default:
- {
- // See if the value can be decoded as a string. If not, it is not
- // supported..
- const char* string;
- if ((string = ippGetString(attr, 0, NULL)) != NULL) {
- value_buf_size = strlen(string);
- if ((value_buf = (char *)calloc((size_t)++value_buf_size,
- sizeof(char))) == NULL)
- continue;
- strlcpy(value_buf, string, value_buf_size);
- } else {
- DEBUG_printf((
- "_ppdConvertOptions: Unsupported attr: name=%s, value_tag=%d",
- ippGetName(attr), ippGetValueTag(attr)));
- continue;
- }
- }
- break;
- }
- if (value_buf)
- {
- ipp_attribute_t *option;
- ipp_t *collection = ippGetCollection(mapping, 0);
-
-
- DEBUG_printf(
- ("2_ppdConvertOptions: attr: name=%s, value_tag=%d, value=%s",
- ippGetName(attr), ippGetValueTag(attr), value_buf));
+ ("2_ppdConvertOptions: attr: name=%s, value_tag=%d, value=%s",
+ ippGetName(attr), ippGetValueTag(attr), value_buf));
/*
* Add the IPP attribute value to each of the remapped options. It is
@@ -4544,9 +4719,16 @@
option;
option = ippNextAttribute(collection))
{
- char *option_value_buf = NULL;
- const char *option_value_fmt = ippGetString(option, 0, NULL);
- int option_value_size =
+ char *option_value_buf = NULL;
+ const char *option_value_fmt;
+ int option_value_size;
+
+ // Skip the meta attributes.
+ if (ippGetName(option)[0] == '_')
+ continue;
+
+ option_value_fmt = ippGetString(option, 0, NULL);
+ option_value_size =
snprintf(NULL, 0, option_value_fmt, value_buf);
if ((option_value_buf = (char *)calloc((size_t)++option_value_size,
sizeof(char))) == NULL)
@@ -4562,9 +4744,9 @@
}
free(value_buf);
- }
}
}
+
return num_options;
}
diff --git a/option_mapping.md b/option_mapping.md
new file mode 100644
index 0000000..bce8e81
--- /dev/null
+++ b/option_mapping.md
@@ -0,0 +1,133 @@
+# IPP Attribute to PPD option mapping
+In order to support features that differ from PPD to PPD, the ppd-cache version
+of a printer now includes a series of mappings that are used to determine what
+options are passed to the filters. What _ppdConvertOptions expects to find is as
+follows:
+
+## option-mappings
+The (cupsd_printer_t*)p->ppd_attrs should contain an ipp collection called
+"option-mappings". It is expect that this collection will contain subcollections
+which indicate how IPP attributes found in a print job are to be converted into
+filter options. The name of these subcollections should be the name of the
+IPP attribute that should be translated:
+
+`
+ipp_t *option_mappings = ippFindAttribute(p->ppd_attrs,
+ "option-mappings",
+ IPP_TAG_BEGIN_COLLECTION);
+ipp_t *ipp_attr = ippNew();
+ippAddString(ipp_attr, IPP_TAG_ZERO, IPP_TAG_TEXT, "setting", NULL, "True");
+ippAddString(ipp_attr, IPP_TAG_ZERO, IPP_TAG_TEXT, "foo", NULL, "%s");
+ippAddString(ipp_attr, IPP_TAG_ZERO, IPP_TAG_TEXT, "bar", NULL, "Custom.%s");
+ippAddCollection(option_mappings, IPP_TAG_ZERO, "ipp-attr", ipp_attr);
+`
+
+If a job comes in whose job->attrs contains an "ipp-attr" whose value is
+baz@Domain.com, then this mapping will result in the following three options
+being passed to the filter:
+
+`
+setting=True
+foo=baz@Domain.com
+bar=Custom.baz@Domain.com
+`
+
+The only substitutions allowed currently are "%s" for the transformed value of
+the attribute. Non-string values will be converted to strings.
+
+### transformations
+Sometimes IPP values cannot be converted directly into PPD values, so
+transformations can also be added to the collection. The name of a
+transformation must begin with "_", and the transformation should be in the same collection which contains the mappings for the specified ippAttribute:
+
+`
+ipp_t *option_mappings = ippFindAttribute(p->ppd_attrs,
+ "option-mappings",
+ IPP_TAG_BEGIN_COLLECTION);
+ipp_t *ipp_attr = ippNew();
+ippAddString(ipp_attr, IPP_TAG_ZERO, IPP_TAG_TEXT, "bar", NULL, "Custom.%s");
+ippAddString(ipp_attr, IPP_TAG_ZERO, IPP_TAG_KEYWORD, "_delimiters", NULL, "@");
+ippAddCollection(option_mappings, IPP_TAG_ZERO, "ipp-attr", ipp_attr);
+`
+
+Transformations are applied to the ipp value before it is added to the option
+strings. And they are applied in the order they are found in the collection.
+So in this case, if a job comes in whose job_attrs contains the
+"ipp-attr=baz@Domain.com", then the following options would be output:
+
+`
+bar=Custom.baz
+`
+
+The currently available transformations are:
+
+#### _delimiters
+Delimiters must always be a IPP_TAG_KEYWORD, and the string is the list of
+characters that terminate the value. So for example:
+
+`
+ippAddString(ipp_attr, IPP_TAG_ZERO, IPP_TAG_KEYWORD, "_delimiters", NULL, "@");
+`
+
+Means that only characters before the first '@' in a ipp attribute value will
+be passed as an option value.
+
+#### _valid-chars
+Valid Chars must always be an IPP_TAG_KEYWORD, and the string is the list of
+characters that are allowed in as an option value. Any invalid character is
+replace with the character found in the _replacement-char transformation. So if
+the following transformations were in place:
+
+`
+ippAddString(ipp_attr, IPP_TAG_ZERO, IPP_TAG_KEYWORD, "_valid-chars", NULL,
+ "abcdefghijklmnopqrstuvwxyz");
+ippAddString(ipp_attr, IPP_TAG_ZERO, IPP_TAG_KEYWORD, "_replacement-char", NULL,
+ "-");
+`
+
+And the value of the specified ipp attribute was "ipp-attr=baz@Domain.com", the transformed value used for the options would be:
+
+`
+baz--omain-com
+`
+#### _constraint
+Constraint must always be an IPP_TAG_RANGE attribute, and it contains the
+minimum and maximum allowed characters for a filter option. If the value is
+shorter than the minimum, then the mapping is aborted and no options are passed
+to the filter for this transformation. If the value is longer than the maximum,
+then the string is truncated to that length. So if this transformation was in
+place:
+
+`
+ippAddRange(ipp_attr, IPP_TAG_ZERO, "_constraint", 2, 8);
+`
+
+And the value of the specified ipp attribute was "ipp-attr=baz@Domain.com", the transformed value used for the options would be:
+
+`
+baz@Doma
+`
+
+#### _mapping
+Mapping is always an ippCollection that consists of a series of IPP_TAG_KEYWORDs
+whose name is the IPP value and whose value is the PPD filter option value. If
+the ipp value can be found in the collection, then it is exchanged for the value
+of that keyword. If not, then the ipp attribute is discard and not converted
+into filter options. So if the following keywords existed in the mapping:
+
+`
+ipp_t *mapping = ippNew();
+ippAddString(mapping, IPP_TAG_ZERO, IPP_TAG_KEYWORD, "baz", NULL, "bottle");
+ippAddString(mapping, IPP_TAG_ZERO, IPP_TAG_KEYWORD, "bat", NULL, "ball");
+ippAddCollection(ipp_attr, IPP_TAG_ZERO, "_mapping", mapping);
+`
+
+And the value of the specified ipp attribute was "ipp-attr=baz", the transformed
+value used for the options would be:
+
+`
+bottle
+`
+
+
+
diff --git a/scheduler/printers.c b/scheduler/printers.c
index 75f99be..aec2bc3 100644
--- a/scheduler/printers.c
+++ b/scheduler/printers.c
@@ -5204,6 +5204,99 @@
ippAddString(mapping, IPP_TAG_ZERO, IPP_TAG_TEXT, coption->keyword, NULL,
"Custom.%s");
}
+ /*** Ricoh ***/
+ else if (((option = ppdFindOption(ppd, "JobType")) != NULL) &&
+ ((choice = ppdFindChoice(option, "LockedPrint")) != NULL))
+ {
+ ppd_coption_t *user_id_opt;
+ ppd_cparam_t *user_id_param;
+
+ /*
+ * While all Ricoh printers use JobType=LockedPrint to indicate PIN
+ * printing, different printers use different custom options to pass the PIN
+ * to the device, so query the PPD to determine the name of the password
+ * option.
+ */
+ if ((((coption = ppdFindCustomOption(ppd, "Password")) != NULL) ||
+ ((coption = ppdFindCustomOption(ppd, "LockedPrintPassword")) != NULL) ||
+ ((coption = ppdFindCustomOption(ppd, "JobPassword")) != NULL)) &&
+ ((cparam = ppdFirstCustomParam(coption)) != NULL))
+ {
+ mapping = ippNew();
+
+ /*
+ * JobType = LockedPrint
+ */
+ ippAddString(mapping, IPP_TAG_ZERO, IPP_TAG_TEXT, option->keyword, NULL,
+ choice->choice);
+
+ /*
+ * JobPassword = Custom.1234 or
+ * LockedPrintPassword = Custom.1234 or
+ * JobPassword = Custom.1234 or
+ */
+ ippAddString(mapping, IPP_TAG_ZERO, IPP_TAG_TEXT, coption->keyword, NULL,
+ "Custom.%s");
+ }
+
+ /*
+ * The option name for User ID varies by PPD.
+ */
+ if ((((user_id_opt = ppdFindCustomOption(ppd, "UserId")) != NULL) ||
+ ((user_id_opt = ppdFindCustomOption(ppd, "UserID")) != NULL)) &&
+ ((user_id_param = ppdFirstCustomParam(user_id_opt)) != NULL))
+ {
+ /*
+ * Ricoh printers display the jobs by "User ID" rather than "User Name".
+ * User IDs must be between 1 & 8 characters, and can only contain the
+ * characters "a-z", "A-Z", "0-9", and "-./:_". If the Job ID is longer
+ * than 8 characters it is rejected. The IPP value to use is the
+ * job-originating-user-name, but since that is the full email address of
+ * the user, trim to just the username, then trim that to no more than 8
+ * characters, and replace any illegal characters with '-'.
+ */
+ ipp_t *userid_mapping = ippNew();
+
+ /*
+ * Remove domain from email address so User ID is just username.
+ */
+ ippAddString(userid_mapping, IPP_TAG_ZERO, IPP_TAG_KEYWORD, "_delimiters",
+ NULL, "@");
+
+ /*
+ * Ricoh specifies that the UserId must be a certain size.
+ */
+ ippAddRange(userid_mapping, IPP_TAG_ZERO, "_constraint",
+ user_id_param->minimum.custom_string,
+ user_id_param->maximum.custom_string);
+
+ /*
+ * Only the characters a-z,A-Z,0-9,-./:_ are valid for User ID.
+ */
+ ippAddString(userid_mapping, IPP_TAG_ZERO, IPP_TAG_KEYWORD,
+ "_valid-chars", NULL,
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789"
+ "-./:_");
+
+ /*
+ * Replace any non-valid characters with "-"
+ */
+ ippAddString(userid_mapping, IPP_TAG_ZERO, IPP_TAG_KEYWORD,
+ "_replacement-char", NULL, "-");
+
+ /*
+ * UserId = Custom.%s or
+ * UserID = Custom.%s
+ */
+ ippAddString(userid_mapping, IPP_TAG_ZERO, IPP_TAG_TEXT,
+ user_id_opt->keyword, NULL, "Custom.%s");
+
+ ippAddCollection(mappings, IPP_TAG_ZERO, "job-originating-user-name",
+ userid_mapping);
+ }
+ }
/* In order for a mapping to be establsihed the code above should have:
* a) initialized mapping with the options to be sent to the filter