root/usr/src/lib/print/libipp-core/common/read.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 */

/* $Id: read.c 146 2006-03-24 00:26:54Z njacobs $ */

#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <inttypes.h>

#include <papi.h>
#include <ipp.h>


#define _ipp_tag_string(id) ipp_tag_string((id), buf, sizeof (buf))

static papi_status_t
read_name_with_language(ipp_reader_t iread, void *fd,
                        papi_attribute_t ***message)
{
        char *string;
        uint16_t size;

        /* read the language */
        if (iread(fd, &size, 2) != 2) {
                ipp_set_status(message, PAPI_BAD_REQUEST,
                                "read failed: lang len\n");
                return (PAPI_BAD_REQUEST);
        }
        size = (uint16_t)ntohs(size);

        if ((string = alloca(size + 1)) == NULL) {
                ipp_set_status(message, PAPI_TEMPORARY_ERROR,
                                "Memory allocation failed");
                return (PAPI_TEMPORARY_ERROR);
        }
        if (iread(fd, string, size) != size) {
                ipp_set_status(message, PAPI_BAD_REQUEST,
                                "read failed: lang\n");
                return (PAPI_BAD_REQUEST);
        }

        /* read the text */
        if (iread(fd, &size, 2) != 2) {
                ipp_set_status(message, PAPI_BAD_REQUEST,
                                "read failed: text len\n");
                return (PAPI_BAD_REQUEST);
        }
        size = (uint16_t)ntohs(size);

        if ((string = alloca(size + 1)) == NULL) {
                ipp_set_status(message, PAPI_TEMPORARY_ERROR,
                                "Memory allocation failed");
                return (PAPI_TEMPORARY_ERROR);
        }
        if (iread(fd, string, size) != size) {
                ipp_set_status(message, PAPI_BAD_REQUEST,
                                "read failed: text\n");
                return (PAPI_BAD_REQUEST);
        }

        return (PAPI_OK);
}


static struct {
        int8_t  ipp_type;
        int8_t  size;
} type_info[] = {
        { VTAG_INTEGER,                 4 },
        { VTAG_ENUM,                    4 },
        { VTAG_BOOLEAN,                 1 },
        { VTAG_RANGE_OF_INTEGER,        8 },
        { VTAG_RESOLUTION,              9 },
        { VTAG_DATE_TIME,               11 },
        { DTAG_MIN,                     0 }
};

/* verify that the IPP type and size are compatible */
static int
validate_length(int8_t type, int8_t size)
{
        int i;

        for (i = 0; type_info[i].ipp_type != DTAG_MIN; i++)
                if (type_info[i].ipp_type == type)
                        return ((type_info[i].size == size) ? 0 : -1);
        return (0);
}

/* convert tyep IPP type to a type that is marginally compatible */
static int8_t
base_type(int8_t i)
{
        switch (i) {
        case VTAG_ENUM:
        case VTAG_INTEGER:
                return (VTAG_INTEGER);
        case VTAG_URI:
        case VTAG_OCTET_STRING:
        case VTAG_TEXT_WITHOUT_LANGUAGE:
        case VTAG_URI_SCHEME:
        case VTAG_CHARSET:
        case VTAG_NATURAL_LANGUAGE:
        case VTAG_MIME_MEDIA_TYPE:
        case VTAG_NAME_WITHOUT_LANGUAGE:
        case VTAG_KEYWORD:
                return (VTAG_TEXT_WITHOUT_LANGUAGE);
        case VTAG_BOOLEAN:
        case VTAG_RANGE_OF_INTEGER:
        case VTAG_DATE_TIME:
        case VTAG_RESOLUTION:
        default:
                return (i);
        }
}

