+ if (!ipp || !name)
+ return (NULL);
+
+ /*
+ * Reset the current pointer...
+ */
+
+ ipp->current = NULL;
+ ipp->atend = 0;
+
+ /*
+ * Search for the attribute...
+ */
+
+ return (ippFindNextAttribute(ipp, name, type));
+}
+
+
+/*
+ * 'ippFindNextAttribute()' - Find the next named attribute in a request.
+ *
+ * Starting with CUPS 2.0, the attribute name can contain a hierarchical list
+ * of attribute and member names separated by slashes, for example
+ * "media-col/media-size".
+ */
+
+ipp_attribute_t * /* O - Matching attribute */
+ippFindNextAttribute(ipp_t *ipp, /* I - IPP message */
+ const char *name, /* I - Name of attribute */
+ ipp_tag_t type) /* I - Type of attribute */
+{
+ ipp_attribute_t *attr, /* Current atttribute */
+ *childattr; /* Child attribute */
+ ipp_tag_t value_tag; /* Value tag */
+ char parent[1024], /* Parent attribute name */
+ *child = NULL; /* Child attribute name */
+
+
+ DEBUG_printf(("2ippFindNextAttribute(ipp=%p, name=\"%s\", type=%02x(%s))", (void *)ipp, name, type, ippTagString(type)));
+
+ if (!ipp || !name)
+ return (NULL);
+
+ DEBUG_printf(("3ippFindNextAttribute: atend=%d", ipp->atend));
+
+ if (ipp->atend)
+ return (NULL);
+
+ if (strchr(name, '/'))
+ {
+ /*
+ * Search for child attribute...
+ */
+
+ strlcpy(parent, name, sizeof(parent));
+ if ((child = strchr(parent, '/')) == NULL)
+ {
+ DEBUG_puts("3ippFindNextAttribute: Attribute name too long.");
+ return (NULL);
+ }
+
+ *child++ = '\0';
+
+ if (ipp->current && ipp->current->name && ipp->current->value_tag == IPP_TAG_BEGIN_COLLECTION && !strcmp(parent, ipp->current->name))
+ {
+ while (ipp->curindex < ipp->current->num_values)
+ {
+ if ((childattr = ippFindNextAttribute(ipp->current->values[ipp->curindex].collection, child, type)) != NULL)
+ return (childattr);
+
+ ipp->curindex ++;
+ if (ipp->curindex < ipp->current->num_values && ipp->current->values[ipp->curindex].collection)
+ ipp->current->values[ipp->curindex].collection->current = NULL;
+ }
+
+ ipp->prev = ipp->current;
+ ipp->current = ipp->current->next;
+ ipp->curindex = 0;
+
+ if (!ipp->current)
+ {
+ ipp->atend = 1;
+ return (NULL);
+ }
+ }
+
+ if (!ipp->current)
+ {
+ ipp->prev = NULL;
+ ipp->current = ipp->attrs;
+ ipp->curindex = 0;
+ }
+
+ name = parent;
+ attr = ipp->current;
+ }
+ else if (ipp->current)
+ {
+ ipp->prev = ipp->current;
+ attr = ipp->current->next;
+ }
+ else
+ {
+ ipp->prev = NULL;
+ attr = ipp->attrs;
+ }
+
+ for (; attr != NULL; ipp->prev = attr, attr = attr->next)
+ {
+ DEBUG_printf(("4ippFindAttribute: attr=%p, name=\"%s\"", (void *)attr, attr->name));
+
+ value_tag = (ipp_tag_t)(attr->value_tag & IPP_TAG_CUPS_MASK);
+
+ if (attr->name != NULL && _cups_strcasecmp(attr->name, name) == 0 &&
+ (value_tag == type || type == IPP_TAG_ZERO || name == parent ||
+ (value_tag == IPP_TAG_TEXTLANG && type == IPP_TAG_TEXT) ||
+ (value_tag == IPP_TAG_NAMELANG && type == IPP_TAG_NAME)))
+ {
+ ipp->current = attr;
+
+ if (name == parent && attr->value_tag == IPP_TAG_BEGIN_COLLECTION)
+ {
+ int i; /* Looping var */
+
+ for (i = 0; i < attr->num_values; i ++)
+ {
+ if ((childattr = ippFindAttribute(attr->values[i].collection, child, type)) != NULL)
+ {
+ attr->values[0].collection->curindex = i;
+ return (childattr);
+ }
+ }
+ }
+ else
+ return (attr);
+ }
+ }
+
+ ipp->current = NULL;
+ ipp->prev = NULL;
+ ipp->atend = 1;
+
+ return (NULL);
+}
+
+
+/*
+ * 'ippFirstAttribute()' - Return the first attribute in the message.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+ipp_attribute_t * /* O - First attribute or @code NULL@ if none */
+ippFirstAttribute(ipp_t *ipp) /* I - IPP message */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!ipp)
+ return (NULL);
+
+ /*
+ * Return the first attribute...
+ */
+
+ return (ipp->current = ipp->attrs);
+}
+
+
+/*
+ * 'ippGetBoolean()' - Get a boolean value for an attribute.
+ *
+ * The @code element@ parameter specifies which value to get from 0 to
+ * @code ippGetCount(attr)@ - 1.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+int /* O - Boolean value or 0 on error */
+ippGetBoolean(ipp_attribute_t *attr, /* I - IPP attribute */
+ int element) /* I - Value number (0-based) */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!attr || attr->value_tag != IPP_TAG_BOOLEAN ||
+ element < 0 || element >= attr->num_values)
+ return (0);
+
+ /*
+ * Return the value...
+ */
+
+ return (attr->values[element].boolean);
+}
+
+
+/*
+ * 'ippGetCollection()' - Get a collection value for an attribute.
+ *
+ * The @code element@ parameter specifies which value to get from 0 to
+ * @code ippGetCount(attr)@ - 1.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+ipp_t * /* O - Collection value or @code NULL@ on error */
+ippGetCollection(
+ ipp_attribute_t *attr, /* I - IPP attribute */
+ int element) /* I - Value number (0-based) */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!attr || attr->value_tag != IPP_TAG_BEGIN_COLLECTION ||
+ element < 0 || element >= attr->num_values)
+ return (NULL);
+
+ /*
+ * Return the value...
+ */
+
+ return (attr->values[element].collection);
+}
+
+
+/*
+ * 'ippGetCount()' - Get the number of values in an attribute.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+int /* O - Number of values or 0 on error */
+ippGetCount(ipp_attribute_t *attr) /* I - IPP attribute */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!attr)
+ return (0);
+
+ /*
+ * Return the number of values...
+ */
+
+ return (attr->num_values);
+}
+
+
+/*
+ * 'ippGetDate()' - Get a dateTime value for an attribute.
+ *
+ * The @code element@ parameter specifies which value to get from 0 to
+ * @code ippGetCount(attr)@ - 1.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+const ipp_uchar_t * /* O - dateTime value or @code NULL@ */
+ippGetDate(ipp_attribute_t *attr, /* I - IPP attribute */
+ int element) /* I - Value number (0-based) */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!attr || attr->value_tag != IPP_TAG_DATE ||
+ element < 0 || element >= attr->num_values)
+ return (NULL);
+
+ /*
+ * Return the value...
+ */
+
+ return (attr->values[element].date);
+}
+
+
+/*
+ * 'ippGetGroupTag()' - Get the group associated with an attribute.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+ipp_tag_t /* O - Group tag or @code IPP_TAG_ZERO@ on error */
+ippGetGroupTag(ipp_attribute_t *attr) /* I - IPP attribute */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!attr)
+ return (IPP_TAG_ZERO);
+
+ /*
+ * Return the group...
+ */
+
+ return (attr->group_tag);
+}
+
+
+/*
+ * 'ippGetInteger()' - Get the integer/enum value for an attribute.
+ *
+ * The @code element@ parameter specifies which value to get from 0 to
+ * @code ippGetCount(attr)@ - 1.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+int /* O - Value or 0 on error */
+ippGetInteger(ipp_attribute_t *attr, /* I - IPP attribute */
+ int element) /* I - Value number (0-based) */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!attr || (attr->value_tag != IPP_TAG_INTEGER && attr->value_tag != IPP_TAG_ENUM) ||
+ element < 0 || element >= attr->num_values)
+ return (0);
+
+ /*
+ * Return the value...
+ */
+
+ return (attr->values[element].integer);
+}
+
+
+/*
+ * 'ippGetName()' - Get the attribute name.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+const char * /* O - Attribute name or @code NULL@ for separators */
+ippGetName(ipp_attribute_t *attr) /* I - IPP attribute */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!attr)
+ return (NULL);
+
+ /*
+ * Return the name...
+ */
+
+ return (attr->name);
+}
+
+
+/*
+ * 'ippGetOctetString()' - Get an octetString value from an IPP attribute.
+ *
+ * The @code element@ parameter specifies which value to get from 0 to
+ * @code ippGetCount(attr)@ - 1.
+ *
+ * @since CUPS 1.7/macOS 10.9@
+ */
+
+void * /* O - Pointer to octetString data */
+ippGetOctetString(
+ ipp_attribute_t *attr, /* I - IPP attribute */
+ int element, /* I - Value number (0-based) */
+ int *datalen) /* O - Length of octetString data */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!attr || attr->value_tag != IPP_TAG_STRING ||
+ element < 0 || element >= attr->num_values)
+ {
+ if (datalen)
+ *datalen = 0;
+
+ return (NULL);
+ }
+
+ /*
+ * Return the values...
+ */
+
+ if (datalen)
+ *datalen = attr->values[element].unknown.length;
+
+ return (attr->values[element].unknown.data);
+}
+
+
+/*
+ * 'ippGetOperation()' - Get the operation ID in an IPP message.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+ipp_op_t /* O - Operation ID or 0 on error */
+ippGetOperation(ipp_t *ipp) /* I - IPP request message */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!ipp)
+ return ((ipp_op_t)0);
+
+ /*
+ * Return the value...
+ */
+
+ return (ipp->request.op.operation_id);
+}
+
+
+/*
+ * 'ippGetRange()' - Get a rangeOfInteger value from an attribute.
+ *
+ * The @code element@ parameter specifies which value to get from 0 to
+ * @code ippGetCount(attr)@ - 1.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+int /* O - Lower value of range or 0 */
+ippGetRange(ipp_attribute_t *attr, /* I - IPP attribute */
+ int element, /* I - Value number (0-based) */
+ int *uppervalue)/* O - Upper value of range */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!attr || attr->value_tag != IPP_TAG_RANGE ||
+ element < 0 || element >= attr->num_values)
+ {
+ if (uppervalue)
+ *uppervalue = 0;
+
+ return (0);
+ }
+
+ /*
+ * Return the values...
+ */
+
+ if (uppervalue)
+ *uppervalue = attr->values[element].range.upper;
+
+ return (attr->values[element].range.lower);
+}
+
+
+/*
+ * 'ippGetRequestId()' - Get the request ID from an IPP message.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+int /* O - Request ID or 0 on error */
+ippGetRequestId(ipp_t *ipp) /* I - IPP message */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!ipp)
+ return (0);
+
+ /*
+ * Return the request ID...
+ */
+
+ return (ipp->request.any.request_id);
+}
+
+
+/*
+ * 'ippGetResolution()' - Get a resolution value for an attribute.
+ *
+ * The @code element@ parameter specifies which value to get from 0 to
+ * @code ippGetCount(attr)@ - 1.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+int /* O - Horizontal/cross feed resolution or 0 */
+ippGetResolution(
+ ipp_attribute_t *attr, /* I - IPP attribute */
+ int element, /* I - Value number (0-based) */
+ int *yres, /* O - Vertical/feed resolution */
+ ipp_res_t *units) /* O - Units for resolution */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!attr || attr->value_tag != IPP_TAG_RESOLUTION ||
+ element < 0 || element >= attr->num_values)
+ {
+ if (yres)
+ *yres = 0;
+
+ if (units)
+ *units = (ipp_res_t)0;
+
+ return (0);
+ }
+
+ /*
+ * Return the value...
+ */
+
+ if (yres)
+ *yres = attr->values[element].resolution.yres;
+
+ if (units)
+ *units = attr->values[element].resolution.units;
+
+ return (attr->values[element].resolution.xres);
+}
+
+
+/*
+ * 'ippGetState()' - Get the IPP message state.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+ipp_state_t /* O - IPP message state value */
+ippGetState(ipp_t *ipp) /* I - IPP message */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!ipp)
+ return (IPP_STATE_IDLE);
+
+ /*
+ * Return the value...
+ */
+
+ return (ipp->state);
+}
+
+
+/*
+ * 'ippGetStatusCode()' - Get the status code from an IPP response or event message.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+ipp_status_t /* O - Status code in IPP message */
+ippGetStatusCode(ipp_t *ipp) /* I - IPP response or event message */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!ipp)
+ return (IPP_STATUS_ERROR_INTERNAL);
+
+ /*
+ * Return the value...
+ */
+
+ return (ipp->request.status.status_code);
+}
+
+
+/*
+ * 'ippGetString()' - Get the string and optionally the language code for an attribute.
+ *
+ * The @code element@ parameter specifies which value to get from 0 to
+ * @code ippGetCount(attr)@ - 1.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+const char *
+ippGetString(ipp_attribute_t *attr, /* I - IPP attribute */
+ int element, /* I - Value number (0-based) */
+ const char **language)/* O - Language code (@code NULL@ for don't care) */
+{
+ ipp_tag_t tag; /* Value tag */
+
+
+ /*
+ * Range check input...
+ */
+
+ tag = ippGetValueTag(attr);
+
+ if (!attr || element < 0 || element >= attr->num_values || (tag != IPP_TAG_TEXTLANG && tag != IPP_TAG_NAMELANG && (tag < IPP_TAG_TEXT || tag > IPP_TAG_MIMETYPE)))
+ return (NULL);
+
+ /*
+ * Return the value...
+ */
+
+ if (language)
+ *language = attr->values[element].string.language;
+
+ return (attr->values[element].string.text);
+}
+
+
+/*
+ * 'ippGetValueTag()' - Get the value tag for an attribute.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+ipp_tag_t /* O - Value tag or @code IPP_TAG_ZERO@ on error */
+ippGetValueTag(ipp_attribute_t *attr) /* I - IPP attribute */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!attr)
+ return (IPP_TAG_ZERO);
+
+ /*
+ * Return the value...
+ */
+
+ return (attr->value_tag & IPP_TAG_CUPS_MASK);
+}
+
+
+/*
+ * 'ippGetVersion()' - Get the major and minor version number from an IPP message.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+int /* O - Major version number or 0 on error */
+ippGetVersion(ipp_t *ipp, /* I - IPP message */
+ int *minor) /* O - Minor version number or @code NULL@ for don't care */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!ipp)
+ {
+ if (minor)
+ *minor = 0;
+
+ return (0);
+ }
+
+ /*
+ * Return the value...
+ */
+
+ if (minor)
+ *minor = ipp->request.any.version[1];
+
+ return (ipp->request.any.version[0]);
+}
+
+
+/*
+ * 'ippLength()' - Compute the length of an IPP message.
+ */
+
+size_t /* O - Size of IPP message */
+ippLength(ipp_t *ipp) /* I - IPP message */
+{
+ return (ipp_length(ipp, 0));
+}
+
+
+/*
+ * 'ippNextAttribute()' - Return the next attribute in the message.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+ipp_attribute_t * /* O - Next attribute or @code NULL@ if none */
+ippNextAttribute(ipp_t *ipp) /* I - IPP message */
+{
+ /*
+ * Range check input...
+ */
+
+ if (!ipp || !ipp->current)
+ return (NULL);
+
+ /*
+ * Return the next attribute...
+ */
+
+ return (ipp->current = ipp->current->next);
+}
+
+
+/*
+ * 'ippNew()' - Allocate a new IPP message.
+ */
+
+ipp_t * /* O - New IPP message */
+ippNew(void)
+{
+ ipp_t *temp; /* New IPP message */
+ _cups_globals_t *cg = _cupsGlobals();
+ /* Global data */
+
+
+ DEBUG_puts("ippNew()");
+
+ if ((temp = (ipp_t *)calloc(1, sizeof(ipp_t))) != NULL)
+ {
+ /*
+ * Set default version - usually 2.0...
+ */
+
+ DEBUG_printf(("4debug_alloc: %p IPP message", (void *)temp));
+
+ if (cg->server_version == 0)
+ _cupsSetDefaults();
+
+ temp->request.any.version[0] = (ipp_uchar_t)(cg->server_version / 10);
+ temp->request.any.version[1] = (ipp_uchar_t)(cg->server_version % 10);
+ temp->use = 1;
+ }
+
+ DEBUG_printf(("1ippNew: Returning %p", (void *)temp));
+
+ return (temp);
+}
+
+
+/*
+ * 'ippNewRequest()' - Allocate a new IPP request message.
+ *
+ * The new request message is initialized with the "attributes-charset" and
+ * "attributes-natural-language" attributes added. The
+ * "attributes-natural-language" value is derived from the current locale.
+ *
+ * @since CUPS 1.2/macOS 10.5@
+ */
+
+ipp_t * /* O - IPP request message */
+ippNewRequest(ipp_op_t op) /* I - Operation code */
+{
+ ipp_t *request; /* IPP request message */
+ cups_lang_t *language; /* Current language localization */
+ static int request_id = 0; /* Current request ID */
+ static _cups_mutex_t request_mutex = _CUPS_MUTEX_INITIALIZER;
+ /* Mutex for request ID */
+
+
+ DEBUG_printf(("ippNewRequest(op=%02x(%s))", op, ippOpString(op)));
+
+ /*
+ * Create a new IPP message...
+ */
+
+ if ((request = ippNew()) == NULL)
+ return (NULL);
+
+ /*
+ * Set the operation and request ID...
+ */
+
+ _cupsMutexLock(&request_mutex);
+
+ request->request.op.operation_id = op;
+ request->request.op.request_id = ++request_id;
+
+ _cupsMutexUnlock(&request_mutex);
+
+ /*
+ * Use UTF-8 as the character set...
+ */
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ /*
+ * Get the language from the current locale...
+ */
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ /*
+ * Return the new request...
+ */
+
+ return (request);
+}
+
+
+/*
+ * 'ippNewResponse()' - Allocate a new IPP response message.
+ *
+ * The new response message is initialized with the same "version-number",
+ * "request-id", "attributes-charset", and "attributes-natural-language" as the
+ * provided request message. If the "attributes-charset" or
+ * "attributes-natural-language" attributes are missing from the request,
+ * 'utf-8' and a value derived from the current locale are substituted,
+ * respectively.
+ *
+ * @since CUPS 1.7/macOS 10.9@
+ */
+
+ipp_t * /* O - IPP response message */
+ippNewResponse(ipp_t *request) /* I - IPP request message */
+{
+ ipp_t *response; /* IPP response message */
+ ipp_attribute_t *attr; /* Current attribute */
+
+
+ /*
+ * Range check input...
+ */
+
+ if (!request)
+ return (NULL);
+
+ /*
+ * Create a new IPP message...
+ */
+
+ if ((response = ippNew()) == NULL)
+ return (NULL);
+
+ /*
+ * Copy the request values over to the response...
+ */
+
+ response->request.status.version[0] = request->request.op.version[0];
+ response->request.status.version[1] = request->request.op.version[1];
+ response->request.status.request_id = request->request.op.request_id;
+
+ /*
+ * The first attribute MUST be attributes-charset...
+ */
+
+ attr = request->attrs;
+
+ if (attr && attr->name && !strcmp(attr->name, "attributes-charset") &&
+ attr->group_tag == IPP_TAG_OPERATION &&
+ attr->value_tag == IPP_TAG_CHARSET &&
+ attr->num_values == 1)
+ {
+ /*
+ * Copy charset from request...
+ */
+
+ ippAddString(response, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, attr->values[0].string.text);
+ }
+ else
+ {
+ /*
+ * Use "utf-8" as the default...
+ */
+
+ ippAddString(response, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+ }
+
+ /*
+ * Then attributes-natural-language...
+ */
+
+ if (attr)
+ attr = attr->next;
+
+ if (attr && attr->name &&
+ !strcmp(attr->name, "attributes-natural-language") &&
+ attr->group_tag == IPP_TAG_OPERATION &&
+ attr->value_tag == IPP_TAG_LANGUAGE &&
+ attr->num_values == 1)
+ {
+ /*
+ * Copy language from request...
+ */
+
+ ippAddString(response, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL,
+ attr->values[0].string.text);
+ }
+ else
+ {
+ /*
+ * Use the language from the current locale...
+ */
+
+ cups_lang_t *language = cupsLangDefault();
+ /* Current locale */
+
+ ippAddString(response, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+ }
+
+ return (response);
+}
+
+
+/*
+ * 'ippRead()' - Read data for an IPP message from a HTTP connection.
+ */
+
+ipp_state_t /* O - Current state */
+ippRead(http_t *http, /* I - HTTP connection */
+ ipp_t *ipp) /* I - IPP data */
+{
+ DEBUG_printf(("ippRead(http=%p, ipp=%p), data_remaining=" CUPS_LLFMT, (void *)http, (void *)ipp, CUPS_LLCAST (http ? http->data_remaining : -1)));
+
+ if (!http)
+ return (IPP_STATE_ERROR);
+
+ DEBUG_printf(("2ippRead: http->state=%d, http->used=%d", http->state, http->used));
+
+ return (ippReadIO(http, (ipp_iocb_t)ipp_read_http, http->blocking, NULL,
+ ipp));
+}
+
+
+/*
+ * 'ippReadFile()' - Read data for an IPP message from a file.
+ *
+ * @since CUPS 1.1.19/macOS 10.3@
+ */
+
+ipp_state_t /* O - Current state */
+ippReadFile(int fd, /* I - HTTP data */
+ ipp_t *ipp) /* I - IPP data */
+{
+ DEBUG_printf(("ippReadFile(fd=%d, ipp=%p)", fd, (void *)ipp));
+
+ return (ippReadIO(&fd, (ipp_iocb_t)ipp_read_file, 1, NULL, ipp));
+}
+
+
+/*
+ * 'ippReadIO()' - Read data for an IPP message.
+ *
+ * @since CUPS 1.2/macOS 10.5@
+ */
+
+ipp_state_t /* O - Current state */
+ippReadIO(void *src, /* I - Data source */
+ ipp_iocb_t cb, /* I - Read callback function */
+ int blocking, /* I - Use blocking IO? */
+ ipp_t *parent, /* I - Parent request, if any */
+ ipp_t *ipp) /* I - IPP data */
+{
+ int n; /* Length of data */
+ unsigned char *buffer, /* Data buffer */
+ string[IPP_MAX_TEXT],
+ /* Small string buffer */
+ *bufptr; /* Pointer into buffer */
+ ipp_attribute_t *attr; /* Current attribute */
+ ipp_tag_t tag; /* Current tag */
+ ipp_tag_t value_tag; /* Current value tag */
+ _ipp_value_t *value; /* Current value */
+
+
+ DEBUG_printf(("ippReadIO(src=%p, cb=%p, blocking=%d, parent=%p, ipp=%p)", (void *)src, (void *)cb, blocking, (void *)parent, (void *)ipp));
+ DEBUG_printf(("2ippReadIO: ipp->state=%d", ipp ? ipp->state : IPP_STATE_ERROR));
+
+ if (!src || !ipp)
+ return (IPP_STATE_ERROR);
+
+ if ((buffer = (unsigned char *)_cupsBufferGet(IPP_BUF_SIZE)) == NULL)
+ {
+ DEBUG_puts("1ippReadIO: Unable to get read buffer.");
+ return (IPP_STATE_ERROR);
+ }
+
+ switch (ipp->state)
+ {
+ case IPP_STATE_IDLE :
+ ipp->state ++; /* Avoid common problem... */
+
+ case IPP_STATE_HEADER :
+ if (parent == NULL)
+ {
+ /*
+ * Get the request header...
+ */
+
+ if ((*cb)(src, buffer, 8) < 8)
+ {
+ DEBUG_puts("1ippReadIO: Unable to read header.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ /*
+ * Then copy the request header over...
+ */
+
+ ipp->request.any.version[0] = buffer[0];
+ ipp->request.any.version[1] = buffer[1];
+ ipp->request.any.op_status = (buffer[2] << 8) | buffer[3];
+ ipp->request.any.request_id = (((((buffer[4] << 8) | buffer[5]) << 8) |
+ buffer[6]) << 8) | buffer[7];
+
+ DEBUG_printf(("2ippReadIO: version=%d.%d", buffer[0], buffer[1]));
+ DEBUG_printf(("2ippReadIO: op_status=%04x",
+ ipp->request.any.op_status));
+ DEBUG_printf(("2ippReadIO: request_id=%d",
+ ipp->request.any.request_id));
+ }
+
+ ipp->state = IPP_STATE_ATTRIBUTE;
+ ipp->current = NULL;
+ ipp->curtag = IPP_TAG_ZERO;
+ ipp->prev = ipp->last;
+
+ /*
+ * If blocking is disabled, stop here...
+ */
+
+ if (!blocking)
+ break;
+
+ case IPP_STATE_ATTRIBUTE :
+ for (;;)
+ {
+ if ((*cb)(src, buffer, 1) < 1)
+ {
+ DEBUG_puts("1ippReadIO: Callback returned EOF/error");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ DEBUG_printf(("2ippReadIO: ipp->current=%p, ipp->prev=%p", (void *)ipp->current, (void *)ipp->prev));
+
+ /*
+ * Read this attribute...
+ */
+
+ tag = (ipp_tag_t)buffer[0];
+ if (tag == IPP_TAG_EXTENSION)
+ {
+ /*
+ * Read 32-bit "extension" tag...
+ */
+
+ if ((*cb)(src, buffer, 4) < 1)
+ {
+ DEBUG_puts("1ippReadIO: Callback returned EOF/error");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ tag = (ipp_tag_t)((((((buffer[0] << 8) | buffer[1]) << 8) |
+ buffer[2]) << 8) | buffer[3]);
+
+ if (tag & IPP_TAG_CUPS_CONST)
+ {
+ /*
+ * Fail if the high bit is set in the tag...
+ */
+
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("IPP extension tag larger than 0x7FFFFFFF."), 1);
+ DEBUG_printf(("1ippReadIO: bad tag 0x%x.", tag));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+ }
+
+ if (tag == IPP_TAG_END)
+ {
+ /*
+ * No more attributes left...
+ */
+
+ DEBUG_puts("2ippReadIO: IPP_TAG_END.");
+
+ ipp->state = IPP_STATE_DATA;
+ break;
+ }
+ else if (tag < IPP_TAG_UNSUPPORTED_VALUE)
+ {
+ /*
+ * Group tag... Set the current group and continue...
+ */
+
+ if (ipp->curtag == tag)
+ ipp->prev = ippAddSeparator(ipp);
+ else if (ipp->current)
+ ipp->prev = ipp->current;
+
+ ipp->curtag = tag;
+ ipp->current = NULL;
+ DEBUG_printf(("2ippReadIO: group tag=%x(%s), ipp->prev=%p", tag, ippTagString(tag), (void *)ipp->prev));
+ continue;
+ }
+
+ DEBUG_printf(("2ippReadIO: value tag=%x(%s)", tag,
+ ippTagString(tag)));
+
+ /*
+ * Get the name...
+ */
+
+ if ((*cb)(src, buffer, 2) < 2)
+ {
+ DEBUG_puts("1ippReadIO: unable to read name length.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ n = (buffer[0] << 8) | buffer[1];
+
+ if (n >= IPP_BUF_SIZE)
+ {
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("IPP name larger than 32767 bytes."), 1);
+ DEBUG_printf(("1ippReadIO: bad name length %d.", n));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ DEBUG_printf(("2ippReadIO: name length=%d", n));
+
+ if (n == 0 && tag != IPP_TAG_MEMBERNAME &&
+ tag != IPP_TAG_END_COLLECTION)
+ {
+ /*
+ * More values for current attribute...
+ */
+
+ if (ipp->current == NULL)
+ {
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("IPP attribute has no name."), 1);
+ DEBUG_puts("1ippReadIO: Attribute without name and no current.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ attr = ipp->current;
+ value_tag = (ipp_tag_t)(attr->value_tag & IPP_TAG_CUPS_MASK);
+
+ /*
+ * Make sure we aren't adding a new value of a different
+ * type...
+ */
+
+ if (value_tag == IPP_TAG_ZERO)
+ {
+ /*
+ * Setting the value of a collection member...
+ */
+
+ attr->value_tag = tag;
+ }
+ else if (value_tag == IPP_TAG_TEXTLANG ||
+ value_tag == IPP_TAG_NAMELANG ||
+ (value_tag >= IPP_TAG_TEXT &&
+ value_tag <= IPP_TAG_MIMETYPE))
+ {
+ /*
+ * String values can sometimes come across in different
+ * forms; accept sets of differing values...
+ */
+
+ if (tag != IPP_TAG_TEXTLANG && tag != IPP_TAG_NAMELANG &&
+ (tag < IPP_TAG_TEXT || tag > IPP_TAG_MIMETYPE) &&
+ tag != IPP_TAG_NOVALUE)
+ {
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
+ _("IPP 1setOf attribute with incompatible value "
+ "tags."), 1);
+ DEBUG_printf(("1ippReadIO: 1setOf value tag %x(%s) != %x(%s)",
+ value_tag, ippTagString(value_tag), tag,
+ ippTagString(tag)));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ if (value_tag != tag)
+ {
+ DEBUG_printf(("1ippReadIO: Converting %s attribute from %s to %s.",
+ attr->name, ippTagString(value_tag), ippTagString(tag)));
+ ippSetValueTag(ipp, &attr, tag);
+ }
+ }
+ else if (value_tag == IPP_TAG_INTEGER ||
+ value_tag == IPP_TAG_RANGE)
+ {
+ /*
+ * Integer and rangeOfInteger values can sometimes be mixed; accept
+ * sets of differing values...
+ */
+
+ if (tag != IPP_TAG_INTEGER && tag != IPP_TAG_RANGE)
+ {
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
+ _("IPP 1setOf attribute with incompatible value "
+ "tags."), 1);
+ DEBUG_printf(("1ippReadIO: 1setOf value tag %x(%s) != %x(%s)",
+ value_tag, ippTagString(value_tag), tag,
+ ippTagString(tag)));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ if (value_tag == IPP_TAG_INTEGER && tag == IPP_TAG_RANGE)
+ {
+ /*
+ * Convert integer values to rangeOfInteger values...
+ */
+
+ DEBUG_printf(("1ippReadIO: Converting %s attribute to "
+ "rangeOfInteger.", attr->name));
+ ippSetValueTag(ipp, &attr, IPP_TAG_RANGE);
+ }
+ }
+ else if (value_tag != tag)
+ {
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
+ _("IPP 1setOf attribute with incompatible value "
+ "tags."), 1);
+ DEBUG_printf(("1ippReadIO: value tag %x(%s) != %x(%s)",
+ value_tag, ippTagString(value_tag), tag,
+ ippTagString(tag)));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ /*
+ * Finally, reallocate the attribute array as needed...
+ */
+
+ if ((value = ipp_set_value(ipp, &attr, attr->num_values)) == NULL)
+ {
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+ }
+ else if (tag == IPP_TAG_MEMBERNAME)
+ {
+ /*
+ * Name must be length 0!
+ */
+
+ if (n)
+ {
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("IPP member name is not empty."), 1);
+ DEBUG_puts("1ippReadIO: member name not empty.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ if (ipp->current)
+ ipp->prev = ipp->current;
+
+ attr = ipp->current = ipp_add_attr(ipp, NULL, ipp->curtag, IPP_TAG_ZERO, 1);
+ if (!attr)
+ {
+ _cupsSetHTTPError(HTTP_STATUS_ERROR);
+ DEBUG_puts("1ippReadIO: unable to allocate attribute.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ DEBUG_printf(("2ippReadIO: membername, ipp->current=%p, ipp->prev=%p", (void *)ipp->current, (void *)ipp->prev));
+
+ value = attr->values;
+ }
+ else if (tag != IPP_TAG_END_COLLECTION)
+ {
+ /*
+ * New attribute; read the name and add it...
+ */
+
+ if ((*cb)(src, buffer, (size_t)n) < n)
+ {
+ DEBUG_puts("1ippReadIO: unable to read name.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ buffer[n] = '\0';
+
+ if (ipp->current)
+ ipp->prev = ipp->current;
+
+ if ((attr = ipp->current = ipp_add_attr(ipp, (char *)buffer, ipp->curtag, tag,
+ 1)) == NULL)
+ {
+ _cupsSetHTTPError(HTTP_STATUS_ERROR);
+ DEBUG_puts("1ippReadIO: unable to allocate attribute.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ DEBUG_printf(("2ippReadIO: name=\"%s\", ipp->current=%p, ipp->prev=%p", buffer, (void *)ipp->current, (void *)ipp->prev));
+
+ value = attr->values;
+ }
+ else
+ {
+ attr = NULL;
+ value = NULL;
+ }
+
+ if ((*cb)(src, buffer, 2) < 2)
+ {
+ DEBUG_puts("1ippReadIO: unable to read value length.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ n = (buffer[0] << 8) | buffer[1];
+ DEBUG_printf(("2ippReadIO: value length=%d", n));
+
+ if (n >= IPP_BUF_SIZE)
+ {
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
+ _("IPP value larger than 32767 bytes."), 1);
+ DEBUG_printf(("1ippReadIO: bad value length %d.", n));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ switch (tag)
+ {
+ case IPP_TAG_INTEGER :
+ case IPP_TAG_ENUM :
+ if (n != 4)
+ {
+ if (tag == IPP_TAG_INTEGER)
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
+ _("IPP integer value not 4 bytes."), 1);
+ else
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
+ _("IPP enum value not 4 bytes."), 1);
+ DEBUG_printf(("1ippReadIO: bad integer value length %d.", n));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ if ((*cb)(src, buffer, 4) < 4)
+ {
+ DEBUG_puts("1ippReadIO: Unable to read integer value.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ n = (((((buffer[0] << 8) | buffer[1]) << 8) | buffer[2]) << 8) |
+ buffer[3];
+
+ if (attr->value_tag == IPP_TAG_RANGE)
+ value->range.lower = value->range.upper = n;
+ else
+ value->integer = n;
+ break;
+
+ case IPP_TAG_BOOLEAN :
+ if (n != 1)
+ {
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("IPP boolean value not 1 byte."),
+ 1);
+ DEBUG_printf(("1ippReadIO: bad boolean value length %d.", n));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ if ((*cb)(src, buffer, 1) < 1)
+ {
+ DEBUG_puts("1ippReadIO: Unable to read boolean value.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ value->boolean = (char)buffer[0];
+ break;
+
+ case IPP_TAG_NOVALUE :
+ case IPP_TAG_NOTSETTABLE :
+ case IPP_TAG_DELETEATTR :
+ case IPP_TAG_ADMINDEFINE :
+ /*
+ * These value types are not supposed to have values, however
+ * some vendors (Brother) do not implement IPP correctly and so
+ * we need to map non-empty values to text...
+ */
+
+ if (attr->value_tag == tag)
+ {
+ if (n == 0)
+ break;
+
+ attr->value_tag = IPP_TAG_TEXT;
+ }
+
+ case IPP_TAG_TEXT :
+ case IPP_TAG_NAME :
+ case IPP_TAG_KEYWORD :
+ case IPP_TAG_URI :
+ case IPP_TAG_URISCHEME :
+ case IPP_TAG_CHARSET :
+ case IPP_TAG_LANGUAGE :
+ case IPP_TAG_MIMETYPE :
+ if (n > 0)
+ {
+ if ((*cb)(src, buffer, (size_t)n) < n)
+ {
+ DEBUG_puts("1ippReadIO: unable to read string value.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+ }
+
+ buffer[n] = '\0';
+ value->string.text = _cupsStrAlloc((char *)buffer);
+ DEBUG_printf(("2ippReadIO: value=\"%s\"", value->string.text));
+ break;
+
+ case IPP_TAG_DATE :
+ if (n != 11)
+ {
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("IPP date value not 11 bytes."), 1);
+ DEBUG_printf(("1ippReadIO: bad date value length %d.", n));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ if ((*cb)(src, value->date, 11) < 11)
+ {
+ DEBUG_puts("1ippReadIO: Unable to read date value.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+ break;
+
+ case IPP_TAG_RESOLUTION :
+ if (n != 9)
+ {
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
+ _("IPP resolution value not 9 bytes."), 1);
+ DEBUG_printf(("1ippReadIO: bad resolution value length %d.", n));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ if ((*cb)(src, buffer, 9) < 9)
+ {
+ DEBUG_puts("1ippReadIO: Unable to read resolution value.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ value->resolution.xres =
+ (((((buffer[0] << 8) | buffer[1]) << 8) | buffer[2]) << 8) |
+ buffer[3];
+ value->resolution.yres =
+ (((((buffer[4] << 8) | buffer[5]) << 8) | buffer[6]) << 8) |
+ buffer[7];
+ value->resolution.units =
+ (ipp_res_t)buffer[8];
+ break;
+
+ case IPP_TAG_RANGE :
+ if (n != 8)
+ {
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
+ _("IPP rangeOfInteger value not 8 bytes."), 1);
+ DEBUG_printf(("1ippReadIO: bad rangeOfInteger value length "
+ "%d.", n));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ if ((*cb)(src, buffer, 8) < 8)
+ {
+ DEBUG_puts("1ippReadIO: Unable to read range value.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ value->range.lower =
+ (((((buffer[0] << 8) | buffer[1]) << 8) | buffer[2]) << 8) |
+ buffer[3];
+ value->range.upper =
+ (((((buffer[4] << 8) | buffer[5]) << 8) | buffer[6]) << 8) |
+ buffer[7];
+ break;
+
+ case IPP_TAG_TEXTLANG :
+ case IPP_TAG_NAMELANG :
+ if (n < 4)
+ {
+ if (tag == IPP_TAG_TEXTLANG)
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
+ _("IPP textWithLanguage value less than "
+ "minimum 4 bytes."), 1);
+ else
+ _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
+ _("IPP nameWithLanguage value less than "
+ "minimum 4 bytes."), 1);
+ DEBUG_printf(("1ippReadIO: bad stringWithLanguage value "
+ "length %d.", n));
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ if ((*cb)(src, buffer, (size_t)n) < n)
+ {
+ DEBUG_puts("1ippReadIO: Unable to read string w/language "
+ "value.");
+ _cupsBufferRelease((char *)buffer);
+ return (IPP_STATE_ERROR);
+ }
+
+ bufptr = buffer;
+
+ /*
+ * text-with-language and name-with-language are composite