/* verify that the IPP type is correct for the named attribute */
static papi_status_t
validate_type(char *name, int8_t type)
{
        int8_t t = name_to_ipp_type(name);

        if (t == 0)             /* The attribute is not defined in the RFC */
                return (PAPI_NOT_FOUND);
        else if (t == type)     /* The supplied type matched the RFC type */
                return (PAPI_OK);
        else {                  /* The supplied type doesn't match the RFC */
                if (base_type(t) == base_type(type))
                        return (PAPI_OK);

                return (PAPI_CONFLICT);
        }
}

/* verify that the IPP value is within specification for the named attribute */
static int
validate_value(papi_attribute_t ***message, char *name, int8_t type, ...)
{
#define within(a, b, c) ((b >= a) && (b <= c))
        va_list ap;
        int rc = -1;
        int min = min_val_len(type, name),
            max = max_val_len(type, name);
        char buf[64];   /* For _ipp_<...>_string() */

        va_start(ap, type);
        switch (type) {
        case VTAG_ENUM:
        case VTAG_INTEGER: {
                int32_t i = (int32_t)va_arg(ap, int32_t);

                if (within(min, i, max))
                        rc = 0;
                else
                        ipp_set_status(message, PAPI_BAD_ARGUMENT,
                                "%s(%s): %d: out of range (%d - %d)", name,
                                _ipp_tag_string(type), i, min, max);
                }
                break;
        case VTAG_BOOLEAN: {
                int8_t v = (int8_t)va_arg(ap, int);

                if (within(0, v, 1))
                        rc = 0;
                else
                        ipp_set_status(message, PAPI_BAD_ARGUMENT,
                                "%s(%s): %d: out of range (0 - 1)", name,
                                _ipp_tag_string(type), v);
                }
                break;
        case VTAG_RANGE_OF_INTEGER: {
                int32_t lower = (int32_t)va_arg(ap, int32_t);
                int32_t upper = (int32_t)va_arg(ap, int32_t);

                if (within(min, lower, max) &&
                    within(min, upper, max))
                        rc = 0;
                else
                        ipp_set_status(message, PAPI_BAD_ARGUMENT,
                                "%s(%s): %d - %d: out of range (%d - %d)", name,
                                _ipp_tag_string(type), lower, upper, min, max);
                }
                break;
        case VTAG_URI:
        case VTAG_OCTET_STRING:
        case VTAG_TEXT_WITHOUT_LANGUAGE:
        case VTAG_URI_SCHEME:
        case VTAG_CHARSET:
        case VTAG_NATURAL_LANGUAGE:
        case VTAG_MIME_MEDIA_TYPE:
        case VTAG_NAME_WITHOUT_LANGUAGE: {
                char *v = (char *)va_arg(ap, char *);

                if (strlen(v) < max)
                        rc = 0;
                else
                        ipp_set_status(message, PAPI_BAD_ARGUMENT,
                                "%s(%s): %s: too long (max length: %d)", name,
                                _ipp_tag_string(type), v, max);
                }
                break;
        case VTAG_KEYWORD: {
                char *v = (char *)va_arg(ap, char *);

                if (strlen(v) >= max)
                        ipp_set_status(message, PAPI_BAD_ARGUMENT,
                                "%s(%s): %s: too long (max length: %d)", name,
                                _ipp_tag_string(type), v, max);
                else if (is_keyword(v) == 0)
                        ipp_set_status(message, PAPI_BAD_ARGUMENT,
                                "%s(%s): %s: invalid keyword", name,
                                _ipp_tag_string(type), v);
                else
                        rc = 0;
                }
                break;
        case VTAG_DATE_TIME:
        case VTAG_RESOLUTION:
        default:
                rc = 0;
        }
        va_end(ap);

        return (rc);
#undef within
}

/*
 * read_attr_group() reads in enough of the message data to parse an entire
 * attribute group.  Since to determine that the group is finished you have to
 * read the character that determines the type of the next group, this function
 * must return that character, in order that our caller knows how to call us for
 * the next group.  Thus type is used both as an input parameter (the type of
 * attribute group to read in) and an output parameter (the type of the next
 * attribute group).
 */

static papi_status_t
ipp_read_attribute_group(ipp_reader_t iread, void *fd, int8_t *type,
                        papi_attribute_t ***message)
{
        int8_t value_tag;
        uint16_t name_length, value_length;
        papi_attribute_t **attributes = NULL;
        char *name = NULL;
        int i;
        char buf[64];   /* For _ipp_<...>_string() */

        /*
         * RFC2910 3.3 says we need to handle `An expected but missing
         * "begin-attribute-group-tag" field.  How?
         */
        if (*type > DTAG_MAX)  {
                /* Scream bloody murder, or assign a new type? */
                ipp_set_status(message, PAPI_BAD_REQUEST,
                        "Bad attribute group tag 0x%.2hx (%s)",
                        *type, _ipp_tag_string(*type));
                return (PAPI_BAD_REQUEST);
        }

        /* This loops through *values* not *attributes*! */
        for (i = 0; ; i++) {
                papi_status_t valid = PAPI_OK;
                if (iread(fd, &value_tag, 1) != 1) {
                        ipp_set_status(message, PAPI_BAD_REQUEST,
                                "bad read: value tag\n");
                        return (PAPI_BAD_REQUEST);
                }
                /* are we done with this group ? */
                if (value_tag <= DTAG_MAX)
                        break;

                if (iread(fd, &name_length, 2) != 2) {
                        ipp_set_status(message, PAPI_BAD_REQUEST,
                                "bad read: name length\n");
                        return (PAPI_BAD_REQUEST);
                }
                name_length = (uint16_t)ntohs(name_length);

                /* Not just another value for the previous attribute */
                if (name_length != 0) {
                        if ((name = alloca(name_length + 1)) == NULL) {
                                ipp_set_status(message, PAPI_TEMPORARY_ERROR,
                                        "alloca(): failed\n");
                                return (PAPI_TEMPORARY_ERROR);
                        }
                        (void) memset(name, 0, name_length + 1);

                        if (iread(fd, name, name_length) != name_length) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: name\n");
                                return (PAPI_BAD_REQUEST);
                        }
                }

                valid = validate_type(name, value_tag);
                if ((valid != PAPI_OK) && (valid != PAPI_NOT_FOUND))
                        ipp_set_status(message, valid, "%s(%s): %s", name,
                                _ipp_tag_string(value_tag),
                                papiStatusString(valid));

                if (iread(fd, &value_length, 2) != 2) {
                        ipp_set_status(message, PAPI_BAD_REQUEST,
                                "bad read: value length\n");
                        return (PAPI_BAD_REQUEST);
                }
                value_length = (uint16_t)ntohs(value_length);

                if (validate_length(value_tag, value_length) < 0) {
                        ipp_set_status(message, PAPI_BAD_REQUEST,
                                "Bad value length (%d) for type %s",
                                value_length, _ipp_tag_string(value_tag));
                        return (PAPI_BAD_REQUEST);
                }

                switch (value_tag) {
                case VTAG_INTEGER:
                case VTAG_ENUM: {
                        int32_t v;

                        if (iread(fd, &v, value_length) != value_length) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: int/enum\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        v = (int32_t)ntohl(v);
                        (void) validate_value(message, name, value_tag, v);
                        papiAttributeListAddInteger(&attributes,
                                                PAPI_ATTR_APPEND, name, v);

                        }
                        break;
                case VTAG_BOOLEAN: {
                        int8_t v;

                        if (iread(fd, &v, value_length) != value_length) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: boolean\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        (void) validate_value(message, name, value_tag, v);
                        papiAttributeListAddBoolean(&attributes,
                                                PAPI_ATTR_APPEND, name, v);
                        }
                        break;
                case VTAG_RANGE_OF_INTEGER: {
                        int32_t min, max;

                        if (iread(fd, &min, 4) != 4) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: min\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        if (iread(fd, &max, 4) != 4) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: max\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        min = (int32_t)ntohl(min);
                        max = (int32_t)ntohl(max);
                        (void) validate_value(message, name, value_tag,
                                        min, max);
                        papiAttributeListAddRange(&attributes, PAPI_ATTR_APPEND,
                                                name, min, max);
                        }
                        break;
                case VTAG_RESOLUTION: {
                        int32_t x, y;
                        int8_t units;

                        if (iread(fd, &x, 4) != 4) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: x\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        if (iread(fd, &y, 4) != 4) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: y\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        if (iread(fd, &units, 1) != 1) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: units\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        x = (int32_t)ntohl(x);
                        y = (int32_t)ntohl(y);
                        papiAttributeListAddResolution(&attributes,
                                                PAPI_ATTR_APPEND, name, x, y,
                                                (papi_resolution_unit_t)units);
                        }
                        break;
                case VTAG_DATE_TIME: {
                        struct tm tm;
                        time_t v;
                        int8_t c;
                        uint16_t s;

                        (void) memset(&tm, 0, sizeof (tm));
                        if (iread(fd, &s, 2) != 2) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: year\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        tm.tm_year = (uint16_t)ntohs(s) - 1900;
                        if (iread(fd, &c, 1) != 1) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: month\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        tm.tm_mon = c - 1;
                        if (iread(fd, &c, 1) != 1) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: day\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        tm.tm_mday = c;
                        if (iread(fd, &c, 1) != 1) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: hour\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        tm.tm_hour = c;
                        if (iread(fd, &c, 1) != 1) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: minutes\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        tm.tm_min = c;
                        if (iread(fd, &c, 1) != 1) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: seconds\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        tm.tm_sec = c;
                        if (iread(fd, &c, 1) != 1) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: decisec\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        /* tm.deciseconds = c; */
                        if (iread(fd, &c, 1) != 1) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: utc_dir\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        /* tm.utc_dir = c; */
                        if (iread(fd, &c, 1) != 1) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: utc_hour\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        /* tm.utc_hours = c; */
                        if (iread(fd, &c, 1) != 1) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: utc_min\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        /* tm.utc_minutes = c; */

                        v = mktime(&tm);

                        (void) validate_value(message, name, value_tag, v);
                        papiAttributeListAddDatetime(&attributes,
                                                PAPI_ATTR_APPEND, name, v);
                        }
                        break;
                case VTAG_NAME_WITH_LANGUAGE:
                case VTAG_TEXT_WITH_LANGUAGE:
                        /*
                         * we are dropping this because we don't support
                         * name with language at this time.
                         */
                        (void) read_name_with_language(iread, fd, message);
                        break;
                case VTAG_NAME_WITHOUT_LANGUAGE:
                case VTAG_TEXT_WITHOUT_LANGUAGE:
                case VTAG_URI:
                case VTAG_KEYWORD:
                case VTAG_CHARSET: {
                        char *v;

                        if ((v = calloc(1, value_length + 1)) == NULL) {
                                ipp_set_status(message, PAPI_TEMPORARY_ERROR,
                                        "calloc(): failed\n");
                                return (PAPI_TEMPORARY_ERROR);
                        }
#ifdef NOTDEF
                        if (iread(fd, v, value_length) != value_length) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: stringy\n");
                                return (PAPI_BAD_REQUEST);
                        }
#else
                        {
                        int rc, i = value_length;
                        char *p = v;

                        while ((rc = iread(fd, p, i)) != i) {
                                if (rc <= 0) {
                                        ipp_set_status(message,
                                                PAPI_BAD_REQUEST,
                                                "bad read: stringy\n");
                                        return (PAPI_BAD_REQUEST);
                                }
                                i -= rc;
                                p += rc;
                        }
                        }
#endif
                        (void) validate_value(message, name, value_tag, v);
                        papiAttributeListAddString(&attributes,
                                                PAPI_ATTR_APPEND, name, v);
                        }
                        break;
                case VTAG_UNKNOWN:
                case VTAG_NOVALUE:
                case VTAG_UNSUPPORTED:
                        papiAttributeListAddValue(&attributes, PAPI_ATTR_EXCL,
                                        name, PAPI_COLLECTION, NULL);
                        break;
                default: {
                        char *v;

                        if ((v = calloc(1, value_length + 1)) == NULL) {
                                ipp_set_status(message, PAPI_TEMPORARY_ERROR,
                                        "calloc(): failed\n");
                                return (PAPI_TEMPORARY_ERROR);
                        }
                        if (iread(fd, v, value_length) != value_length) {
                                ipp_set_status(message, PAPI_BAD_REQUEST,
                                        "bad read: other\n");
                                return (PAPI_BAD_REQUEST);
                        }
                        papiAttributeListAddString(&attributes,
                                                PAPI_ATTR_APPEND, name, v);
                        }
                        break;
                }
        }

        if (attributes != NULL) {
                char name[32];

                (void) ipp_tag_string(*type, name, sizeof (name));
                papiAttributeListAddCollection(message, PAPI_ATTR_APPEND, name,
                                        attributes);
        }

        *type = value_tag;

        return (PAPI_OK);
}


static papi_status_t
ipp_read_header(ipp_reader_t iread, void *fd, papi_attribute_t ***message,
                char type)
{
        char *attr_name = "status-code";        /* default to a response */
        char buf[8];
        int8_t c;
        uint16_t s;
        int32_t i;

        if ((iread == NULL) || (fd == NULL) || (message == NULL))
                return (PAPI_BAD_ARGUMENT);

        /*
         * Apache 1.X uses the buffer supplied to it's read call to read in
         * the chunk size when chunking is used.  This causes problems
         * reading the header a piece at a time, because we don't have
         * enough room to read in the chunk size prior to reading the
         * chunk.
         */

        if (iread(fd, buf, 8) != 8)
                return (PAPI_BAD_REQUEST);

        c = buf[0];
        (void) papiAttributeListAddInteger(message, PAPI_ATTR_REPLACE,
                                "version-major", c);

        c = buf[1];
        (void) papiAttributeListAddInteger(message, PAPI_ATTR_REPLACE,
                                "version-minor", c);

        memcpy(&s, &buf[2], 2);
        s = (uint16_t)ntohs(s);
        if (type == IPP_TYPE_REQUEST)
                attr_name = "operation-id";
        (void) papiAttributeListAddInteger(message, PAPI_ATTR_REPLACE,
                                attr_name, s);

        memcpy(&i, &buf[4], 4);
        i = (uint32_t)ntohl(i);
        (void) papiAttributeListAddInteger(message, PAPI_ATTR_REPLACE,
                                "request-id", i);

        return (PAPI_OK);
}

static papi_status_t
ipp_read_attribute_groups(ipp_reader_t iread, void *fd,
                        papi_attribute_t ***message)
{
        papi_status_t result = PAPI_OK;
        int8_t tag;

        /* start reading the attribute groups */
        if (iread(fd, &tag, 1) != 1)    /* prime the pump */
                return (PAPI_BAD_REQUEST);

        while ((tag != DTAG_END_OF_ATTRIBUTES) && (result == PAPI_OK)) {
                result = ipp_read_attribute_group(iread, fd, &tag, message);
        }

        return (result);
}

papi_status_t
ipp_read_message(ipp_reader_t iread, void *fd, papi_attribute_t ***message,
                char type)
{
        papi_status_t result = PAPI_OK;

        if ((iread == NULL) || (fd == NULL) || (message == NULL))
                return (PAPI_BAD_ARGUMENT);

        result = ipp_read_header(iread, fd, message, type);
        if (result == PAPI_OK)
                result = ipp_read_attribute_groups(iread, fd, message);

        return (result);
}